やる気が出る（？）アプリ作成で学ぶMATLABタイマー割り込み生成

#みんなやる気って出ないよね？
出ませんよね。私も基本出ません。
でも私がネットの海で仕入れた情報によると、やる気っていうものは出そう！って頑張るもんじゃなくて何かしら手を付けてしまえばあとから自然とでてくるものだそうです。

**東大教授「やる気を出す方法を考えるのはムダ」――結局 “とりあえず動けば” 勉強は進んでいく。**
https://studyhacker.net/study-motivation

>つまり、私たちが普段からアテにしている「やる気」とは幻想のようなものであり、そもそも何かをやる前からやる気を出すことは不可能なのです。やる気が出ないからこそ立ち上がる、参考書をめくる、手を動かす。「行動することでしかやる気は引き出せない」と覚えておきましょう。

なるほど。

#とりあえず”手を動かせば”いいんでしょ？
物事を単純化しすぎる悪い癖がでてしまいました。
（私のやる気のなさとおふざけっぷりが読者の皆さんにも伝わったと思いますが）
こんなものを作りました。下の画像をクリックしてご確認ください。

※2020/10/29:ちょっと音がでるように改造しました。
[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/5OAk3Kr1djU/0.jpg)](https://youtu.be/I8VyDbzOhFk)

ほらぁ。やる気が出てきましたね！（無理やり感）


早速どのように作成したかをご紹介したいと思います。
よろしくお願いいたします。
#実装の方向性を考えよう
ざっくりですがこんな感じ。

* 1秒毎に割り込みを呼び出すタイマオブジェクトを生成
* キーボードを押したときにカウントアップする変数を作成
* 1秒毎にコールされる関数でキーボード押下数を基にGUI表示更新
 

#AppDesignerをひらこう

まずはMATLABを起動して新規作成から”アプリ”を選択しましょう。
テンプレートを選ぶウィンドウがでてきますが適当に”空のアプリ”を選択するとIDEのような画面がでてきます。

これは今年の初めにわたくしが投稿した駄文でもご紹介したAppDesiner の編集画面です。
![Snag_419128.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331637/dd798a62-3e95-2b72-3afc-ef25a88e2188.png)
その時の駄文はこちら→https://qiita.com/griffin921/items/d761abc656542f62a611

ざっくりとしたAppDesiner の使い方説明は上記駄文に譲るとして、本記事ではどのような実装を行って動画の動作を実現したか書かせていただきます。

#レイアウトをざっくり決める
![Snag_4db3b9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331637/559bd0a0-f32c-f29e-0aa1-2ca160cb8e78.png)
設計ビュー画面で
コンポーネントライブラリからコンポーネントをドラックアンドドロップで簡単配置です。
我ながらめちゃくちゃ適当な配置です。でもいいんです。手を動かせばやる気が出るんだから（？）

#タイマオブジェクトの生成
1秒ごとにコールされるコールバック関数を追加したいのですが、その前には時間を測ってくれるタイマーが必要ですね。


このタイマー作成をアプリ開始時に呼び出されるコールバック関数に書いちゃいましょう！そしたらアプリ起動したらすぐタイマー開始します。


エディター画面の左上、コールバックと書かれたボタンを押すと、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331637/e614ab0f-a87e-2111-358d-5c82e837f0b5.png)

このような画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331637/02285621-ea50-962e-e696-dbe819ac1dcd.png)

AppDesinerでは設計ビュー画面で配置した部品、コンポーネントごとに
対応したコールバック関数が用意されています。
アプリ：のプルダウン欄でそのコンポーネントを選択し
コールバック：でそのコンポーネントに対応するコールバック関数を選択します。
名前：にはデフォルトで名前が付けられますが、任意の名前を設定することもできます。

ここでコールバックの追加を押すと、勝手にコールバック関数がエディター上に記載されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331637/c1707ef5-89c6-de6b-9ade-8ab29309718a.png)
ここの白いエリアに好きなコードを書き込んでいきます
早速下記のように記載しました。

