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.
Files changed (52) hide show
  1. pyoctaveband-2.0.0/MANIFEST.in +1 -0
  2. pyoctaveband-2.0.0/PKG-INFO +121 -0
  3. pyoctaveband-2.0.0/README.md +90 -0
  4. pyoctaveband-2.0.0/VERSION +1 -0
  5. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/pyproject.toml +20 -4
  6. pyoctaveband-2.0.0/src/PyOctaveBand.egg-info/PKG-INFO +121 -0
  7. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/PyOctaveBand.egg-info/SOURCES.txt +14 -1
  8. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/PyOctaveBand.egg-info/requires.txt +8 -0
  9. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/__init__.py +67 -16
  10. pyoctaveband-2.0.0/src/pyoctaveband/_version.py +17 -0
  11. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/calibration.py +1 -1
  12. pyoctaveband-2.0.0/src/pyoctaveband/compliance.py +203 -0
  13. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/core.py +111 -9
  14. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/filter_design.py +77 -4
  15. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/frequencies.py +4 -5
  16. pyoctaveband-2.0.0/src/pyoctaveband/levels.py +119 -0
  17. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/parametric_filters.py +83 -12
  18. pyoctaveband-2.0.0/src/pyoctaveband/py.typed +0 -0
  19. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/pyoctaveband/utils.py +19 -10
  20. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_basic.py +45 -0
  21. pyoctaveband-2.0.0/tests/test_compliance.py +96 -0
  22. pyoctaveband-2.0.0/tests/test_filter_design.py +171 -0
  23. pyoctaveband-2.0.0/tests/test_graph_measurements.py +75 -0
  24. pyoctaveband-2.0.0/tests/test_iec_compliance.py +100 -0
  25. pyoctaveband-2.0.0/tests/test_iec_weighting_table3.py +94 -0
  26. pyoctaveband-2.0.0/tests/test_levels.py +123 -0
  27. pyoctaveband-2.0.0/tests/test_matplotlib_backend.py +88 -0
  28. pyoctaveband-2.0.0/tests/test_packaging.py +13 -0
  29. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_parametric.py +96 -0
  30. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_parametric_filters.py +157 -1
  31. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_performance.py +5 -1
  32. pyoctaveband-2.0.0/tests/test_spectrogram.py +63 -0
  33. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_stateful_weighting_filter.py +6 -3
  34. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_utils.py +15 -1
  35. pyoctaveband-2.0.0/tests/test_zero_phase.py +75 -0
  36. pyoctaveband-1.2.3/PKG-INFO +0 -628
  37. pyoctaveband-1.2.3/README.md +0 -609
  38. pyoctaveband-1.2.3/src/PyOctaveBand.egg-info/PKG-INFO +0 -628
  39. pyoctaveband-1.2.3/src/pyoctaveband/_version.py +0 -1
  40. pyoctaveband-1.2.3/tests/test_filter_design.py +0 -71
  41. pyoctaveband-1.2.3/tests/test_matplotlib_backend.py +0 -38
  42. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/LICENSE +0 -0
  43. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/setup.cfg +0 -0
  44. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/PyOctaveBand.egg-info/dependency_links.txt +0 -0
  45. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/src/PyOctaveBand.egg-info/top_level.txt +0 -0
  46. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_advanced.py +0 -0
  47. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_audio_processing.py +0 -0
  48. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_errors_and_edge_cases.py +0 -0
  49. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_multichannel.py +0 -0
  50. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_nominal_frequencies.py +0 -0
  51. {pyoctaveband-1.2.3 → pyoctaveband-2.0.0}/tests/test_signal_theory_limits.py +0 -0
  52. {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
+ [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate?hosted_button_id=BLP3R6VGYJB4Q)
33
+ [![Donate](https://img.shields.io/badge/Donate-Ko--fi-brightgreen?color=ff5f5f)](https://ko-fi.com/jmrplens)
34
+ [![PyPI version](https://img.shields.io/pypi/v/PyOctaveBand)](https://pypi.org/project/PyOctaveBand/)
35
+ [![PyPI downloads](https://img.shields.io/pypi/dm/PyOctaveBand)](https://pypi.org/project/PyOctaveBand/)
36
+ [![Python versions](https://img.shields.io/pypi/pyversions/PyOctaveBand)](https://pypi.org/project/PyOctaveBand/)
37
+ [![Python application](https://github.com/jmrplens/PyOctaveBand/actions/workflows/python-app.yml/badge.svg)](https://github.com/jmrplens/PyOctaveBand/actions/workflows/python-app.yml)
38
+ [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=jmrplens_PyOctaveBand&metric=alert_status)](https://sonarcloud.io/summary/overall?id=jmrplens_PyOctaveBand)
39
+ [![codecov](https://codecov.io/gh/jmrplens/PyOctaveBand/branch/main/graph/badge.svg)](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
+ [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate?hosted_button_id=BLP3R6VGYJB4Q)
2
+ [![Donate](https://img.shields.io/badge/Donate-Ko--fi-brightgreen?color=ff5f5f)](https://ko-fi.com/jmrplens)
3
+ [![PyPI version](https://img.shields.io/pypi/v/PyOctaveBand)](https://pypi.org/project/PyOctaveBand/)
4
+ [![PyPI downloads](https://img.shields.io/pypi/dm/PyOctaveBand)](https://pypi.org/project/PyOctaveBand/)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/PyOctaveBand)](https://pypi.org/project/PyOctaveBand/)
6
+ [![Python application](https://github.com/jmrplens/PyOctaveBand/actions/workflows/python-app.yml/badge.svg)](https://github.com/jmrplens/PyOctaveBand/actions/workflows/python-app.yml)
7
+ [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=jmrplens_PyOctaveBand&metric=alert_status)](https://sonarcloud.io/summary/overall?id=jmrplens_PyOctaveBand)
8
+ [![codecov](https://codecov.io/gh/jmrplens/PyOctaveBand/branch/main/graph/badge.svg)](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 = {attr = "pyoctaveband._version.__version__"}
47
+ version = {file = "VERSION"}
35
48
 
36
49
  [tool.mypy]
37
- python_version = "3.11"
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
+ [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate?hosted_button_id=BLP3R6VGYJB4Q)
33
+ [![Donate](https://img.shields.io/badge/Donate-Ko--fi-brightgreen?color=ff5f5f)](https://ko-fi.com/jmrplens)
34
+ [![PyPI version](https://img.shields.io/pypi/v/PyOctaveBand)](https://pypi.org/project/PyOctaveBand/)
35
+ [![PyPI downloads](https://img.shields.io/pypi/dm/PyOctaveBand)](https://pypi.org/project/PyOctaveBand/)
36
+ [![Python versions](https://img.shields.io/pypi/pyversions/PyOctaveBand)](https://pypi.org/project/PyOctaveBand/)
37
+ [![Python application](https://github.com/jmrplens/PyOctaveBand/actions/workflows/python-app.yml/badge.svg)](https://github.com/jmrplens/PyOctaveBand/actions/workflows/python-app.yml)
38
+ [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=jmrplens_PyOctaveBand&metric=alert_status)](https://sonarcloud.io/summary/overall?id=jmrplens_PyOctaveBand)
39
+ [![codecov](https://codecov.io/gh/jmrplens/PyOctaveBand/branch/main/graph/badge.svg)](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
@@ -1,4 +1,12 @@
1
1
  numpy>=2.4.4
2
2
  scipy>=1.17.1
3
+
4
+ [full]
5
+ numba>=0.65.1
3
6
  matplotlib>=3.10.9
7
+
8
+ [perf]
4
9
  numba>=0.65.1
10
+
11
+ [plot]
12
+ matplotlib>=3.10.9
@@ -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 .parametric_filters import WeightingFilter, linkwitz_riley, time_weighting, weighting_filter
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
- # Use the class-based implementation
178
- filter_bank = OctaveFilterBank(
179
- fs=fs,
180
- fraction=fraction,
181
- order=order,
182
- limits=limits,
183
- filter_type=filter_type,
184
- ripple=ripple,
185
- attenuation=attenuation,
186
- show=show,
187
- plot_file=plot_file,
188
- calibration_factor=calibration_factor,
189
- dbfs=dbfs
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.array(ref_signal)**2))
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