diff options
author | Julien Gouesse <[email protected]> | 2022-10-24 00:52:24 +0200 |
---|---|---|
committer | Julien Gouesse <[email protected]> | 2022-10-24 00:52:24 +0200 |
commit | 48a2a211c691392aeef78b8e677b728f43d0ff95 (patch) | |
tree | b14bbbd5defc91ddaf2dba76b92b8a1f144a2ee1 | |
parent | 4f5379ee6d621125d253b325763212a6ee81acf7 (diff) |
Adds the JOAL backend of the Ardor3D sound system
-rw-r--r-- | ardor3d-audio-joal/build.gradle | 10 | ||||
-rw-r--r-- | ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/ChannelJOAL.java | 715 | ||||
-rw-r--r-- | ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/LibraryJOAL.java | 1112 | ||||
-rw-r--r-- | ardor3d-audio-joal/src/main/java/com/ardor3d/audio/joal/SourceJOAL.java | 769 | ||||
-rw-r--r-- | settings.gradle | 2 |
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 |