Core metrology guides: result figures, ResampledSignalResult.plot() and real introductions (#364)
* Add ResampledSignalResult.plot(): the delivered anti-alias filter against its design spec
* Rewrite the multichannel guide intro: concrete use cases, per-channel guarantees, Bendat & Piersol citations
* Give the levels page a real introduction: descriptor families, when-to-use guidance and survey references
* Time weighting: framing intro with detector history, ANSI S1.4/Bies references and sibling links
* Calibration: proper introduction (SPL vs dBFS choice, workflow framing) with Bies reference and sibling links
* Add eleven core-metrology figures: resampling spec, cepstrum variants, liftering, correlation, IR alignment, Hilbert envelope, cross-spectra, Golay recovery, TSA noise law, runs test
* Test signals: anti-alias figure with dual snippet, res.plot() mention, See also, docs mirror sync
* Cepstrum guide: variants and liftering figures with dual snippets, complex-cepstrum plot mention
* Correlation guide: normalization, IR-alignment and Hilbert-envelope figures with dual snippets
* Spectral analysis: cross-spectral density and coherent-output-SNR figures with dual snippets
* System measurement: Golay exact-recovery figure and plot mentions for the inversion workflow
* Synchronous averaging: sqrt(N) figure, diagnostics-tool guidance, See also, recording naming
* Data qualification: runs-test figure with dual snippet and peak-statistics plot mention
* MISO coherence: conditioning-order snippet, practical pitfalls and See also across the three trees
* Time-frequency: Harris window reference and See also across the three trees
* GUM uncertainty: map the domain pages that consume the clause-5 machinery, expand See also
* Filter banks: compliance plot mentions and recording naming in the crossover snippet
* Changelog entry for the core-metrology documentation figures and prose
* Regenerate llms-full.txt for the core-metrology documentation
* Drop the duplicated phase-axis translation key
Correct DSP estimator edge cases: TSA time axis, echo detection, EQ validation (#356)
* Report the true 1/fs time axis from time_synchronous_average
The averaged samples sit on the 1/fs sampling grid (each block is aligned
back to it by the band-limited fractional delay before averaging), but the
result reported a time axis with T/M spacing. For a non-integer number of
samples per period the two grids drift apart across the period: with
100.37 samples per period the noiseless recovery error read against the
reported axis was 9.3e-2, versus 2.8e-6 on the true m/fs grid.
Return times = arange(M)/fs, document that the M samples cover the period
only approximately when fs*T is not an integer (McFadden 1987 revised
model: a block average on the sampling grid, no angular resampling), and
assert the non-integer-period tests on the true grid, with a regression
test pinning the 100.37-sample case on both grids. Also soften the comb
response [0, 1] claim (float cancellation beside a tooth can exceed 1 by
a few 1e-9) and mark the ~14 dB N = 32 figure as derived from Eq. 8
rather than printed in the paper.
* Detect inverting echoes and off-sample delays in echo_detection
echo_detection picked the maximum of the signed power cepstrum, so an
inverting reflection (a < 0), whose first rahmonic is a itself and hence
negative, was silently missed: with a = -0.4 at 313 samples the true
rahmonic c[313] = -0.42 was ignored and an unrelated 4739-sample peak
returned. Peak-pick on |cepstrum| within the searched band instead and
report the signed cepstrum value at the peak as the reflection
coefficient (the Mercator series ln(1 + a e^{-j theta}) holds for either
sign of a).
Also refine the reported delay by three-point parabolic interpolation of
|cepstrum| through the peak, so an off-sample delay (e.g. 313.5 samples)
is estimated to a fraction of a sample, and document the bin-splitting
caveat: at off-sample delays the peak-sample coefficient underestimates
|a| (about 65 % of it midway between bins). One-line convention note
distinguishing Milner's IDFT(ln|X|^2) power cepstrum from Bogert's
squared original. Tests cover a < 0 (exact impulse-echo closed form and
a noise source) and the interpolated half-sample delay.
* Validate parametric-EQ sections at the numerical extremes
Three validation gaps in the RBJ cookbook designer:
- A wide 'bw' at an f0 near Nyquist drives the w0/sin(w0) warping factor
into math.sinh overflow, escaping as a raw OverflowError; slightly less
extreme pairs produced a large but finite alpha whose rounded a2 landed
exactly at -1.0: poles on the unit circle designed silently. Wrap the
sinh with an informative ValueError and check the realized coefficients
against the stability triangle (|a2| < 1, |a1| < 1 + a2) after
normalization.
- A shelf slope beyond the gain-dependent bound (A + 1/A)/(A + 1/A - 2)
makes the cookbook's alpha complex and surfaced as a bare 'math domain
error'. Validate the bound at section construction with the limit in
the message (any slope is fine at 0 dB, where A = 1).
- Non-finite f0/gain_db/q/bw/slope passed validation silently (NaN
compares false against every bound) and produced NaN/inf coefficient
rows. Require finiteness explicitly.
Tests cover all three paths plus extreme-but-valid sections that must
keep designing stably.
* Scope the envelope-spectrum amplitude claim and add the band-pass front end
The docstring promised that a sinusoidal modulation 'reads out as a line
at its exact amplitude'; that holds only when the modulation frequency
falls on an analysis bin. Off-bin lines read low by the taper's
scalloping loss (about 1.42 dB, ~15 %, for the default Hann midway
between bins), like any single-record amplitude spectrum. State the
on-bin condition on the closed forms and the caveat in the module,
function and result docstrings, pinned by a midway-between-bins test.
Also add the optional band=(low, high) pre-filter to envelope_spectrum:
a zero-phase 4th-order Butterworth band-pass ahead of the detector, the
band-pass front end of Bendat & Piersol Figure 13.11 and the classical
bearing-envelope chain (isolate the excited resonance band, then
envelope it). The band travels with the result; a test shows it
recovering the exact modulation line under a strong out-of-band
interferer that swamps the raw envelope.
* Warn when a tone burst cannot close on the tone's zero crossing
IEC 60268-1 Clause A2.1 asks for an integral number of full periods, but
the gate closes after round(fs*cycles/frequency) samples: sample-exact
only when f and fs are commensurate. For 10 cycles of 997 Hz at 48 kHz
the burst spans 481.44 samples, gated at 481, and the waveform carries a
residual step at the gate edge. Document the condition and emit a
PhonometryWarning quantifying the realized residual (bounded by
amplitude*sin(pi*f/fs)) for incommensurate pairs; commensurate bursts
stay silent.
Also tighten the resampler's 'equiripple-bounded' wording: the Kaiser
window method bounds both bands by the same delta = 10^(-A/20), but its
ripple decays away from the band edges, unlike a true equiripple design.
* Document estimator conventions verified against their sources
Documentation and comment corrections, no behavior changes:
- random_data: keep the reverse-arrangement acceptance bounds on the
normal approximation deliberately and say why: B&P Table A.6 itself
follows it, and no percentage-point convention on the exact Mahonian
distribution reproduces the published entries (N = 40 and N = 100 at
a = 0.975 pin it down), so exact bounds would disagree with the table
users check against. The p-value stays exact; the one-count boundary
disagreement is now documented on TrendTestResult.
- time_frequency: the spectrogram energy-recovery statement was wrong as
written; integrating 'density' columns over frequency and summing over
time recovers the record energy only after multiplying by the hop
duration, up to the taper roll-off at the record edges (verified
0.988 vs 1 for Hann at 75 % overlap on white noise).
- spectra: note that the adaptive multitaper sigma^2 is mean(x^2) with no
mean removal (consistent with the documented no-detrending
calibration), and mark the 'P&W Eq. 369a' citation as pending
verification against the book.
- compliance + tests/reference_data: transcription note for the bare
'+3' at 20 Hz, Type 2, in ANSI S1.4-1983 Table V (read as upper-only;
+/-3 plausible; either way the verdict cannot change, the realized
deviation is -0.05 dB).
- room_ir: the Mueller & Massarani 'below 4 dB' crest-factor figure
assumes their slight magnitude smoothing, which shaped_sweep_signal
does not implement; measured sweeps here sit at 4.2-4.3 dB.
* Translate EQ section labels, complete the bibliography and changelog
- The per-section legend labels of the parametric-EQ plot printed the raw
English filter-type names in Spanish figures; route them through the
plot string table (peaking -> campana, shelves, band-pass, notch,
all-pass; lowpass/highpass were already translated for the lifter
plot). English output stays byte-identical.
- docs/references.md: add the Harris 1978 window paper and IEC
60268-1:1985, both cited by guide pages but missing from the collected
bibliography; link the IEC entry (and the one in test-signals.md) to
the IEC webstore page.
- Changelog entries for the DSP fixes; regenerate the API reference,
llms-full.txt and the cepstrum_echo figures (the detected-echo marker
moved by the sub-sample delay interpolation); the conformance report is
unchanged (419/419).
* Propagate the echo and envelope semantics to the site guides
Review follow-ups:
- The EN and ES site guides (cepstrum-echoes.mdx) still described the
old echo reading and the unqualified 'exact amplitude' claim; bring
them in step with docs/cepstrum-echoes.md: |cepstrum| peak-picking,
signed coefficient, interpolated delay, bin-splitting caveat, the
on-bin/scalloping condition and the new band= front end (the docs page
gains the same envelope-section text).
- State in the changelog and the result docstring that delay is no
longer exactly delay_samples/fs when the interpolated offset is
nonzero (delay_samples remains the whole-sample index the coefficient
is read at), and merge the duplicated Unreleased headings.
- Clearer stability message (poles 'do not lie strictly inside the unit
circle', full-precision coefficients) and an informative short-record
error for the envelope band-pass pre-filter instead of scipy's padlen
message.
- Sharper tone-burst warning test (asserts the quantified span and
residual) and a tighter interpolated-delay tolerance.
* Address review notes: cepstrum terminology, band-pass claims, edge validation
- Guides (docs and site, EN and ES): call the signed quantity what it is,
Milner's cepstrum of the log-power spectrum, and spell out that
Bogert's original 1963 power cepstrum squares once more and is
non-negative, so the signed rahmonics are the library's documented
convention; qualify the band-pass front end (out-of-band content is
strongly attenuated, not eliminated: finite Butterworth rejection and
small zero-phase edge transients).
- cepstrum: render reflection_coefficient as inline code in the
delay_samples docstring so the generated API page stops linking the
unrelated underwater seabed function.
- envelope: validate that 'band' is exactly two numeric edges before
indexing (a one-item tuple raised IndexError, extra entries were
ignored); the validated edges now travel to the result.
- equalizer: reject finite but unrepresentable gains through a shared
_gain_amplitude helper (10^(gain_db/40) overflowing, or underflowing
to zero and then dividing by zero in the designers); document the 0 dB
exception to the shelf-slope bound on the slope field.
- tone_burst: validate the repetition configuration before emitting the
incommensurate-frequency warning, so invalid setups raise even under
a warnings-as-errors filter.
- comb_filter_response: reject phase products that overflow to infinity
(sin(inf) returned NaN instead of the documented bounded response).
Tests for every new rejection path; API reference and llms regenerated.
* Test the representable gain amplitude as a positive finite bound
* Scope the first-rahmonic height claim to the source-free identity
Correct DSP estimator edge cases: TSA time axis, echo detection, EQ validation (#356)
* Report the true 1/fs time axis from time_synchronous_average
The averaged samples sit on the 1/fs sampling grid (each block is aligned
back to it by the band-limited fractional delay before averaging), but the
result reported a time axis with T/M spacing. For a non-integer number of
samples per period the two grids drift apart across the period: with
100.37 samples per period the noiseless recovery error read against the
reported axis was 9.3e-2, versus 2.8e-6 on the true m/fs grid.
Return times = arange(M)/fs, document that the M samples cover the period
only approximately when fs*T is not an integer (McFadden 1987 revised
model: a block average on the sampling grid, no angular resampling), and
assert the non-integer-period tests on the true grid, with a regression
test pinning the 100.37-sample case on both grids. Also soften the comb
response [0, 1] claim (float cancellation beside a tooth can exceed 1 by
a few 1e-9) and mark the ~14 dB N = 32 figure as derived from Eq. 8
rather than printed in the paper.
* Detect inverting echoes and off-sample delays in echo_detection
echo_detection picked the maximum of the signed power cepstrum, so an
inverting reflection (a < 0), whose first rahmonic is a itself and hence
negative, was silently missed: with a = -0.4 at 313 samples the true
rahmonic c[313] = -0.42 was ignored and an unrelated 4739-sample peak
returned. Peak-pick on |cepstrum| within the searched band instead and
report the signed cepstrum value at the peak as the reflection
coefficient (the Mercator series ln(1 + a e^{-j theta}) holds for either
sign of a).
Also refine the reported delay by three-point parabolic interpolation of
|cepstrum| through the peak, so an off-sample delay (e.g. 313.5 samples)
is estimated to a fraction of a sample, and document the bin-splitting
caveat: at off-sample delays the peak-sample coefficient underestimates
|a| (about 65 % of it midway between bins). One-line convention note
distinguishing Milner's IDFT(ln|X|^2) power cepstrum from Bogert's
squared original. Tests cover a < 0 (exact impulse-echo closed form and
a noise source) and the interpolated half-sample delay.
* Validate parametric-EQ sections at the numerical extremes
Three validation gaps in the RBJ cookbook designer:
- A wide 'bw' at an f0 near Nyquist drives the w0/sin(w0) warping factor
into math.sinh overflow, escaping as a raw OverflowError; slightly less
extreme pairs produced a large but finite alpha whose rounded a2 landed
exactly at -1.0: poles on the unit circle designed silently. Wrap the
sinh with an informative ValueError and check the realized coefficients
against the stability triangle (|a2| < 1, |a1| < 1 + a2) after
normalization.
- A shelf slope beyond the gain-dependent bound (A + 1/A)/(A + 1/A - 2)
makes the cookbook's alpha complex and surfaced as a bare 'math domain
error'. Validate the bound at section construction with the limit in
the message (any slope is fine at 0 dB, where A = 1).
- Non-finite f0/gain_db/q/bw/slope passed validation silently (NaN
compares false against every bound) and produced NaN/inf coefficient
rows. Require finiteness explicitly.
Tests cover all three paths plus extreme-but-valid sections that must
keep designing stably.
* Scope the envelope-spectrum amplitude claim and add the band-pass front end
The docstring promised that a sinusoidal modulation 'reads out as a line
at its exact amplitude'; that holds only when the modulation frequency
falls on an analysis bin. Off-bin lines read low by the taper's
scalloping loss (about 1.42 dB, ~15 %, for the default Hann midway
between bins), like any single-record amplitude spectrum. State the
on-bin condition on the closed forms and the caveat in the module,
function and result docstrings, pinned by a midway-between-bins test.
Also add the optional band=(low, high) pre-filter to envelope_spectrum:
a zero-phase 4th-order Butterworth band-pass ahead of the detector, the
band-pass front end of Bendat & Piersol Figure 13.11 and the classical
bearing-envelope chain (isolate the excited resonance band, then
envelope it). The band travels with the result; a test shows it
recovering the exact modulation line under a strong out-of-band
interferer that swamps the raw envelope.
* Warn when a tone burst cannot close on the tone's zero crossing
IEC 60268-1 Clause A2.1 asks for an integral number of full periods, but
the gate closes after round(fs*cycles/frequency) samples: sample-exact
only when f and fs are commensurate. For 10 cycles of 997 Hz at 48 kHz
the burst spans 481.44 samples, gated at 481, and the waveform carries a
residual step at the gate edge. Document the condition and emit a
PhonometryWarning quantifying the realized residual (bounded by
amplitude*sin(pi*f/fs)) for incommensurate pairs; commensurate bursts
stay silent.
Also tighten the resampler's 'equiripple-bounded' wording: the Kaiser
window method bounds both bands by the same delta = 10^(-A/20), but its
ripple decays away from the band edges, unlike a true equiripple design.
* Document estimator conventions verified against their sources
Documentation and comment corrections, no behavior changes:
- random_data: keep the reverse-arrangement acceptance bounds on the
normal approximation deliberately and say why: B&P Table A.6 itself
follows it, and no percentage-point convention on the exact Mahonian
distribution reproduces the published entries (N = 40 and N = 100 at
a = 0.975 pin it down), so exact bounds would disagree with the table
users check against. The p-value stays exact; the one-count boundary
disagreement is now documented on TrendTestResult.
- time_frequency: the spectrogram energy-recovery statement was wrong as
written; integrating 'density' columns over frequency and summing over
time recovers the record energy only after multiplying by the hop
duration, up to the taper roll-off at the record edges (verified
0.988 vs 1 for Hann at 75 % overlap on white noise).
- spectra: note that the adaptive multitaper sigma^2 is mean(x^2) with no
mean removal (consistent with the documented no-detrending
calibration), and mark the 'P&W Eq. 369a' citation as pending
verification against the book.
- compliance + tests/reference_data: transcription note for the bare
'+3' at 20 Hz, Type 2, in ANSI S1.4-1983 Table V (read as upper-only;
+/-3 plausible; either way the verdict cannot change, the realized
deviation is -0.05 dB).
- room_ir: the Mueller & Massarani 'below 4 dB' crest-factor figure
assumes their slight magnitude smoothing, which shaped_sweep_signal
does not implement; measured sweeps here sit at 4.2-4.3 dB.
* Translate EQ section labels, complete the bibliography and changelog
- The per-section legend labels of the parametric-EQ plot printed the raw
English filter-type names in Spanish figures; route them through the
plot string table (peaking -> campana, shelves, band-pass, notch,
all-pass; lowpass/highpass were already translated for the lifter
plot). English output stays byte-identical.
- docs/references.md: add the Harris 1978 window paper and IEC
60268-1:1985, both cited by guide pages but missing from the collected
bibliography; link the IEC entry (and the one in test-signals.md) to
the IEC webstore page.
- Changelog entries for the DSP fixes; regenerate the API reference,
llms-full.txt and the cepstrum_echo figures (the detected-echo marker
moved by the sub-sample delay interpolation); the conformance report is
unchanged (419/419).
* Propagate the echo and envelope semantics to the site guides
Review follow-ups:
- The EN and ES site guides (cepstrum-echoes.mdx) still described the
old echo reading and the unqualified 'exact amplitude' claim; bring
them in step with docs/cepstrum-echoes.md: |cepstrum| peak-picking,
signed coefficient, interpolated delay, bin-splitting caveat, the
on-bin/scalloping condition and the new band= front end (the docs page
gains the same envelope-section text).
- State in the changelog and the result docstring that delay is no
longer exactly delay_samples/fs when the interpolated offset is
nonzero (delay_samples remains the whole-sample index the coefficient
is read at), and merge the duplicated Unreleased headings.
- Clearer stability message (poles 'do not lie strictly inside the unit
circle', full-precision coefficients) and an informative short-record
error for the envelope band-pass pre-filter instead of scipy's padlen
message.
- Sharper tone-burst warning test (asserts the quantified span and
residual) and a tighter interpolated-delay tolerance.
* Address review notes: cepstrum terminology, band-pass claims, edge validation
- Guides (docs and site, EN and ES): call the signed quantity what it is,
Milner's cepstrum of the log-power spectrum, and spell out that
Bogert's original 1963 power cepstrum squares once more and is
non-negative, so the signed rahmonics are the library's documented
convention; qualify the band-pass front end (out-of-band content is
strongly attenuated, not eliminated: finite Butterworth rejection and
small zero-phase edge transients).
- cepstrum: render reflection_coefficient as inline code in the
delay_samples docstring so the generated API page stops linking the
unrelated underwater seabed function.
- envelope: validate that 'band' is exactly two numeric edges before
indexing (a one-item tuple raised IndexError, extra entries were
ignored); the validated edges now travel to the result.
- equalizer: reject finite but unrepresentable gains through a shared
_gain_amplitude helper (10^(gain_db/40) overflowing, or underflowing
to zero and then dividing by zero in the designers); document the 0 dB
exception to the shelf-slope bound on the slope field.
- tone_burst: validate the repetition configuration before emitting the
incommensurate-frequency warning, so invalid setups raise even under
a warnings-as-errors filter.
- comb_filter_response: reject phase products that overflow to infinity
(sin(inf) returned NaN instead of the documented bounded response).
Tests for every new rejection path; API reference and llms regenerated.
* Test the representable gain amplitude as a positive finite bound
* Scope the first-rahmonic height claim to the source-free identity
refactor: reorganize the package into twelve domain subpackages (3.2) (#162)
* refactor: private core to _internal, module-path shims, _plot skeleton (C1)
Foundation of the 3.2 package reorganization:
- _validation/_types/_warnings/_levels_math/utils move (git mv) into the
private phonometry/_internal/ package; ~90 import sites retargeted.
- New phonometry/_compat.py: dynamic sys.modules shims keep every moved
public module path importable for one deprecation cycle (silent import,
DeprecationWarning on attribute access); generalizes and absorbs the
former loudness.py PEP 562 shim (its 3.1 wording preserved).
- _plotting.py moves (git mv) to phonometry/_plot/common.py; a silent
explicit re-export shim keeps the old private path and the 84 lazy
.plot() call sites working until each domain retargets them.
- _warn_renamed gains a 'since' parameter (3.2 for the package moves).
- tests: new test_package_architecture.py (ast edge whitelist +
fresh-interpreter subpackage imports); test_deprecated_aliases gains the
frozen 85-path pre-move snapshot and the _MOVED shim behavior test;
pytest pythonpath=tests for the upcoming mirrored test tree.
- .git-blame-ignore-revs scaffold (hashes appended at the end of Phase 1).
* refactor: metrology subpackage (C2)
core, filter_design, frequencies, parametric_filters, levels, calibration,
compliance and uncertainty move (git mv) into phonometry/metrology/ with a
curated re-export __init__; the flat API and the old module paths keep
working (facade re-exports + _compat shims). Uncertainty renderers carve out
of _plot/common.py into _plot/metrology.py; the 14 metrology test files move
to tests/metrology/ and deep imports across tests/scripts point at the new
canonical paths.
* refactor: psychoacoustics subpackage (C3)
The thirteen psychoacoustics modules (Zwicker/ECMA/Moore-Glasberg loudness,
contours, sharpness, tonality, roughness, fluctuation strength, annoyance,
tonal audibility, private Zwicker tables) move into
phonometry/psychoacoustics/; renderers carve into _plot/psychoacoustics.py;
twelve test files move to tests/psychoacoustics/. The phonometry.loudness
alias now resolves to the relocated canonical module in a single hop.
* refactor: hearing and emission subpackages (C4+C5)
hearing/ gains threshold (renamed from hearing.py), noise-induced hearing
loss, occupational exposure, SII and STI; emission/ gains the ISO 3740
sound-power family, ISO 9614 intensity and ISO/TS 7849 vibration-based
power. Renderers carve into _plot/hearing.py and _plot/emission.py; the
eleven domain test files move under tests/hearing/ and tests/emission/.
* refactor: materials, room and building subpackages (C6-C8)
materials/ collects ISO 354/11654/12999-2 absorption, scattering-diffusion,
road absorption, impedance tube, airflow resistance and EN 29052-1 dynamic
stiffness; room/ the room-acoustics, impulse-response, open-plan, room-noise,
reverberation-prediction and EN 12354-6 modules; building/ the eleven-module
EN 12354 / ISO 717 / ISO 16283 insulation family with EN 15657 and
EN 12354-5 structure-borne sound. Renderers carve into the matching _plot
modules; twenty-six test files move under their domain directories.
* fix: complete the building plot TYPE_CHECKING imports (C8 follow-up)
* refactor: vibration and environmental subpackages (C9+C10)
vibration/ collects human vibration (ISO 8041-1/2631/5349), multiple-shock
(ISO 2631-5), mechanical mobility (ISO 7626-1) and transfer stiffness
(ISO 10846); environmental/ collects the renamed rating (Lden/Ldn) and
measurement (ISO 1996-2) modules plus outdoor propagation (ISO 9613-2),
air absorption (ISO 9613-1), wind-turbine noise (IEC 61400-11) and NT
ACOU 112 impulse prominence. Renderers carve into their _plot modules and
ten test files move under the domain directories.
* refactor: aircraft, underwater and electroacoustics subpackages (C11-C13)
aircraft/ collects EPNL, the renamed atmospheric absorption, ECAC Doc 29
airport contours and ECAC Doc 32 rotorcraft hemispheres; underwater/ the
renamed acoustics/propagation/sound_speed modules with ship noise, pile
driving, ambient noise, sonar equation, seabed reflection and the numerical
solvers; electroacoustics/ distortion and frequency response. Renderers
carve into their _plot modules; seventeen test files move under the domain
directories. All twelve domain subpackages are now in place.
* refactor: finalize the 3.2 package reorganization (C14)
- api-reference gains a Namespaces section (the twelve subpackages with
scope and the 'from phonometry import aircraft as air' idiom); README
quick-start notes the namespaces; CHANGELOG records the added namespaces,
the deprecated flat module paths and the pickle note.
- plot_excitation moves from _plot/common.py to _plot/room.py and the
facade imports it from there; _plot/common.py now holds only shared
helpers and infrastructure.
- hearing/ and environmental/ package __init__ re-export the full public
surface of their renamed modules (threshold, rating) so the pre-move
package-path imports stay silent, as promised by the migration contract.
- SonarCloud cpd exclusion follows core.py to metrology/; llms.txt
regenerated; .git-blame-ignore-revs lists the move commits and the local
blame config registers it.
Full suite 2647 passed; conformance 194/194; golden baseline byte-stable;
import time unchanged vs main (1.09 s vs 1.17 s).
* fix: re-export the measurement surface on phonometry.environmental
Subagent-review finding: the generated environmental/__init__.py missed the
ISO 1996-2 measurement module (its facade import block was momentarily
path-corrupted when the subpackage exports were collected), so
phonometry.environmental.assess_tonal_audibility and 15 sibling names
raised AttributeError while every other domain namespace was complete. Adds
the missing re-exports (including EnvironmentalMeasurementWarning on the
domain namespace) and a new architecture invariant test asserting that
every name the facade imports from a domain submodule is reachable on that
subpackage, so this class of gap cannot recur.
* test: forbid absolute self-imports in the architecture check
Review feedback: the ast edge extraction only saw relative imports, so an
absolute 'from phonometry.x import y' inside the package would bypass the
cross-package whitelist. Such imports now fail the architecture test
directly (the codebase has none; relative imports are the convention).
refactor: reorganize the package into twelve domain subpackages (3.2) (#162)
* refactor: private core to _internal, module-path shims, _plot skeleton (C1)
Foundation of the 3.2 package reorganization:
- _validation/_types/_warnings/_levels_math/utils move (git mv) into the
private phonometry/_internal/ package; ~90 import sites retargeted.
- New phonometry/_compat.py: dynamic sys.modules shims keep every moved
public module path importable for one deprecation cycle (silent import,
DeprecationWarning on attribute access); generalizes and absorbs the
former loudness.py PEP 562 shim (its 3.1 wording preserved).
- _plotting.py moves (git mv) to phonometry/_plot/common.py; a silent
explicit re-export shim keeps the old private path and the 84 lazy
.plot() call sites working until each domain retargets them.
- _warn_renamed gains a 'since' parameter (3.2 for the package moves).
- tests: new test_package_architecture.py (ast edge whitelist +
fresh-interpreter subpackage imports); test_deprecated_aliases gains the
frozen 85-path pre-move snapshot and the _MOVED shim behavior test;
pytest pythonpath=tests for the upcoming mirrored test tree.
- .git-blame-ignore-revs scaffold (hashes appended at the end of Phase 1).
* refactor: metrology subpackage (C2)
core, filter_design, frequencies, parametric_filters, levels, calibration,
compliance and uncertainty move (git mv) into phonometry/metrology/ with a
curated re-export __init__; the flat API and the old module paths keep
working (facade re-exports + _compat shims). Uncertainty renderers carve out
of _plot/common.py into _plot/metrology.py; the 14 metrology test files move
to tests/metrology/ and deep imports across tests/scripts point at the new
canonical paths.
* refactor: psychoacoustics subpackage (C3)
The thirteen psychoacoustics modules (Zwicker/ECMA/Moore-Glasberg loudness,
contours, sharpness, tonality, roughness, fluctuation strength, annoyance,
tonal audibility, private Zwicker tables) move into
phonometry/psychoacoustics/; renderers carve into _plot/psychoacoustics.py;
twelve test files move to tests/psychoacoustics/. The phonometry.loudness
alias now resolves to the relocated canonical module in a single hop.
* refactor: hearing and emission subpackages (C4+C5)
hearing/ gains threshold (renamed from hearing.py), noise-induced hearing
loss, occupational exposure, SII and STI; emission/ gains the ISO 3740
sound-power family, ISO 9614 intensity and ISO/TS 7849 vibration-based
power. Renderers carve into _plot/hearing.py and _plot/emission.py; the
eleven domain test files move under tests/hearing/ and tests/emission/.
* refactor: materials, room and building subpackages (C6-C8)
materials/ collects ISO 354/11654/12999-2 absorption, scattering-diffusion,
road absorption, impedance tube, airflow resistance and EN 29052-1 dynamic
stiffness; room/ the room-acoustics, impulse-response, open-plan, room-noise,
reverberation-prediction and EN 12354-6 modules; building/ the eleven-module
EN 12354 / ISO 717 / ISO 16283 insulation family with EN 15657 and
EN 12354-5 structure-borne sound. Renderers carve into the matching _plot
modules; twenty-six test files move under their domain directories.
* fix: complete the building plot TYPE_CHECKING imports (C8 follow-up)
* refactor: vibration and environmental subpackages (C9+C10)
vibration/ collects human vibration (ISO 8041-1/2631/5349), multiple-shock
(ISO 2631-5), mechanical mobility (ISO 7626-1) and transfer stiffness
(ISO 10846); environmental/ collects the renamed rating (Lden/Ldn) and
measurement (ISO 1996-2) modules plus outdoor propagation (ISO 9613-2),
air absorption (ISO 9613-1), wind-turbine noise (IEC 61400-11) and NT
ACOU 112 impulse prominence. Renderers carve into their _plot modules and
ten test files move under the domain directories.
* refactor: aircraft, underwater and electroacoustics subpackages (C11-C13)
aircraft/ collects EPNL, the renamed atmospheric absorption, ECAC Doc 29
airport contours and ECAC Doc 32 rotorcraft hemispheres; underwater/ the
renamed acoustics/propagation/sound_speed modules with ship noise, pile
driving, ambient noise, sonar equation, seabed reflection and the numerical
solvers; electroacoustics/ distortion and frequency response. Renderers
carve into their _plot modules; seventeen test files move under the domain
directories. All twelve domain subpackages are now in place.
* refactor: finalize the 3.2 package reorganization (C14)
- api-reference gains a Namespaces section (the twelve subpackages with
scope and the 'from phonometry import aircraft as air' idiom); README
quick-start notes the namespaces; CHANGELOG records the added namespaces,
the deprecated flat module paths and the pickle note.
- plot_excitation moves from _plot/common.py to _plot/room.py and the
facade imports it from there; _plot/common.py now holds only shared
helpers and infrastructure.
- hearing/ and environmental/ package __init__ re-export the full public
surface of their renamed modules (threshold, rating) so the pre-move
package-path imports stay silent, as promised by the migration contract.
- SonarCloud cpd exclusion follows core.py to metrology/; llms.txt
regenerated; .git-blame-ignore-revs lists the move commits and the local
blame config registers it.
Full suite 2647 passed; conformance 194/194; golden baseline byte-stable;
import time unchanged vs main (1.09 s vs 1.17 s).
* fix: re-export the measurement surface on phonometry.environmental
Subagent-review finding: the generated environmental/__init__.py missed the
ISO 1996-2 measurement module (its facade import block was momentarily
path-corrupted when the subpackage exports were collected), so
phonometry.environmental.assess_tonal_audibility and 15 sibling names
raised AttributeError while every other domain namespace was complete. Adds
the missing re-exports (including EnvironmentalMeasurementWarning on the
domain namespace) and a new architecture invariant test asserting that
every name the facade imports from a domain submodule is reachable on that
subpackage, so this class of gap cannot recur.
* test: forbid absolute self-imports in the architecture check
Review feedback: the ast edge extraction only saw relative imports, so an
absolute 'from phonometry.x import y' inside the package would bypass the
cross-package whitelist. Such imports now fail the architecture test
directly (the codebase has none; relative imports are the convention).
refactor: reorganize the package into twelve domain subpackages (3.2) (#162)
* refactor: private core to _internal, module-path shims, _plot skeleton (C1)
Foundation of the 3.2 package reorganization:
- _validation/_types/_warnings/_levels_math/utils move (git mv) into the
private phonometry/_internal/ package; ~90 import sites retargeted.
- New phonometry/_compat.py: dynamic sys.modules shims keep every moved
public module path importable for one deprecation cycle (silent import,
DeprecationWarning on attribute access); generalizes and absorbs the
former loudness.py PEP 562 shim (its 3.1 wording preserved).
- _plotting.py moves (git mv) to phonometry/_plot/common.py; a silent
explicit re-export shim keeps the old private path and the 84 lazy
.plot() call sites working until each domain retargets them.
- _warn_renamed gains a 'since' parameter (3.2 for the package moves).
- tests: new test_package_architecture.py (ast edge whitelist +
fresh-interpreter subpackage imports); test_deprecated_aliases gains the
frozen 85-path pre-move snapshot and the _MOVED shim behavior test;
pytest pythonpath=tests for the upcoming mirrored test tree.
- .git-blame-ignore-revs scaffold (hashes appended at the end of Phase 1).
* refactor: metrology subpackage (C2)
core, filter_design, frequencies, parametric_filters, levels, calibration,
compliance and uncertainty move (git mv) into phonometry/metrology/ with a
curated re-export __init__; the flat API and the old module paths keep
working (facade re-exports + _compat shims). Uncertainty renderers carve out
of _plot/common.py into _plot/metrology.py; the 14 metrology test files move
to tests/metrology/ and deep imports across tests/scripts point at the new
canonical paths.
* refactor: psychoacoustics subpackage (C3)
The thirteen psychoacoustics modules (Zwicker/ECMA/Moore-Glasberg loudness,
contours, sharpness, tonality, roughness, fluctuation strength, annoyance,
tonal audibility, private Zwicker tables) move into
phonometry/psychoacoustics/; renderers carve into _plot/psychoacoustics.py;
twelve test files move to tests/psychoacoustics/. The phonometry.loudness
alias now resolves to the relocated canonical module in a single hop.
* refactor: hearing and emission subpackages (C4+C5)
hearing/ gains threshold (renamed from hearing.py), noise-induced hearing
loss, occupational exposure, SII and STI; emission/ gains the ISO 3740
sound-power family, ISO 9614 intensity and ISO/TS 7849 vibration-based
power. Renderers carve into _plot/hearing.py and _plot/emission.py; the
eleven domain test files move under tests/hearing/ and tests/emission/.
* refactor: materials, room and building subpackages (C6-C8)
materials/ collects ISO 354/11654/12999-2 absorption, scattering-diffusion,
road absorption, impedance tube, airflow resistance and EN 29052-1 dynamic
stiffness; room/ the room-acoustics, impulse-response, open-plan, room-noise,
reverberation-prediction and EN 12354-6 modules; building/ the eleven-module
EN 12354 / ISO 717 / ISO 16283 insulation family with EN 15657 and
EN 12354-5 structure-borne sound. Renderers carve into the matching _plot
modules; twenty-six test files move under their domain directories.
* fix: complete the building plot TYPE_CHECKING imports (C8 follow-up)
* refactor: vibration and environmental subpackages (C9+C10)
vibration/ collects human vibration (ISO 8041-1/2631/5349), multiple-shock
(ISO 2631-5), mechanical mobility (ISO 7626-1) and transfer stiffness
(ISO 10846); environmental/ collects the renamed rating (Lden/Ldn) and
measurement (ISO 1996-2) modules plus outdoor propagation (ISO 9613-2),
air absorption (ISO 9613-1), wind-turbine noise (IEC 61400-11) and NT
ACOU 112 impulse prominence. Renderers carve into their _plot modules and
ten test files move under the domain directories.
* refactor: aircraft, underwater and electroacoustics subpackages (C11-C13)
aircraft/ collects EPNL, the renamed atmospheric absorption, ECAC Doc 29
airport contours and ECAC Doc 32 rotorcraft hemispheres; underwater/ the
renamed acoustics/propagation/sound_speed modules with ship noise, pile
driving, ambient noise, sonar equation, seabed reflection and the numerical
solvers; electroacoustics/ distortion and frequency response. Renderers
carve into their _plot modules; seventeen test files move under the domain
directories. All twelve domain subpackages are now in place.
* refactor: finalize the 3.2 package reorganization (C14)
- api-reference gains a Namespaces section (the twelve subpackages with
scope and the 'from phonometry import aircraft as air' idiom); README
quick-start notes the namespaces; CHANGELOG records the added namespaces,
the deprecated flat module paths and the pickle note.
- plot_excitation moves from _plot/common.py to _plot/room.py and the
facade imports it from there; _plot/common.py now holds only shared
helpers and infrastructure.
- hearing/ and environmental/ package __init__ re-export the full public
surface of their renamed modules (threshold, rating) so the pre-move
package-path imports stay silent, as promised by the migration contract.
- SonarCloud cpd exclusion follows core.py to metrology/; llms.txt
regenerated; .git-blame-ignore-revs lists the move commits and the local
blame config registers it.
Full suite 2647 passed; conformance 194/194; golden baseline byte-stable;
import time unchanged vs main (1.09 s vs 1.17 s).
* fix: re-export the measurement surface on phonometry.environmental
Subagent-review finding: the generated environmental/__init__.py missed the
ISO 1996-2 measurement module (its facade import block was momentarily
path-corrupted when the subpackage exports were collected), so
phonometry.environmental.assess_tonal_audibility and 15 sibling names
raised AttributeError while every other domain namespace was complete. Adds
the missing re-exports (including EnvironmentalMeasurementWarning on the
domain namespace) and a new architecture invariant test asserting that
every name the facade imports from a domain submodule is reachable on that
subpackage, so this class of gap cannot recur.
* test: forbid absolute self-imports in the architecture check
Review feedback: the ast edge extraction only saw relative imports, so an
absolute 'from phonometry.x import y' inside the package would bypass the
cross-package whitelist. Such imports now fail the architecture test
directly (the codebase has none; relative imports are the convention).
Core metrology guides: result figures, ResampledSignalResult.plot() and real introductions (#364)
* Add ResampledSignalResult.plot(): the delivered anti-alias filter against its design spec
* Rewrite the multichannel guide intro: concrete use cases, per-channel guarantees, Bendat & Piersol citations
* Give the levels page a real introduction: descriptor families, when-to-use guidance and survey references
* Time weighting: framing intro with detector history, ANSI S1.4/Bies references and sibling links
* Calibration: proper introduction (SPL vs dBFS choice, workflow framing) with Bies reference and sibling links
* Add eleven core-metrology figures: resampling spec, cepstrum variants, liftering, correlation, IR alignment, Hilbert envelope, cross-spectra, Golay recovery, TSA noise law, runs test
* Test signals: anti-alias figure with dual snippet, res.plot() mention, See also, docs mirror sync
* Cepstrum guide: variants and liftering figures with dual snippets, complex-cepstrum plot mention
* Correlation guide: normalization, IR-alignment and Hilbert-envelope figures with dual snippets
* Spectral analysis: cross-spectral density and coherent-output-SNR figures with dual snippets
* System measurement: Golay exact-recovery figure and plot mentions for the inversion workflow
* Synchronous averaging: sqrt(N) figure, diagnostics-tool guidance, See also, recording naming
* Data qualification: runs-test figure with dual snippet and peak-statistics plot mention
* MISO coherence: conditioning-order snippet, practical pitfalls and See also across the three trees
* Time-frequency: Harris window reference and See also across the three trees
* GUM uncertainty: map the domain pages that consume the clause-5 machinery, expand See also
* Filter banks: compliance plot mentions and recording naming in the crossover snippet
* Changelog entry for the core-metrology documentation figures and prose
* Regenerate llms-full.txt for the core-metrology documentation
* Drop the duplicated phase-axis translation key
Correct DSP estimator edge cases: TSA time axis, echo detection, EQ validation (#356)
* Report the true 1/fs time axis from time_synchronous_average
The averaged samples sit on the 1/fs sampling grid (each block is aligned
back to it by the band-limited fractional delay before averaging), but the
result reported a time axis with T/M spacing. For a non-integer number of
samples per period the two grids drift apart across the period: with
100.37 samples per period the noiseless recovery error read against the
reported axis was 9.3e-2, versus 2.8e-6 on the true m/fs grid.
Return times = arange(M)/fs, document that the M samples cover the period
only approximately when fs*T is not an integer (McFadden 1987 revised
model: a block average on the sampling grid, no angular resampling), and
assert the non-integer-period tests on the true grid, with a regression
test pinning the 100.37-sample case on both grids. Also soften the comb
response [0, 1] claim (float cancellation beside a tooth can exceed 1 by
a few 1e-9) and mark the ~14 dB N = 32 figure as derived from Eq. 8
rather than printed in the paper.
* Detect inverting echoes and off-sample delays in echo_detection
echo_detection picked the maximum of the signed power cepstrum, so an
inverting reflection (a < 0), whose first rahmonic is a itself and hence
negative, was silently missed: with a = -0.4 at 313 samples the true
rahmonic c[313] = -0.42 was ignored and an unrelated 4739-sample peak
returned. Peak-pick on |cepstrum| within the searched band instead and
report the signed cepstrum value at the peak as the reflection
coefficient (the Mercator series ln(1 + a e^{-j theta}) holds for either
sign of a).
Also refine the reported delay by three-point parabolic interpolation of
|cepstrum| through the peak, so an off-sample delay (e.g. 313.5 samples)
is estimated to a fraction of a sample, and document the bin-splitting
caveat: at off-sample delays the peak-sample coefficient underestimates
|a| (about 65 % of it midway between bins). One-line convention note
distinguishing Milner's IDFT(ln|X|^2) power cepstrum from Bogert's
squared original. Tests cover a < 0 (exact impulse-echo closed form and
a noise source) and the interpolated half-sample delay.
* Validate parametric-EQ sections at the numerical extremes
Three validation gaps in the RBJ cookbook designer:
- A wide 'bw' at an f0 near Nyquist drives the w0/sin(w0) warping factor
into math.sinh overflow, escaping as a raw OverflowError; slightly less
extreme pairs produced a large but finite alpha whose rounded a2 landed
exactly at -1.0: poles on the unit circle designed silently. Wrap the
sinh with an informative ValueError and check the realized coefficients
against the stability triangle (|a2| < 1, |a1| < 1 + a2) after
normalization.
- A shelf slope beyond the gain-dependent bound (A + 1/A)/(A + 1/A - 2)
makes the cookbook's alpha complex and surfaced as a bare 'math domain
error'. Validate the bound at section construction with the limit in
the message (any slope is fine at 0 dB, where A = 1).
- Non-finite f0/gain_db/q/bw/slope passed validation silently (NaN
compares false against every bound) and produced NaN/inf coefficient
rows. Require finiteness explicitly.
Tests cover all three paths plus extreme-but-valid sections that must
keep designing stably.
* Scope the envelope-spectrum amplitude claim and add the band-pass front end
The docstring promised that a sinusoidal modulation 'reads out as a line
at its exact amplitude'; that holds only when the modulation frequency
falls on an analysis bin. Off-bin lines read low by the taper's
scalloping loss (about 1.42 dB, ~15 %, for the default Hann midway
between bins), like any single-record amplitude spectrum. State the
on-bin condition on the closed forms and the caveat in the module,
function and result docstrings, pinned by a midway-between-bins test.
Also add the optional band=(low, high) pre-filter to envelope_spectrum:
a zero-phase 4th-order Butterworth band-pass ahead of the detector, the
band-pass front end of Bendat & Piersol Figure 13.11 and the classical
bearing-envelope chain (isolate the excited resonance band, then
envelope it). The band travels with the result; a test shows it
recovering the exact modulation line under a strong out-of-band
interferer that swamps the raw envelope.
* Warn when a tone burst cannot close on the tone's zero crossing
IEC 60268-1 Clause A2.1 asks for an integral number of full periods, but
the gate closes after round(fs*cycles/frequency) samples: sample-exact
only when f and fs are commensurate. For 10 cycles of 997 Hz at 48 kHz
the burst spans 481.44 samples, gated at 481, and the waveform carries a
residual step at the gate edge. Document the condition and emit a
PhonometryWarning quantifying the realized residual (bounded by
amplitude*sin(pi*f/fs)) for incommensurate pairs; commensurate bursts
stay silent.
Also tighten the resampler's 'equiripple-bounded' wording: the Kaiser
window method bounds both bands by the same delta = 10^(-A/20), but its
ripple decays away from the band edges, unlike a true equiripple design.
* Document estimator conventions verified against their sources
Documentation and comment corrections, no behavior changes:
- random_data: keep the reverse-arrangement acceptance bounds on the
normal approximation deliberately and say why: B&P Table A.6 itself
follows it, and no percentage-point convention on the exact Mahonian
distribution reproduces the published entries (N = 40 and N = 100 at
a = 0.975 pin it down), so exact bounds would disagree with the table
users check against. The p-value stays exact; the one-count boundary
disagreement is now documented on TrendTestResult.
- time_frequency: the spectrogram energy-recovery statement was wrong as
written; integrating 'density' columns over frequency and summing over
time recovers the record energy only after multiplying by the hop
duration, up to the taper roll-off at the record edges (verified
0.988 vs 1 for Hann at 75 % overlap on white noise).
- spectra: note that the adaptive multitaper sigma^2 is mean(x^2) with no
mean removal (consistent with the documented no-detrending
calibration), and mark the 'P&W Eq. 369a' citation as pending
verification against the book.
- compliance + tests/reference_data: transcription note for the bare
'+3' at 20 Hz, Type 2, in ANSI S1.4-1983 Table V (read as upper-only;
+/-3 plausible; either way the verdict cannot change, the realized
deviation is -0.05 dB).
- room_ir: the Mueller & Massarani 'below 4 dB' crest-factor figure
assumes their slight magnitude smoothing, which shaped_sweep_signal
does not implement; measured sweeps here sit at 4.2-4.3 dB.
* Translate EQ section labels, complete the bibliography and changelog
- The per-section legend labels of the parametric-EQ plot printed the raw
English filter-type names in Spanish figures; route them through the
plot string table (peaking -> campana, shelves, band-pass, notch,
all-pass; lowpass/highpass were already translated for the lifter
plot). English output stays byte-identical.
- docs/references.md: add the Harris 1978 window paper and IEC
60268-1:1985, both cited by guide pages but missing from the collected
bibliography; link the IEC entry (and the one in test-signals.md) to
the IEC webstore page.
- Changelog entries for the DSP fixes; regenerate the API reference,
llms-full.txt and the cepstrum_echo figures (the detected-echo marker
moved by the sub-sample delay interpolation); the conformance report is
unchanged (419/419).
* Propagate the echo and envelope semantics to the site guides
Review follow-ups:
- The EN and ES site guides (cepstrum-echoes.mdx) still described the
old echo reading and the unqualified 'exact amplitude' claim; bring
them in step with docs/cepstrum-echoes.md: |cepstrum| peak-picking,
signed coefficient, interpolated delay, bin-splitting caveat, the
on-bin/scalloping condition and the new band= front end (the docs page
gains the same envelope-section text).
- State in the changelog and the result docstring that delay is no
longer exactly delay_samples/fs when the interpolated offset is
nonzero (delay_samples remains the whole-sample index the coefficient
is read at), and merge the duplicated Unreleased headings.
- Clearer stability message (poles 'do not lie strictly inside the unit
circle', full-precision coefficients) and an informative short-record
error for the envelope band-pass pre-filter instead of scipy's padlen
message.
- Sharper tone-burst warning test (asserts the quantified span and
residual) and a tighter interpolated-delay tolerance.
* Address review notes: cepstrum terminology, band-pass claims, edge validation
- Guides (docs and site, EN and ES): call the signed quantity what it is,
Milner's cepstrum of the log-power spectrum, and spell out that
Bogert's original 1963 power cepstrum squares once more and is
non-negative, so the signed rahmonics are the library's documented
convention; qualify the band-pass front end (out-of-band content is
strongly attenuated, not eliminated: finite Butterworth rejection and
small zero-phase edge transients).
- cepstrum: render reflection_coefficient as inline code in the
delay_samples docstring so the generated API page stops linking the
unrelated underwater seabed function.
- envelope: validate that 'band' is exactly two numeric edges before
indexing (a one-item tuple raised IndexError, extra entries were
ignored); the validated edges now travel to the result.
- equalizer: reject finite but unrepresentable gains through a shared
_gain_amplitude helper (10^(gain_db/40) overflowing, or underflowing
to zero and then dividing by zero in the designers); document the 0 dB
exception to the shelf-slope bound on the slope field.
- tone_burst: validate the repetition configuration before emitting the
incommensurate-frequency warning, so invalid setups raise even under
a warnings-as-errors filter.
- comb_filter_response: reject phase products that overflow to infinity
(sin(inf) returned NaN instead of the documented bounded response).
Tests for every new rejection path; API reference and llms regenerated.
* Test the representable gain amplitude as a positive finite bound
* Scope the first-rahmonic height claim to the source-free identity
refactor: reorganize the package into twelve domain subpackages (3.2) (#162)
* refactor: private core to _internal, module-path shims, _plot skeleton (C1)
Foundation of the 3.2 package reorganization:
- _validation/_types/_warnings/_levels_math/utils move (git mv) into the
private phonometry/_internal/ package; ~90 import sites retargeted.
- New phonometry/_compat.py: dynamic sys.modules shims keep every moved
public module path importable for one deprecation cycle (silent import,
DeprecationWarning on attribute access); generalizes and absorbs the
former loudness.py PEP 562 shim (its 3.1 wording preserved).
- _plotting.py moves (git mv) to phonometry/_plot/common.py; a silent
explicit re-export shim keeps the old private path and the 84 lazy
.plot() call sites working until each domain retargets them.
- _warn_renamed gains a 'since' parameter (3.2 for the package moves).
- tests: new test_package_architecture.py (ast edge whitelist +
fresh-interpreter subpackage imports); test_deprecated_aliases gains the
frozen 85-path pre-move snapshot and the _MOVED shim behavior test;
pytest pythonpath=tests for the upcoming mirrored test tree.
- .git-blame-ignore-revs scaffold (hashes appended at the end of Phase 1).
* refactor: metrology subpackage (C2)
core, filter_design, frequencies, parametric_filters, levels, calibration,
compliance and uncertainty move (git mv) into phonometry/metrology/ with a
curated re-export __init__; the flat API and the old module paths keep
working (facade re-exports + _compat shims). Uncertainty renderers carve out
of _plot/common.py into _plot/metrology.py; the 14 metrology test files move
to tests/metrology/ and deep imports across tests/scripts point at the new
canonical paths.
* refactor: psychoacoustics subpackage (C3)
The thirteen psychoacoustics modules (Zwicker/ECMA/Moore-Glasberg loudness,
contours, sharpness, tonality, roughness, fluctuation strength, annoyance,
tonal audibility, private Zwicker tables) move into
phonometry/psychoacoustics/; renderers carve into _plot/psychoacoustics.py;
twelve test files move to tests/psychoacoustics/. The phonometry.loudness
alias now resolves to the relocated canonical module in a single hop.
* refactor: hearing and emission subpackages (C4+C5)
hearing/ gains threshold (renamed from hearing.py), noise-induced hearing
loss, occupational exposure, SII and STI; emission/ gains the ISO 3740
sound-power family, ISO 9614 intensity and ISO/TS 7849 vibration-based
power. Renderers carve into _plot/hearing.py and _plot/emission.py; the
eleven domain test files move under tests/hearing/ and tests/emission/.
* refactor: materials, room and building subpackages (C6-C8)
materials/ collects ISO 354/11654/12999-2 absorption, scattering-diffusion,
road absorption, impedance tube, airflow resistance and EN 29052-1 dynamic
stiffness; room/ the room-acoustics, impulse-response, open-plan, room-noise,
reverberation-prediction and EN 12354-6 modules; building/ the eleven-module
EN 12354 / ISO 717 / ISO 16283 insulation family with EN 15657 and
EN 12354-5 structure-borne sound. Renderers carve into the matching _plot
modules; twenty-six test files move under their domain directories.
* fix: complete the building plot TYPE_CHECKING imports (C8 follow-up)
* refactor: vibration and environmental subpackages (C9+C10)
vibration/ collects human vibration (ISO 8041-1/2631/5349), multiple-shock
(ISO 2631-5), mechanical mobility (ISO 7626-1) and transfer stiffness
(ISO 10846); environmental/ collects the renamed rating (Lden/Ldn) and
measurement (ISO 1996-2) modules plus outdoor propagation (ISO 9613-2),
air absorption (ISO 9613-1), wind-turbine noise (IEC 61400-11) and NT
ACOU 112 impulse prominence. Renderers carve into their _plot modules and
ten test files move under the domain directories.
* refactor: aircraft, underwater and electroacoustics subpackages (C11-C13)
aircraft/ collects EPNL, the renamed atmospheric absorption, ECAC Doc 29
airport contours and ECAC Doc 32 rotorcraft hemispheres; underwater/ the
renamed acoustics/propagation/sound_speed modules with ship noise, pile
driving, ambient noise, sonar equation, seabed reflection and the numerical
solvers; electroacoustics/ distortion and frequency response. Renderers
carve into their _plot modules; seventeen test files move under the domain
directories. All twelve domain subpackages are now in place.
* refactor: finalize the 3.2 package reorganization (C14)
- api-reference gains a Namespaces section (the twelve subpackages with
scope and the 'from phonometry import aircraft as air' idiom); README
quick-start notes the namespaces; CHANGELOG records the added namespaces,
the deprecated flat module paths and the pickle note.
- plot_excitation moves from _plot/common.py to _plot/room.py and the
facade imports it from there; _plot/common.py now holds only shared
helpers and infrastructure.
- hearing/ and environmental/ package __init__ re-export the full public
surface of their renamed modules (threshold, rating) so the pre-move
package-path imports stay silent, as promised by the migration contract.
- SonarCloud cpd exclusion follows core.py to metrology/; llms.txt
regenerated; .git-blame-ignore-revs lists the move commits and the local
blame config registers it.
Full suite 2647 passed; conformance 194/194; golden baseline byte-stable;
import time unchanged vs main (1.09 s vs 1.17 s).
* fix: re-export the measurement surface on phonometry.environmental
Subagent-review finding: the generated environmental/__init__.py missed the
ISO 1996-2 measurement module (its facade import block was momentarily
path-corrupted when the subpackage exports were collected), so
phonometry.environmental.assess_tonal_audibility and 15 sibling names
raised AttributeError while every other domain namespace was complete. Adds
the missing re-exports (including EnvironmentalMeasurementWarning on the
domain namespace) and a new architecture invariant test asserting that
every name the facade imports from a domain submodule is reachable on that
subpackage, so this class of gap cannot recur.
* test: forbid absolute self-imports in the architecture check
Review feedback: the ast edge extraction only saw relative imports, so an
absolute 'from phonometry.x import y' inside the package would bypass the
cross-package whitelist. Such imports now fail the architecture test
directly (the codebase has none; relative imports are the convention).
Core metrology guides: result figures, ResampledSignalResult.plot() and real introductions (#364)
* Add ResampledSignalResult.plot(): the delivered anti-alias filter against its design spec
* Rewrite the multichannel guide intro: concrete use cases, per-channel guarantees, Bendat & Piersol citations
* Give the levels page a real introduction: descriptor families, when-to-use guidance and survey references
* Time weighting: framing intro with detector history, ANSI S1.4/Bies references and sibling links
* Calibration: proper introduction (SPL vs dBFS choice, workflow framing) with Bies reference and sibling links
* Add eleven core-metrology figures: resampling spec, cepstrum variants, liftering, correlation, IR alignment, Hilbert envelope, cross-spectra, Golay recovery, TSA noise law, runs test
* Test signals: anti-alias figure with dual snippet, res.plot() mention, See also, docs mirror sync
* Cepstrum guide: variants and liftering figures with dual snippets, complex-cepstrum plot mention
* Correlation guide: normalization, IR-alignment and Hilbert-envelope figures with dual snippets
* Spectral analysis: cross-spectral density and coherent-output-SNR figures with dual snippets
* System measurement: Golay exact-recovery figure and plot mentions for the inversion workflow
* Synchronous averaging: sqrt(N) figure, diagnostics-tool guidance, See also, recording naming
* Data qualification: runs-test figure with dual snippet and peak-statistics plot mention
* MISO coherence: conditioning-order snippet, practical pitfalls and See also across the three trees
* Time-frequency: Harris window reference and See also across the three trees
* GUM uncertainty: map the domain pages that consume the clause-5 machinery, expand See also
* Filter banks: compliance plot mentions and recording naming in the crossover snippet
* Changelog entry for the core-metrology documentation figures and prose
* Regenerate llms-full.txt for the core-metrology documentation
* Drop the duplicated phase-axis translation key
feat: calibrated spectral analysis, coherent output power and colored-noise generators (#218)
* feat(metrology): calibrated spectral analysis and colored-noise generators (Bendat & Piersol)
Welch power/cross-spectral density estimators that report their own
statistical quality per Bendat & Piersol, Random Data 4e: the effective
number of independent averages under overlapped, tapered segmentation
(Welch 1967 window-correlation formula, the book's Section 11.5.2.2
reference), the normalized random errors of Eqs. 8.158/9.33/9.52, the
chi-square confidence interval of Eq. 8.163 and the effective noise
bandwidth driving the Eq. 8.139 resolution bias (closed peak form of
Eq. 8.141 exposed as resolution_bias_error). coherent_output_spectrum
splits Gyy into Gvv = gamma^2 Gyy and the noise remainder with the
spectral SNR, its Eq. 9.73 random error and the Eq. 9.75 coherence bias.
fractional_octave_smoothing applies a constant-power 1/n-octave kernel
(exact flat-spectrum invariance) to power, amplitude or dB spectra.
noise_signal generates seeded white/pink/red/blue/violet Gaussian noise
shaped bin by bin in the frequency domain, so the power-law slope is
exact on the analysis grid. All results are frozen dataclasses with
.plot().
Oracles: seeded Monte Carlo pins the random errors against the closed
forms (0.1249 measured vs 0.1250 for nd = 64; 0.0910 vs 0.0911 with the
effective nd under 50% overlap) and the 95% interval coverage at 0.949;
a known-SNR path recovers gamma^2 = SNR/(1+SNR) and the exact Gvv/Gnn
split; a smoothed spectral line lands on its closed-form level and
width; the measured noise slopes sit within 0.002 dB/octave of
10 lg(2) per unit exponent.
* refactor: route every Welch consumer through the shared spectral core
transfer_function/coherence (electroacoustics), the p-p sound_intensity
probe (emission) and the ECMA-418-1 averaged spectrum (psychoacoustics
tonality) now call the Welch helpers of phonometry.metrology.spectra
instead of duplicating the scipy invocations locally. Same taper,
overlap policy, detrend-off calibration and segment defaults.
Verified bit-identical: 106 output arrays captured before and after the
refactor (H1/H2 transfer functions and coherence over a nperseg x
overlap grid including odd segment lengths, banded and bias-corrected
intensity with totals, tone-to-noise and prominence ratios) compare
equal byte for byte.
* feat(conformance): calibrated spectral analysis checks (Bendat & Piersol)
New domain with six anchors: the white-noise density level
sigma^2/(fs/2), the 1/sqrt(nd) random error and the 95% chi-square
interval coverage by seeded Monte Carlo, the known-SNR coherent output
spectrum gamma^2 = SNR/(1+SNR), the pink-noise slope against the exact
10 lg(2) dB/octave power law, and the closed-form level of a 1/3-octave
smoothed spectral line. 272/272 checks pass.
* feat(figures): calibrated PSD with confidence interval and 1/3-octave smoothing
One figure for the concept the spectral core adds: a Welch PSD of
exact-slope pink noise carrying its 95% chi-square confidence band,
with the 1/3-octave smoothed curve and the exact -3.01 dB/octave power
law on top, annotated with the effective averages and random error.
Four language/theme variants; ES strings added to the translation
tables.
* docs: calibrated spectral analysis guide (EN/ES), API reference and indices
New guide in the three documentation trees: the PSD/CSD estimators with
their statistical error analysis, the coherent output spectrum and
spectral SNR, the constant-power 1/n-octave smoother and the exact-slope
colored-noise generators, with the new figure, its double snippet and
the closed-form oracles spelled out. Cross-linked from the core
signal-analysis and calibration/uncertainty section pages and the
sidebar. Adds the Spectral analysis section to the API taxonomy,
regenerates the Starlight API reference, extends the curated API table,
the README highlights and guide table, the llms files and the
changelog.
* refactor: apply adversarial-review findings on the spectral core
Second pass over the calibrated spectral analysis branch. The review
verified the mathematics (chi-square orientation, effective-nd Welch
formula across four windows and four overlaps by independent Monte
Carlo, the Eq. 9.73/9.82 transcriptions and the SNR error algebra) and
re-proved the bit-identical refactor claim independently; the applied
findings are consistency fixes:
- frequency_response imports the segment-policy constants from the
shared core instead of keeping duplicates that could drift.
- The colored-noise docstring and the guide (EN/ES) no longer overstate
the slope claim: the shaping is exact in expectation and a measured
slope deviates only by the estimator's random error.
- 'correlacionado' replaces 'correlado' in the ES guide, matching the
established terminology of the GUM pages.
- The unit-coherence test guards its saturated-bin selection against
vacuity, doc snippets use concrete indices and named placeholders,
the intensity comment quotes the actual resolution target of the
shared default, and the CSD phase band is capped at +/-180 deg so
near-zero coherence cannot blow the panel autoscale.
Regenerates the API reference pages and llms-full for the touched
docstrings. Gates re-run: ruff, mypy, bandit, full pytest (3345
passed), conformance byte-stable (272/272), figures within tolerance,
site i18n parity and build.
* fix: PR review round - Sonar gate and CodeRabbit findings
Sonar:
- S1244 (signals.py, 2 bugs): no float equality left - the shaping branch
keys on the color name and the degenerate-record guard uses <=.
- S1192 (_plot/metrology.py): the five 'Frequency [Hz]' literals share a
module-level constant.
- S3776 (spectra.py): fractional_octave_smoothing delegates validation,
the domain-to-power mappings and the window average to four helpers,
dropping the cognitive complexity below the threshold with identical
semantics (the closed-form oracles still pass to 1e-9/1e-12).
- S5778 (test_spectra.py, 2): array constructions moved out of the
pytest.raises blocks.
CodeRabbit:
- Endpoint degrees of freedom (major): the chi-square interval now uses
n = nd at the DC bin, and at the Nyquist bin for an even segment
length, where the one-sided spectrum has a single real Fourier
component; interior bins keep n = 2 nd. Monte Carlo over 300 seeded
records confirms the endpoint statistics (eps 0.163-0.168 vs
sqrt(2/nd) = 0.177, interior 0.130 vs 0.125); pinned by a new test
(even and odd segment lengths) and documented in the result, the
docstrings and the guide (EN/ES).
- The cross-spectrum phase-slope prose now names the group delay
tau_g = -dphi/(2 pi df) and qualifies the propagation-delay reading to
the linear-phase (pure delay) case.
- The smoothed-line closed form names P as the PSD ordinate (units^2/Hz)
instead of the ambiguous 'power of a bin'.
- The default-nperseg wording says bin spacing of at most 4 Hz and points
to the taper-dependent resolution bandwidth instead of implying a
fixed 4 Hz resolution.
- Rewrapped the overlong noise-generator sentence in the three doc trees.
Regenerates the API reference and llms files. Gates: ruff, mypy, bandit,
full pytest (3346 passed), conformance byte-stable (272/272), site i18n
parity and build.
Correct DSP estimator edge cases: TSA time axis, echo detection, EQ validation (#356)
* Report the true 1/fs time axis from time_synchronous_average
The averaged samples sit on the 1/fs sampling grid (each block is aligned
back to it by the band-limited fractional delay before averaging), but the
result reported a time axis with T/M spacing. For a non-integer number of
samples per period the two grids drift apart across the period: with
100.37 samples per period the noiseless recovery error read against the
reported axis was 9.3e-2, versus 2.8e-6 on the true m/fs grid.
Return times = arange(M)/fs, document that the M samples cover the period
only approximately when fs*T is not an integer (McFadden 1987 revised
model: a block average on the sampling grid, no angular resampling), and
assert the non-integer-period tests on the true grid, with a regression
test pinning the 100.37-sample case on both grids. Also soften the comb
response [0, 1] claim (float cancellation beside a tooth can exceed 1 by
a few 1e-9) and mark the ~14 dB N = 32 figure as derived from Eq. 8
rather than printed in the paper.
* Detect inverting echoes and off-sample delays in echo_detection
echo_detection picked the maximum of the signed power cepstrum, so an
inverting reflection (a < 0), whose first rahmonic is a itself and hence
negative, was silently missed: with a = -0.4 at 313 samples the true
rahmonic c[313] = -0.42 was ignored and an unrelated 4739-sample peak
returned. Peak-pick on |cepstrum| within the searched band instead and
report the signed cepstrum value at the peak as the reflection
coefficient (the Mercator series ln(1 + a e^{-j theta}) holds for either
sign of a).
Also refine the reported delay by three-point parabolic interpolation of
|cepstrum| through the peak, so an off-sample delay (e.g. 313.5 samples)
is estimated to a fraction of a sample, and document the bin-splitting
caveat: at off-sample delays the peak-sample coefficient underestimates
|a| (about 65 % of it midway between bins). One-line convention note
distinguishing Milner's IDFT(ln|X|^2) power cepstrum from Bogert's
squared original. Tests cover a < 0 (exact impulse-echo closed form and
a noise source) and the interpolated half-sample delay.
* Validate parametric-EQ sections at the numerical extremes
Three validation gaps in the RBJ cookbook designer:
- A wide 'bw' at an f0 near Nyquist drives the w0/sin(w0) warping factor
into math.sinh overflow, escaping as a raw OverflowError; slightly less
extreme pairs produced a large but finite alpha whose rounded a2 landed
exactly at -1.0: poles on the unit circle designed silently. Wrap the
sinh with an informative ValueError and check the realized coefficients
against the stability triangle (|a2| < 1, |a1| < 1 + a2) after
normalization.
- A shelf slope beyond the gain-dependent bound (A + 1/A)/(A + 1/A - 2)
makes the cookbook's alpha complex and surfaced as a bare 'math domain
error'. Validate the bound at section construction with the limit in
the message (any slope is fine at 0 dB, where A = 1).
- Non-finite f0/gain_db/q/bw/slope passed validation silently (NaN
compares false against every bound) and produced NaN/inf coefficient
rows. Require finiteness explicitly.
Tests cover all three paths plus extreme-but-valid sections that must
keep designing stably.
* Scope the envelope-spectrum amplitude claim and add the band-pass front end
The docstring promised that a sinusoidal modulation 'reads out as a line
at its exact amplitude'; that holds only when the modulation frequency
falls on an analysis bin. Off-bin lines read low by the taper's
scalloping loss (about 1.42 dB, ~15 %, for the default Hann midway
between bins), like any single-record amplitude spectrum. State the
on-bin condition on the closed forms and the caveat in the module,
function and result docstrings, pinned by a midway-between-bins test.
Also add the optional band=(low, high) pre-filter to envelope_spectrum:
a zero-phase 4th-order Butterworth band-pass ahead of the detector, the
band-pass front end of Bendat & Piersol Figure 13.11 and the classical
bearing-envelope chain (isolate the excited resonance band, then
envelope it). The band travels with the result; a test shows it
recovering the exact modulation line under a strong out-of-band
interferer that swamps the raw envelope.
* Warn when a tone burst cannot close on the tone's zero crossing
IEC 60268-1 Clause A2.1 asks for an integral number of full periods, but
the gate closes after round(fs*cycles/frequency) samples: sample-exact
only when f and fs are commensurate. For 10 cycles of 997 Hz at 48 kHz
the burst spans 481.44 samples, gated at 481, and the waveform carries a
residual step at the gate edge. Document the condition and emit a
PhonometryWarning quantifying the realized residual (bounded by
amplitude*sin(pi*f/fs)) for incommensurate pairs; commensurate bursts
stay silent.
Also tighten the resampler's 'equiripple-bounded' wording: the Kaiser
window method bounds both bands by the same delta = 10^(-A/20), but its
ripple decays away from the band edges, unlike a true equiripple design.
* Document estimator conventions verified against their sources
Documentation and comment corrections, no behavior changes:
- random_data: keep the reverse-arrangement acceptance bounds on the
normal approximation deliberately and say why: B&P Table A.6 itself
follows it, and no percentage-point convention on the exact Mahonian
distribution reproduces the published entries (N = 40 and N = 100 at
a = 0.975 pin it down), so exact bounds would disagree with the table
users check against. The p-value stays exact; the one-count boundary
disagreement is now documented on TrendTestResult.
- time_frequency: the spectrogram energy-recovery statement was wrong as
written; integrating 'density' columns over frequency and summing over
time recovers the record energy only after multiplying by the hop
duration, up to the taper roll-off at the record edges (verified
0.988 vs 1 for Hann at 75 % overlap on white noise).
- spectra: note that the adaptive multitaper sigma^2 is mean(x^2) with no
mean removal (consistent with the documented no-detrending
calibration), and mark the 'P&W Eq. 369a' citation as pending
verification against the book.
- compliance + tests/reference_data: transcription note for the bare
'+3' at 20 Hz, Type 2, in ANSI S1.4-1983 Table V (read as upper-only;
+/-3 plausible; either way the verdict cannot change, the realized
deviation is -0.05 dB).
- room_ir: the Mueller & Massarani 'below 4 dB' crest-factor figure
assumes their slight magnitude smoothing, which shaped_sweep_signal
does not implement; measured sweeps here sit at 4.2-4.3 dB.
* Translate EQ section labels, complete the bibliography and changelog
- The per-section legend labels of the parametric-EQ plot printed the raw
English filter-type names in Spanish figures; route them through the
plot string table (peaking -> campana, shelves, band-pass, notch,
all-pass; lowpass/highpass were already translated for the lifter
plot). English output stays byte-identical.
- docs/references.md: add the Harris 1978 window paper and IEC
60268-1:1985, both cited by guide pages but missing from the collected
bibliography; link the IEC entry (and the one in test-signals.md) to
the IEC webstore page.
- Changelog entries for the DSP fixes; regenerate the API reference,
llms-full.txt and the cepstrum_echo figures (the detected-echo marker
moved by the sub-sample delay interpolation); the conformance report is
unchanged (419/419).
* Propagate the echo and envelope semantics to the site guides
Review follow-ups:
- The EN and ES site guides (cepstrum-echoes.mdx) still described the
old echo reading and the unqualified 'exact amplitude' claim; bring
them in step with docs/cepstrum-echoes.md: |cepstrum| peak-picking,
signed coefficient, interpolated delay, bin-splitting caveat, the
on-bin/scalloping condition and the new band= front end (the docs page
gains the same envelope-section text).
- State in the changelog and the result docstring that delay is no
longer exactly delay_samples/fs when the interpolated offset is
nonzero (delay_samples remains the whole-sample index the coefficient
is read at), and merge the duplicated Unreleased headings.
- Clearer stability message (poles 'do not lie strictly inside the unit
circle', full-precision coefficients) and an informative short-record
error for the envelope band-pass pre-filter instead of scipy's padlen
message.
- Sharper tone-burst warning test (asserts the quantified span and
residual) and a tighter interpolated-delay tolerance.
* Address review notes: cepstrum terminology, band-pass claims, edge validation
- Guides (docs and site, EN and ES): call the signed quantity what it is,
Milner's cepstrum of the log-power spectrum, and spell out that
Bogert's original 1963 power cepstrum squares once more and is
non-negative, so the signed rahmonics are the library's documented
convention; qualify the band-pass front end (out-of-band content is
strongly attenuated, not eliminated: finite Butterworth rejection and
small zero-phase edge transients).
- cepstrum: render reflection_coefficient as inline code in the
delay_samples docstring so the generated API page stops linking the
unrelated underwater seabed function.
- envelope: validate that 'band' is exactly two numeric edges before
indexing (a one-item tuple raised IndexError, extra entries were
ignored); the validated edges now travel to the result.
- equalizer: reject finite but unrepresentable gains through a shared
_gain_amplitude helper (10^(gain_db/40) overflowing, or underflowing
to zero and then dividing by zero in the designers); document the 0 dB
exception to the shelf-slope bound on the slope field.
- tone_burst: validate the repetition configuration before emitting the
incommensurate-frequency warning, so invalid setups raise even under
a warnings-as-errors filter.
- comb_filter_response: reject phase products that overflow to infinity
(sin(inf) returned NaN instead of the documented bounded response).
Tests for every new rejection path; API reference and llms regenerated.
* Test the representable gain amplitude as a positive finite bound
* Scope the first-rahmonic height claim to the source-free identity