From 4642519f331ac7a9104df427a7d3af850c257e7e Mon Sep 17 00:00:00 2001 From: LexManos Date: Sat, 2 Jun 2012 19:19:32 -0700 Subject: [PATCH] We now bundle CodexIBXM from PaulsCode. See http://paulscode.com http://www.paulscode.com/forum/index.php?topic=4.0 and the included license files for more info. --- forge/Paulscode IBXM Library License.txt | 10 + ...aulscode SoundSystem CodecIBXM License.txt | 40 + forge/forge.py | 32 + forge/forge_client/src/ibxm/Channel.java | 946 ++++++++++++++++++ forge/forge_client/src/ibxm/Envelope.java | 111 ++ forge/forge_client/src/ibxm/FastTracker2.java | 256 +++++ forge/forge_client/src/ibxm/IBXM.java | 343 +++++++ forge/forge_client/src/ibxm/Instrument.java | 90 ++ forge/forge_client/src/ibxm/LogTable.java | 91 ++ forge/forge_client/src/ibxm/Module.java | 138 +++ forge/forge_client/src/ibxm/Pattern.java | 61 ++ forge/forge_client/src/ibxm/Player.java | 132 +++ forge/forge_client/src/ibxm/ProTracker.java | 234 +++++ forge/forge_client/src/ibxm/Sample.java | 239 +++++ .../forge_client/src/ibxm/ScreamTracker3.java | 489 +++++++++ .../src/forge/ModCompatibilityClient.java | 32 +- .../forge/adaptors/SoundHandlerAdaptor.java | 42 + .../src/paulscode/sound/codecs/CodecIBXM.java | 642 ++++++++++++ forge/install/install.py | 7 +- forge/release.py | 4 + forge/setup.py | 9 +- 21 files changed, 3919 insertions(+), 29 deletions(-) create mode 100644 forge/Paulscode IBXM Library License.txt create mode 100644 forge/Paulscode SoundSystem CodecIBXM License.txt create mode 100644 forge/forge_client/src/ibxm/Channel.java create mode 100644 forge/forge_client/src/ibxm/Envelope.java create mode 100644 forge/forge_client/src/ibxm/FastTracker2.java create mode 100644 forge/forge_client/src/ibxm/IBXM.java create mode 100644 forge/forge_client/src/ibxm/Instrument.java create mode 100644 forge/forge_client/src/ibxm/LogTable.java create mode 100644 forge/forge_client/src/ibxm/Module.java create mode 100644 forge/forge_client/src/ibxm/Pattern.java create mode 100644 forge/forge_client/src/ibxm/Player.java create mode 100644 forge/forge_client/src/ibxm/ProTracker.java create mode 100644 forge/forge_client/src/ibxm/Sample.java create mode 100644 forge/forge_client/src/ibxm/ScreamTracker3.java create mode 100644 forge/forge_client/src/net/minecraft/src/forge/adaptors/SoundHandlerAdaptor.java create mode 100644 forge/forge_client/src/paulscode/sound/codecs/CodecIBXM.java diff --git a/forge/Paulscode IBXM Library License.txt b/forge/Paulscode IBXM Library License.txt new file mode 100644 index 0000000000..b7f2e551a9 --- /dev/null +++ b/forge/Paulscode IBXM Library License.txt @@ -0,0 +1,10 @@ +IBXM is copyright (c) 2007, Martin Cameron, and is licensed under the BSD License. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of mumart nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/forge/Paulscode SoundSystem CodecIBXM License.txt b/forge/Paulscode SoundSystem CodecIBXM License.txt new file mode 100644 index 0000000000..3541def741 --- /dev/null +++ b/forge/Paulscode SoundSystem CodecIBXM License.txt @@ -0,0 +1,40 @@ +SoundSystem CodecIBXM Class License: + +You are free to use this class for any purpose, commercial or otherwise. +You may modify this class or source code, and distribute it any way you +like, provided the following conditions are met: + +1) You may not falsely claim to be the author of this class or any + unmodified portion of it. +2) You may not copyright this class or a modified version of it and then + sue me for copyright infringement. +3) If you modify the source code, you must clearly document the changes + made before redistributing the modified source code, so other users know + it is not the original code. +4) You are not required to give me credit for this class in any derived + work, but if you do, you must also mention my website: + http://www.paulscode.com +5) I the author will not be responsible for any damages (physical, + financial, or otherwise) caused by the use if this class or any + portion of it. +6) I the author do not guarantee, warrant, or make any representations, + either expressed or implied, regarding the use of this class or any + portion of it. + +Author: Paul Lamb +http://www.paulscode.com + + +This software is based on or using the IBXM library available from +http://www.geocities.com/sunet2000/ + + +IBXM is copyright (c) 2007, Martin Cameron, and is licensed under the BSD License. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of mumart nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/forge/forge.py b/forge/forge.py index 111b44429c..b9ee19e255 100644 --- a/forge/forge.py +++ b/forge/forge.py @@ -216,3 +216,35 @@ def cleanup_source(path): for cur_file in fnmatch.filter(filelist, '*.java'): src_file = os.path.normpath(os.path.join(path, cur_file)) updatefile(src_file) + + +def pre_decompile(): + bin_dir = os.path.join(mcp_dir, 'jars', 'bin') + back_jar = os.path.join(bin_dir, 'minecraft.jar.backup') + src_jar = os.path.join(bin_dir, 'minecraft.jar') + + if os.path.isfile(back_jar): + if os.path.isfile(src_jar): + os.remove(src_jar) + shutil.move(back_jar, src_jar) + +def post_decompile(): + print 'Stripping META-INF from minecraft.jar' + bin_dir = os.path.join(mcp_dir, 'jars', 'bin') + back_jar = os.path.join(bin_dir, 'minecraft.jar.backup') + src_jar = os.path.join(bin_dir, 'minecraft.jar') + + if os.path.isfile(back_jar): + os.remove(back_jar) + + shutil.move(src_jar, back_jar) + + zip_in = zipfile.ZipFile(back_jar, mode='a') + zip_out = zipfile.ZipFile(src_jar, 'w', zipfile.ZIP_DEFLATED) + for i in zip_in.filelist: + if not i.filename.startswith('META-INF'): + c = zip_in.read(i.filename) + zip_out.writestr(i.filename, c) + else: + print 'Skipping: %s' % i.filename + zip_out.close() \ No newline at end of file diff --git a/forge/forge_client/src/ibxm/Channel.java b/forge/forge_client/src/ibxm/Channel.java new file mode 100644 index 0000000000..efc4d76234 --- /dev/null +++ b/forge/forge_client/src/ibxm/Channel.java @@ -0,0 +1,946 @@ + +package ibxm; + +public class Channel { + public int pattern_loop_row; + + private Module module; + private Instrument instrument; + private Sample sample; + private int[] global_volume, current_note; + private boolean linear_periods, fast_volume_slides, key_on, silent; + private int sample_idx, sample_frac, step, left_gain, right_gain; + private int volume, panning, fine_tune, period, porta_period, key_add; + private int tremolo_speed, tremolo_depth, tremolo_tick, tremolo_wave, tremolo_add; + private int vibrato_speed, vibrato_depth, vibrato_tick, vibrato_wave, vibrato_add; + private int volume_slide_param, portamento_param, retrig_param; + private int volume_envelope_tick, panning_envelope_tick; + private int effect_tick, trigger_tick, fade_out_volume, random_seed; + + private int log_2_sampling_rate; + private static final int LOG_2_29024 = LogTable.log_2( 29024 ); + private static final int LOG_2_8287 = LogTable.log_2( 8287 ); + private static final int LOG_2_8363 = LogTable.log_2( 8363 ); + private static final int LOG_2_1712 = LogTable.log_2( 1712 ); + + private static final int[] sine_table = new int[] { + 0, 24 , 49, 74, 97, 120, 141, 161, 180, 197, 212, 224, 235, 244, 250, 253, + 255, 253, 250, 244, 235, 224, 212, 197, 180, 161, 141, 120, 97, 74, 49, 24 + }; + + public Channel( Module mod, int sampling_rate, int[] global_vol ) { + module = mod; + global_volume = global_vol; + linear_periods = module.linear_periods; + fast_volume_slides = module.fast_volume_slides; + current_note = new int[ 5 ]; + log_2_sampling_rate = LogTable.log_2( sampling_rate ); + } + + public void reset() { + tremolo_speed = 0; + tremolo_depth = 0; + tremolo_wave = 0; + vibrato_speed = 0; + vibrato_depth = 0; + vibrato_wave = 0; + volume_slide_param = 0; + portamento_param = 0; + retrig_param = 0; + random_seed = 0xABC123; + instrument = module.get_instrument( 0 ); + row( 48, 256, 0, 0, 0 ); + } + + public void resample( int[] mixing_buffer, int frame_offset, int frames, int quality ) { + if( !silent ) { + switch( quality ) { + default: + sample.resample_nearest( sample_idx, sample_frac, step, left_gain, right_gain, mixing_buffer, frame_offset, frames ); + break; + case 1: + sample.resample_linear( sample_idx, sample_frac, step, left_gain, right_gain, mixing_buffer, frame_offset, frames ); + break; + case 2: + sample.resample_sinc( sample_idx, sample_frac, step, left_gain, right_gain, mixing_buffer, frame_offset, frames ); + break; + } + } + } + + public void update_sample_idx( int samples ) { + sample_frac += step * samples; + sample_idx += sample_frac >> IBXM.FP_SHIFT; + sample_frac &= IBXM.FP_MASK; + } + + public void set_volume( int vol ) { + if( vol < 0 ) { + vol = 0; + } + if( vol > 64 ) { + vol = 64; + } + volume = vol; + } + + public void set_panning( int pan ) { + if( pan < 0 ) { + pan = 0; + } + if( pan > 255 ) { + pan = 255; + } + panning = pan; + } + + public void row( int key, int inst_idx, int volume_column, int effect, int effect_param ) { + effect = effect & 0xFF; + if( effect >= 0x30 ) { + /* Effects above 0x30 are internal.*/ + effect = 0; + } + if( effect == 0x00 && effect_param != 0 ) { + /* Arpeggio.*/ + effect = 0x40; + } + if( effect == 0x0E ) { + /* Renumber 0x0Ex effect command.*/ + effect = 0x30 + ( ( effect_param & 0xF0 ) >> 4 ); + effect_param = effect_param & 0x0F; + } + if( effect == 0x21 ) { + /* Renumber 0x21x effect command.*/ + effect = 0x40 + ( ( effect_param & 0xF0 ) >> 4 ); + effect_param = effect_param & 0x0F; + } + current_note[ 0 ] = key; + current_note[ 1 ] = inst_idx; + current_note[ 2 ] = volume_column; + current_note[ 3 ] = effect; + current_note[ 4 ] = effect_param; + effect_tick = 0; + trigger_tick += 1; + update_envelopes(); + key_add = 0; + vibrato_add = 0; + tremolo_add = 0; + if( ! ( effect == 0x3D && effect_param > 0 ) ) { + /* Not note delay.*/ + trigger( key, inst_idx, volume_column, effect ); + /* Handle volume column.*/ + switch( volume_column & 0xF0 ) { + case 0x00: + /* Do nothing.*/ + break; + case 0x60: + /* Volume slide down.*/ + break; + case 0x70: + /* Volume slide up.*/ + break; + case 0x80: + /* Fine volume slide down.*/ + set_volume( volume - ( volume_column & 0x0F ) ); + break; + case 0x90: + /* Fine volume slide up.*/ + set_volume( volume + ( volume_column & 0x0F ) ); + break; + case 0xA0: + /* Set vibrato speed.*/ + set_vibrato_speed( volume_column & 0x0F ); + break; + case 0xB0: + /* Vibrato.*/ + set_vibrato_depth( volume_column & 0x0F ); + vibrato(); + break; + case 0xC0: + /* Set panning.*/ + set_panning( ( volume_column & 0x0F ) << 4 ); + break; + case 0xD0: + /* Panning slide left.*/ + break; + case 0xE0: + /* Panning slide right.*/ + break; + case 0xF0: + /* Tone portamento.*/ + set_portamento_param( volume_column & 0x0F ); + break; + default: + /* Set volume.*/ + set_volume( volume_column - 0x10 ); + break; + } + } + if( instrument.vibrato_depth > 0 ) { + auto_vibrato(); + } + switch( effect ) { + case 0x01: + /* Portmento Up.*/ + set_portamento_param( effect_param ); + portamento_up(); + break; + case 0x02: + /* Portamento Down.*/ + set_portamento_param( effect_param ); + portamento_down(); + break; + case 0x03: + /* Tone Portamento.*/ + set_portamento_param( effect_param ); + break; + case 0x04: + /* Vibrato.*/ + set_vibrato_speed( ( effect_param & 0xF0 ) >> 4 ); + set_vibrato_depth( effect_param & 0x0F ); + vibrato(); + break; + case 0x05: + /* Tone Portamento + Volume Slide.*/ + set_volume_slide_param( effect_param ); + volume_slide(); + break; + case 0x06: + /* Vibrato + Volume Slide.*/ + set_volume_slide_param( effect_param ); + vibrato(); + volume_slide(); + break; + case 0x07: + /* Tremolo.*/ + set_tremolo_speed( ( effect_param & 0xF0 ) >> 4 ); + set_tremolo_depth( effect_param & 0x0F ); + tremolo(); + break; + case 0x08: + /* Set Panning.*/ + set_panning( effect_param ); + break; + case 0x09: + /* Set Sample Index.*/ + set_sample_index( effect_param << 8 ); + break; + case 0x0A: + /* Volume Slide.*/ + set_volume_slide_param( effect_param ); + volume_slide(); + break; + case 0x0B: + /* Pattern Jump.*/ + break; + case 0x0C: + /* Set volume.*/ + set_volume( effect_param ); + break; + case 0x0D: + /* Pattern Break.*/ + break; + case 0x0E: + /* Extended Commands (See 0x30-0x3F).*/ + break; + case 0x0F: + /* Set Speed/Tempo.*/ + break; + case 0x10: + /* Set Global Volume.*/ + set_global_volume( effect_param ); + break; + case 0x11: + /* global Volume Slide.*/ + set_volume_slide_param( effect_param ); + break; + case 0x14: + /* Key Off*/ + if( effect_param == 0 ) { + key_on = false; + } + break; + case 0x15: + /* Set Envelope Tick.*/ + set_envelope_tick( effect_param ); + break; + case 0x19: + /* Panning Slide.*/ + set_volume_slide_param( effect_param ); + break; + case 0x1B: + /* Retrig + Volume Slide.*/ + set_retrig_param( effect_param ); + retrig_volume_slide(); + break; + case 0x1D: + /* Tremor.*/ + set_retrig_param( effect_param ); + tremor(); + break; + case 0x24: + /* S3M Fine Vibrato.*/ + set_vibrato_speed( ( effect_param & 0xF0 ) >> 4 ); + set_vibrato_depth( effect_param & 0x0F ); + fine_vibrato(); + break; + case 0x25: + /* S3M Set Speed.*/ + break; + case 0x30: + /* Amiga Set Filter.*/ + break; + case 0x31: + /* Fine Portamento Up.*/ + set_portamento_param( 0xF0 | effect_param ); + portamento_up(); + break; + case 0x32: + /* Fine Portamento Down.*/ + set_portamento_param( 0xF0 | effect_param ); + portamento_down(); + break; + case 0x33: + /* Set Glissando Mode.*/ + break; + case 0x34: + /* Set Vibrato Waveform.*/ + set_vibrato_wave( effect_param ); + break; + case 0x35: + /* Set Fine Tune.*/ + break; + case 0x36: + /* Pattern Loop.*/ + break; + case 0x37: + /* Set Tremolo Waveform.*/ + set_tremolo_wave( effect_param ); + break; + case 0x38: + /* Set Panning(Obsolete).*/ + break; + case 0x39: + /* Retrig.*/ + set_retrig_param( effect_param ); + break; + case 0x3A: + /* Fine Volume Slide Up.*/ + set_volume_slide_param( ( effect_param << 4 ) | 0x0F ); + volume_slide(); + break; + case 0x3B: + /* Fine Volume Slide Down.*/ + set_volume_slide_param( 0xF0 | effect_param ); + volume_slide(); + break; + case 0x3C: + /* Note Cut.*/ + if( effect_param == 0 ) { + set_volume( 0 ); + } + break; + case 0x3D: + /* Note Delay.*/ + break; + case 0x3E: + /* Pattern Delay.*/ + break; + case 0x3F: + /* Invert Loop.*/ + break; + case 0x40: + /* Arpeggio.*/ + break; + case 0x41: + /* Extra Fine Porta Up.*/ + set_portamento_param( 0xE0 | effect_param ); + portamento_up(); + break; + case 0x42: + /* Extra Fine Porta Down.*/ + set_portamento_param( 0xE0 | effect_param ); + portamento_down(); + break; + } + calculate_amplitude(); + calculate_frequency(); + } + + public void tick() { + int volume_column, effect, effect_param; + volume_column = current_note[ 2 ]; + effect = current_note[ 3 ]; + effect_param = current_note[ 4 ]; + effect_tick += 1; + if( effect == 0x3D && effect_param == effect_tick ) { + /* Note delay.*/ + row( current_note[ 0 ], current_note[ 1 ], volume_column, 0, 0 ); + } else { + trigger_tick += 1; + vibrato_tick += 1; + tremolo_tick += 1; + update_envelopes(); + key_add = 0; + vibrato_add = 0; + tremolo_add = 0; + if( instrument.vibrato_depth > 0 ) { + auto_vibrato(); + } + switch( volume_column & 0xF0 ) { + case 0x60: + /* Volume Slide Down.*/ + set_volume( volume - ( volume_column & 0x0F ) ); + break; + case 0x70: + /* Volume Slide Up.*/ + set_volume( volume + ( volume_column & 0x0F ) ); + break; + case 0xB0: + /* Vibrato.*/ + vibrato(); + break; + case 0xD0: + /* Panning Slide Left.*/ + set_panning( panning - ( volume_column & 0x0F ) ); + break; + case 0xE0: + /* Panning Slide Right.*/ + set_panning( panning + ( volume_column & 0x0F ) ); + break; + case 0xF0: + /* Tone Portamento.*/ + tone_portamento(); + break; + } + switch( effect ) { + case 0x01: + /* Portamento Up.*/ + portamento_up(); + break; + case 0x02: + /* Portamento Down.*/ + portamento_down(); + break; + case 0x03: + /* Tone Portamento.*/ + tone_portamento(); + break; + case 0x04: + /* Vibrato.*/ + vibrato(); + break; + case 0x05: + /* Tone Portamento + Volume Slide.*/ + tone_portamento(); + volume_slide(); + break; + case 0x06: + /* Vibrato + Volume Slide */ + vibrato(); + volume_slide(); + break; + case 0x07: + /* Tremolo.*/ + tremolo(); + break; + case 0x0A: + /* Volume Slide.*/ + volume_slide(); + break; + case 0x11: + /* Global Volume Slide.*/ + global_volume_slide(); + break; + case 0x14: + /* Key off.*/ + if( effect_tick == effect_param ) { + key_on = false; + } + break; + case 0x19: + /* Panning Slide.*/ + panning_slide(); + break; + case 0x1B: + /* Retrig + Volume Slide.*/ + retrig_volume_slide(); + break; + case 0x1D: + /* Tremor.*/ + tremor(); + break; + case 0x24: + /* S3M Fine Vibrato.*/ + fine_vibrato(); + break; + case 0x39: + /* Retrig.*/ + retrig_volume_slide(); + break; + case 0x3C: + /* Note Cut.*/ + if( effect_tick == effect_param ) { + set_volume( 0 ); + } + break; + case 0x40: + /* Arpeggio.*/ + switch( effect_tick % 3 ) { + case 1: + key_add = ( effect_param & 0xF0 ) >> 4; + break; + case 2: + key_add = effect_param & 0x0F; + break; + } + break; + } + } + calculate_amplitude(); + calculate_frequency(); + } + + private void set_vibrato_speed( int speed ) { + if( speed > 0 ) { + vibrato_speed = speed; + } + } + + private void set_vibrato_depth( int depth ) { + if( depth > 0 ) { + vibrato_depth = depth; + } + } + + private void set_vibrato_wave( int wave ) { + if( wave < 0 || wave > 7 ) { + wave = 0; + } + vibrato_wave = wave; + } + + private void set_tremolo_speed( int speed ) { + if( speed > 0 ) { + tremolo_speed = speed; + } + } + + private void set_tremolo_depth( int depth ) { + if( depth > 0 ) { + tremolo_depth = depth; + } + } + + private void set_tremolo_wave( int wave ) { + if( wave < 0 || wave > 7 ) { + wave = 0; + } + tremolo_wave = wave; + } + + private void vibrato() { + int vibrato_phase; + vibrato_phase = vibrato_tick * vibrato_speed; + vibrato_add += waveform( vibrato_phase, vibrato_wave ) * vibrato_depth >> 5; + } + + private void fine_vibrato() { + int vibrato_phase; + vibrato_phase = vibrato_tick * vibrato_speed; + vibrato_add += waveform( vibrato_phase, vibrato_wave ) * vibrato_depth >> 7; + } + + private void tremolo() { + int tremolo_phase; + tremolo_phase = tremolo_tick * tremolo_speed; + tremolo_add += waveform( tremolo_phase, tremolo_wave ) * tremolo_depth >> 6; + } + + private void set_portamento_param( int param ) { + if( param != 0 ) { + portamento_param = param; + } + } + + private void tone_portamento() { + int new_period; + if( porta_period < period ) { + new_period = period - ( portamento_param << 2 ); + if( new_period < porta_period ) { + new_period = porta_period; + } + set_period( new_period ); + } + if( porta_period > period ) { + new_period = period + ( portamento_param << 2 ); + if( new_period > porta_period ) { + new_period = porta_period; + } + set_period( new_period ); + } + } + + private void portamento_up() { + if( ( portamento_param & 0xF0 ) == 0xE0 ) { + /* Extra-fine porta.*/ + if( effect_tick == 0 ) { + set_period( period - ( portamento_param & 0x0F ) ); + } + } else if( ( portamento_param & 0xF0 ) == 0xF0 ) { + /* Fine porta.*/ + if( effect_tick == 0 ) { + set_period( period - ( ( portamento_param & 0x0F ) << 2 ) ); + } + } else { + /* Normal porta.*/ + if( effect_tick > 0 ) { + set_period( period - ( portamento_param << 2 ) ); + } + } + } + + private void portamento_down() { + if( ( portamento_param & 0xF0 ) == 0xE0 ) { + /* Extra-fine porta.*/ + if( effect_tick == 0 ) { + set_period( period + ( portamento_param & 0x0F ) ); + } + } else if( ( portamento_param & 0xF0 ) == 0xF0 ) { + /* Fine porta.*/ + if( effect_tick == 0 ) { + set_period( period + ( ( portamento_param & 0x0F ) << 2 ) ); + } + } else { + /* Normal porta.*/ + if( effect_tick > 0 ) { + set_period( period + ( portamento_param << 2 ) ); + } + } + } + + private void set_period( int p ) { + if( p < 32 ) { + p = 32; + } + if( p > 32768 ) { + p = 32768; + } + period = p; + } + + private void set_global_volume( int vol ) { + if( vol < 0 ) { + vol = 0; + } + if( vol > 64 ) { + vol = 64; + } + global_volume[ 0 ] = vol; + } + + private void set_volume_slide_param( int param ) { + if( param != 0 ) { + volume_slide_param = param; + } + } + + private void global_volume_slide() { + int up, down; + up = ( volume_slide_param & 0xF0 ) >> 4; + down = volume_slide_param & 0x0F; + set_global_volume( global_volume[ 0 ] + up - down ); + } + + private void volume_slide() { + int up, down; + up = ( volume_slide_param & 0xF0 ) >> 4; + down = volume_slide_param & 0x0F; + if( down == 0x0F && up > 0 ) { + /* Fine slide up.*/ + if( effect_tick == 0 ) { + set_volume( volume + up ); + } + } else if( up == 0x0F && down > 0 ) { + /* Fine slide down.*/ + if( effect_tick == 0 ) { + set_volume( volume - down ); + } + } else { + /* Normal slide.*/ + if( effect_tick > 0 || fast_volume_slides ) { + set_volume( volume + up - down ); + } + } + } + + private void panning_slide() { + int left, right; + left = ( volume_slide_param & 0xF0 ) >> 4; + right = volume_slide_param & 0x0F; + set_panning( panning - left + right ); + } + + private void set_retrig_param( int param ) { + if( param != 0 ) { + retrig_param = param; + } + } + + private void tremor() { + int on_ticks, cycle_length, cycle_index; + on_ticks = ( ( retrig_param & 0xF0 ) >> 4 ) + 1; + cycle_length = on_ticks + ( retrig_param & 0x0F ) + 1; + cycle_index = trigger_tick % cycle_length; + if( cycle_index >= on_ticks ) { + tremolo_add = -64; + } + } + + private void retrig_volume_slide() { + int retrig_volume, retrig_tick; + retrig_volume = ( retrig_param & 0xF0 ) >> 4; + retrig_tick = retrig_param & 0x0F; + if( retrig_tick > 0 && ( trigger_tick % retrig_tick ) == 0 ) { + set_sample_index( 0 ); + switch( retrig_volume ) { + case 0x01: + set_volume( volume - 1 ); + break; + case 0x02: + set_volume( volume - 2 ); + break; + case 0x03: + set_volume( volume - 4 ); + break; + case 0x04: + set_volume( volume - 8 ); + break; + case 0x05: + set_volume( volume - 16 ); + break; + case 0x06: + set_volume( volume - volume / 3 ); + break; + case 0x07: + set_volume( volume / 2 ); + break; + case 0x09: + set_volume( volume + 1 ); + break; + case 0x0A: + set_volume( volume + 2 ); + break; + case 0x0B: + set_volume( volume + 4 ); + break; + case 0x0C: + set_volume( volume + 8 ); + break; + case 0x0D: + set_volume( volume + 16 ); + break; + case 0x0E: + set_volume( volume + volume / 2 ); + break; + case 0x0F: + set_volume( volume * 2 ); + break; + } + } + } + + private void set_sample_index( int index ) { + if( index < 0 ) { + index = 0; + } + sample_idx = index; + sample_frac = 0; + } + + private void set_envelope_tick( int tick ) { + volume_envelope_tick = tick; + panning_envelope_tick = tick; + } + + private void trigger( int key, int instrument_idx, int volume_column, int effect ) { + if( instrument_idx > 0 ) { + instrument = module.get_instrument( instrument_idx ); + sample = instrument.get_sample_from_key( key ); + set_volume( sample.volume ); + if( sample.set_panning ) { + set_panning( sample.panning ); + } + set_envelope_tick( 0 ); + fade_out_volume = 32768; + key_on = true; + } + if( key > 0 ) { + if( key < 97 ) { + porta_period = key_to_period( key ); + if( effect != 0x03 && effect != 0x05 ) { + if( ( volume_column & 0xF0 ) != 0xF0 ) { + /* Not portamento.*/ + trigger_tick = 0; + if( vibrato_wave < 4 ) { + vibrato_tick = 0; + } + if( tremolo_wave < 4 ) { + tremolo_tick = 0; + } + set_period( porta_period ); + set_sample_index( 0 ); + } + } + } else { + /* Key off.*/ + key_on = false; + } + } + } + + private void update_envelopes() { + Envelope envelope; + if( instrument.volume_envelope_active ) { + if( !key_on ) { + fade_out_volume -= instrument.volume_fade_out & 0xFFFF; + if( fade_out_volume < 0 ) { + fade_out_volume = 0; + } + } + envelope = instrument.get_volume_envelope(); + volume_envelope_tick = envelope.next_tick( volume_envelope_tick, key_on ); + } + if( instrument.panning_envelope_active ) { + envelope = instrument.get_panning_envelope(); + panning_envelope_tick = envelope.next_tick( panning_envelope_tick, key_on ); + } + } + + private void auto_vibrato() { + int sweep, depth, rate; + sweep = instrument.vibrato_sweep & 0xFF; + depth = instrument.vibrato_depth & 0x0F; + rate = instrument.vibrato_rate & 0x3F; + if( trigger_tick < sweep ) { + depth = depth * trigger_tick / sweep; + } + vibrato_add += waveform( trigger_tick * rate, 0 ) * depth >> 9; + } + + private int waveform( int phase, int wform ) { + int amplitude; + amplitude = 0; + switch( wform & 0x3 ) { + case 0: + /* Sine. */ + if( ( phase & 0x20 ) == 0 ) { + amplitude = sine_table[ phase & 0x1F ]; + } else { + amplitude = -sine_table[ phase & 0x1F ]; + } + break; + case 1: + /* Saw. */ + if( ( phase & 0x20 ) == 0 ) { + amplitude = ( phase & 0x1F ) << 3; + } else { + amplitude = ( ( phase & 0x1F ) << 3 ) - 255; + } + break; + case 2: + /* Square. */ + if( ( phase & 0x20 ) == 0 ) { + amplitude = 255; + } else { + amplitude = -255; + } + break; + case 3: + /* Random. */ + amplitude = ( random_seed >> 15 ) - 255; + random_seed = ( random_seed * 65 + 17 ) & 0xFFFFFF; + break; + } + return amplitude; + } + + private int key_to_period( int key ) { + int octave, log_2_period, period_out; + octave = ( key << IBXM.FP_SHIFT ) / 12 + sample.transpose; + if( linear_periods ) { + period_out = 7744 - ( octave * 768 >> IBXM.FP_SHIFT ); + } else { + log_2_period = LOG_2_29024 - octave; + period_out = LogTable.raise_2( log_2_period ); + period_out = period_out >> ( IBXM.FP_SHIFT - 1 ); + period_out = ( period_out >> 1 ) + ( period_out & 1 ); + } + return period_out; + } + + private void calculate_amplitude() { + int envelope_volume, tremolo_volume, amplitude; + int envelope_panning, mixer_panning, panning_range; + Envelope envelope; + envelope_volume = 0; + if( instrument.volume_envelope_active ) { + envelope = instrument.get_volume_envelope(); + envelope_volume = envelope.calculate_ampl( volume_envelope_tick ); + } else { + if( key_on ) { + envelope_volume = 64; + } + } + tremolo_volume = volume + tremolo_add; + if( tremolo_volume < 0 ) { + tremolo_volume = 0; + } + if( tremolo_volume > 64 ) { + tremolo_volume = 64; + } + amplitude = tremolo_volume << IBXM.FP_SHIFT - 6; + amplitude = amplitude * envelope_volume >> 6; + amplitude = amplitude * fade_out_volume >> 15; + amplitude = amplitude * global_volume[ 0 ] >> 6; + amplitude = amplitude * module.channel_gain >> IBXM.FP_SHIFT; + silent = sample.has_finished( sample_idx ); + if( amplitude <= 0 ) { + silent = true; + } else { + envelope_panning = 32; + if( instrument.panning_envelope_active ) { + envelope = instrument.get_panning_envelope(); + envelope_panning = envelope.calculate_ampl( panning_envelope_tick ); + } + mixer_panning = ( panning & 0xFF ) << IBXM.FP_SHIFT - 8; + panning_range = IBXM.FP_ONE - mixer_panning; + if( panning_range > mixer_panning ) { + panning_range = mixer_panning; + } + mixer_panning = mixer_panning + ( panning_range * ( envelope_panning - 32 ) >> 5 ); + left_gain = amplitude * ( IBXM.FP_ONE - mixer_panning ) >> IBXM.FP_SHIFT; + right_gain = amplitude * mixer_panning >> IBXM.FP_SHIFT; + } + } + + private void calculate_frequency() { + int vibrato_period, log_2_freq; + vibrato_period = period + vibrato_add; + if( vibrato_period < 32 ) { + vibrato_period = 32; + } + if( vibrato_period > 32768 ) { + vibrato_period = 32768; + } + if( linear_periods ) { + log_2_freq = LOG_2_8363 + ( 4608 - vibrato_period << IBXM.FP_SHIFT ) / 768; + } else { + log_2_freq = module.pal ? LOG_2_8287 : LOG_2_8363; + log_2_freq = log_2_freq + LOG_2_1712 - LogTable.log_2( vibrato_period ); + } + log_2_freq += ( key_add << IBXM.FP_SHIFT ) / 12; + step = LogTable.raise_2( log_2_freq - log_2_sampling_rate ); + } +} + diff --git a/forge/forge_client/src/ibxm/Envelope.java b/forge/forge_client/src/ibxm/Envelope.java new file mode 100644 index 0000000000..99ac57d728 --- /dev/null +++ b/forge/forge_client/src/ibxm/Envelope.java @@ -0,0 +1,111 @@ + +package ibxm; + +public class Envelope { + public boolean sustain, looped; + private int sustain_tick, loop_start_tick, loop_end_tick; + private int[] ticks, ampls; + + public Envelope() { + set_num_points( 1 ); + } + + public void set_num_points( int num_points ) { + int point; + if( num_points <= 0 ) { + num_points = 1; + } + ticks = new int[ num_points ]; + ampls = new int[ num_points ]; + set_point( 0, 0, 0, false ); + } + + /* When you set a point, all subsequent points are reset. */ + public void set_point( int point, int tick, int ampl, boolean delta ) { + if( point >= 0 && point < ticks.length ) { + if( point == 0 ) { + tick = 0; + } + if( point > 0 ) { + if( delta ) tick += ticks[ point - 1 ]; + if( tick <= ticks[ point - 1 ] ) { + System.out.println( "Envelope: Point not valid (" + tick + " <= " + ticks[ point - 1 ] + ")"); + tick = ticks[ point - 1 ] + 1; + } + } + ticks[ point ] = tick; + ampls[ point ] = ampl; + point += 1; + while( point < ticks.length ) { + ticks[ point ] = ticks[ point - 1 ] + 1; + ampls[ point ] = 0; + point += 1; + } + } + } + + public void set_sustain_point( int point ) { + if( point < 0 ) { + point = 0; + } + if( point >= ticks.length ) { + point = ticks.length - 1; + } + sustain_tick = ticks[ point ]; + } + + public void set_loop_points( int start, int end ) { + if( start < 0 ) { + start = 0; + } + if( start >= ticks.length ) { + start = ticks.length - 1; + } + if( end < start || end >= ticks.length ) { + end = start; + } + loop_start_tick = ticks[ start ]; + loop_end_tick = ticks[ end ]; + } + + public int next_tick( int tick, boolean key_on ) { + tick = tick + 1; + if( looped && tick >= loop_end_tick ) { + tick = loop_start_tick; + } + if( sustain && key_on && tick >= sustain_tick ) { + tick = sustain_tick; + } + return tick; + } + + public int calculate_ampl( int tick ) { + int idx, point, delta_t, delta_a, ampl; + ampl = ampls[ ticks.length - 1 ]; + if( tick < ticks[ ticks.length - 1 ] ) { + point = 0; + for( idx = 1; idx < ticks.length; idx++ ) { + if( ticks[ idx ] <= tick ) { + point = idx; + } + } + delta_t = ticks[ point + 1 ] - ticks[ point ]; + delta_a = ampls[ point + 1 ] - ampls[ point ]; + ampl = ( delta_a << IBXM.FP_SHIFT ) / delta_t; + ampl = ampl * ( tick - ticks[ point ] ) >> IBXM.FP_SHIFT; + ampl = ampl + ampls[ point ]; + } + return ampl; + } + + public void dump() { + int idx, tick; + for( idx = 0; idx < ticks.length; idx++ ) { + System.out.println( ticks[ idx ] + ", " + ampls[ idx ] ); + } + for( tick = 0; tick < 222; tick++ ) { + System.out.print( calculate_ampl( tick ) + ", " ); + } + } +} + diff --git a/forge/forge_client/src/ibxm/FastTracker2.java b/forge/forge_client/src/ibxm/FastTracker2.java new file mode 100644 index 0000000000..0807c7c317 --- /dev/null +++ b/forge/forge_client/src/ibxm/FastTracker2.java @@ -0,0 +1,256 @@ + +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; + } +} + diff --git a/forge/forge_client/src/ibxm/IBXM.java b/forge/forge_client/src/ibxm/IBXM.java new file mode 100644 index 0000000000..4c4d4f262e --- /dev/null +++ b/forge/forge_client/src/ibxm/IBXM.java @@ -0,0 +1,343 @@ + +package ibxm; + +public class IBXM { + public static final String VERSION = "ibxm alpha 51 (c)2008 mumart@gmail.com"; + + public static final int FP_SHIFT = 15; + public static final int FP_ONE = 1 << FP_SHIFT; + public static final int FP_MASK = FP_ONE - 1; + + private int sampling_rate, resampling_quality, volume_ramp_length; + private int tick_length_samples, current_tick_samples; + private int[] mixing_buffer, volume_ramp_buffer; + + private Module module; + private Channel[] channels; + private int[] global_volume, note; + private int current_sequence_index, next_sequence_index; + private int current_row, next_row; + private int tick_counter, ticks_per_row; + private int pattern_loop_count, pattern_loop_channel; + + public IBXM( int sample_rate ) { + +/** MODIFIED 13 Oct 2009 by Paul Lamb **/ +// System.out.println( VERSION ); +/***************************************/ + + if( sample_rate < 8000 ) { + sample_rate = 8000; + } + sampling_rate = sample_rate; + volume_ramp_length = sampling_rate >> 10; + volume_ramp_buffer = new int[ volume_ramp_length * 2 ]; + mixing_buffer = new int[ sampling_rate / 6 ]; + global_volume = new int[ 1 ]; + note = new int[ 5 ]; + set_module( new Module() ); + set_resampling_quality( 1 ); + } + + public void set_module( Module m ) { + int channel_idx; + module = m; + channels = new Channel[ module.get_num_channels() ]; + for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) { + channels[ channel_idx ] = new Channel( module, sampling_rate, global_volume ); + } + set_sequence_index( 0, 0 ); + } + + public void set_resampling_quality( int quality ) { + resampling_quality = quality; + } + + public int calculate_song_duration() { + int song_duration; + set_sequence_index( 0, 0 ); + next_tick(); + song_duration = tick_length_samples; + while( !next_tick() ) { + song_duration += tick_length_samples; + } + set_sequence_index( 0, 0 ); + return song_duration; + } + + public void set_sequence_index( int sequence_index, int row ) { + int channel_idx; + global_volume[ 0 ] = 64; + for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) { + channels[ channel_idx ].reset(); + channels[ channel_idx ].set_panning( module.get_initial_panning( channel_idx ) ); + } + set_global_volume( module.global_volume ); + set_speed( 6 ); + set_speed( module.default_speed ); + set_tempo( 125 ); + set_tempo( module.default_tempo ); + pattern_loop_count = -1; + next_sequence_index = sequence_index; + next_row = row; + tick_counter = 0; + current_tick_samples = tick_length_samples; + clear_vol_ramp_buffer(); + } + + public void seek( int sample_position ) { + int idx; + set_sequence_index( 0, 0 ); + next_tick(); + while( sample_position > tick_length_samples ) { + sample_position -= tick_length_samples; + next_tick(); + } + mix_tick(); + current_tick_samples = sample_position; + } + + public void get_audio( byte[] output_buffer, int frames ) { + int output_idx, mix_idx, mix_end, count, amplitude; + output_idx = 0; + while( frames > 0 ) { + count = tick_length_samples - current_tick_samples; + if( count > frames ) { + count = frames; + } + mix_idx = current_tick_samples << 1; + mix_end = mix_idx + ( count << 1 ) - 1; + while( mix_idx <= mix_end ) { + amplitude = mixing_buffer[ mix_idx ]; + if( amplitude > 32767 ) { + amplitude = 32767; + } + if( amplitude < -32768 ) { + amplitude = -32768; + } + output_buffer[ output_idx ] = ( byte ) ( amplitude >> 8 ); + output_buffer[ output_idx + 1 ] = ( byte ) ( amplitude & 0xFF ); + output_idx += 2; + mix_idx += 1; + } + current_tick_samples = mix_idx >> 1; + frames -= count; + if( frames > 0 ) { + next_tick(); + mix_tick(); + current_tick_samples = 0; + } + } + } + + private void mix_tick() { + int channel_idx, mix_idx, mix_len; + mix_idx = 0; + mix_len = tick_length_samples + volume_ramp_length << 1; + while( mix_idx < mix_len ) { + mixing_buffer[ mix_idx ] = 0; + mix_idx += 1; + } + for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) { + mix_len = tick_length_samples + volume_ramp_length; + channels[ channel_idx ].resample( mixing_buffer, 0, mix_len, resampling_quality ); + } + volume_ramp(); + } + + private boolean next_tick() { + int channel_idx; + boolean song_end; + for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) { + channels[ channel_idx ].update_sample_idx( tick_length_samples ); + } + tick_counter -= 1; + if( tick_counter <= 0 ) { + tick_counter = ticks_per_row; + song_end = next_row(); + } else { + for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) { + channels[ channel_idx ].tick(); + } + song_end = false; + } + return song_end; + } + + private boolean next_row() { + int channel_idx, effect, effect_param; + boolean song_end; + Pattern pattern; + song_end = false; + if( next_sequence_index < 0 ) { + /* Bad next sequence index.*/ + next_sequence_index = 0; + next_row = 0; + } + if( next_sequence_index >= module.get_sequence_length() ) { + /* End of sequence.*/ + song_end = true; + next_sequence_index = module.restart_sequence_index; + if( next_sequence_index < 0 ) { + next_sequence_index = 0; + } + if( next_sequence_index >= module.get_sequence_length() ) { + next_sequence_index = 0; + } + next_row = 0; + } + if( next_sequence_index < current_sequence_index ) { + /* Jump to previous pattern. */ + song_end = true; + } + if( next_sequence_index == current_sequence_index ) { + if( next_row <= current_row ) { + if( pattern_loop_count < 0 ) { + /* Jump to previous row in the same pattern, but not a pattern loop. */ + song_end = true; + } + } + } + current_sequence_index = next_sequence_index; + pattern = module.get_pattern_from_sequence( current_sequence_index ); + if( next_row < 0 || next_row >= pattern.num_rows ) { + /* Bad next row.*/ + next_row = 0; + } + current_row = next_row; + next_row = current_row + 1; + if( next_row >= pattern.num_rows ) { + next_sequence_index = current_sequence_index + 1; + next_row = 0; + } + for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) { + pattern.get_note( note, current_row * channels.length + channel_idx ); + effect = note[ 3 ]; + effect_param = note[ 4 ]; + channels[ channel_idx ].row( note[ 0 ], note[ 1 ], note[ 2 ], effect, effect_param ); + switch( effect ) { + case 0x0B: + /* Pattern Jump.*/ + if( pattern_loop_count < 0 ) { + next_sequence_index = effect_param; + next_row = 0; + } + break; + case 0x0D: + /* Pattern Break.*/ + if( pattern_loop_count < 0 ) { + next_sequence_index = current_sequence_index + 1; + next_row = ( effect_param >> 4 ) * 10 + ( effect_param & 0x0F ); + } + break; + case 0x0E: + /* Extended.*/ + switch( effect_param & 0xF0 ) { + case 0x60: + /* Pattern loop.*/ + if( ( effect_param & 0x0F ) == 0 ) { + /* Set loop marker on this channel. */ + channels[ channel_idx ].pattern_loop_row = current_row; + } + if( channels[ channel_idx ].pattern_loop_row < current_row ) { + /* Marker and parameter are valid. Begin looping. */ + if( pattern_loop_count < 0 ) { + /* Not already looping, begin. */ + pattern_loop_count = effect_param & 0x0F; + pattern_loop_channel = channel_idx; + } + if( pattern_loop_channel == channel_idx ) { + /* Loop in progress on this channel. Next iteration. */ + if( pattern_loop_count == 0 ) { + /* Loop finished. */ + /* Invalidate current marker. */ + channels[ channel_idx ].pattern_loop_row = current_row + 1; + } else { + /* Count must be higher than zero. */ + /* Loop and cancel any breaks on this row. */ + next_row = channels[ channel_idx ].pattern_loop_row; + next_sequence_index = current_sequence_index; + } + pattern_loop_count -= 1; + } + } + break; + case 0xE0: + /* Pattern delay.*/ + tick_counter += ticks_per_row * ( effect_param & 0x0F ); + break; + } + break; + case 0x0F: + /* Set Speed/Tempo.*/ + if( effect_param < 32 ) { + set_speed( effect_param ); + tick_counter = ticks_per_row; + } else { + set_tempo( effect_param ); + } + break; + case 0x25: + /* S3M Set Speed.*/ + set_speed( effect_param ); + tick_counter = ticks_per_row; + break; + } + } + return song_end; + } + + private void set_global_volume( int volume ) { + if( volume < 0 ) { + volume = 0; + } + if( volume > 64 ) { + volume = 64; + } + global_volume[ 0 ] = volume; + } + + private void set_speed( int speed ) { + if( speed > 0 && speed < 256 ) { + ticks_per_row = speed; + } + } + + private void set_tempo( int bpm ) { + if( bpm > 31 && bpm < 256 ) { + tick_length_samples = ( sampling_rate * 5 ) / ( bpm * 2 ); + } + } + + private void volume_ramp() { + int ramp_idx, next_idx, ramp_end; + int volume_ramp_delta, volume, sample; + sample = 0; + volume_ramp_delta = FP_ONE / volume_ramp_length; + volume = 0; + ramp_idx = 0; + next_idx = 2 * tick_length_samples; + ramp_end = volume_ramp_length * 2 - 1; + while( ramp_idx <= ramp_end ) { + sample = volume_ramp_buffer[ ramp_idx ] * ( FP_ONE - volume ) >> FP_SHIFT; + mixing_buffer[ ramp_idx ] = sample + ( mixing_buffer[ ramp_idx ] * volume >> FP_SHIFT ); + volume_ramp_buffer[ ramp_idx ] = mixing_buffer[ next_idx + ramp_idx ]; + sample = volume_ramp_buffer[ ramp_idx + 1 ] * ( FP_ONE - volume ) >> FP_SHIFT; + mixing_buffer[ ramp_idx + 1 ] = sample + ( mixing_buffer[ ramp_idx + 1 ] * volume >> FP_SHIFT ); + volume_ramp_buffer[ ramp_idx + 1 ] = mixing_buffer[ next_idx + ramp_idx + 1 ]; + volume += volume_ramp_delta; + ramp_idx += 2; + } + } + + private void clear_vol_ramp_buffer() { + int ramp_idx, ramp_end; + ramp_idx = 0; + ramp_end = volume_ramp_length * 2 - 1; + while( ramp_idx <= ramp_end ) { + volume_ramp_buffer[ ramp_idx ] = 0; + ramp_idx += 1; + } + } +} + diff --git a/forge/forge_client/src/ibxm/Instrument.java b/forge/forge_client/src/ibxm/Instrument.java new file mode 100644 index 0000000000..c0dcab91de --- /dev/null +++ b/forge/forge_client/src/ibxm/Instrument.java @@ -0,0 +1,90 @@ + +package ibxm; + +public class Instrument { + public String name; + public int vibrato_type, vibrato_sweep; + public int vibrato_depth, vibrato_rate; + public boolean volume_envelope_active, panning_envelope_active; + public int volume_fade_out; + + private Envelope volume_envelope, panning_envelope; + private int[] key_to_sample; + private Sample[] samples; + + public Instrument() { + name = ""; + set_volume_envelope( new Envelope() ); + set_panning_envelope( new Envelope() ); + key_to_sample = new int[ 96 ]; + set_num_samples( 1 ); + } + + public Envelope get_volume_envelope() { + return volume_envelope; + } + + public void set_volume_envelope( Envelope envelope ) { + if( envelope != null ) { + volume_envelope = envelope; + } + } + + public Envelope get_panning_envelope() { + return panning_envelope; + } + + public void set_panning_envelope( Envelope envelope ) { + if( envelope != null ) { + panning_envelope = envelope; + } + } + + public Sample get_sample_from_key( int key ) { + int sample_idx; + sample_idx = 0; + if( key > 0 && key <= key_to_sample.length ) { + sample_idx = key_to_sample[ key - 1 ]; + } + return get_sample( sample_idx ); + } + + public void set_key_to_sample( int key, int sample ) { + if( key > 0 && key <= key_to_sample.length ) { + key_to_sample[ key - 1 ] = sample; + } + } + + public int get_num_samples() { + return samples.length; + } + + public void set_num_samples( int num_samples ) { + if( num_samples < 1 ) { + num_samples = 1; + } + samples = new Sample[ num_samples ]; + set_sample( 0, null ); + } + + public Sample get_sample( int sample_index ) { + Sample sample; + sample = null; + if( sample_index >= 0 && sample_index < samples.length ) { + sample = samples[ sample_index ]; + } + if( sample == null ) { + sample = samples[ 0 ]; + } + return sample; + } + + public void set_sample( int sample_index, Sample sample ) { + if( sample_index >= 0 && sample_index < samples.length ) { + samples[ sample_index ] = sample; + } + if( samples[ 0 ] == null ) { + samples[ 0 ] = new Sample(); + } + } +} diff --git a/forge/forge_client/src/ibxm/LogTable.java b/forge/forge_client/src/ibxm/LogTable.java new file mode 100644 index 0000000000..3024b6aaba --- /dev/null +++ b/forge/forge_client/src/ibxm/LogTable.java @@ -0,0 +1,91 @@ + +package ibxm; + +/* + Base-2 Log and Exp functions, using linear-interpolated tables. +*/ +public class LogTable { + private static final int TABLE_SHIFT = 7; // 128 points (+1 for interp) + private static final int INTERP_SHIFT = IBXM.FP_SHIFT - TABLE_SHIFT; + private static final int INTERP_MASK = ( 1 << INTERP_SHIFT ) - 1; + + private static final int[] exp_2_table = { + 32768, 32945, 33124, 33304, 33485, 33667, 33850, 34033, + 34218, 34404, 34591, 34779, 34968, 35157, 35348, 35540, + 35733, 35927, 36122, 36319, 36516, 36714, 36913, 37114, + 37315, 37518, 37722, 37926, 38132, 38339, 38548, 38757, + 38967, 39179, 39392, 39606, 39821, 40037, 40254, 40473, + 40693, 40914, 41136, 41359, 41584, 41810, 42037, 42265, + 42494, 42725, 42957, 43190, 43425, 43661, 43898, 44136, + 44376, 44617, 44859, 45103, 45347, 45594, 45841, 46090, + 46340, 46592, 46845, 47099, 47355, 47612, 47871, 48131, + 48392, 48655, 48919, 49185, 49452, 49720, 49990, 50262, + 50535, 50809, 51085, 51362, 51641, 51922, 52204, 52487, + 52772, 53059, 53347, 53636, 53928, 54220, 54515, 54811, + 55108, 55408, 55709, 56011, 56315, 56621, 56928, 57238, + 57548, 57861, 58175, 58491, 58809, 59128, 59449, 59772, + 60096, 60423, 60751, 61081, 61412, 61746, 62081, 62418, + 62757, 63098, 63440, 63785, 64131, 64479, 64830, 65182, + 65536 + }; + + private static final int[] log_2_table = { + 0, 367, 732, 1095, 1454, 1811, 2165, 2517, + 2865, 3212, 3556, 3897, 4236, 4572, 4906, 5238, + 5568, 5895, 6220, 6542, 6863, 7181, 7497, 7812, + 8124, 8434, 8742, 9048, 9352, 9654, 9954, 10252, + 10548, 10843, 11136, 11427, 11716, 12003, 12289, 12573, + 12855, 13136, 13414, 13692, 13967, 14241, 14514, 14785, + 15054, 15322, 15588, 15853, 16117, 16378, 16639, 16898, + 17156, 17412, 17667, 17920, 18172, 18423, 18673, 18921, + 19168, 19413, 19657, 19900, 20142, 20383, 20622, 20860, + 21097, 21333, 21568, 21801, 22034, 22265, 22495, 22724, + 22952, 23178, 23404, 23628, 23852, 24074, 24296, 24516, + 24736, 24954, 25171, 25388, 25603, 25817, 26031, 26243, + 26455, 26665, 26875, 27084, 27292, 27499, 27705, 27910, + 28114, 28317, 28520, 28721, 28922, 29122, 29321, 29519, + 29716, 29913, 30109, 30304, 30498, 30691, 30884, 31076, + 31267, 31457, 31646, 31835, 32023, 32210, 32397, 32582, + 32768 + }; + + /* + Calculate log-base-2 of x (non-fixed-point). + A fixed point value is returned. + */ + public static int log_2( int x ) { + int shift; + /* Scale x to range 1.0 <= x < 2.0 */ + shift = IBXM.FP_SHIFT; + while( x < IBXM.FP_ONE ) { + x <<= 1; + shift--; + } + while( x >= ( IBXM.FP_ONE << 1 ) ) { + x >>= 1; + shift++; + } + return ( IBXM.FP_ONE * shift ) + eval_table( log_2_table, x - IBXM.FP_ONE ); + } + + /* + Raise 2 to the power x (fixed point). + A fixed point value is returned. + */ + public static int raise_2( int x ) { + int y; + y = eval_table( exp_2_table, x & IBXM.FP_MASK ) << IBXM.FP_SHIFT; + return y >> IBXM.FP_SHIFT - ( x >> IBXM.FP_SHIFT ); + } + + private static int eval_table( int[] table, int x ) { + int table_idx, table_frac, c, m, y; + table_idx = x >> INTERP_SHIFT; + table_frac = x & INTERP_MASK; + c = table[ table_idx ]; + m = table[ table_idx + 1 ] - c; + y = ( m * table_frac >> INTERP_SHIFT ) + c; + return y >> 15 - IBXM.FP_SHIFT; + } +} + diff --git a/forge/forge_client/src/ibxm/Module.java b/forge/forge_client/src/ibxm/Module.java new file mode 100644 index 0000000000..638ae1a59f --- /dev/null +++ b/forge/forge_client/src/ibxm/Module.java @@ -0,0 +1,138 @@ + +package ibxm; + +public class Module { + public String song_title; + public boolean linear_periods, fast_volume_slides, pal; + public int global_volume, channel_gain; + public int default_speed, default_tempo; + public int restart_sequence_index; + + private int[] initial_panning, sequence; + private Pattern[] patterns; + private Instrument[] instruments; + + private Pattern default_pattern; + private Instrument default_instrument; + + public Module() { + song_title = IBXM.VERSION; + set_num_channels( 1 ); + set_sequence_length( 1 ); + set_num_patterns( 0 ); + set_num_instruments( 0 ); + default_pattern = new Pattern(); + default_instrument = new Instrument(); + } + + public int get_num_channels() { + return initial_panning.length; + } + + public void set_num_channels( int num_channels ) { + if( num_channels < 1 ) { + num_channels = 1; + } + initial_panning = new int[ num_channels ]; + } + + public int get_initial_panning( int channel ) { + int panning; + panning = 128; + if( channel >= 0 && channel < initial_panning.length ) { + panning = initial_panning[ channel ]; + } + return panning; + } + + public void set_initial_panning( int channel, int panning ) { + if( channel >= 0 && channel < initial_panning.length ) { + initial_panning[ channel ] = panning; + } + } + + public int get_sequence_length() { + return sequence.length; + } + + public void set_sequence_length( int sequence_length ) { + if( sequence_length < 0 ) { + sequence_length = 0; + } + sequence = new int[ sequence_length ]; + } + + public void set_sequence( int sequence_index, int pattern_index ) { + if( sequence_index >= 0 && sequence_index < sequence.length ) { + sequence[ sequence_index ] = pattern_index; + } + } + + public int get_num_patterns() { + return patterns.length; + } + + public void set_num_patterns( int num_patterns ) { + if( num_patterns < 0 ) { + num_patterns = 0; + } + patterns = new Pattern[ num_patterns ]; + } + + public Pattern get_pattern_from_sequence( int sequence_index ) { + Pattern pattern; + pattern = default_pattern; + if( sequence_index >= 0 && sequence_index < sequence.length ) { + pattern = get_pattern( sequence[ sequence_index ] ); + } + return pattern; + } + + public Pattern get_pattern( int pattern_index ) { + Pattern pattern; + pattern = null; + if( pattern_index >= 0 && pattern_index < patterns.length ) { + pattern = patterns[ pattern_index ]; + } + if( pattern == null ) { + pattern = default_pattern; + } + return pattern; + } + + public void set_pattern( int pattern_index, Pattern pattern ) { + if( pattern_index >= 0 && pattern_index < patterns.length ) { + patterns[ pattern_index ] = pattern; + } + } + + public int get_num_instruments() { + return instruments.length; + } + + public void set_num_instruments( int num_instruments ) { + if( num_instruments < 0 ) { + num_instruments = 0; + } + instruments = new Instrument[ num_instruments ]; + } + + public Instrument get_instrument( int instrument_index ) { + Instrument instrument; + instrument = null; + if( instrument_index > 0 && instrument_index <= instruments.length ) { + instrument = instruments[ instrument_index - 1 ]; + } + if( instrument == null ) { + instrument = default_instrument; + } + return instrument; + } + + public void set_instrument( int instrument_index, Instrument instrument ) { + if( instrument_index > 0 && instrument_index <= instruments.length ) { + instruments[ instrument_index - 1 ] = instrument; + } + } +} + diff --git a/forge/forge_client/src/ibxm/Pattern.java b/forge/forge_client/src/ibxm/Pattern.java new file mode 100644 index 0000000000..663b3b2a7f --- /dev/null +++ b/forge/forge_client/src/ibxm/Pattern.java @@ -0,0 +1,61 @@ + +package ibxm; + +public class Pattern { + public int num_rows; + + private int data_offset, note_index; + private byte[] pattern_data; + + public Pattern() { + num_rows = 1; + set_pattern_data( new byte[ 0 ] ); + } + + public void set_pattern_data( byte[] data ) { + if( data != null ) { + pattern_data = data; + } + data_offset = 0; + note_index = 0; + } + + public void get_note( int[] note, int index ) { + if( index < note_index ) { + note_index = 0; + data_offset = 0; + } + while( note_index <= index ) { + data_offset = next_note( data_offset, note ); + note_index += 1; + } + } + + public int next_note( int data_offset, int[] note ) { + int bitmask, field; + if( data_offset < 0 ) { + data_offset = pattern_data.length; + } + bitmask = 0x80; + if( data_offset < pattern_data.length ) { + bitmask = pattern_data[ data_offset ] & 0xFF; + } + if( ( bitmask & 0x80 ) == 0x80 ) { + data_offset += 1; + } else { + bitmask = 0x1F; + } + for( field = 0; field < 5; field++ ) { + note[ field ] = 0; + if( ( bitmask & 0x01 ) == 0x01 ) { + if( data_offset < pattern_data.length ) { + note[ field ] = pattern_data[ data_offset ] & 0xFF; + data_offset += 1; + } + } + bitmask = bitmask >> 1; + } + return data_offset; + } +} + diff --git a/forge/forge_client/src/ibxm/Player.java b/forge/forge_client/src/ibxm/Player.java new file mode 100644 index 0000000000..2ea04ddc47 --- /dev/null +++ b/forge/forge_client/src/ibxm/Player.java @@ -0,0 +1,132 @@ + +package ibxm; + +import java.io.*; +import javax.sound.sampled.*; + +public class Player { + private Thread play_thread; + private IBXM ibxm; + private Module module; + private int song_duration, play_position; + private boolean running, loop; + private byte[] output_buffer; + private SourceDataLine output_line; + + /** + Simple command-line test player. + */ + public static void main( String[] args ) throws Exception { + if( args.length < 1 ) { + System.err.println( "Usage: java ibxm.Player " ); + System.exit( 0 ); + } + FileInputStream file_input_stream = new FileInputStream( args[ 0 ] ); + Player player = new Player(); + player.set_module( Player.load_module( file_input_stream ) ); + file_input_stream.close(); + player.play(); + } + + /** + Decode the data in the specified InputStream into a Module instance. + @param input an InputStream containing the module file to be decoded. + @throws IllegalArgumentException if the data is not recognised as a module file. + */ + public static Module load_module( InputStream input ) throws IllegalArgumentException, IOException { + DataInputStream data_input_stream = new DataInputStream( input ); + /* Check if data is in XM format.*/ + byte[] xm_header = new byte[ 60 ]; + data_input_stream.readFully( xm_header ); + if( FastTracker2.is_xm( xm_header ) ) + return FastTracker2.load_xm( xm_header, data_input_stream ); + /* Check if data is in ScreamTracker 3 format.*/ + byte[] s3m_header = new byte[ 96 ]; + System.arraycopy( xm_header, 0, s3m_header, 0, 60 ); + data_input_stream.readFully( s3m_header, 60, 36 ); + if( ScreamTracker3.is_s3m( s3m_header ) ) + return ScreamTracker3.load_s3m( s3m_header, data_input_stream ); + /* Check if data is in ProTracker format.*/ + byte[] mod_header = new byte[ 1084 ]; + System.arraycopy( s3m_header, 0, mod_header, 0, 96 ); + data_input_stream.readFully( mod_header, 96, 988 ); + return ProTracker.load_mod( mod_header, data_input_stream ); + } + + /** + Instantiate a new Player. + */ + public Player() throws LineUnavailableException { + ibxm = new IBXM( 48000 ); + set_loop( true ); + output_line = AudioSystem.getSourceDataLine( new AudioFormat( 48000, 16, 2, true, true ) ); + output_buffer = new byte[ 1024 * 4 ]; + } + + /** + Set the Module instance to be played. + */ + public void set_module( Module m ) { + if( m != null ) module = m; + stop(); + ibxm.set_module( module ); + song_duration = ibxm.calculate_song_duration(); + } + + /** + If loop is true, playback will continue indefinitely, + otherwise the module will play through once and stop. + */ + public void set_loop( boolean loop ) { + this.loop = loop; + } + + /** + Open the audio device and begin playback. + If a module is already playing it will be restarted. + */ + public void play() { + stop(); + play_thread = new Thread( new Driver() ); + play_thread.start(); + } + + /** + Stop playback and close the audio device. + */ + public void stop() { + running = false; + if( play_thread != null ) { + try { + play_thread.join(); + } catch( InterruptedException ie ) {} + } + } + + private class Driver implements Runnable { + public void run() { + if( running ) return; + try { + output_line.open(); + output_line.start(); + play_position = 0; + running = true; + while( running ) { + int frames = song_duration - play_position; + if( frames > 1024 ) frames = 1024; + ibxm.get_audio( output_buffer, frames ); + output_line.write( output_buffer, 0, frames * 4 ); + play_position += frames; + if( play_position >= song_duration ) { + play_position = 0; + if( !loop ) running = false; + } + } + output_line.drain(); + output_line.close(); + } catch( LineUnavailableException lue ) { + lue.printStackTrace(); + } + } + } +} diff --git a/forge/forge_client/src/ibxm/ProTracker.java b/forge/forge_client/src/ibxm/ProTracker.java new file mode 100644 index 0000000000..102e213f0f --- /dev/null +++ b/forge/forge_client/src/ibxm/ProTracker.java @@ -0,0 +1,234 @@ +package ibxm; + +import java.io.*; + +public class ProTracker { + public static boolean is_mod( byte[] header_1084_bytes ) { + boolean is_mod; + is_mod = false; + if( calculate_num_channels( header_1084_bytes ) > 0 ) { + is_mod = true; + } + return is_mod; + } + + public static Module load_mod( byte[] header_1084_bytes, DataInput data_input ) throws IOException { + int num_channels, channel_idx, panning; + int sequence_length, restart_idx, sequence_idx; + int num_patterns, pattern_idx, instrument_idx; + Module module; + num_channels = calculate_num_channels( header_1084_bytes ); + if( num_channels < 1 ) { + throw new IllegalArgumentException( "ProTracker: Unrecognised module format!" ); + } + module = new Module(); + module.song_title = ascii_text( header_1084_bytes, 0, 20 ); + module.pal = ( num_channels == 4 ); + module.global_volume = 64; + module.channel_gain = IBXM.FP_ONE * 3 / 8; + module.default_speed = 6; + module.default_tempo = 125; + module.set_num_channels( num_channels ); + for( channel_idx = 0; channel_idx < num_channels; channel_idx++ ) { + panning = 64; + if( ( channel_idx & 0x03 ) == 0x01 || ( channel_idx & 0x03 ) == 0x02 ) { + panning = 192; + } + module.set_initial_panning( channel_idx, panning ); + } + sequence_length = header_1084_bytes[ 950 ] & 0x7F; + restart_idx = header_1084_bytes[ 951 ] & 0x7F; + if( restart_idx >= sequence_length ) { + restart_idx = 0; + } + module.restart_sequence_index = restart_idx; + module.set_sequence_length( sequence_length ); + for( sequence_idx = 0; sequence_idx < sequence_length; sequence_idx++ ) { + module.set_sequence( sequence_idx, header_1084_bytes[ 952 + sequence_idx ] & 0x7F ); + } + num_patterns = calculate_num_patterns( header_1084_bytes ); + module.set_num_patterns( num_patterns ); + for( pattern_idx = 0; pattern_idx < num_patterns; pattern_idx++ ) { + module.set_pattern( pattern_idx, read_mod_pattern( data_input, num_channels ) ); + } + module.set_num_instruments( 31 ); + for( instrument_idx = 1; instrument_idx <= 31; instrument_idx++ ) { + module.set_instrument( instrument_idx, read_mod_instrument( header_1084_bytes, instrument_idx, data_input ) ); + } + return module; + } + + private static int calculate_num_patterns( byte[] module_header ) { + int num_patterns, order_entry, pattern_idx; + num_patterns = 0; + for( pattern_idx = 0; pattern_idx < 128; pattern_idx++ ) { + order_entry = module_header[ 952 + pattern_idx ] & 0x7F; + if( order_entry >= num_patterns ) { + num_patterns = order_entry + 1; + } + } + return num_patterns; + } + + private static int calculate_num_channels( byte[] module_header ) { + int num_channels; + switch( ( module_header[ 1082 ] << 8 ) | module_header[ 1083 ] ) { + case 0x4b2e: /* M.K. */ + case 0x4b21: /* M!K! */ + case 0x542e: /* N.T. */ + case 0x5434: /* FLT4 */ + num_channels = 4; + break; + case 0x484e: /* xCHN */ + num_channels = module_header[ 1080 ] - 48; + break; + case 0x4348: /* xxCH */ + num_channels = ( ( module_header[ 1080 ] - 48 ) * 10 ) + ( module_header[ 1081 ] - 48 ); + break; + default: + /* Not recognised. */ + num_channels = 0; + break; + } + return num_channels; + } + + private static Pattern read_mod_pattern( DataInput data_input, int num_channels ) throws IOException { + int input_idx, output_idx; + int period, instrument, effect, effect_param; + Pattern pattern; + byte[] input_pattern_data, output_pattern_data; + pattern = new Pattern(); + pattern.num_rows = 64; + input_pattern_data = new byte[ 64 * num_channels * 4 ]; + output_pattern_data = new byte[ 64 * num_channels * 5 ]; + data_input.readFully( input_pattern_data ); + input_idx = 0; + output_idx = 0; + while( input_idx < input_pattern_data.length ) { + period = ( input_pattern_data[ input_idx ] & 0x0F ) << 8; + period = period | ( input_pattern_data[ input_idx + 1 ] & 0xFF ); + output_pattern_data[ output_idx ] = to_key( period ); + instrument = input_pattern_data[ input_idx ] & 0x10; + instrument = instrument | ( ( input_pattern_data[ input_idx + 2 ] & 0xF0 ) >> 4 ); + output_pattern_data[ output_idx + 1 ] = ( byte ) instrument; + effect = input_pattern_data[ input_idx + 2 ] & 0x0F; + effect_param = input_pattern_data[ input_idx + 3 ] & 0xFF; + if( effect == 0x01 && effect_param == 0 ) { + /* Portamento up of zero has no effect. */ + effect = 0; + } + if( effect == 0x02 && effect_param == 0 ) { + /* Portamento down of zero has no effect. */ + effect = 0; + } + if( effect == 0x08 && num_channels == 4 ) { + /* Some Amiga mods use effect 0x08 for reasons other than panning.*/ + effect = 0; + effect_param = 0; + } + if( effect == 0x0A && effect_param == 0 ) { + /* Volume slide of zero has no effect.*/ + effect = 0; + } + if( effect == 0x05 && effect_param == 0 ) { + /* Porta + Volume slide of zero has no effect.*/ + effect = 0x03; + } + if( effect == 0x06 && effect_param == 0 ) { + /* Vibrato + Volume slide of zero has no effect.*/ + effect = 0x04; + } + output_pattern_data[ output_idx + 3 ] = ( byte ) effect; + output_pattern_data[ output_idx + 4 ] = ( byte ) effect_param; + input_idx += 4; + output_idx += 5; + } + pattern.set_pattern_data( output_pattern_data ); + return pattern; + } + + private static Instrument read_mod_instrument( byte[] mod_header, int idx, DataInput data_input ) throws IOException { + int header_offset, sample_data_length; + int loop_start, loop_length, sample_idx, fine_tune; + Instrument instrument; + Sample sample; + byte[] raw_sample_data; + short[] sample_data; + header_offset = ( idx - 1 ) * 30 + 20; + instrument = new Instrument(); + instrument.name = ascii_text( mod_header, header_offset, 22 ); + sample = new Sample(); + sample_data_length = unsigned_short_be( mod_header, header_offset + 22 ) << 1; + fine_tune = mod_header[ header_offset + 24 ] & 0x0F; + if( fine_tune > 7 ) { + fine_tune -= 16; + } + sample.transpose = ( fine_tune << IBXM.FP_SHIFT ) / 96; + sample.volume = mod_header[ header_offset + 25 ] & 0x7F; + loop_start = unsigned_short_be( mod_header, header_offset + 26 ) << 1; + loop_length = unsigned_short_be( mod_header, header_offset + 28 ) << 1; + if( loop_length < 4 ) { + loop_length = 0; + } + raw_sample_data = new byte[ sample_data_length ]; + sample_data = new short[ sample_data_length ]; + try { + data_input.readFully( raw_sample_data ); + } catch( EOFException e ) { + System.out.println( "ProTracker: Instrument " + idx + " has samples missing." ); + } + for( sample_idx = 0; sample_idx < raw_sample_data.length; sample_idx++ ) { + sample_data[ sample_idx ] = ( short ) ( raw_sample_data[ sample_idx ] << 8 ); + } + 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 byte to_key( int period ) { + int oct, key; + if( period < 32 ) { + key = 0; + } else { + oct = LogTable.log_2( 7256 ) - LogTable.log_2( period ); + if( oct < 0 ) { + key = 0; + } else { + key = oct * 12; + key = key >> ( IBXM.FP_SHIFT - 1 ); + key = ( key >> 1 ) + ( key & 1 ); + } + } + return ( byte ) key; + } + + private static int unsigned_short_be( byte[] buf, int offset ) { + int value; + value = ( buf[ offset ] & 0xFF ) << 8; + value = value | ( buf[ offset + 1 ] & 0xFF ); + 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; + } +} + diff --git a/forge/forge_client/src/ibxm/Sample.java b/forge/forge_client/src/ibxm/Sample.java new file mode 100644 index 0000000000..4820dbba12 --- /dev/null +++ b/forge/forge_client/src/ibxm/Sample.java @@ -0,0 +1,239 @@ + +package ibxm; + +public class Sample { + public String name; + public boolean set_panning; + public int volume, panning; + public int transpose; + + private int loop_start, loop_length; + private short[] sample_data; + + /* For the sinc interpolator.*/ + private static final int POINT_SHIFT = 4; + private static final int POINTS = 1 << POINT_SHIFT; + private static final int OVERLAP = POINTS >> 1; + private static final int INTERP_SHIFT = IBXM.FP_SHIFT - 4; + private static final int INTERP_BITMASK = ( 1 << INTERP_SHIFT ) - 1; + private static final short[] sinc_table = { + 0, -7, 27, -71, 142, -227, 299, 32439, 299, -227, 142, -71, 27, -7, 0, 0, + 0, 0, -5, 36, -142, 450, -1439, 32224, 2302, -974, 455, -190, 64, -15, 2, 0, + 0, 6, -33, 128, -391, 1042, -2894, 31584, 4540, -1765, 786, -318, 105, -25, 3, 0, + 0, 10, -55, 204, -597, 1533, -4056, 30535, 6977, -2573, 1121, -449, 148, -36, 5, 0, + -1, 13, -71, 261, -757, 1916, -4922, 29105, 9568, -3366, 1448, -578, 191, -47, 7, 0, + -1, 15, -81, 300, -870, 2185, -5498, 27328, 12263, -4109, 1749, -698, 232, -58, 9, 0, + -1, 15, -86, 322, -936, 2343, -5800, 25249, 15006, -4765, 2011, -802, 269, -68, 10, 0, + -1, 15, -87, 328, -957, 2394, -5849, 22920, 17738, -5298, 2215, -885, 299, -77, 12, 0, + 0, 14, -83, 319, -938, 2347, -5671, 20396, 20396, -5671, 2347, -938, 319, -83, 14, 0, + 0, 12, -77, 299, -885, 2215, -5298, 17738, 22920, -5849, 2394, -957, 328, -87, 15, -1, + 0, 10, -68, 269, -802, 2011, -4765, 15006, 25249, -5800, 2343, -936, 322, -86, 15, -1, + 0, 9, -58, 232, -698, 1749, -4109, 12263, 27328, -5498, 2185, -870, 300, -81, 15, -1, + 0, 7, -47, 191, -578, 1448, -3366, 9568, 29105, -4922, 1916, -757, 261, -71, 13, -1, + 0, 5, -36, 148, -449, 1121, -2573, 6977, 30535, -4056, 1533, -597, 204, -55, 10, 0, + 0, 3, -25, 105, -318, 786, -1765, 4540, 31584, -2894, 1042, -391, 128, -33, 6, 0, + 0, 2, -15, 64, -190, 455, -974, 2302, 32224, -1439, 450, -142, 36, -5, 0, 0, + 0, 0, -7, 27, -71, 142, -227, 299, 32439, 299, -227, 142, -71, 27, -7, 0 + }; + + public Sample() { + name = ""; + set_sample_data( new short[ 0 ], 0, 0, false ); + } + + public void set_sample_data( short[] data, int loop_start, int loop_length, boolean ping_pong ) { + int offset; + short sample; + if( loop_start < 0 ) { + loop_start = 0; + } + if( loop_start >= data.length ) { + loop_start = data.length - 1; + } + if( loop_start + loop_length > data.length ) { + loop_length = data.length - loop_start; + } + if( loop_length <= 1 ) { + sample_data = new short[ OVERLAP + data.length + OVERLAP * 3 ]; + System.arraycopy( data, 0, sample_data, OVERLAP, data.length ); + offset = 0; + while( offset < OVERLAP ) { + sample = sample_data[ OVERLAP + data.length - 1 ]; + sample = ( short ) ( sample * ( OVERLAP - offset ) / OVERLAP ); + sample_data[ OVERLAP + data.length + offset ] = sample; + offset += 1; + } + loop_start = OVERLAP + data.length + OVERLAP; + loop_length = 1; + } else { + if( ping_pong ) { + sample_data = new short[ OVERLAP + loop_start + loop_length * 2 + OVERLAP * 2 ]; + System.arraycopy( data, 0, sample_data, OVERLAP, loop_start + loop_length ); + offset = 0; + while( offset < loop_length ) { + sample = data[ loop_start + loop_length - offset - 1 ]; + sample_data[ OVERLAP + loop_start + loop_length + offset ] = sample; + offset += 1; + } + loop_start = loop_start + OVERLAP; + loop_length = loop_length * 2; + } else { + sample_data = new short[ OVERLAP + loop_start + loop_length + OVERLAP * 2 ]; + System.arraycopy( data, 0, sample_data, OVERLAP, loop_start + loop_length ); + loop_start = loop_start + OVERLAP; + } + offset = 0; + while( offset < OVERLAP * 2 ) { + sample = sample_data[ loop_start + offset ]; + sample_data[ loop_start + loop_length + offset ] = sample; + offset += 1; + } + } + this.loop_start = loop_start; + this.loop_length = loop_length; + } + + public void resample_nearest( + int sample_idx, int sample_frac, int step, int left_gain, int right_gain, + int[] mix_buffer, int frame_offset, int frames ) { + int loop_end, offset, end, max_sample_idx; + sample_idx += OVERLAP; + loop_end = loop_start + loop_length - 1; + offset = frame_offset << 1; + end = ( frame_offset + frames - 1 ) << 1; + while( frames > 0 ) { + if( sample_idx > loop_end ) { + if( loop_length <= 1 ) { + break; + } + sample_idx = loop_start + ( sample_idx - loop_start ) % loop_length; + } + max_sample_idx = sample_idx + ( ( sample_frac + ( frames - 1 ) * step ) >> IBXM.FP_SHIFT ); + if( max_sample_idx > loop_end ) { + while( sample_idx <= loop_end ) { + mix_buffer[ offset++ ] += sample_data[ sample_idx ] * left_gain >> IBXM.FP_SHIFT; + mix_buffer[ offset++ ] += sample_data[ sample_idx ] * right_gain >> IBXM.FP_SHIFT; + sample_frac += step; + sample_idx += sample_frac >> IBXM.FP_SHIFT; + sample_frac &= IBXM.FP_MASK; + } + } else { + while( offset <= end ) { + mix_buffer[ offset++ ] += sample_data[ sample_idx ] * left_gain >> IBXM.FP_SHIFT; + mix_buffer[ offset++ ] += sample_data[ sample_idx ] * right_gain >> IBXM.FP_SHIFT; + sample_frac += step; + sample_idx += sample_frac >> IBXM.FP_SHIFT; + sample_frac &= IBXM.FP_MASK; + } + } + frames = ( end - offset + 2 ) >> 1; + } + } + + public void resample_linear( + int sample_idx, int sample_frac, int step, int left_gain, int right_gain, + int[] mix_buffer, int frame_offset, int frames ) { + int loop_end, offset, end, max_sample_idx, amplitude; + sample_idx += OVERLAP; + loop_end = loop_start + loop_length - 1; + offset = frame_offset << 1; + end = ( frame_offset + frames - 1 ) << 1; + while( frames > 0 ) { + if( sample_idx > loop_end ) { + if( loop_length <= 1 ) { + break; + } + sample_idx = loop_start + ( sample_idx - loop_start ) % loop_length; + } + max_sample_idx = sample_idx + ( ( sample_frac + ( frames - 1 ) * step ) >> IBXM.FP_SHIFT ); + if( max_sample_idx > loop_end ) { + while( sample_idx <= loop_end ) { + amplitude = sample_data[ sample_idx ]; + amplitude += ( sample_data[ sample_idx + 1 ] - amplitude ) * sample_frac >> IBXM.FP_SHIFT; + mix_buffer[ offset++ ] += amplitude * left_gain >> IBXM.FP_SHIFT; + mix_buffer[ offset++ ] += amplitude * right_gain >> IBXM.FP_SHIFT; + sample_frac += step; + sample_idx += sample_frac >> IBXM.FP_SHIFT; + sample_frac &= IBXM.FP_MASK; + } + } else { + while( offset <= end ) { + amplitude = sample_data[ sample_idx ]; + amplitude += ( sample_data[ sample_idx + 1 ] - amplitude ) * sample_frac >> IBXM.FP_SHIFT; + mix_buffer[ offset++ ] += amplitude * left_gain >> IBXM.FP_SHIFT; + mix_buffer[ offset++ ] += amplitude * right_gain >> IBXM.FP_SHIFT; + sample_frac += step; + sample_idx += sample_frac >> IBXM.FP_SHIFT; + sample_frac &= IBXM.FP_MASK; + } + } + frames = ( end - offset + 2 ) >> 1; + } + } + + public void resample_sinc( + int sample_idx, int sample_frac, int step, int left_gain, int right_gain, + int[] mix_buffer, int frame_offset, int frames ) { + int offset, end, loop_end, table_idx, a1, a2, amplitude; + loop_end = loop_start + loop_length - 1; + offset = frame_offset << 1; + end = ( frame_offset + frames - 1 ) << 1; + while( offset <= end ) { + if( sample_idx > loop_end ) { + if( loop_length <= 1 ) { + break; + } + sample_idx = loop_start + ( sample_idx - loop_start ) % loop_length; + } + table_idx = ( sample_frac >> INTERP_SHIFT ) << POINT_SHIFT; + a1 = sinc_table[ table_idx + 0 ] * sample_data[ sample_idx + 0 ] >> 15; + a1 += sinc_table[ table_idx + 1 ] * sample_data[ sample_idx + 1 ] >> 15; + a1 += sinc_table[ table_idx + 2 ] * sample_data[ sample_idx + 2 ] >> 15; + a1 += sinc_table[ table_idx + 3 ] * sample_data[ sample_idx + 3 ] >> 15; + a1 += sinc_table[ table_idx + 4 ] * sample_data[ sample_idx + 4 ] >> 15; + a1 += sinc_table[ table_idx + 5 ] * sample_data[ sample_idx + 5 ] >> 15; + a1 += sinc_table[ table_idx + 6 ] * sample_data[ sample_idx + 6 ] >> 15; + a1 += sinc_table[ table_idx + 7 ] * sample_data[ sample_idx + 7 ] >> 15; + a1 += sinc_table[ table_idx + 8 ] * sample_data[ sample_idx + 8 ] >> 15; + a1 += sinc_table[ table_idx + 9 ] * sample_data[ sample_idx + 9 ] >> 15; + a1 += sinc_table[ table_idx + 10 ] * sample_data[ sample_idx + 10 ] >> 15; + a1 += sinc_table[ table_idx + 11 ] * sample_data[ sample_idx + 11 ] >> 15; + a1 += sinc_table[ table_idx + 12 ] * sample_data[ sample_idx + 12 ] >> 15; + a1 += sinc_table[ table_idx + 13 ] * sample_data[ sample_idx + 13 ] >> 15; + a1 += sinc_table[ table_idx + 14 ] * sample_data[ sample_idx + 14 ] >> 15; + a1 += sinc_table[ table_idx + 15 ] * sample_data[ sample_idx + 15 ] >> 15; + a2 = sinc_table[ table_idx + 16 ] * sample_data[ sample_idx + 0 ] >> 15; + a2 += sinc_table[ table_idx + 17 ] * sample_data[ sample_idx + 1 ] >> 15; + a2 += sinc_table[ table_idx + 18 ] * sample_data[ sample_idx + 2 ] >> 15; + a2 += sinc_table[ table_idx + 19 ] * sample_data[ sample_idx + 3 ] >> 15; + a2 += sinc_table[ table_idx + 20 ] * sample_data[ sample_idx + 4 ] >> 15; + a2 += sinc_table[ table_idx + 21 ] * sample_data[ sample_idx + 5 ] >> 15; + a2 += sinc_table[ table_idx + 22 ] * sample_data[ sample_idx + 6 ] >> 15; + a2 += sinc_table[ table_idx + 23 ] * sample_data[ sample_idx + 7 ] >> 15; + a2 += sinc_table[ table_idx + 24 ] * sample_data[ sample_idx + 8 ] >> 15; + a2 += sinc_table[ table_idx + 25 ] * sample_data[ sample_idx + 9 ] >> 15; + a2 += sinc_table[ table_idx + 26 ] * sample_data[ sample_idx + 10 ] >> 15; + a2 += sinc_table[ table_idx + 27 ] * sample_data[ sample_idx + 11 ] >> 15; + a2 += sinc_table[ table_idx + 28 ] * sample_data[ sample_idx + 12 ] >> 15; + a2 += sinc_table[ table_idx + 29 ] * sample_data[ sample_idx + 13 ] >> 15; + a2 += sinc_table[ table_idx + 30 ] * sample_data[ sample_idx + 14 ] >> 15; + a2 += sinc_table[ table_idx + 31 ] * sample_data[ sample_idx + 15 ] >> 15; + amplitude = a1 + ( ( a2 - a1 ) * ( sample_frac & INTERP_BITMASK ) >> INTERP_SHIFT ); + mix_buffer[ offset ] += amplitude * left_gain >> IBXM.FP_SHIFT; + mix_buffer[ offset + 1 ] += amplitude * right_gain >> IBXM.FP_SHIFT; + offset += 2; + sample_frac += step; + sample_idx += sample_frac >> IBXM.FP_SHIFT; + sample_frac &= IBXM.FP_MASK; + } + } + + public boolean has_finished( int sample_idx ) { + boolean finished; + finished = false; + if( loop_length <= 1 && sample_idx > loop_start ) { + finished = true; + } + return finished; + } +} diff --git a/forge/forge_client/src/ibxm/ScreamTracker3.java b/forge/forge_client/src/ibxm/ScreamTracker3.java new file mode 100644 index 0000000000..798e799ba5 --- /dev/null +++ b/forge/forge_client/src/ibxm/ScreamTracker3.java @@ -0,0 +1,489 @@ + +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; + } +} + diff --git a/forge/forge_client/src/net/minecraft/src/forge/ModCompatibilityClient.java b/forge/forge_client/src/net/minecraft/src/forge/ModCompatibilityClient.java index c7c6f947c0..db2bf77c5e 100644 --- a/forge/forge_client/src/net/minecraft/src/forge/ModCompatibilityClient.java +++ b/forge/forge_client/src/net/minecraft/src/forge/ModCompatibilityClient.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.lang.reflect.Field; import paulscode.sound.SoundSystemConfig; +import paulscode.sound.codecs.CodecIBXM; import net.minecraft.client.Minecraft; import net.minecraft.src.*; @@ -49,21 +50,6 @@ public class ModCompatibilityClient * It also lowers the interval between background music songs to 6000 */ public static SoundPool audioModSoundPoolCave; - private static int isAudioModInstalled = -1; - - /** - * Determine if AudioMod is installed by checking for the existence of IBMX Codec, - * I wish there was a less ambiguous way, but there isn't. - * @return True if the IBMX codec was found (indicating AudioMod is installed) - */ - public static boolean isAudioModInstalled() - { - if (isAudioModInstalled == -1) - { - isAudioModInstalled = (getClass("paulscode.sound.codecs.CodecIBXM") != null ? 1 : 0); - } - return isAudioModInstalled == 1; - } /** * Populates the sound pools with with sounds from the /resources/mods folder @@ -73,10 +59,6 @@ public class ModCompatibilityClient */ public static void audioModLoad(SoundManager mngr) { - if (!isAudioModInstalled()) - { - return; - } audioModSoundPoolCave = new SoundPool(); audioModLoadModAudio("resources/mod/sound", mngr.getSoundsPool()); audioModLoadModAudio("resources/mod/streaming", mngr.getStreamingPool()); @@ -145,13 +127,9 @@ public class ModCompatibilityClient */ public static void audioModAddCodecs() { - Class codec = getClass("paulscode.sound.codecs.CodecIBXM"); - if (isAudioModInstalled() && codec != null) - { - SoundSystemConfig.setCodec("xm", codec); - SoundSystemConfig.setCodec("s3m", codec); - SoundSystemConfig.setCodec("mod", codec); - } + SoundSystemConfig.setCodec("xm", CodecIBXM.class); + SoundSystemConfig.setCodec("s3m", CodecIBXM.class); + SoundSystemConfig.setCodec("mod", CodecIBXM.class); } /** @@ -165,7 +143,7 @@ public class ModCompatibilityClient public static SoundPoolEntry audioModPickBackgroundMusic(SoundManager soundManager, SoundPoolEntry current) { Minecraft mc = ModLoader.getMinecraftInstance(); - if (isAudioModInstalled() && mc != null && mc.theWorld != null && audioModSoundPoolCave != null) + if (mc != null && mc.theWorld != null && audioModSoundPoolCave != null) { Entity ent = mc.renderViewEntity; int x = MathHelper.func_40346_b(ent.posX); diff --git a/forge/forge_client/src/net/minecraft/src/forge/adaptors/SoundHandlerAdaptor.java b/forge/forge_client/src/net/minecraft/src/forge/adaptors/SoundHandlerAdaptor.java new file mode 100644 index 0000000000..c7b29b145d --- /dev/null +++ b/forge/forge_client/src/net/minecraft/src/forge/adaptors/SoundHandlerAdaptor.java @@ -0,0 +1,42 @@ +package net.minecraft.src.forge.adaptors; + +import net.minecraft.src.SoundManager; +import net.minecraft.src.SoundPoolEntry; +import net.minecraft.src.forge.ISoundHandler; + +/** + * Adaptor class for convince sake when implementing ISoundHandler, as most + * mods will only actually care about onSetupAudio/onLoadSoundSettings. + */ +public class SoundHandlerAdaptor implements ISoundHandler +{ + @Override + public void onSetupAudio(SoundManager soundManager){} + + @Override + public void onLoadSoundSettings(SoundManager soundManager){} + + @Override + public SoundPoolEntry onPlayBackgroundMusic(SoundManager soundManager, SoundPoolEntry entry) + { + return entry; + } + + @Override + public SoundPoolEntry onPlayStreaming(SoundManager soundManager, SoundPoolEntry entry, String soundName, float x, float y, float z) + { + return entry; + } + + @Override + public SoundPoolEntry onPlaySound(SoundManager soundManager, SoundPoolEntry entry, String soundName, float x, float y, float z, float volume, float pitch) + { + return entry; + } + + @Override + public SoundPoolEntry onPlaySoundEffect(SoundManager soundManager, SoundPoolEntry entry, String soundName, float volume, float pitch) + { + return entry; + } +} diff --git a/forge/forge_client/src/paulscode/sound/codecs/CodecIBXM.java b/forge/forge_client/src/paulscode/sound/codecs/CodecIBXM.java new file mode 100644 index 0000000000..7a39ab3068 --- /dev/null +++ b/forge/forge_client/src/paulscode/sound/codecs/CodecIBXM.java @@ -0,0 +1,642 @@ +package paulscode.sound.codecs; + +import java.io.DataInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; +import javax.sound.sampled.AudioFormat; + +import paulscode.sound.ICodec; +import paulscode.sound.SoundBuffer; +import paulscode.sound.SoundSystemConfig; +import paulscode.sound.SoundSystemLogger; + +import ibxm.FastTracker2; +import ibxm.IBXM; +import ibxm.Module; +import ibxm.ProTracker; +import ibxm.ScreamTracker3; + +/** + * The CodecIBXM class provides an ICodec interface for reading from MOD/S3M/XM + * files via the IBXM library. + * SoundSystem CodecIBXM Class License:

