added sources for Slick

Former-commit-id: 1647fa32ef6894bd7db44f741f07c2f4dcdf9054
Former-commit-id: 0e5810dcfbe1fd59b13e7cabe9f1e93c5542da2d
This commit is contained in:
Song Minjae
2016-12-30 23:29:12 +09:00
parent d24b31e15d
commit 059abff814
329 changed files with 58400 additions and 7 deletions

View File

@@ -0,0 +1,268 @@
/*
* Copyright (c) 2002-2004 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 org.newdawn.slick.openal;
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 javax.sound.sampled.AudioFormat.Encoding;
import org.lwjgl.openal.AL10;
/**
*
* Utitlity class for loading wavefiles.
*
* @author Brian Matzon <brian@matzon.dk>
* @version $Revision: 2286 $
*/
public class AiffData {
/** actual AIFF data */
public final ByteBuffer data;
/** format type of data */
public final int format;
/** sample rate of data */
public final int samplerate;
/**
* Creates a new AiffData
*
* @param data actual Aiffdata
* @param format format of Aiff data
* @param samplerate sample rate of data
*/
private AiffData(ByteBuffer data, int format, int samplerate) {
this.data = data;
this.format = format;
this.samplerate = samplerate;
}
/**
* Disposes the Aiffdata
*/
public void dispose() {
data.clear();
}
/**
* Creates a AiffData container from the specified url
*
* @param path URL to file
* @return AiffData containing data, or null if a failure occured
*/
public static AiffData create(URL path) {
try {
return create(
AudioSystem.getAudioInputStream(
new BufferedInputStream(path.openStream())));
} catch (Exception e) {
org.lwjgl.LWJGLUtil.log("Unable to create from: " + path);
e.printStackTrace();
return null;
}
}
/**
* Creates a AiffData container from the specified in the classpath
*
* @param path path to file (relative, and in classpath)
* @return AiffData containing data, or null if a failure occured
*/
public static AiffData create(String path) {
return create(AiffData.class.getClassLoader().getResource(path));
}
/**
* Creates a AiffData container from the specified inputstream
*
* @param is InputStream to read from
* @return AiffData containing data, or null if a failure occured
*/
public static AiffData create(InputStream is) {
try {
return create(
AudioSystem.getAudioInputStream(is));
} catch (Exception e) {
org.lwjgl.LWJGLUtil.log("Unable to create from inputstream");
e.printStackTrace();
return null;
}
}
/**
* Creates a AiffData container from the specified bytes
*
* @param buffer array of bytes containing the complete Aiff file
* @return AiffData containing data, or null if a failure occured
*/
public static AiffData create(byte[] buffer) {
try {
return create(
AudioSystem.getAudioInputStream(
new BufferedInputStream(new ByteArrayInputStream(buffer))));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Creates a AiffData 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 AiffData containing data, or null if a failure occured
*/
public static AiffData 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) {
e.printStackTrace();
return null;
}
}
/**
* Creates a AiffData container from the specified stream
*
* @param ais AudioInputStream to read from
* @return AiffData containing data, or null if a failure occured
*/
public static AiffData 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 {
throw new RuntimeException("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 {
throw new RuntimeException("Illegal sample size");
}
} else {
throw new RuntimeException("Only mono or stereo is supported");
}
//read data into buffer
byte[] buf =
new byte[audioformat.getChannels()
* (int) ais.getFrameLength()
* audioformat.getSampleSizeInBits()
/ 8];
int read = 0, total = 0;
try {
while ((read = ais.read(buf, total, buf.length - total)) != -1
&& total < buf.length) {
total += read;
}
} catch (IOException ioe) {
return null;
}
//insert data into bytebuffer
ByteBuffer buffer = convertAudioBytes(audioformat, buf, audioformat.getSampleSizeInBits() == 16);
//create our result
AiffData Aiffdata =
new AiffData(buffer, channels, (int) audioformat.getSampleRate());
//close stream
try {
ais.close();
} catch (IOException ioe) {
}
return Aiffdata;
}
/**
* Convert the audio bytes into the stream
*
* @param format The audio format being decoded
* @param audio_bytes The audio byts
* @param two_bytes_data True if we using double byte data
* @return The byte bufer of data
*/
private static ByteBuffer convertAudioBytes(AudioFormat format, byte[] audio_bytes, boolean two_bytes_data) {
ByteBuffer dest = ByteBuffer.allocateDirect(audio_bytes.length);
dest.order(ByteOrder.nativeOrder());
ByteBuffer src = ByteBuffer.wrap(audio_bytes);
src.order(ByteOrder.BIG_ENDIAN);
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()) {
byte b = src.get();
if (format.getEncoding() == Encoding.PCM_SIGNED) {
b = (byte) (b + 127);
}
dest.put(b);
}
}
dest.rewind();
return dest;
}
}

View File

@@ -0,0 +1,79 @@
package org.newdawn.slick.openal;
/**
* The description of of audio data loaded by the AudioLoader
*
* @author kevin
* @author Nathan Sweet <misc@n4te.com>
*/
public interface Audio {
/**
* Stop the sound effect
*/
public void stop();
/**
* Get the ID of the OpenAL buffer holding this data (if any). This method
* is not valid with streaming resources.
*
* @return The ID of the OpenAL buffer holding this data
*/
public int getBufferID();
/**
* Check if the sound is playing as sound fx
*
* @return True if the sound is playing
*/
public boolean isPlaying();
/**
* Play this sound as a sound effect
*
* @param pitch The pitch of the play back
* @param gain The gain of the play back
* @param loop True if we should loop
* @return The ID of the source playing the sound
*/
public int playAsSoundEffect(float pitch, float gain, boolean loop);
/**
* Play this sound as a sound effect
*
* @param pitch The pitch of the play back
* @param gain The gain of the play back
* @param loop True if we should loop
* @param x The x position of the sound
* @param y The y position of the sound
* @param z The z position of the sound
* @return The ID of the source playing the sound
*/
public int playAsSoundEffect(float pitch, float gain, boolean loop,
float x, float y, float z);
/**
* Play this sound as music
*
* @param pitch The pitch of the play back
* @param gain The gain of the play back
* @param loop True if we should loop
* @return The ID of the source playing the sound
*/
public int playAsMusic(float pitch, float gain, boolean loop);
/**
* Seeks to a position in the music.
*
* @param position Position in seconds.
* @return True if the setting of the position was successful
*/
public boolean setPosition(float position);
/**
* Return the current playing position in the sound
*
* @return The current position in seconds.
*/
public float getPosition();
}

View File

@@ -0,0 +1,139 @@
package org.newdawn.slick.openal;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.AL11;
/**
* A sound that can be played through OpenAL
*
* @author Kevin Glass
* @author Nathan Sweet <misc@n4te.com>
*/
public class AudioImpl implements Audio {
/** The store from which this sound was loaded */
private SoundStore store;
/** The buffer containing the sound */
private int buffer;
/** The index of the source being used to play this sound */
private int index = -1;
/** The length of the audio */
private float length;
/**
* Create a new sound
*
* @param store The sound store from which the sound was created
* @param buffer The buffer containing the sound data
*/
AudioImpl(SoundStore store, int buffer) {
this.store = store;
this.buffer = buffer;
int bytes = AL10.alGetBufferi(buffer, AL10.AL_SIZE);
int bits = AL10.alGetBufferi(buffer, AL10.AL_BITS);
int channels = AL10.alGetBufferi(buffer, AL10.AL_CHANNELS);
int freq = AL10.alGetBufferi(buffer, AL10.AL_FREQUENCY);
int samples = bytes / (bits / 8);
length = (samples / (float) freq) / channels;
}
/**
* Get the ID of the OpenAL buffer holding this data (if any). This method
* is not valid with streaming resources.
*
* @return The ID of the OpenAL buffer holding this data
*/
public int getBufferID() {
return buffer;
}
/**
*
*/
protected AudioImpl() {
}
/**
* @see org.newdawn.slick.openal.Audio#stop()
*/
public void stop() {
if (index != -1) {
store.stopSource(index);
index = -1;
}
}
/**
* @see org.newdawn.slick.openal.Audio#isPlaying()
*/
public boolean isPlaying() {
if (index != -1) {
return store.isPlaying(index);
}
return false;
}
/**
* @see org.newdawn.slick.openal.Audio#playAsSoundEffect(float, float, boolean)
*/
public int playAsSoundEffect(float pitch, float gain, boolean loop) {
index = store.playAsSound(buffer, pitch, gain, loop);
return store.getSource(index);
}
/**
* @see org.newdawn.slick.openal.Audio#playAsSoundEffect(float, float, boolean, float, float, float)
*/
public int playAsSoundEffect(float pitch, float gain, boolean loop, float x, float y, float z) {
index = store.playAsSoundAt(buffer, pitch, gain, loop, x, y, z);
return store.getSource(index);
}
/**
* @see org.newdawn.slick.openal.Audio#playAsMusic(float, float, boolean)
*/
public int playAsMusic(float pitch, float gain, boolean loop) {
store.playAsMusic(buffer, pitch, gain, loop);
index = 0;
return store.getSource(0);
}
/**
* Pause the music currently being played
*/
public static void pauseMusic() {
SoundStore.get().pauseLoop();
}
/**
* Restart the music currently being paused
*/
public static void restartMusic() {
SoundStore.get().restartLoop();
}
/**
* @see org.newdawn.slick.openal.Audio#setPosition(float)
*/
public boolean setPosition(float position) {
position = position % length;
AL10.alSourcef(store.getSource(index), AL11.AL_SEC_OFFSET, position);
if (AL10.alGetError() != 0) {
return false;
}
return true;
}
/**
* @see org.newdawn.slick.openal.Audio#getPosition()
*/
public float getPosition() {
return AL10.alGetSourcef(store.getSource(index), AL11.AL_SEC_OFFSET);
}
}

View File

