基于Matlab的音乐数字均衡器设计
1. 功能需求
1.1 概述
均衡器通过对各种不同频率声音信号的分别调节来补偿扬声器和声场的缺陷,起到补偿和修饰各种声源的作用。均衡器的实现方法有多种,比较简单的是设计一组带通滤波器,提取不同频段的信号并按照不同的要求分别放大再叠加起来。而Matlab提供的函数可以方便地设计FIR或IIR滤波器。另外,基于Matlab的图形用户界面(GUI)编程技术可以方便地设计均衡器的交互界面。
本文使用matlab完成了音乐数字均衡器的设计,采用FIR滤波器设计了一组带通滤波器,以提取不同频段的信号并按照不同的要求分别放大再叠加,并使用matlab中的APP工具设计了用户交互界面,实现了对MP3音乐进行分频段修饰的功能。
1.2 设计内容
- MP3音乐文件的读取
选取一个MP3音乐文件,利用mp3read函数读取文件的采样率(如44.1kHz)、采样数、声道数、比特等信息。在此基础上,截取单声道的15秒片段并转化为数据序列。
- 滤波器组的设计
根据下表的频段划分,利用fir2或firls函数设计滤波器组,滤波器的阶数可选100。
频段(Hz) | 音感特征 |
---|---|
30~60 | 沉闷 |
60~100 | 沉重 |
100~200 | 丰满 |
200~500 | 力度 |
500~1k | 明朗 |
1k~2k | 透亮 |
2k~4k | 尖锐 |
4k~8k | 清脆 |
8k~16k | 纤细 |
- 均衡器GUI的设计
利用按键、滚动条实现音乐文件选择、曲段选择、各频段音量大小设定、均衡前后音乐播放(wavplay)、均衡前后音乐的时域波形和频谱显示以及预设模式(比如流行、摇滚、古典)选择等功能。
2. 设计过程
2.1 文件读取
在APP设计界面中选取一个按钮用于MP3文件读取,在其回调函数中使用uigetfile函数弹出一个文件选择对话框,让用户用户从当前目录或指定路径中选择一个.mp3文件,然后将选择的文件名存储在FileName中,路径存储在PathName中。接下来使用fullfile函数将文件名和路径组合成一个完整的文件路径,存储在file_name中,使用一个编辑框组件edit1显示选定的文件名。使用mp3read函数读取音乐文件的长度,并将结果存储在SIZ中,从SIZ中提取文件的长度显示在编辑框组件edit2中。
使用mp3read函数读取音乐文件的前15s ( 15 * 44100 = 661500点),绘制读取音频数据的时域波形,然后对读取的数据进行FFT处理并绘制其频谱图。
文件读取按钮的回调函数如下:
function ButtonPushed(app, event)
global file_name;
global len;
global fs;
global x;
[FileName, PathName, ~] = uigetfile({'*.mp3'}, '选择音乐文件'); %打开文件选择窗口
app.edit1.Value = sprintf('%s', FileName); % 在编辑框edit1中显示文件名
file_name = fullfile(PathName, FileName); % 组合完整文件路径
SIZ = mp3read(file_name, 'size'); % 获取文件总长度
len = SIZ(1);
app.edit2.Value = sprintf('%.0f', len); % 在编辑框edit2中显示文件总长度
[x, fs, nbits, OPTS] = mp3read(file_name, 661500, 1, 1, 1); % 读取前15s,单声道,从头开始
dt = 1.0/fs; % 采样间隔
N = length(x); % 采样点数
T = N*dt; % 截取时间
t = linspace(0, T, N); % 生成时域波形的横坐标
plot(app.UIAxes, t, x); % 绘制时域波形
Amax = max(x); % 获取数据最大值
ylim(app.UIAxes, [-Amax, Amax]); % 对时域波形纵坐标做限制
% 绘制频谱图
x_fft = fft(x);
% 计算单边频谱
P2 = abs(x_fft/N); % 计算双边频谱的幅度
P1 = P2(1:N/2+1); % 取单边频谱
P1(2:end-1) = 2*P1(2:end-1); % 双边频谱的幅度是单边频谱的两倍
f_norm = 2*(0:(N/2))/N; % 计算归一化频率(0~1)
plot(app.UIAxes4, f_norm, P1); % 绘制幅度谱
end
2.2 进度条设计
在APP设计界面中插入一个滑块组件作为音乐播放的进度条,然后计算进度值所对应的延迟时间,即mp3read函数的最后一个参数OPTS,表示从delay开始读取15s。接下来绘制读取数据的时域波形,并对其进行FFT处理,然后绘制其频谱。以实现拖动进度条后,从进度条对应的位置开始读取15s音频的效果。
进度条的回调函数如下:
function SliderValueChanged(app, event)
global file_name;
global len;
global fs;
global x;
value = app.Slider.Value;
delay = floor((len - 661500) * value);
if delay < 1
delay = 1;
end
[x, fs, nbit, OPTS] = mp3read(file_name, 661500, 1, 1, delay);
dt = 1.0/fs;
N = length(x);
T = N*dt;
t = linspace(0, T, N);
plot(app.UIAxes, t, x);
Amax = max(x);
ylim(app.UIAxes, [-Amax, Amax]);
% 绘制频谱图
x_fft = fft(x);
% 计算单边频谱
P2 = abs(x_fft/N); % 计算双边频谱的幅度
P1 = P2(1:N/2+1); % 取单边频谱
P1(2:end-1) = 2*P1(2:end-1); % 双边频谱的幅度是单边频谱的两倍
f_norm = 2*(0:(N/2))/N; % 归一化频率从0到1
plot(app.UIAxes4, f_norm, P1); % 绘制幅度谱(转换为分贝单位)
end
2.3 播放原声
在APP设计界面中插入一个按钮,在其回调函数中使用sound函数来播放未经处理的音频信号。
此按钮的回调函数如下:
function Button_2Pushed(app, event)
global x;
global fs;
sound(x, fs);
end
2.4 均衡器设计
在APP设计界面中插入9个滑块,分别表示9个不同频段,通过滑块的值来改变相应频段信号的幅值。再插入一个按钮,用于读取上述滑块的值并对音频信号进行滤波。滤波环节根据9个频段的归一化频率f和f所对应频点处的幅值m,使用fir2函数生成一个FIR带通滤波器组,再使用filter函数对读取的音频信号施加上述滤波器组进行滤波。最后,绘制滤波后信号的时域波形,并对其进行FFT处理后绘制频谱图。
数字均衡按钮的回调函数如下:
function Button_3Pushed(app, event)
global fs;
global x;
global y;
v1 = app.Slider1.Value;
v2 = app.Slider2.Value;
v3 = app.Slider3.Value;
v4 = app.Slider4.Value;
v5 = app.Slider5.Value;
v6 = app.Slider6.Value;
v7 = app.Slider7.Value;
v8 = app.Slider8.Value;
v9 = app.Slider9.Value;
f = [0 0.0028 0.0057 0.0113 0.0227 0.0454 0.0907 0.1814 0.3628 0.7256 1];
m = [0 v1 v2 v3 v4 v5 v6 v7 v8 v9 0];
b = fir2(100, f, m);
y = filter(b, 1, x);
N = length(x);
dt = 1.0/fs;
T = N * dt;
t = linspace(0, T, N);
plot(app.UIAxes2, t, y); %绘制时域波形
Amax = max(y);
ylim(app.UIAxes2, [-Amax, Amax]);
% 绘制频谱图
y_fft = fft(y);
% 计算单边频谱
P2 = abs(y_fft/N); % 计算双边频谱的幅度
P1 = P2(1:N/2+1); % 取单边频谱
P1(2:end-1) = 2*P1(2:end-1); % 双边频谱的幅度是单边频谱的两倍
f_norm = 2*(0:(N/2))/N; % 归一化频率从0到1
plot(app.UIAxes3, f_norm, P1); % 绘制幅度谱(转换为分贝单位)
end
均衡器运行效果
只保留20~200Hz时:
只保留4k~16kHz时:
2.5 播放滤波后声音
在APP设计界面中插入一个按钮,在其回调函数中使用sound函数播放滤波后音频信号。此按钮回调函数如下:
function Button_4Pushed(app, event)
global y;
global fs;
sound(y, fs);
end
2.6 预设模式
在APP设计界面中插入一个下拉框组件作为预设模式选择,并设置自定义、摇滚、流行、古典、民谣、ACG、古典等选项。其中每一种音乐模式的EQ曲线参考网易云音乐均衡器中的预设值。
网易云音乐内置均衡器EQ预设值(以摇滚为例):
然后通过下式将分贝db值转换为线性幅度值A:
A
=
1
0
d
b
10
A=10^{\frac{db}{10}}
A=1010db
取向量A中的最大值Amax,对A进行归一化得到fir2函数的第三个参数m(0~1):
m
=
A
A
max
m=\frac{A}{A_{\max}}
m=AmaxA
针对每一种预设模式生成相应的滤波器组,再通过filter函数对截取的音频信号进行滤波,最后绘制滤波后信号的时域波形,并对其进行FFT处理后绘制频谱图。
预设模式下拉框组件回调函数如下:
function DropDownValueChanged(app, event)
global fs;
global x;
global y;
value = app.DropDown.Value;
f = [0 0.0028 0.0057 0.0113 0.0227 0.0454 0.0907 0.1814 0.3628 0.7256 1];
N = length(x);
dt = 1.0/fs;
T = N * dt;
t = linspace(0, T, N);
f_norm = 2*(0:(N/2))/N; % 归一化频率从0到1
% 根据选中值显示消息
switch value
case '摇滚'
m = [1 0.79 0.79 0.5 0.4 0.32 0.4 0.5 0.63 1 0];
b = fir2(100, f, m);
y = filter(b, 1, x);
plot(app.UIAxes2, t, y); %绘制时域波形
Amax = max(y);
ylim(app.UIAxes2, [-Amax, Amax]);
% 绘制频谱图
y_fft = fft(y);
% 计算单边频谱
P2 = abs(y_fft/N); % 计算双边频谱的幅度
P1 = P2(1:N/2+1); % 取单边频谱
P1(2:end-1) = 2*P1(2:end-1); % 双边频谱的幅度是单边频谱的两倍
plot(app.UIAxes3, f_norm, P1); % 绘制幅度谱
case '流行'
m = [0.25 0.32 0.4 0.5 1 1 1 0.79 0.63 1 0];
b = fir2(100, f, m);
y = filter(b, 1, x);
plot(app.UIAxes2, t, y); %绘制时域波形
Amax = max(y);
ylim(app.UIAxes2, [-Amax, Amax]);
% 绘制频谱图
y_fft = fft(y);
% 计算单边频谱
P2 = abs(y_fft/N); % 计算双边频谱的幅度
P1 = P2(1:N/2+1); % 取单边频谱
P1(2:end-1) = 2*P1(2:end-1); % 双边频谱的幅度是单边频谱的两倍
plot(app.UIAxes3, f_norm, P1); % 绘制幅度谱
case '古典'
m = [0 1 0.79 0.63 0.32 0.32 0.4 0.5 0.79 1 0];
b = fir2(100, f, m);
y = filter(b, 1, x);
plot(app.UIAxes2, t, y); %绘制时域波形
Amax = max(y);
ylim(app.UIAxes2, [-Amax, Amax]);
% 绘制频谱图
y_fft = fft(y);
% 计算单边频谱
P2 = abs(y_fft/N); % 计算双边频谱的幅度
P1 = P2(1:N/2+1); % 取单边频谱
P1(2:end-1) = 2*P1(2:end-1); % 双边频谱的幅度是单边频谱的两倍
plot(app.UIAxes3, f_norm, P1); % 绘制幅度谱
case '民谣'
m = [0.32 0.63 0.32 0.25 0.4 0.79 1 1 0.63 1 0];
b = fir2(100, f, m);
y = filter(b, 1, x);
plot(app.UIAxes2, t, y); %绘制时域波形
Amax = max(y);
ylim(app.UIAxes2, [-Amax, Amax]);
% 绘制频谱图
y_fft = fft(y);
% 计算单边频谱
P2 = abs(y_fft/N); % 计算双边频谱的幅度
P1 = P2(1:N/2+1); % 取单边频谱
P1(2:end-1) = 2*P1(2:end-1); % 双边频谱的幅度是单边频谱的两倍
plot(app.UIAxes3, f_norm, P1); % 绘制幅度谱
case 'ACG'
m = [0.63 1 0.63 0.25 0.25 0.5 1 0.5 0.5 1 0];
b = fir2(100, f, m);
y = filter(b, 1, x);
plot(app.UIAxes2, t, y); %绘制时域波形
Amax = max(y);
ylim(app.UIAxes2, [-Amax, Amax]);
% 绘制频谱图
y_fft = fft(y);
% 计算单边频谱
P2 = abs(y_fft/N); % 计算双边频谱的幅度
P1 = P2(1:N/2+1); % 取单边频谱
P1(2:end-1) = 2*P1(2:end-1); % 双边频谱的幅度是单边频谱的两倍
plot(app.UIAxes3, f_norm, P1); % 绘制幅度谱
case '爵士'
m = [0.79 0.79 0.5 0.63 0.25 0.32 0.4 0.5 0.63 1 0];
b = fir2(100, f, m);
y = filter(b, 1, x);
plot(app.UIAxes2, t, y); %绘制时域波形
Amax = max(y);
ylim(app.UIAxes2, [-Amax, Amax]);
% 绘制频谱图
y_fft = fft(y);
% 计算单边频谱
P2 = abs(y_fft/N); % 计算双边频谱的幅度
P1 = P2(1:N/2+1); % 取单边频谱
P1(2:end-1) = 2*P1(2:end-1); % 双边频谱的幅度是单边频谱的两倍
plot(app.UIAxes3, f_norm, P1); % 绘制幅度谱
end
end
不同预设模式下的运行效果
摇滚模式:
流行模式:
古典模式:
民谣模式:
3. 滤波器频率特性
以民谣模式为例,首先利用下式将9个频段的截止频率转换为归一化频率。(采样频率fs为44100Hz)。
f
n
o
r
m
=
f
f
s
/
2
=
2
f
f
s
=
2
×
f
44100
=
f
22050
f_{norm}=\frac{f}{f_s/2}=2\frac{f}{f_s}=2\times \frac{f}{44100}=\frac{f}{22050}
fnorm=fs/2f=2fsf=2×44100f=22050f
各频段截止频率归一化:
频率f(Hz) | 60 | 100 | 200 | 500 | 1000 | 2000 | 4000 | 8000 | 16000 |
---|---|---|---|---|---|---|---|---|---|
fnorm(0~1) | 0.00272 | 0.00454 | 0.00907 | 0.02268 | 0.04535 | 0.0907 | 0.18141 | 0.36281 | 0.72562 |
再将从网易云音乐均衡器中得到的EQ值转换为归一化的线性幅度:
db值 | 0 | 3 | 0 | -1 | 1 | 4 | 5 | 3 | 0 | 2 |
---|---|---|---|---|---|---|---|---|---|---|
线性幅值M | 1.00 | 2.00 | 1.00 | 0.79 | 1.26 | 2.51 | 3.16 | 2.00 | 1.00 | 1.58 |
归一化m | 0.32 | 0.63 | 0.32 | 0.25 | 0.40 | 0.79 | 1.00 | 1.00 | 0.63 | 1.00 |
在matlab中编写脚本,将上述f和m传入fir2函数设计FIR滤波器,然后使用freqz函数得到此滤波器的频率响应,最后绘制其幅频特性和相频特性曲线。脚本代码如下:
f = [0 0.0028 0.0057 0.0113 0.0227 0.0454 0.0907 0.1814 0.3628 0.7256 1];
m = [0.32 0.63 0.32 0.25 0.4 0.79 1 1 0.63 1 0];
% 使用fir2函数设计FIR滤波器
b = fir2(100, f, m);
freqz(b, 1);
此滤波器(民谣模式)的幅频响应和相频响应如下: