Run the full ISO 532-1 Annex B loudness validation from in-repo signals (#90)
* test: run the full ISO 532-1 Annex B validation from in-repo signals
Commit the ISO 532-1:2017 electronic-attachment validation data under
tests/data/iso532_1/ with a README documenting provenance, ISO copyright,
the source URL and a removal policy. This turns the twelve previously
skipped Annex B.5 technical-signal tests into always-on checks:
- Add the twelve recorded B.5 technical signals (byte-identical to the ISO
attachment) and gather the existing tone-pulse/level/expected fixtures
into the same folder.
- Read the WAVs with the stdlib `wave` module (no soundfile dependency) and
scale per Annex B.1 (full scale = 100 dB SPL -> 2*sqrt(2) Pa peak).
- Validate each signal in the sound field its ISO results workbook was
computed in; signal 15 (vehicle interior) is diffuse, the rest free. This
was the sole failing case and it is a field mismatch, not an algorithm
error. Nmax now reproduces the workbook to < 0.01 % on all twelve, so the
Nmax tolerance is tightened from 5 % to 0.1 %.
- Add two Annex B.5 rows (free + diffuse technical recording) to the
numerical conformance report; docs/CONFORMANCE.md regenerated (34/34).
Also clean the pre-existing mypy --strict debt in scripts/ (13 findings in
conformance_report.py, generate_graphs.py and generate_diagrams.py) and
extend the lint gate and CI to type-check scripts/ alongside src/ so it
cannot regress.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* test: harden ISO 532-1 WAV loading and graceful data absence (PR review)
Addresses the PR #90 bot review:
- Read WAVs with the little-endian dtype '<i2' (RIFF is little-endian, not
host-native) and keep only channel 0 of any multi-channel file, in both
the test reader and the conformance helper.
- Load the Annex B expected-values JSON defensively: the module no longer
crashes on import when the ISO data folder is absent; the six data-backed
tests carry a `requires_iso_data` skip marker instead, honouring the
README's removal policy while the synthetic-signal tests keep running.
- Raise a clear FileNotFoundError (not an IndexError) in the conformance
helper when no Annex B.5 WAV matches.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* test: unify the ISO 532-1 data root behind ISO532_1_TESTDATA (PR review)
CodeRabbit noted the presence gate and expected-values JSON were tied to
the hard-coded in-repo path while the Annex B.5 lookup honoured the
ISO532_1_TESTDATA override, so a valid override with the packaged JSON
absent would still skip. Derive the single DATA root from the override
(defaulting to the in-repo fixtures) and read the JSON, the presence gate
and the B.5 recordings all from it; drop the separate ISO_DIR.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* fix: mypy --strict on PR-C script code surfaced by the merged gate
Merging main (PR #89) brought the ISO 9613-2/9612 figure and conformance
helpers under this branch's extended `mypy src scripts` gate for the first
time: annotate the ISO 9612 Annex D task builder's return type and cast a
numpy int bar-label position to float.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
fix: first pass of audit findings (docs, figures, plotting)
Documentation:
- fix the Spanish filter-figure regression: _showfilter gains a close
flag and the generator saves through themed_path after drawing, so the
translation pass runs on the finished figure; restore the two
_ES_EXACT keys removed in #82 (they are emitted by filter_design, not
by the script)
- repair the broken building-acoustics.md link in the enclosed-space
guide (the repo split lives in room-acoustics.md)
- make the multiple-shock and intensity snippets self-contained: define
az/fs with a synthetic shock train (verified outputs 20.94 m/s2,
R=0.46, probability 0.03) and import numpy with stated outputs
(F2=3.41, Ld=8.0), in the repo doc and both site languages
- decimal points in the English Formula texts (1.07, 55.3) that had
kept the Spanish comma
Figures:
- stop drawing data series in the grid colour (invisible on light
backgrounds): time-weighting input burst, hearing-threshold 20 yr and
NIPTS 10 yr curves, and the unweighted/partial bars now use a visible
neutral grey; regenerate the 15 affected figure sets
Plotting layer:
- plot_facade_insulation forwards user kwargs to the primary D2m,nT
curve only, so label=/color= no longer raise or merge the four curves;
the kwargs-forwarding test now covers the facade plot
- harden three edge cases: all-masked SII (zero contribution no longer
divides 0/0), empty per-impulse prominence, and nearest-band lookup
for the NC governing marker instead of float equality
- sharpness normalization sanity guards raise RuntimeError instead of
AssertionError
EN 12354-6: sound absorption in enclosed spaces (#107)
* feat: EN 12354-6 sound absorption in enclosed spaces
Add the EN 12354-6:2003 prediction of a room's total equivalent sound
absorption area and reverberation time from the absorption of its
surfaces and objects (the normative Clause 4 model).
The absorption area sums the surface contributions, the object areas and
the air absorption (Formula 1), with the air term Aair = 4*m*V*(1 - psi)
(Formula 2, Table 1), the object fraction psi = sum(Vobj)/V (Formula 3)
and the empirical hard-object area Aobj = Vobj^(2/3) (Formula 4). The
reverberation time follows as T = 55.3/c0 * V*(1 - psi)/A (Formula 5,
the 0.16 factor at c0 = 345.6 m/s). The informative Annex D method for
irregular spaces is out of scope.
New module phonometry.en12354_6 with equivalent_absorption_area,
object_fraction, hard_object_absorption, air_absorption_area,
reverberation_time and the per-band enclosed_space_reverberation
returning a ReverberationResult with .plot(). Conformance report gains
an enclosed-space absorption domain (2 checks, 90 total). Adds the
enclosed-space-absorption guide (repo + EN/ES site) with a figure and a
flow diagram.
Validated digit-exact against the three worked cases of Annex E
(A = 2.26/5.03/10.21 m2, T = 2.1/0.9/0.5 s, and the air-absorption
note Aair = 0.12 m2 -> T = 2.0 s).
* refactor: address review feedback on EN 12354-6
- validation helpers _require_positive/_require_fraction: single error
literal and no negated comparison, still rejecting NaN (SonarCloud
S1192/S1940)
- object_fraction: reject negative object volumes and a fraction >= 1
(Gemini)
- air_absorption_area: validate the object fraction range and a
non-negative attenuation coefficient (Gemini)
- equivalent_absorption_area: reject negative air_area, absorption
coefficients and object areas (Gemini)
- reverberation_time: validate the object fraction range (Gemini)
- enclosed_space_reverberation: clear error when a built-in
air_condition is combined with non-standard frequencies (Gemini)
- diagram + alt text: the speed-of-sound symbol is c0, not co
(CodeRabbit); the diagram now renders it as a proper subscript
- tests: cover the speed_of_sound guard (CodeRabbit), NaN volume, the
physical-range validations and the standard-bands guard
* docs: label the object-array sum in the Formula 1 prose
The third summand of Formula 1 is the standard's object arrays k
(groups of identical objects treated as an absorbing surface), not a
duplicated surface term; name all three indices in the surrounding
prose so the equation reads unambiguously (CodeRabbit).
Acoustic materials: impedance tube, airflow resistance & sound-absorption rating (ISO 10534-1/-2, ASTM E2611, ISO 9053-1/-2, ISO 11654) (#91)
* feat: impedance tube, airflow resistance and ISO 11654 absorption rating
Clean-room from the standards (no third-party tool referenced):
- impedance_tube.py: ISO 10534-2 two-microphone transfer-function method
(reflection factor, surface impedance/admittance, absorption), ISO 10534-1
standing-wave-ratio method, and ASTM E2611 four-microphone transfer-matrix
method (wave decomposition, two-load/one-load T-matrix, transmission loss,
material wavenumber and characteristic impedance). Kelvin (ISO) vs Celsius
(ASTM) speed-of-sound helpers keep the two sign/unit conventions apart.
- airflow_resistance.py: ISO 9053-1 static method (through-origin quadratic
fit) and ISO 9053-2 alternating method (Formula 2), with R_s in Pa·s/m and
resistivity in Pa·s/m².
- absorption_rating.py: ISO 11654 weighted absorption alpha_w (0.05-grid
reference-curve shift, L/M/H shape indicators, absorption classes A-E),
verified against the Annex A.1/A.2 worked examples.
103 module tests plus public-export checks; physics-identity anchors for the
tube (rigid wall alpha=0, perfect absorber r=0/Z=rhoc, det(T)=1, air-layer
recovery to <1e-9) since neither tube standard prints a numeric example.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* feat: ISO 9053-2 Annex A kappa', materials docs, figures & conformance
Address per-module review of the impedance-tube / materials modules:
- ISO 9053-2:2020 Annex A (normative): implement the heat-conduction-corrected
effective ratio of specific heats kappa' (Formula (A.7)) and the thermal
boundary-layer thickness b (A.4/A.5) as effective_kappa() and
thermal_boundary_layer_thickness(). The Annex A.3 worked example is a
digit-exact oracle (b = 1.83e-3 m, kappa' = 1.370). Fix the misleading
_ADIABATIC_KAPPA citation: 1.4 is the uncorrected adiabatic fallback, not the
Annex A output.
- absorption_rating: delegate .plot() to _plotting.plot_weighted_absorption,
matching the repo idiom (reuses _unfavourable_mask / _new_axes).
- docs/materials.md: new guide covering ISO 11654, ISO 9053-1/-2 and
ISO 10534-1/-2 + ASTM E2611, with runnable examples and three figures
(EN/ES, light/dark). Linked from the docs index.
- Conformance report: five materials checks (ISO 11654 Annex A.1/A.2,
ISO 9053-2 Annex A.3 b + kappa', ISO 10534-1 SWR); shared oracles in
reference_data.py. 48/48 checks pass.
Impedance-tube module review returned Approved (no code changes needed;
the Eq (20) phase citation was verified correct against ISO 10534-1 p.5).
Claude-Session: https://claude.ai/code/session_01JJLhQzF5hTdnEi3bfmqnqm
* docs: experimental-setup diagrams for the materials methods
Add hand-drawn SVG setup diagrams (EN/ES, light/dark) for the new methods,
mirroring the didactic style of the room-acoustics / outdoor diagrams, and
embed them in docs/materials.md:
- Impedance tube (ISO 10534-2): loudspeaker, tube, two flush microphones at
spacing s and distance x1 from the specimen face, rigid backing, incident/
reflected plane waves and the Eq. (17)-(19) relations.
- Four-microphone transmission-loss tube (ASTM E2611): source, two upstream
and two downstream microphones, adjustable two-load termination, the A/B/C/D
travelling waves, and s1/s2/l1/l2/d labelled to match the library's
transfer_matrix_two_load / wave_decomposition parameters (l1, l2 measured
from the specimen front reference plane).
- Airflow resistance (ISO 9053-1/-2): the static rig (specimen, laminar flow
q_v, differential manometer Δp) and the alternating rig (piston 1-4 Hz,
cavity V, specimen/airtight termination, cavity level L_p).
Also apply the whole-branch review's two before-merge items:
- airflow_resistance.py: add the module-level __all__ it was missing.
- impedance_tube.face_quantities: add functional tests (progressive/backward
wave impedance = ±rho*c) so the exported ASTM Eq. (21) helper is covered.
SVG text() now XML-escapes &, < and > so labels may contain them literally.
Diagrams validated by subagent against the standards' figures and the
library's public-API variable names; the ASTM l2 origin was corrected from
the rear to the front specimen face to match the library convention.
Claude-Session: https://claude.ai/code/session_01JJLhQzF5hTdnEi3bfmqnqm
* fix: harden ISO 11654 input coercion (bot review)
_coerce now rejects multi-dimensional array input instead of silently
flattening it (CodeRabbit) and rejects non-finite values with a clear message
instead of a cryptic downstream integer-conversion error (Gemini). Adds tests
for both.
Claude-Session: https://claude.ai/code/session_01JJLhQzF5hTdnEi3bfmqnqm
* fix: flag ill-conditioned transfer-matrix solves (bot review)
transfer_matrix_two_load / transfer_matrix_one_load now emit an
ImpedanceTubeWarning when the solve denominator is near-singular relative to
its operands — two insufficiently-different loads (two-load) or a near-resonant
geometry (one-load) — instead of silently propagating inf/nan (CodeRabbit).
Adds tests for both the singular and the well-conditioned case.
Claude-Session: https://claude.ai/code/session_01JJLhQzF5hTdnEi3bfmqnqm
Outdoor sound propagation and occupational noise exposure (ISO 9613-1, ISO 9613-2, ISO 9612) (#89)
* feat: ISO 9613-1 atmospheric sound absorption
Full alpha(f, T, RH, p) model per ISO 9613-1:1993 clauses 6.2-6.4:
oxygen/nitrogen relaxation frequencies, water-vapour molar
concentration from relative humidity, classical + rotational +
vibrational terms. Validated against a 13-point Table 1 grid
(worst deviation 0.39 %, rounding-limited, vs the standard's
stated +/-10 % accuracy) with the exact midband convention of
Eq. (6) Note 5. air_attenuation_m() feeds the ISO 354 m parameter
directly, closing the user-supplied gap. Two conformance checks
added (34/34).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: C1 review nits — test name and comment direction
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* test: extend ISO 9613-1 Table 1 oracle against the full standard text
The full 37-page ISO 9613-1:1993 text (now in hand) confirms every
implemented constant of Eqs. (3)-(6), the Annex B psychrometric
conversion and the clause 4.2 reference conditions exactly as coded.
Oracle extended from 13 to 23 Table 1 points, adding the 20-50 degC
sub-tables 1(i)-1(p) unavailable in the interim source. Worst
deviation unchanged at 0.385 % (rounding-limited).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9613-2 outdoor sound propagation
Implement the ISO 9613-2:1996 general method for octave-band outdoor sound
attenuation: geometrical divergence (Eq. 7), atmospheric absorption (Eq. 8,
via ISO 9613-1 air_absorption), ground effect (general per-region method of
7.3.1 with the Table 3 a'/b'/c'/d' functions, Eq. 9, plus the alternative
method of 7.3.2, Eq. 10, and the DOmega index Eq. 11), and screening (Eq. 12-18
with the C2/C3 factors, single/double diffraction, Kmet and the 20/25 dB caps).
Adds the Cmet meteorological correction (Eq. 21/22) and the LfT(DW)=Lw+Dc-A
receiver-level composition (Eq. 3/6). Frozen dataclass result exposes the
per-term breakdown (Adiv/Aatm/Agr/Abar). New module, tests and exports.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9612 occupational noise exposure strategies and uncertainty
Add phonometry.occupational_exposure implementing the three ISO 9612:2009
measurement strategies and the normative Annex C uncertainty budget:
- task_based_exposure (Clause 9): per-task energy average (Eq 7), task
contributions (Eq 8), daily level (Eq 9/10); uncertainty Eq C.3 with
sampling u1a (Eq C.6), duration u1b (Eq C.7) and sensitivity coefficients
c1a (Eq C.4) / c1b (Eq C.5).
- job_based_exposure (Clause 10): effective-day energy average (Eq 11),
daily level (Eq 12); Table 1 minimum-duration check; uncertainty Eq C.9.
- full_day_exposure (Clause 11): whole-day averaging (Eq 11/13) with the
Clause 11.3 three-measurement / 3 dB spread rule.
- Table C.4 (job/full-day c1*u1, bilinear interpolation), Table C.5 (u2),
u3 = 1.0 dB, coverage factor k = 1.65 (one-sided 95 %).
Frozen dataclasses (Task, TaskContribution, ExposureResult), ExposureWarning
advisories for the sampling rules, and exports appended to __init__.
Tests reproduce the Annex D (task), E (job) and F (full-day) worked examples
to the standard's precision, plus closed-form oracles (single 85 dB/8 h task
= 85; equal-level tasks; -3.01 dB per halved duration; k = 1.65) and the
Table C.4 / Table 1 lookups. make check green: 1167 passed, 12 skipped.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 9612 review corrections — Table C.4 digits and Eq C.7 denominator
Two Table C.4 cells verified against the page image at 200 dpi and
corrected (N=6/u1=3.5: 3.4 -> 3.3; N=20/u1=2.0: 0.6 -> 0.5), the test
that echoed the wrong N=6 cell now pins the printed value plus the
corrected N=20 cell. The duration_samples branch of Eq C.7 divides by
J(J-1) like Eq C.6 (standard error of the mean), with a regression test
pinning u1b = 1.0 h for samples (4, 6) h. Module docstring now states
the Lp,Cpeak uncertainty scope cut (Table C.5, NOTE 1).
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* test: ISO 9613-2 and ISO 9612 conformance checks
Add a new "Outdoor propagation & occupational exposure" conformance domain
with seven closed-form / worked-example checks backed by shared constants in
tests/reference_data.py:
- ISO 9613-2:1996 Eq. (7) geometrical divergence Adiv = 51 dB at 100 m.
- ISO 9613-2:1996 Table 3 ground limit b'(0) = 10,1 -> Agr(250 Hz) = 17,2 dB
(porous ground, hs = hr = 0, dp -> inf).
- ISO 9613-2:1996 clause 7.4 single/double diffraction 20/25 dB caps.
- ISO 9612:2009 Annex D/E/F daily exposure LEX,8h and expanded uncertainty U
(84,3/2,7, 88,1/3,8, 90,1/3,4 dB), the Task objects rebuilt from the shared
input table.
Registry grows 34 -> 41 checks across 8 domains; docs/CONFORMANCE.md
regenerated via make conformance. Registration and reference-data consistency
tests pin the constants to the published values.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* docs: figures for outdoor propagation and occupational exposure
Add four documentation figures (each EN/ES x light/dark) plus a setup diagram:
- air_absorption_alpha: ISO 9613-1 alpha(f) over frequency at four
temperature/humidity combinations (relaxation roll-off, f^2 growth).
- outdoor_attenuation_breakdown: ISO 9613-2 per-term stacked bar (Adiv, Aatm,
Agr, Abar) with the total A, for a 200 m path over porous ground with a 4 m
barrier.
- exposure_uncertainty: ISO 9612 Annex D task contributions with the daily
LEX,8h and the one-sided 95 % upper limit LEX,8h + U.
- diagram_outdoor_geometry: source-barrier-receiver geometry (dss/dsr/z, the
blocked direct ray and the diffracted ray over the top edge).
Spanish translations added to the generate_graphs / generate_diagrams
dictionaries (decimal commas, UNE terminology). Every image visually audited.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* docs: ISO 9613-1/2 outdoor propagation guide and ISO 9612 exposure (EN)
Add the didactic Outdoor Sound Propagation guide (docs/outdoor-propagation.md)
covering ISO 9613-1 atmospheric absorption and the ISO 9613-2 general method
(divergence, atmospheric, ground and barrier terms, the OutdoorAttenuation
breakdown, the alternative 7.3.2 ground method, Cmet), with executed snippets
and honest scope notes (Amisc/reflections not implemented, 7.3.2 not
auto-wired, per-band Cmet a convenience). Extend the Levels guide with the
ISO 9612 occupational-exposure section (task/job/full-day strategies, the
C.6 I(I-1) vs C.12 N-1 asymmetry, Table C.4, k = 1,65, LEX,8h + U upper limit,
Lp,Cpeak uncertainty out of scope), reproducing the Annex D/E/F worked
examples.
Add the matching theory sections, API-reference rows for every new public
name, README feature list + docs table, docs index and CHANGELOG entries.
llms-full.txt / llms.txt regenerated (make llms) with the new page wired into
gen_llms.py. GitHub-safe math throughout; every snippet executed with pasted
outputs.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* docs: outdoor propagation and exposure site twins (EN + ES)
Add the English and Spanish Starlight twins of the new content:
- guides/outdoor-propagation.md (EN + ES) — code-identical snippets to the
docs page; Spanish prose uses UNE terminology ("ponderación temporal"),
decimal commas outside math and code, and the _es figure variants.
- The ISO 9612 section added to guides/levels.md (EN + ES).
- reference/theory.md and reference/api.md rows (EN + ES).
- Landing cards and standards list bumped 34 -> 37 (index.mdx EN + ES);
guides/outdoor-propagation added to the sidebar.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* fix: final branch review — API rows, precision phrasing and ES links
Adds the five missing public-symbol rows (TaskContribution,
table_c4_contribution, minimum_cumulative_duration_hours, COVERAGE_FACTOR,
INSTRUMENT_U2) to the three API references, states the ISO 9613-2 default
atmosphere (20 °C / 70 %, a Table 2 reference atmosphere) in the API rows
and guides, and softens "digit-for-digit" to "the standard's printed
precision" wherever it covered Annex E, whose final level differs only by
the standard's own pre-rounding. ES pages now use the /es/ link prefix
consistently. Code nits from the review: dead arg > 1 guard removed
(z > 0 implies arg >= 3), _with_advisory uses dataclasses.replace, and the
float-keyed _PRIME_BY_BAND gets a safety comment. llms files regenerated.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* fix: validate propagation/exposure inputs and docs lint (PR review)
Addresses the PR #89 bot review:
- Barrier.__post_init__ rejects negative edge distances and a zero
edge_separation (which previously divided by zero in the C3 double-
diffraction factor); Task.__post_init__ rejects empty samples and a
non-positive duration.
- ground_attenuation and barrier_attenuation reject non-positive
frequencies (a zero frequency gave an infinite wavelength / division by
zero); ground_attenuation also checks a positive distance and
non-negative heights. job_based_exposure rejects a non-positive
sample_duration_hours.
- Rewrite the ISO 9613-2 7.3.2 closed form in the guides and API tables so
Markdownlint no longer parses `(...)[...]` as a reversed link (MD011).
- Regression tests for every new guard; docstrings note the new raises.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
Acoustic materials: impedance tube, airflow resistance & sound-absorption rating (ISO 10534-1/-2, ASTM E2611, ISO 9053-1/-2, ISO 11654) (#91)
* feat: impedance tube, airflow resistance and ISO 11654 absorption rating
Clean-room from the standards (no third-party tool referenced):
- impedance_tube.py: ISO 10534-2 two-microphone transfer-function method
(reflection factor, surface impedance/admittance, absorption), ISO 10534-1
standing-wave-ratio method, and ASTM E2611 four-microphone transfer-matrix
method (wave decomposition, two-load/one-load T-matrix, transmission loss,
material wavenumber and characteristic impedance). Kelvin (ISO) vs Celsius
(ASTM) speed-of-sound helpers keep the two sign/unit conventions apart.
- airflow_resistance.py: ISO 9053-1 static method (through-origin quadratic
fit) and ISO 9053-2 alternating method (Formula 2), with R_s in Pa·s/m and
resistivity in Pa·s/m².
- absorption_rating.py: ISO 11654 weighted absorption alpha_w (0.05-grid
reference-curve shift, L/M/H shape indicators, absorption classes A-E),
verified against the Annex A.1/A.2 worked examples.
103 module tests plus public-export checks; physics-identity anchors for the
tube (rigid wall alpha=0, perfect absorber r=0/Z=rhoc, det(T)=1, air-layer
recovery to <1e-9) since neither tube standard prints a numeric example.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* feat: ISO 9053-2 Annex A kappa', materials docs, figures & conformance
Address per-module review of the impedance-tube / materials modules:
- ISO 9053-2:2020 Annex A (normative): implement the heat-conduction-corrected
effective ratio of specific heats kappa' (Formula (A.7)) and the thermal
boundary-layer thickness b (A.4/A.5) as effective_kappa() and
thermal_boundary_layer_thickness(). The Annex A.3 worked example is a
digit-exact oracle (b = 1.83e-3 m, kappa' = 1.370). Fix the misleading
_ADIABATIC_KAPPA citation: 1.4 is the uncorrected adiabatic fallback, not the
Annex A output.
- absorption_rating: delegate .plot() to _plotting.plot_weighted_absorption,
matching the repo idiom (reuses _unfavourable_mask / _new_axes).
- docs/materials.md: new guide covering ISO 11654, ISO 9053-1/-2 and
ISO 10534-1/-2 + ASTM E2611, with runnable examples and three figures
(EN/ES, light/dark). Linked from the docs index.
- Conformance report: five materials checks (ISO 11654 Annex A.1/A.2,
ISO 9053-2 Annex A.3 b + kappa', ISO 10534-1 SWR); shared oracles in
reference_data.py. 48/48 checks pass.
Impedance-tube module review returned Approved (no code changes needed;
the Eq (20) phase citation was verified correct against ISO 10534-1 p.5).
Claude-Session: https://claude.ai/code/session_01JJLhQzF5hTdnEi3bfmqnqm
* docs: experimental-setup diagrams for the materials methods
Add hand-drawn SVG setup diagrams (EN/ES, light/dark) for the new methods,
mirroring the didactic style of the room-acoustics / outdoor diagrams, and
embed them in docs/materials.md:
- Impedance tube (ISO 10534-2): loudspeaker, tube, two flush microphones at
spacing s and distance x1 from the specimen face, rigid backing, incident/
reflected plane waves and the Eq. (17)-(19) relations.
- Four-microphone transmission-loss tube (ASTM E2611): source, two upstream
and two downstream microphones, adjustable two-load termination, the A/B/C/D
travelling waves, and s1/s2/l1/l2/d labelled to match the library's
transfer_matrix_two_load / wave_decomposition parameters (l1, l2 measured
from the specimen front reference plane).
- Airflow resistance (ISO 9053-1/-2): the static rig (specimen, laminar flow
q_v, differential manometer Δp) and the alternating rig (piston 1-4 Hz,
cavity V, specimen/airtight termination, cavity level L_p).
Also apply the whole-branch review's two before-merge items:
- airflow_resistance.py: add the module-level __all__ it was missing.
- impedance_tube.face_quantities: add functional tests (progressive/backward
wave impedance = ±rho*c) so the exported ASTM Eq. (21) helper is covered.
SVG text() now XML-escapes &, < and > so labels may contain them literally.
Diagrams validated by subagent against the standards' figures and the
library's public-API variable names; the ASTM l2 origin was corrected from
the rear to the front specimen face to match the library convention.
Claude-Session: https://claude.ai/code/session_01JJLhQzF5hTdnEi3bfmqnqm
* fix: harden ISO 11654 input coercion (bot review)
_coerce now rejects multi-dimensional array input instead of silently
flattening it (CodeRabbit) and rejects non-finite values with a clear message
instead of a cryptic downstream integer-conversion error (Gemini). Adds tests
for both.
Claude-Session: https://claude.ai/code/session_01JJLhQzF5hTdnEi3bfmqnqm
* fix: flag ill-conditioned transfer-matrix solves (bot review)
transfer_matrix_two_load / transfer_matrix_one_load now emit an
ImpedanceTubeWarning when the solve denominator is near-singular relative to
its operands — two insufficiently-different loads (two-load) or a near-resonant
geometry (one-load) — instead of silently propagating inf/nan (CodeRabbit).
Adds tests for both the singular and the well-conditioned case.
Claude-Session: https://claude.ai/code/session_01JJLhQzF5hTdnEi3bfmqnqm
Building acoustics II: facade insulation (ISO 16283-3), laboratory measurement (ISO 10140), prediction with flanking (EN 12354) and uncertainty (ISO 12999-1) (#88)
* feat: ISO 16283-3 facade sound insulation
Add facade_insulation() and FacadeInsulationResult for field facade sound
insulation per ISO 16283-3: the global-method level difference D2m and its
standardized (D2m,nT) and normalized (D2m,n) forms, plus the element-method
apparent sound reduction index R'45 (loudspeaker, -1,5 dB) / R'tr,s (road
traffic, -3 dB). Reuses the ISO 16283-1 energy-averaging helpers, the
Sabine absorption area, and the ISO 717-1 airborne weighted_rating engine
unchanged for the single-number rating. Adds a per-band .plot() profile and
exports.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 16283-3 draft-edition caveat and symmetric R' validation
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 10140 laboratory airborne and impact sound insulation
Add src/phonometry/lab_insulation.py, the laboratory counterpart of the
field ISO 16283 family:
- lab_airborne_insulation: R = L1 - L2 + 10 lg(S/A) with A = 0,16 V/T
(ISO 10140-2:2010 Formula (2); ISO 10140-4:2010 Formula (5)).
- lab_impact_insulation: Ln = Li + 10 lg(A/A0), A0 = 10 m²
(ISO 10140-3:2010 Formula (1)).
- background_correction: ISO 10140-4:2010 Clause 4.3 Formula (4) with the
6/15 dB criteria and the 1,3 dB limit-of-measurement cap.
Single-number Rw / Ln,w with C, Ctr, CI reuse the verified ISO 717-1/2
weighted_rating / weighted_impact_rating engines; position energy
averaging reuses _as_band_levels. 29 new closed-form / identity tests.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: reconcile facade citations with published ISO 16283-3:2016
The facade module was drafted from ISO/DIS 16283-3:2014; its docstrings
cited draft formula numbers with a caveat to reconcile against the
published edition. Reconciled against ISO 16283-3:2016(E) (First edition
2016-02-01):
- Clause numbers are edition-stable: the field-quantity definitions keep
3.12 (R'45deg), 3.13 (R'tr,s), 3.14 (D2m), 3.15 (D2m,nT), 3.16 (D2m,n),
3.17 (A=0,16V/T), and 9.5.1 (surface-level averaging).
- Formula numbers differ: in the 2016 edition the Clause 3 defining
formulas are UNNUMBERED (inline in the term definitions); the numbered
formulas (1)-(21) live in the procedural clauses. The surface-level
energy-average is Formula (7) in 2016 (was Formula (20) in the DIS).
- Dropped the invalid draft Formula (2)-(7) citations from the field-
quantity docstrings and the draft-edition reconciliation caveat.
- Constants and formula bodies are unchanged (-1,5 dB / -3 dB, A0=10 m2,
T0=0,5 s, A=0,16V/T, band ranges): no numeric change; 22 facade tests
green.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: EN 12354-1/2 building acoustic performance prediction with flanking
Implement the EN 12354:2000 simplified single-number prediction model for
apparent airborne (R'w) and impact (L'n,w) sound insulation, including direct
plus flanking transmission (Ff/Df/Fd paths) and the Annex E vibration-reduction
index Kij for rigid cross, rigid T, flexible-interlayer and lightweight-facade
junctions.
Airborne Formula (26)/(27)/(28a) with l0=1 m, Kij,min (29), lining composition
(30)/(31); impact Formula (21) with bare-floor Ln,w,eq (164-35 lg m'), Table 1
flanking correction K, and standardized L'nT,w (3). Results expose per-path
energy contributions so the dominant flanking path is visible.
Validated against the standards' worked examples: Part 1 Annex H.3 airborne
(R'w = 52 dB, 13 paths; +floating-floor 53 dB) and Part 2 Annex E.3 impact
(L'n,w = 45 dB, L'nT,w = 43 dB); all four Annex H junctions reproduce Kij.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 12999-1 building acoustics measurement uncertainty
Add building_uncertainty module implementing ISO 12999-1:2020 standard
uncertainties for sound-insulation quantities: per-band and single-number
values for airborne (Tables 2/3), impact (Tables 4/5) and floor-covering
reduction (Tables 6/7), across measurement situations A/B/C (Clause 5.2),
plus the Annex D sigma_R95 upper limits, Table 1 max repeatability and
Table 8 coverage factors.
Expansion U = k*u with k >= 1 (Clause 8), one-sided factors for conformity
checks (Formulae 4/5), and combination rules from Annexes A/B/C (prediction
input A.1, quadrature A.2/C.2, m-measurement reduction A.7, uncorrelated
single-number B.2). Composable UncertainValue attaches value +- U without
touching the rating dataclasses.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: numpy-stub-agnostic annotations for the two env-dependent mypy sites
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: PR-B review minors — anchors, Kij floor, uncertainty polish
- Table 3 r_w+ctr_50_5000 σsitu(B)=1.0 verified digit-by-digit against the
ISO 12999-1:2020(E) page image; anomalous but normative (comment added).
- Anchor the ISO 10140 rating tests to independent literals (Rw=54, Ln,w=58 —
reference-curve shape + the 32 dB / 16-band = 2 dB ISO 717 shift).
- background_correction: cross-ref sound_power.background_noise_correction and
document the negative-margin (Lb > Lsb) cap at Lsb−1.3.
- flanking_path: optional kij_min clamp for EN 12354-1 Clause 4.4.2 (Kij≥Kij,min),
documented on flanking_element; tested clamped vs unclamped (H.3 unaffected).
- impact_flanking_correction: comment on nearest-neighbour + tie-to-lower.
- Export COVERAGE_FACTORS (Table 8) as public API.
- single_number_uncertainty: note impact situation A is an estimate (Table 5 fn a).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: conformance checks for facade, lab insulation, EN 12354 and ISO 12999
Add six numerical conformance checks for the PR-B building-acoustics
standards, following the established registry pattern (single source of
truth in tests/reference_data.py, clause citations, honest tolerances):
- ISO 16283-3:2016 Clause 3.12: facade R'45 isolates the -1.5 dB
oblique-incidence correction on an S=A constructed case (exact).
- ISO 10140-2:2021 Formula (2): lab airborne R laid on the ISO 717-1
reference shape (S=A) -> Rw = 54 (the +2-shift analytic anchor).
- EN 12354-1:2000 Annex H.3: airborne prediction R'w = 52 from the 13
transmission paths (direct + 12 flanking), the branch's strongest oracle.
- EN 12354-2:2000 Annex E.3: impact prediction L'n,w = 45 (76-33+2).
- ISO 12999-1:2020 Table 2: airborne band uncertainty, situation A @
1 kHz = 1.8 dB (digit-exact), and Clause 8/Table 8 expanded
uncertainty U = 1.96 u = 2.352 dB (exact k=1.96 arithmetic).
Facade and lab checks join "Room & building acoustics"; the EN 12354 /
ISO 12999 checks form a new "Building prediction & uncertainty" domain.
Registry grows 26 -> 32 checks across 7 domains. Shared expected values
and the Annex H.3 input table move into reference_data.py so the report
and tests cannot drift; a consistency test pins them to the published
worked-example results. docs/CONFORMANCE.md regenerated via make conformance.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: building acoustics II — facade, laboratory, prediction and uncertainty (EN)
Extend the Room & Building Acoustics guide with facade insulation
(ISO 16283-3), laboratory characterisation (ISO 10140), flanking-transmission
performance prediction (EN 12354-1/2) and measurement uncertainty
(ISO 12999-1), with executed snippets reproducing the EN 12354-1 Annex H.3
(R'w = 52 dB) and Annex E.3 (L'n,w = 45 dB) worked examples. Add the matching
theory subsections, API-reference rows for every new public name, README and
landing-page entries (29 -> 33 standards), and CHANGELOG. EN docs and site
twins keep byte-identical code blocks.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: flanking-paths diagram, prediction and uncertainty figures
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de edificación II (fachadas, laboratorio, predicción, incertidumbre)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: PR-B final-review fixes — count, GitHub-safe math, edition alignment
Landing count corrected to 34 standards (slash-part convention); the
GitHub-unsafe math spacing tokens reintroduced by the new building
sections are scrubbed from the docs/ tree and its site twins; the whole
ISO 10140-2 citation chain aligned to the verified :2010 edition; plus
the six minor documentation-precision fixes from the final review.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 88 review round 1 — isfinite idiom, heading levels, frequencies validation
- building_prediction._check_finite: replace 'v != v or v in (inf,-inf)'
NaN idiom with math.isfinite for SonarCloud (same behavior, cleaner).
- room-acoustics docs (EN docs + site, ES site): relevel the three
parameter-table headings that skipped H2->H4 to H3 (MD001).
- insulation.facade_insulation: validate 'frequencies' length against the
band count, raising a clear ValueError instead of deferring a matplotlib
shape error to plot(). lab_insulation/building_uncertainty have no such
gap (no user-supplied frequencies stored). Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Building acoustics II: facade insulation (ISO 16283-3), laboratory measurement (ISO 10140), prediction with flanking (EN 12354) and uncertainty (ISO 12999-1) (#88)
* feat: ISO 16283-3 facade sound insulation
Add facade_insulation() and FacadeInsulationResult for field facade sound
insulation per ISO 16283-3: the global-method level difference D2m and its
standardized (D2m,nT) and normalized (D2m,n) forms, plus the element-method
apparent sound reduction index R'45 (loudspeaker, -1,5 dB) / R'tr,s (road
traffic, -3 dB). Reuses the ISO 16283-1 energy-averaging helpers, the
Sabine absorption area, and the ISO 717-1 airborne weighted_rating engine
unchanged for the single-number rating. Adds a per-band .plot() profile and
exports.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 16283-3 draft-edition caveat and symmetric R' validation
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 10140 laboratory airborne and impact sound insulation
Add src/phonometry/lab_insulation.py, the laboratory counterpart of the
field ISO 16283 family:
- lab_airborne_insulation: R = L1 - L2 + 10 lg(S/A) with A = 0,16 V/T
(ISO 10140-2:2010 Formula (2); ISO 10140-4:2010 Formula (5)).
- lab_impact_insulation: Ln = Li + 10 lg(A/A0), A0 = 10 m²
(ISO 10140-3:2010 Formula (1)).
- background_correction: ISO 10140-4:2010 Clause 4.3 Formula (4) with the
6/15 dB criteria and the 1,3 dB limit-of-measurement cap.
Single-number Rw / Ln,w with C, Ctr, CI reuse the verified ISO 717-1/2
weighted_rating / weighted_impact_rating engines; position energy
averaging reuses _as_band_levels. 29 new closed-form / identity tests.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: reconcile facade citations with published ISO 16283-3:2016
The facade module was drafted from ISO/DIS 16283-3:2014; its docstrings
cited draft formula numbers with a caveat to reconcile against the
published edition. Reconciled against ISO 16283-3:2016(E) (First edition
2016-02-01):
- Clause numbers are edition-stable: the field-quantity definitions keep
3.12 (R'45deg), 3.13 (R'tr,s), 3.14 (D2m), 3.15 (D2m,nT), 3.16 (D2m,n),
3.17 (A=0,16V/T), and 9.5.1 (surface-level averaging).
- Formula numbers differ: in the 2016 edition the Clause 3 defining
formulas are UNNUMBERED (inline in the term definitions); the numbered
formulas (1)-(21) live in the procedural clauses. The surface-level
energy-average is Formula (7) in 2016 (was Formula (20) in the DIS).
- Dropped the invalid draft Formula (2)-(7) citations from the field-
quantity docstrings and the draft-edition reconciliation caveat.
- Constants and formula bodies are unchanged (-1,5 dB / -3 dB, A0=10 m2,
T0=0,5 s, A=0,16V/T, band ranges): no numeric change; 22 facade tests
green.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: EN 12354-1/2 building acoustic performance prediction with flanking
Implement the EN 12354:2000 simplified single-number prediction model for
apparent airborne (R'w) and impact (L'n,w) sound insulation, including direct
plus flanking transmission (Ff/Df/Fd paths) and the Annex E vibration-reduction
index Kij for rigid cross, rigid T, flexible-interlayer and lightweight-facade
junctions.
Airborne Formula (26)/(27)/(28a) with l0=1 m, Kij,min (29), lining composition
(30)/(31); impact Formula (21) with bare-floor Ln,w,eq (164-35 lg m'), Table 1
flanking correction K, and standardized L'nT,w (3). Results expose per-path
energy contributions so the dominant flanking path is visible.
Validated against the standards' worked examples: Part 1 Annex H.3 airborne
(R'w = 52 dB, 13 paths; +floating-floor 53 dB) and Part 2 Annex E.3 impact
(L'n,w = 45 dB, L'nT,w = 43 dB); all four Annex H junctions reproduce Kij.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 12999-1 building acoustics measurement uncertainty
Add building_uncertainty module implementing ISO 12999-1:2020 standard
uncertainties for sound-insulation quantities: per-band and single-number
values for airborne (Tables 2/3), impact (Tables 4/5) and floor-covering
reduction (Tables 6/7), across measurement situations A/B/C (Clause 5.2),
plus the Annex D sigma_R95 upper limits, Table 1 max repeatability and
Table 8 coverage factors.
Expansion U = k*u with k >= 1 (Clause 8), one-sided factors for conformity
checks (Formulae 4/5), and combination rules from Annexes A/B/C (prediction
input A.1, quadrature A.2/C.2, m-measurement reduction A.7, uncorrelated
single-number B.2). Composable UncertainValue attaches value +- U without
touching the rating dataclasses.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: numpy-stub-agnostic annotations for the two env-dependent mypy sites
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: PR-B review minors — anchors, Kij floor, uncertainty polish
- Table 3 r_w+ctr_50_5000 σsitu(B)=1.0 verified digit-by-digit against the
ISO 12999-1:2020(E) page image; anomalous but normative (comment added).
- Anchor the ISO 10140 rating tests to independent literals (Rw=54, Ln,w=58 —
reference-curve shape + the 32 dB / 16-band = 2 dB ISO 717 shift).
- background_correction: cross-ref sound_power.background_noise_correction and
document the negative-margin (Lb > Lsb) cap at Lsb−1.3.
- flanking_path: optional kij_min clamp for EN 12354-1 Clause 4.4.2 (Kij≥Kij,min),
documented on flanking_element; tested clamped vs unclamped (H.3 unaffected).
- impact_flanking_correction: comment on nearest-neighbour + tie-to-lower.
- Export COVERAGE_FACTORS (Table 8) as public API.
- single_number_uncertainty: note impact situation A is an estimate (Table 5 fn a).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: conformance checks for facade, lab insulation, EN 12354 and ISO 12999
Add six numerical conformance checks for the PR-B building-acoustics
standards, following the established registry pattern (single source of
truth in tests/reference_data.py, clause citations, honest tolerances):
- ISO 16283-3:2016 Clause 3.12: facade R'45 isolates the -1.5 dB
oblique-incidence correction on an S=A constructed case (exact).
- ISO 10140-2:2021 Formula (2): lab airborne R laid on the ISO 717-1
reference shape (S=A) -> Rw = 54 (the +2-shift analytic anchor).
- EN 12354-1:2000 Annex H.3: airborne prediction R'w = 52 from the 13
transmission paths (direct + 12 flanking), the branch's strongest oracle.
- EN 12354-2:2000 Annex E.3: impact prediction L'n,w = 45 (76-33+2).
- ISO 12999-1:2020 Table 2: airborne band uncertainty, situation A @
1 kHz = 1.8 dB (digit-exact), and Clause 8/Table 8 expanded
uncertainty U = 1.96 u = 2.352 dB (exact k=1.96 arithmetic).
Facade and lab checks join "Room & building acoustics"; the EN 12354 /
ISO 12999 checks form a new "Building prediction & uncertainty" domain.
Registry grows 26 -> 32 checks across 7 domains. Shared expected values
and the Annex H.3 input table move into reference_data.py so the report
and tests cannot drift; a consistency test pins them to the published
worked-example results. docs/CONFORMANCE.md regenerated via make conformance.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: building acoustics II — facade, laboratory, prediction and uncertainty (EN)
Extend the Room & Building Acoustics guide with facade insulation
(ISO 16283-3), laboratory characterisation (ISO 10140), flanking-transmission
performance prediction (EN 12354-1/2) and measurement uncertainty
(ISO 12999-1), with executed snippets reproducing the EN 12354-1 Annex H.3
(R'w = 52 dB) and Annex E.3 (L'n,w = 45 dB) worked examples. Add the matching
theory subsections, API-reference rows for every new public name, README and
landing-page entries (29 -> 33 standards), and CHANGELOG. EN docs and site
twins keep byte-identical code blocks.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: flanking-paths diagram, prediction and uncertainty figures
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de edificación II (fachadas, laboratorio, predicción, incertidumbre)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: PR-B final-review fixes — count, GitHub-safe math, edition alignment
Landing count corrected to 34 standards (slash-part convention); the
GitHub-unsafe math spacing tokens reintroduced by the new building
sections are scrubbed from the docs/ tree and its site twins; the whole
ISO 10140-2 citation chain aligned to the verified :2010 edition; plus
the six minor documentation-precision fixes from the final review.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 88 review round 1 — isfinite idiom, heading levels, frequencies validation
- building_prediction._check_finite: replace 'v != v or v in (inf,-inf)'
NaN idiom with math.isfinite for SonarCloud (same behavior, cleaner).
- room-acoustics docs (EN docs + site, ES site): relevel the three
parameter-table headings that skipped H2->H4 to H3 (MD001).
- insulation.facade_insulation: validate 'frequencies' length against the
band count, raising a clear ValueError instead of deferring a matplotlib
shape error to plot(). lab_insulation/building_uncertainty have no such
gap (no user-supplied frequencies stored). Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Audit-driven accuracy, robustness and didactic improvements across the library (#82)
* fix: audit wave A — inter-sample LCpeak, cheby2 class-1 default, 144 kHz weighting target
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave A — decay validity thresholds, variant sharpness anchors, N5 phase, STIPA warning, Farina guard
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: audit wave B — self-contained snippets, tables, sharpness/open-plan figures, orphans
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave C — remaining minors and optimizations across the library
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: document the audit-wave parameters across guides and API tables
Cover the public parameters added in the audit fix waves across all three
documentation trees (docs/, site EN, site ES):
- lc_peak(..., oversample=8): inter-sample peak recovery (levels guide + API)
- calculate_sensitivity(..., narrowband=False): coherent tone estimator
(calibration guide + API)
- sound_intensity(..., bias_correct=False): finite-difference bias correction
(intensity guide + API)
- room_parameters/decay_curve(..., zero_phase=False): 125 Hz short-T bias
(room-acoustics guide + API)
- OctaveFilterBank/octavefilter attenuation default 60 -> 72 and cheby2
class-1 note (filter-banks guide + API)
- STIPA < 15 s UserWarning (psychoacoustics guide + API)
Also refresh dependent behaviour notes: ln_levels attack skip 2*tau -> 5*tau,
T20/T30 validity thresholds 35/45 -> 46/54 dB, and the zero-phase broadband
~0.2-0.3 dB band-level caveat. Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: truthfulness fixes from the final audit-branch review
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: 2000 Hz, not 2 ms, in the N5/N10 docstring
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 82 review round 1 — bias-correction cutoff, parabolic-peak guard, complexity, snippet self-containment
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Human vibration exposure (ISO 8041-1, ISO 2631, ISO 5349, Directive 2002/44/EC) (#94)
New `human_vibration` module implementing the ISO 8041-1:2017 master
weighting cascade (all nine weightings Wb/Wc/Wd/We/Wf/Wh/Wj/Wk/Wm),
ISO 2631-1/-2/-4 whole-body metrics (running RMS, MTVV, VDV, MSDV, crest
factor, vibration total value, energy-equivalent acceleration), the
ISO 5349-1/-2 hand-arm A(8) arithmetic and vibration-white-finger latency,
and the Directive 2002/44/EC EAV/ELV assessment. Every result object exposes
.plot(); validated to <0.05% against the ISO 8041-1 Annex B tables and the
ISO 5349-2 Annex E worked examples. Includes docs page, figures, setup
diagram, and 7 conformance checks (100%).
EN 12354-6: sound absorption in enclosed spaces (#107)
* feat: EN 12354-6 sound absorption in enclosed spaces
Add the EN 12354-6:2003 prediction of a room's total equivalent sound
absorption area and reverberation time from the absorption of its
surfaces and objects (the normative Clause 4 model).
The absorption area sums the surface contributions, the object areas and
the air absorption (Formula 1), with the air term Aair = 4*m*V*(1 - psi)
(Formula 2, Table 1), the object fraction psi = sum(Vobj)/V (Formula 3)
and the empirical hard-object area Aobj = Vobj^(2/3) (Formula 4). The
reverberation time follows as T = 55.3/c0 * V*(1 - psi)/A (Formula 5,
the 0.16 factor at c0 = 345.6 m/s). The informative Annex D method for
irregular spaces is out of scope.
New module phonometry.en12354_6 with equivalent_absorption_area,
object_fraction, hard_object_absorption, air_absorption_area,
reverberation_time and the per-band enclosed_space_reverberation
returning a ReverberationResult with .plot(). Conformance report gains
an enclosed-space absorption domain (2 checks, 90 total). Adds the
enclosed-space-absorption guide (repo + EN/ES site) with a figure and a
flow diagram.
Validated digit-exact against the three worked cases of Annex E
(A = 2.26/5.03/10.21 m2, T = 2.1/0.9/0.5 s, and the air-absorption
note Aair = 0.12 m2 -> T = 2.0 s).
* refactor: address review feedback on EN 12354-6
- validation helpers _require_positive/_require_fraction: single error
literal and no negated comparison, still rejecting NaN (SonarCloud
S1192/S1940)
- object_fraction: reject negative object volumes and a fraction >= 1
(Gemini)
- air_absorption_area: validate the object fraction range and a
non-negative attenuation coefficient (Gemini)
- equivalent_absorption_area: reject negative air_area, absorption
coefficients and object areas (Gemini)
- reverberation_time: validate the object fraction range (Gemini)
- enclosed_space_reverberation: clear error when a built-in
air_condition is combined with non-standard frequencies (Gemini)
- diagram + alt text: the speed-of-sound symbol is c0, not co
(CodeRabbit); the diagram now renders it as a proper subscript
- tests: cover the speed_of_sound guard (CodeRabbit), NaN volume, the
physical-range validations and the standard-bands guard
* docs: label the object-array sum in the Formula 1 prose
The third summand of Formula 1 is the standard's object arrays k
(groups of identical objects treated as an absorbing surface), not a
duplicated surface term; name all three indices in the surrounding
prose so the equation reads unambiguously (CodeRabbit).
Advanced metrology: G-weighting, ISO 226 contours, ECMA-418-1 tonality, ISO 1996 Lden, IEC 60942:2017 (#76)
* feat: update calibrator validation to IEC 60942:2017 (Ed. 4)
The 2017 edition changes the short-term level fluctuation method (5.3.3):
|max - mean| and |min - mean| against the mean F-time-weighted level, and
tightens the class 1 limit in 160-1250 Hz from 0.10 dB (2003 Table 1) to
0.07 dB (2017 Table 2), with relaxed limits at low frequencies where the
F time-weighting itself ripples. calculate_sensitivity() gains a
frequency= parameter selecting the Table 2 row; max_fluctuation_db=None
now resolves the class 1 limit automatically.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: G frequency weighting for infrasound (ISO 7196:1995)
WeightingFilter/weighting_filter accept curve='G': four zeros at the
origin and the four complex pole pairs of ISO 7196 Table 1 (p. 2),
normalized to 0 dB at 10 Hz per clause 4, bilinear-mapped (no
oversampling needed: the curve acts on 0.25-315 Hz where the plain
design is already exact). CI verifies the response against every
Table 2 nominal value at the exact base-10 third-octave frequencies
(+-0.3 dB) plus the 12 and 24 dB/octave slopes. sel()/ln_levels()
accept 'G' transparently. New docs section EN/ES with an autogenerated
figure (light+dark) overlaying the Table 2 nominals.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 226:2023 normal equal-loudness-level contours
equal_loudness_contour(phon) implements Formula (1) at the 29 preferred
third-octave frequencies of Table 1 (transcribed from the official PDF,
p. 4); loudness_level(spl, frequency) is the exact inverse (Formula 2);
hearing_threshold() exposes the T_f column. Validity enforced per clause
4.1 (20-90 phon, 80 above 4 kHz) and no interpolation between tabulated
frequencies (the standard defines none). Verified in CI against spot
values of the informative Annex B tables B.1/B.2 (+-0.05 dB/phon) plus
the 1 kHz identity and full round-trip. Docs EN/ES with the classic
contour-family figure (light+dark).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: prominent discrete tones - TNR and PR per ECMA-418-1:2024
tone_to_noise_ratio() implements clause 11 (Hann/RMS-averaged FFT, tone
band above the connecting line per Formula 9, masking noise rescaled to
the critical bandwidth per Formula 10, proximate-tone combination per
clause 11.6 Formulae 14-16) and prominence_ratio() implements clause 12
(middle band vs contiguous bands with the fitted Table 2/3 edges of
Formulae 21-22, the 20 Hz-truncated 100 Hz-rescaled lower band of
Formula 24 below 171.4 Hz). Both return a ToneAssessment with the
frequency-dependent prominence criteria (Formulae 12-13 / 25-26).
Critical bands per clause 10 Formulae 2-8. Verified against every
worked example printed in the standard (dfc, band edges, proximity
spacing) and against analytic tones-in-white-noise. Note: the inline
condition printed next to Formula (23) contradicts the clause 12.5
prose (typo in the standard); the prose governs.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: environmental noise descriptors per ISO 1996-1:2016
lden() (3.6.4: +5 dB evening / +10 dB night, default 12/4/8 h periods,
adjustable per country), ldn() (3.6.5, defaults 15/9 h) and
composite_rating_level() (6.5 Formulae 5-6: arbitrary periods with
source/character adjustments, e.g. Table A.1). All reduce to the same
composite form and are verified against hand-computed formula values,
the constant-level analytic offset and period-sum validation.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: changelog for the advanced metrology feature set
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: address advanced-metrology review feedback
- calibration: IEC 60942:2017 Table 2 row boundaries corrected (160 Hz
belongs to the 0.07 dB row, 63 Hz to the 0.20 dB row) with a boundary
regression test
- tonality: tone-band edge walk is safe when the peak sits at a spectrum
boundary; _band_power raises a clear ValueError when the resolution
leaves a band empty (instead of dividing by zero); _fitted_edge clips
marginally out-of-range peaks to the table span
- environmental: composite_rating_level materializes its input (safe for
generators) and rejects an empty period list
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: address second review round
- changelog/docstring wording: the two deviations are each compared
against the limit (not a ratio); 0.07 dB applies at and above 160 Hz
- environmental: periods accepts any Iterable (matches the generator
support and test)
- loudness_contours: document that the 1e-6 relative tolerance only
absorbs float representation error and cannot cross table rows
- tonality: validate resolution_hz > 0 and tone_freq > 0 up front
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: create diagram output dir, hook generate_diagrams into make graphs
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: fourth review round - validation and boundary wording
- composite_rating_level rejects non-finite period durations
- _averaged_spectrum raises a clear error for absurdly coarse
resolution_hz (fewer than 16 samples per segment)
- calibration docs/docstrings state the exact Table 2 boundaries
(0.20 dB at or below 63 Hz; note: the 160 Hz code boundary was
already correct from the previous round)
- weighting page frontmatter mentions G (ISO 7196)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: scope the class 1 HF-accuracy claim to A/C/Z, credit ISO 7196 for G
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Advanced metrology: G-weighting, ISO 226 contours, ECMA-418-1 tonality, ISO 1996 Lden, IEC 60942:2017 (#76)
* feat: update calibrator validation to IEC 60942:2017 (Ed. 4)
The 2017 edition changes the short-term level fluctuation method (5.3.3):
|max - mean| and |min - mean| against the mean F-time-weighted level, and
tightens the class 1 limit in 160-1250 Hz from 0.10 dB (2003 Table 1) to
0.07 dB (2017 Table 2), with relaxed limits at low frequencies where the
F time-weighting itself ripples. calculate_sensitivity() gains a
frequency= parameter selecting the Table 2 row; max_fluctuation_db=None
now resolves the class 1 limit automatically.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: G frequency weighting for infrasound (ISO 7196:1995)
WeightingFilter/weighting_filter accept curve='G': four zeros at the
origin and the four complex pole pairs of ISO 7196 Table 1 (p. 2),
normalized to 0 dB at 10 Hz per clause 4, bilinear-mapped (no
oversampling needed: the curve acts on 0.25-315 Hz where the plain
design is already exact). CI verifies the response against every
Table 2 nominal value at the exact base-10 third-octave frequencies
(+-0.3 dB) plus the 12 and 24 dB/octave slopes. sel()/ln_levels()
accept 'G' transparently. New docs section EN/ES with an autogenerated
figure (light+dark) overlaying the Table 2 nominals.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 226:2023 normal equal-loudness-level contours
equal_loudness_contour(phon) implements Formula (1) at the 29 preferred
third-octave frequencies of Table 1 (transcribed from the official PDF,
p. 4); loudness_level(spl, frequency) is the exact inverse (Formula 2);
hearing_threshold() exposes the T_f column. Validity enforced per clause
4.1 (20-90 phon, 80 above 4 kHz) and no interpolation between tabulated
frequencies (the standard defines none). Verified in CI against spot
values of the informative Annex B tables B.1/B.2 (+-0.05 dB/phon) plus
the 1 kHz identity and full round-trip. Docs EN/ES with the classic
contour-family figure (light+dark).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: prominent discrete tones - TNR and PR per ECMA-418-1:2024
tone_to_noise_ratio() implements clause 11 (Hann/RMS-averaged FFT, tone
band above the connecting line per Formula 9, masking noise rescaled to
the critical bandwidth per Formula 10, proximate-tone combination per
clause 11.6 Formulae 14-16) and prominence_ratio() implements clause 12
(middle band vs contiguous bands with the fitted Table 2/3 edges of
Formulae 21-22, the 20 Hz-truncated 100 Hz-rescaled lower band of
Formula 24 below 171.4 Hz). Both return a ToneAssessment with the
frequency-dependent prominence criteria (Formulae 12-13 / 25-26).
Critical bands per clause 10 Formulae 2-8. Verified against every
worked example printed in the standard (dfc, band edges, proximity
spacing) and against analytic tones-in-white-noise. Note: the inline
condition printed next to Formula (23) contradicts the clause 12.5
prose (typo in the standard); the prose governs.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: environmental noise descriptors per ISO 1996-1:2016
lden() (3.6.4: +5 dB evening / +10 dB night, default 12/4/8 h periods,
adjustable per country), ldn() (3.6.5, defaults 15/9 h) and
composite_rating_level() (6.5 Formulae 5-6: arbitrary periods with
source/character adjustments, e.g. Table A.1). All reduce to the same
composite form and are verified against hand-computed formula values,
the constant-level analytic offset and period-sum validation.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: changelog for the advanced metrology feature set
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: address advanced-metrology review feedback
- calibration: IEC 60942:2017 Table 2 row boundaries corrected (160 Hz
belongs to the 0.07 dB row, 63 Hz to the 0.20 dB row) with a boundary
regression test
- tonality: tone-band edge walk is safe when the peak sits at a spectrum
boundary; _band_power raises a clear ValueError when the resolution
leaves a band empty (instead of dividing by zero); _fitted_edge clips
marginally out-of-range peaks to the table span
- environmental: composite_rating_level materializes its input (safe for
generators) and rejects an empty period list
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: address second review round
- changelog/docstring wording: the two deviations are each compared
against the limit (not a ratio); 0.07 dB applies at and above 160 Hz
- environmental: periods accepts any Iterable (matches the generator
support and test)
- loudness_contours: document that the 1e-6 relative tolerance only
absorbs float representation error and cannot cross table rows
- tonality: validate resolution_hz > 0 and tone_freq > 0 up front
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: create diagram output dir, hook generate_diagrams into make graphs
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: fourth review round - validation and boundary wording
- composite_rating_level rejects non-finite period durations
- _averaged_spectrum raises a clear error for absurdly coarse
resolution_hz (fewer than 16 samples per segment)
- calibration docs/docstrings state the exact Table 2 boundaries
(0.20 dB at or below 63 Hz; note: the 160 Hz code boundary was
already correct from the previous round)
- weighting page frontmatter mentions G (ISO 7196)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: scope the class 1 HF-accuracy claim to A/C/Z, credit ISO 7196 for G
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Advanced metrology: G-weighting, ISO 226 contours, ECMA-418-1 tonality, ISO 1996 Lden, IEC 60942:2017 (#76)
* feat: update calibrator validation to IEC 60942:2017 (Ed. 4)
The 2017 edition changes the short-term level fluctuation method (5.3.3):
|max - mean| and |min - mean| against the mean F-time-weighted level, and
tightens the class 1 limit in 160-1250 Hz from 0.10 dB (2003 Table 1) to
0.07 dB (2017 Table 2), with relaxed limits at low frequencies where the
F time-weighting itself ripples. calculate_sensitivity() gains a
frequency= parameter selecting the Table 2 row; max_fluctuation_db=None
now resolves the class 1 limit automatically.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: G frequency weighting for infrasound (ISO 7196:1995)
WeightingFilter/weighting_filter accept curve='G': four zeros at the
origin and the four complex pole pairs of ISO 7196 Table 1 (p. 2),
normalized to 0 dB at 10 Hz per clause 4, bilinear-mapped (no
oversampling needed: the curve acts on 0.25-315 Hz where the plain
design is already exact). CI verifies the response against every
Table 2 nominal value at the exact base-10 third-octave frequencies
(+-0.3 dB) plus the 12 and 24 dB/octave slopes. sel()/ln_levels()
accept 'G' transparently. New docs section EN/ES with an autogenerated
figure (light+dark) overlaying the Table 2 nominals.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 226:2023 normal equal-loudness-level contours
equal_loudness_contour(phon) implements Formula (1) at the 29 preferred
third-octave frequencies of Table 1 (transcribed from the official PDF,
p. 4); loudness_level(spl, frequency) is the exact inverse (Formula 2);
hearing_threshold() exposes the T_f column. Validity enforced per clause
4.1 (20-90 phon, 80 above 4 kHz) and no interpolation between tabulated
frequencies (the standard defines none). Verified in CI against spot
values of the informative Annex B tables B.1/B.2 (+-0.05 dB/phon) plus
the 1 kHz identity and full round-trip. Docs EN/ES with the classic
contour-family figure (light+dark).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: prominent discrete tones - TNR and PR per ECMA-418-1:2024
tone_to_noise_ratio() implements clause 11 (Hann/RMS-averaged FFT, tone
band above the connecting line per Formula 9, masking noise rescaled to
the critical bandwidth per Formula 10, proximate-tone combination per
clause 11.6 Formulae 14-16) and prominence_ratio() implements clause 12
(middle band vs contiguous bands with the fitted Table 2/3 edges of
Formulae 21-22, the 20 Hz-truncated 100 Hz-rescaled lower band of
Formula 24 below 171.4 Hz). Both return a ToneAssessment with the
frequency-dependent prominence criteria (Formulae 12-13 / 25-26).
Critical bands per clause 10 Formulae 2-8. Verified against every
worked example printed in the standard (dfc, band edges, proximity
spacing) and against analytic tones-in-white-noise. Note: the inline
condition printed next to Formula (23) contradicts the clause 12.5
prose (typo in the standard); the prose governs.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: environmental noise descriptors per ISO 1996-1:2016
lden() (3.6.4: +5 dB evening / +10 dB night, default 12/4/8 h periods,
adjustable per country), ldn() (3.6.5, defaults 15/9 h) and
composite_rating_level() (6.5 Formulae 5-6: arbitrary periods with
source/character adjustments, e.g. Table A.1). All reduce to the same
composite form and are verified against hand-computed formula values,
the constant-level analytic offset and period-sum validation.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: changelog for the advanced metrology feature set
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: address advanced-metrology review feedback
- calibration: IEC 60942:2017 Table 2 row boundaries corrected (160 Hz
belongs to the 0.07 dB row, 63 Hz to the 0.20 dB row) with a boundary
regression test
- tonality: tone-band edge walk is safe when the peak sits at a spectrum
boundary; _band_power raises a clear ValueError when the resolution
leaves a band empty (instead of dividing by zero); _fitted_edge clips
marginally out-of-range peaks to the table span
- environmental: composite_rating_level materializes its input (safe for
generators) and rejects an empty period list
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: address second review round
- changelog/docstring wording: the two deviations are each compared
against the limit (not a ratio); 0.07 dB applies at and above 160 Hz
- environmental: periods accepts any Iterable (matches the generator
support and test)
- loudness_contours: document that the 1e-6 relative tolerance only
absorbs float representation error and cannot cross table rows
- tonality: validate resolution_hz > 0 and tone_freq > 0 up front
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: create diagram output dir, hook generate_diagrams into make graphs
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: fourth review round - validation and boundary wording
- composite_rating_level rejects non-finite period durations
- _averaged_spectrum raises a clear error for absurdly coarse
resolution_hz (fewer than 16 samples per segment)
- calibration docs/docstrings state the exact Table 2 boundaries
(0.20 dB at or below 63 Hz; note: the 160 Hz code boundary was
already correct from the previous round)
- weighting page frontmatter mentions G (ISO 7196)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: scope the class 1 HF-accuracy claim to A/C/Z, credit ISO 7196 for G
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Building acoustics II: facade insulation (ISO 16283-3), laboratory measurement (ISO 10140), prediction with flanking (EN 12354) and uncertainty (ISO 12999-1) (#88)
* feat: ISO 16283-3 facade sound insulation
Add facade_insulation() and FacadeInsulationResult for field facade sound
insulation per ISO 16283-3: the global-method level difference D2m and its
standardized (D2m,nT) and normalized (D2m,n) forms, plus the element-method
apparent sound reduction index R'45 (loudspeaker, -1,5 dB) / R'tr,s (road
traffic, -3 dB). Reuses the ISO 16283-1 energy-averaging helpers, the
Sabine absorption area, and the ISO 717-1 airborne weighted_rating engine
unchanged for the single-number rating. Adds a per-band .plot() profile and
exports.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 16283-3 draft-edition caveat and symmetric R' validation
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 10140 laboratory airborne and impact sound insulation
Add src/phonometry/lab_insulation.py, the laboratory counterpart of the
field ISO 16283 family:
- lab_airborne_insulation: R = L1 - L2 + 10 lg(S/A) with A = 0,16 V/T
(ISO 10140-2:2010 Formula (2); ISO 10140-4:2010 Formula (5)).
- lab_impact_insulation: Ln = Li + 10 lg(A/A0), A0 = 10 m²
(ISO 10140-3:2010 Formula (1)).
- background_correction: ISO 10140-4:2010 Clause 4.3 Formula (4) with the
6/15 dB criteria and the 1,3 dB limit-of-measurement cap.
Single-number Rw / Ln,w with C, Ctr, CI reuse the verified ISO 717-1/2
weighted_rating / weighted_impact_rating engines; position energy
averaging reuses _as_band_levels. 29 new closed-form / identity tests.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: reconcile facade citations with published ISO 16283-3:2016
The facade module was drafted from ISO/DIS 16283-3:2014; its docstrings
cited draft formula numbers with a caveat to reconcile against the
published edition. Reconciled against ISO 16283-3:2016(E) (First edition
2016-02-01):
- Clause numbers are edition-stable: the field-quantity definitions keep
3.12 (R'45deg), 3.13 (R'tr,s), 3.14 (D2m), 3.15 (D2m,nT), 3.16 (D2m,n),
3.17 (A=0,16V/T), and 9.5.1 (surface-level averaging).
- Formula numbers differ: in the 2016 edition the Clause 3 defining
formulas are UNNUMBERED (inline in the term definitions); the numbered
formulas (1)-(21) live in the procedural clauses. The surface-level
energy-average is Formula (7) in 2016 (was Formula (20) in the DIS).
- Dropped the invalid draft Formula (2)-(7) citations from the field-
quantity docstrings and the draft-edition reconciliation caveat.
- Constants and formula bodies are unchanged (-1,5 dB / -3 dB, A0=10 m2,
T0=0,5 s, A=0,16V/T, band ranges): no numeric change; 22 facade tests
green.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: EN 12354-1/2 building acoustic performance prediction with flanking
Implement the EN 12354:2000 simplified single-number prediction model for
apparent airborne (R'w) and impact (L'n,w) sound insulation, including direct
plus flanking transmission (Ff/Df/Fd paths) and the Annex E vibration-reduction
index Kij for rigid cross, rigid T, flexible-interlayer and lightweight-facade
junctions.
Airborne Formula (26)/(27)/(28a) with l0=1 m, Kij,min (29), lining composition
(30)/(31); impact Formula (21) with bare-floor Ln,w,eq (164-35 lg m'), Table 1
flanking correction K, and standardized L'nT,w (3). Results expose per-path
energy contributions so the dominant flanking path is visible.
Validated against the standards' worked examples: Part 1 Annex H.3 airborne
(R'w = 52 dB, 13 paths; +floating-floor 53 dB) and Part 2 Annex E.3 impact
(L'n,w = 45 dB, L'nT,w = 43 dB); all four Annex H junctions reproduce Kij.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 12999-1 building acoustics measurement uncertainty
Add building_uncertainty module implementing ISO 12999-1:2020 standard
uncertainties for sound-insulation quantities: per-band and single-number
values for airborne (Tables 2/3), impact (Tables 4/5) and floor-covering
reduction (Tables 6/7), across measurement situations A/B/C (Clause 5.2),
plus the Annex D sigma_R95 upper limits, Table 1 max repeatability and
Table 8 coverage factors.
Expansion U = k*u with k >= 1 (Clause 8), one-sided factors for conformity
checks (Formulae 4/5), and combination rules from Annexes A/B/C (prediction
input A.1, quadrature A.2/C.2, m-measurement reduction A.7, uncorrelated
single-number B.2). Composable UncertainValue attaches value +- U without
touching the rating dataclasses.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: numpy-stub-agnostic annotations for the two env-dependent mypy sites
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: PR-B review minors — anchors, Kij floor, uncertainty polish
- Table 3 r_w+ctr_50_5000 σsitu(B)=1.0 verified digit-by-digit against the
ISO 12999-1:2020(E) page image; anomalous but normative (comment added).
- Anchor the ISO 10140 rating tests to independent literals (Rw=54, Ln,w=58 —
reference-curve shape + the 32 dB / 16-band = 2 dB ISO 717 shift).
- background_correction: cross-ref sound_power.background_noise_correction and
document the negative-margin (Lb > Lsb) cap at Lsb−1.3.
- flanking_path: optional kij_min clamp for EN 12354-1 Clause 4.4.2 (Kij≥Kij,min),
documented on flanking_element; tested clamped vs unclamped (H.3 unaffected).
- impact_flanking_correction: comment on nearest-neighbour + tie-to-lower.
- Export COVERAGE_FACTORS (Table 8) as public API.
- single_number_uncertainty: note impact situation A is an estimate (Table 5 fn a).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: conformance checks for facade, lab insulation, EN 12354 and ISO 12999
Add six numerical conformance checks for the PR-B building-acoustics
standards, following the established registry pattern (single source of
truth in tests/reference_data.py, clause citations, honest tolerances):
- ISO 16283-3:2016 Clause 3.12: facade R'45 isolates the -1.5 dB
oblique-incidence correction on an S=A constructed case (exact).
- ISO 10140-2:2021 Formula (2): lab airborne R laid on the ISO 717-1
reference shape (S=A) -> Rw = 54 (the +2-shift analytic anchor).
- EN 12354-1:2000 Annex H.3: airborne prediction R'w = 52 from the 13
transmission paths (direct + 12 flanking), the branch's strongest oracle.
- EN 12354-2:2000 Annex E.3: impact prediction L'n,w = 45 (76-33+2).
- ISO 12999-1:2020 Table 2: airborne band uncertainty, situation A @
1 kHz = 1.8 dB (digit-exact), and Clause 8/Table 8 expanded
uncertainty U = 1.96 u = 2.352 dB (exact k=1.96 arithmetic).
Facade and lab checks join "Room & building acoustics"; the EN 12354 /
ISO 12999 checks form a new "Building prediction & uncertainty" domain.
Registry grows 26 -> 32 checks across 7 domains. Shared expected values
and the Annex H.3 input table move into reference_data.py so the report
and tests cannot drift; a consistency test pins them to the published
worked-example results. docs/CONFORMANCE.md regenerated via make conformance.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: building acoustics II — facade, laboratory, prediction and uncertainty (EN)
Extend the Room & Building Acoustics guide with facade insulation
(ISO 16283-3), laboratory characterisation (ISO 10140), flanking-transmission
performance prediction (EN 12354-1/2) and measurement uncertainty
(ISO 12999-1), with executed snippets reproducing the EN 12354-1 Annex H.3
(R'w = 52 dB) and Annex E.3 (L'n,w = 45 dB) worked examples. Add the matching
theory subsections, API-reference rows for every new public name, README and
landing-page entries (29 -> 33 standards), and CHANGELOG. EN docs and site
twins keep byte-identical code blocks.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: flanking-paths diagram, prediction and uncertainty figures
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de edificación II (fachadas, laboratorio, predicción, incertidumbre)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: PR-B final-review fixes — count, GitHub-safe math, edition alignment
Landing count corrected to 34 standards (slash-part convention); the
GitHub-unsafe math spacing tokens reintroduced by the new building
sections are scrubbed from the docs/ tree and its site twins; the whole
ISO 10140-2 citation chain aligned to the verified :2010 edition; plus
the six minor documentation-precision fixes from the final review.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 88 review round 1 — isfinite idiom, heading levels, frequencies validation
- building_prediction._check_finite: replace 'v != v or v in (inf,-inf)'
NaN idiom with math.isfinite for SonarCloud (same behavior, cleaner).
- room-acoustics docs (EN docs + site, ES site): relevel the three
parameter-table headings that skipped H2->H4 to H3 (MD001).
- insulation.facade_insulation: validate 'frequencies' length against the
band count, raising a clear ValueError instead of deferring a matplotlib
shape error to plot(). lab_insulation/building_uncertainty have no such
gap (no user-supplied frequencies stored). Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Audit-driven accuracy, robustness and didactic improvements across the library (#82)
* fix: audit wave A — inter-sample LCpeak, cheby2 class-1 default, 144 kHz weighting target
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave A — decay validity thresholds, variant sharpness anchors, N5 phase, STIPA warning, Farina guard
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: audit wave B — self-contained snippets, tables, sharpness/open-plan figures, orphans
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave C — remaining minors and optimizations across the library
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: document the audit-wave parameters across guides and API tables
Cover the public parameters added in the audit fix waves across all three
documentation trees (docs/, site EN, site ES):
- lc_peak(..., oversample=8): inter-sample peak recovery (levels guide + API)
- calculate_sensitivity(..., narrowband=False): coherent tone estimator
(calibration guide + API)
- sound_intensity(..., bias_correct=False): finite-difference bias correction
(intensity guide + API)
- room_parameters/decay_curve(..., zero_phase=False): 125 Hz short-T bias
(room-acoustics guide + API)
- OctaveFilterBank/octavefilter attenuation default 60 -> 72 and cheby2
class-1 note (filter-banks guide + API)
- STIPA < 15 s UserWarning (psychoacoustics guide + API)
Also refresh dependent behaviour notes: ln_levels attack skip 2*tau -> 5*tau,
T20/T30 validity thresholds 35/45 -> 46/54 dB, and the zero-phase broadband
~0.2-0.3 dB band-level caveat. Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: truthfulness fixes from the final audit-branch review
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: 2000 Hz, not 2 ms, in the N5/N10 docstring
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 82 review round 1 — bias-correction cutoff, parabolic-peak guard, complexity, snippet self-containment
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Advanced psychoacoustics (ISO 532-2/3 Moore-Glasberg, ECMA-418-2 Sottek loudness/tonality/roughness) + numerical conformance report (#87)
* feat: ECMA-418-2 Sottek Hearing Model loudness and auditory front-end
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic loudness metric and
its shared auditory front-end (Sottek Hearing Model):
- Clause 5 front-end: outer/middle-ear cascade (5.1.3, Table 1), 53-band
gammatone-like auditory filter bank (5.1.4), band-dependent segmentation
(5.1.5, Table 4), rectification/RMS (5.1.6-5.1.7), compressive nonlinearity
(5.1.8, Formula 23, Table 2) and threshold in quiet (5.1.9, Table 3).
- Clause 6.2.2-6.2.7 ACF-based tonal/noise specific loudness (reusable by the
later tonality/roughness metrics).
- Clause 8 assembly: tonal/noise power average, average specific loudness,
time-dependent and single representative loudness value.
The front-end helpers are factored for reuse. Calibration verified: a 1 kHz
40 dB SPL sinusoid yields 0.996 sone_HMS (target 1.0). Adds a frozen
EcmaLoudness result with lazy .plot(), exports, and 12 tests.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 tonality (Sottek Hearing Model)
Implements the psychoacoustic tonality metric of ECMA-418-2:2025
(Clause 6.2.8-6.2.11) on top of the committed Sottek front-end and
ACF tonal/noise decomposition.
- tonality_ecma() -> EcmaTonality with the single value T (Formula 63),
average specific tonality T'(z) (Formula 53), tonal frequencies
f_ton,z(z) (Formula 55), time-dependent tonality T(l) (Formula 61)
and its frequency f_ton(l) (Formula 62); optional user band [f_low,f_high].
- Full Clause 6.2.3 band averaging with cross-block-size-group ACF
recomputation (loudness keeps its simplified, valid-for-loudness path;
boundary marker added there). Loudness 0.996 sone guard unchanged.
- _tonal_estimate now also returns the DFT-peak tonal frequency, with the
argmax restricted to the lower DFT half to avoid Nyquist-mirror aliasing.
- .plot() via _plotting.plot_ecma_tonality; exports in __init__.
- 13 conformance tests: 1 kHz/40 dB -> 1 tu_HMS calibration, pure tone
>> noise, f_ton tracks the tone, silence -> 0, band restriction.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 roughness (Sottek Hearing Model)
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic roughness metric
(Clause 7), the third Sottek Hearing Model output and new library
functionality. Reuses the Clause 5 auditory front-end from loudness_ecma
(ear filter, 53-band gammatone bank, specific basis loudness) and adds the
roughness-specific chain: start-only zero-padding and fixed 16384/4096
segmentation (5.1.2.2, 5.1.5.2, 7.1.1), the Hilbert envelope with x32
downsampling to 1500 Hz (7.1.2), the scaled envelope power spectrum
(7.1.3), two-step noise reduction (7.1.4), the four-stage spectral
weighting with quadratic-fit modulation-rate refinement, bias correction,
high/low modulation-rate weighting and fundamental-rate estimation
(7.1.5), interpolation to 50 Hz with the distribution-dependent nonlinear
transform, tabulated calibration c_R and asymmetric smoothing (7.1.7), and
the R'(z) / R(l50) / 90th-percentile aggregation (7.1.8-7.1.10).
Uses the standard's tabulated c_R = 0.0180685 (Formula 104), not
reverse-fit: the 1 kHz / 70 Hz / m=1 / 60 dB calibration signal yields
R = 1.0735 asper (near-but-not-exactly 1). OCR-ambiguous formulae
(66, 71, 78-81) cross-checked against the SQAT MATLAB reference; the
optional entropy weighting (7.1.6, needs RPM telemetry) is documented but
not implemented.
Adds roughness_ecma()/EcmaRoughness, a two-panel .plot(), exports, a
10-test TDD suite and plan/notes-ecma418-2-roughness.md. Full suite
752 passed / 12 skipped; loudness (0.996) and tonality (0.99993) oracles
unchanged (both modules untouched).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* test: pin ECMA-418-2 roughness calibration value and document clean-room variance
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-2 Moore-Glasberg loudness
Add the stationary Moore-Glasberg loudness model of ISO 532-2:2017 as a
third loudness method alongside the Zwicker (ISO 532-1) and Sottek
(ECMA-418-2) implementations. The full Clause 7 chain is implemented:
fixed outer/middle-ear transfer (Table 1), the level-dependent roex
auditory filter bank and excitation pattern on the ERB-number scale
(Formulae 1-6), the compressive specific-loudness transform (Formulae
7-9, Tables 2-4, C=0.0617 sone/Cam), binaural inhibition (Formulae
10-13) and integration to total loudness with the phon mapping of
Table 5.
Public API: loudness_moore_glasberg_from_spectrum (exact 5.2/5.4),
loudness_moore_glasberg_from_third_octave (29 bands, 5.5) and
loudness_moore_glasberg (signal wrapper), returning a frozen
MooreGlasbergLoudness with .plot() over the ERB-number scale.
Validated against the Annex B reference signals (tones, white/pink
noise, multi-tone complexes, tone-in-noise) to ~1-4%, well inside the
standard's 2.8 phon expanded uncertainty; the definitional anchor
(1 kHz/40 dB/free/binaural = 1.000 sone) holds end-to-end from the
tabulated constant. 50 new tests; full suite 802 passed / 12 skipped.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-3 Moore-Glasberg-Schlittenlacher time-varying loudness
Add loudness_moore_glasberg_time() implementing the ISO 532-3:2023 method:
a six-FFT multi-resolution running short-term spectrum (clause 7.3) feeding
the ISO 532-2 excitation and specific-loudness model on the 0.25 Cam grid
with the 532-3 constants (C=0.063, Tables 2/4, E_THRQ/E0=2.307), integrated
by asymmetric attack/release smoothing into short-term (clauses 7.6-7.8) and
long-term (clause 7.9) loudness traces, with peak long-term loudness,
percentiles and a lazy STL/LTL .plot().
Validated against the Annex C.1 tone table (1 kHz 10-80 dB, 3 kHz, 4 kHz,
100 Hz and monaural earphone) to within 0.4 phon, well inside the 2.8 phon
expanded uncertainty; the 1 kHz/40 dB anchor is exact at 1.000 sone. The
ISO 532-2 and ECMA-418-2 oracles are unchanged (831 passed / 12 skipped).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-3 per-ear LTL and Formula-14 kernel width (dichotic path)
Two transcription errors in the ISO 532-3:2023 dichotic (per-ear) binaural
path of the time-varying Moore-Glasberg-Schlittenlacher module:
1. Clause 7.9: the long-term loudness must be computed per ear by the
attack/release averager (Formulae 18-21) and then summed. The code ran a
single AGC on the binaural sum S'_L + S'_R; because the attack/release
branch is nonlinear, AGC(S'_L) + AGC(S'_R) != AGC(S'_L + S'_R) for dichotic
input. Keep two LTL states and sum the two long-term loudnesses. For
diotic/monaural the averager is linear in the (scaled) input so the result
is bit-for-bit identical to the old sum-then-AGC.
2. Formula (14)/(15): the smoothing weight is exp(-(B*Di)^2) with B = 0.08 and
Di in Cam. The code evaluated exp(-((0.08 * Di/0.1)^2)) = exp(-(0.8*Di)^2),
a Gaussian ~10x too narrow, and truncated the taps at +/-1.75 Cam. Remove
the spurious /0.1 and widen the kernel to the standard's +/-18 Cam (145 taps
on the 0.25-Cam grid). For diotic/monaural the L/R excitation ratio is 1
regardless of the kernel, so all oracles are unaffected.
No current validation signal exercises the dichotic path (Annex C signals are
diotic/monaural), so the 532-3 tone oracle and every other oracle remain
byte-identical; added focused dichotic tests plus a byte-identical anchor pin.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: numerical conformance report in CI PR comment
Reframe the PR comment into a persistent numerical conformance report:
each implemented standard -> normative expected value/range -> computed
result -> pass/fail with deviation, grouped by domain.
- scripts/conformance_report.py: registry of 21 checks across 17 standards
and 6 domains (filters/weightings, levels/dosimetry, psychoacoustics,
speech, intensity/power, room/building). Emits a headline summary, a
"Numerical validation - filters & weightings" section (per-architecture
IEC 61260-1 class margins + A/C/G weighting deviation vs the normative
curve) and one conformance table per domain. Deterministic, ~2.7 s.
- tests/reference_data.py: single source of truth for the shared normative
tables (IEC 61672-1 Table 3, ISO 7196 Table 2, ISO 717-1 Annex C R),
imported by both the tests and the report so they cannot drift.
- tests/test_conformance_report.py: smoke test asserting every registered
check passes and the Markdown is well-formed.
- comment_pr.py / workflow: conformance report leads the comment; the
test/coverage table is collapsed below. Retire benchmark_filters.py
(numerical content absorbed into the report).
make check green (740 passed, 12 skipped); ruff, mypy --strict, bandit clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: block-A review minors — docstring notes and tonality band-range
Four Minor findings from the plan-17 block-A psychoacoustics reviews. No
change to any validated numeric output; all five oracles stay byte-identical.
1. ISO 532-2 _source_levels: docstring note that clause 7.4 specifies a roex
weighting for X where the code uses a rectangular +/-ERB_n/2 window —
identical for isolated tones, immaterial for the Annex B broadband cases.
2. ISO 532-2 Formula-10 binaural smoothing kernel: the PDF (clause 8.1) states
"Di is changed in steps of 0,1" over -18..+18 Cam; the code confused the
0.1-Cam index offset with Di-in-Cam (the same /0.1 width bug just fixed in
532-3 Formula-14), making the Gaussian 10x too narrow and truncating to
+/-1.8 Cam. Widen to +/-18 Cam (361 taps) with Di = tap*_I_STEP. Provably
inert: all Annex B cases are diotic (ratio 1) or monaural (unit inhibition).
3. ECMA tonality _band_range: implement the exact Formulae 56-57 edge-midpoint
boundary conditions instead of a centre-frequency threshold; only affects
the optional user-band feature. Added a user-band edge test.
4. ECMA tonality reach: comment that the _CBF-1-band upper-edge term is a dead
defensive guard (N_B>0 only for low bands 0..24, so it never limits reach).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* chore: move conformance design doc out of repo root
* feat: clearer conformance report (value-vs-range) + block-A checks + audit fixes
Table 1 (filters): show, per architecture, the measured relative attenuation
at the binding band vs the class-1 limit it must clear (value + range, not
just a margin). Replace the "none" verdict (read as a failure) with a
"By design (ripple/soft rolloff)" label + footnote; Butterworth (default) and
Chebyshev-II are the only mask-compliant architectures. The measured value and
limit are re-derived with the public class_limits on the same designed SOS; a
smoke-test guard asserts the re-derived class-1 margin matches
verify_filter_class (single source of truth).
Table 2 (weightings): separate the informational max-deviation-from-nominal
(at a frequency extreme with wide, asymmetric tolerance) from the compliance
margin at the binding frequency, where the deviation, the +/- tolerance band
and the headroom are co-located so "value vs range" is unambiguous and in-spec.
Audit: verified all 21 checks (expected value/range vs the standard, real
library call, honest tolerance). One fix - ISO 226 anchored at 60 phon @ 2 kHz
-> 60 dB, a genuine Table B.1 value but coincidentally equal to the phon number
(reads as the trivial 1 kHz identity); re-anchored to the non-coincidental
60 phon @ 100 Hz -> 78.5 dB, sourced from reference_data. No library bug found.
Block-A psychoacoustics (+5, single-source expected in reference_data):
ECMA-418-2 loudness (1 kHz/40 dB -> 1 sone_HMS, c_N), tonality (-> 1 tu_HMS,
c_T), roughness (1 kHz/70 Hz/m=1/60 dB -> clean-room 1.0735 asper with a note
that the standard target is 1.0); ISO 532-2 (1 kHz/40 dB -> 1 sone, C=0.0617);
ISO 532-3 (steady 1 kHz/40 dB -> peak LTL 1 sone). 26/26 checks, 20 standards.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: versioned docs/CONFORMANCE.md with make target, CI staleness check and README link
Persist the numerical conformance report as a committed, versioned file so
every reader sees the library's per-standard validation (expected vs computed)
without running CI.
- docs/CONFORMANCE.md: committed report (26/26 checks, 6 domains, 20 standards)
with an auto-generated "do not hand-edit" header linking the standards docs.
- scripts/conformance_report.py: add a --file-header flag that emits that header
for the committed file (PR-comment body stays header-free); coarsen the
informational delta column to 3 decimals so BLAS/FFT-dependent sub-milli
residuals do not churn the file (status/computed columns keep regressions
visible).
- Makefile: `make conformance` regenerates the file; `make install-hooks`
installs the optional pre-commit hook.
- CI: new fast `conformance` job (runtime deps only) fails a PR if the committed
file is stale, telling the author to run `make conformance` and commit.
- hooks/pre-commit: opt-in convenience hook, scoped to src/scripts/reference
changes; CI is the enforcement.
- README + CONTRIBUTING: link and document the generated report.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced psychoacoustics guide, theory, API (EN)
Document the plan-17 block-A advanced psychoacoustics models: Moore-Glasberg
loudness (ISO 532-2/532-3), Sottek Hearing Model loudness, tonality and
roughness (ECMA-418-2:2025).
- Psychoacoustics guide: new "Advanced loudness & sound-quality models"
section with a model-comparison table, executed snippets, parameter tables,
res.plot() one-liners and figure-code collapsibles (docs + site twin,
code blocks byte-identical).
- Theory: excitation-pattern/specific-loudness chain, Sottek front-end,
ACF tonality and envelope-modulation roughness (GitHub-safe math).
- API reference: rows for the 5 functions (+ 2 MG spectral entry points) and
5 result dataclasses with their .plot().
- README, EN landing (standards 26 -> 29: ISO 532-2, ISO 532-3, ECMA-418-2),
CHANGELOG Unreleased/Added, llms regen.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced-psychoacoustics figures (loudness models, Sottek, tonality/roughness, time-varying)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de psicoacústica avanzada (Moore-Glasberg, Sottek, tonalidad, aspereza)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-2 signal-input path builds a narrowband spectrum (pure-tone loudness)
The loudness_moore_glasberg(x, fs) wrapper routed the signal through the
one-third-octave method, which smeared a pure tone across a whole 1/3-octave
band and used a coherent-gain window normalisation that over-counted power by
1.76 dB. A calibrated 1 kHz tone at 40 dB SPL therefore returned 1.327 sone
instead of the definitional anchor of 1.000 sone.
ISO 532-2 is spectrum-based and its exact input (clauses 5.2/5.4) is a set of
discrete sinusoidal components; the excitation pattern (Formula 5) is computed
per component, so a tone must enter as a single line. The wrapper now forms the
signal's narrowband (FFT) line spectrum with a power-preserving (Parseval)
window normalisation and feeds it to loudness_moore_glasberg_from_spectrum. A
1 kHz/40 dB tone now yields 1.0001 sone / 40 phon and multi-tone signals match
the exact spectrum path. The validated spectrum and third-octave paths are
byte-identical.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: correct MG signal-path mechanism text and presentation default
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 87 review round 1 — float-equality guards, 532-3 FFT window length, tonality short-signal, report/figure fixes
- roughness_ecma: replace three float == 0.0 checks with abs>eps guards
(SonarCloud gate); roughness oracle byte-identical (1.0735).
- loudness_moore_glasberg_time: per-window FFT length (>= window length) so
the 64 ms window is not truncated at 44.1/48 kHz (was ~0.74 dB low in the
20-80 Hz band). 32 kHz anchor byte-identical (n_max 1.0000044713237626).
- tonality_ecma: short signals fall back to averaging all blocks (matches
loudness_ecma), not the final block only; validated anchor unaffected.
- conformance_report: document deliberate 273.0 (speed of sound) vs 273.15
(C1/C2) split matching the library; complete bind_side docstring.
- generate_graphs: roughness annotation uses computed peak fmod; Sottek
metrics use tu_HMS/sone_HMS units; ES translations + 8 figures regenerated.
- hooks/pre-commit: drop set -e so a benign regen failure does not block the
commit (CI staleness check is the gate).
- test_iec_weighting_table3 docstring, Makefile .PHONY completion.
All 5 psychoacoustics oracles byte-identical; make check 868 passed / 12
skipped; ruff + mypy + bandit clean; conformance 26/26.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: localize decimal commas in Sottek-unit figure labels (underscore guard)
The `_` guard in `_translate_figure` skipped decimal-comma localization for
any label containing an underscore. The new Sottek/HMS unit tokens
(`sone_HMS`, `tu_HMS`, `sonios_HMS`) carry underscores, so Spanish figures
regressed to dots (`8.0`, `0.42`) instead of commas (`8,0`, `0,42`).
Drop the underscore guard: the decimal-comma regex only rewrites a bare
`digit.digit` not adjacent to more digits/dots, so unit identifiers and
version numbers (5.3.3) stay intact while genuine decimals get commas.
Mathtext is still skipped via the `$` guard. Regenerated the affected ES
figures (sottek_specific_loudness, tonality_roughness_demo).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Room and building acoustics: ISO 18233, ISO 3382-1/2/3, ISO 16283-1 + ISO 717-1 (#81)
* feat: ISO 18233 sweep/MLS impulse-response acquisition
Add src/phonometry/room_ir.py implementing the ISO 18233:2006
deterministic-excitation IR front end: exponential sine sweep with exact
analytic phase (Annex B), linear spectral-division deconvolution with a
Farina inverse-filter option for harmonic separation (B.5), and
maximum-length-sequence generation (LFSR, orders 2-20) with circular
cross-correlation recovery (Annex A).
Validated against closed forms: known IIR bandpass recovered within 0.1 dB
in band (sweep and MLS), ideal chain to a band-limited delta, +3 dB SNR per
sweep-duration doubling (B.6), and all MLS orders verified as true
maximum-length sequences (autocorrelation L / -1).
433 passed, 12 skipped; ruff, mypy --strict and bandit clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 3382 room acoustic parameters from impulse responses
Add src/phonometry/room_acoustics.py implementing ISO 3382-1:2009 and
ISO 3382-2:2008 analysis of measured impulse responses:
- decay_curve(): Schroeder backward integration of the squared IR
(5.3.3, Eq. 1) with background-noise truncation at the noise/slope
crossing and exponential tail compensation (Eq. 3), broadband or per
IEC 61260 fractional-octave band.
- room_parameters(): per-band EDT (0/-10 dB, A.2.2), T20 (-5/-25 dB)
and T30 (-5/-35 dB) by least-squares fits (ISO 3382-2 Annex C),
clarity C50/C80 (Eq. A.10), definition D50 (Eq. A.11) and centre
time Ts (Eq. A.13), octave bands 125 Hz-4 kHz by default with a
one-third-octave option and a broadband mode.
- Validity flags per the 5.3.3 dynamic-range criterion (noise at least
evaluation range + 15 dB below the IR maximum: 25/35/45 dB) plus the
Annex B curvature indicator C = 100*(T30/T20 - 1).
Tests validate against closed forms for exponential decays (EDT = T20 =
T30 = T within 1 %; C_te = 10*lg(exp(13.8155*te/T) - 1); D50 =
1 - exp(-0.6908/T); Ts = T/13.8155), the exact C50/D50 relation
(Eq. A.12), double-slope decays (EDT < T20 < T30) and noise-driven
validity-flag behaviour, with tolerances far below the Table A.1 JNDs.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* chore: move figure/diagram generators to scripts/
generate_graphs.py and generate_diagrams.py join benchmark_filters.py
and gen_llms.py in scripts/, where dev tooling already lives. Updated
the Makefile graphs target, the sys.path bootstrap in the graph guard
tests and in generate_graphs.py itself (src/ is now one level up), the
CONTRIBUTING instructions and the theme-images.css pointer. Verified:
module loads from the new location and regenerating the SVG diagrams
produces byte-identical output.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 3382-3 open-plan office spatial metrics
Add open_plan_metrics() computing the ISO 3382-3:2012 single-number
quantities from a line of workstation measurements: spatial decay rate
D2,S and nominal speech level Lp,A,S,4m (Clause 6.2, Eq.5; 2-16 m
positions on a log-distance regression) and distraction/privacy
distances rD/rP (Clause 6.3; STI-vs-distance linear regression crossing
0,50 and 0,20). Returns a frozen OpenPlanResult; validates the minimum
of four positions (5.2.2) and equal array lengths.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 16283-1 field insulation and ISO 717-1 weighted ratings
Add phonometry.insulation implementing field airborne sound insulation
(ISO 16283-1:2014) and single-number weighted ratings with C/Ctr
(ISO 717-1):
- airborne_insulation: per-band level difference D (Formula (1)),
standardized level difference DnT = D + 10 lg(T/T0) (Formula (2)) and
apparent sound reduction index R' = D + 10 lg(S/A), A = 0,16 V/T
(Formula (4)/(5)); energy-averages microphone positions (Formula (9)).
- weighted_rating: reference-curve shifting method (Clause 4.4, Table 3)
with the 32,0/10,0 dB unfavourable-deviation bound, plus C/Ctr from the
Table 4 spectra (Clause 4.5). Reference values, spectra and method are
identical in the 2013 and 2020 editions.
- energy_average_level: ISO 16283-1 Formula (9) helper.
Verified against the ISO 717-1 Annex C worked example (Rw(C;Ctr) =
30(-2;-3), unfavourable sum 31,8 dB) and exact-bound / tipping edge
cases. 20 new tests; make check green (484 passed, 12 skipped).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: room & building acoustics guide, theory, API and landing (EN)
New "Room & building acoustics" guide (docs/ + Starlight twin) covering the
full measurement chain: ISO 18233 swept-sine/MLS impulse-response acquisition,
ISO 3382-1/2 decay analysis and room parameters (EDT/T20/T30/C50/C80/D50/Ts),
ISO 3382-3 open-plan speech metrics, and ISO 16283-1 / ISO 717-1 field
insulation with weighted ratings. Adds the matching theory section (Schroeder
integration, regression windows, C/D/Ts, D2,S, DnT/R', reference-curve method),
API-reference rows for all new public names, sidebar entry, README highlight +
docs row, EN landing card, CHANGELOG entries, docs index row, and regenerated
llms-full.txt. All snippets validated; site build and make check green.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: Schroeder, ISO 717-1 rating and insulation-setup figures
Add three room/building-acoustics documentation figure sets (each in
en/es x light/dark):
- schroeder_decay: synthetic IR, Schroeder backward integration with
EDT/T20/T30 regressions and evaluation ranges (ISO 3382); annotated
values match room_parameters.
- insulation_rating: ISO 717-1 Annex C example with shifted reference
curve, unfavourable-deviation shading and Rw read at 500 Hz; Rw, C,
Ctr and the unfavourable sum come from weighted_rating.
- diagram_insulation_setup: ISO 16283-1 airborne setup plan view with
the normative minimum distances (clauses 7.6 and 7.2.2).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de acústica de salas y edificación
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: terminología UNE — ponderación temporal en lugar de balística (ES)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: markdown/README parity with the site + llms guide coverage
Scrub GitHub-unsafe display math (\, thin-spaces and escaped \{ \}) from
docs/intensity.md and docs/psychoacoustics.md — the only two guides that
missed the GitHub-safe math conversion of PRs #78/#79 — matching the safe
forms already used in docs/theory.md. Align calibration's sensitivity
symbol/formula to the site ($S$, single-line \qquad).
Extend scripts/gen_llms.py PAGES with the three omitted guides
(psychoacoustics, intensity, room-acoustics) and regenerate llms.txt /
llms-full.txt so AI-facing artifacts cover all 14 docs pages.
The guides/references were otherwise already in parity (didactic code
comments, theory sections, tables, and figures from PRs #77/#80 all
present); docs/README index and README highlights/table verified complete.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: final-review findings — open_plan validation, limits rename, Farina caveat, MLS averaging test
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: ISO 18233 measurement-chain diagram and guide fixes
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: fix sweep_signal cross-reference in Farina caveat
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 81 review round 1 — inverse-filter guards, truncation threshold, IR input validation, docs pipeline wording
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: blank lines around CONTRIBUTING fence (MD031)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Human vibration exposure (ISO 8041-1, ISO 2631, ISO 5349, Directive 2002/44/EC) (#94)
New `human_vibration` module implementing the ISO 8041-1:2017 master
weighting cascade (all nine weightings Wb/Wc/Wd/We/Wf/Wh/Wj/Wk/Wm),
ISO 2631-1/-2/-4 whole-body metrics (running RMS, MTVV, VDV, MSDV, crest
factor, vibration total value, energy-equivalent acceleration), the
ISO 5349-1/-2 hand-arm A(8) arithmetic and vibration-white-finger latency,
and the Directive 2002/44/EC EAV/ELV assessment. Every result object exposes
.plot(); validated to <0.05% against the ISO 8041-1 Annex B tables and the
ISO 5349-2 Annex E worked examples. Includes docs page, figures, setup
diagram, and 7 conformance checks (100%).
Advanced psychoacoustics (ISO 532-2/3 Moore-Glasberg, ECMA-418-2 Sottek loudness/tonality/roughness) + numerical conformance report (#87)
* feat: ECMA-418-2 Sottek Hearing Model loudness and auditory front-end
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic loudness metric and
its shared auditory front-end (Sottek Hearing Model):
- Clause 5 front-end: outer/middle-ear cascade (5.1.3, Table 1), 53-band
gammatone-like auditory filter bank (5.1.4), band-dependent segmentation
(5.1.5, Table 4), rectification/RMS (5.1.6-5.1.7), compressive nonlinearity
(5.1.8, Formula 23, Table 2) and threshold in quiet (5.1.9, Table 3).
- Clause 6.2.2-6.2.7 ACF-based tonal/noise specific loudness (reusable by the
later tonality/roughness metrics).
- Clause 8 assembly: tonal/noise power average, average specific loudness,
time-dependent and single representative loudness value.
The front-end helpers are factored for reuse. Calibration verified: a 1 kHz
40 dB SPL sinusoid yields 0.996 sone_HMS (target 1.0). Adds a frozen
EcmaLoudness result with lazy .plot(), exports, and 12 tests.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 tonality (Sottek Hearing Model)
Implements the psychoacoustic tonality metric of ECMA-418-2:2025
(Clause 6.2.8-6.2.11) on top of the committed Sottek front-end and
ACF tonal/noise decomposition.
- tonality_ecma() -> EcmaTonality with the single value T (Formula 63),
average specific tonality T'(z) (Formula 53), tonal frequencies
f_ton,z(z) (Formula 55), time-dependent tonality T(l) (Formula 61)
and its frequency f_ton(l) (Formula 62); optional user band [f_low,f_high].
- Full Clause 6.2.3 band averaging with cross-block-size-group ACF
recomputation (loudness keeps its simplified, valid-for-loudness path;
boundary marker added there). Loudness 0.996 sone guard unchanged.
- _tonal_estimate now also returns the DFT-peak tonal frequency, with the
argmax restricted to the lower DFT half to avoid Nyquist-mirror aliasing.
- .plot() via _plotting.plot_ecma_tonality; exports in __init__.
- 13 conformance tests: 1 kHz/40 dB -> 1 tu_HMS calibration, pure tone
>> noise, f_ton tracks the tone, silence -> 0, band restriction.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 roughness (Sottek Hearing Model)
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic roughness metric
(Clause 7), the third Sottek Hearing Model output and new library
functionality. Reuses the Clause 5 auditory front-end from loudness_ecma
(ear filter, 53-band gammatone bank, specific basis loudness) and adds the
roughness-specific chain: start-only zero-padding and fixed 16384/4096
segmentation (5.1.2.2, 5.1.5.2, 7.1.1), the Hilbert envelope with x32
downsampling to 1500 Hz (7.1.2), the scaled envelope power spectrum
(7.1.3), two-step noise reduction (7.1.4), the four-stage spectral
weighting with quadratic-fit modulation-rate refinement, bias correction,
high/low modulation-rate weighting and fundamental-rate estimation
(7.1.5), interpolation to 50 Hz with the distribution-dependent nonlinear
transform, tabulated calibration c_R and asymmetric smoothing (7.1.7), and
the R'(z) / R(l50) / 90th-percentile aggregation (7.1.8-7.1.10).
Uses the standard's tabulated c_R = 0.0180685 (Formula 104), not
reverse-fit: the 1 kHz / 70 Hz / m=1 / 60 dB calibration signal yields
R = 1.0735 asper (near-but-not-exactly 1). OCR-ambiguous formulae
(66, 71, 78-81) cross-checked against the SQAT MATLAB reference; the
optional entropy weighting (7.1.6, needs RPM telemetry) is documented but
not implemented.
Adds roughness_ecma()/EcmaRoughness, a two-panel .plot(), exports, a
10-test TDD suite and plan/notes-ecma418-2-roughness.md. Full suite
752 passed / 12 skipped; loudness (0.996) and tonality (0.99993) oracles
unchanged (both modules untouched).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* test: pin ECMA-418-2 roughness calibration value and document clean-room variance
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-2 Moore-Glasberg loudness
Add the stationary Moore-Glasberg loudness model of ISO 532-2:2017 as a
third loudness method alongside the Zwicker (ISO 532-1) and Sottek
(ECMA-418-2) implementations. The full Clause 7 chain is implemented:
fixed outer/middle-ear transfer (Table 1), the level-dependent roex
auditory filter bank and excitation pattern on the ERB-number scale
(Formulae 1-6), the compressive specific-loudness transform (Formulae
7-9, Tables 2-4, C=0.0617 sone/Cam), binaural inhibition (Formulae
10-13) and integration to total loudness with the phon mapping of
Table 5.
Public API: loudness_moore_glasberg_from_spectrum (exact 5.2/5.4),
loudness_moore_glasberg_from_third_octave (29 bands, 5.5) and
loudness_moore_glasberg (signal wrapper), returning a frozen
MooreGlasbergLoudness with .plot() over the ERB-number scale.
Validated against the Annex B reference signals (tones, white/pink
noise, multi-tone complexes, tone-in-noise) to ~1-4%, well inside the
standard's 2.8 phon expanded uncertainty; the definitional anchor
(1 kHz/40 dB/free/binaural = 1.000 sone) holds end-to-end from the
tabulated constant. 50 new tests; full suite 802 passed / 12 skipped.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-3 Moore-Glasberg-Schlittenlacher time-varying loudness
Add loudness_moore_glasberg_time() implementing the ISO 532-3:2023 method:
a six-FFT multi-resolution running short-term spectrum (clause 7.3) feeding
the ISO 532-2 excitation and specific-loudness model on the 0.25 Cam grid
with the 532-3 constants (C=0.063, Tables 2/4, E_THRQ/E0=2.307), integrated
by asymmetric attack/release smoothing into short-term (clauses 7.6-7.8) and
long-term (clause 7.9) loudness traces, with peak long-term loudness,
percentiles and a lazy STL/LTL .plot().
Validated against the Annex C.1 tone table (1 kHz 10-80 dB, 3 kHz, 4 kHz,
100 Hz and monaural earphone) to within 0.4 phon, well inside the 2.8 phon
expanded uncertainty; the 1 kHz/40 dB anchor is exact at 1.000 sone. The
ISO 532-2 and ECMA-418-2 oracles are unchanged (831 passed / 12 skipped).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-3 per-ear LTL and Formula-14 kernel width (dichotic path)
Two transcription errors in the ISO 532-3:2023 dichotic (per-ear) binaural
path of the time-varying Moore-Glasberg-Schlittenlacher module:
1. Clause 7.9: the long-term loudness must be computed per ear by the
attack/release averager (Formulae 18-21) and then summed. The code ran a
single AGC on the binaural sum S'_L + S'_R; because the attack/release
branch is nonlinear, AGC(S'_L) + AGC(S'_R) != AGC(S'_L + S'_R) for dichotic
input. Keep two LTL states and sum the two long-term loudnesses. For
diotic/monaural the averager is linear in the (scaled) input so the result
is bit-for-bit identical to the old sum-then-AGC.
2. Formula (14)/(15): the smoothing weight is exp(-(B*Di)^2) with B = 0.08 and
Di in Cam. The code evaluated exp(-((0.08 * Di/0.1)^2)) = exp(-(0.8*Di)^2),
a Gaussian ~10x too narrow, and truncated the taps at +/-1.75 Cam. Remove
the spurious /0.1 and widen the kernel to the standard's +/-18 Cam (145 taps
on the 0.25-Cam grid). For diotic/monaural the L/R excitation ratio is 1
regardless of the kernel, so all oracles are unaffected.
No current validation signal exercises the dichotic path (Annex C signals are
diotic/monaural), so the 532-3 tone oracle and every other oracle remain
byte-identical; added focused dichotic tests plus a byte-identical anchor pin.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: numerical conformance report in CI PR comment
Reframe the PR comment into a persistent numerical conformance report:
each implemented standard -> normative expected value/range -> computed
result -> pass/fail with deviation, grouped by domain.
- scripts/conformance_report.py: registry of 21 checks across 17 standards
and 6 domains (filters/weightings, levels/dosimetry, psychoacoustics,
speech, intensity/power, room/building). Emits a headline summary, a
"Numerical validation - filters & weightings" section (per-architecture
IEC 61260-1 class margins + A/C/G weighting deviation vs the normative
curve) and one conformance table per domain. Deterministic, ~2.7 s.
- tests/reference_data.py: single source of truth for the shared normative
tables (IEC 61672-1 Table 3, ISO 7196 Table 2, ISO 717-1 Annex C R),
imported by both the tests and the report so they cannot drift.
- tests/test_conformance_report.py: smoke test asserting every registered
check passes and the Markdown is well-formed.
- comment_pr.py / workflow: conformance report leads the comment; the
test/coverage table is collapsed below. Retire benchmark_filters.py
(numerical content absorbed into the report).
make check green (740 passed, 12 skipped); ruff, mypy --strict, bandit clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: block-A review minors — docstring notes and tonality band-range
Four Minor findings from the plan-17 block-A psychoacoustics reviews. No
change to any validated numeric output; all five oracles stay byte-identical.
1. ISO 532-2 _source_levels: docstring note that clause 7.4 specifies a roex
weighting for X where the code uses a rectangular +/-ERB_n/2 window —
identical for isolated tones, immaterial for the Annex B broadband cases.
2. ISO 532-2 Formula-10 binaural smoothing kernel: the PDF (clause 8.1) states
"Di is changed in steps of 0,1" over -18..+18 Cam; the code confused the
0.1-Cam index offset with Di-in-Cam (the same /0.1 width bug just fixed in
532-3 Formula-14), making the Gaussian 10x too narrow and truncating to
+/-1.8 Cam. Widen to +/-18 Cam (361 taps) with Di = tap*_I_STEP. Provably
inert: all Annex B cases are diotic (ratio 1) or monaural (unit inhibition).
3. ECMA tonality _band_range: implement the exact Formulae 56-57 edge-midpoint
boundary conditions instead of a centre-frequency threshold; only affects
the optional user-band feature. Added a user-band edge test.
4. ECMA tonality reach: comment that the _CBF-1-band upper-edge term is a dead
defensive guard (N_B>0 only for low bands 0..24, so it never limits reach).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* chore: move conformance design doc out of repo root
* feat: clearer conformance report (value-vs-range) + block-A checks + audit fixes
Table 1 (filters): show, per architecture, the measured relative attenuation
at the binding band vs the class-1 limit it must clear (value + range, not
just a margin). Replace the "none" verdict (read as a failure) with a
"By design (ripple/soft rolloff)" label + footnote; Butterworth (default) and
Chebyshev-II are the only mask-compliant architectures. The measured value and
limit are re-derived with the public class_limits on the same designed SOS; a
smoke-test guard asserts the re-derived class-1 margin matches
verify_filter_class (single source of truth).
Table 2 (weightings): separate the informational max-deviation-from-nominal
(at a frequency extreme with wide, asymmetric tolerance) from the compliance
margin at the binding frequency, where the deviation, the +/- tolerance band
and the headroom are co-located so "value vs range" is unambiguous and in-spec.
Audit: verified all 21 checks (expected value/range vs the standard, real
library call, honest tolerance). One fix - ISO 226 anchored at 60 phon @ 2 kHz
-> 60 dB, a genuine Table B.1 value but coincidentally equal to the phon number
(reads as the trivial 1 kHz identity); re-anchored to the non-coincidental
60 phon @ 100 Hz -> 78.5 dB, sourced from reference_data. No library bug found.
Block-A psychoacoustics (+5, single-source expected in reference_data):
ECMA-418-2 loudness (1 kHz/40 dB -> 1 sone_HMS, c_N), tonality (-> 1 tu_HMS,
c_T), roughness (1 kHz/70 Hz/m=1/60 dB -> clean-room 1.0735 asper with a note
that the standard target is 1.0); ISO 532-2 (1 kHz/40 dB -> 1 sone, C=0.0617);
ISO 532-3 (steady 1 kHz/40 dB -> peak LTL 1 sone). 26/26 checks, 20 standards.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: versioned docs/CONFORMANCE.md with make target, CI staleness check and README link
Persist the numerical conformance report as a committed, versioned file so
every reader sees the library's per-standard validation (expected vs computed)
without running CI.
- docs/CONFORMANCE.md: committed report (26/26 checks, 6 domains, 20 standards)
with an auto-generated "do not hand-edit" header linking the standards docs.
- scripts/conformance_report.py: add a --file-header flag that emits that header
for the committed file (PR-comment body stays header-free); coarsen the
informational delta column to 3 decimals so BLAS/FFT-dependent sub-milli
residuals do not churn the file (status/computed columns keep regressions
visible).
- Makefile: `make conformance` regenerates the file; `make install-hooks`
installs the optional pre-commit hook.
- CI: new fast `conformance` job (runtime deps only) fails a PR if the committed
file is stale, telling the author to run `make conformance` and commit.
- hooks/pre-commit: opt-in convenience hook, scoped to src/scripts/reference
changes; CI is the enforcement.
- README + CONTRIBUTING: link and document the generated report.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced psychoacoustics guide, theory, API (EN)
Document the plan-17 block-A advanced psychoacoustics models: Moore-Glasberg
loudness (ISO 532-2/532-3), Sottek Hearing Model loudness, tonality and
roughness (ECMA-418-2:2025).
- Psychoacoustics guide: new "Advanced loudness & sound-quality models"
section with a model-comparison table, executed snippets, parameter tables,
res.plot() one-liners and figure-code collapsibles (docs + site twin,
code blocks byte-identical).
- Theory: excitation-pattern/specific-loudness chain, Sottek front-end,
ACF tonality and envelope-modulation roughness (GitHub-safe math).
- API reference: rows for the 5 functions (+ 2 MG spectral entry points) and
5 result dataclasses with their .plot().
- README, EN landing (standards 26 -> 29: ISO 532-2, ISO 532-3, ECMA-418-2),
CHANGELOG Unreleased/Added, llms regen.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced-psychoacoustics figures (loudness models, Sottek, tonality/roughness, time-varying)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de psicoacústica avanzada (Moore-Glasberg, Sottek, tonalidad, aspereza)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-2 signal-input path builds a narrowband spectrum (pure-tone loudness)
The loudness_moore_glasberg(x, fs) wrapper routed the signal through the
one-third-octave method, which smeared a pure tone across a whole 1/3-octave
band and used a coherent-gain window normalisation that over-counted power by
1.76 dB. A calibrated 1 kHz tone at 40 dB SPL therefore returned 1.327 sone
instead of the definitional anchor of 1.000 sone.
ISO 532-2 is spectrum-based and its exact input (clauses 5.2/5.4) is a set of
discrete sinusoidal components; the excitation pattern (Formula 5) is computed
per component, so a tone must enter as a single line. The wrapper now forms the
signal's narrowband (FFT) line spectrum with a power-preserving (Parseval)
window normalisation and feeds it to loudness_moore_glasberg_from_spectrum. A
1 kHz/40 dB tone now yields 1.0001 sone / 40 phon and multi-tone signals match
the exact spectrum path. The validated spectrum and third-octave paths are
byte-identical.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: correct MG signal-path mechanism text and presentation default
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 87 review round 1 — float-equality guards, 532-3 FFT window length, tonality short-signal, report/figure fixes
- roughness_ecma: replace three float == 0.0 checks with abs>eps guards
(SonarCloud gate); roughness oracle byte-identical (1.0735).
- loudness_moore_glasberg_time: per-window FFT length (>= window length) so
the 64 ms window is not truncated at 44.1/48 kHz (was ~0.74 dB low in the
20-80 Hz band). 32 kHz anchor byte-identical (n_max 1.0000044713237626).
- tonality_ecma: short signals fall back to averaging all blocks (matches
loudness_ecma), not the final block only; validated anchor unaffected.
- conformance_report: document deliberate 273.0 (speed of sound) vs 273.15
(C1/C2) split matching the library; complete bind_side docstring.
- generate_graphs: roughness annotation uses computed peak fmod; Sottek
metrics use tu_HMS/sone_HMS units; ES translations + 8 figures regenerated.
- hooks/pre-commit: drop set -e so a benign regen failure does not block the
commit (CI staleness check is the gate).
- test_iec_weighting_table3 docstring, Makefile .PHONY completion.
All 5 psychoacoustics oracles byte-identical; make check 868 passed / 12
skipped; ruff + mypy + bandit clean; conformance 26/26.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: localize decimal commas in Sottek-unit figure labels (underscore guard)
The `_` guard in `_translate_figure` skipped decimal-comma localization for
any label containing an underscore. The new Sottek/HMS unit tokens
(`sone_HMS`, `tu_HMS`, `sonios_HMS`) carry underscores, so Spanish figures
regressed to dots (`8.0`, `0.42`) instead of commas (`8,0`, `0,42`).
Drop the underscore guard: the decimal-comma regex only rewrites a bare
`digit.digit` not adjacent to more digits/dots, so unit identifiers and
version numbers (5.3.3) stay intact while genuine decimals get commas.
Mathtext is still skipped via the `$` guard. Regenerated the affected ES
figures (sottek_specific_loudness, tonality_roughness_demo).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Sound power determination and building acoustics II: ISO 3744/3746, ISO 3741, ISO 9614-2, ISO 16283-2 + ISO 717-2, ISO 354 (#84)
* feat: ISO 3744/3746 sound power from pressure measurements
Add sound_power.py: sound power level of a noise source from sound
pressure levels over an enveloping measurement surface, ISO 3744:2010
(engineering, grade 2) and ISO 3746:2010 (survey, grade 3).
- sound_power_pressure(): per-band LW and A-weighted LWA from an
(NM, NB) level array over a hemisphere (radius) or parallelepiped
(dimensions + distance); surface area from the standard's closed forms
for 1/2/3 reflecting planes; energy average (Eq. 12), background K1
(Eq. 16), environmental K2 (Eq. A.2), apparent directivity index
(Eq. 7), expanded uncertainty.
- measurement_positions(): normative Annex B coordinates
(Tables B.1/B.2/B.3), grade- and plane-dependent subsets.
- background_noise_correction(): K1 with engineering/survey criteria.
- environmental_correction(): K2 from A, Sabine T+V, or alpha*Sv.
- SoundPowerResult (frozen dataclass), SoundPowerWarning.
Tests: 24 cases incl. monopole-over-plane exact LW recovery, radius
independence, K1/K2 closed forms, LWA via Annex E, and validations.
make check green (543 passed, 12 skipped); ruff/bandit/mypy --strict clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: directivity index under background correction, citation hygiene
Broadband K1 for the apparent directivity index was collapsing to the
surface-area term 10*lg(S/S0) (~+22 dB inflation with background
correction). Compute the true energy-based broadband K1 (per-band Eq. 16
aggregated over bands) so DI is correct in both paths.
Also: multi-band sound_power_level_a without frequencies now returns NaN
(A-weighting needs band centres); dropped PDF page-number citations from
Annex B/E table comments; corrected the survey parallelepiped position
citation to ISO 3746 clause C.1 / Figure C.7.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 3741 reverberation-room sound power
Add the ISO 3741:2010 precision (grade 1) methods for determining sound
power in a reverberation test room, in a new sound_power_reverberation
module:
- sound_power_reverberation() — direct method (Eq. 20): LW from the mean
room SPL and the Sabine equivalent absorption area A = (55,26/c)(V/T60),
with the full normative bracket: 10 lg(A/A0), 4,34*(A/S), the Waterhouse
boundary term 10 lg(1 + S c/(8 V f)), the reference-quantity (C1) and
radiation-impedance (C2) meteorological corrections and the -6 dB
constant. Speed of sound c = 20,05*sqrt(273 + theta).
- sound_power_comparison() — comparison method (Eq. 21) with a reference
sound source: LW = LW(RSS) + (Lp(ST) - Lp(RSS) + C2).
- ReverberationSoundPowerResult frozen dataclass; frequency-dependent
background correction K1 (Eq. 14, same formula as ISO 3744) with the
precision-grade 6/10 dB criteria; A-weighted total via Annex F.
Reuses the ISO 3744 energy-average and A-weighting helpers. Tests cover
exact inversion of Eq. (20), the Waterhouse term vanishing at high
frequency, the comparison method exact by construction, and synergy with
room_parameters T30 feeding the direct method.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 3741 room-qualification advisories and validation ordering
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9614-2 sound power by intensity scanning
Add sound_power_intensity() computing per-band LW = 10lg(sum Pi/P0) from
per-segment signed normal intensity and areas (partial power Pi = <In,i>*Si),
the scanning-method field indicators FpI (Eq. A.1) and F+/- (Eq. A.2), the
repeatability criterion 3, and the achieved engineering/survey grade per band
(Annex B criteria 1-3). Negative total power flags bands as non-determinable
(clause 9.2); negative partial power raises SoundPowerWarning. Reuses
dynamic_capability_index for Ld = dpI0 - K (Table 1).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 16283-2 impact sound insulation and ISO 717-2 ratings
Add field impact sound insulation (ISO 16283-2, tapping machine) and the
ISO 717-2 single-number weighted impact rating with the CI spectrum
adaptation term, reusing the verified reference-curve shift engine.
- impact_insulation(li, t2, *, volume=None, t0=0.5): per-band
L'nT = Li - 10 lg(T/T0) (Formula (1)) and L'n = Li + 10 lg(A/A0) with
A = 0,16 V/T, A0 = 10 m2 (Formula (2)); positions energy-averaged.
- weighted_impact_rating(values_by_band, bands=None): Ln,w / L'n,w /
L'nT,w (Clause 4.3, octave -5 dB rule) and CI (Clause A.2.1, energetic
sum 100-2500 / 125-2000). Impact unfavourable deviations (measurement
exceeds reference, sign opposite to airborne) reduce to the airborne
search on negated curves, so _best_shift is reused verbatim.
- _best_shift: tolerance absorbs float noise at the exact bound (sums are
true multiples of 0,1 dB); airborne behaviour unchanged.
Verified against ISO 717-2 Annex C worked examples (Table C.1 Ln,w=79,
CI=-11; Table C.3 octave Ln,w=54, CI=0) and an independent brute-force
shift search on 10 000 random curves per band set. 2013 and 2020 editions
are identical for all implemented maths (2020 only adds Annex D rubber
ball, out of scope).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 354 sound absorption in a reverberation room
Add phonometry.sound_absorption implementing BS EN ISO 354:2003:
- absorption_area(t60, volume, ...): equivalent sound absorption area
A = 55,3*V/(c*T) - 4*V*m (Eq. 5/7), speed of sound from Eq. (6).
- absorption_coefficient(t1, t2, volume, sample_area, ...): alpha_s =
(A2-A1)/S built from Eq. (8)/(9), per-measurement c1/c2, unclamped
(alpha_s > 1 valid, clause 3.7 NOTE 2); warns when T2 >= T1.
- attenuation_from_alpha: ISO-354 conversion m = alpha/(10 lg e) from an
ISO 9613-1 attenuation coefficient (m deferred to ISO 9613-1, default 0).
Composes directly with room_parameters() T20/T30 outputs. 25 tests
(exact T->A->T inversion, synthetic-alpha recovery, zero-m air term,
two-temperature Eq. (8), range validation, room_parameters synergy).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 354 room and sample-size advisories
Add advisory AbsorptionWarning (never raising; result still returned) for
setup conditions outside ISO 354:2003 limits:
- Room volume below the 150 m3 minimum of clause 6.1.1, in both
absorption_area and absorption_coefficient.
- Sample area outside the clause 6.2.1.1 range 10 <= S <= 12 m2, with the
upper limit scaled by (V/200)^(2/3) when V > 200 m3, in
absorption_coefficient.
The volume advisory is emitted once by absorption_coefficient and suppressed
in its internal absorption_area calls to avoid duplicate warnings.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: deferred review minors — airborne brute-force net, advisory consistency, labels
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: sound power guide, impact and absorption sections, theory and API (EN)
Add a "Sound Power" guide covering the three LW routes (ISO 3744/3746
enveloping surface, ISO 3741 reverberation room with Waterhouse/C1/C2,
ISO 9614-2 intensity scanning), with a method-comparison table, executed
snippets and parameter tables. Extend the Room & Building Acoustics guide
with impact sound insulation (ISO 16283-2 + ISO 717-2, L'nT/L'n, tapping
machine, CI) and sound absorption in a reverberation room (ISO 354).
Add the matching theory sections (K1/K2, Waterhouse, C1/C2 derivations;
impact sign conventions; Sabine absorption) and API-reference rows for all
new public names. Update the landing standards count to 26, the sidebar,
README highlights/table, CHANGELOG and llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: sound-power and impact figures and diagrams
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: include the sound-power guide in llms coverage
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: reposition Ln,w annotation in the impact figure
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de potencia sonora, impacto y absorción
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: final-review findings — comparison K1 reporting, citations, knee unification, docs attribution
- sound_power_comparison now reports the test-source K1 actually applied
per band (zero when no background), matching the dataclass docstring;
LW unchanged.
- Replace gitignored notes-iso3744-3746.md citation in the DI comment with
the standard reference (ISO 3744:2010, 3.24 / Eq. 7 context).
- Unify the K1 zeroing knee to the >= form (ΔLp >= 15 -> no correction).
- Broaden SoundPowerWarning docstring to name all emitters (ISO 3744/3746,
ISO 3741, ISO 9614-2).
- Fix docs misattributing K2 to ISO 3741 (ISO 3744 K2 vs ISO 3741 absorption
term) across EN/ES room-acoustics guides.
- EN impact-setup diagram uses dot decimals (0.16); regenerate the 4 variants.
- CHANGELOG: label the ISO 717-2 Annex C value Ln,w = 79.
- Warn-after-validate in sound_power_comparison; "is raised" -> "is emitted".
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 84 review round 1 — per-band DI and K2, sweep-reversal repeatability, validation and broadcast ergonomics
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 84 round 2 — partial room-data validation, result-field docs
Raise a clear ValueError in environmental_correction (and its
sound_power_pressure pass-through) when exactly one member of a
room-data pair (reverberation_time/room_volume or
mean_absorption_coefficient/room_surface) is supplied, instead of
silently returning K2=0 as if free field. All-None remains the
legitimate free-field K2=0 path.
Document frequencies on the ReverberationSoundPowerResult row and
negative_band as a standalone per-band boolean field on the
SoundPowerIntensityResult row across docs/api-reference.md and the
EN/ES site twins.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Acoustic materials: impedance tube, airflow resistance & sound-absorption rating (ISO 10534-1/-2, ASTM E2611, ISO 9053-1/-2, ISO 11654) (#91)
* feat: impedance tube, airflow resistance and ISO 11654 absorption rating
Clean-room from the standards (no third-party tool referenced):
- impedance_tube.py: ISO 10534-2 two-microphone transfer-function method
(reflection factor, surface impedance/admittance, absorption), ISO 10534-1
standing-wave-ratio method, and ASTM E2611 four-microphone transfer-matrix
method (wave decomposition, two-load/one-load T-matrix, transmission loss,
material wavenumber and characteristic impedance). Kelvin (ISO) vs Celsius
(ASTM) speed-of-sound helpers keep the two sign/unit conventions apart.
- airflow_resistance.py: ISO 9053-1 static method (through-origin quadratic
fit) and ISO 9053-2 alternating method (Formula 2), with R_s in Pa·s/m and
resistivity in Pa·s/m².
- absorption_rating.py: ISO 11654 weighted absorption alpha_w (0.05-grid
reference-curve shift, L/M/H shape indicators, absorption classes A-E),
verified against the Annex A.1/A.2 worked examples.
103 module tests plus public-export checks; physics-identity anchors for the
tube (rigid wall alpha=0, perfect absorber r=0/Z=rhoc, det(T)=1, air-layer
recovery to <1e-9) since neither tube standard prints a numeric example.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* feat: ISO 9053-2 Annex A kappa', materials docs, figures & conformance
Address per-module review of the impedance-tube / materials modules:
- ISO 9053-2:2020 Annex A (normative): implement the heat-conduction-corrected
effective ratio of specific heats kappa' (Formula (A.7)) and the thermal
boundary-layer thickness b (A.4/A.5) as effective_kappa() and
thermal_boundary_layer_thickness(). The Annex A.3 worked example is a
digit-exact oracle (b = 1.83e-3 m, kappa' = 1.370). Fix the misleading
_ADIABATIC_KAPPA citation: 1.4 is the uncorrected adiabatic fallback, not the
Annex A output.
- absorption_rating: delegate .plot() to _plotting.plot_weighted_absorption,
matching the repo idiom (reuses _unfavourable_mask / _new_axes).
- docs/materials.md: new guide covering ISO 11654, ISO 9053-1/-2 and
ISO 10534-1/-2 + ASTM E2611, with runnable examples and three figures
(EN/ES, light/dark). Linked from the docs index.
- Conformance report: five materials checks (ISO 11654 Annex A.1/A.2,
ISO 9053-2 Annex A.3 b + kappa', ISO 10534-1 SWR); shared oracles in
reference_data.py. 48/48 checks pass.
Impedance-tube module review returned Approved (no code changes needed;
the Eq (20) phase citation was verified correct against ISO 10534-1 p.5).
Claude-Session: https://claude.ai/code/session_01JJLhQzF5hTdnEi3bfmqnqm
* docs: experimental-setup diagrams for the materials methods
Add hand-drawn SVG setup diagrams (EN/ES, light/dark) for the new methods,
mirroring the didactic style of the room-acoustics / outdoor diagrams, and
embed them in docs/materials.md:
- Impedance tube (ISO 10534-2): loudspeaker, tube, two flush microphones at
spacing s and distance x1 from the specimen face, rigid backing, incident/
reflected plane waves and the Eq. (17)-(19) relations.
- Four-microphone transmission-loss tube (ASTM E2611): source, two upstream
and two downstream microphones, adjustable two-load termination, the A/B/C/D
travelling waves, and s1/s2/l1/l2/d labelled to match the library's
transfer_matrix_two_load / wave_decomposition parameters (l1, l2 measured
from the specimen front reference plane).
- Airflow resistance (ISO 9053-1/-2): the static rig (specimen, laminar flow
q_v, differential manometer Δp) and the alternating rig (piston 1-4 Hz,
cavity V, specimen/airtight termination, cavity level L_p).
Also apply the whole-branch review's two before-merge items:
- airflow_resistance.py: add the module-level __all__ it was missing.
- impedance_tube.face_quantities: add functional tests (progressive/backward
wave impedance = ±rho*c) so the exported ASTM Eq. (21) helper is covered.
SVG text() now XML-escapes &, < and > so labels may contain them literally.
Diagrams validated by subagent against the standards' figures and the
library's public-API variable names; the ASTM l2 origin was corrected from
the rear to the front specimen face to match the library convention.
Claude-Session: https://claude.ai/code/session_01JJLhQzF5hTdnEi3bfmqnqm
* fix: harden ISO 11654 input coercion (bot review)
_coerce now rejects multi-dimensional array input instead of silently
flattening it (CodeRabbit) and rejects non-finite values with a clear message
instead of a cryptic downstream integer-conversion error (Gemini). Adds tests
for both.
Claude-Session: https://claude.ai/code/session_01JJLhQzF5hTdnEi3bfmqnqm
* fix: flag ill-conditioned transfer-matrix solves (bot review)
transfer_matrix_two_load / transfer_matrix_one_load now emit an
ImpedanceTubeWarning when the solve denominator is near-singular relative to
its operands — two insufficiently-different loads (two-load) or a near-resonant
geometry (one-load) — instead of silently propagating inf/nan (CodeRabbit).
Adds tests for both the singular and the well-conditioned case.
Claude-Session: https://claude.ai/code/session_01JJLhQzF5hTdnEi3bfmqnqm
Advanced psychoacoustics (ISO 532-2/3 Moore-Glasberg, ECMA-418-2 Sottek loudness/tonality/roughness) + numerical conformance report (#87)
* feat: ECMA-418-2 Sottek Hearing Model loudness and auditory front-end
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic loudness metric and
its shared auditory front-end (Sottek Hearing Model):
- Clause 5 front-end: outer/middle-ear cascade (5.1.3, Table 1), 53-band
gammatone-like auditory filter bank (5.1.4), band-dependent segmentation
(5.1.5, Table 4), rectification/RMS (5.1.6-5.1.7), compressive nonlinearity
(5.1.8, Formula 23, Table 2) and threshold in quiet (5.1.9, Table 3).
- Clause 6.2.2-6.2.7 ACF-based tonal/noise specific loudness (reusable by the
later tonality/roughness metrics).
- Clause 8 assembly: tonal/noise power average, average specific loudness,
time-dependent and single representative loudness value.
The front-end helpers are factored for reuse. Calibration verified: a 1 kHz
40 dB SPL sinusoid yields 0.996 sone_HMS (target 1.0). Adds a frozen
EcmaLoudness result with lazy .plot(), exports, and 12 tests.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 tonality (Sottek Hearing Model)
Implements the psychoacoustic tonality metric of ECMA-418-2:2025
(Clause 6.2.8-6.2.11) on top of the committed Sottek front-end and
ACF tonal/noise decomposition.
- tonality_ecma() -> EcmaTonality with the single value T (Formula 63),
average specific tonality T'(z) (Formula 53), tonal frequencies
f_ton,z(z) (Formula 55), time-dependent tonality T(l) (Formula 61)
and its frequency f_ton(l) (Formula 62); optional user band [f_low,f_high].
- Full Clause 6.2.3 band averaging with cross-block-size-group ACF
recomputation (loudness keeps its simplified, valid-for-loudness path;
boundary marker added there). Loudness 0.996 sone guard unchanged.
- _tonal_estimate now also returns the DFT-peak tonal frequency, with the
argmax restricted to the lower DFT half to avoid Nyquist-mirror aliasing.
- .plot() via _plotting.plot_ecma_tonality; exports in __init__.
- 13 conformance tests: 1 kHz/40 dB -> 1 tu_HMS calibration, pure tone
>> noise, f_ton tracks the tone, silence -> 0, band restriction.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 roughness (Sottek Hearing Model)
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic roughness metric
(Clause 7), the third Sottek Hearing Model output and new library
functionality. Reuses the Clause 5 auditory front-end from loudness_ecma
(ear filter, 53-band gammatone bank, specific basis loudness) and adds the
roughness-specific chain: start-only zero-padding and fixed 16384/4096
segmentation (5.1.2.2, 5.1.5.2, 7.1.1), the Hilbert envelope with x32
downsampling to 1500 Hz (7.1.2), the scaled envelope power spectrum
(7.1.3), two-step noise reduction (7.1.4), the four-stage spectral
weighting with quadratic-fit modulation-rate refinement, bias correction,
high/low modulation-rate weighting and fundamental-rate estimation
(7.1.5), interpolation to 50 Hz with the distribution-dependent nonlinear
transform, tabulated calibration c_R and asymmetric smoothing (7.1.7), and
the R'(z) / R(l50) / 90th-percentile aggregation (7.1.8-7.1.10).
Uses the standard's tabulated c_R = 0.0180685 (Formula 104), not
reverse-fit: the 1 kHz / 70 Hz / m=1 / 60 dB calibration signal yields
R = 1.0735 asper (near-but-not-exactly 1). OCR-ambiguous formulae
(66, 71, 78-81) cross-checked against the SQAT MATLAB reference; the
optional entropy weighting (7.1.6, needs RPM telemetry) is documented but
not implemented.
Adds roughness_ecma()/EcmaRoughness, a two-panel .plot(), exports, a
10-test TDD suite and plan/notes-ecma418-2-roughness.md. Full suite
752 passed / 12 skipped; loudness (0.996) and tonality (0.99993) oracles
unchanged (both modules untouched).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* test: pin ECMA-418-2 roughness calibration value and document clean-room variance
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-2 Moore-Glasberg loudness
Add the stationary Moore-Glasberg loudness model of ISO 532-2:2017 as a
third loudness method alongside the Zwicker (ISO 532-1) and Sottek
(ECMA-418-2) implementations. The full Clause 7 chain is implemented:
fixed outer/middle-ear transfer (Table 1), the level-dependent roex
auditory filter bank and excitation pattern on the ERB-number scale
(Formulae 1-6), the compressive specific-loudness transform (Formulae
7-9, Tables 2-4, C=0.0617 sone/Cam), binaural inhibition (Formulae
10-13) and integration to total loudness with the phon mapping of
Table 5.
Public API: loudness_moore_glasberg_from_spectrum (exact 5.2/5.4),
loudness_moore_glasberg_from_third_octave (29 bands, 5.5) and
loudness_moore_glasberg (signal wrapper), returning a frozen
MooreGlasbergLoudness with .plot() over the ERB-number scale.
Validated against the Annex B reference signals (tones, white/pink
noise, multi-tone complexes, tone-in-noise) to ~1-4%, well inside the
standard's 2.8 phon expanded uncertainty; the definitional anchor
(1 kHz/40 dB/free/binaural = 1.000 sone) holds end-to-end from the
tabulated constant. 50 new tests; full suite 802 passed / 12 skipped.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-3 Moore-Glasberg-Schlittenlacher time-varying loudness
Add loudness_moore_glasberg_time() implementing the ISO 532-3:2023 method:
a six-FFT multi-resolution running short-term spectrum (clause 7.3) feeding
the ISO 532-2 excitation and specific-loudness model on the 0.25 Cam grid
with the 532-3 constants (C=0.063, Tables 2/4, E_THRQ/E0=2.307), integrated
by asymmetric attack/release smoothing into short-term (clauses 7.6-7.8) and
long-term (clause 7.9) loudness traces, with peak long-term loudness,
percentiles and a lazy STL/LTL .plot().
Validated against the Annex C.1 tone table (1 kHz 10-80 dB, 3 kHz, 4 kHz,
100 Hz and monaural earphone) to within 0.4 phon, well inside the 2.8 phon
expanded uncertainty; the 1 kHz/40 dB anchor is exact at 1.000 sone. The
ISO 532-2 and ECMA-418-2 oracles are unchanged (831 passed / 12 skipped).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-3 per-ear LTL and Formula-14 kernel width (dichotic path)
Two transcription errors in the ISO 532-3:2023 dichotic (per-ear) binaural
path of the time-varying Moore-Glasberg-Schlittenlacher module:
1. Clause 7.9: the long-term loudness must be computed per ear by the
attack/release averager (Formulae 18-21) and then summed. The code ran a
single AGC on the binaural sum S'_L + S'_R; because the attack/release
branch is nonlinear, AGC(S'_L) + AGC(S'_R) != AGC(S'_L + S'_R) for dichotic
input. Keep two LTL states and sum the two long-term loudnesses. For
diotic/monaural the averager is linear in the (scaled) input so the result
is bit-for-bit identical to the old sum-then-AGC.
2. Formula (14)/(15): the smoothing weight is exp(-(B*Di)^2) with B = 0.08 and
Di in Cam. The code evaluated exp(-((0.08 * Di/0.1)^2)) = exp(-(0.8*Di)^2),
a Gaussian ~10x too narrow, and truncated the taps at +/-1.75 Cam. Remove
the spurious /0.1 and widen the kernel to the standard's +/-18 Cam (145 taps
on the 0.25-Cam grid). For diotic/monaural the L/R excitation ratio is 1
regardless of the kernel, so all oracles are unaffected.
No current validation signal exercises the dichotic path (Annex C signals are
diotic/monaural), so the 532-3 tone oracle and every other oracle remain
byte-identical; added focused dichotic tests plus a byte-identical anchor pin.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: numerical conformance report in CI PR comment
Reframe the PR comment into a persistent numerical conformance report:
each implemented standard -> normative expected value/range -> computed
result -> pass/fail with deviation, grouped by domain.
- scripts/conformance_report.py: registry of 21 checks across 17 standards
and 6 domains (filters/weightings, levels/dosimetry, psychoacoustics,
speech, intensity/power, room/building). Emits a headline summary, a
"Numerical validation - filters & weightings" section (per-architecture
IEC 61260-1 class margins + A/C/G weighting deviation vs the normative
curve) and one conformance table per domain. Deterministic, ~2.7 s.
- tests/reference_data.py: single source of truth for the shared normative
tables (IEC 61672-1 Table 3, ISO 7196 Table 2, ISO 717-1 Annex C R),
imported by both the tests and the report so they cannot drift.
- tests/test_conformance_report.py: smoke test asserting every registered
check passes and the Markdown is well-formed.
- comment_pr.py / workflow: conformance report leads the comment; the
test/coverage table is collapsed below. Retire benchmark_filters.py
(numerical content absorbed into the report).
make check green (740 passed, 12 skipped); ruff, mypy --strict, bandit clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: block-A review minors — docstring notes and tonality band-range
Four Minor findings from the plan-17 block-A psychoacoustics reviews. No
change to any validated numeric output; all five oracles stay byte-identical.
1. ISO 532-2 _source_levels: docstring note that clause 7.4 specifies a roex
weighting for X where the code uses a rectangular +/-ERB_n/2 window —
identical for isolated tones, immaterial for the Annex B broadband cases.
2. ISO 532-2 Formula-10 binaural smoothing kernel: the PDF (clause 8.1) states
"Di is changed in steps of 0,1" over -18..+18 Cam; the code confused the
0.1-Cam index offset with Di-in-Cam (the same /0.1 width bug just fixed in
532-3 Formula-14), making the Gaussian 10x too narrow and truncating to
+/-1.8 Cam. Widen to +/-18 Cam (361 taps) with Di = tap*_I_STEP. Provably
inert: all Annex B cases are diotic (ratio 1) or monaural (unit inhibition).
3. ECMA tonality _band_range: implement the exact Formulae 56-57 edge-midpoint
boundary conditions instead of a centre-frequency threshold; only affects
the optional user-band feature. Added a user-band edge test.
4. ECMA tonality reach: comment that the _CBF-1-band upper-edge term is a dead
defensive guard (N_B>0 only for low bands 0..24, so it never limits reach).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* chore: move conformance design doc out of repo root
* feat: clearer conformance report (value-vs-range) + block-A checks + audit fixes
Table 1 (filters): show, per architecture, the measured relative attenuation
at the binding band vs the class-1 limit it must clear (value + range, not
just a margin). Replace the "none" verdict (read as a failure) with a
"By design (ripple/soft rolloff)" label + footnote; Butterworth (default) and
Chebyshev-II are the only mask-compliant architectures. The measured value and
limit are re-derived with the public class_limits on the same designed SOS; a
smoke-test guard asserts the re-derived class-1 margin matches
verify_filter_class (single source of truth).
Table 2 (weightings): separate the informational max-deviation-from-nominal
(at a frequency extreme with wide, asymmetric tolerance) from the compliance
margin at the binding frequency, where the deviation, the +/- tolerance band
and the headroom are co-located so "value vs range" is unambiguous and in-spec.
Audit: verified all 21 checks (expected value/range vs the standard, real
library call, honest tolerance). One fix - ISO 226 anchored at 60 phon @ 2 kHz
-> 60 dB, a genuine Table B.1 value but coincidentally equal to the phon number
(reads as the trivial 1 kHz identity); re-anchored to the non-coincidental
60 phon @ 100 Hz -> 78.5 dB, sourced from reference_data. No library bug found.
Block-A psychoacoustics (+5, single-source expected in reference_data):
ECMA-418-2 loudness (1 kHz/40 dB -> 1 sone_HMS, c_N), tonality (-> 1 tu_HMS,
c_T), roughness (1 kHz/70 Hz/m=1/60 dB -> clean-room 1.0735 asper with a note
that the standard target is 1.0); ISO 532-2 (1 kHz/40 dB -> 1 sone, C=0.0617);
ISO 532-3 (steady 1 kHz/40 dB -> peak LTL 1 sone). 26/26 checks, 20 standards.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: versioned docs/CONFORMANCE.md with make target, CI staleness check and README link
Persist the numerical conformance report as a committed, versioned file so
every reader sees the library's per-standard validation (expected vs computed)
without running CI.
- docs/CONFORMANCE.md: committed report (26/26 checks, 6 domains, 20 standards)
with an auto-generated "do not hand-edit" header linking the standards docs.
- scripts/conformance_report.py: add a --file-header flag that emits that header
for the committed file (PR-comment body stays header-free); coarsen the
informational delta column to 3 decimals so BLAS/FFT-dependent sub-milli
residuals do not churn the file (status/computed columns keep regressions
visible).
- Makefile: `make conformance` regenerates the file; `make install-hooks`
installs the optional pre-commit hook.
- CI: new fast `conformance` job (runtime deps only) fails a PR if the committed
file is stale, telling the author to run `make conformance` and commit.
- hooks/pre-commit: opt-in convenience hook, scoped to src/scripts/reference
changes; CI is the enforcement.
- README + CONTRIBUTING: link and document the generated report.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced psychoacoustics guide, theory, API (EN)
Document the plan-17 block-A advanced psychoacoustics models: Moore-Glasberg
loudness (ISO 532-2/532-3), Sottek Hearing Model loudness, tonality and
roughness (ECMA-418-2:2025).
- Psychoacoustics guide: new "Advanced loudness & sound-quality models"
section with a model-comparison table, executed snippets, parameter tables,
res.plot() one-liners and figure-code collapsibles (docs + site twin,
code blocks byte-identical).
- Theory: excitation-pattern/specific-loudness chain, Sottek front-end,
ACF tonality and envelope-modulation roughness (GitHub-safe math).
- API reference: rows for the 5 functions (+ 2 MG spectral entry points) and
5 result dataclasses with their .plot().
- README, EN landing (standards 26 -> 29: ISO 532-2, ISO 532-3, ECMA-418-2),
CHANGELOG Unreleased/Added, llms regen.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced-psychoacoustics figures (loudness models, Sottek, tonality/roughness, time-varying)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de psicoacústica avanzada (Moore-Glasberg, Sottek, tonalidad, aspereza)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-2 signal-input path builds a narrowband spectrum (pure-tone loudness)
The loudness_moore_glasberg(x, fs) wrapper routed the signal through the
one-third-octave method, which smeared a pure tone across a whole 1/3-octave
band and used a coherent-gain window normalisation that over-counted power by
1.76 dB. A calibrated 1 kHz tone at 40 dB SPL therefore returned 1.327 sone
instead of the definitional anchor of 1.000 sone.
ISO 532-2 is spectrum-based and its exact input (clauses 5.2/5.4) is a set of
discrete sinusoidal components; the excitation pattern (Formula 5) is computed
per component, so a tone must enter as a single line. The wrapper now forms the
signal's narrowband (FFT) line spectrum with a power-preserving (Parseval)
window normalisation and feeds it to loudness_moore_glasberg_from_spectrum. A
1 kHz/40 dB tone now yields 1.0001 sone / 40 phon and multi-tone signals match
the exact spectrum path. The validated spectrum and third-octave paths are
byte-identical.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: correct MG signal-path mechanism text and presentation default
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 87 review round 1 — float-equality guards, 532-3 FFT window length, tonality short-signal, report/figure fixes
- roughness_ecma: replace three float == 0.0 checks with abs>eps guards
(SonarCloud gate); roughness oracle byte-identical (1.0735).
- loudness_moore_glasberg_time: per-window FFT length (>= window length) so
the 64 ms window is not truncated at 44.1/48 kHz (was ~0.74 dB low in the
20-80 Hz band). 32 kHz anchor byte-identical (n_max 1.0000044713237626).
- tonality_ecma: short signals fall back to averaging all blocks (matches
loudness_ecma), not the final block only; validated anchor unaffected.
- conformance_report: document deliberate 273.0 (speed of sound) vs 273.15
(C1/C2) split matching the library; complete bind_side docstring.
- generate_graphs: roughness annotation uses computed peak fmod; Sottek
metrics use tu_HMS/sone_HMS units; ES translations + 8 figures regenerated.
- hooks/pre-commit: drop set -e so a benign regen failure does not block the
commit (CI staleness check is the gate).
- test_iec_weighting_table3 docstring, Makefile .PHONY completion.
All 5 psychoacoustics oracles byte-identical; make check 868 passed / 12
skipped; ruff + mypy + bandit clean; conformance 26/26.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: localize decimal commas in Sottek-unit figure labels (underscore guard)
The `_` guard in `_translate_figure` skipped decimal-comma localization for
any label containing an underscore. The new Sottek/HMS unit tokens
(`sone_HMS`, `tu_HMS`, `sonios_HMS`) carry underscores, so Spanish figures
regressed to dots (`8.0`, `0.42`) instead of commas (`8,0`, `0,42`).
Drop the underscore guard: the decimal-comma regex only rewrites a bare
`digit.digit` not adjacent to more digits/dots, so unit identifiers and
version numbers (5.3.3) stay intact while genuine decimals get commas.
Mathtext is still skipped via the `$` guard. Regenerated the affected ES
figures (sottek_specific_loudness, tonality_roughness_demo).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Audit-driven accuracy, robustness and didactic improvements across the library (#82)
* fix: audit wave A — inter-sample LCpeak, cheby2 class-1 default, 144 kHz weighting target
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave A — decay validity thresholds, variant sharpness anchors, N5 phase, STIPA warning, Farina guard
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: audit wave B — self-contained snippets, tables, sharpness/open-plan figures, orphans
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave C — remaining minors and optimizations across the library
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: document the audit-wave parameters across guides and API tables
Cover the public parameters added in the audit fix waves across all three
documentation trees (docs/, site EN, site ES):
- lc_peak(..., oversample=8): inter-sample peak recovery (levels guide + API)
- calculate_sensitivity(..., narrowband=False): coherent tone estimator
(calibration guide + API)
- sound_intensity(..., bias_correct=False): finite-difference bias correction
(intensity guide + API)
- room_parameters/decay_curve(..., zero_phase=False): 125 Hz short-T bias
(room-acoustics guide + API)
- OctaveFilterBank/octavefilter attenuation default 60 -> 72 and cheby2
class-1 note (filter-banks guide + API)
- STIPA < 15 s UserWarning (psychoacoustics guide + API)
Also refresh dependent behaviour notes: ln_levels attack skip 2*tau -> 5*tau,
T20/T30 validity thresholds 35/45 -> 46/54 dB, and the zero-phase broadband
~0.2-0.3 dB band-level caveat. Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: truthfulness fixes from the final audit-branch review
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: 2000 Hz, not 2 ms, in the N5/N10 docstring
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 82 review round 1 — bias-correction cutoff, parabolic-peak guard, complexity, snippet self-containment
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
feat: noise-induced hearing loss (ISO 1999:2013 NIPTS and HTLAN) (#103)
Adds a `phonometry.iso1999` module estimating occupational noise-induced hearing loss, extending the ISO 7029 age-related threshold with a noise component.
* `nipts(l_ex, years, fractile)` — noise-induced permanent threshold shift over 500-6000 Hz: median N50 (clause 6.3.1, Formula 2/3, Table 1) and population fractiles from the half-Gaussian spreads du/dl (clause 6.3.2, Formulae 4-7, Tables 2-3), clamped at zero.
* `htlan(age, sex, l_ex, years, fractile)` — hearing threshold level associated with age and noise, combining the age component (HTLA = ISO 7029) with the NIPTS by H' = H + N - H*N/120 (clause 6.1, Formula 1).
Validated against ISO 1999 Annex D (Tables D.1-D.4, 85-100 dB, 10-40 years): reproduces every tabulated dB value. Conformance 82/82; full suite 1686 passed. Figure + two-lane NIHL diagram (EN/ES, light/dark), repo doc and EN/ES site guides.
ISO 2631-5: multiple-shock whole-body vibration (#106)
* feat: ISO 2631-5 multiple-shock whole-body vibration
Add the ISO 2631-5:2018 spinal-response model for whole-body vibration
containing multiple shocks: the normative Clause 5 dose and the Annex C
injury assessment (the vertical z-axis).
A seat-to-spine transfer function (clause 5.2, Formula 1) maps the
measured seat acceleration to the spinal response Az(t); the acceleration
dose Dz = 1.07*(sum Az,i^6)^(1/6) combines the positive response peaks
(Formula 3), scaled to a daily dose (Formula 4/5). Annex C turns the
daily dose into the compressive stress Sd = mz*Dzd (C.1), the
age-cumulated stress variable R (C.3/C.4) and the Weibull probability of
lumbar injury (C.5).
New module phonometry.iso2631_5 with seat_to_spine_transfer,
spinal_response, acceleration_dose, daily_dose(_multi), compression_dose,
injury_risk, injury_probability and the multiple_shock_assessment
convenience returning a MultipleShockResult with .plot(). Conformance
report gains a multiple-shock domain (3 checks, 88 total). Adds the
multiple-shock-vibration guide (repo + EN/ES site) with a figure and a
flow diagram.
Validated against the standard: the seat-to-spine transfer function
reproduces the Annex D 256 Hz digital realization to within the clause
5.2 tolerance, and the Annex C worked example (5 x 40 m/s2, 82 kg male)
gives Dzd=55.97 m/s2, R=1.22 and injury probability 0.37.
* refactor: address review feedback on ISO 2631-5
- response_peaks: vectorize the positive-peak search with NumPy instead
of a Python loop (Gemini, CodeRabbit)
- injury_probability: accept array-like R and return an array or float,
clamping negative R to zero (Gemini)
- multiple_shock_assessment: raise if only one of exposure_time /
measurement_time is given instead of silently skipping the daily-dose
scaling (Gemini, CodeRabbit)
- spinal_response: reject multi-dimensional input with a clear error
(CodeRabbit)
- export static_stress alongside the other Annex C helpers (CodeRabbit)
- figure/plot: use the vectorized injury_probability directly
- tests: assert_allclose over the transfer-function band, use MZ_MALE
and strict zip, cover array probability and the partial-argument guard
- es guide: consistent decimal style in the code comment (CodeRabbit)
* fix: Spanish decimal comma in mathtext figure legends
The decimal-comma pass skips any label containing mathtext ($...$), so
the ISO 2631-5 'Ejemplo $R$ = 1.22' and NT ACOU 112 'Determinante
$K_I$ = 13.0 dB' legends kept a dot in Spanish. Fold the decimal
conversion into the _ES_PATTERNS rules for those labels (CodeRabbit).
Building acoustics II: facade insulation (ISO 16283-3), laboratory measurement (ISO 10140), prediction with flanking (EN 12354) and uncertainty (ISO 12999-1) (#88)
* feat: ISO 16283-3 facade sound insulation
Add facade_insulation() and FacadeInsulationResult for field facade sound
insulation per ISO 16283-3: the global-method level difference D2m and its
standardized (D2m,nT) and normalized (D2m,n) forms, plus the element-method
apparent sound reduction index R'45 (loudspeaker, -1,5 dB) / R'tr,s (road
traffic, -3 dB). Reuses the ISO 16283-1 energy-averaging helpers, the
Sabine absorption area, and the ISO 717-1 airborne weighted_rating engine
unchanged for the single-number rating. Adds a per-band .plot() profile and
exports.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 16283-3 draft-edition caveat and symmetric R' validation
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 10140 laboratory airborne and impact sound insulation
Add src/phonometry/lab_insulation.py, the laboratory counterpart of the
field ISO 16283 family:
- lab_airborne_insulation: R = L1 - L2 + 10 lg(S/A) with A = 0,16 V/T
(ISO 10140-2:2010 Formula (2); ISO 10140-4:2010 Formula (5)).
- lab_impact_insulation: Ln = Li + 10 lg(A/A0), A0 = 10 m²
(ISO 10140-3:2010 Formula (1)).
- background_correction: ISO 10140-4:2010 Clause 4.3 Formula (4) with the
6/15 dB criteria and the 1,3 dB limit-of-measurement cap.
Single-number Rw / Ln,w with C, Ctr, CI reuse the verified ISO 717-1/2
weighted_rating / weighted_impact_rating engines; position energy
averaging reuses _as_band_levels. 29 new closed-form / identity tests.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: reconcile facade citations with published ISO 16283-3:2016
The facade module was drafted from ISO/DIS 16283-3:2014; its docstrings
cited draft formula numbers with a caveat to reconcile against the
published edition. Reconciled against ISO 16283-3:2016(E) (First edition
2016-02-01):
- Clause numbers are edition-stable: the field-quantity definitions keep
3.12 (R'45deg), 3.13 (R'tr,s), 3.14 (D2m), 3.15 (D2m,nT), 3.16 (D2m,n),
3.17 (A=0,16V/T), and 9.5.1 (surface-level averaging).
- Formula numbers differ: in the 2016 edition the Clause 3 defining
formulas are UNNUMBERED (inline in the term definitions); the numbered
formulas (1)-(21) live in the procedural clauses. The surface-level
energy-average is Formula (7) in 2016 (was Formula (20) in the DIS).
- Dropped the invalid draft Formula (2)-(7) citations from the field-
quantity docstrings and the draft-edition reconciliation caveat.
- Constants and formula bodies are unchanged (-1,5 dB / -3 dB, A0=10 m2,
T0=0,5 s, A=0,16V/T, band ranges): no numeric change; 22 facade tests
green.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: EN 12354-1/2 building acoustic performance prediction with flanking
Implement the EN 12354:2000 simplified single-number prediction model for
apparent airborne (R'w) and impact (L'n,w) sound insulation, including direct
plus flanking transmission (Ff/Df/Fd paths) and the Annex E vibration-reduction
index Kij for rigid cross, rigid T, flexible-interlayer and lightweight-facade
junctions.
Airborne Formula (26)/(27)/(28a) with l0=1 m, Kij,min (29), lining composition
(30)/(31); impact Formula (21) with bare-floor Ln,w,eq (164-35 lg m'), Table 1
flanking correction K, and standardized L'nT,w (3). Results expose per-path
energy contributions so the dominant flanking path is visible.
Validated against the standards' worked examples: Part 1 Annex H.3 airborne
(R'w = 52 dB, 13 paths; +floating-floor 53 dB) and Part 2 Annex E.3 impact
(L'n,w = 45 dB, L'nT,w = 43 dB); all four Annex H junctions reproduce Kij.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 12999-1 building acoustics measurement uncertainty
Add building_uncertainty module implementing ISO 12999-1:2020 standard
uncertainties for sound-insulation quantities: per-band and single-number
values for airborne (Tables 2/3), impact (Tables 4/5) and floor-covering
reduction (Tables 6/7), across measurement situations A/B/C (Clause 5.2),
plus the Annex D sigma_R95 upper limits, Table 1 max repeatability and
Table 8 coverage factors.
Expansion U = k*u with k >= 1 (Clause 8), one-sided factors for conformity
checks (Formulae 4/5), and combination rules from Annexes A/B/C (prediction
input A.1, quadrature A.2/C.2, m-measurement reduction A.7, uncorrelated
single-number B.2). Composable UncertainValue attaches value +- U without
touching the rating dataclasses.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: numpy-stub-agnostic annotations for the two env-dependent mypy sites
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: PR-B review minors — anchors, Kij floor, uncertainty polish
- Table 3 r_w+ctr_50_5000 σsitu(B)=1.0 verified digit-by-digit against the
ISO 12999-1:2020(E) page image; anomalous but normative (comment added).
- Anchor the ISO 10140 rating tests to independent literals (Rw=54, Ln,w=58 —
reference-curve shape + the 32 dB / 16-band = 2 dB ISO 717 shift).
- background_correction: cross-ref sound_power.background_noise_correction and
document the negative-margin (Lb > Lsb) cap at Lsb−1.3.
- flanking_path: optional kij_min clamp for EN 12354-1 Clause 4.4.2 (Kij≥Kij,min),
documented on flanking_element; tested clamped vs unclamped (H.3 unaffected).
- impact_flanking_correction: comment on nearest-neighbour + tie-to-lower.
- Export COVERAGE_FACTORS (Table 8) as public API.
- single_number_uncertainty: note impact situation A is an estimate (Table 5 fn a).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: conformance checks for facade, lab insulation, EN 12354 and ISO 12999
Add six numerical conformance checks for the PR-B building-acoustics
standards, following the established registry pattern (single source of
truth in tests/reference_data.py, clause citations, honest tolerances):
- ISO 16283-3:2016 Clause 3.12: facade R'45 isolates the -1.5 dB
oblique-incidence correction on an S=A constructed case (exact).
- ISO 10140-2:2021 Formula (2): lab airborne R laid on the ISO 717-1
reference shape (S=A) -> Rw = 54 (the +2-shift analytic anchor).
- EN 12354-1:2000 Annex H.3: airborne prediction R'w = 52 from the 13
transmission paths (direct + 12 flanking), the branch's strongest oracle.
- EN 12354-2:2000 Annex E.3: impact prediction L'n,w = 45 (76-33+2).
- ISO 12999-1:2020 Table 2: airborne band uncertainty, situation A @
1 kHz = 1.8 dB (digit-exact), and Clause 8/Table 8 expanded
uncertainty U = 1.96 u = 2.352 dB (exact k=1.96 arithmetic).
Facade and lab checks join "Room & building acoustics"; the EN 12354 /
ISO 12999 checks form a new "Building prediction & uncertainty" domain.
Registry grows 26 -> 32 checks across 7 domains. Shared expected values
and the Annex H.3 input table move into reference_data.py so the report
and tests cannot drift; a consistency test pins them to the published
worked-example results. docs/CONFORMANCE.md regenerated via make conformance.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: building acoustics II — facade, laboratory, prediction and uncertainty (EN)
Extend the Room & Building Acoustics guide with facade insulation
(ISO 16283-3), laboratory characterisation (ISO 10140), flanking-transmission
performance prediction (EN 12354-1/2) and measurement uncertainty
(ISO 12999-1), with executed snippets reproducing the EN 12354-1 Annex H.3
(R'w = 52 dB) and Annex E.3 (L'n,w = 45 dB) worked examples. Add the matching
theory subsections, API-reference rows for every new public name, README and
landing-page entries (29 -> 33 standards), and CHANGELOG. EN docs and site
twins keep byte-identical code blocks.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: flanking-paths diagram, prediction and uncertainty figures
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de edificación II (fachadas, laboratorio, predicción, incertidumbre)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: PR-B final-review fixes — count, GitHub-safe math, edition alignment
Landing count corrected to 34 standards (slash-part convention); the
GitHub-unsafe math spacing tokens reintroduced by the new building
sections are scrubbed from the docs/ tree and its site twins; the whole
ISO 10140-2 citation chain aligned to the verified :2010 edition; plus
the six minor documentation-precision fixes from the final review.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 88 review round 1 — isfinite idiom, heading levels, frequencies validation
- building_prediction._check_finite: replace 'v != v or v in (inf,-inf)'
NaN idiom with math.isfinite for SonarCloud (same behavior, cleaner).
- room-acoustics docs (EN docs + site, ES site): relevel the three
parameter-table headings that skipped H2->H4 to H3 (MD001).
- insulation.facade_insulation: validate 'frequencies' length against the
band count, raising a clear ValueError instead of deferring a matplotlib
shape error to plot(). lab_insulation/building_uncertainty have no such
gap (no user-supplied frequencies stored). Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Audit-driven accuracy, robustness and didactic improvements across the library (#82)
* fix: audit wave A — inter-sample LCpeak, cheby2 class-1 default, 144 kHz weighting target
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave A — decay validity thresholds, variant sharpness anchors, N5 phase, STIPA warning, Farina guard
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: audit wave B — self-contained snippets, tables, sharpness/open-plan figures, orphans
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave C — remaining minors and optimizations across the library
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: document the audit-wave parameters across guides and API tables
Cover the public parameters added in the audit fix waves across all three
documentation trees (docs/, site EN, site ES):
- lc_peak(..., oversample=8): inter-sample peak recovery (levels guide + API)
- calculate_sensitivity(..., narrowband=False): coherent tone estimator
(calibration guide + API)
- sound_intensity(..., bias_correct=False): finite-difference bias correction
(intensity guide + API)
- room_parameters/decay_curve(..., zero_phase=False): 125 Hz short-T bias
(room-acoustics guide + API)
- OctaveFilterBank/octavefilter attenuation default 60 -> 72 and cheby2
class-1 note (filter-banks guide + API)
- STIPA < 15 s UserWarning (psychoacoustics guide + API)
Also refresh dependent behaviour notes: ln_levels attack skip 2*tau -> 5*tau,
T20/T30 validity thresholds 35/45 -> 46/54 dB, and the zero-phase broadband
~0.2-0.3 dB band-level caveat. Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: truthfulness fixes from the final audit-branch review
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: 2000 Hz, not 2 ms, in the N5/N10 docstring
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 82 review round 1 — bias-correction cutoff, parabolic-peak guard, complexity, snippet self-containment
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Run the full ISO 532-1 Annex B loudness validation from in-repo signals (#90)
* test: run the full ISO 532-1 Annex B validation from in-repo signals
Commit the ISO 532-1:2017 electronic-attachment validation data under
tests/data/iso532_1/ with a README documenting provenance, ISO copyright,
the source URL and a removal policy. This turns the twelve previously
skipped Annex B.5 technical-signal tests into always-on checks:
- Add the twelve recorded B.5 technical signals (byte-identical to the ISO
attachment) and gather the existing tone-pulse/level/expected fixtures
into the same folder.
- Read the WAVs with the stdlib `wave` module (no soundfile dependency) and
scale per Annex B.1 (full scale = 100 dB SPL -> 2*sqrt(2) Pa peak).
- Validate each signal in the sound field its ISO results workbook was
computed in; signal 15 (vehicle interior) is diffuse, the rest free. This
was the sole failing case and it is a field mismatch, not an algorithm
error. Nmax now reproduces the workbook to < 0.01 % on all twelve, so the
Nmax tolerance is tightened from 5 % to 0.1 %.
- Add two Annex B.5 rows (free + diffuse technical recording) to the
numerical conformance report; docs/CONFORMANCE.md regenerated (34/34).
Also clean the pre-existing mypy --strict debt in scripts/ (13 findings in
conformance_report.py, generate_graphs.py and generate_diagrams.py) and
extend the lint gate and CI to type-check scripts/ alongside src/ so it
cannot regress.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* test: harden ISO 532-1 WAV loading and graceful data absence (PR review)
Addresses the PR #90 bot review:
- Read WAVs with the little-endian dtype '<i2' (RIFF is little-endian, not
host-native) and keep only channel 0 of any multi-channel file, in both
the test reader and the conformance helper.
- Load the Annex B expected-values JSON defensively: the module no longer
crashes on import when the ISO data folder is absent; the six data-backed
tests carry a `requires_iso_data` skip marker instead, honouring the
README's removal policy while the synthetic-signal tests keep running.
- Raise a clear FileNotFoundError (not an IndexError) in the conformance
helper when no Annex B.5 WAV matches.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* test: unify the ISO 532-1 data root behind ISO532_1_TESTDATA (PR review)
CodeRabbit noted the presence gate and expected-values JSON were tied to
the hard-coded in-repo path while the Annex B.5 lookup honoured the
ISO532_1_TESTDATA override, so a valid override with the packaged JSON
absent would still skip. Derive the single DATA root from the override
(defaulting to the in-repo fixtures) and read the JSON, the presence gate
and the B.5 recordings all from it; drop the separate ISO_DIR.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* fix: mypy --strict on PR-C script code surfaced by the merged gate
Merging main (PR #89) brought the ISO 9613-2/9612 figure and conformance
helpers under this branch's extended `mypy src scripts` gate for the first
time: annotate the ISO 9612 Annex D task builder's return type and cast a
numpy int bar-label position to float.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
Advanced psychoacoustics (ISO 532-2/3 Moore-Glasberg, ECMA-418-2 Sottek loudness/tonality/roughness) + numerical conformance report (#87)
* feat: ECMA-418-2 Sottek Hearing Model loudness and auditory front-end
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic loudness metric and
its shared auditory front-end (Sottek Hearing Model):
- Clause 5 front-end: outer/middle-ear cascade (5.1.3, Table 1), 53-band
gammatone-like auditory filter bank (5.1.4), band-dependent segmentation
(5.1.5, Table 4), rectification/RMS (5.1.6-5.1.7), compressive nonlinearity
(5.1.8, Formula 23, Table 2) and threshold in quiet (5.1.9, Table 3).
- Clause 6.2.2-6.2.7 ACF-based tonal/noise specific loudness (reusable by the
later tonality/roughness metrics).
- Clause 8 assembly: tonal/noise power average, average specific loudness,
time-dependent and single representative loudness value.
The front-end helpers are factored for reuse. Calibration verified: a 1 kHz
40 dB SPL sinusoid yields 0.996 sone_HMS (target 1.0). Adds a frozen
EcmaLoudness result with lazy .plot(), exports, and 12 tests.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 tonality (Sottek Hearing Model)
Implements the psychoacoustic tonality metric of ECMA-418-2:2025
(Clause 6.2.8-6.2.11) on top of the committed Sottek front-end and
ACF tonal/noise decomposition.
- tonality_ecma() -> EcmaTonality with the single value T (Formula 63),
average specific tonality T'(z) (Formula 53), tonal frequencies
f_ton,z(z) (Formula 55), time-dependent tonality T(l) (Formula 61)
and its frequency f_ton(l) (Formula 62); optional user band [f_low,f_high].
- Full Clause 6.2.3 band averaging with cross-block-size-group ACF
recomputation (loudness keeps its simplified, valid-for-loudness path;
boundary marker added there). Loudness 0.996 sone guard unchanged.
- _tonal_estimate now also returns the DFT-peak tonal frequency, with the
argmax restricted to the lower DFT half to avoid Nyquist-mirror aliasing.
- .plot() via _plotting.plot_ecma_tonality; exports in __init__.
- 13 conformance tests: 1 kHz/40 dB -> 1 tu_HMS calibration, pure tone
>> noise, f_ton tracks the tone, silence -> 0, band restriction.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 roughness (Sottek Hearing Model)
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic roughness metric
(Clause 7), the third Sottek Hearing Model output and new library
functionality. Reuses the Clause 5 auditory front-end from loudness_ecma
(ear filter, 53-band gammatone bank, specific basis loudness) and adds the
roughness-specific chain: start-only zero-padding and fixed 16384/4096
segmentation (5.1.2.2, 5.1.5.2, 7.1.1), the Hilbert envelope with x32
downsampling to 1500 Hz (7.1.2), the scaled envelope power spectrum
(7.1.3), two-step noise reduction (7.1.4), the four-stage spectral
weighting with quadratic-fit modulation-rate refinement, bias correction,
high/low modulation-rate weighting and fundamental-rate estimation
(7.1.5), interpolation to 50 Hz with the distribution-dependent nonlinear
transform, tabulated calibration c_R and asymmetric smoothing (7.1.7), and
the R'(z) / R(l50) / 90th-percentile aggregation (7.1.8-7.1.10).
Uses the standard's tabulated c_R = 0.0180685 (Formula 104), not
reverse-fit: the 1 kHz / 70 Hz / m=1 / 60 dB calibration signal yields
R = 1.0735 asper (near-but-not-exactly 1). OCR-ambiguous formulae
(66, 71, 78-81) cross-checked against the SQAT MATLAB reference; the
optional entropy weighting (7.1.6, needs RPM telemetry) is documented but
not implemented.
Adds roughness_ecma()/EcmaRoughness, a two-panel .plot(), exports, a
10-test TDD suite and plan/notes-ecma418-2-roughness.md. Full suite
752 passed / 12 skipped; loudness (0.996) and tonality (0.99993) oracles
unchanged (both modules untouched).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* test: pin ECMA-418-2 roughness calibration value and document clean-room variance
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-2 Moore-Glasberg loudness
Add the stationary Moore-Glasberg loudness model of ISO 532-2:2017 as a
third loudness method alongside the Zwicker (ISO 532-1) and Sottek
(ECMA-418-2) implementations. The full Clause 7 chain is implemented:
fixed outer/middle-ear transfer (Table 1), the level-dependent roex
auditory filter bank and excitation pattern on the ERB-number scale
(Formulae 1-6), the compressive specific-loudness transform (Formulae
7-9, Tables 2-4, C=0.0617 sone/Cam), binaural inhibition (Formulae
10-13) and integration to total loudness with the phon mapping of
Table 5.
Public API: loudness_moore_glasberg_from_spectrum (exact 5.2/5.4),
loudness_moore_glasberg_from_third_octave (29 bands, 5.5) and
loudness_moore_glasberg (signal wrapper), returning a frozen
MooreGlasbergLoudness with .plot() over the ERB-number scale.
Validated against the Annex B reference signals (tones, white/pink
noise, multi-tone complexes, tone-in-noise) to ~1-4%, well inside the
standard's 2.8 phon expanded uncertainty; the definitional anchor
(1 kHz/40 dB/free/binaural = 1.000 sone) holds end-to-end from the
tabulated constant. 50 new tests; full suite 802 passed / 12 skipped.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-3 Moore-Glasberg-Schlittenlacher time-varying loudness
Add loudness_moore_glasberg_time() implementing the ISO 532-3:2023 method:
a six-FFT multi-resolution running short-term spectrum (clause 7.3) feeding
the ISO 532-2 excitation and specific-loudness model on the 0.25 Cam grid
with the 532-3 constants (C=0.063, Tables 2/4, E_THRQ/E0=2.307), integrated
by asymmetric attack/release smoothing into short-term (clauses 7.6-7.8) and
long-term (clause 7.9) loudness traces, with peak long-term loudness,
percentiles and a lazy STL/LTL .plot().
Validated against the Annex C.1 tone table (1 kHz 10-80 dB, 3 kHz, 4 kHz,
100 Hz and monaural earphone) to within 0.4 phon, well inside the 2.8 phon
expanded uncertainty; the 1 kHz/40 dB anchor is exact at 1.000 sone. The
ISO 532-2 and ECMA-418-2 oracles are unchanged (831 passed / 12 skipped).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-3 per-ear LTL and Formula-14 kernel width (dichotic path)
Two transcription errors in the ISO 532-3:2023 dichotic (per-ear) binaural
path of the time-varying Moore-Glasberg-Schlittenlacher module:
1. Clause 7.9: the long-term loudness must be computed per ear by the
attack/release averager (Formulae 18-21) and then summed. The code ran a
single AGC on the binaural sum S'_L + S'_R; because the attack/release
branch is nonlinear, AGC(S'_L) + AGC(S'_R) != AGC(S'_L + S'_R) for dichotic
input. Keep two LTL states and sum the two long-term loudnesses. For
diotic/monaural the averager is linear in the (scaled) input so the result
is bit-for-bit identical to the old sum-then-AGC.
2. Formula (14)/(15): the smoothing weight is exp(-(B*Di)^2) with B = 0.08 and
Di in Cam. The code evaluated exp(-((0.08 * Di/0.1)^2)) = exp(-(0.8*Di)^2),
a Gaussian ~10x too narrow, and truncated the taps at +/-1.75 Cam. Remove
the spurious /0.1 and widen the kernel to the standard's +/-18 Cam (145 taps
on the 0.25-Cam grid). For diotic/monaural the L/R excitation ratio is 1
regardless of the kernel, so all oracles are unaffected.
No current validation signal exercises the dichotic path (Annex C signals are
diotic/monaural), so the 532-3 tone oracle and every other oracle remain
byte-identical; added focused dichotic tests plus a byte-identical anchor pin.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: numerical conformance report in CI PR comment
Reframe the PR comment into a persistent numerical conformance report:
each implemented standard -> normative expected value/range -> computed
result -> pass/fail with deviation, grouped by domain.
- scripts/conformance_report.py: registry of 21 checks across 17 standards
and 6 domains (filters/weightings, levels/dosimetry, psychoacoustics,
speech, intensity/power, room/building). Emits a headline summary, a
"Numerical validation - filters & weightings" section (per-architecture
IEC 61260-1 class margins + A/C/G weighting deviation vs the normative
curve) and one conformance table per domain. Deterministic, ~2.7 s.
- tests/reference_data.py: single source of truth for the shared normative
tables (IEC 61672-1 Table 3, ISO 7196 Table 2, ISO 717-1 Annex C R),
imported by both the tests and the report so they cannot drift.
- tests/test_conformance_report.py: smoke test asserting every registered
check passes and the Markdown is well-formed.
- comment_pr.py / workflow: conformance report leads the comment; the
test/coverage table is collapsed below. Retire benchmark_filters.py
(numerical content absorbed into the report).
make check green (740 passed, 12 skipped); ruff, mypy --strict, bandit clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: block-A review minors — docstring notes and tonality band-range
Four Minor findings from the plan-17 block-A psychoacoustics reviews. No
change to any validated numeric output; all five oracles stay byte-identical.
1. ISO 532-2 _source_levels: docstring note that clause 7.4 specifies a roex
weighting for X where the code uses a rectangular +/-ERB_n/2 window —
identical for isolated tones, immaterial for the Annex B broadband cases.
2. ISO 532-2 Formula-10 binaural smoothing kernel: the PDF (clause 8.1) states
"Di is changed in steps of 0,1" over -18..+18 Cam; the code confused the
0.1-Cam index offset with Di-in-Cam (the same /0.1 width bug just fixed in
532-3 Formula-14), making the Gaussian 10x too narrow and truncating to
+/-1.8 Cam. Widen to +/-18 Cam (361 taps) with Di = tap*_I_STEP. Provably
inert: all Annex B cases are diotic (ratio 1) or monaural (unit inhibition).
3. ECMA tonality _band_range: implement the exact Formulae 56-57 edge-midpoint
boundary conditions instead of a centre-frequency threshold; only affects
the optional user-band feature. Added a user-band edge test.
4. ECMA tonality reach: comment that the _CBF-1-band upper-edge term is a dead
defensive guard (N_B>0 only for low bands 0..24, so it never limits reach).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* chore: move conformance design doc out of repo root
* feat: clearer conformance report (value-vs-range) + block-A checks + audit fixes
Table 1 (filters): show, per architecture, the measured relative attenuation
at the binding band vs the class-1 limit it must clear (value + range, not
just a margin). Replace the "none" verdict (read as a failure) with a
"By design (ripple/soft rolloff)" label + footnote; Butterworth (default) and
Chebyshev-II are the only mask-compliant architectures. The measured value and
limit are re-derived with the public class_limits on the same designed SOS; a
smoke-test guard asserts the re-derived class-1 margin matches
verify_filter_class (single source of truth).
Table 2 (weightings): separate the informational max-deviation-from-nominal
(at a frequency extreme with wide, asymmetric tolerance) from the compliance
margin at the binding frequency, where the deviation, the +/- tolerance band
and the headroom are co-located so "value vs range" is unambiguous and in-spec.
Audit: verified all 21 checks (expected value/range vs the standard, real
library call, honest tolerance). One fix - ISO 226 anchored at 60 phon @ 2 kHz
-> 60 dB, a genuine Table B.1 value but coincidentally equal to the phon number
(reads as the trivial 1 kHz identity); re-anchored to the non-coincidental
60 phon @ 100 Hz -> 78.5 dB, sourced from reference_data. No library bug found.
Block-A psychoacoustics (+5, single-source expected in reference_data):
ECMA-418-2 loudness (1 kHz/40 dB -> 1 sone_HMS, c_N), tonality (-> 1 tu_HMS,
c_T), roughness (1 kHz/70 Hz/m=1/60 dB -> clean-room 1.0735 asper with a note
that the standard target is 1.0); ISO 532-2 (1 kHz/40 dB -> 1 sone, C=0.0617);
ISO 532-3 (steady 1 kHz/40 dB -> peak LTL 1 sone). 26/26 checks, 20 standards.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: versioned docs/CONFORMANCE.md with make target, CI staleness check and README link
Persist the numerical conformance report as a committed, versioned file so
every reader sees the library's per-standard validation (expected vs computed)
without running CI.
- docs/CONFORMANCE.md: committed report (26/26 checks, 6 domains, 20 standards)
with an auto-generated "do not hand-edit" header linking the standards docs.
- scripts/conformance_report.py: add a --file-header flag that emits that header
for the committed file (PR-comment body stays header-free); coarsen the
informational delta column to 3 decimals so BLAS/FFT-dependent sub-milli
residuals do not churn the file (status/computed columns keep regressions
visible).
- Makefile: `make conformance` regenerates the file; `make install-hooks`
installs the optional pre-commit hook.
- CI: new fast `conformance` job (runtime deps only) fails a PR if the committed
file is stale, telling the author to run `make conformance` and commit.
- hooks/pre-commit: opt-in convenience hook, scoped to src/scripts/reference
changes; CI is the enforcement.
- README + CONTRIBUTING: link and document the generated report.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced psychoacoustics guide, theory, API (EN)
Document the plan-17 block-A advanced psychoacoustics models: Moore-Glasberg
loudness (ISO 532-2/532-3), Sottek Hearing Model loudness, tonality and
roughness (ECMA-418-2:2025).
- Psychoacoustics guide: new "Advanced loudness & sound-quality models"
section with a model-comparison table, executed snippets, parameter tables,
res.plot() one-liners and figure-code collapsibles (docs + site twin,
code blocks byte-identical).
- Theory: excitation-pattern/specific-loudness chain, Sottek front-end,
ACF tonality and envelope-modulation roughness (GitHub-safe math).
- API reference: rows for the 5 functions (+ 2 MG spectral entry points) and
5 result dataclasses with their .plot().
- README, EN landing (standards 26 -> 29: ISO 532-2, ISO 532-3, ECMA-418-2),
CHANGELOG Unreleased/Added, llms regen.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced-psychoacoustics figures (loudness models, Sottek, tonality/roughness, time-varying)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de psicoacústica avanzada (Moore-Glasberg, Sottek, tonalidad, aspereza)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-2 signal-input path builds a narrowband spectrum (pure-tone loudness)
The loudness_moore_glasberg(x, fs) wrapper routed the signal through the
one-third-octave method, which smeared a pure tone across a whole 1/3-octave
band and used a coherent-gain window normalisation that over-counted power by
1.76 dB. A calibrated 1 kHz tone at 40 dB SPL therefore returned 1.327 sone
instead of the definitional anchor of 1.000 sone.
ISO 532-2 is spectrum-based and its exact input (clauses 5.2/5.4) is a set of
discrete sinusoidal components; the excitation pattern (Formula 5) is computed
per component, so a tone must enter as a single line. The wrapper now forms the
signal's narrowband (FFT) line spectrum with a power-preserving (Parseval)
window normalisation and feeds it to loudness_moore_glasberg_from_spectrum. A
1 kHz/40 dB tone now yields 1.0001 sone / 40 phon and multi-tone signals match
the exact spectrum path. The validated spectrum and third-octave paths are
byte-identical.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: correct MG signal-path mechanism text and presentation default
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 87 review round 1 — float-equality guards, 532-3 FFT window length, tonality short-signal, report/figure fixes
- roughness_ecma: replace three float == 0.0 checks with abs>eps guards
(SonarCloud gate); roughness oracle byte-identical (1.0735).
- loudness_moore_glasberg_time: per-window FFT length (>= window length) so
the 64 ms window is not truncated at 44.1/48 kHz (was ~0.74 dB low in the
20-80 Hz band). 32 kHz anchor byte-identical (n_max 1.0000044713237626).
- tonality_ecma: short signals fall back to averaging all blocks (matches
loudness_ecma), not the final block only; validated anchor unaffected.
- conformance_report: document deliberate 273.0 (speed of sound) vs 273.15
(C1/C2) split matching the library; complete bind_side docstring.
- generate_graphs: roughness annotation uses computed peak fmod; Sottek
metrics use tu_HMS/sone_HMS units; ES translations + 8 figures regenerated.
- hooks/pre-commit: drop set -e so a benign regen failure does not block the
commit (CI staleness check is the gate).
- test_iec_weighting_table3 docstring, Makefile .PHONY completion.
All 5 psychoacoustics oracles byte-identical; make check 868 passed / 12
skipped; ruff + mypy + bandit clean; conformance 26/26.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: localize decimal commas in Sottek-unit figure labels (underscore guard)
The `_` guard in `_translate_figure` skipped decimal-comma localization for
any label containing an underscore. The new Sottek/HMS unit tokens
(`sone_HMS`, `tu_HMS`, `sonios_HMS`) carry underscores, so Spanish figures
regressed to dots (`8.0`, `0.42`) instead of commas (`8,0`, `0,42`).
Drop the underscore guard: the decimal-comma regex only rewrites a bare
`digit.digit` not adjacent to more digits/dots, so unit identifiers and
version numbers (5.3.3) stay intact while genuine decimals get commas.
Mathtext is still skipped via the `$` guard. Regenerated the affected ES
figures (sottek_specific_loudness, tonality_roughness_demo).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Advanced psychoacoustics (ISO 532-2/3 Moore-Glasberg, ECMA-418-2 Sottek loudness/tonality/roughness) + numerical conformance report (#87)
* feat: ECMA-418-2 Sottek Hearing Model loudness and auditory front-end
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic loudness metric and
its shared auditory front-end (Sottek Hearing Model):
- Clause 5 front-end: outer/middle-ear cascade (5.1.3, Table 1), 53-band
gammatone-like auditory filter bank (5.1.4), band-dependent segmentation
(5.1.5, Table 4), rectification/RMS (5.1.6-5.1.7), compressive nonlinearity
(5.1.8, Formula 23, Table 2) and threshold in quiet (5.1.9, Table 3).
- Clause 6.2.2-6.2.7 ACF-based tonal/noise specific loudness (reusable by the
later tonality/roughness metrics).
- Clause 8 assembly: tonal/noise power average, average specific loudness,
time-dependent and single representative loudness value.
The front-end helpers are factored for reuse. Calibration verified: a 1 kHz
40 dB SPL sinusoid yields 0.996 sone_HMS (target 1.0). Adds a frozen
EcmaLoudness result with lazy .plot(), exports, and 12 tests.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 tonality (Sottek Hearing Model)
Implements the psychoacoustic tonality metric of ECMA-418-2:2025
(Clause 6.2.8-6.2.11) on top of the committed Sottek front-end and
ACF tonal/noise decomposition.
- tonality_ecma() -> EcmaTonality with the single value T (Formula 63),
average specific tonality T'(z) (Formula 53), tonal frequencies
f_ton,z(z) (Formula 55), time-dependent tonality T(l) (Formula 61)
and its frequency f_ton(l) (Formula 62); optional user band [f_low,f_high].
- Full Clause 6.2.3 band averaging with cross-block-size-group ACF
recomputation (loudness keeps its simplified, valid-for-loudness path;
boundary marker added there). Loudness 0.996 sone guard unchanged.
- _tonal_estimate now also returns the DFT-peak tonal frequency, with the
argmax restricted to the lower DFT half to avoid Nyquist-mirror aliasing.
- .plot() via _plotting.plot_ecma_tonality; exports in __init__.
- 13 conformance tests: 1 kHz/40 dB -> 1 tu_HMS calibration, pure tone
>> noise, f_ton tracks the tone, silence -> 0, band restriction.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 roughness (Sottek Hearing Model)
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic roughness metric
(Clause 7), the third Sottek Hearing Model output and new library
functionality. Reuses the Clause 5 auditory front-end from loudness_ecma
(ear filter, 53-band gammatone bank, specific basis loudness) and adds the
roughness-specific chain: start-only zero-padding and fixed 16384/4096
segmentation (5.1.2.2, 5.1.5.2, 7.1.1), the Hilbert envelope with x32
downsampling to 1500 Hz (7.1.2), the scaled envelope power spectrum
(7.1.3), two-step noise reduction (7.1.4), the four-stage spectral
weighting with quadratic-fit modulation-rate refinement, bias correction,
high/low modulation-rate weighting and fundamental-rate estimation
(7.1.5), interpolation to 50 Hz with the distribution-dependent nonlinear
transform, tabulated calibration c_R and asymmetric smoothing (7.1.7), and
the R'(z) / R(l50) / 90th-percentile aggregation (7.1.8-7.1.10).
Uses the standard's tabulated c_R = 0.0180685 (Formula 104), not
reverse-fit: the 1 kHz / 70 Hz / m=1 / 60 dB calibration signal yields
R = 1.0735 asper (near-but-not-exactly 1). OCR-ambiguous formulae
(66, 71, 78-81) cross-checked against the SQAT MATLAB reference; the
optional entropy weighting (7.1.6, needs RPM telemetry) is documented but
not implemented.
Adds roughness_ecma()/EcmaRoughness, a two-panel .plot(), exports, a
10-test TDD suite and plan/notes-ecma418-2-roughness.md. Full suite
752 passed / 12 skipped; loudness (0.996) and tonality (0.99993) oracles
unchanged (both modules untouched).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* test: pin ECMA-418-2 roughness calibration value and document clean-room variance
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-2 Moore-Glasberg loudness
Add the stationary Moore-Glasberg loudness model of ISO 532-2:2017 as a
third loudness method alongside the Zwicker (ISO 532-1) and Sottek
(ECMA-418-2) implementations. The full Clause 7 chain is implemented:
fixed outer/middle-ear transfer (Table 1), the level-dependent roex
auditory filter bank and excitation pattern on the ERB-number scale
(Formulae 1-6), the compressive specific-loudness transform (Formulae
7-9, Tables 2-4, C=0.0617 sone/Cam), binaural inhibition (Formulae
10-13) and integration to total loudness with the phon mapping of
Table 5.
Public API: loudness_moore_glasberg_from_spectrum (exact 5.2/5.4),
loudness_moore_glasberg_from_third_octave (29 bands, 5.5) and
loudness_moore_glasberg (signal wrapper), returning a frozen
MooreGlasbergLoudness with .plot() over the ERB-number scale.
Validated against the Annex B reference signals (tones, white/pink
noise, multi-tone complexes, tone-in-noise) to ~1-4%, well inside the
standard's 2.8 phon expanded uncertainty; the definitional anchor
(1 kHz/40 dB/free/binaural = 1.000 sone) holds end-to-end from the
tabulated constant. 50 new tests; full suite 802 passed / 12 skipped.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-3 Moore-Glasberg-Schlittenlacher time-varying loudness
Add loudness_moore_glasberg_time() implementing the ISO 532-3:2023 method:
a six-FFT multi-resolution running short-term spectrum (clause 7.3) feeding
the ISO 532-2 excitation and specific-loudness model on the 0.25 Cam grid
with the 532-3 constants (C=0.063, Tables 2/4, E_THRQ/E0=2.307), integrated
by asymmetric attack/release smoothing into short-term (clauses 7.6-7.8) and
long-term (clause 7.9) loudness traces, with peak long-term loudness,
percentiles and a lazy STL/LTL .plot().
Validated against the Annex C.1 tone table (1 kHz 10-80 dB, 3 kHz, 4 kHz,
100 Hz and monaural earphone) to within 0.4 phon, well inside the 2.8 phon
expanded uncertainty; the 1 kHz/40 dB anchor is exact at 1.000 sone. The
ISO 532-2 and ECMA-418-2 oracles are unchanged (831 passed / 12 skipped).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-3 per-ear LTL and Formula-14 kernel width (dichotic path)
Two transcription errors in the ISO 532-3:2023 dichotic (per-ear) binaural
path of the time-varying Moore-Glasberg-Schlittenlacher module:
1. Clause 7.9: the long-term loudness must be computed per ear by the
attack/release averager (Formulae 18-21) and then summed. The code ran a
single AGC on the binaural sum S'_L + S'_R; because the attack/release
branch is nonlinear, AGC(S'_L) + AGC(S'_R) != AGC(S'_L + S'_R) for dichotic
input. Keep two LTL states and sum the two long-term loudnesses. For
diotic/monaural the averager is linear in the (scaled) input so the result
is bit-for-bit identical to the old sum-then-AGC.
2. Formula (14)/(15): the smoothing weight is exp(-(B*Di)^2) with B = 0.08 and
Di in Cam. The code evaluated exp(-((0.08 * Di/0.1)^2)) = exp(-(0.8*Di)^2),
a Gaussian ~10x too narrow, and truncated the taps at +/-1.75 Cam. Remove
the spurious /0.1 and widen the kernel to the standard's +/-18 Cam (145 taps
on the 0.25-Cam grid). For diotic/monaural the L/R excitation ratio is 1
regardless of the kernel, so all oracles are unaffected.
No current validation signal exercises the dichotic path (Annex C signals are
diotic/monaural), so the 532-3 tone oracle and every other oracle remain
byte-identical; added focused dichotic tests plus a byte-identical anchor pin.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: numerical conformance report in CI PR comment
Reframe the PR comment into a persistent numerical conformance report:
each implemented standard -> normative expected value/range -> computed
result -> pass/fail with deviation, grouped by domain.
- scripts/conformance_report.py: registry of 21 checks across 17 standards
and 6 domains (filters/weightings, levels/dosimetry, psychoacoustics,
speech, intensity/power, room/building). Emits a headline summary, a
"Numerical validation - filters & weightings" section (per-architecture
IEC 61260-1 class margins + A/C/G weighting deviation vs the normative
curve) and one conformance table per domain. Deterministic, ~2.7 s.
- tests/reference_data.py: single source of truth for the shared normative
tables (IEC 61672-1 Table 3, ISO 7196 Table 2, ISO 717-1 Annex C R),
imported by both the tests and the report so they cannot drift.
- tests/test_conformance_report.py: smoke test asserting every registered
check passes and the Markdown is well-formed.
- comment_pr.py / workflow: conformance report leads the comment; the
test/coverage table is collapsed below. Retire benchmark_filters.py
(numerical content absorbed into the report).
make check green (740 passed, 12 skipped); ruff, mypy --strict, bandit clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: block-A review minors — docstring notes and tonality band-range
Four Minor findings from the plan-17 block-A psychoacoustics reviews. No
change to any validated numeric output; all five oracles stay byte-identical.
1. ISO 532-2 _source_levels: docstring note that clause 7.4 specifies a roex
weighting for X where the code uses a rectangular +/-ERB_n/2 window —
identical for isolated tones, immaterial for the Annex B broadband cases.
2. ISO 532-2 Formula-10 binaural smoothing kernel: the PDF (clause 8.1) states
"Di is changed in steps of 0,1" over -18..+18 Cam; the code confused the
0.1-Cam index offset with Di-in-Cam (the same /0.1 width bug just fixed in
532-3 Formula-14), making the Gaussian 10x too narrow and truncating to
+/-1.8 Cam. Widen to +/-18 Cam (361 taps) with Di = tap*_I_STEP. Provably
inert: all Annex B cases are diotic (ratio 1) or monaural (unit inhibition).
3. ECMA tonality _band_range: implement the exact Formulae 56-57 edge-midpoint
boundary conditions instead of a centre-frequency threshold; only affects
the optional user-band feature. Added a user-band edge test.
4. ECMA tonality reach: comment that the _CBF-1-band upper-edge term is a dead
defensive guard (N_B>0 only for low bands 0..24, so it never limits reach).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* chore: move conformance design doc out of repo root
* feat: clearer conformance report (value-vs-range) + block-A checks + audit fixes
Table 1 (filters): show, per architecture, the measured relative attenuation
at the binding band vs the class-1 limit it must clear (value + range, not
just a margin). Replace the "none" verdict (read as a failure) with a
"By design (ripple/soft rolloff)" label + footnote; Butterworth (default) and
Chebyshev-II are the only mask-compliant architectures. The measured value and
limit are re-derived with the public class_limits on the same designed SOS; a
smoke-test guard asserts the re-derived class-1 margin matches
verify_filter_class (single source of truth).
Table 2 (weightings): separate the informational max-deviation-from-nominal
(at a frequency extreme with wide, asymmetric tolerance) from the compliance
margin at the binding frequency, where the deviation, the +/- tolerance band
and the headroom are co-located so "value vs range" is unambiguous and in-spec.
Audit: verified all 21 checks (expected value/range vs the standard, real
library call, honest tolerance). One fix - ISO 226 anchored at 60 phon @ 2 kHz
-> 60 dB, a genuine Table B.1 value but coincidentally equal to the phon number
(reads as the trivial 1 kHz identity); re-anchored to the non-coincidental
60 phon @ 100 Hz -> 78.5 dB, sourced from reference_data. No library bug found.
Block-A psychoacoustics (+5, single-source expected in reference_data):
ECMA-418-2 loudness (1 kHz/40 dB -> 1 sone_HMS, c_N), tonality (-> 1 tu_HMS,
c_T), roughness (1 kHz/70 Hz/m=1/60 dB -> clean-room 1.0735 asper with a note
that the standard target is 1.0); ISO 532-2 (1 kHz/40 dB -> 1 sone, C=0.0617);
ISO 532-3 (steady 1 kHz/40 dB -> peak LTL 1 sone). 26/26 checks, 20 standards.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: versioned docs/CONFORMANCE.md with make target, CI staleness check and README link
Persist the numerical conformance report as a committed, versioned file so
every reader sees the library's per-standard validation (expected vs computed)
without running CI.
- docs/CONFORMANCE.md: committed report (26/26 checks, 6 domains, 20 standards)
with an auto-generated "do not hand-edit" header linking the standards docs.
- scripts/conformance_report.py: add a --file-header flag that emits that header
for the committed file (PR-comment body stays header-free); coarsen the
informational delta column to 3 decimals so BLAS/FFT-dependent sub-milli
residuals do not churn the file (status/computed columns keep regressions
visible).
- Makefile: `make conformance` regenerates the file; `make install-hooks`
installs the optional pre-commit hook.
- CI: new fast `conformance` job (runtime deps only) fails a PR if the committed
file is stale, telling the author to run `make conformance` and commit.
- hooks/pre-commit: opt-in convenience hook, scoped to src/scripts/reference
changes; CI is the enforcement.
- README + CONTRIBUTING: link and document the generated report.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced psychoacoustics guide, theory, API (EN)
Document the plan-17 block-A advanced psychoacoustics models: Moore-Glasberg
loudness (ISO 532-2/532-3), Sottek Hearing Model loudness, tonality and
roughness (ECMA-418-2:2025).
- Psychoacoustics guide: new "Advanced loudness & sound-quality models"
section with a model-comparison table, executed snippets, parameter tables,
res.plot() one-liners and figure-code collapsibles (docs + site twin,
code blocks byte-identical).
- Theory: excitation-pattern/specific-loudness chain, Sottek front-end,
ACF tonality and envelope-modulation roughness (GitHub-safe math).
- API reference: rows for the 5 functions (+ 2 MG spectral entry points) and
5 result dataclasses with their .plot().
- README, EN landing (standards 26 -> 29: ISO 532-2, ISO 532-3, ECMA-418-2),
CHANGELOG Unreleased/Added, llms regen.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced-psychoacoustics figures (loudness models, Sottek, tonality/roughness, time-varying)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de psicoacústica avanzada (Moore-Glasberg, Sottek, tonalidad, aspereza)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-2 signal-input path builds a narrowband spectrum (pure-tone loudness)
The loudness_moore_glasberg(x, fs) wrapper routed the signal through the
one-third-octave method, which smeared a pure tone across a whole 1/3-octave
band and used a coherent-gain window normalisation that over-counted power by
1.76 dB. A calibrated 1 kHz tone at 40 dB SPL therefore returned 1.327 sone
instead of the definitional anchor of 1.000 sone.
ISO 532-2 is spectrum-based and its exact input (clauses 5.2/5.4) is a set of
discrete sinusoidal components; the excitation pattern (Formula 5) is computed
per component, so a tone must enter as a single line. The wrapper now forms the
signal's narrowband (FFT) line spectrum with a power-preserving (Parseval)
window normalisation and feeds it to loudness_moore_glasberg_from_spectrum. A
1 kHz/40 dB tone now yields 1.0001 sone / 40 phon and multi-tone signals match
the exact spectrum path. The validated spectrum and third-octave paths are
byte-identical.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: correct MG signal-path mechanism text and presentation default
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 87 review round 1 — float-equality guards, 532-3 FFT window length, tonality short-signal, report/figure fixes
- roughness_ecma: replace three float == 0.0 checks with abs>eps guards
(SonarCloud gate); roughness oracle byte-identical (1.0735).
- loudness_moore_glasberg_time: per-window FFT length (>= window length) so
the 64 ms window is not truncated at 44.1/48 kHz (was ~0.74 dB low in the
20-80 Hz band). 32 kHz anchor byte-identical (n_max 1.0000044713237626).
- tonality_ecma: short signals fall back to averaging all blocks (matches
loudness_ecma), not the final block only; validated anchor unaffected.
- conformance_report: document deliberate 273.0 (speed of sound) vs 273.15
(C1/C2) split matching the library; complete bind_side docstring.
- generate_graphs: roughness annotation uses computed peak fmod; Sottek
metrics use tu_HMS/sone_HMS units; ES translations + 8 figures regenerated.
- hooks/pre-commit: drop set -e so a benign regen failure does not block the
commit (CI staleness check is the gate).
- test_iec_weighting_table3 docstring, Makefile .PHONY completion.
All 5 psychoacoustics oracles byte-identical; make check 868 passed / 12
skipped; ruff + mypy + bandit clean; conformance 26/26.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: localize decimal commas in Sottek-unit figure labels (underscore guard)
The `_` guard in `_translate_figure` skipped decimal-comma localization for
any label containing an underscore. The new Sottek/HMS unit tokens
(`sone_HMS`, `tu_HMS`, `sonios_HMS`) carry underscores, so Spanish figures
regressed to dots (`8.0`, `0.42`) instead of commas (`8,0`, `0,42`).
Drop the underscore guard: the decimal-comma regex only rewrites a bare
`digit.digit` not adjacent to more digits/dots, so unit identifiers and
version numbers (5.3.3) stay intact while genuine decimals get commas.
Mathtext is still skipped via the `$` guard. Regenerated the affected ES
figures (sottek_specific_loudness, tonality_roughness_demo).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Advanced psychoacoustics (ISO 532-2/3 Moore-Glasberg, ECMA-418-2 Sottek loudness/tonality/roughness) + numerical conformance report (#87)
* feat: ECMA-418-2 Sottek Hearing Model loudness and auditory front-end
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic loudness metric and
its shared auditory front-end (Sottek Hearing Model):
- Clause 5 front-end: outer/middle-ear cascade (5.1.3, Table 1), 53-band
gammatone-like auditory filter bank (5.1.4), band-dependent segmentation
(5.1.5, Table 4), rectification/RMS (5.1.6-5.1.7), compressive nonlinearity
(5.1.8, Formula 23, Table 2) and threshold in quiet (5.1.9, Table 3).
- Clause 6.2.2-6.2.7 ACF-based tonal/noise specific loudness (reusable by the
later tonality/roughness metrics).
- Clause 8 assembly: tonal/noise power average, average specific loudness,
time-dependent and single representative loudness value.
The front-end helpers are factored for reuse. Calibration verified: a 1 kHz
40 dB SPL sinusoid yields 0.996 sone_HMS (target 1.0). Adds a frozen
EcmaLoudness result with lazy .plot(), exports, and 12 tests.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 tonality (Sottek Hearing Model)
Implements the psychoacoustic tonality metric of ECMA-418-2:2025
(Clause 6.2.8-6.2.11) on top of the committed Sottek front-end and
ACF tonal/noise decomposition.
- tonality_ecma() -> EcmaTonality with the single value T (Formula 63),
average specific tonality T'(z) (Formula 53), tonal frequencies
f_ton,z(z) (Formula 55), time-dependent tonality T(l) (Formula 61)
and its frequency f_ton(l) (Formula 62); optional user band [f_low,f_high].
- Full Clause 6.2.3 band averaging with cross-block-size-group ACF
recomputation (loudness keeps its simplified, valid-for-loudness path;
boundary marker added there). Loudness 0.996 sone guard unchanged.
- _tonal_estimate now also returns the DFT-peak tonal frequency, with the
argmax restricted to the lower DFT half to avoid Nyquist-mirror aliasing.
- .plot() via _plotting.plot_ecma_tonality; exports in __init__.
- 13 conformance tests: 1 kHz/40 dB -> 1 tu_HMS calibration, pure tone
>> noise, f_ton tracks the tone, silence -> 0, band restriction.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 roughness (Sottek Hearing Model)
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic roughness metric
(Clause 7), the third Sottek Hearing Model output and new library
functionality. Reuses the Clause 5 auditory front-end from loudness_ecma
(ear filter, 53-band gammatone bank, specific basis loudness) and adds the
roughness-specific chain: start-only zero-padding and fixed 16384/4096
segmentation (5.1.2.2, 5.1.5.2, 7.1.1), the Hilbert envelope with x32
downsampling to 1500 Hz (7.1.2), the scaled envelope power spectrum
(7.1.3), two-step noise reduction (7.1.4), the four-stage spectral
weighting with quadratic-fit modulation-rate refinement, bias correction,
high/low modulation-rate weighting and fundamental-rate estimation
(7.1.5), interpolation to 50 Hz with the distribution-dependent nonlinear
transform, tabulated calibration c_R and asymmetric smoothing (7.1.7), and
the R'(z) / R(l50) / 90th-percentile aggregation (7.1.8-7.1.10).
Uses the standard's tabulated c_R = 0.0180685 (Formula 104), not
reverse-fit: the 1 kHz / 70 Hz / m=1 / 60 dB calibration signal yields
R = 1.0735 asper (near-but-not-exactly 1). OCR-ambiguous formulae
(66, 71, 78-81) cross-checked against the SQAT MATLAB reference; the
optional entropy weighting (7.1.6, needs RPM telemetry) is documented but
not implemented.
Adds roughness_ecma()/EcmaRoughness, a two-panel .plot(), exports, a
10-test TDD suite and plan/notes-ecma418-2-roughness.md. Full suite
752 passed / 12 skipped; loudness (0.996) and tonality (0.99993) oracles
unchanged (both modules untouched).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* test: pin ECMA-418-2 roughness calibration value and document clean-room variance
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-2 Moore-Glasberg loudness
Add the stationary Moore-Glasberg loudness model of ISO 532-2:2017 as a
third loudness method alongside the Zwicker (ISO 532-1) and Sottek
(ECMA-418-2) implementations. The full Clause 7 chain is implemented:
fixed outer/middle-ear transfer (Table 1), the level-dependent roex
auditory filter bank and excitation pattern on the ERB-number scale
(Formulae 1-6), the compressive specific-loudness transform (Formulae
7-9, Tables 2-4, C=0.0617 sone/Cam), binaural inhibition (Formulae
10-13) and integration to total loudness with the phon mapping of
Table 5.
Public API: loudness_moore_glasberg_from_spectrum (exact 5.2/5.4),
loudness_moore_glasberg_from_third_octave (29 bands, 5.5) and
loudness_moore_glasberg (signal wrapper), returning a frozen
MooreGlasbergLoudness with .plot() over the ERB-number scale.
Validated against the Annex B reference signals (tones, white/pink
noise, multi-tone complexes, tone-in-noise) to ~1-4%, well inside the
standard's 2.8 phon expanded uncertainty; the definitional anchor
(1 kHz/40 dB/free/binaural = 1.000 sone) holds end-to-end from the
tabulated constant. 50 new tests; full suite 802 passed / 12 skipped.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-3 Moore-Glasberg-Schlittenlacher time-varying loudness
Add loudness_moore_glasberg_time() implementing the ISO 532-3:2023 method:
a six-FFT multi-resolution running short-term spectrum (clause 7.3) feeding
the ISO 532-2 excitation and specific-loudness model on the 0.25 Cam grid
with the 532-3 constants (C=0.063, Tables 2/4, E_THRQ/E0=2.307), integrated
by asymmetric attack/release smoothing into short-term (clauses 7.6-7.8) and
long-term (clause 7.9) loudness traces, with peak long-term loudness,
percentiles and a lazy STL/LTL .plot().
Validated against the Annex C.1 tone table (1 kHz 10-80 dB, 3 kHz, 4 kHz,
100 Hz and monaural earphone) to within 0.4 phon, well inside the 2.8 phon
expanded uncertainty; the 1 kHz/40 dB anchor is exact at 1.000 sone. The
ISO 532-2 and ECMA-418-2 oracles are unchanged (831 passed / 12 skipped).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-3 per-ear LTL and Formula-14 kernel width (dichotic path)
Two transcription errors in the ISO 532-3:2023 dichotic (per-ear) binaural
path of the time-varying Moore-Glasberg-Schlittenlacher module:
1. Clause 7.9: the long-term loudness must be computed per ear by the
attack/release averager (Formulae 18-21) and then summed. The code ran a
single AGC on the binaural sum S'_L + S'_R; because the attack/release
branch is nonlinear, AGC(S'_L) + AGC(S'_R) != AGC(S'_L + S'_R) for dichotic
input. Keep two LTL states and sum the two long-term loudnesses. For
diotic/monaural the averager is linear in the (scaled) input so the result
is bit-for-bit identical to the old sum-then-AGC.
2. Formula (14)/(15): the smoothing weight is exp(-(B*Di)^2) with B = 0.08 and
Di in Cam. The code evaluated exp(-((0.08 * Di/0.1)^2)) = exp(-(0.8*Di)^2),
a Gaussian ~10x too narrow, and truncated the taps at +/-1.75 Cam. Remove
the spurious /0.1 and widen the kernel to the standard's +/-18 Cam (145 taps
on the 0.25-Cam grid). For diotic/monaural the L/R excitation ratio is 1
regardless of the kernel, so all oracles are unaffected.
No current validation signal exercises the dichotic path (Annex C signals are
diotic/monaural), so the 532-3 tone oracle and every other oracle remain
byte-identical; added focused dichotic tests plus a byte-identical anchor pin.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: numerical conformance report in CI PR comment
Reframe the PR comment into a persistent numerical conformance report:
each implemented standard -> normative expected value/range -> computed
result -> pass/fail with deviation, grouped by domain.
- scripts/conformance_report.py: registry of 21 checks across 17 standards
and 6 domains (filters/weightings, levels/dosimetry, psychoacoustics,
speech, intensity/power, room/building). Emits a headline summary, a
"Numerical validation - filters & weightings" section (per-architecture
IEC 61260-1 class margins + A/C/G weighting deviation vs the normative
curve) and one conformance table per domain. Deterministic, ~2.7 s.
- tests/reference_data.py: single source of truth for the shared normative
tables (IEC 61672-1 Table 3, ISO 7196 Table 2, ISO 717-1 Annex C R),
imported by both the tests and the report so they cannot drift.
- tests/test_conformance_report.py: smoke test asserting every registered
check passes and the Markdown is well-formed.
- comment_pr.py / workflow: conformance report leads the comment; the
test/coverage table is collapsed below. Retire benchmark_filters.py
(numerical content absorbed into the report).
make check green (740 passed, 12 skipped); ruff, mypy --strict, bandit clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: block-A review minors — docstring notes and tonality band-range
Four Minor findings from the plan-17 block-A psychoacoustics reviews. No
change to any validated numeric output; all five oracles stay byte-identical.
1. ISO 532-2 _source_levels: docstring note that clause 7.4 specifies a roex
weighting for X where the code uses a rectangular +/-ERB_n/2 window —
identical for isolated tones, immaterial for the Annex B broadband cases.
2. ISO 532-2 Formula-10 binaural smoothing kernel: the PDF (clause 8.1) states
"Di is changed in steps of 0,1" over -18..+18 Cam; the code confused the
0.1-Cam index offset with Di-in-Cam (the same /0.1 width bug just fixed in
532-3 Formula-14), making the Gaussian 10x too narrow and truncating to
+/-1.8 Cam. Widen to +/-18 Cam (361 taps) with Di = tap*_I_STEP. Provably
inert: all Annex B cases are diotic (ratio 1) or monaural (unit inhibition).
3. ECMA tonality _band_range: implement the exact Formulae 56-57 edge-midpoint
boundary conditions instead of a centre-frequency threshold; only affects
the optional user-band feature. Added a user-band edge test.
4. ECMA tonality reach: comment that the _CBF-1-band upper-edge term is a dead
defensive guard (N_B>0 only for low bands 0..24, so it never limits reach).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* chore: move conformance design doc out of repo root
* feat: clearer conformance report (value-vs-range) + block-A checks + audit fixes
Table 1 (filters): show, per architecture, the measured relative attenuation
at the binding band vs the class-1 limit it must clear (value + range, not
just a margin). Replace the "none" verdict (read as a failure) with a
"By design (ripple/soft rolloff)" label + footnote; Butterworth (default) and
Chebyshev-II are the only mask-compliant architectures. The measured value and
limit are re-derived with the public class_limits on the same designed SOS; a
smoke-test guard asserts the re-derived class-1 margin matches
verify_filter_class (single source of truth).
Table 2 (weightings): separate the informational max-deviation-from-nominal
(at a frequency extreme with wide, asymmetric tolerance) from the compliance
margin at the binding frequency, where the deviation, the +/- tolerance band
and the headroom are co-located so "value vs range" is unambiguous and in-spec.
Audit: verified all 21 checks (expected value/range vs the standard, real
library call, honest tolerance). One fix - ISO 226 anchored at 60 phon @ 2 kHz
-> 60 dB, a genuine Table B.1 value but coincidentally equal to the phon number
(reads as the trivial 1 kHz identity); re-anchored to the non-coincidental
60 phon @ 100 Hz -> 78.5 dB, sourced from reference_data. No library bug found.
Block-A psychoacoustics (+5, single-source expected in reference_data):
ECMA-418-2 loudness (1 kHz/40 dB -> 1 sone_HMS, c_N), tonality (-> 1 tu_HMS,
c_T), roughness (1 kHz/70 Hz/m=1/60 dB -> clean-room 1.0735 asper with a note
that the standard target is 1.0); ISO 532-2 (1 kHz/40 dB -> 1 sone, C=0.0617);
ISO 532-3 (steady 1 kHz/40 dB -> peak LTL 1 sone). 26/26 checks, 20 standards.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: versioned docs/CONFORMANCE.md with make target, CI staleness check and README link
Persist the numerical conformance report as a committed, versioned file so
every reader sees the library's per-standard validation (expected vs computed)
without running CI.
- docs/CONFORMANCE.md: committed report (26/26 checks, 6 domains, 20 standards)
with an auto-generated "do not hand-edit" header linking the standards docs.
- scripts/conformance_report.py: add a --file-header flag that emits that header
for the committed file (PR-comment body stays header-free); coarsen the
informational delta column to 3 decimals so BLAS/FFT-dependent sub-milli
residuals do not churn the file (status/computed columns keep regressions
visible).
- Makefile: `make conformance` regenerates the file; `make install-hooks`
installs the optional pre-commit hook.
- CI: new fast `conformance` job (runtime deps only) fails a PR if the committed
file is stale, telling the author to run `make conformance` and commit.
- hooks/pre-commit: opt-in convenience hook, scoped to src/scripts/reference
changes; CI is the enforcement.
- README + CONTRIBUTING: link and document the generated report.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced psychoacoustics guide, theory, API (EN)
Document the plan-17 block-A advanced psychoacoustics models: Moore-Glasberg
loudness (ISO 532-2/532-3), Sottek Hearing Model loudness, tonality and
roughness (ECMA-418-2:2025).
- Psychoacoustics guide: new "Advanced loudness & sound-quality models"
section with a model-comparison table, executed snippets, parameter tables,
res.plot() one-liners and figure-code collapsibles (docs + site twin,
code blocks byte-identical).
- Theory: excitation-pattern/specific-loudness chain, Sottek front-end,
ACF tonality and envelope-modulation roughness (GitHub-safe math).
- API reference: rows for the 5 functions (+ 2 MG spectral entry points) and
5 result dataclasses with their .plot().
- README, EN landing (standards 26 -> 29: ISO 532-2, ISO 532-3, ECMA-418-2),
CHANGELOG Unreleased/Added, llms regen.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced-psychoacoustics figures (loudness models, Sottek, tonality/roughness, time-varying)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de psicoacústica avanzada (Moore-Glasberg, Sottek, tonalidad, aspereza)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-2 signal-input path builds a narrowband spectrum (pure-tone loudness)
The loudness_moore_glasberg(x, fs) wrapper routed the signal through the
one-third-octave method, which smeared a pure tone across a whole 1/3-octave
band and used a coherent-gain window normalisation that over-counted power by
1.76 dB. A calibrated 1 kHz tone at 40 dB SPL therefore returned 1.327 sone
instead of the definitional anchor of 1.000 sone.
ISO 532-2 is spectrum-based and its exact input (clauses 5.2/5.4) is a set of
discrete sinusoidal components; the excitation pattern (Formula 5) is computed
per component, so a tone must enter as a single line. The wrapper now forms the
signal's narrowband (FFT) line spectrum with a power-preserving (Parseval)
window normalisation and feeds it to loudness_moore_glasberg_from_spectrum. A
1 kHz/40 dB tone now yields 1.0001 sone / 40 phon and multi-tone signals match
the exact spectrum path. The validated spectrum and third-octave paths are
byte-identical.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: correct MG signal-path mechanism text and presentation default
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 87 review round 1 — float-equality guards, 532-3 FFT window length, tonality short-signal, report/figure fixes
- roughness_ecma: replace three float == 0.0 checks with abs>eps guards
(SonarCloud gate); roughness oracle byte-identical (1.0735).
- loudness_moore_glasberg_time: per-window FFT length (>= window length) so
the 64 ms window is not truncated at 44.1/48 kHz (was ~0.74 dB low in the
20-80 Hz band). 32 kHz anchor byte-identical (n_max 1.0000044713237626).
- tonality_ecma: short signals fall back to averaging all blocks (matches
loudness_ecma), not the final block only; validated anchor unaffected.
- conformance_report: document deliberate 273.0 (speed of sound) vs 273.15
(C1/C2) split matching the library; complete bind_side docstring.
- generate_graphs: roughness annotation uses computed peak fmod; Sottek
metrics use tu_HMS/sone_HMS units; ES translations + 8 figures regenerated.
- hooks/pre-commit: drop set -e so a benign regen failure does not block the
commit (CI staleness check is the gate).
- test_iec_weighting_table3 docstring, Makefile .PHONY completion.
All 5 psychoacoustics oracles byte-identical; make check 868 passed / 12
skipped; ruff + mypy + bandit clean; conformance 26/26.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: localize decimal commas in Sottek-unit figure labels (underscore guard)
The `_` guard in `_translate_figure` skipped decimal-comma localization for
any label containing an underscore. The new Sottek/HMS unit tokens
(`sone_HMS`, `tu_HMS`, `sonios_HMS`) carry underscores, so Spanish figures
regressed to dots (`8.0`, `0.42`) instead of commas (`8,0`, `0,42`).
Drop the underscore guard: the decimal-comma regex only rewrites a bare
`digit.digit` not adjacent to more digits/dots, so unit identifiers and
version numbers (5.3.3) stay intact while genuine decimals get commas.
Mathtext is still skipped via the `$` guard. Regenerated the affected ES
figures (sottek_specific_loudness, tonality_roughness_demo).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
NT ACOU 112: impulsive-sound prominence and LAeq adjustment (#105)
* feat: NT ACOU 112 impulsive-sound prominence and LAeq adjustment
Add the Nordtest NT ACOU 112:2002 method for the prominence of
impulsive sounds: the predicted prominence P = 3 lg(OR) + 2 lg(LD)
from each impulse's onset rate and level difference (clause 7,
Formula 1), the graduated adjustment KI = 1.8 (P - 5) dB for P > 5
(clause 8, Formula 2), and the rating level over a reference time
interval (clause 8, Note 1).
New module phonometry.ntacou112 with predicted_prominence,
impulse_adjustment, impulse_prominence (ImpulseProminence result with
.plot()) and rating_level. Conformance report gains an impulsive-sound
domain (2 checks). Adds the impulse-prominence guide (repo + EN/ES
site) with figure and flow diagram.
* refactor: address review feedback on NT ACOU 112
- impulse_prominence: ravel inputs so multi-dimensional arrays flatten
to a 1-D impulse sequence (Gemini)
- figure: use a distinct neutral for the threshold line instead of the
grid color, matching plot_impulse_prominence (Gemini)
- tests: use the shared NTACOU_PROMINENCE / NTACOU_ADJUSTMENT_P10
reference constants instead of hardcoded literals (CodeRabbit)
* refactor: second-pass review polish for NT ACOU 112
- figure: give the impulse markers a distinct light blue (#aec7e8),
matching plot_impulse_prominence, so they read apart from the neutral
threshold line (CodeRabbit)
- tests: split the chained magnitude/approx assertion into two clearer
assertions (CodeRabbit)
Outdoor sound propagation and occupational noise exposure (ISO 9613-1, ISO 9613-2, ISO 9612) (#89)
* feat: ISO 9613-1 atmospheric sound absorption
Full alpha(f, T, RH, p) model per ISO 9613-1:1993 clauses 6.2-6.4:
oxygen/nitrogen relaxation frequencies, water-vapour molar
concentration from relative humidity, classical + rotational +
vibrational terms. Validated against a 13-point Table 1 grid
(worst deviation 0.39 %, rounding-limited, vs the standard's
stated +/-10 % accuracy) with the exact midband convention of
Eq. (6) Note 5. air_attenuation_m() feeds the ISO 354 m parameter
directly, closing the user-supplied gap. Two conformance checks
added (34/34).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: C1 review nits — test name and comment direction
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* test: extend ISO 9613-1 Table 1 oracle against the full standard text
The full 37-page ISO 9613-1:1993 text (now in hand) confirms every
implemented constant of Eqs. (3)-(6), the Annex B psychrometric
conversion and the clause 4.2 reference conditions exactly as coded.
Oracle extended from 13 to 23 Table 1 points, adding the 20-50 degC
sub-tables 1(i)-1(p) unavailable in the interim source. Worst
deviation unchanged at 0.385 % (rounding-limited).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9613-2 outdoor sound propagation
Implement the ISO 9613-2:1996 general method for octave-band outdoor sound
attenuation: geometrical divergence (Eq. 7), atmospheric absorption (Eq. 8,
via ISO 9613-1 air_absorption), ground effect (general per-region method of
7.3.1 with the Table 3 a'/b'/c'/d' functions, Eq. 9, plus the alternative
method of 7.3.2, Eq. 10, and the DOmega index Eq. 11), and screening (Eq. 12-18
with the C2/C3 factors, single/double diffraction, Kmet and the 20/25 dB caps).
Adds the Cmet meteorological correction (Eq. 21/22) and the LfT(DW)=Lw+Dc-A
receiver-level composition (Eq. 3/6). Frozen dataclass result exposes the
per-term breakdown (Adiv/Aatm/Agr/Abar). New module, tests and exports.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9612 occupational noise exposure strategies and uncertainty
Add phonometry.occupational_exposure implementing the three ISO 9612:2009
measurement strategies and the normative Annex C uncertainty budget:
- task_based_exposure (Clause 9): per-task energy average (Eq 7), task
contributions (Eq 8), daily level (Eq 9/10); uncertainty Eq C.3 with
sampling u1a (Eq C.6), duration u1b (Eq C.7) and sensitivity coefficients
c1a (Eq C.4) / c1b (Eq C.5).
- job_based_exposure (Clause 10): effective-day energy average (Eq 11),
daily level (Eq 12); Table 1 minimum-duration check; uncertainty Eq C.9.
- full_day_exposure (Clause 11): whole-day averaging (Eq 11/13) with the
Clause 11.3 three-measurement / 3 dB spread rule.
- Table C.4 (job/full-day c1*u1, bilinear interpolation), Table C.5 (u2),
u3 = 1.0 dB, coverage factor k = 1.65 (one-sided 95 %).
Frozen dataclasses (Task, TaskContribution, ExposureResult), ExposureWarning
advisories for the sampling rules, and exports appended to __init__.
Tests reproduce the Annex D (task), E (job) and F (full-day) worked examples
to the standard's precision, plus closed-form oracles (single 85 dB/8 h task
= 85; equal-level tasks; -3.01 dB per halved duration; k = 1.65) and the
Table C.4 / Table 1 lookups. make check green: 1167 passed, 12 skipped.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 9612 review corrections — Table C.4 digits and Eq C.7 denominator
Two Table C.4 cells verified against the page image at 200 dpi and
corrected (N=6/u1=3.5: 3.4 -> 3.3; N=20/u1=2.0: 0.6 -> 0.5), the test
that echoed the wrong N=6 cell now pins the printed value plus the
corrected N=20 cell. The duration_samples branch of Eq C.7 divides by
J(J-1) like Eq C.6 (standard error of the mean), with a regression test
pinning u1b = 1.0 h for samples (4, 6) h. Module docstring now states
the Lp,Cpeak uncertainty scope cut (Table C.5, NOTE 1).
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* test: ISO 9613-2 and ISO 9612 conformance checks
Add a new "Outdoor propagation & occupational exposure" conformance domain
with seven closed-form / worked-example checks backed by shared constants in
tests/reference_data.py:
- ISO 9613-2:1996 Eq. (7) geometrical divergence Adiv = 51 dB at 100 m.
- ISO 9613-2:1996 Table 3 ground limit b'(0) = 10,1 -> Agr(250 Hz) = 17,2 dB
(porous ground, hs = hr = 0, dp -> inf).
- ISO 9613-2:1996 clause 7.4 single/double diffraction 20/25 dB caps.
- ISO 9612:2009 Annex D/E/F daily exposure LEX,8h and expanded uncertainty U
(84,3/2,7, 88,1/3,8, 90,1/3,4 dB), the Task objects rebuilt from the shared
input table.
Registry grows 34 -> 41 checks across 8 domains; docs/CONFORMANCE.md
regenerated via make conformance. Registration and reference-data consistency
tests pin the constants to the published values.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* docs: figures for outdoor propagation and occupational exposure
Add four documentation figures (each EN/ES x light/dark) plus a setup diagram:
- air_absorption_alpha: ISO 9613-1 alpha(f) over frequency at four
temperature/humidity combinations (relaxation roll-off, f^2 growth).
- outdoor_attenuation_breakdown: ISO 9613-2 per-term stacked bar (Adiv, Aatm,
Agr, Abar) with the total A, for a 200 m path over porous ground with a 4 m
barrier.
- exposure_uncertainty: ISO 9612 Annex D task contributions with the daily
LEX,8h and the one-sided 95 % upper limit LEX,8h + U.
- diagram_outdoor_geometry: source-barrier-receiver geometry (dss/dsr/z, the
blocked direct ray and the diffracted ray over the top edge).
Spanish translations added to the generate_graphs / generate_diagrams
dictionaries (decimal commas, UNE terminology). Every image visually audited.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* docs: ISO 9613-1/2 outdoor propagation guide and ISO 9612 exposure (EN)
Add the didactic Outdoor Sound Propagation guide (docs/outdoor-propagation.md)
covering ISO 9613-1 atmospheric absorption and the ISO 9613-2 general method
(divergence, atmospheric, ground and barrier terms, the OutdoorAttenuation
breakdown, the alternative 7.3.2 ground method, Cmet), with executed snippets
and honest scope notes (Amisc/reflections not implemented, 7.3.2 not
auto-wired, per-band Cmet a convenience). Extend the Levels guide with the
ISO 9612 occupational-exposure section (task/job/full-day strategies, the
C.6 I(I-1) vs C.12 N-1 asymmetry, Table C.4, k = 1,65, LEX,8h + U upper limit,
Lp,Cpeak uncertainty out of scope), reproducing the Annex D/E/F worked
examples.
Add the matching theory sections, API-reference rows for every new public
name, README feature list + docs table, docs index and CHANGELOG entries.
llms-full.txt / llms.txt regenerated (make llms) with the new page wired into
gen_llms.py. GitHub-safe math throughout; every snippet executed with pasted
outputs.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* docs: outdoor propagation and exposure site twins (EN + ES)
Add the English and Spanish Starlight twins of the new content:
- guides/outdoor-propagation.md (EN + ES) — code-identical snippets to the
docs page; Spanish prose uses UNE terminology ("ponderación temporal"),
decimal commas outside math and code, and the _es figure variants.
- The ISO 9612 section added to guides/levels.md (EN + ES).
- reference/theory.md and reference/api.md rows (EN + ES).
- Landing cards and standards list bumped 34 -> 37 (index.mdx EN + ES);
guides/outdoor-propagation added to the sidebar.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* fix: final branch review — API rows, precision phrasing and ES links
Adds the five missing public-symbol rows (TaskContribution,
table_c4_contribution, minimum_cumulative_duration_hours, COVERAGE_FACTOR,
INSTRUMENT_U2) to the three API references, states the ISO 9613-2 default
atmosphere (20 °C / 70 %, a Table 2 reference atmosphere) in the API rows
and guides, and softens "digit-for-digit" to "the standard's printed
precision" wherever it covered Annex E, whose final level differs only by
the standard's own pre-rounding. ES pages now use the /es/ link prefix
consistently. Code nits from the review: dead arg > 1 guard removed
(z > 0 implies arg >= 3), _with_advisory uses dataclasses.replace, and the
float-keyed _PRIME_BY_BAND gets a safety comment. llms files regenerated.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* fix: validate propagation/exposure inputs and docs lint (PR review)
Addresses the PR #89 bot review:
- Barrier.__post_init__ rejects negative edge distances and a zero
edge_separation (which previously divided by zero in the C3 double-
diffraction factor); Task.__post_init__ rejects empty samples and a
non-positive duration.
- ground_attenuation and barrier_attenuation reject non-positive
frequencies (a zero frequency gave an infinite wavelength / division by
zero); ground_attenuation also checks a positive distance and
non-negative heights. job_based_exposure rejects a non-positive
sample_duration_hours.
- Rewrite the ISO 9613-2 7.3.2 closed form in the guides and API tables so
Markdownlint no longer parses `(...)[...]` as a reversed link (MD011).
- Regression tests for every new guard; docstrings note the new raises.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
Room and building acoustics: ISO 18233, ISO 3382-1/2/3, ISO 16283-1 + ISO 717-1 (#81)
* feat: ISO 18233 sweep/MLS impulse-response acquisition
Add src/phonometry/room_ir.py implementing the ISO 18233:2006
deterministic-excitation IR front end: exponential sine sweep with exact
analytic phase (Annex B), linear spectral-division deconvolution with a
Farina inverse-filter option for harmonic separation (B.5), and
maximum-length-sequence generation (LFSR, orders 2-20) with circular
cross-correlation recovery (Annex A).
Validated against closed forms: known IIR bandpass recovered within 0.1 dB
in band (sweep and MLS), ideal chain to a band-limited delta, +3 dB SNR per
sweep-duration doubling (B.6), and all MLS orders verified as true
maximum-length sequences (autocorrelation L / -1).
433 passed, 12 skipped; ruff, mypy --strict and bandit clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 3382 room acoustic parameters from impulse responses
Add src/phonometry/room_acoustics.py implementing ISO 3382-1:2009 and
ISO 3382-2:2008 analysis of measured impulse responses:
- decay_curve(): Schroeder backward integration of the squared IR
(5.3.3, Eq. 1) with background-noise truncation at the noise/slope
crossing and exponential tail compensation (Eq. 3), broadband or per
IEC 61260 fractional-octave band.
- room_parameters(): per-band EDT (0/-10 dB, A.2.2), T20 (-5/-25 dB)
and T30 (-5/-35 dB) by least-squares fits (ISO 3382-2 Annex C),
clarity C50/C80 (Eq. A.10), definition D50 (Eq. A.11) and centre
time Ts (Eq. A.13), octave bands 125 Hz-4 kHz by default with a
one-third-octave option and a broadband mode.
- Validity flags per the 5.3.3 dynamic-range criterion (noise at least
evaluation range + 15 dB below the IR maximum: 25/35/45 dB) plus the
Annex B curvature indicator C = 100*(T30/T20 - 1).
Tests validate against closed forms for exponential decays (EDT = T20 =
T30 = T within 1 %; C_te = 10*lg(exp(13.8155*te/T) - 1); D50 =
1 - exp(-0.6908/T); Ts = T/13.8155), the exact C50/D50 relation
(Eq. A.12), double-slope decays (EDT < T20 < T30) and noise-driven
validity-flag behaviour, with tolerances far below the Table A.1 JNDs.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* chore: move figure/diagram generators to scripts/
generate_graphs.py and generate_diagrams.py join benchmark_filters.py
and gen_llms.py in scripts/, where dev tooling already lives. Updated
the Makefile graphs target, the sys.path bootstrap in the graph guard
tests and in generate_graphs.py itself (src/ is now one level up), the
CONTRIBUTING instructions and the theme-images.css pointer. Verified:
module loads from the new location and regenerating the SVG diagrams
produces byte-identical output.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 3382-3 open-plan office spatial metrics
Add open_plan_metrics() computing the ISO 3382-3:2012 single-number
quantities from a line of workstation measurements: spatial decay rate
D2,S and nominal speech level Lp,A,S,4m (Clause 6.2, Eq.5; 2-16 m
positions on a log-distance regression) and distraction/privacy
distances rD/rP (Clause 6.3; STI-vs-distance linear regression crossing
0,50 and 0,20). Returns a frozen OpenPlanResult; validates the minimum
of four positions (5.2.2) and equal array lengths.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 16283-1 field insulation and ISO 717-1 weighted ratings
Add phonometry.insulation implementing field airborne sound insulation
(ISO 16283-1:2014) and single-number weighted ratings with C/Ctr
(ISO 717-1):
- airborne_insulation: per-band level difference D (Formula (1)),
standardized level difference DnT = D + 10 lg(T/T0) (Formula (2)) and
apparent sound reduction index R' = D + 10 lg(S/A), A = 0,16 V/T
(Formula (4)/(5)); energy-averages microphone positions (Formula (9)).
- weighted_rating: reference-curve shifting method (Clause 4.4, Table 3)
with the 32,0/10,0 dB unfavourable-deviation bound, plus C/Ctr from the
Table 4 spectra (Clause 4.5). Reference values, spectra and method are
identical in the 2013 and 2020 editions.
- energy_average_level: ISO 16283-1 Formula (9) helper.
Verified against the ISO 717-1 Annex C worked example (Rw(C;Ctr) =
30(-2;-3), unfavourable sum 31,8 dB) and exact-bound / tipping edge
cases. 20 new tests; make check green (484 passed, 12 skipped).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: room & building acoustics guide, theory, API and landing (EN)
New "Room & building acoustics" guide (docs/ + Starlight twin) covering the
full measurement chain: ISO 18233 swept-sine/MLS impulse-response acquisition,
ISO 3382-1/2 decay analysis and room parameters (EDT/T20/T30/C50/C80/D50/Ts),
ISO 3382-3 open-plan speech metrics, and ISO 16283-1 / ISO 717-1 field
insulation with weighted ratings. Adds the matching theory section (Schroeder
integration, regression windows, C/D/Ts, D2,S, DnT/R', reference-curve method),
API-reference rows for all new public names, sidebar entry, README highlight +
docs row, EN landing card, CHANGELOG entries, docs index row, and regenerated
llms-full.txt. All snippets validated; site build and make check green.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: Schroeder, ISO 717-1 rating and insulation-setup figures
Add three room/building-acoustics documentation figure sets (each in
en/es x light/dark):
- schroeder_decay: synthetic IR, Schroeder backward integration with
EDT/T20/T30 regressions and evaluation ranges (ISO 3382); annotated
values match room_parameters.
- insulation_rating: ISO 717-1 Annex C example with shifted reference
curve, unfavourable-deviation shading and Rw read at 500 Hz; Rw, C,
Ctr and the unfavourable sum come from weighted_rating.
- diagram_insulation_setup: ISO 16283-1 airborne setup plan view with
the normative minimum distances (clauses 7.6 and 7.2.2).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de acústica de salas y edificación
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: terminología UNE — ponderación temporal en lugar de balística (ES)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: markdown/README parity with the site + llms guide coverage
Scrub GitHub-unsafe display math (\, thin-spaces and escaped \{ \}) from
docs/intensity.md and docs/psychoacoustics.md — the only two guides that
missed the GitHub-safe math conversion of PRs #78/#79 — matching the safe
forms already used in docs/theory.md. Align calibration's sensitivity
symbol/formula to the site ($S$, single-line \qquad).
Extend scripts/gen_llms.py PAGES with the three omitted guides
(psychoacoustics, intensity, room-acoustics) and regenerate llms.txt /
llms-full.txt so AI-facing artifacts cover all 14 docs pages.
The guides/references were otherwise already in parity (didactic code
comments, theory sections, tables, and figures from PRs #77/#80 all
present); docs/README index and README highlights/table verified complete.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: final-review findings — open_plan validation, limits rename, Farina caveat, MLS averaging test
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: ISO 18233 measurement-chain diagram and guide fixes
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: fix sweep_signal cross-reference in Farina caveat
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 81 review round 1 — inverse-filter guards, truncation threshold, IR input validation, docs pipeline wording
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: blank lines around CONTRIBUTING fence (MD031)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Outdoor sound propagation and occupational noise exposure (ISO 9613-1, ISO 9613-2, ISO 9612) (#89)
* feat: ISO 9613-1 atmospheric sound absorption
Full alpha(f, T, RH, p) model per ISO 9613-1:1993 clauses 6.2-6.4:
oxygen/nitrogen relaxation frequencies, water-vapour molar
concentration from relative humidity, classical + rotational +
vibrational terms. Validated against a 13-point Table 1 grid
(worst deviation 0.39 %, rounding-limited, vs the standard's
stated +/-10 % accuracy) with the exact midband convention of
Eq. (6) Note 5. air_attenuation_m() feeds the ISO 354 m parameter
directly, closing the user-supplied gap. Two conformance checks
added (34/34).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: C1 review nits — test name and comment direction
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* test: extend ISO 9613-1 Table 1 oracle against the full standard text
The full 37-page ISO 9613-1:1993 text (now in hand) confirms every
implemented constant of Eqs. (3)-(6), the Annex B psychrometric
conversion and the clause 4.2 reference conditions exactly as coded.
Oracle extended from 13 to 23 Table 1 points, adding the 20-50 degC
sub-tables 1(i)-1(p) unavailable in the interim source. Worst
deviation unchanged at 0.385 % (rounding-limited).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9613-2 outdoor sound propagation
Implement the ISO 9613-2:1996 general method for octave-band outdoor sound
attenuation: geometrical divergence (Eq. 7), atmospheric absorption (Eq. 8,
via ISO 9613-1 air_absorption), ground effect (general per-region method of
7.3.1 with the Table 3 a'/b'/c'/d' functions, Eq. 9, plus the alternative
method of 7.3.2, Eq. 10, and the DOmega index Eq. 11), and screening (Eq. 12-18
with the C2/C3 factors, single/double diffraction, Kmet and the 20/25 dB caps).
Adds the Cmet meteorological correction (Eq. 21/22) and the LfT(DW)=Lw+Dc-A
receiver-level composition (Eq. 3/6). Frozen dataclass result exposes the
per-term breakdown (Adiv/Aatm/Agr/Abar). New module, tests and exports.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9612 occupational noise exposure strategies and uncertainty
Add phonometry.occupational_exposure implementing the three ISO 9612:2009
measurement strategies and the normative Annex C uncertainty budget:
- task_based_exposure (Clause 9): per-task energy average (Eq 7), task
contributions (Eq 8), daily level (Eq 9/10); uncertainty Eq C.3 with
sampling u1a (Eq C.6), duration u1b (Eq C.7) and sensitivity coefficients
c1a (Eq C.4) / c1b (Eq C.5).
- job_based_exposure (Clause 10): effective-day energy average (Eq 11),
daily level (Eq 12); Table 1 minimum-duration check; uncertainty Eq C.9.
- full_day_exposure (Clause 11): whole-day averaging (Eq 11/13) with the
Clause 11.3 three-measurement / 3 dB spread rule.
- Table C.4 (job/full-day c1*u1, bilinear interpolation), Table C.5 (u2),
u3 = 1.0 dB, coverage factor k = 1.65 (one-sided 95 %).
Frozen dataclasses (Task, TaskContribution, ExposureResult), ExposureWarning
advisories for the sampling rules, and exports appended to __init__.
Tests reproduce the Annex D (task), E (job) and F (full-day) worked examples
to the standard's precision, plus closed-form oracles (single 85 dB/8 h task
= 85; equal-level tasks; -3.01 dB per halved duration; k = 1.65) and the
Table C.4 / Table 1 lookups. make check green: 1167 passed, 12 skipped.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 9612 review corrections — Table C.4 digits and Eq C.7 denominator
Two Table C.4 cells verified against the page image at 200 dpi and
corrected (N=6/u1=3.5: 3.4 -> 3.3; N=20/u1=2.0: 0.6 -> 0.5), the test
that echoed the wrong N=6 cell now pins the printed value plus the
corrected N=20 cell. The duration_samples branch of Eq C.7 divides by
J(J-1) like Eq C.6 (standard error of the mean), with a regression test
pinning u1b = 1.0 h for samples (4, 6) h. Module docstring now states
the Lp,Cpeak uncertainty scope cut (Table C.5, NOTE 1).
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* test: ISO 9613-2 and ISO 9612 conformance checks
Add a new "Outdoor propagation & occupational exposure" conformance domain
with seven closed-form / worked-example checks backed by shared constants in
tests/reference_data.py:
- ISO 9613-2:1996 Eq. (7) geometrical divergence Adiv = 51 dB at 100 m.
- ISO 9613-2:1996 Table 3 ground limit b'(0) = 10,1 -> Agr(250 Hz) = 17,2 dB
(porous ground, hs = hr = 0, dp -> inf).
- ISO 9613-2:1996 clause 7.4 single/double diffraction 20/25 dB caps.
- ISO 9612:2009 Annex D/E/F daily exposure LEX,8h and expanded uncertainty U
(84,3/2,7, 88,1/3,8, 90,1/3,4 dB), the Task objects rebuilt from the shared
input table.
Registry grows 34 -> 41 checks across 8 domains; docs/CONFORMANCE.md
regenerated via make conformance. Registration and reference-data consistency
tests pin the constants to the published values.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* docs: figures for outdoor propagation and occupational exposure
Add four documentation figures (each EN/ES x light/dark) plus a setup diagram:
- air_absorption_alpha: ISO 9613-1 alpha(f) over frequency at four
temperature/humidity combinations (relaxation roll-off, f^2 growth).
- outdoor_attenuation_breakdown: ISO 9613-2 per-term stacked bar (Adiv, Aatm,
Agr, Abar) with the total A, for a 200 m path over porous ground with a 4 m
barrier.
- exposure_uncertainty: ISO 9612 Annex D task contributions with the daily
LEX,8h and the one-sided 95 % upper limit LEX,8h + U.
- diagram_outdoor_geometry: source-barrier-receiver geometry (dss/dsr/z, the
blocked direct ray and the diffracted ray over the top edge).
Spanish translations added to the generate_graphs / generate_diagrams
dictionaries (decimal commas, UNE terminology). Every image visually audited.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* docs: ISO 9613-1/2 outdoor propagation guide and ISO 9612 exposure (EN)
Add the didactic Outdoor Sound Propagation guide (docs/outdoor-propagation.md)
covering ISO 9613-1 atmospheric absorption and the ISO 9613-2 general method
(divergence, atmospheric, ground and barrier terms, the OutdoorAttenuation
breakdown, the alternative 7.3.2 ground method, Cmet), with executed snippets
and honest scope notes (Amisc/reflections not implemented, 7.3.2 not
auto-wired, per-band Cmet a convenience). Extend the Levels guide with the
ISO 9612 occupational-exposure section (task/job/full-day strategies, the
C.6 I(I-1) vs C.12 N-1 asymmetry, Table C.4, k = 1,65, LEX,8h + U upper limit,
Lp,Cpeak uncertainty out of scope), reproducing the Annex D/E/F worked
examples.
Add the matching theory sections, API-reference rows for every new public
name, README feature list + docs table, docs index and CHANGELOG entries.
llms-full.txt / llms.txt regenerated (make llms) with the new page wired into
gen_llms.py. GitHub-safe math throughout; every snippet executed with pasted
outputs.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* docs: outdoor propagation and exposure site twins (EN + ES)
Add the English and Spanish Starlight twins of the new content:
- guides/outdoor-propagation.md (EN + ES) — code-identical snippets to the
docs page; Spanish prose uses UNE terminology ("ponderación temporal"),
decimal commas outside math and code, and the _es figure variants.
- The ISO 9612 section added to guides/levels.md (EN + ES).
- reference/theory.md and reference/api.md rows (EN + ES).
- Landing cards and standards list bumped 34 -> 37 (index.mdx EN + ES);
guides/outdoor-propagation added to the sidebar.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* fix: final branch review — API rows, precision phrasing and ES links
Adds the five missing public-symbol rows (TaskContribution,
table_c4_contribution, minimum_cumulative_duration_hours, COVERAGE_FACTOR,
INSTRUMENT_U2) to the three API references, states the ISO 9613-2 default
atmosphere (20 °C / 70 %, a Table 2 reference atmosphere) in the API rows
and guides, and softens "digit-for-digit" to "the standard's printed
precision" wherever it covered Annex E, whose final level differs only by
the standard's own pre-rounding. ES pages now use the /es/ link prefix
consistently. Code nits from the review: dead arg > 1 guard removed
(z > 0 implies arg >= 3), _with_advisory uses dataclasses.replace, and the
float-keyed _PRIME_BY_BAND gets a safety comment. llms files regenerated.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* fix: validate propagation/exposure inputs and docs lint (PR review)
Addresses the PR #89 bot review:
- Barrier.__post_init__ rejects negative edge distances and a zero
edge_separation (which previously divided by zero in the C3 double-
diffraction factor); Task.__post_init__ rejects empty samples and a
non-positive duration.
- ground_attenuation and barrier_attenuation reject non-positive
frequencies (a zero frequency gave an infinite wavelength / division by
zero); ground_attenuation also checks a positive distance and
non-negative heights. job_based_exposure rejects a non-positive
sample_duration_hours.
- Rewrite the ISO 9613-2 7.3.2 closed form in the guides and API tables so
Markdownlint no longer parses `(...)[...]` as a reversed link (MD011).
- Regression tests for every new guard; docstrings note the new raises.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
Audit-driven accuracy, robustness and didactic improvements across the library (#82)
* fix: audit wave A — inter-sample LCpeak, cheby2 class-1 default, 144 kHz weighting target
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave A — decay validity thresholds, variant sharpness anchors, N5 phase, STIPA warning, Farina guard
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: audit wave B — self-contained snippets, tables, sharpness/open-plan figures, orphans
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave C — remaining minors and optimizations across the library
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: document the audit-wave parameters across guides and API tables
Cover the public parameters added in the audit fix waves across all three
documentation trees (docs/, site EN, site ES):
- lc_peak(..., oversample=8): inter-sample peak recovery (levels guide + API)
- calculate_sensitivity(..., narrowband=False): coherent tone estimator
(calibration guide + API)
- sound_intensity(..., bias_correct=False): finite-difference bias correction
(intensity guide + API)
- room_parameters/decay_curve(..., zero_phase=False): 125 Hz short-T bias
(room-acoustics guide + API)
- OctaveFilterBank/octavefilter attenuation default 60 -> 72 and cheby2
class-1 note (filter-banks guide + API)
- STIPA < 15 s UserWarning (psychoacoustics guide + API)
Also refresh dependent behaviour notes: ln_levels attack skip 2*tau -> 5*tau,
T20/T30 validity thresholds 35/45 -> 46/54 dB, and the zero-phase broadband
~0.2-0.3 dB band-level caveat. Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: truthfulness fixes from the final audit-branch review
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: 2000 Hz, not 2 ms, in the N5/N10 docstring
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 82 review round 1 — bias-correction cutoff, parabolic-peak guard, complexity, snippet self-containment
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
fix: first pass of audit findings (docs, figures, plotting)
Documentation:
- fix the Spanish filter-figure regression: _showfilter gains a close
flag and the generator saves through themed_path after drawing, so the
translation pass runs on the finished figure; restore the two
_ES_EXACT keys removed in #82 (they are emitted by filter_design, not
by the script)
- repair the broken building-acoustics.md link in the enclosed-space
guide (the repo split lives in room-acoustics.md)
- make the multiple-shock and intensity snippets self-contained: define
az/fs with a synthetic shock train (verified outputs 20.94 m/s2,
R=0.46, probability 0.03) and import numpy with stated outputs
(F2=3.41, Ld=8.0), in the repo doc and both site languages
- decimal points in the English Formula texts (1.07, 55.3) that had
kept the Spanish comma
Figures:
- stop drawing data series in the grid colour (invisible on light
backgrounds): time-weighting input burst, hearing-threshold 20 yr and
NIPTS 10 yr curves, and the unweighted/partial bars now use a visible
neutral grey; regenerate the 15 affected figure sets
Plotting layer:
- plot_facade_insulation forwards user kwargs to the primary D2m,nT
curve only, so label=/color= no longer raise or merge the four curves;
the kwargs-forwarding test now covers the facade plot
- harden three edge cases: all-masked SII (zero contribution no longer
divides 0/0), empty per-impulse prominence, and nearest-band lookup
for the NC governing marker instead of float equality
- sharpness normalization sanity guards raise RuntimeError instead of
AssertionError
Audit-driven accuracy, robustness and didactic improvements across the library (#82)
* fix: audit wave A — inter-sample LCpeak, cheby2 class-1 default, 144 kHz weighting target
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave A — decay validity thresholds, variant sharpness anchors, N5 phase, STIPA warning, Farina guard
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: audit wave B — self-contained snippets, tables, sharpness/open-plan figures, orphans
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave C — remaining minors and optimizations across the library
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: document the audit-wave parameters across guides and API tables
Cover the public parameters added in the audit fix waves across all three
documentation trees (docs/, site EN, site ES):
- lc_peak(..., oversample=8): inter-sample peak recovery (levels guide + API)
- calculate_sensitivity(..., narrowband=False): coherent tone estimator
(calibration guide + API)
- sound_intensity(..., bias_correct=False): finite-difference bias correction
(intensity guide + API)
- room_parameters/decay_curve(..., zero_phase=False): 125 Hz short-T bias
(room-acoustics guide + API)
- OctaveFilterBank/octavefilter attenuation default 60 -> 72 and cheby2
class-1 note (filter-banks guide + API)
- STIPA < 15 s UserWarning (psychoacoustics guide + API)
Also refresh dependent behaviour notes: ln_levels attack skip 2*tau -> 5*tau,
T20/T30 validity thresholds 35/45 -> 46/54 dB, and the zero-phase broadband
~0.2-0.3 dB band-level caveat. Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: truthfulness fixes from the final audit-branch review
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: 2000 Hz, not 2 ms, in the N5/N10 docstring
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 82 review round 1 — bias-correction cutoff, parabolic-peak guard, complexity, snippet self-containment
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Advanced psychoacoustics (ISO 532-2/3 Moore-Glasberg, ECMA-418-2 Sottek loudness/tonality/roughness) + numerical conformance report (#87)
* feat: ECMA-418-2 Sottek Hearing Model loudness and auditory front-end
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic loudness metric and
its shared auditory front-end (Sottek Hearing Model):
- Clause 5 front-end: outer/middle-ear cascade (5.1.3, Table 1), 53-band
gammatone-like auditory filter bank (5.1.4), band-dependent segmentation
(5.1.5, Table 4), rectification/RMS (5.1.6-5.1.7), compressive nonlinearity
(5.1.8, Formula 23, Table 2) and threshold in quiet (5.1.9, Table 3).
- Clause 6.2.2-6.2.7 ACF-based tonal/noise specific loudness (reusable by the
later tonality/roughness metrics).
- Clause 8 assembly: tonal/noise power average, average specific loudness,
time-dependent and single representative loudness value.
The front-end helpers are factored for reuse. Calibration verified: a 1 kHz
40 dB SPL sinusoid yields 0.996 sone_HMS (target 1.0). Adds a frozen
EcmaLoudness result with lazy .plot(), exports, and 12 tests.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 tonality (Sottek Hearing Model)
Implements the psychoacoustic tonality metric of ECMA-418-2:2025
(Clause 6.2.8-6.2.11) on top of the committed Sottek front-end and
ACF tonal/noise decomposition.
- tonality_ecma() -> EcmaTonality with the single value T (Formula 63),
average specific tonality T'(z) (Formula 53), tonal frequencies
f_ton,z(z) (Formula 55), time-dependent tonality T(l) (Formula 61)
and its frequency f_ton(l) (Formula 62); optional user band [f_low,f_high].
- Full Clause 6.2.3 band averaging with cross-block-size-group ACF
recomputation (loudness keeps its simplified, valid-for-loudness path;
boundary marker added there). Loudness 0.996 sone guard unchanged.
- _tonal_estimate now also returns the DFT-peak tonal frequency, with the
argmax restricted to the lower DFT half to avoid Nyquist-mirror aliasing.
- .plot() via _plotting.plot_ecma_tonality; exports in __init__.
- 13 conformance tests: 1 kHz/40 dB -> 1 tu_HMS calibration, pure tone
>> noise, f_ton tracks the tone, silence -> 0, band restriction.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 roughness (Sottek Hearing Model)
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic roughness metric
(Clause 7), the third Sottek Hearing Model output and new library
functionality. Reuses the Clause 5 auditory front-end from loudness_ecma
(ear filter, 53-band gammatone bank, specific basis loudness) and adds the
roughness-specific chain: start-only zero-padding and fixed 16384/4096
segmentation (5.1.2.2, 5.1.5.2, 7.1.1), the Hilbert envelope with x32
downsampling to 1500 Hz (7.1.2), the scaled envelope power spectrum
(7.1.3), two-step noise reduction (7.1.4), the four-stage spectral
weighting with quadratic-fit modulation-rate refinement, bias correction,
high/low modulation-rate weighting and fundamental-rate estimation
(7.1.5), interpolation to 50 Hz with the distribution-dependent nonlinear
transform, tabulated calibration c_R and asymmetric smoothing (7.1.7), and
the R'(z) / R(l50) / 90th-percentile aggregation (7.1.8-7.1.10).
Uses the standard's tabulated c_R = 0.0180685 (Formula 104), not
reverse-fit: the 1 kHz / 70 Hz / m=1 / 60 dB calibration signal yields
R = 1.0735 asper (near-but-not-exactly 1). OCR-ambiguous formulae
(66, 71, 78-81) cross-checked against the SQAT MATLAB reference; the
optional entropy weighting (7.1.6, needs RPM telemetry) is documented but
not implemented.
Adds roughness_ecma()/EcmaRoughness, a two-panel .plot(), exports, a
10-test TDD suite and plan/notes-ecma418-2-roughness.md. Full suite
752 passed / 12 skipped; loudness (0.996) and tonality (0.99993) oracles
unchanged (both modules untouched).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* test: pin ECMA-418-2 roughness calibration value and document clean-room variance
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-2 Moore-Glasberg loudness
Add the stationary Moore-Glasberg loudness model of ISO 532-2:2017 as a
third loudness method alongside the Zwicker (ISO 532-1) and Sottek
(ECMA-418-2) implementations. The full Clause 7 chain is implemented:
fixed outer/middle-ear transfer (Table 1), the level-dependent roex
auditory filter bank and excitation pattern on the ERB-number scale
(Formulae 1-6), the compressive specific-loudness transform (Formulae
7-9, Tables 2-4, C=0.0617 sone/Cam), binaural inhibition (Formulae
10-13) and integration to total loudness with the phon mapping of
Table 5.
Public API: loudness_moore_glasberg_from_spectrum (exact 5.2/5.4),
loudness_moore_glasberg_from_third_octave (29 bands, 5.5) and
loudness_moore_glasberg (signal wrapper), returning a frozen
MooreGlasbergLoudness with .plot() over the ERB-number scale.
Validated against the Annex B reference signals (tones, white/pink
noise, multi-tone complexes, tone-in-noise) to ~1-4%, well inside the
standard's 2.8 phon expanded uncertainty; the definitional anchor
(1 kHz/40 dB/free/binaural = 1.000 sone) holds end-to-end from the
tabulated constant. 50 new tests; full suite 802 passed / 12 skipped.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-3 Moore-Glasberg-Schlittenlacher time-varying loudness
Add loudness_moore_glasberg_time() implementing the ISO 532-3:2023 method:
a six-FFT multi-resolution running short-term spectrum (clause 7.3) feeding
the ISO 532-2 excitation and specific-loudness model on the 0.25 Cam grid
with the 532-3 constants (C=0.063, Tables 2/4, E_THRQ/E0=2.307), integrated
by asymmetric attack/release smoothing into short-term (clauses 7.6-7.8) and
long-term (clause 7.9) loudness traces, with peak long-term loudness,
percentiles and a lazy STL/LTL .plot().
Validated against the Annex C.1 tone table (1 kHz 10-80 dB, 3 kHz, 4 kHz,
100 Hz and monaural earphone) to within 0.4 phon, well inside the 2.8 phon
expanded uncertainty; the 1 kHz/40 dB anchor is exact at 1.000 sone. The
ISO 532-2 and ECMA-418-2 oracles are unchanged (831 passed / 12 skipped).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-3 per-ear LTL and Formula-14 kernel width (dichotic path)
Two transcription errors in the ISO 532-3:2023 dichotic (per-ear) binaural
path of the time-varying Moore-Glasberg-Schlittenlacher module:
1. Clause 7.9: the long-term loudness must be computed per ear by the
attack/release averager (Formulae 18-21) and then summed. The code ran a
single AGC on the binaural sum S'_L + S'_R; because the attack/release
branch is nonlinear, AGC(S'_L) + AGC(S'_R) != AGC(S'_L + S'_R) for dichotic
input. Keep two LTL states and sum the two long-term loudnesses. For
diotic/monaural the averager is linear in the (scaled) input so the result
is bit-for-bit identical to the old sum-then-AGC.
2. Formula (14)/(15): the smoothing weight is exp(-(B*Di)^2) with B = 0.08 and
Di in Cam. The code evaluated exp(-((0.08 * Di/0.1)^2)) = exp(-(0.8*Di)^2),
a Gaussian ~10x too narrow, and truncated the taps at +/-1.75 Cam. Remove
the spurious /0.1 and widen the kernel to the standard's +/-18 Cam (145 taps
on the 0.25-Cam grid). For diotic/monaural the L/R excitation ratio is 1
regardless of the kernel, so all oracles are unaffected.
No current validation signal exercises the dichotic path (Annex C signals are
diotic/monaural), so the 532-3 tone oracle and every other oracle remain
byte-identical; added focused dichotic tests plus a byte-identical anchor pin.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: numerical conformance report in CI PR comment
Reframe the PR comment into a persistent numerical conformance report:
each implemented standard -> normative expected value/range -> computed
result -> pass/fail with deviation, grouped by domain.
- scripts/conformance_report.py: registry of 21 checks across 17 standards
and 6 domains (filters/weightings, levels/dosimetry, psychoacoustics,
speech, intensity/power, room/building). Emits a headline summary, a
"Numerical validation - filters & weightings" section (per-architecture
IEC 61260-1 class margins + A/C/G weighting deviation vs the normative
curve) and one conformance table per domain. Deterministic, ~2.7 s.
- tests/reference_data.py: single source of truth for the shared normative
tables (IEC 61672-1 Table 3, ISO 7196 Table 2, ISO 717-1 Annex C R),
imported by both the tests and the report so they cannot drift.
- tests/test_conformance_report.py: smoke test asserting every registered
check passes and the Markdown is well-formed.
- comment_pr.py / workflow: conformance report leads the comment; the
test/coverage table is collapsed below. Retire benchmark_filters.py
(numerical content absorbed into the report).
make check green (740 passed, 12 skipped); ruff, mypy --strict, bandit clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: block-A review minors — docstring notes and tonality band-range
Four Minor findings from the plan-17 block-A psychoacoustics reviews. No
change to any validated numeric output; all five oracles stay byte-identical.
1. ISO 532-2 _source_levels: docstring note that clause 7.4 specifies a roex
weighting for X where the code uses a rectangular +/-ERB_n/2 window —
identical for isolated tones, immaterial for the Annex B broadband cases.
2. ISO 532-2 Formula-10 binaural smoothing kernel: the PDF (clause 8.1) states
"Di is changed in steps of 0,1" over -18..+18 Cam; the code confused the
0.1-Cam index offset with Di-in-Cam (the same /0.1 width bug just fixed in
532-3 Formula-14), making the Gaussian 10x too narrow and truncating to
+/-1.8 Cam. Widen to +/-18 Cam (361 taps) with Di = tap*_I_STEP. Provably
inert: all Annex B cases are diotic (ratio 1) or monaural (unit inhibition).
3. ECMA tonality _band_range: implement the exact Formulae 56-57 edge-midpoint
boundary conditions instead of a centre-frequency threshold; only affects
the optional user-band feature. Added a user-band edge test.
4. ECMA tonality reach: comment that the _CBF-1-band upper-edge term is a dead
defensive guard (N_B>0 only for low bands 0..24, so it never limits reach).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* chore: move conformance design doc out of repo root
* feat: clearer conformance report (value-vs-range) + block-A checks + audit fixes
Table 1 (filters): show, per architecture, the measured relative attenuation
at the binding band vs the class-1 limit it must clear (value + range, not
just a margin). Replace the "none" verdict (read as a failure) with a
"By design (ripple/soft rolloff)" label + footnote; Butterworth (default) and
Chebyshev-II are the only mask-compliant architectures. The measured value and
limit are re-derived with the public class_limits on the same designed SOS; a
smoke-test guard asserts the re-derived class-1 margin matches
verify_filter_class (single source of truth).
Table 2 (weightings): separate the informational max-deviation-from-nominal
(at a frequency extreme with wide, asymmetric tolerance) from the compliance
margin at the binding frequency, where the deviation, the +/- tolerance band
and the headroom are co-located so "value vs range" is unambiguous and in-spec.
Audit: verified all 21 checks (expected value/range vs the standard, real
library call, honest tolerance). One fix - ISO 226 anchored at 60 phon @ 2 kHz
-> 60 dB, a genuine Table B.1 value but coincidentally equal to the phon number
(reads as the trivial 1 kHz identity); re-anchored to the non-coincidental
60 phon @ 100 Hz -> 78.5 dB, sourced from reference_data. No library bug found.
Block-A psychoacoustics (+5, single-source expected in reference_data):
ECMA-418-2 loudness (1 kHz/40 dB -> 1 sone_HMS, c_N), tonality (-> 1 tu_HMS,
c_T), roughness (1 kHz/70 Hz/m=1/60 dB -> clean-room 1.0735 asper with a note
that the standard target is 1.0); ISO 532-2 (1 kHz/40 dB -> 1 sone, C=0.0617);
ISO 532-3 (steady 1 kHz/40 dB -> peak LTL 1 sone). 26/26 checks, 20 standards.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: versioned docs/CONFORMANCE.md with make target, CI staleness check and README link
Persist the numerical conformance report as a committed, versioned file so
every reader sees the library's per-standard validation (expected vs computed)
without running CI.
- docs/CONFORMANCE.md: committed report (26/26 checks, 6 domains, 20 standards)
with an auto-generated "do not hand-edit" header linking the standards docs.
- scripts/conformance_report.py: add a --file-header flag that emits that header
for the committed file (PR-comment body stays header-free); coarsen the
informational delta column to 3 decimals so BLAS/FFT-dependent sub-milli
residuals do not churn the file (status/computed columns keep regressions
visible).
- Makefile: `make conformance` regenerates the file; `make install-hooks`
installs the optional pre-commit hook.
- CI: new fast `conformance` job (runtime deps only) fails a PR if the committed
file is stale, telling the author to run `make conformance` and commit.
- hooks/pre-commit: opt-in convenience hook, scoped to src/scripts/reference
changes; CI is the enforcement.
- README + CONTRIBUTING: link and document the generated report.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced psychoacoustics guide, theory, API (EN)
Document the plan-17 block-A advanced psychoacoustics models: Moore-Glasberg
loudness (ISO 532-2/532-3), Sottek Hearing Model loudness, tonality and
roughness (ECMA-418-2:2025).
- Psychoacoustics guide: new "Advanced loudness & sound-quality models"
section with a model-comparison table, executed snippets, parameter tables,
res.plot() one-liners and figure-code collapsibles (docs + site twin,
code blocks byte-identical).
- Theory: excitation-pattern/specific-loudness chain, Sottek front-end,
ACF tonality and envelope-modulation roughness (GitHub-safe math).
- API reference: rows for the 5 functions (+ 2 MG spectral entry points) and
5 result dataclasses with their .plot().
- README, EN landing (standards 26 -> 29: ISO 532-2, ISO 532-3, ECMA-418-2),
CHANGELOG Unreleased/Added, llms regen.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced-psychoacoustics figures (loudness models, Sottek, tonality/roughness, time-varying)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de psicoacústica avanzada (Moore-Glasberg, Sottek, tonalidad, aspereza)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-2 signal-input path builds a narrowband spectrum (pure-tone loudness)
The loudness_moore_glasberg(x, fs) wrapper routed the signal through the
one-third-octave method, which smeared a pure tone across a whole 1/3-octave
band and used a coherent-gain window normalisation that over-counted power by
1.76 dB. A calibrated 1 kHz tone at 40 dB SPL therefore returned 1.327 sone
instead of the definitional anchor of 1.000 sone.
ISO 532-2 is spectrum-based and its exact input (clauses 5.2/5.4) is a set of
discrete sinusoidal components; the excitation pattern (Formula 5) is computed
per component, so a tone must enter as a single line. The wrapper now forms the
signal's narrowband (FFT) line spectrum with a power-preserving (Parseval)
window normalisation and feeds it to loudness_moore_glasberg_from_spectrum. A
1 kHz/40 dB tone now yields 1.0001 sone / 40 phon and multi-tone signals match
the exact spectrum path. The validated spectrum and third-octave paths are
byte-identical.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: correct MG signal-path mechanism text and presentation default
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 87 review round 1 — float-equality guards, 532-3 FFT window length, tonality short-signal, report/figure fixes
- roughness_ecma: replace three float == 0.0 checks with abs>eps guards
(SonarCloud gate); roughness oracle byte-identical (1.0735).
- loudness_moore_glasberg_time: per-window FFT length (>= window length) so
the 64 ms window is not truncated at 44.1/48 kHz (was ~0.74 dB low in the
20-80 Hz band). 32 kHz anchor byte-identical (n_max 1.0000044713237626).
- tonality_ecma: short signals fall back to averaging all blocks (matches
loudness_ecma), not the final block only; validated anchor unaffected.
- conformance_report: document deliberate 273.0 (speed of sound) vs 273.15
(C1/C2) split matching the library; complete bind_side docstring.
- generate_graphs: roughness annotation uses computed peak fmod; Sottek
metrics use tu_HMS/sone_HMS units; ES translations + 8 figures regenerated.
- hooks/pre-commit: drop set -e so a benign regen failure does not block the
commit (CI staleness check is the gate).
- test_iec_weighting_table3 docstring, Makefile .PHONY completion.
All 5 psychoacoustics oracles byte-identical; make check 868 passed / 12
skipped; ruff + mypy + bandit clean; conformance 26/26.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: localize decimal commas in Sottek-unit figure labels (underscore guard)
The `_` guard in `_translate_figure` skipped decimal-comma localization for
any label containing an underscore. The new Sottek/HMS unit tokens
(`sone_HMS`, `tu_HMS`, `sonios_HMS`) carry underscores, so Spanish figures
regressed to dots (`8.0`, `0.42`) instead of commas (`8,0`, `0,42`).
Drop the underscore guard: the decimal-comma regex only rewrites a bare
`digit.digit` not adjacent to more digits/dots, so unit identifiers and
version numbers (5.3.3) stay intact while genuine decimals get commas.
Mathtext is still skipped via the `$` guard. Regenerated the affected ES
figures (sottek_specific_loudness, tonality_roughness_demo).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Audit-driven accuracy, robustness and didactic improvements across the library (#82)
* fix: audit wave A — inter-sample LCpeak, cheby2 class-1 default, 144 kHz weighting target
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave A — decay validity thresholds, variant sharpness anchors, N5 phase, STIPA warning, Farina guard
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: audit wave B — self-contained snippets, tables, sharpness/open-plan figures, orphans
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave C — remaining minors and optimizations across the library
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: document the audit-wave parameters across guides and API tables
Cover the public parameters added in the audit fix waves across all three
documentation trees (docs/, site EN, site ES):
- lc_peak(..., oversample=8): inter-sample peak recovery (levels guide + API)
- calculate_sensitivity(..., narrowband=False): coherent tone estimator
(calibration guide + API)
- sound_intensity(..., bias_correct=False): finite-difference bias correction
(intensity guide + API)
- room_parameters/decay_curve(..., zero_phase=False): 125 Hz short-T bias
(room-acoustics guide + API)
- OctaveFilterBank/octavefilter attenuation default 60 -> 72 and cheby2
class-1 note (filter-banks guide + API)
- STIPA < 15 s UserWarning (psychoacoustics guide + API)
Also refresh dependent behaviour notes: ln_levels attack skip 2*tau -> 5*tau,
T20/T30 validity thresholds 35/45 -> 46/54 dB, and the zero-phase broadband
~0.2-0.3 dB band-level caveat. Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: truthfulness fixes from the final audit-branch review
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: 2000 Hz, not 2 ms, in the N5/N10 docstring
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 82 review round 1 — bias-correction cutoff, parabolic-peak guard, complexity, snippet self-containment
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Sound power determination and building acoustics II: ISO 3744/3746, ISO 3741, ISO 9614-2, ISO 16283-2 + ISO 717-2, ISO 354 (#84)
* feat: ISO 3744/3746 sound power from pressure measurements
Add sound_power.py: sound power level of a noise source from sound
pressure levels over an enveloping measurement surface, ISO 3744:2010
(engineering, grade 2) and ISO 3746:2010 (survey, grade 3).
- sound_power_pressure(): per-band LW and A-weighted LWA from an
(NM, NB) level array over a hemisphere (radius) or parallelepiped
(dimensions + distance); surface area from the standard's closed forms
for 1/2/3 reflecting planes; energy average (Eq. 12), background K1
(Eq. 16), environmental K2 (Eq. A.2), apparent directivity index
(Eq. 7), expanded uncertainty.
- measurement_positions(): normative Annex B coordinates
(Tables B.1/B.2/B.3), grade- and plane-dependent subsets.
- background_noise_correction(): K1 with engineering/survey criteria.
- environmental_correction(): K2 from A, Sabine T+V, or alpha*Sv.
- SoundPowerResult (frozen dataclass), SoundPowerWarning.
Tests: 24 cases incl. monopole-over-plane exact LW recovery, radius
independence, K1/K2 closed forms, LWA via Annex E, and validations.
make check green (543 passed, 12 skipped); ruff/bandit/mypy --strict clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: directivity index under background correction, citation hygiene
Broadband K1 for the apparent directivity index was collapsing to the
surface-area term 10*lg(S/S0) (~+22 dB inflation with background
correction). Compute the true energy-based broadband K1 (per-band Eq. 16
aggregated over bands) so DI is correct in both paths.
Also: multi-band sound_power_level_a without frequencies now returns NaN
(A-weighting needs band centres); dropped PDF page-number citations from
Annex B/E table comments; corrected the survey parallelepiped position
citation to ISO 3746 clause C.1 / Figure C.7.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 3741 reverberation-room sound power
Add the ISO 3741:2010 precision (grade 1) methods for determining sound
power in a reverberation test room, in a new sound_power_reverberation
module:
- sound_power_reverberation() — direct method (Eq. 20): LW from the mean
room SPL and the Sabine equivalent absorption area A = (55,26/c)(V/T60),
with the full normative bracket: 10 lg(A/A0), 4,34*(A/S), the Waterhouse
boundary term 10 lg(1 + S c/(8 V f)), the reference-quantity (C1) and
radiation-impedance (C2) meteorological corrections and the -6 dB
constant. Speed of sound c = 20,05*sqrt(273 + theta).
- sound_power_comparison() — comparison method (Eq. 21) with a reference
sound source: LW = LW(RSS) + (Lp(ST) - Lp(RSS) + C2).
- ReverberationSoundPowerResult frozen dataclass; frequency-dependent
background correction K1 (Eq. 14, same formula as ISO 3744) with the
precision-grade 6/10 dB criteria; A-weighted total via Annex F.
Reuses the ISO 3744 energy-average and A-weighting helpers. Tests cover
exact inversion of Eq. (20), the Waterhouse term vanishing at high
frequency, the comparison method exact by construction, and synergy with
room_parameters T30 feeding the direct method.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 3741 room-qualification advisories and validation ordering
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9614-2 sound power by intensity scanning
Add sound_power_intensity() computing per-band LW = 10lg(sum Pi/P0) from
per-segment signed normal intensity and areas (partial power Pi = <In,i>*Si),
the scanning-method field indicators FpI (Eq. A.1) and F+/- (Eq. A.2), the
repeatability criterion 3, and the achieved engineering/survey grade per band
(Annex B criteria 1-3). Negative total power flags bands as non-determinable
(clause 9.2); negative partial power raises SoundPowerWarning. Reuses
dynamic_capability_index for Ld = dpI0 - K (Table 1).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 16283-2 impact sound insulation and ISO 717-2 ratings
Add field impact sound insulation (ISO 16283-2, tapping machine) and the
ISO 717-2 single-number weighted impact rating with the CI spectrum
adaptation term, reusing the verified reference-curve shift engine.
- impact_insulation(li, t2, *, volume=None, t0=0.5): per-band
L'nT = Li - 10 lg(T/T0) (Formula (1)) and L'n = Li + 10 lg(A/A0) with
A = 0,16 V/T, A0 = 10 m2 (Formula (2)); positions energy-averaged.
- weighted_impact_rating(values_by_band, bands=None): Ln,w / L'n,w /
L'nT,w (Clause 4.3, octave -5 dB rule) and CI (Clause A.2.1, energetic
sum 100-2500 / 125-2000). Impact unfavourable deviations (measurement
exceeds reference, sign opposite to airborne) reduce to the airborne
search on negated curves, so _best_shift is reused verbatim.
- _best_shift: tolerance absorbs float noise at the exact bound (sums are
true multiples of 0,1 dB); airborne behaviour unchanged.
Verified against ISO 717-2 Annex C worked examples (Table C.1 Ln,w=79,
CI=-11; Table C.3 octave Ln,w=54, CI=0) and an independent brute-force
shift search on 10 000 random curves per band set. 2013 and 2020 editions
are identical for all implemented maths (2020 only adds Annex D rubber
ball, out of scope).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 354 sound absorption in a reverberation room
Add phonometry.sound_absorption implementing BS EN ISO 354:2003:
- absorption_area(t60, volume, ...): equivalent sound absorption area
A = 55,3*V/(c*T) - 4*V*m (Eq. 5/7), speed of sound from Eq. (6).
- absorption_coefficient(t1, t2, volume, sample_area, ...): alpha_s =
(A2-A1)/S built from Eq. (8)/(9), per-measurement c1/c2, unclamped
(alpha_s > 1 valid, clause 3.7 NOTE 2); warns when T2 >= T1.
- attenuation_from_alpha: ISO-354 conversion m = alpha/(10 lg e) from an
ISO 9613-1 attenuation coefficient (m deferred to ISO 9613-1, default 0).
Composes directly with room_parameters() T20/T30 outputs. 25 tests
(exact T->A->T inversion, synthetic-alpha recovery, zero-m air term,
two-temperature Eq. (8), range validation, room_parameters synergy).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 354 room and sample-size advisories
Add advisory AbsorptionWarning (never raising; result still returned) for
setup conditions outside ISO 354:2003 limits:
- Room volume below the 150 m3 minimum of clause 6.1.1, in both
absorption_area and absorption_coefficient.
- Sample area outside the clause 6.2.1.1 range 10 <= S <= 12 m2, with the
upper limit scaled by (V/200)^(2/3) when V > 200 m3, in
absorption_coefficient.
The volume advisory is emitted once by absorption_coefficient and suppressed
in its internal absorption_area calls to avoid duplicate warnings.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: deferred review minors — airborne brute-force net, advisory consistency, labels
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: sound power guide, impact and absorption sections, theory and API (EN)
Add a "Sound Power" guide covering the three LW routes (ISO 3744/3746
enveloping surface, ISO 3741 reverberation room with Waterhouse/C1/C2,
ISO 9614-2 intensity scanning), with a method-comparison table, executed
snippets and parameter tables. Extend the Room & Building Acoustics guide
with impact sound insulation (ISO 16283-2 + ISO 717-2, L'nT/L'n, tapping
machine, CI) and sound absorption in a reverberation room (ISO 354).
Add the matching theory sections (K1/K2, Waterhouse, C1/C2 derivations;
impact sign conventions; Sabine absorption) and API-reference rows for all
new public names. Update the landing standards count to 26, the sidebar,
README highlights/table, CHANGELOG and llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: sound-power and impact figures and diagrams
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: include the sound-power guide in llms coverage
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: reposition Ln,w annotation in the impact figure
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de potencia sonora, impacto y absorción
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: final-review findings — comparison K1 reporting, citations, knee unification, docs attribution
- sound_power_comparison now reports the test-source K1 actually applied
per band (zero when no background), matching the dataclass docstring;
LW unchanged.
- Replace gitignored notes-iso3744-3746.md citation in the DI comment with
the standard reference (ISO 3744:2010, 3.24 / Eq. 7 context).
- Unify the K1 zeroing knee to the >= form (ΔLp >= 15 -> no correction).
- Broaden SoundPowerWarning docstring to name all emitters (ISO 3744/3746,
ISO 3741, ISO 9614-2).
- Fix docs misattributing K2 to ISO 3741 (ISO 3744 K2 vs ISO 3741 absorption
term) across EN/ES room-acoustics guides.
- EN impact-setup diagram uses dot decimals (0.16); regenerate the 4 variants.
- CHANGELOG: label the ISO 717-2 Annex C value Ln,w = 79.
- Warn-after-validate in sound_power_comparison; "is raised" -> "is emitted".
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 84 review round 1 — per-band DI and K2, sweep-reversal repeatability, validation and broadcast ergonomics
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 84 round 2 — partial room-data validation, result-field docs
Raise a clear ValueError in environmental_correction (and its
sound_power_pressure pass-through) when exactly one member of a
room-data pair (reverberation_time/room_volume or
mean_absorption_coefficient/room_surface) is supplied, instead of
silently returning K2=0 as if free field. All-None remains the
legitimate free-field K2=0 path.
Document frequencies on the ReverberationSoundPowerResult row and
negative_band as a standalone per-band boolean field on the
SoundPowerIntensityResult row across docs/api-reference.md and the
EN/ES site twins.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Sound power determination and building acoustics II: ISO 3744/3746, ISO 3741, ISO 9614-2, ISO 16283-2 + ISO 717-2, ISO 354 (#84)
* feat: ISO 3744/3746 sound power from pressure measurements
Add sound_power.py: sound power level of a noise source from sound
pressure levels over an enveloping measurement surface, ISO 3744:2010
(engineering, grade 2) and ISO 3746:2010 (survey, grade 3).
- sound_power_pressure(): per-band LW and A-weighted LWA from an
(NM, NB) level array over a hemisphere (radius) or parallelepiped
(dimensions + distance); surface area from the standard's closed forms
for 1/2/3 reflecting planes; energy average (Eq. 12), background K1
(Eq. 16), environmental K2 (Eq. A.2), apparent directivity index
(Eq. 7), expanded uncertainty.
- measurement_positions(): normative Annex B coordinates
(Tables B.1/B.2/B.3), grade- and plane-dependent subsets.
- background_noise_correction(): K1 with engineering/survey criteria.
- environmental_correction(): K2 from A, Sabine T+V, or alpha*Sv.
- SoundPowerResult (frozen dataclass), SoundPowerWarning.
Tests: 24 cases incl. monopole-over-plane exact LW recovery, radius
independence, K1/K2 closed forms, LWA via Annex E, and validations.
make check green (543 passed, 12 skipped); ruff/bandit/mypy --strict clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: directivity index under background correction, citation hygiene
Broadband K1 for the apparent directivity index was collapsing to the
surface-area term 10*lg(S/S0) (~+22 dB inflation with background
correction). Compute the true energy-based broadband K1 (per-band Eq. 16
aggregated over bands) so DI is correct in both paths.
Also: multi-band sound_power_level_a without frequencies now returns NaN
(A-weighting needs band centres); dropped PDF page-number citations from
Annex B/E table comments; corrected the survey parallelepiped position
citation to ISO 3746 clause C.1 / Figure C.7.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 3741 reverberation-room sound power
Add the ISO 3741:2010 precision (grade 1) methods for determining sound
power in a reverberation test room, in a new sound_power_reverberation
module:
- sound_power_reverberation() — direct method (Eq. 20): LW from the mean
room SPL and the Sabine equivalent absorption area A = (55,26/c)(V/T60),
with the full normative bracket: 10 lg(A/A0), 4,34*(A/S), the Waterhouse
boundary term 10 lg(1 + S c/(8 V f)), the reference-quantity (C1) and
radiation-impedance (C2) meteorological corrections and the -6 dB
constant. Speed of sound c = 20,05*sqrt(273 + theta).
- sound_power_comparison() — comparison method (Eq. 21) with a reference
sound source: LW = LW(RSS) + (Lp(ST) - Lp(RSS) + C2).
- ReverberationSoundPowerResult frozen dataclass; frequency-dependent
background correction K1 (Eq. 14, same formula as ISO 3744) with the
precision-grade 6/10 dB criteria; A-weighted total via Annex F.
Reuses the ISO 3744 energy-average and A-weighting helpers. Tests cover
exact inversion of Eq. (20), the Waterhouse term vanishing at high
frequency, the comparison method exact by construction, and synergy with
room_parameters T30 feeding the direct method.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 3741 room-qualification advisories and validation ordering
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9614-2 sound power by intensity scanning
Add sound_power_intensity() computing per-band LW = 10lg(sum Pi/P0) from
per-segment signed normal intensity and areas (partial power Pi = <In,i>*Si),
the scanning-method field indicators FpI (Eq. A.1) and F+/- (Eq. A.2), the
repeatability criterion 3, and the achieved engineering/survey grade per band
(Annex B criteria 1-3). Negative total power flags bands as non-determinable
(clause 9.2); negative partial power raises SoundPowerWarning. Reuses
dynamic_capability_index for Ld = dpI0 - K (Table 1).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 16283-2 impact sound insulation and ISO 717-2 ratings
Add field impact sound insulation (ISO 16283-2, tapping machine) and the
ISO 717-2 single-number weighted impact rating with the CI spectrum
adaptation term, reusing the verified reference-curve shift engine.
- impact_insulation(li, t2, *, volume=None, t0=0.5): per-band
L'nT = Li - 10 lg(T/T0) (Formula (1)) and L'n = Li + 10 lg(A/A0) with
A = 0,16 V/T, A0 = 10 m2 (Formula (2)); positions energy-averaged.
- weighted_impact_rating(values_by_band, bands=None): Ln,w / L'n,w /
L'nT,w (Clause 4.3, octave -5 dB rule) and CI (Clause A.2.1, energetic
sum 100-2500 / 125-2000). Impact unfavourable deviations (measurement
exceeds reference, sign opposite to airborne) reduce to the airborne
search on negated curves, so _best_shift is reused verbatim.
- _best_shift: tolerance absorbs float noise at the exact bound (sums are
true multiples of 0,1 dB); airborne behaviour unchanged.
Verified against ISO 717-2 Annex C worked examples (Table C.1 Ln,w=79,
CI=-11; Table C.3 octave Ln,w=54, CI=0) and an independent brute-force
shift search on 10 000 random curves per band set. 2013 and 2020 editions
are identical for all implemented maths (2020 only adds Annex D rubber
ball, out of scope).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 354 sound absorption in a reverberation room
Add phonometry.sound_absorption implementing BS EN ISO 354:2003:
- absorption_area(t60, volume, ...): equivalent sound absorption area
A = 55,3*V/(c*T) - 4*V*m (Eq. 5/7), speed of sound from Eq. (6).
- absorption_coefficient(t1, t2, volume, sample_area, ...): alpha_s =
(A2-A1)/S built from Eq. (8)/(9), per-measurement c1/c2, unclamped
(alpha_s > 1 valid, clause 3.7 NOTE 2); warns when T2 >= T1.
- attenuation_from_alpha: ISO-354 conversion m = alpha/(10 lg e) from an
ISO 9613-1 attenuation coefficient (m deferred to ISO 9613-1, default 0).
Composes directly with room_parameters() T20/T30 outputs. 25 tests
(exact T->A->T inversion, synthetic-alpha recovery, zero-m air term,
two-temperature Eq. (8), range validation, room_parameters synergy).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 354 room and sample-size advisories
Add advisory AbsorptionWarning (never raising; result still returned) for
setup conditions outside ISO 354:2003 limits:
- Room volume below the 150 m3 minimum of clause 6.1.1, in both
absorption_area and absorption_coefficient.
- Sample area outside the clause 6.2.1.1 range 10 <= S <= 12 m2, with the
upper limit scaled by (V/200)^(2/3) when V > 200 m3, in
absorption_coefficient.
The volume advisory is emitted once by absorption_coefficient and suppressed
in its internal absorption_area calls to avoid duplicate warnings.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: deferred review minors — airborne brute-force net, advisory consistency, labels
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: sound power guide, impact and absorption sections, theory and API (EN)
Add a "Sound Power" guide covering the three LW routes (ISO 3744/3746
enveloping surface, ISO 3741 reverberation room with Waterhouse/C1/C2,
ISO 9614-2 intensity scanning), with a method-comparison table, executed
snippets and parameter tables. Extend the Room & Building Acoustics guide
with impact sound insulation (ISO 16283-2 + ISO 717-2, L'nT/L'n, tapping
machine, CI) and sound absorption in a reverberation room (ISO 354).
Add the matching theory sections (K1/K2, Waterhouse, C1/C2 derivations;
impact sign conventions; Sabine absorption) and API-reference rows for all
new public names. Update the landing standards count to 26, the sidebar,
README highlights/table, CHANGELOG and llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: sound-power and impact figures and diagrams
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: include the sound-power guide in llms coverage
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: reposition Ln,w annotation in the impact figure
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de potencia sonora, impacto y absorción
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: final-review findings — comparison K1 reporting, citations, knee unification, docs attribution
- sound_power_comparison now reports the test-source K1 actually applied
per band (zero when no background), matching the dataclass docstring;
LW unchanged.
- Replace gitignored notes-iso3744-3746.md citation in the DI comment with
the standard reference (ISO 3744:2010, 3.24 / Eq. 7 context).
- Unify the K1 zeroing knee to the >= form (ΔLp >= 15 -> no correction).
- Broaden SoundPowerWarning docstring to name all emitters (ISO 3744/3746,
ISO 3741, ISO 9614-2).
- Fix docs misattributing K2 to ISO 3741 (ISO 3744 K2 vs ISO 3741 absorption
term) across EN/ES room-acoustics guides.
- EN impact-setup diagram uses dot decimals (0.16); regenerate the 4 variants.
- CHANGELOG: label the ISO 717-2 Annex C value Ln,w = 79.
- Warn-after-validate in sound_power_comparison; "is raised" -> "is emitted".
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 84 review round 1 — per-band DI and K2, sweep-reversal repeatability, validation and broadcast ergonomics
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 84 round 2 — partial room-data validation, result-field docs
Raise a clear ValueError in environmental_correction (and its
sound_power_pressure pass-through) when exactly one member of a
room-data pair (reverberation_time/room_volume or
mean_absorption_coefficient/room_surface) is supplied, instead of
silently returning K2=0 as if free field. All-None remains the
legitimate free-field K2=0 path.
Document frequencies on the ReverberationSoundPowerResult row and
negative_band as a standalone per-band boolean field on the
SoundPowerIntensityResult row across docs/api-reference.md and the
EN/ES site twins.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Sound power determination and building acoustics II: ISO 3744/3746, ISO 3741, ISO 9614-2, ISO 16283-2 + ISO 717-2, ISO 354 (#84)
* feat: ISO 3744/3746 sound power from pressure measurements
Add sound_power.py: sound power level of a noise source from sound
pressure levels over an enveloping measurement surface, ISO 3744:2010
(engineering, grade 2) and ISO 3746:2010 (survey, grade 3).
- sound_power_pressure(): per-band LW and A-weighted LWA from an
(NM, NB) level array over a hemisphere (radius) or parallelepiped
(dimensions + distance); surface area from the standard's closed forms
for 1/2/3 reflecting planes; energy average (Eq. 12), background K1
(Eq. 16), environmental K2 (Eq. A.2), apparent directivity index
(Eq. 7), expanded uncertainty.
- measurement_positions(): normative Annex B coordinates
(Tables B.1/B.2/B.3), grade- and plane-dependent subsets.
- background_noise_correction(): K1 with engineering/survey criteria.
- environmental_correction(): K2 from A, Sabine T+V, or alpha*Sv.
- SoundPowerResult (frozen dataclass), SoundPowerWarning.
Tests: 24 cases incl. monopole-over-plane exact LW recovery, radius
independence, K1/K2 closed forms, LWA via Annex E, and validations.
make check green (543 passed, 12 skipped); ruff/bandit/mypy --strict clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: directivity index under background correction, citation hygiene
Broadband K1 for the apparent directivity index was collapsing to the
surface-area term 10*lg(S/S0) (~+22 dB inflation with background
correction). Compute the true energy-based broadband K1 (per-band Eq. 16
aggregated over bands) so DI is correct in both paths.
Also: multi-band sound_power_level_a without frequencies now returns NaN
(A-weighting needs band centres); dropped PDF page-number citations from
Annex B/E table comments; corrected the survey parallelepiped position
citation to ISO 3746 clause C.1 / Figure C.7.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 3741 reverberation-room sound power
Add the ISO 3741:2010 precision (grade 1) methods for determining sound
power in a reverberation test room, in a new sound_power_reverberation
module:
- sound_power_reverberation() — direct method (Eq. 20): LW from the mean
room SPL and the Sabine equivalent absorption area A = (55,26/c)(V/T60),
with the full normative bracket: 10 lg(A/A0), 4,34*(A/S), the Waterhouse
boundary term 10 lg(1 + S c/(8 V f)), the reference-quantity (C1) and
radiation-impedance (C2) meteorological corrections and the -6 dB
constant. Speed of sound c = 20,05*sqrt(273 + theta).
- sound_power_comparison() — comparison method (Eq. 21) with a reference
sound source: LW = LW(RSS) + (Lp(ST) - Lp(RSS) + C2).
- ReverberationSoundPowerResult frozen dataclass; frequency-dependent
background correction K1 (Eq. 14, same formula as ISO 3744) with the
precision-grade 6/10 dB criteria; A-weighted total via Annex F.
Reuses the ISO 3744 energy-average and A-weighting helpers. Tests cover
exact inversion of Eq. (20), the Waterhouse term vanishing at high
frequency, the comparison method exact by construction, and synergy with
room_parameters T30 feeding the direct method.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 3741 room-qualification advisories and validation ordering
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9614-2 sound power by intensity scanning
Add sound_power_intensity() computing per-band LW = 10lg(sum Pi/P0) from
per-segment signed normal intensity and areas (partial power Pi = <In,i>*Si),
the scanning-method field indicators FpI (Eq. A.1) and F+/- (Eq. A.2), the
repeatability criterion 3, and the achieved engineering/survey grade per band
(Annex B criteria 1-3). Negative total power flags bands as non-determinable
(clause 9.2); negative partial power raises SoundPowerWarning. Reuses
dynamic_capability_index for Ld = dpI0 - K (Table 1).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 16283-2 impact sound insulation and ISO 717-2 ratings
Add field impact sound insulation (ISO 16283-2, tapping machine) and the
ISO 717-2 single-number weighted impact rating with the CI spectrum
adaptation term, reusing the verified reference-curve shift engine.
- impact_insulation(li, t2, *, volume=None, t0=0.5): per-band
L'nT = Li - 10 lg(T/T0) (Formula (1)) and L'n = Li + 10 lg(A/A0) with
A = 0,16 V/T, A0 = 10 m2 (Formula (2)); positions energy-averaged.
- weighted_impact_rating(values_by_band, bands=None): Ln,w / L'n,w /
L'nT,w (Clause 4.3, octave -5 dB rule) and CI (Clause A.2.1, energetic
sum 100-2500 / 125-2000). Impact unfavourable deviations (measurement
exceeds reference, sign opposite to airborne) reduce to the airborne
search on negated curves, so _best_shift is reused verbatim.
- _best_shift: tolerance absorbs float noise at the exact bound (sums are
true multiples of 0,1 dB); airborne behaviour unchanged.
Verified against ISO 717-2 Annex C worked examples (Table C.1 Ln,w=79,
CI=-11; Table C.3 octave Ln,w=54, CI=0) and an independent brute-force
shift search on 10 000 random curves per band set. 2013 and 2020 editions
are identical for all implemented maths (2020 only adds Annex D rubber
ball, out of scope).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 354 sound absorption in a reverberation room
Add phonometry.sound_absorption implementing BS EN ISO 354:2003:
- absorption_area(t60, volume, ...): equivalent sound absorption area
A = 55,3*V/(c*T) - 4*V*m (Eq. 5/7), speed of sound from Eq. (6).
- absorption_coefficient(t1, t2, volume, sample_area, ...): alpha_s =
(A2-A1)/S built from Eq. (8)/(9), per-measurement c1/c2, unclamped
(alpha_s > 1 valid, clause 3.7 NOTE 2); warns when T2 >= T1.
- attenuation_from_alpha: ISO-354 conversion m = alpha/(10 lg e) from an
ISO 9613-1 attenuation coefficient (m deferred to ISO 9613-1, default 0).
Composes directly with room_parameters() T20/T30 outputs. 25 tests
(exact T->A->T inversion, synthetic-alpha recovery, zero-m air term,
two-temperature Eq. (8), range validation, room_parameters synergy).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 354 room and sample-size advisories
Add advisory AbsorptionWarning (never raising; result still returned) for
setup conditions outside ISO 354:2003 limits:
- Room volume below the 150 m3 minimum of clause 6.1.1, in both
absorption_area and absorption_coefficient.
- Sample area outside the clause 6.2.1.1 range 10 <= S <= 12 m2, with the
upper limit scaled by (V/200)^(2/3) when V > 200 m3, in
absorption_coefficient.
The volume advisory is emitted once by absorption_coefficient and suppressed
in its internal absorption_area calls to avoid duplicate warnings.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: deferred review minors — airborne brute-force net, advisory consistency, labels
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: sound power guide, impact and absorption sections, theory and API (EN)
Add a "Sound Power" guide covering the three LW routes (ISO 3744/3746
enveloping surface, ISO 3741 reverberation room with Waterhouse/C1/C2,
ISO 9614-2 intensity scanning), with a method-comparison table, executed
snippets and parameter tables. Extend the Room & Building Acoustics guide
with impact sound insulation (ISO 16283-2 + ISO 717-2, L'nT/L'n, tapping
machine, CI) and sound absorption in a reverberation room (ISO 354).
Add the matching theory sections (K1/K2, Waterhouse, C1/C2 derivations;
impact sign conventions; Sabine absorption) and API-reference rows for all
new public names. Update the landing standards count to 26, the sidebar,
README highlights/table, CHANGELOG and llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: sound-power and impact figures and diagrams
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: include the sound-power guide in llms coverage
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: reposition Ln,w annotation in the impact figure
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de potencia sonora, impacto y absorción
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: final-review findings — comparison K1 reporting, citations, knee unification, docs attribution
- sound_power_comparison now reports the test-source K1 actually applied
per band (zero when no background), matching the dataclass docstring;
LW unchanged.
- Replace gitignored notes-iso3744-3746.md citation in the DI comment with
the standard reference (ISO 3744:2010, 3.24 / Eq. 7 context).
- Unify the K1 zeroing knee to the >= form (ΔLp >= 15 -> no correction).
- Broaden SoundPowerWarning docstring to name all emitters (ISO 3744/3746,
ISO 3741, ISO 9614-2).
- Fix docs misattributing K2 to ISO 3741 (ISO 3744 K2 vs ISO 3741 absorption
term) across EN/ES room-acoustics guides.
- EN impact-setup diagram uses dot decimals (0.16); regenerate the 4 variants.
- CHANGELOG: label the ISO 717-2 Annex C value Ln,w = 79.
- Warn-after-validate in sound_power_comparison; "is raised" -> "is emitted".
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 84 review round 1 — per-band DI and K2, sweep-reversal repeatability, validation and broadcast ergonomics
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 84 round 2 — partial room-data validation, result-field docs
Raise a clear ValueError in environmental_correction (and its
sound_power_pressure pass-through) when exactly one member of a
room-data pair (reverberation_time/room_volume or
mean_absorption_coefficient/room_surface) is supplied, instead of
silently returning K2=0 as if free field. All-None remains the
legitimate free-field K2=0 path.
Document frequencies on the ReverberationSoundPowerResult row and
negative_band as a standalone per-band boolean field on the
SoundPowerIntensityResult row across docs/api-reference.md and the
EN/ES site twins.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Sound power determination and building acoustics II: ISO 3744/3746, ISO 3741, ISO 9614-2, ISO 16283-2 + ISO 717-2, ISO 354 (#84)
* feat: ISO 3744/3746 sound power from pressure measurements
Add sound_power.py: sound power level of a noise source from sound
pressure levels over an enveloping measurement surface, ISO 3744:2010
(engineering, grade 2) and ISO 3746:2010 (survey, grade 3).
- sound_power_pressure(): per-band LW and A-weighted LWA from an
(NM, NB) level array over a hemisphere (radius) or parallelepiped
(dimensions + distance); surface area from the standard's closed forms
for 1/2/3 reflecting planes; energy average (Eq. 12), background K1
(Eq. 16), environmental K2 (Eq. A.2), apparent directivity index
(Eq. 7), expanded uncertainty.
- measurement_positions(): normative Annex B coordinates
(Tables B.1/B.2/B.3), grade- and plane-dependent subsets.
- background_noise_correction(): K1 with engineering/survey criteria.
- environmental_correction(): K2 from A, Sabine T+V, or alpha*Sv.
- SoundPowerResult (frozen dataclass), SoundPowerWarning.
Tests: 24 cases incl. monopole-over-plane exact LW recovery, radius
independence, K1/K2 closed forms, LWA via Annex E, and validations.
make check green (543 passed, 12 skipped); ruff/bandit/mypy --strict clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: directivity index under background correction, citation hygiene
Broadband K1 for the apparent directivity index was collapsing to the
surface-area term 10*lg(S/S0) (~+22 dB inflation with background
correction). Compute the true energy-based broadband K1 (per-band Eq. 16
aggregated over bands) so DI is correct in both paths.
Also: multi-band sound_power_level_a without frequencies now returns NaN
(A-weighting needs band centres); dropped PDF page-number citations from
Annex B/E table comments; corrected the survey parallelepiped position
citation to ISO 3746 clause C.1 / Figure C.7.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 3741 reverberation-room sound power
Add the ISO 3741:2010 precision (grade 1) methods for determining sound
power in a reverberation test room, in a new sound_power_reverberation
module:
- sound_power_reverberation() — direct method (Eq. 20): LW from the mean
room SPL and the Sabine equivalent absorption area A = (55,26/c)(V/T60),
with the full normative bracket: 10 lg(A/A0), 4,34*(A/S), the Waterhouse
boundary term 10 lg(1 + S c/(8 V f)), the reference-quantity (C1) and
radiation-impedance (C2) meteorological corrections and the -6 dB
constant. Speed of sound c = 20,05*sqrt(273 + theta).
- sound_power_comparison() — comparison method (Eq. 21) with a reference
sound source: LW = LW(RSS) + (Lp(ST) - Lp(RSS) + C2).
- ReverberationSoundPowerResult frozen dataclass; frequency-dependent
background correction K1 (Eq. 14, same formula as ISO 3744) with the
precision-grade 6/10 dB criteria; A-weighted total via Annex F.
Reuses the ISO 3744 energy-average and A-weighting helpers. Tests cover
exact inversion of Eq. (20), the Waterhouse term vanishing at high
frequency, the comparison method exact by construction, and synergy with
room_parameters T30 feeding the direct method.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 3741 room-qualification advisories and validation ordering
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9614-2 sound power by intensity scanning
Add sound_power_intensity() computing per-band LW = 10lg(sum Pi/P0) from
per-segment signed normal intensity and areas (partial power Pi = <In,i>*Si),
the scanning-method field indicators FpI (Eq. A.1) and F+/- (Eq. A.2), the
repeatability criterion 3, and the achieved engineering/survey grade per band
(Annex B criteria 1-3). Negative total power flags bands as non-determinable
(clause 9.2); negative partial power raises SoundPowerWarning. Reuses
dynamic_capability_index for Ld = dpI0 - K (Table 1).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 16283-2 impact sound insulation and ISO 717-2 ratings
Add field impact sound insulation (ISO 16283-2, tapping machine) and the
ISO 717-2 single-number weighted impact rating with the CI spectrum
adaptation term, reusing the verified reference-curve shift engine.
- impact_insulation(li, t2, *, volume=None, t0=0.5): per-band
L'nT = Li - 10 lg(T/T0) (Formula (1)) and L'n = Li + 10 lg(A/A0) with
A = 0,16 V/T, A0 = 10 m2 (Formula (2)); positions energy-averaged.
- weighted_impact_rating(values_by_band, bands=None): Ln,w / L'n,w /
L'nT,w (Clause 4.3, octave -5 dB rule) and CI (Clause A.2.1, energetic
sum 100-2500 / 125-2000). Impact unfavourable deviations (measurement
exceeds reference, sign opposite to airborne) reduce to the airborne
search on negated curves, so _best_shift is reused verbatim.
- _best_shift: tolerance absorbs float noise at the exact bound (sums are
true multiples of 0,1 dB); airborne behaviour unchanged.
Verified against ISO 717-2 Annex C worked examples (Table C.1 Ln,w=79,
CI=-11; Table C.3 octave Ln,w=54, CI=0) and an independent brute-force
shift search on 10 000 random curves per band set. 2013 and 2020 editions
are identical for all implemented maths (2020 only adds Annex D rubber
ball, out of scope).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 354 sound absorption in a reverberation room
Add phonometry.sound_absorption implementing BS EN ISO 354:2003:
- absorption_area(t60, volume, ...): equivalent sound absorption area
A = 55,3*V/(c*T) - 4*V*m (Eq. 5/7), speed of sound from Eq. (6).
- absorption_coefficient(t1, t2, volume, sample_area, ...): alpha_s =
(A2-A1)/S built from Eq. (8)/(9), per-measurement c1/c2, unclamped
(alpha_s > 1 valid, clause 3.7 NOTE 2); warns when T2 >= T1.
- attenuation_from_alpha: ISO-354 conversion m = alpha/(10 lg e) from an
ISO 9613-1 attenuation coefficient (m deferred to ISO 9613-1, default 0).
Composes directly with room_parameters() T20/T30 outputs. 25 tests
(exact T->A->T inversion, synthetic-alpha recovery, zero-m air term,
two-temperature Eq. (8), range validation, room_parameters synergy).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 354 room and sample-size advisories
Add advisory AbsorptionWarning (never raising; result still returned) for
setup conditions outside ISO 354:2003 limits:
- Room volume below the 150 m3 minimum of clause 6.1.1, in both
absorption_area and absorption_coefficient.
- Sample area outside the clause 6.2.1.1 range 10 <= S <= 12 m2, with the
upper limit scaled by (V/200)^(2/3) when V > 200 m3, in
absorption_coefficient.
The volume advisory is emitted once by absorption_coefficient and suppressed
in its internal absorption_area calls to avoid duplicate warnings.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: deferred review minors — airborne brute-force net, advisory consistency, labels
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: sound power guide, impact and absorption sections, theory and API (EN)
Add a "Sound Power" guide covering the three LW routes (ISO 3744/3746
enveloping surface, ISO 3741 reverberation room with Waterhouse/C1/C2,
ISO 9614-2 intensity scanning), with a method-comparison table, executed
snippets and parameter tables. Extend the Room & Building Acoustics guide
with impact sound insulation (ISO 16283-2 + ISO 717-2, L'nT/L'n, tapping
machine, CI) and sound absorption in a reverberation room (ISO 354).
Add the matching theory sections (K1/K2, Waterhouse, C1/C2 derivations;
impact sign conventions; Sabine absorption) and API-reference rows for all
new public names. Update the landing standards count to 26, the sidebar,
README highlights/table, CHANGELOG and llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: sound-power and impact figures and diagrams
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: include the sound-power guide in llms coverage
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: reposition Ln,w annotation in the impact figure
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de potencia sonora, impacto y absorción
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: final-review findings — comparison K1 reporting, citations, knee unification, docs attribution
- sound_power_comparison now reports the test-source K1 actually applied
per band (zero when no background), matching the dataclass docstring;
LW unchanged.
- Replace gitignored notes-iso3744-3746.md citation in the DI comment with
the standard reference (ISO 3744:2010, 3.24 / Eq. 7 context).
- Unify the K1 zeroing knee to the >= form (ΔLp >= 15 -> no correction).
- Broaden SoundPowerWarning docstring to name all emitters (ISO 3744/3746,
ISO 3741, ISO 9614-2).
- Fix docs misattributing K2 to ISO 3741 (ISO 3744 K2 vs ISO 3741 absorption
term) across EN/ES room-acoustics guides.
- EN impact-setup diagram uses dot decimals (0.16); regenerate the 4 variants.
- CHANGELOG: label the ISO 717-2 Annex C value Ln,w = 79.
- Warn-after-validate in sound_power_comparison; "is raised" -> "is emitted".
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 84 review round 1 — per-band DI and K2, sweep-reversal repeatability, validation and broadcast ergonomics
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 84 round 2 — partial room-data validation, result-field docs
Raise a clear ValueError in environmental_correction (and its
sound_power_pressure pass-through) when exactly one member of a
room-data pair (reverberation_time/room_volume or
mean_absorption_coefficient/room_surface) is supplied, instead of
silently returning K2=0 as if free field. All-None remains the
legitimate free-field K2=0 path.
Document frequencies on the ReverberationSoundPowerResult row and
negative_band as a standalone per-band boolean field on the
SoundPowerIntensityResult row across docs/api-reference.md and the
EN/ES site twins.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Audit-driven accuracy, robustness and didactic improvements across the library (#82)
* fix: audit wave A — inter-sample LCpeak, cheby2 class-1 default, 144 kHz weighting target
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave A — decay validity thresholds, variant sharpness anchors, N5 phase, STIPA warning, Farina guard
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: audit wave B — self-contained snippets, tables, sharpness/open-plan figures, orphans
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave C — remaining minors and optimizations across the library
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: document the audit-wave parameters across guides and API tables
Cover the public parameters added in the audit fix waves across all three
documentation trees (docs/, site EN, site ES):
- lc_peak(..., oversample=8): inter-sample peak recovery (levels guide + API)
- calculate_sensitivity(..., narrowband=False): coherent tone estimator
(calibration guide + API)
- sound_intensity(..., bias_correct=False): finite-difference bias correction
(intensity guide + API)
- room_parameters/decay_curve(..., zero_phase=False): 125 Hz short-T bias
(room-acoustics guide + API)
- OctaveFilterBank/octavefilter attenuation default 60 -> 72 and cheby2
class-1 note (filter-banks guide + API)
- STIPA < 15 s UserWarning (psychoacoustics guide + API)
Also refresh dependent behaviour notes: ln_levels attack skip 2*tau -> 5*tau,
T20/T30 validity thresholds 35/45 -> 46/54 dB, and the zero-phase broadband
~0.2-0.3 dB band-level caveat. Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: truthfulness fixes from the final audit-branch review
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: 2000 Hz, not 2 ms, in the N5/N10 docstring
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 82 review round 1 — bias-correction cutoff, parabolic-peak guard, complexity, snippet self-containment
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Audit-driven accuracy, robustness and didactic improvements across the library (#82)
* fix: audit wave A — inter-sample LCpeak, cheby2 class-1 default, 144 kHz weighting target
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave A — decay validity thresholds, variant sharpness anchors, N5 phase, STIPA warning, Farina guard
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: audit wave B — self-contained snippets, tables, sharpness/open-plan figures, orphans
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave C — remaining minors and optimizations across the library
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: document the audit-wave parameters across guides and API tables
Cover the public parameters added in the audit fix waves across all three
documentation trees (docs/, site EN, site ES):
- lc_peak(..., oversample=8): inter-sample peak recovery (levels guide + API)
- calculate_sensitivity(..., narrowband=False): coherent tone estimator
(calibration guide + API)
- sound_intensity(..., bias_correct=False): finite-difference bias correction
(intensity guide + API)
- room_parameters/decay_curve(..., zero_phase=False): 125 Hz short-T bias
(room-acoustics guide + API)
- OctaveFilterBank/octavefilter attenuation default 60 -> 72 and cheby2
class-1 note (filter-banks guide + API)
- STIPA < 15 s UserWarning (psychoacoustics guide + API)
Also refresh dependent behaviour notes: ln_levels attack skip 2*tau -> 5*tau,
T20/T30 validity thresholds 35/45 -> 46/54 dB, and the zero-phase broadband
~0.2-0.3 dB band-level caveat. Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: truthfulness fixes from the final audit-branch review
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: 2000 Hz, not 2 ms, in the N5/N10 docstring
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 82 review round 1 — bias-correction cutoff, parabolic-peak guard, complexity, snippet self-containment
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
Advanced psychoacoustics (ISO 532-2/3 Moore-Glasberg, ECMA-418-2 Sottek loudness/tonality/roughness) + numerical conformance report (#87)
* feat: ECMA-418-2 Sottek Hearing Model loudness and auditory front-end
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic loudness metric and
its shared auditory front-end (Sottek Hearing Model):
- Clause 5 front-end: outer/middle-ear cascade (5.1.3, Table 1), 53-band
gammatone-like auditory filter bank (5.1.4), band-dependent segmentation
(5.1.5, Table 4), rectification/RMS (5.1.6-5.1.7), compressive nonlinearity
(5.1.8, Formula 23, Table 2) and threshold in quiet (5.1.9, Table 3).
- Clause 6.2.2-6.2.7 ACF-based tonal/noise specific loudness (reusable by the
later tonality/roughness metrics).
- Clause 8 assembly: tonal/noise power average, average specific loudness,
time-dependent and single representative loudness value.
The front-end helpers are factored for reuse. Calibration verified: a 1 kHz
40 dB SPL sinusoid yields 0.996 sone_HMS (target 1.0). Adds a frozen
EcmaLoudness result with lazy .plot(), exports, and 12 tests.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 tonality (Sottek Hearing Model)
Implements the psychoacoustic tonality metric of ECMA-418-2:2025
(Clause 6.2.8-6.2.11) on top of the committed Sottek front-end and
ACF tonal/noise decomposition.
- tonality_ecma() -> EcmaTonality with the single value T (Formula 63),
average specific tonality T'(z) (Formula 53), tonal frequencies
f_ton,z(z) (Formula 55), time-dependent tonality T(l) (Formula 61)
and its frequency f_ton(l) (Formula 62); optional user band [f_low,f_high].
- Full Clause 6.2.3 band averaging with cross-block-size-group ACF
recomputation (loudness keeps its simplified, valid-for-loudness path;
boundary marker added there). Loudness 0.996 sone guard unchanged.
- _tonal_estimate now also returns the DFT-peak tonal frequency, with the
argmax restricted to the lower DFT half to avoid Nyquist-mirror aliasing.
- .plot() via _plotting.plot_ecma_tonality; exports in __init__.
- 13 conformance tests: 1 kHz/40 dB -> 1 tu_HMS calibration, pure tone
>> noise, f_ton tracks the tone, silence -> 0, band restriction.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ECMA-418-2 roughness (Sottek Hearing Model)
Implement the ECMA-418-2:2025 (4th ed.) psychoacoustic roughness metric
(Clause 7), the third Sottek Hearing Model output and new library
functionality. Reuses the Clause 5 auditory front-end from loudness_ecma
(ear filter, 53-band gammatone bank, specific basis loudness) and adds the
roughness-specific chain: start-only zero-padding and fixed 16384/4096
segmentation (5.1.2.2, 5.1.5.2, 7.1.1), the Hilbert envelope with x32
downsampling to 1500 Hz (7.1.2), the scaled envelope power spectrum
(7.1.3), two-step noise reduction (7.1.4), the four-stage spectral
weighting with quadratic-fit modulation-rate refinement, bias correction,
high/low modulation-rate weighting and fundamental-rate estimation
(7.1.5), interpolation to 50 Hz with the distribution-dependent nonlinear
transform, tabulated calibration c_R and asymmetric smoothing (7.1.7), and
the R'(z) / R(l50) / 90th-percentile aggregation (7.1.8-7.1.10).
Uses the standard's tabulated c_R = 0.0180685 (Formula 104), not
reverse-fit: the 1 kHz / 70 Hz / m=1 / 60 dB calibration signal yields
R = 1.0735 asper (near-but-not-exactly 1). OCR-ambiguous formulae
(66, 71, 78-81) cross-checked against the SQAT MATLAB reference; the
optional entropy weighting (7.1.6, needs RPM telemetry) is documented but
not implemented.
Adds roughness_ecma()/EcmaRoughness, a two-panel .plot(), exports, a
10-test TDD suite and plan/notes-ecma418-2-roughness.md. Full suite
752 passed / 12 skipped; loudness (0.996) and tonality (0.99993) oracles
unchanged (both modules untouched).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* test: pin ECMA-418-2 roughness calibration value and document clean-room variance
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-2 Moore-Glasberg loudness
Add the stationary Moore-Glasberg loudness model of ISO 532-2:2017 as a
third loudness method alongside the Zwicker (ISO 532-1) and Sottek
(ECMA-418-2) implementations. The full Clause 7 chain is implemented:
fixed outer/middle-ear transfer (Table 1), the level-dependent roex
auditory filter bank and excitation pattern on the ERB-number scale
(Formulae 1-6), the compressive specific-loudness transform (Formulae
7-9, Tables 2-4, C=0.0617 sone/Cam), binaural inhibition (Formulae
10-13) and integration to total loudness with the phon mapping of
Table 5.
Public API: loudness_moore_glasberg_from_spectrum (exact 5.2/5.4),
loudness_moore_glasberg_from_third_octave (29 bands, 5.5) and
loudness_moore_glasberg (signal wrapper), returning a frozen
MooreGlasbergLoudness with .plot() over the ERB-number scale.
Validated against the Annex B reference signals (tones, white/pink
noise, multi-tone complexes, tone-in-noise) to ~1-4%, well inside the
standard's 2.8 phon expanded uncertainty; the definitional anchor
(1 kHz/40 dB/free/binaural = 1.000 sone) holds end-to-end from the
tabulated constant. 50 new tests; full suite 802 passed / 12 skipped.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 532-3 Moore-Glasberg-Schlittenlacher time-varying loudness
Add loudness_moore_glasberg_time() implementing the ISO 532-3:2023 method:
a six-FFT multi-resolution running short-term spectrum (clause 7.3) feeding
the ISO 532-2 excitation and specific-loudness model on the 0.25 Cam grid
with the 532-3 constants (C=0.063, Tables 2/4, E_THRQ/E0=2.307), integrated
by asymmetric attack/release smoothing into short-term (clauses 7.6-7.8) and
long-term (clause 7.9) loudness traces, with peak long-term loudness,
percentiles and a lazy STL/LTL .plot().
Validated against the Annex C.1 tone table (1 kHz 10-80 dB, 3 kHz, 4 kHz,
100 Hz and monaural earphone) to within 0.4 phon, well inside the 2.8 phon
expanded uncertainty; the 1 kHz/40 dB anchor is exact at 1.000 sone. The
ISO 532-2 and ECMA-418-2 oracles are unchanged (831 passed / 12 skipped).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-3 per-ear LTL and Formula-14 kernel width (dichotic path)
Two transcription errors in the ISO 532-3:2023 dichotic (per-ear) binaural
path of the time-varying Moore-Glasberg-Schlittenlacher module:
1. Clause 7.9: the long-term loudness must be computed per ear by the
attack/release averager (Formulae 18-21) and then summed. The code ran a
single AGC on the binaural sum S'_L + S'_R; because the attack/release
branch is nonlinear, AGC(S'_L) + AGC(S'_R) != AGC(S'_L + S'_R) for dichotic
input. Keep two LTL states and sum the two long-term loudnesses. For
diotic/monaural the averager is linear in the (scaled) input so the result
is bit-for-bit identical to the old sum-then-AGC.
2. Formula (14)/(15): the smoothing weight is exp(-(B*Di)^2) with B = 0.08 and
Di in Cam. The code evaluated exp(-((0.08 * Di/0.1)^2)) = exp(-(0.8*Di)^2),
a Gaussian ~10x too narrow, and truncated the taps at +/-1.75 Cam. Remove
the spurious /0.1 and widen the kernel to the standard's +/-18 Cam (145 taps
on the 0.25-Cam grid). For diotic/monaural the L/R excitation ratio is 1
regardless of the kernel, so all oracles are unaffected.
No current validation signal exercises the dichotic path (Annex C signals are
diotic/monaural), so the 532-3 tone oracle and every other oracle remain
byte-identical; added focused dichotic tests plus a byte-identical anchor pin.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: numerical conformance report in CI PR comment
Reframe the PR comment into a persistent numerical conformance report:
each implemented standard -> normative expected value/range -> computed
result -> pass/fail with deviation, grouped by domain.
- scripts/conformance_report.py: registry of 21 checks across 17 standards
and 6 domains (filters/weightings, levels/dosimetry, psychoacoustics,
speech, intensity/power, room/building). Emits a headline summary, a
"Numerical validation - filters & weightings" section (per-architecture
IEC 61260-1 class margins + A/C/G weighting deviation vs the normative
curve) and one conformance table per domain. Deterministic, ~2.7 s.
- tests/reference_data.py: single source of truth for the shared normative
tables (IEC 61672-1 Table 3, ISO 7196 Table 2, ISO 717-1 Annex C R),
imported by both the tests and the report so they cannot drift.
- tests/test_conformance_report.py: smoke test asserting every registered
check passes and the Markdown is well-formed.
- comment_pr.py / workflow: conformance report leads the comment; the
test/coverage table is collapsed below. Retire benchmark_filters.py
(numerical content absorbed into the report).
make check green (740 passed, 12 skipped); ruff, mypy --strict, bandit clean.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: block-A review minors — docstring notes and tonality band-range
Four Minor findings from the plan-17 block-A psychoacoustics reviews. No
change to any validated numeric output; all five oracles stay byte-identical.
1. ISO 532-2 _source_levels: docstring note that clause 7.4 specifies a roex
weighting for X where the code uses a rectangular +/-ERB_n/2 window —
identical for isolated tones, immaterial for the Annex B broadband cases.
2. ISO 532-2 Formula-10 binaural smoothing kernel: the PDF (clause 8.1) states
"Di is changed in steps of 0,1" over -18..+18 Cam; the code confused the
0.1-Cam index offset with Di-in-Cam (the same /0.1 width bug just fixed in
532-3 Formula-14), making the Gaussian 10x too narrow and truncating to
+/-1.8 Cam. Widen to +/-18 Cam (361 taps) with Di = tap*_I_STEP. Provably
inert: all Annex B cases are diotic (ratio 1) or monaural (unit inhibition).
3. ECMA tonality _band_range: implement the exact Formulae 56-57 edge-midpoint
boundary conditions instead of a centre-frequency threshold; only affects
the optional user-band feature. Added a user-band edge test.
4. ECMA tonality reach: comment that the _CBF-1-band upper-edge term is a dead
defensive guard (N_B>0 only for low bands 0..24, so it never limits reach).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* chore: move conformance design doc out of repo root
* feat: clearer conformance report (value-vs-range) + block-A checks + audit fixes
Table 1 (filters): show, per architecture, the measured relative attenuation
at the binding band vs the class-1 limit it must clear (value + range, not
just a margin). Replace the "none" verdict (read as a failure) with a
"By design (ripple/soft rolloff)" label + footnote; Butterworth (default) and
Chebyshev-II are the only mask-compliant architectures. The measured value and
limit are re-derived with the public class_limits on the same designed SOS; a
smoke-test guard asserts the re-derived class-1 margin matches
verify_filter_class (single source of truth).
Table 2 (weightings): separate the informational max-deviation-from-nominal
(at a frequency extreme with wide, asymmetric tolerance) from the compliance
margin at the binding frequency, where the deviation, the +/- tolerance band
and the headroom are co-located so "value vs range" is unambiguous and in-spec.
Audit: verified all 21 checks (expected value/range vs the standard, real
library call, honest tolerance). One fix - ISO 226 anchored at 60 phon @ 2 kHz
-> 60 dB, a genuine Table B.1 value but coincidentally equal to the phon number
(reads as the trivial 1 kHz identity); re-anchored to the non-coincidental
60 phon @ 100 Hz -> 78.5 dB, sourced from reference_data. No library bug found.
Block-A psychoacoustics (+5, single-source expected in reference_data):
ECMA-418-2 loudness (1 kHz/40 dB -> 1 sone_HMS, c_N), tonality (-> 1 tu_HMS,
c_T), roughness (1 kHz/70 Hz/m=1/60 dB -> clean-room 1.0735 asper with a note
that the standard target is 1.0); ISO 532-2 (1 kHz/40 dB -> 1 sone, C=0.0617);
ISO 532-3 (steady 1 kHz/40 dB -> peak LTL 1 sone). 26/26 checks, 20 standards.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: versioned docs/CONFORMANCE.md with make target, CI staleness check and README link
Persist the numerical conformance report as a committed, versioned file so
every reader sees the library's per-standard validation (expected vs computed)
without running CI.
- docs/CONFORMANCE.md: committed report (26/26 checks, 6 domains, 20 standards)
with an auto-generated "do not hand-edit" header linking the standards docs.
- scripts/conformance_report.py: add a --file-header flag that emits that header
for the committed file (PR-comment body stays header-free); coarsen the
informational delta column to 3 decimals so BLAS/FFT-dependent sub-milli
residuals do not churn the file (status/computed columns keep regressions
visible).
- Makefile: `make conformance` regenerates the file; `make install-hooks`
installs the optional pre-commit hook.
- CI: new fast `conformance` job (runtime deps only) fails a PR if the committed
file is stale, telling the author to run `make conformance` and commit.
- hooks/pre-commit: opt-in convenience hook, scoped to src/scripts/reference
changes; CI is the enforcement.
- README + CONTRIBUTING: link and document the generated report.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced psychoacoustics guide, theory, API (EN)
Document the plan-17 block-A advanced psychoacoustics models: Moore-Glasberg
loudness (ISO 532-2/532-3), Sottek Hearing Model loudness, tonality and
roughness (ECMA-418-2:2025).
- Psychoacoustics guide: new "Advanced loudness & sound-quality models"
section with a model-comparison table, executed snippets, parameter tables,
res.plot() one-liners and figure-code collapsibles (docs + site twin,
code blocks byte-identical).
- Theory: excitation-pattern/specific-loudness chain, Sottek front-end,
ACF tonality and envelope-modulation roughness (GitHub-safe math).
- API reference: rows for the 5 functions (+ 2 MG spectral entry points) and
5 result dataclasses with their .plot().
- README, EN landing (standards 26 -> 29: ISO 532-2, ISO 532-3, ECMA-418-2),
CHANGELOG Unreleased/Added, llms regen.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: advanced-psychoacoustics figures (loudness models, Sottek, tonality/roughness, time-varying)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: gemelas ES de psicoacústica avanzada (Moore-Glasberg, Sottek, tonalidad, aspereza)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 532-2 signal-input path builds a narrowband spectrum (pure-tone loudness)
The loudness_moore_glasberg(x, fs) wrapper routed the signal through the
one-third-octave method, which smeared a pure tone across a whole 1/3-octave
band and used a coherent-gain window normalisation that over-counted power by
1.76 dB. A calibrated 1 kHz tone at 40 dB SPL therefore returned 1.327 sone
instead of the definitional anchor of 1.000 sone.
ISO 532-2 is spectrum-based and its exact input (clauses 5.2/5.4) is a set of
discrete sinusoidal components; the excitation pattern (Formula 5) is computed
per component, so a tone must enter as a single line. The wrapper now forms the
signal's narrowband (FFT) line spectrum with a power-preserving (Parseval)
window normalisation and feeds it to loudness_moore_glasberg_from_spectrum. A
1 kHz/40 dB tone now yields 1.0001 sone / 40 phon and multi-tone signals match
the exact spectrum path. The validated spectrum and third-octave paths are
byte-identical.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: correct MG signal-path mechanism text and presentation default
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 87 review round 1 — float-equality guards, 532-3 FFT window length, tonality short-signal, report/figure fixes
- roughness_ecma: replace three float == 0.0 checks with abs>eps guards
(SonarCloud gate); roughness oracle byte-identical (1.0735).
- loudness_moore_glasberg_time: per-window FFT length (>= window length) so
the 64 ms window is not truncated at 44.1/48 kHz (was ~0.74 dB low in the
20-80 Hz band). 32 kHz anchor byte-identical (n_max 1.0000044713237626).
- tonality_ecma: short signals fall back to averaging all blocks (matches
loudness_ecma), not the final block only; validated anchor unaffected.
- conformance_report: document deliberate 273.0 (speed of sound) vs 273.15
(C1/C2) split matching the library; complete bind_side docstring.
- generate_graphs: roughness annotation uses computed peak fmod; Sottek
metrics use tu_HMS/sone_HMS units; ES translations + 8 figures regenerated.
- hooks/pre-commit: drop set -e so a benign regen failure does not block the
commit (CI staleness check is the gate).
- test_iec_weighting_table3 docstring, Makefile .PHONY completion.
All 5 psychoacoustics oracles byte-identical; make check 868 passed / 12
skipped; ruff + mypy + bandit clean; conformance 26/26.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: localize decimal commas in Sottek-unit figure labels (underscore guard)
The `_` guard in `_translate_figure` skipped decimal-comma localization for
any label containing an underscore. The new Sottek/HMS unit tokens
(`sone_HMS`, `tu_HMS`, `sonios_HMS`) carry underscores, so Spanish figures
regressed to dots (`8.0`, `0.42`) instead of commas (`8,0`, `0,42`).
Drop the underscore guard: the decimal-comma regex only rewrites a bare
`digit.digit` not adjacent to more digits/dots, so unit identifiers and
version numbers (5.3.3) stay intact while genuine decimals get commas.
Mathtext is still skipped via the `$` guard. Regenerated the affected ES
figures (sottek_specific_loudness, tonality_roughness_demo).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
feat: measurement uncertainty (GUM ISO/IEC Guide 98-3:2008 + Monte Carlo Supplement 1) (#102)
Adds a `phonometry.uncertainty` module implementing the two propagation methods of the GUM:
* Law of propagation of uncertainty (ISO/IEC Guide 98-3:2008, clause 5) — `combine_uncertainty` builds the combined standard uncertainty from central-difference sensitivity coefficients, with optional input correlations (validated for shape, symmetry, unit diagonal and positive-semidefiniteness), Welch-Satterthwaite effective degrees of freedom (Annex G.4) and the expanded uncertainty U = k*uc (clause 6).
* Monte Carlo method (ISO/IEC Guide 98-3-1:2008, Supplement 1, clause 7) — `monte_carlo` propagates the input PDFs and returns the estimate, its standard uncertainty and the probabilistically symmetric coverage interval (clause 7.7).
Type B quantities from a half-width: `rectangular` (a/sqrt3), `triangular` (a/sqrt6), `u_shaped` (a/sqrt2). Results expose `.expanded()` and `.plot()` (uncertainty budget).
Validated against the Guides' worked examples: additive uc=2.0 (Suppl. 1, 9.2), coverage factor k=2.92 at p=0.99/v=16 (Table G.2), Welch-Satterthwaite v_eff=40 (Annex G.4), Monte Carlo interval [-3.88, 3.88] (Suppl. 1, 9.2.3). Conformance 79/79.
Includes the budget/Monte-Carlo figure, the two-lane GUM-vs-Monte-Carlo diagram (EN/ES, light/dark), the repo doc and the EN/ES site guides under a new "Metrology & uncertainty" section.
Audit-driven accuracy, robustness and didactic improvements across the library (#82)
* fix: audit wave A — inter-sample LCpeak, cheby2 class-1 default, 144 kHz weighting target
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave A — decay validity thresholds, variant sharpness anchors, N5 phase, STIPA warning, Farina guard
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: audit wave B — self-contained snippets, tables, sharpness/open-plan figures, orphans
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: audit wave C — remaining minors and optimizations across the library
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: document the audit-wave parameters across guides and API tables
Cover the public parameters added in the audit fix waves across all three
documentation trees (docs/, site EN, site ES):
- lc_peak(..., oversample=8): inter-sample peak recovery (levels guide + API)
- calculate_sensitivity(..., narrowband=False): coherent tone estimator
(calibration guide + API)
- sound_intensity(..., bias_correct=False): finite-difference bias correction
(intensity guide + API)
- room_parameters/decay_curve(..., zero_phase=False): 125 Hz short-T bias
(room-acoustics guide + API)
- OctaveFilterBank/octavefilter attenuation default 60 -> 72 and cheby2
class-1 note (filter-banks guide + API)
- STIPA < 15 s UserWarning (psychoacoustics guide + API)
Also refresh dependent behaviour notes: ln_levels attack skip 2*tau -> 5*tau,
T20/T30 validity thresholds 35/45 -> 46/54 dB, and the zero-phase broadband
~0.2-0.3 dB band-level caveat. Regenerated llms-full.txt.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: truthfulness fixes from the final audit-branch review
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: 2000 Hz, not 2 ms, in the N5/N10 docstring
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: PR 82 review round 1 — bias-correction cutoff, parabolic-peak guard, complexity, snippet self-containment
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf