MiniB3D Sound Module

BlitzMax Modules Forums/MiniB3D/MiniB3D Sound Module

I've got a system together and it seems to be working pretty well for handling positional sound. If anyone whose interested could give it a try and let me know if flames leap out the back or not it would be much appreciated.

I've built it as a module and you will need minib3d installed as a module as well (I can't stand non-colored functions...). But you can easily strip out the module stuff at the top and make it work as an include or an import.

you can download it here
http://ima747.gotdns.com/blitzmax/minib3dsoundv1.0.zip

or here's the code if you want to copy and paste (just remember it's a module, so you need to toch it up a little if you do that)
SuperStrict

Rem
bbdoc: MiniB3D Sound
about: If there is no more emiter object then we can't update any sound point attached to that object, so those sound points are removed on the assumption that a dead entity can't make a sound.<br>
If you want a sound to continue from a point even if you kill the object that the sound is attached to then create and attach a attach a pivot to the entity and then attach the sound to the pivot. Then if you remove the entity the pivot with the sound will remain. You can then remove the pivot when the sound is finnished.
End Rem
Module ima.minib3dsound

ModuleInfo "Version: 1.0"
ModuleInfo "Author: Logan Chittenden"
ModuleInfo "License: You are free to use this code as you please."

ModuleInfo "History: 1.0 Initial Release"

Import sidesign.minib3d
Import brl.audio


