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という参考が手元になくてだるいので
やる気が出たら物理メトロノームを買おうかなと思います。がんばっていきましょう