feat: installed structure-borne sound from equipment (EN 12354-5)
Add the EN 12354-5:2009 prediction of the receiving-room sound pressure
level from building service equipment that injects structure-borne sound
into the building. Final PR of the structural vibroacoustics series (#16),
closing the chain source (EN 15657) -> coupling (ISO 10846 / ISO 7626
mobilities) -> building transmission.
New module `installed_structure_borne`:
- `coupling_term` DC = 10 lg(|Ys+Yi+Yk|^2/(|Ys| Re{Yi})) (Formula 19b/e),
with force-source (19c) and velocity-source (19d) limits.
- `installed_structure_borne_power_level` LWs,inst = LWs,c - DC (18b).
- `structure_borne_pressure_level_path` Ln,s,ij = LWs,inst - Dsa - Rij,ref
- 10 lg(Si/S0) - 10 lg(A0/4) (18a, S0 = A0 = 10 m2) and
`total_structure_borne_pressure_level` energetic path sum (17).
- `InstalledSourceResult` (`.overall_level`, `.plot()`) and
`installed_source_prediction` for a multi-path prediction.
The source and receiver mobilities/impedances are those of
mechanical_mobility and transfer_stiffness; LWs,c comes from EN 15657.
Conformance is anchored on the coupling-term force-source limit, the
installed-power identity and the area/absorption terms of Formula 18a;
Dsa and Rij,ref are inputs (measurement / EN 12354-1 / Annexes D, F)
(137/137).
Adds the cascade figure (characteristic -> installed -> paths -> total),
the EN/ES guides, the docs index and api-reference rows, and the sidebar
entry.
feat: laboratory flanking transmission (ISO 10848 vibration reduction index) (#132)
ISO 10848:2006/2010 is the laboratory measurement counterpart of the EN 12354
flanking-transmission prediction: it measures the junction vibration reduction
index Kij that the prediction takes as an input, plus the overall flanking
descriptors Dn,f and Ln,f.
New module phonometry.flanking_transmission:
* vibration_reduction_index() -- Kij (Formula (13), or the simplified (14) for
lightweight well-damped elements), with the equivalent absorption length
(Formula (12)), the direction-averaged velocity level difference (Formula
(11), making Kij symmetric), octave-band combination and the single-number
mean Kij over 200-1250 Hz (Annex A). Returns a plottable
VibrationReductionResult.
* velocity_level_difference() / direction_averaged_level_difference() /
total_loss_factor() / equivalent_absorption_length() -- the building blocks.
* normalized_flanking_level_difference() (Dn,f, Formula (4)) and
normalized_flanking_impact_level() (Ln,f, Formula (5)), rated through the
verified ISO 717-1/-2 engines.
* vibration_reduction_index_from_flanking() -- the indirect Kij from Dn,f.
* Validity criteria: strong_coupling_satisfied() (Formula (15)),
critical_frequency() (Formula (20)), and the Part 4 modal-density /
band-mode-count / modal-overlap-factor checks (Formulas (5)/(4)/(6)).
ISO 10848 contains no worked numeric example, so conformance is anchored on
closed-form identities (simplified Kij, aj at f_ref, the total loss factor) --
three new checks in the conformance report. 29 unit tests cover the closed
forms, the Kij symmetry and simplified/full-formula relationship, the octave
conversion, the Dn,f/Ln,f ratings, the validity criteria and the plotting.
Docs (GitHub + site EN/ES) get a new section 8 with a figure and the API
reference rows; the measured Kij feeds the existing EN 12354 flanking model.
Audit pass 8: retrofit the house conventions onto the legacy guides (#117)
* docs: retrofit the house conventions onto the legacy guides
Audit batch 8 - the pre-convention guides catch up with the style the
newer ones follow, on all three surfaces (repo, site EN, site ES):
- Standards footers for the ten guides that cited clauses inline but
never closed with the normative scope (calibration, filter-banks,
intensity, levels, time-weighting, weighting, outdoor-propagation,
sound-power, psychoacoustics, plus materials synced from the site
style and a new room-acoustics footer); block-processing,
multichannel and getting-started verified as having no normative
citations to footer
- every touched snippet is now self-contained or carries an explicit
reuse annotation, and ~20 prints gained expected outputs EXECUTED
against the library (levels LN values re-verified, materials
# 222956, human-vibration VDV/MTVV/crest, getting-started ~91 dB
bands, Zwicker sone/phon values, enclosed-space RT spectrum)
- 12 figure-reproduction <details> blocks added (materials x3,
outdoor-propagation x2, time-weighting x2, weighting x3,
speech-intelligibility, getting-started - with an honest note where
the committed figure overlays extra context), all verified runnable
- structural reorders, content-preserving: the exponential detector now
precedes the F/S/I modes; the filter parameter table follows the
comparison it references; weighting opens with 'Where the curves come
from'; block-processing gains a motivating opening and drops its
duplicated constraints list; the raw LaTeX in human-vibration code
spans becomes proper math; F2/F3/F4 defined in prose in intensity
- sound-power SS1-3 gain result figures: three new deterministic
generators (12 PNG variants, ES translations, visually verified)
embedded with captions and <details> using each result's .plot()
* docs: address the review findings on the legacy-conventions batch
- NullFormatter imported from matplotlib.ticker in the SII snippet
(plt.NullFormatter does not exist) (Copilot)
- eps floor inside the log10 of the three FFT-magnitude plots in the
weighting snippets, verified warning-free with -W error (Copilot)
- the getting-started figure snippet now reproduces the committed
figure including the gray PSD overlay, note removed (CodeRabbit)
- 8-tau settling is ~99.97 %, not 99.8 % (CodeRabbit)
- the intensity result generator drops its blanket warning suppression
(this data fires none) and masks non-finite bands instead of
nan_to_num zero bars; regenerated variants are byte-identical
(Gemini, CodeRabbit)
- the outdoor attenuation stacks use separate positive/negative
baselines - the 63 Hz ground term (-4.65 dB) now hangs below zero in
BOTH the snippet and the committed generator figure, whose four
variants regenerate (CodeRabbit, Major)
- the IEC verification claim narrows to what is true: the example
verifies the 200 ms Fast row; CI covers Table 4 down to 1 ms for F
and 2 ms for S (CodeRabbit)
- freqs = res.frequencies added to the three sound-power by-hand blocks
(CodeRabbit)
feat: IEC 61260:1995 / ANSI S1.11-2004 class 0 filter verification (#126)
* feat: IEC 61260:1995 / ANSI S1.11-2004 class 0 filter verification
verify_filter_class and class_limits gain an `edition` argument. The default
"2014" path (IEC 61260-1:2014, classes 1 and 2) is unchanged; edition "1995"
selects the withdrawn IEC 61260:1995 / ANSI S1.11-2004 Table 1, which adds the
stricter laboratory-grade class 0 and whose class 1/2 masks differ slightly
from the 2014 edition.
The 1995/ANSI octave-band Table 1 was transcribed digit-for-digit and verified
identical between the two standards (dual-oracle clean-room). The fractional-
octave breakpoint mapping is shared: 1995 Annex B equation (10) is identical to
2014 Formula (9), so the existing _map_breakpoint is reused for both editions.
The library default (Butterworth order 6) already meets class 0 across the usual
configurations (octave/third-octave, 32-96 kHz); the non-Butterworth
architectures cannot reach the class mask by construction (ripple / flat group
delay), so defaults are unchanged and the class each architecture reaches is
documented instead.
Adds a class-0 conformance check (99/99), the EN/ES filter_class0_mask figure,
tests cross-checked against a shared reference_data copy, the "Class 0" doc
section (guide EN/ES + API refs EN/ES) and regenerated CONFORMANCE.md /
llms-full.txt.
* docs: address bot review — scope class-0 claims, fix English decimals
- compliance.py: English docstring tolerances use dot decimals and ± symbol
(were decimal commas, inconsistent in an English context).
- api-reference (docs + site EN/ES): qualify overall_class by edition
(1/2/None for "2014"; 0/1/2/None for "1995"); neutralize the ES headers that
still said "IEC 61260-1:2014" while documenting the edition switch.
- filter-banks (docs + site EN/ES): narrow the class-0 wording to the verified
order-6 Butterworth octave/third-octave banks at 48 kHz; drop the untested
32-96 kHz range and advise re-running verify_filter_class away from those
settings.
- generate_graphs.py: fix misleading "filter_class0_mask.png" print (output is
SVG).
- test_compliance.py: assert the class-0/1/2 minimum ordering the docstring
already promised (previously only the maximum ordering was checked).
feat: installed structure-borne sound from equipment (EN 12354-5)
Add the EN 12354-5:2009 prediction of the receiving-room sound pressure
level from building service equipment that injects structure-borne sound
into the building. Final PR of the structural vibroacoustics series (#16),
closing the chain source (EN 15657) -> coupling (ISO 10846 / ISO 7626
mobilities) -> building transmission.
New module `installed_structure_borne`:
- `coupling_term` DC = 10 lg(|Ys+Yi+Yk|^2/(|Ys| Re{Yi})) (Formula 19b/e),
with force-source (19c) and velocity-source (19d) limits.
- `installed_structure_borne_power_level` LWs,inst = LWs,c - DC (18b).
- `structure_borne_pressure_level_path` Ln,s,ij = LWs,inst - Dsa - Rij,ref
- 10 lg(Si/S0) - 10 lg(A0/4) (18a, S0 = A0 = 10 m2) and
`total_structure_borne_pressure_level` energetic path sum (17).
- `InstalledSourceResult` (`.overall_level`, `.plot()`) and
`installed_source_prediction` for a multi-path prediction.
The source and receiver mobilities/impedances are those of
mechanical_mobility and transfer_stiffness; LWs,c comes from EN 15657.
Conformance is anchored on the coupling-term force-source limit, the
installed-power identity and the area/absorption terms of Formula 18a;
Dsa and Rij,ref are inputs (measurement / EN 12354-1 / Annexes D, F)
(137/137).
Adds the cascade figure (characteristic -> installed -> paths -> total),
the EN/ES guides, the docs index and api-reference rows, and the sidebar
entry.
feat: environmental-noise determination (ISO 1996-2 tonal adjustment + uncertainty) (#133)
* feat: environmental-noise determination (ISO 1996-2 tonal adjustment + uncertainty)
ISO 1996-2:2017 is the determination part of the ISO 1996 environmental-noise
family (the descriptors Lden/Ldn live in ISO 1996-1, already in
phonometry.environmental). New module phonometry.environmental_measurement:
* Tonal adjustment (engineering method, ISO 1996-2:2007 Annex C):
tonal_audibility() forms ΔLta = Lpt − Lpn + 2 + lg[1 + (fc/502)^2.5] dB
(Formula C.3) and tonal_adjustment() the piecewise Kt (Formulae C.4-C.6);
assess_tonal_audibility() bundles them with the critical bandwidth (Table
C.1) into a plottable result. tonal_seeking_survey() is the one-third-octave
15/8/5 dB screen (Annex K) and tonal_adjustment_from_mean_audibility() the
Table J.1 mean-audibility route.
* Residual-noise correction: residual_sound_correction() (Formula 16, flags a
<3 dB margin as an upper bound) and gaussian_residual_level() (Annex I).
* Measurement uncertainty: combined_standard_uncertainty() (Formula 2),
environmental_expanded_uncertainty() (k=2/1.3), residual_correction_uncertainty()
(Formulae F.7-F.9) and uncertainty_from_repeated_measurements() (Formulae 17-20).
Unlike the removed 2017 engineering method (which defers to ISO/PAS 20065), the
detailed 2007/2009 Annex C algorithm is self-contained and has worked numeric
examples: conformance is anchored on Annex C.5 (ΔLta and Kt) and Annex G.2 (the
combined uncertainty u = 2.18 dB) -- three new checks (115/115, byte-stable).
The raw-FFT tone-detection pipeline is intentionally out of scope: the standard
gives no raw input spectra, so it could not be verified. 25 unit tests; docs
(GitHub + site EN/ES) get a new levels section with a figure and API rows.
* test: anchor ISO 1996-2 residual/gaussian/uncertainty tests on hand-computed values
Review found four tests re-derived their expected value from the same
arithmetic expression as the implementation, so a transcription error would
go undetected. Replace them with independent hand-computed literals (residual
correction Formula 16, Gaussian residual I.1/I.2, residual-correction
uncertainty F.9, repeated-measurement mean/uncertainty 18/20).
* fix: avoid assert for type narrowing in gaussian_residual_level (bandit B101)
The CI quality job runs bandit -r src, which flags assert statements (stripped
under python -O). Replace the mypy-narrowing assert with an elif/else-raise so
the branch is both bandit-clean and correctly narrowed.
feat: mechanical mobility and the FRF family (ISO 7626-1:2011) (#136)
Add the ISO 7626-1:2011 frequency-response-function family — the
motion-per-force FRFs (receptance, mobility, accelerance) and their
force-per-motion reciprocals (dynamic stiffness, impedance, apparent
mass) of Table 1 — plus the single-degree-of-freedom reference resonator
of Annex A that underpins the structure-borne source and transmission
standards (ISO 9611, ISO 10846, EN 15657, EN 12354-5).
New module `mechanical_mobility`:
- `convert_frf` converts between any two of the six FRFs, pivoting
through the receptance H (Y = jωH, A = −ω²H, reciprocals = 1/FRF).
- `sdof_receptance` / `sdof_mobility` / `sdof_accelerance` and
`resonance_frequency` for the viscously damped SDOF resonator
H = 1/(k − ω²m + jωc).
- `MobilityResult` (`.magnitude`, `.phase`, `.to(target)`, `.plot()`)
and `sdof_mobility_result`.
Conformance is anchored on the closed-form SDOF identities: the
driving-point mobility peak |Y(f0)| = 1/c, the static receptance
H(0) = 1/k, and the exact Table-1 reciprocity Z·Y = 1 (126/126).
Adds the module figure, the EN/ES guides, the docs index and
api-reference rows, and the sidebar section.
feat: reverberation-time prediction (Sabine, Eyring, Millington-Sette, Fitzroy, Arau-Puchades) (#134)
* feat: reverberation-time prediction (Sabine, Eyring, Millington-Sette, Fitzroy, Arau-Puchades)
Add a general reverberation-time prediction module complementing the
EN 12354-6 enclosed-space model and the ISO 3382 measurement code. Five
statistical-acoustics models predict T from a room's volume, boundary
areas and surface absorption:
- Sabine, Eyring (Norris-Eyring) and Millington-Sette over an arbitrary
surface list;
- Fitzroy (area-weighted arithmetic mean) and Arau-Puchades (geometric
mean, Acustica 65, 1988, Formula 18) over the three wall pairs of a
rectangular room;
- the air-absorption term 4mV throughout (m from air_attenuation_m);
- reverberation_time_models + ReverberationModelResult.plot() to compare
all five per band.
k = 24 ln10 / c0 (0.161 at c0 = 343 m/s). Conformance is anchored on a
real worked oracle — Everest, Master Handbook of Acoustics 4th ed,
Fig. 7-22 Example 1, reproduced to <= 0.02 s after ft -> SI conversion —
reinforced by hand-computed closed-form values and the model identities
(all reduce to Eyring for uniform absorption; Eyring -> Sabine as a -> 0).
Docs (repo + bilingual site guide + API reference), the reverberation_models
figure (4 variants), 6 conformance checks (120/120) and 26 tests.
* build: regenerate reverberation_models figures with pinned matplotlib 3.11.0
The four new SVGs were first rendered with the local matplotlib 3.10.1,
whose glyph/path layout differs structurally from the CI-pinned 3.11.0
(requirements-figures.txt), tripping the figure-staleness check. Regenerate
them under the pinned toolchain so they match a fresh 'make graphs' on CI.
feat: dynamic transfer stiffness of resilient elements (ISO 10846) (#137)
Add the ISO 10846 dynamic transfer stiffness k2,1 = F2,b/u1 of resilient
elements (vibration isolators, mounts, bellows, hoses) — the quantity
that characterises their vibro-acoustic transmission (Part 1, clause 5).
This is the third PR of the structural vibroacoustics series (#16) and
feeds ISO 9611, EN 15657 and EN 12354-5.
New module `transfer_stiffness`:
- `transfer_stiffness_level` Lk = 20 lg(|k2,1|/k0) re k0 = 1 N/m
(Parts 2/3, 3.17) and `loss_factor` eta = Im/Re (Part 1, 3.8).
- `transfer_stiffness_direct` k2,1 = F2,b/u1 (direct method, Part 2).
- `transfer_stiffness_indirect` k2,1 = -(2 pi f)^2 (m2 + mf) T from the
vibration transmissibility and a blocking mass (indirect method,
Part 3, Formula 1), plus `base_transmissibility` for the ideal
mass-loaded Kelvin-Voigt element used in examples and the figure.
- `TransferStiffnessResult` (`.level`, `.loss_factor`, `.magnitude`,
`.to("impedance"/"apparent_mass")`, `.plot()`) and
`indirect_transfer_stiffness_result`.
The dynamic stiffness is the reciprocal receptance of the FRF family, so
k = jw Z = -w^2 m_eff reuse the mechanical-mobility convert_frf pivot
(Part 1, Annex A / Table A.2).
Conformance is anchored on the standard's closed-form definitions: the
level of a decade of stiffness (120 dB re 1 N/m), the indirect inertia
relation, and the Table-A.2 identity k = jw Z (129/129).
Adds the module figure, the EN/ES guides, the docs index and
api-reference rows, and the sidebar entry.
feat: sound power from surface vibration (ISO/TS 7849-1/-2) (#138)
Add the ISO/TS 7849 estimation of the airborne sound power a machine
radiates through the structure-borne vibration of its outer surface,
from the surface vibratory velocity and a radiation factor. Fourth PR of
the structural vibroacoustics series (#16); feeds ISO 9611, EN 15657 and
EN 12354-5.
New module `vibration_sound_power`:
- `velocity_level` Lv = 20 lg(v/v0) re v0 = 5e-8 m/s (Eq. 3) and
`velocity_level_from_acceleration` for a sinusoidal calibration (Eq. 8).
- `mean_velocity_level` energetic / area-weighted surface mean (Eq. 10/11).
- `radiation_factor` eps = P/(Zc <v^2> S) from a measured power (Eq. 4/8).
- `radiated_sound_power_level` LW = Lv + 10 lg(S/S0) + 10 lg(eps) +
10 lg(411/400) (Eq. 12/15): eps = 1 gives the Part 1 upper limit, a
measured eps the Part 2 engineering value.
- `extraneous_velocity_correction` K1A (Table 2).
- `VibrationSoundPowerResult` (`.total_level`, `.plot()`) and
`sound_power_from_vibration`.
Conformance is anchored on the standard's own worked calibration example
(a = 9,81 m/s^2 at 100 Hz -> Lv = 106,9 dB), the exact round-trip between
the radiation factor and LW = 10 lg(P/P0), and the fixed 10 lg(411/400)
impedance term (132/132).
Adds the module figure (Part 1 upper limit vs Part 2 engineering per
band), the EN/ES guides, the docs index and api-reference rows, and the
sidebar entry.