操作過程點擊此處觀看
上段時間補習了一下傅里葉變化的知識,突發奇想可以根據此做一款聲音轉換器,使用工科神器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;%將音頻文件寫入到wavaudiowrite(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 >= 0TimeRate = 0.4*app.KnobShiftTime.Value + 1;elseTimeRate = 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))< MinAbsValuePhaseTagData(i) = 0;elsePhaseTagData(i) = atan2(imag(FFTTagData(i)), real(FFTTagData(i)));endend%頻率軸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, '幅值']);endend