Filter Class Verification (IEC 61260-1)
Standards: IEC 61260ANSI S1.11
A filter bank becomes a measuring instrument only once its bands have been proved against a specification. IEC 61260-1:2014 writes that specification as an acceptance mask: a corridor of relative attenuation around each mid frequency, narrow in the passband, opening into a minimum-attenuation requirement far outside the band, with one corridor per performance class. A bank “is class 1” when every band of it stays inside the class 1 corridor at every normalized frequency, and the margin in decibels says by how much.
This page is the verification half of the octave-filtering topic: the 2014 mask and the per-band verdict, the stricter class 0 kept alive by the withdrawn IEC 61260:1995 and ANSI S1.11-2004 masks, a reading of what a class actually buys in a measurement (passband error, stopband leakage, uncertainty budget), and the one-page accredited fiche that turns the verdict into a document. The design half, the band mathematics and the parameter reference, is Filter Banks, and the five architectures with their compared responses are Filter Architecture Gallery; the same machinery applied to the frequency weightings is section 6 of Frequency Weighting.
1. Verifying the class against IEC 61260-1:2014
Section titled “1. Verifying the class against IEC 61260-1:2014”verify_filter_class checks every band of a bank against the acceptance
limits of IEC 61260-1:2014 (Table 1, with the fractional-octave breakpoint
mapping and log-frequency interpolation from the standard) and reports the
performance class per band with its margin in dB:
from phonometry import filters
bank = filters.OctaveFilterBank(fs=48000, fraction=3, order=6)result = filters.verify_filter_class(bank)print(result["overall_class"]) # 1print(result["range_limited"]) # True for a decimated bankprint(result["bands"][0])# {'freq': 12.589254117941678, 'class': 1, 'checked_to_omega': 3.8127755266765493, 'margin_class1_db': 0.3999999999999595, 'margin_class2_db': 0.5999999999999595}How far up the mask the verdict actually reaches. checked_to_omega is the
highest normalized frequency at which that band was evaluated,
and on a multirate bank it is the band’s own decimated Nyquist rather than the
end of the Table 1 mask. On the bank above it runs from 3.81 down to 1.20, so
the far-stopband requirement — at least 70 dB for class 1 beyond roughly
— is not demonstrated on the band filter at all. It is taken as
satisfied by the anti-alias low-pass that precedes the decimation, which
removed that energy before the band ever saw it. range_limited is the flag
that this argument was used, and it is True here.
Say that plainly in a report: the verdict attests the mask up to
checked_to_omega, and the rest is an argument about the decimation chain.
When a document requires the full mask on the band filter itself, design the
same bank with design=filters.FilterDesign(resample=False); its low bands are
then evaluated to of order instead of 4. range_limited stays
True even then, because the top band’s upper edge still approaches Nyquist —
that band is the one the flag is warning about once decimation is out of the
picture.
What the margin measures. It is the minimum, over every normalized frequency
the band was evaluated at, of the distance to the nearest limit of that class:
positive when the response stays inside the corridor everywhere, and negative by
exactly the worst violation when it does not. The reported class is the
strictest class whose margin is non-negative.
Which constraint binds is worth knowing before you try to improve the number. A
maximally-flat Butterworth is flat at mid-band, where the class 1 corridor is
±0.4 dB, so its margin saturates at +0.400 dB the moment the stopband stops
being the limiting factor: measured on a 48 kHz one-third-octave bank, order 2
fails outright (class None, worst class 1 margin −27.03 dB) and orders 4, 6, 8
and 10 all report class 1 with exactly +0.400 dB. Raising the order therefore
helps only while the margin is negative for a stopband reason; once a design
passes, the margin is capped by the passband half-corridor and no order will
move it. A margin of +0.400 dB is not a mediocre result to be improved — it is
the best a compliant design can report against this mask.
The Table 1 acceptance mask itself is public too: class_limits(fraction, filter_class, omega) returns the minimum/maximum relative-attenuation
limits at normalized frequencies , the same limits the
verifier and the figure below use.
The order-6 Butterworth response (blue) threads between the forbidden regions: it must attenuate at least the red mask outside the band and no more than the purple mask inside it.
Show the code for this figure
import matplotlib.pyplot as pltimport numpy as npfrom scipy.signal import sosfreqzfrom phonometry import filters
fs = 48000bank = filters.OctaveFilterBank(fs, fraction=1, order=6, limits=[800, 1200])idx = int(np.argmin(np.abs(np.array(bank.freq) - 1000)))fm, fsd = bank.freq[idx], fs / bank.factor[idx]w, h = sosfreqz(bank.sos[idx], worN=2**15, fs=fsd)att = -20 * np.log10(np.abs(h) + 1e-12)delta_a = att - np.interp(fm, w, att) # relative attenuation
grid = np.logspace(np.log10(0.05), np.log10(8), 2000)lo1, hi1 = filters.class_limits(1.0, 1, grid) # class 1 min/max attenuation
fig, ax = plt.subplots(figsize=(9, 5.5))ax.fill_between(grid, -10, lo1, alpha=0.15, color="tab:red", label="Forbidden: too little attenuation")finite = np.isfinite(hi1)ax.fill_between(grid[finite], hi1[finite], 90, alpha=0.15, color="tab:purple", label="Forbidden: too much attenuation")ax.plot(w / fm, delta_a, label="Butterworth order 6")ax.set(xscale="log", xlim=(0.08, 8), ylim=(-6, 90), xlabel="Normalized frequency f / fm", ylabel="Relative attenuation [dB]")ax.legend()plt.show()With default parameters (order 6), Butterworth meets class 1, and so does
Chebyshev II: its attenuation default is now 72 dB, clearing the 70 dB
far-stopband class 1 limit (scipy pins the cheby2 equiripple floor at exactly
attenuation, so any value qualifies; the 72 dB default
keeps the same +0.400 dB passband margin as Butterworth). Chebyshev I,
Elliptic and Bessel do
not meet class limits at order 6: passband ripple (cheby1/ellip) and slow
roll-off (bessel) violate the mask. Class 1 is the strictest verdict this
edition can return; the stricter class 0 the default bank also clears belongs to
the withdrawn 1995 mask and is section 2.
2. Class 0 (IEC 61260:1995 / ANSI S1.11-2004)
Section titled “2. Class 0 (IEC 61260:1995 / ANSI S1.11-2004)”The tightest performance class, class 0, was defined by the earlier
IEC 61260:1995 and its US twin ANSI S1.11-2004 (both withdrawn/superseded
but still referenced for laboratory-grade instruments); IEC 61260-1:2014 dropped
it. Its class 1/2 masks differ slightly from the 2014 edition, so it lives behind
an edition switch rather than being mixed into the 2014 mask:
from phonometry import filters
fs = 48000bank = filters.OctaveFilterBank(fs, fraction=1, order=6, limits=[800, 1200])
result = filters.verify_filter_class(bank, edition="1995") # classes 0, 1, 2print(result["overall_class"]) # 0 (the default Butterworth clears it)print(result["bands"][0]["margin_class0_db"])The class 0 corridor (±0.15 dB at mid-band) is the tightest; class 1 (±0.3 dB) and class 2 (±0.5 dB) are progressively wider. The order-6 Butterworth threads inside class 0 across the whole pass-band.
Show the code for this figure
import matplotlib.pyplot as pltimport numpy as npfrom scipy.signal import sosfreqzfrom phonometry import filters
fs = 48000bank = filters.OctaveFilterBank(fs, fraction=1, order=6, limits=[800, 1200])idx = int(np.argmin(np.abs(np.array(bank.freq) - 1000)))fm, fsd = bank.freq[idx], fs / bank.factor[idx]w, h = sosfreqz(bank.sos[idx], worN=2**15, fs=fsd)att = -20 * np.log10(np.abs(h) + 1e-12)delta_a = att - np.interp(fm, w, att)
# Pass-band only: outside the band edges the maximum limit is +inf.g = 10 ** (3 / 10)grid = np.linspace(g ** -0.5, g ** 0.5, 1500)pb = (w / fm >= g ** -0.5) & (w / fm <= g ** 0.5)
fig, ax = plt.subplots(figsize=(9, 5.5))for cls in (2, 1, 0): # nested corridors, class 0 tightest lo, hi = filters.class_limits(1.0, cls, grid, edition="1995") ax.plot(grid, hi, label=f"Class {cls} corridor") ax.plot(grid, lo, color=ax.lines[-1].get_color())ax.plot(w[pb] / fm, delta_a[pb], "k", lw=2, label="Butterworth order 6")ax.set(xscale="log", xlim=(g ** -0.5, g ** 0.5), ylim=(-0.7, 6), xlabel="Normalized frequency f / fm", ylabel="Relative attenuation [dB]")ax.legend()plt.show()3. What a class means physically
Section titled “3. What a class means physically”The masks are worst-case error bounds on a measurement, not abstract grades:
-
In the passband the corridor bounds how much the band can mis-read in-band content: a class 1 bank reads a mid-band tone within ±0.4 dB of its true level and a class 2 bank within ±0.6 dB (IEC 61260-1:2014 Table 1; the stricter IEC 61260:1995 Table 1 masks allowed ±0.3 dB for class 1, ±0.5 dB for class 2 and ±0.15 dB for class 0). Toward the band edges the corridor widens, which is the honest admission that a tone sitting exactly on an edge is genuinely ambiguous between two bands (both read it about 3 dB down).
-
In the stopband the minimum-attenuation mask bounds leakage from the rest of the spectrum: far from the band, class 1 demands at least 70 dB of relative attenuation (the reason the
cheby2default is 72 dB). In energy terms, an out-of-band tone must be roughly 70 dB stronger than the band’s own content before it doubles the band’s energy reading (+3 dB). The practical consequence: measuring bands far below a dominant tone, the reading floors out at the leakage skirt about 70 dB down, and a steeper architecture (or higher order) is the only way to push that floor lower.What the last sentence looks like as a measurement. Every band whose level sits on the skirt is reporting the filter’s rejection of the 1 kHz tone, not the sound present in that band — the dotted line is what is really there. The test is not subtle once you look for it: raise the order and the bands that are measuring the filter move, while the bands that are measuring the sound do not.
-
For the uncertainty budget, the class is the filter’s contribution to the measurement uncertainty: a class 1 bank adds up to a few tenths of a dB to a band level, comparable to a class 1 sound level meter’s other tolerance terms, which is why instrument-grade chains specify the class of every stage rather than a single overall figure.
Which architecture reaches which class? Under the 2014 edition, whose only
classes are 1 and 2, the library’s default Butterworth order-6 bank meets
class 1 with a +0.400 dB binding margin — that is the ceiling, and section 1
explains why. Against the stricter 1995 / ANSI S1.11-2004 mask
(edition="1995") the same default reaches class 0; the configuration the
conformance suite verifies at that class is the octave-band bank at 48 kHz,
so re-run verify_filter_class(bank, edition="1995") yourself before writing
class 0 into a document for any other fraction or sample rate. Writing “class 0
per IEC 61260-1:2014” is a claim against a class that edition does not define.
The table reports the best class each architecture reaches at order 6, fs 48 kHz, under the 1995 mask; the architectures other than Butterworth fall short because they trade the IEC mask for a different property by construction:
| Architecture | Best class (order 6, fs 48 kHz, edition="1995") | Why |
|---|---|---|
butter (default) | 0 | Maximally-flat pass-band, monotone roll-off; fits the mask |
cheby2 | 1 | Flat pass-band but the mask relationship binds at class 1 |
cheby1 | — | Pass-band ripple violates the flatness limit |
ellip | — | Pass- and stop-band ripple |
bessel | — | Flat group delay bought with a slow roll-off |
Under the 2014 edition the same ranking reads 1 / 1 / — / — / —: cheby2 joins
Butterworth at the top because class 0 no longer exists to separate them.
The verdicts of the table, drawn. Chebyshev I and Elliptic poke through the
limit just inside the band edges, where their ripple lives; Bessel leaves it
along the skirt, because it never falls fast enough. The red samples are the
ones verify_filter_class counted against the design, and this is exactly what
filter_class_compliance(bank).plot() draws for a bank of your own — note that
the plot shades the corridor of the class each design came closest to, so the
three failing panels show the class 2 corridor.
Show the code for this figure
import matplotlib.pyplot as plt
# `filters` is the import of the snippets above.fs = 48000fig, axes = plt.subplots(2, 2, figsize=(12, 8))for ax, ftype in zip(axes.ravel(), ("butter", "cheby1", "ellip", "bessel")): bank = filters.OctaveFilterBank( fs, fraction=1, order=6, limits=[800, 1200], design=filters.FilterDesign(filter_type=ftype)) result = filters.filter_class_compliance(bank) result.plot(ax=ax) ax.set_title(f"{ftype}: overall_class = {result.overall_class}")plt.tight_layout()plt.show()So the sensible default is the common one (Butterworth order 6), while the
alternative architectures are deliberate opt-ins whose purpose (steeper
roll-off, linear phase) works against the class mask. Away from these settings
(very high fraction or near-Nyquist bands), always re-run
verify_filter_class to confirm the class you need.
3b. Verifying an instrument, not a design
Section titled “3b. Verifying an instrument, not a design”verify_filter_class answers a question about a design: does this transfer
function fit the mask. A laboratory answers a different question about a
device on a date, and the two verdicts are not interchangeable.
The full Table 1 walk belongs to pattern evaluation (IEC 61260-2:2016), which a filter-set model passes once. What a working instrument actually receives is the periodic test of IEC 61260-3:2016, and it is narrower on purpose: the relative attenuation is measured at the exact midband frequency of every filter in the set, through the electrical input on the reference level range, together with the effective-bandwidth deviation from a swept-frequency test, the linear operating range with its level-range control and overload behaviour, and the lower limit of that range — all under stated environmental conditions and with traceable test equipment. That is why a certificate carries a date, a temperature and a set of serial numbers, and why it says nothing about the parts of the mask it did not walk.
What carries over is worth stating in a report: a class verdict from this page is inherited by every measurement made with the library’s filters, and a hardware chain adds a verdict of its own. Name both. The equivalent regime for sound level meters — IEC 61672-3 periodic tests, and IEC 60942 for the calibrator — is in Calibration and dBFS.
4. The compliance fiche (.report())
Section titled “4. The compliance fiche (.report())”filter_class_compliance(bank) wraps the same verification as a result object
that exposes .plot() and .report(), so a type-test verdict can be rendered
as a one-page accredited fiche. The fiche lists every band’s achieved class and
its binding margin, overlays the worst-margin band’s measured relative
attenuation on the class corridor, and boxes the overall class-compliance
result. Pass a required_class on the ReportMetadata to add a PASS/FAIL
verdict row (a bank “meets class N” when its achieved class is at least as
strict, i.e. a class index of N or lower). The fiche renders in English by
default; pass language="es" for a Spanish fiche (translated fixed strings and
a comma decimal separator), e.g.
result.report("iec61260_es.pdf", language="es").
from phonometry import ( OctaveFilterBank, ReportMetadata, filter_class_compliance,)
bank = OctaveFilterBank(fs=48000, fraction=1, order=6, limits=[125, 4000])result = filter_class_compliance(bank) # overall_class == 1result.plot() # the worst-margin band on its class corridor
result.report( "iec61260.pdf", metadata=ReportMetadata( specimen="1/1-octave filter bank", measurement_standard="IEC 61260-1:2014", required_class=1, # class 1 (or stricter) required ),) # -> Class 1 - COMPLIES, PASSThe example fiche is regenerated with make reports and kept rendered in the
repository; click the preview to open the PDF.

One-page filter-class-compliance fiche: a metadata header, a per-band classification table listing each octave band's achieved class and binding margin, the worst-margin band's measured relative attenuation overlaid on the green class-1 acceptance corridor, the boxed Class 1 - COMPLIES (margin +0.40 dB) result and a PASS verdict against the required class 1.
Passing edition="1995" verifies against the older IEC 61260:1995 /
ANSI S1.11-2004 mask, which keeps the stricter class 0 that the 2014 edition
dropped, so a modest order-6 bank can be certified to class 0:
bank = OctaveFilterBank(fs=48000, fraction=1, order=6, limits=[250, 4000])result = filter_class_compliance(bank, edition="1995") # overall_class == 0result.plot() # the class-0 corridor of the 1995 editionresult.report("iec61260_1995.pdf", metadata=ReportMetadata(measurement_standard="IEC 61260:1995", required_class=0)) # -> Class 0 - COMPLIES
One-page filter-class-compliance fiche under the 1995 edition: a per-band classification table showing every octave band achieving class 0, the measured relative attenuation overlaid on the green class-0 acceptance corridor, the boxed Class 0 - COMPLIES (margin +0.15 dB) result and a PASS verdict against the required class 0.
What this guide covers
Section titled “What this guide covers”Covered
The IEC 61260-1:2014 Table 1 class 1 / class 2 acceptance limits (with the fractional-octave breakpoint mapping and the log-frequency interpolation of the standard), checked band by band by
verify_filter_classand published as a mask byclass_limits; the withdrawn IEC 61260:1995 / ANSI S1.11-2004 class 0 mask, reachable withedition="1995"; and the accredited one-page fiche offilter_class_compliance().report(), with its optionalrequired_classPASS/FAIL verdict, in English and Spanish.Not covered
IEC 61260-1’s conformance tests for the physical filter itself (overload recovery, filter linearity, environmental influences) apply to hardware analog and digital filters and are not implemented:
verify_filter_classchecks the designed digital response against Table 1, not an instrument. Those tests belong to IEC 61260-2:2016 (pattern evaluation) and IEC 61260-3:2016 (periodic tests), which section 3b summarizes and this page does not run. Near Nyquist, the bilinear transform warps the frequency axis and the bank has no correction for it, so the stopband mask beyond the processing Nyquist is reported asrange_limitedrather than verified: keep the top band edge comfortably below Nyquist or raisefs.
See also
Section titled “See also”- Filter Banks: the band mathematics and the architectures whose class is verified here.
- Frequency Weighting: the companion verification of the A, C and Z curves against the IEC 61672-1 tolerance tables, with the G, B, D and AU curves covered in Special Weightings.
- Sound Level Meter: the instrument chain whose spectrum stage this class applies to.
- Conformance report: the verified configurations behind the class claims of this page.
- API reference:
filters.compliance. - Theory: Octave Band Frequencies: the band-edge definitions the IEC 61260-1 mask is drawn around.
Quick answers
Section titled “Quick answers”Which filter architecture meets IEC 61260-1 class 1 with default settings?
Section titled “Which filter architecture meets IEC 61260-1 class 1 with default settings?”With the default order 6, Butterworth meets class 1 of the IEC 61260-1:2014
Table 1 acceptance limits, and so does Chebyshev II: its default
attenuation of 72 dB clears the 70 dB far-stopband class 1 limit.
Chebyshev I, Elliptic and Bessel do not: passband ripple (cheby1, ellip)
and slow roll-off (bessel) violate the mask. verify_filter_class reports
the achieved class per band.
What is class 0 and which standard defines it?
Section titled “What is class 0 and which standard defines it?”Class 0 is the tightest filter performance class, defined by IEC 61260:1995
and its US twin ANSI S1.11-2004 and dropped by IEC 61260-1:2014. Its
passband corridor allows only ±0.15 dB at mid-band, against ±0.3 dB for
class 1 in the 1995 masks. It stays available through edition="1995", and the
default order-6 Butterworth bank meets class 0 in the configuration the
conformance report verifies, the octave-band bank at 48 kHz. Under
IEC 61260-1:2014 that same bank is class 1: the 2014 edition defines no class 0,
so a class 0 claim must cite the 1995 / ANSI S1.11-2004 mask it was measured
against.
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. Its Table 1 class limits are identical to those of IEC 61260:1995 and back the same class 0 mask.
- International Electrotechnical Commission. (1995). Electroacoustics — Octave-band and fractional-octave-band filters (IEC 61260:1995). The withdrawn first edition whose Table 1 supplies the stricter class 0 mask offered by edition='1995'.
- International Electrotechnical Commission. (2014). Electroacoustics — Octave-band and fractional-octave-band filters — Part 1: Specifications (IEC 61260-1:2014). The Table 1 class 1 / class 2 acceptance limits verified here, with the fractional-octave breakpoint mapping and the log-frequency interpolation of the standard.
- International Electrotechnical Commission. (2016). Electroacoustics — Octave-band and fractional-octave-band filters — Part 2: Pattern-evaluation tests (IEC 61260-2:2016). The pattern-evaluation regime a filter-set model passes once: the full Table 1 mask walked on a physical device, which verify_filter_class does not perform.
- International Electrotechnical Commission. (2016). Electroacoustics — Octave-band and fractional-octave-band filters — Part 3: Periodic tests (IEC 61260-3:2016). The periodic tests a working analyser receives: midband relative attenuation of every filter, effective bandwidth, linear operating range and its lower limit, under stated environmental conditions.