001 002 package ibxm; 003 004 import java.io.*; 005 006 public class FastTracker2 { 007 public static boolean is_xm( byte[] header_60_bytes ) { 008 String xm_identifier; 009 xm_identifier = ascii_text( header_60_bytes, 0, 17 ); 010 return xm_identifier.equals( "Extended Module: " ); 011 } 012 013 public static Module load_xm( byte[] header_60_bytes, DataInput data_input ) throws IOException { 014 int xm_version, song_header_length, sequence_length; 015 int num_channels, num_patterns, num_instruments, xm_flags, idx; 016 byte[] structure_header, song_header; 017 boolean delta_env; 018 String tracker_name; 019 Instrument instrument; 020 Module module; 021 if( !is_xm( header_60_bytes ) ) { 022 throw new IllegalArgumentException( "Not an XM file!" ); 023 } 024 xm_version = unsigned_short_le( header_60_bytes, 58 ); 025 if( xm_version != 0x0104 ) { 026 throw new IllegalArgumentException( "Sorry, XM version " + xm_version + " is not supported!" ); 027 } 028 module = new Module(); 029 module.song_title = ascii_text( header_60_bytes, 17, 20 ); 030 tracker_name = ascii_text( header_60_bytes, 38, 20 ); 031 delta_env = tracker_name.startsWith( "DigiBooster Pro" ); 032 structure_header = new byte[ 4 ]; 033 data_input.readFully( structure_header ); 034 song_header_length = int_le( structure_header, 0 ); 035 song_header = new byte[ song_header_length ]; 036 data_input.readFully( song_header, 4, song_header_length - 4 ); 037 sequence_length = unsigned_short_le( song_header, 4 ); 038 module.restart_sequence_index = unsigned_short_le( song_header, 6 ); 039 num_channels = unsigned_short_le( song_header, 8 ); 040 num_patterns = unsigned_short_le( song_header, 10 ); 041 num_instruments = unsigned_short_le( song_header, 12 ); 042 xm_flags = unsigned_short_le( song_header, 14 ); 043 module.linear_periods = ( xm_flags & 0x1 ) == 0x1; 044 module.global_volume = 64; 045 module.channel_gain = IBXM.FP_ONE * 3 / 8; 046 module.default_speed = unsigned_short_le( song_header, 16 ); 047 module.default_tempo = unsigned_short_le( song_header, 18 ); 048 module.set_num_channels( num_channels ); 049 for( idx = 0; idx < num_channels; idx++ ) { 050 module.set_initial_panning( idx, 128 ); 051 } 052 module.set_sequence_length( sequence_length ); 053 for( idx = 0; idx < sequence_length; idx++ ) { 054 module.set_sequence( idx, song_header[ 20 + idx ] & 0xFF ); 055 } 056 module.set_num_patterns( num_patterns ); 057 for( idx = 0; idx < num_patterns; idx++ ) { 058 module.set_pattern( idx, read_xm_pattern( data_input, num_channels ) ); 059 } 060 module.set_num_instruments( num_instruments ); 061 for( idx = 1; idx <= num_instruments; idx++ ) { 062 try { 063 instrument = read_xm_instrument( data_input, delta_env ); 064 module.set_instrument( idx, instrument ); 065 } catch( EOFException e ) { 066 System.out.println( "Instrument " + idx + " is missing!" ); 067 } 068 } 069 return module; 070 } 071 072 private static Pattern read_xm_pattern( DataInput data_input, int num_channels ) throws IOException { 073 int pattern_header_length, packing_type, num_rows, pattern_data_length; 074 byte[] structure_header, pattern_header, pattern_data; 075 Pattern pattern; 076 structure_header = new byte[ 4 ]; 077 data_input.readFully( structure_header ); 078 pattern_header_length = int_le( structure_header, 0 ); 079 pattern_header = new byte[ pattern_header_length ]; 080 data_input.readFully( pattern_header, 4, pattern_header_length - 4 ); 081 packing_type = pattern_header[ 4 ]; 082 if( packing_type != 0 ) { 083 throw new IllegalArgumentException( "Pattern packing type " + packing_type + " is not supported!" ); 084 } 085 pattern = new Pattern(); 086 pattern.num_rows = unsigned_short_le( pattern_header, 5 ); 087 pattern_data_length = unsigned_short_le( pattern_header, 7 ); 088 pattern_data = new byte[ pattern_data_length ]; 089 data_input.readFully( pattern_data ); 090 pattern.set_pattern_data( pattern_data ); 091 return pattern; 092 } 093 094 private static Instrument read_xm_instrument( DataInput data_input, boolean delta_env ) throws IOException { 095 int instrument_header_length, num_samples, idx; 096 int env_tick, env_ampl, env_num_points, flags; 097 byte[] structure_header, instrument_header, sample_headers; 098 Instrument instrument; 099 Envelope envelope; 100 structure_header = new byte[ 4 ]; 101 data_input.readFully( structure_header ); 102 instrument_header_length = int_le( structure_header, 0 ); 103 instrument_header = new byte[ instrument_header_length ]; 104 data_input.readFully( instrument_header, 4, instrument_header_length - 4 ); 105 instrument = new Instrument(); 106 instrument.name = ascii_text( instrument_header, 4, 22 ); 107 num_samples = unsigned_short_le( instrument_header, 27 ); 108 if( num_samples > 0 ) { 109 instrument.set_num_samples( num_samples ); 110 for( idx = 0; idx < 96; idx++ ) { 111 instrument.set_key_to_sample( idx + 1, instrument_header[ 33 + idx ] & 0xFF ); 112 } 113 envelope = new Envelope(); 114 env_num_points = instrument_header[ 225 ] & 0xFF; 115 envelope.set_num_points( env_num_points ); 116 for( idx = 0; idx < env_num_points; idx++ ) { 117 env_tick = unsigned_short_le( instrument_header, 129 + idx * 4 ); 118 env_ampl = unsigned_short_le( instrument_header, 131 + idx * 4 ); 119 envelope.set_point( idx, env_tick, env_ampl, delta_env ); 120 } 121 envelope.set_sustain_point( instrument_header[ 227 ] & 0xFF ); 122 envelope.set_loop_points( instrument_header[ 228 ] & 0xFF, instrument_header[ 229 ] & 0xFF ); 123 flags = instrument_header[ 233 ] & 0xFF; 124 instrument.volume_envelope_active = ( flags & 0x1 ) == 0x1; 125 envelope.sustain = ( flags & 0x2 ) == 0x2; 126 envelope.looped = ( flags & 0x4 ) == 0x4; 127 instrument.set_volume_envelope( envelope ); 128 envelope = new Envelope(); 129 env_num_points = instrument_header[ 226 ] & 0xFF; 130 envelope.set_num_points( env_num_points ); 131 for( idx = 0; idx < env_num_points; idx++ ) { 132 env_tick = unsigned_short_le( instrument_header, 177 + idx * 4 ); 133 env_ampl = unsigned_short_le( instrument_header, 179 + idx * 4 ); 134 envelope.set_point( idx, env_tick, env_ampl, delta_env ); 135 } 136 envelope.set_sustain_point( instrument_header[ 230 ] & 0xFF ); 137 envelope.set_loop_points( instrument_header[ 231 ] & 0xFF, instrument_header[ 232 ] & 0xFF ); 138 flags = instrument_header[ 234 ] & 0xFF; 139 instrument.panning_envelope_active = ( flags & 0x1 ) == 0x1; 140 envelope.sustain = ( flags & 0x2 ) == 0x2; 141 envelope.looped = ( flags & 0x4 ) == 0x4; 142 instrument.set_panning_envelope( envelope ); 143 instrument.vibrato_type = instrument_header[ 235 ] & 0xFF; 144 instrument.vibrato_sweep = instrument_header[ 236 ] & 0xFF; 145 instrument.vibrato_depth = instrument_header[ 237 ] & 0xFF; 146 instrument.vibrato_rate = instrument_header[ 238 ] & 0xFF; 147 instrument.volume_fade_out = unsigned_short_le( instrument_header, 239 ); 148 sample_headers = new byte[ num_samples * 40 ]; 149 data_input.readFully( sample_headers ); 150 for( idx = 0; idx < num_samples; idx++ ) { 151 instrument.set_sample( idx, read_xm_sample( sample_headers, idx, data_input ) ); 152 } 153 } 154 return instrument; 155 } 156 157 private static Sample read_xm_sample( byte[] sample_headers, int sample_idx, DataInput data_input ) throws IOException { 158 int header_offset, sample_length, loop_start, loop_length; 159 int flags, in_idx, out_idx, sam, last_sam; 160 int fine_tune, relative_note; 161 boolean sixteen_bit, ping_pong; 162 byte[] raw_sample_data; 163 short[] decoded_sample_data; 164 Sample sample; 165 header_offset = sample_idx * 40; 166 sample = new Sample(); 167 sample_length = int_le( sample_headers, header_offset ); 168 loop_start = int_le( sample_headers, header_offset + 4 ); 169 loop_length = int_le( sample_headers, header_offset + 8 ); 170 sample.volume = sample_headers[ header_offset + 12 ] & 0xFF; 171 fine_tune = sample_headers[ header_offset + 13 ]; 172 fine_tune = ( fine_tune << IBXM.FP_SHIFT ) / 1536; 173 sample.set_panning = true; 174 flags = sample_headers[ header_offset + 14 ] & 0xFF; 175 if( ( flags & 0x03 ) == 0 ) { 176 loop_length = 0; 177 } 178 ping_pong = ( flags & 0x02 ) == 0x02; 179 sixteen_bit = ( flags & 0x10 ) == 0x10; 180 sample.panning = sample_headers[ header_offset + 15 ] & 0xFF; 181 relative_note = sample_headers[ header_offset + 16 ]; 182 relative_note = ( relative_note << IBXM.FP_SHIFT ) / 12; 183 sample.transpose = relative_note + fine_tune; 184 sample.name = ascii_text( sample_headers, header_offset + 18, 22 ); 185 raw_sample_data = new byte[ sample_length ]; 186 try { 187 data_input.readFully( raw_sample_data ); 188 } catch( EOFException e ) { 189 System.out.println( "Sample has been truncated!" ); 190 } 191 in_idx = 0; 192 out_idx = 0; 193 sam = 0; 194 last_sam = 0; 195 if( sixteen_bit ) { 196 decoded_sample_data = new short[ sample_length >> 1 ]; 197 while( in_idx < raw_sample_data.length ) { 198 sam = raw_sample_data[ in_idx ] & 0xFF; 199 sam = sam | ( ( raw_sample_data[ in_idx + 1 ] & 0xFF ) << 8 ); 200 last_sam = last_sam + sam; 201 decoded_sample_data[ out_idx ] = ( short ) last_sam; 202 in_idx += 2; 203 out_idx += 1; 204 } 205 sample.set_sample_data( decoded_sample_data, loop_start >> 1, loop_length >> 1, ping_pong ); 206 } else { 207 decoded_sample_data = new short[ sample_length ]; 208 while( in_idx < raw_sample_data.length ) { 209 sam = raw_sample_data[ in_idx ] & 0xFF; 210 last_sam = last_sam + sam; 211 decoded_sample_data[ out_idx ] = ( short ) ( last_sam << 8 ); 212 in_idx += 1; 213 out_idx += 1; 214 } 215 sample.set_sample_data( decoded_sample_data, loop_start, loop_length, ping_pong ); 216 } 217 return sample; 218 } 219 220 private static int unsigned_short_le( byte[] buffer, int offset ) { 221 int value; 222 value = buffer[ offset ] & 0xFF; 223 value = value | ( ( buffer[ offset + 1 ] & 0xFF ) << 8 ); 224 return value; 225 } 226 227 private static int int_le( byte[] buffer, int offset ) { 228 int value; 229 value = buffer[ offset ] & 0xFF; 230 value = value | ( ( buffer[ offset + 1 ] & 0xFF ) << 8 ); 231 value = value | ( ( buffer[ offset + 2 ] & 0xFF ) << 16 ); 232 value = value | ( ( buffer[ offset + 3 ] & 0x7F ) << 24 ); 233 return value; 234 } 235 236 private static String ascii_text( byte[] buffer, int offset, int length ) { 237 int idx, chr; 238 byte[] string_buffer; 239 String string; 240 string_buffer = new byte[ length ]; 241 for( idx = 0; idx < length; idx++ ) { 242 chr = buffer[ offset + idx ]; 243 if( chr < 32 ) { 244 chr = 32; 245 } 246 string_buffer[ idx ] = ( byte ) chr; 247 } 248 try { 249 string = new String( string_buffer, 0, length, "ISO-8859-1" ); 250 } catch( UnsupportedEncodingException e ) { 251 string = ""; 252 } 253 return string; 254 } 255 } 256