ステレオWAVEデータ作成

概要:8/16ビットステレオWAVEデータの作り方

前節までに作成したWAVEデータは全てモノラルでした。
モノラルができたら当然ステレオもできるようになりたいですよね?
というわけで、今回はステレオWAVEデータの解説です。

■属性の設定

1秒間のサンプル数(標本化周波数)や1秒間の波形数(周波数)は
左チャンネル、右チャンネルそれぞれの値であり、
モノラルでもステレオでも同じです。

ただし、メモリ領域に格納されている波形データがモノラルなのか、
ステレオなのかを教えてあげる必要があります。
WAVEFORMATEX構造体のnChannelsメンバに 2 を指定すれば
ステレオであると解釈されます。

確保するメモリ領域のバイト数を求める式は wfe.nAvgBytesPerSec * 2 で変更ありませんが、
wfe.nAvgBytesPerSec は wfe.nChannels に影響されている事に注意して下さい。

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

WAVEFORMATEX wfe;
static HWAVEOUT hWaveOut;

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,0,0,CALLBACK_NULL);

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

■波形データの格納規則

8ビットの振幅値は 0 〜 255 で表現します。
16ビットの振幅値は -32768 〜 32767 で表現します。
これはモノラルでもステレオでも同じです。

問題はデータを格納する規則ですね。
ステレオでは「左右左右……」という順番でデータを格納していきます。
左右両チャンネルを同時に書き込む場合は「下位に左、上位に右」という事になります。

それでは8ビットステレオの場合を考えてみましょう。
左右チャンネルは別々に格納していく事にします。
ここでややこしいのはアドレス計算です。
左右それぞれのサンプル数は SRATE*2 はですが、
メモリ領域は (SRATE*2)*2 だけ確保してあります。
SRATE*2 はモノラルの時から使ってきた計算式なので変更したくないですし、
変更したら他の箇所にも影響が及んでしまいます。
従って、左右両チャンネルを一回のループで書き込まなければなりません。
ところで、「左右左右……」という順番をよく見ると、
偶数番目に左、奇数番目に右になっている事に気が付きます。
これを利用したのが次のプログラムです。

static LPBYTE lpWave;
int i,len;

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+32;  //右
    }else{
        lpWave[2*i  ]=128-64;  //左
        lpWave[2*i+1]=128-32;  //右
    }
}

右チャンネルの音を左チャンネルより小さくしているので、スピーカーで聞き比べてみて下さい。

また、左右両チャンネルを一つの変数で表現する事にすれば、
下位に左チャンネル、上位に右チャンネルの振幅値を設定する事になります。
こっちのがわかりやすいですが、16ビットステレオの場合はキャストが必要です。

static LPWORD lpWave2;

lpWave2=(LPWORD)calloc(wfe.nAvgBytesPerSec,2);    //2秒分
len=SRATE/F;    //波長
for(i=0;i<SRATE*2;i++){  //波形データ作成
    if(i%len < len/2)    lpWave2[i]=(128+32)<<8 | (128+64);
    else                 lpWave2[i]=(128-32)<<8 | (128-64);
}

■WAVEHDR構造体の設定

モノラルとステレオの違いは以上、
WAVEFORMATEX構造体のnChannelsメンバ と 振幅値の格納規則 の二つだけで他は同じです。
後はWAVEHDR構造体を設定すれば再生する事ができます。

static WAVEHDR whdr;

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

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

■16ビットステレオ

16ビットステレオも同様ですが、一つだけ問題があります。
負値の取り扱いです。
16ビットの振幅値は -32768 〜 32767 で表現しますが、
32ビットの -32768 = 0x80000000 と、
16ビットの -32768 = 0x8000 はビット列で表現した場合は違うのです。

数値は代入する変数の型によって暗黙の型変換が行われます。
従って、32ビット型変数の下位16ビットに左チャンネル、上位16ビットに右チャンネルを格納する時は
各チャンネルの数値は16ビット型にキャストしなければなりません。
ただし、型は符号無しでも符号付きでも同じです。
何故なら解釈の仕方が違うだけでビット列は同じだからです。

#include<windows.h>

// 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 short *lpWave;
    //static LPDWORD lpWave2;
    int i,len;

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

            waveOutOpen(&hWaveOut,WAVE_MAPPER,&wfe,0,0,CALLBACK_NULL);

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

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

            /*lpWave2=(LPDWORD)calloc(wfe.nAvgBytesPerSec,2);    //2秒分
            len=SRATE/F;    //波長
            for(i=0;i<SRATE*2;i++){  //波形データ作成
                if(i%len < len/2)    lpWave2[i]=(8192 << 16) | (16384);
                else                 lpWave2[i]=((WORD)-8192)<<16 | ((WORD)-16384);
            }*/

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

            waveOutPrepareHeader(hWaveOut,&whdr,sizeof(WAVEHDR));
            waveOutWrite(hWaveOut,&whdr,sizeof(WAVEHDR));
            return 0;
        case WM_DESTROY:
            waveOutReset(hWaveOut);
            waveOutUnprepareHeader(hWaveOut,&whdr,sizeof(WAVEHDR));
            waveOutClose(hWaveOut);
            free(lpWave);
            //free(lpWave2);
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

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


戻る / ホーム