rlm@15
|
1 #+title: Simulated Sense of Hearing
|
rlm@0
|
2 #+author: Robert McIntyre
|
rlm@0
|
3 #+email: rlm@mit.edu
|
rlm@15
|
4 #+description: Simulating multiple listeners and the sense of hearing in jMonkeyEngine3
|
rlm@15
|
5 #+keywords: simulated hearing, openal, clojure, jMonkeyEngine3, LWJGL, AI
|
rlm@15
|
6 #+SETUPFILE: ../../aurellem/org/setup.org
|
rlm@15
|
7 #+INCLUDE: ../../aurellem/org/level-0.org
|
rlm@0
|
8 #+BABEL: :exports both :noweb yes :cache no :mkdirp yes
|
rlm@0
|
9
|
rlm@0
|
10
|
rlm@0
|
11
|
rlm@0
|
12
|
rlm@15
|
13 * Hearing
|
rlm@0
|
14
|
rlm@0
|
15 I want to be able to place ears in a similiar manner to how I place
|
rlm@0
|
16 the eyes. I want to be able to place ears in a unique spatial
|
rlm@0
|
17 position, and recieve as output at every tick the FFT of whatever
|
rlm@0
|
18 signals are happening at that point.
|
rlm@0
|
19
|
rlm@15
|
20 Hearing is one of the more difficult senses to simulate, because there
|
rlm@15
|
21 is less support for obtaining the actual sound data that is processed
|
rlm@15
|
22 by jMonkeyEngine3.
|
rlm@15
|
23
|
rlm@15
|
24 jMonkeyEngine's sound system works as follows:
|
rlm@15
|
25
|
rlm@15
|
26 - jMonkeyEngine uese the =AppSettings= for the particular application
|
rlm@15
|
27 to determine what sort of =AudioRenderer= should be used.
|
rlm@15
|
28 - although some support is provided for multiple AudioRendering
|
rlm@15
|
29 backends, jMonkeyEngine at the time of this writing will either
|
rlm@15
|
30 pick no AudioRender at all, or the =LwjglAudioRenderer=
|
rlm@15
|
31 - jMonkeyEngine tries to figure out what sort of system you're
|
rlm@15
|
32 running and extracts the appropiate native libraries.
|
rlm@15
|
33 - the =LwjglAudioRenderer= uses the [[http://lwjgl.org/][=LWJGL=]] (lightweight java game
|
rlm@15
|
34 library) bindings to interface with a C library called [[http://kcat.strangesoft.net/openal.html][=OpenAL=]]
|
rlm@15
|
35 - =OpenAL= calculates the 3D sound localization and feeds a stream of
|
rlm@15
|
36 sound to any of various sound output devices with which it knows
|
rlm@15
|
37 how to communicate.
|
rlm@15
|
38
|
rlm@15
|
39 A consequence of this is that there's no way to access the actual
|
rlm@15
|
40 sound data produced by =OpenAL=. Even worse, =OpanAL= only supports
|
rlm@15
|
41 one /listener/, which normally isn't a problem for games, but becomes
|
rlm@15
|
42 a problem when trying to make multiple AI creatures that can each hear
|
rlm@15
|
43 the world from a different perspective.
|
rlm@15
|
44
|
rlm@15
|
45 To make many AI creatures in jMonkeyEngine that can each hear the
|
rlm@15
|
46 world from their own perspective, it is necessary to go all the way
|
rlm@15
|
47 back to =OpenAL= and implement support for simulated hearing there.
|
rlm@15
|
48
|
rlm@15
|
49 ** =OpenAL= Devices
|
rlm@15
|
50
|
rlm@15
|
51 =OpenAL= goes to great lengths to support many different systems, all
|
rlm@15
|
52 with different sound capabilities and interfaces. It acomplishes this
|
rlm@15
|
53 difficult task by providing code for many different sound backends in
|
rlm@15
|
54 pseudo-objects called /Devices/. There's a device for the Linux Open
|
rlm@15
|
55 Sound System and the Advanced Linxu Sound Architechture, there's one
|
rlm@15
|
56 for Direct Sound on Windows, there's even one for Solaris. =OpenAL=
|
rlm@15
|
57 solves the problem of platform independence by providing all these
|
rlm@15
|
58 Devices.
|
rlm@15
|
59
|
rlm@15
|
60 Wrapper libraries such as LWJGL are free to examine the system on
|
rlm@15
|
61 which they are running and then select an appropiate device for that
|
rlm@15
|
62 system.
|
rlm@15
|
63
|
rlm@15
|
64 There are also a few "special" devices that don't interface with any
|
rlm@15
|
65 particular system. These include the Null Device, which doesn't do
|
rlm@15
|
66 anything, and the Wave Device, which writes whatever sound it recieves
|
rlm@15
|
67 to a file, if everything has been set up correctly when configuring
|
rlm@15
|
68 =OpenAL=.
|
rlm@15
|
69
|
rlm@15
|
70 Actual mixing of the sound data happens in the Devices, and they are
|
rlm@15
|
71 the only point in the sound rendering process where this data is
|
rlm@15
|
72 available.
|
rlm@15
|
73
|
rlm@15
|
74 Therefore, in order to support multiple listeners, and get the sound
|
rlm@15
|
75 data in a form that the AIs can use, it is necessary to create a new
|
rlm@15
|
76 Device, which supports this features.
|
rlm@15
|
77
|
rlm@15
|
78
|
rlm@15
|
79 ** The Send Device
|
rlm@15
|
80 Adding a device to OpenAL is rather tricky -- there are five separate
|
rlm@15
|
81 files in the =OpenAL= source tree that must be modified to do so. I've
|
rlm@15
|
82 documented this process [[./add-new-device.org][here]] for anyone who is interested.
|
rlm@15
|
83
|
rlm@15
|
84 #+srcname: send
|
rlm@15
|
85 #+begin_src C
|
rlm@15
|
86 #include "config.h"
|
rlm@15
|
87 #include <stdlib.h>
|
rlm@15
|
88 #include "alMain.h"
|
rlm@15
|
89 #include "AL/al.h"
|
rlm@15
|
90 #include "AL/alc.h"
|
rlm@15
|
91 #include "alSource.h"
|
rlm@15
|
92 #include <jni.h>
|
rlm@15
|
93
|
rlm@15
|
94 //////////////////// Summary
|
rlm@15
|
95
|
rlm@15
|
96 struct send_data;
|
rlm@15
|
97 struct context_data;
|
rlm@15
|
98
|
rlm@15
|
99 static void addContext(ALCdevice *, ALCcontext *);
|
rlm@15
|
100 static void syncContexts(ALCcontext *master, ALCcontext *slave);
|
rlm@15
|
101 static void syncSources(ALsource *master, ALsource *slave,
|
rlm@15
|
102 ALCcontext *masterCtx, ALCcontext *slaveCtx);
|
rlm@15
|
103
|
rlm@15
|
104 static void syncSourcei(ALuint master, ALuint slave,
|
rlm@15
|
105 ALCcontext *masterCtx, ALCcontext *ctx2, ALenum param);
|
rlm@15
|
106 static void syncSourcef(ALuint master, ALuint slave,
|
rlm@15
|
107 ALCcontext *masterCtx, ALCcontext *ctx2, ALenum param);
|
rlm@15
|
108 static void syncSource3f(ALuint master, ALuint slave,
|
rlm@15
|
109 ALCcontext *masterCtx, ALCcontext *ctx2, ALenum param);
|
rlm@15
|
110
|
rlm@15
|
111 static void swapInContext(ALCdevice *, struct context_data *);
|
rlm@15
|
112 static void saveContext(ALCdevice *, struct context_data *);
|
rlm@15
|
113 static void limitContext(ALCdevice *, ALCcontext *);
|
rlm@15
|
114 static void unLimitContext(ALCdevice *);
|
rlm@15
|
115
|
rlm@15
|
116 static void init(ALCdevice *);
|
rlm@15
|
117 static void renderData(ALCdevice *, int samples);
|
rlm@15
|
118
|
rlm@15
|
119 #define UNUSED(x) (void)(x)
|
rlm@15
|
120
|
rlm@15
|
121 //////////////////// State
|
rlm@15
|
122
|
rlm@15
|
123 typedef struct context_data {
|
rlm@15
|
124 ALfloat ClickRemoval[MAXCHANNELS];
|
rlm@15
|
125 ALfloat PendingClicks[MAXCHANNELS];
|
rlm@15
|
126 ALvoid *renderBuffer;
|
rlm@15
|
127 ALCcontext *ctx;
|
rlm@15
|
128 } context_data;
|
rlm@15
|
129
|
rlm@15
|
130 typedef struct send_data {
|
rlm@15
|
131 ALuint size;
|
rlm@15
|
132 context_data **contexts;
|
rlm@15
|
133 ALuint numContexts;
|
rlm@15
|
134 ALuint maxContexts;
|
rlm@15
|
135 } send_data;
|
rlm@15
|
136
|
rlm@15
|
137
|
rlm@15
|
138
|
rlm@15
|
139 //////////////////// Context Creation / Synchronization
|
rlm@15
|
140
|
rlm@15
|
141 #define _MAKE_SYNC(NAME, INIT_EXPR, GET_EXPR, SET_EXPR) \
|
rlm@15
|
142 void NAME (ALuint sourceID1, ALuint sourceID2, \
|
rlm@15
|
143 ALCcontext *ctx1, ALCcontext *ctx2, \
|
rlm@15
|
144 ALenum param){ \
|
rlm@15
|
145 INIT_EXPR; \
|
rlm@15
|
146 ALCcontext *current = alcGetCurrentContext(); \
|
rlm@15
|
147 alcMakeContextCurrent(ctx1); \
|
rlm@15
|
148 GET_EXPR; \
|
rlm@15
|
149 alcMakeContextCurrent(ctx2); \
|
rlm@15
|
150 SET_EXPR; \
|
rlm@15
|
151 alcMakeContextCurrent(current); \
|
rlm@15
|
152 }
|
rlm@15
|
153
|
rlm@15
|
154 #define MAKE_SYNC(NAME, TYPE, GET, SET) \
|
rlm@15
|
155 _MAKE_SYNC(NAME, \
|
rlm@15
|
156 TYPE value, \
|
rlm@15
|
157 GET(sourceID1, param, &value), \
|
rlm@15
|
158 SET(sourceID2, param, value))
|
rlm@15
|
159
|
rlm@15
|
160 #define MAKE_SYNC3(NAME, TYPE, GET, SET) \
|
rlm@15
|
161 _MAKE_SYNC(NAME, \
|
rlm@15
|
162 TYPE value1; TYPE value2; TYPE value3;, \
|
rlm@15
|
163 GET(sourceID1, param, &value1, &value2, &value3), \
|
rlm@15
|
164 SET(sourceID2, param, value1, value2, value3))
|
rlm@15
|
165
|
rlm@15
|
166 MAKE_SYNC( syncSourcei, ALint, alGetSourcei, alSourcei);
|
rlm@15
|
167 MAKE_SYNC( syncSourcef, ALfloat, alGetSourcef, alSourcef);
|
rlm@15
|
168 MAKE_SYNC3(syncSource3i, ALint, alGetSource3i, alSource3i);
|
rlm@15
|
169 MAKE_SYNC3(syncSource3f, ALfloat, alGetSource3f, alSource3f);
|
rlm@15
|
170
|
rlm@15
|
171 void syncSources(ALsource *masterSource, ALsource *slaveSource,
|
rlm@15
|
172 ALCcontext *masterCtx, ALCcontext *slaveCtx){
|
rlm@15
|
173 ALuint master = masterSource->source;
|
rlm@15
|
174 ALuint slave = slaveSource->source;
|
rlm@15
|
175 ALCcontext *current = alcGetCurrentContext();
|
rlm@15
|
176
|
rlm@15
|
177 syncSourcef(master,slave,masterCtx,slaveCtx,AL_PITCH);
|
rlm@15
|
178 syncSourcef(master,slave,masterCtx,slaveCtx,AL_GAIN);
|
rlm@15
|
179 syncSourcef(master,slave,masterCtx,slaveCtx,AL_MAX_DISTANCE);
|
rlm@15
|
180 syncSourcef(master,slave,masterCtx,slaveCtx,AL_ROLLOFF_FACTOR);
|
rlm@15
|
181 syncSourcef(master,slave,masterCtx,slaveCtx,AL_REFERENCE_DISTANCE);
|
rlm@15
|
182 syncSourcef(master,slave,masterCtx,slaveCtx,AL_MIN_GAIN);
|
rlm@15
|
183 syncSourcef(master,slave,masterCtx,slaveCtx,AL_MAX_GAIN);
|
rlm@15
|
184 syncSourcef(master,slave,masterCtx,slaveCtx,AL_CONE_OUTER_GAIN);
|
rlm@15
|
185 syncSourcef(master,slave,masterCtx,slaveCtx,AL_CONE_INNER_ANGLE);
|
rlm@15
|
186 syncSourcef(master,slave,masterCtx,slaveCtx,AL_CONE_OUTER_ANGLE);
|
rlm@15
|
187 syncSourcef(master,slave,masterCtx,slaveCtx,AL_SEC_OFFSET);
|
rlm@15
|
188 syncSourcef(master,slave,masterCtx,slaveCtx,AL_SAMPLE_OFFSET);
|
rlm@15
|
189 syncSourcef(master,slave,masterCtx,slaveCtx,AL_BYTE_OFFSET);
|
rlm@15
|
190
|
rlm@15
|
191 syncSource3f(master,slave,masterCtx,slaveCtx,AL_POSITION);
|
rlm@15
|
192 syncSource3f(master,slave,masterCtx,slaveCtx,AL_VELOCITY);
|
rlm@15
|
193 syncSource3f(master,slave,masterCtx,slaveCtx,AL_DIRECTION);
|
rlm@15
|
194
|
rlm@15
|
195 syncSourcei(master,slave,masterCtx,slaveCtx,AL_SOURCE_RELATIVE);
|
rlm@15
|
196 syncSourcei(master,slave,masterCtx,slaveCtx,AL_LOOPING);
|
rlm@15
|
197
|
rlm@15
|
198 alcMakeContextCurrent(masterCtx);
|
rlm@15
|
199 ALint source_type;
|
rlm@15
|
200 alGetSourcei(master, AL_SOURCE_TYPE, &source_type);
|
rlm@15
|
201
|
rlm@15
|
202 // Only static sources are currently synchronized!
|
rlm@15
|
203 if (AL_STATIC == source_type){
|
rlm@15
|
204 ALint master_buffer;
|
rlm@15
|
205 ALint slave_buffer;
|
rlm@15
|
206 alGetSourcei(master, AL_BUFFER, &master_buffer);
|
rlm@15
|
207 alcMakeContextCurrent(slaveCtx);
|
rlm@15
|
208 alGetSourcei(slave, AL_BUFFER, &slave_buffer);
|
rlm@15
|
209 if (master_buffer != slave_buffer){
|
rlm@15
|
210 alSourcei(slave, AL_BUFFER, master_buffer);
|
rlm@15
|
211 }
|
rlm@15
|
212 }
|
rlm@15
|
213
|
rlm@15
|
214 // Synchronize the state of the two sources.
|
rlm@15
|
215 alcMakeContextCurrent(masterCtx);
|
rlm@15
|
216 ALint masterState;
|
rlm@15
|
217 ALint slaveState;
|
rlm@15
|
218
|
rlm@15
|
219 alGetSourcei(master, AL_SOURCE_STATE, &masterState);
|
rlm@15
|
220 alcMakeContextCurrent(slaveCtx);
|
rlm@15
|
221 alGetSourcei(slave, AL_SOURCE_STATE, &slaveState);
|
rlm@15
|
222
|
rlm@15
|
223 if (masterState != slaveState){
|
rlm@15
|
224 switch (masterState){
|
rlm@15
|
225 case AL_INITIAL : alSourceRewind(slave); break;
|
rlm@15
|
226 case AL_PLAYING : alSourcePlay(slave); break;
|
rlm@15
|
227 case AL_PAUSED : alSourcePause(slave); break;
|
rlm@15
|
228 case AL_STOPPED : alSourceStop(slave); break;
|
rlm@15
|
229 }
|
rlm@15
|
230 }
|
rlm@15
|
231 // Restore whatever context was previously active.
|
rlm@15
|
232 alcMakeContextCurrent(current);
|
rlm@15
|
233 }
|
rlm@15
|
234
|
rlm@15
|
235
|
rlm@15
|
236 void syncContexts(ALCcontext *master, ALCcontext *slave){
|
rlm@15
|
237 /* If there aren't sufficient sources in slave to mirror
|
rlm@15
|
238 the sources in master, create them. */
|
rlm@15
|
239 ALCcontext *current = alcGetCurrentContext();
|
rlm@15
|
240
|
rlm@15
|
241 UIntMap *masterSourceMap = &(master->SourceMap);
|
rlm@15
|
242 UIntMap *slaveSourceMap = &(slave->SourceMap);
|
rlm@15
|
243 ALuint numMasterSources = masterSourceMap->size;
|
rlm@15
|
244 ALuint numSlaveSources = slaveSourceMap->size;
|
rlm@15
|
245
|
rlm@15
|
246 alcMakeContextCurrent(slave);
|
rlm@15
|
247 if (numSlaveSources < numMasterSources){
|
rlm@15
|
248 ALuint numMissingSources = numMasterSources - numSlaveSources;
|
rlm@15
|
249 ALuint newSources[numMissingSources];
|
rlm@15
|
250 alGenSources(numMissingSources, newSources);
|
rlm@15
|
251 }
|
rlm@15
|
252
|
rlm@15
|
253 /* Now, slave is gauranteed to have at least as many sources
|
rlm@15
|
254 as master. Sync each source from master to the corresponding
|
rlm@15
|
255 source in slave. */
|
rlm@15
|
256 int i;
|
rlm@15
|
257 for(i = 0; i < masterSourceMap->size; i++){
|
rlm@15
|
258 syncSources((ALsource*)masterSourceMap->array[i].value,
|
rlm@15
|
259 (ALsource*)slaveSourceMap->array[i].value,
|
rlm@15
|
260 master, slave);
|
rlm@15
|
261 }
|
rlm@15
|
262 alcMakeContextCurrent(current);
|
rlm@15
|
263 }
|
rlm@15
|
264
|
rlm@15
|
265 static void addContext(ALCdevice *Device, ALCcontext *context){
|
rlm@15
|
266 send_data *data = (send_data*)Device->ExtraData;
|
rlm@15
|
267 // expand array if necessary
|
rlm@15
|
268 if (data->numContexts >= data->maxContexts){
|
rlm@15
|
269 ALuint newMaxContexts = data->maxContexts*2 + 1;
|
rlm@15
|
270 data->contexts = realloc(data->contexts, newMaxContexts*sizeof(context_data));
|
rlm@15
|
271 data->maxContexts = newMaxContexts;
|
rlm@15
|
272 }
|
rlm@15
|
273 // create context_data and add it to the main array
|
rlm@15
|
274 context_data *ctxData;
|
rlm@15
|
275 ctxData = (context_data*)calloc(1, sizeof(*ctxData));
|
rlm@15
|
276 ctxData->renderBuffer =
|
rlm@15
|
277 malloc(BytesFromDevFmt(Device->FmtType) *
|
rlm@15
|
278 Device->NumChan * Device->UpdateSize);
|
rlm@15
|
279 ctxData->ctx = context;
|
rlm@15
|
280
|
rlm@15
|
281 data->contexts[data->numContexts] = ctxData;
|
rlm@15
|
282 data->numContexts++;
|
rlm@15
|
283 }
|
rlm@15
|
284
|
rlm@15
|
285
|
rlm@15
|
286 //////////////////// Context Switching
|
rlm@15
|
287
|
rlm@15
|
288 /* A device brings along with it two pieces of state
|
rlm@15
|
289 * which have to be swapped in and out with each context.
|
rlm@15
|
290 */
|
rlm@15
|
291 static void swapInContext(ALCdevice *Device, context_data *ctxData){
|
rlm@15
|
292 memcpy(Device->ClickRemoval, ctxData->ClickRemoval, sizeof(ALfloat)*MAXCHANNELS);
|
rlm@15
|
293 memcpy(Device->PendingClicks, ctxData->PendingClicks, sizeof(ALfloat)*MAXCHANNELS);
|
rlm@15
|
294 }
|
rlm@15
|
295
|
rlm@15
|
296 static void saveContext(ALCdevice *Device, context_data *ctxData){
|
rlm@15
|
297 memcpy(ctxData->ClickRemoval, Device->ClickRemoval, sizeof(ALfloat)*MAXCHANNELS);
|
rlm@15
|
298 memcpy(ctxData->PendingClicks, Device->PendingClicks, sizeof(ALfloat)*MAXCHANNELS);
|
rlm@15
|
299 }
|
rlm@15
|
300
|
rlm@15
|
301 static ALCcontext **currentContext;
|
rlm@15
|
302 static ALuint currentNumContext;
|
rlm@15
|
303
|
rlm@15
|
304 /* By default, all contexts are rendered at once for each call to aluMixData.
|
rlm@15
|
305 * This function uses the internals of the ALCdecice struct to temporarly
|
rlm@15
|
306 * cause aluMixData to only render the chosen context.
|
rlm@15
|
307 */
|
rlm@15
|
308 static void limitContext(ALCdevice *Device, ALCcontext *ctx){
|
rlm@15
|
309 currentContext = Device->Contexts;
|
rlm@15
|
310 currentNumContext = Device->NumContexts;
|
rlm@15
|
311 Device->Contexts = &ctx;
|
rlm@15
|
312 Device->NumContexts = 1;
|
rlm@15
|
313 }
|
rlm@15
|
314
|
rlm@15
|
315 static void unLimitContext(ALCdevice *Device){
|
rlm@15
|
316 Device->Contexts = currentContext;
|
rlm@15
|
317 Device->NumContexts = currentNumContext;
|
rlm@15
|
318 }
|
rlm@15
|
319
|
rlm@15
|
320
|
rlm@15
|
321 //////////////////// Main Device Loop
|
rlm@15
|
322
|
rlm@15
|
323 /* Establish the LWJGL context as the main context, which will
|
rlm@15
|
324 * be synchronized to all the slave contexts
|
rlm@15
|
325 */
|
rlm@15
|
326 static void init(ALCdevice *Device){
|
rlm@15
|
327 ALCcontext *masterContext = alcGetCurrentContext();
|
rlm@15
|
328 addContext(Device, masterContext);
|
rlm@15
|
329 }
|
rlm@15
|
330
|
rlm@15
|
331
|
rlm@15
|
332 static void renderData(ALCdevice *Device, int samples){
|
rlm@15
|
333 if(!Device->Connected){return;}
|
rlm@15
|
334 send_data *data = (send_data*)Device->ExtraData;
|
rlm@15
|
335 ALCcontext *current = alcGetCurrentContext();
|
rlm@15
|
336
|
rlm@15
|
337 ALuint i;
|
rlm@15
|
338 for (i = 1; i < data->numContexts; i++){
|
rlm@15
|
339 syncContexts(data->contexts[0]->ctx , data->contexts[i]->ctx);
|
rlm@15
|
340 }
|
rlm@15
|
341
|
rlm@15
|
342 if ((uint) samples > Device->UpdateSize){
|
rlm@15
|
343 printf("exceeding internal buffer size; dropping samples\n");
|
rlm@15
|
344 printf("requested %d; available %d\n", samples, Device->UpdateSize);
|
rlm@15
|
345 samples = (int) Device->UpdateSize;
|
rlm@15
|
346 }
|
rlm@15
|
347
|
rlm@15
|
348 for (i = 0; i < data->numContexts; i++){
|
rlm@15
|
349 context_data *ctxData = data->contexts[i];
|
rlm@15
|
350 ALCcontext *ctx = ctxData->ctx;
|
rlm@15
|
351 alcMakeContextCurrent(ctx);
|
rlm@15
|
352 limitContext(Device, ctx);
|
rlm@15
|
353 swapInContext(Device, ctxData);
|
rlm@15
|
354 aluMixData(Device, ctxData->renderBuffer, samples);
|
rlm@15
|
355 saveContext(Device, ctxData);
|
rlm@15
|
356 unLimitContext(Device);
|
rlm@15
|
357 }
|
rlm@15
|
358 alcMakeContextCurrent(current);
|
rlm@15
|
359 }
|
rlm@15
|
360
|
rlm@15
|
361
|
rlm@15
|
362 //////////////////// JNI Methods
|
rlm@15
|
363
|
rlm@15
|
364 #include "com_aurellem_send_AudioSend.h"
|
rlm@15
|
365
|
rlm@15
|
366 /*
|
rlm@15
|
367 * Class: com_aurellem_send_AudioSend
|
rlm@15
|
368 * Method: nstep
|
rlm@15
|
369 * Signature: (JI)V
|
rlm@15
|
370 */
|
rlm@15
|
371 JNIEXPORT void JNICALL Java_com_aurellem_send_AudioSend_nstep
|
rlm@15
|
372 (JNIEnv *env, jclass clazz, jlong device, jint samples){
|
rlm@15
|
373 UNUSED(env);UNUSED(clazz);UNUSED(device);
|
rlm@15
|
374 renderData((ALCdevice*)((intptr_t)device), samples);
|
rlm@15
|
375 }
|
rlm@15
|
376
|
rlm@15
|
377 /*
|
rlm@15
|
378 * Class: com_aurellem_send_AudioSend
|
rlm@15
|
379 * Method: ngetSamples
|
rlm@15
|
380 * Signature: (JLjava/nio/ByteBuffer;III)V
|
rlm@15
|
381 */
|
rlm@15
|
382 JNIEXPORT void JNICALL Java_com_aurellem_send_AudioSend_ngetSamples
|
rlm@15
|
383 (JNIEnv *env, jclass clazz, jlong device, jobject buffer, jint position,
|
rlm@15
|
384 jint samples, jint n){
|
rlm@15
|
385 UNUSED(clazz);
|
rlm@15
|
386
|
rlm@15
|
387 ALvoid *buffer_address =
|
rlm@15
|
388 ((ALbyte *)(((char*)(*env)->GetDirectBufferAddress(env, buffer)) + position));
|
rlm@15
|
389 ALCdevice *recorder = (ALCdevice*) ((intptr_t)device);
|
rlm@15
|
390 send_data *data = (send_data*)recorder->ExtraData;
|
rlm@15
|
391 if ((ALuint)n > data->numContexts){return;}
|
rlm@15
|
392
|
rlm@15
|
393 //printf("Want %d samples for listener %d\n", samples, n);
|
rlm@15
|
394 //printf("Device's format type is %d bytes per sample,\n",
|
rlm@15
|
395 // BytesFromDevFmt(recorder->FmtType));
|
rlm@15
|
396 //printf("and it has %d channels, making for %d requested bytes\n",
|
rlm@15
|
397 // recorder->NumChan,
|
rlm@15
|
398 // BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples);
|
rlm@15
|
399
|
rlm@15
|
400 memcpy(buffer_address, data->contexts[n]->renderBuffer,
|
rlm@15
|
401 BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples);
|
rlm@15
|
402 //samples*sizeof(ALfloat));
|
rlm@15
|
403 }
|
rlm@15
|
404
|
rlm@15
|
405 /*
|
rlm@15
|
406 * Class: com_aurellem_send_AudioSend
|
rlm@15
|
407 * Method: naddListener
|
rlm@15
|
408 * Signature: (J)V
|
rlm@15
|
409 */
|
rlm@15
|
410 JNIEXPORT void JNICALL Java_com_aurellem_send_AudioSend_naddListener
|
rlm@15
|
411 (JNIEnv *env, jclass clazz, jlong device){
|
rlm@15
|
412 UNUSED(env); UNUSED(clazz);
|
rlm@15
|
413 //printf("creating new context via naddListener\n");
|
rlm@15
|
414 ALCdevice *Device = (ALCdevice*) ((intptr_t)device);
|
rlm@15
|
415 ALCcontext *new = alcCreateContext(Device, NULL);
|
rlm@15
|
416 addContext(Device, new);
|
rlm@15
|
417 }
|
rlm@15
|
418
|
rlm@15
|
419 /*
|
rlm@15
|
420 * Class: com_aurellem_send_AudioSend
|
rlm@15
|
421 * Method: nsetNthListener3f
|
rlm@15
|
422 * Signature: (IFFFJI)V
|
rlm@15
|
423 */
|
rlm@15
|
424 JNIEXPORT void JNICALL Java_com_aurellem_send_AudioSend_nsetNthListener3f
|
rlm@15
|
425 (JNIEnv *env, jclass clazz, jint param,
|
rlm@15
|
426 jfloat v1, jfloat v2, jfloat v3, jlong device, jint contextNum){
|
rlm@15
|
427 UNUSED(env);UNUSED(clazz);
|
rlm@15
|
428
|
rlm@15
|
429 ALCdevice *Device = (ALCdevice*) ((intptr_t)device);
|
rlm@15
|
430 send_data *data = (send_data*)Device->ExtraData;
|
rlm@15
|
431
|
rlm@15
|
432 ALCcontext *current = alcGetCurrentContext();
|
rlm@15
|
433 if ((ALuint)contextNum > data->numContexts){return;}
|
rlm@15
|
434 alcMakeContextCurrent(data->contexts[contextNum]->ctx);
|
rlm@15
|
435 alListener3f(param, v1, v2, v3);
|
rlm@15
|
436 alcMakeContextCurrent(current);
|
rlm@15
|
437 }
|
rlm@15
|
438
|
rlm@15
|
439 /*
|
rlm@15
|
440 * Class: com_aurellem_send_AudioSend
|
rlm@15
|
441 * Method: nsetNthListenerf
|
rlm@15
|
442 * Signature: (IFJI)V
|
rlm@15
|
443 */
|
rlm@15
|
444 JNIEXPORT void JNICALL Java_com_aurellem_send_AudioSend_nsetNthListenerf
|
rlm@15
|
445 (JNIEnv *env, jclass clazz, jint param, jfloat v1, jlong device,
|
rlm@15
|
446 jint contextNum){
|
rlm@15
|
447
|
rlm@15
|
448 UNUSED(env);UNUSED(clazz);
|
rlm@15
|
449
|
rlm@15
|
450 ALCdevice *Device = (ALCdevice*) ((intptr_t)device);
|
rlm@15
|
451 send_data *data = (send_data*)Device->ExtraData;
|
rlm@15
|
452
|
rlm@15
|
453 ALCcontext *current = alcGetCurrentContext();
|
rlm@15
|
454 if ((ALuint)contextNum > data->numContexts){return;}
|
rlm@15
|
455 alcMakeContextCurrent(data->contexts[contextNum]->ctx);
|
rlm@15
|
456 alListenerf(param, v1);
|
rlm@15
|
457 alcMakeContextCurrent(current);
|
rlm@15
|
458 }
|
rlm@15
|
459
|
rlm@15
|
460 /*
|
rlm@15
|
461 * Class: com_aurellem_send_AudioSend
|
rlm@15
|
462 * Method: ninitDevice
|
rlm@15
|
463 * Signature: (J)V
|
rlm@15
|
464 */
|
rlm@15
|
465 JNIEXPORT void JNICALL Java_com_aurellem_send_AudioSend_ninitDevice
|
rlm@15
|
466 (JNIEnv *env, jclass clazz, jlong device){
|
rlm@15
|
467 UNUSED(env);UNUSED(clazz);
|
rlm@15
|
468
|
rlm@15
|
469 ALCdevice *Device = (ALCdevice*) ((intptr_t)device);
|
rlm@15
|
470 init(Device);
|
rlm@15
|
471
|
rlm@15
|
472 }
|
rlm@15
|
473
|
rlm@15
|
474
|
rlm@15
|
475 /*
|
rlm@15
|
476 * Class: com_aurellem_send_AudioSend
|
rlm@15
|
477 * Method: ngetAudioFormat
|
rlm@15
|
478 * Signature: (J)Ljavax/sound/sampled/AudioFormat;
|
rlm@15
|
479 */
|
rlm@15
|
480 JNIEXPORT jobject JNICALL Java_com_aurellem_send_AudioSend_ngetAudioFormat
|
rlm@15
|
481 (JNIEnv *env, jclass clazz, jlong device){
|
rlm@15
|
482 UNUSED(clazz);
|
rlm@15
|
483 jclass AudioFormatClass =
|
rlm@15
|
484 (*env)->FindClass(env, "javax/sound/sampled/AudioFormat");
|
rlm@15
|
485 jmethodID AudioFormatConstructor =
|
rlm@15
|
486 (*env)->GetMethodID(env, AudioFormatClass, "<init>", "(FIIZZ)V");
|
rlm@15
|
487
|
rlm@15
|
488 ALCdevice *Device = (ALCdevice*) ((intptr_t)device);
|
rlm@15
|
489
|
rlm@15
|
490 //float frequency
|
rlm@15
|
491
|
rlm@15
|
492 int isSigned;
|
rlm@15
|
493 switch (Device->FmtType)
|
rlm@15
|
494 {
|
rlm@15
|
495 case DevFmtUByte:
|
rlm@15
|
496 case DevFmtUShort: isSigned = 0; break;
|
rlm@15
|
497 default : isSigned = 1;
|
rlm@15
|
498 }
|
rlm@15
|
499 float frequency = Device->Frequency;
|
rlm@15
|
500 int bitsPerFrame = (8 * BytesFromDevFmt(Device->FmtType));
|
rlm@15
|
501 int channels = Device->NumChan;
|
rlm@15
|
502
|
rlm@15
|
503
|
rlm@15
|
504 //printf("freq = %f, bpf = %d, channels = %d, signed? = %d\n",
|
rlm@15
|
505 // frequency, bitsPerFrame, channels, isSigned);
|
rlm@15
|
506
|
rlm@15
|
507 jobject format = (*env)->
|
rlm@15
|
508 NewObject(
|
rlm@15
|
509 env,AudioFormatClass,AudioFormatConstructor,
|
rlm@15
|
510 frequency,
|
rlm@15
|
511 bitsPerFrame,
|
rlm@15
|
512 channels,
|
rlm@15
|
513 isSigned,
|
rlm@15
|
514 0);
|
rlm@15
|
515 return format;
|
rlm@15
|
516 }
|
rlm@15
|
517
|
rlm@15
|
518
|
rlm@15
|
519
|
rlm@15
|
520 //////////////////// Device Initilization / Management
|
rlm@15
|
521
|
rlm@15
|
522 static const ALCchar sendDevice[] = "Multiple Audio Send";
|
rlm@15
|
523
|
rlm@15
|
524 static ALCboolean send_open_playback(ALCdevice *device,
|
rlm@15
|
525 const ALCchar *deviceName)
|
rlm@15
|
526 {
|
rlm@15
|
527 send_data *data;
|
rlm@15
|
528 // stop any buffering for stdout, so that I can
|
rlm@15
|
529 // see the printf statements in my terminal immediatley
|
rlm@15
|
530 setbuf(stdout, NULL);
|
rlm@15
|
531
|
rlm@15
|
532 if(!deviceName)
|
rlm@15
|
533 deviceName = sendDevice;
|
rlm@15
|
534 else if(strcmp(deviceName, sendDevice) != 0)
|
rlm@15
|
535 return ALC_FALSE;
|
rlm@15
|
536 data = (send_data*)calloc(1, sizeof(*data));
|
rlm@15
|
537 device->szDeviceName = strdup(deviceName);
|
rlm@15
|
538 device->ExtraData = data;
|
rlm@15
|
539 return ALC_TRUE;
|
rlm@15
|
540 }
|
rlm@15
|
541
|
rlm@15
|
542 static void send_close_playback(ALCdevice *device)
|
rlm@15
|
543 {
|
rlm@15
|
544 send_data *data = (send_data*)device->ExtraData;
|
rlm@15
|
545 alcMakeContextCurrent(NULL);
|
rlm@15
|
546 ALuint i;
|
rlm@15
|
547 // Destroy all slave contexts. LWJGL will take care of
|
rlm@15
|
548 // its own context.
|
rlm@15
|
549 for (i = 1; i < data->numContexts; i++){
|
rlm@15
|
550 context_data *ctxData = data->contexts[i];
|
rlm@15
|
551 alcDestroyContext(ctxData->ctx);
|
rlm@15
|
552 free(ctxData->renderBuffer);
|
rlm@15
|
553 free(ctxData);
|
rlm@15
|
554 }
|
rlm@15
|
555 free(data);
|
rlm@15
|
556 device->ExtraData = NULL;
|
rlm@15
|
557 }
|
rlm@15
|
558
|
rlm@15
|
559 static ALCboolean send_reset_playback(ALCdevice *device)
|
rlm@15
|
560 {
|
rlm@15
|
561 SetDefaultWFXChannelOrder(device);
|
rlm@15
|
562 return ALC_TRUE;
|
rlm@15
|
563 }
|
rlm@15
|
564
|
rlm@15
|
565 static void send_stop_playback(ALCdevice *Device){
|
rlm@15
|
566 UNUSED(Device);
|
rlm@15
|
567 }
|
rlm@15
|
568
|
rlm@15
|
569 static const BackendFuncs send_funcs = {
|
rlm@15
|
570 send_open_playback,
|
rlm@15
|
571 send_close_playback,
|
rlm@15
|
572 send_reset_playback,
|
rlm@15
|
573 send_stop_playback,
|
rlm@15
|
574 NULL,
|
rlm@15
|
575 NULL, /* These would be filled with functions to */
|
rlm@15
|
576 NULL, /* handle capturing audio if we we into that */
|
rlm@15
|
577 NULL, /* sort of thing... */
|
rlm@15
|
578 NULL,
|
rlm@15
|
579 NULL
|
rlm@15
|
580 };
|
rlm@15
|
581
|
rlm@15
|
582 ALCboolean alc_send_init(BackendFuncs *func_list){
|
rlm@15
|
583 *func_list = send_funcs;
|
rlm@15
|
584 return ALC_TRUE;
|
rlm@15
|
585 }
|
rlm@15
|
586
|
rlm@15
|
587 void alc_send_deinit(void){}
|
rlm@15
|
588
|
rlm@15
|
589 void alc_send_probe(enum DevProbe type)
|
rlm@15
|
590 {
|
rlm@15
|
591 switch(type)
|
rlm@15
|
592 {
|
rlm@15
|
593 case DEVICE_PROBE:
|
rlm@15
|
594 AppendDeviceList(sendDevice);
|
rlm@15
|
595 break;
|
rlm@15
|
596 case ALL_DEVICE_PROBE:
|
rlm@15
|
597 AppendAllDeviceList(sendDevice);
|
rlm@15
|
598 break;
|
rlm@15
|
599 case CAPTURE_DEVICE_PROBE:
|
rlm@15
|
600 break;
|
rlm@15
|
601 }
|
rlm@15
|
602 }
|
rlm@15
|
603 #+end_src
|
rlm@15
|
604
|
rlm@15
|
605
|
rlm@15
|
606
|
rlm@15
|
607
|
rlm@15
|
608
|
rlm@15
|
609
|
rlm@15
|
610
|
rlm@15
|
611
|
rlm@15
|
612 #+srcname: ears
|
rlm@0
|
613 #+begin_src clojure
|
rlm@15
|
614 (ns cortex.hearing)
|
rlm@0
|
615 (use 'cortex.world)
|
rlm@0
|
616 (use 'cortex.import)
|
rlm@0
|
617 (use 'clojure.contrib.def)
|
rlm@0
|
618 (cortex.import/mega-import-jme3)
|
rlm@0
|
619 (rlm.rlm-commands/help)
|
rlm@0
|
620 (import java.nio.ByteBuffer)
|
rlm@0
|
621 (import java.awt.image.BufferedImage)
|
rlm@0
|
622 (import java.awt.Color)
|
rlm@0
|
623 (import java.awt.Dimension)
|
rlm@0
|
624 (import java.awt.Graphics)
|
rlm@0
|
625 (import java.awt.Graphics2D)
|
rlm@0
|
626 (import java.awt.event.WindowAdapter)
|
rlm@0
|
627 (import java.awt.event.WindowEvent)
|
rlm@0
|
628 (import java.awt.image.BufferedImage)
|
rlm@0
|
629 (import java.nio.ByteBuffer)
|
rlm@0
|
630 (import javax.swing.JFrame)
|
rlm@0
|
631 (import javax.swing.JPanel)
|
rlm@0
|
632 (import javax.swing.SwingUtilities)
|
rlm@0
|
633 (import javax.swing.ImageIcon)
|
rlm@0
|
634 (import javax.swing.JOptionPane)
|
rlm@0
|
635 (import java.awt.image.ImageObserver)
|
rlm@0
|
636
|
rlm@0
|
637 (import 'com.jme3.capture.SoundProcessor)
|
rlm@0
|
638
|
rlm@0
|
639
|
rlm@0
|
640 (defn sound-processor
|
rlm@0
|
641 "deals with converting ByteBuffers into Arrays of bytes so that the
|
rlm@0
|
642 continuation functions can be defined in terms of immutable stuff."
|
rlm@0
|
643 [continuation]
|
rlm@0
|
644 (proxy [SoundProcessor] []
|
rlm@0
|
645 (cleanup [])
|
rlm@0
|
646 (process
|
rlm@0
|
647 [#^ByteBuffer audioSamples numSamples]
|
rlm@0
|
648 (no-exceptions
|
rlm@0
|
649 (let [byte-array (byte-array numSamples)]
|
rlm@0
|
650 (.get audioSamples byte-array 0 numSamples)
|
rlm@0
|
651 (continuation
|
rlm@0
|
652 (vec byte-array)))))))
|
rlm@0
|
653
|
rlm@0
|
654 (defn add-ear
|
rlm@0
|
655 "add an ear to the world. The continuation function will be called
|
rlm@0
|
656 on the FFT or the sounds which the ear hears in the given
|
rlm@0
|
657 timeframe. Sound is 3D."
|
rlm@0
|
658 [world listener continuation]
|
rlm@0
|
659 (let [renderer (.getAudioRenderer world)]
|
rlm@0
|
660 (.addListener renderer listener)
|
rlm@0
|
661 (.registerSoundProcessor renderer listener
|
rlm@0
|
662 (sound-processor continuation))
|
rlm@0
|
663 listener))
|
rlm@0
|
664
|
rlm@0
|
665 #+end_src
|
rlm@0
|
666
|
rlm@0
|
667
|
rlm@0
|
668
|
rlm@0
|
669 #+srcname: test-hearing
|
rlm@0
|
670 #+begin_src clojure :results silent
|
rlm@0
|
671 (ns test.hearing)
|
rlm@0
|
672 (use 'cortex.world)
|
rlm@0
|
673 (use 'cortex.import)
|
rlm@0
|
674 (use 'clojure.contrib.def)
|
rlm@0
|
675 (use 'body.ear)
|
rlm@0
|
676 (cortex.import/mega-import-jme3)
|
rlm@0
|
677 (rlm.rlm-commands/help)
|
rlm@0
|
678
|
rlm@0
|
679 (defn setup-fn [world]
|
rlm@0
|
680 (let [listener (Listener.)]
|
rlm@0
|
681 (add-ear world listener #(println (nth % 0)))))
|
rlm@0
|
682
|
rlm@0
|
683 (defn play-sound [node world value]
|
rlm@0
|
684 (if (not value)
|
rlm@0
|
685 (do
|
rlm@0
|
686 (.playSource (.getAudioRenderer world) node))))
|
rlm@0
|
687
|
rlm@0
|
688 (defn test-world []
|
rlm@0
|
689 (let [node1 (AudioNode. (asset-manager) "Sounds/pure.wav" false false)]
|
rlm@0
|
690 (world
|
rlm@0
|
691 (Node.)
|
rlm@0
|
692 {"key-space" (partial play-sound node1)}
|
rlm@0
|
693 setup-fn
|
rlm@0
|
694 no-op
|
rlm@0
|
695 )))
|
rlm@0
|
696
|
rlm@0
|
697
|
rlm@0
|
698 #+end_src
|
rlm@0
|
699
|
rlm@0
|
700
|
rlm@0
|
701
|
rlm@15
|
702 * Example
|
rlm@15
|
703
|
rlm@0
|
704 * COMMENT Code Generation
|
rlm@0
|
705
|
rlm@15
|
706 #+begin_src clojure :tangle ../../cortex/src/cortex/hearing.clj
|
rlm@15
|
707 <<ears>>
|
rlm@0
|
708 #+end_src
|
rlm@0
|
709
|
rlm@15
|
710 #+begin_src clojure :tangle ../../cortex/src/test/hearing.clj
|
rlm@0
|
711 <<test-hearing>>
|
rlm@0
|
712 #+end_src
|
rlm@0
|
713
|
rlm@0
|
714
|
rlm@15
|
715 #+begin_src C :tangle ../Alc/backends/send.c
|
rlm@15
|
716 <<send>>
|
rlm@15
|
717 #+end_src
|