aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Gouesse <[email protected]>2022-10-24 00:52:24 +0200
committerJulien Gouesse <[email protected]>2022-10-24 00:52:24 +0200
commit48a2a211c691392aeef78b8e677b728f43d0ff95 (patch)
treeb14bbbd5defc91ddaf2dba76b92b8a1f144a2ee1
parent4f5379ee6d621125d253b325763212a6ee81acf7 (diff)
Adds the JOAL backend of the Ardor3D sound system
-rw-r--r--ardor3d-audio-joal/build.gradle10
-rw-r--r--ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/ChannelJOAL.java715
-rw-r--r--ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/LibraryJOAL.java1112
-rw-r--r--ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/SourceJOAL.java769
-rw-r--r--settings.gradle2
5 files changed, 2608 insertions, 0 deletions
diff --git a/ardor3d-audio-joal/build.gradle b/ardor3d-audio-joal/build.gradle
new file mode 100644
index 0000000..b5c2ccf
--- /dev/null
+++ b/ardor3d-audio-joal/build.gradle
@@ -0,0 +1,10 @@
+
+description = 'Ardor 3D Sound System backend based on JOAL'
+dependencies {
+ implementation project(':ardor3d-core')
+ implementation project(':ardor3d-math')
+ implementation project(':ardor3d-savable')
+ implementation project(':ardor3d-audio')
+ implementation group: 'org.jogamp.gluegen', name: 'gluegen-rt-main', version:'2.3.2'
+ implementation group: 'org.jogamp.joal', name: 'joal-main', version:'2.3.2'
+}
diff --git a/ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/ChannelJOAL.java b/ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/ChannelJOAL.java
new file mode 100644
index 0000000..baa6a7f
--- /dev/null
+++ b/ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/ChannelJOAL.java
@@ -0,0 +1,715 @@
+/**
+ * Copyright (c) 2008-2014 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+package com.ardor3d.audio.joal;
+
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.util.LinkedList;
+import com.ardor3d.audio.sampled.AudioFormat;
+
+// From the joal library, https://jogamp.org/joal/www/
+import com.jogamp.openal.AL;
+
+import com.ardor3d.audio.Channel;
+import com.ardor3d.audio.SoundSystemConfig;
+
+/**
+ * The ChannelJOAL class is used to reserve a sound-card voice using the
+ * JOAL binding of OpenAL. Channels can be either normal or streaming
+ * channels.
+ *<b><br><br>
+ * This software is based on or using the JOAL Library available from
+ * https://jogamp.org/joal/www/
+ *</b><br><br>
+ * JOAL License:
+ *<br><i>
+ * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+ *<br>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *<br>
+ * -Redistribution of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *<br>
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *<br>
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * <br>
+ * This software is provided "AS IS," without a warranty of any kind.
+ * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
+ * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS
+ * LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A
+ * RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+ * IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT
+ * OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR
+ * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS
+ * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *<br>
+ * You acknowledge that this software is not designed or intended for use in the
+ * design, construction, operation or maintenance of any nuclear facility.
+ * <br><br><br></i>
+ *<b><i> SoundSystem LibraryJOAL License:</b></i><br><b><br>
+ *<b>
+ * You are free to use this library for any purpose, commercial or otherwise.
+ * You may modify this library or source code, and distribute it any way you
+ * like, provided the following conditions are met:
+ *<br>
+ * 1) You must abide by the conditions of the aforementioned JOAL License.
+ *<br>
+ * 2) You may not falsely claim to be the author of this library or any
+ * unmodified portion of it.
+ *<br>
+ * 3) You may not copyright this library or a modified version of it and then
+ * sue me for copyright infringement.
+ *<br>
+ * 4) If you modify the source code, you must clearly document the changes
+ * made before redistributing the modified source code, so other users know
+ * it is not the original code.
+ *<br>
+ * 5) You are not required to give me credit for this library in any derived
+ * work, but if you do, you must also mention my website:
+ * http://www.paulscode.com
+ *<br>
+ * 6) I the author will not be responsible for any damages (physical,
+ * financial, or otherwise) caused by the use if this library or any part
+ * of it.
+ *<br>
+ * 7) I the author do not guarantee, warrant, or make any representations,
+ * either expressed or implied, regarding the use of this library or any
+ * part of it.
+ * <br><br>
+ * Author: Paul Lamb
+ * <br>
+ * http://www.paulscode.com
+ * </b>
+ */
+public class ChannelJOAL extends Channel
+{
+/**
+ * OpenAL's identifier for this channel.
+ */
+ public int[] ALSource = null;
+
+/**
+ * OpenAL data format to use when playing back the assigned source.
+ */
+ public int ALformat; // OpenAL data format
+
+/**
+ * Sample rate (speed) to use for play-back.
+ */
+ public int sampleRate; // sample rate
+
+/**
+ * Miliseconds of buffers previously played (streaming sources).
+ */
+ public float millisPreviouslyPlayed = 0;
+
+/**
+ * Handle for accessing OpenAL.
+ */
+ private AL al = null;
+
+/**
+ * Constructor: takes channelType identifier and a handle to the OpenAL
+ * identifier to use for this channel. Possible values for channel type can be
+ * found in the {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig}
+ * class.
+ * @param type Type of channel (normal or streaming).
+ * @param src Handle to the OpenAL source identifier.
+ */
+ public ChannelJOAL( int type, int[] src )
+ {
+ super( type );
+ libraryType = LibraryJOAL.class;
+ ALSource = src;
+ al = LibraryJOAL.getAL();
+ }
+
+/**
+ * Empties the streamBuffers list, stops and deletes the ALSource, shuts the
+ * channel down, and removes references to all instantiated objects.
+ */
+ @Override
+ public void cleanup()
+ {
+ if( ALSource != null )
+ {
+ try
+ {
+ // Stop playing the source:
+ al.alSourceStop( ALSource[0] );
+ al.alGetError();
+ }
+ catch( Exception e )
+ {}
+ try
+ {
+ // Delete the source:
+ al.alDeleteSources( 1, ALSource, 0 );
+ al.alGetError();
+ }
+ catch( Exception e )
+ {}
+ }
+ ALSource = null;
+
+ super.cleanup();
+ }
+
+/**
+ * Attaches an OpenAL sound-buffer identifier for the sound data to be played
+ * back for a normal source.
+ * @param buf Identifier for the sound data to play.
+ * @return False if an error occurred.
+ */
+ public boolean attachBuffer( int[] buf )
+ {
+ // A sound buffer can only be attached to a normal source:
+ if( errorCheck( channelType != SoundSystemConfig.TYPE_NORMAL,
+ "Sound buffers may only be attached to normal " +
+ "sources." ) )
+ return false;
+
+ // send the sound buffer to the channel:
+ al.alSourcei( ALSource[0], AL.AL_BUFFER, buf[0] );
+
+ // save the format for later, for determining milliseconds played
+ if( attachedSource != null && attachedSource.soundBuffer != null &&
+ attachedSource.soundBuffer.audioFormat != null )
+ setAudioFormat( attachedSource.soundBuffer.audioFormat );
+
+ // Check for errors and return:
+ return checkALError();
+ }
+
+/**
+ * Sets the channel up to receive the specified audio format.
+ * @param audioFormat Format to use when playing the stream data.
+ */
+ @Override
+ public void setAudioFormat( AudioFormat audioFormat )
+ {
+ int soundFormat = 0;
+ if( audioFormat.getChannels() == 1 )
+ {
+ if( audioFormat.getSampleSizeInBits() == 8 )
+ {
+ soundFormat = AL.AL_FORMAT_MONO8;
+ }
+ else if( audioFormat.getSampleSizeInBits() == 16 )
+ {
+ soundFormat = AL.AL_FORMAT_MONO16;
+ }
+ else
+ {
+ errorMessage( "Illegal sample size in method " +
+ "'setAudioFormat'" );
+ return;
+ }
+ }
+ else if( audioFormat.getChannels() == 2 )
+ {
+ if( audioFormat.getSampleSizeInBits() == 8 )
+ {
+ soundFormat = AL.AL_FORMAT_STEREO8;
+ }
+ else if( audioFormat.getSampleSizeInBits() == 16 )
+ {
+ soundFormat = AL.AL_FORMAT_STEREO16;
+ }
+ else
+ {
+ errorMessage( "Illegal sample size in method " +
+ "'setAudioFormat'" );
+ return;
+ }
+ }
+ else
+ {
+ errorMessage( "Audio data neither mono nor stereo in " +
+ "method 'setAudioFormat'" );
+ return;
+ }
+ ALformat = soundFormat;
+ sampleRate = (int) audioFormat.getSampleRate();
+ }
+
+/**
+ * Sets the channel up to receive the specified OpenAL audio format and sample
+ * rate.
+ * @param format Format to use.
+ * @param rate Sample rate (speed) to use.
+ */
+ public void setFormat( int format, int rate )
+ {
+ ALformat = format;
+ sampleRate = rate;
+ }
+
+/**
+ * Queues up the initial byte[] buffers of data to be streamed.
+ * @param bufferList List of the first buffers to be played for a streaming source.
+ * @return False if problem occurred or if end of stream was reached.
+ */
+ @Override
+ public boolean preLoadBuffers( LinkedList<byte[]> bufferList )
+ {
+ // Stream buffers can only be queued for streaming sources:
+ if( errorCheck( channelType != SoundSystemConfig.TYPE_STREAMING,
+ "Buffers may only be queued for streaming sources." ) )
+ return false;
+
+ if( errorCheck( bufferList == null,
+ "Buffer List null in method 'preLoadBuffers'" ) )
+ return false;
+
+ IntBuffer streamBuffers;
+
+ // Remember if the channel was playing:
+ boolean playing = playing();
+ // stop the channel if it is playing:
+ if( playing )
+ {
+ al.alSourceStop( ALSource[0] );
+ checkALError();
+ }
+ int[] processed = new int[1];
+ al.alGetSourcei( ALSource[0], AL.AL_BUFFERS_PROCESSED, processed, 0 );
+
+ if( processed[0] > 0 )
+ {
+ streamBuffers = IntBuffer.wrap( new int[processed[0]] );
+ al.alGenBuffers( processed[0], streamBuffers );
+ if( errorCheck( checkALError(),
+ "Error clearing stream buffers in method 'preLoadBuffers'" ) )
+ return false;
+ al.alSourceUnqueueBuffers( ALSource[0], processed[0],
+ streamBuffers );
+ if( errorCheck( checkALError(),
+ "Error unqueuing stream buffers in method 'preLoadBuffers'" ) )
+ return false;
+ }
+ // restart the channel if it was previously playing:
+ if( playing )
+ {
+ al.alSourcePlay( ALSource[0] );
+ checkALError();
+ }
+
+ streamBuffers = IntBuffer.wrap( new int[bufferList.size()] );
+ al.alGenBuffers( bufferList.size(), streamBuffers );
+ if( errorCheck( checkALError(),
+ "Error generating stream buffers in method 'preLoadBuffers'" ) )
+ return false;
+
+ byte[] byteBuffer = null;
+ for( int i = 0; i < bufferList.size(); i++ )
+ {
+ byteBuffer = bufferList.get(i);
+ try
+ {
+ al.alBufferData( streamBuffers.get(i), ALformat,
+ ByteBuffer.wrap( byteBuffer, 0,
+ byteBuffer.length ),
+ byteBuffer.length,
+ sampleRate );
+ }
+ catch( Exception e )
+ {
+ errorMessage( "Error creating buffers in method " +
+ "'preLoadBuffers'" );
+ printStackTrace( e );
+ return false;
+ }
+ if( errorCheck( checkALError(),
+ "Error creating buffers in method 'preLoadBuffers'" ) )
+ return false;
+ }
+
+ try
+ {
+ al.alSourceQueueBuffers( ALSource[0], bufferList.size(),
+ streamBuffers );
+ }
+ catch( Exception e )
+ {
+ errorMessage( "Error queuing buffers in method 'preLoadBuffers'" );
+ printStackTrace( e );
+ return false;
+ }
+ if( errorCheck( checkALError(),
+ "Error queuing buffers in method 'preLoadBuffers'" ) )
+ return false;
+
+ al.alSourcePlay( ALSource[0] );
+ if( errorCheck( checkALError(),
+ "Error playing source in method 'preLoadBuffers'" ) )
+ return false;
+
+ // Success:
+ return true;
+ }
+
+/**
+ * Queues up a byte[] buffer of data to be streamed.
+ * @param buffer The next buffer to be played for a streaming source.
+ * @return False if an error occurred or if the channel is shutting down.
+ */
+ @Override
+ public boolean queueBuffer( byte[] buffer )
+ {
+ // Stream buffers can only be queued for streaming sources:
+ if( errorCheck( channelType != SoundSystemConfig.TYPE_STREAMING,
+ "Buffers may only be queued for streaming sources." ) )
+ return false;
+
+ IntBuffer intBuffer = IntBuffer.wrap( new int[1] );
+
+ al.alSourceUnqueueBuffers( ALSource[0], 1, intBuffer );
+ if( checkALError() )
+ return false;
+
+
+ if( al.alIsBuffer( intBuffer.get( 0 ) ) )
+ millisPreviouslyPlayed += millisInBuffer( intBuffer.get( 0 ) );
+ checkALError();
+
+ al.alBufferData( intBuffer.get(0), ALformat,
+ ByteBuffer.wrap( buffer, 0, buffer.length ),
+ buffer.length, sampleRate );
+ if( checkALError() )
+ return false;
+
+ al.alSourceQueueBuffers( ALSource[0], 1, intBuffer );
+ if( checkALError() )
+ return false;
+
+ return true;
+ }
+
+/**
+ * Feeds raw data to the stream.
+ * @param buffer Buffer containing raw audio data to stream.
+ * @return Number of prior buffers that have been processed., or -1 if error.
+ */
+ @Override
+ public int feedRawAudioData( byte[] buffer )
+ {
+ // Stream buffers can only be queued for streaming sources:
+ if( errorCheck( channelType != SoundSystemConfig.TYPE_STREAMING,
+ "Raw audio data can only be fed to streaming sources." ) )
+ return -1;
+
+ IntBuffer intBuffer;
+
+ int[] processed = new int[1];
+ al.alGetSourcei( ALSource[0], AL.AL_BUFFERS_PROCESSED, processed, 0 );
+
+ if( processed[0] > 0 )
+ {
+ intBuffer = IntBuffer.wrap( new int[processed[0]] );
+ al.alGenBuffers( processed[0], intBuffer );
+ if( errorCheck( checkALError(),
+ "Error clearing stream buffers in method 'feedRawAudioData'" ) )
+ return -1;
+ al.alSourceUnqueueBuffers( ALSource[0], processed[0],
+ intBuffer );
+ if( errorCheck( checkALError(),
+ "Error unqueuing stream buffers in method 'feedRawAudioData'" ) )
+ return -1;
+
+ int i;
+ intBuffer.rewind();
+ while( intBuffer.hasRemaining() )
+ {
+ i = intBuffer.get();
+ if( al.alIsBuffer( i ) )
+ {
+ millisPreviouslyPlayed += millisInBuffer( i );
+ }
+ checkALError();
+ }
+ al.alDeleteBuffers( processed[0], intBuffer );
+ checkALError();
+ }
+ intBuffer = IntBuffer.wrap( new int[1] );
+ al.alGenBuffers( 1, intBuffer );
+ if( errorCheck( checkALError(),
+ "Error generating stream buffers in method 'preLoadBuffers'" ) )
+ return -1;
+
+ al.alBufferData( intBuffer.get(0), ALformat,
+ ByteBuffer.wrap( buffer, 0, buffer.length ),
+ buffer.length, sampleRate );
+ if( checkALError() )
+ return -1;
+
+ al.alSourceQueueBuffers( ALSource[0], 1, intBuffer );
+ if( checkALError() )
+ return -1;
+
+ if( attachedSource != null && attachedSource.channel == this &&
+ attachedSource.active() )
+ {
+ // restart the channel if it was previously playing:
+ if( !playing() )
+ {
+ al.alSourcePlay( ALSource[0] );
+ checkALError();
+ }
+ }
+
+ return processed[0];
+ }
+
+/**
+ * Returns the number of milliseconds of audio contained in specified buffer.
+ * @return milliseconds, or 0 if unable to calculate.
+ */
+ public float millisInBuffer( int alBufferi )
+ {
+ int size[] = new int[1];
+ int channels[] = new int[1];
+ int bits[] = new int[1];
+ al.alGetBufferi( alBufferi, AL.AL_SIZE, size, 0 );
+ checkALError();
+ al.alGetBufferi( alBufferi, AL.AL_CHANNELS, channels, 0 );
+ checkALError();
+ al.alGetBufferi( alBufferi, AL.AL_BITS, bits, 0 );
+ checkALError();
+
+ return( ( (float) size[0] / (float) channels[0] / ( (float) bits[0] /
+ 8.0f ) / (float) sampleRate ) * 1000 );
+ }
+
+/**
+ * Calculates the number of milliseconds since the channel began playing.
+ * @return Milliseconds, or -1 if unable to calculate.
+ */
+ @Override
+ public float millisecondsPlayed()
+ {
+ int byteOffset[] = new int[1];
+ // get number of samples played in current buffer
+ al.alGetSourcei( ALSource[0], AL.AL_BYTE_OFFSET, byteOffset, 0 );
+
+ float bytesPerFrame = 1f;
+ switch( ALformat )
+ {
+ case AL.AL_FORMAT_MONO8 :
+ bytesPerFrame = 1f;
+ break;
+ case AL.AL_FORMAT_MONO16 :
+ bytesPerFrame = 2f;
+ break;
+ case AL.AL_FORMAT_STEREO8 :
+ bytesPerFrame = 2f;
+ break;
+ case AL.AL_FORMAT_STEREO16 :
+ bytesPerFrame = 4f;
+ break;
+ default :
+ break;
+ }
+
+ float offset = ( ( (float) byteOffset[0] / bytesPerFrame ) /
+ (float) sampleRate ) * 1000;
+
+ // add the milliseconds from stream-buffers that played previously
+ if( channelType == SoundSystemConfig.TYPE_STREAMING )
+ offset += millisPreviouslyPlayed;
+
+ // Return millis played:
+ return( offset );
+ }
+
+/**
+ * Returns the number of queued byte[] buffers that have finished playing.
+ * @return Number of buffers processed.
+ */
+ @Override
+ public int buffersProcessed()
+ {
+ // Only streaming sources process buffers:
+ if( channelType != SoundSystemConfig.TYPE_STREAMING )
+ return 0;
+
+ // determine how many have been processed:
+ int[] processed = new int[1];
+ al.alGetSourcei( ALSource[0], AL.AL_BUFFERS_PROCESSED, processed, 0 );
+
+ // Check for errors:
+ if( checkALError() )
+ return 0;
+
+ // Return how many were processed:
+ return processed[0];
+ }
+
+/**
+ * Dequeues all previously queued data.
+ */
+ @Override
+ public void flush()
+ {
+ // Only a streaming source can be flushed, because only streaming
+ // sources have queued buffers:
+ if( channelType != SoundSystemConfig.TYPE_STREAMING )
+ return;
+
+ // determine how many buffers have been queued:
+ int[] queued = new int[1];
+ al.alGetSourcei( ALSource[0], AL.AL_BUFFERS_QUEUED, queued, 0 );
+
+ // Check for errors:
+ if( checkALError() )
+ return;
+
+ IntBuffer intBuffer = IntBuffer.wrap( new int[1] );
+ while( queued[0] > 0 )
+ {
+ try
+ {
+ al.alSourceUnqueueBuffers( ALSource[0], 1, intBuffer );
+ }
+ catch( Exception e )
+ {
+ return;
+ }
+ if( checkALError() )
+ return;
+ queued[0]--;
+ }
+ millisPreviouslyPlayed = 0;
+ }
+
+/**
+ * Stops the channel, dequeues any queued data, and closes the channel.
+ */
+ @Override
+ public void close()
+ {
+ try
+ {
+ al.alSourceStop( ALSource[0] );
+ al.alGetError();
+ }
+ catch( Exception e )
+ {}
+
+ if( channelType == SoundSystemConfig.TYPE_STREAMING )
+ flush();
+ }
+
+/**
+ * Plays the currently attached normal source, opens this channel up for
+ * streaming, or resumes playback if this channel was paused.
+ */
+ @Override
+ public void play()
+ {
+ al.alSourcePlay( ALSource[0] );
+ checkALError();
+ }
+
+/**
+ * Temporarily stops playback for this channel.
+ */
+ @Override
+ public void pause()
+ {
+ al.alSourcePause( ALSource[0] );
+ checkALError();
+ }
+
+/**
+ * Stops playback for this channel and rewinds the attached source to the
+ * beginning.
+ */
+ @Override
+ public void stop()
+ {
+ al.alSourceStop( ALSource[0] );
+ if( !checkALError() )
+ millisPreviouslyPlayed = 0;
+ }
+
+/**
+ * Rewinds the attached source to the beginning. Stops the source if it was
+ * paused.
+ */
+ @Override
+ public void rewind()
+ {
+ // rewinding for streaming sources is handled elsewhere
+ if( channelType == SoundSystemConfig.TYPE_STREAMING )
+ return;
+
+ al.alSourceRewind( ALSource[0] );
+ if( !checkALError() )
+ millisPreviouslyPlayed = 0;
+ }
+
+
+/**
+ * Used to determine if a channel is actively playing a source. This method
+ * will return false if the channel is paused or stopped and when no data is
+ * queued to be streamed.
+ * @return True if this channel is playing a source.
+ */
+ @Override
+ public boolean playing()
+ {
+ int[] state = new int[1];
+ al.alGetSourcei( ALSource[0], AL.AL_SOURCE_STATE, state, 0 );
+ if( checkALError() )
+ return false;
+
+ return( state[0] == AL.AL_PLAYING );
+ }
+
+/**
+ * Checks for OpenAL errors, and prints a message if there is an error.
+ * @return True if there was an error, False if not.
+ */
+ private boolean checkALError()
+ {
+ switch( al.alGetError() )
+ {
+ case AL.AL_NO_ERROR:
+ return false;
+ case AL.AL_INVALID_NAME:
+ errorMessage( "Invalid name parameter." );
+ return true;
+ case AL.AL_INVALID_ENUM:
+ errorMessage( "Invalid parameter." );
+ return true;
+ case AL.AL_INVALID_VALUE:
+ errorMessage( "Invalid enumerated parameter value." );
+ return true;
+ case AL.AL_INVALID_OPERATION:
+ errorMessage( "Illegal call." );
+ return true;
+ case AL.AL_OUT_OF_MEMORY:
+ errorMessage( "Unable to allocate memory." );
+ return true;
+ default:
+ errorMessage( "An unrecognized error occurred." );
+ return true;
+ }
+ }
+}
diff --git a/ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/LibraryJOAL.java b/ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/LibraryJOAL.java
new file mode 100644
index 0000000..95b2d6d
--- /dev/null
+++ b/ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/LibraryJOAL.java
@@ -0,0 +1,1112 @@
+/**
+ * Copyright (c) 2008-2014 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+package com.ardor3d.audio.joal;
+
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+import com.ardor3d.audio.sampled.AudioFormat;
+
+// From the joal library, https://jogamp.org/joal/www/
+import com.jogamp.openal.AL;
+import com.jogamp.openal.ALException;
+import com.jogamp.openal.ALFactory;
+import com.jogamp.openal.util.ALut;
+
+import com.ardor3d.audio.Channel;
+import com.ardor3d.audio.FilenameURL;
+import com.ardor3d.audio.ICodec;
+import com.ardor3d.audio.Library;
+import com.ardor3d.audio.ListenerData;
+import com.ardor3d.audio.SoundBuffer;
+import com.ardor3d.audio.SoundSystemConfig;
+import com.ardor3d.audio.SoundSystemException;
+import com.ardor3d.audio.Source;
+
+/**
+ * The LibraryJOAL class interfaces the JOAL binding of OpenAL.
+ *<b><br><br>
+ * This software is based on or using the JOAL Library available from
+ * https://jogamp.org/joal/www/
+ *</b><br><br>
+ * JOAL License:
+ *<br><i>
+ * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+ *<br>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *<br>
+ * -Redistribution of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *<br>
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *<br>
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * <br>
+ * This software is provided "AS IS," without a warranty of any kind.
+ * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
+ * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS
+ * LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A
+ * RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+ * IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT
+ * OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR
+ * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS
+ * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *<br>
+ * You acknowledge that this software is not designed or intended for use in the
+ * design, construction, operation or maintenance of any nuclear facility.
+ * <br><br><br></i>
+ *<b><i> SoundSystem LibraryJOAL License:</b></i><br><b><br>
+ *<b>
+ * You are free to use this library for any purpose, commercial or otherwise.
+ * You may modify this library or source code, and distribute it any way you
+ * like, provided the following conditions are met:
+ *<br>
+ * 1) You must abide by the conditions of the aforementioned JOAL License.
+ *<br>
+ * 2) You may not falsely claim to be the author of this library or any
+ * unmodified portion of it.
+ *<br>
+ * 3) You may not copyright this library or a modified version of it and then
+ * sue me for copyright infringement.
+ *<br>
+ * 4) If you modify the source code, you must clearly document the changes
+ * made before redistributing the modified source code, so other users know
+ * it is not the original code.
+ *<br>
+ * 5) You are not required to give me credit for this library in any derived
+ * work, but if you do, you must also mention my website:
+ * http://www.paulscode.com
+ *<br>
+ * 6) I the author will not be responsible for any damages (physical,
+ * financial, or otherwise) caused by the use if this library or any part
+ * of it.
+ *<br>
+ * 7) I the author do not guarantee, warrant, or make any representations,
+ * either expressed or implied, regarding the use of this library or any
+ * part of it.
+ * <br><br>
+ * Author: Paul Lamb
+ * <br>
+ * http://www.paulscode.com
+ * </b>
+ */
+public class LibraryJOAL extends Library
+{
+/**
+ * Used to return a current value from one of the synchronized
+ * boolean-interface methods.
+ */
+ private static final boolean GET = false;
+/**
+ * Used to set the value in one of the synchronized boolean-interface methods.
+ */
+ private static final boolean SET = true;
+/**
+ * Used when a parameter for one of the synchronized boolean-interface methods
+ * is not aplicable.
+ */
+ private static final boolean XXX = false;
+
+/**
+ * Handle for accessing OpenAL.
+ */
+ private static AL al = null;
+
+/**
+ * Map containing OpenAL identifiers for sound buffers.
+ */
+ private HashMap<String, int[]> ALBufferMap = null;
+
+/**
+ * Whether or not the AL_PITCH control is supported.
+ */
+ private static boolean alPitchSupported = true;
+
+/**
+ * Constructor: Instantiates the source map, buffer map and listener
+ * information. Also sets the library type to
+ * SoundSystemConfig.LIBRARY_OPENAL
+ */
+ public LibraryJOAL() throws SoundSystemException
+ {
+ super();
+ ALBufferMap = new HashMap<String, int[]>();
+ reverseByteOrder = true;
+ }
+
+/**
+ * Initializes OpenAL, creates the listener, and grabs up audio channels.
+ */
+ @Override
+ public void init() throws SoundSystemException
+ {
+ boolean errors = false; // set to 'true' if error(s) occur:
+
+ try
+ {
+ ALut.alutInit(); // creates an OpenAL context.
+ // Grab a handle to use for accessing OpenAL:
+ al = ALFactory.getAL();
+ errors = checkALError();
+ }
+ catch( ALException e )
+ {
+ // There was an exception
+ errorMessage( "Unable to initialize OpenAL. Probable cause: " +
+ "OpenAL not supported." );
+ printStackTrace( e );
+ throw new LibraryJOAL.Exception( e.getMessage(),
+ LibraryJOAL.Exception.CREATE );
+ }
+
+ // Let user know if the library loaded properly
+ if( errors )
+ importantMessage( "OpenAL did not initialize properly!" );
+ else
+ message( "OpenAL initialized." );
+
+ // Pass the listener info to the sound system, and check for errors:
+ al.alListener3f( AL.AL_POSITION, listener.position.getXf(),
+ listener.position.getYf(), listener.position.getZf() );
+ errors = checkALError() || errors;
+
+ al.alListenerfv( AL.AL_ORIENTATION, new float[]{ listener.lookAt.getXf(),
+ listener.lookAt.getYf(),
+ listener.lookAt.getZf(),
+ listener.up.getXf(),
+ listener.up.getYf(),
+ listener.up.getZf() }, 0 );
+ errors = checkALError() || errors;
+
+ al.alListener3f( AL.AL_VELOCITY, listener.velocity.getXf(),
+ listener.velocity.getYf(), listener.velocity.getZf() );
+ errors = checkALError() || errors;
+
+ al.alDopplerFactor( SoundSystemConfig.getDopplerFactor() );
+ errors = checkALError() || errors;
+
+ al.alDopplerVelocity( SoundSystemConfig.getDopplerVelocity() );
+ errors = checkALError() || errors;
+
+ // Let user know what caused the above error messages:
+ if( errors )
+ {
+ importantMessage( "OpenAL did not initialize properly!" );
+ throw new LibraryJOAL.Exception( "Problem encountered while " +
+ "loading OpenAL or creating the " +
+ "listener. Probably cause: " +
+ "OpenAL not supported",
+ LibraryJOAL.Exception.CREATE );
+ }
+
+ super.init();
+
+ // Check if we can use the AL_PITCH control:
+ ChannelJOAL channel = (ChannelJOAL) normalChannels.get( 1 );
+ try
+ {
+ al.alSourcef( channel.ALSource[0], AL.AL_PITCH, 1.0f );
+ if( checkALError() )
+ {
+ alPitchSupported( SET, false );
+ throw new LibraryJOAL.Exception( "OpenAL: AL_PITCH not " +
+ "supported.", LibraryJOAL.Exception.NO_AL_PITCH );
+ }
+ else
+ {
+ alPitchSupported( SET, true );
+ }
+ }
+ catch( Exception e )
+ {
+ alPitchSupported( SET, false );
+ throw new LibraryJOAL.Exception( "OpenAL: AL_PITCH not " +
+ "supported.", LibraryJOAL.Exception.NO_AL_PITCH );
+ }
+ }
+
+/**
+ * Checks if the OpenAL library type is compatible.
+ * @return True or false.
+ */
+ public static boolean libraryCompatible()
+ {
+ try
+ {
+ al = ALFactory.getAL();
+ if( al != null )
+ return true;
+ }
+ catch( ALException e )
+ {}
+
+ boolean compatible = true;
+
+ try
+ {
+ ALut.alutInit();
+ }
+ catch( ALException e )
+ {
+ compatible = false;
+ }
+
+ try
+ {
+ ALut.alutExit();
+ al = null;
+ }
+ catch( java.lang.Exception e )
+ {}
+
+ return compatible;
+ }
+
+/**
+ * Creates a new channel of the specified type (normal or streaming). Possible
+ * values for channel type can be found in the
+ * {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} class.
+ * @param type Type of channel.
+ */
+ @Override
+ protected Channel createChannel( int type )
+ {
+ ChannelJOAL channel;
+
+ int[] ALSource = new int[1];
+ try
+ {
+ al.alGenSources( 1, ALSource, 0 );
+ }
+ catch( java.lang.Exception e )
+ {
+ al.alGetError();
+ return null; // no more voices left
+ }
+
+ if( al.alGetError() != AL.AL_NO_ERROR )
+ return null;
+
+ channel = new ChannelJOAL( type, ALSource );
+ return channel;
+ }
+
+ /**
+ * Stops all sources, shuts down OpenAL, and removes references to all
+ * instantiated objects.
+ */
+ @Override
+ public void cleanup()
+ {
+ super.cleanup();
+
+ Set<String> keys = bufferMap.keySet();
+ Iterator<String> iter = keys.iterator();
+ String filename;
+ int[] buffer;
+
+ // loop through and clear all sound buffers:
+ while( iter.hasNext() )
+ {
+ filename = iter.next();
+ buffer = ALBufferMap.get( filename );
+ if( buffer != null )
+ {
+ al.alDeleteBuffers( 1, buffer, 0 );
+ checkALError();
+ buffer = null;
+ }
+ }
+
+ try
+ {
+ ALut.alutExit();
+ al = null;
+ }
+ catch( java.lang.Exception e )
+ {}
+
+ bufferMap.clear();
+ bufferMap = null;
+ }
+
+/**
+ * Pre-loads a sound into memory.
+ * @param filenameURL Filename/URL of the sound file to load.
+ * @return True if the sound loaded properly.
+ */
+ @Override
+ public boolean loadSound( FilenameURL filenameURL )
+ {
+ // Make sure the buffer map exists:
+ if( bufferMap == null )
+ {
+ bufferMap = new HashMap<String, SoundBuffer>();
+ importantMessage( "Buffer Map was null in method 'loadSound'" );
+ }
+ // Make sure the OpenAL buffer map exists:
+ if( ALBufferMap == null )
+ {
+ ALBufferMap = new HashMap<String, int[]>();
+ importantMessage( "Open AL Buffer Map was null in method" +
+ "'loadSound'" );
+ }
+
+ // make sure they gave us a filename:
+ if( errorCheck( filenameURL == null,
+ "Filename/URL not specified in method 'loadSound'" ) )
+ return false;
+
+ // check if it is already loaded:
+ if( bufferMap.get( filenameURL.getFilename() ) != null )
+ return true;
+
+ ICodec codec = SoundSystemConfig.getCodec( filenameURL.getFilename() );
+ if( errorCheck( codec == null, "No codec found for file '" +
+ filenameURL.getFilename() +
+ "' in method 'loadSound'" ) )
+ return false;
+ codec.reverseByteOrder( true );
+
+ URL url = filenameURL.getURL();
+ if( errorCheck( url == null, "Unable to open file '" +
+ filenameURL.getFilename() +
+ "' in method 'loadSound'" ) )
+ return false;
+
+ codec.initialize( url );
+ SoundBuffer buffer = codec.readAll();
+ codec.cleanup();
+ codec = null;
+ if( errorCheck( buffer == null,
+ "Sound buffer null in method 'loadSound'" ) )
+ return false;
+
+ bufferMap.put( filenameURL.getFilename(), buffer );
+
+ AudioFormat audioFormat = buffer.audioFormat;
+ int soundFormat = 0;
+ if( audioFormat.getChannels() == 1 )
+ {
+ if( audioFormat.getSampleSizeInBits() == 8 )
+ {
+ soundFormat = AL.AL_FORMAT_MONO8;
+ }
+ else if( audioFormat.getSampleSizeInBits() == 16 )
+ {
+ soundFormat = AL.AL_FORMAT_MONO16;
+ }
+ else
+ {
+ errorMessage( "Illegal sample size in method 'loadSound'" );
+ return false;
+ }
+ }
+ else if( audioFormat.getChannels() == 2 )
+ {
+ if( audioFormat.getSampleSizeInBits() == 8 )
+ {
+ soundFormat = AL.AL_FORMAT_STEREO8;
+ }
+ else if( audioFormat.getSampleSizeInBits() == 16 )
+ {
+ soundFormat = AL.AL_FORMAT_STEREO16;
+ }
+ else
+ {
+ errorMessage( "Illegal sample size in method 'loadSound'" );
+ return false;
+ }
+ }
+ else
+ {
+ errorMessage( "File neither mono nor stereo in method " +
+ "'loadSound'" );
+ return false;
+ }
+
+ int[] intBuffer = new int[1];
+ al.alGenBuffers( 1, intBuffer, 0 );
+ if( errorCheck( checkALError(),
+ "alGenBuffers error when loading " +
+ filenameURL.getFilename() ) )
+ return false;
+
+ al.alBufferData( intBuffer[0], soundFormat,
+ ByteBuffer.wrap( buffer.audioData ),
+ buffer.audioData.length,
+ (int) audioFormat.getSampleRate() );
+ if( errorCheck( checkALError(),
+ "alBufferData error when loading " +
+ filenameURL.getFilename() ) )
+
+
+ if( errorCheck( intBuffer == null,
+ "Sound buffer was not created for " +
+ filenameURL.getFilename() ) )
+ return false;
+
+ ALBufferMap.put( filenameURL.getFilename(), intBuffer );
+
+ return true;
+ }
+
+/**
+ * Saves the specified sample data, under the specified identifier. This
+ * identifier can be later used in place of 'filename' parameters to reference
+ * the sample data.
+ * @param buffer the sample data and audio format to save.
+ * @param identifier What to call the sample.
+ * @return True if there weren't any problems.
+ */
+ @Override
+ public boolean loadSound( SoundBuffer buffer, String identifier )
+ {
+ // Make sure the buffer map exists:
+ if( bufferMap == null )
+ {
+ bufferMap = new HashMap<String, SoundBuffer>();
+ importantMessage( "Buffer Map was null in method 'loadSound'" );
+ }
+ // Make sure the OpenAL buffer map exists:
+ if( ALBufferMap == null )
+ {
+ ALBufferMap = new HashMap<String, int[]>();
+ importantMessage( "Open AL Buffer Map was null in method" +
+ "'loadSound'" );
+ }
+
+ // make sure they gave us an identifier:
+ if( errorCheck( identifier == null,
+ "Identifier not specified in method 'loadSound'" ) )
+ return false;
+
+ // check if it is already loaded:
+ if( bufferMap.get( identifier ) != null )
+ return true;
+
+ if( errorCheck( buffer == null,
+ "Sound buffer null in method 'loadSound'" ) )
+ return false;
+
+ bufferMap.put( identifier, buffer );
+
+ AudioFormat audioFormat = buffer.audioFormat;
+ int soundFormat = 0;
+ if( audioFormat.getChannels() == 1 )
+ {
+ if( audioFormat.getSampleSizeInBits() == 8 )
+ {
+ soundFormat = AL.AL_FORMAT_MONO8;
+ }
+ else if( audioFormat.getSampleSizeInBits() == 16 )
+ {
+ soundFormat = AL.AL_FORMAT_MONO16;
+ }
+ else
+ {
+ errorMessage( "Illegal sample size in method 'loadSound'" );
+ return false;
+ }
+ }
+ else if( audioFormat.getChannels() == 2 )
+ {
+ if( audioFormat.getSampleSizeInBits() == 8 )
+ {
+ soundFormat = AL.AL_FORMAT_STEREO8;
+ }
+ else if( audioFormat.getSampleSizeInBits() == 16 )
+ {
+ soundFormat = AL.AL_FORMAT_STEREO16;
+ }
+ else
+ {
+ errorMessage( "Illegal sample size in method 'loadSound'" );
+ return false;
+ }
+ }
+ else
+ {
+ errorMessage( "File neither mono nor stereo in method " +
+ "'loadSound'" );
+ return false;
+ }
+
+ int[] intBuffer = new int[1];
+ al.alGenBuffers( 1, intBuffer, 0 );
+ if( errorCheck( checkALError(),
+ "alGenBuffers error when saving " +
+ identifier ) )
+ return false;
+
+ al.alBufferData( intBuffer[0], soundFormat,
+ ByteBuffer.wrap( buffer.audioData ),
+ buffer.audioData.length,
+ (int) audioFormat.getSampleRate() );
+ if( errorCheck( checkALError(),
+ "alBufferData error when saving " +
+ identifier ) )
+
+
+ if( errorCheck( intBuffer == null,
+ "Sound buffer was not created for " +
+ identifier ) )
+ return false;
+
+ ALBufferMap.put( identifier, intBuffer );
+
+ return true;
+ }
+
+/**
+ * Removes a pre-loaded sound from memory. This is a good method to use for
+ * freeing up memory after a large sound file is no longer needed. NOTE: the
+ * source will remain in memory after this method has been called, for as long
+ * as the sound is attached to an existing source.
+ * @param filename Filename/identifier of the sound file to unload.
+ */
+ @Override
+ public void unloadSound( String filename )
+ {
+ ALBufferMap.remove( filename );
+ super.unloadSound( filename );
+ }
+
+ /**
+ * Sets the overall volume to the specified value, affecting all sources.
+ * @param value New volume, float value ( 0.0f - 1.0f ).
+ */
+ @Override
+ public void setMasterVolume( float value )
+ {
+ super.setMasterVolume( value );
+
+ al.alListenerf( AL.AL_GAIN, value );
+ checkALError();
+ }
+
+/**
+ * Creates a new source and places it into the source map.
+ * @param priority Setting this to true will prevent other sounds from overriding this one.
+ * @param toStream Setting this to true will load the sound in pieces rather than all at once.
+ * @param toLoop Should this source loop, or play only once.
+ * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.
+ * @param filenameURL Filename/URL of the sound file to play at this source.
+ * @param x X position for this source.
+ * @param y Y position for this source.
+ * @param z Z position for this source.
+ * @param attModel Attenuation model to use.
+ * @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel".
+ */
+ @Override
+ public void newSource( boolean priority, boolean toStream, boolean toLoop,
+ String sourcename, FilenameURL filenameURL, float x,
+ float y, float z, int attModel,
+ float distOrRoll )
+ {
+ int[] myBuffer = null;
+ if( !toStream )
+ {
+ // Grab the sound buffer for this file:
+ myBuffer = ALBufferMap.get( filenameURL.getFilename() );
+
+ // if not found, try loading it:
+ if( myBuffer == null )
+ {
+ if( !loadSound( filenameURL ) )
+ {
+ errorMessage( "Source '" + sourcename + "' was not created "
+ + "because an error occurred while loading "
+ + filenameURL.getFilename() );
+ return;
+ }
+ }
+
+ // try and grab the sound buffer again:
+ myBuffer = ALBufferMap.get( filenameURL.getFilename() );
+ // see if it was there this time:
+ if( myBuffer == null )
+ {
+ errorMessage( "Source '" + sourcename + "' was not created "
+ + "because a sound buffer was not found for "
+ + filenameURL.getFilename() );
+ return;
+ }
+ }
+ SoundBuffer buffer = null;
+
+ if( !toStream )
+ {
+ // Grab the audio data for this file:
+ buffer = bufferMap.get( filenameURL.getFilename() );
+ // if not found, try loading it:
+ if( buffer == null )
+ {
+ if( !loadSound( filenameURL ) )
+ {
+ errorMessage( "Source '" + sourcename + "' was not created "
+ + "because an error occurred while loading "
+ + filenameURL.getFilename() );
+ return;
+ }
+ }
+ // try and grab the sound buffer again:
+ buffer = bufferMap.get( filenameURL.getFilename() );
+ // see if it was there this time:
+ if( buffer == null )
+ {
+ errorMessage( "Source '" + sourcename + "' was not created "
+ + "because audio data was not found for "
+ + filenameURL.getFilename() );
+ return;
+ }
+ }
+
+ sourceMap.put( sourcename,
+ new SourceJOAL( listener.position, myBuffer, priority,
+ toStream, toLoop, sourcename,
+ filenameURL, buffer, x, y, z, attModel,
+ distOrRoll, false ) );
+ }
+
+/**
+ * Opens a direct line for streaming audio data.
+ * @param audioFormat Format that the data will be in.
+ * @param priority Setting this to true will prevent other sounds from overriding this one.
+ * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.
+ * @param x X position for this source.
+ * @param y Y position for this source.
+ * @param z Z position for this source.
+ * @param attModel Attenuation model to use.
+ * @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel".
+ */
+ @Override
+ public void rawDataStream( AudioFormat audioFormat, boolean priority,
+ String sourcename, float x, float y,
+ float z, int attModel, float distOrRoll )
+ {
+ sourceMap.put( sourcename,
+ new SourceJOAL( listener.position, audioFormat, priority,
+ sourcename, x, y, z, attModel,
+ distOrRoll ) );
+ }
+
+/**
+ * Creates and immediately plays a new source.
+ * @param priority Setting this to true will prevent other sounds from overriding this one.
+ * @param toStream Setting this to true will load the sound in pieces rather than all at once.
+ * @param toLoop Should this source loop, or play only once.
+ * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.
+ * @param filenameURL Filename/URL of the sound file to play at this source.
+ * @param x X position for this source.
+ * @param y Y position for this source.
+ * @param z Z position for this source.
+ * @param attModel Attenuation model to use.
+ * @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel".
+ * @param temporary Whether or not this source should be removed after it finishes playing.
+ */
+ @Override
+ public void quickPlay( boolean priority, boolean toStream, boolean toLoop,
+ String sourcename, FilenameURL filenameURL, float x,
+ float y, float z, int attModel, float distOrRoll,
+ boolean temporary )
+ {
+ int[] myBuffer = null;
+ if( !toStream )
+ {
+ // Grab the sound buffer for this file:
+ myBuffer = ALBufferMap.get( filenameURL.getFilename() );
+ // if not found, try loading it:
+ if( myBuffer == null )
+ loadSound( filenameURL );
+ // try and grab the sound buffer again:
+ myBuffer = ALBufferMap.get( filenameURL.getFilename() );
+ // see if it was there this time:
+ if( myBuffer == null )
+ {
+ errorMessage( "Sound buffer was not created for " +
+ filenameURL.getFilename() );
+ return;
+ }
+ }
+
+ SoundBuffer buffer = null;
+
+ if( !toStream )
+ {
+ // Grab the sound buffer for this file:
+ buffer = bufferMap.get( filenameURL.getFilename() );
+ // if not found, try loading it:
+ if( buffer == null )
+ {
+ if( !loadSound( filenameURL ) )
+ {
+ errorMessage( "Source '" + sourcename + "' was not created "
+ + "because an error occurred while loading "
+ + filenameURL.getFilename() );
+ return;
+ }
+ }
+ // try and grab the sound buffer again:
+ buffer = bufferMap.get( filenameURL.getFilename() );
+ // see if it was there this time:
+ if( buffer == null )
+ {
+ errorMessage( "Source '" + sourcename + "' was not created "
+ + "because audio data was not found for "
+ + filenameURL.getFilename() );
+ return;
+ }
+ }
+
+ sourceMap.put( sourcename,
+ new SourceJOAL( listener.position, myBuffer, priority,
+ toStream, toLoop, sourcename,
+ filenameURL, buffer, x, y, z, attModel,
+ distOrRoll, temporary ) );
+ }
+
+/**
+ * Creates sources based on the source map provided.
+ * @param srcMap Sources to copy.
+ */
+ @Override
+ public void copySources( HashMap<String, Source> srcMap )
+ {
+ if( srcMap == null )
+ return;
+ Set<String> keys = srcMap.keySet();
+ Iterator<String> iter = keys.iterator();
+ String sourcename;
+ Source source;
+
+ // Make sure the buffer map exists:
+ if( bufferMap == null )
+ {
+ bufferMap = new HashMap<String, SoundBuffer>();
+ importantMessage( "Buffer Map was null in method 'copySources'" );
+ }
+ // Make sure the OpenAL buffer map exists:
+ if( ALBufferMap == null )
+ {
+ ALBufferMap = new HashMap<String, int[]>();
+ importantMessage( "Open AL Buffer Map was null in method" +
+ "'copySources'" );
+ }
+
+ // remove any existing sources before starting:
+ sourceMap.clear();
+
+ SoundBuffer buffer;
+ // loop through and copy all the sources:
+ while( iter.hasNext() )
+ {
+ sourcename = iter.next();
+ source = srcMap.get( sourcename );
+ if( source != null )
+ {
+ buffer = null;
+ if( !source.toStream )
+ {
+ loadSound( source.filenameURL );
+ buffer = bufferMap.get( source.filenameURL.getFilename() );
+ }
+ if( source.toStream || buffer != null )
+ sourceMap.put( sourcename, new SourceJOAL(
+ listener.position,
+ ALBufferMap.get(
+ source.filenameURL.getFilename() ),
+ source, buffer ) );
+ }
+ }
+ }
+
+/**
+ * Changes the listener's position.
+ * @param x Destination X coordinate.
+ * @param y Destination Y coordinate.
+ * @param z Destination Z coordinate.
+ */
+ @Override
+ public void setListenerPosition( float x, float y, float z )
+ {
+ super.setListenerPosition( x, y, z );
+
+ // Update OpenAL listener position:
+ al.alListener3f( AL.AL_POSITION, x, y, z );
+ // Check for errors:
+ checkALError();
+ }
+
+/**
+ * Changes the listeners orientation to the specified 'angle' radians
+ * counterclockwise around the y-Axis.
+ * @param angle Radians.
+ */
+ @Override
+ public void setListenerAngle( float angle )
+ {
+ super.setListenerAngle( angle );
+
+ // Update OpenAL listener orientation:
+ al.alListenerfv( AL.AL_ORIENTATION, new float[]{ listener.lookAt.getXf(),
+ listener.lookAt.getYf(),
+ listener.lookAt.getZf(),
+ listener.up.getXf(),
+ listener.up.getYf(),
+ listener.up.getZf() }, 0 );
+ // Check for errors:
+ checkALError();
+ }
+
+/**
+ * Changes the listeners orientation using the specified coordinates.
+ * @param lookX X element of the look-at direction.
+ * @param lookY Y element of the look-at direction.
+ * @param lookZ Z element of the look-at direction.
+ * @param upX X element of the up direction.
+ * @param upY Y element of the up direction.
+ * @param upZ Z element of the up direction.
+ */
+ @Override
+ public void setListenerOrientation( float lookX, float lookY, float lookZ,
+ float upX, float upY, float upZ )
+ {
+ super.setListenerOrientation( lookX, lookY, lookZ, upX, upY, upZ );
+
+ // Update OpenAL listener orientation:
+ al.alListenerfv( AL.AL_ORIENTATION, new float[]{ listener.lookAt.getXf(),
+ listener.lookAt.getYf(),
+ listener.lookAt.getZf(),
+ listener.up.getXf(),
+ listener.up.getYf(),
+ listener.up.getZf() }, 0 );
+ // Check for errors:
+ checkALError();
+ }
+
+/**
+ * Changes the listeners position and orientation using the specified listener
+ * data.
+ * @param l Listener data to use.
+ */
+ @Override
+ public void setListenerData( ListenerData l )
+ {
+ super.setListenerData( l );
+
+ // Pass the listener info to the sound system, and check for errors:
+ al.alListener3f( AL.AL_POSITION, listener.position.getXf(),
+ listener.position.getYf(), listener.position.getZf() );
+ checkALError();
+ al.alListenerfv( AL.AL_ORIENTATION, new float[]{ listener.lookAt.getXf(),
+ listener.lookAt.getYf(),
+ listener.lookAt.getZf(),
+ listener.up.getXf(),
+ listener.up.getYf(),
+ listener.up.getZf() }, 0 );
+ checkALError();
+ al.alListener3f( AL.AL_VELOCITY, listener.velocity.getXf(),
+ listener.velocity.getYf(), listener.velocity.getZf() );
+ checkALError();
+ }
+
+/**
+ * Sets the listener's velocity, for use in Doppler effect.
+ * @param x Velocity along world x-axis.
+ * @param y Velocity along world y-axis.
+ * @param z Velocity along world z-axis.
+ */
+ @Override
+ public void setListenerVelocity( float x, float y, float z )
+ {
+ super.setListenerVelocity( x, y, z );
+
+ al.alListener3f( AL.AL_VELOCITY, listener.velocity.getXf(),
+ listener.velocity.getYf(), listener.velocity.getZf() );
+ checkALError();
+ }
+
+/**
+ * The Doppler parameters have changed.
+ */
+ @Override
+ public void dopplerChanged()
+ {
+ super.dopplerChanged();
+
+ al.alDopplerFactor( SoundSystemConfig.getDopplerFactor() );
+ checkALError();
+ al.alDopplerVelocity( SoundSystemConfig.getDopplerVelocity() );
+ checkALError();
+ }
+
+/**
+ * Returns a handle to OpenAL, or null if OpenAL is not initialized.
+ * @return Used to interface with OpenAL functions.
+ */
+ public static AL getAL()
+ {
+ return al;
+ }
+
+/**
+ * Checks for OpenAL errors, and prints a message if there is an error.
+ * @return True if there was an error, False if not.
+ */
+ private boolean checkALError()
+ {
+ switch( al.alGetError() )
+ {
+ case AL.AL_NO_ERROR:
+ return false;
+ case AL.AL_INVALID_NAME:
+ errorMessage( "Invalid name parameter." );
+ return true;
+ case AL.AL_INVALID_ENUM:
+ errorMessage( "Invalid parameter." );
+ return true;
+ case AL.AL_INVALID_VALUE:
+ errorMessage( "Invalid enumerated parameter value." );
+ return true;
+ case AL.AL_INVALID_OPERATION:
+ errorMessage( "Illegal call." );
+ return true;
+ case AL.AL_OUT_OF_MEMORY:
+ errorMessage( "Unable to allocate memory." );
+ return true;
+ default:
+ errorMessage( "An unrecognized error occurred." );
+ return true;
+ }
+ }
+
+/**
+ * Whether or not the AL_PITCH control is supported.
+ * @return True if AL_PITCH is supported.
+ */
+ public static boolean alPitchSupported()
+ {
+ return alPitchSupported( GET, XXX );
+ }
+/**
+ * Sets or returns the value of boolean 'alPitchSupported'.
+ * @param action Action to perform (GET or SET).
+ * @param value New value if action is SET, otherwise XXX.
+ * @return value of boolean 'alPitchSupported'.
+ */
+ private static synchronized boolean alPitchSupported( boolean action,
+ boolean value )
+ {
+ if( action == SET )
+ alPitchSupported = value;
+ return alPitchSupported;
+ }
+
+/**
+ * Returns the short title of this library type.
+ * @return A short title.
+ */
+ public static String getTitle()
+ {
+ return "JOAL";
+ }
+
+/**
+ * Returns a longer description of this library type.
+ * @return A longer description.
+ */
+ public static String getDescription()
+ {
+ return "The JOAL binding of OpenAL. For more information, see " +
+ "https://jogamp.org/joal/www/";
+ }
+
+/**
+ * Returns the name of the class.
+ * @return "Library" + library title.
+ */
+ @Override
+ public String getClassName()
+ {
+ return "LibraryJOAL";
+ }
+
+/**
+ * The LibraryJOAL.Exception class provides library-specific error information.
+ */
+ public static class Exception extends SoundSystemException
+ {
+ private static final long serialVersionUID = -7559402481189210355L;
+ /**
+ * Global identifier for an exception during AL.create(). Probably means
+ * that OpenAL is not supported.
+ */
+ public static final int CREATE = 101;
+ /**
+ * Global identifier for an invalid name parameter in OpenAL.
+ */
+ public static final int INVALID_NAME = 102;
+ /**
+ * Global identifier for an invalid parameter in OpenAL.
+ */
+ public static final int INVALID_ENUM = 103;
+ /**
+ * Global identifier for an invalid enumerated parameter value in OpenAL.
+ */
+ public static final int INVALID_VALUE = 104;
+ /**
+ * Global identifier for an illegal call in OpenAL.
+ */
+ public static final int INVALID_OPERATION = 105;
+ /**
+ * Global identifier for OpenAL out of memory.
+ */
+ public static final int OUT_OF_MEMORY = 106;
+ /**
+ * Global identifier for an exception while creating the OpenAL Listener.
+ */
+ public static final int LISTENER = 107;
+ /**
+ * Global identifier for OpenAL AL_PITCH not supported.
+ */
+ public static final int NO_AL_PITCH = 108;
+
+ /**
+ * Constructor: Generates a standard "unknown error" exception with the
+ * specified message.
+ * @param message A brief description of the problem that occurred.
+ */
+ public Exception( String message )
+ {
+ super( message );
+ }
+
+ /**
+ * Constructor: Generates an exception of the specified type, with the
+ * specified message.
+ * @param message A brief description of the problem that occurred.
+ * @param type Identifier indicating they type of error.
+ */
+ public Exception( String message, int type )
+ {
+ super( message, type );
+ }
+ }
+}
diff --git a/ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/SourceJOAL.java b/ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/SourceJOAL.java
new file mode 100644
index 0000000..34bc63b
--- /dev/null
+++ b/ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/SourceJOAL.java
@@ -0,0 +1,769 @@
+/**
+ * Copyright (c) 2008-2014 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+package com.ardor3d.audio.joal;
+
+import java.util.LinkedList;
+import com.ardor3d.audio.sampled.AudioFormat;
+
+// From the joal library, https://jogamp.org/joal/www/
+import com.jogamp.openal.AL;
+
+import com.ardor3d.audio.Channel;
+import com.ardor3d.audio.FilenameURL;
+import com.ardor3d.audio.Source;
+import com.ardor3d.audio.SoundBuffer;
+import com.ardor3d.audio.SoundSystemConfig;
+import com.ardor3d.math.Vector3;
+
+/**
+ * The SourceJOAL class provides an interface to the JOAL binding of OpenAL.
+ *<b><br><br>
+ * This software is based on or using the JOAL Library available from
+ * https://jogamp.org/joal/www/
+ *</b><br><br>
+ * JOAL License:
+ *<br><i>
+ * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+ *<br>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *<br>
+ * -Redistribution of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *<br>
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *<br>
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * <br>
+ * This software is provided "AS IS," without a warranty of any kind.
+ * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
+ * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS
+ * LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A
+ * RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+ * IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT
+ * OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR
+ * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS
+ * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *<br>
+ * You acknowledge that this software is not designed or intended for use in the
+ * design, construction, operation or maintenance of any nuclear facility.
+ * <br><br><br></i>
+ *<b><i> SoundSystem LibraryJOAL License:</b></i><br><b><br>
+ *<b>
+ * You are free to use this library for any purpose, commercial or otherwise.
+ * You may modify this library or source code, and distribute it any way you
+ * like, provided the following conditions are met:
+ *<br>
+ * 1) You must abide by the conditions of the aforementioned JOAL License.
+ *<br>
+ * 2) You may not falsely claim to be the author of this library or any
+ * unmodified portion of it.
+ *<br>
+ * 3) You may not copyright this library or a modified version of it and then
+ * sue me for copyright infringement.
+ *<br>
+ * 4) If you modify the source code, you must clearly document the changes
+ * made before redistributing the modified source code, so other users know
+ * it is not the original code.
+ *<br>
+ * 5) You are not required to give me credit for this library in any derived
+ * work, but if you do, you must also mention my website:
+ * http://www.paulscode.com
+ *<br>
+ * 6) I the author will not be responsible for any damages (physical,
+ * financial, or otherwise) caused by the use if this library or any part
+ * of it.
+ *<br>
+ * 7) I the author do not guarantee, warrant, or make any representations,
+ * either expressed or implied, regarding the use of this library or any
+ * part of it.
+ * <br><br>
+ * Author: Paul Lamb
+ * <br>
+ * http://www.paulscode.com
+ * </b>
+ */
+public class SourceJOAL extends Source
+{
+/**
+ * The source's basic Channel type-cast to a ChannelJOAL.
+ */
+ private ChannelJOAL channelOpenAL = (ChannelJOAL) channel;
+
+/**
+ * OpenAL sound-buffer identifier for this source if it is a normal source.
+ */
+ private int[] myBuffer;
+
+/**
+ * Vector3D containing the listener's 3D coordinates.
+ */
+ private Vector3 listenerPosition;
+
+/**
+ * Handle for accessing OpenAL.
+ */
+ private AL al = null;
+
+/**
+ * Constructor: Creates a new source using the specified parameters.
+ * @param listenerPosition Handle to the Vector3D containing the listener's 3D coordinates.
+ * @param myBuffer OpenAL sound-buffer identifier to use for a new normal source.
+ * @param priority Setting this to true will prevent other sounds from overriding this one.
+ * @param toStream Setting this to true will create a streaming source.
+ * @param toLoop Should this source loop, or play only once.
+ * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.
+ * @param filenameURL Filename/URL of the sound file to play at this source.
+ * @param soundBuffer Buffer containing audio data, or null if not loaded yet.
+ * @param x X position for this source.
+ * @param y Y position for this source.
+ * @param z Z position for this source.
+ * @param attModel Attenuation model to use.
+ * @param distOrRoll Either the fading distance or rolloff factor, depending on the value of 'att'.
+ * @param temporary Whether or not to remove this source after it finishes playing.
+ */
+ public SourceJOAL( Vector3 listenerPosition, int[] myBuffer,
+ boolean priority, boolean toStream, boolean toLoop,
+ String sourcename, FilenameURL filenameURL,
+ SoundBuffer soundBuffer, float x, float y, float z,
+ int attModel, float distOrRoll, boolean temporary )
+ {
+ super( priority, toStream, toLoop, sourcename, filenameURL, soundBuffer,
+ x, y, z, attModel, distOrRoll, temporary );
+ if( codec != null )
+ codec.reverseByteOrder( true );
+ this.listenerPosition = listenerPosition;
+ this.myBuffer = myBuffer;
+ libraryType = LibraryJOAL.class;
+ pitch = 1.0f;
+ al = LibraryJOAL.getAL();
+ positionChanged();
+ }
+
+/**
+ * Constructor: Creates a new source matching the specified source.
+ * @param listenerPosition Handle to the Vector3D containing the listener's 3D coordinates.
+ * @param myBuffer OpenAL sound-buffer identifier to use for a new normal source.
+ * @param old Source to copy information from.
+ * @param soundBuffer Buffer containing audio data, or null if not loaded yet.
+ */
+ public SourceJOAL( Vector3 listenerPosition, int[] myBuffer, Source old,
+ SoundBuffer soundBuffer )
+ {
+ super( old, soundBuffer );
+ if( codec != null )
+ codec.reverseByteOrder( true );
+ this.listenerPosition = listenerPosition;
+ this.myBuffer = myBuffer;
+ libraryType = LibraryJOAL.class;
+ pitch = 1.0f;
+ al = LibraryJOAL.getAL();
+ positionChanged();
+ }
+
+/**
+ * Constructor: Creates a new streaming source that will be directly fed with
+ * raw audio data.
+ * @param listenerPosition Handle to the Vector3D containing the listener's 3D coordinates.
+ * @param audioFormat Format that the data will be in.
+ * @param priority Setting this to true will prevent other sounds from overriding this one.
+ * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.
+ * @param x X position for this source.
+ * @param y Y position for this source.
+ * @param z Z position for this source.
+ * @param attModel Attenuation model to use.
+ * @param distOrRoll Either the fading distance or rolloff factor, depending on the value of 'att'.
+ */
+ public SourceJOAL( Vector3 listenerPosition, AudioFormat audioFormat,
+ boolean priority, String sourcename, float x,
+ float y, float z, int attModel, float distOrRoll )
+ {
+ super( audioFormat, priority, sourcename, x, y, z, attModel,
+ distOrRoll );
+ this.listenerPosition = listenerPosition;
+ libraryType = LibraryJOAL.class;
+ pitch = 1.0f;
+ al = LibraryJOAL.getAL();
+ positionChanged();
+ }
+
+/**
+ * Shuts the source down and removes references to all instantiated objects.
+ */
+ @Override
+ public void cleanup()
+ {
+
+ super.cleanup();
+ }
+
+/**
+ * Changes the peripheral information about the source using the specified
+ * parameters.
+ * @param listenerPosition Handle to the Vector3D containing the listener's 3D coordinates.
+ * @param myBuffer OpenAL sound-buffer identifier to use for a new normal source.
+ * @param priority Setting this to true will prevent other sounds from overriding this one.
+ * @param toStream Setting this to true will create a streaming source.
+ * @param toLoop Should this source loop, or play only once.
+ * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.
+ * @param filenameURL Filename/URL of the sound file to play at this source.
+ * @param soundBuffer Buffer containing audio data, or null if not loaded yet.
+ * @param x X position for this source.
+ * @param y Y position for this source.
+ * @param z Z position for this source.
+ * @param attModel Attenuation model to use.
+ * @param distOrRoll Either the fading distance or rolloff factor, depending on the value of 'att'.
+ * @param temporary Whether or not to remove this source after it finishes playing.
+ */
+ public void changeSource( Vector3 listenerPosition, int[] myBuffer,
+ boolean priority, boolean toStream,
+ boolean toLoop, String sourcename,
+ FilenameURL filenameURL, SoundBuffer soundBuffer,
+ float x, float y, float z, int attModel,
+ float distOrRoll, boolean temporary )
+ {
+ super.changeSource( priority, toStream, toLoop, sourcename, filenameURL,
+ soundBuffer, x, y, z, attModel, distOrRoll,
+ temporary );
+ this.listenerPosition = listenerPosition;
+ this.myBuffer = myBuffer;
+ pitch = 1.0f;
+ positionChanged();
+ }
+
+/**
+ * Removes the next filename from the sound sequence queue and assigns it to
+ * this source. This method has no effect on non-streaming sources. This
+ * method is used internally by SoundSystem, and it is unlikely that the user
+ * will ever need to use it.
+ * @return True if there was something in the queue.
+ */
+ @Override
+ public boolean incrementSoundSequence()
+ {
+ if( !toStream )
+ {
+ errorMessage( "Method 'incrementSoundSequence' may only be used " +
+ "for streaming sources." );
+ return false;
+ }
+ synchronized( soundSequenceLock )
+ {
+ if( soundSequenceQueue != null && soundSequenceQueue.size() > 0 )
+ {
+ filenameURL = soundSequenceQueue.remove( 0 );
+ if( codec != null )
+ codec.cleanup();
+ codec = SoundSystemConfig.getCodec( filenameURL.getFilename() );
+ if( codec != null )
+ {
+ codec.reverseByteOrder( true );
+ if( codec.getAudioFormat() == null )
+ codec.initialize( filenameURL.getURL() );
+
+ AudioFormat audioFormat = codec.getAudioFormat();
+
+ if( audioFormat == null )
+ {
+ errorMessage( "Audio Format null in method " +
+ "'incrementSoundSequence'" );
+ return false;
+ }
+
+ int soundFormat = 0;
+ if( audioFormat.getChannels() == 1 )
+ {
+ if( audioFormat.getSampleSizeInBits() == 8 )
+ {
+ soundFormat = AL.AL_FORMAT_MONO8;
+ }
+ else if( audioFormat.getSampleSizeInBits() == 16 )
+ {
+ soundFormat = AL.AL_FORMAT_MONO16;
+ }
+ else
+ {
+ errorMessage( "Illegal sample size in method " +
+ "'incrementSoundSequence'" );
+ return false;
+ }
+ }
+ else if( audioFormat.getChannels() == 2 )
+ {
+ if( audioFormat.getSampleSizeInBits() == 8 )
+ {
+ soundFormat = AL.AL_FORMAT_STEREO8;
+ }
+ else if( audioFormat.getSampleSizeInBits() == 16 )
+ {
+ soundFormat = AL.AL_FORMAT_STEREO16;
+ }
+ else
+ {
+ errorMessage( "Illegal sample size in method " +
+ "'incrementSoundSequence'" );
+ return false;
+ }
+ }
+ else
+ {
+ errorMessage( "Audio data neither mono nor stereo in " +
+ "method 'incrementSoundSequence'" );
+ return false;
+ }
+
+ // Let the channel know what format and sample rate to use:
+ channelOpenAL.setFormat( soundFormat,
+ (int) audioFormat.getSampleRate() );
+ preLoad = true;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+/**
+ * Called every time the listener's position or orientation changes.
+ */
+ @Override
+ public void listenerMoved()
+ {
+ positionChanged();
+ }
+
+/**
+ * Moves the source to the specified position.
+ * @param x X coordinate to move to.
+ * @param y Y coordinate to move to.
+ * @param z Z coordinate to move to.
+ */
+ @Override
+ public void setPosition( float x, float y, float z )
+ {
+ super.setPosition( x, y, z );
+
+ positionChanged();
+
+ // check if we are assigned to a channel. If so, tell OpenAL where
+ // this source is located:
+ if( channel != null && channel.attachedSource == this &&
+ channelOpenAL != null && channelOpenAL.ALSource != null )
+ {
+ // move the source:
+ al.alSource3f( channelOpenAL.ALSource[0], AL.AL_POSITION,
+ x, y, z );
+ checkALError();
+ }
+ }
+
+/**
+ * Recalculates the distance from the listner and the gain.
+ */
+ @Override
+ public void positionChanged()
+ {
+ calculateDistance();
+ calculateGain();
+
+ if( channel != null && channel.attachedSource == this &&
+ channelOpenAL != null && channelOpenAL.ALSource != null )
+ {
+ al.alSourcef( channelOpenAL.ALSource[0], AL.AL_GAIN,
+ (gain * sourceVolume * (float) Math.abs( fadeOutGain )
+ * fadeInGain) );
+ checkALError();
+ }
+ checkPitch();
+ }
+
+/**
+ * Checks the source's pitch.
+ */
+ private void checkPitch()
+ {
+ if( channel != null && channel.attachedSource == this &&
+ LibraryJOAL.alPitchSupported() && channelOpenAL != null &&
+ channelOpenAL.ALSource != null )
+ {
+ al.alSourcef( channelOpenAL.ALSource[0],
+ AL.AL_PITCH, pitch );
+ checkALError();
+ }
+ }
+
+/**
+ * Sets whether this source should loop or only play once.
+ * @param lp True or false.
+ */
+ @Override
+ public void setLooping( boolean lp )
+ {
+ super.setLooping( lp );
+
+ // make sure we are assigned to a channel:
+ if( channel != null && channel.attachedSource == this &&
+ channelOpenAL != null && channelOpenAL.ALSource != null )
+ {
+ if( lp )
+ al.alSourcei( channelOpenAL.ALSource[0], AL.AL_LOOPING,
+ AL.AL_TRUE );
+ else
+ al.alSourcei( channelOpenAL.ALSource[0], AL.AL_LOOPING,
+ AL.AL_FALSE );
+ checkALError();
+ }
+ }
+
+/**
+ * Sets this source's attenuation model.
+ * @param model Attenuation model to use.
+ */
+ @Override
+ public void setAttenuation( int model )
+ {
+ super.setAttenuation( model );
+ // make sure we are assigned to a channel:
+ if( channel != null && channel.attachedSource == this &&
+ channelOpenAL != null && channelOpenAL.ALSource != null )
+ {
+ // attenuation changed, so update the rolloff factor accordingly
+ if( model == SoundSystemConfig.ATTENUATION_ROLLOFF )
+ al.alSourcef( channelOpenAL.ALSource[0], AL.AL_ROLLOFF_FACTOR,
+ distOrRoll );
+ else
+ al.alSourcef( channelOpenAL.ALSource[0], AL.AL_ROLLOFF_FACTOR,
+ 0.0f );
+ checkALError();
+ }
+ }
+
+/**
+ * Sets this source's fade distance or rolloff factor, depending on the
+ * attenuation model.
+ * @param dr New value for fade distance or rolloff factor.
+ */
+ @Override
+ public void setDistOrRoll( float dr)
+ {
+ super.setDistOrRoll( dr );
+ // make sure we are assigned to a channel:
+ if( channel != null && channel.attachedSource == this &&
+ channelOpenAL != null && channelOpenAL.ALSource != null )
+ {
+ // if we are using rolloff attenuation, then dr is a rolloff factor:
+ if( attModel == SoundSystemConfig.ATTENUATION_ROLLOFF )
+ al.alSourcef( channelOpenAL.ALSource[0], AL.AL_ROLLOFF_FACTOR,
+ dr );
+ else
+ al.alSourcef( channelOpenAL.ALSource[0], AL.AL_ROLLOFF_FACTOR,
+ 0.0f );
+ checkALError();
+ }
+ }
+
+/**
+ * Sets this source's velocity, for use in Doppler effect.
+ * @param x Velocity along world x-axis.
+ * @param y Velocity along world y-axis.
+ * @param z Velocity along world z-axis.
+ */
+ @Override
+ public void setVelocity( float x, float y, float z )
+ {
+ super.setVelocity( x, y, z );
+
+ // make sure we are assigned to a channel:
+ if( channel != null && channel.attachedSource == this &&
+ channelOpenAL != null && channelOpenAL.ALSource != null )
+ {
+ al.alSource3f( channelOpenAL.ALSource[0], AL.AL_VELOCITY,
+ x, y, z );
+ checkALError();
+ }
+ }
+
+/**
+ * Manually sets this source's pitch.
+ * @param value A float value ( 0.5f - 2.0f ).
+ */
+ @Override
+ public void setPitch( float value )
+ {
+ super.setPitch( value );
+ checkPitch();
+ }
+
+/**
+ * Plays the source on the specified channel.
+ * @param c Channel to play on.
+ */
+ @Override
+ public void play( Channel c )
+ {
+ if( !active() )
+ {
+ if( toLoop )
+ toPlay = true;
+ return;
+ }
+
+ if( c == null )
+ {
+ errorMessage( "Unable to play source, because channel was null" );
+ return;
+ }
+
+ boolean newChannel = (channel != c);
+ if( channel != null && channel.attachedSource != this )
+ newChannel = true;
+
+ boolean wasPaused = paused();
+
+ super.play( c );
+
+ channelOpenAL = (ChannelJOAL) channel;
+
+ // Make sure the channel exists:
+ // check if we are already on this channel:
+ if( newChannel )
+ {
+ setPosition( position.getXf(), position.getYf(), position.getZf() );
+ checkPitch();
+
+ // Send the source's attributes to the channel:
+ if( channelOpenAL != null && channelOpenAL.ALSource != null )
+ {
+ if( LibraryJOAL.alPitchSupported() )
+ {
+ al.alSourcef( channelOpenAL.ALSource[0], AL.AL_PITCH,
+ pitch );
+ checkALError();
+ }
+ al.alSource3f( channelOpenAL.ALSource[0], AL.AL_POSITION,
+ position.getXf(), position.getYf(), position.getZf() );
+ checkALError();
+ al.alSource3f( channelOpenAL.ALSource[0], AL.AL_VELOCITY,
+ velocity.getXf(), velocity.getYf(), velocity.getZf() );
+ checkALError();
+ if( attModel == SoundSystemConfig.ATTENUATION_ROLLOFF )
+ al.alSourcef( channelOpenAL.ALSource[0],
+ AL.AL_ROLLOFF_FACTOR, distOrRoll );
+ else
+ al.alSourcef( channelOpenAL.ALSource[0],
+ AL.AL_ROLLOFF_FACTOR, 0.0f );
+ checkALError();
+ if( toLoop && (!toStream) )
+ al.alSourcei( channelOpenAL.ALSource[0],
+ AL.AL_LOOPING, AL.AL_TRUE );
+ else
+ al.alSourcei( channelOpenAL.ALSource[0], AL.AL_LOOPING,
+ AL.AL_FALSE );
+ checkALError();
+ }
+ if( !toStream )
+ {
+ // This is not a streaming source, so make sure there is
+ // a sound buffer loaded to play:
+ if( myBuffer == null )
+ {
+ errorMessage( "No sound buffer to play" );
+ return;
+ }
+
+ channelOpenAL.attachBuffer( myBuffer );
+ }
+ }
+
+ // See if we are already playing:
+ if( !playing() )
+ {
+ if( toStream && !wasPaused )
+ {
+ if( codec == null )
+ {
+ errorMessage( "Decoder null in method 'play'" );
+ return;
+ }
+ if( codec.getAudioFormat() == null )
+ codec.initialize( filenameURL.getURL() );
+
+ AudioFormat audioFormat = codec.getAudioFormat();
+
+ if( audioFormat == null )
+ {
+ errorMessage( "Audio Format null in method 'play'" );
+ return;
+ }
+
+ int soundFormat = 0;
+ if( audioFormat.getChannels() == 1 )
+ {
+ if( audioFormat.getSampleSizeInBits() == 8 )
+ {
+ soundFormat = AL.AL_FORMAT_MONO8;
+ }
+ else if( audioFormat.getSampleSizeInBits() == 16 )
+ {
+ soundFormat = AL.AL_FORMAT_MONO16;
+ }
+ else
+ {
+ errorMessage( "Illegal sample size in method 'play'" );
+ return;
+ }
+ }
+ else if( audioFormat.getChannels() == 2 )
+ {
+ if( audioFormat.getSampleSizeInBits() == 8 )
+ {
+ soundFormat = AL.AL_FORMAT_STEREO8;
+ }
+ else if( audioFormat.getSampleSizeInBits() == 16 )
+ {
+ soundFormat = AL.AL_FORMAT_STEREO16;
+ }
+ else
+ {
+ errorMessage( "Illegal sample size in method 'play'" );
+ return;
+ }
+ }
+ else
+ {
+ errorMessage( "Audio data neither mono nor stereo in " +
+ "method 'play'" );
+ return;
+ }
+
+ // Let the channel know what format and sample rate to use:
+ channelOpenAL.setFormat( soundFormat,
+ (int) audioFormat.getSampleRate() );
+ preLoad = true;
+ }
+ channel.play();
+ if( pitch != 1.0f )
+ checkPitch();
+ }
+ }
+
+/**
+ * Queues up the initial stream-buffers for the stream.
+ * @return False if the end of the stream was reached.
+ */
+ @Override
+ public boolean preLoad()
+ {
+ if( codec == null )
+ return false;
+
+ codec.initialize( filenameURL.getURL() );
+ LinkedList<byte[]> preLoadBuffers = new LinkedList<byte[]>();
+ for( int i = 0; i < SoundSystemConfig.getNumberStreamingBuffers(); i++ )
+ {
+ soundBuffer = codec.read();
+
+ if( soundBuffer == null || soundBuffer.audioData == null )
+ break;
+
+ preLoadBuffers.add( soundBuffer.audioData );
+ }
+ positionChanged();
+
+ channel.preLoadBuffers( preLoadBuffers );
+
+ preLoad = false;
+ return true;
+ }
+
+/**
+ * Calculates this source's distance from the listener.
+ */
+ private void calculateDistance()
+ {
+ if( listenerPosition != null )
+ {
+ // Calculate the source's distance from the listener:
+ double dX = position.getX() - listenerPosition.getX();
+ double dY = position.getY() - listenerPosition.getY();
+ double dZ = position.getZ() - listenerPosition.getZ();
+ distanceFromListener = (float) Math.sqrt( dX*dX + dY*dY + dZ*dZ );
+ }
+ }
+
+/**
+ * If using linear attenuation, calculates the gain for this source based on
+ * its distance from the listener.
+ */
+ private void calculateGain()
+ {
+ // If using linear attenuation, calculate the source's gain:
+ if( attModel == SoundSystemConfig.ATTENUATION_LINEAR )
+ {
+ if( distanceFromListener <= 0 )
+ {
+ gain = 1.0f;
+ }
+ else if( distanceFromListener >= distOrRoll )
+ {
+ gain = 0.0f;
+ }
+ else
+ {
+ gain = 1.0f - (distanceFromListener / distOrRoll);
+ }
+ if( gain > 1.0f )
+ gain = 1.0f;
+ if( gain < 0.0f )
+ gain = 0.0f;
+ }
+ else
+ {
+ gain = 1.0f;
+ }
+ }
+
+/**
+ * Checks for OpenAL errors, and prints a message if there is an error.
+ * @return True if there was an error, False if not.
+ */
+ private boolean checkALError()
+ {
+ switch( al.alGetError() )
+ {
+ case AL.AL_NO_ERROR:
+ return false;
+ case AL.AL_INVALID_NAME:
+ errorMessage( "Invalid name parameter." );
+ return true;
+ case AL.AL_INVALID_ENUM:
+ errorMessage( "Invalid parameter." );
+ return true;
+ case AL.AL_INVALID_VALUE:
+ errorMessage( "Invalid enumerated parameter value." );
+ return true;
+ case AL.AL_INVALID_OPERATION:
+ errorMessage( "Illegal call." );
+ return true;
+ case AL.AL_OUT_OF_MEMORY:
+ errorMessage( "Unable to allocate memory." );
+ return true;
+ default:
+ errorMessage( "An unrecognized error occurred." );
+ return true;
+ }
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index afa7147..db61b6e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -19,6 +19,7 @@ include ':ardor3d-performance'
include ':distribution'
include ':ardor3d-craft'
include ':ardor3d-audio'
+include ':ardor3d-audio-joal'
project(':ardor3d-savable').projectDir = "$rootDir/ardor3d-savable" as File
project(':ardor3d-math').projectDir = "$rootDir/ardor3d-math" as File
@@ -40,3 +41,4 @@ project(':ardor3d-performance').projectDir = "$rootDir/ardor3d-performance" as F
project(':distribution').projectDir = "$rootDir/ardor3d-distribution" as File
project(':ardor3d-craft').projectDir = "$rootDir/ardor3d-craft" as File
project(':ardor3d-audio').projectDir = "$rootDir/ardor3d-audio" as File
+project(':ardor3d-audio-joal').projectDir = "$rootDir/ardor3d-audio-joal" as File