カクタスソフトウェア
カクタスソフトウェア
サウンド MIDI マルチメディア アプリケーション

高精度コールバック

このセクションでは、マルチメディアタイマーを用いた高精度コールバック処理について解説します。

要点は以下のとおりです。

タイマーの能力を調べる

インストールされているハードウェアによってタイマーの能力が異なる場合があるため、事前にタイマーの能力をチェックします。timeGetDevCaps()関数でタイマーが支援する最小と最大のインターバルを調べることができます。

以下の例では、設定したいインターバルがタイマーの能力の範囲内にあるかどうかを確認し、範囲外であれば最も近いものを求めます。

関数が成功すれば、bTimerがTRUEになり、uTimerResに目的のインターバルが得られます。

UINT uPeriod = 設定したいインターバル;
BOOL bTimer = FALSE;
UINT uTimerRes = 0;

TIMECAPS tc;
if (! timeGetDevCaps(&tc, sizeof(TIMECAPS)))
{
    uTimerRes = min(max(tc.wPeriodMin, uPeriod), tc.wPeriodMax);
    bTimer = TRUE;
}

タイマーを起動する

タイマーの起動はtimeSetEvent()関数を用います。

MMRESULT timeSetEvent(
    UINT            uDelay,
    UINT            uResolution,
    LPTIMECALLBACK  lpTimeProc,
    DWORD           dwUser,
    UINT            fuEvent)

uDelay

タイマーを起動した後、最初にコールバック関数が呼ばれるまでの時間です。タイマーの能力の範囲内でなければなりません。

uResolution

この時間間隔でコールバック関数が呼ばれます。この数値をゼロにすると、タイマーの能力のtc.wPeriodMinを指定したのと同じになります。

lpTimeProc

コールバック関数のアドレスです。

dwUser

ユーザー変数です。このパラメータはコールバック関数に伝えられるので、作業用の構造体やクラスのポインターを指定すると良いでしょう。

fuEvent

タイマーの動作モードです。ここではTIME_PERIODICを指定し、定期的にコールバック関数が呼ばれるようにします。

以下に実際の例を示します。

MyClass* pMyClass = 作業用クラス;
UINT uTimerRes = 設定したいインターバル;

timeBeginPeriod(uTimerRes);

uTimerId = timeSetEvent(
    uTimerRes,
    uTimerRes,
    (LPTIMECALLBACK)TimerCallback,
    (DWORD)(LPSTR)pMyClass,
    TIME_PERIODIC);

タイマーを停止する

タイマーの利用が終わったらタイマーを停止します。timeKillEvent()関数の引数はtimeSetEvent()関数で得られたIDを指定します。

if (uTimerId)
{
    timeKillEvent(uTimerId);
    uTimerId = 0;
}
timeEndPeriod(uTimerRes);

timeBeginPeriodとtimeEndPeriod

MIDIの再生用にマルチメディアタイマーを用いる場合、コールバック周期を非常に小さな値、例えば1mSec〜数mSecにしなければなりません。

Windowsはマシンへの負担を軽減するため、タイマーの監視インターバルをデフォルトでかなり荒く設定しています。この状態のままマルチメディアタイマーを利用しても十分な精度が得られません。

高精度でマルチメディアタイマーを用いる場合、タイマーを起動する前にtimeBeginPeriod()関数で一時的にデバイスドライバーのタイマー精度を変更します。この変更は一時的なものなのでタイマーの利用が終わったら、timeEndPeriod()関数で元の設定に復帰させます。

timeBeginPeriod()とtimeEndPeriod()関数の引数は同じものでなければなりません。また2つの関数は必ずペアで実行して下さい。

Windows9xではtimeBeginPeriod()関数を実行しなくても1mSecの精度が得られましたが、WindowsNT以降のバージョンではtimeBeginPeriod()関数を実行しなければ高精度は得られません。

コールバック関数での処理

コールバックで呼び出される関数のテンプレートを示します。

void TimerCallback(
    UINT    uTimerID,
    UINT    uMsg,
    DWORD   dwUser,
    DWORD   dw1,
    DWORD   dw2)

uTimerID

タイマーの識別子です。タイマーが複数起動されているとき、タイマーの識別に用います。

uMsg

予約。利用されません。

dwUser

timeSetEvent()関数の4番目で指定した引数です。作業用の構造体やクラスのポインターを指定すると良いでしょう。

dw1、dw2

予約。利用されません。

コールバック関数をクラスのメソッドとして実装することができます。この場合、関数はスタティックにしなければなりません。またグローバル空間に定義する方法もあります。

コールバック関数について

このコールバック関数は、Windows9xでは割り込み手法で実装されています。割り込みの期間、マシンは非常に不安定な状態にあるため、関数内から安全に呼び出せるAPIは以下のものに限られています。表から分かる通り、strcpyすら実行することはできません。

WindowsNT以降のバージョンでは、コールバック関数はタイムクリティカルなスレッドとして実装されています。タイムクリティカルとは言えスレッド内からの実行であるため、割り込みよりは制限が緩和されているのではと期待するのですが、MSDNには実行可能なAPIや安全性についての記述がありません。

マルチメディアタイマーは元々MIDIの送信用APIとして設計されているため、MIDIメッセージを送信する用途以外には使わない方が良いかも知れません。

コールバック関数は一定のインターバルで呼び出されるようプログラムされていますが、ハードディスクのアクセスやシリアライズ処理、メモリのスワップなど、より優先度が高いプロセスによってブロックされる場合があります。予定した時刻に呼び出しができないとき、以下の症状が発生します。

Windows9xではコールバック関数そのものが呼び出されません。そのため関数内でカウンターを進めるなどの処理は正常に動作しません。WindowsNT以降のバージョンは、スケジューラーがコールバック関数の呼び出しを管理するため、トータルの呼び出し回数は保障されますが、呼び出されるタイミングには偏りが生じます。

コールバック関数がいつ呼び出されるか事前に判断することはできないので、関数に入ったらすぐtimeGetTime()などで現在の時刻を取得することをお勧めします。特にMIDIの演奏では、前回の処理からの経過時間が重要です。

timeSetEventの制限事項と不具合について

timeSetEventには動作の一部に不具合や制限があることが知られています。タイマー内部のカウンターが32ビットであることや、計算方法の問題のため以下の問題が起こります。

TIME_PERIODICを指定した場合イベント遅延(uResolution)は429,496ミリ秒が最大です。

タイマーでサポートされるイベント遅延(uDelay)の最大値は1000秒という制限があります。

timeSetEvent の制限事項と不具合について

ドキュメントの先頭へ

カクタスソフトウェア 技術協力 資料室 資料室の広場 SourceForge.jp お問い合わせ