neverlib 0.2.3__py3-none-any.whl → 0.2.4__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.
- neverlib/.history/Docs/audio_aug/test_snr_20250806011311.py +0 -0
- neverlib/.history/Docs/audio_aug/test_snr_20250806011331.py +75 -0
- neverlib/.history/Docs/audio_aug/test_snr_20250806011342.py +57 -0
- neverlib/.history/Docs/audio_aug/test_snr_20250806011352.py +57 -0
- neverlib/.history/Docs/audio_aug/test_snr_20250806011403.py +57 -0
- neverlib/.history/Docs/audio_aug/test_snr_20250806011413.py +57 -0
- neverlib/.history/Docs/audio_aug/test_snr_20250806011435.py +55 -0
- neverlib/.history/Docs/vad/1_20250810032405.py +0 -0
- neverlib/.history/Docs/vad/1_20250810032417.py +39 -0
- neverlib/.history/audio_aug/audio_aug_20250806010451.py +125 -0
- neverlib/.history/audio_aug/audio_aug_20250806010750.py +138 -0
- neverlib/.history/audio_aug/audio_aug_20250806010759.py +140 -0
- neverlib/.history/audio_aug/audio_aug_20250806010803.py +140 -0
- neverlib/.history/audio_aug/audio_aug_20250806010809.py +140 -0
- neverlib/.history/audio_aug/audio_aug_20250806011108.py +140 -0
- neverlib/.history/dataAnalyze/__init___20250806204125.py +14 -0
- neverlib/.history/dataAnalyze/__init___20250806204139.py +14 -0
- neverlib/.history/dataAnalyze/__init___20250806204159.py +14 -0
- neverlib/.history/filter/__init___20250820103351.py +70 -0
- neverlib/.history/filter/__init___20250821102348.py +70 -0
- neverlib/.history/filter/__init___20250821102405.py +14 -0
- neverlib/.history/filter/auto_eq/__init___20250819213121.py +36 -0
- neverlib/.history/filter/auto_eq/__init___20250821102241.py +36 -0
- neverlib/.history/filter/auto_eq/__init___20250821102259.py +36 -0
- neverlib/.history/filter/auto_eq/__init___20250821102307.py +36 -0
- neverlib/.history/filter/auto_eq/__init___20250821102310.py +36 -0
- neverlib/.history/filter/auto_eq/__init___20250821102318.py +36 -0
- neverlib/.history/filter/auto_eq/__init___20250821102507.py +36 -0
- neverlib/{filter/AudoEQ/auto_eq_de.py → .history/filter/auto_eq/de_eq_20250820103848.py} +1 -1
- neverlib/.history/filter/auto_eq/de_eq_20250821102422.py +360 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820140732.py +75 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820140745.py +75 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820140816.py +75 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820140938.py +77 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820141003.py +77 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820141006.py +77 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820141019.py +77 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820141049.py +77 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820141211.py +77 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820141227.py +77 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820141311.py +78 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820141340.py +78 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820141712.py +78 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820141733.py +78 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250820141755.py +78 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250821102434.py +76 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250821102500.py +76 -0
- neverlib/.history/filter/auto_eq/freq_eq_20250821102502.py +76 -0
- neverlib/{filter/AudoEQ/auto_eq_ga_basic.py → .history/filter/auto_eq/ga_eq_basic_20250820102957.py} +1 -1
- neverlib/.history/filter/auto_eq/ga_eq_basic_20250820113054.py +380 -0
- neverlib/.history/filter/auto_eq/ga_eq_basic_20250820113150.py +380 -0
- neverlib/.history/filter/auto_eq/ga_eq_basic_20250820113520.py +385 -0
- neverlib/.history/filter/auto_eq/ga_eq_basic_20250820113525.py +385 -0
- neverlib/.history/filter/auto_eq/ga_eq_basic_20250821102212.py +385 -0
- neverlib/.history/metrics/dnsmos_20250806001612.py +160 -0
- neverlib/.history/metrics/dnsmos_20250815180659.py +160 -0
- neverlib/.history/metrics/dnsmos_20250815180701.py +158 -0
- neverlib/.history/metrics/dnsmos_20250815181321.py +154 -0
- neverlib/.history/metrics/dnsmos_20250815181327.py +154 -0
- neverlib/.history/metrics/dnsmos_20250815181331.py +154 -0
- neverlib/.history/metrics/dnsmos_20250815181620.py +154 -0
- neverlib/.history/metrics/dnsmos_20250815181631.py +154 -0
- neverlib/.history/metrics/dnsmos_20250815181742.py +154 -0
- neverlib/.history/metrics/dnsmos_20250815181824.py +153 -0
- neverlib/.history/metrics/dnsmos_20250815181834.py +153 -0
- neverlib/.history/metrics/dnsmos_20250815181922.py +153 -0
- neverlib/.history/metrics/dnsmos_20250815182011.py +147 -0
- neverlib/.history/metrics/dnsmos_20250815182036.py +144 -0
- neverlib/.history/metrics/dnsmos_20250815182936.py +143 -0
- neverlib/.history/metrics/dnsmos_20250815182942.py +143 -0
- neverlib/.history/metrics/dnsmos_20250815183032.py +137 -0
- neverlib/.history/metrics/dnsmos_20250815183101.py +144 -0
- neverlib/.history/metrics/dnsmos_20250815183121.py +144 -0
- neverlib/.history/metrics/dnsmos_20250815183123.py +143 -0
- neverlib/.history/metrics/dnsmos_20250815183214.py +143 -0
- neverlib/.history/metrics/dnsmos_20250815183240.py +143 -0
- neverlib/.history/metrics/dnsmos_20250815183248.py +144 -0
- neverlib/.history/metrics/dnsmos_20250815183407.py +142 -0
- neverlib/.history/metrics/dnsmos_20250815183409.py +142 -0
- neverlib/.history/metrics/dnsmos_20250815183431.py +142 -0
- neverlib/.history/metrics/dnsmos_20250815183507.py +140 -0
- neverlib/.history/metrics/dnsmos_20250815183513.py +139 -0
- neverlib/.history/metrics/dnsmos_20250815183618.py +139 -0
- neverlib/.history/metrics/dnsmos_20250815183709.py +140 -0
- neverlib/.history/metrics/dnsmos_20250815183756.py +137 -0
- neverlib/.history/metrics/dnsmos_20250815183815.py +128 -0
- neverlib/.history/metrics/dnsmos_20250815183827.py +129 -0
- neverlib/.history/metrics/dnsmos_20250815183913.py +117 -0
- neverlib/.history/metrics/dnsmos_20250815183914.py +117 -0
- neverlib/.history/metrics/dnsmos_20250815184003.py +118 -0
- neverlib/.history/metrics/dnsmos_20250815184040.py +118 -0
- neverlib/.history/metrics/dnsmos_20250815184049.py +118 -0
- neverlib/.history/metrics/dnsmos_20250815184104.py +117 -0
- neverlib/.history/metrics/dnsmos_20250815184200.py +117 -0
- neverlib/.history/metrics/lpc_lsp_metric_20250816015944.py +128 -0
- neverlib/.history/metrics/lpc_lsp_metric_20250816020142.py +128 -0
- neverlib/.history/metrics/lpc_lsp_metric_20250816020156.py +128 -0
- neverlib/.history/metrics/lpc_lsp_metric_20250816020554.py +130 -0
- neverlib/.history/metrics/lpc_lsp_metric_20250816020600.py +125 -0
- neverlib/.history/metrics/lpc_lsp_metric_20250816020631.py +120 -0
- neverlib/.history/metrics/lpc_lsp_metric_20250816020746.py +118 -0
- neverlib/.history/metrics/lpc_me_20250816013111.py +0 -0
- neverlib/.history/metrics/lpc_me_20250816013129.py +121 -0
- neverlib/.history/metrics/lpc_me_20250816015430.py +103 -0
- neverlib/.history/metrics/lpc_me_20250816015535.py +96 -0
- neverlib/.history/metrics/lpc_me_20250816015542.py +96 -0
- neverlib/.history/metrics/lpc_me_20250816015636.py +97 -0
- neverlib/.history/metrics/lpc_me_20250816015658.py +104 -0
- neverlib/.history/metrics/lpc_me_20250816015703.py +100 -0
- neverlib/.history/metrics/lpc_me_20250816015945.py +128 -0
- neverlib/.history/metrics/snr_20250806010538.py +177 -0
- neverlib/.history/metrics/snr_20250806211634.py +184 -0
- neverlib/.history/metrics/spec_20250805234209.py +45 -0
- neverlib/.history/metrics/spec_20250816135530.py +11 -0
- neverlib/.history/metrics/spec_20250816135654.py +16 -0
- neverlib/.history/metrics/spec_20250816135736.py +68 -0
- neverlib/.history/metrics/spec_20250816135904.py +75 -0
- neverlib/.history/metrics/spec_20250816135921.py +82 -0
- neverlib/.history/metrics/spec_20250816140111.py +82 -0
- neverlib/.history/metrics/spec_20250816140543.py +136 -0
- neverlib/.history/metrics/spec_20250816140559.py +172 -0
- neverlib/.history/metrics/spec_20250816140602.py +172 -0
- neverlib/.history/metrics/spec_20250816140608.py +172 -0
- neverlib/.history/metrics/spec_20250816140654.py +148 -0
- neverlib/.history/metrics/spec_20250816140705.py +144 -0
- neverlib/.history/metrics/spec_20250816140755.py +138 -0
- neverlib/.history/metrics/spec_20250816140823.py +170 -0
- neverlib/.history/metrics/spec_20250816140832.py +170 -0
- neverlib/.history/metrics/spec_20250816140833.py +170 -0
- neverlib/.history/metrics/spec_20250816140922.py +147 -0
- neverlib/.history/metrics/spec_20250816141148.py +107 -0
- neverlib/.history/metrics/spec_20250816141219.py +123 -0
- neverlib/.history/metrics/spec_20250816141732.py +178 -0
- neverlib/.history/metrics/spec_20250816141740.py +178 -0
- neverlib/.history/metrics/spec_20250816142030.py +178 -0
- neverlib/.history/metrics/spec_20250816142107.py +135 -0
- neverlib/.history/metrics/spec_20250816142126.py +135 -0
- neverlib/.history/metrics/spec_20250816142410.py +135 -0
- neverlib/.history/metrics/spec_20250816142415.py +136 -0
- neverlib/.history/metrics/spec_metric_20250816135156.py +0 -0
- neverlib/.history/metrics/spec_metric_20250816135226.py +5 -0
- neverlib/.history/metrics/spec_metric_20250816135227.py +10 -0
- neverlib/.history/metrics/spec_metric_20250816135306.py +15 -0
- neverlib/.history/metrics/spec_metric_20250816135442.py +31 -0
- neverlib/.history/metrics/spec_metric_20250816135448.py +31 -0
- neverlib/.history/metrics/spec_metric_20250816135520.py +29 -0
- neverlib/.history/metrics/spec_metric_20250816135537.py +63 -0
- neverlib/.history/metrics/spec_metric_20250816135653.py +65 -0
- neverlib/.history/vad/PreProcess_20250805234211.py +63 -0
- neverlib/.history/vad/PreProcess_20250809232455.py +63 -0
- neverlib/.history/vad/PreProcess_20250816020725.py +66 -0
- neverlib/.history/vad/VAD_Silero_20250805234211.py +50 -0
- neverlib/.history/vad/VAD_Silero_20250809232456.py +50 -0
- neverlib/.history/vad/VAD_WebRTC_20250805234211.py +61 -0
- neverlib/.history/vad/VAD_WebRTC_20250809232456.py +61 -0
- neverlib/.history/vad/VAD_funasr_20250805234211.py +54 -0
- neverlib/.history/vad/VAD_funasr_20250809232456.py +54 -0
- neverlib/.history/vad/VAD_vadlib_20250805234211.py +70 -0
- neverlib/.history/vad/VAD_vadlib_20250809232455.py +70 -0
- neverlib/.history/vad/VAD_whisper_20250805234211.py +55 -0
- neverlib/.history/vad/VAD_whisper_20250809232456.py +55 -0
- neverlib/.specstory/.what-is-this.md +69 -0
- neverlib/.specstory/history/2025-08-05_17-06Z-/350/277/231/344/270/200/346/255/245/347/232/204/347/233/256/347/232/204/346/230/257/344/273/200/344/271/210.md +424 -0
- neverlib/Docs/audio_aug/test_snr.py +55 -0
- neverlib/audio_aug/HarmonicDistortion.py +79 -0
- neverlib/audio_aug/TFDrop.py +41 -0
- neverlib/audio_aug/TFMask.py +56 -0
- neverlib/audio_aug/audio_aug.py +16 -1
- neverlib/audio_aug/clip_aug.py +41 -0
- neverlib/audio_aug/coder_aug.py +209 -0
- neverlib/audio_aug/coder_aug2.py +118 -0
- neverlib/audio_aug/loss_packet_aug.py +103 -0
- neverlib/audio_aug/quant_aug.py +78 -0
- neverlib/data_analyze/__init__.py +14 -0
- neverlib/filter/auto_eq/__init__.py +36 -0
- neverlib/filter/auto_eq/de_eq.py +360 -0
- neverlib/filter/auto_eq/freq_eq.py +76 -0
- neverlib/filter/{AudoEQ/auto_eq_ga_advanced.py → auto_eq/ga_eq_advanced.py} +1 -1
- neverlib/filter/auto_eq/ga_eq_basic.py +385 -0
- neverlib/metrics/dnsmos.py +58 -101
- neverlib/metrics/lpc_lsp.py +118 -0
- neverlib/metrics/snr.py +11 -4
- neverlib/metrics/spec.py +136 -45
- neverlib/utils/utils.py +17 -14
- neverlib/vad/PreProcess.py +5 -2
- neverlib/vad/VAD_Silero.py +1 -1
- neverlib/vad/VAD_WebRTC.py +1 -1
- neverlib/vad/VAD_funasr.py +1 -1
- neverlib/vad/VAD_vadlib.py +1 -1
- neverlib/vad/VAD_whisper.py +1 -1
- {neverlib-0.2.3.dist-info → neverlib-0.2.4.dist-info}/METADATA +1 -1
- neverlib-0.2.4.dist-info/RECORD +229 -0
- neverlib-0.2.3.dist-info/RECORD +0 -53
- /neverlib/{dataAnalyze/__init__.py → .history/dataAnalyze/__init___20250805234204.py} +0 -0
- /neverlib/{filter/AudoEQ/auto_eq_spectral_direct.py → .history/filter/auto_eq/freq_eq_20250805234206.py} +0 -0
- /neverlib/{dataAnalyze → data_analyze}/README.md +0 -0
- /neverlib/{dataAnalyze → data_analyze}/dataset_analyzer.py +0 -0
- /neverlib/{dataAnalyze → data_analyze}/quality_metrics.py +0 -0
- /neverlib/{dataAnalyze → data_analyze}/rms_distrubution.py +0 -0
- /neverlib/{dataAnalyze → data_analyze}/spectral_analysis.py +0 -0
- /neverlib/{dataAnalyze → data_analyze}/statistics.py +0 -0
- /neverlib/{dataAnalyze → data_analyze}/temporal_features.py +0 -0
- /neverlib/{dataAnalyze → data_analyze}/visualization.py +0 -0
- /neverlib/filter/{AudoEQ → auto_eq}/README.md +0 -0
- {neverlib-0.2.3.dist-info → neverlib-0.2.4.dist-info}/WHEEL +0 -0
- {neverlib-0.2.3.dist-info → neverlib-0.2.4.dist-info}/licenses/LICENSE +0 -0
- {neverlib-0.2.3.dist-info → neverlib-0.2.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# -*- coding:utf-8 -*-
|
|
2
|
+
# Author: AI Assistant based on User's Demand
|
|
3
|
+
# Date: 2023-10-27 (Using Differential Evolution)
|
|
4
|
+
# Modified: 2025-01-05 (Adapted for new filters.py structure)
|
|
5
|
+
import sys
|
|
6
|
+
sys.path.append("..")
|
|
7
|
+
import numpy as np
|
|
8
|
+
import librosa
|
|
9
|
+
import soundfile as sf
|
|
10
|
+
from scipy import signal as sp_signal
|
|
11
|
+
from scipy import optimize as sp_optimize # Keep for potential 'polish' if not using internal
|
|
12
|
+
from scipy.optimize import differential_evolution # Import differential_evolution
|
|
13
|
+
import warnings
|
|
14
|
+
import matplotlib.pyplot as plt
|
|
15
|
+
from neverlib.filter import EQFilter
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_filter_function(filter_type, fs):
|
|
19
|
+
"""获取滤波器函数, 返回配置好采样率的EQFilter实例的方法"""
|
|
20
|
+
eq_filter = EQFilter(fs=fs)
|
|
21
|
+
filter_func_map = {
|
|
22
|
+
'peak': eq_filter.PeakingFilter,
|
|
23
|
+
'low_shelf': eq_filter.LowshelfFilter,
|
|
24
|
+
'high_shelf': eq_filter.HighshelfFilter,
|
|
25
|
+
'low_pass': eq_filter.LowpassFilter,
|
|
26
|
+
'high_pass': eq_filter.HighpassFilter,
|
|
27
|
+
}
|
|
28
|
+
return filter_func_map.get(filter_type)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _calculate_spectrum(audio_data, target_sr, n_fft, hop_length):
|
|
32
|
+
S = librosa.stft(audio_data, n_fft=n_fft, hop_length=hop_length, win_length=n_fft)
|
|
33
|
+
mag = np.mean(np.abs(S), axis=1)
|
|
34
|
+
epsilon = 1e-9 # IMPORTANT: Add epsilon to avoid log(0)
|
|
35
|
+
spec_db = 20 * np.log10(mag + epsilon)
|
|
36
|
+
freq_axis = librosa.fft_frequencies(sr=target_sr, n_fft=n_fft)
|
|
37
|
+
return spec_db, freq_axis
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _load_audio_data(audio_path, target_sr):
|
|
41
|
+
data, sr_orig = sf.read(audio_path, dtype='float32')
|
|
42
|
+
if data.ndim > 1:
|
|
43
|
+
data = np.mean(data, axis=1)
|
|
44
|
+
if sr_orig != target_sr:
|
|
45
|
+
data = librosa.resample(data, orig_sr=sr_orig, target_sr=target_sr)
|
|
46
|
+
return data
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _apply_eq_cascade(audio_data, eq_params_list, fs):
|
|
50
|
+
if not eq_params_list:
|
|
51
|
+
return audio_data
|
|
52
|
+
processed_audio = audio_data.copy()
|
|
53
|
+
|
|
54
|
+
for params in eq_params_list:
|
|
55
|
+
filter_type, fc, Q, db_gain = params['filter_type'], params['fc'], params['Q'], params.get('dBgain')
|
|
56
|
+
filter_func = get_filter_function(filter_type, fs)
|
|
57
|
+
|
|
58
|
+
if filter_func is None:
|
|
59
|
+
warnings.warn(f"Unknown filter type: {filter_type}")
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
# 根据滤波器类型调用相应的方法
|
|
63
|
+
if db_gain is not None:
|
|
64
|
+
b, a = filter_func(fc=fc, Q=Q, dBgain=db_gain)
|
|
65
|
+
else:
|
|
66
|
+
b, a = filter_func(fc=fc, Q=Q)
|
|
67
|
+
|
|
68
|
+
if not np.issubdtype(processed_audio.dtype, np.floating):
|
|
69
|
+
processed_audio = processed_audio.astype(np.float32)
|
|
70
|
+
processed_audio = sp_signal.lfilter(b, a, processed_audio)
|
|
71
|
+
return processed_audio
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _objective_function(flat_params, band_definitions, target_response_db, freq_axis, fs, n_fft):
|
|
75
|
+
current_cascade_response_db = np.zeros_like(freq_axis)
|
|
76
|
+
param_idx_counter = 0
|
|
77
|
+
|
|
78
|
+
for band_def in band_definitions:
|
|
79
|
+
band_type = band_def['type']
|
|
80
|
+
# Safety check for parameter length (can happen if bounds are wrong for DE)
|
|
81
|
+
if param_idx_counter + 1 >= len(flat_params):
|
|
82
|
+
warnings.warn(
|
|
83
|
+
f"Parameter array too short in objective function. Expected at least {param_idx_counter + 2} elements, got {len(flat_params)}")
|
|
84
|
+
return np.finfo(np.float64).max # Return large error
|
|
85
|
+
|
|
86
|
+
fc, q_val = flat_params[param_idx_counter], flat_params[param_idx_counter + 1]
|
|
87
|
+
param_idx_counter += 2
|
|
88
|
+
|
|
89
|
+
filter_func = get_filter_function(band_type, fs)
|
|
90
|
+
if filter_func is None:
|
|
91
|
+
warnings.warn(f"Unknown filter type: {band_type}")
|
|
92
|
+
return np.finfo(np.float64).max
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
if band_type in ['peak', 'low_shelf', 'high_shelf']:
|
|
96
|
+
if param_idx_counter >= len(flat_params):
|
|
97
|
+
warnings.warn(f"Parameter array too short for gain parameter in objective function.")
|
|
98
|
+
return np.finfo(np.float64).max
|
|
99
|
+
db_gain = flat_params[param_idx_counter]
|
|
100
|
+
param_idx_counter += 1
|
|
101
|
+
b, a = filter_func(fc=fc, Q=q_val, dBgain=db_gain)
|
|
102
|
+
else:
|
|
103
|
+
b, a = filter_func(fc=fc, Q=q_val)
|
|
104
|
+
|
|
105
|
+
w, h = sp_signal.freqz(b, a, worN=freq_axis, fs=fs)
|
|
106
|
+
# Add epsilon to avoid log(0) which results in -inf and can break mean calculation
|
|
107
|
+
h_abs = np.abs(h)
|
|
108
|
+
h_db = 20 * np.log10(h_abs + 1e-9)
|
|
109
|
+
current_cascade_response_db += h_db
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
warnings.warn(f"Error computing filter response for {band_type}: {e}")
|
|
113
|
+
return np.finfo(np.float64).max
|
|
114
|
+
|
|
115
|
+
error = np.mean((current_cascade_response_db - target_response_db)**2)
|
|
116
|
+
if np.isnan(error) or np.isinf(error): # Handle potential nan/inf from objective
|
|
117
|
+
# This might happen if parameters lead to unstable filters or extreme responses
|
|
118
|
+
# print(f"Objective function returned NaN/Inf. Current error: {error}")
|
|
119
|
+
# print(f"Params (first few): {flat_params[:6]}")
|
|
120
|
+
return np.finfo(np.float64).max # Return a very large number
|
|
121
|
+
return error
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _get_initial_params_and_bounds(band_definitions, fs, target_response_db, freq_axis):
|
|
125
|
+
x0, bounds_list = [], [] # Changed bounds to bounds_list
|
|
126
|
+
min_fc, max_fc = 20.0, fs / 2.0 * 0.98
|
|
127
|
+
num_gain_filters = sum(1 for bd in band_definitions if bd['type'] in ['peak', 'low_shelf', 'high_shelf'])
|
|
128
|
+
log_fcs = np.logspace(np.log10(max(min_fc, 30)), np.log10(min(max_fc, fs / 2.1)), num_gain_filters, endpoint=True) if num_gain_filters > 0 else []
|
|
129
|
+
gain_filter_idx = 0
|
|
130
|
+
for band_def in band_definitions:
|
|
131
|
+
band_type = band_def['type']
|
|
132
|
+
initial_fc = band_def.get('initial_fc')
|
|
133
|
+
if initial_fc is None:
|
|
134
|
+
if band_type in ['peak', 'low_shelf', 'high_shelf'] and gain_filter_idx < len(log_fcs):
|
|
135
|
+
initial_fc = log_fcs[gain_filter_idx]
|
|
136
|
+
elif band_type == 'low_shelf':
|
|
137
|
+
initial_fc = np.clip(80, min_fc, max_fc)
|
|
138
|
+
elif band_type == 'high_shelf':
|
|
139
|
+
initial_fc = np.clip(8000, min_fc, max_fc)
|
|
140
|
+
elif band_type == 'low_pass':
|
|
141
|
+
initial_fc = np.clip(fs / 2.2, min_fc, max_fc)
|
|
142
|
+
elif band_type == 'high_pass':
|
|
143
|
+
initial_fc = np.clip(40, min_fc, max_fc)
|
|
144
|
+
else:
|
|
145
|
+
initial_fc = (min_fc + max_fc) / 2
|
|
146
|
+
x0.append(np.clip(initial_fc, min_fc, max_fc))
|
|
147
|
+
bounds_list.append((min_fc, max_fc))
|
|
148
|
+
initial_q = band_def.get('initial_Q', 1.0 if band_type == 'peak' else 0.707)
|
|
149
|
+
x0.append(initial_q)
|
|
150
|
+
bounds_list.append((0.1, 20.0))
|
|
151
|
+
if band_type in ['peak', 'low_shelf', 'high_shelf']:
|
|
152
|
+
fc_idx = np.argmin(np.abs(freq_axis - initial_fc))
|
|
153
|
+
initial_gain_default = target_response_db[fc_idx] if len(target_response_db) > 0 and fc_idx < len(target_response_db) else 0.0
|
|
154
|
+
initial_gain = band_def.get('initial_dBgain', initial_gain_default)
|
|
155
|
+
x0.append(np.clip(initial_gain, -20.0, 20.0))
|
|
156
|
+
bounds_list.append((-30.0, 30.0))
|
|
157
|
+
gain_filter_idx += 1
|
|
158
|
+
return np.array(x0), bounds_list # Return bounds_list for differential_evolution
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def plot_spectra_comparison(spectra_data, freq_axis, title="Spectra Comparison"):
|
|
162
|
+
plt.figure(figsize=(12, 7))
|
|
163
|
+
for label, spec_db in spectra_data.items():
|
|
164
|
+
plt.plot(freq_axis, spec_db, label=label, alpha=0.8)
|
|
165
|
+
plt.xscale('log') # Re-enabled log scale for frequency axis
|
|
166
|
+
plt.xlabel("Frequency (Hz)")
|
|
167
|
+
plt.ylabel("Magnitude (dB)")
|
|
168
|
+
plt.title(title)
|
|
169
|
+
plt.legend()
|
|
170
|
+
plt.grid(True, which="both", ls="-", alpha=0.5) # Added which="both" for log grid
|
|
171
|
+
if len(freq_axis) > 0:
|
|
172
|
+
plt.xlim([20, freq_axis[-1]])
|
|
173
|
+
valid_spectra = [s[np.isfinite(s)] for s in spectra_data.values() if s is not None and len(s[np.isfinite(s)]) > 0]
|
|
174
|
+
if valid_spectra:
|
|
175
|
+
min_y = min(np.min(s) for s in valid_spectra) - 10
|
|
176
|
+
max_y = max(np.max(s) for s in valid_spectra) + 10
|
|
177
|
+
if np.isfinite(min_y) and np.isfinite(max_y):
|
|
178
|
+
plt.ylim([min_y, max_y])
|
|
179
|
+
plt.tight_layout()
|
|
180
|
+
try:
|
|
181
|
+
clean_title = "".join(c if c.isalnum() or c in [' ', '_', '-'] else '_' for c in title) # Sanitize more robustly
|
|
182
|
+
plt.savefig(f"{clean_title.replace(' ', '_')}.png")
|
|
183
|
+
# if plt.isinteractive():
|
|
184
|
+
# plt.show()
|
|
185
|
+
plt.close()
|
|
186
|
+
except Exception as e:
|
|
187
|
+
print(f"Error saving/showing plot: {e}")
|
|
188
|
+
finally:
|
|
189
|
+
plt.close()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def match_frequency_response(
|
|
193
|
+
source_audio_path: str,
|
|
194
|
+
target_audio_path: str,
|
|
195
|
+
output_eq_audio_path: str = "source_eq_matched.wav",
|
|
196
|
+
num_eq_bands: int = 10,
|
|
197
|
+
sampling_rate: int = 16000,
|
|
198
|
+
fft_size: int = 1024,
|
|
199
|
+
hop_length_ratio: float = 0.25,
|
|
200
|
+
eq_band_config_list: list = None,
|
|
201
|
+
optimizer_options: dict = None, # For DE, e.g., {'popsize': 20, 'maxiter': 500, 'workers': -1}
|
|
202
|
+
plot_results: bool = True,
|
|
203
|
+
verbose: bool = False
|
|
204
|
+
):
|
|
205
|
+
hop_length = int(fft_size * hop_length_ratio)
|
|
206
|
+
if verbose:
|
|
207
|
+
print(f"SR={sampling_rate}, FFT={fft_size}, Hop={hop_length}")
|
|
208
|
+
print("Spectrum smoothing is DISABLED.")
|
|
209
|
+
|
|
210
|
+
source_data = _load_audio_data(source_audio_path, sampling_rate)
|
|
211
|
+
target_data = _load_audio_data(target_audio_path, sampling_rate)
|
|
212
|
+
|
|
213
|
+
source_spec_db, freq_axis = _calculate_spectrum(source_data, sampling_rate, fft_size, hop_length)
|
|
214
|
+
target_spec_db, _ = _calculate_spectrum(target_data, sampling_rate, fft_size, hop_length)
|
|
215
|
+
|
|
216
|
+
target_eq_overall_response_db = target_spec_db - source_spec_db
|
|
217
|
+
|
|
218
|
+
# _get_initial_params_and_bounds returns x0 and a list of (min,max) tuples for bounds
|
|
219
|
+
actual_num_bands = len(eq_band_config_list)
|
|
220
|
+
_, de_bounds = _get_initial_params_and_bounds(eq_band_config_list, sampling_rate, target_eq_overall_response_db, freq_axis)
|
|
221
|
+
|
|
222
|
+
if verbose:
|
|
223
|
+
print(f"EQ bands: {len(eq_band_config_list)}, Total params: {len(de_bounds)}")
|
|
224
|
+
|
|
225
|
+
# Default options for differential_evolution
|
|
226
|
+
# Note: popsize is often set to N_params * 10 or 15.
|
|
227
|
+
# maxiter might need to be lower than for L-BFGS-B for similar runtime, or higher for better solution.
|
|
228
|
+
num_params_to_optimize = len(de_bounds)
|
|
229
|
+
default_de_options = {
|
|
230
|
+
'strategy': 'best1bin',
|
|
231
|
+
'maxiter': 200 * actual_num_bands if actual_num_bands > 0 else 200, # Max generations
|
|
232
|
+
'popsize': 15, # Population size per generation (popsize * num_params_to_optimize evaluations per generation)
|
|
233
|
+
'tol': 0.01,
|
|
234
|
+
'mutation': (0.5, 1),
|
|
235
|
+
'recombination': 0.7,
|
|
236
|
+
'disp': verbose,
|
|
237
|
+
'polish': True, # Apply a local minimizer (L-BFGS-B) at the end
|
|
238
|
+
'updating': 'deferred', # For parallel processing
|
|
239
|
+
'workers': -1 # Use all available CPU cores
|
|
240
|
+
}
|
|
241
|
+
if optimizer_options:
|
|
242
|
+
default_de_options.update(optimizer_options)
|
|
243
|
+
|
|
244
|
+
obj_args = (eq_band_config_list, target_eq_overall_response_db, freq_axis, sampling_rate, fft_size)
|
|
245
|
+
|
|
246
|
+
if verbose:
|
|
247
|
+
print(f"Starting Differential Evolution, options: {default_de_options} (Smoothing DISABLED)...")
|
|
248
|
+
|
|
249
|
+
result = differential_evolution(
|
|
250
|
+
_objective_function,
|
|
251
|
+
bounds=de_bounds, # Pass the list of (min, max) tuples
|
|
252
|
+
args=obj_args,
|
|
253
|
+
**default_de_options # Pass all other options as keyword arguments
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if verbose:
|
|
257
|
+
print(f"DE Optimization: Success={result.success}, Msg='{result.message}', NFEV={result.nfev},nit={result.nit}, FunVal={result.fun:.4e}")
|
|
258
|
+
|
|
259
|
+
optimized_params_flat = result.x
|
|
260
|
+
# ... (Rest of the function: formatting parameters, applying EQ, plotting - remains the same) ...
|
|
261
|
+
optimized_eq_parameters_list = []
|
|
262
|
+
current_param_idx = 0
|
|
263
|
+
for i, band_def in enumerate(eq_band_config_list):
|
|
264
|
+
params = {'filter_type': band_def['type'], 'fs': float(sampling_rate)}
|
|
265
|
+
params['fc'] = float(optimized_params_flat[current_param_idx])
|
|
266
|
+
params['Q'] = float(optimized_params_flat[current_param_idx + 1])
|
|
267
|
+
current_param_idx += 2
|
|
268
|
+
if params['filter_type'] in ['peak', 'low_shelf', 'high_shelf']:
|
|
269
|
+
params['dBgain'] = float(optimized_params_flat[current_param_idx])
|
|
270
|
+
current_param_idx += 1
|
|
271
|
+
else:
|
|
272
|
+
params['dBgain'] = None
|
|
273
|
+
optimized_eq_parameters_list.append(params)
|
|
274
|
+
|
|
275
|
+
eq_audio_data = None
|
|
276
|
+
if output_eq_audio_path:
|
|
277
|
+
eq_audio_data = _apply_eq_cascade(source_data, optimized_eq_parameters_list, sampling_rate)
|
|
278
|
+
max_val = np.max(np.abs(eq_audio_data))
|
|
279
|
+
if max_val > 1.0:
|
|
280
|
+
eq_audio_data /= max_val
|
|
281
|
+
warnings.warn(f"EQ'd audio clipped (max: {max_val:.2f}), scaled.")
|
|
282
|
+
elif max_val == 0 and len(eq_audio_data) > 0:
|
|
283
|
+
warnings.warn(f"EQ'd audio is all zeros.")
|
|
284
|
+
sf.write(output_eq_audio_path, eq_audio_data, sampling_rate, subtype='FLOAT')
|
|
285
|
+
if verbose:
|
|
286
|
+
print(f"EQ'd audio saved: {output_eq_audio_path}")
|
|
287
|
+
|
|
288
|
+
if plot_results:
|
|
289
|
+
spectra_to_plot = {"Source": source_spec_db, "Target": target_spec_db}
|
|
290
|
+
plot_title_main = f"Spectra (DE) - {len(eq_band_config_list)} bands - No Smoothing"
|
|
291
|
+
eq_spec_db, _ = _calculate_spectrum(eq_audio_data, sampling_rate, fft_size, hop_length)
|
|
292
|
+
spectra_to_plot["EQ'd Source"] = eq_spec_db
|
|
293
|
+
plot_spectra_comparison(spectra_to_plot, freq_axis, title=plot_title_main)
|
|
294
|
+
return optimized_eq_parameters_list, eq_audio_data
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# --- Example Usage ---
|
|
298
|
+
if __name__ == '__main__':
|
|
299
|
+
source_file = "../data/white.wav"
|
|
300
|
+
target_file = "../data/white_EQ.wav"
|
|
301
|
+
output_eq_file = "../data/white_EQ_matched_DE.wav"
|
|
302
|
+
|
|
303
|
+
SR = 16000
|
|
304
|
+
NFFT = 1024
|
|
305
|
+
|
|
306
|
+
custom_band_config = [
|
|
307
|
+
{'type': 'high_pass', 'initial_fc': 40, 'initial_Q': 0.7},
|
|
308
|
+
{'type': 'low_shelf', 'initial_fc': 150, 'initial_Q': 0.7},
|
|
309
|
+
{'type': 'peak', 'initial_fc': 250},
|
|
310
|
+
{'type': 'peak', 'initial_fc': 500},
|
|
311
|
+
{'type': 'peak', 'initial_fc': 750},
|
|
312
|
+
{'type': 'peak', 'initial_fc': 1000},
|
|
313
|
+
{'type': 'peak', 'initial_fc': 1500},
|
|
314
|
+
{'type': 'peak', 'initial_fc': 2500},
|
|
315
|
+
{'type': 'peak', 'initial_fc': 3500},
|
|
316
|
+
{'type': 'peak', 'initial_fc': 5000},
|
|
317
|
+
{'type': 'peak', 'initial_fc': 6500},
|
|
318
|
+
{'type': 'high_shelf', 'initial_fc': 7000, 'initial_Q': 0.7},
|
|
319
|
+
] # 12 bands
|
|
320
|
+
|
|
321
|
+
# Differential Evolution optimizer options
|
|
322
|
+
# popsize * (maxiter+1) * N_params = total evaluations (approx, due to strategy)
|
|
323
|
+
# For 12 bands, ~34 params. popsize=15*34=510 is very large.
|
|
324
|
+
# Let's try popsize = 15 (relative to num_params, so DEAP default like), or fixed like 50-100.
|
|
325
|
+
# maxiter for DE is number of generations.
|
|
326
|
+
de_opt_options = {
|
|
327
|
+
'maxiter': 300, # Number of generations. Start smaller, e.g., 100-500.
|
|
328
|
+
'popsize': 20, # Population size multiplier. popsize * N_params individuals.
|
|
329
|
+
# For ~34 params, 20*34=680 individuals per generation. This is large.
|
|
330
|
+
# Let's set popsize directly to a number like 100 for now.
|
|
331
|
+
# 'popsize': 100, # Try a fixed population size
|
|
332
|
+
'mutation': (0.5, 1.0),
|
|
333
|
+
'recombination': 0.7,
|
|
334
|
+
'workers': -1, # Use all CPU cores for parallel fitness evaluation
|
|
335
|
+
'polish': True, # Recommended: polish the best solution with L-BFGS-B
|
|
336
|
+
'disp': True # Show progress
|
|
337
|
+
}
|
|
338
|
+
# Recalculate popsize for verbose message if using multiplier:
|
|
339
|
+
# num_total_params = 0
|
|
340
|
+
# for band in custom_band_config:
|
|
341
|
+
# num_total_params +=2 # fc, Q
|
|
342
|
+
# if band['type'] in ['peak', 'low_shelf', 'high_shelf']: num_total_params +=1
|
|
343
|
+
# print(f"Total parameters to optimize: {num_total_params}")
|
|
344
|
+
# de_opt_options['popsize'] = 10 * num_total_params # Example: 10 times the number of parameters
|
|
345
|
+
|
|
346
|
+
optimized_parameters, eq_processed_audio = match_frequency_response(
|
|
347
|
+
source_audio_path=source_file, target_audio_path=target_file,
|
|
348
|
+
output_eq_audio_path=output_eq_file, eq_band_config_list=custom_band_config,
|
|
349
|
+
sampling_rate=SR, fft_size=NFFT,
|
|
350
|
+
optimizer_options=de_opt_options, # Pass DE options
|
|
351
|
+
plot_results=True, verbose=True
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
if optimized_parameters:
|
|
355
|
+
print("\n优化后的EQ参数 (差分进化, 未平滑):")
|
|
356
|
+
for i, params in enumerate(optimized_parameters):
|
|
357
|
+
print(f" 频段 {i + 1}: 类型={params['filter_type']}, Fc={params['fc']:.1f}, Q={params['Q']:.2f}" +
|
|
358
|
+
(f", 增益={params['dBgain']:.2f}" if params['dBgain'] is not None else ""))
|
|
359
|
+
else:
|
|
360
|
+
print("未生成EQ参数或处理中发生错误。")
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Author: 凌逆战 | Never
|
|
3
|
+
Date: 2025-08-04 21:49:05
|
|
4
|
+
Description: 自动EQ补偿
|
|
5
|
+
'''
|
|
6
|
+
import numpy as np
|
|
7
|
+
import librosa
|
|
8
|
+
import soundfile as sf
|
|
9
|
+
import matplotlib.pyplot as plt
|
|
10
|
+
|
|
11
|
+
np.set_printoptions(precision=8)
|
|
12
|
+
np.set_printoptions(suppress=True) # 打印不使用科学计数法
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_freq_eq(reference_audio, target_audio, sample_rate, fft_size, window_size, plot_results=False):
|
|
16
|
+
freq_bins = np.fft.rfftfreq(fft_size, d=1.0 / sample_rate) # [0, 31.25, 62.5,.....]
|
|
17
|
+
|
|
18
|
+
stft_reference = librosa.stft(reference_audio, n_fft=fft_size, hop_length=window_size // 2, win_length=window_size, window="hann")
|
|
19
|
+
stft_target = librosa.stft(target_audio, n_fft=fft_size, hop_length=window_size // 2, win_length=window_size, window="hann")
|
|
20
|
+
magnitude_reference, magnitude_target = np.abs(stft_reference), np.abs(stft_target) # (F,T)
|
|
21
|
+
# 求时间平均, 频响曲线 Frequency_Response_curve
|
|
22
|
+
reference_response = np.mean(magnitude_reference, axis=1)
|
|
23
|
+
target_response = np.mean(magnitude_target, axis=1)
|
|
24
|
+
|
|
25
|
+
reference_response_db = 20 * np.log10(reference_response) # 取对数幅度谱, 以便更好地可视化
|
|
26
|
+
target_response_db = 20 * np.log10(target_response) # 取对数幅度谱, 以便更好地可视化
|
|
27
|
+
|
|
28
|
+
eq_curve = target_response_db - reference_response_db # 补偿曲线 (28208, 1)
|
|
29
|
+
# print("补偿EQ曲线: ", len(eq_curve), np.array2string(np.power(10, eq_curve / 20), separator=', '))
|
|
30
|
+
|
|
31
|
+
if plot_results:
|
|
32
|
+
plt.figure(figsize=(10, 5))
|
|
33
|
+
# plt.plot(freq_bins, target_response_db, label="Target Response")
|
|
34
|
+
plt.plot(freq_bins, eq_curve, label="EQ Curve")
|
|
35
|
+
# compensated_response = reference_response_db + eq_curve # 补偿后的曲线
|
|
36
|
+
# plt.plot(freq_bins, compensated_response, label="Compensated Response")
|
|
37
|
+
plt.xlabel('Frequency (Hz)')
|
|
38
|
+
plt.ylabel('Amplitude (dB)')
|
|
39
|
+
plt.title('Frequency Response Compensation')
|
|
40
|
+
plt.grid(True)
|
|
41
|
+
plt.legend()
|
|
42
|
+
plt.xscale('log')
|
|
43
|
+
plt.grid(True, ls="--", alpha=0.4)
|
|
44
|
+
plt.tight_layout()
|
|
45
|
+
# plt.show()
|
|
46
|
+
plt.savefig(f"./frequency_eq_fft{window_size}.png")
|
|
47
|
+
|
|
48
|
+
# 拿到EQ之后我们对音频进行EQ补偿
|
|
49
|
+
reference_phase = np.angle(stft_reference) # (F,T)
|
|
50
|
+
for freq_idx in range(magnitude_reference.shape[0]):
|
|
51
|
+
magnitude_reference[freq_idx, :] *= np.power(10, eq_curve[freq_idx] / 20)
|
|
52
|
+
compensated_spectrum = magnitude_reference * np.exp(1.0j * reference_phase)
|
|
53
|
+
compensated_audio = librosa.istft(compensated_spectrum, hop_length=window_size // 2, win_length=window_size, n_fft=fft_size, window="hann")
|
|
54
|
+
|
|
55
|
+
return eq_curve, compensated_audio
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
SAMPLE_RATE = 16000
|
|
60
|
+
WINDOW_SIZE = FFT_SIZE = 512
|
|
61
|
+
# reference_audio_path = "../../data/white.wav"
|
|
62
|
+
# target_audio_path = "../../data/white_EQ.wav"
|
|
63
|
+
# print(os.path.exists(reference_audio_path))
|
|
64
|
+
|
|
65
|
+
# 读取音频文件
|
|
66
|
+
# reference_audio, _ = sf.read(reference_audio_path, dtype='float32')
|
|
67
|
+
# target_audio, _ = sf.read(target_audio_path, dtype='float32')
|
|
68
|
+
wav_3956, sr = sf.read("../../data/3956_speech.wav")
|
|
69
|
+
reference_audio = wav_3956[:, 1]
|
|
70
|
+
target_audio = wav_3956[:, 0]
|
|
71
|
+
eq_curve, compensated_audio = compute_frequency_eq(
|
|
72
|
+
reference_audio, target_audio,
|
|
73
|
+
SAMPLE_RATE, FFT_SIZE, WINDOW_SIZE,
|
|
74
|
+
plot_results=True
|
|
75
|
+
)
|
|
76
|
+
sf.write("../../data/frequency_eq.wav", compensated_audio, SAMPLE_RATE)
|
|
@@ -8,7 +8,7 @@ import scipy.signal as signal
|
|
|
8
8
|
from scipy.signal import lfilter, freqz
|
|
9
9
|
import matplotlib.pyplot as plt
|
|
10
10
|
from deap import base, creator, tools, algorithms
|
|
11
|
-
from filter import EQFilter
|
|
11
|
+
from neverlib.filter import EQFilter
|
|
12
12
|
import logging
|
|
13
13
|
import pickle
|
|
14
14
|
import yaml
|