SoundEffectInstance - Stop() then Play() in WindowsDX different to WindowsGL

Hi,

I’ve encountered an issue in the WindowsDX build of MonoGame 3.8, where if I Stop a currently playing SoundEffectInstance and then Play it within the same frame (effectively, restarting it from the start), the sound effect doesn’t play. Only on the next run through the Update() loop can the sound play.

In comparison, under the DesktopGL build, you can Stop() then Play() the sound and it restarts correctly.

Here’s some basic code that replicates the issue.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Audio;

namespace SoundBug
{
    public class Game1 : Game
    {
        GraphicsDeviceManager _graphics;
        SoundEffectInstance _EffectInstance;

        KeyboardState _CurrentKeys;
        KeyboardState _PreviousKeys;

        public Game1()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            IsMouseVisible = true;
        }

        protected override void LoadContent()
        {
            SoundEffect effect = Content.Load<SoundEffect>("Medium Explosion");
            _EffectInstance = effect.CreateInstance();
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            _PreviousKeys = _CurrentKeys;
            _CurrentKeys = Keyboard.GetState();

            if (_CurrentKeys.IsKeyDown(Keys.F) && _PreviousKeys.IsKeyUp(Keys.F))
            {
                if (_EffectInstance.State != SoundState.Stopped)
                {
                    _EffectInstance.Stop();
                }

                _EffectInstance.Play();
            }

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            base.Draw(gameTime);
        }
    }
}

facing same issue. My code:

public void Play(bool fromStart = false)
{
    paused = false;
    playing = true;

    LateUpdate();

    

    if(fromStart)
    {
        soundEffectInstance?.Stop(true);
    }
    try
    {
        soundEffectInstance?.Play();
    }
    catch (Exception) { }
}

You might consider logging an issue on github for this.

As a workaround, if you don’t mind a single frame delay (which is probably not a huge issue for audio), you could probably use a coroutine library and do something like…

public void Play(bool fromStart = false)
{
  StartCoroutine(HandlePlay(fromStart));
}

private IEnumerator HandlePlay(bool fromStart)
{
  if (fromStart)
  {
    soundEffectInstance?.Stop(true);
    yield return null;
  }

  soundEffectInstance?.Play();
}

I don’t know of a generic C# coroutine library offhand (but I bet google does!), but the idea is that you fire off a special function that will execute in a frame until a yield call. So if this needs to stop, it will do that on frame one, then play on frame two. If it doesn’t need to stop, it will play on frame 1.

Stuff like that :slight_smile:

This might be a coroutine library you could use, and it’s on nuget :slight_smile: