/**
 * This file is part of reSID, a MOS6581 SID emulator engine.
 * Copyright (C) 2004  Dag Lem <resid@nimrod.no>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * @author Ken Handel
 *
 */
package ucesoft.cbm.peripheral.sid.resid;

/**
 * 
 * The audio output stage in a Commodore 64 consists of two STC networks, a
 * low-pass filter with 3-dB frequency 16kHz followed by a high-pass filter with
 * 3-dB frequency 16Hz (the latter provided an audio equipment input impedance
 * of 1kOhm).
 * <P>
 * The STC networks are connected with a BJT supposedly meant to act as a unity
 * gain buffer, which is not really how it works. A more elaborate model would
 * include the BJT, however DC circuit analysis yields BJT base-emitter and
 * emitter-base impedances sufficiently low to produce additional low-pass and
 * high-pass 3dB-frequencies in the order of hundreds of kHz. This calls for a
 * sampling frequency of several MHz, which is far too high for practical use.
 * 
 * @author Ken Handel
 * 
 */
public class ExternalFilter {

	/**
	 * Filter enabled.
	 */
	protected boolean enabled;

	/**
	 * Maximum mixer DC offset.
	 */
	protected int /* sound_sample */mixer_DC;

	/**
	 * State of filters. lowpass
	 */
	protected int /* sound_sample */Vlp;

	/**
	 * State of filters. highpass
	 */
	protected int /* sound_sample */Vhp;

	/**
	 * State of filters.
	 */
	protected int /* sound_sample */Vo;

	/**
	 * Cutoff frequencies.
	 */
	protected int /* sound_sample */w0lp;

	/**
	 * Cutoff frequencies.
	 */
	protected int /* sound_sample */w0hp;

	// ----------------------------------------------------------------------------
	// Inline functions.
	// The following functions are defined inline because they are called every
	// time a sample is calculated.
	// ----------------------------------------------------------------------------

	/**
	 * SID clocking - 1 cycle.
	 * 
	 * @param Vi
	 */
	public void clock(int /* sound_sample */Vi) {
		// This is handy for testing.
		if (!enabled) {
			// Remove maximum DC level since there is no filter to do it.
			Vlp = Vhp = 0;
			Vo = Vi - mixer_DC;
			return;
		}

		// delta_t is converted to seconds given a 1MHz clock by dividing
		// with 1 000 000.

		// Calculate filter outputs.
		// Vo = Vlp - Vhp;
		// Vlp = Vlp + w0lp*(Vi - Vlp)*delta_t;
		// Vhp = Vhp + w0hp*(Vlp - Vhp)*delta_t;

		int /* sound_sample */dVlp = (w0lp >> 8) * (Vi - Vlp) >> 12;
		int /* sound_sample */dVhp = w0hp * (Vlp - Vhp) >> 20;
		Vo = Vlp - Vhp;
		Vlp += dVlp;
		Vhp += dVhp;
	}

	/**
	 * SID clocking - delta_t cycles.
	 * 
	 * @param delta_t
	 * @param Vi
	 */
	public void clock(int /* cycle_count */delta_t, int /* sound_sample */Vi) {
		// This is handy for testing.
		if (!enabled) {
			// Remove maximum DC level since there is no filter to do it.
			Vlp = Vhp = 0;
			Vo = Vi - mixer_DC;
			return;
		}

		// Maximum delta cycles for the external filter to work satisfactorily
		// is approximately 8.
		int /* cycle_count */delta_t_flt = 8;

		while (delta_t != 0) {
			if (delta_t < delta_t_flt) {
				delta_t_flt = delta_t;
			}

			// delta_t is converted to seconds given a 1MHz clock by dividing
			// with 1 000 000.

			// Calculate filter outputs.
			// Vo = Vlp - Vhp;
			// Vlp = Vlp + w0lp*(Vi - Vlp)*delta_t;
			// Vhp = Vhp + w0hp*(Vlp - Vhp)*delta_t;

			int /* sound_sample */dVlp = (w0lp * delta_t_flt >> 8)
					* (Vi - Vlp) >> 12;
			int /* sound_sample */dVhp = w0hp * delta_t_flt * (Vlp - Vhp) >> 20;
			Vo = Vlp - Vhp;
			Vlp += dVlp;
			Vhp += dVhp;

			delta_t -= delta_t_flt;
		}
	}

	/**
	 * Audio output (20 bits).
	 * 
	 * Audio output (19.5 bits).
	 * 
	 * @return Vo
	 */
	public int /* sound_sample */output() {
		return Vo;
	}

	// ----------------------------------------------------------------------------
	// END Inline functions.
	// ----------------------------------------------------------------------------

	/**
	 * Constructor.
	 */
	public ExternalFilter() {
		reset();
		enable_filter(true);
		set_sampling_parameter(15915.6);
		set_chip_model(ISIDDefs.chip_model.MOS6581);
	}

	/**
	 * Enable filter.
	 * 
	 * @param enable
	 * enable filter
	 */
	public void enable_filter(boolean enable) {
		enabled = enable;
	}

	/**
	 * Setup of the external filter sampling parameters.
	 * 
	 * @param pass_freq
	 */
	public void set_sampling_parameter(double pass_freq) {
		final double pi = 3.1415926535897932385;

		// Low-pass: R = 10kOhm, C = 1000pF; w0l = 1/RC = 1/(1e4*1e-9) = 100000
		// High-pass: R = 1kOhm, C = 10uF; w0h = 1/RC = 1/(1e3*1e-5) = 100
		// Multiply with 1.048576 to facilitate division by 1 000 000 by right-
		// shifting 20 times (2 ^ 20 = 1048576).

		w0hp = 105;
		w0lp = (int /* sound_sample */) (pass_freq * (2.0 * pi * 1.048576));
		if (w0lp > 104858)
			w0lp = 104858;
	}

	/**
	 * Set chip model.
	 * 
	 * @param model
	 * chip model
	 */
	public void set_chip_model(ISIDDefs.chip_model model) {
		if (model == ISIDDefs.chip_model.MOS6581) {
			// Maximum mixer DC output level; to be removed if the external
			// filter is turned off: ((wave DC + voice DC) * voices + mixer DC)
			// * volume
			// See Voice.java and Filter.java for an explanation of the values.
			mixer_DC = ((((0x800 - 0x380) + 0x800) * 0xff * 3 - 0xfff * 0xff / 18) >> 7) * 0x0f;
		} else {
			// No DC offsets in the MOS8580.
			mixer_DC = 0;
		}
	}

	/**
	 * SID reset.
	 */
	public void reset() {
		// State of filter.
		Vlp = 0;
		Vhp = 0;
		Vo = 0;
	}
}
