# HG changeset patch # User Robert McIntyre # Date 1320352347 25200 # Node ID 1e201037f6669afdd88da7178b70d309adcbd326 # Parent fdf84bc57e6765c59b040c6255cd847159e8517d separating out the sections of send.c diff -r fdf84bc57e67 -r 1e201037f666 org/ear.org --- a/org/ear.org Thu Nov 03 12:12:05 2011 -0700 +++ b/org/ear.org Thu Nov 03 13:32:27 2011 -0700 @@ -30,8 +30,8 @@ pick no AudioRender at all, or the =LwjglAudioRenderer= - jMonkeyEngine tries to figure out what sort of system you're running and extracts the appropiate native libraries. - - the =LwjglAudioRenderer= uses the [[http://lwjgl.org/][=LWJGL=]] (lightweight java game - library) bindings to interface with a C library called [[http://kcat.strangesoft.net/openal.html][=OpenAL=]] + - the =LwjglAudioRenderer= uses the [[http://lwjgl.org/][=LWJGL=]] (LightWeight Java Game + Library) bindings to interface with a C library called [[http://kcat.strangesoft.net/openal.html][=OpenAL=]] - =OpenAL= calculates the 3D sound localization and feeds a stream of sound to any of various sound output devices with which it knows how to communicate. @@ -81,7 +81,19 @@ files in the =OpenAL= source tree that must be modified to do so. I've documented this process [[./add-new-device.org][here]] for anyone who is interested. -#+srcname: send + +Onward to that actual Device! + +again, my objectives are: + + - Support Multiple Listeners from jMonkeyEngine3 + - Get access to the rendered sound data for further processing from + clojure. + +** =send.c= + +** Header +#+srcname: send-header #+begin_src C #include "config.h" #include @@ -117,7 +129,31 @@ static void renderData(ALCdevice *, int samples); #define UNUSED(x) (void)(x) +#+end_src +The main idea behing the Send device is to take advantage of the fact +that LWJGL only manages one /context/ when using OpenAL. A /context/ +is like a container that holds samples and keeps track of where the +listener is. In order to support multiple listeners, the Send device +identifies the LWJGL context as the master context, and creates any +number of slave contexts to represent additional listeners. Every +time the device renders sound, it synchronizes every source from the +master LWJGL context to the slave contexts. Then, it renders each +context separately, using a different listener for each one. The +rendered sound is made available via JNI to jMonkeyEngine. + +To recap, the process is: + - Set the LWJGL context as "master" in the =init()= method. + - Create any number of additional contexts via =addContext()= + - At every call to =renderData()= sync the master context with the + slave contexts vit =syncContexts()= + - =syncContexts()= calls =syncSources()= to sync all the sources + which are in the master context. + - =limitContext()= and =unLimitContext()= make it possible to render + only one context at a time. + +** Necessary State +#+begin_src C //////////////////// State typedef struct context_data { @@ -133,9 +169,19 @@ ALuint numContexts; ALuint maxContexts; } send_data; +#+end_src +Switching between contexts is not the normal operation of a Device, +and one of the problems with doing so is that a Device normally keeps +around a few pieces of state such as the =ClickRemoval= array above +which will become corrupted if the contexts are not done in +parallel. The solution is to create a copy of this normally global +device state for each context, and copy it back and forth into and out +of the actual device state whenever a context is rendered. +** Synchronization Macros +#+begin_src C //////////////////// Context Creation / Synchronization #define _MAKE_SYNC(NAME, INIT_EXPR, GET_EXPR, SET_EXPR) \ @@ -168,6 +214,16 @@ MAKE_SYNC3(syncSource3i, ALint, alGetSource3i, alSource3i); MAKE_SYNC3(syncSource3f, ALfloat, alGetSource3f, alSource3f); +#+end_src + +Setting the state of an =OpenAl= source is done with the =alSourcei=, +=alSourcef=, =alSource3i=, and =alSource3f= functions. In order to +complely synchronize two sources, it is necessary to use all of +them. These macros help to condense the otherwise repetitive +synchronization code involving these simillar low-level =OpenAL= functions. + +** Source Synchronization +#+begin_src C void syncSources(ALsource *masterSource, ALsource *slaveSource, ALCcontext *masterCtx, ALCcontext *slaveCtx){ ALuint master = masterSource->source; @@ -231,8 +287,15 @@ // Restore whatever context was previously active. alcMakeContextCurrent(current); } +#+end_src +This function is long because it has to exaustively go through all the +possible state that a source can have and make sure that it is the +same between the master and slave sources. I'd like to take this +moment to salute the [[http://connect.creativelabs.com/openal/Documentation/Forms/AllItems.aspx][=OpenAL= Reference Manual]], which provides a very +good description of =OpenAL='s internals. - +** Context Synchronization +#+begin_src C void syncContexts(ALCcontext *master, ALCcontext *slave){ /* If there aren't sufficient sources in slave to mirror the sources in master, create them. */ @@ -261,7 +324,15 @@ } alcMakeContextCurrent(current); } +#+end_src +Most of the hard work in Context Synchronization is done in +=syncSources()=. The only thing that =syncContexts()= has to worry +about is automoatically creating new sources whenever a slave context +does not have the same number of sources as the master context. + +* Context Creation +#+begin_src C static void addContext(ALCdevice *Device, ALCcontext *context){ send_data *data = (send_data*)Device->ExtraData; // expand array if necessary @@ -281,8 +352,15 @@ data->contexts[data->numContexts] = ctxData; data->numContexts++; } +#+end_src +Here, the slave context is created, and it's data is stored in the +device-wide =ExtraData= structure. The =renderBuffer= that is created +here is where the rendered sound samples for this slave context will +eventually go. +* Context Switching +#+begin_src C //////////////////// Context Switching /* A device brings along with it two pieces of state @@ -316,11 +394,21 @@ Device->Contexts = currentContext; Device->NumContexts = currentNumContext; } +#+end_src +=OpenAL= normally reneders all Contexts in parallel, outputting the +whole result to the buffer. It does this by iterating over the +Device->Contexts array and rendering each context to the buffer in +turn. By temporarly setting Device->NumContexts to 1 and adjusting +the Device's context list to put the desired context-to-be-rendered +into position 0, we can get trick =OpenAL= into rendering each slave +context separate from all the others. +** Main Device Loop +#+begin_src C //////////////////// Main Device Loop -/* Establish the LWJGL context as the main context, which will +/* Establish the LWJGL context as the master context, which will * be synchronized to all the slave contexts */ static void init(ALCdevice *Device){ @@ -357,8 +445,14 @@ } alcMakeContextCurrent(current); } +#+end_src +The main loop synchronizes the master LWJGL context with all the slave +contexts, then walks each context, rendering just that context to it's +audio-sample storage buffer. +* JNI Methods +#+begin_src C //////////////////// JNI Methods #include "com_aurellem_send_AudioSend.h" @@ -389,17 +483,8 @@ ALCdevice *recorder = (ALCdevice*) ((intptr_t)device); send_data *data = (send_data*)recorder->ExtraData; if ((ALuint)n > data->numContexts){return;} - - //printf("Want %d samples for listener %d\n", samples, n); - //printf("Device's format type is %d bytes per sample,\n", - // BytesFromDevFmt(recorder->FmtType)); - //printf("and it has %d channels, making for %d requested bytes\n", - // recorder->NumChan, - // BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples); - memcpy(buffer_address, data->contexts[n]->renderBuffer, BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples); - //samples*sizeof(ALfloat)); } /* @@ -515,8 +600,6 @@ return format; } - - //////////////////// Device Initilization / Management static const ALCchar sendDevice[] = "Multiple Audio Send";