Waveファイルの生成
Waveフォーマット
ファイルの先頭から順に「RIFFヘッダ」,「fmtチャンク」,「dataチャンク」と呼ばれるメモリ領域に分かれています。
各チャンクはそれぞれ識別子とチャンクのサイズ、データと分かれており、波形データはdataチャンクのデータ部分に記録されています。
fmtチャンク: | 定義 | チャンクサイズ | データ |
dataチャンク: | 定義 | チャンクサイズ | データ |
各領域の詳細を構造体として眺めてみると次のようになります。
waveデータを扱う構造体(波形データ以外)
//波形data以外のすべてのheader情報 typedef struct WaveHeader { //RIFF header unsigned char RiffTag[4]; //"RIFF"という文字列(4byte) unsigned long SizeOfFile; //total file size - 8を指定する(4byte) unsigned char WaveTag[4]; // "WAVE"という文字列(4byte) //fmt Chank header Section unsigned char FmtTag[4]; //"fmt "という文字列,4文字目が空白(4byte) unsigned long SizeOfFmt; //fmtチャンクのサイズ(4byte) //fmt Chank data Section struct WaveFormat { unsigned short FormatID; //リニアPCMの場合は1(2byte) unsigned short Channels; //チャネル数(2byte) unsigned long SamplingRate; //サンプリング周波数Hz(4byte) unsigned long BytesPerSec; //レート(4byte) unsigned short BlockAlign; //1ブロックあたりバイト数 (2byte) unsigned short BitsPerSample; //1サンプルあたりビット数 (2byte) }WaveFormat; //Data Chank header Section unsigned char DataTag[4]; //'data' (4byte) unsigned long SizeOfBuffer; //波形データのトータルサイズ (4byte) }WaveHeader;
"普通"のwaveファイルを扱うために必要な情報を整理してみます。
値が定まっている変数
RiffTag,WaveTag,fmtTag,DataTagは固定4文字
SizeOfFmt = 16;//(16 byte)
FormatID = 1;
ヘッダの総サイズ 44byte
必要な変数
Waveの仕様と、音声データの性質を決定する変数です。
Channels = 2;//Stereo
SamplingRate;//44100Hz
BitsPerSample;//16bit
上記3つより計算できる値
残りのフォーマットについては上の3つから計算することができるので、一緒に記憶する必要はありません。
レート
BytesPerSec = SamplingRate * BytesPerSample * Channels;
1ブロックあたりバイト数
BlockAlign = BitsPerSample / 8 * Channels;
ちなみに、1サンプルあたりバイト数
BytesPerSample = BitsPerSample / 8;
波形データのサイズに依存する変数
SizeOfBufferを波形データのbyte数とします。ファイルサイズ部分は次のようにして得られます。
SizeOfFile = SizeOfBuffer + sizeof(WaveHeader)-8; (データ総byte + 44 -8)
残りの波形データについて確認してみます。
波形データの構造
波形データはモノラルの場合配列そのままに、ステレオの場合はLRが交互に格納されています。
mono | sample1 | sample2 | sample3 | ... | |
stereo | sample1(L) | sample1(R) | sample2(L) | sample2(R) | ... |
1サンプルの値は、サンプルあたりのbit数によって少し異なります。
例えば、
8bitの場合1サンプル1byte unsigned char型0〜255 128が無音
16bitの場合1サンプル2byte short型 -32768〜32767 0が無音
という情報が記録されています。
サンプル数(チャネルごと)と波形データサイズの変換式
SizeOfSample個のデータを作りたい場合に必要なバッファサイズ(byte)は次のようにして求められます。
SizeOfBuffer = SizeOfSample * (BitsPerSample / 8) * Channels;
Cではmallocでこのようにメモリを確保すると良いと思います。
void* buffer=malloc(SizeOfBuffer);
確保した領域の先頭アドレスからi番目のサンプルの値を取得するには、サンプルあたりのbit数によってアクセスの仕方が異なります。
モノラル
unsigned char *ptr=(unsigned char*)buffer; //2byte=16bitごとにアクセス for(unsigned long i=0;i<SizeOfSample;i++){ //ptr[i] //i番目のサンプル }
ステレオ
short *ptr=(short*)buffer; //2byte=16bitごとにアクセス for(unsigned long i=0;i<SizeOfSample;i++){ //ptr[i*2] //左チャネルi番目のサンプル //ptr[i*2+1] //右チャネルi番目のサンプル }
秒とサンプル数(チャネル毎)・バッファサイズの変換
ある長さの時間(秒)のデータに必要なサンプル数は次のようにして求めることができます。
float Duration;//波形データの長さ[秒]
unsigned long SizeOfSample=(SamplingRate*Duration); //sample数
バッファサイズは前述のとおりです。
周期関数の生成
振幅,周波数[Hz]の周期関数をサンプリング周波数で標本化する場合について考えてみます。
1[s]をf_s分割するので,1サンプルあたりの間隔はです。
周期[s]としましょう。
i番目の時刻はです。
ちなみに周期[s]なら総サンプル数はになります。
標本化した信号の値はになります
サンプリング周波数44100Hz,2ch,16bit/サンプルのフォーマットで、キーが"A"の正弦波(440Hz)3秒分のバッファを作成する例です。
float SamplingRate = 44100.0f; //[Hz] float Frequency = 440.0f; //[Hz] float Amplification = 0.6f; float Duration=3.0f;//[s] const double PI = 3.1415926535; unsigned long SizeOfSample=unsigned long (SamplingRate*Duration);//sample数の計算 double val; unsigned long i; for(i = 0;i < SizeOfSample;i++){ val= Amplification*sin(2 * PI * i * Frequency / SamplingRate); ptr[i*2] = short(val*32767); //L channel ptr[i*2+1] = short(val*32767); //R channel }