speech-selib 0.1.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 (32) hide show
  1. speech_selib-0.1.0/LICENSE +21 -0
  2. speech_selib-0.1.0/PKG-INFO +133 -0
  3. speech_selib-0.1.0/README.md +102 -0
  4. speech_selib-0.1.0/pyproject.toml +38 -0
  5. speech_selib-0.1.0/selib/__init__.py +7 -0
  6. speech_selib-0.1.0/selib/metrics/__init__.py +31 -0
  7. speech_selib-0.1.0/selib/metrics/basic.py +245 -0
  8. speech_selib-0.1.0/selib/metrics/dnsmos.py +151 -0
  9. speech_selib-0.1.0/selib/models/__init__.py +11 -0
  10. speech_selib-0.1.0/selib/models/core.py +153 -0
  11. speech_selib-0.1.0/selib/models/deepfilter/__init__.py +11 -0
  12. speech_selib-0.1.0/selib/models/deepfilter/features.py +309 -0
  13. speech_selib-0.1.0/selib/models/deepfilter/ops.py +190 -0
  14. speech_selib-0.1.0/selib/models/deepfilter/runtime.py +253 -0
  15. speech_selib-0.1.0/selib/models/factory.py +165 -0
  16. speech_selib-0.1.0/selib/models/se_models.py +185 -0
  17. speech_selib-0.1.0/selib/urls.py +29 -0
  18. speech_selib-0.1.0/selib/utils/__init__.py +8 -0
  19. speech_selib-0.1.0/selib/utils/audio.py +200 -0
  20. speech_selib-0.1.0/selib/utils/noise.py +161 -0
  21. speech_selib-0.1.0/setup.cfg +4 -0
  22. speech_selib-0.1.0/setup.py +57 -0
  23. speech_selib-0.1.0/speech_selib.egg-info/PKG-INFO +133 -0
  24. speech_selib-0.1.0/speech_selib.egg-info/SOURCES.txt +30 -0
  25. speech_selib-0.1.0/speech_selib.egg-info/dependency_links.txt +1 -0
  26. speech_selib-0.1.0/speech_selib.egg-info/requires.txt +8 -0
  27. speech_selib-0.1.0/speech_selib.egg-info/top_level.txt +4 -0
  28. speech_selib-0.1.0/tmp/profile_onnx.py +28 -0
  29. speech_selib-0.1.0/tmp/test.py +149 -0
  30. speech_selib-0.1.0/tmp/test_df.py +25 -0
  31. speech_selib-0.1.0/tmp/test_df_ref.py +36 -0
  32. speech_selib-0.1.0/tmp/test_dnsmos.py +37 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 github.com/nipponjo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: speech-selib
