[Atelier Blue アトリエブルー]HomeMemo>WAVEファイルのデータをリアルタイムに書き換えて再生する

WAVEファイルのデータをリアルタイムに書き換えて再生する

きっかけは「マイナスワン」というプレーヤです。ソースコード付きだったのですが、あまりにも混沌としていて読むことが出来ませんでした。今なら読めるかもしれません。

C言語です。WAVE形式のファイルからデータを抜き出し、モノラル化(L+R)またはボーカルキャンセル(L-R)を行っています。特に難しいところはありません。ただ、WINAPIを使います。

参考資料になったもの

マルチメディアプログラミング【Windowsプログラミング研究所】

一番役に立った。

mmioOpen

リファレンスならここ。

Yggdrasill - Visual C++ & DirectX - Text 09

MMIOでWAVE読みとりはここ。

Wave音源と信号処理実験室

最初の方に見つけた。

マルチメディア API

一番最初に参考にした。

ソースコード

#include <windows.h>

//ライブラリをくっつける
#pragma comment(lib, "winmm.lib")
//#pragma comment(lib, "C:\\borland\\bcc55\\Lib\\PSDK\\winmm.lib")

/****
標準
*****/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
BOOL InitInstance(HINSTANCE hInst);
HWND MakeWnd(HWND *hWnd,HINSTANCE hInst);

static LPSTR lpClassName = "My Class";
static LPSTR lpClassTitle = "First Your WindowsApplication";
static LPSTR lpMenuName = "My Soft";

/*******
WAVE系
********/

//ファイルネーム
static LPCTSTR openFileName = TEXT("i:\\temp\\test.wav");

//バッファ数(2以上にすること)
#define BUFFER_COUNT 2

//チャンネル数(2固定)
static const int channel =2;

//再生時の周波数
int srate;

//バッファ関連情報
int bufferSize;
int bufferLenght;

//バッファセレクター
int bufferSelect;

//バッファ
static short  *(wWave[BUFFER_COUNT]);

//出力のため構造体
static HWAVEOUT hWave;
static WAVEHDR whdr[BUFFER_COUNT];

//WAVEファイルを読み込むための構造体
static HMMIO hmmio;
//フォーマットチャンクのための構造体
static MMCKINFO ckRiff;


//再生予定のWAVEデータサイズ
int waveDataSize;
//再生済のWAVEデータのサイズ
int readDataSize;

int initWave(HWND hWnd);
int oepnWaveFile(WAVEFORMATEX *wfe);
void closeWave(void);
LRESULT wmWomDone(void);
int readWaveData(HMMIO hmmio,short *buf,int bufferSize,int bufferLenght);

/*****************************************************************************
* メイン
*****************************************************************************/
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, INT nCmdShow)
{
    HWND hWndMain;
    MSG msg;


    if(!InitInstance(hInstance)){
        MessageBox(0, "ウィンドウの登録に失敗しました。","MyApp", MB_OK);
        return FALSE;
    }
    if(!MakeWnd(&hWndMain,hInstance)){
        MessageBox(0, "ウィンドウの作成に失敗しました。","MyApp", MB_OK);
        return FALSE;
    }
    ShowWindow(hWndMain, nCmdShow);
    UpdateWindow(hWndMain);

    //Wave系準備
    if(initWave(hWndMain) ==FALSE)return FALSE;

    while(GetMessage(&msg, NULL, 0, 0)){
        DispatchMessage(&msg);
    }

    //後処理
    closeWave();

    return msg.wParam;
}

