diff options
Diffstat (limited to 'src/jogl/classes/com/jogamp/audio/windows/waveout/Mixer.java')
-rwxr-xr-x | src/jogl/classes/com/jogamp/audio/windows/waveout/Mixer.java | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/src/jogl/classes/com/jogamp/audio/windows/waveout/Mixer.java b/src/jogl/classes/com/jogamp/audio/windows/waveout/Mixer.java new file mode 100755 index 000000000..60972873e --- /dev/null +++ b/src/jogl/classes/com/jogamp/audio/windows/waveout/Mixer.java @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + */ + +package com.jogamp.audio.windows.waveout; + +import java.io.*; +import java.nio.*; +import java.util.*; + +// Needed only for NIO workarounds on CVM +import java.lang.reflect.*; + +public class Mixer { + // This class is a singleton + private static Mixer mixer; + + private volatile boolean shutdown; + private volatile Object shutdownLock = new Object(); + private volatile boolean shutdownDone; + + // Windows Event object + private long event; + + private volatile ArrayList/*<Track>*/ tracks = new ArrayList(); + + private Vec3f leftSpeakerPosition = new Vec3f(-1, 0, 0); + private Vec3f rightSpeakerPosition = new Vec3f( 1, 0, 0); + + private float falloffFactor = 1.0f; + + static { + mixer = new Mixer(); + } + + private Mixer() { + event = CreateEvent(); + new FillerThread().start(); + MixerThread m = new MixerThread(); + m.setPriority(Thread.MAX_PRIORITY - 1); + m.start(); + } + + public static Mixer getMixer() { + return mixer; + } + + synchronized void add(Track track) { + ArrayList/*<Track>*/ newTracks = (ArrayList) tracks.clone(); + newTracks.add(track); + tracks = newTracks; + } + + synchronized void remove(Track track) { + ArrayList/*<Track>*/ newTracks = (ArrayList) tracks.clone(); + newTracks.remove(track); + tracks = newTracks; + } + + // NOTE: due to a bug on the APX device, we only have mono sounds, + // so we currently only pay attention to the position of the left + // speaker + public void setLeftSpeakerPosition(float x, float y, float z) { + leftSpeakerPosition.set(x, y, z); + } + + // NOTE: due to a bug on the APX device, we only have mono sounds, + // so we currently only pay attention to the position of the left + // speaker + public void setRightSpeakerPosition(float x, float y, float z) { + rightSpeakerPosition.set(x, y, z); + } + + /** This defines a scale factor of sorts -- the higher the number, + the larger an area the sound will affect. Default value is + 1.0f. Valid values are [1.0f, ...]. The formula for the gain + for each channel is +<PRE> + falloffFactor + ------------------- + falloffFactor + r^2 +</PRE> +*/ + public void setFalloffFactor(float factor) { + falloffFactor = factor; + } + + public void shutdown() { + synchronized(shutdownLock) { + shutdown = true; + SetEvent(event); + try { + shutdownLock.wait(); + } catch (InterruptedException e) { + } + } + } + + class FillerThread extends Thread { + FillerThread() { + super("Mixer Thread"); + } + + public void run() { + while (!shutdown) { + List/*<Track>*/ curTracks = tracks; + + for (Iterator iter = curTracks.iterator(); iter.hasNext(); ) { + Track track = (Track) iter.next(); + try { + track.fill(); + } catch (IOException e) { + e.printStackTrace(); + remove(track); + } + } + + try { + // Run ten times per second + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + class MixerThread extends Thread { + // Temporary mixing buffer + // Interleaved left and right channels + float[] mixingBuffer; + private Vec3f temp = new Vec3f(); + + MixerThread() { + super("Mixer Thread"); + if (!initializeWaveOut(event)) { + throw new InternalError("Error initializing waveout device"); + } + } + + public void run() { + while (!shutdown) { + // Get the next buffer + long mixerBuffer = getNextMixerBuffer(); + if (mixerBuffer != 0) { + ByteBuffer buf = getMixerBufferData(mixerBuffer); + + if (buf == null) { + // This is happening on CVM because + // JNI_NewDirectByteBuffer isn't implemented + // by default and isn't compatible with the + // JSR-239 NIO implementation (apparently) + buf = newDirectByteBuffer(getMixerBufferDataAddress(mixerBuffer), + getMixerBufferDataCapacity(mixerBuffer)); + } + + if (buf == null) { + throw new InternalError("Couldn't wrap the native address with a direct byte buffer"); + } + + // System.out.println("Mixing buffer"); + + // If we don't have enough samples in our mixing buffer, expand it + // FIXME: knowledge of native output rendering format + if ((mixingBuffer == null) || (mixingBuffer.length < (buf.capacity() / 2 /* bytes / sample */))) { + mixingBuffer = new float[buf.capacity() / 2]; + } else { + // Zap it + for (int i = 0; i < mixingBuffer.length; i++) { + mixingBuffer[i] = 0.0f; + } + } + + // This assertion should be in place if we have stereo + if ((mixingBuffer.length % 2) != 0) { + String msg = "FATAL ERROR: odd number of samples in the mixing buffer"; + System.out.println(msg); + throw new InternalError(msg); + } + + // Run down all of the registered tracks mixing them in + List/*<Track>*/ curTracks = tracks; + + for (Iterator iter = curTracks.iterator(); iter.hasNext(); ) { + Track track = (Track) iter.next(); + // Consider only playing tracks + if (track.isPlaying()) { + // First recompute its gain + Vec3f pos = track.getPosition(); + float leftGain = gain(pos, leftSpeakerPosition); + float rightGain = gain(pos, rightSpeakerPosition); + // Now mix it in + int i = 0; + while (i < mixingBuffer.length) { + if (track.hasNextSample()) { + float sample = track.nextSample(); + mixingBuffer[i++] = sample * leftGain; + mixingBuffer[i++] = sample * rightGain; + } else { + // This allows tracks to stall without being abruptly cancelled + if (track.done()) { + remove(track); + } + break; + } + } + } + } + + // Now that we have our data, send it down to the card + int outPos = 0; + for (int i = 0; i < mixingBuffer.length; i++) { + short val = (short) mixingBuffer[i]; + buf.put(outPos++, (byte) val); + buf.put(outPos++, (byte) (val >> 8)); + } + if (!prepareMixerBuffer(mixerBuffer)) { + throw new RuntimeException("Error preparing mixer buffer"); + } + if (!writeMixerBuffer(mixerBuffer)) { + throw new RuntimeException("Error writing mixer buffer to device"); + } + } else { + // System.out.println("No mixer buffer available"); + + // Wait for a buffer to become available + if (!WaitForSingleObject(event)) { + throw new RuntimeException("Error while waiting for event object"); + } + + /* + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + */ + } + } + + // Need to shut down + shutdownWaveOut(); + synchronized(shutdownLock) { + shutdownLock.notifyAll(); + } + } + + // This defines the 3D spatialization gain function. + // The function is defined as: + // falloffFactor + // ------------------- + // falloffFactor + r^2 + private float gain(Vec3f pos, Vec3f speakerPos) { + temp.sub(pos, speakerPos); + float dotp = temp.dot(temp); + return (falloffFactor / (falloffFactor + dotp)); + } + } + + // Initializes waveout device + private static native boolean initializeWaveOut(long eventObject); + // Shuts down waveout device + private static native void shutdownWaveOut(); + + // Gets the next (opaque) buffer of data to fill from the native + // code, or 0 if none was available yet (it should not happen that + // none is available the way the code is written). + private static native long getNextMixerBuffer(); + // Gets the next ByteBuffer to fill out of the mixer buffer. It + // requires interleaved left and right channel samples, 16 signed + // bits per sample, little endian. Implicit 44.1 kHz sample rate. + private static native ByteBuffer getMixerBufferData(long mixerBuffer); + // We need these to work around the lack of + // JNI_NewDirectByteBuffer in CVM + the JSR 239 NIO classes + private static native long getMixerBufferDataAddress(long mixerBuffer); + private static native int getMixerBufferDataCapacity(long mixerBuffer); + // Prepares this mixer buffer for writing to the device. + private static native boolean prepareMixerBuffer(long mixerBuffer); + // Writes this mixer buffer to the device. + private static native boolean writeMixerBuffer(long mixerBuffer); + + // Helpers to prevent mixer thread from busy waiting + private static native long CreateEvent(); + private static native boolean WaitForSingleObject(long event); + private static native void SetEvent(long event); + private static native void CloseHandle(long handle); + + // We need a reflective hack to wrap a direct ByteBuffer around + // the native memory because JNI_NewDirectByteBuffer doesn't work + // in CVM + JSR-239 NIO + private static Class directByteBufferClass; + private static Constructor directByteBufferConstructor; + private static Map createdBuffers = new HashMap(); // Map Long, ByteBuffer + + private static ByteBuffer newDirectByteBuffer(long address, long capacity) { + Long key = new Long(address); + ByteBuffer buf = (ByteBuffer) createdBuffers.get(key); + if (buf == null) { + buf = newDirectByteBufferImpl(address, capacity); + if (buf != null) { + createdBuffers.put(key, buf); + } + } + return buf; + } + private static ByteBuffer newDirectByteBufferImpl(long address, long capacity) { + if (directByteBufferClass == null) { + try { + directByteBufferClass = Class.forName("java.nio.DirectByteBuffer"); + byte[] tmp = new byte[0]; + directByteBufferConstructor = + directByteBufferClass.getDeclaredConstructor(new Class[] { Integer.TYPE, + tmp.getClass(), + Integer.TYPE }); + directByteBufferConstructor.setAccessible(true); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (directByteBufferConstructor != null) { + try { + return (ByteBuffer) + directByteBufferConstructor.newInstance(new Object[] { + new Integer((int) capacity), + null, + new Integer((int) address) + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + return null; + } +} |