```matlab
%タイマオブジェクト作成 
    app.UpdateTimer = timer('Period', 1.0,...
    'ExecutionMode', 'fixedRate', ...
    'TasksToExecute', Inf, ... 
    'TimerFcn', @app.mytimer_func);

%パラメータ初期化
    app.TypeNum = 0.0;
    app.PastTypeNum = 0.0;
    app.MoriagariMeterGauge.Value = 0.0;


%音声データの読み込み
             load('voicedata.mat');
             
             app.VoiceLv1 = audioplayer(Level1, fs);
             app.VoiceLv2 = audioplayer(Level2, fs);
             app.VoiceLv3 = audioplayer(Level3, fs);
             app.VoiceLv4 = audioplayer(Level4, fs);
             app.VoiceLv5 = audioplayer(Level5, fs);
             app.VoiceLv6 = audioplayer(Level6, fs);
             app.VoiceLv7 = audioplayer(Level7, fs);

%タイマ開始
    start(app.UpdateTimer);
```

色々書いてますが
ポイントはapp.UpdateTimerにMALTABのtimerクラスをインスタンスしているところですね。

timerクラスはこのリンク先に記載があるように、
特定の関数をある時間単位で繰り返し呼び出すという処理を設定可能です。
https://jp.mathworks.com/help/matlab/ref/timer-class.html

インスタンス時の引数は呼び出し周期（今回の場合は1.0秒毎）や、どこを起点として周期を定義するか等の設定が可能です。
最後にTimerFcnでは@app.mytimer_funcと記載されていますが、
これは後々定義する　app.mytimer_funcのハンドルを記載することで、
設定の時間になったらどの関数を呼び出せばいいかを登録することができます。

関数名の前に＠をつけることで”関数のハンドル”という意味になります。C言語でいえば関数のポインタみたいなもんです。
これで1秒毎に app.mytimer_funcが呼び出されるようになりました。
start(app.UpdateTimer)でタイマ割り込みが開始されます。


音声データはあらかじめmatファイルに保存しておきました。
ここで変数に代入していつでも呼び出せるようにします。


#キーボードコールバック関数を作成
こちらもコールバック関数の中から"UIFigureKeyPress"を選択します。これはキーボードが押下されたら呼ばれるコールバック関数です。

こちらにあらかじめ設定しておいたプロパティ（アプリ内で使用できる変数）、TypeNumを定義しておいて
そいつをカウントアップさせる処理を記載しましょう。

```matlab
  % Callback function: MoriagariViewerUIFigure, TextEditField
  function UIFigureKeyPress(app, event)
     app.TypeNum = app.TypeNum + 1.0;
  end
```

ただ困ったことにこのキーボードのコールバックはアプリのFigure領域にマウスポインタがあるときしか有効ではないです。
なんとテキストボックスに入力している最中はこのコールバック関数は呼ばれません！

これはテキストボックスというオブジェクト自身がKyePressコールバックを持たないためです。

しかしあきらめてはいけません。
テキストボックスには”ValueChangingFcn”という変数が変更されるたびに呼び出されるコールバックがあるのです！
これが呼び出された時にもUIFigureKyePressが動いてくれたら完璧じゃない。

そんなときにはこんな風に指定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331637/ee93c196-6e75-a7e8-ded6-2489accdc19d.png)
コンポーネントブラウザでTextFieldを選択し”コールバック”画面を表示。
そこに表示させるコールバックのうち”ValueChangingFcn”の右側のプルダウンから"UIFigureKeyPress"を選択します。
これでなんと、”ValueChangingFcn”と"UIFigureKeyPress"が共通化されてどちらの関数がコールされても
"UIFigureKeyPress"で記述した内容が実行されます！

#タイマコールバック関数を作成

最後に1秒ごとに呼び出される関数を記載すればOK。

オブジェクトが持つ変数にアクセスして値を変更すれば
表示させるテキストやメータの状態を更新することが可能です。

たとえば、メータオブジェクトであれば

```matlab
app.MoriagariMeterGauge.Value
```
というメンバを持つので、この場合ですと0~100のいずれかの数値を代入したら
メータの針の位置が更新されます。

またテキストボックス上の応援メッセージを更新するときは

```matlab
app.OuenMessage.Text
```
というメンバに文字列を代入させることで表示される文字を変えることができます。
コンポーネントにどのようなメンバがいるかは画面右側のコンポーネントブラウザで”インスペクター”表示をするとリストアップされるのでここで、設計ビューで配置したコンポーネントがどんなメンバを持つか確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331637/0279e82d-0efa-46f4-2c74-1af65b33221b.png)

タイマコールバック関数の全文はこちら