'############################################################################################## Types
Rem
bbdoc: SoundPoint Type
about: A SoundPoint is a point that plays a sound in 3D space
End Rem
Type SoundPoint
	Rem
	bbdoc: The point the sound is coming from
	End Rem
	Field RelativeSoundPoint:TPivot
	
	Rem
	bbdoc: The angle to the sound from the listening entity
	End Rem
	Field ListenAngularDifference:TPivot
	
	Rem
	bbdoc: The channel the sound is playing in
	End Rem
	Field SoundChannel:TChannel
	
	Rem
	bbdoc: The Entity that is emiting the sound
	End Rem
	Field EmiterObject:TEntity
	
	Rem
	bbdoc: The "loudness" of the sound
	End Rem
	Field Loudness:Float
	
	Rem
	bbdoc: The base volume of the sound
	End Rem
	Field MasterVolume:Float
	
	Rem
	bbdoc: The distance from the listen point to the sound EmitterObject
	End Rem
	Field Distance:Float
	
	Rem
	bbdoc: The angle from the listen point to the EmitterObject
	End Rem
	Field Angle:Float
	
	Rem
	bbdoc: The pan point of the sound
	End Rem
	Field Pan:Float
	
	Rem
	bbdoc: The pan bump
	about: When calculating the sound channels pan this number will be added to the new pan
	End Rem
	Field PanBump:Float
	
	Rem
	bbdoc: The depth point of the sound
	End Rem
	Field Depth:Float
	
	Rem
	bbdoc: The depth bump
	about: When calculating the sound channels depth this number will be added to the new pan
	End Rem
	Field DepthBump:Float
	
	Rem
	bbdoc: The play rate of the sound
	End Rem
	Field Rate:Float = 1
	
	Rem
	bbdoc: The play rate bump
	about: When calculating the sound channels play rate this number will be added to the new rate
	End Rem
	Field RateBump:Float
	
	Rem
	bbdoc: The volume of the sound
	End Rem
	Field Volume:Float
	
	Rem
	bbdoc: The volume bump
	about: When calculating the sound channels volume this number will be added to the new volume
	End Rem
	Field VolumeBump:Float
	
	Rem
	bbdoc: Creates a new sound point
	about: To create a sound use Start3DSound to allow for future changes to the system
	End Rem
	Function Create:SoundPoint(TheSound:TSound, NoisyEntity:TEntity, EntityLoudness:Float=1, Volume:Float=1)
		Local theTemp:SoundPoint = New SoundPoint
		
		theTemp.RelativeSoundPoint = CreatePivot(HearingPoint.ListenPoint) ' parented to the listen point so we can get angles and distance relative to that
		theTemp.ListenAngularDifference = CreatePivot(HearingPoint.ListenPoint) ' used to calculate angle off of the listen point towards the sound

		theTemp.EmiterObject = NoisyEntity
		
		theTemp.SoundChannel = AllocChannel()
		theTemp.MasterVolume = Volume
		theTemp.Loudness = EntityLoudness
		CueSound(TheSound, theTemp.SoundChannel)
		theTemp.UpdatePosition()
		ResumeChannel(theTemp.SoundChannel)
		
		ListAddLast(SoundPointsList, theTemp)
		
		Return(theTemp)
	End Function
	
	Method UpdatePosition()
		Local LastDistance:Float
			
		If EmiterObject Then
			' Update the point the sound is coming from to that of the emiting object's global position
			PositionEntity(RelativeSoundPoint, EntityX(EmiterObject, True), EntityY(EmiterObject, True), EntityZ(EmiterObject, True), True)
			' Get and correct the angle off of the listen point towards where the sound is comming from
			PointEntity(ListenAngularDifference, RelativeSoundPoint) ' update the angle
			Angle = EntityYaw(ListenAngularDifference) ' angle will always be in range of -180 to +180
			' Get the distance from the listen point to the sound point
			LastDistance = Distance ' store the last distance incase we need to do doppler
			Distance = EntityDistance(ListenAngularDifference, RelativeSoundPoint)

			' Pan is based on yaw angle off of the listen point towards the sound source
			Pan = (-Angle)/90
			Pan:+PanBump
			If Pan > 1 Then ' past 90 deg
				Pan = 1-(Pan-1)
			ElseIf Pan < -1 Then ' past -90 deg
				Pan = -1-(Pan+1)
			End If
			
			' Depth is based on how far along Z axis (ahead or behind)
			' the point is in Local coords (which are relative To the listenpoint's postion and angle)
			Depth = EntityZ(RelativeSoundPoint)/HearingPoint.SoundFalloffEnd
			Depth:+DepthBump
			If Depth > 1 Then ' over maximum
				Depth = 1
			ElseIf Depth < -1 Then
				Depth = -1
			End If

			' If the doppler effect is enabled by having a dopplerscale
			If HearingPoint.DopplerScale = 0 Then
				Rate = (343.7+(LastDistance - Distance))/343.7
			ElseIf HearingPoint.DopplerScale > -1 Then
				Rate = (343.7+((LastDistance - Distance)*HearingPoint.DopplerScale))/343.7
				SetChannelRate(SoundChannel, Rate)
			Else ' not using standard or Exaggerated doppler, set the rate to 1
				Rate = 1
			End If
			Rate:+RateBump
			
			' Volume is based on distance from the listen point to the relative sound point, modified by
			' the master volume of the sound (how loud it is)
			Volume = MasterVolume-(Distance/(HearingPoint.SoundFalloffEnd*Loudness))
			Volume:+VolumeBump
			If Volume > 1 Then
				Volume = 1
			ElseIf Volume < 0 Then
				Volume = 0
			End If
			
			PauseChannel(SoundChannel) ' the pause and resume reduce crackling
			SetChannelPan(SoundChannel, Pan)
			SetChannelRate(SoundChannel, Rate)
			SetChannelDepth(SoundChannel, Depth)
			SetChannelVolume(SoundChannel, Volume)
			ResumeChannel(SoundChannel)
		Else
			Stop()
		End If
	End Method
	
	Rem
	bbdoc: Updates the sound channel and it's positional information, as well as clearing the channel if the sound is done playing
	about: To update all sounds at once use Update3DSounds()
	End Rem
	Method Update()
		If SoundChannel Then
			UpdatePosition()
			If Not ChannelPlaying(SoundChannel) Then
				Stop()
			End If
		Else
			Stop()
		End If
	End Method
	
	Rem
	bbdoc: Stops a sound and removes it from the sound system
	about: This will stop a sound point while playing and free it's resources
	End Rem
	Method Stop()
		StopChannel(SoundChannel)
		ListRemove(SoundPointsList, Self)
	End Method
	
	Method Delete()
		StopChannel(SoundChannel)
		FreeEntity(RelativeSoundPoint)
	End Method
