PyOctaveBand 1.2.3__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyoctaveband-2.0.0/MANIFEST.in +1 -0
- pyoctaveband-2.0.0/PKG-INFO +121 -0
- pyoctaveband-2.0.0/README.md +90 -0
- pyoctaveband-2.0.0/VERSION +1 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/pyproject.toml +20 -4
- pyoctaveband-2.0.0/src/PyOctaveBand.egg-info/PKG-INFO +121 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/PyOctaveBand.egg-info/SOURCES.txt +14 -1
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/PyOctaveBand.egg-info/requires.txt +8 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/__init__.py +67 -16
- pyoctaveband-2.0.0/src/pyoctaveband/_version.py +17 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/calibration.py +1 -1
- pyoctaveband-2.0.0/src/pyoctaveband/compliance.py +203 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/core.py +111 -9
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/filter_design.py +77 -4
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/frequencies.py +4 -5
- pyoctaveband-2.0.0/src/pyoctaveband/levels.py +119 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/parametric_filters.py +83 -12
- pyoctaveband-2.0.0/src/pyoctaveband/py.typed +0 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/utils.py +19 -10
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_basic.py +45 -0
- pyoctaveband-2.0.0/tests/test_compliance.py +96 -0
- pyoctaveband-2.0.0/tests/test_filter_design.py +171 -0
- pyoctaveband-2.0.0/tests/test_graph_measurements.py +75 -0
- pyoctaveband-2.0.0/tests/test_iec_compliance.py +100 -0
- pyoctaveband-2.0.0/tests/test_iec_weighting_table3.py +94 -0
- pyoctaveband-2.0.0/tests/test_levels.py +123 -0
- pyoctaveband-2.0.0/tests/test_matplotlib_backend.py +88 -0
- pyoctaveband-2.0.0/tests/test_packaging.py +13 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_parametric.py +96 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_parametric_filters.py +157 -1
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_performance.py +5 -1
- pyoctaveband-2.0.0/tests/test_spectrogram.py +63 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_stateful_weighting_filter.py +6 -3
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_utils.py +15 -1
- pyoctaveband-2.0.0/tests/test_zero_phase.py +75 -0
- pyoctaveband-1.2.3/PKG-INFO +0 -628
- pyoctaveband-1.2.3/README.md +0 -609
- pyoctaveband-1.2.3/src/PyOctaveBand.egg-info/PKG-INFO +0 -628
- pyoctaveband-1.2.3/src/pyoctaveband/_version.py +0 -1
- pyoctaveband-1.2.3/tests/test_filter_design.py +0 -71
- pyoctaveband-1.2.3/tests/test_matplotlib_backend.py +0 -38
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/LICENSE +0 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/setup.cfg +0 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/PyOctaveBand.egg-info/dependency_links.txt +0 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/PyOctaveBand.egg-info/top_level.txt +0 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_advanced.py +0 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_audio_processing.py +0 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_errors_and_edge_cases.py +0 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_multichannel.py +0 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_nominal_frequencies.py +0 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_signal_theory_limits.py +0 -0
- {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_stateful_octave_filter_bank.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include VERSION
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyOctaveBand
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Octave-Band and Fractional Octave-Band filter for signals in time domain.
|
|
5
|
+
Author-email: Jose Manuel Requena Plens <jmrplens@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/jmrplens/PyOctaveBand
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/jmrplens/PyOctaveBand/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: numpy>=2.4.4
|
|
22
|
+
Requires-Dist: scipy>=1.17.1
|
|
23
|
+
Provides-Extra: perf
|
|
24
|
+
Requires-Dist: numba>=0.65.1; extra == "perf"
|
|
25
|
+
Provides-Extra: plot
|
|
26
|
+
Requires-Dist: matplotlib>=3.10.9; extra == "plot"
|
|
27
|
+
Provides-Extra: full
|
|
28
|
+
Requires-Dist: numba>=0.65.1; extra == "full"
|
|
29
|
+
Requires-Dist: matplotlib>=3.10.9; extra == "full"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
[](https://www.paypal.com/donate?hosted_button_id=BLP3R6VGYJB4Q)
|
|
33
|
+
[](https://ko-fi.com/jmrplens)
|
|
34
|
+
[](https://pypi.org/project/PyOctaveBand/)
|
|
35
|
+
[](https://pypi.org/project/PyOctaveBand/)
|
|
36
|
+
[](https://pypi.org/project/PyOctaveBand/)
|
|
37
|
+
[](https://github.com/jmrplens/PyOctaveBand/actions/workflows/python-app.yml)
|
|
38
|
+
[](https://sonarcloud.io/summary/overall?id=jmrplens_PyOctaveBand)
|
|
39
|
+
[](https://codecov.io/gh/jmrplens/PyOctaveBand)
|
|
40
|
+
|
|
41
|
+
# PyOctaveBand
|
|
42
|
+
|
|
43
|
+
Advanced Octave-Band and Fractional Octave-Band filter bank for signals in the time domain. Fully compliant with **ANSI S1.11-2004** (Filters) and **IEC 61672-1:2013** (Weighting).
|
|
44
|
+
|
|
45
|
+
<img src="https://raw.githubusercontent.com/jmrplens/PyOctaveBand/main/.github/images/filter_type_comparison.png" alt="Magnitude response comparison of the five filter architectures for the 1 kHz octave band, with a zoom at the -3 dB crossover" width="80%">
|
|
46
|
+
|
|
47
|
+
## ✨ Highlights
|
|
48
|
+
|
|
49
|
+
- 🎛️ 1/1, 1/3 and arbitrary fractional octave filter banks (stable SOS + multirate decimation)
|
|
50
|
+
- 🏗️ Five architectures: Butterworth, Chebyshev I/II, Elliptic, Bessel — all with −3 dB points on the ANSI band edges
|
|
51
|
+
- 🔊 A/C/Z frequency weighting within IEC 61672-1 class 1 tolerances
|
|
52
|
+
- ⏱️ Fast/Slow/Impulse time ballistics, `Leq`, `LAeq` and `L10/L50/L90` statistical levels
|
|
53
|
+
- 🗺️ Octave spectrogram (band levels over time) and zero-phase offline filtering
|
|
54
|
+
- 📏 Physical SPL calibration and dBFS modes
|
|
55
|
+
- ⚡ Vectorized multichannel processing and stateful block (real-time) workflows
|
|
56
|
+
|
|
57
|
+
## 🚀 Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install PyOctaveBand
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Optional extras: `PyOctaveBand[plot]` (matplotlib for response plots), `PyOctaveBand[perf]` (numba for faster impulse ballistics), `PyOctaveBand[full]` (both).
|
|
64
|
+
|
|
65
|
+
## 📚 Documentation
|
|
66
|
+
|
|
67
|
+
**Full documentation website: https://jmrplens.github.io/PyOctaveBand/** (English / Español)
|
|
68
|
+
|
|
69
|
+
Or browse the Markdown docs on GitHub:
|
|
70
|
+
|
|
71
|
+
| Page | Contents |
|
|
72
|
+
| :--- | :--- |
|
|
73
|
+
| [Getting Started](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/getting-started.md) | Installation, first analysis, WAV files |
|
|
74
|
+
| [Filter Banks](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/filter-banks.md) | Architectures, response gallery, band decomposition, zero-phase |
|
|
75
|
+
| [Frequency Weighting](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/weighting.md) | A/C/Z curves, class 1 high-accuracy mode |
|
|
76
|
+
| [Time Weighting](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/time-weighting.md) | Fast/Slow/Impulse ballistics, initial state |
|
|
77
|
+
| [Levels](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/levels.md) | Leq, LAeq, L10/L50/L90, octave spectrogram |
|
|
78
|
+
| [Calibration and dBFS](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/calibration.md) | Physical SPL, digital full-scale, RMS vs peak |
|
|
79
|
+
| [Block Processing](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/block-processing.md) | Stateful streaming workflows |
|
|
80
|
+
| [Multichannel](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/multichannel.md) | Vectorized multichannel analysis, performance |
|
|
81
|
+
| [API Reference](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/api-reference.md) | Every public function and class |
|
|
82
|
+
| [Theory](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/theory.md) | Standards, math, design decisions |
|
|
83
|
+
| [Why PyOctaveBand](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/why-pyoctaveband.md) | IEC compliance verification vs other libraries |
|
|
84
|
+
|
|
85
|
+
## ⚡ Quick start
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import numpy as np
|
|
89
|
+
from pyoctaveband import octavefilter
|
|
90
|
+
|
|
91
|
+
fs = 48000
|
|
92
|
+
t = np.linspace(0, 1, fs, endpoint=False)
|
|
93
|
+
# Composite signal: 100Hz + 1000Hz
|
|
94
|
+
signal = np.sin(2 * np.pi * 100 * t) + np.sin(2 * np.pi * 1000 * t)
|
|
95
|
+
|
|
96
|
+
# Apply 1/3 octave filter bank
|
|
97
|
+
spl, freq = octavefilter(signal, fs=fs, fraction=3)
|
|
98
|
+
|
|
99
|
+
print(f"Bands: {freq}")
|
|
100
|
+
print(f"SPL [dB]: {spl}")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
<img src="https://raw.githubusercontent.com/jmrplens/PyOctaveBand/main/.github/images/signal_response_fraction_3.png" alt="One-third-octave spectrum analysis of a multi-tone signal with the raw PSD in the background" width="80%">
|
|
104
|
+
|
|
105
|
+
*1/3 Octave Band spectrum analysis of a complex signal. More examples in the
|
|
106
|
+
[documentation](https://jmrplens.github.io/PyOctaveBand/).*
|
|
107
|
+
|
|
108
|
+
## 🧪 Development
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
make install # dependencies + editable install
|
|
112
|
+
make check # ruff + mypy + bandit + tests
|
|
113
|
+
make graphs # regenerate documentation images
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
See https://github.com/jmrplens/PyOctaveBand/blob/main/CONTRIBUTING.md and the
|
|
117
|
+
https://github.com/jmrplens/PyOctaveBand/blob/main/CHANGELOG.md
|
|
118
|
+
|
|
119
|
+
## 📄 License
|
|
120
|
+
|
|
121
|
+
[MIT](https://github.com/jmrplens/PyOctaveBand/blob/main/LICENSE)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
[](https://www.paypal.com/donate?hosted_button_id=BLP3R6VGYJB4Q)
|
|
2
|
+
[](https://ko-fi.com/jmrplens)
|
|
3
|
+
[](https://pypi.org/project/PyOctaveBand/)
|
|
4
|
+
[](https://pypi.org/project/PyOctaveBand/)
|
|
5
|
+
[](https://pypi.org/project/PyOctaveBand/)
|
|
6
|
+
[](https://github.com/jmrplens/PyOctaveBand/actions/workflows/python-app.yml)
|
|
7
|
+
[](https://sonarcloud.io/summary/overall?id=jmrplens_PyOctaveBand)
|
|
8
|
+
[](https://codecov.io/gh/jmrplens/PyOctaveBand)
|
|
9
|
+
|
|
10
|
+
# PyOctaveBand
|
|
11
|
+
|
|
12
|
+
Advanced Octave-Band and Fractional Octave-Band filter bank for signals in the time domain. Fully compliant with **ANSI S1.11-2004** (Filters) and **IEC 61672-1:2013** (Weighting).
|
|
13
|
+
|
|
14
|
+
<img src="https://raw.githubusercontent.com/jmrplens/PyOctaveBand/main/.github/images/filter_type_comparison.png" alt="Magnitude response comparison of the five filter architectures for the 1 kHz octave band, with a zoom at the -3 dB crossover" width="80%">
|
|
15
|
+
|
|
16
|
+
## ✨ Highlights
|
|
17
|
+
|
|
18
|
+
- 🎛️ 1/1, 1/3 and arbitrary fractional octave filter banks (stable SOS + multirate decimation)
|
|
19
|
+
- 🏗️ Five architectures: Butterworth, Chebyshev I/II, Elliptic, Bessel — all with −3 dB points on the ANSI band edges
|
|
20
|
+
- 🔊 A/C/Z frequency weighting within IEC 61672-1 class 1 tolerances
|
|
21
|
+
- ⏱️ Fast/Slow/Impulse time ballistics, `Leq`, `LAeq` and `L10/L50/L90` statistical levels
|
|
22
|
+
- 🗺️ Octave spectrogram (band levels over time) and zero-phase offline filtering
|
|
23
|
+
- 📏 Physical SPL calibration and dBFS modes
|
|
24
|
+
- ⚡ Vectorized multichannel processing and stateful block (real-time) workflows
|
|
25
|
+
|
|
26
|
+
## 🚀 Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install PyOctaveBand
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Optional extras: `PyOctaveBand[plot]` (matplotlib for response plots), `PyOctaveBand[perf]` (numba for faster impulse ballistics), `PyOctaveBand[full]` (both).
|
|
33
|
+
|
|
34
|
+
## 📚 Documentation
|
|
35
|
+
|
|
36
|
+
**Full documentation website: https://jmrplens.github.io/PyOctaveBand/** (English / Español)
|
|
37
|
+
|
|
38
|
+
Or browse the Markdown docs on GitHub:
|
|
39
|
+
|
|
40
|
+
| Page | Contents |
|
|
41
|
+
| :--- | :--- |
|
|
42
|
+
| [Getting Started](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/getting-started.md) | Installation, first analysis, WAV files |
|
|
43
|
+
| [Filter Banks](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/filter-banks.md) | Architectures, response gallery, band decomposition, zero-phase |
|
|
44
|
+
| [Frequency Weighting](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/weighting.md) | A/C/Z curves, class 1 high-accuracy mode |
|
|
45
|
+
| [Time Weighting](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/time-weighting.md) | Fast/Slow/Impulse ballistics, initial state |
|
|
46
|
+
| [Levels](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/levels.md) | Leq, LAeq, L10/L50/L90, octave spectrogram |
|
|
47
|
+
| [Calibration and dBFS](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/calibration.md) | Physical SPL, digital full-scale, RMS vs peak |
|
|
48
|
+
| [Block Processing](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/block-processing.md) | Stateful streaming workflows |
|
|
49
|
+
| [Multichannel](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/multichannel.md) | Vectorized multichannel analysis, performance |
|
|
50
|
+
| [API Reference](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/api-reference.md) | Every public function and class |
|
|
51
|
+
| [Theory](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/theory.md) | Standards, math, design decisions |
|
|
52
|
+
| [Why PyOctaveBand](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/why-pyoctaveband.md) | IEC compliance verification vs other libraries |
|
|
53
|
+
|
|
54
|
+
## ⚡ Quick start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import numpy as np
|
|
58
|
+
from pyoctaveband import octavefilter
|
|
59
|
+
|
|
60
|
+
fs = 48000
|
|
61
|
+
t = np.linspace(0, 1, fs, endpoint=False)
|
|
62
|
+
# Composite signal: 100Hz + 1000Hz
|
|
63
|
+
signal = np.sin(2 * np.pi * 100 * t) + np.sin(2 * np.pi * 1000 * t)
|
|
64
|
+
|
|
65
|
+
# Apply 1/3 octave filter bank
|
|
66
|
+
spl, freq = octavefilter(signal, fs=fs, fraction=3)
|
|
67
|
+
|
|
68
|
+
print(f"Bands: {freq}")
|
|
69
|
+
print(f"SPL [dB]: {spl}")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
<img src="https://raw.githubusercontent.com/jmrplens/PyOctaveBand/main/.github/images/signal_response_fraction_3.png" alt="One-third-octave spectrum analysis of a multi-tone signal with the raw PSD in the background" width="80%">
|
|
73
|
+
|
|
74
|
+
*1/3 Octave Band spectrum analysis of a complex signal. More examples in the
|
|
75
|
+
[documentation](https://jmrplens.github.io/PyOctaveBand/).*
|
|
76
|
+
|
|
77
|
+
## 🧪 Development
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
make install # dependencies + editable install
|
|
81
|
+
make check # ruff + mypy + bandit + tests
|
|
82
|
+
make graphs # regenerate documentation images
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
See https://github.com/jmrplens/PyOctaveBand/blob/main/CONTRIBUTING.md and the
|
|
86
|
+
https://github.com/jmrplens/PyOctaveBand/blob/main/CHANGELOG.md
|
|
87
|
+
|
|
88
|
+
## 📄 License
|
|
89
|
+
|
|
90
|
+
[MIT](https://github.com/jmrplens/PyOctaveBand/blob/main/LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.0.0
|
|
@@ -13,16 +13,26 @@ readme = "README.md"
|
|
|
13
13
|
requires-python = ">=3.11"
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
16
19
|
"License :: OSI Approved :: MIT License",
|
|
17
20
|
"Operating System :: OS Independent",
|
|
21
|
+
"Intended Audience :: Science/Research",
|
|
22
|
+
"Topic :: Multimedia :: Sound/Audio :: Analysis",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
24
|
+
"Typing :: Typed",
|
|
18
25
|
]
|
|
19
26
|
dependencies = [
|
|
20
27
|
"numpy>=2.4.4",
|
|
21
28
|
"scipy>=1.17.1",
|
|
22
|
-
"matplotlib>=3.10.9",
|
|
23
|
-
"numba>=0.65.1",
|
|
24
29
|
]
|
|
25
30
|
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
perf = ["numba>=0.65.1"]
|
|
33
|
+
plot = ["matplotlib>=3.10.9"]
|
|
34
|
+
full = ["numba>=0.65.1", "matplotlib>=3.10.9"]
|
|
35
|
+
|
|
26
36
|
[project.urls]
|
|
27
37
|
"Homepage" = "https://github.com/jmrplens/PyOctaveBand"
|
|
28
38
|
"Bug Tracker" = "https://github.com/jmrplens/PyOctaveBand/issues"
|
|
@@ -30,11 +40,17 @@ dependencies = [
|
|
|
30
40
|
[tool.setuptools.packages.find]
|
|
31
41
|
where = ["src"]
|
|
32
42
|
|
|
43
|
+
[tool.setuptools.package-data]
|
|
44
|
+
pyoctaveband = ["py.typed"]
|
|
45
|
+
|
|
33
46
|
[tool.setuptools.dynamic]
|
|
34
|
-
version = {
|
|
47
|
+
version = {file = "VERSION"}
|
|
35
48
|
|
|
36
49
|
[tool.mypy]
|
|
37
|
-
|
|
50
|
+
# 3.12: numpy >= 2.5 ships PEP 695 `type` statements in its stubs, which mypy
|
|
51
|
+
# rejects under a 3.11 target. Runtime support stays at >= 3.11 (3.11 users
|
|
52
|
+
# resolve numpy 2.4.x, which still supports it).
|
|
53
|
+
python_version = "3.12"
|
|
38
54
|
strict = true
|
|
39
55
|
ignore_missing_imports = true
|
|
40
56
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyOctaveBand
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Octave-Band and Fractional Octave-Band filter for signals in time domain.
|
|
5
|
+
Author-email: Jose Manuel Requena Plens <jmrplens@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/jmrplens/PyOctaveBand
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/jmrplens/PyOctaveBand/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: numpy>=2.4.4
|
|
22
|
+
Requires-Dist: scipy>=1.17.1
|
|
23
|
+
Provides-Extra: perf
|
|
24
|
+
Requires-Dist: numba>=0.65.1; extra == "perf"
|
|
25
|
+
Provides-Extra: plot
|
|
26
|
+
Requires-Dist: matplotlib>=3.10.9; extra == "plot"
|
|
27
|
+
Provides-Extra: full
|
|
28
|
+
Requires-Dist: numba>=0.65.1; extra == "full"
|
|
29
|
+
Requires-Dist: matplotlib>=3.10.9; extra == "full"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
[](https://www.paypal.com/donate?hosted_button_id=BLP3R6VGYJB4Q)
|
|
33
|
+
[](https://ko-fi.com/jmrplens)
|
|
34
|
+
[](https://pypi.org/project/PyOctaveBand/)
|
|
35
|
+
[](https://pypi.org/project/PyOctaveBand/)
|
|
36
|
+
[](https://pypi.org/project/PyOctaveBand/)
|
|
37
|
+
[](https://github.com/jmrplens/PyOctaveBand/actions/workflows/python-app.yml)
|
|
38
|
+
[](https://sonarcloud.io/summary/overall?id=jmrplens_PyOctaveBand)
|
|
39
|
+
[](https://codecov.io/gh/jmrplens/PyOctaveBand)
|
|
40
|
+
|
|
41
|
+
# PyOctaveBand
|
|
42
|
+
|
|
43
|
+
Advanced Octave-Band and Fractional Octave-Band filter bank for signals in the time domain. Fully compliant with **ANSI S1.11-2004** (Filters) and **IEC 61672-1:2013** (Weighting).
|
|
44
|
+
|
|
45
|
+
<img src="https://raw.githubusercontent.com/jmrplens/PyOctaveBand/main/.github/images/filter_type_comparison.png" alt="Magnitude response comparison of the five filter architectures for the 1 kHz octave band, with a zoom at the -3 dB crossover" width="80%">
|
|
46
|
+
|
|
47
|
+
## ✨ Highlights
|
|
48
|
+
|
|
49
|
+
- 🎛️ 1/1, 1/3 and arbitrary fractional octave filter banks (stable SOS + multirate decimation)
|
|
50
|
+
- 🏗️ Five architectures: Butterworth, Chebyshev I/II, Elliptic, Bessel — all with −3 dB points on the ANSI band edges
|
|
51
|
+
- 🔊 A/C/Z frequency weighting within IEC 61672-1 class 1 tolerances
|
|
52
|
+
- ⏱️ Fast/Slow/Impulse time ballistics, `Leq`, `LAeq` and `L10/L50/L90` statistical levels
|
|
53
|
+
- 🗺️ Octave spectrogram (band levels over time) and zero-phase offline filtering
|
|
54
|
+
- 📏 Physical SPL calibration and dBFS modes
|
|
55
|
+
- ⚡ Vectorized multichannel processing and stateful block (real-time) workflows
|
|
56
|
+
|
|
57
|
+
## 🚀 Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install PyOctaveBand
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Optional extras: `PyOctaveBand[plot]` (matplotlib for response plots), `PyOctaveBand[perf]` (numba for faster impulse ballistics), `PyOctaveBand[full]` (both).
|
|
64
|
+
|
|
65
|
+
## 📚 Documentation
|
|
66
|
+
|
|
67
|
+
**Full documentation website: https://jmrplens.github.io/PyOctaveBand/** (English / Español)
|
|
68
|
+
|
|
69
|
+
Or browse the Markdown docs on GitHub:
|
|
70
|
+
|
|
71
|
+
| Page | Contents |
|
|
72
|
+
| :--- | :--- |
|
|
73
|
+
| [Getting Started](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/getting-started.md) | Installation, first analysis, WAV files |
|
|
74
|
+
| [Filter Banks](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/filter-banks.md) | Architectures, response gallery, band decomposition, zero-phase |
|
|
75
|
+
| [Frequency Weighting](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/weighting.md) | A/C/Z curves, class 1 high-accuracy mode |
|
|
76
|
+
| [Time Weighting](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/time-weighting.md) | Fast/Slow/Impulse ballistics, initial state |
|
|
77
|
+
| [Levels](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/levels.md) | Leq, LAeq, L10/L50/L90, octave spectrogram |
|
|
78
|
+
| [Calibration and dBFS](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/calibration.md) | Physical SPL, digital full-scale, RMS vs peak |
|
|
79
|
+
| [Block Processing](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/block-processing.md) | Stateful streaming workflows |
|
|
80
|
+
| [Multichannel](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/multichannel.md) | Vectorized multichannel analysis, performance |
|
|
81
|
+
| [API Reference](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/api-reference.md) | Every public function and class |
|
|
82
|
+
| [Theory](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/theory.md) | Standards, math, design decisions |
|
|
83
|
+
| [Why PyOctaveBand](https://github.com/jmrplens/PyOctaveBand/blob/main/docs/why-pyoctaveband.md) | IEC compliance verification vs other libraries |
|
|
84
|
+
|
|
85
|
+
## ⚡ Quick start
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import numpy as np
|
|
89
|
+
from pyoctaveband import octavefilter
|
|
90
|
+
|
|
91
|
+
fs = 48000
|
|
92
|
+
t = np.linspace(0, 1, fs, endpoint=False)
|
|
93
|
+
# Composite signal: 100Hz + 1000Hz
|
|
94
|
+
signal = np.sin(2 * np.pi * 100 * t) + np.sin(2 * np.pi * 1000 * t)
|
|
95
|
+
|
|
96
|
+
# Apply 1/3 octave filter bank
|
|
97
|
+
spl, freq = octavefilter(signal, fs=fs, fraction=3)
|
|
98
|
+
|
|
99
|
+
print(f"Bands: {freq}")
|
|
100
|
+
print(f"SPL [dB]: {spl}")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
<img src="https://raw.githubusercontent.com/jmrplens/PyOctaveBand/main/.github/images/signal_response_fraction_3.png" alt="One-third-octave spectrum analysis of a multi-tone signal with the raw PSD in the background" width="80%">
|
|
104
|
+
|
|
105
|
+
*1/3 Octave Band spectrum analysis of a complex signal. More examples in the
|
|
106
|
+
[documentation](https://jmrplens.github.io/PyOctaveBand/).*
|
|
107
|
+
|
|
108
|
+
## 🧪 Development
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
make install # dependencies + editable install
|
|
112
|
+
make check # ruff + mypy + bandit + tests
|
|
113
|
+
make graphs # regenerate documentation images
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
See https://github.com/jmrplens/PyOctaveBand/blob/main/CONTRIBUTING.md and the
|
|
117
|
+
https://github.com/jmrplens/PyOctaveBand/blob/main/CHANGELOG.md
|
|
118
|
+
|
|
119
|
+
## 📄 License
|
|
120
|
+
|
|
121
|
+
[MIT](https://github.com/jmrplens/PyOctaveBand/blob/main/LICENSE)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
2
3
|
README.md
|
|
4
|
+
VERSION
|
|
3
5
|
pyproject.toml
|
|
4
6
|
src/PyOctaveBand.egg-info/PKG-INFO
|
|
5
7
|
src/PyOctaveBand.egg-info/SOURCES.txt
|
|
@@ -9,23 +11,34 @@ src/PyOctaveBand.egg-info/top_level.txt
|
|
|
9
11
|
src/pyoctaveband/__init__.py
|
|
10
12
|
src/pyoctaveband/_version.py
|
|
11
13
|
src/pyoctaveband/calibration.py
|
|
14
|
+
src/pyoctaveband/compliance.py
|
|
12
15
|
src/pyoctaveband/core.py
|
|
13
16
|
src/pyoctaveband/filter_design.py
|
|
14
17
|
src/pyoctaveband/frequencies.py
|
|
18
|
+
src/pyoctaveband/levels.py
|
|
15
19
|
src/pyoctaveband/parametric_filters.py
|
|
20
|
+
src/pyoctaveband/py.typed
|
|
16
21
|
src/pyoctaveband/utils.py
|
|
17
22
|
tests/test_advanced.py
|
|
18
23
|
tests/test_audio_processing.py
|
|
19
24
|
tests/test_basic.py
|
|
25
|
+
tests/test_compliance.py
|
|
20
26
|
tests/test_errors_and_edge_cases.py
|
|
21
27
|
tests/test_filter_design.py
|
|
28
|
+
tests/test_graph_measurements.py
|
|
29
|
+
tests/test_iec_compliance.py
|
|
30
|
+
tests/test_iec_weighting_table3.py
|
|
31
|
+
tests/test_levels.py
|
|
22
32
|
tests/test_matplotlib_backend.py
|
|
23
33
|
tests/test_multichannel.py
|
|
24
34
|
tests/test_nominal_frequencies.py
|
|
35
|
+
tests/test_packaging.py
|
|
25
36
|
tests/test_parametric.py
|
|
26
37
|
tests/test_parametric_filters.py
|
|
27
38
|
tests/test_performance.py
|
|
28
39
|
tests/test_signal_theory_limits.py
|
|
40
|
+
tests/test_spectrogram.py
|
|
29
41
|
tests/test_stateful_octave_filter_bank.py
|
|
30
42
|
tests/test_stateful_weighting_filter.py
|
|
31
|
-
tests/test_utils.py
|
|
43
|
+
tests/test_utils.py
|
|
44
|
+
tests/test_zero_phase.py
|
|
@@ -6,14 +6,23 @@ Implementation according to ANSI s1.11-2004 and IEC 61260-1-2014.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
from functools import lru_cache
|
|
9
10
|
from typing import List, Tuple, overload, Literal
|
|
10
11
|
|
|
11
12
|
import numpy as np
|
|
12
13
|
|
|
13
14
|
from .calibration import calculate_sensitivity
|
|
15
|
+
from .compliance import verify_filter_class
|
|
14
16
|
from .core import OctaveFilterBank
|
|
15
17
|
from .frequencies import getansifrequencies, normalizedfreq
|
|
16
|
-
from .
|
|
18
|
+
from .levels import laeq, leq, ln_levels
|
|
19
|
+
from .parametric_filters import (
|
|
20
|
+
TimeWeighting,
|
|
21
|
+
WeightingFilter,
|
|
22
|
+
linkwitz_riley,
|
|
23
|
+
time_weighting,
|
|
24
|
+
weighting_filter,
|
|
25
|
+
)
|
|
17
26
|
from ._version import __version__
|
|
18
27
|
|
|
19
28
|
# Public methods
|
|
@@ -26,11 +35,42 @@ __all__ = [
|
|
|
26
35
|
"WeightingFilter",
|
|
27
36
|
"weighting_filter",
|
|
28
37
|
"time_weighting",
|
|
38
|
+
"TimeWeighting",
|
|
29
39
|
"linkwitz_riley",
|
|
30
40
|
"calculate_sensitivity",
|
|
41
|
+
"leq",
|
|
42
|
+
"laeq",
|
|
43
|
+
"ln_levels",
|
|
44
|
+
"verify_filter_class",
|
|
31
45
|
]
|
|
32
46
|
|
|
33
47
|
|
|
48
|
+
@lru_cache(maxsize=32)
|
|
49
|
+
def _cached_filter_bank(
|
|
50
|
+
fs: int,
|
|
51
|
+
fraction: float,
|
|
52
|
+
order: int,
|
|
53
|
+
limits: Tuple[float, ...] | None,
|
|
54
|
+
filter_type: str,
|
|
55
|
+
ripple: float,
|
|
56
|
+
attenuation: float,
|
|
57
|
+
calibration_factor: float,
|
|
58
|
+
dbfs: bool,
|
|
59
|
+
) -> OctaveFilterBank:
|
|
60
|
+
"""Design (or reuse) a stateless filter bank for octavefilter()."""
|
|
61
|
+
return OctaveFilterBank(
|
|
62
|
+
fs=fs,
|
|
63
|
+
fraction=fraction,
|
|
64
|
+
order=order,
|
|
65
|
+
limits=list(limits) if limits is not None else None,
|
|
66
|
+
filter_type=filter_type,
|
|
67
|
+
ripple=ripple,
|
|
68
|
+
attenuation=attenuation,
|
|
69
|
+
calibration_factor=calibration_factor,
|
|
70
|
+
dbfs=dbfs,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
34
74
|
@overload
|
|
35
75
|
def octavefilter(
|
|
36
76
|
x: List[float] | np.ndarray, # NOSONAR - public API
|
|
@@ -174,19 +214,30 @@ def octavefilter(
|
|
|
174
214
|
Tuple[np.ndarray, List[str], List[np.ndarray]]]
|
|
175
215
|
"""
|
|
176
216
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
217
|
+
if show or plot_file:
|
|
218
|
+
# Plotting has side effects: bypass the cache.
|
|
219
|
+
filter_bank = OctaveFilterBank(
|
|
220
|
+
fs=fs,
|
|
221
|
+
fraction=fraction,
|
|
222
|
+
order=order,
|
|
223
|
+
limits=limits,
|
|
224
|
+
filter_type=filter_type,
|
|
225
|
+
ripple=ripple,
|
|
226
|
+
attenuation=attenuation,
|
|
227
|
+
show=show,
|
|
228
|
+
plot_file=plot_file,
|
|
229
|
+
calibration_factor=calibration_factor,
|
|
230
|
+
dbfs=dbfs,
|
|
231
|
+
)
|
|
232
|
+
else:
|
|
233
|
+
# The bank is immutable in non-stateful mode: reuse the design.
|
|
234
|
+
# Pass limits through as-is (tuple for hashability); the bank
|
|
235
|
+
# constructor is the single place that validates them and owns
|
|
236
|
+
# the default when None.
|
|
237
|
+
limits_key = tuple(map(float, limits)) if limits is not None else None
|
|
238
|
+
filter_bank = _cached_filter_bank(
|
|
239
|
+
fs, fraction, order, limits_key, filter_type,
|
|
240
|
+
ripple, attenuation, calibration_factor, dbfs,
|
|
241
|
+
)
|
|
242
|
+
|
|
192
243
|
return filter_bank.filter(x, sigbands=sigbands, mode=mode, detrend=detrend, nominal=nominal) # type: ignore[call-overload,no-any-return]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copyright (c) 2026. Jose M. Requena-Plens
|
|
2
|
+
"""Package version.
|
|
3
|
+
|
|
4
|
+
The canonical version lives in the repository-root ``VERSION`` file (the
|
|
5
|
+
build backend reads it via ``[tool.setuptools.dynamic]``). Installed
|
|
6
|
+
packages resolve it from their metadata; running from a source tree falls
|
|
7
|
+
back to reading the file directly.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
__version__ = version("pyoctaveband")
|
|
14
|
+
except PackageNotFoundError: # pragma: no cover - source tree without install
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
__version__ = (Path(__file__).resolve().parents[2] / "VERSION").read_text().strip()
|
|
@@ -24,7 +24,7 @@ def calculate_sensitivity(
|
|
|
24
24
|
:param ref_pressure: Reference pressure (default 20 microPascals).
|
|
25
25
|
:return: Calibration factor (sensitivity multiplier).
|
|
26
26
|
"""
|
|
27
|
-
rms_ref = np.sqrt(np.mean(np.
|
|
27
|
+
rms_ref = np.sqrt(np.mean(np.asarray(ref_signal, dtype=np.float64) ** 2))
|
|
28
28
|
if rms_ref == 0:
|
|
29
29
|
raise ValueError("Reference signal is silent, cannot calibrate.")
|
|
30
30
|
|