MIDIプレイヤーにおけるマーカージャンプの実装

これは個人的にまとめている開発メモから「公開したほうが役に立ちそうだな」という感じのエントリを取り出してきたものなので、書いた当時は微妙に現状とは違うのだけど、とりあえず補足は後で追加する。


DAWで便利な機能のひとつにマーカーがあるのだけど(もっともあまり自分が活用できているとは言い難い)、mugene/xmdspではあまり真面目に向き合ってこなかった。しかし曲が長くなってくると、とりあえず早送りで飛ばしてから再生というのも非効率なので、そもそもマーカーでジャンプできれば楽ではないかと気が付いた。

これは主にxmdspの機能となるはずなのだけど、実際にはxmdsp, mugene, managed-midiの全てに手を入れることになった。

まず、MMLからマーカーを吐き出せるようにする必要がある。これはすでにMARKERという命令で実現していたのだけど、mugeneはメタテキストの処理にバグがあって、3バイトのメタテキストを他のMIDIメッセージと同様に扱うというしょうもない問題があった(1文字のメタテキストをもつSMFを生成したことがなかったので気付かなかった)。これを修正したらマーカーが出るようになった。

次は、SMF中のどこにマーカーがあるのか、SMFから取得できるような仕組みが必要になる。これはmanaged-midiのMidiMusicクラスに機能を追加して対応した。

さて、曲中のマーカーを拾うことはできたが、その位置は全て先頭からのtickでしかわからない。ユーザーが直感的にマーカーを選んでジャンプするには、これが秒単位になっている必要がある。このため、指定されたtickからテンポの変更などを考慮して時間位置を取得する実装が必要になった。これは今までトータル演奏時間を取得するために存在していた処理を再利用して実装した。

これだけやって、ようやくxmdspのUI上にマーカー一覧を表示することができた。

https://twitter.com/atsushieno/status/1087763243467104257

ここまでは序の口だ。この指定された位置にジャンプする機能をMidiPlayerに追加しなければならない。これは割と大仕事だ。というのも…

(1) MIDIにおけるseek処理は、単に処理するMIDIイベントのポインタを置き換えれば済むという問題ではない。MIDIトラック中のメッセージには、コントロールチェンジやプログラムチェンジが存在しており、これらを単純に無視して指定された位置からノートオンを続行すると、意図しない音色やコントロールの設定で音が出ることになる。これでは不十分だ。MIDIの場合は、基本的に先頭からseek先までに含まれるノート以外の命令も処理しなければならない。

理想をいえば、この間でも「後で上書きされる命令」は飛ばすべきなのだが、とりあえずは愚直に全部処理することにした。ひとつには、MIDI出力ポートとして抽象的に指定されているものの中には、受信したメッセージを分析したり蓄積したりフィルターしたりするものがあるかもしれず、それらは「間引き」されたメッセージリストを想定していないかもしれないのである。

間にどのような処理が挟まっているかわからないということを考えると、ひとことでseek処理と言っても、実はその実装アプローチはさまざまなのだ。そう考えると、ここには汎用的なシーク処理のインターフェースが必要になるのだけど、どのようなインターフェースなら既存のMidiPlayerのAPIにどう組み込めるのか、検討が必要になるので、とりあえずまだ公開インターフェースを用意できる段階ではないという結論に至った。

(2) 状態遷移を大きく乱す機能なので注意が必要になる…ということで、PauseやStopなどの命令をあーでもないこーでもない、と組み合わせながら取り組んだのだけど、最終的には余計なメソッドを呼び出さずにシンプルにMIDIメッセージ処理のポインタを移動するのみになった。現時点で既に実装に問題があって、seek呼び出し後に「現在の演奏時間」の値が壊れているのだけど、ここは少し腰を据えて取り組まないと解決しなさそうだ。とりあえずseek機能は今切羽詰まっている打ち込み作業の改善のためにほしいので、これは後回しとなった。

…というわけで、seekの実装はそれなりに大変だったのである。MidiPlayerJSなど、seek/jumpを実装しているライブラリでは、この辺はあんまし考慮されていないということもわかった。多分ardourやtracktion_engineの実装はもう少しまともなのだろうけど、さすがにソースを追っかけている時間がない。興味が出たら後で見る。


…というのが当時の現状で、seekの後の演奏時間はその後ちゃんと直った

マーカージャンプで曲中の任意の場所にすぐ移動できるようになってから、#conditional で条件コンパイルする機会は激減した。これの何が良いかというと、#conditionalでブロックをスキップしても、CCやPITCHやらは処理されないので、条件コンパイルすると曲がおかしくなってかえって原因究明に時間を取られる(のでMMLは慎重に書いていた)という本末転倒な状況から脱却できる。#conditionalのセマンティクスを変更するというアイディアもあるのだが、煩雑になりそうなのとマーカージャンプで十分に作業効率が上がったのとで、だいぶ優先度が低い。