Package paulscode.sound.codecs

Source Code of paulscode.sound.codecs.CodecIBXM

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.
*<b><i>   SoundSystem CodecIBXM 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 IBXM library available from
*    http://www.geocities.com/sunet2000/
*</b><br><br>
*<br><b>
* IBXM is copyright (c) 2007, Martin Cameron, and is licensed under the BSD
* License.
*<br><br>
* All rights reserved.
*<br><br>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*<br><br>
* 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.
* <br><br>
* 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.
* <br><br><br></b>
*/
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.
*/
    @Override
    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.
*/
    @Override
    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.
*/
    @Override
    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.
*/
    @Override
    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.
*/
    @Override
    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.
*/
    @Override
    public boolean endOfStream()
    {
        return endOfStream( GET, XXX );
    }

/**
* Closes the audio stream and remove references to all instantiated objects.
*/
    @Override
    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.
*/
    @Override
    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 );
    }
}
TOP

Related Classes of paulscode.sound.codecs.CodecIBXM

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.