wavファイルを作りたい

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
山崎

wavファイルを作りたい

#1

投稿記事 by 山崎 » 15年前

いつもお世話になっております、山崎です。
今回は、wavファイルの作り方について伺いに参りました。

先日、お世話になっている方に
「こんなふうに音が鳴るプログラムが作りたい。どうすればいいかわからないか」
と尋ねられ、音声ファイルの中身を全く知らない私は何も力になることができませんでした。
そこで、現在wavファイルの作り方についていろいろ調べております。

とりあえず、wavファイルを学ぶ上での最初の目標として、
「"ラ"の音を1秒間鳴らすwavファイルを作る」
ことにいたしました。

そしてwavファイルの作り方を検索したところ、
フォーマットについて紹介したサイトはいくつか見つけることができたのですが、
どんな方法でそれらのフォーマットをファイルに書き込めばいいのか
具体的な解説が載ったサイトを見つけることができませんでした。
ちなみに、以下のサイトを主な参考としていました。
http://www.kk.iij4u.or.jp/~kondo/wave/#wav

そこで、それらのサイトのフォーマットを見ながら、
C++で闇雲に下記のようなプログラムを書いて実行し、怪しいwavファイルを作って
メディアプレイヤーで読み込んでみましたが、もちろん再生されることはありませんでした。
本当にwavファイルについて何もわかっていないので、かなり馬鹿なことをしていると思います。
コメントに、作成中の私の心の中の疑問などを書きました。
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE* fp=fopen("TestWave.wav","wb");    //サイトには何も書いていなかったが、wavの中身はおそらくバイナリファイルなのだろう。

    fputs("RIFF",fp);    //"RIFF"という文字列を4バイトで書き込めばいいのだろうか。半角英数字は1文字で1バイトだから、これでいいはず?
    fputs("1500",fp);    //全体のファイルサイズから8を引いたバイト数を書けばいいらしい。今はわからないから適当に1500バイトと書いて後回し。
    fputs("WAVE",fp);    //"WAVE"という文字列を4バイトで書き込む。
    fputs("fmt ",fp);    //"fmt "という文字列を4バイトで書き込む。おっと、スペースを入れるのを忘れちゃいけない。
    fputs("0018",fp);    //fmtチャンクなるもののバイト数を書くのだらしい。ここもよくわからないから適当に18バイトにしておこう。

    fputs("01",fp);        //フォーマットIDとは何だろう?よくわからないので、サイトのとおり1にしておこう。
    fputs("01",fp);        //チャンネル数とはステレオとモノラルのことらしい。まずは簡単そうなモノラルにしてみよう。ということで1。
    fputs("4410",fp);    //サンプリングレート、1秒間に何バイト分の音声データを持たせるか、かな。勝手にキロのオーダーになるのだろうか。よくわからないので4410に。
    fputs("4410",fp);    //データ速度?サンプリングレートとどう違うのだろう。よくわからないから同じ4410に。
    fputs("04",fp);        //ブロックサイズなるものを書くらしい。よくわからないので、サイトのまねをして4に。
    fputs("16",fp);        //「サンプルあたりのビット数」とは何だろう。そもそも何のサンプルだろう?ここもサイトのまねをして16に。
    fputs("00",fp);        //拡張部分のサイズを書くらしい。拡張しなくてもいいから0にしておく。

    fputs("data",fp);    //"data"という文字列を4バイトで書き込む。
    fputs("1000",fp);    //波形データのバイト数をここに入れるようだ。とりあえず1000バイトとしておく。

    //最後に波形データを書き込めばいいようだが、どんな形式で書けばいいのだろう。
    //周波数とか振幅とか、どういう形式で書いて何で区切ればいいのだろう。
    //とりあえず、何の意味もないノイズでもいいので音を鳴らしてみよう。
    //さっき波形データのバイト数を1000にしたから、charが1000個で1000バイトになるはずだ。
    for(int i=0;i<1000;i++)
    {
        char Temp=rand()%256;    //とりあえず0から255までのランダムな数値を求める。
        fputc(Temp,fp);
    }

    fclose(fp);
    return 0;
}
これではラの音を出すどころか、ノイズさえ出すことができません。
どのようにすれば、wavファイルをC++で生成することができるのでしょうか。
あるいは、フォーマットだけでなく具体的なwavの作り方を解説しているサイトはありますでしょうか。

