Correct the framing and the guards of the perception and noise-control fiches (#355)
A review pass over the psychoacoustics, hearing, speech, environmental and
noise-control fiches. The numerics held up; what did not were the statements
printed around them, plus one wrong table lookup.
ISO 1999 validated domain. nipts()/htlan() applied Formula (2) wherever they
were asked to: L_EX,8h = 130 dB over 60 years at the 0,99 fractile returned a
357,9 dB threshold shift with nothing marking it as an extrapolation. Since the
formula is quadratic in (L_EX,8h - L0) that number is meaningless, not merely
uncertain. Both now warn (NoiseInducedHearingLossWarning) outside the stated
ranges while still computing: durations outside 1-40 years (clause 6.3.1
validates Formula (2) over 10-40 and Formula (3) over 1-10), fractiles in the
tails clause 6.3.2 says "should not be estimated" (Q below 5 % or above 95 %),
and levels above the 100 dB of Annex D that the Scope's NOTE 4 restricts
validity to. The fiches print the caveat when the conditions sit outside.
ISO 1999 Q. The fiches printed the library's fractile under ISO 1999's symbol,
inverting its meaning: in Formulae (4)/(5) Q is the percentage with worse
hearing, so the most-susceptible tenth is Q = 10 %, not the "Q = 0.9" the fiche
showed while citing 6.3.2. They now print ISO's Q as ISO defines it; the guides
note the difference from the fractile argument. Two further disclosures on the
same fiches: the age component H is evaluated from ISO 7029:2017 (the edition
ISO 1999 references undated in 6.2.2), which departs from the illustrative
Table A.3 selection by up to about 7 dB at the median, and the 2/3/4 kHz
average is the user's own choice, the Scope's NOTE 1 specifying no frequency
combination. The footer states a population scope rather than "the results
relate only to the tested specimen", which describes a laboratory specimen.
ISO 1999 Annex C is wired in as an independent oracle: its worked example (male
population aged 50, 90 dB for 30 years, 1/2/4 kHz at Q = 10 %) reproduces the
Table D.2 shifts it consumes (0, 9, 19 dB), the 13,3 dB left of the 4 kHz shift
after the Formula (1) compression (C.5) and the 31,1 dB combined threshold
(C.11). The composition it exercises is now exposed as combine_age_and_noise(),
which clause 6.2 wants available on its own: 6.2.3 recommends a database B
collected on a control population of the country under consideration, and this
applies Formula (1) to whichever database the user brings.
Verdict precision. The STI and SII fiches printed the requirement with one
decimal beside a measured value at full precision, so a verdict could contradict
itself: "STI = 0.50, required >= 0.5 -> FAIL" against a 0,52 requirement, and an
SII minimum of 0,75 printed as "0.8" (the committed example said so). Both now
print the requirement at the quantity's own precision. IEC 60268-16 Annex M
joins the conformance report as an end-to-end oracle: its printed MTF matrix,
speech spectrum and ambient noise give back its published MTI row and
STI = 0,76, exercising the whole A.5.3 to A.5.8 chain.
Noise-control prediction framing. The enclosure, silencer and HVAC fiches
compute design models but read as measurements: no prediction statement,
measurement headers, a footer scoped to a tested specimen, and committed
examples citing instrumentation that never existed ("Two-microphone
transfer-matrix bench"). They now follow the EN/ISO 12354 prediction exemplar
already in the repo, with a prediction-basis line, a statement naming what each
model does not represent, and a prediction footer. Their verdicts also decided
on the half-away-from-zero display rounding while printing Python's
round-half-to-even, so 0,25 printed 0.2 against a verdict taken on 0,3; both
now use the display rounding.
ISO/PAS 1996-3. With every onset rate at or below 10 dB/s the fiche withheld
the adjustment on the onset-rate gate (clauses 4.5/8) but justified it with "no
prominent impulse is present (governing P = 6.01 <= 5)", contradicting its own
boxed P. The note now states whichever gate governs. The assessment period is
read from the result instead of being hardcoded: 30 min is the Clause 5
*default*, now the default of the new assessment_period_min argument.
Smaller corrections:
- the HVAC elbow insertion loss took the wrong row of Bies Table 8.11 for a
W/lambda exactly on a bin edge; the rows read "a <= W/lambda < b", so an edge
opens its row (1 dB at 0,14, not 0 dB);
- the wind-turbine tonality fiche cited "subclauses 9.5.2-9.5.5" of
IEC 61400-11 for a chain running through 9.5.6 to 9.5.8 (Formulae 31-34);
- the ISO 1996-2 tonal fiche notes that Table J.1 keys on the mean audibility
of the J assessed spectra (ISO/PAS 20065 Clause 5.3.9), which a
single-spectrum assessment stands in for;
- the shared 10-90 % fractile band carried a hardcoded English legend in
Spanish figures, and the Spanish silencer and HVAC fiches printed the device
kind and duct-element label in English.
docs/ERRATA.md records two more source defects found on the way: ISO/PAS
1996-3:2022 Clause 5 swaps the 3.4/3.5 cross-references of the onset rate and
the level difference (its own units contradict the clause numbers printed with
them), and ISO 9613-2:1996 Table 2 prints 4,1 dB/km at 15 C / 80 % / 1 kHz
where ISO 9613-1 gives 4,1511, the neighbouring cells rounding correctly. The
library computes that coefficient from ISO 9613-1 directly, so it is unaffected.
Committed example fiches, the conformance report, the API reference and the
llms files are regenerated.
Correct the framing and the guards of the perception and noise-control fiches (#355)
A review pass over the psychoacoustics, hearing, speech, environmental and
noise-control fiches. The numerics held up; what did not were the statements
printed around them, plus one wrong table lookup.
ISO 1999 validated domain. nipts()/htlan() applied Formula (2) wherever they
were asked to: L_EX,8h = 130 dB over 60 years at the 0,99 fractile returned a
357,9 dB threshold shift with nothing marking it as an extrapolation. Since the
formula is quadratic in (L_EX,8h - L0) that number is meaningless, not merely
uncertain. Both now warn (NoiseInducedHearingLossWarning) outside the stated
ranges while still computing: durations outside 1-40 years (clause 6.3.1
validates Formula (2) over 10-40 and Formula (3) over 1-10), fractiles in the
tails clause 6.3.2 says "should not be estimated" (Q below 5 % or above 95 %),
and levels above the 100 dB of Annex D that the Scope's NOTE 4 restricts
validity to. The fiches print the caveat when the conditions sit outside.
ISO 1999 Q. The fiches printed the library's fractile under ISO 1999's symbol,
inverting its meaning: in Formulae (4)/(5) Q is the percentage with worse
hearing, so the most-susceptible tenth is Q = 10 %, not the "Q = 0.9" the fiche
showed while citing 6.3.2. They now print ISO's Q as ISO defines it; the guides
note the difference from the fractile argument. Two further disclosures on the
same fiches: the age component H is evaluated from ISO 7029:2017 (the edition
ISO 1999 references undated in 6.2.2), which departs from the illustrative
Table A.3 selection by up to about 7 dB at the median, and the 2/3/4 kHz
average is the user's own choice, the Scope's NOTE 1 specifying no frequency
combination. The footer states a population scope rather than "the results
relate only to the tested specimen", which describes a laboratory specimen.
ISO 1999 Annex C is wired in as an independent oracle: its worked example (male
population aged 50, 90 dB for 30 years, 1/2/4 kHz at Q = 10 %) reproduces the
Table D.2 shifts it consumes (0, 9, 19 dB), the 13,3 dB left of the 4 kHz shift
after the Formula (1) compression (C.5) and the 31,1 dB combined threshold
(C.11). The composition it exercises is now exposed as combine_age_and_noise(),
which clause 6.2 wants available on its own: 6.2.3 recommends a database B
collected on a control population of the country under consideration, and this
applies Formula (1) to whichever database the user brings.
Verdict precision. The STI and SII fiches printed the requirement with one
decimal beside a measured value at full precision, so a verdict could contradict
itself: "STI = 0.50, required >= 0.5 -> FAIL" against a 0,52 requirement, and an
SII minimum of 0,75 printed as "0.8" (the committed example said so). Both now
print the requirement at the quantity's own precision. IEC 60268-16 Annex M
joins the conformance report as an end-to-end oracle: its printed MTF matrix,
speech spectrum and ambient noise give back its published MTI row and
STI = 0,76, exercising the whole A.5.3 to A.5.8 chain.
Noise-control prediction framing. The enclosure, silencer and HVAC fiches
compute design models but read as measurements: no prediction statement,
measurement headers, a footer scoped to a tested specimen, and committed
examples citing instrumentation that never existed ("Two-microphone
transfer-matrix bench"). They now follow the EN/ISO 12354 prediction exemplar
already in the repo, with a prediction-basis line, a statement naming what each
model does not represent, and a prediction footer. Their verdicts also decided
on the half-away-from-zero display rounding while printing Python's
round-half-to-even, so 0,25 printed 0.2 against a verdict taken on 0,3; both
now use the display rounding.
ISO/PAS 1996-3. With every onset rate at or below 10 dB/s the fiche withheld
the adjustment on the onset-rate gate (clauses 4.5/8) but justified it with "no
prominent impulse is present (governing P = 6.01 <= 5)", contradicting its own
boxed P. The note now states whichever gate governs. The assessment period is
read from the result instead of being hardcoded: 30 min is the Clause 5
*default*, now the default of the new assessment_period_min argument.
Smaller corrections:
- the HVAC elbow insertion loss took the wrong row of Bies Table 8.11 for a
W/lambda exactly on a bin edge; the rows read "a <= W/lambda < b", so an edge
opens its row (1 dB at 0,14, not 0 dB);
- the wind-turbine tonality fiche cited "subclauses 9.5.2-9.5.5" of
IEC 61400-11 for a chain running through 9.5.6 to 9.5.8 (Formulae 31-34);
- the ISO 1996-2 tonal fiche notes that Table J.1 keys on the mean audibility
of the J assessed spectra (ISO/PAS 20065 Clause 5.3.9), which a
single-spectrum assessment stands in for;
- the shared 10-90 % fractile band carried a hardcoded English legend in
Spanish figures, and the Spanish silencer and HVAC fiches printed the device
kind and duct-element label in English.
docs/ERRATA.md records two more source defects found on the way: ISO/PAS
1996-3:2022 Clause 5 swaps the 3.4/3.5 cross-references of the onset rate and
the level difference (its own units contradict the clause numbers printed with
them), and ISO 9613-2:1996 Table 2 prints 4,1 dB/km at 15 C / 80 % / 1 kHz
where ISO 9613-1 gives 4,1511, the neighbouring cells rounding correctly. The
library computes that coefficient from ISO 9613-1 directly, so it is unaffected.
Committed example fiches, the conformance report, the API reference and the
llms files are regenerated.
Objective impulsive-sound prominence and LAeq adjustment (ISO/PAS 1996-3:2022) (#261)
* feat(environmental): objective impulsive-sound prominence and LAeq adjustment (ISO/PAS 1996-3:2022)
Add environmental.impulsive_sound, the objective method of ISO/PAS 1996-3:2022
for the prominence of impulsive sounds and the adjustment of LAeq.
impulsive_sound_adjustment takes a calibrated pressure signal, builds the
A-weighted, F time-weighted level history LpAF (Clause 4), detects onsets where
the gradient exceeds 10 dB/s and merges events less than 50 ms apart (Clause 3.3
and procedures a-d of Clause 4), measures each onset's level difference
LD = Le - Ls (3.4) and its least-squares onset rate OR (3.5), evaluates the
prominence P = 3 lg(OR) + 2 lg(LD) (Clause 5) and returns the governing
adjustment KI = 1.8 (P - 5) dB for P > 5, else 0 dB (Clause 6). The result also
carries the source category of Clause 7 (not impulsive, regular impulsive or
highly impulsive) and the adjusted LAeq, in a frozen ImpulsiveSoundResult with a
.plot() that draws the level history with the detected onsets marked.
sound_pressure_level_history and detect_onsets expose the level-history and
onset-analysis stages; detect_onsets also offers the upper-half onset-rate
variant for pass-bys (3.5, Note 1). The prominence and adjustment formulae are
shared with NT ACOU 112 and reused from impulse_prominence.
The standard gives no worked numeric example, so the tests and the two
conformance checks use an oracle derived by hand from the standard's own
definitions: a straight LpAF ramp of 30 dB over 0.30 s has, by construction,
OR = 100 dB/s and LD = 30 dB, so P = 8.9542426 and KI = 7.1176366 dB. Boundary
tests cover a steady interval (no onset, 0 dB) and a very sudden ramp (highly
impulsive), and the onset merging, the pass-by variant and the full signal
chain are exercised on synthesized signals.
Flat-API re-export, API-reference rows, EN and ES guide sections, the
bibliography note and a CHANGELOG entry are included.
* docs: regenerate the conformance report for the ISO/PAS 1996-3 checks
* review: anchor the onset trend line at the span midpoint and hoist test calls
Draw the fitted onset slope through the onset-span midpoint (the least-squares
regression passes through the sample centroid) so the dashed trend no longer
overshoots the marked endpoints. Also construct the signal/result outside the
pytest.raises blocks so only the call under test can raise.
Add ISO 9613-2 outdoor-propagation prediction reports via .report() (#332)
* 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.