Multiple sound channels

BlitzMax Forums/BlitzMax Programming/Multiple sound channels

Grey Alien(Posted 2006) [#1]
Hi. On BlitzPlus if I played several of the same sound at once on different channels, it sounded really noisy, almost distorted. Of course this is due to the combined output peaking and overloading of course. However, I've noticed that this doesn't seem to be as big an issue in Max, it doesn't sound as bad. Anyone else found this?

In BPlus I made an array of sound channels to limit the amount that could be played at once, and for certain sounds I only let one play at once, I just retriggered it. I considered adding an option where if you play a new sound, all the volumes on the other ones are reduced to balance it, but I never got round to it.

In Max, currently if I have 10 UFOs and they all explode at once, each has its own channel and will play an explosion noise. I could of course have a single global explosion channel, but then if a couple explode one after the other, restarting the sample (instead of playing it on another channel) sounds rubbish. So I could make an array and limit the number and perhaps put volume balancing it, but I'm not sure I need to.

Basically what are your views on the matter and how have you "resolved" this issue. Thanks in advance.


TartanTangerine (was Indiepath)(Posted 2006) [#2]
I seem to remember you asking this question on a number of occasions and on each I posted my solution :) I won't dig it out again, it's on here somewhere :D


VomitOnLino(Posted 2006) [#3]
Well I had your problem to, as in my game there is a very short "explosion" (well its not really an explosion) sample - so I made a boolean Array.

Like this :

IsExplosionSamplePlaying[32] '32 Channels
ExplosionSampleStartTime[32]

If Explosion
If ExplosionSamplePlaying[i]=False 'here you would search for the first free place in the array
PlaySound Boom 'no channel
i:+1
IsExplosionSamplePlaying[i]=True
ExplosionSampleStartTime[i]=Millisecs()
End If
End If

For i=1 to 32
If ExplosionSamplePlaying[i]=True
If ExplosionSampleStartTime[i]-Millisecs()>= 50 'assuming the sample is 80 milliseconds long.
ExplosionSamplePlying[i]=False
End If
End If
Next


sswift(Posted 2006) [#4]
This code wraps sounds in a type and records the last time a particular sound was played. If the last time the sound was played was only a few milliseconds ago, it doesn't play the sound. I set it to only a few milliseconds because my goal wasn't simply to avoid having sounds be loud, but instead to avoid the flanging effect you get when two identical sounds are played just milliseconds apart, and the way sounds are louder when repeated soon after one another. You can increase the delay if it isn't long enough for your tastes. I set it to what worked well in my Lego game.

Type Sound

	Global List:TList = CreateList()

	Global Path$ = "sfx\"

	Global Volume_SFX#   = 1.0
	Global Volume_Music# = 1.0
	
	Global MusicChannel:TChannel
	Global MusicTrack% = -1

	Field _TSound:TSound
	Field _Looping%	
	Field _LastPlayTime%


	' -------------------------------------------------------------------------------------------------------------------------------------------------------
	' This method loads a sound. 
	' Specifying Null for Url will create a null sound which will do nothing if played.  This is useful for creating placeholder sound instances.
	' -------------------------------------------------------------------------------------------------------------------------------------------------------
		
		Function Load:Sound(Url:Object, Loop%=False)
		
			Local NS:Sound 
			
			NS = New Sound			
																								
			NS._TSound = LoadSound(Url, Loop)
			If (NS._TSound = Null) And (Url <> Null) Then RuntimeError("Error in Sound.Load() : ''" + String(Url) + "'' failed to load.  Please reinstall the game.")
			
			NS._Looping = Loop
			
			List.AddLast(NS)
			
			Return NS					
																		
		End Function
		

	' -------------------------------------------------------------------------------------------------------------------------------------------------------
	' Volume# allows you to set the sound's volume.
	' Rate# allows you to adjust the speed at which the sound is played back up or down.
	' -------------------------------------------------------------------------------------------------------------------------------------------------------

		Method Play:TChannel(Volume#=1.0, Rate#=1.0)
	
			Local Channel:TChannel
			
			If _TSound = Null Then Return Null
			
			If Volume# < 0 Then Volume# = 0
			If Volume# > 1 Then Volume# = 1
		
			' If the timer has looped, or this sound wasn't played too recently...
			If (_LastPlayTime > MilliSecs()) Or (MilliSecs() > (_LastPlayTime + 60))
	 
				' Note:
				' If any sounds are looped, we'll need to create a channel type to hold the currently active channels and so we can alter their volume when
				' the sound effects volume slider is moved.
				
				' Get sound ready to play.
					Channel = CueSound(_TSound)
						
				' Set sound volume and pitch.
					Channel.SetVolume(Volume_SFX#*Volume#)
					Channel.SetRate(Rate#) 
									
				' Start playing sound.
					Channel.SetPaused(False)
				
				' Record the time at which this sound was played so that we can avoid playing the same sound more than once in quick succession.				
					_LastPlayTime = MilliSecs()
					
				Return Channel
		
			EndIf
			
		End Method

				
End Type


' -------------------------------------------------------------------------------------------------------------------------------------------------------
' This function loads all the sounds the game needs.
' -------------------------------------------------------------------------------------------------------------------------------------------------------

	Global SOUND_MouseClick:Sound
	Global SOUND_HighlightFruit:Sound
	Global SOUND_PickFruit:Sound
	Global SOUND_UnpickFruit:Sound
	Global SOUND_SwapFruit:Sound
	
			
	Function LoadSounds()

		SOUND_MouseClick     = Sound.Load(Sound.Path$ + "mouseclick.wav")
		SOUND_HighlightFruit = Sound.Load(Sound.Path$ + "highlightfruit.wav")
		SOUND_PickFruit      = Sound.Load(Sound.Path$ + "pickfruit.wav")
		'SOUND_UnpickFruit    = Sound.Load(Sound.Path$ + "unpickfruit.wav")
		SOUND_SwapFruit      = Sound.Load(Sound.Path$ + "swapfruit.wav")
	
	End Function



Grey Alien(Posted 2006) [#5]
I knew Indiepath would have a module ;-) Actually I think I have a link or the source somewhere.

VomitOnLino: Yeah I see what you are doing, but try checking out the ChannelPlaying function. Surely it must return false when the sample stops (maybe not...)

sswift: thanks, that's a good idea too, a different approach.

Yeah I was just really looking for ideas and finding out if anyone has tried volume balancing.