Skip to main content

Octave-Band and Fractional Octave-Band filter for signals in time domain.

Project description

Donate Donate PyPI version Python application

PyOctaveBand

Advanced Octave-Band and Fractional Octave-Band filter bank for signals in the time domain. Fully compliant with ANSI s1.11-2004 and IEC 61260-1-2014.

This library provides professional-grade tools for acoustic analysis, including frequency weighting (A, C, Z), time ballistics (Fast, Slow, Impulse), and multiple filter architectures.

Now available on PyPI.


📑 Table of Contents

  1. 🚀 Getting Started
  2. 🛠️ Filter Architectures
  3. 🔊 Acoustic Weighting (A, C, Z)
  4. ⏱️ Time Weighting and Integration
  5. ⚡ Performance: OctaveFilterBank
  6. 🔍 Filter Usage and Examples
  7. 📏 Calibration and dBFS
  8. 📊 Signal Decomposition
  9. 📖 Theory and Equations
  10. 🧪 Testing and Quality

🚀 Getting Started

Installation

Option 1: From PyPI (Recommended) Install PyOctaveBand directly using pip:

pip install PyOctaveBand

Option 2: Cloning and Installing Clone the repository and install it manually:

git clone https://github.com/jmrplens/PyOctaveBand.git
cd PyOctaveBand
pip install .

Option 3: Git Submodule Add PyOctaveBand as a dependency within your own git repository:

git submodule add https://github.com/jmrplens/PyOctaveBand.git
# Then install in editable mode to use it from your project
pip install -e ./PyOctaveBand

📖 Quick API Reference

All core functionality can be imported directly from the pyoctaveband package.

Name Type Description (Inputs) Usage Snippet (Outputs)
octavefilter function High-level analysis.
x: Signal array
fs: Sample rate [Hz]
fraction: 1, 3, etc. (Default: 1)
order: Filter order (Default: 6)
filter_type: 'butter', 'cheby1', 'cheby2', 'ellip', 'bessel' (Default: 'butter')
sigbands: Return time signals (Default: False)
spl, freq = octavefilter(x, fs, ...)
spl: levels [dB]
freq: frequencies [Hz]

With sigbands=True:
spl, freq, xb = octavefilter(x, fs, sigbands=True)
xb: List of filtered signals (one per band)
OctaveFilterBank class Efficient bank implementation.
fs: Sample rate [Hz]
fraction: 1, 3, etc.
order: Filter order
limits: [f_min, f_max] (Default: [12, 20000])
filter_type: Architecture name
bank = OctaveFilterBank(fs=48000, fraction=3, order=6, filter_type='butter')
spl, f = bank.filter(x)

bank: Instance of the filter bank
weighting_filter function Acoustic weighting.
x: Signal array
fs: Sample rate [Hz]
curve: 'A', 'C', or 'Z' (Default: 'A')
y = weighting_filter(x, fs, curve='A')

y: 1D array of weighted signal
time_weighting function Energy capture.
x: Signal array
fs: Sample rate [Hz]
mode: 'fast', 'slow', or 'impulse'
env = time_weighting(x, fs, mode='fast')

env: 1D array of energy envelope (Mean Square)
linkwitz_riley function Audio crossover.
x: Signal array
fs: Sample rate [Hz]
freq: Crossover frequency [Hz]
order: 4 or 8 (Default: 4)
lo, hi = linkwitz_riley(x, fs, freq=1000, order=4)

lo: Low-pass filtered signal
hi: High-pass filtered signal
calculate_sensitivity function SPL Calibration.
x_ref: Calibration signal
fs: Sample rate [Hz]
target_spl: Level of calibrator (Default: 94.0)
s = calculate_sensitivity(x_ref, fs, target_spl=94.0)

s: Float (multiplier for pressure)
getansifrequencies function ANSI Frequency generator.
fraction: 1, 3, etc. (Default: 1)
limits: [f_min, f_max] (Default: [12, 20000])
f = getansifrequencies(fraction=3, limits=[20, 20000])

f: List of center frequencies [Hz]
normalizedfreq function Band edge calculator.
freq: Center frequency [Hz]
fraction: 1, 3, etc.
f_low, f_up = normalizedfreq(1000, 3)

