[READ-ONLY] Mirror of https://github.com/jmrplens/PyOctaveBand. [Python3] Octave-Band and Fractional Octave-Band filter. For signal in time domain. jmrplens.github.io/PyOctaveBand/
acoustics audio filter frequency frequency-analysis frequency-domain octave python3 signal time-domain
0

Configure Feed

Select the types of activity you want to include in your feed.

docs: split the three overloaded guides

Audit batch 6 - reorganization with full content preservation (verified
line-by-line against HEAD across the three surfaces):

- room-acoustics (1096 lines) keeps ISO 18233 + ISO 3382-1/2/3 + ISO 354
measurement; the building half (ISO 16283 + ISO 717, ISO 10140,
EN 12354-1/2, ISO 12999-1) moves to a new docs/building-acoustics.md
built from the site's existing split, resolving the repo/site
asymmetry. The flanking figure is reunited with its <details> code and
flanking_path is introduced before it is referenced (audit fixes), on
all three surfaces.
- psychoacoustics extracts STI/STIPA to speech-transmission.md
(paralleling speech-intelligibility.md) with STI-vs-SII disambiguation
boxes on the three pages involved; ISO 226 moves into psychoacoustics
where its perception content belongs, fixing its stale 'upcoming
feature' sentence.
- levels extracts occupational-exposure.md (ISO 9612) and
tone-prominence.md (ECMA-418-1), keeping Leq/LN/peak/SEL/Lden and the
spectrogram.

Indexes, sidebar, theory/guide cross-links (~40 edits in 19 files) and
the llms.txt generator PAGES list follow the moves; the four extracted
topics now survive into llms-full.txt instead of dropping out.

José M. Requena Plens (Jul 10, 2026, 6:24 AM +0200) 3ef3ca61 eb7609fa

+3584 -2001
+7 -3
README.md
··· 57 57 | [Filter Banks](https://github.com/jmrplens/phonometry/blob/main/docs/filter-banks.md) | Architectures, response gallery, band decomposition, zero-phase | 58 58 | [Frequency Weighting](https://github.com/jmrplens/phonometry/blob/main/docs/weighting.md) | A/C/Z curves, class 1 high-accuracy mode | 59 59 | [Time Weighting](https://github.com/jmrplens/phonometry/blob/main/docs/time-weighting.md) | Fast/Slow/Impulse ballistics, initial state | 60 - | [Levels](https://github.com/jmrplens/phonometry/blob/main/docs/levels.md) | Leq, LAeq, percentiles, LCpeak, SEL, noise dose, occupational exposure strategies and uncertainty (ISO 9612), Lden, tonality, octave spectrogram | 61 - | [Psychoacoustics](https://github.com/jmrplens/phonometry/blob/main/docs/psychoacoustics.md) | Zwicker (ISO 532-1), Moore-Glasberg (ISO 532-2/3) and Sottek (ECMA-418-2) loudness, sharpness (DIN 45692), tonality & roughness (ECMA-418-2), STI/STIPA (IEC 60268-16) | 60 + | [Levels](https://github.com/jmrplens/phonometry/blob/main/docs/levels.md) | Leq, LAeq, percentiles, LCpeak, SEL, noise dose (IEC 61252), Lden and rating levels (ISO 1996-1), octave spectrogram | 61 + | [Occupational Exposure](https://github.com/jmrplens/phonometry/blob/main/docs/occupational-exposure.md) | ISO 9612 task-based, job-based and full-day strategies with the Annex C uncertainty budget (LEX,8h + U) | 62 + | [Tone Prominence](https://github.com/jmrplens/phonometry/blob/main/docs/tone-prominence.md) | ECMA-418-1 tone-to-noise ratio and prominence ratio with frequency-dependent prominence criteria | 63 + | [Psychoacoustics](https://github.com/jmrplens/phonometry/blob/main/docs/psychoacoustics.md) | Zwicker (ISO 532-1), Moore-Glasberg (ISO 532-2/3) and Sottek (ECMA-418-2) loudness, sharpness (DIN 45692), equal-loudness contours (ISO 226), tonality & roughness (ECMA-418-2) | 64 + | [Speech Transmission](https://github.com/jmrplens/phonometry/blob/main/docs/speech-transmission.md) | STI/STIPA (IEC 60268-16): modulation transfer function, indirect method from impulse responses and direct STIPA measurement | 62 65 | [Sound Intensity](https://github.com/jmrplens/phonometry/blob/main/docs/intensity.md) | Two-microphone p-p intensity (IEC 61043), ISO 9614-1 field indicators | 63 - | [Room & Building Acoustics](https://github.com/jmrplens/phonometry/blob/main/docs/room-acoustics.md) | Impulse responses (ISO 18233), room parameters (ISO 3382-1/2), open-plan metrics (ISO 3382-3), field airborne + impact + façade insulation and weighted ratings (ISO 16283-1/2/3, ISO 717-1/2), laboratory characterisation (ISO 10140), flanking-transmission prediction (EN 12354-1/2), measurement uncertainty (ISO 12999-1), sound absorption (ISO 354) | 66 + | [Room Acoustics](https://github.com/jmrplens/phonometry/blob/main/docs/room-acoustics.md) | Impulse responses (ISO 18233), room parameters (ISO 3382-1/2), open-plan metrics (ISO 3382-3), sound absorption (ISO 354) | 67 + | [Building Acoustics](https://github.com/jmrplens/phonometry/blob/main/docs/building-acoustics.md) | Field airborne + impact + façade insulation and weighted ratings (ISO 16283-1/2/3, ISO 717-1/2), laboratory characterisation (ISO 10140), flanking-transmission prediction (EN 12354-1/2), measurement uncertainty (ISO 12999-1) | 64 68 | [Outdoor Sound Propagation](https://github.com/jmrplens/phonometry/blob/main/docs/outdoor-propagation.md) | Atmospheric absorption α(f) (ISO 9613-1) and the ISO 9613-2 general method: geometrical divergence, atmospheric absorption, ground effect, barrier screening and meteorological correction | 65 69 | [Sound Power](https://github.com/jmrplens/phonometry/blob/main/docs/sound-power.md) | Sound power level LW by enveloping surface (ISO 3744/3746), reverberation room (ISO 3741) and intensity scanning (ISO 9614-2) | 66 70 | [Calibration and dBFS](https://github.com/jmrplens/phonometry/blob/main/docs/calibration.md) | Physical SPL, digital full-scale, RMS vs peak |
+8 -4
docs/README.md
··· 9 9 - [Filter Banks](filter-banks.md) — architectures, responses, band decomposition 10 10 - [Frequency Weighting](weighting.md) — A, C, Z curves 11 11 - [Time Weighting](time-weighting.md) — Fast, Slow, Impulse ballistics 12 - - [Integrated & Statistical Levels](levels.md) — Leq, LAeq, L10/L50/L90, noise dose and occupational exposure (ISO 9612), octave spectrogram 13 - - [Psychoacoustics and Speech Intelligibility](psychoacoustics.md) — Zwicker loudness, sharpness, STI/STIPA 12 + - [Integrated & Statistical Levels](levels.md) — Leq, LAeq, L10/L50/L90, LCpeak/SEL, noise dose (IEC 61252), Lden and rating levels (ISO 1996-1), octave spectrogram 13 + - [Occupational noise exposure](occupational-exposure.md) — the ISO 9612 task-based, job-based and full-day measurement strategies and the Annex C uncertainty budget behind every LEX,8h report 14 + - [Prominent discrete tones](tone-prominence.md) — the ECMA-418-1 tone-to-noise and prominence ratios that decide whether a discrete tone is prominent and justify tonal rating adjustments 15 + - [Psychoacoustics](psychoacoustics.md) — Zwicker, Moore-Glasberg and Sottek loudness, sharpness, equal-loudness contours (ISO 226), tonality and roughness 16 + - [Speech Transmission Index](speech-transmission.md) — how much of the speech envelope a room or sound system preserves: the IEC 60268-16 modulation transfer function, indirect method and direct STIPA measurement 14 17 - [Speech Intelligibility Index](speech-intelligibility.md) — the ANSI S3.5-1997 one-third-octave-band SII: band-importance weighting (Table 3), self-speech and upward spread of masking, band audibility, and the index in noise and hearing loss 15 18 - [Room-noise criteria](room-noise.md) — the ANSI/ASA S12.2-2019 room-noise ratings: the NC tangency method (Table 1) and the RC Mark II rating with its rumble/hiss/neutral spectral tag (Annex D) 16 19 - [Hearing threshold](hearing-threshold.md) — the age-related hearing threshold distribution (ISO 7029:2017) and the free-field/diffuse-field reference threshold of hearing (ISO 389-7:2006) ··· 18 21 - [Impulsive-sound prominence](impulse-prominence.md) — the NT ACOU 112:2002 predicted prominence of impulsive sounds (onset rate and level difference) and the graduated adjustment KI added to LAeq 19 22 - [Measurement uncertainty](gum-uncertainty.md) — the GUM law of propagation of uncertainty and the Monte Carlo method (ISO/IEC Guide 98-3:2008 and Supplement 1): combined and expanded uncertainty, Welch–Satterthwaite effective degrees of freedom, and probabilistically symmetric coverage intervals 20 23 - [Sound Intensity (p-p)](intensity.md) — two-microphone intensity and field indicators 21 - - [Room and Building Acoustics](room-acoustics.md) — impulse-response acquisition, reverberation and room parameters, open-plan speech metrics, field airborne/impact/façade sound insulation (ISO 16283-1/2/3), laboratory characterisation (ISO 10140), flanking-transmission prediction (EN 12354-1/2), measurement uncertainty (ISO 12999-1), sound absorption (ISO 354) 24 + - [Room Acoustics](room-acoustics.md) — impulse-response acquisition (ISO 18233), reverberation and room parameters (ISO 3382-1/2), open-plan speech metrics (ISO 3382-3), reverberation-room sound absorption (ISO 354) 25 + - [Building Acoustics](building-acoustics.md) — field airborne/impact/façade insulation and weighted ratings (ISO 16283-1/2/3, ISO 717-1/2), laboratory characterisation (ISO 10140), flanking-transmission prediction (EN 12354-1/2), measurement uncertainty (ISO 12999-1) 22 26 - [Sound absorption in enclosed spaces](enclosed-space-absorption.md) — the EN 12354-6:2003 prediction of a room's total equivalent absorption area and reverberation time from its surfaces and objects (Clause 4) 23 - - [Outdoor Sound Propagation](outdoor-propagation.md) — atmospheric absorption α(f) (ISO 9613-1) and the ISO 9613-2 general method: divergence, atmospheric absorption, ground effect and barrier screening (occupational exposure ISO 9612 lives in [Levels](levels.md)) 27 + - [Outdoor Sound Propagation](outdoor-propagation.md) — atmospheric absorption α(f) (ISO 9613-1) and the ISO 9613-2 general method: divergence, atmospheric absorption, ground effect and barrier screening 24 28 - [Sound Power](sound-power.md) — sound power level by enveloping surface (ISO 3744/3746), reverberation room (ISO 3741), intensity scanning (ISO 9614-2), and the precision grades in an anechoic room (ISO 3745) and by precision intensity scanning (ISO 9614-3) 25 29 - [Acoustic Materials](materials.md) — sound-absorption rating α_w and classes (ISO 11654), airflow resistance static and alternating methods (ISO 9053-1/-2), and impedance-tube measurement of absorption, surface impedance and transmission loss (ISO 10534-1/-2, ASTM E2611) 26 30 - [Surface Scattering, Diffusion and In-situ Absorption](surface-scattering.md) — random-incidence scattering (ISO 17497-1), free-field diffusion coefficient (ISO 17497-2), and in-situ road-surface absorption by the extended-surface subtraction technique (ISO 13472-1) and the spot method (ISO 13472-2)
+663
docs/building-acoustics.md
··· 1 + ← [Documentation index](README.md) 2 + 3 + # Building Acoustics & Sound Insulation 4 + 5 + This guide continues from the [Room Acoustics guide](room-acoustics.md): 6 + the same impulse response, measured either side of a partition, yields its sound 7 + insulation. Where room acoustics describes the sound field inside a single space, 8 + building acoustics describes how much of that field passes *between* spaces. This 9 + page follows the insulation chain — field airborne, impact and façade insulation 10 + with single-number ratings (ISO 16283-1/2/3, ISO 717-1/2), the laboratory 11 + characterisation of a building element (ISO 10140), the prediction of in-situ 12 + performance from flanking transmission (EN 12354-1/2), and the measurement 13 + uncertainty that qualifies every rating (ISO 12999-1). 14 + 15 + ## 1. Field insulation and single-number ratings (ISO 16283-1, ISO 717-1) 16 + 17 + To rate a wall or floor, measure the energy-average level in the **source** 18 + room ($L_1$) and the **receiving** room ($L_2$) per one-third-octave band 19 + and form the level difference $D = L_1 - L_2$. Two normalisations make it 20 + comparable between rooms. The **standardized level difference** references 21 + the receiving-room reverberation time $T$ to $T_0 = 0.5$ s (so with 22 + $T = 0.5$ s, $D_{nT} = D$ exactly), and the **apparent sound reduction 23 + index** normalises by the partition area $S$ and the Sabine absorption area 24 + $A$: 25 + 26 + $$ 27 + D_{nT} = D + 10 \log_{10} \frac{T}{T_0}, \qquad 28 + R' = D + 10 \log_{10} \frac{S}{A}, \qquad A = \frac{0.16\ V}{T}. 29 + $$ 30 + 31 + Positions are energy-averaged with 32 + $L = 10 \log_{10}\left( \frac{1}{n} \sum_i 10^{L_i/10} \right)$. 33 + 34 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_insulation_setup_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_insulation_setup.svg" alt="Field airborne insulation setup: a loudspeaker in the source room, microphones energy-averaged in source and receiving rooms across the common partition" width="92%"></picture> 35 + 36 + The band spectrum is collapsed to one number by the **reference-curve 37 + method** of ISO 717-1: a fixed reference curve is shifted in 1 dB steps 38 + toward the measured curve until the sum of *unfavourable* deviations 39 + (where the measurement falls below the reference) is as large as possible 40 + but not more than 32.0 dB (16 one-third-octave bands) or 10.0 dB (5 octave 41 + bands). The rating (`Rw`, `R'w`, `DnT,w` …) is the shifted reference read at 42 + 500 Hz. The **spectrum adaptation terms** $C$ (pink noise) and $C_{tr}$ 43 + (urban traffic) add the low-frequency penalty of a real source. 44 + 45 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/insulation_rating_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/insulation_rating.png" alt="Measured one-third-octave sound reduction index with the shifted ISO 717-1 reference curve and the resulting weighted rating at 500 Hz" width="80%"></picture> 46 + 47 + ```python 48 + import numpy as np 49 + from phonometry import airborne_insulation, weighted_rating, energy_average_level 50 + 51 + # Energy-average several microphone positions in one room (dB) 52 + print(round(float(energy_average_level([60.0, 66.0])), 1)) # 64.0 53 + 54 + # Field insulation per band; area S and volume V add R' 55 + l1 = np.full(16, 80.0) # source-room levels 56 + l2 = np.full(16, 40.0) # receiving-room levels 57 + t2 = np.full(16, 0.5) # receiving-room T (s) 58 + ins = airborne_insulation(l1, l2, t2, area=10.0, volume=50.0) 59 + print(round(float(ins.dnt[0]), 1)) # 40.0 (= D since T = T0) 60 + print(round(float(ins.r_prime[0]), 1)) # 38.0 61 + 62 + # Single-number rating from a measured 16-band R spectrum (ISO 717-1 Annex C) 63 + R = [20.4, 16.3, 17.7, 22.6, 22.4, 22.7, 24.8, 26.6, 64 + 28.0, 30.5, 31.8, 32.5, 33.4, 33.0, 31.0, 25.5] 65 + w = weighted_rating(R) 66 + print(w.rating, w.c, w.ctr) # 30 -2 -3 -> Rw(C;Ctr) = 30(-2;-3) 67 + 68 + w.plot() # measured R' vs shifted ISO 717-1 reference, deviations shaded (needs matplotlib) 69 + ``` 70 + 71 + <details> 72 + <summary>Show the code for this figure</summary> 73 + 74 + ```python 75 + import matplotlib.pyplot as plt 76 + 77 + # One line — measured curve vs the shifted ISO 717-1 reference, deviations shaded: 78 + w.plot() 79 + plt.show() 80 + 81 + # By hand, from the band curve the result now carries: 82 + fig, ax = plt.subplots() 83 + ax.semilogx(w.band_centers, w.measured, "o-", label="Measured R'") 84 + ax.semilogx(w.band_centers, w.shifted_reference, "s--", label="Shifted reference") 85 + ax.fill_between(w.band_centers, w.measured, w.shifted_reference, 86 + where=w.measured < w.shifted_reference, interpolate=True, 87 + alpha=0.3, label="Unfavourable deviations") 88 + ax.set_xlabel("Frequency [Hz]") 89 + ax.set_ylabel("Sound reduction index [dB]") 90 + ax.set_title(f"Rw = {w.rating} dB (C={w.c:+d}; Ctr={w.ctr:+d})") 91 + ax.legend() 92 + plt.show() 93 + ``` 94 + 95 + </details> 96 + 97 + Compute `l1`, `l2` and `t2` on the same 16 one-third-octave bands from 98 + 100 Hz to 3150 Hz — obtain `t2` from 99 + `room_parameters(ir, fs, limits=(100, 3150), fraction=3).t30`, for example — and 100 + pass them to `airborne_insulation`. Feed that function's `dnt` (or `r_prime`) 101 + spectrum to `weighted_rating`, so every band aligns index-by-index with the 102 + ISO 717-1 reference curve. 103 + 104 + ### `airborne_insulation()` parameters 105 + 106 + | Parameter | Type | Units | Range / default | Notes | 107 + | :--- | :--- | :--- | :--- | :--- | 108 + | `l1` | 1D or 2D array | dB | one/band, or `(positions, bands)` | Source-room levels (2D is energy-averaged) | 109 + | `l2` | 1D or 2D array | dB | same band count | Receiving-room levels | 110 + | `t2` | 1D array | s | > 0, one per band | Receiving-room reverberation time | 111 + | `area` | float, optional | m² | > 0, with `volume` | Partition area `S` (enables `R'`) | 112 + | `volume` | float, optional | m³ | > 0, with `area` | Receiving-room volume `V` | 113 + | `t0` | float | s | default `0.5` | Reference reverberation time `T0` | 114 + 115 + ### `weighted_rating()` parameters 116 + 117 + | Parameter | Type | Units | Range / default | Notes | 118 + | :--- | :--- | :--- | :--- | :--- | 119 + | `values_by_band` | 1D array | dB | 16 (thirds) or 5 (octaves) | Measured `R`, `R'`, `DnT` … per band | 120 + | `bands` | str or `None` | — | `'third-octave'` / `'octave'` / `None` | `None` infers from the count | 121 + 122 + `airborne_insulation()` returns an `AirborneInsulationResult` (`d`, `dnt`, 123 + `r_prime` or `None`); `weighted_rating()` returns a `WeightedRatingResult` 124 + (`rating`, `c`, `ctr`, `unfavourable_sum`, all integers except the sum). 125 + 126 + ### Impact sound (ISO 16283-2, ISO 717-2) 127 + 128 + Footstep noise is rated the other way round. Instead of how much a floor 129 + *blocks*, impact insulation measures how much a standardized **tapping 130 + machine** on the floor above puts into the room below — so a *higher* number 131 + is *worse*. The energy-average impact sound pressure level $L_i$ in the 132 + receiving room is normalised like the airborne case, but with a sign flip on 133 + the reverberation term: 134 + 135 + $$ 136 + L'_{nT} = L_i - 10 \log_{10} \frac{T}{T_0}, \qquad 137 + L'_n = L_i + 10 \log_{10} \frac{A}{A_0}, \quad 138 + A_0 = 10\ \text{m}^2,\ A = \frac{0.16\ V}{T}. 139 + $$ 140 + 141 + The **standardized** impact level $L'_{nT}$ ($T_0 = 0.5$ s for dwellings) 142 + needs only the receiving-room $T$, so with $T = 0.5$ s it equals $L_i$; the 143 + **normalized** level $L'_n$ (referenced to a 10 m² absorption area) also needs 144 + the receiving-room volume. Note the **minus** sign — more reverberation 145 + *lowers* $L'_{nT}$, opposite to the airborne $D_{nT}$. 146 + 147 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_impact_setup_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_impact_setup.svg" alt="Field impact insulation setup: a standardized tapping machine on the floor of the source room above, microphones energy-averaged in the receiving room below, and the receiving-room reverberation time" width="92%"></picture> 148 + 149 + The single-number rating (ISO 717-2) shifts the same style of reference curve, 150 + but an **unfavourable deviation now occurs where the measurement *exceeds* the 151 + reference** (impact noise is worse when higher) — the sign opposite to 152 + ISO 717-1. The rating (`Ln,w`, `L'n,w`, `L'nT,w`) is the shifted reference read 153 + at 500 Hz; for octave bands it is then reduced by 5 dB. The spectrum 154 + adaptation term $C_I = L_{n,\text{sum}} - 15 - L_{n,w}$ uses the energetic sum 155 + over 100–2500 Hz (16-band thirds excluding 3150 Hz) or 125–2000 Hz (octaves). 156 + 157 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/impact_rating_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/impact_rating.png" alt="Measured one-third-octave normalized impact sound pressure level with the shifted ISO 717-2 reference curve and the resulting weighted rating read at 500 Hz" width="80%"></picture> 158 + 159 + ```python 160 + import numpy as np 161 + from phonometry import impact_insulation, weighted_impact_rating 162 + 163 + # 16 one-third-octave impact levels Li (100 Hz - 3150 Hz), dB, from the 164 + # ISO 717-2 Annex C worked example, and the receiving-room T per band. 165 + li = np.array([62.1, 63.2, 63.5, 66.2, 68.5, 70.0, 71.7, 73.1, 166 + 73.8, 73.5, 73.8, 73.3, 73.1, 73.0, 72.4, 71.2]) 167 + t2 = np.full(16, 0.5) 168 + 169 + imp = impact_insulation(li, t2, volume=50.0) 170 + print(round(float(imp.l_n_t[0]), 1)) # 62.1 (= Li since T = T0) 171 + print(round(float(imp.l_n[0]), 1)) # 64.1 normalized to A0 = 10 m^2 172 + 173 + # Weighted impact rating + spectrum adaptation term CI (ISO 717-2) 174 + res_imp = weighted_impact_rating(imp.l_n_t) 175 + print(res_imp.rating, res_imp.ci, res_imp.unfavourable_sum) # 79 -11 28.0 -> L'nT,w(CI)=79(-11) 176 + 177 + # Octave-band data carry the extra -5 dB reduction (Clause 4.3.2) 178 + octave = np.array([65.3, 64.5, 58.0, 55.8, 43.0]) 179 + print(weighted_impact_rating(octave).rating) # 54 180 + 181 + res_imp.plot() # measured L'nT vs shifted ISO 717-2 reference, measured-above shaded (needs matplotlib) 182 + ``` 183 + 184 + <details> 185 + <summary>Show the code for this figure</summary> 186 + 187 + ```python 188 + import matplotlib.pyplot as plt 189 + 190 + # One line — measured L'nT vs the shifted ISO 717-2 reference (measured-above shaded): 191 + res_imp.plot() 192 + plt.show() 193 + 194 + # By hand, from the band curve the result now carries (note the opposite sign: 195 + # an unfavourable deviation is where the MEASURED level exceeds the reference). 196 + # Here the input was l_n_t, so the rated quantity is the field level L'nT,w: 197 + fig, ax = plt.subplots() 198 + ax.semilogx(res_imp.band_centers, res_imp.measured, "o-", label="Measured L'nT") 199 + ax.semilogx(res_imp.band_centers, res_imp.shifted_reference, "s--", label="Shifted reference") 200 + ax.fill_between(res_imp.band_centers, res_imp.shifted_reference, res_imp.measured, 201 + where=res_imp.measured > res_imp.shifted_reference, interpolate=True, 202 + alpha=0.3, label="Unfavourable deviations") 203 + ax.set_xlabel("Frequency [Hz]") 204 + ax.set_ylabel("Impact sound pressure level [dB]") 205 + ax.set_title(f"L'nT,w = {res_imp.rating} dB (CI={res_imp.ci:+d})") 206 + ax.legend() 207 + plt.show() 208 + ``` 209 + 210 + </details> 211 + 212 + Feed `impact_insulation`'s `l_n_t` (or `l_n`) straight into 213 + `weighted_impact_rating`; the rating and `CI` reproduce the ISO 717-2 Annex C 214 + values (thirds `L'nT,w = 79`, `CI = −11`; octave `54`, `CI = 0`). 215 + 216 + #### `impact_insulation()` parameters 217 + 218 + | Parameter | Type | Units | Range / default | Notes | 219 + | :--- | :--- | :--- | :--- | :--- | 220 + | `li` | 1D or 2D array | dB | one/band, or `(positions, bands)` | Energy-average impact SPL (2D is averaged over positions) | 221 + | `t2` | 1D array | s | > 0, one per band | Receiving-room reverberation time | 222 + | `volume` | float, optional | m³ | > 0 | Receiving-room `V` (enables `L'n`) | 223 + | `t0` | float | s | default `0.5` | Reference reverberation time `T0` | 224 + 225 + #### `weighted_impact_rating()` parameters 226 + 227 + | Parameter | Type | Units | Range / default | Notes | 228 + | :--- | :--- | :--- | :--- | :--- | 229 + | `values_by_band` | 1D array | dB | 16 (thirds) or 5 (octaves) | Measured `Ln`, `L'n` or `L'nT` per band | 230 + | `bands` | str or `None` | — | `'third-octave'` / `'octave'` / `None` | `None` infers from the count | 231 + 232 + `impact_insulation()` returns an `ImpactInsulationResult` (`l_n_t`, `l_n` or 233 + `None`); `weighted_impact_rating()` returns an `ImpactRatingResult` (`rating`, 234 + `ci` integers, `unfavourable_sum` in dB). 235 + 236 + ### Field façade insulation (ISO 16283-3) 237 + 238 + The same source/receiver logic reaches the building **façade**, but now the 239 + source is *outdoors* — a loudspeaker at 45° or the road traffic itself. Rather 240 + than a level difference across an internal partition, ISO 16283-3 references the 241 + receiving-room level $L_2$ to the level **2 m in front of the façade** 242 + $L_{1,2m}$, giving the level difference $D_{2m}$ and, exactly as in the airborne 243 + case, its standardized and normalized forms: 244 + 245 + $$ 246 + D_{2m} = L_{1,2m} - L_2, \quad 247 + D_{2m,nT} = D_{2m} + 10 \log_{10}\frac{T}{T_0}, \quad 248 + D_{2m,n} = D_{2m} - 10 \log_{10}\frac{A}{A_0}, 249 + $$ 250 + 251 + with $T_0 = 0.5$ s, $A_0 = 10$ m² and $A = 0.16\ V/T$ (dwellings). When the 252 + microphone sits **on the test element** (surface level $L_{1,s}$) the *element* 253 + method also yields an apparent sound reduction index, carrying a fixed 254 + angle-of-incidence correction — $-1.5$ dB for the 45° loudspeaker method, 255 + $-3$ dB for the all-angle road-traffic method: 256 + 257 + $$ 258 + R'_{45°} = L_{1,s} - L_2 + 10 \log_{10}\frac{S}{A} - 1.5, \qquad 259 + R'_{tr,s} = L_{1,s} - L_2 + 10 \log_{10}\frac{S}{A} - 3. 260 + $$ 261 + 262 + The façade quantity is airborne, so its single-number rating uses the 263 + **ISO 717-1** reference curve through `weighted_rating` unchanged (Annex F). 264 + 265 + ```python 266 + import numpy as np 267 + from phonometry import facade_insulation, weighted_rating 268 + 269 + # Outdoor level 2 m in front of the façade, receiving-room level and T per 270 + # one-third-octave band; surface_level is the microphone on the test element. 271 + l1_2m = np.full(16, 75.0) # L1,2m outdoors 272 + l2 = np.full(16, 33.0) # receiving-room L2 273 + t2 = np.full(16, 0.5) # receiving-room T (s) 274 + 275 + fac = facade_insulation(l1_2m, l2, t2, volume=50.0, area=11.5, 276 + surface_level=np.full(16, 78.0), method="loudspeaker") 277 + print(round(float(fac.d_2m[0]), 1)) # 42.0 D2m = L1,2m - L2 278 + print(round(float(fac.d_2m_nt[0]), 1)) # 42.0 (= D2m since T = T0) 279 + print(round(float(fac.d_2m_n[0]), 1)) # 40.0 normalized to A0 = 10 m^2 280 + print(round(float(fac.r_prime[0]), 1)) # 42.1 R'45deg (loudspeaker, -1.5 dB) 281 + 282 + # The road-traffic element method carries the -3 dB all-angle correction instead 283 + tr = facade_insulation(l1_2m, l2, t2, volume=50.0, area=11.5, 284 + surface_level=np.full(16, 78.0), method="road_traffic") 285 + print(round(float(tr.r_prime[0]), 1)) # 40.6 R'tr,s (traffic, -3 dB) 286 + 287 + # The façade quantity is airborne: rate D2m,nT with the ISO 717-1 engine 288 + print(weighted_rating(fac.d_2m_nt).rating) # 42 Dls,2m,nT,w 289 + 290 + fac.plot() # per-band D2m,nT with D2m, D2m,n and R' overlaid (needs matplotlib) 291 + ``` 292 + 293 + `surface_level`, `area` and `volume` are all optional: with only `l1_2m`, `l2` 294 + and `t2` the function returns `d_2m` and `d_2m_nt`; add `volume` for `d_2m_n`; 295 + add `surface_level` **and** `area` **and** `volume` for `r_prime`. Positions are 296 + energy-averaged with the surface-level formula (Clause 9.5.1); band levels are 297 + assumed already corrected for background noise. 298 + 299 + #### `facade_insulation()` parameters 300 + 301 + | Parameter | Type | Units | Range / default | Notes | 302 + | :--- | :--- | :--- | :--- | :--- | 303 + | `l1_2m` | 1D or 2D array | dB | one/band, or `(positions, bands)` | Level 2 m in front of the façade `L1,2m` | 304 + | `l2` | 1D or 2D array | dB | same band count | Receiving-room levels | 305 + | `t2` | 1D array | s | > 0, one per band | Receiving-room reverberation time | 306 + | `area` | float, optional | m² | > 0, with `surface_level`, `volume` | Test-element area `S` (enables `R'`) | 307 + | `volume` | float, optional | m³ | > 0 | Receiving-room `V` (enables `D2m,n`; required for `R'`) | 308 + | `surface_level` | 1D/2D array, optional | dB | same band count | Surface level `L1,s` on the element (enables `R'`) | 309 + | `method` | str | — | `'loudspeaker'` (−1.5 dB) / `'road_traffic'` (−3 dB) | Angle-of-incidence correction of `R'` | 310 + | `t0` | float | s | default `0.5` | Reference reverberation time `T0` | 311 + | `frequencies` | 1D array, optional | Hz | — | Band centres carried on the result for plotting | 312 + 313 + `facade_insulation()` returns a `FacadeInsulationResult` (`d_2m`, `d_2m_nt`, 314 + `d_2m_n` or `None`, `r_prime` or `None`, `frequencies`); feed any 16-band façade 315 + quantity to `weighted_rating` for its ISO 717-1 single number. 316 + 317 + ## 2. Laboratory measurement (ISO 10140) 318 + 319 + Everything above is a **field** measurement (the primed quantities $R'$, $L'_n$): 320 + the number a real building achieves, flanking transmission and all. To rate an 321 + element on its own — a wall type, a floating floor, a window — you take it to a 322 + qualified **laboratory** (ISO 10140), where suppressed flanking makes the 323 + *direct* transmission the whole story. The formulas lose their primes: the 324 + **sound reduction index** $R$ (not $R'$) and the **normalized impact level** 325 + $L_n$ (not $L'_n$), with the receiving room's absorption area $A = 0.16\ V/T$ 326 + now a known property of the facility: 327 + 328 + $$ 329 + R = L_1 - L_2 + 10 \log_{10}\frac{S}{A}, \qquad 330 + L_n = L_i + 10 \log_{10}\frac{A}{A_0}, \quad A_0 = 10\ \text{m}^2. 331 + $$ 332 + 333 + | | Field (ISO 16283) | Laboratory (ISO 10140) | 334 + | :--- | :--- | :--- | 335 + | Airborne | $R'$ apparent (with flanking) | $R$ direct (flanking suppressed) | 336 + | Impact | $L'_n$ apparent | $L_n$ direct | 337 + | Absorption area | measured in the room | property of the facility | 338 + 339 + The single-number ratings reuse the very same ISO 717-1/2 engines 340 + (`weighted_rating`, `weighted_impact_rating`) — an $R$ spectrum rates to $R_w$ 341 + exactly as an $R'$ spectrum rated to $R'_w$. Before forming the index the 342 + receiving-room levels must be **corrected for background noise** (Clause 4.3): 343 + the energy subtraction $10 \log_{10}(10^{L_{sb}/10} - 10^{L_b/10})$ applies for a 344 + 6–15 dB signal-to-background margin, a fixed 1.3 dB correction (the *limit of 345 + measurement*) at or below 6 dB, and no correction at or above 15 dB. 346 + 347 + ```python 348 + import numpy as np 349 + from phonometry import (lab_airborne_insulation, lab_impact_insulation, 350 + background_correction) 351 + 352 + # Source/receiving levels and receiving-room T over the 16 one-third-octave 353 + # bands; S is the free test-opening area, V the receiving-room volume. 354 + l1 = np.full(16, 80.0) 355 + l2 = np.full(16, 40.0) 356 + t2 = np.full(16, 0.5) 357 + lab = lab_airborne_insulation(l1, l2, t2, area=10.0, volume=50.0) 358 + print(round(float(lab.r[0]), 1)) # 38.0 R = L1 - L2 + 10 lg(S/A) 359 + print(round(float(lab.absorption[0]), 1)) # 16.0 A = 0.16 V / T (m^2) 360 + print(lab.rating.rating, lab.rating.c, lab.rating.ctr) # 38 0 0 -> Rw(C;Ctr) 361 + 362 + # Impact: the tapping-machine level Li normalized to A0 = 10 m^2 gives Ln 363 + li = np.array([62.1, 63.2, 63.5, 66.2, 68.5, 70.0, 71.7, 73.1, 364 + 73.8, 73.5, 73.8, 73.3, 73.1, 73.0, 72.4, 71.2]) 365 + imp = lab_impact_insulation(li, t2, volume=50.0) 366 + print(round(float(imp.l_n[0]), 1)) # 64.1 Ln = Li + 10 lg(A/A0) 367 + print(imp.rating.rating, imp.rating.ci) # 81 -11 -> Ln,w(CI) 368 + 369 + # Background correction: margins 6 / 1 / 20 dB -> capped / capped / unchanged 370 + corrected = background_correction([30.0, 33.0, 50.0], [24.0, 32.0, 30.0]) 371 + print(np.round(corrected, 1)) # [28.7 31.7 50.0] (1.3 dB cap twice) 372 + 373 + lab.rating.plot() # measured R vs shifted ISO 717-1 reference (needs matplotlib) 374 + ``` 375 + 376 + A margin at or below 6 dB emits a `LabInsulationWarning` and flags the band as 377 + the limit of measurement; catch it with `warnings.simplefilter("error", 378 + LabInsulationWarning)`. The automatic rating is formed only when exactly 16 379 + one-third-octave or 5 octave values are supplied (`rating` is `None` otherwise). 380 + 381 + ### `lab_airborne_insulation()` / `lab_impact_insulation()` parameters 382 + 383 + | Parameter | Type | Units | Range / default | Notes | 384 + | :--- | :--- | :--- | :--- | :--- | 385 + | `l1` / `l2` | 1D or 2D array | dB | one/band, or `(positions, bands)` | Source / receiving levels (airborne) | 386 + | `li` | 1D or 2D array | dB | one/band, or `(positions, bands)` | Impact SPL from the tapping machine (impact) | 387 + | `t2` | 1D array | s | > 0, one per band | Receiving-room reverberation time | 388 + | `area` | float | m² | > 0 | Free test-opening area `S` (airborne only) | 389 + | `volume` | float | m³ | > 0 | Receiving-room volume `V` | 390 + 391 + `lab_airborne_insulation()` returns a `LabAirborneInsulationResult` (`r`, 392 + `absorption`, `rating`); `lab_impact_insulation()` a 393 + `LabImpactInsulationResult` (`l_n`, `absorption`, `rating`); 394 + `background_correction(signal_and_background, background)` returns the corrected 395 + levels directly. 396 + 397 + ## 3. Predicting performance (EN 12354) 398 + 399 + A laboratory rating describes an element in isolation, yet the sound a building 400 + actually transmits also travels *around* the partition — along the floor, up the 401 + façade, through the flanking walls — re-radiating into the receiving room. This 402 + **flanking transmission** is the whole difference between the laboratory $R$ and 403 + the field $R'$. EN 12354 predicts the in-situ apparent rating from the 404 + laboratory ratings of the elements plus the vibration transmission of their 405 + junctions. 406 + 407 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_flanking_paths_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_flanking_paths.svg" alt="The direct path Dd through the separating element and the three flanking paths Ff, Df and Fd across each junction between a flanking element and the separating element" width="92%"></picture> 408 + 409 + Each junction between a flanking element and the separating element carries 410 + three paths — $Ff$ (flanking→flanking), $Df$ (direct→flanking) and $Fd$ 411 + (flanking→direct) — alongside the single direct path $Dd$. The **simplified 412 + single-number model** combines them energetically (Formula 26): 413 + 414 + $$ 415 + R'_w = -10 \log_{10}\Big[ 10^{-R_{Dd,w}/10} 416 + + \sum 10^{-R_{Ff,w}/10} + \sum 10^{-R_{Df,w}/10} 417 + + \sum 10^{-R_{Fd,w}/10} \Big], 418 + $$ 419 + 420 + with the direct path $R_{Dd,w} = R_{s,w} + \Delta R_{Dd,w}$ (Formula 27) and each 421 + flanking path (Formula 28a) 422 + 423 + $$ 424 + R_{ij,w} = \tfrac{R_{i,w} + R_{j,w}}{2} + \Delta R_{ij,w} + K_{ij} 425 + + 10 \log_{10}\frac{S_s}{l_0\ l_f}, 426 + $$ 427 + 428 + where $l_0 = 1$ m is the reference coupling length, $l_f$ the junction coupling 429 + length and $K_{ij}$ the junction's **vibration reduction index** (Annex E, 430 + empirical in the mass ratio $M = \log_{10}(m'_{\perp,i}/m'_i)$). 431 + 432 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/prediction_flanking_demo_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/prediction_flanking_demo.png" alt="Per-path sound reduction indices for the EN 12354-1 Annex H.3 example and each path's share of the transmitted energy, showing the direct path dominating at R'w = 52 dB" width="80%"></picture> 433 + 434 + ```python 435 + import numpy as np 436 + from phonometry import (junction_vibration_reduction, flanking_element, 437 + predicted_airborne_insulation) 438 + 439 + # EN 12354-1 Annex H.3: a separating wall Rs,w = 57 dB, area Ss = 11.5 m², with 440 + # four flanking elements. The simplified model reads each junction's Kij at 441 + # 500 Hz from the mass ratio m'perp / m' (Annex E) — here the floor's rigid 442 + # cross-junction (the mass ratio is itself rounded, hence 12.5 vs Annex 12.4): 443 + print(round(junction_vibration_reduction("rigid_cross", "through", 1.61), 1)) # 12.5 KFf 444 + print(round(junction_vibration_reduction("rigid_cross", "corner", 1.61), 1)) # 8.9 KFd = KDf 445 + 446 + # Build each element's three flanking paths (Ff, Df, Fd) from the Annex H 447 + # tabulated Kij, then combine the direct path Dd energetically (Formula 26). 448 + elements = [ # (name, Rw, KFf, KFd = KDf, coupling length lf) 449 + ("floor", 49, 12.4, 8.9, 4.50), 450 + ("ceiling", 46, 14.4, 9.2, 4.50), 451 + ("facade", 42, 12.6, 6.7, 2.55), 452 + ("int-wall", 33, 33.5, 15.7, 2.55), 453 + ] 454 + paths = [] 455 + for name, rw, k_ff, k_fd, lf in elements: 456 + paths += flanking_element(label=name, r_flanking=rw, r_separating=57, 457 + k_ff=k_ff, k_fd=k_fd, k_df=k_fd, 458 + separating_area=11.5, coupling_length=lf) 459 + 460 + res = predicted_airborne_insulation(r_direct=57.0, flanking_paths=paths) 461 + print(round(res.r_prime_w, 1)) # 52.2 -> R'w = 52 dB 462 + print(res.dominant.label, round(res.dominant.fraction, 2)) # Dd 0.33 (direct dominates) 463 + ``` 464 + 465 + <details> 466 + <summary>Show the code for this figure</summary> 467 + 468 + ```python 469 + import matplotlib.pyplot as plt 470 + 471 + # Per-path sound reduction index and each path's share of the transmitted 472 + # energy for the Annex H.3 result computed above. 473 + labels = [p.label for p in res.paths] 474 + r_w = [p.r_w for p in res.paths] 475 + frac = [100.0 * p.fraction for p in res.paths] 476 + 477 + fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6), sharex=True) 478 + ax1.bar(labels, r_w, color="tab:blue") 479 + ax1.axhline(res.r_prime_w, ls="--", color="k", label=f"R'w = {res.r_prime_w:.1f} dB") 480 + ax1.set_ylabel("Path Rij,w [dB]"); ax1.legend() 481 + ax2.bar(labels, frac, color="tab:orange") 482 + ax2.set_ylabel("Energy share [%]"); ax2.set_xlabel("Transmission path") 483 + for ax in (ax1, ax2): 484 + ax.tick_params(axis="x", rotation=45) 485 + fig.suptitle("EN 12354-1 Annex H.3 — flanking transmission") 486 + fig.tight_layout() 487 + plt.show() 488 + ``` 489 + 490 + </details> 491 + 492 + Every added flanking path strictly lowers $R'_w$ below the direct $R_{Dd,w} = 57$; 493 + `res.paths` exposes each path's share of the transmitted energy so the dominant 494 + path is visible. `flanking_element` is a convenience that builds one junction's 495 + three paths at once; the single-path constructor behind it, `flanking_path`, 496 + builds one `Ff`, `Df` or `Fd` path at a time (Formula 28a). Clause 4.4.2 also 497 + enforces a floor $K_{ij} \ge K_{ij,\min}$ from the junction geometry — compute 498 + it with `junction_min_vibration_reduction` and pass it to 499 + `flanking_path(..., kij_min=...)`, which raises a below-floor $K_{ij}$ to the 500 + minimum: 501 + 502 + ```python 503 + from phonometry import junction_min_vibration_reduction 504 + # Kij,min = 10 lg[lf·l0·(1/Si + 1/Sj)]; large elements give a low (here negative) 505 + # floor, so a realistic tabulated Kij is rarely clamped. 506 + print(round(junction_min_vibration_reduction(coupling_length=4.5, 507 + s_i=11.5, s_j=11.5), 1)) # -1.1 508 + ``` 509 + 510 + The impact counterpart (EN 12354-2, Formula 21) is a direct subtraction: 511 + $L'_{n,w} = L_{n,w,eq} - \Delta L_w + K$, with the bare-floor equivalent level 512 + $L_{n,w,eq} = 164 - 35 \log_{10}(m'/m'_0)$ (Annex B), the covering improvement 513 + $\Delta L_w$ (ISO 717-2) and the flanking correction $K$ from Table 1. 514 + 515 + ```python 516 + from phonometry import (equivalent_impact_level, impact_flanking_correction, 517 + predicted_impact_insulation, standardized_impact_level) 518 + 519 + # EN 12354-2 Annex E.3: a 0.14 m concrete floor (m' = 322 kg/m²) with a floating 520 + # floor (ΔLw = 33 dB), rooms one above the other, mean flanking mass 145 kg/m². 521 + ln_eq = equivalent_impact_level(322.0) # 164 - 35 lg(m') 522 + k = impact_flanking_correction(322.0, 145.0) # Table 1 (sep 322, flk 145) 523 + imp = predicted_impact_insulation(ln_w_eq=ln_eq, delta_l_w=33.0, k_correction=k) 524 + print(round(ln_eq, 1), k, round(imp.l_prime_n_w, 1)) # 76.2 2 45.2 -> L'n,w = 45 dB 525 + print(round(standardized_impact_level(imp.l_prime_n_w, 50.0), 1)) # 43.0 L'nT,w 526 + ``` 527 + 528 + ### `junction_vibration_reduction()` / `flanking_element()` parameters 529 + 530 + | Parameter | Type | Units | Range / default | Notes | 531 + | :--- | :--- | :--- | :--- | :--- | 532 + | `junction_type` | str | — | `'rigid_cross'` / `'rigid_t'` / `'flexible_t'` / `'lightweight_facade'` | Junction geometry (Annex E) | 533 + | `path` | str | — | `'through'` (K13) / `'corner'` (K12 = K23) | Path branch | 534 + | `mass_ratio` | float | — | > 0 | `m'⊥,i / m'i` (Formula E.2) | 535 + | `frequency` | float | Hz | default `500` | Only `flexible_t` is frequency-dependent | 536 + | `r_flanking` / `r_separating` | float | dB | — | Weighted indices of the flanking / separating element | 537 + | `k_ff` / `k_fd` / `k_df` | float | dB | — | Junction `Kij` for the three paths | 538 + | `separating_area` | float | m² | > 0 | Separating-element area `Ss` | 539 + | `coupling_length` | float | m | > 0 | Junction coupling length `lf` | 540 + | `delta_r_ff` / `delta_r_fd` / `delta_r_df` | float | dB | default `0` | Lining improvements per path | 541 + 542 + `predicted_airborne_insulation()` returns an `AirbornePredictionResult` 543 + (`r_prime_w`, `r_direct_w`, `paths` of `PathContribution`, `dominant`); 544 + `predicted_impact_insulation()` an `ImpactPredictionResult` (`l_prime_n_w`, 545 + `ln_w_eq`, `delta_l_w`, `k_correction`). The simplified model carries a reported 546 + standard deviation of about 2 dB (Clause 5). 547 + 548 + ## 4. Measurement uncertainty (ISO 12999-1) 549 + 550 + A rating without an uncertainty is only half a result. ISO 12999-1 does not 551 + re-measure anything; it tabulates the **standard uncertainty** $u$ of every 552 + sound-insulation quantity — derived from inter-laboratory tests — and prescribes 553 + how to expand and combine it. Which standard deviation is $u$ depends on the 554 + **measurement situation** (Clause 5.2): 555 + 556 + | Situation | Meaning | Standard uncertainty $u$ | 557 + | :--- | :--- | :--- | 558 + | **A** | laboratory characterisation (ISO 10140) | reproducibility $\sigma_R$ | 559 + | **B** | same location, different teams | in-situ $\sigma_{situ}$ | 560 + | **C** | same location, same operator repeated | repeatability $\sigma_r$ | 561 + 562 + The expanded uncertainty is $U = k\ u$ (Formula 2) with the coverage factor $k$ 563 + of Table 8. A two-sided interval $Y = y \pm U$ (Formula 3, $k = 1.96$ at 95 %) 564 + *reports* a value; the **one-sided** factor ($k = 1.65$ at 95 %) *declares 565 + conformity* with a requirement (Formulae 4/5). 566 + 567 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/insulation_uncertainty_demo_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/insulation_uncertainty_demo.png" alt="A weighted rating reported with its two-sided 95 % expanded uncertainty in situations A, B and C, the reproducibility uncertainty widest and the repeatability uncertainty narrowest" width="80%"></picture> 568 + 569 + ```python 570 + from phonometry import (band_uncertainty, single_number_uncertainty, 571 + uncertain_value, satisfies_lower_requirement) 572 + 573 + # Situation B (same building, different teams) -> the in-situ standard deviation. 574 + print(single_number_uncertainty("r_w", "B")) # 0.9 dB (Table 3) 575 + u = band_uncertainty("airborne", "B") # per-band u (Table 2) 576 + print(len(u.frequencies), u.uncertainties[10]) # 21 1.1 (the 500 Hz band) 577 + 578 + # Report R'w = 52 dB with a two-sided 95 % interval (k = 1.96, Table 8): 579 + uv = uncertain_value(52.0, "rprime_w", "B") # aliases resolve to r_w 580 + print(uv.coverage_factor, round(uv.expanded_uncertainty, 1)) # 1.96 1.8 581 + print(round(uv.lower, 1), round(uv.upper, 1)) # 50.2 53.8 -> 52 ± 1.8 dB 582 + 583 + # Declaring conformity uses the ONE-sided factor (k = 1.65): does R'w provably 584 + # clear a 50 dB requirement? 585 + uc = uncertain_value(52.0, "rprime_w", "B", one_sided=True) 586 + print(satisfies_lower_requirement(52.0, uc.expanded_uncertainty, 50.0)) # True 587 + ``` 588 + 589 + Impact quantities offer situations B/C only (Table 4, no 500 Hz band in the 2020 590 + edition), and $\Delta L$ only situation A. Descriptors are case-insensitive with 591 + aliases (`rprime_w`/`dnt_w`→`r_w`, `lprime_n_w`→`ln_w`); combine independent 592 + components in quadrature with `combine_uncertainties`, and reduce by $m$ 593 + independent measurements with `reduce_by_independent_measurements` ($u/\sqrt{m}$). 594 + 595 + <details> 596 + <summary>Show the code for this figure</summary> 597 + 598 + ```python 599 + import matplotlib.pyplot as plt 600 + from phonometry import uncertain_value 601 + 602 + # The same R'w = 52 dB reported in each situation with its two-sided 95 % U. 603 + situations = ["A", "B", "C"] 604 + vals = [uncertain_value(52.0, "r_w", s) for s in situations] 605 + 606 + fig, ax = plt.subplots(figsize=(7, 4)) 607 + ax.errorbar(situations, [v.value for v in vals], 608 + yerr=[v.expanded_uncertainty for v in vals], 609 + fmt="o", capsize=8, color="tab:blue") 610 + for s, v in zip(situations, vals): 611 + ax.annotate(f"±{v.expanded_uncertainty:.1f}", (s, v.upper), 612 + textcoords="offset points", xytext=(8, 4)) 613 + ax.set_ylabel("R'w [dB]"); ax.set_xlabel("Measurement situation") 614 + ax.set_title("R'w = 52 dB with 95 % expanded uncertainty (ISO 12999-1)") 615 + fig.tight_layout() 616 + plt.show() 617 + ``` 618 + 619 + </details> 620 + 621 + ### `band_uncertainty()` / `single_number_uncertainty()` / `uncertain_value()` parameters 622 + 623 + | Parameter | Type | Units | Range / default | Notes | 624 + | :--- | :--- | :--- | :--- | :--- | 625 + | `measurand` | str | — | `'airborne'` / `'impact'` / `'impact_reduction'` | Selects Table 2 / 4 / 6 | 626 + | `quantity` | str | — | `'r_w'`, `'ln_w'`, `'delta_lw'` (+ aliases, `+c`/`+ctr` variants) | Single-number descriptor | 627 + | `situation` | str | — | `'A'` / `'B'` / `'C'` | Measurement situation (Clause 5.2) | 628 + | `value` | float | dB | — | Best estimate `y` to attach `U` to | 629 + | `coverage` | float | — | default `0.95` | Confidence level (Table 8) | 630 + | `one_sided` | bool | — | default `False` | One-sided factor for conformity checks | 631 + | `upper_limit` | bool | — | default `False` | Select the σR95 upper limit (airborne, situation A) | 632 + 633 + `band_uncertainty()` returns a `BandUncertainty` (`frequencies`, 634 + `uncertainties`, `.to_arrays()`); `single_number_uncertainty()` a float; 635 + `uncertain_value()` an `UncertainValue` (`value`, `standard_uncertainty`, 636 + `coverage_factor`, `expanded_uncertainty`, `.lower`, `.upper`). The read-only 637 + `COVERAGE_FACTORS` mapping exposes Table 8 keyed by `(confidence, one_sided)`. 638 + 639 + --- 640 + 641 + **Standards.** ISO 16283-1:2014, ISO 16283-2 and ISO 16283-3:2016, *Acoustics — 642 + Field measurement of sound insulation in buildings and of building elements* — 643 + the level differences, normalisations and element methods of §1; ISO 717-1 and 644 + ISO 717-2 — the reference-curve single-number ratings and the spectrum 645 + adaptation terms C, Ctr and CI; ISO 10140-2:2010 and ISO 10140-4:2010 — the 646 + laboratory R and Ln with the background-noise correction of §2; EN 12354-1:2000 647 + and EN 12354-2:2000 — the simplified flanking-transmission predictions of §3 648 + (Annex E junctions, worked examples H.3 and E.3); ISO 12999-1:2020 — the 649 + standard uncertainties per measurement situation and the coverage factors 650 + of §4. 651 + 652 + ## See also 653 + 654 + - [Room Acoustics](room-acoustics.md) — the impulse response, 655 + room parameters and sound absorption that this guide's insulation chain builds on. 656 + - [Levels](levels.md) — energy averaging and the level metrics behind 657 + source/receiving-room levels. 658 + - [Filter Banks](filter-banks.md) — the IEC 61260 fractional-octave filters 659 + used for the insulation spectra. 660 + - [Sound Power](sound-power.md) — the `LW` methods that share the 661 + absorption-area machinery of the receiving room. 662 + - [Theory](theory.md) — the reference-curve derivation behind the 663 + weighted single-number ratings.
+2 -2
docs/enclosed-space-absorption.md
··· 7 7 objects — the design counterpart of the measured reverberation time. It is the 8 8 absorption member of the EN 12354 building-acoustics family (the airborne and 9 9 impact insulation members live in 10 - [Room and Building Acoustics](room-acoustics.md)). 10 + [Building Acoustics](building-acoustics.md)). 11 11 phonometry implements the normative Clause 4 model. (The informative Annex D 12 12 method for irregular spaces is out of scope.) 13 13 ··· 112 112 The `ReverberationResult` carries the per-band absorption area and reverberation 113 113 time, the volume and the object fraction, and its `.plot()` draws the 114 114 reverberation-time spectrum. This is the prediction counterpart of the measured 115 - reverberation time in [Room and Building Acoustics](room-acoustics.md) 115 + reverberation time in [Room Acoustics](room-acoustics.md) 116 116 (ISO 3382) and of the reverberation-room absorption of 117 117 [Acoustic Materials](materials.md) (ISO 354). 118 118
+11 -181
docs/levels.md
··· 176 176 | `sound_exposure(x, fs, duration_hours=None, ...)` | `duration_hours` treats `x` as a sample of that period | E [Pa²h] | IEC 61252 | 177 177 | `lex_8h(x, fs, duration_hours=None, ...)` | same sampling semantics | LEX,8h [dB] | IEC 61252 (≡ LEP,d) | 178 178 179 - ## Occupational noise exposure strategies and uncertainty (ISO 9612) 180 - 181 - `lex_8h` above turns *one* recording into a daily level. ISO 9612:2009 — the 182 - engineering method (accuracy grade 2) — is the survey design *around* that 183 - primitive: how to sample a real working day, how to combine the pieces, and how 184 - to attach the normative uncertainty every occupational-hygiene report needs. The 185 - `occupational_exposure` module adds the three **measurement strategies** and the 186 - **Annex C** uncertainty budget on top of the energy-average machinery. 187 - 188 - The *task-based* strategy (Clause 9) splits the nominal day into tasks, takes 189 - $I \ge 3$ samples per task, and energy-sums the task contributions 190 - 191 - $$ 192 - L_{EX,8h,m} = L_{p,A,eqT,m} + 10 \log_{10}(T_m/T_0), \qquad T_0 = 8\ \text{h}, 193 - $$ 194 - 195 - so a loud but short task contributes little. The *job-based* (Clause 10) and 196 - *full-day* (Clause 11) strategies instead take $N \ge 5$ (or three whole-day) 197 - random samples over a homogeneous exposure group and normalise the effective-day 198 - duration. The daily level is the same either way; the strategies differ in how 199 - the **uncertainty** is built. 200 - 201 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/exposure_uncertainty_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/exposure_uncertainty.png" alt="ISO 9612 Annex D task-based exposure: the three task LEX,8h contributions as bars, the energy-summed daily LEX,8h line and the one-sided 95 % upper limit LEX,8h + U band above it" width="80%"></picture> 202 - 203 - ```python 204 - from phonometry.occupational_exposure import ( 205 - Task, task_based_exposure, job_based_exposure, full_day_exposure, 206 - ) 207 - 208 - # ISO 9612 Annex D — a welder's day split into three tasks. Each task level is 209 - # the energy average of its Lp,A,eqT samples; durations carry a measured range. 210 - tasks = [ 211 - Task(samples=(70.0,), duration_hours=1.5, label="planning/breaks"), 212 - Task(samples=(80.1, 82.2, 79.6), duration_hours=5.0, 213 - duration_range=(4.0, 6.0), label="welding"), 214 - Task(samples=(86.5, 92.4, 89.3, 93.2, 87.8, 86.2), duration_hours=1.5, 215 - duration_range=(1.0, 2.0), label="cutting/grinding"), 216 - ] 217 - res = task_based_exposure(tasks, include_duration_uncertainty=False, warn=False) 218 - print(f"LEX,8h = {res.lex_8h:.1f} dB U = {res.expanded_uncertainty:.1f} dB") 219 - # LEX,8h = 84.3 dB U = 2.7 dB 220 - print(f"one-sided 95 % upper limit LEX,8h + U = {res.upper_limit:.1f} dB") # 87.0 dB 221 - for t in res.tasks: 222 - print(f" {t.label:<16} Lp,A,eqT = {t.lp_aeqt:5.1f} contributes {t.lex_8h_contribution:5.1f} dB") 223 - # planning/breaks Lp,A,eqT = 70.0 contributes 62.7 dB 224 - # welding Lp,A,eqT = 80.8 contributes 78.7 dB 225 - # cutting/grinding Lp,A,eqT = 90.1 contributes 82.8 dB 226 - 227 - # The same shift measured job-based (Annex E) and full-day (Annex F): both use 228 - # the Eq C.9 / Table C.4 sampling budget with k = 1.65 (one-sided 95 %). 229 - job = job_based_exposure([88.1, 86.1, 89.7, 86.5, 91.1, 86.7], effective_duration_hours=7.5) 230 - full = full_day_exposure([88.0, 91.9, 87.6, 90.4, 89.0, 88.4], effective_duration_hours=9.25) 231 - print(f"job LEX,8h = {job.lex_8h:.1f} dB U = {job.expanded_uncertainty:.1f} dB") 232 - # job LEX,8h = 88.2 dB U = 3.8 dB 233 - print(f"full-day LEX,8h = {full.lex_8h:.1f} dB U = {full.expanded_uncertainty:.1f} dB") 234 - # full-day LEX,8h = 90.1 dB U = 3.4 dB 235 - ``` 236 - 237 - Two subtleties are worth spelling out. First, the coverage factor is 238 - $k = 1.65$ for a **one-sided** 95 % interval (Clause 14), because a hygienist 239 - cares only about the *upper* bound: `res.upper_limit` = $L_{EX,8h} + U$ is the 240 - value 95 % of measurements fall below, the number compared against an action 241 - limit. Second, the task and job methods weight the *same* spread of samples 242 - differently. The task sampling uncertainty $u_{1a}$ (Eq. C.6) divides the summed 243 - squared deviations by $I(I-1)$ — the standard error of the mean, smaller by a 244 - factor $\sqrt{I}$ — whereas the job/full-day sampling uncertainty $u_1$ (Eq. C.12) 245 - is the plain sample standard deviation with denominator $N-1$, whose contribution 246 - $c_1 u_1$ is then read from **Table C.4** as a function of $(N, u_1)$. The same 247 - raw scatter therefore inflates the job estimate more, which is the standard's 248 - built-in penalty for coarser, fewer samples. (The printed job $L_{EX,8h}$ is 249 - $88.2$ dB where Annex E reports $88.1$: the standard rounds the effective-day 250 - level to $88.4$ before the duration normalisation; the library keeps it 251 - unrounded.) 252 - 253 - When a task's samples span **3 dB or more** (Clause 9.3), or the job contribution 254 - $c_1 u_1$ exceeds 3.5 dB (Clause 10.4), or too few workers are covered 255 - (Table 1 cumulative-duration), the result sets `sampling_advisory=True` and, with 256 - `warn=True`, emits an `OccupationalExposureWarning` recommending more measurements. Peak 257 - levels $L_{p,Cpeak}$ are reported **without** an uncertainty — Annex C gives no 258 - method for them (Table C.5, Note 1), so peak-uncertainty is out of scope. The 259 - three Annex D/E/F worked examples above are reproduced to the standard's printed 260 - precision (Annex E's final rounding is disclosed above), and the theory is 261 - derived on the [Theory](theory.md) page. 262 - 263 - ### `task_based_exposure()` / `job_based_exposure()` / `full_day_exposure()` parameters 264 - 265 - | Parameter | Applies to | Type | Units | Range / default | Notes | 266 - | :--- | :--- | :--- | :--- | :--- | :--- | 267 - | `tasks` | task | list of `Task` | — | ≥ 1 | Each `Task` has `samples`, `duration_hours`, optional `duration_range`/`duration_samples`, `label`, `instrument` | 268 - | `samples` | job / full-day | sequence | dB | ≥ 2 (≥ 5 / ≥ 3 advised) | Random `Lp,A,eqT` samples | 269 - | `effective_duration_hours` | job / full-day | float | h | > 0 | Effective working-day duration $T_e$ | 270 - | `instrument` | all | str | — | `'class1'`, `'class2'`, `'personal_exposimeter'` (default) | Selects $u_2$ (Table C.5) | 271 - | `u3` | all | float | dB | default `1.0` | Microphone-position uncertainty (Clause C.6) | 272 - | `include_duration_uncertainty` | task | bool | — | default `True` | `False` omits the $(c_{1b}u_{1b})^2$ term (Annex D case a) | 273 - | `n_workers` / `sample_duration_hours` | job | int / float | — / h | default `None` | Table 1 cumulative-duration check | 274 - | `warn` | all | bool | — | default `True` | Emit `OccupationalExposureWarning` for the sampling advisories | 275 - 276 - All three return an `ExposureResult` with `lex_8h`, `combined_standard_uncertainty` 277 - $u$, `expanded_uncertainty` $U = 1.65\ u$, `upper_limit` = $L_{EX,8h} + U$, 278 - `sampling_advisory`, and (task-based) the per-task `tasks` breakdown. 279 - 280 - ## Loudness level of pure tones (ISO 226:2023) 281 - 282 - The normal equal-loudness-level contours relate the SPL of a pure tone to its 283 - perceived *loudness level* in phons (the SPL of an equally loud 1 kHz tone). 284 - `equal_loudness_contour(phon)` evaluates ISO 226:2023 Formula (1) at the 29 285 - preferred third-octave frequencies of Table 1, `loudness_level(spl, frequency)` 286 - is the exact inverse (Formula 2), and `hearing_threshold()` returns the 287 - threshold-of-hearing column: 288 - 289 - ```python 290 - from phonometry import equal_loudness_contour, loudness_level 291 - 292 - freqs, spl = equal_loudness_contour(40.0) # the classic 40-phon contour 293 - phon = loudness_level(73.0, 63.0) # 73 dB @ 63 Hz -> 40 phon 294 - ``` 295 - 296 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours.png" alt="ISO 226:2023 normal equal-loudness-level contours from 20 to 90 phon with the hearing threshold curve" width="80%"></picture> 297 - 298 - Validity per clause 4.1: 20-90 phon (80 phon above 4 kHz); the implementation 299 - is verified against the Annex B tables in CI. Note this is the loudness of 300 - *pure tones* - loudness of arbitrary signals (ISO 532 sones) is a different, 301 - upcoming feature. 302 - 303 - ## Prominent discrete tones (ECMA-418-1) 304 - 305 - Tonal components in machinery noise are far more annoying than their level 306 - suggests. ECMA-418-1:2024 (referenced by ECMA-74 Annex D) gives two FFT-based 307 - methods to decide whether a discrete tone is *prominent*: 308 - `tone_to_noise_ratio()` compares the tone level with the masking noise in its 309 - critical band (clause 11), and `prominence_ratio()` compares the critical band 310 - centred on the tone with the two contiguous bands (clause 12). Both return a 311 - structured verdict against the frequency-dependent prominence criteria: 312 - 313 - ```python 314 - import numpy as np 315 - from phonometry import tone_to_noise_ratio, prominence_ratio 316 - 317 - fs = 48000 318 - rng = np.random.default_rng(0) 319 - t = np.arange(fs) / fs 320 - x = np.sin(2 * np.pi * 1000 * t) + 0.05 * rng.standard_normal(fs) # 1 kHz tone in noise 321 - tnr = tone_to_noise_ratio(x, fs) # highest peak, or tone_freq=... 322 - pr = prominence_ratio(x, fs, tone_freq=1000.0) 323 - print(tnr.ratio_db, tnr.criterion_db, tnr.prominent) 324 - ``` 325 - 326 - 327 - The methods hinge on the **critical band** — the ear's analysis bandwidth, 328 - $\Delta f_c = 25 + 75\ [1 + 1.4(f/1000)^2]^{0.69}$ Hz (162 Hz at 1 kHz): a 329 - tone is masked only by the noise *inside* its critical band, so both ratios 330 - compare the tone against exactly that noise, not the whole spectrum. 331 - 332 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tonality_spectrum_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tonality_spectrum.png" alt="Averaged spectrum of a tone in noise with the critical band shaded and the tone-to-noise ratio annotated against its prominence criterion" width="80%"></picture> 333 - 334 - A TNR above $8 + 8.33\log_{10}(1000/f_t)$ dB (8 dB from 1 kHz up) classifies 335 - the tone as *prominent*; the PR criterion is $9 + 10\log_{10}(1000/f_t)$ dB. 336 - Low frequencies get higher thresholds because wider relative bands mask more. 337 - 338 - ECMA-74 (which delegates its tone assessments to ECMA-418-1) also fixes where to measure around a device: 339 - 340 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions.svg" alt="ECMA-74 emission measurement positions: seated operator microphone at 0.25 m and 1.20 m, and the four bystander positions at 1 m" width="92%"></picture> 341 - 342 - Proximate secondary tones in the same critical band are combined per 343 - clause 11.6; for harmonic complexes assess each component (`tone_freq=`). 344 - Both methods work on Hann-windowed, RMS-averaged spectra and need no absolute 345 - calibration (the ratios are level differences). 346 - 347 - ### `tone_to_noise_ratio()` / `prominence_ratio()` parameters 348 - 349 - | Parameter | Type | Units | Range / default | Notes | 350 - | :--- | :--- | :--- | :--- | :--- | 351 - | `x` | 1D array | any (uncalibrated OK) | ≥ `fs/resolution_hz` samples | Ratios are level differences: calibration cancels out | 352 - | `fs` | int | Hz | > 0 | | 353 - | `tone_freq` | float, optional | Hz | 89.1–11 200; default `None` | `None` assesses the highest peak in the range of interest | 354 - | `resolution_hz` | float | Hz | > 0; default `1.0` | Tone band must stay within 15 % of the critical band (clause 11.2) | 355 - 356 - Both return a `ToneAssessment(frequency, ratio_db, criterion_db, prominent)`. 179 + `lex_8h` rates *one* recording; assembling a full working day from task or 180 + job samples — with the normative ISO 9612 uncertainty budget — continues in 181 + [Occupational Noise Exposure](occupational-exposure.md). 357 182 358 183 ## Environmental noise: Lden, Ldn and rating levels (ISO 1996-1) 359 184 ··· 375 200 (51.4, 8, 10.0)]) # night (+10) == lden 376 201 ``` 377 202 378 - 379 203 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/lden_profile_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/lden_profile.png" alt="Synthetic 24-hour urban LAeq profile with day, evening and night bands, the +5 and +10 dB weighted period levels and the resulting Lden" width="80%"></picture> 380 204 381 205 ### `lden()` / `ldn()` / `composite_rating_level()` parameters ··· 391 215 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_env_measurement_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_env_measurement.svg" alt="Environmental noise measurement positions per ISO 1996-2: free field, 2 m from the facade and flush-mounted, with their corrections" width="92%"></picture> 392 216 393 217 Combine with `laeq()` per time period to go from recordings to Lden, and with 394 - `tone_to_noise_ratio()` / `prominence_ratio()` to justify tonal adjustments. 218 + the `tone_to_noise_ratio()` / `prominence_ratio()` verdicts of 219 + [Prominent Discrete Tones](tone-prominence.md) to justify tonal adjustments. 395 220 396 221 ## Octave Spectrogram (levels over time) 397 222 ··· 441 266 ``` 442 267 443 268 See [Calibration and dBFS](calibration.md) to convert digital units to physical 444 - SPL, and [Time Weighting](time-weighting.md) for the envelope details. 269 + SPL, and [Time Weighting](time-weighting.md) for the envelope details. The 270 + ISO 9612 occupational strategies continue in 271 + [Occupational Noise Exposure](occupational-exposure.md), the ECMA-418-1 272 + tonal-prominence verdicts in [Prominent Discrete Tones](tone-prominence.md), 273 + and the ISO 226 equal-loudness contours live with the perception metrics in 274 + [Psychoacoustics](psychoacoustics.md).
+127
docs/occupational-exposure.md
··· 1 + ← [Documentation index](README.md) 2 + 3 + # Occupational Noise Exposure (ISO 9612) 4 + 5 + A working day is rarely measured in one take: the daily exposure level a 6 + regulation acts on has to be assembled from *samples* of a real shift, and 7 + reported with an uncertainty a hygienist can defend. `lex_8h` (in 8 + [Levels](levels.md)) turns *one* recording into a daily level. ISO 9612:2009 — 9 + the engineering method (accuracy grade 2) — is the survey design *around* that 10 + primitive: how to sample a real working day, how to combine the pieces, and how 11 + to attach the normative uncertainty every occupational-hygiene report needs. The 12 + `occupational_exposure` module adds the three **measurement strategies** and the 13 + **Annex C** uncertainty budget on top of the energy-average machinery. 14 + 15 + ## 1. The three measurement strategies (Clauses 9-11) 16 + 17 + The *task-based* strategy (Clause 9) splits the nominal day into tasks, takes 18 + $I \ge 3$ samples per task, and energy-sums the task contributions 19 + 20 + $$ 21 + L_{EX,8h,m} = L_{p,A,eqT,m} + 10 \log_{10}(T_m/T_0), \qquad T_0 = 8\ \text{h}, 22 + $$ 23 + 24 + so a loud but short task contributes little. The *job-based* (Clause 10) and 25 + *full-day* (Clause 11) strategies instead take $N \ge 5$ (or three whole-day) 26 + random samples over a homogeneous exposure group and normalise the effective-day 27 + duration. The daily level is the same either way; the strategies differ in how 28 + the **uncertainty** is built. 29 + 30 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/exposure_uncertainty_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/exposure_uncertainty.png" alt="ISO 9612 Annex D task-based exposure: the three task LEX,8h contributions as bars, the energy-summed daily LEX,8h line and the one-sided 95 % upper limit LEX,8h + U band above it" width="80%"></picture> 31 + 32 + ```python 33 + from phonometry.occupational_exposure import ( 34 + Task, task_based_exposure, job_based_exposure, full_day_exposure, 35 + ) 36 + 37 + # ISO 9612 Annex D — a welder's day split into three tasks. Each task level is 38 + # the energy average of its Lp,A,eqT samples; durations carry a measured range. 39 + tasks = [ 40 + Task(samples=(70.0,), duration_hours=1.5, label="planning/breaks"), 41 + Task(samples=(80.1, 82.2, 79.6), duration_hours=5.0, 42 + duration_range=(4.0, 6.0), label="welding"), 43 + Task(samples=(86.5, 92.4, 89.3, 93.2, 87.8, 86.2), duration_hours=1.5, 44 + duration_range=(1.0, 2.0), label="cutting/grinding"), 45 + ] 46 + res = task_based_exposure(tasks, include_duration_uncertainty=False, warn=False) 47 + print(f"LEX,8h = {res.lex_8h:.1f} dB U = {res.expanded_uncertainty:.1f} dB") 48 + # LEX,8h = 84.3 dB U = 2.7 dB 49 + print(f"one-sided 95 % upper limit LEX,8h + U = {res.upper_limit:.1f} dB") # 87.0 dB 50 + for t in res.tasks: 51 + print(f" {t.label:<16} Lp,A,eqT = {t.lp_aeqt:5.1f} contributes {t.lex_8h_contribution:5.1f} dB") 52 + # planning/breaks Lp,A,eqT = 70.0 contributes 62.7 dB 53 + # welding Lp,A,eqT = 80.8 contributes 78.7 dB 54 + # cutting/grinding Lp,A,eqT = 90.1 contributes 82.8 dB 55 + 56 + # The same shift measured job-based (Annex E) and full-day (Annex F): both use 57 + # the Eq C.9 / Table C.4 sampling budget with k = 1.65 (one-sided 95 %). 58 + job = job_based_exposure([88.1, 86.1, 89.7, 86.5, 91.1, 86.7], effective_duration_hours=7.5) 59 + full = full_day_exposure([88.0, 91.9, 87.6, 90.4, 89.0, 88.4], effective_duration_hours=9.25) 60 + print(f"job LEX,8h = {job.lex_8h:.1f} dB U = {job.expanded_uncertainty:.1f} dB") 61 + # job LEX,8h = 88.2 dB U = 3.8 dB 62 + print(f"full-day LEX,8h = {full.lex_8h:.1f} dB U = {full.expanded_uncertainty:.1f} dB") 63 + # full-day LEX,8h = 90.1 dB U = 3.4 dB 64 + ``` 65 + 66 + ## 2. The Annex C uncertainty budget 67 + 68 + Two subtleties are worth spelling out. First, the coverage factor is 69 + $k = 1.65$ for a **one-sided** 95 % interval (Clause 14), because a hygienist 70 + cares only about the *upper* bound: `res.upper_limit` = $L_{EX,8h} + U$ is the 71 + value 95 % of measurements fall below, the number compared against an action 72 + limit. Second, the task and job methods weight the *same* spread of samples 73 + differently. The task sampling uncertainty $u_{1a}$ (Eq. C.6) divides the summed 74 + squared deviations by $I(I-1)$ — the standard error of the mean, smaller by a 75 + factor $\sqrt{I}$ — whereas the job/full-day sampling uncertainty $u_1$ (Eq. C.12) 76 + is the plain sample standard deviation with denominator $N-1$, whose contribution 77 + $c_1 u_1$ is then read from **Table C.4** as a function of $(N, u_1)$. The same 78 + raw scatter therefore inflates the job estimate more, which is the standard's 79 + built-in penalty for coarser, fewer samples. (The printed job $L_{EX,8h}$ is 80 + $88.2$ dB where Annex E reports $88.1$: the standard rounds the effective-day 81 + level to $88.4$ before the duration normalisation; the library keeps it 82 + unrounded.) 83 + 84 + When a task's samples span **3 dB or more** (Clause 9.3), or the job contribution 85 + $c_1 u_1$ exceeds 3.5 dB (Clause 10.4), or too few workers are covered 86 + (Table 1 cumulative-duration), the result sets `sampling_advisory=True` and, with 87 + `warn=True`, emits an `OccupationalExposureWarning` recommending more measurements. Peak 88 + levels $L_{p,Cpeak}$ are reported **without** an uncertainty — Annex C gives no 89 + method for them (Table C.5, Note 1), so peak-uncertainty is out of scope. The 90 + three Annex D/E/F worked examples above are reproduced to the standard's printed 91 + precision (Annex E's final rounding is disclosed above), and the theory is 92 + derived on the [Theory](theory.md) page. 93 + 94 + ### `task_based_exposure()` / `job_based_exposure()` / `full_day_exposure()` parameters 95 + 96 + | Parameter | Applies to | Type | Units | Range / default | Notes | 97 + | :--- | :--- | :--- | :--- | :--- | :--- | 98 + | `tasks` | task | list of `Task` | — | ≥ 1 | Each `Task` has `samples`, `duration_hours`, optional `duration_range`/`duration_samples`, `label`, `instrument` | 99 + | `samples` | job / full-day | sequence | dB | ≥ 2 (≥ 5 / ≥ 3 advised) | Random `Lp,A,eqT` samples | 100 + | `effective_duration_hours` | job / full-day | float | h | > 0 | Effective working-day duration $T_e$ | 101 + | `instrument` | all | str | — | `'class1'`, `'class2'`, `'personal_exposimeter'` (default) | Selects $u_2$ (Table C.5) | 102 + | `u3` | all | float | dB | default `1.0` | Microphone-position uncertainty (Clause C.6) | 103 + | `include_duration_uncertainty` | task | bool | — | default `True` | `False` omits the $(c_{1b}u_{1b})^2$ term (Annex D case a) | 104 + | `n_workers` / `sample_duration_hours` | job | int / float | — / h | default `None` | Table 1 cumulative-duration check | 105 + | `warn` | all | bool | — | default `True` | Emit `OccupationalExposureWarning` for the sampling advisories | 106 + 107 + All three return an `ExposureResult` with `lex_8h`, `combined_standard_uncertainty` 108 + $u$, `expanded_uncertainty` $U = 1.65\ u$, `upper_limit` = $L_{EX,8h} + U$, 109 + `sampling_advisory`, and (task-based) the per-task `tasks` breakdown. 110 + 111 + ## See also 112 + 113 + - [Levels](levels.md) — the `lex_8h` / `sound_exposure` dose primitives 114 + (IEC 61252) and the LCpeak these strategies report alongside. 115 + - [Measurement uncertainty](gum-uncertainty.md) — the GUM machinery behind 116 + combined and expanded uncertainties. 117 + - [Theory](theory.md) — the derivation of the strategy formulas and the 118 + Annex C budget. 119 + 120 + --- 121 + 122 + **Standards.** ISO 9612:2009, *Acoustics — Determination of occupational noise 123 + exposure — Engineering method* — the task-based (Clause 9), job-based 124 + (Clause 10) and full-day (Clause 11) strategies, the Annex C uncertainty budget 125 + (Formulae C.6, C.9 and C.12, Tables C.4/C.5) and the one-sided coverage factor 126 + k = 1.65 (Clause 14), validated against the worked examples of Annexes D, E 127 + and F.
+2 -2
docs/outdoor-propagation.md
··· 247 247 248 248 The method's stated accuracy is $\pm 1$ to $\pm 3$ dB for broadband noise up to 249 249 1000 m (Table 5). See the [Theory](theory.md) page for the full derivation, the 250 - [Room and Building Acoustics guide](room-acoustics.md) for how $\alpha$ feeds 251 - ISO 354, and the [Levels guide](levels.md) for the ISO 9612 occupational 250 + [Room Acoustics guide](room-acoustics.md) for how $\alpha$ feeds 251 + ISO 354, and the [Occupational Noise Exposure guide](occupational-exposure.md) for the ISO 9612 occupational 252 252 exposure that consumes A-weighted levels.
+27 -89
docs/psychoacoustics.md
··· 1 1 ← [Documentation index](README.md) 2 2 3 - # Psychoacoustics and Speech Intelligibility 3 + # Psychoacoustics 4 4 5 5 Level metrics tell you how much *sound pressure* there is; psychoacoustic 6 6 metrics tell you what a listener actually *perceives*. This page covers 7 - loudness (ISO 532-1), sharpness (DIN 45692) and the speech transmission 8 - index (IEC 60268-16), then the advanced Moore-Glasberg (ISO 532-2/3) and 9 - Sottek Hearing Model (ECMA-418-2) loudness, tonality and roughness models. 7 + loudness (ISO 532-1), sharpness (DIN 45692) and the equal-loudness 8 + contours of pure tones (ISO 226), then the advanced Moore-Glasberg 9 + (ISO 532-2/3) and Sottek Hearing Model (ECMA-418-2) loudness, tonality and 10 + roughness models. Speech metrics live in their own guides: the 11 + transmission-channel STI/STIPA in 12 + [Speech Transmission Index](speech-transmission.md) and the 13 + audibility-based SII in 14 + [Speech Intelligibility Index](speech-intelligibility.md). 10 15 11 16 ## Loudness in sones (ISO 532-1, Zwicker) 12 17 ··· 126 131 CI verifies the Table A.2 target values (0.38 acum at 250 Hz up to 127 132 2.82 acum at 4 kHz) within the standard's 5 % / 0.05 acum tolerance. 128 133 129 - ## Speech Transmission Index (IEC 60268-16) 134 + ## Loudness level of pure tones (ISO 226:2023) 130 135 131 - Reverberation and noise do not muffle speech uniformly — they blur its 132 - *envelope*: the slow (0.63–12.5 Hz) intensity modulations that carry 133 - syllables. STI quantifies how much of that modulation survives from mouth 134 - to ear, per octave band, as the **modulation transfer function** m(F). A 135 - delta-like channel keeps m = 1 (STI = 1); reverberation low-passes the 136 - envelope following Schroeder's closed form, and steady noise scales it: 137 - 138 - $$ 139 - m(F) = \frac{1}{\sqrt{1 + \left(2\pi F\ \frac{T_{60}}{13.8}\right)^2}} 140 - \cdot \frac{1}{1 + 10^{-\mathrm{SNR}/10}} 141 - $$ 142 - 143 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60.png" alt="STI versus reverberation time with the IEC 60268-16 Annex F rating bands shaded" width="80%"></picture> 144 - 145 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain.svg" alt="STI measurement chain: STIPA source signal through the room to the microphone and the MTF analysis" width="92%"></picture> 136 + The normal equal-loudness-level contours relate the SPL of a pure tone to its 137 + perceived *loudness level* in phons (the SPL of an equally loud 1 kHz tone). 138 + `equal_loudness_contour(phon)` evaluates ISO 226:2023 Formula (1) at the 29 139 + preferred third-octave frequencies of Table 1, `loudness_level(spl, frequency)` 140 + is the exact inverse (Formula 2), and `hearing_threshold()` returns the 141 + threshold-of-hearing column: 146 142 147 143 ```python 148 - import numpy as np 149 - from phonometry import sti_from_impulse_response, stipa, stipa_signal 150 - 151 - fs = 48000 152 - # A measured room impulse response (synthesized decay so the example runs) 153 - ir = np.random.default_rng(0).standard_normal(fs) * np.exp(-6.9 * np.arange(fs) / fs / 0.5) 154 - 155 - # Indirect method: from a measured room impulse response 156 - res = sti_from_impulse_response(ir, fs, snr=25.0) 157 - print(f"STI = {res.sti:.2f} ({res.rating})") # e.g. 0.62 (D) 158 - 159 - # Direct STIPA measurement: play stipa_signal() in the room, record it 160 - test = stipa_signal(fs, seconds=18.0, level_db=80.0) 161 - recording = test # in practice, the microphone signal after playback 162 - res = stipa(recording, fs) 163 - res.plot() # per-band modulation transfer index (MTI) bars, STI + rating in the title 164 - ``` 165 - 166 - <details> 167 - <summary>Show the code for this figure</summary> 168 - 169 - ```python 170 - import matplotlib.pyplot as plt 171 - 172 - # STI vs reverberation time: sweep sti_from_impulse_response over synthetic 173 - # exponential decays (white noise x exp(-6.9077 t / T60)) at a T60 grid — 174 - # exactly the physics behind the curve above: 175 - rng = np.random.default_rng(0) 176 - t60_grid = np.array([0.3, 0.5, 0.8, 1.2, 1.6, 2.0, 2.5, 3.0, 4.0, 5.0]) 177 - sti_values = [] 178 - for t60 in t60_grid: 179 - t = np.arange(int(2 * t60 * fs)) / fs 180 - ir = rng.standard_normal(t.size) * np.exp(-6.9077 * t / t60) 181 - sti_values.append(sti_from_impulse_response(ir, fs).sti) 144 + from phonometry import equal_loudness_contour, loudness_level 182 145 183 - fig, ax = plt.subplots() 184 - ax.semilogx(t60_grid, sti_values, "o-") 185 - ax.set_xlabel("Reverberation time T60 [s]") 186 - ax.set_ylabel("STI") 187 - ax.set_ylim(0.0, 1.0) 188 - ax.grid(True, which="both", alpha=0.3) 189 - plt.show() 146 + freqs, spl = equal_loudness_contour(40.0) # the classic 40-phon contour 147 + phon = loudness_level(73.0, 63.0) # 73 dB @ 63 Hz -> 40 phon 190 148 ``` 191 149 192 - </details> 150 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours.png" alt="ISO 226:2023 normal equal-loudness-level contours from 20 to 90 phon with the hearing threshold curve" width="80%"></picture> 193 151 194 - `stipa` emits a `UserWarning` when the recording is shorter than the 195 - recommended 15 s (IEC 60268-16 STIPA practice, 15 s to 25 s): below that the 196 - slow modulation components are averaged over too few periods and the STI is 197 - biased low (an ideal loopback gives STI ≈ 0.944 at 5 s vs ≈ 0.998 at 18 s). 198 - 199 - The implementation follows **Edition 5 (2020)**: Edition 4's normative PDF 200 - is the base and every Ed. 5 change is source-attributed in the code — the 201 - only numeric delta is the revised male speech spectrum of clause A.6.1. 202 - CI checks the standard's own verification vectors: the six weighting-factor 203 - band pairs to ±0.001 STI, the m ↔ STI mapping table, the level-dependent 204 - masking control points, and Schroeder-form decays at four T₆₀ values. 205 - 206 - ### `sti_from_impulse_response()` / `stipa()` parameters 207 - 208 - | Parameter | Type | Units | Range / default | Notes | 209 - | :--- | :--- | :--- | :--- | :--- | 210 - | `ir` / `x` | 1D array | any / Pa | non-empty | IR (indirect) or STIPA recording (direct) | 211 - | `fs` | int | Hz | > 0 | | 212 - | `snr` | float or 7-vector, optional | dB | default `None` | Adds steady-noise degradation | 213 - | `level` | 7-vector, optional | dB SPL | default `None` | Enables auditory masking + reception threshold (Tables A.2/A.3) | 214 - | `ambient` | 7-vector, optional | dB SPL | needs `level` | Ambient noise band levels | 215 - | `reference` | 1D array, optional (`stipa`) | — | default `None` | Measured source signal instead of the nominal m = 0.55 | 216 - 217 - Both return `STIResult`: `sti`, `mti` (7 bands), `mtf` (7×14 or 7×2), 218 - `band_levels`, `rating` (Annex F letter `A+`…`U`). 152 + Validity per clause 4.1: 20-90 phon (80 phon above 4 kHz); the implementation 153 + is verified against the Annex B tables in CI. Note this is the loudness of 154 + *pure tones* — the loudness of arbitrary signals in sones is what the ISO 532 155 + models on this page compute. 219 156 220 157 ## Advanced loudness & sound-quality models 221 158 ··· 552 489 `time`, `roughness_vs_time` (R(l50)), `specific_roughness_vs_time` 553 490 ((n_times, 53) array), `field`. 554 491 555 - See [Levels](levels.md) for tonality metrics and [Theory](theory.md) for the 556 - underlying math. 492 + See [Prominent Discrete Tones](tone-prominence.md) for the ECMA-418-1 TNR/PR 493 + prominence verdicts, [Speech Transmission Index](speech-transmission.md) for 494 + STI/STIPA, and [Theory](theory.md) for the underlying math.
+37 -656
docs/room-acoustics.md
··· 1 1 ← [Documentation index](README.md) 2 2 3 - # Room and Building Acoustics 3 + # Room Acoustics 4 4 5 - Room and building acoustics start from one measurement: the **impulse 6 - response** (IR) between a source and a receiver. Filter it into bands and 7 - integrate it, and it yields reverberation time, clarity and speech 8 - intelligibility; measure it either side of a wall and it yields the sound 9 - insulation of the partition. This page follows that chain in measurement 10 - order — acquiring the IR (ISO 18233), turning it into room parameters 11 - (ISO 3382-1/2), spatial speech metrics for open-plan offices 12 - (ISO 3382-3), field airborne, impact and façade insulation with 13 - single-number ratings (ISO 16283-1/2/3, ISO 717-1/2), the laboratory 14 - characterisation of a building element (ISO 10140), the prediction of 15 - in-situ performance from flanking transmission (EN 12354-1/2), the 16 - measurement uncertainty that qualifies every rating (ISO 12999-1) and, 17 - closing the loop, the sound absorption of a material in a reverberation 18 - room (ISO 354). 5 + Room acoustics starts from one measurement: the **impulse response** (IR) 6 + between a source and a receiver. Filter it into bands and integrate it, and 7 + it yields reverberation time, clarity and speech intelligibility — everything 8 + about the sound field inside a single room. This page follows that chain in 9 + measurement order — acquiring the IR (ISO 18233), turning it into room 10 + parameters (ISO 3382-1/2), spatial speech metrics for open-plan offices 11 + (ISO 3382-3) and, closing the loop, the sound absorption of a material in a 12 + reverberation room (ISO 354). For sound insulation *between* spaces — the same 13 + IR measured either side of a partition — see the companion 14 + [Building Acoustics & Sound Insulation guide](building-acoustics.md). 19 15 20 16 ## 1. Impulse-response acquisition (ISO 18233) 21 17 ··· 92 88 `ImpulseResponseResult` — a drop-in for the raw IR array (`np.asarray(ir)`, 93 89 indexing and `ir.size` all keep working, so `room_parameters(ir, fs)` is 94 90 unchanged) that also carries the sample rate and method and adds an `.plot()`. 95 - The exponential sweep sweeps its energy up the spectrum over the whole signal, 96 - while the MLS is a flat-spectrum two-level sequence. 91 + 92 + **The two excitations.** The exponential sweep sweeps its energy up the 93 + spectrum over the whole signal, while the MLS is a flat-spectrum two-level 94 + sequence — visible as the near-constant magnitude on the right. 97 95 98 96 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/excitation_signals_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/excitation_signals.png" alt="ISO 18233 excitation signals: the exponential sine sweep waveform and its spectrogram showing the exponential frequency rise, and a maximum-length sequence with its flat magnitude spectrum" width="96%"></picture> 99 97 ··· 126 124 127 125 </details> 128 126 129 - Deconvolving the recording gives the broadband IR — the direct sound, 130 - discrete early reflections and the decaying diffuse tail. Its `.plot()` shows 131 - the waveform above and the log-magnitude envelope with the Schroeder 132 - energy-decay curve below, whose straight slope becomes the reverberation time 133 - in §2. 127 + **The recovered impulse response.** Deconvolving the recording gives the 128 + broadband IR: the direct sound, discrete early reflections and the decaying 129 + diffuse tail. Its `.plot()` shows the waveform above and the log-magnitude 130 + envelope with the Schroeder energy-decay curve below — the straight decay 131 + whose slope becomes the reverberation time in §2. 134 132 135 133 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/impulse_response_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/impulse_response.png" alt="Recovered room impulse response: the normalized waveform with the direct sound and reflections labelled, and below it the log-magnitude envelope in dB with the Schroeder energy-decay curve" width="88%"></picture> 136 134 ··· 163 161 164 162 </details> 165 163 166 - One IR characterises a single source–receiver pair; a reported room parameter 167 - is the spatial average over several. ISO 3382-1 (performance spaces) asks for 168 - at least two source positions and microphones spaced $\geq 2$ m apart, 169 - $\geq 1$ m from any surface, at $1.2$ m (seated-ear) height; ISO 3382-2 fixes 170 - the minimum number of source, microphone and source–microphone combinations 171 - per accuracy grade (survey / engineering / precision). 164 + **Where to measure.** One IR characterises a single source–receiver pair; a 165 + reported room parameter is the spatial average over several. ISO 3382-1 166 + (performance spaces) asks for at least two source positions and microphones 167 + spaced $\geq 2$ m apart, $\geq 1$ m from any surface, at $1.2$ m 168 + (seated-ear) height, avoiding symmetric placements; ISO 3382-2 fixes the 169 + minimum number of source, microphone and source–microphone combinations per 170 + accuracy grade (survey / engineering / precision). 172 171 173 172 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_room_measurement_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_room_measurement.svg" alt="Room-acoustics measurement setup: a top-view room plan with two loudspeaker source positions and six microphone positions with the ISO 3382-1 spacing rules, and the ISO 3382-2 table of minimum positions for the survey, engineering and precision grades" width="94%"></picture> 174 173 ··· 399 398 `d2s`/`lp_as_4m` are `nan` if fewer than two positions fall in 2–16 m; 400 399 `rd`/`rp` are `nan` when STI does not decrease with distance. The per-position 401 400 STI can itself be measured with the STIPA tools in the 402 - [Psychoacoustics guide](psychoacoustics.md). 403 - 404 - ## 4. Field insulation and single-number ratings (ISO 16283-1, ISO 717-1) 405 - 406 - To rate a wall or floor, measure the energy-average level in the **source** 407 - room ($L_1$) and the **receiving** room ($L_2$) per one-third-octave band 408 - and form the level difference $D = L_1 - L_2$. Two normalisations make it 409 - comparable between rooms. The **standardized level difference** references 410 - the receiving-room reverberation time $T$ to $T_0 = 0.5$ s (so with 411 - $T = 0.5$ s, $D_{nT} = D$ exactly), and the **apparent sound reduction 412 - index** normalises by the partition area $S$ and the Sabine absorption area 413 - $A$: 414 - 415 - $$ 416 - D_{nT} = D + 10 \log_{10} \frac{T}{T_0}, \qquad 417 - R' = D + 10 \log_{10} \frac{S}{A}, \qquad A = \frac{0.16\ V}{T}. 418 - $$ 419 - 420 - Positions are energy-averaged with 421 - $L = 10 \log_{10}\left( \frac{1}{n} \sum_i 10^{L_i/10} \right)$. 422 - 423 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_insulation_setup_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_insulation_setup.svg" alt="Field airborne insulation setup: a loudspeaker in the source room, microphones energy-averaged in source and receiving rooms across the common partition" width="92%"></picture> 424 - 425 - The band spectrum is collapsed to one number by the **reference-curve 426 - method** of ISO 717-1: a fixed reference curve is shifted in 1 dB steps 427 - toward the measured curve until the sum of *unfavourable* deviations 428 - (where the measurement falls below the reference) is as large as possible 429 - but not more than 32.0 dB (16 one-third-octave bands) or 10.0 dB (5 octave 430 - bands). The rating (`Rw`, `R'w`, `DnT,w` …) is the shifted reference read at 431 - 500 Hz. The **spectrum adaptation terms** $C$ (pink noise) and $C_{tr}$ 432 - (urban traffic) add the low-frequency penalty of a real source. 433 - 434 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/insulation_rating_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/insulation_rating.png" alt="Measured one-third-octave sound reduction index with the shifted ISO 717-1 reference curve and the resulting weighted rating at 500 Hz" width="80%"></picture> 435 - 436 - ```python 437 - import numpy as np 438 - from phonometry import airborne_insulation, weighted_rating, energy_average_level 439 - 440 - # Energy-average several microphone positions in one room (dB) 441 - print(round(float(energy_average_level([60.0, 66.0])), 1)) # 64.0 442 - 443 - # Field insulation per band; area S and volume V add R' 444 - l1 = np.full(16, 80.0) # source-room levels 445 - l2 = np.full(16, 40.0) # receiving-room levels 446 - t2 = np.full(16, 0.5) # receiving-room T (s) 447 - ins = airborne_insulation(l1, l2, t2, area=10.0, volume=50.0) 448 - print(round(float(ins.dnt[0]), 1)) # 40.0 (= D since T = T0) 449 - print(round(float(ins.r_prime[0]), 1)) # 38.0 450 - 451 - # Single-number rating from a measured 16-band R spectrum (ISO 717-1 Annex C) 452 - R = [20.4, 16.3, 17.7, 22.6, 22.4, 22.7, 24.8, 26.6, 453 - 28.0, 30.5, 31.8, 32.5, 33.4, 33.0, 31.0, 25.5] 454 - w = weighted_rating(R) 455 - print(w.rating, w.c, w.ctr) # 30 -2 -3 -> Rw(C;Ctr) = 30(-2;-3) 456 - 457 - w.plot() # measured R' vs shifted ISO 717-1 reference, deviations shaded (needs matplotlib) 458 - ``` 401 + [Speech Transmission Index guide](speech-transmission.md). 459 402 460 - <details> 461 - <summary>Show the code for this figure</summary> 462 - 463 - ```python 464 - import matplotlib.pyplot as plt 465 - 466 - # One line — measured curve vs the shifted ISO 717-1 reference, deviations shaded: 467 - w.plot() 468 - plt.show() 469 - 470 - # By hand, from the band curve the result now carries: 471 - fig, ax = plt.subplots() 472 - ax.semilogx(w.band_centers, w.measured, "o-", label="Measured R'") 473 - ax.semilogx(w.band_centers, w.shifted_reference, "s--", label="Shifted reference") 474 - ax.fill_between(w.band_centers, w.measured, w.shifted_reference, 475 - where=w.measured < w.shifted_reference, interpolate=True, 476 - alpha=0.3, label="Unfavourable deviations") 477 - ax.set_xlabel("Frequency [Hz]") 478 - ax.set_ylabel("Sound reduction index [dB]") 479 - ax.set_title(f"Rw = {w.rating} dB (C={w.c:+d}; Ctr={w.ctr:+d})") 480 - ax.legend() 481 - plt.show() 482 - ``` 483 - 484 - </details> 485 - 486 - Compute `l1`, `l2` and `t2` on the same 16 one-third-octave bands from 487 - 100 Hz to 3150 Hz — obtain `t2` from 488 - `room_parameters(ir, fs, limits=(100, 3150), fraction=3).t30`, for example — and 489 - pass them to `airborne_insulation`. Feed that function's `dnt` (or `r_prime`) 490 - spectrum to `weighted_rating`, so every band aligns index-by-index with the 491 - ISO 717-1 reference curve. 492 - 493 - ### `airborne_insulation()` parameters 494 - 495 - | Parameter | Type | Units | Range / default | Notes | 496 - | :--- | :--- | :--- | :--- | :--- | 497 - | `l1` | 1D or 2D array | dB | one/band, or `(positions, bands)` | Source-room levels (2D is energy-averaged) | 498 - | `l2` | 1D or 2D array | dB | same band count | Receiving-room levels | 499 - | `t2` | 1D array | s | > 0, one per band | Receiving-room reverberation time | 500 - | `area` | float, optional | m² | > 0, with `volume` | Partition area `S` (enables `R'`) | 501 - | `volume` | float, optional | m³ | > 0, with `area` | Receiving-room volume `V` | 502 - | `t0` | float | s | default `0.5` | Reference reverberation time `T0` | 503 - 504 - ### `weighted_rating()` parameters 505 - 506 - | Parameter | Type | Units | Range / default | Notes | 507 - | :--- | :--- | :--- | :--- | :--- | 508 - | `values_by_band` | 1D array | dB | 16 (thirds) or 5 (octaves) | Measured `R`, `R'`, `DnT` … per band | 509 - | `bands` | str or `None` | — | `'third-octave'` / `'octave'` / `None` | `None` infers from the count | 510 - 511 - `airborne_insulation()` returns an `AirborneInsulationResult` (`d`, `dnt`, 512 - `r_prime` or `None`); `weighted_rating()` returns a `WeightedRatingResult` 513 - (`rating`, `c`, `ctr`, `unfavourable_sum`, all integers except the sum). 514 - 515 - ### Impact sound (ISO 16283-2, ISO 717-2) 516 - 517 - Footstep noise is rated the other way round. Instead of how much a floor 518 - *blocks*, impact insulation measures how much a standardized **tapping 519 - machine** on the floor above puts into the room below — so a *higher* number 520 - is *worse*. The energy-average impact sound pressure level $L_i$ in the 521 - receiving room is normalised like the airborne case, but with a sign flip on 522 - the reverberation term: 523 - 524 - $$ 525 - L'_{nT} = L_i - 10 \log_{10} \frac{T}{T_0}, \qquad 526 - L'_n = L_i + 10 \log_{10} \frac{A}{A_0}, \quad 527 - A_0 = 10\ \text{m}^2,\ A = \frac{0.16\ V}{T}. 528 - $$ 529 - 530 - The **standardized** impact level $L'_{nT}$ ($T_0 = 0.5$ s for dwellings) 531 - needs only the receiving-room $T$, so with $T = 0.5$ s it equals $L_i$; the 532 - **normalized** level $L'_n$ (referenced to a 10 m² absorption area) also needs 533 - the receiving-room volume. Note the **minus** sign — more reverberation 534 - *lowers* $L'_{nT}$, opposite to the airborne $D_{nT}$. 535 - 536 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_impact_setup_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_impact_setup.svg" alt="Field impact insulation setup: a standardized tapping machine on the floor of the source room above, microphones energy-averaged in the receiving room below, and the receiving-room reverberation time" width="92%"></picture> 537 - 538 - The single-number rating (ISO 717-2) shifts the same style of reference curve, 539 - but an **unfavourable deviation now occurs where the measurement *exceeds* the 540 - reference** (impact noise is worse when higher) — the sign opposite to 541 - ISO 717-1. The rating (`Ln,w`, `L'n,w`, `L'nT,w`) is the shifted reference read 542 - at 500 Hz; for octave bands it is then reduced by 5 dB. The spectrum 543 - adaptation term $C_I = L_{n,\text{sum}} - 15 - L_{n,w}$ uses the energetic sum 544 - over 100–2500 Hz (16-band thirds excluding 3150 Hz) or 125–2000 Hz (octaves). 545 - 546 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/impact_rating_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/impact_rating.png" alt="Measured one-third-octave normalized impact sound pressure level with the shifted ISO 717-2 reference curve and the resulting weighted rating read at 500 Hz" width="80%"></picture> 547 - 548 - ```python 549 - import numpy as np 550 - from phonometry import impact_insulation, weighted_impact_rating 551 - 552 - # 16 one-third-octave impact levels Li (100 Hz - 3150 Hz), dB, from the 553 - # ISO 717-2 Annex C worked example, and the receiving-room T per band. 554 - li = np.array([62.1, 63.2, 63.5, 66.2, 68.5, 70.0, 71.7, 73.1, 555 - 73.8, 73.5, 73.8, 73.3, 73.1, 73.0, 72.4, 71.2]) 556 - t2 = np.full(16, 0.5) 557 - 558 - imp = impact_insulation(li, t2, volume=50.0) 559 - print(round(float(imp.l_n_t[0]), 1)) # 62.1 (= Li since T = T0) 560 - print(round(float(imp.l_n[0]), 1)) # 64.1 normalized to A0 = 10 m^2 561 - 562 - # Weighted impact rating + spectrum adaptation term CI (ISO 717-2) 563 - res_imp = weighted_impact_rating(imp.l_n_t) 564 - print(res_imp.rating, res_imp.ci, res_imp.unfavourable_sum) # 79 -11 28.0 -> L'nT,w(CI)=79(-11) 565 - 566 - # Octave-band data carry the extra -5 dB reduction (Clause 4.3.2) 567 - octave = np.array([65.3, 64.5, 58.0, 55.8, 43.0]) 568 - print(weighted_impact_rating(octave).rating) # 54 569 - 570 - res_imp.plot() # measured L'nT vs shifted ISO 717-2 reference, measured-above shaded (needs matplotlib) 571 - ``` 572 - 573 - <details> 574 - <summary>Show the code for this figure</summary> 575 - 576 - ```python 577 - import matplotlib.pyplot as plt 578 - 579 - # One line — measured L'nT vs the shifted ISO 717-2 reference (measured-above shaded): 580 - res_imp.plot() 581 - plt.show() 582 - 583 - # By hand, from the band curve the result now carries (note the opposite sign: 584 - # an unfavourable deviation is where the MEASURED level exceeds the reference). 585 - # Here the input was l_n_t, so the rated quantity is the field level L'nT,w: 586 - fig, ax = plt.subplots() 587 - ax.semilogx(res_imp.band_centers, res_imp.measured, "o-", label="Measured L'nT") 588 - ax.semilogx(res_imp.band_centers, res_imp.shifted_reference, "s--", label="Shifted reference") 589 - ax.fill_between(res_imp.band_centers, res_imp.shifted_reference, res_imp.measured, 590 - where=res_imp.measured > res_imp.shifted_reference, interpolate=True, 591 - alpha=0.3, label="Unfavourable deviations") 592 - ax.set_xlabel("Frequency [Hz]") 593 - ax.set_ylabel("Impact sound pressure level [dB]") 594 - ax.set_title(f"L'nT,w = {res_imp.rating} dB (CI={res_imp.ci:+d})") 595 - ax.legend() 596 - plt.show() 597 - ``` 598 - 599 - </details> 600 - 601 - Feed `impact_insulation`'s `l_n_t` (or `l_n`) straight into 602 - `weighted_impact_rating`; the rating and `CI` reproduce the ISO 717-2 Annex C 603 - values (thirds `L'nT,w = 79`, `CI = −11`; octave `54`, `CI = 0`). 604 - 605 - #### `impact_insulation()` parameters 606 - 607 - | Parameter | Type | Units | Range / default | Notes | 608 - | :--- | :--- | :--- | :--- | :--- | 609 - | `li` | 1D or 2D array | dB | one/band, or `(positions, bands)` | Energy-average impact SPL (2D is averaged over positions) | 610 - | `t2` | 1D array | s | > 0, one per band | Receiving-room reverberation time | 611 - | `volume` | float, optional | m³ | > 0 | Receiving-room `V` (enables `L'n`) | 612 - | `t0` | float | s | default `0.5` | Reference reverberation time `T0` | 613 - 614 - #### `weighted_impact_rating()` parameters 615 - 616 - | Parameter | Type | Units | Range / default | Notes | 617 - | :--- | :--- | :--- | :--- | :--- | 618 - | `values_by_band` | 1D array | dB | 16 (thirds) or 5 (octaves) | Measured `Ln`, `L'n` or `L'nT` per band | 619 - | `bands` | str or `None` | — | `'third-octave'` / `'octave'` / `None` | `None` infers from the count | 620 - 621 - `impact_insulation()` returns an `ImpactInsulationResult` (`l_n_t`, `l_n` or 622 - `None`); `weighted_impact_rating()` returns an `ImpactRatingResult` (`rating`, 623 - `ci` integers, `unfavourable_sum` in dB). 624 - 625 - ### Field façade insulation (ISO 16283-3) 626 - 627 - The same source/receiver logic reaches the building **façade**, but now the 628 - source is *outdoors* — a loudspeaker at 45° or the road traffic itself. Rather 629 - than a level difference across an internal partition, ISO 16283-3 references the 630 - receiving-room level $L_2$ to the level **2 m in front of the façade** 631 - $L_{1,2m}$, giving the level difference $D_{2m}$ and, exactly as in the airborne 632 - case, its standardized and normalized forms: 633 - 634 - $$ 635 - D_{2m} = L_{1,2m} - L_2, \quad 636 - D_{2m,nT} = D_{2m} + 10 \log_{10}\frac{T}{T_0}, \quad 637 - D_{2m,n} = D_{2m} - 10 \log_{10}\frac{A}{A_0}, 638 - $$ 639 - 640 - with $T_0 = 0.5$ s, $A_0 = 10$ m² and $A = 0.16\ V/T$ (dwellings). When the 641 - microphone sits **on the test element** (surface level $L_{1,s}$) the *element* 642 - method also yields an apparent sound reduction index, carrying a fixed 643 - angle-of-incidence correction — $-1.5$ dB for the 45° loudspeaker method, 644 - $-3$ dB for the all-angle road-traffic method: 645 - 646 - $$ 647 - R'_{45°} = L_{1,s} - L_2 + 10 \log_{10}\frac{S}{A} - 1.5, \qquad 648 - R'_{tr,s} = L_{1,s} - L_2 + 10 \log_{10}\frac{S}{A} - 3. 649 - $$ 650 - 651 - The façade quantity is airborne, so its single-number rating uses the 652 - **ISO 717-1** reference curve through `weighted_rating` unchanged (Annex F). 653 - 654 - ```python 655 - import numpy as np 656 - from phonometry import facade_insulation, weighted_rating 657 - 658 - # Outdoor level 2 m in front of the façade, receiving-room level and T per 659 - # one-third-octave band; surface_level is the microphone on the test element. 660 - l1_2m = np.full(16, 75.0) # L1,2m outdoors 661 - l2 = np.full(16, 33.0) # receiving-room L2 662 - t2 = np.full(16, 0.5) # receiving-room T (s) 663 - 664 - fac = facade_insulation(l1_2m, l2, t2, volume=50.0, area=11.5, 665 - surface_level=np.full(16, 78.0), method="loudspeaker") 666 - print(round(float(fac.d_2m[0]), 1)) # 42.0 D2m = L1,2m - L2 667 - print(round(float(fac.d_2m_nt[0]), 1)) # 42.0 (= D2m since T = T0) 668 - print(round(float(fac.d_2m_n[0]), 1)) # 40.0 normalized to A0 = 10 m^2 669 - print(round(float(fac.r_prime[0]), 1)) # 42.1 R'45deg (loudspeaker, -1.5 dB) 670 - 671 - # The road-traffic element method carries the -3 dB all-angle correction instead 672 - tr = facade_insulation(l1_2m, l2, t2, volume=50.0, area=11.5, 673 - surface_level=np.full(16, 78.0), method="road_traffic") 674 - print(round(float(tr.r_prime[0]), 1)) # 40.6 R'tr,s (traffic, -3 dB) 675 - 676 - # The façade quantity is airborne: rate D2m,nT with the ISO 717-1 engine 677 - print(weighted_rating(fac.d_2m_nt).rating) # 42 Dls,2m,nT,w 678 - 679 - fac.plot() # per-band D2m,nT with D2m, D2m,n and R' overlaid (needs matplotlib) 680 - ``` 681 - 682 - `surface_level`, `area` and `volume` are all optional: with only `l1_2m`, `l2` 683 - and `t2` the function returns `d_2m` and `d_2m_nt`; add `volume` for `d_2m_n`; 684 - add `surface_level` **and** `area` **and** `volume` for `r_prime`. Positions are 685 - energy-averaged with the surface-level formula (Clause 9.5.1); band levels are 686 - assumed already corrected for background noise. 687 - 688 - #### `facade_insulation()` parameters 689 - 690 - | Parameter | Type | Units | Range / default | Notes | 691 - | :--- | :--- | :--- | :--- | :--- | 692 - | `l1_2m` | 1D or 2D array | dB | one/band, or `(positions, bands)` | Level 2 m in front of the façade `L1,2m` | 693 - | `l2` | 1D or 2D array | dB | same band count | Receiving-room levels | 694 - | `t2` | 1D array | s | > 0, one per band | Receiving-room reverberation time | 695 - | `area` | float, optional | m² | > 0, with `surface_level`, `volume` | Test-element area `S` (enables `R'`) | 696 - | `volume` | float, optional | m³ | > 0 | Receiving-room `V` (enables `D2m,n`; required for `R'`) | 697 - | `surface_level` | 1D/2D array, optional | dB | same band count | Surface level `L1,s` on the element (enables `R'`) | 698 - | `method` | str | — | `'loudspeaker'` (−1.5 dB) / `'road_traffic'` (−3 dB) | Angle-of-incidence correction of `R'` | 699 - | `t0` | float | s | default `0.5` | Reference reverberation time `T0` | 700 - | `frequencies` | 1D array, optional | Hz | — | Band centres carried on the result for plotting | 701 - 702 - `facade_insulation()` returns a `FacadeInsulationResult` (`d_2m`, `d_2m_nt`, 703 - `d_2m_n` or `None`, `r_prime` or `None`, `frequencies`); feed any 16-band façade 704 - quantity to `weighted_rating` for its ISO 717-1 single number. 705 - 706 - ## 5. Laboratory measurement (ISO 10140) 707 - 708 - Everything above is a **field** measurement (the primed quantities $R'$, $L'_n$): 709 - the number a real building achieves, flanking transmission and all. To rate an 710 - element on its own — a wall type, a floating floor, a window — you take it to a 711 - qualified **laboratory** (ISO 10140), where suppressed flanking makes the 712 - *direct* transmission the whole story. The formulas lose their primes: the 713 - **sound reduction index** $R$ (not $R'$) and the **normalized impact level** 714 - $L_n$ (not $L'_n$), with the receiving room's absorption area $A = 0.16\ V/T$ 715 - now a known property of the facility: 716 - 717 - $$ 718 - R = L_1 - L_2 + 10 \log_{10}\frac{S}{A}, \qquad 719 - L_n = L_i + 10 \log_{10}\frac{A}{A_0}, \quad A_0 = 10\ \text{m}^2. 720 - $$ 721 - 722 - | | Field (ISO 16283) | Laboratory (ISO 10140) | 723 - | :--- | :--- | :--- | 724 - | Airborne | $R'$ apparent (with flanking) | $R$ direct (flanking suppressed) | 725 - | Impact | $L'_n$ apparent | $L_n$ direct | 726 - | Absorption area | measured in the room | property of the facility | 727 - 728 - The single-number ratings reuse the very same ISO 717-1/2 engines 729 - (`weighted_rating`, `weighted_impact_rating`) — an $R$ spectrum rates to $R_w$ 730 - exactly as an $R'$ spectrum rated to $R'_w$. Before forming the index the 731 - receiving-room levels must be **corrected for background noise** (Clause 4.3): 732 - the energy subtraction $10 \log_{10}(10^{L_{sb}/10} - 10^{L_b/10})$ applies for a 733 - 6–15 dB signal-to-background margin, a fixed 1.3 dB correction (the *limit of 734 - measurement*) at or below 6 dB, and no correction at or above 15 dB. 735 - 736 - ```python 737 - import numpy as np 738 - from phonometry import (lab_airborne_insulation, lab_impact_insulation, 739 - background_correction) 740 - 741 - # Source/receiving levels and receiving-room T over the 16 one-third-octave 742 - # bands; S is the free test-opening area, V the receiving-room volume. 743 - l1 = np.full(16, 80.0) 744 - l2 = np.full(16, 40.0) 745 - t2 = np.full(16, 0.5) 746 - lab = lab_airborne_insulation(l1, l2, t2, area=10.0, volume=50.0) 747 - print(round(float(lab.r[0]), 1)) # 38.0 R = L1 - L2 + 10 lg(S/A) 748 - print(round(float(lab.absorption[0]), 1)) # 16.0 A = 0.16 V / T (m^2) 749 - print(lab.rating.rating, lab.rating.c, lab.rating.ctr) # 38 0 0 -> Rw(C;Ctr) 750 - 751 - # Impact: the tapping-machine level Li normalized to A0 = 10 m^2 gives Ln 752 - li = np.array([62.1, 63.2, 63.5, 66.2, 68.5, 70.0, 71.7, 73.1, 753 - 73.8, 73.5, 73.8, 73.3, 73.1, 73.0, 72.4, 71.2]) 754 - imp = lab_impact_insulation(li, t2, volume=50.0) 755 - print(round(float(imp.l_n[0]), 1)) # 64.1 Ln = Li + 10 lg(A/A0) 756 - print(imp.rating.rating, imp.rating.ci) # 81 -11 -> Ln,w(CI) 757 - 758 - # Background correction: margins 6 / 1 / 20 dB -> capped / capped / unchanged 759 - corrected = background_correction([30.0, 33.0, 50.0], [24.0, 32.0, 30.0]) 760 - print(np.round(corrected, 1)) # [28.7 31.7 50.0] (1.3 dB cap twice) 761 - 762 - lab.rating.plot() # measured R vs shifted ISO 717-1 reference (needs matplotlib) 763 - ``` 764 - 765 - A margin at or below 6 dB emits a `LabInsulationWarning` and flags the band as 766 - the limit of measurement; catch it with `warnings.simplefilter("error", 767 - LabInsulationWarning)`. The automatic rating is formed only when exactly 16 768 - one-third-octave or 5 octave values are supplied (`rating` is `None` otherwise). 769 - 770 - ### `lab_airborne_insulation()` / `lab_impact_insulation()` parameters 771 - 772 - | Parameter | Type | Units | Range / default | Notes | 773 - | :--- | :--- | :--- | :--- | :--- | 774 - | `l1` / `l2` | 1D or 2D array | dB | one/band, or `(positions, bands)` | Source / receiving levels (airborne) | 775 - | `li` | 1D or 2D array | dB | one/band, or `(positions, bands)` | Impact SPL from the tapping machine (impact) | 776 - | `t2` | 1D array | s | > 0, one per band | Receiving-room reverberation time | 777 - | `area` | float | m² | > 0 | Free test-opening area `S` (airborne only) | 778 - | `volume` | float | m³ | > 0 | Receiving-room volume `V` | 779 - 780 - `lab_airborne_insulation()` returns a `LabAirborneInsulationResult` (`r`, 781 - `absorption`, `rating`); `lab_impact_insulation()` a 782 - `LabImpactInsulationResult` (`l_n`, `absorption`, `rating`); 783 - `background_correction(signal_and_background, background)` returns the corrected 784 - levels directly. 785 - 786 - ## 6. Predicting performance (EN 12354) 787 - 788 - A laboratory rating describes an element in isolation, yet the sound a building 789 - actually transmits also travels *around* the partition — along the floor, up the 790 - façade, through the flanking walls — re-radiating into the receiving room. This 791 - **flanking transmission** is the whole difference between the laboratory $R$ and 792 - the field $R'$. EN 12354 predicts the in-situ apparent rating from the 793 - laboratory ratings of the elements plus the vibration transmission of their 794 - junctions. 795 - 796 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_flanking_paths_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_flanking_paths.svg" alt="The direct path Dd through the separating element and the three flanking paths Ff, Df and Fd across each junction between a flanking element and the separating element" width="92%"></picture> 797 - 798 - Each junction between a flanking element and the separating element carries 799 - three paths — $Ff$ (flanking→flanking), $Df$ (direct→flanking) and $Fd$ 800 - (flanking→direct) — alongside the single direct path $Dd$. The **simplified 801 - single-number model** combines them energetically (Formula 26): 802 - 803 - $$ 804 - R'_w = -10 \log_{10}\Big[ 10^{-R_{Dd,w}/10} 805 - + \sum 10^{-R_{Ff,w}/10} + \sum 10^{-R_{Df,w}/10} 806 - + \sum 10^{-R_{Fd,w}/10} \Big], 807 - $$ 808 - 809 - with the direct path $R_{Dd,w} = R_{s,w} + \Delta R_{Dd,w}$ (Formula 27) and each 810 - flanking path (Formula 28a) 811 - 812 - $$ 813 - R_{ij,w} = \tfrac{R_{i,w} + R_{j,w}}{2} + \Delta R_{ij,w} + K_{ij} 814 - + 10 \log_{10}\frac{S_s}{l_0\ l_f}, 815 - $$ 816 - 817 - where $l_0 = 1$ m is the reference coupling length, $l_f$ the junction coupling 818 - length and $K_{ij}$ the junction's **vibration reduction index** (Annex E, 819 - empirical in the mass ratio $M = \log_{10}(m'_{\perp,i}/m'_i)$). 820 - 821 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/prediction_flanking_demo_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/prediction_flanking_demo.png" alt="Per-path sound reduction indices for the EN 12354-1 Annex H.3 example and each path's share of the transmitted energy, showing the direct path dominating at R'w = 52 dB" width="80%"></picture> 822 - 823 - ```python 824 - import numpy as np 825 - from phonometry import (junction_vibration_reduction, flanking_element, 826 - predicted_airborne_insulation) 827 - 828 - # EN 12354-1 Annex H.3: a separating wall Rs,w = 57 dB, area Ss = 11.5 m², with 829 - # four flanking elements. The simplified model reads each junction's Kij at 830 - # 500 Hz from the mass ratio m'perp / m' (Annex E) — here the floor's rigid 831 - # cross-junction (the mass ratio is itself rounded, hence 12.5 vs Annex 12.4): 832 - print(round(junction_vibration_reduction("rigid_cross", "through", 1.61), 1)) # 12.5 KFf 833 - print(round(junction_vibration_reduction("rigid_cross", "corner", 1.61), 1)) # 8.9 KFd = KDf 834 - 835 - # Build each element's three flanking paths (Ff, Df, Fd) from the Annex H 836 - # tabulated Kij, then combine the direct path Dd energetically (Formula 26). 837 - elements = [ # (name, Rw, KFf, KFd = KDf, coupling length lf) 838 - ("floor", 49, 12.4, 8.9, 4.50), 839 - ("ceiling", 46, 14.4, 9.2, 4.50), 840 - ("facade", 42, 12.6, 6.7, 2.55), 841 - ("int-wall", 33, 33.5, 15.7, 2.55), 842 - ] 843 - paths = [] 844 - for name, rw, k_ff, k_fd, lf in elements: 845 - paths += flanking_element(label=name, r_flanking=rw, r_separating=57, 846 - k_ff=k_ff, k_fd=k_fd, k_df=k_fd, 847 - separating_area=11.5, coupling_length=lf) 848 - 849 - res = predicted_airborne_insulation(r_direct=57.0, flanking_paths=paths) 850 - print(round(res.r_prime_w, 1)) # 52.2 -> R'w = 52 dB 851 - print(res.dominant.label, round(res.dominant.fraction, 2)) # Dd 0.33 (direct dominates) 852 - ``` 853 - 854 - Every added flanking path strictly lowers $R'_w$ below the direct $R_{Dd,w} = 57$; 855 - `res.paths` exposes each path's share of the transmitted energy so the dominant 856 - path is visible. Clause 4.4.2 also enforces a floor $K_{ij} \ge K_{ij,\min}$ from 857 - the junction geometry — compute it with `junction_min_vibration_reduction` and 858 - pass it to `flanking_path(..., kij_min=...)`, which raises a below-floor $K_{ij}$ 859 - to the minimum: 860 - 861 - ```python 862 - from phonometry import junction_min_vibration_reduction 863 - # Kij,min = 10 lg[lf·l0·(1/Si + 1/Sj)]; large elements give a low (here negative) 864 - # floor, so a realistic tabulated Kij is rarely clamped. 865 - print(round(junction_min_vibration_reduction(coupling_length=4.5, 866 - s_i=11.5, s_j=11.5), 1)) # -1.1 867 - ``` 868 - 869 - The impact counterpart (EN 12354-2, Formula 21) is a direct subtraction: 870 - $L'_{n,w} = L_{n,w,eq} - \Delta L_w + K$, with the bare-floor equivalent level 871 - $L_{n,w,eq} = 164 - 35 \log_{10}(m'/m'_0)$ (Annex B), the covering improvement 872 - $\Delta L_w$ (ISO 717-2) and the flanking correction $K$ from Table 1. 873 - 874 - ```python 875 - from phonometry import (equivalent_impact_level, impact_flanking_correction, 876 - predicted_impact_insulation, standardized_impact_level) 877 - 878 - # EN 12354-2 Annex E.3: a 0.14 m concrete floor (m' = 322 kg/m²) with a floating 879 - # floor (ΔLw = 33 dB), rooms one above the other, mean flanking mass 145 kg/m². 880 - ln_eq = equivalent_impact_level(322.0) # 164 - 35 lg(m') 881 - k = impact_flanking_correction(322.0, 145.0) # Table 1 (sep 322, flk 145) 882 - imp = predicted_impact_insulation(ln_w_eq=ln_eq, delta_l_w=33.0, k_correction=k) 883 - print(round(ln_eq, 1), k, round(imp.l_prime_n_w, 1)) # 76.2 2 45.2 -> L'n,w = 45 dB 884 - print(round(standardized_impact_level(imp.l_prime_n_w, 50.0), 1)) # 43.0 L'nT,w 885 - ``` 886 - 887 - <details> 888 - <summary>Show the code for this figure</summary> 889 - 890 - ```python 891 - import matplotlib.pyplot as plt 892 - 893 - # Per-path sound reduction index and each path's share of the transmitted 894 - # energy for the Annex H.3 result computed above. 895 - labels = [p.label for p in res.paths] 896 - r_w = [p.r_w for p in res.paths] 897 - frac = [100.0 * p.fraction for p in res.paths] 898 - 899 - fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6), sharex=True) 900 - ax1.bar(labels, r_w, color="tab:blue") 901 - ax1.axhline(res.r_prime_w, ls="--", color="k", label=f"R'w = {res.r_prime_w:.1f} dB") 902 - ax1.set_ylabel("Path Rij,w [dB]"); ax1.legend() 903 - ax2.bar(labels, frac, color="tab:orange") 904 - ax2.set_ylabel("Energy share [%]"); ax2.set_xlabel("Transmission path") 905 - for ax in (ax1, ax2): 906 - ax.tick_params(axis="x", rotation=45) 907 - fig.suptitle("EN 12354-1 Annex H.3 — flanking transmission") 908 - fig.tight_layout() 909 - plt.show() 910 - ``` 911 - 912 - </details> 913 - 914 - ### `junction_vibration_reduction()` / `flanking_element()` parameters 915 - 916 - | Parameter | Type | Units | Range / default | Notes | 917 - | :--- | :--- | :--- | :--- | :--- | 918 - | `junction_type` | str | — | `'rigid_cross'` / `'rigid_t'` / `'flexible_t'` / `'lightweight_facade'` | Junction geometry (Annex E) | 919 - | `path` | str | — | `'through'` (K13) / `'corner'` (K12 = K23) | Path branch | 920 - | `mass_ratio` | float | — | > 0 | `m'⊥,i / m'i` (Formula E.2) | 921 - | `frequency` | float | Hz | default `500` | Only `flexible_t` is frequency-dependent | 922 - | `r_flanking` / `r_separating` | float | dB | — | Weighted indices of the flanking / separating element | 923 - | `k_ff` / `k_fd` / `k_df` | float | dB | — | Junction `Kij` for the three paths | 924 - | `separating_area` | float | m² | > 0 | Separating-element area `Ss` | 925 - | `coupling_length` | float | m | > 0 | Junction coupling length `lf` | 926 - | `delta_r_ff` / `delta_r_fd` / `delta_r_df` | float | dB | default `0` | Lining improvements per path | 927 - 928 - `predicted_airborne_insulation()` returns an `AirbornePredictionResult` 929 - (`r_prime_w`, `r_direct_w`, `paths` of `PathContribution`, `dominant`); 930 - `predicted_impact_insulation()` an `ImpactPredictionResult` (`l_prime_n_w`, 931 - `ln_w_eq`, `delta_l_w`, `k_correction`). The simplified model carries a reported 932 - standard deviation of about 2 dB (Clause 5). 933 - 934 - ## 7. Measurement uncertainty (ISO 12999-1) 935 - 936 - A rating without an uncertainty is only half a result. ISO 12999-1 does not 937 - re-measure anything; it tabulates the **standard uncertainty** $u$ of every 938 - sound-insulation quantity — derived from inter-laboratory tests — and prescribes 939 - how to expand and combine it. Which standard deviation is $u$ depends on the 940 - **measurement situation** (Clause 5.2): 941 - 942 - | Situation | Meaning | Standard uncertainty $u$ | 943 - | :--- | :--- | :--- | 944 - | **A** | laboratory characterisation (ISO 10140) | reproducibility $\sigma_R$ | 945 - | **B** | same location, different teams | in-situ $\sigma_{situ}$ | 946 - | **C** | same location, same operator repeated | repeatability $\sigma_r$ | 947 - 948 - The expanded uncertainty is $U = k\ u$ (Formula 2) with the coverage factor $k$ 949 - of Table 8. A two-sided interval $Y = y \pm U$ (Formula 3, $k = 1.96$ at 95 %) 950 - *reports* a value; the **one-sided** factor ($k = 1.65$ at 95 %) *declares 951 - conformity* with a requirement (Formulae 4/5). 952 - 953 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/insulation_uncertainty_demo_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/insulation_uncertainty_demo.png" alt="A weighted rating reported with its two-sided 95 % expanded uncertainty in situations A, B and C, the reproducibility uncertainty widest and the repeatability uncertainty narrowest" width="80%"></picture> 954 - 955 - ```python 956 - from phonometry import (band_uncertainty, single_number_uncertainty, 957 - uncertain_value, satisfies_lower_requirement) 958 - 959 - # Situation B (same building, different teams) -> the in-situ standard deviation. 960 - print(single_number_uncertainty("r_w", "B")) # 0.9 dB (Table 3) 961 - u = band_uncertainty("airborne", "B") # per-band u (Table 2) 962 - print(len(u.frequencies), u.uncertainties[10]) # 21 1.1 (the 500 Hz band) 963 - 964 - # Report R'w = 52 dB with a two-sided 95 % interval (k = 1.96, Table 8): 965 - uv = uncertain_value(52.0, "rprime_w", "B") # aliases resolve to r_w 966 - print(uv.coverage_factor, round(uv.expanded_uncertainty, 1)) # 1.96 1.8 967 - print(round(uv.lower, 1), round(uv.upper, 1)) # 50.2 53.8 -> 52 ± 1.8 dB 968 - 969 - # Declaring conformity uses the ONE-sided factor (k = 1.65): does R'w provably 970 - # clear a 50 dB requirement? 971 - uc = uncertain_value(52.0, "rprime_w", "B", one_sided=True) 972 - print(satisfies_lower_requirement(52.0, uc.expanded_uncertainty, 50.0)) # True 973 - ``` 974 - 975 - Impact quantities offer situations B/C only (Table 4, no 500 Hz band in the 2020 976 - edition), and $\Delta L$ only situation A. Descriptors are case-insensitive with 977 - aliases (`rprime_w`/`dnt_w`→`r_w`, `lprime_n_w`→`ln_w`); combine independent 978 - components in quadrature with `combine_uncertainties`, and reduce by $m$ 979 - independent measurements with `reduce_by_independent_measurements` ($u/\sqrt{m}$). 980 - 981 - <details> 982 - <summary>Show the code for this figure</summary> 983 - 984 - ```python 985 - import matplotlib.pyplot as plt 986 - from phonometry import uncertain_value 987 - 988 - # The same R'w = 52 dB reported in each situation with its two-sided 95 % U. 989 - situations = ["A", "B", "C"] 990 - vals = [uncertain_value(52.0, "r_w", s) for s in situations] 991 - 992 - fig, ax = plt.subplots(figsize=(7, 4)) 993 - ax.errorbar(situations, [v.value for v in vals], 994 - yerr=[v.expanded_uncertainty for v in vals], 995 - fmt="o", capsize=8, color="tab:blue") 996 - for s, v in zip(situations, vals): 997 - ax.annotate(f"±{v.expanded_uncertainty:.1f}", (s, v.upper), 998 - textcoords="offset points", xytext=(8, 4)) 999 - ax.set_ylabel("R'w [dB]"); ax.set_xlabel("Measurement situation") 1000 - ax.set_title("R'w = 52 dB with 95 % expanded uncertainty (ISO 12999-1)") 1001 - fig.tight_layout() 1002 - plt.show() 1003 - ``` 1004 - 1005 - </details> 1006 - 1007 - ### `band_uncertainty()` / `single_number_uncertainty()` / `uncertain_value()` parameters 1008 - 1009 - | Parameter | Type | Units | Range / default | Notes | 1010 - | :--- | :--- | :--- | :--- | :--- | 1011 - | `measurand` | str | — | `'airborne'` / `'impact'` / `'impact_reduction'` | Selects Table 2 / 4 / 6 | 1012 - | `quantity` | str | — | `'r_w'`, `'ln_w'`, `'delta_lw'` (+ aliases, `+c`/`+ctr` variants) | Single-number descriptor | 1013 - | `situation` | str | — | `'A'` / `'B'` / `'C'` | Measurement situation (Clause 5.2) | 1014 - | `value` | float | dB | — | Best estimate `y` to attach `U` to | 1015 - | `coverage` | float | — | default `0.95` | Confidence level (Table 8) | 1016 - | `one_sided` | bool | — | default `False` | One-sided factor for conformity checks | 1017 - | `upper_limit` | bool | — | default `False` | Select the σR95 upper limit (airborne, situation A) | 1018 - 1019 - `band_uncertainty()` returns a `BandUncertainty` (`frequencies`, 1020 - `uncertainties`, `.to_arrays()`); `single_number_uncertainty()` a float; 1021 - `uncertain_value()` an `UncertainValue` (`value`, `standard_uncertainty`, 1022 - `coverage_factor`, `expanded_uncertainty`, `.lower`, `.upper`). The read-only 1023 - `COVERAGE_FACTORS` mapping exposes Table 8 keyed by `(confidence, one_sided)`. 1024 - 1025 - ## 8. Sound absorption (ISO 354) 403 + ## 4. Sound absorption (ISO 354) 1026 404 1027 405 The equivalent absorption area `A` that drives `R'`, `L'n`, the ISO 3744 `K2` 1028 406 environmental correction and the ISO 3741 absorption term is itself measured in ··· 1083 461 1084 462 ## See also 1085 463 1086 - - [Sound Power](sound-power.md) — the `LW` methods that consume the ISO 354 1087 - absorption area (the ISO 3744 `K2` and the ISO 3741 absorption term). 1088 - 1089 - - [Psychoacoustics and Speech Intelligibility](psychoacoustics.md) — STI/STIPA 1090 - feeds the open-plan `sti_values`; loudness and sharpness. 464 + - [Building Acoustics & Sound Insulation](building-acoustics.md) — field, 465 + laboratory and predicted sound insulation between spaces, and its measurement uncertainty. 466 + - [Sound Power](sound-power.md) — the `LW` methods that consume the 467 + ISO 354 absorption area (the ISO 3744 `K2` and the ISO 3741 absorption term). 468 + - [Speech Transmission Index](speech-transmission.md) — the STI/STIPA 469 + measurement that feeds the open-plan `sti_values`. 470 + - [Psychoacoustics](psychoacoustics.md) — loudness, sharpness and the other 471 + perception metrics of what the room delivers. 1091 472 - [Filter Banks](filter-banks.md) — the IEC 61260 fractional-octave filters 1092 473 used for band decay curves and insulation spectra. 1093 474 - [Levels](levels.md) — energy averaging and the level metrics behind
+1 -1
docs/sound-power.md
··· 547 547 - [Sound Intensity (p-p)](intensity.md) — the two-microphone probe, its 548 548 finite-difference bias and the ISO 9614-1 field indicators behind the 549 549 scanning method. 550 - - [Room and Building Acoustics](room-acoustics.md) — the reverberation time 550 + - [Room Acoustics](room-acoustics.md) — the reverberation time 551 551 and equivalent absorption area (ISO 354) that feed `K2` and the ISO 3741 552 552 absorption area; impact and airborne insulation. 553 553 - [Levels](levels.md) — energy averaging and the A-weighting behind `LWA`.
+7
docs/speech-intelligibility.md
··· 10 10 covers the **one-third-octave-band method** of **ANSI S3.5-1997 (R2017)** — 18 11 11 bands from 160 Hz to 8000 Hz. 12 12 13 + > [!NOTE] 14 + > **SII vs STI.** The SII predicts intelligibility from *audibility* — how much 15 + > of the speech spectrum clears the noise and the hearing threshold at the 16 + > listener's ear — while the STI characterises a *transmission channel*: how 17 + > much of the speech modulation a room or sound system preserves. For the 18 + > latter, see the [Speech Transmission Index guide](speech-transmission.md). 19 + 13 20 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_speech_intelligibility_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_speech_intelligibility.svg" alt="The SII computation flow: three equivalent-spectrum-level inputs (speech Ei', noise Ni', hearing threshold Ti') feed the self-speech masking and spread-of-masking stage (equivalent masking spectrum level Zi), then the equivalent disturbance Di, then the band-audibility function Ai clipped to [0, 1], and finally the band-importance-weighted sum SII = sum of Ii*Ai over the 18 one-third-octave bands" width="94%"></picture> 14 21 15 22 ## 1. Inputs and the band-importance function
+133
docs/speech-transmission.md
··· 1 + ← [Documentation index](README.md) 2 + 3 + # Speech Transmission Index (IEC 60268-16) 4 + 5 + A public-address system, an intercom, a reverberant lecture hall — each is a 6 + *transmission channel* between a talker's mouth and a listener's ear, and each 7 + degrades speech in its own way. The **Speech Transmission Index** (STI) of 8 + IEC 60268-16 rates that channel with a single number in [0, 1] by measuring 9 + how much of the speech *envelope* survives the trip. This page covers the 10 + modulation-transfer physics behind the index, the indirect method from a 11 + measured room impulse response, and the direct STIPA measurement with its 12 + standardized test signal. 13 + 14 + > [!NOTE] 15 + > **STI vs SII.** The STI characterises a *transmission channel* — how much of 16 + > the speech modulation a room or sound system preserves — while the SII 17 + > predicts intelligibility from *audibility*: how much of the speech spectrum 18 + > clears the noise and the hearing threshold at the listener's ear. For the 19 + > latter, see the [Speech Intelligibility Index guide](speech-intelligibility.md). 20 + 21 + ## 1. The modulation transfer function 22 + 23 + Reverberation and noise do not muffle speech uniformly — they blur its 24 + *envelope*: the slow (0.63–12.5 Hz) intensity modulations that carry 25 + syllables. STI quantifies how much of that modulation survives from mouth 26 + to ear, per octave band, as the **modulation transfer function** m(F). A 27 + delta-like channel keeps m = 1 (STI = 1); reverberation low-passes the 28 + envelope following Schroeder's closed form, and steady noise scales it: 29 + 30 + $$ 31 + m(F) = \frac{1}{\sqrt{1 + \left(2\pi F\,\frac{T_{60}}{13.8}\right)^2}} 32 + \cdot \frac{1}{1 + 10^{-\mathrm{SNR}/10}} 33 + $$ 34 + 35 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60.png" alt="STI versus reverberation time with the IEC 60268-16 Annex F rating bands shaded" width="80%"></picture> 36 + 37 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain.svg" alt="STI measurement chain: STIPA source signal through the room to the microphone and the MTF analysis" width="92%"></picture> 38 + 39 + ## 2. Indirect and direct (STIPA) measurement 40 + 41 + ```python 42 + import numpy as np 43 + from phonometry import sti_from_impulse_response, stipa, stipa_signal 44 + 45 + fs = 48000 46 + # A measured room impulse response (synthesized decay so the example runs) 47 + ir = np.random.default_rng(0).standard_normal(fs) * np.exp(-6.9 * np.arange(fs) / fs / 0.5) 48 + 49 + # Indirect method: from a measured room impulse response 50 + res = sti_from_impulse_response(ir, fs, snr=25.0) 51 + print(f"STI = {res.sti:.2f} ({res.rating})") # e.g. 0.62 (D) 52 + 53 + # Direct STIPA measurement: play stipa_signal() in the room, record it 54 + test = stipa_signal(fs, seconds=18.0, level_db=80.0) 55 + recording = test # in practice, the microphone signal after playback 56 + res = stipa(recording, fs) 57 + res.plot() # per-band modulation transfer index (MTI) bars, STI + rating in the title 58 + ``` 59 + 60 + <details> 61 + <summary>Show the code for this figure</summary> 62 + 63 + ```python 64 + import matplotlib.pyplot as plt 65 + 66 + # STI vs reverberation time: sweep sti_from_impulse_response over synthetic 67 + # exponential decays (white noise x exp(-6.9077 t / T60)) at a T60 grid — 68 + # exactly the physics behind the curve above: 69 + rng = np.random.default_rng(0) 70 + t60_grid = np.array([0.3, 0.5, 0.8, 1.2, 1.6, 2.0, 2.5, 3.0, 4.0, 5.0]) 71 + sti_values = [] 72 + for t60 in t60_grid: 73 + t = np.arange(int(2 * t60 * fs)) / fs 74 + ir = rng.standard_normal(t.size) * np.exp(-6.9077 * t / t60) 75 + sti_values.append(sti_from_impulse_response(ir, fs).sti) 76 + 77 + fig, ax = plt.subplots() 78 + ax.semilogx(t60_grid, sti_values, "o-") 79 + ax.set_xlabel("Reverberation time T60 [s]") 80 + ax.set_ylabel("STI") 81 + ax.set_ylim(0.0, 1.0) 82 + ax.grid(True, which="both", alpha=0.3) 83 + plt.show() 84 + ``` 85 + 86 + </details> 87 + 88 + `stipa` emits a `UserWarning` when the recording is shorter than the 89 + recommended 15 s (IEC 60268-16 STIPA practice, 15 s to 25 s): below that the 90 + slow modulation components are averaged over too few periods and the STI is 91 + biased low (an ideal loopback gives STI ≈ 0.944 at 5 s vs ≈ 0.998 at 18 s). 92 + 93 + The implementation follows **Edition 5 (2020)**: Edition 4's normative PDF 94 + is the base and every Ed. 5 change is source-attributed in the code — the 95 + only numeric delta is the revised male speech spectrum of clause A.6.1. 96 + CI checks the standard's own verification vectors: the six weighting-factor 97 + band pairs to ±0.001 STI, the m ↔ STI mapping table, the level-dependent 98 + masking control points, and Schroeder-form decays at four T₆₀ values. 99 + 100 + ### `sti_from_impulse_response()` / `stipa()` parameters 101 + 102 + | Parameter | Type | Units | Range / default | Notes | 103 + | :--- | :--- | :--- | :--- | :--- | 104 + | `ir` / `x` | 1D array | any / Pa | non-empty | IR (indirect) or STIPA recording (direct) | 105 + | `fs` | int | Hz | > 0 | | 106 + | `snr` | float or 7-vector, optional | dB | default `None` | Adds steady-noise degradation | 107 + | `level` | 7-vector, optional | dB SPL | default `None` | Enables auditory masking + reception threshold (Tables A.2/A.3) | 108 + | `ambient` | 7-vector, optional | dB SPL | needs `level` | Ambient noise band levels | 109 + | `reference` | 1D array, optional (`stipa`) | — | default `None` | Measured source signal instead of the nominal m = 0.55 | 110 + 111 + Both return `STIResult`: `sti`, `mti` (7 bands), `mtf` (7×14 or 7×2), 112 + `band_levels`, `rating` (Annex F letter `A+`…`U`). 113 + 114 + ## See also 115 + 116 + - [Room Acoustics](room-acoustics.md) — the measured impulse response the 117 + indirect method consumes, and the open-plan metrics (ISO 3382-3) built on 118 + per-position STI. 119 + - [Speech Intelligibility Index](speech-intelligibility.md) — the 120 + audibility-based ANSI S3.5 index that complements the STI. 121 + - [Psychoacoustics](psychoacoustics.md) — loudness, sharpness, tonality and 122 + roughness of the received sound. 123 + - [Theory](theory.md) — the modulation-transfer derivation and the m ↔ STI 124 + mapping. 125 + 126 + --- 127 + 128 + **Standards.** IEC 60268-16:2020 (Edition 5), *Sound system equipment — 129 + Part 16: Objective rating of speech intelligibility by speech transmission 130 + index* — the modulation transfer function and the m ↔ STI mapping, the STIPA 131 + test signal and direct method, the indirect method from the impulse response, 132 + auditory masking and the reception threshold (Tables A.2/A.3), the revised 133 + male speech spectrum (clause A.6.1) and the Annex F rating letters.
+7 -6
docs/theory.md
··· 263 263 264 264 The standard specifies **no interpolation** between the tabulated frequencies. Formula (1) is specified for **20 phon to 90 phon** between 20 Hz and 4 kHz, and only up to **80 phon between 5 kHz and 12.5 kHz** — above 80 phon the contour therefore stops at 4 kHz. Values outside these limits from Formula (2) are extrapolations the standard labels as informative only. 265 265 266 - See the [Levels guide](levels.md) for usage. 266 + See the [Psychoacoustics guide](psychoacoustics.md) for usage. 267 267 268 268 ## Tone prominence: TNR and PR (ECMA-418-1) 269 269 ··· 283 283 284 284 **PR** (clause 12) compares the level of the critical band centred on the tone, $L_M$, with the mean power of the two **contiguous** critical bands $L_L$, $L_U$ (edges from the fitted Formulae 21–22 with Tables 2–3): $\mathrm{PR} = 10\log_{10} P_M - 10\log_{10}\left[(P_L + P_U)/2\right]$ (Formula 23). For $f_t \le 171.4$ Hz the lower band is truncated at 20 Hz and its power rescaled to a **100 Hz bandwidth** (Formula 24). The criterion (Formulae 25–26) is 9.0 dB at $f_t \ge 1$ kHz, rising as $9.0 + 10.0\log_{10}(1000/f_t)$ below. Tones are assessed within the 89.1 Hz – 11.2 kHz range of interest (clauses 11.5 / 12.6). 285 285 286 - See the [Levels guide](levels.md) for usage. 286 + See the [Prominent Discrete Tones guide](tone-prominence.md) for usage. 287 287 288 288 ## Event and dose metrics 289 289 ··· 501 501 m_{dr} = \frac{2 \sqrt{\left( \sum_t I_k(t) \sin 2 \pi f_m t \right)^2 + \left( \sum_t I_k(t) \cos 2 \pi f_m t \right)^2}}{\sum_t I_k(t)}, \qquad m = \frac{m_{dr}}{0.55} 502 502 $$ 503 503 504 - See the [Psychoacoustics guide](psychoacoustics.md) for usage. 504 + See the [Speech Transmission Index guide](speech-transmission.md) for usage. 505 505 506 506 ## Speech Intelligibility Index (ANSI S3.5) 507 507 ··· 782 782 (Formula A.7), and the uncorrelated single-number uncertainty is the 783 783 energy-weighted quadrature sum of the band uncertainties (Formula B.2). 784 784 785 - See the [Room and Building Acoustics guide](room-acoustics.md) for usage. 785 + See the [Room Acoustics](room-acoustics.md) and 786 + [Building Acoustics](building-acoustics.md) guides for usage. 786 787 787 788 ## Outdoor propagation and occupational exposure (ISO 9613-1/2, ISO 9612) 788 789 ··· 889 890 $88.1$ dB, $3.8$ dB) and F (full-day, $90.1$ dB, $3.4$ dB) are reproduced to 890 891 the standard's printed precision — every intermediate of Annex E is digit-exact, 891 892 and its final level differs only by the standard's own pre-rounding of the 892 - effective-day level (see the [Levels guide](levels.md)). 893 + effective-day level (see the [Occupational Noise Exposure guide](occupational-exposure.md)). 893 894 894 895 See the [Outdoor Propagation guide](outdoor-propagation.md) and the 895 - [Levels guide](levels.md) for usage. 896 + [Occupational Noise Exposure guide](occupational-exposure.md) for usage. 896 897 897 898 ## Sound power determination (ISO 3744/3745/3746, ISO 3741, ISO 9614-2/3) 898 899
+77
docs/tone-prominence.md
··· 1 + ← [Documentation index](README.md) 2 + 3 + # Prominent Discrete Tones (ECMA-418-1) 4 + 5 + Tonal components in machinery noise are far more annoying than their level 6 + suggests. ECMA-418-1:2024 (referenced by ECMA-74 Annex D) gives two FFT-based 7 + methods to decide whether a discrete tone is *prominent*: 8 + `tone_to_noise_ratio()` compares the tone level with the masking noise in its 9 + critical band (clause 11), and `prominence_ratio()` compares the critical band 10 + centred on the tone with the two contiguous bands (clause 12). Both return a 11 + structured verdict against the frequency-dependent prominence criteria. 12 + 13 + ## 1. Tone-to-noise ratio and prominence ratio 14 + 15 + ```python 16 + import numpy as np 17 + from phonometry import tone_to_noise_ratio, prominence_ratio 18 + 19 + fs = 48000 20 + rng = np.random.default_rng(0) 21 + t = np.arange(fs) / fs 22 + x = np.sin(2 * np.pi * 1000 * t) + 0.05 * rng.standard_normal(fs) # 1 kHz tone in noise 23 + tnr = tone_to_noise_ratio(x, fs) # highest peak, or tone_freq=... 24 + pr = prominence_ratio(x, fs, tone_freq=1000.0) 25 + print(tnr.ratio_db, tnr.criterion_db, tnr.prominent) 26 + ``` 27 + 28 + The methods hinge on the **critical band** — the ear's analysis bandwidth, 29 + $\Delta f_c = 25 + 75\ [1 + 1.4(f/1000)^2]^{0.69}$ Hz (162 Hz at 1 kHz): a 30 + tone is masked only by the noise *inside* its critical band, so both ratios 31 + compare the tone against exactly that noise, not the whole spectrum. 32 + 33 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tonality_spectrum_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tonality_spectrum.png" alt="Averaged spectrum of a tone in noise with the critical band shaded and the tone-to-noise ratio annotated against its prominence criterion" width="80%"></picture> 34 + 35 + A TNR above $8 + 8.33\log_{10}(1000/f_t)$ dB (8 dB from 1 kHz up) classifies 36 + the tone as *prominent*; the PR criterion is $9 + 10\log_{10}(1000/f_t)$ dB. 37 + Low frequencies get higher thresholds because wider relative bands mask more. 38 + 39 + ## 2. Where to measure (ECMA-74) and practice 40 + 41 + ECMA-74 (which delegates its tone assessments to ECMA-418-1) also fixes where to measure around a device: 42 + 43 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions.svg" alt="ECMA-74 emission measurement positions: seated operator microphone at 0.25 m and 1.20 m, and the four bystander positions at 1 m" width="92%"></picture> 44 + 45 + Proximate secondary tones in the same critical band are combined per 46 + clause 11.6; for harmonic complexes assess each component (`tone_freq=`). 47 + Both methods work on Hann-windowed, RMS-averaged spectra and need no absolute 48 + calibration (the ratios are level differences). 49 + 50 + ### `tone_to_noise_ratio()` / `prominence_ratio()` parameters 51 + 52 + | Parameter | Type | Units | Range / default | Notes | 53 + | :--- | :--- | :--- | :--- | :--- | 54 + | `x` | 1D array | any (uncalibrated OK) | ≥ `fs/resolution_hz` samples | Ratios are level differences: calibration cancels out | 55 + | `fs` | int | Hz | > 0 | | 56 + | `tone_freq` | float, optional | Hz | 89.1–11 200; default `None` | `None` assesses the highest peak in the range of interest | 57 + | `resolution_hz` | float | Hz | > 0; default `1.0` | Tone band must stay within 15 % of the critical band (clause 11.2) | 58 + 59 + Both return a `ToneAssessment(frequency, ratio_db, criterion_db, prominent)`. 60 + 61 + ## See also 62 + 63 + - [Levels](levels.md) — the ISO 1996-1 rating levels whose tonal adjustments 64 + (Table A.1) these prominence verdicts justify objectively. 65 + - [Psychoacoustics](psychoacoustics.md) — the ECMA-418-2 psychoacoustic 66 + tonality T in tu_HMS, the hearing-model counterpart of these FFT ratios. 67 + - [Impulsive-sound prominence](impulse-prominence.md) — the NT ACOU 112 68 + counterpart for impulsive (rather than tonal) character. 69 + - [Theory](theory.md) — the critical-band model and criteria derivation. 70 + 71 + --- 72 + 73 + **Standards.** ECMA-418-1:2024 (3rd edition), *Psychoacoustic metrics for ITT 74 + equipment — Part 1: Prominent discrete tones* — the tone-to-noise ratio 75 + (clause 11), the prominence ratio (clause 12), the critical-band model and the 76 + frequency-dependent prominence criteria; ECMA-74 Annex D — the emission 77 + measurement positions, delegating the tone assessment to ECMA-418-1.
+1522 -406
llms-full.txt
··· 16 16 17 17 ```python 18 18 import numpy as np 19 - from phonometry import octavefilter, laeq, ln_levels 19 + from phonometry import octave_filter, laeq, ln_levels 20 20 21 21 fs = 48000 22 22 x = np.random.randn(fs) # 1 s of signal (pressure units) 23 - spl, freq = octavefilter(x, fs, fraction=3) # 1/3-octave band levels 23 + spl, freq = octave_filter(x, fs, fraction=3) # 1/3-octave band levels 24 24 la = laeq(x, fs) # A-weighted Leq 25 25 stats = ln_levels(x, fs, n=(10, 50, 90)) # statistical levels 26 26 ``` 27 27 28 - If you are an AI assistant setting this up for a user: install from PyPI (no system dependencies), remember integer audio (e.g. wavfile.read int16) is handled automatically, use `calibration_factor` from `calculate_sensitivity()` for real dB SPL, and prefer `OctaveFilterBank` over repeated `octavefilter()` calls in tight loops (although designs are cached either way). 28 + If you are an AI assistant setting this up for a user: install from PyPI (no system dependencies), remember integer audio (e.g. wavfile.read int16) is handled automatically, use `calibration_factor` from `sensitivity()` for real dB SPL, and prefer `OctaveFilterBank` over repeated `octave_filter()` calls in tight loops (although designs are cached either way). 29 29 30 30 ## Documentation 31 31 ··· 34 34 - [Frequency Weighting (A, C, G, Z)](https://jmrplens.github.io/phonometry/guides/weighting/) 35 35 - [Time Weighting and Integration](https://jmrplens.github.io/phonometry/guides/time-weighting/) 36 36 - [Integrated and Statistical Levels](https://jmrplens.github.io/phonometry/guides/levels/) 37 - - [Psychoacoustics and Speech Intelligibility](https://jmrplens.github.io/phonometry/guides/psychoacoustics/) 37 + - [Occupational Noise Exposure (ISO 9612)](https://jmrplens.github.io/phonometry/guides/occupational-exposure/) 38 + - [Prominent Discrete Tones (ECMA-418-1)](https://jmrplens.github.io/phonometry/guides/tone-prominence/) 39 + - [Psychoacoustics](https://jmrplens.github.io/phonometry/guides/psychoacoustics/) 40 + - [Speech Transmission Index (IEC 60268-16)](https://jmrplens.github.io/phonometry/guides/speech-transmission/) 38 41 - [Sound Intensity (p-p method)](https://jmrplens.github.io/phonometry/guides/intensity/) 39 - - [Room and Building Acoustics](https://jmrplens.github.io/phonometry/guides/room-acoustics/) 42 + - [Room Acoustics](https://jmrplens.github.io/phonometry/guides/room-acoustics/) 43 + - [Building Acoustics & Sound Insulation](https://jmrplens.github.io/phonometry/guides/building-acoustics/) 40 44 - [Outdoor Sound Propagation](https://jmrplens.github.io/phonometry/guides/outdoor-propagation/) 41 45 - [Sound Power](https://jmrplens.github.io/phonometry/guides/sound-power/) 42 46 - [Calibration and dBFS](https://jmrplens.github.io/phonometry/guides/calibration/) ··· 112 116 113 117 ```python 114 118 import numpy as np 115 - from phonometry import octavefilter 119 + from phonometry import octave_filter 116 120 117 121 fs = 48000 118 122 t = np.linspace(0, 1, fs, endpoint=False) ··· 120 124 signal = np.sin(2 * np.pi * 100 * t) + np.sin(2 * np.pi * 1000 * t) 121 125 122 126 # Apply 1/3 octave filter bank 123 - spl, freq = octavefilter(signal, fs=fs, fraction=3) 127 + spl, freq = octave_filter(signal, fs=fs, fraction=3) 124 128 125 129 print(f"Bands: {freq}") 126 130 print(f"SPL [dB]: {spl}") ··· 134 138 135 139 ```python 136 140 from scipy.io import wavfile 137 - from phonometry import octavefilter 141 + from phonometry import octave_filter 138 142 139 143 # Load standard WAV file 140 144 fs, signal = wavfile.read("measurement.wav") ··· 142 146 # Analyze 143 147 # Note: To obtain real-world SPL values, you must calibrate the input. 144 148 # See the Calibration guide. 145 - spl, freq = octavefilter(signal, fs=fs, fraction=3) 149 + spl, freq = octave_filter(signal, fs=fs, fraction=3) 146 150 ``` 147 151 148 152 Integer audio (e.g. int16 WAV data) is converted to float64 internally, so it is ··· 191 195 192 196 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_multirate_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_multirate.svg" alt="Multirate decimation: high bands filtered at the input rate, low bands after anti-alias low-pass and decimation so the SOS sections stay numerically healthy" width="92%"></picture> 193 197 194 - ### `octavefilter()` / `OctaveFilterBank` parameters 198 + ### `octave_filter()` / `OctaveFilterBank` parameters 195 199 196 200 | Parameter | Type | Units | Range / default | Notes | 197 201 | :--- | :--- | :--- | :--- | :--- | ··· 226 230 227 231 | Type | Name | Usage Example | Best For | 228 232 | :--- | :--- | :--- | :--- | 229 - | `butter` | **Butterworth** | `octavefilter(x, fs, filter_type='butter')` | General acoustic measurement. | 230 - | `cheby1` | **Chebyshev I** | `octavefilter(x, fs, filter_type='cheby1', ripple=0.1)` | Sharper roll-off at the cost of ripple. | 231 - | `cheby2` | **Chebyshev II** | `octavefilter(x, fs, filter_type='cheby2')` | Flat passband with stopband zeros. | 232 - | `ellip` | **Elliptic** | `octavefilter(x, fs, filter_type='ellip', ripple=0.1)` | Maximum selectivity. | 233 - | `bessel` | **Bessel** | `octavefilter(x, fs, filter_type='bessel')` | Preserving transient waveform shapes. | 233 + | `butter` | **Butterworth** | `octave_filter(x, fs, filter_type='butter')` | General acoustic measurement. | 234 + | `cheby1` | **Chebyshev I** | `octave_filter(x, fs, filter_type='cheby1', ripple=0.1)` | Sharper roll-off at the cost of ripple. | 235 + | `cheby2` | **Chebyshev II** | `octave_filter(x, fs, filter_type='cheby2')` | Flat passband with stopband zeros. | 236 + | `ellip` | **Elliptic** | `octave_filter(x, fs, filter_type='ellip', ripple=0.1)` | Maximum selectivity. | 237 + | `bessel` | **Bessel** | `octave_filter(x, fs, filter_type='bessel')` | Preserving transient waveform shapes. | 234 238 235 239 ## Gallery of Filter Bank Responses 236 240 ··· 254 258 255 259 ```python 256 260 import numpy as np 257 - from phonometry import octavefilter 261 + from phonometry import octave_filter 258 262 259 263 # A calibrated signal in Pa so the guide runs standalone 260 264 fs = 48000 261 265 x = 0.2 * np.sin(2 * np.pi * 1000 * np.arange(fs) / fs) 262 266 263 267 # Default standard measurement 264 - spl, freq = octavefilter(x, fs, filter_type='butter') 268 + spl, freq = octave_filter(x, fs, filter_type='butter') 265 269 ``` 266 270 267 271 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/filter_butter_fraction_3_order_6_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/filter_butter_fraction_3_order_6.png" alt="Butterworth one-third-octave filter bank frequency response" width="60%"></picture> ··· 274 278 275 279 ```python 276 280 # Selectivity with 0.1 dB passband ripple 277 - spl, freq = octavefilter(x, fs, filter_type='cheby1', ripple=0.1) 281 + spl, freq = octave_filter(x, fs, filter_type='cheby1', ripple=0.1) 278 282 ``` 279 283 280 284 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/filter_cheby1_fraction_3_order_6_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/filter_cheby1_fraction_3_order_6.png" alt="Chebyshev I one-third-octave filter bank frequency response" width="60%"></picture> ··· 288 292 289 293 ```python 290 294 # Flat passband, class-1 default 72 dB stopband attenuation 291 - spl, freq = octavefilter(x, fs, filter_type='cheby2') 295 + spl, freq = octave_filter(x, fs, filter_type='cheby2') 292 296 ``` 293 297 294 298 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/filter_cheby2_fraction_3_order_6_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/filter_cheby2_fraction_3_order_6.png" alt="Chebyshev II one-third-octave filter bank frequency response" width="60%"></picture> ··· 300 304 301 305 ```python 302 306 # Maximum selectivity for extreme band isolation 303 - spl, freq = octavefilter(x, fs, filter_type='ellip', ripple=0.1) 307 + spl, freq = octave_filter(x, fs, filter_type='ellip', ripple=0.1) 304 308 ``` 305 309 306 310 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/filter_ellip_fraction_3_order_6_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/filter_ellip_fraction_3_order_6.png" alt="Elliptic one-third-octave filter bank frequency response" width="60%"></picture> ··· 313 317 314 318 ```python 315 319 # Best for pulse analysis and transient preservation 316 - spl, freq = octavefilter(x, fs, filter_type='bessel') 320 + spl, freq = octave_filter(x, fs, filter_type='bessel') 317 321 ``` 318 322 319 323 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/filter_bessel_fraction_3_order_6_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/filter_bessel_fraction_3_order_6.png" alt="Bessel one-third-octave filter bank frequency response" width="60%"></picture> ··· 374 378 375 379 ```python 376 380 import numpy as np 377 - from phonometry import octavefilter 381 + from phonometry import octave_filter 378 382 379 383 # 1. Generate a signal (Sum of 250Hz and 1000Hz) 380 384 fs = 48000 ··· 382 386 y = np.sin(2 * np.pi * 250 * t) + np.sin(2 * np.pi * 1000 * t) 383 387 384 388 # 2. Compare architectures (Butterworth vs Chebyshev II) 385 - spl_b, freq, xb_butter = octavefilter(y, fs=fs, fraction=1, sigbands=True, filter_type='butter') 386 - spl_c2, _, xb_cheby2 = octavefilter(y, fs=fs, fraction=1, sigbands=True, filter_type='cheby2') 389 + spl_b, freq, xb_butter = octave_filter(y, fs=fs, fraction=1, sigbands=True, filter_type='butter') 390 + spl_c2, _, xb_cheby2 = octave_filter(y, fs=fs, fraction=1, sigbands=True, filter_type='cheby2') 387 391 388 392 # 'xb_butter' and 'xb_cheby2' contain the time-domain signals per band 389 393 ``` ··· 756 760 | :--- | :--- | :--- | :--- | :--- | 757 761 | `x` | 1D or 2D array | digital units (or Pa if calibrated) | non-empty | 2D is `[channels, samples]`; returns one level per channel | 758 762 | `fs` | int | Hz | > 0 (`laeq` only) | `leq` needs no sample rate (pure RMS integral) | 759 - | `calibration_factor` | float | Pa per digital unit | default `1.0` | From `calculate_sensitivity()` | 763 + | `calibration_factor` | float | Pa per digital unit | default `1.0` | From `sensitivity()` | 760 764 | `dbfs` | bool | — | default `False` | `True`: 0 dBFS = full-scale RMS sine; ignores calibration | 761 765 762 766 ## Percentile levels (LN) ··· 882 886 | `sound_exposure(x, fs, duration_hours=None, ...)` | `duration_hours` treats `x` as a sample of that period | E [Pa²h] | IEC 61252 | 883 887 | `lex_8h(x, fs, duration_hours=None, ...)` | same sampling semantics | LEX,8h [dB] | IEC 61252 (≡ LEP,d) | 884 888 885 - ## Occupational noise exposure strategies and uncertainty (ISO 9612) 889 + `lex_8h` rates *one* recording; assembling a full working day from task or 890 + job samples — with the normative ISO 9612 uncertainty budget — continues in 891 + [Occupational Noise Exposure](https://jmrplens.github.io/phonometry/guides/occupational-exposure/). 892 + 893 + ## Environmental noise: Lden, Ldn and rating levels (ISO 1996-1) 894 + 895 + Regulatory noise assessment weights evenings and nights more heavily. 896 + `lden()` implements the day-evening-night level of ISO 1996-1:2016 (3.6.4: 897 + +5 dB evening, +10 dB night, default 12/4/8 h periods — adjustable, since 898 + countries define them differently), `ldn()` the day-night variant (3.6.5), 899 + and `composite_rating_level()` the general whole-day composite of clause 6.5 900 + (Formulae 5-6) for arbitrary periods with source or character adjustments 901 + (Table A.1: e.g. +5 dB regular impulsive, +12 dB highly impulsive, +3 to 902 + +6 dB prominent tones): 903 + 904 + ```python 905 + from phonometry import lden, composite_rating_level 906 + 907 + l = lden(63.2, 58.1, 51.4) # from LAeq per period 908 + r = composite_rating_level([(63.2, 12, 0.0), # day 909 + (58.1, 4, 5.0), # evening (+5) 910 + (51.4, 8, 10.0)]) # night (+10) == lden 911 + ``` 912 + 913 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/lden_profile_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/lden_profile.png" alt="Synthetic 24-hour urban LAeq profile with day, evening and night bands, the +5 and +10 dB weighted period levels and the resulting Lden" width="80%"></picture> 914 + 915 + ### `lden()` / `ldn()` / `composite_rating_level()` parameters 916 + 917 + | Function | Key parameters | Notes | 918 + | :--- | :--- | :--- | 919 + | `lden(lday, levening, lnight, hours=(12, 4, 8))` | period LAeq values [dB]; `hours` must sum to 24 | +5 dB evening, +10 dB night (3.6.4) | 920 + | `ldn(lday, lnight, hours=(15, 9))` | | +10 dB night (3.6.5) | 921 + | `composite_rating_level(periods)` | iterable of `(level_db, hours, adjustment_db)`; hours positive, finite and summing to 24 | General Formulae (5)-(6); adjustments per Table A.1 | 922 + 923 + Where you put the microphone changes the number: ISO 1996-2 fixes the receiver positions and their façade corrections: 924 + 925 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_env_measurement_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_env_measurement.svg" alt="Environmental noise measurement positions per ISO 1996-2: free field, 2 m from the facade and flush-mounted, with their corrections" width="92%"></picture> 926 + 927 + Combine with `laeq()` per time period to go from recordings to Lden, and with 928 + the `tone_to_noise_ratio()` / `prominence_ratio()` verdicts of 929 + [Prominent Discrete Tones](https://jmrplens.github.io/phonometry/guides/tone-prominence/) to justify tonal adjustments. 930 + 931 + ## Octave Spectrogram (levels over time) 932 + 933 + Short-time fractional-octave analysis: one level per band per window, 934 + time-aligned across bands. 935 + 936 + ```python 937 + from phonometry import OctaveFilterBank 938 + 939 + bank = OctaveFilterBank(fs=48000, fraction=3) 940 + levels, freq, times = bank.spectrogram(recording, window_time=0.125, overlap=0.5) 941 + # levels: (bands, frames) — ready for pcolormesh(times, freq, levels) 942 + ``` 943 + 944 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/spectrogram_example_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/spectrogram_example.png" alt="One-third-octave spectrogram of a logarithmic sweep with two tone bursts" width="80%"></picture> 945 + 946 + *A logarithmic sweep plus two tone bursts, resolved in time and in standardized 947 + 1/3-octave bands.* 948 + 949 + - Multichannel input `(channels, samples)` returns `(channels, bands, frames)`. 950 + - `times` holds each window's center in seconds. 951 + - `mode='peak'` gives per-window peak-holding levels instead of RMS. 952 + - `zero_phase=True` filters bands forward-backward so per-band group delay does 953 + not skew the frames (offline analysis only). 954 + 955 + ### `OctaveFilterBank.spectrogram()` parameters 956 + 957 + | Parameter | Type | Units | Range / default | Notes | 958 + | :--- | :--- | :--- | :--- | :--- | 959 + | `x` | 1D or 2D array | digital units | non-empty | 2D returns `(channels, bands, frames)` | 960 + | `window_time` | float | s | > 0; default `0.125` | Frame length (0.125 s mirrors Fast) | 961 + | `overlap` | float | — | 0 ≤ overlap < 1; default `0.5` | Fraction of window overlap (0 = none) | 962 + | `mode` | str | — | `'rms'` (default) or `'peak'` | Per-window detector | 963 + | `detrend` | bool | — | default `True` | Remove each band's DC offset before the level (improves low-frequency accuracy) | 964 + | `zero_phase` | bool | — | default `False` | Forward-backward filtering (offline only) | 965 + | `calibration_factor` / `dbfs` | — | — | constructor-only | Set on `OctaveFilterBank(...)`, not per call | 886 966 887 - `lex_8h` above turns *one* recording into a daily level. ISO 9612:2009 — the 888 - engineering method (accuracy grade 2) — is the survey design *around* that 967 + ```python 968 + import matplotlib.pyplot as plt 969 + 970 + fig, ax = plt.subplots() 971 + mesh = ax.pcolormesh(times, freq, levels, shading="auto") 972 + ax.set_yscale("log") 973 + ax.set_xlabel("Time [s]") 974 + ax.set_ylabel("Frequency [Hz]") 975 + fig.colorbar(mesh, label="Level [dB]") 976 + ``` 977 + 978 + See [Calibration and dBFS](https://jmrplens.github.io/phonometry/guides/calibration/) to convert digital units to physical 979 + SPL, and [Time Weighting](https://jmrplens.github.io/phonometry/guides/time-weighting/) for the envelope details. The 980 + ISO 9612 occupational strategies continue in 981 + [Occupational Noise Exposure](https://jmrplens.github.io/phonometry/guides/occupational-exposure/), the ECMA-418-1 982 + tonal-prominence verdicts in [Prominent Discrete Tones](https://jmrplens.github.io/phonometry/guides/tone-prominence/), 983 + and the ISO 226 equal-loudness contours live with the perception metrics in 984 + [Psychoacoustics](https://jmrplens.github.io/phonometry/guides/psychoacoustics/). 985 + 986 + --- 987 + 988 + 989 + <!-- source: docs/occupational-exposure.md | canonical: https://jmrplens.github.io/phonometry/guides/occupational-exposure/ --> 990 + 991 + # Occupational Noise Exposure (ISO 9612) 992 + 993 + A working day is rarely measured in one take: the daily exposure level a 994 + regulation acts on has to be assembled from *samples* of a real shift, and 995 + reported with an uncertainty a hygienist can defend. `lex_8h` (in 996 + [Levels](https://jmrplens.github.io/phonometry/guides/levels/)) turns *one* recording into a daily level. ISO 9612:2009 — 997 + the engineering method (accuracy grade 2) — is the survey design *around* that 889 998 primitive: how to sample a real working day, how to combine the pieces, and how 890 999 to attach the normative uncertainty every occupational-hygiene report needs. The 891 1000 `occupational_exposure` module adds the three **measurement strategies** and the 892 1001 **Annex C** uncertainty budget on top of the energy-average machinery. 893 1002 1003 + ## 1. The three measurement strategies (Clauses 9-11) 1004 + 894 1005 The *task-based* strategy (Clause 9) splits the nominal day into tasks, takes 895 1006 $I \ge 3$ samples per task, and energy-sums the task contributions 896 1007 ··· 940 1051 # full-day LEX,8h = 90.1 dB U = 3.4 dB 941 1052 ``` 942 1053 1054 + ## 2. The Annex C uncertainty budget 1055 + 943 1056 Two subtleties are worth spelling out. First, the coverage factor is 944 1057 $k = 1.65$ for a **one-sided** 95 % interval (Clause 14), because a hygienist 945 1058 cares only about the *upper* bound: `res.upper_limit` = $L_{EX,8h} + U$ is the ··· 959 1072 When a task's samples span **3 dB or more** (Clause 9.3), or the job contribution 960 1073 $c_1 u_1$ exceeds 3.5 dB (Clause 10.4), or too few workers are covered 961 1074 (Table 1 cumulative-duration), the result sets `sampling_advisory=True` and, with 962 - `warn=True`, emits an `ExposureWarning` recommending more measurements. Peak 1075 + `warn=True`, emits an `OccupationalExposureWarning` recommending more measurements. Peak 963 1076 levels $L_{p,Cpeak}$ are reported **without** an uncertainty — Annex C gives no 964 1077 method for them (Table C.5, Note 1), so peak-uncertainty is out of scope. The 965 1078 three Annex D/E/F worked examples above are reproduced to the standard's printed ··· 977 1090 | `u3` | all | float | dB | default `1.0` | Microphone-position uncertainty (Clause C.6) | 978 1091 | `include_duration_uncertainty` | task | bool | — | default `True` | `False` omits the $(c_{1b}u_{1b})^2$ term (Annex D case a) | 979 1092 | `n_workers` / `sample_duration_hours` | job | int / float | — / h | default `None` | Table 1 cumulative-duration check | 980 - | `warn` | all | bool | — | default `True` | Emit `ExposureWarning` for the sampling advisories | 1093 + | `warn` | all | bool | — | default `True` | Emit `OccupationalExposureWarning` for the sampling advisories | 981 1094 982 1095 All three return an `ExposureResult` with `lex_8h`, `combined_standard_uncertainty` 983 1096 $u$, `expanded_uncertainty` $U = 1.65\ u$, `upper_limit` = $L_{EX,8h} + U$, 984 1097 `sampling_advisory`, and (task-based) the per-task `tasks` breakdown. 985 1098 986 - ## Loudness level of pure tones (ISO 226:2023) 1099 + ## See also 987 1100 988 - The normal equal-loudness-level contours relate the SPL of a pure tone to its 989 - perceived *loudness level* in phons (the SPL of an equally loud 1 kHz tone). 990 - `equal_loudness_contour(phon)` evaluates ISO 226:2023 Formula (1) at the 29 991 - preferred third-octave frequencies of Table 1, `loudness_level(spl, frequency)` 992 - is the exact inverse (Formula 2), and `hearing_threshold()` returns the 993 - threshold-of-hearing column: 1101 + - [Levels](https://jmrplens.github.io/phonometry/guides/levels/) — the `lex_8h` / `sound_exposure` dose primitives 1102 + (IEC 61252) and the LCpeak these strategies report alongside. 1103 + - [Measurement uncertainty](gum-uncertainty.md) — the GUM machinery behind 1104 + combined and expanded uncertainties. 1105 + - [Theory](https://jmrplens.github.io/phonometry/reference/theory/) — the derivation of the strategy formulas and the 1106 + Annex C budget. 994 1107 995 - ```python 996 - from phonometry import equal_loudness_contour, loudness_level 1108 + --- 997 1109 998 - freqs, spl = equal_loudness_contour(40.0) # the classic 40-phon contour 999 - phon = loudness_level(73.0, 63.0) # 73 dB @ 63 Hz -> 40 phon 1000 - ``` 1110 + **Standards.** ISO 9612:2009, *Acoustics — Determination of occupational noise 1111 + exposure — Engineering method* — the task-based (Clause 9), job-based 1112 + (Clause 10) and full-day (Clause 11) strategies, the Annex C uncertainty budget 1113 + (Formulae C.6, C.9 and C.12, Tables C.4/C.5) and the one-sided coverage factor 1114 + k = 1.65 (Clause 14), validated against the worked examples of Annexes D, E 1115 + and F. 1001 1116 1002 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours.png" alt="ISO 226:2023 normal equal-loudness-level contours from 20 to 90 phon with the hearing threshold curve" width="80%"></picture> 1117 + --- 1003 1118 1004 - Validity per clause 4.1: 20-90 phon (80 phon above 4 kHz); the implementation 1005 - is verified against the Annex B tables in CI. Note this is the loudness of 1006 - *pure tones* - loudness of arbitrary signals (ISO 532 sones) is a different, 1007 - upcoming feature. 1119 + 1120 + <!-- source: docs/tone-prominence.md | canonical: https://jmrplens.github.io/phonometry/guides/tone-prominence/ --> 1008 1121 1009 - ## Prominent discrete tones (ECMA-418-1) 1122 + # Prominent Discrete Tones (ECMA-418-1) 1010 1123 1011 1124 Tonal components in machinery noise are far more annoying than their level 1012 1125 suggests. ECMA-418-1:2024 (referenced by ECMA-74 Annex D) gives two FFT-based ··· 1014 1127 `tone_to_noise_ratio()` compares the tone level with the masking noise in its 1015 1128 critical band (clause 11), and `prominence_ratio()` compares the critical band 1016 1129 centred on the tone with the two contiguous bands (clause 12). Both return a 1017 - structured verdict against the frequency-dependent prominence criteria: 1130 + structured verdict against the frequency-dependent prominence criteria. 1131 + 1132 + ## 1. Tone-to-noise ratio and prominence ratio 1018 1133 1019 1134 ```python 1020 1135 import numpy as np ··· 1029 1144 print(tnr.ratio_db, tnr.criterion_db, tnr.prominent) 1030 1145 ``` 1031 1146 1032 - 1033 1147 The methods hinge on the **critical band** — the ear's analysis bandwidth, 1034 1148 $\Delta f_c = 25 + 75\ [1 + 1.4(f/1000)^2]^{0.69}$ Hz (162 Hz at 1 kHz): a 1035 1149 tone is masked only by the noise *inside* its critical band, so both ratios ··· 1041 1155 the tone as *prominent*; the PR criterion is $9 + 10\log_{10}(1000/f_t)$ dB. 1042 1156 Low frequencies get higher thresholds because wider relative bands mask more. 1043 1157 1158 + ## 2. Where to measure (ECMA-74) and practice 1159 + 1044 1160 ECMA-74 (which delegates its tone assessments to ECMA-418-1) also fixes where to measure around a device: 1045 1161 1046 1162 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions.svg" alt="ECMA-74 emission measurement positions: seated operator microphone at 0.25 m and 1.20 m, and the four bystander positions at 1 m" width="92%"></picture> ··· 1061 1177 1062 1178 Both return a `ToneAssessment(frequency, ratio_db, criterion_db, prominent)`. 1063 1179 1064 - ## Environmental noise: Lden, Ldn and rating levels (ISO 1996-1) 1065 - 1066 - Regulatory noise assessment weights evenings and nights more heavily. 1067 - `lden()` implements the day-evening-night level of ISO 1996-1:2016 (3.6.4: 1068 - +5 dB evening, +10 dB night, default 12/4/8 h periods — adjustable, since 1069 - countries define them differently), `ldn()` the day-night variant (3.6.5), 1070 - and `composite_rating_level()` the general whole-day composite of clause 6.5 1071 - (Formulae 5-6) for arbitrary periods with source or character adjustments 1072 - (Table A.1: e.g. +5 dB regular impulsive, +12 dB highly impulsive, +3 to 1073 - +6 dB prominent tones): 1074 - 1075 - ```python 1076 - from phonometry import lden, composite_rating_level 1077 - 1078 - l = lden(63.2, 58.1, 51.4) # from LAeq per period 1079 - r = composite_rating_level([(63.2, 12, 0.0), # day 1080 - (58.1, 4, 5.0), # evening (+5) 1081 - (51.4, 8, 10.0)]) # night (+10) == lden 1082 - ``` 1083 - 1084 - 1085 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/lden_profile_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/lden_profile.png" alt="Synthetic 24-hour urban LAeq profile with day, evening and night bands, the +5 and +10 dB weighted period levels and the resulting Lden" width="80%"></picture> 1086 - 1087 - ### `lden()` / `ldn()` / `composite_rating_level()` parameters 1088 - 1089 - | Function | Key parameters | Notes | 1090 - | :--- | :--- | :--- | 1091 - | `lden(lday, levening, lnight, hours=(12, 4, 8))` | period LAeq values [dB]; `hours` must sum to 24 | +5 dB evening, +10 dB night (3.6.4) | 1092 - | `ldn(lday, lnight, hours=(15, 9))` | | +10 dB night (3.6.5) | 1093 - | `composite_rating_level(periods)` | iterable of `(level_db, hours, adjustment_db)`; hours positive, finite and summing to 24 | General Formulae (5)-(6); adjustments per Table A.1 | 1094 - 1095 - Where you put the microphone changes the number: ISO 1996-2 fixes the receiver positions and their façade corrections: 1180 + ## See also 1096 1181 1097 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_env_measurement_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_env_measurement.svg" alt="Environmental noise measurement positions per ISO 1996-2: free field, 2 m from the facade and flush-mounted, with their corrections" width="92%"></picture> 1182 + - [Levels](https://jmrplens.github.io/phonometry/guides/levels/) — the ISO 1996-1 rating levels whose tonal adjustments 1183 + (Table A.1) these prominence verdicts justify objectively. 1184 + - [Psychoacoustics](https://jmrplens.github.io/phonometry/guides/psychoacoustics/) — the ECMA-418-2 psychoacoustic 1185 + tonality T in tu_HMS, the hearing-model counterpart of these FFT ratios. 1186 + - [Impulsive-sound prominence](impulse-prominence.md) — the NT ACOU 112 1187 + counterpart for impulsive (rather than tonal) character. 1188 + - [Theory](https://jmrplens.github.io/phonometry/reference/theory/) — the critical-band model and criteria derivation. 1098 1189 1099 - Combine with `laeq()` per time period to go from recordings to Lden, and with 1100 - `tone_to_noise_ratio()` / `prominence_ratio()` to justify tonal adjustments. 1190 + --- 1101 1191 1102 - ## Octave Spectrogram (levels over time) 1103 - 1104 - Short-time fractional-octave analysis: one level per band per window, 1105 - time-aligned across bands. 1106 - 1107 - ```python 1108 - from phonometry import OctaveFilterBank 1109 - 1110 - bank = OctaveFilterBank(fs=48000, fraction=3) 1111 - levels, freq, times = bank.spectrogram(recording, window_time=0.125, overlap=0.5) 1112 - # levels: (bands, frames) — ready for pcolormesh(times, freq, levels) 1113 - ``` 1114 - 1115 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/spectrogram_example_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/spectrogram_example.png" alt="One-third-octave spectrogram of a logarithmic sweep with two tone bursts" width="80%"></picture> 1116 - 1117 - *A logarithmic sweep plus two tone bursts, resolved in time and in standardized 1118 - 1/3-octave bands.* 1119 - 1120 - - Multichannel input `(channels, samples)` returns `(channels, bands, frames)`. 1121 - - `times` holds each window's center in seconds. 1122 - - `mode='peak'` gives per-window peak-holding levels instead of RMS. 1123 - - `zero_phase=True` filters bands forward-backward so per-band group delay does 1124 - not skew the frames (offline analysis only). 1125 - 1126 - ### `OctaveFilterBank.spectrogram()` parameters 1127 - 1128 - | Parameter | Type | Units | Range / default | Notes | 1129 - | :--- | :--- | :--- | :--- | :--- | 1130 - | `x` | 1D or 2D array | digital units | non-empty | 2D returns `(channels, bands, frames)` | 1131 - | `window_time` | float | s | > 0; default `0.125` | Frame length (0.125 s mirrors Fast) | 1132 - | `overlap` | float | — | 0 ≤ overlap < 1; default `0.5` | Fraction of window overlap (0 = none) | 1133 - | `mode` | str | — | `'rms'` (default) or `'peak'` | Per-window detector | 1134 - | `detrend` | bool | — | default `True` | Remove each band's DC offset before the level (improves low-frequency accuracy) | 1135 - | `zero_phase` | bool | — | default `False` | Forward-backward filtering (offline only) | 1136 - | `calibration_factor` / `dbfs` | — | — | constructor-only | Set on `OctaveFilterBank(...)`, not per call | 1137 - 1138 - ```python 1139 - import matplotlib.pyplot as plt 1140 - 1141 - fig, ax = plt.subplots() 1142 - mesh = ax.pcolormesh(times, freq, levels, shading="auto") 1143 - ax.set_yscale("log") 1144 - ax.set_xlabel("Time [s]") 1145 - ax.set_ylabel("Frequency [Hz]") 1146 - fig.colorbar(mesh, label="Level [dB]") 1147 - ``` 1148 - 1149 - See [Calibration and dBFS](https://jmrplens.github.io/phonometry/guides/calibration/) to convert digital units to physical 1150 - SPL, and [Time Weighting](https://jmrplens.github.io/phonometry/guides/time-weighting/) for the envelope details. 1192 + **Standards.** ECMA-418-1:2024 (3rd edition), *Psychoacoustic metrics for ITT 1193 + equipment — Part 1: Prominent discrete tones* — the tone-to-noise ratio 1194 + (clause 11), the prominence ratio (clause 12), the critical-band model and the 1195 + frequency-dependent prominence criteria; ECMA-74 Annex D — the emission 1196 + measurement positions, delegating the tone assessment to ECMA-418-1. 1151 1197 1152 1198 --- 1153 1199 1154 1200 1155 1201 <!-- source: docs/psychoacoustics.md | canonical: https://jmrplens.github.io/phonometry/guides/psychoacoustics/ --> 1156 1202 1157 - # Psychoacoustics and Speech Intelligibility 1203 + # Psychoacoustics 1158 1204 1159 1205 Level metrics tell you how much *sound pressure* there is; psychoacoustic 1160 1206 metrics tell you what a listener actually *perceives*. This page covers 1161 - loudness (ISO 532-1), sharpness (DIN 45692) and the speech transmission 1162 - index (IEC 60268-16), then the advanced Moore-Glasberg (ISO 532-2/3) and 1163 - Sottek Hearing Model (ECMA-418-2) loudness, tonality and roughness models. 1207 + loudness (ISO 532-1), sharpness (DIN 45692) and the equal-loudness 1208 + contours of pure tones (ISO 226), then the advanced Moore-Glasberg 1209 + (ISO 532-2/3) and Sottek Hearing Model (ECMA-418-2) loudness, tonality and 1210 + roughness models. Speech metrics live in their own guides: the 1211 + transmission-channel STI/STIPA in 1212 + [Speech Transmission Index](https://jmrplens.github.io/phonometry/guides/speech-transmission/) and the 1213 + audibility-based SII in 1214 + [Speech Intelligibility Index](speech-intelligibility.md). 1164 1215 1165 1216 ## Loudness in sones (ISO 532-1, Zwicker) 1166 1217 ··· 1247 1298 | `fs` | int | Hz | > 0 | | 1248 1299 | `field` | str | — | `'free'` (default) / `'diffuse'` | Sound-field correction (Table A.5) | 1249 1300 | `stationary` | bool | — | default `False` | `True`: single N from the averaged spectrum | 1250 - | `calibration_factor` | float | Pa per digital unit | default `1.0` | From `calculate_sensitivity()` | 1301 + | `calibration_factor` | float | Pa per digital unit | default `1.0` | From `sensitivity()` | 1251 1302 1252 1303 Returns a `ZwickerLoudness` dataclass: `loudness` (N, sones), `loudness_level` 1253 1304 (phon), `specific` (N′(z), 240 bins of 0.1 Bark), and for time-varying runs ··· 1280 1331 CI verifies the Table A.2 target values (0.38 acum at 250 Hz up to 1281 1332 2.82 acum at 4 kHz) within the standard's 5 % / 0.05 acum tolerance. 1282 1333 1283 - ## Speech Transmission Index (IEC 60268-16) 1284 - 1285 - Reverberation and noise do not muffle speech uniformly — they blur its 1286 - *envelope*: the slow (0.63–12.5 Hz) intensity modulations that carry 1287 - syllables. STI quantifies how much of that modulation survives from mouth 1288 - to ear, per octave band, as the **modulation transfer function** m(F). A 1289 - delta-like channel keeps m = 1 (STI = 1); reverberation low-passes the 1290 - envelope following Schroeder's closed form, and steady noise scales it: 1291 - 1292 - $$ 1293 - m(F) = \frac{1}{\sqrt{1 + \left(2\pi F\ \frac{T_{60}}{13.8}\right)^2}} 1294 - \cdot \frac{1}{1 + 10^{-\mathrm{SNR}/10}} 1295 - $$ 1296 - 1297 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60.png" alt="STI versus reverberation time with the IEC 60268-16 Annex F rating bands shaded" width="80%"></picture> 1334 + ## Loudness level of pure tones (ISO 226:2023) 1298 1335 1299 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain.svg" alt="STI measurement chain: STIPA source signal through the room to the microphone and the MTF analysis" width="92%"></picture> 1336 + The normal equal-loudness-level contours relate the SPL of a pure tone to its 1337 + perceived *loudness level* in phons (the SPL of an equally loud 1 kHz tone). 1338 + `equal_loudness_contour(phon)` evaluates ISO 226:2023 Formula (1) at the 29 1339 + preferred third-octave frequencies of Table 1, `loudness_level(spl, frequency)` 1340 + is the exact inverse (Formula 2), and `hearing_threshold()` returns the 1341 + threshold-of-hearing column: 1300 1342 1301 1343 ```python 1302 - import numpy as np 1303 - from phonometry import sti_from_impulse_response, stipa, stipa_signal 1344 + from phonometry import equal_loudness_contour, loudness_level 1304 1345 1305 - fs = 48000 1306 - # A measured room impulse response (synthesized decay so the example runs) 1307 - ir = np.random.default_rng(0).standard_normal(fs) * np.exp(-6.9 * np.arange(fs) / fs / 0.5) 1308 - 1309 - # Indirect method: from a measured room impulse response 1310 - res = sti_from_impulse_response(ir, fs, snr=25.0) 1311 - print(f"STI = {res.sti:.2f} ({res.rating})") # e.g. 0.62 (D) 1312 - 1313 - # Direct STIPA measurement: play stipa_signal() in the room, record it 1314 - test = stipa_signal(fs, seconds=18.0, level_db=80.0) 1315 - recording = test # in practice, the microphone signal after playback 1316 - res = stipa(recording, fs) 1317 - res.plot() # per-band modulation transfer index (MTI) bars, STI + rating in the title 1346 + freqs, spl = equal_loudness_contour(40.0) # the classic 40-phon contour 1347 + phon = loudness_level(73.0, 63.0) # 73 dB @ 63 Hz -> 40 phon 1318 1348 ``` 1319 1349 1320 - <details> 1321 - <summary>Show the code for this figure</summary> 1350 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours.png" alt="ISO 226:2023 normal equal-loudness-level contours from 20 to 90 phon with the hearing threshold curve" width="80%"></picture> 1322 1351 1323 - ```python 1324 - import matplotlib.pyplot as plt 1325 - 1326 - # STI vs reverberation time: sweep sti_from_impulse_response over synthetic 1327 - # exponential decays (white noise x exp(-6.9077 t / T60)) at a T60 grid — 1328 - # exactly the physics behind the curve above: 1329 - rng = np.random.default_rng(0) 1330 - t60_grid = np.array([0.3, 0.5, 0.8, 1.2, 1.6, 2.0, 2.5, 3.0, 4.0, 5.0]) 1331 - sti_values = [] 1332 - for t60 in t60_grid: 1333 - t = np.arange(int(2 * t60 * fs)) / fs 1334 - ir = rng.standard_normal(t.size) * np.exp(-6.9077 * t / t60) 1335 - sti_values.append(sti_from_impulse_response(ir, fs).sti) 1336 - 1337 - fig, ax = plt.subplots() 1338 - ax.semilogx(t60_grid, sti_values, "o-") 1339 - ax.set_xlabel("Reverberation time T60 [s]") 1340 - ax.set_ylabel("STI") 1341 - ax.set_ylim(0.0, 1.0) 1342 - ax.grid(True, which="both", alpha=0.3) 1343 - plt.show() 1344 - ``` 1345 - 1346 - </details> 1347 - 1348 - `stipa` emits a `UserWarning` when the recording is shorter than the 1349 - recommended 15 s (IEC 60268-16 STIPA practice, 15 s to 25 s): below that the 1350 - slow modulation components are averaged over too few periods and the STI is 1351 - biased low (an ideal loopback gives STI ≈ 0.944 at 5 s vs ≈ 0.998 at 18 s). 1352 - 1353 - The implementation follows **Edition 5 (2020)**: Edition 4's normative PDF 1354 - is the base and every Ed. 5 change is source-attributed in the code — the 1355 - only numeric delta is the revised male speech spectrum of clause A.6.1. 1356 - CI checks the standard's own verification vectors: the six weighting-factor 1357 - band pairs to ±0.001 STI, the m ↔ STI mapping table, the level-dependent 1358 - masking control points, and Schroeder-form decays at four T₆₀ values. 1359 - 1360 - ### `sti_from_impulse_response()` / `stipa()` parameters 1361 - 1362 - | Parameter | Type | Units | Range / default | Notes | 1363 - | :--- | :--- | :--- | :--- | :--- | 1364 - | `ir` / `x` | 1D array | any / Pa | non-empty | IR (indirect) or STIPA recording (direct) | 1365 - | `fs` | int | Hz | > 0 | | 1366 - | `snr` | float or 7-vector, optional | dB | default `None` | Adds steady-noise degradation | 1367 - | `level` | 7-vector, optional | dB SPL | default `None` | Enables auditory masking + reception threshold (Tables A.2/A.3) | 1368 - | `ambient` | 7-vector, optional | dB SPL | needs `level` | Ambient noise band levels | 1369 - | `reference` | 1D array, optional (`stipa`) | — | default `None` | Measured source signal instead of the nominal m = 0.55 | 1370 - 1371 - Both return `STIResult`: `sti`, `mti` (7 bands), `mtf` (7×14 or 7×2), 1372 - `band_levels`, `rating` (Annex F letter `A+`…`U`). 1352 + Validity per clause 4.1: 20-90 phon (80 phon above 4 kHz); the implementation 1353 + is verified against the Annex B tables in CI. Note this is the loudness of 1354 + *pure tones* — the loudness of arbitrary signals in sones is what the ISO 532 1355 + models on this page compute. 1373 1356 1374 1357 ## Advanced loudness & sound-quality models 1375 1358 ··· 1706 1689 `time`, `roughness_vs_time` (R(l50)), `specific_roughness_vs_time` 1707 1690 ((n_times, 53) array), `field`. 1708 1691 1709 - See [Levels](https://jmrplens.github.io/phonometry/guides/levels/) for tonality metrics and [Theory](https://jmrplens.github.io/phonometry/reference/theory/) for the 1710 - underlying math. 1692 + See [Prominent Discrete Tones](https://jmrplens.github.io/phonometry/guides/tone-prominence/) for the ECMA-418-1 TNR/PR 1693 + prominence verdicts, [Speech Transmission Index](https://jmrplens.github.io/phonometry/guides/speech-transmission/) for 1694 + STI/STIPA, and [Theory](https://jmrplens.github.io/phonometry/reference/theory/) for the underlying math. 1695 + 1696 + --- 1697 + 1698 + 1699 + <!-- source: docs/speech-transmission.md | canonical: https://jmrplens.github.io/phonometry/guides/speech-transmission/ --> 1700 + 1701 + # Speech Transmission Index (IEC 60268-16) 1702 + 1703 + A public-address system, an intercom, a reverberant lecture hall — each is a 1704 + *transmission channel* between a talker's mouth and a listener's ear, and each 1705 + degrades speech in its own way. The **Speech Transmission Index** (STI) of 1706 + IEC 60268-16 rates that channel with a single number in [0, 1] by measuring 1707 + how much of the speech *envelope* survives the trip. This page covers the 1708 + modulation-transfer physics behind the index, the indirect method from a 1709 + measured room impulse response, and the direct STIPA measurement with its 1710 + standardized test signal. 1711 + 1712 + > [!NOTE] 1713 + > **STI vs SII.** The STI characterises a *transmission channel* — how much of 1714 + > the speech modulation a room or sound system preserves — while the SII 1715 + > predicts intelligibility from *audibility*: how much of the speech spectrum 1716 + > clears the noise and the hearing threshold at the listener's ear. For the 1717 + > latter, see the [Speech Intelligibility Index guide](speech-intelligibility.md). 1718 + 1719 + ## 1. The modulation transfer function 1720 + 1721 + Reverberation and noise do not muffle speech uniformly — they blur its 1722 + *envelope*: the slow (0.63–12.5 Hz) intensity modulations that carry 1723 + syllables. STI quantifies how much of that modulation survives from mouth 1724 + to ear, per octave band, as the **modulation transfer function** m(F). A 1725 + delta-like channel keeps m = 1 (STI = 1); reverberation low-passes the 1726 + envelope following Schroeder's closed form, and steady noise scales it: 1727 + 1728 + $$ 1729 + m(F) = \frac{1}{\sqrt{1 + \left(2\pi F\,\frac{T_{60}}{13.8}\right)^2}} 1730 + \cdot \frac{1}{1 + 10^{-\mathrm{SNR}/10}} 1731 + $$ 1732 + 1733 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60.png" alt="STI versus reverberation time with the IEC 60268-16 Annex F rating bands shaded" width="80%"></picture> 1734 + 1735 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain.svg" alt="STI measurement chain: STIPA source signal through the room to the microphone and the MTF analysis" width="92%"></picture> 1736 + 1737 + ## 2. Indirect and direct (STIPA) measurement 1738 + 1739 + ```python 1740 + import numpy as np 1741 + from phonometry import sti_from_impulse_response, stipa, stipa_signal 1742 + 1743 + fs = 48000 1744 + # A measured room impulse response (synthesized decay so the example runs) 1745 + ir = np.random.default_rng(0).standard_normal(fs) * np.exp(-6.9 * np.arange(fs) / fs / 0.5) 1746 + 1747 + # Indirect method: from a measured room impulse response 1748 + res = sti_from_impulse_response(ir, fs, snr=25.0) 1749 + print(f"STI = {res.sti:.2f} ({res.rating})") # e.g. 0.62 (D) 1750 + 1751 + # Direct STIPA measurement: play stipa_signal() in the room, record it 1752 + test = stipa_signal(fs, seconds=18.0, level_db=80.0) 1753 + recording = test # in practice, the microphone signal after playback 1754 + res = stipa(recording, fs) 1755 + res.plot() # per-band modulation transfer index (MTI) bars, STI + rating in the title 1756 + ``` 1757 + 1758 + <details> 1759 + <summary>Show the code for this figure</summary> 1760 + 1761 + ```python 1762 + import matplotlib.pyplot as plt 1763 + 1764 + # STI vs reverberation time: sweep sti_from_impulse_response over synthetic 1765 + # exponential decays (white noise x exp(-6.9077 t / T60)) at a T60 grid — 1766 + # exactly the physics behind the curve above: 1767 + rng = np.random.default_rng(0) 1768 + t60_grid = np.array([0.3, 0.5, 0.8, 1.2, 1.6, 2.0, 2.5, 3.0, 4.0, 5.0]) 1769 + sti_values = [] 1770 + for t60 in t60_grid: 1771 + t = np.arange(int(2 * t60 * fs)) / fs 1772 + ir = rng.standard_normal(t.size) * np.exp(-6.9077 * t / t60) 1773 + sti_values.append(sti_from_impulse_response(ir, fs).sti) 1774 + 1775 + fig, ax = plt.subplots() 1776 + ax.semilogx(t60_grid, sti_values, "o-") 1777 + ax.set_xlabel("Reverberation time T60 [s]") 1778 + ax.set_ylabel("STI") 1779 + ax.set_ylim(0.0, 1.0) 1780 + ax.grid(True, which="both", alpha=0.3) 1781 + plt.show() 1782 + ``` 1783 + 1784 + </details> 1785 + 1786 + `stipa` emits a `UserWarning` when the recording is shorter than the 1787 + recommended 15 s (IEC 60268-16 STIPA practice, 15 s to 25 s): below that the 1788 + slow modulation components are averaged over too few periods and the STI is 1789 + biased low (an ideal loopback gives STI ≈ 0.944 at 5 s vs ≈ 0.998 at 18 s). 1790 + 1791 + The implementation follows **Edition 5 (2020)**: Edition 4's normative PDF 1792 + is the base and every Ed. 5 change is source-attributed in the code — the 1793 + only numeric delta is the revised male speech spectrum of clause A.6.1. 1794 + CI checks the standard's own verification vectors: the six weighting-factor 1795 + band pairs to ±0.001 STI, the m ↔ STI mapping table, the level-dependent 1796 + masking control points, and Schroeder-form decays at four T₆₀ values. 1797 + 1798 + ### `sti_from_impulse_response()` / `stipa()` parameters 1799 + 1800 + | Parameter | Type | Units | Range / default | Notes | 1801 + | :--- | :--- | :--- | :--- | :--- | 1802 + | `ir` / `x` | 1D array | any / Pa | non-empty | IR (indirect) or STIPA recording (direct) | 1803 + | `fs` | int | Hz | > 0 | | 1804 + | `snr` | float or 7-vector, optional | dB | default `None` | Adds steady-noise degradation | 1805 + | `level` | 7-vector, optional | dB SPL | default `None` | Enables auditory masking + reception threshold (Tables A.2/A.3) | 1806 + | `ambient` | 7-vector, optional | dB SPL | needs `level` | Ambient noise band levels | 1807 + | `reference` | 1D array, optional (`stipa`) | — | default `None` | Measured source signal instead of the nominal m = 0.55 | 1808 + 1809 + Both return `STIResult`: `sti`, `mti` (7 bands), `mtf` (7×14 or 7×2), 1810 + `band_levels`, `rating` (Annex F letter `A+`…`U`). 1811 + 1812 + ## See also 1813 + 1814 + - [Room Acoustics](https://jmrplens.github.io/phonometry/guides/room-acoustics/) — the measured impulse response the 1815 + indirect method consumes, and the open-plan metrics (ISO 3382-3) built on 1816 + per-position STI. 1817 + - [Speech Intelligibility Index](speech-intelligibility.md) — the 1818 + audibility-based ANSI S3.5 index that complements the STI. 1819 + - [Psychoacoustics](https://jmrplens.github.io/phonometry/guides/psychoacoustics/) — loudness, sharpness, tonality and 1820 + roughness of the received sound. 1821 + - [Theory](https://jmrplens.github.io/phonometry/reference/theory/) — the modulation-transfer derivation and the m ↔ STI 1822 + mapping. 1823 + 1824 + --- 1825 + 1826 + **Standards.** IEC 60268-16:2020 (Edition 5), *Sound system equipment — 1827 + Part 16: Objective rating of speech intelligibility by speech transmission 1828 + index* — the modulation transfer function and the m ↔ STI mapping, the STIPA 1829 + test signal and direct method, the indirect method from the impulse response, 1830 + auditory masking and the reception threshold (Tables A.2/A.3), the revised 1831 + male speech spectrum (clause A.6.1) and the Annex F rating letters. 1711 1832 1712 1833 --- 1713 1834 ··· 1820 1941 are available directly: 1821 1942 1822 1943 ```python 1944 + import numpy as np 1823 1945 from phonometry import field_indicators, dynamic_capability_index 1824 1946 1825 1947 # Per-position measurements over the ISO 9614-1 measurement surface 1826 1948 pressure_levels = np.array([74.1, 73.8, 74.5, 73.2]) # Lp per position (dB) 1827 1949 normal_intensity = np.array([1.2e-5, 1.0e-5, 1.4e-5, 0.9e-5]) # signed In per position (W/m²) 1828 1950 1829 - fi = field_indicators(pressure_levels, normal_intensity) # F2, F3, F4 1951 + fi = field_indicators(pressure_levels, normal_intensity) 1952 + print(round(fi.f2, 2), round(fi.f3, 2), round(fi.f4, 3)) # 3.41 3.41 0.197 1830 1953 ld = dynamic_capability_index(18.0) # δpI0 = 18 dB → Ld = δpI0 − K 1831 - ok = ld > fi.f2 # criterion 1 1954 + print(ld, ld > fi.f2) # 8.0 True (criterion 1) 1832 1955 ``` 1833 1956 1834 1957 ### `sound_intensity()` parameters ··· 1852 1975 1853 1976 <!-- source: docs/room-acoustics.md | canonical: https://jmrplens.github.io/phonometry/guides/room-acoustics/ --> 1854 1977 1855 - # Room and Building Acoustics 1978 + # Room Acoustics 1856 1979 1857 - Room and building acoustics start from one measurement: the **impulse 1858 - response** (IR) between a source and a receiver. Filter it into bands and 1859 - integrate it, and it yields reverberation time, clarity and speech 1860 - intelligibility; measure it either side of a wall and it yields the sound 1861 - insulation of the partition. This page follows that chain in measurement 1862 - order — acquiring the IR (ISO 18233), turning it into room parameters 1863 - (ISO 3382-1/2), spatial speech metrics for open-plan offices 1864 - (ISO 3382-3), field airborne, impact and façade insulation with 1865 - single-number ratings (ISO 16283-1/2/3, ISO 717-1/2), the laboratory 1866 - characterisation of a building element (ISO 10140), the prediction of 1867 - in-situ performance from flanking transmission (EN 12354-1/2), the 1868 - measurement uncertainty that qualifies every rating (ISO 12999-1) and, 1869 - closing the loop, the sound absorption of a material in a reverberation 1870 - room (ISO 354). 1980 + Room acoustics starts from one measurement: the **impulse response** (IR) 1981 + between a source and a receiver. Filter it into bands and integrate it, and 1982 + it yields reverberation time, clarity and speech intelligibility — everything 1983 + about the sound field inside a single room. This page follows that chain in 1984 + measurement order — acquiring the IR (ISO 18233), turning it into room 1985 + parameters (ISO 3382-1/2), spatial speech metrics for open-plan offices 1986 + (ISO 3382-3) and, closing the loop, the sound absorption of a material in a 1987 + reverberation room (ISO 354). For sound insulation *between* spaces — the same 1988 + IR measured either side of a partition — see the companion 1989 + [Building Acoustics & Sound Insulation guide](https://jmrplens.github.io/phonometry/guides/building-acoustics/). 1871 1990 1872 1991 ## 1. Impulse-response acquisition (ISO 18233) 1873 1992 ··· 1939 2058 print(int(np.argmax(np.abs(ir_m)))) # 100 1940 2059 ``` 1941 2060 2061 + `sweep_signal`/`mls_signal` return plain arrays, ready to write to a WAV file 2062 + and play. `impulse_response`/`mls_impulse_response` return an 2063 + `ImpulseResponseResult` — a drop-in for the raw IR array (`np.asarray(ir)`, 2064 + indexing and `ir.size` all keep working, so `room_parameters(ir, fs)` is 2065 + unchanged) that also carries the sample rate and method and adds an `.plot()`. 2066 + 2067 + **The two excitations.** The exponential sweep sweeps its energy up the 2068 + spectrum over the whole signal, while the MLS is a flat-spectrum two-level 2069 + sequence — visible as the near-constant magnitude on the right. 2070 + 2071 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/excitation_signals_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/excitation_signals.png" alt="ISO 18233 excitation signals: the exponential sine sweep waveform and its spectrogram showing the exponential frequency rise, and a maximum-length sequence with its flat magnitude spectrum" width="96%"></picture> 2072 + 2073 + <details> 2074 + <summary>Show the code for this figure</summary> 2075 + 2076 + ```python 2077 + from phonometry import sweep_signal, mls_signal, plot_excitation 2078 + 2079 + fs = 48000 2080 + sweep = sweep_signal(fs, 50.0, 20000.0, 1.0) # ESS excitation 2081 + mls = mls_signal(12).astype(float) # length 2**12 - 1 2082 + 2083 + # One-liner: waveform + spectrogram (sweep), sequence + flat spectrum (MLS) 2084 + plot_excitation(sweep, fs, kind="sweep") 2085 + plot_excitation(mls, fs, kind="mls") 2086 + 2087 + # By hand: the sweep spectrogram and the MLS magnitude spectrum 2088 + import numpy as np 2089 + import matplotlib.pyplot as plt 2090 + 2091 + fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4)) 2092 + ax1.specgram(sweep, NFFT=1024, Fs=fs, noverlap=512) 2093 + ax1.set(xlabel="Time [s]", ylabel="Frequency [Hz]", title="Sweep spectrogram") 2094 + spec = np.abs(np.fft.rfft(mls)) 2095 + freqs = np.fft.rfftfreq(mls.size, d=1.0 / fs) 2096 + ax2.semilogx(freqs[1:], 20 * np.log10(spec[1:] / np.median(spec[1:]))) 2097 + ax2.set(xlabel="Frequency [Hz]", ylabel="Magnitude [dB]", title="MLS spectrum (flat)") 2098 + ``` 2099 + 2100 + </details> 2101 + 2102 + **The recovered impulse response.** Deconvolving the recording gives the 2103 + broadband IR: the direct sound, discrete early reflections and the decaying 2104 + diffuse tail. Its `.plot()` shows the waveform above and the log-magnitude 2105 + envelope with the Schroeder energy-decay curve below — the straight decay 2106 + whose slope becomes the reverberation time in §2. 2107 + 2108 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/impulse_response_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/impulse_response.png" alt="Recovered room impulse response: the normalized waveform with the direct sound and reflections labelled, and below it the log-magnitude envelope in dB with the Schroeder energy-decay curve" width="88%"></picture> 2109 + 2110 + <details> 2111 + <summary>Show the code for this figure</summary> 2112 + 2113 + ```python 2114 + import numpy as np 2115 + from scipy.signal import fftconvolve 2116 + from phonometry import sweep_signal, impulse_response 2117 + 2118 + fs = 48000 2119 + sweep = sweep_signal(fs, 20.0, 20000.0, 1.5) 2120 + # A synthetic room: direct sound + two reflections + a decaying diffuse tail 2121 + system = np.zeros(int(0.7 * fs)) 2122 + system[80], system[1400], system[3100] = 1.0, 0.5, 0.32 2123 + ir = impulse_response(fftconvolve(sweep, system), sweep, fs, length=system.size) 2124 + 2125 + # One-liner: waveform + log-magnitude / Schroeder decay 2126 + ir.plot() 2127 + 2128 + # By hand: the normalized log-magnitude envelope in dB 2129 + import matplotlib.pyplot as plt 2130 + h = np.asarray(ir) 2131 + t = np.arange(h.size) / fs 2132 + plt.plot(t, 20 * np.log10(np.abs(h) / np.max(np.abs(h)))) 2133 + plt.ylim(-80, 5) 2134 + plt.xlabel("Time [s]"); plt.ylabel("Level re peak [dB]") 2135 + ``` 2136 + 2137 + </details> 2138 + 2139 + **Where to measure.** One IR characterises a single source–receiver pair; a 2140 + reported room parameter is the spatial average over several. ISO 3382-1 2141 + (performance spaces) asks for at least two source positions and microphones 2142 + spaced $\geq 2$ m apart, $\geq 1$ m from any surface, at $1.2$ m 2143 + (seated-ear) height, avoiding symmetric placements; ISO 3382-2 fixes the 2144 + minimum number of source, microphone and source–microphone combinations per 2145 + accuracy grade (survey / engineering / precision). 2146 + 2147 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_room_measurement_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_room_measurement.svg" alt="Room-acoustics measurement setup: a top-view room plan with two loudspeaker source positions and six microphone positions with the ISO 3382-1 spacing rules, and the ISO 3382-2 table of minimum positions for the survey, engineering and precision grades" width="94%"></picture> 2148 + 1942 2149 ### `sweep_signal()` / `inverse_filter()` parameters 1943 2150 1944 2151 | Parameter | Type | Units | Range / default | Notes | ··· 2166 2373 `d2s`/`lp_as_4m` are `nan` if fewer than two positions fall in 2–16 m; 2167 2374 `rd`/`rp` are `nan` when STI does not decrease with distance. The per-position 2168 2375 STI can itself be measured with the STIPA tools in the 2169 - [Psychoacoustics guide](https://jmrplens.github.io/phonometry/guides/psychoacoustics/). 2376 + [Speech Transmission Index guide](https://jmrplens.github.io/phonometry/guides/speech-transmission/). 2377 + 2378 + ## 4. Sound absorption (ISO 354) 2379 + 2380 + The equivalent absorption area `A` that drives `R'`, `L'n`, the ISO 3744 `K2` 2381 + environmental correction and the ISO 3741 absorption term is itself measured in 2382 + a reverberation room (ISO 354). 2383 + Measure the room's reverberation time **empty** ($T_1$) and again **with the 2384 + test specimen installed** ($T_2$); the specimen's absorption is the difference 2385 + of the two Sabine areas, and dividing by the covered area gives the absorption 2386 + coefficient: 2170 2387 2171 - ## 4. Field insulation and single-number ratings (ISO 16283-1, ISO 717-1) 2388 + $$ 2389 + A = \frac{55.3\ V}{c\ T} - 4 V m, \qquad 2390 + \alpha_s = \frac{A_2 - A_1}{S}, \qquad c = 331 + 0.6\ t , 2391 + $$ 2392 + 2393 + with $c$ from the room air temperature $t$ in °C (valid 15–30 °C) and $m$ the 2394 + power attenuation coefficient of air (default 0; convert an ISO 9613-1 2395 + $\alpha$ in dB/m with `attenuation_from_alpha`). Because edge and diffraction 2396 + effects can scatter more energy than the sample's flat area intercepts, 2397 + $\alpha_s$ may exceed 1.0 and is never clamped (ISO 354 Clause 3.7). 2398 + 2399 + ```python 2400 + import numpy as np 2401 + from phonometry import absorption_area, absorption_coefficient 2402 + 2403 + # Third-octave reverberation times of a 200 m^3 room, empty (T1) and with a 2404 + # 10.8 m^2 absorber sample installed (T2). 2405 + t1 = np.array([5.0, 4.0, 3.0]) 2406 + t2 = np.array([3.0, 2.5, 2.0]) 2407 + 2408 + a_empty = absorption_area(t1, volume=200.0, temperature=20.0) 2409 + print(np.round(a_empty, 2)) # [ 6.45 8.06 10.75] m^2 2410 + 2411 + alpha = absorption_coefficient(t1, t2, volume=200.0, sample_area=10.8, 2412 + temperature1=20.0) 2413 + print(np.round(alpha, 3)) # [0.398 0.448 0.498] 2414 + ``` 2415 + 2416 + `T1` and `T2` are exactly the reverberation times `room_parameters` returns, so 2417 + an ISO 3382-2 decay measurement of the empty and treated room flows straight 2418 + into `absorption_coefficient`. A room volume below the 150 m³ minimum or a 2419 + sample area outside 10–12 m² raises an advisory `AbsorptionWarning`; the result 2420 + still returns. 2421 + 2422 + ### `absorption_area()` / `absorption_coefficient()` parameters 2423 + 2424 + | Parameter | Type | Units | Range / default | Notes | 2425 + | :--- | :--- | :--- | :--- | :--- | 2426 + | `t60` / `t1`, `t2` | 1D array | s | > 0 | Reverberation time(s); `t1` empty, `t2` with specimen | 2427 + | `volume` | float | m³ | > 0 | Room volume `V` (advisory below 150 m³) | 2428 + | `sample_area` | float | m² | > 0 | Area `S` the specimen covers (coefficient only) | 2429 + | `temperature` / `temperature1`, `temperature2` | float | °C | default `20.0`, 15–30 | Sets `c` via Eq. (6); `temperature2` defaults to `temperature1` | 2430 + | `speed_of_sound` (`…1`, `…2`) | float, optional | m/s | > 0 | Overrides the temperature-derived `c` | 2431 + | `m` (`m1`, `m2`) | float or 1D array | 1/m | ≥ 0, default `0` | Air power attenuation coefficient | 2432 + 2433 + `absorption_area()` returns the equivalent absorption area `A` (m²) with the 2434 + shape of `t60`; `absorption_coefficient()` returns `alpha_s`; 2435 + `attenuation_from_alpha(alpha)` converts an ISO 9613-1 `alpha` (dB/m) to `m`. 2436 + 2437 + ## See also 2438 + 2439 + - [Building Acoustics & Sound Insulation](https://jmrplens.github.io/phonometry/guides/building-acoustics/) — field, 2440 + laboratory and predicted sound insulation between spaces, and its measurement uncertainty. 2441 + - [Sound Power](https://jmrplens.github.io/phonometry/guides/sound-power/) — the `LW` methods that consume the 2442 + ISO 354 absorption area (the ISO 3744 `K2` and the ISO 3741 absorption term). 2443 + - [Speech Transmission Index](https://jmrplens.github.io/phonometry/guides/speech-transmission/) — the STI/STIPA 2444 + measurement that feeds the open-plan `sti_values`. 2445 + - [Psychoacoustics](https://jmrplens.github.io/phonometry/guides/psychoacoustics/) — loudness, sharpness and the other 2446 + perception metrics of what the room delivers. 2447 + - [Filter Banks](https://jmrplens.github.io/phonometry/guides/filter-banks/) — the IEC 61260 fractional-octave filters 2448 + used for band decay curves and insulation spectra. 2449 + - [Levels](https://jmrplens.github.io/phonometry/guides/levels/) — energy averaging and the level metrics behind 2450 + source/receiving-room levels. 2451 + - [Theory](https://jmrplens.github.io/phonometry/reference/theory/) — Schroeder integration, regression windows and the 2452 + reference-curve derivation. 2453 + 2454 + --- 2455 + 2456 + 2457 + <!-- source: docs/building-acoustics.md | canonical: https://jmrplens.github.io/phonometry/guides/building-acoustics/ --> 2458 + 2459 + # Building Acoustics & Sound Insulation 2460 + 2461 + This guide continues from the [Room Acoustics guide](https://jmrplens.github.io/phonometry/guides/room-acoustics/): 2462 + the same impulse response, measured either side of a partition, yields its sound 2463 + insulation. Where room acoustics describes the sound field inside a single space, 2464 + building acoustics describes how much of that field passes *between* spaces. This 2465 + page follows the insulation chain — field airborne, impact and façade insulation 2466 + with single-number ratings (ISO 16283-1/2/3, ISO 717-1/2), the laboratory 2467 + characterisation of a building element (ISO 10140), the prediction of in-situ 2468 + performance from flanking transmission (EN 12354-1/2), and the measurement 2469 + uncertainty that qualifies every rating (ISO 12999-1). 2470 + 2471 + ## 1. Field insulation and single-number ratings (ISO 16283-1, ISO 717-1) 2172 2472 2173 2473 To rate a wall or floor, measure the energy-average level in the **source** 2174 2474 room ($L_1$) and the **receiving** room ($L_2$) per one-third-octave band ··· 2470 2770 `d_2m_n` or `None`, `r_prime` or `None`, `frequencies`); feed any 16-band façade 2471 2771 quantity to `weighted_rating` for its ISO 717-1 single number. 2472 2772 2473 - ## 5. Laboratory measurement (ISO 10140) 2773 + ## 2. Laboratory measurement (ISO 10140) 2474 2774 2475 2775 Everything above is a **field** measurement (the primed quantities $R'$, $L'_n$): 2476 2776 the number a real building achieves, flanking transmission and all. To rate an ··· 2550 2850 `background_correction(signal_and_background, background)` returns the corrected 2551 2851 levels directly. 2552 2852 2553 - ## 6. Predicting performance (EN 12354) 2853 + ## 3. Predicting performance (EN 12354) 2554 2854 2555 2855 A laboratory rating describes an element in isolation, yet the sound a building 2556 2856 actually transmits also travels *around* the partition — along the floor, up the ··· 2618 2918 print(res.dominant.label, round(res.dominant.fraction, 2)) # Dd 0.33 (direct dominates) 2619 2919 ``` 2620 2920 2921 + <details> 2922 + <summary>Show the code for this figure</summary> 2923 + 2924 + ```python 2925 + import matplotlib.pyplot as plt 2926 + 2927 + # Per-path sound reduction index and each path's share of the transmitted 2928 + # energy for the Annex H.3 result computed above. 2929 + labels = [p.label for p in res.paths] 2930 + r_w = [p.r_w for p in res.paths] 2931 + frac = [100.0 * p.fraction for p in res.paths] 2932 + 2933 + fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6), sharex=True) 2934 + ax1.bar(labels, r_w, color="tab:blue") 2935 + ax1.axhline(res.r_prime_w, ls="--", color="k", label=f"R'w = {res.r_prime_w:.1f} dB") 2936 + ax1.set_ylabel("Path Rij,w [dB]"); ax1.legend() 2937 + ax2.bar(labels, frac, color="tab:orange") 2938 + ax2.set_ylabel("Energy share [%]"); ax2.set_xlabel("Transmission path") 2939 + for ax in (ax1, ax2): 2940 + ax.tick_params(axis="x", rotation=45) 2941 + fig.suptitle("EN 12354-1 Annex H.3 — flanking transmission") 2942 + fig.tight_layout() 2943 + plt.show() 2944 + ``` 2945 + 2946 + </details> 2947 + 2621 2948 Every added flanking path strictly lowers $R'_w$ below the direct $R_{Dd,w} = 57$; 2622 2949 `res.paths` exposes each path's share of the transmitted energy so the dominant 2623 - path is visible. Clause 4.4.2 also enforces a floor $K_{ij} \ge K_{ij,\min}$ from 2624 - the junction geometry — compute it with `junction_min_vibration_reduction` and 2625 - pass it to `flanking_path(..., kij_min=...)`, which raises a below-floor $K_{ij}$ 2626 - to the minimum: 2950 + path is visible. `flanking_element` is a convenience that builds one junction's 2951 + three paths at once; the single-path constructor behind it, `flanking_path`, 2952 + builds one `Ff`, `Df` or `Fd` path at a time (Formula 28a). Clause 4.4.2 also 2953 + enforces a floor $K_{ij} \ge K_{ij,\min}$ from the junction geometry — compute 2954 + it with `junction_min_vibration_reduction` and pass it to 2955 + `flanking_path(..., kij_min=...)`, which raises a below-floor $K_{ij}$ to the 2956 + minimum: 2627 2957 2628 2958 ```python 2629 2959 from phonometry import junction_min_vibration_reduction ··· 2651 2981 print(round(standardized_impact_level(imp.l_prime_n_w, 50.0), 1)) # 43.0 L'nT,w 2652 2982 ``` 2653 2983 2654 - <details> 2655 - <summary>Show the code for this figure</summary> 2656 - 2657 - ```python 2658 - import matplotlib.pyplot as plt 2659 - 2660 - # Per-path sound reduction index and each path's share of the transmitted 2661 - # energy for the Annex H.3 result computed above. 2662 - labels = [p.label for p in res.paths] 2663 - r_w = [p.r_w for p in res.paths] 2664 - frac = [100.0 * p.fraction for p in res.paths] 2665 - 2666 - fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6), sharex=True) 2667 - ax1.bar(labels, r_w, color="tab:blue") 2668 - ax1.axhline(res.r_prime_w, ls="--", color="k", label=f"R'w = {res.r_prime_w:.1f} dB") 2669 - ax1.set_ylabel("Path Rij,w [dB]"); ax1.legend() 2670 - ax2.bar(labels, frac, color="tab:orange") 2671 - ax2.set_ylabel("Energy share [%]"); ax2.set_xlabel("Transmission path") 2672 - for ax in (ax1, ax2): 2673 - ax.tick_params(axis="x", rotation=45) 2674 - fig.suptitle("EN 12354-1 Annex H.3 — flanking transmission") 2675 - fig.tight_layout() 2676 - plt.show() 2677 - ``` 2678 - 2679 - </details> 2680 - 2681 2984 ### `junction_vibration_reduction()` / `flanking_element()` parameters 2682 2985 2683 2986 | Parameter | Type | Units | Range / default | Notes | ··· 2698 3001 `ln_w_eq`, `delta_l_w`, `k_correction`). The simplified model carries a reported 2699 3002 standard deviation of about 2 dB (Clause 5). 2700 3003 2701 - ## 7. Measurement uncertainty (ISO 12999-1) 3004 + ## 4. Measurement uncertainty (ISO 12999-1) 2702 3005 2703 3006 A rating without an uncertainty is only half a result. ISO 12999-1 does not 2704 3007 re-measure anything; it tabulates the **standard uncertainty** $u$ of every ··· 2789 3092 `coverage_factor`, `expanded_uncertainty`, `.lower`, `.upper`). The read-only 2790 3093 `COVERAGE_FACTORS` mapping exposes Table 8 keyed by `(confidence, one_sided)`. 2791 3094 2792 - ## 8. Sound absorption (ISO 354) 2793 - 2794 - The equivalent absorption area `A` that drives `R'`, `L'n`, the ISO 3744 `K2` 2795 - environmental correction and the ISO 3741 absorption term is itself measured in 2796 - a reverberation room (ISO 354). 2797 - Measure the room's reverberation time **empty** ($T_1$) and again **with the 2798 - test specimen installed** ($T_2$); the specimen's absorption is the difference 2799 - of the two Sabine areas, and dividing by the covered area gives the absorption 2800 - coefficient: 2801 - 2802 - $$ 2803 - A = \frac{55.3\ V}{c\ T} - 4 V m, \qquad 2804 - \alpha_s = \frac{A_2 - A_1}{S}, \qquad c = 331 + 0.6\ t , 2805 - $$ 2806 - 2807 - with $c$ from the room air temperature $t$ in °C (valid 15–30 °C) and $m$ the 2808 - power attenuation coefficient of air (default 0; convert an ISO 9613-1 2809 - $\alpha$ in dB/m with `attenuation_from_alpha`). Because edge and diffraction 2810 - effects can scatter more energy than the sample's flat area intercepts, 2811 - $\alpha_s$ may exceed 1.0 and is never clamped (ISO 354 Clause 3.7). 2812 - 2813 - ```python 2814 - import numpy as np 2815 - from phonometry import absorption_area, absorption_coefficient 2816 - 2817 - # Third-octave reverberation times of a 200 m^3 room, empty (T1) and with a 2818 - # 10.8 m^2 absorber sample installed (T2). 2819 - t1 = np.array([5.0, 4.0, 3.0]) 2820 - t2 = np.array([3.0, 2.5, 2.0]) 2821 - 2822 - a_empty = absorption_area(t1, volume=200.0, temperature=20.0) 2823 - print(np.round(a_empty, 2)) # [ 6.45 8.06 10.75] m^2 2824 - 2825 - alpha = absorption_coefficient(t1, t2, volume=200.0, sample_area=10.8, 2826 - temperature1=20.0) 2827 - print(np.round(alpha, 3)) # [0.398 0.448 0.498] 2828 - ``` 2829 - 2830 - `T1` and `T2` are exactly the reverberation times `room_parameters` returns, so 2831 - an ISO 3382-2 decay measurement of the empty and treated room flows straight 2832 - into `absorption_coefficient`. A room volume below the 150 m³ minimum or a 2833 - sample area outside 10–12 m² raises an advisory `AbsorptionWarning`; the result 2834 - still returns. 2835 - 2836 - ### `absorption_area()` / `absorption_coefficient()` parameters 2837 - 2838 - | Parameter | Type | Units | Range / default | Notes | 2839 - | :--- | :--- | :--- | :--- | :--- | 2840 - | `t60` / `t1`, `t2` | 1D array | s | > 0 | Reverberation time(s); `t1` empty, `t2` with specimen | 2841 - | `volume` | float | m³ | > 0 | Room volume `V` (advisory below 150 m³) | 2842 - | `sample_area` | float | m² | > 0 | Area `S` the specimen covers (coefficient only) | 2843 - | `temperature` / `temperature1`, `temperature2` | float | °C | default `20.0`, 15–30 | Sets `c` via Eq. (6); `temperature2` defaults to `temperature1` | 2844 - | `speed_of_sound` (`…1`, `…2`) | float, optional | m/s | > 0 | Overrides the temperature-derived `c` | 2845 - | `m` (`m1`, `m2`) | float or 1D array | 1/m | ≥ 0, default `0` | Air power attenuation coefficient | 3095 + --- 2846 3096 2847 - `absorption_area()` returns the equivalent absorption area `A` (m²) with the 2848 - shape of `t60`; `absorption_coefficient()` returns `alpha_s`; 2849 - `attenuation_from_alpha(alpha)` converts an ISO 9613-1 `alpha` (dB/m) to `m`. 3097 + **Standards.** ISO 16283-1:2014, ISO 16283-2 and ISO 16283-3:2016, *Acoustics — 3098 + Field measurement of sound insulation in buildings and of building elements* — 3099 + the level differences, normalisations and element methods of §1; ISO 717-1 and 3100 + ISO 717-2 — the reference-curve single-number ratings and the spectrum 3101 + adaptation terms C, Ctr and CI; ISO 10140-2:2010 and ISO 10140-4:2010 — the 3102 + laboratory R and Ln with the background-noise correction of §2; EN 12354-1:2000 3103 + and EN 12354-2:2000 — the simplified flanking-transmission predictions of §3 3104 + (Annex E junctions, worked examples H.3 and E.3); ISO 12999-1:2020 — the 3105 + standard uncertainties per measurement situation and the coverage factors 3106 + of §4. 2850 3107 2851 3108 ## See also 2852 3109 2853 - - [Sound Power](https://jmrplens.github.io/phonometry/guides/sound-power/) — the `LW` methods that consume the ISO 354 2854 - absorption area (the ISO 3744 `K2` and the ISO 3741 absorption term). 2855 - 2856 - - [Psychoacoustics and Speech Intelligibility](https://jmrplens.github.io/phonometry/guides/psychoacoustics/) — STI/STIPA 2857 - feeds the open-plan `sti_values`; loudness and sharpness. 2858 - - [Filter Banks](https://jmrplens.github.io/phonometry/guides/filter-banks/) — the IEC 61260 fractional-octave filters 2859 - used for band decay curves and insulation spectra. 3110 + - [Room Acoustics](https://jmrplens.github.io/phonometry/guides/room-acoustics/) — the impulse response, 3111 + room parameters and sound absorption that this guide's insulation chain builds on. 2860 3112 - [Levels](https://jmrplens.github.io/phonometry/guides/levels/) — energy averaging and the level metrics behind 2861 3113 source/receiving-room levels. 2862 - - [Theory](https://jmrplens.github.io/phonometry/reference/theory/) — Schroeder integration, regression windows and the 2863 - reference-curve derivation. 3114 + - [Filter Banks](https://jmrplens.github.io/phonometry/guides/filter-banks/) — the IEC 61260 fractional-octave filters 3115 + used for the insulation spectra. 3116 + - [Sound Power](https://jmrplens.github.io/phonometry/guides/sound-power/) — the `LW` methods that share the 3117 + absorption-area machinery of the receiving room. 3118 + - [Theory](https://jmrplens.github.io/phonometry/reference/theory/) — the reference-curve derivation behind the 3119 + weighted single-number ratings. 2864 3120 2865 3121 --- 2866 3122 ··· 3029 3285 att = outdoor_propagation_attenuation( 3030 3286 200.0, source_height=1.5, receiver_height=1.5, frequencies=bands, 3031 3287 ground_source=1.0, ground_middle=1.0, ground_receiver=1.0, 3032 - barrier=barrier, temperature=15.0, humidity=70.0, 3288 + barrier=barrier, temperature=15.0, relative_humidity=70.0, 3033 3289 ) 3034 3290 print(np.round(att.a_div, 1)) # [57. 57. 57. 57. 57. 57. 57. 57.] divergence 3035 3291 print(np.round(att.a_gr, 2)) # [-4.65 2.34 13.79 9.76 1.3 -0. -0. -0. ] ··· 3040 3296 lw = np.full(len(bands), 95.0) 3041 3297 lp = predicted_receiver_level( 3042 3298 lw, 200.0, 1.5, 1.5, bands, 1.0, 1.0, 1.0, 3043 - barrier=barrier, temperature=15.0, humidity=70.0, 3299 + barrier=barrier, temperature=15.0, relative_humidity=70.0, 3044 3300 ) 3045 3301 print(np.round(lp, 1)) # [28.8 26.7 24. 21.1 17.9 16.2 12.7 -1. ] 3046 3302 ``` ··· 3095 3351 | `frequencies` | 1D array | Hz | default 8 octaves 63–8000 | `DEFAULT_FREQUENCIES` | 3096 3352 | `ground_source` / `ground_middle` / `ground_receiver` | float | — | `[0, 1]`, default `0.0` | Ground factor $G$ (0 hard, 1 porous) | 3097 3353 | `barrier` | `Barrier` or None | — | default `None` | Screening obstacle | 3098 - | `temperature` / `humidity` / `pressure` | float | °C / % / kPa | 20 / 70 / 101.325 | Passed to $A_{atm}$ | 3354 + | `temperature` / `relative_humidity` / `pressure` | float | °C / % / kPa | 20 / 70 / 101.325 | Passed to $A_{atm}$ | 3099 3355 | `projected_distance` | float or None | m | default $\sqrt{d^2-(h_s-h_r)^2}$ | Ground-plane $d_p$ | 3100 3356 3101 3357 Returns an `OutdoorAttenuation` with `a_div`, `a_atm`, `a_gr`, `a_bar`, ··· 3114 3370 3115 3371 The method's stated accuracy is $\pm 1$ to $\pm 3$ dB for broadband noise up to 3116 3372 1000 m (Table 5). See the [Theory](https://jmrplens.github.io/phonometry/reference/theory/) page for the full derivation, the 3117 - [Room and Building Acoustics guide](https://jmrplens.github.io/phonometry/guides/room-acoustics/) for how $\alpha$ feeds 3118 - ISO 354, and the [Levels guide](https://jmrplens.github.io/phonometry/guides/levels/) for the ISO 9612 occupational 3373 + [Room Acoustics guide](https://jmrplens.github.io/phonometry/guides/room-acoustics/) for how $\alpha$ feeds 3374 + ISO 354, and the [Occupational Noise Exposure guide](https://jmrplens.github.io/phonometry/guides/occupational-exposure/) for the ISO 9612 occupational 3119 3375 exposure that consumes A-weighted levels. 3120 3376 3121 3377 --- ··· 3133 3389 limit. This page covers the three routes phonometry implements to obtain it 3134 3390 and when to reach for each: an enveloping *pressure* surface in the field 3135 3391 (ISO 3744/3746), the diffuse field of a *reverberation room* (ISO 3741), 3136 - and *intensity* scanning over a surface (ISO 9614-2). 3392 + *intensity* scanning over a surface (ISO 9614-2), and — for the highest 3393 + accuracy — the precision grades in an *anechoic room* (ISO 3745) and by 3394 + precision *intensity* scanning (ISO 9614-3). 3137 3395 3138 3396 ## Choosing a method 3139 3397 3140 - All three deliver the same quantity — a per-band `LW` and an A-weighted 3141 - total `LWA` — but under different environments, accuracy grades and 3142 - practical constraints. 3398 + All deliver the same quantity — a per-band `LW` and an A-weighted total 3399 + `LWA` — but under different environments, accuracy grades and practical 3400 + constraints. 3143 3401 3144 3402 | Method | Standard | Measured quantity | Environment | Accuracy grade | Use when | 3145 3403 | :--- | :--- | :--- | :--- | :--- | :--- | 3146 3404 | Enveloping surface | **ISO 3744** (engineering) / **ISO 3746** (survey) | Sound pressure on a hemisphere or box | Essentially free field over one or more reflecting planes | Grade 2 (`σR0 ≈ 1.5 dB`) / grade 3 (`≈ 3.0 dB`) | In situ or a large room; no special test facility available | 3147 3405 | Reverberation room | **ISO 3741** | Sound pressure in the diffuse field | Qualified hard-walled reverberation room | Grade 1 (precision) | Highest accuracy for steady, broadband sources in a lab | 3148 3406 | Intensity scanning | **ISO 9614-2** | Normal sound intensity scanned over a surface | Almost any, tolerant of steady extraneous noise | Grade 2 / 3 (from per-band field indicators) | On-site with background noise, or one machine among many | 3407 + | Anechoic room | **ISO 3745** | Sound pressure on a fixed microphone array | Qualified anechoic or hemi-anechoic room | Grade 1 (precision) | Reference-grade emission in a free-field laboratory | 3408 + | Precision intensity scanning | **ISO 9614-3** | Scanned normal intensity, tighter criteria | Almost any, tolerant of steady extraneous noise | Grade 1 (precision) | Precision on-site, with the ISO 9614-3 field-indicator checks | 3149 3409 3150 3410 The pressure methods correct the surface level for the room (`K2`) and for 3151 3411 background noise (`K1`); the reverberation method needs a *qualified* room ··· 3213 3473 res = sound_power_pressure( 3214 3474 levels, "hemisphere", radius=1.5, reflecting_planes=1, 3215 3475 background_levels=background, frequencies=freqs, 3216 - reverberation_time=0.6, room_volume=300.0, # room data -> K2 3476 + reverberation_time=0.6, volume=300.0, # room data -> K2 3217 3477 ) 3218 3478 print(round(res.surface_area, 2)) # 14.14 m^2 (= 2*pi*1.5^2) 3219 3479 print(round(float(res.environmental_correction[0]), 2)) # K2 = 2.32 dB ··· 3226 3486 3227 3487 The A-weighted total `LWA` is combined from the band powers with the ISO 3744 3228 3488 Annex E A-weighting corrections, so it needs `frequencies`. Passing the room 3229 - data (`reverberation_time` + `room_volume`, or `absorption_area`, or 3489 + data (`reverberation_time` + `volume`, or `absorption_area`, or 3230 3490 `mean_absorption_coefficient` + `room_surface`) enables `K2`; omit it and the 3231 3491 field is treated as free (`K2 = 0`). If the background margin drops below the 3232 3492 grade criterion or `K2` exceeds the validity limit, a `SoundPowerWarning` ··· 3245 3505 | `background_levels` | 2D array or spectrum | dB | `(NM, NB)`, or `(NB,)` / `(1, NB)` | Enables `K1`; a single spectrum broadcasts to every position | 3246 3506 | `frequencies` | 1D array | Hz | nominal band centres | Enables `LWA` (Annex E) | 3247 3507 | `absorption_area` | float or 1D array | m² | > 0 | `A` for `K2` (direct); per-band array → per-band `K2` | 3248 - | `reverberation_time`, `room_volume` | float/array, float | s, m³ | > 0 | `A = 0.16 V/T` for `K2`; per-band `T` → per-band `K2` | 3508 + | `reverberation_time`, `volume` | float/array, float | s, m³ | > 0 | `A = 0.16 V/T` for `K2`; per-band `T` → per-band `K2` | 3249 3509 | `mean_absorption_coefficient`, `room_surface` | float/array, float | —, m² | `(0,1]`, > 0 | `A = α·Sv` (Eq. A.7); per-band `α` → per-band `K2` | 3250 3510 | `grade` | str | — | `'engineering'` (default) / `'survey'` | ISO 3744 vs ISO 3746 | 3251 3511 | `omc_uncertainty` | float | dB | default `0.0` | `σomc`, operating/mounting instability, folded into `U` | ··· 3450 3710 `dynamic_capability_index` (`Ld`), `achieved_grade`, `surface_area`, 3451 3711 `sound_power_level_a` and `grade`. 3452 3712 3713 + ## 4. Precision grade, anechoic room (ISO 3745) 3714 + 3715 + When the highest accuracy is required, ISO 3745 measures sound power in a 3716 + qualified **anechoic** or **hemi-anechoic** room, where the free field lets a 3717 + fixed array of microphones sample the radiated sound pressure directly. It is the 3718 + grade-1 counterpart to the enveloping-surface method of Section 1, with 3719 + standardized microphone coordinates, a per-position background correction and an 3720 + explicit meteorological correction. 3721 + 3722 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_precision_anechoic_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_precision_anechoic.svg" alt="ISO 3745 precision sound power in an anechoic room: wedge-lined walls, the device under test at the centre and a hemispherical array of microphones at a fixed radius, with the sound power level formed from the surface-averaged pressure plus the area, background and meteorological corrections" width="92%"></picture> 3723 + 3724 + **Sound power level (Clause 8).** The band sound power level is the 3725 + surface-averaged pressure level plus the surface term and the corrections: 3726 + 3727 + $$ 3728 + L_W = \overline{L_p} + 10\lg\frac{S}{S_0} + C_1 + C_2 + C_3, 3729 + $$ 3730 + 3731 + with $S = 4\pi r^2$ over the sphere or $S = 2\pi r^2$ over the hemisphere, 3732 + $S_0 = 1\ \text{m}^2$. $C_1$ and $C_2$ are the meteorological corrections 3733 + (reference and radiation-impedance terms); $C_3$ accounts for air absorption over 3734 + the measurement radius. The microphone positions are the standardized 3735 + unit-vector arrays of Tables D.1 (sphere), E.1 (hemisphere) and E.2 (hemisphere, 3736 + broadband). 3737 + 3738 + ```python 3739 + import numpy as np 3740 + import phonometry as ph 3741 + 3742 + # The 40 standardized hemisphere positions (unit vectors scaled by the radius). 3743 + pos = ph.precision_positions("hemisphere", radius=1.0, count=40) 3744 + print(pos.shape) # (40, 3) 3745 + 3746 + # Octave/third-octave band SPL (dB) at each of the 40 positions; here a uniform 3747 + # 74 dB in one band. The result carries S = 2*pi*r^2 and LW with C1+C2+C3. 3748 + levels = np.full((40, 1), 74.0) 3749 + res = ph.sound_power_anechoic(levels, "hemisphere", radius=1.0) 3750 + print(round(res.surface_area, 3)) # 6.283 (2*pi*1^2) 3751 + print(np.round(res.sound_power_level, 2)) # [81.85] 3752 + ``` 3753 + 3754 + **Background and meteorological corrections.** The $K_1$ background correction is 3755 + applied **per position** and floored where the signal-to-background difference is 3756 + small (Eq. 11); the meteorological correction is evaluated from the measured 3757 + temperature and static pressure. 3758 + 3759 + ```python 3760 + import numpy as np 3761 + import phonometry as ph 3762 + 3763 + # K1 for a 6 dB signal-to-background difference in a <=200 Hz edge band: the 3764 + # floor is 1.26 dB (Eq. 11). Source and background levels are [positions, bands]. 3765 + k1 = ph.precision_background_correction( 3766 + np.array([[56.0]]), np.array([[50.0]]), np.array([200.0])) 3767 + print(round(float(k1[0, 0]), 4)) # 1.2563 3768 + 3769 + # Meteorological corrections at the 23 C, 101.325 kPa reference (Eq. 16): 3770 + mc = ph.meteorological_corrections(23.0, 101.325) 3771 + print(round(mc.c1, 4), round(mc.c2, 4)) # -0.1282 0.0 3772 + 3773 + # Expanded uncertainty (Clause 10.5 EXAMPLE): sigma_R0 = 0.5, sigma_omc = 2.0, 3774 + # k = 2 -> U = 4.1 dB. 3775 + print(round(ph.precision_uncertainty(0.5, 2.0, 2.0), 3)) # 4.123 3776 + ``` 3777 + 3778 + Over several bands `sound_power_anechoic` returns a plottable 3779 + `PrecisionSoundPowerResult` carrying the per-band `LW` and the A-weighted total: 3780 + 3781 + ```python 3782 + import numpy as np 3783 + import phonometry as ph 3784 + 3785 + # A mid-frequency-peaked machine measured over the 40-position hemisphere array 3786 + # (Annex E). levels_positions is the (40, NB) surface pressure spectrum: a base 3787 + # spectrum peaked near 1 kHz plus a small per-position spatial spread. 3788 + freqs = np.array([125, 250, 500, 1000, 2000, 4000, 8000], float) 3789 + base = 70.0 + 8.0 * np.exp(-(np.log2(freqs / 1000.0) ** 2) / 2.0) 3790 + rng = np.random.default_rng(7) 3791 + levels = base[None, :] + rng.normal(0.0, 1.0, (40, freqs.size)) 3792 + 3793 + result = ph.sound_power_anechoic(levels, "hemisphere", radius=1.0, frequencies=freqs) 3794 + print(round(result.sound_power_level_a, 1)) # 89.3 3795 + result.plot() # LW spectrum, LWA in the title (needs matplotlib) 3796 + ``` 3797 + 3798 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/precision_anechoic_power_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/precision_anechoic_power.png" alt="The precision sound power level spectrum of a mid-frequency-peaked machine measured over the ISO 3745 hemisphere array, one bar per band peaking near 1 kHz, with the A-weighted total of 89.3 dB(A) in the title" width="88%"></picture> 3799 + 3800 + *One bar per band: the surface-averaged pressure plus the area, background and 3801 + meteorological corrections give `LW(f)`, and the A-weighted energy sum across 3802 + bands gives the single-number `LWA` in the title.* 3803 + 3804 + <details> 3805 + <summary>Show the code for this figure</summary> 3806 + 3807 + ```python 3808 + import matplotlib.pyplot as plt 3809 + import numpy as np 3810 + 3811 + # result is the PrecisionSoundPowerResult computed above. One line: 3812 + result.plot() 3813 + plt.show() 3814 + 3815 + # By hand: a bar spectrum of LW with the A-weighted total in the title. 3816 + freqs = result.frequencies 3817 + positions = np.arange(freqs.size) 3818 + fig, ax = plt.subplots() 3819 + ax.bar(positions, result.sound_power_level, width=0.7, color="#1f77b4") 3820 + ax.set_xticks(positions) 3821 + ax.set_xticklabels([f"{f:g}" for f in freqs], rotation=45, ha="right") 3822 + ax.set_xlabel("Frequency [Hz]") 3823 + ax.set_ylabel("Sound power level LW [dB]") 3824 + ax.set_title( 3825 + f"Precision sound power (ISO 3745) LWA = {result.sound_power_level_a:.1f} dB(A)") 3826 + plt.show() 3827 + ``` 3828 + 3829 + </details> 3830 + 3831 + ## 5. Precision intensity scanning (ISO 9614-3) 3832 + 3833 + ISO 9614-3 is the grade-1 scanning method: like ISO 9614-2 it integrates the 3834 + normal intensity over a surface enclosing the source, but with a continuous 3835 + scan, tighter field-indicator criteria and an explicit uncertainty budget. 3836 + 3837 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_intensity_scan_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_intensity_scan.svg" alt="ISO 9614-3 precision sound intensity scanning: a source enclosed by a measurement surface divided into segments, a two-microphone intensity probe scanned along a serpentine path over each segment, and the sound power formed by summing the normal intensity times segment area, subject to the field-indicator acceptance criteria" width="92%"></picture> 3838 + 3839 + **Power and level (Clause 7).** The partial power of each segment is 3840 + $P_i = I_{n,i}\,S_i$; the total $P = \sum_i P_i$ gives 3841 + $L_W = 10\lg(P/P_0)$, $P_0 = 1\ \text{pW}$. A band whose net intensity is 3842 + negative (more power flowing in than out) is flagged not-applicable rather than 3843 + logged. The field indicators (temporal variability $F_T$, the signed and 3844 + unsigned pressure–intensity indicators, and the non-uniformity $F_S$) drive the 3845 + five acceptance criteria. 3846 + 3847 + ```python 3848 + import numpy as np 3849 + import phonometry as ph 3850 + 3851 + # A fully enclosing surface with a uniform normal intensity In = W/S recovers 3852 + # the source power exactly: LW = 10*lg(W/P0). Here W = 100 uW -> 80 dB. 3853 + areas = np.array([0.5, 1.0, 0.25, 2.0]) 3854 + w = 1.0e-4 3855 + i_n = np.full(areas.shape, w / float(areas.sum())) 3856 + res = ph.sound_power_intensity_precision(i_n, areas) 3857 + print(round(float(res.sound_power[0]), 6)) # 0.0001 3858 + print(round(float(res.sound_power_level[0]), 2)) # 80.0 3859 + ``` 3860 + 3861 + Across several bands the result carries the per-band `LW` (`NaN` where the net 3862 + power is non-positive) and flags those bands `not_applicable`: 3863 + 3864 + ```python 3865 + import numpy as np 3866 + import phonometry as ph 3867 + 3868 + # Four partial surfaces scanned over five one-third-octave bands. Each cell of 3869 + # partial_intensity is the signed normal intensity In_i (W/m^2); areas are the 3870 + # partial-surface areas Si. The 250 Hz band has net-negative power (a locally 3871 + # reactive field), so ISO 9614-3 flags it not-applicable (clause 9.2) -> NaN. 3872 + freqs = np.array([250, 500, 1000, 2000, 4000], float) 3873 + areas = np.array([0.5, 1.0, 0.75, 0.5]) 3874 + base_intensity = np.array([2.0e-6, 8.0e-6, 2.0e-5, 1.0e-5, 3.0e-6]) 3875 + partial_intensity = base_intensity[None, :] * np.array([1.0, 1.1, 0.9, 1.05])[:, None] 3876 + partial_intensity[:, 0] = [2.0e-6, -3.0e-6, -4.0e-6, -1.0e-6] # net-negative band 3877 + 3878 + result = ph.sound_power_intensity_precision(partial_intensity, areas, frequencies=freqs) 3879 + print(result.not_applicable_band.tolist()) # [True, False, False, False, False] 3880 + print(round(result.sound_power_level_a, 1)) # 80.6 3881 + result.plot() # LW spectrum; the not-applicable band is hatched (needs matplotlib) 3882 + ``` 3883 + 3884 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/intensity_scan_power_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/intensity_scan_power.png" alt="The precision intensity-scanning sound power level spectrum over five one-third-octave bands, four determinate bars and a hatched, greyed 250 Hz band flagged not-applicable because its net intensity is negative, with the A-weighted total of 80.6 dB(A) in the title" width="88%"></picture> 3885 + 3886 + *The 250 Hz band nets negative (more energy flowing in than out), so ISO 9614-3 3887 + declares it not-applicable — the figure hatches and greys it while the four 3888 + determinate bands and the A-weighted total stand.* 3889 + 3890 + <details> 3891 + <summary>Show the code for this figure</summary> 3892 + 3893 + ```python 3894 + import matplotlib.pyplot as plt 3895 + import numpy as np 3896 + 3897 + # result is the PrecisionIntensityResult computed above. One line: 3898 + result.plot() 3899 + plt.show() 3900 + 3901 + # By hand: determinate bands as LW bars; a not-applicable band (its LW is NaN) 3902 + # is flagged by a full-height greyed, hatched span rather than a zero-height bar. 3903 + freqs = result.frequencies 3904 + positions = np.arange(freqs.size) 3905 + neg = result.not_applicable_band 3906 + lw = np.nan_to_num(result.sound_power_level) 3907 + fig, ax = plt.subplots() 3908 + ax.bar(positions[~neg], lw[~neg], width=0.7, color="#1f77b4") 3909 + for pos in positions[neg]: 3910 + ax.axvspan(pos - 0.35, pos + 0.35, facecolor="#888888", alpha=0.28, 3911 + hatch="//", edgecolor="#888888") 3912 + ax.set_xticks(positions) 3913 + ax.set_xticklabels([f"{f:g}" for f in freqs], rotation=45, ha="right") 3914 + ax.set_xlabel("Frequency [Hz]") 3915 + ax.set_ylabel("Sound power level LW [dB]") 3916 + ax.set_title( 3917 + f"Precision intensity scanning (ISO 9614-3) " 3918 + f"LWA = {result.sound_power_level_a:.1f} dB(A)") 3919 + plt.show() 3920 + ``` 3921 + 3922 + </details> 3923 + 3453 3924 ## See also 3454 3925 3455 3926 - [Sound Intensity (p-p)](https://jmrplens.github.io/phonometry/guides/intensity/) — the two-microphone probe, its 3456 3927 finite-difference bias and the ISO 9614-1 field indicators behind the 3457 3928 scanning method. 3458 - - [Room and Building Acoustics](https://jmrplens.github.io/phonometry/guides/room-acoustics/) — the reverberation time 3929 + - [Room Acoustics](https://jmrplens.github.io/phonometry/guides/room-acoustics/) — the reverberation time 3459 3930 and equivalent absorption area (ISO 354) that feed `K2` and the ISO 3741 3460 3931 absorption area; impact and airborne insulation. 3461 3932 - [Levels](https://jmrplens.github.io/phonometry/guides/levels/) — energy averaging and the A-weighting behind `LWA`. ··· 3485 3956 3486 3957 where $L_\text{cal}$ is the calibrator's level (typically 94 dB, i.e. 1 Pa), 3487 3958 $p_\text{ref} = 20\ \mu\text{Pa}$ and $\tilde{x}_\text{ref}$ is the RMS of 3488 - the recorded calibration tone in digital units. `calculate_sensitivity()` is 3959 + the recorded calibration tone in digital units. `sensitivity()` is 3489 3960 exactly that equation. The factor is valid as long as nothing in the chain 3490 3961 changes — touch the gain knob and you must recalibrate. 3491 3962 3492 - <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_calibration_setup_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_calibration_setup.svg" alt="Calibration chain: sound calibrator coupled on the microphone, preamplifier, ADC and calculate_sensitivity producing pascals per digital unit" width="92%"></picture> 3963 + <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_calibration_setup_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_calibration_setup.svg" alt="Calibration chain: sound calibrator coupled on the microphone, preamplifier, ADC and sensitivity producing pascals per digital unit" width="92%"></picture> 3493 3964 3494 3965 ## Physical Calibration (Sound Level Meter) 3495 3966 3496 3967 ```mermaid 3497 3968 flowchart LR 3498 3969 A["Calibrator tone\n94 dB @ 1 kHz\n(IEC 60942)"] --> B["Recording\ncalibrator_recording"] 3499 - B --> C["calculate_sensitivity()"] 3970 + B --> C["sensitivity()"] 3500 3971 C --> D["calibration_factor\n(digital units → Pa)"] 3501 - D --> E["octavefilter / leq / laeq / ln_levels"] 3972 + D --> E["octave_filter / leq / laeq / ln_levels"] 3502 3973 F["Measurement\nrecording"] --> E 3503 3974 E --> G["Levels in dB SPL\n(re 20 µPa)"] 3504 3975 ``` ··· 3509 3980 3510 3981 ```python 3511 3982 import numpy as np 3512 - from phonometry import octavefilter, calculate_sensitivity 3983 + from phonometry import octave_filter, sensitivity 3513 3984 3514 3985 # 1. Record your 94 dB calibrator signal (1 kHz, 1 Pa RMS = 94 dB SPL) 3515 3986 fs = 48000 ··· 3521 3992 recording = 0.2 * np.sin(2 * np.pi * 1000 * np.arange(fs) / fs) 3522 3993 3523 3994 # 2. Calculate sensitivity factor 3524 - sensitivity = calculate_sensitivity(calibrator_recording, target_spl=94.0, fs=fs) 3995 + calibration_factor = sensitivity(calibrator_recording, target_spl=94.0, fs=fs) 3525 3996 3526 3997 # 3. Apply calibration to your measurements 3527 - spl, freq = octavefilter(recording, fs, calibration_factor=sensitivity) 3998 + spl, freq = octave_filter(recording, fs, calibration_factor=calibration_factor) 3528 3999 # Now 'spl' values are in real-world dB SPL! 3529 4000 ``` 3530 4001 3531 - The same `calibration_factor` works across the whole library: `octavefilter`, 4002 + The same `calibration_factor` works across the whole library: `octave_filter`, 3532 4003 `OctaveFilterBank`, `leq`, `laeq` and `ln_levels`. 3533 4004 3534 4005 ## Calibrator assumptions (IEC 60942) 3535 4006 3536 - `calculate_sensitivity` assumes the reference recording comes from an acoustic 4007 + `sensitivity` assumes the reference recording comes from an acoustic 3537 4008 calibrator as specified by **IEC 60942** (classes LS, 1 and 2): 3538 4009 3539 4010 - The default `target_spl=94.0` matches the common 94 dB @ 1 kHz calibrator ··· 3549 4020 ### Automatic stability validation 3550 4021 3551 4022 When you pass the sample rate (and `validate=True`, the default), 3552 - `calculate_sensitivity(ref, fs=fs)` checks the recording the way 4023 + `sensitivity(ref, fs=fs)` checks the recording the way 3553 4024 IEC 60942:2017 checks the calibrator itself (5.3.3): the *short-term level 3554 4025 fluctuation* — the absolute difference between each of the maximum and minimum 3555 4026 F-time-weighted levels and the mean level — must not exceed the Table 2 class 1 ··· 3568 4039 3569 4040 <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/calibration_stability_dark.png"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/calibration_stability.png" alt="F-weighted level of a stable calibration tone versus a 3 percent amplitude-modulated one against the plus-minus 0.07 dB IEC 60942 class 1 limit" width="80%"></picture> 3570 4041 3571 - ### `calculate_sensitivity()` parameters 4042 + ### `sensitivity()` parameters 3572 4043 3573 4044 | Parameter | Type / shape | Units | Range / default | Notes | 3574 4045 | :--- | :--- | :--- | :--- | :--- | ··· 3582 4053 | `narrowband` | bool | — | default `False` | Estimate the tone with a coherent Goertzel detector near `frequency` (needs `fs`) instead of full-band RMS; rejects broadband hum/noise that otherwise inflates the RMS and shrinks every later level (~−0.44 dB at 20 dB SNR). Enable for noisy coupler recordings | 3583 4054 3584 4055 Returns the sensitivity factor (float) to pass as `calibration_factor=` to 3585 - `octavefilter`, `leq`, `laeq`, `ln_levels`, `lc_peak`, `sel` and the dose 4056 + `octave_filter`, `leq`, `laeq`, `ln_levels`, `lc_peak`, `sel` and the dose 3586 4057 functions. 3587 4058 3588 4059 ## Digital Analysis (dBFS) ··· 3599 4070 3600 4071 ```python 3601 4072 # Assume 'recording' is normalized between -1.0 and 1.0 3602 - spl_dbfs, freq = octavefilter(recording, fs, dbfs=True) 4073 + spl_dbfs, freq = octave_filter(recording, fs, dbfs=True) 3603 4074 # Results will be negative (e.g., -20 dBFS) 3604 4075 ``` 3605 4076 ··· 3614 4085 3615 4086 ```python 3616 4087 # Measure peak-holding levels for impact analysis 3617 - spl_peak, freq = octavefilter(recording, fs, mode='peak') 4088 + spl_peak, freq = octave_filter(recording, fs, mode='peak') 3618 4089 ``` 3619 4090 3620 4091 > [!NOTE] ··· 3673 4144 from phonometry import OctaveFilterBank, WeightingFilter 3674 4145 3675 4146 fs = 48000 3676 - octavefilter = OctaveFilterBank(fs, 1, stateful=True, resample=False) 4147 + octave_filter = OctaveFilterBank(fs, 1, stateful=True, resample=False) 3677 4148 afilter = WeightingFilter(fs, "A", stateful=True) 3678 4149 3679 4150 for block in sf.blocks("measurement.wav", blocksize=256, overlap=0): ··· 3682 4153 weighted = afilter.filter(block) 3683 4154 3684 4155 # Split into octave bands 3685 - block_spl, _, block_output = octavefilter.filter(weighted, sigbands=True, detrend=False) 4156 + block_spl, _, block_output = octave_filter.filter(weighted, sigbands=True, detrend=False) 3686 4157 3687 4158 # further signal processing 3688 4159 ... ··· 3760 4231 Channel (Log Sine Sweep).* 3761 4232 3762 4233 The convention is consistent across the whole library: time is always the 3763 - **last axis**. This applies to `octavefilter`, `OctaveFilterBank`, 4234 + **last axis**. This applies to `octave_filter`, `OctaveFilterBank`, 3764 4235 `weighting_filter`, `time_weighting`, `leq`, `laeq`, `ln_levels` and 3765 4236 `spectrogram`. 3766 4237 3767 4238 ```python 3768 4239 import numpy as np 3769 - from phonometry import octavefilter 4240 + from phonometry import octave_filter 3770 4241 3771 4242 # Two calibrated channels in Pa so the guide runs standalone 3772 4243 fs = 48000 ··· 3775 4246 right = 0.1 * np.sin(2 * np.pi * 500 * t) 3776 4247 3777 4248 stereo = np.stack([left, right]) # (2, n_samples) 3778 - spl, freq = octavefilter(stereo, fs, fraction=3) 4249 + spl, freq = octave_filter(stereo, fs, fraction=3) 3779 4250 # spl has shape (2, n_bands): one row per channel 3780 4251 ``` 3781 4252 ··· 3817 4288 3818 4289 Additional performance notes: 3819 4290 3820 - - **Design cache**: `octavefilter()` reuses filter bank designs across calls 4291 + - **Design cache**: `octave_filter()` reuses filter bank designs across calls 3821 4292 with identical parameters (LRU cache, 32 entries), so calling it in a loop 3822 4293 does not redesign the bank each time. `OctaveFilterBank` gives you explicit 3823 4294 control over the design lifetime. ··· 3838 4309 3839 4310 | Name | Type | Description (Inputs) | Usage Snippet (Outputs) | 3840 4311 | :--- | :--- | :--- | :--- | 3841 - | `octavefilter` | `function` | **High-level analysis.**<br>• `x`: Signal array<br>• `fs`: Sample rate [Hz]<br>• `fraction`: 1, 3, etc. (Default: 1)<br>• `order`: Filter order (Default: 6)<br>• `limits`: [f_min, f_max] (Default: [12, 20000])<br>• `filter_type`: 'butter', 'cheby1', 'cheby2', 'ellip', 'bessel' (Default: 'butter')<br>• `sigbands`: Return time signals (Default: False)<br>• `detrend`: Remove DC offset (Default: True)<br>• `calibration_factor`: Sensitivity multiplier (Default: 1.0)<br>• `dbfs`: Output in dBFS instead of dB SPL (Default: False)<br>• `mode`: 'rms' or 'peak' (Default: 'rms')<br>• `nominal`: IEC 61260-1 nominal labels (Default: False)<br>• `show`: Plot response (Default: False)<br>• `plot_file`: Path to save plot (Default: None)<br>• `ripple`: Passband ripple [dB] (for cheby1/ellip)<br>• `attenuation`: Stopband atten. [dB] (Default: 72; for cheby2/ellip — cheby2 needs >= 70 dB for class 1) | `spl, freq = octavefilter(x, fs, ...)`<br>• `spl`: levels [dB]<br>• `freq`: frequencies [Hz]<br><br>**With `sigbands=True`:**<br>`spl, freq, xb = octavefilter(x, fs, sigbands=True)`<br>• `xb`: List of filtered signals (one per band)<br><br>**Calibrated usage:**<br>`spl, f = octavefilter(x, fs, calibration_factor=0.05)` | 4312 + | `octave_filter` | `function` | **High-level analysis.**<br>• `x`: Signal array<br>• `fs`: Sample rate [Hz]<br>• `fraction`: 1, 3, etc. (Default: 1)<br>• `order`: Filter order (Default: 6)<br>• `limits`: [f_min, f_max] (Default: [12, 20000])<br>• `filter_type`: 'butter', 'cheby1', 'cheby2', 'ellip', 'bessel' (Default: 'butter')<br>• `sigbands`: Return time signals (Default: False)<br>• `detrend`: Remove DC offset (Default: True)<br>• `calibration_factor`: Sensitivity multiplier (Default: 1.0)<br>• `dbfs`: Output in dBFS instead of dB SPL (Default: False)<br>• `mode`: 'rms' or 'peak' (Default: 'rms')<br>• `nominal`: IEC 61260-1 nominal labels (Default: False)<br>• `show`: Plot response (Default: False)<br>• `plot_file`: Path to save plot (Default: None)<br>• `ripple`: Passband ripple [dB] (for cheby1/ellip)<br>• `attenuation`: Stopband atten. [dB] (Default: 72; for cheby2/ellip — cheby2 needs >= 70 dB for class 1) | `spl, freq = octave_filter(x, fs, ...)`<br>• `spl`: levels [dB]<br>• `freq`: frequencies [Hz]<br><br>**With `sigbands=True`:**<br>`spl, freq, xb = octave_filter(x, fs, sigbands=True)`<br>• `xb`: List of filtered signals (one per band)<br><br>**Calibrated usage:**<br>`spl, f = octave_filter(x, fs, calibration_factor=0.05)` | 3842 4313 | `OctaveFilterBank` | `class` | **Efficient bank implementation.**<br>• `fs`: Sample rate [Hz]<br>• `fraction`: 1, 3, etc.<br>• `order`: Filter order<br>• `limits`: [f_min, f_max] (Default: [12, 20000])<br>• `filter_type`: Architecture name<br>• `show`: Plot response (Default: False)<br>• `plot_file`: Path to save the plot (Default: None)<br>• `calibration_factor`: Sensitivity multiplier<br>• `dbfs`: Use dBFS (Default: False)<br>• `stateful`: Carry filter state between calls (Default: False)<br>• `steady_ic`: Steady-state initial conditions (Default: False)<br>• `resample`: Multirate decimation (Default: True)<br>• `ripple`: Passband ripple [dB]<br>• `attenuation`: Stopband attenuation [dB] (Default: 72; cheby2 needs >= 70 dB for class 1) | `bank = OctaveFilterBank(fs=48000, fraction=3, order=6, filter_type='butter')`<br>`spl, f = bank.filter(x, sigbands=False, mode='rms', detrend=True, zero_phase=False)`<br><br>• `bank`: Instance of the filter bank<br>• `bank.freq` / `bank.freq_d` / `bank.freq_u` / `bank.sos`: computed properties | 3843 4314 | `OctaveFilterBank.spectrogram` | `method` | **Band levels over time.**<br>• `x`: Signal array (1D or 2D)<br>• `window_time`: Window length [s] (Default: 0.125)<br>• `overlap`: Fraction in [0, 1) (Default: 0.5)<br>• `mode`: 'rms' or 'peak'<br>• `zero_phase`: Group-delay-free frames (Default: False) | `levels, freq, times = bank.spectrogram(x)`<br><br>• `levels`: (bands, frames) or (channels, bands, frames)<br>• `times`: window centers [s] | 3844 4315 | `weighting_filter` | `function` | **Acoustic weighting.**<br>• `x`: Signal array<br>• `fs`: Sample rate [Hz]<br>• `curve`: 'A', 'C', 'G' (ISO 7196 infrasound) or 'Z' (Default: 'A')<br>• `high_accuracy`: IEC class 1 HF accuracy via internal oversampling (Default: True) | `y = weighting_filter(x, fs, curve='A')`<br><br>• `y`: weighted signal | ··· 3882 4353 | `sound_intensity` | `function` | **p-p sound intensity (IEC 61043).**<br>• `p1`, `p2`: Microphone signals [Pa], equal length<br>• `fs`: Sample rate [Hz]<br>• `spacing`: Microphone separation Δr [m]<br>• `rho`: Air density (Default: 1.204)<br>• `c`: Speed of sound (Default: 343.0)<br>• `fraction`: None, 1 or 3 (Default: None)<br>• `limits`: [f_min, f_max] (Default: [12, 20000])<br>• `bias_correct`: undo finite-difference under-read of band/broadband totals near `max_valid_frequency` (Default: False) | `res = sound_intensity(p1, p2, fs, spacing=0.012, fraction=3)`<br><br>• `IntensityResult` | 3883 4354 | `IntensityResult` | `dataclass` | **Intensity result.**<br>• Per band (with `fraction`): `frequency`, `intensity` [W/m²], `intensity_level`, `pressure_level`, `pressure_intensity_index`, `direction` (±1), `bias_correction`<br>• Broadband: `total_*` counterparts<br>• `max_valid_frequency`: 0.1·c/Δr | `res.total_intensity_level`<br>`res.total_direction` | 3884 4355 | `field_indicators` | `function` | **ISO 9614-1 Annex A field indicators.**<br>• `pressure_levels`: Lpi per position [dB]<br>• `normal_intensity`: Signed Ini per position [W/m²] | `fi = field_indicators(lp, i_n)`<br><br>• `FieldIndicators(f2, f3, f4)` | 4356 + | `FieldIndicators` | `dataclass` | **ISO 9614-1 Annex A indicators.**<br>• `f2`: surface pressure-intensity indicator (Eq. A.3)<br>• `f3`: negative partial power indicator (Eq. A.6)<br>• `f4`: field non-uniformity indicator (Eq. A.8)<br>• f3 − f2 > 0 reveals negative partial power | `fi.f2, fi.f3, fi.f4`<br><br>• Criteria: Ld > F2, N > C·F4² | 3885 4357 | `dynamic_capability_index` | `function` | **Dynamic capability Ld (ISO 9614-1 §3.12).**<br>• `pressure_residual_intensity_index`: δpI0 [dB]<br>• `bias_error_factor`: K [dB] (Default: 10.0) | `ld = dynamic_capability_index(18.0)`<br><br>• Adequate when `Ld > F2` (criterion 1) | 3886 - | `CalibrationWarning` | `warning class` | **Unreliable calibration recording.**<br>Emitted by `calculate_sensitivity` — with `fs` given and `validate=True` — when the tone is unstable (IEC 60942 limit) or too short | `warnings.simplefilter("error", CalibrationWarning)` | 4358 + | `CalibrationWarning` | `warning class` | **Unreliable calibration recording.**<br>Emitted by `sensitivity` — with `fs` given and `validate=True` — when the tone is unstable (IEC 60942 limit) or too short | `warnings.simplefilter("error", CalibrationWarning)` | 3887 4359 | `lden` | `function` | **Day-evening-night level (ISO 1996-1 §3.6.4).**<br>• `lday`/`levening`/`lnight`: LAeq per period [dB]<br>• `hours`: Period durations (Default: (12, 4, 8)) | `l = lden(63.2, 58.1, 51.4)` | 3888 4360 | `ldn` | `function` | **Day-night level (ISO 1996-1 §3.6.5).**<br>• `lday`/`lnight`: LAeq per period [dB]<br>• `hours`: Default (15, 9) | `l = ldn(63.2, 51.4)` | 3889 4361 | `composite_rating_level` | `function` | **Whole-day composite rating (ISO 1996-1 §6.5).**<br>• `periods`: list of (level_db, hours, adjustment_db) summing 24 h | `r = composite_rating_level([(63, 12, 0), (58, 4, 5), (51, 8, 10)])` | 3890 4362 | `linkwitz_riley` | `function` | **Audio crossover.**<br>• `x`: Signal array<br>• `fs`: Sample rate [Hz]<br>• `freq`: Crossover frequency [Hz]<br>• `order`: Any even number (Default: 4) | `lo, hi = linkwitz_riley(x, fs, freq=1000, order=4)`<br><br>• `lo`: Low-pass filtered signal<br>• `hi`: High-pass filtered signal | 3891 - | `calculate_sensitivity` | `function`| **SPL Calibration.**<br>• `ref_signal`: Calibration signal<br>• `target_spl`: Level of calibrator (Default: 94.0)<br>• `ref_pressure`: Reference pressure (Default: 20e-6)<br>• `narrowband`: coherent Goertzel tone estimate that rejects broadband hum/noise (needs `fs`; Default: False) | `s = calculate_sensitivity(ref_signal, target_spl=94.0)`<br><br>• `s`: Float (multiplier for pressure) | 4363 + | `sensitivity` | `function`| **SPL Calibration.**<br>• `ref_signal`: Calibration signal<br>• `target_spl`: Level of calibrator (Default: 94.0)<br>• `ref_pressure`: Reference pressure (Default: 20e-6)<br>• `narrowband`: coherent Goertzel tone estimate that rejects broadband hum/noise (needs `fs`; Default: False) | `s = sensitivity(ref_signal, target_spl=94.0)`<br><br>• `s`: Float (multiplier for pressure) | 3892 4364 | `verify_filter_class` | `function` | **IEC 61260-1:2014 class check.**<br>• `bank`: an `OctaveFilterBank`<br>• `num_points`: frequency grid points per band (Default: 32768) | `result = verify_filter_class(bank)`<br><br>• `result["overall_class"]`: 1, 2 or None<br>• `result["bands"]`: per-band class and margins [dB] | 3893 - | `getansifrequencies` | `function` | **ANSI Frequency generator.**<br>• `fraction`: 1, 3, etc. (Required)<br>• `limits`: [f_min, f_max] (Default: [12, 20000]) | `f_cen, f_low, f_high, labels = getansifrequencies(fraction=3)`<br><br>• `f_cen`: List of center frequencies [Hz]<br>• `f_low`: List of lower edges [Hz]<br>• `f_high`: List of upper edges [Hz]<br>• `labels`: IEC nominal frequency labels | 3894 - | `normalizedfreq` | `function` | **Standard IEC Frequencies.**<br>• `fraction`: 1 or 3 | `freqs = normalizedfreq(fraction=3)`<br><br>• `freqs`: List of standard center frequencies [Hz] | 4365 + | `nominal_frequencies` | `function` | **ANSI Frequency generator.**<br>• `fraction`: 1, 3, etc. (Required)<br>• `limits`: [f_min, f_max] (Default: [12, 20000]) | `f_cen, f_low, f_high, labels = nominal_frequencies(fraction=3)`<br><br>• `f_cen`: List of center frequencies [Hz]<br>• `f_low`: List of lower edges [Hz]<br>• `f_high`: List of upper edges [Hz]<br>• `labels`: IEC nominal frequency labels | 4366 + | `normalized_frequencies` | `function` | **Standard IEC Frequencies.**<br>• `fraction`: 1 or 3 | `freqs = normalized_frequencies(fraction=3)`<br><br>• `freqs`: List of standard center frequencies [Hz] | 3895 4367 | `sweep_signal` | `function` | **ESS excitation (ISO 18233 Annex B).**<br>• `fs`: Sample rate [Hz]<br>• `f1`: Start frequency [Hz]<br>• `f2`: Stop frequency [Hz] (≤ fs/2)<br>• `seconds`: Duration [s]<br>• `amplitude`: Peak (Default: 1.0)<br>• `fade`: Half-Hann fraction (Default: 0.01) | `s = sweep_signal(48000, 20, 20000, 3.0)`<br><br>• 1D exponential sine sweep | 3896 4368 | `inverse_filter` | `function` | **Farina inverse filter for an ESS.**<br>• Same parameters as `sweep_signal` | `inv = inverse_filter(48000, 20, 20000, 3.0)`<br><br>• Time-reversed, +6 dB/oct compensated sweep | 3897 - | `impulse_response` | `function` | **Sweep deconvolution (ISO 18233 B.5).**<br>• `recorded`: Recorded response (1D)<br>• `reference`: Emitted sweep (1D)<br>• `fs`: Sample rate [Hz]<br>• `method`: 'spectral' (Default) or 'farina'<br>• `f_range`: (f1, f2) for 'farina'<br>• `regularization`: Tikhonov term (Default: 1e-6)<br>• `length`: causal samples (Default: len(recorded))<br>• `return_full`: keep distortion tail (Default: False) | `ir = impulse_response(rec, sweep, fs)`<br><br>• 1D impulse response | 4369 + | `impulse_response` | `function` | **Sweep deconvolution (ISO 18233 B.5).**<br>• `recorded`: Recorded response (1D)<br>• `reference`: Emitted sweep (1D)<br>• `fs`: Sample rate [Hz]<br>• `method`: 'spectral' (Default) or 'farina'<br>• `f_range`: (f1, f2) for 'farina'<br>• `regularization`: Tikhonov term (Default: 1e-6)<br>• `length`: causal samples (Default: len(recorded))<br>• `return_full`: keep distortion tail (Default: False) | `ir = impulse_response(rec, sweep, fs)`<br><br>• `ImpulseResponseResult` (array-like; `.plot()`) | 3898 4370 | `mls_signal` | `function` | **Maximum-length sequence (ISO 18233 Annex A).**<br>• `order`: Register length N, 2-20 | `mls = mls_signal(16)`<br><br>• Bipolar sequence, length 2**N − 1 | 3899 - | `mls_impulse_response` | `function` | **IR from a periodic MLS.**<br>• `recorded`: Response spanning whole MLS periods (1D)<br>• `mls`: Excitation sequence<br>• `length`: IR samples (Default: 2**N − 1) | `ir = mls_impulse_response(rec, mls)`<br><br>• Circular cross-correlation / averaging | 4371 + | `mls_impulse_response` | `function` | **IR from a periodic MLS.**<br>• `recorded`: Response spanning whole MLS periods (1D)<br>• `mls`: Excitation sequence<br>• `length`: IR samples (Default: 2**N − 1)<br>• `fs`: optional sample rate for the plot time axis | `ir = mls_impulse_response(rec, mls)`<br><br>• `ImpulseResponseResult` (array-like; `.plot()`) | 4372 + | `ImpulseResponseResult` | `dataclass` | **Recovered impulse response with metadata.**<br>• `ir`: IR samples<br>• `fs` [Hz] or None (e.g. an MLS recovery without one)<br>• `method`: 'spectral'/'farina'/'mls'<br>• array-like: `np.asarray(res)`, indexing, `len()` and `shape`/`dtype` forward to `ir`<br>• `.plot()` | `ir = impulse_response(rec, sweep, fs)`<br>`room_parameters(ir, fs) # drop-in array` | 4373 + | `ImpulseResponseWarning` | `warning class` | **Suspect recovered impulse response.**<br>Emitted e.g. for MLS aliasing in the recovery | `warnings.simplefilter('error', ImpulseResponseWarning)` | 4374 + | `plot_excitation` | `function` | **Plot an excitation signal.**<br>• `signal`: sweep or MLS samples (1D)<br>• `fs`: Sample rate [Hz]<br>• `kind`: `'sweep'` (default) or `'mls'` | `plot_excitation(sweep, fs, kind="sweep")`<br><br>• waveform + spectrogram / spectrum axes | 3900 4375 | `decay_curve` | `function` | **Schroeder decay curve (ISO 3382-1 5.3.3).**<br>• `ir`: Impulse response (1D)<br>• `fs`: Sample rate [Hz]<br>• `band`: Band centre [Hz] (Default: None → broadband)<br>• `fraction`: 1 or 3 (Default: 1)<br>• `zero_phase`: forward-backward band filtering, halves the 125 Hz short-T bias (Default: False) | `dc = decay_curve(ir, fs)` → `DecayCurve`<br>`time, level = dc` still unpacks (`time` [s], `level` [dB], 0 dB at t=0)<br>`dc.plot()` draws the decay + EDT/T20/T30 fits | 4376 + | `DecayCurve` | `dataclass` | **Schroeder decay curve (ISO 3382-1 5.3.3).**<br>• `time`: from the direct sound [s]<br>• `level`: decay [dB], 0 dB at t = 0, up to the noise truncation point<br>• `band`: centre [Hz] or None (broadband)<br>• iterable: unpacks as `time, level`<br>• `.plot()`: decay + EDT/T20/T30 fits | `dc = decay_curve(ir, fs)`<br>`dc.time, dc.level, dc.band` | 3901 4377 | `room_parameters` | `function` | **Room acoustics (ISO 3382-1/2).**<br>• `ir`: Impulse response (1D)<br>• `fs`: Sample rate [Hz]<br>• `limits`: (f_min, f_max) or None (Default: (125, 4000))<br>• `fraction`: 1 or 3 (Default: 1)<br>• `zero_phase`: forward-backward octave filtering, halves the 125 Hz short-T T30 bias (Default: False) | `res = room_parameters(ir, fs)`<br><br>• `RoomAcousticsResult` per band | 3902 4378 | `RoomAcousticsResult` | `dataclass` | **Per-band room parameters.**<br>• `frequency` [Hz]<br>• `edt`, `t20`, `t30` [s]<br>• `c50`, `c80` [dB], `d50`, `ts` [s]<br>• `dynamic_range` [dB]<br>• `edt_valid`/`t20_valid`/`t30_valid`<br>• `curvature` [%] | `res.t30, res.c80, res.t30_valid`<br><br>• Arrays with one entry per band | 3903 4379 | `open_plan_metrics` | `function` | **Open-plan office metrics (ISO 3382-3).**<br>• `positions_m`: Source distances [m] (≥ 4)<br>• `spl_a_speech`: A-weighted speech level per position [dB]<br>• `sti_values`: STI per position | `m = open_plan_metrics(r, lp, sti)`<br><br>• `OpenPlanResult` | ··· 3937 4413 | `single_number_uncertainty` | `function` | **Single-number standard uncertainty u (ISO 12999-1 Tables 3/5/7).**<br>• `quantity`: 'r_w'/'ln_w'/'delta_lw' (+ aliases, +c/+ctr variants)<br>• `situation`: 'A'/'B'/'C'<br>• `upper_limit` (Default: False) | `u = single_number_uncertainty('r_w', 'B')`<br><br>• u [dB] (0.9) | 3938 4414 | `single_number_uncertainty_uncorrelated` | `function` | **Uncorrelated single-number u from bands (ISO 12999-1 Formula B.2).**<br>• `band_uncertainties`: per-band u_i [dB]<br>• `reference_differences`: L_i − R_i [dB] | `u = single_number_uncertainty_uncorrelated(u_i, d_i)`<br><br>• Energy-weighted quadrature u [dB] | 3939 4415 | `maximum_repeatability_standard_deviation` | `function` | **Max repeatability σx per band (ISO 12999-1 Table 1).**<br>• (no parameters) | `b = maximum_repeatability_standard_deviation()`<br><br>• `BandUncertainty` (lab self-verification) | 3940 - | `coverage_factor` | `function` | **Coverage factor k (ISO 12999-1 Table 8).**<br>• `confidence`: fraction (Default: 0.95)<br>• `one_sided` (Default: False) | `k = coverage_factor(0.95)`<br><br>• 1.96 (two-sided) / 1.65 (one-sided) | 3941 - | `expanded_uncertainty` | `function` | **Expanded uncertainty U = k·u (ISO 12999-1 Formula 2).**<br>• `u` [dB]<br>• `coverage`: fraction (Default: 0.95)<br>• `one_sided` (Default: False); enforces k ≥ 1 | `U = expanded_uncertainty(0.9)`<br><br>• 1.764 [dB] | 4416 + | `insulation_coverage_factor` | `function` | **Coverage factor k (ISO 12999-1 Table 8).**<br>• `confidence`: fraction (Default: 0.95)<br>• `one_sided` (Default: False) | `k = insulation_coverage_factor(0.95)`<br><br>• 1.96 (two-sided) / 1.65 (one-sided) | 4417 + | `insulation_expanded_uncertainty` | `function` | **Expanded uncertainty U = k·u (ISO 12999-1 Formula 2).**<br>• `u` [dB]<br>• `coverage`: fraction (Default: 0.95)<br>• `one_sided` (Default: False); enforces k ≥ 1 | `U = insulation_expanded_uncertainty(0.9)`<br><br>• 1.764 [dB] | 3942 4418 | `uncertain_value` | `function` | **Attach U to a rating (ISO 12999-1 Clause 8).**<br>• `value` [dB], `quantity`, `situation`<br>• `coverage` (Default: 0.95), `one_sided` (Default: False), `upper_limit` (Default: False) | `uv = uncertain_value(52.0, 'rprime_w', 'B')`<br><br>• `UncertainValue` (value ± U) | 3943 4419 | `combine_uncertainties` | `function` | **Quadrature combination (ISO 12999-1 Formula C.2).**<br>• `*components`: non-negative u_i [dB] | `uc = combine_uncertainties(1.0, 0.6)`<br><br>• sqrt(Σ u_i²) = 1.166 [dB] | 3944 4420 | `prediction_input_uncertainty` | `function` | **Prediction input uncertainty (ISO 12999-1 Formula A.1).**<br>• `sigma_reproducibility`, `sigma_product` [dB]<br>• `n`: measurements (≥ 1) | `u = prediction_input_uncertainty(1.8, 1.0, 3)`<br><br>• sqrt((σR²+σp²)/n + σp²) [dB] | ··· 3948 4424 | `BandUncertainty` | `dataclass` | **Per-band standard uncertainty (ISO 12999-1).**<br>• `measurand`, `situation`<br>• `frequencies` [Hz], `uncertainties` [dB]<br>• `upper_limit`: σR95 flag<br>• `.to_arrays()` | `b.frequencies, b.uncertainties` | 3949 4425 | `UncertainValue` | `dataclass` | **A value with its expanded uncertainty.**<br>• `value`, `standard_uncertainty`, `expanded_uncertainty` [dB]<br>• `coverage_factor`, `confidence`, `one_sided`<br>• `.lower` = y − U, `.upper` = y + U | `uv.lower, uv.upper` | 3950 4426 | `COVERAGE_FACTORS` | `mapping` | **Table 8 coverage factors (read-only).**<br>Keyed by `(confidence, one_sided)` → k | `COVERAGE_FACTORS[(0.95, False)] # 1.96` | 3951 - | `sound_power_pressure` | `function` | **Sound power from surface pressure (ISO 3744/3746).**<br>• `levels_positions`: (NM, NB) SPL [dB]<br>• `surface`: 'hemisphere' / 'box'<br>• `radius` [m] or `dimensions`+`distance` [m]<br>• `reflecting_planes`: 1/2/3 (Default: 1)<br>• `background_levels`: for K1<br>• `frequencies` [Hz]: for LWA<br>• `reverberation_time`+`room_volume` / `absorption_area` / `mean_absorption_coefficient`+`room_surface`: for K2<br>• `grade`: 'engineering' (Default) / 'survey'<br>• `omc_uncertainty` [dB] (Default: 0) | `res = sound_power_pressure(levels, 'hemisphere', radius=1.5, frequencies=f)`<br><br>• `SoundPowerResult` | 4427 + | `sound_power_pressure` | `function` | **Sound power from surface pressure (ISO 3744/3746).**<br>• `levels_positions`: (NM, NB) SPL [dB]<br>• `surface`: 'hemisphere' / 'box'<br>• `radius` [m] or `dimensions`+`distance` [m]<br>• `reflecting_planes`: 1/2/3 (Default: 1)<br>• `background_levels`: for K1<br>• `frequencies` [Hz]: for LWA<br>• `reverberation_time`+`volume` / `absorption_area` / `mean_absorption_coefficient`+`room_surface`: for K2<br>• `grade`: 'engineering' (Default) / 'survey'<br>• `omc_uncertainty` [dB] (Default: 0) | `res = sound_power_pressure(levels, 'hemisphere', radius=1.5, frequencies=f)`<br><br>• `SoundPowerResult` | 3952 4428 | `measurement_positions` | `function` | **Hemisphere mic coordinates (ISO 3744 Annex B).**<br>• `surface`: 'hemisphere'<br>• `radius` [m]<br>• `reflecting_planes`: 1/2/3<br>• `tones`: Table B.1 vs B.2 (Default: True)<br>• `grade`: 'engineering'/'survey' | `xyz = measurement_positions('hemisphere', radius=1.5)`<br><br>• (N, 3) coordinates [m] | 3953 4429 | `background_noise_correction` | `function` | **Background correction K1 (ISO 3744 Eq. 16).**<br>• `source_levels` [dB]<br>• `background_levels` [dB]<br>• `grade`: 'engineering'/'survey' | `k1 = background_noise_correction(src, bg)`<br><br>• K1 per band [dB] | 3954 - | `environmental_correction` | `function` | **Environmental correction K2 (ISO 3744 Eq. A.2).**<br>• `surface_area` S [m²]<br>• `absorption_area` A, or `reverberation_time`+`room_volume`, or `mean_absorption_coefficient`+`room_surface` | `k2 = environmental_correction(14.1, reverberation_time=0.6, room_volume=300)`<br><br>• K2 [dB] | 4430 + | `environmental_correction` | `function` | **Environmental correction K2 (ISO 3744 Eq. A.2).**<br>• `surface_area` S [m²]<br>• `absorption_area` A, or `reverberation_time`+`volume`, or `mean_absorption_coefficient`+`room_surface` | `k2 = environmental_correction(14.1, reverberation_time=0.6, volume=300)`<br><br>• K2 [dB] | 3955 4431 | `SoundPowerResult` | `dataclass` | **Surface-pressure sound power.**<br>• `sound_power_level` [dB]<br>• `surface_pressure_level`, `mean_pressure_level` [dB]<br>• `background_correction`, `environmental_correction` [dB]<br>• `directivity_index` [dB] `(NM, NB)`<br>• `surface_area` [m²]<br>• `sound_power_level_a` [dB]<br>• `uncertainty` [dB]<br>• `grade` | `res.sound_power_level, res.sound_power_level_a` | 3956 4432 | `sound_power_reverberation` | `function` | **Reverberation-room LW, direct (ISO 3741).**<br>• `levels`: mean room SPL [dB] (1D or (NM, NB))<br>• `t60` [s]<br>• `volume` V [m³]<br>• `surface_area` S [m²]<br>• `frequencies` [Hz] (required)<br>• `background_levels`: for K1<br>• `temperature` [°C] (Default: 23)<br>• `static_pressure` [kPa] (Default: 101.325) | `rev = sound_power_reverberation(lp, t60, 200, 220, f)`<br><br>• `ReverberationSoundPowerResult` | 3957 4433 | `sound_power_comparison` | `function` | **Reverberation-room LW, comparison (ISO 3741).**<br>• `levels`, `levels_ref`: room SPL [dB]<br>• `lw_ref`: reference-source LW [dB]<br>• `frequencies` [Hz]<br>• `background_levels`, `background_levels_ref`<br>• `temperature`, `static_pressure` | `cmp = sound_power_comparison(lp, lp_ref, lw_ref)`<br><br>• `ReverberationSoundPowerResult` (method='comparison') | ··· 3986 4462 | `minimum_cumulative_duration_hours` | `function` | **Minimum cumulative measurement duration [h] (ISO 9612 Table 1).**<br>• `n_workers`: group size n_G (> 40 → 17 h; the standard advises splitting the group) | `minimum_cumulative_duration_hours(18) # 10.75` | 3987 4463 | `COVERAGE_FACTOR` | `float` | **ISO 9612 coverage factor k = 1.65 (Clause 14).**<br>One-sided 95 % confidence: U = 1.65 u, upper limit LEX,8h + U | `COVERAGE_FACTOR # 1.65` | 3988 4464 | `INSTRUMENT_U2` | `mapping` | **Instrument standard uncertainty u2 [dB] (ISO 9612 Table C.5).**<br>Keyed by `'class1'` (0.7), `'class2'` (1.5), `'personal_exposimeter'` (1.5) | `INSTRUMENT_U2['class1'] # 0.7` | 3989 - | `ExposureWarning` | `warning class` | **ISO 9612 sampling advisory.**<br>Emitted for the 3 dB task spread (Clause 9.3), c₁u₁ > 3.5 dB (Clause 10.4) or a short Table 1 cumulative duration | `warnings.simplefilter('error', ExposureWarning)` | 4465 + | `OccupationalExposureWarning` | `warning class` | **ISO 9612 sampling advisory.**<br>Emitted for the 3 dB task spread (Clause 9.3), c₁u₁ > 3.5 dB (Clause 10.4) or a short Table 1 cumulative duration | `warnings.simplefilter('error', OccupationalExposureWarning)` | 3990 4466 | `SoundPowerWarning` | `warning class` | **ISO 3744/3746/3741/9614-2 qualification issue.**<br>Emitted when the background margin is below the criterion, K2 exceeds the validity limit, a band's power is negative, or the room fails qualification; levels are then upper bounds | `warnings.simplefilter('error', SoundPowerWarning)` | 3991 4467 | `AbsorptionWarning` | `warning class` | **ISO 354 advisory.**<br>Emitted for a room below 150 m³, a sample area outside 10-12 m², an out-of-range temperature, or a non-physical α_s ≤ 0; the result still returns | `warnings.simplefilter('error', AbsorptionWarning)` | 3992 - | `.plot()` | `method` | **One-line canonical figure on every result object (soft matplotlib dependency).**<br>Available on `ZwickerLoudness`, `MooreGlasbergLoudness`, `MooreGlasbergTimeVaryingLoudness`, `EcmaLoudness`, `EcmaTonality`, `EcmaRoughness`, `STIResult`, `RoomAcousticsResult`, `DecayCurve`, `WeightedRatingResult`, `ImpactRatingResult`, `FacadeInsulationResult`, `LabAirborneInsulationResult`, `LabImpactInsulationResult`, `SoundPowerResult`, `ReverberationSoundPowerResult`, `SoundPowerIntensityResult` and `IntensityResult`.<br>• `ax`: existing Axes, or None to build a fresh figure (Default: None)<br>• returns the Matplotlib `Axes` (an array of Axes for multi-panel figures); never calls `plt.show()`<br>• needs matplotlib (`pip install phonometry[plot]`) | `res.plot()`<br>`decay_curve(ir, fs).plot()` | 4468 + | `speech_intelligibility_index` | `function` | **Speech Intelligibility Index (ANSI S3.5-1997, one-third-octave method).**<br>• `speech_spectrum`: 18 equivalent speech spectrum levels, 160 Hz–8 kHz [dB SPL], or a vocal-effort name 'normal'/'raised'/'loud'/'shout' (Table 3)<br>• `noise_spectrum`: equivalent noise spectrum levels [dB SPL] (Default: None → quiet, −80 dB)<br>• `threshold`: equivalent hearing threshold [dB HL] (Default: None → 0) | `res = speech_intelligibility_index("normal", noise_spectrum=[40.0]*18)`<br><br>• `SIIResult`; `res.sii` = 0.066 | 4469 + | `standard_speech_spectrum` | `function` | **Standard speech spectrum level by vocal effort (ANSI S3.5-1997 Table 3).**<br>• `vocal_effort`: 'normal', 'raised', 'loud' or 'shout' (Default: 'normal') | `u = standard_speech_spectrum('raised')`<br><br>• 18 band levels [dB SPL] | 4470 + | `SIIResult` | `dataclass` | **SII result.**<br>• `sii`: index in [0, 1]<br>• `band_audibility`: Ai per band (§5.8)<br>• `band_importance`: Ii (Table 3)<br>• `frequencies` [Hz]<br>• `speech_spectrum` / `disturbance` / `masking`: Ei′ / Di / Zi per band [dB]<br>• `.plot()` | `res.sii, res.band_audibility` | 4471 + | `noise_criterion` | `function` | **NC rating, tangency method (ANSI/ASA S12.2-2019).**<br>• `levels`: octave-band SPL [dB] (the 10 bands 16 Hz–8 kHz without `frequencies`)<br>• `frequencies`: optional band centres [Hz] (subset allowed) | `nc = noise_criterion(levels)`<br><br>• `NCResult`; highest NC curve touched | 4472 + | `room_criterion` | `function` | **RC Mark II rating (ANSI/ASA S12.2-2019 Annex D).**<br>• `levels`: octave-band SPL [dB]<br>• `frequencies`: optional band centres [Hz] | `rc = room_criterion(levels)`<br><br>• `RCResult`; LMF average + 'N'/'R'/'H'/'RH' tag | 4473 + | `nc_curve` | `function` | **NC curve levels (Table 1).**<br>• `index`: NC designation 15–70 (intermediate values interpolated band by band) | `nc_curve(35) # [82, 71, 60, ...]`<br><br>• 10 band levels [dB], 16 Hz–8 kHz | 4474 + | `rc_curve` | `function` | **RC Mark II curve (Table D.1).**<br>• `index`: RC designation (value at 1000 Hz) | `rc_curve(35)`<br><br>• −5 dB/octave line, 10 bands [dB] | 4475 + | `NCResult` | `dataclass` | **NC rating result.**<br>• `rating`: NC value<br>• `governing_frequency`: band of the touch [Hz]<br>• `frequencies` [Hz], `levels` [dB]<br>• `.plot()` | `nc.rating, nc.governing_frequency` | 4476 + | `RCResult` | `dataclass` | **RC Mark II result.**<br>• `rating`: LMF rounded [dB], int<br>• `lmf`: 500/1000/2000 Hz average [dB]<br>• `classification`: 'N'/'R'/'H'/'RH'<br>• `reference_curve` [dB], `frequencies` [Hz], `levels` [dB]<br>• `.plot()` | `rc.rating, rc.classification` | 4477 + | `age_threshold` | `function` | **Age-related hearing threshold distribution (ISO 7029:2017).**<br>• `age`: years (≥ 18)<br>• `sex`: 'male'/'female' (Default: 'male')<br>• `fractile`: population fractile in (0, 1) (Default: 0.5)<br>• `frequencies`: subset of the 11 audiometric frequencies [Hz] (Default: None → 125–8000 Hz) | `at = age_threshold(60, 'male')`<br><br>• `AgeThresholdResult`; median at 4 kHz = 20.2 dB | 4478 + | `reference_threshold` | `function` | **Reference threshold of hearing, 0 dB HL (ISO 389-7:2006 Table 1).**<br>• `field`: 'free-field' or 'diffuse-field' (Default: 'free-field')<br>• `frequencies`: optional subset [Hz] | `t = reference_threshold()`<br><br>• dB SPL per audiometric frequency (1 kHz → 2.4) | 4479 + | `AgeThresholdResult` | `dataclass` | **Age threshold distribution.**<br>• `age`, `sex`, `fractile`<br>• `frequencies` [Hz]<br>• `median`: deviation from age 18 [dB]<br>• `spread_upper` / `spread_lower`: half-Gaussian su/sl [dB]<br>• `threshold`: value at `fractile` [dB]<br>• `.plot()` | `at.median, at.threshold` | 4480 + | `nipts` | `function` | **Noise-induced permanent threshold shift (ISO 1999:2013 §6.3).**<br>• `l_ex`: LEX,8h [dB]<br>• `years`: exposure duration (10–40 established; 1–10 extrapolated by Formula 3)<br>• `fractile`: (0, 1) (Default: 0.5)<br>• `frequencies`: subset [Hz] (Default: None → 500–6000 Hz) | `n = nipts(95.0, 20.0)`<br><br>• `NiptsResult`; N50 at 4 kHz = 23.0 dB | 4481 + | `htlan` | `function` | **Threshold associated with age and noise, H′ = H + N − HN/120 (ISO 1999 Formula 1).**<br>• `age`: years (≥ 18), `sex`: 'male'/'female'<br>• `l_ex`: LEX,8h [dB], `years`<br>• `fractile`: (0, 1) applied to both components (Default: 0.5)<br>• `frequencies` | `h = htlan(60, 'male', 95.0, 20.0)`<br><br>• `HtlanResult`; H′ at 4 kHz = 39.3 dB | 4482 + | `NiptsResult` | `dataclass` | **NIPTS distribution.**<br>• `l_ex`, `years`, `fractile`<br>• `frequencies` [Hz]<br>• `median`: N50 (Formula 2/3) [dB]<br>• `value`: NIPTS at `fractile` (Formula 4/5) [dB]<br>• `spread_upper` / `spread_lower`: du/dl [dB]<br>• `.plot()` | `n.median, n.value` | 4483 + | `HtlanResult` | `dataclass` | **Combined HTLAN result.**<br>• `age`, `sex`, `l_ex`, `years`, `fractile`<br>• `frequencies` [Hz]<br>• `htla`: age component H [dB]<br>• `nipts`: noise component N [dB]<br>• `threshold`: combined H′ [dB]<br>• `.plot()` | `h.htla, h.nipts, h.threshold` | 4484 + | `predicted_prominence` | `function` | **Predicted prominence P of an impulse (NT ACOU 112 Formula 1).**<br>• `onset_rate`: slope of the A/F level onset [dB/s] (> 0; qualifies above 10 dB/s)<br>• `level_difference`: level rise over the onset [dB] (> 0) | `predicted_prominence(50.0, 20.0) # 7.7`<br><br>• P = 3 lg(rate) + 2 lg(diff) | 4485 + | `impulse_adjustment` | `function` | **LAeq adjustment KI (Formula 2).**<br>• `prominence`: P | `impulse_adjustment(7.7) # 4.86`<br><br>• 1.8(P − 5) dB for P > 5, else 0 | 4486 + | `impulse_prominence` | `function` | **Governing prominence and adjustment of a set of impulses (clauses 7–8).**<br>• `onset_rates` [dB/s]<br>• `level_differences` [dB] | `r = impulse_prominence([50, 120], [20, 15])`<br><br>• `ImpulseProminenceResult` (P = 8.59, KI = 6.46 dB) | 4487 + | `rating_level` | `function` | **Rating level LAr,T over a reference time (clause 8 Note 1).**<br>• `laeq`: LAeq,N per sub-interval [dB]<br>• `adjustment`: KI,N per sub-interval [dB]<br>• `durations`, `reference_time`: same time unit | `rating_level([55, 60], [0, 4.9], [20, 10], 30) # 60.9` | 4488 + | `ImpulseProminenceResult` | `dataclass` | **Impulse-prominence verdict.**<br>• `onset_rates` [dB/s], `level_differences` [dB]<br>• `per_impulse`: P of each impulse<br>• `prominence`: governing (highest) P<br>• `adjustment`: KI of the governing impulse [dB]<br>• `.plot()` | `r.prominence, r.adjustment` | 4489 + | `multiple_shock_assessment` | `function` | **Full multiple-shock assessment from seat acceleration (ISO 2631-5:2018).**<br>• `acceleration`: vertical seat az(t) [m/s²], `fs` [Hz]<br>• `start_age`: years, `years`: exposure years, `days_per_year`<br>• `exposure_time` / `measurement_time`: scale Dz to a daily dose (Default: None)<br>• `sex`: 'male'/'female' (Default: 'male')<br>• `mz`: MPa per m/s² (Default: sex-specific) | `res = multiple_shock_assessment(az, fs, start_age=25, years=30, days_per_year=220)`<br><br>• `MultipleShockResult` | 4490 + | `seat_to_spine_transfer` | `function` | **Seat-to-spine transfer function H(ω) (clause 5.2 Formula 1).**<br>• `frequencies` [Hz] | `H = seat_to_spine_transfer(freqs)`<br><br>• Complex response; unity at 0 Hz | 4491 + | `spinal_response` | `function` | **Vertical spinal response Az(t) (Formula 2).**<br>• `acceleration`: seat az(t) [m/s²]<br>• `fs` [Hz] | `Az = spinal_response(az, fs)`<br><br>• Same length as the input [m/s²] | 4492 + | `response_peaks` | `function` | **Positive response peaks Az,i (clause 5.3).**<br>• `response`: Az(t) | `peaks = response_peaks(Az)`<br><br>• Maxima between zero crossings [m/s²] | 4493 + | `dose_from_peaks` | `function` | **Acceleration dose Dz from peaks (Formula 3).**<br>• `peaks`: Az,i [m/s²] | `dz = dose_from_peaks(peaks)`<br><br>• Dz = 1.07 (Σ Az,i⁶)^(1/6) [m/s²] | 4494 + | `acceleration_dose` | `function` | **Acceleration dose Dz from a time history (Formulas 2 + 3).**<br>• `acceleration`: seat az(t) [m/s²], `fs` [Hz] | `dz = acceleration_dose(az, fs)`<br><br>• Dz [m/s²] | 4495 + | `daily_dose` | `function` | **Daily dose Dzd (Formula 4).**<br>• `dose`: Dz [m/s²]<br>• `exposure_time` td / `measurement_time` tm: same unit | `daily_dose(4.0, 8.0, 2.0) # 5.04`<br><br>• Dz (td/tm)^(1/6) [m/s²] | 4496 + | `daily_dose_multi` | `function` | **Daily dose from several exposure conditions (Formula 5).**<br>• `doses`: Dz,j [m/s²]<br>• `exposure_times` / `measurement_times`: per condition | `dzd = daily_dose_multi(dz, td, tm)`<br><br>• [Σ Dz,j⁶ (td,j/tm,j)]^(1/6) [m/s²] | 4497 + | `compression_dose` | `function` | **Daily compressive stress Sd (Annex C Formula C.1).**<br>• `daily_dose_value`: Dzd [m/s²]<br>• `mz`: MPa per m/s² (Default: 0.029, 82 kg male) | `compression_dose(5.0) # 0.145`<br><br>• Sd = mz·Dzd [MPa] | 4498 + | `static_stress` | `function` | **Static compressive stress Sstat = mz·9.81 (Annex C).**<br>• `mz` (Default: 0.029) | `static_stress() # 0.284`<br><br>• [MPa] | 4499 + | `ultimate_strength` | `function` | **Ultimate lumbar strength Su at an age (Formula C.4).**<br>• `age`: years<br>• `sex`: 'male'/'female' (Default: 'male') | `ultimate_strength(45.0) # 4.41`<br><br>• 6.75 − Sage·age [MPa] | 4500 + | `injury_risk` | `function` | **Cumulative injury stress variable R (Formula C.3).**<br>• `daily_compression`: Sd [MPa]<br>• `start_age`, `years`, `days_per_year`<br>• `sex`, `mz` | `R = injury_risk(0.5, start_age=25, years=30, days_per_year=220) # 0.509` | 4501 + | `injury_probability` | `function` | **Probability of lumbar injury P(R) (Formula C.5, Weibull).**<br>• `risk`: R<br>• `sex` (Default: 'male') | `injury_probability(0.509) # 0.0389`<br><br>• 1 − exp(−(R/α)^β) in 0–1 | 4502 + | `MultipleShockResult` | `dataclass` | **Multiple-shock health assessment.**<br>• `acceleration_dose` Dz, `daily_dose` Dzd [m/s²]<br>• `compression_dose` Sd [MPa]<br>• `risk` R, `probability` P(R)<br>• `sex`, `start_age`, `years`, `days_per_year`<br>• `peaks` [m/s²], `risk_thresholds`: R at 10/50/90 % (Table C.2)<br>• `.plot()` | `res.risk, res.probability` | 4503 + | `object_fraction` | `function` | **Object fraction ψ of an enclosed space (EN 12354-6 Formula 3).**<br>• `object_volumes` [m³]<br>• `volume`: empty space V [m³] | `object_fraction([12.0, 8.0], 500.0) # 0.04` | 4504 + | `hard_object_absorption` | `function` | **Equivalent absorption area of a hard object (Formula 4).**<br>• `object_volume`: Vobj [m³] | `hard_object_absorption(8.0) # 4.0`<br><br>• Aobj = Vobj^(2/3) [m²] | 4505 + | `air_absorption_area` | `function` | **Equivalent absorption area of the air (Formula 2).**<br>• `m`: power attenuation of air [Np/m]<br>• `volume` V [m³]<br>• `object_fraction` ψ (Default: 0.0) | `air_absorption_area(0.001, 500.0) # 2.0`<br><br>• Aair = 4mV(1 − ψ) [m²] | 4506 + | `equivalent_absorption_area` | `function` | **Total equivalent sound absorption area (Formula 1).**<br>• `surfaces`: sequence of (area, absorption_coefficient) pairs (coefficient scalar or per band)<br>• `objects`: object areas Aobj [m²] (Default: ())<br>• `air_area`: Aair [m²] (Default: 0.0) | `A = equivalent_absorption_area([(100.0, 0.03), (50.0, 0.6)], objects=[4.0], air_area=2.0) # 39.0` | 4507 + | `reverberation_time` | `function` | **Reverberation time from the absorption area (Formula 5).**<br>• `absorption_area` A [m²]<br>• `volume` V [m³]<br>• `object_fraction` ψ (Default: 0.0)<br>• `speed_of_sound` c0 [m/s] (Default: 345.6 → factor 0.16) | `reverberation_time(39.0, 500.0) # 2.05`<br><br>• T = 55.3/c0 · V(1 − ψ)/A [s] | 4508 + | `enclosed_space_reverberation` | `function` | **Predicted A and T per octave band (EN 12354-6 Clause 4).**<br>• `surfaces`: (area, per-band α) pairs<br>• `volume` V [m³]<br>• `objects` [m²], `object_fraction` ψ<br>• `air_condition`: air-attenuation key, e.g. '20C_50-70' (Default: None → neglect air)<br>• `frequencies` [Hz] (Default: 125–8000 Hz octaves)<br>• `speed_of_sound` [m/s] | `res = enclosed_space_reverberation(surfaces, 500.0, air_condition="20C_50-70")`<br><br>• `ReverberationResult` | 4509 + | `ReverberationResult` | `dataclass` | **Enclosed-space absorption result.**<br>• `frequencies` [Hz]<br>• `absorption_area`: A per band [m²]<br>• `reverberation_time`: T per band [s]<br>• `volume` [m³], `object_fraction`<br>• `.plot()` | `res.absorption_area, res.reverberation_time` | 4510 + | `combine_uncertainty` | `function` | **GUM law of propagation of uncertainty (ISO/IEC Guide 98-3 clause 5).**<br>• `model`: measurement function f(x1…xN)<br>• `quantities`: input `Quantity` objects, in argument order<br>• `correlation`: optional N×N matrix r_ij (Default: None → uncorrelated) | `u = combine_uncertainty(lambda a, b: a*b, [Quantity(10.0, 0.1), Quantity(5.0, 0.2)])`<br><br>• `UncertaintyResult`; uc = 2.062 | 4511 + | `monte_carlo` | `function` | **Monte Carlo propagation (Guide 98-3-1, Supplement 1).**<br>• `model`: vectorised f(x1…xN)<br>• `quantities`: input `Quantity` objects<br>• `trials`: M (Default: 1000000)<br>• `coverage`: interval probability (Default: 0.95)<br>• `seed`: reproducibility (Default: None) | `mc = monte_carlo(model, quantities, seed=1)`<br><br>• `MonteCarloResult` (mean, u, coverage interval) | 4512 + | `rectangular` | `function` | **Type B quantity, rectangular PDF (GUM 4.3.7).**<br>• `value`, `half_width` a, `name` | `rectangular(20.0, 0.5)`<br><br>• `Quantity` with u = a/√3 = 0.289 | 4513 + | `triangular` | `function` | **Type B quantity, triangular PDF (GUM 4.3.9).**<br>• `value`, `half_width` a, `name` | `triangular(20.0, 0.5)`<br><br>• `Quantity` with u = a/√6 | 4514 + | `u_shaped` | `function` | **Type B quantity, U-shaped (arcsine) PDF.**<br>• `value`, `half_width` a, `name` | `u_shaped(20.0, 0.5)`<br><br>• `Quantity` with u = a/√2 | 4515 + | `Quantity` | `dataclass` | **Input quantity of a measurement model (GUM clause 4).**<br>• `value`: estimate xi<br>• `uncertainty`: u(xi) ≥ 0<br>• `distribution`: 'gaussian'/'rectangular'/'triangular'/'u-shaped' (Default: 'gaussian')<br>• `dof`: degrees of freedom (Default: inf)<br>• `name`: budget label | `Quantity(94.0, 0.3, name="calibrator")` | 4516 + | `UncertaintyResult` | `dataclass` | **GUM propagation result.**<br>• `value`: y = f(x1…xN)<br>• `combined_uncertainty`: uc(y)<br>• `sensitivities`: ci = ∂f/∂xi<br>• `contributions`: \|ci\|·u(xi)<br>• `effective_dof`: Welch–Satterthwaite<br>• `names`<br>• `.plot()`: uncertainty budget | `u.combined_uncertainty, u.contributions` | 4517 + | `MonteCarloResult` | `dataclass` | **Monte Carlo result.**<br>• `value`: sample mean<br>• `standard_uncertainty`: sample std<br>• `interval`: (low, high) coverage interval (§7.7)<br>• `coverage`, `trials` | `mc.value, mc.interval` | 4518 + | `sound_power_anechoic` | `function` | **Precision sound power in an (hemi-)anechoic room (ISO 3745:2012).**<br>• `levels_positions`: (NM, NB) position levels [dB]<br>• `surface`: 'sphere'/'hemisphere'<br>• `radius` r [m]<br>• `background_levels` + `frequencies`: for K1i<br>• `areas`: partial areas Si (Eq. 13) (Default: equal-area Eq. 12)<br>• `temperature` [°C] (Default: 23), `static_pressure` [kPa] (Default: 101.325)<br>• `air_absorption_coefficient`: α(f) [dB/m] for C3<br>• `sigma_omc` [dB] (Default: 0), `coverage_factor` k (Default: 2.0) | `res = sound_power_anechoic(levels, 'hemisphere', radius=2.0, frequencies=f)`<br><br>• `PrecisionSoundPowerResult` | 4519 + | `PrecisionSoundPowerResult` | `dataclass` | **ISO 3745 sound power result.**<br>• `sound_power_level` [dB], `sound_power_level_a` [dB]<br>• `surface_pressure_level`, `mean_pressure_level` [dB]<br>• `background_correction`: K1i (NM, NB) [dB]<br>• `c1`, `c2`, `c3`: meteorological corrections [dB]<br>• `directivity_index` DIi, `non_uniformity_index` VIr [dB]<br>• `surface_area` [m²], `surface`<br>• `uncertainty`, `uncertainty_bands`, `coverage_factor`<br>• `.plot()` | `res.sound_power_level, res.uncertainty` | 4520 + | `precision_positions` | `function` | **ISO 3745 microphone coordinates (Annex D/E).**<br>• `surface`: 'sphere' (Table D.1) / 'hemisphere' (Tables E.1/E.2)<br>• `radius` r [m]<br>• `array`: 'general'/'broadband' (Default: 'general')<br>• `count`: 20 or 40 (Default: 40) | `xyz = precision_positions('hemisphere', radius=2.0)`<br><br>• (40, 3) coordinates [m] | 4521 + | `precision_background_correction` | `function` | **Per-position background correction K1i (ISO 3745 Eq. 11).**<br>• `source_levels` / `background_levels`: (NM, NB) [dB]<br>• `frequencies` [Hz]: per-band criterion (10 dB for 250–5000 Hz, 6 dB outside) | `k1 = precision_background_correction(src, bg, f)`<br><br>• K1i [dB]; clamped + `SoundPowerWarning` below the criterion | 4522 + | `meteorological_corrections` | `function` | **Corrections C1, C2, C3 (ISO 3745 Eq. 14 block).**<br>• `temperature` θ [°C] (Default: 23), `static_pressure` ps [kPa] (Default: 101.325)<br>• `air_absorption_coefficient`: α(f) [dB/m] for C3 (Default: None → C3 = 0)<br>• `radius` r [m] (Default: 1.0) | `met = meteorological_corrections(23.0, 101.325)`<br><br>• `MeteorologicalCorrection`; C1 = −0.128 dB, C2 = 0 at reference | 4523 + | `MeteorologicalCorrection` | `dataclass` | **C1/C2/C3 corrections.**<br>• `c1`: reference-quantity correction [dB]<br>• `c2`: radiation-impedance correction [dB]<br>• `c3`: air-absorption correction [dB], scalar or per band | `met.c1, met.c2, met.c3` | 4524 + | `precision_uncertainty` | `function` | **Expanded uncertainty U = k·√(σR0² + σomc²) (ISO 3745 Eq. 24/25).**<br>• `sigma_r0`: reproducibility (Tables 2/3) [dB]<br>• `sigma_omc` [dB] (Default: 0.0)<br>• `coverage_factor` k (Default: 2.0; 1.6 one-sided) | `precision_uncertainty(0.5) # 1.0`<br><br>• U [dB], scalar or per band | 4525 + | `sound_power_intensity_precision` | `function` | **Sound power by intensity scanning, precision (ISO 9614-3:2002).**<br>• `partial_intensity`: (N, NB) signed In,i [W/m²]<br>• `areas`: partial areas Si [m²]<br>• `frequencies` [Hz]: for LWA<br>• `temperature` [°C] (Default: 23), `barometric_pressure` [Pa] (Default: 101325): for LW0 (Eq. 10) | `res = sound_power_intensity_precision(In, areas, frequencies=f)`<br><br>• `PrecisionIntensityResult` | 4526 + | `PrecisionIntensityResult` | `dataclass` | **ISO 9614-3 scanning result.**<br>• `partial_power`: Pi = In,i·Si [W]<br>• `sound_power` P, `sound_power_level` LW [dB] (NaN where P ≤ 0)<br>• `sound_power_level_normalized`: LW0 [dB]<br>• `not_applicable_band`: per-band bool<br>• `surface_area` [m²], `sound_power_level_a` [dB]<br>• `.plot()` | `res.sound_power_level, res.not_applicable_band` | 4527 + | `precision_field_indicators` | `function` | **ISO 9614-3 Annex B field indicators.**<br>• `segment_intensity`: (N, NB) signed In,j [W/m²]<br>• `segment_pressure_levels`: (N, NB) Lpj [dB]<br>• `time_window_intensity`: (M, NB) for FT (Default: None) | `fi = precision_field_indicators(In, lp)`<br><br>• `PrecisionFieldIndicators` | 4528 + | `PrecisionFieldIndicators` | `dataclass` | **Annex B indicators (per band).**<br>• `ft`: temporal variability (= F1) or None<br>• `f_pi_unsigned`: F_pIn unsigned (= F2)<br>• `f_pi_signed`: F_pIn signed (= F3), ≥ unsigned<br>• `fs`: field non-uniformity FS (= F4) | `fi.f_pi_signed, fi.fs` | 4529 + | `precision_qualification` | `function` | **The five ISO 9614-3 Annex C acceptance criteria.**<br>• `indicators`: `PrecisionFieldIndicators`<br>• `scan_intensity_level_1`/`_2`: LIn per scan [dB] (criterion 1)<br>• `pressure_residual_index`: δpI0 [dB] (criterion 2, K = 10)<br>• `field_nonuniformity_1`/`_2`: FS per scan density (criterion 5)<br>• `frequencies` [Hz] or `repeatability_limit`: Table 1 limit s | `q = precision_qualification(fi, pressure_residual_index=18.0)`<br><br>• `PrecisionCriteria` | 4530 + | `PrecisionCriteria` | `dataclass` | **Annex C pass/fail per band.**<br>• `criterion_1`…`criterion_5`: bool arrays or None where not evaluable<br>• `qualified`: conjunction of criteria 1–4 or None | `q.qualified, q.criterion_2` | 4531 + | `plane_wave_frequency_range` | `function` | **Working plane-wave range (f_l, f_u) (ISO 10534-2 §4.2–4.5).**<br>• `spacing` s [m]<br>• `speed_of_sound` c0 [m/s]<br>• `diameter` d [m] (Default: None → spacing bound only)<br>• `shape`: 'circular'/'rectangular' (Default: 'circular') | `plane_wave_frequency_range(0.05, 343.2, diameter=0.1) # (343.2, 1990.6)`<br><br>• [Hz] | 4532 + | `speed_of_sound_iso` | `function` | **Speed of sound (ISO 10534-2 Eq. 5).**<br>• `temperature` T [K] | `speed_of_sound_iso(293.0) # 343.2`<br><br>• c0 = 343.2 √(T/293) [m/s] | 4533 + | `air_density_iso` | `function` | **Air density (ISO 10534-2 Eq. 7).**<br>• `temperature` T [K]<br>• `atmospheric_pressure` [kPa] (Default: 101.325) | `air_density_iso(293.0) # 1.186`<br><br>• ρ [kg/m³] | 4534 + | `speed_of_sound_astm` | `function` | **Speed of sound (ASTM E2611-19 Eq. 4).**<br>• `temperature` T [°C] | `speed_of_sound_astm(20.0) # 343.2`<br><br>• 20.047 √(273.15 + T) [m/s] | 4535 + | `air_density_astm` | `function` | **Air density (ASTM E2611-19 Eq. 5).**<br>• `temperature` T [°C]<br>• `atmospheric_pressure` P [kPa] (Default: 101.325) | `air_density_astm(20.0) # 1.202`<br><br>• ρ [kg/m³] | 4536 + | `characteristic_impedance` | `function` | **Characteristic impedance of air ρc.**<br>• `density` ρ [kg/m³]<br>• `speed_of_sound` c [m/s] | `characteristic_impedance(1.186, 343.2) # 407.0`<br><br>• [rayl] | 4537 + | `tube_attenuation_constant` | `function` | **Lower-bound tube attenuation k0″ (ISO 10534-2 Eq. A.18).**<br>• `frequency` f [Hz]<br>• `speed_of_sound` c0 [m/s]<br>• `diameter` d [m] (hydraulic for rectangular) | `tube_attenuation_constant(1000.0, 343.2, 0.1) # 0.0179`<br><br>• [Np/m] | 4538 + | `tube_wavenumber` | `function` | **Complex wavenumber k0 = k0′ − j k0″ (ISO 10534-2 §2.6).**<br>• `frequency` f [Hz]<br>• `speed_of_sound` c0 [m/s]<br>• `attenuation`: k0″ [Np/m] (Default: None → lossless) | `tube_wavenumber(1000.0, 343.2, attenuation=0.018)`<br><br>• 18.308 − 0.018j [1/m] | 4539 + | `mic_calibration_factor` | `function` | **Microphone-mismatch calibration factor Hc (ISO 10534-2 Eq. 10).**<br>• `h12_config1`: H12 in the standard configuration<br>• `h12_config2`: H12 with the microphones interchanged | `hc = mic_calibration_factor(h12_a, h12_b)`<br><br>• Hc = √(H12ᴵ/H12ᴵᴵ) (complex) | 4540 + | `apply_mic_calibration` | `function` | **Apply the calibration factor (Eq. 13).**<br>• `h12_uncorrected`<br>• `calibration_factor`: Hc | `h12 = apply_mic_calibration(h12_raw, hc)`<br><br>• H12 = H12,raw/Hc | 4541 + | `reflection_factor` | `function` | **Complex reflection factor at the sample surface (ISO 10534-2 Eq. 17).**<br>• `h12`: corrected transfer function<br>• `spacing` s [m]<br>• `x1`: sample to the farther microphone [m]<br>• `wavenumber`: complex k0 | `r = reflection_factor(h12, spacing=0.05, x1=0.15, wavenumber=k0)`<br><br>• r = (H12 − HI)/(HR − H12)·e^(+2jk0x1) | 4542 + | `absorption_from_reflection` | `function` | **Normal-incidence absorption coefficient (Eq. 18).**<br>• `reflection`: complex r | `absorption_from_reflection(0.5) # 0.75`<br><br>• α = 1 − \|r\|² | 4543 + | `surface_impedance` | `function` | **Absolute surface impedance Z (Eq. 19).**<br>• `reflection`: complex r<br>• `characteristic_impedance`: ρc0 [rayl] | `surface_impedance(0.5, 407.0) # 1221`<br><br>• Z = ρc0(1 + r)/(1 − r) [rayl] | 4544 + | `normalized_surface_impedance` | `function` | **Normalised surface impedance Z/(ρc0) (Eq. 19).**<br>• `reflection`: complex r | `normalized_surface_impedance(0.5) # 3.0` | 4545 + | `normalized_surface_admittance` | `function` | **Normalised surface admittance Gρc0 (Eq. 20).**<br>• `reflection`: complex r | `normalized_surface_admittance(0.5) # 0.333`<br><br>• (1 − r)/(1 + r) | 4546 + | `two_microphone_impedance` | `function` | **Full two-microphone reduction (ISO 10534-2 Clause 7).**<br>• `h12`: corrected transfer function<br>• `frequency` f [Hz]<br>• `spacing` s [m], `x1` [m]<br>• `speed_of_sound` c0 [m/s], `characteristic_impedance` ρc0 [rayl]<br>• `attenuation`: k0″ [Np/m] (Default: None)<br>• `diameter` + `shape`: activate the plane-wave range check | `res = two_microphone_impedance(h12, frequency=f, spacing=0.05, x1=0.15, speed_of_sound=343.2, characteristic_impedance=407.0)`<br><br>• `ImpedanceTubeResult` | 4547 + | `ImpedanceTubeResult` | `dataclass` | **Two-microphone result per frequency.**<br>• `frequency` [Hz]<br>• `reflection`: complex r (Eq. 17)<br>• `surface_impedance` [rayl], `normalized_impedance` (Eq. 19)<br>• `absorption`: α = 1 − \|r\|² (Eq. 18) | `res.absorption, res.normalized_impedance` | 4548 + | `ImpedanceTubeWarning` | `warning class` | **ISO 10534-2 advisory.**<br>Emitted by `two_microphone_impedance` for frequencies outside the plane-wave working range (Eqs. 1–4); the results are still returned | `warnings.simplefilter('error', ImpedanceTubeWarning)` | 4549 + | `standing_wave_ratio_from_level` | `function` | **Standing-wave ratio from ΔL (ISO 10534-1 Eq. 15).**<br>• `level_difference`: Lmax − Lmin [dB] | `standing_wave_ratio_from_level(20.0) # 10.0`<br><br>• s = 10^(ΔL/20) | 4550 + | `standing_wave_reflection_magnitude` | `function` | **Reflection magnitude from the SWR (Eq. 14).**<br>• `swr`: s ≥ 1 | `standing_wave_reflection_magnitude(10.0) # 0.818`<br><br>• \|r\| = (s − 1)/(s + 1) | 4551 + | `standing_wave_reflection` | `function` | **Complex reflection factor from the standing wave (Eqs. 17–23).**<br>• `swr`: s<br>• `first_min_distance`: x_min1 [m]<br>• `wavelength`: λ0 [m] | `standing_wave_reflection(10.0, 0.05, 0.4) # -0.818j`<br><br>• phase φ = π(4x_min1/λ0 − 1) | 4552 + | `standing_wave_absorption` | `function` | **Absorption from the SWR (Eqs. 9 + 14).**<br>• `swr`: s ≥ 1 | `standing_wave_absorption(10.0) # 0.331`<br><br>• α = 4s/(s + 1)² | 4553 + | `standing_wave_normalized_impedance` | `function` | **Normalised impedance from the standing wave (Eqs. 24–26).**<br>• `swr`, `first_min_distance` [m], `wavelength` [m] | `standing_wave_normalized_impedance(10.0, 0.05, 0.4)`<br><br>• 0.198 − 0.98j | 4554 + | `wave_decomposition` | `function` | **Decompose the field into (A, B, C, D) (ASTM E2611-19 Eqs. 17–20).**<br>• `h1`…`h4`: microphone transfer functions<br>• `l1`, `s1`, `l2`, `s2`: geometry [m]<br>• `wavenumber` k | `A, B, C, D = wave_decomposition(h1, h2, h3, h4, l1=0.2, s1=0.05, l2=0.2, s2=0.05, wavenumber=k)`<br><br>• Forward/backward amplitudes, both sides | 4555 + | `face_quantities` | `function` | **Face pressures and velocities (Eq. 21).**<br>• `a`, `b`, `c`, `d`: wave amplitudes<br>• `wavenumber` k, `thickness` d [m], `characteristic_impedance` ρc | `p0, pd, u0, ud = face_quantities(A, B, C, D, wavenumber=k, thickness=0.05, characteristic_impedance=407.0)` | 4556 + | `transfer_matrix_two_load` | `function` | **Two-load transfer matrix (ASTM E2611-19 Eqs. 17–22).**<br>• `load_a`, `load_b`: (H1, H2, H3, H4) per termination<br>• `l1`, `s1`, `l2`, `s2`, `thickness` [m]<br>• `wavenumber`, `characteristic_impedance` | `T = transfer_matrix_two_load(load_a, load_b, l1=0.2, s1=0.05, l2=0.2, s2=0.05, thickness=0.05, wavenumber=k, characteristic_impedance=407.0)`<br><br>• `TransferMatrix` | 4557 + | `transfer_matrix_one_load` | `function` | **One-load transfer matrix, symmetric specimen (Eqs. 23–24).**<br>• `load`: (H1, H2, H3, H4)<br>• Same geometry parameters as `transfer_matrix_two_load` | `T = transfer_matrix_one_load(load, l1=0.2, s1=0.05, l2=0.2, s2=0.05, thickness=0.05, wavenumber=k, characteristic_impedance=407.0)`<br><br>• Requires reciprocity + symmetry | 4558 + | `air_layer_transfer_matrix` | `function` | **Analytic loss-free air-layer matrix (validation reference).**<br>• `wavenumber` k<br>• `thickness` d [m]<br>• `characteristic_impedance` ρc [rayl] | `T = air_layer_transfer_matrix(k, 0.05, 407.0)`<br><br>• det(T) = 1, T11 = T22 | 4559 + | `TransferMatrix` | `dataclass` | **Acoustic transfer matrix [[T11, T12], [T21, T22]] (ASTM E2611-19 Eq. 16).**<br>• `t11`, `t12`, `t21`, `t22`: complex, scalar or per frequency | `T.t11, T.t12` | 4560 + | `airflow_resistance` | `function` | **Airflow resistance R = Δp/qv (ISO 9053-1:2018 §3.1).**<br>• `pressure_drop` Δp [Pa]<br>• `volume_flow_rate` qv [m³/s] | `airflow_resistance(2.0, 0.0005) # 4000.0`<br><br>• [Pa·s/m³] | 4561 + | `specific_airflow_resistance` | `function` | **Specific airflow resistance Rs (§3.2).**<br>• `resistance` R + `area` A, **or** `pressure_drop` Δp + `velocity` u | `specific_airflow_resistance(4000.0, 0.01) # 40.0`<br><br>• Rs = R·A = Δp/u [Pa·s/m] | 4562 + | `airflow_resistivity` | `function` | **Airflow resistivity σ = Rs/d (§3.3).**<br>• `specific_resistance` Rs [Pa·s/m]<br>• `thickness` d [m] | `airflow_resistivity(40.0, 0.05) # 800.0`<br><br>• [Pa·s/m²] | 4563 + | `linear_airflow_velocity` | `function` | **Linear airflow velocity u = qv/A (§3.4).**<br>• `volume_flow_rate` qv [m³/s]<br>• `area` A [m²] | `linear_airflow_velocity(5e-6, 0.01) # 0.0005`<br><br>• [m/s] | 4564 + | `static_airflow_resistance` | `function` | **Stepwise static-method determination (ISO 9053-1 §7.5).**<br>• `velocities` u [m/s], `pressure_drops` Δp [Pa] (≥ 2 steps)<br>• `area` A [m²]<br>• `thickness` d [m] (Default: None → no σ)<br>• `evaluation_velocity` [m/s] (Default: 0.0005) | `res = static_airflow_resistance([0.0005, 0.001, 0.002], [0.02, 0.041, 0.086], 0.01, 0.05)`<br><br>• `StaticAirflowResult` (Rs = 40.0, σ = 800) | 4565 + | `StaticAirflowResult` | `dataclass` | **Static-method result at the evaluation velocity.**<br>• `resistance` R [Pa·s/m³], `specific_resistance` Rs [Pa·s/m]<br>• `resistivity` σ [Pa·s/m²] or None<br>• `evaluation_velocity` [m/s], `pressure_drop` [Pa]<br>• `linear_coefficient` a (zero-velocity Rs), `quadratic_coefficient` b | `res.specific_resistance, res.resistivity` | 4566 + | `piston_volume_flow_rate` | `function` | **RMS piston volume flow qv = 2πf·h·AP (ISO 9053-2 §6.2).**<br>• `frequency` f [Hz]<br>• `stroke_amplitude` h [m]<br>• `piston_area` AP [m²] | `piston_volume_flow_rate(2.0, 0.005, 0.01) # 0.000628`<br><br>• [m³/s] | 4567 + | `alternating_airflow_resistance` | `function` | **Alternating-method resistance (ISO 9053-2:2020 Formula 2).**<br>• `level_specimen` Lps / `level_termination` Lpt [dB]<br>• `piston_stroke_specimen` hs / `piston_stroke_termination` ht [m]<br>• `frequency` f [Hz] (1–4 Hz), `cavity_volume` V [m³]<br>• `static_pressure` [Pa] (Default: 101325)<br>• `kappa_prime` κ′ (Default: 1.4; use `effective_kappa` for Annex A conformity)<br>• `background_level` Lpb [dB] (Default: None) | `r = alternating_airflow_resistance(78.0, 60.0, piston_stroke_specimen=5e-3, piston_stroke_termination=5e-4, frequency=2.0, cavity_volume=7.854e-4)`<br><br>• R [Pa·s/m³]; `AirflowResistanceWarning` when criteria fail | 4568 + | `thermal_boundary_layer_thickness` | `function` | **Thermal boundary-layer thickness b (ISO 9053-2 Formulas A.4/A.5).**<br>• `frequency` f [Hz]<br>• Air properties (Default: Annex A.3 values) | `thermal_boundary_layer_thickness(2.0) # 0.00183`<br><br>• [m] | 4569 + | `effective_kappa` | `function` | **Effective ratio of specific heats κ′ (Formula A.7).**<br>• `cavity_surface` S [m²], `cavity_volume` V [m³]<br>• `frequency` f [Hz]<br>• Air properties (Default: Annex A.3 values) | `effective_kappa(0.0471, 7.854e-4, 2.0) # 1.37`<br><br>• The Annex A.3 worked example | 4570 + | `AirflowResistanceWarning` | `warning class` | **ISO 9053 advisory.**<br>Emitted for a velocity above the 15 mm/s static limit, a piston frequency outside 1–4 Hz, or a failed Formula 3/4 validity criterion | `warnings.simplefilter('error', AirflowResistanceWarning)` | 4571 + | `practical_absorption_coefficient` | `function` | **Practical coefficients αp (ISO 11654 §4.1).**<br>• `third_octave_alpha_s`: 15 one-third-octave αs, 200–5000 Hz (sequence or mapping keyed by band) | `ap = practical_absorption_coefficient(alpha_s)`<br><br>• 5 octave values (250–4000 Hz), 0.05 steps, capped at 1.00 | 4572 + | `weighted_absorption` | `function` | **Weighted absorption αw + class (ISO 11654 §4.2).**<br>• `alpha_p`: the 5 octave practical coefficients | `w = weighted_absorption([0.25, 0.7, 0.95, 1.0, 0.85])`<br><br>• `AbsorptionRatingResult` (αw = 0.55, class D, 'MH') | 4573 + | `absorption_class` | `function` | **Sound absorption class (Table B.1).**<br>• `alpha_w`: multiple of 0.05 in [0, 1] | `absorption_class(0.7) # 'C'`<br><br>• 'A'–'E' or 'Not classified' | 4574 + | `AbsorptionRatingResult` | `dataclass` | **αw rating result.**<br>• `alpha_w`: shifted curve read at 500 Hz<br>• `shape_indicator`: 'L'/'M'/'H' concatenation or ''<br>• `absorption_class`: 'A'–'E'/'Not classified'<br>• `shift`, `unfavourable_sum` (≤ 0.10)<br>• `band_centers` [Hz], `measured`, `shifted_reference`<br>• `.plot()` | `w.alpha_w, w.absorption_class` | 4575 + | `OCTAVE_BANDS` | `tuple` | **ISO 11654 octave rating bands [Hz].**<br>(250, 500, 1000, 2000, 4000) | `OCTAVE_BANDS # (250, ..., 4000)` | 4576 + | `THIRD_OCTAVE_BANDS` | `tuple` | **ISO 11654 one-third-octave input bands [Hz].**<br>200 Hz to 5000 Hz (15 bands) | `THIRD_OCTAVE_BANDS[0] # 200` | 4577 + | `REFERENCE_CURVE` | `mapping` | **ISO 11654 Figure 1 reference curve (read-only).**<br>Keyed by octave band [Hz] → coefficient | `REFERENCE_CURVE[250] # 0.8` | 4578 + | `speed_of_sound` | `function` | **Speed of sound (ISO 17497-1 Eq. 2).**<br>• `temperature` t [°C] | `speed_of_sound(20.0) # 343.2`<br><br>• 343.2 √((273.15 + t)/293.15) [m/s] | 4579 + | `air_attenuation_coefficient` | `function` | **Energy attenuation m from ISO 9613-1 α (ISO 17497-1 Eq. 3).**<br>• `pressure_attenuation_db_per_m`: α [dB/m] | `air_attenuation_coefficient(0.005) # 0.00115`<br><br>• m ≈ α/4.343 [1/m] | 4580 + | `random_incidence_absorption` | `function` | **Random-incidence absorption αs (ISO 17497-1 Eq. 1).**<br>• `volume` V [m³], `area` S [m²]<br>• `c1`/`T1`: static base plate, no sample<br>• `c2`/`T2`: with the test sample<br>• `m1`/`m2`: air attenuation [1/m] (Default: 0) | `a_s = random_incidence_absorption(200.0, 10.8, c1=343.0, T1=2.5, c2=343.2, T2=1.8) # 0.463` | 4581 + | `specular_absorption_coefficient` | `function` | **Specular absorption αspec (Eq. 4).**<br>• `volume`, `area`<br>• `c3`/`T3`: rotating base plate, no sample<br>• `c4`/`T4`: sample on the rotating turntable<br>• `m3`/`m4` (Default: 0) | `a_spec = specular_absorption_coefficient(200.0, 10.8, c3=c3, T3=t3, c4=c4, T4=t4)`<br><br>• Includes the energy lost to scattering | 4582 + | `scattering_coefficient` | `function` | **Random-incidence scattering coefficient s (Eq. 5).**<br>• `alpha_spec`, `alpha_s`<br>• `truncate_negative`: clip s < 0 to 0 (Default: True); s > 1 is kept | `scattering_coefficient(0.45, 0.30) # 0.214`<br><br>• s = (αspec − αs)/(1 − αs) | 4583 + | `scattering_coefficient_spectrum` | `function` | **Scattering spectrum s(f) (Eq. 5).**<br>• `frequencies`: one-third-octave centres [Hz]<br>• `specular_absorption`, `random_absorption`: per band<br>• `truncate_negative` (Default: True) | `res = scattering_coefficient_spectrum(f, a_spec, a_s)`<br><br>• `ScatteringResult` | 4584 + | `ScatteringResult` | `dataclass` | **Scattering spectrum result.**<br>• `frequencies` [Hz]<br>• `scattering`: s per band<br>• `random_incidence`: αs, `specular`: αspec<br>• `.plot()` | `res.scattering` | 4585 + | `base_plate_scattering` | `function` | **Scattering of the base plate alone (Eq. 6).**<br>• `volume`, `area`<br>• `c1`/`T1`: static, `c3`/`T3`: rotating<br>• `m1`/`m3` (Default: 0) | `s_base = base_plate_scattering(200.0, 10.8, c1=c1, T1=t1, c3=c3, T3=t3)`<br><br>• Quality metric vs Table 1 | 4586 + | `check_base_plate_scattering` | `function` | **Verify the base plate against Table 1 (Clause 6.2).**<br>• `scattering`: mapping by band [Hz] or 18 values ordered as `BASE_PLATE_BANDS` | `check_base_plate_scattering(s_base) # ()`<br><br>• Offending band centres; `ScatteringDiffusionWarning` if any | 4587 + | `BASE_PLATE_BANDS` | `tuple` | **ISO 17497-1 base-plate check bands [Hz].**<br>100 Hz to 5000 Hz (18 one-third octaves) | `BASE_PLATE_BANDS[0] # 100` | 4588 + | `BASE_PLATE_MAX_SCATTERING` | `mapping` | **Table 1 base-plate scattering limits (read-only).**<br>Keyed by band [Hz] → maximum s | `BASE_PLATE_MAX_SCATTERING[500] # 0.05` | 4589 + | `reverberation_time_uncertainty` | `function` | **Standard uncertainty of a mean T (ISO 17497-1 Eq. A.1).**<br>• `times`: N ≥ 2 spatially-averaged measurements [s] | `reverberation_time_uncertainty([2.31, 2.28, 2.35]) # 0.0203`<br><br>• Standard error of the mean [s] | 4590 + | `absorption_coefficient_uncertainty` | `function` | **Uncertainty of a Sabine coefficient (Eqs. A.3/A.4).**<br>• `volume`, `area`<br>• `c` [m/s]<br>• `T_a`/`u_a`, `T_b`/`u_b`: the two situations [s] | `u_alpha = absorption_coefficient_uncertainty(200.0, 10.8, c=343.2, T_a=2.5, u_a=0.02, T_b=1.8, u_b=0.02)` | 4591 + | `scattering_coefficient_uncertainty` | `function` | **Uncertainty of the scattering coefficient (Eq. A.5).**<br>• `alpha_spec`, `alpha_s`<br>• `u_alpha_spec`, `u_alpha_s` | `u = scattering_coefficient_uncertainty(0.45, 0.30, 0.02, 0.02)`<br><br>• `ScatteringUncertainty` (U = 2u, 95 %) | 4592 + | `ScatteringUncertainty` | `dataclass` | **Scattering uncertainty result.**<br>• `u_scattering`: combined standard u_s<br>• `expanded`: U = 2u_s | `u.u_scattering, u.expanded` | 4593 + | `directional_diffusion_coefficient` | `function` | **Directional diffusion coefficient d_θ (ISO 17497-2 Formulas 5/6).**<br>• `levels`: n ≥ 2 reflected SPL [dB] (−inf = zero energy)<br>• `area_weights`: Ni (Formula 8) (Default: None → equal-area Formula 5) | `directional_diffusion_coefficient([-10., -12., -15., -11., -13.]) # 0.854` | 4594 + | `directional_diffusion` | `function` | **Polar response + its coefficient.**<br>• `angles` [°], `levels` [dB]<br>• `weights`: Ni (Default: None) | `res = directional_diffusion(angles, levels)`<br><br>• `DiffusionResult` | 4595 + | `DiffusionResult` | `dataclass` | **Polar diffusion result.**<br>• `angles` [°], `levels` [dB]<br>• `coefficient`: d_θ (autocorrelation)<br>• `.plot()` | `res.coefficient` | 4596 + | `normalized_diffusion_coefficient` | `function` | **Normalised coefficient d_θ,n (Formula 7).**<br>• `d_theta`: test surface<br>• `d_theta_reference`: flat reference | `normalized_diffusion_coefficient(0.6, 0.2) # 0.5`<br><br>• (d − d_r)/(1 − d_r) | 4597 + | `area_factors` | `function` | **Per-receiver area weights Ni (Clause 8.3 Formula 8).**<br>• `elevations`: θ [°], 0–90<br>• `delta_theta` [°]<br>• `delta_phi` [°] (Default: None → `delta_theta`) | `n_i = area_factors([0, 15, 30, 45, 60, 75, 90], delta_theta=15.0)`<br><br>• Dimensionless, min 1 | 4598 + | `random_incidence_diffusion` | `function` | **Random-incidence diffusion coefficient d (Clause 8.4).**<br>• `directional_coefficients`: d_θ per source<br>• `weights`: source weights (Default: None → equal; 2-D uses `TWO_DIMENSIONAL_SOURCE_WEIGHTS`) | `d = random_incidence_diffusion(d_thetas, weights=TWO_DIMENSIONAL_SOURCE_WEIGHTS)` | 4599 + | `TWO_DIMENSIONAL_SOURCE_WEIGHTS` | `tuple` | **ISO 17497-2 single-plane source weights.**<br>(1, 3, 3, 3, 3) for 0°, ±30°, ±60° | `TWO_DIMENSIONAL_SOURCE_WEIGHTS` | 4600 + | `ScatteringDiffusionWarning` | `warning class` | **ISO 17497 advisory.**<br>Emitted for out-of-range scattering/diffusion measurement conditions (e.g. a base plate over the Table 1 limits) | `warnings.simplefilter('error', ScatteringDiffusionWarning)` | 4601 + | `adrienne_window` | `function` | **Adrienne temporal window (ISO 13472-1 Clause 6.4).**<br>• `fs` [Hz]<br>• `flat_duration` [s] (Default: 0.005)<br>• `leading_duration` [s] (Default: 0.0005), `trailing_duration` [s] (Default: 0.005)<br>• `leading_edge`/`trailing_edge`: 'blackman-harris' or 'cosine-squared' | `w = adrienne_window(48000)`<br><br>• Rising edge + flat top + falling edge, peak 1.0 | 4602 + | `geometric_spreading_factor` | `function` | **Geometrical-spreading factor Kr (Clause 4.1).**<br>• `source_height` ds [m] (Default: 1.25)<br>• `mic_height` dm [m] (Default: 0.25) | `geometric_spreading_factor() # 0.6667`<br><br>• Kr = (ds − dm)/(ds + dm) | 4603 + | `geometric_spreading_factor_angle` | `function` | **Oblique factor Kr,θ (Annex F).**<br>• `incidence_angle` θ [rad]<br>• `source_height`, `mic_height` | `geometric_spreading_factor_angle(np.pi/6) # 0.764` | 4604 + | `reflected_path_delay` | `function` | **Reflected-path delay Δτ = 2dm/c (Annex C).**<br>• `mic_height` dm [m] (Default: 0.25)<br>• `speed_of_sound` c [m/s] (Default: 340.0) | `reflected_path_delay() # 0.001471`<br><br>• [s] | 4605 + | `insitu_reflection_factor` | `function` | **Complex reflection factor r(f) (ISO 13472-1 Clause 4.1).**<br>• `incident_ir` / `reflected_ir`: windowed impulse responses<br>• `source_height`, `mic_height` [m]<br>• `incidence_angle` [rad] (Default: 0)<br>• `fs` + `delay`: undo the reflected-path offset (Default: None)<br>• `n`: FFT length | `r = insitu_reflection_factor(hi, hr, fs=48000, delay=dtau)`<br><br>• (1/Kr)·Hr(f)/Hi(f) at the rfft bins | 4606 + | `insitu_absorption_from_reflection` | `function` | **α from the reflection factor (Clause 4.1).**<br>• `reflection`: complex r | `alpha = insitu_absorption_from_reflection(r)`<br><br>• α = 1 − \|r\|² | 4607 + | `power_reflection_coefficient` | `function` | **Power reflection factor QW(f), direct energy route.**<br>• `incident_ir`, `reflected_ir`<br>• `source_height`, `mic_height`, `incidence_angle`, `n` | `qw = power_reflection_coefficient(hi, hr)`<br><br>• (1/Kr²)\|Hr/Hi\|², offset-independent | 4608 + | `insitu_absorption_coefficient` | `function` | **Narrow-band absorption α(f) (Clause 4.1).**<br>• Same parameters as `power_reflection_coefficient` | `alpha = insitu_absorption_coefficient(hi, hr)`<br><br>• α = 1 − QW(f) | 4609 + | `one_third_octave_absorption` | `function` | **Aggregate narrow-band α into one-third octaves.**<br>• `frequency` [Hz], `absorption`<br>• `f_min` (Default: 250), `f_max` (Default: 4000; 1600 for Part 2) [Hz]<br>• `clip_negative` (Default: True) | `fc, ab = one_third_octave_absorption(f, alpha)`<br><br>• Band centres + linear-averaged α (nan if empty) | 4610 + | `insitu_absorption_spectrum` | `function` | **End-to-end in-situ spectrum (ISO 13472-1).**<br>• `incident_ir`, `reflected_ir`, `fs` [Hz]<br>• geometry + `incidence_angle`<br>• `f_min`/`f_max` [Hz], `clip_negative` | `res = insitu_absorption_spectrum(hi, hr, 48000)`<br><br>• `InsituAbsorptionResult` | 4611 + | `InsituAbsorptionResult` | `dataclass` | **In-situ absorption spectrum.**<br>• `frequencies`: one-third-octave centres [Hz]<br>• `absorption`: α per band (nan when empty)<br>• `.plot()` | `res.absorption` | 4612 + | `absorption_reference_corrected` | `function` | **Reference-corrected road absorption (Annex B).**<br>• `road_reflection` / `reference_reflection`: measured Qp (complex, same geometry) | `alpha = absorption_reference_corrected(q_road, q_ref)`<br><br>• 1 − \|Qp,road/Qp,ref\|²; removes chain error and Kr | 4613 + | `max_sampled_area_radius` | `function` | **Maximum sampled-area radius (Annex A).**<br>• `window_width` Tw [s]<br>• `source_height`, `mic_height`, `speed_of_sound` | `max_sampled_area_radius(0.005) # 1.34`<br><br>• [m], the Annex A worked example | 4614 + | `msa_major_axis` | `function` | **Major axis of the oblique sampled ellipsoid (Annex F).**<br>• `window_width` Tw [s]<br>• `projected_distance` dp [m]<br>• `source_height`, `mic_height`, `speed_of_sound` | `msa_major_axis(0.005, 0.0) # 3.2`<br><br>• a = cTw + √((ds + dm)² + dp²) [m] | 4615 + | `spot_tube_upper_frequency` | `function` | **Spot-tube upper frequency (ISO 13472-2 §5.4.1).**<br>• `diameter` d [m]<br>• `speed_of_sound` (Default: 340.0) | `spot_tube_upper_frequency(0.1) # 1972`<br><br>• f_u = 0.58c0/d [Hz] | 4616 + | `spot_microphone_spacing_bounds` | `function` | **Spacing bounds (s_min, s_max) (§5.4.2).**<br>• `speed_of_sound` (Default: 340.0)<br>• `f_min` (Default: 220), `f_max` (Default: 1800) [Hz] | `spot_microphone_spacing_bounds() # (0.0773, 0.085)`<br><br>• [m], brackets the nominal 81 mm | 4617 + | `check_spot_frequency_range` | `function` | **Advise outside 250–1600 Hz (ISO 13472-2 Scope).**<br>• `frequency` [Hz] | `check_spot_frequency_range(f)`<br><br>• `RoadAbsorptionWarning` out of range | 4618 + | `spot_internal_loss_correction` | `function` | **Internal-loss (system) correction (Annex A).**<br>• `measured_absorption`, `system_absorption`: same bands<br>• `clip_negative` (Default: True) | `spot_internal_loss_correction([0.12, 0.10], [0.02, 0.03]) # [0.1, 0.07]`<br><br>• Subtractive Part-2 correction | 4619 + | `DEFAULT_SOURCE_HEIGHT` | `float` | **ISO 13472-1 mandatory source height ds [m].**<br>1.25 | `DEFAULT_SOURCE_HEIGHT # 1.25` | 4620 + | `DEFAULT_MIC_HEIGHT` | `float` | **ISO 13472-1 mandatory microphone height dm [m].**<br>0.25 | `DEFAULT_MIC_HEIGHT # 0.25` | 4621 + | `DEFAULT_SPEED_OF_SOUND` | `float` | **Default speed of sound for the road methods [m/s].**<br>340.0 | `DEFAULT_SPEED_OF_SOUND # 340.0` | 4622 + | `PART1_FREQUENCY_RANGE` | `tuple` | **ISO 13472-1 valid band range [Hz].**<br>(250.0, 4000.0) | `PART1_FREQUENCY_RANGE` | 4623 + | `SPOT_FREQUENCY_RANGE` | `tuple` | **ISO 13472-2 valid band range [Hz].**<br>(250.0, 1600.0) | `SPOT_FREQUENCY_RANGE` | 4624 + | `SPOT_NARROW_BAND_RANGE` | `tuple` | **ISO 13472-2 narrow-band range [Hz].**<br>(220.0, 1800.0) | `SPOT_NARROW_BAND_RANGE` | 4625 + | `RoadAbsorptionWarning` | `warning class` | **ISO 13472 advisory.**<br>Emitted for frequencies outside the valid in-situ road-absorption ranges; results there are advisory | `warnings.simplefilter('error', RoadAbsorptionWarning)` | 4626 + | `frequency_weighting` | `function` | **Vibration frequency weighting H(f) (ISO 8041-1:2017 Formula 5).**<br>• `name`: one of `WEIGHTING_NAMES`<br>• `frequencies` [Hz] (> 0) | `wr = frequency_weighting('Wk', [1.0, 8.0, 63.0])`<br><br>• `WeightingResponse`; \|H\| = [0.482, 1.036, 0.186] | 4627 + | `weighting_factors` | `function` | **Weighting factors \|H(f)\| (the Wi of ISO 2631-1 Eq. 9).**<br>• `name`, `frequencies` [Hz] | `weighting_factors('Wh', [16.0])`<br><br>• Magnitude per frequency | 4628 + | `apply_weighting` | `function` | **Apply a weighting to a time signal (frequency domain, exact response).**<br>• `signal`: acceleration (1D) [m/s²]<br>• `fs` [Hz]<br>• `name`: one of `WEIGHTING_NAMES` | `aw_t = apply_weighting(a, fs, 'Wk')`<br><br>• Weighted signal, same length (circular FFT filtering) | 4629 + | `WeightingResponse` | `dataclass` | **Weighting response.**<br>• `name`<br>• `frequencies` [Hz]<br>• `response`: complex H<br>• `magnitude`, `magnitude_db` [dB]<br>• `.plot()` | `wr.magnitude, wr.magnitude_db` | 4630 + | `WEIGHTING_NAMES` | `tuple` | **ISO 8041-1 weighting names.**<br>('Wb', 'Wc', 'Wd', 'We', 'Wf', 'Wh', 'Wj', 'Wk', 'Wm') | `'Wk' in WEIGHTING_NAMES # True` | 4631 + | `weighted_acceleration` | `function` | **Weighted r.m.s. from a band spectrum (ISO 2631-1 Eq. 9 / ISO 5349-1 Eq. A.1).**<br>• `band_accelerations`: ai per band [m/s²]<br>• `frequencies`: band centres [Hz]<br>• `weighting`: name | `ws = weighted_acceleration([0.1, 0.3, 0.2], [4.0, 8.0, 16.0], 'Wk')`<br><br>• `WeightedSpectrum`; aw = 0.36 m/s² | 4632 + | `WeightedSpectrum` | `dataclass` | **Weighted band spectrum.**<br>• `frequencies` [Hz], `band_accelerations` [m/s²]<br>• `weighting_name`, `weighting_factors`<br>• `weighted`: Wi·ai [m/s²]<br>• `overall`: aw [m/s²]<br>• `.plot()` | `ws.overall` | 4633 + | `running_rms` | `function` | **Running r.m.s. of a weighted signal (ISO 2631-1 Eqs. 2/3).**<br>• `signal` [m/s²], `fs` [Hz]<br>• `integration_time` τ [s] (Default: 1.0)<br>• `method`: 'linear' (Eq. 2) or 'exponential' (Eq. 3) (Default: 'linear') | `env = running_rms(aw_t, fs)`<br><br>• aw(t0) per sample [m/s²] | 4634 + | `mtvv` | `function` | **Maximum transient vibration value (Eq. 4).**<br>• `signal` [m/s²], `fs` [Hz]<br>• `integration_time` [s] (Default: 1.0) | `mtvv(aw_t, fs)`<br><br>• max aw(t0) [m/s²] | 4635 + | `vibration_dose_value` | `function` | **Vibration dose value VDV (Eq. 5).**<br>• `signal`: weighted acceleration [m/s²]<br>• `fs` [Hz] | `vdv = vibration_dose_value(aw_t, fs)`<br><br>• (∫aw⁴dt)^(1/4) [m/s^1.75] | 4636 + | `motion_sickness_dose_value` | `function` | **Motion sickness dose value MSDV (ISO 2631-1 clause 9).**<br>• `signal`: Wf-weighted acceleration [m/s²]<br>• `fs` [Hz] | `msdv = motion_sickness_dose_value(aw_t, fs)`<br><br>• (∫aw²dt)^(1/2) [m/s^1.5] | 4637 + | `crest_factor` | `function` | **Crest factor of a weighted signal (clause 6.2.1).**<br>• `signal` [m/s²] | `crest_factor(aw_t)`<br><br>• peak/r.m.s.; `HumanVibrationWarning` above 9 | 4638 + | `vibration_total_value` | `function` | **Vibration total value av/ahv (ISO 2631-1 Eq. 10).**<br>• `components`: axis-weighted r.m.s. [m/s²]<br>• `k`: per-axis factors (Default: None → 1, the ISO 5349-1 vector sum) | `vibration_total_value([0.3, 0.4, 0.5], k=[1.4, 1.4, 1.0]) # 0.86`<br><br>• √(Σ kj²awj²) [m/s²] | 4639 + | `daily_exposure` | `function` | **Daily exposure A(8) for one operation (ISO 5349-1 Eq. 2).**<br>• `total_value`: ahv or av [m/s²]<br>• `duration_s`: T [s] | `daily_exposure(3.0, 4*3600) # 2.12`<br><br>• ahv √(T/8h) [m/s²] | 4640 + | `partial_exposure` | `function` | **Partial exposure Ai(8) of one operation (Eq. 2).**<br>• `total_value` [m/s²], `duration_s` [s] | `partial_exposure(3.0, 2*3600) # 1.5` | 4641 + | `combine_partial_exposures` | `function` | **Combine partial exposures (Eq. 3).**<br>• `partials`: Ai(8) [m/s²] | `combine_partial_exposures([1.5, 2.1]) # 2.58`<br><br>• √(Σ Ai(8)²) [m/s²] | 4642 + | `hav_daily_exposure` | `function` | **A(8) for several operations (ISO 5349-1 Eq. 3).**<br>• `total_values`: ahvi [m/s²]<br>• `durations_s`: Ti [s] | `hav_daily_exposure([3.0, 5.0], [2*3600, 3600]) # 2.32` | 4643 + | `energy_equivalent_acceleration` | `function` | **Energy-equivalent magnitude aw,e (ISO 2631-1 Eq. B.3).**<br>• `magnitudes` [m/s²], `durations_s` [s] | `energy_equivalent_acceleration([0.5, 1.0], [3600, 1800]) # 0.707` | 4644 + | `hav_vwf_lifetime_years` | `function` | **Years to 10 % vibration-white-finger prevalence (ISO 5349-1 Eq. C.1).**<br>• `a8`: A(8) [m/s²] (> 0) | `hav_vwf_lifetime_years(2.5) # 12.0`<br><br>• Dy = 31.8 A(8)^−1.06 [years] | 4645 + | `exposure_assessment` | `function` | **Assess a daily exposure (Directive 2002/44/EC Article 3).**<br>• `value`: A(8) [m/s²] or VDV [m/s^1.75]<br>• `kind`: 'hav'/'wbv'<br>• `metric`: 'a8' (Default) or 'vdv' (whole-body only) | `ea = exposure_assessment(3.0, kind='hav')`<br><br>• `ExposureAssessment` (zone 'action') | 4646 + | `ExposureAssessment` | `dataclass` | **Directive assessment.**<br>• `value`, `kind`, `metric`<br>• `action_value` (EAV), `limit_value` (ELV)<br>• `exceeds_action`, `exceeds_limit`<br>• `zone`: 'below action'/'action'/'limit' | `ea.zone, ea.exceeds_limit` | 4647 + | `daily_vibration_exposure` | `function` | **A(8) from several operations, assessed (ISO 5349 + Directive).**<br>• `total_values` [m/s²], `durations_s` [s]<br>• `kind`: 'hav'/'wbv'<br>• `labels` (Default: None → 'op 1', …) | `res = daily_vibration_exposure([3.0, 5.0], [2*3600, 3600], kind='hav')`<br><br>• `DailyVibrationExposure` (A(8) = 2.32) | 4648 + | `DailyVibrationExposure` | `dataclass` | **Assessed daily exposure.**<br>• `a8` [m/s²]<br>• `labels`, `total_values`, `durations_s`, `partials`<br>• `assessment`: `ExposureAssessment`<br>• `.plot()` | `res.a8, res.assessment.zone` | 4649 + | `HumanVibrationWarning` | `warning class` | **Human-vibration advisory.**<br>Emitted for a crest factor above 9 (ISO 2631-1 6.2.2) or other out-of-range measurement conditions | `warnings.simplefilter('error', HumanVibrationWarning)` | 4650 + | `REFERENCE_ACCELERATION` | `float` | **Vibration reference acceleration [m/s²].**<br>1e-6 (ISO 1683 acceleration level reference) | `REFERENCE_ACCELERATION # 1e-06` | 4651 + | `REFERENCE_DURATION_S` | `float` | **Daily-exposure reference duration T0 [s].**<br>28800 (8 h) | `REFERENCE_DURATION_S # 28800.0` | 4652 + | `HAV_EAV_A8` | `float` | **Hand-arm exposure action value [m/s²].**<br>2.5 (Directive 2002/44/EC) | `HAV_EAV_A8 # 2.5` | 4653 + | `HAV_ELV_A8` | `float` | **Hand-arm exposure limit value [m/s²].**<br>5.0 | `HAV_ELV_A8 # 5.0` | 4654 + | `WBV_EAV_A8` | `float` | **Whole-body A(8) action value [m/s²].**<br>0.5 | `WBV_EAV_A8 # 0.5` | 4655 + | `WBV_ELV_A8` | `float` | **Whole-body A(8) limit value [m/s²].**<br>1.15 | `WBV_ELV_A8 # 1.15` | 4656 + | `WBV_EAV_VDV` | `float` | **Whole-body VDV action value [m/s^1.75].**<br>9.1 | `WBV_EAV_VDV # 9.1` | 4657 + | `WBV_ELV_VDV` | `float` | **Whole-body VDV limit value [m/s^1.75].**<br>21.0 | `WBV_ELV_VDV # 21.0` | 4658 + | `PhonometryWarning` | `warning class` | **Base class for all phonometry warnings.**<br>Catch or silence every library advisory at once | `warnings.simplefilter('error', PhonometryWarning)` | 4659 + | `FilterBankWarning` | `warning class` | **Fractional-octave filter-bank advisory.**<br>Emitted for filter-bank processing pitfalls | `warnings.simplefilter('error', FilterBankWarning)` | 4660 + | `TonalityWarning` | `warning class` | **Tonality advisory.**<br>Emitted for biased tonality estimates (e.g. coarse FFT resolution) | `warnings.simplefilter('error', TonalityWarning)` | 4661 + | `STIWarning` | `warning class` | **STI/STIPA advisory.**<br>Emitted for suspect speech-intelligibility measurements or inputs | `warnings.simplefilter('error', STIWarning)` | 4662 + | `__version__` | `str` | **Package version string.**<br>(no parameters) | `phonometry.__version__ # '3.0.0'` | 4663 + | `.plot()` | `method` | **One-line canonical figure on every result object (soft matplotlib dependency).**<br>Available on `ZwickerLoudness`, `MooreGlasbergLoudness`, `MooreGlasbergTimeVaryingLoudness`, `EcmaLoudness`, `EcmaTonality`, `EcmaRoughness`, `STIResult`, `SIIResult`, `NCResult`, `RCResult`, `AgeThresholdResult`, `NiptsResult`, `HtlanResult`, `ImpulseProminenceResult`, `MultipleShockResult`, `ImpulseResponseResult`, `DecayCurve`, `RoomAcousticsResult`, `ReverberationResult`, `WeightedRatingResult`, `ImpactRatingResult`, `FacadeInsulationResult`, `LabAirborneInsulationResult`, `LabImpactInsulationResult`, `SoundPowerResult`, `ReverberationSoundPowerResult`, `SoundPowerIntensityResult`, `PrecisionSoundPowerResult`, `PrecisionIntensityResult`, `IntensityResult`, `UncertaintyResult`, `AbsorptionRatingResult`, `ScatteringResult`, `DiffusionResult`, `InsituAbsorptionResult`, `WeightingResponse`, `WeightedSpectrum` and `DailyVibrationExposure`.<br>• `ax`: existing Axes, or None to build a fresh figure (Default: None)<br>• returns the Matplotlib `Axes` (an array of Axes for multi-panel figures); never calls `plt.show()`<br>• needs matplotlib (`pip install phonometry[plot]`) | `res.plot()`<br>`decay_curve(ir, fs).plot()` | 3993 4664 3994 4665 ## Notes 3995 4666 ··· 4002 4673 zero-phase for when the temporal envelope matters (e.g. reverberation decay). 4003 4674 - `mode='peak'` includes the filter's onset transient; a tone that starts 4004 4675 abruptly can overshoot by ~1 dB. See [Calibration and dBFS](https://jmrplens.github.io/phonometry/guides/calibration/). 4005 - - `octavefilter()` caches filter bank designs internally (32 entries), so 4676 + - `octave_filter()` caches filter bank designs internally (32 entries), so 4006 4677 repeated calls with the same parameters skip the design phase. For explicit 4007 4678 control use `OctaveFilterBank`. 4679 + - Deprecated aliases (kept for one cycle, warn on use, removal in 4.0): 4680 + `octavefilter` → `octave_filter`, `getansifrequencies` → 4681 + `nominal_frequencies`, `normalizedfreq` → `normalized_frequencies`, 4682 + `calculate_sensitivity` → `sensitivity`, `coverage_factor` → 4683 + `insulation_coverage_factor`, `expanded_uncertainty` → 4684 + `insulation_expanded_uncertainty`, plus the renamed names 4685 + `OCTAVE_BANDS_HZ` → `OCTAVE_BANDS`, `THIRD_OCTAVE_BANDS_HZ` → 4686 + `THIRD_OCTAVE_BANDS`, `BASE_PLATE_BANDS_HZ` → `BASE_PLATE_BANDS` and 4687 + `ExposureWarning` → `OccupationalExposureWarning`. 4008 4688 4009 4689 --- 4010 4690 ··· 4037 4717 4038 4718 ## Frequency Resolution vs FFT Bin Spacing 4039 4719 4040 - `octavefilter` is a **time-domain fractional-octave filter bank**, not an FFT or 4720 + `octave_filter` is a **time-domain fractional-octave filter bank**, not an FFT or 4041 4721 Welch spectrum estimator. Therefore, its result does not have a frequency 4042 4722 resolution in the `fs / nfft` sense. 4043 4723 ··· 4057 4737 You can inspect the exact bands with: 4058 4738 4059 4739 ```python 4060 - from phonometry import getansifrequencies 4740 + from phonometry import nominal_frequencies 4061 4741 4062 - fc, fl, fu, labels = getansifrequencies(fraction=3, limits=[12, 20000]) 4742 + fc, fl, fu, labels = nominal_frequencies(fraction=3, limits=[12, 20000]) 4063 4743 for label, center, lower, upper in zip(labels, fc, fl, fu): 4064 4744 print(label, center, lower, upper, upper - lower) 4065 4745 ``` ··· 4070 4750 ```python 4071 4751 import numpy as np 4072 4752 from scipy import signal 4073 - from phonometry import octavefilter, getansifrequencies 4753 + from phonometry import octave_filter, nominal_frequencies 4074 4754 4075 4755 fs = 100_000 4076 4756 # any 1D pressure signal in Pa (synthesized here so the example runs) ··· 4078 4758 x = pressure_signal_pa 4079 4759 4080 4760 # Standardized third-octave levels from phonometry. 4081 - levels, centers = octavefilter( 4761 + levels, centers = octave_filter( 4082 4762 x, 4083 4763 fs=fs, 4084 4764 fraction=3, ··· 4086 4766 ) 4087 4767 4088 4768 # Same standardized band definitions, including lower/upper edges. 4089 - fc, fl, fu, labels = getansifrequencies(fraction=3, limits=[12, 20_000]) 4769 + fc, fl, fu, labels = nominal_frequencies(fraction=3, limits=[12, 20_000]) 4090 4770 4091 4771 # Narrowband Welch estimate on the original signal. 4092 4772 nperseg = min(2**15, len(x)) ··· 4115 4795 Window choice and overlap affect leakage and averaging variance, but they do not 4116 4796 change the bin spacing of each FFT segment. 4117 4797 4118 - When `sigbands=True`, `octavefilter` can also return the time-domain waveform 4798 + When `sigbands=True`, `octave_filter` can also return the time-domain waveform 4119 4799 filtered by each band. Applying Welch/FFT to one selected filtered waveform can 4120 4800 be useful as a diagnostic view of the content inside that filtered band, but it 4121 4801 does not recover FFT bins from the scalar band levels. ··· 4274 4954 4275 4955 The standard specifies **no interpolation** between the tabulated frequencies. Formula (1) is specified for **20 phon to 90 phon** between 20 Hz and 4 kHz, and only up to **80 phon between 5 kHz and 12.5 kHz** — above 80 phon the contour therefore stops at 4 kHz. Values outside these limits from Formula (2) are extrapolations the standard labels as informative only. 4276 4956 4277 - See the [Levels guide](https://jmrplens.github.io/phonometry/guides/levels/) for usage. 4957 + See the [Psychoacoustics guide](https://jmrplens.github.io/phonometry/guides/psychoacoustics/) for usage. 4278 4958 4279 4959 ## Tone prominence: TNR and PR (ECMA-418-1) 4280 4960 ··· 4294 4974 4295 4975 **PR** (clause 12) compares the level of the critical band centred on the tone, $L_M$, with the mean power of the two **contiguous** critical bands $L_L$, $L_U$ (edges from the fitted Formulae 21–22 with Tables 2–3): $\mathrm{PR} = 10\log_{10} P_M - 10\log_{10}\left[(P_L + P_U)/2\right]$ (Formula 23). For $f_t \le 171.4$ Hz the lower band is truncated at 20 Hz and its power rescaled to a **100 Hz bandwidth** (Formula 24). The criterion (Formulae 25–26) is 9.0 dB at $f_t \ge 1$ kHz, rising as $9.0 + 10.0\log_{10}(1000/f_t)$ below. Tones are assessed within the 89.1 Hz – 11.2 kHz range of interest (clauses 11.5 / 12.6). 4296 4976 4297 - See the [Levels guide](https://jmrplens.github.io/phonometry/guides/levels/) for usage. 4977 + See the [Prominent Discrete Tones guide](https://jmrplens.github.io/phonometry/guides/tone-prominence/) for usage. 4298 4978 4299 4979 ## Event and dose metrics 4300 4980 ··· 4345 5025 The adjustments $K_i$ cover time-of-day penalties (ISO 1996-1 Table A.1: evening 5 dB, night 10 dB) as well as source-character adjustments — e.g. tonal penalties, which the ECMA-418-1 TNR/PR assessments can justify objectively. 4346 5026 4347 5027 See the [Levels guide](https://jmrplens.github.io/phonometry/guides/levels/) for usage. 5028 + 5029 + ## Impulsive-sound prominence (NT ACOU 112) 5030 + 5031 + An impulse annoys beyond its energy, so environmental surveys after ISO 1996-2 penalize periods containing prominent impulsive sounds; NT ACOU 112:2002 makes that penalty objective. From the A-weighted, time-weighting-F level history of a single event, the onset rate (dB/s) and the level difference (dB) of the onset — which qualifies when steeper than 10 dB/s (clauses 4.5–4.7) — predict the perceived prominence (clause 7, Formula 1): 5032 + 5033 + $$ 5034 + P = 3 \lg(\text{onset rate}) + 2 \lg(\text{level difference}), 5035 + $$ 5036 + 5037 + designed to peak around 15 for very sudden, loud impulses. The adjustment to the measurement-period level takes the governing (highest-$P$) impulse (clause 8, Formula 2): 5038 + 5039 + $$ 5040 + K_I = 1.8\ (P - 5)\ \text{dB} \quad (P > 5;\ \text{else } K_I = 0), 5041 + $$ 5042 + 5043 + and the whole-day rating level combines the adjusted periods energetically (clause 8, Note 1): 5044 + 5045 + $$ 5046 + L_{Ar,T} = 10 \lg\Big[ \frac{1}{T} \sum_N \Delta t_N\ 10^{(L_{Aeq,N} + K_{I,N})/10} \Big]. 5047 + $$ 5048 + 5049 + $K_I$ is exactly the kind of source-character adjustment that enters the ISO 1996-1 composite rating level above. The anchors $P(1000\ \text{dB/s}, 30\ \text{dB}) = 9 + 2\lg 30 = 11.95$ and $K_I(P{=}10) = 9.0$ dB are reproduced exactly. 5050 + 5051 + See the [Impulse Prominence guide](impulse-prominence.md) for usage. 4348 5052 4349 5053 ## Zwicker loudness (ISO 532-1) 4350 5054 ··· 4443 5147 4444 5148 (Formulae 65–111). The single value $R$ is the 90th percentile of $R(l_{50})$ over time (Clause 7.1.10); the constant $c_R$ (Formula 104) calibrates the reference sound — a 1 kHz carrier 100 % amplitude-modulated at 70 Hz at 60 dB SPL — to 1 asper. 4445 5149 5150 + ### Sharpness (DIN 45692) 5151 + 5152 + Sharpness condenses the high-frequency emphasis of a sound into one number: the $g(z)$-weighted first moment of the ISO 532-1 stationary specific-loudness pattern (DIN 45692:2009, Equation 1): 5153 + 5154 + $$ 5155 + S = k\ \frac{\int_0^{24} N'(z)\ g(z)\ z\ dz}{\int_0^{24} N'(z)\ dz} \ \text{acum}, \qquad 5156 + g(z) = \begin{cases} 1 & z \le 15.8\ \text{Bark} \\ 0.15\ e^{0.42 (z - 15.8)} + 0.85 & z > 15.8\ \text{Bark} \end{cases} 5157 + $$ 5158 + 5159 + evaluated on the same 240-bin, 0.1-Bark grid. The constant $k$ is not hard-coded but derived from the calibration requirement (clause 6): a critical-band-wide narrowband noise 920–1080 Hz at 60 dB SPL scores exactly 1 acum — the derived $k = 0.108$ lands inside the normative window $0.105 \le k < 0.115$ (clause 5.2). The informative Annex B weightings are provided under the same 1-acum anchor: von Bismarck (knee at 15 Bark, $0.2\ e^{0.308(z-15)} + 0.8$) and Aures (loudness-dependent, $g(z) = 0.078\ (e^{0.171 z}/z)\ N/\ln(0.05 N + 1)$). The Table A.2 narrow-band targets are reproduced within the clause 6 tolerance (5 % or 0.05 acum): 0.38 acum at 250 Hz, 1.00 at 1 kHz, 1.78 at 2.5 kHz, 2.82 at 4 kHz. 5160 + 4446 5161 See the [Psychoacoustics guide](https://jmrplens.github.io/phonometry/guides/psychoacoustics/) for usage. 4447 5162 4448 5163 ## Modulation transfer and STI (IEC 60268-16) ··· 4477 5192 m_{dr} = \frac{2 \sqrt{\left( \sum_t I_k(t) \sin 2 \pi f_m t \right)^2 + \left( \sum_t I_k(t) \cos 2 \pi f_m t \right)^2}}{\sum_t I_k(t)}, \qquad m = \frac{m_{dr}}{0.55} 4478 5193 $$ 4479 5194 4480 - See the [Psychoacoustics guide](https://jmrplens.github.io/phonometry/guides/psychoacoustics/) for usage. 5195 + See the [Speech Transmission Index guide](https://jmrplens.github.io/phonometry/guides/speech-transmission/) for usage. 5196 + 5197 + ## Speech Intelligibility Index (ANSI S3.5) 5198 + 5199 + Where the STI characterizes a transmission channel, the SII (ANSI S3.5-1997) predicts intelligibility from what the listener can actually hear: 18 one-third-octave bands 160 Hz – 8 kHz, each contributing its band importance $I_i$ (Table 3, $\sum I_i = 1$, peaking near 2 kHz). All inputs are equivalent spectrum levels (clauses 3.11/3.55). Speech masks itself upward: each band's masking spectrum $Z_i$ (clause 5.4) accumulates the lower bands along slopes $C_i = -80 + 0.6\,(B_i + 10 \lg f_i - 6.353)$ dB, and the disturbance is the energetic sum of masking and hearing floor, $D_i = 10 \lg(10^{0.1 Z_i} + 10^{0.1 X'_i})$ with $X'_i = X_i + T'_i$ the reference internal noise spectrum plus the listener's hearing-threshold shift (clauses 5.5/5.6). The band audibility clips the speech-to-disturbance margin into $[0, 1]$ (clause 5.8), a level-distortion factor discounts overly loud presentation (clause 5.7), and the index sums (clause 6): 5200 + 5201 + $$ 5202 + A_i = \operatorname{clip}\Big( \frac{E'_i - D_i + 15}{30},\ 0,\ 1 \Big), \qquad 5203 + L_i = \operatorname{clip}\Big( 1 - \frac{E'_i - U_i - 10}{160},\ 0,\ 1 \Big), \qquad 5204 + \mathrm{SII} = \sum_{i=1}^{18} I_i\ L_i\ A_i . 5205 + $$ 5206 + 5207 + The Table 3 standard speech spectra for the normal, raised, loud and shout vocal efforts are built in (25.01 / 33.86 / 42.16 / 51.31 dB at 1 kHz); $U_i$ in the level-distortion factor is always the normal-effort spectrum. The anchor values: the normal-effort spectrum in quiet with normal hearing scores SII ≈ 0.996, the masking-spectrum reference values are matched to $10^{-4}$, and the vocal-effort spectra are cross-verified against the Google and CRAN reference implementations. 5208 + 5209 + See the [Speech Intelligibility guide](speech-intelligibility.md) for usage. 5210 + 5211 + ## Hearing thresholds and presbycusis (ISO 389-7, ISO 7029) 5212 + 5213 + ISO 389-7:2006 Table 1 fixes the reference threshold of hearing of otologically normal young adults — the free-field and diffuse-field SPL corresponding to 0 dB HL at the 11 audiometric frequencies 125 Hz – 8 kHz (22.1 dB at 125 Hz for both fields, 2.4/0.8 dB free/diffuse at 1 kHz, diverging at high frequency to 12.6 vs 6.8 dB at 8 kHz). ISO 7029:2017 describes how that threshold shifts statistically with age: the median deviation from age 18 is (clause 4.2, Table 1) 5214 + 5215 + $$ 5216 + \Delta H_{md} = a\ (Y - 18)^b \ \text{dB}, 5217 + $$ 5218 + 5219 + and any fractile follows a two-sided Gaussian model (clause 4.4), $\Delta H_Q = \Delta H_{md} + z(Q)\ s$, using the upper spread $s_u$ for $z \ge 0$ (worse than median) and the lower spread $s_l$ otherwise — each a degree-5 polynomial in $Y - 18$ per sex and frequency (clause 4.3, Tables 2–5). At age 18 every deviation is zero by construction. The formulae are established to 80 years at and below 2 kHz and to 70 years above; beyond that the evaluation is an extrapolation. Anchors: at 60 years the medians evaluate to 7.85 dB (male, 1 kHz), 20.21 dB (male, 4 kHz) and 15.32 dB (female, 4 kHz) — the Table 1 formula to $10^{-3}$. 5220 + 5221 + See the [Hearing Threshold guide](hearing-threshold.md) for usage. 5222 + 5223 + ## Noise-induced hearing loss (ISO 1999) 5224 + 5225 + ISO 1999:2013 predicts the permanent threshold shift a noise-exposed population accrues. The median noise-induced shift (NIPTS) for 10–40 years of exposure is (clause 6.3.1, Formula 2, Table 1): 5226 + 5227 + $$ 5228 + N_{50} = \big[ u + v \lg(t/t_0) \big]\ (L_{EX,8h} - L_0)^2, \qquad t_0 = 1\ \text{yr}, 5229 + $$ 5230 + 5231 + quadratic in the excess over the frequency-dependent onset level $L_0$ (75 dB at 4 kHz — the most sensitive band — up to 93 dB at 500 Hz) and zero below it; under 10 years it scales as $\lg(t+1)/\lg 11$ (Formula 3). Fractiles add the spread, $N_Q = N_{50} + z\ d_{u,l}$ with $d = (X + Y \lg t)(L_{EX,8h} - L_0)^2$ (clause 6.3.2, Formulae 4–7, Tables 2/3), clamped at zero; the convention counts the fraction of the population with the *smaller* shift, so $Q = 0.9$ is the most-susceptible decile (reliable range 0.05–0.95). The hearing threshold level associated with age and noise (HTLAN) combines NIPTS with the ISO 7029 age component at the same fractile through the compressed sum (clause 6.1, Formula 1): 5232 + 5233 + $$ 5234 + H' = H + N - \frac{H\ N}{120}. 5235 + $$ 5236 + 5237 + The Annex D worked examples (Tables D.1–D.4; e.g. 100 dB / 40 yr at 3 kHz: 29/38/60 dB at the 0.10/0.50/0.90 fractiles) are reproduced exactly at the standard's integer rounding, and the Formula 2 hand value at 4 kHz / 20 yr / 90 dB is $N_{50} = 12.94$ dB. 5238 + 5239 + See the [Noise-Induced Hearing Loss guide](noise-induced-hearing-loss.md) for usage. 4481 5240 4482 5241 ## Sound intensity (IEC 61043) 4483 5242 ··· 4511 5270 4512 5271 See the [Sound Intensity guide](https://jmrplens.github.io/phonometry/guides/intensity/) for usage. 4513 5272 5273 + ## Room noise criteria (ANSI S12.2) 5274 + 5275 + ANSI/ASA S12.2-2019 rates steady background noise in rooms against families of octave-band curves (16 Hz – 8 kHz). The **NC rating** uses the tangency method on the Table 1 curves (NC-15 to NC-70): each measured band is interpolated against the tabulated curve values, the rating is the highest per-band index and the band that sets it is the governing band — the interpolation makes the rating continuous (an NC-42.5 is reported as such, not snapped to a curve). The **RC Mark II** contour (Annex D) is a pure −5 dB/octave line keyed to its 1000 Hz value with a low-frequency floor of $\max(\mathrm{RC} + 25,\ 55)$ dB at 16/31.5 Hz; the rating is the arithmetic mean of the 500/1000/2000 Hz levels rounded to an integer (clause D.4), and the spectral-quality tag compares the spectrum with the reference contour (clause D.3): rumble "R" when any band at or below 500 Hz exceeds it by more than 5 dB, hiss "H" when any band at or above 1 kHz exceeds it by more than 3 dB (both together "RH"), else neutral "N" — reported as e.g. RC-35(N). The generated RC contours reproduce Table D.1 digit for digit, and feeding any Table 1 NC curve back returns its own rating. NCB, RNC (Annex A) and the QAI (clause D.5) are deliberately out of scope. 5276 + 5277 + See the [Room Noise guide](room-noise.md) for usage. 4514 5278 4515 5279 ## Room and building acoustics (ISO 18233, ISO 3382, ISO 16283, ISO 10140, EN 12354, ISO 12999, ISO 717, ISO 354) 4516 5280 ··· 4655 5419 ($L'_{n,w} = 45$ dB) worked examples are reproduced exactly; the simplified 4656 5420 model is stated to have about a 2 dB standard deviation (Clause 5). 4657 5421 5422 + ### Absorption in enclosed spaces (EN 12354-6) 5423 + 5424 + EN 12354-6:2003 predicts the equivalent absorption area of a room from its 5425 + parts (the normative Clause 4 model). The total (Formula 1) sums the surfaces, 5426 + the objects and the air: 5427 + 5428 + $$ 5429 + A = \sum_i \alpha_{s,i}\ S_i + \sum_j A_{obj,j} + \sum_k \alpha_{s,k}\ S_k + A_{air}, 5430 + \qquad A_{air} = 4\ m\ V\ (1 - \psi), 5431 + $$ 5432 + 5433 + with $m$ the power attenuation coefficient of air (Formula 2; Table 1 5434 + tabulates it for six temperature/humidity climates over the octave bands 5435 + 125 Hz – 8 kHz), $\psi = \sum V_{obj} / V$ the volume fraction occupied by 5436 + objects (Formula 3), and a hard irregular object approximated by 5437 + $A_{obj} = V_{obj}^{2/3}$ (Formula 4). The reverberation time follows from 5438 + Sabine applied to the free volume (clause 4.4, Formula 5): 5439 + 5440 + $$ 5441 + T = \frac{55.3}{c_0}\ \frac{V\ (1 - \psi)}{A}, 5442 + $$ 5443 + 5444 + with $c_0 = 345.6$ m/s chosen so that $55.3/c_0$ is the familiar $0.16$ 5445 + (clause 4.4 NOTE). The three Annex E worked cases are reproduced: the 5446 + bare 29.75 m³ room gives $A = 2.26$ m² and $T = 2.1$ s at 1 kHz, and adding 5447 + hard objects ($\psi \approx 0.072$) raises $A$ to 5.03 m² and drops $T$ to 5448 + 0.9 s. The informative Annex D method for irregular spaces and unevenly 5449 + distributed absorption is out of scope. 5450 + 5451 + See the [Enclosed-Space Absorption guide](enclosed-space-absorption.md) for usage. 5452 + 4658 5453 ### Measurement uncertainty (ISO 12999-1) 4659 5454 4660 5455 ISO 12999-1 supplies the uncertainty of the quantities above from ··· 4678 5473 (Formula A.7), and the uncorrelated single-number uncertainty is the 4679 5474 energy-weighted quadrature sum of the band uncertainties (Formula B.2). 4680 5475 4681 - See the [Room and Building Acoustics guide](https://jmrplens.github.io/phonometry/guides/room-acoustics/) for usage. 5476 + See the [Room Acoustics](https://jmrplens.github.io/phonometry/guides/room-acoustics/) and 5477 + [Building Acoustics](https://jmrplens.github.io/phonometry/guides/building-acoustics/) guides for usage. 4682 5478 4683 5479 ## Outdoor propagation and occupational exposure (ISO 9613-1/2, ISO 9612) 4684 5480 ··· 4785 5581 $88.1$ dB, $3.8$ dB) and F (full-day, $90.1$ dB, $3.4$ dB) are reproduced to 4786 5582 the standard's printed precision — every intermediate of Annex E is digit-exact, 4787 5583 and its final level differs only by the standard's own pre-rounding of the 4788 - effective-day level (see the [Levels guide](https://jmrplens.github.io/phonometry/guides/levels/)). 5584 + effective-day level (see the [Occupational Noise Exposure guide](https://jmrplens.github.io/phonometry/guides/occupational-exposure/)). 4789 5585 4790 5586 See the [Outdoor Propagation guide](https://jmrplens.github.io/phonometry/guides/outdoor-propagation/) and the 4791 - [Levels guide](https://jmrplens.github.io/phonometry/guides/levels/) for usage. 5587 + [Occupational Noise Exposure guide](https://jmrplens.github.io/phonometry/guides/occupational-exposure/) for usage. 4792 5588 4793 - ## Sound power determination (ISO 3744/3746, ISO 3741, ISO 9614-2) 5589 + ## Sound power determination (ISO 3744/3745/3746, ISO 3741, ISO 9614-2/3) 4794 5590 4795 5591 The sound power level $L_W = 10 \log_{10}(P/P_0)$ ($P_0 = 1$ pW) is an 4796 5592 *emission* quantity: unlike a pressure level it does not depend on the receiver ··· 4815 5611 the maths with looser criteria. The expanded uncertainty is 4816 5612 $U = 2 \sqrt{\sigma_{R0}^2 + \sigma_{omc}^2}$. 4817 5613 5614 + ### Precision grade in anechoic rooms (ISO 3745) 5615 + 5616 + ISO 3745:2012 is the grade-1 (precision) sibling: a qualified anechoic or 5617 + hemi-anechoic room removes the reverberant field, so there is no $K_2$ term and 5618 + the corrections become meteorological. The power level is 5619 + $L_W = \bar{L}_p + 10 \lg(S/S_0) + C_1 + C_2 + C_3$ (Eq. 14/15) over a full 5620 + sphere $S = 4 \pi r^2$ or hemisphere $S = 2 \pi r^2$, with the background 5621 + correction $K_{1i} = -10 \lg(1 - 10^{-0.1 \Delta L_{pi}})$ applied per 5622 + microphone position *before* the energy average (Eq. 11) — no correction is 5623 + needed above a 15 dB margin, and below 10 dB (250 Hz – 5 kHz) or 6 dB (edge 5624 + bands) the correction is clamped and the result flagged as an upper bound 5625 + (clause 9.4.2). The meteorological terms are 5626 + $C_1 = -10 \lg(p_s/p_{s0}) + 5 \lg[(273 + \theta)/\theta_0]$ and 5627 + $C_2 = -10 \lg(p_s/p_{s0}) + 15 \lg[(273 + \theta)/\theta_1]$ with 5628 + $\theta_0 = 314$ K, $\theta_1 = 296$ K — at the 23 °C / 101.325 kPa reference 5629 + $C_2 = 0$ exactly and $C_1 = -0.128$ dB — and 5630 + $C_3 = A_0 (1.0053 - 0.0012 A_0)^{1.6}$ with $A_0 = a(f)\ r$ restores the 5631 + ISO 9613-1 air absorption over the measurement radius. The Annex D/E 5632 + microphone arrays are built in as digit-exact coordinate tables (40 equal-area 5633 + positions; the mirror set 21–40 is added when the band-SPL spread exceeds 5634 + $N_M/2$, clause 9.3.2), and the same positions yield the directivity index 5635 + $DI_i = L_{pi} - \bar{L}_p$ (Eq. 21). The clause 10.5 uncertainty example, 5636 + $U = 2\sqrt{0.5^2 + 2.0^2} = 4.12$ dB, is reproduced, along with the Table 2/3 5637 + per-band $\sigma_{R0}$ values. 5638 + 4818 5639 ### Reverberation room (ISO 3741) 4819 5640 4820 5641 In a qualified diffuse field the steady energy density $w = 4P/(A c)$ ties the ··· 4848 5669 $F_{pI}$. A band earns the engineering grade when $L_d > F_{pI}$, $F_{+/-} \le 3$ dB 4849 5670 and the two repeated sweeps agree within the Table 2 limit. 4850 5671 5672 + ### Precision intensity scanning (ISO 9614-3) 5673 + 5674 + ISO 9614-3:2002 upgrades the scanning method to precision grade with a tighter 5675 + indicator machinery. The partial powers $P_i = I_{n,i} S_i$ (Eq. 5) sum as 5676 + before, but validity now rests on the signed and unsigned pressure-intensity 5677 + indicators $F_{pIn} = \bar{L}_p - L_{In}$ (Eqs. B.3/B.6 — the F2/F3 of 5678 + ISO 9614-1) and the normalized intensity non-uniformity $F_S$ (Eq. B.8), 5679 + through five acceptance criteria (Annex C): scan repeatability 5680 + $|L_{In}(1) - L_{In}(2)| \le s/2$ (C.1), dynamic capability 5681 + $L_d = \delta_{pI0} - K \ge F_{pIn}(\text{signed})$ with the precision 5682 + bias-error factor $K = 10$ dB (C.2), 5683 + $F_{pIn}(\text{signed}) - F_{pIn}(\text{unsigned}) \le 3$ dB (C.3), 5684 + $F_S \le 2$ (C.4) and the scan-density convergence 5685 + $0.83 \le F_S(1)/F_S(2) \le 1.2$ (C.5). Eq. 10 normalizes the result to the 5686 + reference meteorological conditions, 5687 + $L_{W0} = L_W - 15 \lg[(B/101325) \cdot 296.15/(273.15 + \theta)]$. Bands whose 5688 + net power is negative are not determinable (clause 9.2) and are flagged. A 5689 + uniform normal intensity recovers the power exactly (100 µW over 3.75 m² → 5690 + 80.0 dB re 1 pW), independent of how the surface is segmented. 5691 + 4851 5692 See the [Sound Power guide](https://jmrplens.github.io/phonometry/guides/sound-power/) for usage. 4852 5693 5694 + ## Surface scattering and diffusion (ISO 17497-1, ISO 17497-2) 5695 + 5696 + ### Random-incidence scattering coefficient (ISO 17497-1) 5697 + 5698 + A rough surface splits the reflected energy into a specular and a scattered 5699 + part; the scattering coefficient $s$ is the non-specular energy fraction. 5700 + ISO 17497-1:2004+A1:2014 measures it in a reverberation room with the test 5701 + sample on a turntable: four reverberation times — stationary and rotating, 5702 + each without and with the sample (Table 2) — give the random-incidence 5703 + absorption $\alpha_s$ (clause 8.1.1, Formula 1) and the *specular* absorption 5704 + $\alpha_{spec}$ (clause 8.1.2, Formula 4). Rotation decorrelates the scattered 5705 + reflections between decays, so they average out and register as extra 5706 + "absorption", and the scattering coefficient follows (clause 8.1.3, 5707 + Formula 5): 5708 + 5709 + $$ 5710 + s = \frac{\alpha_{spec} - \alpha_s}{1 - \alpha_s}, 5711 + $$ 5712 + 5713 + each $\alpha$ being a two-condition Sabine difference 5714 + $55.3 (V/S) [1/(c_b T_b) - 1/(c_a T_a)] - 4 (V/S)(m_b - m_a)$ with 5715 + $c = 343.2 \sqrt{(273.15 + t)/293.15}$ (Formula 2) and $m$ from ISO 9613-1 5716 + via $m = \alpha_{dB}/(10 \lg e)$ (Formula 3). The base plate itself must 5717 + scatter little: Table 1 caps its coefficient (Formula 6) at 0.05–0.25 across 5718 + 100 Hz – 5 kHz (clause 6.2). Negative $s$ is truncated to zero for 5719 + presentation (clause 8.3), but values above 1 near grazing bands are kept 5720 + (clause 6.3.2). The Annex A uncertainty chain ($u_\alpha$, Formulae A.3/A.4; 5721 + $u_s$, Formula A.5; $U = 2 u_s$) is implemented. Since the standard prints no 5722 + worked example, the oracle is a synthetic end-to-end chain 5723 + ($V = 200$ m³, $S = 10$ m², $T = 8.0/6.0/7.5/5.0$ s → $s = 0.093$) plus the 5724 + Formula A.5 hand value $u_s = 0.0297$. 5725 + 5726 + ### Directional diffusion coefficient (ISO 17497-2) 5727 + 5728 + ISO 17497-2:2012 measures, in the free field, how uniformly a surface spreads 5729 + its reflected polar response over $n$ microphones. The autocorrelation-based 5730 + coefficient (clause 8.1, Formula 5) is 5731 + 5732 + $$ 5733 + d_\theta = \frac{\left( \sum_i p_i \right)^2 - \sum_i p_i^2}{(n - 1) \sum_i p_i^2}, 5734 + \qquad p_i = 10^{L_i/10}, 5735 + $$ 5736 + 5737 + 1 for a perfectly uniform response and tending to 0 for a single specular 5738 + lobe; Formula 6 is the area-weighted form with $N_i = A_i / A_{min}$ from the 5739 + Formula 8 solid-angle factors ($A_i = (4\pi/\Delta\phi) \sin^2(\Delta\theta/4)$ 5740 + at the zenith). Normalizing against a flat reference reflector of the same 5741 + size removes edge diffraction (clause 8.2, Formula 7): 5742 + $d_{\theta,n} = (d_\theta - d_{\theta,r})/(1 - d_{\theta,r})$. The 5743 + random-incidence value averages the source angles with weights 1:3:3:3:3 for 5744 + 0°, ±30°, ±60° (clause 8.4). Anchors: levels (70, 74, 68, 72) dB → 5745 + $d = 0.7367$; zenith area factor 1.5710. 5746 + 5747 + See the [Surface Scattering guide](surface-scattering.md) for usage. 5748 + 5749 + ## In-situ road surface absorption (ISO 13472-1, ISO 13472-2) 5750 + 5751 + ISO 13472-1:2002 (extended surface method) recovers the normal-incidence 5752 + absorption of a road surface in place, from one microphone above it: the 5753 + direct and reflected components of an impulse response are separated by the 5754 + **subtraction technique** and the **Adrienne window** (clause 6.4: a sharp 5755 + leading edge, a mandated 5 ms flat top and a Blackman-Harris trailing edge), 5756 + and 5757 + 5758 + $$ 5759 + \alpha(f) = 1 - \frac{1}{K_r^2} \left| \frac{H_r(f)}{H_i(f)} \right|^2, 5760 + \qquad K_r = \frac{d_s - d_m}{d_s + d_m} = \frac{2}{3} 5761 + $$ 5762 + 5763 + for the mandatory geometry $d_s = 1.25$ m, $d_m = 0.25$ m (clause 4.2, 5764 + Annex C) — $K_r$ is the spherical-spreading ratio between the direct and the 5765 + image path. Ratioing the road measurement against one on a highly reflective 5766 + reference surface cancels the entire electro-acoustic chain along with $K_r$ 5767 + (Annex B). The 5 ms window bounds the sampled area (Annex A closed form: 5768 + radius ≈ 1.34 m for the standard geometry) and the valid range is 5769 + 250 Hz – 4 kHz in one-third octaves. ISO 13472-2:2010 (spot method, 5770 + 250–1600 Hz) instead couples a small impedance tube to the surface and defers 5771 + the mathematics to the ISO 10534-2 transfer-function method below (its 5772 + clauses 4/5.7/6.6) — the implementation reuses that module, adding the Part 2 5773 + geometry and validity limits ($f_u = 0.58\ c_0/d$; microphone spacing bounds 5774 + $0.45\ c_0/f_{max}$ and $0.05\ c_0/f_{min}$, clause 5.4) and the Annex A 5775 + subtractive correction for internal system losses. 5776 + 5777 + See the [Surface Scattering guide](surface-scattering.md) for usage. 5778 + 5779 + ## Acoustic material characterisation (ISO 11654, ISO 9053-1/2, ISO 10534-1/2, ASTM E2611) 5780 + 5781 + ### Weighted sound absorption (ISO 11654) 5782 + 5783 + ISO 11654:1997 condenses an ISO 354 third-octave absorption curve into a 5784 + single number. The practical coefficient $\alpha_p$ averages the three thirds 5785 + of each octave 250 Hz – 4 kHz and rounds to steps of 0.05 (clause 4.1). The 5786 + reference curve (0.80, 1.00, 1.00, 1.00, 0.90 at 250–4000 Hz) is then shifted 5787 + downward in 0.05 steps until the sum of unfavourable deviations — counted only 5788 + where the measurement falls *below* the shifted curve — is $\le 0.10$; 5789 + $\alpha_w$ is the shifted curve at 500 Hz (clause 4.2). A shape indicator 5790 + flags excess absorption $\ge 0.25$ above the shifted curve: L at 250 Hz, M at 5791 + 500/1000 Hz, H at 2000/4000 Hz (clause 4.3), and the informative Annex B maps 5792 + $\alpha_w$ to the absorption classes A–E. Because every quantity is a multiple 5793 + of 0.05, the implementation does the whole grid arithmetic in integer 5794 + twentieths, making the shift search and class boundaries exact and 5795 + float-safe. The two Annex A worked examples are reproduced: 5796 + $\alpha_p = (0.35, 0.70, 0.65, 0.60, 0.55)$ → $\alpha_w = 0.60$, class C; and 5797 + raising 500 Hz to 1.00 keeps $\alpha_w = 0.60$ but adds the indicator, "0.60(M)". 5798 + 5799 + ### Airflow resistance (ISO 9053-1/2) 5800 + 5801 + Airflow resistivity $\sigma = R\,A/d$ is the key transport parameter of a 5802 + porous absorber. ISO 9053-1:2018 (static method) drives a steady flow through 5803 + the specimen and fits $\Delta p = a\,u + b\,u^2$ through the origin 5804 + (clause 7.5); since $R_s = \Delta p / u = a + b\,u$, the linear coefficient is 5805 + the zero-velocity specific resistance, reported at the reference velocity 5806 + $u = 0.5$ mm/s. ISO 9053-2:2020 (alternating method) replaces the flowmeter 5807 + with a ~2 Hz piston and a microphone in a closed cavity (clause 8.7, 5808 + Formula 2): 5809 + 5810 + $$ 5811 + R = \kappa'\ \frac{p_s}{2 \pi f V}\ \frac{h_t}{h_s}\ 10^{(L_{ps} - L_{pt})/20} 5812 + $$ 5813 + 5814 + — only a level *difference* enters, so the sound-level device needs no 5815 + absolute calibration. The effective exponent $\kappa'$ (Annex A, 5816 + Formula A.7) corrects the adiabatic $\kappa$ for wall heat conduction through 5817 + the thermal boundary layer $b = \sqrt{2 c_0 l_h / \omega}$ (Formulae A.4/A.5). 5818 + The Annex A.3 worked example (100 mm closed cylinder at 2 Hz: $b = 1.83$ mm, 5819 + $\kappa' = 1.370 = 0.978\,\kappa$) is reproduced, and the validity guards of 5820 + Formula 3 (transfer ratio < 0.3) and Formula 4 (10 dB background margin) are 5821 + enforced. 5822 + 5823 + ### Impedance tube (ISO 10534-1, ISO 10534-2, ASTM E2611) 5824 + 5825 + A tube below its cut-on frequency ($f d < 0.58\ c_0$ circular, 5826 + $< 0.50\ c_0$ rectangular; microphone-spacing limits $f s < 0.45\ c_0$ and 5827 + $f > c_0/(20 s)$; clauses 4.2–4.5) carries only plane waves, so the surface 5828 + reflection factor of a sample is fully observable. ISO 10534-2 5829 + (transfer-function method) compares the measured two-microphone transfer 5830 + function $H_{12}$ with the analytic incident and reflected ones 5831 + $H_I = e^{-j k_0 s}$, $H_R = e^{+j k_0 s}$ (Annex D) to give (clause 7, 5832 + Eq. 17): 5833 + 5834 + $$ 5835 + r = \frac{H_{12} - H_I}{H_R - H_{12}}\ e^{2 j k_0 x_1}, \qquad 5836 + \alpha = 1 - |r|^2, \qquad \frac{Z}{\rho c_0} = \frac{1 + r}{1 - r}, 5837 + $$ 5838 + 5839 + with the complex wavenumber's attenuation lower bound 5840 + $k_0'' = 1.94 \times 10^{-2} \sqrt{f}/(c_0 d)$ (Eq. A.18). ISO 10534-1 5841 + (standing-wave-ratio method) is the closed-form classic: 5842 + $|r| = (s - 1)/(s + 1)$ from the max/min ratio $s = 10^{\Delta L/20}$ and the 5843 + phase from the first-minimum position (Eqs. 12–26) — an SWR of 3 gives exactly 5844 + $|r| = 0.5$ and $\alpha = 0.75$. ASTM E2611-19 adds transmission: four 5845 + microphones decompose the up- and downstream fields into the $A, B, C, D$ 5846 + waves (Eqs. 17–20) and a two-load (or symmetric one-load) solve yields the 5847 + specimen's 2×2 **transfer matrix** $[p; u]_0 = T\,[p; u]_d$ (Eqs. 16/22–24), 5848 + from which the anechoic-backing normal-incidence transmission loss is 5849 + (Eqs. 25/26) 5850 + 5851 + $$ 5852 + TL = 20 \lg \frac{\left| T_{11} + T_{12}/\rho c + \rho c\ T_{21} + T_{22} \right|}{2}, 5853 + $$ 5854 + 5855 + plus the hard-backed reflection 5856 + $R = (T_{11} - \rho c\,T_{21})/(T_{11} + \rho c\,T_{21})$ (Eq. 27), the 5857 + material wavenumber $\arccos(T_{11})/d$ (Eq. 29) and the characteristic 5858 + impedance $\sqrt{T_{12}/T_{21}}$ (Eq. 30). The three standards deliberately 5859 + keep their own sign ansatz and temperature units (ISO in kelvin, ASTM in 5860 + Celsius), and near-singular load solves raise a warning. Since neither 5861 + standard prints a numeric example, the oracles are physics identities: the 5862 + analytic air-layer matrix ($\det T = 1$, $T_{11} = T_{22}$, TL = 0 dB, 5863 + hard-backed $|R| = 1$), synthetic round-trips that recover a known $r$, and 5864 + two-load recovery of an asymmetric reciprocal specimen. 5865 + 5866 + See the [Materials guide](materials.md) for usage. 5867 + 5868 + ## Human vibration (ISO 8041-1, ISO 2631-1/2, ISO 5349-1/2, Directive 2002/44/EC) 5869 + 5870 + Human response to vibration depends on frequency, axis and body part, so 5871 + acceleration is filtered by the frequency weightings of ISO 8041-1:2017 before 5872 + any metric. Each weighting is the analog cascade 5873 + $H(s) = H_h(s) H_l(s) H_t(s) H_s(s)$ (Formula 5): two-pole Butterworth 5874 + band-limiting high-pass and low-pass stages (Formulae 1/2), an 5875 + acceleration–velocity transition (Formula 3, carrying the only non-unity gain, 5876 + $K = 1.024$ for Wb) and an upward step (Formula 4), with the Table 3 corner 5877 + frequencies and Q factors; a corner at infinity collapses its stage to unity 5878 + (Table 3 NOTEs). Wk (vertical whole-body) and Wd (horizontal) of 5879 + ISO 2631-1, Wm (buildings, ISO 2631-2), Wb (rail, ISO 2631-4), Wc/We/Wj 5880 + (seat-back, rotational, head) and Wh (hand-arm, ISO 5349-1) plus Wf (motion 5881 + sickness) are all implemented from the exact cascade — the filter is applied 5882 + as the exact complex response via FFT (magnitude *and* phase), not a 5883 + bilinear-warped digital approximation — and the ISO 8041-1 Annex B design-goal 5884 + tables (B.1–B.9) are reproduced to 0.1 %. 5885 + 5886 + The weighted metrics follow ISO 2631-1:1997: running rms with linear or 5887 + exponential integration (Eqs. 2/3), **MTVV** as its maximum (Eq. 4), the 5888 + fourth-power **VDV** $= (\int a_w^4\, dt)^{1/4}$ in m/s^1.75 (Eq. 5), the crest 5889 + factor with the basic method deemed adequate up to 9 (clause 6.2), and the 5890 + vibration total value $a_v = \sqrt{\sum_j k_j^2 a_{wj}^2}$ (Eq. 10). Hand-arm 5891 + exposure follows ISO 5349-1:2001: $a_{hv}$ (Eq. 1, all $k = 1$), daily 5892 + exposure $A(8) = a_{hv} \sqrt{T/T_0}$ with $T_0 = 8$ h (Eq. 2), partial 5893 + exposures combined in quadrature (ISO 5349-2:2001, Eqs. 1–3), and the Annex C 5894 + vascular-risk model $D_y = 31.8\ A(8)^{-1.06}$ for the years to 10 % 5895 + white-finger prevalence. The Directive 2002/44/EC action and limit values are 5896 + built in: hand-arm $A(8)$ 2.5/5.0 m/s², whole-body $A(8)$ 0.5/1.15 m/s² or 5897 + VDV 9.1/21.0 m/s^1.75 (Article 3). The ISO 5349-2 worked examples are 5898 + reproduced (E.2.1: 7.4 m/s² for 2.5 h → $A(8) = 4.1$ m/s²; E.3 forestry, 5899 + three tools → 3.6 m/s²), as are the ISO 5349-1 Table C.1 exposure-duration 5900 + rows. 5901 + 5902 + ### Multiple shocks (ISO 2631-5) 5903 + 5904 + Repeated shocks damage the lumbar spine through peak compression rather than 5905 + average energy, so ISO 2631-5:2018 replaces the Wk weighting with the 5906 + seat-to-spine transfer function of clause 5.2 (Formula 1: one complex zero and 5907 + six complex pole pairs, unity at DC, resonance near 5 Hz — 5908 + $|H| \approx 1.54$ at 5 Hz) and accumulates the positive spinal-response peaks with a 5909 + sixth-power (Palmgren-Miner) dose (clause 5.3, Formulae 3/4): 5910 + 5911 + $$ 5912 + D_z = 1.07 \left( \sum_i A_{z,i}^6 \right)^{1/6}, \qquad 5913 + D_{zd} = D_z\ (t_d / t_m)^{1/6}. 5914 + $$ 5915 + 5916 + Annex C converts the daily dose to a compressive stress $S_d = m_z D_{zd}$ 5917 + ($m_z = 0.029/0.025$ MPa per m/s² for the 82 kg male / 64 kg female), tracks 5918 + the age-declining ultimate strength $S_u = 6.75 - S_{age}(b + i)$ and forms 5919 + the cumulative stress variable $R$ (Formulae C.3/C.4), mapped to an injury 5920 + probability by the Table C.1 Weibull law $\Pi = 1 - e^{-(R/\alpha)^\beta}$. 5921 + The spinal filter is evaluated analytically in the frequency domain and 5922 + validated against the Annex D 256 Hz digital-filter tabulation within the 5923 + clause 5.2 tolerance; the Annex C worked example (five 40 m/s² shocks per day 5924 + over 20 years) is reproduced: $D_{zd} = 55.97$ m/s², $R = 1.22$, 5925 + $\Pi = 0.37$. The Annex A finite-element spinal model (distributed by ISO as 5926 + separate software) is out of scope. 5927 + 5928 + See the [Human Vibration guide](human-vibration.md) and the 5929 + [Multiple-Shock Vibration guide](multiple-shock-vibration.md) for usage. 5930 + 5931 + ## Measurement uncertainty (ISO/IEC Guide 98-3 — GUM and Supplement 1) 5932 + 5933 + Domain budgets like ISO 12999-1 and ISO 9612 Annex C are instances of the 5934 + general framework of the GUM (ISO/IEC Guide 98-3:2008). Given a measurement 5935 + model $y = f(x_1, \ldots, x_N)$, the law of propagation of uncertainty 5936 + (clause 5) combines the input standard uncertainties through sensitivity 5937 + coefficients: 5938 + 5939 + $$ 5940 + u_c^2(y) = \sum_{i=1}^{N} \left( \frac{\partial f}{\partial x_i} \right)^2 u^2(x_i), 5941 + $$ 5942 + 5943 + generalized to $(c \odot u)^{\top} r\ (c \odot u)$ for correlated inputs. The 5944 + sensitivities are obtained by central differences on the user's model callable 5945 + (step scaled to $10^{-3}$ of each input uncertainty), so no hand-derived 5946 + partials are needed. Type B inputs enter through the clause 4.3 half-width 5947 + rules: rectangular $a/\sqrt{3}$ (4.3.7), triangular $a/\sqrt{6}$ (4.3.9), 5948 + U-shaped $a/\sqrt{2}$. The expanded uncertainty $U = k\,u_c$ takes $k$ from 5949 + the t-distribution at the Welch–Satterthwaite effective degrees of freedom 5950 + (Annex G.4): 5951 + 5952 + $$ 5953 + \nu_{\mathrm{eff}} = \frac{u_c^4}{\sum_i u_i^4 / \nu_i}. 5954 + $$ 5955 + 5956 + **Supplement 1** (ISO/IEC Guide 98-3-1:2008) propagates the full distributions 5957 + instead: $10^6$ Monte Carlo draws (clause 6.4) through the same model give 5958 + $u(y)$ and the probabilistically symmetric coverage interval from the 5959 + $\frac{1}{2}(1 \mp p)$ fractiles (clause 7.7) — the route when the model is 5960 + non-linear or the output visibly non-Gaussian. The Guides' own examples are 5961 + reproduced: the four-term additive model gives $u_c = 2.0$ and the Monte Carlo 5962 + 95 % interval $\pm 3.88$ of Supplement 1 clause 9.2/Table 3 (four rectangular 5963 + inputs — the output is nearly trapezoidal, not Gaussian, so the interval is 5964 + narrower than $\pm 1.96\,u$), and the GUM Annex H.1 end-gauge example gives 5965 + $k = t_{0.99}(\nu_{\mathrm{eff}} = 16) = 2.92$ and $U_{99} = 93$ nm. 5966 + 5967 + See the [GUM Uncertainty guide](gum-uncertainty.md) for usage. 5968 + 4853 5969 --- 4854 5970 4855 5971 ··· 4951 6067 | IEC 61672-1:2013 Table 5 | `lc_peak()` one-cycle/half-cycle peak responses, class 1 limits | `tests/test_levels.py` | 4952 6068 | IEC 61260-1:2014 Table 1 | Filter-bank class 1/2 acceptance limits via `verify_filter_class()` | `tests/test_compliance.py` | 4953 6069 | ISO 7196:1995 Table 2 | G weighting (infrasound) at every nominal response value, 0.25–315 Hz | `tests/test_g_weighting.py` | 4954 - | ISO 226:2023 Annex B | Equal-loudness contours, loudness levels and hearing threshold against the Annex B tables | `tests/test_equal_loudness.py` | 6070 + | ISO 226:2023 Annex B | Equal-loudness contours, loudness levels and hearing threshold against the Annex B tables | `tests/test_loudness_contours.py` | 4955 6071 | ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/test_tonality.py` | 4956 6072 | ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/test_environmental.py` | 4957 - | IEC 60942:2017 Table 2 | Calibrator short-term stability limits (frequency-dependent, class 1) in `calculate_sensitivity()` | `tests/test_calibration_validation.py` | 6073 + | IEC 60942:2017 Table 2 | Calibrator short-term stability limits (frequency-dependent, class 1) in `sensitivity()` | `tests/test_calibration_validation.py` | 4958 6074 4959 6075 Beyond IEC 61252-style noise dose (`sound_exposure()`, `lex_8h()`), the same 4960 6076 standards-first mindset shows up in the numerics: filter banks place their
+9 -5
llms.txt
··· 16 16 17 17 ```python 18 18 import numpy as np 19 - from phonometry import octavefilter, laeq, ln_levels 19 + from phonometry import octave_filter, laeq, ln_levels 20 20 21 21 fs = 48000 22 22 x = np.random.randn(fs) # 1 s of signal (pressure units) 23 - spl, freq = octavefilter(x, fs, fraction=3) # 1/3-octave band levels 23 + spl, freq = octave_filter(x, fs, fraction=3) # 1/3-octave band levels 24 24 la = laeq(x, fs) # A-weighted Leq 25 25 stats = ln_levels(x, fs, n=(10, 50, 90)) # statistical levels 26 26 ``` 27 27 28 - If you are an AI assistant setting this up for a user: install from PyPI (no system dependencies), remember integer audio (e.g. wavfile.read int16) is handled automatically, use `calibration_factor` from `calculate_sensitivity()` for real dB SPL, and prefer `OctaveFilterBank` over repeated `octavefilter()` calls in tight loops (although designs are cached either way). 28 + If you are an AI assistant setting this up for a user: install from PyPI (no system dependencies), remember integer audio (e.g. wavfile.read int16) is handled automatically, use `calibration_factor` from `sensitivity()` for real dB SPL, and prefer `OctaveFilterBank` over repeated `octave_filter()` calls in tight loops (although designs are cached either way). 29 29 30 30 ## Documentation 31 31 ··· 34 34 - [Frequency Weighting (A, C, G, Z)](https://jmrplens.github.io/phonometry/guides/weighting/) 35 35 - [Time Weighting and Integration](https://jmrplens.github.io/phonometry/guides/time-weighting/) 36 36 - [Integrated and Statistical Levels](https://jmrplens.github.io/phonometry/guides/levels/) 37 - - [Psychoacoustics and Speech Intelligibility](https://jmrplens.github.io/phonometry/guides/psychoacoustics/) 37 + - [Occupational Noise Exposure (ISO 9612)](https://jmrplens.github.io/phonometry/guides/occupational-exposure/) 38 + - [Prominent Discrete Tones (ECMA-418-1)](https://jmrplens.github.io/phonometry/guides/tone-prominence/) 39 + - [Psychoacoustics](https://jmrplens.github.io/phonometry/guides/psychoacoustics/) 40 + - [Speech Transmission Index (IEC 60268-16)](https://jmrplens.github.io/phonometry/guides/speech-transmission/) 38 41 - [Sound Intensity (p-p method)](https://jmrplens.github.io/phonometry/guides/intensity/) 39 - - [Room and Building Acoustics](https://jmrplens.github.io/phonometry/guides/room-acoustics/) 42 + - [Room Acoustics](https://jmrplens.github.io/phonometry/guides/room-acoustics/) 43 + - [Building Acoustics & Sound Insulation](https://jmrplens.github.io/phonometry/guides/building-acoustics/) 40 44 - [Outdoor Sound Propagation](https://jmrplens.github.io/phonometry/guides/outdoor-propagation/) 41 45 - [Sound Power](https://jmrplens.github.io/phonometry/guides/sound-power/) 42 46 - [Calibration and dBFS](https://jmrplens.github.io/phonometry/guides/calibration/)
+4
scripts/generate_llms.py
··· 25 25 ("weighting.md", "guides/weighting"), 26 26 ("time-weighting.md", "guides/time-weighting"), 27 27 ("levels.md", "guides/levels"), 28 + ("occupational-exposure.md", "guides/occupational-exposure"), 29 + ("tone-prominence.md", "guides/tone-prominence"), 28 30 ("psychoacoustics.md", "guides/psychoacoustics"), 31 + ("speech-transmission.md", "guides/speech-transmission"), 29 32 ("intensity.md", "guides/intensity"), 30 33 ("room-acoustics.md", "guides/room-acoustics"), 34 + ("building-acoustics.md", "guides/building-acoustics"), 31 35 ("outdoor-propagation.md", "guides/outdoor-propagation"), 32 36 ("sound-power.md", "guides/sound-power"), 33 37 ("calibration.md", "guides/calibration"),
+3 -2
site/astro.config.mjs
··· 284 284 translations: { es: 'Niveles y ponderación' }, 285 285 items: [ 286 286 'guides/levels', 287 + 'guides/occupational-exposure', 287 288 'guides/weighting', 288 289 'guides/time-weighting', 289 290 ], ··· 291 292 { 292 293 label: 'Perception & speech', 293 294 translations: { es: 'Percepción e inteligibilidad' }, 294 - items: ['guides/psychoacoustics', 'guides/speech-intelligibility', 'guides/hearing-threshold', 'guides/noise-induced-hearing-loss'], 295 + items: ['guides/psychoacoustics', 'guides/speech-transmission', 'guides/speech-intelligibility', 'guides/hearing-threshold', 'guides/noise-induced-hearing-loss'], 295 296 }, 296 297 { 297 298 label: 'Sound power & intensity', ··· 320 321 { 321 322 label: 'Environmental acoustics', 322 323 translations: { es: 'Acústica ambiental' }, 323 - items: ['guides/outdoor-propagation', 'guides/impulse-prominence'], 324 + items: ['guides/outdoor-propagation', 'guides/tone-prominence', 'guides/impulse-prominence'], 324 325 }, 325 326 { 326 327 label: 'Human vibration',
+44 -28
site/src/content/docs/es/guides/building-acoustics.md
··· 472 472 print(res.dominant.label, round(res.dominant.fraction, 2)) # Dd 0.33 (domina el directo) 473 473 ``` 474 474 475 + <details> 476 + <summary>Ver el código de esta figura</summary> 477 + 478 + ```python 479 + import matplotlib.pyplot as plt 480 + 481 + # Índice de reducción sonora por camino y fracción de energía transmitida de cada 482 + # camino para el resultado del Anexo H.3 calculado arriba. 483 + labels = [p.label for p in res.paths] 484 + r_w = [p.r_w for p in res.paths] 485 + frac = [100.0 * p.fraction for p in res.paths] 486 + 487 + fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6), sharex=True) 488 + ax1.bar(labels, r_w, color="tab:blue") 489 + ax1.axhline(res.r_prime_w, ls="--", color="k", label=f"R'w = {res.r_prime_w:.1f} dB") 490 + ax1.set_ylabel("Rij,w del camino [dB]"); ax1.legend() 491 + ax2.bar(labels, frac, color="tab:orange") 492 + ax2.set_ylabel("Fracción de energía [%]"); ax2.set_xlabel("Camino de transmisión") 493 + for ax in (ax1, ax2): 494 + ax.tick_params(axis="x", rotation=45) 495 + fig.suptitle("EN 12354-1 Anexo H.3 — transmisión por flancos") 496 + fig.tight_layout() 497 + plt.show() 498 + ``` 499 + 500 + </details> 501 + 475 502 Cada camino de flanco añadido rebaja estrictamente $R'_w$ por debajo del directo 476 503 $R_{Dd,w} = 57$; `res.paths` expone la fracción de energía transmitida de cada 477 - camino, de modo que el camino dominante queda visible. La Cláusula 4.4.2 también 504 + camino, de modo que el camino dominante queda visible. `flanking_element` es una 505 + comodidad que construye de una vez los tres caminos de una unión; el constructor 506 + de camino único que hay detrás, `flanking_path`, construye un camino `Ff`, `Df` 507 + o `Fd` cada vez (Fórmula 28a). La Cláusula 4.4.2 también 478 508 impone un límite inferior $K_{ij} \ge K_{ij,\min}$ a partir de la geometría de la 479 509 unión: calcúlalo con `junction_min_vibration_reduction` y pásalo a 480 510 `flanking_path(..., kij_min=...)`, que eleva un $K_{ij}$ por debajo del límite ··· 506 536 print(round(ln_eq, 1), k, round(imp.l_prime_n_w, 1)) # 76.2 2 45.2 -> L'n,w = 45 dB 507 537 print(round(standardized_impact_level(imp.l_prime_n_w, 50.0), 1)) # 43.0 L'nT,w 508 538 ``` 509 - 510 - <details> 511 - <summary>Ver el código de esta figura</summary> 512 - 513 - ```python 514 - import matplotlib.pyplot as plt 515 - 516 - # Índice de reducción sonora por camino y fracción de energía transmitida de cada 517 - # camino para el resultado del Anexo H.3 calculado arriba. 518 - labels = [p.label for p in res.paths] 519 - r_w = [p.r_w for p in res.paths] 520 - frac = [100.0 * p.fraction for p in res.paths] 521 - 522 - fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6), sharex=True) 523 - ax1.bar(labels, r_w, color="tab:blue") 524 - ax1.axhline(res.r_prime_w, ls="--", color="k", label=f"R'w = {res.r_prime_w:.1f} dB") 525 - ax1.set_ylabel("Rij,w del camino [dB]"); ax1.legend() 526 - ax2.bar(labels, frac, color="tab:orange") 527 - ax2.set_ylabel("Fracción de energía [%]"); ax2.set_xlabel("Camino de transmisión") 528 - for ax in (ax1, ax2): 529 - ax.tick_params(axis="x", rotation=45) 530 - fig.suptitle("EN 12354-1 Anexo H.3 — transmisión por flancos") 531 - fig.tight_layout() 532 - plt.show() 533 - ``` 534 - 535 - </details> 536 539 537 540 ### Parámetros de `junction_vibration_reduction()` / `flanking_element()` 538 541 ··· 645 648 `uncertain_value()` un `UncertainValue` (`value`, `standard_uncertainty`, 646 649 `coverage_factor`, `expanded_uncertainty`, `.lower`, `.upper`). El mapa de solo 647 650 lectura `COVERAGE_FACTORS` expone la Tabla 8 indexada por `(confidence, one_sided)`. 651 + 652 + --- 653 + 654 + **Normas.** ISO 16283-1:2014, ISO 16283-2 e ISO 16283-3:2016, *Acoustics — 655 + Field measurement of sound insulation in buildings and of building elements* — 656 + las diferencias de nivel, normalizaciones y métodos de elemento del §1; 657 + ISO 717-1 e ISO 717-2 — los índices de un solo número por curva de referencia y 658 + los términos de adaptación espectral C, Ctr y CI; ISO 10140-2:2010 e 659 + ISO 10140-4:2010 — los R y Ln de laboratorio con la corrección de ruido de 660 + fondo del §2; EN 12354-1:2000 y EN 12354-2:2000 — las predicciones 661 + simplificadas de transmisión por flancos del §3 (uniones del Anexo E, ejemplos 662 + resueltos H.3 y E.3); ISO 12999-1:2020 — las incertidumbres típicas por 663 + situación de medición y los factores de cobertura del §4. 648 664 649 665 ## Véase también 650 666
+1 -1
site/src/content/docs/es/guides/enclosed-space-absorption.md
··· 114 114 por banda, el volumen y la fracción de objetos, y su `.plot()` dibuja el espectro 115 115 del tiempo de reverberación. Es la contraparte de predicción del tiempo de 116 116 reverberación medido en 117 - [Acústica de salas y edificación](/phonometry/es/guides/room-acoustics/) 117 + [Acústica de salas](/phonometry/es/guides/room-acoustics/) 118 118 (ISO 3382) y de la absorción en cámara reverberante de 119 119 [Materiales acústicos](/phonometry/es/guides/materials/) (ISO 354). 120 120
+16 -191
site/src/content/docs/es/guides/levels.md
··· 1 1 --- 2 2 title: "Niveles integrados y estadísticos" 3 - description: "Leq, LAeq, percentiles L10/L50/L90 y espectrogramas de octava." 3 + description: "Leq, LAeq, percentiles L10/L50/L90, LCpeak/SEL y dosis de ruido (IEC 61252), Lden y niveles de evaluación (ISO 1996-1), y espectrogramas de octava." 4 4 --- 5 5 6 6 Métricas de ruido ambiental calculadas directamente sobre la señal cruda ··· 180 180 | `sound_exposure(x, fs, duration_hours=None, ...)` | `duration_hours` trata `x` como muestra de ese periodo | E [Pa²h] | IEC 61252 | 181 181 | `lex_8h(x, fs, duration_hours=None, ...)` | misma semántica de muestreo | LEX,8h [dB] | IEC 61252 (≡ LEP,d) | 182 182 183 - ## Estrategias de exposición al ruido en el trabajo e incertidumbre (ISO 9612) 184 - 185 - `lex_8h`, más arriba, convierte *una* grabación en un nivel diario. La 186 - ISO 9612:2009 —el método de ingeniería (clase de exactitud 2)— es el diseño de 187 - la medición *en torno* a esa primitiva: cómo muestrear una jornada laboral real, 188 - cómo combinar las partes y cómo adjuntar la incertidumbre normativa que necesita 189 - todo informe de higiene laboral. El módulo `occupational_exposure` añade las tres 190 - **estrategias de medición** y el presupuesto de incertidumbre del **anexo C** 191 - sobre la maquinaria del promediado en energía. 192 - 193 - La estrategia *basada en tareas* (apartado 9) divide la jornada nominal en 194 - tareas, toma $I \ge 3$ muestras por tarea y suma en energía las contribuciones de 195 - cada tarea 196 - 197 - $$ 198 - L_{EX,8h,m} = L_{p,A,eqT,m} + 10 \log_{10}(T_m/T_0), \qquad T_0 = 8\ \text{h}, 199 - $$ 200 - 201 - de modo que una tarea ruidosa pero corta contribuye poco. Las estrategias 202 - *basada en la función* (apartado 10) y *de jornada completa* (apartado 11) toman 203 - en cambio $N \ge 5$ (o tres jornadas completas) muestras aleatorias sobre un grupo 204 - de exposición homogéneo y normalizan la duración efectiva de la jornada. El nivel 205 - diario es el mismo en ambos casos; las estrategias difieren en cómo se construye 206 - la **incertidumbre**. 207 - 208 - <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/exposure_uncertainty_es.png" alt="Exposición por tareas del anexo D de la ISO 9612: las tres contribuciones de tarea a LEX,8h como barras, la línea del LEX,8h diario sumado en energía y la banda del límite superior unilateral al 95 % LEX,8h + U por encima" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/exposure_uncertainty_es_dark.png" alt="Exposición por tareas del anexo D de la ISO 9612: las tres contribuciones de tarea a LEX,8h como barras, la línea del LEX,8h diario sumado en energía y la banda del límite superior unilateral al 95 % LEX,8h + U por encima" style="width:80%"> 209 - 210 - ```python 211 - from phonometry.occupational_exposure import ( 212 - Task, task_based_exposure, job_based_exposure, full_day_exposure, 213 - ) 214 - 215 - # ISO 9612 anexo D — la jornada de un soldador dividida en tres tareas. El nivel 216 - # de cada tarea es el promedio en energía de sus muestras Lp,A,eqT; las 217 - # duraciones llevan un rango medido. 218 - tasks = [ 219 - Task(samples=(70.0,), duration_hours=1.5, label="planning/breaks"), 220 - Task(samples=(80.1, 82.2, 79.6), duration_hours=5.0, 221 - duration_range=(4.0, 6.0), label="welding"), 222 - Task(samples=(86.5, 92.4, 89.3, 93.2, 87.8, 86.2), duration_hours=1.5, 223 - duration_range=(1.0, 2.0), label="cutting/grinding"), 224 - ] 225 - res = task_based_exposure(tasks, include_duration_uncertainty=False, warn=False) 226 - print(f"LEX,8h = {res.lex_8h:.1f} dB U = {res.expanded_uncertainty:.1f} dB") 227 - # LEX,8h = 84.3 dB U = 2.7 dB 228 - print(f"one-sided 95 % upper limit LEX,8h + U = {res.upper_limit:.1f} dB") # 87.0 dB 229 - for t in res.tasks: 230 - print(f" {t.label:<16} Lp,A,eqT = {t.lp_aeqt:5.1f} contributes {t.lex_8h_contribution:5.1f} dB") 231 - # planning/breaks Lp,A,eqT = 70.0 contributes 62.7 dB 232 - # welding Lp,A,eqT = 80.8 contributes 78.7 dB 233 - # cutting/grinding Lp,A,eqT = 90.1 contributes 82.8 dB 234 - 235 - # La misma jornada medida basada en la función (anexo E) y de jornada completa 236 - # (anexo F): ambas usan el presupuesto de muestreo Ec C.9 / Tabla C.4 con 237 - # k = 1.65 (unilateral 95 %). 238 - job = job_based_exposure([88.1, 86.1, 89.7, 86.5, 91.1, 86.7], effective_duration_hours=7.5) 239 - full = full_day_exposure([88.0, 91.9, 87.6, 90.4, 89.0, 88.4], effective_duration_hours=9.25) 240 - print(f"job LEX,8h = {job.lex_8h:.1f} dB U = {job.expanded_uncertainty:.1f} dB") 241 - # job LEX,8h = 88.2 dB U = 3.8 dB 242 - print(f"full-day LEX,8h = {full.lex_8h:.1f} dB U = {full.expanded_uncertainty:.1f} dB") 243 - # full-day LEX,8h = 90.1 dB U = 3.4 dB 244 - ``` 245 - 246 - Conviene detallar dos sutilezas. Primero, el factor de cobertura es $k = 1.65$ 247 - para un intervalo **unilateral** al 95 % (apartado 14), porque al higienista solo 248 - le importa la cota *superior*: `res.upper_limit` = $L_{EX,8h} + U$ es el valor por 249 - debajo del cual queda el 95 % de las mediciones, el número que se compara con un 250 - valor de acción. Segundo, los métodos por tareas y por función ponderan de forma 251 - distinta la *misma* dispersión de muestras. La incertidumbre de muestreo de la 252 - tarea $u_{1a}$ (Ec. C.6) divide la suma de desviaciones al cuadrado entre 253 - $I(I-1)$ —el error típico de la media, menor en un factor $\sqrt{I}$—, mientras 254 - que la incertidumbre de muestreo por función/jornada completa $u_1$ (Ec. C.12) es 255 - la desviación típica muestral simple con denominador $N-1$, cuya contribución 256 - $c_1 u_1$ se lee luego de la **Tabla C.4** en función de $(N, u_1)$. La misma 257 - dispersión bruta infla, por tanto, más la estimación por función, que es la 258 - penalización que el estándar impone a un muestreo más grueso y con menos 259 - muestras. (El $L_{EX,8h}$ por función impreso es $88.2$ dB donde el anexo E 260 - declara $88.1$: el estándar redondea el nivel de jornada efectiva a $88.4$ antes 261 - de la normalización de la duración; la biblioteca lo mantiene sin redondear.) 262 - 263 - Cuando las muestras de una tarea abarcan **3 dB o más** (apartado 9.3), o la 264 - contribución por función $c_1 u_1$ supera 3,5 dB (apartado 10.4), o se cubren 265 - demasiado pocos trabajadores (duración acumulada de la Tabla 1), el resultado fija 266 - `sampling_advisory=True` y, con `warn=True`, emite una `OccupationalExposureWarning` que 267 - recomienda más mediciones. Los niveles de pico $L_{p,Cpeak}$ se declaran **sin** 268 - incertidumbre —el anexo C no da método para ellos (Tabla C.5, Nota 1)—, así que 269 - la incertidumbre de pico queda fuera del alcance. Los tres ejemplos resueltos de 270 - los anexos D/E/F anteriores se reproducen con la precisión impresa de la norma 271 - (el redondeo final del anexo E se explica más arriba), y la teoría se deriva 272 - en la página de [Teoría](/phonometry/es/reference/theory/). 273 - 274 - ### Parámetros de `task_based_exposure()` / `job_based_exposure()` / `full_day_exposure()` 275 - 276 - | Parámetro | Se aplica a | Tipo | Unidades | Rango / def. | Notas | 277 - | :--- | :--- | :--- | :--- | :--- | :--- | 278 - | `tasks` | tarea | lista de `Task` | — | ≥ 1 | Cada `Task` tiene `samples`, `duration_hours`, `duration_range`/`duration_samples` opcionales, `label`, `instrument` | 279 - | `samples` | función / jornada | secuencia | dB | ≥ 2 (≥ 5 / ≥ 3 recomendado) | Muestras aleatorias `Lp,A,eqT` | 280 - | `effective_duration_hours` | función / jornada | float | h | > 0 | Duración efectiva de la jornada $T_e$ | 281 - | `instrument` | todas | str | — | `'class1'`, `'class2'`, `'personal_exposimeter'` (def.) | Selecciona $u_2$ (Tabla C.5) | 282 - | `u3` | todas | float | dB | def. `1.0` | Incertidumbre de posición del micrófono (apartado C.6) | 283 - | `include_duration_uncertainty` | tarea | bool | — | def. `True` | `False` omite el término $(c_{1b}u_{1b})^2$ (anexo D caso a) | 284 - | `n_workers` / `sample_duration_hours` | función | int / float | — / h | def. `None` | Comprobación de duración acumulada de la Tabla 1 | 285 - | `warn` | todas | bool | — | def. `True` | Emitir `OccupationalExposureWarning` para los avisos de muestreo | 286 - 287 - Las tres devuelven un `ExposureResult` con `lex_8h`, `combined_standard_uncertainty` 288 - $u$, `expanded_uncertainty` $U = 1.65\ u$, `upper_limit` = $L_{EX,8h} + U$, 289 - `sampling_advisory` y (basada en tareas) el desglose por tarea en `tasks`. 290 - 291 - ## Nivel de sonoridad de tonos puros (ISO 226:2023) 292 - 293 - Las curvas isofónicas normales relacionan el SPL de un tono puro con su *nivel 294 - de sonoridad* percibido en fonos (el SPL de un tono de 1 kHz igual de fuerte). 295 - `equal_loudness_contour(phon)` evalúa la Fórmula (1) de ISO 226:2023 en las 29 296 - frecuencias preferentes de tercio de octava de la Tabla 1, 297 - `loudness_level(spl, frequency)` es la inversa exacta (Fórmula 2) y 298 - `hearing_threshold()` devuelve la columna del umbral de audición: 299 - 300 - ```python 301 - from phonometry import equal_loudness_contour, loudness_level 302 - 303 - freqs, spl = equal_loudness_contour(40.0) # la clásica isofónica de 40 fonos 304 - phon = loudness_level(73.0, 63.0) # 73 dB @ 63 Hz -> 40 fonos 305 - ``` 306 - 307 - <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours_es.png" alt="Curvas isofónicas normales de ISO 226:2023 de 20 a 90 fonos con la curva del umbral de audición" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours_es_dark.png" alt="Curvas isofónicas normales de ISO 226:2023 de 20 a 90 fonos con la curva del umbral de audición" style="width:80%"> 308 - 309 - Validez según el apartado 4.1: 20–90 fonos (80 fonos por encima de 4 kHz); la 310 - implementación se verifica en CI contra las tablas del Anexo B. Ojo: esto es la 311 - sonoridad de *tonos puros* — la sonoridad de señales arbitrarias (sonos, 312 - ISO 532) es una feature distinta, prevista más adelante. 313 - 314 - ## Tonos discretos prominentes (ECMA-418-1) 315 - 316 - Los componentes tonales del ruido de maquinaria molestan mucho más de lo que 317 - sugiere su nivel. ECMA-418-1:2024 (referenciada por el Anexo D de ECMA-74) 318 - define dos métodos FFT para decidir si un tono discreto es *prominente*: 319 - `tone_to_noise_ratio()` compara el nivel del tono con el ruido enmascarante de 320 - su banda crítica (apartado 11) y `prominence_ratio()` compara la banda crítica 321 - centrada en el tono con las dos bandas contiguas (apartado 12). Ambos devuelven 322 - un veredicto estructurado frente a los criterios de prominencia dependientes de 323 - la frecuencia: 324 - 325 - ```python 326 - import numpy as np 327 - from phonometry import tone_to_noise_ratio, prominence_ratio 328 - 329 - fs = 48000 330 - rng = np.random.default_rng(0) 331 - t = np.arange(fs) / fs 332 - x = np.sin(2 * np.pi * 1000 * t) + 0.05 * rng.standard_normal(fs) # tono de 1 kHz en ruido 333 - tnr = tone_to_noise_ratio(x, fs) # pico más alto, o tone_freq=... 334 - pr = prominence_ratio(x, fs, tone_freq=1000.0) 335 - print(tnr.ratio_db, tnr.criterion_db, tnr.prominent) 336 - ``` 337 - 338 - Los métodos se apoyan en la **banda crítica** — el ancho de banda de análisis 339 - del oído, $\Delta f_c = 25 + 75\ [1 + 1.4(f/1000)^2]^{0.69}$ Hz (162 Hz a 340 - 1 kHz): a un tono solo lo enmascara el ruido que hay *dentro* de su banda 341 - crítica, así que ambos ratios comparan el tono exactamente con ese ruido, no 342 - con todo el espectro. 343 - 344 - <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tonality_spectrum_es.png" alt="Espectro promediado de un tono en ruido con la banda crítica sombreada y la relación tono-ruido anotada frente a su criterio de prominencia" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tonality_spectrum_es_dark.png" alt="Espectro promediado de un tono en ruido con la banda crítica sombreada y la relación tono-ruido anotada frente a su criterio de prominencia" style="width:80%"> 345 - 346 - Un TNR por encima de $8 + 8.33\log_{10}(1000/f_t)$ dB (8 dB de 1 kHz hacia 347 - arriba) clasifica el tono como *prominente*; el criterio del PR es 348 - $9 + 10\log_{10}(1000/f_t)$ dB. Las frecuencias bajas reciben umbrales más 349 - altos porque unas bandas relativamente más anchas enmascaran más. 350 - 351 - ECMA-74 (que delega la evaluación tonal en ECMA-418-1) también fija dónde medir alrededor de un equipo: 352 - 353 - <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions_es.svg" alt="Posiciones de medida de emisión ECMA-74: micrófono del operador sentado a 0,25 m y 1,20 m, y las cuatro posiciones de observador a 1 m" style="width:92%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions_es_dark.svg" alt="Posiciones de medida de emisión ECMA-74: micrófono del operador sentado a 0,25 m y 1,20 m, y las cuatro posiciones de observador a 1 m" style="width:92%"> 354 - 355 - Los tonos secundarios próximos en la misma banda crítica se combinan según el 356 - apartado 11.6; para complejos armónicos evalúa cada componente (`tone_freq=`). 357 - Ambos métodos trabajan sobre espectros promediados RMS con ventana Hann y no 358 - necesitan calibración absoluta (los ratios son diferencias de nivel). 359 - 360 - ### Parámetros de `tone_to_noise_ratio()` / `prominence_ratio()` 361 - 362 - | Parámetro | Tipo | Unidades | Rango / valor por defecto | Notas | 363 - | :--- | :--- | :--- | :--- | :--- | 364 - | `x` | array 1D | cualquiera (vale sin calibrar) | ≥ `fs/resolution_hz` muestras | Los ratios son diferencias de nivel: la calibración se cancela | 365 - | `fs` | int | Hz | > 0 | | 366 - | `tone_freq` | float, opcional | Hz | 89,1–11 200; por defecto `None` | `None` evalúa el pico más alto del rango de interés | 367 - | `resolution_hz` | float | Hz | > 0; por defecto `1.0` | La banda del tono debe quedar dentro del 15 % de la banda crítica (apartado 11.2) | 368 - 369 - Ambos devuelven un `ToneAssessment(frequency, ratio_db, criterion_db, prominent)`. 183 + `lex_8h` califica *una* grabación; componer una jornada laboral completa a 184 + partir de muestras por tarea o por función —con el presupuesto normativo de 185 + incertidumbre de la ISO 9612— continúa en 186 + [Exposición al ruido en el trabajo](/phonometry/es/guides/occupational-exposure/). 370 187 371 188 ## Ruido ambiental: Lden, Ldn y niveles de evaluación (ISO 1996-1) 372 189 ··· 402 219 403 220 <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_env_measurement_es.svg" alt="Posiciones de medida de ruido ambiental según ISO 1996-2: campo libre, a 2 m de la fachada y enrasado, con sus correcciones" style="width:92%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_env_measurement_es_dark.svg" alt="Posiciones de medida de ruido ambiental según ISO 1996-2: campo libre, a 2 m de la fachada y enrasado, con sus correcciones" style="width:92%"> 404 221 405 - Combínalo con `laeq()` por periodo para ir de grabaciones a Lden, y con 406 - `tone_to_noise_ratio()` / `prominence_ratio()` para justificar ajustes tonales. 222 + Combínalo con `laeq()` por periodo para ir de grabaciones a Lden, y con los 223 + veredictos `tone_to_noise_ratio()` / `prominence_ratio()` de 224 + [Tonos discretos prominentes](/phonometry/es/guides/tone-prominence/) para 225 + justificar ajustes tonales. 407 226 408 227 ## Espectrograma de octavas (niveles vs tiempo) 409 228 ··· 455 274 Consulta [Calibración y dBFS](/phonometry/es/guides/calibration/) para 456 275 convertir unidades digitales a SPL físico, y 457 276 [Ponderación temporal](/phonometry/es/guides/time-weighting/) para los 458 - detalles de la envolvente. 277 + detalles de la envolvente. Las estrategias ocupacionales de la ISO 9612 278 + continúan en 279 + [Exposición al ruido en el trabajo](/phonometry/es/guides/occupational-exposure/), 280 + los veredictos de prominencia tonal de ECMA-418-1 en 281 + [Tonos discretos prominentes](/phonometry/es/guides/tone-prominence/), y las 282 + curvas isofónicas de ISO 226 viven con las métricas de percepción en 283 + [Psicoacústica](/phonometry/es/guides/psychoacoustics/).
+138
site/src/content/docs/es/guides/occupational-exposure.md
··· 1 + --- 2 + title: "Exposición al ruido en el trabajo (ISO 9612)" 3 + description: "Las estrategias de medición basada en tareas, basada en la función y de jornada completa de la ISO 9612 para el nivel de exposición diario LEX,8h, con el presupuesto de incertidumbre del anexo C y el límite superior unilateral al 95 %." 4 + --- 5 + 6 + Una jornada laboral rara vez se mide de una sola toma: el nivel de exposición 7 + diario sobre el que actúa la normativa hay que componerlo a partir de 8 + *muestras* de un turno real, y declararlo con una incertidumbre que un 9 + higienista pueda defender. `lex_8h` (en 10 + [Niveles](/phonometry/es/guides/levels/)) convierte *una* grabación en un nivel 11 + diario. La ISO 9612:2009 —el método de ingeniería (clase de exactitud 2)— es el 12 + diseño de la medición *en torno* a esa primitiva: cómo muestrear una jornada 13 + laboral real, cómo combinar las partes y cómo adjuntar la incertidumbre 14 + normativa que necesita todo informe de higiene laboral. El módulo 15 + `occupational_exposure` añade las tres **estrategias de medición** y el 16 + presupuesto de incertidumbre del **anexo C** sobre la maquinaria del promediado 17 + en energía. 18 + 19 + ## 1. Las tres estrategias de medición (apartados 9-11) 20 + 21 + La estrategia *basada en tareas* (apartado 9) divide la jornada nominal en 22 + tareas, toma $I \ge 3$ muestras por tarea y suma en energía las contribuciones de 23 + cada tarea 24 + 25 + $$ 26 + L_{EX,8h,m} = L_{p,A,eqT,m} + 10 \log_{10}(T_m/T_0), \qquad T_0 = 8\ \text{h}, 27 + $$ 28 + 29 + de modo que una tarea ruidosa pero corta contribuye poco. Las estrategias 30 + *basada en la función* (apartado 10) y *de jornada completa* (apartado 11) toman 31 + en cambio $N \ge 5$ (o tres jornadas completas) muestras aleatorias sobre un grupo 32 + de exposición homogéneo y normalizan la duración efectiva de la jornada. El nivel 33 + diario es el mismo en ambos casos; las estrategias difieren en cómo se construye 34 + la **incertidumbre**. 35 + 36 + <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/exposure_uncertainty_es.png" alt="Exposición por tareas del anexo D de la ISO 9612: las tres contribuciones de tarea a LEX,8h como barras, la línea del LEX,8h diario sumado en energía y la banda del límite superior unilateral al 95 % LEX,8h + U por encima" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/exposure_uncertainty_es_dark.png" alt="Exposición por tareas del anexo D de la ISO 9612: las tres contribuciones de tarea a LEX,8h como barras, la línea del LEX,8h diario sumado en energía y la banda del límite superior unilateral al 95 % LEX,8h + U por encima" style="width:80%"> 37 + 38 + ```python 39 + from phonometry.occupational_exposure import ( 40 + Task, task_based_exposure, job_based_exposure, full_day_exposure, 41 + ) 42 + 43 + # ISO 9612 anexo D — la jornada de un soldador dividida en tres tareas. El nivel 44 + # de cada tarea es el promedio en energía de sus muestras Lp,A,eqT; las 45 + # duraciones llevan un rango medido. 46 + tasks = [ 47 + Task(samples=(70.0,), duration_hours=1.5, label="planning/breaks"), 48 + Task(samples=(80.1, 82.2, 79.6), duration_hours=5.0, 49 + duration_range=(4.0, 6.0), label="welding"), 50 + Task(samples=(86.5, 92.4, 89.3, 93.2, 87.8, 86.2), duration_hours=1.5, 51 + duration_range=(1.0, 2.0), label="cutting/grinding"), 52 + ] 53 + res = task_based_exposure(tasks, include_duration_uncertainty=False, warn=False) 54 + print(f"LEX,8h = {res.lex_8h:.1f} dB U = {res.expanded_uncertainty:.1f} dB") 55 + # LEX,8h = 84.3 dB U = 2.7 dB 56 + print(f"one-sided 95 % upper limit LEX,8h + U = {res.upper_limit:.1f} dB") # 87.0 dB 57 + for t in res.tasks: 58 + print(f" {t.label:<16} Lp,A,eqT = {t.lp_aeqt:5.1f} contributes {t.lex_8h_contribution:5.1f} dB") 59 + # planning/breaks Lp,A,eqT = 70.0 contributes 62.7 dB 60 + # welding Lp,A,eqT = 80.8 contributes 78.7 dB 61 + # cutting/grinding Lp,A,eqT = 90.1 contributes 82.8 dB 62 + 63 + # La misma jornada medida basada en la función (anexo E) y de jornada completa 64 + # (anexo F): ambas usan el presupuesto de muestreo Ec C.9 / Tabla C.4 con 65 + # k = 1.65 (unilateral 95 %). 66 + job = job_based_exposure([88.1, 86.1, 89.7, 86.5, 91.1, 86.7], effective_duration_hours=7.5) 67 + full = full_day_exposure([88.0, 91.9, 87.6, 90.4, 89.0, 88.4], effective_duration_hours=9.25) 68 + print(f"job LEX,8h = {job.lex_8h:.1f} dB U = {job.expanded_uncertainty:.1f} dB") 69 + # job LEX,8h = 88.2 dB U = 3.8 dB 70 + print(f"full-day LEX,8h = {full.lex_8h:.1f} dB U = {full.expanded_uncertainty:.1f} dB") 71 + # full-day LEX,8h = 90.1 dB U = 3.4 dB 72 + ``` 73 + 74 + ## 2. El presupuesto de incertidumbre del anexo C 75 + 76 + Conviene detallar dos sutilezas. Primero, el factor de cobertura es $k = 1.65$ 77 + para un intervalo **unilateral** al 95 % (apartado 14), porque al higienista solo 78 + le importa la cota *superior*: `res.upper_limit` = $L_{EX,8h} + U$ es el valor por 79 + debajo del cual queda el 95 % de las mediciones, el número que se compara con un 80 + valor de acción. Segundo, los métodos por tareas y por función ponderan de forma 81 + distinta la *misma* dispersión de muestras. La incertidumbre de muestreo de la 82 + tarea $u_{1a}$ (Ec. C.6) divide la suma de desviaciones al cuadrado entre 83 + $I(I-1)$ —el error típico de la media, menor en un factor $\sqrt{I}$—, mientras 84 + que la incertidumbre de muestreo por función/jornada completa $u_1$ (Ec. C.12) es 85 + la desviación típica muestral simple con denominador $N-1$, cuya contribución 86 + $c_1 u_1$ se lee luego de la **Tabla C.4** en función de $(N, u_1)$. La misma 87 + dispersión bruta infla, por tanto, más la estimación por función, que es la 88 + penalización que el estándar impone a un muestreo más grueso y con menos 89 + muestras. (El $L_{EX,8h}$ por función impreso es $88.2$ dB donde el anexo E 90 + declara $88.1$: el estándar redondea el nivel de jornada efectiva a $88.4$ antes 91 + de la normalización de la duración; la biblioteca lo mantiene sin redondear.) 92 + 93 + Cuando las muestras de una tarea abarcan **3 dB o más** (apartado 9.3), o la 94 + contribución por función $c_1 u_1$ supera 3,5 dB (apartado 10.4), o se cubren 95 + demasiado pocos trabajadores (duración acumulada de la Tabla 1), el resultado fija 96 + `sampling_advisory=True` y, con `warn=True`, emite una `OccupationalExposureWarning` que 97 + recomienda más mediciones. Los niveles de pico $L_{p,Cpeak}$ se declaran **sin** 98 + incertidumbre —el anexo C no da método para ellos (Tabla C.5, Nota 1)—, así que 99 + la incertidumbre de pico queda fuera del alcance. Los tres ejemplos resueltos de 100 + los anexos D/E/F anteriores se reproducen con la precisión impresa de la norma 101 + (el redondeo final del anexo E se explica más arriba), y la teoría se deriva 102 + en la página de [Teoría](/phonometry/es/reference/theory/). 103 + 104 + ### Parámetros de `task_based_exposure()` / `job_based_exposure()` / `full_day_exposure()` 105 + 106 + | Parámetro | Se aplica a | Tipo | Unidades | Rango / def. | Notas | 107 + | :--- | :--- | :--- | :--- | :--- | :--- | 108 + | `tasks` | tarea | lista de `Task` | — | ≥ 1 | Cada `Task` tiene `samples`, `duration_hours`, `duration_range`/`duration_samples` opcionales, `label`, `instrument` | 109 + | `samples` | función / jornada | secuencia | dB | ≥ 2 (≥ 5 / ≥ 3 recomendado) | Muestras aleatorias `Lp,A,eqT` | 110 + | `effective_duration_hours` | función / jornada | float | h | > 0 | Duración efectiva de la jornada $T_e$ | 111 + | `instrument` | todas | str | — | `'class1'`, `'class2'`, `'personal_exposimeter'` (def.) | Selecciona $u_2$ (Tabla C.5) | 112 + | `u3` | todas | float | dB | def. `1.0` | Incertidumbre de posición del micrófono (apartado C.6) | 113 + | `include_duration_uncertainty` | tarea | bool | — | def. `True` | `False` omite el término $(c_{1b}u_{1b})^2$ (anexo D caso a) | 114 + | `n_workers` / `sample_duration_hours` | función | int / float | — / h | def. `None` | Comprobación de duración acumulada de la Tabla 1 | 115 + | `warn` | todas | bool | — | def. `True` | Emitir `OccupationalExposureWarning` para los avisos de muestreo | 116 + 117 + Las tres devuelven un `ExposureResult` con `lex_8h`, `combined_standard_uncertainty` 118 + $u$, `expanded_uncertainty` $U = 1.65\ u$, `upper_limit` = $L_{EX,8h} + U$, 119 + `sampling_advisory` y (basada en tareas) el desglose por tarea en `tasks`. 120 + 121 + ## Véase también 122 + 123 + - [Niveles](/phonometry/es/guides/levels/) — las primitivas de dosis `lex_8h` / 124 + `sound_exposure` (IEC 61252) y el LCpeak que estas estrategias declaran al 125 + lado. 126 + - [Incertidumbre de medición](/phonometry/es/guides/gum-uncertainty/) — la 127 + maquinaria GUM tras las incertidumbres combinadas y expandidas. 128 + - [Teoría](/phonometry/es/reference/theory/) — la derivación de las fórmulas de 129 + las estrategias y del presupuesto del anexo C. 130 + 131 + --- 132 + 133 + **Normas.** ISO 9612:2009, *Acoustics — Determination of occupational noise 134 + exposure — Engineering method* — las estrategias basada en tareas (apartado 9), 135 + basada en la función (apartado 10) y de jornada completa (apartado 11), el 136 + presupuesto de incertidumbre del anexo C (Ecuaciones C.6, C.9 y C.12, Tablas 137 + C.4/C.5) y el factor de cobertura unilateral k = 1,65 (apartado 14), validado 138 + frente a los ejemplos resueltos de los anexos D, E y F.
+3 -2
site/src/content/docs/es/guides/outdoor-propagation.md
··· 260 260 La exactitud declarada del método es de $\pm 1$ a $\pm 3$ dB para ruido de banda 261 261 ancha hasta 1000 m (Tabla 5). Consulta la página de [Teoría](/phonometry/es/reference/theory/) 262 262 para la derivación completa, la [guía de Acústica de salas](/phonometry/es/guides/room-acoustics/) 263 - para cómo $\alpha$ alimenta la ISO 354, y la [guía de Niveles](/phonometry/es/guides/levels/) 264 - para la exposición al ruido en el trabajo (ISO 9612) que consume niveles 263 + para cómo $\alpha$ alimenta la ISO 354, y la 264 + [guía de exposición al ruido en el trabajo](/phonometry/es/guides/occupational-exposure/) 265 + para la exposición ocupacional (ISO 9612) que consume niveles 265 266 ponderados A.
+29 -95
site/src/content/docs/es/guides/psychoacoustics.md
··· 1 1 --- 2 - title: "Psicoacústica e inteligibilidad del habla" 3 - description: "Sonoridad de Zwicker (ISO 532-1), Moore-Glasberg (ISO 532-2/3) y Sottek (ECMA-418-2), sharpness (DIN 45692), tonalidad y aspereza, además del índice de transmisión del habla STI/STIPA (IEC 60268-16)." 2 + title: "Psicoacústica" 3 + description: "Sonoridad de Zwicker (ISO 532-1), Moore-Glasberg (ISO 532-2/3) y Sottek (ECMA-418-2), sharpness (DIN 45692), curvas isofónicas de ISO 226, y tonalidad y aspereza de ECMA-418-2." 4 4 --- 5 5 6 6 Las métricas de nivel dicen cuánta *presión sonora* hay; las métricas 7 7 psicoacústicas dicen qué *percibe* realmente quien escucha. Esta página cubre 8 - la sonoridad (ISO 532-1), el sharpness (DIN 45692) y el índice de transmisión 9 - del habla (IEC 60268-16), y luego los modelos avanzados de sonoridad, tonalidad 8 + la sonoridad (ISO 532-1), el sharpness (DIN 45692) y las curvas isofónicas de 9 + tonos puros (ISO 226), y luego los modelos avanzados de sonoridad, tonalidad 10 10 y aspereza de Moore-Glasberg (ISO 532-2/3) y del modelo de Sottek 11 - (ECMA-418-2). 11 + (ECMA-418-2). Las métricas de habla tienen guía propia: el STI/STIPA de canal 12 + de transmisión en el 13 + [índice de transmisión del habla](/phonometry/es/guides/speech-transmission/) y 14 + el SII basado en audibilidad en el 15 + [índice de inteligibilidad del habla](/phonometry/es/guides/speech-intelligibility/). 12 16 13 17 ## Sonoridad en sonos (ISO 532-1, Zwicker) 14 18 ··· 135 139 hasta 2,82 acum a 4 kHz) dentro de la tolerancia del 5 % / 0,05 acum de la 136 140 norma. 137 141 138 - ## Índice de transmisión del habla (IEC 60268-16) 139 - 140 - La reverberación y el ruido no amortiguan el habla de manera uniforme — 141 - emborronan su *envolvente*: las modulaciones lentas de intensidad 142 - (0,63–12,5 Hz) que transportan las sílabas. El STI cuantifica cuánta de esa 143 - modulación sobrevive de la boca al oído, por banda de octava, como la 144 - **función de transferencia de modulación (MTF)** m(F). Un canal tipo delta 145 - mantiene m = 1 (STI = 1); la reverberación filtra paso-bajo la envolvente 146 - siguiendo la forma cerrada de Schroeder, y el ruido estacionario la escala: 147 - 148 - $$ 149 - m(F) = \frac{1}{\sqrt{1 + \left(2\pi F\ \frac{T_{60}}{13.8}\right)^2}} 150 - \cdot \frac{1}{1 + 10^{-\mathrm{SNR}/10}} 151 - $$ 142 + ## Nivel de sonoridad de tonos puros (ISO 226:2023) 152 143 153 - <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60_es.png" alt="STI frente al tiempo de reverberación con las bandas de calificación del Anexo F de IEC 60268-16 sombreadas" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60_es_dark.png" alt="STI frente al tiempo de reverberación con las bandas de calificación del Anexo F de IEC 60268-16 sombreadas" style="width:80%"> 154 - 155 - <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain_es.svg" alt="Cadena de medición del STI: señal de la fuente STIPA a través de la sala hasta el micrófono y el análisis de la MTF" style="width:92%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain_es_dark.svg" alt="Cadena de medición del STI: señal de la fuente STIPA a través de la sala hasta el micrófono y el análisis de la MTF" style="width:92%"> 144 + Las curvas isofónicas normales relacionan el SPL de un tono puro con su *nivel 145 + de sonoridad* percibido en fonos (el SPL de un tono de 1 kHz igual de fuerte). 146 + `equal_loudness_contour(phon)` evalúa la Fórmula (1) de ISO 226:2023 en las 29 147 + frecuencias preferentes de tercio de octava de la Tabla 1, 148 + `loudness_level(spl, frequency)` es la inversa exacta (Fórmula 2) y 149 + `hearing_threshold()` devuelve la columna del umbral de audición: 156 150 157 151 ```python 158 - import numpy as np 159 - from phonometry import sti_from_impulse_response, stipa, stipa_signal 160 - 161 - fs = 48000 162 - # Una respuesta al impulso medida en la sala (decaimiento sintetizado para que el ejemplo funcione) 163 - ir = np.random.default_rng(0).standard_normal(fs) * np.exp(-6.9 * np.arange(fs) / fs / 0.5) 164 - 165 - # Método indirecto: desde una respuesta al impulso medida en la sala 166 - res = sti_from_impulse_response(ir, fs, snr=25.0) 167 - print(f"STI = {res.sti:.2f} ({res.rating})") # p. ej. 0.62 (D) 168 - 169 - # Medición STIPA directa: reproduce stipa_signal() en la sala y grábala 170 - test = stipa_signal(fs, seconds=18.0, level_db=80.0) 171 - recording = test # en la práctica, la señal del micrófono tras la reproducción 172 - res = stipa(recording, fs) 173 - res.plot() # barras del índice de transferencia de modulación (MTI) por banda; STI y valoración en el título 174 - ``` 175 - 176 - <details> 177 - <summary>Ver el código de esta figura</summary> 178 - 179 - ```python 180 - import matplotlib.pyplot as plt 181 - 182 - # STI frente al tiempo de reverberación: barre sti_from_impulse_response sobre 183 - # decaimientos exponenciales sintéticos (ruido blanco x exp(-6.9077 t / T60)) 184 - # en una rejilla de T60 — exactamente la física de la curva de arriba: 185 - rng = np.random.default_rng(0) 186 - t60_grid = np.array([0.3, 0.5, 0.8, 1.2, 1.6, 2.0, 2.5, 3.0, 4.0, 5.0]) 187 - sti_values = [] 188 - for t60 in t60_grid: 189 - t = np.arange(int(2 * t60 * fs)) / fs 190 - ir = rng.standard_normal(t.size) * np.exp(-6.9077 * t / t60) 191 - sti_values.append(sti_from_impulse_response(ir, fs).sti) 152 + from phonometry import equal_loudness_contour, loudness_level 192 153 193 - fig, ax = plt.subplots() 194 - ax.semilogx(t60_grid, sti_values, "o-") 195 - ax.set_xlabel("Tiempo de reverberación T60 [s]") 196 - ax.set_ylabel("STI") 197 - ax.set_ylim(0.0, 1.0) 198 - ax.grid(True, which="both", alpha=0.3) 199 - plt.show() 154 + freqs, spl = equal_loudness_contour(40.0) # la clásica isofónica de 40 fonos 155 + phon = loudness_level(73.0, 63.0) # 73 dB @ 63 Hz -> 40 fonos 200 156 ``` 201 157 202 - </details> 158 + <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours_es.png" alt="Curvas isofónicas normales de ISO 226:2023 de 20 a 90 fonos con la curva del umbral de audición" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours_es_dark.png" alt="Curvas isofónicas normales de ISO 226:2023 de 20 a 90 fonos con la curva del umbral de audición" style="width:80%"> 203 159 204 - `stipa` emite un `UserWarning` cuando la grabación es más corta que los 15 s 205 - recomendados (práctica STIPA de IEC 60268-16, de 15 s a 25 s): por debajo de eso 206 - las componentes de modulación lentas se promedian sobre muy pocos periodos y el 207 - STI queda sesgado a la baja (un lazo ideal da STI ≈ 0,944 a 5 s frente a 208 - ≈ 0,998 a 18 s). 209 - 210 - La implementación sigue la **Edición 5 (2020)**: el PDF normativo de la 211 - Edición 4 es la base y cada cambio de la Ed. 5 está atribuido a su fuente en el 212 - código — el único delta numérico es el espectro de habla masculina revisado del 213 - apartado A.6.1. CI comprueba los vectores de verificación de la propia norma: 214 - los seis pares de bandas de los factores de ponderación a ±0,001 STI, la tabla 215 - de correspondencia m ↔ STI, los puntos de control del enmascaramiento 216 - dependiente del nivel y decaimientos con la forma de Schroeder a cuatro valores 217 - de T₆₀. 218 - 219 - ### Parámetros de `sti_from_impulse_response()` / `stipa()` 220 - 221 - | Parámetro | Tipo | Unidades | Rango / valor por defecto | Notas | 222 - | :--- | :--- | :--- | :--- | :--- | 223 - | `ir` / `x` | array 1D | cualquiera / Pa | no vacío | Respuesta al impulso (indirecto) o grabación STIPA (directo) | 224 - | `fs` | int | Hz | > 0 | | 225 - | `snr` | float o vector de 7, opcional | dB | por defecto `None` | Añade la degradación por ruido estacionario | 226 - | `level` | vector de 7, opcional | dB SPL | por defecto `None` | Activa el enmascaramiento auditivo + umbral de recepción (Tablas A.2/A.3) | 227 - | `ambient` | vector de 7, opcional | dB SPL | requiere `level` | Niveles de banda del ruido ambiente | 228 - | `reference` | array 1D, opcional (`stipa`) | — | por defecto `None` | Señal de la fuente medida en lugar del m = 0,55 nominal | 229 - 230 - Ambas devuelven `STIResult`: `sti`, `mti` (7 bandas), `mtf` (7×14 o 7×2), 231 - `band_levels`, `rating` (letra del Anexo F, `A+`…`U`). 160 + Validez según el apartado 4.1: 20–90 fonos (80 fonos por encima de 4 kHz); la 161 + implementación se verifica en CI contra las tablas del Anexo B. Ojo: esto es la 162 + sonoridad de *tonos puros* — la sonoridad de señales arbitrarias en sonos es lo 163 + que calculan los modelos ISO 532 de esta página. 232 164 233 165 ## Modelos avanzados de sonoridad y calidad sonora 234 166 ··· 571 503 `time`, `roughness_vs_time` (R(l50)), `specific_roughness_vs_time` 572 504 (array de (n_times, 53)), `field`. 573 505 574 - Consulta [Niveles](/phonometry/es/guides/levels/) para las métricas de 575 - tonalidad y [Teoría](/phonometry/es/reference/theory/) para la matemática 576 - subyacente. 506 + Consulta [Tonos discretos prominentes](/phonometry/es/guides/tone-prominence/) 507 + para los veredictos TNR/PR de ECMA-418-1, el 508 + [índice de transmisión del habla](/phonometry/es/guides/speech-transmission/) 509 + para el STI/STIPA, y [Teoría](/phonometry/es/reference/theory/) para la 510 + matemática subyacente.
+5 -3
site/src/content/docs/es/guides/room-acoustics.md
··· 412 412 `d2s`/`lp_as_4m` son `nan` si menos de dos posiciones caen en 2–16 m; 413 413 `rd`/`rp` son `nan` cuando el STI no decrece con la distancia. El STI por 414 414 posición puede medirse a su vez con las herramientas STIPA de la 415 - [guía de Psicoacústica](/phonometry/es/guides/psychoacoustics/). 415 + [guía del índice de transmisión del habla](/phonometry/es/guides/speech-transmission/). 416 416 417 417 ## 4. Absorción sonora (ISO 354) 418 418 ··· 481 481 - [Potencia sonora](/phonometry/es/guides/sound-power/) — los métodos de `LW` que 482 482 consumen el área de absorción de ISO 354 (el `K2` de ISO 3744 y el término de 483 483 absorción de ISO 3741). 484 - - [Psicoacústica e inteligibilidad del habla](/phonometry/es/guides/psychoacoustics/) — el 485 - STI/STIPA alimenta los `sti_values` de oficinas diáfanas; sonoridad y sharpness. 484 + - [Índice de transmisión del habla](/phonometry/es/guides/speech-transmission/) — la 485 + medición STI/STIPA que alimenta los `sti_values` de oficinas diáfanas. 486 + - [Psicoacústica](/phonometry/es/guides/psychoacoustics/) — la sonoridad, el 487 + sharpness y las demás métricas de percepción de lo que entrega la sala. 486 488 - [Bancos de filtros](/phonometry/es/guides/filter-banks/) — los filtros de octava 487 489 fraccionaria IEC 61260 usados para las curvas de decaimiento por banda y los espectros de 488 490 aislamiento.
+13 -2
site/src/content/docs/es/guides/speech-intelligibility.md
··· 11 11 **método por bandas de tercio de octava** de **ANSI S3.5-1997 (R2017)** — 18 12 12 bandas de 160 Hz a 8000 Hz. 13 13 14 + :::note 15 + **SII frente a STI.** El SII predice la inteligibilidad desde la 16 + *audibilidad* — cuánto del espectro del habla supera el ruido y el umbral de 17 + audición en el oído de quien escucha —, mientras que el STI caracteriza un 18 + *canal de transmisión*: cuánta de la modulación del habla conserva una sala o 19 + un sistema de sonido. Para esto último, consulta la 20 + [guía del índice de transmisión del habla](/phonometry/es/guides/speech-transmission/). 21 + ::: 22 + 14 23 <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_speech_intelligibility_es.svg" alt="El flujo de cálculo del SII: tres entradas de nivel espectral equivalente (habla Ei', ruido Ni', umbral auditivo Ti') alimentan la etapa de automáscara del habla y propagación de la máscara (nivel espectral de enmascaramiento equivalente Zi), después la perturbación equivalente Di, después la función de audibilidad de banda Ai acotada a [0, 1], y por último la suma ponderada por importancia de banda SII sobre las 18 bandas de tercio de octava" style="width:94%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_speech_intelligibility_es_dark.svg" alt="El flujo de cálculo del SII: tres entradas de nivel espectral equivalente (habla Ei', ruido Ni', umbral auditivo Ti') alimentan la etapa de automáscara del habla y propagación de la máscara (nivel espectral de enmascaramiento equivalente Zi), después la perturbación equivalente Di, después la función de audibilidad de banda Ai acotada a [0, 1], y por último la suma ponderada por importancia de banda SII sobre las 18 bandas de tercio de octava" style="width:94%"> 15 24 16 25 ## 1. Entradas y la función de importancia de banda ··· 159 168 160 169 ## Véase también 161 170 162 - - [Psicoacústica e inteligibilidad del habla](/phonometry/es/guides/psychoacoustics/) — la 163 - sonoridad, la nitidez y el índice de transmisión STI/STIPA que el SII complementa. 171 + - [Índice de transmisión del habla](/phonometry/es/guides/speech-transmission/) — el 172 + índice de transmisión STI/STIPA que el SII complementa. 173 + - [Psicoacústica](/phonometry/es/guides/psychoacoustics/) — la sonoridad, el 174 + sharpness y las métricas de percepción de lo que oye quien escucha. 164 175 - [Bancos de filtros](/phonometry/es/guides/filter-banks/) — las bandas de tercio de octava 165 176 sobre las que se evalúa el SII. 166 177 - [Niveles](/phonometry/es/guides/levels/) — los niveles espectrales y de banda tras las
+141
site/src/content/docs/es/guides/speech-transmission.md
··· 1 + --- 2 + title: "Índice de transmisión del habla (STI)" 3 + description: "El índice de transmisión del habla de IEC 60268-16: la función de transferencia de modulación, el método indirecto desde una respuesta al impulso medida y la medición STIPA directa con su señal de ensayo normalizada." 4 + --- 5 + 6 + Un sistema de megafonía, un interfono, un aula reverberante — cada uno es un 7 + *canal de transmisión* entre la boca de quien habla y el oído de quien escucha, 8 + y cada uno degrada el habla a su manera. El **índice de transmisión del habla** 9 + (STI) de IEC 60268-16 califica ese canal con un único número en [0, 1] midiendo 10 + cuánta de la *envolvente* del habla sobrevive al trayecto. Esta página cubre la 11 + física de transferencia de modulación tras el índice, el método indirecto desde 12 + una respuesta al impulso medida en la sala y la medición STIPA directa con su 13 + señal de ensayo normalizada. 14 + 15 + :::note 16 + **STI frente a SII.** El STI caracteriza un *canal de transmisión* — cuánta de 17 + la modulación del habla conserva una sala o un sistema de sonido —, mientras 18 + que el SII predice la inteligibilidad desde la *audibilidad*: cuánto del 19 + espectro del habla supera el ruido y el umbral de audición en el oído de quien 20 + escucha. Para esto último, consulta la 21 + [guía del índice de inteligibilidad del habla](/phonometry/es/guides/speech-intelligibility/). 22 + ::: 23 + 24 + ## 1. La función de transferencia de modulación 25 + 26 + La reverberación y el ruido no amortiguan el habla de manera uniforme — 27 + emborronan su *envolvente*: las modulaciones lentas de intensidad 28 + (0,63–12,5 Hz) que transportan las sílabas. El STI cuantifica cuánta de esa 29 + modulación sobrevive de la boca al oído, por banda de octava, como la 30 + **función de transferencia de modulación (MTF)** m(F). Un canal tipo delta 31 + mantiene m = 1 (STI = 1); la reverberación filtra paso-bajo la envolvente 32 + siguiendo la forma cerrada de Schroeder, y el ruido estacionario la escala: 33 + 34 + $$ 35 + m(F) = \frac{1}{\sqrt{1 + \left(2\pi F\ \frac{T_{60}}{13.8}\right)^2}} 36 + \cdot \frac{1}{1 + 10^{-\mathrm{SNR}/10}} 37 + $$ 38 + 39 + <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60_es.png" alt="STI frente al tiempo de reverberación con las bandas de calificación del Anexo F de IEC 60268-16 sombreadas" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60_es_dark.png" alt="STI frente al tiempo de reverberación con las bandas de calificación del Anexo F de IEC 60268-16 sombreadas" style="width:80%"> 40 + 41 + <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain_es.svg" alt="Cadena de medición del STI: señal de la fuente STIPA a través de la sala hasta el micrófono y el análisis de la MTF" style="width:92%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain_es_dark.svg" alt="Cadena de medición del STI: señal de la fuente STIPA a través de la sala hasta el micrófono y el análisis de la MTF" style="width:92%"> 42 + 43 + ## 2. Medición indirecta y directa (STIPA) 44 + 45 + ```python 46 + import numpy as np 47 + from phonometry import sti_from_impulse_response, stipa, stipa_signal 48 + 49 + fs = 48000 50 + # Una respuesta al impulso medida en la sala (decaimiento sintetizado para que el ejemplo funcione) 51 + ir = np.random.default_rng(0).standard_normal(fs) * np.exp(-6.9 * np.arange(fs) / fs / 0.5) 52 + 53 + # Método indirecto: desde una respuesta al impulso medida en la sala 54 + res = sti_from_impulse_response(ir, fs, snr=25.0) 55 + print(f"STI = {res.sti:.2f} ({res.rating})") # p. ej. 0.62 (D) 56 + 57 + # Medición STIPA directa: reproduce stipa_signal() en la sala y grábala 58 + test = stipa_signal(fs, seconds=18.0, level_db=80.0) 59 + recording = test # en la práctica, la señal del micrófono tras la reproducción 60 + res = stipa(recording, fs) 61 + res.plot() # barras del índice de transferencia de modulación (MTI) por banda; STI y valoración en el título 62 + ``` 63 + 64 + <details> 65 + <summary>Ver el código de esta figura</summary> 66 + 67 + ```python 68 + import matplotlib.pyplot as plt 69 + 70 + # STI frente al tiempo de reverberación: barre sti_from_impulse_response sobre 71 + # decaimientos exponenciales sintéticos (ruido blanco x exp(-6.9077 t / T60)) 72 + # en una rejilla de T60 — exactamente la física de la curva de arriba: 73 + rng = np.random.default_rng(0) 74 + t60_grid = np.array([0.3, 0.5, 0.8, 1.2, 1.6, 2.0, 2.5, 3.0, 4.0, 5.0]) 75 + sti_values = [] 76 + for t60 in t60_grid: 77 + t = np.arange(int(2 * t60 * fs)) / fs 78 + ir = rng.standard_normal(t.size) * np.exp(-6.9077 * t / t60) 79 + sti_values.append(sti_from_impulse_response(ir, fs).sti) 80 + 81 + fig, ax = plt.subplots() 82 + ax.semilogx(t60_grid, sti_values, "o-") 83 + ax.set_xlabel("Tiempo de reverberación T60 [s]") 84 + ax.set_ylabel("STI") 85 + ax.set_ylim(0.0, 1.0) 86 + ax.grid(True, which="both", alpha=0.3) 87 + plt.show() 88 + ``` 89 + 90 + </details> 91 + 92 + `stipa` emite un `UserWarning` cuando la grabación es más corta que los 15 s 93 + recomendados (práctica STIPA de IEC 60268-16, de 15 s a 25 s): por debajo de eso 94 + las componentes de modulación lentas se promedian sobre muy pocos periodos y el 95 + STI queda sesgado a la baja (un lazo ideal da STI ≈ 0,944 a 5 s frente a 96 + ≈ 0,998 a 18 s). 97 + 98 + La implementación sigue la **Edición 5 (2020)**: el PDF normativo de la 99 + Edición 4 es la base y cada cambio de la Ed. 5 está atribuido a su fuente en el 100 + código — el único delta numérico es el espectro de habla masculina revisado del 101 + apartado A.6.1. CI comprueba los vectores de verificación de la propia norma: 102 + los seis pares de bandas de los factores de ponderación a ±0,001 STI, la tabla 103 + de correspondencia m ↔ STI, los puntos de control del enmascaramiento 104 + dependiente del nivel y decaimientos con la forma de Schroeder a cuatro valores 105 + de T₆₀. 106 + 107 + ### Parámetros de `sti_from_impulse_response()` / `stipa()` 108 + 109 + | Parámetro | Tipo | Unidades | Rango / valor por defecto | Notas | 110 + | :--- | :--- | :--- | :--- | :--- | 111 + | `ir` / `x` | array 1D | cualquiera / Pa | no vacío | Respuesta al impulso (indirecto) o grabación STIPA (directo) | 112 + | `fs` | int | Hz | > 0 | | 113 + | `snr` | float o vector de 7, opcional | dB | por defecto `None` | Añade la degradación por ruido estacionario | 114 + | `level` | vector de 7, opcional | dB SPL | por defecto `None` | Activa el enmascaramiento auditivo + umbral de recepción (Tablas A.2/A.3) | 115 + | `ambient` | vector de 7, opcional | dB SPL | requiere `level` | Niveles de banda del ruido ambiente | 116 + | `reference` | array 1D, opcional (`stipa`) | — | por defecto `None` | Señal de la fuente medida en lugar del m = 0,55 nominal | 117 + 118 + Ambas devuelven `STIResult`: `sti`, `mti` (7 bandas), `mtf` (7×14 o 7×2), 119 + `band_levels`, `rating` (letra del Anexo F, `A+`…`U`). 120 + 121 + ## Véase también 122 + 123 + - [Acústica de salas](/phonometry/es/guides/room-acoustics/) — la respuesta al 124 + impulso medida que consume el método indirecto, y las métricas de oficinas 125 + diáfanas (ISO 3382-3) construidas sobre el STI por posición. 126 + - [Índice de inteligibilidad del habla](/phonometry/es/guides/speech-intelligibility/) — 127 + el índice basado en audibilidad de ANSI S3.5 que complementa al STI. 128 + - [Psicoacústica](/phonometry/es/guides/psychoacoustics/) — sonoridad, sharpness, 129 + tonalidad y aspereza del sonido recibido. 130 + - [Teoría](/phonometry/es/reference/theory/) — la derivación de la transferencia 131 + de modulación y la correspondencia m ↔ STI. 132 + 133 + --- 134 + 135 + **Normas.** IEC 60268-16:2020 (Edición 5), *Sound system equipment — Part 16: 136 + Objective rating of speech intelligibility by speech transmission index* — la 137 + función de transferencia de modulación y la correspondencia m ↔ STI, la señal 138 + de ensayo STIPA y el método directo, el método indirecto desde la respuesta al 139 + impulso, el enmascaramiento auditivo y el umbral de recepción (Tablas A.2/A.3), 140 + el espectro de habla masculina revisado (apartado A.6.1) y las letras de 141 + valoración del Anexo F.
+85
site/src/content/docs/es/guides/tone-prominence.md
··· 1 + --- 2 + title: "Tonos discretos prominentes (ECMA-418-1)" 3 + description: "La relación tono-ruido y la relación de prominencia de ECMA-418-1: veredictos FFT de prominencia para tonos discretos frente a criterios dependientes de la frecuencia, con las posiciones de medida de ECMA-74." 4 + --- 5 + 6 + Los componentes tonales del ruido de maquinaria molestan mucho más de lo que 7 + sugiere su nivel. ECMA-418-1:2024 (referenciada por el Anexo D de ECMA-74) 8 + define dos métodos FFT para decidir si un tono discreto es *prominente*: 9 + `tone_to_noise_ratio()` compara el nivel del tono con el ruido enmascarante de 10 + su banda crítica (apartado 11) y `prominence_ratio()` compara la banda crítica 11 + centrada en el tono con las dos bandas contiguas (apartado 12). Ambos devuelven 12 + un veredicto estructurado frente a los criterios de prominencia dependientes de 13 + la frecuencia. 14 + 15 + ## 1. Relación tono-ruido y relación de prominencia 16 + 17 + ```python 18 + import numpy as np 19 + from phonometry import tone_to_noise_ratio, prominence_ratio 20 + 21 + fs = 48000 22 + rng = np.random.default_rng(0) 23 + t = np.arange(fs) / fs 24 + x = np.sin(2 * np.pi * 1000 * t) + 0.05 * rng.standard_normal(fs) # tono de 1 kHz en ruido 25 + tnr = tone_to_noise_ratio(x, fs) # pico más alto, o tone_freq=... 26 + pr = prominence_ratio(x, fs, tone_freq=1000.0) 27 + print(tnr.ratio_db, tnr.criterion_db, tnr.prominent) 28 + ``` 29 + 30 + Los métodos se apoyan en la **banda crítica** — el ancho de banda de análisis 31 + del oído, $\Delta f_c = 25 + 75\ [1 + 1.4(f/1000)^2]^{0.69}$ Hz (162 Hz a 32 + 1 kHz): a un tono solo lo enmascara el ruido que hay *dentro* de su banda 33 + crítica, así que ambos ratios comparan el tono exactamente con ese ruido, no 34 + con todo el espectro. 35 + 36 + <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tonality_spectrum_es.png" alt="Espectro promediado de un tono en ruido con la banda crítica sombreada y la relación tono-ruido anotada frente a su criterio de prominencia" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tonality_spectrum_es_dark.png" alt="Espectro promediado de un tono en ruido con la banda crítica sombreada y la relación tono-ruido anotada frente a su criterio de prominencia" style="width:80%"> 37 + 38 + Un TNR por encima de $8 + 8.33\log_{10}(1000/f_t)$ dB (8 dB de 1 kHz hacia 39 + arriba) clasifica el tono como *prominente*; el criterio del PR es 40 + $9 + 10\log_{10}(1000/f_t)$ dB. Las frecuencias bajas reciben umbrales más 41 + altos porque unas bandas relativamente más anchas enmascaran más. 42 + 43 + ## 2. Dónde medir (ECMA-74) y práctica 44 + 45 + ECMA-74 (que delega la evaluación tonal en ECMA-418-1) también fija dónde medir alrededor de un equipo: 46 + 47 + <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions_es.svg" alt="Posiciones de medida de emisión ECMA-74: micrófono del operador sentado a 0,25 m y 1,20 m, y las cuatro posiciones de observador a 1 m" style="width:92%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions_es_dark.svg" alt="Posiciones de medida de emisión ECMA-74: micrófono del operador sentado a 0,25 m y 1,20 m, y las cuatro posiciones de observador a 1 m" style="width:92%"> 48 + 49 + Los tonos secundarios próximos en la misma banda crítica se combinan según el 50 + apartado 11.6; para complejos armónicos evalúa cada componente (`tone_freq=`). 51 + Ambos métodos trabajan sobre espectros promediados RMS con ventana Hann y no 52 + necesitan calibración absoluta (los ratios son diferencias de nivel). 53 + 54 + ### Parámetros de `tone_to_noise_ratio()` / `prominence_ratio()` 55 + 56 + | Parámetro | Tipo | Unidades | Rango / valor por defecto | Notas | 57 + | :--- | :--- | :--- | :--- | :--- | 58 + | `x` | array 1D | cualquiera (vale sin calibrar) | ≥ `fs/resolution_hz` muestras | Los ratios son diferencias de nivel: la calibración se cancela | 59 + | `fs` | int | Hz | > 0 | | 60 + | `tone_freq` | float, opcional | Hz | 89,1–11 200; por defecto `None` | `None` evalúa el pico más alto del rango de interés | 61 + | `resolution_hz` | float | Hz | > 0; por defecto `1.0` | La banda del tono debe quedar dentro del 15 % de la banda crítica (apartado 11.2) | 62 + 63 + Ambos devuelven un `ToneAssessment(frequency, ratio_db, criterion_db, prominent)`. 64 + 65 + ## Véase también 66 + 67 + - [Niveles](/phonometry/es/guides/levels/) — los niveles de evaluación de 68 + ISO 1996-1 cuyos ajustes tonales (Tabla A.1) estos veredictos de prominencia 69 + justifican objetivamente. 70 + - [Psicoacústica](/phonometry/es/guides/psychoacoustics/) — la tonalidad 71 + psicoacústica T en tu_HMS de ECMA-418-2, la contraparte de modelo auditivo de 72 + estos ratios FFT. 73 + - [Prominencia de sonidos impulsivos](/phonometry/es/guides/impulse-prominence/) — 74 + la contraparte NT ACOU 112 para el carácter impulsivo (en lugar de tonal). 75 + - [Teoría](/phonometry/es/reference/theory/) — el modelo de banda crítica y la 76 + derivación de los criterios. 77 + 78 + --- 79 + 80 + **Normas.** ECMA-418-1:2024 (3.ª edición), *Psychoacoustic metrics for ITT 81 + equipment — Part 1: Prominent discrete tones* — la relación tono-ruido 82 + (apartado 11), la relación de prominencia (apartado 12), el modelo de banda 83 + crítica y los criterios de prominencia dependientes de la frecuencia; el 84 + Anexo D de ECMA-74 — las posiciones de medida de emisión, que delegan la 85 + evaluación tonal en ECMA-418-1.
+7 -6
site/src/content/docs/es/reference/theory.md
··· 269 269 270 270 La norma **no especifica interpolación** entre las frecuencias tabuladas. La Fórmula (1) está especificada para **20 fonios a 90 fonios** entre 20 Hz y 4 kHz, y solo hasta **80 fonios entre 5 kHz y 12,5 kHz** — por encima de 80 fonios la línea isofónica se detiene, por tanto, en 4 kHz. Los valores fuera de estos límites obtenidos con la Fórmula (2) son extrapolaciones que la norma califica de meramente informativas. 271 271 272 - Consulta la [guía de niveles](/phonometry/es/guides/levels/) para su uso. 272 + Consulta la [guía de psicoacústica](/phonometry/es/guides/psychoacoustics/) para su uso. 273 273 274 274 ## Prominencia tonal: TNR y PR (ECMA-418-1) 275 275 ··· 289 289 290 290 **PR** (apartado 12) compara el nivel de la banda crítica centrada en el tono, $L_M$, con la potencia media de las dos bandas críticas **contiguas** $L_L$, $L_U$ (bordes según las Fórmulas ajustadas 21–22 con las Tablas 2–3): $\mathrm{PR} = 10\log_{10} P_M - 10\log_{10}\left[(P_L + P_U)/2\right]$ (Fórmula 23). Para $f_t \le 171.4$ Hz la banda inferior se trunca en 20 Hz y su potencia se reescala a un **ancho de banda de 100 Hz** (Fórmula 24). El criterio (Fórmulas 25–26) es 9,0 dB para $f_t \ge 1$ kHz, y crece como $9.0 + 10.0\log_{10}(1000/f_t)$ por debajo. Los tonos se evalúan dentro del rango de interés de 89,1 Hz – 11,2 kHz (apartados 11.5 / 12.6). 291 291 292 - Consulta la [guía de niveles](/phonometry/es/guides/levels/) para su uso. 292 + Consulta la [guía de tonos discretos prominentes](/phonometry/es/guides/tone-prominence/) para su uso. 293 293 294 294 ## Métricas de evento y de dosis 295 295 ··· 507 507 m_{dr} = \frac{2 \sqrt{\left( \sum_t I_k(t) \sin 2 \pi f_m t \right)^2 + \left( \sum_t I_k(t) \cos 2 \pi f_m t \right)^2}}{\sum_t I_k(t)}, \qquad m = \frac{m_{dr}}{0.55} 508 508 $$ 509 509 510 - Consulta la [guía de psicoacústica](/phonometry/es/guides/psychoacoustics/) para su uso. 510 + Consulta la [guía del índice de transmisión del habla](/phonometry/es/guides/speech-transmission/) para su uso. 511 511 512 512 ## Índice de inteligibilidad del habla (ANSI S3.5) 513 513 ··· 794 794 correlacionada es la suma en cuadratura ponderada en energía de las 795 795 incertidumbres por banda (Fórmula B.2). 796 796 797 - Consulta la [guía de acústica de salas y edificación](/phonometry/es/guides/room-acoustics/) para su uso. 797 + Consulta las guías de [acústica de salas](/phonometry/es/guides/room-acoustics/) y de 798 + [acústica de la edificación](/phonometry/es/guides/building-acoustics/) para su uso. 798 799 799 800 ## Propagación en exteriores y exposición al ruido en el trabajo (ISO 9613-1/2, ISO 9612) 800 801 ··· 910 911 (jornada completa, $90.1$ dB, $3.4$ dB) se reproducen con la precisión impresa 911 912 de la norma: todos los intermedios del anexo E son exactos dígito a dígito, y su 912 913 nivel final difiere solo por el redondeo previo que la propia norma aplica al 913 - nivel efectivo de la jornada (véase la [guía de Niveles](/phonometry/es/guides/levels/)). 914 + nivel efectivo de la jornada (véase la [guía de exposición al ruido en el trabajo](/phonometry/es/guides/occupational-exposure/)). 914 915 915 916 Consulta la [guía de Propagación en exteriores](/phonometry/es/guides/outdoor-propagation/) 916 - y la [guía de Niveles](/phonometry/es/guides/levels/) para su uso. 917 + y la [guía de exposición al ruido en el trabajo](/phonometry/es/guides/occupational-exposure/) para su uso. 917 918 918 919 ## Determinación de la potencia sonora (ISO 3744/3745/3746, ISO 3741, ISO 9614-2/3) 919 920
+47 -31
site/src/content/docs/guides/building-acoustics.md
··· 463 463 print(res.dominant.label, round(res.dominant.fraction, 2)) # Dd 0.33 (direct dominates) 464 464 ``` 465 465 466 + <details> 467 + <summary>Show the code for this figure</summary> 468 + 469 + ```python 470 + import matplotlib.pyplot as plt 471 + 472 + # Per-path sound reduction index and each path's share of the transmitted 473 + # energy for the Annex H.3 result computed above. 474 + labels = [p.label for p in res.paths] 475 + r_w = [p.r_w for p in res.paths] 476 + frac = [100.0 * p.fraction for p in res.paths] 477 + 478 + fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6), sharex=True) 479 + ax1.bar(labels, r_w, color="tab:blue") 480 + ax1.axhline(res.r_prime_w, ls="--", color="k", label=f"R'w = {res.r_prime_w:.1f} dB") 481 + ax1.set_ylabel("Path Rij,w [dB]"); ax1.legend() 482 + ax2.bar(labels, frac, color="tab:orange") 483 + ax2.set_ylabel("Energy share [%]"); ax2.set_xlabel("Transmission path") 484 + for ax in (ax1, ax2): 485 + ax.tick_params(axis="x", rotation=45) 486 + fig.suptitle("EN 12354-1 Annex H.3 — flanking transmission") 487 + fig.tight_layout() 488 + plt.show() 489 + ``` 490 + 491 + </details> 492 + 466 493 Every added flanking path strictly lowers $R'_w$ below the direct $R_{Dd,w} = 57$; 467 494 `res.paths` exposes each path's share of the transmitted energy so the dominant 468 - path is visible. Clause 4.4.2 also enforces a floor $K_{ij} \ge K_{ij,\min}$ from 469 - the junction geometry — compute it with `junction_min_vibration_reduction` and 470 - pass it to `flanking_path(..., kij_min=...)`, which raises a below-floor $K_{ij}$ 471 - to the minimum: 495 + path is visible. `flanking_element` is a convenience that builds one junction's 496 + three paths at once; the single-path constructor behind it, `flanking_path`, 497 + builds one `Ff`, `Df` or `Fd` path at a time (Formula 28a). Clause 4.4.2 also 498 + enforces a floor $K_{ij} \ge K_{ij,\min}$ from the junction geometry — compute 499 + it with `junction_min_vibration_reduction` and pass it to 500 + `flanking_path(..., kij_min=...)`, which raises a below-floor $K_{ij}$ to the 501 + minimum: 472 502 473 503 ```python 474 504 from phonometry import junction_min_vibration_reduction ··· 495 525 print(round(ln_eq, 1), k, round(imp.l_prime_n_w, 1)) # 76.2 2 45.2 -> L'n,w = 45 dB 496 526 print(round(standardized_impact_level(imp.l_prime_n_w, 50.0), 1)) # 43.0 L'nT,w 497 527 ``` 498 - 499 - <details> 500 - <summary>Show the code for this figure</summary> 501 - 502 - ```python 503 - import matplotlib.pyplot as plt 504 - 505 - # Per-path sound reduction index and each path's share of the transmitted 506 - # energy for the Annex H.3 result computed above. 507 - labels = [p.label for p in res.paths] 508 - r_w = [p.r_w for p in res.paths] 509 - frac = [100.0 * p.fraction for p in res.paths] 510 - 511 - fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6), sharex=True) 512 - ax1.bar(labels, r_w, color="tab:blue") 513 - ax1.axhline(res.r_prime_w, ls="--", color="k", label=f"R'w = {res.r_prime_w:.1f} dB") 514 - ax1.set_ylabel("Path Rij,w [dB]"); ax1.legend() 515 - ax2.bar(labels, frac, color="tab:orange") 516 - ax2.set_ylabel("Energy share [%]"); ax2.set_xlabel("Transmission path") 517 - for ax in (ax1, ax2): 518 - ax.tick_params(axis="x", rotation=45) 519 - fig.suptitle("EN 12354-1 Annex H.3 — flanking transmission") 520 - fig.tight_layout() 521 - plt.show() 522 - ``` 523 - 524 - </details> 525 528 526 529 ### `junction_vibration_reduction()` / `flanking_element()` parameters 527 530 ··· 633 636 `uncertain_value()` an `UncertainValue` (`value`, `standard_uncertainty`, 634 637 `coverage_factor`, `expanded_uncertainty`, `.lower`, `.upper`). The read-only 635 638 `COVERAGE_FACTORS` mapping exposes Table 8 keyed by `(confidence, one_sided)`. 639 + 640 + --- 641 + 642 + **Standards.** ISO 16283-1:2014, ISO 16283-2 and ISO 16283-3:2016, *Acoustics — 643 + Field measurement of sound insulation in buildings and of building elements* — 644 + the level differences, normalisations and element methods of §1; ISO 717-1 and 645 + ISO 717-2 — the reference-curve single-number ratings and the spectrum 646 + adaptation terms C, Ctr and CI; ISO 10140-2:2010 and ISO 10140-4:2010 — the 647 + laboratory R and Ln with the background-noise correction of §2; EN 12354-1:2000 648 + and EN 12354-2:2000 — the simplified flanking-transmission predictions of §3 649 + (Annex E junctions, worked examples H.3 and E.3); ISO 12999-1:2020 — the 650 + standard uncertainties per measurement situation and the coverage factors 651 + of §4. 636 652 637 653 ## See also 638 654
+1 -1
site/src/content/docs/guides/enclosed-space-absorption.md
··· 114 114 time, the volume and the object fraction, and its `.plot()` draws the 115 115 reverberation-time spectrum. This is the prediction counterpart of the measured 116 116 reverberation time in 117 - [Room and Building Acoustics](/phonometry/guides/room-acoustics/) (ISO 3382) and 117 + [Room Acoustics](/phonometry/guides/room-acoustics/) (ISO 3382) and 118 118 of the reverberation-room absorption of 119 119 [Acoustic Materials](/phonometry/guides/materials/) (ISO 354). 120 120
+12 -182
site/src/content/docs/guides/levels.md
··· 1 1 --- 2 2 title: "Integrated and Statistical Levels" 3 - description: "Leq, LAeq, L10/L50/L90 percentile levels and octave spectrograms." 3 + description: "Leq, LAeq, L10/L50/L90 percentile levels, LCpeak/SEL and noise dose (IEC 61252), Lden and rating levels (ISO 1996-1), and octave spectrograms." 4 4 --- 5 5 6 6 Environmental noise metrics computed directly from the raw (calibrated) signal. ··· 177 177 | `sound_exposure(x, fs, duration_hours=None, ...)` | `duration_hours` treats `x` as a sample of that period | E [Pa²h] | IEC 61252 | 178 178 | `lex_8h(x, fs, duration_hours=None, ...)` | same sampling semantics | LEX,8h [dB] | IEC 61252 (≡ LEP,d) | 179 179 180 - ## Occupational noise exposure strategies and uncertainty (ISO 9612) 181 - 182 - `lex_8h` above turns *one* recording into a daily level. ISO 9612:2009 — the 183 - engineering method (accuracy grade 2) — is the survey design *around* that 184 - primitive: how to sample a real working day, how to combine the pieces, and how 185 - to attach the normative uncertainty every occupational-hygiene report needs. The 186 - `occupational_exposure` module adds the three **measurement strategies** and the 187 - **Annex C** uncertainty budget on top of the energy-average machinery. 188 - 189 - The *task-based* strategy (Clause 9) splits the nominal day into tasks, takes 190 - $I \ge 3$ samples per task, and energy-sums the task contributions 191 - 192 - $$ 193 - L_{EX,8h,m} = L_{p,A,eqT,m} + 10 \log_{10}(T_m/T_0), \qquad T_0 = 8\ \text{h}, 194 - $$ 195 - 196 - so a loud but short task contributes little. The *job-based* (Clause 10) and 197 - *full-day* (Clause 11) strategies instead take $N \ge 5$ (or three whole-day) 198 - random samples over a homogeneous exposure group and normalise the effective-day 199 - duration. The daily level is the same either way; the strategies differ in how 200 - the **uncertainty** is built. 201 - 202 - <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/exposure_uncertainty.png" alt="ISO 9612 Annex D task-based exposure: the three task LEX,8h contributions as bars, the energy-summed daily LEX,8h line and the one-sided 95 % upper limit LEX,8h + U band above it" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/exposure_uncertainty_dark.png" alt="ISO 9612 Annex D task-based exposure: the three task LEX,8h contributions as bars, the energy-summed daily LEX,8h line and the one-sided 95 % upper limit LEX,8h + U band above it" style="width:80%"> 203 - 204 - ```python 205 - from phonometry.occupational_exposure import ( 206 - Task, task_based_exposure, job_based_exposure, full_day_exposure, 207 - ) 208 - 209 - # ISO 9612 Annex D — a welder's day split into three tasks. Each task level is 210 - # the energy average of its Lp,A,eqT samples; durations carry a measured range. 211 - tasks = [ 212 - Task(samples=(70.0,), duration_hours=1.5, label="planning/breaks"), 213 - Task(samples=(80.1, 82.2, 79.6), duration_hours=5.0, 214 - duration_range=(4.0, 6.0), label="welding"), 215 - Task(samples=(86.5, 92.4, 89.3, 93.2, 87.8, 86.2), duration_hours=1.5, 216 - duration_range=(1.0, 2.0), label="cutting/grinding"), 217 - ] 218 - res = task_based_exposure(tasks, include_duration_uncertainty=False, warn=False) 219 - print(f"LEX,8h = {res.lex_8h:.1f} dB U = {res.expanded_uncertainty:.1f} dB") 220 - # LEX,8h = 84.3 dB U = 2.7 dB 221 - print(f"one-sided 95 % upper limit LEX,8h + U = {res.upper_limit:.1f} dB") # 87.0 dB 222 - for t in res.tasks: 223 - print(f" {t.label:<16} Lp,A,eqT = {t.lp_aeqt:5.1f} contributes {t.lex_8h_contribution:5.1f} dB") 224 - # planning/breaks Lp,A,eqT = 70.0 contributes 62.7 dB 225 - # welding Lp,A,eqT = 80.8 contributes 78.7 dB 226 - # cutting/grinding Lp,A,eqT = 90.1 contributes 82.8 dB 227 - 228 - # The same shift measured job-based (Annex E) and full-day (Annex F): both use 229 - # the Eq C.9 / Table C.4 sampling budget with k = 1.65 (one-sided 95 %). 230 - job = job_based_exposure([88.1, 86.1, 89.7, 86.5, 91.1, 86.7], effective_duration_hours=7.5) 231 - full = full_day_exposure([88.0, 91.9, 87.6, 90.4, 89.0, 88.4], effective_duration_hours=9.25) 232 - print(f"job LEX,8h = {job.lex_8h:.1f} dB U = {job.expanded_uncertainty:.1f} dB") 233 - # job LEX,8h = 88.2 dB U = 3.8 dB 234 - print(f"full-day LEX,8h = {full.lex_8h:.1f} dB U = {full.expanded_uncertainty:.1f} dB") 235 - # full-day LEX,8h = 90.1 dB U = 3.4 dB 236 - ``` 237 - 238 - Two subtleties are worth spelling out. First, the coverage factor is 239 - $k = 1.65$ for a **one-sided** 95 % interval (Clause 14), because a hygienist 240 - cares only about the *upper* bound: `res.upper_limit` = $L_{EX,8h} + U$ is the 241 - value 95 % of measurements fall below, the number compared against an action 242 - limit. Second, the task and job methods weight the *same* spread of samples 243 - differently. The task sampling uncertainty $u_{1a}$ (Eq. C.6) divides the summed 244 - squared deviations by $I(I-1)$ — the standard error of the mean, smaller by a 245 - factor $\sqrt{I}$ — whereas the job/full-day sampling uncertainty $u_1$ (Eq. C.12) 246 - is the plain sample standard deviation with denominator $N-1$, whose contribution 247 - $c_1 u_1$ is then read from **Table C.4** as a function of $(N, u_1)$. The same 248 - raw scatter therefore inflates the job estimate more, which is the standard's 249 - built-in penalty for coarser, fewer samples. (The printed job $L_{EX,8h}$ is 250 - $88.2$ dB where Annex E reports $88.1$: the standard rounds the effective-day 251 - level to $88.4$ before the duration normalisation; the library keeps it 252 - unrounded.) 253 - 254 - When a task's samples span **3 dB or more** (Clause 9.3), or the job contribution 255 - $c_1 u_1$ exceeds 3.5 dB (Clause 10.4), or too few workers are covered 256 - (Table 1 cumulative-duration), the result sets `sampling_advisory=True` and, with 257 - `warn=True`, emits an `OccupationalExposureWarning` recommending more measurements. Peak 258 - levels $L_{p,Cpeak}$ are reported **without** an uncertainty — Annex C gives no 259 - method for them (Table C.5, Note 1), so peak-uncertainty is out of scope. The 260 - three Annex D/E/F worked examples above are reproduced to the standard's printed 261 - precision (Annex E's final rounding is disclosed above), and the theory is 262 - derived on the [Theory](/phonometry/reference/theory/) page. 263 - 264 - ### `task_based_exposure()` / `job_based_exposure()` / `full_day_exposure()` parameters 265 - 266 - | Parameter | Applies to | Type | Units | Range / default | Notes | 267 - | :--- | :--- | :--- | :--- | :--- | :--- | 268 - | `tasks` | task | list of `Task` | — | ≥ 1 | Each `Task` has `samples`, `duration_hours`, optional `duration_range`/`duration_samples`, `label`, `instrument` | 269 - | `samples` | job / full-day | sequence | dB | ≥ 2 (≥ 5 / ≥ 3 advised) | Random `Lp,A,eqT` samples | 270 - | `effective_duration_hours` | job / full-day | float | h | > 0 | Effective working-day duration $T_e$ | 271 - | `instrument` | all | str | — | `'class1'`, `'class2'`, `'personal_exposimeter'` (default) | Selects $u_2$ (Table C.5) | 272 - | `u3` | all | float | dB | default `1.0` | Microphone-position uncertainty (Clause C.6) | 273 - | `include_duration_uncertainty` | task | bool | — | default `True` | `False` omits the $(c_{1b}u_{1b})^2$ term (Annex D case a) | 274 - | `n_workers` / `sample_duration_hours` | job | int / float | — / h | default `None` | Table 1 cumulative-duration check | 275 - | `warn` | all | bool | — | default `True` | Emit `OccupationalExposureWarning` for the sampling advisories | 276 - 277 - All three return an `ExposureResult` with `lex_8h`, `combined_standard_uncertainty` 278 - $u$, `expanded_uncertainty` $U = 1.65\ u$, `upper_limit` = $L_{EX,8h} + U$, 279 - `sampling_advisory`, and (task-based) the per-task `tasks` breakdown. 280 - 281 - ## Loudness level of pure tones (ISO 226:2023) 282 - 283 - The normal equal-loudness-level contours relate the SPL of a pure tone to its 284 - perceived *loudness level* in phons (the SPL of an equally loud 1 kHz tone). 285 - `equal_loudness_contour(phon)` evaluates ISO 226:2023 Formula (1) at the 29 286 - preferred third-octave frequencies of Table 1, `loudness_level(spl, frequency)` 287 - is the exact inverse (Formula 2), and `hearing_threshold()` returns the 288 - threshold-of-hearing column: 289 - 290 - ```python 291 - from phonometry import equal_loudness_contour, loudness_level 292 - 293 - freqs, spl = equal_loudness_contour(40.0) # the classic 40-phon contour 294 - phon = loudness_level(73.0, 63.0) # 73 dB @ 63 Hz -> 40 phon 295 - ``` 296 - 297 - <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours.png" alt="ISO 226:2023 normal equal-loudness-level contours from 20 to 90 phon with the hearing threshold curve" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours_dark.png" alt="ISO 226:2023 normal equal-loudness-level contours from 20 to 90 phon with the hearing threshold curve" style="width:80%"> 298 - 299 - Validity per clause 4.1: 20-90 phon (80 phon above 4 kHz); the implementation 300 - is verified against the Annex B tables in CI. Note this is the loudness of 301 - *pure tones* - loudness of arbitrary signals (ISO 532 sones) is a different, 302 - upcoming feature. 303 - 304 - ## Prominent discrete tones (ECMA-418-1) 305 - 306 - Tonal components in machinery noise are far more annoying than their level 307 - suggests. ECMA-418-1:2024 (referenced by ECMA-74 Annex D) gives two FFT-based 308 - methods to decide whether a discrete tone is *prominent*: 309 - `tone_to_noise_ratio()` compares the tone level with the masking noise in its 310 - critical band (clause 11), and `prominence_ratio()` compares the critical band 311 - centred on the tone with the two contiguous bands (clause 12). Both return a 312 - structured verdict against the frequency-dependent prominence criteria: 313 - 314 - ```python 315 - import numpy as np 316 - from phonometry import tone_to_noise_ratio, prominence_ratio 317 - 318 - fs = 48000 319 - rng = np.random.default_rng(0) 320 - t = np.arange(fs) / fs 321 - x = np.sin(2 * np.pi * 1000 * t) + 0.05 * rng.standard_normal(fs) # 1 kHz tone in noise 322 - tnr = tone_to_noise_ratio(x, fs) # highest peak, or tone_freq=... 323 - pr = prominence_ratio(x, fs, tone_freq=1000.0) 324 - print(tnr.ratio_db, tnr.criterion_db, tnr.prominent) 325 - ``` 326 - 327 - 328 - The methods hinge on the **critical band** — the ear's analysis bandwidth, 329 - $\Delta f_c = 25 + 75\ [1 + 1.4(f/1000)^2]^{0.69}$ Hz (162 Hz at 1 kHz): a 330 - tone is masked only by the noise *inside* its critical band, so both ratios 331 - compare the tone against exactly that noise, not the whole spectrum. 332 - 333 - <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tonality_spectrum.png" alt="Averaged spectrum of a tone in noise with the critical band shaded and the tone-to-noise ratio annotated against its prominence criterion" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tonality_spectrum_dark.png" alt="Averaged spectrum of a tone in noise with the critical band shaded and the tone-to-noise ratio annotated against its prominence criterion" style="width:80%"> 334 - 335 - A TNR above $8 + 8.33\log_{10}(1000/f_t)$ dB (8 dB from 1 kHz up) classifies 336 - the tone as *prominent*; the PR criterion is $9 + 10\log_{10}(1000/f_t)$ dB. 337 - Low frequencies get higher thresholds because wider relative bands mask more. 338 - 339 - ECMA-74 (which delegates its tone assessments to ECMA-418-1) also fixes where to measure around a device: 340 - 341 - <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions.svg" alt="ECMA-74 emission measurement positions: seated operator microphone at 0.25 m and 1.20 m, and the four bystander positions at 1 m" style="width:92%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions_dark.svg" alt="ECMA-74 emission measurement positions: seated operator microphone at 0.25 m and 1.20 m, and the four bystander positions at 1 m" style="width:92%"> 342 - 343 - Proximate secondary tones in the same critical band are combined per 344 - clause 11.6; for harmonic complexes assess each component (`tone_freq=`). 345 - Both methods work on Hann-windowed, RMS-averaged spectra and need no absolute 346 - calibration (the ratios are level differences). 347 - 348 - ### `tone_to_noise_ratio()` / `prominence_ratio()` parameters 349 - 350 - | Parameter | Type | Units | Range / default | Notes | 351 - | :--- | :--- | :--- | :--- | :--- | 352 - | `x` | 1D array | any (uncalibrated OK) | ≥ `fs/resolution_hz` samples | Ratios are level differences: calibration cancels out | 353 - | `fs` | int | Hz | > 0 | | 354 - | `tone_freq` | float, optional | Hz | 89.1–11 200; default `None` | `None` assesses the highest peak in the range of interest | 355 - | `resolution_hz` | float | Hz | > 0; default `1.0` | Tone band must stay within 15 % of the critical band (clause 11.2) | 356 - 357 - Both return a `ToneAssessment(frequency, ratio_db, criterion_db, prominent)`. 180 + `lex_8h` rates *one* recording; assembling a full working day from task or 181 + job samples — with the normative ISO 9612 uncertainty budget — continues in 182 + [Occupational Noise Exposure](/phonometry/guides/occupational-exposure/). 358 183 359 184 ## Environmental noise: Lden, Ldn and rating levels (ISO 1996-1) 360 185 ··· 376 201 (51.4, 8, 10.0)]) # night (+10) == lden 377 202 ``` 378 203 379 - 380 204 <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/lden_profile.png" alt="Synthetic 24-hour urban LAeq profile with day, evening and night bands, the +5 and +10 dB weighted period levels and the resulting Lden" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/lden_profile_dark.png" alt="Synthetic 24-hour urban LAeq profile with day, evening and night bands, the +5 and +10 dB weighted period levels and the resulting Lden" style="width:80%"> 381 205 382 206 ### `lden()` / `ldn()` / `composite_rating_level()` parameters ··· 392 216 <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_env_measurement.svg" alt="Environmental noise measurement positions per ISO 1996-2: free field, 2 m from the facade and flush-mounted, with their corrections" style="width:92%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_env_measurement_dark.svg" alt="Environmental noise measurement positions per ISO 1996-2: free field, 2 m from the facade and flush-mounted, with their corrections" style="width:92%"> 393 217 394 218 Combine with `laeq()` per time period to go from recordings to Lden, and with 395 - `tone_to_noise_ratio()` / `prominence_ratio()` to justify tonal adjustments. 219 + the `tone_to_noise_ratio()` / `prominence_ratio()` verdicts of 220 + [Prominent Discrete Tones](/phonometry/guides/tone-prominence/) to justify tonal adjustments. 396 221 397 222 ## Octave Spectrogram (levels over time) 398 223 ··· 442 267 ``` 443 268 444 269 See [Calibration and dBFS](/phonometry/guides/calibration/) to convert digital units to physical 445 - SPL, and [Time Weighting](/phonometry/guides/time-weighting/) for the envelope details. 270 + SPL, and [Time Weighting](/phonometry/guides/time-weighting/) for the envelope details. The 271 + ISO 9612 occupational strategies continue in 272 + [Occupational Noise Exposure](/phonometry/guides/occupational-exposure/), the ECMA-418-1 273 + tonal-prominence verdicts in [Prominent Discrete Tones](/phonometry/guides/tone-prominence/), 274 + and the ISO 226 equal-loudness contours live with the perception metrics in 275 + [Psychoacoustics](/phonometry/guides/psychoacoustics/).
+128
site/src/content/docs/guides/occupational-exposure.md
··· 1 + --- 2 + title: "Occupational Noise Exposure (ISO 9612)" 3 + description: "The ISO 9612 task-based, job-based and full-day measurement strategies for the daily noise exposure level LEX,8h, with the Annex C uncertainty budget and the one-sided 95 % upper limit." 4 + --- 5 + 6 + A working day is rarely measured in one take: the daily exposure level a 7 + regulation acts on has to be assembled from *samples* of a real shift, and 8 + reported with an uncertainty a hygienist can defend. `lex_8h` (in 9 + [Levels](/phonometry/guides/levels/)) turns *one* recording into a daily level. ISO 9612:2009 — 10 + the engineering method (accuracy grade 2) — is the survey design *around* that 11 + primitive: how to sample a real working day, how to combine the pieces, and how 12 + to attach the normative uncertainty every occupational-hygiene report needs. The 13 + `occupational_exposure` module adds the three **measurement strategies** and the 14 + **Annex C** uncertainty budget on top of the energy-average machinery. 15 + 16 + ## 1. The three measurement strategies (Clauses 9-11) 17 + 18 + The *task-based* strategy (Clause 9) splits the nominal day into tasks, takes 19 + $I \ge 3$ samples per task, and energy-sums the task contributions 20 + 21 + $$ 22 + L_{EX,8h,m} = L_{p,A,eqT,m} + 10 \log_{10}(T_m/T_0), \qquad T_0 = 8\ \text{h}, 23 + $$ 24 + 25 + so a loud but short task contributes little. The *job-based* (Clause 10) and 26 + *full-day* (Clause 11) strategies instead take $N \ge 5$ (or three whole-day) 27 + random samples over a homogeneous exposure group and normalise the effective-day 28 + duration. The daily level is the same either way; the strategies differ in how 29 + the **uncertainty** is built. 30 + 31 + <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/exposure_uncertainty.png" alt="ISO 9612 Annex D task-based exposure: the three task LEX,8h contributions as bars, the energy-summed daily LEX,8h line and the one-sided 95 % upper limit LEX,8h + U band above it" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/exposure_uncertainty_dark.png" alt="ISO 9612 Annex D task-based exposure: the three task LEX,8h contributions as bars, the energy-summed daily LEX,8h line and the one-sided 95 % upper limit LEX,8h + U band above it" style="width:80%"> 32 + 33 + ```python 34 + from phonometry.occupational_exposure import ( 35 + Task, task_based_exposure, job_based_exposure, full_day_exposure, 36 + ) 37 + 38 + # ISO 9612 Annex D — a welder's day split into three tasks. Each task level is 39 + # the energy average of its Lp,A,eqT samples; durations carry a measured range. 40 + tasks = [ 41 + Task(samples=(70.0,), duration_hours=1.5, label="planning/breaks"), 42 + Task(samples=(80.1, 82.2, 79.6), duration_hours=5.0, 43 + duration_range=(4.0, 6.0), label="welding"), 44 + Task(samples=(86.5, 92.4, 89.3, 93.2, 87.8, 86.2), duration_hours=1.5, 45 + duration_range=(1.0, 2.0), label="cutting/grinding"), 46 + ] 47 + res = task_based_exposure(tasks, include_duration_uncertainty=False, warn=False) 48 + print(f"LEX,8h = {res.lex_8h:.1f} dB U = {res.expanded_uncertainty:.1f} dB") 49 + # LEX,8h = 84.3 dB U = 2.7 dB 50 + print(f"one-sided 95 % upper limit LEX,8h + U = {res.upper_limit:.1f} dB") # 87.0 dB 51 + for t in res.tasks: 52 + print(f" {t.label:<16} Lp,A,eqT = {t.lp_aeqt:5.1f} contributes {t.lex_8h_contribution:5.1f} dB") 53 + # planning/breaks Lp,A,eqT = 70.0 contributes 62.7 dB 54 + # welding Lp,A,eqT = 80.8 contributes 78.7 dB 55 + # cutting/grinding Lp,A,eqT = 90.1 contributes 82.8 dB 56 + 57 + # The same shift measured job-based (Annex E) and full-day (Annex F): both use 58 + # the Eq C.9 / Table C.4 sampling budget with k = 1.65 (one-sided 95 %). 59 + job = job_based_exposure([88.1, 86.1, 89.7, 86.5, 91.1, 86.7], effective_duration_hours=7.5) 60 + full = full_day_exposure([88.0, 91.9, 87.6, 90.4, 89.0, 88.4], effective_duration_hours=9.25) 61 + print(f"job LEX,8h = {job.lex_8h:.1f} dB U = {job.expanded_uncertainty:.1f} dB") 62 + # job LEX,8h = 88.2 dB U = 3.8 dB 63 + print(f"full-day LEX,8h = {full.lex_8h:.1f} dB U = {full.expanded_uncertainty:.1f} dB") 64 + # full-day LEX,8h = 90.1 dB U = 3.4 dB 65 + ``` 66 + 67 + ## 2. The Annex C uncertainty budget 68 + 69 + Two subtleties are worth spelling out. First, the coverage factor is 70 + $k = 1.65$ for a **one-sided** 95 % interval (Clause 14), because a hygienist 71 + cares only about the *upper* bound: `res.upper_limit` = $L_{EX,8h} + U$ is the 72 + value 95 % of measurements fall below, the number compared against an action 73 + limit. Second, the task and job methods weight the *same* spread of samples 74 + differently. The task sampling uncertainty $u_{1a}$ (Eq. C.6) divides the summed 75 + squared deviations by $I(I-1)$ — the standard error of the mean, smaller by a 76 + factor $\sqrt{I}$ — whereas the job/full-day sampling uncertainty $u_1$ (Eq. C.12) 77 + is the plain sample standard deviation with denominator $N-1$, whose contribution 78 + $c_1 u_1$ is then read from **Table C.4** as a function of $(N, u_1)$. The same 79 + raw scatter therefore inflates the job estimate more, which is the standard's 80 + built-in penalty for coarser, fewer samples. (The printed job $L_{EX,8h}$ is 81 + $88.2$ dB where Annex E reports $88.1$: the standard rounds the effective-day 82 + level to $88.4$ before the duration normalisation; the library keeps it 83 + unrounded.) 84 + 85 + When a task's samples span **3 dB or more** (Clause 9.3), or the job contribution 86 + $c_1 u_1$ exceeds 3.5 dB (Clause 10.4), or too few workers are covered 87 + (Table 1 cumulative-duration), the result sets `sampling_advisory=True` and, with 88 + `warn=True`, emits an `OccupationalExposureWarning` recommending more measurements. Peak 89 + levels $L_{p,Cpeak}$ are reported **without** an uncertainty — Annex C gives no 90 + method for them (Table C.5, Note 1), so peak-uncertainty is out of scope. The 91 + three Annex D/E/F worked examples above are reproduced to the standard's printed 92 + precision (Annex E's final rounding is disclosed above), and the theory is 93 + derived on the [Theory](/phonometry/reference/theory/) page. 94 + 95 + ### `task_based_exposure()` / `job_based_exposure()` / `full_day_exposure()` parameters 96 + 97 + | Parameter | Applies to | Type | Units | Range / default | Notes | 98 + | :--- | :--- | :--- | :--- | :--- | :--- | 99 + | `tasks` | task | list of `Task` | — | ≥ 1 | Each `Task` has `samples`, `duration_hours`, optional `duration_range`/`duration_samples`, `label`, `instrument` | 100 + | `samples` | job / full-day | sequence | dB | ≥ 2 (≥ 5 / ≥ 3 advised) | Random `Lp,A,eqT` samples | 101 + | `effective_duration_hours` | job / full-day | float | h | > 0 | Effective working-day duration $T_e$ | 102 + | `instrument` | all | str | — | `'class1'`, `'class2'`, `'personal_exposimeter'` (default) | Selects $u_2$ (Table C.5) | 103 + | `u3` | all | float | dB | default `1.0` | Microphone-position uncertainty (Clause C.6) | 104 + | `include_duration_uncertainty` | task | bool | — | default `True` | `False` omits the $(c_{1b}u_{1b})^2$ term (Annex D case a) | 105 + | `n_workers` / `sample_duration_hours` | job | int / float | — / h | default `None` | Table 1 cumulative-duration check | 106 + | `warn` | all | bool | — | default `True` | Emit `OccupationalExposureWarning` for the sampling advisories | 107 + 108 + All three return an `ExposureResult` with `lex_8h`, `combined_standard_uncertainty` 109 + $u$, `expanded_uncertainty` $U = 1.65\ u$, `upper_limit` = $L_{EX,8h} + U$, 110 + `sampling_advisory`, and (task-based) the per-task `tasks` breakdown. 111 + 112 + ## See also 113 + 114 + - [Levels](/phonometry/guides/levels/) — the `lex_8h` / `sound_exposure` dose primitives 115 + (IEC 61252) and the LCpeak these strategies report alongside. 116 + - [Measurement uncertainty](/phonometry/guides/gum-uncertainty/) — the GUM machinery behind 117 + combined and expanded uncertainties. 118 + - [Theory](/phonometry/reference/theory/) — the derivation of the strategy formulas and the 119 + Annex C budget. 120 + 121 + --- 122 + 123 + **Standards.** ISO 9612:2009, *Acoustics — Determination of occupational noise 124 + exposure — Engineering method* — the task-based (Clause 9), job-based 125 + (Clause 10) and full-day (Clause 11) strategies, the Annex C uncertainty budget 126 + (Formulae C.6, C.9 and C.12, Tables C.4/C.5) and the one-sided coverage factor 127 + k = 1.65 (Clause 14), validated against the worked examples of Annexes D, E 128 + and F.
+1 -1
site/src/content/docs/guides/outdoor-propagation.md
··· 249 249 The method's stated accuracy is $\pm 1$ to $\pm 3$ dB for broadband noise up to 250 250 1000 m (Table 5). See the [Theory](/phonometry/reference/theory/) page for the full derivation, the 251 251 [Room Acoustics guide](/phonometry/guides/room-acoustics/) for how $\alpha$ feeds 252 - ISO 354, and the [Levels guide](/phonometry/guides/levels/) for the ISO 9612 occupational 252 + ISO 354, and the [Occupational Noise Exposure guide](/phonometry/guides/occupational-exposure/) for the ISO 9612 occupational 253 253 exposure that consumes A-weighted levels.
+29 -90
site/src/content/docs/guides/psychoacoustics.md
··· 1 1 --- 2 - title: "Psychoacoustics & Speech Intelligibility" 3 - description: "Zwicker (ISO 532-1), Moore-Glasberg (ISO 532-2/3) and Sottek (ECMA-418-2) loudness, sharpness (DIN 45692), tonality and roughness, plus STI/STIPA (IEC 60268-16)." 2 + title: "Psychoacoustics" 3 + description: "Zwicker (ISO 532-1), Moore-Glasberg (ISO 532-2/3) and Sottek (ECMA-418-2) loudness, sharpness (DIN 45692), ISO 226 equal-loudness contours, and ECMA-418-2 tonality and roughness." 4 4 --- 5 5 6 6 Level metrics tell you how much *sound pressure* there is; psychoacoustic 7 7 metrics tell you what a listener actually *perceives*. This page covers 8 - loudness (ISO 532-1), sharpness (DIN 45692) and the speech transmission 9 - index (IEC 60268-16), then the advanced Moore-Glasberg (ISO 532-2/3) and 10 - Sottek Hearing Model (ECMA-418-2) loudness, tonality and roughness models. 8 + loudness (ISO 532-1), sharpness (DIN 45692) and the equal-loudness 9 + contours of pure tones (ISO 226), then the advanced Moore-Glasberg 10 + (ISO 532-2/3) and Sottek Hearing Model (ECMA-418-2) loudness, tonality and 11 + roughness models. Speech metrics live in their own guides: the 12 + transmission-channel STI/STIPA in 13 + [Speech Transmission Index](/phonometry/guides/speech-transmission/) and the 14 + audibility-based SII in 15 + [Speech Intelligibility Index](/phonometry/guides/speech-intelligibility/). 11 16 12 17 ## Loudness in sones (ISO 532-1, Zwicker) 13 18 ··· 127 132 CI verifies the Table A.2 target values (0.38 acum at 250 Hz up to 128 133 2.82 acum at 4 kHz) within the standard's 5 % / 0.05 acum tolerance. 129 134 130 - ## Speech Transmission Index (IEC 60268-16) 135 + ## Loudness level of pure tones (ISO 226:2023) 131 136 132 - Reverberation and noise do not muffle speech uniformly — they blur its 133 - *envelope*: the slow (0.63–12.5 Hz) intensity modulations that carry 134 - syllables. STI quantifies how much of that modulation survives from mouth 135 - to ear, per octave band, as the **modulation transfer function** m(F). A 136 - delta-like channel keeps m = 1 (STI = 1); reverberation low-passes the 137 - envelope following Schroeder's closed form, and steady noise scales it: 138 - 139 - $$ 140 - m(F) = \frac{1}{\sqrt{1 + \left(2\pi F\,\frac{T_{60}}{13.8}\right)^2}} 141 - \cdot \frac{1}{1 + 10^{-\mathrm{SNR}/10}} 142 - $$ 143 - 144 - <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60.png" alt="STI versus reverberation time with the IEC 60268-16 Annex F rating bands shaded" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60_dark.png" alt="STI versus reverberation time with the IEC 60268-16 Annex F rating bands shaded" style="width:80%"> 145 - 146 - <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain.svg" alt="STI measurement chain: STIPA source signal through the room to the microphone and the MTF analysis" style="width:92%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain_dark.svg" alt="STI measurement chain: STIPA source signal through the room to the microphone and the MTF analysis" style="width:92%"> 137 + The normal equal-loudness-level contours relate the SPL of a pure tone to its 138 + perceived *loudness level* in phons (the SPL of an equally loud 1 kHz tone). 139 + `equal_loudness_contour(phon)` evaluates ISO 226:2023 Formula (1) at the 29 140 + preferred third-octave frequencies of Table 1, `loudness_level(spl, frequency)` 141 + is the exact inverse (Formula 2), and `hearing_threshold()` returns the 142 + threshold-of-hearing column: 147 143 148 144 ```python 149 - import numpy as np 150 - from phonometry import sti_from_impulse_response, stipa, stipa_signal 151 - 152 - fs = 48000 153 - # A measured room impulse response (synthesized decay so the example runs) 154 - ir = np.random.default_rng(0).standard_normal(fs) * np.exp(-6.9 * np.arange(fs) / fs / 0.5) 155 - 156 - # Indirect method: from a measured room impulse response 157 - res = sti_from_impulse_response(ir, fs, snr=25.0) 158 - print(f"STI = {res.sti:.2f} ({res.rating})") # e.g. 0.62 (D) 159 - 160 - # Direct STIPA measurement: play stipa_signal() in the room, record it 161 - test = stipa_signal(fs, seconds=18.0, level_db=80.0) 162 - recording = test # in practice, the microphone signal after playback 163 - res = stipa(recording, fs) 164 - res.plot() # per-band modulation transfer index (MTI) bars, STI + rating in the title 165 - ``` 166 - 167 - <details> 168 - <summary>Show the code for this figure</summary> 169 - 170 - ```python 171 - import matplotlib.pyplot as plt 172 - 173 - # STI vs reverberation time: sweep sti_from_impulse_response over synthetic 174 - # exponential decays (white noise x exp(-6.9077 t / T60)) at a T60 grid — 175 - # exactly the physics behind the curve above: 176 - rng = np.random.default_rng(0) 177 - t60_grid = np.array([0.3, 0.5, 0.8, 1.2, 1.6, 2.0, 2.5, 3.0, 4.0, 5.0]) 178 - sti_values = [] 179 - for t60 in t60_grid: 180 - t = np.arange(int(2 * t60 * fs)) / fs 181 - ir = rng.standard_normal(t.size) * np.exp(-6.9077 * t / t60) 182 - sti_values.append(sti_from_impulse_response(ir, fs).sti) 145 + from phonometry import equal_loudness_contour, loudness_level 183 146 184 - fig, ax = plt.subplots() 185 - ax.semilogx(t60_grid, sti_values, "o-") 186 - ax.set_xlabel("Reverberation time T60 [s]") 187 - ax.set_ylabel("STI") 188 - ax.set_ylim(0.0, 1.0) 189 - ax.grid(True, which="both", alpha=0.3) 190 - plt.show() 147 + freqs, spl = equal_loudness_contour(40.0) # the classic 40-phon contour 148 + phon = loudness_level(73.0, 63.0) # 73 dB @ 63 Hz -> 40 phon 191 149 ``` 192 150 193 - </details> 151 + <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours.png" alt="ISO 226:2023 normal equal-loudness-level contours from 20 to 90 phon with the hearing threshold curve" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/equal_loudness_contours_dark.png" alt="ISO 226:2023 normal equal-loudness-level contours from 20 to 90 phon with the hearing threshold curve" style="width:80%"> 194 152 195 - `stipa` emits a `UserWarning` when the recording is shorter than the 196 - recommended 15 s (IEC 60268-16 STIPA practice, 15 s to 25 s): below that the 197 - slow modulation components are averaged over too few periods and the STI is 198 - biased low (an ideal loopback gives STI ≈ 0.944 at 5 s vs ≈ 0.998 at 18 s). 199 - 200 - The implementation follows **Edition 5 (2020)**: Edition 4's normative PDF 201 - is the base and every Ed. 5 change is source-attributed in the code — the 202 - only numeric delta is the revised male speech spectrum of clause A.6.1. 203 - CI checks the standard's own verification vectors: the six weighting-factor 204 - band pairs to ±0.001 STI, the m ↔ STI mapping table, the level-dependent 205 - masking control points, and Schroeder-form decays at four T₆₀ values. 206 - 207 - ### `sti_from_impulse_response()` / `stipa()` parameters 208 - 209 - | Parameter | Type | Units | Range / default | Notes | 210 - | :--- | :--- | :--- | :--- | :--- | 211 - | `ir` / `x` | 1D array | any / Pa | non-empty | IR (indirect) or STIPA recording (direct) | 212 - | `fs` | int | Hz | > 0 | | 213 - | `snr` | float or 7-vector, optional | dB | default `None` | Adds steady-noise degradation | 214 - | `level` | 7-vector, optional | dB SPL | default `None` | Enables auditory masking + reception threshold (Tables A.2/A.3) | 215 - | `ambient` | 7-vector, optional | dB SPL | needs `level` | Ambient noise band levels | 216 - | `reference` | 1D array, optional (`stipa`) | — | default `None` | Measured source signal instead of the nominal m = 0.55 | 217 - 218 - Both return `STIResult`: `sti`, `mti` (7 bands), `mtf` (7×14 or 7×2), 219 - `band_levels`, `rating` (Annex F letter `A+`…`U`). 153 + Validity per clause 4.1: 20-90 phon (80 phon above 4 kHz); the implementation 154 + is verified against the Annex B tables in CI. Note this is the loudness of 155 + *pure tones* — the loudness of arbitrary signals in sones is what the ISO 532 156 + models on this page compute. 220 157 221 158 ## Advanced loudness & sound-quality models 222 159 ··· 553 490 `time`, `roughness_vs_time` (R(l50)), `specific_roughness_vs_time` 554 491 ((n_times, 53) array), `field`. 555 492 556 - See [Levels](/phonometry/guides/levels/) for tonality metrics and [Theory](/phonometry/reference/theory/) for the 557 - underlying math. 493 + See [Prominent Discrete Tones](/phonometry/guides/tone-prominence/) for the 494 + ECMA-418-1 TNR/PR prominence verdicts, 495 + [Speech Transmission Index](/phonometry/guides/speech-transmission/) for 496 + STI/STIPA, and [Theory](/phonometry/reference/theory/) for the underlying math.
+5 -3
site/src/content/docs/guides/room-acoustics.md
··· 399 399 `d2s`/`lp_as_4m` are `nan` if fewer than two positions fall in 2–16 m; 400 400 `rd`/`rp` are `nan` when STI does not decrease with distance. The per-position 401 401 STI can itself be measured with the STIPA tools in the 402 - [Psychoacoustics guide](/phonometry/guides/psychoacoustics/). 402 + [Speech Transmission Index guide](/phonometry/guides/speech-transmission/). 403 403 404 404 ## 4. Sound absorption (ISO 354) 405 405 ··· 466 466 laboratory and predicted sound insulation between spaces, and its measurement uncertainty. 467 467 - [Sound Power](/phonometry/guides/sound-power/) — the `LW` methods that consume the 468 468 ISO 354 absorption area (the ISO 3744 `K2` and the ISO 3741 absorption term). 469 - - [Psychoacoustics and Speech Intelligibility](/phonometry/guides/psychoacoustics/) — STI/STIPA 470 - feeds the open-plan `sti_values`; loudness and sharpness. 469 + - [Speech Transmission Index](/phonometry/guides/speech-transmission/) — the STI/STIPA 470 + measurement that feeds the open-plan `sti_values`. 471 + - [Psychoacoustics](/phonometry/guides/psychoacoustics/) — loudness, sharpness and the other 472 + perception metrics of what the room delivers. 471 473 - [Filter Banks](/phonometry/guides/filter-banks/) — the IEC 61260 fractional-octave filters 472 474 used for band decay curves and insulation spectra. 473 475 - [Levels](/phonometry/guides/levels/) — energy averaging and the level metrics behind
+12 -2
site/src/content/docs/guides/speech-intelligibility.md
··· 11 11 covers the **one-third-octave-band method** of **ANSI S3.5-1997 (R2017)** — 18 12 12 bands from 160 Hz to 8000 Hz. 13 13 14 + :::note 15 + **SII vs STI.** The SII predicts intelligibility from *audibility* — how much 16 + of the speech spectrum clears the noise and the hearing threshold at the 17 + listener's ear — while the STI characterises a *transmission channel*: how 18 + much of the speech modulation a room or sound system preserves. For the 19 + latter, see the [Speech Transmission Index guide](/phonometry/guides/speech-transmission/). 20 + ::: 21 + 14 22 <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_speech_intelligibility.svg" alt="The SII computation flow: three equivalent-spectrum-level inputs (speech Ei', noise Ni', hearing threshold Ti') feed the self-speech masking and spread-of-masking stage (equivalent masking spectrum level Zi), then the equivalent disturbance Di, then the band-audibility function Ai clipped to [0, 1], and finally the band-importance-weighted sum SII over the 18 one-third-octave bands" style="width:94%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_speech_intelligibility_dark.svg" alt="The SII computation flow: three equivalent-spectrum-level inputs (speech Ei', noise Ni', hearing threshold Ti') feed the self-speech masking and spread-of-masking stage (equivalent masking spectrum level Zi), then the equivalent disturbance Di, then the band-audibility function Ai clipped to [0, 1], and finally the band-importance-weighted sum SII over the 18 one-third-octave bands" style="width:94%"> 15 23 16 24 ## 1. Inputs and the band-importance function ··· 155 163 156 164 ## See also 157 165 158 - - [Psychoacoustics and Speech Intelligibility](/phonometry/guides/psychoacoustics/) — loudness, 159 - sharpness and the STI/STIPA transmission index that the SII complements. 166 + - [Speech Transmission Index](/phonometry/guides/speech-transmission/) — the STI/STIPA 167 + transmission index that the SII complements. 168 + - [Psychoacoustics](/phonometry/guides/psychoacoustics/) — loudness, sharpness and the 169 + perception metrics of what the listener hears. 160 170 - [Filter Banks](/phonometry/guides/filter-banks/) — the one-third-octave bands the SII is 161 171 evaluated on. 162 172 - [Levels](/phonometry/guides/levels/) — the spectrum and band levels behind the equivalent
+135
site/src/content/docs/guides/speech-transmission.md
··· 1 + --- 2 + title: "Speech Transmission Index (STI)" 3 + description: "The IEC 60268-16 speech transmission index: the modulation transfer function, the indirect method from a measured impulse response, and the direct STIPA measurement with its standardized test signal." 4 + --- 5 + 6 + A public-address system, an intercom, a reverberant lecture hall — each is a 7 + *transmission channel* between a talker's mouth and a listener's ear, and each 8 + degrades speech in its own way. The **Speech Transmission Index** (STI) of 9 + IEC 60268-16 rates that channel with a single number in [0, 1] by measuring 10 + how much of the speech *envelope* survives the trip. This page covers the 11 + modulation-transfer physics behind the index, the indirect method from a 12 + measured room impulse response, and the direct STIPA measurement with its 13 + standardized test signal. 14 + 15 + :::note 16 + **STI vs SII.** The STI characterises a *transmission channel* — how much of 17 + the speech modulation a room or sound system preserves — while the SII 18 + predicts intelligibility from *audibility*: how much of the speech spectrum 19 + clears the noise and the hearing threshold at the listener's ear. For the 20 + latter, see the [Speech Intelligibility Index guide](/phonometry/guides/speech-intelligibility/). 21 + ::: 22 + 23 + ## 1. The modulation transfer function 24 + 25 + Reverberation and noise do not muffle speech uniformly — they blur its 26 + *envelope*: the slow (0.63–12.5 Hz) intensity modulations that carry 27 + syllables. STI quantifies how much of that modulation survives from mouth 28 + to ear, per octave band, as the **modulation transfer function** m(F). A 29 + delta-like channel keeps m = 1 (STI = 1); reverberation low-passes the 30 + envelope following Schroeder's closed form, and steady noise scales it: 31 + 32 + $$ 33 + m(F) = \frac{1}{\sqrt{1 + \left(2\pi F\,\frac{T_{60}}{13.8}\right)^2}} 34 + \cdot \frac{1}{1 + 10^{-\mathrm{SNR}/10}} 35 + $$ 36 + 37 + <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60.png" alt="STI versus reverberation time with the IEC 60268-16 Annex F rating bands shaded" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sti_vs_t60_dark.png" alt="STI versus reverberation time with the IEC 60268-16 Annex F rating bands shaded" style="width:80%"> 38 + 39 + <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain.svg" alt="STI measurement chain: STIPA source signal through the room to the microphone and the MTF analysis" style="width:92%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_sti_chain_dark.svg" alt="STI measurement chain: STIPA source signal through the room to the microphone and the MTF analysis" style="width:92%"> 40 + 41 + ## 2. Indirect and direct (STIPA) measurement 42 + 43 + ```python 44 + import numpy as np 45 + from phonometry import sti_from_impulse_response, stipa, stipa_signal 46 + 47 + fs = 48000 48 + # A measured room impulse response (synthesized decay so the example runs) 49 + ir = np.random.default_rng(0).standard_normal(fs) * np.exp(-6.9 * np.arange(fs) / fs / 0.5) 50 + 51 + # Indirect method: from a measured room impulse response 52 + res = sti_from_impulse_response(ir, fs, snr=25.0) 53 + print(f"STI = {res.sti:.2f} ({res.rating})") # e.g. 0.62 (D) 54 + 55 + # Direct STIPA measurement: play stipa_signal() in the room, record it 56 + test = stipa_signal(fs, seconds=18.0, level_db=80.0) 57 + recording = test # in practice, the microphone signal after playback 58 + res = stipa(recording, fs) 59 + res.plot() # per-band modulation transfer index (MTI) bars, STI + rating in the title 60 + ``` 61 + 62 + <details> 63 + <summary>Show the code for this figure</summary> 64 + 65 + ```python 66 + import matplotlib.pyplot as plt 67 + 68 + # STI vs reverberation time: sweep sti_from_impulse_response over synthetic 69 + # exponential decays (white noise x exp(-6.9077 t / T60)) at a T60 grid — 70 + # exactly the physics behind the curve above: 71 + rng = np.random.default_rng(0) 72 + t60_grid = np.array([0.3, 0.5, 0.8, 1.2, 1.6, 2.0, 2.5, 3.0, 4.0, 5.0]) 73 + sti_values = [] 74 + for t60 in t60_grid: 75 + t = np.arange(int(2 * t60 * fs)) / fs 76 + ir = rng.standard_normal(t.size) * np.exp(-6.9077 * t / t60) 77 + sti_values.append(sti_from_impulse_response(ir, fs).sti) 78 + 79 + fig, ax = plt.subplots() 80 + ax.semilogx(t60_grid, sti_values, "o-") 81 + ax.set_xlabel("Reverberation time T60 [s]") 82 + ax.set_ylabel("STI") 83 + ax.set_ylim(0.0, 1.0) 84 + ax.grid(True, which="both", alpha=0.3) 85 + plt.show() 86 + ``` 87 + 88 + </details> 89 + 90 + `stipa` emits a `UserWarning` when the recording is shorter than the 91 + recommended 15 s (IEC 60268-16 STIPA practice, 15 s to 25 s): below that the 92 + slow modulation components are averaged over too few periods and the STI is 93 + biased low (an ideal loopback gives STI ≈ 0.944 at 5 s vs ≈ 0.998 at 18 s). 94 + 95 + The implementation follows **Edition 5 (2020)**: Edition 4's normative PDF 96 + is the base and every Ed. 5 change is source-attributed in the code — the 97 + only numeric delta is the revised male speech spectrum of clause A.6.1. 98 + CI checks the standard's own verification vectors: the six weighting-factor 99 + band pairs to ±0.001 STI, the m ↔ STI mapping table, the level-dependent 100 + masking control points, and Schroeder-form decays at four T₆₀ values. 101 + 102 + ### `sti_from_impulse_response()` / `stipa()` parameters 103 + 104 + | Parameter | Type | Units | Range / default | Notes | 105 + | :--- | :--- | :--- | :--- | :--- | 106 + | `ir` / `x` | 1D array | any / Pa | non-empty | IR (indirect) or STIPA recording (direct) | 107 + | `fs` | int | Hz | > 0 | | 108 + | `snr` | float or 7-vector, optional | dB | default `None` | Adds steady-noise degradation | 109 + | `level` | 7-vector, optional | dB SPL | default `None` | Enables auditory masking + reception threshold (Tables A.2/A.3) | 110 + | `ambient` | 7-vector, optional | dB SPL | needs `level` | Ambient noise band levels | 111 + | `reference` | 1D array, optional (`stipa`) | — | default `None` | Measured source signal instead of the nominal m = 0.55 | 112 + 113 + Both return `STIResult`: `sti`, `mti` (7 bands), `mtf` (7×14 or 7×2), 114 + `band_levels`, `rating` (Annex F letter `A+`…`U`). 115 + 116 + ## See also 117 + 118 + - [Room Acoustics](/phonometry/guides/room-acoustics/) — the measured impulse response the 119 + indirect method consumes, and the open-plan metrics (ISO 3382-3) built on 120 + per-position STI. 121 + - [Speech Intelligibility Index](/phonometry/guides/speech-intelligibility/) — the 122 + audibility-based ANSI S3.5 index that complements the STI. 123 + - [Psychoacoustics](/phonometry/guides/psychoacoustics/) — loudness, sharpness, tonality and 124 + roughness of the received sound. 125 + - [Theory](/phonometry/reference/theory/) — the modulation-transfer derivation and the m ↔ STI 126 + mapping. 127 + 128 + --- 129 + 130 + **Standards.** IEC 60268-16:2020 (Edition 5), *Sound system equipment — 131 + Part 16: Objective rating of speech intelligibility by speech transmission 132 + index* — the modulation transfer function and the m ↔ STI mapping, the STIPA 133 + test signal and direct method, the indirect method from the impulse response, 134 + auditory masking and the reception threshold (Tables A.2/A.3), the revised 135 + male speech spectrum (clause A.6.1) and the Annex F rating letters.
+78
site/src/content/docs/guides/tone-prominence.md
··· 1 + --- 2 + title: "Prominent Discrete Tones (ECMA-418-1)" 3 + description: "The ECMA-418-1 tone-to-noise ratio and prominence ratio: FFT-based prominence verdicts for discrete tones against frequency-dependent criteria, with the ECMA-74 measurement positions." 4 + --- 5 + 6 + Tonal components in machinery noise are far more annoying than their level 7 + suggests. ECMA-418-1:2024 (referenced by ECMA-74 Annex D) gives two FFT-based 8 + methods to decide whether a discrete tone is *prominent*: 9 + `tone_to_noise_ratio()` compares the tone level with the masking noise in its 10 + critical band (clause 11), and `prominence_ratio()` compares the critical band 11 + centred on the tone with the two contiguous bands (clause 12). Both return a 12 + structured verdict against the frequency-dependent prominence criteria. 13 + 14 + ## 1. Tone-to-noise ratio and prominence ratio 15 + 16 + ```python 17 + import numpy as np 18 + from phonometry import tone_to_noise_ratio, prominence_ratio 19 + 20 + fs = 48000 21 + rng = np.random.default_rng(0) 22 + t = np.arange(fs) / fs 23 + x = np.sin(2 * np.pi * 1000 * t) + 0.05 * rng.standard_normal(fs) # 1 kHz tone in noise 24 + tnr = tone_to_noise_ratio(x, fs) # highest peak, or tone_freq=... 25 + pr = prominence_ratio(x, fs, tone_freq=1000.0) 26 + print(tnr.ratio_db, tnr.criterion_db, tnr.prominent) 27 + ``` 28 + 29 + The methods hinge on the **critical band** — the ear's analysis bandwidth, 30 + $\Delta f_c = 25 + 75\ [1 + 1.4(f/1000)^2]^{0.69}$ Hz (162 Hz at 1 kHz): a 31 + tone is masked only by the noise *inside* its critical band, so both ratios 32 + compare the tone against exactly that noise, not the whole spectrum. 33 + 34 + <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tonality_spectrum.png" alt="Averaged spectrum of a tone in noise with the critical band shaded and the tone-to-noise ratio annotated against its prominence criterion" style="width:80%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tonality_spectrum_dark.png" alt="Averaged spectrum of a tone in noise with the critical band shaded and the tone-to-noise ratio annotated against its prominence criterion" style="width:80%"> 35 + 36 + A TNR above $8 + 8.33\log_{10}(1000/f_t)$ dB (8 dB from 1 kHz up) classifies 37 + the tone as *prominent*; the PR criterion is $9 + 10\log_{10}(1000/f_t)$ dB. 38 + Low frequencies get higher thresholds because wider relative bands mask more. 39 + 40 + ## 2. Where to measure (ECMA-74) and practice 41 + 42 + ECMA-74 (which delegates its tone assessments to ECMA-418-1) also fixes where to measure around a device: 43 + 44 + <img class="light-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions.svg" alt="ECMA-74 emission measurement positions: seated operator microphone at 0.25 m and 1.20 m, and the four bystander positions at 1 m" style="width:92%"><img class="dark-only" src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_tonality_positions_dark.svg" alt="ECMA-74 emission measurement positions: seated operator microphone at 0.25 m and 1.20 m, and the four bystander positions at 1 m" style="width:92%"> 45 + 46 + Proximate secondary tones in the same critical band are combined per 47 + clause 11.6; for harmonic complexes assess each component (`tone_freq=`). 48 + Both methods work on Hann-windowed, RMS-averaged spectra and need no absolute 49 + calibration (the ratios are level differences). 50 + 51 + ### `tone_to_noise_ratio()` / `prominence_ratio()` parameters 52 + 53 + | Parameter | Type | Units | Range / default | Notes | 54 + | :--- | :--- | :--- | :--- | :--- | 55 + | `x` | 1D array | any (uncalibrated OK) | ≥ `fs/resolution_hz` samples | Ratios are level differences: calibration cancels out | 56 + | `fs` | int | Hz | > 0 | | 57 + | `tone_freq` | float, optional | Hz | 89.1–11 200; default `None` | `None` assesses the highest peak in the range of interest | 58 + | `resolution_hz` | float | Hz | > 0; default `1.0` | Tone band must stay within 15 % of the critical band (clause 11.2) | 59 + 60 + Both return a `ToneAssessment(frequency, ratio_db, criterion_db, prominent)`. 61 + 62 + ## See also 63 + 64 + - [Levels](/phonometry/guides/levels/) — the ISO 1996-1 rating levels whose tonal adjustments 65 + (Table A.1) these prominence verdicts justify objectively. 66 + - [Psychoacoustics](/phonometry/guides/psychoacoustics/) — the ECMA-418-2 psychoacoustic 67 + tonality T in tu_HMS, the hearing-model counterpart of these FFT ratios. 68 + - [Impulsive-sound prominence](/phonometry/guides/impulse-prominence/) — the NT ACOU 112 69 + counterpart for impulsive (rather than tonal) character. 70 + - [Theory](/phonometry/reference/theory/) — the critical-band model and criteria derivation. 71 + 72 + --- 73 + 74 + **Standards.** ECMA-418-1:2024 (3rd edition), *Psychoacoustic metrics for ITT 75 + equipment — Part 1: Prominent discrete tones* — the tone-to-noise ratio 76 + (clause 11), the prominence ratio (clause 12), the critical-band model and the 77 + frequency-dependent prominence criteria; ECMA-74 Annex D — the emission 78 + measurement positions, delegating the tone assessment to ECMA-418-1.
+7 -6
site/src/content/docs/reference/theory.md
··· 264 264 265 265 The standard specifies **no interpolation** between the tabulated frequencies. Formula (1) is specified for **20 phon to 90 phon** between 20 Hz and 4 kHz, and only up to **80 phon between 5 kHz and 12.5 kHz** — above 80 phon the contour therefore stops at 4 kHz. Values outside these limits from Formula (2) are extrapolations the standard labels as informative only. 266 266 267 - See the [Levels guide](/phonometry/guides/levels/) for usage. 267 + See the [Psychoacoustics guide](/phonometry/guides/psychoacoustics/) for usage. 268 268 269 269 ## Tone prominence: TNR and PR (ECMA-418-1) 270 270 ··· 284 284 285 285 **PR** (clause 12) compares the level of the critical band centred on the tone, $L_M$, with the mean power of the two **contiguous** critical bands $L_L$, $L_U$ (edges from the fitted Formulae 21–22 with Tables 2–3): $\mathrm{PR} = 10\log_{10} P_M - 10\log_{10}\left[(P_L + P_U)/2\right]$ (Formula 23). For $f_t \le 171.4$ Hz the lower band is truncated at 20 Hz and its power rescaled to a **100 Hz bandwidth** (Formula 24). The criterion (Formulae 25–26) is 9.0 dB at $f_t \ge 1$ kHz, rising as $9.0 + 10.0\log_{10}(1000/f_t)$ below. Tones are assessed within the 89.1 Hz – 11.2 kHz range of interest (clauses 11.5 / 12.6). 286 286 287 - See the [Levels guide](/phonometry/guides/levels/) for usage. 287 + See the [Prominent Discrete Tones guide](/phonometry/guides/tone-prominence/) for usage. 288 288 289 289 ## Event and dose metrics 290 290 ··· 502 502 m_{dr} = \frac{2 \sqrt{\left( \sum_t I_k(t) \sin 2 \pi f_m t \right)^2 + \left( \sum_t I_k(t) \cos 2 \pi f_m t \right)^2}}{\sum_t I_k(t)}, \qquad m = \frac{m_{dr}}{0.55} 503 503 $$ 504 504 505 - See the [Psychoacoustics guide](/phonometry/guides/psychoacoustics/) for usage. 505 + See the [Speech Transmission Index guide](/phonometry/guides/speech-transmission/) for usage. 506 506 507 507 ## Speech Intelligibility Index (ANSI S3.5) 508 508 ··· 783 783 (Formula A.7), and the uncorrelated single-number uncertainty is the 784 784 energy-weighted quadrature sum of the band uncertainties (Formula B.2). 785 785 786 - See the [Room and Building Acoustics guide](/phonometry/guides/room-acoustics/) for usage. 786 + See the [Room Acoustics](/phonometry/guides/room-acoustics/) and 787 + [Building Acoustics](/phonometry/guides/building-acoustics/) guides for usage. 787 788 788 789 ## Outdoor propagation and occupational exposure (ISO 9613-1/2, ISO 9612) 789 790 ··· 890 891 $88.1$ dB, $3.8$ dB) and F (full-day, $90.1$ dB, $3.4$ dB) are reproduced to 891 892 the standard's printed precision — every intermediate of Annex E is digit-exact, 892 893 and its final level differs only by the standard's own pre-rounding of the 893 - effective-day level (see the [Levels guide](/phonometry/guides/levels/)). 894 + effective-day level (see the [Occupational Noise Exposure guide](/phonometry/guides/occupational-exposure/)). 894 895 895 896 See the [Outdoor Propagation guide](/phonometry/guides/outdoor-propagation/) and the 896 - [Levels guide](/phonometry/guides/levels/) for usage. 897 + [Occupational Noise Exposure guide](/phonometry/guides/occupational-exposure/) for usage. 897 898 898 899 ## Sound power determination (ISO 3744/3745/3746, ISO 3741, ISO 9614-2/3) 899 900