Spanish docs: UNE terminology, decimal commas, localized snippets and theory figures (#365)
* Normalize Spanish docs prose to UNE acoustic terminology
Sweep the Spanish pages for the non-UNE forms "presion sonora",
"potencia sonora" and "intensidad sonora" and normalize the prose to
the wording used by the official Spanish editions of the standards:
"presion acustica" (UNE-EN ISO 16283-2:2016, UNE-EN ISO 10052:2005,
UNE-EN ISO 3746:2011, UNE-ISO 226:2013), "potencia acustica"
(UNE-EN ISO 3746:2011, UNE-EN ISO 4871:2010, UNE-EN ISO 9614-1:2010),
"intensidad acustica" (UNE-EN 61043:1999, UNE-EN ISO 9614-1:2010) and
"nivel de presion acustica de impactos" (UNE-EN ISO 12354-2:2018,
UNE-EN ISO 717-2:2013). 92 replacements across 32 pages, prose only:
code snippets keep the labels they share with the generated figures.
Rename the intensity guide to "Intensidad acustica (p-p)" and the
structure-borne power guide to "Potencia acustica estructural de
equipos (EN 15657)", updating every cross-link text and the theory
anchor intensidad-sonora-iec-61043 accordingly. The API sidebar group
label becomes "Potencia acustica e intensidad" via api_taxonomy.py
with the generated sidebar regenerated.
* Use decimal commas in Spanish docs prose quantities
Sweep every Spanish page for decimal points leaking into prose numbers
and switch them to the comma the locale convention calls for: the
rotorcraft validation prose (fourteen tolerances such as 0,08 dB(A) and
0,5 s), the insulation-prediction worked example (53,6 dB / 53,8 dB),
the tone-audibility Annex E tone (137,3 Hz), the Mackenzie check value
(1550,744 m/s), the EPNL, loudness and absorption fiche descriptions
(98,3 EPNdB; 8,2 sonos and 70,4 fonios; 0,60 y 0,55), the junction
constants table and the third-octave band-edge table in the signal
analysis theory page. Code snippets, inline code, math, clause numbers
and quoted source text keep their points.
* Pass language="es" in Spanish figure-reproduction snippets
The <details> blocks under each localized figure in the Spanish guides
are meant to reproduce the _es variant shown on the page, but 82 of the
one-line result .plot() calls omitted the language="es" argument and
would render English axis labels instead. Add the argument across 33
guides, matching the pattern the intensity field-indicators snippet
already used. The three lab-insulation results whose plot() does not
take a language argument (lab airborne, lab impact and the
element-normalized intensity difference) are left as they were.
* Extend the UNE terminology sweep to sound absorption
Normalize "absorción sonora" to "absorción acústica" across the
Spanish pages (36 occurrences in 14 pages, including the EN 12354-6
guide title and the theory headings), the wording of UNE-EN ISO
3746:2011 and of the equivalent absorption area in UNE-EN ISO
10140-3:2011, UNE-EN ISO 12354-2:2018 and UNE-EN ISO 16283-2:2016, and
the same term the accredited report strings already use. Also restore
the accent in the theory index anchor to the renamed intensity heading,
since heading slugs keep accented characters.
* Theory pages: illustrate each section family with existing result figures
The seven theory pages carried no figures at all. Embed sixteen of the
existing generated figures (light and dark variants, Spanish variants on
the Spanish mirrors) where the concept matches the prose: weighting
curves, time integration and the p-p intensity estimator in the signal
analysis page; equal-loudness contours, the specific-loudness pattern
and the hearing-threshold panels in perception; NC/RC rating, the
Schroeder decay and the ISO 717-1 shifted reference in rooms and
buildings; the Lden profile, ISO 9613-1 absorption and the three sound
power routes in environment and transport; the scattering coefficient
and ISO 11654 rating in materials and surfaces; and the Wk weighting and
the resonator mobility triplet in vibration. Each embed reuses the alt
text of the guide that owns the figure and adds a one-line caption.
Also give the wave-simulation section hub the reading-order paragraph
its siblings already have, linking the closed-form pages the FDTD solver
cross-checks, in both languages.
* Address review findings from the Spanish locale sweeps
Second pass fixes flagged by review: five terminology occurrences the
line-based sweep missed because the noun and adjective were split by a
line wrap or emphasis markup (sound-power, rotorcraft-noise and two
section hubs); six Spanish pages that still embedded the English figure
variant although the _es variant exists (junction-transmission,
aircraft-noise airport contour, miso-coherence,
objective-intelligibility, psychoacoustic-annoyance twice); and 19 more
language="es" arguments on figure-reproduction plot calls the first
pass missed, chained-call receivers such as room.noise_criterion(spl)
.plot() and the axes-creating first call of the two-profile
atmospheric-refraction snippet among them. The lab-insulation plot
wrappers forward keyword arguments to the rating plot, so the three
calls excluded earlier now take the argument too. All Spanish python
fences re-checked to compile and EN/ES parity re-verified.
* Carry the UNE terminology into the generated-asset Spanish strings
Apply the same evidence-based decisions to every Spanish string source
that feeds generated assets: the translation tables of
generate_graphs.py and generate_diagrams.py, the _STRINGS tables of the
plot modules and the report i18n values. 35 strings change: "presión
acústica" (UNE-EN ISO 16283-2:2016, 10052:2005, 3746:2011),
"potencia acústica" (UNE-EN ISO 3746:2011, 4871:2010, 9614-1:2010),
"intensidad acústica" (UNE-EN 61043:1999, 9614-1:2010), "absorción
acústica" (UNE-EN ISO 3746:2011) and "nivel de presión acústica de
impactos" (UNE-EN ISO 12354-2:2018). Strings the UNE editions keep are
untouched: "emisión sonora", "exposición sonora", "campo sonoro",
"fuente sonora". Only Spanish values change; the English keys stay.
The two plot-i18n test assertions that pin the axis label follow, and
the eight Spanish doc fences that deliberately mirrored the old figure
labels now mirror the new ones.
* Regenerate the Spanish figure variants with the UNE terminology
make graphs after the string-source change: 48 files, all of them _es
or _es_dark variants (24 figure/diagram pairs), picking up "presión
acústica", "potencia acústica", "intensidad acústica", "absorción
acústica" and "nivel de presión acústica de impactos" in titles and
axis labels. The English variants are untouched. The committed example
fiches need no regeneration: every example in generate_reports.py
renders in English, so no changed Spanish report string reaches them.
* Align the intensity-fiche Spanish assertions with the UNE terminology
Add random-data qualification: stationarity tests and Rice crossing statistics (#291)
* Add random-data qualification: stationarity tests and Rice crossing statistics
Add phonometry.metrology.random_data, implementing the two quantitative
pillars of Bendat & Piersol, Random Data 4e: record qualification through
nonparametric trend tests and the Rice level-crossing and peak statistics.
trend_test() runs the reverse arrangement test (Sec. 4.5.2) with the
Table A.6 acceptance regions, reproduced exactly at alpha = 0.05 for
N = 10 to 100, and an exact Mahonian p-value up to N = 100, or the runs
test about the median with the exact Wald-Wolfowitz conditional
distribution at any N. stationarity_test() applies either test to the
per-segment mean squares (or rms, mean, variance) of a single record,
the Sec. 10.3.1.1 procedure.
level_crossing_rate() counts level crossings with both slopes and
compares them with the Rice expectations N0 = 2 sqrt(m2/m0) and
Na = N0 exp(-a^2 / 2 sigma^2) taken from the record's own Welch
autospectrum moments. peak_statistics() measures the rate of local
maxima against M = sqrt(m4/m2), derives the irregularity factor
r = N0 / 2M and exposes the Rice peak-height distribution
(peak_exceedance, peak_density) interpolating Rayleigh at r = 1 and
Gaussian as r goes to 0.
Every result carries an EN/ES .plot() renderer. Add a Data qualification
guide (EN/ES) under Calibration and uncertainty with three SVG figures,
linked from the GUM and levels guides, and rename the API "Uncertainty"
section to "Uncertainty and data quality". The B&P worked examples (4.4,
5.12 to 5.14, 10.3) and the full Table A.6 alpha = 0.05 column are pinned
as conformance rows.
* docs: regenerate derived artifacts after rebasing onto main
feat: psychoacoustic fluctuation strength (ECMA-418-2:2025 clause 9, Sottek Hearing Model) (#216)
* feat: psychoacoustic fluctuation strength per ECMA-418-2:2025 Clause 9 (Sottek Hearing Model)
Clean-room implementation of the Clause 9 chain on the shared Clause 5
auditory front-end: fixed 65536/16384 segmentation, downsampled Hilbert
envelopes, envelope-dependent analysis windows with quieter-period
detection, High-resolution Spectral Analysis (least-squares window-kernel
line-pair fits with the closed-form single-pair solution), prominent-line
selection, band-pass modulation-rate weighting, damped-Newton rate fine
tuning, harmonic-complex analysis, HSA-based loudness scaling and the
50 Hz specific fluctuation strength with the tabulated calibration
constant c_F = 0.003840572.
The Clause 9 calibration signal (1 kHz, 100 % AM at 4 Hz, overall
60 dB SPL) converges to 0.9958 vacil against the defined 1 vacil without
reverse fitting; the standard's qualitative behaviours (4 Hz band-pass,
zero in the roughness domain, growth with modulation depth) are pinned in
tests together with machine-precision HSA recovery anchors and unit tests
of the analysis-window machinery. The result dataclass exposes .plot();
the renderer shares one helper with the roughness heatmap plot.
* feat(conformance): ECMA-418-2 Clause 9 fluctuation-strength calibration check
Adds the Clause 9 anchor to the Psychoacoustics domain: the 1 kHz / 4 Hz /
m=1 / overall 60 dB calibration signal must compute 1 vacil_HMS within
0.01 with the tabulated c_F (computed: 0.9931 for the 5 s check signal).
262/262 checks pass.
* feat(figures): slow vs fast modulation band-pass demo (fluctuation strength vs roughness)
One figure for the concept Clause 9 adds next to Clause 7: the Sottek
model splits envelope modulation into a slow band-pass peaking near
4-5 Hz (fluctuation strength, vacil) and a fast one peaking near 70 Hz
(roughness, asper), computed for the same 1 kHz / 100 % AM / 60 dB tone
family. Four language/theme variants; ES strings added to the
translation table.
* docs: fluctuation strength guide (EN/ES), Clause 9 errata and API reference
Extends the sound-quality guide (docs and site, English and Spanish) with
the Clause 9 metric: concept, runnable calibration example, the new
modulation band-pass figure with its double snippet, parameter table and
cross-links from the Fastl & Zwicker fluctuation-strength page. Records
four confirmed print defects of ECMA-418-2:2025 Clause 9 in docs/ERRATA.md
(Formula (127) kernel phase, Formula (144) bin offset, unit-less Newton
constants of Clause 9.1.7, broken cross-reference in the introduction).
Adds the module to the API taxonomy, regenerates the Starlight API
reference and updates the curated API table, indices and changelog.
* refactor(psychoacoustics): harden fluctuation_strength_ecma and unify vacil_HMS units
- Replace np.polyfit with the closed-form linear regression in the
Clause 9.1.3.6 validity check (per-block, per-band hot path): outputs
are bit-identical (calibration anchor 0.9931 vacil_HMS unchanged) and
the fit is about 10x faster (106 us to 11 us per call).
- Validate inputs: reject non-finite samples and a non-finite fs via the
shared require_positive helper, and clamp the internal resample length
to at least one sample.
- Preserve matplotlib's c= color alias in the shared HMS plot renderer;
the fill now uses the plotted line's resolved color.
- Unify the normative ECMA-418-2 Clause 9 unit as vacil_HMS across the
module docstrings, plot labels, guides (EN/ES), API reference, README
and changelog; plain vacil stays for the Fastl & Zwicker model. The
documented example values match fresh runs: 0.9931 (5 s), 0.9957 (8 s),
0.9958 (12 s).
- Move signal construction out of pytest.raises blocks and add tests for
non-finite signals, non-finite fs, the resample clamp and the color
alias. Regenerate the site API reference and llms files.
* fix(figures): label the fluctuation-strength annotation and axis in vacil_HMS
feat: spherical-wave ground effect and advanced barrier attenuation (#227)
* feat: spherical-wave ground effect and advanced barrier diffraction
Add phonometry.environmental.ground_barriers with the wave-acoustic
counterparts of the tabulated ISO 9613-2 ground and barrier terms.
ground_effect computes the excess attenuation over a finite-impedance
ground from the Weyl-Van der Pol spherical-wave reflection coefficient
Q = Rp + (1 - Rp) F(w), with the boundary-loss factor through the scaled
complementary error function (scipy.special.wofz) and the ground
impedance taken directly or from the Delany-Bazley / Miki porous models
of phonometry.materials.
barrier_insertion_loss adds the Kurze-Anderson closed form in the
Fresnel number, the exact rigid half-plane (flat-wedge limit of the
MacDonald / Hadden & Pierce solution), thick barriers by double-edge
diffraction, and the coherent four-path barrier on the ground combining
the source/receiver images with the spherical-wave Q. Both results
expose .plot() (excess attenuation and insertion loss vs frequency).
Clean-room from Attenborough & Van Renterghem (2021), Salomons (2001)
Appendix D and Bies, Hansen & Howard (2017). Tests anchor the analytic
limits (hard ground Q -> 1 and +6 dB, grazing Rp -> -1, Kurze-Anderson
5 dB at N = 0, the exact half-plane 6 dB at the shadow boundary) and
reciprocity.
* test: conformance checks for the ground effect and barriers
Add a conformance domain pinning the analytic limits of the new
wave-acoustic models: the hard-ground Q -> 1 and +6 dB enhancement, the
grazing Rp -> -1, the Kurze-Anderson 5 dB at N = 0 and its decade slope,
and the exact half-plane 6 dB at the shadow boundary. Regenerate the
committed report.
* docs: spherical ground-effect concept figure (four ground types)
Add the excess-attenuation curve for fresh snow, forest floor,
grassland and asphalt, showing the ground dip shifting with the
effective flow resistivity and the +6 dB hard-ground ceiling, in the
four language x theme variants.
* docs: guides and reference for the ground effect and advanced barriers
Add the EN and ES site guides and the GitHub theory page covering the
Weyl-Van der Pol spherical-wave ground effect and wave-theoretic
barriers, with a typed bibliography (Attenborough & Van Renterghem,
Salomons, Bies, Kurze & Anderson, Hadden & Pierce). Wire the module
into the API taxonomy and regenerate the API reference, sidebar and
llms files; extend the GitHub API index, references and documentation
index; and record the change in the changelog.
* refactor: address static-analysis and review feedback on the ground/barrier module
- Replace the exact float equality on the impedance guard with a relational
magnitude check (same behaviour, no float-equality smell).
- Validate the frequency vector through the shared require_positive_array
helper, removing the four duplicated "must be positive" literals, and
validate speed_of_sound at every public entry point.
- Bundle the wave-theoretic barrier field inputs into a private
_ExactBarrierSetup dataclass so the helper takes one argument instead of
fourteen.
- Route a PorousMediumResult ground impedance through the same shape and
non-zero validation as a raw array, and reject a thick barrier whose far
edge reaches or passes the receiver.
- Clip the Kurze-Anderson argument at N = -0.2 before the tanh so the
illuminated-zone branch never evaluates through the tangent poles (the
result there is already forced to 0), avoiding stray runtime warnings.
- Reword the hard-ground conformance description to drop the raw pipes that
broke the generated Markdown table, and regenerate the report.
- Tests: assert the concrete FrozenInstanceError, make the barrier
reciprocity geometry asymmetric, and cover the new thick-barrier and
speed-of-sound guards.
- Docs: restore the omitted "that" in the English guide.
Conformance oracles are unchanged (abs(Q) hard = 1, +6.02 dB, Rp -> -1,
Kurze-Anderson 5 dB at N = 0, exact half-plane 6 dB at the shadow boundary).
* refactor: cut barrier complexity and reject non-finite ground/thickness inputs
- Extract the barrier geometry validation into _validate_barrier_geometry so
barrier_insertion_loss drops below the cognitive-complexity threshold; the
behaviour and error messages are unchanged.
- Reject a non-finite ground impedance: an infinite Z is not the hard-ground
limit (that is a large finite Z) and would give inf/inf = NaN in the
reflection coefficient, so it is refused alongside the existing zero check.
- Validate a thick-barrier top width through require_positive, which rejects
NaN and infinities that slipped past the bare "<= 0" comparison.
- Add regression tests for the non-finite impedance and NaN/inf thickness
inputs.
Conformance oracles are unchanged (abs(Q) hard = 1, +6.0205 dB,
Kurze-Anderson 5 dB at N = 0, exact half-plane 5.7932 dB at the shadow
boundary).
* test: hoist the impedance construction out of the raises block
feat: psychoacoustic fluctuation strength (ECMA-418-2:2025 clause 9, Sottek Hearing Model) (#216)
* feat: psychoacoustic fluctuation strength per ECMA-418-2:2025 Clause 9 (Sottek Hearing Model)
Clean-room implementation of the Clause 9 chain on the shared Clause 5
auditory front-end: fixed 65536/16384 segmentation, downsampled Hilbert
envelopes, envelope-dependent analysis windows with quieter-period
detection, High-resolution Spectral Analysis (least-squares window-kernel
line-pair fits with the closed-form single-pair solution), prominent-line
selection, band-pass modulation-rate weighting, damped-Newton rate fine
tuning, harmonic-complex analysis, HSA-based loudness scaling and the
50 Hz specific fluctuation strength with the tabulated calibration
constant c_F = 0.003840572.
The Clause 9 calibration signal (1 kHz, 100 % AM at 4 Hz, overall
60 dB SPL) converges to 0.9958 vacil against the defined 1 vacil without
reverse fitting; the standard's qualitative behaviours (4 Hz band-pass,
zero in the roughness domain, growth with modulation depth) are pinned in
tests together with machine-precision HSA recovery anchors and unit tests
of the analysis-window machinery. The result dataclass exposes .plot();
the renderer shares one helper with the roughness heatmap plot.
* feat(conformance): ECMA-418-2 Clause 9 fluctuation-strength calibration check
Adds the Clause 9 anchor to the Psychoacoustics domain: the 1 kHz / 4 Hz /
m=1 / overall 60 dB calibration signal must compute 1 vacil_HMS within
0.01 with the tabulated c_F (computed: 0.9931 for the 5 s check signal).
262/262 checks pass.
* feat(figures): slow vs fast modulation band-pass demo (fluctuation strength vs roughness)
One figure for the concept Clause 9 adds next to Clause 7: the Sottek
model splits envelope modulation into a slow band-pass peaking near
4-5 Hz (fluctuation strength, vacil) and a fast one peaking near 70 Hz
(roughness, asper), computed for the same 1 kHz / 100 % AM / 60 dB tone
family. Four language/theme variants; ES strings added to the
translation table.
* docs: fluctuation strength guide (EN/ES), Clause 9 errata and API reference
Extends the sound-quality guide (docs and site, English and Spanish) with
the Clause 9 metric: concept, runnable calibration example, the new
modulation band-pass figure with its double snippet, parameter table and
cross-links from the Fastl & Zwicker fluctuation-strength page. Records
four confirmed print defects of ECMA-418-2:2025 Clause 9 in docs/ERRATA.md
(Formula (127) kernel phase, Formula (144) bin offset, unit-less Newton
constants of Clause 9.1.7, broken cross-reference in the introduction).
Adds the module to the API taxonomy, regenerates the Starlight API
reference and updates the curated API table, indices and changelog.
* refactor(psychoacoustics): harden fluctuation_strength_ecma and unify vacil_HMS units
- Replace np.polyfit with the closed-form linear regression in the
Clause 9.1.3.6 validity check (per-block, per-band hot path): outputs
are bit-identical (calibration anchor 0.9931 vacil_HMS unchanged) and
the fit is about 10x faster (106 us to 11 us per call).
- Validate inputs: reject non-finite samples and a non-finite fs via the
shared require_positive helper, and clamp the internal resample length
to at least one sample.
- Preserve matplotlib's c= color alias in the shared HMS plot renderer;
the fill now uses the plotted line's resolved color.
- Unify the normative ECMA-418-2 Clause 9 unit as vacil_HMS across the
module docstrings, plot labels, guides (EN/ES), API reference, README
and changelog; plain vacil stays for the Fastl & Zwicker model. The
documented example values match fresh runs: 0.9931 (5 s), 0.9957 (8 s),
0.9958 (12 s).
- Move signal construction out of pytest.raises blocks and add tests for
non-finite signals, non-finite fs, the resample clamp and the color
alias. Regenerate the site API reference and llms files.
* fix(figures): label the fluctuation-strength annotation and axis in vacil_HMS
feat: image-source room impulse responses and steady-state room field (#229)
* feat(room): image-source RIR and steady-state room field
Add two rectangular-room field models to phonometry.room.
image_source (A1) synthesises the room impulse response of a shoebox by
mirroring a point source in its walls (Kuttruff Room Acoustics 4.1,
Eqs. 4.4-4.6; Vorlander Auralization 11.4; Allen & Berkley 1979): the
direct sound plus one delayed, attenuated impulse per image, amplitude
prod(R_wall^count) * exp(-m r / 2) / (4 pi r) with the pressure reflection
factor R = sqrt(1 - alpha) and delay r / c. It returns a broadband or
per-octave-band RIR plus the exact sub-sample reflection table (times,
distances, orders, amplitudes, image positions), with audible_image_count
(Eq. 9.23) and reflection_density (Eq. 4.6) helpers and a reflectogram
.plot().
steady_field (A4) predicts the steady-state level
Lp = Lw + 10 lg(Q/(4 pi r^2) + 4/R) with the room constant
R = S alpha/(1 - alpha) (Bies Engineering Noise Control 6.43-6.44), the
critical distance sqrt(Q R / 16 pi) and the Schroeder frequency
2000 sqrt(T/V) (Kuttruff 3.44, 5.44). SteadyFieldResult carries the
direct/reverberant/total split versus distance with a .plot().
* test(room): image-source and steady-state field oracles
Pin the exact reflection geometry (direct and first-reflection delay and
amplitude), the audible image count (Kuttruff Eq. 9.23), the reflection
density (Eq. 4.6), per-band and per-wall assembly, and the domain guards.
Cross-check the Eyring reverberation time recovered from the synthetic
decay in the near-cubic limit, and use the 2D FDTD solver of
phonometry.simulation as an independent oracle for the rigid-wall echo
delay and the uniform-damping T60. Verify the closed-form room constant,
the critical distance as the direct/reverberant crossover, the Schroeder
frequency and the steady-state level.
* test(conformance): image-source and steady-state field checks
Add seven Room-acoustics anchors: the image-source direct-sound amplitude
and delay, the audible image count (Kuttruff Eq. 9.23), the reflection
density (Eq. 4.6), the room constant (Bies Eq. 6.44), the critical distance
as the direct/reverberant crossover, the Schroeder frequency (Kuttruff
Eq. 3.44) and the steady-state level (Bies Eq. 6.43). Register the two new
modules in the API taxonomy. 319/319 checks pass; the report regenerates
byte-identically.
* feat(figures): image-source reflectogram
Add the image-source room-impulse-response reflectogram of a 7x5x3 m room:
the direct sound with the reflection cloud coloured by reflection order,
decaying under the 1/r spreading envelope. Four language/theme variants;
Spanish strings added to the translation table. Committed as PNG (the dense
reflection cloud is far lighter than the vector form).
* docs: image-source and steady-state room field guide
New guide across the three documentation trees (GitHub docs, theory chapter
and the EN/ES site guides): the image-source room impulse response with its
reflection-count decomposition, the audible-image count and reflection
density, the reflectogram figure with its double snippet, and the
steady-state field with the room constant, critical distance and Schroeder
frequency. Cross-linked from the room-acoustics section pages and the
sidebar. Regenerates the Starlight API reference for the two new modules,
extends the curated API table, the README highlights, the bibliography
(Vorlander, Allen & Berkley) and the llms files, and records the change in
the changelog.
* fix: PR review round (Sonar gate and bot findings)
Sonar:
- S1244 (image_source.py): drop float == comparisons. The coincident
source/receiver guard and the fully-absorbing-wall mask now use <= 0.0
(both quantities are non-negative by construction, so the behaviour and
the pinned oracles are unchanged).
- S3776 (image_source_rir cognitive complexity): extract _resolve_band_maps,
_image_lattice, _image_amplitudes and _sample_ir with identical semantics;
the reflection count (1561 at order 10) and exact geometry are unchanged.
- S5778 (tests): move the array constructions out of the pytest.raises blocks.
Bot findings:
- Reflectogram renderer: guard the scatter and colorbar so a max_order=0
(direct-only) result does not crash on an empty scatter; regression test.
- reflection_density: clamp negative times so the t**2 term cannot report a
spurious positive density before the direct sound.
- steady_state_spl: rename the room-constant local to r_const to avoid
clashing with the critical distance rc.
- Tests: assert the per-band energy decay monotonically across all bands.
- Docs: language identifiers on the formula fences (MD040); describe T as the
Eyring reverberation time (not a rate) and the fitted slope as recovering
it; present the Schroeder frequency as a heuristic modal-to-diffuse
crossover rather than a hard cutoff; note the order-limited T30 in the
example. References sentence completed. llms regenerated.
* docs: correct the Bies 5th-edition year to 2017 in the bibliography
Spanish docs: UNE terminology, decimal commas, localized snippets and theory figures (#365)
* Normalize Spanish docs prose to UNE acoustic terminology
Sweep the Spanish pages for the non-UNE forms "presion sonora",
"potencia sonora" and "intensidad sonora" and normalize the prose to
the wording used by the official Spanish editions of the standards:
"presion acustica" (UNE-EN ISO 16283-2:2016, UNE-EN ISO 10052:2005,
UNE-EN ISO 3746:2011, UNE-ISO 226:2013), "potencia acustica"
(UNE-EN ISO 3746:2011, UNE-EN ISO 4871:2010, UNE-EN ISO 9614-1:2010),
"intensidad acustica" (UNE-EN 61043:1999, UNE-EN ISO 9614-1:2010) and
"nivel de presion acustica de impactos" (UNE-EN ISO 12354-2:2018,
UNE-EN ISO 717-2:2013). 92 replacements across 32 pages, prose only:
code snippets keep the labels they share with the generated figures.
Rename the intensity guide to "Intensidad acustica (p-p)" and the
structure-borne power guide to "Potencia acustica estructural de
equipos (EN 15657)", updating every cross-link text and the theory
anchor intensidad-sonora-iec-61043 accordingly. The API sidebar group
label becomes "Potencia acustica e intensidad" via api_taxonomy.py
with the generated sidebar regenerated.
* Use decimal commas in Spanish docs prose quantities
Sweep every Spanish page for decimal points leaking into prose numbers
and switch them to the comma the locale convention calls for: the
rotorcraft validation prose (fourteen tolerances such as 0,08 dB(A) and
0,5 s), the insulation-prediction worked example (53,6 dB / 53,8 dB),
the tone-audibility Annex E tone (137,3 Hz), the Mackenzie check value
(1550,744 m/s), the EPNL, loudness and absorption fiche descriptions
(98,3 EPNdB; 8,2 sonos and 70,4 fonios; 0,60 y 0,55), the junction
constants table and the third-octave band-edge table in the signal
analysis theory page. Code snippets, inline code, math, clause numbers
and quoted source text keep their points.
* Pass language="es" in Spanish figure-reproduction snippets
The <details> blocks under each localized figure in the Spanish guides
are meant to reproduce the _es variant shown on the page, but 82 of the
one-line result .plot() calls omitted the language="es" argument and
would render English axis labels instead. Add the argument across 33
guides, matching the pattern the intensity field-indicators snippet
already used. The three lab-insulation results whose plot() does not
take a language argument (lab airborne, lab impact and the
element-normalized intensity difference) are left as they were.
* Extend the UNE terminology sweep to sound absorption
Normalize "absorción sonora" to "absorción acústica" across the
Spanish pages (36 occurrences in 14 pages, including the EN 12354-6
guide title and the theory headings), the wording of UNE-EN ISO
3746:2011 and of the equivalent absorption area in UNE-EN ISO
10140-3:2011, UNE-EN ISO 12354-2:2018 and UNE-EN ISO 16283-2:2016, and
the same term the accredited report strings already use. Also restore
the accent in the theory index anchor to the renamed intensity heading,
since heading slugs keep accented characters.
* Theory pages: illustrate each section family with existing result figures
The seven theory pages carried no figures at all. Embed sixteen of the
existing generated figures (light and dark variants, Spanish variants on
the Spanish mirrors) where the concept matches the prose: weighting
curves, time integration and the p-p intensity estimator in the signal
analysis page; equal-loudness contours, the specific-loudness pattern
and the hearing-threshold panels in perception; NC/RC rating, the
Schroeder decay and the ISO 717-1 shifted reference in rooms and
buildings; the Lden profile, ISO 9613-1 absorption and the three sound
power routes in environment and transport; the scattering coefficient
and ISO 11654 rating in materials and surfaces; and the Wk weighting and
the resonator mobility triplet in vibration. Each embed reuses the alt
text of the guide that owns the figure and adds a one-line caption.
Also give the wave-simulation section hub the reading-order paragraph
its siblings already have, linking the closed-form pages the FDTD solver
cross-checks, in both languages.
* Address review findings from the Spanish locale sweeps
Second pass fixes flagged by review: five terminology occurrences the
line-based sweep missed because the noun and adjective were split by a
line wrap or emphasis markup (sound-power, rotorcraft-noise and two
section hubs); six Spanish pages that still embedded the English figure
variant although the _es variant exists (junction-transmission,
aircraft-noise airport contour, miso-coherence,
objective-intelligibility, psychoacoustic-annoyance twice); and 19 more
language="es" arguments on figure-reproduction plot calls the first
pass missed, chained-call receivers such as room.noise_criterion(spl)
.plot() and the axes-creating first call of the two-profile
atmospheric-refraction snippet among them. The lab-insulation plot
wrappers forward keyword arguments to the rating plot, so the three
calls excluded earlier now take the argument too. All Spanish python
fences re-checked to compile and EN/ES parity re-verified.
* Carry the UNE terminology into the generated-asset Spanish strings
Apply the same evidence-based decisions to every Spanish string source
that feeds generated assets: the translation tables of
generate_graphs.py and generate_diagrams.py, the _STRINGS tables of the
plot modules and the report i18n values. 35 strings change: "presión
acústica" (UNE-EN ISO 16283-2:2016, 10052:2005, 3746:2011),
"potencia acústica" (UNE-EN ISO 3746:2011, 4871:2010, 9614-1:2010),
"intensidad acústica" (UNE-EN 61043:1999, 9614-1:2010), "absorción
acústica" (UNE-EN ISO 3746:2011) and "nivel de presión acústica de
impactos" (UNE-EN ISO 12354-2:2018). Strings the UNE editions keep are
untouched: "emisión sonora", "exposición sonora", "campo sonoro",
"fuente sonora". Only Spanish values change; the English keys stay.
The two plot-i18n test assertions that pin the axis label follow, and
the eight Spanish doc fences that deliberately mirrored the old figure
labels now mirror the new ones.
* Regenerate the Spanish figure variants with the UNE terminology
make graphs after the string-source change: 48 files, all of them _es
or _es_dark variants (24 figure/diagram pairs), picking up "presión
acústica", "potencia acústica", "intensidad acústica", "absorción
acústica" and "nivel de presión acústica de impactos" in titles and
axis labels. The English variants are untouched. The committed example
fiches need no regeneration: every example in generate_reports.py
renders in English, so no changed Spanish report string reaches them.
* Align the intensity-fiche Spanish assertions with the UNE terminology
feat: swept-sine harmonic separation, THD(f) by order and phase utilities (Farina / Novak) (#226)
* feat: swept-sine harmonic separation and phase utilities (Farina / Novak / Bendat & Piersol)
synchronized_sweep_signal() generates the synchronized exponential sweep
of Novak, Lotton & Simon (2015, Eqs. 47/49): the rounded rate L makes
f1*L an integer, so delaying the signal by L*ln(n) is exactly equivalent
to generating its nth harmonic (Eq. 18) and the harmonic phases measured
downstream are properties of the system under test, not of the
excitation.
swept_sine_distortion() in electroacoustics deconvolves a recorded sweep
response and windows the impulse response of every distortion order at
its exact L*ln(n) advance (integer part in the window position, the
fractional remainder removed with a frequency-domain phase ramp on the
one-sided spectrum, and the centring delay compensated). Two paths:
method="synchronized" (default) uses the closed-form analytic
inverse-filter spectrum X~(f) = 2*sqrt(f/L)*exp(-j2pifL(1-ln(f/f1)) +
jpi/4) (Novak Eqs. 50-51), which extends each Hn to [n*f1, n*f2];
method="farina" reuses the ISO 18233 inverse-filter machinery of
room_ir for recordings made with the classical ESS (magnitudes exact,
phases documented as excitation-dependent). The frozen
SweptSineDistortionResult carries H1..HN, the windowed harmonic IRs, the
arrival advances, THD(f) with per-order distortion ratios evaluated at
|Hn(n*f)|/|H1(f)| inside each order's valid band, and .plot(). The
recording mean is removed by default (Novak Sec. A.2.3) and the analysis
is referenced to the excitation amplitude. room_ir cross-references the
new analyzer for the distortion products impulse_response discards; the
electroacoustics -> room edge is whitelisted in the architecture test.
The metrology phase utilities implement the B&P Sec. 13.1.4 Hilbert
relation: minimum_phase() reconstructs the phase of a magnitude via the
real cepstrum, with exact trigonometric-interpolation oversampling
against cepstral aliasing and a documented floor for magnitude zeros;
group_delay() differentiates the unwrapped phase; excess_phase() returns
the all-pass part (measured minus minimum, DC-referenced); and
phase_decomposition() bundles everything into a frozen result with the
total and excess group delays and .plot().
Oracles: the Chebyshev identities of the memoryless cubic polynomial
(|H1| = 1+3a3/4, |H2| = a2/2 at -pi/2, |H3| = a3/4 at pi, closed-form
THD, all within a few 1e-3), the time-shift/harmonic identity of the
synchronized sweep at 1e-9, agreement with the tone-by-tone thd()
analyzer at 1e-3, the pure-linear THD floor below 1e-3, a delayed/gained
Hammerstein composition, the band extension beyond f2, a strictly
minimum-phase biquad reconstructed to 1e-12 rad, the exact first-order
allpass group delay (1-a^2)/(1+2a cos w+a^2), and the exact -wD excess
phase and constant excess group delay of a fractional latency.
* feat(conformance): swept-sine distortion and phase-utility checks
New domain with seven anchors: the Chebyshev |H3| = a3/4 magnitude and
the pi phase of the third harmonic separated from a synchronized sweep,
the closed-form THD(1 kHz) of the cubic polynomial, the THD floor of a
purely linear path, the minimum-phase reconstruction of a strictly
minimum-phase biquad at the 1e-9 level, the exact first-order-allpass
group delay at w = pi/2, and the excess group delay of a biquad delayed
7.25 samples. 306/306 checks pass; the report regenerates
byte-identically.
* feat(figures): swept-sine THD by order (Farina / Novak)
One figure for the concept the separator adds: the total THD(f) and the
per-order ratios d2(f)/d3(f) of a cubic polynomial followed by a 3 kHz
low-pass, measured from one synchronized sweep. Each order sits exactly
on its Chebyshev level at low frequency and rolls off where its own
product n*f crosses the filter corner. Four language/theme variants; ES
strings added to the translation tables. The remaining committed figures
regenerate within the check_figures tolerance.
* docs: swept-sine distortion and phase utilities guide (EN/ES), API reference and indices
New guide in the three documentation trees: the L*ln(n) harmonic
separation with the THD(f) definition, the synchronized sweep with its
analytic deconvolution and band extension, the practice notes for
classical ESS recordings, and the phase utilities with their numerical
contract and sampling precautions. Wired into the Sources and devices
section pages and the sidebar. Adds the new modules to the API taxonomy
(electroacoustics / spectral analysis), regenerates the Starlight API
reference, extends the curated API table, the README highlights and
guide table, the bibliography (Farina, Novak et al., Mueller &
Massarani), the llms files (regenerated from the current docs) and the
changelog.
* fix: review round for the swept-sine separator and its docs
- The Farina buffer is a linear convolution whose anticausal span is only
sweep_samples - 1: an order arriving beyond it (n > f2/f1) wrapped onto
the causal linear response and read back a spurious copy of H1 in
harmonic_responses. swept_sine_distortion now rejects the request with
a sizing error naming the sweep ratio; the synchronized path is
unaffected (its padded circular buffer keeps absent orders at the
deconvolution floor, pinned by a new regression test).
- The duration-quantization bound in the synchronized_sweep_signal
docstring was wrong by a factor ln(f2/f1): the rounding perturbs the
duration by up to ln(f2/f1)/2 periods of f1, not half a period.
- An explicitly tiny ir_length now gets its own message instead of the
arrival-spacing diagnosis meant for the default-window path.
- The guides no longer attribute the |H1|/|H2| Chebyshev anchors to the
conformance suite alone (they are pinned by the test suite; conformance
pins |H3|, its phase, the THD and the linear floor).
- _sweep_rate drops an unused parameter; the thd_frequencies field doc no
longer cross-links to the unrelated tone-by-tone thd() function.
- Regenerated API reference (including the room_ir cross-reference to the
new analyzer) and llms files.
* fix(electroacoustics): name the actual culprit in the Farina sizing error
The anticausal-span guard raised the same "orders beyond f2/f1" message
whether the order itself did not fit or only its window did. The two
causes now get their own diagnosis and remedy: order beyond the ratio
(lower n_harmonics or widen the band) versus a window that overruns the
span around an order that fits (shorten ir_length, lower n_harmonics or
lengthen the sweep). Both branches pinned by tests.
* fix: PR review round - Sonar smells and bot findings
Sonar:
- S3776 (swept_sine_distortion, complexity 26): the validation cascade
moves into _validated_recording, _sweep_timing, _harmonic_window and
_check_farina_span with identical semantics and messages; every pinned
oracle (Chebyshev magnitudes/phases, THD closed form, min-phase 1e-12)
is unchanged.
- S1192 (_plot/electroacoustics.py): the three "Frequency [Hz]" literals
become the module constant _FREQ_LABEL, matching _plot/metrology.py.
- S1172: _window_harmonics drops its unused fs parameter.
- S5778 (test_swept_sine.py, test_phase.py): the array constructions move
out of the pytest.raises blocks so each block holds one statement.
CodeRabbit:
- synchronized_sweep_signal now validates 'amplitude' like the analyzer
does (zero, negative or non-finite raise); regression test.
- _thd_curves raises an actionable ValueError when no excitation bin has
a measurable order-2 product (f1 above fs/4) instead of returning
silently empty THD arrays; the farina wording was dropped from the
message because that path is unreachable there (the anticausal guard
already caps farina orders at the sweep ratio, which excludes
sub-octave sweeps). Regression test and raises-doc updated.
- The guide example asks for n_harmonics=3 so the highest product stays
below Nyquist, consistent with its own sizing rule (EN/ES, three
trees).
- The intro no longer claims a sweep measures a nonlinear system
"completely": it characterises the linear response and every harmonic
order at once (guide three trees + module docstring).
- excess_group_delay is described as the all-pass part, not "the
pure-latency part", and the phase-utility input note now states that
only minimum_phase accepts a plain magnitude array (three trees).
- The figure asymptote labels name what is drawn, (a2/2)/H1 and
(a3/4)/H1, in the generator, the ES translation table and both guide
snippets; the four swept_sine_thd variants regenerate.
- api-reference group_delay example comment reads "# seconds".
- Rejected: exact-integer validation for n_harmonics/ir_length/oversample
(int() conversion is the library-wide convention, e.g.
envelope.decimation_factor and correlation upsample); the llms-full
loudness wording (pre-existing docs/loudness.md prose outside this PR,
already hedged and followed by the exact phon/sone definition).
Gemini (all four rejected with evidence): no English fragment exists in
the ES guide (the flagged line is fully Spanish); the phase sentence
already uses "queda totalmente determinada"; "paso-todo" follows the
repo-wide ES convention (paso bajo/paso banda/paso alto in the existing
guides), not "pasa-todo".
Regenerated: API reference, llms files, conformance byte-identical
(306/306), figures within check_figures tolerance.
feat: STOI/ESTOI objective intelligibility and AES17 dynamic range / idle channel noise (#230)
* feat: STOI/ESTOI intelligibility and AES17 dynamic range / idle channel noise
Add the two correlation-based objective speech-intelligibility measures in
phonometry.hearing.objective_intelligibility: STOI (Taal, Hendriks, Heusdens
& Jensen 2011) and ESTOI (Jensen & Taal 2016). They share a 10 kHz, 256-sample
Hann, 512-point, 15-band one-third-octave, 384 ms segment front end; STOI
averages the clipped per-band envelope correlation, ESTOI the row- and
column-normalised spectral correlation that tracks modulated maskers. The
stoi() entry point returns a STOIResult with the index, the intermediate
scores and a .plot().
Add the AES17-2015 noise measurements to electroacoustics.distortion:
dynamic_range (6.4.1) and idle_channel_noise (6.4.2), both reusing the
standard notch and the ITU-R BS.468-4 curve through the CCIR-RMS weighting
(the 468 curve with the standard -5.63 dB offset, unity at 2 kHz).
* test: oracles and conformance for STOI/ESTOI and AES17
STOI/ESTOI: degenerate cases (identical -> 1, uncorrelated -> low), SNR
monotonicity, level invariance, input validation and an external cross-check
against pystoi (added as a test-only dependency; the library reimplements from
the papers and never imports it at runtime, and the two agree to under 1e-6).
AES17: closed-form dBFS oracles for idle channel noise (a 1 kHz tone reads its
level minus the 5.63 dB CCIR-RMS offset) and dynamic range (full-scale sine
over a known residual), plus level scaling and monotonicity with noise.
Register five conformance checks and refresh docs/CONFORMANCE.md.
* docs: objective-intelligibility guides, AES17 sections and concept figure
Add the Objective Intelligibility (STOI & ESTOI) guide across the GitHub docs
and the EN/ES site, wire it into the Speech section, sidebar and indexes, and
document the AES17 dynamic range and idle channel noise in the electroacoustics
guides. Add the STOI-vs-ESTOI concept figure (four language/theme variants)
showing that ESTOI credits the speech glimpsed in a modulated masker's gaps
while STOI barely separates the two maskers. Refresh the generated API
reference, the curated API table and the CHANGELOG.
* refactor: address Sonar and bot review on STOI/ESTOI and AES17
- Extract the AES17 notch-Q validation into a shared _validate_notch_q helper,
removing the duplicated range literal across the distortion functions
(Sonar S1192).
- Move the input construction out of the pytest.raises blocks in the STOI
input-validation test so each block exercises a single call (Sonar S5778).
- Plot the STOI/ESTOI intermediate correlations on a [-1, 1] axis (they are
cosine-similarity quantities, not [0, 1] ratios) so anti-correlated bands
are not clipped.
- Vectorise the STOI frame extraction and spectrogram (one strided gather and
a single batched rfft), numerically identical to the per-frame loop; the
pystoi cross-check is unchanged at ~1e-16.
The framing keeps the reference range(0, n - frame, hop) (the trailing partial
frame is dropped, matching the authors' MATLAB), so the STOI/ESTOI oracle does
not move.
feat(vibration): bending-wave transmission coefficients for rigid plate junctions (#264)
I add the wave-approach transmission coefficients for rigid junctions of thin
plates (Cremer et al. 1973; Craik 1981/1996; Hopkins 2007, Section 5.2.1.3),
which give the frequency-independent bending-wave transmission around a corner
and across a straight section for the X, T, L and in-line junctions.
The new `phonometry.vibration.junction_transmission` module exposes:
- `junction_wave_parameters` for the wave parameters chi and psi of a plate
pair (Eqs 5.10/5.11);
- `corner_transmission_coefficient` (Eq. 5.12) and
`straight_transmission_coefficient` (Eq. 5.13) for the angle-resolved
coefficients, `inline_transmission_coefficient` for the in-line normal
incidence coefficient (Eq. 5.14), and
`angular_average_transmission_coefficient` for the diffuse-field average
(Eq. 5.6);
- `coupling_loss_factor` (Eq. 2.154) and `wave_vibration_reduction_index`
(Eq. 5.116) for the SEA coupling loss factor and the wave-approach Kij;
- the frozen `JunctionTransmissionResult` (with a `.plot()` of tau versus
incidence angle) built by `junction_transmission`.
The oracle is the closed form itself: identical plates give
tau12 = tau13 = cos^2(theta)/8 for the X-junction (angular average 1/12),
cos^2(theta)/2 for the L-junction (average 1/3), a perfect-square
normal-incidence denominator for arbitrary chi and psi, in-line tau = 1, the
cut-off tau = 0 for chi < sin(theta), and the reciprocity tau_bar_12 = chi
tau_bar_21. These hand-derived values anchor the tests and six conformance
checks. Adds the EN and ES guides, the bibliography entries for Cremer 1973 and
Craik 1996, a figure and the changelog entry.
feat(vibration): bending-wave transmission coefficients for rigid plate junctions (#264)
I add the wave-approach transmission coefficients for rigid junctions of thin
plates (Cremer et al. 1973; Craik 1981/1996; Hopkins 2007, Section 5.2.1.3),
which give the frequency-independent bending-wave transmission around a corner
and across a straight section for the X, T, L and in-line junctions.
The new `phonometry.vibration.junction_transmission` module exposes:
- `junction_wave_parameters` for the wave parameters chi and psi of a plate
pair (Eqs 5.10/5.11);
- `corner_transmission_coefficient` (Eq. 5.12) and
`straight_transmission_coefficient` (Eq. 5.13) for the angle-resolved
coefficients, `inline_transmission_coefficient` for the in-line normal
incidence coefficient (Eq. 5.14), and
`angular_average_transmission_coefficient` for the diffuse-field average
(Eq. 5.6);
- `coupling_loss_factor` (Eq. 2.154) and `wave_vibration_reduction_index`
(Eq. 5.116) for the SEA coupling loss factor and the wave-approach Kij;
- the frozen `JunctionTransmissionResult` (with a `.plot()` of tau versus
incidence angle) built by `junction_transmission`.
The oracle is the closed form itself: identical plates give
tau12 = tau13 = cos^2(theta)/8 for the X-junction (angular average 1/12),
cos^2(theta)/2 for the L-junction (average 1/3), a perfect-square
normal-incidence denominator for arbitrary chi and psi, in-line tau = 1, the
cut-off tau = 0 for chi < sin(theta), and the reciprocity tau_bar_12 = chi
tau_bar_21. These hand-derived values anchor the tests and six conformance
checks. Adds the EN and ES guides, the bibliography entries for Cremer 1973 and
Craik 1996, a figure and the changelog entry.