001    
002    package ibxm;
003    
004    import java.io.*;
005    import javax.sound.sampled.*;
006    
007    public 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    }