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 }