music

BlitzMax Forums/BlitzMax Programming/music

Robert Cummings(Posted 2006) [#1]
Hi...

need some help with bmax music. I'd like to play music in my game but handle it properly. Do I need a channel? whats the best way to go about it? any tips and please share your own ways of doing this.

I ask because the docs are very open ended, they also confuse me.

Thanks!


TartanTangerine (was Indiepath)(Posted 2006) [#2]
I use the following code for music, loops, one shot and sfx queuing. Hope it helps. It based on some old code that I think was written by Eikon.
DebugLog "MOD_SFX_Engine.bmx"

Const CH_ANY 	= -1
Const CH_1		= 1				'Brick Sounds 
Const CH_2		= 2				'Score Sounds
Const CH_3		= 3				'Powerup Sounds
Const CH_4		= 4				'Incidential Sounds
Const CH_5		= 5				
Const CH_6		= 6				' Diamond Sounds
Const CH_7		= 7				' Chords
Const CH_8		= 8				' Vox
Const CH_9		= 9				' GUI Sounds
Const CH_10		= 10			' BG Music
Const CH_11		= 11			' Score Sounds

' ---------------------------------------------------------------------

Global ChannelTList:TList = New TList

Type ActiveChannel
	Field SampleID	:Int
	Field Address	:TChannel
	Field FadeTime	:Double
	Field FadeStart	:Double
	Field CreateTime:Int
	Field Volume	:Double
	Field VolumeS	:Double
	Field Queue		:TSound[8]
	Field VolQ		:Double[8]
	Field Qcount	:Double
	
		Method New ()
            	If ChannelTList = Null Then ChannelTList = New TList
            	ChannelTList.AddLast Self
    	End Method		

		'----------------------------------------------------------------
		
		Function Destroy (a:ActiveChannel)
				Local b:Int
				a.Address = Null
				For b = 0 To 7
					a.Queue[b] = Null
				Next
				ChannelTList.Remove a
				a = Null
	    End Function

		'----------------------------------------------------------------
		
		Function MusicQueueState(ID:Int)
				Local c:ActiveChannel
				For c:ActiveChannel = EachIn ChannelTList
					If c.SampleID = ID Then Return Int(c.QCount)
				Next
		End Function

		'----------------------------------------------------------------
	
		Function PlaySample(add:TSound,ID:Int=-1,Vol:Double=1,Queue:Byte=False,overlap:Byte=False,rate:Double=1)
		
				Local Channel:ActiveChannel
				Local Simultaneous:Byte = False
				Local Playing:Byte = False
				
				If ID <0 Then Simultaneous = True
				
				For Channel:ActiveChannel = EachIn ChannelTList
				
					If Channel.SampleID = ID
						If Queue = True
							Channel.Qcount = Channel.Qcount + 1
							If Channel.Qcount > 1 Then 
								Channel.QCount = 1
								DebugLog "**SFX Queue Overload!"
								Return
							EndIf
							Channel.Queue[Channel.Qcount] = add
							Channel.VolQ[Channel.Qcount] = vol
							Playing = True

						Else
							Playing = True
						EndIf
						
						If overlap And (MilliSecs() - Channel.Createtime > 100) Then Playing = False
									
					EndIf
					
					If Not ChannelPlaying(Channel.Address) Then 
						StopChannel(Channel.Address)
						Destroy(Channel)
					EndIf
				
				Next
				
				If Not (Simultaneous = False And Playing = True)
				
					Channel:ActiveChannel = New ActiveChannel
					Channel.SampleID = ID
					Channel.Address = AllocChannel()
					Channel.Volume = Vol
					Channel.VolumeS = Vol
					SetChannelVolume(Channel.Address,Channel.Volume)
					PlaySound(Add,Channel.Address)
					Channel.FadeTime = 0
					Channel.CreateTime = MilliSecs()
					SetChannelRate(Channel.Address,rate)
					
				EndIf	
		
		End Function
		
		'----------------------------------------------------------------

		Method Update()
		
				Local a:Int
		
				If Not ChannelPlaying(self.Address)
					If self.Qcount = 0
						StopChannel(self.address)
						Destroy(Self)
						Return
					Else
						SetChannelVolume(self.address,self.volQ[1])
						PlaySound(self.queue[1],self.address)
						For a = 1 To self.qCount - 1
							self.queue[a] = self.queue[a+1]
							self.volQ[a] = self.volQ[a+1]
						Next
						self.Queue[self.Qcount] = Null
						self.Qcount = self.Qcount - 1
					EndIf
				EndIf
				
				If Self.FadeTime
					If self.Volume <= 0 Then
						If self.Qcount = 0
							StopChannel(self.address)
							Destroy(Self)
						Else
							Self.FadeTime = 0
							Self.Volume = self.volQ[1]
							SetChannelVolume(self.address,self.volQ[1])
							PlaySound(self.queue[1],self.address)
							For a = 1 To self.qCount - 1
								self.queue[a] = self.queue[a+1]
								self.volQ[a] = self.volQ[a+1]
							Next
							self.Queue[self.Qcount] = Null
							self.Qcount = self.Qcount - 1
						EndIf
						
					Else
						Self.Volume = self.VolumeS - Float(MilliSecs() - self.FadeStart) / self.fadetime
						SetChannelVolume(self.address,self.volume)
					EndIf
				EndIf		
				
		End Method
		
		'----------------------------------------------------------------

		Function UpdateAll()
				Local c:ActiveChannel
				For c:ActiveChannel = EachIn ChannelTList
					c.Update()
				Next
		End Function
		
		'----------------------------------------------------------------
		
		Function SoundOff()
				Local c:ActiveChannel
				For c:ActiveChannel = EachIn ChannelTList
					StopChannel(c.address)
					Destroy(c)
				Next
		End Function
		
		'----------------------------------------------------------------
		
		Function SetVolume(ID:Int,Vol:Double)
				Local c:ActiveChannel
				For c:ActiveChannel = EachIn ChannelTList
					If c.SampleID = ID Then SetChannelVolume(c.address,vol)
				Next
		End Function
		
		
		
		'----------------------------------------------------------------
		
		Function Playing(ID:Int)
				Local c:ActiveChannel
				For c:ActiveChannel = EachIn ChannelTList
					If c.SampleID = ID And ChannelPlaying(c.address) Then Return True
				Next
				Return False
		End Function
		
		'----------------------------------------------------------------
		
		Function ReleaseSample(ID:Int,Timeout:Double)
				Local c:ActiveChannel
				For c:ActiveChannel = EachIn ChannelTList
					If Not c.FadeTime
						If c.SampleID = ID Then
							If Timeout
								c.FadeTime = Timeout
								c.FadeStart = MilliSecs()
							Else
								StopChannel(c.Address)
								Destroy(c)
							EndIf
						EndIf
					EndIf
				Next
		End Function
		
		'----------------------------------------------------------------
		
		Function NextTrack()
			MusicTrack:+1

			If MusicTrack > MusicTracks Then MusicTrack = 1
			ActiveChannel.ReleaseSample(CH_10,4000)
			DebugLog "Music Track : "+MusicTrack
			ActiveChannel.Playsample(MainSkin.BGMusic[MusicTrack],CH_10,MusicVolume,True,False)
			Return True
		End Function
		
End Type




Robert Cummings(Posted 2006) [#3]
Thanks - is there an example of use?


TartanTangerine (was Indiepath)(Posted 2006) [#4]
Just make sure you call UpdateAll() each loop and then just chuck stuff at PlaySample.

The NextTrack() function shows how the system can be used. It fades the current track and then queues the next track to play when the current one has finished. It's a fire and forget system.

[edit]You can also change the volume and pitch as an audio stream is playing, for when a game is paused etc.


Robert Cummings(Posted 2006) [#5]
Thanks. I will try and learn from it and then roll my own because it's peculiar way of working hurts my delicate little head :)

If anyone could continue contributing their own ways of doing it I will appreciate and learn from it rather than blatantly copying? Would be very helpful, thanks!


TartanTangerine (was Indiepath)(Posted 2006) [#6]
I have made significant improvements over this version, this is quite messy and I was one of the first things I ever wrote in BMAX.


Blitzplotter(Posted 2006) [#7]
I am cutting my teeth in BMax. I am a noob to BMax, and your sound engine looks like a good way for me to learn some BMax functionality. Could you please tell me what changes I need to make to my calling code (below) to enable your sound engine. I have a sound called word1.wav within the same directory as your sound engine and my calling code. Thanks in advance - BMax is making my head hurt also....




SebHoll(Posted 2006) [#8]
I think it has to be something like:



Remember the PlaySample & UpdateAll functions are within the ActiveChannel type, so you need to call them using Type.Function().


Blitzplotter(Posted 2006) [#9]
Cheers Seb, it worked after a minor modification to the SFX_Engine, compiler was hunting for BG Music so commented out...:

@ Indiepath : Thx for the post




Booticus(Posted 2006) [#10]
@ Indiepath: You mention: "I have made significant improvements over this version, this is quite messy and I was one of the first things I ever wrote in BMAX."

Does that mean you'll have a newer one available soon? I saw on your site for your modules the list for the SFX mod. Is that your latest and greatest? Any ETA? Just curious. Nothing mission-critical for me. Just never played around with sound much and your mod looked pretty neat.

@Blitzplotter: Got an example of it in action after you fixed it?


Diordna(Posted 2006) [#11]
I've got a pretty simple sound engine that's got automatic stereo placement based on a coordinate system, email if you want it, it's really too big to paste.

diordna (at) gmail.com


Mark1nc(Posted 2006) [#12]
Here's my panning solution (relative to player in the game world):

Local pan# = (enemy_x-player_x)/PLAYFIELDW
Local vol# = (1 - Abs(pan)/10) * (1 - (Abs(enemy_y-player_y)/PLAYFIELDH)/10)
PlaySound2(enemy_sfx, 1, pan, vol)



'The PlaySound replacement
Function PlaySound2:TChannel(snd:TSound, freq# = 1, pan#=0, vol# = 1)

	Local ch:TChannel = Null
	
	If sfxvol > 0 And snd <> Null
		ch=CueSound(snd)
		If freq <> 1
			SetChannelRate(ch,freq)
		EndIf
		SetChannelPan ch, pan		
		SetChannelVolume ch,sfxvol*vol
		ResumeChannel ch	
	EndIf
	Return ch

End Function





TartanTangerine (was Indiepath)(Posted 2006) [#13]
@Booticus, yes this is complete but I won't be releasing it yet. Well not until I decide if I'm staying with the BMAX sound engine or move to something like OpenAL.


Blitzplotter(Posted 2006) [#14]
@Booticus... If you use the big post from indiepath above, just comment out the BG field in the snippet I put in above. Then use Sebs suggestion to call the SFX_Engine (big piece of code at top), as long as you have your .wav of choice within the directory of the two files talked about... should play.

@Indiepath.... I'm quite new to this sound engine malarky... however Grey Alien's Game Framework has a pretty good implementation of playing sound files - Once you suss it out. By good, I mean the quality of the sound that is played...

Regards....


TartanTangerine (was Indiepath)(Posted 2006) [#15]
@Indiepath.... I'm quite new to this sound engine malarky... however Grey Alien's Game Framework has a pretty good implementation of playing sound files - Once you suss it out. By good, I mean the quality of the sound that is played...

The quality of the sound has nothing to do with the code playing it :P The issue with the BMAX sound module is that it takes a couple of days to load .ogg files, and it does not offer streaming of any kind.


Grey Alien(Posted 2006) [#16]
no my code use special magic sprinked in there to make it sound better ;-) Yeah that non-streaming ogg stuff is a pain.