End Type


'# A HearingPointType is a point to where the sound travels. i.e. the center of a head
Type ListeningPoint
	Field ListenPoint:TEntity	' the microphone, should be attached to the camera
	Field SoundFalloffEnd:Float	' Sounds are silent past this point
	Field DopplerScale:Float		' How far you have to move to double or half the sound rate. 0 to dissable doppler effect
End Type



'############################################################################################## Variables
Private
Global HearingPoint:ListeningPoint = New ListeningPoint ' the point where sound is heard
Global SoundPointsList:TList ' all currently active 3d sounds

'############################################################################################## Functions
Public
Rem 
bbdoc: Setup the 3D sound system
about:
Call this before any other 3D sound calls<br>
The sound system can be re-initialized later without loosing the sounds currently playing, however the positional information of the sound points will be wrong untill the next call to Update3DSounds()<br>
<br>
<b>ListeningEntity</b> is the entity you will hear sound through.<br>
<b>MaxRange</b> is the maximum distance a sound will be before it is silent<br>
<b>ExaggerateDopplerScale</b> <i>optional</i> is the amound to exagerate the doppler effect bye. A value of -1 will dissable the doppler effect. A value of 0 will use accurate doppler effect (1 unit = 1 meter). The value will be dependent on the speeds of objects in your program. A recommended starting value is 4, which will multipley any movement my 4x in the doppler effect.
End Rem
Function Init3DSound(ListeningEntity:TEntity, MaxRange:Float, ExaggerateDopplerScale:Float=-1)
	Local tempSoundPoint:SoundPoint

	If Not HearingPoint Then
		HearingPoint = New HearingPoint
	End If
	HearingPoint.ListenPoint = ListeningEntity
	HearingPoint.SoundFalloffEnd = MaxRange
	HearingPoint.DopplerScale = ExaggerateDopplerScale
	
	If SoundPointsList Then
		For tempSoundPoint = EachIn SoundPointsList ' update all sound points to listen from the new point
			EntityParent(tempSoundPoint.RelativeSoundPoint, HearingPoint.ListenPoint) ' re-attach relative point
			EntityParent(tempSoundPoint.ListenAngularDifference, HearingPoint.ListenPoint) ' re-attach angle diff point
			PositionEntity(tempSoundPoint.ListenAngularDifference, 0, 0, 0) ' re-center the angle diff point
		Next
	Else
		SoundPointsList = CreateList()
	End If
End Function


Rem
bbdoc: Updates all the 3D sound channels
about: Should be called (optimaly) once per loop, preferably after positions and collisions have been handled.
End Rem
Function Update3DSounds()
	Local tempSoundPoint:SoundPoint
	
	For tempSoundPoint = EachIn SoundPointsList
		tempSoundPoint.Update()
	Next
End Function


Rem
bbdoc: Attaches a sound to an entity and starts it playing.
returns: Sound Point so you can control it further. You can ignore the return value as it is held in the sound system.
about:
<b>TheSound</b> is the sound you wish to play.<br>
<b>NoisyEntity</b> is the entity that is making the sound.<br>
<b>Loudness</b> <i>optional</i> determines how loud a sound is. Louder sounds will cary farther. Distance is proportional to the MaxRange set durring Init3DSound. A value of 1 will cary to the MaxRange.
<b>Volume</b> <i>optional</i> sets a master volume for the sound
End Rem
Function Start3DSound:SoundPoint(TheSound:TSound, NoisyEntity:TEntity, Loudness:Float=1, Volume:Float=1)
	Local tempSoundPoint:SoundPoint
	
	tempSoundPoint = SoundPoint.Create(TheSound, NoisyEntity, Loudness, Volume)
	
	Return(tempSoundPoint)
End Function