2009年3月8日日曜日

[PC][プログラム][C++]WAVEファイルを扱うクラスを作ってみた

割とよく使う割に、自前でちゃんとしたクラスを用意していなかったということで、ちょっくら自前で書いてみた。
リニアPCM限定、かつ先頭から44byteがヘッダであること前提というかなり手抜きなものですが、まぁさして実用にて困ることもないでしょう。
意外とWAVEファイルを扱うクラスの実装って少ないなーって思ってたんですが、単にWAVEファイルと言えども、その実、中身が結構異っているため、実装は簡単ではないみたいですね^^;
Windowsだったらwinmm.dllでしたっけか、あれのWAVEHEADEREXか忘れましたが、定義済みの構造体を使って、ついでにAPIも使う方が楽だとかなんとか。
とは言え、私はWinAPIが大嫌いなもので、それを使いたくなかったんですよねぇ。それに、扱うのはリニアPCMにほぼ限定されますし(せいぜい、32bit floatのWAVEファイルくらい)。
ってな訳で、これまで都度てけとーに関数やらクラスを書いてたのですが、いい加減まじめに作ってみようということでやってみました。
昨晩と今日の昼で書き上げたのですが、まだ動作チェックしてません…。したらボロが出そうだ。
ちなみに、今回使ってみたIDEはMonoDevelopです。本来はC#のオープンソース実装であるMonoの開発用なんですが、一応C/C++も開発できます。
見た目はVC++っぽく、最初は
「お、いい感じじゃん!」
と思ったのですが、悲しいかなインテリセンスが発生しませんでしたOrz
確かC#の開発のときには出てたと思うので、C/C++では出ないということだと思います。ちょっと残念。まぁC/C++開発はオマケってことでしょう。見た目はかなりいい感じなんだけどなぁ。
C/C++の開発はKDevelopとかEclipse + CDT、最近だったらQtDevelopとかになるんかな? WindowsだったらVC++EE一択なんだけどw
ところで、ofstreamをios::binaryで開いたときって、>>演算子で値の取得ってできないんですかね? コンパイルエラーは出ないんですが、値は取得できてませんでした。
まぁ、readメソッドでいいっちゃいいんですが、アレは第1引数に(char*)を要求するんですよね。binaryで開いてるんだし、(void*)でいいじゃねーかと思わなくもない。きっとなにか理由はあるんでしょうけどね^^;
さて、次は何を作りましょうかね?


せっかくなので晒してみる。
*WaveHeader.h

/** @file WaveHeader.h
    @brief WAVEファイルのヘッダ構造体
    @version 1.0
    @author code_air_edge
    @date 09/03/06
*/
/** @brief WAVEヘッダ構造体
*/
struct WaveHeader
{
    char RIFFTag[4]; //!< RIFFヘッダタグ
    int nFileSize; //!< これ以降のファイルサイズ(ファイルサイズー8)
    char WAVETag[4]; //!< WAVEヘッダタグ
    char fmtTag[4]; //!< fmtチャンクタグ
    int nFmtSize; //!< fmtチャンクのサイズ
    short int shFmtID; //!< フォーマットID
    short int shCh; //!< チャネル数
    int nSampleRate; //!< サンプリングレート
    int nBytePerSec; //!< データ速度
    short int shBlockSize; //!< ブロックサイズ
    short int shBitPerSample; //!< サンプルあたりのビット数
    char dataTag[4]; //!< dataチャンクタグ
    int nBytesData; //!< 波形データサイズ
};


*WaveStream.h

/** @file WaveStream.h
    @brief WaveStreamクラス
    fstreamを継承したWAVEファイルを扱うクラス
    @version 1.0
    @author code_air_edge
    @date 09/03/06
*/
#pragma once
#include <fstream>
#include "WaveHeader.h"
using namespace std;
/** @brief WaveStreamクラス
*/
class WaveStream
{
    protected:
    WaveHeader header; //!< WAVEヘッダ
    char *waveData; //!< 波形データ
    fstream stream; //!< ファイルストリーム
    ios::open_mode flag; //!< RWフラグ
    public:
    WaveStream(); //!< コンストラクタ
    /** @brief 引数付きコンストラクタ
        @param [in] filename WAVEファイル名
        @param [in] flag 入出力フラグ
        @exception 読み込みモード時、WAVEファイルでなければ例外発生
    */
    WaveStream(char *filename, ios::open_mode flag);
    ~WaveStream(); //!< デストラクタ
    /** @brief WAVEファイルオープン
        @param [in] filename WAVEファイル名
        @param [in] flag 入出力フラグ
        @exception 読み込みモード時、WAVEファイルでなければ例外発生
    */
    void Open(char *filename, ios::open_mode flag);
    /** @brief WAVEファイルクローズ
    */
    void Close();
    /** @brief WAVEファイルを書き出し
        @exception 波形データがなければ例外発生
        @exception 書き出しモードでWAVEファイルをオープンしていなければ例外発生
    */
    void Write();
    /** @brief WAVEヘッダ取得
        @return WAVEヘッダ
        @exception 読み込みモードでWAVEファイルをオープンしていなければ例外発生
    */
    WaveHeader GetHeader();
    /** @brief WAVEヘッダセット
        @param[in] header WAVEヘッダ
        @exception 書き出しモードでWAVEファイルをオープンしていなければ例外発生
    */
    void SetHeader(WaveHeader *header);
    /** @brief 波形データ取得
        予め必要な領域は確保しておくこと
        @param [out] waveData 波形データ
        @exception 読み込みモードでWAVEファイルをオープンしていなければ例外発生
    */
    void GetWaveData(char *waveData);
    /** @brief 波形データセット
        予めヘッダをセットしておくこと。
        @param[in] waveData 波形データ
        @exception 書き出しモードでWAVEファイルをオープンしていなければ例外発生
    */
    void SetWaveData(char *waveData);   
};


