今回は、音の波形を「加算」して和音を作ってみます。音は重ね合わせることで「和音」になりその重ね合わせ処理も単純な波形同士の加算で行えるので(ただし、これは波形の表現と音の強さが一定の比例関係にある場合。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メッセージの応答で波形を作成し和音を演奏します。 |