Skip to content

Test signals and sample-rate tools

Standards: IEC 60268Key references: Bendat & Piersol 2010

A measurement is only as trustworthy as its stimulus and its sample-rate bookkeeping. This page covers the signal toolbox of phonometry.metrology: tone bursts with the exact gating IEC 60268-1 prescribes, the colored-noise generators (detailed in the spectral analysis guide), resampling whose anti-alias rejection is a stated, verifiable specification rather than a library default, and fractional delay that shifts a record by any sub-sample amount with band-limited exactness.

The gated sine burst is the standard stimulus for dynamic behaviour: sound-level-meter ballistics, quasi-peak meters, loudspeaker power handling. IEC 60268-1:1985 (Clause A2.1) pins down what a well-formed burst is: it “should start at the zero-crossing of the tone and should consist of an integral number of full periods”. tone_burst generates exactly that, as a single burst or as the repetitive train of Clause A2.2 in which each burst occupies one full repetition period:

from phonometry import tone_burst
# One 5 ms burst of 5 kHz tone (25 full periods), as in Table AII.
single = tone_burst(48000, 5000, 25)
print(single.burst_samples) # 240 samples = 5 ms at 48 kHz
# Clause A2.2: 5 ms bursts at 10 bursts per second.
train = tone_burst(48000, 5000, 25, repetitions=4, repetition_rate=10)
print(train.period_samples, train.duty_cycle) # 4800, 0.05
train.plot() # waveform with the gating envelope

The result carries the record, the rectangular gating envelope and the exact sample bookkeeping (burst_samples, onset_sample, period_samples, duty_cycle), so a test report can state its stimulus numerically. Because the gate spans an integral number of full periods starting at a zero crossing, the burst energy has the closed form A²N/2 exactly, which is how the generator is verified.

IEC 60268-1 tone bursts: a single 5 ms burst of 5 kHz tone starting at a zero crossing with its rectangular gating envelope, and a repetitive train of four bursts at 10 bursts per second with a 5 percent duty cycleIEC 60268-1 tone bursts: a single 5 ms burst of 5 kHz tone starting at a zero crossing with its rectangular gating envelope, and a repetitive train of four bursts at 10 bursts per second with a 5 percent duty cycle
Show the code for this figure
import matplotlib.pyplot as plt
import numpy as np
from phonometry import tone_burst
fs = 48000.0
single = tone_burst(fs, 5000, 25, pre_silence=0.001, post_silence=0.001)
train = tone_burst(fs, 5000, 25, repetitions=4, repetition_rate=10)
fig, axes = plt.subplots(2, 1, figsize=(10, 6.4))
t_ms = 1e3 * np.arange(single.signal.size) / single.fs
axes[0].plot(t_ms, single.signal, lw=0.9)
axes[0].plot(t_ms, single.envelope, "r--", label="Gating envelope")
axes[0].plot(t_ms, -single.envelope, "r--")
axes[0].set_xlabel("Time [ms]")
t_s = np.arange(train.signal.size) / train.fs
axes[1].plot(t_s, train.signal, lw=0.5)
axes[1].plot(t_s, train.envelope, "r--", label="Gating envelope")
axes[1].plot(t_s, -train.envelope, "r--")
axes[1].set_xlabel("Time [s]")
for ax in axes:
ax.set_ylabel("Amplitude")
ax.legend(loc="upper right")
plt.tight_layout()
plt.show()

These are the bursts behind the Fast/Slow/Impulse ballistics reference responses (IEC 61672-1 Table 4 uses 4 kHz tonebursts of 200, 50 and 10 ms) and the quasi-peak dynamic tests of IEC 60268-1 itself (5 kHz bursts of 1 to 200 ms, Table AII).

2. Resampling with a stated anti-alias specification

Section titled “2. Resampling with a stated anti-alias specification”

Sample-rate conversion hides a filter, and that filter decides how much aliased energy contaminates the result. resample_signal performs rational polyphase resampling (44.1 to 48 kHz is the ratio 160/147) with a lowpass FIR designed inside the function by the Kaiser window method, from two numbers the caller controls:

  • stopband_attenuation_db (default 120): the alias rejection, with the stopband starting exactly at the smaller of the two Nyquist frequencies, where folding happens;
  • transition_width (default 0.05): the fraction of that Nyquist frequency given up to the filter’s transition band, so the passband ends at (1 - transition_width)·f_Nyq and is flat within the same Kaiser ripple bound δ = 10^(-A/20).
from phonometry import noise_signal, resample_signal
x = noise_signal(44100, 5.0, color="pink", seed=1)
res = resample_signal(x, 44100, 48000) # 120 dB alias rejection
print(res.up, res.down) # 160, 147
print(res.n_taps, res.passband_edge_hz) # designed FIR, 20947.5 Hz
res.plot() # the delivered anti-alias filter against its design spec
Magnitude response of the anti-alias filter designed for a 44.1 to 48 kilohertz polyphase resampling on a logarithmic frequency axis: a flat passband up to the dashed passband edge just below 21 kilohertz, a steep transition to the dashed stopband edge at 22.05 kilohertz, and a stopband floor staying below the dotted minus 120 decibel design-attenuation line across the shaded rejected bandMagnitude response of the anti-alias filter designed for a 44.1 to 48 kilohertz polyphase resampling on a logarithmic frequency axis: a flat passband up to the dashed passband edge just below 21 kilohertz, a steep transition to the dashed stopband edge at 22.05 kilohertz, and a stopband floor staying below the dotted minus 120 decibel design-attenuation line across the shaded rejected band

