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.
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.