GameProgrammar's Night

ゲームプログラム系の覚え書き

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);

 無音のサウンドをループ再生し続けるというやり方もあるそうなので、お好みで