package ibxm; import java.io.*; public class FastTracker2 { public static boolean is_xm( byte[] header_60_bytes ) { String xm_identifier; xm_identifier = ascii_text( header_60_bytes, 0, 17 ); return xm_identifier.equals( "Extended Module: " ); } public static Module load_xm( byte[] header_60_bytes, DataInput data_input ) throws IOException { int xm_version, song_header_length, sequence_length; int num_channels, num_patterns, num_instruments, xm_flags, idx; byte[] structure_header, song_header; boolean delta_env; String tracker_name; Instrument instrument; Module module; if( !is_xm( header_60_bytes ) ) { throw new IllegalArgumentException( "Not an XM file!" ); } xm_version = unsigned_short_le( header_60_bytes, 58 ); if( xm_version != 0x0104 ) { throw new IllegalArgumentException( "Sorry, XM version " + xm_version + " is not supported!" ); } module = new Module(); module.song_title = ascii_text( header_60_bytes, 17, 20 ); tracker_name = ascii_text( header_60_bytes, 38, 20 ); delta_env = tracker_name.startsWith( "DigiBooster Pro" ); structure_header = new byte[ 4 ]; data_input.readFully( structure_header ); song_header_length = int_le( structure_header, 0 ); song_header = new byte[ song_header_length ]; data_input.readFully( song_header, 4, song_header_length - 4 ); sequence_length = unsigned_short_le( song_header, 4 ); module.restart_sequence_index = unsigned_short_le( song_header, 6 ); num_channels = unsigned_short_le( song_header, 8 ); num_patterns = unsigned_short_le( song_header, 10 ); num_instruments = unsigned_short_le( song_header, 12 ); xm_flags = unsigned_short_le( song_header, 14 ); module.linear_periods = ( xm_flags & 0x1 ) == 0x1; module.global_volume = 64; module.channel_gain = IBXM.FP_ONE * 3 / 8; module.default_speed = unsigned_short_le( song_header, 16 ); module.default_tempo = unsigned_short_le( song_header, 18 ); module.set_num_channels( num_channels ); for( idx = 0; idx < num_channels; idx++ ) { module.set_initial_panning( idx, 128 ); } module.set_sequence_length( sequence_length ); for( idx = 0; idx < sequence_length; idx++ ) { module.set_sequence( idx, song_header[ 20 + idx ] & 0xFF ); } module.set_num_patterns( num_patterns ); for( idx = 0; idx < num_patterns; idx++ ) { module.set_pattern( idx, read_xm_pattern( data_input, num_channels ) ); } module.set_num_instruments( num_instruments ); for( idx = 1; idx <= num_instruments; idx++ ) { try { instrument = read_xm_instrument( data_input, delta_env ); module.set_instrument( idx, instrument ); } catch( EOFException e ) { System.out.println( "Instrument " + idx + " is missing!" ); } } return module; } private static Pattern read_xm_pattern( DataInput data_input, int num_channels ) throws IOException { int pattern_header_length, packing_type, num_rows, pattern_data_length; byte[] structure_header, pattern_header, pattern_data; Pattern pattern; structure_header = new byte[ 4 ]; data_input.readFully( structure_header ); pattern_header_length = int_le( structure_header, 0 ); pattern_header = new byte[ pattern_header_length ]; data_input.readFully( pattern_header, 4, pattern_header_length - 4 ); packing_type = pattern_header[ 4 ]; if( packing_type != 0 ) { throw new IllegalArgumentException( "Pattern packing type " + packing_type + " is not supported!" ); } pattern = new Pattern(); pattern.num_rows = unsigned_short_le( pattern_header, 5 ); pattern_data_length = unsigned_short_le( pattern_header, 7 ); pattern_data = new byte[ pattern_data_length ]; data_input.readFully( pattern_data ); pattern.set_pattern_data( pattern_data ); return pattern; } private static Instrument read_xm_instrument( DataInput data_input, boolean delta_env ) throws IOException { int instrument_header_length, num_samples, idx; int env_tick, env_ampl, env_num_points, flags; byte[] structure_header, instrument_header, sample_headers; Instrument instrument; Envelope envelope; structure_header = new byte[ 4 ]; data_input.readFully( structure_header ); instrument_header_length = int_le( structure_header, 0 ); instrument_header = new byte[ instrument_header_length ]; data_input.readFully( instrument_header, 4, instrument_header_length - 4 ); instrument = new Instrument(); instrument.name = ascii_text( instrument_header, 4, 22 ); num_samples = unsigned_short_le( instrument_header, 27 ); if( num_samples > 0 ) { instrument.set_num_samples( num_samples ); for( idx = 0; idx < 96; idx++ ) { instrument.set_key_to_sample( idx + 1, instrument_header[ 33 + idx ] & 0xFF ); } envelope = new Envelope(); env_num_points = instrument_header[ 225 ] & 0xFF; envelope.set_num_points( env_num_points ); for( idx = 0; idx < env_num_points; idx++ ) { env_tick = unsigned_short_le( instrument_header, 129 + idx * 4 ); env_ampl = unsigned_short_le( instrument_header, 131 + idx * 4 ); envelope.set_point( idx, env_tick, env_ampl, delta_env ); } envelope.set_sustain_point( instrument_header[ 227 ] & 0xFF ); envelope.set_loop_points( instrument_header[ 228 ] & 0xFF, instrument_header[ 229 ] & 0xFF ); flags = instrument_header[ 233 ] & 0xFF; instrument.volume_envelope_active = ( flags & 0x1 ) == 0x1; envelope.sustain = ( flags & 0x2 ) == 0x2; envelope.looped = ( flags & 0x4 ) == 0x4; instrument.set_volume_envelope( envelope ); envelope = new Envelope(); env_num_points = instrument_header[ 226 ] & 0xFF; envelope.set_num_points( env_num_points ); for( idx = 0; idx < env_num_points; idx++ ) { env_tick = unsigned_short_le( instrument_header, 177 + idx * 4 ); env_ampl = unsigned_short_le( instrument_header, 179 + idx * 4 ); envelope.set_point( idx, env_tick, env_ampl, delta_env ); } envelope.set_sustain_point( instrument_header[ 230 ] & 0xFF ); envelope.set_loop_points( instrument_header[ 231 ] & 0xFF, instrument_header[ 232 ] & 0xFF ); flags = instrument_header[ 234 ] & 0xFF; instrument.panning_envelope_active = ( flags & 0x1 ) == 0x1; envelope.sustain = ( flags & 0x2 ) == 0x2; envelope.looped = ( flags & 0x4 ) == 0x4; instrument.set_panning_envelope( envelope ); instrument.vibrato_type = instrument_header[ 235 ] & 0xFF; instrument.vibrato_sweep = instrument_header[ 236 ] & 0xFF; instrument.vibrato_depth = instrument_header[ 237 ] & 0xFF; instrument.vibrato_rate = instrument_header[ 238 ] & 0xFF; instrument.volume_fade_out = unsigned_short_le( instrument_header, 239 ); sample_headers = new byte[ num_samples * 40 ]; data_input.readFully( sample_headers ); for( idx = 0; idx < num_samples; idx++ ) { instrument.set_sample( idx, read_xm_sample( sample_headers, idx, data_input ) ); } } return instrument; } private static Sample read_xm_sample( byte[] sample_headers, int sample_idx, DataInput data_input ) throws IOException { int header_offset, sample_length, loop_start, loop_length; int flags, in_idx, out_idx, sam, last_sam; int fine_tune, relative_note; boolean sixteen_bit, ping_pong; byte[] raw_sample_data; short[] decoded_sample_data; Sample sample; header_offset = sample_idx * 40; sample = new Sample(); sample_length = int_le( sample_headers, header_offset ); loop_start = int_le( sample_headers, header_offset + 4 ); loop_length = int_le( sample_headers, header_offset + 8 ); sample.volume = sample_headers[ header_offset + 12 ] & 0xFF; fine_tune = sample_headers[ header_offset + 13 ]; fine_tune = ( fine_tune << IBXM.FP_SHIFT ) / 1536; sample.set_panning = true; flags = sample_headers[ header_offset + 14 ] & 0xFF; if( ( flags & 0x03 ) == 0 ) { loop_length = 0; } ping_pong = ( flags & 0x02 ) == 0x02; sixteen_bit = ( flags & 0x10 ) == 0x10; sample.panning = sample_headers[ header_offset + 15 ] & 0xFF; relative_note = sample_headers[ header_offset + 16 ]; relative_note = ( relative_note << IBXM.FP_SHIFT ) / 12; sample.transpose = relative_note + fine_tune; sample.name = ascii_text( sample_headers, header_offset + 18, 22 ); raw_sample_data = new byte[ sample_length ]; try { data_input.readFully( raw_sample_data ); } catch( EOFException e ) { System.out.println( "Sample has been truncated!" ); } in_idx = 0; out_idx = 0; sam = 0; last_sam = 0; if( sixteen_bit ) { decoded_sample_data = new short[ sample_length >> 1 ]; while( in_idx < raw_sample_data.length ) { sam = raw_sample_data[ in_idx ] & 0xFF; sam = sam | ( ( raw_sample_data[ in_idx + 1 ] & 0xFF ) << 8 ); last_sam = last_sam + sam; decoded_sample_data[ out_idx ] = ( short ) last_sam; in_idx += 2; out_idx += 1; } sample.set_sample_data( decoded_sample_data, loop_start >> 1, loop_length >> 1, ping_pong ); } else { decoded_sample_data = new short[ sample_length ]; while( in_idx < raw_sample_data.length ) { sam = raw_sample_data[ in_idx ] & 0xFF; last_sam = last_sam + sam; decoded_sample_data[ out_idx ] = ( short ) ( last_sam << 8 ); in_idx += 1; out_idx += 1; } sample.set_sample_data( decoded_sample_data, loop_start, loop_length, ping_pong ); } return sample; } private static int unsigned_short_le( byte[] buffer, int offset ) { int value; value = buffer[ offset ] & 0xFF; value = value | ( ( buffer[ offset + 1 ] & 0xFF ) << 8 ); return value; } private static int int_le( byte[] buffer, int offset ) { int value; value = buffer[ offset ] & 0xFF; value = value | ( ( buffer[ offset + 1 ] & 0xFF ) << 8 ); value = value | ( ( buffer[ offset + 2 ] & 0xFF ) << 16 ); value = value | ( ( buffer[ offset + 3 ] & 0x7F ) << 24 ); return value; } private static String ascii_text( byte[] buffer, int offset, int length ) { int idx, chr; byte[] string_buffer; String string; string_buffer = new byte[ length ]; for( idx = 0; idx < length; idx++ ) { chr = buffer[ offset + idx ]; if( chr < 32 ) { chr = 32; } string_buffer[ idx ] = ( byte ) chr; } try { string = new String( string_buffer, 0, length, "ISO-8859-1" ); } catch( UnsupportedEncodingException e ) { string = ""; } return string; } }