【MATLAB/AppDesigner】波形をマウスドラッグで範囲選択する

#はじめに
音楽編集ソフトでよくある，波形をクリック→ドラッグして範囲選択するというのを実装したい．
<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/87c76e5a-a37a-c5e7-96f1-cce7e039bf4c.png">

今回は`patch`関数を使用して実装を行う．

#ググる
ググったら殆どやりたいことに近いものが出てきた．
[App Designer で作成した GUI において，マウスの操​作でグラフィックスオ​ブジェクトを動かすこ​とはできますか？](https://jp.mathworks.com/matlabcentral/answers/480221-app-designer-gui)

[1 つ以上の塗りつぶされた多角形領域のプロット - MATLAB patch](https://jp.mathworks.com/help/matlab/ref/patch.html)

これを参考に作成する．

#AppDesigner実装
以下実装をまとめる．

##コンポーネント配置
以下のコンポーネントを適当に配置する．

| コンポーネント名            | 説明              |
|:-----------------|:------------------|
| UIAxes                |  グラフを表示する  |
| OpenFileDialogButton     |  ファイルダイアログを開く   |
| FilePathEditField               |  ファイル名を表示 |
![component.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/f228de99-1b5d-967e-8232-f460a7912736.png)

##メンバ変数


| 変数名            | 説明              |
|:-----------------|:------------------|
| h                |  patchオブジェクト  |
| mouseStatus      | マウスクリック状態  |
| clickPos         | クリックした座標    |

##コールバック関数
###起動時初期化処理
```matlab:startupFcn
function startupFcn(app)
% グラフをセットアップ
xlabel(app.UIAxes, '時間[s]');
ylabel(app.UIAxes, '振幅');
% ツールバーを無効にする
app.UIAxes.Toolbar.Visible = 'off';
% 座標軸の組み込み操作を無効にする
disableDefaultInteractivity(app.UIAxes);
% マウス状態を初期化
mouseStatus = "ButtonUp";
end
```
###ファイル開くボタン押下時
グラフの間引き表示処理として前回投稿した
[【MATLAB】plot表示時間短縮のためにデータを間引く](https://qiita.com/dokagui_tairan/items/3851f702573ab4bac8aa)
で作成した`DecimationProc`を使用する．
（間引き表示しないと表示が重たすぎるので）

```matlab:OpenFileDialogButtonPushed
function OpenFileDialogButtonPushed(app, event)
% ファイルダイアログを開く
[file1, path1] = uigetfile({'*.wav;*.mp3','Audio Files (*.wav,*.mp3)'});
if(file1 == 0)
    % キャンセルで閉じた場合
    return
end

% ファイル名をエディットボックスに格納
app.FilePathEditField.Value = file1;

% ファイルを開いてメンバ変数に格納
[y, Fs] = audioread(fullpath1);
y = y(:, 1);% ステレオの場合は片側だけ使用
decimation = 5000;
y = app.DecimationProc(y, decimation);
Fs = Fs/decimation;

% 時間波形をプロット
N = length(y);
t = ((1:N)-1)/Fs;
plot(app.UIAxes, t, y);

% patchオブジェクトを作成
app.h = patch(app.UIAxes,[0, 0, 0, 0],[0, 0, 0, 0],'r');
set(app.h,'FaceAlpha', 0.3); % 透明度
end
```

### マウスボタン押下時の処理

```matlab:UIFigureWindowButtonDown
function UIFigureWindowButtonDown(app, event)
app.mouseStatus = "ButtonDown";
app.clickPos = app.UIAxes.CurrentPoint;
% クリック位置が範囲外の場合は範囲内に調整
% clamp処理とか言うらしいですね，よく分かりませんが・・・
if(app.clickPos(1,1) < app.UIAxes.XLim(1))
    app.clickPos(1,1) = app.UIAxes.XLim(1);
end
if(app.clickPos(1,1) > app.UIAxes.XLim(2))
    app.clickPos(1,1) = app.UIAxes.XLim(2);
end
% patch描画を消す
app.h.XData = [0, 0, 0, 0];
end
```

###マウスボタンを離した時の処理

```matlab:UIFigureWindowButtonUp
function UIFigureWindowButtonUp(app, event)
app.mouseStatus = "ButtonUp";
end
```


###マウスを移動中の処理
これが本記事でのメインの処理である．
マウスをクリックした位置から現在カーソルの範囲を赤色で重畳表示を行う．

```matlab:UIFigureWindowButtonMotion
function UIFigureWindowButtonMotion(app, event)
if(app.mouseStatus ~= "ButtonDown")
    return
end
Cp = app.UIAxes.CurrentPoint; % get current mouse point on Axes
if(Cp(1,1) < app.UIAxes.XLim(1))
    Cp(1,1) = app.UIAxes.XLim(1);
end
if(Cp(1,1) > app.UIAxes.XLim(2))
    Cp(1,1) = app.UIAxes.XLim(2);
end
app.h.XData = [app.clickPos(1,1), Cp(1,1), Cp(1,1), app.clickPos(1,1)]; % Update xdata for patch object
drawnow
end
```


#結果
ドラッグしたところ，いい感じに範囲選択が出来た．
<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/2fd0bf50-7b5e-898c-86d0-228a8945bb64.png">

##結果をGIFで
GIFで見ると結果がよく伝わると思うが，かなり重たいので実用レベルでは無いかも．
まぁ，これはキャプチャソフトで録画しながらMATLABアプリを動かしてたのでより重たくなっていたので，録画していない時はもうちょっとサクサクに動くわよ．
<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373168/50d452c9-43ee-ecc8-9582-0a51df05f857.gif">