#参考書籍
C言語ではじめる音のプログラミング
#WAVEフォーマット構造
- RIFFチャンク
変数名 | バイト数 | 説明 |
---|---|---|
id | 4 | "RIFF"が入っている |
size | 4 | ファイル全体のサイズ |
type | 4 | "WAVE"が入っている |
- FMTチャンク
変数名 | バイト数 | 説明 |
---|---|---|
id | 4 | "fmt "が入っている |
size | 4 | FMTチャンクのサイズ |
type | 2 | PCM音源を表す'1'が基本は入っている |
channel | 2 | チャンネル数 |
sample | 4 | サンプリング周波数 |
byte | 4 | 1回に処理するデータサイズ(sample*channel) |
block | 2 | 1サンプルのサイズ(bit/8*channel) |
bit | 2 | 量子化ビット数 |
- DATAチャンク
変数名 | バイト数 | 説明 |
---|---|---|
id | 4 | "data"が入っている |
size | 4 | DATAチャンクのサイズ |
data | size | 波形データ |
これらのチャンクを読み込み、必要な情報を保持しておく。 | ||
#実装 |
- 必要な情報を構造体にしておく
Info.h
namespace snd
{
//音声情報
struct Info {
unsigned short sample;
unsigned char bit;
unsigned char channel;
};
}
- 読み込みとデータ保持のクラス
Loader.h
#include "Info.h"
#include <string>
#include <vector>
#include <memory>
#include <unordered_map>
class Loader
{
public:
//デストラクタ
~Loader() {}
//読み込み
int Load(const std::string& fileName);
//音声情報取得
Info GetInfo(const std::string& fileName);
//波形データ取得
std::weak_ptr<std::vector<float>> GetData(const std::string& fileName);
//インスタンス変数の取得
static Loader& Get(void);
private:
//コンストラクタ
Loader() {}
Loader(const Loader&) = delete;
void operator=(const Loader&) = delete;
//サウンド情報
std::unordered_map<std::string, snd::Info>info;
// 波形データ
std::unordered_map<std::string, std::shared_ptr<std::vector<float>>>data;
}
これぐらいあれば大丈夫かな...
波形データはデータサイズが大きいのでコピーが走らないようにポインタで参照する。
Loader.cpp
int Loader::Load(const std::string& fileName)
{
//すでに読み込み済みなら正常終了
if (data.find(fileName) != data.end())
{
return 0;
}
return wav::Load(fileName, info[fileName], data[fileName]);
}
すでに読み込んでいるものはデータを使いまわす。
- WAVEの読み込み
WaveFmt.h
int Load(const std::string& fileName, snd::Info& info, std::shared_ptr<std::vector<float>>& outData)
{
FILE* file = nullptr;
//ファイルが参照できるかチェック
if (fopen_s(&file, fileName.c_str(), "rb") != 0)
{
return -1;
}
//RIFFチャンクの読み込み
RIFF riff{};
fread_s(&riff, sizeof(riff), sizeof(riff), 1, file);
//IDとTYPEが間違っていないかチェック
std::string id(&riff.id[0], sizeof(riff.id));
std::string type(&riff.type[0], sizeof(riff.type));
if (id != "RIFF" || type != "WAVE")
{
fclose(file);
return -1;
}
//FMTチャンクの読み込み
FMT fmt{};
fread_s(&fmt, sizeof(fmt), sizeof(fmt), 1, file);
//IDが間違っていないかチェック
id.assaign(&fmt.id[0], sizeof(fmt.id));
if (id != "fmt ")
{
fclose(file);
return -1;
}
//FMTチャンクには拡張部分がある場合があるので考慮する
std::vector<char>extended(fmt.size - (sizeof(fmt) - sizeof(fmt.id) - sizeof(fmt.size)));
fread_s(extended.data(), sizeof(char) * extended.size(), sizeof(char) * extended.size(), 1, file);
//DATAチャンクを見つける前に不要なチャンクがあるかもしれないのでループで飛ばす
while (true)
{
fread_s(id.data(), sizeof(char) * id.size(), sizeof(char) * id.size(), 1, file);
//DATAチャンク発見
if (id == "data")
{
break;
}
//その他のチャンク
else
{
long size = 0;
fread_s(&size, sizeof(size), sizeof(size), 1, file);
fseek(file, size, SEEK_CUR);
}
}
//DATAチャンクの読み込み
long size = 0;
fread_s(&size, sizeof(size), sizeof(size), 1, file);
//必要な情報を引数に格納
info = { unsigned short(fmt.sample), unsigned char(fmt.bit), unsigned char(fmt.channel) };
outData = std::make_shared<std::vector<float>>(size / (fmt.bit / 8));
//量子化ビット数によって読み込むバイト数が違うので分岐
switch (fmt.bit)
{
case 8:
{
std::vector<unsigned char>tmp(outData->size());
fread_s(tmp.data(), sizeof(tmp[0]) * tmp.size(), sizeof(tmp[0]) * tmp.size(), 1, file);
outData.assign(tmp.begin(), tnp.end());
//-1.0f~1.0fの範囲にする
for(float& i : *outData)
{
i = (i / float(0xff / 2)) - 1.0f;
}
break;
}
case 16:
{
std::vector<short>tmp(outData->size());
fread_s(tmp.data(), sizeof(tmp[0]) * tmp.size(), sizeof(tmp[0]) * tmp.size(), 1, file);
outData.assign(tmp.begin(), tmp.end());
//-1.0f~1.0fの範囲にする
for(float& i : *outData)
{
i /= float(0xffff / 2);
}
break;
}
default:
break;
}
fclose(file);
return 0;
}
自分なりのWAVEファイル(モノラル・ステレオ:8ビット・16ビット)の読み込み方法です。
-1.0f~1.0fの範囲にするのは波形を編集しやすくするためです。
問題点は波形データの読み込みの際、読み込み用の一時変数のせいでメモリ使用率が一瞬高くなってしまう点です。
この部分の良い方法があれば教えてほしいです。