/*****************************************************************************
 ウィンドウ作成
*****************************************************************************/
BOOL InitInstance(HINSTANCE hInst){
    WNDCLASSEX   wndclass;
    wndclass.cbSize = sizeof(WNDCLASSEX);
    wndclass.hInstance = hInst;
    wndclass.lpszClassName = lpClassName;
    wndclass.lpfnWndProc =MainWndProc;
    wndclass.style =0;
    wndclass.hIcon =LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hIconSm =LoadIcon(NULL, IDI_WINLOGO);
    wndclass.hCursor =LoadCursor(NULL, IDC_ARROW);
    wndclass.lpszMenuName =NULL;
    wndclass.cbClsExtra = wndclass.cbWndExtra =0;
    wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH);
    return RegisterClassEx(&wndclass);
}
HWND MakeWnd(HWND *hWnd,HINSTANCE hInstance){
    *hWnd=CreateWindow(lpClassName,lpClassTitle,WS_OVERLAPPEDWINDOW
        ,CW_USEDEFAULT,CW_USEDEFAULT,100,100,HWND_DESKTOP
        ,NULL,hInstance,NULL);
    return *hWnd;
}
/*****************************************************************************
 ウィンドウプロシージャー
*****************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam){
    switch (Message){

    case MM_WOM_DONE:
        //バッファが切れたときに呼び出される。
        return wmWomDone();
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, Message, wParam, lParam);
    }
    return FALSE;
}


/*****************************************************************************
WAVEファイルの読み込み
*****************************************************************************/
int oepnWaveFile(WAVEFORMATEX *wfe)
{
    //WAVEファイルのオープン
    hmmio = mmioOpen((LPSTR)openFileName,NULL,MMIO_READ);
    if(!hmmio){
        MessageBox(0, "WAVEファイルのオープンに失敗","MyApp", MB_OK);
        return FALSE;
    }
    //WAVEファイルかのチェック
    if(mmioDescend(hmmio,&ckRiff, NULL, 0) != MMSYSERR_NOERROR
        || ckRiff.ckid != FOURCC_RIFF
        || ckRiff.fccType != mmioFOURCC('W', 'A', 'V', 'E'))
    {
        MessageBox(0, "WAVEファイルではありません","MyApp", MB_OK);
        mmioClose(hmmio, 0);
        return FALSE;
    }

    // フォーマットチャンクの読み込み
    ckRiff.ckid = mmioFOURCC('f', 'm', 't', ' ');
    //フォーマットチャンクに侵入
    if(mmioDescend(hmmio, &ckRiff, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR){
        MessageBox(0, "フォーマットチャンクへの侵入に失敗","MyApp", MB_OK);
        mmioClose(hmmio, 0);
        return FALSE;
    }

    //フォーマットチャンクを読み込む
    ZeroMemory(wfe, sizeof(*wfe));
    if(mmioRead(hmmio, (HPSTR)wfe, (long)(ckRiff.cksize)) != (long)(ckRiff.cksize)){
        MessageBox(0, "フォーマットチャンクの読み込みに失敗","MyApp", MB_OK);
        mmioClose(hmmio, 0);
        return FALSE;
    }

    //フォーマットチャンクから抜け出す
    if(mmioAscend(hmmio, &ckRiff, 0) != MMSYSERR_NOERROR){
        MessageBox(0, "フォーマットチャンクからの抜け出しに失敗","MyApp", MB_OK);
        mmioClose(hmmio, 0);
        return FALSE;
    }

    // データチャンクの読み込み
    ckRiff.ckid = mmioFOURCC('d', 'a', 't', 'a');
    //データチャンクに侵入
    if(mmioDescend(hmmio, &ckRiff, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR){
        MessageBox(0, "データチャンクの侵入に失敗","MyApp", MB_OK);
        mmioClose(hmmio, 0);
        return FALSE;
    }

    //16Bitステレオかをチェック
    if(wfe->nChannels !=2 || wfe->wBitsPerSample !=16){
        MessageBox(0, "16Bitまたはステレオではないので再生できません","MyApp", MB_OK);
        return FALSE;
    }

    //データのサイズ
    waveDataSize = ckRiff.cksize;

    //再生周波数を設定する。(自由に変えても良い。再生速度が変わる)
    srate = wfe->nSamplesPerSec;
    //srate = 44100;
    //srate = 49500;

    return TRUE;
}

/*****************************************************************************
 Wave系の初期化全部
*****************************************************************************/
int initWave(HWND hWnd)
{
    int i;
    WAVEFORMATEX wfe;

    //WAVEデータ読み込みのための設定をする
    if(oepnWaveFile(&wfe) ==FALSE){
        return FALSE;
    }

    //バッファの長さを計算
    bufferLenght = srate * channel;
    bufferSize =bufferLenght * sizeof(short);

    //バッファのための領域確保
    for(i =0;i<BUFFER_COUNT;i++){
        wWave[i] = (short *)malloc(bufferSize);
    }

    //出力デバイスの設定
    wfe.wFormatTag = WAVE_FORMAT_PCM;
    wfe.nSamplesPerSec = srate;
    wfe.nBlockAlign = wfe.nChannels * wfe.wBitsPerSample / 8;
    wfe.nAvgBytesPerSec = wfe.nSamplesPerSec * wfe.nBlockAlign;

    //出力デバイスを開く
    waveOutOpen(&hWave , WAVE_MAPPER , &wfe ,
        (DWORD)hWnd , 0 , CALLBACK_WINDOW);

    for(i=0;i<BUFFER_COUNT;i++){

        //再生バッファにデータを送るための設定
        whdr[i].lpData = (LPSTR)wWave[i];
        whdr[i].dwBufferLength = 0;
        whdr[i].dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
        whdr[i].dwLoops = 1;

        waveOutPrepareHeader(hWave , &(whdr[i]) , sizeof(WAVEHDR));
    }

    //マルチバッファリングをするために空データを書き込み
    waveOutWrite(hWave , &(whdr[0]) , sizeof(WAVEHDR));
    waveOutWrite(hWave , &(whdr[1]) , sizeof(WAVEHDR));

    //セレクタの設定
    bufferSelect = 2%BUFFER_COUNT;
    return TRUE;
}

/******************************************************************************
Wave系の後処理全般
******************************************************************************/
void closeWave(void)
{
    int i;
    waveOutReset((HWAVEOUT)hWave);
    for( i=0;i<BUFFER_COUNT;i++){
        waveOutUnprepareHeader((HWAVEOUT)hWave , &(whdr[i]) , sizeof(WAVEHDR));
        free(wWave[i]);
    }

    //クローズ
    waveOutClose(hWave);

    //データチャンクから抜け出す
    mmioAscend(hmmio, &ckRiff, 0);
    mmioClose(hmmio,0);
}

/*****************************************************************************
バッファが切れたときに呼び出される
*****************************************************************************/
LRESULT wmWomDone(void)
{
    int readsize;

    //最後まで再生した場合は最初にシークする。
    if(waveDataSize - readDataSize == 0){
        mmioSeek(hmmio,-waveDataSize,SEEK_CUR);
        readDataSize = 0;

        //MM_WOM_DONEを起こすために「waveOutWrite」を呼ぶ
        whdr[bufferSelect].dwBufferLength = 0;
        waveOutWrite((HWAVEOUT)hWave , &(whdr[bufferSelect]) , sizeof(WAVEHDR));

        //バッファセレクターのインクリメント
        bufferSelect = (bufferSelect+1)%BUFFER_COUNT;
        return 0;
    }

    //読み込むサイズを決定する。
    readsize =bufferSize;
    if(readsize > waveDataSize - readDataSize){
        readsize = waveDataSize -readDataSize;
        readDataSize = waveDataSize;
    }else{
        readDataSize+=readsize;
    }

    //読み込み
    readsize = readWaveData(hmmio,wWave[bufferSelect],readsize,bufferLenght);

    //再生バッファに書き込み
    whdr[bufferSelect].dwBufferLength =readsize;
    waveOutWrite((HWAVEOUT)hWave , &(whdr[bufferSelect]) , sizeof(WAVEHDR));

    //バッファセレクターのインクリメント
    bufferSelect = (bufferSelect+1)%BUFFER_COUNT;
    return 0;
}

/*****************************************************************************
ファイルからデータを読みとり、加工する
*****************************************************************************/
int readWaveData(HMMIO hmmio,short *buf,int bufferSize,int bufferLenght)
{
    //読み込み
    int ret = mmioRead(hmmio,(HPSTR)buf,bufferSize);

    //手抜きボーカルキャンセラー&モノラル化
    if(0){
        int i;
        for(i=0;i<bufferLenght;i+=2){
            //モノラル化
            //short t1 = (short)((((int)buf[i]) +((int)buf[i+1]))/2);

            //真ん中の音を消す
            short t1 = (short)(((int)buf[i]) -((int)buf[i+1]));

            buf[i] = buf[i+1] =t1;
        }
    }

    return ret;
}

説明

え~と、参考資料の方を見てください。あんまり説明することがありません。使っているテクニックは

あんまりたいしたこと無いです。詳しく知りたい方は一方下されば説明書きます。そのうちCodeZineに載るかもしれません(確証無し)。

注意

無限リピートです。適当なところで止めてやってください。


ページの一番上へ
初版2006-4-22
[Atelier Blue アトリエブルー]HomeMemo>WAVEファイルのデータをリアルタイムに書き換えて再生する