Programming Field - プログラミング Tips

DirectX: DirectX Music で小節番号を演奏位置に変換する方法

ここでは、演奏する曲の開始位置を小節番号と拍で指定してしまうコードを紹介します。

DirectX Music には演奏開始位置などに MUSIC_TIME という単位を使用してます。この定義は四分音符一つ分を「DMUS_PPQ (現在 768 に定義されている)」の長さにしているため、テンポ(Tempo)に依存しない時間単位となります。曲データが読み込まれると、曲の先頭の位置を 0 として、音符、調号、拍子記号などの位置は MUSIC_TIME 上に合うように計算されます。したがって、小節位置や拍子数も MUSIC_TIME で表すことが可能となります。

例: 拍子が 4/4 の曲で 2 小節目の 3 拍目(右図の赤い色の位置)は、MUSIC_TIME で表すと ((2 - 1) * 4 + 2) * DMUS_PPQ = 4608 となります。
1 2
1 2 3 4 1 2 3 4

複雑な曲になると拍子記号が途中で変わったりします。そこで曲データから拍子記号を取得し、IDirectMusicPerformance(または IDirectMusicPerformance8)のメソッド TimeToRhythmRhythmToTime を使って位置を計算します。

実際のコードは以下の通りです。

[C++]

// wMeasure: 小節番号(最初を0とする)
// bBeat: 拍子数(各小節の頭を0とする)
MUSIC_TIME CalcMusicTime(IDirectMusicPerformance* pPerf,
    IDirectMusicSegment* pSegment,
    WORD wMeasure,
    BYTE bBeat)
{
    DMUS_TIMESIGNATURE ts;
    // 変数 mtTime は曲の頭から数えて現在計算中の位置が
    // どの辺に当たるかを保持する
    MUSIC_TIME mtTime, mtNext;
    HRESULT hr;
    WORD wM;
    BYTE bB, bG;
    short nO;

    mtTime = 0;

    // 最初の拍子記号を取得する
    hr = pSegment->GetParam(GUID_TimeSignature, 0xFFFFFFFF, 0,
        mtTime, &mtNext, (void*) &ts);
    if (SUCCEEDED(hr))
    {
        do
        {
            // GUID_TimeSignature は ts.mtTime を 0 以下に設定する
            // (これは GetParam の mtTime パラメータの位置における拍子記号を取得し、
            // その拍子記号がどの位置から始まっているかを ts.mtTime に
            // オフセットとして設定するため)
            // そこで ts.mtTime を「曲全体における拍子記号の開始位置」とするため
            // mtTime を値に加える
            //
            // ※ 今回は ts.mtTime はほぼ 0 になる
            ts.mtTime += mtTime;

            // ts には○分の○拍子の情報が既に入っているため、
            // ts.mtTime にある「拍子記号の開始位置」を基点として、
            // 「次の拍子記号の位置」が何小節目の何拍目にあるかを取得する
            // (bG, nO は使用しない)
            //
            // ※ mtNext が 0 のときは wM などには 0 が返る
            hr = m_pDXMusicPerformance->TimeToRhythm(mtTime + mtNext,
                &ts, &wM, &bB, &bG, &nO);
            if (FAILED(hr))
                break;

            // wMeasure の値が次の拍子変更位置よりも手前の場合
            // → mtTime ~ mtTime + mtNext の間に欲しい値がある
            if (wMeasure < wM)
            {
OnSetMeasure:
                // ts.mtTime の位置から wMeasure, bBeat 経過したときの
                // 位置を、位置 0 を基準にして計算する
                // (相対位置は mtNext - ts.mtTime になる)
                hr = m_pDXMusicPerformance->RhythmToTime(wMeasure, bBeat,
                    0, 0, &ts, &mtNext);
                if (FAILED(hr))
                    break;

                return mtNext;
            }

            // 位置が見つかるまでの残り小節数を設定する
            wMeasure -= wM;

            // mtNext が 0 のときは「拍子記号がもう存在しない」
            // → ts を使って残り小節数が経過したときの位置を計算する
            if (!mtNext)
                goto OnSetMeasure;

            // 計算する位置を次の拍子記号の位置に更新する
            mtTime += mtNext;

            // 次の拍子記号を取得する
            hr = pSegment->GetParam(GUID_TimeSignature, 0xFFFFFFFF, 0,
                mtTime, &mtNext, (void*) &ts);
        } while (SUCCEEDED(hr));
    }

    // どこかで処理に失敗
    return 0;
}

ここで注意したいメソッドは今回のコードの主役でもある TimeToRhythm、そして RhythmToTime です。これらのメソッドでは、DMUS_TIMESIGNATURE 構造体内の mtTime の値を使用して計算するのですが、TimeToRhythm で指定する MUSIC_TIME の値と、RhythmToTime で返される MUSIC_TIME の値はいずれも DMUS_TIMESIGNATURE 構造体の mtTime の値をベースとはせず、その mtTime の値における基準点をそのまま流用しています。つまり、TimeToRhythm で拍子記号の位置を 200、引数として渡す位置を 600 とすると、この位置は拍子記号を基準に取ると 400 という位置になります。

また、ここで扱われる小節番号は拍子記号のある位置を 0 としています。すなわち、曲の先頭から小節番号を(0 をスタートとして)数えていくとき、拍子記号が出るたびに 0 にリセットされる、ということです。

最終更新日: 2007/01/10