ほとんどのPCにはWave(PCM)音源が搭載され、サウンド機能を利用することが出来ます。今回は、自分でサウンドの波形データを作成し、それをwaveOutWrite()などを使ってWave音源で再生してみることにしましょう。
Wave音源の処理
Wave音源で音を鳴らす手順は、以下のようになります。
- バッファを確保し、波形データを作成
- 音声形式を指定するWAVEFORMATEX構造体を作成
- waveOutOpen()でWaveデバイスをオープン
- データバッファの情報をWAVEHDR構造体で設定
- waveOutPrepareHeader()で再生準備
- waveOutWrite()でバッファの波形データを再生
- 再生が終了したらwaveOutClose()でデバイスを閉じる
- waveOutUnprepareHeader()した後バッファを解放
波形データは、数値の配列です。データ形式には8ビットと16ビットがあり、8ビットは0−255、16ビットは符号付のー32768〜32767までの値です。いずれも中央の値(128、0)が無音状態を表し、その上下は絶対値が波形の大きさになります。データの時間方向の解像度はサンプリング周波数の逆数ですから、例えばサンプリングレートが8KHzなら最初の値が0秒、2番目の値が1/8000秒後、3番目の値が2/8000秒後における波形の状態を表しています。今回は、サンプリング周波数を8KHzとし、以下のようにしてバッファlpBufに周期10(8KHzで再生した場合は、周期1/8000×10秒、周波数800Hz)/振幅64の矩形波を作ってみました。
lpBuf=(LPBYTE)GlobalAlloc(GPTR,8000); for (i=0;i<8000;i++) { if ((i % 10)<5) lpBuf[i]=192; else lpBuf[i]=64; }
波形データが出来たら、8ビットモノラル、サンプリング周波数8KHz用のWAVEFORMATEX構造体wfを設定します。
wf.wFormatTag=WAVE_FORMAT_PCM; wf.nChannels=1; wf.nSamplesPerSec=8000; wf.nAvgBytesPerSec=8000; wf.nBlockAlign=1 ; wf.wBitsPerSample=8 ; wf.cbSize=0 ;
続いてWaveデバイスをオープンし、デバイスの操作に必要なHWAVEOUT型ハンドルhWOutを取得します。
waveOutOpen(&hWOut,WAVE_MAPPER,&wf,(DWORD)hwnd,0,CALLBACK_WINDOW);
次はデータバッファの情報を格納するWAVEHDR型ヘッダ、whの設定
wh.lpData=lpBuf; wh.dwBufferLength=8000; wh.dwBytesRecorded=0; wh.dwUser=0; wh.dwFlags=0; wh.dwLoops=1; wh.lpNext=NULL ; wh.reserved=0 ;
ヘッダが出来たら、そのヘッダをwaveOutPrepareHeader()に渡してデータバッファ再生の準備をします。
waveOutPrepareHeader(hWOut,&wh,sizeof(WAVEHDR));
これで再生の準備が出来たので、後は再生するだけ。
waveOutWrite(hWOut,&wh,sizeof(WAVEHDR));
再生が終わったら、デバイスをクローズしてバッファを解放します。この手順は、まず再生が終わった時点でウインドウに送られてくるMM_WOM_DONEメッセージを捉えてデバイスをクローズし、デバイスがクローズされたら送られてくるMM_WOM_CLOSEメッセージでwaveOutUnprepareHeader()の後バッファを解放、という感じです。
case MM_WOM_DONE: waveOutClose(hWOut); return 0 ; case MM_WOM_CLOSE: waveOutUnprepareHeader(hWOut,&wh,sizeof(WAVEHDR)); GlobalFree(lpBuf); return 0;
プログラム
実行すると800Hzの矩形波を1秒間再生します。中断処理は入れていないので、途中でプログラムを終了しないでください。ビルドする時には、winmm.libをリンクに追加する必要があります。