The delivered anti-alias filter of the default 44.1 → 48 kHz conversion: the stopband starts exactly at the smaller Nyquist frequency (where aliases fold) and stays below the −120 dB design line; the passband ends 5 % below it, flat within the same ripple bound.

Show the code for this figure
import matplotlib.pyplot as plt
import numpy as np
from scipy import signal
from phonometry import noise_signal, resample_signal
x = noise_signal(44100, 5.0, color="pink", seed=1)
res = resample_signal(x, 44100, 48000) # 120 dB alias rejection
# res is the ResampledSignalResult computed in the example above.
# One line — the delivered anti-alias filter against its design spec:
res.plot()
plt.show()
# By hand, from the taps the result carries — mirroring what
# ResampledSignalResult.plot() draws:
fs_up = res.original_fs * res.up
freqs, h = signal.freqz(res.filter_taps, worN=1 << 18, fs=fs_up)
mag_db = 20 * np.log10(np.maximum(np.abs(h), 1e-300))
view = (freqs > 0) & (freqs <= 4 * res.stopband_edge_hz)
fig, ax = plt.subplots()
ax.semilogx(freqs[view], mag_db[view], label="Anti-alias filter |H(f)|")
ax.axvline(res.passband_edge_hz, color="g", linestyle="--",
label="Passband edge")
ax.axvline(res.stopband_edge_hz, color="r", linestyle="--",
label="Stopband edge (alias fold)")
ax.axhline(-res.stopband_attenuation_db, color="k", linestyle=":",
label="Design attenuation -120 dB")
ax.axvspan(res.stopband_edge_hz, 4 * res.stopband_edge_hz,
color="r", alpha=0.08)
ax.set(xlabel="Frequency [Hz]", ylabel="Magnitude [dB]")
ax.legend()
plt.show()

The designed taps travel with the result (filter_taps), so the specification is checkable: the test suite measures the frequency response of the returned filter and asserts the passband deviation and stopband leakage against the design’s own ripple bound (the design internally targets 1 dB past the request, so the delivered filter meets the stated numbers rather than a Kaiser-formula approximation of them). A passband tone resampled through the default specification matches the analytic tone at the new rate within 10^(-120/20) = 10^-6.

Several estimators resample internally at fixed rates (the ECMA-418-2 psychoacoustics at 48 kHz, STOI at 10 kHz); this function is the public, documented counterpart for preparing records outside those chains.

fractional_delay shifts a record by any number of samples, including sub-sample amounts, by multiplying the spectrum with the phase ramp e^(-j2πf·D/fs): every component is delayed by exactly D samples. Two boundary conventions cover the two use cases:

  • mode="linear" (default) zero-pads the record past the shift, so samples leaving one end land in padding instead of wrapping around. Use it for transients and impulse responses; it is bit-identical to the alignment kernel inside align_impulse_responses. An integer delay reduces to an exact sample shift.
  • mode="circular" applies the ramp over the record itself and wraps. For periodic records it is exact: a tone centered on a DFT bin delayed by D samples equals the analytically delayed tone to machine precision, and its phase changes by exactly -2πf·D/fs radians.
import numpy as np
from phonometry import fractional_delay
y = fractional_delay(x, 0.37) # 0.37 samples later
z = fractional_delay(x, -2.5, mode="circular") # advance, wrapped

One subtlety worth knowing: a real record of even length cannot carry a fractionally delayed Nyquist-bin component (the inverse real FFT keeps only its real part). Any properly sampled signal is band-limited below Nyquist, so in practice the operation is exact; for synthetic corner cases, odd lengths avoid the bin entirely.

The deterministic colored-noise generators (noise_signal: white, pink, red, blue, violet with an exact power-law slope and bit-reproducible seeds) complete the toolbox; they are documented with their spectral verification in the spectral analysis guide.

The window figures of merit quantify the taper every spectral estimate in the library rests on; the burst generator feeds ballistics and dynamic-response testing; and the resampler and fractional delay are the sample-rate half of correlation and delay work, where sub-sample alignment is the difference between averaging impulse responses and smearing them.

Covered. IEC 60268-1:1985 Annex A, Clause A2: the tone burst that “should start at the zero-crossing of the tone and should consist of an integral number of full periods” (A2.1) and the repetitive burst train of A2.2, implemented by tone_burst and hand-checked against the Table AII durations. Bendat & Piersol Section 10.2’s anti-alias requirement, turned into an explicit, checkable specification by resample_signal’s stopband attenuation and transition width.

Not covered. IEC 60268-1:1985 is a general standard for sound system equipment. Only the Annex A tone-burst clauses are implemented here; its other parts (amplifiers, loudspeakers, connectors, and so on) are untouched. fractional_delay and the colored-noise generators of noise_signal are general-purpose DSP tools with no governing standard of their own; their accuracy claims are closed-form, not normative.

  • Bendat, J. S., & Piersol, A. G. (2010). Random data: Analysis and measurement procedures (4th ed.). Wiley. https://doi.org/10.1002/9781118032428Section 10.2 (data preparation: sampling, aliasing and the anti-alias filtering requirement the resampler states explicitly). ISBN 978-0-470-24877-5.
  • International Electrotechnical Commission. (1985). Sound system equipment — Part 1: General (IEC 60268-1:1985). Annex A, Clause A2: tone bursts starting at the zero crossing of the tone with an integral number of full periods (A2.1), repetitive burst trains at a stated repetition rate (A2.2), and the Table AII burst durations the sample counts are hand-checked against.
Created and maintained by· GitHub· PyPI· All projects