classdef AudioProcessor < matlab.apps.AppBase
% 属性定义(存储音频数据和参数)
properties (Access = public)
UIFigure matlab.ui.Figure
ImportButton matlab.ui.control.Button
ProcessButton matlab.ui.control.Button
SaveButton matlab.ui.control.Button
GainSlider matlab.ui.control.Slider
GainLabel matlab.ui.control.Label
DelaySlider matlab.ui.control.Slider
DelayLabel matlab.ui.control.Label
EchoAmpSlider matlab.ui.control.Slider
EchoAmpLabel matlab.ui.control.Label
NoiseSlider matlab.ui.control.Slider
NoiseLabel matlab.ui.control.Label
WaveformAxes1 matlab.ui.control.UIAxes
WaveformAxes2 matlab.ui.control.UIAxes
SpectrumAxes1 matlab.ui.control.UIAxes
SpectrumAxes2 matlab.ui.control.UIAxes
StatusLabel matlab.ui.control.Label
OriginalAudio % 原始音频数据
ProcessedAudio % 处理后音频数据
SampleRate % 采样率
IsMono % 是否单声道
end
% 构造函数(初始化界面)
methods (Access = private)
function createComponents(app)
% 创建主窗口
app.UIFigure = uifigure('Visible', 'off');
app.UIFigure.Position = [100, 100, 800, 600];
app.UIFigure.Name = '音频处理系统';
% 导入按钮
app.ImportButton = uibutton(app.UIFigure, 'push');
app.ImportButton.Position = [20, 540, 100, 30];
app.ImportButton.Text = '导入音频';
app.ImportButton.ButtonPushedFcn = @(src, event) app.ImportButtonPushed(src, event);
% 处理按钮
app.ProcessButton = uibutton(app.UIFigure, 'push');
app.ProcessButton.Position = [140, 540, 100, 30];
app.ProcessButton.Text = '开始处理';
app.ProcessButton.ButtonPushedFcn = @(src, event) app.ProcessButtonPushed(src, event);
app.ProcessButton.Enable = 'off';
% 保存按钮
app.SaveButton = uibutton(app.UIFigure, 'push');
app.SaveButton.Position = [260, 540, 100, 30];
app.SaveButton.Text = '保存结果';
app.SaveButton.ButtonPushedFcn = @(src, event) app.SaveButtonPushed(src, event);
app.SaveButton.Enable = 'off';
% 增益滑块
app.GainLabel = uilabel(app.UIFigure);
app.GainLabel.Position = [400, 545, 50, 22];
app.GainLabel.Text = '增益(dB)';
app.GainSlider = uislider(app.UIFigure);
app.GainSlider.Position = [460, 540, 200, 30];
app.GainSlider.Limits = [0, 12];
app.GainSlider.Value = 6;
app.GainSlider.MajorTicks = [0, 6, 12];
app.GainSlider.MajorTickLabels = {'0', '6', '12'};
% 延迟滑块
app.DelayLabel = uilabel(app.UIFigure);
app.DelayLabel.Position = [400, 515, 50, 22];
app.DelayLabel.Text = '延迟(ms)';
app.DelaySlider = uislider(app.UIFigure);
app.DelaySlider.Position = [460, 510, 200, 30];
app.DelaySlider.Limits = [0, 500];
app.DelaySlider.Value = 300;
app.DelaySlider.MajorTicks = [0, 250, 500];
app.DelaySlider.MajorTickLabels = {'0', '250', '500'};
% 回音衰减滑块
app.EchoAmpLabel = uilabel(app.UIFigure);
app.EchoAmpLabel.Position = [400, 485, 80, 22];
app.EchoAmpLabel.Text = '回音衰减系数';
app.EchoAmpSlider = uislider(app.UIFigure);
app.EchoAmpSlider.Position = [460, 480, 200, 30];
app.EchoAmpSlider.Limits = [0, 1];
app.EchoAmpSlider.Value = 0.5;
app.EchoAmpSlider.MajorTicks = [0, 0.5, 1];
app.EchoAmpSlider.MajorTickLabels = {'0', '0.5', '1'};
% 噪声幅度滑块
app.NoiseLabel = uilabel(app.UIFigure);
app.NoiseLabel.Position = [400, 455, 80, 22];
app.NoiseLabel.Text = '噪声幅度系数';
app.NoiseSlider = uislider(app.UIFigure);
app.NoiseSlider.Position = [460, 450, 200, 30];
app.NoiseSlider.Limits = [0, 0.2];
app.NoiseSlider.Value = 0.1;
app.NoiseSlider.MajorTicks = [0, 0.1, 0.2];
app.NoiseSlider.MajorTickLabels = {'0', '0.1', '0.2'};
% 波形图坐标轴1(原始立体声)
app.WaveformAxes1 = uiaxes(app.UIFigure);
app.WaveformAxes1.Position = [20, 320, 760, 180];
app.WaveformAxes1.Title = '原始音频(立体声)';
app.WaveformAxes1.XLabel = '时间(s)';
app.WaveformAxes1.YLabel = '振幅';
% 波形图坐标轴2(单声道)
app.WaveformAxes2 = uiaxes(app.UIFigure);
app.WaveformAxes2.Position = [20, 120, 760, 180];
app.WaveformAxes2.Title = '单声道音频';
app.WaveformAxes2.XLabel = '时间(s)';
app.WaveformAxes2.YLabel = '振幅';
% 频谱图坐标轴(可扩展)
app.SpectrumAxes1 = uiaxes(app.UIFigure);
app.SpectrumAxes1.Position = [20, 320, 760, 180];
app.SpectrumAxes1.Visible = 'off'; % 初始隐藏,处理后显示
app.SpectrumAxes2 = uiaxes(app.UIFigure);
app.SpectrumAxes2.Position = [20, 120, 760, 180];
app.SpectrumAxes2.Visible = 'off';
% 状态标签
app.StatusLabel = uilabel(app.UIFigure);
app.StatusLabel.Position = [20, 50, 760, 22];
app.StatusLabel.Text = '请点击"导入音频"开始';
app.StatusLabel.HorizontalAlignment = 'center';
% 显示窗口
app.UIFigure.Visible = 'on';
end
end
% 构造函数
methods (Access = public)
function app = AudioProcessor
% 创建组件
createComponents(app);
end
end
% 导入音频按钮回调
methods (Access = private)
function ImportButtonPushed(app, ~)
% 打开文件选择对话框
[filePath, fileName] = uigetfile({'*.wav', 'WAV音频文件 (*.wav)'});
if filePath == 0
return; % 未选择文件
end
try
% 读取音频文件
[y, Fs] = audioread(fullfile(filePath, fileName));
app.OriginalAudio = y;
app.SampleRate = Fs;
app.IsMono = size(y, 2) == 1;
% 显示原始波形
plotOriginalWaveforms(app, y, Fs);
% 启用处理按钮
app.ProcessButton.Enable = 'on';
app.StatusLabel.Text = ['已导入: ', fileName];
catch e
uialert(app.UIFigure, ['读取失败: ', e.message], '错误');
end
end
% 开始处理按钮回调
function ProcessButtonPushed(app, ~)
if isempty(app.OriginalAudio)
uialert(app.UIFigure, '请先导入音频', '提示');
return;
end
y = app.OriginalAudio;
Fs = app.SampleRate;
% 1. 双声道转单声道
if ~app.IsMono
y_mono = mean(y, 2);
else
y_mono = y;
end
% 2. 增益调节(dB转线性因子)
gain_db = app.GainSlider.Value;
gain_factor = 10^(gain_db/20); % +6dB = 2倍,+12dB=4倍
y_gain = y_mono * gain_factor;
% 3. 混音处理(添加白噪声)
noise_amp = app.NoiseSlider.Value;
noise = noise_amp * randn(size(y_gain));
y_mix = y_gain + noise;
% 4. 回音效果
echo_delay_ms = app.DelaySlider.Value;
echo_delay = echo_delay_ms / 1000; % 转换为秒
echo_amp = app.EchoAmpSlider.Value;
y_echo = addEcho(y_mix, Fs, echo_delay, echo_amp);
% 5. 减抽样处理
new_Fs = Fs/2; % 半抽样率
y_down = resample(y_echo, new_Fs, Fs); % 防混叠重采样
% 6. IIR低通滤波器设计
fc = 4000; % 截止频率4kHz
[b, a] = butter(4, fc/(new_Fs/2), 'low'); % 4阶巴特沃斯滤波器
y_filtered = filter(b, a, y_down);
% 保存处理结果
app.ProcessedAudio = y_filtered;
% 显示处理后的波形和频谱
plotProcessedResults(app, y_mono, y_filtered, Fs, new_Fs);
% 启用保存按钮
app.SaveButton.Enable = 'on';
app.StatusLabel.Text = '处理完成!';
end
% 保存结果按钮回调
function SaveButtonPushed(app, ~)
if isempty(app.ProcessedAudio)
uialert(app.UIFigure, '无处理结果可保存', '提示');
return;
end
[filePath, fileName] = uiputfile({'*.wav', 'WAV文件 (*.wav)'}, ...
'保存处理后的音频', 'processed_audio.wav');
if filePath == 0
return; % 未选择保存
end
try
audiowrite(fullfile(filePath, fileName), app.ProcessedAudio, app.SampleRate/2);
uialert(app.UIFigure, ['已保存至: ', fileName], '成功');
catch e
uialert(app.UIFigure, ['保存失败: ', e.message], '错误');
end
end
% 绘制原始波形
function plotOriginalWaveforms(app, y_stereo, Fs)
t = (0:length(y_stereo)-1)/Fs;
% 显示原始立体声波形
cla(app.WaveformAxes1);
if size(y_stereo, 2) == 2
plot(app.WaveformAxes1, t, y_stereo(:,1), 'b', t, y_stereo(:,2), 'r');
legend(app.WaveformAxes1, '左声道', '右声道');
else
plot(app.WaveformAxes1, t, y_stereo);
legend(app.WaveformAxes1, '单声道');
end
title(app.WaveformAxes1, '原始音频波形');
xlabel(app.WaveformAxes1, '时间(s)');
ylabel(app.WaveformAxes1, '振幅');
grid(app.WaveformAxes1, 'on');
% 显示单声道波形(若为立体声)
if size(y_stereo, 2) == 2
y_mono = mean(y_stereo, 2);
cla(app.WaveformAxes2);
plot(app.WaveformAxes2, t, y_mono);
title(app.WaveformAxes2, '单声道转换后波形');
xlabel(app.WaveformAxes2, '时间(s)');
ylabel(app.WaveformAxes2, '振幅');
grid(app.WaveformAxes2, 'on');
else
app.WaveformAxes2.Visible = 'off';
end
end
% 绘制处理结果(波形和频谱)
function plotProcessedResults(app, y_orig, y_proc, Fs, new_Fs)
% 隐藏原始波形,显示频谱
app.WaveformAxes1.Visible = 'off';
app.WaveformAxes2.Visible = 'off';
app.SpectrumAxes1.Visible = 'on';
app.SpectrumAxes2.Visible = 'on';
% 绘制原始音频频谱
plotSpectrum(app, y_orig, Fs, app.SpectrumAxes1);
title(app.SpectrumAxes1, '原始音频频谱');
% 绘制处理后音频频谱
plotSpectrum(app, y_proc, new_Fs, app.SpectrumAxes2);
title(app.SpectrumAxes2, '处理后音频频谱');
end
% 频谱分析函数
function plotSpectrum(app, y, Fs, axesHandle)
N = 2^nextpow2(length(y));
f = Fs*(0:(N/2))/N;
Y = fft(y, N);
P = abs(Y/N).^2; % 功率谱密度
cla(axesHandle);
plot(axesHandle, f, 10*log10(P(1:N/2+1))); % 转换为dB
xlabel(axesHandle, '频率(Hz)');
ylabel(axesHandle, '功率谱密度(dB/Hz)');
xlim(axesHandle, [0, Fs/2]);
grid(axesHandle, 'on');
end
end
% 核心音频处理函数(与原逻辑一致)
methods (Access = private)
function y_out = addEcho(y_in, Fs, delay, amp)
delay_samples = round(delay * Fs);
y_out = y_in;
% 避免数组越界
max_idx = length(y_in) - delay_samples;
if max_idx > 0
y_out(delay_samples+1:end) = y_out(delay_samples+1:end) + amp * y_in(1:max_idx);
end
end
end
end