*WaveStream.cpp

/** @file WaveStream.cpp
*/
#include "WaveStream.h"
#include <cstring>
#include <fstream>
using namespace std;
WaveStream::WaveStream()
{
    memset(&header, 0, sizeof(WaveHeader));
    waveData = NULL;
}
WaveStream::WaveStream(char *filename, ios::open_mode flag)
{
    this->Open(filename, flag);
}
WaveStream::~WaveStream()
{
    if(waveData)
    {
        delete [] waveData;
    }
    if(stream.is_open())
    {
        stream.close();
    }
}
void WaveStream::Open(char *filename, ios::open_mode flag)
{
    if(flag != ios::in && flag != ios::out)
    {
        throw "フラグはin,outのいずれかである必要があります";
    }
    stream.open(filename, ios::binary | (flag == ios::in) ? ios::in : ios::out);
    this->flag = flag;
   
    if(flag == ios::out)
    {
        memset(&header, 0, sizeof(WaveHeader));
        return;
    }
    stream.read((char *)&header, sizeof(WaveHeader));
    if(strncmp(header.RIFFTag, "RIFF", 4))
    {
        stream.close();
        throw "WAVEファイルではありません";
    }
    if(strncmp(header.WAVETag, "WAVE", 4))
    {
        stream.close();
        throw "WAVEファイルではありません";
    }
    if(strncmp(header.fmtTag, "fmt ", 4))
    {
        stream.close();
        throw "リニPCM WAVEファイルではありません";
    }
    if(header.shFmtID != 1)
    {
        stream.close();
        throw "リニアPCM WAVEファイルではありません";
    }
   
    waveData = new char[header.nBytesData];
    stream.read(waveData, header.nBytesData);
}
void WaveStream::Close()
{
    if(stream.is_open())
    {
        stream.close();
    }
    if(waveData)
    {
        delete [] waveData;
    }
}
void WaveStream::Write()
{
    if(!stream.is_open())
    {
        throw "ファイルハンドルが開かれていません";
    }
    if(flag != ios::out)
    {
        stream.close();
        throw "読み込みモードで開かれています";
    }
    if(!waveData)
    {
        stream.close();
        throw "波形データがありません";
    }
    stream.write((char *)&header, sizeof(WaveHeader));
    stream.write(waveData, header.nBytesData);
}
WaveHeader WaveStream::GetHeader()
{
    if(!stream.is_open())
    {
        throw "ファイルハンドルが開かれていません";
    }
    if(flag != ios::in)
    {
        stream.close();
        throw "書き出しモードで開かれています";
    }
    return header;
}
void WaveStream::SetHeader(WaveHeader *header)
{
    if(!stream.is_open())
    {
        throw "ファイルハンドルが開かれていません";
    }
    if(flag != ios::out)
    {
        stream.close();
        throw "読み込みモードで開かれています";
    }
    memcpy(&(this->header), header, sizeof(WaveHeader));
}
void WaveStream::GetWaveData(char *waveData)
{
    if(!stream.is_open())
    {
        throw "ファイルハンドルが開かれていません";
    }
    if(flag != ios::in)
    {
        stream.close();
        throw "書き出しモードで開かれています";
    }
    memcpy(waveData, this->waveData, header.nBytesData);
}
void WaveStream::SetWaveData(char *waveData)
{
    if(!stream.is_open())
    {
        throw "ファイルハンドルが開かれていません";
    }
    if(flag != ios::out)
    {
        stream.close();
        throw "読み込みモードで開かれています";
    }
    memcpy(this->waveData, waveData, header.nBytesData);
}