@@ -0,0 +1,71 @@
package org.newdawn.slick.openal;
import java.io.IOException;
/**
* The description of an input stream that supplied audio data suitable for
* use in OpenAL buffers
*
* @author kevin
*/
interface AudioInputStream {
/**
* Get the number of channels used by the audio
*
* @return The number of channels used by the audio
*/
public int getChannels();
/**
* The play back rate described in the underling audio file
*
* @return The playback rate
*/
public int getRate();
/**
* Read a single byte from the stream
*
* @return The single byte read
* @throws IOException Indicates a failure to read the underlying media
* @see java.io.InputStream#read()
*/
public int read() throws IOException;
/**
* Read up to data.length bytes from the stream
*
* @param data The array to read into
* @return The number of bytes read or -1 to indicate no more bytes are available
* @throws IOException Indicates a failure to read the underlying media
* @see java.io.InputStream#read(byte[])
*/
public int read(byte[] data) throws IOException;
/**
* Read up to len bytes from the stream
*
* @param data The array to read into
* @param ofs The offset into the array at which to start writing
* @param len The maximum number of bytes to read
* @return The number of bytes read or -1 to indicate no more bytes are available
* @throws IOException Indicates a failure to read the underlying media
* @see java.io.InputStream#read(byte[], int, int)
*/
public int read(byte[] data, int ofs, int len) throws IOException;
/**
* Check if the stream is at the end, i.e. end of file or URL
*
* @return True if the stream has no more data available
*/
public boolean atEnd();
/**
* Close the stream
*
* @see java.io.InputStream#close()
* @throws IOException Indicates a failure to access the resource
*/
public void close() throws IOException;
}

View File

@@ -0,0 +1,96 @@
package org.newdawn.slick.openal;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
/**
* A utility to provide a simple and rational interface to the
* slick internals
*
* @author kevin
*/
public class AudioLoader {
/** AIF Format Indicator */
private static final String AIF = "AIF";
/** WAV Format Indicator */
private static final String WAV = "WAV";
/** OGG Format Indicator */
private static final String OGG = "OGG";
/** MOD/XM Format Indicator */
private static final String MOD = "MOD";
/** MOD/XM Format Indicator */
private static final String XM = "XM";
/** True if the audio loader has be initialised */
private static boolean inited = false;
/**
* Initialise the audio loader
*/
private static void init() {
if (!inited) {
SoundStore.get().init();
inited = true;
}
}
/**
* Get audio data in a playable state by loading the complete audio into
* memory.
*
* @param format The format of the audio to be loaded (something like "XM" or "OGG")
* @param in The input stream from which to load the audio data
* @return An object representing the audio data
* @throws IOException Indicates a failure to access the audio data
*/
public static Audio getAudio(String format, InputStream in) throws IOException {
init();
if (format.equals(AIF)) {
return SoundStore.get().getAIF(in);
}
if (format.equals(WAV)) {
return SoundStore.get().getWAV(in);
}
if (format.equals(OGG)) {
return SoundStore.get().getOgg(in);
}
throw new IOException("Unsupported format for non-streaming Audio: "+format);
}
/**
* Get audio data in a playable state by setting up a stream that can be piped into
* OpenAL - i.e. streaming audio
*
* @param format The format of the audio to be loaded (something like "XM" or "OGG")
* @param url The location of the data that should be streamed
* @return An object representing the audio data
* @throws IOException Indicates a failure to access the audio data
*/
public static Audio getStreamingAudio(String format, URL url) throws IOException {
init();
if (format.equals(OGG)) {
return SoundStore.get().getOggStream(url);
}
if (format.equals(MOD)) {
return SoundStore.get().getMOD(url.openStream());
}
if (format.equals(XM)) {
return SoundStore.get().getMOD(url.openStream());
}
throw new IOException("Unsupported format for streaming Audio: "+format);
}
/**
* Allow the streaming system to update itself
*/
public static void update() {
init();
SoundStore.get().poll(0);
}
}

View File

@@ -0,0 +1,164 @@
package org.newdawn.slick.openal;
import java.io.IOException;
import java.io.InputStream;
import org.newdawn.slick.loading.DeferredResource;
import org.newdawn.slick.loading.LoadingList;
import org.newdawn.slick.util.Log;
/**
* A sound implementation that can load the actual sound file at a later
* point.
*
* @author kevin
*/
public class DeferredSound extends AudioImpl implements DeferredResource {
/** Indicate a OGG to be loaded */
public static final int OGG = 1;
/** Indicate a WAV to be loaded */
public static final int WAV = 2;
/** Indicate a MOD/XM to be loaded */
public static final int MOD = 3;
/** Indicate a AIF to be loaded */
public static final int AIF = 4;
/** The type of sound to be loader */
private int type;
/** The location of the sound this proxy wraps */
private String ref;
/** The loaded sound if it's already been brought up */
private Audio target;
/** The input stream to load the sound this proxy wraps from (can be null) */
private InputStream in;
/**
* Create a new sound on request to load
*
* @param ref The location of the sound to load
* @param type The type of sound to load
* @param in The input stream to load from
*/
public DeferredSound(String ref, InputStream in, int type) {
this.ref = ref;
this.type = type;
// nasty hack to detect when we're loading from a stream
if (ref.equals(in.toString())) {
this.in = in;
}
LoadingList.get().add(this);
}
/**
* Check if the target has already been loaded
*/
private void checkTarget() {
if (target == null) {
throw new RuntimeException("Attempt to use deferred sound before loading");
}
}
/**
* @see org.newdawn.slick.loading.DeferredResource#load()
*/
public void load() throws IOException {
boolean before = SoundStore.get().isDeferredLoading();
SoundStore.get().setDeferredLoading(false);
if (in != null) {
switch (type) {
case OGG:
target = SoundStore.get().getOgg(in);
break;
case WAV:
target = SoundStore.get().getWAV(in);
break;
case MOD:
target = SoundStore.get().getMOD(in);
break;
case AIF:
target = SoundStore.get().getAIF(in);
break;
default:
Log.error("Unrecognised sound type: "+type);
break;
}
} else {
switch (type) {
case OGG:
target = SoundStore.get().getOgg(ref);
break;
case WAV:
target = SoundStore.get().getWAV(ref);
break;
case MOD:
target = SoundStore.get().getMOD(ref);
break;
case AIF:
target = SoundStore.get().getAIF(ref);
break;
default:
Log.error("Unrecognised sound type: "+type);
break;
}
}
SoundStore.get().setDeferredLoading(before);
}
/**
* @see org.newdawn.slick.openal.AudioImpl#isPlaying()
*/
public boolean isPlaying() {
checkTarget();
return target.isPlaying();
}
/**
* @see org.newdawn.slick.openal.AudioImpl#playAsMusic(float, float, boolean)
*/
public int playAsMusic(float pitch, float gain, boolean loop) {
checkTarget();
return target.playAsMusic(pitch, gain, loop);
}
/**
* @see org.newdawn.slick.openal.AudioImpl#playAsSoundEffect(float, float, boolean)
*/
public int playAsSoundEffect(float pitch, float gain, boolean loop) {
checkTarget();
return target.playAsSoundEffect(pitch, gain, loop);
}
/**
* Play this sound as a sound effect
*
* @param pitch The pitch of the play back
* @param gain The gain of the play back
* @param loop True if we should loop
* @param x The x position of the sound
* @param y The y position of the sound
* @param z The z position of the sound
*/
public int playAsSoundEffect(float pitch, float gain, boolean loop, float x, float y, float z) {
checkTarget();
return target.playAsSoundEffect(pitch, gain, loop, x, y, z);
}
/**
* @see org.newdawn.slick.openal.AudioImpl#stop()
*/
public void stop() {
checkTarget();
target.stop();
}
/**
* @see org.newdawn.slick.loading.DeferredResource#getDescription()
*/
public String getDescription() {
return ref;
}
}

View File

@@ -0,0 +1,105 @@
package org.newdawn.slick.openal;
import ibxm.Module;
import ibxm.OpenALMODPlayer;
import java.io.IOException;
import java.io.InputStream;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL10;
/**
* A sound as a MOD file - can only be played as music
*
* @author Kevin Glass
*/
public class MODSound extends AudioImpl {
/** The MOD play back system */
private static OpenALMODPlayer player = new OpenALMODPlayer();
/** The module to play back */
private Module module;
/** The sound store this belongs to */
private SoundStore store;
/**
* Create a mod sound to be played back
*
* @param store The store this sound belongs to
* @param in The input stream to read the data from
* @throws IOException Indicates a failure to load a sound
*/
public MODSound(SoundStore store, InputStream in) throws IOException {
this.store = store;
module = OpenALMODPlayer.loadModule(in);
}
/**
* @see org.newdawn.slick.openal.AudioImpl#playAsMusic(float, float, boolean)
*/
public int playAsMusic(float pitch, float gain, boolean loop) {
cleanUpSource();
player.play(module, store.getSource(0), loop, SoundStore.get().isMusicOn());
player.setup(pitch, 1.0f);
store.setCurrentMusicVolume(gain);
store.setMOD(this);
return store.getSource(0);
}
/**
* Clean up the buffers applied to the sound source
*/
private void cleanUpSource() {
AL10.alSourceStop(store.getSource(0));
IntBuffer buffer = BufferUtils.createIntBuffer(1);
int queued = AL10.alGetSourcei(store.getSource(0), AL10.AL_BUFFERS_QUEUED);
while (queued > 0)
{
AL10.alSourceUnqueueBuffers(store.getSource(0), buffer);
queued--;
}
AL10.alSourcei(store.getSource(0), AL10.AL_BUFFER, 0);
}
/**
* Poll the streaming on the MOD
*/
public void poll() {
player.update();
}
/**
* @see org.newdawn.slick.openal.AudioImpl#playAsSoundEffect(float, float, boolean)
*/
public int playAsSoundEffect(float pitch, float gain, boolean loop) {
return -1;
}
/**
* @see org.newdawn.slick.openal.AudioImpl#stop()
*/
public void stop() {
store.setMOD(null);
}
/**
* @see org.newdawn.slick.openal.AudioImpl#getPosition()
*/
public float getPosition() {
throw new RuntimeException("Positioning on modules is not currently supported");
}
/**
* @see org.newdawn.slick.openal.AudioImpl#setPosition(float)
*/
public boolean setPosition(float position) {
throw new RuntimeException("Positioning on modules is not currently supported");
}
}

