diff options
author | Julien Gouesse <[email protected]> | 2022-10-23 23:29:32 +0200 |
---|---|---|
committer | Julien Gouesse <[email protected]> | 2022-10-23 23:29:32 +0200 |
commit | 889b24bfc7a60efcd0779b8c2606cbc58f9d5b67 (patch) | |
tree | 31e79c94a693870440c6a4be0ccddcca95ceddff | |
parent | 37240573f4d95be2c79477fd86e5f6186e2a4dab (diff) |
Adds an audio system to Ardor3D, no backend yet
56 files changed, 20565 insertions, 1 deletions
diff --git a/ardor3d-audio/build.gradle b/ardor3d-audio/build.gradle new file mode 100644 index 0000000..08d57d6 --- /dev/null +++ b/ardor3d-audio/build.gradle @@ -0,0 +1,6 @@ + +description = 'Ardor 3D Sound System' +dependencies { + implementation project(':ardor3d-core') + implementation project(':ardor3d-math') +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/Channel.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/Channel.java new file mode 100644 index 0000000..4906809 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/Channel.java @@ -0,0 +1,279 @@ +/** + * 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; + +import java.util.LinkedList; +import com.ardor3d.audio.sampled.AudioFormat; + +/** + * The Channel class is the base class which can be extended for + * library-specific channels. It is also used in the "no-sound" library. + * A channel is a reserved sound-card voice through which sources are played + * back. Channels can be either streaming channels or normal (non-streaming) + * ones. For consistant naming conventions, each sub-class should have the + * name prefix "Channel". + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 Channel +{ +/** + * The library class associated with this type of channel. + */ + protected Class libraryType = Library.class; + +/** + * Global identifier for the type of channel (normal or streaming). Possible + * values for this varriable can be found in the + * {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} class. + */ + public int channelType; + +/** + * Processes status messages, warnings, and error messages. + */ + private SoundSystemLogger logger; + +/** + * Whatever source is attached to this channel. + */ + public Source attachedSource = null; + +/** + * Cumulative counter of the buffers played then unqued. + */ + public int buffersUnqueued = 0; + +/** + * Constructor: Takes channelType identifier as a paramater. 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). + */ + public Channel( int type ) + { + // grab a handle to the message logger: + logger = SoundSystemConfig.getLogger(); + + channelType = type; + } + +/** + * Shuts the channel down and removes references to all instantiated objects. + */ + public void cleanup() + { + logger = null; + } + +/** + * 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 an error occurred or if end of stream was reached. + */ + public boolean preLoadBuffers( LinkedList<byte[]> bufferList ) + { + 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. + */ + public boolean queueBuffer( byte[] buffer ) + { + return false; + } + +/** + * Feeds raw data to the stream. + * @param buffer Buffer containing raw audio data to stream. + * @return Number of prior buffers that have been processed. + */ + public int feedRawAudioData( byte[] buffer ) + { + return 1; + } + +/** + * Returns the number of queued byte[] buffers that have finished playing. + * @return Number of buffers processed. + */ + public int buffersProcessed() + { + return 0; + } + +/** + * Calculates the number of milliseconds since the channel began playing. + * @return Milliseconds, or -1 if unable to calculate. + */ + public float millisecondsPlayed() + { + return -1; + } +/** + * Plays the next queued byte[] buffer. This method is run from the seperate + * {@link com.ardor3d.audio.StreamThread StreamThread}. + * @return False when no more buffers are left to process. + */ + public boolean processBuffer() + { + return false; + } + +/** + * Sets the channel up to receive the specified audio format. + */ + public void setAudioFormat( AudioFormat audioFormat ) + {} + +/** + * Dequeues all previously queued data. + */ + public void flush() + {} + +/** + * Stops the channel, dequeues any queued data, and closes the channel. + */ + public void close() + {} + +/** + * Plays the currently attached normal source, opens this channel up for + * streaming, or resumes playback if this channel was paused. + */ + public void play() + {} + +/** + * Temporarily stops playback for this channel. + */ + public void pause() + {} + +/** + * Stops playback for this channel and rewinds the attached source to the + * beginning. + */ + public void stop() + {} + +/** + * Rewinds the attached source to the beginning. Stops the source if it was + * paused. + */ + public void rewind() + {} + +/** + * 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. + */ + public boolean playing() + { + return false; + } + +/** + * Returns the name of the class. + * @return "Channel" + library title. + */ + public String getClassName() + { + String libTitle = SoundSystemConfig.getLibraryTitle( libraryType ); + + if( libTitle.equals( "No Sound" ) ) + return "Channel"; + else + return "Channel" + libTitle; + } + +/** + * Prints a message. + * @param message Message to print. + */ + protected void message( String message ) + { + logger.message( message, 0 ); + } + +/** + * Prints an important message. + * @param message Message to print. + */ + protected void importantMessage( String message ) + { + logger.importantMessage( message, 0 ); + } + +/** + * Prints the specified message if error is true. + * @param error True or False. + * @param message Message to print if error is true. + * @return True if error is true. + */ + protected boolean errorCheck( boolean error, String message ) + { + return logger.errorCheck( error, getClassName(), message, 0 ); + } + +/** + * Prints an error message. + * @param message Message to print. + */ + protected void errorMessage( String message ) + { + logger.errorMessage( getClassName(), message, 0 ); + } + +/** + * Prints an exception's error message followed by the stack trace. + * @param e Exception containing the information to print. + */ + protected void printStackTrace( Exception e ) + { + logger.printStackTrace( e, 1 ); + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/CommandObject.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/CommandObject.java new file mode 100644 index 0000000..0b3dbed --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/CommandObject.java @@ -0,0 +1,612 @@ +/** + * 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; + +/** + * The CommandObject class is used to store arguments in the SoundSystem's + * Command Queue. Queued CommandObjects are then processed by the + * {@link com.ardor3d.audio.CommandThread CommandThread}. Commands are queued + * and executed in the background, so it is unlikely that the user will ever + * need to use this class. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 CommandObject +{ +/** + * Global identifier for the command to initialize the current sound library. + */ + public static final int INITIALIZE = 1; +/** + * Global identifier for the command to pre-load a sound file. + */ + public static final int LOAD_SOUND = 2; +/** + * Global identifier for the command to pre-load a sound file. + */ + public static final int LOAD_DATA = 3; +/** + * Global identifier for the command to remove a sound file from memory. + */ + public static final int UNLOAD_SOUND = 4; +/** + * Global identifier for the command to queue a sound file. + */ + public static final int QUEUE_SOUND = 5; +/** + * Global identifier for the command to dequeue a sound file. + */ + public static final int DEQUEUE_SOUND = 6; +/** + * Global identifier for the command to fade-out transition a source. + */ + public static final int FADE_OUT = 7; +/** + * Global identifier for the command to fade-out/in transition a source. + */ + public static final int FADE_OUT_IN = 8; +/** + * Global identifier for the command to check volume levels of fading sources. + */ + public static final int CHECK_FADE_VOLUMES = 9; +/** + * Global identifier for the command to create a new source. + */ + public static final int NEW_SOURCE = 10; +/** + * Global identifier for the command to create a new raw data stream. + */ + public static final int RAW_DATA_STREAM = 11; +/** + * Global identifier for the command to create a source and immediately play it. + */ + public static final int QUICK_PLAY = 12; +/** + * Global identifier for the command to set a source's position in 3D space. + */ + public static final int SET_POSITION = 13; +/** + * Global identifier for the command to change a source's volume. + */ + public static final int SET_VOLUME = 14; +/** + * Global identifier for the command to change a source's pitch. + */ + public static final int SET_PITCH = 15; +/** + * Global identifier for the command to change a source's priority. + */ + public static final int SET_PRIORITY = 16; +/** + * Global identifier for the command to tell a source whether or not to loop. + */ + public static final int SET_LOOPING = 17; +/** + * Global identifier for the command to set a source's attenuation model. + */ + public static final int SET_ATTENUATION = 18; +/** + * Global identifier for the command to set a source's fade distance or rolloff + * factor. + */ + public static final int SET_DIST_OR_ROLL = 19; +/** + * Global identifier for the command to change the Doppler factor. + */ + public static final int CHANGE_DOPPLER_FACTOR = 20; +/** + * Global identifier for the command to change the Doppler velocity. + */ + public static final int CHANGE_DOPPLER_VELOCITY = 21; +/** + * Global identifier for the command to set a source's velocity. + */ + public static final int SET_VELOCITY = 22; +/** + * Global identifier for the command to set a source's velocity. + */ + public static final int SET_LISTENER_VELOCITY = 23; +/** + * Global identifier for the command to play a source. + */ + public static final int PLAY = 24; +/** + * Global identifier for the command to play a source. + */ + public static final int FEED_RAW_AUDIO_DATA = 25; +/** + * Global identifier for the command to pause a source. + */ + public static final int PAUSE = 26; +/** + * Global identifier for the command to stop a source. + */ + public static final int STOP = 27; +/** + * Global identifier for the command to rewind a source. + */ + public static final int REWIND = 28; +/** + * Global identifier for the command to flush all queued data. + */ + public static final int FLUSH = 29; +/** + * Global identifier for the command to cull a source. + */ + public static final int CULL = 30; +/** + * Global identifier for the command to activate a source. + */ + public static final int ACTIVATE = 31; +/** + * Global identifier for the command to set a source as permanant or temporary. + */ + public static final int SET_TEMPORARY = 32; +/** + * Global identifier for the command to delete a source. + */ + public static final int REMOVE_SOURCE = 33; +/** + * Global identifier for the command to move the listner. + */ + public static final int MOVE_LISTENER = 34; +/** + * Global identifier for the command to set the listener's position. + */ + public static final int SET_LISTENER_POSITION = 35; +/** + * Global identifier for the command to turn the listener. + */ + public static final int TURN_LISTENER = 36; +/** + * Global identifier for the command to set the listener's turn angle. + */ + public static final int SET_LISTENER_ANGLE = 37; +/** + * Global identifier for the command to change the listener's orientation. + */ + public static final int SET_LISTENER_ORIENTATION = 38; +/** + * Global identifier for the command to change the master volume. + */ + public static final int SET_MASTER_VOLUME = 39; +/** + * Global identifier for the command to create a new library. + */ + public static final int NEW_LIBRARY = 40; + +/** + * Any buffer required for a command. + */ + public byte[] buffer; +/** + * Any int arguments required for a command. + */ + public int[] intArgs; +/** + * Any float arguments required for a command. + */ + public float[] floatArgs; +/** + * Any long arguments required for a command. + */ + public long[] longArgs; +/** + * Any boolean arguments required for a command. + */ + public boolean[] boolArgs; +/** + * Any String arguments required for a command. + */ + public String[] stringArgs; + +/** + * Any Class arguments required for a command. + */ + public Class[] classArgs; + +/** + * Any Object arguments required for a command. + */ + public Object[] objectArgs; + +/** + * Which command to execute. + */ + public int Command; + +/** + * Constructor used to create a command which doesn't require any arguments. + * @param cmd Which command to execute. + */ + public CommandObject( int cmd ) + { + Command = cmd; + } +/** + * Constructor used to create a command which requires one integer argument. + * @param cmd Which command to execute. + * @param i The integer argument needed to execute this command. + */ + public CommandObject( int cmd, int i ) + { + Command = cmd; + intArgs = new int[1]; + intArgs[0] = i; + } +/** + * Constructor used to create a command which requires one Library Class + * argument. + * @param cmd Which command to execute. + * @param c The Library Class argument needed to execute this command. + */ + public CommandObject( int cmd, Class c ) + { + Command = cmd; + classArgs = new Class[1]; + classArgs[0] = c; + } +/** + * Constructor used to create a command which requires one float argument. + * @param cmd Which command to execute. + * @param f The float argument needed to execute this command. + */ + public CommandObject( int cmd, float f ) + { + Command = cmd; + floatArgs = new float[1]; + floatArgs[0] = f; + } +/** + * Constructor used to create a command which requires one String argument. + * @param cmd Which command to execute. + * @param s The String argument needed to execute this command. + */ + public CommandObject( int cmd, String s ) + { + Command = cmd; + stringArgs = new String[1]; + stringArgs[0] = s; + } +/** + * Constructor used to create a command which requires one Object argument. + * @param cmd Which command to execute. + * @param o The Object argument needed to execute this command. + */ + public CommandObject( int cmd, Object o ) + { + Command = cmd; + objectArgs = new Object[1]; + objectArgs[0] = o; + } +/** + * Constructor used to create a command which requires one String argument and + * one Object argument. + * @param cmd Which command to execute. + * @param s The String argument needed to execute this command. + * @param o The Object argument needed to execute this command. + */ + public CommandObject( int cmd, String s, Object o ) + { + Command = cmd; + stringArgs = new String[1]; + stringArgs[0] = s; + objectArgs = new Object[1]; + objectArgs[0] = o; + } +/** + * Constructor used to create a command which requires one String argument and + * one byte buffer argument. + * @param cmd Which command to execute. + * @param s The String argument needed to execute this command. + * @param buff The byte buffer argument needed to execute this command. + */ + public CommandObject( int cmd, String s, byte[] buff ) + { + Command = cmd; + stringArgs = new String[1]; + stringArgs[0] = s; + buffer = buff; + } +/** + * Constructor used to create a command which requires one String argument, one + * Object argument, and one long argument. + * @param cmd Which command to execute. + * @param s The String argument needed to execute this command. + * @param o The Object argument needed to execute this command. + * @param l The long argument needed to execute this command. + */ + public CommandObject( int cmd, String s, Object o, long l ) + { + Command = cmd; + stringArgs = new String[1]; + stringArgs[0] = s; + objectArgs = new Object[1]; + objectArgs[0] = o; + longArgs = new long[1]; + longArgs[0] = l; + } +/** + * Constructor used to create a command which requires one String argument, one + * Object argument, and two long arguments. + * @param cmd Which command to execute. + * @param s The String argument needed to execute this command. + * @param o The Object argument needed to execute this command. + * @param l1 The first long argument needed to execute this command. + * @param l2 The second long argument needed to execute this command. + */ + public CommandObject( int cmd, String s, Object o, long l1, long l2 ) + { + Command = cmd; + stringArgs = new String[1]; + stringArgs[0] = s; + objectArgs = new Object[1]; + objectArgs[0] = o; + longArgs = new long[2]; + longArgs[0] = l1; + longArgs[1] = l2; + } +/** + * Constructor used to create a command which requires two String arguments. + * @param cmd Which command to execute. + * @param s1 The first String argument needed to execute this command. + * @param s2 The second String argument needed to execute this command. + */ + public CommandObject( int cmd, String s1, String s2 ) + { + Command = cmd; + stringArgs = new String[2]; + stringArgs[0] = s1; + stringArgs[1] = s2; + } +/** + * Constructor used to create a command which requires a String and an int as + * arguments. + * @param cmd Which command to execute. + * @param s The String argument needed to execute this command. + * @param i The integer argument needed to execute this command. + */ + public CommandObject( int cmd, String s, int i ) + { + Command = cmd; + intArgs = new int[1]; + stringArgs = new String[1]; + intArgs[0] = i; + stringArgs[0] = s; + } +/** + * Constructor used to create a command which requires a String and a float as + * arguments. + * @param cmd Which command to execute. + * @param s The String argument needed to execute this command. + * @param f The float argument needed to execute this command. + */ + public CommandObject( int cmd, String s, float f ) + { + Command = cmd; + floatArgs = new float[1]; + stringArgs = new String[1]; + floatArgs[0] = f; + stringArgs[0] = s; + } +/** + * Constructor used to create a command which requires a String and a boolean + * as arguments. + * @param cmd Which command to execute. + * @param s The String argument needed to execute this command. + * @param b The boolean argument needed to execute this command. + */ + public CommandObject( int cmd, String s, boolean b ) + { + Command = cmd; + boolArgs = new boolean[1]; + stringArgs = new String[1]; + boolArgs[0] = b; + stringArgs[0] = s; + } +/** + * Constructor used to create a command which requires three float arguments. + * @param cmd Which command to execute. + * @param f1 The first float argument needed to execute this command. + * @param f2 The second float argument needed to execute this command. + * @param f3 The third float argument needed to execute this command. + */ + public CommandObject( int cmd, float f1, float f2, float f3 ) + { + Command = cmd; + floatArgs = new float[3]; + floatArgs[0] = f1; + floatArgs[1] = f2; + floatArgs[2] = f3; + } +/** + * Constructor used to create a command which a String and three float + * arguments. + * @param cmd Which command to execute. + * @param s The String argument needed to execute this command. + * @param f1 The first float argument needed to execute this command. + * @param f2 The second float argument needed to execute this command. + * @param f3 The third float argument needed to execute this command. + */ + public CommandObject( int cmd, String s, float f1, float f2, float f3 ) + { + Command = cmd; + floatArgs = new float[3]; + stringArgs = new String[1]; + floatArgs[0] = f1; + floatArgs[1] = f2; + floatArgs[2] = f3; + stringArgs[0] = s; + } +/** + * Constructor used to create a command which requires six float arguments. + * @param cmd Which command to execute. + * @param f1 The first float argument needed to execute this command. + * @param f2 The second float argument needed to execute this command. + * @param f3 The third float argument needed to execute this command. + * @param f4 The fourth float argument needed to execute this command. + * @param f5 The fifth float argument needed to execute this command. + * @param f6 The sixth float argument needed to execute this command. + */ + public CommandObject( int cmd, float f1, float f2, float f3, float f4, + float f5, float f6 ) + { + Command = cmd; + floatArgs = new float[6]; + floatArgs[0] = f1; + floatArgs[1] = f2; + floatArgs[2] = f3; + floatArgs[3] = f4; + floatArgs[4] = f5; + floatArgs[5] = f6; + } +/** + * Constructor used to create a command which requires several arguments. + * @param cmd Which command to execute. + * @param b1 The first boolean argument needed to execute this command. + * @param b2 The second boolean argument needed to execute this command. + * @param b3 The third boolean argument needed to execute this command. + * @param s The String argument needed to execute this command. + * @param o The Object argument needed to execute this command. + * @param f1 The first float argument needed to execute this command. + * @param f2 The second float argument needed to execute this command. + * @param f3 The third float argument needed to execute this command. + * @param i The integer argument needed to execute this command. + * @param f4 The fourth float argument needed to execute this command. + */ + public CommandObject( int cmd, + boolean b1, boolean b2, boolean b3, + String s, Object o, + float f1, float f2, float f3, + int i, float f4 ) + { + Command = cmd; + intArgs = new int[1]; + floatArgs = new float[4]; + boolArgs = new boolean[3]; + stringArgs = new String[1]; + objectArgs = new Object[1]; + intArgs[0] = i; + floatArgs[0] = f1; + floatArgs[1] = f2; + floatArgs[2] = f3; + floatArgs[3] = f4; + boolArgs[0] = b1; + boolArgs[1] = b2; + boolArgs[2] = b3; + stringArgs[0] = s; + objectArgs[0] = o; + } +/** + * Constructor used to create a command which requires several arguments. + * @param cmd Which command to execute. + * @param b1 The first boolean argument needed to execute this command. + * @param b2 The second boolean argument needed to execute this command. + * @param b3 The third boolean argument needed to execute this command. + * @param s The String argument needed to execute this command. + * @param o The Object argument needed to execute this command. + * @param f1 The first float argument needed to execute this command. + * @param f2 The second float argument needed to execute this command. + * @param f3 The third float argument needed to execute this command. + * @param i The integer argument needed to execute this command. + * @param f4 The fourth float argument needed to execute this command. + * @param b4 The fourth boolean argument needed to execute this command. + */ + public CommandObject( int cmd, + boolean b1, boolean b2, boolean b3, + String s, + Object o, + float f1, float f2, float f3, + int i, float f4, boolean b4 ) + { + Command = cmd; + intArgs = new int[1]; + floatArgs = new float[4]; + boolArgs = new boolean[4]; + stringArgs = new String[1]; + objectArgs = new Object[1]; + intArgs[0] = i; + floatArgs[0] = f1; + floatArgs[1] = f2; + floatArgs[2] = f3; + floatArgs[3] = f4; + boolArgs[0] = b1; + boolArgs[1] = b2; + boolArgs[2] = b3; + boolArgs[3] = b4; + stringArgs[0] = s; + objectArgs[0] = o; + } +/** + * Constructor used to create a command which requires several arguments. + * @param cmd Which command to execute. + * @param o The Object argument needed to execute this command. + * @param b The first boolean argument needed to execute this command. + * @param s The String argument needed to execute this command. + * @param f1 The first float argument needed to execute this command. + * @param f2 The second float argument needed to execute this command. + * @param f3 The third float argument needed to execute this command. + * @param i The integer argument needed to execute this command. + * @param f4 The fourth float argument needed to execute this command. + */ + public CommandObject( int cmd, + Object o, + boolean b, + String s, + float f1, float f2, float f3, + int i, + float f4 ) + { + Command = cmd; + intArgs = new int[1]; + floatArgs = new float[4]; + boolArgs = new boolean[1]; + stringArgs = new String[1]; + objectArgs = new Object[1]; + intArgs[0] = i; + floatArgs[0] = f1; + floatArgs[1] = f2; + floatArgs[2] = f3; + floatArgs[3] = f4; + boolArgs[0] = b; + stringArgs[0] = s; + objectArgs[0] = o; + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/CommandThread.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/CommandThread.java new file mode 100644 index 0000000..11ae506 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/CommandThread.java @@ -0,0 +1,185 @@ +/** + * 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; + +/** + * The CommandThread class is designed to move all command processing into a + * single thread to be run in the background and avoid conflicts between + * threads. Commands are processed in the order that they were queued. The + * arguements for each command are stored in a + * {@link com.ardor3d.audio.CommandObject CommandObject}. The Command Queue is + * located in the {@link com.ardor3d.audio.SoundSystem SoundSystem} class. + * Calling kill() stops the thread, and this should be immediatly followed + * by a call to interrupt() to wake up the thread so it may end. This class + * also checks for temporary sources that are finished playing, and removes + * them. + * + * NOTE: The command thread is created automatically by the sound system, so it + * is unlikely that the user would ever need to use this class. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 CommandThread extends SimpleThread +{ +/** + * Processes status messages, warnings, and error messages. + */ + protected SoundSystemLogger logger; + +/** + * Handle to the Sound System. This is where the Command Queue is located. + */ + private SoundSystem soundSystem; + +/** + * Name of this class. + */ + protected String className = "CommandThread"; + +/** + * Constructor: Takes a handle to the SoundSystem object as a parameter. + * @param s Handle to the SoundSystem. +*/ + public CommandThread( SoundSystem s ) + { + // grab a handle to the message logger: + logger = SoundSystemConfig.getLogger(); + + soundSystem = s; + } + +/** + * Shuts the thread down and removes references to all instantiated objects. + * NOTE: Method alive() will return false when cleanup() has finished. + */ + @Override + protected void cleanup() + { + kill(); + + logger = null; + soundSystem = null; + + super.cleanup(); // Important! + } + +/** + * The main loop for processing commands. The Command Thread starts out + * asleep, and it sleeps again after it finishes processing commands, so it + * must be interrupted when commands are queued for processing. + */ + @Override + public void run() + { + long previousTime = System.currentTimeMillis(); + long currentTime = previousTime; + + if( soundSystem == null ) + { + errorMessage( "SoundSystem was null in method run().", 0 ); + cleanup(); + return; + } + + // Start out asleep: + snooze( 3600000 ); + + while( !dying() ) + { + // Perform user-specific source management: + soundSystem.ManageSources(); + + // Process all queued commands: + soundSystem.CommandQueue( null ); + + // Remove temporary sources every ten seconds: + currentTime = System.currentTimeMillis(); + if( (!dying()) && ((currentTime - previousTime) > 10000) ) + { + previousTime = currentTime; + soundSystem.removeTemporarySources(); + } + + // Wait for more commands: + if( !dying() ) + snooze( 3600000 ); + } + + cleanup(); // Important! + } + +/** + * Prints a message. + * @param message Message to print. + */ + protected void message( String message, int indent ) + { + logger.message( message, indent ); + } + +/** + * Prints an important message. + * @param message Message to print. + */ + protected void importantMessage( String message, int indent ) + { + logger.importantMessage( message, indent ); + } + +/** + * Prints the specified message if error is true. + * @param error True or False. + * @param message Message to print if error is true. + * @return True if error is true. + */ + protected boolean errorCheck( boolean error, String message ) + { + return logger.errorCheck( error, className, message, 0 ); + } + +/** + * Prints an error message. + * @param message Message to print. + */ + protected void errorMessage( String message, int indent ) + { + logger.errorMessage( className, message, indent ); + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/FilenameURL.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/FilenameURL.java new file mode 100644 index 0000000..eab3749 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/FilenameURL.java @@ -0,0 +1,162 @@ +/** + * 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; + +import java.net.URL; + +/** + * The FilenameURL class is designed to associate a String filename/identifier + * with a URL. Handles either case where user supplies a String path or user + * supplies a URL instance. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 FilenameURL +{ +/** + * Processes status messages, warnings, and error messages. + */ + private SoundSystemLogger logger; + +/** + * Filename or identifier for the file. + */ + private String filename = null; + +/** + * URL interface to the file. + */ + private URL url = null; + +/** + * Constructor: Saves handles to the url and identifier. The identifier should + * look like a filename, and it must have the correct extension so SoundSystem + * knows what format to use for the file referenced by the URL instance. + * @param url URL interface to a file. + * @param identifier Identifier (filename) for the file. + */ + public FilenameURL( URL url, String identifier ) + { + // grab a handle to the message logger: + logger = SoundSystemConfig.getLogger(); + + filename = identifier; + this.url = url; + } + +/** + * Constructor: Saves a handle to the filename (used later to generate a URL + * instance). The file may either be located within the + * JAR or at an online location. If the file is online, filename must begin + * with "http://", since that is how SoundSystem recognizes URL names. + * @param filename Name of the file. + */ + public FilenameURL( String filename ) + { + // grab a handle to the message logger: + logger = SoundSystemConfig.getLogger(); + + this.filename = filename; + url = null; + } + +/** + * Returns the filename/identifier. + * @return Filename or identifier for the file. + */ + public String getFilename() + { + return filename; + } + +/** + * Returns the URL interface to the file. If a URL was not originally specified + * in the constructor, then the first time this method is called it creates a + * URL instance using the previously specified filename. + * @return URL interface to the file. + */ + public URL getURL() + { + if( url == null ) + { + // Check if the file is online or inside the JAR: + if( filename.matches( SoundSystemConfig.PREFIX_URL ) ) + { + // Online + try + { + url = new URL( filename ); + } + catch( Exception e ) + { + errorMessage( "Unable to access online URL in " + + "method 'getURL'" ); + printStackTrace( e ); + return null; + } + } + else + { + // Inside the JAR + url = getClass().getClassLoader().getResource( + SoundSystemConfig.getSoundFilesPackage() + filename ); + } + } + return url; + } + +/** + * Prints an error message. + * @param message Message to print. + */ + private void errorMessage( String message ) + { + logger.errorMessage( "MidiChannel", message, 0 ); + } + +/** + * Prints an exception's error message followed by the stack trace. + * @param e Exception containing the information to print. + */ + private void printStackTrace( Exception e ) + { + logger.printStackTrace( e, 1 ); + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/ICodec.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/ICodec.java new file mode 100644 index 0000000..bddd958 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/ICodec.java @@ -0,0 +1,128 @@ +/** + * 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; + +import java.net.URL; +import com.ardor3d.audio.sampled.AudioFormat; + +/** + * The ICodec interface provides a common interface for SoundSystem to use + * for accessing external codec libraries. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 interface ICodec +{ +/** + * Should tell derived classes when they may need to reverse the byte order of + * the data before returning it in the read() and readAll() methods. The + * reason for the reversBytOrder method is because some external codec + * libraries produce audio data in a format that some external audio libraries + * require to be reversed. Derivatives of the Library and Source classes for + * audio libraries which require this type of data to be reversed should call + * the reverseByteOrder() method for all instances of ICodec that they use. + * Derivatives of the ICodec interface for codec libraries which which produce + * this type of data should use the reverseByteOrder() method to know when the + * data needs to be reversed before returning it in the read() and readAll() + * methods. If a particular codec library does not produce this type of data, + * its derived ICodec class may disregard any calls to the reverseByteOrder() + * method. + * @param b True if the calling audio library requires byte-reversal by some codec libraries. + */ + public void reverseByteOrder( boolean b ); + +/** + * Should make any preperations required before reading from the audio stream. + * If another stream is already opened, it should be closed and a new audio + * stream opened in its place. This method is used internally by SoundSystem + * not only to initialize a stream, but also to rewind streams and to switch + * stream sources on the fly. + * @return False if an error occurred or if end of stream was reached. + */ + public boolean initialize( URL url ); + +/** + * Should return false if the stream is busy initializing. To prevent bad + * data from being returned by this method, derived classes should internally + * synchronize with any elements used by both the initialized() and initialize() + * methods. + * @return True if steam is initialized. + */ + public boolean initialized(); + +/** + * Should read in one stream buffer worth of audio data. See + * {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about accessing and changing default settings. + * @return The audio data wrapped into a SoundBuffer context. + */ + public SoundBuffer read(); + +/** + * Should read in all the audio data from the stream (up to the default + * "maximum file size". See + * {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about accessing and changing default settings. + * @return the audio data wrapped into a SoundBuffer context. + */ + public SoundBuffer readAll(); + +/** + * Should return false if there is still more data available to be read in. To + * prevent bad data from being returned by this method, derived classes should + * internally synchronize with any elements used in both the endOfStream() and + * the read() or readAll() methods. + * @return True if end of stream was reached. + */ + public boolean endOfStream(); + +/** + * Should close any open streams and remove references to all instantiated + * objects. + */ + public void cleanup(); + +/** + * Should return the audio format of the data being returned by the read() and + * readAll() methods. + * @return Information wrapped into an AudioFormat context. + */ + public AudioFormat getAudioFormat(); +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/IStreamListener.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/IStreamListener.java new file mode 100644 index 0000000..418fe59 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/IStreamListener.java @@ -0,0 +1,20 @@ +/** + * 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; + +public interface IStreamListener +{ + /** + * Notifies implementation that an End Of Stream was reached. + * @param sourcename String identifier of the source which reached the EOS. + * @param queueSize Number of items left the the stream's play queue, or zero if none. + */ + public void endOfStream( String sourcename, int queueSize ); +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/Library.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/Library.java new file mode 100644 index 0000000..8abd6b6 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/Library.java @@ -0,0 +1,1563 @@ +/** + * 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; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import com.ardor3d.audio.sampled.AudioFormat; + +/** + * The Library class is the class from which all library types are extended. + * It provides generic methods for interfacing with the audio libraries + * supported by the SoundSystem. Specific libraries should extend this class + * and override the necessary methods. For consistant naming conventions, each + * sub-class should have the name prefix "Library". + * + * This class may also be used as the "No Sound Library" (i.e. silent mode) if + * no other audio libraries are supported by the host machine, or to mute all + * sound. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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> + * + * Julien Gouesse: I removed Midi support in order to stop depending on javax.sound more easily + */ +public class Library +{ +/** + * Processes status messages, warnings, and error messages. + */ + private SoundSystemLogger logger; + +/** + * Position and orientation of the listener. + */ + protected ListenerData listener; + +/** + * Map containing sound file data for easy lookup by filename / identifier. + */ + protected HashMap<String, SoundBuffer> bufferMap = null; + +/** + * Map containing all created sources for easy look-up by name. + */ + protected HashMap<String, Source> sourceMap; // (name, source data) pairs + +/** + * Array containing maximum number of non-streaming audio channels. + */ + protected List<Channel> streamingChannels; + +/** + * Array containing maximum number of non-streaming audio channels. + */ + protected List<Channel> normalChannels; + +/** + * Source name last played on each streaming channel. + */ + private String[] streamingChannelSourceNames; + +/** + * Source name last played on each non-streaming channel. + */ + private String[] normalChannelSourceNames; + +/** + * Increments through the steaming channel list as new sources are played. + */ + private int nextStreamingChannel = 0; + +/** + * Increments through the non-steaming channel list as new sources are played. + */ + private int nextNormalChannel = 0; + +/** + * Handles processing for all streaming sources. + */ + protected StreamThread streamThread; + +/** + * Whether or not the library requires reversal of audio data byte order. + */ + protected boolean reverseByteOrder = false; + +/** + * Constructor: Instantiates the source map and listener information. NOTES: + * The 'super()' method should be at the top of constructors for all extended + * classes. The varriable 'libraryType' should be given a new value in the + * constructors for all extended classes. + */ + public Library() throws SoundSystemException + { + // grab a handle to the message logger: + logger = SoundSystemConfig.getLogger(); + + // instantiate the buffer map: + bufferMap = new HashMap<String, SoundBuffer>(); + + // instantiate the source map: + sourceMap = new HashMap<String, Source>(); + + listener = new ListenerData( 0.0f, 0.0f, 0.0f, // position + 0.0f, 0.0f, -1.0f, // look-at direction + 0.0f, 1.0f, 0.0f, // up direction + 0.0f ); // angle + + streamingChannels = new LinkedList<Channel>(); + normalChannels = new LinkedList<Channel>(); + streamingChannelSourceNames = new String[ + SoundSystemConfig.getNumberStreamingChannels() ]; + normalChannelSourceNames = new String[ + SoundSystemConfig.getNumberNormalChannels() ]; + + streamThread = new StreamThread(); + streamThread.start(); + } + + +/* ########################################################################## */ +/* BEGIN OVERRIDE METHODS */ +/* */ +/* The following methods should be overrided as required */ +/* ########################################################################## */ + +/** + * Stops all sources, shuts down sound library, and removes references to all + * instantiated objects. + */ + public void cleanup() + { + streamThread.kill(); + streamThread.interrupt(); + + // wait up to 5 seconds for stream thread to end: + for( int i = 0; i < 50; i++ ) + { + if( !streamThread.alive() ) + break; + try + { + Thread.sleep(100); + } + catch(Exception e) + {} + } + + if( streamThread.alive() ) + { + errorMessage( "Stream thread did not die!" ); + message( "Ignoring errors... continuing clean-up." ); + } + + + + Channel channel = null; + if( streamingChannels != null ) + { + while( !streamingChannels.isEmpty() ) + { + channel = streamingChannels.remove(0); + channel.close(); + channel.cleanup(); + channel = null; + } + streamingChannels.clear(); + streamingChannels = null; + } + if( normalChannels != null ) + { + while( !normalChannels.isEmpty() ) + { + channel = normalChannels.remove(0); + channel.close(); + channel.cleanup(); + channel = null; + } + normalChannels.clear(); + normalChannels = null; + } + + Set<String> keys = sourceMap.keySet(); + Iterator<String> iter = keys.iterator(); + String sourcename; + Source source; + + // loop through and cleanup all the sources: + while( iter.hasNext() ) + { + sourcename = iter.next(); + source = sourceMap.get( sourcename ); + if( source != null ) + source.cleanup(); + } + sourceMap.clear(); + sourceMap = null; + + listener = null; + streamThread = null; + } + +/** + * Initializes the sound library. + */ + public void init() throws SoundSystemException + { + Channel channel = null; + + // create the streaming channels: + for( int x = 0; x < SoundSystemConfig.getNumberStreamingChannels(); x++ ) + { + channel = createChannel( SoundSystemConfig.TYPE_STREAMING ); + if( channel == null ) + break; + streamingChannels.add( channel ); + } + // create the non-streaming channels: + for( int x = 0; x < SoundSystemConfig.getNumberNormalChannels(); x++ ) + { + channel = createChannel( SoundSystemConfig.TYPE_NORMAL ); + if( channel == null ) + break; + normalChannels.add( channel ); + } + } + +/** + * Checks if the no-sound library type is compatible. + * @return True or false. + */ + public static boolean libraryCompatible() + { + return true; // the no-sound library is always 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. + * @return The new channel. + */ + protected Channel createChannel( int type ) + { + return new Channel( type ); + } + +/** + * Pre-loads a sound into memory. + * @param filenameURL Filename/URL of the sound file to load. + * @return True if the sound loaded properly. + */ + public boolean loadSound( FilenameURL filenameURL ) + { + 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. + */ + public boolean loadSound( SoundBuffer buffer, String identifier ) + { + return true; + } + +/** + * Returns the filenames of all previously loaded sounds. + * @return LinkedList of String filenames. + */ + public LinkedList<String> getAllLoadedFilenames() + { + LinkedList<String> filenames = new LinkedList<String>(); + Set<String> keys = bufferMap.keySet(); + Iterator<String> iter = keys.iterator(); + + // loop through and update the volume of all sources: + while( iter.hasNext() ) + { + filenames.add( iter.next() ); + } + + return filenames; + } + +/** + * Returns the sourcenames of all sources. + * @return LinkedList of String sourcenames. + */ + public LinkedList<String> getAllSourcenames() + { + LinkedList<String> sourcenames = new LinkedList<String>(); + Set<String> keys = sourceMap.keySet(); + Iterator<String> iter = keys.iterator(); + + // loop through and update the volume of all sources: + while( iter.hasNext() ) + { + sourcenames.add( iter.next() ); + } + + return sourcenames; + } + +/** + * 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. + */ + public void unloadSound( String filename ) + { + bufferMap.remove( filename ); + } + +/** + * Opens a direct line for streaming audio data. + * @param audioFormat Format that the data will be in. + * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename. + * @param posX X position for this source. + * @param posY Y position for this source. + * @param posZ 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". + */ + public void rawDataStream( AudioFormat audioFormat, boolean priority, + String sourcename, float posX, float posY, + float posZ, int attModel, float distOrRoll ) + { + sourceMap.put( sourcename, + new Source( audioFormat, priority, sourcename, posX, + posY, posZ, attModel, distOrRoll ) ); + } + +/** + * Creates a new source using the specified information. + * @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 posX X position for this source. + * @param posY Y position for this source. + * @param posZ 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". + */ + public void newSource( boolean priority, boolean toStream, boolean toLoop, + String sourcename, FilenameURL filenameURL, + float posX, float posY, float posZ, int attModel, + float distOrRoll ) + { + sourceMap.put( sourcename, + new Source( priority, toStream, toLoop, sourcename, + filenameURL, null, posX, posY, posZ, + attModel, distOrRoll, false ) ); + } + +/** + * Creates and immediately plays a new source that will be removed when it + * finishes playing. + * @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 The filename/URL of the sound file to play at this source. + * @param posX X position for this source. + * @param posY Y position for this source. + * @param posZ 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". + */ + public void quickPlay( boolean priority, boolean toStream, boolean toLoop, + String sourcename, FilenameURL filenameURL, + float posX, float posY, float posZ, int attModel, + float distOrRoll, boolean tmp ) + { + sourceMap.put( sourcename, + new Source( priority, toStream, toLoop, sourcename, + filenameURL, null, posX, posY, posZ, + attModel, distOrRoll, tmp ) ); + } + +/** + * + * Defines whether or not the source should be removed after it finishes + * playing. + * @param sourcename The source's name. + * @param temporary True or False. + */ + public void setTemporary( String sourcename, boolean temporary ) + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.setTemporary( temporary ); + } + +/** + * Changes the specified source's position. + * @param sourcename The source's name. + * @param x Destination X coordinate. + * @param y Destination Y coordinate. + * @param z Destination Z coordinate. + */ + public void setPosition( String sourcename, float x, float y, float z ) + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.setPosition( x, y, z ); + } + +/** + * Sets the specified source's priority factor. A priority source will not be + * overriden if there are too many sources playing at once. + * @param sourcename The source's name. + * @param pri True or False. + */ + public void setPriority( String sourcename, boolean pri ) + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.setPriority( pri ); + } + +/** + * Sets the specified source's looping parameter. If parameter lp is false, + * the source will play once and stop. + * @param sourcename The source's name. + * @param lp True or False. + */ + public void setLooping( String sourcename, boolean lp ) + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.setLooping( lp ); + } + +/** + * Sets the specified source's attenuation model. + * @param sourcename The source's name. + * @param model Attenuation model to use. + */ + public void setAttenuation( String sourcename, int model ) + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.setAttenuation( model ); + } + +/** + * Sets the specified source's fade distance or rolloff factor. + * @param sourcename The source's name. + * @param dr Fade distance or rolloff factor. + */ + public void setDistOrRoll( String sourcename, float dr) + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.setDistOrRoll( dr ); + } + +/** + * Sets the specified source's velocity, for use in Doppler effect. + * @param sourcename The source's name. + * @param x Velocity along world x-axis. + * @param y Velocity along world y-axis. + * @param z Velocity along world z-axis. + */ + public void setVelocity( String sourcename, float x, float y, float z ) + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.setVelocity( x, y, z ); + } + +/** + * 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. + */ + public void setListenerVelocity( float x, float y, float z ) + { + listener.setVelocity( x, y, z ); + } + +/** + * Notifies the underlying library that the Doppler parameters have changed. + */ + public void dopplerChanged() + {} + +/** + * Returns the number of miliseconds since the specified source began playing. + * @return miliseconds, or -1 if not playing or unable to calculate + */ + public float millisecondsPlayed( String sourcename ) + { + if( sourcename == null || sourcename.equals( "" ) ) + { + errorMessage( "Sourcename not specified in method " + + "'millisecondsPlayed'" ); + return -1; + } + + if( midiSourcename( sourcename ) ) + { + errorMessage( "Unable to calculate milliseconds for MIDI source." ); + return -1; + } + else + { + Source source = sourceMap.get( sourcename ); + if( source == null ) + { + errorMessage( "Source '" + sourcename + "' not found in " + + "method 'millisecondsPlayed'" ); + } + return source.millisecondsPlayed(); + } + } +/** + * Feeds raw data through the specified source. The source must be a + * streaming source and it can not be already associated with a file or URL to + * stream from. + * @param sourcename Name of the streaming source to play from. + * @param buffer Byte buffer containing raw audio data to stream. + * @return Number of prior buffers that have been processed, or -1 if unable to queue the buffer (if the source was culled, for example). + */ + public int feedRawAudioData( String sourcename, byte[] buffer ) + { + if( sourcename == null || sourcename.equals( "" ) ) + { + errorMessage( "Sourcename not specified in method " + + "'feedRawAudioData'" ); + return -1; + } + + if( midiSourcename( sourcename ) ) + { + errorMessage( "Raw audio data can not be fed to the " + + "MIDI channel." ); + return -1; + } + else + { + Source source = sourceMap.get( sourcename ); + if( source == null ) + { + errorMessage( "Source '" + sourcename + "' not found in " + + "method 'feedRawAudioData'" ); + } + return feedRawAudioData( source, buffer ); + } + } + +/** + * Feeds raw data through the specified source. The source must be a + * streaming source and it can not be already associated with a file or URL to + * stream from. + * @param source Streaming source to play from. + * @param buffer Byte buffer containing raw audio data to stream. + * @return Number of prior buffers that have been processed, or -1 if unable to queue the buffer (if the source was culled, for example). + */ + public int feedRawAudioData( Source source, byte[] buffer ) + { + if( source == null ) + { + errorMessage( "Source parameter null in method " + + "'feedRawAudioData'" ); + return -1; + } + if( !source.toStream ) + { + errorMessage( "Only a streaming source may be specified in " + + "method 'feedRawAudioData'" ); + return -1; + } + if( !source.rawDataStream ) + { + errorMessage( "Streaming source already associated with a " + + "file or URL in method'feedRawAudioData'" ); + return -1; + } + + if( !source.playing() || source.channel == null ) + { + Channel channel; + if( source.channel != null && ( source.channel.attachedSource == + source ) ) + channel = source.channel; + else + channel = getNextChannel( source ); + + int processed = source.feedRawAudioData( channel, buffer ); + channel.attachedSource = source; + streamThread.watch( source ); + streamThread.interrupt(); + return processed; + } + + return( source.feedRawAudioData( source.channel, buffer ) ); + } + +/** + * Looks up the specified source and plays it. + * @param sourcename Name of the source to play. + */ + public void play( String sourcename ) + { + if( sourcename == null || sourcename.equals( "" ) ) + { + errorMessage( "Sourcename not specified in method 'play'" ); + return; + } + if( midiSourcename( sourcename ) ) + { + throw new UnsupportedOperationException("MIDI format not supported without a codec"); + } + else + { + Source source = sourceMap.get( sourcename ); + if( source == null ) + { + errorMessage( "Source '" + sourcename + "' not found in " + + "method 'play'" ); + } + play( source ); + } + } + +/** + * Plays the specified source. + * @param source The source to play. + */ + public void play( Source source ) + { + if( source == null ) + return; + + // raw data streams will automatically play when data is sent to them, + // so no need to do anything here. + if( source.rawDataStream ) + return; + + if( !source.active() ) + return; + + if( !source.playing() ) + { + Channel channel = getNextChannel( source ); + + if( source != null && channel != null ) + { + if( source.channel != null && + source.channel.attachedSource != source ) + source.channel = null; + channel.attachedSource = source; + source.play( channel ); + if( source.toStream ) + { + streamThread.watch( source ); + streamThread.interrupt(); + } + } + } + } + +/** + * Stops the specified source. + * @param sourcename The source's name. + */ + public void stop( String sourcename ) + { + if( sourcename == null || sourcename.equals( "" ) ) + { + errorMessage( "Sourcename not specified in method 'stop'" ); + return; + } + if( midiSourcename( sourcename ) ) + { + throw new UnsupportedOperationException("MIDI format not supported without a codec"); + } + else + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.stop(); + } + } + +/** + * Pauses the specified source. + * @param sourcename The source's name. + */ + public void pause( String sourcename ) + { + if( sourcename == null || sourcename.equals( "" ) ) + { + errorMessage( "Sourcename not specified in method 'pause'" ); + return; + } + if( midiSourcename( sourcename ) ) + { + throw new UnsupportedOperationException("MIDI format not supported without a codec"); + } + else + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.pause(); + } + } + +/** + * Rewinds the specified source. + * @param sourcename The source's name. + */ + public void rewind( String sourcename ) + { + if( midiSourcename( sourcename ) ) + { + throw new UnsupportedOperationException("MIDI format not supported without a codec"); + } + else + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.rewind(); + } + } + +/** + * Clears all previously queued data from a stream. + * @param sourcename The source's name. + */ + public void flush( String sourcename ) + { + if( midiSourcename( sourcename ) ) + errorMessage( "You can not flush the MIDI channel" ); + else + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.flush(); + } + } + +/** + * Culls the specified source. A culled source will not play until it has been + * activated again. + * @param sourcename The source's name. + */ + public void cull( String sourcename ) + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.cull(); + } + +/** + * Activates a previously culled source, so it can be played again. + * @param sourcename The source's name. + */ + public void activate( String sourcename ) + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + { + mySource.activate(); + if( mySource.toPlay ) + play( mySource ); + } + } + +/** + * Sets the overall volume to the specified value, affecting all sources. + * @param value New volume, float value ( 0.0f - 1.0f ). + */ + public void setMasterVolume( float value ) + { + SoundSystemConfig.setMasterGain( value ); + } + +/** + * Manually sets the specified source's volume. + * @param sourcename The source's name. + * @param value A float value ( 0.0f - 1.0f ). + */ + public void setVolume( String sourcename, float value ) + { + if( midiSourcename( sourcename ) ) + { + throw new UnsupportedOperationException("MIDI format not supported without a codec"); + } + else + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + { + float newVolume = value; + if( newVolume < 0.0f ) + newVolume = 0.0f; + else if( newVolume > 1.0f ) + newVolume = 1.0f; + + mySource.sourceVolume = newVolume; + mySource.positionChanged(); + } + } + } + +/** + * Returns the current volume of the specified source, or zero if the specified + * source was not found. + * @param sourcename Source to read volume from. + * @return Float value representing the source volume (0.0f - 1.0f). + */ + public float getVolume( String sourcename ) + { + if( midiSourcename( sourcename ) ) + { + throw new UnsupportedOperationException("MIDI format not supported without a codec"); + } + else + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + return mySource.sourceVolume; + else + return 0.0f; + } + } + +/** + * Manually sets the specified source's pitch. + * @param sourcename The source's name. + * @param value A float value ( 0.5f - 2.0f ). + */ + public void setPitch( String sourcename, float value ) + { + if( !midiSourcename( sourcename ) ) + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + { + float newPitch = value; + if( newPitch < 0.5f ) + newPitch = 0.5f; + else if( newPitch > 2.0f ) + newPitch = 2.0f; + + mySource.setPitch( newPitch ); + mySource.positionChanged(); + } + } + } + +/** + * Returns the pitch of the specified source. + * @param sourcename The source's name. + * @return Float value representing the source pitch (0.5f - 2.0f). + */ + public float getPitch( String sourcename ) + { + if( !midiSourcename( sourcename ) ) + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + return mySource.getPitch(); + } + return 1.0f; + } + +/** + * Moves the listener relative to the current position. + * @param x X offset. + * @param y Y offset. + * @param z Z offset. + */ + public void moveListener( float x, float y, float z ) + { + setListenerPosition( listener.position.x + x, listener.position.y + y, + listener.position.z + z ); + } + +/** + * Changes the listener's position. + * @param x Destination X coordinate. + * @param y Destination Y coordinate. + * @param z Destination Z coordinate. + */ + public void setListenerPosition( float x, float y, float z ) + { + // update listener's position + listener.setPosition( x, y, z ); + + Set<String> keys = sourceMap.keySet(); + Iterator<String> iter = keys.iterator(); + String sourcename; + Source source; + + // loop through and update the volume of all sources: + while( iter.hasNext() ) + { + sourcename = iter.next(); + source = sourceMap.get( sourcename ); + if( source != null ) + source.positionChanged(); + } + } + +/** + * Turn the listener 'angle' radians counterclockwise around the y-Axis, + * relative to the current angle. + * @param angle Angle in radians. + */ + public void turnListener( float angle ) + { + setListenerAngle( listener.angle + angle ); + + Set<String> keys = sourceMap.keySet(); + Iterator<String> iter = keys.iterator(); + String sourcename; + Source source; + + // loop through and update the volume of all sources: + while( iter.hasNext() ) + { + sourcename = iter.next(); + source = sourceMap.get( sourcename ); + if( source != null ) + source.positionChanged(); + } + } + +/** + * Changes the listeners orientation to the specified 'angle' radians + * counterclockwise around the y-Axis. + * @param angle Angle in radians. + */ + public void setListenerAngle( float angle ) + { + listener.setAngle( angle ); + + Set<String> keys = sourceMap.keySet(); + Iterator<String> iter = keys.iterator(); + String sourcename; + Source source; + + // loop through and update the volume of all sources: + while( iter.hasNext() ) + { + sourcename = iter.next(); + source = sourceMap.get( sourcename ); + if( source != null ) + source.positionChanged(); + } + } + +/** + * 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. + */ + public void setListenerOrientation( float lookX, float lookY, float lookZ, + float upX, float upY, float upZ ) + { + listener.setOrientation( lookX, lookY, lookZ, upX, upY, upZ ); + + Set<String> keys = sourceMap.keySet(); + Iterator<String> iter = keys.iterator(); + String sourcename; + Source source; + + // loop through and update the volume of all sources: + while( iter.hasNext() ) + { + sourcename = iter.next(); + source = sourceMap.get( sourcename ); + if( source != null ) + source.positionChanged(); + } + } + +/** + * Changes the listeners position and orientation using the specified listener + * data. + * @param l Listener data to use. + */ + public void setListenerData( ListenerData l ) + { + listener.setData( l ); + } + +/** + * Creates sources based on the source map provided. + * @param srcMap Sources to copy. + */ + public void copySources( HashMap<String, Source> srcMap ) + { + if( srcMap == null ) + return; + Set<String> keys = srcMap.keySet(); + Iterator<String> iter = keys.iterator(); + String sourcename; + Source srcData; + + // remove any existing sources before starting: + sourceMap.clear(); + + // loop through and copy all the sources: + while( iter.hasNext() ) + { + sourcename = iter.next(); + srcData = srcMap.get( sourcename ); + if( srcData != null ) + { + loadSound( srcData.filenameURL ); + sourceMap.put( sourcename, new Source( srcData, null ) ); + } + } + } + +/** + * Stops and deletes the specified source. + * @param sourcename The source's name. + */ + public void removeSource( String sourcename ) + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.cleanup(); // end the source, free memory + sourceMap.remove( sourcename ); + } + +/** + * Searches for and removes all temporary sources that have finished playing. + */ + public void removeTemporarySources() + { + Set<String> keys = sourceMap.keySet(); + Iterator<String> iter = keys.iterator(); + String sourcename; + Source srcData; + + // loop through and cleanup all the sources: + while( iter.hasNext() ) + { + sourcename = iter.next(); + srcData = sourceMap.get( sourcename ); + if( (srcData != null) && (srcData.temporary) + && (!srcData.playing()) ) + { + srcData.cleanup(); // end the source, free memory + iter.remove(); + } + } + } + +/* ########################################################################## */ +/* END OVERRIDE METHODS */ +/* ########################################################################## */ + +/** + * Returns a handle to the next available channel. If the specified + * source is a normal source, a normal channel is returned, and if it is a + * streaming source, then a streaming channel is returned. If all channels of + * the required type are currently playing, then the next channel playing a + * non-priority source is returned. If no channels are available (i.e. they + * are all playing priority sources) then getNextChannel returns null. + * @param source Source to find a channel for. + * @return The next available channel, or null. + */ + private Channel getNextChannel( Source source ) + { + if( source == null ) + return null; + + String sourcename = source.sourcename; + if( sourcename == null ) + return null; + + int x; + int channels; + int nextChannel; + List<Channel> channelList; + String[] sourceNames; + String name; + + if( source.toStream ) + { + nextChannel = nextStreamingChannel; + channelList = streamingChannels; + sourceNames = streamingChannelSourceNames; + } + else + { + nextChannel = nextNormalChannel; + channelList = normalChannels; + sourceNames = normalChannelSourceNames; + } + + channels = channelList.size(); + + // Check if this source is already on a channel: + for( x = 0; x < channels; x++ ) + { + if( sourcename.equals( sourceNames[x] ) ) + return channelList.get( x ); + } + + int n = nextChannel; + Source src; + // Play on the next new or non-playing channel: + for( x = 0; x < channels; x++ ) + { + name = sourceNames[n]; + if( name == null ) + src = null; + else + src = sourceMap.get( name ); + + if( src == null || !src.playing() ) + { + if( source.toStream ) + { + nextStreamingChannel = n + 1; + if( nextStreamingChannel >= channels ) + nextStreamingChannel = 0; + } + else + { + nextNormalChannel = n + 1; + if( nextNormalChannel >= channels ) + nextNormalChannel = 0; + } + sourceNames[n] = sourcename; + return channelList.get( n ); + } + n++; + if( n >= channels ) + n = 0; + } + + n = nextChannel; + // Play on the next non-priority channel: + for( x = 0; x < channels; x++ ) + { + name = sourceNames[n]; + if( name == null ) + src = null; + else + src = sourceMap.get( name ); + + if( src == null || !src.playing() || !src.priority ) + { + if( source.toStream ) + { + nextStreamingChannel = n + 1; + if( nextStreamingChannel >= channels ) + nextStreamingChannel = 0; + } + else + { + nextNormalChannel = n + 1; + if( nextNormalChannel >= channels ) + nextNormalChannel = 0; + } + sourceNames[n] = sourcename; + return channelList.get( n ); + } + n++; + if( n >= channels ) + n = 0; + } + + return null; + } + +/** + * Plays all sources whose 'toPlay' varriable is true but are not currently + * playing (such as sources which were culled while looping and then + * reactivated). + */ + public void replaySources() + { + Set<String> keys = sourceMap.keySet(); + Iterator<String> iter = keys.iterator(); + String sourcename; + Source source; + + // loop through and cleanup all the sources: + while( iter.hasNext() ) + { + sourcename = iter.next(); + source = sourceMap.get( sourcename ); + if( source != null ) + { + if( source.toPlay && !source.playing() ) + { + play( sourcename ); + source.toPlay = false; + } + } + } + } + +/** + * If the specified source is a streaming source or MIDI source, this method + * queues up the next sound to play when the previous playback ends. This + * method has no effect on non-streaming sources. + * @param sourcename Source identifier. + * @param filenameURL Filename/URL of the sound file to play next. + */ + public void queueSound( String sourcename, FilenameURL filenameURL ) + { + if( midiSourcename( sourcename ) ) + { + throw new UnsupportedOperationException("MIDI format not supported without a codec"); + } + else + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.queueSound( filenameURL ); + } + } + +/** + * Removes the first occurrence of the specified filename from the specified + * source's list of sounds to play when previous playback ends. This method + * has no effect on non-streaming sources. + * @param sourcename Source identifier. + * @param filename Filename/identifier of the sound file to remove from the queue. + */ + public void dequeueSound( String sourcename, String filename ) + { + if( midiSourcename( sourcename ) ) + { + throw new UnsupportedOperationException("MIDI format not supported without a codec"); + } + else + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.dequeueSound( filename ); + } + } + +/** + * Fades out the volume of whatever the specified source is currently playing, + * then begins playing the specified file at the source's previously + * assigned volume level. If the filenameURL parameter is null or empty, the + * specified source will simply fade out and stop. The miliseconds parameter + * must be non-negative or zero. This method will remove anything that is + * currently in the specified source's list of queued sounds that would have + * played next when the current sound finished playing. This method may only + * be used for streaming and MIDI sources. + * @param sourcename Name of the source to fade out. + * @param filenameURL Filename/URL of the sound file to play next, or null for none. + * @param milis Number of miliseconds the fadeout should take. + */ + public void fadeOut( String sourcename, FilenameURL filenameURL, + long milis ) + { + if( midiSourcename( sourcename ) ) + { + throw new UnsupportedOperationException("MIDI format not supported without a codec"); + } + else + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.fadeOut( filenameURL, milis ); + } + } + +/** + * Fades out the volume of whatever the specified source is currently playing, + * then fades the volume back in playing the specified file. Final volume + * after fade-in completes will be equal to the source's previously assigned + * volume level. The filenameURL parameter may not be null or empty. The + * miliseconds parameters must be non-negative or zero. This method will + * remove anything that is currently in the specified source's list of queued + * sounds that would have played next when the current sound finished playing. + * This method may only be used for streaming and MIDI sources. + * @param sourcename Name of the source to fade out/in. + * @param filenameURL Filename/URL of the sound file to play next, or null for none. + * @param milisOut Number of miliseconds the fadeout should take. + * @param milisIn Number of miliseconds the fadein should take. + */ + public void fadeOutIn( String sourcename, FilenameURL filenameURL, + long milisOut, long milisIn ) + { + if( midiSourcename( sourcename ) ) + { + throw new UnsupportedOperationException("MIDI format not supported without a codec"); + } + else + { + Source mySource = sourceMap.get( sourcename ); + if( mySource != null ) + mySource.fadeOutIn( filenameURL, milisOut, milisIn ); + } + } + +/** + * Makes sure the current volume levels of streaming sources and MIDI are + * correct. This method is designed to help reduce the "jerky" fading behavior + * that happens when using some library and codec pluggins (such as + * LibraryJavaSound and CodecJOrbis). This method has no effect on normal + * "non-streaming" sources. It would normally be called somewhere in the main + * "game loop". IMPORTANT: To optimize frame-rates, do not call this method + * for every frame. It is better to just call this method at some acceptable + * "granularity" (play around with different granularities to find what sounds + * acceptable for a particular situation). + */ + public void checkFadeVolumes() + { + Channel c; + Source s; + for( int x = 0; x < streamingChannels.size(); x++ ) + { + c = streamingChannels.get( x ); + if( c != null ) + { + s = c.attachedSource; + if( s != null ) + s.checkFadeOut(); + } + } + c = null; + s = null; + } + +/** + * Loads the specified MIDI file, and saves the source information about it. + * @param toLoop Midi file should loop or play once. + * @param sourcename Source identifier. + * @param filenameURL Filename/URL of the MIDI file to load. + */ + public void loadMidi( boolean toLoop, String sourcename, + FilenameURL filenameURL ) + { + if( filenameURL == null ) + { + errorMessage( "Filename/URL not specified in method 'loadMidi'." ); + return; + } + + if( !filenameURL.getFilename().matches( + SoundSystemConfig.EXTENSION_MIDI ) ) + { + errorMessage( "Filename/identifier doesn't end in '.mid' or" + + "'.midi' in method loadMidi." ); + return; + } + + throw new UnsupportedOperationException("MIDI format not supported without a codec"); + } + +/** + * Unloads the current Midi file. + */ + public void unloadMidi() + { + throw new UnsupportedOperationException("MIDI format not supported without a codec"); + } + +/** + * Checks if the sourcename matches the midi source. + * @param sourcename Source identifier. + * @return True if sourcename and midi sourcename match. + */ + public boolean midiSourcename( String sourcename ) + { + return false; + } + +/** + * + * Returns the Source object identified by the specified name. + * @param sourcename The source's name. + * @return The source, or null if not found. + */ + public Source getSource( String sourcename ) + { + return sourceMap.get( sourcename ); + } + +/** + * Tells all the sources that the listener has moved. + */ + public void listenerMoved() + { + Set<String> keys = sourceMap.keySet(); + Iterator<String> iter = keys.iterator(); + String sourcename; + Source srcData; + + // loop through and copy all the sources: + while( iter.hasNext() ) + { + sourcename = iter.next(); + srcData = sourceMap.get( sourcename ); + if( srcData != null ) + { + srcData.listenerMoved(); + } + } + } + +/** + * Returns the sources map. + * @return Map of all sources. + */ + public HashMap<String, Source> getSources() + { + return sourceMap; + } + +/** + * Returns information about the listener. + * @return A ListenerData object. + */ + public ListenerData getListenerData() + { + return listener; + } + +/** + * Indicates whether or not this library requires some codecs to reverse-order + * the audio data they generate. + * @return True if audio data should be reverse-ordered. + */ + public boolean reverseByteOrder() + { + return reverseByteOrder; + } +/** + * Returns the short title of this library type. + * @return A short title. + */ + public static String getTitle() + { + return "No Sound"; + } + +/** + * Returns a longer description of this library type. + * @return A longer description. + */ + public static String getDescription() + { + return "Silent Mode"; + } + +/** + * Returns the name of the class. + * @return "Library" + library title. + */ + public String getClassName() + { + return "Library"; + } + +/** + * Prints a message. + * @param message Message to print. + */ + protected void message( String message ) + { + logger.message( message, 0 ); + } + +/** + * Prints an important message. + * @param message Message to print. + */ + protected void importantMessage( String message ) + { + logger.importantMessage( message, 0 ); + } + +/** + * Prints the specified message if error is true. + * @param error True or False. + * @param message Message to print if error is true. + * @return True if error is true. + */ + protected boolean errorCheck( boolean error, String message ) + { + return logger.errorCheck( error, getClassName(), message, 0 ); + } + +/** + * Prints an error message. + * @param message Message to print. + */ + protected void errorMessage( String message ) + { + logger.errorMessage( getClassName(), message, 0 ); + } + +/** + * Prints an exception's error message followed by the stack trace. + * @param e Exception containing the information to print. + */ + protected void printStackTrace( Exception e ) + { + logger.printStackTrace( e, 1 ); + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/ListenerData.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/ListenerData.java new file mode 100644 index 0000000..94e2ac6 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/ListenerData.java @@ -0,0 +1,291 @@ +/** + * 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; + +/** + * The listenerData class is used to store information about the + * listener's position and orientation. A ListenerData object can be obtained + * using SoundSystem's getListenerData() method. See + * {@link com.ardor3d.audio.Vector3D Vector3D} for more information about 3D + * coordinates and vectors. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 ListenerData +{ +/** + * Listener's position in 3D space + */ + public Vector3D position; +/** + * A normalized vector indicating the direction the listener is facing + */ + public Vector3D lookAt; +/** + * A normalized vector indicating the up direction + */ + public Vector3D up; +/** + * Listener's velocity in world-space + */ + public Vector3D velocity; + +/** + * Used for easy rotation along the x/z plane (for use in a first-person + * shooter type of application). + */ + public float angle = 0.0f; + +/** + * Constructor: Set this listener data to the origin facing along the z-axis + */ + public ListenerData() + { + position = new Vector3D( 0.0f, 0.0f, 0.0f ); + lookAt = new Vector3D( 0.0f, 0.0f, -1.0f ); + up = new Vector3D( 0.0f, 1.0f, 0.0f ); + velocity = new Vector3D( 0.0f, 0.0f, 0.0f ); + angle = 0.0f; + } + +/** + * Constructor: Set this listener data to the specified values for position and + * orientation. + * @param pX Listener's X coordinate. + * @param pY Listener's Y coordinate. + * @param pZ Listener's Z coordinate. + * @param lX X element of the look-at direction. + * @param lY Y element of the look-at direction. + * @param lZ Z element of the look-at direction. + * @param uX X element of the up direction. + * @param uY Y element of the up direction. + * @param uZ Z element of the up direction. + * @param a Angle in radians that the listener is turned counterclockwise around the y-axis. + */ + public ListenerData( float pX, float pY, float pZ, float lX, float lY, + float lZ, float uX, float uY, float uZ, float a ) + { + position = new Vector3D( pX, pY, pZ ); + lookAt = new Vector3D( lX, lY, lZ ); + up = new Vector3D( uX, uY, uZ ); + velocity = new Vector3D( 0.0f, 0.0f, 0.0f ); + angle = a; + } + +/** + * Constructor: Set this listener data to the specified values for position and + * orientation. + * @param p Position of the listener in 3D space. + * @param l Normalized vector indicating the direction which the listener is facing. + * @param u Normalized vector indicating the up direction. + * @param a Angle in radians that the listener is turned counterclockwise around the y-axis. + */ + public ListenerData( Vector3D p, Vector3D l, Vector3D u, float a ) + { + position = p.clone(); + lookAt = l.clone(); + up = u.clone(); + velocity = new Vector3D( 0.0f, 0.0f, 0.0f ); + angle = a; + } + +/** + * Change this listener data using the specified coordinates for position and + * orientation. + * @param pX Listener's X coordinate. + * @param pY Listener's Y coordinate. + * @param pZ Listener's Z coordinate. + * @param lX X element of the look-at direction. + * @param lY Y element of the look-at direction. + * @param lZ Z element of the look-at direction. + * @param uX X element of the up direction. + * @param uY Y element of the up direction. + * @param uZ Z element of the up direction. + * @param a Angle in radians that the listener is turned counterclockwise around the y-axis. + */ + public void setData( float pX, float pY, float pZ, float lX, float lY, + float lZ, float uX, float uY, float uZ, float a ) + { + position.x = pX; + position.y = pY; + position.z = pZ; + lookAt.x = lX; + lookAt.y = lY; + lookAt.z = lZ; + up.x = uX; + up.y = uY; + up.z = uZ; + angle = a; + } + +/** + * Change this listener data using the specified 3D vectors for position and + * orientation. + * @param p Position of the listener in 3D space. + * @param l Normalized vector indicating the direction which the listener is facing. + * @param u Normalized vector indicating the up direction. + * @param a Angle in radians that the listener is turned counterclockwise around the y-axis. + */ + public void setData( Vector3D p, Vector3D l, Vector3D u, float a ) + { + position.x = p.x; + position.y = p.y; + position.z = p.z; + lookAt.x = l.x; + lookAt.y = l.y; + lookAt.z = l.z; + up.x = u.x; + up.y = u.y; + up.z = u.z; + angle = a; + } + +/** + * Change this listener data to match the specified listener data. + * @param l Listener data to use. + */ + public void setData( ListenerData l ) + { + position.x = l.position.x; + position.y = l.position.y; + position.z = l.position.z; + lookAt.x = l.lookAt.x; + lookAt.y = l.lookAt.y; + lookAt.z = l.lookAt.z; + up.x = l.up.x; + up.y = l.up.y; + up.z = l.up.z; + angle = l.angle; + } + +/** + * Change this listener's position using the specified coordinates. + * @param x Listener's X coordinate. + * @param y Listener's Y coordinate. + * @param z Listener's Z coordinate. + */ + public void setPosition( float x, float y, float z ) + { + position.x = x; + position.y = y; + position.z = z; + } + +/** + * Change this listener's position using the specified vector. + * @param p New position. + */ + public void setPosition( Vector3D p ) + { + position.x = p.x; + position.y = p.y; + position.z = p.z; + } + +/** + * Changes the listeners orientation using the specified coordinates. + * @param lX X element of the look-at direction. + * @param lY Y element of the look-at direction. + * @param lZ Z element of the look-at direction. + * @param uX X element of the up direction. + * @param uY Y element of the up direction. + * @param uZ Z element of the up direction. + */ + public void setOrientation( float lX, float lY, float lZ, + float uX, float uY, float uZ ) + { + lookAt.x = lX; + lookAt.y = lY; + lookAt.z = lZ; + up.x = uX; + up.y = uY; + up.z = uZ; + } + +/** + * Changes the listeners orientation using the specified vectors. + * @param l Normalized vector representing the look-at direction. + * @param u Normalized vector representing the up direction. + */ + public void setOrientation( Vector3D l, Vector3D u ) + { + lookAt.x = l.x; + lookAt.y = l.y; + lookAt.z = l.z; + up.x = u.x; + up.y = u.y; + up.z = u.z; + } + +/** + * Change this listener's velocity in world-space. + * @param v New velocity. + */ + public void setVelocity( Vector3D v ) + { + velocity.x = v.x; + velocity.y = v.y; + velocity.z = v.z; + } + +/** + * Change this listener's velocity in world-space. + * @param x New velocity along world x-axis. + * @param y New velocity along world y-axis. + * @param z New velocity along world z-axis. + */ + public void setVelocity( float x, float y, float z ) + { + velocity.x = x; + velocity.y = y; + velocity.z = z; + } + +/** + * Sets the listener's angle counterclockwise around the y-axis. + * @param a Angle in radians. + */ + public void setAngle( float a ) + { + angle = a; + lookAt.x = -1.0f * (float) Math.sin( angle ); + lookAt.z = -1.0f * (float) Math.cos( angle ); + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/SimpleThread.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/SimpleThread.java new file mode 100644 index 0000000..72dbb38 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/SimpleThread.java @@ -0,0 +1,209 @@ +/** + * 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; + + +/** + * The SimpleThread class is the template used to create all thread classes + * used by in the SoundSystem library. It provides methods for common actions + * like sleeping, killing, and checking liveness. NOTE: super.cleanup() must + * be called at the bottom of overriden cleanup() methods, and cleanup() + * must be called at the bottom of the run() method for all extended classes. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 SimpleThread extends Thread +{ +/** + * 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; + +/** + * True when thread is running. + */ + private boolean alive = true; + +/** + * True when thread should end. + */ + private boolean kill = false; + +/** + * Removes all references to instantiated objects, and changes the thread's + * state to "not alive". Method alive() returns false when this method has + * completed. NOTE: super.cleanup() must be called at the bottom of overriden + * cleanup() methods, and cleanup() must be called at the bottom of the run() + * method for all extended classes. + */ + protected void cleanup() + { + kill( SET, true ); // tread needs to shut down + alive( SET, false ); // thread has ended + } + +/** + * Executes the thread's main loop. NOTES: Extended classes should check + * method dying() often to know when the user wants the thread to shut down. + * Method cleanup() must be called at the bottom of the run() method for all + * extended classes. + */ + @Override + public void run() + { + /* How the run() method should be set up: */ + + + // Do your stuff here. Remember to check dying() often to know when + // the user wants the thread to shut down. + + // MUST call cleanup() at the bottom of Overridden run() method!!!!! + cleanup(); // clears memory and sets status to dead. + } + +/** + * Calls the rerun() method on a seperate thread, which calls run() when the + * previous thread finishes. + */ + public void restart() + { + new Thread() + { + @Override + public void run() + { + rerun(); + } + }.start(); + } + +/** + * Kills the previous thread, waits for it to die, then calls run(). + */ + private void rerun() + { + kill( SET, true ); + while( alive( GET, XXX ) ) + { + snooze( 100 ); + } + alive( SET, true ); + kill( SET, false ); + run(); + } + +/** + * Returns false when the cleanup() method has finished. This method should be + * used to know when the thread has been safely shut down. + * @return True while the thread is alive. + */ + public boolean alive() + { + return alive( GET, XXX ); + } + +/** + * Causes method dying() to return true, letting the thread know it needs to + * shut down. + */ + public void kill() + { + kill( SET, true ); + } + +/** + * Returns true when the thread is supposed to shut down. + * @return True if the thread should die. + */ + protected boolean dying() + { + return kill( GET, XXX ); + } + +/** + * Sets or returns the value of boolean 'alive'. + * @param action GET or SET. + * @param value New value if action == SET, or XXX if action == GET. + * @return True while the thread is alive. + */ + private synchronized boolean alive( boolean action, boolean value ) + { + if( action == SET ) + alive = value; + return alive; + } + +/** + * Sets or returns the value of boolean 'kill'. + * @param action GET or SET. + * @param value New value if action == SET, or XXX if action == GET. + * @return True if the thread should die. + */ + private synchronized boolean kill( boolean action, boolean value ) + { + if( action == SET ) + kill = value; + return kill; + } + +/** + * Sleeps for the specified number of milliseconds. + */ + protected void snooze( long milliseconds ) + { + try + { + Thread.sleep( milliseconds ); + } + catch( InterruptedException e ){} + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundBuffer.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundBuffer.java new file mode 100644 index 0000000..047e926 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundBuffer.java @@ -0,0 +1,100 @@ +/** + * 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; + +import com.ardor3d.audio.sampled.AudioFormat; + +/** + * The SoundBuffer class is used to wrap audio data along with the format in + * which the data is stored. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 SoundBuffer +{ +/** + * The actual audio data. + */ + public byte[] audioData; +/** + * The audio format in which the data is stored. + */ + public AudioFormat audioFormat; + +/** + * Constructor: Wraps the specified data with the specified audio format. + * + * @param audioData The actual audio data. + * @param audioFormat The audio format in which the data is stored. + */ + public SoundBuffer( byte[] audioData, AudioFormat audioFormat ) + { + this.audioData = audioData; + this.audioFormat = audioFormat; + } + +/** + * Removes handles to all instantiated objects. + */ + public void cleanup() + { + audioData = null; + audioFormat = null; + } + +/** + * Trims down the size of the audio data if it is larger than the specified + * maximum length. + * + * @param maxLength Maximum size this buffer may be. + */ + public void trimData( int maxLength ) + { + if( audioData == null || maxLength == 0 ) + audioData = null; + else if( audioData.length > maxLength ) + { + byte[] trimmedArray = new byte[maxLength]; + System.arraycopy( audioData, 0, trimmedArray, 0, + maxLength ); + audioData = trimmedArray; + } + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundSystem.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundSystem.java new file mode 100644 index 0000000..8f09183 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundSystem.java @@ -0,0 +1,2893 @@ +/** + * 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; + +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Random; +import java.util.Set; +import com.ardor3d.audio.sampled.AudioFormat; + +/** + * The SoundSystem class is the core class for the SoundSystem library. It is + * capable of interfacing with external sound library and codec library + * pluggins. This core class is stripped down to give it a smaller memory + * footprint and to make it more customizable. This library was created to + * provide a simple, common interface to a variety of 3rd-party sound and codec + * libraries, and to simplify switching between them on the fly. If no + * external pluggins are loaded, this core class by itself is only capable of + * playing MIDI files. Specific implementations (such as SoundSystemJPCT) will + * extend this core class. They will automatically link with popular + * external pluggins and provide extra methods for ease of use. + * There should be only one instance of this class in any program! The + * SoundSystem can be constructed by defining which sound library to use, or by + * allowing SoundSystem to perform its own library compatibility checking. See + * {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for information + * about changing default settings and linking with external pluggins. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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> + * + * Julien Gouesse: I removed Midi support in order to stop depending on javax.sound more easily and I replaced a deprecated call + */ +public class SoundSystem +{ +/** + * 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; + +/** + * Processes status messages, warnings, and error messages. + */ + protected SoundSystemLogger logger; + +/** + * Handle to the active sound library. + */ + protected Library soundLibrary; + +/** + * List of queued commands to perform. + */ + protected List<CommandObject> commandQueue; + +/** + * Used internally by SoundSystem to keep track of play/pause/stop/rewind + * commands. This prevents source management (culling and activating) from + * being adversely affected by the quickPlay, quickStream, and backgroundMusic + * methods. + */ + private List<CommandObject> sourcePlayList; + +/** + * Processes queued commands in the background. + */ + protected CommandThread commandThread; + +/** + * Generates random numbers. + */ + public Random randomNumberGenerator; + +/** + * Name of this class. + */ + protected String className = "SoundSystem"; + +/** + * Indicates the currently loaded sound-library, or null if none. + */ + private static Class currentLibrary = null; + +/** + * Becomes true when the sound library has been initialized. + */ + private static boolean initialized = false; + +/** + * Indicates the last exception that was thrown. + */ + private static SoundSystemException lastException = null; + +/** + * Constructor: Create the sound system using the default library. If the + * default library is not compatible, another library type will be loaded + * instead, in the order of library preference. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for + * information about sound library types. + */ + public SoundSystem() + { + // create the message logger: + logger = SoundSystemConfig.getLogger(); + // if the user didn't create one, then do it now: + if( logger == null ) + { + logger = new SoundSystemLogger(); + SoundSystemConfig.setLogger( logger ); + } + + linkDefaultLibrariesAndCodecs(); + + LinkedList<Class> libraries = SoundSystemConfig.getLibraries(); + + if( libraries != null ) + { + ListIterator<Class> i = libraries.listIterator(); + Class c; + while( i.hasNext() ) + { + c = i.next(); + try + { + init( c ); + return; + } + catch( SoundSystemException sse ) + { + logger.printExceptionMessage( sse, 1 ); + } + } + } + try + { + init( Library.class ); + return; + } + catch( SoundSystemException sse ) + { + logger.printExceptionMessage( sse, 1 ); + } + } + +/** + * Constructor: Create the sound system using the specified library. + * @param libraryClass Library to use. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for + * information about chosing a sound library. + */ + public SoundSystem( Class libraryClass ) throws SoundSystemException + { + // create the message logger: + logger = SoundSystemConfig.getLogger(); + // if the user didn't create one, then do it now: + if( logger == null ) + { + logger = new SoundSystemLogger(); + SoundSystemConfig.setLogger( logger ); + } + linkDefaultLibrariesAndCodecs(); + + init( libraryClass ); + } + +/** + * Links with any default libraries or codecs should be made in this method. + * This method is empty in the core SoundSystem class, and should be overriden + * by classes which extend SoundSystem. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for + * information about linking with sound libraries and codecs. + */ + protected void linkDefaultLibrariesAndCodecs() + { + } + +/** + * Loads the message logger, initializes the specified sound library, and + * starts the command thread. Also instantiates the random number generator + * and the command queue. + * @param libraryClass Library to initialize. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for + * information about chosing a sound library. + */ + protected void init( Class libraryClass ) throws SoundSystemException + { + message( "", 0 ); + message( "Starting up " + className + "...", 0 ); + + // create the random number generator: + randomNumberGenerator = new Random(); + // create the command queue: + commandQueue = new LinkedList<CommandObject>(); + // create the working source playlist: + sourcePlayList = new LinkedList<CommandObject>(); + + // Instantiate and start the Command Processer thread: + commandThread = new CommandThread( this ); // Gets a SoundSystem handle + commandThread.start(); + + snooze( 200 ); + + newLibrary( libraryClass ); + message( "", 0 ); + } + +/** + * Ends the command thread, shuts down the sound system, and removes references + * to all instantiated objects. + */ + public void cleanup() + { + boolean killException = false; + message( "", 0 ); + message( className + " shutting down...", 0 ); + + // End the command thread: + try + { + commandThread.kill(); // end the command processor loop. + commandThread.interrupt(); // wake the thread up so it can end. + } + catch( Exception e ) + { + killException = true; + } + + if( !killException ) + { + // wait up to 5 seconds for command thread to end: + for( int i = 0; i < 50; i++ ) + { + if( !commandThread.alive() ) + break; + snooze( 100 ); + } + } + + // Let user know if there was a problem ending the command thread + if( killException || commandThread.alive() ) + { + errorMessage( "Command thread did not die!", 0 ); + message( "Ignoring errors... continuing clean-up.", 0 ); + } + + initialized( SET, false ); + currentLibrary( SET, null ); + try + { + // Stop all sources and shut down the sound library: + if( soundLibrary != null ) + soundLibrary.cleanup(); + } + catch( Exception e ) + { + errorMessage( "Problem during Library.cleanup()!", 0 ); + message( "Ignoring errors... continuing clean-up.", 0 ); + } + + try + { + // remove any queued commands: + if( commandQueue != null ) + commandQueue.clear(); + } + catch( Exception e ) + { + errorMessage( "Unable to clear the command queue!", 0 ); + message( "Ignoring errors... continuing clean-up.", 0 ); + } + + try + { + // empty the source management list: + if( sourcePlayList != null ) + sourcePlayList.clear(); + } + catch( Exception e ) + { + errorMessage( "Unable to clear the source management list!", 0 ); + message( "Ignoring errors... continuing clean-up.", 0 ); + } + + // Remove references to all instantiated objects: + randomNumberGenerator = null; + soundLibrary = null; + commandQueue = null; + sourcePlayList = null; + commandThread = null; + + importantMessage( "Author: Paul Lamb, www.paulscode.com", 1 ); + message( "", 0 ); + } + +/** + * Wakes up the Command Thread to process commands. This method should be + * used if there is a need to call methods 'ManageSources' and 'CommandQueue' + * externally. In most cases, this method will not be needed, since + * SoundSystem automatically wakes the Command Thread every time commands are + * placed into either the ManageSources queue or CommandQueue to be processed. + */ + public void interruptCommandThread() + { + if( commandThread == null ) + { + errorMessage( "Command Thread null in method " + + "'interruptCommandThread'", 0 ); + return; + } + // Wake the command thread to process commands: + commandThread.interrupt(); + } + +/** + * Pre-loads a sound into memory. The file may either be located within the + * JAR or at an online location. If the file is online, filename must begin + * with "http://", since that is how SoundSystem recognizes URL's. If the file + * is located within the compiled JAR, the package in which sound files are + * located may be set by calling SoundSystemConfig.setSoundFilesPackage(). + * @param filename Filename of the sound file to load. + */ + public void loadSound( String filename ) + { + // Queue a command to load the sound file: + CommandQueue( new CommandObject( CommandObject.LOAD_SOUND, + new FilenameURL( filename ) ) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + } + +/** + * Pre-loads a sound specified by the given URL into memory. The second + * parameter 'identifier' should look like a filename, and it must have the + * correct extension so SoundSystem knows what codec to use for the file + * referenced by the URL instance. + * @param url URL handle to the sound file to load. + * @param identifier Filename/identifier of the file referenced by the URL. + */ + public void loadSound( URL url, String identifier ) + { + // Queue a command to load the sound file from a URL: + CommandQueue( new CommandObject( CommandObject.LOAD_SOUND, + new FilenameURL( url, identifier ) ) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + } + +/** + * Saves raw PCM audio data in the specified audio format, under the specified + * identifier. This identifier can be later used in place of 'filename' + * parameters to reference the sample data. + * @param data The sample data + * @param format Format the sample data is stored in + * @param identifier What to call the sample. + */ + public void loadSound( byte[] data, AudioFormat format, String identifier ) + { + // Queue a command to load the sound file from a URL: + CommandQueue( new CommandObject( CommandObject.LOAD_DATA, + identifier, + new SoundBuffer( data, format ) ) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + } + + +/** + * 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 calling this method as long as the + * sound is attached to an existing source. When calling this method, calls + * should also be made to method removeSource( String ) for all sources which + * this sound is bound to. + * @param filename Filename/identifier of the sound file to unload. + */ + public void unloadSound( String filename ) + { + // Queue a command to unload the sound file: + CommandQueue( new CommandObject( CommandObject.UNLOAD_SOUND, filename ) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + } + +/** + * If the specified source is a streaming source or MIDI source, this method + * queues up the next sound to play when the previous playback ends. The file + * may either be located within the JAR or at an online location. If the file + * is online, filename must begin with "http://", since that is how SoundSystem + * recognizes URL paths. If the file is located within the compiled JAR, the + * package in which sound files are located may be set by calling + * SoundSystemConfig.setSoundFilesPackage(). This method has no effect on + * non-streaming sources. + * @param sourcename Source identifier. + * @param filename Name of the sound file to play next. + */ + public void queueSound( String sourcename, String filename ) + { + // Queue a command to queue the sound: + CommandQueue( new CommandObject( CommandObject.QUEUE_SOUND, sourcename, + new FilenameURL( filename ) ) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + } + +/** + * If the specified source is a streaming source or MIDI source, this method + * queues up the next sound to play when the previous playback ends. The third + * parameter 'identifier' should look like a filename, and it must have the + * correct extension so SoundSystem knows what codec to use for the file + * referenced by the URL instance. This method has no effect on non-streaming + * sources. + * @param sourcename Source identifier. + * @param url URL handle to the sound file to load. + * @param identifier Filename/identifier of the file referenced by the URL. + */ + public void queueSound( String sourcename, URL url, String identifier ) + { + // Queue a command to queue the sound: + CommandQueue( new CommandObject( CommandObject.QUEUE_SOUND, sourcename, + new FilenameURL( url, identifier ) ) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + } + +/** + * Removes the first occurrence of the specified filename/identifier from the + * specified source's list of sounds to play when previous playback ends. This + * method has no effect on non-streaming sources. + * @param sourcename Source identifier. + * @param filename Filename/identifier of the sound file to play next. + */ + public void dequeueSound( String sourcename, String filename ) + { + // Queue a command to dequeue the sound: + CommandQueue( new CommandObject( CommandObject.DEQUEUE_SOUND, + sourcename, filename ) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + } + +/** + * Fades out the volume of whatever the specified source is currently playing, + * then begins playing the specified file at the source's previously + * assigned volume level. The file may either be located within the JAR or at + * an online location. If the file is online, filename must begin with + * "http://", since that is how SoundSystem recognizes URL paths. If the file + * is located within the compiled JAR, the package in which sound files are + * located may be set by calling SoundSystemConfig.setSoundFilesPackage(). If + * the filename parameter is null or empty, the specified source will simply + * fade out and stop. The miliseconds parameter must be non-negative or zero. + * This method will remove anything that is currently in the specified source's + * list of queued sounds that would have played next when the current sound + * finished playing. This method may only be used for streaming and MIDI + * sources. + * @param sourcename Name of the source to fade out. + * @param filename Name of a sound file to play next, or null for none. + * @param milis Number of miliseconds the fadeout should take. + */ + public void fadeOut( String sourcename, String filename, long milis ) + { + FilenameURL fu = null; + if( filename != null ) + fu = new FilenameURL( filename ); + // Queue a command to fade out: + CommandQueue( new CommandObject( CommandObject.FADE_OUT, sourcename, fu, + milis ) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + } + +/** + * Fades out the volume of whatever the specified source is currently playing, + * then begins playing the specified file at the source's previously + * assigned volume level. If the url parameter is null or empty, the + * specified source will simply fade out and stop. The third + * parameter 'identifier' should look like a filename, and it must have the + * correct extension so SoundSystem knows what codec to use for the file + * referenced by the URL instance. The miliseconds parameter must be + * non-negative or zero. This method will remove anything that is currently in + * the specified source's list of queued sounds that would have played next + * when the current sound finished playing. This method may only be used for + * streaming and MIDI sources. + * @param sourcename Name of the source to fade out. + * @param url URL handle to the sound file to play next, or null for none. + * @param identifier Filename/identifier of the file referenced by the URL. + * @param milis Number of miliseconds the fadeout should take. + */ + public void fadeOut( String sourcename, URL url, String identifier, + long milis ) + { + FilenameURL fu = null; + if( url != null && identifier != null ) + fu = new FilenameURL( url, identifier ); + // Queue a command to fade out: + CommandQueue( new CommandObject( CommandObject.FADE_OUT, sourcename, fu, + milis ) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + } + +/** + * Fades out the volume of whatever the specified source is currently playing, + * then fades the volume back in playing the specified filename. Final volume + * after fade-in completes will be equal to the source's previously assigned + * volume level. The filename parameter may not be null or empty. The file + * may either be located within the JAR or at an online location. If the file + * is online, filename must begin with "http://", since that is how + * SoundSystem recognizes URL paths. If the file is located within the + * compiled JAR, the package in which sound files are located may be set by + * calling SoundSystemConfig.setSoundFilesPackage(). The miliseconds + * parameters must be non-negative or zero. This method will remove anything + * that is currently in the specified source's list of queued sounds that would + * have played next when the current sound finished playing. This method may + * only be used for streaming and MIDI sources. + * @param sourcename Name of the source to fade out/in. + * @param filename Name of a sound file to play next, or null for none. + * @param milisOut Number of miliseconds the fadeout should take. + * @param milisIn Number of miliseconds the fadein should take. + */ + public void fadeOutIn( String sourcename, String filename, long milisOut, + long milisIn ) + { + // Queue a command to load the sound file: + CommandQueue( new CommandObject( CommandObject.FADE_OUT_IN, + sourcename, + new FilenameURL( filename ), milisOut, + milisIn ) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + } + +/** + * Fades out the volume of whatever the specified source is currently playing, + * then fades the volume back in playing the specified file. Final volume + * after fade-in completes will be equal to the source's previously assigned + * volume level. The url parameter may not be null or empty. The third + * parameter 'identifier' should look like a filename, and it must have the + * correct extension so SoundSystem knows what codec to use for the file + * referenced by the URL instance. The miliseconds parameters must be + * non-negative or zero. This method will remove anything that is currently + * in the specified source's list of queued sounds that would have played next + * when the current sound finished playing. This method may only be used for + * streaming and MIDI sources. + * @param sourcename Name of the source to fade out/in. + * @param url URL handle to the sound file to play next. + * @param identifier Filename/identifier of the file referenced by the URL. + * @param milisOut Number of miliseconds the fadeout should take. + * @param milisIn Number of miliseconds the fadein should take. + */ + public void fadeOutIn( String sourcename, URL url, String identifier, + long milisOut, long milisIn ) + { + // Queue a command to load the sound file: + CommandQueue( new CommandObject( CommandObject.FADE_OUT_IN, + sourcename, + new FilenameURL( url, identifier ), + milisOut, milisIn ) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + } + +/** + * Makes sure the current volume levels of streaming sources and MIDI are + * correct. This method is designed to help reduce the "jerky" fading behavior + * that happens when using some library and codec pluggins (such as + * LibraryJavaSound and CodecJOrbis). This method has no effect on normal + * "non-streaming" sources. It would normally be called somewhere in the main + * "game loop". IMPORTANT: To optimize frame-rates, do not call this method + * for every frame. It is better to just call this method at some acceptable + * "granularity" (play around with different granularities to find what sounds + * acceptable for a particular situation). + */ + public void checkFadeVolumes() + { + // Queue a command to load check fading source volumes: + CommandQueue( new CommandObject( CommandObject.CHECK_FADE_VOLUMES ) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + } + +/** + * Creates a new permanant, streaming, priority source with zero attenuation. + * The file may either be located within the JAR or at an online location. If + * the file is online, filename must begin with "http://", since that is how + * SoundSystem recognizes URL paths. If the file is located within the + * compiled JAR, the package in which sound files are located may be set by + * calling SoundSystemConfig.setSoundFilesPackage(). + * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename. + * @param filename Filename of the sound file to stream at this source. + * @param toLoop Should this source loop, or play only once. + */ + public void backgroundMusic( String sourcename, String filename, + boolean toLoop ) + { + // Queue a command to quick stream a new source: + CommandQueue( new CommandObject( CommandObject.QUICK_PLAY, true, + true, toLoop, sourcename, + new FilenameURL( filename ), 0, 0, 0, + SoundSystemConfig.ATTENUATION_NONE, 0, false ) ); + CommandQueue( new CommandObject( CommandObject.PLAY, sourcename) ); + + commandThread.interrupt(); + } + +/** + * Creates a new permanant, streaming, priority source with zero attenuation. + * The third parameter 'identifier' should look like a filename, and it must + * have the correct extension so SoundSystem knows what codec to use for the + * file referenced by the URL instance. + * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename. + * @param url URL handle to the sound file to stream at this source. + * @param identifier Filename/identifier of the file referenced by the URL. + * @param toLoop Should this source loop, or play only once. + */ + public void backgroundMusic( String sourcename, URL url, String identifier, + boolean toLoop ) + { + // Queue a command to quick stream a new source: + CommandQueue( new CommandObject( CommandObject.QUICK_PLAY, true, + true, toLoop, sourcename, + new FilenameURL( url, identifier ), + 0, 0, 0, + SoundSystemConfig.ATTENUATION_NONE, + 0, false ) ); + CommandQueue( new CommandObject( CommandObject.PLAY, sourcename) ); + + commandThread.interrupt(); + } + +/** + * Creates a new non-streaming source. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about Attenuation, fade distance, and rolloff factor. + * @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 filename Filename/identifier of the sound file to play at this source. + * @param toLoop Should this source loop, or play only once. + * @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". + */ + public void newSource( boolean priority, String sourcename, String filename, + boolean toLoop, float x, float y, float z, + int attmodel, float distOrRoll ) + { + CommandQueue( new CommandObject( CommandObject.NEW_SOURCE, priority, + false, toLoop, sourcename, + new FilenameURL( filename ), x, y, z, + attmodel, distOrRoll ) ); + commandThread.interrupt(); + } + +/** + * Creates a new non-streaming source. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about Attenuation, fade distance, and rolloff factor. + * @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 url URL handle to the sound file to stream at this source. + * @param identifier Filename/identifier of the file referenced by the URL. + * @param toLoop Should this source loop, or play only once. + * @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". + */ + public void newSource( boolean priority, String sourcename, URL url, + String identifier, boolean toLoop, float x, float y, + float z, int attmodel, float distOrRoll ) + { + CommandQueue( new CommandObject( CommandObject.NEW_SOURCE, priority, + false, toLoop, sourcename, + new FilenameURL( url, identifier ), + x, y, z, + attmodel, distOrRoll ) ); + commandThread.interrupt(); + } + +/** + * Creates a new streaming source. The file may either be located within the + * JAR or at an online location. If the file is online, filename must begin + * with "http://", since that is how SoundSystem recognizes URL paths. If the + * file is located within the compiled JAR, the package in which sound files + * are located may be set by calling SoundSystemConfig.setSoundFilesPackage(). + * @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 filename The filename of the sound file to play at this source. + * @param toLoop Should this source loop, or play only once. + * @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". + */ + public void newStreamingSource( boolean priority, String sourcename, + String filename, boolean toLoop, float x, + float y, float z, int attmodel, + float distOrRoll ) + { + CommandQueue( new CommandObject( CommandObject.NEW_SOURCE, priority, + true, toLoop, sourcename, + new FilenameURL( filename ), x, y, z, + attmodel, distOrRoll ) ); + commandThread.interrupt(); + } + +/** + * Creates a new streaming source. The fourth parameter 'identifier' should + * look like a filename, and it must have the correct extension so SoundSystem + * knows what codec to use for the file referenced by the URL instance. + * @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 url URL handle to the sound file to stream at this source. + * @param identifier Filename/identifier of the file referenced by the URL. + * @param toLoop Should this source loop, or play only once. + * @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". + */ + public void newStreamingSource( boolean priority, String sourcename, + URL url, String identifier, boolean toLoop, + float x, float y, float z, int attmodel, + float distOrRoll ) + { + CommandQueue( new CommandObject( CommandObject.NEW_SOURCE, priority, + true, toLoop, sourcename, + new FilenameURL( url, identifier ), + x, y, z, attmodel, distOrRoll ) ); + commandThread.interrupt(); + } + +/** + * Opens a direct line for streaming audio data. This method creates a new + * streaming source to play the data at. The resulting streaming source can be + * manipulated the same as any other streaming source. Raw data can be sent to + * the new streaming source using the feedRawAudioData() method. + * @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". + */ + public void rawDataStream( AudioFormat audioFormat, boolean priority, + String sourcename, float x, float y, float z, + int attModel, float distOrRoll ) + { + CommandQueue( new CommandObject( CommandObject.RAW_DATA_STREAM, + audioFormat, priority, sourcename, x, + y, z, attModel, distOrRoll ) ); + commandThread.interrupt(); + } + +/** + * Creates a temporary source and plays it. After the source finishes playing, + * it is removed. Returns a randomly generated name for the new source. NOTE: + * to make a source created by this method permanant, call the setActive() + * method using the return value for sourcename. + * @param priority Setting this to true will prevent other sounds from overriding this one. + * @param filename Filename/identifier of the sound file to play at this source. + * @param toLoop Should this source loop, or play only once. + * @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". + * @return The new sorce's name. + */ + public String quickPlay( boolean priority, String filename, boolean toLoop, + float x, float y, float z, int attmodel, + float distOrRoll ) + { + //generate a random name for this source: + String sourcename = "Source_" + + randomNumberGenerator.nextInt() + + "_" + randomNumberGenerator.nextInt(); + + // Queue a command to quick play this new source: + CommandQueue( new CommandObject( CommandObject.QUICK_PLAY, priority, + false, toLoop, sourcename, + new FilenameURL( filename ), x, y, z, + attmodel, distOrRoll, true ) ); + CommandQueue( new CommandObject( CommandObject.PLAY, sourcename) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + + // return the new source name. + return sourcename; + } + +/** + * Creates a temporary source and plays it. After the source finishes playing, + * it is removed. Returns a randomly generated name for the new source. NOTE: + * to make a source created by this method permanant, call the setActive() + * method using the return value for sourcename. + * @param priority Setting this to true will prevent other sounds from overriding this one. + * @param url URL handle to the sound file to stream at this source. + * @param identifier Filename/identifier of the file referenced by the URL. + * @param toLoop Should this source loop, or play only once. + * @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". + * @return The new sorce's name. + */ + public String quickPlay( boolean priority, URL url, String identifier, + boolean toLoop, float x, float y, float z, + int attmodel, float distOrRoll ) + { + //generate a random name for this source: + String sourcename = "Source_" + + randomNumberGenerator.nextInt() + + "_" + randomNumberGenerator.nextInt(); + + // Queue a command to quick play this new source: + CommandQueue( new CommandObject( CommandObject.QUICK_PLAY, priority, + false, toLoop, sourcename, + new FilenameURL( url, identifier ), + x, y, z, attmodel, distOrRoll, + true ) ); + CommandQueue( new CommandObject( CommandObject.PLAY, sourcename) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + + // return the new source name. + return sourcename; + } + +/** + * Creates a temporary source and streams it. After the source finishes + * playing, it is removed. The file may either be located within the + * JAR or at an online location. If the file is online, filename must begin + * with "http://", since that is how SoundSystem recognizes URL paths. If the + * file is located within the compiled JAR, the package in which sound files + * are located may be set by calling SoundSystemConfig.setSoundFilesPackage(). + * Returns a randomly generated name for the new source. NOTE: to make a + * source created by this method permanant, call the setActive() method using + * the return value for sourcename. + * @param priority Setting this to true will prevent other sounds from overriding this one. + * @param filename Filename of the sound file to stream at this source. + * @param toLoop Should this source loop, or play only once. + * @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". + * @return The new sorce's name. + */ + public String quickStream( boolean priority, String filename, + boolean toLoop, float x, float y, float z, + int attmodel, float distOrRoll ) + { + //generate a random name for this source: + String sourcename = "Source_" + + randomNumberGenerator.nextInt() + + "_" + randomNumberGenerator.nextInt(); + + // Queue a command to quick stream this new source: + CommandQueue( new CommandObject( CommandObject.QUICK_PLAY, priority, + true, toLoop, sourcename, + new FilenameURL( filename ), x, y, z, + attmodel, distOrRoll, true ) ); + CommandQueue( new CommandObject( CommandObject.PLAY, sourcename) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + + // return the new source name. + return sourcename; + } +/** + * Creates a temporary source and streams it. After the source finishes + * playing, it is removed. The third parameter 'identifier' should + * look like a filename, and it must have the correct extension so SoundSystem + * knows what codec to use for the file referenced by the URL instance. + * Returns a randomly generated name for the new source. NOTE: to make a + * source created by this method permanant, call the setActive() method using + * the return value for sourcename. + * @param priority Setting this to true will prevent other sounds from overriding this one. + * @param url URL handle to the sound file to stream at this source. + * @param identifier Filename/identifier of the file referenced by the URL. + * @param toLoop Should this source loop, or play only once. + * @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". + * @return The new sorce's name. + */ + public String quickStream( boolean priority, URL url, String identifier, + boolean toLoop, float x, float y, float z, + int attmodel, float distOrRoll ) + { + //generate a random name for this source: + String sourcename = "Source_" + + randomNumberGenerator.nextInt() + + "_" + randomNumberGenerator.nextInt(); + + // Queue a command to quick stream this new source: + CommandQueue( new CommandObject( CommandObject.QUICK_PLAY, priority, + true, toLoop, sourcename, + new FilenameURL( url, identifier ), + x, y, z, attmodel, distOrRoll, + true ) ); + CommandQueue( new CommandObject( CommandObject.PLAY, sourcename) ); + // Wake the command thread to process commands: + commandThread.interrupt(); + + // return the new source name. + return sourcename; + } + +/** + * Move a source to the specified location. + * @param sourcename Identifier for the source. + * @param x destination X coordinate. + * @param y destination Y coordinate. + * @param z destination Z coordinate. + */ + public void setPosition( String sourcename, float x, float y, float z ) + { + CommandQueue( new CommandObject( CommandObject.SET_POSITION, + sourcename, x, y, z ) ); + commandThread.interrupt(); + } +/** + * Manually sets the specified source's volume. + * @param sourcename Source to move. + * @param value New volume, float value ( 0.0f - 1.0f ). + */ + public void setVolume( String sourcename, float value ) + { + CommandQueue( new CommandObject( CommandObject.SET_VOLUME, + sourcename, value ) ); + commandThread.interrupt(); + } + +/** + * Returns the current volume of the specified source, or zero if the specified + * source was not found. + * @param sourcename Source to read volume from. + * @return Float value representing the source volume (0.0f - 1.0f). + */ + public float getVolume( String sourcename ) + { + synchronized( SoundSystemConfig.THREAD_SYNC ) + { + if( soundLibrary != null ) + return soundLibrary.getVolume( sourcename ); + else + return 0.0f; + } + } + +/** + * Manually sets the specified source's pitch. + * @param sourcename The source's name. + * @param value A float value ( 0.5f - 2.0f ). + */ + public void setPitch( String sourcename, float value ) + { + CommandQueue( new CommandObject( CommandObject.SET_PITCH, + sourcename, value ) ); + commandThread.interrupt(); + } + +/** + * Returns the pitch of the specified source. + * @param sourcename The source's name. + * @return Float value representing the source pitch (0.5f - 2.0f). + */ + public float getPitch( String sourcename ) + { + if( soundLibrary != null ) + return soundLibrary.getPitch( sourcename ); + else + return 1.0f; + } + +/** + * Set a source's priority factor. A priority source will not be overriden when + * too many sources are playing at once. + * @param sourcename Identifier for the source. + * @param pri Setting this to true makes this source a priority source. + */ + public void setPriority( String sourcename, boolean pri ) + { + CommandQueue( new CommandObject( CommandObject.SET_PRIORITY, + sourcename, pri ) ); + commandThread.interrupt(); + } +/** + * Changes a source to looping or non-looping. + * @param sourcename Identifier for the source. + * @param lp This source should loop. + */ + public void setLooping( String sourcename, boolean lp ) + { + CommandQueue( new CommandObject( CommandObject.SET_LOOPING, + sourcename, lp ) ); + commandThread.interrupt(); + } +/** + * Changes a source's attenuation model. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about Attenuation. + * @param sourcename Identifier for the source. + * @param model Attenuation model to use. + */ + public void setAttenuation( String sourcename, int model ) + { + CommandQueue( new CommandObject( CommandObject.SET_ATTENUATION, + sourcename, model ) ); + commandThread.interrupt(); + } +/** + * Changes a source's fade distance or rolloff factor. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about fade distance and rolloff. + * @param sourcename Identifier for the source. + * @param dr Either the fading distance or rolloff factor, depending on the attenuation model used. + */ + public void setDistOrRoll( String sourcename, float dr) + { + CommandQueue( new CommandObject( CommandObject.SET_DIST_OR_ROLL, + sourcename, dr ) ); + commandThread.interrupt(); + } + +/** + * Changes the Doppler factor, for determining Doppler effect scale. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about Doppler effect. + * @param dopplerFactor New value for Doppler factor. + */ + public void changeDopplerFactor( float dopplerFactor) + { + CommandQueue( new CommandObject( CommandObject.CHANGE_DOPPLER_FACTOR, + dopplerFactor ) ); + commandThread.interrupt(); + } + +/** + * Changes the Doppler velocity, for use in Doppler effect. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about Doppler effect. + * @param dopplerVelocity New value for Doppler velocity. + */ + public void changeDopplerVelocity( float dopplerVelocity ) + { + CommandQueue( new CommandObject( CommandObject.CHANGE_DOPPLER_VELOCITY, + dopplerVelocity ) ); + commandThread.interrupt(); + } + +/** + * Sets the specified source's velocity, for use in Doppler effect. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about Doppler effect. + * @param sourcename The source's name. + * @param x Velocity along world x-axis. + * @param y Velocity along world y-axis. + * @param z Velocity along world z-axis. + */ + public void setVelocity( String sourcename, float x, float y, float z ) + { + CommandQueue( new CommandObject( CommandObject.SET_VELOCITY, + sourcename, x, y, z ) ); + commandThread.interrupt(); + } + +/** + * Sets the listener's velocity, for use in Doppler effect. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about Doppler effect. + * @param x Velocity along world x-axis. + * @param y Velocity along world y-axis. + * @param z Velocity along world z-axis. + */ + public void setListenerVelocity( float x, float y, float z ) + { + CommandQueue( new CommandObject( CommandObject.SET_LISTENER_VELOCITY, + x, y, z ) ); + commandThread.interrupt(); + } + +/** + * Returns the number of miliseconds since the specified source began playing. + * @return miliseconds, or -1 if not playing or unable to calculate + */ + public float millisecondsPlayed( String sourcename ) + { + synchronized( SoundSystemConfig.THREAD_SYNC ) + { + return soundLibrary.millisecondsPlayed( sourcename ); + } + } + +/** + * Feeds raw data through the specified source. The source must be a + * streaming source and it can not be already associated with a file or URL to + * stream from. Only use this for streaming sources created with the + * rawDataStream() method. NOTE: Be carefull how much data you send to a + * source to stream. The data will be processed at playback speed, so if you + * queue up 1 hour worth of data, it will take 1 hour to play (not to mention + * hogging a ton of memory). To clear out all queued data from the source, use + * the flush() method. Also note: if there is a break in the data stream, + * you will hear clicks and studders, so ensure that the data flow is steady. + * @param sourcename Name of the streaming source to play from. + * @param buffer Byte buffer containing raw audio data to stream. + */ + public void feedRawAudioData( String sourcename, byte[] buffer ) + { + CommandQueue( new CommandObject( CommandObject.FEED_RAW_AUDIO_DATA, + sourcename, buffer ) ); + commandThread.interrupt(); + } +/** + * Plays the specified source. + * @param sourcename Identifier for the source. + */ + public void play( String sourcename ) + { + CommandQueue( new CommandObject( CommandObject.PLAY, sourcename) ); + commandThread.interrupt(); + } +/** + * Pauses the specified source. + * @param sourcename Identifier for the source. + */ + public void pause( String sourcename ) + { + CommandQueue( new CommandObject( CommandObject.PAUSE, sourcename) ); + commandThread.interrupt(); + } +/** + * Stops the specified source. + * @param sourcename Identifier for the source. + */ + public void stop( String sourcename ) + { + CommandQueue( new CommandObject( CommandObject.STOP, sourcename) ); + commandThread.interrupt(); + } +/** + * Rewinds the specified source. + * @param sourcename Identifier for the source. + */ + public void rewind( String sourcename ) + { + CommandQueue( new CommandObject( CommandObject.REWIND, sourcename) ); + commandThread.interrupt(); + } +/** + * Flushes all previously queued audio data from a streaming source. + * @param sourcename Identifier for the source. + */ + public void flush( String sourcename ) + { + CommandQueue( new CommandObject( CommandObject.FLUSH, sourcename) ); + commandThread.interrupt(); + } + +/** + * Culls the specified source. A culled source can not be played until it has + * been activated again. + * @param sourcename Identifier for the source. + */ + public void cull( String sourcename ) + { + CommandQueue( new CommandObject( CommandObject.CULL, sourcename) ); + commandThread.interrupt(); + } + +/** + * Activates the specified source after it was culled, so it can be played + * again. + * @param sourcename Identifier for the source. + */ + public void activate( String sourcename ) + { + CommandQueue( new CommandObject( CommandObject.ACTIVATE, sourcename) ); + commandThread.interrupt(); + } + +/** + * Sets a flag for a source indicating whether it should be used or if it + * should be removed after it finishes playing. One possible use for this + * method is to make temporary sources that were created with quickPlay() + * permanant. Another use could be to have a source automatically removed + * after it finishes playing. NOTE: Setting a source to temporary does not + * stop it, and setting a source to permanant does not play it. It is also + * important to note that a looping temporary source will not be removed as + * long as it keeps playing. + * @param sourcename Identifier for the source. + * @param temporary True = temporary, False = permanant. + */ + public void setTemporary( String sourcename, boolean temporary ) + { + CommandQueue( new CommandObject( CommandObject.SET_TEMPORARY, + sourcename, temporary ) ); + commandThread.interrupt(); + } + +/** + * Removes the specified source and clears up any memory it used. + * @param sourcename Identifier for the source. + */ + public void removeSource( String sourcename ) + { + CommandQueue( new CommandObject( CommandObject.REMOVE_SOURCE, + sourcename ) ); + commandThread.interrupt(); + } +/** + * Moves the listener relative to the current location. + * @param x X offset. + * @param y Y offset. + * @param z Z offset. + */ + public void moveListener( float x, float y, float z ) + { + CommandQueue( new CommandObject( CommandObject.MOVE_LISTENER, + x, y, z ) ); + commandThread.interrupt(); + } +/** + * Moves the listener to the specified location. + * @param x Destination X coordinate. + * @param y Destination Y coordinate. + * @param z Destination Z coordinate. + */ + public void setListenerPosition( float x, float y, float z ) + { + CommandQueue( new CommandObject( CommandObject.SET_LISTENER_POSITION, + x, y, z ) ); + commandThread.interrupt(); + } +/** + * Turns the listener counterclockwise by "angle" radians around the y-axis, + * relative to the current angle. + * @param angle radian offset. + */ + public void turnListener( float angle ) + { + CommandQueue( new CommandObject( CommandObject.TURN_LISTENER, + angle ) ); + commandThread.interrupt(); + } +/** + * Sets the listener's angle in radians around the y-axis. + * @param angle radians. + */ + public void setListenerAngle( float angle ) + { + CommandQueue( new CommandObject( CommandObject.SET_LISTENER_ANGLE, + angle ) ); + commandThread.interrupt(); + } +/** + * Sets the listener's orientation. + * @param lookX X coordinate of the (normalized) look-at vector. + * @param lookY Y coordinate of the (normalized) look-at vector. + * @param lookZ Z coordinate of the (normalized) look-at vector. + * @param upX X coordinate of the (normalized) up-direction vector. + * @param upY Y coordinate of the (normalized) up-direction vector. + * @param upZ Z coordinate of the (normalized) up-direction vector. + */ + public void setListenerOrientation( float lookX, float lookY, float lookZ, + float upX, float upY, float upZ ) + { + CommandQueue( new CommandObject( CommandObject.SET_LISTENER_ORIENTATION, + lookX, lookY, lookZ, upX, upY, upZ ) ); + commandThread.interrupt(); + } + +/** + * Sets the overall volume, affecting all sources. + * @param value New volume, float value ( 0.0f - 1.0f ). + */ + public void setMasterVolume( float value ) + { + CommandQueue( new CommandObject( CommandObject.SET_MASTER_VOLUME, + value ) ); + commandThread.interrupt(); + } + +/** + * Returns the overall volume, affecting all sources. + * @return Float value representing the master volume (0.0f - 1.0f). + */ + public float getMasterVolume() + { + return SoundSystemConfig.getMasterGain(); + } + +/** + * Method for obtaining information about the listener's position and + * orientation. + * @return a {@link com.ardor3d.audio.ListenerData ListenerData} object. + */ + public ListenerData getListenerData() + { + synchronized( SoundSystemConfig.THREAD_SYNC ) + { + return soundLibrary.getListenerData(); + } + } +/** + * Switches to the specified library, and preserves all sources. + * @param libraryClass Library to use. + * @return True if switch was successful. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for + * information about chosing a sound library. + */ + public boolean switchLibrary( Class libraryClass ) + throws SoundSystemException + { + synchronized( SoundSystemConfig.THREAD_SYNC ) + { + initialized( SET, false ); + + HashMap<String, Source> sourceMap = null; + ListenerData listenerData = null; + + if( soundLibrary != null ) + { + currentLibrary( SET, null ); + sourceMap = copySources( soundLibrary.getSources() ); + listenerData = soundLibrary.getListenerData(); + + soundLibrary.cleanup(); + soundLibrary = null; + } + message( "", 0 ); + message( "Switching to " + + SoundSystemConfig.getLibraryTitle( libraryClass ), 0 ); + message( "(" + SoundSystemConfig.getLibraryDescription( libraryClass ) + + ")", 1 ); + + try + { + soundLibrary = (Library) libraryClass.getDeclaredConstructor().newInstance(); + } + catch( NoSuchMethodException nsme ) + { + errorMessage( "The specified library did not load properly", 1 ); + } + catch( InvocationTargetException ite ) + { + errorMessage( "The specified library did not load properly", 1 ); + } + catch( InstantiationException ie ) + { + errorMessage( "The specified library did not load properly", 1 ); + } + catch( IllegalAccessException iae ) + { + errorMessage( "The specified library did not load properly", 1 ); + } + catch( ExceptionInInitializerError eiie ) + { + errorMessage( "The specified library did not load properly", 1 ); + } + catch( SecurityException se ) + { + errorMessage( "The specified library did not load properly", 1 ); + } + + if( errorCheck( soundLibrary == null, "Library null after " + + "initialization in method 'switchLibrary'", 1 ) ) + { + SoundSystemException sse = new SoundSystemException( + className + " did not load properly. " + + "Library was null after initialization.", + SoundSystemException.LIBRARY_NULL ); + lastException( SET, sse ); + initialized( SET, true ); + throw sse; + } + + try + { + soundLibrary.init(); + } + catch( SoundSystemException sse ) + { + lastException( SET, sse ); + initialized( SET, true ); + throw sse; + } + + soundLibrary.setListenerData( listenerData ); + soundLibrary.copySources( sourceMap ); + + message( "", 0 ); + + lastException( SET, null ); + initialized( SET, true ); + + return true; + } + } + +/** + * Switches to the specified library, loosing all sources. + * @param libraryClass Library to use. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for + * information about chosing a sound library. + */ + public boolean newLibrary( Class libraryClass ) + throws SoundSystemException + { + initialized( SET, false ); + + CommandQueue( new CommandObject( CommandObject.NEW_LIBRARY, + libraryClass ) ); + commandThread.interrupt(); + + for( int x = 0; (!initialized( GET, XXX )) && (x < 100); x++ ) + { + snooze( 400 ); + commandThread.interrupt(); + } + + if( !initialized( GET, XXX ) ) + { + SoundSystemException sse = new SoundSystemException( + className + + " did not load after 30 seconds.", + SoundSystemException.LIBRARY_NULL ); + lastException( SET, sse ); + throw sse; + } + else + { + SoundSystemException sse = lastException( GET, null ); + if( sse != null ) + throw sse; + } + return true; + } + +/** + * Switches to the specified library, loosing all sources. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the newLibrary() method instead. + * @param libraryClass Library to use. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for + * information about chosing a sound library. + */ + private void CommandNewLibrary( Class libraryClass ) + { + initialized( SET, false ); + + String headerMessage = "Initializing "; + if( soundLibrary != null ) + { + currentLibrary( SET, null ); + // we are switching libraries + headerMessage = "Switching to "; + soundLibrary.cleanup(); + soundLibrary = null; + } + message( headerMessage + + SoundSystemConfig.getLibraryTitle( libraryClass ), 0 ); + message( "(" + SoundSystemConfig.getLibraryDescription( libraryClass ) + + ")", 1 ); + + try + { + soundLibrary = (Library) libraryClass.newInstance(); + } + catch( InstantiationException ie ) + { + errorMessage( "The specified library did not load properly", 1 ); + } + catch( IllegalAccessException iae ) + { + errorMessage( "The specified library did not load properly", 1 ); + } + catch( ExceptionInInitializerError eiie ) + { + errorMessage( "The specified library did not load properly", 1 ); + } + catch( SecurityException se ) + { + errorMessage( "The specified library did not load properly", 1 ); + } + + if( errorCheck( soundLibrary == null, "Library null after " + + "initialization in method 'newLibrary'", 1 ) ) + { + lastException( SET, new SoundSystemException( + className + " did not load properly. " + + "Library was null after initialization.", + SoundSystemException.LIBRARY_NULL ) ); + importantMessage( "Switching to silent mode", 1 ); + + try + { + soundLibrary = new Library(); + } + catch( SoundSystemException sse ) + { + lastException( SET, new SoundSystemException( + "Silent mode did not load properly. " + + "Library was null after initialization.", + SoundSystemException.LIBRARY_NULL ) ); + initialized( SET, true ); + return; + } + } + + try + { + soundLibrary.init(); + } + catch( SoundSystemException sse ) + { + lastException( SET, sse ); + initialized( SET, true ); + return; + } + + lastException( SET, null ); + initialized( SET, true ); + + return; + } +/** + * Calls the library's initialize() method. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly. + */ + private void CommandInitialize() + { + try + { + if( errorCheck( soundLibrary == null, "Library null after " + + "initialization in method 'CommandInitialize'", + 1 ) ) + { + SoundSystemException sse = new SoundSystemException( + className + " did not load properly. " + + "Library was null after initialization.", + SoundSystemException.LIBRARY_NULL ); + lastException( SET, sse ); + throw sse; + } + soundLibrary.init(); + } + catch( SoundSystemException sse ) + { + lastException( SET, sse ); + initialized( SET, true ); + } + } +/** + * Loads sample data from a sound file or URL into memory. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the loadSound() method instead. + * @param filenameURL Filename/URL of the sound file to load. + */ + private void CommandLoadSound( FilenameURL filenameURL ) + { + if( soundLibrary != null ) + soundLibrary.loadSound( filenameURL ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandLoadSound'", 0 ); + } +/** + * 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. This method is used internally by SoundSystem for thread + * synchronization, and it can not be called directly - please use the + * loadSound() method instead. + * @param buffer the sample data and audio format to save. + * @param identifier What to call the sample. + */ + private void CommandLoadSound( SoundBuffer buffer, String identifier ) + { + if( soundLibrary != null ) + soundLibrary.loadSound( buffer, identifier ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandLoadSound'", 0 ); + } +/** + * Removes previously loaded sampled data from memory. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the unloadSound() method instead. + * @param filename Filename or string identifyer of sound to unload. + */ + private void CommandUnloadSound( String filename ) + { + if( soundLibrary != null ) + soundLibrary.unloadSound( filename ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandLoadSound'", 0 ); + } +/** + * If the specified source is a streaming source or MIDI source, this method + * queues up the next sound to play when the previous playback ends. This + * method has no effect on non-streaming sources. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the queueSound() method instead. + * @param sourcename Source identifier. + * @param filenameURL Filename/URL of the sound file to play next. + */ + private void CommandQueueSound( String sourcename, + FilenameURL filenameURL ) + { + if( soundLibrary != null ) + soundLibrary.queueSound( sourcename, filenameURL ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandQueueSound'", 0 ); + } +/** + * Removes the first occurrence of the specified filename/identifier from the + * specified source's list of sounds to play when previous playback ends. This + * method has no effect on non-streaming sources. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the dequeueSound() method instead. + * @param sourcename Source identifier. + * @param filename Filename/identifier of the sound file to remove from the queue. + */ + private void CommandDequeueSound( String sourcename, String filename ) + { + if( soundLibrary != null ) + soundLibrary.dequeueSound( sourcename, filename ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandDequeueSound'", 0 ); + } +/** + * Fades out the volume of whatever the specified source is currently playing, + * then begins playing the specified file at the source's previously + * assigned volume level. If the filenameURL parameter is null or empty, the + * specified source will simply fade out and stop. The miliseconds parameter + * must be non-negative or zero. This method will remove anything that is + * currently in the specified source's list of queued sounds that would have + * played next when the current sound finished playing. This method may only + * be used for streaming and MIDI sources. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the fadeOut() method instead. + * @param sourcename Name of the source to fade out. + * @param filenameURL Filename/URL of a sound file to play next, or null for none. + * @param milis Number of miliseconds the fadeout should take. + */ + private void CommandFadeOut( String sourcename, FilenameURL filenameURL, + long milis ) + { + if( soundLibrary != null ) + soundLibrary.fadeOut( sourcename, filenameURL, milis ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandFadeOut'", 0 ); + } +/** + * Fades out the volume of whatever the specified source is currently playing, + * then fades the volume back in playing the specified file. Final volume + * after fade-in completes will be equal to the source's previously assigned + * volume level. The filenameURL parameter may not be null or empty. The + * miliseconds parameters must be non-negative or zero. This method will + * remove anything that is currently in the specified source's list of queued + * sounds that would have played next when the current sound finished playing. + * This method may only be used for streaming and MIDI sources. This method is + * used internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the fadeOutIn() method instead. + * @param sourcename Name of the source to fade out/in. + * @param filenameURL Filename/URL of a sound file to play next, or null for none. + * @param milisOut Number of miliseconds the fadeout should take. + * @param milisIn Number of miliseconds the fadein should take. + */ + private void CommandFadeOutIn( String sourcename, FilenameURL filenameURL, + long milisOut, long milisIn ) + { + if( soundLibrary != null ) + soundLibrary.fadeOutIn( sourcename, filenameURL, milisOut, + milisIn ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandFadeOutIn'", 0 ); + } +/** + * Makes sure the current volume levels of streaming sources and MIDI are + * correct. This method is designed to help reduce the "jerky" fading behavior + * that happens when using some library and codec pluggins (such as + * LibraryJavaSound and CodecJOrbis). This method has no effect on normal + * "non-streaming" sources. It would normally be called somewhere in the main + * "game loop". IMPORTANT: To optimize frame-rates, do not call this method + * for every frame. It is better to just call this method at some acceptable + * "granularity" (play around with different granularities to find what sounds + * acceptable for a particular situation). This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the checkFadeVolumes() method instead. + */ + private void CommandCheckFadeVolumes() + { + if( soundLibrary != null ) + soundLibrary.checkFadeVolumes(); + else + errorMessage( "Variable 'soundLibrary' null in method " + + "'CommandCheckFadeVolumes'", 0 ); + } +/** + * Loads a sound file into memory. This method is used internally by + * SoundSystem for thread synchronization, and it can not be called directly - + * please use the newSource() method instead. + * @param priority Setting this to true will prevent other sounds from overriding this one. + * @param toStream Whether or not to stream the source. + * @param toLoop Whether or not to loop the source. + * @param sourcename A unique identifier for the source. + * @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". + */ + private void CommandNewSource( boolean priority, boolean toStream, + boolean toLoop, String sourcename, + FilenameURL filenameURL, float x, + float y, float z, int attModel, + float distORroll ) + { + if( soundLibrary != null ) + { + if( filenameURL.getFilename().matches( + SoundSystemConfig.EXTENSION_MIDI ) + && !SoundSystemConfig.midiCodec() ) + { + soundLibrary.loadMidi( toLoop, sourcename, filenameURL ); + } + else + { + soundLibrary.newSource( priority, toStream, toLoop, sourcename, + filenameURL, x, y, z, attModel, + distORroll ); + } + } + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandNewSource'", 0 ); + } +/** + * Opens a direct line for streaming audio data. This method is used + * internally by SoundSystem, and it can not be called directly - please use + * the rawDataStream() method instead. + * @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". + */ + private void CommandRawDataStream( AudioFormat audioFormat, + boolean priority, String sourcename, + float x, float y, float z, + int attModel, float distOrRoll ) + { + if( soundLibrary != null ) + soundLibrary.rawDataStream( audioFormat, priority, sourcename, + x, y, z, attModel, distOrRoll ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandRawDataStream'", 0 ); + } +/** + * Creates a temporary source and either plays or streams it. After the source + * finishes playing, it is removed. This method is used internally by + * SoundSystem for thread synchronization, and it can not be called directly - + * please use the quickPlay() method instead. + * @param priority Setting this to true will prevent other sounds from overriding this one. + * @param toStream Whether or not to stream the source. + * @param toLoop Whether or not to loop the source. + * @param sourcename A unique identifier for the source. + * @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 the source should be removed after it finishes playing. + */ + private void CommandQuickPlay( boolean priority, boolean toStream, + boolean toLoop, String sourcename, + FilenameURL filenameURL, float x, float y, + float z, int attModel, float distORroll, + boolean temporary ) + { + if( soundLibrary != null ) + { + if( filenameURL.getFilename().matches( SoundSystemConfig.EXTENSION_MIDI ) && + !SoundSystemConfig.midiCodec() ) + { + soundLibrary.loadMidi( toLoop, sourcename, filenameURL ); + } + else + { + soundLibrary.quickPlay( priority, toStream, toLoop, sourcename, + filenameURL, x, y, z, attModel, + distORroll, temporary ); + } + } + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandQuickPlay'", 0 ); + } +/** + * Moves a source to the specified coordinates. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setPosition() method instead. + * @param sourcename Source to move. + * @param x Destination X coordinate. + * @param y Destination Y coordinate. + * @param z Destination Z coordinate. + */ + private void CommandSetPosition( String sourcename, float x, float y, + float z) + { + if( soundLibrary != null ) + soundLibrary.setPosition( sourcename, x, y, z ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandMoveSource'", 0 ); + } +/** + * Manually sets the specified source's volume. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setVolume() method instead. + * @param sourcename Source to change the volume of. + * @param value New volume, float value ( 0.0f - 1.0f ). + */ + private void CommandSetVolume( String sourcename, float value ) + { + if( soundLibrary != null ) + soundLibrary.setVolume( sourcename, value ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetVolume'", 0 ); + } +/** + * Manually sets the specified source's pitch. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setPitch() method instead. + * @param sourcename Source to change the pitch of. + * @param value New pitch, float value ( 0.5f - 2.0f ). + */ + private void CommandSetPitch( String sourcename, float value ) + { + if( soundLibrary != null ) + soundLibrary.setPitch( sourcename, value ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetPitch'", 0 ); + } +/** + * Set a source's priority factor. A priority source will not be overriden when + * too many sources are playing at once. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setPriority() method instead. + * @param sourcename Identifier for the source. + * @param pri Setting this to true makes this source a priority source. + */ + private void CommandSetPriority( String sourcename, boolean pri ) + { + if( soundLibrary != null ) + soundLibrary.setPriority( sourcename, pri ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetPriority'", 0 ); + } +/** + * Changes a source to looping or non-looping. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setLooping() method instead. + * @param sourcename Identifier for the source. + * @param lp This source should loop. + */ + private void CommandSetLooping( String sourcename, boolean lp ) + { + if( soundLibrary != null ) + soundLibrary.setLooping( sourcename, lp ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetLooping'", 0 ); + } +/** + * Changes a source's attenuation model. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setAttenuation() method instead. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about Attenuation. + * @param sourcename Identifier for the source. + * @param model Attenuation model to use. + */ + private void CommandSetAttenuation( String sourcename, int model ) + { + if( soundLibrary != null ) + soundLibrary.setAttenuation( sourcename, model ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetAttenuation'", + 0 ); + } +/** + * Changes a source's fade distance or rolloff factor. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about fade distance and rolloff. + * @param sourcename Identifier for the source. + * @param dr Either the fading distance or rolloff factor, depending on the attenuation model used. + */ + private void CommandSetDistOrRoll( String sourcename, float dr ) + { + if( soundLibrary != null ) + soundLibrary.setDistOrRoll( sourcename, dr ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetDistOrRoll'", + 0 ); + } +/** + * Changes the Doppler factor. This method is used internally by SoundSystem + * for thread synchronization, and it can not be called directly - please use + * the setDopplerFactor() method instead. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about Doppler effect. + * @param dopplerFactor New Doppler factor, for determining Doppler effect scale. + */ + private void CommandChangeDopplerFactor( float dopplerFactor ) + { + if( soundLibrary != null ) + { + SoundSystemConfig.setDopplerFactor( dopplerFactor ); + soundLibrary.dopplerChanged(); + } + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetDopplerFactor'", + 0 ); + } +/** + * Changes the Doppler velocity. This method is used internally by SoundSystem + * for thread synchronization, and it can not be called directly - please use + * the setDopplerVelocity() method instead. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about Doppler effect. + * @param dopplerVelocity New Doppler velocity, for use in Doppler effect. + */ + private void CommandChangeDopplerVelocity( float dopplerVelocity ) + { + if( soundLibrary != null ) + { + SoundSystemConfig.setDopplerVelocity( dopplerVelocity ); + soundLibrary.dopplerChanged(); + } + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetDopplerFactor'", + 0 ); + } +/** + * Changes a source's velocity, for use in Doppler effect. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setVelocity() method instead. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about Doppler effect. + * @param sourcename Identifier for the source. + * @param x Source's velocity along the world x-axis. + * @param y Source's velocity along the world y-axis. + * @param z Source's velocity along the world z-axis. + */ + private void CommandSetVelocity( String sourcename, float x, float y, float z ) + { + if( soundLibrary != null ) + soundLibrary.setVelocity( sourcename, x, y, z ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandVelocity'", + 0 ); + } +/** + * Changes the listener's velocity, for use in Doppler effect. This method is + * used internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setListenerVelocity() method instead. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about Doppler effect. + * @param x Velocity along the world x-axis. + * @param y Velocity along the world y-axis. + * @param z Velocity along the world z-axis. + */ + private void CommandSetListenerVelocity( float x, float y, float z ) + { + if( soundLibrary != null ) + soundLibrary.setListenerVelocity( x, y, z ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetListenerVelocity'", + 0 ); + } +/** + * Plays the specified source. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the play() method instead. + * @param sourcename Identifier for the source. + */ + private void CommandPlay( String sourcename ) + { + if( soundLibrary != null ) + soundLibrary.play( sourcename ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandPlay'", 0 ); + } +/** + * Feeds raw data through the specified source. The source must be a + * streaming source and it can not be already associated with a file or URL to + * stream from. This method is used internally by SoundSystem for thread + * synchronization, and it can not be called directly - please use the + * feedRawAudioData() method instead. + * @param sourcename Name of the streaming source to play from. + * @param buffer Byte buffer containing raw audio data to stream. + */ + private void CommandFeedRawAudioData( String sourcename, byte[] buffer ) + { + if( soundLibrary != null ) + soundLibrary.feedRawAudioData( sourcename, buffer ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandFeedRawAudioData'", 0 ); + } +/** + * Pauses the specified source. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the pause() method instead. + * @param sourcename Identifier for the source. + */ + private void CommandPause( String sourcename ) + { + if( soundLibrary != null ) + soundLibrary.pause( sourcename ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandPause'", 0 ); + } +/** + * Stops the specified source. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the stop() method instead. + * @param sourcename Identifier for the source. + */ + private void CommandStop( String sourcename ) + { + if( soundLibrary != null ) + soundLibrary.stop( sourcename ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandStop'", 0 ); + } +/** + * Rewinds the specified source. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the rewind() method instead. + * @param sourcename Identifier for the source. + */ + private void CommandRewind( String sourcename ) + { + if( soundLibrary != null ) + soundLibrary.rewind( sourcename ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandRewind'", 0 ); + } +/** + * Flushes all previously queued audio data from a streaming source. This + * method is used internally by SoundSystem for thread synchronization, and it + * can not be called directly - please use the flush() method instead. + * @param sourcename Identifier for the source. + */ + private void CommandFlush( String sourcename ) + { + if( soundLibrary != null ) + soundLibrary.flush( sourcename ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandFlush'", 0 ); + } +/** + * Sets a flag for a source indicating whether it should be used or if it + * should be removed after it finishes playing. One possible use for this + * method is to make temporary sources that were created with quickPlay() + * permanant. Another use could be to have a source automatically removed + * after it finishes playing. NOTE: Setting a source to inactive does not stop + * it, and setting a source to active does not play it. It is also important + * to note that a looping inactive source will not be removed as long as + * it keeps playing. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setTemporary() method instead. + * @param sourcename Identifier for the source. + * @param temporary True or False. + */ + private void CommandSetTemporary( String sourcename, boolean temporary ) + { + if( soundLibrary != null ) + soundLibrary.setTemporary( sourcename, temporary ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetActive'", 0 ); + } +/** + * Removes the specified source and clears up any memory it used. This method + * is used internally by SoundSystem for thread synchronization, and it can not + * be called directly - please use the removeSource() method instead. + * @param sourcename Identifier for the source. + */ + private void CommandRemoveSource( String sourcename ) + { + if( soundLibrary != null ) + soundLibrary.removeSource( sourcename ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandRemoveSource'", 0 ); + } +/** + * Moves the listener relative to the current location. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the moveListener() method instead. + * @param x X offset. + * @param y Y offset. + * @param z Z offset. + */ + private void CommandMoveListener( float x, float y, float z ) + { + if( soundLibrary != null ) + soundLibrary.moveListener( x, y, z ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandMoveListener'", 0 ); + } + /** + * Moves the listener to the specified location. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setListenerPosition() method instead. + * @param x Destination X coordinate. + * @param y Destination Y coordinate. + * @param z Destination Z coordinate. + */ + private void CommandSetListenerPosition( float x, float y, float z ) + { + if( soundLibrary != null ) + soundLibrary.setListenerPosition( x, y, z ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetListenerPosition'", + 0 ); + } +/** + * Turns the listener counterclockwise by "angle" radians around the y-axis, + * relative to the current angle. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the turnListener() method instead. + * @param angle radian offset. + */ + private void CommandTurnListener( float angle ) + { + if( soundLibrary != null ) + soundLibrary.turnListener( angle ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandTurnListener'", + 0 ); + } +/** + * Sets the listener's angle in radians around the y-axis. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setListenerAngle() method instead. + * @param angle radians. + */ + private void CommandSetListenerAngle( float angle ) + { + if( soundLibrary != null ) + soundLibrary.setListenerAngle( angle ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetListenerAngle'", + 0 ); + } +/** + * Sets the listener's orientation. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setListenerOrientation() method instead. + * @param lookX X coordinate of the (normalized) look-at vector. + * @param lookY Y coordinate of the (normalized) look-at vector. + * @param lookZ Z coordinate of the (normalized) look-at vector. + * @param upX X coordinate of the (normalized) look-at vector. + * @param upY Y coordinate of the (normalized) look-at vector. + * @param upZ Z coordinate of the (normalized) look-at vector. + */ + private void CommandSetListenerOrientation( float lookX, float lookY, + float lookZ, float upX, + float upY, float upZ ) + { + if( soundLibrary != null ) + soundLibrary.setListenerOrientation( lookX, lookY, lookZ, upX, upY, + upZ ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetListenerOrientation'", + 0 ); + } +/** + * Culls the specified source. A culled source can not be played until it has + * been activated again. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the cull() method instead. + * @param sourcename Identifier for the source. + */ + private void CommandCull( String sourcename ) + { + if( soundLibrary != null ) + soundLibrary.cull( sourcename ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandCull'", 0 ); + } +/** + * Activates a previously culled source, so it can be played again. This + * method is used internally by SoundSystem for thread synchronization, and it + * can not be called directly - please use the activate() method instead. + * @param sourcename Identifier for the source. + */ + private void CommandActivate( String sourcename ) + { + if( soundLibrary != null ) + soundLibrary.activate( sourcename ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandActivate'", + 0 ); + } +/** + * Sets the overall volume, affecting all sources. This method is used + * internally by SoundSystem for thread synchronization, and it can not be + * called directly - please use the setMasterVolume() method instead. + * @param value New volume, float value ( 0.0f - 1.0f ). + */ + private void CommandSetMasterVolume( float value ) + { + if( soundLibrary != null ) + soundLibrary.setMasterVolume( value ); + else + errorMessage( + "Variable 'soundLibrary' null in method 'CommandSetMasterVolume'", + 0 ); + } + +/** + * This method can be overridden by extended classes to be used for source + * management (culling and activating sources based on established rules). One + * possible use for this method is sorting sources by distance, culling the + * furthest, and activating the closest. + * This method is automatically called on the CommandThread before processing + * queued commands, so you do not need to call it anywhere else. Note: use + * methods cull() and activate() here, NOT CommandCull() and CommandActivate() + * (thread safety issue). + * IMPORTANT: Always use synchronized( SoundSystemConfig.THREAD_SYNC ) when + * manually manipulating sources from outside the Command Thread! + */ + protected void ManageSources() + { + // OVERRIDDEN METHODS MUST USE THIS: + + /******* + synchronized( SoundSystemConfig.THREAD_SYNC ) + { + // TODO: Sort the sources, cull and activate, etc. + } + ********/ + } + +/** + * Queues a command. + * If newCommand is null, all commands are dequeued and executed. + * This is automatically used by the sound system, so it is not + * likely that a user would ever need to use this method. + * See {@link com.ardor3d.audio.CommandObject CommandObject} for more information + * about commands. + * @param newCommand Command to queue, or null to execute commands. + * @return True if more commands exist, false if queue is empty. + */ + public boolean CommandQueue( CommandObject newCommand ) + { + synchronized( SoundSystemConfig.THREAD_SYNC ) + { + if( newCommand == null ) + { + // New command is null - that means execute all queued commands. + boolean activations = false; + CommandObject commandObject; + + // Loop through the command queue: + while( commandQueue != null && commandQueue.size() > 0 ) + { + // Grab the oldest command in the queue: + commandObject = commandQueue.remove( 0 ); + // See what it is, and execute the proper Command method: + if( commandObject != null ) + { + switch( commandObject.Command ) + { + case CommandObject.INITIALIZE: + CommandInitialize(); + break; + case CommandObject.LOAD_SOUND: + CommandLoadSound( + (FilenameURL) commandObject.objectArgs[0] ); + break; + case CommandObject.LOAD_DATA: + CommandLoadSound( + (SoundBuffer) commandObject.objectArgs[0], + commandObject.stringArgs[0] ); + break; + case CommandObject.UNLOAD_SOUND: + CommandUnloadSound( commandObject.stringArgs[0] ); + break; + case CommandObject.QUEUE_SOUND: + CommandQueueSound( commandObject.stringArgs[0], + (FilenameURL) commandObject.objectArgs[0] ); + break; + case CommandObject.DEQUEUE_SOUND: + CommandDequeueSound( commandObject.stringArgs[0], + commandObject.stringArgs[1] ); + break; + case CommandObject.FADE_OUT: + CommandFadeOut( commandObject.stringArgs[0], + (FilenameURL) commandObject.objectArgs[0], + commandObject.longArgs[0] ); + break; + case CommandObject.FADE_OUT_IN: + CommandFadeOutIn( commandObject.stringArgs[0], + (FilenameURL) commandObject.objectArgs[0], + commandObject.longArgs[0], + commandObject.longArgs[1] ); + break; + case CommandObject.CHECK_FADE_VOLUMES: + CommandCheckFadeVolumes(); + break; + case CommandObject.NEW_SOURCE: + CommandNewSource( commandObject.boolArgs[0], + commandObject.boolArgs[1], + commandObject.boolArgs[2], + commandObject.stringArgs[0], + (FilenameURL) commandObject.objectArgs[0], + commandObject.floatArgs[0], + commandObject.floatArgs[1], + commandObject.floatArgs[2], + commandObject.intArgs[0], + commandObject.floatArgs[3] ); + break; + case CommandObject.RAW_DATA_STREAM: + CommandRawDataStream( + (AudioFormat) commandObject.objectArgs[0], + commandObject.boolArgs[0], + commandObject.stringArgs[0], + commandObject.floatArgs[0], + commandObject.floatArgs[1], + commandObject.floatArgs[2], + commandObject.intArgs[0], + commandObject.floatArgs[3] ); + break; + case CommandObject.QUICK_PLAY: + CommandQuickPlay( commandObject.boolArgs[0], + commandObject.boolArgs[1], + commandObject.boolArgs[2], + commandObject.stringArgs[0], + (FilenameURL) commandObject.objectArgs[0], + commandObject.floatArgs[0], + commandObject.floatArgs[1], + commandObject.floatArgs[2], + commandObject.intArgs[0], + commandObject.floatArgs[3], + commandObject.boolArgs[3] ); + break; + case CommandObject.SET_POSITION: + CommandSetPosition( commandObject.stringArgs[0], + commandObject.floatArgs[0], + commandObject.floatArgs[1], + commandObject.floatArgs[2] ); + break; + case CommandObject.SET_VOLUME: + CommandSetVolume( commandObject.stringArgs[0], + commandObject.floatArgs[0] ); + break; + case CommandObject.SET_PITCH: + CommandSetPitch( commandObject.stringArgs[0], + commandObject.floatArgs[0] ); + break; + case CommandObject.SET_PRIORITY: + CommandSetPriority( commandObject.stringArgs[0], + commandObject.boolArgs[0] ); + break; + case CommandObject.SET_LOOPING: + CommandSetLooping( commandObject.stringArgs[0], + commandObject.boolArgs[0] ); + break; + case CommandObject.SET_ATTENUATION: + CommandSetAttenuation( commandObject.stringArgs[0], + commandObject.intArgs[0] ); + break; + case CommandObject.SET_DIST_OR_ROLL: + CommandSetDistOrRoll( commandObject.stringArgs[0], + commandObject.floatArgs[0] ); + break; + case CommandObject.CHANGE_DOPPLER_FACTOR: + CommandChangeDopplerFactor( + commandObject.floatArgs[0] ); + break; + case CommandObject.CHANGE_DOPPLER_VELOCITY: + CommandChangeDopplerVelocity( + commandObject.floatArgs[0] ); + break; + case CommandObject.SET_VELOCITY: + CommandSetVelocity( commandObject.stringArgs[0], + commandObject.floatArgs[0], + commandObject.floatArgs[1], + commandObject.floatArgs[2] + ); + break; + case CommandObject.SET_LISTENER_VELOCITY: + CommandSetListenerVelocity( + commandObject.floatArgs[0], + commandObject.floatArgs[1], + commandObject.floatArgs[2] + ); + break; + // Methods related to playing sources must be processed + // after cull/activate commands in order for source + // management to work properly, so save them for + // later: + //------------------------------------------------------ + case CommandObject.PLAY: + sourcePlayList.add( commandObject ); + break; + case CommandObject.FEED_RAW_AUDIO_DATA: + sourcePlayList.add( commandObject ); + break; + //------------------------------------------------------ + case CommandObject.PAUSE: + CommandPause( commandObject.stringArgs[0] ); + break; + case CommandObject.STOP: + CommandStop( commandObject.stringArgs[0] ); + break; + case CommandObject.REWIND: + CommandRewind( commandObject.stringArgs[0] ); + break; + case CommandObject.FLUSH: + CommandFlush( commandObject.stringArgs[0] ); + break; + case CommandObject.CULL: + CommandCull( commandObject.stringArgs[0] ); + break; + case CommandObject.ACTIVATE: + activations = true; + CommandActivate( commandObject.stringArgs[0] ); + break; + case CommandObject.SET_TEMPORARY: + CommandSetTemporary( commandObject.stringArgs[0], + commandObject.boolArgs[0] ); + break; + case CommandObject.REMOVE_SOURCE: + CommandRemoveSource( commandObject.stringArgs[0] ); + break; + case CommandObject.MOVE_LISTENER: + CommandMoveListener( commandObject.floatArgs[0], + commandObject.floatArgs[1], + commandObject.floatArgs[2]); + break; + case CommandObject.SET_LISTENER_POSITION: + CommandSetListenerPosition( + commandObject.floatArgs[0], + commandObject.floatArgs[1], + commandObject.floatArgs[2]); + break; + case CommandObject.TURN_LISTENER: + CommandTurnListener( commandObject.floatArgs[0] ); + break; + case CommandObject.SET_LISTENER_ANGLE: + CommandSetListenerAngle( + commandObject.floatArgs[0]); + break; + case CommandObject.SET_LISTENER_ORIENTATION: + CommandSetListenerOrientation( + commandObject.floatArgs[0], + commandObject.floatArgs[1], + commandObject.floatArgs[2], + commandObject.floatArgs[3], + commandObject.floatArgs[4], + commandObject.floatArgs[5]); + break; + case CommandObject.SET_MASTER_VOLUME: + CommandSetMasterVolume( + commandObject.floatArgs[0] ); + break; + case CommandObject.NEW_LIBRARY: + CommandNewLibrary( commandObject.classArgs[0] ); + break; + // If we don't recognize the command, just skip it: + default: + break; + } + } + } + + // If any sources were reactivated, check if they need to be + // replayed: + if( activations ) + soundLibrary.replaySources(); + + // Now that we have the correct sources culled and activated, we + // can start playing sources. Loop through the playlist and + // execute the commands: + while( sourcePlayList != null && sourcePlayList.size() > 0 ) + { + // Grab the oldest command in the queue: + commandObject = sourcePlayList.remove( 0 ); + if( commandObject != null ) + { + // See what it is, and execute the proper Command method: + switch( commandObject.Command ) + { + case CommandObject.PLAY: + CommandPlay( commandObject.stringArgs[0] ); + break; + case CommandObject.FEED_RAW_AUDIO_DATA: + CommandFeedRawAudioData( + commandObject.stringArgs[0], + commandObject.buffer ); + break; + } + } + } + + return( commandQueue != null && commandQueue.size() > 0 ); + } + else + { + // make sure the commandQueue exists: + if( commandQueue == null ) + return false; + // queue a new command + commandQueue.add( newCommand ); + // Of course there is something in the list now, since we just + // added it: + return true; + } + } + } + +/** + * Searches for and removes any temporary sources that have finished + * playing. This method is used internally by SoundSystem, and it is + * unlikely that the user will ever need to use it. + */ + public void removeTemporarySources() + { + synchronized( SoundSystemConfig.THREAD_SYNC ) + { + if( soundLibrary != null ) + soundLibrary.removeTemporarySources(); + } + } + +/** + * Returns true if the specified source is playing. + * @param sourcename Unique identifier of the source to check. + * @return True or false. + */ + public boolean playing( String sourcename ) + { + synchronized( SoundSystemConfig.THREAD_SYNC ) + { + if( soundLibrary == null ) + return false; + + Source src = soundLibrary.getSources().get( sourcename ); + + if( src == null ) + return false; + + return src.playing(); + } + } + +/** + * Returns true if anything is currently playing. + * @return True or false. + */ + public boolean playing() + { + synchronized( SoundSystemConfig.THREAD_SYNC ) + { + if( soundLibrary == null ) + return false; + + HashMap<String, Source> sourceMap = soundLibrary.getSources(); + if( sourceMap == null ) + return false; + + Set<String> keys = sourceMap.keySet(); + Iterator<String> iter = keys.iterator(); + String sourcename; + Source source; + + while( iter.hasNext() ) + { + sourcename = iter.next(); + source = sourceMap.get( sourcename ); + if( source != null ) + if( source.playing() ) + return true; + } + + return false; + } + } + +/** + * Copies and returns the peripheral information from a map of sources. This + * method is used internally by SoundSystem, and it is unlikely that the user + * will ever need to use it. + * @param sourceMap Sources to copy. + * @return New map of sources. + */ + private HashMap<String, Source> copySources( HashMap<String, + Source> sourceMap ) + { + Set<String> keys = sourceMap.keySet(); + Iterator<String> iter = keys.iterator(); + String sourcename; + Source source; + + // New map of generic source data: + HashMap<String, Source> returnMap = new HashMap<String, Source>(); + + + // loop through and store information from all the sources: + while( iter.hasNext() ) + { + sourcename = iter.next(); + source = sourceMap.get( sourcename ); + if( source != null ) + returnMap.put( sourcename, new Source( source, null ) ); + } + return returnMap; + } + +/** + * Checks if the specified library type is compatible. + * @param libraryClass Libary type to check. + * @return True or false. + */ + public static boolean libraryCompatible( Class libraryClass ) + { + // create the message logger: + SoundSystemLogger logger = SoundSystemConfig.getLogger(); + // if the user didn't create one, then do it now: + if( logger == null ) + { + logger = new SoundSystemLogger(); + SoundSystemConfig.setLogger( logger ); + } + logger.message( "", 0 ); + logger.message( "Checking if " + + SoundSystemConfig.getLibraryTitle( libraryClass ) + + " is compatible...", 0 ); + + boolean comp = SoundSystemConfig.libraryCompatible( libraryClass ); + + if( comp ) + logger.message( "...yes", 1 ); + else + logger.message( "...no", 1 ); + + return comp; + } + +/** + * Returns the currently loaded library, or -1 if none. + * @return Global library identifier + */ + public static Class currentLibrary() + { + return( currentLibrary( GET, null ) ); + } + +/** + * Returns false if a sound library is busy initializing. + * @return True or false. + */ + public static boolean initialized() + { + return( initialized( GET, XXX ) ); + } + +/** + * Returns the last SoundSystemException thrown, or null if none. + * @return The last exception. + */ + public static SoundSystemException getLastException() + { + return( lastException( GET, null ) ); + } + +/** + * Stores a SoundSystemException which can be retreived later with the + * 'getLastException' method. + * @param e Exception to store. + */ + public static void setException( SoundSystemException e ) + { + lastException( SET, e ); + } + +/** + * Sets or returns the value of boolean 'initialized'. + * @param action Action to perform (GET or SET). + * @param value New value if action is SET, otherwise XXX. + * @return value of boolean 'initialized'. + */ + private static boolean initialized( boolean action, boolean value ) + { + synchronized( SoundSystemConfig.THREAD_SYNC ) + { + if( action == SET ) + initialized = value; + return initialized; + } + } + +/** + * Sets or returns the value of boolean 'initialized'. + * @param action Action to perform (GET or SET). + * @param value New value if action is SET, otherwise XXX. + * @return value of boolean 'initialized'. + */ + private static Class currentLibrary( boolean action, + Class value ) + { + synchronized( SoundSystemConfig.THREAD_SYNC ) + { + if( action == SET ) + currentLibrary = value; + return currentLibrary; + } + } + +/** + * Sets or returns the error code for the last error that occurred. If no + * errors have occurred, returns SoundSystem.ERROR_NONE + * @param action Action to perform (GET or SET). + * @param e New exception if action is SET, otherwise XXX. + * @return Last SoundSystemException thrown. + */ + private static SoundSystemException lastException( boolean action, + SoundSystemException e ) + { + synchronized( SoundSystemConfig.THREAD_SYNC ) + { + if( action == SET ) + lastException = e; + return lastException; + } + } + +/** + * Sleeps for the specified number of milliseconds. + */ + protected static void snooze( long milliseconds ) + { + try + { + Thread.sleep( milliseconds ); + } + catch( InterruptedException e ){} + } + +/** + * Prints a message. + * @param message Message to print. + * @param indent Number of tabs to indent the message. + */ + protected void message( String message, int indent ) + { + logger.message( message, indent ); + } + +/** + * Prints an important message. + * @param message Message to print. + * @param indent Number of tabs to indent the message. + */ + protected void importantMessage( String message, int indent ) + { + logger.importantMessage( message, indent ); + } + +/** + * Prints the specified message if error is true. + * @param error True or False. + * @param message Message to print if error is true. + * @param indent Number of tabs to indent the message. + * @return True if error is true. + */ + protected boolean errorCheck( boolean error, String message, int indent ) + { + return logger.errorCheck( error, className, message, indent ); + } + +/** + * Prints an error message. + * @param message Message to print. + * @param indent Number of tabs to indent the message. + */ + protected void errorMessage( String message, int indent ) + { + logger.errorMessage( className, message, indent ); + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundSystemConfig.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundSystemConfig.java new file mode 100644 index 0000000..546d71e --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundSystemConfig.java @@ -0,0 +1,1083 @@ +/** + * 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; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Locale; +import java.util.ListIterator; +import java.util.LinkedList; + +/** + * The SoundSystemConfig class is used to access global sound system settings, + * and to link with external pluggins. All members of this class are static. + * SoundSystemConfig is sort of a "catch all" configuration class, so if you + * are not sure where to find something in the SoundSystem library, this is + * probably a good place to start. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 SoundSystemConfig +{ +// GLOBAL THREAD SYNCHRONIZATION +/** + * Lock object used to synchronize the three threads used by SoundSystem. + * Synchronize on this anytime you manually manipulate a Source's properties. + */ + public static final Object THREAD_SYNC = new Object(); +// END GLOBAL THREAD SYNCHRONIZATION + +// GLOBAL IDENTIFIERS + +/** + * A normal (non-streaming) source. Also used to define a Channel type as + * normal. + */ + public static final int TYPE_NORMAL = 0; +/** + * A streaming source. Also used to define a Channel type as streaming. + */ + public static final int TYPE_STREAMING = 1; + +/** + * Global identifier for no attenuation. Attenuation is how a source's volume + * fades with distance. When there is no attenuation, a source's volume + * remains constaint regardles of distance. + */ + public static final int ATTENUATION_NONE = 0; // no attenuation +/** + * Global identifier for rolloff attenuation. Rolloff attenuation is a + * realistic attenuation model, which uses a rolloff factor to determine how + * quickly a source fades with distance. A smaller rolloff factor will fade at + * a further distance, and a rolloff factor of 0 will never fade. NOTE: In + * OpenAL, rolloff attenuation only works for monotone sounds. + */ + public static final int ATTENUATION_ROLLOFF = 1; // logrithmic attenuation +/** + * Global identifier for linear attenuation. Linear attenuation is less + * realistic than rolloff attenuation, but it allows the user to specify a + * maximum "fade distance" where a source's volume becomes zero. + */ + public static final int ATTENUATION_LINEAR = 2; // linear attenuation + +/** + * A Regular expression for determining if a file's extension is MIDI. + */ + public static String EXTENSION_MIDI = ".*[mM][iI][dD][iI]?$"; + +/** + * A Regular expression for determining if a path is an online URL. + */ + public static String PREFIX_URL = "^[hH][tT][tT][pP]://.*"; + +// END GLOBAL IDENTIFIERS + + +// PRIVATE STATIC VARIABLES + +/** + * Handle to the message logger. The default logger can be changed by + * overridding the {@link com.ardor3d.audio.SoundSystemLogger SoundSystemLogger} + * class and calling the setLogger() method (must be done BEFORE instantiating + * the SoundSystem class!) + */ + private static SoundSystemLogger logger = null; + +/** + * List of library types in their order of priority. + */ + private static LinkedList<Class> libraries; + +/** + * List of codecs and the file formats they are associated with. + */ + private static LinkedList<Codec> codecs = null; + +/** + * List of stream listeners. + */ + private static LinkedList<IStreamListener> streamListeners = null; +/** + * For synchronizing access to the streamListeners list. + */ + private static final Object streamListenersLock = new Object(); + +/** + * Maximum number of normal (non-streaming) channels that can be created. + * NOTE: JavaSound may require the total number of channels (non-streaming + + * streaming) to be 32. + */ + private static int numberNormalChannels = 28; +/** + * Maximum number of streaming channels that can be created. + * NOTE: JavaSound may require the total number of channels (non-streaming + + * streaming) to be 32. + */ + private static int numberStreamingChannels = 4; +/** + * Overall volume, affecting all sources. Float value (0.0f - 1.0f). + */ + private static float masterGain = 1.0f; +/** + * Attenuation model to use if not specified. Attenuation is how a source's + * volume fades with distance. + */ + private static int defaultAttenuationModel = ATTENUATION_ROLLOFF; +/** + * Default value to use for the rolloff factor if not specified. + */ + private static float defaultRolloffFactor = 0.03f; +/** + * Value to use for the doppler factor, for determining Doppler scale. + */ + private static float dopplerFactor = 0.0f; +/** + * Value to use for the doppler velocity. + */ + private static float dopplerVelocity = 1.0f; +/** + * Default value to use for fade distance if not specified. + */ + private static float defaultFadeDistance = 1000.0f; +/** + * Package where the sound files are located (must be followed by '/'). + */ + private static String soundFilesPackage = "Sounds/"; + +/** + * Number of bytes to load at a time when streaming. + */ + private static int streamingBufferSize = 131072; +/** + * Number of buffers used for each streaming sorce. Slow codecs may require + * this number to be greater than 2 to prevent audio skipping during playback. + */ + private static int numberStreamingBuffers = 3; +/** + * Enables a transition-speed optimization by assuming all sounds in each + * streaming source's queue will have exactly the same format once decoded + * (including channels, sample rate, and sample size). This is an advanced + * setting which should only be changed by experienced developers. + */ + private static boolean streamQueueFormatsMatch = false; +/** + * The maximum number of bytes to read in for (non-streaming) files. + * Increase this value if non-streaming sounds are getting cut off. + * Decrease this value if large sound files are causing lag during load time. + */ + private static int maxFileSize = 268435456; +/** + * Size of each chunk to read at a time for loading (non-streaming) files. + * Increase if loading sound files is causing significant lag. + */ + private static int fileChunkSize = 1048576; + +/** + * Indicates whether or not there is a codec for reading from MIDI files. If + * there is no codec for MIDI, then SoundSystem uses javax.sound.midi. + */ + private static boolean midiCodec = false; + +/** + * MIDI device to try using as the Synthesizer. May be the full name or part + * of the name. If this String is empty, the default Synthesizer will be used, + * or one of the common alternate synthesizers if the default Synthesizer is + * unavailable. + */ + private static String overrideMIDISynthesizer = ""; + +// END PRIVATE STATIC VARIABLES + +// THESE TWO METHODS PROVIDE INFORMATION ABOUT THE INDIVIDUAL SOUND LIBRARIES + +/** + * Adds an entry to the list of library types. This method has no effect if + * the specified library type is already in the list of libraries. + * NOTE: The parameterless constructor of the SoundSystem class will try to + * load libraries in the order that they were entered into the list. + * @param libraryClass Derivitive of class 'Library'. +*/ + public static void addLibrary( Class libraryClass ) + throws SoundSystemException + { + if( libraryClass == null ) + throw new SoundSystemException( + "Parameter null in method 'addLibrary'", + SoundSystemException.NULL_PARAMETER ); + if( !Library.class.isAssignableFrom( libraryClass ) ) + throw new SoundSystemException( "The specified class does not " + + "extend class 'Library' in method 'addLibrary'" ); + + if( libraries == null ) + libraries = new LinkedList<Class>(); + + if( !libraries.contains( libraryClass ) ) + libraries.add( libraryClass ); + } + +/** + * Removes the specified library from the list of library types. + * @param libraryClass Derivitive of class 'Library'. +*/ + public static void removeLibrary( Class libraryClass ) + throws SoundSystemException + { + if( libraries == null || libraryClass == null ) + return; + + libraries.remove( libraryClass ); + } + +/** + * Returns the list of library types. + * @return LinkedList of classes derived from 'Library', or null if none were specified. +*/ + public static LinkedList<Class> getLibraries() + { + return libraries; + } + +/** + * Checks if the specified library class is compatible on the user's machine. + * @param libraryClass Library type to check. + * @return True or false. +*/ + public static boolean libraryCompatible( Class libraryClass ) + { + if( libraryClass == null ) + { + errorMessage( "Parameter 'libraryClass' null in method" + + "'librayCompatible'" ); + return false; + } + if( !Library.class.isAssignableFrom( libraryClass ) ) + { + errorMessage( "The specified class does not extend class " + + "'Library' in method 'libraryCompatible'" ); + return false; + } + + Object o = runMethod( libraryClass, "libraryCompatible", + new Class[0], new Object[0] ); + + if( o == null ) + { + errorMessage( "Method 'Library.libraryCompatible' returned " + + "'null' in method 'libraryCompatible'" ); + return false; + } + + return( ( (Boolean) o ).booleanValue() ); + } + +/** + * Return the short title of the specified library, or null if error. + * @param libraryClass Derivitive of class 'Library'. + * @return String containing the library title. +*/ + public static String getLibraryTitle( Class libraryClass ) + { + if( libraryClass == null ) + { + errorMessage( "Parameter 'libraryClass' null in method" + + "'getLibrayTitle'" ); + return null; + } + if( !Library.class.isAssignableFrom( libraryClass ) ) + { + errorMessage( "The specified class does not extend class " + + "'Library' in method 'getLibraryTitle'" ); + return null; + } + + Object o = runMethod( libraryClass, "getTitle", new Class[0], + new Object[0] ); + if( o == null ) + { + errorMessage( "Method 'Library.getTitle' returned " + + "'null' in method 'getLibraryTitle'" ); + return null; + } + + return( (String) o ); + } + +/** + * Return the longer description of the specified library, or null if error. + * @param libraryClass Derivitive of class 'Library'. + * @return String containing the library title. +*/ + public static String getLibraryDescription( Class libraryClass ) + { + if( libraryClass == null ) + { + errorMessage( "Parameter 'libraryClass' null in method" + + "'getLibrayDescription'" ); + return null; + } + if( !Library.class.isAssignableFrom( libraryClass ) ) + { + errorMessage( "The specified class does not extend class " + + "'Library' in method 'getLibraryDescription'" ); + return null; + } + + Object o = runMethod( libraryClass, "getDescription", + new Class[0], new Object[0] ); + if( o == null ) + { + errorMessage( "Method 'Library.getDescription' returned " + + "'null' in method 'getLibraryDescription'" ); + return null; + } + + return( (String) o ); + } + +/** + * Return whether or not requires reversal of audio data byte-order. + * @param libraryClass Derivitive of class 'Library'. + * @return True if byte-order reversal is required. +*/ + public static boolean reverseByteOrder( Class libraryClass ) + { + if( libraryClass == null ) + { + errorMessage( "Parameter 'libraryClass' null in method" + + "'reverseByteOrder'" ); + return false; + } + if( !Library.class.isAssignableFrom( libraryClass ) ) + { + errorMessage( "The specified class does not extend class " + + "'Library' in method 'reverseByteOrder'" ); + return false; + } + + Object o = runMethod( libraryClass, "reversByteOrder", + new Class[0], new Object[0] ); + if( o == null ) + { + errorMessage( "Method 'Library.reverseByteOrder' returned " + + "'null' in method 'getLibraryDescription'" ); + return false; + } + + return( ((Boolean) o).booleanValue() ); + } + +// END LIBRARY INFORMATION + +// Use the following methods to interface the private variables above: + +// STATIC NONSYNCHRONIZED INTERFACE METHODS +/** + * Changes the message logger to use for handling status messages, warnings, + * and error messages. This method should only be called BEFORE instantiating + * the SoundSystem class! If this method is called after the SoundSystem has + * been created, there will be handles floating around to two different + * loggers, and the results will be undesirable. This method can be used to + * change how messages are handled. First, the + * {@link com.ardor3d.audio.SoundSystemLogger SoundSystemLogger} class should be + * extended and methods overriden to change how messages are handled. Then, + * the overridden class should be instantiated, and a call made to + * SoundSystemConfig.setLogger() before creating the SoundSystem object. + * If an alternate logger is not set by the user before the SoundSystem is + * instantiated, then an instance of the base SoundSystemLogger class will be + * used by default. + * @param l Handle to a message logger. + */ + public static void setLogger( SoundSystemLogger l ) + { + logger = l; + } +/** + * Returns a handle to the message logger. + * @return The current message logger. + */ + public static SoundSystemLogger getLogger() + { + return logger; + } + +// STATIC SYNCHRONIZED INTERFACE METHODS + +/** + * Sets the maximum number of normal (non-streaming) channels that can be + * created. Streaming channels are created first, so the higher the maximum + * number of streaming channels is set, the fewer non-streaming channels will + * be available. If unable to create the number of channels specified, + * SoundSystem will create as many as possible. + * NOTE: Some sound library pluggins may require the total number of channels + * (non-streaming + streaming) to be 32. + * @param number How many normal audio channels. + */ + public static synchronized void setNumberNormalChannels( int number ) + { + numberNormalChannels = number; + } + +/** + * Returns the maximum number of normal (non-streaming) channels that can be + * created. + * @return Maximum non-streaming channels. + */ + public static synchronized int getNumberNormalChannels() + { + return numberNormalChannels; + } + +/** + * Sets the maximum number of streaming channels that can be created. + * Streaming channels are created first, so the higher the maximum number of + * streaming channels is set, the fewer non-streaming channels will + * be available. If unable to create the number of channels specified, + * SoundSystem will create as many as possible. + * NOTE: Some sound library pluggins may require the total number of channels + * (non-streaming + streaming) to be 32. + * @param number How many streaming audio channels. + */ + public static synchronized void setNumberStreamingChannels( int number ) + { + numberStreamingChannels = number; + } + +/** + * Returns the maximum number of streaming channels that can be created. + * @return Maximum streaming channels. + */ + public static synchronized int getNumberStreamingChannels() + { + return numberStreamingChannels; + } + +/** + * Sets the varriable used for overall volume, affecting all sources. + * @param value Float value (0.0f - 1.0f). + */ + public static synchronized void setMasterGain( float value ) + { + masterGain = value; + } + +/** + * Returns the value for the overall volume. + * @return A float value (0.0f - 1.0f). + */ + public static synchronized float getMasterGain() + { + return masterGain; + } + +/** + * Sets the default attenuation model to use when one is not specified. + * Attenuation is how a source's volume fades with distance. + * @param model A global attenuation model identifier. + */ + public static synchronized void setDefaultAttenuation( int model ) + { + defaultAttenuationModel = model; + } +/** + * Returns the default attenuation model used when one is not specified. + * @return A global attenuation model identifier + */ + public static synchronized int getDefaultAttenuation() + { + return defaultAttenuationModel; + } +/** + * Sets the default rolloff factor to use when one is not specified. + * @param rolloff Rolloff factor. + */ + public static synchronized void setDefaultRolloff( float rolloff ) + { + defaultRolloffFactor = rolloff; + } +/** + * Returns the doppler factor, for determining Doppler Effect scale. + * @return Doppler factor + */ + public static synchronized float getDopplerFactor() + { + return dopplerFactor; + } +/** + * Sets the doppler factor, for determining Doppler Effect scale. Use this + * method BEFORE instantiating the SoundSystem. To change the Doppler factor + * after the SoundSystem is instantiated, use the + * SoundSystem.changeDopplerFactor method instead. + * @param factor Doppler factor. + */ + public static synchronized void setDopplerFactor( float factor ) + { + dopplerFactor = factor; + } +/** + * Returns the Doppler Velocity, for use in Doppler Effect. + * @return Doppler velocity. + */ + public static synchronized float getDopplerVelocity() + { + return dopplerVelocity; + } +/** + * Sets the Doppler velocity, for use in Doppler Effect. Use this method + * BEFORE instantiating the SoundSystem. To change the Doppler velocity after + * the SoundSystem is instantiated, use the SoundSystem.changeDopplerVelocity + * method instead. + * @param velocity Doppler velocity. + */ + public static synchronized void setDopplerVelocity( float velocity ) + { + dopplerVelocity = velocity; + } +/** + * Returns the default rolloff factor used when one is not specified. + * @return Default rolloff factor + */ + public static synchronized float getDefaultRolloff() + { + return defaultRolloffFactor; + } +/** + * Sets the default fade distance to use when one is not specified. + * @param distance Fade Distance. + */ + public static synchronized void setDefaultFadeDistance( float distance ) + { + defaultFadeDistance = distance; + } +/** + * Returns the default fade distance used when one is not specified. + * @return Default fade distance + */ + public static synchronized float getDefaultFadeDistance() + { + return defaultFadeDistance; + } +/** + * Sets the package where sound files are located. + * @param location Path to the sound files location (must be followed by '/'). + */ + public static synchronized void setSoundFilesPackage( String location ) + { + soundFilesPackage = location; + } +/** + * Returns the package where sound files are located. + * @return Path to the sound files location + */ + public static synchronized String getSoundFilesPackage() + { + return soundFilesPackage; + } +/** + * Sets the number of bytes to load at a time when streaming. + * @param size Size in bytes. + */ + public static synchronized void setStreamingBufferSize( int size ) + { + streamingBufferSize = size; + } +/** + * Returns the number of bytes to load at a time when streaming. + * @return Size in bytes. + */ + public static synchronized int getStreamingBufferSize() + { + return streamingBufferSize; + } +/** + * Sets the number of buffers used for each streaming sorce. + * Slow codecs may require this number to be greater than 2 to prevent audio + * skipping during playback. + * @param num How many buffers. + */ + public static synchronized void setNumberStreamingBuffers( int num ) + { + numberStreamingBuffers = num; + } +/** + * Returns the number of buffers used for each streaming sorce. + * @return How many buffers. + */ + public static synchronized int getNumberStreamingBuffers() + { + return numberStreamingBuffers; + } + +/** + * Enables a transition-speed optimization by assuming all sounds in each + * streaming source's queue will have exactly the same format once decoded + * (including channels, sample rate, and sample size). This is an advanced + * setting which should only be changed by experienced developers. + * @param val False by default. + */ + public static synchronized void setStreamQueueFormatsMatch( boolean val ) + { + streamQueueFormatsMatch = val; + } + +/** + * Returns whether or not all sounds in each streaming source's queue will be + * handled as if they have exactly the same format once decoded (including + * channels, sample rate, and sample size). This is an advanced setting which + * should only be changed by experienced developers. + * @return Normally false. + */ + public static synchronized boolean getStreamQueueFormatsMatch() + { + return streamQueueFormatsMatch; + } + +/** + * Sets the maximum number of bytes to read in for (non-streaming) files. + * Increase this value if non-streaming sounds are getting cut off. + * Decrease this value if large sound files are causing lag during load time. + * @param size Size in bytes. + */ + public static synchronized void setMaxFileSize( int size ) + { + maxFileSize = size; + } +/** + * Returns the maximum number of bytes to read in for (non-streaming) files. + * @return Size in bytes. + */ + public static synchronized int getMaxFileSize() + { + return maxFileSize; + } +/** + * Sets the size of each chunk to read at a time for loading (non-streaming) + * files. Increase if loading sound files is causing significant lag. + * @param size Size in bytes. + */ + public static synchronized void setFileChunkSize( int size ) + { + fileChunkSize = size; + } +/** + * Returns the size of each chunk to read at a time for loading (non-streaming) + * files. + * @return Size in bytes. + */ + public static synchronized int getFileChunkSize() + { + return fileChunkSize; + } +/** + * Returns the name of the MIDI synthesizer to use instead of the default, or + * empty string if none was specified. + * @return All or part of a MIDI device name, or empty string for not specified. + */ + public static synchronized String getOverrideMIDISynthesizer() + { + return overrideMIDISynthesizer; + } +/** + * Sets the name of the MIDI synthesizer to use instead of the default. If + * 'name' is an empty string, the default Synthesizer will be used, or one of + * the common alternate synthesizers if the default Synthesizer is unavailable. + * @param name All or part of the MIDI device name. + */ + public static synchronized void setOverrideMIDISynthesizer( String name ) + { + overrideMIDISynthesizer = name; + } +/** + * Uses the specified file extension to associate a particular file format + * with the codec used to read audio data from it. + * @param extension File extension to be associated with the specified codec. + * @param iCodecClass Codec type to use for files with the specified extension. + */ + public static synchronized void setCodec( String extension, + Class iCodecClass ) + throws SoundSystemException + { + if( extension == null ) + throw new SoundSystemException( "Parameter 'extension' null in " + + "method 'setCodec'.", + SoundSystemException.NULL_PARAMETER ); + if( iCodecClass == null ) + throw new SoundSystemException( "Parameter 'iCodecClass' null in " + + "method 'setCodec'.", + SoundSystemException.NULL_PARAMETER ); + if( !ICodec.class.isAssignableFrom( iCodecClass ) ) + throw new SoundSystemException( "The specified class does " + + "not implement interface 'ICodec' in method 'setCodec'", + SoundSystemException.CLASS_TYPE_MISMATCH ); + + if( codecs == null ) + codecs = new LinkedList<Codec>(); + + ListIterator<Codec> i = codecs.listIterator(); + Codec codec; + + while( i.hasNext() ) + { + codec = i.next(); + if( extension.matches( codec.extensionRegX ) ) + i.remove(); + } + codecs.add( new Codec( extension, iCodecClass ) ); + + // Let SoundSystem know if this is a MIDI codec, so it won't use + // javax.sound.midi anymore: + if( extension.matches( EXTENSION_MIDI ) ) + midiCodec = true; + } +/** + * Returns the codec that can be used to read audio data from the specified + * file. + * @param filename File to get a codec for. + * @return Codec to use for reading audio data. + */ + public static synchronized ICodec getCodec( String filename ) + { + if( codecs == null ) + return null; + + ListIterator<Codec> i = codecs.listIterator(); + Codec codec; + + while( i.hasNext() ) + { + codec = i.next(); + if( filename.matches( codec.extensionRegX ) ) + return codec.getInstance(); + } + + return null; + } + +/** + * Indicates whether or not there is a codec for reading from MIDI files. If + * there is no codec for MIDI, then SoundSystem uses javax.sound.midi. + * @return True if there the user defined a MIDI codec. + */ + public static boolean midiCodec() + { + return midiCodec; + } + +/** + * Adds an entry to the list of stream listeners. If the instance is already + * in the list, the command is ignored. + * @param streamListener Implementation of interface 'IStreamListener'. +*/ + public static void addStreamListener( IStreamListener streamListener ) + { + synchronized( streamListenersLock ) + { + if( streamListeners == null ) + streamListeners = new LinkedList<IStreamListener>(); + + if( !streamListeners.contains( streamListener ) ) + streamListeners.add( streamListener ); + } + } + +/** + * Removes an entry from the list of stream listeners. + * @param streamListener Implementation of interface 'IStreamListener'. +*/ + public static void removeStreamListener( IStreamListener streamListener ) + { + + synchronized( streamListenersLock ) + { + if( streamListeners == null ) + streamListeners = new LinkedList<IStreamListener>(); + + if( streamListeners.contains( streamListener ) ) + streamListeners.remove( streamListener ); + } + } + +/** + * Notifies all stream listeners that an End Of Stream was reached. If there + * are no listeners, the command is ignored. + * @param sourcename String identifier of the source which reached the EOS. + * @param queueSize Number of items left the the stream's play queue, or zero if none. +*/ + public static void notifyEOS( String sourcename, int queueSize ) + { + synchronized( streamListenersLock ) + { + if( streamListeners == null ) + return; + } + final String srcName = sourcename; + final int qSize = queueSize; + + new Thread() + { + @Override + public void run() + { + synchronized( streamListenersLock ) + { + if( streamListeners == null ) + return; + ListIterator<IStreamListener> i = streamListeners.listIterator(); + IStreamListener streamListener; + while( i.hasNext() ) + { + streamListener = i.next(); + if( streamListener == null ) + i.remove(); + else + streamListener.endOfStream( srcName, qSize ); + } + } + } + }.start(); + } + +// END STATIC SYNCHRONIZED INTERFACE METHODS + + +// PRIVATE INTERNAL METHODS + +/** + * Display the specified error message using the current logger. + * @param message Error message to display. +*/ + private static void errorMessage( String message ) + { + if( logger != null ) + logger.errorMessage( "SoundSystemConfig", message, 0 ); + } + + // We don't know what Class parameter 'c' is, so we will ignore the + // warning message "unchecked call to getMethod". + @SuppressWarnings("unchecked") +/** + * Returns the results of calling the specified method from the specified + * class using the specified parameters. + * @param c Class to call the method on. + * @param method Name of the method. + * @param paramTypes Data types of the parameters being passed to the method. + * @param params Actual parameters to pass to the method. + * @return Specified method's return value, or null if error or void. +*/ + private static Object runMethod( Class c, String method, Class[] paramTypes, + Object[] params ) + { + Method m = null; + try + { + m = c.getMethod( method, paramTypes ); // <--- generates a warning + } + catch( NoSuchMethodException nsme ) + { + errorMessage( "NoSuchMethodException thrown when attempting " + + "to call method '" + method + "' in " + + "method 'runMethod'" ); + return null; + } + catch( SecurityException se ) + { + errorMessage( "Access denied when attempting to call method '" + + method + "' in method 'runMethod'" ); + return null; + } + catch( NullPointerException npe ) + { + errorMessage( "NullPointerException thrown when attempting " + + "to call method '" + method + "' in " + + "method 'runMethod'" ); + return null; + } + if( m == null ) + { + errorMessage( "Method '" + method + "' not found for the class " + + "specified in method 'runMethod'" ); + return null; + } + + Object o = null; + try + { + o = m.invoke( null, params ); + } + catch( IllegalAccessException iae ) + { + errorMessage( "IllegalAccessException thrown when attempting " + + "to invoke method '" + method + "' in " + + "method 'runMethod'" ); + return null; + } + catch( IllegalArgumentException iae ) + { + errorMessage( "IllegalArgumentException thrown when attempting " + + "to invoke method '" + method + "' in " + + "method 'runMethod'" ); + return null; + } + catch( InvocationTargetException ite ) + { + errorMessage( "InvocationTargetException thrown while attempting " + + "to invoke method 'Library.getTitle' in " + + "method 'getLibraryTitle'" ); + return null; + } + catch( NullPointerException npe ) + { + errorMessage( "NullPointerException thrown when attempting " + + "to invoke method '" + method + "' in " + + "method 'runMethod'" ); + return null; + } + catch( ExceptionInInitializerError eiie ) + { + errorMessage( "ExceptionInInitializerError thrown when " + + "attempting to invoke method '" + method + "' in " + + "method 'runMethod'" ); + return null; + } + + return( o ); + } + +// END PRIVATE INTERNAL METHODS + + +// PRIVATE INTERNAL CLASSES + +/** + * The Codec class is used to associate individual file formats with the + * codecs used to load audio data from them. + * + * Author: Paul Lamb + */ + private static class Codec + { +/** + * A regular expression used to match a file's extension. This is used to + * determine the file format. + */ + public String extensionRegX; +/** + * Codec used to load audio data from this file format. + */ + public Class iCodecClass; +/** + * Constructor: Converts the specified extension string into a regular + * expression, and associates that with the specified codec. + * @param extension File extension to be associated with the specified codec. + * @param iCodec Codec to use for files with the specified extension. + */ + public Codec( String extension, Class iCodecClass ) + { + extensionRegX = ""; + // Make sure an extension was specified: + if( extension != null && extension.length() > 0 ) + { + // We are only interested in the file extension. The filename + // can begin with whatever: + extensionRegX = ".*"; + String c; + for( int x = 0; x < extension.length(); x++ ) + { + // Each character could be either upper or lower case: + c = extension.substring( x, x + 1 ); + extensionRegX += "[" + c.toLowerCase( Locale.ENGLISH ) + + c.toUpperCase( Locale.ENGLISH ) + "]"; + } + // The extension will be at the end of the filename: + extensionRegX += "$"; + } + // remember the codec to use for this format: + this.iCodecClass = iCodecClass; + } + + public ICodec getInstance() + { + if( iCodecClass == null ) + return null; + + Object o = null; + try + { + o = iCodecClass.newInstance(); + } + catch( InstantiationException ie ) + { + instantiationErrorMessage(); + return null; + } + catch( IllegalAccessException iae ) + { + instantiationErrorMessage(); + return null; + } + catch( ExceptionInInitializerError eiie ) + { + instantiationErrorMessage(); + return null; + } + catch( SecurityException se ) + { + instantiationErrorMessage(); + return null; + } + + + if( o == null ) + { + instantiationErrorMessage(); + return null; + } + + return (ICodec) o; + } + + private void instantiationErrorMessage() + { + errorMessage( "Unrecognized ICodec implementation in method " + + "'getInstance'. Ensure that the implementing " + + "class has one public, parameterless constructor." ); + } + } +// END PRIVATE INTERNAL CLASSES +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundSystemException.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundSystemException.java new file mode 100644 index 0000000..9d22c81 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundSystemException.java @@ -0,0 +1,103 @@ +/** + * 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; + +/** + * The SoundSystemException class is used to provide information about serious + * errors. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 SoundSystemException extends Exception +{ +/** + * Global identifier for no problem. + */ + public static final int ERROR_NONE = 0; +/** + * Global identifier for a generic exception. + */ + public static final int UNKNOWN_ERROR = 1; +/** + * Global identifier for a null parameter. + */ + public static final int NULL_PARAMETER = 2; +/** + * Global identifier for a class type mismatch. + */ + public static final int CLASS_TYPE_MISMATCH = 3; +/** + * Global identifier for the sound library does not exist. + */ + public static final int LIBRARY_NULL = 4; +/** + * Global identifier for the sound library does not exist. + */ + public static final int LIBRARY_TYPE = 5; + +/** + * Holds a global identifier indicating the type of exception. + */ + private int myType = UNKNOWN_ERROR; + +/** + * Constructor: Generic exception. Specify the error message. + */ + public SoundSystemException( String message ) + { + super( message ); + } + +/** + * Constructor: Specify the error message and type of exception. + * @param message Description of the problem. + * @param type Global identifier for type of exception. + */ + public SoundSystemException( String message, int type ) + { + super( message ); + myType = type; + } + + public int getType() + { + return myType; + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundSystemLogger.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundSystemLogger.java new file mode 100644 index 0000000..dbb4a90 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/SoundSystemLogger.java @@ -0,0 +1,182 @@ +/** + * 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; + +/** + * The SoundSystemLogger class handles all status messages, warnings, and error + * messages for the SoundSystem library. This class can be extended and + * methods overriden to change how messages are handled. To do this, the + * overridden class should be instantiated, and a call should be made to method + * SoundSystemConfig.setLogger() BEFORE creating the SoundSystem object. If + * the setLogger() method is called after the SoundSystem has been created, + * there will be handles floating around to two different message loggers, and + * the results will be undesirable. + * See {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about changing default settings. If an alternate logger is not + * set by the user, then an instance of this base class will be automatically + * created by default when the SoundSystem class is instantiated. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 SoundSystemLogger +{ +/** + * Prints a message. + * @param message Message to print. + * @param indent Number of tabs to indent the message. + */ + public void message( String message, int indent ) + { + String messageText; + // Determine how many spaces to indent: + String spacer = ""; + for( int x = 0; x < indent; x++ ) + { + spacer += " "; + } + // indent the message: + messageText = spacer + message; + + // Print the message: + System.out.println( messageText ); + } + +/** + * Prints an important message. + * @param message Message to print. + * @param indent Number of tabs to indent the message. + */ + public void importantMessage( String message, int indent ) + { + String messageText; + // Determine how many spaces to indent: + String spacer = ""; + for( int x = 0; x < indent; x++ ) + { + spacer += " "; + } + // indent the message: + messageText = spacer + message; + + // Print the message: + System.out.println( messageText ); + } + +/** + * Prints the specified message if error is true. + * @param error True or False. + * @param classname Name of the class checking for an error. + * @param message Message to print if error is true. + * @param indent Number of tabs to indent the message. + * @return True if error is true. + */ + public boolean errorCheck( boolean error, String classname, String message, + int indent ) + { + if( error ) + errorMessage( classname, message, indent ); + return error; + } + +/** + * Prints the classname which generated the error, followed by the error + * message. + * @param classname Name of the class which generated the error. + * @param message The actual error message. + * @param indent Number of tabs to indent the message. +*/ + public void errorMessage( String classname, String message, int indent ) + { + String headerLine, messageText; + // Determine how many spaces to indent: + String spacer = ""; + for( int x = 0; x < indent; x++ ) + { + spacer += " "; + } + // indent the header: + headerLine = spacer + "Error in class '" + classname + "'"; + // indent the message one more than the header: + messageText = " " + spacer + message; + + // Print the error message: + System.out.println( headerLine ); + System.out.println( messageText ); + } + +/** + * Prints an exception's error message followed by the stack trace. + * @param e Exception containing the information to print. + * @param indent Number of tabs to indent the message and stack trace. + */ + public void printStackTrace( Exception e, int indent ) + { + printExceptionMessage( e, indent ); + importantMessage( "STACK TRACE:", indent ); + if( e == null ) + return; + + StackTraceElement[] stack = e.getStackTrace(); + if( stack == null ) + return; + + StackTraceElement line; + for( int x = 0; x < stack.length; x++ ) + { + line = stack[x]; + if( line != null ) + message( line.toString(), indent + 1 ); + } + } + +/** + * Prints an exception's error message. + * @param e Exception containing the message to print. + * @param indent Number of tabs to indent the message. + */ + public void printExceptionMessage( Exception e, int indent ) + { + importantMessage( "ERROR MESSAGE:", indent ); + if( e.getMessage() == null ) + message( "(none)", indent + 1 ); + else + message( e.getMessage(), indent + 1 ); + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/Source.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/Source.java new file mode 100644 index 0000000..09ac50d --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/Source.java @@ -0,0 +1,1347 @@ +/** + * 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; + +import java.net.URL; +import java.util.LinkedList; +import java.util.ListIterator; +import com.ardor3d.audio.sampled.AudioFormat; + +/** + * The Source class is used to store information about a source. + * Source objects are stored in a map in the Library class. The + * information they contain is used to create library-specific sources. + * This is the template class which is extended for each specific library. + * This class is also used by the "No Sound" library to represent a mute + * source. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 Source +{ +/** + * The library class associated with this type of channel. + */ + protected Class libraryType = Library.class; + +/** + * 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; + +/** + * Processes status messages, warnings, and error messages. + */ + private SoundSystemLogger logger; + +/** + * True if this source is being directly fed with raw audio data. + */ + public boolean rawDataStream = false; + +/** + * Format the raw data will be in if this is a Raw Data Stream source. + */ + public AudioFormat rawDataFormat = null; + +/** + * Determines whether a source should be removed after it finishes playing. + */ + public boolean temporary = false; + +/** + * Determines whether or not this is a priority source. Priority sources will + * not be overwritten by other sources when there are no available channels. + */ + public boolean priority = false; + +/** + * Whether or not this source should be streamed. + */ + public boolean toStream = false; + +/** + * Whether this source should loop or only play once. + */ + public boolean toLoop = false; + +/** + * Whether this source needs to be played (for example if it was playing and + * looping when it got culled). + */ + public boolean toPlay = false; + +/** + * Unique name for this source. More than one source can not have the same + * sourcename. + */ + public String sourcename = ""; + +/** + * The audio file which this source should play. + */ + public FilenameURL filenameURL = null; + +/** + * This source's position in 3D space. + */ + public Vector3D position; + +/** + * Attenuation model to use for this source. + */ + public int attModel = 0; + +/** + * Either fade distance or rolloff factor, depending on the value of attModel. + */ + public float distOrRoll = 0.0f; + +/** + * Source's velocity in world-space, for use in Doppler effect. + */ + public Vector3D velocity; + +/** + * This source's volume (a float between 0.0 - 1.0). This value is used + * internally for attenuation, and should not be used to manually change a + * source's volume. + */ + public float gain = 1.0f; + +/** + * This value should be used to manually increase or decrease source volume. + */ + public float sourceVolume = 1.0f; + +/** + * This value represents the source's pitch (float value between 0.5f - 2.0f). + */ + protected float pitch = 1.0f; + +/** + * This source's distance from the listener. + */ + public float distanceFromListener = 0.0f; + +/** + * Channel to play this source on. + */ + public Channel channel = null; + +/** + * Holds the data used by normal sources. + */ + public SoundBuffer soundBuffer = null; + +/** + * False when this source gets culled. + */ + private boolean active = true; + +/** + * Whether or not this source has been stopped. + */ + private boolean stopped = true; + +/** + * Whether or not this source has been paused. + */ + private boolean paused = false; + +/** + * Codec used to read data for streaming sources. + */ + protected ICodec codec = null; + +/** + * Codec used to read in some initial data from the next sound in the queue. + */ + protected ICodec nextCodec = null; + +/** + * List of buffers to hold some initial data from the next sound in the queue. + */ + protected LinkedList<SoundBuffer> nextBuffers = null; + + +/** + * The list of files to stream when the current stream finishes. + */ + protected LinkedList<FilenameURL> soundSequenceQueue = null; + +/** + * Ensures that only one thread accesses the soundSequenceQueue at a time. + */ + protected final Object soundSequenceLock = new Object(); + +/** + * Used by streaming sources to indicate whether or not the initial + * stream-buffers still need to be queued. + */ + public boolean preLoad = false; + +/** + * Specifies the gain factor used for the fade-out effect, or -1 when + * source is not currently fading out. + */ + protected float fadeOutGain = -1.0f; + +/** + * Specifies the gain factor used for the fade-in effect, or 1 when + * source is not currently fading in. + */ + protected float fadeInGain = 1.0f; + +/** + * Specifies the number of miliseconds it should take to fade out. + */ + protected long fadeOutMilis = 0; + +/** + * Specifies the number of miliseconds it should take to fade in. + */ + protected long fadeInMilis = 0; + +/** + * System time in miliseconds when the last fade in/out volume check occurred. + */ + protected long lastFadeCheck = 0; + +/** + * Constructor: Creates a new source using the specified parameters. + * @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 The 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 Source( boolean priority, boolean toStream, boolean toLoop, + String sourcename, FilenameURL filenameURL, + SoundBuffer soundBuffer, float x, float y, float z, + int attModel, float distOrRoll, boolean temporary ) + { + // grab a handle to the message logger: + logger = SoundSystemConfig.getLogger(); + + this.priority = priority; + this.toStream = toStream; + this.toLoop = toLoop; + this.sourcename = sourcename; + this.filenameURL = filenameURL; + this.soundBuffer = soundBuffer; + position = new Vector3D( x, y, z ); + this.attModel = attModel; + this.distOrRoll = distOrRoll; + this.velocity = new Vector3D( 0, 0, 0 ); + this.temporary = temporary; + + if( toStream && filenameURL != null ) + codec = SoundSystemConfig.getCodec( filenameURL.getFilename() ); + } + +/** + * Constructor: Creates a new source matching the specified one. + * @param old Source to copy information from. + * @param soundBuffer Buffer containing audio data, or null if not loaded yet. + */ + public Source( Source old, SoundBuffer soundBuffer ) + { + // grab a handle to the message logger: + logger = SoundSystemConfig.getLogger(); + + priority = old.priority; + toStream = old.toStream; + toLoop = old.toLoop; + sourcename = old.sourcename; + filenameURL = old.filenameURL; + position = old.position.clone(); + attModel = old.attModel; + distOrRoll = old.distOrRoll; + velocity = old.velocity.clone(); + temporary = old.temporary; + + sourceVolume = old.sourceVolume; + + rawDataStream = old.rawDataStream; + rawDataFormat = old.rawDataFormat; + + this.soundBuffer = soundBuffer; + + if( toStream && filenameURL != null ) + codec = SoundSystemConfig.getCodec( filenameURL.getFilename() ); + } + +/** + * Constructor: Creates a new streaming source that will be directly fed with + * raw 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 'att'. + */ + public Source( AudioFormat audioFormat, boolean priority, String sourcename, + float x, float y, float z, int attModel, float distOrRoll ) + { + // grab a handle to the message logger: + logger = SoundSystemConfig.getLogger(); + + this.priority = priority; + this.toStream = true; + this.toLoop = false; + this.sourcename = sourcename; + this.filenameURL = null; + this.soundBuffer = null; + position = new Vector3D( x, y, z ); + this.attModel = attModel; + this.distOrRoll = distOrRoll; + this.velocity = new Vector3D( 0, 0, 0 ); + this.temporary = false; + + rawDataStream = true; + rawDataFormat = audioFormat; + } +/* Override methods */ + +/** + * Shuts the source down and removes references to all instantiated objects. + */ + public void cleanup() + { + if( codec != null ) + codec.cleanup(); + + synchronized( soundSequenceLock ) + { + if( soundSequenceQueue != null ) + soundSequenceQueue.clear(); + soundSequenceQueue = null; + } + + sourcename = null; + filenameURL = null; + position = null; + soundBuffer = null; + codec = null; + } + +/** + * If this is a streaming source, queues up the next sound to play when + * the previous stream ends. This method has no effect on non-streaming + * sources. + * @param filenameURL The filename/URL of the sound file to stream next. + */ + public void queueSound( FilenameURL filenameURL ) + { + if( !toStream ) + { + errorMessage( "Method 'queueSound' may only be used for " + + "streaming and MIDI sources." ); + return; + } + if( filenameURL == null ) + { + errorMessage( "File not specified in method 'queueSound'" ); + return; + } + + synchronized( soundSequenceLock ) + { + if( soundSequenceQueue == null ) + soundSequenceQueue = new LinkedList<FilenameURL>(); + soundSequenceQueue.add( filenameURL ); + } + } + +/** + * Removes the first occurrence of the specified filename from the list of + * sounds to play when the previous stream ends. This method has no effect + * on non-streaming sources. + * @param filename Filename/identifier of a sound file to remove from the queue. + */ + public void dequeueSound( String filename ) + { + if( !toStream ) + { + errorMessage( "Method 'dequeueSound' may only be used for " + + "streaming and MIDI sources." ); + return; + } + if( filename == null || filename.equals( "" ) ) + { + errorMessage( "Filename not specified in method 'dequeueSound'" ); + return; + } + + synchronized( soundSequenceLock ) + { + if( soundSequenceQueue != null ) + { + ListIterator<FilenameURL> i = soundSequenceQueue.listIterator(); + while( i.hasNext() ) + { + if( i.next().getFilename().equals( filename ) ) + { + i.remove(); + break; + } + } + } + } + } + +/** + * Fades out the volume of whatever this source is currently playing, then + * begins playing the specified filename at the source's previously assigned + * volume level. If the filename parameter is null or empty, the source will + * simply fade out and stop. The miliseconds parameter must be non-negative or + * zero. This method will remove anything that is currently in the list of + * queued sounds that would have played next when the current sound finished + * playing. This method has no effect on non-streaming sources. + * @param filenameURL Filename/URL of the sound file to play next, or null for none. + * @param milis Number of miliseconds the fadeout should take. + */ + public void fadeOut( FilenameURL filenameURL, long milis ) + { + if( !toStream ) + { + errorMessage( "Method 'fadeOut' may only be used for " + + "streaming and MIDI sources." ); + return; + } + if( milis < 0 ) + { + errorMessage( "Miliseconds may not be negative in method " + + "'fadeOut'." ); + return; + } + + fadeOutMilis = milis; + fadeInMilis = 0; + fadeOutGain = 1.0f; + lastFadeCheck = System.currentTimeMillis(); + + synchronized( soundSequenceLock ) + { + if( soundSequenceQueue != null ) + soundSequenceQueue.clear(); + + if( filenameURL != null ) + { + if( soundSequenceQueue == null ) + soundSequenceQueue = new LinkedList<FilenameURL>(); + soundSequenceQueue.add( filenameURL ); + } + } + } + +/** + * Fades out the volume of whatever this source is currently playing, then + * fades the volume back in playing the specified file. Final volume after + * fade-in completes will be equal to the source's previously assigned volume + * level. The filenameURL parameter may not be null or empty. The miliseconds + * parameters must be non-negative or zero. This method will remove anything + * that is currently in the list of queued sounds that would have played next + * when the current sound finished playing. This method has no effect on + * non-streaming sources. + * @param filenameURL Filename/URL of the sound file to play next, or null for none. + * @param milisOut Number of miliseconds the fadeout should take. + * @param milisIn Number of miliseconds the fadein should take. + */ + public void fadeOutIn( FilenameURL filenameURL, long milisOut, long milisIn ) + { + if( !toStream ) + { + errorMessage( "Method 'fadeOutIn' may only be used for " + + "streaming and MIDI sources." ); + return; + } + if( filenameURL == null ) + { + errorMessage( "Filename/URL not specified in method 'fadeOutIn'." ); + return; + } + if( milisOut < 0 || milisIn < 0 ) + { + errorMessage( "Miliseconds may not be negative in method " + + "'fadeOutIn'." ); + return; + } + + fadeOutMilis = milisOut; + fadeInMilis = milisIn; + + fadeOutGain = 1.0f; + lastFadeCheck = System.currentTimeMillis(); + + synchronized( soundSequenceLock ) + { + if( soundSequenceQueue == null ) + soundSequenceQueue = new LinkedList<FilenameURL>(); + soundSequenceQueue.clear(); + soundSequenceQueue.add( filenameURL ); + } + } + +/** + * Resets this source's volume if it is fading out or in. Returns true if this + * source is currently in the process of fading out. When fade-out completes, + * this method transitions the source to the next sound in the sound sequence + * queue if there is one. This method has no effect on non-streaming sources. + * @return True if this source is in the process of fading out. + */ + public boolean checkFadeOut() + { + if( !toStream ) + return false; + + if( fadeOutGain == -1.0f && fadeInGain == 1.0f ) + return false; + + long currentTime = System.currentTimeMillis(); + long milisPast = currentTime - lastFadeCheck; + lastFadeCheck = currentTime; + + if( fadeOutGain >= 0.0f ) + { + if( fadeOutMilis == 0 ) + { + fadeOutGain = -1.0f; + fadeInGain = 0.0f; + if( !incrementSoundSequence() ) + { + stop(); + } + positionChanged(); + preLoad = true; + return false; + } + else + { + float fadeOutReduction = ((float)milisPast) / ((float)fadeOutMilis); + fadeOutGain -= fadeOutReduction; + if( fadeOutGain <= 0.0f ) + { + fadeOutGain = -1.0f; + fadeInGain = 0.0f; + if( !incrementSoundSequence() ) + stop(); + positionChanged(); + preLoad = true; + return false; + } + } + positionChanged(); + return true; + } + + if( fadeInGain < 1.0f ) + { + fadeOutGain = -1.0f; + if( fadeInMilis == 0 ) + { + fadeOutGain = -1.0f; + fadeInGain = 1.0f; + } + else + { + float fadeInIncrease = ((float)milisPast) / ((float)fadeInMilis); + fadeInGain += fadeInIncrease; + if( fadeInGain >= 1.0f ) + { + fadeOutGain = -1.0f; + fadeInGain = 1.0f; + } + } + positionChanged(); + return true; + } + return false; + } + +/** + * Removes the next filename/URL 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. + */ + public boolean incrementSoundSequence() + { + if( !toStream ) + { + errorMessage( "Method 'incrementSoundSequence' may only be used " + + "for streaming and MIDI 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() ); + return true; + } + } + return false; + } + +/** + * Reads in initial buffers of data from the next sound in the sound sequence + * queue, to reduce lag when the transition occurrs. 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 False if there is nothing in the queue to read from. + */ + public boolean readBuffersFromNextSoundInSequence() + { + if( !toStream ) + { + errorMessage( "Method 'readBuffersFromNextSoundInSequence' may " + + "only be used for streaming sources." ); + return false; + } + + synchronized( soundSequenceLock ) + { + if( soundSequenceQueue != null && soundSequenceQueue.size() > 0 ) + { + if( nextCodec != null ) + nextCodec.cleanup(); + nextCodec = SoundSystemConfig.getCodec( + soundSequenceQueue.get( 0 ).getFilename() ); + nextCodec.initialize( soundSequenceQueue.get( 0 ).getURL() ); + + SoundBuffer buffer = null; + for( int i = 0; + i < SoundSystemConfig.getNumberStreamingBuffers() + && !nextCodec.endOfStream(); + i++ ) + { + buffer = nextCodec.read(); + if( buffer != null ) + { + if( nextBuffers == null ) + nextBuffers = new LinkedList<SoundBuffer>(); + nextBuffers.add( buffer ); + } + } + return true; + } + } + return false; + } + + +/** + * Returns the size of the sound sequence queue (if this is a streaming source). + * @return Number of sounds left in the queue, or zero if none. + */ + public int getSoundSequenceQueueSize() + { + if( soundSequenceQueue == null ) + return 0; + return soundSequenceQueue.size(); + } + +/** + * Sets whether or not this source should be removed when it finishes playing. + * @param tmp True or false. + */ + public void setTemporary( boolean tmp ) + { + temporary = tmp; + } + +/** + * Called every time the listener's position or orientation changes. + */ + public void listenerMoved() + {} + +/** + * 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. + */ + public void setPosition( float x, float y, float z ) + { + position.x = x; + position.y = y; + position.z = z; + } + +/** + * Called every time the source changes position. + */ + public void positionChanged() + {} + +/** + * Sets whether or not this source is a priority source. A priority source + * will not be overritten by another source if there are no channels available + * to play on. + * @param pri True or false. + */ + public void setPriority( boolean pri ) + { + priority = pri; + } + +/** + * Sets whether this source should loop or only play once. + * @param lp True or false. + */ + public void setLooping( boolean lp ) + { + toLoop = lp; + } + +/** + * Sets this source's attenuation model. + * @param model Attenuation model to use. + */ + public void setAttenuation( int model ) + { + attModel = model; + } + +/** + * Sets this source's fade distance or rolloff factor, depending on the + * attenuation model. + * @param dr New value for fade distance or rolloff factor. + */ + public void setDistOrRoll( float dr) + { + distOrRoll = dr; + } + +/** + * 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. + */ + public void setVelocity( float x, float y, float z ) + { + this.velocity.x = x; + this.velocity.y = y; + this.velocity.z = z; + } + +/** + * Returns the source's distance from the listener. + * @return How far away the source is. + */ + public float getDistanceFromListener() + { + return distanceFromListener; + } + +/** + * Manually sets the specified source's pitch. + * @param value A float value ( 0.5f - 2.0f ). + */ + public void setPitch( float value ) + { + float newPitch = value; + if( newPitch < 0.5f ) + newPitch = 0.5f; + else if( newPitch > 2.0f ) + newPitch = 2.0f; + pitch = newPitch; + } + +/** + * Returns the pitch of the specified source. + * @return Float value representing the source pitch (0.5f - 2.0f). + */ + public float getPitch() + { + return pitch; + } + +/** + * Indicates whether or not this source's associated library requires some + * codecs to reverse-order the audio data they generate. + * @return True if audio data should be reverse-ordered. + */ + public boolean reverseByteOrder() + { + return SoundSystemConfig.reverseByteOrder( libraryType ); + } + +/** + * Changes the sources peripheral information to match the supplied parameters. + * @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 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( boolean priority, boolean toStream, + boolean toLoop, String sourcename, + FilenameURL filenameURL, SoundBuffer soundBuffer, + float x, float y, float z, int attModel, + float distOrRoll, boolean temporary ) + { + this.priority = priority; + this.toStream = toStream; + this.toLoop = toLoop; + this.sourcename = sourcename; + this.filenameURL = filenameURL; + this.soundBuffer = soundBuffer; + position.x = x; + position.y = y; + position.z = z; + this.attModel = attModel; + this.distOrRoll = distOrRoll; + this.temporary = temporary; + } + +/** + * Feeds raw data to the specified channel. + * @param buffer Byte buffer containing raw audio data to stream. + * @param c Channel to stream on. + * @return Number of prior buffers that have been processed, or -1 if unable to queue the buffer (if the source was culled, for example). + */ + public int feedRawAudioData( Channel c, byte[] buffer ) + { + if( !active( GET, XXX) ) + { + toPlay = true; + return -1; + } + if( channel != c ) + { + channel = c; + channel.close(); + channel.setAudioFormat( rawDataFormat ); + positionChanged(); + } + + // change the state of this source to not stopped and not paused: + stopped( SET, false ); + paused( SET, false ); + + return channel.feedRawAudioData( buffer ); + } + +/** + * Plays the source on the specified channel. + * @param c Channel to play on. + */ + public void play( Channel c ) + { + if( !active( GET, XXX) ) + { + if( toLoop ) + toPlay = true; + return; + } + if( channel != c ) + { + channel = c; + channel.close(); + } + // change the state of this source to not stopped and not paused: + stopped( SET, false ); + paused( SET, false ); + } +/* END Override methods */ + +/** + * Streams the source on its current channel + * @return False when stream has finished playing. + */ + public boolean stream() + { + if( channel == null ) + return false; + + if( preLoad ) + { + if( rawDataStream ) + preLoad = false; + else + return preLoad(); + } + + if( rawDataStream ) + { + if( stopped() || paused() ) + return true; + if( channel.buffersProcessed() > 0 ) + channel.processBuffer(); + return true; + } + else + { + if( codec == null ) + return false; + if( stopped() ) + return false; + if( paused() ) + return true; + + int processed = channel.buffersProcessed(); + + SoundBuffer buffer = null; + for( int i = 0; i < processed; i++ ) + { + buffer = codec.read(); + if( buffer != null ) + { + if( buffer.audioData != null ) + channel.queueBuffer( buffer.audioData ); + buffer.cleanup(); + buffer = null; + return true; + } + else if( codec.endOfStream() ) + { + synchronized( soundSequenceLock ) + { + if( SoundSystemConfig.getStreamQueueFormatsMatch() ) + { + if( soundSequenceQueue != null && + soundSequenceQueue.size() > 0 ) + { + if( codec != null ) + codec.cleanup(); + filenameURL = soundSequenceQueue.remove( 0 ); + codec = SoundSystemConfig.getCodec( + filenameURL.getFilename() ); + codec.initialize( filenameURL.getURL() ); + buffer = codec.read(); + if( buffer != null ) + { + if( buffer.audioData != null ) + channel.queueBuffer( buffer.audioData ); + buffer.cleanup(); + buffer = null; + return true; + } + } + else if( toLoop ) + { + codec.initialize( filenameURL.getURL() ); + buffer = codec.read(); + if( buffer != null ) + { + if( buffer.audioData != null ) + channel.queueBuffer( buffer.audioData ); + buffer.cleanup(); + buffer = null; + return true; + } + } + } + } + } +/* + if( codec.endOfStream() ) + { + synchronized( soundSequenceLock ) + { + if( SoundSystemConfig.getStreamQueueFormatsMatch() ) + { + if( soundSequenceQueue != null && + soundSequenceQueue.size() > 0 ) + { + if( codec != null ) + codec.cleanup(); + filenameURL = soundSequenceQueue.remove( 0 ); + codec = SoundSystemConfig.getCodec( + filenameURL.getFilename() ); + codec.initialize( filenameURL.getURL() ); + return true; + } + else if( toLoop ) + { + codec.initialize( filenameURL.getURL() ); + buffer = codec.read(); + if( buffer != null ) + { + if( buffer.audioData != null ) + channel.queueBuffer( buffer.audioData ); + buffer.cleanup(); + buffer = null; + } + } + } + } + return false; + } +*/ + } + } + return false; + } + +/** + * Queues up the initial stream-buffers for the stream. + * @return False if the end of the stream was reached. + */ + public boolean preLoad() + { + if( channel == null ) + return false; + + if( codec == null ) + return false; + + SoundBuffer buffer = null; + + boolean noNextBuffers = false; + synchronized( soundSequenceLock ) + { + if( nextBuffers == null || nextBuffers.isEmpty() ) + noNextBuffers = true; + } + + if( nextCodec != null && !noNextBuffers ) + { + codec = nextCodec; + nextCodec = null; + synchronized( soundSequenceLock ) + { + while( !nextBuffers.isEmpty() ) + { + buffer = nextBuffers.remove( 0 ); + if( buffer != null ) + { + if( buffer.audioData != null ) + channel.queueBuffer( buffer.audioData ); + buffer.cleanup(); + buffer = null; + } + } + } + } + else + { + nextCodec = null; + URL url = filenameURL.getURL(); + + codec.initialize( url ); + for( int i = 0; i < SoundSystemConfig.getNumberStreamingBuffers(); + i++ ) + { + buffer = codec.read(); + if( buffer != null ) + { + if( buffer.audioData != null ) + channel.queueBuffer( buffer.audioData ); + buffer.cleanup(); + buffer = null; + } + } + } + + return true; + } + +/** + * Pauses the source. + */ + public void pause() + { + toPlay = false; + paused( SET, true ); + if( channel != null ) + channel.pause(); + else + errorMessage( "Channel null in method 'pause'" ); + } + +/** + * Stops the source. + */ + public void stop() + { + toPlay = false; + stopped( SET, true ); + paused( SET, false ); + if( channel != null ) + channel.stop(); + else + errorMessage( "Channel null in method 'stop'" ); + } + +/** + * Rewinds the source. If the source was paused, then it is stopped. + */ + public void rewind() + { + if( paused( GET, XXX ) ) + { + stop(); + } + if( channel != null ) + { + boolean rePlay = playing(); + channel.rewind(); + if( toStream && rePlay ) + { + stop(); + play( channel ); + } + } + else + errorMessage( "Channel null in method 'rewind'" ); + } + +/** + * Dequeues any previously queued data. + */ + public void flush() + { + if( channel != null ) + channel.flush(); + else + errorMessage( "Channel null in method 'flush'" ); + } + +/** + * Stops and flushes the source, and prevents it from being played again until + * the activate() is called. + */ + public void cull() + { + if( !active( GET, XXX ) ) + return; + if( playing() && toLoop ) + toPlay = true; + if( rawDataStream ) + toPlay = true; + active( SET, false ); + if( channel != null ) + channel.close(); + channel = null; + } + +/** + * Allows a previously culled source to be played again. + */ + public void activate() + { + active( SET, true ); + } + +/** + * Returns false if the source has been culled. + * @return True or False + */ + public boolean active() + { + return active( GET, XXX ); + } + +/** + * Returns true if the source is playing. + * @return True or False + */ + public boolean playing() + { + if( channel == null || channel.attachedSource != this ) + return false; + else if( paused() || stopped() ) + return false; + else + return channel.playing(); + } + +/** + * Returns true if the source has been stopped. + * @return True or False + */ + public boolean stopped() + { + return stopped( GET, XXX ); + } + +/** + * Returns true if the source has been paused. + * @return True or False + */ + public boolean paused() + { + return paused( GET, XXX ); + } + +/** + * Returns the number of miliseconds since the source began playing. + * @return miliseconds, or -1 if not playing or unable to calculate + */ + public float millisecondsPlayed() + { + if( channel == null ) + return( -1 ); + else + return channel.millisecondsPlayed(); + } + +/** + * Sets or returns whether or not the source has been culled. + * @return True or False + */ + private synchronized boolean active( boolean action, boolean value ) + { + if( action == SET ) + active = value; + return active; + } + +/** + * Sets or returns whether or not the source has been stopped. + * @return True or False + */ + private synchronized boolean stopped( boolean action, boolean value ) + { + if( action == SET ) + stopped = value; + return stopped; + } + +/** + * Sets or returns whether or not the source has been paused. + * @return True or False + */ + private synchronized boolean paused( boolean action, boolean value ) + { + if( action == SET ) + paused = value; + return paused; + } + +/** + * Returns the name of the class. + * @return SoundLibraryXXXX. + */ + public String getClassName() + { + String libTitle = SoundSystemConfig.getLibraryTitle( libraryType ); + + if( libTitle.equals( "No Sound" ) ) + return "Source"; + else + return "Source" + libTitle; + } +/** + * Prints a message. + * @param message Message to print. + */ + protected void message( String message ) + { + logger.message( message, 0 ); + } + +/** + * Prints an important message. + * @param message Message to print. + */ + protected void importantMessage( String message ) + { + logger.importantMessage( message, 0 ); + } + +/** + * Prints the specified message if error is true. + * @param error True or False. + * @param message Message to print if error is true. + * @return True if error is true. + */ + protected boolean errorCheck( boolean error, String message ) + { + return logger.errorCheck( error, getClassName(), message, 0 ); + } + +/** + * Prints an error message. + * @param message Message to print. + */ + protected void errorMessage( String message ) + { + logger.errorMessage( getClassName(), message, 0 ); + } + +/** + * Prints an exception's error message followed by the stack trace. + * @param e Exception containing the information to print. + */ + protected void printStackTrace( Exception e ) + { + logger.printStackTrace( e, 1 ); + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/StreamThread.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/StreamThread.java new file mode 100644 index 0000000..77b411a --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/StreamThread.java @@ -0,0 +1,306 @@ +/** + * 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; + +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +/** + * The StreamThread class is used to process all streaming sources. This + * thread starts out asleep, and it sleeps when all streaming sources are + * finished playing, so it is necessary to call interrupt() after adding new + * streaming sources to the list. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 StreamThread extends SimpleThread +{ +/** + * Processes status messages, warnings, and error messages. + */ + private SoundSystemLogger logger; + +/** + * List of sources that are currently streaming. + */ + private List<Source> streamingSources; + +/** + * Used to synchronize access to the streaming sources list. + */ + private final Object listLock = new Object(); + +/** + * Constructor: Grabs a handle to the message logger and instantiates the + * streaming sources list. + */ + public StreamThread() + { + // grab a handle to the message logger: + logger = SoundSystemConfig.getLogger(); + + streamingSources = new LinkedList<Source>(); + } + +/** + * Removes all references to instantiated objects, and changes the thread's + * state to "not alive". Method alive() returns false when the cleanup() + * method has completed. + */ + @Override + protected void cleanup() + { + kill(); + super.cleanup(); // Important!! + } + +/** + * The main loop for processing commands. The thread sleeps when it finishes + * processing commands, and it must be interrupted to process more. + */ + @Override + public void run() + { + ListIterator<Source> iter; + Source src; + + // Start out asleep: + snooze( 3600000 ); + + while( !dying() ) + { + while( !dying() && !streamingSources.isEmpty() ) + { + // Make sure noone else is accessing the list of sources: + synchronized( listLock ) + { + iter = streamingSources.listIterator(); + while( !dying() && iter.hasNext() ) + { + src = iter.next(); + if( src == null ) + { + iter.remove(); + } + else if( src.stopped() ) + { + if( !src.rawDataStream ) + iter.remove(); + } + else if( !src.active() ) + { + if( src.toLoop || src.rawDataStream ) + src.toPlay = true; + iter.remove(); + } + else if( !src.paused() ) + { + src.checkFadeOut(); + if( (!src.stream()) && (!src.rawDataStream) ) + { + if( src.channel == null + || !src.channel.processBuffer() ) + { + if( src.nextCodec == null ) + { + src.readBuffersFromNextSoundInSequence(); + } +/* + if( src.getSoundSequenceQueueSize() > 0 ) + { + src.incrementSoundSequence(); + } + + // check if this is a looping source + else*/ if( src.toLoop ) + { + // wait for stream to finish playing + if( !src.playing() ) + { + // Generate an EOS event: + SoundSystemConfig.notifyEOS( + src.sourcename, + src.getSoundSequenceQueueSize() + ); + // Check if the source is currently + // in the process of fading out. + if( src.checkFadeOut() ) + { + // Source is fading out. + // Keep looping until it + // finishes. + src.preLoad = true; + } + else + { + // Source is not fading out. + // If there is another sound in + // the sequence, switch to it + // before replaying. + src.incrementSoundSequence(); + src.preLoad = true; // replay + } + } + } + else + { + // wait for stream to finish playing + if( !src.playing() ) + { + // Generate an EOS event: + SoundSystemConfig.notifyEOS( + src.sourcename, + src.getSoundSequenceQueueSize() + ); + // Check if the source is currently + // in the process of fading out + if( !src.checkFadeOut() ) + { + // Source is not fading out. + // Play anything else that is + // in the sound sequence queue. + if( + src.incrementSoundSequence() ) + src.preLoad = true; + else + iter.remove(); // finished + } + } + } + } + } + } + } + } + if( !dying() && !streamingSources.isEmpty() ) + snooze( 20 ); // sleep a bit so we don't peg the cpu + } + if( !dying() && streamingSources.isEmpty() ) + snooze( 3600000 ); // sleep until there is more to do. + } + + cleanup(); // Important!! + } + +/** + * Adds a new streaming source to the list. If another source in the list is + * already playing on the same channel, it is stopped and removed from the + * list. + * @param source New source to stream. + */ + public void watch( Source source ) + { + // make sure the source exists: + if( source == null ) + return; + + // make sure we aren't already watching this source: + if( streamingSources.contains( source ) ) + return; + + ListIterator<Source> iter; + Source src; + + // Make sure noone else is accessing the list of sources: + synchronized( listLock ) + { + // Any currently watched source which is null or playing on the + // same channel as the new source should be stopped and removed + // from the list. + iter = streamingSources.listIterator(); + while( iter.hasNext() ) + { + src = iter.next(); + if( src == null ) + { + iter.remove(); + } + else if( source.channel == src.channel ) + { + src.stop(); + iter.remove(); + } + } + + // Add the new source to the list: + streamingSources.add( source ); + } + } + +/** + * Prints a message. + * @param message Message to print. + */ + private void message( String message ) + { + logger.message( message, 0 ); + } + +/** + * Prints an important message. + * @param message Message to print. + */ + private void importantMessage( String message ) + { + logger.importantMessage( message, 0 ); + } + +/** + * Prints the specified message if error is true. + * @param error True or False. + * @param message Message to print if error is true. + * @return True if error is true. + */ + private boolean errorCheck( boolean error, String message ) + { + return logger.errorCheck( error, "StreamThread", message, 0 ); + } + +/** + * Prints an error message. + * @param message Message to print. + */ + private void errorMessage( String message ) + { + logger.errorMessage( "StreamThread", message, 0 ); + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/Vector3D.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/Vector3D.java new file mode 100644 index 0000000..de1e8c0 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/Vector3D.java @@ -0,0 +1,219 @@ +/** + * 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; + +/** + * The Vector3D class contains methods to simplify common 3D vector functions, + * such as cross and dot product, normalize, etc. + *<br><br> + *<b><i> SoundSystem License:</b></i><br><b><br> + * 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 may not falsely claim to be the author of this library or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this library or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) 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> + * 5) 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> + * 6) 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 Vector3D +{ + +/** + * The vector's X coordinate. + */ + public float x; + +/** + * The vector's Y coordinate. + */ + public float y; + +/** + * The vector's Z coordinate. + */ + public float z; + +/** + * Constructor: Places the vector at the origin. + */ + public Vector3D() + { + x = 0.0f; + y = 0.0f; + z = 0.0f; + } + +/** + * Constructor: Places the vector at the specified 3D coordinates. + * @param nx X coordinate for the new vector. + * @param ny Y coordinate for the new vector. + * @param nz Z coordinate for the new vector. + */ + public Vector3D( float nx, float ny, float nz ) + { + x = nx; + y = ny; + z = nz; + } + +/** + * Returns a new instance containing the same information as this one. + * @return A new Vector3D. + */ + @Override + public Vector3D clone() + { + return new Vector3D( x, y, z ); + } + +/** + * Returns a vector containing the cross-product: A cross B. + * @param A First vector in the cross product. + * @param B Second vector in the cross product. + * @return A new Vector3D. + */ + public Vector3D cross( Vector3D A, Vector3D B ) + { + return new Vector3D( + A.y * B.z - B.y * A.z, + A.z * B.x - B.z * A.x, + A.x * B.y - B.x * A.y ); + } + +/** + * Returns a vector containing the cross-product: (this) cross B. + * @param B Second vector in the cross product. + * @return A new Vector3D. + */ + public Vector3D cross( Vector3D B ) + { + return new Vector3D( + y * B.z - B.y * z, + z * B.x - B.z * x, + x * B.y - B.x * y ); + + } + +/** + * Returns the dot-product result of: A dot B. + * @param A First vector in the dot product. + * @param B Second vector in the dot product. + * @return Dot product. + */ + public float dot( Vector3D A, Vector3D B ) + { + return( (A.x * B.x) + (A.y * B.y) + (A.z * B.z) ); + } + +/** + * Returns the dot-product result of: (this) dot B. + * @param B Second vector in the dot product. + * @return Dot product. + */ + public float dot( Vector3D B ) + { + return( (x * B.x) + (y * B.y) + (z * B.z) ); + } + +/** + * Returns the vector represented by: A + B. + * @param A First vector. + * @param B Vector to add to A. + * @return A new Vector3D. + */ + public Vector3D add( Vector3D A, Vector3D B ) + { + return new Vector3D( A.x + B.x, A.y + B.y, A.z + B.z ); + } + +/** + * Returns the vector represented by: (this) + B. + * @param B Vector to add to this one. + * @return A new Vector3D. + */ + public Vector3D add( Vector3D B ) + { + return new Vector3D( x + B.x, y + B.y, z + B.z ); + } + +/** + * Returns the vector represented by: A - B. + * @param A First vector. + * @param B Vector to subtract from A. + * @return A new Vector3D. + */ + public Vector3D subtract( Vector3D A, Vector3D B ) + { + return new Vector3D( A.x - B.x, A.y - B.y, A.z - B.z ); + } + +/** + * Returns the vector represented by: (this) - B. + * @param B Vector to subtract from this one. + * @return A new Vector3D. + */ + public Vector3D subtract( Vector3D B ) + { + return new Vector3D( x - B.x, y - B.y, z - B.z ); + } + +/** + * Returns the length of this vector. + * @return Length. + */ + public float length() + { + return (float) Math.sqrt( x * x + y * y + z * z ); + } + +/** + * Changes the length of this vector to 1.0. + */ + public void normalize() + { + double t = Math.sqrt( x*x + y*y + z*z ); + x = (float) (x / t); + y = (float) (y / t); + z = (float) (z / t); + } + +/** + * Returns a string depicting this vector. + * @return "Vector3D (x, y, z)". + */ + @Override + public String toString() + { + return "Vector3D (" + x + ", " + y + ", " + z + ")"; + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/codecs/CodecJOrbis.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/codecs/CodecJOrbis.java new file mode 100644 index 0000000..5bfde5d --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/codecs/CodecJOrbis.java @@ -0,0 +1,823 @@ +/** + * 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.codecs; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.net.UnknownServiceException; +import com.ardor3d.audio.sampled.AudioFormat; + +// From the JOrbis library, http://www.jcraft.com/jorbis/ +import com.jcraft.jogg.Packet; +import com.jcraft.jogg.Page; +import com.jcraft.jogg.StreamState; +import com.jcraft.jogg.SyncState; +import com.jcraft.jorbis.DspState; +import com.jcraft.jorbis.Block; +import com.jcraft.jorbis.Comment; +import com.jcraft.jorbis.Info; + +import com.ardor3d.audio.ICodec; +import com.ardor3d.audio.SoundBuffer; +import com.ardor3d.audio.SoundSystemConfig; +import com.ardor3d.audio.SoundSystemLogger; + +/** + * The CodecJOrbis class provides an ICodec interface to the external JOrbis + * library. + *<b><i> SoundSystem CodecJOrbis Class License:</b></i><br><b><br> + * You are free to use this class for any purpose, commercial or otherwise. + * You may modify this class or source code, and distribute it any way you + * like, provided the following conditions are met: + *<br> + * 1) You may not falsely claim to be the author of this class or any + * unmodified portion of it. + *<br> + * 2) You may not copyright this class or a modified version of it and then + * sue me for copyright infringement. + *<br> + * 3) 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> + * 4) You are not required to give me credit for this class in any derived + * work, but if you do, you must also mention my website: + * http://www.paulscode.com + *<br> + * 5) I the author will not be responsible for any damages (physical, + * financial, or otherwise) caused by the use if this class or any portion + * of it. + *<br> + * 6) I the author do not guarantee, warrant, or make any representations, + * either expressed or implied, regarding the use of this class or any + * portion of it. + * <br><br> + * Author: Paul Lamb + * <br> + * http://www.paulscode.com + *</b><br><br> + *<b> + * This software is based on or using the JOrbis library available from + * http://www.jcraft.com/jorbis/ + *</b><br><br> + *<br><b> + * <br><br> + * Author: Paul Lamb + * <br> + * http://www.paulscode.com + * </b><br><br> + */ +public class CodecJOrbis implements ICodec +{ +/** + * 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; + +/** + * URL to the audio file to stream from. + */ + private URL url; + +/** + * Used for connecting to the URL. + */ + private URLConnection urlConnection = null; + +/** + * InputStream context for reading data from the file. + */ + private InputStream inputStream; + +/** + * Format in which the converted audio data is stored. + */ + private AudioFormat audioFormat; + +/** + * True if there is no more data to read in. + */ + private boolean endOfStream = false; + +/** + * True if the stream has finished initializing. + */ + private boolean initialized = false; + +/** + * Used to hold the data as it is read in. + */ + private byte[] buffer = null; + +/** + * Amount of data to read in at one time. + */ + private int bufferSize; + +/** + * Number of bytes read. + */ + private int count = 0; + +/** + * Location within the data. + */ + private int index = 0; + +/** + * Size of the data after it has been converted into pcm. + */ + private int convertedBufferSize; + +/** + * Linear buffer to hold the data after it has been converted into pcm. + */ + private byte[] convertedBuffer = null; + +/** + * Nonlinear pcm data. + */ + private float[][][] pcmInfo; + +/** + * Location within the data. + */ + private int[] pcmIndex; + +/** + * Data packet. + */ + private Packet joggPacket = new Packet(); +/** + * Data Page. + */ + private Page joggPage = new Page(); +/** + * Stream state. + */ + private StreamState joggStreamState = new StreamState(); +/** + * Packet streaming layer. + */ + private SyncState joggSyncState = new SyncState(); +/** + * Internal data storage. + */ + private DspState jorbisDspState = new DspState(); +/** + * Block of stored JOrbis data. + */ + private Block jorbisBlock = new Block(jorbisDspState); +/** + * Comment fields. + */ + private Comment jorbisComment = new Comment(); +/** + * Info about the data. + */ + private Info jorbisInfo = new Info(); + +/** + * Processes status messages, warnings, and error messages. + */ + private SoundSystemLogger logger; + +/** + * Constructor: Grabs a handle to the logger. + */ + public CodecJOrbis() + { + logger = SoundSystemConfig.getLogger(); + } + +/** + * This method is ignored by CodecJOrbis, because it produces "nice" data. + * @param b True if the calling audio library requires byte-reversal from certain codecs + */ + public void reverseByteOrder( boolean b ) + {} + +/** + * Prepares an input stream to read from. If another stream is already opened, + * it will be closed and a new input stream opened in its place. + * @param url URL to an ogg file to stream from. + * @return False if an error occurred or if end of stream was reached. + */ + public boolean initialize( URL url ) + { + initialized( SET, false ); + + if( joggStreamState != null ) + joggStreamState.clear(); + if( jorbisBlock != null ) + jorbisBlock.clear(); + if( jorbisDspState != null ) + jorbisDspState.clear(); + if( jorbisInfo != null ) + jorbisInfo.clear(); + if( joggSyncState != null ) + joggSyncState.clear(); + + if( inputStream != null ) + { + try + { + inputStream.close(); + } + catch( IOException ioe ) + {} + } + + this.url = url; + //this.bufferSize = SoundSystemConfig.getStreamingBufferSize() / 2; + this.bufferSize = 4096*2; + + buffer = null; + count = 0; + index = 0; + + joggStreamState = new StreamState(); + jorbisBlock = new Block(jorbisDspState); + jorbisDspState = new DspState(); + jorbisInfo = new Info(); + joggSyncState = new SyncState(); + + try + { + urlConnection = url.openConnection(); + } + catch( UnknownServiceException use ) + { + errorMessage( "Unable to create a UrlConnection in method " + + "'initialize'." ); + printStackTrace( use ); + cleanup(); + return false; + } + catch( IOException ioe ) + { + errorMessage( "Unable to create a UrlConnection in method " + + "'initialize'." ); + printStackTrace( ioe ); + cleanup(); + return false; + } + if( urlConnection != null ) + { + try + { + inputStream = urlConnection.getInputStream(); + } + catch( IOException ioe ) + { + errorMessage( "Unable to acquire inputstream in method " + + "'initialize'." ); + printStackTrace( ioe ); + cleanup(); + return false; + } + } + + endOfStream( SET, false ); + + joggSyncState.init(); + joggSyncState.buffer( bufferSize ); + buffer = joggSyncState.data; + + try + { + if( !readHeader() ) + { + errorMessage( "Error reading the header" ); + return false; + } + } + catch( IOException ioe ) + { + errorMessage( "Error reading the header" ); + return false; + } + + convertedBufferSize = bufferSize * 2; + + jorbisDspState.synthesis_init( jorbisInfo ); + jorbisBlock.init( jorbisDspState ); + + int channels = jorbisInfo.channels; + int rate = jorbisInfo.rate; + + audioFormat = new AudioFormat( (float) rate, 16, channels, true, + false ); + pcmInfo = new float[1][][]; + pcmIndex = new int[ jorbisInfo.channels ]; + + initialized( SET, true ); + + return true; + } + +/** + * Returns false if the stream is busy initializing. + * @return True if steam is initialized. + */ + public boolean initialized() + { + return initialized( GET, XXX ); + } + +/** + * Reads in one stream buffer worth of audio data. See + * {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about accessing and changing default settings. + * @return The audio data wrapped into a SoundBuffer context. + */ + public SoundBuffer read() + { + byte[] returnBuffer = null; + + while( !endOfStream( GET, XXX ) && ( returnBuffer == null || + returnBuffer.length < SoundSystemConfig.getStreamingBufferSize() ) ) + { + if( returnBuffer == null ) + returnBuffer = readBytes(); + else + returnBuffer = appendByteArrays( returnBuffer, readBytes() ); + } + + if( returnBuffer == null ) + return null; + + return new SoundBuffer( returnBuffer, audioFormat ); + } + +/** + * Reads in all the audio data from the stream (up to the default + * "maximum file size". See + * {@link com.ardor3d.audio.SoundSystemConfig SoundSystemConfig} for more + * information about accessing and changing default settings. + * @return the audio data wrapped into a SoundBuffer context. + */ + public SoundBuffer readAll() + { + byte[] returnBuffer = null; + + while( !endOfStream( GET, XXX ) ) + { + if( returnBuffer == null ) + returnBuffer = readBytes(); + else + returnBuffer = appendByteArrays( returnBuffer, readBytes() ); + } + + if( returnBuffer == null ) + return null; + + return new SoundBuffer( returnBuffer, audioFormat ); + } + +/** + * Returns false if there is still more data available to be read in. + * @return True if end of stream was reached. + */ + public boolean endOfStream() + { + return endOfStream( GET, XXX ); + } + +/** + * Closes the input stream and remove references to all instantiated objects. + */ + public void cleanup() + { + joggStreamState.clear(); + jorbisBlock.clear(); + jorbisDspState.clear(); + jorbisInfo.clear(); + joggSyncState.clear(); + + if( inputStream != null ) + { + try + { + inputStream.close(); + } + catch( IOException ioe ) + {} + } + + joggStreamState = null; + jorbisBlock = null; + jorbisDspState = null; + jorbisInfo = null; + joggSyncState = null; + inputStream = null; + } + +/** + * Returns the audio format of the data being returned by the read() and + * readAll() methods. + * @return Information wrapped into an AudioFormat context. + */ + public AudioFormat getAudioFormat() + { + return audioFormat; + } + +/** + * Reads in the header information for the ogg file, which is contained in the + * first three packets of data. + * @return True if successful. + */ + private boolean readHeader() throws IOException + { + // Update up JOrbis internal buffer: + index = joggSyncState.buffer( bufferSize ); + // Read in a buffer of data: + int bytes = inputStream.read( joggSyncState.data, index, bufferSize ); + if( bytes < 0 ) + bytes = 0; + // Let JOrbis know how many bytes we got: + joggSyncState.wrote( bytes ); + + if( joggSyncState.pageout( joggPage ) != 1 ) + { + // Finished reading the entire file: + if( bytes < bufferSize ) + return true; + + errorMessage( "Ogg header not recognized in method 'readHeader'." ); + return false; + } + + // Initialize JOrbis: + joggStreamState.init( joggPage.serialno() ); + + jorbisInfo.init(); + jorbisComment.init(); + if( joggStreamState.pagein( joggPage ) < 0 ) + { + errorMessage( "Problem with first Ogg header page in method " + + "'readHeader'." ); + return false; + } + + if( joggStreamState.packetout( joggPacket ) != 1 ) + { + errorMessage( "Problem with first Ogg header packet in method " + + "'readHeader'." ); + return false; + } + + if( jorbisInfo.synthesis_headerin( jorbisComment, joggPacket ) < 0 ) + { + errorMessage( "File does not contain Vorbis header in method " + + "'readHeader'." ); + return false; + } + + int i = 0; + while( i < 2 ) + { + while( i < 2 ) + { + int result = joggSyncState.pageout( joggPage ); + if( result == 0 ) + break; + if( result == 1 ) + { + joggStreamState.pagein( joggPage ); + while( i < 2 ) + { + result = joggStreamState.packetout( joggPacket ); + if( result == 0 ) + break; + + if( result == -1 ) + { + errorMessage( "Secondary Ogg header corrupt in " + + "method 'readHeader'." ); + return false; + } + + jorbisInfo.synthesis_headerin( jorbisComment, + joggPacket ); + i++; + } + } + } + index = joggSyncState.buffer( bufferSize ); + bytes = inputStream.read( joggSyncState.data, index, bufferSize ); + if( bytes < 0 ) + bytes = 0; + if( bytes == 0 && i < 2 ) + { + errorMessage( "End of file reached before finished reading" + + "Ogg header in method 'readHeader'" ); + return false; + } + + joggSyncState.wrote( bytes ); + } + + index = joggSyncState.buffer( bufferSize ); + buffer = joggSyncState.data; + + return true; + } + +/** + * Reads and decodes a chunk of data of length "convertedBufferSize". + * @return Array containing the converted audio data. + */ + private byte[] readBytes() + { + if( !initialized( GET, XXX ) ) + return null; + + if( endOfStream( GET, XXX ) ) + return null; + + if( convertedBuffer == null ) + convertedBuffer = new byte[ convertedBufferSize ]; + byte[] returnBuffer = null; + + float[][] pcmf; + int samples, bout, ptr, mono, val, i, j; + + switch( joggSyncState.pageout( joggPage ) ) + { + case( 0 ): + case( -1 ): + break; + default: + { + joggStreamState.pagein( joggPage ); + if( joggPage.granulepos() == 0 ) + { + endOfStream( SET, true ); + return null; + } + + processPackets: while( true ) + { + switch( joggStreamState.packetout( joggPacket ) ) + { + case( 0 ): + break processPackets; + case( -1 ): + break; + default: + { + if( jorbisBlock.synthesis( joggPacket ) == 0 ) + jorbisDspState.synthesis_blockin( jorbisBlock ); + + while( ( samples=jorbisDspState.synthesis_pcmout( + pcmInfo, pcmIndex ) ) > 0 ) + { + pcmf = pcmInfo[0]; + bout = ( samples < convertedBufferSize ? + samples : convertedBufferSize ); + for( i = 0; i < jorbisInfo.channels; i++ ) + { + ptr = i * 2; + mono = pcmIndex[i]; + for( j = 0; j < bout; j++ ) + { + val = (int) ( pcmf[i][mono + j] * + 32767. ); + if( val > 32767 ) + val = 32767; + if( val < -32768 ) + val = -32768; + if( val < 0 ) + val = val | 0x8000; + convertedBuffer[ptr] = (byte) (val); + convertedBuffer[ptr+1] = + (byte) (val>>>8); + ptr += 2 * (jorbisInfo.channels); + } + } + jorbisDspState.synthesis_read( bout ); + + returnBuffer = appendByteArrays( returnBuffer, + convertedBuffer, + 2 * jorbisInfo.channels * bout ); + } + } + } + } + + if( joggPage.eos() != 0 ) + endOfStream( SET, true ); + } + } + + if( !endOfStream( GET, XXX ) ) + { + index = joggSyncState.buffer( bufferSize ); + buffer = joggSyncState.data; + try + { + count = inputStream.read( buffer, index, bufferSize ); + } + catch( Exception e ) + { + printStackTrace( e ); + return null; + } + if( count == -1 ) + return returnBuffer; + + joggSyncState.wrote( count ); + if( count==0 ) + endOfStream( SET, true ); + } + + return returnBuffer; + } + +/** + * Internal method for synchronizing access to the boolean 'initialized'. + * @param action GET or SET. + * @param value New value if action == SET, or XXX if action == GET. + * @return True if steam is initialized. + */ + private synchronized boolean initialized( boolean action, boolean value ) + { + if( action == SET ) + initialized = value; + return initialized; + } + +/** + * Internal method for synchronizing access to the boolean 'endOfStream'. + * @param action GET or SET. + * @param value New value if action == SET, or XXX if action == GET. + * @return True if end of stream was reached. + */ + private synchronized boolean endOfStream( boolean action, boolean value ) + { + if( action == SET ) + endOfStream = value; + return endOfStream; + } + +/** + * Trims down the size of the array if it is larger than the specified + * maximum length. + * @param array Array containing audio data. + * @param maxLength Maximum size this array may be. + * @return New array. + */ + private static byte[] trimArray( byte[] array, int maxLength ) + { + byte[] trimmedArray = null; + if( array != null && array.length > maxLength ) + { + trimmedArray = new byte[maxLength]; + System.arraycopy( array, 0, trimmedArray, 0, maxLength ); + } + return trimmedArray; + } + +/** + * Creates a new array with the second array appended to the end of the first + * array. + * @param arrayOne The first array. + * @param arrayTwo The second array. + * @param arrayTwoBytes The number of bytes to append from the second array. + * @return Byte array containing information from both arrays. + */ + private static byte[] appendByteArrays( byte[] arrayOne, byte[] arrayTwo, + int arrayTwoBytes ) + { + byte[] newArray; + int bytes = arrayTwoBytes; + + // Make sure we aren't trying to append more than is there: + if( arrayTwo == null || arrayTwo.length == 0 ) + bytes = 0; + else if( arrayTwo.length < arrayTwoBytes ) + bytes = arrayTwo.length; + + if( arrayOne == null && (arrayTwo == null || bytes <= 0) ) + { + // no data, just return + return null; + } + else if( arrayOne == null ) + { + // create the new array, same length as arrayTwo: + newArray = new byte[ bytes ]; + // fill the new array with the contents of arrayTwo: + System.arraycopy( arrayTwo, 0, newArray, 0, bytes ); + arrayTwo = null; + } + else if( arrayTwo == null || bytes <= 0 ) + { + // create the new array, same length as arrayOne: + newArray = new byte[ arrayOne.length ]; + // fill the new array with the contents of arrayOne: + System.arraycopy( arrayOne, 0, newArray, 0, arrayOne.length ); + arrayOne = null; + } + else + { + // create the new array large enough to hold both arrays: + newArray = new byte[ arrayOne.length + bytes ]; + System.arraycopy( arrayOne, 0, newArray, 0, arrayOne.length ); + // fill the new array with the contents of both arrays: + System.arraycopy( arrayTwo, 0, newArray, arrayOne.length, + bytes ); + arrayOne = null; + arrayTwo = null; + } + + return newArray; + } + +/** + * Creates a new array with the second array appended to the end of the first + * array. + * @param arrayOne The first array. + * @param arrayTwo The second array. + * @return Byte array containing information from both arrays. + */ + private static byte[] appendByteArrays( byte[] arrayOne, byte[] arrayTwo ) + { + byte[] newArray; + if( arrayOne == null && arrayTwo == null ) + { + // no data, just return + return null; + } + else if( arrayOne == null ) + { + // create the new array, same length as arrayTwo: + newArray = new byte[ arrayTwo.length ]; + // fill the new array with the contents of arrayTwo: + System.arraycopy( arrayTwo, 0, newArray, 0, arrayTwo.length ); + arrayTwo = null; + } + else if( arrayTwo == null ) + { + // create the new array, same length as arrayOne: + newArray = new byte[ arrayOne.length ]; + // fill the new array with the contents of arrayOne: + System.arraycopy( arrayOne, 0, newArray, 0, arrayOne.length ); + arrayOne = null; + } + else + { + // create the new array large enough to hold both arrays: + newArray = new byte[ arrayOne.length + arrayTwo.length ]; + System.arraycopy( arrayOne, 0, newArray, 0, arrayOne.length ); + // fill the new array with the contents of both arrays: + System.arraycopy( arrayTwo, 0, newArray, arrayOne.length, + arrayTwo.length ); + arrayOne = null; + arrayTwo = null; + } + + return newArray; + } + +/** + * Prints an error message. + * @param message Message to print. + */ + private void errorMessage( String message ) + { + logger.errorMessage( "CodecJOrbis", message, 0 ); + } + +/** + * Prints an exception's error message followed by the stack trace. + * @param e Exception containing the information to print. + */ + private void printStackTrace( Exception e ) + { + logger.printStackTrace( e, 1 ); + } +} diff --git a/ardor3d-audio/src/main/java/com/ardor3d/audio/sampled/AudioFormat.java b/ardor3d-audio/src/main/java/com/ardor3d/audio/sampled/AudioFormat.java new file mode 100644 index 0000000..a2959ea --- /dev/null +++ b/ardor3d-audio/src/main/java/com/ardor3d/audio/sampled/AudioFormat.java @@ -0,0 +1,623 @@ +/** + * 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>. + */ +/* + * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.ardor3d.audio.sampled; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * {@code AudioFormat} is the class that specifies a particular arrangement of + * data in a sound stream. By examining the information stored in the audio + * format, you can discover how to interpret the bits in the binary sound data. + * <p> + * Every data line has an audio format associated with its data stream. The + * audio format of a source (playback) data line indicates what kind of data the + * data line expects to receive for output. For a target (capture) data line, + * the audio format specifies the kind of the data that can be read from the + * line. + * <p> + * Sound files also have audio formats, of course. The {@link AudioFileFormat} + * class encapsulates an {@code AudioFormat} in addition to other, file-specific + * information. Similarly, an {@link AudioInputStream} has an + * {@code AudioFormat}. + * <p> + * The {@code AudioFormat} class accommodates a number of common sound-file + * encoding techniques, including pulse-code modulation (PCM), mu-law encoding, + * and a-law encoding. These encoding techniques are predefined, but service + * providers can create new encoding types. The encoding that a specific format + * uses is named by its {@code encoding} field. + * <p> + * In addition to the encoding, the audio format includes other properties that + * further specify the exact arrangement of the data. These include the number + * of channels, sample rate, sample size, byte order, frame rate, and frame + * size. Sounds may have different numbers of audio channels: one for mono, two + * for stereo. The sample rate measures how many "snapshots" (samples) of the + * sound pressure are taken per second, per channel. (If the sound is stereo + * rather than mono, two samples are actually measured at each instant of time: + * one for the left channel, and another for the right channel; however, the + * sample rate still measures the number per channel, so the rate is the same + * regardless of the number of channels. This is the standard use of the term.) + * The sample size indicates how many bits are used to store each snapshot; 8 + * and 16 are typical values. For 16-bit samples (or any other sample size + * larger than a byte), byte order is important; the bytes in each sample are + * arranged in either the "little-endian" or "big-endian" style. For encodings + * like PCM, a frame consists of the set of samples for all channels at a given + * point in time, and so the size of a frame (in bytes) is always equal to the + * size of a sample (in bytes) times the number of channels. However, with some + * other sorts of encodings a frame can contain a bundle of compressed data for + * a whole series of samples, as well as additional, non-sample data. For such + * encodings, the sample rate and sample size refer to the data after it is + * decoded into PCM, and so they are completely different from the frame rate + * and frame size. + * <p> + * An {@code AudioFormat} object can include a set of properties. A property is + * a pair of key and value: the key is of type {@code String}, the associated + * property value is an arbitrary object. Properties specify additional format + * specifications, like the bit rate for compressed formats. Properties are + * mainly used as a means to transport additional information of the audio + * format to and from the service providers. Therefore, properties are ignored + * in the {@link #matches(AudioFormat)} method. However, methods which rely on + * the installed service providers, like + * {@link AudioSystem#isConversionSupported (AudioFormat, AudioFormat) + * isConversionSupported} may consider properties, depending on the respective + * service provider implementation. + * <p> + * The following table lists some common properties which service providers + * should use, if applicable: + * + * <table class="striped"> + * <caption>Audio Format Properties</caption> + * <thead> + * <tr> + * <th scope="col">Property key + * <th scope="col">Value type + * <th scope="col">Description + * </thead> + * <tbody> + * <tr> + * <th scope="row">"bitrate" + * <td>{@link java.lang.Integer Integer} + * <td>average bit rate in bits per second + * <tr> + * <th scope="row">"vbr" + * <td>{@link java.lang.Boolean Boolean} + * <td>{@code true}, if the file is encoded in variable bit rate (VBR) + * <tr> + * <th scope="row">"quality" + * <td>{@link java.lang.Integer Integer} + * <td>encoding/conversion quality, 1..100 + * </tbody> + * </table> + * <p> + * Vendors of service providers (plugins) are encouraged to seek information + * about other already established properties in third party plugins, and follow + * the same conventions. + * + * @author Kara Kytle + * @author Florian Bomers + * @see DataLine#getFormat + * @see AudioInputStream#getFormat + * @see AudioFileFormat + * @see javax.sound.sampled.spi.FormatConversionProvider + * @since 1.3 + */ +public class AudioFormat { + + /** + * Field copied from AudioSystem + */ + public static final int NOT_SPECIFIED = -1; + + /** + * The audio encoding technique used by this format. + */ + protected Encoding encoding; + + /** + * The number of samples played or recorded per second, for sounds that have + * this format. + */ + protected float sampleRate; + + /** + * The number of bits in each sample of a sound that has this format. + */ + protected int sampleSizeInBits; + + /** + * The number of audio channels in this format (1 for mono, 2 for stereo). + */ + protected int channels; + + /** + * The number of bytes in each frame of a sound that has this format. + */ + protected int frameSize; + + /** + * The number of frames played or recorded per second, for sounds that have + * this format. + */ + protected float frameRate; + + /** + * Indicates whether the audio data is stored in big-endian or little-endian + * order. + */ + protected boolean bigEndian; + + /** + * The set of properties. + */ + private HashMap<String, Object> properties; + + /** + * Constructs an {@code AudioFormat} with the given parameters. The encoding + * specifies the convention used to represent the data. The other parameters + * are further explained in the {@link AudioFormat class description}. + * + * @param encoding the audio encoding technique + * @param sampleRate the number of samples per second + * @param sampleSizeInBits the number of bits in each sample + * @param channels the number of channels (1 for mono, 2 for stereo, and so + * on) + * @param frameSize the number of bytes in each frame + * @param frameRate the number of frames per second + * @param bigEndian indicates whether the data for a single sample is + * stored in big-endian byte order ({@code false} means + * little-endian) + */ + public AudioFormat(Encoding encoding, float sampleRate, int sampleSizeInBits, + int channels, int frameSize, float frameRate, boolean bigEndian) { + + this.encoding = encoding; + this.sampleRate = sampleRate; + this.sampleSizeInBits = sampleSizeInBits; + this.channels = channels; + this.frameSize = frameSize; + this.frameRate = frameRate; + this.bigEndian = bigEndian; + this.properties = null; + } + + /** + * Constructs an {@code AudioFormat} with the given parameters. The encoding + * specifies the convention used to represent the data. The other parameters + * are further explained in the {@link AudioFormat class description}. + * + * @param encoding the audio encoding technique + * @param sampleRate the number of samples per second + * @param sampleSizeInBits the number of bits in each sample + * @param channels the number of channels (1 for mono, 2 for stereo, and so + * on) + * @param frameSize the number of bytes in each frame + * @param frameRate the number of frames per second + * @param bigEndian indicates whether the data for a single sample is + * stored in big-endian byte order ({@code false} means + * little-endian) + * @param properties a {@code Map<String, Object>} object containing format + * properties + * @since 1.5 + */ + public AudioFormat(Encoding encoding, float sampleRate, + int sampleSizeInBits, int channels, + int frameSize, float frameRate, + boolean bigEndian, Map<String, Object> properties) { + this(encoding, sampleRate, sampleSizeInBits, channels, + frameSize, frameRate, bigEndian); + this.properties = new HashMap<>(properties); + } + + /** + * Constructs an {@code AudioFormat} with a linear PCM encoding and the + * given parameters. The frame size is set to the number of bytes required + * to contain one sample from each channel, and the frame rate is set to the + * sample rate. + * + * @param sampleRate the number of samples per second + * @param sampleSizeInBits the number of bits in each sample + * @param channels the number of channels (1 for mono, 2 for stereo, and so + * on) + * @param signed indicates whether the data is signed or unsigned + * @param bigEndian indicates whether the data for a single sample is + * stored in big-endian byte order ({@code false} means + * little-endian) + */ + public AudioFormat(float sampleRate, int sampleSizeInBits, + int channels, boolean signed, boolean bigEndian) { + + this((signed == true ? Encoding.PCM_SIGNED : Encoding.PCM_UNSIGNED), + sampleRate, + sampleSizeInBits, + channels, + (channels == NOT_SPECIFIED || sampleSizeInBits == NOT_SPECIFIED)? + NOT_SPECIFIED: + ((sampleSizeInBits + 7) / 8) * channels, + sampleRate, + bigEndian); + } + + /** + * Obtains the type of encoding for sounds in this format. + * + * @return the encoding type + * @see Encoding#PCM_SIGNED + * @see Encoding#PCM_UNSIGNED + * @see Encoding#ULAW + * @see Encoding#ALAW + */ + public Encoding getEncoding() { + return encoding; + } + + /** + * Obtains the sample rate. For compressed formats, the return value is the + * sample rate of the uncompressed audio data. When this {@code AudioFormat} + * is used for queries (e.g. + * {@link AudioSystem#isConversionSupported(AudioFormat, AudioFormat) + * AudioSystem.isConversionSupported}) or capabilities (e.g. + * {@link DataLine.Info#getFormats DataLine.Info.getFormats}), a sample rate + * of {@code AudioSystem.NOT_SPECIFIED} means that any sample rate is + * acceptable. {@code AudioSystem.NOT_SPECIFIED} is also returned when the + * sample rate is not defined for this audio format. + * + * @return the number of samples per second, or + * {@code AudioSystem.NOT_SPECIFIED} + * @see #getFrameRate() + * @see AudioSystem#NOT_SPECIFIED + */ + public float getSampleRate() { + return sampleRate; + } + + /** + * Obtains the size of a sample. For compressed formats, the return value is + * the sample size of the uncompressed audio data. When this + * {@code AudioFormat} is used for queries (e.g. + * {@link AudioSystem#isConversionSupported(AudioFormat,AudioFormat) + * AudioSystem.isConversionSupported}) or capabilities (e.g. + * {@link DataLine.Info#getFormats DataLine.Info.getFormats}), a sample size + * of {@code AudioSystem.NOT_SPECIFIED} means that any sample size is + * acceptable. {@code AudioSystem.NOT_SPECIFIED} is also returned when the + * sample size is not defined for this audio format. + * + * @return the number of bits in each sample, or + * {@code AudioSystem.NOT_SPECIFIED} + * @see #getFrameSize() + * @see AudioSystem#NOT_SPECIFIED + */ + public int getSampleSizeInBits() { + return sampleSizeInBits; + } + + /** + * Obtains the number of channels. When this {@code AudioFormat} is used for + * queries (e.g. {@link AudioSystem#isConversionSupported(AudioFormat, + * AudioFormat) AudioSystem.isConversionSupported}) or capabilities (e.g. + * {@link DataLine.Info#getFormats DataLine.Info.getFormats}), a return + * value of {@code AudioSystem.NOT_SPECIFIED} means that any (positive) + * number of channels is acceptable. + * + * @return The number of channels (1 for mono, 2 for stereo, etc.), or + * {@code AudioSystem.NOT_SPECIFIED} + * @see AudioSystem#NOT_SPECIFIED + */ + public int getChannels() { + return channels; + } + + /** + * Obtains the frame size in bytes. When this {@code AudioFormat} is used + * for queries (e.g. {@link AudioSystem#isConversionSupported(AudioFormat, + * AudioFormat) AudioSystem.isConversionSupported}) or capabilities (e.g. + * {@link DataLine.Info#getFormats DataLine.Info.getFormats}), a frame size + * of {@code AudioSystem.NOT_SPECIFIED} means that any frame size is + * acceptable. {@code AudioSystem.NOT_SPECIFIED} is also returned when the + * frame size is not defined for this audio format. + * + * @return the number of bytes per frame, or + * {@code AudioSystem.NOT_SPECIFIED} + * @see #getSampleSizeInBits() + * @see AudioSystem#NOT_SPECIFIED + */ + public int getFrameSize() { + return frameSize; + } + + /** + * Obtains the frame rate in frames per second. When this + * {@code AudioFormat} is used for queries (e.g. + * {@link AudioSystem#isConversionSupported(AudioFormat,AudioFormat) + * AudioSystem.isConversionSupported}) or capabilities (e.g. + * {@link DataLine.Info#getFormats DataLine.Info.getFormats}), a frame rate + * of {@code AudioSystem.NOT_SPECIFIED} means that any frame rate is + * acceptable. {@code AudioSystem.NOT_SPECIFIED} is also returned when the + * frame rate is not defined for this audio format. + * + * @return the number of frames per second, or + * {@code AudioSystem.NOT_SPECIFIED} + * @see #getSampleRate() + * @see AudioSystem#NOT_SPECIFIED + */ + public float getFrameRate() { + return frameRate; + } + + /** + * Indicates whether the audio data is stored in big-endian or little-endian + * byte order. If the sample size is not more than one byte, the return + * value is irrelevant. + * + * @return {@code true} if the data is stored in big-endian byte order, + * {@code false} if little-endian + */ + public boolean isBigEndian() { + return bigEndian; + } + + /** + * Obtain an unmodifiable map of properties. The concept of properties is + * further explained in the {@link AudioFileFormat class description}. + * + * @return a {@code Map<String, Object>} object containing all properties. + * If no properties are recognized, an empty map is returned. + * @see #getProperty(String) + * @since 1.5 + */ + @SuppressWarnings("unchecked") // Cast of result of clone. + public Map<String,Object> properties() { + Map<String,Object> ret; + if (properties == null) { + ret = new HashMap<>(0); + } else { + ret = (Map<String,Object>) (properties.clone()); + } + return Collections.unmodifiableMap(ret); + } + + /** + * Obtain the property value specified by the key. The concept of properties + * is further explained in the {@link AudioFileFormat class description}. + * <p> + * If the specified property is not defined for a particular file format, + * this method returns {@code null}. + * + * @param key the key of the desired property + * @return the value of the property with the specified key, or {@code null} + * if the property does not exist + * @see #properties() + * @since 1.5 + */ + public Object getProperty(String key) { + if (properties == null) { + return null; + } + return properties.get(key); + } + + /** + * Indicates whether this format matches the one specified. To match, two + * formats must have the same encoding, and consistent values of the number + * of channels, sample rate, sample size, frame rate, and frame size. The + * values of the property are consistent if they are equal or the specified + * format has the property value {@code AudioSystem.NOT_SPECIFIED}. The byte + * order (big-endian or little-endian) must be the same if the sample size + * is greater than one byte. + * + * @param format format to test for match + * @return {@code true} if this format matches the one specified, + * {@code false} otherwise + */ + public boolean matches(AudioFormat format) { + if (format.getEncoding().equals(getEncoding()) + && (format.getChannels() == NOT_SPECIFIED + || format.getChannels() == getChannels()) + && (format.getSampleRate() == (float)NOT_SPECIFIED + || format.getSampleRate() == getSampleRate()) + && (format.getSampleSizeInBits() == NOT_SPECIFIED + || format.getSampleSizeInBits() == getSampleSizeInBits()) + && (format.getFrameRate() == (float)NOT_SPECIFIED + || format.getFrameRate() == getFrameRate()) + && (format.getFrameSize() == NOT_SPECIFIED + || format.getFrameSize() == getFrameSize()) + && (getSampleSizeInBits() <= 8 + || format.isBigEndian() == isBigEndian())) { + return true; + } + return false; + } + + /** + * Returns a string that describes the audio format, such as: "PCM SIGNED + * 22050 Hz 16 bit mono big-endian". The contents of the string may vary + * between implementations of Java Sound. + * + * @return a string representation of the audio format + */ + @Override + public String toString() { + String sampleRate = getSampleRate() == NOT_SPECIFIED ? + "unknown sample rate" : getSampleRate() + " Hz"; + + String sampleSize = getSampleSizeInBits() == NOT_SPECIFIED ? + "unknown bits per sample" : getSampleSizeInBits() + " bit"; + + String channels = switch (getChannels()) { + case 1 -> "mono"; + case 2 -> "stereo"; + case NOT_SPECIFIED -> "unknown number of channels"; + default -> getChannels() + " channels"; + }; + + String frameSize = getFrameSize() == NOT_SPECIFIED ? + "unknown frame size" : getFrameSize() + " bytes/frame"; + + String frameRate = ""; + if (Math.abs(getSampleRate() - getFrameRate()) > 0.00001) { + frameRate = getFrameRate() == NOT_SPECIFIED ? + ", unknown frame rate":", " + getFrameRate() + " frames/second"; + } + + String bigEndian = ""; + if ((getEncoding().equals(Encoding.PCM_SIGNED) + || getEncoding().equals(Encoding.PCM_UNSIGNED)) + && ((getSampleSizeInBits() > 8) + || (getSampleSizeInBits() == NOT_SPECIFIED))) { + bigEndian = isBigEndian() ? ", big-endian" : ", little-endian"; + } + + return String.format("%s %s, %s, %s, %s%s%s", getEncoding(), + sampleRate, sampleSize, channels, frameSize, + frameRate, bigEndian); + } + + /** + * The {@code Encoding} class names the specific type of data representation + * used for an audio stream. The encoding includes aspects of the sound + * format other than the number of channels, sample rate, sample size, frame + * rate, frame size, and byte order. + * <p> + * One ubiquitous type of audio encoding is pulse-code modulation (PCM), + * which is simply a linear (proportional) representation of the sound + * waveform. With PCM, the number stored in each sample is proportional to + * the instantaneous amplitude of the sound pressure at that point in time. + * The numbers may be signed or unsigned integers or floats. Besides PCM, + * other encodings include mu-law and a-law, which are nonlinear mappings of + * the sound amplitude that are often used for recording speech. + * <p> + * You can use a predefined encoding by referring to one of the static + * objects created by this class, such as {@code PCM_SIGNED} or + * {@code PCM_UNSIGNED}. Service providers can create new encodings, such as + * compressed audio formats, and make these available through the + * {@link AudioSystem} class. + * <p> + * The {@code Encoding} class is static, so that all {@code AudioFormat} + * objects that have the same encoding will refer to the same object (rather + * than different instances of the same class). This allows matches to be + * made by checking that two format's encodings are equal. + * + * @author Kara Kytle + * @see AudioFormat + * @see javax.sound.sampled.spi.FormatConversionProvider + * @since 1.3 + */ + public static class Encoding { + + /** + * Specifies signed, linear PCM data. + */ + public static final Encoding PCM_SIGNED = new Encoding("PCM_SIGNED"); + + /** + * Specifies unsigned, linear PCM data. + */ + public static final Encoding PCM_UNSIGNED = new Encoding("PCM_UNSIGNED"); + + /** + * Specifies floating-point PCM data. + * + * @since 1.7 + */ + public static final Encoding PCM_FLOAT = new Encoding("PCM_FLOAT"); + + /** + * Specifies u-law encoded data. + */ + public static final Encoding ULAW = new Encoding("ULAW"); + + /** + * Specifies a-law encoded data. + */ + public static final Encoding ALAW = new Encoding("ALAW"); + + /** + * Encoding name. + */ + private final String name; + + /** + * Constructs a new encoding. + * + * @param name the name of the new type of encoding + */ + public Encoding(final String name) { + this.name = name; + } + + /** + * Indicates whether the specified object is equal to this encoding, + * returning {@code true} if the objects are equal. + * + * @param obj the reference object with which to compare + * @return {@code true} if the specified object is equal to this + * encoding; {@code false} otherwise + */ + @Override + public final boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Encoding)) { + return false; + } + return Objects.equals(name, ((Encoding) obj).name); + } + + /** + * Returns a hash code value for this encoding. + * + * @return a hash code value for this encoding + */ + @Override + public final int hashCode() { + return name != null ? name.hashCode() : 0; + } + + /** + * Returns encoding's name as the string representation of the encoding. + * For the predefined encodings, the name is similar to the encoding's + * variable (field) name. For example, {@code PCM_SIGNED.toString()} + * returns the name "PCM_SIGNED". + * + * @return a string representation of the encoding + */ + @Override + public final String toString() { + return name; + } + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jogg/Buffer.java b/ardor3d-audio/src/main/java/com/jcraft/jogg/Buffer.java new file mode 100644 index 0000000..1e5c92e --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jogg/Buffer.java @@ -0,0 +1,294 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +public class Buffer{ + private static final int BUFFER_INCREMENT=256; + + private static final int[] mask= {0x00000000, 0x00000001, 0x00000003, + 0x00000007, 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, + 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, + 0x00007fff, 0x0000ffff, 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff, + 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff, 0x01ffffff, 0x03ffffff, + 0x07ffffff, 0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff}; + + int ptr=0; + byte[] buffer=null; + int endbit=0; + int endbyte=0; + int storage=0; + + public void writeinit(){ + buffer=new byte[BUFFER_INCREMENT]; + ptr=0; + buffer[0]=(byte)'\0'; + storage=BUFFER_INCREMENT; + } + + public void write(byte[] s){ + for(int i=0; i<s.length; i++){ + if(s[i]==0) + break; + write(s[i], 8); + } + } + + public void read(byte[] s, int bytes){ + int i=0; + while(bytes--!=0){ + s[i++]=(byte)(read(8)); + } + } + + void reset(){ + ptr=0; + buffer[0]=(byte)'\0'; + endbit=endbyte=0; + } + + public void writeclear(){ + buffer=null; + } + + public void readinit(byte[] buf, int bytes){ + readinit(buf, 0, bytes); + } + + public void readinit(byte[] buf, int start, int bytes){ + ptr=start; + buffer=buf; + endbit=endbyte=0; + storage=bytes; + } + + public void write(int value, int bits){ + if(endbyte+4>=storage){ + byte[] foo=new byte[storage+BUFFER_INCREMENT]; + System.arraycopy(buffer, 0, foo, 0, storage); + buffer=foo; + storage+=BUFFER_INCREMENT; + } + + value&=mask[bits]; + bits+=endbit; + buffer[ptr]|=(byte)(value<<endbit); + + if(bits>=8){ + buffer[ptr+1]=(byte)(value>>>(8-endbit)); + if(bits>=16){ + buffer[ptr+2]=(byte)(value>>>(16-endbit)); + if(bits>=24){ + buffer[ptr+3]=(byte)(value>>>(24-endbit)); + if(bits>=32){ + if(endbit>0) + buffer[ptr+4]=(byte)(value>>>(32-endbit)); + else + buffer[ptr+4]=0; + } + } + } + } + + endbyte+=bits/8; + ptr+=bits/8; + endbit=bits&7; + } + + public int look(int bits){ + int ret; + int m=mask[bits]; + + bits+=endbit; + + if(endbyte+4>=storage){ + if(endbyte+(bits-1)/8>=storage) + return (-1); + } + + ret=((buffer[ptr])&0xff)>>>endbit; + if(bits>8){ + ret|=((buffer[ptr+1])&0xff)<<(8-endbit); + if(bits>16){ + ret|=((buffer[ptr+2])&0xff)<<(16-endbit); + if(bits>24){ + ret|=((buffer[ptr+3])&0xff)<<(24-endbit); + if(bits>32&&endbit!=0){ + ret|=((buffer[ptr+4])&0xff)<<(32-endbit); + } + } + } + } + return (m&ret); + } + + public int look1(){ + if(endbyte>=storage) + return (-1); + return ((buffer[ptr]>>endbit)&1); + } + + public void adv(int bits){ + bits+=endbit; + ptr+=bits/8; + endbyte+=bits/8; + endbit=bits&7; + } + + public void adv1(){ + ++endbit; + if(endbit>7){ + endbit=0; + ptr++; + endbyte++; + } + } + + public int read(int bits){ + int ret; + int m=mask[bits]; + + bits+=endbit; + + if(endbyte+4>=storage){ + ret=-1; + if(endbyte+(bits-1)/8>=storage){ + ptr+=bits/8; + endbyte+=bits/8; + endbit=bits&7; + return (ret); + } + } + + ret=((buffer[ptr])&0xff)>>>endbit; + if(bits>8){ + ret|=((buffer[ptr+1])&0xff)<<(8-endbit); + if(bits>16){ + ret|=((buffer[ptr+2])&0xff)<<(16-endbit); + if(bits>24){ + ret|=((buffer[ptr+3])&0xff)<<(24-endbit); + if(bits>32&&endbit!=0){ + ret|=((buffer[ptr+4])&0xff)<<(32-endbit); + } + } + } + } + + ret&=m; + + ptr+=bits/8; + endbyte+=bits/8; + endbit=bits&7; + return (ret); + } + + public int readB(int bits){ + int ret; + int m=32-bits; + + bits+=endbit; + + if(endbyte+4>=storage){ + /* not the main path */ + ret=-1; + if(endbyte*8+bits>storage*8){ + ptr+=bits/8; + endbyte+=bits/8; + endbit=bits&7; + return (ret); + } + } + + ret=(buffer[ptr]&0xff)<<(24+endbit); + if(bits>8){ + ret|=(buffer[ptr+1]&0xff)<<(16+endbit); + if(bits>16){ + ret|=(buffer[ptr+2]&0xff)<<(8+endbit); + if(bits>24){ + ret|=(buffer[ptr+3]&0xff)<<(endbit); + if(bits>32&&(endbit!=0)) + ret|=(buffer[ptr+4]&0xff)>>(8-endbit); + } + } + } + ret=(ret>>>(m>>1))>>>((m+1)>>1); + + ptr+=bits/8; + endbyte+=bits/8; + endbit=bits&7; + return (ret); + } + + public int read1(){ + int ret; + if(endbyte>=storage){ + ret=-1; + endbit++; + if(endbit>7){ + endbit=0; + ptr++; + endbyte++; + } + return (ret); + } + + ret=(buffer[ptr]>>endbit)&1; + + endbit++; + if(endbit>7){ + endbit=0; + ptr++; + endbyte++; + } + return (ret); + } + + public int bytes(){ + return (endbyte+(endbit+7)/8); + } + + public int bits(){ + return (endbyte*8+endbit); + } + + public byte[] buffer(){ + return (buffer); + } + + public static int ilog(int v){ + int ret=0; + while(v>0){ + ret++; + v>>>=1; + } + return (ret); + } + + public static void report(String in){ + System.err.println(in); + System.exit(1); + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jogg/Packet.java b/ardor3d-audio/src/main/java/com/jcraft/jogg/Packet.java new file mode 100644 index 0000000..7df75d1 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jogg/Packet.java @@ -0,0 +1,47 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +public class Packet{ + public byte[] packet_base; + public int packet; + public int bytes; + public int b_o_s; + public int e_o_s; + + public long granulepos; + + /** + * sequence number for decode; the framing + * knows where there's a hole in the data, + * but we need coupling so that the codec + * (which is in a seperate abstraction + * layer) also knows about the gap + */ + public long packetno; + +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jogg/Page.java b/ardor3d-audio/src/main/java/com/jcraft/jogg/Page.java new file mode 100644 index 0000000..dee59d6 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jogg/Page.java @@ -0,0 +1,135 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +public class Page{ + private static int[] crc_lookup=new int[256]; + static{ + for(int i=0; i<crc_lookup.length; i++){ + crc_lookup[i]=crc_entry(i); + } + } + + private static int crc_entry(int index){ + int r=index<<24; + for(int i=0; i<8; i++){ + if((r&0x80000000)!=0){ + r=(r<<1)^0x04c11db7; /* The same as the ethernet generator + polynomial, although we use an + unreflected alg and an init/final + of 0, not 0xffffffff */ + } + else{ + r<<=1; + } + } + return (r&0xffffffff); + } + + public byte[] header_base; + public int header; + public int header_len; + public byte[] body_base; + public int body; + public int body_len; + + int version(){ + return header_base[header+4]&0xff; + } + + int continued(){ + return (header_base[header+5]&0x01); + } + + public int bos(){ + return (header_base[header+5]&0x02); + } + + public int eos(){ + return (header_base[header+5]&0x04); + } + + public long granulepos(){ + long foo=header_base[header+13]&0xff; + foo=(foo<<8)|(header_base[header+12]&0xff); + foo=(foo<<8)|(header_base[header+11]&0xff); + foo=(foo<<8)|(header_base[header+10]&0xff); + foo=(foo<<8)|(header_base[header+9]&0xff); + foo=(foo<<8)|(header_base[header+8]&0xff); + foo=(foo<<8)|(header_base[header+7]&0xff); + foo=(foo<<8)|(header_base[header+6]&0xff); + return (foo); + } + + public int serialno(){ + return (header_base[header+14]&0xff)|((header_base[header+15]&0xff)<<8) + |((header_base[header+16]&0xff)<<16) + |((header_base[header+17]&0xff)<<24); + } + + int pageno(){ + return (header_base[header+18]&0xff)|((header_base[header+19]&0xff)<<8) + |((header_base[header+20]&0xff)<<16) + |((header_base[header+21]&0xff)<<24); + } + + void checksum(){ + int crc_reg=0; + + for(int i=0; i<header_len; i++){ + crc_reg=(crc_reg<<8) + ^crc_lookup[((crc_reg>>>24)&0xff)^(header_base[header+i]&0xff)]; + } + for(int i=0; i<body_len; i++){ + crc_reg=(crc_reg<<8) + ^crc_lookup[((crc_reg>>>24)&0xff)^(body_base[body+i]&0xff)]; + } + header_base[header+22]=(byte)crc_reg; + header_base[header+23]=(byte)(crc_reg>>>8); + header_base[header+24]=(byte)(crc_reg>>>16); + header_base[header+25]=(byte)(crc_reg>>>24); + } + + public Page copy(){ + return copy(new Page()); + } + + public Page copy(Page p){ + byte[] tmp=new byte[header_len]; + System.arraycopy(header_base, header, tmp, 0, header_len); + p.header_len=header_len; + p.header_base=tmp; + p.header=0; + tmp=new byte[body_len]; + System.arraycopy(body_base, body, tmp, 0, body_len); + p.body_len=body_len; + p.body_base=tmp; + p.body=0; + return p; + } + +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jogg/StreamState.java b/ardor3d-audio/src/main/java/com/jcraft/jogg/StreamState.java new file mode 100644 index 0000000..2fb6447 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jogg/StreamState.java @@ -0,0 +1,526 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +public class StreamState{ + byte[] body_data; /* bytes from packet bodies */ + int body_storage; /* storage elements allocated */ + int body_fill; /* elements stored; fill mark */ + private int body_returned; /* elements of fill returned */ + + int[] lacing_vals; /* The values that will go to the segment table */ + long[] granule_vals; /* pcm_pos values for headers. Not compact + this way, but it is simple coupled to the + lacing fifo */ + int lacing_storage; + int lacing_fill; + int lacing_packet; + int lacing_returned; + + byte[] header=new byte[282]; /* working space for header encode */ + int header_fill; + + public int e_o_s; /* set when we have buffered the last packet in the + logical bitstream */ + int b_o_s; /* set after we've written the initial page + of a logical bitstream */ + int serialno; + int pageno; + long packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a seperate abstraction + layer) also knows about the gap */ + long granulepos; + + public StreamState(){ + init(); + } + + StreamState(int serialno){ + this(); + init(serialno); + } + + void init(){ + body_storage=16*1024; + body_data=new byte[body_storage]; + lacing_storage=1024; + lacing_vals=new int[lacing_storage]; + granule_vals=new long[lacing_storage]; + } + + public void init(int serialno){ + if(body_data==null){ + init(); + } + else{ + for(int i=0; i<body_data.length; i++) + body_data[i]=0; + for(int i=0; i<lacing_vals.length; i++) + lacing_vals[i]=0; + for(int i=0; i<granule_vals.length; i++) + granule_vals[i]=0; + } + this.serialno=serialno; + } + + public void clear(){ + body_data=null; + lacing_vals=null; + granule_vals=null; + } + + void destroy(){ + clear(); + } + + void body_expand(int needed){ + if(body_storage<=body_fill+needed){ + body_storage+=(needed+1024); + byte[] foo=new byte[body_storage]; + System.arraycopy(body_data, 0, foo, 0, body_data.length); + body_data=foo; + } + } + + void lacing_expand(int needed){ + if(lacing_storage<=lacing_fill+needed){ + lacing_storage+=(needed+32); + int[] foo=new int[lacing_storage]; + System.arraycopy(lacing_vals, 0, foo, 0, lacing_vals.length); + lacing_vals=foo; + + long[] bar=new long[lacing_storage]; + System.arraycopy(granule_vals, 0, bar, 0, granule_vals.length); + granule_vals=bar; + } + } + + /* submit data to the internal buffer of the framing engine */ + public int packetin(Packet op){ + int lacing_val=op.bytes/255+1; + + if(body_returned!=0){ + /* advance packet data according to the body_returned pointer. We + had to keep it around to return a pointer into the buffer last + call */ + + body_fill-=body_returned; + if(body_fill!=0){ + System.arraycopy(body_data, body_returned, body_data, 0, body_fill); + } + body_returned=0; + } + + /* make sure we have the buffer storage */ + body_expand(op.bytes); + lacing_expand(lacing_val); + + /* Copy in the submitted packet. Yes, the copy is a waste; this is + the liability of overly clean abstraction for the time being. It + will actually be fairly easy to eliminate the extra copy in the + future */ + + System.arraycopy(op.packet_base, op.packet, body_data, body_fill, op.bytes); + body_fill+=op.bytes; + + /* Store lacing vals for this packet */ + int j; + for(j=0; j<lacing_val-1; j++){ + lacing_vals[lacing_fill+j]=255; + granule_vals[lacing_fill+j]=granulepos; + } + lacing_vals[lacing_fill+j]=(op.bytes)%255; + granulepos=granule_vals[lacing_fill+j]=op.granulepos; + + /* flag the first segment as the beginning of the packet */ + lacing_vals[lacing_fill]|=0x100; + + lacing_fill+=lacing_val; + + /* for the sake of completeness */ + packetno++; + + if(op.e_o_s!=0) + e_o_s=1; + return (0); + } + + public int packetout(Packet op){ + + /* The last part of decode. We have the stream broken into packet + segments. Now we need to group them into packets (or return the + out of sync markers) */ + + int ptr=lacing_returned; + + if(lacing_packet<=ptr){ + return (0); + } + + if((lacing_vals[ptr]&0x400)!=0){ + /* We lost sync here; let the app know */ + lacing_returned++; + + /* we need to tell the codec there's a gap; it might need to + handle previous packet dependencies. */ + packetno++; + return (-1); + } + + /* Gather the whole packet. We'll have no holes or a partial packet */ + { + int size=lacing_vals[ptr]&0xff; + int bytes=0; + + op.packet_base=body_data; + op.packet=body_returned; + op.e_o_s=lacing_vals[ptr]&0x200; /* last packet of the stream? */ + op.b_o_s=lacing_vals[ptr]&0x100; /* first packet of the stream? */ + bytes+=size; + + while(size==255){ + int val=lacing_vals[++ptr]; + size=val&0xff; + if((val&0x200)!=0) + op.e_o_s=0x200; + bytes+=size; + } + + op.packetno=packetno; + op.granulepos=granule_vals[ptr]; + op.bytes=bytes; + + body_returned+=bytes; + + lacing_returned=ptr+1; + } + packetno++; + return (1); + } + + // add the incoming page to the stream state; we decompose the page + // into packet segments here as well. + + public int pagein(Page og){ + byte[] header_base=og.header_base; + int header=og.header; + byte[] body_base=og.body_base; + int body=og.body; + int bodysize=og.body_len; + int segptr=0; + + int version=og.version(); + int continued=og.continued(); + int bos=og.bos(); + int eos=og.eos(); + long granulepos=og.granulepos(); + int _serialno=og.serialno(); + int _pageno=og.pageno(); + int segments=header_base[header+26]&0xff; + + // clean up 'returned data' + { + int lr=lacing_returned; + int br=body_returned; + + // body data + if(br!=0){ + body_fill-=br; + if(body_fill!=0){ + System.arraycopy(body_data, br, body_data, 0, body_fill); + } + body_returned=0; + } + + if(lr!=0){ + // segment table + if((lacing_fill-lr)!=0){ + System.arraycopy(lacing_vals, lr, lacing_vals, 0, lacing_fill-lr); + System.arraycopy(granule_vals, lr, granule_vals, 0, lacing_fill-lr); + } + lacing_fill-=lr; + lacing_packet-=lr; + lacing_returned=0; + } + } + + // check the serial number + if(_serialno!=serialno) + return (-1); + if(version>0) + return (-1); + + lacing_expand(segments+1); + + // are we in sequence? + if(_pageno!=pageno){ + int i; + + // unroll previous partial packet (if any) + for(i=lacing_packet; i<lacing_fill; i++){ + body_fill-=lacing_vals[i]&0xff; + //System.out.println("??"); + } + lacing_fill=lacing_packet; + + // make a note of dropped data in segment table + if(pageno!=-1){ + lacing_vals[lacing_fill++]=0x400; + lacing_packet++; + } + + // are we a 'continued packet' page? If so, we'll need to skip + // some segments + if(continued!=0){ + bos=0; + for(; segptr<segments; segptr++){ + int val=(header_base[header+27+segptr]&0xff); + body+=val; + bodysize-=val; + if(val<255){ + segptr++; + break; + } + } + } + } + + if(bodysize!=0){ + body_expand(bodysize); + System.arraycopy(body_base, body, body_data, body_fill, bodysize); + body_fill+=bodysize; + } + + { + int saved=-1; + while(segptr<segments){ + int val=(header_base[header+27+segptr]&0xff); + lacing_vals[lacing_fill]=val; + granule_vals[lacing_fill]=-1; + + if(bos!=0){ + lacing_vals[lacing_fill]|=0x100; + bos=0; + } + + if(val<255) + saved=lacing_fill; + + lacing_fill++; + segptr++; + + if(val<255) + lacing_packet=lacing_fill; + } + + /* set the granulepos on the last pcmval of the last full packet */ + if(saved!=-1){ + granule_vals[saved]=granulepos; + } + } + + if(eos!=0){ + e_o_s=1; + if(lacing_fill>0) + lacing_vals[lacing_fill-1]|=0x200; + } + + pageno=_pageno+1; + return (0); + } + + /* This will flush remaining packets into a page (returning nonzero), + even if there is not enough data to trigger a flush normally + (undersized page). If there are no packets or partial packets to + flush, ogg_stream_flush returns 0. Note that ogg_stream_flush will + try to flush a normal sized page like ogg_stream_pageout; a call to + ogg_stream_flush does not gurantee that all packets have flushed. + Only a return value of 0 from ogg_stream_flush indicates all packet + data is flushed into pages. + + ogg_stream_page will flush the last page in a stream even if it's + undersized; you almost certainly want to use ogg_stream_pageout + (and *not* ogg_stream_flush) unless you need to flush an undersized + page in the middle of a stream for some reason. */ + + public int flush(Page og){ + + int i; + int vals=0; + int maxvals=(lacing_fill>255 ? 255 : lacing_fill); + int bytes=0; + int acc=0; + long granule_pos=granule_vals[0]; + + if(maxvals==0) + return (0); + + /* construct a page */ + /* decide how many segments to include */ + + /* If this is the initial header case, the first page must only include + the initial header packet */ + if(b_o_s==0){ /* 'initial header page' case */ + granule_pos=0; + for(vals=0; vals<maxvals; vals++){ + if((lacing_vals[vals]&0x0ff)<255){ + vals++; + break; + } + } + } + else{ + for(vals=0; vals<maxvals; vals++){ + if(acc>4096) + break; + acc+=(lacing_vals[vals]&0x0ff); + granule_pos=granule_vals[vals]; + } + } + + /* construct the header in temp storage */ + System.arraycopy("OggS".getBytes(), 0, header, 0, 4); + + /* stream structure version */ + header[4]=0x00; + + /* continued packet flag? */ + header[5]=0x00; + if((lacing_vals[0]&0x100)==0) + header[5]|=0x01; + /* first page flag? */ + if(b_o_s==0) + header[5]|=0x02; + /* last page flag? */ + if(e_o_s!=0&&lacing_fill==vals) + header[5]|=0x04; + b_o_s=1; + + /* 64 bits of PCM position */ + for(i=6; i<14; i++){ + header[i]=(byte)granule_pos; + granule_pos>>>=8; + } + + /* 32 bits of stream serial number */ + { + int _serialno=serialno; + for(i=14; i<18; i++){ + header[i]=(byte)_serialno; + _serialno>>>=8; + } + } + + /* 32 bits of page counter (we have both counter and page header + because this val can roll over) */ + if(pageno==-1) + pageno=0; /* because someone called + stream_reset; this would be a + strange thing to do in an + encode stream, but it has + plausible uses */ + { + int _pageno=pageno++; + for(i=18; i<22; i++){ + header[i]=(byte)_pageno; + _pageno>>>=8; + } + } + + /* zero for computation; filled in later */ + header[22]=0; + header[23]=0; + header[24]=0; + header[25]=0; + + /* segment table */ + header[26]=(byte)vals; + for(i=0; i<vals; i++){ + header[i+27]=(byte)lacing_vals[i]; + bytes+=(header[i+27]&0xff); + } + + /* set pointers in the ogg_page struct */ + og.header_base=header; + og.header=0; + og.header_len=header_fill=vals+27; + og.body_base=body_data; + og.body=body_returned; + og.body_len=bytes; + + /* advance the lacing data and set the body_returned pointer */ + + lacing_fill-=vals; + System.arraycopy(lacing_vals, vals, lacing_vals, 0, lacing_fill*4); + System.arraycopy(granule_vals, vals, granule_vals, 0, lacing_fill*8); + body_returned+=bytes; + + /* calculate the checksum */ + + og.checksum(); + + /* done */ + return (1); + } + + /* This constructs pages from buffered packet segments. The pointers + returned are to static buffers; do not free. The returned buffers are + good only until the next call (using the same ogg_stream_state) */ + public int pageout(Page og){ + if((e_o_s!=0&&lacing_fill!=0)|| /* 'were done, now flush' case */ + body_fill-body_returned>4096|| /* 'page nominal size' case */ + lacing_fill>=255|| /* 'segment table full' case */ + (lacing_fill!=0&&b_o_s==0)){ /* 'initial header page' case */ + return flush(og); + } + return 0; + } + + public int eof(){ + return e_o_s; + } + + public int reset(){ + body_fill=0; + body_returned=0; + + lacing_fill=0; + lacing_packet=0; + lacing_returned=0; + + header_fill=0; + + e_o_s=0; + b_o_s=0; + pageno=-1; + packetno=0; + granulepos=0; + return (0); + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jogg/SyncState.java b/ardor3d-audio/src/main/java/com/jcraft/jogg/SyncState.java new file mode 100644 index 0000000..f9d3406 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jogg/SyncState.java @@ -0,0 +1,275 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +// DECODING PRIMITIVES: packet streaming layer + +// This has two layers to place more of the multi-serialno and paging +// control in the application's hands. First, we expose a data buffer +// using ogg_decode_buffer(). The app either copies into the +// buffer, or passes it directly to read(), etc. We then call +// ogg_decode_wrote() to tell how many bytes we just added. +// +// Pages are returned (pointers into the buffer in ogg_sync_state) +// by ogg_decode_stream(). The page is then submitted to +// ogg_decode_page() along with the appropriate +// ogg_stream_state* (ie, matching serialno). We then get raw +// packets out calling ogg_stream_packet() with a +// ogg_stream_state. See the 'frame-prog.txt' docs for details and +// example code. + +public class SyncState{ + + public byte[] data; + int storage; + int fill; + int returned; + + int unsynced; + int headerbytes; + int bodybytes; + + public int clear(){ + data=null; + return (0); + } + + public int buffer(int size){ + // first, clear out any space that has been previously returned + if(returned!=0){ + fill-=returned; + if(fill>0){ + System.arraycopy(data, returned, data, 0, fill); + } + returned=0; + } + + if(size>storage-fill){ + // We need to extend the internal buffer + int newsize=size+fill+4096; // an extra page to be nice + if(data!=null){ + byte[] foo=new byte[newsize]; + System.arraycopy(data, 0, foo, 0, data.length); + data=foo; + } + else{ + data=new byte[newsize]; + } + storage=newsize; + } + + return (fill); + } + + public int wrote(int bytes){ + if(fill+bytes>storage) + return (-1); + fill+=bytes; + return (0); + } + + // sync the stream. This is meant to be useful for finding page + // boundaries. + // + // return values for this: + // -n) skipped n bytes + // 0) page not ready; more data (no bytes skipped) + // n) page synced at current location; page length n bytes + private Page pageseek=new Page(); + private byte[] chksum=new byte[4]; + + public int pageseek(Page og){ + int page=returned; + int next; + int bytes=fill-returned; + + if(headerbytes==0){ + int _headerbytes, i; + if(bytes<27) + return (0); // not enough for a header + + /* verify capture pattern */ + if(data[page]!='O'||data[page+1]!='g'||data[page+2]!='g' + ||data[page+3]!='S'){ + headerbytes=0; + bodybytes=0; + + // search for possible capture + next=0; + for(int ii=0; ii<bytes-1; ii++){ + if(data[page+1+ii]=='O'){ + next=page+1+ii; + break; + } + } + //next=memchr(page+1,'O',bytes-1); + if(next==0) + next=fill; + + returned=next; + return (-(next-page)); + } + _headerbytes=(data[page+26]&0xff)+27; + if(bytes<_headerbytes) + return (0); // not enough for header + seg table + + // count up body length in the segment table + + for(i=0; i<(data[page+26]&0xff); i++){ + bodybytes+=(data[page+27+i]&0xff); + } + headerbytes=_headerbytes; + } + + if(bodybytes+headerbytes>bytes) + return (0); + + // The whole test page is buffered. Verify the checksum + synchronized(chksum){ + // Grab the checksum bytes, set the header field to zero + + System.arraycopy(data, page+22, chksum, 0, 4); + data[page+22]=0; + data[page+23]=0; + data[page+24]=0; + data[page+25]=0; + + // set up a temp page struct and recompute the checksum + Page log=pageseek; + log.header_base=data; + log.header=page; + log.header_len=headerbytes; + + log.body_base=data; + log.body=page+headerbytes; + log.body_len=bodybytes; + log.checksum(); + + // Compare + if(chksum[0]!=data[page+22]||chksum[1]!=data[page+23] + ||chksum[2]!=data[page+24]||chksum[3]!=data[page+25]){ + // D'oh. Mismatch! Corrupt page (or miscapture and not a page at all) + // replace the computed checksum with the one actually read in + System.arraycopy(chksum, 0, data, page+22, 4); + // Bad checksum. Lose sync */ + + headerbytes=0; + bodybytes=0; + // search for possible capture + next=0; + for(int ii=0; ii<bytes-1; ii++){ + if(data[page+1+ii]=='O'){ + next=page+1+ii; + break; + } + } + //next=memchr(page+1,'O',bytes-1); + if(next==0) + next=fill; + returned=next; + return (-(next-page)); + } + } + + // yes, have a whole page all ready to go + { + page=returned; + + if(og!=null){ + og.header_base=data; + og.header=page; + og.header_len=headerbytes; + og.body_base=data; + og.body=page+headerbytes; + og.body_len=bodybytes; + } + + unsynced=0; + returned+=(bytes=headerbytes+bodybytes); + headerbytes=0; + bodybytes=0; + return (bytes); + } + } + + // sync the stream and get a page. Keep trying until we find a page. + // Supress 'sync errors' after reporting the first. + // + // return values: + // -1) recapture (hole in data) + // 0) need more data + // 1) page returned + // + // Returns pointers into buffered data; invalidated by next call to + // _stream, _clear, _init, or _buffer + + public int pageout(Page og){ + // all we need to do is verify a page at the head of the stream + // buffer. If it doesn't verify, we look for the next potential + // frame + + while(true){ + int ret=pageseek(og); + if(ret>0){ + // have a page + return (1); + } + if(ret==0){ + // need more data + return (0); + } + + // head did not start a synced page... skipped some bytes + if(unsynced==0){ + unsynced=1; + return (-1); + } + // loop. keep looking + } + } + + // clear things to an initial state. Good to call, eg, before seeking + public int reset(){ + fill=0; + returned=0; + unsynced=0; + headerbytes=0; + bodybytes=0; + return (0); + } + + public void init(){ + } + + public int getDataOffset(){ + return returned; + } + + public int getBufferOffset(){ + return fill; + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Block.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Block.java new file mode 100644 index 0000000..f1d8678 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Block.java @@ -0,0 +1,128 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +public class Block{ + ///necessary stream state for linking to the framing abstraction + float[][] pcm=new float[0][]; // this is a pointer into local storage + Buffer opb=new Buffer(); + + int lW; + int W; + int nW; + int pcmend; + int mode; + + int eofflag; + long granulepos; + long sequence; + DspState vd; // For read-only access of configuration + + // bitmetrics for the frame + int glue_bits; + int time_bits; + int floor_bits; + int res_bits; + + public Block(DspState vd){ + this.vd=vd; + if(vd.analysisp!=0){ + opb.writeinit(); + } + } + + public void init(DspState vd){ + this.vd=vd; + } + + public int clear(){ + if(vd!=null){ + if(vd.analysisp!=0){ + opb.writeclear(); + } + } + return (0); + } + + public int synthesis(Packet op){ + Info vi=vd.vi; + + // first things first. Make sure decode is ready + opb.readinit(op.packet_base, op.packet, op.bytes); + + // Check the packet type + if(opb.read(1)!=0){ + // Oops. This is not an audio data packet + return (-1); + } + + // read our mode and pre/post windowsize + int _mode=opb.read(vd.modebits); + if(_mode==-1) + return (-1); + + mode=_mode; + W=vi.mode_param[mode].blockflag; + if(W!=0){ + lW=opb.read(1); + nW=opb.read(1); + if(nW==-1) + return (-1); + } + else{ + lW=0; + nW=0; + } + + // more setup + granulepos=op.granulepos; + sequence=op.packetno-3; // first block is third packet + eofflag=op.e_o_s; + + // alloc pcm passback storage + pcmend=vi.blocksizes[W]; + if(pcm.length<vi.channels){ + pcm=new float[vi.channels][]; + } + for(int i=0; i<vi.channels; i++){ + if(pcm[i]==null||pcm[i].length<pcmend){ + pcm[i]=new float[pcmend]; + } + else{ + for(int j=0; j<pcmend; j++){ + pcm[i][j]=0; + } + } + } + + // unpack_header enforces range checking + int type=vi.map_type[vi.mode_param[mode].mapping]; + return (FuncMapping.mapping_P[type].inverse(this, vd.mode[mode])); + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/ChainingExample.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/ChainingExample.java new file mode 100644 index 0000000..7ed0199 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/ChainingExample.java @@ -0,0 +1,69 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class ChainingExample{ + public static void main(String[] arg){ + VorbisFile ov=null; + + try{ + if(arg.length>0){ + ov=new VorbisFile(arg[0]); + } + else{ + ov=new VorbisFile(System.in, null, -1); + } + } + catch(Exception e){ + System.err.println(e); + return; + } + + if(ov.seekable()){ + System.out.println("Input bitstream contained "+ov.streams() + +" logical bitstream section(s)."); + System.out.println("Total bitstream playing time: "+ov.time_total(-1) + +" seconds\n"); + } + else{ + System.out.println("Standard input was not seekable."); + System.out.println("First logical bitstream information:\n"); + } + + for(int i=0; i<ov.streams(); i++){ + Info vi=ov.getInfo(i); + System.out.println("\tlogical bitstream section "+(i+1)+" information:"); + System.out.println("\t\t"+vi.rate+"Hz "+vi.channels+" channels bitrate " + +(ov.bitrate(i)/1000)+"kbps serial number="+ov.serialnumber(i)); + System.out.print("\t\tcompressed length: "+ov.raw_total(i)+" bytes "); + System.out.println(" play time: "+ov.time_total(i)+"s"); + Comment vc=ov.getComment(i); + System.out.println(vc); + } + //ov.clear(); + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/CodeBook.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/CodeBook.java new file mode 100644 index 0000000..51041c1 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/CodeBook.java @@ -0,0 +1,478 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class CodeBook{ + int dim; // codebook dimensions (elements per vector) + int entries; // codebook entries + StaticCodeBook c=new StaticCodeBook(); + + float[] valuelist; // list of dim*entries actual entry values + int[] codelist; // list of bitstream codewords for each entry + DecodeAux decode_tree; + + // returns the number of bits + int encode(int a, Buffer b){ + b.write(codelist[a], c.lengthlist[a]); + return (c.lengthlist[a]); + } + + // One the encode side, our vector writers are each designed for a + // specific purpose, and the encoder is not flexible without modification: + // + // The LSP vector coder uses a single stage nearest-match with no + // interleave, so no step and no error return. This is specced by floor0 + // and doesn't change. + // + // Residue0 encoding interleaves, uses multiple stages, and each stage + // peels of a specific amount of resolution from a lattice (thus we want + // to match by threshhold, not nearest match). Residue doesn't *have* to + // be encoded that way, but to change it, one will need to add more + // infrastructure on the encode side (decode side is specced and simpler) + + // floor0 LSP (single stage, non interleaved, nearest match) + // returns entry number and *modifies a* to the quantization value + int errorv(float[] a){ + int best=best(a, 1); + for(int k=0; k<dim; k++){ + a[k]=valuelist[best*dim+k]; + } + return (best); + } + + // returns the number of bits and *modifies a* to the quantization value + int encodev(int best, float[] a, Buffer b){ + for(int k=0; k<dim; k++){ + a[k]=valuelist[best*dim+k]; + } + return (encode(best, b)); + } + + // res0 (multistage, interleave, lattice) + // returns the number of bits and *modifies a* to the remainder value + int encodevs(float[] a, Buffer b, int step, int addmul){ + int best=besterror(a, step, addmul); + return (encode(best, b)); + } + + private int[] t=new int[15]; // decodevs_add is synchronized for re-using t. + + synchronized int decodevs_add(float[] a, int offset, Buffer b, int n){ + int step=n/dim; + int entry; + int i, j, o; + + if(t.length<step){ + t=new int[step]; + } + + for(i=0; i<step; i++){ + entry=decode(b); + if(entry==-1) + return (-1); + t[i]=entry*dim; + } + for(i=0, o=0; i<dim; i++, o+=step){ + for(j=0; j<step; j++){ + a[offset+o+j]+=valuelist[t[j]+i]; + } + } + + return (0); + } + + int decodev_add(float[] a, int offset, Buffer b, int n){ + int i, j, entry; + int t; + + if(dim>8){ + for(i=0; i<n;){ + entry=decode(b); + if(entry==-1) + return (-1); + t=entry*dim; + for(j=0; j<dim;){ + a[offset+(i++)]+=valuelist[t+(j++)]; + } + } + } + else{ + for(i=0; i<n;){ + entry=decode(b); + if(entry==-1) + return (-1); + t=entry*dim; + j=0; + switch(dim){ + case 8: + a[offset+(i++)]+=valuelist[t+(j++)]; + case 7: + a[offset+(i++)]+=valuelist[t+(j++)]; + case 6: + a[offset+(i++)]+=valuelist[t+(j++)]; + case 5: + a[offset+(i++)]+=valuelist[t+(j++)]; + case 4: + a[offset+(i++)]+=valuelist[t+(j++)]; + case 3: + a[offset+(i++)]+=valuelist[t+(j++)]; + case 2: + a[offset+(i++)]+=valuelist[t+(j++)]; + case 1: + a[offset+(i++)]+=valuelist[t+(j++)]; + case 0: + break; + } + } + } + return (0); + } + + int decodev_set(float[] a, int offset, Buffer b, int n){ + int i, j, entry; + int t; + + for(i=0; i<n;){ + entry=decode(b); + if(entry==-1) + return (-1); + t=entry*dim; + for(j=0; j<dim;){ + a[offset+i++]=valuelist[t+(j++)]; + } + } + return (0); + } + + int decodevv_add(float[][] a, int offset, int ch, Buffer b, int n){ + int i, j, entry; + int chptr=0; + + for(i=offset/ch; i<(offset+n)/ch;){ + entry=decode(b); + if(entry==-1) + return (-1); + + int t=entry*dim; + for(j=0; j<dim; j++){ + a[chptr++][i]+=valuelist[t+j]; + if(chptr==ch){ + chptr=0; + i++; + } + } + } + return (0); + } + + // Decode side is specced and easier, because we don't need to find + // matches using different criteria; we simply read and map. There are + // two things we need to do 'depending': + // + // We may need to support interleave. We don't really, but it's + // convenient to do it here rather than rebuild the vector later. + // + // Cascades may be additive or multiplicitive; this is not inherent in + // the codebook, but set in the code using the codebook. Like + // interleaving, it's easiest to do it here. + // stage==0 -> declarative (set the value) + // stage==1 -> additive + // stage==2 -> multiplicitive + + // returns the entry number or -1 on eof + int decode(Buffer b){ + int ptr=0; + DecodeAux t=decode_tree; + int lok=b.look(t.tabn); + + if(lok>=0){ + ptr=t.tab[lok]; + b.adv(t.tabl[lok]); + if(ptr<=0){ + return -ptr; + } + } + do{ + switch(b.read1()){ + case 0: + ptr=t.ptr0[ptr]; + break; + case 1: + ptr=t.ptr1[ptr]; + break; + case -1: + default: + return (-1); + } + } + while(ptr>0); + return (-ptr); + } + + // returns the entry number or -1 on eof + int decodevs(float[] a, int index, Buffer b, int step, int addmul){ + int entry=decode(b); + if(entry==-1) + return (-1); + switch(addmul){ + case -1: + for(int i=0, o=0; i<dim; i++, o+=step) + a[index+o]=valuelist[entry*dim+i]; + break; + case 0: + for(int i=0, o=0; i<dim; i++, o+=step) + a[index+o]+=valuelist[entry*dim+i]; + break; + case 1: + for(int i=0, o=0; i<dim; i++, o+=step) + a[index+o]*=valuelist[entry*dim+i]; + break; + default: + //System.err.println("CodeBook.decodeves: addmul="+addmul); + } + return (entry); + } + + int best(float[] a, int step){ + // brute force it! + { + int besti=-1; + float best=0.f; + int e=0; + for(int i=0; i<entries; i++){ + if(c.lengthlist[i]>0){ + float _this=dist(dim, valuelist, e, a, step); + if(besti==-1||_this<best){ + best=_this; + besti=i; + } + } + e+=dim; + } + return (besti); + } + } + + // returns the entry number and *modifies a* to the remainder value + int besterror(float[] a, int step, int addmul){ + int best=best(a, step); + switch(addmul){ + case 0: + for(int i=0, o=0; i<dim; i++, o+=step) + a[o]-=valuelist[best*dim+i]; + break; + case 1: + for(int i=0, o=0; i<dim; i++, o+=step){ + float val=valuelist[best*dim+i]; + if(val==0){ + a[o]=0; + } + else{ + a[o]/=val; + } + } + break; + } + return (best); + } + + void clear(){ + } + + private static float dist(int el, float[] ref, int index, float[] b, int step){ + float acc=(float)0.; + for(int i=0; i<el; i++){ + float val=(ref[index+i]-b[i*step]); + acc+=val*val; + } + return (acc); + } + + int init_decode(StaticCodeBook s){ + c=s; + entries=s.entries; + dim=s.dim; + valuelist=s.unquantize(); + + decode_tree=make_decode_tree(); + if(decode_tree==null){ + clear(); + return (-1); + } + return (0); + } + + // given a list of word lengths, generate a list of codewords. Works + // for length ordered or unordered, always assigns the lowest valued + // codewords first. Extended to handle unused entries (length 0) + static int[] make_words(int[] l, int n){ + int[] marker=new int[33]; + int[] r=new int[n]; + + for(int i=0; i<n; i++){ + int length=l[i]; + if(length>0){ + int entry=marker[length]; + + // when we claim a node for an entry, we also claim the nodes + // below it (pruning off the imagined tree that may have dangled + // from it) as well as blocking the use of any nodes directly + // above for leaves + + // update ourself + if(length<32&&(entry>>>length)!=0){ + // error condition; the lengths must specify an overpopulated tree + //free(r); + return (null); + } + r[i]=entry; + + // Look to see if the next shorter marker points to the node + // above. if so, update it and repeat. + { + for(int j=length; j>0; j--){ + if((marker[j]&1)!=0){ + // have to jump branches + if(j==1) + marker[1]++; + else + marker[j]=marker[j-1]<<1; + break; // invariant says next upper marker would already + // have been moved if it was on the same path + } + marker[j]++; + } + } + + // prune the tree; the implicit invariant says all the longer + // markers were dangling from our just-taken node. Dangle them + // from our *new* node. + for(int j=length+1; j<33; j++){ + if((marker[j]>>>1)==entry){ + entry=marker[j]; + marker[j]=marker[j-1]<<1; + } + else{ + break; + } + } + } + } + + // bitreverse the words because our bitwise packer/unpacker is LSb + // endian + for(int i=0; i<n; i++){ + int temp=0; + for(int j=0; j<l[i]; j++){ + temp<<=1; + temp|=(r[i]>>>j)&1; + } + r[i]=temp; + } + + return (r); + } + + // build the decode helper tree from the codewords + DecodeAux make_decode_tree(){ + int top=0; + DecodeAux t=new DecodeAux(); + int[] ptr0=t.ptr0=new int[entries*2]; + int[] ptr1=t.ptr1=new int[entries*2]; + int[] codelist=make_words(c.lengthlist, c.entries); + + if(codelist==null) + return (null); + t.aux=entries*2; + + for(int i=0; i<entries; i++){ + if(c.lengthlist[i]>0){ + int ptr=0; + int j; + for(j=0; j<c.lengthlist[i]-1; j++){ + int bit=(codelist[i]>>>j)&1; + if(bit==0){ + if(ptr0[ptr]==0){ + ptr0[ptr]=++top; + } + ptr=ptr0[ptr]; + } + else{ + if(ptr1[ptr]==0){ + ptr1[ptr]=++top; + } + ptr=ptr1[ptr]; + } + } + + if(((codelist[i]>>>j)&1)==0){ + ptr0[ptr]=-i; + } + else{ + ptr1[ptr]=-i; + } + + } + } + + t.tabn=Util.ilog(entries)-4; + + if(t.tabn<5) + t.tabn=5; + int n=1<<t.tabn; + t.tab=new int[n]; + t.tabl=new int[n]; + for(int i=0; i<n; i++){ + int p=0; + int j=0; + for(j=0; j<t.tabn&&(p>0||j==0); j++){ + if((i&(1<<j))!=0){ + p=ptr1[p]; + } + else{ + p=ptr0[p]; + } + } + t.tab[i]=p; // -code + t.tabl[i]=j; // length + } + + return (t); + } + + class DecodeAux{ + int[] tab; + int[] tabl; + int tabn; + + int[] ptr0; + int[] ptr1; + int aux; // number of tree entries + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Comment.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Comment.java new file mode 100644 index 0000000..02b3084 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Comment.java @@ -0,0 +1,243 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +// the comments are not part of vorbis_info so that vorbis_info can be +// static storage +public class Comment{ + private static byte[] _vorbis="vorbis".getBytes(); + private static byte[] _vendor="Xiphophorus libVorbis I 20000508".getBytes(); + + private static final int OV_EIMPL=-130; + + // unlimited user comment fields. + public byte[][] user_comments; + public int[] comment_lengths; + public int comments; + public byte[] vendor; + + public void init(){ + user_comments=null; + comments=0; + vendor=null; + } + + public void add(String comment){ + add(comment.getBytes()); + } + + private void add(byte[] comment){ + byte[][] foo=new byte[comments+2][]; + if(user_comments!=null){ + System.arraycopy(user_comments, 0, foo, 0, comments); + } + user_comments=foo; + + int[] goo=new int[comments+2]; + if(comment_lengths!=null){ + System.arraycopy(comment_lengths, 0, goo, 0, comments); + } + comment_lengths=goo; + + byte[] bar=new byte[comment.length+1]; + System.arraycopy(comment, 0, bar, 0, comment.length); + user_comments[comments]=bar; + comment_lengths[comments]=comment.length; + comments++; + user_comments[comments]=null; + } + + public void add_tag(String tag, String contents){ + if(contents==null) + contents=""; + add(tag+"="+contents); + } + + static boolean tagcompare(byte[] s1, byte[] s2, int n){ + int c=0; + byte u1, u2; + while(c<n){ + u1=s1[c]; + u2=s2[c]; + if('Z'>=u1&&u1>='A') + u1=(byte)(u1-'A'+'a'); + if('Z'>=u2&&u2>='A') + u2=(byte)(u2-'A'+'a'); + if(u1!=u2){ + return false; + } + c++; + } + return true; + } + + public String query(String tag){ + return query(tag, 0); + } + + public String query(String tag, int count){ + int foo=query(tag.getBytes(), count); + if(foo==-1) + return null; + byte[] comment=user_comments[foo]; + for(int i=0; i<comment_lengths[foo]; i++){ + if(comment[i]=='='){ + return new String(comment, i+1, comment_lengths[foo]-(i+1)); + } + } + return null; + } + + private int query(byte[] tag, int count){ + int i=0; + int found=0; + int fulltaglen=tag.length+1; + byte[] fulltag=new byte[fulltaglen]; + System.arraycopy(tag, 0, fulltag, 0, tag.length); + fulltag[tag.length]=(byte)'='; + + for(i=0; i<comments; i++){ + if(tagcompare(user_comments[i], fulltag, fulltaglen)){ + if(count==found){ + // We return a pointer to the data, not a copy + //return user_comments[i] + taglen + 1; + return i; + } + else{ + found++; + } + } + } + return -1; + } + + int unpack(Buffer opb){ + int vendorlen=opb.read(32); + if(vendorlen<0){ + clear(); + return (-1); + } + vendor=new byte[vendorlen+1]; + opb.read(vendor, vendorlen); + comments=opb.read(32); + if(comments<0){ + clear(); + return (-1); + } + user_comments=new byte[comments+1][]; + comment_lengths=new int[comments+1]; + + for(int i=0; i<comments; i++){ + int len=opb.read(32); + if(len<0){ + clear(); + return (-1); + } + comment_lengths[i]=len; + user_comments[i]=new byte[len+1]; + opb.read(user_comments[i], len); + } + if(opb.read(1)!=1){ + clear(); + return (-1); + + } + return (0); + } + + int pack(Buffer opb){ + // preamble + opb.write(0x03, 8); + opb.write(_vorbis); + + // vendor + opb.write(_vendor.length, 32); + opb.write(_vendor); + + // comments + opb.write(comments, 32); + if(comments!=0){ + for(int i=0; i<comments; i++){ + if(user_comments[i]!=null){ + opb.write(comment_lengths[i], 32); + opb.write(user_comments[i]); + } + else{ + opb.write(0, 32); + } + } + } + opb.write(1, 1); + return (0); + } + + public int header_out(Packet op){ + Buffer opb=new Buffer(); + opb.writeinit(); + + if(pack(opb)!=0) + return OV_EIMPL; + + op.packet_base=new byte[opb.bytes()]; + op.packet=0; + op.bytes=opb.bytes(); + System.arraycopy(opb.buffer(), 0, op.packet_base, 0, op.bytes); + op.b_o_s=0; + op.e_o_s=0; + op.granulepos=0; + return 0; + } + + void clear(){ + for(int i=0; i<comments; i++) + user_comments[i]=null; + user_comments=null; + vendor=null; + } + + public String getVendor(){ + return new String(vendor, 0, vendor.length-1); + } + + public String getComment(int i){ + if(comments<=i) + return null; + return new String(user_comments[i], 0, user_comments[i].length-1); + } + + public String toString(){ + String foo="Vendor: "+new String(vendor, 0, vendor.length-1); + for(int i=0; i<comments; i++){ + foo=foo+"\nComment: " + +new String(user_comments[i], 0, user_comments[i].length-1); + } + foo=foo+"\n"; + return foo; + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/DecodeExample.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/DecodeExample.java new file mode 100644 index 0000000..b73889e --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/DecodeExample.java @@ -0,0 +1,324 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +// Takes a vorbis bitstream from stdin and writes raw stereo PCM to +// stdout. Decodes simple and chained OggVorbis files from beginning +// to end. Vorbisfile.a is somewhat more complex than the code below. + +class DecodeExample{ + static int convsize=4096*2; + static byte[] convbuffer=new byte[convsize]; // take 8k out of the data segment, not the stack + + public static void main(String[] arg){ + java.io.InputStream input=System.in; + if(arg.length>0){ + try{ + input=new java.io.FileInputStream(arg[0]); + } + catch(Exception e){ + System.err.println(e); + } + } + + SyncState oy=new SyncState(); // sync and verify incoming physical bitstream + StreamState os=new StreamState(); // take physical pages, weld into a logical stream of packets + Page og=new Page(); // one Ogg bitstream page. Vorbis packets are inside + Packet op=new Packet(); // one raw packet of data for decode + + Info vi=new Info(); // struct that stores all the static vorbis bitstream settings + Comment vc=new Comment(); // struct that stores all the bitstream user comments + DspState vd=new DspState(); // central working state for the packet->PCM decoder + Block vb=new Block(vd); // local working space for packet->PCM decode + + byte[] buffer; + int bytes=0; + + // Decode setup + + oy.init(); // Now we can read pages + + while(true){ // we repeat if the bitstream is chained + int eos=0; + + // grab some data at the head of the stream. We want the first page + // (which is guaranteed to be small and only contain the Vorbis + // stream initial header) We need the first page to get the stream + // serialno. + + // submit a 4k block to libvorbis' Ogg layer + int index=oy.buffer(4096); + buffer=oy.data; + try{ + bytes=input.read(buffer, index, 4096); + } + catch(Exception e){ + System.err.println(e); + System.exit(-1); + } + oy.wrote(bytes); + + // Get the first page. + if(oy.pageout(og)!=1){ + // have we simply run out of data? If so, we're done. + if(bytes<4096) + break; + + // error case. Must not be Vorbis data + System.err.println("Input does not appear to be an Ogg bitstream."); + System.exit(1); + } + + // Get the serial number and set up the rest of decode. + // serialno first; use it to set up a logical stream + os.init(og.serialno()); + + // extract the initial header from the first page and verify that the + // Ogg bitstream is in fact Vorbis data + + // I handle the initial header first instead of just having the code + // read all three Vorbis headers at once because reading the initial + // header is an easy way to identify a Vorbis bitstream and it's + // useful to see that functionality seperated out. + + vi.init(); + vc.init(); + if(os.pagein(og)<0){ + // error; stream version mismatch perhaps + System.err.println("Error reading first page of Ogg bitstream data."); + System.exit(1); + } + + if(os.packetout(op)!=1){ + // no page? must not be vorbis + System.err.println("Error reading initial header packet."); + System.exit(1); + } + + if(vi.synthesis_headerin(vc, op)<0){ + // error case; not a vorbis header + System.err + .println("This Ogg bitstream does not contain Vorbis audio data."); + System.exit(1); + } + + // At this point, we're sure we're Vorbis. We've set up the logical + // (Ogg) bitstream decoder. Get the comment and codebook headers and + // set up the Vorbis decoder + + // The next two packets in order are the comment and codebook headers. + // They're likely large and may span multiple pages. Thus we reead + // and submit data until we get our two pacakets, watching that no + // pages are missing. If a page is missing, error out; losing a + // header page is the only place where missing data is fatal. */ + + int i=0; + while(i<2){ + while(i<2){ + + int result=oy.pageout(og); + if(result==0) + break; // Need more data + // Don't complain about missing or corrupt data yet. We'll + // catch it at the packet output phase + + if(result==1){ + os.pagein(og); // we can ignore any errors here + // as they'll also become apparent + // at packetout + while(i<2){ + result=os.packetout(op); + if(result==0) + break; + if(result==-1){ + // Uh oh; data at some point was corrupted or missing! + // We can't tolerate that in a header. Die. + System.err.println("Corrupt secondary header. Exiting."); + System.exit(1); + } + vi.synthesis_headerin(vc, op); + i++; + } + } + } + // no harm in not checking before adding more + index=oy.buffer(4096); + buffer=oy.data; + try{ + bytes=input.read(buffer, index, 4096); + } + catch(Exception e){ + System.err.println(e); + System.exit(1); + } + if(bytes==0&&i<2){ + System.err.println("End of file before finding all Vorbis headers!"); + System.exit(1); + } + oy.wrote(bytes); + } + + // Throw the comments plus a few lines about the bitstream we're + // decoding + { + byte[][] ptr=vc.user_comments; + for(int j=0; j<ptr.length; j++){ + if(ptr[j]==null) + break; + System.err.println(new String(ptr[j], 0, ptr[j].length-1)); + } + System.err.println("\nBitstream is "+vi.channels+" channel, "+vi.rate + +"Hz"); + System.err.println("Encoded by: " + +new String(vc.vendor, 0, vc.vendor.length-1)+"\n"); + } + + convsize=4096/vi.channels; + + // OK, got and parsed all three headers. Initialize the Vorbis + // packet->PCM decoder. + vd.synthesis_init(vi); // central decode state + vb.init(vd); // local state for most of the decode + // so multiple block decodes can + // proceed in parallel. We could init + // multiple vorbis_block structures + // for vd here + + float[][][] _pcm=new float[1][][]; + int[] _index=new int[vi.channels]; + // The rest is just a straight decode loop until end of stream + while(eos==0){ + while(eos==0){ + + int result=oy.pageout(og); + if(result==0) + break; // need more data + if(result==-1){ // missing or corrupt data at this page position + System.err + .println("Corrupt or missing data in bitstream; continuing..."); + } + else{ + os.pagein(og); // can safely ignore errors at + // this point + while(true){ + result=os.packetout(op); + + if(result==0) + break; // need more data + if(result==-1){ // missing or corrupt data at this page position + // no reason to complain; already complained above + } + else{ + // we have a packet. Decode it + int samples; + if(vb.synthesis(op)==0){ // test for success! + vd.synthesis_blockin(vb); + } + + // **pcm is a multichannel float vector. In stereo, for + // example, pcm[0] is left, and pcm[1] is right. samples is + // the size of each channel. Convert the float values + // (-1.<=range<=1.) to whatever PCM format and write it out + + while((samples=vd.synthesis_pcmout(_pcm, _index))>0){ + float[][] pcm=_pcm[0]; + int bout=(samples<convsize ? samples : convsize); + + // convert floats to 16 bit signed ints (host order) and + // interleave + for(i=0; i<vi.channels; i++){ + int ptr=i*2; + //int ptr=i; + int mono=_index[i]; + for(int j=0; j<bout; j++){ + int val=(int)(pcm[i][mono+j]*32767.); + // short val=(short)(pcm[i][mono+j]*32767.); + // int val=(int)Math.round(pcm[i][mono+j]*32767.); + // might as well guard against clipping + if(val>32767){ + val=32767; + } + if(val<-32768){ + val=-32768; + } + if(val<0) + val=val|0x8000; + convbuffer[ptr]=(byte)(val); + convbuffer[ptr+1]=(byte)(val>>>8); + ptr+=2*(vi.channels); + } + } + + System.out.write(convbuffer, 0, 2*vi.channels*bout); + + // tell libvorbis how + // many samples we + // actually consumed + vd.synthesis_read(bout); + } + } + } + if(og.eos()!=0) + eos=1; + } + } + if(eos==0){ + index=oy.buffer(4096); + buffer=oy.data; + try{ + bytes=input.read(buffer, index, 4096); + } + catch(Exception e){ + System.err.println(e); + System.exit(1); + } + oy.wrote(bytes); + if(bytes==0) + eos=1; + } + } + + // clean up this logical bitstream; before exit we see if we're + // followed by another [chained] + + os.clear(); + + // ogg_page and ogg_packet structs always point to storage in + // libvorbis. They're never freed or manipulated directly + + vb.clear(); + vd.clear(); + vi.clear(); // must be called last + } + + // OK, clean up the framer + oy.clear(); + System.err.println("Done."); + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Drft.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Drft.java new file mode 100644 index 0000000..3aaad34 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Drft.java @@ -0,0 +1,1327 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Drft{ + int n; + float[] trigcache; + int[] splitcache; + + void backward(float[] data){ + if(n==1) + return; + drftb1(n, data, trigcache, trigcache, n, splitcache); + } + + void init(int n){ + this.n=n; + trigcache=new float[3*n]; + splitcache=new int[32]; + fdrffti(n, trigcache, splitcache); + } + + void clear(){ + if(trigcache!=null) + trigcache=null; + if(splitcache!=null) + splitcache=null; + } + + static int[] ntryh= {4, 2, 3, 5}; + static float tpi=6.28318530717958647692528676655900577f; + static float hsqt2=.70710678118654752440084436210485f; + static float taui=.86602540378443864676372317075293618f; + static float taur=-.5f; + static float sqrt2=1.4142135623730950488016887242097f; + + static void drfti1(int n, float[] wa, int index, int[] ifac){ + float arg, argh, argld, fi; + int ntry=0, i, j=-1; + int k1, l1, l2, ib; + int ld, ii, ip, is, nq, nr; + int ido, ipm, nfm1; + int nl=n; + int nf=0; + + int state=101; + + loop: while(true){ + switch(state){ + case 101: + j++; + if(j<4) + ntry=ntryh[j]; + else + ntry+=2; + case 104: + nq=nl/ntry; + nr=nl-ntry*nq; + if(nr!=0){ + state=101; + break; + } + nf++; + ifac[nf+1]=ntry; + nl=nq; + if(ntry!=2){ + state=107; + break; + } + if(nf==1){ + state=107; + break; + } + + for(i=1; i<nf; i++){ + ib=nf-i+1; + ifac[ib+1]=ifac[ib]; + } + ifac[2]=2; + case 107: + if(nl!=1){ + state=104; + break; + } + ifac[0]=n; + ifac[1]=nf; + argh=tpi/n; + is=0; + nfm1=nf-1; + l1=1; + + if(nfm1==0) + return; + + for(k1=0; k1<nfm1; k1++){ + ip=ifac[k1+2]; + ld=0; + l2=l1*ip; + ido=n/l2; + ipm=ip-1; + + for(j=0; j<ipm; j++){ + ld+=l1; + i=is; + argld=(float)ld*argh; + fi=0.f; + for(ii=2; ii<ido; ii+=2){ + fi+=1.f; + arg=fi*argld; + wa[index+i++]=(float)Math.cos(arg); + wa[index+i++]=(float)Math.sin(arg); + } + is+=ido; + } + l1=l2; + } + break loop; + } + } + } + + static void fdrffti(int n, float[] wsave, int[] ifac){ + if(n==1) + return; + drfti1(n, wsave, n, ifac); + } + + static void dradf2(int ido, int l1, float[] cc, float[] ch, float[] wa1, + int index){ + int i, k; + float ti2, tr2; + int t0, t1, t2, t3, t4, t5, t6; + + t1=0; + t0=(t2=l1*ido); + t3=ido<<1; + for(k=0; k<l1; k++){ + ch[t1<<1]=cc[t1]+cc[t2]; + ch[(t1<<1)+t3-1]=cc[t1]-cc[t2]; + t1+=ido; + t2+=ido; + } + + if(ido<2) + return; + + if(ido!=2){ + t1=0; + t2=t0; + for(k=0; k<l1; k++){ + t3=t2; + t4=(t1<<1)+(ido<<1); + t5=t1; + t6=t1+t1; + for(i=2; i<ido; i+=2){ + t3+=2; + t4-=2; + t5+=2; + t6+=2; + tr2=wa1[index+i-2]*cc[t3-1]+wa1[index+i-1]*cc[t3]; + ti2=wa1[index+i-2]*cc[t3]-wa1[index+i-1]*cc[t3-1]; + ch[t6]=cc[t5]+ti2; + ch[t4]=ti2-cc[t5]; + ch[t6-1]=cc[t5-1]+tr2; + ch[t4-1]=cc[t5-1]-tr2; + } + t1+=ido; + t2+=ido; + } + if(ido%2==1) + return; + } + + t3=(t2=(t1=ido)-1); + t2+=t0; + for(k=0; k<l1; k++){ + ch[t1]=-cc[t2]; + ch[t1-1]=cc[t3]; + t1+=ido<<1; + t2+=ido; + t3+=ido; + } + } + + static void dradf4(int ido, int l1, float[] cc, float[] ch, float[] wa1, + int index1, float[] wa2, int index2, float[] wa3, int index3){ + int i, k, t0, t1, t2, t3, t4, t5, t6; + float ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; + t0=l1*ido; + + t1=t0; + t4=t1<<1; + t2=t1+(t1<<1); + t3=0; + + for(k=0; k<l1; k++){ + tr1=cc[t1]+cc[t2]; + tr2=cc[t3]+cc[t4]; + + ch[t5=t3<<2]=tr1+tr2; + ch[(ido<<2)+t5-1]=tr2-tr1; + ch[(t5+=(ido<<1))-1]=cc[t3]-cc[t4]; + ch[t5]=cc[t2]-cc[t1]; + + t1+=ido; + t2+=ido; + t3+=ido; + t4+=ido; + } + if(ido<2) + return; + + if(ido!=2){ + t1=0; + for(k=0; k<l1; k++){ + t2=t1; + t4=t1<<2; + t5=(t6=ido<<1)+t4; + for(i=2; i<ido; i+=2){ + t3=(t2+=2); + t4+=2; + t5-=2; + + t3+=t0; + cr2=wa1[index1+i-2]*cc[t3-1]+wa1[index1+i-1]*cc[t3]; + ci2=wa1[index1+i-2]*cc[t3]-wa1[index1+i-1]*cc[t3-1]; + t3+=t0; + cr3=wa2[index2+i-2]*cc[t3-1]+wa2[index2+i-1]*cc[t3]; + ci3=wa2[index2+i-2]*cc[t3]-wa2[index2+i-1]*cc[t3-1]; + t3+=t0; + cr4=wa3[index3+i-2]*cc[t3-1]+wa3[index3+i-1]*cc[t3]; + ci4=wa3[index3+i-2]*cc[t3]-wa3[index3+i-1]*cc[t3-1]; + + tr1=cr2+cr4; + tr4=cr4-cr2; + ti1=ci2+ci4; + ti4=ci2-ci4; + + ti2=cc[t2]+ci3; + ti3=cc[t2]-ci3; + tr2=cc[t2-1]+cr3; + tr3=cc[t2-1]-cr3; + + ch[t4-1]=tr1+tr2; + ch[t4]=ti1+ti2; + + ch[t5-1]=tr3-ti4; + ch[t5]=tr4-ti3; + + ch[t4+t6-1]=ti4+tr3; + ch[t4+t6]=tr4+ti3; + + ch[t5+t6-1]=tr2-tr1; + ch[t5+t6]=ti1-ti2; + } + t1+=ido; + } + if((ido&1)!=0) + return; + } + + t2=(t1=t0+ido-1)+(t0<<1); + t3=ido<<2; + t4=ido; + t5=ido<<1; + t6=ido; + + for(k=0; k<l1; k++){ + ti1=-hsqt2*(cc[t1]+cc[t2]); + tr1=hsqt2*(cc[t1]-cc[t2]); + + ch[t4-1]=tr1+cc[t6-1]; + ch[t4+t5-1]=cc[t6-1]-tr1; + + ch[t4]=ti1-cc[t1+t0]; + ch[t4+t5]=ti1+cc[t1+t0]; + + t1+=ido; + t2+=ido; + t4+=t3; + t6+=ido; + } + } + + static void dradfg(int ido, int ip, int l1, int idl1, float[] cc, float[] c1, + float[] c2, float[] ch, float[] ch2, float[] wa, int index){ + int idij, ipph, i, j, k, l, ic, ik, is; + int t0, t1, t2=0, t3, t4, t5, t6, t7, t8, t9, t10; + float dc2, ai1, ai2, ar1, ar2, ds2; + int nbd; + float dcp=0, arg, dsp=0, ar1h, ar2h; + int idp2, ipp2; + + arg=tpi/(float)ip; + dcp=(float)Math.cos(arg); + dsp=(float)Math.sin(arg); + ipph=(ip+1)>>1; + ipp2=ip; + idp2=ido; + nbd=(ido-1)>>1; + t0=l1*ido; + t10=ip*ido; + + int state=100; + loop: while(true){ + switch(state){ + case 101: + if(ido==1){ + state=119; + break; + } + for(ik=0; ik<idl1; ik++) + ch2[ik]=c2[ik]; + + t1=0; + for(j=1; j<ip; j++){ + t1+=t0; + t2=t1; + for(k=0; k<l1; k++){ + ch[t2]=c1[t2]; + t2+=ido; + } + } + + is=-ido; + t1=0; + if(nbd>l1){ + for(j=1; j<ip; j++){ + t1+=t0; + is+=ido; + t2=-ido+t1; + for(k=0; k<l1; k++){ + idij=is-1; + t2+=ido; + t3=t2; + for(i=2; i<ido; i+=2){ + idij+=2; + t3+=2; + ch[t3-1]=wa[index+idij-1]*c1[t3-1]+wa[index+idij]*c1[t3]; + ch[t3]=wa[index+idij-1]*c1[t3]-wa[index+idij]*c1[t3-1]; + } + } + } + } + else{ + + for(j=1; j<ip; j++){ + is+=ido; + idij=is-1; + t1+=t0; + t2=t1; + for(i=2; i<ido; i+=2){ + idij+=2; + t2+=2; + t3=t2; + for(k=0; k<l1; k++){ + ch[t3-1]=wa[index+idij-1]*c1[t3-1]+wa[index+idij]*c1[t3]; + ch[t3]=wa[index+idij-1]*c1[t3]-wa[index+idij]*c1[t3-1]; + t3+=ido; + } + } + } + } + + t1=0; + t2=ipp2*t0; + if(nbd<l1){ + for(j=1; j<ipph; j++){ + t1+=t0; + t2-=t0; + t3=t1; + t4=t2; + for(i=2; i<ido; i+=2){ + t3+=2; + t4+=2; + t5=t3-ido; + t6=t4-ido; + for(k=0; k<l1; k++){ + t5+=ido; + t6+=ido; + c1[t5-1]=ch[t5-1]+ch[t6-1]; + c1[t6-1]=ch[t5]-ch[t6]; + c1[t5]=ch[t5]+ch[t6]; + c1[t6]=ch[t6-1]-ch[t5-1]; + } + } + } + } + else{ + for(j=1; j<ipph; j++){ + t1+=t0; + t2-=t0; + t3=t1; + t4=t2; + for(k=0; k<l1; k++){ + t5=t3; + t6=t4; + for(i=2; i<ido; i+=2){ + t5+=2; + t6+=2; + c1[t5-1]=ch[t5-1]+ch[t6-1]; + c1[t6-1]=ch[t5]-ch[t6]; + c1[t5]=ch[t5]+ch[t6]; + c1[t6]=ch[t6-1]-ch[t5-1]; + } + t3+=ido; + t4+=ido; + } + } + } + case 119: + for(ik=0; ik<idl1; ik++) + c2[ik]=ch2[ik]; + + t1=0; + t2=ipp2*idl1; + for(j=1; j<ipph; j++){ + t1+=t0; + t2-=t0; + t3=t1-ido; + t4=t2-ido; + for(k=0; k<l1; k++){ + t3+=ido; + t4+=ido; + c1[t3]=ch[t3]+ch[t4]; + c1[t4]=ch[t4]-ch[t3]; + } + } + + ar1=1.f; + ai1=0.f; + t1=0; + t2=ipp2*idl1; + t3=(ip-1)*idl1; + for(l=1; l<ipph; l++){ + t1+=idl1; + t2-=idl1; + ar1h=dcp*ar1-dsp*ai1; + ai1=dcp*ai1+dsp*ar1; + ar1=ar1h; + t4=t1; + t5=t2; + t6=t3; + t7=idl1; + + for(ik=0; ik<idl1; ik++){ + ch2[t4++]=c2[ik]+ar1*c2[t7++]; + ch2[t5++]=ai1*c2[t6++]; + } + + dc2=ar1; + ds2=ai1; + ar2=ar1; + ai2=ai1; + + t4=idl1; + t5=(ipp2-1)*idl1; + for(j=2; j<ipph; j++){ + t4+=idl1; + t5-=idl1; + + ar2h=dc2*ar2-ds2*ai2; + ai2=dc2*ai2+ds2*ar2; + ar2=ar2h; + + t6=t1; + t7=t2; + t8=t4; + t9=t5; + for(ik=0; ik<idl1; ik++){ + ch2[t6++]+=ar2*c2[t8++]; + ch2[t7++]+=ai2*c2[t9++]; + } + } + } + t1=0; + for(j=1; j<ipph; j++){ + t1+=idl1; + t2=t1; + for(ik=0; ik<idl1; ik++) + ch2[ik]+=c2[t2++]; + } + + if(ido<l1){ + state=132; + break; + } + + t1=0; + t2=0; + for(k=0; k<l1; k++){ + t3=t1; + t4=t2; + for(i=0; i<ido; i++) + cc[t4++]=ch[t3++]; + t1+=ido; + t2+=t10; + } + state=135; + break; + + case 132: + for(i=0; i<ido; i++){ + t1=i; + t2=i; + for(k=0; k<l1; k++){ + cc[t2]=ch[t1]; + t1+=ido; + t2+=t10; + } + } + case 135: + t1=0; + t2=ido<<1; + t3=0; + t4=ipp2*t0; + for(j=1; j<ipph; j++){ + t1+=t2; + t3+=t0; + t4-=t0; + + t5=t1; + t6=t3; + t7=t4; + + for(k=0; k<l1; k++){ + cc[t5-1]=ch[t6]; + cc[t5]=ch[t7]; + t5+=t10; + t6+=ido; + t7+=ido; + } + } + + if(ido==1) + return; + if(nbd<l1){ + state=141; + break; + } + + t1=-ido; + t3=0; + t4=0; + t5=ipp2*t0; + for(j=1; j<ipph; j++){ + t1+=t2; + t3+=t2; + t4+=t0; + t5-=t0; + t6=t1; + t7=t3; + t8=t4; + t9=t5; + for(k=0; k<l1; k++){ + for(i=2; i<ido; i+=2){ + ic=idp2-i; + cc[i+t7-1]=ch[i+t8-1]+ch[i+t9-1]; + cc[ic+t6-1]=ch[i+t8-1]-ch[i+t9-1]; + cc[i+t7]=ch[i+t8]+ch[i+t9]; + cc[ic+t6]=ch[i+t9]-ch[i+t8]; + } + t6+=t10; + t7+=t10; + t8+=ido; + t9+=ido; + } + } + return; + case 141: + t1=-ido; + t3=0; + t4=0; + t5=ipp2*t0; + for(j=1; j<ipph; j++){ + t1+=t2; + t3+=t2; + t4+=t0; + t5-=t0; + for(i=2; i<ido; i+=2){ + t6=idp2+t1-i; + t7=i+t3; + t8=i+t4; + t9=i+t5; + for(k=0; k<l1; k++){ + cc[t7-1]=ch[t8-1]+ch[t9-1]; + cc[t6-1]=ch[t8-1]-ch[t9-1]; + cc[t7]=ch[t8]+ch[t9]; + cc[t6]=ch[t9]-ch[t8]; + t6+=t10; + t7+=t10; + t8+=ido; + t9+=ido; + } + } + } + break loop; + } + } + } + + static void drftf1(int n, float[] c, float[] ch, float[] wa, int[] ifac){ + int i, k1, l1, l2; + int na, kh, nf; + int ip, iw, ido, idl1, ix2, ix3; + + nf=ifac[1]; + na=1; + l2=n; + iw=n; + + for(k1=0; k1<nf; k1++){ + kh=nf-k1; + ip=ifac[kh+1]; + l1=l2/ip; + ido=n/l2; + idl1=ido*l1; + iw-=(ip-1)*ido; + na=1-na; + + int state=100; + loop: while(true){ + switch(state){ + case 100: + if(ip!=4){ + state=102; + break; + } + + ix2=iw+ido; + ix3=ix2+ido; + if(na!=0) + dradf4(ido, l1, ch, c, wa, iw-1, wa, ix2-1, wa, ix3-1); + else + dradf4(ido, l1, c, ch, wa, iw-1, wa, ix2-1, wa, ix3-1); + state=110; + break; + case 102: + if(ip!=2){ + state=104; + break; + } + if(na!=0){ + state=103; + break; + } + dradf2(ido, l1, c, ch, wa, iw-1); + state=110; + break; + case 103: + dradf2(ido, l1, ch, c, wa, iw-1); + case 104: + if(ido==1) + na=1-na; + if(na!=0){ + state=109; + break; + } + dradfg(ido, ip, l1, idl1, c, c, c, ch, ch, wa, iw-1); + na=1; + state=110; + break; + case 109: + dradfg(ido, ip, l1, idl1, ch, ch, ch, c, c, wa, iw-1); + na=0; + case 110: + l2=l1; + break loop; + } + } + } + if(na==1) + return; + for(i=0; i<n; i++) + c[i]=ch[i]; + } + + static void dradb2(int ido, int l1, float[] cc, float[] ch, float[] wa1, + int index){ + int i, k, t0, t1, t2, t3, t4, t5, t6; + float ti2, tr2; + + t0=l1*ido; + + t1=0; + t2=0; + t3=(ido<<1)-1; + for(k=0; k<l1; k++){ + ch[t1]=cc[t2]+cc[t3+t2]; + ch[t1+t0]=cc[t2]-cc[t3+t2]; + t2=(t1+=ido)<<1; + } + + if(ido<2) + return; + if(ido!=2){ + t1=0; + t2=0; + for(k=0; k<l1; k++){ + t3=t1; + t5=(t4=t2)+(ido<<1); + t6=t0+t1; + for(i=2; i<ido; i+=2){ + t3+=2; + t4+=2; + t5-=2; + t6+=2; + ch[t3-1]=cc[t4-1]+cc[t5-1]; + tr2=cc[t4-1]-cc[t5-1]; + ch[t3]=cc[t4]-cc[t5]; + ti2=cc[t4]+cc[t5]; + ch[t6-1]=wa1[index+i-2]*tr2-wa1[index+i-1]*ti2; + ch[t6]=wa1[index+i-2]*ti2+wa1[index+i-1]*tr2; + } + t2=(t1+=ido)<<1; + } + if((ido%2)==1) + return; + } + + t1=ido-1; + t2=ido-1; + for(k=0; k<l1; k++){ + ch[t1]=cc[t2]+cc[t2]; + ch[t1+t0]=-(cc[t2+1]+cc[t2+1]); + t1+=ido; + t2+=ido<<1; + } + } + + static void dradb3(int ido, int l1, float[] cc, float[] ch, float[] wa1, + int index1, float[] wa2, int index2){ + int i, k, t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10; + float ci2, ci3, di2, di3, cr2, cr3, dr2, dr3, ti2, tr2; + t0=l1*ido; + + t1=0; + t2=t0<<1; + t3=ido<<1; + t4=ido+(ido<<1); + t5=0; + for(k=0; k<l1; k++){ + tr2=cc[t3-1]+cc[t3-1]; + cr2=cc[t5]+(taur*tr2); + ch[t1]=cc[t5]+tr2; + ci3=taui*(cc[t3]+cc[t3]); + ch[t1+t0]=cr2-ci3; + ch[t1+t2]=cr2+ci3; + t1+=ido; + t3+=t4; + t5+=t4; + } + + if(ido==1) + return; + + t1=0; + t3=ido<<1; + for(k=0; k<l1; k++){ + t7=t1+(t1<<1); + t6=(t5=t7+t3); + t8=t1; + t10=(t9=t1+t0)+t0; + + for(i=2; i<ido; i+=2){ + t5+=2; + t6-=2; + t7+=2; + t8+=2; + t9+=2; + t10+=2; + tr2=cc[t5-1]+cc[t6-1]; + cr2=cc[t7-1]+(taur*tr2); + ch[t8-1]=cc[t7-1]+tr2; + ti2=cc[t5]-cc[t6]; + ci2=cc[t7]+(taur*ti2); + ch[t8]=cc[t7]+ti2; + cr3=taui*(cc[t5-1]-cc[t6-1]); + ci3=taui*(cc[t5]+cc[t6]); + dr2=cr2-ci3; + dr3=cr2+ci3; + di2=ci2+cr3; + di3=ci2-cr3; + ch[t9-1]=wa1[index1+i-2]*dr2-wa1[index1+i-1]*di2; + ch[t9]=wa1[index1+i-2]*di2+wa1[index1+i-1]*dr2; + ch[t10-1]=wa2[index2+i-2]*dr3-wa2[index2+i-1]*di3; + ch[t10]=wa2[index2+i-2]*di3+wa2[index2+i-1]*dr3; + } + t1+=ido; + } + } + + static void dradb4(int ido, int l1, float[] cc, float[] ch, float[] wa1, + int index1, float[] wa2, int index2, float[] wa3, int index3){ + int i, k, t0, t1, t2, t3, t4, t5, t6, t7, t8; + float ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; + t0=l1*ido; + + t1=0; + t2=ido<<2; + t3=0; + t6=ido<<1; + for(k=0; k<l1; k++){ + t4=t3+t6; + t5=t1; + tr3=cc[t4-1]+cc[t4-1]; + tr4=cc[t4]+cc[t4]; + tr1=cc[t3]-cc[(t4+=t6)-1]; + tr2=cc[t3]+cc[t4-1]; + ch[t5]=tr2+tr3; + ch[t5+=t0]=tr1-tr4; + ch[t5+=t0]=tr2-tr3; + ch[t5+=t0]=tr1+tr4; + t1+=ido; + t3+=t2; + } + + if(ido<2) + return; + if(ido!=2){ + t1=0; + for(k=0; k<l1; k++){ + t5=(t4=(t3=(t2=t1<<2)+t6))+t6; + t7=t1; + for(i=2; i<ido; i+=2){ + t2+=2; + t3+=2; + t4-=2; + t5-=2; + t7+=2; + ti1=cc[t2]+cc[t5]; + ti2=cc[t2]-cc[t5]; + ti3=cc[t3]-cc[t4]; + tr4=cc[t3]+cc[t4]; + tr1=cc[t2-1]-cc[t5-1]; + tr2=cc[t2-1]+cc[t5-1]; + ti4=cc[t3-1]-cc[t4-1]; + tr3=cc[t3-1]+cc[t4-1]; + ch[t7-1]=tr2+tr3; + cr3=tr2-tr3; + ch[t7]=ti2+ti3; + ci3=ti2-ti3; + cr2=tr1-tr4; + cr4=tr1+tr4; + ci2=ti1+ti4; + ci4=ti1-ti4; + + ch[(t8=t7+t0)-1]=wa1[index1+i-2]*cr2-wa1[index1+i-1]*ci2; + ch[t8]=wa1[index1+i-2]*ci2+wa1[index1+i-1]*cr2; + ch[(t8+=t0)-1]=wa2[index2+i-2]*cr3-wa2[index2+i-1]*ci3; + ch[t8]=wa2[index2+i-2]*ci3+wa2[index2+i-1]*cr3; + ch[(t8+=t0)-1]=wa3[index3+i-2]*cr4-wa3[index3+i-1]*ci4; + ch[t8]=wa3[index3+i-2]*ci4+wa3[index3+i-1]*cr4; + } + t1+=ido; + } + if(ido%2==1) + return; + } + + t1=ido; + t2=ido<<2; + t3=ido-1; + t4=ido+(ido<<1); + for(k=0; k<l1; k++){ + t5=t3; + ti1=cc[t1]+cc[t4]; + ti2=cc[t4]-cc[t1]; + tr1=cc[t1-1]-cc[t4-1]; + tr2=cc[t1-1]+cc[t4-1]; + ch[t5]=tr2+tr2; + ch[t5+=t0]=sqrt2*(tr1-ti1); + ch[t5+=t0]=ti2+ti2; + ch[t5+=t0]=-sqrt2*(tr1+ti1); + + t3+=ido; + t1+=t2; + t4+=t2; + } + } + + static void dradbg(int ido, int ip, int l1, int idl1, float[] cc, float[] c1, + float[] c2, float[] ch, float[] ch2, float[] wa, int index){ + + int idij, ipph=0, i, j, k, l, ik, is, t0=0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10=0, t11, t12; + float dc2, ai1, ai2, ar1, ar2, ds2; + int nbd=0; + float dcp=0, arg, dsp=0, ar1h, ar2h; + int ipp2=0; + + int state=100; + + loop: while(true){ + switch(state){ + case 100: + t10=ip*ido; + t0=l1*ido; + arg=tpi/(float)ip; + dcp=(float)Math.cos(arg); + dsp=(float)Math.sin(arg); + nbd=(ido-1)>>>1; + ipp2=ip; + ipph=(ip+1)>>>1; + if(ido<l1){ + state=103; + break; + } + t1=0; + t2=0; + for(k=0; k<l1; k++){ + t3=t1; + t4=t2; + for(i=0; i<ido; i++){ + ch[t3]=cc[t4]; + t3++; + t4++; + } + t1+=ido; + t2+=t10; + } + state=106; + break; + case 103: + t1=0; + for(i=0; i<ido; i++){ + t2=t1; + t3=t1; + for(k=0; k<l1; k++){ + ch[t2]=cc[t3]; + t2+=ido; + t3+=t10; + } + t1++; + } + case 106: + t1=0; + t2=ipp2*t0; + t7=(t5=ido<<1); + for(j=1; j<ipph; j++){ + t1+=t0; + t2-=t0; + t3=t1; + t4=t2; + t6=t5; + for(k=0; k<l1; k++){ + ch[t3]=cc[t6-1]+cc[t6-1]; + ch[t4]=cc[t6]+cc[t6]; + t3+=ido; + t4+=ido; + t6+=t10; + } + t5+=t7; + } + if(ido==1){ + state=116; + break; + } + if(nbd<l1){ + state=112; + break; + } + + t1=0; + t2=ipp2*t0; + t7=0; + for(j=1; j<ipph; j++){ + t1+=t0; + t2-=t0; + t3=t1; + t4=t2; + + t7+=(ido<<1); + t8=t7; + for(k=0; k<l1; k++){ + t5=t3; + t6=t4; + t9=t8; + t11=t8; + for(i=2; i<ido; i+=2){ + t5+=2; + t6+=2; + t9+=2; + t11-=2; + ch[t5-1]=cc[t9-1]+cc[t11-1]; + ch[t6-1]=cc[t9-1]-cc[t11-1]; + ch[t5]=cc[t9]-cc[t11]; + ch[t6]=cc[t9]+cc[t11]; + } + t3+=ido; + t4+=ido; + t8+=t10; + } + } + state=116; + break; + case 112: + t1=0; + t2=ipp2*t0; + t7=0; + for(j=1; j<ipph; j++){ + t1+=t0; + t2-=t0; + t3=t1; + t4=t2; + t7+=(ido<<1); + t8=t7; + t9=t7; + for(i=2; i<ido; i+=2){ + t3+=2; + t4+=2; + t8+=2; + t9-=2; + t5=t3; + t6=t4; + t11=t8; + t12=t9; + for(k=0; k<l1; k++){ + ch[t5-1]=cc[t11-1]+cc[t12-1]; + ch[t6-1]=cc[t11-1]-cc[t12-1]; + ch[t5]=cc[t11]-cc[t12]; + ch[t6]=cc[t11]+cc[t12]; + t5+=ido; + t6+=ido; + t11+=t10; + t12+=t10; + } + } + } + case 116: + ar1=1.f; + ai1=0.f; + t1=0; + t9=(t2=ipp2*idl1); + t3=(ip-1)*idl1; + for(l=1; l<ipph; l++){ + t1+=idl1; + t2-=idl1; + + ar1h=dcp*ar1-dsp*ai1; + ai1=dcp*ai1+dsp*ar1; + ar1=ar1h; + t4=t1; + t5=t2; + t6=0; + t7=idl1; + t8=t3; + for(ik=0; ik<idl1; ik++){ + c2[t4++]=ch2[t6++]+ar1*ch2[t7++]; + c2[t5++]=ai1*ch2[t8++]; + } + dc2=ar1; + ds2=ai1; + ar2=ar1; + ai2=ai1; + + t6=idl1; + t7=t9-idl1; + for(j=2; j<ipph; j++){ + t6+=idl1; + t7-=idl1; + ar2h=dc2*ar2-ds2*ai2; + ai2=dc2*ai2+ds2*ar2; + ar2=ar2h; + t4=t1; + t5=t2; + t11=t6; + t12=t7; + for(ik=0; ik<idl1; ik++){ + c2[t4++]+=ar2*ch2[t11++]; + c2[t5++]+=ai2*ch2[t12++]; + } + } + } + + t1=0; + for(j=1; j<ipph; j++){ + t1+=idl1; + t2=t1; + for(ik=0; ik<idl1; ik++) + ch2[ik]+=ch2[t2++]; + } + + t1=0; + t2=ipp2*t0; + for(j=1; j<ipph; j++){ + t1+=t0; + t2-=t0; + t3=t1; + t4=t2; + for(k=0; k<l1; k++){ + ch[t3]=c1[t3]-c1[t4]; + ch[t4]=c1[t3]+c1[t4]; + t3+=ido; + t4+=ido; + } + } + + if(ido==1){ + state=132; + break; + } + if(nbd<l1){ + state=128; + break; + } + + t1=0; + t2=ipp2*t0; + for(j=1; j<ipph; j++){ + t1+=t0; + t2-=t0; + t3=t1; + t4=t2; + for(k=0; k<l1; k++){ + t5=t3; + t6=t4; + for(i=2; i<ido; i+=2){ + t5+=2; + t6+=2; + ch[t5-1]=c1[t5-1]-c1[t6]; + ch[t6-1]=c1[t5-1]+c1[t6]; + ch[t5]=c1[t5]+c1[t6-1]; + ch[t6]=c1[t5]-c1[t6-1]; + } + t3+=ido; + t4+=ido; + } + } + state=132; + break; + case 128: + t1=0; + t2=ipp2*t0; + for(j=1; j<ipph; j++){ + t1+=t0; + t2-=t0; + t3=t1; + t4=t2; + for(i=2; i<ido; i+=2){ + t3+=2; + t4+=2; + t5=t3; + t6=t4; + for(k=0; k<l1; k++){ + ch[t5-1]=c1[t5-1]-c1[t6]; + ch[t6-1]=c1[t5-1]+c1[t6]; + ch[t5]=c1[t5]+c1[t6-1]; + ch[t6]=c1[t5]-c1[t6-1]; + t5+=ido; + t6+=ido; + } + } + } + case 132: + if(ido==1) + return; + + for(ik=0; ik<idl1; ik++) + c2[ik]=ch2[ik]; + + t1=0; + for(j=1; j<ip; j++){ + t2=(t1+=t0); + for(k=0; k<l1; k++){ + c1[t2]=ch[t2]; + t2+=ido; + } + } + + if(nbd>l1){ + state=139; + break; + } + + is=-ido-1; + t1=0; + for(j=1; j<ip; j++){ + is+=ido; + t1+=t0; + idij=is; + t2=t1; + for(i=2; i<ido; i+=2){ + t2+=2; + idij+=2; + t3=t2; + for(k=0; k<l1; k++){ + c1[t3-1]=wa[index+idij-1]*ch[t3-1]-wa[index+idij]*ch[t3]; + c1[t3]=wa[index+idij-1]*ch[t3]+wa[index+idij]*ch[t3-1]; + t3+=ido; + } + } + } + return; + + case 139: + is=-ido-1; + t1=0; + for(j=1; j<ip; j++){ + is+=ido; + t1+=t0; + t2=t1; + for(k=0; k<l1; k++){ + idij=is; + t3=t2; + for(i=2; i<ido; i+=2){ + idij+=2; + t3+=2; + c1[t3-1]=wa[index+idij-1]*ch[t3-1]-wa[index+idij]*ch[t3]; + c1[t3]=wa[index+idij-1]*ch[t3]+wa[index+idij]*ch[t3-1]; + } + t2+=ido; + } + } + break loop; + } + } + } + + static void drftb1(int n, float[] c, float[] ch, float[] wa, int index, + int[] ifac){ + int i, k1, l1, l2=0; + int na; + int nf, ip=0, iw, ix2, ix3, ido=0, idl1=0; + + nf=ifac[1]; + na=0; + l1=1; + iw=1; + + for(k1=0; k1<nf; k1++){ + int state=100; + loop: while(true){ + switch(state){ + case 100: + ip=ifac[k1+2]; + l2=ip*l1; + ido=n/l2; + idl1=ido*l1; + if(ip!=4){ + state=103; + break; + } + ix2=iw+ido; + ix3=ix2+ido; + + if(na!=0) + dradb4(ido, l1, ch, c, wa, index+iw-1, wa, index+ix2-1, wa, index + +ix3-1); + else + dradb4(ido, l1, c, ch, wa, index+iw-1, wa, index+ix2-1, wa, index + +ix3-1); + na=1-na; + state=115; + break; + case 103: + if(ip!=2){ + state=106; + break; + } + + if(na!=0) + dradb2(ido, l1, ch, c, wa, index+iw-1); + else + dradb2(ido, l1, c, ch, wa, index+iw-1); + na=1-na; + state=115; + break; + + case 106: + if(ip!=3){ + state=109; + break; + } + + ix2=iw+ido; + if(na!=0) + dradb3(ido, l1, ch, c, wa, index+iw-1, wa, index+ix2-1); + else + dradb3(ido, l1, c, ch, wa, index+iw-1, wa, index+ix2-1); + na=1-na; + state=115; + break; + case 109: + if(na!=0) + dradbg(ido, ip, l1, idl1, ch, ch, ch, c, c, wa, index+iw-1); + else + dradbg(ido, ip, l1, idl1, c, c, c, ch, ch, wa, index+iw-1); + if(ido==1) + na=1-na; + + case 115: + l1=l2; + iw+=(ip-1)*ido; + break loop; + } + } + } + if(na==0) + return; + for(i=0; i<n; i++) + c[i]=ch[i]; + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/DspState.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/DspState.java new file mode 100644 index 0000000..052482d --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/DspState.java @@ -0,0 +1,376 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +public class DspState{ + static final float M_PI=3.1415926539f; + static final int VI_TRANSFORMB=1; + static final int VI_WINDOWB=1; + + int analysisp; + Info vi; + int modebits; + + float[][] pcm; + int pcm_storage; + int pcm_current; + int pcm_returned; + + float[] multipliers; + int envelope_storage; + int envelope_current; + + int eofflag; + + int lW; + int W; + int nW; + int centerW; + + long granulepos; + long sequence; + + long glue_bits; + long time_bits; + long floor_bits; + long res_bits; + + // local lookup storage + float[][][][][] window; // block, leadin, leadout, type + Object[][] transform; + CodeBook[] fullbooks; + // backend lookups are tied to the mode, not the backend or naked mapping + Object[] mode; + + // local storage, only used on the encoding side. This way the + // application does not need to worry about freeing some packets' + // memory and not others'; packet storage is always tracked. + // Cleared next call to a _dsp_ function + byte[] header; + byte[] header1; + byte[] header2; + + public DspState(){ + transform=new Object[2][]; + window=new float[2][][][][]; + window[0]=new float[2][][][]; + window[0][0]=new float[2][][]; + window[0][1]=new float[2][][]; + window[0][0][0]=new float[2][]; + window[0][0][1]=new float[2][]; + window[0][1][0]=new float[2][]; + window[0][1][1]=new float[2][]; + window[1]=new float[2][][][]; + window[1][0]=new float[2][][]; + window[1][1]=new float[2][][]; + window[1][0][0]=new float[2][]; + window[1][0][1]=new float[2][]; + window[1][1][0]=new float[2][]; + window[1][1][1]=new float[2][]; + } + + static float[] window(int type, int window, int left, int right){ + float[] ret=new float[window]; + switch(type){ + case 0: + // The 'vorbis window' (window 0) is sin(sin(x)*sin(x)*2pi) + { + int leftbegin=window/4-left/2; + int rightbegin=window-window/4-right/2; + + for(int i=0; i<left; i++){ + float x=(float)((i+.5)/left*M_PI/2.); + x=(float)Math.sin(x); + x*=x; + x*=M_PI/2.; + x=(float)Math.sin(x); + ret[i+leftbegin]=x; + } + + for(int i=leftbegin+left; i<rightbegin; i++){ + ret[i]=1.f; + } + + for(int i=0; i<right; i++){ + float x=(float)((right-i-.5)/right*M_PI/2.); + x=(float)Math.sin(x); + x*=x; + x*=M_PI/2.; + x=(float)Math.sin(x); + ret[i+rightbegin]=x; + } + } + break; + default: + //free(ret); + return (null); + } + return (ret); + } + + // Analysis side code, but directly related to blocking. Thus it's + // here and not in analysis.c (which is for analysis transforms only). + // The init is here because some of it is shared + + int init(Info vi, boolean encp){ + this.vi=vi; + modebits=Util.ilog2(vi.modes); + + transform[0]=new Object[VI_TRANSFORMB]; + transform[1]=new Object[VI_TRANSFORMB]; + + // MDCT is tranform 0 + + transform[0][0]=new Mdct(); + transform[1][0]=new Mdct(); + ((Mdct)transform[0][0]).init(vi.blocksizes[0]); + ((Mdct)transform[1][0]).init(vi.blocksizes[1]); + + window[0][0][0]=new float[VI_WINDOWB][]; + window[0][0][1]=window[0][0][0]; + window[0][1][0]=window[0][0][0]; + window[0][1][1]=window[0][0][0]; + window[1][0][0]=new float[VI_WINDOWB][]; + window[1][0][1]=new float[VI_WINDOWB][]; + window[1][1][0]=new float[VI_WINDOWB][]; + window[1][1][1]=new float[VI_WINDOWB][]; + + for(int i=0; i<VI_WINDOWB; i++){ + window[0][0][0][i]=window(i, vi.blocksizes[0], vi.blocksizes[0]/2, + vi.blocksizes[0]/2); + window[1][0][0][i]=window(i, vi.blocksizes[1], vi.blocksizes[0]/2, + vi.blocksizes[0]/2); + window[1][0][1][i]=window(i, vi.blocksizes[1], vi.blocksizes[0]/2, + vi.blocksizes[1]/2); + window[1][1][0][i]=window(i, vi.blocksizes[1], vi.blocksizes[1]/2, + vi.blocksizes[0]/2); + window[1][1][1][i]=window(i, vi.blocksizes[1], vi.blocksizes[1]/2, + vi.blocksizes[1]/2); + } + + fullbooks=new CodeBook[vi.books]; + for(int i=0; i<vi.books; i++){ + fullbooks[i]=new CodeBook(); + fullbooks[i].init_decode(vi.book_param[i]); + } + + // initialize the storage vectors to a decent size greater than the + // minimum + + pcm_storage=8192; // we'll assume later that we have + // a minimum of twice the blocksize of + // accumulated samples in analysis + pcm=new float[vi.channels][]; + { + for(int i=0; i<vi.channels; i++){ + pcm[i]=new float[pcm_storage]; + } + } + + // all 1 (large block) or 0 (small block) + // explicitly set for the sake of clarity + lW=0; // previous window size + W=0; // current window size + + // all vector indexes; multiples of samples_per_envelope_step + centerW=vi.blocksizes[1]/2; + + pcm_current=centerW; + + // initialize all the mapping/backend lookups + mode=new Object[vi.modes]; + for(int i=0; i<vi.modes; i++){ + int mapnum=vi.mode_param[i].mapping; + int maptype=vi.map_type[mapnum]; + mode[i]=FuncMapping.mapping_P[maptype].look(this, vi.mode_param[i], + vi.map_param[mapnum]); + } + return (0); + } + + public int synthesis_init(Info vi){ + init(vi, false); + // Adjust centerW to allow an easier mechanism for determining output + pcm_returned=centerW; + centerW-=vi.blocksizes[W]/4+vi.blocksizes[lW]/4; + granulepos=-1; + sequence=-1; + return (0); + } + + DspState(Info vi){ + this(); + init(vi, false); + // Adjust centerW to allow an easier mechanism for determining output + pcm_returned=centerW; + centerW-=vi.blocksizes[W]/4+vi.blocksizes[lW]/4; + granulepos=-1; + sequence=-1; + } + + // Unike in analysis, the window is only partially applied for each + // block. The time domain envelope is not yet handled at the point of + // calling (as it relies on the previous block). + + public int synthesis_blockin(Block vb){ + // Shift out any PCM/multipliers that we returned previously + // centerW is currently the center of the last block added + if(centerW>vi.blocksizes[1]/2&&pcm_returned>8192){ + // don't shift too much; we need to have a minimum PCM buffer of + // 1/2 long block + + int shiftPCM=centerW-vi.blocksizes[1]/2; + shiftPCM=(pcm_returned<shiftPCM ? pcm_returned : shiftPCM); + + pcm_current-=shiftPCM; + centerW-=shiftPCM; + pcm_returned-=shiftPCM; + if(shiftPCM!=0){ + for(int i=0; i<vi.channels; i++){ + System.arraycopy(pcm[i], shiftPCM, pcm[i], 0, pcm_current); + } + } + } + + lW=W; + W=vb.W; + nW=-1; + + glue_bits+=vb.glue_bits; + time_bits+=vb.time_bits; + floor_bits+=vb.floor_bits; + res_bits+=vb.res_bits; + + if(sequence+1!=vb.sequence) + granulepos=-1; // out of sequence; lose count + + sequence=vb.sequence; + + { + int sizeW=vi.blocksizes[W]; + int _centerW=centerW+vi.blocksizes[lW]/4+sizeW/4; + int beginW=_centerW-sizeW/2; + int endW=beginW+sizeW; + int beginSl=0; + int endSl=0; + + // Do we have enough PCM/mult storage for the block? + if(endW>pcm_storage){ + // expand the storage + pcm_storage=endW+vi.blocksizes[1]; + for(int i=0; i<vi.channels; i++){ + float[] foo=new float[pcm_storage]; + System.arraycopy(pcm[i], 0, foo, 0, pcm[i].length); + pcm[i]=foo; + } + } + + // overlap/add PCM + switch(W){ + case 0: + beginSl=0; + endSl=vi.blocksizes[0]/2; + break; + case 1: + beginSl=vi.blocksizes[1]/4-vi.blocksizes[lW]/4; + endSl=beginSl+vi.blocksizes[lW]/2; + break; + } + + for(int j=0; j<vi.channels; j++){ + int _pcm=beginW; + // the overlap/add section + int i=0; + for(i=beginSl; i<endSl; i++){ + pcm[j][_pcm+i]+=vb.pcm[j][i]; + } + // the remaining section + for(; i<sizeW; i++){ + pcm[j][_pcm+i]=vb.pcm[j][i]; + } + } + + // track the frame number... This is for convenience, but also + // making sure our last packet doesn't end with added padding. If + // the last packet is partial, the number of samples we'll have to + // return will be past the vb->granulepos. + // + // This is not foolproof! It will be confused if we begin + // decoding at the last page after a seek or hole. In that case, + // we don't have a starting point to judge where the last frame + // is. For this reason, vorbisfile will always try to make sure + // it reads the last two marked pages in proper sequence + + if(granulepos==-1){ + granulepos=vb.granulepos; + } + else{ + granulepos+=(_centerW-centerW); + if(vb.granulepos!=-1&&granulepos!=vb.granulepos){ + if(granulepos>vb.granulepos&&vb.eofflag!=0){ + // partial last frame. Strip the padding off + _centerW-=(granulepos-vb.granulepos); + }// else{ Shouldn't happen *unless* the bitstream is out of + // spec. Either way, believe the bitstream } + granulepos=vb.granulepos; + } + } + + // Update, cleanup + + centerW=_centerW; + pcm_current=endW; + if(vb.eofflag!=0) + eofflag=1; + } + return (0); + } + + // pcm==NULL indicates we just want the pending samples, no more + public int synthesis_pcmout(float[][][] _pcm, int[] index){ + if(pcm_returned<centerW){ + if(_pcm!=null){ + for(int i=0; i<vi.channels; i++){ + index[i]=pcm_returned; + } + _pcm[0]=pcm; + } + return (centerW-pcm_returned); + } + return (0); + } + + public int synthesis_read(int bytes){ + if(bytes!=0&&pcm_returned+bytes>centerW) + return (-1); + pcm_returned+=bytes; + return (0); + } + + public void clear(){ + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Floor0.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Floor0.java new file mode 100644 index 0000000..02e8393 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Floor0.java @@ -0,0 +1,335 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Floor0 extends FuncFloor{ + + void pack(Object i, Buffer opb){ + InfoFloor0 info=(InfoFloor0)i; + opb.write(info.order, 8); + opb.write(info.rate, 16); + opb.write(info.barkmap, 16); + opb.write(info.ampbits, 6); + opb.write(info.ampdB, 8); + opb.write(info.numbooks-1, 4); + for(int j=0; j<info.numbooks; j++) + opb.write(info.books[j], 8); + } + + Object unpack(Info vi, Buffer opb){ + InfoFloor0 info=new InfoFloor0(); + info.order=opb.read(8); + info.rate=opb.read(16); + info.barkmap=opb.read(16); + info.ampbits=opb.read(6); + info.ampdB=opb.read(8); + info.numbooks=opb.read(4)+1; + + if((info.order<1)||(info.rate<1)||(info.barkmap<1)||(info.numbooks<1)){ + return (null); + } + + for(int j=0; j<info.numbooks; j++){ + info.books[j]=opb.read(8); + if(info.books[j]<0||info.books[j]>=vi.books){ + return (null); + } + } + return (info); + } + + Object look(DspState vd, InfoMode mi, Object i){ + float scale; + Info vi=vd.vi; + InfoFloor0 info=(InfoFloor0)i; + LookFloor0 look=new LookFloor0(); + look.m=info.order; + look.n=vi.blocksizes[mi.blockflag]/2; + look.ln=info.barkmap; + look.vi=info; + look.lpclook.init(look.ln, look.m); + + // we choose a scaling constant so that: + scale=look.ln/toBARK((float)(info.rate/2.)); + + // the mapping from a linear scale to a smaller bark scale is + // straightforward. We do *not* make sure that the linear mapping + // does not skip bark-scale bins; the decoder simply skips them and + // the encoder may do what it wishes in filling them. They're + // necessary in some mapping combinations to keep the scale spacing + // accurate + look.linearmap=new int[look.n]; + for(int j=0; j<look.n; j++){ + int val=(int)Math.floor(toBARK((float)((info.rate/2.)/look.n*j))*scale); // bark numbers represent band edges + if(val>=look.ln) + val=look.ln; // guard against the approximation + look.linearmap[j]=val; + } + return look; + } + + static float toBARK(float f){ + return (float)(13.1*Math.atan(.00074*(f))+2.24*Math.atan((f)*(f)*1.85e-8)+1e-4*(f)); + } + + Object state(Object i){ + EchstateFloor0 state=new EchstateFloor0(); + InfoFloor0 info=(InfoFloor0)i; + + // a safe size if usually too big (dim==1) + state.codewords=new int[info.order]; + state.curve=new float[info.barkmap]; + state.frameno=-1; + return (state); + } + + void free_info(Object i){ + } + + void free_look(Object i){ + } + + void free_state(Object vs){ + } + + int forward(Block vb, Object i, float[] in, float[] out, Object vs){ + return 0; + } + + float[] lsp=null; + + int inverse(Block vb, Object i, float[] out){ + //System.err.println("Floor0.inverse "+i.getClass()+"]"); + LookFloor0 look=(LookFloor0)i; + InfoFloor0 info=look.vi; + int ampraw=vb.opb.read(info.ampbits); + if(ampraw>0){ // also handles the -1 out of data case + int maxval=(1<<info.ampbits)-1; + float amp=(float)ampraw/maxval*info.ampdB; + int booknum=vb.opb.read(Util.ilog(info.numbooks)); + + if(booknum!=-1&&booknum<info.numbooks){ + + synchronized(this){ + if(lsp==null||lsp.length<look.m){ + lsp=new float[look.m]; + } + else{ + for(int j=0; j<look.m; j++) + lsp[j]=0.f; + } + + CodeBook b=vb.vd.fullbooks[info.books[booknum]]; + float last=0.f; + + for(int j=0; j<look.m; j++) + out[j]=0.0f; + + for(int j=0; j<look.m; j+=b.dim){ + if(b.decodevs(lsp, j, vb.opb, 1, -1)==-1){ + for(int k=0; k<look.n; k++) + out[k]=0.0f; + return (0); + } + } + for(int j=0; j<look.m;){ + for(int k=0; k<b.dim; k++, j++) + lsp[j]+=last; + last=lsp[j-1]; + } + // take the coefficients back to a spectral envelope curve + Lsp.lsp_to_curve(out, look.linearmap, look.n, look.ln, lsp, look.m, + amp, info.ampdB); + + return (1); + } + } + } + return (0); + } + + Object inverse1(Block vb, Object i, Object memo){ + LookFloor0 look=(LookFloor0)i; + InfoFloor0 info=look.vi; + float[] lsp=null; + if(memo instanceof float[]){ + lsp=(float[])memo; + } + + int ampraw=vb.opb.read(info.ampbits); + if(ampraw>0){ // also handles the -1 out of data case + int maxval=(1<<info.ampbits)-1; + float amp=(float)ampraw/maxval*info.ampdB; + int booknum=vb.opb.read(Util.ilog(info.numbooks)); + + if(booknum!=-1&&booknum<info.numbooks){ + CodeBook b=vb.vd.fullbooks[info.books[booknum]]; + float last=0.f; + + if(lsp==null||lsp.length<look.m+1){ + lsp=new float[look.m+1]; + } + else{ + for(int j=0; j<lsp.length; j++) + lsp[j]=0.f; + } + + for(int j=0; j<look.m; j+=b.dim){ + if(b.decodev_set(lsp, j, vb.opb, b.dim)==-1){ + return (null); + } + } + + for(int j=0; j<look.m;){ + for(int k=0; k<b.dim; k++, j++) + lsp[j]+=last; + last=lsp[j-1]; + } + lsp[look.m]=amp; + return (lsp); + } + } + return (null); + } + + int inverse2(Block vb, Object i, Object memo, float[] out){ + LookFloor0 look=(LookFloor0)i; + InfoFloor0 info=look.vi; + + if(memo!=null){ + float[] lsp=(float[])memo; + float amp=lsp[look.m]; + + Lsp.lsp_to_curve(out, look.linearmap, look.n, look.ln, lsp, look.m, amp, + info.ampdB); + return (1); + } + for(int j=0; j<look.n; j++){ + out[j]=0.f; + } + return (0); + } + + static float fromdB(float x){ + return (float)(Math.exp((x)*.11512925)); + } + + static void lsp_to_lpc(float[] lsp, float[] lpc, int m){ + int i, j, m2=m/2; + float[] O=new float[m2]; + float[] E=new float[m2]; + float A; + float[] Ae=new float[m2+1]; + float[] Ao=new float[m2+1]; + float B; + float[] Be=new float[m2]; + float[] Bo=new float[m2]; + float temp; + + // even/odd roots setup + for(i=0; i<m2; i++){ + O[i]=(float)(-2.*Math.cos(lsp[i*2])); + E[i]=(float)(-2.*Math.cos(lsp[i*2+1])); + } + + // set up impulse response + for(j=0; j<m2; j++){ + Ae[j]=0.f; + Ao[j]=1.f; + Be[j]=0.f; + Bo[j]=1.f; + } + Ao[j]=1.f; + Ae[j]=1.f; + + // run impulse response + for(i=1; i<m+1; i++){ + A=B=0.f; + for(j=0; j<m2; j++){ + temp=O[j]*Ao[j]+Ae[j]; + Ae[j]=Ao[j]; + Ao[j]=A; + A+=temp; + + temp=E[j]*Bo[j]+Be[j]; + Be[j]=Bo[j]; + Bo[j]=B; + B+=temp; + } + lpc[i-1]=(A+Ao[j]+B-Ae[j])/2; + Ao[j]=A; + Ae[j]=B; + } + } + + static void lpc_to_curve(float[] curve, float[] lpc, float amp, LookFloor0 l, + String name, int frameno){ + // l->m+1 must be less than l->ln, but guard in case we get a bad stream + float[] lcurve=new float[Math.max(l.ln*2, l.m*2+2)]; + + if(amp==0){ + for(int j=0; j<l.n; j++) + curve[j]=0.0f; + return; + } + l.lpclook.lpc_to_curve(lcurve, lpc, amp); + + for(int i=0; i<l.n; i++) + curve[i]=lcurve[l.linearmap[i]]; + } + + class InfoFloor0{ + int order; + int rate; + int barkmap; + + int ampbits; + int ampdB; + + int numbooks; // <= 16 + int[] books=new int[16]; + } + + class LookFloor0{ + int n; + int ln; + int m; + int[] linearmap; + + InfoFloor0 vi; + Lpc lpclook=new Lpc(); + } + + class EchstateFloor0{ + int[] codewords; + float[] curve; + long frameno; + long codes; + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Floor1.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Floor1.java new file mode 100644 index 0000000..6d8cede --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Floor1.java @@ -0,0 +1,611 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Floor1 extends FuncFloor{ + static final int floor1_rangedb=140; + static final int VIF_POSIT=63; + + void pack(Object i, Buffer opb){ + InfoFloor1 info=(InfoFloor1)i; + + int count=0; + int rangebits; + int maxposit=info.postlist[1]; + int maxclass=-1; + + /* save out partitions */ + opb.write(info.partitions, 5); /* only 0 to 31 legal */ + for(int j=0; j<info.partitions; j++){ + opb.write(info.partitionclass[j], 4); /* only 0 to 15 legal */ + if(maxclass<info.partitionclass[j]) + maxclass=info.partitionclass[j]; + } + + /* save out partition classes */ + for(int j=0; j<maxclass+1; j++){ + opb.write(info.class_dim[j]-1, 3); /* 1 to 8 */ + opb.write(info.class_subs[j], 2); /* 0 to 3 */ + if(info.class_subs[j]!=0){ + opb.write(info.class_book[j], 8); + } + for(int k=0; k<(1<<info.class_subs[j]); k++){ + opb.write(info.class_subbook[j][k]+1, 8); + } + } + + /* save out the post list */ + opb.write(info.mult-1, 2); /* only 1,2,3,4 legal now */ + opb.write(Util.ilog2(maxposit), 4); + rangebits=Util.ilog2(maxposit); + + for(int j=0, k=0; j<info.partitions; j++){ + count+=info.class_dim[info.partitionclass[j]]; + for(; k<count; k++){ + opb.write(info.postlist[k+2], rangebits); + } + } + } + + Object unpack(Info vi, Buffer opb){ + int count=0, maxclass=-1, rangebits; + InfoFloor1 info=new InfoFloor1(); + + /* read partitions */ + info.partitions=opb.read(5); /* only 0 to 31 legal */ + for(int j=0; j<info.partitions; j++){ + info.partitionclass[j]=opb.read(4); /* only 0 to 15 legal */ + if(maxclass<info.partitionclass[j]) + maxclass=info.partitionclass[j]; + } + + /* read partition classes */ + for(int j=0; j<maxclass+1; j++){ + info.class_dim[j]=opb.read(3)+1; /* 1 to 8 */ + info.class_subs[j]=opb.read(2); /* 0,1,2,3 bits */ + if(info.class_subs[j]<0){ + info.free(); + return (null); + } + if(info.class_subs[j]!=0){ + info.class_book[j]=opb.read(8); + } + if(info.class_book[j]<0||info.class_book[j]>=vi.books){ + info.free(); + return (null); + } + for(int k=0; k<(1<<info.class_subs[j]); k++){ + info.class_subbook[j][k]=opb.read(8)-1; + if(info.class_subbook[j][k]<-1||info.class_subbook[j][k]>=vi.books){ + info.free(); + return (null); + } + } + } + + /* read the post list */ + info.mult=opb.read(2)+1; /* only 1,2,3,4 legal now */ + rangebits=opb.read(4); + + for(int j=0, k=0; j<info.partitions; j++){ + count+=info.class_dim[info.partitionclass[j]]; + for(; k<count; k++){ + int t=info.postlist[k+2]=opb.read(rangebits); + if(t<0||t>=(1<<rangebits)){ + info.free(); + return (null); + } + } + } + info.postlist[0]=0; + info.postlist[1]=1<<rangebits; + + return (info); + } + + Object look(DspState vd, InfoMode mi, Object i){ + int _n=0; + + int[] sortpointer=new int[VIF_POSIT+2]; + + // Info vi=vd.vi; + + InfoFloor1 info=(InfoFloor1)i; + LookFloor1 look=new LookFloor1(); + look.vi=info; + look.n=info.postlist[1]; + + /* we drop each position value in-between already decoded values, + and use linear interpolation to predict each new value past the + edges. The positions are read in the order of the position + list... we precompute the bounding positions in the lookup. Of + course, the neighbors can change (if a position is declined), but + this is an initial mapping */ + + for(int j=0; j<info.partitions; j++){ + _n+=info.class_dim[info.partitionclass[j]]; + } + _n+=2; + look.posts=_n; + + /* also store a sorted position index */ + for(int j=0; j<_n; j++){ + sortpointer[j]=j; + } + // qsort(sortpointer,n,sizeof(int),icomp); // !! + + int foo; + for(int j=0; j<_n-1; j++){ + for(int k=j; k<_n; k++){ + if(info.postlist[sortpointer[j]]>info.postlist[sortpointer[k]]){ + foo=sortpointer[k]; + sortpointer[k]=sortpointer[j]; + sortpointer[j]=foo; + } + } + } + + /* points from sort order back to range number */ + for(int j=0; j<_n; j++){ + look.forward_index[j]=sortpointer[j]; + } + /* points from range order to sorted position */ + for(int j=0; j<_n; j++){ + look.reverse_index[look.forward_index[j]]=j; + } + /* we actually need the post values too */ + for(int j=0; j<_n; j++){ + look.sorted_index[j]=info.postlist[look.forward_index[j]]; + } + + /* quantize values to multiplier spec */ + switch(info.mult){ + case 1: /* 1024 -> 256 */ + look.quant_q=256; + break; + case 2: /* 1024 -> 128 */ + look.quant_q=128; + break; + case 3: /* 1024 -> 86 */ + look.quant_q=86; + break; + case 4: /* 1024 -> 64 */ + look.quant_q=64; + break; + default: + look.quant_q=-1; + } + + /* discover our neighbors for decode where we don't use fit flags + (that would push the neighbors outward) */ + for(int j=0; j<_n-2; j++){ + int lo=0; + int hi=1; + int lx=0; + int hx=look.n; + int currentx=info.postlist[j+2]; + for(int k=0; k<j+2; k++){ + int x=info.postlist[k]; + if(x>lx&&x<currentx){ + lo=k; + lx=x; + } + if(x<hx&&x>currentx){ + hi=k; + hx=x; + } + } + look.loneighbor[j]=lo; + look.hineighbor[j]=hi; + } + + return look; + } + + void free_info(Object i){ + } + + void free_look(Object i){ + } + + void free_state(Object vs){ + } + + int forward(Block vb, Object i, float[] in, float[] out, Object vs){ + return 0; + } + + Object inverse1(Block vb, Object ii, Object memo){ + LookFloor1 look=(LookFloor1)ii; + InfoFloor1 info=look.vi; + CodeBook[] books=vb.vd.fullbooks; + + /* unpack wrapped/predicted values from stream */ + if(vb.opb.read(1)==1){ + int[] fit_value=null; + if(memo instanceof int[]){ + fit_value=(int[])memo; + } + if(fit_value==null||fit_value.length<look.posts){ + fit_value=new int[look.posts]; + } + else{ + for(int i=0; i<fit_value.length; i++) + fit_value[i]=0; + } + + fit_value[0]=vb.opb.read(Util.ilog(look.quant_q-1)); + fit_value[1]=vb.opb.read(Util.ilog(look.quant_q-1)); + + /* partition by partition */ + for(int i=0, j=2; i<info.partitions; i++){ + int clss=info.partitionclass[i]; + int cdim=info.class_dim[clss]; + int csubbits=info.class_subs[clss]; + int csub=1<<csubbits; + int cval=0; + + /* decode the partition's first stage cascade value */ + if(csubbits!=0){ + cval=books[info.class_book[clss]].decode(vb.opb); + + if(cval==-1){ + return (null); + } + } + + for(int k=0; k<cdim; k++){ + int book=info.class_subbook[clss][cval&(csub-1)]; + cval>>>=csubbits; + if(book>=0){ + if((fit_value[j+k]=books[book].decode(vb.opb))==-1){ + return (null); + } + } + else{ + fit_value[j+k]=0; + } + } + j+=cdim; + } + + /* unwrap positive values and reconsitute via linear interpolation */ + for(int i=2; i<look.posts; i++){ + int predicted=render_point(info.postlist[look.loneighbor[i-2]], + info.postlist[look.hineighbor[i-2]], + fit_value[look.loneighbor[i-2]], fit_value[look.hineighbor[i-2]], + info.postlist[i]); + int hiroom=look.quant_q-predicted; + int loroom=predicted; + int room=(hiroom<loroom ? hiroom : loroom)<<1; + int val=fit_value[i]; + + if(val!=0){ + if(val>=room){ + if(hiroom>loroom){ + val=val-loroom; + } + else{ + val=-1-(val-hiroom); + } + } + else{ + if((val&1)!=0){ + val=-((val+1)>>>1); + } + else{ + val>>=1; + } + } + + fit_value[i]=val+predicted; + fit_value[look.loneighbor[i-2]]&=0x7fff; + fit_value[look.hineighbor[i-2]]&=0x7fff; + } + else{ + fit_value[i]=predicted|0x8000; + } + } + return (fit_value); + } + + return (null); + } + + private static int render_point(int x0, int x1, int y0, int y1, int x){ + y0&=0x7fff; /* mask off flag */ + y1&=0x7fff; + + { + int dy=y1-y0; + int adx=x1-x0; + int ady=Math.abs(dy); + int err=ady*(x-x0); + + int off=(int)(err/adx); + if(dy<0) + return (y0-off); + return (y0+off); + } + } + + int inverse2(Block vb, Object i, Object memo, float[] out){ + LookFloor1 look=(LookFloor1)i; + InfoFloor1 info=look.vi; + int n=vb.vd.vi.blocksizes[vb.mode]/2; + + if(memo!=null){ + /* render the lines */ + int[] fit_value=(int[])memo; + int hx=0; + int lx=0; + int ly=fit_value[0]*info.mult; + for(int j=1; j<look.posts; j++){ + int current=look.forward_index[j]; + int hy=fit_value[current]&0x7fff; + if(hy==fit_value[current]){ + hy*=info.mult; + hx=info.postlist[current]; + + render_line(lx, hx, ly, hy, out); + + lx=hx; + ly=hy; + } + } + for(int j=hx; j<n; j++){ + out[j]*=out[j-1]; /* be certain */ + } + return (1); + } + for(int j=0; j<n; j++){ + out[j]=0.f; + } + return (0); + } + + private static float[] FLOOR_fromdB_LOOKUP= {1.0649863e-07F, 1.1341951e-07F, + 1.2079015e-07F, 1.2863978e-07F, 1.3699951e-07F, 1.4590251e-07F, + 1.5538408e-07F, 1.6548181e-07F, 1.7623575e-07F, 1.8768855e-07F, + 1.9988561e-07F, 2.128753e-07F, 2.2670913e-07F, 2.4144197e-07F, + 2.5713223e-07F, 2.7384213e-07F, 2.9163793e-07F, 3.1059021e-07F, + 3.3077411e-07F, 3.5226968e-07F, 3.7516214e-07F, 3.9954229e-07F, + 4.2550680e-07F, 4.5315863e-07F, 4.8260743e-07F, 5.1396998e-07F, + 5.4737065e-07F, 5.8294187e-07F, 6.2082472e-07F, 6.6116941e-07F, + 7.0413592e-07F, 7.4989464e-07F, 7.9862701e-07F, 8.5052630e-07F, + 9.0579828e-07F, 9.6466216e-07F, 1.0273513e-06F, 1.0941144e-06F, + 1.1652161e-06F, 1.2409384e-06F, 1.3215816e-06F, 1.4074654e-06F, + 1.4989305e-06F, 1.5963394e-06F, 1.7000785e-06F, 1.8105592e-06F, + 1.9282195e-06F, 2.0535261e-06F, 2.1869758e-06F, 2.3290978e-06F, + 2.4804557e-06F, 2.6416497e-06F, 2.8133190e-06F, 2.9961443e-06F, + 3.1908506e-06F, 3.3982101e-06F, 3.6190449e-06F, 3.8542308e-06F, + 4.1047004e-06F, 4.3714470e-06F, 4.6555282e-06F, 4.9580707e-06F, + 5.2802740e-06F, 5.6234160e-06F, 5.9888572e-06F, 6.3780469e-06F, + 6.7925283e-06F, 7.2339451e-06F, 7.7040476e-06F, 8.2047000e-06F, + 8.7378876e-06F, 9.3057248e-06F, 9.9104632e-06F, 1.0554501e-05F, + 1.1240392e-05F, 1.1970856e-05F, 1.2748789e-05F, 1.3577278e-05F, + 1.4459606e-05F, 1.5399272e-05F, 1.6400004e-05F, 1.7465768e-05F, + 1.8600792e-05F, 1.9809576e-05F, 2.1096914e-05F, 2.2467911e-05F, + 2.3928002e-05F, 2.5482978e-05F, 2.7139006e-05F, 2.8902651e-05F, + 3.0780908e-05F, 3.2781225e-05F, 3.4911534e-05F, 3.7180282e-05F, + 3.9596466e-05F, 4.2169667e-05F, 4.4910090e-05F, 4.7828601e-05F, + 5.0936773e-05F, 5.4246931e-05F, 5.7772202e-05F, 6.1526565e-05F, + 6.5524908e-05F, 6.9783085e-05F, 7.4317983e-05F, 7.9147585e-05F, + 8.4291040e-05F, 8.9768747e-05F, 9.5602426e-05F, 0.00010181521F, + 0.00010843174F, 0.00011547824F, 0.00012298267F, 0.00013097477F, + 0.00013948625F, 0.00014855085F, 0.00015820453F, 0.00016848555F, + 0.00017943469F, 0.00019109536F, 0.00020351382F, 0.00021673929F, + 0.00023082423F, 0.00024582449F, 0.00026179955F, 0.00027881276F, + 0.00029693158F, 0.00031622787F, 0.00033677814F, 0.00035866388F, + 0.00038197188F, 0.00040679456F, 0.00043323036F, 0.00046138411F, + 0.00049136745F, 0.00052329927F, 0.00055730621F, 0.00059352311F, + 0.00063209358F, 0.00067317058F, 0.00071691700F, 0.00076350630F, + 0.00081312324F, 0.00086596457F, 0.00092223983F, 0.00098217216F, + 0.0010459992F, 0.0011139742F, 0.0011863665F, 0.0012634633F, + 0.0013455702F, 0.0014330129F, 0.0015261382F, 0.0016253153F, + 0.0017309374F, 0.0018434235F, 0.0019632195F, 0.0020908006F, + 0.0022266726F, 0.0023713743F, 0.0025254795F, 0.0026895994F, + 0.0028643847F, 0.0030505286F, 0.0032487691F, 0.0034598925F, + 0.0036847358F, 0.0039241906F, 0.0041792066F, 0.0044507950F, + 0.0047400328F, 0.0050480668F, 0.0053761186F, 0.0057254891F, + 0.0060975636F, 0.0064938176F, 0.0069158225F, 0.0073652516F, + 0.0078438871F, 0.0083536271F, 0.0088964928F, 0.009474637F, 0.010090352F, + 0.010746080F, 0.011444421F, 0.012188144F, 0.012980198F, 0.013823725F, + 0.014722068F, 0.015678791F, 0.016697687F, 0.017782797F, 0.018938423F, + 0.020169149F, 0.021479854F, 0.022875735F, 0.024362330F, 0.025945531F, + 0.027631618F, 0.029427276F, 0.031339626F, 0.033376252F, 0.035545228F, + 0.037855157F, 0.040315199F, 0.042935108F, 0.045725273F, 0.048696758F, + 0.051861348F, 0.055231591F, 0.058820850F, 0.062643361F, 0.066714279F, + 0.071049749F, 0.075666962F, 0.080584227F, 0.085821044F, 0.091398179F, + 0.097337747F, 0.10366330F, 0.11039993F, 0.11757434F, 0.12521498F, + 0.13335215F, 0.14201813F, 0.15124727F, 0.16107617F, 0.17154380F, + 0.18269168F, 0.19456402F, 0.20720788F, 0.22067342F, 0.23501402F, + 0.25028656F, 0.26655159F, 0.28387361F, 0.30232132F, 0.32196786F, + 0.34289114F, 0.36517414F, 0.38890521F, 0.41417847F, 0.44109412F, + 0.46975890F, 0.50028648F, 0.53279791F, 0.56742212F, 0.60429640F, + 0.64356699F, 0.68538959F, 0.72993007F, 0.77736504F, 0.82788260F, + 0.88168307F, 0.9389798F, 1.F}; + + private static void render_line(int x0, int x1, int y0, int y1, float[] d){ + int dy=y1-y0; + int adx=x1-x0; + int ady=Math.abs(dy); + int base=dy/adx; + int sy=(dy<0 ? base-1 : base+1); + int x=x0; + int y=y0; + int err=0; + + ady-=Math.abs(base*adx); + + d[x]*=FLOOR_fromdB_LOOKUP[y]; + while(++x<x1){ + err=err+ady; + if(err>=adx){ + err-=adx; + y+=sy; + } + else{ + y+=base; + } + d[x]*=FLOOR_fromdB_LOOKUP[y]; + } + } + + class InfoFloor1{ + static final int VIF_POSIT=63; + static final int VIF_CLASS=16; + static final int VIF_PARTS=31; + + int partitions; /* 0 to 31 */ + int[] partitionclass=new int[VIF_PARTS]; /* 0 to 15 */ + + int[] class_dim=new int[VIF_CLASS]; /* 1 to 8 */ + int[] class_subs=new int[VIF_CLASS]; /* 0,1,2,3 (bits: 1<<n poss) */ + int[] class_book=new int[VIF_CLASS]; /* subs ^ dim entries */ + int[][] class_subbook=new int[VIF_CLASS][]; /* [VIF_CLASS][subs] */ + + int mult; /* 1 2 3 or 4 */ + int[] postlist=new int[VIF_POSIT+2]; /* first two implicit */ + + /* encode side analysis parameters */ + float maxover; + float maxunder; + float maxerr; + + int twofitminsize; + int twofitminused; + int twofitweight; + float twofitatten; + int unusedminsize; + int unusedmin_n; + + int n; + + InfoFloor1(){ + for(int i=0; i<class_subbook.length; i++){ + class_subbook[i]=new int[8]; + } + } + + void free(){ + partitionclass=null; + class_dim=null; + class_subs=null; + class_book=null; + class_subbook=null; + postlist=null; + } + + Object copy_info(){ + InfoFloor1 info=this; + InfoFloor1 ret=new InfoFloor1(); + + ret.partitions=info.partitions; + System + .arraycopy(info.partitionclass, 0, ret.partitionclass, 0, VIF_PARTS); + System.arraycopy(info.class_dim, 0, ret.class_dim, 0, VIF_CLASS); + System.arraycopy(info.class_subs, 0, ret.class_subs, 0, VIF_CLASS); + System.arraycopy(info.class_book, 0, ret.class_book, 0, VIF_CLASS); + + for(int j=0; j<VIF_CLASS; j++){ + System.arraycopy(info.class_subbook[j], 0, ret.class_subbook[j], 0, 8); + } + + ret.mult=info.mult; + System.arraycopy(info.postlist, 0, ret.postlist, 0, VIF_POSIT+2); + + ret.maxover=info.maxover; + ret.maxunder=info.maxunder; + ret.maxerr=info.maxerr; + + ret.twofitminsize=info.twofitminsize; + ret.twofitminused=info.twofitminused; + ret.twofitweight=info.twofitweight; + ret.twofitatten=info.twofitatten; + ret.unusedminsize=info.unusedminsize; + ret.unusedmin_n=info.unusedmin_n; + + ret.n=info.n; + + return (ret); + } + + } + + class LookFloor1{ + static final int VIF_POSIT=63; + + int[] sorted_index=new int[VIF_POSIT+2]; + int[] forward_index=new int[VIF_POSIT+2]; + int[] reverse_index=new int[VIF_POSIT+2]; + int[] hineighbor=new int[VIF_POSIT]; + int[] loneighbor=new int[VIF_POSIT]; + int posts; + + int n; + int quant_q; + InfoFloor1 vi; + + int phrasebits; + int postbits; + int frames; + + void free(){ + sorted_index=null; + forward_index=null; + reverse_index=null; + hineighbor=null; + loneighbor=null; + } + } + + class Lsfit_acc{ + long x0; + long x1; + + long xa; + long ya; + long x2a; + long y2a; + long xya; + long n; + long an; + long un; + long edgey0; + long edgey1; + } + + class EchstateFloor1{ + int[] codewords; + float[] curve; + long frameno; + long codes; + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/FuncFloor.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/FuncFloor.java new file mode 100644 index 0000000..6a0b12c --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/FuncFloor.java @@ -0,0 +1,52 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +abstract class FuncFloor{ + + public static FuncFloor[] floor_P= {new Floor0(), new Floor1()}; + + abstract void pack(Object i, Buffer opb); + + abstract Object unpack(Info vi, Buffer opb); + + abstract Object look(DspState vd, InfoMode mi, Object i); + + abstract void free_info(Object i); + + abstract void free_look(Object i); + + abstract void free_state(Object vs); + + abstract int forward(Block vb, Object i, float[] in, float[] out, Object vs); + + abstract Object inverse1(Block vb, Object i, Object memo); + + abstract int inverse2(Block vb, Object i, Object memo, float[] out); +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/FuncMapping.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/FuncMapping.java new file mode 100644 index 0000000..50c1028 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/FuncMapping.java @@ -0,0 +1,45 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +abstract class FuncMapping{ + public static FuncMapping[] mapping_P= {new Mapping0()}; + + abstract void pack(Info info, Object imap, Buffer buffer); + + abstract Object unpack(Info info, Buffer buffer); + + abstract Object look(DspState vd, InfoMode vm, Object m); + + abstract void free_info(Object imap); + + abstract void free_look(Object imap); + + abstract int inverse(Block vd, Object lm); +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/FuncResidue.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/FuncResidue.java new file mode 100644 index 0000000..e381975 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/FuncResidue.java @@ -0,0 +1,46 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +abstract class FuncResidue{ + public static FuncResidue[] residue_P= {new Residue0(), new Residue1(), + new Residue2()}; + + abstract void pack(Object vr, Buffer opb); + + abstract Object unpack(Info vi, Buffer opb); + + abstract Object look(DspState vd, InfoMode vm, Object vr); + + abstract void free_info(Object i); + + abstract void free_look(Object i); + + abstract int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch); +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/FuncTime.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/FuncTime.java new file mode 100644 index 0000000..37b868b --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/FuncTime.java @@ -0,0 +1,45 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +abstract class FuncTime{ + public static FuncTime[] time_P= {new Time0()}; + + abstract void pack(Object i, Buffer opb); + + abstract Object unpack(Info vi, Buffer opb); + + abstract Object look(DspState vd, InfoMode vm, Object i); + + abstract void free_info(Object i); + + abstract void free_look(Object i); + + abstract int inverse(Block vb, Object i, float[] in, float[] out); +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Info.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Info.java new file mode 100644 index 0000000..86fa9b2 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Info.java @@ -0,0 +1,472 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +/* + * Julien Gouesse: I just replaced new Integer(int) by Integer.valueOf(int) + */ +public class Info{ + private static final int OV_EBADPACKET=-136; + private static final int OV_ENOTAUDIO=-135; + + private static byte[] _vorbis="vorbis".getBytes(); + private static final int VI_TIMEB=1; + // private static final int VI_FLOORB=1; + private static final int VI_FLOORB=2; + // private static final int VI_RESB=1; + private static final int VI_RESB=3; + private static final int VI_MAPB=1; + private static final int VI_WINDOWB=1; + + public int version; + public int channels; + public int rate; + + // The below bitrate declarations are *hints*. + // Combinations of the three values carry the following implications: + // + // all three set to the same value: + // implies a fixed rate bitstream + // only nominal set: + // implies a VBR stream that averages the nominal bitrate. No hard + // upper/lower limit + // upper and or lower set: + // implies a VBR bitstream that obeys the bitrate limits. nominal + // may also be set to give a nominal rate. + // none set: + // the coder does not care to speculate. + + int bitrate_upper; + int bitrate_nominal; + int bitrate_lower; + + // Vorbis supports only short and long blocks, but allows the + // encoder to choose the sizes + + int[] blocksizes=new int[2]; + + // modes are the primary means of supporting on-the-fly different + // blocksizes, different channel mappings (LR or mid-side), + // different residue backends, etc. Each mode consists of a + // blocksize flag and a mapping (along with the mapping setup + + int modes; + int maps; + int times; + int floors; + int residues; + int books; + int psys; // encode only + + InfoMode[] mode_param=null; + + int[] map_type=null; + Object[] map_param=null; + + int[] time_type=null; + Object[] time_param=null; + + int[] floor_type=null; + Object[] floor_param=null; + + int[] residue_type=null; + Object[] residue_param=null; + + StaticCodeBook[] book_param=null; + + PsyInfo[] psy_param=new PsyInfo[64]; // encode only + + // for block long/sort tuning; encode only + int envelopesa; + float preecho_thresh; + float preecho_clamp; + + // used by synthesis, which has a full, alloced vi + public void init(){ + rate=0; + } + + public void clear(){ + for(int i=0; i<modes; i++){ + mode_param[i]=null; + } + mode_param=null; + + for(int i=0; i<maps; i++){ // unpack does the range checking + FuncMapping.mapping_P[map_type[i]].free_info(map_param[i]); + } + map_param=null; + + for(int i=0; i<times; i++){ // unpack does the range checking + FuncTime.time_P[time_type[i]].free_info(time_param[i]); + } + time_param=null; + + for(int i=0; i<floors; i++){ // unpack does the range checking + FuncFloor.floor_P[floor_type[i]].free_info(floor_param[i]); + } + floor_param=null; + + for(int i=0; i<residues; i++){ // unpack does the range checking + FuncResidue.residue_P[residue_type[i]].free_info(residue_param[i]); + } + residue_param=null; + + // the static codebooks *are* freed if you call info_clear, because + // decode side does alloc a 'static' codebook. Calling clear on the + // full codebook does not clear the static codebook (that's our + // responsibility) + for(int i=0; i<books; i++){ + // just in case the decoder pre-cleared to save space + if(book_param[i]!=null){ + book_param[i].clear(); + book_param[i]=null; + } + } + //if(vi->book_param)free(vi->book_param); + book_param=null; + + for(int i=0; i<psys; i++){ + psy_param[i].free(); + } + + } + + // Header packing/unpacking + int unpack_info(Buffer opb){ + version=opb.read(32); + if(version!=0) + return (-1); + + channels=opb.read(8); + rate=opb.read(32); + + bitrate_upper=opb.read(32); + bitrate_nominal=opb.read(32); + bitrate_lower=opb.read(32); + + blocksizes[0]=1<<opb.read(4); + blocksizes[1]=1<<opb.read(4); + + if((rate<1)||(channels<1)||(blocksizes[0]<8)||(blocksizes[1]<blocksizes[0]) + ||(opb.read(1)!=1)){ + clear(); + return (-1); + } + return (0); + } + + // all of the real encoding details are here. The modes, books, + // everything + int unpack_books(Buffer opb){ + + books=opb.read(8)+1; + + if(book_param==null||book_param.length!=books) + book_param=new StaticCodeBook[books]; + for(int i=0; i<books; i++){ + book_param[i]=new StaticCodeBook(); + if(book_param[i].unpack(opb)!=0){ + clear(); + return (-1); + } + } + + // time backend settings + times=opb.read(6)+1; + if(time_type==null||time_type.length!=times) + time_type=new int[times]; + if(time_param==null||time_param.length!=times) + time_param=new Object[times]; + for(int i=0; i<times; i++){ + time_type[i]=opb.read(16); + if(time_type[i]<0||time_type[i]>=VI_TIMEB){ + clear(); + return (-1); + } + time_param[i]=FuncTime.time_P[time_type[i]].unpack(this, opb); + if(time_param[i]==null){ + clear(); + return (-1); + } + } + + // floor backend settings + floors=opb.read(6)+1; + if(floor_type==null||floor_type.length!=floors) + floor_type=new int[floors]; + if(floor_param==null||floor_param.length!=floors) + floor_param=new Object[floors]; + + for(int i=0; i<floors; i++){ + floor_type[i]=opb.read(16); + if(floor_type[i]<0||floor_type[i]>=VI_FLOORB){ + clear(); + return (-1); + } + + floor_param[i]=FuncFloor.floor_P[floor_type[i]].unpack(this, opb); + if(floor_param[i]==null){ + clear(); + return (-1); + } + } + + // residue backend settings + residues=opb.read(6)+1; + + if(residue_type==null||residue_type.length!=residues) + residue_type=new int[residues]; + + if(residue_param==null||residue_param.length!=residues) + residue_param=new Object[residues]; + + for(int i=0; i<residues; i++){ + residue_type[i]=opb.read(16); + if(residue_type[i]<0||residue_type[i]>=VI_RESB){ + clear(); + return (-1); + } + residue_param[i]=FuncResidue.residue_P[residue_type[i]].unpack(this, opb); + if(residue_param[i]==null){ + clear(); + return (-1); + } + } + + // map backend settings + maps=opb.read(6)+1; + if(map_type==null||map_type.length!=maps) + map_type=new int[maps]; + if(map_param==null||map_param.length!=maps) + map_param=new Object[maps]; + for(int i=0; i<maps; i++){ + map_type[i]=opb.read(16); + if(map_type[i]<0||map_type[i]>=VI_MAPB){ + clear(); + return (-1); + } + map_param[i]=FuncMapping.mapping_P[map_type[i]].unpack(this, opb); + if(map_param[i]==null){ + clear(); + return (-1); + } + } + + // mode settings + modes=opb.read(6)+1; + if(mode_param==null||mode_param.length!=modes) + mode_param=new InfoMode[modes]; + for(int i=0; i<modes; i++){ + mode_param[i]=new InfoMode(); + mode_param[i].blockflag=opb.read(1); + mode_param[i].windowtype=opb.read(16); + mode_param[i].transformtype=opb.read(16); + mode_param[i].mapping=opb.read(8); + + if((mode_param[i].windowtype>=VI_WINDOWB) + ||(mode_param[i].transformtype>=VI_WINDOWB) + ||(mode_param[i].mapping>=maps)){ + clear(); + return (-1); + } + } + + if(opb.read(1)!=1){ + clear(); + return (-1); + } + + return (0); + } + + // The Vorbis header is in three packets; the initial small packet in + // the first page that identifies basic parameters, a second packet + // with bitstream comments and a third packet that holds the + // codebook. + + public int synthesis_headerin(Comment vc, Packet op){ + Buffer opb=new Buffer(); + + if(op!=null){ + opb.readinit(op.packet_base, op.packet, op.bytes); + + // Which of the three types of header is this? + // Also verify header-ness, vorbis + { + byte[] buffer=new byte[6]; + int packtype=opb.read(8); + opb.read(buffer, 6); + if(buffer[0]!='v'||buffer[1]!='o'||buffer[2]!='r'||buffer[3]!='b' + ||buffer[4]!='i'||buffer[5]!='s'){ + // not a vorbis header + return (-1); + } + switch(packtype){ + case 0x01: // least significant *bit* is read first + if(op.b_o_s==0){ + // Not the initial packet + return (-1); + } + if(rate!=0){ + // previously initialized info header + return (-1); + } + return (unpack_info(opb)); + case 0x03: // least significant *bit* is read first + if(rate==0){ + // um... we didn't get the initial header + return (-1); + } + return (vc.unpack(opb)); + case 0x05: // least significant *bit* is read first + if(rate==0||vc.vendor==null){ + // um... we didn;t get the initial header or comments yet + return (-1); + } + return (unpack_books(opb)); + default: + // Not a valid vorbis header type + //return(-1); + break; + } + } + } + return (-1); + } + + // pack side + int pack_info(Buffer opb){ + // preamble + opb.write(0x01, 8); + opb.write(_vorbis); + + // basic information about the stream + opb.write(0x00, 32); + opb.write(channels, 8); + opb.write(rate, 32); + + opb.write(bitrate_upper, 32); + opb.write(bitrate_nominal, 32); + opb.write(bitrate_lower, 32); + + opb.write(Util.ilog2(blocksizes[0]), 4); + opb.write(Util.ilog2(blocksizes[1]), 4); + opb.write(1, 1); + return (0); + } + + int pack_books(Buffer opb){ + opb.write(0x05, 8); + opb.write(_vorbis); + + // books + opb.write(books-1, 8); + for(int i=0; i<books; i++){ + if(book_param[i].pack(opb)!=0){ + //goto err_out; + return (-1); + } + } + + // times + opb.write(times-1, 6); + for(int i=0; i<times; i++){ + opb.write(time_type[i], 16); + FuncTime.time_P[time_type[i]].pack(this.time_param[i], opb); + } + + // floors + opb.write(floors-1, 6); + for(int i=0; i<floors; i++){ + opb.write(floor_type[i], 16); + FuncFloor.floor_P[floor_type[i]].pack(floor_param[i], opb); + } + + // residues + opb.write(residues-1, 6); + for(int i=0; i<residues; i++){ + opb.write(residue_type[i], 16); + FuncResidue.residue_P[residue_type[i]].pack(residue_param[i], opb); + } + + // maps + opb.write(maps-1, 6); + for(int i=0; i<maps; i++){ + opb.write(map_type[i], 16); + FuncMapping.mapping_P[map_type[i]].pack(this, map_param[i], opb); + } + + // modes + opb.write(modes-1, 6); + for(int i=0; i<modes; i++){ + opb.write(mode_param[i].blockflag, 1); + opb.write(mode_param[i].windowtype, 16); + opb.write(mode_param[i].transformtype, 16); + opb.write(mode_param[i].mapping, 8); + } + opb.write(1, 1); + return (0); + } + + public int blocksize(Packet op){ + //codec_setup_info + Buffer opb=new Buffer(); + + int mode; + + opb.readinit(op.packet_base, op.packet, op.bytes); + + /* Check the packet type */ + if(opb.read(1)!=0){ + /* Oops. This is not an audio data packet */ + return (OV_ENOTAUDIO); + } + { + int modebits=0; + int v=modes; + while(v>1){ + modebits++; + v>>>=1; + } + + /* read our mode and pre/post windowsize */ + mode=opb.read(modebits); + } + if(mode==-1) + return (OV_EBADPACKET); + return (blocksizes[mode_param[mode].blockflag]); + } + + public String toString(){ + return "version:"+Integer.valueOf(version)+", channels:"+Integer.valueOf(channels) + +", rate:"+Integer.valueOf(rate)+", bitrate:"+Integer.valueOf(bitrate_upper) + +","+Integer.valueOf(bitrate_nominal)+","+Integer.valueOf(bitrate_lower); + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/InfoMode.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/InfoMode.java new file mode 100644 index 0000000..e864174 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/InfoMode.java @@ -0,0 +1,34 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class InfoMode{ + int blockflag; + int windowtype; + int transformtype; + int mapping; +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/JOrbisException.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/JOrbisException.java new file mode 100644 index 0000000..98c95a4 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/JOrbisException.java @@ -0,0 +1,40 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +public class JOrbisException extends Exception{ + + private static final long serialVersionUID=1L; + + public JOrbisException(){ + super(); + } + + public JOrbisException(String s){ + super("JOrbis: "+s); + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Lookup.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Lookup.java new file mode 100644 index 0000000..0d15266 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Lookup.java @@ -0,0 +1,152 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Lookup{ + static final int COS_LOOKUP_SZ=128; + static final float[] COS_LOOKUP= {+1.0000000000000f, +0.9996988186962f, + +0.9987954562052f, +0.9972904566787f, +0.9951847266722f, + +0.9924795345987f, +0.9891765099648f, +0.9852776423889f, + +0.9807852804032f, +0.9757021300385f, +0.9700312531945f, + +0.9637760657954f, +0.9569403357322f, +0.9495281805930f, + +0.9415440651830f, +0.9329927988347f, +0.9238795325113f, + +0.9142097557035f, +0.9039892931234f, +0.8932243011955f, + +0.8819212643484f, +0.8700869911087f, +0.8577286100003f, + +0.8448535652497f, +0.8314696123025f, +0.8175848131516f, + +0.8032075314806f, +0.7883464276266f, +0.7730104533627f, + +0.7572088465065f, +0.7409511253550f, +0.7242470829515f, + +0.7071067811865f, +0.6895405447371f, +0.6715589548470f, + +0.6531728429538f, +0.6343932841636f, +0.6152315905806f, + +0.5956993044924f, +0.5758081914178f, +0.5555702330196f, + +0.5349976198871f, +0.5141027441932f, +0.4928981922298f, + +0.4713967368260f, +0.4496113296546f, +0.4275550934303f, + +0.4052413140050f, +0.3826834323651f, +0.3598950365350f, + +0.3368898533922f, +0.3136817403989f, +0.2902846772545f, + +0.2667127574749f, +0.2429801799033f, +0.2191012401569f, + +0.1950903220161f, +0.1709618887603f, +0.1467304744554f, + +0.1224106751992f, +0.0980171403296f, +0.0735645635997f, + +0.0490676743274f, +0.0245412285229f, +0.0000000000000f, + -0.0245412285229f, -0.0490676743274f, -0.0735645635997f, + -0.0980171403296f, -0.1224106751992f, -0.1467304744554f, + -0.1709618887603f, -0.1950903220161f, -0.2191012401569f, + -0.2429801799033f, -0.2667127574749f, -0.2902846772545f, + -0.3136817403989f, -0.3368898533922f, -0.3598950365350f, + -0.3826834323651f, -0.4052413140050f, -0.4275550934303f, + -0.4496113296546f, -0.4713967368260f, -0.4928981922298f, + -0.5141027441932f, -0.5349976198871f, -0.5555702330196f, + -0.5758081914178f, -0.5956993044924f, -0.6152315905806f, + -0.6343932841636f, -0.6531728429538f, -0.6715589548470f, + -0.6895405447371f, -0.7071067811865f, -0.7242470829515f, + -0.7409511253550f, -0.7572088465065f, -0.7730104533627f, + -0.7883464276266f, -0.8032075314806f, -0.8175848131516f, + -0.8314696123025f, -0.8448535652497f, -0.8577286100003f, + -0.8700869911087f, -0.8819212643484f, -0.8932243011955f, + -0.9039892931234f, -0.9142097557035f, -0.9238795325113f, + -0.9329927988347f, -0.9415440651830f, -0.9495281805930f, + -0.9569403357322f, -0.9637760657954f, -0.9700312531945f, + -0.9757021300385f, -0.9807852804032f, -0.9852776423889f, + -0.9891765099648f, -0.9924795345987f, -0.9951847266722f, + -0.9972904566787f, -0.9987954562052f, -0.9996988186962f, + -1.0000000000000f,}; + + /* interpolated lookup based cos function, domain 0 to PI only */ + static float coslook(float a){ + double d=a*(.31830989*(float)COS_LOOKUP_SZ); + int i=(int)d; + return COS_LOOKUP[i]+((float)(d-i))*(COS_LOOKUP[i+1]-COS_LOOKUP[i]); + } + + static final int INVSQ_LOOKUP_SZ=32; + static final float[] INVSQ_LOOKUP= {1.414213562373f, 1.392621247646f, + 1.371988681140f, 1.352246807566f, 1.333333333333f, 1.315191898443f, + 1.297771369046f, 1.281025230441f, 1.264911064067f, 1.249390095109f, + 1.234426799697f, 1.219988562661f, 1.206045378311f, 1.192569588000f, + 1.179535649239f, 1.166919931983f, 1.154700538379f, 1.142857142857f, + 1.131370849898f, 1.120224067222f, 1.109400392450f, 1.098884511590f, + 1.088662107904f, 1.078719779941f, 1.069044967650f, 1.059625885652f, + 1.050451462878f, 1.041511287847f, 1.032795558989f, 1.024295039463f, + 1.016001016002f, 1.007905261358f, 1.000000000000f,}; + + /* interpolated 1./sqrt(p) where .5 <= p < 1. */ + static float invsqlook(float a){ + double d=a*(2.f*(float)INVSQ_LOOKUP_SZ)-(float)INVSQ_LOOKUP_SZ; + int i=(int)d; + return INVSQ_LOOKUP[i]+((float)(d-i))*(INVSQ_LOOKUP[i+1]-INVSQ_LOOKUP[i]); + } + + static final int INVSQ2EXP_LOOKUP_MIN=-32; + static final int INVSQ2EXP_LOOKUP_MAX=32; + static final float[] INVSQ2EXP_LOOKUP= {65536.f, 46340.95001f, 32768.f, + 23170.47501f, 16384.f, 11585.2375f, 8192.f, 5792.618751f, 4096.f, + 2896.309376f, 2048.f, 1448.154688f, 1024.f, 724.0773439f, 512.f, + 362.038672f, 256.f, 181.019336f, 128.f, 90.50966799f, 64.f, 45.254834f, + 32.f, 22.627417f, 16.f, 11.3137085f, 8.f, 5.656854249f, 4.f, + 2.828427125f, 2.f, 1.414213562f, 1.f, 0.7071067812f, 0.5f, 0.3535533906f, + 0.25f, 0.1767766953f, 0.125f, 0.08838834765f, 0.0625f, 0.04419417382f, + 0.03125f, 0.02209708691f, 0.015625f, 0.01104854346f, 0.0078125f, + 0.005524271728f, 0.00390625f, 0.002762135864f, 0.001953125f, + 0.001381067932f, 0.0009765625f, 0.000690533966f, 0.00048828125f, + 0.000345266983f, 0.000244140625f, 0.0001726334915f, 0.0001220703125f, + 8.631674575e-05f, 6.103515625e-05f, 4.315837288e-05f, 3.051757812e-05f, + 2.157918644e-05f, 1.525878906e-05f,}; + + /* interpolated 1./sqrt(p) where .5 <= p < 1. */ + static float invsq2explook(int a){ + return INVSQ2EXP_LOOKUP[a-INVSQ2EXP_LOOKUP_MIN]; + } + + static final int FROMdB_LOOKUP_SZ=35; + static final int FROMdB2_LOOKUP_SZ=32; + static final int FROMdB_SHIFT=5; + static final int FROMdB2_SHIFT=3; + static final int FROMdB2_MASK=31; + static final float[] FROMdB_LOOKUP= {1.f, 0.6309573445f, 0.3981071706f, + 0.2511886432f, 0.1584893192f, 0.1f, 0.06309573445f, 0.03981071706f, + 0.02511886432f, 0.01584893192f, 0.01f, 0.006309573445f, 0.003981071706f, + 0.002511886432f, 0.001584893192f, 0.001f, 0.0006309573445f, + 0.0003981071706f, 0.0002511886432f, 0.0001584893192f, 0.0001f, + 6.309573445e-05f, 3.981071706e-05f, 2.511886432e-05f, 1.584893192e-05f, + 1e-05f, 6.309573445e-06f, 3.981071706e-06f, 2.511886432e-06f, + 1.584893192e-06f, 1e-06f, 6.309573445e-07f, 3.981071706e-07f, + 2.511886432e-07f, 1.584893192e-07f,}; + static final float[] FROMdB2_LOOKUP= {0.9928302478f, 0.9786445908f, + 0.9646616199f, 0.9508784391f, 0.9372921937f, 0.92390007f, 0.9106992942f, + 0.8976871324f, 0.8848608897f, 0.8722179097f, 0.8597555737f, + 0.8474713009f, 0.835362547f, 0.8234268041f, 0.8116616003f, 0.8000644989f, + 0.7886330981f, 0.7773650302f, 0.7662579617f, 0.755309592f, 0.7445176537f, + 0.7338799116f, 0.7233941627f, 0.7130582353f, 0.7028699885f, + 0.6928273125f, 0.6829281272f, 0.6731703824f, 0.6635520573f, + 0.6540711597f, 0.6447257262f, 0.6355138211f,}; + + /* interpolated lookup based fromdB function, domain -140dB to 0dB only */ + static float fromdBlook(float a){ + int i=(int)(a*((float)(-(1<<FROMdB2_SHIFT)))); + return (i<0) ? 1.f : ((i>=(FROMdB_LOOKUP_SZ<<FROMdB_SHIFT)) ? 0.f + : FROMdB_LOOKUP[i>>>FROMdB_SHIFT]*FROMdB2_LOOKUP[i&FROMdB2_MASK]); + } + +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Lpc.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Lpc.java new file mode 100644 index 0000000..aef5e99 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Lpc.java @@ -0,0 +1,188 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Lpc{ + // en/decode lookups + Drft fft=new Drft();; + + int ln; + int m; + + // Autocorrelation LPC coeff generation algorithm invented by + // N. Levinson in 1947, modified by J. Durbin in 1959. + + // Input : n elements of time doamin data + // Output: m lpc coefficients, excitation energy + + static float lpc_from_data(float[] data, float[] lpc, int n, int m){ + float[] aut=new float[m+1]; + float error; + int i, j; + + // autocorrelation, p+1 lag coefficients + + j=m+1; + while(j--!=0){ + float d=0; + for(i=j; i<n; i++) + d+=data[i]*data[i-j]; + aut[j]=d; + } + + // Generate lpc coefficients from autocorr values + + error=aut[0]; + /* + if(error==0){ + for(int k=0; k<m; k++) lpc[k]=0.0f; + return 0; + } + */ + + for(i=0; i<m; i++){ + float r=-aut[i+1]; + + if(error==0){ + for(int k=0; k<m; k++) + lpc[k]=0.0f; + return 0; + } + + // Sum up this iteration's reflection coefficient; note that in + // Vorbis we don't save it. If anyone wants to recycle this code + // and needs reflection coefficients, save the results of 'r' from + // each iteration. + + for(j=0; j<i; j++) + r-=lpc[j]*aut[i-j]; + r/=error; + + // Update LPC coefficients and total error + + lpc[i]=r; + for(j=0; j<i/2; j++){ + float tmp=lpc[j]; + lpc[j]+=r*lpc[i-1-j]; + lpc[i-1-j]+=r*tmp; + } + if(i%2!=0) + lpc[j]+=lpc[j]*r; + + error*=1.0-r*r; + } + + // we need the error value to know how big an impulse to hit the + // filter with later + + return error; + } + + // Input : n element envelope spectral curve + // Output: m lpc coefficients, excitation energy + + float lpc_from_curve(float[] curve, float[] lpc){ + int n=ln; + float[] work=new float[n+n]; + float fscale=(float)(.5/n); + int i, j; + + // input is a real curve. make it complex-real + // This mixes phase, but the LPC generation doesn't care. + for(i=0; i<n; i++){ + work[i*2]=curve[i]*fscale; + work[i*2+1]=0; + } + work[n*2-1]=curve[n-1]*fscale; + + n*=2; + fft.backward(work); + + // The autocorrelation will not be circular. Shift, else we lose + // most of the power in the edges. + + for(i=0, j=n/2; i<n/2;){ + float temp=work[i]; + work[i++]=work[j]; + work[j++]=temp; + } + + return (lpc_from_data(work, lpc, n, m)); + } + + void init(int mapped, int m){ + ln=mapped; + this.m=m; + + // we cheat decoding the LPC spectrum via FFTs + fft.init(mapped*2); + } + + void clear(){ + fft.clear(); + } + + static float FAST_HYPOT(float a, float b){ + return (float)Math.sqrt((a)*(a)+(b)*(b)); + } + + // One can do this the long way by generating the transfer function in + // the time domain and taking the forward FFT of the result. The + // results from direct calculation are cleaner and faster. + // + // This version does a linear curve generation and then later + // interpolates the log curve from the linear curve. + + void lpc_to_curve(float[] curve, float[] lpc, float amp){ + + for(int i=0; i<ln*2; i++) + curve[i]=0.0f; + + if(amp==0) + return; + + for(int i=0; i<m; i++){ + curve[i*2+1]=lpc[i]/(4*amp); + curve[i*2+2]=-lpc[i]/(4*amp); + } + + fft.backward(curve); + + { + int l2=ln*2; + float unit=(float)(1./amp); + curve[0]=(float)(1./(curve[0]*2+unit)); + for(int i=1; i<ln; i++){ + float real=(curve[i]+curve[l2-i]); + float imag=(curve[i]-curve[l2-i]); + + float a=real+unit; + curve[i]=(float)(1.0/FAST_HYPOT(a, imag)); + } + } + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Lsp.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Lsp.java new file mode 100644 index 0000000..e3b08fe --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Lsp.java @@ -0,0 +1,107 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +/* + function: LSP (also called LSF) conversion routines + + The LSP generation code is taken (with minimal modification) from + "On the Computation of the LSP Frequencies" by Joseph Rothweiler + <[email protected]>, available at: + + http://www2.xtdl.com/~rothwlr/lsfpaper/lsfpage.html + ********************************************************************/ + +class Lsp{ + + static final float M_PI=(float)(3.1415926539); + + static void lsp_to_curve(float[] curve, int[] map, int n, int ln, + float[] lsp, int m, float amp, float ampoffset){ + int i; + float wdel=M_PI/ln; + for(i=0; i<m; i++) + lsp[i]=Lookup.coslook(lsp[i]); + int m2=(m/2)*2; + + i=0; + while(i<n){ + int k=map[i]; + float p=.7071067812f; + float q=.7071067812f; + float w=Lookup.coslook(wdel*k); + + for(int j=0; j<m2; j+=2){ + q*=lsp[j]-w; + p*=lsp[j+1]-w; + } + + if((m&1)!=0){ + /* odd order filter; slightly assymetric */ + /* the last coefficient */ + q*=lsp[m-1]-w; + q*=q; + p*=p*(1.f-w*w); + } + else{ + /* even order filter; still symmetric */ + q*=q*(1.f+w); + p*=p*(1.f-w); + } + + // q=frexp(p+q,&qexp); + q=p+q; + int hx=Float.floatToIntBits(q); + int ix=0x7fffffff&hx; + int qexp=0; + + if(ix>=0x7f800000||(ix==0)){ + // 0,inf,nan + } + else{ + if(ix<0x00800000){ // subnormal + q*=3.3554432000e+07; // 0x4c000000 + hx=Float.floatToIntBits(q); + ix=0x7fffffff&hx; + qexp=-25; + } + qexp+=((ix>>>23)-126); + hx=(hx&0x807fffff)|0x3f000000; + q=Float.intBitsToFloat(hx); + } + + q=Lookup.fromdBlook(amp*Lookup.invsqlook(q)*Lookup.invsq2explook(qexp+m) + -ampoffset); + + do{ + curve[i++]*=q; + } + while(i<n&&map[i]==k); + + } + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Mapping0.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Mapping0.java new file mode 100644 index 0000000..d1bc10b --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Mapping0.java @@ -0,0 +1,375 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Mapping0 extends FuncMapping{ + static int seq=0; + + void free_info(Object imap){ + }; + + void free_look(Object imap){ + } + + Object look(DspState vd, InfoMode vm, Object m){ + //System.err.println("Mapping0.look"); + Info vi=vd.vi; + LookMapping0 look=new LookMapping0(); + InfoMapping0 info=look.map=(InfoMapping0)m; + look.mode=vm; + + look.time_look=new Object[info.submaps]; + look.floor_look=new Object[info.submaps]; + look.residue_look=new Object[info.submaps]; + + look.time_func=new FuncTime[info.submaps]; + look.floor_func=new FuncFloor[info.submaps]; + look.residue_func=new FuncResidue[info.submaps]; + + for(int i=0; i<info.submaps; i++){ + int timenum=info.timesubmap[i]; + int floornum=info.floorsubmap[i]; + int resnum=info.residuesubmap[i]; + + look.time_func[i]=FuncTime.time_P[vi.time_type[timenum]]; + look.time_look[i]=look.time_func[i].look(vd, vm, vi.time_param[timenum]); + look.floor_func[i]=FuncFloor.floor_P[vi.floor_type[floornum]]; + look.floor_look[i]=look.floor_func[i].look(vd, vm, + vi.floor_param[floornum]); + look.residue_func[i]=FuncResidue.residue_P[vi.residue_type[resnum]]; + look.residue_look[i]=look.residue_func[i].look(vd, vm, + vi.residue_param[resnum]); + + } + + if(vi.psys!=0&&vd.analysisp!=0){ + // ?? + } + + look.ch=vi.channels; + + return (look); + } + + void pack(Info vi, Object imap, Buffer opb){ + InfoMapping0 info=(InfoMapping0)imap; + + /* another 'we meant to do it this way' hack... up to beta 4, we + packed 4 binary zeros here to signify one submapping in use. We + now redefine that to mean four bitflags that indicate use of + deeper features; bit0:submappings, bit1:coupling, + bit2,3:reserved. This is backward compatable with all actual uses + of the beta code. */ + + if(info.submaps>1){ + opb.write(1, 1); + opb.write(info.submaps-1, 4); + } + else{ + opb.write(0, 1); + } + + if(info.coupling_steps>0){ + opb.write(1, 1); + opb.write(info.coupling_steps-1, 8); + for(int i=0; i<info.coupling_steps; i++){ + opb.write(info.coupling_mag[i], Util.ilog2(vi.channels)); + opb.write(info.coupling_ang[i], Util.ilog2(vi.channels)); + } + } + else{ + opb.write(0, 1); + } + + opb.write(0, 2); /* 2,3:reserved */ + + /* we don't write the channel submappings if we only have one... */ + if(info.submaps>1){ + for(int i=0; i<vi.channels; i++) + opb.write(info.chmuxlist[i], 4); + } + for(int i=0; i<info.submaps; i++){ + opb.write(info.timesubmap[i], 8); + opb.write(info.floorsubmap[i], 8); + opb.write(info.residuesubmap[i], 8); + } + } + + // also responsible for range checking + Object unpack(Info vi, Buffer opb){ + InfoMapping0 info=new InfoMapping0(); + + if(opb.read(1)!=0){ + info.submaps=opb.read(4)+1; + } + else{ + info.submaps=1; + } + + if(opb.read(1)!=0){ + info.coupling_steps=opb.read(8)+1; + + for(int i=0; i<info.coupling_steps; i++){ + int testM=info.coupling_mag[i]=opb.read(Util.ilog2(vi.channels)); + int testA=info.coupling_ang[i]=opb.read(Util.ilog2(vi.channels)); + + if(testM<0||testA<0||testM==testA||testM>=vi.channels + ||testA>=vi.channels){ + //goto err_out; + info.free(); + return (null); + } + } + } + + if(opb.read(2)>0){ /* 2,3:reserved */ + info.free(); + return (null); + } + + if(info.submaps>1){ + for(int i=0; i<vi.channels; i++){ + info.chmuxlist[i]=opb.read(4); + if(info.chmuxlist[i]>=info.submaps){ + info.free(); + return (null); + } + } + } + + for(int i=0; i<info.submaps; i++){ + info.timesubmap[i]=opb.read(8); + if(info.timesubmap[i]>=vi.times){ + info.free(); + return (null); + } + info.floorsubmap[i]=opb.read(8); + if(info.floorsubmap[i]>=vi.floors){ + info.free(); + return (null); + } + info.residuesubmap[i]=opb.read(8); + if(info.residuesubmap[i]>=vi.residues){ + info.free(); + return (null); + } + } + return info; + } + + float[][] pcmbundle=null; + int[] zerobundle=null; + int[] nonzero=null; + Object[] floormemo=null; + + synchronized int inverse(Block vb, Object l){ + DspState vd=vb.vd; + Info vi=vd.vi; + LookMapping0 look=(LookMapping0)l; + InfoMapping0 info=look.map; + InfoMode mode=look.mode; + int n=vb.pcmend=vi.blocksizes[vb.W]; + + float[] window=vd.window[vb.W][vb.lW][vb.nW][mode.windowtype]; + if(pcmbundle==null||pcmbundle.length<vi.channels){ + pcmbundle=new float[vi.channels][]; + nonzero=new int[vi.channels]; + zerobundle=new int[vi.channels]; + floormemo=new Object[vi.channels]; + } + + // time domain information decode (note that applying the + // information would have to happen later; we'll probably add a + // function entry to the harness for that later + // NOT IMPLEMENTED + + // recover the spectral envelope; store it in the PCM vector for now + for(int i=0; i<vi.channels; i++){ + float[] pcm=vb.pcm[i]; + int submap=info.chmuxlist[i]; + + floormemo[i]=look.floor_func[submap].inverse1(vb, + look.floor_look[submap], floormemo[i]); + if(floormemo[i]!=null){ + nonzero[i]=1; + } + else{ + nonzero[i]=0; + } + for(int j=0; j<n/2; j++){ + pcm[j]=0; + } + + } + + for(int i=0; i<info.coupling_steps; i++){ + if(nonzero[info.coupling_mag[i]]!=0||nonzero[info.coupling_ang[i]]!=0){ + nonzero[info.coupling_mag[i]]=1; + nonzero[info.coupling_ang[i]]=1; + } + } + + // recover the residue, apply directly to the spectral envelope + + for(int i=0; i<info.submaps; i++){ + int ch_in_bundle=0; + for(int j=0; j<vi.channels; j++){ + if(info.chmuxlist[j]==i){ + if(nonzero[j]!=0){ + zerobundle[ch_in_bundle]=1; + } + else{ + zerobundle[ch_in_bundle]=0; + } + pcmbundle[ch_in_bundle++]=vb.pcm[j]; + } + } + + look.residue_func[i].inverse(vb, look.residue_look[i], pcmbundle, + zerobundle, ch_in_bundle); + } + + for(int i=info.coupling_steps-1; i>=0; i--){ + float[] pcmM=vb.pcm[info.coupling_mag[i]]; + float[] pcmA=vb.pcm[info.coupling_ang[i]]; + + for(int j=0; j<n/2; j++){ + float mag=pcmM[j]; + float ang=pcmA[j]; + + if(mag>0){ + if(ang>0){ + pcmM[j]=mag; + pcmA[j]=mag-ang; + } + else{ + pcmA[j]=mag; + pcmM[j]=mag+ang; + } + } + else{ + if(ang>0){ + pcmM[j]=mag; + pcmA[j]=mag+ang; + } + else{ + pcmA[j]=mag; + pcmM[j]=mag-ang; + } + } + } + } + + // /* compute and apply spectral envelope */ + + for(int i=0; i<vi.channels; i++){ + float[] pcm=vb.pcm[i]; + int submap=info.chmuxlist[i]; + look.floor_func[submap].inverse2(vb, look.floor_look[submap], + floormemo[i], pcm); + } + + // transform the PCM data; takes PCM vector, vb; modifies PCM vector + // only MDCT right now.... + + for(int i=0; i<vi.channels; i++){ + float[] pcm=vb.pcm[i]; + //_analysis_output("out",seq+i,pcm,n/2,0,0); + ((Mdct)vd.transform[vb.W][0]).backward(pcm, pcm); + } + + // now apply the decoded pre-window time information + // NOT IMPLEMENTED + + // window the data + for(int i=0; i<vi.channels; i++){ + float[] pcm=vb.pcm[i]; + if(nonzero[i]!=0){ + for(int j=0; j<n; j++){ + pcm[j]*=window[j]; + } + } + else{ + for(int j=0; j<n; j++){ + pcm[j]=0.f; + } + } + } + + // now apply the decoded post-window time information + // NOT IMPLEMENTED + // all done! + return (0); + } + + class InfoMapping0{ + int submaps; // <= 16 + int[] chmuxlist=new int[256]; // up to 256 channels in a Vorbis stream + + int[] timesubmap=new int[16]; // [mux] + int[] floorsubmap=new int[16]; // [mux] submap to floors + int[] residuesubmap=new int[16];// [mux] submap to residue + int[] psysubmap=new int[16]; // [mux]; encode only + + int coupling_steps; + int[] coupling_mag=new int[256]; + int[] coupling_ang=new int[256]; + + void free(){ + chmuxlist=null; + timesubmap=null; + floorsubmap=null; + residuesubmap=null; + psysubmap=null; + + coupling_mag=null; + coupling_ang=null; + } + } + + class LookMapping0{ + InfoMode mode; + InfoMapping0 map; + Object[] time_look; + Object[] floor_look; + Object[] floor_state; + Object[] residue_look; + PsyLook[] psy_look; + + FuncTime[] time_func; + FuncFloor[] floor_func; + FuncResidue[] residue_func; + + int ch; + float[][] decay; + int lastframe; // if a different mode is called, we need to + // invalidate decay and floor state + } + +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Mdct.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Mdct.java new file mode 100644 index 0000000..e7657eb --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Mdct.java @@ -0,0 +1,250 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Mdct{ + + int n; + int log2n; + + float[] trig; + int[] bitrev; + + float scale; + + void init(int n){ + bitrev=new int[n/4]; + trig=new float[n+n/4]; + + log2n=(int)Math.rint(Math.log(n)/Math.log(2)); + this.n=n; + + int AE=0; + int AO=1; + int BE=AE+n/2; + int BO=BE+1; + int CE=BE+n/2; + int CO=CE+1; + // trig lookups... + for(int i=0; i<n/4; i++){ + trig[AE+i*2]=(float)Math.cos((Math.PI/n)*(4*i)); + trig[AO+i*2]=(float)-Math.sin((Math.PI/n)*(4*i)); + trig[BE+i*2]=(float)Math.cos((Math.PI/(2*n))*(2*i+1)); + trig[BO+i*2]=(float)Math.sin((Math.PI/(2*n))*(2*i+1)); + } + for(int i=0; i<n/8; i++){ + trig[CE+i*2]=(float)Math.cos((Math.PI/n)*(4*i+2)); + trig[CO+i*2]=(float)-Math.sin((Math.PI/n)*(4*i+2)); + } + + { + int mask=(1<<(log2n-1))-1; + int msb=1<<(log2n-2); + for(int i=0; i<n/8; i++){ + int acc=0; + for(int j=0; msb>>>j!=0; j++) + if(((msb>>>j)&i)!=0) + acc|=1<<j; + bitrev[i*2]=((~acc)&mask); + // bitrev[i*2]=((~acc)&mask)-1; + bitrev[i*2+1]=acc; + } + } + scale=4.f/n; + } + + void clear(){ + } + + void forward(float[] in, float[] out){ + } + + float[] _x=new float[1024]; + float[] _w=new float[1024]; + + synchronized void backward(float[] in, float[] out){ + if(_x.length<n/2){ + _x=new float[n/2]; + } + if(_w.length<n/2){ + _w=new float[n/2]; + } + float[] x=_x; + float[] w=_w; + int n2=n>>>1; + int n4=n>>>2; + int n8=n>>>3; + + // rotate + step 1 + { + int inO=1; + int xO=0; + int A=n2; + + int i; + for(i=0; i<n8; i++){ + A-=2; + x[xO++]=-in[inO+2]*trig[A+1]-in[inO]*trig[A]; + x[xO++]=in[inO]*trig[A+1]-in[inO+2]*trig[A]; + inO+=4; + } + + inO=n2-4; + + for(i=0; i<n8; i++){ + A-=2; + x[xO++]=in[inO]*trig[A+1]+in[inO+2]*trig[A]; + x[xO++]=in[inO]*trig[A]-in[inO+2]*trig[A+1]; + inO-=4; + } + } + + float[] xxx=mdct_kernel(x, w, n, n2, n4, n8); + int xx=0; + + // step 8 + + { + int B=n2; + int o1=n4, o2=o1-1; + int o3=n4+n2, o4=o3-1; + + for(int i=0; i<n4; i++){ + float temp1=(xxx[xx]*trig[B+1]-xxx[xx+1]*trig[B]); + float temp2=-(xxx[xx]*trig[B]+xxx[xx+1]*trig[B+1]); + + out[o1]=-temp1; + out[o2]=temp1; + out[o3]=temp2; + out[o4]=temp2; + + o1++; + o2--; + o3++; + o4--; + xx+=2; + B+=2; + } + } + } + + private float[] mdct_kernel(float[] x, float[] w, int n, int n2, int n4, + int n8){ + // step 2 + + int xA=n4; + int xB=0; + int w2=n4; + int A=n2; + + for(int i=0; i<n4;){ + float x0=x[xA]-x[xB]; + float x1; + w[w2+i]=x[xA++]+x[xB++]; + + x1=x[xA]-x[xB]; + A-=4; + + w[i++]=x0*trig[A]+x1*trig[A+1]; + w[i]=x1*trig[A]-x0*trig[A+1]; + + w[w2+i]=x[xA++]+x[xB++]; + i++; + } + + // step 3 + + { + for(int i=0; i<log2n-3; i++){ + int k0=n>>>(i+2); + int k1=1<<(i+3); + int wbase=n2-2; + + A=0; + float[] temp; + + for(int r=0; r<(k0>>>2); r++){ + int w1=wbase; + w2=w1-(k0>>1); + float AEv=trig[A], wA; + float AOv=trig[A+1], wB; + wbase-=2; + + k0++; + for(int s=0; s<(2<<i); s++){ + wB=w[w1]-w[w2]; + x[w1]=w[w1]+w[w2]; + + wA=w[++w1]-w[++w2]; + x[w1]=w[w1]+w[w2]; + + x[w2]=wA*AEv-wB*AOv; + x[w2-1]=wB*AEv+wA*AOv; + + w1-=k0; + w2-=k0; + } + k0--; + A+=k1; + } + + temp=w; + w=x; + x=temp; + } + } + + // step 4, 5, 6, 7 + { + int C=n; + int bit=0; + int x1=0; + int x2=n2-1; + + for(int i=0; i<n8; i++){ + int t1=bitrev[bit++]; + int t2=bitrev[bit++]; + + float wA=w[t1]-w[t2+1]; + float wB=w[t1-1]+w[t2]; + float wC=w[t1]+w[t2+1]; + float wD=w[t1-1]-w[t2]; + + float wACE=wA*trig[C]; + float wBCE=wB*trig[C++]; + float wACO=wA*trig[C]; + float wBCO=wB*trig[C++]; + + x[x1++]=(wC+wACO+wBCE)*.5f; + x[x2--]=(-wD+wBCO-wACE)*.5f; + x[x1++]=(wD+wBCO-wACE)*.5f; + x[x2--]=(wC-wACO-wBCE)*.5f; + } + } + return (x); + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/PsyInfo.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/PsyInfo.java new file mode 100644 index 0000000..63b250d --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/PsyInfo.java @@ -0,0 +1,74 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +// psychoacoustic setup +class PsyInfo{ + int athp; + int decayp; + int smoothp; + int noisefitp; + int noisefit_subblock; + float noisefit_threshdB; + + float ath_att; + + int tonemaskp; + float[] toneatt_125Hz=new float[5]; + float[] toneatt_250Hz=new float[5]; + float[] toneatt_500Hz=new float[5]; + float[] toneatt_1000Hz=new float[5]; + float[] toneatt_2000Hz=new float[5]; + float[] toneatt_4000Hz=new float[5]; + float[] toneatt_8000Hz=new float[5]; + + int peakattp; + float[] peakatt_125Hz=new float[5]; + float[] peakatt_250Hz=new float[5]; + float[] peakatt_500Hz=new float[5]; + float[] peakatt_1000Hz=new float[5]; + float[] peakatt_2000Hz=new float[5]; + float[] peakatt_4000Hz=new float[5]; + float[] peakatt_8000Hz=new float[5]; + + int noisemaskp; + float[] noiseatt_125Hz=new float[5]; + float[] noiseatt_250Hz=new float[5]; + float[] noiseatt_500Hz=new float[5]; + float[] noiseatt_1000Hz=new float[5]; + float[] noiseatt_2000Hz=new float[5]; + float[] noiseatt_4000Hz=new float[5]; + float[] noiseatt_8000Hz=new float[5]; + + float max_curve_dB; + + float attack_coeff; + float decay_coeff; + + void free(){ + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/PsyLook.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/PsyLook.java new file mode 100644 index 0000000..ff30571 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/PsyLook.java @@ -0,0 +1,42 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class PsyLook{ + int n; + PsyInfo vi; + + float[][][] tonecurves; + float[][] peakatt; + float[][][] noisecurves; + + float[] ath; + int[] octave; + + void init(PsyInfo vi, int n, int rate){ + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Residue0.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Residue0.java new file mode 100644 index 0000000..46ca59a --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Residue0.java @@ -0,0 +1,330 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Residue0 extends FuncResidue{ + void pack(Object vr, Buffer opb){ + InfoResidue0 info=(InfoResidue0)vr; + int acc=0; + opb.write(info.begin, 24); + opb.write(info.end, 24); + + opb.write(info.grouping-1, 24); /* residue vectors to group and + code with a partitioned book */ + opb.write(info.partitions-1, 6); /* possible partition choices */ + opb.write(info.groupbook, 8); /* group huffman book */ + + /* secondstages is a bitmask; as encoding progresses pass by pass, a + bitmask of one indicates this partition class has bits to write + this pass */ + for(int j=0; j<info.partitions; j++){ + int i=info.secondstages[j]; + if(Util.ilog(i)>3){ + /* yes, this is a minor hack due to not thinking ahead */ + opb.write(i, 3); + opb.write(1, 1); + opb.write(i>>>3, 5); + } + else{ + opb.write(i, 4); /* trailing zero */ + } + acc+=Util.icount(i); + } + for(int j=0; j<acc; j++){ + opb.write(info.booklist[j], 8); + } + } + + Object unpack(Info vi, Buffer opb){ + int acc=0; + InfoResidue0 info=new InfoResidue0(); + info.begin=opb.read(24); + info.end=opb.read(24); + info.grouping=opb.read(24)+1; + info.partitions=opb.read(6)+1; + info.groupbook=opb.read(8); + + for(int j=0; j<info.partitions; j++){ + int cascade=opb.read(3); + if(opb.read(1)!=0){ + cascade|=(opb.read(5)<<3); + } + info.secondstages[j]=cascade; + acc+=Util.icount(cascade); + } + + for(int j=0; j<acc; j++){ + info.booklist[j]=opb.read(8); + } + + if(info.groupbook>=vi.books){ + free_info(info); + return (null); + } + + for(int j=0; j<acc; j++){ + if(info.booklist[j]>=vi.books){ + free_info(info); + return (null); + } + } + return (info); + } + + Object look(DspState vd, InfoMode vm, Object vr){ + InfoResidue0 info=(InfoResidue0)vr; + LookResidue0 look=new LookResidue0(); + int acc=0; + int dim; + int maxstage=0; + look.info=info; + look.map=vm.mapping; + + look.parts=info.partitions; + look.fullbooks=vd.fullbooks; + look.phrasebook=vd.fullbooks[info.groupbook]; + + dim=look.phrasebook.dim; + + look.partbooks=new int[look.parts][]; + + for(int j=0; j<look.parts; j++){ + int i=info.secondstages[j]; + int stages=Util.ilog(i); + if(stages!=0){ + if(stages>maxstage) + maxstage=stages; + look.partbooks[j]=new int[stages]; + for(int k=0; k<stages; k++){ + if((i&(1<<k))!=0){ + look.partbooks[j][k]=info.booklist[acc++]; + } + } + } + } + + look.partvals=(int)Math.rint(Math.pow(look.parts, dim)); + look.stages=maxstage; + look.decodemap=new int[look.partvals][]; + for(int j=0; j<look.partvals; j++){ + int val=j; + int mult=look.partvals/look.parts; + look.decodemap[j]=new int[dim]; + + for(int k=0; k<dim; k++){ + int deco=val/mult; + val-=deco*mult; + mult/=look.parts; + look.decodemap[j][k]=deco; + } + } + return (look); + } + + void free_info(Object i){ + } + + void free_look(Object i){ + } + + private static int[][][] _01inverse_partword=new int[2][][]; // _01inverse is synchronized for + + // re-using partword + synchronized static int _01inverse(Block vb, Object vl, float[][] in, int ch, + int decodepart){ + int i, j, k, l, s; + LookResidue0 look=(LookResidue0)vl; + InfoResidue0 info=look.info; + + // move all this setup out later + int samples_per_partition=info.grouping; + int partitions_per_word=look.phrasebook.dim; + int n=info.end-info.begin; + + int partvals=n/samples_per_partition; + int partwords=(partvals+partitions_per_word-1)/partitions_per_word; + + if(_01inverse_partword.length<ch){ + _01inverse_partword=new int[ch][][]; + } + + for(j=0; j<ch; j++){ + if(_01inverse_partword[j]==null||_01inverse_partword[j].length<partwords){ + _01inverse_partword[j]=new int[partwords][]; + } + } + + for(s=0; s<look.stages; s++){ + // each loop decodes on partition codeword containing + // partitions_pre_word partitions + for(i=0, l=0; i<partvals; l++){ + if(s==0){ + // fetch the partition word for each channel + for(j=0; j<ch; j++){ + int temp=look.phrasebook.decode(vb.opb); + if(temp==-1){ + return (0); + } + _01inverse_partword[j][l]=look.decodemap[temp]; + if(_01inverse_partword[j][l]==null){ + return (0); + } + } + } + + // now we decode residual values for the partitions + for(k=0; k<partitions_per_word&&i<partvals; k++, i++) + for(j=0; j<ch; j++){ + int offset=info.begin+i*samples_per_partition; + int index=_01inverse_partword[j][l][k]; + if((info.secondstages[index]&(1<<s))!=0){ + CodeBook stagebook=look.fullbooks[look.partbooks[index][s]]; + if(stagebook!=null){ + if(decodepart==0){ + if(stagebook.decodevs_add(in[j], offset, vb.opb, + samples_per_partition)==-1){ + return (0); + } + } + else if(decodepart==1){ + if(stagebook.decodev_add(in[j], offset, vb.opb, + samples_per_partition)==-1){ + return (0); + } + } + } + } + } + } + } + return (0); + } + + static int[][] _2inverse_partword=null; + + synchronized static int _2inverse(Block vb, Object vl, float[][] in, int ch){ + int i, k, l, s; + LookResidue0 look=(LookResidue0)vl; + InfoResidue0 info=look.info; + + // move all this setup out later + int samples_per_partition=info.grouping; + int partitions_per_word=look.phrasebook.dim; + int n=info.end-info.begin; + + int partvals=n/samples_per_partition; + int partwords=(partvals+partitions_per_word-1)/partitions_per_word; + + if(_2inverse_partword==null||_2inverse_partword.length<partwords){ + _2inverse_partword=new int[partwords][]; + } + for(s=0; s<look.stages; s++){ + for(i=0, l=0; i<partvals; l++){ + if(s==0){ + // fetch the partition word for each channel + int temp=look.phrasebook.decode(vb.opb); + if(temp==-1){ + return (0); + } + _2inverse_partword[l]=look.decodemap[temp]; + if(_2inverse_partword[l]==null){ + return (0); + } + } + + // now we decode residual values for the partitions + for(k=0; k<partitions_per_word&&i<partvals; k++, i++){ + int offset=info.begin+i*samples_per_partition; + int index=_2inverse_partword[l][k]; + if((info.secondstages[index]&(1<<s))!=0){ + CodeBook stagebook=look.fullbooks[look.partbooks[index][s]]; + if(stagebook!=null){ + if(stagebook.decodevv_add(in, offset, ch, vb.opb, + samples_per_partition)==-1){ + return (0); + } + } + } + } + } + } + return (0); + } + + int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch){ + int used=0; + for(int i=0; i<ch; i++){ + if(nonzero[i]!=0){ + in[used++]=in[i]; + } + } + if(used!=0) + return (_01inverse(vb, vl, in, used, 0)); + else + return (0); + } + + class LookResidue0{ + InfoResidue0 info; + int map; + + int parts; + int stages; + CodeBook[] fullbooks; + CodeBook phrasebook; + int[][] partbooks; + + int partvals; + int[][] decodemap; + + int postbits; + int phrasebits; + int frames; + } + + class InfoResidue0{ + // block-partitioned VQ coded straight residue + int begin; + int end; + + // first stage (lossless partitioning) + int grouping; // group n vectors per partition + int partitions; // possible codebooks for a partition + int groupbook; // huffbook for partitioning + int[] secondstages=new int[64]; // expanded out to pointers in lookup + int[] booklist=new int[256]; // list of second stage books + + // encode-only heuristic settings + float[] entmax=new float[64]; // book entropy threshholds + float[] ampmax=new float[64]; // book amp threshholds + int[] subgrp=new int[64]; // book heuristic subgroup size + int[] blimit=new int[64]; // subgroup position limits + } + +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Residue1.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Residue1.java new file mode 100644 index 0000000..294e131 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Residue1.java @@ -0,0 +1,45 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Residue1 extends Residue0{ + + int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch){ + int used=0; + for(int i=0; i<ch; i++){ + if(nonzero[i]!=0){ + in[used++]=in[i]; + } + } + if(used!=0){ + return (_01inverse(vb, vl, in, used, 1)); + } + else{ + return 0; + } + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Residue2.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Residue2.java new file mode 100644 index 0000000..613cc7d --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Residue2.java @@ -0,0 +1,41 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Residue2 extends Residue0{ + + int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch){ + int i=0; + for(i=0; i<ch; i++) + if(nonzero[i]!=0) + break; + if(i==ch) + return (0); /* no nonzero vectors */ + + return (_2inverse(vb, vl, in, ch)); + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/StaticCodeBook.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/StaticCodeBook.java new file mode 100644 index 0000000..fea49c7 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/StaticCodeBook.java @@ -0,0 +1,443 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class StaticCodeBook{ + int dim; // codebook dimensions (elements per vector) + int entries; // codebook entries + int[] lengthlist; // codeword lengths in bits + + // mapping + int maptype; // 0=none + // 1=implicitly populated values from map column + // 2=listed arbitrary values + + // The below does a linear, single monotonic sequence mapping. + int q_min; // packed 32 bit float; quant value 0 maps to minval + int q_delta; // packed 32 bit float; val 1 - val 0 == delta + int q_quant; // bits: 0 < quant <= 16 + int q_sequencep; // bitflag + + // additional information for log (dB) mapping; the linear mapping + // is assumed to actually be values in dB. encodebias is used to + // assign an error weight to 0 dB. We have two additional flags: + // zeroflag indicates if entry zero is to represent -Inf dB; negflag + // indicates if we're to represent negative linear values in a + // mirror of the positive mapping. + + int[] quantlist; // map == 1: (int)(entries/dim) element column map + // map == 2: list of dim*entries quantized entry vals + + StaticCodeBook(){ + } + + int pack(Buffer opb){ + int i; + boolean ordered=false; + + opb.write(0x564342, 24); + opb.write(dim, 16); + opb.write(entries, 24); + + // pack the codewords. There are two packings; length ordered and + // length random. Decide between the two now. + + for(i=1; i<entries; i++){ + if(lengthlist[i]<lengthlist[i-1]) + break; + } + if(i==entries) + ordered=true; + + if(ordered){ + // length ordered. We only need to say how many codewords of + // each length. The actual codewords are generated + // deterministically + + int count=0; + opb.write(1, 1); // ordered + opb.write(lengthlist[0]-1, 5); // 1 to 32 + + for(i=1; i<entries; i++){ + int _this=lengthlist[i]; + int _last=lengthlist[i-1]; + if(_this>_last){ + for(int j=_last; j<_this; j++){ + opb.write(i-count, Util.ilog(entries-count)); + count=i; + } + } + } + opb.write(i-count, Util.ilog(entries-count)); + } + else{ + // length random. Again, we don't code the codeword itself, just + // the length. This time, though, we have to encode each length + opb.write(0, 1); // unordered + + // algortihmic mapping has use for 'unused entries', which we tag + // here. The algorithmic mapping happens as usual, but the unused + // entry has no codeword. + for(i=0; i<entries; i++){ + if(lengthlist[i]==0) + break; + } + + if(i==entries){ + opb.write(0, 1); // no unused entries + for(i=0; i<entries; i++){ + opb.write(lengthlist[i]-1, 5); + } + } + else{ + opb.write(1, 1); // we have unused entries; thus we tag + for(i=0; i<entries; i++){ + if(lengthlist[i]==0){ + opb.write(0, 1); + } + else{ + opb.write(1, 1); + opb.write(lengthlist[i]-1, 5); + } + } + } + } + + // is the entry number the desired return value, or do we have a + // mapping? If we have a mapping, what type? + opb.write(maptype, 4); + switch(maptype){ + case 0: + // no mapping + break; + case 1: + case 2: + // implicitly populated value mapping + // explicitly populated value mapping + if(quantlist==null){ + // no quantlist? error + return (-1); + } + + // values that define the dequantization + opb.write(q_min, 32); + opb.write(q_delta, 32); + opb.write(q_quant-1, 4); + opb.write(q_sequencep, 1); + + { + int quantvals=0; + switch(maptype){ + case 1: + // a single column of (c->entries/c->dim) quantized values for + // building a full value list algorithmically (square lattice) + quantvals=maptype1_quantvals(); + break; + case 2: + // every value (c->entries*c->dim total) specified explicitly + quantvals=entries*dim; + break; + } + + // quantized values + for(i=0; i<quantvals; i++){ + opb.write(Math.abs(quantlist[i]), q_quant); + } + } + break; + default: + // error case; we don't have any other map types now + return (-1); + } + return (0); + } + + // unpacks a codebook from the packet buffer into the codebook struct, + // readies the codebook auxiliary structures for decode + int unpack(Buffer opb){ + int i; + //memset(s,0,sizeof(static_codebook)); + + // make sure alignment is correct + if(opb.read(24)!=0x564342){ + // goto _eofout; + clear(); + return (-1); + } + + // first the basic parameters + dim=opb.read(16); + entries=opb.read(24); + if(entries==-1){ + // goto _eofout; + clear(); + return (-1); + } + + // codeword ordering.... length ordered or unordered? + switch(opb.read(1)){ + case 0: + // unordered + lengthlist=new int[entries]; + + // allocated but unused entries? + if(opb.read(1)!=0){ + // yes, unused entries + + for(i=0; i<entries; i++){ + if(opb.read(1)!=0){ + int num=opb.read(5); + if(num==-1){ + // goto _eofout; + clear(); + return (-1); + } + lengthlist[i]=num+1; + } + else{ + lengthlist[i]=0; + } + } + } + else{ + // all entries used; no tagging + for(i=0; i<entries; i++){ + int num=opb.read(5); + if(num==-1){ + // goto _eofout; + clear(); + return (-1); + } + lengthlist[i]=num+1; + } + } + break; + case 1: + // ordered + { + int length=opb.read(5)+1; + lengthlist=new int[entries]; + + for(i=0; i<entries;){ + int num=opb.read(Util.ilog(entries-i)); + if(num==-1){ + // goto _eofout; + clear(); + return (-1); + } + for(int j=0; j<num; j++, i++){ + lengthlist[i]=length; + } + length++; + } + } + break; + default: + // EOF + return (-1); + } + + // Do we have a mapping to unpack? + switch((maptype=opb.read(4))){ + case 0: + // no mapping + break; + case 1: + case 2: + // implicitly populated value mapping + // explicitly populated value mapping + q_min=opb.read(32); + q_delta=opb.read(32); + q_quant=opb.read(4)+1; + q_sequencep=opb.read(1); + + { + int quantvals=0; + switch(maptype){ + case 1: + quantvals=maptype1_quantvals(); + break; + case 2: + quantvals=entries*dim; + break; + } + + // quantized values + quantlist=new int[quantvals]; + for(i=0; i<quantvals; i++){ + quantlist[i]=opb.read(q_quant); + } + if(quantlist[quantvals-1]==-1){ + // goto _eofout; + clear(); + return (-1); + } + } + break; + default: + // goto _eofout; + clear(); + return (-1); + } + // all set + return (0); + // _errout: + // _eofout: + // vorbis_staticbook_clear(s); + // return(-1); + } + + // there might be a straightforward one-line way to do the below + // that's portable and totally safe against roundoff, but I haven't + // thought of it. Therefore, we opt on the side of caution + private int maptype1_quantvals(){ + int vals=(int)(Math.floor(Math.pow(entries, 1./dim))); + + // the above *should* be reliable, but we'll not assume that FP is + // ever reliable when bitstream sync is at stake; verify via integer + // means that vals really is the greatest value of dim for which + // vals^b->bim <= b->entries + // treat the above as an initial guess + while(true){ + int acc=1; + int acc1=1; + for(int i=0; i<dim; i++){ + acc*=vals; + acc1*=vals+1; + } + if(acc<=entries&&acc1>entries){ + return (vals); + } + else{ + if(acc>entries){ + vals--; + } + else{ + vals++; + } + } + } + } + + void clear(){ + } + + // unpack the quantized list of values for encode/decode + // we need to deal with two map types: in map type 1, the values are + // generated algorithmically (each column of the vector counts through + // the values in the quant vector). in map type 2, all the values came + // in in an explicit list. Both value lists must be unpacked + float[] unquantize(){ + + if(maptype==1||maptype==2){ + int quantvals; + float mindel=float32_unpack(q_min); + float delta=float32_unpack(q_delta); + float[] r=new float[entries*dim]; + + // maptype 1 and 2 both use a quantized value vector, but + // different sizes + switch(maptype){ + case 1: + // most of the time, entries%dimensions == 0, but we need to be + // well defined. We define that the possible vales at each + // scalar is values == entries/dim. If entries%dim != 0, we'll + // have 'too few' values (values*dim<entries), which means that + // we'll have 'left over' entries; left over entries use zeroed + // values (and are wasted). So don't generate codebooks like that + quantvals=maptype1_quantvals(); + for(int j=0; j<entries; j++){ + float last=0.f; + int indexdiv=1; + for(int k=0; k<dim; k++){ + int index=(j/indexdiv)%quantvals; + float val=quantlist[index]; + val=Math.abs(val)*delta+mindel+last; + if(q_sequencep!=0) + last=val; + r[j*dim+k]=val; + indexdiv*=quantvals; + } + } + break; + case 2: + for(int j=0; j<entries; j++){ + float last=0.f; + for(int k=0; k<dim; k++){ + float val=quantlist[j*dim+k]; + //if((j*dim+k)==0){System.err.println(" | 0 -> "+val+" | ");} + val=Math.abs(val)*delta+mindel+last; + if(q_sequencep!=0) + last=val; + r[j*dim+k]=val; + //if((j*dim+k)==0){System.err.println(" $ r[0] -> "+r[0]+" | ");} + } + } + //System.err.println("\nr[0]="+r[0]); + } + return (r); + } + return (null); + } + + // 32 bit float (not IEEE; nonnormalized mantissa + + // biased exponent) : neeeeeee eeemmmmm mmmmmmmm mmmmmmmm + // Why not IEEE? It's just not that important here. + + static final int VQ_FEXP=10; + static final int VQ_FMAN=21; + static final int VQ_FEXP_BIAS=768; // bias toward values smaller than 1. + + // doesn't currently guard under/overflow + static long float32_pack(float val){ + int sign=0; + int exp; + int mant; + if(val<0){ + sign=0x80000000; + val=-val; + } + exp=(int)Math.floor(Math.log(val)/Math.log(2)); + mant=(int)Math.rint(Math.pow(val, (VQ_FMAN-1)-exp)); + exp=(exp+VQ_FEXP_BIAS)<<VQ_FMAN; + return (sign|exp|mant); + } + + static float float32_unpack(int val){ + float mant=val&0x1fffff; + float exp=(val&0x7fe00000)>>>VQ_FMAN; + if((val&0x80000000)!=0) + mant=-mant; + return (ldexp(mant, ((int)exp)-(VQ_FMAN-1)-VQ_FEXP_BIAS)); + } + + static float ldexp(float foo, int e){ + return (float)(foo*Math.pow(2, e)); + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Time0.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Time0.java new file mode 100644 index 0000000..fef6549 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Time0.java @@ -0,0 +1,52 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Time0 extends FuncTime{ + void pack(Object i, Buffer opb){ + } + + Object unpack(Info vi, Buffer opb){ + return ""; + } + + Object look(DspState vd, InfoMode mi, Object i){ + return ""; + } + + void free_info(Object i){ + } + + void free_look(Object i){ + } + + int inverse(Block vb, Object i, float[] in, float[] out){ + return 0; + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/Util.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Util.java new file mode 100644 index 0000000..983c568 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/Util.java @@ -0,0 +1,30 @@ +package com.jcraft.jorbis; + +class Util{ + static int ilog(int v){ + int ret=0; + while(v!=0){ + ret++; + v>>>=1; + } + return (ret); + } + + static int ilog2(int v){ + int ret=0; + while(v>1){ + ret++; + v>>>=1; + } + return (ret); + } + + static int icount(int v){ + int ret=0; + while(v!=0){ + ret+=(v&1); + v>>>=1; + } + return (ret); + } +} diff --git a/ardor3d-audio/src/main/java/com/jcraft/jorbis/VorbisFile.java b/ardor3d-audio/src/main/java/com/jcraft/jorbis/VorbisFile.java new file mode 100644 index 0000000..f2e7336 --- /dev/null +++ b/ardor3d-audio/src/main/java/com/jcraft/jorbis/VorbisFile.java @@ -0,0 +1,1397 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<[email protected]> + * + * Many thanks to + * Monty <[email protected]> and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +import java.io.InputStream; +import java.io.IOException; + +public class VorbisFile{ + static final int CHUNKSIZE=8500; + static final int SEEK_SET=0; + static final int SEEK_CUR=1; + static final int SEEK_END=2; + + static final int OV_FALSE=-1; + static final int OV_EOF=-2; + static final int OV_HOLE=-3; + + static final int OV_EREAD=-128; + static final int OV_EFAULT=-129; + static final int OV_EIMPL=-130; + static final int OV_EINVAL=-131; + static final int OV_ENOTVORBIS=-132; + static final int OV_EBADHEADER=-133; + static final int OV_EVERSION=-134; + static final int OV_ENOTAUDIO=-135; + static final int OV_EBADPACKET=-136; + static final int OV_EBADLINK=-137; + static final int OV_ENOSEEK=-138; + + InputStream datasource; + boolean seekable=false; + long offset; + long end; + + SyncState oy=new SyncState(); + + int links; + long[] offsets; + long[] dataoffsets; + int[] serialnos; + long[] pcmlengths; + Info[] vi; + Comment[] vc; + + // Decoding working state local storage + long pcm_offset; + boolean decode_ready=false; + + int current_serialno; + int current_link; + + float bittrack; + float samptrack; + + StreamState os=new StreamState(); // take physical pages, weld into a logical + // stream of packets + DspState vd=new DspState(); // central working state for + // the packet->PCM decoder + Block vb=new Block(vd); // local working space for packet->PCM decode + + //ov_callbacks callbacks; + + public VorbisFile(String file) throws JOrbisException{ + super(); + InputStream is=null; + try{ + is=new SeekableInputStream(file); + int ret=open(is, null, 0); + if(ret==-1){ + throw new JOrbisException("VorbisFile: open return -1"); + } + } + catch(Exception e){ + throw new JOrbisException("VorbisFile: "+e.toString()); + } + finally{ + if(is!=null){ + try{ + is.close(); + } + catch(IOException e){ + e.printStackTrace(); + } + } + } + } + + public VorbisFile(InputStream is, byte[] initial, int ibytes) + throws JOrbisException{ + super(); + int ret=open(is, initial, ibytes); + if(ret==-1){ + } + } + + private int get_data(){ + int index=oy.buffer(CHUNKSIZE); + byte[] buffer=oy.data; + int bytes=0; + try{ + bytes=datasource.read(buffer, index, CHUNKSIZE); + } + catch(Exception e){ + return OV_EREAD; + } + oy.wrote(bytes); + if(bytes==-1){ + bytes=0; + } + return bytes; + } + + private void seek_helper(long offst){ + fseek(datasource, offst, SEEK_SET); + this.offset=offst; + oy.reset(); + } + + private int get_next_page(Page page, long boundary){ + if(boundary>0) + boundary+=offset; + while(true){ + int more; + if(boundary>0&&offset>=boundary) + return OV_FALSE; + more=oy.pageseek(page); + if(more<0){ + offset-=more; + } + else{ + if(more==0){ + if(boundary==0) + return OV_FALSE; + int ret=get_data(); + if(ret==0) + return OV_EOF; + if(ret<0) + return OV_EREAD; + } + else{ + int ret=(int)offset; //!!! + offset+=more; + return ret; + } + } + } + } + + private int get_prev_page(Page page) throws JOrbisException{ + long begin=offset; //!!! + int ret; + int offst=-1; + while(offst==-1){ + begin-=CHUNKSIZE; + if(begin<0) + begin=0; + seek_helper(begin); + while(offset<begin+CHUNKSIZE){ + ret=get_next_page(page, begin+CHUNKSIZE-offset); + if(ret==OV_EREAD){ + return OV_EREAD; + } + if(ret<0){ + if(offst==-1) + throw new JOrbisException(); + break; + } + else{ + offst=ret; + } + } + } + seek_helper(offst); //!!! + ret=get_next_page(page, CHUNKSIZE); + if(ret<0){ + return OV_EFAULT; + } + return offst; + } + + int bisect_forward_serialno(long begin, long searched, long end, + int currentno, int m){ + long endsearched=end; + long next=end; + Page page=new Page(); + int ret; + + while(searched<endsearched){ + long bisect; + if(endsearched-searched<CHUNKSIZE){ + bisect=searched; + } + else{ + bisect=(searched+endsearched)/2; + } + + seek_helper(bisect); + ret=get_next_page(page, -1); + if(ret==OV_EREAD) + return OV_EREAD; + if(ret<0||page.serialno()!=currentno){ + endsearched=bisect; + if(ret>=0) + next=ret; + } + else{ + searched=ret+page.header_len+page.body_len; + } + } + seek_helper(next); + ret=get_next_page(page, -1); + if(ret==OV_EREAD) + return OV_EREAD; + + if(searched>=end||ret==-1){ + links=m+1; + offsets=new long[m+2]; + offsets[m+1]=searched; + } + else{ + ret=bisect_forward_serialno(next, offset, end, page.serialno(), m+1); + if(ret==OV_EREAD) + return OV_EREAD; + } + offsets[m]=begin; + return 0; + } + + // uses the local ogg_stream storage in vf; this is important for + // non-streaming input sources + int fetch_headers(Info vi, Comment vc, int[] serialno, Page og_ptr){ + Page og=new Page(); + Packet op=new Packet(); + int ret; + + if(og_ptr==null){ + ret=get_next_page(og, CHUNKSIZE); + if(ret==OV_EREAD) + return OV_EREAD; + if(ret<0) + return OV_ENOTVORBIS; + og_ptr=og; + } + + if(serialno!=null) + serialno[0]=og_ptr.serialno(); + + os.init(og_ptr.serialno()); + + // extract the initial header from the first page and verify that the + // Ogg bitstream is in fact Vorbis data + + vi.init(); + vc.init(); + + int i=0; + while(i<3){ + os.pagein(og_ptr); + while(i<3){ + int result=os.packetout(op); + if(result==0) + break; + if(result==-1){ + vi.clear(); + vc.clear(); + os.clear(); + return -1; + } + if(vi.synthesis_headerin(vc, op)!=0){ + vi.clear(); + vc.clear(); + os.clear(); + return -1; + } + i++; + } + if(i<3) + if(get_next_page(og_ptr, 1)<0){ + vi.clear(); + vc.clear(); + os.clear(); + return -1; + } + } + return 0; + } + + // last step of the OggVorbis_File initialization; get all the + // vorbis_info structs and PCM positions. Only called by the seekable + // initialization (local stream storage is hacked slightly; pay + // attention to how that's done) + void prefetch_all_headers(Info first_i, Comment first_c, int dataoffset) + throws JOrbisException{ + Page og=new Page(); + int ret; + + vi=new Info[links]; + vc=new Comment[links]; + dataoffsets=new long[links]; + pcmlengths=new long[links]; + serialnos=new int[links]; + + for(int i=0; i<links; i++){ + if(first_i!=null&&first_c!=null&&i==0){ + // we already grabbed the initial header earlier. This just + // saves the waste of grabbing it again + vi[i]=first_i; + vc[i]=first_c; + dataoffsets[i]=dataoffset; + } + else{ + // seek to the location of the initial header + seek_helper(offsets[i]); //!!! + vi[i]=new Info(); + vc[i]=new Comment(); + if(fetch_headers(vi[i], vc[i], null, null)==-1){ + dataoffsets[i]=-1; + } + else{ + dataoffsets[i]=offset; + os.clear(); + } + } + + // get the serial number and PCM length of this link. To do this, + // get the last page of the stream + { + long end=offsets[i+1]; //!!! + seek_helper(end); + + while(true){ + ret=get_prev_page(og); + if(ret==-1){ + // this should not be possible + vi[i].clear(); + vc[i].clear(); + break; + } + if(og.granulepos()!=-1){ + serialnos[i]=og.serialno(); + pcmlengths[i]=og.granulepos(); + break; + } + } + } + } + } + + private int make_decode_ready(){ + if(decode_ready) + System.exit(1); + vd.synthesis_init(vi[0]); + vb.init(vd); + decode_ready=true; + return (0); + } + + int open_seekable() throws JOrbisException{ + Info initial_i=new Info(); + Comment initial_c=new Comment(); + int serialno; + long end; + int ret; + int dataoffset; + Page og=new Page(); + // is this even vorbis...? + int[] foo=new int[1]; + ret=fetch_headers(initial_i, initial_c, foo, null); + serialno=foo[0]; + dataoffset=(int)offset; //!! + os.clear(); + if(ret==-1) + return (-1); + if(ret<0) + return (ret); + // we can seek, so set out learning all about this file + seekable=true; + fseek(datasource, 0, SEEK_END); + offset=ftell(datasource); + end=offset; + // We get the offset for the last page of the physical bitstream. + // Most OggVorbis files will contain a single logical bitstream + end=get_prev_page(og); + // moer than one logical bitstream? + if(og.serialno()!=serialno){ + // Chained bitstream. Bisect-search each logical bitstream + // section. Do so based on serial number only + if(bisect_forward_serialno(0, 0, end+1, serialno, 0)<0){ + clear(); + return OV_EREAD; + } + } + else{ + // Only one logical bitstream + if(bisect_forward_serialno(0, end, end+1, serialno, 0)<0){ + clear(); + return OV_EREAD; + } + } + prefetch_all_headers(initial_i, initial_c, dataoffset); + return 0; + } + + int open_nonseekable(){ + // we cannot seek. Set up a 'single' (current) logical bitstream entry + links=1; + vi=new Info[links]; + vi[0]=new Info(); // ?? + vc=new Comment[links]; + vc[0]=new Comment(); // ?? bug? + + // Try to fetch the headers, maintaining all the storage + int[] foo=new int[1]; + if(fetch_headers(vi[0], vc[0], foo, null)==-1) + return (-1); + current_serialno=foo[0]; + make_decode_ready(); + return 0; + } + + // clear out the current logical bitstream decoder + void decode_clear(){ + os.clear(); + vd.clear(); + vb.clear(); + decode_ready=false; + bittrack=0.f; + samptrack=0.f; + } + + // fetch and process a packet. Handles the case where we're at a + // bitstream boundary and dumps the decoding machine. If the decoding + // machine is unloaded, it loads it. It also keeps pcm_offset up to + // date (seek and read both use this. seek uses a special hack with + // readp). + // + // return: -1) hole in the data (lost packet) + // 0) need more date (only if readp==0)/eof + // 1) got a packet + + int process_packet(int readp){ + Page og=new Page(); + + // handle one packet. Try to fetch it from current stream state + // extract packets from page + while(true){ + // process a packet if we can. If the machine isn't loaded, + // neither is a page + if(decode_ready){ + Packet op=new Packet(); + int result=os.packetout(op); + long granulepos; + // if(result==-1)return(-1); // hole in the data. For now, swallow + // and go. We'll need to add a real + // error code in a bit. + if(result>0){ + // got a packet. process it + granulepos=op.granulepos; + if(vb.synthesis(op)==0){ // lazy check for lazy + // header handling. The + // header packets aren't + // audio, so if/when we + // submit them, + // vorbis_synthesis will + // reject them + // suck in the synthesis data and track bitrate + { + int oldsamples=vd.synthesis_pcmout(null, null); + vd.synthesis_blockin(vb); + samptrack+=vd.synthesis_pcmout(null, null)-oldsamples; + bittrack+=op.bytes*8; + } + + // update the pcm offset. + if(granulepos!=-1&&op.e_o_s==0){ + int link=(seekable ? current_link : 0); + int samples; + // this packet has a pcm_offset on it (the last packet + // completed on a page carries the offset) After processing + // (above), we know the pcm position of the *last* sample + // ready to be returned. Find the offset of the *first* + // + // As an aside, this trick is inaccurate if we begin + // reading anew right at the last page; the end-of-stream + // granulepos declares the last frame in the stream, and the + // last packet of the last page may be a partial frame. + // So, we need a previous granulepos from an in-sequence page + // to have a reference point. Thus the !op.e_o_s clause above + + samples=vd.synthesis_pcmout(null, null); + granulepos-=samples; + for(int i=0; i<link; i++){ + granulepos+=pcmlengths[i]; + } + pcm_offset=granulepos; + } + return (1); + } + } + } + + if(readp==0) + return (0); + if(get_next_page(og, -1)<0) + return (0); // eof. leave unitialized + + // bitrate tracking; add the header's bytes here, the body bytes + // are done by packet above + bittrack+=og.header_len*8; + + // has our decoding just traversed a bitstream boundary? + if(decode_ready){ + if(current_serialno!=og.serialno()){ + decode_clear(); + } + } + + // Do we need to load a new machine before submitting the page? + // This is different in the seekable and non-seekable cases. + // + // In the seekable case, we already have all the header + // information loaded and cached; we just initialize the machine + // with it and continue on our merry way. + // + // In the non-seekable (streaming) case, we'll only be at a + // boundary if we just left the previous logical bitstream and + // we're now nominally at the header of the next bitstream + + if(!decode_ready){ + int i; + if(seekable){ + current_serialno=og.serialno(); + + // match the serialno to bitstream section. We use this rather than + // offset positions to avoid problems near logical bitstream + // boundaries + for(i=0; i<links; i++){ + if(serialnos[i]==current_serialno) + break; + } + if(i==links) + return (-1); // sign of a bogus stream. error out, + // leave machine uninitialized + current_link=i; + + os.init(current_serialno); + os.reset(); + + } + else{ + // we're streaming + // fetch the three header packets, build the info struct + int foo[]=new int[1]; + int ret=fetch_headers(vi[0], vc[0], foo, og); + current_serialno=foo[0]; + if(ret!=0) + return ret; + current_link++; + i=0; + } + make_decode_ready(); + } + os.pagein(og); + } + } + + // The helpers are over; it's all toplevel interface from here on out + // clear out the OggVorbis_File struct + int clear(){ + vb.clear(); + vd.clear(); + os.clear(); + + if(vi!=null&&links!=0){ + for(int i=0; i<links; i++){ + vi[i].clear(); + vc[i].clear(); + } + vi=null; + vc=null; + } + if(dataoffsets!=null) + dataoffsets=null; + if(pcmlengths!=null) + pcmlengths=null; + if(serialnos!=null) + serialnos=null; + if(offsets!=null) + offsets=null; + oy.clear(); + + return (0); + } + + static int fseek(InputStream fis, long off, int whence){ + if(fis instanceof SeekableInputStream){ + SeekableInputStream sis=(SeekableInputStream)fis; + try{ + if(whence==SEEK_SET){ + sis.seek(off); + } + else if(whence==SEEK_END){ + sis.seek(sis.getLength()-off); + } + else{ + } + } + catch(Exception e){ + } + return 0; + } + try{ + if(whence==0){ + fis.reset(); + } + fis.skip(off); + } + catch(Exception e){ + return -1; + } + return 0; + } + + static long ftell(InputStream fis){ + try{ + if(fis instanceof SeekableInputStream){ + SeekableInputStream sis=(SeekableInputStream)fis; + return (sis.tell()); + } + } + catch(Exception e){ + } + return 0; + } + + // inspects the OggVorbis file and finds/documents all the logical + // bitstreams contained in it. Tries to be tolerant of logical + // bitstream sections that are truncated/woogie. + // + // return: -1) error + // 0) OK + + int open(InputStream is, byte[] initial, int ibytes) throws JOrbisException{ + return open_callbacks(is, initial, ibytes); + } + + int open_callbacks(InputStream is, byte[] initial, int ibytes//, callbacks callbacks + ) throws JOrbisException{ + int ret; + datasource=is; + + oy.init(); + + // perhaps some data was previously read into a buffer for testing + // against other stream types. Allow initialization from this + // previously read data (as we may be reading from a non-seekable + // stream) + if(initial!=null){ + int index=oy.buffer(ibytes); + System.arraycopy(initial, 0, oy.data, index, ibytes); + oy.wrote(ibytes); + } + // can we seek? Stevens suggests the seek test was portable + if(is instanceof SeekableInputStream){ + ret=open_seekable(); + } + else{ + ret=open_nonseekable(); + } + if(ret!=0){ + datasource=null; + clear(); + } + return ret; + } + + // How many logical bitstreams in this physical bitstream? + public int streams(){ + return links; + } + + // Is the FILE * associated with vf seekable? + public boolean seekable(){ + return seekable; + } + + // returns the bitrate for a given logical bitstream or the entire + // physical bitstream. If the file is open for random access, it will + // find the *actual* average bitrate. If the file is streaming, it + // returns the nominal bitrate (if set) else the average of the + // upper/lower bounds (if set) else -1 (unset). + // + // If you want the actual bitrate field settings, get them from the + // vorbis_info structs + + public int bitrate(int i){ + if(i>=links) + return (-1); + if(!seekable&&i!=0) + return (bitrate(0)); + if(i<0){ + long bits=0; + for(int j=0; j<links; j++){ + bits+=(offsets[j+1]-dataoffsets[j])*8; + } + return ((int)Math.rint(bits/time_total(-1))); + } + else{ + if(seekable){ + // return the actual bitrate + return ((int)Math.rint((offsets[i+1]-dataoffsets[i])*8/time_total(i))); + } + else{ + // return nominal if set + if(vi[i].bitrate_nominal>0){ + return vi[i].bitrate_nominal; + } + else{ + if(vi[i].bitrate_upper>0){ + if(vi[i].bitrate_lower>0){ + return (vi[i].bitrate_upper+vi[i].bitrate_lower)/2; + } + else{ + return vi[i].bitrate_upper; + } + } + return (-1); + } + } + } + } + + // returns the actual bitrate since last call. returns -1 if no + // additional data to offer since last call (or at beginning of stream) + public int bitrate_instant(){ + int _link=(seekable ? current_link : 0); + if(samptrack==0) + return (-1); + int ret=(int)(bittrack/samptrack*vi[_link].rate+.5); + bittrack=0.f; + samptrack=0.f; + return (ret); + } + + public int serialnumber(int i){ + if(i>=links) + return (-1); + if(!seekable&&i>=0) + return (serialnumber(-1)); + if(i<0){ + return (current_serialno); + } + else{ + return (serialnos[i]); + } + } + + // returns: total raw (compressed) length of content if i==-1 + // raw (compressed) length of that logical bitstream for i==0 to n + // -1 if the stream is not seekable (we can't know the length) + + public long raw_total(int i){ + if(!seekable||i>=links) + return (-1); + if(i<0){ + long acc=0; // bug? + for(int j=0; j<links; j++){ + acc+=raw_total(j); + } + return (acc); + } + else{ + return (offsets[i+1]-offsets[i]); + } + } + + // returns: total PCM length (samples) of content if i==-1 + // PCM length (samples) of that logical bitstream for i==0 to n + // -1 if the stream is not seekable (we can't know the length) + public long pcm_total(int i){ + if(!seekable||i>=links) + return (-1); + if(i<0){ + long acc=0; + for(int j=0; j<links; j++){ + acc+=pcm_total(j); + } + return (acc); + } + else{ + return (pcmlengths[i]); + } + } + + // returns: total seconds of content if i==-1 + // seconds in that logical bitstream for i==0 to n + // -1 if the stream is not seekable (we can't know the length) + public float time_total(int i){ + if(!seekable||i>=links) + return (-1); + if(i<0){ + float acc=0; + for(int j=0; j<links; j++){ + acc+=time_total(j); + } + return (acc); + } + else{ + return ((float)(pcmlengths[i])/vi[i].rate); + } + } + + // seek to an offset relative to the *compressed* data. This also + // immediately sucks in and decodes pages to update the PCM cursor. It + // will cross a logical bitstream boundary, but only if it can't get + // any packets out of the tail of the bitstream we seek to (so no + // surprises). + // + // returns zero on success, nonzero on failure + + public int raw_seek(int pos){ + if(!seekable) + return (-1); // don't dump machine if we can't seek + if(pos<0||pos>offsets[links]){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + + // clear out decoding machine state + pcm_offset=-1; + decode_clear(); + + // seek + seek_helper(pos); + + // we need to make sure the pcm_offset is set. We use the + // _fetch_packet helper to process one packet with readp set, then + // call it until it returns '0' with readp not set (the last packet + // from a page has the 'granulepos' field set, and that's how the + // helper updates the offset + + switch(process_packet(1)){ + case 0: + // oh, eof. There are no packets remaining. Set the pcm offset to + // the end of file + pcm_offset=pcm_total(-1); + return (0); + case -1: + // error! missing data or invalid bitstream structure + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + default: + // all OK + break; + } + while(true){ + switch(process_packet(0)){ + case 0: + // the offset is set. If it's a bogus bitstream with no offset + // information, it's not but that's not our fault. We still run + // gracefully, we're just missing the offset + return (0); + case -1: + // error! missing data or invalid bitstream structure + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + default: + // continue processing packets + break; + } + } + + // seek_error: + // dump the machine so we're in a known state + //pcm_offset=-1; + //decode_clear(); + //return -1; + } + + // seek to a sample offset relative to the decompressed pcm stream + // returns zero on success, nonzero on failure + + public int pcm_seek(long pos){ + int link=-1; + long total=pcm_total(-1); + + if(!seekable) + return (-1); // don't dump machine if we can't seek + if(pos<0||pos>total){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + + // which bitstream section does this pcm offset occur in? + for(link=links-1; link>=0; link--){ + total-=pcmlengths[link]; + if(pos>=total) + break; + } + + // search within the logical bitstream for the page with the highest + // pcm_pos preceeding (or equal to) pos. There is a danger here; + // missing pages or incorrect frame number information in the + // bitstream could make our task impossible. Account for that (it + // would be an error condition) + { + long target=pos-total; + long end=offsets[link+1]; + long begin=offsets[link]; + int best=(int)begin; + + Page og=new Page(); + while(begin<end){ + long bisect; + int ret; + + if(end-begin<CHUNKSIZE){ + bisect=begin; + } + else{ + bisect=(end+begin)/2; + } + + seek_helper(bisect); + ret=get_next_page(og, end-bisect); + + if(ret==-1){ + end=bisect; + } + else{ + long granulepos=og.granulepos(); + if(granulepos<target){ + best=ret; // raw offset of packet with granulepos + begin=offset; // raw offset of next packet + } + else{ + end=bisect; + } + } + } + // found our page. seek to it (call raw_seek). + if(raw_seek(best)!=0){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + } + + // verify result + if(pcm_offset>=pos){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + if(pos>pcm_total(-1)){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + + // discard samples until we reach the desired position. Crossing a + // logical bitstream boundary with abandon is OK. + while(pcm_offset<pos){ + int target=(int)(pos-pcm_offset); + float[][][] _pcm=new float[1][][]; + int[] _index=new int[getInfo(-1).channels]; + int samples=vd.synthesis_pcmout(_pcm, _index); + + if(samples>target) + samples=target; + vd.synthesis_read(samples); + pcm_offset+=samples; + + if(samples<target) + if(process_packet(1)==0){ + pcm_offset=pcm_total(-1); // eof + } + } + return 0; + + // seek_error: + // dump machine so we're in a known state + //pcm_offset=-1; + //decode_clear(); + //return -1; + } + + // seek to a playback time relative to the decompressed pcm stream + // returns zero on success, nonzero on failure + int time_seek(float seconds){ + // translate time to PCM position and call pcm_seek + + int link=-1; + long pcm_total=pcm_total(-1); + float time_total=time_total(-1); + + if(!seekable) + return (-1); // don't dump machine if we can't seek + if(seconds<0||seconds>time_total){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + + // which bitstream section does this time offset occur in? + for(link=links-1; link>=0; link--){ + pcm_total-=pcmlengths[link]; + time_total-=time_total(link); + if(seconds>=time_total) + break; + } + + // enough information to convert time offset to pcm offset + { + long target=(long)(pcm_total+(seconds-time_total)*vi[link].rate); + return (pcm_seek(target)); + } + + //seek_error: + // dump machine so we're in a known state + //pcm_offset=-1; + //decode_clear(); + //return -1; + } + + // tell the current stream offset cursor. Note that seek followed by + // tell will likely not give the set offset due to caching + public long raw_tell(){ + return (offset); + } + + // return PCM offset (sample) of next PCM sample to be read + public long pcm_tell(){ + return (pcm_offset); + } + + // return time offset (seconds) of next PCM sample to be read + public float time_tell(){ + // translate time to PCM position and call pcm_seek + + int link=-1; + long pcm_total=0; + float time_total=0.f; + + if(seekable){ + pcm_total=pcm_total(-1); + time_total=time_total(-1); + + // which bitstream section does this time offset occur in? + for(link=links-1; link>=0; link--){ + pcm_total-=pcmlengths[link]; + time_total-=time_total(link); + if(pcm_offset>=pcm_total) + break; + } + } + + return ((float)time_total+(float)(pcm_offset-pcm_total)/vi[link].rate); + } + + // link: -1) return the vorbis_info struct for the bitstream section + // currently being decoded + // 0-n) to request information for a specific bitstream section + // + // In the case of a non-seekable bitstream, any call returns the + // current bitstream. NULL in the case that the machine is not + // initialized + + public Info getInfo(int link){ + if(seekable){ + if(link<0){ + if(decode_ready){ + return vi[current_link]; + } + else{ + return null; + } + } + else{ + if(link>=links){ + return null; + } + else{ + return vi[link]; + } + } + } + else{ + if(decode_ready){ + return vi[0]; + } + else{ + return null; + } + } + } + + public Comment getComment(int link){ + if(seekable){ + if(link<0){ + if(decode_ready){ + return vc[current_link]; + } + else{ + return null; + } + } + else{ + if(link>=links){ + return null; + } + else{ + return vc[link]; + } + } + } + else{ + if(decode_ready){ + return vc[0]; + } + else{ + return null; + } + } + } + + int host_is_big_endian(){ + return 1; + // short pattern = 0xbabe; + // unsigned char *bytewise = (unsigned char *)&pattern; + // if (bytewise[0] == 0xba) return 1; + // assert(bytewise[0] == 0xbe); + // return 0; + } + + // up to this point, everything could more or less hide the multiple + // logical bitstream nature of chaining from the toplevel application + // if the toplevel application didn't particularly care. However, at + // the point that we actually read audio back, the multiple-section + // nature must surface: Multiple bitstream sections do not necessarily + // have to have the same number of channels or sampling rate. + // + // read returns the sequential logical bitstream number currently + // being decoded along with the PCM data in order that the toplevel + // application can take action on channel/sample rate changes. This + // number will be incremented even for streamed (non-seekable) streams + // (for seekable streams, it represents the actual logical bitstream + // index within the physical bitstream. Note that the accessor + // functions above are aware of this dichotomy). + // + // input values: buffer) a buffer to hold packed PCM data for return + // length) the byte length requested to be placed into buffer + // bigendianp) should the data be packed LSB first (0) or + // MSB first (1) + // word) word size for output. currently 1 (byte) or + // 2 (16 bit short) + // + // return values: -1) error/hole in data + // 0) EOF + // n) number of bytes of PCM actually returned. The + // below works on a packet-by-packet basis, so the + // return length is not related to the 'length' passed + // in, just guaranteed to fit. + // + // *section) set to the logical bitstream number + + int read(byte[] buffer, int length, int bigendianp, int word, int sgned, + int[] bitstream){ + int host_endian=host_is_big_endian(); + int index=0; + + while(true){ + if(decode_ready){ + float[][] pcm; + float[][][] _pcm=new float[1][][]; + int[] _index=new int[getInfo(-1).channels]; + int samples=vd.synthesis_pcmout(_pcm, _index); + pcm=_pcm[0]; + if(samples!=0){ + // yay! proceed to pack data into the byte buffer + int channels=getInfo(-1).channels; + int bytespersample=word*channels; + if(samples>length/bytespersample) + samples=length/bytespersample; + + // a tight loop to pack each size + { + int val; + if(word==1){ + int off=(sgned!=0 ? 0 : 128); + for(int j=0; j<samples; j++){ + for(int i=0; i<channels; i++){ + val=(int)(pcm[i][_index[i]+j]*128.+0.5); + if(val>127) + val=127; + else if(val<-128) + val=-128; + buffer[index++]=(byte)(val+off); + } + } + } + else{ + int off=(sgned!=0 ? 0 : 32768); + + if(host_endian==bigendianp){ + if(sgned!=0){ + for(int i=0; i<channels; i++){ // It's faster in this order + int src=_index[i]; + int dest=i; + for(int j=0; j<samples; j++){ + val=(int)(pcm[i][src+j]*32768.+0.5); + if(val>32767) + val=32767; + else if(val<-32768) + val=-32768; + buffer[dest]=(byte)(val>>>8); + buffer[dest+1]=(byte)(val); + dest+=channels*2; + } + } + } + else{ + for(int i=0; i<channels; i++){ + float[] src=pcm[i]; + int dest=i; + for(int j=0; j<samples; j++){ + val=(int)(src[j]*32768.+0.5); + if(val>32767) + val=32767; + else if(val<-32768) + val=-32768; + buffer[dest]=(byte)((val+off)>>>8); + buffer[dest+1]=(byte)(val+off); + dest+=channels*2; + } + } + } + } + else if(bigendianp!=0){ + for(int j=0; j<samples; j++){ + for(int i=0; i<channels; i++){ + val=(int)(pcm[i][j]*32768.+0.5); + if(val>32767) + val=32767; + else if(val<-32768) + val=-32768; + val+=off; + buffer[index++]=(byte)(val>>>8); + buffer[index++]=(byte)val; + } + } + } + else{ + //int val; + for(int j=0; j<samples; j++){ + for(int i=0; i<channels; i++){ + val=(int)(pcm[i][j]*32768.+0.5); + if(val>32767) + val=32767; + else if(val<-32768) + val=-32768; + val+=off; + buffer[index++]=(byte)val; + buffer[index++]=(byte)(val>>>8); + } + } + } + } + } + + vd.synthesis_read(samples); + pcm_offset+=samples; + if(bitstream!=null) + bitstream[0]=current_link; + return (samples*bytespersample); + } + } + + // suck in another packet + switch(process_packet(1)){ + case 0: + return (0); + case -1: + return -1; + default: + break; + } + } + } + + public Info[] getInfo(){ + return vi; + } + + public Comment[] getComment(){ + return vc; + } + + public void close() throws java.io.IOException{ + datasource.close(); + } + + class SeekableInputStream extends InputStream{ + java.io.RandomAccessFile raf=null; + final String mode="r"; + + SeekableInputStream(String file) throws java.io.IOException{ + raf=new java.io.RandomAccessFile(file, mode); + } + + public int read() throws java.io.IOException{ + return raf.read(); + } + + public int read(byte[] buf) throws java.io.IOException{ + return raf.read(buf); + } + + public int read(byte[] buf, int s, int len) throws java.io.IOException{ + return raf.read(buf, s, len); + } + + public long skip(long n) throws java.io.IOException{ + return (long)(raf.skipBytes((int)n)); + } + + public long getLength() throws java.io.IOException{ + return raf.length(); + } + + public long tell() throws java.io.IOException{ + return raf.getFilePointer(); + } + + public int available() throws java.io.IOException{ + return (raf.length()==raf.getFilePointer()) ? 0 : 1; + } + + public void close() throws java.io.IOException{ + raf.close(); + } + + public synchronized void mark(int m){ + } + + public synchronized void reset() throws java.io.IOException{ + } + + public boolean markSupported(){ + return false; + } + + public void seek(long pos) throws java.io.IOException{ + raf.seek(pos); + } + } + +} diff --git a/settings.gradle b/settings.gradle index 4b20feb..afa7147 100644 --- a/settings.gradle +++ b/settings.gradle @@ -18,6 +18,7 @@ include ':ardor3d-examples' include ':ardor3d-performance' include ':distribution' include ':ardor3d-craft' +include ':ardor3d-audio' project(':ardor3d-savable').projectDir = "$rootDir/ardor3d-savable" as File project(':ardor3d-math').projectDir = "$rootDir/ardor3d-math" as File @@ -37,4 +38,5 @@ project(':ardor3d-terrain').projectDir = "$rootDir/ardor3d-terrain" as File project(':ardor3d-examples').projectDir = "$rootDir/ardor3d-examples" as File project(':ardor3d-performance').projectDir = "$rootDir/ardor3d-performance" as File project(':distribution').projectDir = "$rootDir/ardor3d-distribution" as File -project(':ardor3d-craft').projectDir = "$rootDir/ardor3d-craft" as File
\ No newline at end of file +project(':ardor3d-craft').projectDir = "$rootDir/ardor3d-craft" as File +project(':ardor3d-audio').projectDir = "$rootDir/ardor3d-audio" as File |