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 );
+ }
+}