]> gitweb.factorcode.org Git - factor.git/blob - extra/audio/engine/engine.factor
specialized-arrays: performed some cleanup.
[factor.git] / extra / audio / engine / engine.factor
1 ! (c)2009 Joe Groff bsd license
2 USING: accessors alien alien.data audio classes.struct fry
3 calendar timers combinators combinators.short-circuit
4 destructors generalizations kernel literals locals math openal
5 sequences sequences.generalizations specialized-arrays strings ;
6 QUALIFIED-WITH: alien.c-types c
7 SPECIALIZED-ARRAYS: c:float c:uchar c:uint ;
8 IN: audio.engine
9
10 TUPLE: audio-source
11     { position initial: { 0.0 0.0 0.0 } }
12     { gain float initial: 1.0 }
13     { velocity initial: { 0.0 0.0 0.0 } }
14     { relative? boolean initial: f }
15     { distance float initial: 1.0 }
16     { rolloff float initial: 1.0 } ;
17
18 TUPLE: audio-orientation
19     { forward initial: { 0.0 0.0 -1.0 } }
20     { up initial: { 0.0 1.0 0.0 } } ;
21
22 C: <audio-orientation> audio-orientation
23
24 : orientation>float-array ( orientation -- float-array )
25     [ forward>> first3 ]
26     [ up>> first3 ] bi 6 float-array{ } nsequence ; inline
27
28 TUPLE: audio-listener
29     { position initial: { 0.0 0.0 0.0 } }
30     { gain float initial: 1.0 }
31     { velocity initial: { 0.0 0.0 0.0 } }
32     { orientation initial: T{ audio-orientation } } ;
33
34 GENERIC: audio-position ( source/listener -- position )
35 GENERIC: audio-gain ( source/listener -- gain )
36 GENERIC: audio-velocity ( source/listener -- velocity )
37 GENERIC: audio-relative? ( source -- relative? )
38 GENERIC: audio-distance ( source -- distance )
39 GENERIC: audio-rolloff ( source -- rolloff )
40 GENERIC: audio-orientation ( listener -- orientation )
41
42 M: object audio-position drop { 0.0 0.0 0.0 } ; inline
43 M: object audio-gain drop 1.0 ; inline
44 M: object audio-velocity drop { 0.0 0.0 0.0 } ; inline
45 M: object audio-relative? drop f ; inline
46 M: object audio-distance drop 1.0 ; inline
47 M: object audio-rolloff drop 1.0 ; inline
48 M: object audio-orientation drop T{ audio-orientation } ; inline
49
50 M: audio-source audio-position position>> ; inline
51 M: audio-source audio-gain gain>> ; inline
52 M: audio-source audio-velocity velocity>> ; inline
53 M: audio-source audio-relative? relative?>> ; inline
54 M: audio-source audio-distance distance>> ; inline
55 M: audio-source audio-rolloff rolloff>> ; inline
56
57 M: audio-listener audio-position position>> ; inline
58 M: audio-listener audio-gain gain>> ; inline
59 M: audio-listener audio-velocity velocity>> ; inline
60 M: audio-listener audio-orientation orientation>> ; inline
61
62 GENERIC: generate-audio ( generator -- c-ptr size )
63 GENERIC: generator-audio-format ( generator -- channels sample-bits sample-rate )
64
65 TUPLE: audio-engine < disposable
66     { voice-count integer }
67     { al-device c-ptr }
68     { al-context c-ptr }
69     al-sources
70     listener
71     { next-source integer }
72     clips
73     update-timer ;
74
75 TUPLE: audio-clip < disposable
76     { audio-engine audio-engine }
77     source
78     { al-source integer } ;
79
80 TUPLE: static-audio-clip < audio-clip
81     { al-buffer integer } ;
82
83 TUPLE: streaming-audio-clip < audio-clip
84     generator
85     { channels integer }
86     { sample-bits integer }
87     { sample-rate integer }
88     { al-buffers uint-array }
89     { done? boolean } ;
90
91 ERROR: audio-device-not-found device-name ;
92 ERROR: audio-context-not-available device-name ;
93
94 :: <audio-engine> ( device-name voice-count -- engine )
95     [
96         device-name alcOpenDevice :> al-device
97         al-device [ device-name audio-device-not-found ] unless
98         al-device |alcCloseDevice* drop
99
100         al-device f alcCreateContext :> al-context
101         al-context [ device-name audio-context-not-available ] unless
102         al-context |alcDestroyContext drop
103
104         al-context alcSuspendContext
105
106         audio-engine new-disposable
107             voice-count >>voice-count
108             al-device >>al-device
109             al-context >>al-context
110     ] with-destructors ;
111
112 : <standard-audio-engine> ( -- engine )
113     f 16 <audio-engine> ;
114
115 <PRIVATE
116
117 : make-engine-current ( audio-engine -- )
118     al-context>> alcMakeContextCurrent drop ; inline
119
120 : allocate-sources ( audio-engine -- sources )
121     voice-count>> dup c:uint (c-array) [ alGenSources ] keep ; inline
122
123 :: flush-source ( al-source -- )
124     al-source alSourceStop
125     0 c:uint <ref> :> dummy-buffer
126     al-source AL_BUFFERS_PROCESSED get-source-param [
127         al-source 1 dummy-buffer alSourceUnqueueBuffers
128     ] times
129     al-source AL_BUFFER 0 alSourcei ;
130
131 : free-sources ( sources -- )
132     [ length ] keep alDeleteSources ; inline
133
134 :: (get-available-source) ( sources source# stop-source# -- next-source# al-source/f )
135     source# sources nth :> al-source
136     source# 1 + sources length mod :> next-source#
137     al-source {
138         [ AL_BUFFERS_PROCESSED get-source-param 0 = ]
139         [ AL_BUFFERS_QUEUED get-source-param 0 = ]
140         [ AL_SOURCE_STATE get-source-param { $ AL_INITIAL $ AL_STOPPED } member? ]
141     } 1&&
142     [ next-source# al-source ] [
143         next-source# stop-source# =
144         [ next-source# f ]
145         [ sources next-source# stop-source# (get-available-source) ] if
146     ] if ;
147
148 :: get-available-source ( audio-engine -- al-source/f )
149     audio-engine [ al-sources>> ] [ next-source>> ] bi dup (get-available-source)
150         :> ( next-source al-source )
151     audio-engine next-source >>next-source drop
152     al-source ;
153
154 :: queue-clip-buffer ( audio-clip al-buffer -- )
155     audio-clip done?>> [
156         audio-clip al-source>> :> al-source
157         audio-clip generator>> :> generator
158         generator generate-audio :> ( data size )
159
160         size { [ not ] [ zero? ] } 1|| [
161             audio-clip t >>done? drop
162         ] [
163             al-buffer audio-clip openal-format data size audio-clip sample-rate>> alBufferData
164             al-source 1 al-buffer c:uint <ref> alSourceQueueBuffers
165         ] if
166     ] unless ;
167
168 : update-listener ( audio-engine -- )
169     listener>> {
170         [ AL_POSITION swap audio-position first3 alListener3f ]
171         [ AL_GAIN swap audio-gain alListenerf ]
172         [ AL_VELOCITY swap audio-velocity first3 alListener3f ]
173         [ AL_ORIENTATION swap audio-orientation orientation>float-array alListenerfv ]
174     } cleave ;
175
176 : update-source ( audio-clip -- )
177     [ al-source>> ] [ source>> ] bi {
178         [ AL_POSITION swap audio-position first3 alSource3f ]
179         [ AL_GAIN swap audio-gain alSourcef ]
180         [ AL_VELOCITY swap audio-velocity first3 alSource3f ]
181         [ AL_SOURCE_RELATIVE swap audio-relative? c:>c-bool alSourcei ]
182         [ AL_REFERENCE_DISTANCE swap audio-distance alSourcef ]
183         [ AL_ROLLOFF_FACTOR swap audio-rolloff alSourcef ]
184     } 2cleave ;
185
186 GENERIC: (update-audio-clip) ( audio-clip -- )
187
188 M: static-audio-clip (update-audio-clip)
189     drop ;
190
191 M:: streaming-audio-clip (update-audio-clip) ( audio-clip -- )
192     audio-clip al-source>> :> al-source
193     0 c:uint <ref> :> buffer
194     al-source AL_BUFFERS_PROCESSED get-source-param [
195         al-source 1 buffer alSourceUnqueueBuffers
196         audio-clip buffer c:uint deref queue-clip-buffer
197     ] times ;
198
199 : update-audio-clip ( audio-clip -- )
200     [ update-source ] [
201         dup al-source>> AL_SOURCE_STATE get-source-param AL_STOPPED = 
202         [ dispose ] [ (update-audio-clip) ] if
203     ] bi ;
204
205 : clip-al-sources ( clips -- length sources )
206     [ length ] [ [ al-source>> ] uint-array{ } map-as ] bi ;
207
208 PRIVATE>
209
210 DEFER: update-audio
211
212 : start-audio* ( audio-engine -- )
213     dup al-sources>> [ drop ] [
214         {
215             [ make-engine-current ]
216             [ al-context>> alcProcessContext ]
217             [
218                 dup allocate-sources >>al-sources
219                 0 >>next-source
220                 V{ } clone >>clips
221                 drop
222             ]
223             [ update-listener ]
224         } cleave
225     ] if ;
226
227 : start-audio ( audio-engine -- )
228     dup start-audio*
229     dup '[ _ update-audio ] 20 milliseconds every >>update-timer
230     drop ;
231
232 : stop-audio ( audio-engine -- )
233     dup al-sources>> [
234         {
235             [ make-engine-current ]
236             [ update-timer>> [ stop-timer ] when* ]
237             [ clips>> clone [ dispose ] each ]
238             [ al-sources>> free-sources ]
239             [
240                 f >>al-sources
241                 f >>clips
242                 f >>update-timer
243                 drop
244             ]
245             [ al-context>> alcSuspendContext ]
246         } cleave
247     ] [ drop ] if ;
248
249 M: audio-engine dispose*
250     dup stop-audio
251     [ [ alcDestroyContext ] when* f ] change-al-context
252     [ [ alcCloseDevice*   ] when* f ] change-al-device
253     drop ;
254
255 :: <static-audio-clip> ( audio-engine source audio loop? -- audio-clip/f )
256     audio-engine get-available-source :> al-source
257
258     al-source [
259         1 0 c:uint <ref> [ alGenBuffers ] keep c:uint deref :> al-buffer
260         al-buffer audio { [ openal-format ] [ data>> ] [ size>> ] [ sample-rate>> ] } cleave
261             alBufferData
262
263         al-source AL_BUFFER al-buffer alSourcei
264         al-source AL_LOOPING loop? c:>c-bool alSourcei
265
266         static-audio-clip new-disposable
267             audio-engine >>audio-engine
268             source >>source
269             al-source >>al-source
270             al-buffer >>al-buffer
271             :> clip
272         clip audio-engine clips>> push
273         clip
274     ] [ f ] if ;
275
276 :: <streaming-audio-clip> ( audio-engine source generator buffer-count -- audio-clip/f )
277     audio-engine get-available-source :> al-source
278
279     al-source [
280         buffer-count dup c:uint (c-array) [ alGenBuffers ] keep :> al-buffers
281         generator generator-audio-format :> ( channels sample-bits sample-rate )
282
283         streaming-audio-clip new-disposable
284             audio-engine >>audio-engine
285             source >>source
286             al-source >>al-source
287             generator >>generator
288             channels >>channels
289             sample-bits >>sample-bits
290             sample-rate >>sample-rate
291             al-buffers >>al-buffers
292             :> clip
293         al-buffers [ clip swap queue-clip-buffer ] each
294         clip audio-engine clips>> push
295         clip
296     ] [ generator dispose f ] if ;
297
298 M: audio-clip dispose*
299     [ dup audio-engine>> clips>> remove! drop ]
300     [ al-source>> flush-source ] bi ;
301
302 M: static-audio-clip dispose*
303     [ call-next-method ]
304     [ [ 1 ] dip al-buffer>> c:uint <ref> alDeleteBuffers ] bi ;
305
306 M: streaming-audio-clip dispose*
307     [ call-next-method ]
308     [ generator>> dispose ]
309     [ al-buffers>> [ length ] keep alDeleteBuffers ] tri ;
310
311 : play-clip ( audio-clip -- )
312     [ update-source ]
313     [ al-source>> alSourcePlay ] bi ;
314
315 : play-clips ( audio-clips -- )
316     [ [ update-source ] each ]
317     [ clip-al-sources alSourcePlayv ] bi ;
318
319 : play-static-audio-clip ( audio-engine source audio loop? -- audio-clip/f )
320     <static-audio-clip> dup [ play-clip ] when* ;
321
322 : play-streaming-audio-clip ( audio-engine source generator buffer-count -- audio-clip/f ) 
323     <streaming-audio-clip> dup [ play-clip ] when* ;
324
325 : pause-clip ( audio-clip -- )
326     al-source>> alSourcePause ;
327
328 : pause-clips ( audio-clips -- )
329     clip-al-sources alSourcePausev ;
330
331 : stop-clip ( audio-clip -- )
332     dispose ;
333
334 : stop-clips ( audio-clips -- )
335     [ clip-al-sources alSourceStopv ]
336     [ [ dispose ] each ] bi ;
337
338 : update-audio ( audio-engine -- )
339     {
340         [ make-engine-current ]
341         [ update-listener ]
342         [ clips>> clone [ update-audio-clip ] each ]
343     } cleave ;
344