|
今回は、音の波形を「加算」して和音を作ってみます。音は重ね合わせることで「和音」になりその重ね合わせ処理も単純な波形同士の加算で行えるので(ただし、これは波形の表現と音の強さが一定の比例関係にある場合。Waveはどうなんだろう?)、3つの波形を生成し、順次重ねてみましょう。 和音の生成今回は波形の加算処理を行いやすくするために波形データを16ビット/8KHzで作成します。Waveの波形データは、8ビットだと符号なし、16ビットだと符号付で処理されるため、マイナスの値も含む波形データの加算には16ビットのほうが好都合なのです(8ビットだと、中央値を境に正負が変わるため中央値からの「絶対値」と「符号」を意識して処理する必要がある)。重ねる波形は、4オクターブのド・ミ・ソの3つ。16ビットだと−32768〜32767まで表現できるので、各波形の振幅は8000にしましょう。これなら3つ重なっても32000なので範囲内に収まります。 和音を作成する前に、重ね合わせる音の波形を求めておきましょう。今回も単純な矩形派を使うことにして、最初にオクターブ3のラの音(220Hz)を元にオクターブ4のドミソの音の周波数を計算します。 /* 半音一つの倍率 */ lfRate=pow(2,1.0/12.0); /* オクターブ4のド、ミ、ソの周波数 */ lfFreq[0]=220*pow(lfRate,3); /* ド */ lfFreq[1]=lfFreq[0]*pow(lfRate,4); /* ミ */ lfFreq[2]=lfFreq[1]*pow(lfRate,3); /* ソ */ 音の周波数が求まったら、次は和音の作成。今回は、全体で3秒間24000個のデータを作成することにして、1秒毎にドミソの音を重ねて行きます。つまり、最初にドの音を3秒間持続する波形を生成し、その上に1秒後から2秒間ミの音を、さらに2秒後から1秒間ソの音を重ねるわけです。 最初に、3秒間ドの音を作成しましょう。サンプリング形式が16ビット/8KHzなので、波形データはshort int型配列lpBuf[24000]に作成することにします。
/* ドの音の波長計算 */
l=(int)(8000.0/lfFreq[0]);
for (i=0;i<24000;i++) { /* ドの音の波形生成 */
if ((i % l)<l/2)
lpBuf[i]=8000;
else
lpBuf[i]=-8000;
}
続いて、ドの音の波形の2〜3秒の場所にミの音を加算し重ね合わせます。サンプリング周波数が8KHzなので、2〜3秒というのはデータで言えば8001〜24000個目ですね。
/* ミの音の波長計算 */
l=(int)(8000.0/lfFreq[1]);
for (i=8000;i<24000;i++) { /* ミの音を重ねる */
if ((i % l)<l/2)
lpBuf[i]+=8000;
else
lpBuf[i]-=8000;
}
最後に3秒目にソの音を重ねて、波形データの完成。
/* ソの音の波長計算 */
l=(int)(8000.0/lfFreq[2]);
for (i=16000;i<24000;i++) { /* ソの音を重ねる */
if ((i % l)<l/2)
lpBuf[i]+=8000;
else
lpBuf[i]-=8000;
}
lpBufに波形データが出来たら、そのデータを16ビット/8KHzの波形データとして出力します。 wf.wFormatTag=WAVE_FORMAT_PCM; wf.nChannels=1; wf.nSamplesPerSec=8000; wf.nAvgBytesPerSec=16000; wf.nBlockAlign=1; wf.wBitsPerSample=16; wf.cbSize=0; /* デバイスオープン */ waveOutOpen(&hWOut,WAVE_MAPPER,&wf,(DWORD)hwnd,0,CALLBACK_WINDOW); wh.lpData=(char *)lpBuf; wh.dwBufferLength=48000; wh.dwBytesRecorded=0; wh.dwUser=0; wh.dwFlags=0; wh.dwLoops=1; wh.lpNext=NULL ; wh.reserved=0 ; waveOutPrepareHeader(hWOut,&wh,sizeof(WAVEHDR)); waveOutWrite(hWOut,&wh,sizeof(WAVEHDR)); プログラム実行すると、WM_CREATEメッセージの応答で波形を作成し和音を演奏します。 |