← [Documentation index](README.md)

# Time Weighting and Integration

Accurate SPL measurement requires capturing energy over specific time windows.
phonometry implements exact time constants per **IEC 61672-1:2013**.

## 1. The exponential detector

A sound level meter's needle cannot follow the pressure waveform: it shows a
running *mean square* with an exponential memory. Formally (IEC 61672-1, 3.8):

$$
\tau\ \frac{dy}{dt} + y = x^2(t)
\quad\Longleftrightarrow\quad
y(t) = \frac{1}{\tau} \int_{-\infty}^{t} x^2(\xi)\ e^{-(t-\xi)/\tau}\ d\xi
$$

a first-order low-pass on the squared signal. The time constant τ sets the
trade-off: **Fast** (125 ms) follows speech-like fluctuations, **Slow** (1 s)
steadies the readout for quasi-stationary noise. After a step onset the
envelope reaches 63 % of its final value in one τ and ~99.97 % after 8τ;
that is why level analyses discard the first instants of a recording.

<picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_time_weighting_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/diagram_time_weighting.svg" alt="The exponential-detector chain: the band pressure is squared, smoothed by a one-pole RC low-pass with time constant tau, and converted to decibels, with the Fast, Slow and Impulse time constants listed" width="88%"></picture>

## 2. The three time weightings

* **Fast (`fast`):** τ = 125 ms. Standard for noise fluctuations.
* **Slow (`slow`):** τ = 1000 ms. Standard for steady noise.
* **Impulse (`impulse`):** **Asymmetric** ballistics. 35 ms rise time for rapid
  onset capture, 1500 ms decay for readability.

<picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/anim_time_weighting_dark.gif"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/anim_time_weighting.gif" alt="Animation: a tone burst driving the RC exponential detector, the capacitor charging and draining, while the Fast, Slow and Impulse meter needles follow their own ballistics" width="640" height="360" loading="lazy"></picture>

[Watch the high-resolution video (WebM)](https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/anim_time_weighting.webm)

<picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/time_weighting_analysis_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/time_weighting_analysis.svg" alt="Fast, Slow and Impulse time weighting responses to a noise burst" width="80%"></picture>

<details>
<summary>Show the code for this figure</summary>

```python
import numpy as np
import matplotlib.pyplot as plt
from phonometry import metrology

fs = 48000
t = np.arange(int(fs * 4)) / fs
burst = np.zeros_like(t)  # 0.5 s noise burst (Pa) starting at t = 1 s
rng = np.random.default_rng(42)
burst[fs:int(1.5 * fs)] = 0.2 * rng.standard_normal(int(0.5 * fs))

p0 = 2e-5
plt.figure()
for mode in ('fast', 'slow', 'impulse'):
    envelope = metrology.time_weighting(burst, fs, mode=mode)
    plt.plot(t, 10 * np.log10(np.maximum(envelope, 1e-12) / p0**2), label=mode)
plt.xlabel('Time [s]')
plt.ylabel('Level [dB SPL]')
plt.legend()
plt.show()
```

</details>

```python
import numpy as np
from phonometry import metrology

# recording: a calibrated microphone capture (Pa) — recorded through your measurement chain. Synthesized here so the guide runs standalone.
fs = 48000
recording = 0.2 * np.sin(2 * np.pi * 1000 * np.arange(fs) / fs)

# Calculate energy envelope (Mean Square)
energy_envelope = metrology.time_weighting(recording, fs, mode='fast')
# dB SPL relative to 20 μPa
spl_t = 10 * np.log10(energy_envelope / (2e-5)**2)

print(f"Steady-state Fast level: {spl_t[-1]:.1f} dB SPL")
# Steady-state Fast level: 77.0 dB SPL
```

The asymmetric Impulse ballistics use two constants, a fast attack and a slow
decay, switching per sample on the sign of the change:

$$
y[n] = y[n-1] + \alpha \ (x^2[n] - y[n-1]), \qquad
\alpha = \begin{cases}1 - e^{-1/(f_s \cdot 0.035)} & x^2[n] > y[n-1]\\[2pt] 1 - e^{-1/(f_s \cdot 1.5)} & \text{otherwise}\end{cases}
$$

### Choosing F, S or I

- **Fast** is the default of nearly every modern method: percentile levels,
  impulsive-event detection, community noise, the general "level vs time"
  plot. Its 125 ms constant is of the same order as the ear's own loudness
  integration time, so an LAF trace roughly tracks what a listener notices.
- **Slow** suits quasi-stationary sources and any procedure that needs a
  steady readout: it averages away the flicker of a fluctuating source at
  the price of missing short events (a 100 ms burst peaks about 7.5 dB lower
  on S than on F). Some legacy methods prescribe it outright, most famously
  aircraft-certification levels, which are built from Slow-weighted samples.
- **Impulse** is legacy, and deprecated for rating. It was a 1960s attempt
  to make a meter needle track the perceived loudness of impacts; it does
  not (the 35 ms attack still misses very short impulses, and the 1.5 s hold
  exaggerates duration). It entered the international standards with
  IEC 60651 and was dropped from the requirements of its successor
  IEC 61672-1, whose first edition (2002) explains why: I-weighted levels
  are not suitable for rating impulsive sounds. It survives in meters only
  for continuity with older national requirements. Modern practice rates
  impulsiveness with
  $L_{Aeq}$ plus an adjustment (ISO 1996-1 Table A.1, or the onset analysis
  of [Impulsive-sound prominence](impulse-prominence.md)) and assesses
  hearing-damage risk with $L_{Cpeak}$, never with I-weighted levels.