View File

@@ -0,0 +1,66 @@
package org.newdawn.slick.openal;
/**
* A null implementation used to provide an object reference when sound
* has failed.
*
* @author kevin
*/
public class NullAudio implements Audio {
/**
* @see org.newdawn.slick.openal.Audio#getBufferID()
*/
public int getBufferID() {
return 0;
}
/**
* @see org.newdawn.slick.openal.Audio#getPosition()
*/
public float getPosition() {
return 0;
}
/**
* @see org.newdawn.slick.openal.Audio#isPlaying()
*/
public boolean isPlaying() {
return false;
}
/**
* @see org.newdawn.slick.openal.Audio#playAsMusic(float, float, boolean)
*/
public int playAsMusic(float pitch, float gain, boolean loop) {
return 0;
}
/**
* @see org.newdawn.slick.openal.Audio#playAsSoundEffect(float, float, boolean)
*/
public int playAsSoundEffect(float pitch, float gain, boolean loop) {
return 0;
}
/**
* @see org.newdawn.slick.openal.Audio#playAsSoundEffect(float, float, boolean, float, float, float)
*/
public int playAsSoundEffect(float pitch, float gain, boolean loop,
float x, float y, float z) {
return 0;
}
/**
* @see org.newdawn.slick.openal.Audio#setPosition(float)
*/
public boolean setPosition(float position) {
return false;
}
/**
* @see org.newdawn.slick.openal.Audio#stop()
*/
public void stop() {
}
}

View File

@@ -0,0 +1,17 @@
package org.newdawn.slick.openal;
import java.nio.ByteBuffer;
/**
* Data describing the sounds in a OGG file
*
* @author Kevin Glass
*/
public class OggData {
/** The data that has been read from the OGG file */
public ByteBuffer data;
/** The sampling rate */
public int rate;
/** The number of channels in the sound file */
public int channels;
}

View File

@@ -0,0 +1,329 @@
package org.newdawn.slick.openal;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* Decode an OGG file to PCM data
*
* @author Kevin Glass
*/
public class OggDecoder {
/** The conversion buffer size */
private int convsize = 4096 * 4;
/** The buffer used to read OGG file */
private byte[] convbuffer = new byte[convsize]; // take 8k out of the data segment, not the stack
/**
* Create a new OGG decoder
*/
public OggDecoder() {
}
/**
* Get the data out of an OGG file
*
* @param input The input stream from which to read the OGG file
* @return The data describing the OGG thats been read
* @throws IOException Indicaites a failure to read the OGG file
*/
public OggData getData(InputStream input) throws IOException {
if (input == null) {
throw new IOException("Failed to read OGG, source does not exist?");
}
ByteArrayOutputStream dataout = new ByteArrayOutputStream();
// SyncState oy = new SyncState(); // sync and verify incoming physical bitstream
// StreamState os = new StreamState(); // take physical pages, weld into a logical stream of packets
// Page og = new Page(); // one Ogg bitstream page. Vorbis packets are inside
// Packet op = new Packet(); // one raw packet of data for decode
//
// Info vi = new Info(); // struct that stores all the static vorbis bitstream settings
// Comment vc = new Comment(); // struct that stores all the bitstream user comments
// DspState vd = new DspState(); // central working state for the packet->PCM decoder
// Block vb = new Block(vd); // local working space for packet->PCM decode
//
// byte[] buffer;
// int bytes = 0;
//
// boolean bigEndian = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN);
// // Decode setup
//
// oy.init(); // Now we can read pages
//
// while (true) { // we repeat if the bitstream is chained
// int eos = 0;
//
// // grab some data at the head of the stream. We want the first page
// // (which is guaranteed to be small and only contain the Vorbis
// // stream initial header) We need the first page to get the stream
// // serialno.
//
// // submit a 4k block to libvorbis' Ogg layer
// int index = oy.buffer(4096);
//
// buffer = oy.data;
// try {
// bytes = input.read(buffer, index, 4096);
// } catch (Exception e) {
// Log.error("Failure reading in vorbis");
// Log.error(e);
// System.exit(0);
// }
// oy.wrote(bytes);
//
// // Get the first page.
// if (oy.pageout(og) != 1) {
// // have we simply run out of data? If so, we're done.
// if (bytes < 4096)
// break;
//
// // error case. Must not be Vorbis data
// Log.error("Input does not appear to be an Ogg bitstream.");
// System.exit(0);
// }
//
// // Get the serial number and set up the rest of decode.
// // serialno first; use it to set up a logical stream
// os.init(og.serialno());
//
// // extract the initial header from the first page and verify that the
// // Ogg bitstream is in fact Vorbis data
//
// // I handle the initial header first instead of just having the code
// // read all three Vorbis headers at once because reading the initial
// // header is an easy way to identify a Vorbis bitstream and it's
// // useful to see that functionality seperated out.
//
// vi.init();
// vc.init();
// if (os.pagein(og) < 0) {
// // error; stream version mismatch perhaps
// Log.error("Error reading first page of Ogg bitstream data.");
// System.exit(0);
// }
//
// if (os.packetout(op) != 1) {
// // no page? must not be vorbis
// Log.error("Error reading initial header packet.");
// System.exit(0);
// }
//
// if (vi.synthesis_headerin(vc, op) < 0) {
// // error case; not a vorbis header
// Log.error("This Ogg bitstream does not contain Vorbis audio data.");
// System.exit(0);
// }
//
// // At this point, we're sure we're Vorbis. We've set up the logical
// // (Ogg) bitstream decoder. Get the comment and codebook headers and
// // set up the Vorbis decoder
//
// // The next two packets in order are the comment and codebook headers.
// // They're likely large and may span multiple pages. Thus we reead
// // and submit data until we get our two pacakets, watching that no
// // pages are missing. If a page is missing, error out; losing a
// // header page is the only place where missing data is fatal. */
//
// int i = 0;
// while (i < 2) {
// while (i < 2) {
//
// int result = oy.pageout(og);
// if (result == 0)
// break; // Need more data
// // Don't complain about missing or corrupt data yet. We'll
// // catch it at the packet output phase
//
// if (result == 1) {
// os.pagein(og); // we can ignore any errors here
// // as they'll also become apparent
// // at packetout
// while (i < 2) {
// result = os.packetout(op);
// if (result == 0)
// break;
// if (result == -1) {
// // Uh oh; data at some point was corrupted or missing!
// // We can't tolerate that in a header. Die.
// Log.error("Corrupt secondary header. Exiting.");
// System.exit(0);
// }
// vi.synthesis_headerin(vc, op);
// i++;
// }
// }
// }
// // no harm in not checking before adding more
// index = oy.buffer(4096);
// buffer = oy.data;
// try {
// bytes = input.read(buffer, index, 4096);
// } catch (Exception e) {
// Log.error("Failed to read Vorbis: ");
// Log.error(e);
// System.exit(0);
// }
// if (bytes == 0 && i < 2) {
// Log.error("End of file before finding all Vorbis headers!");
// System.exit(0);
// }
// oy.wrote(bytes);
// }
//
// convsize = 4096 / vi.channels;
//
// // OK, got and parsed all three headers. Initialize the Vorbis
// // packet->PCM decoder.
// vd.synthesis_init(vi); // central decode state
// vb.init(vd); // local state for most of the decode
// // so multiple block decodes can
// // proceed in parallel. We could init
// // multiple vorbis_block structures
// // for vd here
//
// float[][][] _pcm = new float[1][][];
// int[] _index = new int[vi.channels];
// // The rest is just a straight decode loop until end of stream
// while (eos == 0) {
// while (eos == 0) {
//
// int result = oy.pageout(og);
// if (result == 0)
// break; // need more data
// if (result == -1) { // missing or corrupt data at this page position
// Log.error("Corrupt or missing data in bitstream; continuing...");
// } else {
// os.pagein(og); // can safely ignore errors at
// // this point
// while (true) {
// result = os.packetout(op);
//
// if (result == 0)
// break; // need more data
// if (result == -1) { // missing or corrupt data at this page position
// // no reason to complain; already complained above
// } else {
// // we have a packet. Decode it
// int samples;
// if (vb.synthesis(op) == 0) { // test for success!
// vd.synthesis_blockin(vb);
// }
//
// // **pcm is a multichannel float vector. In stereo, for
// // example, pcm[0] is left, and pcm[1] is right. samples is
// // the size of each channel. Convert the float values
// // (-1.<=range<=1.) to whatever PCM format and write it out
//
// while ((samples = vd.synthesis_pcmout(_pcm,
// _index)) > 0) {
// float[][] pcm = _pcm[0];
// //boolean clipflag = false;
// int bout = (samples < convsize ? samples
// : convsize);
//
// // convert floats to 16 bit signed ints (host order) and
// // interleave
// for (i = 0; i < vi.channels; i++) {
// int ptr = i * 2;
// //int ptr=i;
// int mono = _index[i];
// for (int j = 0; j < bout; j++) {
// int val = (int) (pcm[i][mono + j] * 32767.);
// // short val=(short)(pcm[i][mono+j]*32767.);
// // int val=(int)Math.round(pcm[i][mono+j]*32767.);
// // might as well guard against clipping
// if (val > 32767) {
// val = 32767;
// //clipflag = true;
// }
// if (val < -32768) {
// val = -32768;
// //clipflag = true;
// }
// if (val < 0)
// val = val | 0x8000;
//
// if (bigEndian) {
// convbuffer[ptr] = (byte) (val >>> 8);
// convbuffer[ptr + 1] = (byte) (val);
// } else {
// convbuffer[ptr] = (byte) (val);
// convbuffer[ptr + 1] = (byte) (val >>> 8);
// }
// ptr += 2 * (vi.channels);
// }
// }
//
// dataout.write(convbuffer, 0, 2 * vi.channels * bout);
//
// vd.synthesis_read(bout); // tell libvorbis how
// // many samples we
// // actually consumed
// }
// }
// }
// if (og.eos() != 0)
// eos = 1;
// }
// }
// if (eos == 0) {
// index = oy.buffer(4096);
// if (index >= 0) {
// buffer = oy.data;
// try {
// bytes = input.read(buffer, index, 4096);
// } catch (Exception e) {
// Log.error("Failure during vorbis decoding");
// Log.error(e);
// return null;
// }
// } else {
// bytes = 0;
// }
// oy.wrote(bytes);
// if (bytes == 0)
// eos = 1;
// }
// }
//
// // clean up this logical bitstream; before exit we see if we're
// // followed by another [chained]
//
// os.clear();
//
// // ogg_page and ogg_packet structs always point to storage in
// // libvorbis. They're never freed or manipulated directly
//
// vb.clear();
// vd.clear();
// vi.clear(); // must be called last
// }
//
// // OK, clean up the framer
// oy.clear();
// OggData ogg = new OggData();
// ogg.channels = vi.channels;
// ogg.rate = vi.rate;
OggInputStream oggInput = new OggInputStream(input);
boolean done = false;
while (!oggInput.atEnd()) {
dataout.write(oggInput.read());
}
OggData ogg = new OggData();
ogg.channels = oggInput.getChannels();
ogg.rate = oggInput.getRate();
byte[] data = dataout.toByteArray();
ogg.data = ByteBuffer.allocateDirect(data.length);
ogg.data.put(data);
ogg.data.rewind();
return ogg;
}
}

