From bd93dd58d0a09e55298cfbdd05f69becf59ab4e8 Mon Sep 17 00:00:00 2001 From: LexManos Date: Sun, 5 Aug 2012 03:23:38 -0700 Subject: [PATCH] Missed a file --- client/paulscode/sound/codecs/CodecIBXM.java | 642 +++++++++++++++++++ 1 file changed, 642 insertions(+) create mode 100644 client/paulscode/sound/codecs/CodecIBXM.java diff --git a/client/paulscode/sound/codecs/CodecIBXM.java b/client/paulscode/sound/codecs/CodecIBXM.java new file mode 100644 index 000000000..4bb032d8d --- /dev/null +++ b/client/paulscode/sound/codecs/CodecIBXM.java @@ -0,0 +1,642 @@ +package paulscode.sound.codecs; + +import java.io.DataInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; +import javax.sound.sampled.AudioFormat; + +import paulscode.sound.ICodec; +import paulscode.sound.SoundBuffer; +import paulscode.sound.SoundSystemConfig; +import paulscode.sound.SoundSystemLogger; + +import ibxm.FastTracker2; +import ibxm.IBXM; +import ibxm.Module; +import ibxm.ProTracker; +import ibxm.ScreamTracker3; + +/** + * The CodecIBXM class provides an ICodec interface for reading from MOD/S3M/XM + * files via the IBXM library. + * SoundSystem CodecIBXM Class License:

+ * 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: + *
+ * 1) You may not falsely claim to be the author of this class or any + * unmodified portion of it. + *
+ * 2) You may not copyright this class or a modified version of it and then + * sue me for copyright infringement. + *
+ * 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. + *
+ * 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 + *
+ * 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. + *
+ * 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. + *

+ * Author: Paul Lamb + *
+ * http://www.paulscode.com + *


+ * + * This software is based on or using the IBXM library available from + * http://www.geocities.com/sunet2000/ + *

+ *
+ * IBXM is copyright (c) 2007, Martin Cameron, and is licensed under the BSD + * License. + *

+ * All rights reserved. + *

+ * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + *

+ * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials + * provided with the distribution. Neither the name of mumart nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *


+ */ +public class CodecIBXM 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; + +/** + * 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; + +/** + * Format the converted audio will be in. + */ + private AudioFormat myAudioFormat = null; + +/** + * True if the using library requires data read by this codec to be + * reverse-ordered before returning it from methods read() and readAll(). + */ + private boolean reverseBytes = false; + +/** + * IBXM decoder. + */ + private IBXM ibxm; + +/** + * Module instance to be played. + */ + private Module module; + +/** + * Duration of the audio (in frames). + */ + private int songDuration; + +/** + * Audio read position (in frames). + */ + private int playPosition; + +/** + * Processes status messages, warnings, and error messages. + */ + private SoundSystemLogger logger; + +/** + * Constructor: Grabs a handle to the logger. + */ + public CodecIBXM() + { + logger = SoundSystemConfig.getLogger(); + } + +/** + * Tells this codec when it will need to reverse the byte order of + * the data before returning it in the read() and readAll() methods. The + * IBXM library produces 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 + * will call the reverseByteOrder() method. + * @param b True if the calling audio library requires byte-reversal. + */ + public void reverseByteOrder( boolean b ) + { + reverseBytes = b; + } + +/** + * Prepares an audio stream to read from. If another stream is already opened, + * it will be closed and a new audio stream opened in its place. + * @param url URL to an audio 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 ); + cleanup(); + + if( url == null ) + { + errorMessage( "url null in method 'initialize'" ); + cleanup(); + return false; + } + + InputStream is = null; + + try + { + is = url.openStream(); + } + catch( IOException ioe ) + { + errorMessage( "Unable to open stream in method 'initialize'" ); + printStackTrace( ioe ); + return false; + } + + if( ibxm == null ) + ibxm = new IBXM( 48000 ); + if( myAudioFormat == null ) + myAudioFormat = new AudioFormat( 48000, 16, 2, true, true ); + + try + { + setModule( loadModule( is ) ); + } + catch( IllegalArgumentException iae ) + { + errorMessage( "Illegal argument in method 'initialize'" ); + printStackTrace( iae ); + if( is != null ) + { + try + { + is.close(); + } + catch( IOException ioe ) + {} + } + return false; + } + catch( IOException ioe ) + { + errorMessage( "Error loading module in method 'initialize'" ); + printStackTrace( ioe ); + if( is != null ) + { + try + { + is.close(); + } + catch( IOException ioe2 ) + {} + } + return false; + } + + if( is != null ) + { + try + { + is.close(); + } + catch( IOException ioe ) + {} + } + + endOfStream( SET, false ); + 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 paulscode.sound.SoundSystemConfig SoundSystemConfig} for more + * information about accessing and changing default settings. + * @return The audio data wrapped into a SoundBuffer context. + */ + public SoundBuffer read() + { + if( endOfStream( GET, XXX ) ) + return null; + + if( module == null ) + { + errorMessage( "Module null in method 'read'" ); + return null; + } + + // Check to make sure there is an audio format: + if( myAudioFormat == null ) + { + errorMessage( "Audio Format null in method 'read'" ); + return null; + } + + int bufferFrameSize = (int) SoundSystemConfig.getStreamingBufferSize() + / 4; + + int frames = songDuration - playPosition; + if( frames > bufferFrameSize ) + frames = bufferFrameSize; + + if( frames <= 0 ) + { + endOfStream( SET, true ); + return null; + } + byte[] outputBuffer = new byte[ frames * 4 ]; + + ibxm.get_audio( outputBuffer, frames ); + + playPosition += frames; + if( playPosition >= songDuration ) + { + endOfStream( SET, true ); + } + + // Reverse the byte order if necessary: + if( reverseBytes ) + reverseBytes( outputBuffer, 0, frames * 4 ); + + // Wrap the data into a SoundBuffer: + SoundBuffer buffer = new SoundBuffer( outputBuffer, myAudioFormat ); + + return buffer; + } + +/** + * Reads in all the audio data from the stream (up to the default + * "maximum file size". See + * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more + * information about accessing and changing default settings. + * @return the audio data wrapped into a SoundBuffer context. + */ + public SoundBuffer readAll() + { + if( module == null ) + { + errorMessage( "Module null in method 'readAll'" ); + return null; + } + + // Check to make sure there is an audio format: + if( myAudioFormat == null ) + { + errorMessage( "Audio Format null in method 'readAll'" ); + return null; + } + + int bufferFrameSize = (int) SoundSystemConfig.getFileChunkSize() + / 4; + + byte[] outputBuffer = new byte[ bufferFrameSize * 4 ]; + + // Buffer to contain the audio data: + byte[] fullBuffer = null; + // frames of audio data: + int frames; + // bytes of audio data: + int totalBytes = 0; + + while( (!endOfStream(GET, XXX)) && + (totalBytes < SoundSystemConfig.getMaxFileSize()) ) + { + frames = songDuration - playPosition; + if( frames > bufferFrameSize ) + frames = bufferFrameSize; + ibxm.get_audio( outputBuffer, frames ); + totalBytes += (frames * 4); + + fullBuffer = appendByteArrays( fullBuffer, outputBuffer, + frames * 4 ); + + playPosition += frames; + if( playPosition >= songDuration ) + { + endOfStream( SET, true ); + } + } + + // Reverse the byte order if necessary: + if( reverseBytes ) + reverseBytes( fullBuffer, 0, totalBytes ); + + // Wrap the data into a SoundBuffer: + SoundBuffer buffer = new SoundBuffer( fullBuffer, myAudioFormat ); + + return buffer; + } + +/** + * 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 audio stream and remove references to all instantiated objects. + */ + public void cleanup() + { +// if( ibxm != null ) +// ibxm.seek( 0 ); + playPosition = 0; + } + +/** + * 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 myAudioFormat; + } + +/** + * Decodes the data in the specified InputStream into an instance of + * ibxm.Module. + * @param input an InputStream containing the module file to be decoded. + * @throws IllegalArgumentException if the data is not recognised as a module file. + */ + private static Module loadModule( InputStream input ) + throws IllegalArgumentException, IOException + { + DataInputStream data_input_stream = new DataInputStream( input ); + + // Check if data is in XM format: + byte[] xm_header = new byte[ 60 ]; + data_input_stream.readFully( xm_header ); + if( FastTracker2.is_xm( xm_header ) ) + return FastTracker2.load_xm( xm_header, data_input_stream ); + + // Check if data is in ScreamTracker 3 format: + byte[] s3m_header = new byte[ 96 ]; + System.arraycopy( xm_header, 0, s3m_header, 0, 60 ); + data_input_stream.readFully( s3m_header, 60, 36 ); + if( ScreamTracker3.is_s3m( s3m_header ) ) + return ScreamTracker3.load_s3m( s3m_header, data_input_stream ); + + // Check if data is in ProTracker format: + byte[] mod_header = new byte[ 1084 ]; + System.arraycopy( s3m_header, 0, mod_header, 0, 96 ); + data_input_stream.readFully( mod_header, 96, 988 ); + return ProTracker.load_mod( mod_header, data_input_stream ); + } + +/** + * Sets the Module instance to be played. + */ + private void setModule( Module m ) + { + if( m != null ) + module = m; + ibxm.set_module( module ); + songDuration = ibxm.calculate_song_duration(); + } + +/** + * 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; + } + +/** + * Reverse-orders all bytes contained in the specified array. + * @param buffer Array containing audio data. + */ + public static void reverseBytes( byte[] buffer ) + { + reverseBytes( buffer, 0, buffer.length ); + } + +/** + * Reverse-orders the specified range of bytes contained in the specified array. + * @param buffer Array containing audio data. + * @param offset Array index to begin. + * @param size number of bytes to reverse-order. + */ + public static void reverseBytes( byte[] buffer, int offset, int size ) + { + + byte b; + for( int i = offset; i < ( offset + size ); i += 2 ) + { + b = buffer[i]; + buffer[i] = buffer[i + 1]; + buffer[i + 1] = b; + } + } + +/** + * Converts sound bytes to little-endian format. + * @param audio_bytes The original wave data + * @param two_bytes_data For stereo sounds. + * @return byte array containing the converted data. + */ + private static byte[] convertAudioBytes( byte[] audio_bytes, + boolean two_bytes_data ) + { + ByteBuffer dest = ByteBuffer.allocateDirect( audio_bytes.length ); + dest.order( ByteOrder.nativeOrder() ); + ByteBuffer src = ByteBuffer.wrap( audio_bytes ); + src.order( ByteOrder.LITTLE_ENDIAN ); + if( two_bytes_data ) + { + ShortBuffer dest_short = dest.asShortBuffer(); + ShortBuffer src_short = src.asShortBuffer(); + while( src_short.hasRemaining() ) + { + dest_short.put(src_short.get()); + } + } + else + { + while( src.hasRemaining() ) + { + dest.put( src.get() ); + } + } + dest.rewind(); + + if( !dest.hasArray() ) + { + byte[] arrayBackedBuffer = new byte[dest.capacity()]; + dest.get( arrayBackedBuffer ); + dest.clear(); + + return arrayBackedBuffer; + } + + return dest.array(); + } + +/** + * 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 length How many bytes to append from the second array. + * @return Byte array containing information from both arrays. + */ + private static byte[] appendByteArrays( byte[] arrayOne, byte[] arrayTwo, + int length ) + { + 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[ length ]; + // fill the new array with the contents of arrayTwo: + System.arraycopy( arrayTwo, 0, newArray, 0, 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 + 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, + length ); + arrayOne = null; + arrayTwo = null; + } + + return newArray; + } + +/** + * Prints an error message. + * @param message Message to print. + */ + private void errorMessage( String message ) + { + logger.errorMessage( "CodecWav", 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 ); + } +}