+ * You are free to use this class for any purpose, commercial or otherwise. + * You may modify this class or source code, and distribute it any way you + * like, provided the following conditions are met: + *
+ * 1) You may not falsely claim to be the author of this class or any + * unmodified portion of it. + *
+ * 2) You may not copyright this class or a modified version of it and then + * sue me for copyright infringement. + *
+ * 3) If you modify the source code, you must clearly document the changes + * made before redistributing the modified source code, so other users know + * it is not the original code. + *
+ * 4) You are not required to give me credit for this class in any derived + * work, but if you do, you must also mention my website: + * http://www.paulscode.com + *
+ * 5) I the author will not be responsible for any damages (physical, + * financial, or otherwise) caused by the use if this class or any portion + * of it. + *
+ * 6) I the author do not guarantee, warrant, or make any representations, + * either expressed or implied, regarding the use of this class or any + * portion of it. + *

+ * Author: Paul Lamb + *
+ * http://www.paulscode.com + *


+ * + * This software is based on or using the IBXM library available from + * http://www.geocities.com/sunet2000/ + *

+ *
+ * IBXM is copyright (c) 2007, Martin Cameron, and is licensed under the BSD + * License. + *

+ * All rights reserved. + *

+ * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + *

+ * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials + * provided with the distribution. Neither the name of mumart nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *


+ */ +public class CodecIBXM implements ICodec +{ +/** + * Used to return a current value from one of the synchronized + * boolean-interface methods. + */ + private static final boolean GET = false; + +/** + * Used to set the value in one of the synchronized boolean-interface methods. + */ + private static final boolean SET = true; + +/** + * Used when a parameter for one of the synchronized boolean-interface methods + * is not aplicable. + */ + private static final boolean XXX = false; + +/** + * True if there is no more data to read in. + */ + private boolean endOfStream = false; + +/** + * True if the stream has finished initializing. + */ + private boolean initialized = false; + +/** + * Format the converted audio will be in. + */ + private AudioFormat myAudioFormat = null; + +/** + * True if the using library requires data read by this codec to be + * reverse-ordered before returning it from methods read() and readAll(). + */ + private boolean reverseBytes = false; + +/** + * IBXM decoder. + */ + private IBXM ibxm; + +/** + * Module instance to be played. + */ + private Module module; + +/** + * Duration of the audio (in frames). + */ + private int songDuration; + +/** + * Audio read position (in frames). + */ + private int playPosition; + +/** + * Processes status messages, warnings, and error messages. + */ + private SoundSystemLogger logger; + +/** + * Constructor: Grabs a handle to the logger. + */ + public CodecIBXM() + { + logger = SoundSystemConfig.getLogger(); + } + +/** + * Tells this codec when it will need to reverse the byte order of + * the data before returning it in the read() and readAll() methods. The + * IBXM library produces audio data in a format that some external audio + * libraries require to be reversed. Derivatives of the Library and Source + * classes for audio libraries which require this type of data to be reversed + * will call the reverseByteOrder() method. + * @param b True if the calling audio library requires byte-reversal. + */ + public void reverseByteOrder( boolean b ) + { + reverseBytes = b; + } + +/** + * Prepares an audio stream to read from. If another stream is already opened, + * it will be closed and a new audio stream opened in its place. + * @param url URL to an audio file to stream from. + * @return False if an error occurred or if end of stream was reached. + */ + public boolean initialize( URL url ) + { + initialized( SET, false ); + cleanup(); + + if( url == null ) + { + errorMessage( "url null in method 'initialize'" ); + cleanup(); + return false; + } + + InputStream is = null; + + try + { + is = url.openStream(); + } + catch( IOException ioe ) + { + errorMessage( "Unable to open stream in method 'initialize'" ); + printStackTrace( ioe ); + return false; + } + + if( ibxm == null ) + ibxm = new IBXM( 48000 ); + if( myAudioFormat == null ) + myAudioFormat = new AudioFormat( 48000, 16, 2, true, true ); + + try + { + setModule( loadModule( is ) ); + } + catch( IllegalArgumentException iae ) + { + errorMessage( "Illegal argument in method 'initialize'" ); + printStackTrace( iae ); + if( is != null ) + { + try + { + is.close(); + } + catch( IOException ioe ) + {} + } + return false; + } + catch( IOException ioe ) + { + errorMessage( "Error loading module in method 'initialize'" ); + printStackTrace( ioe ); + if( is != null ) + { + try + { + is.close(); + } + catch( IOException ioe2 ) + {} + } + return false; + } + + if( is != null ) + { + try + { + is.close(); + } + catch( IOException ioe ) + {} + } + + endOfStream( SET, false ); + initialized( SET, true ); + return true; + } + +/** + * Returns false if the stream is busy initializing. + * @return True if steam is initialized. + */ + public boolean initialized() + { + return initialized( GET, XXX ); + } + +/** + * Reads in one stream buffer worth of audio data. See + * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more + * information about accessing and changing default settings. + * @return The audio data wrapped into a SoundBuffer context. + */ + public SoundBuffer read() + { + if( endOfStream( GET, XXX ) ) + return null; + + if( module == null ) + { + errorMessage( "Module null in method 'read'" ); + return null; + } + + // Check to make sure there is an audio format: + if( myAudioFormat == null ) + { + errorMessage( "Audio Format null in method 'read'" ); + return null; + } + + int bufferFrameSize = (int) SoundSystemConfig.getStreamingBufferSize() + / 4; + + int frames = songDuration - playPosition; + if( frames > bufferFrameSize ) + frames = bufferFrameSize; + + if( frames <= 0 ) + { + endOfStream( SET, true ); + return null; + } + byte[] outputBuffer = new byte[ frames * 4 ]; + + ibxm.get_audio( outputBuffer, frames ); + + playPosition += frames; + if( playPosition >= songDuration ) + { + endOfStream( SET, true ); + } + + // Reverse the byte order if necessary: + if( reverseBytes ) + reverseBytes( outputBuffer, 0, frames * 4 ); + + // Wrap the data into a SoundBuffer: + SoundBuffer buffer = new SoundBuffer( outputBuffer, myAudioFormat ); + + return buffer; + } + +/** + * Reads in all the audio data from the stream (up to the default + * "maximum file size". See + * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more + * information about accessing and changing default settings. + * @return the audio data wrapped into a SoundBuffer context. + */ + public SoundBuffer readAll() + { + if( module == null ) + { + errorMessage( "Module null in method 'readAll'" ); + return null; + } + + // Check to make sure there is an audio format: + if( myAudioFormat == null ) + { + errorMessage( "Audio Format null in method 'readAll'" ); + return null; + } + + int bufferFrameSize = (int) SoundSystemConfig.getFileChunkSize() + / 4; + + byte[] outputBuffer = new byte[ bufferFrameSize * 4 ]; + + // Buffer to contain the audio data: + byte[] fullBuffer = null; + // frames of audio data: + int frames; + // bytes of audio data: + int totalBytes = 0; + + while( (!endOfStream(GET, XXX)) && + (totalBytes < SoundSystemConfig.getMaxFileSize()) ) + { + frames = songDuration - playPosition; + if( frames > bufferFrameSize ) + frames = bufferFrameSize; + ibxm.get_audio( outputBuffer, frames ); + totalBytes += (frames * 4); + + fullBuffer = appendByteArrays( fullBuffer, outputBuffer, + frames * 4 ); + + playPosition += frames; + if( playPosition >= songDuration ) + { + endOfStream( SET, true ); + } + } + + // Reverse the byte order if necessary: + if( reverseBytes ) + reverseBytes( fullBuffer, 0, totalBytes ); + + // Wrap the data into a SoundBuffer: + SoundBuffer buffer = new SoundBuffer( fullBuffer, myAudioFormat ); + + return buffer; + } + +/** + * Returns false if there is still more data available to be read in. + * @return True if end of stream was reached. + */ + public boolean endOfStream() + { + return endOfStream( GET, XXX ); + } + +/** + * Closes the audio stream and remove references to all instantiated objects. + */ + public void cleanup() + { +// if( ibxm != null ) +// ibxm.seek( 0 ); + playPosition = 0; + } + +/** + * Returns the audio format of the data being returned by the read() and + * readAll() methods. + * @return Information wrapped into an AudioFormat context. + */ + public AudioFormat getAudioFormat() + { + return myAudioFormat; + } + +/** + * Decodes the data in the specified InputStream into an instance of + * ibxm.Module. + * @param input an InputStream containing the module file to be decoded. + * @throws IllegalArgumentException if the data is not recognised as a module file. + */ + private static Module loadModule( InputStream input ) + throws IllegalArgumentException, IOException + { + DataInputStream data_input_stream = new DataInputStream( input ); + + // Check if data is in XM format: + byte[] xm_header = new byte[ 60 ]; + data_input_stream.readFully( xm_header ); + if( FastTracker2.is_xm( xm_header ) ) + return FastTracker2.load_xm( xm_header, data_input_stream ); + + // Check if data is in ScreamTracker 3 format: + byte[] s3m_header = new byte[ 96 ]; + System.arraycopy( xm_header, 0, s3m_header, 0, 60 ); + data_input_stream.readFully( s3m_header, 60, 36 ); + if( ScreamTracker3.is_s3m( s3m_header ) ) + return ScreamTracker3.load_s3m( s3m_header, data_input_stream ); + + // Check if data is in ProTracker format: + byte[] mod_header = new byte[ 1084 ]; + System.arraycopy( s3m_header, 0, mod_header, 0, 96 ); + data_input_stream.readFully( mod_header, 96, 988 ); + return ProTracker.load_mod( mod_header, data_input_stream ); + } + +/** + * Sets the Module instance to be played. + */ + private void setModule( Module m ) + { + if( m != null ) + module = m; + ibxm.set_module( module ); + songDuration = ibxm.calculate_song_duration(); + } + +/** + * Internal method for synchronizing access to the boolean 'initialized'. + * @param action GET or SET. + * @param value New value if action == SET, or XXX if action == GET. + * @return True if steam is initialized. + */ + private synchronized boolean initialized( boolean action, boolean value ) + { + if( action == SET ) + initialized = value; + return initialized; + } + +/** + * Internal method for synchronizing access to the boolean 'endOfStream'. + * @param action GET or SET. + * @param value New value if action == SET, or XXX if action == GET. + * @return True if end of stream was reached. + */ + private synchronized boolean endOfStream( boolean action, boolean value ) + { + if( action == SET ) + endOfStream = value; + return endOfStream; + } + +/** + * Trims down the size of the array if it is larger than the specified + * maximum length. + * @param array Array containing audio data. + * @param maxLength Maximum size this array may be. + * @return New array. + */ + private static byte[] trimArray( byte[] array, int maxLength ) + { + byte[] trimmedArray = null; + if( array != null && array.length > maxLength ) + { + trimmedArray = new byte[maxLength]; + System.arraycopy( array, 0, trimmedArray, 0, maxLength ); + } + return trimmedArray; + } + +/** + * Reverse-orders all bytes contained in the specified array. + * @param buffer Array containing audio data. + */ + public static void reverseBytes( byte[] buffer ) + { + reverseBytes( buffer, 0, buffer.length ); + } + +/** + * Reverse-orders the specified range of bytes contained in the specified array. + * @param buffer Array containing audio data. + * @param offset Array index to begin. + * @param size number of bytes to reverse-order. + */ + public static void reverseBytes( byte[] buffer, int offset, int size ) + { + + byte b; + for( int i = offset; i < ( offset + size ); i += 2 ) + { + b = buffer[i]; + buffer[i] = buffer[i + 1]; + buffer[i + 1] = b; + } + } + +/** + * Converts sound bytes to little-endian format. + * @param audio_bytes The original wave data + * @param two_bytes_data For stereo sounds. + * @return byte array containing the converted data. + */ + private static byte[] convertAudioBytes( byte[] audio_bytes, + boolean two_bytes_data ) + { + ByteBuffer dest = ByteBuffer.allocateDirect( audio_bytes.length ); + dest.order( ByteOrder.nativeOrder() ); + ByteBuffer src = ByteBuffer.wrap( audio_bytes ); + src.order( ByteOrder.LITTLE_ENDIAN ); + if( two_bytes_data ) + { + ShortBuffer dest_short = dest.asShortBuffer(); + ShortBuffer src_short = src.asShortBuffer(); + while( src_short.hasRemaining() ) + { + dest_short.put(src_short.get()); + } + } + else + { + while( src.hasRemaining() ) + { + dest.put( src.get() ); + } + } + dest.rewind(); + + if( !dest.hasArray() ) + { + byte[] arrayBackedBuffer = new byte[dest.capacity()]; + dest.get( arrayBackedBuffer ); + dest.clear(); + + return arrayBackedBuffer; + } + + return dest.array(); + } + +/** + * Creates a new array with the second array appended to the end of the first + * array. + * @param arrayOne The first array. + * @param arrayTwo The second array. + * @param length How many bytes to append from the second array. + * @return Byte array containing information from both arrays. + */ + private static byte[] appendByteArrays( byte[] arrayOne, byte[] arrayTwo, + int length ) + { + byte[] newArray; + if( arrayOne == null && arrayTwo == null ) + { + // no data, just return + return null; + } + else if( arrayOne == null ) + { + // create the new array, same length as arrayTwo: + newArray = new byte[ length ]; + // fill the new array with the contents of arrayTwo: + System.arraycopy( arrayTwo, 0, newArray, 0, length ); + arrayTwo = null; + } + else if( arrayTwo == null ) + { + // create the new array, same length as arrayOne: + newArray = new byte[ arrayOne.length ]; + // fill the new array with the contents of arrayOne: + System.arraycopy( arrayOne, 0, newArray, 0, arrayOne.length ); + arrayOne = null; + } + else + { + // create the new array large enough to hold both arrays: + newArray = new byte[ arrayOne.length + length ]; + System.arraycopy( arrayOne, 0, newArray, 0, arrayOne.length ); + // fill the new array with the contents of both arrays: + System.arraycopy( arrayTwo, 0, newArray, arrayOne.length, + length ); + arrayOne = null; + arrayTwo = null; + } + + return newArray; + } + +/** + * Prints an error message. + * @param message Message to print. + */ + private void errorMessage( String message ) + { + logger.errorMessage( "CodecWav", message, 0 ); + } + +/** + * Prints an exception's error message followed by the stack trace. + * @param e Exception containing the information to print. + */ + private void printStackTrace( Exception e ) + { + logger.printStackTrace( e, 1 ); + } +} diff --git a/forge/install/install.py b/forge/install/install.py index 9ef800f779..bf7d17c7b9 100644 --- a/forge/install/install.py +++ b/forge/install/install.py @@ -14,7 +14,7 @@ from runtime.updatemd5 import updatemd5 from runtime.cleanup import cleanup from runtime.updatemcp import updatemcp -from forge import apply_patches, copytree, reset_logger, download_ff, cleanup_source +from forge import apply_patches, copytree, reset_logger, download_ff, cleanup_source, pre_decompile, post_decompile def main(): print '=================================== Minecraft Forge Setup Start =================================' @@ -37,11 +37,16 @@ def main(): sys.exit(1) try: + pre_decompile() + os.chdir(mcp_dir) # Conf JAD CSV -r -d -a -n -p -o -l -g decompile(None, False, False, True, True, False, True, False, False, False, False) reset_logger() os.chdir(forge_dir) + + post_decompile() + except SystemExit, e: print 'Decompile Exception: %d ' % e.code raise e diff --git a/forge/release.py b/forge/release.py index 500ae41be9..181ef21da1 100644 --- a/forge/release.py +++ b/forge/release.py @@ -64,6 +64,8 @@ def main(): zip_add('fml/LICENSE-fml.txt') zip_add('fml/README-fml.txt') zip_add('fml/src/minecraft/fmlversion.properties') + zip_add('Paulscode IBXM Library License.txt') + zip_add('Paulscode SoundSystem CodecIBXM License.txt') zip_end() zip_start('minecraftforge-server-%s.zip' % version_str) @@ -93,6 +95,8 @@ def main(): zip_add('forge.py') zip_add('MinecraftForge-Credits.txt') zip_add('MinecraftForge-License.txt') + zip_add('Paulscode IBXM Library License.txt') + zip_add('Paulscode SoundSystem CodecIBXM License.txt') zip_end() inject_version(os.path.join(forge_dir, 'forge_common', 'net', 'minecraft', 'src', 'forge', 'ForgeHooks.java'), 0) diff --git a/forge/setup.py b/forge/setup.py index a02305c0fe..0000690029 100644 --- a/forge/setup.py +++ b/forge/setup.py @@ -12,7 +12,7 @@ from runtime.decompile import decompile from runtime.updatenames import updatenames from runtime.updatemd5 import updatemd5 -from forge import apply_patches, copytree, reset_logger, download_ff, cleanup_source +from forge import apply_patches, copytree, reset_logger, download_ff, cleanup_source, pre_decompile, post_decompile def main(): @@ -25,12 +25,19 @@ def main(): if os.path.isdir(src_dir): shutil.rmtree(src_dir) + try: + + pre_decompile() + os.chdir(mcp_dir) # Conf JAD CSV -r -d -a -n -p -o -l -g decompile(None, False, False, True, True, False, True, False, False, False, False) reset_logger() os.chdir(forge_dir) + + post_decompile() + except SystemExit, e: print 'Decompile Exception: %d ' % e.code raise e