View File

@@ -0,0 +1,506 @@
package org.newdawn.slick.openal;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.lwjgl.BufferUtils;
import org.newdawn.slick.util.Log;
import com.jcraft.jogg.Packet;
import com.jcraft.jogg.Page;
import com.jcraft.jogg.StreamState;
import com.jcraft.jogg.SyncState;
import com.jcraft.jorbis.Block;
import com.jcraft.jorbis.Comment;
import com.jcraft.jorbis.DspState;
import com.jcraft.jorbis.Info;
/**
* An input stream that can extract ogg data. This class is a bit of an experiment with continuations
* so uses thread where possibly not required. It's just a test to see if continuations make sense in
* some cases.
*
* @author kevin
*/
public class OggInputStream extends InputStream implements AudioInputStream {
/** The conversion buffer size */
private int convsize = 4096 * 4;
/** The buffer used to read OGG file */
private byte[] convbuffer = new byte[convsize]; // take 8k out of the data segment, not the stack
/** The stream we're reading the OGG file from */
private InputStream input;
/** The audio information from the OGG header */
private Info oggInfo = new Info(); // struct that stores all the static vorbis bitstream settings
/** True if we're at the end of the available data */
private boolean endOfStream;
/** The Vorbis SyncState used to decode the OGG */
private SyncState syncState = new SyncState(); // sync and verify incoming physical bitstream
/** The Vorbis Stream State used to decode the OGG */
private StreamState streamState = new StreamState(); // take physical pages, weld into a logical stream of packets
/** The current OGG page */
private Page page = new Page(); // one Ogg bitstream page. Vorbis packets are inside
/** The current packet page */
private Packet packet = new Packet(); // one raw packet of data for decode
/** The comment read from the OGG file */
private Comment comment = new Comment(); // struct that stores all the bitstream user comments
/** The Vorbis DSP stat eused to decode the OGG */
private DspState dspState = new DspState(); // central working state for the packet->PCM decoder
/** The OGG block we're currently working with to convert PCM */
private Block vorbisBlock = new Block(dspState); // local working space for packet->PCM decode
/** Temporary scratch buffer */
byte[] buffer;
/** The number of bytes read */
int bytes = 0;
/** The true if we should be reading big endian */
boolean bigEndian = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN);
/** True if we're reached the end of the current bit stream */
boolean endOfBitStream = true;
/** True if we're initialise the OGG info block */
boolean inited = false;
/** The index into the byte array we currently read from */
private int readIndex;
/** The byte array store used to hold the data read from the ogg */
private ByteBuffer pcmBuffer = BufferUtils.createByteBuffer(4096 * 500);
/** The total number of bytes */
private int total;
/**
* Create a new stream to decode OGG data
*
* @param input The input stream from which to read the OGG file
* @throws IOException Indicates a failure to read from the supplied stream
*/
public OggInputStream(InputStream input) throws IOException {
this.input = input;
total = input.available();
init();
}
/**
* Get the number of bytes on the stream
*
* @return The number of the bytes on the stream
*/
public int getLength() {
return total;
}
/**
* @see org.newdawn.slick.openal.AudioInputStream#getChannels()
*/
public int getChannels() {
return oggInfo.channels;
}
/**
* @see org.newdawn.slick.openal.AudioInputStream#getRate()
*/
public int getRate() {
return oggInfo.rate;
}
/**
* Initialise the streams and thread involved in the streaming of OGG data
*
* @throws IOException Indicates a failure to link up the streams
*/
private void init() throws IOException {
initVorbis();
readPCM();
}
/**
* @see java.io.InputStream#available()
*/
public int available() {
return endOfStream ? 0 : 1;
}
/**
* Initialise the vorbis decoding
*/
private void initVorbis() {
syncState.init();
}
/**
* Get a page and packet from that page
*
* @return True if there was a page available
*/
private boolean getPageAndPacket() {
// grab some data at the head of the stream. We want the first page
// (which is guaranteed to be small and only contain the Vorbis
// stream initial header) We need the first page to get the stream
// serialno.
// submit a 4k block to libvorbis' Ogg layer
int index = syncState.buffer(4096);
buffer = syncState.data;
if (buffer == null) {
endOfStream = true;
return false;
}
try {
bytes = input.read(buffer, index, 4096);
} catch (Exception e) {
Log.error("Failure reading in vorbis");
Log.error(e);
endOfStream = true;
return false;
}
syncState.wrote(bytes);
// Get the first page.
if (syncState.pageout(page) != 1) {
// have we simply run out of data? If so, we're done.
if (bytes < 4096)
return false;
// error case. Must not be Vorbis data
Log.error("Input does not appear to be an Ogg bitstream.");
endOfStream = true;
return false;
}
// Get the serial number and set up the rest of decode.
// serialno first; use it to set up a logical stream
streamState.init(page.serialno());
// extract the initial header from the first page and verify that the
// Ogg bitstream is in fact Vorbis data
// I handle the initial header first instead of just having the code
// read all three Vorbis headers at once because reading the initial
// header is an easy way to identify a Vorbis bitstream and it's
// useful to see that functionality seperated out.
oggInfo.init();
comment.init();
if (streamState.pagein(page) < 0) {
// error; stream version mismatch perhaps
Log.error("Error reading first page of Ogg bitstream data.");
endOfStream = true;
return false;
}
if (streamState.packetout(packet) != 1) {
// no page? must not be vorbis
Log.error("Error reading initial header packet.");
endOfStream = true;
return false;
}
if (oggInfo.synthesis_headerin(comment, packet) < 0) {
// error case; not a vorbis header
Log.error("This Ogg bitstream does not contain Vorbis audio data.");
endOfStream = true;
return false;
}
// At this point, we're sure we're Vorbis. We've set up the logical
// (Ogg) bitstream decoder. Get the comment and codebook headers and
// set up the Vorbis decoder
// The next two packets in order are the comment and codebook headers.
// They're likely large and may span multiple pages. Thus we reead
// and submit data until we get our two pacakets, watching that no
// pages are missing. If a page is missing, error out; losing a
// header page is the only place where missing data is fatal. */
int i = 0;
while (i < 2) {
while (i < 2) {
int result = syncState.pageout(page);
if (result == 0)
break; // Need more data
// Don't complain about missing or corrupt data yet. We'll
// catch it at the packet output phase
if (result == 1) {
streamState.pagein(page); // we can ignore any errors here
// as they'll also become apparent
// at packetout
while (i < 2) {
result = streamState.packetout(packet);
if (result == 0)
break;
if (result == -1) {
// Uh oh; data at some point was corrupted or missing!
// We can't tolerate that in a header. Die.
Log.error("Corrupt secondary header. Exiting.");
endOfStream = true;
return false;
}
oggInfo.synthesis_headerin(comment, packet);
i++;
}
}
}
// no harm in not checking before adding more
index = syncState.buffer(4096);
buffer = syncState.data;
try {
bytes = input.read(buffer, index, 4096);
} catch (Exception e) {
Log.error("Failed to read Vorbis: ");
Log.error(e);
endOfStream = true;
return false;
}
if (bytes == 0 && i < 2) {
Log.error("End of file before finding all Vorbis headers!");
endOfStream = true;
return false;
}
syncState.wrote(bytes);
}
convsize = 4096 / oggInfo.channels;
// OK, got and parsed all three headers. Initialize the Vorbis
// packet->PCM decoder.
dspState.synthesis_init(oggInfo); // central decode state
vorbisBlock.init(dspState); // local state for most of the decode
// so multiple block decodes can
// proceed in parallel. We could init
// multiple vorbis_block structures
// for vd here
return true;
}
/**
* Decode the OGG file as shown in the jogg/jorbis examples
*
* @throws IOException Indicates a failure to read from the supplied stream
*/
private void readPCM() throws IOException {
boolean wrote = false;
while (true) { // we repeat if the bitstream is chained
if (endOfBitStream) {
if (!getPageAndPacket()) {
break;
}
endOfBitStream = false;
}
if (!inited) {
inited = true;
return;
}
float[][][] _pcm = new float[1][][];
int[] _index = new int[oggInfo.channels];
// The rest is just a straight decode loop until end of stream
while (!endOfBitStream) {
while (!endOfBitStream) {
int result = syncState.pageout(page);
if (result == 0) {
break; // need more data
}
if (result == -1) { // missing or corrupt data at this page position
Log.error("Corrupt or missing data in bitstream; continuing...");
} else {
streamState.pagein(page); // can safely ignore errors at
// this point
while (true) {
result = streamState.packetout(packet);
if (result == 0)
break; // need more data
if (result == -1) { // missing or corrupt data at this page position
// no reason to complain; already complained above
} else {
// we have a packet. Decode it
int samples;
if (vorbisBlock.synthesis(packet) == 0) { // test for success!
dspState.synthesis_blockin(vorbisBlock);
}
// **pcm is a multichannel float vector. In stereo, for
// example, pcm[0] is left, and pcm[1] is right. samples is
// the size of each channel. Convert the float values
// (-1.<=range<=1.) to whatever PCM format and write it out
while ((samples = dspState.synthesis_pcmout(_pcm,
_index)) > 0) {
float[][] pcm = _pcm[0];
//boolean clipflag = false;
int bout = (samples < convsize ? samples
: convsize);
// convert floats to 16 bit signed ints (host order) and
// interleave
for (int i = 0; i < oggInfo.channels; i++) {
int ptr = i * 2;
//int ptr=i;
int mono = _index[i];
for (int j = 0; j < bout; j++) {
int val = (int) (pcm[i][mono + j] * 32767.);
// might as well guard against clipping
if (val > 32767) {
val = 32767;
}
if (val < -32768) {
val = -32768;
}
if (val < 0)
val = val | 0x8000;
if (bigEndian) {
convbuffer[ptr] = (byte) (val >>> 8);
convbuffer[ptr + 1] = (byte) (val);
} else {
convbuffer[ptr] = (byte) (val);
convbuffer[ptr + 1] = (byte) (val >>> 8);
}
ptr += 2 * (oggInfo.channels);
}
}
int bytesToWrite = 2 * oggInfo.channels * bout;
if (bytesToWrite >= pcmBuffer.remaining()) {
Log.warn("Read block from OGG that was too big to be buffered: " + bytesToWrite);
} else {
pcmBuffer.put(convbuffer, 0, bytesToWrite);
}
wrote = true;
dspState.synthesis_read(bout); // tell libvorbis how
// many samples we
// actually consumed
}
}
}
if (page.eos() != 0) {
endOfBitStream = true;
}
if ((!endOfBitStream) && (wrote)) {
return;
}
}
}
if (!endOfBitStream) {
bytes = 0;
int index = syncState.buffer(4096);
if (index >= 0) {
buffer = syncState.data;
try {
bytes = input.read(buffer, index, 4096);
} catch (Exception e) {
Log.error("Failure during vorbis decoding");
Log.error(e);
endOfStream = true;
return;
}
} else {
bytes = 0;
}
syncState.wrote(bytes);
if (bytes == 0) {
endOfBitStream = true;
}
}
}
// clean up this logical bitstream; before exit we see if we're
// followed by another [chained]
streamState.clear();
// ogg_page and ogg_packet structs always point to storage in
// libvorbis. They're never freed or manipulated directly
vorbisBlock.clear();
dspState.clear();
oggInfo.clear(); // must be called last
}
// OK, clean up the framer
syncState.clear();
endOfStream = true;
}
/**
* @see java.io.InputStream#read()
*/
public int read() throws IOException {
if (readIndex >= pcmBuffer.position()) {
pcmBuffer.clear();
readPCM();
readIndex = 0;
}
if (readIndex >= pcmBuffer.position()) {
return -1;
}
int value = pcmBuffer.get(readIndex);
if (value < 0) {
value = 256 + value;
}
readIndex++;
return value;
}
/**
* @see org.newdawn.slick.openal.AudioInputStream#atEnd()
*/
public boolean atEnd() {
return endOfStream && (readIndex >= pcmBuffer.position());
}
/**
* @see java.io.InputStream#read(byte[], int, int)
*/
public int read(byte[] b, int off, int len) throws IOException {
for (int i=0;i<len;i++) {
try {
int value = read();
if (value >= 0) {
b[i] = (byte) value;
} else {
if (i == 0) {
return -1;
} else {
return i;
}
}
} catch (IOException e) {
Log.error(e);
return i;
}
}
return len;
}
/**
* @see java.io.InputStream#read(byte[])
*/
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
/**
* @see java.io.InputStream#close()
*/
public void close() throws IOException {
}
}

