audio.monkey2 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. #rem monkeydoc
  2. Highly experimental audio module!
  3. #end
  4. Namespace mojo.audio
  5. #Import "native/bbmusic.cpp"
  6. #Import "native/bbmusic.h"
  7. Extern Private
  8. Function playMusic:Int( file:libc.FILE ptr,callback:Int,source:Int )="bbMusic::playMusic"
  9. Function getBuffersProcessed:Int( source:Int )="bbMusic::getBuffersProcessed"
  10. Function endMusic:Void( source:Int )="bbMusic::endMusic"
  11. Private
  12. Const MUSIC_BUFFER_MS:=100
  13. Const MUSIC_BUFFER_SECS:=0.1
  14. Function ALFormat:ALenum( format:AudioFormat )
  15. Local alFormat:ALenum
  16. Select format
  17. Case AudioFormat.Mono8
  18. alFormat=AL_FORMAT_MONO8
  19. Case AudioFormat.Mono16
  20. alFormat=AL_FORMAT_MONO16
  21. Case AudioFormat.Stereo8
  22. alFormat=AL_FORMAT_STEREO8
  23. Case AudioFormat.Stereo16
  24. alFormat=AL_FORMAT_STEREO16
  25. End
  26. Return alFormat
  27. End
  28. Public
  29. #rem monkeydoc Global instance of the AudioDevice class.
  30. #end
  31. Const Audio:=New AudioDevice
  32. #rem monkeydoc The AudioDevice class.
  33. An instance of the AudioDevice class is automatically created when an [[AppInstance]] is created, and can be accessed via the global [[Audio]] const.
  34. #end
  35. Class AudioDevice
  36. #rem monkeydoc Starts streaming audio playback.
  37. PlayMusic starts a piece of audio streaming from a file in the background.
  38. When the audio finishes, the optional `finished` function is invoked.
  39. The returned [[Channel]] instance is automatically discarded when the audio stops, so should not be discarded by your code.
  40. The audio file must be in .ogg format.
  41. #end
  42. Method PlayMusic:Channel( path:String,finished:Void()=Null,paused:Bool=False )
  43. 'DO NOT use AutoDiscard here or music wont receive 'stop' signal!
  44. '
  45. Local channel:=New Channel( ChannelFlags.Music )
  46. Local callback:=async.CreateAsyncCallback( Lambda()
  47. channel.Discard()
  48. endMusic( channel._alSource )
  49. finished()
  50. End,True )
  51. Local file:=filesystem.OpenCFile( path,"rb" )
  52. Local sampleRate:=playMusic( file,callback,channel._alSource )
  53. If Not sampleRate
  54. async.DestroyAsyncCallback( callback )
  55. Return Null
  56. Endif
  57. channel._sampleRate=sampleRate
  58. Return channel
  59. End
  60. Internal
  61. Method Init()
  62. Local error:=""
  63. _alcDevice=alcOpenDevice( Null )
  64. If _alcDevice
  65. _alcContext=alcCreateContext( _alcDevice,Null )
  66. If _alcContext
  67. If alcMakeContextCurrent( _alcContext )
  68. Return
  69. Else
  70. error="Failed to make OpenAL current"
  71. Endif
  72. Else
  73. error="Failed to create OpenAL context"
  74. Endif
  75. Else
  76. error="Failed to create OpenAL device"
  77. Endif
  78. End
  79. Private
  80. Field _alcDevice:ALCdevice Ptr
  81. Field _alcContext:ALCcontext Ptr
  82. Field _error:String
  83. End
  84. #rem monkeydoc The Sound class.
  85. #end
  86. Class Sound Extends Resource
  87. #rem monkeydoc Creates a new sound.
  88. #end
  89. Method New( data:AudioData )
  90. alGenBuffers( 1,Varptr _alBuffer )
  91. alBufferData( _alBuffer,ALFormat( data.Format ),data.Data,data.Size,data.Hertz )
  92. _format=data.Format
  93. _length=data.Length
  94. _hertz=data.Hertz
  95. End
  96. #rem monkeydoc The length, in samples, of the sound.
  97. #end
  98. Property Length:Int()
  99. Return _length
  100. End
  101. #rem monkeydoc The format of the sound.
  102. #end
  103. Property Format:AudioFormat()
  104. Return _format
  105. End
  106. #rem monkeydoc The playback rate of the sound.
  107. #end
  108. Property Hertz:Int()
  109. Return _hertz
  110. End
  111. #rem monkeydoc The duration, in seconds, of the sound.
  112. #end
  113. Property Duration:Double()
  114. Return Double(_length)/Double(_hertz)
  115. End
  116. #rem monkeydoc Plays a sound through a temporary channel.
  117. The returned channel will be automatically discarded when it finishes playing or is stopped with [[Channel.Stop]].
  118. #end
  119. Method Play:Channel( loop:Bool=False )
  120. Local channel:=New Channel( ChannelFlags.AutoDiscard )
  121. channel.Play( Self,loop )
  122. Return channel
  123. End
  124. #rem monkeydoc Loads a sound.
  125. #end
  126. Function Load:Sound( path:String )
  127. Local data:=AudioData.Load( path )
  128. If Not data Return Null
  129. Local sound:=New Sound( data )
  130. data.Discard()
  131. Return sound
  132. End
  133. Protected
  134. Method OnDiscard() Override
  135. If _alBuffer alDeleteBuffers( 1,Varptr _alBuffer )
  136. _alBuffer=0
  137. End
  138. Method OnFinalize() Override
  139. If _alBuffer alDeleteBuffers( 1,Varptr _alBuffer )
  140. End
  141. Private
  142. Field _alBuffer:ALuint
  143. Field _format:AudioFormat
  144. Field _length:Int
  145. Field _hertz:Int
  146. End
  147. #rem monkeydoc ChannelFlags enum.
  148. | Flag | Description
  149. |:--------------|:-----------
  150. | `AutoDiscard` | Channel will be automatically discarded when it finishes playing, or when it is stopped using [[Channel.Stop]].
  151. #end
  152. Enum ChannelFlags
  153. AutoDiscard=1
  154. Music=2
  155. End
  156. Class Channel Extends Resource
  157. #rem monkeydoc Creates a new audio channel.
  158. If `flags` is ChannelFlags.AutoDiscard, then the channel will be automatically discarded when it finishes playing, or when it is
  159. stopped using [[Stop]].
  160. #end
  161. Method New( flags:ChannelFlags=Null )
  162. _flags=flags
  163. FlushAutoDiscard()
  164. alGenSources( 1,Varptr _alSource )
  165. If _flags & ChannelFlags.AutoDiscard _autoDiscard.Push( Self )
  166. End
  167. Property Flags:ChannelFlags()
  168. Return _flags
  169. End
  170. #rem monkeydoc True if channel is playing audio.
  171. If the channel is playing audio but is in the paused state, this property will still return true.
  172. #end
  173. Property Playing:Bool()
  174. If Not _alSource Return False
  175. Local state:=ALState()
  176. Return state=AL_PLAYING Or state=AL_PAUSED
  177. End
  178. #rem monkeydoc True if channel is paused.
  179. #end
  180. Property Paused:Bool()
  181. If Not _alSource Return False
  182. Return ALState()=AL_PAUSED
  183. Setter( paused:Bool )
  184. If Not Playing Return
  185. If paused
  186. alSourcePause( _alSource )
  187. Else
  188. alSourcePlay( _alSource )
  189. Endif
  190. End
  191. #rem monkeydoc Channel volume in the range 0 to 1.
  192. #end
  193. Property Volume:Float()
  194. If Not _alSource Return 0
  195. Return _volume
  196. Setter( volume:Float )
  197. If Not _alSource Return
  198. _volume=Clamp( volume,0.0,1.0 )
  199. alSourcef( _alSource,AL_GAIN,_volume )
  200. End
  201. #rem monkeydoc Channel playback rate.
  202. #end
  203. Property Rate:Float()
  204. If Not _alSource Return 0
  205. Return _rate
  206. Setter( rate:Float )
  207. If Not _alSource Return
  208. _rate=rate
  209. alSourcef( _alSource,AL_PITCH,_rate )
  210. End
  211. #rem monkeydoc Channel pan in the range -1 (left) to 1 (right).
  212. #end
  213. Property Pan:Float()
  214. If Not _alSource Return 0
  215. Return _pan
  216. Setter( pan:Float)
  217. If Not _alSource Return
  218. _pan=Clamp( pan,-1.0,1.0 )
  219. Local x:=Sin( _pan ),z:=-Cos( _pan )
  220. alSource3f( _alSource,AL_POSITION,x,0,z )
  221. End
  222. #rem monkeydoc Channel playhead sample offset.
  223. Gets or sets the sample offset of the sound currently playing.
  224. If the channel is playing when set, playback is immediately affected.
  225. If the channel is not playing when set, the offset will be applied when the Play method is invoked.
  226. #end
  227. Property PlayheadSample:Int()
  228. If Not _alSource Return 0
  229. Local sample:Int
  230. alGetSourcei( _alSource,AL_SAMPLE_OFFSET,Varptr sample )
  231. If _flags & ChannelFlags.Music
  232. Local samplesPerBuffer:=MUSIC_BUFFER_MS * _sampleRate / 1000
  233. Local buffersProcessed:=getBuffersProcessed( _alSource )
  234. sample+=samplesPerBuffer * buffersProcessed
  235. If sample<_sample
  236. ' Print "Sample catch up:"+sample+"->"+_sample
  237. While sample<_sample
  238. sample+=samplesPerBuffer
  239. Wend
  240. Else
  241. _sample=sample
  242. Endif
  243. Endif
  244. Return sample
  245. Setter( sample:Int )
  246. If Not _alSource Return
  247. alSourcei( _alSource,AL_SAMPLE_OFFSET,sample )
  248. End
  249. #rem monkeydoc Channel playhead time offset.
  250. Gets or sets the time offset of the sound currently playing.
  251. If the channel is playing when set, playback is immediately affected.
  252. If the channel is not playing when set, the offset will be applied when the Play method is invoked.
  253. #end
  254. Property PlayheadTime:Float()
  255. If Not _alSource Return 0
  256. Local time:Float
  257. alGetSourcef( _alSource,AL_SEC_OFFSET,Varptr time )
  258. If _flags & ChannelFlags.Music
  259. Local buffersProcessed:=getBuffersProcessed( _alSource )
  260. time+=MUSIC_BUFFER_SECS * buffersProcessed
  261. If time<_time
  262. ' Print "Time catchup: "+time+"->"+_time
  263. While time<_time
  264. time+=MUSIC_BUFFER_SECS
  265. Wend
  266. Else
  267. _time=time
  268. Endif
  269. Endif
  270. Return time
  271. Setter( time:Float )
  272. If Not _alSource Return
  273. alSourcef( _alSource,AL_SEC_OFFSET,time )
  274. End
  275. #rem monkeydoc Plays a sound through the channel.
  276. #end
  277. Method Play( sound:Sound,loop:Bool=False )
  278. If Not _alSource Or Not sound Or Not sound._alBuffer Return
  279. alSourcei( _alSource,AL_LOOPING,loop ? AL_TRUE Else AL_FALSE )
  280. alSourcei( _alSource,AL_BUFFER,sound._alBuffer )
  281. alSourcePlay( _alSource )
  282. End
  283. #if __TARGET__<>"emscripten"
  284. #rem monkeydoc @hidden - Highly experimental!!!!!
  285. #end
  286. Method WaitQueued( queued:Int )
  287. While _queued>queued
  288. FlushProcessed()
  289. If _queued<=queued Return
  290. _waiting=True
  291. _future.Get()
  292. Wend
  293. End
  294. #rem monkeydoc @hidden - Highly experimental!!!!!
  295. #end
  296. Method Queue( data:AudioData )
  297. Local buf:ALuint
  298. If Not _tmpBuffers
  299. _tmpBuffers=New Stack<ALuint>
  300. _freeBuffers=New Stack<ALuint>
  301. _future=New Future<Int>
  302. _waiting=False
  303. _queued=0
  304. _timer=New Timer( 60,Lambda()
  305. FlushProcessed()
  306. End )
  307. Endif
  308. If _freeBuffers.Empty
  309. alGenBuffers( 1,Varptr buf )
  310. _tmpBuffers.Push( buf )
  311. Else
  312. buf=_freeBuffers.Pop()
  313. Endif
  314. alBufferData( buf,ALFormat( data.Format ),data.Data,data.Size,data.Hertz )
  315. alSourceQueueBuffers( _alSource,1,Varptr buf )
  316. _queued+=1
  317. Local state:=ALState()
  318. If state=AL_INITIAL Or state=AL_STOPPED alSourcePlay( _alSource )
  319. End
  320. #endif
  321. #rem monkeydoc Stops channel.
  322. #end
  323. Method Stop()
  324. If Not _alSource Return
  325. alSourceStop( _alSource )
  326. If _flags & ChannelFlags.AutoDiscard Discard()
  327. End
  328. Protected
  329. #rem monkeydoc @hidden
  330. #end
  331. Method OnDiscard() Override
  332. If _alSource alDeleteSources( 1,Varptr _alSource )
  333. _alSource=0
  334. End
  335. #rem monkeydoc @hidden
  336. #end
  337. Method OnFinalize() Override
  338. If _alSource alDeleteSources( 1,Varptr _alSource )
  339. End
  340. Private
  341. Field _flags:ChannelFlags
  342. Field _alSource:ALuint
  343. Field _volume:Float=1
  344. Field _rate:Float=1
  345. Field _pan:Float=0
  346. Field _sampleRate:Int
  347. Field _time:float
  348. Field _sample:Int
  349. Global _autoDiscard:=New Stack<Channel>
  350. Method ALState:ALenum()
  351. Local state:ALenum
  352. alGetSourcei( _alSource,AL_SOURCE_STATE,Varptr state )
  353. Return state
  354. End
  355. Function FlushAutoDiscard()
  356. Local put:=0
  357. For Local chan:=Eachin _autoDiscard
  358. If Not chan._alSource Continue
  359. If chan.ALState()<>AL_STOPPED
  360. _autoDiscard[put]=chan;put+=1
  361. Continue
  362. Endif
  363. chan.Discard()
  364. Next
  365. _autoDiscard.Resize( put )
  366. End
  367. #if __TARGET__<>"emscripten"
  368. Field _tmpBuffers:Stack<ALuint>
  369. Field _freeBuffers:Stack<ALuint>
  370. Field _future:Future<Int>
  371. Field _waiting:Bool
  372. Field _queued:Int
  373. Field _timer:Timer
  374. Method FlushProcessed:Int()
  375. Local proc:ALint
  376. alGetSourcei( _alSource,AL_BUFFERS_PROCESSED,Varptr proc )
  377. If Not proc Return 0
  378. For Local i:=0 Until proc
  379. Local buf:ALuint
  380. alSourceUnqueueBuffers( _alSource,1,Varptr buf )
  381. _queued-=1
  382. If _tmpBuffers.Contains( buf ) _freeBuffers.Push( buf )
  383. Next
  384. If _waiting
  385. _waiting=False
  386. _future.Set( proc )
  387. Endif
  388. Return proc
  389. End
  390. #endif
  391. End