summaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/com/jogamp/audio
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2010-03-29 04:51:18 +0200
committerSven Gothel <[email protected]>2010-03-29 04:51:18 +0200
commitb3f2ab84401efef233c0038f004a9f210cbe83fc (patch)
tree6741a098981c0742e148ce5587aff35d0a776250 /src/jogl/classes/com/jogamp/audio
parent49b145b3fb10b2dd070615c60613d69ca44d4548 (diff)
parent2d57c25287542dcbad59c6b4782e87f86bd0fbc6 (diff)
Merge branch 'master' of github.com:mbien/jogl
Diffstat (limited to 'src/jogl/classes/com/jogamp/audio')
-rwxr-xr-xsrc/jogl/classes/com/jogamp/audio/windows/waveout/Audio.java66
-rwxr-xr-xsrc/jogl/classes/com/jogamp/audio/windows/waveout/Mixer.java362
-rwxr-xr-xsrc/jogl/classes/com/jogamp/audio/windows/waveout/SoundBuffer.java124
-rwxr-xr-xsrc/jogl/classes/com/jogamp/audio/windows/waveout/TestSpatialization.java89
-rwxr-xr-xsrc/jogl/classes/com/jogamp/audio/windows/waveout/Track.java206
-rwxr-xr-xsrc/jogl/classes/com/jogamp/audio/windows/waveout/Vec3f.java212
6 files changed, 1059 insertions, 0 deletions
diff --git a/src/jogl/classes/com/jogamp/audio/windows/waveout/Audio.java b/src/jogl/classes/com/jogamp/audio/windows/waveout/Audio.java
new file mode 100755
index 000000000..2b51be164
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/audio/windows/waveout/Audio.java
@@ -0,0 +1,66 @@
+/*
+ * 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.*;
+
+public class Audio {
+ private static Audio instance = null;
+ private Mixer mixer;
+
+ public synchronized static Audio getInstance() {
+ if (instance == null) {
+ instance = new Audio();
+ }
+ return instance;
+ }
+
+ private Audio() {
+ mixer = Mixer.getMixer();
+ }
+
+ public Mixer getMixer() {
+ return mixer;
+ }
+
+ public Track newTrack(File file) throws IOException
+ {
+ Track res = new Track(file);
+ mixer.add(res);
+ return res;
+ }
+
+ public void shutdown() {
+ mixer.shutdown();
+ }
+}
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;
+ }
+}
diff --git a/src/jogl/classes/com/jogamp/audio/windows/waveout/SoundBuffer.java b/src/jogl/classes/com/jogamp/audio/windows/waveout/SoundBuffer.java
new file mode 100755
index 000000000..c45430d23
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/audio/windows/waveout/SoundBuffer.java
@@ -0,0 +1,124 @@
+/*
+ * 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.*;
+
+class SoundBuffer {
+ private byte[] data;
+ private boolean needsByteSwap;
+ private int numBytes;
+ private int bytesPerSample;
+ private int numSamples;
+ private boolean playing;
+ private boolean empty;
+
+ // Note: needsByteSwap argument makes assumptions about the format
+ SoundBuffer(int size, int bytesPerSample, boolean needsByteSwap) {
+ this.bytesPerSample = bytesPerSample;
+ this.needsByteSwap = needsByteSwap;
+ data = new byte[size * bytesPerSample];
+ empty = true;
+ }
+
+ boolean playing() {
+ return playing;
+ }
+
+ void playing(boolean playing) {
+ this.playing = playing;
+ }
+
+ boolean empty() {
+ return empty;
+ }
+
+ void empty(boolean empty) {
+ this.empty = empty;
+ }
+
+ void fill(InputStream input) throws IOException {
+ synchronized(this) {
+ if (playing) {
+ throw new IllegalStateException("Can not fill a buffer that is playing");
+ }
+ }
+
+ empty(true);
+ int num = input.read(data);
+ if (num > 0) {
+ numBytes = num;
+ numSamples = numBytes / bytesPerSample;
+ empty(false);
+ if ((numBytes % bytesPerSample) != 0) {
+ System.out.println("WARNING: needed integral multiple of " + bytesPerSample +
+ " bytes, but read " + numBytes + " bytes");
+ }
+ } else {
+ numBytes = 0;
+ }
+ }
+
+ int numSamples() {
+ return numSamples;
+ }
+
+ // This is called by the mixer and must be extremely fast
+ // FIXME: may want to reconsider use of floating point at this point
+ // FIXME: assumes all sounds are of the same format to avoid normalization
+ float getSample(int sample) {
+ int startByte = sample * bytesPerSample;
+ // FIXME: assumes no more than 4 bytes per sample
+ int res = 0;
+ if (needsByteSwap) {
+ for (int i = startByte + bytesPerSample - 1; i >= startByte; i--) {
+ res <<= 8;
+ res |= (data[i] & 0xff);
+ }
+ } else {
+ int endByte = startByte + bytesPerSample - 1;
+ for (int i = startByte; i <= endByte; i++) {
+ res <<= 8;
+ res |= (data[i] & 0xff);
+ }
+ }
+ // Sign extend
+ if (bytesPerSample == 2) {
+ res = (short) res;
+ } else if (bytesPerSample == 1) {
+ res = (byte) res;
+ }
+
+ return (float) res;
+ }
+}
diff --git a/src/jogl/classes/com/jogamp/audio/windows/waveout/TestSpatialization.java b/src/jogl/classes/com/jogamp/audio/windows/waveout/TestSpatialization.java
new file mode 100755
index 000000000..78fb3493f
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/audio/windows/waveout/TestSpatialization.java
@@ -0,0 +1,89 @@
+/*
+ * 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 javax.media.nativewindow.NativeWindow;
+import javax.media.opengl.GLDrawableFactory;
+
+public class TestSpatialization {
+ public static void main(String[] args) {
+ if (args.length != 1) {
+ System.out.println("Usage: TestSpatialization [file name]");
+ System.exit(1);
+ }
+
+ try {
+ // FIXME: this is a hack to get the native library loaded
+ try {
+ GLDrawableFactory.getFactory(NativeWindow.class);
+ } catch (Exception e) {}
+ // Initialize the audio subsystem
+ Audio audio = Audio.getInstance();
+ // Create a track
+ Track track = audio.newTrack(new File(args[0]));
+ track.setPosition(1, 0, 0);
+ // Run for ten seconds
+ long startTime = System.currentTimeMillis();
+ long duration = 10000;
+ long curTime = 0;
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ }
+ System.out.println("Playing...");
+ track.setLooping(true);
+ track.play();
+ while ((curTime = System.currentTimeMillis()) < startTime + duration) {
+ // Make one revolution every two seconds
+ float rads = (float) (((2 * Math.PI) * (((float) (curTime - startTime)) / 1000.0f)) / 2);
+ track.setPosition((float) Math.cos(rads), 0, (float) Math.sin(rads));
+ // Would like to make it go in a circle, but since
+ // stereo doesn't work now, make it move along a line
+ // track.setPosition(-1.0f, 0, 2.0f * (float) Math.sin(rads));
+ // Sleep a little between updates
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ }
+ }
+ System.out.println("Shutting down audio subsystem");
+ audio.shutdown();
+ System.out.println("Exiting.");
+ System.exit(0);
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+}
diff --git a/src/jogl/classes/com/jogamp/audio/windows/waveout/Track.java b/src/jogl/classes/com/jogamp/audio/windows/waveout/Track.java
new file mode 100755
index 000000000..b57bf1dc6
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/audio/windows/waveout/Track.java
@@ -0,0 +1,206 @@
+/*
+ * 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.*;
+
+public class Track {
+ // Default number of samples per buffer
+ private static final int BUFFER_SIZE = 32768;
+ // Number of bytes per sample (FIXME: dependence on audio format)
+ static final int BYTES_PER_SAMPLE = 2;
+ // Whether we need byte swapping (FIXME: dependence on audio format)
+ static final boolean NEEDS_BYTE_SWAP = true;
+
+ // This is the buffer this track is currently playing from
+ private SoundBuffer activeBuffer;
+ // This is the sample position in the active buffer
+ private int samplePosition;
+ // This is the total number of samples in the file
+ private int totalSamples;
+ // This is the total number of samples we have read
+ private int samplesRead;
+ // This is the buffer that the background filler thread may be filling
+ private SoundBuffer fillingBuffer;
+ // If we're playing the file, this is its input stream
+ private InputStream input;
+ // Keep around the file name
+ private File file;
+ // Whether we're playing this sound
+ private boolean playing;
+ // Whether we're looping this sound
+ private boolean looping;
+ // The position of this sound; defaults to being at the origin
+ private volatile Vec3f position = new Vec3f();
+
+ Track(File file) throws IOException {
+ if (!file.getName().endsWith(".rawsound")) {
+ throw new IOException("Unsupported file format (currently supports only raw sounds)");
+ }
+
+ this.file = file;
+ openInput();
+
+ // Allocate the buffers
+ activeBuffer = new SoundBuffer(BUFFER_SIZE, BYTES_PER_SAMPLE, NEEDS_BYTE_SWAP);
+ fillingBuffer = new SoundBuffer(BUFFER_SIZE, BYTES_PER_SAMPLE, NEEDS_BYTE_SWAP);
+
+ // Fill the first buffer immediately
+ fill();
+ swapBuffers();
+ }
+
+ private void openInput() throws IOException {
+ input = new BufferedInputStream(new FileInputStream(file));
+ totalSamples = (int) file.length() / BYTES_PER_SAMPLE;
+ }
+
+ public File getFile() {
+ return file;
+ }
+
+ public synchronized void play() {
+ if (input == null) {
+ try {
+ openInput();
+ // Fill it immediately
+ fill();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+
+ playing = true;
+ }
+
+ public synchronized boolean isPlaying() {
+ return playing;
+ }
+
+ public synchronized void setLooping(boolean looping) {
+ this.looping = looping;
+ }
+
+ public synchronized boolean isLooping() {
+ return looping;
+ }
+
+ public void setPosition(float x, float y, float z) {
+ position = new Vec3f(x, y, z);
+ }
+
+ synchronized void fill() throws IOException {
+ if (input == null) {
+ return;
+ }
+ SoundBuffer curBuffer = fillingBuffer;
+ if (!curBuffer.empty()) {
+ return;
+ }
+ curBuffer.fill(input);
+ if (curBuffer.empty()) {
+ // End of file
+ InputStream tmp = null;
+ synchronized(this) {
+ tmp = input;
+ input = null;
+ }
+ tmp.close();
+
+ // If looping, re-open
+ if (isLooping()) {
+ openInput();
+ // and fill
+ fill();
+ }
+ }
+ }
+
+ // These are only for use by the Mixer
+ private float leftGain;
+ private float rightGain;
+
+ void setLeftGain(float leftGain) {
+ this.leftGain = leftGain;
+ }
+
+ float getLeftGain() {
+ return leftGain;
+ }
+
+ void setRightGain(float rightGain) {
+ this.rightGain = rightGain;
+ }
+
+ float getRightGain() {
+ return rightGain;
+ }
+
+ Vec3f getPosition() {
+ return position;
+ }
+
+ // This is called by the mixer and must be extremely fast
+ // Note this assumes mono sounds (FIXME)
+ boolean hasNextSample() {
+ return (!activeBuffer.empty() && samplePosition < activeBuffer.numSamples());
+ }
+
+ // This is called by the mixer and must be extremely fast
+ float nextSample() {
+ float res = activeBuffer.getSample(samplePosition++);
+ ++samplesRead;
+ if (!hasNextSample()) {
+ swapBuffers();
+ samplePosition = 0;
+ if (done()) {
+ playing = false;
+ }
+ }
+ return res;
+ }
+
+ synchronized void swapBuffers() {
+ SoundBuffer tmp = activeBuffer;
+ activeBuffer = fillingBuffer;
+ fillingBuffer = tmp;
+ fillingBuffer.empty(true);
+ }
+
+ // This provides a more robust termination condition
+ boolean done() {
+ return (samplesRead == totalSamples) && !looping;
+ }
+}
diff --git a/src/jogl/classes/com/jogamp/audio/windows/waveout/Vec3f.java b/src/jogl/classes/com/jogamp/audio/windows/waveout/Vec3f.java
new file mode 100755
index 000000000..1afdaf081
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/audio/windows/waveout/Vec3f.java
@@ -0,0 +1,212 @@
+/*
+ * 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;
+
+/** 3-element single-precision vector */
+
+class Vec3f {
+ public static final Vec3f X_AXIS = new Vec3f( 1, 0, 0);
+ public static final Vec3f Y_AXIS = new Vec3f( 0, 1, 0);
+ public static final Vec3f Z_AXIS = new Vec3f( 0, 0, 1);
+ public static final Vec3f NEG_X_AXIS = new Vec3f(-1, 0, 0);
+ public static final Vec3f NEG_Y_AXIS = new Vec3f( 0, -1, 0);
+ public static final Vec3f NEG_Z_AXIS = new Vec3f( 0, 0, -1);
+
+ private float x;
+ private float y;
+ private float z;
+
+ public Vec3f() {}
+
+ public Vec3f(Vec3f arg) {
+ set(arg);
+ }
+
+ public Vec3f(float x, float y, float z) {
+ set(x, y, z);
+ }
+
+ public Vec3f copy() {
+ return new Vec3f(this);
+ }
+
+ public void set(Vec3f arg) {
+ set(arg.x, arg.y, arg.z);
+ }
+
+ public void set(float x, float y, float z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ /** Sets the ith component, 0 <= i < 3 */
+ public void set(int i, float val) {
+ switch (i) {
+ case 0: x = val; break;
+ case 1: y = val; break;
+ case 2: z = val; break;
+ default: throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /** Gets the ith component, 0 <= i < 3 */
+ public float get(int i) {
+ switch (i) {
+ case 0: return x;
+ case 1: return y;
+ case 2: return z;
+ default: throw new IndexOutOfBoundsException();
+ }
+ }
+
+ public float x() { return x; }
+ public float y() { return y; }
+ public float z() { return z; }
+
+ public void setX(float x) { this.x = x; }
+ public void setY(float y) { this.y = y; }
+ public void setZ(float z) { this.z = z; }
+
+ public float dot(Vec3f arg) {
+ return x * arg.x + y * arg.y + z * arg.z;
+ }
+
+ public float length() {
+ return (float) Math.sqrt(lengthSquared());
+ }
+
+ public float lengthSquared() {
+ return this.dot(this);
+ }
+
+ public void normalize() {
+ float len = length();
+ if (len == 0.0f) return;
+ scale(1.0f / len);
+ }
+
+ /** Returns this * val; creates new vector */
+ public Vec3f times(float val) {
+ Vec3f tmp = new Vec3f(this);
+ tmp.scale(val);
+ return tmp;
+ }
+
+ /** this = this * val */
+ public void scale(float val) {
+ x *= val;
+ y *= val;
+ z *= val;
+ }
+
+ /** Returns this + arg; creates new vector */
+ public Vec3f plus(Vec3f arg) {
+ Vec3f tmp = new Vec3f();
+ tmp.add(this, arg);
+ return tmp;
+ }
+
+ /** this = this + b */
+ public void add(Vec3f b) {
+ add(this, b);
+ }
+
+ /** this = a + b */
+ public void add(Vec3f a, Vec3f b) {
+ x = a.x + b.x;
+ y = a.y + b.y;
+ z = a.z + b.z;
+ }
+
+ /** Returns this + s * arg; creates new vector */
+ public Vec3f addScaled(float s, Vec3f arg) {
+ Vec3f tmp = new Vec3f();
+ tmp.addScaled(this, s, arg);
+ return tmp;
+ }
+
+ /** this = a + s * b */
+ public void addScaled(Vec3f a, float s, Vec3f b) {
+ x = a.x + s * b.x;
+ y = a.y + s * b.y;
+ z = a.z + s * b.z;
+ }
+
+ /** Returns this - arg; creates new vector */
+ public Vec3f minus(Vec3f arg) {
+ Vec3f tmp = new Vec3f();
+ tmp.sub(this, arg);
+ return tmp;
+ }
+
+ /** this = this - b */
+ public void sub(Vec3f b) {
+ sub(this, b);
+ }
+
+ /** this = a - b */
+ public void sub(Vec3f a, Vec3f b) {
+ x = a.x - b.x;
+ y = a.y - b.y;
+ z = a.z - b.z;
+ }
+
+ /** Returns this cross arg; creates new vector */
+ public Vec3f cross(Vec3f arg) {
+ Vec3f tmp = new Vec3f();
+ tmp.cross(this, arg);
+ return tmp;
+ }
+
+ /** this = a cross b. NOTE: "this" must be a different vector than
+ both a and b. */
+ public void cross(Vec3f a, Vec3f b) {
+ x = a.y * b.z - a.z * b.y;
+ y = a.z * b.x - a.x * b.z;
+ z = a.x * b.y - a.y * b.x;
+ }
+
+ /** Sets each component of this vector to the product of the
+ component with the corresponding component of the argument
+ vector. */
+ public void componentMul(Vec3f arg) {
+ x *= arg.x;
+ y *= arg.y;
+ z *= arg.z;
+ }
+
+ public String toString() {
+ return "(" + x + ", " + y + ", " + z + ")";
+ }
+}