ookami

Re:wavファイルを作りたい

#2

投稿記事 by ookami » 15年前

とりあえず、数値のところは、文字列ではなくバイナリデータで書き込むんじゃないかと思います。
例えば、

fputs("1500",fp);

i=1500;
fwrite(&i,4,1,fp);

エンディアンとか、ちょい不安です。

toyo

Re:wavファイルを作りたい

#3

投稿記事 by toyo » 15年前

構造体にした方がまとまりますよ
windowsなら
#include <windows.h>
で使えます
WAVEFORMAT wf;
PCMWAVEFORMAT pfm;
pfm.wBitsPerSample = 16;//「サンプルあたりのビット数」。16か8、16ビットサウンドの方が音質がよい。
wf.wFormatTag = 1; //フォーマットID PCMフォーマットなので1。
wf.nChannels = 1; //モノラルで1。
wf.nSamplesPerSec = 44100; //サンプリングレート、よくみかける44.1Kに
wf.nAvgBytesPerSec = wf.nChannels * wf.nSamplesPerSec * pfm.wBitsPerSample / 2;//データ速度。上の2つをかけたやつに16ビットサウンドなら2倍
wf.nBlockAlign = wf.nChannels * pfm.wBitsPerSample / 2;// データブロック1個のバイト数
書き込むときも
fwrite(&pfm, sizeof(pfm), 1, fp);
でまとめ書きできます

toyo

Re:wavファイルを作りたい

#4

投稿記事 by toyo » 15年前

間違えた
wf.nAvgBytesPerSec = wf.nChannels * wf.nSamplesPerSec * pfm.wBitsPerSample / 8;//データ速度。上の2つをかけたやつに16ビットサウンドなら2倍
wf.nBlockAlign = wf.nChannels * pfm.wBitsPerSample / 8;// データブロック1個のバイト数

/2じゃなくて/8です

追加編集
書き込む前に
pfm.wf = wf;
も必要です
画像

toyo

Re:wavファイルを作りたい

#5

投稿記事 by toyo » 15年前

ちょっと作ってみました
音データは+-800の矩形波です
#include <stdio.h>
#include <windows.h>

int main()
{
DWORD file_size;
DWORD pfm_size = sizeof(PCMWAVEFORMAT);
DWORD data_size;
PCMWAVEFORMAT pfm;
short* wave_data;
int freqency = 441; // 適当(ラの音に近くてサンプリングレートにきりがいい)

pfm.wBitsPerSample = 16;
pfm.wf.wFormatTag = 1;
pfm.wf.nChannels = 1;
pfm.wf.nSamplesPerSec = 44100;
pfm.wf.nAvgBytesPerSec = pfm.wf.nChannels * pfm.wf.nSamplesPerSec * pfm.wBitsPerSample / 8;
pfm.wf.nBlockAlign = pfm.wf.nChannels * pfm.wBitsPerSample / 8;
data_size = pfm.wf.nAvgBytesPerSec * 10; // 10秒分のデータ
wave_data = (short*)malloc(data_size);
int loop = pfm.wf.nSamplesPerSec / freqency / 2;
int count = 0;
for (int sec = 0; sec < 10; sec++)
{
for (int j = 0; j < freqency; j++)
{
for(int i=0; i < loop; i++)
{
wave_data[count++] = 800; // 音量
}
for(int i=0; i < loop; i++)
{
wave_data[count++] = -800; // -音量
}
}
}
file_size = sizeof(PCMWAVEFORMAT) + data_size + 20; // 下の+の分
FILE* fp=fopen("TestWave.wav","wb");
fputs("RIFF",fp);
fwrite(&file_size, sizeof(DWORD), 1, fp);
fputs("WAVE",fp); // +4
fputs("fmt ",fp); // +4
fwrite(&pfm_size, sizeof(DWORD), 1, fp); // +4
fwrite(&pfm, sizeof(PCMWAVEFORMAT), 1, fp); // +sizeof(PCMWAVEFORMAT)
fputs("data",fp); // +4
fwrite(&data_size, sizeof(DWORD), 1, fp); // +4
fwrite(wave_data, 1, data_size, fp); // +data_size
fclose(fp);
free(wave_data);
return 0;
}