View File

@@ -0,0 +1,322 @@
package org.newdawn.slick.openal;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.AL11;
import org.lwjgl.openal.OpenALException;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
/**
* A generic tool to work on a supplied stream, pulling out PCM data and buffered it to OpenAL
* as required.
*
* @author Kevin Glass
* @author Nathan Sweet <misc@n4te.com>
* @author Rockstar play and setPosition cleanup
*/
public class OpenALStreamPlayer {
/** The number of buffers to maintain */
public static final int BUFFER_COUNT = 3;
/** The size of the sections to stream from the stream */
private static final int sectionSize = 4096 * 20;
/** The buffer read from the data stream */
private byte[] buffer = new byte[sectionSize];
/** Holds the OpenAL buffer names */
private IntBuffer bufferNames;
/** The byte buffer passed to OpenAL containing the section */
private ByteBuffer bufferData = BufferUtils.createByteBuffer(sectionSize);
/** The buffer holding the names of the OpenAL buffer thats been fully played back */
private IntBuffer unqueued = BufferUtils.createIntBuffer(1);
/** The source we're playing back on */
private int source;
/** The number of buffers remaining */
private int remainingBufferCount;
/** True if we should loop the track */
private boolean loop;
/** True if we've completed play back */
private boolean done = true;
/** The stream we're currently reading from */
private AudioInputStream audio;
/** The source of the data */
private String ref;
/** The source of the data */
private URL url;
/** The pitch of the music */
private float pitch;
/** Position in seconds of the previously played buffers */
private float positionOffset;
/**
* Create a new player to work on an audio stream
*
* @param source The source on which we'll play the audio
* @param ref A reference to the audio file to stream
*/
public OpenALStreamPlayer(int source, String ref) {
this.source = source;
this.ref = ref;
bufferNames = BufferUtils.createIntBuffer(BUFFER_COUNT);
AL10.alGenBuffers(bufferNames);
}
/**
* Create a new player to work on an audio stream
*
* @param source The source on which we'll play the audio
* @param url A reference to the audio file to stream
*/
public OpenALStreamPlayer(int source, URL url) {
this.source = source;
this.url = url;
bufferNames = BufferUtils.createIntBuffer(BUFFER_COUNT);
AL10.alGenBuffers(bufferNames);
}
/**
* Initialise our connection to the underlying resource
*
* @throws IOException Indicates a failure to open the underling resource
*/
private void initStreams() throws IOException {
if (audio != null) {
audio.close();
}
OggInputStream audio;
if (url != null) {
audio = new OggInputStream(url.openStream());
} else {
audio = new OggInputStream(ResourceLoader.getResourceAsStream(ref));
}
this.audio = audio;
positionOffset = 0;
}
/**
* Get the source of this stream
*
* @return The name of the source of string
*/
public String getSource() {
return (url == null) ? ref : url.toString();
}
/**
* Clean up the buffers applied to the sound source
*/
private void removeBuffers() {
IntBuffer buffer = BufferUtils.createIntBuffer(1);
int queued = AL10.alGetSourcei(source, AL10.AL_BUFFERS_QUEUED);
while (queued > 0)
{
AL10.alSourceUnqueueBuffers(source, buffer);
queued--;
}
}
/**
* Start this stream playing
*
* @param loop True if the stream should loop
* @throws IOException Indicates a failure to read from the stream
*/
public void play(boolean loop) throws IOException {
this.loop = loop;
initStreams();
done = false;
AL10.alSourceStop(source);
removeBuffers();
startPlayback();
}
/**
* Setup the playback properties
*
* @param pitch The pitch to play back at
*/
public void setup(float pitch) {
this.pitch = pitch;
}
/**
* Check if the playback is complete. Note this will never
* return true if we're looping
*
* @return True if we're looping
*/
public boolean done() {
return done;
}
/**
* Poll the bufferNames - check if we need to fill the bufferNames with another
* section.
*
* Most of the time this should be reasonably quick
*/
public void update() {
if (done) {
return;
}
float sampleRate = audio.getRate();
float sampleSize;
if (audio.getChannels() > 1) {
sampleSize = 4; // AL10.AL_FORMAT_STEREO16
} else {
sampleSize = 2; // AL10.AL_FORMAT_MONO16
}
int processed = AL10.alGetSourcei(source, AL10.AL_BUFFERS_PROCESSED);
while (processed > 0) {
unqueued.clear();
AL10.alSourceUnqueueBuffers(source, unqueued);
int bufferIndex = unqueued.get(0);
float bufferLength = (AL10.alGetBufferi(bufferIndex, AL10.AL_SIZE) / sampleSize) / sampleRate;
positionOffset += bufferLength;
if (stream(bufferIndex)) {
AL10.alSourceQueueBuffers(source, unqueued);
} else {
remainingBufferCount--;
if (remainingBufferCount == 0) {
done = true;
}
}
processed--;
}
int state = AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE);
if (state != AL10.AL_PLAYING) {
AL10.alSourcePlay(source);
}
}
/**
* Stream some data from the audio stream to the buffer indicates by the ID
*
* @param bufferId The ID of the buffer to fill
* @return True if another section was available
*/
public boolean stream(int bufferId) {
try {
int count = audio.read(buffer);
if (count != -1) {
bufferData.clear();
bufferData.put(buffer,0,count);
bufferData.flip();
int format = audio.getChannels() > 1 ? AL10.AL_FORMAT_STEREO16 : AL10.AL_FORMAT_MONO16;
try {
AL10.alBufferData(bufferId, format, bufferData, audio.getRate());
} catch (OpenALException e) {
Log.error("Failed to loop buffer: "+bufferId+" "+format+" "+count+" "+audio.getRate(), e);
return false;
}
} else {
if (loop) {
initStreams();
stream(bufferId);
} else {
done = true;
return false;
}
}
return true;
} catch (IOException e) {
Log.error(e);
return false;
}
}
/**
* Seeks to a position in the music.
*
* @param position Position in seconds.
* @return True if the setting of the position was successful
*/
public boolean setPosition(float position) {
try {
if (getPosition() > position) {
initStreams();
}
float sampleRate = audio.getRate();
float sampleSize;
if (audio.getChannels() > 1) {
sampleSize = 4; // AL10.AL_FORMAT_STEREO16
} else {
sampleSize = 2; // AL10.AL_FORMAT_MONO16
}
while (positionOffset < position) {
int count = audio.read(buffer);
if (count != -1) {
float bufferLength = (count / sampleSize) / sampleRate;
positionOffset += bufferLength;
} else {
if (loop) {
initStreams();
} else {
done = true;
}
return false;
}
}
startPlayback();
return true;
} catch (IOException e) {
Log.error(e);
return false;
}
}
/**
* Starts the streaming.
*/
private void startPlayback() {
AL10.alSourcei(source, AL10.AL_LOOPING, AL10.AL_FALSE);
AL10.alSourcef(source, AL10.AL_PITCH, pitch);
remainingBufferCount = BUFFER_COUNT;
for (int i = 0; i < BUFFER_COUNT; i++) {
stream(bufferNames.get(i));
}
AL10.alSourceQueueBuffers(source, bufferNames);
AL10.alSourcePlay(source);
}
/**
* Return the current playing position in the sound
*
* @return The current position in seconds.
*/
public float getPosition() {
return positionOffset + AL10.alGetSourcef(source, AL11.AL_SEC_OFFSET);
}
}

