
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; Forge, Unused
		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;
		}
	}
}
