ISO 9612 occupational noise-exposure measurement report via .report() (#288)
* feat(hearing): ISO 9612 occupational noise-exposure measurement report via .report()
ExposureResult.report() writes the one-page measurement report of ISO
9612:2009 Clause 15, laid out like a prevention-service measurement sheet:
the standard-basis line naming the applied strategy, a header grid with the
company, worker(s)/job, workplace and the new ReportMetadata free-text
instrumentation and calibration fields (Clause 15 c), the work analysis
(the per-task table of durations, Lp,A,eqT,m levels and LEX,8h,m
contributions with the result's contribution chart for a task-based
determination, or the sampling summary with the Formula C.9 budget for the
job-based/full-day strategies; verbose=True adds the per-task Annex C
uncertainty columns), and the boxed LEX,8h with the expanded uncertainty U
stated as a separate value (Clause 15 e), k = 1.65 and the one-sided 95 %
upper limit.
The fiche then assesses the result against Directive 2003/10/EC Article 3:
the lower and upper exposure action values (80/85 dB(A)) and the exposure
limit value (87 dB(A)), each marked exceeded / not exceeded on the level
rounded to one decimal exactly as displayed, with a PASS/FAIL verdict
against the limit value and a printed note that the limit value applies to
the effective exposure with the worn hearing protectors' attenuation taken
into account. A sampling advisory line surfaces the Clause 9.3/10.4/11.3
recommendations when the result carries them.
ExposureResult records the instrument class the strategy was called with,
printed as the Clause 15 c instrumentation fallback when no free text is
supplied. English and Spanish fiches (language="es" uses the RD 286/2006
vocabulary: nivel de exposicion diario equivalente, valor limite de
exposicion, comma decimals).
Tests pin a hand-derivable oracle (two 4 h tasks at 85/75 dB give
LEX,8h = 75 + 10 lg 5.5 = 82.4 dB and the closed-form
U = 1.65 sqrt((101/121) 3.25) = 2.7 dB) read back from the PDF, the Annex D
84.3 dB / 3.2 dB example, and the 80/85/87 dB(A) boundary flips on the
displayed value. The rendered Annex D welders' example is committed under
.github/reports/ and embedded in the EN/ES guides and the GitHub docs.
* test: construct the report path outside the ISO 9612 pytest.raises blocks
fix: SII disturbance per ANSI S3.5, IEC-exact intermodulation and the AES17 measurement chain (#173)
* fix: SII equivalent disturbance is the clause 5.6 maximum, not an energy sum
ANSI S3.5-1997 clause 5.6 defines the equivalent disturbance spectrum
level Di as the larger of the equivalent masking level Zi and the
equivalent internal noise level Xi'. The module summed the two in energy,
underestimating the SII by up to 0.042 in noise-plus-hearing-loss
conditions (0.1841 vs 0.2185 for normal speech in flat 30 dB noise with a
flat 40 dB loss). The official Hornsby worksheet computes the maximum in
every band and the R CRAN worked example confirms it.
Oracles: quiet-condition SII pinned at the worksheet full precision
(0.99582516666667, abs 1e-6), the disturbance intermediate locked at
5000 Hz (-23.6 dB), a discriminating noise-plus-loss case (0.2185) and
the independent R package Example C.2 (0.8513749). A code comment records
that the level-distortion factor referencing the normal-effort spectrum
for every vocal effort is the standard's own definition.
* test: STIPA direct-method modulation-depth staircase and NIPTS reading notes
The IEC 60268-16:2020 C.3.2 verification signal (Formula C.1, sinusoidal
carriers at the A.6.1 male levels) is synthesized end to end and the
published STI staircase for m = 0.0 to 1.0 (0, 0.18, 0.30, 0.38, 0.44,
0.50, 0.56, 0.62, 0.70, 0.82, 1) is asserted within the published
tolerance of 0.05 through the full stipa() audio path; the m = 0.5 point
also joins the conformance report.
ISO 1999 notes: the sub-10-year NIPTS spread keeps the raw lg(t) reading
(Formulae 6/7 carry no Formula-3 extrapolation provision) and the Annex D
half-integer rounding ambiguity of Table D.4 is recorded next to the
pinned cells.
* fix: IEC-exact intermodulation distortion (14.12.7, 14.12.8, 14.12.10)
modulation_distortion now returns the per-order IEC 60268-3 values: d_m,2
and d_m,3 are the arithmetic sums of the sideband amplitudes referenced to
the output voltage at f2 (14.12.7.2 g-h), bundled with the SMPTE-analyzer
combined RMS as an explicitly labelled convention in a new
ModulationDistortionResult.
difference_frequency_distortion references U_2,ref = 2 U_2,f2 (the sum of
both tone amplitudes) and sums the third-order products arithmetically per
14.12.8.1; the former half-difference search window could swallow the
primary tones (octave-spaced clean tones read d2 = 1.0) or the DC bin, so
product windows are now a few bins wide, capped by the component spacing,
clamped to (0, Nyquist) and forced to zero when they cannot be separated
from a primary tone or DC.
total_difference_frequency_distortion implements 14.12.10.1 directly: only
the two in-band products at f0 -/+ delta, rms-summed over the arithmetic
sum of the tone output voltages, with the standard 8 kHz / 11.95 kHz tones
as defaults.
The conformance entries carry the corrected per-clause labels and exact
values (d_m,2 = 0.16, d_m,3 = 0.08, d_d,2 = 0.03, d_d,3 = 0.04, d_TDFD =
sqrt(0.0013)). The 14.12.9.2 f) print of the DIM denominator (U2 vs the
14.12.9.1 f_s amplitude) is recorded in docs/ERRATA.md with a code comment
at the reference measurement.
* fix: AES17 measurement bandwidth, effective notch Q and the ITU-R 468 weighted THD
thd_plus_noise and sinad now measure both the residual and the total
signal through the AES17 chain (5.2.5 / 6.3.1): a 20 Hz high-pass plus
the standard low-pass at a configurable bandwidth (default 20 kHz),
realised as a brick-wall frequency-domain band per 5.2.10. Out-of-band
noise at high sample rates (+7 dB at fs = 192 kHz) and DC offsets no
longer inflate the result; bandwidth=None keeps the full-Nyquist
measurement.
The standard notch is designed so the APPLIED zero-phase response has the
requested quality factor (AES17 5.2.8 defines Q on the combined response;
filtfilt squares the magnitude, so the single-pass design is sharpened by
sqrt(1 + sqrt(2))). Previously a nominal Q of 2.0 acted as 1.29 and
requests below ~1.87 silently fell outside the AES17 [1.2, 3] range.
weighted_thd defaults to the network IEC 60268-3 14.12.11 actually
requires: the IEC 60268-1 Appendix A curve, i.e. ITU-R BS.468-4 Table 1
(peak +12.2 dB at 6.3 kHz), interpolated per the recommendation's own
log-frequency rule and exposed as itu_r_468_weighting; A and C remain as
labelled options and the 31.5 Hz - 400 Hz fundamental validity range is
documented. THD now raises when no harmonic fits below Nyquist instead of
returning 0, captures shorter than 64 samples are rejected, and the THD/
SINAD docstrings state the 14.12.3.2 R-convention and the derived nature
of SINAD explicitly.
New oracles: clipped-sine Fourier constants, the full-signal DIM
regression recipe (1.536 MHz synthesis, weak polynomial nonlinearity,
FIR decimation to 192 kHz, exact-bin FFT oracle), the H1 input-noise
bias SNR/(1+SNR), the printed ITU-R 468 table values including the AES17
CCIR-RMS cross-check, and the effective-notch-Q validation.
* test: ISO 532-1 Annex B.4 level-ramp oracles and reference-source provenance
The Annex B.4 ramp signals 6-9 join the validation suite: the tone ramps
(250 Hz / 1 kHz / 4 kHz, 30 dB to 80 dB at 5 dB/s over 10 s inside a
10.6 s file) are synthesized from parameters measured on the official
electronic-attachment WAVs and pinned to the workbook Nmax/N5 with
tolerances calibrated against those WAVs (module-on-official-WAV agrees
with the workbook to < 0.01 % on Nmax; the synthesis lands within +0.3 %
/ -0.8 %, pinned at 0.6 % / 2 %). The pink-noise ramp (overall SPL
25.85 + 5t dB per the WAV's Annex B.1 calibration) is pinned at a
realization-honest 10 %.
The sub-1-sone phon conversion (0.0005 offset, 3.0 phon floor, exact
10/lg 2 factor) and the NX percentile ((k-1, k) mean at
k = int((1 - X/100) n)) are now confirmed line by line against the
attachment's Annex A.4 reference program source; the provenance
docstrings record the exact code they match.
* docs: IEC-exact IMD semantics, AES17 bandwidth and ITU-R 468 weighting across guides and reference
The electroacoustics guide (EN and ES) and the API reference describe the
per-order modulation and difference-frequency definitions with their
references and arithmetic sums, the 14.12.10 two-product TDFD with the
standard 8 kHz / 11.95 kHz defaults, the AES17 measurement-bandwidth chain
with its bandwidth escape hatch, the effective notch Q, the ITU-R BS.468-4
default weighting for weighted THD with the 31.5-400 Hz validity range,
the DIM denominator erratum link and the corrected THD/SINAD convention
wording. CHANGELOG records the added API and the fixes; the conformance
report and the distortion figure variants are regenerated.
* docs: the ISO 12354-1:2017 edition repeats the K24 clamp misprint and flips the double-leaf K24 sign
The 2017 edition repeats the impossible '0 <= K24 <= -4 dB' interval of the
2000 print verbatim, and its E.3.5 prints the double-leaf K24 with a plus
where the 2000 edition's formula and its own corroborating Figure E.9 curve
carry a minus; both are recorded in the errata registry and the sign choice
is noted at the implementation.
* docs: the theory pages and SII diagram teach the max-form disturbance
The equivalent disturbance prose in the theory pages and the diagram label
follow the corrected ANSI S3.5 clause 5.6 reading (the larger of masking
and internal noise, not their energy sum), in English and Spanish; the
ISO 532-1 provenance docstrings paraphrase the reference program instead of
quoting it; and the notch-Q compensation note states the bilinear-warp
caveat near Nyquist.
* fix: regenerate the SII figure for the max-form disturbance and analysis follow-ups
The speech-intelligibility figure consumes the corrected clause 5.6
disturbance, so its four variants are regenerated; the 468 weighting's
zero-frequency guard avoids floating-point equality and the exception
tests keep one throwing call per block.
feat: STOI/ESTOI objective intelligibility and AES17 dynamic range / idle channel noise (#230)
* feat: STOI/ESTOI intelligibility and AES17 dynamic range / idle channel noise
Add the two correlation-based objective speech-intelligibility measures in
phonometry.hearing.objective_intelligibility: STOI (Taal, Hendriks, Heusdens
& Jensen 2011) and ESTOI (Jensen & Taal 2016). They share a 10 kHz, 256-sample
Hann, 512-point, 15-band one-third-octave, 384 ms segment front end; STOI
averages the clipped per-band envelope correlation, ESTOI the row- and
column-normalised spectral correlation that tracks modulated maskers. The
stoi() entry point returns a STOIResult with the index, the intermediate
scores and a .plot().
Add the AES17-2015 noise measurements to electroacoustics.distortion:
dynamic_range (6.4.1) and idle_channel_noise (6.4.2), both reusing the
standard notch and the ITU-R BS.468-4 curve through the CCIR-RMS weighting
(the 468 curve with the standard -5.63 dB offset, unity at 2 kHz).
* test: oracles and conformance for STOI/ESTOI and AES17
STOI/ESTOI: degenerate cases (identical -> 1, uncorrelated -> low), SNR
monotonicity, level invariance, input validation and an external cross-check
against pystoi (added as a test-only dependency; the library reimplements from
the papers and never imports it at runtime, and the two agree to under 1e-6).
AES17: closed-form dBFS oracles for idle channel noise (a 1 kHz tone reads its
level minus the 5.63 dB CCIR-RMS offset) and dynamic range (full-scale sine
over a known residual), plus level scaling and monotonicity with noise.
Register five conformance checks and refresh docs/CONFORMANCE.md.
* docs: objective-intelligibility guides, AES17 sections and concept figure
Add the Objective Intelligibility (STOI & ESTOI) guide across the GitHub docs
and the EN/ES site, wire it into the Speech section, sidebar and indexes, and
document the AES17 dynamic range and idle channel noise in the electroacoustics
guides. Add the STOI-vs-ESTOI concept figure (four language/theme variants)
showing that ESTOI credits the speech glimpsed in a modulated masker's gaps
while STOI barely separates the two maskers. Refresh the generated API
reference, the curated API table and the CHANGELOG.
* refactor: address Sonar and bot review on STOI/ESTOI and AES17
- Extract the AES17 notch-Q validation into a shared _validate_notch_q helper,
removing the duplicated range literal across the distortion functions
(Sonar S1192).
- Move the input construction out of the pytest.raises blocks in the STOI
input-validation test so each block exercises a single call (Sonar S5778).
- Plot the STOI/ESTOI intermediate correlations on a [-1, 1] axis (they are
cosine-similarity quantities, not [0, 1] ratios) so anti-correlated bands
are not clipped.
- Vectorise the STOI frame extraction and spectrogram (one strided gather and
a single batched rfft), numerically identical to the per-frame loop; the
pystoi cross-check is unchanged at ~1e-16.
The framing keeps the reference range(0, n - frame, hop) (the trailing partial
frame is dropped, matching the authors' MATLAB), so the STOI/ESTOI oracle does
not move.
refactor: reorganize the package into twelve domain subpackages (3.2) (#162)
* refactor: private core to _internal, module-path shims, _plot skeleton (C1)
Foundation of the 3.2 package reorganization:
- _validation/_types/_warnings/_levels_math/utils move (git mv) into the
private phonometry/_internal/ package; ~90 import sites retargeted.
- New phonometry/_compat.py: dynamic sys.modules shims keep every moved
public module path importable for one deprecation cycle (silent import,
DeprecationWarning on attribute access); generalizes and absorbs the
former loudness.py PEP 562 shim (its 3.1 wording preserved).
- _plotting.py moves (git mv) to phonometry/_plot/common.py; a silent
explicit re-export shim keeps the old private path and the 84 lazy
.plot() call sites working until each domain retargets them.
- _warn_renamed gains a 'since' parameter (3.2 for the package moves).
- tests: new test_package_architecture.py (ast edge whitelist +
fresh-interpreter subpackage imports); test_deprecated_aliases gains the
frozen 85-path pre-move snapshot and the _MOVED shim behavior test;
pytest pythonpath=tests for the upcoming mirrored test tree.
- .git-blame-ignore-revs scaffold (hashes appended at the end of Phase 1).
* refactor: metrology subpackage (C2)
core, filter_design, frequencies, parametric_filters, levels, calibration,
compliance and uncertainty move (git mv) into phonometry/metrology/ with a
curated re-export __init__; the flat API and the old module paths keep
working (facade re-exports + _compat shims). Uncertainty renderers carve out
of _plot/common.py into _plot/metrology.py; the 14 metrology test files move
to tests/metrology/ and deep imports across tests/scripts point at the new
canonical paths.
* refactor: psychoacoustics subpackage (C3)
The thirteen psychoacoustics modules (Zwicker/ECMA/Moore-Glasberg loudness,
contours, sharpness, tonality, roughness, fluctuation strength, annoyance,
tonal audibility, private Zwicker tables) move into
phonometry/psychoacoustics/; renderers carve into _plot/psychoacoustics.py;
twelve test files move to tests/psychoacoustics/. The phonometry.loudness
alias now resolves to the relocated canonical module in a single hop.
* refactor: hearing and emission subpackages (C4+C5)
hearing/ gains threshold (renamed from hearing.py), noise-induced hearing
loss, occupational exposure, SII and STI; emission/ gains the ISO 3740
sound-power family, ISO 9614 intensity and ISO/TS 7849 vibration-based
power. Renderers carve into _plot/hearing.py and _plot/emission.py; the
eleven domain test files move under tests/hearing/ and tests/emission/.
* refactor: materials, room and building subpackages (C6-C8)
materials/ collects ISO 354/11654/12999-2 absorption, scattering-diffusion,
road absorption, impedance tube, airflow resistance and EN 29052-1 dynamic
stiffness; room/ the room-acoustics, impulse-response, open-plan, room-noise,
reverberation-prediction and EN 12354-6 modules; building/ the eleven-module
EN 12354 / ISO 717 / ISO 16283 insulation family with EN 15657 and
EN 12354-5 structure-borne sound. Renderers carve into the matching _plot
modules; twenty-six test files move under their domain directories.
* fix: complete the building plot TYPE_CHECKING imports (C8 follow-up)
* refactor: vibration and environmental subpackages (C9+C10)
vibration/ collects human vibration (ISO 8041-1/2631/5349), multiple-shock
(ISO 2631-5), mechanical mobility (ISO 7626-1) and transfer stiffness
(ISO 10846); environmental/ collects the renamed rating (Lden/Ldn) and
measurement (ISO 1996-2) modules plus outdoor propagation (ISO 9613-2),
air absorption (ISO 9613-1), wind-turbine noise (IEC 61400-11) and NT
ACOU 112 impulse prominence. Renderers carve into their _plot modules and
ten test files move under the domain directories.
* refactor: aircraft, underwater and electroacoustics subpackages (C11-C13)
aircraft/ collects EPNL, the renamed atmospheric absorption, ECAC Doc 29
airport contours and ECAC Doc 32 rotorcraft hemispheres; underwater/ the
renamed acoustics/propagation/sound_speed modules with ship noise, pile
driving, ambient noise, sonar equation, seabed reflection and the numerical
solvers; electroacoustics/ distortion and frequency response. Renderers
carve into their _plot modules; seventeen test files move under the domain
directories. All twelve domain subpackages are now in place.
* refactor: finalize the 3.2 package reorganization (C14)
- api-reference gains a Namespaces section (the twelve subpackages with
scope and the 'from phonometry import aircraft as air' idiom); README
quick-start notes the namespaces; CHANGELOG records the added namespaces,
the deprecated flat module paths and the pickle note.
- plot_excitation moves from _plot/common.py to _plot/room.py and the
facade imports it from there; _plot/common.py now holds only shared
helpers and infrastructure.
- hearing/ and environmental/ package __init__ re-export the full public
surface of their renamed modules (threshold, rating) so the pre-move
package-path imports stay silent, as promised by the migration contract.
- SonarCloud cpd exclusion follows core.py to metrology/; llms.txt
regenerated; .git-blame-ignore-revs lists the move commits and the local
blame config registers it.
Full suite 2647 passed; conformance 194/194; golden baseline byte-stable;
import time unchanged vs main (1.09 s vs 1.17 s).
* fix: re-export the measurement surface on phonometry.environmental
Subagent-review finding: the generated environmental/__init__.py missed the
ISO 1996-2 measurement module (its facade import block was momentarily
path-corrupted when the subpackage exports were collected), so
phonometry.environmental.assess_tonal_audibility and 15 sibling names
raised AttributeError while every other domain namespace was complete. Adds
the missing re-exports (including EnvironmentalMeasurementWarning on the
domain namespace) and a new architecture invariant test asserting that
every name the facade imports from a domain submodule is reachable on that
subpackage, so this class of gap cannot recur.
* test: forbid absolute self-imports in the architecture check
Review feedback: the ast edge extraction only saw relative imports, so an
absolute 'from phonometry.x import y' inside the package would bypass the
cross-package whitelist. Such imports now fail the architecture test
directly (the codebase has none; relative imports are the convention).
feat: rotorcraft single-event noise (ECAC Doc 32): flight-condition interpolation, kinematics, SEL/LASmax/EPNL and contours (#213)
* feat: rotorcraft flight-condition interpolation, kinematics and single-event metrics (ECAC Doc 32 / NORAH2)
Complete the hemisphere chain into the single-event method:
- flight_condition_weights / interpolated_source_level: the guidance
Eq. 3-10 interpolation across a hemisphere database (span-normalised
conditions with Ffc = 2 on the path angle, distance-scaled Delaunay
triangle blend inside the convex hull, nearest neighbour outside, and
optional database lookup triangulations, which the shipped NORAH
tables require because they triangulate the raw (V, gamma) plane).
- flight_path_kinematics: ground speed, airspeed, heading, curvature,
bank and path angle by central differences (Eq. 16-21; the path angle
follows Doc 32 Eq. 10, whose atan corrects the guidance's acos print).
- rotorcraft_event_level: the received one-third-octave history at
recorded time tr = te + r/c (Eq. 22, c = 346.1 m/s) with the
hemisphere frame oriented by heading and tilted by bank (pitch is
implicit in the hemispheres), integrated into LASmax, full-history
and 10 dB-down SEL (Doc 32 Eq. 27) and ICAO Annex 16 EPNL (Doc 32
Eq. 28) with the helicopter 50 Hz tone-correction start band.
Per-point airspeed/path-angle/heading/bank overrides serve radar-track
workflows and per-point level offsets the Eq. 2 class substitutions.
- rotorcraft_noise_contour: the single-event SEL/LASmax ground grid in
one vectorised pass per emission step.
- RotorcraftHemisphere.mirrored() for mirrored-rotor class members;
vectorised hemisphere lookup and ground-effect cores (bit-identical
to the previous scalar paths).
Every result is a frozen dataclass with .plot(). Validated end to end
against the NORAH2 reference implementation's ARP verification cases:
emission angles to 0.01 deg, hard-ground step levels to 0.08 dB(A) out
to 18 km, LASmax to 0.03 dB, SEL to 0.05-0.4 dB and PNLTM to 0.1 dB;
plus hand-checked interpolation simplices, closed-form kinematics and
the Lorentzian flyover SEL integral.
* test: NORAH2 ARP end-to-end oracle suite for the rotorcraft event chain
Local-only validation against the NORAH2 V2.0.74 prototype outputs
(skips cleanly when the archive is absent; NORAH2_DATA points at an
existing extraction). The ARP cases' .his/.onl/.inp files provide the
oracle for the emission angles, retarded times, hemisphere selection,
per-step levels, event metrics and the 187-microphone contour grid:
- Case 4 (sigma 1e6, 0.2 m microphone) and the Case 2 radar track
reproduce every step to 0.08-0.15 dB(A) out to 18 km; SEL/LASmax to
0.03-0.1 dB, PNLTM to 0.09 dB.
- Case 2 soft-ground events (sigma 2e5, 1.2 m microphone) reproduce the
metrics while bounding the documented far-tail divergence: the
prototype damps the coherent two-ray interference of guidance Eq. 30
towards the incoherent sum where the in-band comb phase is large, a
term neither ECAC Doc 32 nor the guidance contains; this
implementation follows the published Eq. 28-35.
- The A600 events pin the per-condition Eq. 2 certification offsets and
the contour test covers both ground classes of the .onl grid.
No NORAH data is committed; the parsers are test-side plumbing.
* feat: conformance checks for the rotorcraft interpolation, kinematics and event
Four hand-checkable additions to the Doc 32 domain: the Eq. 8
distance-scaled triangle blend of three uniform hemispheres (97.0367 dB
by hand), the Eq. 17 airspeed of a straight climbing track, the Eq. 22
recorded-time delay at 100 m slant distance, and the closed-form
Lorentzian SEL minus LASmax of a constant-speed level flyover
(10 lg(pi d / V), with the class-H ground holding dLg at the +6 dB
pressure doubling along the whole path). 261/261 checks pass.
* docs: rotorcraft single-event guide, flyover figure and errata entries
Extend the rotorcraft guide (docs tree and site EN/ES) with the
flight-condition interpolation, the track kinematics, the single-event
SEL/LASmax/EPNL and the ground-grid contours, each with runnable
modular-API snippets, and add the flyover time-history figure (four
language and theme variants with its reproduction snippet). Refresh the
aircraft section landing pages, the API quick reference and the
generated API pages, and record two NORAH2 guidance defects in the
errata registry: Eq. 21 prints acos for the path angle where Doc 32
Eq. 10 prints the correct atan, and the shipped triangulation lookup
tables triangulate the raw (V, gamma) plane instead of the normalised
one prescribed by section A.3.1.
* fix: drop runtime asserts from the rotorcraft event chain (Bandit B101)
The event histories now always carry the first receiver's unweighted
band levels (the single-receiver event needs them for the
perceived-noise metrics and the cost is negligible next to the (K, G)
histories), so the Optional return and its narrowing assert disappear;
the per-point level-offset narrowing spells out its zero fallback
instead of asserting. bandit -r src is clean and the behaviour is
unchanged.
* test: record the expected library warnings instead of leaking them
pytest-xdist forwards every uncaught warning from a worker to the
controller, which re-materialises it by importing the warning class's
module; a warning class defined inside phonometry therefore makes the
controller import the package concurrently from several receiver
threads, and the import can be observed partially initialised
(ImportError on phonometry.hearing.sti) or deadlock on Windows. The
suite leaked exactly five such warnings, all expected by their tests:
the two extra STIWarning advisories of the pure-tone STIPA call (the
matched pytest.warns re-emitted the non-matching ones), the
FilterBankWarning detrending advisory of the steady-state initial
conditions, and the ISO 10846-3 validity advisory of the synthetic
transfer-stiffness plot data. Each is now asserted where it is
expected; the remaining run-summary warnings all carry builtin
categories, which the controller reconstructs without touching
phonometry.
* refactor: rotorcraft quality-gate and review-round fixes
SonarCloud:
- The bank tilt of the emission frame applies unconditionally: at zero
bank the rotation is the IEEE identity (cos 0 = 1, sin 0 = 0), so the
float-equality branch disappears with bit-identical output (verified
against the NORAH2 ARP residuals, unchanged to the last digit).
- The lookup-table search of the enveloping triangle moves to its own
helper, cutting the cognitive complexity of _enveloping_simplex.
- The single-event internals now flow through one validated _EventSetup
(source database, track state, ground and atmosphere) built by a
shared boundary that both public functions call, removing their
duplicated validation; the public signatures keep the ECAC Doc 32
parameter set one for one, covered by the same per-file S107 record
the Doc 29 airport API already uses in sonar-project.properties.
- The band-grid validation test builds its hemispheres outside the
pytest.raises blocks so each block holds one failing call.
Review round:
- The kinematics bank angle evaluates (dTheta/dt)*Vg/g, equal to
K*Vg^2/g without the intermediate 1/Vg division that could overflow
at minute ground speeds; the curvature output is unchanged.
- flight_path_kinematics reuses the shared track validation helper.
- atmospheric_adjustment reuses the shared pure-tone coefficient
helper, leaving a single suppression site for the sub-50 Hz advisory.
- The flight-condition docstring states the guidance Eq. 3-6 scaling
exactly: spans only, no minima subtracted (a shared offset cancels in
the Eq. 7 distances); the generated API page follows.
fix: zero-phase STI analysis bank, verified against the IEC 60268-16 certified test bench (#177)
* fix: zero-phase STI analysis bank; dead octave bands warn instead of raising
The single-pass octave bank failed two normative verification criteria
of IEC 60268-16:2020 on the rev 5 verification test bench: the C.4.2
filter-slope test (an unmodulated tone one octave below the observed
band, 41 dB louder, leaked with only ~31 dB attenuation and dragged the
measured m down to ~0.1 against the m >= 0.5 criterion) and the A.3.1.2
phase-distortion limit (STI bias reached -0.020 at TI = 0.9 against the
< 0.01 criterion). Forward-backward (zero-phase) filtering removes the
phase bias by construction and doubles the effective skirt attenuation
to ~63 dB one octave out; both criteria now pass with wide margin
(worst slope-test m = 0.937, worst edge-carrier bias -0.0029).
stipa() also no longer raises when an octave band of the recording has
no energy: dead bands read m = 0 (TI = 0, the worst-case reading of a
real meter) under an STIWarning, so component-level verification
signals that deliberately occupy only two bands (C.4.2) remain
measurable.
STI values on ordinary signals shift by well under 0.001; the
sti_vs_t60 figures are regenerated accordingly (sub-visual marker
moves) and the quoted loopback figures updated (5 s loopback is now
~0.956).
* test: end-to-end STIPA oracle against the stipa.info certified bench
The 49 verification WAVs of the IEC 60268-16 rev 5 test bench published
by Embedded Acoustics at stipa.info (Annex C.3.2 modulation-depth
staircase, C.3.3 exponential decays, C.4.2 filter slope, A.2.2
weighting factors, A.3.1.2 filter-bank phase) run as a black-box oracle
over stipa() and sti_from_impulse_response(). The data is third-party
and stays local: the module skips cleanly when plan/stipa-verification/
is absent (STIPA_VERIFICATION_DATA overrides the location).
Tolerances are evidence-based on the measured worst-case deviations:
C.3.2 |dSTI| 0.0031 and per-cell |dm| 0.004 (tol 0.01), C.3.3 |dSTI|
0.0002 (tol 0.005) and per-cell |dm| 0.018 (tol 0.03), A.2.2 |dSTI|
0.0002 vs the exact alpha/beta identity (tol 0.005), plus the normative
criteria m >= 0.5 (C.4.2) and |STI bias| < 0.01 (A.3.1.2).
* feat: six IEC 60268-16 verification conformance checks (257 total)
CI-runnable synthetic counterparts of the certified STIPA bench, built
from deterministic sine carriers: the C.3.2 direct-method staircase at
m = 0.2/0.5/0.8 (+/-0.01 STI, tightened from +/-0.05 on the evidence of
the certified bench), the C.3.3 RT60 = 1 s exponential decay against
the closed-form Schroeder MTF (+/-0.005), the C.4.2 filter-slope
criterion (m >= 0.5 in the 125 Hz band under a +41 dB unmodulated
62.5 Hz tone) and the A.2.2/A.3.1.2 audio-path checks (weighting-factor
pair 0.398 +/-0.005; edge carriers at TI = 0.9 within the normative
0.01 bias).
* docs: note the IEC 60268-16 rev 5 certified verification bench in the STI guides
The STI guide (docs and EN/ES site mirrors) now states that the
analyzer is verified end to end against the stipa.info verification
test bench signals (Embedded Acoustics BV), lists the five suites and
notes that the certified WAVs stay local while CI re-derives the same
constructions synthetically. CHANGELOG updated under Added and Fixed.
* fix: correct dead-band warning stacklevel, guard silent bench files and document short-decay MTF smearing
* test: assert STIWarning type for dead bands and clarify the conformance check count