|
@@ -1,12 +1,31 @@
|
|
|
|
|
|
+#rem monkeydoc
|
|
|
+
|
|
|
+Highly experimental audio module!
|
|
|
+
|
|
|
+#end
|
|
|
Namespace mojo.audio
|
|
|
|
|
|
Private
|
|
|
|
|
|
-Global _alloced:Int[]
|
|
|
+Function ALFormat:ALenum( format:AudioFormat )
|
|
|
+ Local alFormat:ALenum
|
|
|
+ Select format
|
|
|
+ Case AudioFormat.Mono8
|
|
|
+ alFormat=AL_FORMAT_MONO8
|
|
|
+ Case AudioFormat.Mono16
|
|
|
+ alFormat=AL_FORMAT_MONO16
|
|
|
+ Case AudioFormat.Stereo8
|
|
|
+ alFormat=AL_FORMAT_STEREO8
|
|
|
+ Case AudioFormat.Stereo16
|
|
|
+ alFormat=AL_FORMAT_STEREO16
|
|
|
+ End
|
|
|
+ Return alFormat
|
|
|
+End
|
|
|
|
|
|
Public
|
|
|
|
|
|
+
|
|
|
#rem monkeydoc Global instance of the AudioDevice class.
|
|
|
#end
|
|
|
Const Audio:=New AudioDevice
|
|
@@ -21,16 +40,57 @@ Class AudioDevice
|
|
|
#end
|
|
|
Method Init()
|
|
|
|
|
|
- Mix_Init( 0 )
|
|
|
+ Local error:=""
|
|
|
+
|
|
|
+ _alcDevice=alcOpenDevice( Null )
|
|
|
+ If _alcDevice
|
|
|
+ _alcContext=alcCreateContext( _alcDevice,Null )
|
|
|
+ If _alcContext
|
|
|
+ If alcMakeContextCurrent( _alcContext )
|
|
|
+ Return
|
|
|
+ Else
|
|
|
+ error="Failed to make OpenAL current"
|
|
|
+ Endif
|
|
|
+ Else
|
|
|
+ error="Failed to create OpenAL context"
|
|
|
+ Endif
|
|
|
+ Else
|
|
|
+ error="Failed to create OpenAL device"
|
|
|
+ Endif
|
|
|
+
|
|
|
+ End
|
|
|
+
|
|
|
+ Private
|
|
|
+
|
|
|
+ Field _alcDevice:ALCdevice Ptr
|
|
|
+ Field _alcContext:ALCcontext Ptr
|
|
|
+ Field _error:String
|
|
|
+
|
|
|
+ Field _channels:=New Stack<Channel>
|
|
|
+
|
|
|
+ Method FlushTmpChannels()
|
|
|
+
|
|
|
+ Local put:=0
|
|
|
+ For Local chan:=Eachin _channels
|
|
|
|
|
|
- Mix_OpenAudio( 44100,MIX_DEFAULT_FORMAT,2,1024 )
|
|
|
+ If chan.Playing
|
|
|
+ _channels[put]=chan;put+=1
|
|
|
+ Continue
|
|
|
+ Endif
|
|
|
+
|
|
|
+ chan.Discard()
|
|
|
+ Next
|
|
|
+ _channels.Resize( put )
|
|
|
+ End
|
|
|
+
|
|
|
+ Method AllocTmpChannel:Channel()
|
|
|
+
|
|
|
+ FlushTmpChannels()
|
|
|
|
|
|
- Mix_AllocateChannels( 32 )
|
|
|
+ Local channel:=New Channel()
|
|
|
+ _channels.Push( channel )
|
|
|
|
|
|
- _alloced=New Int[32]
|
|
|
- For Local i:=0 Until 32
|
|
|
- _alloced[i]=i+32
|
|
|
- Next
|
|
|
+ Return channel
|
|
|
End
|
|
|
|
|
|
End
|
|
@@ -39,126 +99,242 @@ End
|
|
|
#end
|
|
|
Class Sound
|
|
|
|
|
|
- #rem monkeydoc Plays a sound.
|
|
|
+ #rem monkeydoc Creates a new sound.
|
|
|
#end
|
|
|
- Method Play:Channel( loops:Int=0 )
|
|
|
-
|
|
|
- Local channel:=New Channel
|
|
|
- If Not channel.Play( Self,loops ) Return Null
|
|
|
+ Method New( data:AudioData )
|
|
|
+ alGenBuffers( 1,Varptr _alBuffer )
|
|
|
+ alBufferData( _alBuffer,ALFormat( data.Format ),data.Data,data.Size,data.Hertz )
|
|
|
+ End
|
|
|
+
|
|
|
+ #rem monkeydoc Discards a sound.
|
|
|
+ #end
|
|
|
+ Method Discard()
|
|
|
+ If Not _alBuffer Return
|
|
|
+ alDeleteBuffers( 1,Varptr _alBuffer )
|
|
|
+ _alBuffer=0
|
|
|
+ End
|
|
|
+
|
|
|
+ #rem monkeydoc Plays a sound through a temporary channel.
|
|
|
+
|
|
|
+ The returned channel will be automatically discarded when it finishes playing or is stopped with [[Channel.Stop]].
|
|
|
+
|
|
|
+ #end
|
|
|
+ Method Play:Channel( loop:Bool=False )
|
|
|
+
|
|
|
+ Local channel:=Audio.AllocTmpChannel()
|
|
|
+
|
|
|
+ channel.Play( Self,loop )
|
|
|
|
|
|
Return channel
|
|
|
End
|
|
|
-
|
|
|
+
|
|
|
#rem monkeydoc Loads a sound.
|
|
|
#end
|
|
|
Function Load:Sound( path:String )
|
|
|
|
|
|
- Local data:=DataBuffer.Load( path )
|
|
|
+ Local data:=AudioData.Load( path )
|
|
|
If Not data Return Null
|
|
|
|
|
|
- Local rwops:=SDL_RWFromMem( data.Data,data.Length )
|
|
|
- Local chunk:=Mix_LoadWAV_RW( rwops,1 )
|
|
|
- If Not chunk Return Null
|
|
|
-
|
|
|
- Return New Sound( chunk )
|
|
|
+ Local sound:=New Sound( data )
|
|
|
+
|
|
|
+ data.Discard()
|
|
|
+ Return sound
|
|
|
End
|
|
|
|
|
|
Private
|
|
|
|
|
|
- Field _chunk:Mix_Chunk Ptr
|
|
|
-
|
|
|
- Method New( chunk:Mix_Chunk Ptr )
|
|
|
- _chunk=chunk
|
|
|
- End
|
|
|
-
|
|
|
+ Field _alBuffer:ALuint
|
|
|
+
|
|
|
End
|
|
|
|
|
|
-#rem monkeydoc The Channel class.
|
|
|
-#end
|
|
|
Class Channel
|
|
|
|
|
|
- #rem monkeydoc Creates a new channel.
|
|
|
+ #rem monkeydoc Creates a new audio channel
|
|
|
#end
|
|
|
Method New()
|
|
|
+ Audio.FlushTmpChannels()
|
|
|
+ alGenSources( 1,Varptr _alSource )
|
|
|
End
|
|
|
-
|
|
|
- #rem monkeydoc True if this channel is playing audio.
|
|
|
+
|
|
|
+ #rem monkeydoc True if channel is playing audio.
|
|
|
|
|
|
If the channel is playing audio but is in the paused state, this property will still return true.
|
|
|
|
|
|
#end
|
|
|
Property Playing:Bool()
|
|
|
- If Invalid Return False
|
|
|
- Return Mix_Playing( _id & 31 )
|
|
|
+ If Not _alSource Return False
|
|
|
+ UpdateALState()
|
|
|
+ Return _alState=AL_PLAYING Or _alState=AL_PAUSED
|
|
|
+ End
|
|
|
+
|
|
|
+ #rem monkeydoc True if channel is paused.
|
|
|
+ #end
|
|
|
+ Property Paused:Bool()
|
|
|
+ If Not _alSource Return False
|
|
|
+ UpdateALState()
|
|
|
+ Return _alState=AL_PAUSED
|
|
|
+ Setter( paused:Bool )
|
|
|
+ If Not Playing Return
|
|
|
+ If paused
|
|
|
+ alSourcePause( _alSource )
|
|
|
+ Else
|
|
|
+ alSourcePlay( _alSource )
|
|
|
+ Endif
|
|
|
+ UpdateALState()
|
|
|
End
|
|
|
|
|
|
#rem monkeydoc Channel volume in the range 0 to 1.
|
|
|
#end
|
|
|
Property Volume:Float()
|
|
|
- If Invalid Return 0
|
|
|
Return _volume
|
|
|
Setter( volume:Float )
|
|
|
- If Invalid Return
|
|
|
+ If Not _alSource Return
|
|
|
_volume=Clamp( volume,0.0,1.0 )
|
|
|
- Mix_Volume( _id & 31,_volume*128 )
|
|
|
+ alSourcef( _alSource,AL_GAIN,_volume )
|
|
|
End
|
|
|
-
|
|
|
+
|
|
|
#rem monkeydoc Channel playback rate.
|
|
|
#end
|
|
|
Property Rate:Float()
|
|
|
- Return 1
|
|
|
+ Return _rate
|
|
|
Setter( rate:Float )
|
|
|
+ If Not _alSource Return
|
|
|
+ _rate=rate
|
|
|
+ alSourcef( _alSource,AL_PITCH,_rate )
|
|
|
End
|
|
|
|
|
|
- #rem monkeydoc Channel pan in the range -1 to 1.
|
|
|
- #end
|
|
|
+ #rem monkeydoc Channel pan in the range -1 (left) to 1 (right).
|
|
|
+ #end
|
|
|
Property Pan:Float()
|
|
|
- Return 0
|
|
|
- Setter( pan:Float )
|
|
|
+ Return _pan
|
|
|
+ Setter( pan:Float)
|
|
|
+ If Not _alSource Return
|
|
|
+ _pan=Clamp( pan,-1.0,1.0 )
|
|
|
+ Local x:=Sin( _pan ),z:=-Cos( _pan )
|
|
|
+ alSource3f( _alSource,AL_POSITION,x,0,z )
|
|
|
End
|
|
|
|
|
|
- #rem monkeydoc Channel paused state.
|
|
|
+ #rem monkeydoc Discard channel resources.
|
|
|
#end
|
|
|
- Property Paused:Bool()
|
|
|
- If Invalid Return False
|
|
|
- Return Mix_Paused( _id & 31 )
|
|
|
- Setter( paused:Bool )
|
|
|
- If Invalid Return
|
|
|
- Mix_Pause( _id & 31 )
|
|
|
+ Method Discard()
|
|
|
+ If Not _alSource Return
|
|
|
+ alDeleteSources( 1,Varptr _alSource )
|
|
|
+ _alSource=0
|
|
|
+ End
|
|
|
+
|
|
|
+ #rem monkeydoc Play a sound through the channel.
|
|
|
+ #end
|
|
|
+ Method Play( sound:Sound,loop:Bool=False )
|
|
|
+ If Not _alSource Or Not sound Or Not sound._alBuffer Return
|
|
|
+
|
|
|
+ alSourcei( _alSource,AL_LOOPING,loop ? AL_TRUE Else AL_FALSE )
|
|
|
+
|
|
|
+ alSourcei( _alSource,AL_BUFFER,sound._alBuffer )
|
|
|
+
|
|
|
+ alSourcePlay( _alSource )
|
|
|
End
|
|
|
|
|
|
- #rem monkeydoc Plays a sound through the channel.
|
|
|
+ #rem monkeydoc @hidden - Highly experimental!!!!!
|
|
|
#end
|
|
|
- Method Play:Bool( sound:Sound,loops:Int=0 )
|
|
|
+ Method Queue( data:AudioData )
|
|
|
|
|
|
- Local id:=_id & 31
|
|
|
- If _alloced[id]<>_id id=-1
|
|
|
+ If _waiting Return
|
|
|
+
|
|
|
+ If Not _buffers
|
|
|
+
|
|
|
+ Local n_bufs:=4 'need some work on these magic numbers!
|
|
|
+
|
|
|
+ _buffers=New ALuint[n_bufs]
|
|
|
+ _ubuffers=New ALuint[n_bufs]
|
|
|
+ _pbuffers=New Stack<ALuint>
|
|
|
|
|
|
- id=Mix_PlayChannel( id,sound._chunk,loops )
|
|
|
- If id<0 Return False
|
|
|
+ For Local i:=0 Until n_bufs
|
|
|
+ Local buf:ALuint
|
|
|
+ alGenBuffers( 1,Varptr buf )
|
|
|
+ _buffers[i]=buf
|
|
|
+ _pbuffers.Push( buf )
|
|
|
+ Next
|
|
|
+
|
|
|
+ _future=New Future<Int>
|
|
|
+
|
|
|
+ _waiting=False
|
|
|
+
|
|
|
+ _timer=New Timer( 25,Lambda()
|
|
|
+
|
|
|
+ FlushProcessed()
|
|
|
+ End )
|
|
|
+
|
|
|
+ Endif
|
|
|
+
|
|
|
+ If _pbuffers.Empty
|
|
|
+
|
|
|
+ FlushProcessed()
|
|
|
+
|
|
|
+ If _pbuffers.Empty
|
|
|
+ _waiting=True
|
|
|
+ If Not _future.Get() Return
|
|
|
+ Endif
|
|
|
+
|
|
|
+ Endif
|
|
|
|
|
|
- _alloced[id]+=32
|
|
|
- _id=_alloced[id]
|
|
|
+ Local buf:=_pbuffers.Pop()
|
|
|
|
|
|
- _volume=1
|
|
|
+ alBufferData( buf,ALFormat( data.Format ),data.Data,data.Size,data.Hertz )
|
|
|
+
|
|
|
+ alSourceQueueBuffers( _alSource,1,Varptr buf )
|
|
|
+
|
|
|
+ UpdateALState()
|
|
|
+ If _alState=AL_INITIAL Or _alState=AL_STOPPED alSourcePlay( _alSource )
|
|
|
|
|
|
- Return True
|
|
|
End
|
|
|
|
|
|
- #rem monkeydoc Stops the channel.
|
|
|
+ #rem monkeydoc Stops channel.
|
|
|
#end
|
|
|
Method Stop()
|
|
|
- If Invalid Return
|
|
|
- Mix_HaltChannel( _id )
|
|
|
+ If Not _alSource Return
|
|
|
+ alSourceStop( _alSource )
|
|
|
End
|
|
|
-
|
|
|
+
|
|
|
Private
|
|
|
|
|
|
- Field _id:Int=0
|
|
|
+ Field _alSource:ALuint
|
|
|
+ Field _alState:ALenum=AL_INITIAL
|
|
|
Field _volume:Float=1
|
|
|
+ Field _rate:Float=1
|
|
|
+ Field _pan:Float=0
|
|
|
|
|
|
- Property Invalid:Bool()
|
|
|
- Return _alloced[_id&31]<>_id
|
|
|
- End
|
|
|
+ Field _buffers:ALuint[]
|
|
|
+ Field _ubuffers:ALuint[]
|
|
|
+ Field _pbuffers:Stack<ALuint>
|
|
|
+ Field _future:Future<Int>
|
|
|
+ Field _waiting:Bool
|
|
|
+ Field _timer:Timer
|
|
|
+
|
|
|
+ Method FlushProcessed:Int()
|
|
|
+
|
|
|
+ Local proc:ALint
|
|
|
+ alGetSourcei( _alSource,AL_BUFFERS_PROCESSED,Varptr proc )
|
|
|
+' Print "processed: "+proc
|
|
|
+ If Not proc Return 0
|
|
|
+
|
|
|
+ alSourceUnqueueBuffers( _alSource,proc,_ubuffers.Data )
|
|
|
+
|
|
|
+ For Local i:=0 Until proc
|
|
|
+ _pbuffers.Push( _ubuffers[i] )
|
|
|
+ Next
|
|
|
|
|
|
+ If _waiting
|
|
|
+ _waiting=False
|
|
|
+ _future.Set( proc )
|
|
|
+ Endif
|
|
|
+
|
|
|
+ Return proc
|
|
|
+
|
|
|
+ End
|
|
|
+
|
|
|
+ Method UpdateALState:ALenum()
|
|
|
+ alGetSourcei( _alSource,AL_SOURCE_STATE,Varptr _alState )
|
|
|
+ Return _alState
|
|
|
+ End
|
|
|
+
|
|
|
End
|