操作过程点击此处观看
上段时间补习了一下傅里叶变化的知识,突发奇想可以根据此做一款声音转换器,使用工科神器Matlab进行完成,并且开发了可操作界面如下图所示:
功能实现与描述
软件中可以实现声音的录制、回放、文件的保存与打开功能,两个旋钮括约肌与肛缩可以调整声音的频率与播放速度。
声音录制
matlab封装了声音的开始录制与结束录制功能函数,该软件使用appdesigner进行开发,声音录制与暂停按键的callback函数如下:
global FlagStartOrEnd; %控制开始或结束录制标志位
global recObj; %录音对象
global TextState; %状态信息提示
global TagSoundData; %目标音源数据
global TagSoundFs; %目标音源频率
global MinAbsValue; %最小绝对值 傅里叶变化数值
if FlagStartOrEnd
% 切换开始录音图片 %开始录音
app.ButtonStartRecording.Icon = 'Start.png';
% 重置标志位
FlagStartOrEnd = false;
% 结束录音
stop(recObj);
% 获取录音数据
audioData = getaudiodata(recObj);
% 参数传递
TagSoundData = audioData;
TagSoundFs = 22050;
% 图像展示
[ResFreq, ResMag, ResPhase] = FFTAnslysis(app, TagSoundData, TagSoundFs, MinAbsValue, '录制声音', 100);
% 状态信息提示
TextState = {'录音结束了哦(^_^)'};
app.TextAreaState.Value = TextState;
% 另存为录音文件
[FileName, FilePath] = uiputfile('*.wav');
% 状态信息提示
TextState = {['文件<', FileName, '>保存完成了呐'], TextState{:}};
app.TextAreaState.Value = TextState;
%将音频文件写入到wav
audiowrite(FileName, audioData, 22050);
else
% 切换结束录音图片 %结束录音
app.ButtonStartRecording.Icon = 'End.png';
% 重置标志位
FlagStartOrEnd = true;
% 打开录音设备
record(recObj);
% 状态信息提示
TextState = {'小哥哥开始录音啦...', TextState{:}};
app.TextAreaState.Value = TextState;
end
需要说明的是,全局变量的定义需要在appdesigner中的startup函数中,代码如下:
global FlagStartOrEnd; %控制开始或结束录制标志位
global recObj; %录音对象
global TextState; %状态信息提示
global TagSoundData; %目标音源数据
global TagSoundFs; %目标音源频率
global MinAbsValue; %最小绝对值 傅里叶变化数值
FlagStartOrEnd = false;
recObj = audiorecorder(22050, 16, 1); %第一个参数为声音的采集频率
TextState = {};
TagSoundData = [];
TagSoundFs = [];
MinAbsValue = 1e-16;
声音回放
声音的回放直接调用sound函数,为实现夹子声音的转换,shiftPitch函数可以调整声音频率,函数中可以接收旋钮括约肌的数值,声音的播放速度可以通过调整sound函数第二个参数来实现。代码如下:
global TagSoundData; %目标音源数据
global TagSoundFs; %目标音源频率
global MinAbsValue; %最小绝对值 傅里叶变化数值
% 夹子音转换
TagSoundDataTemp = shiftPitch(TagSoundData, app.KnobShiftFs.Value, 1, TagSoundFs, 0);
% 计算时间比率
if app.KnobShiftTime.Value >= 0
TimeRate = 0.4*app.KnobShiftTime.Value + 1;
else
TimeRate = 0.08*app.KnobShiftTime.Value + 1;
end
% 听取声音
sound(TagSoundDataTemp, TagSoundFs*TimeRate);
% 图像展示
[ResFreq, ResMag, ResPhase] = FFTAnslysis(app, TagSoundData, TagSoundFs, MinAbsValue, '声音展示', 100);
文件保存
声音文件的保存可以直接使用audiowrite函数实现,代码如下:
global TagSoundData; %目标音源数据
global TagSoundFs; %目标音源频率
global TextState; %状态信息提示
% 将音频文件写入到wav文件
FileName = ['Sound.wav'];
audiowrite(FileName, TagSoundData, TagSoundFs);
% 加入提示信息
TextState = {['音频文件<', FileName, '>已保存了哦'], TextState{:}};
app.TextAreaState.Value = TextState;
文件打开
文件打开可以使用audioread实现
global TagSoundData; %目标音源数据
global TagSoundFs; %目标音源频率
global TextState; %状态信息提示
% 获取音源文件
[FileName, FilePath] = uigetfile('*wav', '小哥哥打开音频文件哦');
% 加入提示信息
TextState = {['音频文件<', FileName, '>已打开了哦'], TextState{:}};
app.TextAreaState.Value = TextState;
% 打开音源数据
[TagSoundData, TagSoundFs] = audioread([FilePath, '\', FileName]);
图像展示
图像中可以展示声音的时域与频域曲线,对采集的声音做傅里叶变化可以将时域信息转换为频域信息,实现代码如下:
function [ResFreq, ResMag, ResPhase] = FFTAnslysis(app, TagData, TagFs, MinAbsValue, Name, NumOrder)
%% 参数含义
%输入:
%TagData:目标函数
%TagFs:采集数据频率
%MinAbsValue:幅值最小限度
%Name:图窗名称别
%NumOrder:选取阶数
%ResFreg:结果频率
%ResMag:结果幅值
%ResPhase:结果相位
%% 数据长度
Length=length(TagData);
%% 计算幅值谱与相位谱
%双边频谱
FFTTagData =fft(TagData/Length);
%单边频谱
FFTTagData=FFTTagData(1:Length/2+1);
%幅值谱
MagTagData = abs(FFTTagData);
MagTagData(2:end-1) = 2*MagTagData(2:end-1);
%相位谱
PhaseTagData = [];
for i = 1:length(FFTTagData)
if abs(FFTTagData(i))< MinAbsValue
PhaseTagData(i) = 0;
else
PhaseTagData(i) = atan2(imag(FFTTagData(i)), real(FFTTagData(i)));
end
end
%频率轴
FreqMagPhase = (0 : length(MagTagData)/(length(MagTagData)-1) : length (MagTagData))*(TagFs/2)/length(FFTTagData);
%时间轴
Time = (0:Length-1)/TagFs;
%% 截取指定点
%将频谱数值排序
[ResMag, IndexMag] = sort(MagTagData, 'descend');
%取前NumOrder阶数
ResMag = ResMag(1:NumOrder);
IndexMag = IndexMag(1:NumOrder);
%频率
ResFreq = FreqMagPhase(IndexMag)';
%相位
ResPhase = PhaseTagData(IndexMag)';
% 绘制图像 频域
plot(app.UIAxesFs, FreqMagPhase, MagTagData);
xlabel(app.UIAxesFs, '频率(Hz)');
ylabel(app.UIAxesFs, '幅值');
title(app.UIAxesFs, ['频域-', Name, '-幅值', '选取阶数:', num2str(NumOrder)]);
hold(app.UIAxesFs, 'on');
plot(app.UIAxesFs, ResFreq, ResMag, 'r*');legend(app.UIAxesFs, '幅值', '选取点');
hold(app.UIAxesFs, 'off');
% 绘制图像 时域
plot(app.UIAxesTime, Time, TagData);
xlabel(app.UIAxesTime, '时间(s)');
ylabel(app.UIAxesTime, '幅值');
title(app.UIAxesTime, ['时域-', Name, '幅值']);
end
end