    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