基于matlab GUI数字音频处理系统(一)——功能概览与GUI

前言

本次项目为数字信号处理课程项目,项目全部采用matlab GUI编程,已上传至github/git_dsp

项目要求如下:

  • 至少实现 1 种音效测试分析功能(频率响应,瀑布频谱图,相位响应曲线,抗阻曲线,谐波失真曲线,互调失真曲线,音压曲线等);
  • 至少实现 1 种语音处理功能(声纹识别,关键词识别,语音去噪,声源分离等),并分别对其进行实际应用;

matlab对git的支持

为了方便修改,本次项目使用了git,并托管到github上。
matlab对Git有很好的支持,文件栏处右击源代码管理,可以直接对文件进行添加、提交、推送到github。

所用函数功能一览

名称 格式 项目内用法
abs abs(fft_sample) 求fft后样本模值
angel angle(ph_y0) 求fft后样本相角
audioplayer h.player=audioplayer(h.CSample,h.Fs) 创建音频播放器
audioread [h.Sample,h.Fs]=audioread(h.Filepath) 读取音频文件
audiowrite audiowrite() 输出音频
colorbar colorbar(ax) 创建颜色栏
colormap colormap(hot) 改变语谱图颜色
delete delete(gcf) 关闭窗口
fft fft(sample,nfft) 对样本进行快速傅里叶变换
fftshift fftshift(y) 以中心为零,频域循环移位
gcf set(gcf, 当前窗口句柄
guidata guidata(hObject, handles) 更新句柄
isempty isempty(handles.Sample) 判断样本是否为空
isstrprop str(isstrprop(str,’digit’)) 求字符串内数字
length length(sample) 求样本长度
mean mean(handles.Sample) 求样本均值
movegui movegui(gcf,’center’) 窗口居中
nargin nargin GUI输入参数个数
nargout varargout{1:nargout} GUI输出参数个数
pow2 pow2(nextpow2(sample_length)) 求2的次方
nextpow2 nextpow2(sample_length) 求最小nfft使,2^nfft≥len
round round(get(hObject,’Value’),2) 求指定有效数位约值
size size(handles.Sample) 求音频通道数
spectrogram spectrogram(sample,1024,512,nfft,fs) 绘制语谱图
std std(handles.CSample) 求样本标准差
struct struct(‘gui_Name’,mfilename, … 构造GUI参数、回调函数
varargin varargin{:} GUI可变长度输入,GUI之间参数传递
varargout varargput{:} GUI可变长度输出,GUI之间参数传递
uigetdir handles.foldername=uigetdir() 获取文件夹位置
uigetfile [filename,pathname]=uigetfile({‘*.wav; 获取路径和文件名
uiwait OpeningFcn 等待响应
uiresume 响应,继续运行
unwrap unwrap(angle(ph_y0)) 矫正相角跳变由2pi为pi

GUI编程模块解析

关于句柄

GUI函数中使用了大量的句柄,句柄类似指针,可以自己创建指向某一函数,GUI中使用句柄指向图形对象。

GUI的创建

GUi的建立基本上有两种方式,一种是使用figure创建新窗口,但这种需要对GUI部件与属性十分了解,并且不利于复杂部件的排版;第二种是使用guide可视化界面,这种方式容易上手,利于排版,部件的属性可以直观地修改,但是由于在创建时会自动生成默认参数,不利于在生成后对于一些默认属性如文件名地修改。

强烈建议在命名时一定不要随意,我在创建主窗口时比较随意地使用了dsp作为文件名,然而在之后了解到matlab本身有一个dsp类的工具箱,虽然在使用上没有什么问题,但是感觉还是不好,在改名后会出现很多问题,比如回调函数、属性等都没有改变,很难受。

解析自动生成的代码

在创建好一个GUI后,会自动生成两个同名文件,即.fig.m.fig应该是保存了设计界面时的默认属性,右击可以对界面进行修改,.m文件会自动生成窗口的参数和函数以及部件的回调函数等,下面对其中生成的代码介绍一下我浅薄的认识,这里输出音频窗口为例:

  • 主程序
function varargout = putfile(varargin)
% --- 输出音频
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @putfile_OpeningFcn, ...
                   'gui_OutputFcn',  @putfile_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
  1. 首先生成的代码中有大量注释,其中包含了各种介绍,队以初学者还是很有用的,这里我就删掉不放了,可以在GUI设置里去掉生成注释选项,虽然主函数部分还是会生成一些注释。
  2. varargoutvarargin可以用于多个GUI直接进行参数传递,并且输入与输出的个数是可变的,具体在之后的GUI参数传递中介绍。
  3. gui_Singleton表示单一运行实例,可以在GUI选择项中设置,为1时,如果之前窗口未关闭,下次运行时仍会使用该窗口;为0时,下次运行会新打开一个窗口。
  4. struct创建结构数组,定义了gui_stateGUI状态,即名字、运行实例、打开函数、输出函数、布局以及回调函数
  5. str2func(varargin{1})取得创建figure和部件的CreateFcn回调函数。
  6. 最后是执行gui_mainfcn函数,输入参数为gui_State回调函数句柄,生成整个界面,判断条件为输出参数个数,如果有输出就将gui_mainfcn结果分别输出。
  • 接下来运行打开函数
function putfile_OpeningFcn(hObject, eventdata, handles, varargin)
movegui(gcf,'center');
handles.putSample=varargin{1}; %保存输入
% 初始化采样率为输入音频采样率
handles.putFs=varargin{2};
str=get(handles.Fs_popupmenu,'String');
for val=1:5
    if str2double(str{val})==handles.putFs
        break
    end
end
set(handles.Fs_popupmenu,'Value',val);

% Update handles structure
guidata(hObject, handles);

% UIWAIT makes putfile wait for user response (see UIRESUME)
% uiwait(handles.figure1); % 等待输出响应
  1. 该函数创建了打开窗口后的初始化属性,我们也可以自己在这里初始化的一些参数。
  2. hObject为该部件的句柄(这里指该figure),eventdata存储事件信息(面向对象),handles是一个句柄类,包含了所有部件,可以用于回调函数之间的参数传递 。
  3. guidata(hObject, handles)用于函数之间参数传递,如果有handles类的参数生成或改变,必须添加。
  • 运行输出函数
function varargout = putfile_OutputFcn(hObject, eventdata, handles) 
varargout{1} = 0; % 不需要输出

该函数传递窗口输出,默认为varargout{1}=handles.output,指向的是原OpeningFcn中的handles.output = hObject(我删掉了),由于我的这个窗口不需要输出,设置为0。

GUI参数传递

详细参考Matlab的GUI参数传递方式总结

函数与函数之间

  1. 使用global定义全局变量,需要在每次使用前定义一次。
  2. 使用handles类,我基本上全部使用了handles,这样方便、清晰,但是一定要在后面加上guidata(hObject, handles),不然参数传递不出去。

GUI与GUI之间

这里以主figuredsp和输出音频putfile为例,需要对dsp中的样本handles.CSample输出,在dsp中

putfile(handles.CSample,handles.Fs); % 传递样本与采样率

这时putfile接收到了这两个数据,需要在OpeningFcn(如上面的程序)添加

handles.putSample=varargin{1};
handles.putFs=varargin{2};

这样就可以在putfile调用这两个数据。

由于我这里putfile没有输出,就将handles.out去掉,varargout{1} = 0,保证关闭窗口时不会报错,如果需要输出的话,需要在putfile的OpeningFcn中添加

uiwait(handles.figure1); % handles.figure1为putfile窗口句柄,该语句被默认注释掉

这样可以保证再打开窗口后不会直接运行OutputFcn,在后面需要输出参数的地方加上uiresume(handles.figure1)

function varargout = putfile_OutputFcn(hObject, eventdata, handles) 
varargout{1} = handles.out1;

function out(hObject, eventdata, handles))
handles.out1=out1;
uiresume(handles.figure1);

这样在输出参数设置好后,接着会运行OutputFcn进行输出。

注意如果使用了uiwait,在输出参数没有设定时(即没有运行uiresume),关闭窗口会报错。

参考关于GUI中uiwait关闭出错的问题

如果GUI不需要输出,OutputFcn中可以写varargout{1} = 0;
如果GUI需要有输出,OutputFcn中可以做一个判断isempty(handles),如果返回值是1,说明是点击右上角的关闭按钮退出的,如果返回值是0,说明是使用uiresume退出的。这两种情况返回的varargout根据需要分别设定。

matlab GUI面向对象(oop)编程

GUI中部件属性使用类来创建的,涉及到了matlab opp编程,这里先不细研究。

在编写程序时使用面向过程有些方法比较繁琐,很多函数需要重复调用,考虑之后探究一下matlab opp来优化这些问题,比如使用监听器之类。