| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- #rem monkeydoc
- Highly experimental audio module!
- #end
- Namespace mojo.audio
- #Import "native/bbmusic.cpp"
- #Import "native/bbmusic.h"
- Extern Private
- Function playMusic:Int( file:libc.FILE ptr,callback:Int,source:Int )="bbMusic::playMusic"
- Function getBuffersProcessed:Int( source:Int )="bbMusic::getBuffersProcessed"
- Function endMusic:Void( source:Int )="bbMusic::endMusic"
-
- Private
- Const MUSIC_BUFFER_MS:=100
- Const MUSIC_BUFFER_SECS:=0.1
- 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
- #rem monkeydoc The AudioDevice class.
- An instance of the AudioDevice class is automatically created when an [[AppInstance]] is created, and can be accessed via the global [[Audio]] const.
- #end
- Class AudioDevice
-
- #rem monkeydoc Starts streaming audio playback.
-
- PlayMusic starts a piece of audio streaming from a file in the background.
- When the audio finishes, the optional `finished` function is invoked.
-
- The returned [[Channel]] instance is automatically discarded when the audio stops, so should not be discarded by your code.
-
- The audio file must be in .ogg format.
-
- #end
- Method PlayMusic:Channel( path:String,finished:Void()=Null,paused:Bool=False )
- 'DO NOT use AutoDiscard here or music wont receive 'stop' signal!
- '
- Local channel:=New Channel( ChannelFlags.Music )
-
- Local callback:=async.CreateAsyncCallback( Lambda()
- channel.Discard()
- endMusic( channel._alSource )
- finished()
- End,True )
-
- Local file:=filesystem.OpenCFile( path,"r" )
-
- Local sampleRate:=playMusic( file,callback,channel._alSource )
-
- If Not sampleRate
- async.DestroyAsyncCallback( callback )
- Return Null
- Endif
-
- channel._sampleRate=sampleRate
-
- Return channel
- End
- Internal
-
- Method Init()
-
- 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
-
- End
- #rem monkeydoc The Sound class.
- #end
- Class Sound Extends Resource
- #rem monkeydoc Creates a new sound.
- #end
- Method New( data:AudioData )
-
- alGenBuffers( 1,Varptr _alBuffer )
- alBufferData( _alBuffer,ALFormat( data.Format ),data.Data,data.Size,data.Hertz )
- _format=data.Format
- _length=data.Length
- _hertz=data.Hertz
- End
-
- #rem monkeydoc The length, in samples, of the sound.
- #end
- Property Length:Int()
-
- Return _length
- End
-
- #rem monkeydoc The format of the sound.
- #end
- Property Format:AudioFormat()
-
- Return _format
- End
-
- #rem monkeydoc The playback rate of the sound.
- #end
- Property Hertz:Int()
-
- Return _hertz
- End
-
- #rem monkeydoc The duration, in seconds, of the sound.
- #end
- Property Duration:Double()
-
- Return Double(_length)/Double(_hertz)
- 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:=New Channel( ChannelFlags.AutoDiscard )
-
- channel.Play( Self,loop )
-
- Return channel
- End
-
- #rem monkeydoc Loads a sound.
- #end
- Function Load:Sound( path:String )
-
- Local data:=AudioData.Load( path )
- If Not data Return Null
-
- Local sound:=New Sound( data )
- data.Discard()
- Return sound
- End
-
- Protected
-
- Method OnDiscard() Override
-
- If _alBuffer alDeleteBuffers( 1,Varptr _alBuffer )
-
- _alBuffer=0
- End
-
- Method OnFinalize() Override
-
- If _alBuffer alDeleteBuffers( 1,Varptr _alBuffer )
- End
-
- Private
-
- Field _alBuffer:ALuint
- Field _format:AudioFormat
- Field _length:Int
- Field _hertz:Int
- End
- #rem monkeydoc ChannelFlags enum.
- | Flag | Description
- |:--------------|:-----------
- | `AutoDiscard` | Channel will be automatically discarded when it finishes playing, or when it is stopped using [[Channel.Stop]].
- #end
- Enum ChannelFlags
-
- AutoDiscard=1
- Music=2
- End
- Class Channel Extends Resource
- #rem monkeydoc Creates a new audio channel.
-
- If `flags` is ChannelFlags.AutoDiscard, then the channel will be automatically discarded when it finishes playing, or when it is
- stopped using [[Stop]].
-
- #end
- Method New( flags:ChannelFlags=Null )
-
- _flags=flags
-
- FlushAutoDiscard()
-
- alGenSources( 1,Varptr _alSource )
-
- If _flags & ChannelFlags.AutoDiscard _autoDiscard.Push( Self )
- End
-
- Property Flags:ChannelFlags()
-
- Return _flags
- End
-
- #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 Not _alSource Return False
-
- Local state:=ALState()
- Return state=AL_PLAYING Or state=AL_PAUSED
- End
-
- #rem monkeydoc True if channel is paused.
- #end
- Property Paused:Bool()
- If Not _alSource Return False
-
- Return ALState()=AL_PAUSED
-
- Setter( paused:Bool )
- If Not Playing Return
-
- If paused
- alSourcePause( _alSource )
- Else
- alSourcePlay( _alSource )
- Endif
- End
-
- #rem monkeydoc Channel volume in the range 0 to 1.
- #end
- Property Volume:Float()
- If Not _alSource Return 0
-
- Return _volume
-
- Setter( volume:Float )
- If Not _alSource Return
-
- _volume=Clamp( volume,0.0,1.0 )
- alSourcef( _alSource,AL_GAIN,_volume )
- End
-
- #rem monkeydoc Channel playback rate.
- #end
- Property Rate:Float()
- If Not _alSource Return 0
- 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 (left) to 1 (right).
- #end
- Property Pan:Float()
- If Not _alSource Return 0
-
- 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 playhead sample offset.
-
- Gets or sets the sample offset of the sound currently playing.
-
- If the channel is playing when set, playback is immediately affected.
-
- If the channel is not playing when set, the offset will be applied when the Play method is invoked.
-
- #end
- Property PlayheadSample:Int()
- If Not _alSource Return 0
-
- Local sample:Int
- alGetSourcei( _alSource,AL_SAMPLE_OFFSET,Varptr sample )
-
- If _flags & ChannelFlags.Music
-
- Local samplesPerBuffer:=MUSIC_BUFFER_MS * _sampleRate / 1000
-
- Local buffersProcessed:=getBuffersProcessed( _alSource )
-
- sample+=samplesPerBuffer * buffersProcessed
-
- If sample<_sample
- ' Print "Sample catch up:"+sample+"->"+_sample
- While sample<_sample
- sample+=samplesPerBuffer
- Wend
- Else
- _sample=sample
- Endif
- Endif
-
- Return sample
-
- Setter( sample:Int )
- If Not _alSource Return
-
- alSourcei( _alSource,AL_SAMPLE_OFFSET,sample )
- End
-
- #rem monkeydoc Channel playhead time offset.
- Gets or sets the time offset of the sound currently playing.
-
- If the channel is playing when set, playback is immediately affected.
-
- If the channel is not playing when set, the offset will be applied when the Play method is invoked.
-
- #end
- Property PlayheadTime:Float()
- If Not _alSource Return 0
- Local time:Float
- alGetSourcef( _alSource,AL_SEC_OFFSET,Varptr time )
-
- If _flags & ChannelFlags.Music
-
- Local buffersProcessed:=getBuffersProcessed( _alSource )
-
- time+=MUSIC_BUFFER_SECS * buffersProcessed
-
- If time<_time
- ' Print "Time catchup: "+time+"->"+_time
- While time<_time
- time+=MUSIC_BUFFER_SECS
- Wend
- Else
- _time=time
- Endif
- Endif
-
- Return time
-
- Setter( time:Float )
- If Not _alSource Return
-
- alSourcef( _alSource,AL_SEC_OFFSET,time )
- End
-
- #rem monkeydoc Plays 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
- #if __TARGET__<>"emscripten"
- #rem monkeydoc @hidden - Highly experimental!!!!!
- #end
- Method WaitQueued( queued:Int )
-
- While _queued>queued
-
- FlushProcessed()
-
- If _queued<=queued Return
-
- _waiting=True
-
- _future.Get()
-
- Wend
- End
-
- #rem monkeydoc @hidden - Highly experimental!!!!!
- #end
- Method Queue( data:AudioData )
-
- Local buf:ALuint
-
- If Not _tmpBuffers
-
- _tmpBuffers=New Stack<ALuint>
- _freeBuffers=New Stack<ALuint>
- _future=New Future<Int>
- _waiting=False
- _queued=0
-
- _timer=New Timer( 60,Lambda()
- FlushProcessed()
- End )
- Endif
-
- If _freeBuffers.Empty
-
- alGenBuffers( 1,Varptr buf )
- _tmpBuffers.Push( buf )
-
- Else
- buf=_freeBuffers.Pop()
- Endif
-
- alBufferData( buf,ALFormat( data.Format ),data.Data,data.Size,data.Hertz )
-
- alSourceQueueBuffers( _alSource,1,Varptr buf )
- _queued+=1
-
- Local state:=ALState()
- If state=AL_INITIAL Or state=AL_STOPPED alSourcePlay( _alSource )
-
- End
-
- #endif
-
- #rem monkeydoc Stops channel.
- #end
- Method Stop()
- If Not _alSource Return
- alSourceStop( _alSource )
-
- If _flags & ChannelFlags.AutoDiscard Discard()
- End
-
- Protected
-
- #rem monkeydoc @hidden
- #end
- Method OnDiscard() Override
- If _alSource alDeleteSources( 1,Varptr _alSource )
-
- _alSource=0
- End
-
- #rem monkeydoc @hidden
- #end
- Method OnFinalize() Override
- If _alSource alDeleteSources( 1,Varptr _alSource )
- End
- Private
-
- Field _flags:ChannelFlags
- Field _alSource:ALuint
- Field _volume:Float=1
- Field _rate:Float=1
- Field _pan:Float=0
-
- Field _sampleRate:Int
-
- Field _time:float
- Field _sample:Int
-
- Global _autoDiscard:=New Stack<Channel>
-
- Method ALState:ALenum()
- Local state:ALenum
- alGetSourcei( _alSource,AL_SOURCE_STATE,Varptr state )
- Return state
- End
-
- Function FlushAutoDiscard()
-
- Local put:=0
-
- For Local chan:=Eachin _autoDiscard
- If Not chan._alSource Continue
-
- If chan.ALState()<>AL_STOPPED
- _autoDiscard[put]=chan;put+=1
- Continue
- Endif
-
- chan.Discard()
- Next
- _autoDiscard.Resize( put )
- End
-
- #if __TARGET__<>"emscripten"
-
- Field _tmpBuffers:Stack<ALuint>
- Field _freeBuffers:Stack<ALuint>
- Field _future:Future<Int>
- Field _waiting:Bool
- Field _queued:Int
- Field _timer:Timer
-
- Method FlushProcessed:Int()
-
- Local proc:ALint
- alGetSourcei( _alSource,AL_BUFFERS_PROCESSED,Varptr proc )
-
- If Not proc Return 0
-
- For Local i:=0 Until proc
-
- Local buf:ALuint
-
- alSourceUnqueueBuffers( _alSource,1,Varptr buf )
- _queued-=1
-
- If _tmpBuffers.Contains( buf ) _freeBuffers.Push( buf )
- Next
- If _waiting
- _waiting=False
- _future.Set( proc )
- Endif
-
- Return proc
- End
-
- #endif
-
- End
|