waveOutSetPosition関数作成

概要:現在の再生位置を設定する関数を作成します

waveOutGetPosition関数があるんだから、waveOutSetPosition関数もあるだろうと
ネットで探している方は結構いるみたいです。
私も探しました。
しかし、そんな関数は用意されていないんですよねー、不思議な事に。

標準関数がないなら作ればいいじゃない!
という事で、作りました。
正確には現在の再生位置を変更してるわけじゃなかったりしますが、
表面上はシークと同じ効果があります。

■waveOutSetPosition関数の定義

int waveOutSetPosition(
  HWAVEOUT hwo,
  LPWAVEHDR pwh,
  LPMMTIME pmmt,
  WOSP *wosp
);


hwo にはウェーブフォームオーディオ出力デバイスのハンドルを指定します。
pwh には現在の再生位置を変更したいWAVEHDR構造体のアドレスを指定します。
pmmt には希望の時間形式と時間が格納されたMMTIME構造体のアドレスを指定します。
wosp にはWOSP構造体のアドレスを指定します。

WOSP構造体はwaveOutSetPosition関数に必要な情報を格納する構造体です。

typedef struct _WOSP{
  WORD nBlockAlign;
  DWORD nAvgBytesPerSec;
}WOSP;


nBlockAlign にはブロックアライメント(データの最小単位)を格納します。
nAvgBytesPerSec には1秒間のバイト数を格納します。

waveOutSetPosition関数は成功したら 0 、失敗したら負値を返します。

できるだけwaveOutGetPosition関数に似た定義にしてみましたが、
気に入らなければ自由に変更して使って下さい。

■waveOutSetPosition関数の設計

waveOutSetPosition関数はwaveOutGetPosition関数で得られる「現在の再生位置」を変更する関数ではありません。
どうもこの「現在の再生位置」は変更できないみたいなので。

ならば、そもそもデータが途中から始まっていれば良いんじゃないか?
という発想からこの関数は生まれました。
しかし、本当に途中から始まっているデータを作成するより
データの先頭を指すアドレスを変更した方が簡単です。
そして、データのバイト数も調節すればプログラムを完全に騙す事ができます。

再生中のWAVEはどうしましょうか?
これは停止してから、新しいWAVEの再生を開始する他ないでしょう。
一瞬だけ無音が発生してしまうかもしれないのが残念ですが、使ってみるとそれほど気になりません。

設計思想はこんなところです。
それではこれをプログラムとして実装してみましょう。

■waveOutSetPosition関数のプログラム

希望の時間はMMTIME構造体で指定してもらいます。
時間形式は TIME_BYTES , TIME_SAMPLES , TIME_MS だけに限定します。
WAVEなのでこれだけで十分でしょう。
基準になるのは TIME_BYTES です。
何故なら、WAVEバッファの先頭を指すアドレスからのバイト数(オフセット)が必要だからです。

ここで一つ注意しなければならない事があります。
それは……
再生デバイスに書き込むデータサイズは
  ブロックアライメント(データの最小単位)の整数倍でなければならない

途中から再生する場合、先頭からブロックアライメントの整数倍の位置から始めなければならない
……という制約です。

ところで、ブロックアライメントは wfe.nChannels * wfe.wBitsPerSample/8 の計算結果であるとし、
全く気にしてきませんでしたが、この計算式はPCM以外では成り立たないかもしれないのです。

ブロックアライメントは wfe.nChannels * wfe.wBitsPerSample/8 の整数の最小値です。
例えば、wfe.nChannels=2 , wfe.wBitsPerSample=6 の場合は 3 です。
これは 2*6=12 と 8 の最小公倍数 24 を 8 で割った値です。
PCM形式の場合は wfe.wBitsPerSample が 8 か 16 しか取り得ないので上式は必ず整数になり、
正しいブロックアライメントがいつも得られていたというわけです。

プログラムでは、これを正しい値に近い方に切り下げ、または切り上げを行います。

WAVEバッファの先頭からのバイト数が決まったら、これが有効な範囲内にあるかチェックしましょう。
0 未満、または pwh->dwBufferLength 以上なら無効な領域です。
無効な領域である場合は何もしません。
再生中であれば、現在の再生を継続します。

次に現在の再生を停止して、WAVEHDR構造体を初期化をします。
初期化するWAVEHDR構造体は引数で渡されたそれとは別物です。
この構造体はwaveOutSetPosition関数の他に、
初期化をクリーンアップする別な関数でも必要なのでグローバル変数として用意します。
ただし、外部から参照されると困るので static なグローバル変数にします。
この静的な変数は何度も使われる可能性があるので、
関心のないメンバも 0 で埋める必要がある事に注意して下さい。
WAVEバッファの先頭アドレスは、
引数で渡されたWAVEバッファの先頭アドレスに計算したオフセットを足したものです。
pwh->lpData はLPSTR型ですが、LPSTR型は char* 型であることが宣言を辿っていくとわかります。
また、リファレンスには次の記述があります。
  Pointer to a null-terminated string of 8-bit Windows (ANSI) characters.
