1週間ばかりブランクがあったのにしれっと続きを書いているわけだけど、前回までで技術的に参考になりそうな話はだいぶ書いてしまって、xmdspにかかる作業も後は細かい実装だけという雰囲気になった。そんなわけで今回から多少細かいネタを書く。
ここまでのバージョンでは演奏制御機能が何も無かったのでので、一旦演奏を開始したら演奏終了を待つかアプリケーションを終了するしか無かった。MidiPlayerのAPIとしては、その他にも一時停止、再開、変速再生(!)の機能が備わっている。とりあえずpauseとresumeはmust to doだ。
これらの機能は簡単に実装できた。変速機能については、後で説明する。
次に、各種の時間経過に伴うステータスのテキスト表示部分を実装することにした。具体的には、(1)時間による演奏位置の表示、(2)トータル演奏時間、(3)ティックカウント、(4)テンポ、(5)拍子 の5つだ。(2)はSMF全体を眺めて、テンポの変化の流れと、そこに至るまでのティックカウント数から、計算で求めることが出来る(MidiPlayerのAPIにはこれが既に含まれている)。(4)と(5)はMETAイベントを追跡していれば分かる(これらもMidiPlayer上に「現在の値」を取得できるように実装した)。
(3)は割と面倒くさい。(3)は面倒というか、設計を決めるのが悩ましい。MidiPlayerには、現在位置までのティックカウントが保存されていて、簡単に取得できる。内部的には単にMIDIイベントのデルタタイムを演奏しながら加算しているだけだ。View側では、そのティックカウントを表示するだけで足りる…はずである。実際に「それだけ」にして何が起きるかというと、ティックカウントはしばしば「止まったまま」になる、ということだ。時間は進んでいるのに、ティックカウントは次のイベントが来るまで変更されないので、イマイチ美しくない。
しかしこれはこれで実用性がある。このヴィジュアル プレイヤーの目的のひとつは、SMFの作成しやすくすることだ。単に時間を追いかけるだけでなく、長い沈黙の間に、イベントが適宜送信されているかどうかを、これで確認できることがあるなら、それは好ましいことだ。そういうわけで、このティックカウントはそのままの値を表示することにした。
(1)には2つ面倒な側面がある。ひとつは、これは完全に「時間」という独立したイベントループに基づいて、描画イベントが発生するということだ。他の描画イベントの端緒は、MIDIメッセージの受信、または演奏状態の変化を伴うユーザーの操作、に限られていた。この現在時間の表示のためだけに、別のループが発生することになる。
もうひとつ、さらに面倒な側面の話だが、ここで変速再生の話を混ぜることにしよう。
変速再生はいわゆる早送りの機能を実現するためにあって、使うと面白いと思えることもあるのだけど、MIDIファイルをオーサリングしている時に実用的かどうかは分からない。テンポを(SMF上の指定値からユーザー入力に応じて)変更するという機能は、割と複雑な処理を要求する。演奏時に、「今の演奏位置は時間でいうとどの辺だろう?」という情報(つまり(1)である)を知りたい場合、テンポがユーザーによって変更されなければ、演奏開始時間から現在時間までの差分のみで計算できる。しかし途中でテンポが変更されると、変更されたテンポに基づく時間の補正計算が必要になる(「テンポ変更が無ければこの時間になるはずだった」値と実際の演奏時間とのデルタ値を保存しておく必要がある)。これは少々面倒だ。
とは言え…実際にはpause / resume機能を実装すると、この「現在の演奏時間位置」の表示はいずれにしろ再計算されなければならなくなるので、どうせならこの倍速処理を実装してしまっても大して問題ない。
というわけで、(1)の実装にあたっては、「現在までにごちゃごちゃとユーザー操作によって操作された時間(TimeSpan)」と「最後に時間周りの操作が完了した時間(DateTime)」を合わせて計算することになる。早送り中は、テンポが補正されることによる時間の名目上の時間補正も必要になるので、それも考慮に入れられている。
というわけで、表示上は地味だが、内部ではいろいろな考慮事項が働いているのである。
https://github.com/atsushieno/xmdsp/commit/f5eae9634b5509a9c6fd85448e79127abdc6551c
https://github.com/atsushieno/xmdsp/commit/0b0b781df303759cdb45b93ee633c49acffef5f6