見出し画像

MATLABでGUI付きアプリ開発 ~ App Designer を使おう(2)~ストリーミングオーディオをフィルター処理

MATLABでGUI付きアプリ開発 ~ App Designer を使おう(1)~フィルター特性を変えてグラフ表示

MATLABでGUI付きアプリ開発 ~ App Designer を使おう(3)~タイマー割り込みでスペクトル表示(非同期FIFOを使う)

MATLABでGUI付きアプリ開発 ~ App Designer を使おう(4)~ フィルター周波数・バンド幅をマウスで操作


まえがき

今回は前回のアプリに、オーディオファイル読み込み・再生機能を追加してみます。フィルター効果の ON/OFF 切替も追加します。

最後に、実行結果動画スクリプトがあります。

前回同様、Audio Toolbox を使用します。
DSP System Toolbox だけあれば動くかもしれませんが未確認です。

こちらも合わせてご覧ください。
MATLAB/Simulink で気軽に始める音響信号処理 ~ App Designer で GUIアプリも簡単開発!

ストリーミング・オーディオ

MATLAB でのオーディオファイルの取り扱いはとても簡単です。OSで扱える形式はそのまま読み込めます。ビデオの音声部分も直接読み込めます。

まずは標準のファイル選択 UI を使って、オーディオファイルを選択します。

[FileName, PathName, ~] = uigetfile({'*.mp3;*.m4a;*.aac;*.wav;*.mp4;*.flac;*.ogg;*.webm','Music Files';'*.*','All Files'},'Select Song', 'MultiSelect', 'off');
inputFilename = [PathName FileName];

次に、ストリーミングオブジェクトを定義します。
ストリーミングオブジェクトは、ファイル全体を読み込むことなく、あるフレームサイズ毎に連続して処理するとこができます。それぞれ、ファイル読み込み、音声出力用です。

hAudioSource = dsp.AudioFileReader(inputFilename);
hAudioOut = audioDeviceWriter('SampleRate', hAudioSource.SampleRate);

後は、

while ~isDone(hAudioSource)
    in = hAudioSource();
    hAudioOut(in);
end

で最後まで再生されます。

フィルター処理を入れる場合は、

octFilt = octaveFilter();
while ~isDone(hAudioSource)
    in = hAudioSource();
    hAudioOut(octFilt(in));
end

とするだけです。

使い終わったら、

release(hAudioSource)
release(hAudioOut)

で解放しておきましょう。

アプリ

では、実際のコードについてです。

GUIコンポーネントの追加

前回のアプリで置いたコンポーネントを移動してスペースを空け、以下のコンポーネントを追加します。

コンポーネント配置例

・ボタン(LoadFileButton)
 読み込むオーディオファイル選択用に使います。
 Text:LoadFile にします。

・ドロップダウン(OutputDeviceDropDown)
 オーディオ出力デバイス設定に使います。
 ラベル:OutputDevice にします。
 後はコード中で設定します

・ボタン(RSTDevButton)
 オーディオインターフェースの状態が変わったときに、クリックすると再設定します。
 アプリ再立ち上げの必要がなくなります。
 Text:RSTDev にします。

・状態ボタン(PLAYButton)
 押す度にON/OFF状態が切り替わるボタンです(トグルボタン)。
 再生・停止に使います。
 Text:PLAY とします。
 コード中で、表示するテキスト、背景色を切り替えます。

・状態ボタン(BYPASSButton)
 Text:BYPASS とします。
 フィルター効果のON/OFFを切り替えます。

・テキストエリア
 読み込んだファイル名を表示するのに使います。
 ラベル:Track
 Value:最初にオーディオファイルを読み込んでください

コンポーネントは、Ctrl+C / Ctrl+V でコピーができますが、DropDownなどは実はラベル部分と分かれています。選択済みのを再クリックすると個別選択になってしまうので、全体を選択する場合は中央付近をクリックするなどしてください。

プロパティの追加

以下を追加します。

    FrameSize = 256; % processing frame size
    BufferSize = 1024; % PC buffer size
    inputFilename
    Output_Device
    PLAY = false;
    BYPASS = false;
    setFile = false;  % File is set?
    RSTDev = false;

もし再生時に音が途切れる場合は、FrameSize を大きくしてみてください。

レイテンシー(GUI操作してから音が変化するまでのディレイ)も増加するので~4096程度までが良いとは思います。

逆に音切れしない範囲で小さくもできます。

FrameSize を大きくしても音切れする場合は BufferSize を大きくしてみてください。

コールバックの追加

・LoadFileButton
オーディオファイルが選択されたらファイル情報を取得し、PLAYボタンの背景を白に変更、Track に曲名を表示します。

PLAY 状態で呼ばれた場合は強制的に STOP 状態にします。