従って、任意のバイト数だけ移動する為に1バイト型にキャストする必要はありません。
WAVEバッファのバイト数は、
引数で渡されたWAVEバッファのバイト数から計算したオフセットを引いたものです。

そして最後に新しいWAVEバッファを再生します。
再生中でなければ、途中から再生が開始される事になります。
この時、waveOutSetPosition関数は再生開始位置を設定して再生する関数として働きます。

■SetPositionUnprepareHeader関数

SetPositionUnprepareHeader関数はWAVEHDR構造体の初期化をクリーンアップします。
元のWAVEHDR構造体の初期化をクリーンアップする前に実行して下さい。
初期化されていないWAVEHDR構造体を渡した場合は何もしません。

void SetPositionUnprepareHeader(
  HWAVEOUT hwo
);


hwo にはウェーブフォームオーディオ出力デバイスのハンドルを指定します。

static WAVEHDR g_whdr;

int waveOutSetPosition(HWAVEOUT hwo,LPWAVEHDR pwh,LPMMTIME pmmt,WOSP *wosp)
{
    DWORD cb;
    switch(pmmt->wType){
        case TIME_BYTES:
            cb=pmmt->u.cb;
            break;
        case TIME_SAMPLES:
            cb=pmmt->u.sample * wosp->nBlockAlign;
            break;
        case TIME_MS:
            cb=pmmt->u.ms * wosp->nAvgBytesPerSec / 1000;
            break;
        default:
            return -1;
    }
    if(cb % wosp->nBlockAlign){
        if((cb % wosp->nBlockAlign) <= (wosp->nBlockAlign / 2)){
            cb-=(cb % wosp->nBlockAlign);
        }else{
            cb+=(wosp->nBlockAlign - (cb % wosp->nBlockAlign));
        }
    }
    if(cb<0 || cb >= pwh->dwBufferLength) return -2;

    waveOutReset(hwo);
    SetPositionUnprepareHeader(hwo);

    ZeroMemory(&g_whdr,sizeof(WAVEHDR));
    g_whdr.lpData=pwh->lpData + cb;
    g_whdr.dwBufferLength=pwh->dwBufferLength - cb;
    g_whdr.dwFlags=WHDR_BEGINLOOP | WHDR_ENDLOOP;
    g_whdr.dwLoops=1;

    waveOutPrepareHeader(hwo,&g_whdr,sizeof(WAVEHDR));
    waveOutWrite(hwo,&g_whdr,sizeof(WAVEHDR));
    return 0;
}

void SetPositionUnprepareHeader(HWAVEOUT hwo)
{
    if(g_whdr.dwFlags & WHDR_PREPARED){
        waveOutUnprepareHeader(hwo,&g_whdr,sizeof(WAVEHDR));
    }
}

■シークするプログラム

8ビットステレオWAVEを作成して再生します。
前回同様、モノラルでも試したい方の為に必要な箇所をコメントアウトしておきました。
シークする時間はミリ秒単位で指定してみます。

#include<windows.h>
#include"waveOutSetPosition.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 LPBYTE lpWave;
    int i,len;

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

    MMTIME mmt;
    static int ms;
    static WOSP wosp;    //waveOutSetPosition関数の専用構造体

    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;

            wosp.nBlockAlign=wfe.nBlockAlign;
            wosp.nAvgBytesPerSec=wfe.nAvgBytesPerSec;

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

            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+32;    //右
                }else{
                    lpWave[2*i ]=128-64;     //左
                    lpWave[2*i+1]=128-32;    //右
                }
            }
            /*for(i=0;i<SRATE*2;i++){    //波形データ作成
                if(i%len < len/2)    lpWave[i]=(128+32)<<8 | (128+64);
                else                 lpWave[i]=(128-32)<<8 | (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));
            waveOutWrite(hWaveOut,&whdr,sizeof(WAVEHDR));
            return 0;
        case WM_LBUTTONDOWN:    //戻る
            ms-=300;
            mmt.wType=TIME_MS;
            mmt.u.cb=ms;
            waveOutSetPosition(hWaveOut,&whdr,&mmt,&wosp);
            InvalidateRect(hWnd,NULL,TRUE);
            return 0;
        case WM_RBUTTONDOWN:    //進む
            ms+=300;
            mmt.wType=TIME_MS;
            mmt.u.cb=ms;
            waveOutSetPosition(hWaveOut,&whdr,&mmt,&wosp);
            InvalidateRect(hWnd,NULL,TRUE);
            return 0;
        case WM_PAINT:
            hdc=BeginPaint(hWnd,&ps);
            wsprintf(str,"再生開始時間 = %d [ms]",ms);
            TextOut(hdc,0,0,str,lstrlen(str));
            EndPaint(hWnd,&ps);
            return 0;
        case WM_DESTROY:
            waveOutReset(hWaveOut);
            SetPositionUnprepareHeader(hWaveOut);
            waveOutUnprepareHeader(hWaveOut,&whdr,sizeof(WAVEHDR));
            waveOutClose(hWaveOut);
            free(lpWave);
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

なかなかのできでしょう?
waveOutSetPosition関数を探している方、ここにありますよー。(^_^)/~

★☆ ダウンロード ☆★


戻る / ホーム