ピアノロールレインでキー押下タイミングとピアノロールがずれる
ピアノロールレインでは、ノートONになる前からキーの押下を開始しなければならないため、 シーンオブジェクト側においてノートごとのON/OFF発生時間をあらかじめ保持している。 ところが、シーケンサからノートON/OFFと共に通知される演奏時間の値が、 曲が進行するにつれてシーンオブジェクト側で想定している値より(わずかではあるが)大きくなっている。
シーンオブジェクト側は「この時間でノートONになるはず」と予測して、キー押下アニメーションを開始するが、 シーケンサがノートON発生時に通知する時刻の値はこれより大きくなる(遅くなる)ため、ピアノロールとずれてしまう。
問題の発生例:
(A) シーンオブジェクトはトラックから取得したノートリストで、ノートON/OFFの発生時間を管理している。 この発生時間は、デルタタイム(チックタイム)を実時間(double型)に変換した値を積算して導き出している。
SMTrack::_GetNoteList() totalRealtime += _ConvTick2TimeMsec(deltaTime, tempo, timeDivision);
(B) 一方シーケンサも同様に、タイマー処理においてイベント発生ごとに、デルタタイム(チックタイム)を 実時間(double型)に変換した値を積算して、次回イベント発生時刻を導き出している。
SMSequencer::_OnTimer() m_NextEventTime += _ConvTick2TimeMsec(deltaTime);
(A)と(B)は同じ計算式でdouble型の値を積算しているにも関わらず、 (B)のほうが徐々に(A)よりも大きな値になっていくようである。
doubleの計算誤差による問題と思われるが、決定的な原因が分からない・・・。
SMIDILib.dllにおいて、コンパイルオプションの浮動小数点モデルの指定を、変更してみたが改善されず。
#ここ一ヶ月近く悩んでいるけど、一向に進展せず。プロジェクト始まって以来、最難関の不具合です。
浮動小数点の演算精度の設定が、描画処理を担当するメインスレッドは単精度、 MIDIデータ演奏処理を担当するスレッドは倍精度になっていた。 このため、それぞれのスレッドで全く同じ計算を処理したにもかかわらず、 計算結果に差異が発生した。 メインスレッドが単精度になった原因は、Direct3Dを利用しているため。
以上により、キー押下とピアノロールの描画が徐々にずれる現象が発生した。
Win32アプリケーションにおいて、丸め方/演算精度/例外といった 浮動小数点プロセッサの動作は、スレッドごとに制御される。
INFO: How Windows handles floating-point calculations http://support.microsoft.com/kb/102555/en-us/
演算精度はデフォルトで倍精度であるが、IDirect3D9::CreateDeviceを呼び出すと、 DirectXの仕様により、呼び出したスレッドの演算精度が単精度に切り替わる。 これを抑止するには、IDirect3D9::CreateDeviceの引数でD3DCREATE_FPU_PRESERVEを指定すればよいが、 性能の低下や予期しない動作を招く可能性がある。
一方MIDIデータ演奏処理は、メインスレッドからtimeSetEventによって起動される マルチメディアタイマー処理のスレッドで実行している。 このため、メインスレッドとは異なる演算精度が適用されていた。
なお、浮動小数点プロセッサの動作設定は、_controlfpで参照/変更することが可能である。
SMIDILibにおいて、doubleを使用する演算処理の前に、浮動小数点の演算精度を倍精度に設定する。 演算処理が完了した後は、変更前の状態に戻す。
浮動点小数プロセッサ制御クラスを新規追加する。
対策方針に従い、特定区間で演算精度を倍精度に保つ。
ピアノロールレインで演奏時間が長い曲を再生すると、曲が進むにつれて、キー押下タイミングとピアノロールのスクロールがずれる。
ピアノロールバーがキーに届く前に、キー押下状態になる。 再生時間10分で約0.1秒、再生時間20分で約0.2秒のズレが生じる。 5分程度の曲ではズレの発生には気づかない。