鳥の巣箱

ネトゲしたり、機械いじったり、ソフト書いたり、山篭ったり、ギャンブルしたりする人

RIFFフォーマットファイルの読み込み

DelphiでWaveファイルを扱うためにRIFFフォーマットのファイルを読み込む必要がでてきたんで色々とメモ書き。


まず必要なものの説明。

MMSystemライブラリ

マルチメディア関連のAPIが各種はいっている。
uses句にこれを追加する必要あり。

MMRESULT型

MMSystem内の関数は一部、戻り値をこの型で返してくる。
中身はエラーコード。

TWaveFormatEx構造体

Wave形式のオーディオデータフォーマットが定義されている構造体。
この構造体のメンバは

メンバ名 データ型 詳細                     
wFromatTag WORD ウェーブフォームオーディオフォーマットのタイプ 
nChannel WORD チャンネル数、モノラルなら1Ch、ステレオなら2Ch
nSamplesPerSec DWORD サンプリング周波数、標本化周波数とも
nAvgBytesPerSec DWORD フォーマットタグで必要な平均データ転送速度
nBlockAlign WORD ブロックアライメント、フォーマットタイプのデータの最小単位
wBitesPerSample WORD 1サンプリングあたりのビット数
cbSize WORD WaveFormatEx構造体の後ろに追加されるフォーマット情報のサイズ

hMMIO型

オープンされているファイルのハンドルを格納するためのもの。

TMMCKINFO構造体

RIFFファイル内のチャンクに関する情報が定義されている。
TMMCKINFO構造体のメンバは

メンバ名 概要
ckid FOURCC チャンク識別子
cksize DWORD チャンクのサイズ
fccType FOURCC フォームのタイプ
dwDataOffset DWORD Discend、Ascendを行ったあとのファイル上での位置
dwFlags DWORD 原則0が格納

mmioOpen関数

入出力をバッファリングしてファイルを開くための関数。
パラメーターは全部で3つ。

mmioOpen(szFilename,lpmmioinfo,dwOpenFlags);
パラメータ データ型 概要
szFilename LPSTR 開くファイルのファイル名がはいった文字列のアドレスを指定
lpmmioinfo LPMMIOINFO ファイルを開くためにインストールされていないI/Oプロシージャを指定する場合以外はnil指定
dwOpenGlags DWORD オープン操作のためのフラグを指定
MMIO_ALLOCBUF : ファイルを不バッファリングされた入出力用に開く
MMIO_COMPAT : ファイルを互換モードで開き、指定されたマシン上の全てのプロセスでそのファイルを何度も開くことができるようにする
MMIO_CREATE : 新規ファイルを作成する
MMIO_DELETE : ファイルを削除する
MMIO_DENYONE : 他のプロセスからのファイルの読み書きを拒否せず開く
MMIO_DENYWRITE : ファイルに対する他のプロセスからの読み取りを拒否して開く
MMIO_EXCLUSIVE : ファイルに対する他のプロセスからの読み書きアクセスを拒否して開く
MMIO_EXIST : 指定されたファイルが存在するかどうかを調べ、szFilenameパラメータで指定したパスからファイル名を作成
MMIO_GETTEMP : szFilenameパラメータに渡されるパラメータを暫定的に使いテンポラリファイル名を作成
MMIO_PARSE : MMIO_EXISTに近いが存在するかどうかを調べない
MMIO_READ : ファイルを読み取り専用として開く
MMIO_READWRITE : ファイルを読み書き用に開く
MMIO_WRITE : ファイルを書き込み専用として開く

関数が成功すると、開いたファイルのハンドルが返ってくる。

mmioStringToFOURCC関数

NULLで終わる文字列を4文字コードに変換する。
第1引数に変換したい文字列、第2引数はフラグを指定するが「MMIO_TOUPPER」の1種のみ。
フラグを指定するとすべての文字が大文字に変換される。変換が必要ない場合、パラメータは0でよい。

mmioAscend関数

mmioDescend関数で侵入、またはmmioCreateChunk関数で作成したRIFFファイルのチャンクから退出する関数。

mmioAscend(hmmio,lpck,wFlags);
パラメータ データ型 概要
hmmio HMMIO 開いてるRIFFファイルのハンドルを指定
lpck LPMMCKINFO mmioDescend関数またはmmioCreateChunk関数で値が書き込まれている、アプリケーション定義のMMCKINFO構造体のアドレスを指定
wFlags UNIT 予約されているので0を指定

関数が成功するとMMSYSERR_NOERRORが返る。

mmioDescend関数

mmioOpen関数で開いたRIFFファイルのチャンクへ進入する。または指定されたチャンクを検索する。

mmioDescend(hmmio,lpck,lpckParent,wFlags);
パラメータ データ型 概要
hmmio HMMIO 開いてるRIFFファイルのハンドルを指定
lpck LPMMCKINFO アプリケーション定義のMMCKINFO構造体のアドレスを指定
lpckParent LPMMCKINFO 検索するチャンクの親を識別するためのMMCKINFO構造体のアドレスを指定
親チャンクが指定されてない場合nil指定
wFlags UNIT 検索フラグを指定する
MMIO_FINDCHUNK : 指定されたチャンク識別子のチャンクを検索
MMIO_FINDLIST : チャンク識別子がLISTで指定されたフォームタイプのチャンクを検索
MMIO_FINDRIFF : チャンク識別子がRIFFで指定されたフォームタイプのチャンクを検索