View File

@@ -0,0 +1,974 @@
package org.newdawn.slick.openal;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import org.lwjgl.BufferUtils;
import org.lwjgl.Sys;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.OpenALException;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
/**
* Responsible for holding and playing the sounds used in the game.
*
* @author Kevin Glass
* @author Rockstar setVolume cleanup
*/
public class SoundStore {
/** The single instance of this class */
private static SoundStore store = new SoundStore();
/** True if sound effects are turned on */
private boolean sounds;
/** True if music is turned on */
private boolean music;
/** True if sound initialisation succeeded */
private boolean soundWorks;
/** The number of sound sources enabled - default 8 */
private int sourceCount;
/** The map of references to IDs of previously loaded sounds */
private HashMap loaded = new HashMap();
/** The ID of the buffer containing the music currently being played */
private int currentMusic = -1;
/** The OpenGL AL sound sources in use */
private IntBuffer sources;
/** The next source to be used for sound effects */
private int nextSource;
/** True if the sound system has been initialise */
private boolean inited = false;
/** The MODSound to be updated */
private MODSound mod;
/** The stream to be updated */
private OpenALStreamPlayer stream;
/** The global music volume setting */
private float musicVolume = 1.0f;
/** The global sound fx volume setting */
private float soundVolume = 1.0f;
/** The volume given for the last current music */
private float lastCurrentMusicVolume = 1.0f;
/** True if the music is paused */
private boolean paused;
/** True if we're returning deferred versions of resources */
private boolean deferred;
/** The buffer used to set the velocity of a source */
private FloatBuffer sourceVel = BufferUtils.createFloatBuffer(3).put(new float[] { 0.0f, 0.0f, 0.0f });
/** The buffer used to set the position of a source */
private FloatBuffer sourcePos = BufferUtils.createFloatBuffer(3);
/** The maximum number of sources */
private int maxSources = 64;
/**
* Create a new sound store
*/
private SoundStore() {
}
/**
* Clear out the sound store contents
*/
public void clear() {
store = new SoundStore();
}
/**
* Disable use of the Sound Store
*/
public void disable() {
inited = true;
}
/**
* True if we should only record the request to load in the intention
* of loading the sound later
*
* @param deferred True if the we should load a token
*/
public void setDeferredLoading(boolean deferred) {
this.deferred = deferred;
}
/**
* Check if we're using deferred loading
*
* @return True if we're loading deferred sounds
*/
public boolean isDeferredLoading() {
return deferred;
}
/**
* Inidicate whether music should be playing
*
* @param music True if music should be played
*/
public void setMusicOn(boolean music) {
if (soundWorks) {
this.music = music;
if (music) {
restartLoop();
setMusicVolume(musicVolume);
} else {
pauseLoop();
}
}
}
/**
* Check if music should currently be playing
*
* @return True if music is currently playing
*/
public boolean isMusicOn() {
return music;
}
/**
* Set the music volume
*
* @param volume The volume for music
*/
public void setMusicVolume(float volume) {
if (volume < 0) {
volume = 0;
}
if (volume > 1) {
volume = 1;
}
musicVolume = volume;
if (soundWorks) {
AL10.alSourcef(sources.get(0), AL10.AL_GAIN, lastCurrentMusicVolume * musicVolume);
}
}
/**
* Get the volume scalar of the music that is currently playing.
*
* @return The volume of the music currently playing
*/
public float getCurrentMusicVolume() {
return lastCurrentMusicVolume;
}
/**
* Set the music volume of the current playing music. Does NOT affect the global volume
*
* @param volume The volume for the current playing music
*/
public void setCurrentMusicVolume(float volume) {
if (volume < 0) {
volume = 0;
}
if (volume > 1) {
volume = 1;
}
if (soundWorks) {
lastCurrentMusicVolume = volume;
AL10.alSourcef(sources.get(0), AL10.AL_GAIN, lastCurrentMusicVolume * musicVolume);
}
}
/**
* Set the sound volume
*
* @param volume The volume for sound fx
*/
public void setSoundVolume(float volume) {
if (volume < 0) {
volume = 0;
}
soundVolume = volume;
}
/**
* Check if sound works at all
*
* @return True if sound works at all
*/
public boolean soundWorks() {
return soundWorks;
}
/**
* Check if music is currently enabled
*
* @return True if music is currently enabled
*/
public boolean musicOn() {
return music;
}
/**
* Get the volume for sounds
*
* @return The volume for sounds
*/
public float getSoundVolume() {
return soundVolume;
}
/**
* Get the volume for music
*
* @return The volume for music
*/
public float getMusicVolume() {
return musicVolume;
}
/**
* Get the ID of a given source
*
* @param index The ID of a given source
* @return The ID of the given source
*/
public int getSource(int index) {
if (!soundWorks) {
return -1;
}
if (index < 0) {
return -1;
}
return sources.get(index);
}
/**
* Indicate whether sound effects should be played
*
* @param sounds True if sound effects should be played
*/
public void setSoundsOn(boolean sounds) {
if (soundWorks) {
this.sounds = sounds;
}
}
/**
* Check if sound effects are currently enabled
*
* @return True if sound effects are currently enabled
*/
public boolean soundsOn() {
return sounds;
}
/**
* Set the maximum number of concurrent sound effects that will be
* attempted
*
* @param max The maximum number of sound effects/music to mix
*/
public void setMaxSources(int max) {
this.maxSources = max;
}
/**
* Initialise the sound effects stored. This must be called
* before anything else will work
*/
public void init() {
if (inited) {
return;
}
Log.info("Initialising sounds..");
inited = true;
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
AL.create();
soundWorks = true;
sounds = true;
music = true;
Log.info("- Sound works");
} catch (Exception e) {
Log.error("Sound initialisation failure.");
Log.error(e);
soundWorks = false;
sounds = false;
music = false;
}
return null;
}});
if (soundWorks) {
sourceCount = 0;
sources = BufferUtils.createIntBuffer(maxSources);
while (AL10.alGetError() == AL10.AL_NO_ERROR) {
IntBuffer temp = BufferUtils.createIntBuffer(1);
try {
AL10.alGenSources(temp);
if (AL10.alGetError() == AL10.AL_NO_ERROR) {
sourceCount++;
sources.put(temp.get(0));
if (sourceCount > maxSources-1) {
break;
}
}
} catch (OpenALException e) {
// expected at the end
break;
}
}
Log.info("- "+sourceCount+" OpenAL source available");
if (AL10.alGetError() != AL10.AL_NO_ERROR) {
sounds = false;
music = false;
soundWorks = false;
Log.error("- AL init failed");
} else {
FloatBuffer listenerOri = BufferUtils.createFloatBuffer(6).put(
new float[] { 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f });
FloatBuffer listenerVel = BufferUtils.createFloatBuffer(3).put(
new float[] { 0.0f, 0.0f, 0.0f });
FloatBuffer listenerPos = BufferUtils.createFloatBuffer(3).put(
new float[] { 0.0f, 0.0f, 0.0f });
listenerPos.flip();
listenerVel.flip();
listenerOri.flip();
AL10.alListener(AL10.AL_POSITION, listenerPos);
AL10.alListener(AL10.AL_VELOCITY, listenerVel);
AL10.alListener(AL10.AL_ORIENTATION, listenerOri);
Log.info("- Sounds source generated");
}
}
}
/**
* Stop a particular sound source
*
* @param index The index of the source to stop
*/
void stopSource(int index) {
AL10.alSourceStop(sources.get(index));
}
/**
* Play the specified buffer as a sound effect with the specified
* pitch and gain.
*
* @param buffer The ID of the buffer to play
* @param pitch The pitch to play at
* @param gain The gain to play at
* @param loop True if the sound should loop
* @return source The source that will be used
*/
int playAsSound(int buffer,float pitch,float gain,boolean loop) {
return playAsSoundAt(buffer, pitch, gain, loop, 0, 0, 0);
}
/**
* Play the specified buffer as a sound effect with the specified
* pitch and gain.
*
* @param buffer The ID of the buffer to play
* @param pitch The pitch to play at
* @param gain The gain to play at
* @param loop True if the sound should loop
* @param x The x position to play the sound from
* @param y The y position to play the sound from
* @param z The z position to play the sound from
* @return source The source that will be used
*/
int playAsSoundAt(int buffer,float pitch,float gain,boolean loop,float x, float y, float z) {
gain *= soundVolume;
if (gain == 0) {
gain = 0.001f;
}
if (soundWorks) {
if (sounds) {
int nextSource = findFreeSource();
if (nextSource == -1) {
return -1;
}
AL10.alSourceStop(sources.get(nextSource));
AL10.alSourcei(sources.get(nextSource), AL10.AL_BUFFER, buffer);
AL10.alSourcef(sources.get(nextSource), AL10.AL_PITCH, pitch);
AL10.alSourcef(sources.get(nextSource), AL10.AL_GAIN, gain);
AL10.alSourcei(sources.get(nextSource), AL10.AL_LOOPING, loop ? AL10.AL_TRUE : AL10.AL_FALSE);
sourcePos.clear();
sourceVel.clear();
sourceVel.put(new float[] { 0, 0, 0 });
sourcePos.put(new float[] { x, y, z });
sourcePos.flip();
sourceVel.flip();
AL10.alSource(sources.get(nextSource), AL10.AL_POSITION, sourcePos);
AL10.alSource(sources.get(nextSource), AL10.AL_VELOCITY, sourceVel);
AL10.alSourcePlay(sources.get(nextSource));
return nextSource;
}
}
return -1;
}
/**
* Check if a particular source is playing
*
* @param index The index of the source to check
* @return True if the source is playing
*/
boolean isPlaying(int index) {
int state = AL10.alGetSourcei(sources.get(index), AL10.AL_SOURCE_STATE);
return (state == AL10.AL_PLAYING);
}
/**
* Find a free sound source
*
* @return The index of the free sound source
*/
private int findFreeSource() {
for (int i=1;i<sourceCount-1;i++) {
int state = AL10.alGetSourcei(sources.get(i), AL10.AL_SOURCE_STATE);
if ((state != AL10.AL_PLAYING) && (state != AL10.AL_PAUSED)) {
return i;
}
}
return -1;
}
/**
* Play the specified buffer as music (i.e. use the music channel)
*
* @param buffer The buffer to be played
* @param pitch The pitch to play the music at
* @param gain The gaing to play the music at
* @param loop True if we should loop the music
*/
void playAsMusic(int buffer,float pitch,float gain, boolean loop) {
paused = false;
setMOD(null);
if (soundWorks) {
if (currentMusic != -1) {
AL10.alSourceStop(sources.get(0));
}
getMusicSource();
AL10.alSourcei(sources.get(0), AL10.AL_BUFFER, buffer);
AL10.alSourcef(sources.get(0), AL10.AL_PITCH, pitch);
AL10.alSourcei(sources.get(0), AL10.AL_LOOPING, loop ? AL10.AL_TRUE : AL10.AL_FALSE);
currentMusic = sources.get(0);
if (!music) {
pauseLoop();
} else {
AL10.alSourcePlay(sources.get(0));
}
}
}
/**
* Get the OpenAL source used for music
*
* @return The open al source used for music
*/
private int getMusicSource() {
return sources.get(0);
}
/**
* Set the pitch at which the current music is being played
*
* @param pitch The pitch at which the current music is being played
*/
public void setMusicPitch(float pitch) {
if (soundWorks) {
AL10.alSourcef(sources.get(0), AL10.AL_PITCH, pitch);
}
}
/**
* Pause the music loop that is currently playing
*/
public void pauseLoop() {
if ((soundWorks) && (currentMusic != -1)){
paused = true;
AL10.alSourcePause(currentMusic);
}
}
/**
* Restart the music loop that is currently paused
*/
public void restartLoop() {
if ((music) && (soundWorks) && (currentMusic != -1)){
paused = false;
AL10.alSourcePlay(currentMusic);
}
}
/**
* Check if the supplied player is currently being polled by this
* sound store.
*
* @param player The player to check
* @return True if this player is currently in use by this sound store
*/
boolean isPlaying(OpenALStreamPlayer player) {
return stream == player;
}
/**
* Get a MOD sound (mod/xm etc)
*
* @param ref The refernece to the mod to load
* @return The sound for play back
* @throws IOException Indicates a failure to read the data
*/
public Audio getMOD(String ref) throws IOException {
return getMOD(ref, ResourceLoader.getResourceAsStream(ref));
}
/**
* Get a MOD sound (mod/xm etc)
*
* @param in The stream to the MOD to load
* @return The sound for play back
* @throws IOException Indicates a failure to read the data
*/
public Audio getMOD(InputStream in) throws IOException {
return getMOD(in.toString(), in);
}
/**
* Get a MOD sound (mod/xm etc)
*
* @param ref The stream to the MOD to load
* @param in The stream to the MOD to load
* @return The sound for play back
* @throws IOException Indicates a failure to read the data
*/
public Audio getMOD(String ref, InputStream in) throws IOException {
if (!soundWorks) {
return new NullAudio();
}
if (!inited) {
throw new RuntimeException("Can't load sounds until SoundStore is init(). Use the container init() method.");
}
if (deferred) {
return new DeferredSound(ref, in, DeferredSound.MOD);
}
return new MODSound(this, in);
}
/**
* Get the Sound based on a specified AIF file
*
* @param ref The reference to the AIF file in the classpath
* @return The Sound read from the AIF file
* @throws IOException Indicates a failure to load the AIF
*/
public Audio getAIF(String ref) throws IOException {
return getAIF(ref, ResourceLoader.getResourceAsStream(ref));
}
/**
* Get the Sound based on a specified AIF file
*
* @param in The stream to the MOD to load
* @return The Sound read from the AIF file
* @throws IOException Indicates a failure to load the AIF
*/
public Audio getAIF(InputStream in) throws IOException {
return getAIF(in.toString(), in);
}
/**
* Get the Sound based on a specified AIF file
*
* @param ref The reference to the AIF file in the classpath
* @param in The stream to the AIF to load
* @return The Sound read from the AIF file
* @throws IOException Indicates a failure to load the AIF
*/
public Audio getAIF(String ref, InputStream in) throws IOException {
in = new BufferedInputStream(in);
if (!soundWorks) {
return new NullAudio();
}
if (!inited) {
throw new RuntimeException("Can't load sounds until SoundStore is init(). Use the container init() method.");
}
if (deferred) {
return new DeferredSound(ref, in, DeferredSound.AIF);
}
int buffer = -1;
if (loaded.get(ref) != null) {
buffer = ((Integer) loaded.get(ref)).intValue();
} else {
try {
IntBuffer buf = BufferUtils.createIntBuffer(1);
AiffData data = AiffData.create(in);
AL10.alGenBuffers(buf);
AL10.alBufferData(buf.get(0), data.format, data.data, data.samplerate);
loaded.put(ref,new Integer(buf.get(0)));
buffer = buf.get(0);
} catch (Exception e) {
Log.error(e);
IOException x = new IOException("Failed to load: "+ref);
x.initCause(e);
throw x;
}
}
if (buffer == -1) {
throw new IOException("Unable to load: "+ref);
}
return new AudioImpl(this, buffer);
}
/**
* Get the Sound based on a specified WAV file
*
* @param ref The reference to the WAV file in the classpath
* @return The Sound read from the WAV file
* @throws IOException Indicates a failure to load the WAV
*/
public Audio getWAV(String ref) throws IOException {
return getWAV(ref, ResourceLoader.getResourceAsStream(ref));
}
/**
* Get the Sound based on a specified WAV file
*
* @param in The stream to the WAV to load
* @return The Sound read from the WAV file
* @throws IOException Indicates a failure to load the WAV
*/
public Audio getWAV(InputStream in) throws IOException {
return getWAV(in.toString(), in);
}
/**
* Get the Sound based on a specified WAV file
*
* @param ref The reference to the WAV file in the classpath
* @param in The stream to the WAV to load
* @return The Sound read from the WAV file
* @throws IOException Indicates a failure to load the WAV
*/
public Audio getWAV(String ref, InputStream in) throws IOException {
if (!soundWorks) {
return new NullAudio();
}
if (!inited) {
throw new RuntimeException("Can't load sounds until SoundStore is init(). Use the container init() method.");
}
if (deferred) {
return new DeferredSound(ref, in, DeferredSound.WAV);
}
int buffer = -1;
if (loaded.get(ref) != null) {
buffer = ((Integer) loaded.get(ref)).intValue();
} else {
try {
IntBuffer buf = BufferUtils.createIntBuffer(1);
WaveData data = WaveData.create(in);
AL10.alGenBuffers(buf);
AL10.alBufferData(buf.get(0), data.format, data.data, data.samplerate);
loaded.put(ref,new Integer(buf.get(0)));
buffer = buf.get(0);
} catch (Exception e) {
Log.error(e);
IOException x = new IOException("Failed to load: "+ref);
x.initCause(e);
throw x;
}
}
if (buffer == -1) {
throw new IOException("Unable to load: "+ref);
}
return new AudioImpl(this, buffer);
}
/**
* Get the Sound based on a specified OGG file
*
* @param ref The reference to the OGG file in the classpath
* @return The Sound read from the OGG file
* @throws IOException Indicates a failure to load the OGG
*/
public Audio getOggStream(String ref) throws IOException {
if (!soundWorks) {
return new NullAudio();
}
setMOD(null);
setStream(null);
if (currentMusic != -1) {
AL10.alSourceStop(sources.get(0));
}
getMusicSource();
currentMusic = sources.get(0);
return new StreamSound(new OpenALStreamPlayer(currentMusic, ref));
}
/**
* Get the Sound based on a specified OGG file
*
* @param ref The reference to the OGG file in the classpath
* @return The Sound read from the OGG file
* @throws IOException Indicates a failure to load the OGG
*/
public Audio getOggStream(URL ref) throws IOException {
if (!soundWorks) {
return new NullAudio();
}
setMOD(null);
setStream(null);
if (currentMusic != -1) {
AL10.alSourceStop(sources.get(0));
}
getMusicSource();
currentMusic = sources.get(0);
return new StreamSound(new OpenALStreamPlayer(currentMusic, ref));
}
/**
* Get the Sound based on a specified OGG file
*
* @param ref The reference to the OGG file in the classpath
* @return The Sound read from the OGG file
* @throws IOException Indicates a failure to load the OGG
*/
public Audio getOgg(String ref) throws IOException {
return getOgg(ref, ResourceLoader.getResourceAsStream(ref));
}
/**
* Get the Sound based on a specified OGG file
*
* @param in The stream to the OGG to load
* @return The Sound read from the OGG file
* @throws IOException Indicates a failure to load the OGG
*/
public Audio getOgg(InputStream in) throws IOException {
return getOgg(in.toString(), in);
}
/**
* Get the Sound based on a specified OGG file
*
* @param ref The reference to the OGG file in the classpath
* @param in The stream to the OGG to load
* @return The Sound read from the OGG file
* @throws IOException Indicates a failure to load the OGG
*/
public Audio getOgg(String ref, InputStream in) throws IOException {
if (!soundWorks) {
return new NullAudio();
}
if (!inited) {
throw new RuntimeException("Can't load sounds until SoundStore is init(). Use the container init() method.");
}
if (deferred) {
return new DeferredSound(ref, in, DeferredSound.OGG);
}
int buffer = -1;
if (loaded.get(ref) != null) {
buffer = ((Integer) loaded.get(ref)).intValue();
} else {
try {
IntBuffer buf = BufferUtils.createIntBuffer(1);
OggDecoder decoder = new OggDecoder();
OggData ogg = decoder.getData(in);
AL10.alGenBuffers(buf);
AL10.alBufferData(buf.get(0), ogg.channels > 1 ? AL10.AL_FORMAT_STEREO16 : AL10.AL_FORMAT_MONO16, ogg.data, ogg.rate);
loaded.put(ref,new Integer(buf.get(0)));
buffer = buf.get(0);
} catch (Exception e) {
Log.error(e);
Sys.alert("Error","Failed to load: "+ref+" - "+e.getMessage());
throw new IOException("Unable to load: "+ref);
}
}
if (buffer == -1) {
throw new IOException("Unable to load: "+ref);
}
return new AudioImpl(this, buffer);
}
/**
* Set the mod thats being streamed if any
*
* @param sound The mod being streamed
*/
void setMOD(MODSound sound) {
if (!soundWorks) {
return;
}
currentMusic = sources.get(0);
stopSource(0);
this.mod = sound;
if (sound != null) {
this.stream = null;
}
paused = false;
}
/**
* Set the stream being played
*
* @param stream The stream being streamed
*/
void setStream(OpenALStreamPlayer stream) {
if (!soundWorks) {
return;
}
currentMusic = sources.get(0);
this.stream = stream;
if (stream != null) {
this.mod = null;
}
paused = false;
}
/**
* Poll the streaming system
*
* @param delta The amount of time passed since last poll (in milliseconds)
*/
public void poll(int delta) {
if (!soundWorks) {
return;
}
if (paused) {
return;
}
if (music) {
if (mod != null) {
try {
mod.poll();
} catch (OpenALException e) {
Log.error("Error with OpenGL MOD Player on this this platform");
Log.error(e);
mod = null;
}
}
if (stream != null) {
try {
stream.update();
} catch (OpenALException e) {
Log.error("Error with OpenGL Streaming Player on this this platform");
Log.error(e);
mod = null;
}
}
}
}
/**
* Check if the music is currently playing
*
* @return True if the music is playing
*/
public boolean isMusicPlaying()
{
if (!soundWorks) {
return false;
}
int state = AL10.alGetSourcei(sources.get(0), AL10.AL_SOURCE_STATE);
return ((state == AL10.AL_PLAYING) || (state == AL10.AL_PAUSED));
}
/**
* Get the single instance of this class
*
* @return The single instnace of this class
*/
public static SoundStore get() {
return store;
}
/**
* Stop a playing sound identified by the ID returned from playing. This utility method
* should only be used when needing to stop sound effects that may have been played
* more than once and need to be explicitly stopped.
*
* @param id The ID of the underlying OpenAL source as returned from playAsSoundEffect
*/
public void stopSoundEffect(int id) {
AL10.alSourceStop(id);
}
/**
* Retrieve the number of OpenAL sound sources that have been
* determined at initialisation.
*
* @return The number of sources available
*/
public int getSourceCount() {
return sourceCount;
}
}

