音量の左右個別設定

概要:スピーカーの左右音量を調節する事で特殊効果を演出する実験

今回はちょっとした実験をしてみます。
スピーカーの左右音量を調節する事で左から右に音源が移動するかのような特殊効果を演出できないかな?
という発想に起因する実験です。
映画などで耳にした事がありますよね?

■プログラムの概要

作成するWAVEデータは8ビットステレオです。
再生は MM_WOM_DONE メッセージ処理で無限ループさせます。
音量を変更するので、起動前の音量を保存しておき、プログラム終了時に起動前の音量に戻します。

音量の最小値は 0 、最大値は 0xffff です。
今回は左右の合計が 0xffff で一定になるように上下させます。
左が大きくなれば右が小さくなり、左が小さくなれば右が大きくなります。
左チャンネルを基準として 0 → 0xffff → 0 → …… のように推移します。

音量を変更する間隔はタイマーによって管理します。

// winmm.lib をリンクする
#pragma comment(lib,"winmm")

#define SRATE    8000    //標本化周波数(1秒間のサンプル数)
#define F        400     //周波数(1秒間の波形数)

LRESULT CALLBACK WindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    WAVEFORMATEX wfe;
    static HWAVEOUT hWaveOut;
    static WAVEHDR whdr;
    static LPBYTE lpWave;
    int i,len;

    static DWORD befvol;
    static int add=512;
    DWORD vol;

    HDC hdc;
    PAINTSTRUCT ps;
    char str[32];

    switch(uMsg){
        case WM_CREATE:
            wfe.wFormatTag=WAVE_FORMAT_PCM;
            wfe.nChannels=2;    //ステレオ
            wfe.wBitsPerSample=8;    //量子化ビット数
            wfe.nBlockAlign=wfe.nChannels * wfe.wBitsPerSample/8;
            wfe.nSamplesPerSec=SRATE;    //標本化周波数
            wfe.nAvgBytesPerSec=wfe.nSamplesPerSec * wfe.nBlockAlign;

            waveOutOpen(&hWaveOut,WAVE_MAPPER,&wfe,(DWORD)hWnd,0,CALLBACK_WINDOW);

            lpWave=(LPBYTE)calloc(wfe.nAvgBytesPerSec,2);    //2秒分

            len=SRATE/F;    //波長
            for(i=0;i<SRATE*2;i++){    //波形データ作成
                if(i%len < len/2){
                    lpWave[2*i  ]=128+64;    //左
                    lpWave[2*i+1]=128+64;    //右
                }else{
                    lpWave[2*i  ]=128-64;    //左
                    lpWave[2*i+1]=128-64;    //右
                }
            }

            whdr.lpData=(LPSTR)lpWave;
            whdr.dwBufferLength=wfe.nAvgBytesPerSec * 2;
            whdr.dwFlags=WHDR_BEGINLOOP | WHDR_ENDLOOP;
            whdr.dwLoops=10;

            waveOutPrepareHeader(hWaveOut,&whdr,sizeof(WAVEHDR));
            waveOutWrite(hWaveOut,&whdr,sizeof(WAVEHDR));

            waveOutGetVolume(hWaveOut,&befvol);    //起動前の音量を保存
            SetTimer(hWnd,1,50,NULL);
            return 0;
        case MM_WOM_DONE:
            waveOutWrite((HWAVEOUT)wParam,(LPWAVEHDR)lParam,sizeof(WAVEHDR));
            return 0;
        case WM_TIMER:
            waveOutGetVolume(hWaveOut,&vol);
            vol=LOWORD(vol);    //左
            if((int)vol+add <= 0){
                vol=0;
                add*=-1;
            }else if((int)vol+add >= 0xffff){
                vol=0xffff;
                add*=-1;
            }else{
                vol+=add;
            }
            waveOutSetVolume(hWaveOut,MAKELONG(vol,0xffff-vol));
            InvalidateRect(hWnd,NULL,TRUE);
            return 0;
        case WM_PAINT:
            hdc=BeginPaint(hWnd,&ps);
            waveOutGetVolume(hWaveOut,&vol);
            wsprintf(str,"左 = %5d , 右 = %5d",LOWORD(vol),HIWORD(vol));
            TextOut(hdc,0,0,str,lstrlen(str));
            EndPaint(hWnd,&ps);
            return 0;
        case WM_DESTROY:
            KillTimer(hWnd,1);
            waveOutSetVolume(hWaveOut,befvol);    //起動前の音量に戻す

            waveOutReset(hWaveOut);
            waveOutUnprepareHeader(hWaveOut,&whdr,sizeof(WAVEHDR));
            waveOutClose(hWaveOut);
            free(lpWave);
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

このプログラムで注意すべきは if((int)vol+add <= 0) です。
何故 int 型にキャストしているのか?
それは vol が DWORD 型であり、unsigned 型と signed 型の計算では
unsigned 型に暗黙の型変換が行われ、負値を表現できなくなってしまうからです。
上記の条件式で意味があるのは add が負値の時なので、
計算結果に負値は必要不可欠なのです。

それに対して、if((int)vol+add >= 0xffff) は add が正値の時に意味のある条件式ですが、
vol+add では32ビットunsigned型になってしまいます。
この計算結果がもし負値になるとしたら32ビットunsigned型の負値(正確には正値だが)は
0xffff を軽く越えてしまうので、やっぱりsigned型へのキャストが必要です。

whdr.dwLoops=10; にも注目して下さい。
10回ループ再生する事を指示しています。
作成したWAVEデータは2秒なので、20秒は間断なく再生されます。
しかし、MM_WOM_DONE メッセージ処理でもう一度10回ループ再生を指示する僅かな処理時間に
無音が発生してしまうのが確認できるでしょう。
この無音をなくす手法を次節では解説します。

作成したWAVEデータの音が気に入らなければ、waveOutWrite関数を呼び出さず、
別のアプリで適当な音楽ファイルを再生させればいいでしょう。

■実験結果の考察

音源が移動するかのような錯覚を期待していましたが、正直イマイチですね(笑)。
やっぱり単純に音量を調節するだけじゃ表現に限界があるみたいです。
根本的な解決には波形データの編集が必要でしょうが、再生中に波形データを編集するのは非常に困難です。
ま、素直にDirectXを使えば簡単に実現してくれるわけですが。

★☆ ソースファイル表示 ☆★


戻る / ホーム