diff --git a/core/pom.xml b/core/pom.xml
index cffc232e..1d8b2551 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -32,15 +32,9 @@
true
- org.lwjgl.lwjgl
- lwjgl
- 2.9.3
- true
-
-
- org.lwjgl.lwjgl
- lwjgl_util
- 2.9.3
+ org.lwjgl
+ lwjgl-openal
+ 3.3.6
true
diff --git a/core/src/main/java/com/threerings/openal/Clip.java b/core/src/main/java/com/threerings/openal/Clip.java
index 95a2007a..86fc7540 100644
--- a/core/src/main/java/com/threerings/openal/Clip.java
+++ b/core/src/main/java/com/threerings/openal/Clip.java
@@ -22,7 +22,7 @@ package com.threerings.openal;
import java.nio.ByteBuffer;
import org.lwjgl.openal.AL10;
-import org.lwjgl.util.WaveData;
+import com.threerings.openal.util.WaveData;
/**
* Contains data for a single sampled sound.
diff --git a/core/src/main/java/com/threerings/openal/Listener.java b/core/src/main/java/com/threerings/openal/Listener.java
index f49c24a6..0ce6c605 100644
--- a/core/src/main/java/com/threerings/openal/Listener.java
+++ b/core/src/main/java/com/threerings/openal/Listener.java
@@ -19,9 +19,6 @@
package com.threerings.openal;
-import java.nio.FloatBuffer;
-
-import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL10;
/**
@@ -89,9 +86,13 @@ public class Listener
public void setOrientation (float ax, float ay, float az, float ux, float uy, float uz)
{
if (_ax != ax || _ay != ay || _az != az || _ux != ux || _uy != uy || _uz != uz) {
- _vbuf.put(_ax = ax).put(_ay = ay).put(_az = az);
- _vbuf.put(_ux = ux).put(_uy = uy).put(_uz = uz).rewind();
- AL10.alListener(AL10.AL_ORIENTATION, _vbuf);
+ _vbuf[0] = (_ax = ax);
+ _vbuf[1] = (_ay = ay);
+ _vbuf[2] = (_az = az);
+ _vbuf[3] = (_ux = ux);
+ _vbuf[4] = (_uy = uy);
+ _vbuf[5] = (_uz = uz);
+ AL10.alListenerfv(AL10.AL_ORIENTATION, _vbuf);
}
}
@@ -115,5 +116,5 @@ public class Listener
protected float _ax, _ay, _az = -1f, _ux, _uy = 1f, _uz;
/** A buffer for floating point values. */
- protected FloatBuffer _vbuf = BufferUtils.createFloatBuffer(6);
+ protected float[] _vbuf = new float[6];
}
diff --git a/core/src/main/java/com/threerings/openal/OpenALSoundPlayer.java b/core/src/main/java/com/threerings/openal/OpenALSoundPlayer.java
index fa0a64b3..aaa4523a 100644
--- a/core/src/main/java/com/threerings/openal/OpenALSoundPlayer.java
+++ b/core/src/main/java/com/threerings/openal/OpenALSoundPlayer.java
@@ -34,7 +34,7 @@ import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import org.lwjgl.openal.AL10;
-import org.lwjgl.util.WaveData;
+import com.threerings.openal.util.WaveData;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
diff --git a/core/src/main/java/com/threerings/openal/SoundManager.java b/core/src/main/java/com/threerings/openal/SoundManager.java
index e66a20a8..6f78e394 100644
--- a/core/src/main/java/com/threerings/openal/SoundManager.java
+++ b/core/src/main/java/com/threerings/openal/SoundManager.java
@@ -23,11 +23,15 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
-import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
+import org.lwjgl.openal.AL;
+import org.lwjgl.openal.ALC10;
+import org.lwjgl.openal.ALC;
+import org.lwjgl.openal.ALCCapabilities;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -77,8 +81,13 @@ public class SoundManager
*/
public void shutdown ()
{
- if (isInitialized() && !_sharingAL) {
- AL.destroy();
+ if (_alcContext != 0L) {
+ ALC10.alcDestroyContext(_alcContext);
+ _alcContext = 0L;
+ }
+ if (_alcDevice != 0L) {
+ ALC10.alcCloseDevice(_alcDevice);
+ _alcDevice = 0L;
}
}
@@ -201,23 +210,31 @@ public class SoundManager
_rqueue = rqueue;
// initialize the OpenAL sound system
- if (AL.isCreated()) {
- _sharingAL = true;
- } else {
- try {
- AL.create("", 44100, 15, false);
- } catch (Exception e) {
- log.warning("Failed to initialize sound system.", e);
+ try {
+ _alcDevice = ALC10.alcOpenDevice((ByteBuffer)null);
+ if (_alcDevice == 0) {
+ log.warning("Failed to create AL device.");
// don't start the background loading thread
return;
}
+ ALCCapabilities deviceCaps = ALC.createCapabilities(_alcDevice);
- int errno = AL10.alGetError();
- if (errno != AL10.AL_NO_ERROR) {
- log.warning("Failed to initialize sound system [errno=" + errno + "].");
- // don't start the background loading thread
- return;
- }
+ _alcContext = ALC10.alcCreateContext(_alcDevice, (IntBuffer)null);
+ ALC10.alcMakeContextCurrent(_alcContext);
+ AL.createCapabilities(deviceCaps);
+
+ // AL.create("", 44100, 15, false);
+ } catch (Exception e) {
+ log.warning("Failed to initialize sound system.", e);
+ // don't start the background loading thread
+ return;
+ }
+
+ int errno = AL10.alGetError();
+ if (errno != AL10.AL_NO_ERROR) {
+ log.warning("Failed to initialize sound system [errno=" + errno + "].");
+ // don't start the background loading thread
+ return;
}
// configure our LRU map with a removal observer
@@ -415,8 +432,8 @@ public class SoundManager
}
};
- /** Whether or not we're sharing an AL context that someone else created. */
- protected boolean _sharingAL;
+ protected long _alcDevice;
+ protected long _alcContext;
/** Used to get back from the background thread to our "main" thread. */
protected RunQueue _rqueue;
diff --git a/core/src/main/java/com/threerings/openal/WaveDataClipProvider.java b/core/src/main/java/com/threerings/openal/WaveDataClipProvider.java
index de18233d..c91b5ea5 100644
--- a/core/src/main/java/com/threerings/openal/WaveDataClipProvider.java
+++ b/core/src/main/java/com/threerings/openal/WaveDataClipProvider.java
@@ -21,7 +21,7 @@ package com.threerings.openal;
import java.io.IOException;
-import org.lwjgl.util.WaveData;
+import com.threerings.openal.util.WaveData;
/**
* Loads clips via the class loader using LWJGL's {@link WaveData} utility
diff --git a/core/src/main/java/com/threerings/openal/util/SunFileReader.java b/core/src/main/java/com/threerings/openal/util/SunFileReader.java
new file mode 100644
index 00000000..3a32a769
--- /dev/null
+++ b/core/src/main/java/com/threerings/openal/util/SunFileReader.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.threerings.openal.util;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.net.URL;
+
+import javax.sound.sampled.AudioFileFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.UnsupportedAudioFileException;
+import javax.sound.sampled.spi.AudioFileReader;
+
+
+
+/**
+ * Abstract File Reader class.
+ *
+ * @author Jan Borgersen
+ */
+abstract class SunFileReader extends AudioFileReader {
+
+ // buffer size for temporary input streams
+ protected static final int bisBufferSize = 4096;
+
+ /**
+ * Constructs a new SunFileReader object.
+ */
+ SunFileReader() {
+ }
+
+
+ // METHODS TO IMPLEMENT AudioFileReader
+
+ /**
+ * Obtains the audio file format of the input stream provided. The stream must
+ * point to valid audio file data. In general, audio file providers may
+ * need to read some data from the stream before determining whether they
+ * support it. These parsers must
+ * be able to mark the stream, read enough data to determine whether they
+ * support the stream, and, if not, reset the stream's read pointer to its original
+ * position. If the input stream does not support this, this method may fail
+ * with an IOException.
+ * @param stream the input stream from which file format information should be
+ * extracted
+ * @return an AudioFileFormat object describing the audio file format
+ * @throws UnsupportedAudioFileException if the stream does not point to valid audio
+ * file data recognized by the system
+ * @throws IOException if an I/O exception occurs
+ * @see InputStream#markSupported
+ * @see InputStream#mark
+ */
+ abstract public AudioFileFormat getAudioFileFormat(InputStream stream) throws UnsupportedAudioFileException, IOException;
+
+
+ /**
+ * Obtains the audio file format of the URL provided. The URL must
+ * point to valid audio file data.
+ * @param url the URL from which file format information should be
+ * extracted
+ * @return an AudioFileFormat object describing the audio file format
+ * @throws UnsupportedAudioFileException if the URL does not point to valid audio
+ * file data recognized by the system
+ * @throws IOException if an I/O exception occurs
+ */
+ abstract public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException;
+
+
+ /**
+ * Obtains the audio file format of the File provided. The File must
+ * point to valid audio file data.
+ * @param file the File from which file format information should be
+ * extracted
+ * @return an AudioFileFormat object describing the audio file format
+ * @throws UnsupportedAudioFileException if the File does not point to valid audio
+ * file data recognized by the system
+ * @throws IOException if an I/O exception occurs
+ */
+ abstract public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException;
+
+
+ /**
+ * Obtains an audio stream from the input stream provided. The stream must
+ * point to valid audio file data. In general, audio file providers may
+ * need to read some data from the stream before determining whether they
+ * support it. These parsers must
+ * be able to mark the stream, read enough data to determine whether they
+ * support the stream, and, if not, reset the stream's read pointer to its original
+ * position. If the input stream does not support this, this method may fail
+ * with an IOException.
+ * @param stream the input stream from which the AudioInputStream should be
+ * constructed
+ * @return an AudioInputStream object based on the audio file data contained
+ * in the input stream.
+ * @throws UnsupportedAudioFileException if the stream does not point to valid audio
+ * file data recognized by the system
+ * @throws IOException if an I/O exception occurs
+ * @see InputStream#markSupported
+ * @see InputStream#mark
+ */
+ abstract public AudioInputStream getAudioInputStream(InputStream stream) throws UnsupportedAudioFileException, IOException;
+
+
+ /**
+ * Obtains an audio stream from the URL provided. The URL must
+ * point to valid audio file data.
+ * @param url the URL for which the AudioInputStream should be
+ * constructed
+ * @return an AudioInputStream object based on the audio file data pointed
+ * to by the URL
+ * @throws UnsupportedAudioFileException if the URL does not point to valid audio
+ * file data recognized by the system
+ * @throws IOException if an I/O exception occurs
+ */
+ abstract public AudioInputStream getAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException;
+
+
+ /**
+ * Obtains an audio stream from the File provided. The File must
+ * point to valid audio file data.
+ * @param file the File for which the AudioInputStream should be
+ * constructed
+ * @return an AudioInputStream object based on the audio file data pointed
+ * to by the File
+ * @throws UnsupportedAudioFileException if the File does not point to valid audio
+ * file data recognized by the system
+ * @throws IOException if an I/O exception occurs
+ */
+ abstract public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException;
+
+
+ // HELPER METHODS
+
+
+
+ /**
+ * rllong
+ * Protected helper method to read 64 bits and changing the order of
+ * each bytes.
+ * @param DataInputStream
+ * @return 32 bits swapped value.
+ * @exception IOException
+ */
+ final int rllong(DataInputStream dis) throws IOException {
+
+ int b1, b2, b3, b4 ;
+ int i = 0;
+
+ i = dis.readInt();
+
+ b1 = ( i & 0xFF ) << 24 ;
+ b2 = ( i & 0xFF00 ) << 8;
+ b3 = ( i & 0xFF0000 ) >> 8;
+ b4 = ( i & 0xFF000000 ) >>> 24;
+
+ i = ( b1 | b2 | b3 | b4 );
+
+ return i;
+ }
+
+ /**
+ * big2little
+ * Protected helper method to swap the order of bytes in a 32 bit int
+ * @param int
+ * @return 32 bits swapped value
+ */
+ final int big2little(int i) {
+
+ int b1, b2, b3, b4 ;
+
+ b1 = ( i & 0xFF ) << 24 ;
+ b2 = ( i & 0xFF00 ) << 8;
+ b3 = ( i & 0xFF0000 ) >> 8;
+ b4 = ( i & 0xFF000000 ) >>> 24;
+
+ i = ( b1 | b2 | b3 | b4 );
+
+ return i;
+ }
+
+ /**
+ * rlshort
+ * Protected helper method to read 16 bits value. Swap high with low byte.
+ * @param DataInputStream
+ * @return the swapped value.
+ * @exception IOException
+ */
+ final short rlshort(DataInputStream dis) throws IOException {
+
+ short s=0;
+ short high, low;
+
+ s = dis.readShort();
+
+ high = (short)(( s & 0xFF ) << 8) ;
+ low = (short)(( s & 0xFF00 ) >>> 8);
+
+ s = (short)( high | low );
+
+ return s;
+ }
+
+ /**
+ * big2little
+ * Protected helper method to swap the order of bytes in a 16 bit short
+ * @param int
+ * @return 16 bits swapped value
+ */
+ final short big2littleShort(short i) {
+
+ short high, low;
+
+ high = (short)(( i & 0xFF ) << 8) ;
+ low = (short)(( i & 0xFF00 ) >>> 8);
+
+ i = (short)( high | low );
+
+ return i;
+ }
+
+ /** Calculates the frame size for PCM frames.
+ * Note that this method is appropriate for non-packed samples.
+ * For instance, 12 bit, 2 channels will return 4 bytes, not 3.
+ * @param sampleSizeInBits the size of a single sample in bits
+ * @param channels the number of channels
+ * @return the size of a PCM frame in bytes.
+ */
+ static final int calculatePCMFrameSize(int sampleSizeInBits, int channels) {
+ return ((sampleSizeInBits + 7) / 8) * channels;
+ }
+}
diff --git a/core/src/main/java/com/threerings/openal/util/WaveData.java b/core/src/main/java/com/threerings/openal/util/WaveData.java
new file mode 100644
index 00000000..38f43468
--- /dev/null
+++ b/core/src/main/java/com/threerings/openal/util/WaveData.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2002-2008 LWJGL Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions 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 'LWJGL' nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.threerings.openal.util;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ShortBuffer;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+
+import org.lwjgl.openal.AL10;
+
+import static com.threerings.openal.Log.log;
+
+/**
+ *
+ * Utitlity class for loading wavefiles.
+ *
+ * @author Brian Matzon
+ * @version $Revision$
+ * $Id$
+ */
+public class WaveData {
+ /** actual wave data */
+ public final ByteBuffer data;
+
+ /** format type of data */
+ public final int format;
+
+ /** sample rate of data */
+ public final int samplerate;
+
+ /**
+ * Creates a new WaveData
+ *
+ * @param data actual wavedata
+ * @param format format of wave data
+ * @param samplerate sample rate of data
+ */
+ private WaveData(ByteBuffer data, int format, int samplerate) {
+ this.data = data;
+ this.format = format;
+ this.samplerate = samplerate;
+ }
+
+ /**
+ * Disposes the wavedata
+ */
+ public void dispose() {
+ data.clear();
+ }
+
+ /**
+ * Creates a WaveData container from the specified url
+ *
+ * @param path URL to file
+ * @return WaveData containing data, or null if a failure occured
+ */
+ public static WaveData create(URL path) {
+ try {
+ // due to an issue with AudioSystem.getAudioInputStream
+ // and mixing unsigned and signed code
+ // we will use the reader directly
+ WaveFileReader wfr = new WaveFileReader();
+ return create(wfr.getAudioInputStream(new BufferedInputStream(path.openStream())));
+ } catch (Exception e) {
+ log.warning("Unable to create from: " + path + ", " + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Creates a WaveData container from the specified in the classpath
+ *
+ * @param path path to file (relative, and in classpath)
+ * @return WaveData containing data, or null if a failure occured
+ */
+ public static WaveData create(String path) {
+ return create(Thread.currentThread().getContextClassLoader().getResource(path));
+ }
+
+ /**
+ * Creates a WaveData container from the specified inputstream
+ *
+ * @param is InputStream to read from
+ * @return WaveData containing data, or null if a failure occured
+ */
+ public static WaveData create(InputStream is) {
+ try {
+ return create(
+ AudioSystem.getAudioInputStream(is));
+ } catch (Exception e) {
+ log.warning("Unable to create from inputstream, " + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Creates a WaveData container from the specified bytes
+ *
+ * @param buffer array of bytes containing the complete wave file
+ * @return WaveData containing data, or null if a failure occured
+ */
+ public static WaveData create(byte[] buffer) {
+ try {
+ return create(
+ AudioSystem.getAudioInputStream(
+ new BufferedInputStream(new ByteArrayInputStream(buffer))));
+ } catch (Exception e) {
+ log.warning("Unable to create from byte array, " + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Creates a WaveData container from the specified ByetBuffer.
+ * If the buffer is backed by an array, it will be used directly,
+ * else the contents of the buffer will be copied using get(byte[]).
+ *
+ * @param buffer ByteBuffer containing sound file
+ * @return WaveData containing data, or null if a failure occured
+ */
+ public static WaveData create(ByteBuffer buffer) {
+ try {
+ byte[] bytes = null;
+
+ if(buffer.hasArray()) {
+ bytes = buffer.array();
+ } else {
+ bytes = new byte[buffer.capacity()];
+ buffer.get(bytes);
+ }
+ return create(bytes);
+ } catch (Exception e) {
+ log.warning("Unable to create from ByteBuffer, " + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Creates a WaveData container from the specified stream
+ *
+ * @param ais AudioInputStream to read from
+ * @return WaveData containing data, or null if a failure occured
+ */
+ public static WaveData create(AudioInputStream ais) {
+ //get format of data
+ AudioFormat audioformat = ais.getFormat();
+
+ // get channels
+ int channels = 0;
+ if (audioformat.getChannels() == 1) {
+ if (audioformat.getSampleSizeInBits() == 8) {
+ channels = AL10.AL_FORMAT_MONO8;
+ } else if (audioformat.getSampleSizeInBits() == 16) {
+ channels = AL10.AL_FORMAT_MONO16;
+ } else {
+ assert false : "Illegal sample size";
+ }
+ } else if (audioformat.getChannels() == 2) {
+ if (audioformat.getSampleSizeInBits() == 8) {
+ channels = AL10.AL_FORMAT_STEREO8;
+ } else if (audioformat.getSampleSizeInBits() == 16) {
+ channels = AL10.AL_FORMAT_STEREO16;
+ } else {
+ assert false : "Illegal sample size";
+ }
+ } else {
+ assert false : "Only mono or stereo is supported";
+ }
+
+ //read data into buffer
+ ByteBuffer buffer = null;
+ try {
+ int available = ais.available();
+ if(available <= 0) {
+ available = ais.getFormat().getChannels() * (int) ais.getFrameLength() * ais.getFormat().getSampleSizeInBits() / 8;
+ }
+ byte[] buf = new byte[ais.available()];
+ int read = 0, total = 0;
+ while ((read = ais.read(buf, total, buf.length - total)) != -1
+ && total < buf.length) {
+ total += read;
+ }
+ buffer = convertAudioBytes(buf, audioformat.getSampleSizeInBits() == 16, audioformat.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
+ } catch (IOException ioe) {
+ return null;
+ }
+
+
+ //create our result
+ WaveData wavedata =
+ new WaveData(buffer, channels, (int) audioformat.getSampleRate());
+
+ //close stream
+ try {
+ ais.close();
+ } catch (IOException ioe) {
+ }
+
+ return wavedata;
+ }
+
+ private static ByteBuffer convertAudioBytes(byte[] audio_bytes, boolean two_bytes_data, ByteOrder order) {
+ ByteBuffer dest = ByteBuffer.allocateDirect(audio_bytes.length);
+ dest.order(ByteOrder.nativeOrder());
+ ByteBuffer src = ByteBuffer.wrap(audio_bytes);
+ src.order(order);
+ if (two_bytes_data) {
+ ShortBuffer dest_short = dest.asShortBuffer();
+ ShortBuffer src_short = src.asShortBuffer();
+ while (src_short.hasRemaining())
+ dest_short.put(src_short.get());
+ } else {
+ while (src.hasRemaining())
+ dest.put(src.get());
+ }
+ dest.rewind();
+ return dest;
+ }
+}
diff --git a/core/src/main/java/com/threerings/openal/util/WaveFileFormat.java b/core/src/main/java/com/threerings/openal/util/WaveFileFormat.java
new file mode 100644
index 00000000..f3ee7ba8
--- /dev/null
+++ b/core/src/main/java/com/threerings/openal/util/WaveFileFormat.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.threerings.openal.util;
+
+import javax.sound.sampled.AudioFileFormat;
+import javax.sound.sampled.AudioFormat;
+
+
+/**
+ * WAVE file format class.
+ *
+ * @author Jan Borgersen
+ */
+
+final class WaveFileFormat extends AudioFileFormat {
+
+ /**
+ * Wave format type.
+ */
+ private final int waveType;
+
+ //$$fb 2001-07-13: added management of header size in this class
+ //$$fb 2002-04-16: Fix for 4636355: RIFF audio headers could be _more_ spec compliant
+ private static final int STANDARD_HEADER_SIZE = 28;
+
+ //$$fb 2002-04-16: Fix for 4636355: RIFF audio headers could be _more_ spec compliant
+ /**
+ * fmt_ chunk size in bytes
+ */
+ private static final int STANDARD_FMT_CHUNK_SIZE = 16;
+
+ // magic numbers
+ static final int RIFF_MAGIC = 1380533830;
+ static final int WAVE_MAGIC = 1463899717;
+ static final int FMT_MAGIC = 0x666d7420; // "fmt "
+ static final int DATA_MAGIC = 0x64617461; // "data"
+
+ // encodings
+ static final int WAVE_FORMAT_UNKNOWN = 0x0000;
+ static final int WAVE_FORMAT_PCM = 0x0001;
+ static final int WAVE_FORMAT_ADPCM = 0x0002;
+ static final int WAVE_FORMAT_ALAW = 0x0006;
+ static final int WAVE_FORMAT_MULAW = 0x0007;
+ static final int WAVE_FORMAT_OKI_ADPCM = 0x0010;
+ static final int WAVE_FORMAT_DIGISTD = 0x0015;
+ static final int WAVE_FORMAT_DIGIFIX = 0x0016;
+ static final int WAVE_IBM_FORMAT_MULAW = 0x0101;
+ static final int WAVE_IBM_FORMAT_ALAW = 0x0102;
+ static final int WAVE_IBM_FORMAT_ADPCM = 0x0103;
+ static final int WAVE_FORMAT_DVI_ADPCM = 0x0011;
+ static final int WAVE_FORMAT_SX7383 = 0x1C07;
+
+
+ WaveFileFormat( AudioFileFormat aff ) {
+
+ this( aff.getType(), aff.getByteLength(), aff.getFormat(), aff.getFrameLength() );
+ }
+
+ WaveFileFormat(AudioFileFormat.Type type, int lengthInBytes, AudioFormat format, int lengthInFrames) {
+
+ super(type,lengthInBytes,format,lengthInFrames);
+
+ AudioFormat.Encoding encoding = format.getEncoding();
+
+ if( encoding.equals(AudioFormat.Encoding.ALAW) ) {
+ waveType = WAVE_FORMAT_ALAW;
+ } else if( encoding.equals(AudioFormat.Encoding.ULAW) ) {
+ waveType = WAVE_FORMAT_MULAW;
+ } else if( encoding.equals(AudioFormat.Encoding.PCM_SIGNED) ||
+ encoding.equals(AudioFormat.Encoding.PCM_UNSIGNED) ) {
+ waveType = WAVE_FORMAT_PCM;
+ } else {
+ waveType = WAVE_FORMAT_UNKNOWN;
+ }
+ }
+
+ int getWaveType() {
+
+ return waveType;
+ }
+
+ int getHeaderSize() {
+ return getHeaderSize(getWaveType());
+ }
+
+ static int getHeaderSize(int waveType) {
+ //$$fb 2002-04-16: Fix for 4636355: RIFF audio headers could be _more_ spec compliant
+ // use dynamic format chunk size
+ return STANDARD_HEADER_SIZE + getFmtChunkSize(waveType);
+ }
+
+ static int getFmtChunkSize(int waveType) {
+ //$$fb 2002-04-16: Fix for 4636355: RIFF audio headers could be _more_ spec compliant
+ // add 2 bytes for "codec specific data length" for non-PCM codecs
+ int result = STANDARD_FMT_CHUNK_SIZE;
+ if (waveType != WAVE_FORMAT_PCM) {
+ result += 2; // WORD for "codec specific data length"
+ }
+ return result;
+ }
+}
diff --git a/core/src/main/java/com/threerings/openal/util/WaveFileReader.java b/core/src/main/java/com/threerings/openal/util/WaveFileReader.java
new file mode 100644
index 00000000..b79deca5
--- /dev/null
+++ b/core/src/main/java/com/threerings/openal/util/WaveFileReader.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.threerings.openal.util;
+
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.sound.sampled.AudioFileFormat;
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.UnsupportedAudioFileException;
+
+
+
+/**
+ * WAVE file reader.
+ *
+ * @author Kara Kytle
+ * @author Jan Borgersen
+ * @author Florian Bomers
+ */
+public final class WaveFileReader extends SunFileReader {
+
+ private static final int MAX_READ_LENGTH = 12;
+
+ /**
+ * Obtains the audio file format of the input stream provided. The stream must
+ * point to valid audio file data. In general, audio file providers may
+ * need to read some data from the stream before determining whether they
+ * support it. These parsers must
+ * be able to mark the stream, read enough data to determine whether they
+ * support the stream, and, if not, reset the stream's read pointer to its original
+ * position. If the input stream does not support this, this method may fail
+ * with an IOException.
+ * @param stream the input stream from which file format information should be
+ * extracted
+ * @return an AudioFileFormat object describing the audio file format
+ * @throws UnsupportedAudioFileException if the stream does not point to valid audio
+ * file data recognized by the system
+ * @throws IOException if an I/O exception occurs
+ * @see InputStream#markSupported
+ * @see InputStream#mark
+ */
+ public AudioFileFormat getAudioFileFormat(InputStream stream) throws UnsupportedAudioFileException, IOException {
+ // fix for 4489272: AudioSystem.getAudioFileFormat() fails for InputStream, but works for URL
+ AudioFileFormat aff = getFMT(stream, true);
+ // the following is not strictly necessary - but was implemented like that in 1.3.0 - 1.4.1
+ // so I leave it as it was. May remove this for 1.5.0
+ stream.reset();
+ return aff;
+ }
+
+
+ /**
+ * Obtains the audio file format of the URL provided. The URL must
+ * point to valid audio file data.
+ * @param url the URL from which file format information should be
+ * extracted
+ * @return an AudioFileFormat object describing the audio file format
+ * @throws UnsupportedAudioFileException if the URL does not point to valid audio
+ * file data recognized by the system
+ * @throws IOException if an I/O exception occurs
+ */
+ public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException {
+ InputStream urlStream = url.openStream(); // throws IOException
+ AudioFileFormat fileFormat = null;
+ try {
+ fileFormat = getFMT(urlStream, false);
+ } finally {
+ urlStream.close();
+ }
+ return fileFormat;
+ }
+
+
+ /**
+ * Obtains the audio file format of the File provided. The File must
+ * point to valid audio file data.
+ * @param file the File from which file format information should be
+ * extracted
+ * @return an AudioFileFormat object describing the audio file format
+ * @throws UnsupportedAudioFileException if the File does not point to valid audio
+ * file data recognized by the system
+ * @throws IOException if an I/O exception occurs
+ */
+ public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException {
+ AudioFileFormat fileFormat = null;
+ FileInputStream fis = new FileInputStream(file); // throws IOException
+ // part of fix for 4325421
+ try {
+ fileFormat = getFMT(fis, false);
+ } finally {
+ fis.close();
+ }
+
+ return fileFormat;
+ }
+
+
+ /**
+ * Obtains an audio stream from the input stream provided. The stream must
+ * point to valid audio file data. In general, audio file providers may
+ * need to read some data from the stream before determining whether they
+ * support it. These parsers must
+ * be able to mark the stream, read enough data to determine whether they
+ * support the stream, and, if not, reset the stream's read pointer to its original
+ * position. If the input stream does not support this, this method may fail
+ * with an IOException.
+ * @param stream the input stream from which the AudioInputStream should be
+ * constructed
+ * @return an AudioInputStream object based on the audio file data contained
+ * in the input stream.
+ * @throws UnsupportedAudioFileException if the stream does not point to valid audio
+ * file data recognized by the system
+ * @throws IOException if an I/O exception occurs
+ * @see InputStream#markSupported
+ * @see InputStream#mark
+ */
+ public AudioInputStream getAudioInputStream(InputStream stream) throws UnsupportedAudioFileException, IOException {
+ // getFMT leaves the input stream at the beginning of the audio data
+ AudioFileFormat fileFormat = getFMT(stream, true); // throws UnsupportedAudioFileException, IOException
+
+ // we've got everything, and the stream is at the
+ // beginning of the audio data, so return an AudioInputStream.
+ return new AudioInputStream(stream, fileFormat.getFormat(), fileFormat.getFrameLength());
+ }
+
+
+ /**
+ * Obtains an audio stream from the URL provided. The URL must
+ * point to valid audio file data.
+ * @param url the URL for which the AudioInputStream should be
+ * constructed
+ * @return an AudioInputStream object based on the audio file data pointed
+ * to by the URL
+ * @throws UnsupportedAudioFileException if the URL does not point to valid audio
+ * file data recognized by the system
+ * @throws IOException if an I/O exception occurs
+ */
+ public AudioInputStream getAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException {
+ InputStream urlStream = url.openStream(); // throws IOException
+ AudioFileFormat fileFormat = null;
+ try {
+ fileFormat = getFMT(urlStream, false);
+ } finally {
+ if (fileFormat == null) {
+ urlStream.close();
+ }
+ }
+ return new AudioInputStream(urlStream, fileFormat.getFormat(), fileFormat.getFrameLength());
+ }
+
+
+ /**
+ * Obtains an audio stream from the File provided. The File must
+ * point to valid audio file data.
+ * @param file the File for which the AudioInputStream should be
+ * constructed
+ * @return an AudioInputStream object based on the audio file data pointed
+ * to by the File
+ * @throws UnsupportedAudioFileException if the File does not point to valid audio
+ * file data recognized by the system
+ * @throws IOException if an I/O exception occurs
+ */
+ public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException {
+ FileInputStream fis = new FileInputStream(file); // throws IOException
+ AudioFileFormat fileFormat = null;
+ // part of fix for 4325421
+ try {
+ fileFormat = getFMT(fis, false);
+ } finally {
+ if (fileFormat == null) {
+ fis.close();
+ }
+ }
+ return new AudioInputStream(fis, fileFormat.getFormat(), fileFormat.getFrameLength());
+ }
+
+
+ //--------------------------------------------------------------------
+
+
+ private AudioFileFormat getFMT(InputStream stream, boolean doReset) throws UnsupportedAudioFileException, IOException {
+
+ // assumes sream is rewound
+
+ int bytesRead;
+ int nread = 0;
+ int fmt;
+ int length = 0;
+ int wav_type = 0;
+ short channels;
+ long sampleRate;
+ long avgBytesPerSec;
+ short blockAlign;
+ int sampleSizeInBits;
+ AudioFormat.Encoding encoding = null;
+
+ DataInputStream dis = new DataInputStream( stream );
+
+ if (doReset) {
+ dis.mark(MAX_READ_LENGTH);
+ }
+
+ int magic = dis.readInt();
+ int fileLength = rllong(dis);
+ int waveMagic = dis.readInt();
+ int totallength;
+ if (fileLength <= 0) {
+ fileLength = AudioSystem.NOT_SPECIFIED;
+ totallength = AudioSystem.NOT_SPECIFIED;
+ } else {
+ totallength = fileLength + 8;
+ }
+
+ if ((magic != WaveFileFormat.RIFF_MAGIC) || (waveMagic != WaveFileFormat.WAVE_MAGIC)) {
+ // not WAVE, throw UnsupportedAudioFileException
+ if (doReset) {
+ dis.reset();
+ }
+ throw new UnsupportedAudioFileException("not a WAVE file");
+ }
+
+ // find and read the "fmt" chunk
+ // we break out of this loop either by hitting EOF or finding "fmt "
+ while(true) {
+
+ try {
+ fmt = dis.readInt();
+ nread += 4;
+ if( fmt==WaveFileFormat.FMT_MAGIC ) {
+ // we've found the 'fmt' chunk
+ break;
+ } else {
+ // else not 'fmt', skip this chunk
+ length = rllong(dis);
+ nread += 4;
+ if (length % 2 > 0) length++;
+ nread += dis.skipBytes(length);
+ }
+ } catch (EOFException eof) {
+ // we've reached the end of the file without finding the 'fmt' chunk
+ throw new UnsupportedAudioFileException("Not a valid WAV file");
+ }
+ }
+
+ // Read the format chunk size.
+ length = rllong(dis);
+ nread += 4;
+
+ // This is the nread position at the end of the format chunk
+ int endLength = nread + length;
+
+ // Read the wave format data out of the format chunk.
+
+ // encoding.
+ wav_type = rlshort(dis); nread += 2;
+
+ if (wav_type == WaveFileFormat.WAVE_FORMAT_PCM)
+ encoding = AudioFormat.Encoding.PCM_SIGNED; // if 8-bit, we need PCM_UNSIGNED, below...
+ else if ( wav_type == WaveFileFormat.WAVE_FORMAT_ALAW )
+ encoding = AudioFormat.Encoding.ALAW;
+ else if ( wav_type == WaveFileFormat.WAVE_FORMAT_MULAW )
+ encoding = AudioFormat.Encoding.ULAW;
+ else {
+ // we don't support any other WAVE formats....
+ throw new UnsupportedAudioFileException("Not a supported WAV file");
+ }
+ // channels
+ channels = rlshort(dis); nread += 2;
+ if (channels <= 0) {
+ throw new UnsupportedAudioFileException("Invalid number of channels");
+ }
+
+ // sample rate.
+ sampleRate = rllong(dis); nread += 4;
+
+ // this is the avgBytesPerSec
+ avgBytesPerSec = rllong(dis); nread += 4;
+
+ // this is blockAlign value
+ blockAlign = rlshort(dis); nread += 2;
+
+ // this is the PCM-specific value bitsPerSample
+ sampleSizeInBits = (int)rlshort(dis); nread += 2;
+ if (sampleSizeInBits <= 0) {
+ throw new UnsupportedAudioFileException("Invalid bitsPerSample");
+ }
+
+ // if sampleSizeInBits==8, we need to use PCM_UNSIGNED
+ if ((sampleSizeInBits==8) && encoding.equals(AudioFormat.Encoding.PCM_SIGNED))
+ encoding = AudioFormat.Encoding.PCM_UNSIGNED;
+
+ // skip any difference between the length of the format chunk
+ // and what we read
+
+ // if the length of the chunk is odd, there's an extra pad byte
+ // at the end. i've never seen this in the fmt chunk, but we
+ // should check to make sure.
+
+ if (length % 2 != 0) length += 1;
+
+ // $$jb: 07.28.99: endLength>nread, not length>nread.
+ // This fixes #4257986
+ if (endLength > nread)
+ nread += dis.skipBytes(endLength - nread);
+
+ // we have a format now, so find the "data" chunk
+ // we break out of this loop either by hitting EOF or finding "data"
+ // $$kk: if "data" chunk precedes "fmt" chunk we are hosed -- can this legally happen?
+ nread = 0;
+ while(true) {
+ try{
+ int datahdr = dis.readInt();
+ nread+=4;
+ if (datahdr == WaveFileFormat.DATA_MAGIC) {
+ // we've found the 'data' chunk
+ break;
+ } else {
+ // else not 'data', skip this chunk
+ int thisLength = rllong(dis); nread += 4;
+ if (thisLength % 2 > 0) thisLength++;
+ nread += dis.skipBytes(thisLength);
+ }
+ } catch (EOFException eof) {
+ // we've reached the end of the file without finding the 'data' chunk
+ throw new UnsupportedAudioFileException("Not a valid WAV file");
+ }
+ }
+ // this is the length of the data chunk
+ int dataLength = rllong(dis); nread += 4;
+
+ // now build the new AudioFileFormat and return
+
+ AudioFormat format = new AudioFormat(encoding,
+ (float)sampleRate,
+ sampleSizeInBits, channels,
+ calculatePCMFrameSize(sampleSizeInBits, channels),
+ (float)sampleRate, false);
+
+ return new WaveFileFormat(AudioFileFormat.Type.WAVE,
+ totallength,
+ format,
+ dataLength / format.getFrameSize());
+ }
+}