View File

@@ -0,0 +1,108 @@
package org.newdawn.slick.openal;
import java.io.IOException;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL10;
import org.newdawn.slick.util.Log;
/**
* A sound implementation wrapped round a player which reads (and potentially) rereads
* a stream. This supplies streaming audio
*
* @author kevin
* @author Nathan Sweet <misc@n4te.com>
* @author Rockstar playAsMusic cleanup
*/
public class StreamSound extends AudioImpl {
/** The player we're going to ask to stream data */
private OpenALStreamPlayer player;
/**
* Create a new sound wrapped round a stream
*
* @param player The stream player we'll use to access the stream
*/
public StreamSound(OpenALStreamPlayer player) {
this.player = player;
}
/**
* @see org.newdawn.slick.openal.AudioImpl#isPlaying()
*/
public boolean isPlaying() {
return SoundStore.get().isPlaying(player);
}
/**
* @see org.newdawn.slick.openal.AudioImpl#playAsMusic(float, float, boolean)
*/
public int playAsMusic(float pitch, float gain, boolean loop) {
try {
cleanUpSource();
player.setup(pitch);
player.play(loop);
SoundStore.get().setStream(player);
} catch (IOException e) {
Log.error("Failed to read OGG source: "+player.getSource());
}
return SoundStore.get().getSource(0);
}
/**
* Clean up the buffers applied to the sound source
*/
private void cleanUpSource() {
SoundStore store = SoundStore.get();
AL10.alSourceStop(store.getSource(0));
IntBuffer buffer = BufferUtils.createIntBuffer(1);
int queued = AL10.alGetSourcei(store.getSource(0), AL10.AL_BUFFERS_QUEUED);
while (queued > 0)
{
AL10.alSourceUnqueueBuffers(store.getSource(0), buffer);
queued--;
}
AL10.alSourcei(store.getSource(0), AL10.AL_BUFFER, 0);
}
/**
* @see org.newdawn.slick.openal.AudioImpl#playAsSoundEffect(float, float, boolean, float, float, float)
*/
public int playAsSoundEffect(float pitch, float gain, boolean loop, float x, float y, float z) {
return playAsMusic(pitch, gain, loop);
}
/**
* @see org.newdawn.slick.openal.AudioImpl#playAsSoundEffect(float, float, boolean)
*/
public int playAsSoundEffect(float pitch, float gain, boolean loop) {
return playAsMusic(pitch, gain, loop);
}
/**
* @see org.newdawn.slick.openal.AudioImpl#stop()
*/
public void stop() {
SoundStore.get().setStream(null);
}
/**
* @see org.newdawn.slick.openal.AudioImpl#setPosition(float)
*/
public boolean setPosition(float position) {
return player.setPosition(position);
}
/**
* @see org.newdawn.slick.openal.AudioImpl#getPosition()
*/
public float getPosition() {
return player.getPosition();
}
}

