SoundEffect changes in XNA Game Studio 3.1
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:
- Fire & forget is when you want to trigger a sound, then not have to worry about it any more, and have the system automatically clean up after it finishes playing.
- Create & configure is when you want to get back some sort of object that lets you alter the sound (changing volume or pitch, applying 3D settings, pausing or resuming) while it plays. This is obviously more powerful, but also more work because you have to keep track of which sounds are playing and clean up the relevant objects when you are done with them.
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:
- Because the Play method did not know whether you were going to store the return value, it had to allocate a new SoundEffectInstance each time, so fire & forget sounds created garbage.
- The only way we could tell the difference between fire & forget vs. create & configure usage was by noticing if the garbage collector tried to reclaim a SoundEffectInstance that was still playing. But .NET garbage collection is not deterministic! This worked ok as long as GC occurred regularly, but the better optimized your game was, the less frequently GC would run, in which case fire & forget sounds might not be reclaimed often enough, so the system would run out of voices.
In Game Studio 3.1, we changed the API to make these two usage scenarios explicit:
- SoundEffect.Play is now only for fire & forget, and it no longer returns a SoundEffectInstance. Instead, you get back a bool indicating whether the call succeeded. This no longer generates any garbage, and no longer supports looping (because if you started a looping sound this way, it would be impossible to ever stop it, which doesn't seem particularly useful :-)
- For create & configure scenarios, we added a new SoundEffect.CreateInstance method. This returns a paused SoundEffectInstance, so you can set properties such as Volume, Pitch, and Pan, then call SoundEffectInstance.Play when you are ready to trigger it.
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.