changeset 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 fdf84bc57e67
children 22ac5a0367cd
files org/ear.org
diffstat 1 files changed, 99 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
     1.1 --- a/org/ear.org	Thu Nov 03 12:12:05 2011 -0700
     1.2 +++ b/org/ear.org	Thu Nov 03 13:32:27 2011 -0700
     1.3 @@ -30,8 +30,8 @@
     1.4     pick no AudioRender at all, or the =LwjglAudioRenderer=
     1.5   - jMonkeyEngine tries to figure out what sort of system you're
     1.6     running and extracts the appropiate native libraries.
     1.7 - - the =LwjglAudioRenderer= uses the [[http://lwjgl.org/][=LWJGL=]] (lightweight java game
     1.8 -   library) bindings to interface with a C library called [[http://kcat.strangesoft.net/openal.html][=OpenAL=]]
     1.9 + - the =LwjglAudioRenderer= uses the [[http://lwjgl.org/][=LWJGL=]] (LightWeight Java Game
    1.10 +   Library) bindings to interface with a C library called [[http://kcat.strangesoft.net/openal.html][=OpenAL=]]
    1.11   - =OpenAL= calculates the 3D sound localization and feeds a stream of
    1.12     sound to any of various sound output devices with which it knows
    1.13     how to communicate.
    1.14 @@ -81,7 +81,19 @@
    1.15  files in the =OpenAL= source tree that must be modified to do so. I've
    1.16  documented this process [[./add-new-device.org][here]] for anyone who is interested.
    1.17  
    1.18 -#+srcname: send
    1.19 +
    1.20 +Onward to that actual Device!
    1.21 +
    1.22 +again, my objectives are:
    1.23 +
    1.24 + - Support Multiple Listeners from jMonkeyEngine3
    1.25 + - Get access to the rendered sound data for further processing from
    1.26 +   clojure.
    1.27 +
    1.28 +** =send.c=
    1.29 +
    1.30 +** Header
    1.31 +#+srcname: send-header
    1.32  #+begin_src C
    1.33  #include "config.h"
    1.34  #include <stdlib.h>
    1.35 @@ -117,7 +129,31 @@
    1.36  static void renderData(ALCdevice *, int samples);
    1.37  
    1.38  #define UNUSED(x)  (void)(x)
    1.39 +#+end_src
    1.40  
    1.41 +The main idea behing the Send device is to take advantage of the fact
    1.42 +that LWJGL only manages one /context/ when using OpenAL.  A /context/
    1.43 +is like a container that holds samples and keeps track of where the
    1.44 +listener is.  In order to support multiple listeners, the Send device
    1.45 +identifies the LWJGL context as the master context, and creates any
    1.46 +number of slave contexts to represent additional listeners.  Every
    1.47 +time the device renders sound, it synchronizes every source from the
    1.48 +master LWJGL context to the slave contexts.  Then, it renders each
    1.49 +context separately, using a different listener for each one.  The
    1.50 +rendered sound is made available via JNI to jMonkeyEngine.
    1.51 +
    1.52 +To recap, the process is: 
    1.53 + - Set the LWJGL context as "master" in the =init()= method.
    1.54 + - Create any number of additional contexts via =addContext()=
    1.55 + - At every call to =renderData()= sync the master context with the
    1.56 +   slave contexts vit =syncContexts()=
    1.57 + - =syncContexts()= calls =syncSources()= to sync all the sources
    1.58 +   which are in the master context.
    1.59 + - =limitContext()= and =unLimitContext()= make it possible to render
    1.60 +   only one context at a time.
    1.61 +
    1.62 +** Necessary State
    1.63 +#+begin_src C
    1.64  ////////////////////  State
    1.65  
    1.66  typedef struct context_data {
    1.67 @@ -133,9 +169,19 @@
    1.68    ALuint numContexts;
    1.69    ALuint maxContexts;
    1.70  } send_data;
    1.71 +#+end_src
    1.72  
    1.73 +Switching between contexts is not the normal operation of a Device,
    1.74 +and one of the problems with doing so is that a Device normally keeps
    1.75 +around a few pieces of state such as the =ClickRemoval= array above
    1.76 +which will become corrupted if the contexts are not done in
    1.77 +parallel. The solution is to create a copy of this normally global
    1.78 +device state for each context, and copy it back and forth into and out
    1.79 +of the actual device state whenever a context is rendered.
    1.80  
    1.81 +** Synchronization Macros
    1.82  
    1.83 +#+begin_src C
    1.84  ////////////////////  Context Creation / Synchronization
    1.85  
    1.86  #define _MAKE_SYNC(NAME, INIT_EXPR, GET_EXPR, SET_EXPR)	\
    1.87 @@ -168,6 +214,16 @@
    1.88  MAKE_SYNC3(syncSource3i, ALint,   alGetSource3i, alSource3i);
    1.89  MAKE_SYNC3(syncSource3f, ALfloat, alGetSource3f, alSource3f);
    1.90    
    1.91 +#+end_src
    1.92 +
    1.93 +Setting the state of an =OpenAl= source is done with the =alSourcei=,
    1.94 +=alSourcef=, =alSource3i=, and =alSource3f= functions.  In order to
    1.95 +complely synchronize two sources, it is necessary to use all of
    1.96 +them. These macros help to condense the otherwise repetitive
    1.97 +synchronization code involving these simillar low-level =OpenAL= functions.
    1.98 +
    1.99 +** Source Synchronization
   1.100 +#+begin_src C
   1.101  void syncSources(ALsource *masterSource, ALsource *slaveSource, 
   1.102  		 ALCcontext *masterCtx, ALCcontext *slaveCtx){
   1.103    ALuint master = masterSource->source;
   1.104 @@ -231,8 +287,15 @@
   1.105    // Restore whatever context was previously active.
   1.106    alcMakeContextCurrent(current);
   1.107  }
   1.108 +#+end_src
   1.109 +This function is long because it has to exaustively go through all the
   1.110 +possible state that a source can have and make sure that it is the
   1.111 +same between the master and slave sources.  I'd like to take this
   1.112 +moment to salute the [[http://connect.creativelabs.com/openal/Documentation/Forms/AllItems.aspx][=OpenAL= Reference Manual]], which provides a very
   1.113 +good description of =OpenAL='s internals.
   1.114  
   1.115 -
   1.116 +** Context Synchronization
   1.117 +#+begin_src C
   1.118  void syncContexts(ALCcontext *master, ALCcontext *slave){
   1.119    /* If there aren't sufficient sources in slave to mirror 
   1.120       the sources in master, create them. */
   1.121 @@ -261,7 +324,15 @@
   1.122    }
   1.123    alcMakeContextCurrent(current);
   1.124  }
   1.125 +#+end_src
   1.126  
   1.127 +Most of the hard work in Context Synchronization is done in
   1.128 +=syncSources()=. The only thing that =syncContexts()= has to worry
   1.129 +about is automoatically creating new sources whenever a slave context
   1.130 +does not have the same number of sources as the master context.
   1.131 +
   1.132 +* Context Creation
   1.133 +#+begin_src C
   1.134  static void addContext(ALCdevice *Device, ALCcontext *context){
   1.135    send_data *data = (send_data*)Device->ExtraData;
   1.136    // expand array if necessary
   1.137 @@ -281,8 +352,15 @@
   1.138    data->contexts[data->numContexts] = ctxData;
   1.139    data->numContexts++;
   1.140  }
   1.141 +#+end_src
   1.142  
   1.143 +Here, the slave context is created, and it's data is stored in the
   1.144 +device-wide =ExtraData= structure.  The =renderBuffer= that is created
   1.145 +here is where the rendered sound samples for this slave context will
   1.146 +eventually go.
   1.147  
   1.148 +* Context Switching
   1.149 +#+begin_src C
   1.150  ////////////////////  Context Switching 
   1.151  
   1.152  /* A device brings along with it two pieces of state
   1.153 @@ -316,11 +394,21 @@
   1.154    Device->Contexts = currentContext;
   1.155    Device->NumContexts = currentNumContext;
   1.156  }
   1.157 +#+end_src
   1.158  
   1.159 +=OpenAL= normally reneders all Contexts in parallel, outputting the
   1.160 +whole result to the buffer.  It does this by iterating over the
   1.161 +Device->Contexts array and rendering each context to the buffer in
   1.162 +turn.  By temporarly setting Device->NumContexts to 1 and adjusting
   1.163 +the Device's context list to put the desired context-to-be-rendered
   1.164 +into position 0, we can get trick =OpenAL= into rendering each slave
   1.165 +context separate from all the others.
   1.166  
   1.167 +** Main Device Loop
   1.168 +#+begin_src C
   1.169  ////////////////////   Main Device Loop
   1.170  
   1.171 -/* Establish the LWJGL context as the main context, which will
   1.172 +/* Establish the LWJGL context as the master context, which will
   1.173   * be synchronized to all the slave contexts
   1.174   */
   1.175  static void init(ALCdevice *Device){
   1.176 @@ -357,8 +445,14 @@
   1.177    }
   1.178    alcMakeContextCurrent(current);
   1.179  }
   1.180 +#+end_src
   1.181  
   1.182 +The main loop synchronizes the master LWJGL context with all the slave
   1.183 +contexts, then walks each context, rendering just that context to it's
   1.184 +audio-sample storage buffer.
   1.185  
   1.186 +* JNI Methods
   1.187 +#+begin_src C
   1.188  ////////////////////   JNI Methods
   1.189  
   1.190  #include "com_aurellem_send_AudioSend.h"
   1.191 @@ -389,17 +483,8 @@
   1.192    ALCdevice *recorder = (ALCdevice*) ((intptr_t)device);
   1.193    send_data *data = (send_data*)recorder->ExtraData;
   1.194    if ((ALuint)n > data->numContexts){return;}
   1.195 -  
   1.196 -  //printf("Want %d samples for listener %d\n", samples, n);
   1.197 -  //printf("Device's format type is %d bytes per sample,\n", 
   1.198 -  //  BytesFromDevFmt(recorder->FmtType));
   1.199 -  //printf("and it has %d channels, making for %d requested bytes\n", 
   1.200 -  //	 recorder->NumChan, 
   1.201 -  //	 BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples);
   1.202 -
   1.203    memcpy(buffer_address, data->contexts[n]->renderBuffer, 
   1.204  	 BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples);
   1.205 -  //samples*sizeof(ALfloat));
   1.206  }
   1.207  
   1.208  /*
   1.209 @@ -515,8 +600,6 @@
   1.210    return format;
   1.211  }
   1.212  
   1.213 -
   1.214 -
   1.215  ////////////////////   Device Initilization / Management
   1.216  
   1.217  static const ALCchar sendDevice[] = "Multiple Audio Send";