Mercurial > audio-send
comparison org/ear.org @ 18:1e201037f666
separating out the sections of send.c
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Thu, 03 Nov 2011 13:32:27 -0700 |
parents | 19ff95c69cf5 |
children | 22ac5a0367cd |
comparison
equal
deleted
inserted
replaced
17:fdf84bc57e67 | 18:1e201037f666 |
---|---|
28 - although some support is provided for multiple AudioRendering | 28 - although some support is provided for multiple AudioRendering |
29 backends, jMonkeyEngine at the time of this writing will either | 29 backends, jMonkeyEngine at the time of this writing will either |
30 pick no AudioRender at all, or the =LwjglAudioRenderer= | 30 pick no AudioRender at all, or the =LwjglAudioRenderer= |
31 - jMonkeyEngine tries to figure out what sort of system you're | 31 - jMonkeyEngine tries to figure out what sort of system you're |
32 running and extracts the appropiate native libraries. | 32 running and extracts the appropiate native libraries. |
33 - the =LwjglAudioRenderer= uses the [[http://lwjgl.org/][=LWJGL=]] (lightweight java game | 33 - the =LwjglAudioRenderer= uses the [[http://lwjgl.org/][=LWJGL=]] (LightWeight Java Game |
34 library) bindings to interface with a C library called [[http://kcat.strangesoft.net/openal.html][=OpenAL=]] | 34 Library) bindings to interface with a C library called [[http://kcat.strangesoft.net/openal.html][=OpenAL=]] |
35 - =OpenAL= calculates the 3D sound localization and feeds a stream of | 35 - =OpenAL= calculates the 3D sound localization and feeds a stream of |
36 sound to any of various sound output devices with which it knows | 36 sound to any of various sound output devices with which it knows |
37 how to communicate. | 37 how to communicate. |
38 | 38 |
39 A consequence of this is that there's no way to access the actual | 39 A consequence of this is that there's no way to access the actual |
79 ** The Send Device | 79 ** The Send Device |
80 Adding a device to OpenAL is rather tricky -- there are five separate | 80 Adding a device to OpenAL is rather tricky -- there are five separate |
81 files in the =OpenAL= source tree that must be modified to do so. I've | 81 files in the =OpenAL= source tree that must be modified to do so. I've |
82 documented this process [[./add-new-device.org][here]] for anyone who is interested. | 82 documented this process [[./add-new-device.org][here]] for anyone who is interested. |
83 | 83 |
84 #+srcname: send | 84 |
85 Onward to that actual Device! | |
86 | |
87 again, my objectives are: | |
88 | |
89 - Support Multiple Listeners from jMonkeyEngine3 | |
90 - Get access to the rendered sound data for further processing from | |
91 clojure. | |
92 | |
93 ** =send.c= | |
94 | |
95 ** Header | |
96 #+srcname: send-header | |
85 #+begin_src C | 97 #+begin_src C |
86 #include "config.h" | 98 #include "config.h" |
87 #include <stdlib.h> | 99 #include <stdlib.h> |
88 #include "alMain.h" | 100 #include "alMain.h" |
89 #include "AL/al.h" | 101 #include "AL/al.h" |
115 | 127 |
116 static void init(ALCdevice *); | 128 static void init(ALCdevice *); |
117 static void renderData(ALCdevice *, int samples); | 129 static void renderData(ALCdevice *, int samples); |
118 | 130 |
119 #define UNUSED(x) (void)(x) | 131 #define UNUSED(x) (void)(x) |
120 | 132 #+end_src |
133 | |
134 The main idea behing the Send device is to take advantage of the fact | |
135 that LWJGL only manages one /context/ when using OpenAL. A /context/ | |
136 is like a container that holds samples and keeps track of where the | |
137 listener is. In order to support multiple listeners, the Send device | |
138 identifies the LWJGL context as the master context, and creates any | |
139 number of slave contexts to represent additional listeners. Every | |
140 time the device renders sound, it synchronizes every source from the | |
141 master LWJGL context to the slave contexts. Then, it renders each | |
142 context separately, using a different listener for each one. The | |
143 rendered sound is made available via JNI to jMonkeyEngine. | |
144 | |
145 To recap, the process is: | |
146 - Set the LWJGL context as "master" in the =init()= method. | |
147 - Create any number of additional contexts via =addContext()= | |
148 - At every call to =renderData()= sync the master context with the | |
149 slave contexts vit =syncContexts()= | |
150 - =syncContexts()= calls =syncSources()= to sync all the sources | |
151 which are in the master context. | |
152 - =limitContext()= and =unLimitContext()= make it possible to render | |
153 only one context at a time. | |
154 | |
155 ** Necessary State | |
156 #+begin_src C | |
121 //////////////////// State | 157 //////////////////// State |
122 | 158 |
123 typedef struct context_data { | 159 typedef struct context_data { |
124 ALfloat ClickRemoval[MAXCHANNELS]; | 160 ALfloat ClickRemoval[MAXCHANNELS]; |
125 ALfloat PendingClicks[MAXCHANNELS]; | 161 ALfloat PendingClicks[MAXCHANNELS]; |
131 ALuint size; | 167 ALuint size; |
132 context_data **contexts; | 168 context_data **contexts; |
133 ALuint numContexts; | 169 ALuint numContexts; |
134 ALuint maxContexts; | 170 ALuint maxContexts; |
135 } send_data; | 171 } send_data; |
136 | 172 #+end_src |
137 | 173 |
138 | 174 Switching between contexts is not the normal operation of a Device, |
175 and one of the problems with doing so is that a Device normally keeps | |
176 around a few pieces of state such as the =ClickRemoval= array above | |
177 which will become corrupted if the contexts are not done in | |
178 parallel. The solution is to create a copy of this normally global | |
179 device state for each context, and copy it back and forth into and out | |
180 of the actual device state whenever a context is rendered. | |
181 | |
182 ** Synchronization Macros | |
183 | |
184 #+begin_src C | |
139 //////////////////// Context Creation / Synchronization | 185 //////////////////// Context Creation / Synchronization |
140 | 186 |
141 #define _MAKE_SYNC(NAME, INIT_EXPR, GET_EXPR, SET_EXPR) \ | 187 #define _MAKE_SYNC(NAME, INIT_EXPR, GET_EXPR, SET_EXPR) \ |
142 void NAME (ALuint sourceID1, ALuint sourceID2, \ | 188 void NAME (ALuint sourceID1, ALuint sourceID2, \ |
143 ALCcontext *ctx1, ALCcontext *ctx2, \ | 189 ALCcontext *ctx1, ALCcontext *ctx2, \ |
166 MAKE_SYNC( syncSourcei, ALint, alGetSourcei, alSourcei); | 212 MAKE_SYNC( syncSourcei, ALint, alGetSourcei, alSourcei); |
167 MAKE_SYNC( syncSourcef, ALfloat, alGetSourcef, alSourcef); | 213 MAKE_SYNC( syncSourcef, ALfloat, alGetSourcef, alSourcef); |
168 MAKE_SYNC3(syncSource3i, ALint, alGetSource3i, alSource3i); | 214 MAKE_SYNC3(syncSource3i, ALint, alGetSource3i, alSource3i); |
169 MAKE_SYNC3(syncSource3f, ALfloat, alGetSource3f, alSource3f); | 215 MAKE_SYNC3(syncSource3f, ALfloat, alGetSource3f, alSource3f); |
170 | 216 |
217 #+end_src | |
218 | |
219 Setting the state of an =OpenAl= source is done with the =alSourcei=, | |
220 =alSourcef=, =alSource3i=, and =alSource3f= functions. In order to | |
221 complely synchronize two sources, it is necessary to use all of | |
222 them. These macros help to condense the otherwise repetitive | |
223 synchronization code involving these simillar low-level =OpenAL= functions. | |
224 | |
225 ** Source Synchronization | |
226 #+begin_src C | |
171 void syncSources(ALsource *masterSource, ALsource *slaveSource, | 227 void syncSources(ALsource *masterSource, ALsource *slaveSource, |
172 ALCcontext *masterCtx, ALCcontext *slaveCtx){ | 228 ALCcontext *masterCtx, ALCcontext *slaveCtx){ |
173 ALuint master = masterSource->source; | 229 ALuint master = masterSource->source; |
174 ALuint slave = slaveSource->source; | 230 ALuint slave = slaveSource->source; |
175 ALCcontext *current = alcGetCurrentContext(); | 231 ALCcontext *current = alcGetCurrentContext(); |
229 } | 285 } |
230 } | 286 } |
231 // Restore whatever context was previously active. | 287 // Restore whatever context was previously active. |
232 alcMakeContextCurrent(current); | 288 alcMakeContextCurrent(current); |
233 } | 289 } |
234 | 290 #+end_src |
235 | 291 This function is long because it has to exaustively go through all the |
292 possible state that a source can have and make sure that it is the | |
293 same between the master and slave sources. I'd like to take this | |
294 moment to salute the [[http://connect.creativelabs.com/openal/Documentation/Forms/AllItems.aspx][=OpenAL= Reference Manual]], which provides a very | |
295 good description of =OpenAL='s internals. | |
296 | |
297 ** Context Synchronization | |
298 #+begin_src C | |
236 void syncContexts(ALCcontext *master, ALCcontext *slave){ | 299 void syncContexts(ALCcontext *master, ALCcontext *slave){ |
237 /* If there aren't sufficient sources in slave to mirror | 300 /* If there aren't sufficient sources in slave to mirror |
238 the sources in master, create them. */ | 301 the sources in master, create them. */ |
239 ALCcontext *current = alcGetCurrentContext(); | 302 ALCcontext *current = alcGetCurrentContext(); |
240 | 303 |
259 (ALsource*)slaveSourceMap->array[i].value, | 322 (ALsource*)slaveSourceMap->array[i].value, |
260 master, slave); | 323 master, slave); |
261 } | 324 } |
262 alcMakeContextCurrent(current); | 325 alcMakeContextCurrent(current); |
263 } | 326 } |
264 | 327 #+end_src |
328 | |
329 Most of the hard work in Context Synchronization is done in | |
330 =syncSources()=. The only thing that =syncContexts()= has to worry | |
331 about is automoatically creating new sources whenever a slave context | |
332 does not have the same number of sources as the master context. | |
333 | |
334 * Context Creation | |
335 #+begin_src C | |
265 static void addContext(ALCdevice *Device, ALCcontext *context){ | 336 static void addContext(ALCdevice *Device, ALCcontext *context){ |
266 send_data *data = (send_data*)Device->ExtraData; | 337 send_data *data = (send_data*)Device->ExtraData; |
267 // expand array if necessary | 338 // expand array if necessary |
268 if (data->numContexts >= data->maxContexts){ | 339 if (data->numContexts >= data->maxContexts){ |
269 ALuint newMaxContexts = data->maxContexts*2 + 1; | 340 ALuint newMaxContexts = data->maxContexts*2 + 1; |
279 ctxData->ctx = context; | 350 ctxData->ctx = context; |
280 | 351 |
281 data->contexts[data->numContexts] = ctxData; | 352 data->contexts[data->numContexts] = ctxData; |
282 data->numContexts++; | 353 data->numContexts++; |
283 } | 354 } |
284 | 355 #+end_src |
285 | 356 |
357 Here, the slave context is created, and it's data is stored in the | |
358 device-wide =ExtraData= structure. The =renderBuffer= that is created | |
359 here is where the rendered sound samples for this slave context will | |
360 eventually go. | |
361 | |
362 * Context Switching | |
363 #+begin_src C | |
286 //////////////////// Context Switching | 364 //////////////////// Context Switching |
287 | 365 |
288 /* A device brings along with it two pieces of state | 366 /* A device brings along with it two pieces of state |
289 * which have to be swapped in and out with each context. | 367 * which have to be swapped in and out with each context. |
290 */ | 368 */ |
314 | 392 |
315 static void unLimitContext(ALCdevice *Device){ | 393 static void unLimitContext(ALCdevice *Device){ |
316 Device->Contexts = currentContext; | 394 Device->Contexts = currentContext; |
317 Device->NumContexts = currentNumContext; | 395 Device->NumContexts = currentNumContext; |
318 } | 396 } |
319 | 397 #+end_src |
320 | 398 |
399 =OpenAL= normally reneders all Contexts in parallel, outputting the | |
400 whole result to the buffer. It does this by iterating over the | |
401 Device->Contexts array and rendering each context to the buffer in | |
402 turn. By temporarly setting Device->NumContexts to 1 and adjusting | |
403 the Device's context list to put the desired context-to-be-rendered | |
404 into position 0, we can get trick =OpenAL= into rendering each slave | |
405 context separate from all the others. | |
406 | |
407 ** Main Device Loop | |
408 #+begin_src C | |
321 //////////////////// Main Device Loop | 409 //////////////////// Main Device Loop |
322 | 410 |
323 /* Establish the LWJGL context as the main context, which will | 411 /* Establish the LWJGL context as the master context, which will |
324 * be synchronized to all the slave contexts | 412 * be synchronized to all the slave contexts |
325 */ | 413 */ |
326 static void init(ALCdevice *Device){ | 414 static void init(ALCdevice *Device){ |
327 ALCcontext *masterContext = alcGetCurrentContext(); | 415 ALCcontext *masterContext = alcGetCurrentContext(); |
328 addContext(Device, masterContext); | 416 addContext(Device, masterContext); |
355 saveContext(Device, ctxData); | 443 saveContext(Device, ctxData); |
356 unLimitContext(Device); | 444 unLimitContext(Device); |
357 } | 445 } |
358 alcMakeContextCurrent(current); | 446 alcMakeContextCurrent(current); |
359 } | 447 } |
360 | 448 #+end_src |
361 | 449 |
450 The main loop synchronizes the master LWJGL context with all the slave | |
451 contexts, then walks each context, rendering just that context to it's | |
452 audio-sample storage buffer. | |
453 | |
454 * JNI Methods | |
455 #+begin_src C | |
362 //////////////////// JNI Methods | 456 //////////////////// JNI Methods |
363 | 457 |
364 #include "com_aurellem_send_AudioSend.h" | 458 #include "com_aurellem_send_AudioSend.h" |
365 | 459 |
366 /* | 460 /* |
387 ALvoid *buffer_address = | 481 ALvoid *buffer_address = |
388 ((ALbyte *)(((char*)(*env)->GetDirectBufferAddress(env, buffer)) + position)); | 482 ((ALbyte *)(((char*)(*env)->GetDirectBufferAddress(env, buffer)) + position)); |
389 ALCdevice *recorder = (ALCdevice*) ((intptr_t)device); | 483 ALCdevice *recorder = (ALCdevice*) ((intptr_t)device); |
390 send_data *data = (send_data*)recorder->ExtraData; | 484 send_data *data = (send_data*)recorder->ExtraData; |
391 if ((ALuint)n > data->numContexts){return;} | 485 if ((ALuint)n > data->numContexts){return;} |
392 | |
393 //printf("Want %d samples for listener %d\n", samples, n); | |
394 //printf("Device's format type is %d bytes per sample,\n", | |
395 // BytesFromDevFmt(recorder->FmtType)); | |
396 //printf("and it has %d channels, making for %d requested bytes\n", | |
397 // recorder->NumChan, | |
398 // BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples); | |
399 | |
400 memcpy(buffer_address, data->contexts[n]->renderBuffer, | 486 memcpy(buffer_address, data->contexts[n]->renderBuffer, |
401 BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples); | 487 BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples); |
402 //samples*sizeof(ALfloat)); | |
403 } | 488 } |
404 | 489 |
405 /* | 490 /* |
406 * Class: com_aurellem_send_AudioSend | 491 * Class: com_aurellem_send_AudioSend |
407 * Method: naddListener | 492 * Method: naddListener |
512 channels, | 597 channels, |
513 isSigned, | 598 isSigned, |
514 0); | 599 0); |
515 return format; | 600 return format; |
516 } | 601 } |
517 | |
518 | |
519 | 602 |
520 //////////////////// Device Initilization / Management | 603 //////////////////// Device Initilization / Management |
521 | 604 |
522 static const ALCchar sendDevice[] = "Multiple Audio Send"; | 605 static const ALCchar sendDevice[] = "Multiple Audio Send"; |
523 | 606 |