Python处理音频文件:信号图表与Cool Edit Pro显示不一致问题
Python绘制音频波形与Cool Edit Pro存在差异的问题
问题描述
使用Python脚本绘制WAV音频信号波形时,约半数文件的生成效果与Cool Edit Pro显示的正常波形存在差异:
- 正常效果:连续完整的音频信号波形,清晰展示信号的起伏变化
- 异常效果:波形存在断裂、局部失真,无法准确还原原始音频的信号特征
所用代码
import os import glob import numpy as np import matplotlib.pyplot as plt from scipy.io import wavfile INPUT_FOLDER = "aaaaa" OUTPUT_FOLDER = "aaaaa" os.makedirs(OUTPUT_FOLDER, exist_ok=True) def load_wav_normalized(filepath): fs, data = wavfile.read(filepath) if data.dtype == np.int32: data = data.astype(np.float32) / 2147483648.0 elif data.dtype == np.int16: data = data.astype(np.float32) / 32768.0 elif data.dtype == np.uint8: data = (data.astype(np.float32) - 128) / 128.0 if len(data.shape) > 1: data = np.mean(data, axis=1) return data, fs def draw_exactly_like_cool_edit(signal, fs, output_path): n_samples = len(signal) target_width = 2000 if n_samples <= target_width: samples_per_pixel = 1 min_vals = signal max_vals = signal time = np.arange(n_samples) / fs else: samples_per_pixel = n_samples // target_width n_blocks = target_width trimmed_len = n_blocks * samples_per_pixel trimmed = signal[:trimmed_len] blocks = trimmed.reshape(n_blocks, samples_per_pixel) min_vals = np.min(blocks, axis=1) max_vals = np.max(blocks, axis=1) time = np.arange(n_blocks) * samples_per_pixel / fs fig, ax = plt.subplots(figsize=(16, 5), dpi=100) fig.patch.set_facecolor('#0A0A0A') ax.set_facecolor('#0A0A0A') for i in range(len(time)): ax.plot([time[i], time[i]], [min_vals[i], max_vals[i]], color='#1EFF00', linewidth=1.5, alpha=0.9, solid_capstyle='butt') ax.axhline(y=0, color='#666666', linewidth=0.8) ax.axhline(y=1.0, color='#FF4444', linewidth=0.5, linestyle='--', alpha=0.6) ax.axhline(y=-1.0, color='#FF4444', linewidth=0.5, linestyle='--', alpha=0.6) for level in [0.5, 0.25]: ax.axhline(y=level, color='#2A2A2A', linewidth=0.3, linestyle=':', alpha=0.5) ax.axhline(y=-level, color='#2A2A2A', linewidth=0.3, linestyle=':', alpha=0.5) ax.set_title(f"{os.path.basename(output_path).replace('.png', '')} | {fs} Hz", color='white', fontsize=10) ax.set_xlabel("Время (секунды)", color='white', fontsize=9) ax.set_ylabel("Амплитуда", color='white', fontsize=9) ax.tick_params(colors='white', labelsize=8) ax.grid(True, color='#2A2A2A', alpha=0.3, linestyle='-', linewidth=0.3) for spine in ax.spines.values(): spine.set_color('#333333') ax.set_xlim(time[0], time[-1]) ax.set_ylim(-1.1, 1.1) plt.tight_layout() plt.savefig(output_path, facecolor='#0A0A0A', dpi=100) plt.close() return min_vals, max_vals, time wav_files = glob.glob(os.path.join(INPUT_FOLDER, "*.wav")) for filepath in wav_files: filename = os.path.splitext(os.path.basename(filepath))[0] signal, fs = load_wav_normalized(filepath) output_path = os.path.join(OUTPUT_FOLDER, f"{filename}_cooledit_style.png") min_vals, max_vals, time = draw_exactly_like_cool_edit(signal, fs, output_path)
问题原因及解决方案
1. 音频样本截断导致信息丢失
原代码处理长音频时,直接截断末尾剩余样本(trimmed = signal[:trimmed_len]),丢失部分音频数据,导致波形不完整。
修正方案:覆盖所有样本分块计算,不截断:
# 替换draw_exactly_like_cool_edit函数中的else块 else: # 生成覆盖所有样本的分块索引 indices = np.linspace(0, n_samples, target_width + 1, dtype=int) min_vals = [] max_vals = [] for i in range(target_width): start_idx = indices[i] end_idx = indices[i+1] block = signal[start_idx:end_idx] min_vals.append(np.min(block)) max_vals.append(np.max(block)) min_vals = np.array(min_vals) max_vals = np.array(max_vals) # 基于分块中心计算时间轴 time = np.array([(indices[i] + indices[i+1]) / 2 / fs for i in range(target_width)])
2. 未支持24位深度音频
原代码仅处理int32、int16、uint8格式音频,未覆盖常见的24位WAV文件,导致这类文件解析错误。
修正方案:在load_wav_normalized函数中添加24位音频处理逻辑:
elif data.dtype == np.int24: data = data.astype(np.float32) / 8388608.0 # 2^23 = 8388608
3. 立体声混合方式与Cool Edit Pro不一致
原代码用np.mean(data, axis=1)混合立体声,而Cool Edit Pro默认采用取声道最大绝对值的方式展示,导致波形差异。
修正方案:调整立体声混合逻辑:
if len(data.shape) > 1: # 取每个样本的最大绝对值,并保留对应声道的符号 abs_data = np.abs(data) max_abs_idx = np.argmax(abs_data, axis=1) data = np.take_along_axis(data, max_abs_idx[:, np.newaxis], axis=1).flatten()
4. 时间轴计算偏差
原代码时间轴基于分块起始位置,Cool Edit Pro通常采用分块中心位置作为时间标记,导致波形对齐偏差。
修正方案:使用分块中心位置计算时间轴,如上述修正后的else块中的time计算方式。
修正后的完整代码
import os import glob import numpy as np import matplotlib.pyplot as plt from scipy.io import wavfile INPUT_FOLDER = "aaaaa" OUTPUT_FOLDER = "aaaaa" os.makedirs(OUTPUT_FOLDER, exist_ok=True) def load_wav_normalized(filepath): fs, data = wavfile.read(filepath) # 支持更多音频位深度 if data.dtype == np.int32: data = data.astype(np.float32) / 2147483648.0 elif data.dtype == np.int24: data = data.astype(np.float32) / 8388608.0 elif data.dtype == np.int16: data = data.astype(np.float32) / 32768.0 elif data.dtype == np.uint8: data = (data.astype(np.float32) - 128) / 128.0 # 调整立体声混合逻辑,贴近Cool Edit Pro if len(data.shape) > 1: abs_data = np.abs(data) max_abs_idx = np.argmax(abs_data, axis=1) data = np.take_along_axis(data, max_abs_idx[:, np.newaxis], axis=1).flatten() return data, fs def draw_exactly_like_cool_edit(signal, fs, output_path): n_samples = len(signal) target_width = 2000 if n_samples <= target_width: samples_per_pixel = 1 min_vals = signal max_vals = signal time = np.arange(n_samples) / fs else: # 生成覆盖所有样本的分块索引 indices = np.linspace(0, n_samples, target_width + 1, dtype=int) min_vals = [] max_vals = [] for i in range(target_width): start_idx = indices[i] end_idx = indices[i+1] block = signal[start_idx:end_idx] min_vals.append(np.min(block)) max_vals.append(np.max(block)) min_vals = np.array(min_vals) max_vals = np.array(max_vals) # 基于分块中心计算时间轴 time = np.array([(indices[i] + indices[i+1]) / 2 / fs for i in range(target_width)]) fig, ax = plt.subplots(figsize=(16, 5), dpi=100) fig.patch.set_facecolor('#0A0A0A') ax.set_facecolor('#0A0A0A') for i in range(len(time)): ax.plot([time[i], time[i]], [min_vals[i], max_vals[i]], color='#1EFF00', linewidth=1.5, alpha=0.9, solid_capstyle='butt') ax.axhline(y=0, color='#666666', linewidth=0.8) ax.axhline(y=1.0, color='#FF4444', linewidth=0.5, linestyle='--', alpha=0.6) ax.axhline(y=-1.0, color='#FF4444', linewidth=0.5, linestyle='--', alpha=0.6) for level in [0.5, 0.25]: ax.axhline(y=level, color='#2A2A2A', linewidth=0.3, linestyle=':', alpha=0.5) ax.axhline(y=-level, color='#2A2A2A', linewidth=0.3, linestyle=':', alpha=0.5) ax.set_title(f"{os.path.basename(output_path).replace('.png', '')} | {fs} Hz", color='white', fontsize=10) ax.set_xlabel("Время (секунды)", color='white', fontsize=9) ax.set_ylabel("Амплитуда", color='white', fontsize=9) ax.tick_params(colors='white', labelsize=8) ax.grid(True, color='#2A2A2A', alpha=0.3, linestyle='-', linewidth=0.3) for spine in ax.spines.values(): spine.set_color('#333333') ax.set_xlim(time[0], time[-1]) ax.set_ylim(-1.1, 1.1) plt.tight_layout() plt.savefig(output_path, facecolor='#0A0A0A', dpi=100) plt.close() return min_vals, max_vals, time wav_files = glob.glob(os.path.join(INPUT_FOLDER, "*.wav")) for filepath in wav_files: filename = os.path.splitext(os.path.basename(filepath))[0] signal, fs = load_wav_normalized(filepath) output_path = os.path.join(OUTPUT_FOLDER, f"{filename}_cooledit_style.png") min_vals, max_vals, time = draw_exactly_like_cool_edit(signal, fs, output_path)
内容的提问来源于stack exchange,提问作者Alex Smirnov
相关产品推荐
相关产品推荐

