【MATLAB】AppDesignerでGUIを作成する

#はじめに
`matlab`は便利なツールではあるが，スクリプトを実行する場合には基本的にはコマンドウィンドウ（CUI)でコマンドを叩く必要があり，コンピュータ操作に不慣れな人には使いづらいことがある．今回は`matlab`の`AppDesigner`を使用してGUIを作成する．
なお，作成は私も初めてであるので，上手くいくかは分からない．

#どういうものを作るか
[【MATLAB】指定フォルダ内のファイルを検索して処理をする](https://qiita.com/dokagui_tairan/items/e60ba14f0519397616a5)
にて作成した，

**指定したフォルダとそのサブフォルダ内の全てのWAVファイルを検索し，一つ一つのファイルを開いて，時間波形を描いてPNGで保存する**

という処理をGUIにて行えるようにする．

#AppDesigner立ち上げ
![000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/a90a7838-aa4a-5939-f9bf-e65a2a401461.png)
ホームタブの「新規作成」から「アプリ」を選択する．

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/1c3644c4-f0a3-01ae-66c1-8a32807b5ea9.png)
「App Designer スタートページ」が立ち上がるので，「新規作成」の「空のアプリ」を選択する．

![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/d460f975-d643-3a64-b5f0-366c3c1d38be.png)
GUIの作成画面が立ち上がる．


#コンポーネントを配置する
![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/6cd027a0-e9c8-ccba-10e0-c9aead4b73a1.png)
「コンポーネント ライブラリ」から必要なコンポーネントを画面中央にドラッグアンドドロップする．

![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/6c2aa83c-f060-4b83-a4bc-e9531881c7df.png)
上図のように，
・編集フィールド（テキスト）
・ボタンを2個
・テキストエリア
配置した．

##コンポーネント説明
![005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/c19e36ab-b699-1537-8a0d-7f7344d4dc42.png)
各コンポーネントの説明は以下の通り．

**①フォルダ名指定エディットボックス**
ここに処理するフォルダのフルパスを入力する．

**②フォルダ名指定ダイアログ立ち上げボタン**
	「参照」ボタンを押下すると，フォルダ選択ダイアログが立ち上がり，ここで選択したフォルダのフルパスが①のフォルダ名指定エディットボックスに入力される．

ユーザは①または②から処理するフォルダを指定することになる．

**③処理実行ボタン**
「処理実行」ボタンを押下すると，「フォルダ名指定エディットボックス」で指定されたフォルダを検索し，処理を行う．

**④メッセージ表示テキストボックス**
各メッセージを表示する．
例えば「フォルダ名指定エディットボックス」が空または存在しないパスを指定して「処理実行」ボタンを押下した場合に「フォルダが存在しません！」などのメッセージを表示する．

##コンポーネント名称
![009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/2c27daa0-8a0a-da38-4e6f-19844d12bdd5.png)
各コンポーネントの名称がデフォルトだと「Button」「Button_2」など分かりづらいので，上のように変更した．

##試しに実行
![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/b427f13e-b520-9a86-6529-e7a81bc5e919.png)
実行してみた．当然，中の動きはまだ何も記述していないので，ボタンを押しても何も起こらない．


#コンポーネントの処理を記述する
各ボタンに対して，ボタンが押された場合の動きを記述していく．

##「参照」ボタン
![010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/6ee73657-ce2b-f028-8278-c3998dd2fd64.png)
「設計ビュー」で「参照」ボタンを押下または右カラムの「コンポーネントブラウザー」から「app.DirectoryButton」を選択し，「コンポーネントブラウザー」下部の「コールバック」タブを選択する．「ButtonPushedFcn」のコンボボックスの▼を押して「ButtonPushed コールバックの追加」を選択する．

![011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/ece8f126-e2a6-a2ad-1157-852606885520.png)
中央カラムの「コードビュー」に「DirectoryButtonPushed」関数が追加された．ここに「参照」ボタンが押下された時の処理を記述していく．


```matlab:DirectoryButtonPushed

function DirectoryButtonPushed(app, event)
    rootDir = uigetdir(pwd, '分析するディレクトリを指定してください');
    % 「キャンセル」が押された場合
    if(rootDir==0)
        return
    end
    app.DirectoryEditField.Value = rootDir;
end
```
`app.DirectoryEditField.Value`が「フォルダ名指定エディットボックス」の値である．


##「処理実行」ボタン
「参照」ボタンと同様に，ButtonPushed コールバック（`ExecuteButtonPushed`）を追加する．
`ExecuteButtonPushed`内に処理を全部書くと行数が増えて大変なので，プライベート関数を追加して細かい処理はそっちに書く．

###プライベート関数の追加
![012.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/046b5e09-9eb5-5dea-cc88-13984dc978d2.png)
「エディター」→「関数」→「プライベート関数」を選択する．