3
+ Version: 0.1.0
4
+ Summary: A simple speech enhancement library using ONNX models
5
+ Home-page: https://github.com/nipponjo/selib
6
+ Author: nipponjo
7
+ Author-email: nipponjo <nipponjo.git@gmail.com>
8
+ Project-URL: Homepage, https://github.com/nipponjo/selib
9
+ Classifier: Programming Language :: Python
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Telecommunications Industry
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Multimedia
17
+ Classifier: Topic :: Multimedia :: Sound/Audio
18
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.8.0
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: numpy
24
+ Requires-Dist: librosa
25
+ Requires-Dist: onnxruntime-gpu; sys_platform != "darwin"
26
+ Requires-Dist: onnxruntime; sys_platform == "darwin"
27
+ Dynamic: author
28
+ Dynamic: home-page
29
+ Dynamic: license-file
30
+ Dynamic: requires-python
31
+
32
+ # SeLib
33
+
34
+ SeLib is a small speech-enhancement library for ONNX models. It provides a
35
+ simple `enhance()` function, lightweight model wrappers, and utility functions for audio/noise experiments.
36
+
37
+ ## Install
38
+
39
+ Install the package directly from GitHub while the project is still small and moving quickly.
40
+
41
+ ```bash
42
+ # Install SeLib and its Python dependencies.
43
+ pip install git+https://github.com/nipponjo/selib.git
44
+ ```
45
+
46
+ ## Basic Use
47
+
48
+ The shortest path is `selib.enhance(wave, model_id)`. Pass a mono NumPy waveform at the sample rate expected by the model.
49
+ ```python
50
+ import librosa
51
+ import selib
52
+
53
+ from selib.utils import NoiseGenerator, snr_mixer
54
+
55
+ # Load clean speech at the model sample rate.
56
+ wave_clean, sr = librosa.load("./data/clean_freesound_33711.wav", sr=48000)
57
+
58
+ # Create artificial noise and mix it with the clean speech at 5 dB SNR.
59
+ noise_gen = NoiseGenerator(sample_rate=sr)
60
+ noise = noise_gen.sample(n_samples=len(wave_clean))
61
+ wave_noisy = snr_mixer(wave_clean, noise, snr=5)
62
+
63
+ # Enhance with a registered model id.
64
+ # The ONNX model is downloaded and cached automatically on first use.
65
+ wave_enhanced = selib.enhance(wave_noisy, "deepfilternet3")
66
+ ```
67
+
68
+ You can also save the enhanced waveform directly. The loaded model is cached by
69
+ default, so repeated calls with the same `model_id` do not reload the ONNX
70
+ session.
71
+
72
+ ```python
73
+ # Save as 24-bit PCM WAV while returning the enhanced NumPy array.
74
+ wave_enhanced = selib.enhance(
75
+ wave_noisy,
76
+ "deepfilternet3",
77
+ save_to="enhanced.wav",
78
+ bits_per_sample=24,
79
+ )
80
+ ```
81
+
82
+ ## Model Objects
83
+
84
+ For more control, load the model wrapper once and call its
85
+ `enhance()` method directly.
86
+
87
+ ```python
88
+ from selib import load_model
89
+ from selib.models import DeepFilterNetOnnx
90
+
91
+ # load_model() chooses the correct wrapper from the registry metadata.
92
+ model: DeepFilterNetOnnx = load_model("deepfilternet3")
93
+
94
+ # DeepFilterNetOnnx supports extra options such as attenuation limiting.
95
+ wave_enhanced = model.enhance(wave_noisy, atten_lim_db=12)
96
+ ```
97
+
98
+ Magnitude-mask models can also be used directly. They predict a mask, multiply
99
+ it with the noisy magnitude spectrogram, and reuse the noisy phase.
100
+
101
+ ```python
102
+ from selib import MagnitudeMaskModel
103
+
104
+ # ul_unas_16k is a 16 kHz magnitude-mask model.
105
+ mask_model = MagnitudeMaskModel("ul_unas_16k")
106
+ wave_enhanced = mask_model.enhance(wave_noisy_16k)
107
+ ```
108
+
109
+ ## Metrics
110
+
111
+ SeLib includes a few quick NumPy-only metrics that do not require PESQ/STOI or
112
+ other external scoring libraries.
113
+
114
+ ```python
115
+ from selib.metrics import snr, segsnr, si_sdr
116
+
117
+ # Compare clean reference speech against an enhanced waveform.
118
+ print("SNR:", snr(wave_clean, wave_enhanced))
119
+ print("segSNR:", segsnr(wave_clean, wave_enhanced, sample_rate=sr))
120
+ print("SI-SDR:", si_sdr(wave_clean, wave_enhanced))
121
+ ```
122
+
123
+ ## Available Models
124
+
125
+ | Model ID | Type | Sample rate | #params | Paper | Repository |
126
+ | --- | --- | --- | --- | --- | --- |
127
+ | `deepfilternet3` | DeepFilterNet | 48 kHz | 2.13M | [arXiv:2305.08227](https://arxiv.org/abs/2305.08227) | [GitHub](https://github.com/rikorose/deepfilternet) |
128
+ | `deepfilternet2` | DeepFilterNet | 48 kHz | 2.31M | [arXiv:2205.05474](https://arxiv.org/abs/2205.05474) | [GitHub](https://github.com/rikorose/deepfilternet) |
129
+ | `deepfilternet1` | DeepFilterNet | 48 kHz | 1.78M | [arXiv:2110.05588](https://arxiv.org/abs/2110.05588) | [GitHub](https://github.com/rikorose/deepfilternet) |
130
+ | `ul_unas_16k` | Magnitude mask | 16 kHz | 0.171M | [arXiv:2503.00340](https://arxiv.org/abs/2503.00340) | [GitHub](https://github.com/Xiaobin-Rong/ul-unas) |
131
+
132
+ DeepFilterNet models use ERB masks plus deep-filter coefficients. Magnitude-mask
133
+ models predict a spectrogram mask and reconstruct audio with the noisy phase.
@@ -0,0 +1,102 @@
1
+ # SeLib
2
+
3
+ SeLib is a small speech-enhancement library for ONNX models. It provides a
4
+ simple `enhance()` function, lightweight model wrappers, and utility functions for audio/noise experiments.
5
+
6
+ ## Install
7
+
8
+ Install the package directly from GitHub while the project is still small and moving quickly.
9
+
10
+ ```bash
11
+ # Install SeLib and its Python dependencies.
12
+ pip install git+https://github.com/nipponjo/selib.git
13
+ ```
14
+
15
+ ## Basic Use
16
+
17
+ The shortest path is `selib.enhance(wave, model_id)`. Pass a mono NumPy waveform at the sample rate expected by the model.
18
+ ```python
19
+ import librosa
20
+ import selib
21
+
22
+ from selib.utils import NoiseGenerator, snr_mixer
23
+
24
+ # Load clean speech at the model sample rate.
25
+ wave_clean, sr = librosa.load("./data/clean_freesound_33711.wav", sr=48000)
26
+
27
+ # Create artificial noise and mix it with the clean speech at 5 dB SNR.
28
+ noise_gen = NoiseGenerator(sample_rate=sr)
29
+ noise = noise_gen.sample(n_samples=len(wave_clean))
30
+ wave_noisy = snr_mixer(wave_clean, noise, snr=5)
31
+
32
+ # Enhance with a registered model id.
33
+ # The ONNX model is downloaded and cached automatically on first use.
34
+ wave_enhanced = selib.enhance(wave_noisy, "deepfilternet3")
35
+ ```
36
+
37
+ You can also save the enhanced waveform directly. The loaded model is cached by
38
+ default, so repeated calls with the same `model_id` do not reload the ONNX
39
+ session.
40
+
41
+ ```python
42
+ # Save as 24-bit PCM WAV while returning the enhanced NumPy array.
43
+ wave_enhanced = selib.enhance(
44
+ wave_noisy,
45
+ "deepfilternet3",
46
+ save_to="enhanced.wav",
47
+ bits_per_sample=24,
48
+ )
49
+ ```
50
+
51
+ ## Model Objects
52
+
53
+ For more control, load the model wrapper once and call its
54
+ `enhance()` method directly.
55
+
56
+ ```python
57
+ from selib import load_model
58
+ from selib.models import DeepFilterNetOnnx
59
+
60
+ # load_model() chooses the correct wrapper from the registry metadata.
61
+ model: DeepFilterNetOnnx = load_model("deepfilternet3")
62
+
63
+ # DeepFilterNetOnnx supports extra options such as attenuation limiting.
64
+ wave_enhanced = model.enhance(wave_noisy, atten_lim_db=12)
65
+ ```
66
+
67
+ Magnitude-mask models can also be used directly. They predict a mask, multiply
68
+ it with the noisy magnitude spectrogram, and reuse the noisy phase.
69
+
70
+ ```python
71
+ from selib import MagnitudeMaskModel
72
+
73
+ # ul_unas_16k is a 16 kHz magnitude-mask model.
74
+ mask_model = MagnitudeMaskModel("ul_unas_16k")
75
+ wave_enhanced = mask_model.enhance(wave_noisy_16k)
76
+ ```
77
+
78
+ ## Metrics
79
+
80
+ SeLib includes a few quick NumPy-only metrics that do not require PESQ/STOI or
81
+ other external scoring libraries.
82
+
83
+ ```python
84
+ from selib.metrics import snr, segsnr, si_sdr
85
+
86
+ # Compare clean reference speech against an enhanced waveform.
87
+ print("SNR:", snr(wave_clean, wave_enhanced))
88
+ print("segSNR:", segsnr(wave_clean, wave_enhanced, sample_rate=sr))
89
+ print("SI-SDR:", si_sdr(wave_clean, wave_enhanced))
90
+ ```
91
+
92
+ ## Available Models
93
+
94
+ | Model ID | Type | Sample rate | #params | Paper | Repository |
95
+ | --- | --- | --- | --- | --- | --- |
96
+ | `deepfilternet3` | DeepFilterNet | 48 kHz | 2.13M | [arXiv:2305.08227](https://arxiv.org/abs/2305.08227) | [GitHub](https://github.com/rikorose/deepfilternet) |
97
+ | `deepfilternet2` | DeepFilterNet | 48 kHz | 2.31M | [arXiv:2205.05474](https://arxiv.org/abs/2205.05474) | [GitHub](https://github.com/rikorose/deepfilternet) |
98
+ | `deepfilternet1` | DeepFilterNet | 48 kHz | 1.78M | [arXiv:2110.05588](https://arxiv.org/abs/2110.05588) | [GitHub](https://github.com/rikorose/deepfilternet) |
99
+ | `ul_unas_16k` | Magnitude mask | 16 kHz | 0.171M | [arXiv:2503.00340](https://arxiv.org/abs/2503.00340) | [GitHub](https://github.com/Xiaobin-Rong/ul-unas) |
100
+
101
+ DeepFilterNet models use ERB masks plus deep-filter coefficients. Magnitude-mask
102
+ models predict a spectrogram mask and reconstruct audio with the noisy phase.
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "speech-selib"
7
+ version = "0.1.0"
8
+ description = "A simple speech enhancement library using ONNX models"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8.0"
11
+ authors = [
12
+ { name = "nipponjo", email = "nipponjo.git@gmail.com" },
13
+ ]
14
+ dependencies = [
15
+ "numpy",
16
+ "librosa",
17
+ "onnxruntime-gpu; sys_platform != 'darwin'",
18
+ "onnxruntime; sys_platform == 'darwin'",
19
+ ]
20
+ classifiers = [
21
+ "Programming Language :: Python",
22
+ "Programming Language :: Python :: 3",
23
+ "Intended Audience :: Science/Research",
24
+ "Intended Audience :: Developers",
25
+ "Intended Audience :: Telecommunications Industry",
26
+ "Topic :: Software Development :: Libraries",
27
+ "Topic :: Software Development :: Libraries :: Python Modules",
28
+ "Topic :: Multimedia",
29
+ "Topic :: Multimedia :: Sound/Audio",
30
+ "Topic :: Multimedia :: Sound/Audio :: Speech",
31
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/nipponjo/selib"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["."]
@@ -0,0 +1,7 @@
1
+ from .models.factory import enhance, load_model
2
+ from . import metrics
3
+
4
+ __all__ = [
5
+ "enhance",
6
+ "load_model",
7
+ ]
@@ -0,0 +1,31 @@
1
+ from .basic import (
2
+ mae,
3
+ mse,
4
+ power_db,
5
+ rms,
6
+ rmse,
7
+ segmental_snr,
8
+ segsnr,
9
+ si_sdr,
10
+ si_snr,
11
+ snr,
12
+ )
13
+
14
+ try:
15
+ from .dnsmos import DnsMosSigBakOvrOnnx
16
+ except ImportError:
17
+ DnsMosSigBakOvrOnnx = None
18
+
19
+ __all__ = [
20
+ "DnsMosSigBakOvrOnnx",
21
+ "mae",
22
+ "mse",
23
+ "power_db",
24
+ "rms",
25
+ "rmse",
26
+ "segmental_snr",
27
+ "segsnr",
28
+ "si_sdr",
29
+ "si_snr",
30
+ "snr",
31
+ ]
@@ -0,0 +1,245 @@
1
+ """Lightweight NumPy metrics for speech enhancement.
2
+
3
+ These metrics do not depend on external perceptual-metric packages such as
4
+ PESQ, STOI, or DNSMOS. They are useful for quick checks, regression tests, and
5
+ objective comparisons when a clean reference signal is available.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Optional, Sequence, Union
11
+ import warnings
12
+
13
+ import numpy as np
14
+
15
+ ArrayLike = Union[np.ndarray, Sequence[float]]
16
+ _WARNED_UNEQUAL_LENGTH = False
17
+
18
+
19
+ def _as_1d_float(name: str, x: ArrayLike) -> np.ndarray:
20
+ """Convert input audio to a contiguous 1-D float64 array."""
21
+ x = np.asarray(x, dtype=np.float64).reshape(-1)
22
+ if x.size == 0:
23
+ raise ValueError(f"{name} must contain at least one sample")
24
+ return x
25
+
26
+
27
+ def _align_pair(reference: ArrayLike,
28
+ estimate: ArrayLike) -> tuple[np.ndarray, np.ndarray]:
29
+ """Convert and truncate two signals to a shared length."""
30
+ global _WARNED_UNEQUAL_LENGTH
31
+ reference = _as_1d_float("reference", reference)
32
+ estimate = _as_1d_float("estimate", estimate)
33
+ n = min(reference.size, estimate.size)
34
+ if n == 0:
35
+ raise ValueError("reference and estimate must contain samples")
36
+ if reference.size != estimate.size and not _WARNED_UNEQUAL_LENGTH:
37
+ warnings.warn(
38
+ "reference and estimate have unequal lengths "
39
+ f"({reference.size} vs {estimate.size}); aligning by truncating "
40
+ f"both signals to the first {n} samples.",
41
+ RuntimeWarning,
42
+ stacklevel=2,
43
+ )
44
+ _WARNED_UNEQUAL_LENGTH = True
45
+ return reference[:n], estimate[:n]
46
+
47
+
48
+ def power_db(x: ArrayLike, eps: float = 1e-12) -> float:
49
+ """Return average signal power in dB.
50
+
51
+ Parameters
52
+ ----------
53
+ x : ndarray
54
+ Input waveform.
55
+ eps : float, default=1e-12
56
+ Numerical floor applied before ``log10``.
57
+
58
+ Returns
59
+ -------
60
+ float
61
+ ``10 * log10(mean(x ** 2))`` in dB.
62
+ """
63
+ x = _as_1d_float("x", x)
64
+ return float(10.0 * np.log10(np.mean(np.square(x)) + eps))
65
+
66
+
67
+ def rms(x: ArrayLike) -> float:
68
+ """Return root-mean-square amplitude."""
69
+ x = _as_1d_float("x", x)
70
+ return float(np.sqrt(np.mean(np.square(x))))
71
+
72
+
73
+ def mse(reference: ArrayLike, estimate: ArrayLike) -> float:
74
+ """Return mean squared error between two aligned waveforms."""
75
+ reference, estimate = _align_pair(reference, estimate)
76
+ return float(np.mean(np.square(reference - estimate)))
77
+
78
+
79
+ def rmse(reference: ArrayLike, estimate: ArrayLike) -> float:
80
+ """Return root mean squared error between two aligned waveforms."""
81
+ return float(np.sqrt(mse(reference, estimate)))
82
+
83
+
84
+ def mae(reference: ArrayLike, estimate: ArrayLike) -> float:
85
+ """Return mean absolute error between two aligned waveforms."""
86
+ reference, estimate = _align_pair(reference, estimate)
87
+ return float(np.mean(np.abs(reference - estimate)))
88
+
89
+
90
+ def snr(reference: ArrayLike,
91
+ estimate: ArrayLike,
92
+ eps: float = 1e-12) -> float:
93
+ """Return signal-to-noise ratio in dB.
94
+
95
+ ``reference`` is normally the clean speech signal. ``estimate`` may be a
96
+ noisy or enhanced signal. The residual ``reference - estimate`` is treated
97
+ as noise/error.
98
+
99
+ Parameters
100
+ ----------
101
+ reference : ndarray
102
+ Clean reference waveform.
103
+ estimate : ndarray
104
+ Estimated waveform to compare against ``reference``.
105
+ eps : float, default=1e-12
106
+ Numerical floor for the denominator.
107
+
108
+ Returns
109
+ -------
110
+ float
111
+ SNR in dB.
112
+ """
113
+ reference, estimate = _align_pair(reference, estimate)
114
+ signal_power = np.sum(np.square(reference))
115
+ noise_power = np.sum(np.square(reference - estimate))
116
+ return float(10.0 * np.log10((signal_power + eps) / (noise_power + eps)))
117
+
118
+
119
+ def segmental_snr(reference: ArrayLike,
120
+ estimate: ArrayLike,
121
+ sample_rate: int = 16000,
122
+ frame_ms: float = 20.0,
123
+ hop_ms: Optional[float] = None,
124
+ min_snr_db: float = -10.0,
125
+ max_snr_db: float = 35.0,
126
+ eps: float = 1e-12) -> float:
127
+ """Return mean clipped frame-wise SNR in dB.
128
+
129
+ Segmental SNR computes SNR per short frame and averages the clipped values.
130
+ Clipping keeps silent or near-silent frames from dominating the score.
131
+
132
+ Parameters
133
+ ----------
134
+ reference : ndarray
135
+ Clean reference waveform.
136
+ estimate : ndarray
137
+ Estimated waveform to compare against ``reference``.
138
+ sample_rate : int, default=16000
139
+ Sample rate in Hz, used to convert frame and hop durations to samples.
140
+ frame_ms : float, default=20.0
141
+ Frame length in milliseconds.
142
+ hop_ms : float, optional
143
+ Hop length in milliseconds. Defaults to ``frame_ms``.
144
+ min_snr_db : float, default=-10.0
145
+ Lower clipping bound for each frame SNR.
146
+ max_snr_db : float, default=35.0
147
+ Upper clipping bound for each frame SNR.
148
+ eps : float, default=1e-12
149
+ Numerical floor for frame powers.
150
+
151
+ Returns
152
+ -------
153
+ float
154
+ Mean clipped segmental SNR in dB.
155
+ """
156
+ reference, estimate = _align_pair(reference, estimate)
157
+ if hop_ms is None:
158
+ hop_ms = frame_ms
159
+
160
+ frame_len = max(1, int(round(sample_rate * frame_ms / 1000.0)))
161
+ hop_len = max(1, int(round(sample_rate * hop_ms / 1000.0)))
162
+ if reference.size < frame_len:
163
+ return snr(reference, estimate, eps=eps)
164
+
165
+ values = []
166
+ for start in range(0, reference.size - frame_len + 1, hop_len):
167
+ ref_frame = reference[start:start + frame_len]
168
+ err_frame = ref_frame - estimate[start:start + frame_len]
169
+ frame_snr = 10.0 * np.log10(
170
+ (np.sum(np.square(ref_frame)) + eps)
171
+ / (np.sum(np.square(err_frame)) + eps)
172
+ )
173
+ values.append(np.clip(frame_snr, min_snr_db, max_snr_db))
174
+ return float(np.mean(values))
175
+
176
+
177
+ def segsnr(reference: ArrayLike,
178
+ estimate: ArrayLike,
179
+ sample_rate: int = 16000,
180
+ frame_ms: float = 20.0,
181
+ hop_ms: Optional[float] = None,
182
+ min_snr_db: float = -10.0,
183
+ max_snr_db: float = 35.0,
184
+ eps: float = 1e-12) -> float:
185
+ """Alias for ``segmental_snr``."""
186
+ return segmental_snr(
187
+ reference,
188
+ estimate,
189
+ sample_rate=sample_rate,
190
+ frame_ms=frame_ms,
191
+ hop_ms=hop_ms,
192
+ min_snr_db=min_snr_db,
193
+ max_snr_db=max_snr_db,
194
+ eps=eps,
195
+ )
196
+
197
+
198
+ def si_sdr(reference: ArrayLike,
199
+ estimate: ArrayLike,
200
+ zero_mean: bool = True,
201
+ eps: float = 1e-12) -> float:
202
+ """Return scale-invariant signal-to-distortion ratio in dB.
203
+
204
+ SI-SDR removes gain differences between ``reference`` and ``estimate``
205
+ before measuring residual error. This is often more useful than plain SNR
206
+ when an enhancement model changes output loudness.
207
+
208
+ Parameters
209
+ ----------
210
+ reference : ndarray
211
+ Clean reference waveform.
212
+ estimate : ndarray
213
+ Estimated waveform to compare against ``reference``.
214
+ zero_mean : bool, default=True
215
+ If True, remove the mean from both signals before projection.
216
+ eps : float, default=1e-12
217
+ Numerical floor for divisions and logarithms.
218
+
219
+ Returns
220
+ -------
221
+ float
222
+ SI-SDR in dB.
223
+ """
224
+ reference, estimate = _align_pair(reference, estimate)
225
+ if zero_mean:
226
+ reference = reference - np.mean(reference)
227
+ estimate = estimate - np.mean(estimate)
228
+
229
+ scale = np.sum(estimate * reference) / (np.sum(np.square(reference)) + eps)
230
+ target = scale * reference
231
+ noise = estimate - target
232
+ return float(
233
+ 10.0 * np.log10(
234
+ (np.sum(np.square(target)) + eps)
235
+ / (np.sum(np.square(noise)) + eps)
236
+ )
237
+ )
238
+
239
+
240
+ def si_snr(reference: ArrayLike,
241
+ estimate: ArrayLike,
242
+ zero_mean: bool = True,
243
+ eps: float = 1e-12) -> float:
244
+ """Alias for ``si_sdr``."""
245
+ return si_sdr(reference, estimate, zero_mean=zero_mean, eps=eps)