feat: psychoacoustic fluctuation strength (ECMA-418-2:2025 clause 9, Sottek Hearing Model) (#216)
* feat: psychoacoustic fluctuation strength per ECMA-418-2:2025 Clause 9 (Sottek Hearing Model)
Clean-room implementation of the Clause 9 chain on the shared Clause 5
auditory front-end: fixed 65536/16384 segmentation, downsampled Hilbert
envelopes, envelope-dependent analysis windows with quieter-period
detection, High-resolution Spectral Analysis (least-squares window-kernel
line-pair fits with the closed-form single-pair solution), prominent-line
selection, band-pass modulation-rate weighting, damped-Newton rate fine
tuning, harmonic-complex analysis, HSA-based loudness scaling and the
50 Hz specific fluctuation strength with the tabulated calibration
constant c_F = 0.003840572.
The Clause 9 calibration signal (1 kHz, 100 % AM at 4 Hz, overall
60 dB SPL) converges to 0.9958 vacil against the defined 1 vacil without
reverse fitting; the standard's qualitative behaviours (4 Hz band-pass,
zero in the roughness domain, growth with modulation depth) are pinned in
tests together with machine-precision HSA recovery anchors and unit tests
of the analysis-window machinery. The result dataclass exposes .plot();
the renderer shares one helper with the roughness heatmap plot.
* feat(conformance): ECMA-418-2 Clause 9 fluctuation-strength calibration check
Adds the Clause 9 anchor to the Psychoacoustics domain: the 1 kHz / 4 Hz /
m=1 / overall 60 dB calibration signal must compute 1 vacil_HMS within
0.01 with the tabulated c_F (computed: 0.9931 for the 5 s check signal).
262/262 checks pass.
* feat(figures): slow vs fast modulation band-pass demo (fluctuation strength vs roughness)
One figure for the concept Clause 9 adds next to Clause 7: the Sottek
model splits envelope modulation into a slow band-pass peaking near
4-5 Hz (fluctuation strength, vacil) and a fast one peaking near 70 Hz
(roughness, asper), computed for the same 1 kHz / 100 % AM / 60 dB tone
family. Four language/theme variants; ES strings added to the
translation table.
* docs: fluctuation strength guide (EN/ES), Clause 9 errata and API reference
Extends the sound-quality guide (docs and site, English and Spanish) with
the Clause 9 metric: concept, runnable calibration example, the new
modulation band-pass figure with its double snippet, parameter table and
cross-links from the Fastl & Zwicker fluctuation-strength page. Records
four confirmed print defects of ECMA-418-2:2025 Clause 9 in docs/ERRATA.md
(Formula (127) kernel phase, Formula (144) bin offset, unit-less Newton
constants of Clause 9.1.7, broken cross-reference in the introduction).
Adds the module to the API taxonomy, regenerates the Starlight API
reference and updates the curated API table, indices and changelog.
* refactor(psychoacoustics): harden fluctuation_strength_ecma and unify vacil_HMS units
- Replace np.polyfit with the closed-form linear regression in the
Clause 9.1.3.6 validity check (per-block, per-band hot path): outputs
are bit-identical (calibration anchor 0.9931 vacil_HMS unchanged) and
the fit is about 10x faster (106 us to 11 us per call).
- Validate inputs: reject non-finite samples and a non-finite fs via the
shared require_positive helper, and clamp the internal resample length
to at least one sample.
- Preserve matplotlib's c= color alias in the shared HMS plot renderer;
the fill now uses the plotted line's resolved color.
- Unify the normative ECMA-418-2 Clause 9 unit as vacil_HMS across the
module docstrings, plot labels, guides (EN/ES), API reference, README
and changelog; plain vacil stays for the Fastl & Zwicker model. The
documented example values match fresh runs: 0.9931 (5 s), 0.9957 (8 s),
0.9958 (12 s).
- Move signal construction out of pytest.raises blocks and add tests for
non-finite signals, non-finite fs, the resample clamp and the color
alias. Regenerate the site API reference and llms files.
* fix(figures): label the fluctuation-strength annotation and axis in vacil_HMS
Correct the framing and the guards of the perception and noise-control fiches (#355)
A review pass over the psychoacoustics, hearing, speech, environmental and
noise-control fiches. The numerics held up; what did not were the statements
printed around them, plus one wrong table lookup.
ISO 1999 validated domain. nipts()/htlan() applied Formula (2) wherever they
were asked to: L_EX,8h = 130 dB over 60 years at the 0,99 fractile returned a
357,9 dB threshold shift with nothing marking it as an extrapolation. Since the
formula is quadratic in (L_EX,8h - L0) that number is meaningless, not merely
uncertain. Both now warn (NoiseInducedHearingLossWarning) outside the stated
ranges while still computing: durations outside 1-40 years (clause 6.3.1
validates Formula (2) over 10-40 and Formula (3) over 1-10), fractiles in the
tails clause 6.3.2 says "should not be estimated" (Q below 5 % or above 95 %),
and levels above the 100 dB of Annex D that the Scope's NOTE 4 restricts
validity to. The fiches print the caveat when the conditions sit outside.
ISO 1999 Q. The fiches printed the library's fractile under ISO 1999's symbol,
inverting its meaning: in Formulae (4)/(5) Q is the percentage with worse
hearing, so the most-susceptible tenth is Q = 10 %, not the "Q = 0.9" the fiche
showed while citing 6.3.2. They now print ISO's Q as ISO defines it; the guides
note the difference from the fractile argument. Two further disclosures on the
same fiches: the age component H is evaluated from ISO 7029:2017 (the edition
ISO 1999 references undated in 6.2.2), which departs from the illustrative
Table A.3 selection by up to about 7 dB at the median, and the 2/3/4 kHz
average is the user's own choice, the Scope's NOTE 1 specifying no frequency
combination. The footer states a population scope rather than "the results
relate only to the tested specimen", which describes a laboratory specimen.
ISO 1999 Annex C is wired in as an independent oracle: its worked example (male
population aged 50, 90 dB for 30 years, 1/2/4 kHz at Q = 10 %) reproduces the
Table D.2 shifts it consumes (0, 9, 19 dB), the 13,3 dB left of the 4 kHz shift
after the Formula (1) compression (C.5) and the 31,1 dB combined threshold
(C.11). The composition it exercises is now exposed as combine_age_and_noise(),
which clause 6.2 wants available on its own: 6.2.3 recommends a database B
collected on a control population of the country under consideration, and this
applies Formula (1) to whichever database the user brings.
Verdict precision. The STI and SII fiches printed the requirement with one
decimal beside a measured value at full precision, so a verdict could contradict
itself: "STI = 0.50, required >= 0.5 -> FAIL" against a 0,52 requirement, and an
SII minimum of 0,75 printed as "0.8" (the committed example said so). Both now
print the requirement at the quantity's own precision. IEC 60268-16 Annex M
joins the conformance report as an end-to-end oracle: its printed MTF matrix,
speech spectrum and ambient noise give back its published MTI row and
STI = 0,76, exercising the whole A.5.3 to A.5.8 chain.
Noise-control prediction framing. The enclosure, silencer and HVAC fiches
compute design models but read as measurements: no prediction statement,
measurement headers, a footer scoped to a tested specimen, and committed
examples citing instrumentation that never existed ("Two-microphone
transfer-matrix bench"). They now follow the EN/ISO 12354 prediction exemplar
already in the repo, with a prediction-basis line, a statement naming what each
model does not represent, and a prediction footer. Their verdicts also decided
on the half-away-from-zero display rounding while printing Python's
round-half-to-even, so 0,25 printed 0.2 against a verdict taken on 0,3; both
now use the display rounding.
ISO/PAS 1996-3. With every onset rate at or below 10 dB/s the fiche withheld
the adjustment on the onset-rate gate (clauses 4.5/8) but justified it with "no
prominent impulse is present (governing P = 6.01 <= 5)", contradicting its own
boxed P. The note now states whichever gate governs. The assessment period is
read from the result instead of being hardcoded: 30 min is the Clause 5
*default*, now the default of the new assessment_period_min argument.
Smaller corrections:
- the HVAC elbow insertion loss took the wrong row of Bies Table 8.11 for a
W/lambda exactly on a bin edge; the rows read "a <= W/lambda < b", so an edge
opens its row (1 dB at 0,14, not 0 dB);
- the wind-turbine tonality fiche cited "subclauses 9.5.2-9.5.5" of
IEC 61400-11 for a chain running through 9.5.6 to 9.5.8 (Formulae 31-34);
- the ISO 1996-2 tonal fiche notes that Table J.1 keys on the mean audibility
of the J assessed spectra (ISO/PAS 20065 Clause 5.3.9), which a
single-spectrum assessment stands in for;
- the shared 10-90 % fractile band carried a hardcoded English legend in
Spanish figures, and the Spanish silencer and HVAC fiches printed the device
kind and duct-element label in English.
docs/ERRATA.md records two more source defects found on the way: ISO/PAS
1996-3:2022 Clause 5 swaps the 3.4/3.5 cross-references of the onset rate and
the level difference (its own units contradict the clause numbers printed with
them), and ISO 9613-2:1996 Table 2 prints 4,1 dB/km at 15 C / 80 % / 1 kHz
where ISO 9613-1 gives 4,1511, the neighbouring cells rounding correctly. The
library computes that coefficient from ISO 9613-1 directly, so it is unaffected.
Committed example fiches, the conformance report, the API reference and the
llms files are regenerated.
fix: psychoacoustics standards-correctness fixes with corrected calibrations and printed oracles (#172)
* test: repair the ISO 532-1 Annex B fixtures path and extend the Annex B coverage
The relative data path was not adjusted when the test file moved into
tests/psychoacoustics/, so all 21 Annex B validation tests had been
silently skipping through the requires_iso_data guard. Point DATA at
tests/data/iso532_1 again and add a hard in-repo presence assertion so a
future move fails loudly instead of re-skipping.
Also enable the shipped Annex B.4 Test signal 13 (Nmax reproduces the
workbook header to 6e-6 relative), add a bounds-only Annex B.3 pink-noise
check against the workbook tolerance band, and note why the B.4 ramp
signals 6-9 cannot be regenerated.
* fix: ECMA-418-2 roughness front-end applies the Clause 5.1.2 fade-in and the calibration hits 1 asper
The roughness path skipped the mandatory 5 ms trigonometric fade-in of
Formula (1), which loudness and tonality already apply; recordings cut
mid-sound picked up an onset artefact that survived the transient discard
through the asymmetric smoother. The fade-in is now a shared helper used
by all three metrics.
With the fade-in in place and the Clause 7 reference signal synthesized at
its stated level (the overall RMS SPL of the modulated signal, 60 dB, not
the carrier-alone level), the chain reproduces the 1 asper calibration to
0.99990 asper using the tabulated c_R = 0.0180685. The former +7.35 %
'clean-room methodology variance' narrative was wrong and is removed from
the docstring, tests, reference data, conformance check and docs; the test
and conformance now pin the true 1.000 asper anchor.
A code note in the segmentation records why the printed Clause 5.1.5.2
l_last formula cannot be implemented literally (blocks would overrun the
padded signal): the flush-to-end reading is the only self-consistent one.
* fix: psychoacoustic annoyance combines the weightings per F&Z Eq. (16.2)
The combination formula had the '1 +' inside the radical,
N5*sqrt(1 + wS^2 + wFR^2), where Fastl & Zwicker (2006, p. 328) print
PA = N5*(1 + sqrt(wS^2 + wFR^2)). Every PA value with a nonzero
sharpness, fluctuation or roughness contribution was underestimated by
13 to 29 percent, and the worked reference value had been hand-computed
with the same wrong form, so tests and conformance confirmed the bug.
The worked tuple (N5,S,F,R) = (30, 2.0, 0.5, 0.3) now anchors
PA = 37.0478 (wS and wFR unchanged), and the docs, site guides and API
reference show the corrected formula. The SQAT claim is now accurate:
SQAT implements the (1 + sqrt) combination.
* fix: fluctuation-strength Bark constant 0.76e-3 per Zwicker-Terhardt (Osses 2016 Eq. 3 misprint)
Osses/Garcia/Kohlrausch (2016) Eq. (3) prints 0.76e-4 in the critical-band
rate formula, a 10x typo of the Zwicker-Terhardt 0.76e-3 disproved by the
paper's own anchors (0.5 Bark = 50 Hz, 15 Bark = 2.7 kHz, 23.5 Bark =
13.2 kHz). As transcribed, the 47 filter centres spanned 491 Hz-20 kHz with
12 bands pinned at the grid edge, distorting the specific pattern for any
signal away from 1 kHz (0.56 instead of 0.86 vacil at a 125 Hz carrier,
1.10 instead of 0.58 at 8 kHz).
The self-derived calibration constant lands at C_FS = 0.279, close to the
paper's 0.2490 and inside the sanity guard. The re-fitted H(fmod) corners
did not compensate the bug: the 1 kHz modulation sweep is essentially
unchanged (16 Hz point 0.19 -> 0.17 vacil), so the corners stand as fitted.
New oracles: the carrier-frequency sweep (125 Hz-8 kHz) against measured
reference values and the F&Z Fig. 10.5 trend (low-mid plateau, 8 kHz
roll-off), and the Osses Table 1 AM-broadband-noise row as a trend-only
cross-check (the excitation front-end overshoots the BBN pass-band by up
to ~3x, now documented; the FM row is deliberately not pinned). The module
docstring also records the front-end deviations from the paper and that
the normative ECMA-418-2 Clause 9 fluctuation strength is a different,
unimplemented method.
* fix: ECMA-418-2 loudness uses the full Clause 6.2.3 cross-block-size averaging
The loudness path averaged neighbouring-band ACFs only within a block-size
group, skipping the cross-group recomputation Clause 6.2.3 mandates for
bands adjacent to a block-size change. Since Clause 8.1.1 builds loudness
on the Clause 6.2 outputs, the requirement applies to loudness identically;
tonal content near the block-size boundaries was mis-stated by up to
-9.5 % (166.7 Hz tone at the z=2 edge).
The full averaging now lives in loudness_ecma as a single shared
_tonal_noise_split (front-end + Clause 6.2 tonal/noise decomposition) that
both the loudness and tonality metrics call, so the two report an
identical N'_tonal(l, z) for the same signal -- asserted by a new
cross-consistency test that spies on the shared intermediate through both
call sites.
The 1 kHz / 40 dB calibration moves from 0.9960 to 0.9845 sone_HMS. The
-1.55 % residual exceeds the +/-0.25 % adjustment allowed for c_N, which
is kept at the verbatim tabulated value; the investigation (documented in
the module docstring) attributes it to the mandated band averaging around
the block-size-boundary bands excited by the tone's lower flank: no band
averaging reads 0.955, same-group-only averaging 0.996, full cross-group
averaging 0.9845, while block-time smoothing and the fade-in/LP transient
contribute under 0.01 %.
Also in this pass: the common time grid is sized from the 1024-sample
stage's block count instead of the 8192 group's (whose 7 extra trailing
blocks only produced edge-held interpolations), the benign max(.,0) clamp
that Formula 48 does not print is documented in place, _band_range
validates the Formulae 56/57 preconditions (16 Hz < f_L, f_H < 20 kHz,
f_L < f_H) instead of silently swapping reversed edges, and the three
Sottek metrics document their mono-only scope (no Formulae 112/118
binaural combination).
* feat: ISO 532-1 stationary TimeSkip parameter and reference-program provenance notes
Annex B.1 requires the stationary calculation to start from 0.2 s when
validating against the official Annex B recordings (the reference
f_square_and_smooth takes a TimeSkip and the CLI requires it); the
stationary path previously always averaged the whole signal, so leading
silence or the filterbank transient diluted the mean square. The new
time_skip parameter (default 0.0, validated in both modes, applied by the
stationary method only) implements it.
Documentation nits from the same review: the sone-to-phon mapping records
that the exact 10/lg 2 factor and the 3 phon floor come from the
electronic attachment's reference program rather than the printed
Formula (2), the percentile note records the same provenance for the
(k-1, k) mean, and the filterbank tables are labelled correctly (Table A.1
is the reference section; the deviations and gains are Table A.2).
* fix: sharpness Annex B variants use the literal k = 0.11; ISO 532-2/-3 oracle expansion
DIN 45692 Formulas (B.1)/(B.2) print S = 0,11 * moment for the Aures and
von Bismarck variants; the implementation had derived per-variant
constants so the clause 6 reference sound read exactly 1.00 acum, shifting
every Aures result +4.2 % against other implementations of the published
formulas. With the literal 0.11 the reference lands near (not exactly at)
1 acum: 0.96 Aures / 1.02 von Bismarck, asserted non-circularly at
1.00 +/- 0.05. The normative clause 5.2 k remains derived, as the standard
prescribes.
Sharpness verification now covers all 21 Table A.2 rows and all 20
Table A.3 broadband rows (band edges per Tabelle A.1, every signal set to
the clause 6 loudness of 4 sone) at the normative 5 % / 0.05 acum
tolerance, and the conformance report gains a non-definitional Table A.2
row (2.5 kHz -> 1.78 acum) beside the definitional reference-signal check.
ISO 532-3: the module docstring now states the native-fs conformance mode
(clause 5 prescribes a 32 kHz conversion; this implementation processes at
the native rate with per-window FFT lengths) and the Annex C.1 anchor is
pinned at 32/44.1/48 kHz (0.9918/0.9926/0.9900 with a 0.5 s tone, 0.3 %
cross-rate spread). New oracles: the Annex C.3 multi-tone complexes, the
Annex C.1.2/C.1.3 sone columns, the ISO 532-2 Annex B phon columns, and a
note marking the byte-identical anchor pin as a regression pin rather than
a conformance oracle. The Table 5 phon mapping documents its silent
saturation at 120 phon.
* feat: ISO/PAS 20065 extended uncertainty of the audibility (Clauses 5.4 and 6)
Implements the Gaussian propagation of the uniform 3 dB narrow-band level
uncertainty through the audibility chain (Formulae (22)-(27)) to the
extended uncertainty U with 90 % bilateral coverage (k = 1.645,
Formula (29)), plus the Formula (28) combination for the mean audibility.
analyze_spectrum now reports the per-tone U on the result object, and the
docstrings carry the Clause 6 requirement that U shall be considered when
fewer than 12 spectra are averaged.
Oracles: the printed Table E.2 U column (the decisive 137.3 Hz tone
reproduces 2.79 dB to 0.006 dB from the E.1 lines; the flanking tones
within 0.1 dB, their critical bands extending beyond the truncated table),
the 2 FG row (3.21 dB, using the N summated tone levels as the K summands,
the reading that reproduces the print), the Table E.4 decisive chains of
all five spectra, the Annex E Step 4 mean uncertainty (1.38 dB) and the
Table E.2 LG/av columns and line-snapped band limits. Table E.3's full
tone record is pinned as printed data (its spectra lines are not
published). The E.1 NOTE 1 truncated-band LS values are recorded as not
reproducible under any tested iteration variant.
The distinctness test and module docstring record the DIN-vs-ISO print
difference (fT/sqrt(2) on both edges per the executable DIN 45681 Annex J
program, versus the asymmetric ISO print), the A-weighting requirement of
Clause 5.3.2, and the Formula (19) validity range 50 Hz < fT < 1000 Hz.
Two new conformance rows cover the per-tone and mean uncertainties.
* feat: ECMA-418 decision-threshold constants, degenerate-input warnings and range-note fixes
The verbatim decision criteria of ECMA-418-2 are now exposed and pinned as
constants-tests: audibility at 0.01 sone_HMS total basis loudness
(Clause 5.1.9), prominent tonality at 0.4 tu_HMS (Clause 6.3) and
prominent roughness at 0.2 asper (Clause 7.2) -- beyond the calibration
points these are the standard's only further numeric anchors (its annexes
are graphical).
ECMA-418-1 TNR/PR: the prominent verdict is documented as the numeric
criterion only (the aural-examination and lower-threshold-of-hearing
requirements of clauses 11.8/12.8 and 8/9 are the caller's
responsibility), and degenerate inputs now warn instead of silently
returning meaningless verdicts -- peaks within one bin of the 89.1 Hz /
11.2 kHz range edge (where bin snapping can flip the verdict) and
critical bands at numeric-noise power (silence, DC). A code note records
that the '11 220 Hz' upper range limit printed in clause 4.1.2
contradicts every formula and table of the standard, which use 11 200 Hz.
The binaural-scope notes now cite the correct clauses: Formula (118) /
Clause 8.1.5 for loudness, Formula (112) / Clause 7.1.11 for roughness,
and no binaural combination exists for tonality.
* docs: errata entries for the ECMA-418, ISO/PAS 20065 and Osses 2016 print defects; changelog for the psychoacoustics review
Records four defects surfaced by the standards-correctness review in the
errata registry: the ECMA-418-1 clause 4.1.2 '11 220 Hz' range misprint,
the internally inconsistent ECMA-418-2 clause 5.1.5.2 last-block formula,
the ISO/PAS 20065 clause 5.3.4 edge-steepness print that contradicts the
executable DIN 45681 Annex J reference program, and the Osses 2016 Eq. (3)
Bark-constant exponent typo (marked as a non-standard source). The
changelog gains the corresponding Added/Fixed entries.
* docs: regenerate the psychoacoustics figures and the conformance report
The five figures whose generators consume the corrected metrics
(Sottek loudness, tonality/roughness demo, loudness-model comparison,
fluctuation strength, psychoacoustic annoyance) are re-rendered in all
four language/theme variants. The conformance report picks up the
corrected anchors (roughness 0.9999 asper at the overall-60 dB reference,
ECMA loudness 0.9843 sone_HMS with the full Clause 6.2.3 averaging,
PA 37.0478) and the three new rows: the DIN 45692 Table A.2 sharpness
oracle and the ISO/PAS 20065 per-tone and mean extended uncertainties.
235/235 checks pass.
* docs: correct the PA formula box in the annoyance figure
The figure's info panel still printed the pre-fix combination
N5 sqrt(1 + wS^2 + wFR^2); it now shows the Fastl & Zwicker Eq. (16.2)
form N5 (1 + sqrt(wS^2 + wFR^2)) that the plotted curves already use.
* test: regenerate the ECMA loudness and roughness golden baselines
The goldens pin exact refactor-drift baselines; the ECMA-418-2 loudness
values legitimately changed with the full Clause 6.2.3 band averaging and
the roughness values with the Clause 5.1.2 fade-in, so the two cases are
recaptured with scripts/bench.py --golden (the tonality case is
byte-identical: it already used the full averaging).
* docs: uncertainty API in the tone-audibility guides and API reference
The Clause 5.4/6 extended uncertainty gets its section in the guide (EN and
the two site mirrors), the API reference gains the new function row and the
changed signatures (time_skip, extended_uncertainties), and a Spanish
output comment prints the dot the program actually emits.
* refactor: review follow-ups on the psychoacoustics changes
Range guards use direct comparisons, the API-reference row states the real
audibility_uncertainty signature, and the loudness calibration comment
matches the report's computed figure.
fix: SII disturbance per ANSI S3.5, IEC-exact intermodulation and the AES17 measurement chain (#173)
* fix: SII equivalent disturbance is the clause 5.6 maximum, not an energy sum
ANSI S3.5-1997 clause 5.6 defines the equivalent disturbance spectrum
level Di as the larger of the equivalent masking level Zi and the
equivalent internal noise level Xi'. The module summed the two in energy,
underestimating the SII by up to 0.042 in noise-plus-hearing-loss
conditions (0.1841 vs 0.2185 for normal speech in flat 30 dB noise with a
flat 40 dB loss). The official Hornsby worksheet computes the maximum in
every band and the R CRAN worked example confirms it.
Oracles: quiet-condition SII pinned at the worksheet full precision
(0.99582516666667, abs 1e-6), the disturbance intermediate locked at
5000 Hz (-23.6 dB), a discriminating noise-plus-loss case (0.2185) and
the independent R package Example C.2 (0.8513749). A code comment records
that the level-distortion factor referencing the normal-effort spectrum
for every vocal effort is the standard's own definition.
* test: STIPA direct-method modulation-depth staircase and NIPTS reading notes
The IEC 60268-16:2020 C.3.2 verification signal (Formula C.1, sinusoidal
carriers at the A.6.1 male levels) is synthesized end to end and the
published STI staircase for m = 0.0 to 1.0 (0, 0.18, 0.30, 0.38, 0.44,
0.50, 0.56, 0.62, 0.70, 0.82, 1) is asserted within the published
tolerance of 0.05 through the full stipa() audio path; the m = 0.5 point
also joins the conformance report.
ISO 1999 notes: the sub-10-year NIPTS spread keeps the raw lg(t) reading
(Formulae 6/7 carry no Formula-3 extrapolation provision) and the Annex D
half-integer rounding ambiguity of Table D.4 is recorded next to the
pinned cells.
* fix: IEC-exact intermodulation distortion (14.12.7, 14.12.8, 14.12.10)
modulation_distortion now returns the per-order IEC 60268-3 values: d_m,2
and d_m,3 are the arithmetic sums of the sideband amplitudes referenced to
the output voltage at f2 (14.12.7.2 g-h), bundled with the SMPTE-analyzer
combined RMS as an explicitly labelled convention in a new
ModulationDistortionResult.
difference_frequency_distortion references U_2,ref = 2 U_2,f2 (the sum of
both tone amplitudes) and sums the third-order products arithmetically per
14.12.8.1; the former half-difference search window could swallow the
primary tones (octave-spaced clean tones read d2 = 1.0) or the DC bin, so
product windows are now a few bins wide, capped by the component spacing,
clamped to (0, Nyquist) and forced to zero when they cannot be separated
from a primary tone or DC.
total_difference_frequency_distortion implements 14.12.10.1 directly: only
the two in-band products at f0 -/+ delta, rms-summed over the arithmetic
sum of the tone output voltages, with the standard 8 kHz / 11.95 kHz tones
as defaults.
The conformance entries carry the corrected per-clause labels and exact
values (d_m,2 = 0.16, d_m,3 = 0.08, d_d,2 = 0.03, d_d,3 = 0.04, d_TDFD =
sqrt(0.0013)). The 14.12.9.2 f) print of the DIM denominator (U2 vs the
14.12.9.1 f_s amplitude) is recorded in docs/ERRATA.md with a code comment
at the reference measurement.
* fix: AES17 measurement bandwidth, effective notch Q and the ITU-R 468 weighted THD
thd_plus_noise and sinad now measure both the residual and the total
signal through the AES17 chain (5.2.5 / 6.3.1): a 20 Hz high-pass plus
the standard low-pass at a configurable bandwidth (default 20 kHz),
realised as a brick-wall frequency-domain band per 5.2.10. Out-of-band
noise at high sample rates (+7 dB at fs = 192 kHz) and DC offsets no
longer inflate the result; bandwidth=None keeps the full-Nyquist
measurement.
The standard notch is designed so the APPLIED zero-phase response has the
requested quality factor (AES17 5.2.8 defines Q on the combined response;
filtfilt squares the magnitude, so the single-pass design is sharpened by
sqrt(1 + sqrt(2))). Previously a nominal Q of 2.0 acted as 1.29 and
requests below ~1.87 silently fell outside the AES17 [1.2, 3] range.
weighted_thd defaults to the network IEC 60268-3 14.12.11 actually
requires: the IEC 60268-1 Appendix A curve, i.e. ITU-R BS.468-4 Table 1
(peak +12.2 dB at 6.3 kHz), interpolated per the recommendation's own
log-frequency rule and exposed as itu_r_468_weighting; A and C remain as
labelled options and the 31.5 Hz - 400 Hz fundamental validity range is
documented. THD now raises when no harmonic fits below Nyquist instead of
returning 0, captures shorter than 64 samples are rejected, and the THD/
SINAD docstrings state the 14.12.3.2 R-convention and the derived nature
of SINAD explicitly.
New oracles: clipped-sine Fourier constants, the full-signal DIM
regression recipe (1.536 MHz synthesis, weak polynomial nonlinearity,
FIR decimation to 192 kHz, exact-bin FFT oracle), the H1 input-noise
bias SNR/(1+SNR), the printed ITU-R 468 table values including the AES17
CCIR-RMS cross-check, and the effective-notch-Q validation.
* test: ISO 532-1 Annex B.4 level-ramp oracles and reference-source provenance
The Annex B.4 ramp signals 6-9 join the validation suite: the tone ramps
(250 Hz / 1 kHz / 4 kHz, 30 dB to 80 dB at 5 dB/s over 10 s inside a
10.6 s file) are synthesized from parameters measured on the official
electronic-attachment WAVs and pinned to the workbook Nmax/N5 with
tolerances calibrated against those WAVs (module-on-official-WAV agrees
with the workbook to < 0.01 % on Nmax; the synthesis lands within +0.3 %
/ -0.8 %, pinned at 0.6 % / 2 %). The pink-noise ramp (overall SPL
25.85 + 5t dB per the WAV's Annex B.1 calibration) is pinned at a
realization-honest 10 %.
The sub-1-sone phon conversion (0.0005 offset, 3.0 phon floor, exact
10/lg 2 factor) and the NX percentile ((k-1, k) mean at
k = int((1 - X/100) n)) are now confirmed line by line against the
attachment's Annex A.4 reference program source; the provenance
docstrings record the exact code they match.
* docs: IEC-exact IMD semantics, AES17 bandwidth and ITU-R 468 weighting across guides and reference
The electroacoustics guide (EN and ES) and the API reference describe the
per-order modulation and difference-frequency definitions with their
references and arithmetic sums, the 14.12.10 two-product TDFD with the
standard 8 kHz / 11.95 kHz defaults, the AES17 measurement-bandwidth chain
with its bandwidth escape hatch, the effective notch Q, the ITU-R BS.468-4
default weighting for weighted THD with the 31.5-400 Hz validity range,
the DIM denominator erratum link and the corrected THD/SINAD convention
wording. CHANGELOG records the added API and the fixes; the conformance
report and the distortion figure variants are regenerated.
* docs: the ISO 12354-1:2017 edition repeats the K24 clamp misprint and flips the double-leaf K24 sign
The 2017 edition repeats the impossible '0 <= K24 <= -4 dB' interval of the
2000 print verbatim, and its E.3.5 prints the double-leaf K24 with a plus
where the 2000 edition's formula and its own corroborating Figure E.9 curve
carry a minus; both are recorded in the errata registry and the sign choice
is noted at the implementation.
* docs: the theory pages and SII diagram teach the max-form disturbance
The equivalent disturbance prose in the theory pages and the diagram label
follow the corrected ANSI S3.5 clause 5.6 reading (the larger of masking
and internal noise, not their energy sum), in English and Spanish; the
ISO 532-1 provenance docstrings paraphrase the reference program instead of
quoting it; and the notch-Q compensation note states the bilinear-warp
caveat near Nyquist.
* fix: regenerate the SII figure for the max-form disturbance and analysis follow-ups
The speech-intelligibility figure consumes the corrected clause 5.6
disturbance, so its four variants are regenerated; the 468 weighting's
zero-frequency guard avoids floating-point equality and the exception
tests keep one throwing call per block.
perf: parallel test suite with shared heavy fixtures (#203)
* test: share memoised heavy analysis results and drop a debug artifact write
The Annex C.1 Moore-Glasberg time-varying loudness suites analysed the
same calibrated tones independently (the phon table and the sone table
pin different columns of the identical result), and the anchor tone was
recomputed by three checks; a module-scoped memoising fixture now
computes each distinct (frequency, level, field, presentation) tone once
and shares the frozen result object. The ECMA-418-2 roughness peak and
modulation-depth checks likewise reuse the module calibration result
instead of recomputing the same 70 Hz AM chain. No oracle, tolerance or
assertion changes; the shared dataclasses are frozen and only read.
The pink-noise flatness test no longer renders and writes a debug PNG to
a fixed repo path: the plot asserted nothing, left an untracked file
behind and raced under parallel test workers. Its numerical flatness
assertion is unchanged.
* build: run the test suite in parallel with pytest-xdist
pytest -n auto in make test, make coverage and the CI tests job fans the
3241 tests out across every CPU core; pytest-xdist joins the dev
requirements. Each worker pins its numerical thread pools to one thread
(OMP/MKL/OpenBLAS/NumExpr/Accelerate) because with one worker per core
the nested BLAS pools only add contention: measured about 25% faster
wall-clock and about 40% less total CPU than unpinned workers on the
heavy DSP modules. Full suite on a 12-core host: 508 s to 138 s
(about 3.7x). pytest-cov combines the per-worker data files itself, so
the coverage.xml handed to Codecov and Sonar is unchanged (verified
identical 96% line coverage, same statement and miss counts). The
tests-perf job stays serial: it exists to exercise the numba-jitted
kernel, where per-worker JIT recompilation would dominate.
* docs: changelog entry for the parallel test suite
Perception, hearing and speech guides: result figures and when-to-use prose (#361)
* Perception, hearing and speech guides: result figures and when-to-use prose
Add ToneAssessment.plot(), which draws an ECMA-418-1 tone against the
prominence criterion of its own method (recovered from criterion_db) over the
whole range of interest, with the deciding margin marked, and a
view="levels" option on ToneAudibilityResult.plot() that exposes the
level-versus-frequency view the ISO 1996-2 fiche already embedded.
Add eleven single-concept figures to the perception, hearing and speech
guides, each a realistic result drawn by its own .plot(): the tone prominence
verdict, the ISO/PAS 20065 tone levels, the ISO/PAS 1996-3 onset detection,
the ISO 532-2 specific loudness, the ECMA-418-2 specific tonality, the
specific fluctuation strength, the SII band audibility under a hearing loss,
the ISO 7029 fractile band, the ISO 1999 NIPTS audiogram, the STOI per-band
correlation and the IEC 60268-16 per-band MTI. Every figure is embedded in
the English guide, the Spanish mirror and the docs mirror with the dual
snippet block, and every plottable snippet on those pages now names its
.plot().
Prose: the tone-prominence guide gains a comparison of the four tonality
assessments the library implements and when each applies, the
hearing-threshold guide explains how the ISO 7029 fractiles feed the ISO 1999
age component and gains a See-also section, and the objective-intelligibility
guide gains the "which measure, and when" section that existed only in the
docs mirror, extended with the STOI/ESTOI, STI and SII choice.
Also label the STOI band axis at whole-hertz resolution and let the shaded
critical band of the tone-audibility levels plot follow the figure
background, so it reads on the white report page and on dark figures alike.
* Correct two figure alt texts against the plotted values
The ISO 7029 fractile figure describes the median and the 90 % fractile with
the values it actually draws (10 to 50 dB and 22 to 74 dB), and the
ECMA-418-2 specific tonality peak is at 9 Bark_HMS.
fix: psychoacoustics standards-correctness fixes with corrected calibrations and printed oracles (#172)
* test: repair the ISO 532-1 Annex B fixtures path and extend the Annex B coverage
The relative data path was not adjusted when the test file moved into
tests/psychoacoustics/, so all 21 Annex B validation tests had been
silently skipping through the requires_iso_data guard. Point DATA at
tests/data/iso532_1 again and add a hard in-repo presence assertion so a
future move fails loudly instead of re-skipping.
Also enable the shipped Annex B.4 Test signal 13 (Nmax reproduces the
workbook header to 6e-6 relative), add a bounds-only Annex B.3 pink-noise
check against the workbook tolerance band, and note why the B.4 ramp
signals 6-9 cannot be regenerated.
* fix: ECMA-418-2 roughness front-end applies the Clause 5.1.2 fade-in and the calibration hits 1 asper
The roughness path skipped the mandatory 5 ms trigonometric fade-in of
Formula (1), which loudness and tonality already apply; recordings cut
mid-sound picked up an onset artefact that survived the transient discard
through the asymmetric smoother. The fade-in is now a shared helper used
by all three metrics.
With the fade-in in place and the Clause 7 reference signal synthesized at
its stated level (the overall RMS SPL of the modulated signal, 60 dB, not
the carrier-alone level), the chain reproduces the 1 asper calibration to
0.99990 asper using the tabulated c_R = 0.0180685. The former +7.35 %
'clean-room methodology variance' narrative was wrong and is removed from
the docstring, tests, reference data, conformance check and docs; the test
and conformance now pin the true 1.000 asper anchor.
A code note in the segmentation records why the printed Clause 5.1.5.2
l_last formula cannot be implemented literally (blocks would overrun the
padded signal): the flush-to-end reading is the only self-consistent one.
* fix: psychoacoustic annoyance combines the weightings per F&Z Eq. (16.2)
The combination formula had the '1 +' inside the radical,
N5*sqrt(1 + wS^2 + wFR^2), where Fastl & Zwicker (2006, p. 328) print
PA = N5*(1 + sqrt(wS^2 + wFR^2)). Every PA value with a nonzero
sharpness, fluctuation or roughness contribution was underestimated by
13 to 29 percent, and the worked reference value had been hand-computed
with the same wrong form, so tests and conformance confirmed the bug.
The worked tuple (N5,S,F,R) = (30, 2.0, 0.5, 0.3) now anchors
PA = 37.0478 (wS and wFR unchanged), and the docs, site guides and API
reference show the corrected formula. The SQAT claim is now accurate:
SQAT implements the (1 + sqrt) combination.
* fix: fluctuation-strength Bark constant 0.76e-3 per Zwicker-Terhardt (Osses 2016 Eq. 3 misprint)
Osses/Garcia/Kohlrausch (2016) Eq. (3) prints 0.76e-4 in the critical-band
rate formula, a 10x typo of the Zwicker-Terhardt 0.76e-3 disproved by the
paper's own anchors (0.5 Bark = 50 Hz, 15 Bark = 2.7 kHz, 23.5 Bark =
13.2 kHz). As transcribed, the 47 filter centres spanned 491 Hz-20 kHz with
12 bands pinned at the grid edge, distorting the specific pattern for any
signal away from 1 kHz (0.56 instead of 0.86 vacil at a 125 Hz carrier,
1.10 instead of 0.58 at 8 kHz).
The self-derived calibration constant lands at C_FS = 0.279, close to the
paper's 0.2490 and inside the sanity guard. The re-fitted H(fmod) corners
did not compensate the bug: the 1 kHz modulation sweep is essentially
unchanged (16 Hz point 0.19 -> 0.17 vacil), so the corners stand as fitted.
New oracles: the carrier-frequency sweep (125 Hz-8 kHz) against measured
reference values and the F&Z Fig. 10.5 trend (low-mid plateau, 8 kHz
roll-off), and the Osses Table 1 AM-broadband-noise row as a trend-only
cross-check (the excitation front-end overshoots the BBN pass-band by up
to ~3x, now documented; the FM row is deliberately not pinned). The module
docstring also records the front-end deviations from the paper and that
the normative ECMA-418-2 Clause 9 fluctuation strength is a different,
unimplemented method.
* fix: ECMA-418-2 loudness uses the full Clause 6.2.3 cross-block-size averaging
The loudness path averaged neighbouring-band ACFs only within a block-size
group, skipping the cross-group recomputation Clause 6.2.3 mandates for
bands adjacent to a block-size change. Since Clause 8.1.1 builds loudness
on the Clause 6.2 outputs, the requirement applies to loudness identically;
tonal content near the block-size boundaries was mis-stated by up to
-9.5 % (166.7 Hz tone at the z=2 edge).
The full averaging now lives in loudness_ecma as a single shared
_tonal_noise_split (front-end + Clause 6.2 tonal/noise decomposition) that
both the loudness and tonality metrics call, so the two report an
identical N'_tonal(l, z) for the same signal -- asserted by a new
cross-consistency test that spies on the shared intermediate through both
call sites.
The 1 kHz / 40 dB calibration moves from 0.9960 to 0.9845 sone_HMS. The
-1.55 % residual exceeds the +/-0.25 % adjustment allowed for c_N, which
is kept at the verbatim tabulated value; the investigation (documented in
the module docstring) attributes it to the mandated band averaging around
the block-size-boundary bands excited by the tone's lower flank: no band
averaging reads 0.955, same-group-only averaging 0.996, full cross-group
averaging 0.9845, while block-time smoothing and the fade-in/LP transient
contribute under 0.01 %.
Also in this pass: the common time grid is sized from the 1024-sample
stage's block count instead of the 8192 group's (whose 7 extra trailing
blocks only produced edge-held interpolations), the benign max(.,0) clamp
that Formula 48 does not print is documented in place, _band_range
validates the Formulae 56/57 preconditions (16 Hz < f_L, f_H < 20 kHz,
f_L < f_H) instead of silently swapping reversed edges, and the three
Sottek metrics document their mono-only scope (no Formulae 112/118
binaural combination).
* feat: ISO 532-1 stationary TimeSkip parameter and reference-program provenance notes
Annex B.1 requires the stationary calculation to start from 0.2 s when
validating against the official Annex B recordings (the reference
f_square_and_smooth takes a TimeSkip and the CLI requires it); the
stationary path previously always averaged the whole signal, so leading
silence or the filterbank transient diluted the mean square. The new
time_skip parameter (default 0.0, validated in both modes, applied by the
stationary method only) implements it.
Documentation nits from the same review: the sone-to-phon mapping records
that the exact 10/lg 2 factor and the 3 phon floor come from the
electronic attachment's reference program rather than the printed
Formula (2), the percentile note records the same provenance for the
(k-1, k) mean, and the filterbank tables are labelled correctly (Table A.1
is the reference section; the deviations and gains are Table A.2).
* fix: sharpness Annex B variants use the literal k = 0.11; ISO 532-2/-3 oracle expansion
DIN 45692 Formulas (B.1)/(B.2) print S = 0,11 * moment for the Aures and
von Bismarck variants; the implementation had derived per-variant
constants so the clause 6 reference sound read exactly 1.00 acum, shifting
every Aures result +4.2 % against other implementations of the published
formulas. With the literal 0.11 the reference lands near (not exactly at)
1 acum: 0.96 Aures / 1.02 von Bismarck, asserted non-circularly at
1.00 +/- 0.05. The normative clause 5.2 k remains derived, as the standard
prescribes.
Sharpness verification now covers all 21 Table A.2 rows and all 20
Table A.3 broadband rows (band edges per Tabelle A.1, every signal set to
the clause 6 loudness of 4 sone) at the normative 5 % / 0.05 acum
tolerance, and the conformance report gains a non-definitional Table A.2
row (2.5 kHz -> 1.78 acum) beside the definitional reference-signal check.
ISO 532-3: the module docstring now states the native-fs conformance mode
(clause 5 prescribes a 32 kHz conversion; this implementation processes at
the native rate with per-window FFT lengths) and the Annex C.1 anchor is
pinned at 32/44.1/48 kHz (0.9918/0.9926/0.9900 with a 0.5 s tone, 0.3 %
cross-rate spread). New oracles: the Annex C.3 multi-tone complexes, the
Annex C.1.2/C.1.3 sone columns, the ISO 532-2 Annex B phon columns, and a
note marking the byte-identical anchor pin as a regression pin rather than
a conformance oracle. The Table 5 phon mapping documents its silent
saturation at 120 phon.
* feat: ISO/PAS 20065 extended uncertainty of the audibility (Clauses 5.4 and 6)
Implements the Gaussian propagation of the uniform 3 dB narrow-band level
uncertainty through the audibility chain (Formulae (22)-(27)) to the
extended uncertainty U with 90 % bilateral coverage (k = 1.645,
Formula (29)), plus the Formula (28) combination for the mean audibility.
analyze_spectrum now reports the per-tone U on the result object, and the
docstrings carry the Clause 6 requirement that U shall be considered when
fewer than 12 spectra are averaged.
Oracles: the printed Table E.2 U column (the decisive 137.3 Hz tone
reproduces 2.79 dB to 0.006 dB from the E.1 lines; the flanking tones
within 0.1 dB, their critical bands extending beyond the truncated table),
the 2 FG row (3.21 dB, using the N summated tone levels as the K summands,
the reading that reproduces the print), the Table E.4 decisive chains of
all five spectra, the Annex E Step 4 mean uncertainty (1.38 dB) and the
Table E.2 LG/av columns and line-snapped band limits. Table E.3's full
tone record is pinned as printed data (its spectra lines are not
published). The E.1 NOTE 1 truncated-band LS values are recorded as not
reproducible under any tested iteration variant.
The distinctness test and module docstring record the DIN-vs-ISO print
difference (fT/sqrt(2) on both edges per the executable DIN 45681 Annex J
program, versus the asymmetric ISO print), the A-weighting requirement of
Clause 5.3.2, and the Formula (19) validity range 50 Hz < fT < 1000 Hz.
Two new conformance rows cover the per-tone and mean uncertainties.
* feat: ECMA-418 decision-threshold constants, degenerate-input warnings and range-note fixes
The verbatim decision criteria of ECMA-418-2 are now exposed and pinned as
constants-tests: audibility at 0.01 sone_HMS total basis loudness
(Clause 5.1.9), prominent tonality at 0.4 tu_HMS (Clause 6.3) and
prominent roughness at 0.2 asper (Clause 7.2) -- beyond the calibration
points these are the standard's only further numeric anchors (its annexes
are graphical).
ECMA-418-1 TNR/PR: the prominent verdict is documented as the numeric
criterion only (the aural-examination and lower-threshold-of-hearing
requirements of clauses 11.8/12.8 and 8/9 are the caller's
responsibility), and degenerate inputs now warn instead of silently
returning meaningless verdicts -- peaks within one bin of the 89.1 Hz /
11.2 kHz range edge (where bin snapping can flip the verdict) and
critical bands at numeric-noise power (silence, DC). A code note records
that the '11 220 Hz' upper range limit printed in clause 4.1.2
contradicts every formula and table of the standard, which use 11 200 Hz.
The binaural-scope notes now cite the correct clauses: Formula (118) /
Clause 8.1.5 for loudness, Formula (112) / Clause 7.1.11 for roughness,
and no binaural combination exists for tonality.
* docs: errata entries for the ECMA-418, ISO/PAS 20065 and Osses 2016 print defects; changelog for the psychoacoustics review
Records four defects surfaced by the standards-correctness review in the
errata registry: the ECMA-418-1 clause 4.1.2 '11 220 Hz' range misprint,
the internally inconsistent ECMA-418-2 clause 5.1.5.2 last-block formula,
the ISO/PAS 20065 clause 5.3.4 edge-steepness print that contradicts the
executable DIN 45681 Annex J reference program, and the Osses 2016 Eq. (3)
Bark-constant exponent typo (marked as a non-standard source). The
changelog gains the corresponding Added/Fixed entries.
* docs: regenerate the psychoacoustics figures and the conformance report
The five figures whose generators consume the corrected metrics
(Sottek loudness, tonality/roughness demo, loudness-model comparison,
fluctuation strength, psychoacoustic annoyance) are re-rendered in all
four language/theme variants. The conformance report picks up the
corrected anchors (roughness 0.9999 asper at the overall-60 dB reference,
ECMA loudness 0.9843 sone_HMS with the full Clause 6.2.3 averaging,
PA 37.0478) and the three new rows: the DIN 45692 Table A.2 sharpness
oracle and the ISO/PAS 20065 per-tone and mean extended uncertainties.
235/235 checks pass.
* docs: correct the PA formula box in the annoyance figure
The figure's info panel still printed the pre-fix combination
N5 sqrt(1 + wS^2 + wFR^2); it now shows the Fastl & Zwicker Eq. (16.2)
form N5 (1 + sqrt(wS^2 + wFR^2)) that the plotted curves already use.
* test: regenerate the ECMA loudness and roughness golden baselines
The goldens pin exact refactor-drift baselines; the ECMA-418-2 loudness
values legitimately changed with the full Clause 6.2.3 band averaging and
the roughness values with the Clause 5.1.2 fade-in, so the two cases are
recaptured with scripts/bench.py --golden (the tonality case is
byte-identical: it already used the full averaging).
* docs: uncertainty API in the tone-audibility guides and API reference
The Clause 5.4/6 extended uncertainty gets its section in the guide (EN and
the two site mirrors), the API reference gains the new function row and the
changed signatures (time_skip, extended_uncertainties), and a Spanish
output comment prints the dot the program actually emits.
* refactor: review follow-ups on the psychoacoustics changes
Range guards use direct comparisons, the API-reference row states the real
audibility_uncertainty signature, and the loudness calibration comment
matches the report's computed figure.
Perception, hearing and speech guides: result figures and when-to-use prose (#361)
* Perception, hearing and speech guides: result figures and when-to-use prose
Add ToneAssessment.plot(), which draws an ECMA-418-1 tone against the
prominence criterion of its own method (recovered from criterion_db) over the
whole range of interest, with the deciding margin marked, and a
view="levels" option on ToneAudibilityResult.plot() that exposes the
level-versus-frequency view the ISO 1996-2 fiche already embedded.
Add eleven single-concept figures to the perception, hearing and speech
guides, each a realistic result drawn by its own .plot(): the tone prominence
verdict, the ISO/PAS 20065 tone levels, the ISO/PAS 1996-3 onset detection,
the ISO 532-2 specific loudness, the ECMA-418-2 specific tonality, the
specific fluctuation strength, the SII band audibility under a hearing loss,
the ISO 7029 fractile band, the ISO 1999 NIPTS audiogram, the STOI per-band
correlation and the IEC 60268-16 per-band MTI. Every figure is embedded in
the English guide, the Spanish mirror and the docs mirror with the dual
snippet block, and every plottable snippet on those pages now names its
.plot().
Prose: the tone-prominence guide gains a comparison of the four tonality
assessments the library implements and when each applies, the
hearing-threshold guide explains how the ISO 7029 fractiles feed the ISO 1999
age component and gains a See-also section, and the objective-intelligibility
guide gains the "which measure, and when" section that existed only in the
docs mirror, extended with the STOI/ESTOI, STI and SII choice.
Also label the STOI band axis at whole-hertz resolution and let the shaded
critical band of the tone-audibility levels plot follow the figure
background, so it reads on the white report page and on dark figures alike.
* Correct two figure alt texts against the plotted values
The ISO 7029 fractile figure describes the median and the 90 % fractile with
the values it actually draws (10 to 50 dB and 22 to 74 dB), and the
ECMA-418-2 specific tonality peak is at 9 Bark_HMS.