![013.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/d4be3ec3-fd13-ae77-030e-ed4a2eb81913.png)
「コードビュー」に`func`が追加される．`func`を適当な名前（ここでは`getWavFileFullPaths`）に変更する．

今回は引数としてフォルダのパスを指定すると，そのフォルダとサブフォルダからWAVファイルを検索して，そのフルパスを返す`getWavFileFullPaths`関数を作る．

```matlab:getWavFileFullPaths
function wavFileFullPaths = getWavFileFullPaths(~, directoryFullPath)
    % カレントディレクトリをバッファ
    origDir = pwd;
    % カレントディレクトリを変更
    cd(directoryFullPath);
    % カレントディレクトリとサブディレクトリを検索
    fileList = dir('**/*.WAV');
    % カレントディレクトリを戻す
    cd(origDir);
    % fileListからフルパスを作成
    wavFileFullPaths = fullfile({fileList.folder}, {fileList.name});
end
```
なお，`fullfile`の使い方は[過去の投稿記事でいただいたコメント](https://qiita.com/dokagui_tairan/items/e60ba14f0519397616a5#comment-0760a5c99cf489a3b3cc)を参考にさせていただいた．

同様に，プロセスバーの文字列を生成する`getWaitBarText`も作成する．

```matlab:getWaitBarText
function waitBarText = getWaitBarText(~, n, total)
    waitBarText = sprintf('Please wait...[%d/%d]', n, total);
end
```

###「処理実行」ボタン コールバック関数


```matlab:ExecuteButtonPushed
function ExecuteButtonPushed(app, event)
    % メッセージテキストエリアの中身をクリア
    app.MessageTextArea.Value = '';
    
    % エディットボックスの値をチェック
    if(~isfolder(app.DirectoryEditField.Value))
        app.MessageTextArea.Value = 'フォルダが指定されていません．処理を終了します．';
        return
    end
    % WAVファイルのフルパスを取得
    wavFileFullPaths = getWavFileFullPaths(app, app.DirectoryEditField.Value);
    
    n = length(wavFileFullPaths);
    
    % プロセスバーを表示
    h = waitbar(0, getWaitBarText(app, 0, n));
    
    app.MessageTextArea.Value = '処理を開始します．';
    
    for each = struct('path', wavFileFullPaths, 'index', num2cell(1:n))
        % メインの処理
        sampleSaveGraph(each.path);
        % プロセスバーを更新
        waitbar(each.index/n, h, getWaitBarText(app, each.index, n));
        % テキストエリアにメッセージを表示
        s = sprintf('処理完了[%d/%d]：%s', each.index, n, each.path);
        app.MessageTextArea.Value = [app.MessageTextArea.Value; s];
    end
    
    app.MessageTextArea.Value = [app.MessageTextArea.Value; '処理を全て完了しました．'];
    
    % プロセスバーを閉じる
    close(h);
end
```

WAVファイルのフルパスを引数に入れると時間波形をPNG保存する関数`sampleSaveGraph`は[過去記事](https://qiita.com/dokagui_tairan/items/e60ba14f0519397616a5)で作成した`sampleSaveGraph.m`を用いた．
<details><summary>念のため再掲すると↓</summary><div>

```matlab:sampleSaveGraph.m
function sampleSaveGraph(fullpath)
% WAVを開く
[y, Fs] = audioread(fullpath);

% グラフを描く
N = length(y);
t = ((1:N)-1)/Fs;
h = figure('WindowState','minimized');
plot(t, y);
xlim([0, t(end)]);
ylim([-1, 1]);
[filepath ,name, ext] = fileparts(fullpath);

% グラフタイトルはWAVファイル名とする
title([name, ext], 'Interpreter', 'none');

xlabel('時間[秒]');
ylabel('振幅');

% 保存するファイルのフルパスを生成
filename = fullfile(filepath, [name, '.png']);
% ファイルを保存
saveas(h, filename);

close(h);
end
```
</div></details>

なお，メインのループ処理は[過去の投稿記事でいただいたコメント](https://qiita.com/dokagui_tairan/items/e60ba14f0519397616a5#comment-0760a5c99cf489a3b3cc)を参考にさせていただいた．

`app.TextArea.Value = [app.TextArea.Value; hogehoge];`と書くと，テキストエリアに文字を追加出来るようである．



#実行
![014.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/41440de5-1ef1-b5cc-e424-00c9c1a2d8db.png)
実行してみた．（上図，パスの一部にユーザ名などが入っていたので白塗りにした）
期待通りの動きとなっている．


#まとめ
今回は`matlab`の`AppDesigner`を用いてGUIを作成した．
過去に作成したスクリプト（*.mファイル）を実行出来るので，移行もやりやすい．
また，コールバック関数など自分で記述する必要のある箇所以外は編集できないようにプロテクトがかかっており，不用意にいじってアプリを起動できなくしちゃうなどの事故が防げるのはとても良いと思った．