JOGL v2.6.0-rc-20250706
JOGL, High-Performance Graphics Binding for Java™ (public API).
Mixer.java
Go to the documentation of this file.
1/*
2 * Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * - Redistribution of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * - Redistribution in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * Neither the name of Sun Microsystems, Inc. or the names of
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * This software is provided "AS IS," without a warranty of any kind. ALL
20 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
21 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
22 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
23 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
24 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
25 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
26 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
27 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
28 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
29 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
30 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
31 */
32
33package com.jogamp.audio.windows.waveout;
34
35import java.io.*;
36import java.nio.*;
37import java.util.*;
38
39import com.jogamp.common.util.InterruptSource;
40import com.jogamp.common.util.InterruptedRuntimeException;
41
42// Needed only for NIO workarounds on CVM
43import java.lang.reflect.*;
44
45public class Mixer {
46 // This class is a singleton
47 private static Mixer mixer;
48
49 volatile boolean fillerAlive;
50 volatile boolean mixerAlive;
51 volatile boolean shutdown;
52 volatile Object shutdownLock = new Object();
53
54 // Windows Event object
55 private final long event;
56
57 private volatile ArrayList<Track> tracks = new ArrayList<Track>();
58
59 private final Vec3f leftSpeakerPosition = new Vec3f(-1, 0, 0);
60 private final Vec3f rightSpeakerPosition = new Vec3f( 1, 0, 0);
61
62 private float falloffFactor = 1.0f;
63
64 static {
65 mixer = new Mixer();
66 }
67
68 private Mixer() {
69 event = CreateEvent();
70 fillerAlive = false;
71 mixerAlive = false;
72 shutdown = false;
73 new FillerThread().start();
74 final MixerThread m = new MixerThread();
75 m.setPriority(Thread.MAX_PRIORITY - 1);
76 m.start();
77 }
78
79 public static Mixer getMixer() {
80 return mixer;
81 }
82
83 synchronized void add(final Track track) {
84 final ArrayList<Track> newTracks = new ArrayList<Track>(tracks);
85 newTracks.add(track);
86 tracks = newTracks;
87 }
88
89 synchronized void remove(final Track track) {
90 final ArrayList<Track> newTracks = new ArrayList<Track>(tracks);
91 newTracks.remove(track);
92 tracks = newTracks;
93 }
94
95 // NOTE: due to a bug on the APX device, we only have mono sounds,
96 // so we currently only pay attention to the position of the left
97 // speaker
98 public void setLeftSpeakerPosition(final float x, final float y, final float z) {
99 leftSpeakerPosition.set(x, y, z);
100 }
101
102 // NOTE: due to a bug on the APX device, we only have mono sounds,
103 // so we currently only pay attention to the position of the left
104 // speaker
105 public void setRightSpeakerPosition(final float x, final float y, final float z) {
106 rightSpeakerPosition.set(x, y, z);
107 }
108
109 /** This defines a scale factor of sorts -- the higher the number,
110 the larger an area the sound will affect. Default value is
111 1.0f. Valid values are [1.0f, ...]. The formula for the gain
112 for each channel is
113<PRE>
114 falloffFactor
115 -------------------
116 falloffFactor + r^2
117</PRE>
118*/
119 public void setFalloffFactor(final float factor) {
120 falloffFactor = factor;
121 }
122
123 public void shutdown() {
124 synchronized(shutdownLock) {
125 shutdown = true;
126 SetEvent(event);
127 try {
128 while(fillerAlive || mixerAlive) {
129 shutdownLock.wait();
130 }
131 } catch (final InterruptedException e) {
132 throw new InterruptedRuntimeException(e);
133 }
134 }
135 }
136
137 class FillerThread extends InterruptSource.Thread {
138 FillerThread() {
139 super(null, null, "Mixer Thread");
140 }
141
142 @Override
143 public void run() {
144 fillerAlive = true;
145 try {
146 while (!shutdown) {
147 final List<Track> curTracks = tracks;
148
149 for (final Iterator<Track> iter = curTracks.iterator(); iter.hasNext(); ) {
150 final Track track = iter.next();
151 try {
152 track.fill();
153 } catch (final IOException e) {
154 e.printStackTrace();
155 remove(track);
156 }
157 }
158 try {
159 // Run ten times per second
160 java.lang.Thread.sleep(100);
161 } catch (final InterruptedException e) {
162 throw new InterruptedRuntimeException(e);
163 }
164 }
165 } finally {
166 fillerAlive = false;
167 }
168 }
169 }
170
171 class MixerThread extends InterruptSource.Thread {
172 // Temporary mixing buffer
173 // Interleaved left and right channels
174 float[] mixingBuffer;
175 private final Vec3f temp = new Vec3f();
176
177 MixerThread() {
178 super(null, null, "Mixer Thread");
179 if (!initializeWaveOut(event)) {
180 throw new InternalError("Error initializing waveout device");
181 }
182 }
183
184 @Override
185 public void run() {
186 mixerAlive = true;
187 try {
188 while (!shutdown) {
189 // Get the next buffer
190 final long mixerBuffer = getNextMixerBuffer();
191 if (mixerBuffer != 0) {
192 ByteBuffer buf = getMixerBufferData(mixerBuffer);
193
194 if (buf == null) {
195 // This is happening on CVM because
196 // JNI_NewDirectByteBuffer isn't implemented
197 // by default and isn't compatible with the
198 // JSR-239 NIO implementation (apparently)
199 buf = newDirectByteBuffer(getMixerBufferDataAddress(mixerBuffer),
200 getMixerBufferDataCapacity(mixerBuffer));
201 }
202
203 if (buf == null) {
204 throw new InternalError("Couldn't wrap the native address with a direct byte buffer");
205 }
206
207 // System.out.println("Mixing buffer");
208
209 // If we don't have enough samples in our mixing buffer, expand it
210 // FIXME: knowledge of native output rendering format
211 if ((mixingBuffer == null) || (mixingBuffer.length < (buf.capacity() / 2 /* bytes / sample */))) {
212 mixingBuffer = new float[buf.capacity() / 2];
213 } else {
214 // Zap it
215 for (int i = 0; i < mixingBuffer.length; i++) {
216 mixingBuffer[i] = 0.0f;
217 }
218 }
219
220 // This assertion should be in place if we have stereo
221 if ((mixingBuffer.length % 2) != 0) {
222 final String msg = "FATAL ERROR: odd number of samples in the mixing buffer";
223 System.out.println(msg);
224 throw new InternalError(msg);
225 }
226
227 // Run down all of the registered tracks mixing them in
228 final List<Track> curTracks = tracks;
229
230 for (final Iterator<Track> iter = curTracks.iterator(); iter.hasNext(); ) {
231 final Track track = iter.next();
232 // Consider only playing tracks
233 if (track.isPlaying()) {
234 // First recompute its gain
235 final Vec3f pos = track.getPosition();
236 final float leftGain = gain(pos, leftSpeakerPosition);
237 final float rightGain = gain(pos, rightSpeakerPosition);
238 // Now mix it in
239 int i = 0;
240 while (i < mixingBuffer.length) {
241 if (track.hasNextSample()) {
242 final float sample = track.nextSample();
243 mixingBuffer[i++] = sample * leftGain;
244 mixingBuffer[i++] = sample * rightGain;
245 } else {
246 // This allows tracks to stall without being abruptly cancelled
247 if (track.done()) {
248 remove(track);
249 }
250 break;
251 }
252 }
253 }
254 }
255
256 // Now that we have our data, send it down to the card
257 int outPos = 0;
258 for (int i = 0; i < mixingBuffer.length; i++) {
259 final short val = (short) mixingBuffer[i];
260 buf.put(outPos++, (byte) val);
261 buf.put(outPos++, (byte) (val >> 8));
262 }
263 if (!prepareMixerBuffer(mixerBuffer)) {
264 throw new RuntimeException("Error preparing mixer buffer");
265 }
266 if (!writeMixerBuffer(mixerBuffer)) {
267 throw new RuntimeException("Error writing mixer buffer to device");
268 }
269 } else {
270 // System.out.println("No mixer buffer available");
271
272 // Wait for a buffer to become available
273 if (!WaitForSingleObject(event)) {
274 throw new RuntimeException("Error while waiting for event object");
275 }
276
277 /*
278 try {
279 Thread.sleep(10);
280 } catch (InterruptedException e) {
281 throw new InterruptedRuntimeException(e);
282 }
283 */
284 }
285 }
286 } finally {
287 mixerAlive = false;
288 // Need to shut down
289 shutdownWaveOut();
290 synchronized(shutdownLock) {
291 shutdownLock.notifyAll();
292 }
293 }
294 }
295
296 // This defines the 3D spatialization gain function.
297 // The function is defined as:
298 // falloffFactor
299 // -------------------
300 // falloffFactor + r^2
301 private float gain(final Vec3f pos, final Vec3f speakerPos) {
302 temp.sub(pos, speakerPos);
303 final float dotp = temp.dot(temp);
304 return (falloffFactor / (falloffFactor + dotp));
305 }
306 }
307
308 // Initializes waveout device
309 private static native boolean initializeWaveOut(long eventObject);
310 // Shuts down waveout device
311 private static native void shutdownWaveOut();
312
313 // Gets the next (opaque) buffer of data to fill from the native
314 // code, or 0 if none was available yet (it should not happen that
315 // none is available the way the code is written).
316 private static native long getNextMixerBuffer();
317 // Gets the next ByteBuffer to fill out of the mixer buffer. It
318 // requires interleaved left and right channel samples, 16 signed
319 // bits per sample, little endian. Implicit 44.1 kHz sample rate.
320 private static native ByteBuffer getMixerBufferData(long mixerBuffer);
321 // We need these to work around the lack of
322 // JNI_NewDirectByteBuffer in CVM + the JSR 239 NIO classes
323 private static native long getMixerBufferDataAddress(long mixerBuffer);
324 private static native int getMixerBufferDataCapacity(long mixerBuffer);
325 // Prepares this mixer buffer for writing to the device.
326 private static native boolean prepareMixerBuffer(long mixerBuffer);
327 // Writes this mixer buffer to the device.
328 private static native boolean writeMixerBuffer(long mixerBuffer);
329
330 // Helpers to prevent mixer thread from busy waiting
331 private static native long CreateEvent();
332 private static native boolean WaitForSingleObject(long event);
333 private static native void SetEvent(long event);
334 private static native void CloseHandle(long handle);
335
336 // We need a reflective hack to wrap a direct ByteBuffer around
337 // the native memory because JNI_NewDirectByteBuffer doesn't work
338 // in CVM + JSR-239 NIO
339 private static Class directByteBufferClass;
340 private static Constructor directByteBufferConstructor;
341 private static Map createdBuffers = new HashMap(); // Map Long, ByteBuffer
342
343 private static ByteBuffer newDirectByteBuffer(final long address, final long capacity) {
344 final Long key = Long.valueOf(address);
345 ByteBuffer buf = (ByteBuffer) createdBuffers.get(key);
346 if (buf == null) {
347 buf = newDirectByteBufferImpl(address, capacity);
348 if (buf != null) {
349 createdBuffers.put(key, buf);
350 }
351 }
352 return buf;
353 }
354 private static ByteBuffer newDirectByteBufferImpl(final long address, final long capacity) {
355 if (directByteBufferClass == null) {
356 try {
357 directByteBufferClass = Class.forName("java.nio.DirectByteBuffer");
358 final byte[] tmp = new byte[0];
359 directByteBufferConstructor =
360 directByteBufferClass.getDeclaredConstructor(new Class[] { Integer.TYPE,
361 tmp.getClass(),
362 Integer.TYPE });
363 directByteBufferConstructor.setAccessible(true);
364 } catch (final Exception e) {
365 e.printStackTrace();
366 }
367 }
368
369 if (directByteBufferConstructor != null) {
370 try {
371 return (ByteBuffer)
372 directByteBufferConstructor.newInstance(new Object[] {
373 Integer.valueOf((int) capacity),
374 null,
375 Integer.valueOf((int) address)
376 });
377 } catch (final Exception e) {
378 e.printStackTrace();
379 }
380 }
381 return null;
382 }
383}
void setRightSpeakerPosition(final float x, final float y, final float z)
Definition: Mixer.java:105
void setFalloffFactor(final float factor)
This defines a scale factor of sorts – the higher the number, the larger an area the sound will affec...
Definition: Mixer.java:119
void setLeftSpeakerPosition(final float x, final float y, final float z)
Definition: Mixer.java:98