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