diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e8de1671..376914a76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1036,6 +1036,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/). the same 251 names, and the nineteen pre-split module paths keep working until 5.0. +- `phonometry.psychoacoustics` has two families, along the split every text on + sound quality makes. `psychoacoustics.loudness` holds how loud a sound is: + the four models (ISO 532-1 Zwicker, ISO 532-2 Moore-Glasberg, the + time-varying Moore-Glasberg of ISO 532-3 and ECMA-418-2) and the ISO 226 + equal-loudness contours that put a level in phons. + `psychoacoustics.quality` holds what the sound is like + once its loudness is known: sharpness, roughness, fluctuation strength, + tonality, tone audibility and the annoyance models built on them. The ERB + scale stays at the root, because both families measure on it. + + Five modules lose the word the family now says: `loudness_zwicker`, + `loudness_moore_glasberg`, `loudness_moore_glasberg_time`, `loudness_ecma` + and `loudness_contours` become `loudness.zwicker`, `loudness.moore_glasberg`, + `loudness.moore_glasberg_time`, `loudness.ecma` and `loudness.contours`; + `psychoacoustic_annoyance` becomes `quality.annoyance`. + + Nothing moves in the namespace: `from phonometry import psychoacoustics` + exports the same 60 names, and the thirteen pre-split module paths keep + working until 5.0. + - `phonometry.materials` has four families, by what the material does rather than by how it is measured. `materials.absorbers` holds everything that absorbs and the methods that characterise it: ISO 354 in the reverberation diff --git a/docs/ERRATA.md b/docs/ERRATA.md index 6805e5ca1..251b5ae26 100644 --- a/docs/ERRATA.md +++ b/docs/ERRATA.md @@ -773,7 +773,7 @@ which is the check that enforces the rule; see printed p. 20, 220 dpi (Figure 6 and its 11220 tick). - **Library behaviour:** uses the internally consistent 89,1 Hz to 11 200 Hz range (upper end exclusive per the formulas), with a code note - in [`tonality.py`](../src/phonometry/psychoacoustics/tonality.py). + in [`tonality.py`](../src/phonometry/psychoacoustics/quality/tonality.py). - **Status:** unreported. ## ECMA-418-1:2024 (3rd edition), Formula (21) (repeated constant term) @@ -795,7 +795,7 @@ which is the check that enforces the rule; see Formula (22) as the consistent control. Render: `plan/ECMA-418-1_3rd_edition_december_2024.pdf`, PDF page 25, printed p. 17, 300 dpi. - **Library behaviour:** implements the $C_{L,1}$ reading, which is the only one that returns a usable band edge, with a code note in - [`tonality.py`](../src/phonometry/psychoacoustics/tonality.py). + [`tonality.py`](../src/phonometry/psychoacoustics/quality/tonality.py). - **Status:** unreported. ## ECMA-418-1:2024 (3rd edition), clause 11.3 (unresolved field references) @@ -834,7 +834,7 @@ which is the check that enforces the rule; see reading reproduces the Clause 7 roughness calibration (1 asper) to 0,9999. - **Library behaviour:** implements the flush-to-end reading with a code - note in [`roughness_ecma.py`](../src/phonometry/psychoacoustics/roughness_ecma.py). + note in [`roughness_ecma.py`](../src/phonometry/psychoacoustics/quality/roughness_ecma.py). - **Status:** unreported. ## ECMA-418-2:2025 (4th edition), clause 9.1.4, Formula (127) (HSA kernel phase) @@ -952,7 +952,7 @@ which is the check that enforces the rule; see printed p. 14, 400 dpi. - **Library behaviour:** follows the DIN/sqrt(2) reading (it matches the only executable reference), with the choice recorded in - [`tone_audibility.py`](../src/phonometry/psychoacoustics/tone_audibility.py). + [`tone_audibility.py`](../src/phonometry/psychoacoustics/quality/tone_audibility.py). - **Status:** unreported. ## DIN 45681:2005-03, Anhang I, Tabelle I.6, row "6 FG" @@ -1382,7 +1382,7 @@ which is the check that enforces the rule; see - **Library behaviour:** implements $0{,}76 \cdot 10^{-3}$ with a note at the formula; the carrier-frequency sweep test would catch a regression to the printed - value ([`fluctuation_strength.py`](../src/phonometry/psychoacoustics/fluctuation_strength.py)). + value ([`fluctuation_strength.py`](../src/phonometry/psychoacoustics/quality/fluctuation_strength.py)). - **Status:** unreported (conference paper rather than a standard). ## Medwin & Clay, Fundamentals of Acoustical Oceanography (1998), Eq. (3.4.30) (boric-acid coefficient) diff --git a/docs/advanced-loudness.md b/docs/advanced-loudness.md index f49c5f267..259261f10 100644 --- a/docs/advanced-loudness.md +++ b/docs/advanced-loudness.md @@ -359,7 +359,7 @@ The Zwicker method (ISO 532-1:2017) is the reference: stationary and time-varyin - [Sound Quality Metrics](sound-quality.md): the tonality, roughness and fluctuation strength built on the same ECMA-418-2 front-end. - [Theory](theory-perception.md): the equations behind the loudness models. -- API reference: [`psychoacoustics.loudness_moore_glasberg`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/), [`psychoacoustics.loudness_moore_glasberg_time`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg-time/) and [`psychoacoustics.loudness_ecma`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-ecma/). +- API reference: [`psychoacoustics.loudness.moore_glasberg`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/moore-glasberg/), [`psychoacoustics.loudness.moore_glasberg_time`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/moore-glasberg-time/) and [`psychoacoustics.loudness.ecma`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/ecma/). ## References diff --git a/docs/api-reference.md b/docs/api-reference.md index 3d48674a1..c72e82570 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -27,7 +27,7 @@ contour = aircraft.noise_contour(path, powers, distances, sel, lmax, x=gx, y=gy) | `phonometry.filters` | Octave and fractional-octave filter banks, frequency weightings and time weighting, parametric EQ, IEC 61260-1 and IEC 61672-1 class verification | | `phonometry.signals` | Levels (Leq, LAeq, percentiles), Welch and multitaper spectra, coherence, time-frequency, correlation, envelope, cepstrum, phase, synchronous averaging, test signals | | `phonometry.metrology` | Calibration, GUM uncertainty and Monte Carlo, data qualification (stationarity, trends, peak statistics), IEC 61043 intensity class | -| `phonometry.psychoacoustics` | Loudness (Zwicker, ECMA, Moore-Glasberg), sharpness, tonality, roughness, fluctuation strength, annoyance, tonal audibility | +| `phonometry.psychoacoustics` | Two families: `loudness` (ISO 532-1 Zwicker, ISO 532-2 and ISO 532-3 Moore-Glasberg, ECMA-418-2, ISO 226 equal-loudness contours) and `quality` (sharpness, roughness, fluctuation strength, tonality, tone audibility, annoyance), plus the ERB scale both measure on | | `phonometry.speech` | Speech Transmission Index (IEC 60268-16), Speech Intelligibility Index (ANSI S3.5), STOI and ESTOI | | `phonometry.hearing` | Audiometric thresholds (ISO 7029/389-7), noise-induced hearing loss (ISO 1999), occupational exposure (ISO 9612) | | `phonometry.emission` | Sound power (ISO 3740 family), sound intensity, vibration-based power | @@ -59,6 +59,7 @@ warns and delegates as well. | `phonometry.building.spanish_building_code` | `phonometry.building.regulation.spain` | 5.0 | | `phonometry.materials.porous_absorber` | `phonometry.materials.absorbers.porous` | 5.0 | | `phonometry.environmental` | `phonometry.environment` | 5.0 | +| `phonometry.psychoacoustics.loudness_zwicker` | `phonometry.psychoacoustics.loudness.zwicker` | 5.0 | | Name | Type | Description (Inputs) | Usage Snippet (Outputs) | | :--- | :--- | :--- | :--- | diff --git a/docs/loudness.md b/docs/loudness.md index fc7d518e9..eddae7817 100644 --- a/docs/loudness.md +++ b/docs/loudness.md @@ -274,4 +274,4 @@ hearing threshold. - [Psychoacoustic annoyance and fluctuation strength](psychoacoustic-annoyance.md): the Zwicker and Fastl model that consumes the percentile loudness $N_5$. - [Theory](theory-perception.md): the equations behind the loudness models. -- API reference: [`psychoacoustics.loudness_zwicker`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-zwicker/) and [`psychoacoustics.loudness_contours`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-contours/). +- API reference: [`psychoacoustics.loudness.zwicker`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/zwicker/) and [`psychoacoustics.loudness.contours`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/contours/). diff --git a/docs/tone-prominence.md b/docs/tone-prominence.md index ea28d568c..cfcfb80b0 100644 --- a/docs/tone-prominence.md +++ b/docs/tone-prominence.md @@ -215,7 +215,7 @@ other only as supporting evidence. - [Impulsive-sound prominence](impulse-prominence.md): the NT ACOU 112 counterpart for impulsive (rather than tonal) character. - [Theory](theory-perception.md): the critical-band model and criteria derivation. -- API reference: [`psychoacoustics.tonality`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/tonality/). +- API reference: [`psychoacoustics.quality.tonality`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/tonality/). ## References diff --git a/docs/why-phonometry.md b/docs/why-phonometry.md index 8a2ddc16d..6ca2ddf99 100644 --- a/docs/why-phonometry.md +++ b/docs/why-phonometry.md @@ -128,8 +128,8 @@ sample from the metrology core: | IEC 61672-1:2013 Table 5 | `lc_peak()` one-cycle/half-cycle peak responses, class 1 limits | `tests/signals/test_levels.py` | | IEC 61260-1:2014 Table 1 | Filter-bank class 1/2 acceptance limits via `verify_filter_class()` | `tests/filters/test_compliance.py` | | ISO 7196:1995 Table 2 | G weighting (infrasound) at every nominal response value, 0.25–315 Hz | `tests/filters/test_g_weighting.py` | -| ISO 226:2023 Table 1 and Annex B | Equal-loudness contours and loudness levels against the Annex B tables, hearing threshold against the Table 1 $T_f$ parameters | `tests/psychoacoustics/test_loudness_contours.py` | -| ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/psychoacoustics/test_tonality.py` | +| ISO 226:2023 Table 1 and Annex B | Equal-loudness contours and loudness levels against the Annex B tables, hearing threshold against the Table 1 $T_f$ parameters | `tests/psychoacoustics/loudness/test_contours.py` | +| ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/psychoacoustics/quality/test_tonality.py` | | ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/environment/assessment/test_rating.py` | | IEC 60942:2017 Table 2 | Calibrator short-term stability limits (frequency-dependent, class 1) in `sensitivity()` | `tests/metrology/test_calibration_validation.py` | diff --git a/llms-full.txt b/llms-full.txt index e8fa96011..4fde8edb6 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -334,20 +334,20 @@ The generated API reference, one page per module. Fetch these only when a specif - [power/sound-power-intensity](https://jmrplens.github.io/phonometry/reference/api/power/sound-power-intensity/) - [power/sound-power-reverberation](https://jmrplens.github.io/phonometry/reference/api/power/sound-power-reverberation/) - [power/vibration-sound-power](https://jmrplens.github.io/phonometry/reference/api/power/vibration-sound-power/) +- [psychoacoustics/annoyance](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/annoyance/) +- [psychoacoustics/contours](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/contours/) +- [psychoacoustics/ecma](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/ecma/) - [psychoacoustics/erb-scale](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/erb-scale/) - [psychoacoustics/fluctuation-strength](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/fluctuation-strength/) - [psychoacoustics/fluctuation-strength-ecma](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/fluctuation-strength-ecma/) -- [psychoacoustics/loudness-contours](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-contours/) -- [psychoacoustics/loudness-ecma](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-ecma/) -- [psychoacoustics/loudness-moore-glasberg](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/) -- [psychoacoustics/loudness-moore-glasberg-time](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg-time/) -- [psychoacoustics/loudness-zwicker](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-zwicker/) -- [psychoacoustics/psychoacoustic-annoyance](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/psychoacoustic-annoyance/) +- [psychoacoustics/moore-glasberg](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/moore-glasberg/) +- [psychoacoustics/moore-glasberg-time](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/moore-glasberg-time/) - [psychoacoustics/roughness-ecma](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/roughness-ecma/) - [psychoacoustics/sharpness](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/sharpness/) - [psychoacoustics/tonality](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/tonality/) - [psychoacoustics/tonality-ecma](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/tonality-ecma/) - [psychoacoustics/tone-audibility](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/tone-audibility/) +- [psychoacoustics/zwicker](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/zwicker/) - [rooms/crowd-noise](https://jmrplens.github.io/phonometry/reference/api/rooms/crowd-noise/) - [rooms/enclosed-space-absorption](https://jmrplens.github.io/phonometry/reference/api/rooms/enclosed-space-absorption/) - [rooms/image-source](https://jmrplens.github.io/phonometry/reference/api/rooms/image-source/) @@ -1315,7 +1315,7 @@ The Zwicker method (ISO 532-1:2017) is the reference: stationary and time-varyin - [Sound Quality Metrics](https://jmrplens.github.io/phonometry/guides/sound-quality/): the tonality, roughness and fluctuation strength built on the same ECMA-418-2 front-end. - [Theory](https://jmrplens.github.io/phonometry/reference/theory/perception/): the equations behind the loudness models. -- API reference: [`psychoacoustics.loudness_moore_glasberg`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/), [`psychoacoustics.loudness_moore_glasberg_time`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg-time/) and [`psychoacoustics.loudness_ecma`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-ecma/). +- API reference: [`psychoacoustics.loudness.moore_glasberg`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/moore-glasberg/), [`psychoacoustics.loudness.moore_glasberg_time`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/moore-glasberg-time/) and [`psychoacoustics.loudness.ecma`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/ecma/). ## References @@ -2240,7 +2240,7 @@ contour = aircraft.noise_contour(path, powers, distances, sel, lmax, x=gx, y=gy) | `phonometry.filters` | Octave and fractional-octave filter banks, frequency weightings and time weighting, parametric EQ, IEC 61260-1 and IEC 61672-1 class verification | | `phonometry.signals` | Levels (Leq, LAeq, percentiles), Welch and multitaper spectra, coherence, time-frequency, correlation, envelope, cepstrum, phase, synchronous averaging, test signals | | `phonometry.metrology` | Calibration, GUM uncertainty and Monte Carlo, data qualification (stationarity, trends, peak statistics), IEC 61043 intensity class | -| `phonometry.psychoacoustics` | Loudness (Zwicker, ECMA, Moore-Glasberg), sharpness, tonality, roughness, fluctuation strength, annoyance, tonal audibility | +| `phonometry.psychoacoustics` | Two families: `loudness` (ISO 532-1 Zwicker, ISO 532-2 and ISO 532-3 Moore-Glasberg, ECMA-418-2, ISO 226 equal-loudness contours) and `quality` (sharpness, roughness, fluctuation strength, tonality, tone audibility, annoyance), plus the ERB scale both measure on | | `phonometry.speech` | Speech Transmission Index (IEC 60268-16), Speech Intelligibility Index (ANSI S3.5), STOI and ESTOI | | `phonometry.hearing` | Audiometric thresholds (ISO 7029/389-7), noise-induced hearing loss (ISO 1999), occupational exposure (ISO 9612) | | `phonometry.emission` | Sound power (ISO 3740 family), sound intensity, vibration-based power | @@ -2272,6 +2272,7 @@ warns and delegates as well. | `phonometry.building.spanish_building_code` | `phonometry.building.regulation.spain` | 5.0 | | `phonometry.materials.porous_absorber` | `phonometry.materials.absorbers.porous` | 5.0 | | `phonometry.environmental` | `phonometry.environment` | 5.0 | +| `phonometry.psychoacoustics.loudness_zwicker` | `phonometry.psychoacoustics.loudness.zwicker` | 5.0 | | Name | Type | Description (Inputs) | Usage Snippet (Outputs) | | :--- | :--- | :--- | :--- | @@ -19351,7 +19352,7 @@ hearing threshold. - [Psychoacoustic annoyance and fluctuation strength](https://jmrplens.github.io/phonometry/guides/psychoacoustic-annoyance/): the Zwicker and Fastl model that consumes the percentile loudness $N_5$. - [Theory](https://jmrplens.github.io/phonometry/reference/theory/perception/): the equations behind the loudness models. -- API reference: [`psychoacoustics.loudness_zwicker`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-zwicker/) and [`psychoacoustics.loudness_contours`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-contours/). +- API reference: [`psychoacoustics.loudness.zwicker`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/zwicker/) and [`psychoacoustics.loudness.contours`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/contours/). --- @@ -40787,7 +40788,7 @@ other only as supporting evidence. - [Impulsive-sound prominence](https://jmrplens.github.io/phonometry/guides/impulse-prominence/): the NT ACOU 112 counterpart for impulsive (rather than tonal) character. - [Theory](https://jmrplens.github.io/phonometry/reference/theory/perception/): the critical-band model and criteria derivation. -- API reference: [`psychoacoustics.tonality`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/tonality/). +- API reference: [`psychoacoustics.quality.tonality`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/tonality/). ## References @@ -43151,8 +43152,8 @@ sample from the metrology core: | IEC 61672-1:2013 Table 5 | `lc_peak()` one-cycle/half-cycle peak responses, class 1 limits | `tests/signals/test_levels.py` | | IEC 61260-1:2014 Table 1 | Filter-bank class 1/2 acceptance limits via `verify_filter_class()` | `tests/filters/test_compliance.py` | | ISO 7196:1995 Table 2 | G weighting (infrasound) at every nominal response value, 0.25–315 Hz | `tests/filters/test_g_weighting.py` | -| ISO 226:2023 Table 1 and Annex B | Equal-loudness contours and loudness levels against the Annex B tables, hearing threshold against the Table 1 $T_f$ parameters | `tests/psychoacoustics/test_loudness_contours.py` | -| ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/psychoacoustics/test_tonality.py` | +| ISO 226:2023 Table 1 and Annex B | Equal-loudness contours and loudness levels against the Annex B tables, hearing threshold against the Table 1 $T_f$ parameters | `tests/psychoacoustics/loudness/test_contours.py` | +| ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/psychoacoustics/quality/test_tonality.py` | | ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/environment/assessment/test_rating.py` | | IEC 60942:2017 Table 2 | Calibrator short-term stability limits (frequency-dependent, class 1) in `sensitivity()` | `tests/metrology/test_calibration_validation.py` | diff --git a/llms.txt b/llms.txt index befbc543b..cfc22b298 100644 --- a/llms.txt +++ b/llms.txt @@ -334,20 +334,20 @@ The generated API reference, one page per module. Fetch these only when a specif - [power/sound-power-intensity](https://jmrplens.github.io/phonometry/reference/api/power/sound-power-intensity/) - [power/sound-power-reverberation](https://jmrplens.github.io/phonometry/reference/api/power/sound-power-reverberation/) - [power/vibration-sound-power](https://jmrplens.github.io/phonometry/reference/api/power/vibration-sound-power/) +- [psychoacoustics/annoyance](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/annoyance/) +- [psychoacoustics/contours](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/contours/) +- [psychoacoustics/ecma](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/ecma/) - [psychoacoustics/erb-scale](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/erb-scale/) - [psychoacoustics/fluctuation-strength](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/fluctuation-strength/) - [psychoacoustics/fluctuation-strength-ecma](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/fluctuation-strength-ecma/) -- [psychoacoustics/loudness-contours](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-contours/) -- [psychoacoustics/loudness-ecma](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-ecma/) -- [psychoacoustics/loudness-moore-glasberg](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/) -- [psychoacoustics/loudness-moore-glasberg-time](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg-time/) -- [psychoacoustics/loudness-zwicker](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-zwicker/) -- [psychoacoustics/psychoacoustic-annoyance](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/psychoacoustic-annoyance/) +- [psychoacoustics/moore-glasberg](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/moore-glasberg/) +- [psychoacoustics/moore-glasberg-time](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/moore-glasberg-time/) - [psychoacoustics/roughness-ecma](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/roughness-ecma/) - [psychoacoustics/sharpness](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/sharpness/) - [psychoacoustics/tonality](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/tonality/) - [psychoacoustics/tonality-ecma](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/tonality-ecma/) - [psychoacoustics/tone-audibility](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/tone-audibility/) +- [psychoacoustics/zwicker](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/zwicker/) - [rooms/crowd-noise](https://jmrplens.github.io/phonometry/reference/api/rooms/crowd-noise/) - [rooms/enclosed-space-absorption](https://jmrplens.github.io/phonometry/reference/api/rooms/enclosed-space-absorption/) - [rooms/image-source](https://jmrplens.github.io/phonometry/reference/api/rooms/image-source/) diff --git a/scripts/api_taxonomy.py b/scripts/api_taxonomy.py index 97190a2e2..a52b478c7 100644 --- a/scripts/api_taxonomy.py +++ b/scripts/api_taxonomy.py @@ -96,19 +96,19 @@ class Section: label_en="Psychoacoustics", label_es="Psicoacústica", modules=( - "phonometry.psychoacoustics.loudness_zwicker", - "phonometry.psychoacoustics.loudness_moore_glasberg", - "phonometry.psychoacoustics.loudness_moore_glasberg_time", - "phonometry.psychoacoustics.loudness_ecma", - "phonometry.psychoacoustics.loudness_contours", - "phonometry.psychoacoustics.sharpness", - "phonometry.psychoacoustics.roughness_ecma", - "phonometry.psychoacoustics.tonality", - "phonometry.psychoacoustics.tonality_ecma", - "phonometry.psychoacoustics.tone_audibility", - "phonometry.psychoacoustics.fluctuation_strength", - "phonometry.psychoacoustics.fluctuation_strength_ecma", - "phonometry.psychoacoustics.psychoacoustic_annoyance", + "phonometry.psychoacoustics.loudness.zwicker", + "phonometry.psychoacoustics.loudness.moore_glasberg", + "phonometry.psychoacoustics.loudness.moore_glasberg_time", + "phonometry.psychoacoustics.loudness.ecma", + "phonometry.psychoacoustics.loudness.contours", + "phonometry.psychoacoustics.quality.sharpness", + "phonometry.psychoacoustics.quality.roughness_ecma", + "phonometry.psychoacoustics.quality.tonality", + "phonometry.psychoacoustics.quality.tonality_ecma", + "phonometry.psychoacoustics.quality.tone_audibility", + "phonometry.psychoacoustics.quality.fluctuation_strength", + "phonometry.psychoacoustics.quality.fluctuation_strength_ecma", + "phonometry.psychoacoustics.quality.annoyance", "phonometry.psychoacoustics.erb_scale", ), ), diff --git a/scripts/conformance_report.py b/scripts/conformance_report.py index 0339640c0..2e91e737e 100644 --- a/scripts/conformance_report.py +++ b/scripts/conformance_report.py @@ -59,7 +59,7 @@ class_limits, verify_filter_class, ) -from phonometry.psychoacoustics.sharpness import reference_sound +from phonometry.psychoacoustics.quality.sharpness import reference_sound from phonometry.speech.sti import _sti_from_mtf @@ -4900,7 +4900,7 @@ def _chk_enclosed_space_rt() -> Outcome: @register(_TONES, "ECMA-418-1:2024 Clause 10 Formula (2)", "Critical band at 1 kHz (f1,c / f2,c / dfc)") def _chk_ecma418_1_critical_band() -> Outcome: - from phonometry.psychoacoustics.tonality import _critical_band + from phonometry.psychoacoustics.quality.tonality import _critical_band f1, f2, dfc = _critical_band(1000.0) # 0.05 Hz = half a unit in the last printed digit (the clause EXAMPLE @@ -4923,7 +4923,7 @@ def _chk_ecma418_1_critical_band() -> Outcome: @register(_TONES, "ECMA-418-1:2024 Clause 11.6 Formula (14)", "Proximity spacing dfprox at 150 / 850 Hz") def _chk_ecma418_1_proximity_spacing() -> Outcome: - from phonometry.psychoacoustics.tonality import _proximity_spacing + from phonometry.psychoacoustics.quality.tonality import _proximity_spacing v150 = float(_proximity_spacing(150.0)) v850 = float(_proximity_spacing(850.0)) diff --git a/scripts/generate_graphs.py b/scripts/generate_graphs.py index 8806c1cb0..4ea1e69d3 100644 --- a/scripts/generate_graphs.py +++ b/scripts/generate_graphs.py @@ -3592,7 +3592,10 @@ def generate_tonality_spectrum(output_dir: str) -> None: """Annotated spectrum for the tone-to-noise ratio method.""" print("Generating tonality_spectrum.png...") from phonometry import tone_to_noise_ratio - from phonometry.psychoacoustics.tonality import _averaged_spectrum, _critical_band + from phonometry.psychoacoustics.quality.tonality import ( + _averaged_spectrum, + _critical_band, + ) fs = 48000 rng = np.random.default_rng(21) @@ -4154,7 +4157,12 @@ def generate_impact_rating(output_dir: str) -> None: def generate_sharpness_weighting(output_dir: str) -> None: """DIN 45692 sharpness weighting g(z): DIN vs Aures vs von Bismarck.""" print("Generating sharpness_weighting.png...") - from phonometry.psychoacoustics.sharpness import _Z, _g_aures, _g_bismarck, _g_din + from phonometry.psychoacoustics.quality.sharpness import ( + _Z, + _g_aures, + _g_bismarck, + _g_din, + ) z = _Z # 0.1 .. 24 Bark, 0.1-Bark steps total_n = 4.0 # reference loudness for the Aures variant (sone) diff --git a/site/.pa11yci.json b/site/.pa11yci.json index 0c2977882..a77594cb2 100644 --- a/site/.pa11yci.json +++ b/site/.pa11yci.json @@ -37,7 +37,7 @@ "http://localhost:4321/phonometry/es/getting-started/", "http://localhost:4321/phonometry/es/guides/filter-banks/", "http://localhost:4321/phonometry/reference/api/signals/levels/", - "http://localhost:4321/phonometry/reference/api/psychoacoustics/loudness-zwicker/", + "http://localhost:4321/phonometry/reference/api/psychoacoustics/zwicker/", "http://localhost:4321/phonometry/guides/insulation-field/", "http://localhost:4321/phonometry/guides/loudness/", "http://localhost:4321/phonometry/es/reference/api/signals/levels/", diff --git a/site/public/llms/llms-psychoacoustics.txt b/site/public/llms/llms-psychoacoustics.txt index 39e7a9d02..c4eba07cb 100644 --- a/site/public/llms/llms-psychoacoustics.txt +++ b/site/public/llms/llms-psychoacoustics.txt @@ -281,7 +281,7 @@ hearing threshold. - [Psychoacoustic annoyance and fluctuation strength](https://jmrplens.github.io/phonometry/guides/psychoacoustic-annoyance/): the Zwicker and Fastl model that consumes the percentile loudness $N_5$. - [Theory](https://jmrplens.github.io/phonometry/reference/theory/perception/): the equations behind the loudness models. -- API reference: [`psychoacoustics.loudness_zwicker`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-zwicker/) and [`psychoacoustics.loudness_contours`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-contours/). +- API reference: [`psychoacoustics.loudness.zwicker`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/zwicker/) and [`psychoacoustics.loudness.contours`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/contours/). --- @@ -648,7 +648,7 @@ The Zwicker method (ISO 532-1:2017) is the reference: stationary and time-varyin - [Sound Quality Metrics](https://jmrplens.github.io/phonometry/guides/sound-quality/): the tonality, roughness and fluctuation strength built on the same ECMA-418-2 front-end. - [Theory](https://jmrplens.github.io/phonometry/reference/theory/perception/): the equations behind the loudness models. -- API reference: [`psychoacoustics.loudness_moore_glasberg`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/), [`psychoacoustics.loudness_moore_glasberg_time`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg-time/) and [`psychoacoustics.loudness_ecma`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/loudness-ecma/). +- API reference: [`psychoacoustics.loudness.moore_glasberg`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/moore-glasberg/), [`psychoacoustics.loudness.moore_glasberg_time`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/moore-glasberg-time/) and [`psychoacoustics.loudness.ecma`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/ecma/). ## References @@ -1262,7 +1262,7 @@ other only as supporting evidence. - [Impulsive-sound prominence](https://jmrplens.github.io/phonometry/guides/impulse-prominence/): the NT ACOU 112 counterpart for impulsive (rather than tonal) character. - [Theory](https://jmrplens.github.io/phonometry/reference/theory/perception/): the critical-band model and criteria derivation. -- API reference: [`psychoacoustics.tonality`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/tonality/). +- API reference: [`psychoacoustics.quality.tonality`](https://jmrplens.github.io/phonometry/reference/api/psychoacoustics/tonality/). ## References diff --git a/site/public/llms/llms-start.txt b/site/public/llms/llms-start.txt index 063e4b7e1..0433e03d3 100644 --- a/site/public/llms/llms-start.txt +++ b/site/public/llms/llms-start.txt @@ -297,8 +297,8 @@ sample from the metrology core: | IEC 61672-1:2013 Table 5 | `lc_peak()` one-cycle/half-cycle peak responses, class 1 limits | `tests/signals/test_levels.py` | | IEC 61260-1:2014 Table 1 | Filter-bank class 1/2 acceptance limits via `verify_filter_class()` | `tests/filters/test_compliance.py` | | ISO 7196:1995 Table 2 | G weighting (infrasound) at every nominal response value, 0.25–315 Hz | `tests/filters/test_g_weighting.py` | -| ISO 226:2023 Table 1 and Annex B | Equal-loudness contours and loudness levels against the Annex B tables, hearing threshold against the Table 1 $T_f$ parameters | `tests/psychoacoustics/test_loudness_contours.py` | -| ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/psychoacoustics/test_tonality.py` | +| ISO 226:2023 Table 1 and Annex B | Equal-loudness contours and loudness levels against the Annex B tables, hearing threshold against the Table 1 $T_f$ parameters | `tests/psychoacoustics/loudness/test_contours.py` | +| ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/psychoacoustics/quality/test_tonality.py` | | ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/environment/assessment/test_rating.py` | | IEC 60942:2017 Table 2 | Calibrator short-term stability limits (frequency-dependent, class 1) in `sensitivity()` | `tests/metrology/test_calibration_validation.py` | diff --git a/site/src/content/docs/es/guides/advanced-loudness.mdx b/site/src/content/docs/es/guides/advanced-loudness.mdx index 3815f02c6..5de06a82d 100644 --- a/site/src/content/docs/es/guides/advanced-loudness.mdx +++ b/site/src/content/docs/es/guides/advanced-loudness.mdx @@ -448,7 +448,7 @@ apartado. tonalidad, la aspereza y la intensidad de fluctuación construidas sobre el mismo front-end de ECMA-418-2. - [Teoría](/phonometry/es/reference/theory/perception/): las ecuaciones que sustentan los modelos de sonoridad. -- Referencia de la API: [`psychoacoustics.loudness_moore_glasberg`](/phonometry/es/reference/api/psychoacoustics/loudness-moore-glasberg/), [`psychoacoustics.loudness_moore_glasberg_time`](/phonometry/es/reference/api/psychoacoustics/loudness-moore-glasberg-time/) y [`psychoacoustics.loudness_ecma`](/phonometry/es/reference/api/psychoacoustics/loudness-ecma/). +- Referencia de la API: [`psychoacoustics.loudness.moore_glasberg`](/phonometry/es/reference/api/psychoacoustics/moore-glasberg/), [`psychoacoustics.loudness.moore_glasberg_time`](/phonometry/es/reference/api/psychoacoustics/moore-glasberg-time/) y [`psychoacoustics.loudness.ecma`](/phonometry/es/reference/api/psychoacoustics/ecma/). ## Respuestas rápidas diff --git a/site/src/content/docs/es/guides/loudness.mdx b/site/src/content/docs/es/guides/loudness.mdx index 2c36d01b3..b85aea050 100644 --- a/site/src/content/docs/es/guides/loudness.mdx +++ b/site/src/content/docs/es/guides/loudness.mdx @@ -322,7 +322,7 @@ espera una de las frecuencias de la Tabla 1. - [Molestia psicoacústica e intensidad de fluctuación](/phonometry/es/guides/psychoacoustic-annoyance/): el modelo de Zwicker y Fastl que consume la sonoridad percentil $N_5$. - [Teoría](/phonometry/es/reference/theory/perception/): las ecuaciones que sustentan los modelos de sonoridad. -- Referencia de la API: [`psychoacoustics.loudness_zwicker`](/phonometry/es/reference/api/psychoacoustics/loudness-zwicker/) y [`psychoacoustics.loudness_contours`](/phonometry/es/reference/api/psychoacoustics/loudness-contours/). +- Referencia de la API: [`psychoacoustics.loudness.zwicker`](/phonometry/es/reference/api/psychoacoustics/zwicker/) y [`psychoacoustics.loudness.contours`](/phonometry/es/reference/api/psychoacoustics/contours/). ## Respuestas rápidas diff --git a/site/src/content/docs/es/guides/psychoacoustic-annoyance.mdx b/site/src/content/docs/es/guides/psychoacoustic-annoyance.mdx index 1975dc786..b049d1d32 100644 --- a/site/src/content/docs/es/guides/psychoacoustic-annoyance.mdx +++ b/site/src/content/docs/es/guides/psychoacoustic-annoyance.mdx @@ -336,4 +336,4 @@ página: está en ## Véase también -- Referencia de la API: [`psychoacoustics.psychoacoustic_annoyance`](/phonometry/es/reference/api/psychoacoustics/psychoacoustic-annoyance/) y [`psychoacoustics.fluctuation_strength`](/phonometry/es/reference/api/psychoacoustics/fluctuation-strength/). +- Referencia de la API: [`psychoacoustics.quality.annoyance`](/phonometry/es/reference/api/psychoacoustics/annoyance/) y [`psychoacoustics.quality.fluctuation_strength`](/phonometry/es/reference/api/psychoacoustics/fluctuation-strength/). diff --git a/site/src/content/docs/es/guides/sound-quality.mdx b/site/src/content/docs/es/guides/sound-quality.mdx index 087135e9a..9047d0135 100644 --- a/site/src/content/docs/es/guides/sound-quality.mdx +++ b/site/src/content/docs/es/guides/sound-quality.mdx @@ -411,4 +411,4 @@ fluctuación de Fastl y Zwicker, en ## Véase también -- Referencia de la API: [`psychoacoustics.sharpness`](/phonometry/es/reference/api/psychoacoustics/sharpness/), [`psychoacoustics.tonality_ecma`](/phonometry/es/reference/api/psychoacoustics/tonality-ecma/), [`psychoacoustics.roughness_ecma`](/phonometry/es/reference/api/psychoacoustics/roughness-ecma/) y [`psychoacoustics.fluctuation_strength_ecma`](/phonometry/es/reference/api/psychoacoustics/fluctuation-strength-ecma/). +- Referencia de la API: [`psychoacoustics.quality.sharpness`](/phonometry/es/reference/api/psychoacoustics/sharpness/), [`psychoacoustics.quality.tonality_ecma`](/phonometry/es/reference/api/psychoacoustics/tonality-ecma/), [`psychoacoustics.quality.roughness_ecma`](/phonometry/es/reference/api/psychoacoustics/roughness-ecma/) y [`psychoacoustics.quality.fluctuation_strength_ecma`](/phonometry/es/reference/api/psychoacoustics/fluctuation-strength-ecma/). diff --git a/site/src/content/docs/es/guides/tone-audibility.mdx b/site/src/content/docs/es/guides/tone-audibility.mdx index ac8ccfe53..5fd4d84ca 100644 --- a/site/src/content/docs/es/guides/tone-audibility.mdx +++ b/site/src/content/docs/es/guides/tone-audibility.mdx @@ -440,4 +440,4 @@ calculado (niveles y frecuencias). ## Véase también -- Referencia de la API: [`psychoacoustics.tone_audibility`](/phonometry/es/reference/api/psychoacoustics/tone-audibility/). +- Referencia de la API: [`psychoacoustics.quality.tone_audibility`](/phonometry/es/reference/api/psychoacoustics/tone-audibility/). diff --git a/site/src/content/docs/es/guides/tone-prominence.mdx b/site/src/content/docs/es/guides/tone-prominence.mdx index 6755b7715..70dc3d486 100644 --- a/site/src/content/docs/es/guides/tone-prominence.mdx +++ b/site/src/content/docs/es/guides/tone-prominence.mdx @@ -275,5 +275,5 @@ están implementados aquí. la contraparte NT ACOU 112 para el carácter impulsivo (en lugar de tonal). - [Teoría](/phonometry/es/reference/theory/perception/): el modelo de banda crítica y la derivación de los criterios. -- Referencia de la API: [`psychoacoustics.tonality`](/phonometry/es/reference/api/psychoacoustics/tonality/). +- Referencia de la API: [`psychoacoustics.quality.tonality`](/phonometry/es/reference/api/psychoacoustics/tonality/). diff --git a/site/src/content/docs/es/reference/errata.md b/site/src/content/docs/es/reference/errata.md index 3246e0439..a7ec735de 100644 --- a/site/src/content/docs/es/reference/errata.md +++ b/site/src/content/docs/es/reference/errata.md @@ -808,7 +808,7 @@ which is the check that enforces the rule; see printed p. 20, 220 dpi (Figure 6 and its 11220 tick). - **Library behaviour:** uses the internally consistent 89,1 Hz to 11 200 Hz range (upper end exclusive per the formulas), with a code note - in [`tonality.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/tonality.py). + in [`tonality.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/quality/tonality.py). - **Status:** unreported. ## ECMA-418-1:2024 (3rd edition), Formula (21) (repeated constant term) @@ -830,7 +830,7 @@ which is the check that enforces the rule; see Formula (22) as the consistent control. Render: `plan/ECMA-418-1_3rd_edition_december_2024.pdf`, PDF page 25, printed p. 17, 300 dpi. - **Library behaviour:** implements the $C_{L,1}$ reading, which is the only one that returns a usable band edge, with a code note in - [`tonality.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/tonality.py). + [`tonality.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/quality/tonality.py). - **Status:** unreported. ## ECMA-418-1:2024 (3rd edition), clause 11.3 (unresolved field references) @@ -869,7 +869,7 @@ which is the check that enforces the rule; see reading reproduces the Clause 7 roughness calibration (1 asper) to 0,9999. - **Library behaviour:** implements the flush-to-end reading with a code - note in [`roughness_ecma.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/roughness_ecma.py). + note in [`roughness_ecma.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/quality/roughness_ecma.py). - **Status:** unreported. ## ECMA-418-2:2025 (4th edition), clause 9.1.4, Formula (127) (HSA kernel phase) @@ -987,7 +987,7 @@ which is the check that enforces the rule; see printed p. 14, 400 dpi. - **Library behaviour:** follows the DIN/sqrt(2) reading (it matches the only executable reference), with the choice recorded in - [`tone_audibility.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/tone_audibility.py). + [`tone_audibility.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/quality/tone_audibility.py). - **Status:** unreported. ## DIN 45681:2005-03, Anhang I, Tabelle I.6, row "6 FG" @@ -1417,7 +1417,7 @@ which is the check that enforces the rule; see - **Library behaviour:** implements $0{,}76 \cdot 10^{-3}$ with a note at the formula; the carrier-frequency sweep test would catch a regression to the printed - value ([`fluctuation_strength.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/fluctuation_strength.py)). + value ([`fluctuation_strength.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/quality/fluctuation_strength.py)). - **Status:** unreported (conference paper rather than a standard). ## Medwin & Clay, Fundamentals of Acoustical Oceanography (1998), Eq. (3.4.30) (boric-acid coefficient) diff --git a/site/src/content/docs/es/reference/why-phonometry.mdx b/site/src/content/docs/es/reference/why-phonometry.mdx index 2cdbe8ee9..2b5b77bf3 100644 --- a/site/src/content/docs/es/reference/why-phonometry.mdx +++ b/site/src/content/docs/es/reference/why-phonometry.mdx @@ -105,8 +105,8 @@ metrología: | IEC 61672-1:2013 Tabla 5 | Respuestas de pico de un ciclo/medio ciclo de `lc_peak()`, límites de clase 1 | `tests/signals/test_levels.py` | | IEC 61260-1:2014 Tabla 1 | Límites de aceptación de clase 1/2 del banco de filtros mediante `verify_filter_class()` | `tests/filters/test_compliance.py` | | ISO 7196:1995 Tabla 2 | Ponderación G (infrasonidos) en todos los valores nominales de respuesta, 0,25–315 Hz | `tests/filters/test_g_weighting.py` | -| ISO 226:2023 Tabla 1 y Anexo B | Líneas isofónicas y niveles de sonoridad frente a las tablas del Anexo B, umbral de audición frente a los parámetros $T_f$ de la Tabla 1 | `tests/psychoacoustics/test_loudness_contours.py` | -| ECMA-418-1:2024 | Prominencia tonal TNR/PR: anchos de banda críticos, separación de proximidad y criterios de prominencia frente a los ejemplos resueltos de los apartados 10–12 | `tests/psychoacoustics/test_tonality.py` | +| ISO 226:2023 Tabla 1 y Anexo B | Líneas isofónicas y niveles de sonoridad frente a las tablas del Anexo B, umbral de audición frente a los parámetros $T_f$ de la Tabla 1 | `tests/psychoacoustics/loudness/test_contours.py` | +| ECMA-418-1:2024 | Prominencia tonal TNR/PR: anchos de banda críticos, separación de proximidad y criterios de prominencia frente a los ejemplos resueltos de los apartados 10–12 | `tests/psychoacoustics/quality/test_tonality.py` | | ISO 1996-1:2016 | `lden()`, `ldn()` y `composite_rating_level()` frente a valores de las fórmulas calculados a mano | `tests/environment/assessment/test_rating.py` | | IEC 60942:2017 Tabla 2 | Límites de estabilidad a corto plazo del calibrador (dependientes de la frecuencia, clase 1) en `sensitivity()` | `tests/metrology/test_calibration_validation.py` | diff --git a/site/src/content/docs/guides/advanced-loudness.mdx b/site/src/content/docs/guides/advanced-loudness.mdx index f418467ec..1036f85a9 100644 --- a/site/src/content/docs/guides/advanced-loudness.mdx +++ b/site/src/content/docs/guides/advanced-loudness.mdx @@ -439,7 +439,7 @@ clause-by-clause conformance matters. - [Sound Quality Metrics](/phonometry/guides/sound-quality/): the tonality, roughness and fluctuation strength built on the same ECMA-418-2 front-end. - [Theory](/phonometry/reference/theory/perception/): the equations behind the loudness models. -- API reference: [`psychoacoustics.loudness_moore_glasberg`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/), [`psychoacoustics.loudness_moore_glasberg_time`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg-time/) and [`psychoacoustics.loudness_ecma`](/phonometry/reference/api/psychoacoustics/loudness-ecma/). +- API reference: [`psychoacoustics.loudness.moore_glasberg`](/phonometry/reference/api/psychoacoustics/moore-glasberg/), [`psychoacoustics.loudness.moore_glasberg_time`](/phonometry/reference/api/psychoacoustics/moore-glasberg-time/) and [`psychoacoustics.loudness.ecma`](/phonometry/reference/api/psychoacoustics/ecma/). ## Quick answers diff --git a/site/src/content/docs/guides/loudness.mdx b/site/src/content/docs/guides/loudness.mdx index 2587c02fd..8675c244f 100644 --- a/site/src/content/docs/guides/loudness.mdx +++ b/site/src/content/docs/guides/loudness.mdx @@ -307,7 +307,7 @@ expects one of the Table 1 frequencies. - [Psychoacoustic annoyance and fluctuation strength](/phonometry/guides/psychoacoustic-annoyance/): the Zwicker and Fastl model that consumes the percentile loudness $N_5$. - [Theory](/phonometry/reference/theory/perception/): the equations behind the loudness models. -- API reference: [`psychoacoustics.loudness_zwicker`](/phonometry/reference/api/psychoacoustics/loudness-zwicker/) and [`psychoacoustics.loudness_contours`](/phonometry/reference/api/psychoacoustics/loudness-contours/). +- API reference: [`psychoacoustics.loudness.zwicker`](/phonometry/reference/api/psychoacoustics/zwicker/) and [`psychoacoustics.loudness.contours`](/phonometry/reference/api/psychoacoustics/contours/). ## Quick answers diff --git a/site/src/content/docs/guides/psychoacoustic-annoyance.mdx b/site/src/content/docs/guides/psychoacoustic-annoyance.mdx index d5536b689..1fd631e99 100644 --- a/site/src/content/docs/guides/psychoacoustic-annoyance.mdx +++ b/site/src/content/docs/guides/psychoacoustic-annoyance.mdx @@ -326,4 +326,4 @@ on this page: it lives in ## See also -- API reference: [`psychoacoustics.psychoacoustic_annoyance`](/phonometry/reference/api/psychoacoustics/psychoacoustic-annoyance/) and [`psychoacoustics.fluctuation_strength`](/phonometry/reference/api/psychoacoustics/fluctuation-strength/). +- API reference: [`psychoacoustics.quality.annoyance`](/phonometry/reference/api/psychoacoustics/annoyance/) and [`psychoacoustics.quality.fluctuation_strength`](/phonometry/reference/api/psychoacoustics/fluctuation-strength/). diff --git a/site/src/content/docs/guides/sound-quality.mdx b/site/src/content/docs/guides/sound-quality.mdx index bb713d18b..c16d86079 100644 --- a/site/src/content/docs/guides/sound-quality.mdx +++ b/site/src/content/docs/guides/sound-quality.mdx @@ -398,4 +398,4 @@ fluctuation-strength models in ## See also -- API reference: [`psychoacoustics.sharpness`](/phonometry/reference/api/psychoacoustics/sharpness/), [`psychoacoustics.tonality_ecma`](/phonometry/reference/api/psychoacoustics/tonality-ecma/), [`psychoacoustics.roughness_ecma`](/phonometry/reference/api/psychoacoustics/roughness-ecma/) and [`psychoacoustics.fluctuation_strength_ecma`](/phonometry/reference/api/psychoacoustics/fluctuation-strength-ecma/). +- API reference: [`psychoacoustics.quality.sharpness`](/phonometry/reference/api/psychoacoustics/sharpness/), [`psychoacoustics.quality.tonality_ecma`](/phonometry/reference/api/psychoacoustics/tonality-ecma/), [`psychoacoustics.quality.roughness_ecma`](/phonometry/reference/api/psychoacoustics/roughness-ecma/) and [`psychoacoustics.quality.fluctuation_strength_ecma`](/phonometry/reference/api/psychoacoustics/fluctuation-strength-ecma/). diff --git a/site/src/content/docs/guides/tone-audibility.mdx b/site/src/content/docs/guides/tone-audibility.mdx index 05d1ea0d5..8b7f19901 100644 --- a/site/src/content/docs/guides/tone-audibility.mdx +++ b/site/src/content/docs/guides/tone-audibility.mdx @@ -423,4 +423,4 @@ takes an already-computed spectrum (levels and frequencies). ## See also -- API reference: [`psychoacoustics.tone_audibility`](/phonometry/reference/api/psychoacoustics/tone-audibility/). +- API reference: [`psychoacoustics.quality.tone_audibility`](/phonometry/reference/api/psychoacoustics/tone-audibility/). diff --git a/site/src/content/docs/guides/tone-prominence.mdx b/site/src/content/docs/guides/tone-prominence.mdx index 518dae4f8..8e5936b3a 100644 --- a/site/src/content/docs/guides/tone-prominence.mdx +++ b/site/src/content/docs/guides/tone-prominence.mdx @@ -265,5 +265,5 @@ for context) are not implemented here. - [Impulsive-sound prominence](/phonometry/guides/impulse-prominence/): the NT ACOU 112 counterpart for impulsive (rather than tonal) character. - [Theory](/phonometry/reference/theory/perception/): the critical-band model and criteria derivation. -- API reference: [`psychoacoustics.tonality`](/phonometry/reference/api/psychoacoustics/tonality/). +- API reference: [`psychoacoustics.quality.tonality`](/phonometry/reference/api/psychoacoustics/tonality/). diff --git a/site/src/content/docs/reference/api/index.md b/site/src/content/docs/reference/api/index.md index 6e1e644e8..3a014fcb7 100644 --- a/site/src/content/docs/reference/api/index.md +++ b/site/src/content/docs/reference/api/index.md @@ -61,19 +61,19 @@ La referencia de la API se genera a partir de los docstrings del código (en ing | Module | Summary | | :--- | :--- | -| [`psychoacoustics.loudness_zwicker`](/phonometry/reference/api/psychoacoustics/loudness-zwicker/) | Zwicker loudness for stationary and time-varying sounds per ISO 532-1:2017. | -| [`psychoacoustics.loudness_moore_glasberg`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/) | Stationary loudness per ISO 532-2:2017 (Moore-Glasberg method). | -| [`psychoacoustics.loudness_moore_glasberg_time`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg-time/) | Time-varying loudness per ISO 532-3:2023 (Moore-Glasberg-Schlittenlacher). | -| [`psychoacoustics.loudness_ecma`](/phonometry/reference/api/psychoacoustics/loudness-ecma/) | Psychoacoustic loudness per ECMA-418-2:2025 (4th ed., Sottek Hearing Model). | -| [`psychoacoustics.loudness_contours`](/phonometry/reference/api/psychoacoustics/loudness-contours/) | Normal equal-loudness-level contours per ISO 226:2023. | -| [`psychoacoustics.sharpness`](/phonometry/reference/api/psychoacoustics/sharpness/) | Sharpness per DIN 45692:2009-08. | -| [`psychoacoustics.roughness_ecma`](/phonometry/reference/api/psychoacoustics/roughness-ecma/) | Psychoacoustic roughness per ECMA-418-2:2025 (4th ed., Sottek Hearing Model). | -| [`psychoacoustics.tonality`](/phonometry/reference/api/psychoacoustics/tonality/) | Prominent discrete tone assessment per ECMA-418-1:2024 (3rd edition). | -| [`psychoacoustics.tonality_ecma`](/phonometry/reference/api/psychoacoustics/tonality-ecma/) | Psychoacoustic tonality per ECMA-418-2:2025 (4th ed., Sottek Hearing Model). | -| [`psychoacoustics.tone_audibility`](/phonometry/reference/api/psychoacoustics/tone-audibility/) | Objective audibility of tones in noise -- engineering method (ISO/PAS 20065:2016). | -| [`psychoacoustics.fluctuation_strength`](/phonometry/reference/api/psychoacoustics/fluctuation-strength/) | Fluctuation strength after Fastl & Zwicker / Osses et al. | -| [`psychoacoustics.fluctuation_strength_ecma`](/phonometry/reference/api/psychoacoustics/fluctuation-strength-ecma/) | Psychoacoustic fluctuation strength per ECMA-418-2:2025 (4th ed., Clause 9). | -| [`psychoacoustics.psychoacoustic_annoyance`](/phonometry/reference/api/psychoacoustics/psychoacoustic-annoyance/) | Psychoacoustic annoyance (PA) after Fastl & Zwicker. | +| [`psychoacoustics.loudness.zwicker`](/phonometry/reference/api/psychoacoustics/zwicker/) | Zwicker loudness for stationary and time-varying sounds per ISO 532-1:2017. | +| [`psychoacoustics.loudness.moore_glasberg`](/phonometry/reference/api/psychoacoustics/moore-glasberg/) | Stationary loudness per ISO 532-2:2017 (Moore-Glasberg method). | +| [`psychoacoustics.loudness.moore_glasberg_time`](/phonometry/reference/api/psychoacoustics/moore-glasberg-time/) | Time-varying loudness per ISO 532-3:2023 (Moore-Glasberg-Schlittenlacher). | +| [`psychoacoustics.loudness.ecma`](/phonometry/reference/api/psychoacoustics/ecma/) | Psychoacoustic loudness per ECMA-418-2:2025 (4th ed., Sottek Hearing Model). | +| [`psychoacoustics.loudness.contours`](/phonometry/reference/api/psychoacoustics/contours/) | Normal equal-loudness-level contours per ISO 226:2023. | +| [`psychoacoustics.quality.sharpness`](/phonometry/reference/api/psychoacoustics/sharpness/) | Sharpness per DIN 45692:2009-08. | +| [`psychoacoustics.quality.roughness_ecma`](/phonometry/reference/api/psychoacoustics/roughness-ecma/) | Psychoacoustic roughness per ECMA-418-2:2025 (4th ed., Sottek Hearing Model). | +| [`psychoacoustics.quality.tonality`](/phonometry/reference/api/psychoacoustics/tonality/) | Prominent discrete tone assessment per ECMA-418-1:2024 (3rd edition). | +| [`psychoacoustics.quality.tonality_ecma`](/phonometry/reference/api/psychoacoustics/tonality-ecma/) | Psychoacoustic tonality per ECMA-418-2:2025 (4th ed., Sottek Hearing Model). | +| [`psychoacoustics.quality.tone_audibility`](/phonometry/reference/api/psychoacoustics/tone-audibility/) | Objective audibility of tones in noise -- engineering method (ISO/PAS 20065:2016). | +| [`psychoacoustics.quality.fluctuation_strength`](/phonometry/reference/api/psychoacoustics/fluctuation-strength/) | Fluctuation strength after Fastl & Zwicker / Osses et al. | +| [`psychoacoustics.quality.fluctuation_strength_ecma`](/phonometry/reference/api/psychoacoustics/fluctuation-strength-ecma/) | Psychoacoustic fluctuation strength per ECMA-418-2:2025 (4th ed., Clause 9). | +| [`psychoacoustics.quality.annoyance`](/phonometry/reference/api/psychoacoustics/annoyance/) | Psychoacoustic annoyance (PA) after Fastl & Zwicker. | | [`psychoacoustics.erb_scale`](/phonometry/reference/api/psychoacoustics/erb-scale/) | The ERB_N scale: auditory-filter bandwidth and the Cam frequency scale. | ## Speech diff --git a/site/src/content/docs/reference/api/psychoacoustics/psychoacoustic-annoyance.md b/site/src/content/docs/reference/api/psychoacoustics/annoyance.md similarity index 88% rename from site/src/content/docs/reference/api/psychoacoustics/psychoacoustic-annoyance.md rename to site/src/content/docs/reference/api/psychoacoustics/annoyance.md index e6ee3a1fd..0c52ab43d 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/psychoacoustic-annoyance.md +++ b/site/src/content/docs/reference/api/psychoacoustics/annoyance.md @@ -1,8 +1,8 @@ --- -title: "psychoacoustics.psychoacoustic_annoyance" +title: "psychoacoustics.quality.annoyance" description: "Psychoacoustic annoyance (PA) after Fastl & Zwicker." sidebar: - label: "psychoacoustic_annoyance" + label: "annoyance" --- Psychoacoustic annoyance (PA) after Fastl & Zwicker. @@ -35,14 +35,14 @@ no ISO standard for PA; the formula is exact, verified against a hand-computed worked tuple, and matches the combination implemented by the open SQAT reference implementation. -[`psychoacoustic_annoyance`](/phonometry/reference/api/psychoacoustics/psychoacoustic-annoyance/#psychoacoustic_annoyance) evaluates the model from the four quantities -directly. [`psychoacoustic_annoyance_from_signal`](/phonometry/reference/api/psychoacoustics/psychoacoustic-annoyance/#psychoacoustic_annoyance_from_signal) is a convenience that +[`psychoacoustic_annoyance`](/phonometry/reference/api/psychoacoustics/annoyance/#psychoacoustic_annoyance) evaluates the model from the four quantities +directly. [`psychoacoustic_annoyance_from_signal`](/phonometry/reference/api/psychoacoustics/annoyance/#psychoacoustic_annoyance_from_signal) is a convenience that derives them from a calibrated pressure signal using the library's existing models -- N5/S from ISO 532-1 Zwicker loudness and DIN 45692 sharpness, R from ECMA-418-2 roughness and F from [`fluctuation_strength`](/phonometry/reference/api/psychoacoustics/fluctuation-strength/#fluctuation_strength). That composite mixes model families (Zwicker N5/S, Sottek R, Osses F); the original PA model was calibrated with Zwicker-family sensations, so the signal convenience is an engineering estimate, while -[`psychoacoustic_annoyance`](/phonometry/reference/api/psychoacoustics/psychoacoustic-annoyance/#psychoacoustic_annoyance) is the exact model. +[`psychoacoustic_annoyance`](/phonometry/reference/api/psychoacoustics/annoyance/#psychoacoustic_annoyance) is the exact model. > Auto-generated from the source docstrings by `scripts/generate_api_docs.py` (`make api-docs`). Do not edit by hand. @@ -68,12 +68,12 @@ $S \le 1.75$ acum. | Name | Description | | :--- | :--- | -| `n5` | Percentile loudness `N5`, in sone (the loudness exceeded 5 % of the time; [`n5`](/phonometry/reference/api/psychoacoustics/loudness-zwicker/#zwickerloudness)). | +| `n5` | Percentile loudness `N5`, in sone (the loudness exceeded 5 % of the time; [`n5`](/phonometry/reference/api/psychoacoustics/zwicker/#zwickerloudness)). | | `sharpness` | Sharpness `S`, in acum (DIN 45692). | | `fluctuation_strength` | Fluctuation strength `F`, in vacil. | | `roughness` | Roughness `R`, in asper (ECMA-418-2). | -**Returns:** A [`PsychoacousticAnnoyanceResult`](/phonometry/reference/api/psychoacoustics/psychoacoustic-annoyance/#psychoacousticannoyanceresult). +**Returns:** A [`PsychoacousticAnnoyanceResult`](/phonometry/reference/api/psychoacoustics/annoyance/#psychoacousticannoyanceresult). **Raises** @@ -96,7 +96,7 @@ psychoacoustic_annoyance_from_signal( Psychoacoustic annoyance from a calibrated pressure signal (convenience). Derives the four sensations from the library's models and combines them -with [`psychoacoustic_annoyance`](/phonometry/reference/api/psychoacoustics/psychoacoustic-annoyance/#psychoacoustic_annoyance): `N5` from the ISO 532-1 Zwicker +with [`psychoacoustic_annoyance`](/phonometry/reference/api/psychoacoustics/annoyance/#psychoacoustic_annoyance): `N5` from the ISO 532-1 Zwicker time-varying loudness, `S` from DIN 45692 sharpness, `R` from ECMA-418-2 roughness and `F` from [`fluctuation_strength`](/phonometry/reference/api/psychoacoustics/fluctuation-strength/#fluctuation_strength). @@ -106,7 +106,7 @@ This composite mixes model families (Zwicker `N5`/`S`, Sottek `R`, Osses `F`); the PA model was calibrated with Zwicker-family sensations, so treat the signal convenience as an engineering estimate. For exact, reproducible results pass the four quantities to -[`psychoacoustic_annoyance`](/phonometry/reference/api/psychoacoustics/psychoacoustic-annoyance/#psychoacoustic_annoyance) directly. +[`psychoacoustic_annoyance`](/phonometry/reference/api/psychoacoustics/annoyance/#psychoacoustic_annoyance) directly. `field` selects the sound field for the loudness, sharpness and roughness front-ends; the fluctuation strength `F` is always computed @@ -123,7 +123,7 @@ variant (see [`fluctuation_strength`](/phonometry/reference/api/psychoacoustics/ | `field` | `'free'` (default) or `'diffuse'` sound field for the loudness/sharpness/roughness front-ends (`F` is always free-field). | | `calibration_factor` | Digital-units-to-Pa factor applied to `x`. | -**Returns:** A [`PsychoacousticAnnoyanceResult`](/phonometry/reference/api/psychoacoustics/psychoacoustic-annoyance/#psychoacousticannoyanceresult). +**Returns:** A [`PsychoacousticAnnoyanceResult`](/phonometry/reference/api/psychoacoustics/annoyance/#psychoacousticannoyanceresult). ## PsychoacousticAnnoyanceResult diff --git a/site/src/content/docs/reference/api/psychoacoustics/loudness-contours.md b/site/src/content/docs/reference/api/psychoacoustics/contours.md similarity index 92% rename from site/src/content/docs/reference/api/psychoacoustics/loudness-contours.md rename to site/src/content/docs/reference/api/psychoacoustics/contours.md index 890aa1c71..956a2b2c2 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/loudness-contours.md +++ b/site/src/content/docs/reference/api/psychoacoustics/contours.md @@ -1,8 +1,8 @@ --- -title: "psychoacoustics.loudness_contours" +title: "psychoacoustics.loudness.contours" description: "Normal equal-loudness-level contours per ISO 226:2023." sidebar: - label: "loudness_contours" + label: "contours" --- Normal equal-loudness-level contours per ISO 226:2023. @@ -48,9 +48,9 @@ equal_loudness_contours( Build the ISO 226:2023 equal-loudness-level contour family. -Evaluates [`equal_loudness_contour`](/phonometry/reference/api/psychoacoustics/loudness-contours/#equal_loudness_contour) at each level in `phons` and -[`hearing_threshold`](/phonometry/reference/api/psychoacoustics/loudness-contours/#hearing_threshold), and bundles them into an -[`EqualLoudnessContours`](/phonometry/reference/api/psychoacoustics/loudness-contours/#equalloudnesscontours) result that exposes `.plot()`. The maths +Evaluates [`equal_loudness_contour`](/phonometry/reference/api/psychoacoustics/contours/#equal_loudness_contour) at each level in `phons` and +[`hearing_threshold`](/phonometry/reference/api/psychoacoustics/contours/#hearing_threshold), and bundles them into an +[`EqualLoudnessContours`](/phonometry/reference/api/psychoacoustics/contours/#equalloudnesscontours) result that exposes `.plot()`. The maths is unchanged; this is a thin, plottable wrapper around the existing functions. @@ -61,7 +61,7 @@ functions. | `phons` | Loudness levels of the contours, in phon; each must be in the 20 phon to 90 phon validity range of Formula (1). Defaults to the classic family from 20 phon to 90 phon in 10 phon steps. | | `frequencies` | Frequency grid in Hz; `None` (default) uses the 29 preferred third-octave frequencies of Table 1. Any value given must be one of those preferred frequencies (the standard specifies no interpolation between them). | -**Returns:** An [`EqualLoudnessContours`](/phonometry/reference/api/psychoacoustics/loudness-contours/#equalloudnesscontours). +**Returns:** An [`EqualLoudnessContours`](/phonometry/reference/api/psychoacoustics/contours/#equalloudnesscontours). ## EqualLoudnessContours diff --git a/site/src/content/docs/reference/api/psychoacoustics/loudness-ecma.md b/site/src/content/docs/reference/api/psychoacoustics/ecma.md similarity index 94% rename from site/src/content/docs/reference/api/psychoacoustics/loudness-ecma.md rename to site/src/content/docs/reference/api/psychoacoustics/ecma.md index d544773dc..fdc24d8fd 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/loudness-ecma.md +++ b/site/src/content/docs/reference/api/psychoacoustics/ecma.md @@ -1,8 +1,8 @@ --- -title: "psychoacoustics.loudness_ecma" +title: "psychoacoustics.loudness.ecma" description: "Psychoacoustic loudness per ECMA-418-2:2025 (4th ed., Sottek Hearing Model)." sidebar: - label: "loudness_ecma" + label: "ecma" --- Psychoacoustic loudness per ECMA-418-2:2025 (4th ed., Sottek Hearing Model). @@ -109,7 +109,7 @@ Psychoacoustic loudness per ECMA-418-2:2025 (Sottek Hearing Model). | `fs` | Sampling rate in Hz. Signals not at 48 kHz are resampled (Clause 5.1.1). | | `field` | `"free"` (default) or `"diffuse"` sound field, selecting the outer/middle-ear filter of Clause 5.1.3. | -**Returns:** An [`EcmaLoudness`](/phonometry/reference/api/psychoacoustics/loudness-ecma/#ecmaloudness) with the single loudness value N (Formula 117), the average specific loudness N'(z) (Formula 115) and the time-dependent loudness N(l) (Formula 116). +**Returns:** An [`EcmaLoudness`](/phonometry/reference/api/psychoacoustics/ecma/#ecmaloudness) with the single loudness value N (Formula 117), the average specific loudness N'(z) (Formula 115) and the time-dependent loudness N(l) (Formula 116). The 1 kHz / 40 dB SPL sinusoid defines 1 sone_HMS via the calibration constant of Formula (23); with the full Clause 6.2.3 averaging this diff --git a/site/src/content/docs/reference/api/psychoacoustics/erb-scale.md b/site/src/content/docs/reference/api/psychoacoustics/erb-scale.md index b7201a96f..d40d629fb 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/erb-scale.md +++ b/site/src/content/docs/reference/api/psychoacoustics/erb-scale.md @@ -36,7 +36,7 @@ flattens out but direct measurements show `ERB_N` continuing to shrink. This module states the constants to the precision used by the ISO 532-2 implementation of the Moore-Glasberg loudness model -([`phonometry.psychoacoustics.loudness_moore_glasberg`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/)), which shares +([`phonometry.psychoacoustics.loudness.moore_glasberg`](/phonometry/reference/api/psychoacoustics/moore-glasberg/)), which shares them: $$ diff --git a/site/src/content/docs/reference/api/psychoacoustics/fluctuation-strength-ecma.md b/site/src/content/docs/reference/api/psychoacoustics/fluctuation-strength-ecma.md index c21d18304..660980fb5 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/fluctuation-strength-ecma.md +++ b/site/src/content/docs/reference/api/psychoacoustics/fluctuation-strength-ecma.md @@ -1,5 +1,5 @@ --- -title: "psychoacoustics.fluctuation_strength_ecma" +title: "psychoacoustics.quality.fluctuation_strength_ecma" description: "Psychoacoustic fluctuation strength per ECMA-418-2:2025 (4th ed., Clause 9)." sidebar: label: "fluctuation_strength_ecma" @@ -11,9 +11,10 @@ Clean-room implementation of the fluctuation-strength signal chain of ECMA-418-2:2025 (Clause 9, Sottek Hearing Model). The shared auditory front-end (Clause 5: outer/middle-ear filter, 53-band auditory filter bank, compressive nonlinearity of Formulae 23-25) is reused from -[`.loudness_ecma`](/phonometry/reference/api/psychoacoustics/loudness-ecma/#loudness_ecma); this module adds the fluctuation-strength chain, which -mirrors the roughness chain of Clause 7 but replaces the DFT-based envelope -analysis with High-resolution Spectral Analysis (HSA): +[`phonometry.psychoacoustics.loudness.ecma`](/phonometry/reference/api/psychoacoustics/ecma/); this module adds the +fluctuation-strength chain, which mirrors the roughness chain of Clause 7 but +replaces the DFT-based envelope analysis with High-resolution Spectral +Analysis (HSA): * fluctuation-strength zero-padding (Clause 5.1.2.2) and segmentation (Clause 5.1.5.2) with the fixed block/hop $s_b = 65536$ / diff --git a/site/src/content/docs/reference/api/psychoacoustics/fluctuation-strength.md b/site/src/content/docs/reference/api/psychoacoustics/fluctuation-strength.md index 04ade52b6..e0ba5bd54 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/fluctuation-strength.md +++ b/site/src/content/docs/reference/api/psychoacoustics/fluctuation-strength.md @@ -1,5 +1,5 @@ --- -title: "psychoacoustics.fluctuation_strength" +title: "psychoacoustics.quality.fluctuation_strength" description: "Fluctuation strength after Fastl & Zwicker / Osses et al." sidebar: label: "fluctuation_strength" diff --git a/site/src/content/docs/reference/api/psychoacoustics/loudness-moore-glasberg-time.md b/site/src/content/docs/reference/api/psychoacoustics/moore-glasberg-time.md similarity index 97% rename from site/src/content/docs/reference/api/psychoacoustics/loudness-moore-glasberg-time.md rename to site/src/content/docs/reference/api/psychoacoustics/moore-glasberg-time.md index 04d2e431e..9b9ea792e 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/loudness-moore-glasberg-time.md +++ b/site/src/content/docs/reference/api/psychoacoustics/moore-glasberg-time.md @@ -1,8 +1,8 @@ --- -title: "psychoacoustics.loudness_moore_glasberg_time" +title: "psychoacoustics.loudness.moore_glasberg_time" description: "Time-varying loudness per ISO 532-3:2023 (Moore-Glasberg-Schlittenlacher)." sidebar: - label: "loudness_moore_glasberg_time" + label: "moore_glasberg_time" --- Time-varying loudness per ISO 532-3:2023 (Moore-Glasberg-Schlittenlacher). @@ -93,7 +93,7 @@ the instantaneous sound pressure in pascals. | `presentation` | `"binaural"`/`"diotic"` (default) or `"monaural"`. | | `percentiles` | Fractions (percent) for which the exceeded long-term loudness is reported. | -**Returns:** A [`MooreGlasbergTimeVaryingLoudness`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg-time/#mooreglasbergtimevaryingloudness). +**Returns:** A [`MooreGlasbergTimeVaryingLoudness`](/phonometry/reference/api/psychoacoustics/moore-glasberg-time/#mooreglasbergtimevaryingloudness). A steady 1 kHz tone at 40 dB SPL, binaural, free field, yields a peak long-term loudness of 1.000 sone (40 phon) by definition of the sone. diff --git a/site/src/content/docs/reference/api/psychoacoustics/loudness-moore-glasberg.md b/site/src/content/docs/reference/api/psychoacoustics/moore-glasberg.md similarity index 88% rename from site/src/content/docs/reference/api/psychoacoustics/loudness-moore-glasberg.md rename to site/src/content/docs/reference/api/psychoacoustics/moore-glasberg.md index ee8a9d538..e1eb36e8c 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/loudness-moore-glasberg.md +++ b/site/src/content/docs/reference/api/psychoacoustics/moore-glasberg.md @@ -1,8 +1,8 @@ --- -title: "psychoacoustics.loudness_moore_glasberg" +title: "psychoacoustics.loudness.moore_glasberg" description: "Stationary loudness per ISO 532-2:2017 (Moore-Glasberg method)." sidebar: - label: "loudness_moore_glasberg" + label: "moore_glasberg" --- Stationary loudness per ISO 532-2:2017 (Moore-Glasberg method). @@ -32,10 +32,10 @@ tabulated by the standard (0.0617 sone/Cam); a 1 kHz tone at 40 dB SPL presented binaurally in a free field yields 1.000 sone by definition of the sone (clause 3.17), which this implementation reproduces without tuning. -The stationary method is spectrum based. [`loudness_moore_glasberg_from_spectrum`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/#loudness_moore_glasberg_from_spectrum) +The stationary method is spectrum-based. [`loudness_moore_glasberg_from_spectrum`](/phonometry/reference/api/psychoacoustics/moore-glasberg/#loudness_moore_glasberg_from_spectrum) takes the exact sinusoidal-component representation of clauses 5.2/5.4, -[`loudness_moore_glasberg_from_third_octave`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/#loudness_moore_glasberg_from_third_octave) takes the 29 one-third-octave -band levels of clause 5.5, and [`loudness_moore_glasberg`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/#loudness_moore_glasberg) forms the +[`loudness_moore_glasberg_from_third_octave`](/phonometry/reference/api/psychoacoustics/moore-glasberg/#loudness_moore_glasberg_from_third_octave) takes the 29 one-third-octave +band levels of clause 5.5, and [`loudness_moore_glasberg`](/phonometry/reference/api/psychoacoustics/moore-glasberg/#loudness_moore_glasberg) forms the narrowband (FFT) line spectrum of a calibrated pressure signal and feeds it to the exact sinusoidal-component method. @@ -56,13 +56,13 @@ loudness_moore_glasberg( Moore-Glasberg loudness of a calibrated stationary pressure signal. Convenience wrapper around -[`loudness_moore_glasberg_from_spectrum`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/#loudness_moore_glasberg_from_spectrum): the signal's narrowband +[`loudness_moore_glasberg_from_spectrum`](/phonometry/reference/api/psychoacoustics/moore-glasberg/#loudness_moore_glasberg_from_spectrum): the signal's narrowband (FFT) line spectrum is formed and each bin is passed to the exact sinusoidal-component method of ISO 532-2:2017 (clauses 5.2/5.4). Because the model computes the excitation pattern per spectral component (Formula 5), a pure tone enters as a single line - so a calibrated 1 kHz tone at 40 dB SPL yields 1.000 sone / 40 phon (the definitional anchor), -matching [`loudness_moore_glasberg_from_spectrum`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/#loudness_moore_glasberg_from_spectrum). The signal must be +matching [`loudness_moore_glasberg_from_spectrum`](/phonometry/reference/api/psychoacoustics/moore-glasberg/#loudness_moore_glasberg_from_spectrum). The signal must be calibrated so that `x` is the instantaneous sound pressure in pascals. **Parameters** @@ -74,7 +74,7 @@ calibrated so that `x` is the instantaneous sound pressure in pascals. | `field` | `"free"` (default), `"diffuse"` or `"eardrum"`. | | `presentation` | `"binaural"` (default; alias `"diotic"`) or `"monaural"`. | -**Returns:** A [`MooreGlasbergLoudness`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/#mooreglasbergloudness). +**Returns:** A [`MooreGlasbergLoudness`](/phonometry/reference/api/psychoacoustics/moore-glasberg/#mooreglasbergloudness). ## loudness_moore_glasberg_from_spectrum @@ -103,7 +103,7 @@ closely spaced components of clause 5.3. | `field` | Listening condition setting the outer-ear transfer: `"free"` (frontal free field, default), `"diffuse"` (diffuse field) or `"eardrum"` (levels already specified at the tympanic membrane, e.g. a flat earphone). | | `presentation` | `"binaural"` (default; `"diotic"` is an equivalent alias: the same sound at both ears) or `"monaural"` (one ear only). | -**Returns:** A [`MooreGlasbergLoudness`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/#mooreglasbergloudness). +**Returns:** A [`MooreGlasbergLoudness`](/phonometry/reference/api/psychoacoustics/moore-glasberg/#mooreglasbergloudness). A 1 kHz tone at 40 dB SPL in a free field presented binaurally yields 1.000 sone / 40 phon by definition of the sone. @@ -135,7 +135,7 @@ sinusoidal components and the exact method is applied. | `field` | `"free"` (default), `"diffuse"` or `"eardrum"`. | | `presentation` | `"binaural"` (default; alias `"diotic"`) or `"monaural"`. | -**Returns:** A [`MooreGlasbergLoudness`](/phonometry/reference/api/psychoacoustics/loudness-moore-glasberg/#mooreglasbergloudness). +**Returns:** A [`MooreGlasbergLoudness`](/phonometry/reference/api/psychoacoustics/moore-glasberg/#mooreglasbergloudness). ## MooreGlasbergLoudness diff --git a/site/src/content/docs/reference/api/psychoacoustics/roughness-ecma.md b/site/src/content/docs/reference/api/psychoacoustics/roughness-ecma.md index a7ce52978..88c4fa552 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/roughness-ecma.md +++ b/site/src/content/docs/reference/api/psychoacoustics/roughness-ecma.md @@ -1,5 +1,5 @@ --- -title: "psychoacoustics.roughness_ecma" +title: "psychoacoustics.quality.roughness_ecma" description: "Psychoacoustic roughness per ECMA-418-2:2025 (4th ed., Sottek Hearing Model)." sidebar: label: "roughness_ecma" @@ -11,8 +11,8 @@ Clean-room implementation of the roughness signal chain of ECMA-418-2:2025 (Clause 7). The shared auditory front-end (Clause 5: outer/middle-ear filter, 53-band gammatone-like filter bank, half-wave rectification, compressive nonlinearity to the specific basis loudness `N'_basis(l, z)` of Formula 25) -is reused from [`.loudness_ecma`](/phonometry/reference/api/psychoacoustics/loudness-ecma/#loudness_ecma); this module adds the roughness-specific -chain: +is reused from [`phonometry.psychoacoustics.loudness.ecma`](/phonometry/reference/api/psychoacoustics/ecma/); this module +adds the roughness-specific chain: * roughness-specific zero-padding (Clause 5.1.2.2) and segmentation (Clause 5.1.5.2) with the fixed block/hop $s_b = 16384$ / diff --git a/site/src/content/docs/reference/api/psychoacoustics/sharpness.md b/site/src/content/docs/reference/api/psychoacoustics/sharpness.md index c6951891a..7f1c1e5a7 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/sharpness.md +++ b/site/src/content/docs/reference/api/psychoacoustics/sharpness.md @@ -1,5 +1,5 @@ --- -title: "psychoacoustics.sharpness" +title: "psychoacoustics.quality.sharpness" description: "Sharpness per DIN 45692:2009-08." sidebar: label: "sharpness" diff --git a/site/src/content/docs/reference/api/psychoacoustics/tonality-ecma.md b/site/src/content/docs/reference/api/psychoacoustics/tonality-ecma.md index f53dcd984..ac47d44b9 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/tonality-ecma.md +++ b/site/src/content/docs/reference/api/psychoacoustics/tonality-ecma.md @@ -1,5 +1,5 @@ --- -title: "psychoacoustics.tonality_ecma" +title: "psychoacoustics.quality.tonality_ecma" description: "Psychoacoustic tonality per ECMA-418-2:2025 (4th ed., Sottek Hearing Model)." sidebar: label: "tonality_ecma" @@ -10,9 +10,11 @@ Psychoacoustic tonality per ECMA-418-2:2025 (4th ed., Sottek Hearing Model). Clean-room implementation of the tonality signal chain of ECMA-418-2:2025 (Clause 6.2). The shared auditory front-end (Clause 5) and the ACF-based tonal/noise decomposition with the full Clause 6.2.3 band averaging -(Clause 6.2.2-6.2.7, [`loudness_ecma._tonal_noise_split`](/phonometry/reference/api/psychoacoustics/loudness-ecma/#loudness_ecma)) are reused -from [`.loudness_ecma`](/phonometry/reference/api/psychoacoustics/loudness-ecma/#loudness_ecma) -- loudness and tonality therefore report the -same underlying `N'_tonal(l, z)` for the same signal; this module adds +(Clause 6.2.2-6.2.7, +[`phonometry.psychoacoustics.loudness.ecma._tonal_noise_split`](/phonometry/reference/api/psychoacoustics/ecma/)) are +reused from [`phonometry.psychoacoustics.loudness.ecma`](/phonometry/reference/api/psychoacoustics/ecma/) -- loudness and +tonality therefore report the same underlying `N'_tonal(l, z)` for the same +signal; this module adds * the tonality output stages (Clause 6.2.8-6.2.11): the overall-SNR gate `q(l)` (Formulae 49-50), the time-dependent specific tonality diff --git a/site/src/content/docs/reference/api/psychoacoustics/tonality.md b/site/src/content/docs/reference/api/psychoacoustics/tonality.md index 938f1d44c..da529cb82 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/tonality.md +++ b/site/src/content/docs/reference/api/psychoacoustics/tonality.md @@ -1,5 +1,5 @@ --- -title: "psychoacoustics.tonality" +title: "psychoacoustics.quality.tonality" description: "Prominent discrete tone assessment per ECMA-418-1:2024 (3rd edition)." sidebar: label: "tonality" diff --git a/site/src/content/docs/reference/api/psychoacoustics/tone-audibility.md b/site/src/content/docs/reference/api/psychoacoustics/tone-audibility.md index a11899fd1..175f98a48 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/tone-audibility.md +++ b/site/src/content/docs/reference/api/psychoacoustics/tone-audibility.md @@ -1,5 +1,5 @@ --- -title: "psychoacoustics.tone_audibility" +title: "psychoacoustics.quality.tone_audibility" description: "Objective audibility of tones in noise -- engineering method (ISO/PAS 20065:2016)." sidebar: label: "tone_audibility" diff --git a/site/src/content/docs/reference/api/psychoacoustics/loudness-zwicker.md b/site/src/content/docs/reference/api/psychoacoustics/zwicker.md similarity index 88% rename from site/src/content/docs/reference/api/psychoacoustics/loudness-zwicker.md rename to site/src/content/docs/reference/api/psychoacoustics/zwicker.md index 62f958a29..415a2cb6b 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/loudness-zwicker.md +++ b/site/src/content/docs/reference/api/psychoacoustics/zwicker.md @@ -1,8 +1,8 @@ --- -title: "psychoacoustics.loudness_zwicker" +title: "psychoacoustics.loudness.zwicker" description: "Zwicker loudness for stationary and time-varying sounds per ISO 532-1:2017." sidebar: - label: "loudness_zwicker" + label: "zwicker" --- Zwicker loudness for stationary and time-varying sounds per ISO 532-1:2017. @@ -11,16 +11,16 @@ Clean-room Python port of the normative reference implementation given in Annex A of ISO 532-1:2017 (program "ISO_532-1", Annex A.4). Two entry points are provided: -* [`loudness_zwicker_from_spectrum`](/phonometry/reference/api/psychoacoustics/loudness-zwicker/#loudness_zwicker_from_spectrum) - stationary loudness from 28 +* [`loudness_zwicker_from_spectrum`](/phonometry/reference/api/psychoacoustics/zwicker/#loudness_zwicker_from_spectrum) - stationary loudness from 28 one-third-octave band levels, 25 Hz to 12.5 kHz (clause 5.3 / A.2). -* [`loudness_zwicker`](/phonometry/reference/api/psychoacoustics/loudness-zwicker/#loudness_zwicker) - loudness from a calibrated time signal, +* [`loudness_zwicker`](/phonometry/reference/api/psychoacoustics/zwicker/#loudness_zwicker) - loudness from a calibrated time signal, either with the stationary method (clause 5) or the time-varying method (clause 6), including the one-third-octave filterbank of Annex A (Tables A.1/A.2), the nonlinear temporal decay and the temporal weighting of the total loudness. -All numeric tables live in `phonometry._zwicker_data` and reproduce -Tables A.1 to A.9 of the standard digit for digit. +All numeric tables live in `._zwicker_data` and reproduce Tables A.1 to +A.9 of the standard digit for digit. > Auto-generated from the source docstrings by `scripts/generate_api_docs.py` (`make api-docs`). Do not edit by hand. @@ -76,7 +76,7 @@ with `ref` scaled to +-1 full scale as well. | `calibration_factor` | Multiplier converting `x` to pascals. | | `time_skip` | Leading time, in seconds, excluded from the stationary mean square (the reference implementation's TimeSkip). Annex B.1 states the stationary calculation "shall start from 0,2 s" when validating against the official Annex B WAV files, excluding the filterbank transient; the default 0.0 preserves the whole-signal behaviour for synthetic steady signals. Validated always (negative, non-finite or whole-signal skips raise `ValueError`) but applied only by the stationary method -- clause 6 has no TimeSkip. | -**Returns:** [`ZwickerLoudness`](/phonometry/reference/api/psychoacoustics/loudness-zwicker/#zwickerloudness). Stationary: as in [`loudness_zwicker_from_spectrum`](/phonometry/reference/api/psychoacoustics/loudness-zwicker/#loudness_zwicker_from_spectrum). Time-varying: `loudness` is the maximum loudness Nmax, `loudness_level` its phon mapping, `specific` the pattern at the loudness maximum, `n5`/`n10` the percentile values and `time` / `loudness_vs_time` the loudness trace at 500 Hz. +**Returns:** [`ZwickerLoudness`](/phonometry/reference/api/psychoacoustics/zwicker/#zwickerloudness). Stationary: as in [`loudness_zwicker_from_spectrum`](/phonometry/reference/api/psychoacoustics/zwicker/#loudness_zwicker_from_spectrum). Time-varying: `loudness` is the maximum loudness Nmax, `loudness_level` its phon mapping, `specific` the pattern at the loudness maximum, `n5`/`n10` the percentile values and `time` / `loudness_vs_time` the loudness trace at 500 Hz. ## loudness_zwicker_from_spectrum @@ -100,7 +100,7 @@ center frequencies 25 Hz to 12.5 kHz (base-ten bands, IEC 61260-1). | `levels` | 28 band levels in dB SPL (re 20 uPa), 25 Hz..12.5 kHz. | | `field` | Sound field the levels were measured in: 'free' or 'diffuse'. | -**Returns:** [`ZwickerLoudness`](/phonometry/reference/api/psychoacoustics/loudness-zwicker/#zwickerloudness) with `loudness` (N in sone), `loudness_level` (LN in phon) and `specific` (N' in sone/Bark, 240 values at 0.1-Bark steps); the time-varying fields are None. +**Returns:** [`ZwickerLoudness`](/phonometry/reference/api/psychoacoustics/zwicker/#zwickerloudness) with `loudness` (N in sone), `loudness_level` (LN in phon) and `specific` (N' in sone/Bark, 240 values at 0.1-Bark steps); the time-varying fields are None. ## ZwickerLoudness diff --git a/site/src/content/docs/reference/errata.md b/site/src/content/docs/reference/errata.md index a45659906..5ce854f6a 100644 --- a/site/src/content/docs/reference/errata.md +++ b/site/src/content/docs/reference/errata.md @@ -799,7 +799,7 @@ which is the check that enforces the rule; see printed p. 20, 220 dpi (Figure 6 and its 11220 tick). - **Library behaviour:** uses the internally consistent 89,1 Hz to 11 200 Hz range (upper end exclusive per the formulas), with a code note - in [`tonality.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/tonality.py). + in [`tonality.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/quality/tonality.py). - **Status:** unreported. ## ECMA-418-1:2024 (3rd edition), Formula (21) (repeated constant term) @@ -821,7 +821,7 @@ which is the check that enforces the rule; see Formula (22) as the consistent control. Render: `plan/ECMA-418-1_3rd_edition_december_2024.pdf`, PDF page 25, printed p. 17, 300 dpi. - **Library behaviour:** implements the $C_{L,1}$ reading, which is the only one that returns a usable band edge, with a code note in - [`tonality.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/tonality.py). + [`tonality.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/quality/tonality.py). - **Status:** unreported. ## ECMA-418-1:2024 (3rd edition), clause 11.3 (unresolved field references) @@ -860,7 +860,7 @@ which is the check that enforces the rule; see reading reproduces the Clause 7 roughness calibration (1 asper) to 0,9999. - **Library behaviour:** implements the flush-to-end reading with a code - note in [`roughness_ecma.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/roughness_ecma.py). + note in [`roughness_ecma.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/quality/roughness_ecma.py). - **Status:** unreported. ## ECMA-418-2:2025 (4th edition), clause 9.1.4, Formula (127) (HSA kernel phase) @@ -978,7 +978,7 @@ which is the check that enforces the rule; see printed p. 14, 400 dpi. - **Library behaviour:** follows the DIN/sqrt(2) reading (it matches the only executable reference), with the choice recorded in - [`tone_audibility.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/tone_audibility.py). + [`tone_audibility.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/quality/tone_audibility.py). - **Status:** unreported. ## DIN 45681:2005-03, Anhang I, Tabelle I.6, row "6 FG" @@ -1408,7 +1408,7 @@ which is the check that enforces the rule; see - **Library behaviour:** implements $0{,}76 \cdot 10^{-3}$ with a note at the formula; the carrier-frequency sweep test would catch a regression to the printed - value ([`fluctuation_strength.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/fluctuation_strength.py)). + value ([`fluctuation_strength.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/psychoacoustics/quality/fluctuation_strength.py)). - **Status:** unreported (conference paper rather than a standard). ## Medwin & Clay, Fundamentals of Acoustical Oceanography (1998), Eq. (3.4.30) (boric-acid coefficient) diff --git a/site/src/content/docs/reference/why-phonometry.mdx b/site/src/content/docs/reference/why-phonometry.mdx index 49bfe45b2..f43d84484 100644 --- a/site/src/content/docs/reference/why-phonometry.mdx +++ b/site/src/content/docs/reference/why-phonometry.mdx @@ -101,8 +101,8 @@ sample from the metrology core: | IEC 61672-1:2013 Table 5 | `lc_peak()` one-cycle/half-cycle peak responses, class 1 limits | `tests/signals/test_levels.py` | | IEC 61260-1:2014 Table 1 | Filter-bank class 1/2 acceptance limits via `verify_filter_class()` | `tests/filters/test_compliance.py` | | ISO 7196:1995 Table 2 | G weighting (infrasound) at every nominal response value, 0.25–315 Hz | `tests/filters/test_g_weighting.py` | -| ISO 226:2023 Table 1 and Annex B | Equal-loudness contours and loudness levels against the Annex B tables, hearing threshold against the Table 1 $T_f$ parameters | `tests/psychoacoustics/test_loudness_contours.py` | -| ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/psychoacoustics/test_tonality.py` | +| ISO 226:2023 Table 1 and Annex B | Equal-loudness contours and loudness levels against the Annex B tables, hearing threshold against the Table 1 $T_f$ parameters | `tests/psychoacoustics/loudness/test_contours.py` | +| ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/psychoacoustics/quality/test_tonality.py` | | ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/environment/assessment/test_rating.py` | | IEC 60942:2017 Table 2 | Calibrator short-term stability limits (frequency-dependent, class 1) in `sensitivity()` | `tests/metrology/test_calibration_validation.py` | diff --git a/site/src/generated/api-sidebar.mjs b/site/src/generated/api-sidebar.mjs index a269ab1c6..a4326a65c 100644 --- a/site/src/generated/api-sidebar.mjs +++ b/site/src/generated/api-sidebar.mjs @@ -52,11 +52,11 @@ export const apiSidebar = { translations: { es: 'Psicoacústica' }, collapsed: true, items: [ - 'reference/api/psychoacoustics/loudness-zwicker', - 'reference/api/psychoacoustics/loudness-moore-glasberg', - 'reference/api/psychoacoustics/loudness-moore-glasberg-time', - 'reference/api/psychoacoustics/loudness-ecma', - 'reference/api/psychoacoustics/loudness-contours', + 'reference/api/psychoacoustics/zwicker', + 'reference/api/psychoacoustics/moore-glasberg', + 'reference/api/psychoacoustics/moore-glasberg-time', + 'reference/api/psychoacoustics/ecma', + 'reference/api/psychoacoustics/contours', 'reference/api/psychoacoustics/sharpness', 'reference/api/psychoacoustics/roughness-ecma', 'reference/api/psychoacoustics/tonality', @@ -64,7 +64,7 @@ export const apiSidebar = { 'reference/api/psychoacoustics/tone-audibility', 'reference/api/psychoacoustics/fluctuation-strength', 'reference/api/psychoacoustics/fluctuation-strength-ecma', - 'reference/api/psychoacoustics/psychoacoustic-annoyance', + 'reference/api/psychoacoustics/annoyance', 'reference/api/psychoacoustics/erb-scale', ], }, diff --git a/src/phonometry/__init__.py b/src/phonometry/__init__.py index 3745ee852..01e21d7f3 100644 --- a/src/phonometry/__init__.py +++ b/src/phonometry/__init__.py @@ -1041,53 +1041,56 @@ erb_bandwidth, frequency_from_cam, ) -from .psychoacoustics.fluctuation_strength import ( - FluctuationStrengthResult, - fluctuation_strength, - fluctuation_strength_am_noise, -) -from .psychoacoustics.fluctuation_strength_ecma import ( - EcmaFluctuationStrength, - fluctuation_strength_ecma, -) -from .psychoacoustics.loudness_contours import ( +from .psychoacoustics.loudness.contours import ( EqualLoudnessContours, equal_loudness_contour, equal_loudness_contours, hearing_threshold, loudness_level, ) -from .psychoacoustics.loudness_ecma import EcmaLoudness, loudness_ecma -from .psychoacoustics.loudness_moore_glasberg import ( +from .psychoacoustics.loudness.ecma import EcmaLoudness, loudness_ecma +from .psychoacoustics.loudness.moore_glasberg import ( MooreGlasbergLoudness, loudness_moore_glasberg, loudness_moore_glasberg_from_spectrum, loudness_moore_glasberg_from_third_octave, ) -from .psychoacoustics.loudness_moore_glasberg_time import ( +from .psychoacoustics.loudness.moore_glasberg_time import ( MooreGlasbergTimeVaryingLoudness, loudness_moore_glasberg_time, ) -from .psychoacoustics.loudness_zwicker import ( +from .psychoacoustics.loudness.zwicker import ( ZwickerLoudness, loudness_zwicker, loudness_zwicker_from_spectrum, ) -from .psychoacoustics.psychoacoustic_annoyance import ( +from .psychoacoustics.quality.annoyance import ( PsychoacousticAnnoyanceResult, psychoacoustic_annoyance, psychoacoustic_annoyance_from_signal, ) -from .psychoacoustics.roughness_ecma import EcmaRoughness, roughness_ecma -from .psychoacoustics.sharpness import sharpness_din, sharpness_din_from_specific -from .psychoacoustics.tonality import ( +from .psychoacoustics.quality.fluctuation_strength import ( + FluctuationStrengthResult, + fluctuation_strength, + fluctuation_strength_am_noise, +) +from .psychoacoustics.quality.fluctuation_strength_ecma import ( + EcmaFluctuationStrength, + fluctuation_strength_ecma, +) +from .psychoacoustics.quality.roughness_ecma import EcmaRoughness, roughness_ecma +from .psychoacoustics.quality.sharpness import ( + sharpness_din, + sharpness_din_from_specific, +) +from .psychoacoustics.quality.tonality import ( TonalityWarning, ToneAssessment, prominence_ratio, tone_to_noise_ratio, ) -from .psychoacoustics.tonality_ecma import EcmaTonality, tonality_ecma -from .psychoacoustics.tone_audibility import ( +from .psychoacoustics.quality.tonality_ecma import EcmaTonality, tonality_ecma +from .psychoacoustics.quality.tone_audibility import ( HANNING_BANDWIDTH_FACTOR, NO_TONE_AUDIBILITY, ToneAudibilityResult, diff --git a/src/phonometry/_compat.py b/src/phonometry/_compat.py index 414b50588..37df8cb97 100644 --- a/src/phonometry/_compat.py +++ b/src/phonometry/_compat.py @@ -54,18 +54,18 @@ "phonometry.levels": "phonometry.signals.levels", "phonometry.parametric_filters": "phonometry.filters.weighting", "phonometry.uncertainty": "phonometry.metrology.uncertainty", - "phonometry.fluctuation_strength": "phonometry.psychoacoustics.fluctuation_strength", - "phonometry.loudness_contours": "phonometry.psychoacoustics.loudness_contours", - "phonometry.loudness_ecma": "phonometry.psychoacoustics.loudness_ecma", - "phonometry.loudness_moore_glasberg": "phonometry.psychoacoustics.loudness_moore_glasberg", - "phonometry.loudness_moore_glasberg_time": "phonometry.psychoacoustics.loudness_moore_glasberg_time", - "phonometry.loudness_zwicker": "phonometry.psychoacoustics.loudness_zwicker", - "phonometry.psychoacoustic_annoyance": "phonometry.psychoacoustics.psychoacoustic_annoyance", - "phonometry.roughness_ecma": "phonometry.psychoacoustics.roughness_ecma", - "phonometry.sharpness": "phonometry.psychoacoustics.sharpness", - "phonometry.tonality": "phonometry.psychoacoustics.tonality", - "phonometry.tonality_ecma": "phonometry.psychoacoustics.tonality_ecma", - "phonometry.tone_audibility": "phonometry.psychoacoustics.tone_audibility", + "phonometry.fluctuation_strength": "phonometry.psychoacoustics.quality.fluctuation_strength", + "phonometry.loudness_contours": "phonometry.psychoacoustics.loudness.contours", + "phonometry.loudness_ecma": "phonometry.psychoacoustics.loudness.ecma", + "phonometry.loudness_moore_glasberg": "phonometry.psychoacoustics.loudness.moore_glasberg", + "phonometry.loudness_moore_glasberg_time": "phonometry.psychoacoustics.loudness.moore_glasberg_time", + "phonometry.loudness_zwicker": "phonometry.psychoacoustics.loudness.zwicker", + "phonometry.psychoacoustic_annoyance": "phonometry.psychoacoustics.quality.annoyance", + "phonometry.roughness_ecma": "phonometry.psychoacoustics.quality.roughness_ecma", + "phonometry.sharpness": "phonometry.psychoacoustics.quality.sharpness", + "phonometry.tonality": "phonometry.psychoacoustics.quality.tonality", + "phonometry.tonality_ecma": "phonometry.psychoacoustics.quality.tonality_ecma", + "phonometry.tone_audibility": "phonometry.psychoacoustics.quality.tone_audibility", "phonometry.noise_induced_hearing_loss": "phonometry.hearing.noise_induced_hearing_loss", "phonometry.occupational_exposure": "phonometry.hearing.occupational_exposure", "phonometry.sii": "phonometry.speech.sii", @@ -161,8 +161,10 @@ } #: Renames that were already shimmed before 3.2 (target differs from a plain -#: package move). ``phonometry.loudness`` predates the reorganization. -_MOVED_3X["phonometry.loudness"] = "phonometry.psychoacoustics.loudness_zwicker" +#: package move). ``phonometry.loudness`` predates the reorganization and +#: lands wherever the Zwicker module currently lives, so it reads its target +#: from the row above instead of repeating it. +_MOVED_3X["phonometry.loudness"] = _MOVED_3X["phonometry.loudness_zwicker"] #: Old module path -> relocated module path for the 4.0 taxonomy. The #: oversized ``metrology`` catch-all became three packages: the normalized @@ -299,6 +301,32 @@ "phonometry.materials.surfaces.road_absorption", "phonometry.materials.dynamic_stiffness": "phonometry.materials.resilient.dynamic_stiffness", + "phonometry.psychoacoustics.loudness_zwicker": + "phonometry.psychoacoustics.loudness.zwicker", + "phonometry.psychoacoustics.loudness_moore_glasberg": + "phonometry.psychoacoustics.loudness.moore_glasberg", + "phonometry.psychoacoustics.loudness_moore_glasberg_time": + "phonometry.psychoacoustics.loudness.moore_glasberg_time", + "phonometry.psychoacoustics.loudness_ecma": + "phonometry.psychoacoustics.loudness.ecma", + "phonometry.psychoacoustics.loudness_contours": + "phonometry.psychoacoustics.loudness.contours", + "phonometry.psychoacoustics.sharpness": + "phonometry.psychoacoustics.quality.sharpness", + "phonometry.psychoacoustics.roughness_ecma": + "phonometry.psychoacoustics.quality.roughness_ecma", + "phonometry.psychoacoustics.fluctuation_strength": + "phonometry.psychoacoustics.quality.fluctuation_strength", + "phonometry.psychoacoustics.fluctuation_strength_ecma": + "phonometry.psychoacoustics.quality.fluctuation_strength_ecma", + "phonometry.psychoacoustics.tonality": + "phonometry.psychoacoustics.quality.tonality", + "phonometry.psychoacoustics.tonality_ecma": + "phonometry.psychoacoustics.quality.tonality_ecma", + "phonometry.psychoacoustics.tone_audibility": + "phonometry.psychoacoustics.quality.tone_audibility", + "phonometry.psychoacoustics.psychoacoustic_annoyance": + "phonometry.psychoacoustics.quality.annoyance", } #: The two generations, each with the release that deprecated it and the one diff --git a/src/phonometry/_plot/psychoacoustics.py b/src/phonometry/_plot/psychoacoustics.py index 269495874..54602ce4b 100644 --- a/src/phonometry/_plot/psychoacoustics.py +++ b/src/phonometry/_plot/psychoacoustics.py @@ -25,20 +25,22 @@ if TYPE_CHECKING: from matplotlib.axes import Axes - from ..psychoacoustics.fluctuation_strength import FluctuationStrengthResult - from ..psychoacoustics.fluctuation_strength_ecma import EcmaFluctuationStrength - from ..psychoacoustics.loudness_contours import EqualLoudnessContours - from ..psychoacoustics.loudness_ecma import EcmaLoudness - from ..psychoacoustics.loudness_moore_glasberg import MooreGlasbergLoudness - from ..psychoacoustics.loudness_moore_glasberg_time import ( + from ..psychoacoustics.loudness.contours import EqualLoudnessContours + from ..psychoacoustics.loudness.ecma import EcmaLoudness + from ..psychoacoustics.loudness.moore_glasberg import MooreGlasbergLoudness + from ..psychoacoustics.loudness.moore_glasberg_time import ( MooreGlasbergTimeVaryingLoudness, ) - from ..psychoacoustics.loudness_zwicker import ZwickerLoudness - from ..psychoacoustics.psychoacoustic_annoyance import PsychoacousticAnnoyanceResult - from ..psychoacoustics.roughness_ecma import EcmaRoughness - from ..psychoacoustics.tonality import ToneAssessment - from ..psychoacoustics.tonality_ecma import EcmaTonality - from ..psychoacoustics.tone_audibility import ToneAudibilityResult + from ..psychoacoustics.loudness.zwicker import ZwickerLoudness + from ..psychoacoustics.quality.annoyance import PsychoacousticAnnoyanceResult + from ..psychoacoustics.quality.fluctuation_strength import FluctuationStrengthResult + from ..psychoacoustics.quality.fluctuation_strength_ecma import ( + EcmaFluctuationStrength, + ) + from ..psychoacoustics.quality.roughness_ecma import EcmaRoughness + from ..psychoacoustics.quality.tonality import ToneAssessment + from ..psychoacoustics.quality.tonality_ecma import EcmaTonality + from ..psychoacoustics.quality.tone_audibility import ToneAudibilityResult #: Spanish translations of the fixed strings rendered by the #: psychoacoustics ``.plot()`` renderers, keyed by their verbatim English @@ -119,7 +121,7 @@ def plot_zwicker_loudness( returned; otherwise (a stationary result, or an ``ax`` was supplied) a single axes is returned. - :param result: A :class:`~phonometry.psychoacoustics.loudness_zwicker.ZwickerLoudness`. + :param result: A :class:`~phonometry.psychoacoustics.loudness.zwicker.ZwickerLoudness`. :param ax: Existing axes to draw on, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the specific-loudness line ``plot`` call. @@ -174,7 +176,7 @@ def plot_zwicker_loudness_time( the second panel of :func:`plot_zwicker_loudness`. :param result: A time-varying - :class:`~phonometry.psychoacoustics.loudness_zwicker.ZwickerLoudness` (with + :class:`~phonometry.psychoacoustics.loudness.zwicker.ZwickerLoudness` (with ``time`` / ``loudness_vs_time``). :param ax: Existing axes to draw on, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. @@ -224,7 +226,7 @@ def plot_ecma_loudness( two axes is returned; when ``ax`` is supplied only the specific-loudness panel is drawn on it and that single axes is returned. - :param result: An :class:`~phonometry.psychoacoustics.loudness_ecma.EcmaLoudness`. + :param result: An :class:`~phonometry.psychoacoustics.loudness.ecma.EcmaLoudness`. :param ax: Existing axes to draw on, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the specific-loudness line ``plot`` call. @@ -279,7 +281,7 @@ def plot_moore_glasberg_loudness( """Specific loudness N'(i) over the ERB-number (Cam) scale (ISO 532-2). :param result: A - :class:`~phonometry.psychoacoustics.loudness_moore_glasberg.MooreGlasbergLoudness`. + :class:`~phonometry.psychoacoustics.loudness.moore_glasberg.MooreGlasbergLoudness`. :param ax: Existing axes to draw on, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the specific-loudness line ``plot`` call. @@ -314,7 +316,7 @@ def plot_moore_glasberg_time_loudness( """Short-term and long-term loudness against time (ISO 532-3). :param result: A - :class:`~phonometry.psychoacoustics.loudness_moore_glasberg_time.MooreGlasbergTimeVaryingLoudness`. + :class:`~phonometry.psychoacoustics.loudness.moore_glasberg_time.MooreGlasbergTimeVaryingLoudness`. :param ax: Existing axes to draw on, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the long-term-loudness line ``plot`` call. @@ -359,7 +361,7 @@ def plot_ecma_tonality( is returned; when ``ax`` is supplied only the specific-tonality panel is drawn on it and that single axes is returned. - :param result: An :class:`~phonometry.psychoacoustics.tonality_ecma.EcmaTonality`. + :param result: An :class:`~phonometry.psychoacoustics.quality.tonality_ecma.EcmaTonality`. :param ax: Existing axes to draw on, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the specific-tonality line ``plot`` call. @@ -477,7 +479,7 @@ def plot_ecma_roughness( and an array of two axes is returned; when ``ax`` is supplied only the time-dependent roughness is drawn on it and that single axes is returned. - :param result: An :class:`~phonometry.psychoacoustics.roughness_ecma.EcmaRoughness`. + :param result: An :class:`~phonometry.psychoacoustics.quality.roughness_ecma.EcmaRoughness`. :param ax: Existing axes to draw on, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the roughness-vs-time line ``plot`` call. @@ -558,7 +560,7 @@ def plot_fluctuation_strength( annotated with the overall ``F`` in vacil. :param result: A - :class:`~phonometry.psychoacoustics.fluctuation_strength.FluctuationStrengthResult`. + :class:`~phonometry.psychoacoustics.quality.fluctuation_strength.FluctuationStrengthResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the ``plot`` call. @@ -593,7 +595,7 @@ def plot_psychoacoustic_annoyance( Draws the PA value alongside the two loudness-weighted terms so the sharpness and fluctuation/roughness contributions are visible. - :param result: A :class:`~phonometry.psychoacoustics.psychoacoustic_annoyance. + :param result: A :class:`~phonometry.psychoacoustics.quality.annoyance. PsychoacousticAnnoyanceResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. @@ -646,7 +648,7 @@ def plot_tone_assessment( up, and different low-frequency slopes below), so the closer of the two identifies the curve to draw and the quantity to label. - :param result: A :class:`~phonometry.psychoacoustics.tonality.ToneAssessment`. + :param result: A :class:`~phonometry.psychoacoustics.quality.tonality.ToneAssessment`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the assessed-tone marker ``plot`` call. @@ -716,7 +718,7 @@ def plot_tone_audibility( Draws one bar per tone with the decisive (most audible) tone emphasised and the ``ΔL = 0`` audibility threshold marked; tones above it are present. - :param result: A :class:`~phonometry.psychoacoustics.tone_audibility.ToneAudibilityResult`. + :param result: A :class:`~phonometry.psychoacoustics.quality.tone_audibility.ToneAudibilityResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the bar ``bar`` call. @@ -767,7 +769,7 @@ def plot_tone_audibility_levels( masking threshold is the audibility ``ΔL_ta``. The decisive (most audible) tone is emphasised and the frequency axis is continuous and logarithmic. - :param result: A :class:`~phonometry.psychoacoustics.tone_audibility.ToneAudibilityResult`. + :param result: A :class:`~phonometry.psychoacoustics.quality.tone_audibility.ToneAudibilityResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the tone-level marker ``plot`` call. @@ -854,7 +856,7 @@ def plot_equal_loudness_contours( is the iconic ISO 226 chart. :param result: An - :class:`~phonometry.psychoacoustics.loudness_contours.EqualLoudnessContours`. + :class:`~phonometry.psychoacoustics.loudness.contours.EqualLoudnessContours`. :param ax: Existing axes to draw on, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the contour line ``plot`` calls. diff --git a/src/phonometry/_report/iso1996_tone.py b/src/phonometry/_report/iso1996_tone.py index ed0e7fcb3..b56090b49 100644 --- a/src/phonometry/_report/iso1996_tone.py +++ b/src/phonometry/_report/iso1996_tone.py @@ -2,7 +2,7 @@ """Tonal audibility assessment fiche (reportlab renderer, ISO 1996-2:2017). Renders a -:class:`~phonometry.psychoacoustics.tone_audibility.ToneAudibilityResult` to a +:class:`~phonometry.psychoacoustics.quality.tone_audibility.ToneAudibilityResult` to a one-page PDF laid out like a tonal-assessment report of an environmental-noise laboratory. ISO 1996-2:2017 Annex J assesses the audibility of prominent tones by the engineering method of ISO/PAS 20065:2016 (the critical band about a @@ -67,7 +67,7 @@ from .metadata import ReportMetadata if TYPE_CHECKING: - from ..psychoacoustics.tone_audibility import ToneAudibilityResult + from ..psychoacoustics.quality.tone_audibility import ToneAudibilityResult def _fmt(value: float, language: str, decimals: int = 1) -> str: @@ -286,7 +286,7 @@ def render_tone_audibility_report( """Render a tonal audibility assessment fiche to a PDF at ``path``. :param result: A - :class:`~phonometry.psychoacoustics.tone_audibility.ToneAudibilityResult` + :class:`~phonometry.psychoacoustics.quality.tone_audibility.ToneAudibilityResult` carrying the detected tones, their tone/masking levels and audibilities. :param path: Destination path of the PDF file. :param metadata: Optional :class:`ReportMetadata` supplying the header diff --git a/src/phonometry/_report/iso532.py b/src/phonometry/_report/iso532.py index fc648d20c..c0276cbc2 100644 --- a/src/phonometry/_report/iso532.py +++ b/src/phonometry/_report/iso532.py @@ -2,7 +2,7 @@ """ISO 532-1 Zwicker loudness fiche (reportlab renderer). Renders a -:class:`~phonometry.psychoacoustics.loudness_zwicker.ZwickerLoudness` to a +:class:`~phonometry.psychoacoustics.loudness.zwicker.ZwickerLoudness` to a one-page PDF laid out like an accredited loudness report: * a title and the standard-basis line (measurement standard + ISO 532-1), @@ -55,7 +55,7 @@ from .metadata import ReportMetadata if TYPE_CHECKING: - from ..psychoacoustics.loudness_zwicker import ZwickerLoudness + from ..psychoacoustics.loudness.zwicker import ZwickerLoudness def _metadata_pairs( @@ -203,7 +203,7 @@ def render_iso532_report( """Render an ISO 532-1 Zwicker loudness fiche to a PDF at ``path``. :param result: A - :class:`~phonometry.psychoacoustics.loudness_zwicker.ZwickerLoudness` + :class:`~phonometry.psychoacoustics.loudness.zwicker.ZwickerLoudness` carrying the total loudness, loudness level and specific-loudness pattern (and the N5/N10 percentiles for a time-varying result). :param path: Destination path of the PDF file. diff --git a/src/phonometry/psychoacoustics/__init__.py b/src/phonometry/psychoacoustics/__init__.py index e131930f1..79ce794d2 100644 --- a/src/phonometry/psychoacoustics/__init__.py +++ b/src/phonometry/psychoacoustics/__init__.py @@ -1,8 +1,19 @@ # Copyright (c) 2026. Jose Manuel Requena Plens -"""psychoacoustics domain of phonometry (see module docstrings).""" +"""psychoacoustics domain of phonometry (see module docstrings). + +Two families since 4.0, along the split every textbook on sound quality +makes: :mod:`~phonometry.psychoacoustics.loudness` for how loud a sound is +(the four models and the equal-loudness contours) and +:mod:`~phonometry.psychoacoustics.quality` for what it is like once its +loudness is known (sharpness, roughness, fluctuation strength, tonality and +the annoyance models built on them). The ERB scale stays at the root: both +families measure on it. Every public name is still exported here, so +``from phonometry import psychoacoustics`` reads as it did. +""" from __future__ import annotations +from .._compat import _namespace_dir, _namespace_shim from .erb_scale import ( CAM_C, ERB_C1, @@ -11,55 +22,34 @@ erb_bandwidth, frequency_from_cam, ) -from .fluctuation_strength import ( - FluctuationStrengthResult, - fluctuation_strength, - fluctuation_strength_am_noise, -) -from .fluctuation_strength_ecma import ( - EcmaFluctuationStrength, - fluctuation_strength_ecma, -) -from .loudness_contours import ( +from .loudness import ( + EcmaLoudness, EqualLoudnessContours, + MooreGlasbergLoudness, + MooreGlasbergTimeVaryingLoudness, + ZwickerLoudness, equal_loudness_contour, equal_loudness_contours, hearing_threshold, + loudness_ecma, loudness_level, -) -from .loudness_ecma import EcmaLoudness, loudness_ecma -from .loudness_moore_glasberg import ( - MooreGlasbergLoudness, loudness_moore_glasberg, loudness_moore_glasberg_from_spectrum, loudness_moore_glasberg_from_third_octave, -) -from .loudness_moore_glasberg_time import ( - MooreGlasbergTimeVaryingLoudness, loudness_moore_glasberg_time, -) -from .loudness_zwicker import ( - ZwickerLoudness, loudness_zwicker, loudness_zwicker_from_spectrum, ) -from .psychoacoustic_annoyance import ( +from .quality import ( + HANNING_BANDWIDTH_FACTOR, + NO_TONE_AUDIBILITY, + EcmaFluctuationStrength, + EcmaRoughness, + EcmaTonality, + FluctuationStrengthResult, PsychoacousticAnnoyanceResult, - psychoacoustic_annoyance, - psychoacoustic_annoyance_from_signal, -) -from .roughness_ecma import EcmaRoughness, roughness_ecma -from .sharpness import sharpness_din, sharpness_din_from_specific -from .tonality import ( TonalityWarning, ToneAssessment, - prominence_ratio, - tone_to_noise_ratio, -) -from .tonality_ecma import EcmaTonality, tonality_ecma -from .tone_audibility import ( - HANNING_BANDWIDTH_FACTOR, - NO_TONE_AUDIBILITY, ToneAudibilityResult, analyze_spectrum, assess_tones, @@ -70,13 +60,24 @@ critical_band_level, critical_bandwidth_engineering, energy_sum_level, + fluctuation_strength, + fluctuation_strength_am_noise, + fluctuation_strength_ecma, masking_index, mean_audibility, mean_audibility_uncertainty, mean_narrowband_level, + prominence_ratio, + psychoacoustic_annoyance, + psychoacoustic_annoyance_from_signal, resolve_tones_separately, + roughness_ecma, + sharpness_din, + sharpness_din_from_specific, + tonality_ecma, tone_audibility, tone_level, + tone_to_noise_ratio, two_tone_separation_frequency, ) @@ -142,3 +143,9 @@ "tone_to_noise_ratio", "two_tone_separation_frequency", ] + +#: No public name left this namespace in 4.0, but the modules did, so +#: ``psychoacoustics.loudness_zwicker`` has to keep resolving to its alias +#: module until 5.0: the import registers it, the attribute read needs this. +__getattr__ = _namespace_shim(__name__) +__dir__ = _namespace_dir(__name__, __all__) diff --git a/src/phonometry/psychoacoustics/erb_scale.py b/src/phonometry/psychoacoustics/erb_scale.py index a3279c3be..95da71fa8 100644 --- a/src/phonometry/psychoacoustics/erb_scale.py +++ b/src/phonometry/psychoacoustics/erb_scale.py @@ -31,7 +31,7 @@ This module states the constants to the precision used by the ISO 532-2 implementation of the Moore-Glasberg loudness model -(:mod:`phonometry.psychoacoustics.loudness_moore_glasberg`), which shares +(:mod:`phonometry.psychoacoustics.loudness.moore_glasberg`), which shares them: .. math:: diff --git a/src/phonometry/psychoacoustics/loudness/__init__.py b/src/phonometry/psychoacoustics/loudness/__init__.py new file mode 100644 index 000000000..a43e2879c --- /dev/null +++ b/src/phonometry/psychoacoustics/loudness/__init__.py @@ -0,0 +1,55 @@ +# Copyright (c) 2026. Jose Manuel Requena Plens +"""psychoacoustics.loudness subdomain of phonometry: how loud a sound is. + +The four loudness models (ISO 532-1 Zwicker, ISO 532-2 Moore-Glasberg, +ISO 532-3 time-varying Moore-Glasberg and ECMA-418-2) and the ISO 226 +equal-loudness contours that put a level in phons. +""" + +from __future__ import annotations + +from .contours import ( + EqualLoudnessContours, + equal_loudness_contour, + equal_loudness_contours, + hearing_threshold, + loudness_level, +) +from .ecma import ( + EcmaLoudness, + loudness_ecma, +) +from .moore_glasberg import ( + MooreGlasbergLoudness, + loudness_moore_glasberg, + loudness_moore_glasberg_from_spectrum, + loudness_moore_glasberg_from_third_octave, +) +from .moore_glasberg_time import ( + MooreGlasbergTimeVaryingLoudness, + loudness_moore_glasberg_time, +) +from .zwicker import ( + ZwickerLoudness, + loudness_zwicker, + loudness_zwicker_from_spectrum, +) + +__all__ = [ + "EcmaLoudness", + "EqualLoudnessContours", + "MooreGlasbergLoudness", + "MooreGlasbergTimeVaryingLoudness", + "ZwickerLoudness", + "equal_loudness_contour", + "equal_loudness_contours", + "hearing_threshold", + "loudness_ecma", + "loudness_level", + "loudness_moore_glasberg", + "loudness_moore_glasberg_from_spectrum", + "loudness_moore_glasberg_from_third_octave", + "loudness_moore_glasberg_time", + "loudness_zwicker", + "loudness_zwicker_from_spectrum", +] diff --git a/src/phonometry/psychoacoustics/_zwicker_data.py b/src/phonometry/psychoacoustics/loudness/_zwicker_data.py similarity index 99% rename from src/phonometry/psychoacoustics/_zwicker_data.py rename to src/phonometry/psychoacoustics/loudness/_zwicker_data.py index 7968555aa..bee92491e 100644 --- a/src/phonometry/psychoacoustics/_zwicker_data.py +++ b/src/phonometry/psychoacoustics/loudness/_zwicker_data.py @@ -7,7 +7,7 @@ Annex A.4). The values equal the printed tables A.1 to A.9 of the standard. Do not edit these numbers. -Shared by :mod:`phonometry.psychoacoustics.loudness_zwicker`. +Shared by :mod:`phonometry.psychoacoustics.loudness.zwicker`. """ from __future__ import annotations diff --git a/src/phonometry/psychoacoustics/loudness_contours.py b/src/phonometry/psychoacoustics/loudness/contours.py similarity index 98% rename from src/phonometry/psychoacoustics/loudness_contours.py rename to src/phonometry/psychoacoustics/loudness/contours.py index 31a773559..407cc5702 100644 --- a/src/phonometry/psychoacoustics/loudness_contours.py +++ b/src/phonometry/psychoacoustics/loudness/contours.py @@ -180,8 +180,8 @@ def plot( :param kwargs: Forwarded to the contour ``plot`` calls. :return: The axes. """ - from .._i18n import check_language - from .._plot.psychoacoustics import plot_equal_loudness_contours + from ..._i18n import check_language + from ..._plot.psychoacoustics import plot_equal_loudness_contours return plot_equal_loudness_contours( self, ax=ax, language=check_language(language), **kwargs diff --git a/src/phonometry/psychoacoustics/loudness_ecma.py b/src/phonometry/psychoacoustics/loudness/ecma.py similarity index 99% rename from src/phonometry/psychoacoustics/loudness_ecma.py rename to src/phonometry/psychoacoustics/loudness/ecma.py index bc6b3a9ad..8d17210ec 100644 --- a/src/phonometry/psychoacoustics/loudness_ecma.py +++ b/src/phonometry/psychoacoustics/loudness/ecma.py @@ -55,8 +55,8 @@ if TYPE_CHECKING: from matplotlib.axes import Axes -from .._internal.utils import _typesignal -from .._internal.validation import require_1d_signal +from ..._internal.utils import _typesignal +from ..._internal.validation import require_1d_signal # -------------------------------------------------------------------------- # Global constants (Clause 5) @@ -202,8 +202,8 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - Adds a loudness-vs-time panel. Requires matplotlib (``pip install phonometry[plot]``). """ - from .._i18n import check_language - from .._plot.psychoacoustics import plot_ecma_loudness + from ..._i18n import check_language + from ..._plot.psychoacoustics import plot_ecma_loudness return plot_ecma_loudness(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/psychoacoustics/loudness_moore_glasberg.py b/src/phonometry/psychoacoustics/loudness/moore_glasberg.py similarity index 98% rename from src/phonometry/psychoacoustics/loudness_moore_glasberg.py rename to src/phonometry/psychoacoustics/loudness/moore_glasberg.py index 66a5ef68d..c88053ccd 100644 --- a/src/phonometry/psychoacoustics/loudness_moore_glasberg.py +++ b/src/phonometry/psychoacoustics/loudness/moore_glasberg.py @@ -27,7 +27,7 @@ presented binaurally in a free field yields 1.000 sone by definition of the sone (clause 3.17), which this implementation reproduces without tuning. -The stationary method is spectrum based. :func:`loudness_moore_glasberg_from_spectrum` +The stationary method is spectrum-based. :func:`loudness_moore_glasberg_from_spectrum` takes the exact sinusoidal-component representation of clauses 5.2/5.4, :func:`loudness_moore_glasberg_from_third_octave` takes the 29 one-third-octave band levels of clause 5.5, and :func:`loudness_moore_glasberg` forms the @@ -47,9 +47,9 @@ if TYPE_CHECKING: from matplotlib.axes import Axes -from .._internal.utils import _typesignal -from .._internal.validation import require_1d_signal -from .erb_scale import CAM_C, ERB_C1, ERB_C2, erb_bandwidth, frequency_from_cam +from ..._internal.utils import _typesignal +from ..._internal.validation import require_1d_signal +from ..erb_scale import CAM_C, ERB_C1, ERB_C2, erb_bandwidth, frequency_from_cam # --------------------------------------------------------------------------- # Fixed transfer functions (clauses 7.2, 7.3, Table 1) @@ -537,8 +537,8 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - Requires matplotlib (``pip install phonometry[plot]``); returns the :class:`~matplotlib.axes.Axes`. See :mod:`._plotting`. """ - from .._i18n import check_language - from .._plot.psychoacoustics import plot_moore_glasberg_loudness + from ..._i18n import check_language + from ..._plot.psychoacoustics import plot_moore_glasberg_loudness return plot_moore_glasberg_loudness(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/psychoacoustics/loudness_moore_glasberg_time.py b/src/phonometry/psychoacoustics/loudness/moore_glasberg_time.py similarity index 99% rename from src/phonometry/psychoacoustics/loudness_moore_glasberg_time.py rename to src/phonometry/psychoacoustics/loudness/moore_glasberg_time.py index 7bfadc4d9..d390405bb 100644 --- a/src/phonometry/psychoacoustics/loudness_moore_glasberg_time.py +++ b/src/phonometry/psychoacoustics/loudness/moore_glasberg_time.py @@ -66,7 +66,7 @@ if TYPE_CHECKING: from matplotlib.axes import Axes -from .loudness_moore_glasberg import ( +from .moore_glasberg import ( _ERB_C1, _ERB_C2, _ROEX_D, @@ -376,8 +376,8 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - Requires matplotlib (``pip install phonometry[plot]``); returns the :class:`~matplotlib.axes.Axes`. See :mod:`._plotting`. """ - from .._i18n import check_language - from .._plot.psychoacoustics import plot_moore_glasberg_time_loudness + from ..._i18n import check_language + from ..._plot.psychoacoustics import plot_moore_glasberg_time_loudness return plot_moore_glasberg_time_loudness(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/psychoacoustics/loudness_zwicker.py b/src/phonometry/psychoacoustics/loudness/zwicker.py similarity index 98% rename from src/phonometry/psychoacoustics/loudness_zwicker.py rename to src/phonometry/psychoacoustics/loudness/zwicker.py index ea2785e99..629141cbf 100644 --- a/src/phonometry/psychoacoustics/loudness_zwicker.py +++ b/src/phonometry/psychoacoustics/loudness/zwicker.py @@ -14,8 +14,8 @@ Annex A (Tables A.1/A.2), the nonlinear temporal decay and the temporal weighting of the total loudness. -All numeric tables live in :mod:`phonometry._zwicker_data` and reproduce -Tables A.1 to A.9 of the standard digit for digit. +All numeric tables live in :mod:`._zwicker_data` and reproduce Tables A.1 to +A.9 of the standard digit for digit. """ from __future__ import annotations @@ -29,11 +29,11 @@ if TYPE_CHECKING: from matplotlib.axes import Axes - from .._report.metadata import ReportMetadata + from ..._report.metadata import ReportMetadata from scipy import signal -from .._internal.utils import _typesignal -from .._internal.validation import require_positive +from ..._internal.utils import _typesignal +from ..._internal.validation import require_positive from ._zwicker_data import ( A0_TRANSMISSION, DCB_ADAPTATION, @@ -124,8 +124,8 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - present. Requires matplotlib (``pip install phonometry[plot]``); returns the :class:`~matplotlib.axes.Axes` (or array thereof). """ - from .._i18n import check_language - from .._plot.psychoacoustics import plot_zwicker_loudness + from ..._i18n import check_language + from ..._plot.psychoacoustics import plot_zwicker_loudness return plot_zwicker_loudness(self, ax=ax, language=check_language(language), **kwargs) @@ -168,14 +168,14 @@ def report( (``pip install phonometry[report]``), or matplotlib is missing for the embedded figure (``pip install phonometry[plot]``). """ - from .._i18n import check_language + from ..._i18n import check_language check_language(language) if engine != "reportlab": raise ValueError( f"Unknown report engine {engine!r}; only 'reportlab' is supported." ) - from .._report.iso532 import render_iso532_report + from ..._report.iso532 import render_iso532_report return render_iso532_report( self, path, metadata=metadata, verbose=verbose, language=language diff --git a/src/phonometry/psychoacoustics/quality/__init__.py b/src/phonometry/psychoacoustics/quality/__init__.py new file mode 100644 index 000000000..22f768603 --- /dev/null +++ b/src/phonometry/psychoacoustics/quality/__init__.py @@ -0,0 +1,105 @@ +# Copyright (c) 2026. Jose Manuel Requena Plens +"""psychoacoustics.quality subdomain of phonometry: what a sound is like. + +The attributes a listener hears beyond loudness (sharpness, roughness, +fluctuation strength, tonality and tone audibility) and the annoyance +models built on them. +""" + +from __future__ import annotations + +from .annoyance import ( + PsychoacousticAnnoyanceResult, + psychoacoustic_annoyance, + psychoacoustic_annoyance_from_signal, +) +from .fluctuation_strength import ( + FluctuationStrengthResult, + fluctuation_strength, + fluctuation_strength_am_noise, +) +from .fluctuation_strength_ecma import ( + EcmaFluctuationStrength, + fluctuation_strength_ecma, +) +from .roughness_ecma import ( + EcmaRoughness, + roughness_ecma, +) +from .sharpness import ( + sharpness_din, + sharpness_din_from_specific, +) +from .tonality import ( + TonalityWarning, + ToneAssessment, + prominence_ratio, + tone_to_noise_ratio, +) +from .tonality_ecma import ( + EcmaTonality, + tonality_ecma, +) +from .tone_audibility import ( + HANNING_BANDWIDTH_FACTOR, + NO_TONE_AUDIBILITY, + ToneAudibilityResult, + analyze_spectrum, + assess_tones, + audibility_from_levels, + audibility_uncertainty, + combined_tone_level, + critical_band_corners, + critical_band_level, + critical_bandwidth_engineering, + energy_sum_level, + masking_index, + mean_audibility, + mean_audibility_uncertainty, + mean_narrowband_level, + resolve_tones_separately, + tone_audibility, + tone_level, + two_tone_separation_frequency, +) + +__all__ = [ + "HANNING_BANDWIDTH_FACTOR", + "NO_TONE_AUDIBILITY", + "EcmaFluctuationStrength", + "EcmaRoughness", + "EcmaTonality", + "FluctuationStrengthResult", + "PsychoacousticAnnoyanceResult", + "TonalityWarning", + "ToneAssessment", + "ToneAudibilityResult", + "analyze_spectrum", + "assess_tones", + "audibility_from_levels", + "audibility_uncertainty", + "combined_tone_level", + "critical_band_corners", + "critical_band_level", + "critical_bandwidth_engineering", + "energy_sum_level", + "fluctuation_strength", + "fluctuation_strength_am_noise", + "fluctuation_strength_ecma", + "masking_index", + "mean_audibility", + "mean_audibility_uncertainty", + "mean_narrowband_level", + "prominence_ratio", + "psychoacoustic_annoyance", + "psychoacoustic_annoyance_from_signal", + "resolve_tones_separately", + "roughness_ecma", + "sharpness_din", + "sharpness_din_from_specific", + "tonality_ecma", + "tone_audibility", + "tone_level", + "tone_to_noise_ratio", + "two_tone_separation_frequency", +] diff --git a/src/phonometry/psychoacoustics/psychoacoustic_annoyance.py b/src/phonometry/psychoacoustics/quality/annoyance.py similarity index 97% rename from src/phonometry/psychoacoustics/psychoacoustic_annoyance.py rename to src/phonometry/psychoacoustics/quality/annoyance.py index 7e2d6c2a6..bee66006f 100644 --- a/src/phonometry/psychoacoustics/psychoacoustic_annoyance.py +++ b/src/phonometry/psychoacoustics/quality/annoyance.py @@ -34,7 +34,7 @@ directly. :func:`psychoacoustic_annoyance_from_signal` is a convenience that derives them from a calibrated pressure signal using the library's existing models -- N5/S from ISO 532-1 Zwicker loudness and DIN 45692 sharpness, R from -ECMA-418-2 roughness and F from :func:`~phonometry.psychoacoustics.fluctuation_strength. +ECMA-418-2 roughness and F from :func:`~phonometry.psychoacoustics.quality.fluctuation_strength. fluctuation_strength`. That composite mixes model families (Zwicker N5/S, Sottek R, Osses F); the original PA model was calibrated with Zwicker-family sensations, so the signal convenience is an engineering estimate, while @@ -98,8 +98,8 @@ class PsychoacousticAnnoyanceResult: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the PA value and the ``wS`` / ``wFR`` term contributions.""" - from .._i18n import check_language - from .._plot.psychoacoustics import plot_psychoacoustic_annoyance + from ..._i18n import check_language + from ..._plot.psychoacoustics import plot_psychoacoustic_annoyance return plot_psychoacoustic_annoyance(self, ax=ax, language=check_language(language), **kwargs) @@ -195,8 +195,8 @@ def psychoacoustic_annoyance_from_signal( :param calibration_factor: Digital-units-to-Pa factor applied to ``x``. :return: A :class:`PsychoacousticAnnoyanceResult`. """ + from ..loudness.zwicker import loudness_zwicker from .fluctuation_strength import fluctuation_strength as _fluctuation_strength - from .loudness_zwicker import loudness_zwicker from .roughness_ecma import roughness_ecma from .sharpness import sharpness_din_from_specific diff --git a/src/phonometry/psychoacoustics/fluctuation_strength.py b/src/phonometry/psychoacoustics/quality/fluctuation_strength.py similarity index 99% rename from src/phonometry/psychoacoustics/fluctuation_strength.py rename to src/phonometry/psychoacoustics/quality/fluctuation_strength.py index f27e8e221..ecdd5f5ee 100644 --- a/src/phonometry/psychoacoustics/fluctuation_strength.py +++ b/src/phonometry/psychoacoustics/quality/fluctuation_strength.py @@ -208,8 +208,8 @@ class FluctuationStrengthResult: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the specific fluctuation strength against critical-band rate.""" - from .._i18n import check_language - from .._plot.psychoacoustics import plot_fluctuation_strength + from ..._i18n import check_language + from ..._plot.psychoacoustics import plot_fluctuation_strength return plot_fluctuation_strength(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/psychoacoustics/fluctuation_strength_ecma.py b/src/phonometry/psychoacoustics/quality/fluctuation_strength_ecma.py similarity index 98% rename from src/phonometry/psychoacoustics/fluctuation_strength_ecma.py rename to src/phonometry/psychoacoustics/quality/fluctuation_strength_ecma.py index 3efacf587..3b1bef368 100644 --- a/src/phonometry/psychoacoustics/fluctuation_strength_ecma.py +++ b/src/phonometry/psychoacoustics/quality/fluctuation_strength_ecma.py @@ -6,9 +6,10 @@ ECMA-418-2:2025 (Clause 9, Sottek Hearing Model). The shared auditory front-end (Clause 5: outer/middle-ear filter, 53-band auditory filter bank, compressive nonlinearity of Formulae 23-25) is reused from -:mod:`.loudness_ecma`; this module adds the fluctuation-strength chain, which -mirrors the roughness chain of Clause 7 but replaces the DFT-based envelope -analysis with High-resolution Spectral Analysis (HSA): +:mod:`phonometry.psychoacoustics.loudness.ecma`; this module adds the +fluctuation-strength chain, which mirrors the roughness chain of Clause 7 but +replaces the DFT-based envelope analysis with High-resolution Spectral +Analysis (HSA): * fluctuation-strength zero-padding (Clause 5.1.2.2) and segmentation (Clause 5.1.5.2) with the fixed block/hop :math:`s_b = 65536` / @@ -104,9 +105,9 @@ if TYPE_CHECKING: from matplotlib.axes import Axes -from .._internal.utils import _typesignal -from .._internal.validation import require_1d_signal, require_positive -from .loudness_ecma import ( +from ..._internal.utils import _typesignal +from ..._internal.validation import require_1d_signal, require_positive +from ..loudness.ecma import ( _CBF, _F_CENTRE, _FS, @@ -221,8 +222,8 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - specific-fluctuation-strength heatmap. Requires matplotlib (``pip install phonometry[plot]``). """ - from .._i18n import check_language - from .._plot.psychoacoustics import plot_ecma_fluctuation_strength + from ..._i18n import check_language + from ..._plot.psychoacoustics import plot_ecma_fluctuation_strength return plot_ecma_fluctuation_strength(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/psychoacoustics/roughness_ecma.py b/src/phonometry/psychoacoustics/quality/roughness_ecma.py similarity index 98% rename from src/phonometry/psychoacoustics/roughness_ecma.py rename to src/phonometry/psychoacoustics/quality/roughness_ecma.py index 411b507bb..b4f13d8de 100644 --- a/src/phonometry/psychoacoustics/roughness_ecma.py +++ b/src/phonometry/psychoacoustics/quality/roughness_ecma.py @@ -6,8 +6,8 @@ (Clause 7). The shared auditory front-end (Clause 5: outer/middle-ear filter, 53-band gammatone-like filter bank, half-wave rectification, compressive nonlinearity to the specific basis loudness ``N'_basis(l, z)`` of Formula 25) -is reused from :mod:`.loudness_ecma`; this module adds the roughness-specific -chain: +is reused from :mod:`phonometry.psychoacoustics.loudness.ecma`; this module +adds the roughness-specific chain: * roughness-specific zero-padding (Clause 5.1.2.2) and segmentation (Clause 5.1.5.2) with the fixed block/hop :math:`s_b = 16384` / @@ -58,9 +58,9 @@ if TYPE_CHECKING: from matplotlib.axes import Axes -from .._internal.utils import _typesignal -from .._internal.validation import require_1d_signal -from .loudness_ecma import ( +from ..._internal.utils import _typesignal +from ..._internal.validation import require_1d_signal +from ..loudness.ecma import ( _CBF, _EPS, _F_CENTRE, @@ -181,8 +181,8 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - Draws the time-dependent roughness R(l50) and a specific-roughness heatmap. Requires matplotlib (``pip install phonometry[plot]``). """ - from .._i18n import check_language - from .._plot.psychoacoustics import plot_ecma_roughness + from ..._i18n import check_language + from ..._plot.psychoacoustics import plot_ecma_roughness return plot_ecma_roughness(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/psychoacoustics/sharpness.py b/src/phonometry/psychoacoustics/quality/sharpness.py similarity index 99% rename from src/phonometry/psychoacoustics/sharpness.py rename to src/phonometry/psychoacoustics/quality/sharpness.py index 8fd173f46..b1fbd4bc0 100644 --- a/src/phonometry/psychoacoustics/sharpness.py +++ b/src/phonometry/psychoacoustics/quality/sharpness.py @@ -19,7 +19,7 @@ import numpy as np -from .loudness_zwicker import ZwickerLoudness, loudness_zwicker +from ..loudness.zwicker import ZwickerLoudness, loudness_zwicker _DZ = 0.1 # Bark step of the ISO 532-1 specific-loudness pattern _Z = np.arange(1, 241) * _DZ # Bark bin centers (0.1 .. 24.0), reference convention diff --git a/src/phonometry/psychoacoustics/tonality.py b/src/phonometry/psychoacoustics/quality/tonality.py similarity index 98% rename from src/phonometry/psychoacoustics/tonality.py rename to src/phonometry/psychoacoustics/quality/tonality.py index 21c583263..752b407fc 100644 --- a/src/phonometry/psychoacoustics/tonality.py +++ b/src/phonometry/psychoacoustics/quality/tonality.py @@ -22,9 +22,9 @@ import numpy as np -from .._internal.utils import _typesignal -from .._internal.warnings import PhonometryWarning -from ..signals.spectra import _welch_autospectrum +from ..._internal.utils import _typesignal +from ..._internal.warnings import PhonometryWarning +from ...signals.spectra import _welch_autospectrum if TYPE_CHECKING: from matplotlib.axes import Axes @@ -130,8 +130,8 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - :param kwargs: Forwarded to the assessed-tone marker ``plot`` call. :return: The axes. """ - from .._i18n import check_language - from .._plot.psychoacoustics import plot_tone_assessment + from ..._i18n import check_language + from ..._plot.psychoacoustics import plot_tone_assessment return plot_tone_assessment( self, ax=ax, language=check_language(language), **kwargs diff --git a/src/phonometry/psychoacoustics/tonality_ecma.py b/src/phonometry/psychoacoustics/quality/tonality_ecma.py similarity index 94% rename from src/phonometry/psychoacoustics/tonality_ecma.py rename to src/phonometry/psychoacoustics/quality/tonality_ecma.py index 07ce32038..97bd42e19 100644 --- a/src/phonometry/psychoacoustics/tonality_ecma.py +++ b/src/phonometry/psychoacoustics/quality/tonality_ecma.py @@ -5,9 +5,11 @@ Clean-room implementation of the tonality signal chain of ECMA-418-2:2025 (Clause 6.2). The shared auditory front-end (Clause 5) and the ACF-based tonal/noise decomposition with the full Clause 6.2.3 band averaging -(Clause 6.2.2-6.2.7, :func:`loudness_ecma._tonal_noise_split`) are reused -from :mod:`.loudness_ecma` -- loudness and tonality therefore report the -same underlying ``N'_tonal(l, z)`` for the same signal; this module adds +(Clause 6.2.2-6.2.7, +:func:`phonometry.psychoacoustics.loudness.ecma._tonal_noise_split`) are +reused from :mod:`phonometry.psychoacoustics.loudness.ecma` -- loudness and +tonality therefore report the same underlying ``N'_tonal(l, z)`` for the same +signal; this module adds * the tonality output stages (Clause 6.2.8-6.2.11): the overall-SNR gate ``q(l)`` (Formulae 49-50), the time-dependent specific tonality @@ -37,9 +39,9 @@ if TYPE_CHECKING: from matplotlib.axes import Axes -from .._internal.utils import _typesignal -from .._internal.validation import require_1d_signal -from .loudness_ecma import ( +from ..._internal.utils import _typesignal +from ..._internal.validation import require_1d_signal +from ..loudness.ecma import ( _CBF, _EPS, _F_CENTRE, @@ -96,8 +98,8 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - Adds a tonality-vs-time panel. Requires matplotlib (``pip install phonometry[plot]``). """ - from .._i18n import check_language - from .._plot.psychoacoustics import plot_ecma_tonality + from ..._i18n import check_language + from ..._plot.psychoacoustics import plot_ecma_tonality return plot_ecma_tonality(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/psychoacoustics/tone_audibility.py b/src/phonometry/psychoacoustics/quality/tone_audibility.py similarity index 99% rename from src/phonometry/psychoacoustics/tone_audibility.py rename to src/phonometry/psychoacoustics/quality/tone_audibility.py index 1fe9b52c6..298ca356f 100644 --- a/src/phonometry/psychoacoustics/tone_audibility.py +++ b/src/phonometry/psychoacoustics/quality/tone_audibility.py @@ -103,7 +103,7 @@ from matplotlib.axes import Axes from numpy.typing import ArrayLike, NDArray - from .._report.metadata import ReportMetadata + from ..._report.metadata import ReportMetadata #: Constant term of the critical bandwidth Δfc (Formula (2)), in Hz. _DFC_CONSTANT = 25.0 @@ -1100,8 +1100,8 @@ def plot( :return: The axes. :raises ValueError: If ``view`` is not one of the two names above. """ - from .._i18n import check_language - from .._plot.psychoacoustics import ( + from ..._i18n import check_language + from ..._plot.psychoacoustics import ( plot_tone_audibility, plot_tone_audibility_levels, ) @@ -1154,14 +1154,14 @@ def report( (``pip install phonometry[report]``), or matplotlib is missing for the embedded figure (``pip install phonometry[plot]``). """ - from .._i18n import check_language + from ..._i18n import check_language check_language(language) if engine != "reportlab": raise ValueError( f"Unknown report engine {engine!r}; only 'reportlab' is supported." ) - from .._report.iso1996_tone import render_tone_audibility_report + from ..._report.iso1996_tone import render_tone_audibility_report return render_tone_audibility_report( self, path, metadata=metadata, verbose=verbose, language=language diff --git a/tests/conftest.py b/tests/conftest.py index 1fe9ecf58..2791ca154 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,11 +36,11 @@ def pytest_configure(config): "tests/simulation/test_elastic_fluid_solid.py", "tests/test_impact_insulation.py", "tests/building/measurement/test_insulation.py", - "tests/psychoacoustics/test_tonality_ecma.py", - "tests/psychoacoustics/test_loudness_ecma.py", - "tests/psychoacoustics/test_fluctuation_strength_ecma.py", - "tests/psychoacoustics/test_loudness_moore_glasberg_time.py", - "tests/psychoacoustics/test_loudness_zwicker.py", + "tests/psychoacoustics/quality/test_tonality_ecma.py", + "tests/psychoacoustics/loudness/test_ecma.py", + "tests/psychoacoustics/quality/test_fluctuation_strength_ecma.py", + "tests/psychoacoustics/loudness/test_moore_glasberg_time.py", + "tests/psychoacoustics/loudness/test_zwicker.py", "tests/underwater/test_numerical_propagation.py", "tests/filters/test_iec61260_report.py", "tests/room/test_room_ir.py", diff --git a/tests/data/iso532_1/README.md b/tests/data/iso532_1/README.md index ca293c1d8..a89ce3426 100644 --- a/tests/data/iso532_1/README.md +++ b/tests/data/iso532_1/README.md @@ -38,7 +38,7 @@ result workbooks, not original ISO files. ISO publishes this attachment so that implementations of the ISO 532-1 method can be validated against the standard's reference signals and tolerances (Annex B). The copies in this folder serve **exactly that purpose**: they are -consumed by `tests/psychoacoustics/test_loudness_zwicker.py` and `scripts/conformance_report.py` to +consumed by `tests/psychoacoustics/loudness/test_zwicker.py` and `scripts/conformance_report.py` to demonstrate conformance of this library's independent implementation. They are **not** part of the `phonometry` package, are not installed with it, and are not covered by this repository's MIT licence — ISO's terms apply to them. diff --git a/tests/psychoacoustics/test_loudness_contours.py b/tests/psychoacoustics/loudness/test_contours.py similarity index 100% rename from tests/psychoacoustics/test_loudness_contours.py rename to tests/psychoacoustics/loudness/test_contours.py diff --git a/tests/psychoacoustics/test_loudness_ecma.py b/tests/psychoacoustics/loudness/test_ecma.py similarity index 94% rename from tests/psychoacoustics/test_loudness_ecma.py rename to tests/psychoacoustics/loudness/test_ecma.py index be5ab1695..ad437955e 100644 --- a/tests/psychoacoustics/test_loudness_ecma.py +++ b/tests/psychoacoustics/loudness/test_ecma.py @@ -52,7 +52,7 @@ def test_calibration_constant_is_the_tabulated_c_n() -> None: # standard's value verbatim (shared oracle in tests/reference_data.py). from reference_data import ECMA418_2_LOUDNESS_C_N - from phonometry.psychoacoustics.loudness_ecma import _C_N + from phonometry.psychoacoustics.loudness.ecma import _C_N assert _C_N == ECMA418_2_LOUDNESS_C_N @@ -108,7 +108,8 @@ def test_free_and_diffuse_fields_differ() -> None: free = loudness_ecma(x, FS, field="free").loudness diffuse = loudness_ecma(x, FS, field="diffuse").loudness # Both plausible loudspeaker-range values, but the ear filter differs. - assert free > 1.0 and diffuse > 1.0 + assert free > 1.0 + assert diffuse > 1.0 assert free != diffuse @@ -141,18 +142,21 @@ def test_result_structure(ref_1k_40: EcmaLoudness) -> None: def test_invalid_field() -> None: + tone = _tone(1000.0, 40.0, seconds=0.5) with pytest.raises(ValueError): - loudness_ecma(_tone(1000.0, 40.0, seconds=0.5), FS, field="reverberant") + loudness_ecma(tone, FS, field="reverberant") def test_invalid_fs() -> None: + tone = _tone(1000.0, 40.0, seconds=0.5) with pytest.raises(ValueError): - loudness_ecma(_tone(1000.0, 40.0, seconds=0.5), 0.0) + loudness_ecma(tone, 0.0) def test_empty_signal() -> None: + empty = np.array([]) with pytest.raises(ValueError): - loudness_ecma(np.array([]), FS) + loudness_ecma(empty, FS) @pytest.mark.xdist_group("ecma-loudness-ref") @@ -175,7 +179,7 @@ def _reference_band_acf(rect: np.ndarray, s_b: int) -> np.ndarray: Retained reference for the column-vectorized ``_band_acf``: the production kernel must reproduce this loop bit for bit. """ - from phonometry.psychoacoustics.loudness_ecma import _EPS + from phonometry.psychoacoustics.loudness.ecma import _EPS n_fft = 2 * s_b spec = np.fft.fft(rect, n=n_fft, axis=1) @@ -197,7 +201,7 @@ def _short_band_signals() -> tuple[list, int]: """Clause 5 front-end band-pass signals for a short two-tone signal.""" from scipy import signal as sp_signal - from phonometry.psychoacoustics.loudness_ecma import ( + from phonometry.psychoacoustics.loudness.ecma import ( _CBF, _auditory_bandpass, _ear_filter_sos, @@ -216,7 +220,7 @@ def _short_band_signals() -> tuple[list, int]: def test_band_acf_matches_reference_loop_bitwise() -> None: # The vectorized lag normalization must be bit-identical to the per-lag # reference loop for all four block sizes (8192/4096/2048/1024). - from phonometry.psychoacoustics.loudness_ecma import ( + from phonometry.psychoacoustics.loudness.ecma import ( _S_B, _band_acf, _segment_bs, @@ -237,7 +241,7 @@ def test_average_bands_matches_stacked_mean_bitwise() -> None: # bit-identical to the stacked np.mean formulation it replaced. import importlib - le = importlib.import_module("phonometry.psychoacoustics.loudness_ecma") + le = importlib.import_module("phonometry.psychoacoustics.loudness.ecma") p_bands, n_new = _short_band_signals() got = le._average_bands_full(p_bands, n_new) diff --git a/tests/psychoacoustics/test_loudness_moore_glasberg.py b/tests/psychoacoustics/loudness/test_moore_glasberg.py similarity index 98% rename from tests/psychoacoustics/test_loudness_moore_glasberg.py rename to tests/psychoacoustics/loudness/test_moore_glasberg.py index bc09ca6b0..e093d084c 100644 --- a/tests/psychoacoustics/test_loudness_moore_glasberg.py +++ b/tests/psychoacoustics/loudness/test_moore_glasberg.py @@ -82,7 +82,7 @@ def test_calibration_constant_is_the_tabulated_c() -> None: # tests/reference_data.py). from reference_data import ISO532_2_C - from phonometry.psychoacoustics.loudness_moore_glasberg import _C_SONE + from phonometry.psychoacoustics.loudness.moore_glasberg import _C_SONE assert _C_SONE == ISO532_2_C @@ -382,9 +382,11 @@ def test_invalid_inputs() -> None: loudness_moore_glasberg_from_spectrum([(1000.0, np.inf)]) with pytest.raises(ValueError, match="band_levels must contain exactly"): loudness_moore_glasberg_from_third_octave([60.0] * 10) # wrong length + empty = np.array([]) with pytest.raises(ValueError, match="Input signal 'x' cannot be empty"): - loudness_moore_glasberg(np.array([]), FS) + loudness_moore_glasberg(empty, FS) + short = np.ones(100) with pytest.raises( ValueError, match="'fs' must be a positive sampling rate" ): - loudness_moore_glasberg(np.ones(100), -1.0) + loudness_moore_glasberg(short, -1.0) diff --git a/tests/psychoacoustics/test_loudness_moore_glasberg_time.py b/tests/psychoacoustics/loudness/test_moore_glasberg_time.py similarity index 98% rename from tests/psychoacoustics/test_loudness_moore_glasberg_time.py rename to tests/psychoacoustics/loudness/test_moore_glasberg_time.py index b7a278d2d..82554da88 100644 --- a/tests/psychoacoustics/test_loudness_moore_glasberg_time.py +++ b/tests/psychoacoustics/loudness/test_moore_glasberg_time.py @@ -31,7 +31,7 @@ MooreGlasbergTimeVaryingLoudness, loudness_moore_glasberg_time, ) -from phonometry.psychoacoustics.loudness_moore_glasberg_time import _ALPHA_AL, _ALPHA_RL +from phonometry.psychoacoustics.loudness.moore_glasberg_time import _ALPHA_AL, _ALPHA_RL FS = 32000.0 # the Annex B/C sampling rate; keeps reference tones on FFT bins @@ -328,7 +328,8 @@ def test_result_fields_and_percentiles() -> None: # Higher exceedance fraction -> lower level. assert res.percentiles[5.0] >= res.percentiles[50.0] >= res.percentiles[95.0] assert res.n_max >= res.percentiles[5.0] - assert res.field == "free" and res.presentation == "binaural" + assert res.field == "free" + assert res.presentation == "binaural" def test_diotic_equals_binaural_and_exceeds_monaural() -> None: @@ -433,7 +434,7 @@ def test_low_freq_band_not_truncated_across_sample_rates() -> None: """ import importlib - _mgt_mod = importlib.import_module("phonometry.psychoacoustics.loudness_moore_glasberg_time") + _mgt_mod = importlib.import_module("phonometry.psychoacoustics.loudness.moore_glasberg_time") def _band_level(fs: float) -> float: p_rms = 2e-5 * 10.0 ** (60.0 / 20.0) @@ -460,12 +461,14 @@ def test_invalid_inputs_raise() -> None: ValueError, match="'fs' must be a positive sampling rate" ): loudness_moore_glasberg_time(tone, -1.0) + empty = np.array([]) with pytest.raises(ValueError, match="Input signal cannot be empty"): - loudness_moore_glasberg_time(np.array([]), FS) + loudness_moore_glasberg_time(empty, FS) + with_nan = np.array([1.0, np.nan, 2.0]) with pytest.raises( ValueError, match="Input signal must contain only finite values" ): - loudness_moore_glasberg_time(np.array([1.0, np.nan, 2.0]), FS) + loudness_moore_glasberg_time(with_nan, FS) def test_plot_smoke() -> None: diff --git a/tests/psychoacoustics/test_loudness_zwicker.py b/tests/psychoacoustics/loudness/test_zwicker.py similarity index 95% rename from tests/psychoacoustics/test_loudness_zwicker.py rename to tests/psychoacoustics/loudness/test_zwicker.py index e291d9b78..c203fa2f5 100644 --- a/tests/psychoacoustics/test_loudness_zwicker.py +++ b/tests/psychoacoustics/loudness/test_zwicker.py @@ -26,7 +26,7 @@ # presence gate, the expected-values JSON and the Annex B.5 recordings all # resolve from the same directory. Defaults to the in-repo fixtures. DATA = pathlib.Path(os.environ.get( - "ISO532_1_TESTDATA", str(pathlib.Path(__file__).parents[1] / "data" / "iso532_1") + "ISO532_1_TESTDATA", str(pathlib.Path(__file__).parents[2] / "data" / "iso532_1") )) # The ISO 532-1 Annex B validation data may be absent (its README documents a # removal policy); the tests that need it then skip rather than crash on import. @@ -310,7 +310,8 @@ def test_annex_b4_tone_pulses(case: str, num: int, level: float) -> None: exp = EXPECTED[case] x, fs = _read_wav_pa(DATA / f"iso532_1_test_signal_{num}.wav", level) res = loudness_zwicker(x, fs, stationary=False) - assert res.n5 is not None and res.loudness_vs_time is not None + assert res.n5 is not None + assert res.loudness_vs_time is not None # Nmax reproduces the workbook header to < 0.01 %; 1e-3 locks that in # (was 0.05, ~700x looser than the achieved accuracy). assert res.loudness == pytest.approx(exp["Nmax"], rel=1e-3), ( @@ -333,11 +334,11 @@ def test_n5_n10_use_full_rate_series(monkeypatch) -> None: ``loudness_vs_time`` output is unchanged (public contract).""" import sys - from phonometry.psychoacoustics.loudness_zwicker import _SR_LEVEL, _SR_LOUDNESS + from phonometry.psychoacoustics.loudness.zwicker import _SR_LEVEL, _SR_LOUDNESS # The package re-exports the *function* ``loudness_zwicker``, which shadows # the module attribute of the same name; go through sys.modules instead. - L = sys.modules["phonometry.psychoacoustics.loudness_zwicker"] + L = sys.modules["phonometry.psychoacoustics.loudness.zwicker"] seen: dict[int, np.ndarray] = {} orig = L._percentile @@ -349,7 +350,8 @@ def spy(values: np.ndarray, pct: int) -> float: monkeypatch.setattr(L, "_percentile", spy) x, fs = _read_wav_pa(DATA / "iso532_1_test_signal_10.wav", 70.0) res = loudness_zwicker(x, fs, stationary=False) - assert res.n5 is not None and res.loudness_vs_time is not None + assert res.n5 is not None + assert res.loudness_vs_time is not None full = seen[5] dec = _SR_LEVEL // _SR_LOUDNESS @@ -365,9 +367,12 @@ def spy(values: np.ndarray, pct: int) -> float: def test_time_varying_outputs() -> None: x = _tone(1000.0, 70.0, seconds=0.5) res = loudness_zwicker(x, FS) - assert res.time is not None and res.loudness_vs_time is not None + assert res.time is not None + assert res.loudness_vs_time is not None assert res.time.shape == res.loudness_vs_time.shape - assert res.n5 is not None and res.n10 is not None and res.n5 >= res.n10 + assert res.n5 is not None + assert res.n10 is not None + assert res.n5 >= res.n10 # --------------------------------------------------------------------------- @@ -406,12 +411,15 @@ def test_annex_b5_technical_signals(num: int) -> None: # --------------------------------------------------------------------------- def test_invalid_inputs() -> None: + ten_bands = np.zeros(10) with pytest.raises(ValueError, match="28"): - loudness_zwicker_from_spectrum(np.zeros(10)) + loudness_zwicker_from_spectrum(ten_bands) + levels = np.full(28, 60.0) with pytest.raises(ValueError, match="field"): - loudness_zwicker_from_spectrum(np.full(28, 60.0), field="reverberant") + loudness_zwicker_from_spectrum(levels, field="reverberant") + x = np.ones(1000) with pytest.raises(ValueError, match="fs"): - loudness_zwicker(np.ones(1000), 0) + loudness_zwicker(x, 0) def test_non_finite_inputs_rejected() -> None: @@ -428,8 +436,9 @@ def test_non_finite_inputs_rejected() -> None: def test_pathological_resampling_ratio_rejected() -> None: """gcd(48000, 44101) = 1 would demand a 48000/44101 polyphase filter; reject instead of hanging.""" + x = np.ones(1000) with pytest.raises(ValueError, match="resampl"): - loudness_zwicker(np.ones(1000), 44101) + loudness_zwicker(x, 44101) def test_diffuse_field_differs() -> None: @@ -443,8 +452,9 @@ def test_diffuse_field_differs() -> None: def test_minimal_length_validation() -> None: """Signals shorter than one 500 Hz output sample raise cleanly instead of crashing on an empty percentile buffer.""" + too_short = np.ones(48) with pytest.raises(ValueError, match="too short"): - loudness_zwicker(np.ones(48), FS) + loudness_zwicker(too_short, FS) res = loudness_zwicker(np.ones(96 * 4), FS) # exactly a few output samples assert res.loudness >= 0.0 diff --git a/tests/psychoacoustics/test_psychoacoustic_annoyance.py b/tests/psychoacoustics/quality/test_annoyance.py similarity index 94% rename from tests/psychoacoustics/test_psychoacoustic_annoyance.py rename to tests/psychoacoustics/quality/test_annoyance.py index c52a25489..768f3261b 100644 --- a/tests/psychoacoustics/test_psychoacoustic_annoyance.py +++ b/tests/psychoacoustics/quality/test_annoyance.py @@ -85,8 +85,10 @@ def test_pa_rejects_bad_inputs(bad: float) -> None: def test_pa_result_fields_and_plot() -> None: res = psychoacoustic_annoyance(15.0, 2.2, 0.4, 0.6) assert isinstance(res, PsychoacousticAnnoyanceResult) - assert res.n5 == 15.0 and res.sharpness == 2.2 - assert res.fluctuation_strength == 0.4 and res.roughness == 0.6 + assert res.n5 == 15.0 + assert res.sharpness == 2.2 + assert res.fluctuation_strength == 0.4 + assert res.roughness == 0.6 ax = res.plot() assert ax is not None @@ -100,5 +102,6 @@ def test_pa_from_signal_runs_and_is_positive() -> None: x = x / np.sqrt(np.mean(x**2)) * 2e-5 * 10 ** (70.0 / 20) res = psychoacoustic_annoyance_from_signal(x, fs) assert isinstance(res, PsychoacousticAnnoyanceResult) - assert np.isfinite(res.annoyance) and res.annoyance > 0.0 + assert np.isfinite(res.annoyance) + assert res.annoyance > 0.0 assert res.n5 > 0.0 diff --git a/tests/psychoacoustics/test_fluctuation_strength.py b/tests/psychoacoustics/quality/test_fluctuation_strength.py similarity index 100% rename from tests/psychoacoustics/test_fluctuation_strength.py rename to tests/psychoacoustics/quality/test_fluctuation_strength.py diff --git a/tests/psychoacoustics/test_fluctuation_strength_ecma.py b/tests/psychoacoustics/quality/test_fluctuation_strength_ecma.py similarity index 99% rename from tests/psychoacoustics/test_fluctuation_strength_ecma.py rename to tests/psychoacoustics/quality/test_fluctuation_strength_ecma.py index dcb95ce21..66b4d5712 100644 --- a/tests/psychoacoustics/test_fluctuation_strength_ecma.py +++ b/tests/psychoacoustics/quality/test_fluctuation_strength_ecma.py @@ -22,7 +22,7 @@ from phonometry import EcmaFluctuationStrength, fluctuation_strength_ecma -_fse = importlib.import_module("phonometry.psychoacoustics.fluctuation_strength_ecma") +_fse = importlib.import_module("phonometry.psychoacoustics.quality.fluctuation_strength_ecma") FS = 48000 P0 = 2e-5 diff --git a/tests/psychoacoustics/test_roughness_ecma.py b/tests/psychoacoustics/quality/test_roughness_ecma.py similarity index 97% rename from tests/psychoacoustics/test_roughness_ecma.py rename to tests/psychoacoustics/quality/test_roughness_ecma.py index fdfa7044d..8218062fd 100644 --- a/tests/psychoacoustics/test_roughness_ecma.py +++ b/tests/psychoacoustics/quality/test_roughness_ecma.py @@ -74,7 +74,7 @@ def test_calibration_constant_is_the_tabulated_c_r() -> None: # standard's value verbatim (shared oracle in tests/reference_data.py). from reference_data import ECMA418_2_ROUGHNESS_C_R - from phonometry.psychoacoustics.roughness_ecma import _C_R + from phonometry.psychoacoustics.quality.roughness_ecma import _C_R assert _C_R == ECMA418_2_ROUGHNESS_C_R @@ -159,7 +159,7 @@ def test_time_dependent_pchip_batch_matches_per_band_bitwise() -> None: # must stay bit-identical to the per-band interpolator loop it replaced. from scipy.interpolate import PchipInterpolator - from phonometry.psychoacoustics.roughness_ecma import _CBF, _R_S50 + from phonometry.psychoacoustics.quality.roughness_ecma import _CBF, _R_S50 rng = np.random.default_rng(20260715) block_times = np.arange(9) * (4096.0 / FS) diff --git a/tests/psychoacoustics/test_sharpness.py b/tests/psychoacoustics/quality/test_sharpness.py similarity index 96% rename from tests/psychoacoustics/test_sharpness.py rename to tests/psychoacoustics/quality/test_sharpness.py index 2650c2db5..1556e7d32 100644 --- a/tests/psychoacoustics/test_sharpness.py +++ b/tests/psychoacoustics/quality/test_sharpness.py @@ -18,7 +18,7 @@ sharpness_din, sharpness_din_from_specific, ) -from phonometry.psychoacoustics.sharpness import reference_sound +from phonometry.psychoacoustics.quality.sharpness import reference_sound FS = 48000 @@ -90,7 +90,7 @@ def test_reference_signal_is_one_acum() -> None: def test_k_in_normative_range() -> None: """Clause 5.2: the normalization constant satisfies 0.105 <= k < 0.115.""" - from phonometry.psychoacoustics.sharpness import _k_din + from phonometry.psychoacoustics.quality.sharpness import _k_din assert 0.105 <= _k_din() < 0.115 @@ -144,7 +144,7 @@ def test_annex_b_literal_factor() -> None: """Formulas (B.1)/(B.2) print S = 0,11 * moment; the implementation must use the literal 0.11, not a self-derived per-variant constant (which would shift every Aures result ~4 % against other implementations).""" - from phonometry.psychoacoustics.sharpness import _K_ANNEX_B + from phonometry.psychoacoustics.quality.sharpness import _K_ANNEX_B assert _K_ANNEX_B == 0.11 diff --git a/tests/psychoacoustics/test_tonality.py b/tests/psychoacoustics/quality/test_tonality.py similarity index 98% rename from tests/psychoacoustics/test_tonality.py rename to tests/psychoacoustics/quality/test_tonality.py index f08cfea8c..d500eb99b 100644 --- a/tests/psychoacoustics/test_tonality.py +++ b/tests/psychoacoustics/quality/test_tonality.py @@ -22,7 +22,10 @@ ) from phonometry import prominence_ratio, tone_to_noise_ratio -from phonometry.psychoacoustics.tonality import _critical_band, _proximity_spacing +from phonometry.psychoacoustics.quality.tonality import ( + _critical_band, + _proximity_spacing, +) FS = 48000 @@ -95,7 +98,7 @@ def test_pr_of_synthetic_tone_matches_analytic() -> None: # the 8 s default (measured error: 0.12 dB at 8 s, 0.037 dB at 16 s). x = _tone_in_noise(1000.0, tone_rms, noise_rms, seconds=16.0) n0 = noise_rms**2 / (FS / 2) - from phonometry.psychoacoustics.tonality import ( + from phonometry.psychoacoustics.quality.tonality import ( _LOWER_EDGE_COEFFS, _UPPER_EDGE_COEFFS, _fitted_edge, diff --git a/tests/psychoacoustics/test_tonality_ecma.py b/tests/psychoacoustics/quality/test_tonality_ecma.py similarity index 94% rename from tests/psychoacoustics/test_tonality_ecma.py rename to tests/psychoacoustics/quality/test_tonality_ecma.py index 7c4e9cd1b..f079d9a84 100644 --- a/tests/psychoacoustics/test_tonality_ecma.py +++ b/tests/psychoacoustics/quality/test_tonality_ecma.py @@ -67,7 +67,7 @@ def test_calibration_constant_is_the_tabulated_c_t() -> None: # standard's value verbatim (shared oracle in tests/reference_data.py). from reference_data import ECMA418_2_TONALITY_C_T - from phonometry.psychoacoustics.tonality_ecma import _C_T + from phonometry.psychoacoustics.quality.tonality_ecma import _C_T assert _C_T == ECMA418_2_TONALITY_C_T @@ -79,13 +79,13 @@ def test_decision_thresholds_are_the_standard_constants() -> None: # audibility criterion on the total basis loudness (Clause 5.1.9). import reference_data as ref - from phonometry.psychoacoustics.loudness_ecma import ( + from phonometry.psychoacoustics.loudness.ecma import ( AUDIBILITY_THRESHOLD_SONE_HMS, ) - from phonometry.psychoacoustics.roughness_ecma import ( + from phonometry.psychoacoustics.quality.roughness_ecma import ( PROMINENT_ROUGHNESS_ASPER, ) - from phonometry.psychoacoustics.tonality_ecma import ( + from phonometry.psychoacoustics.quality.tonality_ecma import ( PROMINENT_TONALITY_TU_HMS, ) @@ -179,8 +179,8 @@ def test_loudness_and_tonality_share_the_tonal_split(monkeypatch) -> None: from phonometry import loudness_ecma - L = sys.modules["phonometry.psychoacoustics.loudness_ecma"] - T = sys.modules["phonometry.psychoacoustics.tonality_ecma"] + L = sys.modules["phonometry.psychoacoustics.loudness.ecma"] + T = sys.modules["phonometry.psychoacoustics.quality.tonality_ecma"] assert T._tonal_noise_split is L._tonal_noise_split recorded = [] @@ -216,8 +216,8 @@ def test_band_range_uses_edge_midpoints() -> None: # by the centre frequency. For an f_low that lies between band z's centre # F(z) and its upper boundary (F(z)+F(z+0.5))/2, band z must still be the # lower edge z_L (a centre-frequency threshold would wrongly exclude it). - from phonometry.psychoacoustics.loudness_ecma import _F_CENTRE - from phonometry.psychoacoustics.tonality_ecma import _band_range + from phonometry.psychoacoustics.loudness.ecma import _F_CENTRE + from phonometry.psychoacoustics.quality.tonality_ecma import _band_range z = 20 upper_mid = 0.5 * (_F_CENTRE[z] + _F_CENTRE[z + 1]) @@ -244,7 +244,8 @@ def test_free_and_diffuse_fields_differ() -> None: x = _tone(1000.0, 60.0, seconds=0.7) free = tonality_ecma(x, FS, field="free").tonality diffuse = tonality_ecma(x, FS, field="diffuse").tonality - assert free > 0.5 and diffuse > 0.5 + assert free > 0.5 + assert diffuse > 0.5 assert free != diffuse diff --git a/tests/psychoacoustics/test_tone_audibility.py b/tests/psychoacoustics/quality/test_tone_audibility.py similarity index 99% rename from tests/psychoacoustics/test_tone_audibility.py rename to tests/psychoacoustics/quality/test_tone_audibility.py index 69683d91a..77d398cc8 100644 --- a/tests/psychoacoustics/test_tone_audibility.py +++ b/tests/psychoacoustics/quality/test_tone_audibility.py @@ -295,7 +295,7 @@ def test_analyze_spectrum_137hz_tone_audibility() -> None: def test_detect_tone_at_first_line_not_dropped() -> None: # Regression: a peak on the very first line must not be skipped because the # left-neighbour test wrapped around to the last line (lev[-1]). - from phonometry.psychoacoustics.tone_audibility import _detect_tones + from phonometry.psychoacoustics.quality.tone_audibility import _detect_tones freqs = np.arange(100.0, 100.0 + 2.7 * 40, 2.7) levels = np.full(freqs.size, 50.0) @@ -673,7 +673,7 @@ def test_table_e2_fg_uncertainty() -> None: """The '2 FG' row of Table E.2: with the N summated tone levels as the K summands (the Clause 6 reading for combined tones) and the decisive tone's noise lines, U reproduces the printed 3.21 dB.""" - from phonometry.psychoacoustics.tone_audibility import ( + from phonometry.psychoacoustics.quality.tone_audibility import ( _mean_narrowband_level_lines, ) @@ -688,7 +688,7 @@ def test_table_e2_fg_uncertainty() -> None: def test_uncertainty_constants_and_validation() -> None: - from phonometry.psychoacoustics.tone_audibility import ( + from phonometry.psychoacoustics.quality.tone_audibility import ( COVERAGE_FACTOR_90, SIGMA_NARROWBAND_LEVEL, ) @@ -778,7 +778,7 @@ def test_din_anhang_i_decisive_tone_uncertainty() -> None: """Tabelle I.10 row k = 2: the Clause 6 / Anhang G extended uncertainty of the decisive tone reproduces the printed u = 3.18 dB from the K = 4 tone lines and the M noise lines of the final Formula (6) iteration.""" - from phonometry.psychoacoustics.tone_audibility import ( + from phonometry.psychoacoustics.quality.tone_audibility import ( _mean_narrowband_level_lines, ) diff --git a/tests/psychoacoustics/test_erb_scale.py b/tests/psychoacoustics/test_erb_scale.py index 923ebc43b..8807df288 100644 --- a/tests/psychoacoustics/test_erb_scale.py +++ b/tests/psychoacoustics/test_erb_scale.py @@ -44,7 +44,7 @@ #: The ISO 532-2 loudness *module* (the package attribute of that name is the #: re-exported function, so it has to be imported explicitly). -mg = importlib.import_module("phonometry.psychoacoustics.loudness_moore_glasberg") +mg = importlib.import_module("phonometry.psychoacoustics.loudness.moore_glasberg") def _published_erb(frequency: float) -> float: diff --git a/tests/psychoacoustics/test_iso1996_tone_report.py b/tests/psychoacoustics/test_iso1996_tone_report.py index 251b72477..e6a890f66 100644 --- a/tests/psychoacoustics/test_iso1996_tone_report.py +++ b/tests/psychoacoustics/test_iso1996_tone_report.py @@ -9,7 +9,7 @@ tonal adjustment ``K`` (ISO 1996-2:2017 Table J.1), the decisive tone frequency and the metadata appear in the extracted text. The tone-audibility algorithm itself is validated against the ISO/PAS 20065 Annex E oracle elsewhere -(tests/psychoacoustics/test_tone_audibility.py); this fiche test reuses the same +(tests/psychoacoustics/quality/test_tone_audibility.py); this fiche test reuses the same Annex E spectrum so its numbers are documented. """ diff --git a/tests/psychoacoustics/test_iso532_report.py b/tests/psychoacoustics/test_iso532_report.py index 728b89278..0ee4aa7b1 100644 --- a/tests/psychoacoustics/test_iso532_report.py +++ b/tests/psychoacoustics/test_iso532_report.py @@ -7,7 +7,7 @@ engines are rejected, XML specials in metadata do not break reportlab, and the maximum-loudness verdict renders both ways. The loudness algorithm itself is validated against the ISO 532-1 Annex B data elsewhere -(tests/psychoacoustics/test_loudness_zwicker.py); here a fixed synthetic +(tests/psychoacoustics/loudness/test_zwicker.py); here a fixed synthetic one-third-octave spectrum keeps the fiche test self-contained. """ diff --git a/tests/test_deprecated_aliases.py b/tests/test_deprecated_aliases.py index b20c15c56..4964ac3dc 100644 --- a/tests/test_deprecated_aliases.py +++ b/tests/test_deprecated_aliases.py @@ -33,8 +33,8 @@ def test_loudness_module_attribute_access_warns_and_delegates() -> None: import phonometry.loudness # noqa: F401 (PEP 562 shim; import is silent) shim = sys.modules["phonometry.loudness"] - target = sys.modules["phonometry.psychoacoustics.loudness_zwicker"] - with pytest.warns(DeprecationWarning, match="loudness_zwicker"): + target = sys.modules["phonometry.psychoacoustics.loudness.zwicker"] + with pytest.warns(DeprecationWarning, match=r"loudness\.zwicker"): cls = shim.ZwickerLoudness assert cls is target.ZwickerLoudness with pytest.warns(DeprecationWarning, match="deprecated since phonometry 3.1"): @@ -478,6 +478,19 @@ def test_pre_move_module_path_still_imports(path: str) -> None: "phonometry.materials.scattering_diffusion", "phonometry.materials.slow_sound_absorber", "phonometry.materials.sound_absorption", + "phonometry.psychoacoustics.fluctuation_strength", + "phonometry.psychoacoustics.fluctuation_strength_ecma", + "phonometry.psychoacoustics.loudness_contours", + "phonometry.psychoacoustics.loudness_ecma", + "phonometry.psychoacoustics.loudness_moore_glasberg", + "phonometry.psychoacoustics.loudness_moore_glasberg_time", + "phonometry.psychoacoustics.loudness_zwicker", + "phonometry.psychoacoustics.psychoacoustic_annoyance", + "phonometry.psychoacoustics.roughness_ecma", + "phonometry.psychoacoustics.sharpness", + "phonometry.psychoacoustics.tonality", + "phonometry.psychoacoustics.tonality_ecma", + "phonometry.psychoacoustics.tone_audibility", ] diff --git a/tests/test_package_architecture.py b/tests/test_package_architecture.py index 47de43735..9ea02c4b1 100644 --- a/tests/test_package_architecture.py +++ b/tests/test_package_architecture.py @@ -88,6 +88,8 @@ def _edges() -> set[tuple[str, str, str]]: ("building", "prediction", "measurement"), # A metadiffuser well is a slit absorber: it reuses the porous air state. ("materials", "diffusers", "absorbers"), + # Every sound quality metric is read off a loudness pattern. + ("psychoacoustics", "quality", "loudness"), }