Commits
The committed 37-receiver polar arc in tests/reference_data.py is now
generated by the library's own Fraunhofer far-field model for a published
geometry: the N = 7 QRD, 6 periods, 3.6 m wide, 0.2 m deep row of
Cox & D'Antonio, Acoustic Absorbers and Diffusers, 3rd ed., Appendix B
(the commercial N = 7 QRD of Hargreaves, Cox, Lam & D'Antonio, JASA 108(4),
1710-1720, 2000, Table I). The arc is an arithmetic oracle for ISO 17497-2
Formulas (5)/(7): d = 0.1099, flat reference d = 0.0049, d_n = 0.1055,
recomputed exactly from the committed rounded levels.
A new external third-party anchor pins the model's one-third-octave
band-averaged normalised diffusion to the published Appendix B 2D BEM values
for that row at normal incidence in the 200-400 Hz bands (agreement within
0.01, asserted at +/-0.015) in the tests and in four new conformance rows.
Across the full published 100-5000 Hz range the model-vs-BEM mean absolute
deviation is about 0.09, documented everywhere as a low-band anchor.
The previous in-house COMSOL simulation arc and its numbers are removed from
the tests, the conformance report, the surface-scattering guide (EN/ES), the
theory pages and the diffusion_polar figure, which now all use the same
published-geometry prediction.
- ISO 3741:2010, 9.1.2/9.1.3: apply the background correction at each
microphone position (K1i, Eq. 14), correct the position levels first
(Eq. 15) and only then energy-average (Eq. 16). Pre-averaged 1D spectra
keep the single-K1 path, documented as an approximation.
- ISO 9614-2:1996, 10.6 b: omit the bands failing criteria 1 and/or 2 from
the A-weighted total, flag them in the new a_weighting_omitted_bands field
and name them in the fiche basis strip (EN/ES); warn when the criteria
inputs are absent and the screening cannot run.
- ISO 9614-3:2002, C.1.6.2: a band satisfying criterion 5 qualifies as a
final result even if FS(2) >= 2.
- ISO 3745:2012: bands above 10 kHz no longer abort the determination; only
the A-weighted total (outside the ISO 3744 Annex E table) is NaN.
- ISO 3744:2010, 8.2.3 / ISO 3746:2010, 8.3.3: K1 = 0 only strictly above
the upper criterion; Equation (16) still applies at exactly 15 dB / 10 dB.
- ISO 4871:1996, 3.16/6.2: verify dual-number declarations against the sum
of the separately rounded declared values (verified_dual), and lay the
dual-number fiche out exactly as Annex B.2 (no derived L_WAd row).
- Cite clause 8.4 for the apparent directivity indices and note that the
4.3.2 validity limit concerns K2A (per-band check kept, conservative).
Hand-derived oracles cover each correction; the ISO 4871 and ISO 9614-2
example fiches, the API reference pages and the llms text are regenerated.
Rate NC spectra by the standard's two-step procedure (5.2.2): the speech
interference level (clause 3.2, the average of the 500/1000/2000/4000 Hz
octave-band levels) selects the NC-(SIL) curve and the spectrum is
designated NC-(SIL) unless an octave band exceeds it, in which case the
tangency method (5.2.3) applies. NCResult gains sil, tangency_rating,
method, out_of_range and a label property; the tangency rating stays
available on every in-family result. Spectra outside the NC-15 to NC-70
family of Table 1 are flagged out of range instead of clamping the
interpolation to fabricated NC-71/NC-14 designations: above the family
the governing band is the maximum exceedance over the NC-70 curve and the
label reads ">NC-70 (band)"; below it, "<NC-15". Fiche, plot and
verdict render the flagged designation in English and Spanish.
On the RC Mark II side, warn when the clause D.4 minimum band set
(31.5 Hz through 4000 Hz) is incomplete, flag ratings outside the
tabulated RC-25 to RC-50 family (Table D.1) with an extrapolation note on
the fiche, and document the combined RH tag as a diagnostic extension
beyond the clause D.3.5 letters (N, R, H, RV; the RV vibration/rattle
classification needs the Table 6 test and is not implemented).
On the ISO 3382-1/-2 fiche, label the mid-frequency descriptor honestly
for one-third-octave data (only the 500 Hz and 1 kHz one-third-octave
bands are averaged, so the octave "500-1000 Hz" claim is dropped and the
bands are named), name the band of a fallback T30/EDT descriptor when the
range does not span both mid bands, and choose the EDT_mid vs EDT label
from EDT's own band coverage instead of T30's. Fiche verdicts (ISO 3382
and S12.2) now compare display-rounded values so the printed numbers can
never contradict the verdict at a tolerance boundary.
Correct two citations in the room-acoustics docstrings: the 5 %
reverberation-time JND is ISO 3382-1:2009 Table A.1 (ISO 3382-2:2008
Table A.1 tabulates the uncertainty constants G and H) and the -20 dB
onset trigger is ISO 3382-1:2009 A.3.4, with a note on the at-edge
sample choice. English ISO 3382-3 strings use point decimals
(STI = 0.50/0.20); the Spanish strings keep the comma.
Docs (EN/ES), API reference, conformance check, changelog and the
affected example fiches are updated accordingly.
Directive 2002/44/EC bases the whole-body daily exposure A(8) on the
highest of the frequency-weighted axis values 1,4*a_wx, 1,4*a_wy, a_wz
(Annex, Part B, point 1), not on the ISO 2631-1 Eq. (10) vector total
a_v; the hand-arm side keeps the Part A vector total a_hv.
- Relabel the whole-body magnitude of the daily-exposure fiche as
a_w,max with a printed note stating the Part B basis (EN/ES); add
vibration.wbv_exposure_basis() returning the dominant-axis value and
document in daily_vibration_exposure which magnitude each kind must
be fed with; state the same basis in the human-vibration guide
(EN/ES) and the measurement-chain diagram.
- Derive the boxed exposure zone of the fiche from the same
displayed-rounded comparisons as the assessment rows and the verdict,
so an A(8) printing exactly at a threshold cannot show a zone one
step below its own Exceeded row.
- ISO 10848 Kij fiche: distinguish an empty single-number mean caused
by every in-range band being bracketed (M < 0,25) from a spectrum
with no bands in the Annex A range (EN/ES).
- Spanish ISO 2631-5 outputs: translate the subject sex in the
injury-probability plot title and the word Formula in the fiche's
clause references.
- Correct the running_rms comment: the linear window divides by the
full window length (zero-padded front), it does not average over the
available samples.
The ruff 0.16 adoption applied the PLC0414 and F401 unsafe fixes to
src/phonometry/_plotting.py and deleted all 82 redundant-alias re-exports,
leaving only the docstring. The module is documented as a silent re-export
of the plot renderers (moved to phonometry._plot) for one deprecation
cycle, so from phonometry._plotting import plot_impulse_response and every
sibling raised ImportError. The sibling shim scripts/fdtd2d.py got the
explicit __all__ treatment in the same commit; this one was missed.
Bring back all 82 re-exports as grouped plain imports per source module
and declare an explicit __all__ so the lint fixers keep them (every target
still exists in phonometry._plot, none dropped). Keep the module out of
_compat._MOVED on purpose: that table maps one old path to one relocated
module and warns on attribute access, while _plotting fans out to thirteen
domain modules and is documented as silent.
Harden tests/test_deprecated_aliases.py so this cannot regress silently:
every pre-move module path must now expose a non-empty public surface, and
a new test pins the frozen 82-name _plotting re-export snapshot, asserting
__all__ matches and each name resolves to a callable without warnings.
Pure structural refactor of the two functions flagged by SonarCloud S3776,
with no numeric change:
- ISO 532-1 _calc_slopes: the two segment kinds of the slope state machine
move to _masked_segment/_flat_segment and the Table A.9 range searches to
_rns_index_of/_next_rns_index.
- Osses 2016 _analyze: the per-frame excitation front-end, generalised
modulation depth and neighbour cross-covariance become _band_envelopes,
_modulation_depth and _neighbour_covariance.
Outputs verified byte-for-byte identical on AM/FM tones, AM noise and
stationary/time-varying loudness cases; the committed conformance report
regenerates unchanged.
* Add slow-sound slit and Helmholtz-resonator perfect absorbers
Introduce materials.slow_sound_absorber, a transfer-matrix model of a
rigid panel of thin closed slits loaded by an array of Helmholtz
resonators. The resonators slow the sound inside the slit and pull its
resonance into the deep-subwavelength regime, and the visco-thermal
losses of the sub-millimetre slit and of the square resonator necks and
cavities make perfect absorption possible.
slit_helmholtz_absorber predicts the oblique-incidence absorption of the
panel, returning a SlitResonatorAbsorberResult with the surface
impedance, reflection factor, absorption, retrieved effective parameters
and the full chain matrix, plus a .plot() of alpha(f) with |R| overlaid.
critical_coupling_design solves the inverse problem: it tunes the cavity
length and the slit height so the reflection zero sits on the real
frequency axis, giving perfect absorption at a chosen frequency and
angle. The building blocks slit_effective_properties,
rectangular_duct_properties and helmholtz_resonator_impedance and the
HelmholtzResonator geometry are exported alongside.
The model is pinned to its exact analytic anchors: perfect absorption at
the design frequency, the Poiseuille resistivity limits 12 eta / h^2
(slit) and 28.454 eta / w^2 (square duct), and the loss-free
effective-parameter limits (rho -> rho0, kappa -> kappa0). Adds a
conformance section, an example figure, tests, an API-reference row set,
and EN/ES guide sections.
* Keep the Sonar gate clean for the slow-sound absorber
Exempt materials.slow_sound_absorber from S107 the same way the other
scientific APIs are: its functions carry the slit and resonator geometry
alongside the shared keyword-only air state, matching the porous-absorber
and JCA signatures, so the parameter count reflects the physics rather
than incidental complexity.
Extract a shared alpha(f) + |R| overlay renderer for the layered-absorber
and slit-resonator plots so the two oblique-incidence absorber renderers
no longer duplicate their body. Output is byte-identical.
* Make the slow-sound absorber files Ruff 0.16 clean
Use dict literals for the shared air-state mappings and drop the
redundant quoted type annotations (the module already imports
annotations from __future__). Keeps the new feature files clean under
the widened Ruff 0.16 default rule set without touching unrelated files.
* Modernize the slow-sound absorber additions for ruff 0.16 and regenerate references
* Silence the unused figure handle in the slow-sound graph helper
* Clarify slow-sound docstrings and cover the optional-correction branches
Fix the trailing space in a docstring code span, state the design warning is
emitted rather than raised, correct the deep-subwavelength depth ratio in the
figure docstring, and add a test exercising the end_correction and
slit_radiation False branches.
* Regenerate API reference and llms for the slow-sound docstring fixes
* Regenerate conformance and references after rebase
* Translate the slow-sound figure strings and require convergence in the check
Add the Spanish figure-string translations for the slow-sound absorber plot
(legend, design annotation, title and the panel-depth pattern) so the ES
variant is not left in English, and make the critical-coupling conformance
check fail unless the design solver converged.
* Regenerate conformance and references after rebase
* Use a real polar response for the ISO 17497-2 diffusion oracle
Replace the hand-made four-level diffusion anchor with a real free-field
polar response: a boundary-element (COMSOL) prediction of an N = 7
quadratic-residue diffuser and its flat reference panel at 1000 Hz, normal
incidence, reduced to the standard 37-receiver single-plane semicircle
(5 deg spacing, -90 to +90 deg) in the plane of maximum diffusion. The 37
arc points are coplanar to 3.4e-3 of unit radius, so the equal-area
Formula (5) applies exactly; levels are L_i = 20 lg |p_i|.
- reference_data: commit the 37 QRD and flat-reference arc levels and the
directional diffusion coefficients d_theta = 0.7572 (QRD) and 0.1391
(flat) and the normalised d_theta,n = 0.7180 (Formula (7)), with full
provenance. For context the full dense hemisphere gives 0.5007/0.1487/
0.4135 (energy) and reproduces the source's own value under the amplitude
convention.
- conformance: three real-data rows (Formula (5) twice, Formula (7)),
reproduced to 1e-6; regenerate docs/CONFORMANCE.md (412/412 pass).
- tests: clean-room checks that re-derive Formula (5) from the levels
independently of the library kernel.
- Surface Scattering guide (English and Spanish, plus the GitHub mirror)
and the theory anchor now use the cited real QRD example; regenerate the
polar figure (d = 0.76) and llms-full.txt.
* Regenerate conformance and references after rebase
* Correct the diffusion oracle provenance to an in-house COMSOL FEM simulation
The single-plane QRD field is the maintainer's own COMSOL finite-element
free-field simulation cross-validated against an independent MATLAB
implementation of ISO 17497-2 Formula (5), not a boundary-element method and
not a third-party published measurement; drop the unverifiable thesis
citation and the mislabelled method across the guide, its mirror and the
oracle provenance.
* Make the diffusion-oracle provenance consistent in the test module
Match the reference-data provenance: in-house COMSOL finite-element simulation
cross-validated against an independent MATLAB implementation, not a
boundary-element 2018 thesis and not a measurement; rename the tests to
_matches_simulation_oracle.
* Add diffuser-design far-field prediction of the diffusion coefficient
Predict the far-field polar response and the ISO 17497-2 directional
diffusion coefficient of a Schroeder phase-grating diffuser from its
surface design, complementing the measurement-only scattering_diffusion
module. The new materials.diffuser_design module implements the
single-plane Fraunhofer (Fourier) far-field model of Cox and D'Antonio:
each rigid-bottom well of depth d_n contributes a pressure reflection
coefficient R_n = exp(-2 j k d_n) and the scattered pressure at each
reflection angle is the sum over the wells of the periodic surface, whose
predicted levels feed the existing directional_diffusion_coefficient.
- predict_diffuser_polar_response(): per-frequency polar response reduced
to the directional diffusion coefficient, from a well-depth sequence or
an explicit complex per-well reflection sequence.
- predicted_diffusion_spectrum(): predicted d(f), normalised band by band
against the same-footprint flat reference (Formula (7)).
- quadratic_residue_sequence() and qrd_well_depths() for QRD geometry
(s_n = n^2 mod N; d_n = s_n lambda_0 / (2 N)).
- DiffuserPolarResponse result with the per-angle levels, the coefficient
and a semicircular polar .plot() (English and Spanish).
Validated by the exact-by-physics anchors: a flat panel collapses to a
single specular lobe and normalises to zero, while a well-designed QRD
scatters far more evenly and sits well above it. Adds conformance rows,
the surface-scattering guide section (EN and ES) with a predicted-diffusion
figure, tests and the regenerated API reference, taxonomy and llms output.
* Modernize the diffuser-design additions for ruff 0.16 and regenerate references
* Hoist array construction out of the diffuser-design raise assertions
* Regenerate conformance and references after rebase
* Anchor ISO 16251-1 floor-covering improvement on a real measurement
Replace the illustrative improvement spectrum with a real textile-carpet
measurement digitized from the vector chart of Foret, Chene and
Guigou-Carter (Forum Acusticum 2011, CSTB), which rates to the paper's
published DeltaLw = 29 dB. Add a conformance row and a clean-room test that
assert the rating and its robustness to the digitization tolerance, refresh
the laboratory-insulation guide figure and worked example to the measured
carpet, and cite the source (mirrored in Spanish).
* Clarify the digitized draft-standard provenance and pin the tolerance test endpoints
* Regenerate the floor-covering example report and references
The Materials and Surfaces theory page derives surface diffusion and porous
absorber prediction, yet its references frontmatter listed only Cox & D'Antonio
and Allard & Atalla. Add three open-access works by Jiménez and co-workers that
the page already speaks to: the metadiffuser paper (Jiménez, Cox, Romero-García
and Groby, 2017, Scientific Reports) on the surface-diffusion section, and the
two critical-coupling perfect-absorption papers (Jiménez, Groby, Pagneux and
Romero-García, 2017, Applied Sciences; Jiménez, Romero-García and Groby, 2018,
Acta Acustica) on the material-characterisation section.
Reference the same works from the scattering_diffusion and porous_absorber
module docstrings, and regenerate the committed API reference.
Add the edited volume Acoustic Waves in Periodic Structures, Metamaterials, and
Porous Media (Jiménez, Umnova and Groby, Eds., 2021, Springer) to the
bibliography page as a metamaterials companion to Cox & D'Antonio.
Mirror the frontmatter and bibliography changes in the Spanish pages with
translated notes, keeping authors, titles and DOIs identical.
ruff 0.16 broadens the default rule set (pyupgrade, isort, comprehensions and
more). Bump the dev pin to ruff>=0.16.0 and apply the resulting fixes across
the tree: PEP 585 built-in generic annotations, unquoted annotations under
from-future annotations, sorted imports and the assorted small lint fixes. A
re-export shim that relied on redundant import aliases now declares __all__
explicitly. The api reference is regenerated for the modernized signatures.
No runtime behavior changes; the full test suite passes.
The second half of the Vibration theory page derives point mobilities and
radiation efficiency from Cremer, Heckl and Petersson (Structure-borne sound,
Eq. 5.23 and Table 5.1) and Hopkins (Sound insulation, Eqs 2.227-2.230), and
ties the results to ISO 7626-1 mobility measurement and the ISO 7849 radiation
factor, yet the references frontmatter only listed Griffin and Mansfield. Add
the missing bibliography entries for those works so the rendered References
section matches what the prose actually cites.
Add the two table-of-contents anchors the theory index was missing for this
page: the point-mobility and radiation-efficiency section and the multiple-
shock subsection.
Mirror both changes into the Spanish pages with translated notes and labels.
* Add ISO 9053-1 static airflow-resistance report via .report()
Add a one-page PDF material airflow-resistance test report to
StaticAirflowResult, laid out like an accredited ISO 9053-1:2018
static/direct airflow-method certificate.
The fiche carries a standard-basis line, an optional metadata header
(client, manufacturer, specimen, specimen thickness, test facility,
date, climate), a two-panel body with a metrics table (the evaluation
velocity, the fitted pressure difference, the airflow resistance R, the
specific airflow resistance R_s, the airflow resistivity sigma when a
thickness is available, and the through-origin fit coefficients a and b)
beside the fitted pressure-drop curve, and a boxed specific airflow
resistance R_s with R and sigma alongside, all read at the clause 7.5
reference velocity of 0.5 mm/s. ISO 9053-1 is a material
characterisation, so the fiche carries no pass/fail verdict.
The renderer reuses the shared report layout and metadata container, and
renders in English and Spanish. A worked example is registered in the
report generator and its rendered fiche committed alongside the others.
* Document the plot dependency and use a language-neutral precision note
The static airflow-resistance fiche embeds the fitted curve, so state that
rendering needs both reportlab and matplotlib (phonometry[report,plot]) in the
English and Spanish guides and the materials doc. Describe the evaluation-velocity
precision as one decimal place rather than with a locale-specific separator, and
regenerate the API page.
* Extract the shared material-test fiche scaffold
The dynamic-stiffness (EN 29052-1 / ISO 9052-1) and static airflow-resistance
(ISO 9053-1) fiches share the same one-page shape: title and basis line,
metadata identity grid, a metrics table beside the result's self-scaling plot,
a boxed single-number result with extended terms, and the footer. Move that
common scaffold into a single _material_fiche helper (the content dataclass, the
identity-grid builder, the standard-basis-line helper and the renderer) and have
both fiches supply only their own labels, metric rows and boxed statement. The
rendered output is unchanged.
* Add element-normalized intensity insulation report via .report()
Add IntensityElementNormalizedResult.report(): a one-page PDF fiche for
the element-normalized level difference DI,n,e of a small building element
measured with sound intensity (ISO 15186-1:2000, Clause 3.9, Formula (8)).
DI,n,e is a level difference rated by the ISO 717-1 airborne machinery, the
same as the sibling intensity sound reduction index RI, so the fiche is
driven through the shared insulation skeleton (render_insulation_fiche) with
the shared iso717_columns_builder. It lives in the existing iso15186.py
renderer beside the RI fiche, so no second near-identical renderer is added.
The sheet carries the standard-basis line, an optional metadata header, the
per-band table (16 one-third-octave or 5 octave bands) beside the
measured-versus-shifted-reference curve, the boxed rating DI,n,e,w (C; Ctr)
and the intensity-method statement. verbose=True shows the ISO 717 evaluation
per band; a metadata requirement adds a PASS/FAIL verdict (the element
insulation passes at or above the target); language="es" renders the Spanish
fiche.
The example is anchored to the ISO 717-1:2020 Annex C worked-example curve
read as a documented DI,n,e spectrum, pinning the rating to the published
30 (-2; -3) dB through the intensity path without a new numeric oracle. It is
registered in the report generator with its committed PDF and WebP preview,
and the guides and API reference are updated.
* Share the intensity-report request validation helper
Extract the engine, language, rating-presence and band-count guards the two
ISO 15186-1 intensity report methods share into a single
_validate_intensity_report helper, mirroring the flanking-transmission module.
It returns the validated non-None rating so each report method hands it
straight to the renderer, removing the duplicated guard block.
* Regenerate API reference after rebase
* Add wind-turbine tonal audibility report via .report()
Add WindTurbineTonalityResult.report() rendering a one-page PDF
wind-turbine tonality-assessment fiche (IEC 61400-11:2012+A1:2018,
subclauses 9.5.2-9.5.5).
The sheet carries a standard-basis line, an optional metadata header
(source/situation, client, measurement position, instrumentation, date),
a two-panel body with the critical-band and masking analysis in a metrics
table (tone frequency, critical bandwidth, tone level L_pt, masking-noise
level L_pn, tonality dL_tn, audibility criterion L_a and tonal audibility
dL_a) beside the narrowband-spectrum plot with the critical band, masking
level and tone marked, and a boxed decisive tonal audibility dL_a with the
tone frequency and the audibility decision (a tone is audible when dL_a
exceeds 0 dB). A maximum acceptable tonal audibility supplied via the
metadata requirement adds a PASS/FAIL verdict (a lower audibility is
better). English and Spanish both render.
Register the example fiche, add structural and i18n tests, and regenerate
the committed example PDF, WebP preview and API reference.
* Base the audibility decision on the displayed rounded tonal audibility
The boxed result, verdict and decision text all commit to the tonal
audibility rounded as displayed, but the decision phrase and note branched
on the raw is_audible flag. A raw dL_a just above 0 dB that rounds to a
displayed 0.0 dB could therefore print a self-contradicting "0.0 dB > 0".
Derive the audibility decision from the same rounded value through a shared
helper, and add a boundary regression test.
* Add field and survey building-insulation reports via .report()
Give the field and survey building-insulation results a one-page PDF field
test report, reusing the shared ISO 717 insulation skeleton so each is a
per-band curve rated against a shifted reference:
- SurveyAirborneResult, SurveyImpactResult and SurveyFacadeResult (ISO
10052:2021 survey/control method, octave bands) report DnT (or R'), L'nT and
D2m,nT with their ISO 717 weighted single number and the survey-method
statement.
- FacadeInsulationResult (ISO 16283-3:2016 field facade, one-third-octave
bands) reports D2m,nT (default), D2m,n or R'45 with the D2m,nT,w (C; Ctr)
rating and the engineering-method statement.
Each sheet carries the standard-basis line, an optional metadata header, the
per-band table beside the measured-versus-shifted-reference curve, the boxed
single number, an optional requirement verdict (level differences and
reduction indices pass at or above the target, the impact level at or below
it) and a footer; verbose=True annexes the ISO 717 evaluation per band, and
both English and Spanish render.
The shared ISO 717 default/verbose columns builder is hoisted into the
insulation-fiche helper so the survey, field-facade and flanking renderers
drive a single implementation. Registers one committed example per report,
extends the field-insulation guide (EN and ES), and updates the changelog.
* Share the field engineering-method statement across the ISO 16283 specs
* Caption the survey table by its band set and state the facade rating per quantity
The ISO 10052 survey report hard-coded the octave-band table caption, so a
one-third-octave survey report (16 bands, which the survey API accepts) was
mislabelled; derive the caption from the reported curve length and cover it
with a one-third-octave regression test. Correct the facade report
description: it boxes the ISO 717 weighted rating of the reported quantity
(D2m,nT,w, D2m,n,w or R'45,w), not always D2m,nT,w.
* Add structural-vibration FRF reports via .report() (ISO 7626, ISO 10846)
Add a one-page PDF .report() fiche to the two structural-vibration
frequency-response result types.
MobilityResult.report() renders a mechanical-mobility measurement fiche
(ISO 7626-1:2011 frequency-response-function definitions; measurement per
ISO 7626-2:2015). Mechanical mobility is a continuous frequency-response
function, not an octave-band quantity, so the sheet presents it honestly as the
mobility magnitude spectrum |Y(f)| plus a compact table of the FRF's
characteristic points (the FRF type, driving-point or transfer, the frequency
range, the peak frequency, the peak mobility magnitude and the phase there),
with a boxed peak mobility |Y| at the frequency it occurs at. It is a
characterisation, so there is no pass/fail verdict.
TransferStiffnessResult.report() renders a dynamic-transfer-stiffness
characterisation fiche for a resilient element (ISO 10846-1:2008 definition;
determined by the direct method, ISO 10846-2:2008, or the indirect
blocking-mass method, ISO 10846-3:2002). The transfer stiffness is a continuous
frequency-response function, so the sheet presents it as the transfer-stiffness
level spectrum Lk(f) plus a compact table of characteristic points (the
determination method, the blocking mass for the indirect method, the frequency
range, and the low-frequency stiffness plateau |k2,1|, its level Lk and the loss
factor there), with a boxed low-frequency Lk. It too is a characterisation, with
no verdict.
Both fiches share a common FRF body (_report/_frf_fiche.py): the title and
basis line, the optional metadata header, the two-panel body with the
characteristic-point table beside the result's own spectrum plot, the boxed
representative value and the footer. English and Spanish both render; example
inputs reuse the modules' oracle-validated closed forms.
* Reuse the result loss_factor in the transfer-stiffness fiche
Share the single ISO 10846-1 (3.8) loss-factor definition by reading the
result's own loss_factor property at the low-frequency index, instead of
recomputing eta = Im/Re in the renderer.
* Drop the unused low-frequency frequency in the stiffness table
The characteristic-point table only needs the magnitude, level and loss
factor at the low-frequency plateau, not its frequency.
Add a one-page PDF .report() to the two reverberation result types.
ReverberationModelResult.report() renders a design-stage prediction of the
reverberation time by the five classical statistical-acoustics models (Sabine,
Eyring, Millington-Sette, Fitzroy and Arau-Puchades): a per-band table with one
reverberation-time column per model beside the model comparison plot, and a
boxed mid-frequency reverberation time from Arau-Puchades with the per-model
spread alongside. It is labelled a prediction, not a measurement: the five
models bracket the reverberation time likely to occur, so no PASS/FAIL verdict
is emitted; a target reverberation time is printed as a reference line only.
ReverberationResult.report() renders an enclosed-space characterisation
(EN 12354-6:2003): a per-band table of the equivalent sound absorption area A
and the reverberation time T beside the reverberation-time plot, the room
volume and object fraction in the header, and a boxed mid-frequency
reverberation time with the mid-frequency absorption area alongside. A target
reverberation time is likewise printed as a reference line without a verdict,
since a room reverberation time is a target range rather than a strictly
higher/lower-is-better quantity.
Both renderers live in a shared _report/reverberation.py module (shared
mid-frequency descriptor, time formatting, octave-band table and header grid
helpers). Add the English and Spanish fixed strings, one committed example
fiche per result under .github/reports/, structural and value-presence tests
in English and Spanish, and the guide sections in both languages. Regenerate
the API reference.
* Add ISO 1999 noise-induced hearing-loss prediction reports via .report()
Add one-page PDF prediction fiches to the two occupational-hearing-loss
result types of the ISO 1999:2013 module. Both sheets are clearly labelled
statistical predictions for a noise-exposed population, not clinical
diagnoses of any individual.
NiptsResult.report() renders the noise-induced permanent threshold shift
(clause 6.3): a prediction-basis line, an optional metadata header, a
per-audiometric-frequency table of the median N50 and the NIPTS at the chosen
population fractile beside the shift spectrum, and a boxed representative shift
averaged over the 2/3/4 kHz hearing-handicap set with the exposure conditions.
verbose=True adds the upper/lower spread columns.
HtlanResult.report() renders the hearing threshold level associated with age
and noise (clause 6.1): a per-audiometric-frequency table of the age component
H, the noise component N and the combined threshold H' = H + N - H*N/120
beside the plot, and a boxed representative combined threshold. verbose=True
adds the H*N/120 compression term.
A maximum acceptable representative value supplied through the metadata
requirement adds a PASS/FAIL verdict (a lower value is better); without it
neither fiche prints a verdict. Both fiches render in English and Spanish.
Register one example per fiche, add structural and number-presence tests in
both languages, and regenerate the API reference and the committed example
artifacts.
* Drop the unused caption style from the prediction-notes helper
The shared ISO 1999 prediction-notes block builds its own muted paragraph
style, so it never used the caption style threaded in from the callers.
* Add ISO 9613-2 outdoor-propagation prediction reports via .report()
Add one-page PDF prediction fiches to the two outdoor-propagation result
types and a shared ISO 9613-2 family renderer.
OutdoorAttenuation.report() renders the octave-band attenuation breakdown
(divergence, atmospheric, ground and barrier terms with the total A), the
source power level and the downwind level LfT(DW), the attenuation-breakdown
plot and a boxed A-weighted downwind level LAT(DW) at the receiver. The sheet
is clearly labelled a prediction, not a measurement, and states the
meteorological and ground assumptions. A declared limit level via the metadata
requirement adds a PASS/FAIL verdict (a lower level is better). To carry the
composed level, outdoor_propagation_attenuation now accepts an optional
sound_power_level (with directivity_index, d_omega and c0).
BarrierInsertionLoss.report() renders the per-band insertion loss, the
insertion-loss spectrum and a boxed mean insertion loss over the octave bands.
Its basis line names the actual diffraction model used (the wave-theoretic
rigid-screen solution or the Kurze-Anderson closed form), a wave-acoustics
complement to the tabulated ISO 9613-2 screening term. A minimum required
insertion loss via requirement adds a PASS/FAIL verdict (higher is better).
Both fiches render in English and Spanish, register an example each under
.github/reports, are covered by structural and number-presence tests, and are
showcased in the Outdoor Sound Propagation guide.
* Regenerate API reference for the outdoor propagation report method
* Move the outdoor-propagation receiver level to a report-time SourceEmission
Keep outdoor_propagation_attenuation a pure attenuation calculation (its
signature is unchanged) and expose the downwind receiver level through a new
frozen SourceEmission object passed to OutdoorAttenuation.report(). When a
source emission is supplied the fiche lists the source power and the downwind
level and boxes the A-weighted downwind level at the receiver; without it the
fiche boxes the octave-band range of the total attenuation. The receiver-level
composition stays in the shared _compose_receiver_level helper.
Hoist the result and object construction out of the pytest.raises blocks in the
report tests so only the failing call is inside.
* Re-trigger CI for the outdoor-propagation report changes
* Restore Spanish diacritics and correct the composition-helper docstring
Add the missing accents to the two Spanish requirement comments in the outdoor
propagation guide, and update the receiver-level helper docstring: the level is
composed at report time from a SourceEmission, not stored on the attenuation
result, so drop the stale reference to the factory storing it.
* Drop the stray npm lockfile from the site
The site uses pnpm (pnpm-lock.yaml); an npm package-lock.json was generated
by accident during the site build and does not belong in the tree.
* Add noise-control performance reports via .report()
Add a one-page PDF .report() fiche to the three noise_control result
types, each laid out with a per-band table beside the result's own plot,
a boxed single-number performance figure and an optional PASS/FAIL
verdict:
- EnclosureResult: machine-enclosure insertion loss (Bies, Hansen &
Howard, section 7.4.2). The table lists the supplied panel transmission
loss R, the interior build-up correction C and the net insertion loss
IL = R - C; the boxed figure is the mean insertion loss with the
external and internal surface areas. verbose=True adds the interior
room constant column. A declared minimum passes when the mean meets it.
- ReactiveSilencerResult: reactive-silencer transmission loss (Munjal
Eq. (3.27); Bies sections 8.8-8.9). The table lists the transmission
loss TL and, when end impedances were given, the insertion loss IL; the
boxed figure is the mean transmission loss with the peak and the device
kind. A declared minimum passes when the mean meets it.
- HvacSpectrumResult: HVAC duct-noise spectrum (Bies Chapter 8; VDI
2081-1). A regenerated-noise spectrum boxes the A-weighted sound power
level with the overall total (lower is better); an attenuation spectrum
boxes the mean attenuation (more is better). verbose=True adds the
A-weighting correction and A-weighted band-level columns.
The three renderers share a two-panel skeleton in
_report/_noise_control_fiche.py and reuse the sound-power table builder,
band labels and header grid. Each accepts an optional metadata header,
states its method basis and renders in English or Spanish.
Register one committed example per fiche under .github/reports/, add
structural and clean-room number-presence tests (EN and ES), and update
the CHANGELOG and the regenerated API reference.
* Address SonarCloud findings on the noise-control renderers
Reduce render_noise_control_fiche below the parameter-count threshold by
fixing the two-panel split widths internally (the three renderers never
overrode them), and lift the HVAC verdict symbol/unit selection out of a
nested conditional into an explicit if/elif/else. No change to rendered
output; the committed example fiches are unaffected.
* Round the requirement to display precision and fit the verbose table
Compare the declared requirement at the same one-decimal precision as the
measured value in the noise-control verdict, so the printed comparison can
never contradict the verdict at the boundary. Also trim the verbose
enclosure table columns to sum to the 64 mm left panel width.
* Speech-intelligibility reports via .report() (STI and SII)
Add a one-page PDF fiche to the two speech-intelligibility result types.
STIResult.report() renders an IEC 60268-16:2020 speech-transmission-index
fiche for verifying voice-alarm and public-address intelligibility: a
standard-basis line naming the measurement method (the full STI indirect
method from an impulse response, or the direct STIPA method on a recorded
signal), an optional metadata header, a per-octave-band modulation transfer
index table beside the per-band MTI bars, and a boxed STI with the Annex F
qualification band. A minimum STI supplied via the metadata requirement adds a
PASS/FAIL verdict (a higher STI passes).
SIIResult.report() renders an ANSI S3.5-1997 speech-intelligibility-index
fiche (one-third-octave-band method) for a speech-audibility assessment: a
per-one-third-octave-band table of the equivalent speech spectrum, the Table 3
band-importance function and the band-audibility function beside the audibility
and importance-weighted contribution bars, and a boxed SII. verbose adds the
equivalent disturbance spectrum level column. A minimum SII supplied via the
requirement adds a PASS/FAIL verdict (a higher SII passes).
Both reuse the shared report layout and metadata container, render in English
and Spanish, register a committed example fiche under .github/reports, and are
documented in the speech-transmission and speech-intelligibility guides.
* Address review: Spanish diacritics and invalid-language docstring
* Hoist the report path out of the intelligibility raise assertions
Build the output path before each pytest.raises block so only the failing
report call remains inside it.
* Room-noise rating reports via .report() (ANSI/ASA S12.2-2019)
Add a one-page PDF room-noise assessment fiche to both room-noise rating
result types of ANSI/ASA S12.2-2019:
- NCResult.report(): the Noise Criteria rating by the tangency method
(Table 1). The box states NC-nn with the governing octave band.
- RCResult.report(): the Room Criteria Mark II rating (Annex D). The box
states RC-nn(tag) with the mid-frequency average LMF and the
neutral/rumble/hiss spectral quality.
Both fiches share one renderer built on the accredited-report engine: a
standard-basis line, an optional metadata header, the measured octave-band
levels beside the measured spectrum plotted against the NC/RC curve family
(the result's own plot), the boxed rating and a footer disclaimer. A target
rating on the metadata requirement adds a PASS/FAIL verdict, where a lower
rating passes. With verbose=True the table gains the per-band NC contour
value read by the tangency method, or the reference RC Mark II curve and the
measured deviation from it. English and Spanish both render.
Register one example per fiche in the report generator (an NC-40 office
spectrum governed by the 250 Hz band and an RC-35(R) rumble spectrum, both
built from the tabulated curves so the ratings are exact), document the
method on the room-noise guide, and cover the reports with structural,
number-presence, one-page and EN/ES tests. Regenerate the API reference and
the conformance report.
* Factor out the shared room-noise table builder
Extract the common frequency/level columns and the caption-plus-table
assembly shared by the NC and RC left cells into helpers, so the two
renderers only add their own evaluation columns.
* Drop the unused table-language parameter and hoist the raise setup
_value_table never used its language argument (its cells arrive already
formatted), so remove it and its call site. Build the result before the
engine and language rejection assertions so only the reporting call sits
inside pytest.raises.
* Laboratory flanking-transmission reports via .report() (ISO 10848)
Add a one-page PDF fiche to each of the three ISO 10848 laboratory
flanking-transmission results.
VibrationReductionResult.report() writes a junction-characterization
report of the vibration reduction index Kij (ISO 10848-1:2006): the
standard-basis line, an optional metadata header, the per-band Kij table
beside the Kij(f) curve and a boxed single-number mean Kij over the
Annex A band range (200 Hz to 1250 Hz for one-third-octave bands, 125 Hz
to 1000 Hz for octave bands) with the count of averaged and bracketed
bands. Bands bracketed for poor modal overlap (M < 0,25,
ISO 10848-4:2010 Clause 9) print their value in brackets and are excluded
from the mean; verbose=True adds a column stating whether each band
enters the mean.
FlankingLevelDifferenceResult.report() and
FlankingImpactLevelResult.report() write measurement reports of the
overall descriptors Dn,f (airborne) and Ln,f (impact, tapping machine,
ISO 10848-2:2006), reusing the shared two-panel insulation report
skeleton: the per-band quantity beside the measured-versus-shifted
ISO 717 reference curve and the boxed single number Dn,f,w (C; Ctr)
(ISO 717-1) or Ln,f,w (CI) (ISO 717-2). verbose=True annexes the ISO 717
evaluation per band (the value, the shifted reference and the
unfavourable deviation). A requirement on the metadata adds a PASS/FAIL
verdict (higher is better for Dn,f,w, lower for Ln,f,w), and
language="es" renders every fiche in Spanish.
Register one example fiche per report kind in the generator with its
committed PDF and WebP preview, document the reports in the EN and ES
laboratory-insulation guides and the docs mirror, add the Spanish
strings, and regenerate the API reference and llms-full.txt.
* Fix the Kij single-number membership shown in the verbose fiche
The verbose Kij table marked a band as entering the single number from its
bracket flag alone, so a non-bracketed band outside the Annex A range (for
instance above 1250 Hz in the one-third-octave case) was labelled as counted
while the boxed "bands averaged" total correctly left it out. Share one
membership mask (inside the Annex A range and not bracketed) between the table
column and the result box so the two always agree, and lock it with a unit
test on the range exclusion. Hoist the fiche construction out of the
engine-rejection pytest.raises blocks.
* Structure-borne sound power characterization and installed prediction reports via .report()
Add one-page PDF .report() fiches to the two structure-borne building
result types, closing the EN 15657 -> EN 12354-5 chain from source
characterization to installed prediction.
StructureBornePowerResult.report() renders an EN 15657:2018 reception-plate
structure-borne sound power characterization: the per-band spatial mean plate
velocity level Lv and the injected power level L_Ws (Formula 14), the L_Ws(f)
spectrum, and the boxed band-summed total L_Ws (dB re 1 pW) with the plate
mass per area m and area S. verbose=True adds the plate loss factor eta
column; the basis strip states Formula 14 and the conversion to the
plate-independent source quantities required before EN 12354-5.
InstalledSourceResult.report() renders an EN 12354-5:2009 installed
structure-borne sound prediction, clearly labelled a prediction and not a
measurement: the per-band installed power level L_Ws,inst, each transmission
path's normalised SPL L_n,s,ij and the combined total L_n,s, the per-path and
total L_n,s(f) spectra, and the boxed band-summed total L_n,s. verbose=True
adds one column per transmission path; the basis strip states Formulae 18a/17
and the prediction disclaimer.
Both fiches reuse the shared sound-power report engine (per-band table,
spectrum, boxed result and flow assembly), extended with a caller-supplied
verdict so the structure-borne quantity symbols are stated consistently, and
render in English and Spanish. Register one example per fiche, add structural
and number-presence tests, document the reports in the guides, and record the
additions in the changelog.
* Address review: per-path column [dB] units and Spanish accent
Add the [dB] unit to the installed-prediction table's per-path column headers
so they match the installed-power and total columns, and correct the accent in
the Spanish structure-borne power guide (acelerometro -> acelerometro with the
acute accent).
* Add FacadePredictionResult.report() for predicted facade insulation (EN 12354-3)
Render a one-page prediction fiche for the predicted standardized level
difference of a facade D2m,nT (EN/ISO 12354-3, Formula 13), reusing the
shared EN/ISO 12354 prediction report body: the facade-element table
(each element's weighted partial index Rp,w, with an optional transmitted
energy share) beside the result's own per-element / R' / D2m,nT plot, the
boxed predicted D2m,nT,w and the prediction statement. Clearly labelled a
prediction from element data, never a measurement.
* Test the predicted facade insulation fiche against EN 12354-3 Annex F
Pin the facade fiche to the Annex F worked example (D2m,nT,w = 33 dB) run
through the tested prediction code: pypdf assertions on the boxed single
number, the prediction / not-a-measurement wording, the model terms, the
per-element partial indices, the verbose energy share, the requirement
verdict, the Spanish fiche and the missing-rating guard.
* Add the EN 12354-3 facade prediction example fiche and regenerate artifacts
Add the Annex F facade prediction example to generate_reports.py with its
committed PDF and WebP preview, and regenerate the generated API reference
and llms bundle for the new report() method.
* Document the predicted facade insulation report (EN+ES)
Add the facade prediction report subsection to the EN and ES prediction
guides (with a ReportPreview of the committed example fiche) and a
CHANGELOG entry.
* Show the apparent-index values on the facade fiche and unify R'tr,s,w notation
State the computed apparent traffic-referenced index and spectrum term as
values on the predicted facade fiche (R'tr,s,w = 31 dB, Ctr = -3 dB in the
Annex F example), not just their symbols, by filling the basis line with the
result's own numbers. Use the primed apparent-index notation R'tr,s,w
consistently across the fiche, the guides and the example comments.
* Add ISO/TS 7849 sound-power-from-vibration .report() fiche
Render VibrationSoundPowerResult (airborne sound power radiated through
surface vibration, ISO/TS 7849-1/-2:2009) to a one-page PDF fiche via the
shared sound-power report engine. The vibration-method variant adds the
surface velocity level Lv and radiation factor epsilon columns, the
radiating area S in the boxed result and the LW = Lv + 10 lg(S/S0) +
10 lg(epsilon) + 10 lg(411/400) basis strip, and names the survey (Part 1,
fixed epsilon = 1) or engineering (Part 2, determined epsilon) method.
Add a sound_power_level_a property (A-weighted total) to the result and the
Spanish translations for the new strings.
* Add the ISO/TS 7849 example fiche and its committed preview
Register an engineering-method (Part 2) example in generate_reports.py: a
gearbox casing of radiating area S = 1.6 m2 surveyed over six octave bands
with a measured radiation factor, giving LWA = 88.7 dB(A) re 1 pW against a
declared 90 dB(A) limit. Commit the rendered PDF and its WebP preview.
* Test the ISO/TS 7849 sound-power-from-vibration fiche
Recompute LW and LWA from the closed-form ISO/TS 7849 Eq. 3/8/12 against a
clean-room oracle and assert they, the band labels, the method part and the
basis prose appear in the PDF; cover the survey/engineering variants, the
verbose radiation-factor column, the verdict, the metadata header, the
Spanish fiche and the rendering contract.
* Document the ISO/TS 7849 report and regenerate the API reference
Add a measurement-report section to the EN and ES vibration-sound-power
guides (with the ReportPreview) and the docs mirror, regenerate the
generated API page for the new report() and sound_power_level_a members, and
record the addition in the changelog.
* Address review on the ISO/TS 7849 report
- Do not present an unweighted broadband LW as LWA: sound_power_level_a now
returns nan without a band spectrum, so the fiche boxes the unweighted total
LW and draws no A-weighted verdict, and the basis strip omits the A-weighting
sentence for a broadband result.
- Drop the unused result parameter from the relation-strip helper.
- Render the fixed impedance term with the locale decimal separator (0.12 dB in
the English fiche, 0,12 dB in the Spanish one) via format_number.
- Cite the accelerometer calibration standard (ISO 16063-21) in the example
instead of IEC 60651, which specifies sound level meters.
- Assert the one-page contract on the longer one-third-octave table and add a
broadband test asserting no false A-weighted claim.
Regenerate the example fiche PDF and WebP preview and the generated API page.
Add .report() PDF fiches to AirbornePredictionResult and ImpactPredictionResult that render a predicted building sound-insulation calculation report per EN/ISO 12354-1/-2: the model terms and the single-number predicted apparent sound reduction index or normalized impact level, clearly labelled as a prediction rather than a measurement.
Add a .report() PDF fiche to ReverberationSoundPowerResult that renders a sound power determination report for the reverberation-room precision method (ISO 3741): the per-band room pressure and sound power levels, the sound power spectrum and the boxed A-weighted level, reusing the shared sound-power report body.
Add a .report() PDF fiche to IntensityReductionResult that renders a laboratory intensity sound-insulation test report per ISO 15186-1: the per-band intensity sound reduction index with the shifted ISO 717-1 reference curve and the single-number rating. The report body reuses the shared insulation-fiche helper.
Add a .report() PDF fiche to ImpulseProminenceResult that renders an impulsive-sound assessment: the per-impulse prominence table (capped with a truncation note, always keeping the governing impulse), the prominence plot, and the boxed governing prominence with the derived LAeq adjustment.
Add a .report() PDF fiche to DynamicStiffnessResult that renders the dynamic stiffness of a resilient material under a floating floor per ISO 9052-1: the apparent dynamic stiffness per unit area, the resonance frequency and the air-stiffness correction, with the design curve.
Add a .report() PDF fiche to SoundPowerIntensityResult that renders a sound-power-by-intensity determination report per ISO 9614: the per-band sound power level from the surface intensity, the sound power spectrum and the boxed A-weighted level, with the measurement grade and field-indicator quality criteria. The sound-power report body is shared with the ISO 3744 pressure-method fiche through a common helper.
Add .report() PDF fiches for the ISO 17497 surface descriptors: the random-incidence scattering coefficient (ISO 17497-1), the directional diffusion coefficient spectrum (a new DiffusionSpectrum result, ISO 17497-2 with per-band source-position averaging), and the single-source polar-response diffusion fiche. The report bodies reuse the shared two-panel and polar figure helpers.
Add a .report() PDF fiche to FloorCoveringImprovementResult that renders the reduction of transmitted impact sound by a floor covering on a lightweight mock-up: the per-band improvement spectrum and the weighted reduction, rated per ISO 717-2. The report body reuses the shared insulation-fiche helpers.
Add a .report() PDF fiche to ImpedanceTubeResult that renders an impedance-tube test report: the per-frequency normal-incidence absorption coefficient and normalized surface impedance in a table, and the absorption curve over the tube's valid frequency range. The shared two-panel report body is unified with the ISO 354 fiche.
Add a .report() PDF fiche to ToneAudibilityResult that renders an ISO 1996-2 tonal assessment: the spectrum with the detected tones and their critical-band masking noise, the per-tone key quantities, and the boxed decisive tonal audibility with the derived tonal adjustment.
Add a .report() PDF fiche to LabAirborneInsulationResult and LabImpactInsulationResult that renders a laboratory sound-insulation test report: the per-band sound reduction index or normalized impact level with the shifted ISO 717 reference curve and the single-number rating. The two-panel insulation report body is shared with the field (ISO 16283) report through a common helper.
Add a .report() PDF fiche to SoundPowerResult and PrecisionSoundPowerResult that renders a sound power determination report: the per-band surface pressure and sound power levels, the sound power spectrum, and the boxed A-weighted sound power level, with the method grade and correction basis, per ISO 3744 (engineering) and ISO 3745 (precision).
Add a SeabedReflection result and the seabed_reflection constructor wrapping the existing plane-wave reflection coefficient at a fluid-fluid seabed interface, with a .plot() that draws the reflection coefficient magnitude against grazing angle and marks the critical angle. This complements the existing BottomLossResult (bottom loss in dB).
Add a StandardSpeechSpectrum result and the standard_speech_spectra constructor wrapping the existing ANSI S3.5-1997 standard speech spectrum band levels, with a .plot() that draws the band level per one-third-octave band for the selected vocal efforts.
Add an AtmosphericAttenuation result and the atmospheric_attenuation constructor wrapping the existing ISO 9613-1 pure-tone attenuation calculation, with a .plot() that draws the attenuation coefficient in dB/km against a logarithmic frequency axis with a linear decibel ordinate, plus an optional total attenuation over a propagation distance.
Add a PistonDirectivity result and the piston_directivity_pattern constructor wrapping the existing baffled-piston far-field directivity, with a .plot() that draws the beam pattern in dB as a polar diagram for one or more ka values.
Add a KWeightingResponse result and the k_weighting_response constructor wrapping the existing ITU-R BS.1770 K-weighting biquads, with a .plot() that draws the combined magnitude response and its shelf and high-pass stages on a logarithmic frequency axis.
Add a .plot(quantity=...) method to LoudspeakerCharacteristics and MicrophoneCharacteristics that draws one rated characteristic per figure (response, impedance, THD, directivity for the loudspeaker; response, directivity, inherent noise, distortion for the microphone), sharing the panel drawing with the .report() data sheets so the plot and the report never diverge.
Add an EqualLoudnessContours result and the equal_loudness_contours constructor wrapping the existing ISO 226:2023 functions, with a .plot() that draws the normal equal-loudness contour family and the hearing threshold. A short note records that the standard defines the contours from 20 to 90 phon, with the 90 phon contour valid only up to 4 kHz.
Share the verdict-label and sequence-median drawing between the trend-test and stationarity-test plot renderers via two small helpers, keeping the rendered output identical.
Add a .plot() method to RigidMassCalibrationResult that draws the ISO 7626-2 operational rigid-mass calibration check: the measured driving-point FRF magnitude against the known rigid-mass line with its tolerance band, and the relative deviation against the same band, with out-of-tolerance points highlighted and the verdict in the title.
Add a .plot() method to TrendTestResult that draws the tested sequence against its sample index with the reverse-arrangements or runs statistic, the acceptance region and the trend verdict, mirroring the stationarity-test plot.
Add a .report() PDF fiche to MultipleShockResult that renders a whole-body multiple-shock health-risk assessment per ISO 2631-5:2018: the spinal response acceleration dose, the daily equivalent static compressive stress and the stress variable R with the injury probability, classified against the Annex C risk bands, above the injury-probability chart.
Add a .report() PDF fiche to OpenPlanResult that renders an open-plan office acoustics report per ISO 3382-3: the spatial decay rate of A-weighted speech D2,S, the speech level at 4 m Lp,A,S,4m and the distraction and privacy distances, above a full-width spatial-decay curve with the regression line and distance markers, plus an optional verdict against a target supplied through the report metadata.
Add a .report() PDF fiche to RoomAcousticsResult that renders a room acoustic parameters measurement report per ISO 3382-1/-2: a per-band table of T20, T30, EDT, C50, C80, D50 and Ts alongside a reverberation-time curve, with the mid-frequency T30 as the boxed value and an optional verdict against a target reverberation time supplied through the report metadata.
Add a .report() PDF fiche to DailyVibrationExposure that renders a daily hand-arm or whole-body vibration exposure assessment: the per-operation exposure table, a colour-coded contribution chart, the boxed A(8) with its zone, and the assessment against the Directive 2002/44/EC action and limit values. The committed example reproduces the ISO 5349-2 Annex E.3 worked case.
Add a public reverberation-room sound absorption measurement result (BS EN ISO 354:2003) with a .plot() and a normative PDF .report() fiche. The new SoundAbsorptionMeasurement result and measure_sound_absorption constructor wrap the existing absorption-area and absorption-coefficient functions, exposing the empty and specimen equivalent absorption areas and the sound absorption coefficient per third-octave band.
Add time synchronous averaging with the comb-filter model, exact-recovery and sqrt(N) noise-reduction behaviour, an EN/ES guide and conformance rows. Generalise MISO coherence to any number of correlated inputs and correct the scipy minimum-version wording.
Final pass over the "Signals and spectra" guides now that the whole set is
in place.
- Cross-links: the spectral-analysis "Relation" section now points to the
multiple/partial coherence guide, and the correlation-delay one points to
the envelope spectrum and the fractional-delay/resampling tools, so every
guide in the area has an inbound link from a sibling. EN and ES in parity.
- Landing and index accuracy: the Core signal analysis landing was missing
the multiple/partial coherence, test-signals and data-qualification
entries; the signals-and-spectra sub-landing prose was missing the
multiple/partial coherence paragraph. Added, EN and ES identical.
- Mirror completeness: added the GitHub-tree mirrors for the sound level
meter walk-through and the time-frequency guide (the only two guides that
had no mirror), listed them in the docs index, and synced the two
cross-link additions into the mirror. Regenerated llms-full.txt.
- Fixed a broken API link in the system-measurement guide
(rooms/room_ir -> rooms/room-ir) and shortened the system-measurement page
title so the rendered document title stays within 70 characters.
- CHANGELOG: merged the fragmented [Unreleased] subsections into one Added,
one Changed and one Fixed block in Keep a Changelog order, without losing
any of the 185 entries, and completed six entries that had been committed
as truncated single-line fragments.
* Multiple and partial coherence for MISO systems (Bendat & Piersol)
Add miso_coherence for the multiple-input/single-output coherence
functions of Bendat & Piersol, Random Data 4e, Chapter 7. From the Welch
cross-spectral matrix of two or three partially correlated inputs and one
output it reports:
- the ordinary coherence of each input with the output (Eq. 7.109);
- the multiple coherence explained by all inputs jointly (Eq. 7.35), equal
to SNR/(1+SNR) for additive output noise of known level;
- the partial coherences (Eq. 7.87, 4th-edition total-output denominator)
obtained by the Gaussian-elimination conditioning of Section 7.3
(Eq. 7.94), so a source that only correlates with the true cause is no
longer credited for it;
- the partial coherent output spectra that decompose the output power
source by source (Eq. 7.86, with the shares plus the residual noise
reconstructing the output exactly), and a dominant_input() helper for the
strongest source per band.
The frozen MISOCoherenceResult also carries the Section 9.3 random errors
(the i-th ordered input keeps nd-(i-1) effective averages) and an EN/ES
.plot(). The estimators reuse the Welch core of spectra.py, so a MISO
coherence and a power_spectral_density taken with the same segment length
are consistent bin by bin.
The conditioning is pinned to the exact rational values of Problem 7.2
(G22.1 = 4/3, G2y.1 = 4/3, Gyy.1 = 13/3, partial coherence 2/15, multiple
coherence 0.7), and the identities are checked against the SNR relation and
the uncorrelated-input case where the partial coherences reduce to the
ordinary ones and their sum is the multiple coherence. Five conformance
rows, a test suite, an EN/ES guide with a worked figure, and the API
reference, bibliography and taxonomy entries accompany the module.
* Address MISO coherence review findings
- Give the coherent-output fills a fixed dB baseline derived once from the
finite dynamic range of the panel (new _finite_db_floor), clip the plotted
levels to it and set the y-limits explicitly, so a coherent output that
dips to zero no longer drags the axis through get_ylim() inside the loop.
- Share one pivot-safety mask between the coherent-output accumulation and
the Schur update (new _pivot_safe): a near-singular conditioned pivot is
now gated identically in both places, so the power decomposition
(sum of contributions + residual = Gyy) closes bin by bin. Add a singular
(perfectly collinear) and a near-collinear test covering the invariant.
- Reword the conformance description that contained literal pipe characters
("abs(G2y.1)^2/G22.1") so the generated CONFORMANCE.md table is not
corrupted; regenerate the report.
Harden a few edge cases surfaced while reviewing the system-measurement
and parametric-EQ code.
- shaped_sweep_signal: a very small 'seconds' next to a dominant
'start_delay' could round the two edges of the constant-envelope
window onto the same sample, leaving an empty core slice that made
the crest factor come out NaN or infinite. Fall back to the whole
retained sweep in that degenerate case, and cover it with a test.
- plot_shaped_sweep: the Welch grid is resolved independently of the
synthesis grid, so a narrow (f1, f2) band on a short signal could
leave no Welch bin inside the band and crash the normalization on an
empty slice. Fall back to the overall positive-frequency maximum as
the reference so the plot still renders.
- regularized_inverse_filter: document max_gain_db as the achieved
peak-normalized out-of-band gain the code actually returns (the
computation is unchanged), and narrow the plot() return type to a
single Axes.
- impulse_response (MLS): drop a redundant rec.size == 0 term that is
already guaranteed false by the non-empty check above it.
- Hoist input construction out of pytest.raises blocks in the inversion,
Golay, shaped-sweep and parametric-EQ tests so only the call under
test can raise.
Regenerates the inversion API reference from the updated docstrings.
The generated SVGs kept text selectable via svg.fonttype='none', but
matplotlib writes mathtext runs ('$...$') with a bare font-family
('DejaVu Sans', no generic fallback) while plain <text> carries the full
chain ending in sans-serif. Viewers lacking DejaVu Sans (iOS, macOS
Preview) then fell back to a serif for the math labels while the prose
stayed sans, an inconsistent legend.
save_figure now rewrites every font-family declaration in the saved SVG
to the generic 'sans-serif' so all text renders in the viewer's native
sans font, consistently and viewer-independently. Text stays selectable
(svg.fonttype unchanged). Regenerated all figures; only the SVG
font-family declarations change, the WebP rasters are untouched.
The committed 37-receiver polar arc in tests/reference_data.py is now
generated by the library's own Fraunhofer far-field model for a published
geometry: the N = 7 QRD, 6 periods, 3.6 m wide, 0.2 m deep row of
Cox & D'Antonio, Acoustic Absorbers and Diffusers, 3rd ed., Appendix B
(the commercial N = 7 QRD of Hargreaves, Cox, Lam & D'Antonio, JASA 108(4),
1710-1720, 2000, Table I). The arc is an arithmetic oracle for ISO 17497-2
Formulas (5)/(7): d = 0.1099, flat reference d = 0.0049, d_n = 0.1055,
recomputed exactly from the committed rounded levels.
A new external third-party anchor pins the model's one-third-octave
band-averaged normalised diffusion to the published Appendix B 2D BEM values
for that row at normal incidence in the 200-400 Hz bands (agreement within
0.01, asserted at +/-0.015) in the tests and in four new conformance rows.
Across the full published 100-5000 Hz range the model-vs-BEM mean absolute
deviation is about 0.09, documented everywhere as a low-band anchor.
The previous in-house COMSOL simulation arc and its numbers are removed from
the tests, the conformance report, the surface-scattering guide (EN/ES), the
theory pages and the diffusion_polar figure, which now all use the same
published-geometry prediction.
- ISO 3741:2010, 9.1.2/9.1.3: apply the background correction at each
microphone position (K1i, Eq. 14), correct the position levels first
(Eq. 15) and only then energy-average (Eq. 16). Pre-averaged 1D spectra
keep the single-K1 path, documented as an approximation.
- ISO 9614-2:1996, 10.6 b: omit the bands failing criteria 1 and/or 2 from
the A-weighted total, flag them in the new a_weighting_omitted_bands field
and name them in the fiche basis strip (EN/ES); warn when the criteria
inputs are absent and the screening cannot run.
- ISO 9614-3:2002, C.1.6.2: a band satisfying criterion 5 qualifies as a
final result even if FS(2) >= 2.
- ISO 3745:2012: bands above 10 kHz no longer abort the determination; only
the A-weighted total (outside the ISO 3744 Annex E table) is NaN.
- ISO 3744:2010, 8.2.3 / ISO 3746:2010, 8.3.3: K1 = 0 only strictly above
the upper criterion; Equation (16) still applies at exactly 15 dB / 10 dB.
- ISO 4871:1996, 3.16/6.2: verify dual-number declarations against the sum
of the separately rounded declared values (verified_dual), and lay the
dual-number fiche out exactly as Annex B.2 (no derived L_WAd row).
- Cite clause 8.4 for the apparent directivity indices and note that the
4.3.2 validity limit concerns K2A (per-band check kept, conservative).
Hand-derived oracles cover each correction; the ISO 4871 and ISO 9614-2
example fiches, the API reference pages and the llms text are regenerated.
Rate NC spectra by the standard's two-step procedure (5.2.2): the speech
interference level (clause 3.2, the average of the 500/1000/2000/4000 Hz
octave-band levels) selects the NC-(SIL) curve and the spectrum is
designated NC-(SIL) unless an octave band exceeds it, in which case the
tangency method (5.2.3) applies. NCResult gains sil, tangency_rating,
method, out_of_range and a label property; the tangency rating stays
available on every in-family result. Spectra outside the NC-15 to NC-70
family of Table 1 are flagged out of range instead of clamping the
interpolation to fabricated NC-71/NC-14 designations: above the family
the governing band is the maximum exceedance over the NC-70 curve and the
label reads ">NC-70 (band)"; below it, "<NC-15". Fiche, plot and
verdict render the flagged designation in English and Spanish.
On the RC Mark II side, warn when the clause D.4 minimum band set
(31.5 Hz through 4000 Hz) is incomplete, flag ratings outside the
tabulated RC-25 to RC-50 family (Table D.1) with an extrapolation note on
the fiche, and document the combined RH tag as a diagnostic extension
beyond the clause D.3.5 letters (N, R, H, RV; the RV vibration/rattle
classification needs the Table 6 test and is not implemented).
On the ISO 3382-1/-2 fiche, label the mid-frequency descriptor honestly
for one-third-octave data (only the 500 Hz and 1 kHz one-third-octave
bands are averaged, so the octave "500-1000 Hz" claim is dropped and the
bands are named), name the band of a fallback T30/EDT descriptor when the
range does not span both mid bands, and choose the EDT_mid vs EDT label
from EDT's own band coverage instead of T30's. Fiche verdicts (ISO 3382
and S12.2) now compare display-rounded values so the printed numbers can
never contradict the verdict at a tolerance boundary.
Correct two citations in the room-acoustics docstrings: the 5 %
reverberation-time JND is ISO 3382-1:2009 Table A.1 (ISO 3382-2:2008
Table A.1 tabulates the uncertainty constants G and H) and the -20 dB
onset trigger is ISO 3382-1:2009 A.3.4, with a note on the at-edge
sample choice. English ISO 3382-3 strings use point decimals
(STI = 0.50/0.20); the Spanish strings keep the comma.
Docs (EN/ES), API reference, conformance check, changelog and the
affected example fiches are updated accordingly.
Directive 2002/44/EC bases the whole-body daily exposure A(8) on the
highest of the frequency-weighted axis values 1,4*a_wx, 1,4*a_wy, a_wz
(Annex, Part B, point 1), not on the ISO 2631-1 Eq. (10) vector total
a_v; the hand-arm side keeps the Part A vector total a_hv.
- Relabel the whole-body magnitude of the daily-exposure fiche as
a_w,max with a printed note stating the Part B basis (EN/ES); add
vibration.wbv_exposure_basis() returning the dominant-axis value and
document in daily_vibration_exposure which magnitude each kind must
be fed with; state the same basis in the human-vibration guide
(EN/ES) and the measurement-chain diagram.
- Derive the boxed exposure zone of the fiche from the same
displayed-rounded comparisons as the assessment rows and the verdict,
so an A(8) printing exactly at a threshold cannot show a zone one
step below its own Exceeded row.
- ISO 10848 Kij fiche: distinguish an empty single-number mean caused
by every in-range band being bracketed (M < 0,25) from a spectrum
with no bands in the Annex A range (EN/ES).
- Spanish ISO 2631-5 outputs: translate the subject sex in the
injury-probability plot title and the word Formula in the fiche's
clause references.
- Correct the running_rms comment: the linear window divides by the
full window length (zero-padded front), it does not average over the
available samples.
The ruff 0.16 adoption applied the PLC0414 and F401 unsafe fixes to
src/phonometry/_plotting.py and deleted all 82 redundant-alias re-exports,
leaving only the docstring. The module is documented as a silent re-export
of the plot renderers (moved to phonometry._plot) for one deprecation
cycle, so from phonometry._plotting import plot_impulse_response and every
sibling raised ImportError. The sibling shim scripts/fdtd2d.py got the
explicit __all__ treatment in the same commit; this one was missed.
Bring back all 82 re-exports as grouped plain imports per source module
and declare an explicit __all__ so the lint fixers keep them (every target
still exists in phonometry._plot, none dropped). Keep the module out of
_compat._MOVED on purpose: that table maps one old path to one relocated
module and warns on attribute access, while _plotting fans out to thirteen
domain modules and is documented as silent.
Harden tests/test_deprecated_aliases.py so this cannot regress silently:
every pre-move module path must now expose a non-empty public surface, and
a new test pins the frozen 82-name _plotting re-export snapshot, asserting
__all__ matches and each name resolves to a callable without warnings.
Pure structural refactor of the two functions flagged by SonarCloud S3776,
with no numeric change:
- ISO 532-1 _calc_slopes: the two segment kinds of the slope state machine
move to _masked_segment/_flat_segment and the Table A.9 range searches to
_rns_index_of/_next_rns_index.
- Osses 2016 _analyze: the per-frame excitation front-end, generalised
modulation depth and neighbour cross-covariance become _band_envelopes,
_modulation_depth and _neighbour_covariance.
Outputs verified byte-for-byte identical on AM/FM tones, AM noise and
stationary/time-varying loudness cases; the committed conformance report
regenerates unchanged.
* Add slow-sound slit and Helmholtz-resonator perfect absorbers
Introduce materials.slow_sound_absorber, a transfer-matrix model of a
rigid panel of thin closed slits loaded by an array of Helmholtz
resonators. The resonators slow the sound inside the slit and pull its
resonance into the deep-subwavelength regime, and the visco-thermal
losses of the sub-millimetre slit and of the square resonator necks and
cavities make perfect absorption possible.
slit_helmholtz_absorber predicts the oblique-incidence absorption of the
panel, returning a SlitResonatorAbsorberResult with the surface
impedance, reflection factor, absorption, retrieved effective parameters
and the full chain matrix, plus a .plot() of alpha(f) with |R| overlaid.
critical_coupling_design solves the inverse problem: it tunes the cavity
length and the slit height so the reflection zero sits on the real
frequency axis, giving perfect absorption at a chosen frequency and
angle. The building blocks slit_effective_properties,
rectangular_duct_properties and helmholtz_resonator_impedance and the
HelmholtzResonator geometry are exported alongside.
The model is pinned to its exact analytic anchors: perfect absorption at
the design frequency, the Poiseuille resistivity limits 12 eta / h^2
(slit) and 28.454 eta / w^2 (square duct), and the loss-free
effective-parameter limits (rho -> rho0, kappa -> kappa0). Adds a
conformance section, an example figure, tests, an API-reference row set,
and EN/ES guide sections.
* Keep the Sonar gate clean for the slow-sound absorber
Exempt materials.slow_sound_absorber from S107 the same way the other
scientific APIs are: its functions carry the slit and resonator geometry
alongside the shared keyword-only air state, matching the porous-absorber
and JCA signatures, so the parameter count reflects the physics rather
than incidental complexity.
Extract a shared alpha(f) + |R| overlay renderer for the layered-absorber
and slit-resonator plots so the two oblique-incidence absorber renderers
no longer duplicate their body. Output is byte-identical.
* Make the slow-sound absorber files Ruff 0.16 clean
Use dict literals for the shared air-state mappings and drop the
redundant quoted type annotations (the module already imports
annotations from __future__). Keeps the new feature files clean under
the widened Ruff 0.16 default rule set without touching unrelated files.
* Modernize the slow-sound absorber additions for ruff 0.16 and regenerate references
* Silence the unused figure handle in the slow-sound graph helper
* Clarify slow-sound docstrings and cover the optional-correction branches
Fix the trailing space in a docstring code span, state the design warning is
emitted rather than raised, correct the deep-subwavelength depth ratio in the
figure docstring, and add a test exercising the end_correction and
slit_radiation False branches.
* Regenerate API reference and llms for the slow-sound docstring fixes
* Regenerate conformance and references after rebase
* Translate the slow-sound figure strings and require convergence in the check
Add the Spanish figure-string translations for the slow-sound absorber plot
(legend, design annotation, title and the panel-depth pattern) so the ES
variant is not left in English, and make the critical-coupling conformance
check fail unless the design solver converged.
* Regenerate conformance and references after rebase
* Use a real polar response for the ISO 17497-2 diffusion oracle
Replace the hand-made four-level diffusion anchor with a real free-field
polar response: a boundary-element (COMSOL) prediction of an N = 7
quadratic-residue diffuser and its flat reference panel at 1000 Hz, normal
incidence, reduced to the standard 37-receiver single-plane semicircle
(5 deg spacing, -90 to +90 deg) in the plane of maximum diffusion. The 37
arc points are coplanar to 3.4e-3 of unit radius, so the equal-area
Formula (5) applies exactly; levels are L_i = 20 lg |p_i|.
- reference_data: commit the 37 QRD and flat-reference arc levels and the
directional diffusion coefficients d_theta = 0.7572 (QRD) and 0.1391
(flat) and the normalised d_theta,n = 0.7180 (Formula (7)), with full
provenance. For context the full dense hemisphere gives 0.5007/0.1487/
0.4135 (energy) and reproduces the source's own value under the amplitude
convention.
- conformance: three real-data rows (Formula (5) twice, Formula (7)),
reproduced to 1e-6; regenerate docs/CONFORMANCE.md (412/412 pass).
- tests: clean-room checks that re-derive Formula (5) from the levels
independently of the library kernel.
- Surface Scattering guide (English and Spanish, plus the GitHub mirror)
and the theory anchor now use the cited real QRD example; regenerate the
polar figure (d = 0.76) and llms-full.txt.
* Regenerate conformance and references after rebase
* Correct the diffusion oracle provenance to an in-house COMSOL FEM simulation
The single-plane QRD field is the maintainer's own COMSOL finite-element
free-field simulation cross-validated against an independent MATLAB
implementation of ISO 17497-2 Formula (5), not a boundary-element method and
not a third-party published measurement; drop the unverifiable thesis
citation and the mislabelled method across the guide, its mirror and the
oracle provenance.
* Make the diffusion-oracle provenance consistent in the test module
Match the reference-data provenance: in-house COMSOL finite-element simulation
cross-validated against an independent MATLAB implementation, not a
boundary-element 2018 thesis and not a measurement; rename the tests to
_matches_simulation_oracle.
* Add diffuser-design far-field prediction of the diffusion coefficient
Predict the far-field polar response and the ISO 17497-2 directional
diffusion coefficient of a Schroeder phase-grating diffuser from its
surface design, complementing the measurement-only scattering_diffusion
module. The new materials.diffuser_design module implements the
single-plane Fraunhofer (Fourier) far-field model of Cox and D'Antonio:
each rigid-bottom well of depth d_n contributes a pressure reflection
coefficient R_n = exp(-2 j k d_n) and the scattered pressure at each
reflection angle is the sum over the wells of the periodic surface, whose
predicted levels feed the existing directional_diffusion_coefficient.
- predict_diffuser_polar_response(): per-frequency polar response reduced
to the directional diffusion coefficient, from a well-depth sequence or
an explicit complex per-well reflection sequence.
- predicted_diffusion_spectrum(): predicted d(f), normalised band by band
against the same-footprint flat reference (Formula (7)).
- quadratic_residue_sequence() and qrd_well_depths() for QRD geometry
(s_n = n^2 mod N; d_n = s_n lambda_0 / (2 N)).
- DiffuserPolarResponse result with the per-angle levels, the coefficient
and a semicircular polar .plot() (English and Spanish).
Validated by the exact-by-physics anchors: a flat panel collapses to a
single specular lobe and normalises to zero, while a well-designed QRD
scatters far more evenly and sits well above it. Adds conformance rows,
the surface-scattering guide section (EN and ES) with a predicted-diffusion
figure, tests and the regenerated API reference, taxonomy and llms output.
* Modernize the diffuser-design additions for ruff 0.16 and regenerate references
* Hoist array construction out of the diffuser-design raise assertions
* Regenerate conformance and references after rebase
* Anchor ISO 16251-1 floor-covering improvement on a real measurement
Replace the illustrative improvement spectrum with a real textile-carpet
measurement digitized from the vector chart of Foret, Chene and
Guigou-Carter (Forum Acusticum 2011, CSTB), which rates to the paper's
published DeltaLw = 29 dB. Add a conformance row and a clean-room test that
assert the rating and its robustness to the digitization tolerance, refresh
the laboratory-insulation guide figure and worked example to the measured
carpet, and cite the source (mirrored in Spanish).
* Clarify the digitized draft-standard provenance and pin the tolerance test endpoints
* Regenerate the floor-covering example report and references
The Materials and Surfaces theory page derives surface diffusion and porous
absorber prediction, yet its references frontmatter listed only Cox & D'Antonio
and Allard & Atalla. Add three open-access works by Jiménez and co-workers that
the page already speaks to: the metadiffuser paper (Jiménez, Cox, Romero-García
and Groby, 2017, Scientific Reports) on the surface-diffusion section, and the
two critical-coupling perfect-absorption papers (Jiménez, Groby, Pagneux and
Romero-García, 2017, Applied Sciences; Jiménez, Romero-García and Groby, 2018,
Acta Acustica) on the material-characterisation section.
Reference the same works from the scattering_diffusion and porous_absorber
module docstrings, and regenerate the committed API reference.
Add the edited volume Acoustic Waves in Periodic Structures, Metamaterials, and
Porous Media (Jiménez, Umnova and Groby, Eds., 2021, Springer) to the
bibliography page as a metamaterials companion to Cox & D'Antonio.
Mirror the frontmatter and bibliography changes in the Spanish pages with
translated notes, keeping authors, titles and DOIs identical.
ruff 0.16 broadens the default rule set (pyupgrade, isort, comprehensions and
more). Bump the dev pin to ruff>=0.16.0 and apply the resulting fixes across
the tree: PEP 585 built-in generic annotations, unquoted annotations under
from-future annotations, sorted imports and the assorted small lint fixes. A
re-export shim that relied on redundant import aliases now declares __all__
explicitly. The api reference is regenerated for the modernized signatures.
No runtime behavior changes; the full test suite passes.
The second half of the Vibration theory page derives point mobilities and
radiation efficiency from Cremer, Heckl and Petersson (Structure-borne sound,
Eq. 5.23 and Table 5.1) and Hopkins (Sound insulation, Eqs 2.227-2.230), and
ties the results to ISO 7626-1 mobility measurement and the ISO 7849 radiation
factor, yet the references frontmatter only listed Griffin and Mansfield. Add
the missing bibliography entries for those works so the rendered References
section matches what the prose actually cites.
Add the two table-of-contents anchors the theory index was missing for this
page: the point-mobility and radiation-efficiency section and the multiple-
shock subsection.
Mirror both changes into the Spanish pages with translated notes and labels.
* Add ISO 9053-1 static airflow-resistance report via .report()
Add a one-page PDF material airflow-resistance test report to
StaticAirflowResult, laid out like an accredited ISO 9053-1:2018
static/direct airflow-method certificate.
The fiche carries a standard-basis line, an optional metadata header
(client, manufacturer, specimen, specimen thickness, test facility,
date, climate), a two-panel body with a metrics table (the evaluation
velocity, the fitted pressure difference, the airflow resistance R, the
specific airflow resistance R_s, the airflow resistivity sigma when a
thickness is available, and the through-origin fit coefficients a and b)
beside the fitted pressure-drop curve, and a boxed specific airflow
resistance R_s with R and sigma alongside, all read at the clause 7.5
reference velocity of 0.5 mm/s. ISO 9053-1 is a material
characterisation, so the fiche carries no pass/fail verdict.
The renderer reuses the shared report layout and metadata container, and
renders in English and Spanish. A worked example is registered in the
report generator and its rendered fiche committed alongside the others.
* Document the plot dependency and use a language-neutral precision note
The static airflow-resistance fiche embeds the fitted curve, so state that
rendering needs both reportlab and matplotlib (phonometry[report,plot]) in the
English and Spanish guides and the materials doc. Describe the evaluation-velocity
precision as one decimal place rather than with a locale-specific separator, and
regenerate the API page.
* Extract the shared material-test fiche scaffold
The dynamic-stiffness (EN 29052-1 / ISO 9052-1) and static airflow-resistance
(ISO 9053-1) fiches share the same one-page shape: title and basis line,
metadata identity grid, a metrics table beside the result's self-scaling plot,
a boxed single-number result with extended terms, and the footer. Move that
common scaffold into a single _material_fiche helper (the content dataclass, the
identity-grid builder, the standard-basis-line helper and the renderer) and have
both fiches supply only their own labels, metric rows and boxed statement. The
rendered output is unchanged.
* Add element-normalized intensity insulation report via .report()
Add IntensityElementNormalizedResult.report(): a one-page PDF fiche for
the element-normalized level difference DI,n,e of a small building element
measured with sound intensity (ISO 15186-1:2000, Clause 3.9, Formula (8)).
DI,n,e is a level difference rated by the ISO 717-1 airborne machinery, the
same as the sibling intensity sound reduction index RI, so the fiche is
driven through the shared insulation skeleton (render_insulation_fiche) with
the shared iso717_columns_builder. It lives in the existing iso15186.py
renderer beside the RI fiche, so no second near-identical renderer is added.
The sheet carries the standard-basis line, an optional metadata header, the
per-band table (16 one-third-octave or 5 octave bands) beside the
measured-versus-shifted-reference curve, the boxed rating DI,n,e,w (C; Ctr)
and the intensity-method statement. verbose=True shows the ISO 717 evaluation
per band; a metadata requirement adds a PASS/FAIL verdict (the element
insulation passes at or above the target); language="es" renders the Spanish
fiche.
The example is anchored to the ISO 717-1:2020 Annex C worked-example curve
read as a documented DI,n,e spectrum, pinning the rating to the published
30 (-2; -3) dB through the intensity path without a new numeric oracle. It is
registered in the report generator with its committed PDF and WebP preview,
and the guides and API reference are updated.
* Share the intensity-report request validation helper
Extract the engine, language, rating-presence and band-count guards the two
ISO 15186-1 intensity report methods share into a single
_validate_intensity_report helper, mirroring the flanking-transmission module.
It returns the validated non-None rating so each report method hands it
straight to the renderer, removing the duplicated guard block.
* Regenerate API reference after rebase
* Add wind-turbine tonal audibility report via .report()
Add WindTurbineTonalityResult.report() rendering a one-page PDF
wind-turbine tonality-assessment fiche (IEC 61400-11:2012+A1:2018,
subclauses 9.5.2-9.5.5).
The sheet carries a standard-basis line, an optional metadata header
(source/situation, client, measurement position, instrumentation, date),
a two-panel body with the critical-band and masking analysis in a metrics
table (tone frequency, critical bandwidth, tone level L_pt, masking-noise
level L_pn, tonality dL_tn, audibility criterion L_a and tonal audibility
dL_a) beside the narrowband-spectrum plot with the critical band, masking
level and tone marked, and a boxed decisive tonal audibility dL_a with the
tone frequency and the audibility decision (a tone is audible when dL_a
exceeds 0 dB). A maximum acceptable tonal audibility supplied via the
metadata requirement adds a PASS/FAIL verdict (a lower audibility is
better). English and Spanish both render.
Register the example fiche, add structural and i18n tests, and regenerate
the committed example PDF, WebP preview and API reference.
* Base the audibility decision on the displayed rounded tonal audibility
The boxed result, verdict and decision text all commit to the tonal
audibility rounded as displayed, but the decision phrase and note branched
on the raw is_audible flag. A raw dL_a just above 0 dB that rounds to a
displayed 0.0 dB could therefore print a self-contradicting "0.0 dB > 0".
Derive the audibility decision from the same rounded value through a shared
helper, and add a boundary regression test.
* Add field and survey building-insulation reports via .report()
Give the field and survey building-insulation results a one-page PDF field
test report, reusing the shared ISO 717 insulation skeleton so each is a
per-band curve rated against a shifted reference:
- SurveyAirborneResult, SurveyImpactResult and SurveyFacadeResult (ISO
10052:2021 survey/control method, octave bands) report DnT (or R'), L'nT and
D2m,nT with their ISO 717 weighted single number and the survey-method
statement.
- FacadeInsulationResult (ISO 16283-3:2016 field facade, one-third-octave
bands) reports D2m,nT (default), D2m,n or R'45 with the D2m,nT,w (C; Ctr)
rating and the engineering-method statement.
Each sheet carries the standard-basis line, an optional metadata header, the
per-band table beside the measured-versus-shifted-reference curve, the boxed
single number, an optional requirement verdict (level differences and
reduction indices pass at or above the target, the impact level at or below
it) and a footer; verbose=True annexes the ISO 717 evaluation per band, and
both English and Spanish render.
The shared ISO 717 default/verbose columns builder is hoisted into the
insulation-fiche helper so the survey, field-facade and flanking renderers
drive a single implementation. Registers one committed example per report,
extends the field-insulation guide (EN and ES), and updates the changelog.
* Share the field engineering-method statement across the ISO 16283 specs
* Caption the survey table by its band set and state the facade rating per quantity
The ISO 10052 survey report hard-coded the octave-band table caption, so a
one-third-octave survey report (16 bands, which the survey API accepts) was
mislabelled; derive the caption from the reported curve length and cover it
with a one-third-octave regression test. Correct the facade report
description: it boxes the ISO 717 weighted rating of the reported quantity
(D2m,nT,w, D2m,n,w or R'45,w), not always D2m,nT,w.
* Add structural-vibration FRF reports via .report() (ISO 7626, ISO 10846)
Add a one-page PDF .report() fiche to the two structural-vibration
frequency-response result types.
MobilityResult.report() renders a mechanical-mobility measurement fiche
(ISO 7626-1:2011 frequency-response-function definitions; measurement per
ISO 7626-2:2015). Mechanical mobility is a continuous frequency-response
function, not an octave-band quantity, so the sheet presents it honestly as the
mobility magnitude spectrum |Y(f)| plus a compact table of the FRF's
characteristic points (the FRF type, driving-point or transfer, the frequency
range, the peak frequency, the peak mobility magnitude and the phase there),
with a boxed peak mobility |Y| at the frequency it occurs at. It is a
characterisation, so there is no pass/fail verdict.
TransferStiffnessResult.report() renders a dynamic-transfer-stiffness
characterisation fiche for a resilient element (ISO 10846-1:2008 definition;
determined by the direct method, ISO 10846-2:2008, or the indirect
blocking-mass method, ISO 10846-3:2002). The transfer stiffness is a continuous
frequency-response function, so the sheet presents it as the transfer-stiffness
level spectrum Lk(f) plus a compact table of characteristic points (the
determination method, the blocking mass for the indirect method, the frequency
range, and the low-frequency stiffness plateau |k2,1|, its level Lk and the loss
factor there), with a boxed low-frequency Lk. It too is a characterisation, with
no verdict.
Both fiches share a common FRF body (_report/_frf_fiche.py): the title and
basis line, the optional metadata header, the two-panel body with the
characteristic-point table beside the result's own spectrum plot, the boxed
representative value and the footer. English and Spanish both render; example
inputs reuse the modules' oracle-validated closed forms.
* Reuse the result loss_factor in the transfer-stiffness fiche
Share the single ISO 10846-1 (3.8) loss-factor definition by reading the
result's own loss_factor property at the low-frequency index, instead of
recomputing eta = Im/Re in the renderer.
* Drop the unused low-frequency frequency in the stiffness table
The characteristic-point table only needs the magnitude, level and loss
factor at the low-frequency plateau, not its frequency.
Add a one-page PDF .report() to the two reverberation result types.
ReverberationModelResult.report() renders a design-stage prediction of the
reverberation time by the five classical statistical-acoustics models (Sabine,
Eyring, Millington-Sette, Fitzroy and Arau-Puchades): a per-band table with one
reverberation-time column per model beside the model comparison plot, and a
boxed mid-frequency reverberation time from Arau-Puchades with the per-model
spread alongside. It is labelled a prediction, not a measurement: the five
models bracket the reverberation time likely to occur, so no PASS/FAIL verdict
is emitted; a target reverberation time is printed as a reference line only.
ReverberationResult.report() renders an enclosed-space characterisation
(EN 12354-6:2003): a per-band table of the equivalent sound absorption area A
and the reverberation time T beside the reverberation-time plot, the room
volume and object fraction in the header, and a boxed mid-frequency
reverberation time with the mid-frequency absorption area alongside. A target
reverberation time is likewise printed as a reference line without a verdict,
since a room reverberation time is a target range rather than a strictly
higher/lower-is-better quantity.
Both renderers live in a shared _report/reverberation.py module (shared
mid-frequency descriptor, time formatting, octave-band table and header grid
helpers). Add the English and Spanish fixed strings, one committed example
fiche per result under .github/reports/, structural and value-presence tests
in English and Spanish, and the guide sections in both languages. Regenerate
the API reference.
* Add ISO 1999 noise-induced hearing-loss prediction reports via .report()
Add one-page PDF prediction fiches to the two occupational-hearing-loss
result types of the ISO 1999:2013 module. Both sheets are clearly labelled
statistical predictions for a noise-exposed population, not clinical
diagnoses of any individual.
NiptsResult.report() renders the noise-induced permanent threshold shift
(clause 6.3): a prediction-basis line, an optional metadata header, a
per-audiometric-frequency table of the median N50 and the NIPTS at the chosen
population fractile beside the shift spectrum, and a boxed representative shift
averaged over the 2/3/4 kHz hearing-handicap set with the exposure conditions.
verbose=True adds the upper/lower spread columns.
HtlanResult.report() renders the hearing threshold level associated with age
and noise (clause 6.1): a per-audiometric-frequency table of the age component
H, the noise component N and the combined threshold H' = H + N - H*N/120
beside the plot, and a boxed representative combined threshold. verbose=True
adds the H*N/120 compression term.
A maximum acceptable representative value supplied through the metadata
requirement adds a PASS/FAIL verdict (a lower value is better); without it
neither fiche prints a verdict. Both fiches render in English and Spanish.
Register one example per fiche, add structural and number-presence tests in
both languages, and regenerate the API reference and the committed example
artifacts.
* Drop the unused caption style from the prediction-notes helper
The shared ISO 1999 prediction-notes block builds its own muted paragraph
style, so it never used the caption style threaded in from the callers.
* Add ISO 9613-2 outdoor-propagation prediction reports via .report()
Add one-page PDF prediction fiches to the two outdoor-propagation result
types and a shared ISO 9613-2 family renderer.
OutdoorAttenuation.report() renders the octave-band attenuation breakdown
(divergence, atmospheric, ground and barrier terms with the total A), the
source power level and the downwind level LfT(DW), the attenuation-breakdown
plot and a boxed A-weighted downwind level LAT(DW) at the receiver. The sheet
is clearly labelled a prediction, not a measurement, and states the
meteorological and ground assumptions. A declared limit level via the metadata
requirement adds a PASS/FAIL verdict (a lower level is better). To carry the
composed level, outdoor_propagation_attenuation now accepts an optional
sound_power_level (with directivity_index, d_omega and c0).
BarrierInsertionLoss.report() renders the per-band insertion loss, the
insertion-loss spectrum and a boxed mean insertion loss over the octave bands.
Its basis line names the actual diffraction model used (the wave-theoretic
rigid-screen solution or the Kurze-Anderson closed form), a wave-acoustics
complement to the tabulated ISO 9613-2 screening term. A minimum required
insertion loss via requirement adds a PASS/FAIL verdict (higher is better).
Both fiches render in English and Spanish, register an example each under
.github/reports, are covered by structural and number-presence tests, and are
showcased in the Outdoor Sound Propagation guide.
* Regenerate API reference for the outdoor propagation report method
* Move the outdoor-propagation receiver level to a report-time SourceEmission
Keep outdoor_propagation_attenuation a pure attenuation calculation (its
signature is unchanged) and expose the downwind receiver level through a new
frozen SourceEmission object passed to OutdoorAttenuation.report(). When a
source emission is supplied the fiche lists the source power and the downwind
level and boxes the A-weighted downwind level at the receiver; without it the
fiche boxes the octave-band range of the total attenuation. The receiver-level
composition stays in the shared _compose_receiver_level helper.
Hoist the result and object construction out of the pytest.raises blocks in the
report tests so only the failing call is inside.
* Re-trigger CI for the outdoor-propagation report changes
* Restore Spanish diacritics and correct the composition-helper docstring
Add the missing accents to the two Spanish requirement comments in the outdoor
propagation guide, and update the receiver-level helper docstring: the level is
composed at report time from a SourceEmission, not stored on the attenuation
result, so drop the stale reference to the factory storing it.
* Drop the stray npm lockfile from the site
The site uses pnpm (pnpm-lock.yaml); an npm package-lock.json was generated
by accident during the site build and does not belong in the tree.
* Add noise-control performance reports via .report()
Add a one-page PDF .report() fiche to the three noise_control result
types, each laid out with a per-band table beside the result's own plot,
a boxed single-number performance figure and an optional PASS/FAIL
verdict:
- EnclosureResult: machine-enclosure insertion loss (Bies, Hansen &
Howard, section 7.4.2). The table lists the supplied panel transmission
loss R, the interior build-up correction C and the net insertion loss
IL = R - C; the boxed figure is the mean insertion loss with the
external and internal surface areas. verbose=True adds the interior
room constant column. A declared minimum passes when the mean meets it.
- ReactiveSilencerResult: reactive-silencer transmission loss (Munjal
Eq. (3.27); Bies sections 8.8-8.9). The table lists the transmission
loss TL and, when end impedances were given, the insertion loss IL; the
boxed figure is the mean transmission loss with the peak and the device
kind. A declared minimum passes when the mean meets it.
- HvacSpectrumResult: HVAC duct-noise spectrum (Bies Chapter 8; VDI
2081-1). A regenerated-noise spectrum boxes the A-weighted sound power
level with the overall total (lower is better); an attenuation spectrum
boxes the mean attenuation (more is better). verbose=True adds the
A-weighting correction and A-weighted band-level columns.
The three renderers share a two-panel skeleton in
_report/_noise_control_fiche.py and reuse the sound-power table builder,
band labels and header grid. Each accepts an optional metadata header,
states its method basis and renders in English or Spanish.
Register one committed example per fiche under .github/reports/, add
structural and clean-room number-presence tests (EN and ES), and update
the CHANGELOG and the regenerated API reference.
* Address SonarCloud findings on the noise-control renderers
Reduce render_noise_control_fiche below the parameter-count threshold by
fixing the two-panel split widths internally (the three renderers never
overrode them), and lift the HVAC verdict symbol/unit selection out of a
nested conditional into an explicit if/elif/else. No change to rendered
output; the committed example fiches are unaffected.
* Round the requirement to display precision and fit the verbose table
Compare the declared requirement at the same one-decimal precision as the
measured value in the noise-control verdict, so the printed comparison can
never contradict the verdict at the boundary. Also trim the verbose
enclosure table columns to sum to the 64 mm left panel width.
* Speech-intelligibility reports via .report() (STI and SII)
Add a one-page PDF fiche to the two speech-intelligibility result types.
STIResult.report() renders an IEC 60268-16:2020 speech-transmission-index
fiche for verifying voice-alarm and public-address intelligibility: a
standard-basis line naming the measurement method (the full STI indirect
method from an impulse response, or the direct STIPA method on a recorded
signal), an optional metadata header, a per-octave-band modulation transfer
index table beside the per-band MTI bars, and a boxed STI with the Annex F
qualification band. A minimum STI supplied via the metadata requirement adds a
PASS/FAIL verdict (a higher STI passes).
SIIResult.report() renders an ANSI S3.5-1997 speech-intelligibility-index
fiche (one-third-octave-band method) for a speech-audibility assessment: a
per-one-third-octave-band table of the equivalent speech spectrum, the Table 3
band-importance function and the band-audibility function beside the audibility
and importance-weighted contribution bars, and a boxed SII. verbose adds the
equivalent disturbance spectrum level column. A minimum SII supplied via the
requirement adds a PASS/FAIL verdict (a higher SII passes).
Both reuse the shared report layout and metadata container, render in English
and Spanish, register a committed example fiche under .github/reports, and are
documented in the speech-transmission and speech-intelligibility guides.
* Address review: Spanish diacritics and invalid-language docstring
* Hoist the report path out of the intelligibility raise assertions
Build the output path before each pytest.raises block so only the failing
report call remains inside it.
* Room-noise rating reports via .report() (ANSI/ASA S12.2-2019)
Add a one-page PDF room-noise assessment fiche to both room-noise rating
result types of ANSI/ASA S12.2-2019:
- NCResult.report(): the Noise Criteria rating by the tangency method
(Table 1). The box states NC-nn with the governing octave band.
- RCResult.report(): the Room Criteria Mark II rating (Annex D). The box
states RC-nn(tag) with the mid-frequency average LMF and the
neutral/rumble/hiss spectral quality.
Both fiches share one renderer built on the accredited-report engine: a
standard-basis line, an optional metadata header, the measured octave-band
levels beside the measured spectrum plotted against the NC/RC curve family
(the result's own plot), the boxed rating and a footer disclaimer. A target
rating on the metadata requirement adds a PASS/FAIL verdict, where a lower
rating passes. With verbose=True the table gains the per-band NC contour
value read by the tangency method, or the reference RC Mark II curve and the
measured deviation from it. English and Spanish both render.
Register one example per fiche in the report generator (an NC-40 office
spectrum governed by the 250 Hz band and an RC-35(R) rumble spectrum, both
built from the tabulated curves so the ratings are exact), document the
method on the room-noise guide, and cover the reports with structural,
number-presence, one-page and EN/ES tests. Regenerate the API reference and
the conformance report.
* Factor out the shared room-noise table builder
Extract the common frequency/level columns and the caption-plus-table
assembly shared by the NC and RC left cells into helpers, so the two
renderers only add their own evaluation columns.
* Drop the unused table-language parameter and hoist the raise setup
_value_table never used its language argument (its cells arrive already
formatted), so remove it and its call site. Build the result before the
engine and language rejection assertions so only the reporting call sits
inside pytest.raises.
* Laboratory flanking-transmission reports via .report() (ISO 10848)
Add a one-page PDF fiche to each of the three ISO 10848 laboratory
flanking-transmission results.
VibrationReductionResult.report() writes a junction-characterization
report of the vibration reduction index Kij (ISO 10848-1:2006): the
standard-basis line, an optional metadata header, the per-band Kij table
beside the Kij(f) curve and a boxed single-number mean Kij over the
Annex A band range (200 Hz to 1250 Hz for one-third-octave bands, 125 Hz
to 1000 Hz for octave bands) with the count of averaged and bracketed
bands. Bands bracketed for poor modal overlap (M < 0,25,
ISO 10848-4:2010 Clause 9) print their value in brackets and are excluded
from the mean; verbose=True adds a column stating whether each band
enters the mean.
FlankingLevelDifferenceResult.report() and
FlankingImpactLevelResult.report() write measurement reports of the
overall descriptors Dn,f (airborne) and Ln,f (impact, tapping machine,
ISO 10848-2:2006), reusing the shared two-panel insulation report
skeleton: the per-band quantity beside the measured-versus-shifted
ISO 717 reference curve and the boxed single number Dn,f,w (C; Ctr)
(ISO 717-1) or Ln,f,w (CI) (ISO 717-2). verbose=True annexes the ISO 717
evaluation per band (the value, the shifted reference and the
unfavourable deviation). A requirement on the metadata adds a PASS/FAIL
verdict (higher is better for Dn,f,w, lower for Ln,f,w), and
language="es" renders every fiche in Spanish.
Register one example fiche per report kind in the generator with its
committed PDF and WebP preview, document the reports in the EN and ES
laboratory-insulation guides and the docs mirror, add the Spanish
strings, and regenerate the API reference and llms-full.txt.
* Fix the Kij single-number membership shown in the verbose fiche
The verbose Kij table marked a band as entering the single number from its
bracket flag alone, so a non-bracketed band outside the Annex A range (for
instance above 1250 Hz in the one-third-octave case) was labelled as counted
while the boxed "bands averaged" total correctly left it out. Share one
membership mask (inside the Annex A range and not bracketed) between the table
column and the result box so the two always agree, and lock it with a unit
test on the range exclusion. Hoist the fiche construction out of the
engine-rejection pytest.raises blocks.
* Structure-borne sound power characterization and installed prediction reports via .report()
Add one-page PDF .report() fiches to the two structure-borne building
result types, closing the EN 15657 -> EN 12354-5 chain from source
characterization to installed prediction.
StructureBornePowerResult.report() renders an EN 15657:2018 reception-plate
structure-borne sound power characterization: the per-band spatial mean plate
velocity level Lv and the injected power level L_Ws (Formula 14), the L_Ws(f)
spectrum, and the boxed band-summed total L_Ws (dB re 1 pW) with the plate
mass per area m and area S. verbose=True adds the plate loss factor eta
column; the basis strip states Formula 14 and the conversion to the
plate-independent source quantities required before EN 12354-5.
InstalledSourceResult.report() renders an EN 12354-5:2009 installed
structure-borne sound prediction, clearly labelled a prediction and not a
measurement: the per-band installed power level L_Ws,inst, each transmission
path's normalised SPL L_n,s,ij and the combined total L_n,s, the per-path and
total L_n,s(f) spectra, and the boxed band-summed total L_n,s. verbose=True
adds one column per transmission path; the basis strip states Formulae 18a/17
and the prediction disclaimer.
Both fiches reuse the shared sound-power report engine (per-band table,
spectrum, boxed result and flow assembly), extended with a caller-supplied
verdict so the structure-borne quantity symbols are stated consistently, and
render in English and Spanish. Register one example per fiche, add structural
and number-presence tests, document the reports in the guides, and record the
additions in the changelog.
* Address review: per-path column [dB] units and Spanish accent
Add the [dB] unit to the installed-prediction table's per-path column headers
so they match the installed-power and total columns, and correct the accent in
the Spanish structure-borne power guide (acelerometro -> acelerometro with the
acute accent).
* Add FacadePredictionResult.report() for predicted facade insulation (EN 12354-3)
Render a one-page prediction fiche for the predicted standardized level
difference of a facade D2m,nT (EN/ISO 12354-3, Formula 13), reusing the
shared EN/ISO 12354 prediction report body: the facade-element table
(each element's weighted partial index Rp,w, with an optional transmitted
energy share) beside the result's own per-element / R' / D2m,nT plot, the
boxed predicted D2m,nT,w and the prediction statement. Clearly labelled a
prediction from element data, never a measurement.
* Test the predicted facade insulation fiche against EN 12354-3 Annex F
Pin the facade fiche to the Annex F worked example (D2m,nT,w = 33 dB) run
through the tested prediction code: pypdf assertions on the boxed single
number, the prediction / not-a-measurement wording, the model terms, the
per-element partial indices, the verbose energy share, the requirement
verdict, the Spanish fiche and the missing-rating guard.
* Add the EN 12354-3 facade prediction example fiche and regenerate artifacts
Add the Annex F facade prediction example to generate_reports.py with its
committed PDF and WebP preview, and regenerate the generated API reference
and llms bundle for the new report() method.
* Document the predicted facade insulation report (EN+ES)
Add the facade prediction report subsection to the EN and ES prediction
guides (with a ReportPreview of the committed example fiche) and a
CHANGELOG entry.
* Show the apparent-index values on the facade fiche and unify R'tr,s,w notation
State the computed apparent traffic-referenced index and spectrum term as
values on the predicted facade fiche (R'tr,s,w = 31 dB, Ctr = -3 dB in the
Annex F example), not just their symbols, by filling the basis line with the
result's own numbers. Use the primed apparent-index notation R'tr,s,w
consistently across the fiche, the guides and the example comments.
* Add ISO/TS 7849 sound-power-from-vibration .report() fiche
Render VibrationSoundPowerResult (airborne sound power radiated through
surface vibration, ISO/TS 7849-1/-2:2009) to a one-page PDF fiche via the
shared sound-power report engine. The vibration-method variant adds the
surface velocity level Lv and radiation factor epsilon columns, the
radiating area S in the boxed result and the LW = Lv + 10 lg(S/S0) +
10 lg(epsilon) + 10 lg(411/400) basis strip, and names the survey (Part 1,
fixed epsilon = 1) or engineering (Part 2, determined epsilon) method.
Add a sound_power_level_a property (A-weighted total) to the result and the
Spanish translations for the new strings.
* Add the ISO/TS 7849 example fiche and its committed preview
Register an engineering-method (Part 2) example in generate_reports.py: a
gearbox casing of radiating area S = 1.6 m2 surveyed over six octave bands
with a measured radiation factor, giving LWA = 88.7 dB(A) re 1 pW against a
declared 90 dB(A) limit. Commit the rendered PDF and its WebP preview.
* Test the ISO/TS 7849 sound-power-from-vibration fiche
Recompute LW and LWA from the closed-form ISO/TS 7849 Eq. 3/8/12 against a
clean-room oracle and assert they, the band labels, the method part and the
basis prose appear in the PDF; cover the survey/engineering variants, the
verbose radiation-factor column, the verdict, the metadata header, the
Spanish fiche and the rendering contract.
* Document the ISO/TS 7849 report and regenerate the API reference
Add a measurement-report section to the EN and ES vibration-sound-power
guides (with the ReportPreview) and the docs mirror, regenerate the
generated API page for the new report() and sound_power_level_a members, and
record the addition in the changelog.
* Address review on the ISO/TS 7849 report
- Do not present an unweighted broadband LW as LWA: sound_power_level_a now
returns nan without a band spectrum, so the fiche boxes the unweighted total
LW and draws no A-weighted verdict, and the basis strip omits the A-weighting
sentence for a broadband result.
- Drop the unused result parameter from the relation-strip helper.
- Render the fixed impedance term with the locale decimal separator (0.12 dB in
the English fiche, 0,12 dB in the Spanish one) via format_number.
- Cite the accelerometer calibration standard (ISO 16063-21) in the example
instead of IEC 60651, which specifies sound level meters.
- Assert the one-page contract on the longer one-third-octave table and add a
broadband test asserting no false A-weighted claim.
Regenerate the example fiche PDF and WebP preview and the generated API page.
Add .report() PDF fiches to AirbornePredictionResult and ImpactPredictionResult that render a predicted building sound-insulation calculation report per EN/ISO 12354-1/-2: the model terms and the single-number predicted apparent sound reduction index or normalized impact level, clearly labelled as a prediction rather than a measurement.
Add a .report() PDF fiche to ReverberationSoundPowerResult that renders a sound power determination report for the reverberation-room precision method (ISO 3741): the per-band room pressure and sound power levels, the sound power spectrum and the boxed A-weighted level, reusing the shared sound-power report body.
Add a .report() PDF fiche to IntensityReductionResult that renders a laboratory intensity sound-insulation test report per ISO 15186-1: the per-band intensity sound reduction index with the shifted ISO 717-1 reference curve and the single-number rating. The report body reuses the shared insulation-fiche helper.
Add a .report() PDF fiche to SoundPowerIntensityResult that renders a sound-power-by-intensity determination report per ISO 9614: the per-band sound power level from the surface intensity, the sound power spectrum and the boxed A-weighted level, with the measurement grade and field-indicator quality criteria. The sound-power report body is shared with the ISO 3744 pressure-method fiche through a common helper.
Add .report() PDF fiches for the ISO 17497 surface descriptors: the random-incidence scattering coefficient (ISO 17497-1), the directional diffusion coefficient spectrum (a new DiffusionSpectrum result, ISO 17497-2 with per-band source-position averaging), and the single-source polar-response diffusion fiche. The report bodies reuse the shared two-panel and polar figure helpers.
Add a .report() PDF fiche to FloorCoveringImprovementResult that renders the reduction of transmitted impact sound by a floor covering on a lightweight mock-up: the per-band improvement spectrum and the weighted reduction, rated per ISO 717-2. The report body reuses the shared insulation-fiche helpers.
Add a .report() PDF fiche to ImpedanceTubeResult that renders an impedance-tube test report: the per-frequency normal-incidence absorption coefficient and normalized surface impedance in a table, and the absorption curve over the tube's valid frequency range. The shared two-panel report body is unified with the ISO 354 fiche.
Add a .report() PDF fiche to LabAirborneInsulationResult and LabImpactInsulationResult that renders a laboratory sound-insulation test report: the per-band sound reduction index or normalized impact level with the shifted ISO 717 reference curve and the single-number rating. The two-panel insulation report body is shared with the field (ISO 16283) report through a common helper.
Add a .report() PDF fiche to SoundPowerResult and PrecisionSoundPowerResult that renders a sound power determination report: the per-band surface pressure and sound power levels, the sound power spectrum, and the boxed A-weighted sound power level, with the method grade and correction basis, per ISO 3744 (engineering) and ISO 3745 (precision).
Add a SeabedReflection result and the seabed_reflection constructor wrapping the existing plane-wave reflection coefficient at a fluid-fluid seabed interface, with a .plot() that draws the reflection coefficient magnitude against grazing angle and marks the critical angle. This complements the existing BottomLossResult (bottom loss in dB).
Add an AtmosphericAttenuation result and the atmospheric_attenuation constructor wrapping the existing ISO 9613-1 pure-tone attenuation calculation, with a .plot() that draws the attenuation coefficient in dB/km against a logarithmic frequency axis with a linear decibel ordinate, plus an optional total attenuation over a propagation distance.
Add a .plot(quantity=...) method to LoudspeakerCharacteristics and MicrophoneCharacteristics that draws one rated characteristic per figure (response, impedance, THD, directivity for the loudspeaker; response, directivity, inherent noise, distortion for the microphone), sharing the panel drawing with the .report() data sheets so the plot and the report never diverge.
Add an EqualLoudnessContours result and the equal_loudness_contours constructor wrapping the existing ISO 226:2023 functions, with a .plot() that draws the normal equal-loudness contour family and the hearing threshold. A short note records that the standard defines the contours from 20 to 90 phon, with the 90 phon contour valid only up to 4 kHz.
Add a .plot() method to RigidMassCalibrationResult that draws the ISO 7626-2 operational rigid-mass calibration check: the measured driving-point FRF magnitude against the known rigid-mass line with its tolerance band, and the relative deviation against the same band, with out-of-tolerance points highlighted and the verdict in the title.
Add a .report() PDF fiche to MultipleShockResult that renders a whole-body multiple-shock health-risk assessment per ISO 2631-5:2018: the spinal response acceleration dose, the daily equivalent static compressive stress and the stress variable R with the injury probability, classified against the Annex C risk bands, above the injury-probability chart.
Add a .report() PDF fiche to OpenPlanResult that renders an open-plan office acoustics report per ISO 3382-3: the spatial decay rate of A-weighted speech D2,S, the speech level at 4 m Lp,A,S,4m and the distraction and privacy distances, above a full-width spatial-decay curve with the regression line and distance markers, plus an optional verdict against a target supplied through the report metadata.
Add a .report() PDF fiche to RoomAcousticsResult that renders a room acoustic parameters measurement report per ISO 3382-1/-2: a per-band table of T20, T30, EDT, C50, C80, D50 and Ts alongside a reverberation-time curve, with the mid-frequency T30 as the boxed value and an optional verdict against a target reverberation time supplied through the report metadata.
Add a .report() PDF fiche to DailyVibrationExposure that renders a daily hand-arm or whole-body vibration exposure assessment: the per-operation exposure table, a colour-coded contribution chart, the boxed A(8) with its zone, and the assessment against the Directive 2002/44/EC action and limit values. The committed example reproduces the ISO 5349-2 Annex E.3 worked case.
Add a public reverberation-room sound absorption measurement result (BS EN ISO 354:2003) with a .plot() and a normative PDF .report() fiche. The new SoundAbsorptionMeasurement result and measure_sound_absorption constructor wrap the existing absorption-area and absorption-coefficient functions, exposing the empty and specimen equivalent absorption areas and the sound absorption coefficient per third-octave band.
Final pass over the "Signals and spectra" guides now that the whole set is
in place.
- Cross-links: the spectral-analysis "Relation" section now points to the
multiple/partial coherence guide, and the correlation-delay one points to
the envelope spectrum and the fractional-delay/resampling tools, so every
guide in the area has an inbound link from a sibling. EN and ES in parity.
- Landing and index accuracy: the Core signal analysis landing was missing
the multiple/partial coherence, test-signals and data-qualification
entries; the signals-and-spectra sub-landing prose was missing the
multiple/partial coherence paragraph. Added, EN and ES identical.
- Mirror completeness: added the GitHub-tree mirrors for the sound level
meter walk-through and the time-frequency guide (the only two guides that
had no mirror), listed them in the docs index, and synced the two
cross-link additions into the mirror. Regenerated llms-full.txt.
- Fixed a broken API link in the system-measurement guide
(rooms/room_ir -> rooms/room-ir) and shortened the system-measurement page
title so the rendered document title stays within 70 characters.
- CHANGELOG: merged the fragmented [Unreleased] subsections into one Added,
one Changed and one Fixed block in Keep a Changelog order, without losing
any of the 185 entries, and completed six entries that had been committed
as truncated single-line fragments.
* Multiple and partial coherence for MISO systems (Bendat & Piersol)
Add miso_coherence for the multiple-input/single-output coherence
functions of Bendat & Piersol, Random Data 4e, Chapter 7. From the Welch
cross-spectral matrix of two or three partially correlated inputs and one
output it reports:
- the ordinary coherence of each input with the output (Eq. 7.109);
- the multiple coherence explained by all inputs jointly (Eq. 7.35), equal
to SNR/(1+SNR) for additive output noise of known level;
- the partial coherences (Eq. 7.87, 4th-edition total-output denominator)
obtained by the Gaussian-elimination conditioning of Section 7.3
(Eq. 7.94), so a source that only correlates with the true cause is no
longer credited for it;
- the partial coherent output spectra that decompose the output power
source by source (Eq. 7.86, with the shares plus the residual noise
reconstructing the output exactly), and a dominant_input() helper for the
strongest source per band.
The frozen MISOCoherenceResult also carries the Section 9.3 random errors
(the i-th ordered input keeps nd-(i-1) effective averages) and an EN/ES
.plot(). The estimators reuse the Welch core of spectra.py, so a MISO
coherence and a power_spectral_density taken with the same segment length
are consistent bin by bin.
The conditioning is pinned to the exact rational values of Problem 7.2
(G22.1 = 4/3, G2y.1 = 4/3, Gyy.1 = 13/3, partial coherence 2/15, multiple
coherence 0.7), and the identities are checked against the SNR relation and
the uncorrelated-input case where the partial coherences reduce to the
ordinary ones and their sum is the multiple coherence. Five conformance
rows, a test suite, an EN/ES guide with a worked figure, and the API
reference, bibliography and taxonomy entries accompany the module.
* Address MISO coherence review findings
- Give the coherent-output fills a fixed dB baseline derived once from the
finite dynamic range of the panel (new _finite_db_floor), clip the plotted
levels to it and set the y-limits explicitly, so a coherent output that
dips to zero no longer drags the axis through get_ylim() inside the loop.
- Share one pivot-safety mask between the coherent-output accumulation and
the Schur update (new _pivot_safe): a near-singular conditioned pivot is
now gated identically in both places, so the power decomposition
(sum of contributions + residual = Gyy) closes bin by bin. Add a singular
(perfectly collinear) and a near-collinear test covering the invariant.
- Reword the conformance description that contained literal pipe characters
("abs(G2y.1)^2/G22.1") so the generated CONFORMANCE.md table is not
corrupted; regenerate the report.
Harden a few edge cases surfaced while reviewing the system-measurement
and parametric-EQ code.
- shaped_sweep_signal: a very small 'seconds' next to a dominant
'start_delay' could round the two edges of the constant-envelope
window onto the same sample, leaving an empty core slice that made
the crest factor come out NaN or infinite. Fall back to the whole
retained sweep in that degenerate case, and cover it with a test.
- plot_shaped_sweep: the Welch grid is resolved independently of the
synthesis grid, so a narrow (f1, f2) band on a short signal could
leave no Welch bin inside the band and crash the normalization on an
empty slice. Fall back to the overall positive-frequency maximum as
the reference so the plot still renders.
- regularized_inverse_filter: document max_gain_db as the achieved
peak-normalized out-of-band gain the code actually returns (the
computation is unchanged), and narrow the plot() return type to a
single Axes.
- impulse_response (MLS): drop a redundant rec.size == 0 term that is
already guaranteed false by the non-empty check above it.
- Hoist input construction out of pytest.raises blocks in the inversion,
Golay, shaped-sweep and parametric-EQ tests so only the call under
test can raise.
Regenerates the inversion API reference from the updated docstrings.
The generated SVGs kept text selectable via svg.fonttype='none', but
matplotlib writes mathtext runs ('$...$') with a bare font-family
('DejaVu Sans', no generic fallback) while plain <text> carries the full
chain ending in sans-serif. Viewers lacking DejaVu Sans (iOS, macOS
Preview) then fell back to a serif for the math labels while the prose
stayed sans, an inconsistent legend.
save_figure now rewrites every font-family declaration in the saved SVG
to the generic 'sans-serif' so all text renders in the viewer's native
sans font, consistently and viewer-independently. Text stays selectable
(svg.fonttype unchanged). Regenerated all figures; only the SVG
font-family declarations change, the WebP rasters are untouched.