## 3. `time_weighting()` / `TimeWeighting` parameters

| Parameter | Type | Units | Range / default | Notes |
| :--- | :--- | :--- | :--- | :--- |
| `x` | 1D or 2D array | pressure (any scale) | non-empty | Squared internally; output is a mean-square envelope |
| `fs` | int | Hz | > 0 | |
| `mode` | str | — | `'fast'` (default), `'slow'`, `'impulse'` | τ = 125 ms / 1 s / 35 ms attack + 1.5 s decay |
| `TimeWeighting(fs, mode)` (class) | — | — | — | Stateful variant for streaming: `process(x)` carries the integrator state between blocks |

The output has the units of $x^2$: take `10*log10(y / p0**2)` for SPL or use
the level functions, which do it for you.

## 4. Verified ballistics (IEC 61672-1 Table 4)

The Fast envelope's response to 4 kHz tonebursts lands exactly on the
standard's reference values: the example below verifies the 200 ms Fast
burst row; the CI suite covers the full Table 4, from 1 s down to 1 ms for F
and 1 s down to 2 ms for S, at class 1 acceptance limits:

<picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tone_burst_iec_dark.svg"><img src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/tone_burst_iec.svg" alt="Fast envelope responses to 200, 50 and 10 ms tone bursts peaking exactly at the IEC 61672-1 Table 4 reference values" width="80%"></picture>

<details>
<summary>Show the code for this figure</summary>

```python
import numpy as np
import matplotlib.pyplot as plt
from phonometry import metrology

fs = 48000
t = np.arange(int(fs * 2)) / fs
tone = np.sin(2 * np.pi * 4000 * t)

# Steady-state Fast reference of the continuous tone
reference = metrology.time_weighting(tone, fs, mode='fast')[int(1.5 * fs):].mean()

# 200 ms burst of the same tone (IEC 61672-1 Table 4 target: -1.0 dB)
burst = np.zeros_like(t)
burst[int(0.5 * fs):int(0.7 * fs)] = tone[int(0.5 * fs):int(0.7 * fs)]
envelope = metrology.time_weighting(burst, fs, mode='fast')
env_db = 10 * np.log10(np.maximum(envelope / reference, 1e-6))

plt.figure()
plt.plot(t, env_db, label='Fast envelope')
plt.axhline(-1.0, linestyle='--', label='IEC target −1.0 dB')
plt.xlabel('Time [s]')
plt.ylabel('Level re steady state [dB]')
plt.legend()
plt.show()
```

</details>

## 5. Initial state

By default, the exponential integrator starts from rest (`y[-1] = 0`). Passing
`initial_state=None` leaves this default unspecified, while `initial_state='zero'`
requests the same zero state explicitly. If the recorded segment begins after a
steady signal is already present, you can start from the first sample energy instead:

```python
import numpy as np
from phonometry import metrology

# recording: a calibrated microphone capture (Pa) — recorded through your measurement chain. Synthesized here so the guide runs standalone.
fs = 48000
recording = 0.2 * np.sin(2 * np.pi * 1000 * np.arange(fs) / fs)

energy_envelope = metrology.time_weighting(recording, fs, mode='fast', initial_state='first')
```

## 6. Block processing

For block processing, pass the last output value from the previous block as the
next block's `initial_state` instead of resetting each block:

```python
from phonometry import metrology

state = None

# audio_blocks: consecutive frames of your calibrated recording (Pa),
#   streamed from your sound card or read from a WAV in blocks.
for block in audio_blocks:
    energy_envelope = metrology.time_weighting(block, fs, mode='fast', initial_state=state)
    state = energy_envelope[-1]
```

For multichannel blocks with time on the last axis, carry one state per channel:
use `state = energy_envelope[..., -1]`. A scalar `initial_state` is applied to
every channel, while an array must match or broadcast to the non-time shape,
such as `(n_channels,)` for input shaped `(n_channels, n_samples)`.

Or let the `TimeWeighting` class carry the state for you:

```python
from phonometry import metrology

tw = metrology.TimeWeighting(fs, mode='fast')
# audio_blocks: consecutive frames of your calibrated recording (Pa),
#   streamed from your sound card or read from a WAV in blocks.
for block in audio_blocks:
    energy_envelope = tw.process(block)
```

Concatenated block outputs are exactly equal to a single continuous call
(verified for all three modes, mono and multichannel). Call `tw.reset()` to
start from rest again.

## 7. Performance note

The `impulse` mode uses an asymmetric kernel that is JIT-compiled when
[numba](https://numba.pydata.org/) is installed (`pip install phonometry[perf]`).
Without numba a pure-Python fallback produces identical results, just slower.

See [Integrated & Statistical Levels](levels.md) for Leq/LN metrics built on
these envelopes, and [Why phonometry](why-phonometry.md) for the IEC
61672-1 tone-burst verification.

## References

- International Electrotechnical Commission. (2013). *Electroacoustics —
  Sound level meters — Part 1: Specifications* (IEC 61672-1:2013).
  [IEC webstore](https://webstore.iec.ch/en/publication/5708).
  The exponential-detector definition, the F and S time constants and the
  Table 4 toneburst responses the ballistics are verified against in CI.

## Standards

IEC 61672-1:2013, *Electroacoustics — Sound level meters —
Part 1: Specifications*: the exponential time-weighting detector (clause 3.8)
with the F and S time constants (clause 5.7), and the 4 kHz toneburst reference
responses of Table 4 (class 1 acceptance limits) used to verify the ballistics
in CI.
