ストリームからBPMを計算する。RxJSを使って
ボタンを叩くタイミングからBPMを検出して設定してくれる、いわゆるタップテンポ機能。 シーケンサーやサンプラーなどに搭載されていて、最近はブラウザ上で確認できるサービスもいくつかある。
- Tap for Beats Per Minute BPM
- Tap BPM beat finder
- Tap BPM - Online Beats Per Minute Calculator and Counter
- http://www.tempotap.com
Reactive Programming
そしてこの記事。
【翻訳】あなたが求めていたリアクティブプログラミング入門 - ninjinkun's diary
昔これ読んだときに適当なイベントストリームからBPM計算することもできそうと思ったので、勉強ついでに似たようなものを作ってみた。
Tap Tempo Machine - Simple BPM calculator
Jキーを押す*1か灰色部分をタイミングよくクリックしてるとBPMが表示される。直近4回のBPMの平均を表示する。それだけのページ。
GitHub - Reactive-Extensions/RxJS: The Reactive Extensions for JavaScript
クリックイベントからBPMを出力する
クリックイベントをobservableに変換してBPM出力まで持ってくあたりの部分を書いてみる。BPMの計算方法は一応書いておくと、
BPM = Beats Per Minute = Beats/Minute = Beats/60s
つまり60秒間に打つBeatの数がBPMということなので、60秒を各Beat間の秒間隔で割るとBPMが出てくるということになる。 実際のコードからちょっと改変してるけど雰囲気こんな感じになる。
// クリックイベントをobservableのシーケンスに変換 var source = Rx.Observable.fromEvent(window, 'click') // observable間の時間間隔の情報を持つobservableのシーケンスを返す .timeInterval() // 1つめのobservableの時間間隔は0になるので無視する。 .skip(1) // timeInterval()で返ってきたそれぞれのobservableがもつintervalというプロパティだけを // 新しいobservableのシーケンスとして返す。 .pluck('interval') // 前回のイベント発生から10秒経っていたら次のobservableは無視する。 .where(function (interval) { return interval <= 10000; }) // 4つのobservableをひとまとまりのobservableとして返す。 // 1要素単位でずらして格納していく。 .windowWithCount(4, 1) // 入れ子になったobservableのシーケンスを平準化する。 // 平準化するときに各observableの値4つ(ここではintervalの値)の平均をとる。 .selectMany(function (elements) { return elements.average(); }) // 平均されたintervalをBPMに変換して小数点以下を四捨五入する。 .map(function (interval) { return Math.round(60000 / interval); }); // クリック毎に直近4回の平均BPMが出力される。 source.subscribe(function (bpm) { console.log(bpm); });
多分この中でイメージしにくいのはwindowWithCount
とselectMany
だと思う。
windowWithCount(4, 1)
は、例えば
1,2,3,4,5,6,...
このようなシーケンスの場合、
[1,2,3,4],[2,3,4,5],[3,4,5,6]...
となって返ってくる。[ ]
がひとつのobservable。[ ]
に含まれている各要素もobservable。observableが入れ子になって返ってくる。
第二引数を変えると、例えばwindowWithCount(4, 4)
の場合、
[1,2,3,4],[5,6,7,8],[9,10,11,12]...
となる。4つずつskipして格納される。ドキュメント見るといいです。
RxJS/windowwithcount.md at master · Reactive-Extensions/RxJS · GitHub
selectMany
はwindowWithCount
によって入れ子のobservableになってしまったシーケンスを平らなobservableのシーケンスに戻す。
[1,2,3,4],[2,3,4,5],[3,4,5,6]...
これが
1,2,3,4,2,3,4,5,3,4,5,6...
となって返ってくる。*3 BPMの例の場合、平らなシーケンスに戻す前にaverage()
によって子observable各要素の平均をとっている。例えば
[120,122,122,120],[124,122,123,123]...
こんな入力の場合
121,123...
このように返ってくる。
RxJS/selectmany.md at master · Reactive-Extensions/RxJS · GitHub
おわり
普段C#のLinq書いてるからRxのメソッドチェーン書いててとても気持ち良い。嬉しい。ありがとう、ありがとう。
GitHub - distkloc/tap-tempo-machine: Simple BPM calculator
Tap Tempo Machine - Simple BPM calculator
スマホでも一応動くけど、ngTouchのngClick使ってるにもかかわらず反応が鈍い。CSSアニメーション切っても改善しなかったのでJS見なおす必要ありそう。