f_low: Lower cut-off frequency [Hz]
f_up: Upper cut-off frequency [Hz]

Basic Usage: 1/3 Octave Analysis

Analyze a signal and get the Sound Pressure Level (SPL) per frequency band.

import numpy as np
from pyoctaveband import octavefilter

fs = 48000
t = np.linspace(0, 1, fs)
# Composite signal: 100Hz + 1000Hz
signal = np.sin(2 * np.pi * 100 * t) + np.sin(2 * np.pi * 1000 * t)

# Apply 1/3 octave filter bank
spl, freq = octavefilter(signal, fs=fs, fraction=3)

print(f"Bands: {freq}")
print(f"SPL [dB]: {spl}")

🛠️ Filter Architectures

PyOctaveBand supports several filter types, each with its own transfer function characteristic.

Filter Comparison and Zoom

We use Second-Order Sections (SOS) for all filters to ensure numerical stability. The following plot compares the architectures focusing on the -3 dB crossover point.

Type Name Usage Example Best For
butter Butterworth octavefilter(x, fs, filter_type='butter') General acoustic measurement.
cheby1 Chebyshev I octavefilter(x, fs, filter_type='cheby1', ripple=0.1) Sharper roll-off at the cost of ripple.
cheby2 Chebyshev II octavefilter(x, fs, filter_type='cheby2', attenuation=60) Flat passband with stopband zeros.
ellip Elliptic octavefilter(x, fs, filter_type='ellip', ripple=0.1, attenuation=60) Maximum selectivity.
bessel Bessel octavefilter(x, fs, filter_type='bessel') Preserving transient waveform shapes.

Gallery of Filter Bank Responses

Full spectral view of the filter banks for Octave (1/1) and 1/3-Octave fractions.

Architecture 1/1 Octave (Fraction=1) 1/3 Octave (Fraction=3)
Butterworth
Chebyshev I
Chebyshev II
Elliptic
Bessel

🔊 Acoustic Weighting (A, C, Z)

Frequency weighting curves simulate the human ear's sensitivity.

  • A-Weighting (A): Standard for environmental noise (IEC 61672-1).
  • C-Weighting (C): Used for peak sound pressure and high-level noise.
  • Z-Weighting (Z): Zero weighting, completely flat response.
from pyoctaveband import weighting_filter

# Apply A-weighting to the raw signal
weighted_signal = weighting_filter(signal, fs, curve='A')

# Apply C-weighting for peak analysis
c_weighted_signal = weighting_filter(signal, fs, curve='C')

⏱️ Time Weighting and Integration

Accurate SPL measurement requires capturing energy over specific time windows.

  • Fast (fast): $\tau = 125$ ms. Standard for noise fluctuations.
  • Slow (slow): $\tau = 1000$ ms. Standard for steady noise.
  • Impulse (impulse): 35 ms rise time. For explosive sounds.
from pyoctaveband import time_weighting

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

⚡ Performance: OctaveFilterBank Class

Pre-calculating coefficients saves significant CPU time when processing multiple frames.

from pyoctaveband import OctaveFilterBank

bank = OctaveFilterBank(fs=48000, fraction=3, filter_type='butter')

# Process multiple signals efficiently
for frame in stream:
    spl, freq = bank.filter(frame)

🔍 Filter Usage and Examples

This section provides detailed examples and characteristics for each supported filter architecture.

1. Butterworth (butter)

The Butterworth filter is known for its maximally flat passband. It is the standard choice for acoustic measurements where no ripple is allowed within the frequency bands.

from pyoctaveband import octavefilter
# Default standard measurement
spl, freq = octavefilter(x, fs, filter_type='butter')

2. Chebyshev I (cheby1)

Chebyshev Type I filters provide a steeper roll-off than Butterworth at the expense of ripples in the passband. Useful when high selectivity is needed near the cut-off frequencies.

# Selectivity with 0.1 dB passband ripple
spl, freq = octavefilter(x, fs, filter_type='cheby1', ripple=0.1)

3. Chebyshev II (cheby2)

Also known as Inverse Chebyshev, it has a flat passband and ripples in the stopband. It provides faster roll-off than Butterworth without affecting the signal in the passband.