```matlab
function mytimer_func(app, obj, event)

            %インゲージバー更新---------------------------------
            %今のタイプ数がPastTypeNumに対して同値以上であれば
            if app.TypeNum >= app.PastTypeNum 
                %タイプ数をメータに追加
                NewMerterValue = app.TypeNum + app.MoriagariMeterGauge.Value;
            else
                %今のタイプ数がPastTypeNum以下であれば現在のメータ値を0.95倍にする。
                NewMerterValue = app.MoriagariMeterGauge.Value*0.95;
            end
            
            %メータ値の上限、下限処理
            if NewMerterValue > 100.0
                NewMerterValue = 100.0;
            end
            
            if NewMerterValue < 0.0
                NewMerterValue = 0.0;
            end
            
            %メータ値更新
            app.MoriagariMeterGauge.Value = NewMerterValue;
            %------------------------------------------------
            
            %ユーザへのメッセージ更新---------------------------
            if NewMerterValue < 10
                app.OuenMessage.Text = "ここ↓になんでもいいから文字打ったらやる気が出るよ";
                app.OuenMessage.FontSize = 18;
                app.OuenMessage.FontWeight = 'normal';
                app.OuenMessage.FontColor = 'black';
                play(app.VoiceLv1);
                
            elseif NewMerterValue < 30
                app.OuenMessage.Text = "もう少し頑張ったらやる気でるよ！知らんけど！";
                app.OuenMessage.FontSize = 18;
                app.OuenMessage.FontWeight = 'normal';
                app.OuenMessage.FontColor = '#0072BD';
                play(app.VoiceLv2);
                
            elseif NewMerterValue < 50
                app.OuenMessage.Text = "いけるいける！もうちょっと頑張ろう！";
                app.OuenMessage.FontSize = 20;
                app.OuenMessage.FontWeight = 'normal';
                app.OuenMessage.FontColor = '#D95319';
                play(app.VoiceLv3);
                
            elseif NewMerterValue < 70
                app.OuenMessage.Text = "すごいすごい！本当にやる気出てるんじゃない！？";
                app.OuenMessage.FontSize = 22;
                app.OuenMessage.FontWeight = 'bold';
                app.OuenMessage.FontColor = '#D95319';
                play(app.VoiceLv4);
                
            elseif NewMerterValue < 80
                app.OuenMessage.Text = "やる気出すぎでしょ。この勢い止まらないね！";
                app.OuenMessage.FontSize = 26;
                app.OuenMessage.FontWeight = 'bold';
                app.OuenMessage.FontColor = '#D95319';
                play(app.VoiceLv5);
                
            elseif NewMerterValue < 90
                app.OuenMessage.Text = "神の領域だね！やる気MAX！！"; 
                app.OuenMessage.FontSize = 30;
                app.OuenMessage.FontWeight = 'bold';
                app.OuenMessage.FontColor = '#7E2F8E';
                play(app.VoiceLv6);
            else 
                app.OuenMessage.Text = "やる気のボーナスタイムや！"; 
                app.OuenMessage.FontSize = 34;
                app.OuenMessage.FontWeight = 'bold';
                app.OuenMessage.FontColor = '#FF0000';
                play(app.VoiceLv7);
                
            end
             %------------------------------------------------
            
            %TypePerSecが0でないならPastYtpeNumを更新
            if app.TypeNum > 0
                app.PastTypeNum = app.TypeNum;
            end
            
            %TypePerSec更新
            app.TypesPerSecEditField.Value = app.TypeNum;
            
            %TypeNum初期化
            app.TypeNum = 0.0;
        end
```
#お片付けも忘れずに
ウィンドウが閉じたときに呼ばれるコールバック関数、
CloseRequestコールバック関数も実装し、タイマの停止とタイマオブジェクトのデリートをしておきましょう。
これをしないとタイマー処理がずーっと残ってしまいます。

```matlab
% Close request function: MoriagariViewerUIFigure
        function MoriagariViewerUIFigureCloseRequest(app, event)
            stop(app.UpdateTimer)
            delete(app.UpdateTimer)
            delete(app)
            
        end
```
#できましたね！あとはお仕事するだけです。

え？作るまでのやる気が出ない？
そういう方は鬼滅の刃劇場版を見に行きましょう！
これでやる気出しまくってください！
最後まで読んでくださってありがとうございました！！やる気MAX!!!