関数が成功するとMMSYSERR_NOERRORが返る。

mmioRead関数

mmioOpen関数で開いたファイルから指定されたバイト数を読み取る。

mmioRead(hmmio,pch,cch);
パラメータ データ型 概要
hmmio HMMIO 読み取るファイルのハンドルを指定
pch HPSTR ファイルから読み取られたデータが入るバッファのアドレスを指定
cch LONG ファイルから読み取るバイト数を指定

と、この辺を使えば読み込みが可能になる。
関数などのパラメータ等はかなり簡略化してまとめてある。
詳細はMSDNなどで関数名を調べれば出てくる。C系の書き方がしてあるが、書式を変えればそのまんまDelphiでも使える。
今書いてるソースの中から該当部分だけをピックアップするとこんな具合。

uses MMSystem;

//==============================//
//    FileOpenDialog呼び出し    //
//==============================//
procedure TForm1.BtOpenClick(Sender: TObject);
begin
  if OpenDialog1.Execute then
  begin
    wav_open(OpenDialog1.FileName);
  end;
end;

//========================================//
//    waveファイル読み込みプロシージャ    //
//========================================//
procedure TForm1.wav_open(name:string);
var
  x           : integer;
  mmres       : MMRESULT;
  PCM         : TWaveFormatEx;
  hIO         : hMMIO;
  RIFF_INFO   : TMMCKINFO;
  DATA_INFO   : TMMCKINFO;
  FMT_INFO    : TMMCKINFO;
  buf         : array[0 .. (1024 -1)] of Smallint;
  sz          : DWORD;
  remain      : DWORD;
  read_size   : DWORD;
  total_size  : DWORD;
  limit       : DWORD;
  WaveData1   : array of Smallint;
  WaveData2   : array of Smallint;

begin
  Ch1Index := 0;
  Ch2Index := 0;

  //  wavファイル読み込み
  hIO := mmioOpen(PWideChar(name),nil,MMIO_READ);
  try
    RIFF_INFO.fccType := mmioStringToFOURCC('WAVE',0);
    mmres := mmioDescend(hIO, @RIFF_INFO, nil, MMIO_FINDRIFF);
    if(mmres <> MMSYSERR_NOERROR) then
    begin
      Memo1.Lines.Add('ERROR');
      Exit;
    end;

    FMT_INFO.ckid := mmioStringTOFOURCC('fmt', 0);
    mmres := mmioDescend(hIO, @FMT_INFO, @RIFF_INFO, MMIO_FINDCHUNK);
    if(mmres <> MMSYSERR_NOERROR) then
    begin
      Memo1.Lines.Add('FMT CHUNK ERROR');
      Exit;
    end;

    sz := mmioRead(hIO, @PCM, FMT_INFO.cksize);
    if sz <> FMT_INFO.cksize then
    begin
      Memo1.Lines.Add('Wave Format ERROR');
      Exit;
    end;

    DATA_INFO.ckid := mmioStringTOFOURCC('data',0);
    mmres := mmioDescend(hIO, @DATA_INFO, @RIFF_INFO, MMIO_FINDCHUNK);
    if (mmres <> MMSYSERR_NOERROR) then
    begin
      Memo1.Lines.Add('Data Chunk Error');
      exit;
    end;

    remain := DATA_INFO.cksize;     // waveファイルのデータ数
    read_size := Length(buf) * sizeof(Smallint);
    total_size := 0;
    
    //  読み込んだデータを格納するための配列の長さを決定
    //  2chあるためデータ総数div2
    SetLength(WaveData1,DATA_INFO.cksize div 2);
    SetLength(WaveData2,DATA_INFO.cksize div 2);

    while remain > 0 do
    begin
      if read_size > remain then
        read_size := remain;

        mmioRead(hIO, @buf, read_size);
        for x := 0 to (read_size div (sizeof(Smallint))) - 1 do
        begin
          if (x mod 2) = 0 then
            begin
              WaveData1[Ch1Index] := buf[x];
              inc(Ch1Index);
            end
          else
            begin
              WaveData2[Ch2Index] := buf[x];
              inc(Ch2Index);
            end;
        end;

        inc(total_size, read_size);
        dec(remain, read_size);

        if limit > 0 then
        begin
          if total_size >= limit then
            Break;
        end;
    end;

    mmioAscend(hIO, @DATA_INFO, 0);

    mmioAscend(hIO, @RIFF_INFO, 0);
  finally
    mmioClose(hIO, 0);
  end;
end;

ざっくり流れとしては
1.ファイル展開
2.チャンクを検索してWaveファイルかどうか調べる
3.チャンクを検索してフォーマットが正しいか調べる
3.チャンクを検索してデータフォーマットが正しいか調べる
4.配列の長さをデータ数に合わせる
5.チャンネルごとにデータを分けて格納していく
6.チャンクから退出
7.ファイルを閉じる

という感じ。
これだとWaveファイルをただ開いてデータを取り込むだけなので、追加で色々実装する必要はあり。
TWaveFormatEx構造体のメンバは結構良く使う。