View File

@@ -0,0 +1,265 @@
/*
* Copyright (c) 2002-2004 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 org.newdawn.slick.openal;
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;
/**
*
* Utitlity class for loading wavefiles.
*
* @author Brian Matzon <brian@matzon.dk>
* @version $Revision: 2286 $
* $Id: WaveData.java 2286 2006-03-23 19:32:21Z matzon $
*/
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 {
return create(
AudioSystem.getAudioInputStream(
new BufferedInputStream(path.openStream())));
} catch (Exception e) {
org.lwjgl.LWJGLUtil.log("Unable to create from: " + path);
e.printStackTrace();
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(WaveData.class.getClassLoader().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) {
org.lwjgl.LWJGLUtil.log("Unable to create from inputstream");
e.printStackTrace();
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) {
e.printStackTrace();
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) {
e.printStackTrace();
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 {
throw new RuntimeException("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 {
throw new RuntimeException("Illegal sample size");
}
} else {
throw new RuntimeException("Only mono or stereo is supported");
}
//read data into buffer
byte[] buf =
new byte[audioformat.getChannels()
* (int) ais.getFrameLength()
* audioformat.getSampleSizeInBits()
/ 8];
int read = 0, total = 0;
try {
while ((read = ais.read(buf, total, buf.length - total)) != -1
&& total < buf.length) {
total += read;
}
} catch (IOException ioe) {
return null;
}
//insert data into bytebuffer
ByteBuffer buffer = convertAudioBytes(buf, audioformat.getSampleSizeInBits() == 16);
/* ByteBuffer buffer = ByteBuffer.allocateDirect(buf.length);
buffer.put(buf);
buffer.rewind();*/
//create our result
WaveData wavedata =
new WaveData(buffer, channels, (int) audioformat.getSampleRate());
//close stream
try {
ais.close();
} catch (IOException ioe) {
}
return wavedata;
}
/**
* Convert the audio bytes into the stream
*
* @param audio_bytes The audio byts
* @param two_bytes_data True if we using double byte data
* @return The byte bufer of data
*/
private static ByteBuffer convertAudioBytes(byte[] audio_bytes, boolean two_bytes_data) {
ByteBuffer dest = ByteBuffer.allocateDirect(audio_bytes.length);
dest.order(ByteOrder.nativeOrder());
ByteBuffer src = ByteBuffer.wrap(audio_bytes);
src.order(ByteOrder.LITTLE_ENDIAN);
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;
}
}

View File

@@ -0,0 +1,4 @@
<BODY>
This package contains the nitty gritty sound manipulation code for using OpenAL with standard audio formats. As
a user you shouldn't need to access anything here directly.
</BODY>