Skip to content

Block Processing

Standards: IEC 61260IEC 61672Key references: Oppenheim & Schafer 2010

Some measurements never fit in memory: an hour-long environmental recording, a live monitor that must report levels while the microphone is still capturing, or an embedded logger that only ever sees one buffer at a time. In all of these the signal has to be processed block by block, and the filters must behave exactly as if they had seen the whole signal at once.

Two lanes comparing block processing with the filter state carried across blocks, giving one continuous envelope, versus reset each block, where the envelope restarts from zero at every seamTwo lanes comparing block processing with the filter state carried across blocks, giving one continuous envelope, versus reset each block, where the envelope restarts from zero at every seam

The OctaveFilterBank, WeightingFilter (for A, C, or Z-weighting) and TimeWeighting classes support block (streaming) processing: the internal filter state is carried between calls, so concatenated block outputs match a single full-signal pass.

Stateful block processing matching the continuous result versus independent blocks restarting the filter transient at each boundaryStateful block processing matching the continuous result versus independent blocks restarting the filter transient at each boundary

With stateful=True the concatenated block outputs match the continuous result exactly; without state, every block boundary restarts the filter transient.

Show the code for this figure
import matplotlib.pyplot as plt
import numpy as np
from phonometry import metrology
# 1 kHz octave band: four stateful blocks vs one continuous pass
fs, block = 8000, 1000
rng = np.random.default_rng(42)
x = rng.standard_normal(4 * block)
t = np.arange(x.size) / fs
bank = metrology.OctaveFilterBank(fs, fraction=1, limits=[900, 1100],
stateful=True, resample=False)
streamed = np.concatenate([
bank.filter(x[i * block:(i + 1) * block], sigbands=True,
detrend=False, calculate_level=False)[2][0]
for i in range(4)
])
offline = metrology.OctaveFilterBank(fs, fraction=1, limits=[900, 1100],
resample=False).filter(
x, sigbands=True, detrend=False, calculate_level=False)[2][0]
print(np.max(np.abs(streamed - offline))) # 0.0 (bit-exact)
fig, ax = plt.subplots(figsize=(9, 4.5))
ax.plot(t, offline, linewidth=2.5, alpha=0.35, label="Continuous (whole signal)")
ax.plot(t, streamed, label="Stateful blocks (state carried)")
ax.axvline(block / fs, color="gray", linestyle=":", label="Block boundary")
ax.set(xlim=(0.11, 0.17), xlabel="Time [s]", ylabel="Amplitude")
ax.legend()
plt.show()

Create a stateful filter bank with stateful=True. The internal state is zero-initialized by default but may be initialized for step-response steady-state (like scipy.signal.sosfilt_zi) with steady_ic=True. The options that must be disabled in stateful mode are summarized in the constraints table at the end of this guide.

This example streams a WAV file with soundfile, an optional dependency (pip install soundfile). Any block source works just as well: scipy.io.wavfile plus manual slicing, or a live capture callback.

import soundfile as sf
from phonometry import metrology
fs = 48000
octave_filter = metrology.OctaveFilterBank(fs, 1, stateful=True, resample=False)
afilter = metrology.WeightingFilter(fs, "A", stateful=True)
for block in sf.blocks("measurement.wav", blocksize=256, overlap=0):
# Apply A-filter
weighted = afilter.filter(block)
# Split into octave bands
block_spl, _, block_output = octave_filter.filter(weighted, sigbands=True, detrend=False)
# further signal processing
...

Use the TimeWeighting class (state carried automatically):

from phonometry import metrology
tw = metrology.TimeWeighting(fs, mode="fast")
# audio_blocks: successive frames of your microphone recording (Pa),
# e.g. from sf.blocks("measurement.wav", ...) as in the block above.
for block in audio_blocks:
envelope = tw.process(block)

Or manage the state yourself with the functional API; see Time Weighting.

All stateful classes handle multichannel input (channels, samples): the state is allocated lazily on the first call to match the channel count, and reallocated if the channel count changes (e.g. switching from stereo to mono resets the state).

Every filter in the library runs as a cascade of second-order sections in transposed direct form II. For one section with coefficients :

