neverlib 0.2.2__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.
Files changed (236) hide show
  1. neverlib/.history/Docs/audio_aug/test_snr_20250806011311.py +0 -0
  2. neverlib/.history/Docs/audio_aug/test_snr_20250806011331.py +75 -0
  3. neverlib/.history/Docs/audio_aug/test_snr_20250806011342.py +57 -0
  4. neverlib/.history/Docs/audio_aug/test_snr_20250806011352.py +57 -0
  5. neverlib/.history/Docs/audio_aug/test_snr_20250806011403.py +57 -0
  6. neverlib/.history/Docs/audio_aug/test_snr_20250806011413.py +57 -0
  7. neverlib/.history/Docs/audio_aug/test_snr_20250806011435.py +55 -0
  8. neverlib/.history/Docs/vad/1_20250810032405.py +0 -0
  9. neverlib/.history/Docs/vad/1_20250810032417.py +39 -0
  10. neverlib/.history/audio_aug/audio_aug_20250806010451.py +125 -0
  11. neverlib/.history/audio_aug/audio_aug_20250806010750.py +138 -0
  12. neverlib/.history/audio_aug/audio_aug_20250806010759.py +140 -0
  13. neverlib/.history/audio_aug/audio_aug_20250806010803.py +140 -0
  14. neverlib/.history/audio_aug/audio_aug_20250806010809.py +140 -0
  15. neverlib/.history/audio_aug/audio_aug_20250806011108.py +140 -0
  16. neverlib/.history/dataAnalyze/__init___20250805234204.py +87 -0
  17. neverlib/.history/dataAnalyze/__init___20250806204125.py +14 -0
  18. neverlib/.history/dataAnalyze/__init___20250806204139.py +14 -0
  19. neverlib/.history/dataAnalyze/__init___20250806204159.py +14 -0
  20. neverlib/.history/filter/__init___20250820103351.py +70 -0
  21. neverlib/.history/filter/__init___20250821102348.py +70 -0
  22. neverlib/.history/filter/__init___20250821102405.py +14 -0
  23. neverlib/.history/filter/auto_eq/__init___20250819213121.py +36 -0
  24. neverlib/.history/filter/auto_eq/__init___20250821102241.py +36 -0
  25. neverlib/.history/filter/auto_eq/__init___20250821102259.py +36 -0
  26. neverlib/.history/filter/auto_eq/__init___20250821102307.py +36 -0
  27. neverlib/.history/filter/auto_eq/__init___20250821102310.py +36 -0
  28. neverlib/.history/filter/auto_eq/__init___20250821102318.py +36 -0
  29. neverlib/.history/filter/auto_eq/__init___20250821102507.py +36 -0
  30. neverlib/.history/filter/auto_eq/de_eq_20250820103848.py +361 -0
  31. neverlib/.history/filter/auto_eq/de_eq_20250821102422.py +360 -0
  32. neverlib/.history/filter/auto_eq/freq_eq_20250805234206.py +75 -0
  33. neverlib/.history/filter/auto_eq/freq_eq_20250820140732.py +75 -0
  34. neverlib/.history/filter/auto_eq/freq_eq_20250820140745.py +75 -0
  35. neverlib/.history/filter/auto_eq/freq_eq_20250820140816.py +75 -0
  36. neverlib/.history/filter/auto_eq/freq_eq_20250820140938.py +77 -0
  37. neverlib/.history/filter/auto_eq/freq_eq_20250820141003.py +77 -0
  38. neverlib/.history/filter/auto_eq/freq_eq_20250820141006.py +77 -0
  39. neverlib/.history/filter/auto_eq/freq_eq_20250820141019.py +77 -0
  40. neverlib/.history/filter/auto_eq/freq_eq_20250820141049.py +77 -0
  41. neverlib/.history/filter/auto_eq/freq_eq_20250820141211.py +77 -0
  42. neverlib/.history/filter/auto_eq/freq_eq_20250820141227.py +77 -0
  43. neverlib/.history/filter/auto_eq/freq_eq_20250820141311.py +78 -0
  44. neverlib/.history/filter/auto_eq/freq_eq_20250820141340.py +78 -0
  45. neverlib/.history/filter/auto_eq/freq_eq_20250820141712.py +78 -0
  46. neverlib/.history/filter/auto_eq/freq_eq_20250820141733.py +78 -0
  47. neverlib/.history/filter/auto_eq/freq_eq_20250820141755.py +78 -0
  48. neverlib/.history/filter/auto_eq/freq_eq_20250821102434.py +76 -0
  49. neverlib/.history/filter/auto_eq/freq_eq_20250821102500.py +76 -0
  50. neverlib/.history/filter/auto_eq/freq_eq_20250821102502.py +76 -0
  51. neverlib/.history/filter/auto_eq/ga_eq_basic_20250820102957.py +380 -0
  52. neverlib/.history/filter/auto_eq/ga_eq_basic_20250820113054.py +380 -0
  53. neverlib/.history/filter/auto_eq/ga_eq_basic_20250820113150.py +380 -0
  54. neverlib/.history/filter/auto_eq/ga_eq_basic_20250820113520.py +385 -0
  55. neverlib/.history/filter/auto_eq/ga_eq_basic_20250820113525.py +385 -0
  56. neverlib/.history/filter/auto_eq/ga_eq_basic_20250821102212.py +385 -0
  57. neverlib/.history/metrics/dnsmos_20250806001612.py +160 -0
  58. neverlib/.history/metrics/dnsmos_20250815180659.py +160 -0
  59. neverlib/.history/metrics/dnsmos_20250815180701.py +158 -0
  60. neverlib/.history/metrics/dnsmos_20250815181321.py +154 -0
  61. neverlib/.history/metrics/dnsmos_20250815181327.py +154 -0
  62. neverlib/.history/metrics/dnsmos_20250815181331.py +154 -0
  63. neverlib/.history/metrics/dnsmos_20250815181620.py +154 -0
  64. neverlib/.history/metrics/dnsmos_20250815181631.py +154 -0
  65. neverlib/.history/metrics/dnsmos_20250815181742.py +154 -0
  66. neverlib/.history/metrics/dnsmos_20250815181824.py +153 -0
  67. neverlib/.history/metrics/dnsmos_20250815181834.py +153 -0
  68. neverlib/.history/metrics/dnsmos_20250815181922.py +153 -0
  69. neverlib/.history/metrics/dnsmos_20250815182011.py +147 -0
  70. neverlib/.history/metrics/dnsmos_20250815182036.py +144 -0
  71. neverlib/.history/metrics/dnsmos_20250815182936.py +143 -0
  72. neverlib/.history/metrics/dnsmos_20250815182942.py +143 -0
  73. neverlib/.history/metrics/dnsmos_20250815183032.py +137 -0
  74. neverlib/.history/metrics/dnsmos_20250815183101.py +144 -0
  75. neverlib/.history/metrics/dnsmos_20250815183121.py +144 -0
  76. neverlib/.history/metrics/dnsmos_20250815183123.py +143 -0
  77. neverlib/.history/metrics/dnsmos_20250815183214.py +143 -0
  78. neverlib/.history/metrics/dnsmos_20250815183240.py +143 -0
  79. neverlib/.history/metrics/dnsmos_20250815183248.py +144 -0
  80. neverlib/.history/metrics/dnsmos_20250815183407.py +142 -0
  81. neverlib/.history/metrics/dnsmos_20250815183409.py +142 -0
  82. neverlib/.history/metrics/dnsmos_20250815183431.py +142 -0
  83. neverlib/.history/metrics/dnsmos_20250815183507.py +140 -0
  84. neverlib/.history/metrics/dnsmos_20250815183513.py +139 -0
  85. neverlib/.history/metrics/dnsmos_20250815183618.py +139 -0
  86. neverlib/.history/metrics/dnsmos_20250815183709.py +140 -0
  87. neverlib/.history/metrics/dnsmos_20250815183756.py +137 -0
  88. neverlib/.history/metrics/dnsmos_20250815183815.py +128 -0
  89. neverlib/.history/metrics/dnsmos_20250815183827.py +129 -0
  90. neverlib/.history/metrics/dnsmos_20250815183913.py +117 -0
  91. neverlib/.history/metrics/dnsmos_20250815183914.py +117 -0
  92. neverlib/.history/metrics/dnsmos_20250815184003.py +118 -0
  93. neverlib/.history/metrics/dnsmos_20250815184040.py +118 -0
  94. neverlib/.history/metrics/dnsmos_20250815184049.py +118 -0
  95. neverlib/.history/metrics/dnsmos_20250815184104.py +117 -0
  96. neverlib/.history/metrics/dnsmos_20250815184200.py +117 -0
  97. neverlib/.history/metrics/lpc_lsp_metric_20250816015944.py +128 -0
  98. neverlib/.history/metrics/lpc_lsp_metric_20250816020142.py +128 -0
  99. neverlib/.history/metrics/lpc_lsp_metric_20250816020156.py +128 -0
  100. neverlib/.history/metrics/lpc_lsp_metric_20250816020554.py +130 -0
  101. neverlib/.history/metrics/lpc_lsp_metric_20250816020600.py +125 -0
  102. neverlib/.history/metrics/lpc_lsp_metric_20250816020631.py +120 -0
  103. neverlib/.history/metrics/lpc_lsp_metric_20250816020746.py +118 -0
  104. neverlib/.history/metrics/lpc_me_20250816013111.py +0 -0
  105. neverlib/.history/metrics/lpc_me_20250816013129.py +121 -0
  106. neverlib/.history/metrics/lpc_me_20250816015430.py +103 -0
  107. neverlib/.history/metrics/lpc_me_20250816015535.py +96 -0
  108. neverlib/.history/metrics/lpc_me_20250816015542.py +96 -0
  109. neverlib/.history/metrics/lpc_me_20250816015636.py +97 -0
  110. neverlib/.history/metrics/lpc_me_20250816015658.py +104 -0
  111. neverlib/.history/metrics/lpc_me_20250816015703.py +100 -0
  112. neverlib/.history/metrics/lpc_me_20250816015945.py +128 -0
  113. neverlib/.history/metrics/snr_20250806010538.py +177 -0
  114. neverlib/.history/metrics/snr_20250806211634.py +184 -0
  115. neverlib/.history/metrics/spec_20250805234209.py +45 -0
  116. neverlib/.history/metrics/spec_20250816135530.py +11 -0
  117. neverlib/.history/metrics/spec_20250816135654.py +16 -0
  118. neverlib/.history/metrics/spec_20250816135736.py +68 -0
  119. neverlib/.history/metrics/spec_20250816135904.py +75 -0
  120. neverlib/.history/metrics/spec_20250816135921.py +82 -0
  121. neverlib/.history/metrics/spec_20250816140111.py +82 -0
  122. neverlib/.history/metrics/spec_20250816140543.py +136 -0
  123. neverlib/.history/metrics/spec_20250816140559.py +172 -0
  124. neverlib/.history/metrics/spec_20250816140602.py +172 -0
  125. neverlib/.history/metrics/spec_20250816140608.py +172 -0
  126. neverlib/.history/metrics/spec_20250816140654.py +148 -0
  127. neverlib/.history/metrics/spec_20250816140705.py +144 -0
  128. neverlib/.history/metrics/spec_20250816140755.py +138 -0
  129. neverlib/.history/metrics/spec_20250816140823.py +170 -0
  130. neverlib/.history/metrics/spec_20250816140832.py +170 -0
  131. neverlib/.history/metrics/spec_20250816140833.py +170 -0
  132. neverlib/.history/metrics/spec_20250816140922.py +147 -0
  133. neverlib/.history/metrics/spec_20250816141148.py +107 -0
  134. neverlib/.history/metrics/spec_20250816141219.py +123 -0
  135. neverlib/.history/metrics/spec_20250816141732.py +178 -0
  136. neverlib/.history/metrics/spec_20250816141740.py +178 -0
  137. neverlib/.history/metrics/spec_20250816142030.py +178 -0
  138. neverlib/.history/metrics/spec_20250816142107.py +135 -0
  139. neverlib/.history/metrics/spec_20250816142126.py +135 -0
  140. neverlib/.history/metrics/spec_20250816142410.py +135 -0
  141. neverlib/.history/metrics/spec_20250816142415.py +136 -0
  142. neverlib/.history/metrics/spec_metric_20250816135156.py +0 -0
  143. neverlib/.history/metrics/spec_metric_20250816135226.py +5 -0
  144. neverlib/.history/metrics/spec_metric_20250816135227.py +10 -0
  145. neverlib/.history/metrics/spec_metric_20250816135306.py +15 -0
  146. neverlib/.history/metrics/spec_metric_20250816135442.py +31 -0
  147. neverlib/.history/metrics/spec_metric_20250816135448.py +31 -0
  148. neverlib/.history/metrics/spec_metric_20250816135520.py +29 -0
  149. neverlib/.history/metrics/spec_metric_20250816135537.py +63 -0
  150. neverlib/.history/metrics/spec_metric_20250816135653.py +65 -0
  151. neverlib/.history/vad/PreProcess_20250805234211.py +63 -0
  152. neverlib/.history/vad/PreProcess_20250809232455.py +63 -0
  153. neverlib/.history/vad/PreProcess_20250816020725.py +66 -0
  154. neverlib/.history/vad/VAD_Silero_20250805234211.py +50 -0
  155. neverlib/.history/vad/VAD_Silero_20250809232456.py +50 -0
  156. neverlib/.history/vad/VAD_WebRTC_20250805234211.py +61 -0
  157. neverlib/.history/vad/VAD_WebRTC_20250809232456.py +61 -0
  158. neverlib/.history/vad/VAD_funasr_20250805234211.py +54 -0
  159. neverlib/.history/vad/VAD_funasr_20250809232456.py +54 -0
  160. neverlib/.history/vad/VAD_vadlib_20250805234211.py +70 -0
  161. neverlib/.history/vad/VAD_vadlib_20250809232455.py +70 -0
  162. neverlib/.history/vad/VAD_whisper_20250805234211.py +55 -0
  163. neverlib/.history/vad/VAD_whisper_20250809232456.py +55 -0
  164. neverlib/.specstory/.what-is-this.md +69 -0
  165. 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
  166. neverlib/Docs/audio_aug/test_snr.py +55 -0
  167. neverlib/__init__.py +2 -2
  168. neverlib/audio_aug/HarmonicDistortion.py +79 -0
  169. neverlib/audio_aug/TFDrop.py +41 -0
  170. neverlib/audio_aug/TFMask.py +56 -0
  171. neverlib/audio_aug/__init__.py +1 -1
  172. neverlib/audio_aug/audio_aug.py +19 -5
  173. neverlib/audio_aug/clip_aug.py +41 -0
  174. neverlib/audio_aug/coder_aug.py +209 -0
  175. neverlib/audio_aug/coder_aug2.py +118 -0
  176. neverlib/audio_aug/loss_packet_aug.py +103 -0
  177. neverlib/audio_aug/quant_aug.py +78 -0
  178. neverlib/data_analyze/README.md +234 -0
  179. neverlib/data_analyze/__init__.py +14 -0
  180. neverlib/data_analyze/dataset_analyzer.py +590 -0
  181. neverlib/data_analyze/quality_metrics.py +364 -0
  182. neverlib/data_analyze/rms_distrubution.py +62 -0
  183. neverlib/data_analyze/spectral_analysis.py +218 -0
  184. neverlib/data_analyze/statistics.py +406 -0
  185. neverlib/data_analyze/temporal_features.py +126 -0
  186. neverlib/data_analyze/visualization.py +468 -0
  187. neverlib/filter/README.md +101 -0
  188. neverlib/filter/__init__.py +7 -0
  189. neverlib/filter/auto_eq/README.md +165 -0
  190. neverlib/filter/auto_eq/__init__.py +36 -0
  191. neverlib/filter/auto_eq/de_eq.py +360 -0
  192. neverlib/filter/auto_eq/freq_eq.py +76 -0
  193. neverlib/filter/auto_eq/ga_eq_advanced.py +577 -0
  194. neverlib/filter/auto_eq/ga_eq_basic.py +385 -0
  195. neverlib/filter/biquad.py +45 -0
  196. neverlib/filter/common.py +5 -6
  197. neverlib/filter/core.py +339 -0
  198. neverlib/metrics/dnsmos.py +117 -0
  199. neverlib/metrics/lpc_lsp.py +118 -0
  200. neverlib/metrics/snr.py +184 -0
  201. neverlib/metrics/spec.py +136 -0
  202. neverlib/metrics/test_pesq.py +35 -0
  203. neverlib/metrics/time.py +68 -0
  204. neverlib/tests/test_vad.py +21 -0
  205. neverlib/utils/audio_split.py +2 -1
  206. neverlib/utils/message.py +4 -4
  207. neverlib/utils/utils.py +36 -16
  208. neverlib/vad/PreProcess.py +6 -3
  209. neverlib/vad/README.md +10 -10
  210. neverlib/vad/VAD_Energy.py +1 -1
  211. neverlib/vad/VAD_Silero.py +2 -2
  212. neverlib/vad/VAD_WebRTC.py +2 -2
  213. neverlib/vad/VAD_funasr.py +2 -2
  214. neverlib/vad/VAD_statistics.py +3 -3
  215. neverlib/vad/VAD_vadlib.py +3 -3
  216. neverlib/vad/VAD_whisper.py +2 -2
  217. neverlib/vad/__init__.py +1 -1
  218. neverlib/vad/class_get_speech.py +4 -4
  219. neverlib/vad/class_vad.py +1 -1
  220. neverlib/vad/utils.py +47 -5
  221. {neverlib-0.2.2.dist-info → neverlib-0.2.4.dist-info}/METADATA +120 -120
  222. neverlib-0.2.4.dist-info/RECORD +229 -0
  223. {neverlib-0.2.2.dist-info → neverlib-0.2.4.dist-info}/WHEEL +1 -1
  224. neverlib/Documents/vad/VAD_Energy.ipynb +0 -159
  225. neverlib/Documents/vad/VAD_Silero.ipynb +0 -305
  226. neverlib/Documents/vad/VAD_WebRTC.ipynb +0 -183
  227. neverlib/Documents/vad/VAD_funasr.ipynb +0 -179
  228. neverlib/Documents/vad/VAD_ppasr.ipynb +0 -175
  229. neverlib/Documents/vad/VAD_statistics.ipynb +0 -522
  230. neverlib/Documents/vad/VAD_vadlib.ipynb +0 -184
  231. neverlib/Documents/vad/VAD_whisper.ipynb +0 -430
  232. neverlib/utils/waveform_analyzer.py +0 -51
  233. neverlib/wav_data/000_short.wav +0 -0
  234. neverlib-0.2.2.dist-info/RECORD +0 -40
  235. {neverlib-0.2.2.dist-info → neverlib-0.2.4.dist-info}/licenses/LICENSE +0 -0
  236. {neverlib-0.2.2.dist-info → neverlib-0.2.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,577 @@
1
+ import sys
2
+ sys.path.append("..")
3
+
4
+ import random
5
+ import numpy as np
6
+ import soundfile as sf
7
+ import scipy.signal as signal
8
+ from scipy.signal import lfilter, freqz
9
+ import matplotlib.pyplot as plt
10
+ from deap import base, creator, tools, algorithms
11
+ from neverlib.filter import EQFilter
12
+ import logging
13
+ import pickle
14
+ import yaml
15
+ from scipy import stats
16
+
17
+ from datetime import datetime
18
+ from concurrent.futures import ThreadPoolExecutor
19
+ import json
20
+ import os
21
+ from typing import List, Dict, Tuple, Optional
22
+ from dataclasses import dataclass
23
+
24
+ # 设置日志
25
+ logging.basicConfig(
26
+ level=logging.INFO,
27
+ format='%(asctime)s - %(levelname)s - %(message)s',
28
+ handlers=[
29
+ logging.FileHandler('eq_optimization.log'),
30
+ logging.StreamHandler()
31
+ ]
32
+ )
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ @dataclass
37
+ class EQConfig():
38
+ """EQ优化配置类"""
39
+ # 文件路径
40
+ source_audio_path: str = "../../data/white.wav"
41
+ target_audio_path: str = "../../data/white_EQ.wav"
42
+ output_matched_audio_path: str = "../../data/white_matched.wav"
43
+
44
+ # 音频参数
45
+ sr: int = 16000
46
+ nfft: int = 1024
47
+
48
+ # GA配置
49
+ max_filters: int = 10
50
+ population_size: int = 200
51
+ max_generations: int = 150
52
+ cxpb: float = 0.7
53
+ mutpb_ind: float = 0.4
54
+ mutpb_gene: float = 0.15
55
+
56
+ # 复杂度惩罚
57
+ complexity_penalty_factor: float = 0.01
58
+
59
+ # 滤波器参数范围
60
+ fc_min: float = 20.0
61
+ fc_max: Optional[float] = None # 将在初始化时设置为 sr/2-50
62
+ q_min_peak: float = 0.3
63
+ q_max_peak: float = 10.0
64
+ q_min_shelf: float = 0.3
65
+ q_max_shelf: float = 2.0
66
+ dbgain_min: float = -25.0
67
+ dbgain_max: float = 25.0
68
+
69
+ # 优化参数
70
+ early_stopping_patience: int = 20
71
+ convergence_threshold: float = 1e-4
72
+ tournament_size: int = 3
73
+ save_checkpoint_interval: int = 25
74
+
75
+ def __post_init__(self):
76
+ if self.fc_max is None:
77
+ self.fc_max = self.sr / 2 - 50
78
+
79
+
80
+ # 滤波器类型定义
81
+ FILTER_TYPE_PEAK = 0
82
+ FILTER_TYPE_LOW_SHELF = 1
83
+ FILTER_TYPE_HIGH_SHELF = 2
84
+ AVAILABLE_FILTER_TYPES = [FILTER_TYPE_PEAK, FILTER_TYPE_LOW_SHELF, FILTER_TYPE_HIGH_SHELF]
85
+
86
+ FILTER_TYPE_MAP_INT_TO_STR = {
87
+ FILTER_TYPE_PEAK: 'peak',
88
+ FILTER_TYPE_LOW_SHELF: 'low_shelf',
89
+ FILTER_TYPE_HIGH_SHELF: 'high_shelf',
90
+ }
91
+
92
+ GENES_PER_FILTER_BLOCK = 5
93
+
94
+
95
+ class EQOptimizer:
96
+ def __init__(self, config: EQConfig = EQConfig()):
97
+ self.config = config
98
+ self.freq_num = config.nfft // 2 + 1
99
+
100
+ # 参数边界
101
+ self.q_bounds_per_type = {
102
+ FILTER_TYPE_PEAK: (config.q_min_peak, config.q_max_peak),
103
+ FILTER_TYPE_LOW_SHELF: (config.q_min_shelf, config.q_max_shelf),
104
+ FILTER_TYPE_HIGH_SHELF: (config.q_min_shelf, config.q_max_shelf),
105
+ }
106
+
107
+ # 全局变量
108
+ self.target_eq_shape_db_global = None
109
+ self.objective_freq_axis_global = None
110
+
111
+ # 设置DEAP
112
+ self._setup_deap()
113
+
114
+ # 统计信息
115
+ self.best_fitness_history = []
116
+ self.convergence_counter = 0
117
+
118
+ logger.info(f"EQ优化器初始化完成, 配置: {config}")
119
+
120
+ def _setup_deap(self):
121
+ """设置DEAP遗传算法框架"""
122
+ # 清除之前的注册(如果有的话)
123
+ if hasattr(creator, "FitnessMin"):
124
+ del creator.FitnessMin
125
+ if hasattr(creator, "Individual"):
126
+ del creator.Individual
127
+
128
+ creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
129
+ creator.create("Individual", list, fitness=creator.FitnessMin)
130
+
131
+ self.toolbox = base.Toolbox()
132
+ self.toolbox.register("individual", self._create_individual)
133
+ self.toolbox.register("population", tools.initRepeat, list, self.toolbox.individual)
134
+ self.toolbox.register("evaluate", self._evaluate_individual)
135
+ self.toolbox.register("mate", tools.cxTwoPoint)
136
+ self.toolbox.register("mutate", self._custom_mutate, indpb_gene=self.config.mutpb_gene)
137
+ self.toolbox.register("select", tools.selTournament, tournsize=self.config.tournament_size)
138
+
139
+ def _create_individual(self):
140
+ """创建个体"""
141
+ chromosome = []
142
+ for i in range(self.config.max_filters):
143
+ active = random.randint(0, 1)
144
+ type_val = random.choice(AVAILABLE_FILTER_TYPES)
145
+ fc = random.uniform(self.config.fc_min, self.config.fc_max)
146
+ q_min, q_max = self.q_bounds_per_type[type_val]
147
+ q = random.uniform(q_min, q_max)
148
+ dbgain = random.uniform(self.config.dbgain_min, self.config.dbgain_max)
149
+ chromosome.extend([active, type_val, fc, q, dbgain])
150
+ return creator.Individual(chromosome)
151
+
152
+ def _custom_mutate(self, individual, indpb_gene):
153
+ """自定义变异操作"""
154
+ for i in range(len(individual)):
155
+ if random.random() < indpb_gene:
156
+ block_index = i // GENES_PER_FILTER_BLOCK
157
+ gene_type_in_block = i % GENES_PER_FILTER_BLOCK
158
+
159
+ current_filter_type_gene_idx = block_index * GENES_PER_FILTER_BLOCK + 1
160
+ current_filter_type = individual[current_filter_type_gene_idx]
161
+
162
+ if gene_type_in_block == 0: # Active gene
163
+ individual[i] = 1 - individual[i]
164
+ elif gene_type_in_block == 1: # Type gene
165
+ new_type = random.choice([t for t in AVAILABLE_FILTER_TYPES if t != individual[i]])
166
+ individual[i] = new_type
167
+ q_gene_idx = block_index * GENES_PER_FILTER_BLOCK + 3
168
+ q_min, q_max = self.q_bounds_per_type[new_type]
169
+ individual[q_gene_idx] = random.uniform(q_min, q_max)
170
+ elif gene_type_in_block == 2: # Fc gene
171
+ individual[i] = random.uniform(self.config.fc_min, self.config.fc_max)
172
+ elif gene_type_in_block == 3: # Q gene
173
+ q_min, q_max = self.q_bounds_per_type[current_filter_type]
174
+ individual[i] = random.uniform(q_min, q_max)
175
+ elif gene_type_in_block == 4: # dBGain gene
176
+ individual[i] = random.uniform(self.config.dbgain_min, self.config.dbgain_max)
177
+ return individual,
178
+
179
+ def get_magnitude_spectrum_db(self, audio: np.ndarray, sr: int, n_fft: int) -> Tuple[np.ndarray, np.ndarray]:
180
+ """获取音频的幅度谱(dB)"""
181
+ f_spec, t_spec, Sxx_spec = signal.spectrogram(
182
+ audio, fs=sr, nperseg=n_fft, noverlap=n_fft // 4,
183
+ scaling='spectrum', mode='magnitude'
184
+ )
185
+ avg_magnitude_spectrum_spec = np.mean(Sxx_spec, axis=1)
186
+ db_spectrum = 20 * np.log10(avg_magnitude_spectrum_spec + 1e-12)
187
+ return f_spec, db_spectrum
188
+
189
+ def _get_single_filter_freq_response_db(self, filter_params: Dict, num_freq_points: int, fs_proc: int) -> Tuple[np.ndarray, np.ndarray]:
190
+ """获取单个滤波器的频率响应"""
191
+ eq_filter_instance = EQFilter(fs=fs_proc)
192
+ filter_type = filter_params['type_int']
193
+
194
+ if filter_type == FILTER_TYPE_PEAK:
195
+ filter_func = eq_filter_instance.PeakingFilter
196
+ elif filter_type == FILTER_TYPE_LOW_SHELF:
197
+ filter_func = eq_filter_instance.LowshelfFilter
198
+ else: # HIGH_SHELF
199
+ filter_func = eq_filter_instance.HighshelfFilter
200
+
201
+ b, a = filter_func(fc=filter_params['fc'], Q=filter_params['q'], dBgain=filter_params['dBgain'])
202
+ w_native, h_native = freqz(b, a, worN=num_freq_points, fs=fs_proc)
203
+ response_db_native = 20 * np.log10(np.abs(h_native) + 1e-12)
204
+ return w_native, response_db_native
205
+
206
+ def _get_combined_eq_response_db(self, active_filters_list: List[Dict], num_points_calc: int,
207
+ fs_proc: int, freq_axis_target: np.ndarray) -> np.ndarray:
208
+ """获取组合EQ响应"""
209
+ num_target_freq_bins = len(freq_axis_target)
210
+ combined_response_db = np.zeros(num_target_freq_bins)
211
+
212
+ if not active_filters_list:
213
+ return combined_response_db
214
+
215
+ # 使用并行处理计算多个滤波器响应
216
+ with ThreadPoolExecutor(max_workers=min(4, len(active_filters_list))) as executor:
217
+ responses = list(executor.map(
218
+ lambda p: self._get_single_filter_freq_response_db(p, num_points_calc, fs_proc),
219
+ active_filters_list
220
+ ))
221
+
222
+ for w_native, individual_response_db_native in responses:
223
+ individual_response_db_interp = np.interp(
224
+ freq_axis_target, w_native, individual_response_db_native
225
+ )
226
+ combined_response_db += individual_response_db_interp
227
+
228
+ return combined_response_db
229
+
230
+ def _evaluate_individual(self, individual_chromosome: List) -> Tuple[float]:
231
+ """评估个体适应度"""
232
+ if self.target_eq_shape_db_global is None or self.objective_freq_axis_global is None:
233
+ raise ValueError("全局目标频谱未设置!")
234
+
235
+ active_filters_params_list = []
236
+ num_active_filters = 0
237
+
238
+ for i in range(self.config.max_filters):
239
+ base_idx = i * GENES_PER_FILTER_BLOCK
240
+ is_active = individual_chromosome[base_idx]
241
+
242
+ if is_active == 1:
243
+ num_active_filters += 1
244
+ filter_type_int = individual_chromosome[base_idx + 1]
245
+ fc_val = individual_chromosome[base_idx + 2]
246
+ q_val = individual_chromosome[base_idx + 3]
247
+ dbgain_val = individual_chromosome[base_idx + 4]
248
+
249
+ # 参数约束
250
+ fc_val = np.clip(fc_val, self.config.fc_min, self.config.fc_max)
251
+ q_min_type, q_max_type = self.q_bounds_per_type[filter_type_int]
252
+ q_val = np.clip(q_val, q_min_type, q_max_type)
253
+ dbgain_val = np.clip(dbgain_val, self.config.dbgain_min, self.config.dbgain_max)
254
+
255
+ active_filters_params_list.append({
256
+ 'type_int': filter_type_int,
257
+ 'fc': fc_val,
258
+ 'q': q_val,
259
+ 'dBgain': dbgain_val
260
+ })
261
+
262
+ if not active_filters_params_list:
263
+ achieved_eq_response_db = np.zeros_like(self.target_eq_shape_db_global)
264
+ else:
265
+ achieved_eq_response_db = self._get_combined_eq_response_db(
266
+ active_filters_params_list,
267
+ self.freq_num,
268
+ self.config.sr,
269
+ self.objective_freq_axis_global
270
+ )
271
+
272
+ # 计算误差
273
+ error = np.sum((achieved_eq_response_db - self.target_eq_shape_db_global)**2)
274
+
275
+ # 自适应复杂度惩罚
276
+ penalty_scale = np.sum(self.target_eq_shape_db_global**2) / \
277
+ len(self.target_eq_shape_db_global) if len(self.target_eq_shape_db_global) > 0 else 1.0
278
+ if penalty_scale < 1e-3:
279
+ penalty_scale = 1.0
280
+
281
+ complexity_cost = self.config.complexity_penalty_factor * num_active_filters * (1 + penalty_scale * 0.1)
282
+ total_cost = error + complexity_cost
283
+
284
+ return (total_cost,)
285
+
286
+ def _check_convergence(self, logbook: tools.Logbook) -> bool:
287
+ """检查收敛条件"""
288
+ if len(logbook) < self.config.early_stopping_patience:
289
+ return False
290
+
291
+ recent_fitness = [log['min'] for log in logbook[-self.config.early_stopping_patience:]]
292
+ improvement = abs(recent_fitness[-1] - recent_fitness[0])
293
+
294
+ if improvement < self.config.convergence_threshold:
295
+ self.convergence_counter += 1
296
+ else:
297
+ self.convergence_counter = 0
298
+
299
+ return self.convergence_counter >= self.config.early_stopping_patience // 2
300
+
301
+ def _save_checkpoint(self, population: List, generation: int, logbook: tools.Logbook):
302
+ """保存检查点"""
303
+ checkpoint_data = {
304
+ 'population': population,
305
+ 'generation': generation,
306
+ 'logbook': logbook,
307
+ 'config': self.config,
308
+ 'timestamp': datetime.now().isoformat()
309
+ }
310
+
311
+ filename = f"eq_checkpoint_gen_{generation}.pkl"
312
+ with open(filename, 'wb') as f:
313
+ pickle.dump(checkpoint_data, f)
314
+ logger.info(f"检查点已保存: {filename}")
315
+
316
+ def _apply_eq_to_signal(self, audio: np.ndarray, eq_params_list: List[Dict], fs: int) -> np.ndarray:
317
+ """应用EQ到音频信号"""
318
+ if audio is None or len(audio) == 0:
319
+ raise ValueError("Invalid audio input")
320
+ if not eq_params_list:
321
+ return audio.copy()
322
+
323
+ processed_audio = np.copy(audio)
324
+ eq_filter_instance = EQFilter(fs=fs)
325
+
326
+ for p_dict_decoded in eq_params_list:
327
+ if p_dict_decoded['type'] == 'peak':
328
+ filter_func = eq_filter_instance.PeakingFilter
329
+ elif p_dict_decoded['type'] == 'low_shelf':
330
+ filter_func = eq_filter_instance.LowshelfFilter
331
+ else: # high_shelf
332
+ filter_func = eq_filter_instance.HighshelfFilter
333
+
334
+ b, a = filter_func(fc=p_dict_decoded['fc'], Q=p_dict_decoded['q'], dBgain=p_dict_decoded['dBgain'])
335
+ processed_audio = lfilter(b, a, processed_audio)
336
+
337
+ return processed_audio
338
+
339
+ def _evaluate_eq_quality(self, source_audio: np.ndarray, processed_audio: np.ndarray, sr: int) -> Dict:
340
+ """评估EQ质量的额外指标"""
341
+ # 计算频谱相关性
342
+ source_fft = np.abs(np.fft.rfft(source_audio))
343
+ processed_fft = np.abs(np.fft.rfft(processed_audio))
344
+ corr, _ = stats.pearsonr(source_fft, processed_fft)
345
+
346
+ # 计算响度差异
347
+ loudness_diff = np.mean(np.abs(processed_audio)) - np.mean(np.abs(source_audio))
348
+
349
+ # 计算峰值差异
350
+ peak_diff = np.max(np.abs(processed_audio)) - np.max(np.abs(source_audio))
351
+
352
+ return {
353
+ 'spectral_correlation': corr,
354
+ 'loudness_difference': loudness_diff,
355
+ 'peak_difference': peak_diff
356
+ }
357
+
358
+ def optimize(self) -> Dict:
359
+ """主优化函数"""
360
+ source_audio, sr = sf.read(self.config.source_audio_path)
361
+ target_audio, sr = sf.read(self.config.target_audio_path)
362
+ assert sr == self.config.sr, "采样率不匹配"
363
+ assert source_audio.ndim == 1, "源音频不是单声道"
364
+ assert target_audio.ndim == 1, "目标音频不是单声道"
365
+
366
+ # 计算频谱
367
+ source_freq_axis, source_db_spectrum = self.get_magnitude_spectrum_db(source_audio, sr, self.config.nfft)
368
+ target_freq_axis, target_db_spectrum = self.get_magnitude_spectrum_db(target_audio, sr, self.config.nfft)
369
+
370
+ # 设置全局目标
371
+ self.target_eq_shape_db_global = target_db_spectrum - source_db_spectrum
372
+ self.objective_freq_axis_global = source_freq_axis
373
+
374
+ logger.info(f"运行遗传算法 (种群: {self.config.population_size}, 最大迭代: {self.config.max_generations}, 最大滤波器数: {self.config.max_filters})...")
375
+
376
+ # 初始化种群
377
+ population = self.toolbox.population(n=self.config.population_size)
378
+ hall_of_fame = tools.HallOfFame(1)
379
+
380
+ # 设置统计信息
381
+ stats = tools.Statistics(lambda ind: ind.fitness.values)
382
+ stats.register("avg", np.mean)
383
+ stats.register("std", np.std)
384
+ stats.register("min", np.min)
385
+ stats.register("max", np.max)
386
+
387
+ # 运行遗传算法
388
+ logbook = tools.Logbook()
389
+ logbook.header = ['gen', 'nevals'] + stats.fields
390
+
391
+ # 评估初始种群
392
+ fitnesses = list(map(self.toolbox.evaluate, population))
393
+ for ind, fit in zip(population, fitnesses):
394
+ ind.fitness.values = fit
395
+
396
+ hall_of_fame.update(population)
397
+ record = stats.compile(population)
398
+ logbook.record(gen=0, nevals=len(population), **record)
399
+
400
+ logger.info(logbook.stream)
401
+
402
+ # 主循环
403
+ for gen in range(1, self.config.max_generations + 1):
404
+ # 选择
405
+ offspring = self.toolbox.select(population, len(population))
406
+ offspring = list(map(self.toolbox.clone, offspring))
407
+
408
+ # 交叉和变异
409
+ for child1, child2 in zip(offspring[::2], offspring[1::2]):
410
+ if random.random() < self.config.cxpb:
411
+ self.toolbox.mate(child1, child2)
412
+ del child1.fitness.values
413
+ del child2.fitness.values
414
+
415
+ for mutant in offspring:
416
+ if random.random() < self.config.mutpb_ind:
417
+ self.toolbox.mutate(mutant)
418
+ del mutant.fitness.values
419
+
420
+ # 评估需要评估的个体
421
+ invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
422
+ fitnesses = list(map(self.toolbox.evaluate, invalid_ind))
423
+ for ind, fit in zip(invalid_ind, fitnesses):
424
+ ind.fitness.values = fit
425
+
426
+ # 更新种群
427
+ population[:] = offspring
428
+ hall_of_fame.update(population)
429
+
430
+ # 记录统计信息
431
+ record = stats.compile(population)
432
+ logbook.record(gen=gen, nevals=len(invalid_ind), **record)
433
+
434
+ if gen % 10 == 0: # 每10代打印一次
435
+ logger.info(logbook.stream)
436
+
437
+ # 检查收敛
438
+ if self._check_convergence(logbook):
439
+ logger.info(f"在第 {gen} 代收敛, 提前停止")
440
+ break
441
+
442
+ # 保存检查点
443
+ if gen % self.config.save_checkpoint_interval == 0:
444
+ self._save_checkpoint(population, gen, logbook)
445
+
446
+ # 获取最优解
447
+ best_individual = hall_of_fame[0]
448
+ logger.info(f"最优个体适应度: {best_individual.fitness.values[0]:.4f}")
449
+
450
+ # 解码最优个体
451
+ optimized_eq_params = self._decode_individual(best_individual)
452
+
453
+ # 应用EQ并保存
454
+ results = {'eq_parameters': optimized_eq_params}
455
+
456
+ if optimized_eq_params and self.config.output_matched_audio_path:
457
+ logger.info(f"应用优化EQ并保存到 {self.config.output_matched_audio_path}...")
458
+ source_audio_matched = self._apply_eq_to_signal(source_audio, optimized_eq_params, sr)
459
+ sf.write(self.config.output_matched_audio_path, source_audio_matched, sr)
460
+
461
+ # 评估EQ质量
462
+ quality_metrics = self._evaluate_eq_quality(source_audio, source_audio_matched, sr)
463
+ results['quality_metrics'] = quality_metrics
464
+ logger.info(f"EQ质量指标: {quality_metrics}")
465
+
466
+ # 生成对比图
467
+ self._generate_comparison_plot(source_audio, target_audio, optimized_eq_params, sr)
468
+
469
+ # 保存结果
470
+ self._save_results(results, logbook)
471
+
472
+ logger.info("EQ优化完成")
473
+ return results
474
+
475
+ def _decode_individual(self, individual: List) -> List[Dict]:
476
+ """解码个体为EQ参数"""
477
+ optimized_eq_params = []
478
+
479
+ logger.info("--- 解码最优EQ滤波器参数 ---")
480
+ for i in range(self.config.max_filters):
481
+ base_idx = i * GENES_PER_FILTER_BLOCK
482
+ is_active = individual[base_idx]
483
+
484
+ if is_active == 1:
485
+ filter_type_int = individual[base_idx + 1]
486
+ fc_val = individual[base_idx + 2]
487
+ q_val = individual[base_idx + 3]
488
+ dbgain_val = individual[base_idx + 4]
489
+
490
+ param_dict = {
491
+ 'type': FILTER_TYPE_MAP_INT_TO_STR[filter_type_int],
492
+ 'fc': round(fc_val, 2),
493
+ 'q': round(q_val, 3),
494
+ 'dBgain': round(dbgain_val, 2),
495
+ 'fs': self.config.sr
496
+ }
497
+ optimized_eq_params.append(param_dict)
498
+ logger.info(f"滤波器 {len(optimized_eq_params)}: {param_dict}")
499
+
500
+ if not optimized_eq_params:
501
+ logger.warning("警告: 遗传算法没有找到任何活跃的滤波器")
502
+
503
+ return optimized_eq_params
504
+
505
+ def _generate_comparison_plot(self, source_audio: np.ndarray, target_audio: np.ndarray,
506
+ eq_params: List[Dict], sr: int):
507
+ """生成对比图"""
508
+ logger.info("生成对比图...")
509
+
510
+ source_freq_axis, source_db_spectrum = self.get_magnitude_spectrum_db(source_audio, sr, self.config.nfft)
511
+ target_freq_axis, target_db_spectrum = self.get_magnitude_spectrum_db(target_audio, sr, self.config.nfft)
512
+
513
+ if eq_params:
514
+ # 计算匹配后的频谱
515
+ processed_audio = self._apply_eq_to_signal(source_audio, eq_params, sr)
516
+ _, processed_db_spectrum = self.get_magnitude_spectrum_db(processed_audio, sr, self.config.nfft)
517
+ else:
518
+ processed_db_spectrum = source_db_spectrum
519
+
520
+ plt.figure(figsize=(14, 8))
521
+ plt.semilogx(source_freq_axis, source_db_spectrum, label='源音频频谱', alpha=0.8, color='deepskyblue', linewidth=2)
522
+ plt.semilogx(target_freq_axis, target_db_spectrum, label='目标音频频谱', alpha=0.8, color='coral', linewidth=2)
523
+ plt.semilogx(source_freq_axis, processed_db_spectrum, label='匹配后频谱', alpha=0.8, color='limegreen', linewidth=2)
524
+
525
+ plt.title(f'EQ匹配结果 ({len(eq_params)} 个活跃滤波器) - {sr}Hz', fontsize=14)
526
+ plt.xlabel('频率 (Hz)', fontsize=12)
527
+ plt.ylabel('幅度 (dB)', fontsize=12)
528
+ plt.legend(loc='best', fontsize=11)
529
+ plt.grid(True, ls="--", alpha=0.4)
530
+ plt.tight_layout()
531
+
532
+ plot_filename = f"eq_matching_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
533
+ plt.savefig(plot_filename, dpi=300, bbox_inches='tight')
534
+ plt.close()
535
+
536
+ logger.info(f"对比图已保存: {plot_filename}")
537
+
538
+ def _save_results(self, results: Dict, logbook: tools.Logbook):
539
+ """保存结果"""
540
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
541
+
542
+ # 保存EQ参数
543
+ eq_filename = f"eq_parameters_{timestamp}.json"
544
+ with open(eq_filename, 'w', encoding='utf-8') as f:
545
+ json.dump(results, f, indent=2, ensure_ascii=False)
546
+
547
+ # 保存训练日志
548
+ log_filename = f"training_log_{timestamp}.json"
549
+ log_data = {
550
+ 'generations': len(logbook),
551
+ 'final_fitness': logbook[-1]['min'] if logbook else None,
552
+ 'config': self.config.__dict__,
553
+ 'logbook': [dict(record) for record in logbook]
554
+ }
555
+ with open(log_filename, 'w', encoding='utf-8') as f:
556
+ json.dump(log_data, f, indent=2)
557
+
558
+ logger.info(f"结果已保存: {eq_filename}, {log_filename}")
559
+
560
+
561
+ def load_config_from_yaml(config_file: str) -> EQConfig:
562
+ """从YAML文件加载配置"""
563
+ with open(config_file, 'r', encoding='utf-8') as f:
564
+ config_dict = yaml.safe_load(f)
565
+ return EQConfig(**config_dict)
566
+
567
+
568
+ def main():
569
+ # 创建优化器并运行
570
+ optimizer = EQOptimizer()
571
+ results = optimizer.optimize()
572
+
573
+ print("程序执行完成")
574
+
575
+
576
+ if __name__ == '__main__':
577
+ main()