34:BGM再生の改良


ループ再生
 DirectShowにはループ再生を指示するための命令が存在しません。そこで、Exec()がタスクリストから呼び出されるたびに、IMediaSeekingから現在の再生位置を取得し、その再生位置がメディアの終了位置に達したのであれば、再生位置を再び先頭に戻させることでループ再生を実装することにします。対象のメディアファイルの再生位置・終了位置を取得するにはIMediaSeeking::GetPositions()、位置の変更にはIMediaSeeking::SetPositions()を使います。
void CBGMusic::Play(BOOL loop)
{
    if(pMediaCtrl){
        bLoop = loop;    // ループ再生を行うかのフラグ
        if(pMediaSeeking){
            // 再生位置を先頭に移動する
            LONGLONG s = 0;
            pMediaSeeking->SetPositions(
                // 再生位置は絶対位置で(s * 100)ナノ秒
                &s, AM_SEEKING_AbsolutePositioning,
                // 終了位置は変更しないのでNoPositioningを指定
                NULL, AM_SEEKING_NoPositioning);
        }
        pMediaCtrl->Run();
    }
}

void CBGMusic::Exec()
{
    if(bLoop == TRUE && pMediaSeeking){
        LONGLONG s, e;
        pMediaSeeking->GetPositions(&s, &e);    // sは開始位置、eは終了位置
        if(s >= e){
            s = 0;
            pMediaSeeking->SetPositions(
                &s, AM_SEEKING_AbsolutePositioning,
                NULL, AM_SEEKING_NoPositioning);
        }
    }
}

フェードイン・アウト
 シューティングゲームでボスが登場する場面になったのであれば、ステージ音楽からボス音楽へと切り替えたいところですが、いきなり音楽が切り替わるよりも、フェードアウト処理で切り替えた方が演出としてかっこいいと思いませんか? そこで、DirectShowによるフェードイン・フェードアウト処理を独自に作成してみることにし、「tミリ秒経過するまでに音量をVsからVeまで徐々に変更する」計算を行うことで実現させます。

 ここで中学数学のおさらいです。「xがaからbに移行するまでに、yはcからdまで移行する」という2点間を通る計算処理は1次関数によって数式化できます。


1次関数の式

 2点間の傾きaは以下の公式で求めることができるので、


傾きを求める公式

 開始時間(=0ms)・終了時間(te)・開始音量(vs)・終了音量(ve)をこの公式に当てはめると結果は



と、なります。この結果を1次関数に当てはめると



となることがおわかりいただけるでしょう。xが0msのとき、yはvsになればよいので、それぞれを上の式に代入すると、



bを求めることができます。これらより、「xがaからbに移行するまでに、yはcからdまで移行する」計算式は



であることがわかりました。あとはフェード処理が有効であるなら、Exec()が呼び出されるたびに経過時間に応じたボリュームを、上記の式で算出するようにするだけです。
/* 省略 */

class CBGMusic : public CGameObject
{
private:
    /* 省略 */
    
    BOOL bFading;
    float time_s, time_e, vol_s, delta;

    /* 省略 */

public:
    /* 省略 */

    void SetVolume(LONG volume);
    void Fade(DWORD endtime, LONG volume);

protected:
    void Exec();
};
#include "BGMusic.h"

void CBGMusic::InitializeBGM()
{
    /* 省略 */

    bFading = FALSE;
}

/* 省略 */

void CBGMusic::SetVolume(LONG volume)
{
    if(pBasicAudio) pBasicAudio->put_Volume(volume);
}

void CBGMusic::Exec()
{
    /* 省略 */

    if(bFading == TRUE){
        // 差分で経過時間を取得
        float t = (float)timeGetTime() - time_s;
        pBasicAudio->put_Volume((long)(delta * t + vol_s));

        // 経過時間を超えたらフェード処理終了
        if(t >= time_e) bFading = FALSE;
    }
}

void CBGMusic::Fade(DWORD endtime, LONG volume)
{
    if(!pBasicAudio){
        DXTRACE_MSG(_T("ボリュームの設定ができません。"));
        return;
    }

    bFading = TRUE;

    LONG v;
    pBasicAudio->get_Volume(&v);

    vol_s = (float)v;
    float vol_e = (float)volume;

    time_s = (float)timeGetTime();
    time_e = (float)endtime;

    delta = (vol_e - vol_s) / time_e;
}
 このプログラム例の場合、Fade関数を実行すると、endtimeミリ秒後にvolumeになるように徐々にボリュームが調整されます。ここで注意しておきたいのが、ボリュームはメディアファイル自体の音量から、指定したボリューム値を差し引いた音量になるため、volumeに最低音量である-10000を指定すると、なかなかフェードインされず、音楽が停止状態になっているような印象を与えかねないということです。