1

I am trying to make a musical instrument type of application. The problem I am having is that a new sound will only play if the old one is finished. I would like to be able to play them simultaneously.

This is how my code looks like:

First, the MyWave class which simply holds an audio buffer and some other info:

class MyWave
{
    public AudioBuffer Buffer { get; set; }
    public uint[] DecodedPacketsInfo { get; set; }
    public WaveFormat WaveFormat { get; set; }
}

In the SoundPlayer class:

    private XAudio2 xaudio;
    private MasteringVoice mvoice;
    Dictionary<string, MyWave> sounds;

    // Constructor
    public SoundPlayer()
    {
        xaudio = new XAudio2();
        xaudio.StartEngine();
        mvoice = new MasteringVoice(xaudio);
        sounds = new Dictionary<string, MyWave>();
    }

    // Reads a sound and puts it in the dictionary
    public void AddWave(string key, string filepath)
    {
        MyWave wave = new MyWave();

        var nativeFileStream = new NativeFileStream(filepath, NativeFileMode.Open, NativeFileAccess.Read, NativeFileShare.Read);
        var soundStream = new SoundStream(nativeFileStream);
        var buffer = new AudioBuffer() { Stream = soundStream, AudioBytes = (int)soundStream.Length, Flags = BufferFlags.EndOfStream };

        wave.Buffer = buffer;
        wave.DecodedPacketsInfo = soundStream.DecodedPacketsInfo;
        wave.WaveFormat = soundStream.Format;

        this.sounds.Add(key, wave);
    }

    // Plays the sound
    public void Play(string key)
    {
        if (!this.sounds.ContainsKey(key)) return;
        MyWave w = this.sounds[key];

        var sourceVoice = new SourceVoice(this.xaudio, w.WaveFormat);
        sourceVoice.SubmitSourceBuffer(w.Buffer, w.DecodedPacketsInfo);
        sourceVoice.Start();
    }
}

Google wasn't very helpful, I couldn't find anything useful. So how can I play multiple sounds simultaneously?

Tibi
  • 4,015
  • 8
  • 40
  • 64

2 Answers2

3

You'll have to create (and preferably pool) multiple SourceVoice instances and play them back simultaneously.

In fact, your current code should work, no? You may want to add a StreamEnd event listener to the SourceVoice to dispose of itself after playback is complete, and remember to enable callbacks when calling the constructor of the SourceVoice.

  • 1
    Yes, that is what I did. However, I read somewhere that I should not destroy the SourceVoice after I used it, but reuse it, so that is what I did. To play a sound, I check for a voice that has the `State.BuffersQueued` property equal to 0, that voice is not playing anything. If I don't find any empty voice, I pick one of these, stop it from playing, and play the new sound. – Tibi Feb 07 '13 at 15:23
  • Sorry for the long absence. In case you are working on the issue, "not destroying" the SourceVoice simply means that you should reuse the voice when possible, however one SourceVoice can still only play a single sound at a given time. My advice to you is to create an object pool for your SourceVoice instances, and use the StreamEnd event to return the voice to the pool when playback is stopped/complete, taking concurrency into account as StreamEnd is called from a separate thread. – Jason Dyman Low Mar 06 '13 at 12:29
  • Important note about "You may want to add a StreamEnd event listener to the SourceVoice to dispose of itself after playback is complete": A per the XAudio2 documentation, these callbacks (StreamEnd and others) are run on the same thread as the one playing audio, and if anything long happens in these callbacks, sound can stop playing momentarily. If disposing take time (more than a few milliseconds at most), then it should be done in a separate thread. Then again, maybe disposing is fast, I don't know, but worth noting anyway. – Mickael Bergeron Néron Jul 07 '22 at 07:52
0

Just use mciSendString with the open and play commands. This sample is playing note1.wav and note2.wav together at launch.

[System.Runtime.InteropServices.DllImport("winmm.dll")]
static extern Int32 mciSendString(string command,                                             
                                  StringBuilder buffer, 
                                  int bufferSize, 
                                  IntPtr hwndCallback);

        public frmGame()
        {
            InitializeComponent();
            DoubleBuffered = true;            
            mciSendString("open note1.wav type waveaudio  alias s1", null, 0, IntPtr.Zero);
            mciSendString("play s1", null, 0, IntPtr.Zero);
            mciSendString("open note2.wav type waveaudio  alias s2", null, 0, IntPtr.Zero);
            mciSendString("play s2", null, 0, IntPtr.Zero);
        }        
roulioz
  • 19
  • 1