山崎

Re:wavファイルを作りたい

#6

投稿記事 by 山崎 » 15年前

ookamiさん、toyoさん
ご返信まことにありがとうございます。

toyoさんの提示してくださったコードを実行したところ、
大体"ラ"の音が10秒続くwaveファイルが作成されました。
このプログラムを元に、これからwaveファイル作成に関して精進していこうと思います。
本当にありがとうございます。



提示して頂いたプログラムについて、
いくつか質問があるのですがよろしいでしょうか。

1.
short* wave_data; とありますが、なぜ波形データの部分はshort型で領域を確保するのでしょうか。

2.
fputs("RIFF",fp);  と
fwrite(&file_size, sizeof(DWORD), 1, fp);  のように、
fputsとfwriteで書き込む2通りがありますが、
fputsだけを使う、あるいはfwriteだけを使う、といった場合は、どのようにすればいいでしょうか。

3.
最後はちょっと意見をお伺いしたいのですが、
提示してくださったプログラムではwindows.hのDWORD構造体やPCMWAVEFORMAT構造体を使っておられますが、
windows.hの構造体を用いずにwaveファイルを生成することについてどうお考えでしょうか。
プログラムが煩雑になりすぎて非現実的ではないとか、
そこまで自力でのwaveファイルの作成にこだわる必要はない、など意見がありましたらどうかお聞かせください。

toyo

Re:wavファイルを作りたい

#7

投稿記事 by toyo » 15年前

お答えします
1.
今回はサンプリングデータpfm.wBitsPerSampleを16ビットに指定したため同じビット数のshortにしました
16ビットサウンドの場合はサンプルデータの型はsigned shortになります。この場合0が中央値で正と負の値をとります。
これが8ビットサウンドの場合データ型はunsigned charにしないといけません。この場合中央値が128になります。
2.
バイナリデータを扱うのでfputsだけでは無理です。
unsigned int riff = 'FFIR';
fwrite(&riff, 4, 1, fp);
のような方法もありますが自分ではどうかなと思います。
windowsでは
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ))
のマクロも定義されていてこれを使えば
DWORD riff = MAKEFOURCC('R','I','F','F');
fwrite(&riff, 4, 1, fp);
と書けます。
こちらのほうがお勧めですね。
3.
PCMWAVEFORMATは構造体ではなくばらばらの変数にしてもまったく問題ないと思いますよ。
最初にあげられた参考URLのサンプルプログラムではそうなっているようです。
構造体をファイルで読み書きするにはpaddingの問題がからんできますし。
DWORDは構造体ではなく型のtypedefですのでunsigned longに書き換えればいいだけです。
タイプするのがめんどくさかっただけですので。

山崎

Re:wavファイルを作りたい

#8

投稿記事 by 山崎 » 15年前

toyoさん
ご丁寧に回答くださり、誠にありがとうございます。
それとお礼が遅れてしまい申し訳ございません、夏風邪を引いたためか体調を崩しておりました。

wave_dataに使う型や中央値には、決まりがあるのですね。
それを聞かないままでは、危うく不適切な型などを使うところでした。
気をつけて学んでいきたいと思います。

当初、wavファイルの編集に特化したwindows.hの構造体の存在を知らなかったので、
知らないままでもプログラムが作れるのかと疑問に思っていたのです。
どうやら、PCMWAVEFORMAT構造体などを使わなくても可能みたいですね。
慣れてきましたら、チャレンジしたいと思います。

本当に、ご丁寧な解説ありがとうございました。 画像

閉鎖

“C言語何でも質問掲示板” へ戻る