Shawn Hargreaves Blog
One of the few breaking API changes between Game Studio 3.0 and 3.1 concerns the SoundEffect.Play method.
There are two different scenarios for playing sound effects:
We think it is important for the SoundEffect API to support both scenarios, and in Game Studio 3.0, we tried to make the SoundEffect.Play method handle them both. It returned a SoundEffectInstance object, which you could use for create & configure scenarios, but you could also just ignore this return value if you were doing fire & forget, in which case we did some magic to make sure the garbage collector would not try to reclaim the SoundEffectInstance while the sound was still playing.
Turns out, this didn't work as well as we expected. There were two problems:
In Game Studio 3.1, we changed the API to make these two usage scenarios explicit:
We removed the SoundEffect.Play3D method. To play a 3D sound, call SoundEffect.CreateInstance, SoundEffectInstance.Apply3D, and then SoundEffectInstance.Play. You cannot use fire & forget with 3D sounds.
We also changed what happens if you try to play too many sounds at the same time. In GS 3.0, the Play call would throw InstancePlayLimitException. In 3.1, CreateInstance still throws this exception if it runs out of voices, but Play just returns false. This is convenient for the common case where you are playing fire & forget sounds and want to silently ignore voice overflows, as you can simply ignore the Play return value and no longer have to write special error handling code.
Great decisions, I think. The designer of FMod had to make similar ones when working on FMod Ex, and in my experience they all turned out to be the right ones - in this case, convenience is the enemy of both correctness and good performance.
Correctness is a little obscure there, I suppose: Since Play would return an already-playing instance, it's possible that a context switch would prevent you from adjusting properties/pausing the instance before samples are sent to the player's speakers. Disconcerting when it happens.
This is a great change. I spent ages trying to reproduce random crashes in my game with the debugger attached. Eventually I discovered it was too many sounds being played (I didn't actually notice a SoundEffectInstance was returned and was calling Play() on the SoundEffect each time!).
I had to create a nasty wrapper class that called Play() on the SoundEffect the first time, then on the SoundEffectInstance each time after. This new change means I don't have to worry :D
Thanks Shawn, I just ripped a couple of teddy bears apart after I upgraded to 3.1 and found all my sound management was broken.
At least I can fix it now, but why isn't this information on the creators club website, in big bold letters, with a sign saying "beware of the leopard".
Breaking changes should be highlighted and solutions published with a release.
Hi Shawn, I have a question regarding SoundEffect.Play() in XNA 3.1: Above, you write that using SoundEffect.Play() in the basic sense (not create and configure, just fire and forget) "no longer generates any garbage." Using a fairly basic setup in my code, I have tested playing the same sound effect several times per second for minutes at a time (for the sake of profiling).
When I profile this with CLRProfiler, I see allocations for System.WeakReference objects equal to the amount of times I called SoundEffect.Play(). Pushing further into CLRProfiler, the allocations are all coming from SoundEffect::Play. Am I doing something wrong or am I misunderstanding something fundamental here? I want to believe that there is a no-garbage audio solution in XNA. Any advice or guidance you could give would be much appreciated.
Thanks so much for this and all your previous blogs. You have been a huge help to me in my dealings with XNA. Keep up the great work!
For anybody who is interested, I've continued this discussion (see my previous comment above) on the XNA forums where I have also posted source code for reproducing my problem(error?): http://forums.xna.com/forums/p/36800/212970.aspx#212970