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