続 Flutterで書いたメトロノームの精度を高める
ハトナミ。
th.hatenablog.jp前回に引き続きやっていきます。
とりあえず、音が鳴る瞬間のDateTimeを観測しました。
I/flutter (24608): 2021-01-17 10:03:23.019920
I/flutter (24608): 2021-01-17 10:03:23.523110 + 503190
I/flutter (24608): 2021-01-17 10:03:24.025354 + 502244
I/flutter (24608): 2021-01-17 10:03:24.527632 + 502278
I/flutter (24608): 2021-01-17 10:03:25.030974 + 503342
I/flutter (24608): 2021-01-17 10:03:25.533099 + 502125
I/flutter (24608): 2021-01-17 10:03:26.037653 + 504554
右のプラスは手計算した差分です。
BPMは120に設定しているので、ここが500000からズレている分だけ想定値とズレています。
やはり安定しておらず、常に遅れている状態なようです。
遅れ具合もまばらです。これを均していきましょう。
色々なマサカリがきているのですが、今回は1つ試した結果を書きます。
while(true)をやめる
こんなマサカリがありました。
https://twitter.com/sirrow/status/1348493240538787842?s=20
htt
://twitter.com/sirrow/status/1348493240
音を鳴らすところを await してるように見えるんだけど、こんな難しいことしないで、単純に javascript の setInterval とか使うインターバルタイマか、あるいはそれを薄くラップしただけの flutter のインタフェース叩けばいいのでは
オッケイ!やってみましょう。
dartはjsの代替を目指して設計されたらしいので、jsのアレコレに対して互換性があるとは思うのですが
Flutterでモバイル開発をしてる限りではjsの存在を感じる事が殆ど無く
どのような関係になってるか具体的なイメージが湧いていません。勉強しないとですね
インターバルタイマについて考えましょう。
繰り返し処理させる君はdartにおいてはTimerでやるっぽいので、とりあえず試してみます。
/// Timerで繰り返し処理する用の音源再生くん void _beat(Soundpool pool, int soundId, Timer t) { print(DateTime.now().difference(check).inMicroseconds); // 計測用 check = DateTime.now(); // 計測のために関数外で定義したDateTime if(!_run) {t.cancel();} pool.play(soundId); }
(差分を手計算するのが面倒だったので、計算してから出力するようにしました。)
とりあえずこんな感じの関数を生やして
var duration = Duration(microseconds: (60000000 ~/ _tempo)); // _tempo = 120; Timer.periodic(duration, (Timer t) => _beat(beatPool, beat, t));
こう書くだけで、Duration毎に音を鳴らして、前回実行時との間隔を出力してくれます。
超簡単。これ使えばwhile使ってごちゃごちゃ書いてたのがスッキリしそうですね。
動かしてみた結果、printの出力と録音した波形はこんな感じでした。
I/flutter (25379): 499513
I/flutter (25379): 500720
I/flutter (25379): 499491
I/flutter (25379): 498867
I/flutter (25379): 500159
I/flutter (25379): 500759
I/flutter (25379): 499186
げ、劇的に改善してる……。マジか。
という訳で、whileループをやめてTimerの繰り返し処理で動くよう修正しました。
github.comコミット8f7bf782bc74401559bc267c47745e2363e473c2でガーッと修正しています。
単純なループ処理は簡単だったのですが、ループ→転換処理→ループ……をうまく書けず
TimerがTimerを起動して無限にぐるぐる回る感じの実装をしました。
とっ散らかり感が凄いです。
とりあえずこれでも動いてはいますが、もっと良い書き方が絶対ありそう。
波形はこんな感じになりました。
前のよりはかなり良い……けど、やっぱり基準に対して微妙に前後していますね。
今まで起きていただんだん遅くなっていく = そもそもテンポがズレている。という問題は解消したのですが
音源再生のタイミングが微妙に前後してるのが直ってません。
人間が聞いて違和感があるぐらいにはズレてるので、まだまだ使用感は微妙です。
もう少し何とかしたいので、頂いたマサカリをもとに何が出来るか考えてみます。
そりでは
5
これは38787842?s=20
Flutterで書いたメトロノームの精度を高める
ハロートナミです。
th.hatenablog.jpについてです。
有り難い事に先日記事をあげたあと、すぐにIT強人(つよんちゅ)からいっぱいマサカリが来ました。ウハウハです。
自分より賢い人に指摘もらうの本当に最高で良い気分です。
頂いたマサカリをもとにもうちょっと精度なんとかならんの?をやっていきます。
現状の問題点
BPM120の設定で録音したデータをBPM120のDAW編集画面に貼っつけた状態です。
濃い縦線と波形が一致している必要がありますが、明らかにだんだんズレていっています。
これを出来るだけピッタリにしましょう。という作業をやります。
音源の再生時間を考慮する
while(_run) { waitTime = 60000 ~/ _tempo; // ここで拍の間の時間を定義 beatPool.play(beat); setState(() => _remainBeat = _remainBeat - 1); await Future.delayed(Duration(milliseconds: waitTime)); // 時間分待機
・・・
まず、メトロノームの拍として使ってる音源の再生時間がDurationに入っとらんやんけ。
という当然のツッコミが入ります。そりゃそうだ。
実装中は、音源を管理するSoundオブジェクトみたいなのを想定していて
そいつから音源の長さを取って、その分差っ引いて……と考えていたんですが
そんなオブジェクトは(自分で作らないと)無いので、面倒になってサボった箇所でした。
頂いたアドバイスがいくつかあったのですが
とりあえず再生前後の時刻を取得して再生時間を計算するようにしてみました。
///音を再生し、再生にかかった時間をmillisecで返す。 Future<int> playSound(Soundpool pool, int soundId) async { final lastTime = DateTime.now(); await pool.play(soundId); return DateTime.now().difference(lastTime).inMilliseconds; }
という感じで再生時間を出してます。
これだけで大分良くなりました。最初と比べるとかなり改善してます。
(上が最初のです。音の大きさは録音環境のせいで変わりました。)
かなり良いんですが、それでも少しずつ遅れていきます。
多少前後するけどかなり合ってますね。
んん、君もしかしてBPMズレてない?
BPMのズレを確認する
赤枠のがDAWの設定値です。
120で録音したものと119で録音したものを並べていますが、120の方がBPM119とぴったりですね。何ででしょう。
BPMを改めて手計算してからソースコードを見直して、まず気付いたのはミリ秒では精度が足りないのでは?という事です。
例えばBPM120の時拍子の間は500msで、BPM119だと504.2です。
今のソースコードだと小数点以下は切り捨てられてしまうので、これは悪影響がありそうです。
という訳で単純に値を全て1000倍して、使う単位をマイクロ秒に更新しました。
dartのDateTime及びDurationはmicrosecondsに対応しているため、ぱぱっと更新するだけです。
soundLength = await playSound(beatPool, beat); setState(() => _remainBeat = max(_remainBeat - 1, 0)); await Future.delayed(Duration(microseconds: (60000000 ~/ _tempo) - soundLength));
とりあえず感が凄いですが、これで精度の問題は解消とします。
……ですが、BPMのブレはあまり改善しませんでした。原因は他にあるようです。
今後
とりあえず書いた分で1エントリにしたいのでここで一旦終わりますが
何とか精度を上げて良い感じに使えるようにしたいです。
今気になってるのは
- そも、whileループってそんなに安定して動くの?
- 音源再生時間を取得するための処理にかかる時間が気になる。
の2つです。
BPMが1ずれてた問題についてはまだ良く分かりませんが
精度が悪いのがたまたまBPM1分のズレとして観測出来ただけなんじゃないかなーという気もしています。
あと、これと合っていればOKという参考が手元になくてだるいので
やる気が出たら物理メトロノームを買おうかなと思います。がんばっていきましょう
だんだん早くなるメトロノームをFlutterで書いた
トナミです。夏休みの話をしてから3ヶ月ぐらい経ちましたね。
今日は冬の話をします。
文脈
11月の半ばにギターを買いました。
本当はテレキャスターが欲しかったけど、某サイトを見てたら中古で安く出ていたので
www.soundhouse.co.jpオタクギターです。紗夜さんとおそろ。
友達が今井リサモデルのベースを持っているので早く撮影会をしたいです。
んでギター買ったんですけど弾けないので頑張って練習しています。
だんだん早くなるメトロノームをFlutterで書いた
自分はもともとベースをやっていたので、その時と同じような練習をしています。
基本的にどのフレーズも規定速度よりかなり遅いテンポで練習し始めて
だんだん早く弾けるようにしていく……というのを繰り返しています。
なので、メトロノーム設定してギター弾いて、メトロノーム弄ってギター弾いて、メトロノーム弄って……を繰り返しています。
で、メトロノームが勝手に早くなってったら面白いかもと思ったので作りました。
スタジオで使う事を考えてスマホアプリにしたかったので、Flutterで実装しました。
github.com出来たものがこれです。
画面も1枚しかないし、機能もごく簡単なのでモノリシックです。オブジェクトもクソもありません。
メトロノーム部分だけ抜き出すと
while(_run) { waitTime = 60000 ~/ _tempo; beatPool.play(beat); setState(() => _remainBeat = _remainBeat - 1); await Future.delayed(Duration(milliseconds: waitTime)); if (_tempo < _maxTempo && _remainBeat == 0) { await finishPool.play(finish); setState(() { _tempo = _tempo + _stepSize; _remainBeat = calcBeatPerLoop(); }); // その時のテンポに合わせてインターバルを設定しないと違和感が出る await Future.delayed(Duration(milliseconds: 60000 * 4 ~/ _tempo)); // 入の4カウント waitTime = 60000 ~/ _tempo; for(int i = 0; i < 4; i++) { await clickPool.play(click); await Future.delayed(Duration(milliseconds: waitTime)); } } }
という感じの実装になっています。
音を出すやつはsoundpool | Flutter Packageを使っています。
こいつを非同期で動かして基本的に無限ループさせ、外部からsetStateで_runをfalseにすると止まる。というだけです。手抜き感
これ微妙に安定していなくて、音源のロード?の時間のせいなのか細かくテンポが前後する気がしています。
手元に高精度のメトロノームが無いのでテンポに関する検証が面倒くさいです。
個人的には別に良いかと思える範疇なのですが、どなたか改善案あったらマサカリ下さい……。
あ、今書いてて気づいたけどpool.playにawaitついてるのは凡ミスな気がしています。
書いてみて
3連休初日に環境設定だけして、2日目でFlutter公式サンプルアプリ(?)から上記リポジトリまで実装しました。3日目の昼にこのブログを書いています。
1日で作ったにしては良い感じという事でそれなりに満足しています。
Flutterは状態管理が難しいようで、複雑なアプリに耐えれるかは良く分かりませんが
単機能なアプリを作るのはめちゃくちゃ簡単で良いですね。
今日はこのアプリを使って練習してみて、以降のブラッシュアップに繋げたいと思います。ドッグフード美味しいと良いですね。
実装中、機能が増える度に完成したつってTwitterに投稿してたんですが
細かく披露する事でモチベーションが保てたので良かったです。
どうせ誰も見ないので気軽に披露していきたいですね。
ちなみに1日で9回完成しました。良かったですね