はじめに
その①↓の続きです。
その②では、その①で作成したピアノに、録音&再生機能を追加してみます。
実現方法
利用するコントロール(その①で紹介したもの以外)
弾いた情報を逐一記録する方法として、コレクションを利用してみます。
コレクションとは↓
他、弾いたタイミングの計測に使用する録音用タイマー、再生時に使用する再生用タイマーを用意します。
制御方法
録音開始と同時に録音用タイマーをスタートさせ、録音中に鍵盤をタップしたら録音用タイマーの値とタップした鍵盤の種類をコレクションに追記します。
鍵盤をタップする度にストップウォッチのスプリットタイムを刻むイメージです。
再生のやり方はいくつか方法がありそうですが、今回はコレクションに登録された各音の録音用タイマー値の差分をその音の長さとみなして、その差分値を再生用タイマーの時間に都度設定し、音を再生していくことにしました。
作り方
録音機能
1. 録音用タイマーを置く
録音開始/終了ボタンを兼ねます。タイマー期間=録音できる時間はタイマーの初期値のまま30秒としました。
クリック時の処理"OnSelect"は以下を記載しました。
// 録音中 or 録音してない を反転
UpdateContext({RecStartStop:!RecStartStop});
// 録音開始 or 終了時で処理を分岐
If(RecStartStop=true,
// 録音開始時の処理
// 録音用カウンタの初期化
UpdateContext({RecIndex:1});
// 録音用コレクションの初期化と、開始タイミングを記録
ClearCollect(SoundRec,{Index:RecIndex, Key:0, TimerValue:0});
// カウントアップ
UpdateContext({RecIndex:RecIndex+1}),
// 終了時の処理
// 録音終了時のタイミングを記録
Collect(SoundRec,{Index:RecIndex, Key:0, TimerValue:RecTimer.Value});
// 次回以降も録音できるようタイマーリセット
Reset(RecTimer)
)
1, 2行目は録音中か否かを変数RecStartStopに格納しています。クリックする度にtrueとfalseが反転します。タイマーのRec / Stop表示切り替えはこの変数を参照しています。
3行目以降で、録音開始時と終了時の各々の処理を記載しています。
開始時は、録音された音の数をカウントする変数RecIndexと、録音するためのコレクションSoundRecを初期化。終了時は最後に弾いた音の長さを測るために終了時のタイムもコレクションSoundRecに追記し、次回録音時にタイマー値を0から再スタートさせるためにタイマーをReset処理することで終了させています。
2. 録音中に鍵盤をタップしたらコレクションに追記する
録音中に鍵盤をタップした場合は、音を鳴らす処理に加えてコレクションSoundRecに追記する処理を記載します。コレクション内のKeyの値は、低いドから高いドまでを1~13の数値で記録することにしました。
この処理を、各鍵盤に追記します。
ド→レ→ミ(上記の鍵盤番号で言うと1→3→5)と弾くと、コレクションの中身はこんな感じになります。
再生機能
1. 再生用タイマーを置く
録音用と同じく、再生開始/終了ボタンを兼ねます。
タイマーを動的に制御するため、開始有無(Start)/タイマー長(Duration)/繰り返し有無(Repeat)/リセット(Reset)に各々変数を埋め込んでおきます。各々の利用方法は後ほど…
クリック時の処理"OnSelect"は以下を記載しました。
// 再生中 or 再生してない を反転
UpdateContext({playStartStop:!playStartStop});
// 再生時と終了時で処理を分岐
If(playStartStop=true,
// 再生開始時の処理
// 再生用カウンタの初期化
UpdateContext({PlayIndex:1});
// 録音開始~1音目までの時間をタイマーセット
UpdateContext({PlayTime:LookUp(SoundRec,Index=PlayIndex+1).TimerValue - LookUp(SoundRec,Index=PlayIndex).TimerValue});
// タイマーの繰り返し処理を有効化
UpdateContext({PlayRepeat:true});
// タイマースタート or 終了時のおまじない
UpdateContext({
PlayReset:false,
Play:true
}),
// 終了時の処理
// タイマースタート or 終了時のおまじない
UpdateContext({
Play:false,
PlayReset:true
})
)
1, 2行目は再生中か否かを変数playStartStopに格納しています。クリックする度にtrueとfalseが反転します。タイマーのPlay / Stop表示切り替えはこの変数を参照しています。
3行目以降で、再生開始時と終了時の各々の処理を記載しています。
開始時は、まずコレクションSoundRecを1つずつ読み込むためのインデックス用変数PlayIndexを初期化。続いて、コレクションSoundRecから1つ目と2つ目の長さを読み込んで差分を計算することで、1つ目のタイマーの長さ(=録音開始から1つ目の音を弾くまでの時間)を抽出し、タイマー長用変数PlayTimeに格納します。
タイマーの初回の長さが定義できたら、後はタイマーの繰り返し設定と開始処理のみです。
今回の再生方法は、1つのタイマーの長さを変化させながら繰り返しタイマーを動かし、録音データが最後まできたら繰り返しをOFFにすることで終了させますので、最初は繰り返しあり状態です。繰り返し制御変数PlayRepeatをtrueにしておきます。
後は、開始時にReset:false/Start:trueにし、終了時にReset:true/Start:falseとするように変数を設定することで、2回目以降も再生できるようにします。これはおまじないとして覚えておくとよいかと思います。
これで、まずは「録音開始から1つ目の音を弾くまでの時間」タイマーを動かすことができました。が、この時点ではまだ音を鳴らしていません。ということで、実際に音を鳴らすのはタイマー終了時に処理することにします。
2. OnTimerEndをカスタマイズする
タイマー終了時の処理"OnTimerEnd"には以下を記載しました。
// コレクション読み込み用インデックス変数を更新
UpdateContext({PlayIndex:PlayIndex+1});
// 弾いた音の番号を取得
UpdateContext({PlayKey:LookUp(SoundRec,Index=PlayIndex).Key});
// 弾いた音の長さを計算
UpdateContext({PlayTime:LookUp(SoundRec,Index=PlayIndex+1).TimerValue - LookUp(SoundRec,Index=PlayIndex).TimerValue});
// 弾いた音に応じて音を再生
Switch(PlayKey,
1, // ド
Reset(Audio_C4);
UpdateContext({playC4:true});
UpdateContext({playC4:false}),
2, // ド#
Reset(Audio_Cis4);
UpdateContext({playCis4:true});
UpdateContext({playCis4:false}),
3, // レ
Reset(Audio_D4);
UpdateContext({playD4:true});
UpdateContext({playD4:false}),
4, // レ#
Reset(Audio_Dis4);
UpdateContext({playDis4:true});
UpdateContext({playDis4:false}),
5, // ミ
Reset(Audio_E4);
UpdateContext({playE4:true});
UpdateContext({playE4:false}),
6, // ファ
Reset(Audio_F4);
UpdateContext({playF4:true});
UpdateContext({playF4:false}),
7, // ファ#
Reset(Audio_Fis4);
UpdateContext({playFis4:true});
UpdateContext({playFis4:false}),
8, // ソ
Reset(Audio_G4);
UpdateContext({playG4:true});
UpdateContext({playG4:false}),
9, // ソ#
Reset(Audio_Gis4);
UpdateContext({playGis4:true});
UpdateContext({playGis4:false}),
10, // ラ
Reset(Audio_A4);
UpdateContext({playA4:true});
UpdateContext({playA4:false}),
11, // ラ#
Reset(Audio_Ais4);
UpdateContext({playAis4:true});
UpdateContext({playAis4:false}),
12, // シ
Reset(Audio_H4);
UpdateContext({playB4:true});
UpdateContext({playB4:false}),
13, // 高いド
Reset(Audio_C5);
UpdateContext({playC5:true});
UpdateContext({playC5:false})
);
// 最後の音まで来たらタイマーの繰り返しをOFFにする
If(PlayIndex=Max(SoundRec,Index)-1,
UpdateContext({PlayRepeat:false})
)
記載量が多いですが、やっていることは再生する音を抽出して再生し、音の長さをタイマーセットし、次の音へ…を繰り替えしているだけです。
最後の4行で、最後の音まで来たら繰り返しをOFFにすることで、そこで再生を終了させるようにしています。
3. ついでに再生中の音がどれか分かるように鍵盤を光らせる処理を入れる
せっかくなら再生中にどの鍵盤が弾かれたか分かる方が楽しいので、音番号格納変数PlayKeyを用いて、弾かれた鍵盤に色を付けてみます。
変数PlayKeyが各鍵盤に合致していたら、色を変えるというだけです。
この処理を、音番号を変えながら各鍵盤に追記します。
完成
あとはこれまで出てきた変数をScreenのOnVisibleで初期化すれば、完成です。
こんな感じで動きます。
TimerとCollect関数を組み合わせて、ピアノに録音(?)機能を付けてみた#powerapps pic.twitter.com/lNdA1v1hCR
— Jun’ichi Kodama (@KodamaJn) 2018年12月7日
なお、今回作成したアプリは、以下からダウンロードできます。
ご興味あれば、動かしてみてください。
また、差分値の誤差を丸めて音符の長さに当てはめれば、録音した情報から
楽譜を作成することもできます。
TimerとCollect関数を組み合わせて、ピアノに録音機能と楽譜生成機能を付けてみた。うまくいったとこをアップしたが、綺麗な楽譜になるように弾くのは結構難易度が高い。この手の専門ソフトは微妙なタッチのズレをどう補正しているのだろうか。#powerapps@KeithWhatling Thank you for your idea!! pic.twitter.com/UjOI6VRgW4
— Jun’ichi Kodama (@KodamaJn) 2018年12月10日
まとめ
PowerAppsのタイマーコントロールとコレクションを用いて、ピアノ演奏を録音し再生するアプリができました。
今回は完全に趣味の話になってしまいましたが、何かの作業を記録し再生する
仕組み自体は業務利用にもつながる話なのではないかと思い、記事にしてみました。
何かのご参考になれば幸いです。