「バイナリファイルを解析するスクリプト例」にて、WAVファイルの形式について解説し、ファイル構造を解析するスクリプトを組みました。ここでは、WAVファイルを生成してみようと思います。
例題として、基準周波数440[Hz]の「ラ」の音を1[s]程再生するWAVファイルを作ってみたいと思います。
"fmt "チャンクに指定するフォーマットの項目については、「バイナリファイルを解析するスクリプト例」で解説済みです。
識別子 | サイズ | 意味 |
wFormatTag | 2 | WAVEデータの種類(形式) |
nChannels | 2 | チャンネル数:モノラル=1、ステレオ=2 |
nSamplesPerSec | 4 | サンプリングレート(周波数)[Hz] |
nAvgBytesPerSec | 4 | データレート[バイト/s] |
nBlockAlign | 2 | ブロックサイズ |
wBitsPerSample | 2 | 1サンプル当たりのビット数 |
cbSize | 2 | 拡張データサイズ |
まず、どのようなフォーマットにするか?1つ1つ決めていきます。
Olive+のスクリプト言語MODELAでは、fopenという命令を使ってファイル入出力を実行することが可能です。
バイナリデータの書き出しには、fputb命令が使えます。この命令では、0〜255の整数値を1バイトデータとして書き出すことができます。
ただ、一々1バイトのデータに分解して書き出すのは手間なので、いくつか簡略化する手続きを作っておきます。
まずは、"RIFF"、"data"などのIDの書き出しを行う手続きです。
procedure fput_id fptr, $$wav_id
{
// 4文字のIDを書き出す
var dat;
macro chr;
// 1文字目
substr chr=$(wav_id),1,1;
chr2num dat=$(chr);
fputb fptr,dat;
// 2文字目
substr chr=$(wav_id),2,1;
chr2num dat=$(chr);
fputb fptr,dat;
// 3文字目
substr chr=$(wav_id),3,1;
chr2num dat=$(chr);
fputb fptr,dat;
// 4文字目
substr chr=$(wav_id),4,1;
chr2num dat=$(chr);
fputb fptr,dat;
}
IDを文字列として受け取り($$wav_id)、substr命令で1文字づつ取り出しながら、chr2num命令でASCIIコード番号に変換して書き出しています。
これで、例えば"RIFF"というIDを書きだしたい時は、以下の様にすればOKとなります。
fput_id fptr, "RIFF";
さらに、インテル系のOSなので、32ビットや16ビットの数値を書き出す時は、リトルエンディアンとなります。32ビットの整数値を書き出す手続きを以下のように定義しておきます。
procedure fput_int fptr, idat
{
// 32ビットの整数値をリトルエンディアンで書き出す
var dat;
// 1バイト目
dat=@@(idat&0xff);
idat=@@(idat>>8);
fputb fptr,dat;
// 2バイト目
dat=@@(idat&0xff);
idat=@@(idat>>8);
fputb fptr,dat;
// 3バイト目
dat=@@(idat&0xff);
idat=@@(idat>>8);
fputb fptr,dat;
// 4バイト目
dat=@@(idat&0xff);
idat=@@(idat>>8);
fputb fptr,dat;
}
リトルエンディアンですので、下位バイトから順に書き出します。書き出した後で、元のデータを8ビット右シフトして次のバイトに臨みます。「@@(〜)」は、カッコ内を符号無整数として計算する、「バイナリ演算部」です。バイナリ演算部の内部では、演算子の機能が変化していて、ビット毎のAND演算(&)や右シフト演算(>>)を使って処理しています。
同様に、16ビット整数値の書き出し手続きfput_shortも作ってあります。
肝心なデータですが、440Hzのsin波です。WAVファイルに記録する1つ分の波高データは、nSamplesPerSecという周波数でサンプリングされたもの、と言う解釈です。逆に言うと、1つの波高データは、1÷nSamplesPerSec[s]期間の代表データ、という事になります。
つまり、1[s]で一周(2π)するsin波は、以下の通りです。
sin(2π×x÷nSamplesPerSec)
xを0〜nSamplesPerSec−1まで変化させてグラフを書くと、1周期分のsin波となります。かつ、この1周期は1[s]に相当しているので、このsin波は1[Hz]です。440[Hz]のsin波は、1[s]の間に440回振動すれば良いので、以下となります。
sin(2π×x÷nSamplesPerSec×440)
sin関数は-1〜1の値を取りますが、WAVの波高データは0〜255(wBitsPerSample=8ビットの場合)ですので、sin波も、中央の128を中心として振動させます。結果、以下の様なスクリプトになります。
// 「ラ」の音の波形を生成する。
var idx;
var dat;
idx=0;
while (idx<nAvgBytesPerSec)
{
dat=(sin (2*PI*idx/nSamplesPerSec*wav_freq) + 1)/2*256;
clip dat=0,255;
fputb fout,dat;
// 継続
idx=idx + 1;
}
完成したスクリプトとWAVファイルです。
Olive+スクリプト | make_wav.olv |
出力されるWAVファイル | make_wav.wav |
Cで作った時は一瞬でした(実行時間)が、スクリプトだとノロいです……。まあ、仕方ないですね……。その代わりにスクリプトは、手軽にテキストエディタだけでプログラミングできたり、本格的に他のプログラミング言語を使うほどでもない場合などに便利に使えるのが利点ですので、使い分けましょう。
出来上がったWAVファイルの先頭部分をダンプしてみた結果がこちら。
期待通りのsin波になっていることが確認できました。