%% ========================================================================
% ex0_synthesize_all.m - 音频变速实验主控程序
% ========================================================================
% 功能:调用 ex1-ex6 函数实现基于相位声码器/频域插值的音频时间拉伸
% 兼容版本:MATLAB 2014b 及以上
% ========================================================================
clc; % 清除命令窗口
clear; % 清除工作区变量
close all; % 关闭所有打开的图形窗口
%% ========================== 程序开始提示 ================================
fprintf('\n');
fprintf('================================================================\n');
fprintf(' 音频变速实验 - 基于相位声码器的时间拉伸\n');
fprintf(' Digital Signal Processing\n');
fprintf('================================================================\n');
fprintf('\n');
%% ========================== 步骤1:读取音频文件 ==========================
fprintf('----------------------------------------------------------------\n');
fprintf('[步骤 1/8] 读取音频文件...\n');
fprintf('----------------------------------------------------------------\n');
try
audioFile = './audio/orig.wav'; % 使用单引号(兼容2014b)
[x, fs] = audioread(audioFile);
fprintf(' [OK] 音频文件读取成功!\n');
fprintf(' - 文件路径: %s\n', audioFile);
fprintf(' - 采样率: %d Hz\n', fs);
fprintf(' - 信号长度: %d 样本点\n', length(x));
fprintf(' - 音频时长: %.2f 秒\n', length(x)/fs);
if size(x, 2) > 1
x = x(:, 1); % 如果是立体声,取左声道
fprintf(' - 注意: 检测到立体声,已转换为单声道\n');
end
catch ME
fprintf(' [ERROR] 无法读取音频文件!\n');
fprintf(' - 错误信息: %s\n', ME.message);
fprintf(' - 请检查文件路径是否正确\n');
return;
end
fprintf('\n');
%% ========================== 步骤2:参数设置 ==============================
fprintf('----------------------------------------------------------------\n');
fprintf('[步骤 2/8] 参数设置...\n');
fprintf('----------------------------------------------------------------\n');
Lwin = 1024; % 每帧的长度(通常为 2 的幂次)
Ra = Lwin / 4; % 帧移 (Hop size),通常为帧长的 1/4 或 1/2
stretchFactor = 2; % 时间拉伸因子
fprintf(' [OK] 参数设置完成!\n');
fprintf(' - 帧长 (Lwin): %d 样本点\n', Lwin);
fprintf(' - 帧移 (Ra): %d 样本点 (%.1f%% 重叠)\n', Ra, (1-Ra/Lwin)*100);
fprintf(' - 拉伸因子: %.2f\n', stretchFactor);
if stretchFactor > 1
fprintf(' - 效果: 音频将被拉伸为原来的 %.1f 倍(减速播放)\n', stretchFactor);
elseif stretchFactor < 1
fprintf(' - 效果: 音频将被压缩为原来的 %.1f 倍(加速播放)\n', stretchFactor);
else
fprintf(' - 效果: 音频长度保持不变\n');
end
fprintf('\n');
%% ========================== 步骤3:信号分帧 ==============================
fprintf('----------------------------------------------------------------\n');
fprintf('[步骤 3/8] 调用 ex1_frame_signal 进行信号分帧...\n');
fprintf('----------------------------------------------------------------\n');
try
tic;
[x_mat] = ex1_frame_signal(x, Lwin, Ra);
time_ex1 = toc;
% 统一命名图形窗口
set(gcf, 'Name', '分帧结果 - ex1_frame_signal', 'NumberTitle', 'off');
fprintf(' [OK] ex1_frame_signal 执行成功! (耗时 %.3f 秒)\n', time_ex1);
fprintf(' - 输入: 长度为 %d 的一维信号\n', length(x));
fprintf(' - 输出: %d x %d 的分帧矩阵\n', size(x_mat, 1), size(x_mat, 2));
fprintf(' - 总帧数: %d 帧\n', size(x_mat, 2));
fprintf('\n');
fprintf(' [图形输出 - 来自 ex1_frame_signal]\n');
fprintf(' - 图形内容: 原始信号与各帧信号的叠加显示\n');
fprintf(' - 说明: 不同颜色代表不同的帧,可观察帧间重叠情况\n');
catch ME
fprintf(' [ERROR] ex1_frame_signal 执行失败!\n');
fprintf(' - 错误信息: %s\n', ME.message);
return;
end
fprintf('\n');
%% ========================== 步骤4:加窗处理 ==============================
fprintf('----------------------------------------------------------------\n');
fprintf('[步骤 4/8] 调用 ex2_window_signal 进行加窗处理...\n');
fprintf('----------------------------------------------------------------\n');
try
tic;
[W_x_mat] = ex2_window_signal(x_mat, Lwin);
time_ex2 = toc;
% 统一命名图形窗口
set(gcf, 'Name', '加窗处理 - ex2_window_signal', 'NumberTitle', 'off');
fprintf(' [OK] ex2_window_signal 执行成功! (耗时 %.3f 秒)\n', time_ex2);
fprintf(' - 窗函数类型: Hann 窗\n');
fprintf(' - 窗口长度: %d 样本点\n', Lwin);
fprintf(' - 输出矩阵大小: %d x %d\n', size(W_x_mat, 1), size(W_x_mat, 2));
fprintf('\n');
fprintf(' [图形输出 - 来自 ex2_window_signal]\n');
fprintf(' - 子图1: 原始信号的第一帧(未加窗)\n');
fprintf(' - 子图2: Hann 窗函数形状\n');
fprintf(' - 子图3: 加窗后的第一帧信号(两端幅度减小)\n');
catch ME
fprintf(' [ERROR] ex2_window_signal 执行失败!\n');
fprintf(' - 错误信息: %s\n', ME.message);
return;
end
fprintf('\n');
%% ========================== 步骤5:计算频谱 ==============================
fprintf('----------------------------------------------------------------\n');
fprintf('[步骤 5/8] 调用 ex3_compute_spectrum 计算频谱...\n');
fprintf('----------------------------------------------------------------\n');
try
tic;
[W_X_MAT] = ex3_compute_spectrum(W_x_mat);
time_ex3 = toc;
% 统一命名图形窗口
set(gcf, 'Name', '频谱分析 - ex3_compute_spectrum', 'NumberTitle', 'off');
fprintf(' [OK] ex3_compute_spectrum 执行成功! (耗时 %.3f 秒)\n', time_ex3);
fprintf(' - 变换类型: FFT (快速傅里叶变换)\n');
fprintf(' - 频谱矩阵大小: %d x %d (复数矩阵)\n', size(W_X_MAT, 1), size(W_X_MAT, 2));
fprintf(' - 频率分辨率: %.2f Hz\n', fs/Lwin);
fprintf('\n');
fprintf(' [图形输出 - 来自 ex3_compute_spectrum]\n');
fprintf(' - 子图1: 加窗后第一帧的时域波形\n');
fprintf(' - 子图2: 第一帧的幅度谱(显示各频率成分强度)\n');
fprintf(' - 子图3: 第一帧的相位谱(显示各频率成分相位)\n');
catch ME
fprintf(' [ERROR] ex3_compute_spectrum 执行失败!\n');
fprintf(' - 错误信息: %s\n', ME.message);
return;
end
fprintf('\n');
%% ========================== 步骤6:修改频谱 ==============================
fprintf('----------------------------------------------------------------\n');
fprintf('[步骤 6/8] 修改频谱(时间拉伸核心步骤)...\n');
fprintf('----------------------------------------------------------------\n');
usePhaseVocoder = true; % true: 相位声码器法; false: 频域插值法
if ~usePhaseVocoder
% -------------------- 方法1:频域插值法 --------------------
fprintf(' -> 使用方法: 频域插值法 (ex4_modify_STFT)\n');
fprintf(' - 原理: 通过线性插值调整频谱矩阵的帧数\n');
fprintf('\n');
try
tic;
[freq_mat_modified] = ex4_modify_STFT(W_X_MAT, stretchFactor);
time_ex4 = toc;
% 统一命名图形窗口
set(gcf, 'Name', '频域插值 - ex4_modify_STFT', 'NumberTitle', 'off');
fprintf(' [OK] ex4_modify_STFT 执行成功! (耗时 %.3f 秒)\n', time_ex4);
fprintf(' - 原始帧数: %d\n', size(W_X_MAT, 2));
fprintf(' - 拉伸后帧数: %d\n', size(freq_mat_modified, 2));
fprintf(' - 重建帧移: %d (使用原始帧移 Ra)\n', Ra);
fprintf('\n');
fprintf(' [图形输出 - 来自 ex4_modify_STFT]\n');
fprintf(' - 子图1: 原始最低频率分量随时间变化\n');
fprintf(' - 子图2: 插值后最低频率分量随时间变化\n');
fprintf(' - 说明: 可观察到帧数变化,但频率成分形状保持\n');
% 设置重建用的帧移
Rs_reconstruct = Ra;
catch ME
fprintf(' [ERROR] ex4_modify_STFT 执行失败!\n');
fprintf(' - 错误信息: %s\n', ME.message);
return;
end
else
% -------------------- 方法2:相位声码器法 --------------------
fprintf(' -> 使用方法: 相位声码器法 (ex6_Phase_Vocoder)\n');
fprintf(' - 原理: 保持幅度不变,通过相位调整实现时间拉伸\n');
fprintf(' - 优势: 避免帧间相位不连续,音质更好\n');
fprintf('\n');
Rs = round(Ra * stretchFactor);
fprintf(' - 原始帧移 Ra: %d\n', Ra);
fprintf(' - 拉伸后帧移 Rs: %d\n', Rs);
fprintf('\n');
try
tic;
[freq_mat_modified] = ex6_Phase_Vocoder(W_X_MAT, Lwin, Ra, Rs);
time_ex6 = toc;
% 统一命名图形窗口
set(gcf, 'Name', '相位声码器 - ex6_Phase_Vocoder', 'NumberTitle', 'off');
fprintf(' [OK] ex6_Phase_Vocoder 执行成功! (耗时 %.3f 秒)\n', time_ex6);
fprintf(' - 输入帧数: %d\n', size(W_X_MAT, 2));
fprintf(' - 输出帧数: %d (少1帧用于相位差计算)\n', size(freq_mat_modified, 2));
fprintf(' - 重建帧移: %d (使用拉伸后帧移 Rs)\n', Rs);
fprintf('\n');
fprintf(' [图形输出 - 来自 ex6_Phase_Vocoder]\n');
fprintf(' - 子图1-2: 原始与处理后的幅度谱对比\n');
fprintf(' - 子图3-4: 原始与处理后的相位谱对比\n');
fprintf(' - 说明: 幅度应基本保持,相位经过重新累积\n');
% 设置重建用的帧移
Rs_reconstruct = Rs;
catch ME
fprintf(' [ERROR] ex6_Phase_Vocoder 执行失败!\n');
fprintf(' - 错误信息: %s\n', ME.message);
return;
end
end
fprintf('\n');
%% ========================== 步骤7:信号重建 ==============================
fprintf('----------------------------------------------------------------\n');
fprintf('[步骤 7/8] 调用 ex5_reconstruct_signal 重建信号...\n');
fprintf('----------------------------------------------------------------\n');
try
tic;
[outputSignal] = ex5_reconstruct_signal(freq_mat_modified, Lwin, Rs_reconstruct);
time_ex5 = toc;
fprintf(' [OK] ex5_reconstruct_signal 执行成功! (耗时 %.3f 秒)\n', time_ex5);
fprintf(' - 重建方法: IFFT + Hamming窗 + 重叠相加(OLA)\n');
fprintf(' - 使用帧移: %d\n', Rs_reconstruct);
fprintf(' - 输出信号长度: %d 样本点\n', length(outputSignal));
fprintf(' - 输出音频时长: %.2f 秒\n', length(outputSignal)/fs);
fprintf(' - 实际拉伸比例: %.2f 倍\n', length(outputSignal)/length(x));
catch ME
fprintf(' [ERROR] ex5_reconstruct_signal 执行失败!\n');
fprintf(' - 错误信息: %s\n', ME.message);
return;
end
fprintf('\n');
%% ========================== 步骤8:播放和绘图 ============================
fprintf('----------------------------------------------------------------\n');
fprintf('[步骤 8/8] 播放重构音频并绘制对比图...\n');
fprintf('----------------------------------------------------------------\n');
% 播放重构后的音频
fprintf(' >> 正在播放重构后的音频...\n');
fprintf(' - 预计播放时长: %.2f 秒\n', length(outputSignal)/fs);
sound(outputSignal, fs);
% 创建时间轴
t_orig = (0:length(x)-1) / fs;
t_recon = (0:length(outputSignal)-1) / fs;
% 创建图形窗口进行对比绘图(统一命名风格)
figure;
set(gcf, 'Name', '音频变速结果对比 - ex0_synthesize_all', 'NumberTitle', 'off');
% 绘制原始音频信号
subplot(2, 1, 1);
plot(t_orig, x, 'b', 'LineWidth', 0.5);
title('原始音频信号');
xlabel('时间 (秒)');
ylabel('幅度');
xlim([0, max(t_orig)]);
grid on;
% 绘制重构后的音频信号
subplot(2, 1, 2);
plot(t_recon, outputSignal, 'r', 'LineWidth', 0.5);
title('重构后的音频信号');
xlabel('时间 (秒)');
ylabel('幅度');
xlim([0, max(t_recon)]);
grid on;
fprintf('\n');
fprintf(' [图形输出 - 来自 ex0_synthesize_all]\n');
fprintf(' - 子图1: 原始音频波形 (蓝色,时长 %.2f 秒)\n', length(x)/fs);
fprintf(' - 子图2: 重构音频波形 (红色,时长 %.2f 秒)\n', length(outputSignal)/fs);
fprintf(' - 说明: 重构信号应比原始信号长 %.1f 倍\n', stretchFactor);
fprintf('\n');
%% ========================== 运行总结 =====================================
fprintf('================================================================\n');
fprintf(' 运 行 总 结\n');
fprintf('================================================================\n');
fprintf('\n');
fprintf(' [处理参数]\n');
fprintf(' - 帧长: %d | 帧移: %d | 拉伸因子: %.2f\n', Lwin, Ra, stretchFactor);
if usePhaseVocoder
fprintf(' - 处理方法: 相位声码器法 (Phase Vocoder)\n');
else
fprintf(' - 处理方法: 频域插值法\n');
end
fprintf('\n');
fprintf(' [处理结果]\n');
fprintf(' - 原始时长: %.2f 秒 -> 输出时长: %.2f 秒\n', length(x)/fs, length(outputSignal)/fs);
fprintf(' - 实际拉伸比: %.2f (目标: %.2f)\n', length(outputSignal)/length(x), stretchFactor);
fprintf('\n');
fprintf(' [生成图形汇总]\n');
fprintf(' - Figure 1: 分帧结果 - ex1_frame_signal\n');
fprintf(' - Figure 2: 加窗处理 - ex2_window_signal\n');
fprintf(' - Figure 3: 频谱分析 - ex3_compute_spectrum\n');
if usePhaseVocoder
fprintf(' - Figure 4: 相位声码器 - ex6_Phase_Vocoder\n');
else
fprintf(' - Figure 4: 频域插值 - ex4_modify_STFT\n');
end
fprintf(' - Figure 5: 音频变速结果对比 - ex0_synthesize_all\n');
fprintf('\n');
fprintf(' [执行状态] 所有步骤执行成功! 音频正在播放中...\n');
fprintf('\n');
fprintf('================================================================\n');
fprintf(' 程序运行完成\n');
fprintf('================================================================\n');
fprintf('\n');