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,380 @@
1
+ import sys
2
+ sys.path.append("..")
3
+ import random
4
+ import numpy as np
5
+ import soundfile as sf
6
+ import scipy.signal as signal
7
+ from scipy.signal import lfilter, freqz
8
+ import matplotlib.pyplot as plt
9
+ from deap import base, creator, tools, algorithms
10
+ from filter import EQFilter
11
+
12
+ # --- Configuration Parameters ---
13
+ SOURCE_AUDIO_PATH = "../data/white.wav"
14
+ TARGET_AUDIO_PATH = "../data/white_EQ.wav"
15
+ OUTPUT_MATCHED_AUDIO_PATH = "../data/white_matched.wav"
16
+
17
+ SR = 16000
18
+ NFFT = 1024
19
+ FREQ_NUM = NFFT // 2 + 1
20
+
21
+ # --- GA Configuration - 需要重点调整这些参数 ---
22
+ MAX_FILTERS = 10 # 尝试增加或减少, 取决于EQ预期复杂度
23
+ POPULATION_SIZE = 200 # 建议增加 (例如 100-200)
24
+ MAX_GENERATIONS = 150 # 建议增加 (例如 100-300, 甚至更多)
25
+ CXPB = 0.7 # 交叉概率
26
+ MUTPB_IND = 0.4 # 个体变异概率, 可以适当增加以增强探索
27
+ MUTPB_GENE = 0.15 # 基因变异概率, 可以适当增加
28
+
29
+ # 复杂度惩罚因子 - 关键调整参数!
30
+ # 初始可以尝试较小的值, 如果滤波器过多, 再逐渐增大
31
+ COMPLEXITY_PENALTY_FACTOR = 0.01 # 尝试不同的值: 0.001, 0.005, 0.01, 0.05, 0.1 等
32
+
33
+ # Filter Type Definitions (整数编码)
34
+ FILTER_TYPE_PEAK = 0
35
+ FILTER_TYPE_LOW_SHELF = 1
36
+ FILTER_TYPE_HIGH_SHELF = 2
37
+ AVAILABLE_FILTER_TYPES = [FILTER_TYPE_PEAK, FILTER_TYPE_LOW_SHELF, FILTER_TYPE_HIGH_SHELF]
38
+
39
+ FILTER_TYPE_MAP_INT_TO_STR = {
40
+ FILTER_TYPE_PEAK: 'peak',
41
+ FILTER_TYPE_LOW_SHELF: 'low_shelf',
42
+ FILTER_TYPE_HIGH_SHELF: 'high_shelf',
43
+ }
44
+ # 创建EQFilter实例
45
+ eq_filter = EQFilter(fs=SR)
46
+
47
+ FILTER_TYPE_MAP_INT_TO_FUNC = {
48
+ FILTER_TYPE_PEAK: eq_filter.PeakingFilter,
49
+ FILTER_TYPE_LOW_SHELF: eq_filter.LowshelfFilter,
50
+ FILTER_TYPE_HIGH_SHELF: eq_filter.HighshelfFilter,
51
+ }
52
+
53
+ # Parameter Bounds
54
+ FC_MIN, FC_MAX = 20, SR / 2 - 50
55
+ Q_MIN_PEAK, Q_MAX_PEAK = 0.3, 10.0
56
+ Q_MIN_SHELF, Q_MAX_SHELF = 0.3, 2.0
57
+ DBGAIN_MIN, DBGAIN_MAX = -25.0, 25.0 # 略微扩大增益范围
58
+
59
+ Q_BOUNDS_PER_TYPE = {
60
+ FILTER_TYPE_PEAK: (Q_MIN_PEAK, Q_MAX_PEAK),
61
+ FILTER_TYPE_LOW_SHELF: (Q_MIN_SHELF, Q_MAX_SHELF),
62
+ FILTER_TYPE_HIGH_SHELF: (Q_MIN_SHELF, Q_MAX_SHELF),
63
+ }
64
+
65
+ GENES_PER_FILTER_BLOCK = 5
66
+
67
+ creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
68
+ creator.create("Individual", list, fitness=creator.FitnessMin)
69
+
70
+ toolbox = base.Toolbox()
71
+
72
+
73
+ def generate_active_gene(): return random.randint(0, 1)
74
+ def generate_type_gene(): return random.choice(AVAILABLE_FILTER_TYPES)
75
+ def generate_fc_gene(): return random.uniform(FC_MIN, FC_MAX)
76
+
77
+
78
+ def generate_q_gene(filter_type_int):
79
+ q_min, q_max = Q_BOUNDS_PER_TYPE[filter_type_int]
80
+ return random.uniform(q_min, q_max)
81
+
82
+
83
+ def generate_dbgain_gene(): return random.uniform(DBGAIN_MIN, DBGAIN_MAX)
84
+
85
+
86
+ attribute_generators = []
87
+ for i in range(MAX_FILTERS):
88
+ toolbox.register(f"active_{i}", generate_active_gene)
89
+ attribute_generators.append(toolbox.__getattribute__(f"active_{i}"))
90
+ toolbox.register(f"type_{i}", generate_type_gene)
91
+ attribute_generators.append(toolbox.__getattribute__(f"type_{i}"))
92
+ toolbox.register(f"fc_{i}", generate_fc_gene)
93
+ attribute_generators.append(toolbox.__getattribute__(f"fc_{i}"))
94
+ attribute_generators.append(None)
95
+ toolbox.register(f"dbgain_{i}", generate_dbgain_gene)
96
+ attribute_generators.append(toolbox.__getattribute__(f"dbgain_{i}"))
97
+
98
+
99
+ def individual_creator():
100
+ chromosome = []
101
+ for i in range(MAX_FILTERS):
102
+ active = generate_active_gene()
103
+ type_val = generate_type_gene()
104
+ fc = generate_fc_gene()
105
+ q = generate_q_gene(type_val)
106
+ dbgain = generate_dbgain_gene()
107
+ chromosome.extend([active, type_val, fc, q, dbgain])
108
+ return creator.Individual(chromosome)
109
+
110
+
111
+ toolbox.register("individual", individual_creator)
112
+ toolbox.register("population", tools.initRepeat, list, toolbox.individual)
113
+
114
+
115
+ def get_magnitude_spectrum_db(audio, sr, n_fft):
116
+ # 使用 spectrogram 进行频谱估计, 并对时间帧平均
117
+ f_spec, t_spec, Sxx_spec = signal.spectrogram(audio, fs=sr, nperseg=n_fft, noverlap=n_fft // 4, scaling='spectrum', mode='magnitude')
118
+ avg_magnitude_spectrum_spec = np.mean(Sxx_spec, axis=1)
119
+ db_spectrum = 20 * np.log10(avg_magnitude_spectrum_spec + 1e-12)
120
+ return f_spec, db_spectrum
121
+
122
+
123
+ def get_single_filter_freq_response_db_from_coeffs(filter_params, num_freq_points, fs_proc):
124
+ # 为每个滤波器创建新的EQFilter实例, 使用正确的采样率
125
+ eq_filter_instance = EQFilter(fs=fs_proc)
126
+ filter_type = filter_params['type_int']
127
+ if filter_type == FILTER_TYPE_PEAK:
128
+ filter_func = eq_filter_instance.PeakingFilter
129
+ elif filter_type == FILTER_TYPE_LOW_SHELF:
130
+ filter_func = eq_filter_instance.LowshelfFilter
131
+ else: # HIGH_SHELF
132
+ filter_func = eq_filter_instance.HighshelfFilter
133
+
134
+ b, a = filter_func(fc=filter_params['fc'], Q=filter_params['q'], dBgain=filter_params['dBgain'])
135
+ w_native, h_native = freqz(b, a, worN=num_freq_points, fs=fs_proc)
136
+ response_db_native = 20 * np.log10(np.abs(h_native) + 1e-12)
137
+ return w_native, response_db_native
138
+
139
+
140
+ def get_combined_eq_response_db(active_filters_list, num_points_calc, fs_proc, freq_axis_target):
141
+ num_target_freq_bins = len(freq_axis_target)
142
+ combined_response_db = np.zeros(num_target_freq_bins)
143
+ if not active_filters_list:
144
+ return combined_response_db
145
+
146
+ for p_dict in active_filters_list:
147
+ w_native, individual_response_db_native = get_single_filter_freq_response_db_from_coeffs(
148
+ p_dict, num_points_calc, fs_proc
149
+ )
150
+ individual_response_db_interp = np.interp(
151
+ freq_axis_target, w_native, individual_response_db_native
152
+ )
153
+ combined_response_db += individual_response_db_interp
154
+ return combined_response_db
155
+
156
+
157
+ target_eq_shape_db_global = None
158
+ objective_freq_axis_global = None
159
+
160
+
161
+ def evaluate_individual(individual_chromosome):
162
+ global target_eq_shape_db_global, objective_freq_axis_global
163
+ if target_eq_shape_db_global is None or objective_freq_axis_global is None:
164
+ raise ValueError("全局目标频谱未设置!") # 中文注释
165
+
166
+ active_filters_params_list = []
167
+ num_active_filters = 0
168
+
169
+ for i in range(MAX_FILTERS):
170
+ base_idx = i * GENES_PER_FILTER_BLOCK
171
+ is_active = individual_chromosome[base_idx]
172
+
173
+ if is_active == 1:
174
+ num_active_filters += 1
175
+ filter_type_int = individual_chromosome[base_idx + 1]
176
+ fc_val = individual_chromosome[base_idx + 2]
177
+ q_val = individual_chromosome[base_idx + 3]
178
+ dbgain_val = individual_chromosome[base_idx + 4]
179
+
180
+ fc_val = np.clip(fc_val, FC_MIN, FC_MAX)
181
+ q_min_type, q_max_type = Q_BOUNDS_PER_TYPE[filter_type_int]
182
+ q_val = np.clip(q_val, q_min_type, q_max_type)
183
+ dbgain_val = np.clip(dbgain_val, DBGAIN_MIN, DBGAIN_MAX)
184
+
185
+ active_filters_params_list.append({
186
+ 'type_int': filter_type_int,
187
+ 'fc': fc_val,
188
+ 'q': q_val,
189
+ 'dBgain': dbgain_val,
190
+ 'fs': SR
191
+ })
192
+
193
+ if not active_filters_params_list:
194
+ achieved_eq_response_db = np.zeros_like(target_eq_shape_db_global)
195
+ else:
196
+ achieved_eq_response_db = get_combined_eq_response_db(
197
+ active_filters_params_list,
198
+ FREQ_NUM,
199
+ SR,
200
+ objective_freq_axis_global
201
+ )
202
+
203
+ error = np.sum((achieved_eq_response_db - target_eq_shape_db_global)**2)
204
+
205
+ # 调整复杂度惩罚项的计算方式, 使其与误差的量级更相关
206
+ # 例如, 如果误差本身就很大, 那么滤波器的数量惩罚可以相对小一些
207
+ # 或者, 如果目标EQ形状本身就很复杂(变化剧烈), 那么多用几个滤波器也是合理的
208
+ # penalty_scale = 1 + np.mean(np.abs(target_eq_shape_db_global)) # 基于目标EQ形状的平均绝对值
209
+ penalty_scale = np.sum(target_eq_shape_db_global**2) / len(target_eq_shape_db_global) if len(target_eq_shape_db_global) > 0 else 1.0
210
+ if penalty_scale < 1e-3:
211
+ penalty_scale = 1.0 # 避免除以过小的值或0
212
+
213
+ complexity_cost = COMPLEXITY_PENALTY_FACTOR * num_active_filters * (1 + penalty_scale * 0.1)
214
+
215
+ total_cost = error + complexity_cost
216
+ return (total_cost,)
217
+
218
+
219
+ toolbox.register("evaluate", evaluate_individual)
220
+ toolbox.register("mate", tools.cxTwoPoint)
221
+
222
+
223
+ def custom_mutate(individual, indpb_gene):
224
+ for i in range(len(individual)):
225
+ if random.random() < indpb_gene:
226
+ block_index = i // GENES_PER_FILTER_BLOCK
227
+ gene_type_in_block = i % GENES_PER_FILTER_BLOCK
228
+
229
+ current_filter_type_gene_idx = block_index * GENES_PER_FILTER_BLOCK + 1
230
+ current_filter_type = individual[current_filter_type_gene_idx]
231
+
232
+ if gene_type_in_block == 0: # Active gene
233
+ individual[i] = 1 - individual[i]
234
+ elif gene_type_in_block == 1: # Type gene
235
+ new_type = random.choice([t for t in AVAILABLE_FILTER_TYPES if t != individual[i]])
236
+ individual[i] = new_type
237
+ q_gene_idx = block_index * GENES_PER_FILTER_BLOCK + 3
238
+ individual[q_gene_idx] = generate_q_gene(new_type) # 根据新类型更新Q
239
+ elif gene_type_in_block == 2: # Fc gene
240
+ individual[i] = generate_fc_gene()
241
+ elif gene_type_in_block == 3: # Q gene
242
+ individual[i] = generate_q_gene(current_filter_type) # Q依赖于当前块的Type
243
+ elif gene_type_in_block == 4: # dBGain gene
244
+ individual[i] = generate_dbgain_gene()
245
+ return individual,
246
+
247
+
248
+ toolbox.register("mutate", custom_mutate, indpb_gene=MUTPB_GENE)
249
+ toolbox.register("select", tools.selTournament, tournsize=3) # 锦标赛选择, tournsize可调整
250
+
251
+
252
+ def main_ga():
253
+ global target_eq_shape_db_global, objective_freq_axis_global
254
+
255
+ source_audio, sr = sf.read(SOURCE_AUDIO_PATH)
256
+ target_audio, sr = sf.read(TARGET_AUDIO_PATH)
257
+ assert sr == SR, "采样率不匹配"
258
+ assert source_audio.ndim == 1, "源音频必须是单声道"
259
+
260
+ source_freq_axis, source_db_spectrum = get_magnitude_spectrum_db(
261
+ source_audio, SR, NFFT
262
+ )
263
+ target_freq_axis, target_db_spectrum = get_magnitude_spectrum_db(
264
+ target_audio, SR, NFFT
265
+ )
266
+ assert np.array_equal(source_freq_axis, target_freq_axis), "源频谱和目标频谱的频率轴不一致"
267
+
268
+ target_eq_shape_db_global = target_db_spectrum - source_db_spectrum
269
+ objective_freq_axis_global = source_freq_axis
270
+
271
+ print(f"运行遗传算法 (种群: {POPULATION_SIZE}, 迭代: {MAX_GENERATIONS}, 最大滤波器数: {MAX_FILTERS})...") # 中文注释
272
+ population = toolbox.population(n=POPULATION_SIZE)
273
+ hall_of_fame = tools.HallOfFame(1) # 只记录最好的一个
274
+
275
+ # 设置统计信息
276
+ stats = tools.Statistics(lambda ind: ind.fitness.values)
277
+ stats.register("avg", np.mean)
278
+ stats.register("std", np.std)
279
+ stats.register("min", np.min)
280
+ stats.register("max", np.max)
281
+
282
+ # 运行GA
283
+ final_pop, logbook = algorithms.eaSimple(
284
+ population,
285
+ toolbox,
286
+ cxpb=CXPB,
287
+ mutpb=MUTPB_IND, # 个体变异概率
288
+ ngen=MAX_GENERATIONS,
289
+ stats=stats,
290
+ halloffame=hall_of_fame,
291
+ verbose=True # 打印每代统计信息
292
+ )
293
+
294
+ best_individual_chromosome = hall_of_fame[0]
295
+ print(f"\n最优个体适应度 (误差+惩罚): {best_individual_chromosome.fitness.values[0]:.4f}") # 中文注释
296
+
297
+ # 解码最优个体
298
+ optimized_eq_params_list = []
299
+ num_active_found = 0
300
+ print("\n--- Decoded Optimal EQ Filter Parameters ---") # 英文输出标题
301
+ for i in range(MAX_FILTERS):
302
+ base_idx = i * GENES_PER_FILTER_BLOCK
303
+ is_active = best_individual_chromosome[base_idx]
304
+ if is_active == 1:
305
+ num_active_found += 1
306
+ filter_type_int = best_individual_chromosome[base_idx + 1]
307
+ fc_val = best_individual_chromosome[base_idx + 2]
308
+ q_val = best_individual_chromosome[base_idx + 3]
309
+ dbgain_val = best_individual_chromosome[base_idx + 4]
310
+
311
+ param_dict = {
312
+ 'type': FILTER_TYPE_MAP_INT_TO_STR[filter_type_int],
313
+ 'fc': round(fc_val, 2),
314
+ 'q': round(q_val, 3),
315
+ 'dBgain': round(dbgain_val, 2),
316
+ 'fs': SR
317
+ }
318
+ optimized_eq_params_list.append(param_dict)
319
+ print(param_dict) # 打印每个找到的滤波器参数
320
+
321
+ if not optimized_eq_params_list:
322
+ print("Warning: Genetic algorithm did not find any active filters.")
323
+
324
+ # 应用EQ并保存 (如果找到了滤波器)
325
+ if optimized_eq_params_list and OUTPUT_MATCHED_AUDIO_PATH:
326
+ print(f"\nApplying optimized EQ to source audio and saving to {OUTPUT_MATCHED_AUDIO_PATH}...")
327
+
328
+ def apply_eq_to_signal_structural(audio, eq_params_list_decoded, fs):
329
+ processed_audio = np.copy(audio)
330
+ eq_filter_instance = EQFilter(fs=fs)
331
+
332
+ for p_dict_decoded in eq_params_list_decoded:
333
+ if p_dict_decoded['type'] == 'peak':
334
+ filter_func = eq_filter_instance.PeakingFilter
335
+ elif p_dict_decoded['type'] == 'low_shelf':
336
+ filter_func = eq_filter_instance.LowshelfFilter
337
+ else: # high_shelf
338
+ filter_func = eq_filter_instance.HighshelfFilter
339
+
340
+ b, a = filter_func(fc=p_dict_decoded['fc'], Q=p_dict_decoded['q'], dBgain=p_dict_decoded['dBgain'])
341
+ processed_audio = lfilter(b, a, processed_audio)
342
+ return processed_audio
343
+
344
+ source_audio_matched = apply_eq_to_signal_structural(source_audio, optimized_eq_params_list, SR)
345
+ sf.write(OUTPUT_MATCHED_AUDIO_PATH, source_audio_matched, SR)
346
+
347
+ # 生成对比图
348
+ print("Generating comparison plot...") # 英文输出
349
+ decoded_active_filters_for_eval = []
350
+ for p_dict in optimized_eq_params_list:
351
+ type_str_to_int_map = {v: k for k, v in FILTER_TYPE_MAP_INT_TO_STR.items()}
352
+ decoded_active_filters_for_eval.append({
353
+ 'type_int': type_str_to_int_map[p_dict['type']],
354
+ 'fc': p_dict['fc'],
355
+ 'q': p_dict['q'],
356
+ 'dBgain': p_dict['dBgain']
357
+ })
358
+
359
+ achieved_eq_response_for_sum_db = get_combined_eq_response_db(
360
+ decoded_active_filters_for_eval, FREQ_NUM, SR, objective_freq_axis_global
361
+ )
362
+ source_plus_achieved_eq_db = source_db_spectrum + achieved_eq_response_for_sum_db
363
+
364
+ plt.figure(figsize=(12, 7))
365
+ plt.semilogx(objective_freq_axis_global, source_db_spectrum, label='Source Audio Spectrum', alpha=0.8, color='deepskyblue')
366
+ plt.semilogx(objective_freq_axis_global, target_db_spectrum, label='Target Audio Spectrum', alpha=0.8, color='coral')
367
+ plt.semilogx(objective_freq_axis_global, source_plus_achieved_eq_db, label='Source Spectrum + Matched EQ', alpha=0.8, color='limegreen')
368
+
369
+ plt.title(f'EQ Matching Result ({num_active_found} active filters) - {SR}Hz')
370
+ plt.xlabel('Frequency (Hz)')
371
+ plt.ylabel('Magnitude (dB)')
372
+ plt.legend(loc='best')
373
+ plt.xscale('log')
374
+ plt.grid(True, ls="--", alpha=0.4)
375
+ plt.tight_layout()
376
+ plt.savefig("eq_matching_plot_3curves.png")
377
+
378
+
379
+ if __name__ == '__main__':
380
+ main_ga()
@@ -0,0 +1,75 @@
1
+ '''
2
+ Author: 凌逆战 | Never
3
+ Date: 2025-08-04 21:49:05
4
+ Description: 自动EQ补偿
5
+ '''
6
+ import os
7
+ import numpy as np
8
+ import librosa
9
+ import soundfile as sf
10
+ import pandas
11
+ import matplotlib.pyplot as plt
12
+
13
+ np.set_printoptions(precision=8)
14
+ np.set_printoptions(suppress=True) # 打印不使用科学计数法
15
+
16
+
17
+ def compute_frequency_eq(reference_audio, target_audio, sample_rate, fft_size, window_size, plot_results=False):
18
+ freq_bins = np.fft.rfftfreq(fft_size, d=1.0 / sample_rate) # [0, 31.25, 62.5,.....]
19
+
20
+ stft_reference = librosa.stft(reference_audio, n_fft=fft_size, hop_length=window_size // 2, win_length=window_size, window="hann")
21
+ stft_target = librosa.stft(target_audio, n_fft=fft_size, hop_length=window_size // 2, win_length=window_size, window="hann")
22
+ magnitude_reference, magnitude_target = np.abs(stft_reference), np.abs(stft_target) # (F,T)
23
+ # 求时间平均, 频响曲线 Frequency_Response_curve
24
+ reference_response = np.mean(magnitude_reference, axis=1)
25
+ target_response = np.mean(magnitude_target, axis=1)
26
+
27
+ reference_response_db = 20 * np.log10(reference_response) # 取对数幅度谱, 以便更好地可视化
28
+ target_response_db = 20 * np.log10(target_response) # 取对数幅度谱, 以便更好地可视化
29
+
30
+ eq_curve = target_response_db - reference_response_db # 补偿曲线 (28208, 1)
31
+ # print("补偿EQ曲线: ", len(eq_curve), np.array2string(np.power(10, eq_curve / 20), separator=', '))
32
+
33
+ if plot_results:
34
+ plt.figure(figsize=(10, 5))
35
+ plt.plot(freq_bins, target_response_db, label="Target Response")
36
+ plt.plot(freq_bins, eq_curve, label="EQ Curve")
37
+ compensated_response = reference_response_db + eq_curve # 补偿后的曲线
38
+ plt.plot(freq_bins, compensated_response, label="Compensated Response")
39
+ plt.xlabel('Frequency (Hz)')
40
+ plt.ylabel('Amplitude (dB)')
41
+ plt.title('Frequency Response Compensation')
42
+ plt.grid(True)
43
+ plt.legend()
44
+ plt.xscale('log')
45
+ plt.grid(True, ls="--", alpha=0.4)
46
+ plt.tight_layout()
47
+ # plt.show()
48
+ plt.savefig(f"./frequency_eq_fft{window_size}.png")
49
+
50
+ # 拿到EQ之后我们对音频进行EQ补偿
51
+ reference_phase = np.angle(stft_reference) # (F,T)
52
+ for freq_idx in range(magnitude_reference.shape[0]):
53
+ magnitude_reference[freq_idx, :] *= np.power(10, eq_curve[freq_idx] / 20)
54
+ compensated_spectrum = magnitude_reference * np.exp(1.0j * reference_phase)
55
+ compensated_audio = librosa.istft(compensated_spectrum, hop_length=window_size // 2, win_length=window_size, n_fft=fft_size, window="hann")
56
+
57
+ return eq_curve, compensated_audio
58
+
59
+
60
+ if __name__ == "__main__":
61
+ SAMPLE_RATE = 16000
62
+ WINDOW_SIZE = FFT_SIZE = 512
63
+ reference_audio_path = "../data/white.wav"
64
+ target_audio_path = "../data/white_EQ.wav"
65
+
66
+ # 读取音频文件
67
+ reference_audio, _ = sf.read(reference_audio_path, dtype='float32')
68
+ target_audio, _ = sf.read(target_audio_path, dtype='float32')
69
+
70
+ eq_curve, compensated_audio = compute_frequency_eq(
71
+ reference_audio, target_audio,
72
+ SAMPLE_RATE, FFT_SIZE, WINDOW_SIZE,
73
+ plot_results=False
74
+ )
75
+ sf.write("../data/frequency_eq.wav", compensated_audio, SAMPLE_RATE)
@@ -0,0 +1,101 @@
1
+ # neverlib.filter
2
+
3
+ 本项目包含音频滤波器的实现和自动EQ匹配算法, 主要基于 scipy.signal 进行封装和扩展, 提供便捷的音频滤波器设计、处理功能以及智能EQ补偿解决方案。
4
+
5
+ ## 主要功能
6
+
7
+ ### 滤波器类型
8
+ - 低通滤波器 (Low Pass Filter, LPF)
9
+ - 高通滤波器 (High Pass Filter, HPF)
10
+ - 带通滤波器 (Band Pass Filter, BPF)
11
+ - 恒定裙边增益模式 (constant skirt gain, peak gain = Q)
12
+ - 恒定 0dB 峰值增益模式 (constant 0 dB peak gain)
13
+ - 陷波滤波器 (Notch Filter)
14
+ - 全通滤波器 (All Pass Filter, APF)
15
+ - 峰值滤波器 (Peaking EQ)
16
+ - 低切滤波器 (Low Shelf Filter)
17
+ - 高切滤波器 (High Shelf Filter)
18
+
19
+ ### 核心文件说明
20
+ - `filters.py`: 提供 EQFilter 类, 包含多种滤波器的设计和实现
21
+ - `biquad.py`: 二阶节(Biquad)滤波器的实现, 支持逐点处理
22
+ - `common.py`: 基础滤波器函数, 提供 numpy/scipy 和 torch 版本
23
+
24
+ ### 自动EQ匹配算法 (AudoEQ/)
25
+ 提供多种智能EQ匹配算法, 可以自动分析两个音频文件的频谱差异并生成最优的EQ补偿参数:
26
+
27
+ #### 🧬 基于优化算法的EQ匹配
28
+ - `auto_eq_ga_basic.py`: **基础遗传算法** - 使用DEAP库实现, 代码简洁, 适合学习和快速原型
29
+ - `auto_eq_ga_advanced.py`: **高级遗传算法** - 面向对象设计, 包含日志、检查点、早停等生产级功能
30
+ - `auto_eq_de.py`: **差分进化算法** - 使用scipy优化, 全局收敛性好, 适合高精度匹配
31
+
32
+ #### 📊 基于频谱分析的EQ匹配
33
+ - `auto_eq_spectral_direct.py`: **频谱直接补偿** - 基于STFT频谱分析, 直接计算频谱差异, 速度最快
34
+
35
+ 详细使用说明请参考 `AudoEQ/README.md`
36
+
37
+ ## 使用说明
38
+
39
+ 对于基础滤波需求, 推荐直接使用 scipy.signal 的原生函数:
40
+ ```python
41
+ from scipy import signal
42
+
43
+ # 设计巴特沃斯滤波器
44
+ b, a = signal.butter(N=2, Wn=100, btype='high', fs=16000)
45
+ # 应用滤波器
46
+ filtered = signal.lfilter(b, a, audio)
47
+ ```
48
+
49
+ 对于需要批量处理或自定义参数的场景, 可以使用本库的封装:
50
+ ```python
51
+ from neverlib.filter import EQFilter, BiquadFilter
52
+
53
+ # 使用 EQFilter
54
+ eq = EQFilter(fs=16000)
55
+ b, a = eq.LowpassFilter(fc=300, Q=0.707)
56
+
57
+ # 使用 BiquadFilter 进行逐点处理
58
+ biquad = BiquadFilter(b, a)
59
+ output = [biquad.process(x) for x in input_signal]
60
+ ```
61
+
62
+ ### 自动EQ匹配快速开始
63
+
64
+ 对于需要自动EQ匹配的场景, 可以直接运行AudoEQ中的脚本:
65
+
66
+ ```bash
67
+ # 快速频谱匹配(推荐入门)
68
+ cd AudoEQ
69
+ python auto_eq_spectral_direct.py
70
+
71
+ # 高精度优化匹配
72
+ python auto_eq_de.py # 差分进化算法
73
+ python auto_eq_ga_basic.py # 基础遗传算法
74
+ python auto_eq_ga_advanced.py # 高级遗传算法
75
+ ```
76
+
77
+ 使用前请修改脚本中的音频文件路径:
78
+ ```python
79
+ SOURCE_AUDIO_PATH = "path/to/source.wav" # 源音频
80
+ TARGET_AUDIO_PATH = "path/to/target.wav" # 目标音频
81
+ OUTPUT_MATCHED_AUDIO_PATH = "path/to/output.wav" # 输出音频
82
+ ```
83
+
84
+ ## 详细教程
85
+
86
+ ### 滤波器教程
87
+ 请参考 Documents/filter/ 目录下的 Jupyter notebooks:
88
+ - `filter_family.ipynb`: 各类滤波器的设计和频率响应示例
89
+ - `biquad.ipynb`: 二阶节滤波器的实现和验证
90
+ - `scipy_filter_family.ipynb`: scipy 原生滤波器的使用示例
91
+
92
+ ### 自动EQ匹配教程
93
+ 请参考 `AudoEQ/README.md` 了解:
94
+ - 各种EQ匹配算法的详细介绍和对比
95
+ - 参数调优指南和性能优化建议
96
+ - 常见问题解决方案和故障排除
97
+
98
+ ## 参考资料
99
+ - [Audio-EQ-Cookbook](http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt)
100
+ - [beqdesigner](https://github.com/3ll3d00d/beqdesigner)
101
+ - [torch-audiomentations](https://github.com/iver56/torch-audiomentations)
@@ -1,3 +1,8 @@
1
+ '''
2
+ Author: 凌逆战 | Never
3
+ Date: 2025-03-17 19:23:33
4
+ Description:
5
+ '''
1
6
  """
2
7
  节省路径
3
8
  from neverlib.filter import common
@@ -5,3 +10,5 @@ from neverlib.filter import common
5
10
  from neverlib.filter.common import *
6
11
  """
7
12
  from .common import *
13
+ from .core import *
14
+ from .biquad import *
@@ -0,0 +1,45 @@
1
+ '''
2
+ Author: 凌逆战 | Never
3
+ Date: 2025-08-04 16:06:46
4
+ encoding: utf-8
5
+ Description: 双二阶滤波器
6
+ 公式: y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2]
7
+ '''
8
+ import numpy as np
9
+ from scipy import signal
10
+
11
+
12
+ class BiquadFilter():
13
+ def __init__(self, b, a):
14
+ self.b0, self.b1, self.b2 = b
15
+ _, self.a1, self.a2 = a
16
+ self.x1, self.x2, self.y1, self.y2 = 0, 0, 0, 0
17
+
18
+ def process(self, x):
19
+ y = (self.b0 * x + self.b1 * self.x1 + self.b2 * self.x2
20
+ - self.a1 * self.y1 - self.a2 * self.y2)
21
+ self.x2 = self.x1
22
+ self.x1 = x
23
+ self.y2 = self.y1
24
+ self.y1 = y
25
+ return y
26
+
27
+
28
+ if __name__ == "__main__":
29
+ # 设计高通滤波器系数
30
+ fs = 16000 # 采样率
31
+ fc = 70 # 截止频率(Hz)
32
+ # 输入信号
33
+ input_signal = [0.5, 0.8, 1.0, 0.7, -0.2, -0.6, -0.8, -0.3, -0.3, -0.3, -0.3]
34
+
35
+ # 定义一个二阶高通滤波器(巴特沃斯)
36
+ b, a = signal.butter(2, fc, btype='highpass', analog=False, output='ba', fs=fs)
37
+
38
+ # 创建双二阶滤波器实例
39
+ biquad_filter = BiquadFilter(b, a)
40
+ output_signal_biquad = [biquad_filter.process(x) for x in input_signal]
41
+
42
+ # 使用signal.butter进行前向滤波
43
+ output_signal_butter = signal.lfilter(b, a, input_signal)
44
+
45
+ print("Equal Outputs:", np.allclose(output_signal_biquad, output_signal_butter, atol=1e-08)) # True
neverlib/filter/common.py CHANGED
@@ -1,9 +1,8 @@
1
- # -*- coding:utf-8 -*-
2
- # Author:凌逆战 | Never
3
- # Date: 2024/10/16
4
- """
5
- 一些基础和通用的滤波器
6
- """
1
+ '''
2
+ Author: 凌逆战 | Never
3
+ Date: 2025-08-05 23:42:08
4
+ Description: 一些基础和通用的滤波器
5
+ '''
7
6
  import torch
8
7
  import numpy as np
9
8
  from scipy import signal