Просмотр исходного кода

Updated mojo.audio to use openal.

Mark Sibly 9 лет назад
Родитель
Сommit
58ed01af63
4 измененных файлов с 247 добавлено и 70 удалено
  1. 1 1
      modules/mojo/app/app.monkey2
  2. 243 67
      modules/mojo/audio/audio.monkey2
  3. 1 1
      modules/mojo/module.json
  4. 2 1
      modules/mojo/mojo.monkey2

+ 1 - 1
modules/mojo/app/app.monkey2

@@ -61,7 +61,7 @@ Class AppInstance
 	
 		App=Self
 	
-		SDL_Init( SDL_INIT_EVERYTHING )
+		SDL_Init( SDL_INIT_EVERYTHING & ~SDL_INIT_AUDIO )
 		
 		_sdlThread=SDL_ThreadID()
 		

+ 243 - 67
modules/mojo/audio/audio.monkey2

@@ -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

+ 1 - 1
modules/mojo/module.json

@@ -1,5 +1,5 @@
 {
 	"module":"mojo",
 	"version":"1.0.0",
-	"depends":["freetype","emscripten","std","sdl2","sdl2-mixer","gles20"]
+	"depends":["freetype","emscripten","std","sdl2","gles20","openal"]
 }

+ 2 - 1
modules/mojo/mojo.monkey2

@@ -5,7 +5,7 @@ Namespace mojo
 #Import "<std>"
 #Import "<sdl2>"
 #Import "<gles20>"
-#Import "<sdl2-mixer>"
+#Import "<openal>"
 
 #Import "app/app"
 #Import "app/event"
@@ -41,6 +41,7 @@ Using emscripten..
 Using std..
 Using sdl2..
 Using gles20..
+Using openal..
 Using mojo..
 
 Private