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


Excellent, I've just had a moan about the lack of sound, and here you've just created a module, thanks I'll give it a go :D

Hope it's usefull to you. I'm working on an update with some more control possibilites and a couple minor bug fixes. let me know if you come across anything that needs fixing/adding etc.

very nice work.
I have also started to add a basic 3DSound System with OpenAL and the OpenAL Sound Driver. So maybe we could support both in minib3d as 2 alternatives. 1 with and 1 alternative with openal.

But for this it would be useful to have one interface which will provide some kind of 3DAudio Driver. If yours is ready I will try to bring both (mine and yours) to one level.

keep up the good work. we are thankful for every helping hand :)

Sounds good. I'm working on a few more features and a couple minor bug fixes. I'll keep you posted. I started looking into open AL but it seems to refuse to work on my PC so I gave up on that for now. My system uses the blitz audio so it shouldn't have any of those complications.

I've kind of got the grips of it, but could you supply a quick example of the key features?

I'll hopefully have an update today with a few more features and Ill put together a commented demo to go along with it for you.

http://ima747.gotdns.com/blitzmax/minib3dSoundv1.1.zip

New version with more features. Also included are 2 example programs. a simple one that shows just he basics needed to get going. and an advanced one that uses almost every feature. I've commented the pants off of everything so it should be fairly easy to understand and use. Let me know if anything is confusing etc.

Looks like you have taken SOUND_LOOP out, it can't find it to compile?

Sorry, my mistake, needs to be changed to True.

SOUND_LOOP (I'm assuming you're talking about in LoadSound) is part of the blitz audio system and is seperate from the 3D audio. The 3D audio just plays normal sounds through the normal methods, it just manipulates the channels for a 3D effect. SOUND_LOOP and SOUND_HARDWARE work for me.

Ah, would this then have something to do with me not updating my BMax yet? Maybe they changed it from True to specific functions, like LOOP and HARDWARE. I'm experiencing quite a few issues with libraries that people have written, I best update.

I've just updated my BMax to 1.24 and it still pulls the error up? Regarding SOUND_LOOP. I'm fairly new to BMax so you may need to bare with me, sorry.

I cant find anything to do with SOUND_LOOP or SOUND_HARDWARE, here is the help file for LoadSound
Function LoadSound:TSound( url:Object,loop_flag=False )
Rem
Load and Play a small example wav file with looping.
End Rem

sound=LoadSound("shoot.wav",true)
PlaySound sound

Input "Press any key to continue"


Have you synced your mods since you updated? the example in my help file is
Rem
Load and Play a small example wav file.
End Rem

sound=LoadSound("shoot.wav")
PlaySound sound

Input "Press any key to continue"


the information fied above it is
url can be either a string, a stream or an audio sample object. The returned sound can be played using PlaySound or CueSound.

The flags parameter can be any combination of:
SOUND_LOOP : The sound should loop when played back.
SOUND_HARDWARE : The sound should be placed in onboard soundcard memory if possible.


After I built your mods I then went into Synchronize Modules yes.

Sorry I was wrong, I hadn't synchronized, everything running fine now, your module is very nice, no bugs to report yet :)

glad to hear :0) keep me posted if you spot any, I haven't had any time to poke at it for the last few days.

Will do, so far so good though :)

V1.2 - http://ima747.gotdns.com/blitzmax/minib3dSoundv1.2.zip

Updated the worklog as well.

If there's interest I will update the advanced example to include the new functions, but I think they should be fairly easy to understand as is.

As always, let me know if you find any bugs.