Filter Banks
Standards: IEC 61260ANSI S1.11ISO 266Key references: Oppenheim & Schafer 2010
phonometry supports several filter types, each with its own transfer function characteristic. Butterworth, Chebyshev II and Bessel place their −3 dB points on the ANSI S1.11 band edges, so their band levels are directly comparable. The two equiripple designs (Chebyshev I, Elliptic) do not: they take those edges as the ripple edge instead, which widens the band and biases every band level by a fixed few tenths of a decibel — see section 1, because the bias is systematic and does not average out.
By the end of this page you can design a bank for any fraction, read every argument off one table, cascade RBJ EQ sections, pull out per-band time signals and filter with zero phase. Section 1 is the mathematics the design rests on; if you only want to call the function, start at section 2. How the five architectures differ is Filter Architecture Gallery, and what “class 1” means for the result is Filter Class Verification.
1. Fractional octave bands: the math
Section titled “1. Fractional octave bands: the math”IEC 61260-1:2014 builds every band from the base-10 octave ratio (so “one octave” is not exactly 2). For band fraction , the mid frequencies and band edges follow (5.2-5.5):
so every 1/3-octave band spans : ten bands per decade, which is why the nominal frequencies (25, 31.5, 40 …) repeat scaled by 10. phonometry designs each band as an SOS cascade on those edges, but where the −3 dB point ends up depends on what the architecture means by its design frequency, and only three of the five put it on and .
The two forms exist so that the reference frequency 1000 Hz is a band mid
frequency when is odd and a band edge when is even. That is what
keeps the odd fractions nested — each octave band is exactly three
one-third-octave bands — while the even fractions split at the octave
boundaries; the edge formulas are the same in both cases. The practical
consequence is easy to trip over: a fraction=2, 12 or 24 spectrum has no
1 kHz band at all, its nominal labels come from the Annex E.3 significant-figure
rounding rather than from the ISO 266 preferred series, and comparing it band by
band against a one-third-octave table is meaningless.
Where each architecture’s −3 dB point actually lands
Section titled “Where each architecture’s −3 dB point actually lands”Butterworth’s SciPy design frequency is its −3 dB point, so it needs no
correction. Chebyshev II is designed through its stopband edges, mapped back
from the band edges analytically, and Bessel is normalized with norm="mag"
rather than SciPy’s default phase normalization — both corrections exist so that
their −3 dB points land on the edges too. Chebyshev I and Elliptic are handed
the band edges directly, and for those two families SciPy reads them as the
equiripple passband edge: the response there is the ripple depth, not −3 dB.
Measured on the 1 kHz octave band at 48 kHz with order 6 and the default
ripple=0.1:
| Architecture | Response at and | Noise bandwidth / | Band-level offset |
|---|---|---|---|
butter | −3.01 dB | 1.003 | reference |
cheby2 | −3.01 dB | 1.003 | −0.02 dB |
bessel | −3.01 dB | 0.971 | +0.05 dB |
cheby1 | −0.10 dB | 1.066 | +0.33 dB |
ellip | −0.10 dB | 1.052 | +0.25 dB |
The first three columns are the 1 kHz octave band; the last is the median
band-level difference from Butterworth over a whole 1/3-octave bank analysing
20 s of white noise. That last column matters because it is a bias, not
scatter: it does not shrink with averaging time, it is larger than the whole
class 1 passband corridor (±0.4 dB), and it moves every band at once, so a
spectrum measured with cheby1 sits bodily above one measured with butter.
Choose one architecture per campaign, and never mix architectures inside one
spectrum. Raising order shrinks the offset, because a narrower transition band
pulls the −3 dB point back toward the ripple edge — for cheby1 the median goes
+0.76 dB at order 4, +0.33 dB at order 6, +0.16 dB at order 8 and +0.04 dB at
order 12. Lowering design.ripple does the opposite of what intuition suggests
and makes the offset larger (+0.62 dB at ripple=0.02), because at fixed
order a flatter passband buys a slower roll-off.
Poles, zeros and stability
Section titled “Poles, zeros and stability”A digital band-pass filter is a constellation of poles and zeros in the z-plane: zeros at or near DC and Nyquist pin the response down far from the band (to the stopband floor, for equiripple designs), and the poles cluster just inside the unit circle at the angles the passband spans. Two intuitions follow. First, selectivity is proximity: the closer the poles sit to the unit circle, the sharper the band and the longer the filter rings (the group-delay peaks of section 4 are that ringing, measured). Second, stability is a margin, not a property of the architecture: an IIR filter is stable only while every pole stays strictly inside the unit circle, and a narrow band at a high sample rate pushes the poles outward (pole radius for bandwidth ) and squeezes them together, until double-precision coefficients can no longer represent their positions accurately. Second-order sections (SOS) defuse half of the problem: each pole pair keeps its own coefficients, so rounding errors stay local instead of compounding through one high-order polynomial. The other half, the tiny ratio itself, is what decimation fixes.
Selectivity is proximity, and the price is conditioning. At the full rate the 25 Hz band’s poles sit less than from the unit circle and on top of each other; the same band realized at its decimated rate has them spread out and 500 times further away. The right panel is the design decision as one curve: the multirate bank keeps every band in the same well-conditioned region, whatever its centre frequency.
How this figure was measured
Both banks come from the parameter table of section 2, one with the default
design and one with design=FilterDesign(resample=False). The pole positions
are read straight off the bank: bank.sos[idx] is the SOS cascade of band
idx and scipy.signal.sos2zpk turns it into zeros, poles and gain, so the
distance from the unit circle is 1 - abs(poles).max(). For the 25.1 Hz band
of a 48 kHz one-third-octave bank that distance is 8.73e-05 at the full
rate and 4.62e-02 at the decimated rate the bank actually uses
(bank.factor[idx] is 500, so the band runs at 96 Hz). SciPy raises
BadCoefficients while reading the full-rate design — that warning is the
argument of this section, printed by the library that hit it.
Multirate decimation
Section titled “Multirate decimation”A 25 Hz one-third-octave band at 48 kHz spans about 5.8 Hz, 0.024 % of Nyquist, with coefficients so stiff they go numerically unstable. The bank avoids that by filtering low bands at a decimated rate:
Decimating by rescales the problem: the same 5.8 Hz bandwidth becomes times larger relative to the new Nyquist, the pole radius pulls away from the unit circle, and the SOS coefficients return to a well-conditioned range. The price is bookkeeping the bank pays internally: an anti-alias low-pass must run before every decimation stage, because a component above the new Nyquist that folds down lands inside the low bands being measured, and no later filter can remove it.
Aliasing pitfalls
Section titled “Aliasing pitfalls”The bank protects its own decimation stages, but it can only analyze what the capture chain delivered:
- Fold-down at the ADC. Energy above that reaches the converter without an analog anti-alias filter folds into the analysis range and is indistinguishable from real in-band sound. Sound cards filter this internally; custom instrumentation chains may not.
- Cheap resampling. Converting a 44.1 kHz recording to 48 kHz with a
low-quality resampler leaves images that bias the highest bands. Use a
polyphase resampler (
scipy.signal.resample_poly) or, simpler, analyze at the native rate: every phonometry function takesfsdirectly. - Bands near Nyquist. A band whose upper edge approaches cannot
realize its design response: the bilinear transform compresses the
frequency axis there (the same effect the weighting filters counter with
high_accuracy). Keep the top band edge comfortably below Nyquist or raisefs, and letverify_filter_classreport how much margin is left.
How long must the record be?
Section titled “How long must the record be?”A band level is not read off a signal, it is estimated from it, and the narrower the band the longer that takes. The bandwidth of a -octave band is , which for one-third octaves is , and two consequences follow from it.
The filter has to settle: a band rings for a few times , so that much of the front of the record is transient rather than level and must be discarded — about a second in the 12.5 Hz band, twelve milliseconds at 1 kHz. And the estimate carries a random error of about dB on noise-like sound, so a standard deviation of 0.4 dB needs :
| Lowest band reported | [Hz] | for a 0.4 dB standard deviation |
|---|---|---|
| 12.5 Hz | 2.9 | 35 s |
| 125 Hz | 29 | 3.5 s |
| 1 kHz | 231 | 0.4 s |
The rule that follows is one line: the lowest band you intend to report sets
the record length for the whole bank. Analysing a 1 s clip down to 12.5 Hz
and reporting the result to one decimal is not a measurement of that band, and
neither octave_filter nor OctaveFilterBank warns you. The general
law is derived in
Correlation, time delay and envelope;
the same arithmetic applied per frame instead of per record is the
window_time note of
Integrated and Statistical Levels.
The band mathematics above is shared by every architecture. How the architectures actually differ, the comparison at the −3 dB crossover, the full 1/1 and 1/3 octave response gallery and the usage examples per architecture, up to the Linkwitz-Riley crossover, is Filter Architecture Gallery.
2. octave_filter() / OctaveFilterBank parameters
Section titled “2. octave_filter() / OctaveFilterBank parameters”The everyday arguments come first and positionally — x, fs, fraction,
order, limits — and everything else is grouped into four small frozen
dataclasses passed by keyword: design (FilterDesign), calibration
(LevelCalibration), block_processing (BlockProcessing) and
response_plot (ResponsePlot). Being frozen, they are hashable, which is
what lets equal option bundles hit the same design cache. The table below names
each option by the bundle it travels in.
Three call sites share this table, and they do not accept the same options, so
the Where column names the one each option belongs to: octave_filter() is
the one-shot function, OctaveFilterBank(...) is the constructor, and
bank.filter() is the per-call method.
| Parameter | Where | Type | Units | Range / default | Notes |
|---|---|---|---|---|---|
x | function, .filter() | 1D or 2D array | digital units | non-empty | 2D is [channels, samples] |
fs | function, constructor | int | Hz | > 0 | |
fraction | function, constructor | int | — | default 1; common 3; any | Bands per octave = |
order | function, constructor | int | — | default 6 | SOS order per band |
limits | function, constructor | list [lo, hi] | Hz | default [12, 20000] | Analysis range |
design.filter_type | function, constructor | str | — | 'butter' (default), 'cheby1', 'cheby2', 'ellip', 'bessel' | See the Filter Architecture Gallery |
design.ripple / design.attenuation | function, constructor | float | dB | ripple default 0.1; attenuation default 72.0 | Passband ripple / stopband attenuation (cheby/ellip); cheby2 needs attenuation for class 1, since scipy pins its equiripple floor at exactly this value |
design.resample | function, constructor | bool | — | default True | Filter each band on a decimated rate (multirate) |
response_plot.show | function, constructor | bool | — | default False | Plot the bank response (needs matplotlib) |
response_plot.file | function, constructor | str or None | — | default None | Save the bank-response plot to this path |
calibration.factor | function, constructor | float | — | default 1.0 | Scales the input to pascals (see the Calibration guide) |
calibration.dbfs | function, constructor | bool | — | default False | Reference levels to digital full scale instead of 20 µPa |
block_processing.stateful / .steady_ic | constructor only | bool | — | default False | Streaming state; see Block Processing |
sigbands | function, .filter() | bool | — | default False | Also return the per-band time signals |
mode | function, .filter() | str | — | 'rms' (default) or 'peak' | Per-band statistic returned |
nominal | function, .filter() | bool | — | default False | Return nominal band labels (e.g. 1000) instead of exact centre frequencies |
detrend | function, .filter() | bool | — | default True | Remove each band’s DC offset before the level (improves low-frequency accuracy) |
zero_phase | .filter() only | bool | — | default False | Forward-backward filtering (offline); see section 5 |
calculate_level | .filter() only | bool | — | default True | False returns the band signals without computing levels (use with sigbands=True) |
The split is not arbitrary: the one-shot octave_filter() designs a bank and
uses it once, so it carries the design-time options only. zero_phase and
calculate_level are decided per call and therefore live on
OctaveFilterBank.filter() — octave_filter(x, fs, zero_phase=True) raises
TypeError — while streaming state is fixed when the bank is built.
mode has exactly two values. 'rms' reports the energy-mean level of the band
over the whole record, which is what every standard means by “band level”.
'peak' reports of the largest absolute sample inside the band,
against the same 20 µPa reference; it is an instantaneous band peak, not a
peak-hold reading, it is band-limited and unweighted so it is not the
C-weighted peak of the
Levels guide, and on impulsive signals it
is dominated by the filter’s own ringing — it belongs to transient diagnostics,
not to level reporting. There is no 'sum' mode: a total across bands is an
energy sum the caller performs, 10*np.log10(np.sum(10**(spl/10))), never an
arithmetic mean of decibels.
verify_filter_class(bank) checks the designed bank against the IEC 61260-1
Table 1 acceptance limits and reports the class (1, 2 or None if outside both) with per-band
margins.
What order buys, and where it stops buying
Section titled “What order buys, and where it stops buying”order is the order of each band-pass section pair, so it sets how fast the
skirts fall away and therefore how much of a neighbouring band leaks into a
band level. Order 6 is the default because it clears the IEC 61260-1 mask with
room to spare for Butterworth, and the arithmetic is worth seeing: at 48 kHz on
a one-third-octave Butterworth bank, order 2 misses class 1 by 27.03 dB on the
far stopband, while orders 4, 6, 8 and 10 all pass with a class 1 margin of
exactly +0.40 dB.
That identical margin is not a coincidence and it is the useful half of the rule. From order 4 upward the binding constraint has moved out of the stopband and into the passband, where a maximally flat design sits at 0 dB deviation and the class 1 corridor is ±0.4 dB — so raising the order further buys stopband depth but no class margin at all. It does cost: every extra pole pair lengthens the ringing (the group-delay peaks of section 4 grow with order), which is why transient work and short-window spectrograms prefer low orders. The margin arithmetic itself is Filter Class Verification.
3. Parametric EQ (ParametricEQ)
Section titled “3. Parametric EQ (ParametricEQ)”Biquad equalizer sections per the RBJ Audio EQ Cookbook
(Bristow-Johnson): peaking (bell), low/high shelf, low/high-pass, band-pass
(constant 0 dB peak or constant skirt gain), notch and all-pass, each
parameterized by fs, f0, gain_db and one of q, bw (bandwidth in
octaves) or slope exactly as the cookbook defines them. Sections cascade
as a numerically robust SOS chain, and the design is closed-form exact: a
peaking section passes exactly gain_db at f0 and exactly 0 dB at DC and
Nyquist, shelves land exactly on gain_db at their shelved end, and the
all-pass has unit magnitude everywhere (only the phase turns).
import numpy as npfrom phonometry import EQSection, ParametricEQ
fs = 48000rng = np.random.default_rng(1)x = rng.standard_normal(fs) # one second of noise
eq = ParametricEQ(fs, [ EQSection("lowshelf", 100.0, gain_db=4.0), EQSection("peaking", 1000.0, gain_db=-6.0, bw=1.0), # one-octave cut EQSection("highshelf", 8000.0, gain_db=3.0),])y = eq.filter(x) # apply the cascaderes = eq.response() # frozen result carrying the SOS cascadeaxes = res.plot() # magnitude + phase of the cascadeFor block processing pass stateful=True (the same convention as
WeightingFilter); the one-shot helper is parametric_eq(x, fs, sections).
The three sections of the snippet (grey) and the cascade they sum to; the
lower panel is the phase the magnitude costs. Any result in the library redraws
itself this way — res.plot() above produced exactly this figure.
Show the code for this figure
import matplotlib.pyplot as pltfrom phonometry import EQSection, ParametricEQ
fs = 48000family = [ EQSection("peaking", 1000.0, gain_db=6.0, q=1.4), EQSection("lowshelf", 125.0, gain_db=6.0), EQSection("highshelf", 4000.0, gain_db=-6.0), EQSection("lowpass", 10000.0), EQSection("highpass", 50.0), EQSection("bandpass", 500.0, q=2.0), EQSection("notch", 2000.0, q=6.0),]fig, ax = plt.subplots(figsize=(10, 6))for section in family: res = ParametricEQ(fs, [section]).response(f_min=20.0, f_max=20000.0) ax.semilogx(res.frequencies, res.magnitude_db, label=f"{section.filter_type} @ {section.f0:g} Hz")ax.set(xlim=(20, 20000), ylim=(-27, 9), xlabel="Frequency [Hz]", ylabel="Magnitude [dB]")ax.grid(True, which="both", alpha=0.3)ax.legend(loc="lower center", ncols=2, fontsize=9)plt.show()Everything above is design. Proving that a designed bank meets a performance class of IEC 61260-1, band by band and with its margin in decibels, is Filter Class Verification: the Table 1 acceptance mask, the stricter class 0 of the withdrawn 1995 edition, what a class buys in a measurement, and the accredited compliance fiche.
4. Band signals and group delay
Section titled “4. Band signals and group delay”sigbands=True returns the per-band time signals alongside the levels, which
is what makes phase and transient behaviour visible — and what exposes the
group delay each architecture pays for its selectivity. Stability itself is
section 1’s subject, under
Poles, zeros and stability; what this section
adds is the evidence, because a filter whose impulse response decays to zero is
a stable filter.
import numpy as npfrom phonometry import filters
# 1. Generate a signal (Sum of 250Hz and 1000Hz)fs = 48000t = np.linspace(0, 0.5, int(fs * 0.5), endpoint=False)y = np.sin(2 * np.pi * 250 * t) + np.sin(2 * np.pi * 1000 * t)
# 2. Compare architectures (Butterworth vs Chebyshev II)spl_b, freq, xb_butter = filters.octave_filter( y, fs=fs, fraction=1, sigbands=True, design=filters.FilterDesign(filter_type='butter'))spl_c2, _, xb_cheby2 = filters.octave_filter( y, fs=fs, fraction=1, sigbands=True, design=filters.FilterDesign(filter_type='cheby2'))
# 'xb_butter' and 'xb_cheby2' contain the time-domain signals per bandThe top panel is the input, the middle panels are its band signals under Butterworth (solid) and Chebyshev II (dashed), and the bottom panel is the impulse response of the 1 kHz band of each. Both impulse responses decay to zero — that is the stability check — but the Chebyshev II ring lasts visibly longer, which is the transient price of its steeper skirt.
Show the code for this figure
import matplotlib.pyplot as pltimport numpy as npfrom phonometry import filters
fs = 48000t = np.linspace(0, 0.5, int(fs * 0.5), endpoint=False)y = np.sin(2 * np.pi * 250 * t) + np.sin(2 * np.pi * 1000 * t)
bank_b = filters.OctaveFilterBank(fs=fs, fraction=1, order=6, limits=[100.0, 2000.0])bank_c = filters.OctaveFilterBank(fs=fs, fraction=1, order=6, limits=[100.0, 2000.0], design=filters.FilterDesign(filter_type="cheby2"))_, freq, xb_butter = bank_b.filter(y, sigbands=True)_, _, xb_cheby2 = bank_c.filter(y, sigbands=True)
# The published figure has two extra panels: the input on top and, at the# bottom, the 1 kHz band of a unit impulse through each bank.impulse = np.zeros_like(t)impulse[0] = 1.0_, _, imp_butter = bank_b.filter(impulse, sigbands=True)_, _, imp_cheby2 = bank_c.filter(impulse, sigbands=True)one_k = int(np.argmin(np.abs(np.asarray(freq) - 1000.0)))
fig, axes = plt.subplots(len(freq) + 2, 1, figsize=(9, 2 * (len(freq) + 2)), sharex=True)axes[0].plot(t, y)axes[0].set_title("Original signal (250 Hz + 1000 Hz)")for ax, fc, xb, xc in zip(axes[1:-1], freq, xb_butter, xb_cheby2): ax.plot(t, xb, label="Butterworth") ax.plot(t, xc, "--", label="Chebyshev II") ax.set_title(f"{fc:.0f} Hz band")axes[-1].plot(t, imp_butter[one_k], label="Butterworth")axes[-1].plot(t, imp_cheby2[one_k], "--", label="Chebyshev II")axes[-1].set_title("Impulse response, 1 kHz band")axes[1].legend()axes[0].set_xlim(0, 0.04)axes[-1].set_xlabel("Time [s]")plt.tight_layout()plt.show()Group delay, quantified
Section titled “Group delay, quantified”The group delay of the 1 kHz octave band shows the trade-off directly: Bessel stays nearly flat across the passband (transient shapes survive), while Chebyshev I and Elliptic pay for their steep roll-off with strong delay peaks at the band edges.
Show the code for this figure
import matplotlib.pyplot as pltimport numpy as npfrom scipy.signal import group_delayfrom phonometry import filters
fs = 48000w = np.logspace(np.log10(500), np.log10(2000), 1024)fig, ax = plt.subplots(figsize=(9, 5))for ftype in ("butter", "cheby1", "cheby2", "ellip", "bessel"): bank = filters.OctaveFilterBank(fs, fraction=1, order=6, limits=[800, 1200], design=filters.FilterDesign(filter_type=ftype)) idx = int(np.argmin(np.abs(np.array(bank.freq) - 1000))) fsd = fs / bank.factor[idx] # Group delay of an SOS cascade = sum of the sections' group delays gd = sum(group_delay((sec[:3], sec[3:]), w=w, fs=fsd)[1] for sec in bank.sos[idx]) ax.semilogx(w, gd / fsd * 1000, label=ftype)ax.set(xlim=(500, 2000), xlabel="Frequency [Hz]", ylabel="Group delay [ms]")ax.grid(True, which="both", alpha=0.3)ax.legend()plt.show()5. Zero-phase filtering
Section titled “5. Zero-phase filtering”For offline analysis you can eliminate group delay entirely:
OctaveFilterBank.filter(…, zero_phase=True) filters each band
forward-backward (scipy.signal.sosfiltfilt), keeping band signals
time-aligned with the input. It is a per-call option on the bank, not an
argument of the one-shot octave_filter(). The effective attenuation doubles and the
effective passband narrows, lowering the measured broadband band level by
~0.2 to 0.3 dB per band (a pure in-band tone is unaffected); prefer forward
filtering when the absolute band SPL must match single-pass conventions, and
reserve zero-phase for when the temporal envelope matters (e.g. reverberation
decay). The option is incompatible with stateful (block) processing.
import numpy as npfrom phonometry import filters
fs = 48000t = np.linspace(0, 0.5, int(fs * 0.5), endpoint=False)y = np.sin(2 * np.pi * 250 * t) + np.sin(2 * np.pi * 1000 * t)
bank = filters.OctaveFilterBank(fs=48000, fraction=3)spl, freq, xb = bank.filter(y, sigbands=True, zero_phase=True)Causal filtering delays the burst by the filter’s group delay; zero-phase filtering keeps it aligned with the input.
Show the code for this figure
import matplotlib.pyplot as pltimport numpy as npfrom phonometry import filters
fs = 48000t = np.linspace(0, 0.15, int(fs * 0.15), endpoint=False)x = np.zeros_like(t) # 250 Hz tone burst mid-framestart, end = int(0.05 * fs), int(0.10 * fs)x[start:end] = np.sin(2 * np.pi * 250 * t[start:end]) * np.hanning(end - start)
bank = filters.OctaveFilterBank(fs=fs, fraction=1, order=6, limits=[200.0, 300.0])_, _, fwd = bank.filter(x, sigbands=True, calculate_level=False)_, _, zp = bank.filter(x, sigbands=True, calculate_level=False, zero_phase=True)
fig, ax = plt.subplots(figsize=(9, 4.5))ax.plot(t, x, color="gray", alpha=0.5, label="Input burst (250 Hz)")ax.plot(t, fwd[0], label="Causal (group delay)")ax.plot(t, zp[0], "--", label="zero_phase=True (aligned)")ax.set(xlabel="Time [s]", ylabel="Amplitude")ax.legend()plt.show()What this guide covers
Section titled “What this guide covers”Covered
IEC 61260-1:2014’s band-edge mathematics (clauses 5.2-5.5) and the ISO 266:1997 preferred-frequency series behind
nominal_frequencies. The library designs each band as an SOS cascade across five architectures (Butterworth, Chebyshev I/II, Elliptic, Bessel), plus the RBJ Audio EQ Cookbook’sParametricEQ, with band decomposition and zero-phase filtering.Not covered
The compared responses of the five architectures, the full 1/1 and 1/3 octave gallery and the per-architecture usage examples, including the Linkwitz-Riley crossover, are Filter Architecture Gallery. The Table 1 class acceptance masks and their verification have moved to Filter Class Verification. Near Nyquist, the bilinear transform warps the frequency axis and the bank has no correction for it (unlike
WeightingFilter’shigh_accuracyoption): keep the top band edge comfortably below Nyquist or raisefs, and confirm the margin withverify_filter_class.
See also
Section titled “See also”- Filter Architecture Gallery: the five architectures compared at the band edges, the full response gallery and per-architecture usage, with the Linkwitz-Riley crossover.
- Filter Class Verification (IEC 61260-1): the Table 1 acceptance mask, class 0 and the compliance fiche of the banks designed here.
- API reference:
phonometry,filters.coreandfilters.weighting. - Theory: Octave Band Frequencies: where the base-10 midband grid and the band edges come from, and why the bank is designed on them rather than on nominal labels.
Quick answers
Section titled “Quick answers”How are fractional-octave centre frequencies and band edges defined?
Section titled “How are fractional-octave centre frequencies and band edges defined?”IEC 61260-1:2014 (clauses 5.2-5.5) builds every band from the base-10 octave ratio , so one octave is not exactly 2. Mid frequencies follow for odd and for even — so 1 kHz is a band mid frequency in the odd fractions and a band edge in the even ones — and the edges are and in both cases; every one-third-octave band spans , ten bands per decade.
References
Section titled “References”- American National Standards Institute. (2004). Specification for octave-band and fractional-octave-band analog and digital filters (ANSI S1.11-2004). Acoustical Society of America. The band-edge convention on which every bank of this guide places its −3 dB points.
- Bristow-Johnson, R. (2021). Audio EQ Cookbook. W3C Working Group Note (ed. R. Toy), 8 June 2021. The biquad coefficient recipes and the Q / bandwidth / shelf-slope parameterization behind ParametricEQ (section 3).
- International Electrotechnical Commission. (2014). Electroacoustics — Octave-band and fractional-octave-band filters — Part 1: Specifications (IEC 61260-1:2014). The band-edge mathematics of section 1 (base-10 mid frequencies and band edges, clauses 5.2-5.5) and the nominal band labels behind every bank designed here.
- International Organization for Standardization. (1997). Acoustics — Preferred frequencies (ISO 266:1997). The preferred-frequency series behind the nominal band labels reported by nominal_frequencies.
- Oppenheim, A. V., & Schafer, R. W. (2010). Discrete-time signal processing (3rd ed.). Pearson. The pole-zero, stability and multirate theory condensed in section 1: SOS cascades, the bilinear transform and decimation (ISBN 978-0-13-198842-2).
- Smith, J. O. (n.d.). Introduction to digital filters with audio applications. Center for Computer Research in Music and Acoustics (CCRMA), Stanford University. A free online-book companion treatment of digital-filter design and analysis, from pole-zero geometry to filter stability.