前章WAVEファイルの再生 第1部の続きです。
ファイル保存時に保存経過情報などを、コールバックするための関数ポインタです。
typedef BOOL (__stdcall *SAVECALLBACK)(int percent,LPCTSTR lpszMessage,LPVOID lpParam);
WAVEファイルから音源データを時系列で読み込み、演奏管理するためのCWaveFileクラスを独自に定義しました。
// CWaveFileクラス
class CWaveFile : public CWaveOut
{
public:
//コンストラクタ
CWaveFile(CWnd* pWndCallBack=NULL);
//デストラクタ
virtual ~CWaveFile();
//メンバー変数の初期化
void Init();
//演奏の停止して、読み込み中のファイルを閉じます。(オーバーライド)
void Delete(BOOL bDeleteBaseClass=FALSE);
//Waveファイルがオープンされているかどうか?(オーバーライド)
virtual BOOL IsWaveFile() const{return (m_hmmio!=0);};
//読み込み中のデバイスハンドルが有効か?(オーバーライド)
virtual BOOL IsOpen() const{return IsWaveFile();};
//オーディオファイルを開きます。
BOOL OpenFile(LPCTSTR lpszFileName,BOOL bDeleteMemberParam=TRUE);
//Wave音源ストリームを読み込みます。(オーバーライド)
// pIndexSample:現在の演奏位置変数へのポインタ
virtual UINT StreamIn(LPVOID pBuffer,UINT nBufSize,UINT* pIndexSample);
//基底クラスに音源ストリームを引渡します。(オーバーライド)
virtual UINT StreamIn(LPVOID pBuffer,UINT nBufSize);
//現在演奏中の音源データをオーディオファイルに書き込みます。(重ね書き可)
BOOL SaveFile(LPCTSTR lpszFileName,SAVECALLBACK lpfnCallBack,LPVOID lpParam);
//現在演奏中の音源データをWAVEファイルの書き込みます。(重ね書き不可&オーバーライド)
virtual BOOL WriteFile(LPCTSTR lpszFileName);
//現在演奏中のファイルを重ね書きします。
BOOL OverWriteFile(){return SaveFile(m_strPathName,NULL,NULL);};
//演奏を開始します。
// timePerOut:一回に演奏する時間(_秒)
BOOL Play(UINT timePerOut=100){return CWaveOut::Play(&m_wfx,timePerOut);};
//演奏を停止します。
void Stop(UINT indexSample=-1);
//演奏位置を設定します。(オーバーライド)
virtual void SetPos(UINT nPos);
//演奏位置を取得します。(オーバーライド)
virtual UINT GetPos() const{return m_indexSample;};
//音源データのバイト数を取得します。
UINT GetLength() const{return m_nDataSize;};
//演奏予定の音源データ数を取得します。
UINT GetSamples() const{return (m_nDataSize)?m_nDataSize/m_wfx.nBlockAlign:0;};
//指定の演奏時間から演奏位置を設定します。
void SetPastTime(UINT nTime){m_indexSample=(UINT)(((double)nTime*(double)m_wfx.nSamplesPerSec/1000.0)+0.5);};
//現在の演奏時間を取得します。
UINT GetPastTime() const{return (UINT)((double)m_indexSample*1000.0/(double)m_wfx.nSamplesPerSec+0.5);};
//総演奏予定時間を取得します。(オーバーライド)
virtual UINT GetTotalTime() const{return (UINT)((double)GetSamples() const*1000.0/(double)m_wfx.nSamplesPerSec+0.5);};
//ファイルのWAVEFORMATEX構造体を取得します。
int GetWaveFormat(LPWAVEFORMATEX pwfx=NULL) const;
protected:
//WAVEファイルヘッダーを読み込み、WAVEFORMATEX構造体を取得します。
BOOL ReadHeader(HMMIO hmmio);
//Wave音源データをファイルストリームに書き込みます。(オーバーライド)
virtual BOOL WaveStreamOut(HMMIO hmmio);
//派生クラスから書き込み用のバッファサイズを取得します。(オーバーライド)
//(オーバーライドしない場合、Waveファイルの音源データを読み込むバッファサイズは、
//100_秒演奏するのに必要なサイズとします。)
virtual UINT GetSizeOfSaveBuffer() const{return m_wfx.nAvgBytesPerSec/10;};
//ファイル保存時に、メンバー変数に指定のあるコールバック関数を呼び出します。
// 戻り値=TRUE:保存継続、[FALSE]:保存中止。
BOOL SaveCallBack(int percent,LPCTSTR lpszMessage=NULL);
//データストリームをリセットします。(オーバーライド)
virtual void ResetStream(){Stop(0);};
//INFOチャンク内のメタデータを読み込みます。
BOOL ReadListINFO(HMMIO hmmio);
//INFOチャンクを新たに作成して、メタデータを書き込みます。
BOOL WriteListINFO(HMMIO hmmio);
HMMIO m_hmmio; //読み込みファイルハンドル
MMCKINFO m_mmckRiff; //現在読み込み中のRIFFチャンクのMMCKINFO構造体
MMCKINFO m_mmckData; //現在読み込み中のdataチャンクのMMCKINFO構造体
LONG m_posStart; //音源データの開始位置
WAVEFORMATEX m_wfx; //ファイルのWAVEFORMATEX構造体
UINT m_indexSample; //現在の演奏位置
UINT m_nDataSize; //wave音源データバイト数
CString m_strPathName; //現在演奏中のパス名
//ファイル保存時に呼び出される、コールバック関数へのポインタと引数を保存します。
SAVECALLBACK m_lpfnSaveCallBack;
LPVOID m_lpSaveParam;
CString m_strPostMsg; //コールバック関数にポストする文字列を一時格納します。
CString m_strFileType; //ファイル形式文字列を格納します。(例:"WAV")
public:
//現在演奏中のパス名の取得
LPCTSTR GetPathName() const{return m_strPathName;};
//現在演奏中のファイル形式文字列を取得します。
LPCTSTR GetFileType() const{return m_strFileType;};
/*
INFOチャンクタグ一覧
[ 0]:メディア元 ISRC
[ 1]:作成日 ICRD
[ 2]:アルバム名 IPRD
[ 3]:曲名 INAM
[ 4]:ジャンル IGNR
[ 5]:コメント ICMT
[ 6]:アーティスト IART
[ 7]:著作権代理人 ICMS
[ 8]:著作権情報 ICOP
[ 9]:録音エンジニア IENG
[10]:ソフトウェア ISFT
[11]:キーワード IKEY
[12]:エンコード技術者 ITCH
[13]:タイトル ISBJ
[14]:トラック番号 ITRK
*/
#define LIST_INFO_CHUNK_COUNT 15
protected:
//INFOチャンク文字列格納用CStringクラス
CString m_strChunkText[LIST_INFO_CHUNK_COUNT];
public:
//INFOチャンク文字列数の取得
UINT GetChunkCount() const{return LIST_INFO_CHUNK_COUNT;};
//タグ文字列の取得
LPCSTR GetChunkTagByIndex(UINT index) const;
//INFOチャンク文字列の取得
LPCTSTR GetChunkTextByIndex(UINT index) const;
//INFOチャンク文字列の検索
LPCTSTR FindChunkTextByTag(LPCSTR lpszTag) const;
//INFOチャンク文字列の設定
void SetChunkTextByTag(LPCSTR lpszTag,LPCTSTR lpszText);
//INFOチャンク文字列の設定
void SetChunkTextByIndex(UINT index,LPCTSTR lpszText);
//タグの詳細記述の取得
LPCTSTR ChunkDescription(LPCSTR lpszTag) const;
//INFOチャンク文字列の削除
void DeleteChunkByTag(LPCSTR lpszTag);
//INFOチャンク文字列の削除
void DeleteChunkByIndex(UINT index);
//すべてのINFOチャンク文字列の削除
void DeleteAllINFOChunk();
};
CWaveFileクラスのインライン関数です。
//演奏を停止します。
inline void CWaveFile::Stop(UINT indexSample)
{
//WAVE出力デバイスを停止します。
CWaveOut::Stop();
//引数が-1以外の場合にのみ、指定の演奏位置に移動します。
if (indexSample<-1) SetPos(indexSample);
}
//演奏位置を設定します。
inline void CWaveFile::SetPos(UINT nPos)
{
UINT nSamples=GetSamples();
m_indexSample=(nPos<nSamples)? nPos:nSamples;
}
//ファイルのWAVEFORMATEX構造体と、WAVE出力デバイスに引き渡すWAVEFORMATEX構造体を取得します。
inline int CWaveFile::GetWaveFormat(LPWAVEFORMATEX pwfx) const
{
//第一引数が有効な場合、第一引数の示す位置に
//ファイルのWAVEFORMATEX構造体をコピーします。
if (!::IsBadWritePtr(pwfx,sizeof(WAVEFORMATEX)))
::CopyMemory(pwfx,&m_wfx,sizeof(WAVEFORMATEX));
return sizeof(WAVEFORMATEX);
}
CWaveFileクラスのコンストラクタ/デストラクタです。
//コンストラクタ
CWaveFile::CWaveFile(CWnd* pWndCallBack)
:CWaveOut(pWndCallBack)
{
Init();
}
//デストラクタ
CWaveFile::~CWaveFile()
{
Delete(FALSE);
}
CWaveFileクラスのメンバ関数です。
//メンバ変数の初期化
void CWaveFile::Init()
{
m_hmmio=0;
::ZeroMemory(&m_mmckRiff,sizeof(MMCKINFO));
::ZeroMemory(&m_mmckData,sizeof(MMCKINFO));
::ZeroMemory(&m_wfx,sizeof(WAVEFORMATEX));
m_posStart=0;
m_indexSample=0;
m_nDataSize=0;
m_lpfnSaveCallBack=NULL;
m_lpSaveParam=NULL;
m_strPathName=_T("");
m_strPostMsg=_T("");
m_strFileType=_T("");
DeleteAllINFOChunk();
}
//演奏の停止して、読み込み中のファイルを閉じます。
void CWaveFile::Delete(BOOL bDeleteBaseClass)
{
//WAVE出力デバイスを停止します。
Stop();
//ファイルを読み込み中の場合。
if (m_hmmio){
//データチャンクから抜け出して、ファイルを閉じます。
if (m_posStart){
::mmioAscend(m_hmmio,&m_mmckData,0);
::mmioAscend(m_hmmio,&m_mmckRiff,0);
}
::mmioClose(m_hmmio,0);
}
//メンバ変数の初期化
Init();
}
マルチメディアファイル入出力関数を使ってWAVEファイルを開き、CWaveFile::ReadHeader関数を呼び出します。
//オーディオファイルを開きます。
BOOL CWaveFile::OpenFile(LPCTSTR lpszFileName,BOOL bDeleteMemberParam)
{
if (bDeleteMemberParam) Delete(TRUE);
//マルチメディア関数を使ってファイルを開きます。
HMMIO hmmio=::mmioOpen((LPTSTR)lpszFileName,NULL,MMIO_READ|MMIO_DENYNONE);
if (!hmmio) return FALSE;
//WAVEファイルヘッダーを読み込みます。
if (!ReadHeader(hmmio)){
::mmioClose(hmmio,0);
return FALSE;
}
//ファイルハンドルを保存します。
m_hmmio=hmmio;
//ファイルのパス名を保存しておきます。
m_strPathName=lpszFileName;
//ファイル形式を書き込んでおきます。
m_strFileType=_T("WAV");
return TRUE;
}
WAVEファイルからWAVEFORMATEX構造体を取得し、演奏のための準備をします。
//WAVEファイルヘッダーを読み込み、WAVEFORMATEX構造体を取得します。
BOOL CWaveFile::ReadHeader(HMMIO hmmio)
{
ASSERT(hmmio);
//WAVEファイルかのチェック
//RIFFチャンクに進入してWAVEチャンクを探します。
m_mmckRiff.fccType=::mmioStringToFOURCC("WAVE", 0);
MMRESULT mmr=::mmioDescend(hmmio,&m_mmckRiff,NULL,MMIO_FINDRIFF);
//WAVEチャンクがなければ、エラー終了します。
if (mmr!=MMSYSERR_NOERROR) return FALSE;
//INFOチャンク内のメタデータを読み込みます。
ReadListINFO(hmmio);
//fmtチャンクに進入します。
MMCKINFO mmckFmt;
mmckFmt.ckid=::mmioStringToFOURCC("fmt ",0);
mmr=::mmioDescend(hmmio,&mmckFmt,NULL,MMIO_FINDCHUNK);
//進入に失敗したら、エラー終了します。
if (mmr!=MMSYSERR_NOERROR) return FALSE;
//fmtチャンクを読み込む。
CByteArray byteFmt;
byteFmt.SetSize(mmckFmt.cksize);
LPWAVEFORMATEX pwfxSrc=(LPWAVEFORMATEX)byteFmt.GetData();
LONG size=::mmioRead(hmmio,(HPSTR)pwfxSrc,mmckFmt.cksize);
if (size!=mmckFmt.cksize) return FALSE;
::CopyMemory(&m_wfx,pwfxSrc,sizeof(WAVEFORMATEX));
//fmtチャンクから退出します。
if (::mmioAscend(hmmio,&mmckFmt,0)!=MMSYSERR_NOERROR) return FALSE;
//現在のファイルポインタの位置を保存します。
LONG mmioPos=::mmioSeek(hmmio,0,SEEK_CUR);
//factチャンクに進入します。
MMCKINFO mmckFact;
mmckFact.ckid=::mmioStringToFOURCC("fact ",0);
mmr=::mmioDescend(hmmio,&mmckFact,NULL,MMIO_FINDCHUNK);
//進入に成功した場合。
if (mmr==MMSYSERR_NOERROR){
//factチャンクを読み込む。
DWORD nSamples;
LONG size=::mmioRead(hmmio,(HPSTR)&nSamples,mmckFact.cksize);
if (size!=mmckFact.cksize) return FALSE;
m_nDataSize=nSamples*m_wfx.nBlockAlign;
//factチャンクから退出します。
if (::mmioAscend(hmmio,&mmckFact,0)!=MMSYSERR_NOERROR) return FALSE;
}
//進入に失敗した場合。
else {
m_nDataSize=0;
//元のファイルポインタの位置に戻します。
::mmioSeek(hmmio,mmioPos,SEEK_SET);
}
//dataチャンクに進入します。
m_mmckData.ckid=::mmioStringToFOURCC("data",0);
mmr=::mmioDescend(hmmio,&m_mmckData,NULL,MMIO_FINDCHUNK);
//進入に失敗したら、エラー終了します。
if (mmr!=MMSYSERR_NOERROR) return FALSE;
//音源データの先頭位置を保存します。
m_posStart=::mmioSeek(hmmio,0,SEEK_CUR);
//factチャンクがなかった場合。
if ((!m_nDataSize)||(m_nDataSize>m_mmckData.cksize)){
//音源データサイズにdataチャンクサイズを代入します。
m_nDataSize=m_mmckData.cksize;
//ファイル末尾に移動して、実際の音源データサイズを取得します。
LONG posEnd=::mmioSeek(hmmio,0,SEEK_END);
UINT sizeToEnd=posEnd-m_posStart;
//音源データサイズが不正な値の場合。
if ((!m_nDataSize)||(m_nDataSize>sizeToEnd))
//ファイル末尾までの実際の音源データサイズを代入します。
m_nDataSize=sizeToEnd;
}
return TRUE;
}
演奏中に基底クラスのCWaveOutクラスからバッファへの音源データ読み込み要求があるたびに呼び出される、WAVEストリーム読み込み関数です。
//基底クラスに音源ストリームを引渡します。(オーバーライド)
UINT CWaveFile::StreamIn(LPVOID pBuffer,UINT nBufSize)
{
ASSERT(!::IsBadWritePtr(pBuffer,nBufSize));
UINT readBytes=0;
//ファイルとWAVE出力デバイスのWAVEFORMATEX構造体要素のうち、
//サンプル当りのバイト数、1秒あたりのサンプル数、サンプル当りのビット数が
//いずれも等しければ、読み込んだ音源をそのまま返して関数を終了します。
if ((m_wfxOut.nBlockAlign==m_wfx.nBlockAlign)&&
(m_wfxOut.nSamplesPerSec==m_wfx.nSamplesPerSec)&&
(m_wfxOut.wBitsPerSample==m_wfx.wBitsPerSample))
{
readBytes=StreamIn(pBuffer,nBufSize,&m_indexSample);
}
else
{
//入力バッファのサイズから、加工前の入力バイト数を算出します。
UINT nSamples=nBufSize/m_wfxOut.nBlockAlign;
UINT inputBytes=nSamples*m_wfx.nBlockAlign;
//入力用バッファ(CByteArrayクラス)のサイズが入力バイト数より小さい場合、
//入力用バッファのサイズを入力バイト数に合わせます。
if ((UINT)m_byteInBuf.GetSize()<inputBytes)
m_byteInBuf.SetSize(inputBytes);
//入力用バッファへのポインタを取得します。
PBYTE pInBuf=m_byteInBuf.GetData();
//入力用バッファをゼロクリアします。
::ZeroMemory(pInBuf,inputBytes);
UINT indexSafe=m_indexSample;
UINT sizeBytes=StreamIn(pInBuf,inputBytes,&m_indexSample);
//音源データに擬似ステレオ加工を施します。
readBytes=DoStereo(pBuffer,nBufSize,indexSafe,sizeBytes,&m_wfx);
}
return readBytes;
}
//音源ストリームの読み込み(オーバーライド)
// pIndexSample://現在の演奏位置変数へのポインタ
UINT CWaveFile::StreamIn(LPVOID pBuffer,UINT nBufSize,UINT* pIndexSample)
{
ASSERT(!::IsBadWritePtr(pBuffer,nBufSize));
ASSERT(m_hmmio);
ASSERT(m_posStart);
ASSERT(::AfxIsValidAddress(pIndexSample,sizeof(UINT)));
//指定の演奏位置からファイル読み込み位置を算出して、
//その位置にシークします。
LONG offset=m_posStart+*pIndexSample*m_wfx.nBlockAlign;
LONG pos=::mmioSeek(m_hmmio,offset,SEEK_SET);
//シークに失敗したら、エラー終了します。
if (pos!=offset) return FALSE;
//データチャンク外にシークにしていたら、エラー終了します。
LONG endOfDataChunk=m_posStart+m_nDataSize;
if (pos>=endOfDataChunk) return FALSE;
//バッファに取り込むサイズを算定します。
LONG sizeToRead=min(nBufSize,(UINT)(endOfDataChunk-pos));
//dataチャンクの音源データを読み込みます。
LONG readBytes=::mmioRead(m_hmmio,(HPSTR)pBuffer,sizeToRead);
//読み込みに失敗したら、エラー終了します。
if (readBytes<m_wfx.nBlockAlign) return FALSE;
//指定の演奏位置を読み込んだ分だけ進めます。
*pIndexSample+=readBytes/m_wfx.nBlockAlign;
//読み込んだバイト数を返して終了します。
return readBytes;
}
CWaveFileクラスのインスタンスを作成した後、CWaveFile::OpenFile関数でWAVEファイルを開きます。エラーがなければCWaveFile::Play関数で演奏を開始します。演奏を中断したり演奏終了後、WAVEファイルとWAVE出力デバイスを閉じる場合は、CWaveFile::Stop関数を呼び出して下さい。
入力したWAVEファイルが演奏されれば成功です。
WaveOut.h
WaveOut.cpp
WaveFile.h
WaveFile.cpp