Small Race Condition with SDL Mixer Audio
The easiest way to explain this is to go through the order of events.
- We have a sound playing in channel 0 with the mixer handle 0
- S_UpdateSounds() is called, the sounds are updated and the channel list is the same
- In SDL Mixer, the sound finishes playing so any calls to Mix_Playing will return 0
- We call S_StartSound() to start a new sound somewhere, now because we already have a different sound in the list, this goes into channel 1, but mixer gives it the handle 0 because the other sound has stopped. Both sounds now have handle 0.
- We come back to S_UpdateSounds(), now because both sounds think they are on handle 0, and sound 2 is still playing on it, both sounds think they are still playing, and neither sounds are removed (both call Mix_Playing(0) and get back 1)
- For whatever reason, we want to try and stop sound 1 using S_StopSound() (Maybe we used S_IsSoundPlaying() which will also still return true! Or maybe we just tried to play the exact same sound as sound 1 on the same origin.). What happens here is that sound 2 will stop playing, because the call to Mix_HaltChannel will take handle 0, and sound 2 is on it!
- The game will then cleanup sound 1 (or reset it back onto handle 0 if we tried to replay it) and think sound 2 is still playing until S_UpdateSounds where it clears that too (Unless we played sound 1 again, in which case they both still use the same handle).
So while this may not be a big problem in vanilla right now, it's definitely coming up with weird side effects in Kart semi-often. Wouldn't be surprised if people have noticed it before in vanilla relating to sounds like spindash charge though.
I have 3 separate proposals that can fix it. First one is going through the channels whenever we try to start a new sound to see if an old one has already stopped and clear it away. I feel like this will possibly not fully fix the problem (Maybe the sound stops in the incredibly small time between checking if it has and trying to start the new sound). But it would almost certainly make it less noticeable, I just worry whether this could also be a little slow.
The second is telling SDL2_Mixer exactly which sound channel we want it to use (based on the ones SRB2 is assigning). This is almost certain to work because if SRB2 thinks a sound is still playing on one of its channels it will tell Mixer to give it a different handle, and when S_UpdateSounds comes back around the old sound will be cleaned up as normal. The only issue with this is that it requires changing the interface code to tell it which sound channel to use, I don't know if this will have negative impacts on different interfaces (or if they can even support this, I don't see why not but you never know). It also requires changing a lot and making sure it works, and I can't even test every interface.
The third is using Mix_ChannelFinished to cause Mixer to call a callback function when the sound is halted that could find the sound in the sound channels list and remove it then. This would also be called by Mix_HaltChannel from S_StopSound directly though.
Basically just opening this for reference and to potentially get any other ideas on how I can fix this since I need to anyway.
EDIT: I just realised a third option, added it to the Issue.