DIRECTX1 DirectSound
DirectXにはWaveを演奏するためのCOMが用意されています。DirectSoundといいます。WavファイルはPlaySoundで手軽に再生出来ますが、呼ぶたびにファイルから読み込むので若干の遅れがでます。DirectSoundは、あらかじめバッファに読み込んでおくので再生がすばやいのと、バッファの数を増やせば同時に再生できる音数も増えるという利点があります。
コーディングが面倒ですが、一度クラス化しておけば、PlaySoundと同じ手軽さで扱うことができます。
では、ここでもクラスとしてDirectSoundを使えるようにしてみます。
テストに使うプロジェクトは、ダイアログか、FormViewにしましょう。ボタンを押すと音が再生されるようにします。作ったクラスは、ファイルさえコピーすれば別のアプリにもクラスとして使えるようになります。
プロジェクトが出来たら、ボタンを4個つけてください。
1個めは、DirectSoundの初期化用です。
2個めと3個めは、Waveファイルを鳴らすためのボタン、
4個めは、再生を止めるためのボタンです。
プロジェクトが出来たら、新規クラスを作成します。名前は、CDxSoundにしましょう。CDxSound.hに以下のヘッダをインクルードします。
#include "mmsystem.h"
#include "dsound.h"
必要なメンバ変数、およびメンバ関数は、以下の通り。
青いものがメンバ変数、赤いものがメンバ関数です。
class CDxSound
{
public:
LPDIRECTSOUND lpDS; ----------------------DirectSoundのオブジェクト
LPDIRECTSOUNDBUFFER lpDSBuffer;-----------プライマリバッファ
LPDIRECTSOUNDBUFFER lpDSSecond[6];-------セカンダリバッファが6個(6音同時発音可能)
HRESULT InitDirectSound(HWND hWnd);---------DirectSoundオブジェクトの初期化
HRESULT LoadWave( const HWND hWnd,--------Wavファイルからバッファに読み込む
const int num,
char* pName); // HWND BufferNum
void WavePlay(int num);----------------------指定したバッファにある音を再生する
CDxSound();
virtual ~CDxSound();
};
関数本体のほうは、以下のインクルードファイルを入れてください。#include "wavesoundread.h"はWavファイルを読み込むためのクラスで、後述します。MMSYSTEMはおなじみのマルチメディアヘッダです。
#include "wavesoundread.h"
#include "mmsystem.h"
DirectSoundオブジェクトの初期化は以下の通り。大まかな説明は書いてありますが、細かいことはDirectXのヘルプを参照してください。
HRESULT CDxSound::InitDirectSound(HWND hWnd)
{
// DirectSoundオブジェクトを初期化する
HRESULT ret = DirectSoundCreate(NULL, &lpDS, NULL);
if(ret != DS_OK){
AfxMessageBox("DirectSoundの初期化に失敗");
return ret;
}
// DirectSound 協調レベルをセットする
ret = lpDS->SetCooperativeLevel(hWnd, DSSCL_PRIORITY);
if(ret != DS_OK){
AfxMessageBox("CorperativLevel の設定に失敗");
return ret;
}
// プライマリバッファの作成
DSBUFFERDESC dsbdesc;
// DSBUFFERDESC構造体を設定する
ZeroMemory(& dsbdesc, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
// プライマリバッファを指定
dsbdesc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLPAN;
dsbdesc.dwBufferBytes = 0;
dsbdesc.lpwfxFormat = NULL;
// バッファを作る
ret = lpDS->CreateSoundBuffer(&dsbdesc, &lpDSBuffer, NULL);
if(ret != DS_OK){
AfxMessageBox("SoundBuffer の作成に失敗");
return ret;
}
// プライマリバッファのWaveフォーマットを設定
WAVEFORMATEX pcmwf;
ZeroMemory(& pcmwf, sizeof(WAVEFORMATEX));
pcmwf.wFormatTag = WAVE_FORMAT_PCM;
pcmwf.nChannels = 2;
pcmwf.nSamplesPerSec = 22050;
pcmwf.nBlockAlign = 4;
pcmwf.nAvgBytesPerSec = pcmwf.nSamplesPerSec * pcmwf.nBlockAlign;
pcmwf.wBitsPerSample = 16;
ret = lpDSBuffer->SetFormat(&pcmwf);
if (FAILED(ret)){
AfxMessageBox("プライマリBufferのフォーマットセットに失敗");
return false;
}
return ret;
}
次はWaveファイルを読み込む部分です。これはファイルから読み込むタイプですね。バッファはあらかじめ6個作れるようになっているので、ファイル名、バッファの番号を指定すると指定されたバッファに読み込むようにしてみました。
//----- Wavファイルを読み込んでバッファを作る
HRESULT CDxSound::LoadWave(const HWND hWnd,
const int num, // Buffer番号
char* pName) // ファイル名
{
HRESULT ret;
DSBUFFERDESC dsbdesc;
// クラスの作成
CWaveSoundRead *pWaveFile = new CWaveSoundRead();
// Wavファイルを開く
ret = pWaveFile->Open(pName);
if (ret != DS_OK){
AfxMessageBox("Waveファイルのオープンに失敗");
return false;
}
// 読み込み開始位置をファイルの先頭に設定する
pWaveFile->Reset();
// WAV データを読み込む
UINT datasize = pWaveFile->m_ckIn.cksize; // WaveFileのサイズを決める
BYTE* wavedata = new BYTE[datasize]; // Waveデータ用バッファ
UINT readsize;
pWaveFile->Read(datasize,
wavedata,
&readsize);
// DSBUFFERDESC 構造体を設定する
ZeroMemory(&dsbdesc, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 |
DSBCAPS_GLOBALFOCUS |
DSBCAPS_LOCDEFER |
DSBCAPS_CTRLPAN | // パンコントロールを使う
DSBCAPS_CTRLFREQUENCY | // 周波数コントロールを使う
DSBCAPS_CTRLVOLUME; // ボリュームコントロールを使う
dsbdesc.dwBufferBytes = datasize;
dsbdesc.lpwfxFormat = pWaveFile->m_pwfx;
// バッファを作る
ret = lpDS->CreateSoundBuffer(&dsbdesc, &lpDSSecond[num], NULL);
if(ret != DS_OK){
AfxMessageBox("バッファの作成失敗3");
pWaveFile->Close(); // ファイルを閉じる
delete pWaveFile;
return -1;
}
// バッファをロックする
LPVOID pMem1, pMem2;
DWORD dwSize1, dwSize2;
ret = lpDSSecond[num]->Lock(0, datasize, &pMem1, &dwSize1, &pMem2, &dwSize2,0);
if(ret != DS_OK){
AfxMessageBox("ロック失敗");
pWaveFile->Close(); // ファイルを閉じる
delete pWaveFile;
return -1;
}
// バッファにコピーする
CopyMemory(pMem1, wavedata, dwSize1);
if(dwSize2 != 0)
CopyMemory(pMem2, wavedata + dwSize1, dwSize2);
// 速やかにLockを解除する
ret = lpDSBuffer->Unlock(pMem1, dwSize1, pMem2, dwSize2);
// ファイルを閉じる
pWaveFile->Close();
delete pWaveFile;
return ret;
}
読み込んだ後は再生します。
再生は、バッファのデータをPlayすればよいので、ボタンのハンドラから直接呼んでも良いのですが、DirexctSoundが初期化されていないといきなりエラーになるので、やはり再生用の関数も作った方が無難でしょう。簡単ですが、以下のように初期化を確認してから、演奏します。
void CDxSound::WavePlay(int num)
{
HRESULT ret;
if(lpDS != NULL){ // DirectSoundオブジェクトがあるか
if(lpDSSecond[num] != NULL){// バッファにデータは読み込まれているか
lpDSSecond[1]->Play(0,0,DSBPLAY_LOOPING); // 再生開始
}
}
}
これでDirectSoundクラスの初歩的なものが完成です。アプリから演奏する方法は、まず、以下のコードで初期化と読み込みを行います。ボタン1を押したときに初期化、読み込みします。
void CDxs4View::OnButton1()
{
// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
dxs.InitDirectSound(m_hWnd);
dxs.LoadWave(m_hWnd,0,"test.wav");
dxs.LoadWave(m_hWnd,1,"uy.wav");
}
ファイル名は、test.wav, yu.wav になっているので、適当なWAVファイルをプロジェクトのディレクトリにコピーして名前も変更してください。これで演奏可能になりました。
演奏開始のコードは簡単です。
void CDxs4View::OnButton3()
{
// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
dxs.WavePlay(1);
}
あと、メンバ関数は作っていませんが、主な機能をあげておきます。
dxs.lpDSSecond[1]->SetPan(DSBPAN_RIGHT); //左右バランス調整 -10000 〜 10000
dxs.lpDSSecond[1]->SetFrequency(5000); // 周波数の変更 100 〜 100000
dxs.lpDSSecond[1]->SetVolume(-1000); // ボリュームの変更 0〜-10000
関数の数値を変更してどのように変化するか見てください。それから詳しい説明を読むと理解が早いと思います。