neverlib 0.2.2__py3-none-any.whl → 0.2.3__py3-none-any.whl

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 (60) hide show
  1. neverlib/__init__.py +2 -2
  2. neverlib/audio_aug/__init__.py +1 -1
  3. neverlib/audio_aug/audio_aug.py +4 -5
  4. neverlib/dataAnalyze/README.md +234 -0
  5. neverlib/dataAnalyze/__init__.py +87 -0
  6. neverlib/dataAnalyze/dataset_analyzer.py +590 -0
  7. neverlib/dataAnalyze/quality_metrics.py +364 -0
  8. neverlib/dataAnalyze/rms_distrubution.py +62 -0
  9. neverlib/dataAnalyze/spectral_analysis.py +218 -0
  10. neverlib/dataAnalyze/statistics.py +406 -0
  11. neverlib/dataAnalyze/temporal_features.py +126 -0
  12. neverlib/dataAnalyze/visualization.py +468 -0
  13. neverlib/filter/AudoEQ/README.md +165 -0
  14. neverlib/filter/AudoEQ/auto_eq_de.py +361 -0
  15. neverlib/filter/AudoEQ/auto_eq_ga_advanced.py +577 -0
  16. neverlib/filter/AudoEQ/auto_eq_ga_basic.py +380 -0
  17. neverlib/filter/AudoEQ/auto_eq_spectral_direct.py +75 -0
  18. neverlib/filter/README.md +101 -0
  19. neverlib/filter/__init__.py +7 -0
  20. neverlib/filter/biquad.py +45 -0
  21. neverlib/filter/common.py +5 -6
  22. neverlib/filter/core.py +339 -0
  23. neverlib/metrics/dnsmos.py +160 -0
  24. neverlib/metrics/snr.py +177 -0
  25. neverlib/metrics/spec.py +45 -0
  26. neverlib/metrics/test_pesq.py +35 -0
  27. neverlib/metrics/time.py +68 -0
  28. neverlib/tests/test_vad.py +21 -0
  29. neverlib/utils/audio_split.py +2 -1
  30. neverlib/utils/message.py +4 -4
  31. neverlib/utils/utils.py +32 -15
  32. neverlib/vad/PreProcess.py +1 -1
  33. neverlib/vad/README.md +10 -10
  34. neverlib/vad/VAD_Energy.py +1 -1
  35. neverlib/vad/VAD_Silero.py +1 -1
  36. neverlib/vad/VAD_WebRTC.py +1 -1
  37. neverlib/vad/VAD_funasr.py +1 -1
  38. neverlib/vad/VAD_statistics.py +3 -3
  39. neverlib/vad/VAD_vadlib.py +2 -2
  40. neverlib/vad/VAD_whisper.py +1 -1
  41. neverlib/vad/__init__.py +1 -1
  42. neverlib/vad/class_get_speech.py +4 -4
  43. neverlib/vad/class_vad.py +1 -1
  44. neverlib/vad/utils.py +47 -5
  45. {neverlib-0.2.2.dist-info → neverlib-0.2.3.dist-info}/METADATA +120 -120
  46. neverlib-0.2.3.dist-info/RECORD +53 -0
  47. {neverlib-0.2.2.dist-info → neverlib-0.2.3.dist-info}/WHEEL +1 -1
  48. neverlib/Documents/vad/VAD_Energy.ipynb +0 -159
  49. neverlib/Documents/vad/VAD_Silero.ipynb +0 -305
  50. neverlib/Documents/vad/VAD_WebRTC.ipynb +0 -183
  51. neverlib/Documents/vad/VAD_funasr.ipynb +0 -179
  52. neverlib/Documents/vad/VAD_ppasr.ipynb +0 -175
  53. neverlib/Documents/vad/VAD_statistics.ipynb +0 -522
  54. neverlib/Documents/vad/VAD_vadlib.ipynb +0 -184
  55. neverlib/Documents/vad/VAD_whisper.ipynb +0 -430
  56. neverlib/utils/waveform_analyzer.py +0 -51
  57. neverlib/wav_data/000_short.wav +0 -0
  58. neverlib-0.2.2.dist-info/RECORD +0 -40
  59. {neverlib-0.2.2.dist-info → neverlib-0.2.3.dist-info}/licenses/LICENSE +0 -0
  60. {neverlib-0.2.2.dist-info → neverlib-0.2.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,339 @@
1
+ '''
2
+ Author: 凌逆战 | Never
3
+ Date: 2025-08-04 16:25:00
4
+ Description: EQ滤波器
5
+ type(fc, gain, Q)
6
+
7
+ enum IIR_BIQUARD_TYPE {
8
+ IIR_BIQUARD_PASS = 0, // pass through
9
+ IIR_BIQUARD_RAW, // raw filter
10
+ IIR_BIQUARD_LPF, // 低通滤波器 low pass filter
11
+ IIR_BIQUARD_HPF, // 高通滤波器 high pass filter
12
+ IIR_BIQUARD_BPF0, // 带通滤波器 band pass filter, constant skirt gain, peak gain = Q
13
+ IIR_BIQUARD_BPF1, // 带通滤波器 band pass filter, const 0 dB peak gain
14
+ IIR_BIQUARD_NOTCH, // 陷波滤波器 notch filter
15
+ IIR_BIQUARD_APF, // 全通滤波器 allpass filter
16
+ IIR_BIQUARD_PEAKINGEQ, // 峰值滤波器 peakingEQ
17
+ IIR_BIQUARD_LOWSHELF, // 低切滤波器 low shelf filter
18
+ IIR_BIQUARD_HIGHSHELF, // 高切滤波器 high shelf filter
19
+ IIR_BIQUARD_QTY // number of biquard types
20
+ };
21
+ '''
22
+ import random
23
+ import numpy as np
24
+ from scipy import signal
25
+
26
+
27
+ class EQFilter():
28
+ def __init__(self, fs=16000):
29
+ self.fs = fs
30
+
31
+ def LowpassFilter(self, fc, Q=1 / np.sqrt(2.0)):
32
+ """ 低通滤波器(Low Pass Filter)
33
+ LPF: H(s) = 1 / (s^2 + s/Q + 1)
34
+
35
+ b0 = (1 - cos(w0))/2;
36
+ b1 = 1 - cos(w0);
37
+ b2 = (1 - cos(w0))/2;
38
+ a0 = 1 + alpha;
39
+ a1 = -2*cos(w0);
40
+ a2 = 1 - alpha;
41
+ """
42
+ # 中间变量
43
+ w0 = 2.0 * np.pi * fc / self.fs # 角频率
44
+ cos_w0 = np.cos(w0) # cos(w0)
45
+ sin_w0 = np.sin(w0) # sin(w0)
46
+ alpha = sin_w0 / (2.0 * Q) # alpha
47
+ # ---------------------------------------------
48
+ b0 = (1.0 - cos_w0) / 2.0
49
+ b1 = 1.0 - cos_w0
50
+ b2 = (1.0 - cos_w0) / 2.0
51
+ a0 = 1.0 + alpha
52
+ a1 = -2.0 * cos_w0
53
+ a2 = 1.0 - alpha
54
+ numerator_B = np.array([b0, b1, b2], dtype=np.float32)
55
+ denominator_A = np.array([a0, a1, a2], dtype=np.float32)
56
+ return numerator_B / a0, denominator_A / a0
57
+
58
+ def HighpassFilter(self, fc, Q=1 / np.sqrt(2)):
59
+ """ 高通滤波器(High Pass Filter)
60
+ HPF: $H(s)=\frac{s^2}{s^2 + s/Q + 1}$
61
+ b0 = (1 + cos(w0))/2
62
+ b1 = -(1 + cos(w0))
63
+ b2 = (1 + cos(w0))/2
64
+ a0 = 1 + alpha
65
+ a1 = -2*cos(w0)
66
+ a2 = 1 - alpha
67
+ """
68
+ # 中间变量
69
+ w0 = 2.0 * np.pi * fc / self.fs # 角频率
70
+ cos_w0 = np.cos(w0) # cos(w0)
71
+ sin_w0 = np.sin(w0) # sin(w0)
72
+ alpha = sin_w0 / (2.0 * Q) # alpha
73
+ # ---------------------------------------------
74
+ b0 = (1.0 + cos_w0) / 2.0
75
+ b1 = -(1.0 + cos_w0)
76
+ b2 = (1.0 + cos_w0) / 2.0
77
+ a0 = 1.0 + alpha
78
+ a1 = -2.0 * cos_w0
79
+ a2 = 1.0 - alpha
80
+ numerator_B = np.array([b0, b1, b2], dtype=np.float32)
81
+ denominator_A = np.array([a0, a1, a2], dtype=np.float32)
82
+ return numerator_B / a0, denominator_A / a0
83
+
84
+ def BandpassFilter_Q(self, fc, Q):
85
+ """带通 Band Pass Filter (增益 = Q)
86
+ BPF: H(s) = s / (s^2 + s/Q + 1) (constant skirt gain, peak gain = Q)
87
+ b0 = sin(w0)/2 = Q*alpha
88
+ b1 = 0
89
+ b2 = -sin(w0)/2 = -Q*alpha
90
+ a0 = 1 + alpha
91
+ a1 = -2*cos(w0)
92
+ a2 = 1 - alpha
93
+ """
94
+ # 中间变量
95
+ w0 = 2.0 * np.pi * fc / self.fs # 角频率
96
+ cos_w0 = np.cos(w0) # cos(w0)
97
+ sin_w0 = np.sin(w0) # sin(w0)
98
+ alpha = sin_w0 / (2.0 * Q) # alpha
99
+ # ---------------------------------------------
100
+ b0 = sin_w0 / 2.0 # Q*alpha
101
+ b1 = 0.0
102
+ b2 = -sin_w0 / 2.0 # -Q*alpha
103
+ a0 = 1.0 + alpha
104
+ a1 = -2.0 * cos_w0
105
+ a2 = 1.0 - alpha
106
+ numerator_B = np.array([b0, b1, b2], dtype=np.float32)
107
+ denominator_A = np.array([a0, a1, a2], dtype=np.float32)
108
+ return numerator_B / a0, denominator_A / a0
109
+
110
+ def BandpassFilter_0dB(self, fc, Q=1 / np.sqrt(2)):
111
+ """带通 Band Pass Filter(0 db增益)
112
+ BPF: H(s) = (s/Q) / (s^2 + s/Q + 1) (constant 0 dB peak gain)
113
+ b0 = alpha
114
+ b1 = 0
115
+ b2 = -alpha
116
+ a0 = 1 + alpha
117
+ a1 = -2*cos(w0)
118
+ a2 = 1 - alpha
119
+ """
120
+ # 中间变量
121
+ w0 = 2.0 * np.pi * fc / self.fs # 角频率
122
+ cos_w0 = np.cos(w0) # cos(w0)
123
+ sin_w0 = np.sin(w0) # sin(w0)
124
+ alpha = sin_w0 / (2.0 * Q) # alpha
125
+ # ---------------------------------------------
126
+ b0 = alpha
127
+ b1 = 0.0
128
+ b2 = -alpha
129
+ a0 = 1.0 + alpha
130
+ a1 = -2.0 * cos_w0
131
+ a2 = 1.0 - alpha
132
+ numerator_B = np.array([b0, b1, b2], dtype=np.float32)
133
+ denominator_A = np.array([a0, a1, a2], dtype=np.float32)
134
+ return numerator_B / a0, denominator_A / a0
135
+
136
+ def NotchFilter(self, fc, Q=1 / np.sqrt(2)):
137
+ """Notch滤波器
138
+ notch: H(s) = (s^2 + 1) / (s^2 + s/Q + 1)$
139
+ b0 = 1
140
+ b1 = -2*cos(w0)
141
+ b2 = 1
142
+ a0 = 1 + alpha
143
+ a1 = -2*cos(w0)
144
+ a2 = 1 - alpha
145
+ """
146
+ # 中间变量
147
+ w0 = 2.0 * np.pi * fc / self.fs # 角频率
148
+ cos_w0 = np.cos(w0) # cos(w0)
149
+ sin_w0 = np.sin(w0) # sin(w0)
150
+ alpha = sin_w0 / (2.0 * Q) # alpha
151
+ # ---------------------------------------------
152
+ b0 = 1.0
153
+ b1 = -2.0 * cos_w0
154
+ b2 = 1.0
155
+ a0 = 1.0 + alpha
156
+ a1 = -2.0 * cos_w0
157
+ a2 = 1.0 - alpha
158
+ numerator_B = np.array([b0, b1, b2], dtype=np.float32)
159
+ denominator_A = np.array([a0, a1, a2], dtype=np.float32)
160
+ return numerator_B / a0, denominator_A / a0
161
+
162
+ def AllpassFilter(self, fc, Q=1 / np.sqrt(2)):
163
+ """全通 All Pass Filter
164
+ APF: H(s) = (s^2 - s/Q + 1) / (s^2 + s/Q + 1)$
165
+ b0 = 1 - alpha
166
+ b1 = -2*cos(w0)
167
+ b2 = 1 + alpha
168
+ a0 = 1 + alpha
169
+ a1 = -2*cos(w0)
170
+ a2 = 1 - alpha
171
+ """
172
+ # 中间变量
173
+ w0 = 2.0 * np.pi * fc / self.fs # 角频率
174
+ cos_w0 = np.cos(w0) # cos(w0)
175
+ sin_w0 = np.sin(w0) # sin(w0)
176
+ alpha = sin_w0 / (2.0 * Q) # alpha
177
+ # ---------------------------------------------
178
+ b0 = 1.0 - alpha
179
+ b1 = -2.0 * cos_w0
180
+ b2 = 1.0 + alpha
181
+ a0 = 1.0 + alpha
182
+ a1 = -2.0 * cos_w0
183
+ a2 = 1.0 - alpha
184
+ numerator_B = np.array([b0, b1, b2], dtype=np.float32)
185
+ denominator_A = np.array([a0, a1, a2], dtype=np.float32)
186
+ return numerator_B / a0, denominator_A / a0
187
+
188
+ def PeakingFilter(self, fc, dBgain, Q=1 / np.sqrt(2)):
189
+ """峰值滤波器
190
+ peakingEQ: H(s) = (s^2 + s*(A/Q) + 1) / (s^2 + s/(A*Q) + 1)
191
+ b0 = 1 + alpha*A
192
+ b1 = -2*cos(w0)
193
+ b2 = 1 - alpha*A
194
+ a0 = 1 + alpha/A
195
+ a1 = -2*cos(w0)
196
+ a2 = 1 - alpha/A
197
+ """
198
+ # 中间变量
199
+ w0 = 2.0 * np.pi * fc / self.fs # 角频率
200
+ # cos_w0 = np.cos(w0) # cos(w0)
201
+ sin_w0 = np.sin(w0) # sin(w0)
202
+ alpha = sin_w0 / (2.0 * Q) # alpha
203
+ # gain、A 仅用于峰值和shelf滤波器
204
+ dBgain = round(float(dBgain), 3)
205
+ A = 10 ** (dBgain / 40)
206
+ # ---------------------------------------------
207
+ b0 = 1 + alpha * A
208
+ b1 = -2 * np.cos(w0)
209
+ b2 = 1 - alpha * A
210
+ a0 = 1 + alpha / A
211
+ a1 = -2 * np.cos(w0)
212
+ a2 = 1 - alpha / A
213
+ numerator_B = np.array([b0, b1, b2], dtype=np.float32)
214
+ denominator_A = np.array([a0, a1, a2], dtype=np.float32)
215
+ return numerator_B / a0, denominator_A / a0
216
+
217
+ def LowshelfFilter(self, fc, dBgain, Q=1 / np.sqrt(2)):
218
+ """低切滤波器
219
+ lowShelf: H(s) = A * (s^2 + (sqrt(A)/Q)*s + A)/(A*s^2 + (sqrt(A)/Q)*s + 1)
220
+ b0 = A*( (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha )
221
+ b1 = 2*A*( (A-1) - (A+1)*cos(w0) )
222
+ b2 = A*( (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha )
223
+ a0 = (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha
224
+ a1 = -2*( (A-1) + (A+1)*cos(w0) )
225
+ a2 = (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha
226
+ """
227
+ # 中间变量
228
+ w0 = 2.0 * np.pi * fc / self.fs # 角频率
229
+ cos_w0 = np.cos(w0) # cos(w0)
230
+ sin_w0 = np.sin(w0) # sin(w0)
231
+ alpha = sin_w0 / (2.0 * Q) # alpha
232
+ # gain、A 仅用于峰值和shelf滤波器
233
+ dBgain = round(float(dBgain), 3)
234
+ A = 10.0 ** (dBgain / 40.0)
235
+ # ---------------------------------------------
236
+ b0 = A * ((A + 1) - (A - 1) * cos_w0 + 2 * np.sqrt(A) * alpha)
237
+ b1 = 2 * A * ((A - 1) - (A + 1) * cos_w0)
238
+ b2 = A * ((A + 1) - (A - 1) * cos_w0 - 2 * np.sqrt(A) * alpha)
239
+ a0 = (A + 1) + (A - 1) * cos_w0 + 2 * np.sqrt(A) * alpha
240
+ a1 = -2 * ((A - 1) + (A + 1) * cos_w0)
241
+ a2 = (A + 1) + (A - 1) * cos_w0 - 2 * np.sqrt(A) * alpha
242
+ numerator_B = np.array([b0, b1, b2], dtype=np.float32)
243
+ denominator_A = np.array([a0, a1, a2], dtype=np.float32)
244
+ return numerator_B / a0, denominator_A / a0
245
+
246
+ def HighshelfFilter(self, fc, dBgain, Q=1 / np.sqrt(2)):
247
+ """高切滤波器
248
+ highShelf: H(s) = A * (A*s^2 + (sqrt(A)/Q)*s + 1)/(s^2 + (sqrt(A)/Q)*s + A)
249
+ b0 = A*( (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha )
250
+ b1 = -2*A*( (A-1) + (A+1)*cos(w0) )
251
+ b2 = A*( (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha )
252
+ a0 = (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha
253
+ a1 = 2*( (A-1) - (A+1)*cos(w0) )
254
+ a2 = (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha
255
+ """
256
+ # 中间变量
257
+ w0 = 2.0 * np.pi * fc / self.fs # 角频率
258
+ cos_w0 = np.cos(w0) # cos(w0)
259
+ sin_w0 = np.sin(w0) # sin(w0)
260
+ alpha = sin_w0 / (2.0 * Q) # alpha
261
+ # gain、A 仅用于峰值和shelf滤波器
262
+ dBgain = round(float(dBgain), 3)
263
+ A = 10.0 ** (dBgain / 40.0)
264
+ # ---------------------------------------------
265
+ b0 = A * ((A + 1) + (A - 1) * cos_w0 + 2 * np.sqrt(A) * alpha)
266
+ b1 = -2 * A * ((A - 1) + (A + 1) * cos_w0)
267
+ b2 = A * ((A + 1) + (A - 1) * cos_w0 - 2 * np.sqrt(A) * alpha)
268
+ a0 = (A + 1) - (A - 1) * cos_w0 + 2 * np.sqrt(A) * alpha
269
+ a1 = 2 * ((A - 1) - (A + 1) * cos_w0)
270
+ a2 = (A + 1) - (A - 1) * cos_w0 - 2 * np.sqrt(A) * alpha
271
+ numerator_B = np.array([b0, b1, b2], dtype=np.float32)
272
+ denominator_A = np.array([a0, a1, a2], dtype=np.float32)
273
+ return numerator_B / a0, denominator_A / a0
274
+
275
+
276
+ def eq_process(sig, b_list, a_list, ratio):
277
+ """ psap eq增强
278
+ :param sig: input signal with size [T] or [T, C]
279
+ :param b_list: 滤波器系数列表
280
+ :param a_list: 滤波器系数列表
281
+ :param ratio: 0-1, processing ratio
282
+ :return: [T] when ipt.shape is [T]; [T, C] when ipt.shape is [T, C]
283
+ """
284
+ if len(sig.shape) == 1:
285
+ sig.shape = -1, 1
286
+ assert len(sig.shape) == 2, f"input signal's shape must be 2, but {sig.shape} now!"
287
+ sig_eq = sig.copy()
288
+ if random.random() < ratio:
289
+ # 逐个应用滤波器
290
+ for b, a in zip(b_list, a_list):
291
+ sig_eq = signal.lfilter(b, a, sig_eq, axis=0)
292
+ return sig_eq
293
+
294
+
295
+ def eq_process_test():
296
+ import soundfile as sf
297
+
298
+ EQ_Coef = [
299
+ ([1.5023072, -1.0912886, 0.1981803], [1., -1.3417218, 0.4500543]), # f0 = 80, gain = -20, q = 1
300
+ ([1.2288871, -1.173879, 0.18292512], [1., -1.173879, 0.4118123]), # f0 = 100, gain = -10, q = 1
301
+ ]
302
+
303
+ wav, wav_sr = sf.read("./white.wav", dtype="float32", always_2d=True)
304
+
305
+ wav = eq_process(wav, EQ_Coef, ratio=1)
306
+
307
+ sf.write("./white_eq.wav", wav, wav_sr)
308
+
309
+
310
+ def EQ_test():
311
+ """
312
+ lowshelf滤波器 freq=1.5kHz, gain=15dB, Q=0.5
313
+ b: [ 1.5023072 -1.0912886 0.1981803]
314
+ a: [ 1. -1.3417218 0.4500543]
315
+
316
+ peak freq=1.5kHz, gain=5dB, Q=0.5
317
+ b: [ 1.2288871 -1.173879 0.18292512]
318
+ a: [ 1. -1.173879 0.4118123]
319
+ """
320
+ import numpy as np
321
+ import matplotlib.pyplot as plt
322
+ from scipy import signal
323
+
324
+ fs = 16000
325
+
326
+ # b, a = LowshelfFilter(1500, 15, Q=0.5)
327
+ b, a = PeakingFilter(1500, 5, Q=0.5)
328
+ print(b)
329
+ print(a)
330
+
331
+ w, h = signal.freqz(b, a) # 根据系数计算滤波器的频率响应, w是角频率, h是频率响应
332
+ plt.plot(0.5 * fs * w / np.pi, 20 * np.log10(h)) # 0.5*fs*w/np.pi 为频率
333
+ plt.title('Butterworth filter frequency response')
334
+ plt.xlabel('Frequency [Hz]')
335
+ plt.ylabel('Amplitude [dB]')
336
+ plt.grid(which='both', axis='both') # 显示网格
337
+ # 画红色的垂直线, 标记截止频率
338
+ plt.xscale('log') # x轴对数化
339
+ plt.show()
@@ -0,0 +1,160 @@
1
+ '''
2
+ Author: 凌逆战 | Never
3
+ Date: 2025-08-06 10:00:00
4
+ Description:
5
+ 要计算个性化 MOS 分数(干扰说话者受到惩罚),请提供“-p”参数,例如:python dnsmos.py -t ./SampleClips -o sample.csv -p
6
+ 要计算常规 MOS 分数,请省略“-p”参数。例如:python dnsmos.py -t ./SampleClips -o sample.csv
7
+ '''
8
+ import argparse
9
+ import concurrent.futures
10
+ import glob
11
+ import os
12
+ import librosa
13
+ import numpy as np
14
+ import onnxruntime as ort
15
+ import pandas as pd
16
+ import soundfile as sf
17
+ from tqdm import tqdm
18
+
19
+ SAMPLING_RATE = 16000
20
+ INPUT_LENGTH = 9.01
21
+
22
+
23
+ class ComputeScore:
24
+ def __init__(self, primary_model_path, p808_model_path) -> None:
25
+ self.onnx_sess = ort.InferenceSession(primary_model_path)
26
+ self.p808_onnx_sess = ort.InferenceSession(p808_model_path)
27
+
28
+ def audio_melspec(self, audio, n_mels=120, frame_size=320, hop_length=160, sr=16000, to_db=True):
29
+ mel_spec = librosa.feature.melspectrogram(y=audio, sr=sr, n_fft=frame_size+1, hop_length=hop_length, n_mels=n_mels)
30
+ if to_db:
31
+ mel_spec = (librosa.power_to_db(mel_spec, ref=np.max)+40)/40
32
+ return mel_spec.T
33
+
34
+ def get_polyfit_val(self, sig, bak, ovr, is_personalized_MOS):
35
+ if is_personalized_MOS:
36
+ p_ovr = np.poly1d([-0.00533021, 0.005101, 1.18058466, -0.11236046])
37
+ p_sig = np.poly1d([-0.01019296, 0.02751166, 1.19576786, -0.24348726])
38
+ p_bak = np.poly1d([-0.04976499, 0.44276479, -0.1644611, 0.96883132])
39
+ else:
40
+ p_ovr = np.poly1d([-0.06766283, 1.11546468, 0.04602535])
41
+ p_sig = np.poly1d([-0.08397278, 1.22083953, 0.0052439])
42
+ p_bak = np.poly1d([-0.13166888, 1.60915514, -0.39604546])
43
+
44
+ sig_poly = p_sig(sig)
45
+ bak_poly = p_bak(bak)
46
+ ovr_poly = p_ovr(ovr)
47
+
48
+ return sig_poly, bak_poly, ovr_poly
49
+
50
+ def __call__(self, fpath, sampling_rate, is_personalized_MOS):
51
+ aud, input_fs = sf.read(fpath)
52
+ fs = sampling_rate
53
+ if input_fs != fs:
54
+ audio = librosa.resample(aud, input_fs, fs)
55
+ else:
56
+ audio = aud
57
+ actual_audio_len = len(audio)
58
+ len_samples = int(INPUT_LENGTH*fs)
59
+ while len(audio) < len_samples:
60
+ audio = np.append(audio, audio)
61
+
62
+ num_hops = int(np.floor(len(audio)/fs) - INPUT_LENGTH)+1
63
+ hop_len_samples = fs
64
+ predicted_mos_sig_seg_raw = []
65
+ predicted_mos_bak_seg_raw = []
66
+ predicted_mos_ovr_seg_raw = []
67
+ predicted_mos_sig_seg = []
68
+ predicted_mos_bak_seg = []
69
+ predicted_mos_ovr_seg = []
70
+ predicted_p808_mos = []
71
+
72
+ for idx in range(num_hops):
73
+ audio_seg = audio[int(idx*hop_len_samples): int((idx+INPUT_LENGTH)*hop_len_samples)]
74
+ if len(audio_seg) < len_samples:
75
+ continue
76
+
77
+ input_features = np.array(audio_seg).astype('float32')[np.newaxis, :]
78
+ p808_input_features = np.array(self.audio_melspec(audio=audio_seg[:-160])).astype('float32')[np.newaxis, :, :]
79
+ oi = {'input_1': input_features}
80
+ p808_oi = {'input_1': p808_input_features}
81
+ p808_mos = self.p808_onnx_sess.run(None, p808_oi)[0][0][0]
82
+ mos_sig_raw, mos_bak_raw, mos_ovr_raw = self.onnx_sess.run(None, oi)[0][0]
83
+ mos_sig, mos_bak, mos_ovr = self.get_polyfit_val(mos_sig_raw, mos_bak_raw, mos_ovr_raw, is_personalized_MOS)
84
+ predicted_mos_sig_seg_raw.append(mos_sig_raw)
85
+ predicted_mos_bak_seg_raw.append(mos_bak_raw)
86
+ predicted_mos_ovr_seg_raw.append(mos_ovr_raw)
87
+ predicted_mos_sig_seg.append(mos_sig)
88
+ predicted_mos_bak_seg.append(mos_bak)
89
+ predicted_mos_ovr_seg.append(mos_ovr)
90
+ predicted_p808_mos.append(p808_mos)
91
+
92
+ clip_dict = {'filename': fpath, 'len_in_sec': actual_audio_len/fs, 'sr': fs}
93
+ clip_dict['num_hops'] = num_hops
94
+ clip_dict['OVRL_raw'] = np.mean(predicted_mos_ovr_seg_raw)
95
+ clip_dict['SIG_raw'] = np.mean(predicted_mos_sig_seg_raw)
96
+ clip_dict['BAK_raw'] = np.mean(predicted_mos_bak_seg_raw)
97
+ clip_dict['OVRL'] = np.mean(predicted_mos_ovr_seg)
98
+ clip_dict['SIG'] = np.mean(predicted_mos_sig_seg)
99
+ clip_dict['BAK'] = np.mean(predicted_mos_bak_seg)
100
+ clip_dict['P808_MOS'] = np.mean(predicted_p808_mos)
101
+ return clip_dict
102
+
103
+
104
+ def main(args):
105
+ models = glob.glob(os.path.join(args.testset_dir, "*"))
106
+ audio_clips_list = []
107
+ p808_model_path = os.path.join('DNSMOS', 'model_v8.onnx')
108
+
109
+ if args.personalized_MOS:
110
+ primary_model_path = os.path.join('pDNSMOS', 'sig_bak_ovr.onnx')
111
+ else:
112
+ primary_model_path = os.path.join('DNSMOS', 'sig_bak_ovr.onnx')
113
+
114
+ compute_score = ComputeScore(primary_model_path, p808_model_path)
115
+
116
+ rows = []
117
+ clips = []
118
+ clips = glob.glob(os.path.join(args.testset_dir, "*.wav"))
119
+ is_personalized_eval = args.personalized_MOS
120
+ desired_fs = SAMPLING_RATE
121
+ for m in tqdm(models):
122
+ max_recursion_depth = 10
123
+ audio_path = os.path.join(args.testset_dir, m)
124
+ audio_clips_list = glob.glob(os.path.join(audio_path, "*.wav"))
125
+ while len(audio_clips_list) == 0 and max_recursion_depth > 0:
126
+ audio_path = os.path.join(audio_path, "**")
127
+ audio_clips_list = glob.glob(os.path.join(audio_path, "*.wav"))
128
+ max_recursion_depth -= 1
129
+ clips.extend(audio_clips_list)
130
+
131
+ with concurrent.futures.ThreadPoolExecutor() as executor:
132
+ future_to_url = {executor.submit(compute_score, clip, desired_fs, is_personalized_eval): clip for clip in clips}
133
+ for future in tqdm(concurrent.futures.as_completed(future_to_url)):
134
+ clip = future_to_url[future]
135
+ try:
136
+ data = future.result()
137
+ except Exception as exc:
138
+ print('%r generated an exception: %s' % (clip, exc))
139
+ else:
140
+ rows.append(data)
141
+
142
+ df = pd.DataFrame(rows)
143
+ if args.csv_path:
144
+ csv_path = args.csv_path
145
+ df.to_csv(csv_path)
146
+ else:
147
+ print(df.describe())
148
+
149
+
150
+ if __name__ == "__main__":
151
+ parser = argparse.ArgumentParser()
152
+ parser.add_argument('-t', "--testset_dir", default='.',
153
+ help='Path to the dir containing audio clips in .wav to be evaluated')
154
+ parser.add_argument('-o', "--csv_path", default=None, help='Dir to the csv that saves the results')
155
+ parser.add_argument('-p', "--personalized_MOS", action='store_true',
156
+ help='Flag to indicate if personalized MOS score is needed or regular')
157
+
158
+ args = parser.parse_args()
159
+
160
+ main(args)
@@ -0,0 +1,177 @@
1
+ import sys
2
+ sys.path.append("../")
3
+ import librosa
4
+ import numpy as np
5
+ from vad.utils import vad2nad
6
+
7
+
8
+ def get_snr(speech, noise):
9
+ """计算信噪比
10
+ Args:
11
+ speech: 语音音频
12
+ noise: 噪声音频
13
+ Returns:
14
+ snr: 信噪比
15
+ """
16
+ assert speech.ndim == noise.ndim, "speech和noise的维度不一样"
17
+
18
+ power_speech = np.mean(speech**2)
19
+ power_noise = max(np.mean(noise**2), 1e-10)
20
+
21
+ snr = 10 * np.log10(power_speech / power_noise)
22
+ return snr
23
+
24
+
25
+ def get_snr_from_noisy(noisy, speech_vad=None):
26
+ """根据带噪音频计算信噪比
27
+ Args:
28
+ noisy: 带噪音频
29
+ speech_vad: [{start:xxx, end:xxx}, ...]
30
+ Returns:
31
+ snr: 信噪比
32
+ """
33
+ assert speech_vad is not None, "speech_vad不能为空"
34
+
35
+ # 提取语音段
36
+ speech_segments = []
37
+ for segment in speech_vad:
38
+ start = segment['start']
39
+ end = segment['end']
40
+ speech_segments.append(noisy[start:end])
41
+ speech = np.concatenate(speech_segments, axis=0)
42
+
43
+ # 提取非语音段
44
+ noise_segments = []
45
+ noise_point_list = vad2nad(speech_vad, len(noisy))
46
+ for noise_point in noise_point_list:
47
+ noise_segments.append(noisy[noise_point['start']:noise_point['end']])
48
+ noise = np.concatenate(noise_segments, axis=0)
49
+
50
+ return get_snr(speech, noise)
51
+
52
+
53
+ def seg_snr(clean, noisy, frame_length: int, hop_length: int):
54
+ """
55
+ 分帧计算信噪比
56
+ Args:
57
+ clean: 干净音频, numpy array
58
+ noisy: 带噪音频, numpy array
59
+ frame_length: 帧长
60
+ hop_length: 帧移
61
+ Returns:
62
+ snr_mean: 平均信噪比, float
63
+ Raises:
64
+ ValueError: 当输入参数不合法时抛出
65
+ """
66
+ assert clean.shape == noisy.shape, "clean和noisy的维度不一样"
67
+
68
+ # 分帧
69
+ clean_frames = librosa.util.frame(clean, frame_length=frame_length, hop_length=hop_length) # (frame_length, n_frames)
70
+ noisy_frames = librosa.util.frame(noisy, frame_length=frame_length, hop_length=hop_length) # (frame_length, n_frames)
71
+
72
+ # 计算每帧的信噪比
73
+ snr_frames = []
74
+ for i in range(clean_frames.shape[1]):
75
+ clean_frame = clean_frames[:, i]
76
+ noisy_frame = noisy_frames[:, i]
77
+ # 跳过静音帧
78
+ if np.all(np.abs(clean_frame) < 1e-6) or np.all(np.abs(noisy_frame) < 1e-6):
79
+ continue
80
+ snr_frames.append(get_snr(clean_frame, noisy_frame))
81
+
82
+ # 如果所有帧都是静音
83
+ if not snr_frames:
84
+ return float('-inf')
85
+
86
+ return np.mean(snr_frames)
87
+
88
+
89
+ def psnr(clean, noisy, max_val=None):
90
+ """
91
+ 计算峰值信噪比
92
+ Args:
93
+ clean: 干净音频, numpy array
94
+ noisy: 带噪音频, numpy array
95
+ max_val: 信号最大值, 如果为None则使用clean信号的实际最大值
96
+ Returns:
97
+ psnr: 峰值信噪比, 单位dB
98
+ """
99
+ assert clean.shape == noisy.shape, "clean和noisy的维度不一样"
100
+
101
+ # 如果没有指定最大值, 使用clean信号的实际最大值
102
+ if max_val is None:
103
+ max_val = np.abs(clean).max()
104
+
105
+ # 计算均方误差 (MSE)
106
+ mse = np.mean((clean - noisy) ** 2)
107
+
108
+ # 避免除以0
109
+ if mse == 0:
110
+ return float('inf')
111
+
112
+ # 计算PSNR
113
+ psnr = 10 * np.log10(max_val**2 / mse)
114
+ return psnr
115
+
116
+
117
+ def si_sdr(reference, estimate, epsilon=1e-8):
118
+ """
119
+ 计算尺度不变信噪比 (Scale-Invariant Signal-to-Distortion Ratio, SI-SDR)。
120
+
121
+ Args:
122
+ reference (np.ndarray): 原始的、干净的参考信号 (一维数组)。
123
+ estimate (np.ndarray): 模型估计或处理后的信号 (一维数组)。
124
+ epsilon (float): 一个非常小的数值, 用于防止分母为零, 保证数值稳定性。
125
+
126
+ Returns:
127
+ float: SI-SDR 值, 单位为分贝 (dB)。
128
+ """
129
+ assert reference.shape == estimate.shape, "reference和estimate的维度不一样"
130
+
131
+ # 2. 零均值化 (可选但推荐)
132
+ # 移除直流分量, 使计算更关注信号的动态变化
133
+ reference = reference - np.mean(reference)
134
+ estimate = estimate - np.mean(estimate)
135
+
136
+ # 3. 计算目标信号分量 (s_target)
137
+ # s_target 是 estimate 在 reference 上的投影
138
+ # 公式: s_target = (<ŝ, s> / ||s||²) * s
139
+ dot_product = np.dot(estimate, reference) # <ŝ, s> (点积)
140
+ norm_s_squared = np.dot(reference, reference) # ||s||² (s的能量)
141
+
142
+ # 检查参考信号能量, 避免除以零
143
+ if norm_s_squared < epsilon:
144
+ # 如果参考信号几乎是静音, SI-SDR没有意义
145
+ return -np.inf # 返回负无穷或np.nan
146
+
147
+ alpha = dot_product / (norm_s_squared + epsilon) # 最佳缩放因子 α
148
+ s_target = alpha * reference
149
+
150
+ # 4. 计算误差/失真分量 (e_noise)
151
+ e_noise = estimate - s_target
152
+
153
+ # 5. 计算 SI-SDR
154
+ # SI-SDR = 10 * log10 ( ||s_target||² / ||e_noise||² )
155
+ power_s_target = np.sum(s_target**2) # ||s_target||²
156
+ power_e_noise = np.sum(e_noise**2) # ||e_noise||²
157
+
158
+ # 同样加上 epsilon 防止除以零
159
+ if power_e_noise < epsilon:
160
+ # 如果噪声能量极小, 说明匹配得非常好
161
+ return np.inf # 返回正无穷
162
+
163
+ si_sdr_val = 10 * np.log10(power_s_target / (power_e_noise + epsilon))
164
+
165
+ return si_sdr_val
166
+
167
+
168
+ if __name__ == "__main__":
169
+ # 生成测试信号
170
+ speech = np.random.randn(1000)
171
+ noise = np.random.randn(1000) * 0.1 # 较小的噪声
172
+ noisy = speech + noise
173
+
174
+ # 测试各种信噪比计算方法
175
+ print(f"SNR: {get_snr(speech, noise):.2f} dB")
176
+ print(f"Segmental SNR: {SegSNR(speech, noisy, 100, 50):.2f} dB")
177
+ print(f"PSNR: {PSNR(speech, noisy):.2f} dB")