蝙蝠侠这部电影中,最让人着迷的不是男主蝙蝠侠,因为他太严肃了(Why so serious?),不好玩;我反而很喜欢小丑莱杰这个角色,够sick,经常口出狂言,却又发人深省。
既然莱杰这样发言了,那么为了爱与和平,我们这些(音)乐(白)痴就要开始曲线救国了!
众所周知,闲下来的人类总会迸发出无穷的创造力去让生活变得丰富有趣。
好比咖喱与米饭的完美碰撞,
咖啡与牛奶的两厢情悦,
烤鸭与甜面酱的难分难离,
以及Matlab对音乐的科学透析!
那么今天,我们就一起来看看Matlab如何和Music迸发出爱——的火花!
我们将采取渐进式的编程路径,实现三个Matlab版本的mp3音乐播放器:
(1)简单版:
简单得只有按钮。
(2)复杂版:
在按钮的基础上,增加了音乐的波形显示功能,时间进度滑条和音量控制滑条。
(3)模拟千千静音的高逼格版本,包括三个窗口:
播放窗口 + 文件管理窗口 + 歌词窗口。
咳咳,好了,让我们一起开始这段红娘之旅!
------------------------- 1. 简单版 -----------------------
我们先来实现第一个简单版本的Matlab音乐播放器。
这个版本我们就不考虑多个音乐文件播放的问题,选择一个音乐文件(就决定是你了!Canon!卡农,它的名字叫做'CanonInD.mp3'),把它放到当前的文件夹中即可。这个文件夹是我专门为利用Matlab GUI写mp3播放器的项目新建的,具体位置在:
D:MatlabPrograms_AdvancedProject_mp3player
从资源管理器中,把项目文件夹所在的路径,拷贝到Matlab的工作目录中。
接着通过点击Matlab界面左上角的 "新建脚本"按钮来创建一个脚本文件
剧透一下,就是下面的palyCinD.m(你可以任意取你喜欢的名字),注意使用Function-end作为开头与结尾。你只要输入function y = playCinD(~),这个时候你点击保存,这个函数引导语的函数名就会自动写入到弹出的保存对话框中的文件名编辑框里边。
这个时候你点保存,文件名就自动从untitiled.m变成了playCinD.m 。
function y = playCinD(~)
y = 1;
end
创建好脚本了,第一步做什么呢?
当然是,让相亲的双方相互遇见啦!
第一步,读取音乐文件。
% prepare the mp3 filename
mp3FileName = 'CanonInD.mp3';
% read the mp3 file
[mp3Y,mp3Fs] = audioread(mp3FileName);
% create a mp3p object, which can be manipulated
mp3p = audioplayer(mp3Y,mp3Fs);
第1句语言,告诉Matlab它de对象的名字;
第2句语言,使用audioread语言将它对象的采样(mp3Y)与频率(mp3Fs)读取进来。
第3句语言,设立一个接头暗号,用来播放Matlab读取到的音乐数据(采样和频率)。这个相当于把读取进来的mp3数据和频率信息,纳入到一个完整的对象。
那么相亲的背景材料都已经准备好了,让我们给他们创造一个约会的好地方吧!
第二步,建立一个控制窗口,并设立两个控制按钮。
% create a figure and set its properties
hFigure = figure(1);
set(hFigure, 'position',[100 50 400 400]);
% create two Pushbuttons, Stop and Play
hPushbutton_Stop = uicontrol('Style','Pushbutton', 'String','Stop', 'Position',[50 100 100 30]);
hPushbutton_Play = uicontrol('Style','Pushbutton', 'String','Play', 'Position',[200 100 100 30]);
第1句语言,建立一个名叫1的窗口;
第2句语言,设定好这个窗口的位置坐标。(你可以把控会场的位置和大小哦!)
第3、4句语言,设计好两个控件按钮,用‘Style’表明属性是‘Pushbutton’,用‘String’写上控件是干什么用的,再设置位置。
这时候两个控件还没有写对用的功能程序,所以现在是摆设哦,点击没有任何反应。
嗯嗯不错,对象有了,地方有了,现在红娘们可以开始约会控场了。
我们的目标是,当用户想要开始播放音乐的时候,小M就可以播放音乐,用户想要暂停音乐的时候,小M 就可以停止音乐,用户想要从头开始听音乐时,小M 又乖乖地让对象回到原点。
红娘的功能就是media用户的操作(控件端)和执行对应的功能(函数端)。
那我们该怎么办呢?
别急,我们还需要有一个跟随变量,以了解当前所处的状态。
你看,我们上述目标就很明确地表述了三种约会状态:开始约会、停止约会、重新约会。那么我们给这三个状态进行一个分类就好啦~
这时候就要我们的指标数字,一个伟大的助攻出场!
第三步一开始,设立指标数字mState,这个参数就是为了在执行的时候判断执行的状态:stop vs. play vs. pause。
% mState: 0 == stop vs. 1 == play vs. 2 == pause
mState = 0;
我们假设一个叫做mState的指标,它有三个值,分别是0,1,2。当mState=0的时候,表示原始的音乐状态,什么都没有,mState=1的时候,就是播放的状态,mState=2的时候就是暂停的状态!
在约会开始之前,小M 和乐曲都没有交流过呢,所以乐曲处于安静状态,我们先给mState赋值为0 。
这样一来,思虑就很清晰了呢!
第四步,让我们来给“play”控件和“stop”控件加上功能吧!这个就是著名的绑定机制!红娘就是要media表层的按钮控件,和底层的功能函数。
% Binding mechanism
set(hPushbutton_Stop, 'Callback',@hPushbutton_StopFcn);
set(hPushbutton_Play, 'Callback',@hPushbutton_PlayFcn);
为两个控件追加函数功能,括号里分别是“名字,属性,@调用函数名称”
先来设计一下“hPushbutton_Stop”的函数“hPushbutton_StopFcn”。
% function --> hPushbutton_StopFcn
function hPushbutton_StopFcn(hObjecgt, hEvent, handles)
if mState == 1
stop(mp3p);
mState = 0;
set(hPushbutton_Play, 'String','Play');
else
if mState == 2
stop(mp3p);
mState = 0;
set(hPushbutton_Play, 'String','Play');
else
fprintf('passn');
end
end
end
if作为判断语句在这里成为了逻辑的向导。
这里的逻辑需要大家理解一下,初始的时候mState==0,如果此刻点击按钮,对应就会什么都不需要做 fprintf('passn'));如果这个时候是处于mState == 1,说明处于播放状态,此刻点击按钮,将会停止播放音乐;如果这时候处于mState == 2,说明处于暂停状态,此刻点击会停止播放音乐。
再来设计一下“hPushbutton_Play”的函数“hPushbutton_PlayFcn”。
% function --> hPushbutton_PlayFcn
function hPushbutton_PlayFcn(hObjecgt, hEvent, handles)
if mState == 1
pause(mp3p);
mState = 2;
set(hPushbutton_Play, 'String','Play');
else
if mState == 2
resume(mp3p);
mState = 1;
set(hPushbutton_Play, 'String','Pause');
else
play(mp3p);
mState = 1;
set(hPushbutton_Play, 'String','Pause');
end
end
end
如果这个时候是播放的状态mState == 1,点击播放按钮,就pause暂停;如果这个时候是暂停状态mState == 2,点击播放按钮,就是resume继续;如果是停止状态mState == 0,点击播放按钮就play播放。
这样,播放器最基本的状态就做好啦!
恭喜你获得系统奖励【Matlab简单版音乐播放器】。
上集回顾:
同学们,大家还记得我们Matlab播放篇的第一步红娘工作吗?上集说道,我们使用audioread与audioplayer两大利器,让Matlab和乐曲顺利进行了第一次约会。同时,凭借我们的智慧,我们获得了一个可以Hold住全场的极简播放器。接下来,我们要开始制作复杂版的mp3音乐播放器,将跳动的旋律可视化出来。
但是,作为这一届优秀的红娘,我们的目标肯定不止于此,何况——爱的风暴已经出现,怎么能够停滞不前呢?穿越时空,竭尽全力,我会来到你身边……(咳咳,收!)只收获一个糙汉版的播放器,我们当然是不能满足的,那么我们今天就给这个糙汉播放器好·好·打扮打扮吧!
------------------------- 2. 复杂版 -----------------------
复杂版相对于简单版来说,主要是增加了波形显示功能。从界面上看,多了一个显示波形的窗口(坐标轴hAxes_Wave);将原来两个按钮,stop和play/pause,修改成了三个:stop + play + pause;还会增加一个滑动条显示当前所处的时间。
从功能上看,难点集中在:如何利用播放器mp3player自带的timerfcn,将当前播放的音乐对应的波形plot到坐标轴上。
我们先来制作一版界面:
我们用的是封装的matlab gui写法:
function y = mp3player_plotwav(~)
y = 1;
end
把matlab封装在一个函数里边,input输入为~,代表省略;output输出为y,需要在函数中给y赋一个值,y = 1。
创建窗口的代码如下:
function y = mp3player_plotwav(~)
% mState: 0 == stop vs. 1 == play vs. 2 == pause
mState = 0;
% set the parameters for figure: x y width height
% get the screen size
screenRect = get(0,'Screensize'); % 获取整个屏幕的位置大小信息
screenWidth = screenRect(3); %屏幕的宽
screenHeight = screenRect(4); %屏幕的高
winWidth = 600;
winHeight = 400;
winX = (screenWidth - winWidth)/2;
winY = (screenHeight-winHeight)/2;
% create a figure
hFigure = figure(1);
set(hFigure, 'position',[winX winY winWidth winHeight], 'toolbar','none', 'menubar','none', 'numbertitle','off', 'name',' 嗨皮Mp3音乐播放器 ');
% create an axes
hAxes_BKG = axes('parent',hFigure);
set(hAxes_BKG, 'units','pixels', 'position',[1 1 winWidth winHeight], 'xtick',[], 'ytick',[], 'xcolor','w', 'ycolor','w');
% create an axes for plot wav
hAxes_Wave = axes('parent',hFigure);
set(hAxes_Wave, 'units','pixels', 'position',[50 100 441 200], 'Tag','hAxes_Wave', 'xtick',[], 'ytick',[], 'box','on');
axis([1 441 1 200]);
% create three pushbuttons
hPushbutton_Stop = uicontrol('Style','Pushbutton', 'String','Stop', 'Position',[71 51 100 30], 'Tag','hPushbutton_mState');
hPushbutton_Play = uicontrol('Style','Pushbutton', 'String','Play', 'Position',[221 51 100 30], 'Tag','hPushbutton_mState');
hPushbutton_Pause = uicontrol('Style','Pushbutton', 'String','Pause', 'Position',[371 51 100 30], 'Tag','hPushbutton_mState');
y =1;
end
窗口的大小是600宽 * 400高;整个窗口相对屏幕居中放置。
窗口上放置了一个背景hAxes_BKG,同时创建了一个用于显示音乐波形的hAxes_Wave。
同时,创建了三个pushbutton按钮:hPushbutton_Stop + hPushbutton_Play + hPushbutton_Pause。
大致de框架就是这样,我们接下来要把讲解集中在波形呈现这个难点上。
----------------------- 我是分割线 -----------------------
每天起床第一句,先给自己打个气,然后赚他一个亿写好播放器。
今天的第一个小目标——实现波形可视化。
提到波形,我们就要从音频的采样说起。
这里有一个问题:麦克风是如何收集声音信息的呢?
答案就是 —— 电磁效应。
声波打击隔膜,隔膜移动磁体,磁体的磁场令线圈产生电流,电流转换为数字信号。
因此音乐文件在电子设备里面的真面目就是一连串特殊数值的数字啦!
不同的数字代表着不同的声音。
为了更进一步地看清楚声音信号,我们试着读取一首mp3歌曲进来看看这些信号都长啥样。
% prepare mp3 filename
mp3FileName = 'Consoul Trainin-Take Me to Infinity.mp3';
% read the mp3, and prepare mp3p object
[mp3Y mp3Fs] = audioread(mp3FileName);
我们选择了一首叫做'Take Me to Infinity'的.mp3文件,这是小丑御用的BGM背景歌曲:
我们来看看读入到Matlab中的mp3Y信号:
另外一个返回值是SampleRate采样频率,通常为44100Hz,意味着1s的时间内,我们能够采集到44100个数据点信息。
通过信号和采样频率,我们就可以建构一个mp3播放器对象mp3player:
mp3p = audioplayer(mp3Y,mp3Fs);
谈到这里可以给大家一个小提示:显示波形的一种方式,就是将这些个数据点在一个坐标轴里展示出来。
那么就像要把大象装进冰箱里需要三个步骤,把这些点呈现在坐标轴里也需要三步:
1、 建立一个坐标轴;
2、 将mp3波形数据plot到坐标轴上;
3、 通过timer固定间隔时间plot,达到动画的目的。
--- 1 --- 建立一个坐标轴
% create an axes for plot wave
hAxes_Wave = axes('parent',hFigure);
set(hAxes_Wave, 'units','pixels', 'position',[50 100 441 200], 'Tag','hAxes_Wave', 'xtick',[], 'ytick',[], 'box','on');
axis([1 441 1 200]);
第1句,创造一个叫做hAxes_Wave的坐标轴,当一个控件来到这个世界的时候,一般都是要先指定一个父对象‘parent’,也可以理解为将坐标轴放置到hFigure窗口中的意思。
第2句,设置hAxes的单位(units)为像素(pixels),并放置在指定位置(设置position属性);这里我们把x轴的刻度和y轴的刻度都取消掉了,却开启了坐标轴的'box'属性,这样就可以在窗口中看到明显的一个框。在这里有一个伏笔,就是'Tag','hAxes_Wave',我们之所以要给整个坐标轴一个标签,就是希望以后能借助整个hFigure的句柄,利用findobj这个函数找到hAxes_Wave句柄。
第3句,分别设置了坐标轴的x轴和y轴的最小值和最大值[1 441 1 200]。为什么是441?是因为一般mp3的声音文件读取之后的mp3Fs,也就是采样率SampleRate是这个值。
--- 2 --- 将mp3波形数据plot到坐标轴上
数据plot之前,要先想办法获取其信息:
我们牢记一个公式:频率=样本/时间。即频率为单位时间内的样本数量。
利用这个公式,我们是可以根据获取的mp3的采样频率信息和总的样本数,计算得到总的时间,以秒为单位。所有这些有关mp3的信息都是可以从matlab的工作空间里边看到的。因为,我们的程序封装在函数里边了,所以在工作空间里边mp3player是不显示的。
所以,我专门写了一个脚本.m文件,来看mp3player相关的参数,截图如下所示:
运行这个文件,我们可以看到工作空间中mp3player变量的信息:
为了进一步了解这些属性都是什么意思,我们需要借助开发Matlab的公司的社区网站:
创建用于播放音频的对象 - MATLAB audioplayer - MathWorks 中国ww2.mathworks.cn在里边,有专门关于这些属性的描述,截图如下:
为了验证之前看到的2个通道的波形数据,我们选择第4个属性,NumberOfChannels,看看返回的参数值是不是跟我们看到的2个通道的一致?
nChannels = get(mp3player,'NumberOfChannels');
这个变量在运行之后,返回值nChannels确实是2,说明确实通道数量的属性起作用了。
第一个参数是SampleRate,就是我们的返回值mp3Fs,44100。
第七个参数是TotalSamples,代表所有采样点的数量,16299120。
根据公式,时间=样本/频率,可以计算得到当前的mp3player的时间信息:
一般mp3文件都是只需要记录分钟songMM和秒钟songSS。
% get this mp3player's info
totalSample = get(mp3p,'TotalSample');
totalSeconds = floor(totalSample/mp3Fs); % 总秒数,向下取整
songMM = floor(totalSeconds/60); % 总分钟数,向下取整
songSS = rem(totalSeconds,60); % 显示的秒数,向下取整
我们试着画一幅画,就需要从整体的时间中截取一段,画出它对应的波形数据,可以考虑画个最简单的:从当前的播放位置,截取一个SampleRate时间周期。
% clear
clc; clear; close all;
% prepare mp3 filename
mp3FileName = 'Consoul Trainin-Take Me to Infinity.mp3';
% read the mp3, and prepare mp3p object
[mp3Y mp3Fs] = audioread(mp3FileName);
mp3player = audioplayer(mp3Y, mp3Fs);
% 获取当前的位置,这个其实是1
mCurrentSample = get(mp3player,'CurrentSample');
% 获取采样率,这个等同于mp3Fs
mSampleRate = get(mp3player,'SampleRate');
% 准备一个mp3样本的y值序列
mp3Y_oneSample = mp3Y(mCurrentSample:100:mCurrentSample+44099,1);
% 准备x
tmpX = mSampleRate/100;
axes_X = 1:tmpX;
% 用plot函数画图
plot(axes_X, mp3Y_oneSample, '-r');
axis([1 441 -1 1]);
--- 3 --- 通过timer固定间隔时间plot,达到动画的目的
我们之前的章节中有提到timer机制,mp3播放器自带这样的一个TimerFcn函数。
TimerFcn — 间隔特定时间运行一次的回调函数
具体实现的代码是:
set(mp3p, 'TimerPeriod',0.1, 'TimerFcn',{@PlayerTimerFcn,hFigure});
第一个指定的参数是时间,为了实现动画效果,我们用了 0.1,相当于1s播放10帧。44100 = 4410 * 10,每次呈现的是441个数据点,相当于1s就是播放10张图片,形成动态的效果(这个当然是可以优化的,暂时这样设置,方便计算和理解)。
44100 = 4410 * 10,每次画图,我们都可以每10个数据点只取一个点,示意图如下:
具体的函数,指定了一个名称PlayerTimerFcn,我们把hFigure这个参数传进去了,只要这个句柄在手上,通过findobj函数,窗口中的啥句柄都能给你找到。
我说明一个事情,为什么这个教程要拖这么久?三个原因:
第一,程序出现了一个bug,鼠标一旦点击界面上的波形坐标轴hAxes_Wave以外的位置,当前的坐标轴的波形就停住了,开始在背景坐标轴hAxes_BKG上plot波形图,这个我一直没找到合适的方法。
第二,我不想用全局变量来完成Timer的参数传递,这会破坏程序的模块化。如果不用全局变量,怎么把外部的数据传到Timer函数里边一直是一个很头疼的问题。
第三,音乐信息中的高频部分使得波形看起来太尖了,不美观。
好在这三个问题最后都愉快地解决了,要感谢CS,QJM和ZDY在关键的时刻发挥了作用,帮了我大忙。
第一个难题,我们解决的方法是,在Timer一开始,把hFigure当前的坐标轴固定在波形坐标轴上。
% get the handle of the hAxes_Wave
hAxes_w = findobj(hFig, 'Tag','hAxes_Wave');
set(hFig,'CurrentAxes',hAxes_w); % 这句很关键
第二个难题,就是想办法设置一系列的隐藏控件,利用这些控件的UserData这个变量来传递参数。
比如,当前播放音乐的状态,我就是解决一个pushbutton的UserData变量来传递参数的。
% mState: 0 == stop vs. 1 == play vs. 2 == pause
mState = 0;
hPushbutton_mState = uicontrol('Style','Pushbutton', 'String','mState', 'Position',[50 200 100 30], 'Tag','hPushbutton_mState', 'visible','off');
set(hPushbutton_mState,'UserData',mState);
第三个难题,就是想办法设定一个滤波器,ZDY是数学大神,提供了滤波的代码:
mCurrentSample = get(hObject,'CurrentSample');
mSampleRate = get(hObject,'SampleRate');
mp3Y1y = mp3Yy(mCurrentSample:10:mCurrentSample+44099,1);
fc = 150;
wn = (2/4410) * fc;
b = fir1(640,wn,'low',kaiser(641,3));
mp3Y1y = filter(b,1,mp3Y1y);
然后,就可以愉快地播放音乐,同时欣赏动画了,超级开心 o(* ̄▽ ̄*)ブ
不带滑动条的相对完整版代码分享如下:
function y = mp3player_plotwav(~)
% mState: 0 == stop vs. 1 == play vs. 2 == pause
mState = 0;
% set the parameters for figure: x y width height
% get the screen size
screenRect = get(0,'Screensize'); % 获取整个屏幕的位置大小信息
screenWidth = screenRect(3); % 屏幕的宽
screenHeight = screenRect(4); % 屏幕的高
winWidth = 600;
winHeight = 400;
winX = (screenWidth - winWidth)/2;
winY = (screenHeight-winHeight)/2;
% create a figure
hFigure = figure(1);
set(hFigure, 'position',[winX winY winWidth winHeight], 'color','white','toolbar','none', 'menubar','none', 'numbertitle','off', 'name',' 嗨皮Mp3音乐播放器 ');
% create an axes
hAxes_BKG = axes('parent',hFigure);
set(hAxes_BKG, 'units','pixels', 'position',[1 1 winWidth winHeight], 'xtick',[], 'ytick',[], 'xcolor','w', 'ycolor','w', 'Tag','hAxes_BKG');
imgMatrix_BKG = ones(400,600,3);
imshow(imgMatrix_BKG,'parent',hAxes_BKG);
axis off;
% create an axes for plot wave
hAxes_Wave = axes('parent',hFigure);
set(hAxes_Wave, 'units','pixels', 'position',[50 100 441 200], 'Tag','hAxes_Wave', 'xtick',[], 'ytick',[], 'box','on', 'Tag','hAxes_Wave');
axis([1 441 1 200]);
% axis off;
% hide handles
hPushbutton_mp3p = uicontrol('Style','Pushbutton', 'String','mp3p', 'Position',[50 200 100 30], 'Tag','hPushbutton_mp3p', 'visible','off');
hPushbutton_mp3Y = uicontrol('Style','Pushbutton', 'String','mp3Y', 'Position',[50 200 100 30], 'Tag','hPushbutton_mp3Y', 'visible','off');
hPushbutton_mp3Fs = uicontrol('Style','Pushbutton', 'String','mp3Fs', 'Position',[50 200 100 30], 'Tag','hPushbutton_mp3Fs', 'visible','off');
%
hPushbutton_mState = uicontrol('Style','Pushbutton', 'String','mState', 'Position',[50 200 100 30], 'Tag','hPushbutton_mState', 'visible','off');
set(hPushbutton_mState,'UserData',mState);
% create three pushbuttons
hPushbutton_Stop = uicontrol('Style','Pushbutton', 'String','Stop', 'Position',[71 51 100 30], 'Tag','hPushbutton_mState');
hPushbutton_Play = uicontrol('Style','Pushbutton', 'String','Play', 'Position',[221 51 100 30], 'Tag','hPushbutton_mState');
hPushbutton_Pause = uicontrol('Style','Pushbutton', 'String','Pause', 'Position',[371 51 100 30], 'Tag','hPushbutton_mState');
% prepare mp3 filename
mp3FileName = 'Consoul Trainin-Take Me to Infinity.mp3'; %CanonInD.mp3
% read the mp3, and prepare mp3p object
[mp3Y mp3Fs] = audioread(mp3FileName);
mp3p = audioplayer(mp3Y,mp3Fs);
%
set(hPushbutton_mp3p,'UserData',mp3p);
set(hPushbutton_mp3Y,'UserData',mp3Y);
set(hPushbutton_mp3Fs,'UserData',mp3Fs);
% set timer function for mp3p ---
set(mp3p, 'TimerPeriod',0.1, 'TimerFcn',{@PlayerTimerFcn,hFigure});
% get this mp3player's info
totalSample = get(mp3p,'TotalSample');
totalSeconds = floor(totalSample/mp3Fs); % 总秒数,向下取整
songMM = floor(totalSeconds/60); % 总分钟数,向下取整
songSS = rem(totalSeconds,60); % 显示的秒数,向下取整
% Binding Mechanism
set(hPushbutton_Stop, 'Callback',{@hPushbutton_StopFcn,hFigure});
set(hPushbutton_Play, 'Callback',{@hPushbutton_PlayFcn,hFigure});
set(hPushbutton_Pause, 'Callback',{@hPushbutton_PauseFcn,hFigure});
% function 1 --> hPushbutton_StopFcn
function hPushbutton_StopFcn(hObject, eventData, hFig)
% get the handle of the pushbutton mState
hPushbtn_mState = findobj(hFig, 'Tag','hPushbutton_mState');
mState_mp3pCell = get(hPushbtn_mState,'UserData');
if iscell(mState_mp3pCell)
mState_mp3p = mState_mp3pCell{1};
else
mState_mp3p = mState_mp3pCell;
end
% get the handle of hPushbutton_Play
hPushbtn_Play = findobj(hFig,'Tag','hPushbutton_Play');
% get the handle of hPushbutton_mp3p
hPushbtn_mp3p = findobj(hFig, 'Tag','hPushbutton_mp3p');
tmpMp3p = get(hPushbtn_mp3p, 'UserData');
%
if mState_mp3p == 1
stop(tmpMp3p);
mState_mp3p = 0;
set(hPushbtn_mState, 'UserData',mState_mp3p);
else
if mState_mp3p == 2
stop(tmpMp3p);
mState_mp3p = 0;
set(hPushbtn_mState, 'UserData',mState_mp3p);
else
fprintf('pass stopn');
end
end
end
% function 2 --> hPushbutton_PlayFcn
function hPushbutton_PlayFcn(hObject, eventData, hFig)
% get the handle of the pushbutton mState
hPushbtn_mState = findobj(hFig, 'Tag','hPushbutton_mState');
mState_mp3pCell = get(hPushbtn_mState,'UserData');
if iscell(mState_mp3pCell)
mState_mp3p = mState_mp3pCell{1};
else
mState_mp3p = mState_mp3pCell;
end
% get the handle of hPushbutton_Play
hPushbtn_Play = findobj(hFig, 'Tag','hPushbutton_Play');
% get the handle of hPushbutton_mp3p
hPushbtn_mp3p = findobj(hFig, 'Tag','hPushbutton_mp3p');
tmpMp3p = get(hPushbtn_mp3p, 'UserData');
if mState_mp3p == 1
fprintf('pass playn');
else
if mState_mp3p == 2
resume(tmpMp3p);
mState_mp3p = 1;
set(hPushbtn_mState, 'UserData',mState_mp3p);
else
play(tmpMp3p);
mState_mp3p = 1;
set(hPushbtn_mState, 'UserData',mState_mp3p);
end
end
end
% function 3 --> hPushbutton_PauseFcn
function hPushbutton_PauseFcn(hObject, eventData, hFig)
% get the handle of the pushbutton mState
hPushbtn_mState = findobj(hFig, 'Tag','hPushbutton_mState');
mState_mp3pCell = get(hPushbtn_mState,'UserData');
if iscell(mState_mp3pCell)
mState_mp3p = mState_mp3pCell{1};
else
mState_mp3p = mState_mp3pCell;
end
% get the handle of hPushbutton_Play
hPushbtn_Play = findobj(hFig, 'Tag','hPushbutton_Play');
% get the handle of hPushbutton_mp3p
hPushbtn_mp3p = findobj(hFig, 'Tag','hPushbutton_mp3p');
tmpMp3p = get(hPushbtn_mp3p, 'UserData');
if mState_mp3p == 1
pause(tmpMp3p);
mState_mp3p = 2;
set(hPushbtn_mState, 'UserData',mState_mp3p);
else
if mState_mp3p == 2
fprintf('pass pause1n');
else
fprintf('pass pause2n');
end
end
end
% function 4 --> PlayerTimerFcn
function PlayerTimerFcn(hObject, eventData, hFig)
% get the handle of the hAxes_Wave
hAxes_w = findobj(hFig, 'Tag','hAxes_Wave');
set(hFig,'CurrentAxes',hAxes_w);
% get the handle of the pushbutton mState
hPushbtn_mState = findobj(hFig, 'Tag','hPushbutton_mState');
mState_mp3pCell = get(hPushbtn_mState,'UserData');
if iscell(mState_mp3pCell)
mState_mp3p = mState_mp3pCell{1};
else
mState_mp3p = mState_mp3pCell;
end
hPushbtn_mp3Y = findobj(hFig, 'Tag','hPushbutton_mp3Y');
mp3Y_Cell = get(hPushbtn_mp3Y,'UserData');
if iscell(mp3Y_Cell)
mp3Yy = mp3Y_Cell{1};
else
mp3Yy = mp3Y_Cell;
end
axes(hAxes_w);
mCurrentSample = get(hObject,'CurrentSample');
mSampleRate = get(hObject,'SampleRate');
tmpX = mSampleRate/10;
axes_X = 1:tmpX;
a = length(axes_X);
mp3Y1y = mp3Yy(mCurrentSample:10:mCurrentSample+44099,1);
fc = 150;
wn = (2/4410) * fc;
b = fir1(640,wn,'low',kaiser(641,3));
mp3Y1y = filter(b,1,mp3Y1y);
b = length(mp3Y1y);
fprintf('%d, %dn',a, b);
plot(axes_X, mp3Y1y, '-r');
axis([1 4410 -1 1]);
axis off;
% set(hAxes_w,'box','on');
% hold off;
end
% assign y
y = 1;
end
因为audioplayer对象有timerfcn,就可以把timer机制和slider滑动条联系起来,形成一种播放(指向某个特定的时间点CurrentSample当前的)带动进度条缓慢向前移动,反过来,进度条的拖动也可以改变时间点CurrentSample的位置。
先用代码制造一个Slider滑动条控件:
% 新增加一个滑条
hSlider = uicontrol('Style','Slider','Min',0,'Max',totalSeconds,'Value',0,'SliderStep',[1/totalSeconds 0.05],'Position',[50 120 441 10], 'Tag','hSlider');
滑动条的最小值设置为0,最大值设置为totalSeconds,这个值是通过总的采样点数除以采样率(totalSample/SampleRate)计算得到的。
第一个功能,我们先来看如何利用audioplayer的timerfcn实现CurrentSample的变化带动Slider缓慢移动。
这个功能实现的第1步是获取audioplayer的句柄,并get它的CurrentSample值;第2步是将这个采样点数值转化为以秒计算的时间值,借助CurrentSecond = CurrentSample/SampleRate计算得到;第3步是将Slider的值设置为当前CurrentSample对应的时间点CurrentSecond。
设定mp3p对象的timerfcn函数:
% set timer function for mp3p ---
set(mp3p, 'TimerPeriod',0.1, 'TimerFcn',{@PlayerTimerFcn,hFigure});
第1步的代码:
mCurrentSample = get(hObject,'CurrentSample');
纳尼,居然这么简单。是的,如果你不理解整个timer的机制,你可能会这么写代码:
% 获取mp3p的句柄
hSlider_w = findobj(hFig, 'Tag','hSlider');
hPushbutton_mp3p_w = findobj(hFig, 'Tag','hPushbutton_mp3p');
mp3p_w = get(hPushbutton_mp3p_w, 'UserData');
mCurrentSample = get(mp3p_w,'CurrentSample')
如果你理解了matlab gui函数的机制,你就知道timerfcn函数的第一个参数hObject本身是mp3p对象,因为,这个函数就是从属于mp3p的。
第2步的代码:
% 计算当前采样点CurrentSample对应的时间数值
mCurrentSecond = round(mCurrentSample/mTotalSamples * mTotalSeconds);
第3步的代码:
% set the value of slider
if mCurrentSample > sliderV & mCurrentSample < sliderV+44100
set(hSlider_w, 'Value',mCurrentSecond);
end
这句是整个mp3播放器.m文件中最精髓的地方!
如果没有这句话,你拖动Slider的时候,一松开鼠标左键,它马上就会回到原来的位置。因为timerfcn的间隔时间很短,每隔0.1秒就会执行一次,而执行的结果就是把Slider的值设置为计算得到的时间点值。所以,有没有一个判断拖动的方法呢?也就是说,找到一个区分 Slider拖动过的 vs. 没有拖动过的方法?答案就是现在这个限制条件,“如果当前的CurrentSample比Slider的时间对应的位置大一点,但是又不超过Slider的时间对应的位置+一个采样频率,相当于1s时间”,就判定为是audioplayer类型的mp3p对象自动运行timerfcn在起作用;如果超过这个限定条件,则判定为是人为地拖动了Slider,就不需要对CurrentSample和Slider进行同步操作(不需要将Slider参数设置为CurrentSampel对应的时间点参数)。
第二个功能:进度条的拖动也可以反过来改变当前采样点CurrentSample的位置。
绑定Slider控件的Callback函数:
set(hSlider,'Callback',{@hSlider_Callback,hFigure});
同样是分三步走:
第1步,获取Slider控件的拖动的位置点数值信息;第二步,获取mp3p的句柄,暂停它;第三步,在新的Slider的时间点对应的采样点,重新开始播放mp3p。
第1步的代码:
% 获取hSlider的当前值,并计算当前采样频率
sliderCurrentSecondCell = get(hObject,'value');
第2步的代码:
hPushbutton_mp3p_w = findobj(hFig, 'Tag','hPushbutton_mp3p');
mp3p_w = get(hPushbutton_mp3p_w,'UserData');
pause(mp3p_w);
第3步的代码:
% 3 获取hSlider的当前值,并计算当前采样点
sliderCurrentSecondCell = get(hObject,'value');
if iscell(sliderCurrentSecondCell)
sliderCurrentSecond = sliderCurrentSecondCell{1};
else
sliderCurrentSecond = sliderCurrentSecondCell;
end
mCurrentSample = round(sliderCurrentSecond * 44100);
% 在新的位置重新播放mp3p音乐
play(mp3p_w,mCurrentSample);
讲到这里,整个程序的框架,主要功能和对应的代码都介绍完了,以下分享的是完整版本的代码:
function y = mp3player_plotwav(~)
% mState: 0 == stop vs. 1 == play vs. 2 == pause
mState = 0;
% set the parameters for figure: x y width height
% get the screen size
screenRect = get(0,'Screensize'); % 获取整个屏幕的位置大小信息
screenWidth = screenRect(3); % 屏幕的宽
screenHeight = screenRect(4); % 屏幕的高
winWidth = 600;
winHeight = 400;
winX = (screenWidth - winWidth)/2;
winY = (screenHeight-winHeight)/2;
% create a figure
hFigure = figure(1);
set(hFigure, 'position',[winX winY winWidth winHeight], 'color','white','toolbar','none', 'menubar','none', 'numbertitle','off', 'name',' 嗨皮Mp3音乐播放器 ');
% create an axes
hAxes_BKG = axes('parent',hFigure);
set(hAxes_BKG, 'units','pixels', 'position',[1 1 winWidth winHeight], 'xtick',[], 'ytick',[], 'xcolor','w', 'ycolor','w', 'Tag','hAxes_BKG');
imgMatrix_BKG = ones(400,600,3);
imshow(imgMatrix_BKG,'parent',hAxes_BKG);
%增加一个圆角矩形边框,使得波形呈现更加的美观
rectangle('Position',[50 50 441 200],'Curvature',[0.1 0.2]);
axis off;
% create an axes for plot wave
hAxes_Wave = axes('parent',hFigure);
set(hAxes_Wave, 'units','pixels', 'position',[50 150 441 200], 'Tag','hAxes_Wave', 'xtick',[], 'ytick',[], 'box','on', 'Tag','hAxes_Wave');
axis([1 441 1 200]);
% 把原来的坐标轴给off掉,因为之前已经增加了一个圆角矩形边框
axis off;
% ------------ hide handles ---------------
hPushbutton_mp3p = uicontrol('Style','Pushbutton', 'String','mp3p', 'Position',[50 200 100 30], 'Tag','hPushbutton_mp3p', 'visible','off');
hPushbutton_mp3Y = uicontrol('Style','Pushbutton', 'String','mp3Y', 'Position',[50 200 100 30], 'Tag','hPushbutton_mp3Y', 'visible','off');
hPushbutton_mp3Fs = uicontrol('Style','Pushbutton', 'String','mp3Fs', 'Position',[50 200 100 30], 'Tag','hPushbutton_mp3Fs', 'visible','off');
%
hPushbutton_mState = uicontrol('Style','Pushbutton', 'String','mState', 'Position',[50 200 100 30], 'Tag','hPushbutton_mState', 'visible','off');
set(hPushbutton_mState,'UserData',mState);
% create three pushbuttons
hPushbutton_Stop = uicontrol('Style','Pushbutton', 'String','Stop', 'Position',[71 51 100 30], 'Tag','hPushbutton_mState');
hPushbutton_Play = uicontrol('Style','Pushbutton', 'String','Play', 'Position',[221 51 100 30], 'Tag','hPushbutton_mState');
hPushbutton_Pause = uicontrol('Style','Pushbutton', 'String','Pause', 'Position',[371 51 100 30], 'Tag','hPushbutton_mState');
% prepare mp3 filename
mp3FileName = 'Consoul Trainin-Take Me to Infinity.mp3'; % CanonInD.mp3
% read the mp3, and prepare mp3p object
[mp3Y mp3Fs] = audioread(mp3FileName);
mp3p = audioplayer(mp3Y,mp3Fs);
%
set(hPushbutton_mp3p,'UserData',mp3p);
set(hPushbutton_mp3Y,'UserData',mp3Y);
set(hPushbutton_mp3Fs,'UserData',mp3Fs);
% set timer function for mp3p ---
set(mp3p, 'TimerPeriod',0.1, 'TimerFcn',{@PlayerTimerFcn,hFigure});
% get this mp3player's info
totalSample = get(mp3p,'TotalSample');
totalSeconds = floor(totalSample/mp3Fs); % 总秒数,向下取整
songMM = floor(totalSeconds/60); % 总分钟数,向下取整
songSS = rem(totalSeconds,60); % 显示的秒数,向下取整
% 新增加一个滑条
hSlider = uicontrol('Style','Slider','Min',0,'Max',totalSeconds,'Value',0,'SliderStep',[1/totalSeconds 0.05],'Position',[50 120 441 10], 'Tag','hSlider');
% ============ inding Mechanism ===========
set(hPushbutton_Stop, 'Callback',{@hPushbutton_StopFcn,hFigure});
set(hPushbutton_Play, 'Callback',{@hPushbutton_PlayFcn,hFigure});
set(hPushbutton_Pause, 'Callback',{@hPushbutton_PauseFcn,hFigure});
set(hSlider,'Callback',{@hSlider_Callback,hFigure});
% function 1 --> hPushbutton_StopFcn
function hPushbutton_StopFcn(hObject, eventData, hFig)
% 1 首先是获取当前的播放状态,需要借助句柄 hPushbutton_mState
% get the handle of the pushbutton mState
hPushbtn_mState = findobj(hFig, 'Tag','hPushbutton_mState');
mState_mp3pCell = get(hPushbtn_mState,'UserData');
if iscell(mState_mp3pCell)
mState_mp3p = mState_mp3pCell{1};
else
mState_mp3p = mState_mp3pCell;
end
% 2 其次是获取mp3p这个变量,需要借助句柄 hPushbutton_mp3p
% get the handle of hPushbutton_mp3p
hPushbtn_mp3p = findobj(hFig, 'Tag','hPushbutton_mp3p');
tmpMp3p = get(hPushbtn_mp3p, 'UserData');
% 3 根据具体变量的值来判断当前的播放状态:0 == stop vs. 1 == play vs. 2 == pause
if mState_mp3p == 1
stop(tmpMp3p);
mState_mp3p = 0;
set(hPushbtn_mState, 'UserData',mState_mp3p);
% set hSlider's value as 1
hSlider_w = findobj(hFig, 'Tag','hSlider');
set(hSlider_w,'value',1);
else
if mState_mp3p == 2
stop(tmpMp3p);
mState_mp3p = 0;
set(hPushbtn_mState, 'UserData',mState_mp3p);
% set hSlider's value as 1
hSlider_w = findobj(hFig, 'Tag','hSlider');
set(hSlider_w,'value',1);
else
fprintf('pass stopn');
end
end
end
% function 2 --> hPushbutton_PlayFcn
function hPushbutton_PlayFcn(hObject, eventData, hFig)
% get the handle of the pushbutton mState
hPushbtn_mState = findobj(hFig, 'Tag','hPushbutton_mState');
mState_mp3pCell = get(hPushbtn_mState,'UserData');
if iscell(mState_mp3pCell)
mState_mp3p = mState_mp3pCell{1};
else
mState_mp3p = mState_mp3pCell;
end
% get the handle of hPushbutton_mp3p
hPushbtn_mp3p = findobj(hFig, 'Tag','hPushbutton_mp3p');
tmpMp3pCell = get(hPushbtn_mp3p, 'UserData');
if iscell(tmpMp3pCell)
tmpMp3p = tmpMp3pCell{1};
else
tmpMp3p = tmpMp3pCell;
end
if mState_mp3p == 1
fprintf('pass playn');
else
if mState_mp3p == 2
resume(tmpMp3p);
mState_mp3p = 1;
set(hPushbtn_mState, 'UserData',mState_mp3p);
else
play(tmpMp3p);
mState_mp3p = 1;
set(hPushbtn_mState, 'UserData',mState_mp3p);
end
end
end
% function 3 --> hPushbutton_PauseFcn
function hPushbutton_PauseFcn(hObject, eventData, hFig)
% get the handle of the pushbutton mState
hPushbtn_mState = findobj(hFig, 'Tag','hPushbutton_mState');
mState_mp3pCell = get(hPushbtn_mState,'UserData');
if iscell(mState_mp3pCell)
mState_mp3p = mState_mp3pCell{1};
else
mState_mp3p = mState_mp3pCell;
end
% get the handle of hPushbutton_mp3p
hPushbtn_mp3p = findobj(hFig, 'Tag','hPushbutton_mp3p');
tmpMp3p = get(hPushbtn_mp3p, 'UserData');
if mState_mp3p == 1
pause(tmpMp3p);
mState_mp3p = 2;
set(hPushbtn_mState, 'UserData',mState_mp3p);
else
if mState_mp3p == 2
fprintf('pass pause1n');
else
fprintf('pass pause2n');
end
end
end
% function 4 --> PlayerTimerFcn
function PlayerTimerFcn(hObject, eventData, hFig)
% 获取mp3p的句柄
hSlider_w = findobj(hFig, 'Tag','hSlider');
hPushbutton_mp3p_w = findobj(hFig, 'Tag','hPushbutton_mp3p');
mp3p_w = get(hPushbutton_mp3p_w, 'UserData'); % 这个函数的第一个参数hObject本身就是mp3p对象,因为这个函数就是从属于mp3p的
% find the handle of the hAxes_Wave, and fix it
hAxes_w = findobj(hFig, 'Tag','hAxes_Wave');
% 将当前窗口hFig的坐标轴设置为hAxes_w
set(hFig,'CurrentAxes',hAxes_w);
% get the handle of the slider
hSlider_w = findobj(hFig, 'Tag','hSlider');
sliderValue = get(hSlider_w,'value');
sliderV = sliderValue * 44100;
% get the handle of the pushbutton mState
hPushbtn_mState = findobj(hFig, 'Tag','hPushbutton_mState');
mState_mp3pCell = get(hPushbtn_mState,'UserData');
if iscell(mState_mp3pCell)
mState_mp3p = mState_mp3pCell{1};
else
mState_mp3p = mState_mp3pCell;
end
% get the y of mp3player
hPushbtn_mp3Y = findobj(hFig, 'Tag','hPushbutton_mp3Y');
mp3Y_Cell = get(hPushbtn_mp3Y,'UserData');
if iscell(mp3Y_Cell)
mp3Yy = mp3Y_Cell{1};
else
mp3Yy = mp3Y_Cell;
end
mTotalSamples = length(mp3Yy);
mTotalSeconds = round(mTotalSamples/44100);
mCurrentSample = get(hObject,'CurrentSample');
% fprintf('%dn',mCurrentSample);
% 计算当前时间数值
mCurrentSecond = round(mCurrentSample/mTotalSamples * mTotalSeconds);
if mCurrentSecond < mTotalSeconds - 1
% set the value of slider
if mCurrentSample > sliderV & mCurrentSample < sliderV+44100
set(hSlider_w, 'Value',mCurrentSecond);
end
mSampleRate = get(hObject,'SampleRate');
tmpX = mSampleRate/10;
axes_X = 1:tmpX;
a = length(axes_X);
mp3Y1y = mp3Yy(mCurrentSample:10:mCurrentSample+44099,1);
fc = 150;
wn = (2/4410) * fc;
b = fir1(640,wn,'low',kaiser(641,3));
mp3Y1y = filter(b,1,mp3Y1y);
b = length(mp3Y1y);
% fprintf('%d, %dn',a, b);
plot(axes_X, mp3Y1y, '-r');
axis([1 4410 -1 1]);
axis off;
% set(hAxes_w,'box','on');
% hold off;
else
mp3p_w = hPushbutton_mp3p_w.UserData;
stop(mp3p_w);
end
end
% function 5 --> hSlider_Callback
function hSlider_Callback(hObject, eventData, hFig)
% 1 获取mp3p的句柄
% hSlider_w = findobj(hFig, 'Tag','hSlider');
hPushbutton_mp3p_w = findobj(hFig, 'Tag','hPushbutton_mp3p');
mp3p_w = get(hPushbutton_mp3p_w,'UserData');
% get the y of mp3player
hPushbtn_mp3Y = findobj(hFig, 'Tag','hPushbutton_mp3Y');
mp3Y_Cell = get(hPushbtn_mp3Y,'UserData');
if iscell(mp3Y_Cell)
mp3Yy = mp3Y_Cell{1};
else
mp3Yy = mp3Y_Cell;
end
% 2 calculate totalsamples and totalseconds
mTotalSamples = length(mp3Yy);
mTotalSeconds = round(mTotalSamples/44100);
% mCurrentSample = get(mp3p_w,'CurrentSample');
% mCurrentSecond = round(mCurrentSample/44100);
% mCurrentSecond = mCurrentSample/mTotalSamples * mTotalSeconds;
% 3 获取hSlider的当前值,并计算当前采样点
sliderCurrentSecondCell = get(hObject,'value');
if iscell(sliderCurrentSecondCell)
sliderCurrentSecond = sliderCurrentSecondCell{1};
else
sliderCurrentSecond = sliderCurrentSecondCell;
end
mCurrentSample = round(sliderCurrentSecond * 44100);
% fprintf('sliderCurrentSecond = %d, mCurrentSample = %d', sliderCurrentSecond, mCurrentSample);
% if mCurrentSecond > sliderCurrentSecond & mCurrentSample < sliderCurrentSecond + 1
% set(hSlider_w,'Value',sliderCurrentSecond);
% end
% mp3p_w = hPushbutton_mp3p_w.UserData;
% set(mp3p_w,'CurrentSample',mCurrentSample);
pause(mp3p_w);
% set(hSlider_w,'Value',sliderCurrentSecond);
play(mp3p_w,mCurrentSample);
% get(mp3p_w,'CurrentSample');
% sliderSeconds = mCurrentSample/mTotalSamples * mTotalSeconds;
% set(hObject,'value',);
end
% assign y
y = 1;
end
就这样,恭喜糙汉版mp3播放器升级为内涵版播放器✌