LoginSignup
5
7

More than 3 years have passed since last update.

C++によるWAVEファイルの読み込みとLoaderクラスの設計

Last updated at Posted at 2019-05-20

参考書籍

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の範囲にするのは波形を編集しやすくするためです。
問題点は波形データの読み込みの際、読み込み用の一時変数のせいでメモリ使用率が一瞬高くなってしまう点です。
この部分の良い方法があれば教えてほしいです。

5
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7