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 }