function LoadFileButtonPushed(app, event)
    [FileName, PathName, ~] = uigetfile({['*.mp3;*.m4a;*.aac;*.wav;* ...' ...
                '.mp4;*.flac;*.ogg;*.webm'],'Music Files';'*.*','All Files'}, ...
                'Select Song', 'MultiSelect', 'off');
    if FileName ~= 0
        app.inputFilename = [PathName FileName];
        m_info = audioinfo(app.inputFilename);
        app.fs = m_info.SampleRate;
        app.setFile = true;
        app.PLAYButton.BackgroundColor = [1 1 1];
        app.TrackTextArea.Value = FileName;
        
	if app.PLAY % if so force stop
	    app.PLAYButton.Value = false;
	    PLAYButtonValueChanged(app, event);
	end
    end
end

・OutputDeviceDropDown
出力デバイスを選択します。
再生中は変更できないようにしています。

function OutputDeviceDropDownValueChanged(app, event)
    value = app.OutputDeviceDropDown.Value;
    if ~app.PLAY % while STOP state only
        app.Output_Device = value;
    else
        app.OutputDeviceDropDown.Value = app.Output_Device;
    end
end

・RSTDevButton
オーディオデバイスリストの更新を行い、startupFcn を呼び出して再度設定を行います。

function RSTDevButtonPushed(app, event)
    app.RSTDev = true;
    audiodevreset
    startupFcn(app);
end

・PLAYButton
ファイル選択済みであれば(ループ)再生を行います。
PLAYがクリックされれば表示テキストを "STOP" に、再度押されれば "PLAY" に戻しています。

BYPASS時は入力をそのまま、そうでなければフィルター処理をして出力します。

フィルターオブジェクト等は途中で列サイズを変更できないため、入力がモノラルであった場合はステレオにして列サイズを揃えています。

drawnow で GUI からの割り込みを受け付けます。

その後、PLAY 中にウィンドウが閉じられた場合のチェックを入れています。

function PLAYButtonValueChanged(app, event)
    value = app.PLAYButton.Value;
    if ~app.setFile % file isn't set
        app.PLAYButton.Value = false;
        return;
    end

    app.PLAY = value;
    if app.PLAY
        app.PLAYButton.Text = 'STOP';
    else
       app.PLAYButton.Text = 'PLAY';
    end

    if app.PLAY
        hAudioSource = dsp.AudioFileReader(app.inputFilename,'SamplesPerFrame',app.FrameSize);
        hAudioOut = audioDeviceWriter('DeviceName', app.Output_Device, 'SampleRate', hAudioSource.SampleRate,...
            'SupportVariableSizeInput', true, 'BufferSize', app.BufferSize);

        while app.PLAY
            u = step(hAudioSource);
            if size(u,2) >= 2
                u_st = u(:,1:2);
            else  % mono
                u_st = [u(:,1) u(:,1)];
            end

            if isDone(hAudioSource) % Loop Play
                reset(hAudioSource);
            end

            if app.BYPASS
                out = u_st;
            else
                out = app.octFilt(u_st);
            end
            hAudioOut(out);  % sound out
            drawnow limitrate % for GUI interrupt
            if ~isvalid(app); break; end % check unexpected force termination by user
        end
        reset(hAudioSource);
    end
end


startupFcnの追加

出力デバイス一覧の OutputDeviceDropDown への追加、PLAYButton の初期背景色設定を追加します。RSTDev がクリックされたときは出力デバイスリストの再設定のみを行います。

function startupFcn(app)
    % set output devices
    info = audiodevinfo;
    dev_n = length(info.output);
    sa = struct2cell(info.output);
    sa_Name = sa(1,:,:);
    sa_Name_s = squeeze(sa_Name);
    for i=1:dev_n
        idx = strfind(sa_Name_s{i},'(');
        if isempty(idx)
            idx = length(sa_Name_s{i});
        else
            idx = idx(end) - 1;
        end
        sa_Name_s{i} = strtrim(sa_Name_s{i}(1:idx));
    end
    app.OutputDeviceDropDown.Items = sa_Name_s;
    app.Output_Device = sa_Name_s{1};
    app.OutputDeviceDropDown.Value = app.Output_Device;

    if ~app.RSTDev
        app.PLAYButton.BackgroundColor = [0.75 0.75 0.75];

        app.BandwidthDropDown.Items = {'1 octave', '2/3 octave', '1/2 octave', ...
            '1/3 octave', '1/6 octave', '1/12 octave', '1/24 octave', '1/48 octave'};
        app.BandwidthDropDown.Value = '1 octave';

        app.octFilt = octaveFilter; % octave filter object

        drawFrequencyResponse(app);
    else 
        app.RSTDev = false;
    end
end


終了処理の追加

オーディオ入出力オブジェクトのリリースを追加します。

function UIFigureCloseRequest(app, event)
    release(app.octFilt)
    if exist('hAudioSource','var'); release(hAudioSource); end
    if exist('hAudioOut','var'); release(hAudioOut); end
    delete(app)
end


実行結果

動画でご覧ください(音付き)。


スクリプト


まとめ

前回から比べれば少し複雑に見えるかもしれませんが、オーディオ信号処理一般に共通に使えると思いますので、1回作ってしまえばコピペで使い回しができます。ぜひご活用ください!

(3)があるかどうかは未定です。(¬_¬)


いいなと思ったら応援しよう!