package ibxm; import java.io.*; public class ScreamTracker3 { private static final int[] effect_map = new int[] { 0xFF, 0x25, /* A: Set Speed.*/ 0x0B, /* B: Pattern Jump.*/ 0x0D, /* C: Pattern Break.*/ 0x0A, /* D: Volume Slide.*/ 0x02, /* E: Portamento Down.*/ 0x01, /* F: Portamento Up.*/ 0x03, /* G: Tone Portamento.*/ 0x04, /* H: Vibrato.*/ 0x1D, /* I: Tremor.*/ 0x00, /* J: Arpeggio.*/ 0x06, /* K: Vibrato + Volume Slide.*/ 0x05, /* L: Tone Portamento + Volume Slide.*/ 0xFF, /* M: */ 0xFF, /* N: */ 0x09, /* O: Sample Offset.*/ 0xFF, /* P: */ 0x1B, /* Q: Retrig + Volume Slide.*/ 0x07, /* R: Tremolo.*/ 0x0E, /* S: Extended Effects.*/ 0x0F, /* T: Set Tempo.*/ 0x24, /* U: Fine Vibrato.*/ 0x10, /* V: Set Global Volume. */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; private static final int[] effect_s_map = new int[] { 0x00, /* 0: Set Filter.*/ 0x03, /* 1: Glissando.*/ 0x05, /* 2: Set Fine Tune.*/ 0x04, /* 3: Set Vibrato Waveform.*/ 0x07, /* 4: Set Tremolo Waveform.*/ 0xFF, /* 5: */ 0xFF, /* 6: */ 0xFF, /* 7: */ 0x08, /* 8: Set Panning.*/ 0xFF, /* 9: */ 0x09, /* A: Stereo Control.*/ 0x06, /* B: Pattern Loop.*/ 0x0C, /* C: Note Cut.*/ 0x0D, /* D: Note Delay.*/ 0x0E, /* E: Pattern Delay.*/ 0x0F /* F: Invert Loop.*/ }; public static boolean is_s3m( byte[] header_96_bytes ) { String s3m_identifier; s3m_identifier = ascii_text( header_96_bytes, 44, 4 ); return s3m_identifier.equals( "SCRM" ); } public static Module load_s3m( byte[] header_96_bytes, DataInput data_input ) throws IOException { int num_pattern_orders, num_instruments, num_patterns, num_channels; int flags, tracker_version, master_volume, panning, channel_config, sequence_length; int instrument_idx, pattern_idx, channel_idx, order_idx, panning_offset; boolean signed_samples, stereo_mode, default_panning; int[] channel_map, sequence; byte[] s3m_file; Module module; Instrument instrument; s3m_file = read_s3m_file( header_96_bytes, data_input ); module = new Module(); module.song_title = ascii_text( s3m_file, 0, 28 ); num_pattern_orders = get_num_pattern_orders( s3m_file ); num_instruments = get_num_instruments( s3m_file ); num_patterns = get_num_patterns( s3m_file ); flags = unsigned_short_le( s3m_file, 38 ); tracker_version = unsigned_short_le( s3m_file, 40 ); if( ( flags & 0x40 ) == 0x40 || tracker_version == 0x1300 ) { module.fast_volume_slides = true; } signed_samples = false; if( unsigned_short_le( s3m_file, 42 ) == 0x01 ) { signed_samples = true; } module.global_volume = s3m_file[ 48 ] & 0xFF; module.default_speed = s3m_file[ 49 ] & 0xFF; module.default_tempo = s3m_file[ 50 ] & 0xFF; master_volume = s3m_file[ 51 ] & 0x7F; module.channel_gain = ( master_volume << IBXM.FP_SHIFT ) >> 7; stereo_mode = ( s3m_file[ 51 ] & 0x80 ) == 0x80; default_panning = ( s3m_file[ 53 ] & 0xFF ) == 0xFC; channel_map = new int[ 32 ]; num_channels = 0; for( channel_idx = 0; channel_idx < 32; channel_idx++ ) { channel_config = s3m_file[ 64 + channel_idx ] & 0xFF; channel_map[ channel_idx ] = -1; if( channel_config < 16 ) { channel_map[ channel_idx ] = num_channels; num_channels += 1; } } module.set_num_channels( num_channels ); panning_offset = 96 + num_pattern_orders + num_instruments * 2 + num_patterns * 2; for( channel_idx = 0; channel_idx < 32; channel_idx++ ) { if( channel_map[ channel_idx ] < 0 ) continue; panning = 7; if( stereo_mode ) { panning = 12; if( ( s3m_file[ 64 + channel_idx ] & 0xFF ) < 8 ) { panning = 3; } } if( default_panning ) { flags = s3m_file[ panning_offset + channel_idx ] & 0xFF; if( ( flags & 0x20 ) == 0x20 ) { panning = flags & 0xF; } } module.set_initial_panning( channel_map[ channel_idx ], panning * 17 ); } sequence = read_s3m_sequence( s3m_file ); module.set_sequence_length( sequence.length ); for( order_idx = 0; order_idx < sequence.length; order_idx++ ) { module.set_sequence( order_idx, sequence[ order_idx ] ); } module.set_num_instruments( num_instruments ); for( instrument_idx = 0; instrument_idx < num_instruments; instrument_idx++ ) { instrument = read_s3m_instrument( s3m_file, instrument_idx, signed_samples ); module.set_instrument( instrument_idx + 1, instrument ); } module.set_num_patterns( num_patterns ); for( pattern_idx = 0; pattern_idx < num_patterns; pattern_idx++ ) { module.set_pattern( pattern_idx, read_s3m_pattern( s3m_file, pattern_idx, channel_map ) ); } return module; } private static int[] read_s3m_sequence( byte[] s3m_file ) { int num_pattern_orders, sequence_length; int sequence_idx, order_idx, pattern_order; int[] sequence; num_pattern_orders = get_num_pattern_orders( s3m_file ); sequence_length = 0; for( order_idx = 0; order_idx < num_pattern_orders; order_idx++ ) { pattern_order = s3m_file[ 96 + order_idx ] & 0xFF; if( pattern_order == 255 ) { break; } else if( pattern_order < 254 ) { sequence_length += 1; } } sequence = new int[ sequence_length ]; sequence_idx = 0; for( order_idx = 0; order_idx < num_pattern_orders; order_idx++ ) { pattern_order = s3m_file[ 96 + order_idx ] & 0xFF; if( pattern_order == 255 ) { break; } else if( pattern_order < 254 ) { sequence[ sequence_idx ] = pattern_order; sequence_idx += 1; } } return sequence; } private static Instrument read_s3m_instrument( byte[] s3m_file, int instrument_idx, boolean signed_samples ) { int instrument_offset; int sample_data_offset, sample_data_length; int loop_start, loop_length, c2_rate, sample_idx, amplitude; boolean sixteen_bit; Instrument instrument; Sample sample; short[] sample_data; instrument_offset = get_instrument_offset( s3m_file, instrument_idx ); instrument = new Instrument(); instrument.name = ascii_text( s3m_file, instrument_offset + 48, 28 ); sample = new Sample(); if( s3m_file[ instrument_offset ] == 1 ) { sample_data_length = get_sample_data_length( s3m_file, instrument_offset ); loop_start = unsigned_short_le( s3m_file, instrument_offset + 20 ); loop_length = unsigned_short_le( s3m_file, instrument_offset + 24 ) - loop_start; sample.volume = s3m_file[ instrument_offset + 28 ] & 0xFF; if( s3m_file[ instrument_offset + 30 ] != 0 ) { throw new IllegalArgumentException( "ScreamTracker3: Packed samples not supported!" ); } if( ( s3m_file[ instrument_offset + 31 ] & 0x01 ) == 0 ) { loop_length = 0; } if( ( s3m_file[ instrument_offset + 31 ] & 0x02 ) != 0 ) { throw new IllegalArgumentException( "ScreamTracker3: Stereo samples not supported!" ); } sixteen_bit = ( s3m_file[ instrument_offset + 31 ] & 0x04 ) != 0; c2_rate = unsigned_short_le( s3m_file, instrument_offset + 32 ); sample.transpose = LogTable.log_2( c2_rate ) - LogTable.log_2( 8363 ); sample_data_offset = get_sample_data_offset( s3m_file, instrument_offset ); if( sixteen_bit ) { if( signed_samples ) { throw new IllegalArgumentException( "ScreamTracker3: Signed 16-bit samples not supported!" ); } sample_data_length >>= 1; sample_data = new short[ sample_data_length ]; for( sample_idx = 0; sample_idx < sample_data_length; sample_idx++ ) { amplitude = s3m_file[ sample_data_offset + sample_idx * 2 ] & 0xFF; amplitude |= ( s3m_file[ sample_data_offset + sample_idx * 2 + 1 ] & 0xFF ) << 8; sample_data[ sample_idx ] = ( short ) ( amplitude - 32768 ); } } else { sample_data = new short[ sample_data_length ]; if( signed_samples ) { for( sample_idx = 0; sample_idx < sample_data_length; sample_idx++ ) { amplitude = s3m_file[ sample_data_offset + sample_idx ] << 8; sample_data[ sample_idx ] = ( short ) amplitude; } } else { for( sample_idx = 0; sample_idx < sample_data_length; sample_idx++ ) { amplitude = ( s3m_file[ sample_data_offset + sample_idx ] & 0xFF ) << 8; sample_data[ sample_idx ] = ( short ) ( amplitude - 32768 ); } } } sample.set_sample_data( sample_data, loop_start, loop_length, false ); } instrument.set_num_samples( 1 ); instrument.set_sample( 0, sample ); return instrument; } private static Pattern read_s3m_pattern( byte[] s3m_file, int pattern_idx, int[] channel_map ) { int pattern_offset; int num_channels, num_notes; int row_idx, channel_idx, note_idx; int token, key, volume_column, effect, effect_param; byte[] pattern_data; Pattern pattern; num_channels = 0; for( channel_idx = 0; channel_idx < 32; channel_idx++ ) { if( channel_map[ channel_idx ] >= num_channels ) { num_channels = channel_idx + 1; } } num_notes = num_channels * 64; pattern_data = new byte[ num_notes * 5 ]; row_idx = 0; pattern_offset = get_pattern_offset( s3m_file, pattern_idx ) + 2; while( row_idx < 64 ) { token = s3m_file[ pattern_offset ] & 0xFF; pattern_offset += 1; if( token > 0 ) { channel_idx = channel_map[ token & 0x1F ]; note_idx = ( num_channels * row_idx + channel_idx ) * 5; if( ( token & 0x20 ) == 0x20 ) { /* Key + Instrument.*/ if( channel_idx >= 0 ) { key = s3m_file[ pattern_offset ] & 0xFF; if( key == 255 ) { key = 0; } else if( key == 254 ) { key = 97; } else { key = ( ( key & 0xF0 ) >> 4 ) * 12 + ( key & 0x0F ) + 1; while( key > 96 ) { key = key - 12; } } pattern_data[ note_idx ] = ( byte ) key; pattern_data[ note_idx + 1 ] = s3m_file[ pattern_offset + 1 ]; } pattern_offset += 2; } if( ( token & 0x40 ) == 0x40 ) { /* Volume.*/ if( channel_idx >= 0 ) { volume_column = ( s3m_file[ pattern_offset ] & 0xFF ) + 0x10; pattern_data[ note_idx + 2 ] = ( byte ) volume_column; } pattern_offset += 1; } if( ( token & 0x80 ) == 0x80 ) { /* Effect + Param.*/ if( channel_idx >= 0 ) { effect = s3m_file[ pattern_offset ] & 0xFF; effect_param = s3m_file[ pattern_offset + 1 ] & 0xFF; effect = effect_map[ effect & 0x1F ]; if( effect == 0xFF ) { effect = 0; effect_param = 0; } if( effect == 0x0E ) { effect = effect_s_map[ ( effect_param & 0xF0 ) >> 4 ]; effect_param = effect_param & 0x0F; switch( effect ) { case 0x08: effect = 0x08; effect_param = effect_param * 17; break; case 0x09: effect = 0x08; if( effect_param > 7 ) { effect_param -= 8; } else { effect_param += 8; } effect_param = effect_param * 17; break; case 0xFF: effect = 0; effect_param = 0; break; default: effect_param = ( ( effect & 0x0F ) << 4 ) | ( effect_param & 0x0F ); effect = 0x0E; break; } } pattern_data[ note_idx + 3 ] = ( byte ) effect; pattern_data[ note_idx + 4 ] = ( byte ) effect_param; } pattern_offset += 2; } } else { row_idx += 1; } } pattern = new Pattern(); pattern.num_rows = 64; pattern.set_pattern_data( pattern_data ); return pattern; } private static byte[] read_s3m_file( byte[] header_96_bytes, DataInput data_input ) throws IOException { int s3m_file_length; int num_pattern_orders, num_instruments, num_patterns; int instrument_idx, pattern_idx; int instrument_offset, sample_data_offset, pattern_offset; byte[] s3m_file; if( !is_s3m( header_96_bytes ) ) { throw new IllegalArgumentException( "ScreamTracker3: Not an S3M file!" ); } s3m_file = header_96_bytes; s3m_file_length = header_96_bytes.length; num_pattern_orders = get_num_pattern_orders( s3m_file ); num_instruments = get_num_instruments( s3m_file ); num_patterns = get_num_patterns( s3m_file ); s3m_file_length += num_pattern_orders; s3m_file_length += num_instruments * 2; s3m_file_length += num_patterns * 2; /* Read enough of file to calculate the length.*/ s3m_file = read_more( s3m_file, s3m_file_length, data_input ); for( instrument_idx = 0; instrument_idx < num_instruments; instrument_idx++ ) { instrument_offset = get_instrument_offset( s3m_file, instrument_idx ); instrument_offset += 80; if( instrument_offset > s3m_file_length ) { s3m_file_length = instrument_offset; } } for( pattern_idx = 0; pattern_idx < num_patterns; pattern_idx++ ) { pattern_offset = get_pattern_offset( s3m_file, pattern_idx ); pattern_offset += 2; if( pattern_offset > s3m_file_length ) { s3m_file_length = pattern_offset; } } s3m_file = read_more( s3m_file, s3m_file_length, data_input ); /* Read rest of file.*/ for( instrument_idx = 0; instrument_idx < num_instruments; instrument_idx++ ) { instrument_offset = get_instrument_offset( s3m_file, instrument_idx ); sample_data_offset = get_sample_data_offset( s3m_file, instrument_offset ); sample_data_offset += get_sample_data_length( s3m_file, instrument_offset ); if( sample_data_offset > s3m_file_length ) { s3m_file_length = sample_data_offset; } } for( pattern_idx = 0; pattern_idx < num_patterns; pattern_idx++ ) { pattern_offset = get_pattern_offset( s3m_file, pattern_idx ); pattern_offset += get_pattern_length( s3m_file, pattern_offset ); pattern_offset += 2; if( pattern_offset > s3m_file_length ) { s3m_file_length = pattern_offset; } } s3m_file = read_more( s3m_file, s3m_file_length, data_input ); return s3m_file; } private static int get_num_pattern_orders( byte[] s3m_file ) { int num_pattern_orders; num_pattern_orders = unsigned_short_le( s3m_file, 32 ); return num_pattern_orders; } private static int get_num_instruments( byte[] s3m_file ) { int num_instruments; num_instruments = unsigned_short_le( s3m_file, 34 ); return num_instruments; } private static int get_num_patterns( byte[] s3m_file ) { int num_patterns; num_patterns = unsigned_short_le( s3m_file, 36 ); return num_patterns; } private static int get_instrument_offset( byte[] s3m_file, int instrument_idx ) { int instrument_offset, pointer_offset; pointer_offset = 96 + get_num_pattern_orders( s3m_file ); instrument_offset = unsigned_short_le( s3m_file, pointer_offset + instrument_idx * 2 ) << 4; return instrument_offset; } private static int get_sample_data_offset( byte[] s3m_file, int instrument_offset ) { int sample_data_offset; sample_data_offset = 0; if( s3m_file[ instrument_offset ] == 1 ) { sample_data_offset = ( s3m_file[ instrument_offset + 13 ] & 0xFF ) << 20; sample_data_offset |= unsigned_short_le( s3m_file, instrument_offset + 14 ) << 4; } return sample_data_offset; } private static int get_sample_data_length( byte[] s3m_file, int instrument_offset ) { int sample_data_length; boolean sixteen_bit; sample_data_length = 0; if( s3m_file[ instrument_offset ] == 1 ) { sample_data_length = unsigned_short_le( s3m_file, instrument_offset + 16 ); sixteen_bit = ( s3m_file[ instrument_offset + 31 ] & 0x04 ) != 0; if( sixteen_bit ) { sample_data_length <<= 1; } } return sample_data_length; } private static int get_pattern_offset( byte[] s3m_file, int pattern_idx ) { int pattern_offset, pointer_offset; pointer_offset = 96 + get_num_pattern_orders( s3m_file ); pointer_offset += get_num_instruments( s3m_file ) * 2; pattern_offset = unsigned_short_le( s3m_file, pointer_offset + pattern_idx * 2 ) << 4; return pattern_offset; } private static int get_pattern_length( byte[] s3m_file, int pattern_offset ) { int pattern_length; pattern_length = unsigned_short_le( s3m_file, pattern_offset ); return pattern_length; } private static byte[] read_more( byte[] old_data, int new_length, DataInput data_input ) throws IOException { byte[] new_data; new_data = old_data; if( new_length > old_data.length ) { new_data = new byte[ new_length ]; System.arraycopy( old_data, 0, new_data, 0, old_data.length ); try { data_input.readFully( new_data, old_data.length, new_data.length - old_data.length ); } catch( EOFException e ) { System.out.println( "ScreamTracker3: Module has been truncated!" ); } } return new_data; } 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 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; } }