The pair per section is the filter’s entire memory of the past. stateful=True stores these values when a block ends and restores them when the next begins, so the recursion cannot tell where one buffer stopped and the next started: the concatenated output equals the single-pass output exactly, not approximately. The TimeWeighting detector carries even less, just its last envelope value , which is the same value the functional API hands back as initial_state (see Time Weighting).

  • Per-block preprocessing creates seams. Anything computed from a single block that should be global, like detrending (removing the block’s own mean) or normalization, gives each block a slightly different operation: the outputs no longer concatenate into the continuous result. This is why detrend must be False in stateful mode.
  • Not every metric streams. Energy metrics accumulate cleanly (a running Leq is a running energy sum), but rank statistics do not: the L90 of a recording is not any combination of per-block L90 values. Stream the envelope and compute percentiles once, on the pooled result.
  • The first block still carries the onset transient. State starts at rest, so the filter settles during the first instants exactly as in a single pass. Use steady_ic=True to start in step-response steady state, or discard the settling time once (not once per block).
  • One stream per object. A stateful instance holds the memory of one signal; feeding two interleaved streams through it corrupts both. Create one filter object per stream: the design cost is paid once at construction, not per block.

The canonical streaming loop weights, envelopes and reports block by block, carrying all state across calls:

import numpy as np
from phonometry import metrology
fs, block = 48000, 4800 # 100 ms blocks
aw = metrology.WeightingFilter(fs, "A", stateful=True)
env = metrology.TimeWeighting(fs, mode="fast") # the class is inherently stateful
for x in audio_stream(block): # your capture callback
y = env.process(aw.filter(x))
spl = 10 * np.log10(y[..., -1] / (2e-5) ** 2) # instantaneous LAF
display(spl)
OptionStateful behaviorWhy
detrendmust be FalsePer-block detrending creates boundary discontinuities
resamplemust be FalseThe resampler is not stateful
zero_phaseunsupportedForward-backward filtering needs the whole signal
high_accuracy (weighting)resolves to False by default (the legacy bilinear design, see Frequency Weighting); explicitly passing True raises ValueErrorThe polyphase resampling inside is block-incompatible
steady_icoptionalStarts the filters in step-response steady state

Covered. IEC 61260-1:2014 and IEC 61672-1:2013 exactly as far as Filter Banks, Frequency Weighting and Time Weighting already implement them: stateful=True on OctaveFilterBank, WeightingFilter and TimeWeighting carries the direct-form-II-transposed section state (z1, z2), or the envelope’s last value for TimeWeighting, across block boundaries, so the concatenated streamed output is bit-exact with a single full-signal pass. This page adds no normative content of its own; it only proves and documents that equivalence.

Not covered. zero_phase forward-backward filtering needs the whole signal, so it is unsupported in stateful mode; use the offline path of Filter Banks instead. high_accuracy weighting design resolves to the legacy bilinear filter in stateful mode (passing True explicitly raises ValueError), because its polyphase resampling stage is not block-compatible; see Frequency Weighting for the offline high-accuracy design. Rank statistics such as L90 do not stream either: stream the envelope and compute percentiles once on the pooled result, as Integrated and Statistical Levels describes.

  • International Electrotechnical Commission. (2013). Electroacoustics — Sound level meters — Part 1: Specifications (IEC 61672-1:2013). The streamed frequency and time weightings are the same designs this standard governs; the carried state keeps the class and tolerance claims of the Frequency Weighting and Time Weighting pages valid in streaming use.
  • International Electrotechnical Commission. (2014). Electroacoustics — Octave-band and fractional-octave-band filters — Part 1: Specifications (IEC 61260-1:2014). Block processing adds no normative content of its own: the streamed octave and fractional-octave filters are the same designs this standard governs, and carrying the internal filter state across blocks is exactly what makes the concatenated output identical to a single full-signal pass, so every class and tolerance claim of the Filter Banks page holds unchanged in streaming use.
  • Oppenheim, A. V., & Schafer, R. W. (2010). Discrete-time signal processing (3rd ed.). Pearson. The direct-form filter structures and state recursion behind the carried state equation (ISBN 978-0-13-198842-2).
Created and maintained by· GitHub· PyPI· All projects