    001
    002package ibxm;
    003
    004import java.io.*;
    005import javax.sound.sampled.*;
    006
    007public class Player {
    008    private Thread play_thread;
    009    private IBXM ibxm;
    010    private Module module;
    011    private int song_duration, play_position;
    012    private boolean running, loop;
    013    private byte[] output_buffer;
    014    private SourceDataLine output_line;
    015
    016    /**
    017        Simple command-line test player.
    018    */
    019    public static void main( String[] args ) throws Exception {
    020        if( args.length < 1 ) {
    021            System.err.println( "Usage: java ibxm.Player <module file>" );
    022            System.exit( 0 );
    023        }
    024        FileInputStream file_input_stream = new FileInputStream( args[ 0 ] );
    025        Player player = new Player();
    026        player.set_module( Player.load_module( file_input_stream ) );
    027        file_input_stream.close();
    028        player.play();
    029    }
    030
    031    /**
    032        Decode the data in the specified InputStream into a Module instance.
    033        @param input an InputStream containing the module file to be decoded.
    034        @throws IllegalArgumentException if the data is not recognised as a module file.
    035    */
    036    public static Module load_module( InputStream input ) throws IllegalArgumentException, IOException {
    037        DataInputStream data_input_stream = new DataInputStream( input );
    038        /* Check if data is in XM format.*/
    039        byte[] xm_header = new byte[ 60 ];
    040        data_input_stream.readFully( xm_header );
    041        if( FastTracker2.is_xm( xm_header ) )
    042            return FastTracker2.load_xm( xm_header, data_input_stream );
    043        /* Check if data is in ScreamTracker 3 format.*/
    044        byte[] s3m_header = new byte[ 96 ];
    045        System.arraycopy( xm_header, 0, s3m_header, 0, 60 );
    046        data_input_stream.readFully( s3m_header, 60, 36 );
    047        if( ScreamTracker3.is_s3m( s3m_header ) )
    048            return ScreamTracker3.load_s3m( s3m_header, data_input_stream );
    049        /* Check if data is in ProTracker format.*/
    050        byte[] mod_header = new byte[ 1084 ];
    051        System.arraycopy( s3m_header, 0, mod_header, 0, 96 );
    052        data_input_stream.readFully( mod_header, 96, 988 );
    053            return ProTracker.load_mod( mod_header, data_input_stream );
    054    }
    055
    056    /**
    057        Instantiate a new Player.
    058    */
    059    public Player() throws LineUnavailableException {
    060        ibxm = new IBXM( 48000 );
    061        set_loop( true );
    062        output_line = AudioSystem.getSourceDataLine( new AudioFormat( 48000, 16, 2, true, true ) );
    063        output_buffer = new byte[ 1024 * 4 ];
    064    }
    065
    066    /**
    067        Set the Module instance to be played.
    068    */
    069    public void set_module( Module m ) {
    070        if( m != null ) module = m;
    071        stop();
    072        ibxm.set_module( module );
    073        song_duration = ibxm.calculate_song_duration();
    074    }
    075
    076    /**
    077        If loop is true, playback will continue indefinitely,
    078        otherwise the module will play through once and stop.
    079    */
    080    public void set_loop( boolean loop ) {
    081        this.loop = loop;
    082    }
    083
    084    /**
    085        Open the audio device and begin playback.
    086        If a module is already playing it will be restarted.
    087    */
    088    public void play() {
    089        stop();
    090        play_thread = new Thread( new Driver() );
    091        play_thread.start();
    092    }
    093
    094    /**
    095        Stop playback and close the audio device.
    096    */
    097    public void stop() {
    098        running = false;
    099        if( play_thread != null ) {
    100            try {
    101                play_thread.join();
    102            } catch( InterruptedException ie ) {}
    103        }
    104    }
    105
    106    private class Driver implements Runnable {
    107        public void run() {
    108            if( running ) return;
    109            try {
    110                output_line.open();
    111                output_line.start();
    112                play_position = 0;
    113                running = true;
    114                while( running ) {
    115                    int frames = song_duration - play_position;
    116                    if( frames > 1024 ) frames = 1024;
    117                    ibxm.get_audio( output_buffer, frames );
    118                    output_line.write( output_buffer, 0, frames * 4 );
    119                    play_position += frames;
    120                    if( play_position >= song_duration ) {
    121                        play_position = 0;
    122                        if( !loop ) running = false;
    123                    }
    124                }
    125                output_line.drain();
    126                output_line.close();
    127            } catch( LineUnavailableException lue ) {
    128                lue.printStackTrace();
    129            }
    130        }
    131    }
    132}