# Flat passband with 60 dB stopband attenuation
spl, freq = octavefilter(x, fs, filter_type='cheby2', attenuation=60)

4. Elliptic (ellip)

Elliptic (Cauer) filters have the shortest transition width (steepest roll-off) for a given order. They feature ripples in both the passband and stopband.

# Maximum selectivity for extreme band isolation
spl, freq = octavefilter(x, fs, filter_type='ellip', ripple=0.1, attenuation=60)

5. Bessel (bessel)

Bessel filters are optimized for linear phase response and minimal group delay. They preserve the shape of filtered waveforms (transients) better than any other type, but have the slowest roll-off.

# Best for pulse analysis and transient preservation
spl, freq = octavefilter(x, fs, filter_type='bessel')

6. Linkwitz-Riley (lr)

Specifically designed for audio crossovers. Linkwitz-Riley filters (typically 4th order) allow splitting a signal into bands that, when summed, result in a perfectly flat magnitude response and zero phase difference between bands at the crossover.

from pyoctaveband import linkwitz_riley
# Split signal into Low and High bands at 1000 Hz
low, high = linkwitz_riley(signal, fs, freq=1000, order=4)
# Reconstruction: low + high == signal (flat response)


📏 Calibration and dBFS

PyOctaveBand can return results in physical Sound Pressure Level (dB SPL) or digital decibels relative to Full Scale (dBFS).

Physical Calibration (Sound Level Meter)

To get accurate SPL measurements from a digital recording, you must first calculate the sensitivity of your measurement chain using a reference tone (e.g., 94 dB @ 1kHz).

from pyoctaveband import octavefilter, calculate_sensitivity

# 1. Record your 94dB calibrator signal
# ref_signal = ... (your recording)

# 2. Calculate sensitivity factor
sensitivity = calculate_sensitivity(ref_signal, target_spl=94.0)

# 3. Apply calibration to your measurements
spl, freq = octavefilter(signal, fs, calibration_factor=sensitivity)
# Now 'spl' values are in real-world dB SPL!

Digital Analysis (dBFS)

...

RMS vs Peak Levels

PyOctaveBand supports two measurement modes to align with professional software like BK:

  • RMS (mode='rms'): Energy-based level (standard).
  • Peak (mode='peak'): Absolute maximum value reached in the frame (Peak-holding).
# Measure peak-holding levels for impact analysis
spl_peak, freq = octavefilter(signal, fs, mode='peak')

📊 Signal Decomposition and Stability

By setting sigbands=True, you can retrieve the time-domain components of each band. This allows for advanced analysis or comparing how different architectures (e.g., Butterworth vs Chebyshev) affect the signal phase and transient response.

import numpy as np
from pyoctaveband import octavefilter

# 1. Generate a signal (Sum of 250Hz and 1000Hz)
fs = 48000
t = np.linspace(0, 0.5, int(fs * 0.5), endpoint=False)
y = np.sin(2 * np.pi * 250 * t) + np.sin(2 * np.pi * 1000 * t)

# 2. Compare architectures (Butterworth vs Chebyshev II)
# Filter with Butterworth (default)
spl_b, freq, xb_butter = octavefilter(y, fs=fs, fraction=1, sigbands=True, filter_type='butter')

# Filter with Chebyshev II (flat passband, ripples in stopband)
spl_c2, _, xb_cheby2 = octavefilter(y, fs=fs, fraction=1, sigbands=True, filter_type='cheby2')

# 'xb_butter' and 'xb_cheby2' contain the time-domain signals per band

The plot compares the Butterworth (solid blue) and Chebyshev II (dashed red) responses. The bottom plot shows the Impulse Response, highlighting the differences in stability and transient decay.


📖 Theoretical Background

Octave Band Frequencies (ANSI S1.11 / IEC 61260)

The mid-band frequencies ($f_m$) and edges ($f_1, f_2$) use a base-10 ratio $G = 10^{0.3}$:

  • Mid-band: $f_m = 1000 \cdot G^{x/b}$ (for odd $b$)
  • Band edges: $f_1 = f_m \cdot G^{-1/2b}$, $f_2 = f_m \cdot G^{1/2b}$

Magnitude Responses $|H(j\omega)|$

