SlimDXでDirectSound

SlimDXではXAudio2に一歩遅れましたがDirectSoundも実装されています。
しかしSlimDXのサンプルには含まれていません。
Managed DirectXからの類推でやろうとしても結構違いがあります。
SlimDXのインターフェイスC++寄りです。


今回C++側のヘルプ・サンプルを参考にしながらSlimDXでDirectSoundを使うコードを書いたので、少ない日本語SlimDX情報の足しになればと公開します。
でもたぶん間違い・勘違いも含まれるので参考にする場合は注意してくださいね


ASoundクラスの使い方は、

  1. ASound.Open を関連付けるFormを引数に与え呼び出す。
  2. new ASound(<ファイル名>) でインスタンス作成。あとはPlayなりStopなり。
  3. インスタンスを使い終わったらDispose。
  4. ASound.Close を最後に呼び出し後始末。

となっています。
また、Loggerクラスは自作のログ出力クラスなので、このソースを利用する場合は削除や置き換えをしてください。
バグなど発見されましたらコメントいただけると幸い。

class ASound : IDisposable {
    static DirectSound.DirectSound directSound;
    SecondarySoundBuffer buffer;

    /// <summary>
    /// 音量を0.0f(最小:無音)〜1.0f(最大)の間で設定する。
    /// </summary>
    public float Volume {
        get {
            return (float)(buffer.Volume - DirectSound.Volume.Minimum) /
                (DirectSound.Volume.Maximum - DirectSound.Volume.Minimum);
        }
        set {
            if(value < 0) value = 0;
            if(1 < value) value = 1;
            buffer.Volume =
                (int)(value * (DirectSound.Volume.Maximum - DirectSound.Volume.Minimum) +
                    (int)DirectSound.Volume.Minimum);
        }
    }

    public static void Open(Form form) {
        Logger.WriteLine("音声機能の初期化を開始...");
        Logger.Indent();

        Logger.Write("DirectSoundオブジェクトの作成...");
        directSound = new SlimDX.DirectSound.DirectSound();
        Logger.WriteLine("完了");

        Logger.Write("優先度の設定...");
        directSound.SetCooperativeLevel(
            ADirect3D.Window.Handle, SlimDX.DirectSound.CooperativeLevel.Priority);
        Logger.WriteLine("完了");

        //SetPrimaryBufferFormat(2, 44100, 16); // 必要ないらしい。

        Logger.Unindent();
        Logger.WriteLine("...音声機能の初期化を完了");
    }

    public static void Close() {
        Logger.WriteLine("音声機能の終了処理を開始...");
        Logger.Indent();

        Debug.Assert(directSound != null);
        if(directSound != null) {
            Logger.Write("DirectSoundオブジェクトの破棄...");
            directSound.Dispose();
            directSound = null;
            Logger.WriteLine("完了");
        }

        Logger.Unindent();
        Logger.WriteLine("...音声機能の終了処理を完了");
    }

    public ASound(string fileName) {
        Logger.Write("音声ファイル:" + fileName + " の読み込み...");
        Logger.Indent();

        // Waveファイル読み込み
        using(WaveFile waveFile = new WaveFile(fileName)) {
            // Flagsの設定はてきとー。パンはそのうち使うかもしれない
            SoundBufferDescription description = new SoundBufferDescription();
            description.Flags = BufferFlags.ControlVolume | BufferFlags.ControlPan;
            description.Format = waveFile.Format;
            description.SizeInBytes = waveFile.Size;
            // セカンダリバッファ作成
            buffer = new SecondarySoundBuffer(directSound, description);

            // バッファにWaveファイルの内容を転送するため読み出し
            byte[] data = new byte[waveFile.Size];
            if(waveFile.Read(data, data.Length) != waveFile.Size) {
                Logger.WriteLine("!!! Waveファイルから全ての内容を読み出すことが出来ませんでした。 !!!");
            }
            // バッファとwavの大きさはそろえてあるのでLockFlags.EntireBufferに実効的な意味はない
            buffer.Write(data, 0, DirectSound.LockFlags.EntireBuffer);
        }
        // DXUTのSDKsound.cppではバッファがロストしたときの復旧に使用するため
        // Waveファイルを破棄していないが、今の実装ではロストしたらそのままなので破棄してしまう。

        Logger.Unindent();
        Logger.WriteLine("完了");
    }

    #region IDisposable メンバ
    public void Dispose() {
        if(buffer != null) {
            buffer.Dispose();
            buffer = null;
        }
    }
    #endregion

    public void Play() {
        if(buffer.Status == BufferStatus.Playing) {
            buffer.Stop();
        }
        buffer.CurrentPlayPosition = 0;
        buffer.Play(0, PlayFlags.None);
    }
    
    public void Stop() {
        buffer.Stop();
    }

    /// <summary>
    /// SDKドキュメントより
    /// > For applications that require specialized mixing or other effects not supported
    /// > by secondary buffers, DirectSound allows direct access to the primary buffer.
    /// この辺は参考にしたサイト・ソースでやっていたから作っておいたけど、なくても動く。
    /// </summary>
    public static void SetPrimaryBufferFormat(short channels, int frequency, short bitrate) {
        // 参考にしたもの:
        // http://dn.codegear.com/jp/article/20941
        // DirectX SDK(Aug 2007)のSDKsound.cpp

        SoundBufferDescription description = new SoundBufferDescription();
        description.Flags = BufferFlags.PrimaryBuffer;
        description.SizeInBytes = 0; // プライマリバッファは0でなければならないらしい
        description.Format = null; // nullable? C++はポインタであり同じくNULLでなきゃならんと

        Logger.Write("プライマリバッファを作成...");
        PrimarySoundBuffer primaryBuffer = new PrimarySoundBuffer(directSound, description);
        Logger.WriteLine("完了");

        WaveFormat wf = new WaveFormat();
        wf.FormatTag = WaveFormatTag.Pcm;
        wf.Channels = channels;
        wf.SamplesPerSecond = frequency;
        wf.BitsPerSample = bitrate;
        wf.BlockAlignment = (short)(wf.BitsPerSample / 8 * wf.Channels);
        wf.AverageBytesPerSecond = wf.SamplesPerSecond * wf.BlockAlignment;

        Logger.Write("プライマリバッファのフォーマットを設定...");
        primaryBuffer.Format = wf;
        Logger.WriteLine("完了");
    }
}