Dealing with sound.

Monkey Forums/Monkey Programming/Dealing with sound.

Paul - Taiphoz(Posted 2012) [#1]
Sup all. would like to open a discussion on sound and how to deal with it in game's that require lots of sound to be played rapidly.

At the moment I am working on a shmup, up screen arcade shooter.

I have already realized that I don't have enough audio channels available to me to play every sound as it happens on screen, for example when the player fire's a continued stream of bullets, your only hearing a sound representation of about a quarter of them.

When shooting is combined with your bullets hitting an object and a sound linked to the hit, then again the number of available channels is noticeable.

I am running into this and at the moment all I have is one stream of low end fire hitting one ship, when I think ahead to the potential sound sources I will have in a proper level I realize that I need to sort this our now before I go any further.

So my hope is that one of you might have already come across this and found a solution.


At the moment my plan is to try and give each sound a Priority value, and then when more than one sound is trying to play, the one with the highest value gets to play and the other gets ignored.

Anyone got any better ideas, or experience with monkey and sound.


Paul - Taiphoz(Posted 2012) [#2]
This is what I have in play at the moment, which seems to be working well enough.

The FireGun method is called when the player hits the fire or holds the fire button down.

I am checking to see if the interval between bullets is ok and if it is I shoot a new bullet. at the same time I then check to see if the bullet sound is playing already, if it is I stop it and play it again from the start.

Not sure how well this will scale up with tons of bullets and tons of other sound sources but we will soon find out.

	Method FireGun()
	
		If Millisecs() - Self.BulletTime > Self.BulletDuration
			If Not Player.sound_shoot.IsPlaying() Then
				Player.sound_shoot.Play()
			Else	
				Player.sound_shoot.Stop()
				Player.sound_shoot.Play()
			End If			
		End If	
						
		Select Self.PowerType
			Case RedPower
				Self.FireRedGun(Self.GunPower)
				
			Case GreenPower
				Self.FireGreenGun(Self.GunPower)
			Case BluePower
				Self.FireBlueGun(Self.GunPower)
		End Select
	
		
	End Method



JIM(Posted 2012) [#3]
There are a few mechanisms you can use:

1. Layers/Categories. Divide your sounds by groups (shooting, impact, ambient, etc.) Make sure you play one of each category, then, if channels are still available, use priorities.

2. Priorities. Just like you said, you need a bunch of priorities for sounds. Either automatic (might fail badly) or by hand.

3. Merge sounds. If you have sounds that play at the same time, try to make a new sound that contains both. That way you can get 1 free channel. I would make the bullet firing loopable and play it continuously (press key > start sound. Release key > stop sound). If you fire 3 types of bullets at once, instead of 3 sounds, you could use one that combines them.

There might be other tricks here, but nothing else comes to mind atm.
Hope it helps :)

A bit OT: You can tidy up the code a little bit :)

		If Millisecs() - Self.BulletTime > Self.BulletDuration
			If Player.sound_shoot.IsPlaying() Then
				Player.sound_shoot.Stop()
			End If
			Player.sound_shoot.Play()
		End If	



muddy_shoes(Posted 2012) [#4]
Sound is currently (and probably always will be) variable across platforms. My sound system is pretty basic but it attempts to recycle channels by using the length of the sounds to judge if or how close they are to being fully played (or complete at all on platforms where ChannelState isn't available).

On GLFW (at least on my laptop) this works very well, XNA not quite as well but good enough for my purposes, Flash is generally okay, HTML5 on Chrome is mostly fine but unreliable, Firefox is a mess and I haven't bothered investigating IE. I haven't stress-tested Android but what I have works okay for my purposes, which don't include dozens of samples a second.

You can try a rapid fire test I did on HTML5 (ogg, so no IE) and Flash if you like. On HTML5 you'll probably need to increase the estimated playback delay to 100ms+, this simply pads the estimated completion time to allow for delay in the sound being started.

I suspect that if you want really good cross-platform sound support you're going to have to dig into some native code to get the best from each platform.


Paul - Taiphoz(Posted 2012) [#5]
Yeah kinda finding the same, to display the channels in use are you just looking through the first 9 channels and reporting their state each loop ? or you doing something else ?

I never thought if displaying channels in use like that it might actually help me find a solution to my issue.


Gerry Quinn(Posted 2012) [#6]
I would try not to be too fancy. Try dividing sounds into classes that might happen simultaneously, and then assign an appropriate number of channels (maybe just one in some cases) to each class. Then just assign the next channel in its class to any new sound, rotating back to the first channel.

For example you might have:
Bullets firing = channels 1 - 3
Bullets hitting = channels 4 - 5
Death screams = channels 6 - 7
Tank engine = channel 8
Occasional effects = channel 9

...or whatever breakdown you feel is correct.

If you fire a fourth bullet while three are still flying, the first will get its sound cut off - but who's going to notice? You will still hear when it hits something.


Paul - Taiphoz(Posted 2012) [#7]
Yeah I might try that, is there an easy way to tell what sound is active on a given channel ??

I'm thinking that if you can tell what sound is active on a channel you can do a check and make sure that any given sound is only allowed to be playing once or twice without having to worry about assigning a channel range to it.


muddy_shoes(Posted 2012) [#8]
to display the channels in use are you just looking through the first 9 channels and reporting their state each loop ? or you doing something else ?


The system tracks which sounds are active via ChannelState if available or through an estimate of how much has been played if not. The list of active channels is just that: the channel ids on which a sound is currently being played.

If running out of channels it finds the sound that is closest to finishing, stops it and frees the channel for re-use. Monkey's audio system allows 32 channels so this doesn't happen a lot.


Paul - Taiphoz(Posted 2012) [#9]
32 total but its only really 9 for fx, the rest are music are they not ?


muddy_shoes(Posted 2012) [#10]
According to the documentation PlaySound takes a channel ID from 0-31 and PlayMusic doesn't take one at all. That suggests thirty-two effects and one music channel.

It wouldn't be the first time the documentation has been wrong, of course, but I'd like to know where the contradictory info is coming from.


Jesse(Posted 2012) [#11]
On a game I made(Blitzmax) I have 16 channels for sfx of which I rotated through.
something like this:

    PlaySound(sfx,activeChannel)
    activeChannel += (activeChannel + 1) mod 16


and it worked good.

I don't check which one is over. I figured that after about 16 you can't really tell the difference so even if a sound fx was playing it would stop the oldest and play the new one. and I believe that you do have 32 channels for sound from what I read in the documents.


muddy_shoes(Posted 2012) [#12]
Jesse, it all depends on what your effects are. The player would definitely notice a bonus jingle being cut-off, for example.

Taiphoz, I've built my test again with an artificial limit of 9 channels: Flash, HTML5. You can see sounds being killed off in the debug. It works, but the audio latency starts to be a real problem. People are much more able to hear breaks and variance in a steady beat (like the firing) than slight delays in initial playback for single sounds.


Jesse(Posted 2012) [#13]

Jesse, it all depends on what your effects are. The player would definitely notice a bonus jingle being cut-off, for example.


Probably, When I get some time I will do some test and post them here.


muddy_shoes(Posted 2012) [#14]
I quickly added a method of limiting the numbers of each sound individually, similar to Gerry's suggestion or JIM's categories and replaced the 9-channel tests above with one that limits firing to four instances and explosions to two. It seems to be a good way to go. It certainly improves the Chrome hiccuping: Flash, HTML5


muddy_shoes(Posted 2012) [#15]
On further digging it looks like ChannelState + latency is the bit that causes problems when killing old sounds. ChannelState only has a value for playing, not for "in the process of starting to play" when on platforms with high latency.

My code assumed that 0 could be trusted to mean that the channel is not active. It can't. Removing that check fixes the stuttering just as well as placing limits on individual sounds. It's still a reasonable technique for avoiding unnecessary numbers of samples being played though.


Dima(Posted 2012) [#16]
in my game i don't allow same/similar sounds to be played at the same time as it creates a weird echo effect. Like firing 2 bullets at a similar time , or 2 rocks falling down at a very close time, the effect wasn't pleasant with 2 of the same sound playing at the same time in different channels. I do a check if the same sound is already playing, and if duration is too short I just ignore the new playsound command and let the old one play out.


Paul - Taiphoz(Posted 2012) [#17]
try and see if you can play more than 9 sounds at the exact same time, as I understand it we should be able to have 31 sounds playing all at once, if that were true I would not be having the problems I am.

My channel's seem to be 31, but with a limit on the number of active channels at any given moment to about 8 or 9..


Paul - Taiphoz(Posted 2012) [#18]
yeah

For Local tl:Int =1 To 30
   PlaySound shoot,tl
Next


Yeah that will only play on channels 1 - 8 , it will not play on the others, so why do we have 31 channels when we can only play on 8 of them at any one time.

Is that supposed to be like that ?


muddy_shoes(Posted 2012) [#19]
What platform are you talking about and what do you mean by "will not play"? If you just go to the first Flash link I posted in the thread you can set the firing rate high and see that the number of active sounds is > 8 and it is still triggering new sounds.


Paul - Taiphoz(Posted 2012) [#20]
Im building to html5 at the moment cos it compiles faster. your html5 example didnt run but yeah I saw the flash one playing more than 8 that is odd, so I wonder if its a limitation enforced by html5 then.


muddy_shoes(Posted 2012) [#21]
If you Google the issue you'll find that audio in HTML5 is not consistently implemented across browsers or even functional in some. The advice for games is actually to use an embedded Flash object to play sounds if you want the best cross-browser functionality.

Monkey could improve its out-of-the-box audio abilities by using the Flash option and dropping back to HTML5 if Flash is not available (or based on a compile flag so that the programmer can create "pure" HTML5 if desired). That sort of commitment to cross-platform consistency doesn't seem to be a priority though.


Gerry Quinn(Posted 2012) [#22]
I think Mark has enough on his plate without introducing hybrid targets!


muddy_shoes(Posted 2012) [#23]
He has the same thing on his plate: a commercially offered cross-platform language and supporting libraries for games. What I described is the best available option for one of those platforms.