前節ではWAVEデータを作成して、再生するプログラムを解説しました。
本節では一時停止や音量調節のプログラム、
コールバックメッセージ、エラー文字列について解説します。
本節を理解すれば、一通りの事はできるようになります。
■よく使うwaveOut○○関数
waveOutOpen関数、waveOutPrepareHeader関数の後に呼び出す主な関数として次のものがあります。
waveOutWrite …… 再生(一時停止の解除はできない)
waveOutReset …… 停止(現在位置を 0 にリセット)
waveOutPause …… 一時停止
waveOutRestart …… 一時停止解除
waveOutGetVolume …… 音量取得
waveOutSetVolume …… 音量設定
waveOutGetPosition …… 現在の再生位置を取得
殆どの関数はプログラムを見ただけで使い方がわかると思いますが、
waveOutGetVolume関数とwaveOutSetVolume関数については少し解説します。
MMRESULT waveOutGetVolume(
HWAVEOUT hwo,
LPDWORD pdwVolume
);
pdwVolume に現在の音量が取得されます。
DWORD型の下位2バイト(WORD型)が左チャンネル、
上位2バイト(WORD型)が右チャンネルです。
音量の最小値は 0 、最大値は 0xFFFF です。
ただし、左右個別の設定に対応していないデバイスの場合は
下位2バイト(WORD型)だけに音量が取得されます。
MMRESULT waveOutSetVolume(
HWAVEOUT hwo,
DWORD dwVolume
);
dwVolume に新しい音量を指定します。
ただし、左右個別の設定に対応していないデバイスの場合は下位バイトだけで設定します。
waveOutGetPosition関数については後の節で解説します。
また、現在の再生位置を設定する関数はありません。
case WM_KEYDOWN: switch(wParam){ case 'A': //再生 waveOutWrite(hWaveOut,&whdr,sizeof(WAVEHDR)); return 0; case 'S': //停止 waveOutReset(hWaveOut); return 0; case 'D': //一時停止 waveOutPause(hWaveOut); return 0; case 'F': //一時停止解除 waveOutRestart(hWaveOut); return 0; case 'J': //音量 上げる waveOutGetVolume(hWaveOut,&vol); lv=LOWORD(vol); rv=HIWORD(vol); lv=(lv+512 > 0xffff)? 0xffff:lv+512; //左 rv=(rv+512 > 0xffff)? 0xffff:rv+512; //右 waveOutSetVolume(hWaveOut,MAKELONG(lv,rv)); return 0; case 'K': //音量 下げる waveOutGetVolume(hWaveOut,&vol); lv=LOWORD(vol); rv=HIWORD(vol); lv=(lv-512 < 0)? 0:lv-512; //左 rv=(rv-512 < 0)? 0:rv-512; //右 waveOutSetVolume(hWaveOut,MAKELONG(lv,rv)); return 0; } return 0;
サンプルでは128段階の音量調節を行えるように、512(=0xFFFF/128)ずつ値を上下させています。
他の部分は8ビット矩形波プログラムと全く同じです。
音量はシステム全体のWAVE再生に影響を与えます。
Windows標準アプリである「ボリュームコントロール」の"WAVE"項目が変更されるのを確認して下さい。
プログラム終了後も変更が維持されるので、
良心的なプログラムであれば起動直後の状態に戻す処理を実装すべきです。
左右個別の設定については、ステレオWAVEが作れるようになってから試します。
★☆ ソースファイル表示 ☆★
■コールバック機構の設定
基本的な操作は身に付いたと思いますが、
実践的なプログラムにはコールバックメッセージによる制御も必要でしょう。
コールバック機構を利用するにはwaveOutOpen関数の dwCallback にコールバック対象のアドレスを指定します。
そして fdwOpen にコールバックの種類を示すフラグを指定します。
メッセージは 3個 しかないので、コールバック関数じゃなくウィンドウメッセージとして処理すれば十分でしょう。
waveOutOpen(&hWaveOut,WAVE_MAPPER,&wfe,(DWORD)hWnd,0,CALLBACK_WINDOW);
■コールバックメッセージ
ウィンドウに送信されるコールバックメッセージは次の 3個 だけです。
MM_WOM_OPEN …… waveOutOpen関数を呼び出した時に発行される(waveOutReset関数を呼び出した時も)
MM_WOM_DONE …… 再生が終了した時に発行される
MM_WOM_CLOSE …… waveOutClose関数を呼び出した時に発行される
MM_WOM_OPEN
wParam = (WPARAM) hOutputDev
lParam = reserved
WPARAM にはデバイス識別用ハンドルが格納されています。
MM_WOM_DONE
wParam = (WPARAM) hOutputDev
lParam = (LONG) lpwvhdr
WPARAM にはデバイス識別用ハンドルが格納されています。
LPARAM にはWAVEHDR構造体を指すポインタが格納されています。
MM_WOM_CLOSE
wParam = (WPARAM) hOutputDev
lParam = reserved
WPARAM にはデバイス識別用ハンドルが格納されています。
■無限ループ再生
再生が終了した時に MM_WON_DONE メッセージが発行されるので、
このメッセージ処理で再び再生を開始すれば、無限ループ再生する事ができます。
それでは、コールバックメッセージが発生するタイミングに注意してプログラムを見てみましょう。
#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; switch(uMsg){ case WM_CREATE: wfe.wFormatTag=WAVE_FORMAT_PCM; wfe.nChannels=1; //モノラル 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[i]=128+64; else lpWave[i]=128-64; } whdr.lpData=(LPSTR)lpWave; whdr.dwBufferLength=wfe.nAvgBytesPerSec * 2; whdr.dwFlags=WHDR_BEGINLOOP | WHDR_ENDLOOP; whdr.dwLoops=1; waveOutPrepareHeader(hWaveOut,&whdr,sizeof(WAVEHDR)); return 0; case MM_WOM_OPEN: case MM_WOM_DONE: waveOutWrite((HWAVEOUT)wParam,&whdr,sizeof(WAVEHDR)); return 0; case WM_LBUTTONDOWN: waveOutReset(hWaveOut); return 0; case WM_CLOSE: waveOutReset(hWaveOut); waveOutUnprepareHeader(hWaveOut,&whdr,sizeof(WAVEHDR)); waveOutClose(hWaveOut); return 0; case MM_WOM_CLOSE: free(lpWave); DestroyWindow(hWnd); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd,uMsg,wParam,lParam); }
MM_WOM_OPEN メッセージを受信した時の処理は再生する事です。
MM_WOM_OPEN メッセージが発生するのは waveOutOpen 関数を呼び出した時です。
この時点ではまだ再生する事はできませんが、
MM_WOM_OPEN メッセージを処理するのは現在のメッセージ( WM_CREATE )を処理し終えた後なので、
その時には再生できる状態になっています。
MM_WOM_CLOSE メッセージは waveOutClose 関数を呼び出した時に発生します。
サンプルではプログラム終了時にクローズしたいのですが、
ウィンドウが破棄された後にウィンドウでメッセージを受信できるわけないので、
破棄される前( WM_CLOSE )に waveOutClose 関数を呼び出して、
MM_WON_CLOSE メッセージ処理で、ウィンドウを破棄しています。
MM_WON_DONE メッセージは再生が終了した時に発生します。
waveOutReset関数によって停止させた場合も発生します。
MCIコマンドのように任意のコマンドに対してコールバックメッセージを発生させる事はできません。
★☆ ソースファイル表示 ☆★
■エラー文字列
waveOut○○関数の返却値は
成功した時は MMSYSERR_NOERROR 、失敗した時はエラーコードです。
成功した時の値も含めて、これら返却値の意味を表す文字列を取得する関数があります。
waveOutGetErrorText関数です。
MMRESULT waveOutGetErrorText(
MMRESULT mmrError,
LPSTR pszText,
UINT cchText
);
mmrError にはwaveOut○○関数の返却値を指定します。
pszText にはバッファを指すポインタを指定します。
cchText にはバッファのサイズを文字単位で指定します。
生成されるエラー文字列は MAXERRORLENGTH 文字未満です。
それでは、プログラムを見てみましょう。
MMRESULT mmRes; TCHAR str[MAXERRORLENGTH]; case WM_KEYDOWN: switch(wParam){ case 'A': //再生 mmRes=waveOutWrite(hWaveOut,&whdr,sizeof(WAVEHDR)); if(mmRes!=MMSYSERR_NOERROR){ waveOutGetErrorText(mmRes,str,MAXERRORLENGTH); MessageBox(hWnd,str,"再生",MB_OK); } return 0; case 'S': //停止 mmRes=waveOutReset(hWaveOut); if(mmRes!=MMSYSERR_NOERROR){ waveOutGetErrorText(mmRes,str,MAXERRORLENGTH); MessageBox(hWnd,str,"停止",MB_OK); } return 0; case 'D': //一時停止 mmRes=waveOutPause(hWaveOut); if(mmRes!=MMSYSERR_NOERROR){ waveOutGetErrorText(mmRes,str,MAXERRORLENGTH); MessageBox(hWnd,str,"一時停止",MB_OK); } return 0; case 'F': //一時停止解除 mmRes=waveOutRestart(hWaveOut); if(mmRes!=MMSYSERR_NOERROR){ waveOutGetErrorText(mmRes,str,MAXERRORLENGTH); MessageBox(hWnd,str,"一時停止解除",MB_OK); } return 0; } return 0;
サンプルではなかなかエラーが発生しませんが、
一時停止を再生で解除しようとするとエラーが発生します。
また、成功した時の値も文字列に変換してくれるので、
条件文 if(mmRes!=MMSYSERR_NOERROR) を取り払ってみて下さい。
★☆ ソースファイル表示 ☆★