DirectSoundの音量とプチノイズ対策
音量の比率とdB変換
DirectSoundの音量は、0~-100dBのデシベル単位です。より正確には100倍したDSBVOLUME_MAX(0)~DSBVOLUME_MIN(-10000)の範囲で指定します。
デシベルだということを忘れて、音量を最大の半分にしようと思って-5000を指定すると、まったく音が聞こえなくなります。
デシベルでの音量半分は-6dBに相当しますので、最大の半分にするには-600を指定する必要があります。さらに半分最大の4分の1にするには-1200を指定します。
のように、半分にする度に-6dBしないといけないので、音量をdBで直接扱うのは少し面倒です。そこで、100(最大音量)~0(無音)の比率で指定できるようにします。
class volume { public: volume():nDB_(0){} int32_t db()const{ return nDB_; } void set_db(int32_t nDB){ if(nDB<=0) nDB_=nDB; } // 比率での取得。0~100 int32_t per()const { if(nDB_>=-600) return 50 + ((nDB_+600)*50)/600; else if(nDB_>=-1200) return 25 + ((nDB_+1200)*25)/600; else if(nDB_>=-1800) return 12 + ((nDB_+1800)*12)/600; else if(nDB_>=-2400) return 6 + ((nDB_+2400)*6)/600; else if(nDB_>=-3000) return 3 + ((nDB_+3000)*3)/600; else if(nDB_>=-3600) return 1 + ((nDB_+3600))/600; else return 0; } // 比率での設定。0~100 void set_per(int32_t nPer) { if(nPer>=50) {// 0 ~ -600 nDB_ = -600 + 600*(nPer-50)/50; } else if(nPer>=25) {// -600 ~ -1200 nDB_ = -1200 + 600*(nPer-25)/25; } else if(nPer>=12) {// -1200 ~ -1800 nDB_ = -1800 + 600*(nPer-12)/12; } else if(nPer>=6) {// -1800 ~ -2400 nDB_ = -2400 + 600*(nPer-6)/6; } else if(nPer>=3) {// -2400 ~ -3000 nDB_ = -3000 + 600*(nPer-3)/3; } else if(nPer>=1) {// -3000~-3600 nDB_ = -3600 + 600*(nPer-1); } else { nDB_ = DSBVOLUME_MIN; } } private: int32_t nDB_; };
てな感じのクラスを用意して、ゲーム側からは比率のset_perを使って音量を設定し、DirectSound側ではdbを使って取得するという形にします。
真面目にlogスケールの比率を使っても良いと思いますが、そこまで正確なスケールを使っても人間の耳にはわからないと思いますので、半分にするとこまでは線形変換にしてます。
単発再生のプチノイズ対策
SEなどの短い音を鳴らす時は、サウンドバッファに全部読み込んでしまって鳴らすことがあると思います。その際に再生終了の際にノイズがのることが良くあります。
DirectSoundが低レベルルーチン過ぎるために起きるようです。細かい仕組みについてはググって頂くことにして、ここではその対策を紹介します。
サウンドバッファを作る際に1ブロックだけ多く作成し、追加した1ブロックに無音(0)を書き込みます。
// pDriverは初期化済みLPDIRECTSOUND8 // WAVEフォーマット設定 WAVEFORMATEX wvFmt; wvFmt.cbSize = sizeof(wvFmt); wvFmt.wFormatTag = WAVE_FORMAT_PCM; wvFmt.nChannels = 2; wvFmt.nSamplesPerSec = 44100; wvFmt.wBitsPerSample = 16; wvFmt.nBlockAlign = wvFmt.nChannels * wvFmt.wBitsPerSample / 8; wvFmt.nAvgBytesPerSec = wvFmt.nSamplesPerSec * wvFmt.nBlockAlign; // DirectSoundBufferの初期化構造体 DSBUFFERDESC desc; ::ZeroMemory(&desc, sizeof(desc)); desc.dwSize = sizeof(desc); desc.dwFlags = DSBCAPS_LOCSOFTWARE; // フラグは適当に desc.lpwfxFormat = &wvFmt; // nSoundSizeはサウンドデータのサイズとする desc.dwBufferBytes = nSoundSize + wvFmt.nBlockAlign; // 1ブロック追加 // サウンドバッファ作成 LPDIRECTSOUNDBUFFER pBuffer; pDriver->CreateSoundBuffer(&desc, &pBuffer, NULL); LPDIRECTSOUNDBUFFER8 pSoundBuffer; pBuffer->QueryInterface(IID_IDirectSoundBuffer8, reinterpret_cast<void**>(&pSoundBuffer)); // サウンドデータ書き込み void* pData; DWORD nLen; pSoundBuffer->Lock(0,0,&pData,&nLen,NULL,NULL,DSBLOCK_ENTIREBUFFER); // サウンドデータ本体書き込み memcpy(pData, /*サウンドデータへのポインタ*/, nSoundSize); // 追加した1ブロックに無音(0)を書き込む memset(&pData[nSoundSize], 0, wvFmt.nBlockAlign); pSoundBuffer->Unlock(pData, nLen, NULL, 0);
無音のサウンドをループ再生し続けるというやり方もあるそうなので、お好みで