The library implements standard classical filter prototypes:

  1. Butterworth: $|H(j\omega)| = \frac{1}{\sqrt{1 + (\omega/\omega_c)^{2n}}}$ (Maximally flat passband)
  2. Chebyshev I: $|H(j\omega)| = \frac{1}{\sqrt{1 + \epsilon^2 T_n^2(\omega/\omega_c)}}$ (Equiripple in passband, steeper roll-off)
  3. Chebyshev II: Inverse Chebyshev, equiripple in stopband, flat passband.
  4. Elliptic: $|H(j\omega)| = \frac{1}{\sqrt{1 + \epsilon^2 R_n^2(\omega/\omega_c, L)}}$ (Equiripple in both, maximum selectivity)

Filter Bank Design & Numerical Stability

To ensure 100% stability across the entire audible spectrum (even at low frequencies like 16Hz with high sample rates), PyOctaveBand employs two critical strategies:

  1. Second-Order Sections (SOS): All filters are implemented as a series of cascaded biquads. This avoids the catastrophic numerical precision loss associated with high-order transfer functions ($a, b$ coefficients).
  2. Multi-rate Decimation: For low-frequency bands, the signal is automatically downsampled (decimated) before filtering and upsampled afterwards. This keeps the digital pole locations far from the unit circle boundary, preventing oscillation and noise.

Weighting Curves (IEC 61672-1)

The A-weighting transfer function: $$R_A(f) = \frac{12194^2 \cdot f^4}{(f^2 + 20.6^2)\sqrt{(f^2 + 107.7^2)(f^2 + 737.9^2)}(f^2 + 12194^2)}$$ $$A(f) = 20 \log_{10}(R_A(f)) + 2.00$$

Time Integration

Implemented as a first-order IIR exponential integrator: $$y[n] = \alpha \cdot x^2[n] + (1 - \alpha) \cdot y[n-1]$$ $$\alpha = 1 - e^{-1 / (f_s \cdot \tau)}$$ Where $\tau$ is the time constant (e.g., 125ms for Fast).


🧪 Development and Verification

We maintain 100% stability and compliance through a rigorous test suite.

Test Categories

  1. Isolation Tests: Verifies that a pure 1kHz tone is attenuated by >20dB in the 250Hz and 4kHz bands.
  2. Weighting Response: Checks gains at 100Hz (-19.1dB for A) and 1kHz (0dB).
  3. Stability (IR Tail): Analyzes the Impulse Response of every filter. Energy in the last 100ms must be $< 10^{-6}$ to pass.
  4. Crossover Flatness: Verifies that the sum of Linkwitz-Riley bands has $< 0.1$ dB deviation.

Commands

# Run full suite
pytest tests/

# Generate technical report
python scripts/benchmark_filters.py

Author

Jose M. Requena Plens, 2020 - 2026.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyoctaveband-1.0.10.tar.gz (39.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyoctaveband-1.0.10-py3-none-any.whl (30.5 kB view details)

Uploaded Python 3

File details

Details for the file pyoctaveband-1.0.10.tar.gz.

File metadata

  • Download URL: pyoctaveband-1.0.10.tar.gz
  • Upload date:
  • Size: 39.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for pyoctaveband-1.0.10.tar.gz
Algorithm Hash digest
SHA256 f4375ba3296cb66efe90eb7024e16289e60f5ad40774e52b5b510ea99f838ec4
MD5 292de3334d2628598c88f9147236e33f
BLAKE2b-256 5cc751d80359cb1596835e7bb950574e8ea756c508b570d3c500b96928bcd86a

See more details on using hashes here.

File details

Details for the file pyoctaveband-1.0.10-py3-none-any.whl.

File metadata

  • Download URL: pyoctaveband-1.0.10-py3-none-any.whl
  • Upload date:
  • Size: 30.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for pyoctaveband-1.0.10-py3-none-any.whl
Algorithm Hash digest
SHA256 042e090c7ba762830a12a8727d6b849c064d0480658379342cb9f7e757045936
MD5 a1e9e8c50b5dbc35aedc96eb9add10e6
BLAKE2b-256 05184620852d80fbf990581f429620b7651d1bfaa72ebd1e2d045087f3fbed2c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page