/*****************************************************************************

    Texas Instruments SN76477 emulator

    authors: Derrick Renaud - info
             Zsolt Vasvari  - software

    (see sn76477.h for details)

    Notes:
        * All formulas were derived by taking measurements of a real device,
          then running the data sets through the numerical analysis
          application at http://zunzun.com to come up with the functions.

    Known issues/to-do's:
        * VCO
            * confirm value of VCO_MAX_EXT_VOLTAGE, VCO_TO_SLF_VOLTAGE_DIFF
              VCO_CAP_VOLTAGE_MIN and VCO_CAP_VOLTAGE_MAX
            * confirm value of VCO_MIN_DUTY_CYCLE
            * get real formulas for VCO cap charging and discharging
            * get real formula for VCO duty cycle
            * what happens if no vco_res
            * what happens if no vco_cap

        * Attack/Decay
            * get real formulas for a/d cap charging and discharging

 *****************************************************************************/

#include "emu.h"
#include "wavwrite.h"
#include "sn76477.h"



/*****************************************************************************
 *
 *  Debugging
 *
 *****************************************************************************/

#define VERBOSE					1

/* if 1, create a .wav file of the sound produced */
#define LOG_WAV					0

/* if 1 and LOG_WAV is 1, only logs to .wav file in chip is enabled */
#define LOG_WAV_ENABLED_ONLY	0

/* determines what value will be logged in the left channel of the .wav file */
#define LOG_WAV_VALUE_L			0	/* 0 = OUT voltage */
									/* 1 = enable line */
									/* 2 = one-shot cap voltage */
									/* 3 = a/d cap voltage */
									/* 4 = slf cap voltage */
									/* 5 = vco cap voltage */
									/* 6 = noise filter cap voltage */

/* determines what value will be logged in the right channel of the .wav file
   same values as for the left channel above */
#define LOG_WAV_VALUE_R			3

#define LOG_WAV_GAIN_FACTOR		1000

#define LOG_WAV_FILE_NAME		"sn76477_%s.wav"


#define LOG(n,x) do { if (VERBOSE >= (n)) logerror x; } while (0)

#define CHECK_CHIP_NUM					assert(sn != NULL)
#define CHECK_CHIP_NUM_AND_BOOLEAN		CHECK_CHIP_NUM; assert((data & 0x01) == data)
#define CHECK_CHIP_NUM_AND_POSITIVE		CHECK_CHIP_NUM; assert(data >= 0.0)
#define CHECK_CHIP_NUM_AND_VOLTAGE		CHECK_CHIP_NUM; assert((data >= 0.0) && (data <= 5.0))
#define CHECK_CHIP_NUM_AND_CAP_VOLTAGE	CHECK_CHIP_NUM; assert(((data >= 0.0) && (data <= 5.0)) || (data == SN76477_EXTERNAL_VOLTAGE_DISCONNECT))



/*****************************************************************************
 *
 *  Test Mode
 *
 *  in test mode, the interface structure
 *  passed in by the driver is not used.
 *  Instead, the values for all the inputs
 *  can be specified by modifing the structure
 *  below.  Calls by the driver to the input
 *  setter functions are ignored. Use the
 *  space bar to enable/disable the chip.
 *
 *****************************************************************************/

#define TEST_MODE	0


#if TEST_MODE

static const sn76477_interface empty_interface =
{
	0,			/*  4 noise_clock_res  */
	0,			/*  5 filter_res       */
	0,			/*  6 filter_cap       */
	0,			/*  7 decay_res        */
	0,			/*  8 attack_decay_cap */
	0,			/* 10 attack_res       */
	0,			/* 11 amplitude_res    */
	0,			/* 12 feedback_res     */
	0,			/* 16 vco_voltage      */
	0,			/* 17 vco_cap          */
	0,			/* 18 vco_res          */
	0,			/* 19 pitch_voltage    */
	0,			/* 20 slf_res          */
	0,			/* 21 slf_cap          */
	0,			/* 23 oneshot_cap      */
	0,			/* 24 oneshot_res      */
	0,			/* 22 vco              */
	0,			/* 26 mixer A          */
	0,			/* 25 mixer B          */
	0,			/* 27 mixer C          */
	0,			/* 1  envelope 1       */
	0,			/* 28 envelope 2       */
	0			/* 9  enable           */
};

#define test_interface empty_empty_interface

#endif



/*****************************************************************************
 *
 *  Constants
 *
 *****************************************************************************/

#define ONE_SHOT_CAP_VOLTAGE_MIN	(0)			/* the voltage at which the one-shot starts from (measured) */
#define ONE_SHOT_CAP_VOLTAGE_MAX	(2.5)		/* the voltage at which the one-shot finishes (measured) */
#define ONE_SHOT_CAP_VOLTAGE_RANGE	(ONE_SHOT_CAP_VOLTAGE_MAX - ONE_SHOT_CAP_VOLTAGE_MIN)

#define SLF_CAP_VOLTAGE_MIN			(0.33)		/* the voltage at the bottom peak of the SLF triangle wave (measured) */
#define SLF_CAP_VOLTAGE_MAX			(2.37)		/* the voltage at the top peak of the SLF triangle wave (measured) */
#define SLF_CAP_VOLTAGE_RANGE		(SLF_CAP_VOLTAGE_MAX - SLF_CAP_VOLTAGE_MIN)

#define VCO_MAX_EXT_VOLTAGE			(2.35)		/* the external voltage at which the VCO saturates and produces no output,
                                                   also used as the voltage threshold for the SLF */
#define VCO_TO_SLF_VOLTAGE_DIFF		(0.35)
#define VCO_CAP_VOLTAGE_MIN			(SLF_CAP_VOLTAGE_MIN)	/* the voltage at the bottom peak of the VCO triangle wave */
#define VCO_CAP_VOLTAGE_MAX			(SLF_CAP_VOLTAGE_MAX + VCO_TO_SLF_VOLTAGE_DIFF)	/* the voltage at the bottom peak of the VCO triangle wave */
#define VCO_CAP_VOLTAGE_RANGE		(VCO_CAP_VOLTAGE_MAX - VCO_CAP_VOLTAGE_MIN)
#define VCO_DUTY_CYCLE_50			(5.0)		/* the high voltage that produces a 50% duty cycle */
#define VCO_MIN_DUTY_CYCLE			(18)		/* the smallest possible duty cycle, in % */

#define NOISE_MIN_CLOCK_RES			RES_K(10)	/* the maximum resistor value that still produces a noise (measured) */
#define NOISE_MAX_CLOCK_RES			RES_M(3.3)	/* the minimum resistor value that still produces a noise (measured) */
#define NOISE_CAP_VOLTAGE_MIN		(0)			/* the minimum voltage that the noise filter cap can hold (measured) */
#define NOISE_CAP_VOLTAGE_MAX		(5.0)		/* the maximum voltage that the noise filter cap can hold (measured) */
#define NOISE_CAP_VOLTAGE_RANGE		(NOISE_CAP_VOLTAGE_MAX - NOISE_CAP_VOLTAGE_MIN)
#define NOISE_CAP_HIGH_THRESHOLD	(3.35)		/* the voltage at which the filtered noise bit goes to 0 (measured) */
#define NOISE_CAP_LOW_THRESHOLD		(0.74)		/* the voltage at which the filtered noise bit goes to 1 (measured) */

#define AD_CAP_VOLTAGE_MIN			(0)			/* the minimum voltage the attack/decay cap can hold (measured) */
#define AD_CAP_VOLTAGE_MAX			(4.44)		/* the minimum voltage the attack/decay cap can hold (measured) */
#define AD_CAP_VOLTAGE_RANGE		(AD_CAP_VOLTAGE_MAX - AD_CAP_VOLTAGE_MIN)

#define OUT_CENTER_LEVEL_VOLTAGE	(2.57)		/* the voltage that gets outputted when the volumne is 0 (measured) */
#define OUT_HIGH_CLIP_THRESHOLD		(3.51)		/* the maximum voltage that can be put out (measured) */
#define OUT_LOW_CLIP_THRESHOLD		(0.715)		/* the minimum voltage that can be put out (measured) */

/* gain factors for OUT voltage in 0.1V increments (measured) */
static const double out_pos_gain[] =
{
	0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.01,	 /* 0.0 - 0.9V */
	0.03, 0.11, 0.15, 0.19, 0.21, 0.23, 0.26, 0.29, 0.31, 0.33,  /* 1.0 - 1.9V */
	0.36, 0.38, 0.41, 0.43, 0.46, 0.49, 0.52, 0.54, 0.57, 0.60,  /* 2.0 - 2.9V */
	0.62, 0.65, 0.68, 0.70, 0.73, 0.76, 0.80, 0.82, 0.84, 0.87,  /* 3.0 - 3.9V */
	0.90, 0.93, 0.96, 0.98, 1.00								 /* 4.0 - 4.4V */
};

static const double out_neg_gain[] =
{
	 0.00,  0.00,  0.00,  0.00,  0.00,  0.00,  0.00,  0.00,  0.00, -0.01,  /* 0.0 - 0.9V */
	-0.02, -0.09, -0.13, -0.15, -0.17, -0.19, -0.22, -0.24, -0.26, -0.28,  /* 1.0 - 1.9V */
	-0.30, -0.32, -0.34, -0.37, -0.39, -0.41, -0.44, -0.46, -0.48, -0.51,  /* 2.0 - 2.9V */
	-0.53, -0.56, -0.58, -0.60, -0.62, -0.65, -0.67, -0.69, -0.72, -0.74,  /* 3.0 - 3.9V */
	-0.76, -0.78, -0.81, -0.84, -0.85									   /* 4.0 - 4.4V */
};



/*****************************************************************************
 *
 *  State structure
 *
 *****************************************************************************/

typedef struct _sn76477_state sn76477_state;
struct _sn76477_state
{
	/* chip's external interface */
	UINT32 enable;
	UINT32 envelope_mode;
	UINT32 vco_mode;
	UINT32 mixer_mode;

	double one_shot_res;
	double one_shot_cap;
	UINT32 one_shot_cap_voltage_ext;

	double slf_res;
	double slf_cap;
	UINT32 slf_cap_voltage_ext;

	double vco_voltage;
	double vco_res;
	double vco_cap;
	UINT32 vco_cap_voltage_ext;

	double noise_clock_res;
	UINT32 noise_clock_ext;
	UINT32 noise_clock;
	double noise_filter_res;
	double noise_filter_cap;
	UINT32 noise_filter_cap_voltage_ext;

	double attack_res;
	double decay_res;
	double attack_decay_cap;
	UINT32 attack_decay_cap_voltage_ext;

	double amplitude_res;
	double feedback_res;
	double pitch_voltage;

	/* chip's internal state */
	double one_shot_cap_voltage;		/* voltage on the one-shot cap */
	UINT32 one_shot_running_ff;			/* 1 = one-shot running, 0 = stopped */

	double slf_cap_voltage;				/* voltage on the SLF cap */
	UINT32 slf_out_ff;					/* output of the SLF */

	double vco_cap_voltage;				/* voltage on the VCO cap */
	UINT32 vco_out_ff;					/* output of the VCO */
	UINT32 vco_alt_pos_edge_ff;			/* keeps track of the # of positive edges for VCO Alt envelope */

	double noise_filter_cap_voltage;	/* voltage on the noise filter cap */
	UINT32 real_noise_bit_ff;			/* the current noise bit before filtering */
	UINT32 filtered_noise_bit_ff;		/* the noise bit after filtering */
	UINT32 noise_gen_count;				/* noise freq emulation */

	double attack_decay_cap_voltage;	/* voltage on the attack/decay cap */

	UINT32 rng;							/* current value of the random number generator */

	/* others */
	sound_stream *channel;				/* returned by stream_create() */
	int sample_rate;					/* from machine.sample_rate() */
	device_t *device;

	wav_file *file;						/* handle of the wave file to produce */
};


INLINE sn76477_state *get_safe_token(device_t *device)
{
	assert(device != NULL);
	assert(device->type() == SN76477);
	return (sn76477_state *)downcast<legacy_device_base *>(device)->token();
}


/*****************************************************************************
 *
 *  Max/min
 *
 *****************************************************************************/

#undef max
#undef min

INLINE double max(double a, double b)
{
	return (a > b) ? a : b;
}


INLINE double min(double a, double b)
{
	return (a < b) ? a : b;
}



/*****************************************************************************
 *
 *  Functions for computing frequencies, voltages and similar values based
 *  on the hardware itself.  Do NOT put anything emulation specific here,
 *  such as calculations based on sample_rate.
 *
 *****************************************************************************/

static double compute_one_shot_cap_charging_rate(sn76477_state *sn) /* in V/sec */
{
	/* this formula was derived using the data points below

     Res (kohms)  Cap (uF)   Time (millisec)
         47         0.33         11.84
         47         1.0          36.2
         47         1.5          52.1
         47         2.0          76.4
        100         0.33         24.4
        100         1.0          75.2
        100         1.5         108.5
        100         2.0         158.4
    */

	double ret = 0;

	if ((sn->one_shot_res > 0) && (sn->one_shot_cap > 0))
	{
		ret = ONE_SHOT_CAP_VOLTAGE_RANGE / (0.8024 * sn->one_shot_res * sn->one_shot_cap + 0.002079);
	}
	else if (sn->one_shot_cap > 0)
	{
		/* if no resistor, there is no current to charge the cap,
           effectively making the one-shot time effectively infinite */
		ret = +1e-30;
	}
	else if (sn->one_shot_res > 0)
	{
		/* if no cap, the voltage changes extremely fast,
           effectively making the one-shot time 0 */
		ret = +1e+30;
	}

	return ret;
}


static double compute_one_shot_cap_discharging_rate(sn76477_state *sn) /* in V/sec */
{
	/* this formula was derived using the data points below

    Cap (uF)   Time (microsec)
      0.33           300
      1.0            850
      1.5           1300
      2.0           1900
    */

	double ret = 0;

	if ((sn->one_shot_res > 0) && (sn->one_shot_cap > 0))
	{
		ret = ONE_SHOT_CAP_VOLTAGE_RANGE / (854.7 * sn->one_shot_cap + 0.00001795);
	}
	else if (sn->one_shot_res > 0)
	{
		/* if no cap, the voltage changes extremely fast,
           effectively making the one-shot time 0 */
		ret = +1e+30;
	}

	return ret;
}


static double compute_slf_cap_charging_rate(sn76477_state *sn) /* in V/sec */
{
	/* this formula was derived using the data points below

    Res (kohms)  Cap (uF)   Time (millisec)
         47        0.47          14.3
        120        0.47          35.6
        200        0.47          59.2
         47        1.00          28.6
        120        1.00          71.6
        200        1.00         119.0
    */
	double ret = 0;

	if ((sn->slf_res > 0) && (sn->slf_cap > 0))
	{
		ret = SLF_CAP_VOLTAGE_RANGE / (0.5885 * sn->slf_res * sn->slf_cap + 0.001300);
	}

	return ret;
}


static double compute_slf_cap_discharging_rate(sn76477_state *sn) /* in V/sec */
{
	/* this formula was derived using the data points below

    Res (kohms)  Cap (uF)   Time (millisec)
         47        0.47          13.32
        120        0.47          32.92
        200        0.47          54.4
         47        1.00          26.68
        120        1.00          66.2
        200        1.00         109.6
    */
	double ret = 0;

	if ((sn->slf_res > 0) && (sn->slf_cap > 0))
	{
		ret = SLF_CAP_VOLTAGE_RANGE / (0.5413 * sn->slf_res * sn->slf_cap + 0.001343);
	}

	return ret;
}


static double compute_vco_cap_charging_discharging_rate(sn76477_state *sn) /* in V/sec */
{
	double ret = 0;

	if ((sn->vco_res > 0) && (sn->vco_cap > 0))
	{
		ret = 0.64 * 2 * VCO_CAP_VOLTAGE_RANGE / (sn->vco_res * sn->vco_cap);
	}

	return ret;
}


static double compute_vco_duty_cycle(sn76477_state *sn) /* no measure, just a number */
{
	double ret = 0.5;	/* 50% */

	if ((sn->vco_voltage > 0) && (sn->pitch_voltage != VCO_DUTY_CYCLE_50))
	{
		ret = max(0.5 * (sn->pitch_voltage / sn->vco_voltage), (VCO_MIN_DUTY_CYCLE / 100.0));

		ret = min(ret, 1);
	}

	return ret;
}


static UINT32 compute_noise_gen_freq(sn76477_state *sn) /* in Hz */
{
	/* this formula was derived using the data points below

     Res (ohms)   Freq (Hz)
        10k         97493
        12k         83333
        15k         68493
        22k         49164
        27k         41166
        33k         34449
        36k         31969
        47k         25126
        56k         21322
        68k         17721.5
        82k         15089.2
        100k        12712.0
        150k         8746.4
        220k         6122.4
        270k         5101.5
        330k         4217.2
        390k         3614.5
        470k         3081.7
        680k         2132.7
        820k         1801.8
          1M         1459.9
        2.2M          705.13
        3.3M          487.59
    */

	UINT32 ret = 0;

	if ((sn->noise_clock_res >= NOISE_MIN_CLOCK_RES) &&
	    (sn->noise_clock_res <= NOISE_MAX_CLOCK_RES))
	{
		ret = 339100000 * pow(sn->noise_clock_res, -0.8849);
	}

	return ret;
}


static double compute_noise_filter_cap_charging_rate(sn76477_state *sn) /* in V/sec */
{
	/* this formula was derived using the data points below

     R*C        Time (sec)
    .000068     .0000184
    .0001496    .0000378
    .0002244    .0000548
    .0003196    .000077
    .0015       .000248
    .0033       .000540
    .00495      .000792
    .00705      .001096
    */

	double ret = 0;

	if ((sn->noise_filter_res > 0) && (sn->noise_filter_cap > 0))
	{
		ret = NOISE_CAP_VOLTAGE_RANGE / (0.1571 * sn->noise_filter_res * sn->noise_filter_cap + 0.00001430);
	}
	else if (sn->noise_filter_cap > 0)
	{
		/* if no resistor, there is no current to charge the cap,
           effectively making the filter's output constants */
		ret = +1e-30;
	}
	else if (sn->noise_filter_res > 0)
	{
		/* if no cap, the voltage changes extremely fast,
           effectively disabling the filter */
		ret = +1e+30;
	}

	return ret;
}


static double compute_noise_filter_cap_discharging_rate(sn76477_state *sn) /* in V/sec */
{
	/* this formula was derived using the data points below

     R*C        Time (sec)
    .000068     .000016
    .0001496    .0000322
    .0002244    .0000472
    .0003196    .0000654
    .0015       .000219
    .0033       .000468
    .00495      .000676
    .00705      .000948
    */

	double ret = 0;

	if ((sn->noise_filter_res > 0) && (sn->noise_filter_cap > 0))
	{
		ret = NOISE_CAP_VOLTAGE_RANGE / (0.1331 * sn->noise_filter_res * sn->noise_filter_cap + 0.00001734);
	}
	else if (sn->noise_filter_cap > 0)
	{
		/* if no resistor, there is no current to charge the cap,
           effectively making the filter's output constants */
		ret = +1e-30;
	}
	else if (sn->noise_filter_res > 0)
	{
		/* if no cap, the voltage changes extremely fast,
           effectively disabling the filter */
		ret = +1e+30;
	}

	return ret;
}


static double compute_attack_decay_cap_charging_rate(sn76477_state *sn)  /* in V/sec */
{
	double ret = 0;

	if ((sn->attack_res > 0) && (sn->attack_decay_cap > 0))
	{
		ret = AD_CAP_VOLTAGE_RANGE / (sn->attack_res * sn->attack_decay_cap);
	}
	else if (sn->attack_decay_cap > 0)
	{
		/* if no resistor, there is no current to charge the cap,
           effectively making the attack time infinite */
		ret = +1e-30;
	}
	else if (sn->attack_res > 0)
	{
		/* if no cap, the voltage changes extremely fast,
           effectively making the attack time 0 */
		ret = +1e+30;
	}

	return ret;
}


static double compute_attack_decay_cap_discharging_rate(sn76477_state *sn)  /* in V/sec */
{
	double ret = 0;

	if ((sn->decay_res > 0) && (sn->attack_decay_cap > 0))
	{
		ret = AD_CAP_VOLTAGE_RANGE / (sn->decay_res * sn->attack_decay_cap);
	}
	else if (sn->attack_decay_cap > 0)
	{
		/* if no resistor, there is no current to charge the cap,
           effectively making the decay time infinite */
		ret = +1e-30;
	}
	else if (sn->attack_res > 0)
	{
		/* if no cap, the voltage changes extremely fast,
           effectively making the decay time 0 */
		ret = +1e+30;
	}

	return ret;
}


static double compute_center_to_peak_voltage_out(sn76477_state *sn)
{
	/* this formula was derived using the data points below

     Ra (kohms)  Rf (kohms)   Voltage
        150         47          1.28
        200         47          0.96
         47         22          1.8
        100         22          0.87
        150         22          0.6
        200         22          0.45
         47         10          0.81
        100         10          0.4
        150         10          0.27
    */

	double ret = 0;

	if (sn->amplitude_res > 0)
	{
		ret = 3.818 * (sn->feedback_res / sn->amplitude_res) + 0.03;
	}

	return ret;
}



/*****************************************************************************
 *
 *  Logging functions
 *
 *****************************************************************************/

static void log_enable_line(sn76477_state *sn)
{
	static const char *const desc[] =
	{
		"Enabled", "Inhibited"
	};

	LOG(1, ("SN76477 '%s':              Enable line (9): %d [%s]\n", sn->device->tag(), sn->enable, desc[sn->enable]));
}


static void log_mixer_mode(sn76477_state *sn)
{
	static const char *const desc[] =
	{
		"VCO", "SLF", "Noise", "VCO/Noise",
		"SLF/Noise", "SLF/VCO/Noise", "SLF/VCO", "Inhibit"
	};

	LOG(1, ("SN76477 '%s':           Mixer mode (25-27): %d [%s]\n", sn->device->tag(), sn->mixer_mode, desc[sn->mixer_mode]));
}


static void log_envelope_mode(sn76477_state *sn)
{
	static const char *const desc[] =
	{
		"VCO", "One-Shot", "Mixer Only", "VCO with Alternating Polarity"
	};

	LOG(1, ("SN76477 '%s':         Envelope mode (1,28): %d [%s]\n", sn->device->tag(), sn->envelope_mode, desc[sn->envelope_mode]));
}


static void log_vco_mode(sn76477_state *sn)
{
	static const char *const desc[] =
	{
		"External (Pin 16)", "Internal (SLF)"
	};

	LOG(1, ("SN76477 '%s':                VCO mode (22): %d [%s]\n", sn->device->tag(), sn->vco_mode, desc[sn->vco_mode]));
}


static void log_one_shot_time(sn76477_state *sn)
{
	if (!sn->one_shot_cap_voltage_ext)
	{
		if (compute_one_shot_cap_charging_rate(sn) > 0)
		{
			LOG(1, ("SN76477 '%s':        One-shot time (23,24): %.4f sec\n", sn->device->tag(), ONE_SHOT_CAP_VOLTAGE_RANGE * (1 / compute_one_shot_cap_charging_rate(sn))));
		}
		else
		{
			LOG(1, ("SN76477 '%s':        One-shot time (23,24): N/A\n", sn->device->tag()));
		}
	}
	else
	{
		LOG(1, ("SN76477 '%s':        One-shot time (23,24): External (cap = %.2fV)\n", sn->device->tag(), sn->one_shot_cap_voltage));
	}
}


static void log_slf_freq(sn76477_state *sn)
{
	if (!sn->slf_cap_voltage_ext)
	{
		if (compute_slf_cap_charging_rate(sn) > 0)
		{
			double charging_time = (1 / compute_slf_cap_charging_rate(sn)) * SLF_CAP_VOLTAGE_RANGE;
			double discharging_time = (1 / compute_slf_cap_discharging_rate(sn)) * SLF_CAP_VOLTAGE_RANGE;

			LOG(1, ("SN76477 '%s':        SLF frequency (20,21): %.2f Hz\n", sn->device->tag(), 1 / (charging_time + discharging_time)));
		}
		else
		{
			LOG(1, ("SN76477 '%s':        SLF frequency (20,21): N/A\n", sn->device->tag()));
		}
	}
	else
	{
		LOG(1, ("SN76477 '%s':        SLF frequency (20,21): External (cap = %.2fV)\n", sn->device->tag(), sn->slf_cap_voltage));
	}
}


static void log_vco_pitch_voltage(sn76477_state *sn)
{
	LOG(1, ("SN76477 '%s':       VCO pitch voltage (19): %.2fV\n", sn->device->tag(), sn->pitch_voltage));
}


static void log_vco_duty_cycle(sn76477_state *sn)
{
	LOG(1, ("SN76477 '%s':       VCO duty cycle (16,19): %.0f%%\n", sn->device->tag(), compute_vco_duty_cycle(sn) * 100.0));
}


static void log_vco_freq(sn76477_state *sn)
{
	if (!sn->vco_cap_voltage_ext)
	{
		if (compute_vco_cap_charging_discharging_rate(sn) > 0)
		{
			double min_freq = compute_vco_cap_charging_discharging_rate(sn) / (2 * VCO_CAP_VOLTAGE_RANGE);
			double max_freq = compute_vco_cap_charging_discharging_rate(sn) / (2 * VCO_TO_SLF_VOLTAGE_DIFF);

			LOG(1, ("SN76477 '%s':        VCO frequency (17,18): %.2f Hz - %.1f Hz\n", sn->device->tag(), min_freq, max_freq));
		}
		else
		{
			LOG(1, ("SN76477 '%s':        VCO frequency (17,18): N/A\n", sn->device->tag()));
		}
	}
	else
	{
		LOG(1, ("SN76477 '%s':        VCO frequency (17,18): External (cap = %.2fV)\n", sn->device->tag(), sn->vco_cap_voltage));
	}
}


static void log_vco_ext_voltage(sn76477_state *sn)
{
	if (sn->vco_voltage <= VCO_MAX_EXT_VOLTAGE)
	{
		double min_freq = compute_vco_cap_charging_discharging_rate(sn) / (2 * VCO_CAP_VOLTAGE_RANGE);
		double max_freq = compute_vco_cap_charging_discharging_rate(sn) / (2 * VCO_TO_SLF_VOLTAGE_DIFF);

		LOG(1, ("SN76477 '%s':        VCO ext. voltage (16): %.2fV (%.2f Hz)\n", sn->device->tag(),
				sn->vco_voltage,
				min_freq + ((max_freq - min_freq) * sn->vco_voltage / VCO_MAX_EXT_VOLTAGE)));
	}
	else
	{
		LOG(1, ("SN76477 '%s':        VCO ext. voltage (16): %.2fV (saturated, no output)\n", sn->device->tag(), sn->vco_voltage));
	}
}


static void log_noise_gen_freq(sn76477_state *sn)
{
	if (sn->noise_clock_ext)
	{
		LOG(1, ("SN76477 '%s':      Noise gen frequency (4): External\n", sn->device->tag()));
	}
	else
	{
		if (compute_noise_gen_freq(sn) > 0)
		{
			LOG(1, ("SN76477 '%s':      Noise gen frequency (4): %d Hz\n", sn->device->tag(), compute_noise_gen_freq(sn)));
		}
		else
		{
			LOG(1, ("SN76477 '%s':      Noise gen frequency (4): N/A\n", sn->device->tag()));
		}
	}
}


static void log_noise_filter_freq(sn76477_state *sn)
{
	if (!sn->noise_filter_cap_voltage_ext)
	{
		double charging_rate = compute_noise_filter_cap_charging_rate(sn);

		if (charging_rate > 0)
		{
			if (charging_rate < 1000000.0)
			{
				double charging_time = (1 / charging_rate) * NOISE_CAP_VOLTAGE_RANGE;
				double discharging_time = (1 / charging_rate) * NOISE_CAP_VOLTAGE_RANGE;

				LOG(1, ("SN76477 '%s': Noise filter frequency (5,6): %.0f Hz\n", sn->device->tag(), 1 / (charging_time + discharging_time)));
			}
			else
			{
				LOG(1, ("SN76477 '%s': Noise filter frequency (5,6): Very Large (Filtering Disabled)\n", sn->device->tag()));
			}
		}
		else
		{
			LOG(1, ("SN76477 '%s': Noise filter frequency (5,6): N/A\n", sn->device->tag()));
		}
	}
	else
	{
		LOG(1, ("SN76477 '%s': Noise filter frequency (5,6): External (cap = %.2fV)\n", sn->device->tag(), sn->noise_filter_cap));
	}
}


static void log_attack_time(sn76477_state *sn)
{
	if (!sn->attack_decay_cap_voltage_ext)
	{
		if (compute_attack_decay_cap_charging_rate(sn) > 0)
		{
			LOG(1, ("SN76477 '%s':           Attack time (8,10): %.4f sec\n", sn->device->tag(), AD_CAP_VOLTAGE_RANGE * (1 / compute_attack_decay_cap_charging_rate(sn))));
		}
		else
		{
			LOG(1, ("SN76477 '%s':           Attack time (8,10): N/A\n", sn->device->tag()));
		}
	}
	else
	{
		LOG(1, ("SN76477 '%s':           Attack time (8,10): External (cap = %.2fV)\n", sn->device->tag(), sn->attack_decay_cap_voltage));
	}
}


static void log_decay_time(sn76477_state *sn)
{
	if (!sn->attack_decay_cap_voltage_ext)
	{
		if (compute_attack_decay_cap_discharging_rate(sn) > 0)
		{
			LOG(1, ("SN76477 '%s':             Decay time (7,8): %.4f sec\n", sn->device->tag(), AD_CAP_VOLTAGE_RANGE * (1 / compute_attack_decay_cap_discharging_rate(sn))));
		}
		else
		{
			LOG(1, ("SN76477 '%s':            Decay time (8,10): N/A\n", sn->device->tag()));
		}
	}
	else
	{
		LOG(1, ("SN76477 '%s':             Decay time (7, 8): External (cap = %.2fV)\n", sn->device->tag(), sn->attack_decay_cap_voltage));
	}
}


static void log_voltage_out(sn76477_state *sn)
{
	LOG(1, ("SN76477 '%s':    Voltage OUT range (11,12): %.2fV - %.2fV (clips above %.2fV)\n",
			sn->device->tag(),
			OUT_CENTER_LEVEL_VOLTAGE + compute_center_to_peak_voltage_out(sn) * out_neg_gain[(int)(AD_CAP_VOLTAGE_MAX * 10)],
			OUT_CENTER_LEVEL_VOLTAGE + compute_center_to_peak_voltage_out(sn) * out_pos_gain[(int)(AD_CAP_VOLTAGE_MAX * 10)],
			OUT_HIGH_CLIP_THRESHOLD));
}


static void log_complete_state(sn76477_state *sn)
{
	log_enable_line(sn);
	log_mixer_mode(sn);
	log_envelope_mode(sn);
	log_vco_mode(sn);
	log_one_shot_time(sn);
	log_slf_freq(sn);
	log_vco_freq(sn);
	log_vco_ext_voltage(sn);
	log_vco_pitch_voltage(sn);
	log_vco_duty_cycle(sn);
	log_noise_filter_freq(sn);
	log_noise_gen_freq(sn);
	log_attack_time(sn);
	log_decay_time(sn);
	log_voltage_out(sn);
}



/*****************************************************************************
 *
 *  .WAV file functions
 *
 *****************************************************************************/


static void open_wav_file(sn76477_state *sn)
{
	char wav_file_name[30];

	sprintf(wav_file_name, LOG_WAV_FILE_NAME, sn->device->tag());
	sn->file = wav_open(wav_file_name, sn->sample_rate, 2);

	LOG(1, ("SN76477 '%s':         Logging output: %s\n", sn->device->tag(), wav_file_name));
}


static void close_wav_file(sn76477_state *sn)
{
	wav_close(sn->file);
}


static void add_wav_data(sn76477_state *sn, INT16 data_l, INT16 data_r)
{
	wav_add_data_16lr(sn->file, &data_l, &data_r, 1);
}



/*****************************************************************************
 *
 *  Noise generator
 *
 *****************************************************************************/

static void intialize_noise(sn76477_state *sn)
{
	sn->rng = 0;
}


INLINE UINT32 generate_next_real_noise_bit(sn76477_state *sn)
{
	UINT32 out = ((sn->rng >> 28) & 1) ^ ((sn->rng >> 0) & 1);

	 /* if bits 0-4 and 28 are all zero then force the output to 1 */
	if ((sn->rng & 0x1000001f) == 0)
	{
		out = 1;
	}

	sn->rng = (sn->rng >> 1) | (out << 30);

	return out;
}



/*****************************************************************************
 *
 *  Set enable input
 *
 *****************************************************************************/

static void _SN76477_enable_w(sn76477_state *sn, UINT32 data)
{
	sn->enable = data;

	 /* if falling edge */
	if (!sn->enable)
	{
		/* start the attack phase */
		sn->attack_decay_cap_voltage = AD_CAP_VOLTAGE_MIN;

		/* one-shot runs regardless of envelope mode */
		sn->one_shot_running_ff = 1;
	}
}


static void SN76477_test_enable_w(sn76477_state *sn, UINT32 data)
{
	if (data != sn->enable)
	{
		sn->channel->update();

		_SN76477_enable_w(sn, data);

		log_enable_line(sn);
	}
}


void sn76477_enable_w(device_t *device, UINT32 data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_BOOLEAN;

	SN76477_test_enable_w(sn, data);
#endif
}



/*****************************************************************************
 *
 *  Set mixer select inputs
 *
 *****************************************************************************/

static void _SN76477_mixer_a_w(sn76477_state *sn, UINT32 data)
{
	sn->mixer_mode = (sn->mixer_mode & ~0x01) | (data << 0);
}


void sn76477_mixer_a_w(device_t *device, UINT32 data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_BOOLEAN;

	if (data != ((sn->mixer_mode >> 0) & 0x01))
	{
		sn->channel->update();

		_SN76477_mixer_a_w(sn, data);

		log_mixer_mode(sn);
	}
#endif
}


static void _SN76477_mixer_b_w(sn76477_state *sn, UINT32 data)
{
	sn->mixer_mode = (sn->mixer_mode & ~0x02) | (data << 1);
}


void sn76477_mixer_b_w(device_t *device, UINT32 data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_BOOLEAN;

	if (data != ((sn->mixer_mode >> 1) & 0x01))
	{
		sn->channel->update();

		_SN76477_mixer_b_w(sn, data);

		log_mixer_mode(sn);
	}
#endif
}


static void _SN76477_mixer_c_w(sn76477_state *sn, UINT32 data)
{
	sn->mixer_mode = (sn->mixer_mode & ~0x04) | (data << 2);
}


void sn76477_mixer_c_w(device_t *device, UINT32 data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_BOOLEAN;

	if (data != ((sn->mixer_mode >> 2) & 0x01))
	{
		sn->channel->update();

		_SN76477_mixer_c_w(sn, data);

		log_mixer_mode(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set envelope select inputs
 *
 *****************************************************************************/

static void _SN76477_envelope_1_w(sn76477_state *sn, UINT32 data)
{
	sn->envelope_mode = (sn->envelope_mode & ~0x01) | (data << 0);
}


void sn76477_envelope_1_w(device_t *device, UINT32 data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_BOOLEAN;

	if (data != ((sn->envelope_mode >> 0) & 0x01))
	{
		sn->channel->update();

		_SN76477_envelope_1_w(sn, data);

		log_envelope_mode(sn);
	}
#endif
}


static void _SN76477_envelope_2_w(sn76477_state *sn, UINT32 data)
{
	sn->envelope_mode = (sn->envelope_mode & ~0x02) | (data << 1);
}


void sn76477_envelope_2_w(device_t *device, UINT32 data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_BOOLEAN;

	if (data != ((sn->envelope_mode >> 1) & 0x01))
	{
		sn->channel->update();

		_SN76477_envelope_2_w(sn, data);

		log_envelope_mode(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set VCO select input
 *
 *****************************************************************************/

static void _SN76477_vco_w(sn76477_state *sn, UINT32 data)
{
	sn->vco_mode = data;
}


void sn76477_vco_w(device_t *device, UINT32 data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_BOOLEAN;

	if (data != sn->vco_mode)
	{
		sn->channel->update();

		_SN76477_vco_w(sn, data);

		log_vco_mode(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set one-shot resistor
 *
 *****************************************************************************/

static void _SN76477_one_shot_res_w(sn76477_state *sn, double data)
{
	sn->one_shot_res = data;
}


void sn76477_one_shot_res_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->one_shot_res)
	{
		sn->channel->update();

		_SN76477_one_shot_res_w(sn, data);

		log_one_shot_time(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set one-shot capacitor
 *
 *****************************************************************************/

static void _SN76477_one_shot_cap_w(sn76477_state *sn, double data)
{
	sn->one_shot_cap = data;
}


void sn76477_one_shot_cap_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->one_shot_cap)
	{
		sn->channel->update();

		_SN76477_one_shot_cap_w(sn, data);

		log_one_shot_time(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set the voltage on the one-shot capacitor
 *
 *****************************************************************************/

void sn76477_one_shot_cap_voltage_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_CAP_VOLTAGE;

	if (data == SN76477_EXTERNAL_VOLTAGE_DISCONNECT)
	{
		/* switch to internal, if not already */
		if (sn->one_shot_cap_voltage_ext)
		{
			sn->channel->update();

			sn->one_shot_cap_voltage_ext = 0;

			log_one_shot_time(sn);
		}
	}
	else
	{
		/* set the voltage on the cap */
		if (!sn->one_shot_cap_voltage_ext || (data != sn->one_shot_cap_voltage))
		{
			sn->channel->update();

			sn->one_shot_cap_voltage_ext = 1;
			sn->one_shot_cap_voltage = data;

			log_one_shot_time(sn);
		}
	}
#endif
}



/*****************************************************************************
 *
 *  Set SLF resistor
 *
 *****************************************************************************/

static void _SN76477_slf_res_w(sn76477_state *sn, double data)
{
	sn->slf_res = data;
}


void sn76477_slf_res_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->slf_res)
	{
		sn->channel->update();

		_SN76477_slf_res_w(sn, data);

		log_slf_freq(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set SLF capacitor
 *
 *****************************************************************************/

static void _SN76477_slf_cap_w(sn76477_state *sn, double data)
{
	sn->slf_cap = data;
}


void sn76477_slf_cap_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->slf_cap)
	{
		sn->channel->update();

		_SN76477_slf_cap_w(sn, data);

		log_slf_freq(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set the voltage on the SLF capacitor
 *
 *  This is an alternate way of controlling the VCO as described in the book
 *
 *****************************************************************************/

void sn76477_slf_cap_voltage_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_CAP_VOLTAGE;

	if (data == SN76477_EXTERNAL_VOLTAGE_DISCONNECT)
	{
		/* switch to internal, if not already */
		if (sn->slf_cap_voltage_ext)
		{
			sn->channel->update();

			sn->slf_cap_voltage_ext = 0;

			log_slf_freq(sn);
		}
	}
	else
	{
		/* set the voltage on the cap */
		if (!sn->slf_cap_voltage_ext || (data != sn->slf_cap_voltage))
		{
			sn->channel->update();

			sn->slf_cap_voltage_ext = 1;
			sn->slf_cap_voltage = data;

			log_slf_freq(sn);
		}
	}
#endif
}



/*****************************************************************************
 *
 *  Set VCO resistor
 *
 *****************************************************************************/

static void _SN76477_vco_res_w(sn76477_state *sn, double data)
{
	sn->vco_res = data;
}


void sn76477_vco_res_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->vco_res)
	{
		sn->channel->update();

		_SN76477_vco_res_w(sn, data);

		log_vco_freq(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set VCO capacitor
 *
 *****************************************************************************/

static void _SN76477_vco_cap_w(sn76477_state *sn, double data)
{
	sn->vco_cap = data;
}


void sn76477_vco_cap_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->vco_cap)
	{
		sn->channel->update();

		_SN76477_vco_cap_w(sn, data);

		log_vco_freq(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set the voltage on the VCO capacitor
 *
 *****************************************************************************/

void sn76477_vco_cap_voltage_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_CAP_VOLTAGE;

	if (data == SN76477_EXTERNAL_VOLTAGE_DISCONNECT)
	{
		/* switch to internal, if not already */
		if (sn->vco_cap_voltage_ext)
		{
			sn->channel->update();

			sn->vco_cap_voltage_ext = 0;

			log_vco_freq(sn);
		}
	}
	else
	{
		/* set the voltage on the cap */
		if (!sn->vco_cap_voltage_ext || (data != sn->vco_cap_voltage))
		{
			sn->channel->update();

			sn->vco_cap_voltage_ext = 1;
			sn->vco_cap_voltage = data;

			log_vco_freq(sn);
		}
	}
#endif
}



/*****************************************************************************
 *
 *  Set VCO voltage
 *
 *****************************************************************************/

static void _SN76477_vco_voltage_w(sn76477_state *sn, double data)
{
	sn->vco_voltage = data;
}


void sn76477_vco_voltage_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_VOLTAGE;

	if (data != sn->vco_voltage)
	{
		sn->channel->update();

		_SN76477_vco_voltage_w(sn, data);

		log_vco_ext_voltage(sn);
		log_vco_duty_cycle(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set pitch voltage
 *
 *****************************************************************************/

static void _SN76477_pitch_voltage_w(sn76477_state *sn, double data)
{
	sn->pitch_voltage = data;
}


void sn76477_pitch_voltage_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_VOLTAGE;

	if (data != sn->pitch_voltage)
	{
		sn->channel->update();

		_SN76477_pitch_voltage_w(sn, data);

		log_vco_pitch_voltage(sn);
		log_vco_duty_cycle(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set noise external clock
 *
 *****************************************************************************/

void sn76477_noise_clock_w(device_t *device, UINT32 data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_BOOLEAN;

	if (data != sn->noise_clock)
	{
		sn->noise_clock = data;

		/* on the rising edge shift generate next value,
           if external control is enabled */
		if (sn->noise_clock && sn->noise_clock_ext)
		{
			sn->channel->update();

			sn->real_noise_bit_ff = generate_next_real_noise_bit(sn);
		}
	}
#endif
}



/*****************************************************************************
 *
 *  Set noise clock resistor
 *
 *****************************************************************************/

static void _SN76477_noise_clock_res_w(sn76477_state *sn, double data)
{
	if (data == 0)
	{
		sn->noise_clock_ext = 1;
	}
	else
	{
		sn->noise_clock_ext = 0;

		sn->noise_clock_res = data;
	}
}


void sn76477_noise_clock_res_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (((data == 0) && !sn->noise_clock_ext) ||
		((data != 0) && (data != sn->noise_clock_res)))
	{
		sn->channel->update();

		_SN76477_noise_clock_res_w(sn, data);

		log_noise_gen_freq(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set noise filter resistor
 *
 *****************************************************************************/

static void _SN76477_noise_filter_res_w(sn76477_state *sn, double data)
{
	sn->noise_filter_res = data;
}


void sn76477_noise_filter_res_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->noise_filter_res)
	{
		sn->channel->update();

		_SN76477_noise_filter_res_w(sn, data);

		log_noise_filter_freq(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set noise filter capacitor
 *
 *****************************************************************************/

static void _SN76477_noise_filter_cap_w(sn76477_state *sn, double data)
{
	sn->noise_filter_cap = data;
}


void sn76477_noise_filter_cap_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->noise_filter_cap)
	{
		sn->channel->update();

		_SN76477_noise_filter_cap_w(sn, data);

		log_noise_filter_freq(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set the voltage on the noise filter capacitor
 *
 *****************************************************************************/

void sn76477_noise_filter_cap_voltage_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_CAP_VOLTAGE;

	if (data == SN76477_EXTERNAL_VOLTAGE_DISCONNECT)
	{
		/* switch to internal, if not already */
		if (sn->noise_filter_cap_voltage_ext)
		{
			sn->channel->update();

			sn->noise_filter_cap_voltage_ext = 0;

			log_noise_filter_freq(sn);
		}
	}
	else
	{
		/* set the voltage on the cap */
		if (!sn->noise_filter_cap_voltage_ext || (data != sn->noise_filter_cap_voltage))
		{
			sn->channel->update();

			sn->noise_filter_cap_voltage_ext = 1;
			sn->noise_filter_cap_voltage = data;

			log_noise_filter_freq(sn);
		}
	}
#endif
}



/*****************************************************************************
 *
 *  Set attack resistor
 *
 *****************************************************************************/

static void _SN76477_attack_res_w(sn76477_state *sn, double data)
{
	sn->attack_res = data;
}


void sn76477_attack_res_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->attack_res)
	{
		sn->channel->update();

		_SN76477_attack_res_w(sn, data);

		log_attack_time(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set decay resistor
 *
 *****************************************************************************/

static void _SN76477_decay_res_w(sn76477_state *sn, double data)
{
	sn->decay_res = data;
}


void sn76477_decay_res_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->decay_res)
	{
		sn->channel->update();

		_SN76477_decay_res_w(sn, data);

		log_decay_time(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set attack/decay capacitor
 *
 *****************************************************************************/

static void _SN76477_attack_decay_cap_w(sn76477_state *sn, double data)
{
	sn->attack_decay_cap = data;
}


void sn76477_attack_decay_cap_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->attack_decay_cap)
	{
		sn->channel->update();

		_SN76477_attack_decay_cap_w(sn, data);

		log_attack_time(sn);
		log_decay_time(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set the voltage on the attack/decay capacitor
 *
 *****************************************************************************/

void sn76477_attack_decay_cap_voltage_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_CAP_VOLTAGE;

	if (data == SN76477_EXTERNAL_VOLTAGE_DISCONNECT)
	{
		/* switch to internal, if not already */
		if (sn->attack_decay_cap_voltage_ext)
		{
			sn->channel->update();

			sn->attack_decay_cap_voltage_ext = 0;

			log_attack_time(sn);
			log_decay_time(sn);
		}
	}
	else
	{
		/* set the voltage on the cap */
		if (!sn->attack_decay_cap_voltage_ext || (data != sn->attack_decay_cap_voltage))
		{
			sn->channel->update();

			sn->attack_decay_cap_voltage_ext = 1;
			sn->attack_decay_cap_voltage = data;

			log_attack_time(sn);
			log_decay_time(sn);
		}
	}
#endif
}



/*****************************************************************************
 *
 *  Set amplitude resistor
 *
 *****************************************************************************/

static void _SN76477_amplitude_res_w(sn76477_state *sn, double data)
{
	sn->amplitude_res = data;
}


void sn76477_amplitude_res_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->amplitude_res)
	{
		sn->channel->update();

		_SN76477_amplitude_res_w(sn, data);

		log_voltage_out(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Set feedback resistor
 *
 *****************************************************************************/

static void _SN76477_feedback_res_w(sn76477_state *sn, double data)
{
	sn->feedback_res = data;
}


void sn76477_feedback_res_w(device_t *device, double data)
{
#if TEST_MODE == 0
	sn76477_state *sn = get_safe_token(device);

	CHECK_CHIP_NUM_AND_POSITIVE;

	if (data != sn->feedback_res)
	{
		sn->channel->update();

		_SN76477_feedback_res_w(sn, data);

		log_voltage_out(sn);
	}
#endif
}



/*****************************************************************************
 *
 *  Sample generation
 *
 *****************************************************************************/

static STREAM_UPDATE( SN76477_update )
{
	double one_shot_cap_charging_step;
	double one_shot_cap_discharging_step;
	double slf_cap_charging_step;
	double slf_cap_discharging_step;
	double vco_duty_cycle_multiplier;
	double vco_cap_charging_step;
	double vco_cap_discharging_step;
	double vco_cap_voltage_max;
	UINT32 noise_gen_freq;
	double noise_filter_cap_charging_step;
	double noise_filter_cap_discharging_step;
	double attack_decay_cap_charging_step;
	double attack_decay_cap_discharging_step;
	int    attack_decay_cap_charging;
	double voltage_out;
	double center_to_peak_voltage_out;

	sn76477_state *sn = (sn76477_state *)param;
	stream_sample_t *buffer = outputs[0];


#if TEST_MODE
	static int recursing = 0;	/* we need to prevent recursion since enable_w calls device->machine().input().code_pressed_once(KEYCODE_SPACE->update */

	if () && !recursing)
	{
		recursing = 1;

		device->machine().sound().system_enable();
		SN76477_test_enable_w(sn, !sn->enable);
	}

	recursing = 0;
#endif

	/* compute charging values, doing it here ensures that we always use the latest values */
	one_shot_cap_charging_step = compute_one_shot_cap_charging_rate(sn) / sn->sample_rate;
	one_shot_cap_discharging_step = compute_one_shot_cap_discharging_rate(sn) / sn->sample_rate;

	slf_cap_charging_step = compute_slf_cap_charging_rate(sn) / sn->sample_rate;
	slf_cap_discharging_step = compute_slf_cap_discharging_rate(sn) / sn->sample_rate;

	vco_duty_cycle_multiplier = (1 - compute_vco_duty_cycle(sn)) * 2;
	vco_cap_charging_step = compute_vco_cap_charging_discharging_rate(sn) / vco_duty_cycle_multiplier / sn->sample_rate;
	vco_cap_discharging_step = compute_vco_cap_charging_discharging_rate(sn) * vco_duty_cycle_multiplier / sn->sample_rate;

	noise_filter_cap_charging_step = compute_noise_filter_cap_charging_rate(sn) / sn->sample_rate;
	noise_filter_cap_discharging_step = compute_noise_filter_cap_discharging_rate(sn) / sn->sample_rate;
	noise_gen_freq = compute_noise_gen_freq(sn);

	attack_decay_cap_charging_step = compute_attack_decay_cap_charging_rate(sn) / sn->sample_rate;
	attack_decay_cap_discharging_step = compute_attack_decay_cap_discharging_rate(sn) / sn->sample_rate;

	center_to_peak_voltage_out = compute_center_to_peak_voltage_out(sn);


	/* process 'samples' number of samples */
	while (samples--)
	{
		/* update the one-shot cap voltage */
		if (!sn->one_shot_cap_voltage_ext)
		{
			if (sn->one_shot_running_ff)
			{
				/* charging */
				sn->one_shot_cap_voltage = min(sn->one_shot_cap_voltage + one_shot_cap_charging_step, ONE_SHOT_CAP_VOLTAGE_MAX);
			}
			else
			{
				/* discharging */
				sn->one_shot_cap_voltage = max(sn->one_shot_cap_voltage - one_shot_cap_discharging_step, ONE_SHOT_CAP_VOLTAGE_MIN);
			}
		}

		if (sn->one_shot_cap_voltage >= ONE_SHOT_CAP_VOLTAGE_MAX)
		{
			sn->one_shot_running_ff = 0;
		}


		/* update the SLF (super low frequency oscillator) */
		if (!sn->slf_cap_voltage_ext)
		{
			/* internal */
			if (!sn->slf_out_ff)
			{
				/* charging */
				sn->slf_cap_voltage = min(sn->slf_cap_voltage + slf_cap_charging_step, SLF_CAP_VOLTAGE_MAX);
			}
			else
			{
				/* discharging */
				sn->slf_cap_voltage = max(sn->slf_cap_voltage - slf_cap_discharging_step, SLF_CAP_VOLTAGE_MIN);
			}
		}

		if (sn->slf_cap_voltage >= SLF_CAP_VOLTAGE_MAX)
		{
			sn->slf_out_ff = 1;
		}
		else if (sn->slf_cap_voltage <= SLF_CAP_VOLTAGE_MIN)
		{
			sn->slf_out_ff = 0;
		}


		/* update the VCO (voltage controlled oscillator) */
		if (sn->vco_mode)
		{
			/* VCO is controlled by SLF */
			vco_cap_voltage_max =  sn->slf_cap_voltage + VCO_TO_SLF_VOLTAGE_DIFF;
		}
		else
		{
			/* VCO is controlled by external voltage */
			vco_cap_voltage_max = sn->vco_voltage + VCO_TO_SLF_VOLTAGE_DIFF;
		}

		if (!sn->vco_cap_voltage_ext)
		{
			if (!sn->vco_out_ff)
			{
				/* charging */
				sn->vco_cap_voltage = min(sn->vco_cap_voltage + vco_cap_charging_step, vco_cap_voltage_max);
			}
			else
			{
				/* discharging */
				sn->vco_cap_voltage = max(sn->vco_cap_voltage - vco_cap_discharging_step, VCO_CAP_VOLTAGE_MIN);
			}
		}

		if (sn->vco_cap_voltage >= vco_cap_voltage_max)
		{
			if (!sn->vco_out_ff)
			{
				/* positive edge */
				sn->vco_alt_pos_edge_ff = !sn->vco_alt_pos_edge_ff;
			}

			sn->vco_out_ff = 1;
		}
		else if (sn->vco_cap_voltage <= VCO_CAP_VOLTAGE_MIN)
		{
			sn->vco_out_ff = 0;
		}


		/* update the noise generator */
		while (!sn->noise_clock_ext && (sn->noise_gen_count <= noise_gen_freq))
		{
			sn->noise_gen_count = sn->noise_gen_count + sn->sample_rate;

			sn->real_noise_bit_ff = generate_next_real_noise_bit(sn);
		}

		sn->noise_gen_count = sn->noise_gen_count - noise_gen_freq;


		/* update the noise filter */
		if (!sn->noise_filter_cap_voltage_ext)
		{
			/* internal */
			if (sn->real_noise_bit_ff)
			{
				/* charging */
				sn->noise_filter_cap_voltage = min(sn->noise_filter_cap_voltage + noise_filter_cap_charging_step, NOISE_CAP_VOLTAGE_MAX);
			}
			else
			{
				/* discharging */
				sn->noise_filter_cap_voltage = max(sn->noise_filter_cap_voltage - noise_filter_cap_discharging_step, NOISE_CAP_VOLTAGE_MIN);
			}
		}

		/* check the thresholds */
		if (sn->noise_filter_cap_voltage >= NOISE_CAP_HIGH_THRESHOLD)
		{
			sn->filtered_noise_bit_ff = 0;
		}
		else if (sn->noise_filter_cap_voltage <= NOISE_CAP_LOW_THRESHOLD)
		{
			sn->filtered_noise_bit_ff = 1;
		}


		/* based on the envelope mode figure out the attack/decay phase we are in */
		switch (sn->envelope_mode)
		{
		case 0:		/* VCO */
			attack_decay_cap_charging = sn->vco_out_ff;
			break;

		case 1:		/* one-shot */
			attack_decay_cap_charging = sn->one_shot_running_ff;
			break;

		case 2:
		default:	/* mixer only */
			attack_decay_cap_charging = 1;	/* never a decay phase */
			break;

		case 3:		/* VCO with alternating polarity */
			attack_decay_cap_charging = sn->vco_out_ff && sn->vco_alt_pos_edge_ff;
			break;
		}


		/* update a/d cap voltage */
		if (!sn->attack_decay_cap_voltage_ext)
		{
			if (attack_decay_cap_charging)
			{
				if (attack_decay_cap_charging_step > 0)
				{
					sn->attack_decay_cap_voltage = min(sn->attack_decay_cap_voltage + attack_decay_cap_charging_step, AD_CAP_VOLTAGE_MAX);
				}
				else
				{
					/* no attack, voltage to max instantly */
					sn->attack_decay_cap_voltage = AD_CAP_VOLTAGE_MAX;
				}
			}
			else
			{
				/* discharging */
				if (attack_decay_cap_discharging_step > 0)
				{
					sn->attack_decay_cap_voltage = max(sn->attack_decay_cap_voltage - attack_decay_cap_discharging_step, AD_CAP_VOLTAGE_MIN);
				}
				else
				{
					/* no decay, voltage to min instantly */
					sn->attack_decay_cap_voltage = AD_CAP_VOLTAGE_MIN;
				}
			}
		}


		/* mix the output, if enabled, or not saturated by the VCO */
		if (!sn->enable && (sn->vco_cap_voltage <= VCO_CAP_VOLTAGE_MAX))
		{
			UINT32 out;

			/* enabled */
			switch (sn->mixer_mode)
			{
			case 0:		/* VCO */
				out = sn->vco_out_ff;
				break;

			case 1:		/* SLF */
				out = sn->slf_out_ff;
				break;

			case 2:		/* noise */
				out = sn->filtered_noise_bit_ff;
				break;

			case 3:		/* VCO and noise */
				out = sn->vco_out_ff & sn->filtered_noise_bit_ff;
				break;

			case 4:		/* SLF and noise */
				out = sn->slf_out_ff & sn->filtered_noise_bit_ff;
				break;

			case 5:		/* VCO, SLF and noise */
				out = sn->vco_out_ff & sn->slf_out_ff & sn->filtered_noise_bit_ff;
				break;

			case 6:		/* VCO and SLF */
				out = sn->vco_out_ff & sn->slf_out_ff;
				break;

			case 7:		/* inhibit */
			default:
				out = 0;
				break;
			}

			/* determine the OUT voltage from the attack/delay cap voltage and clip it */
			if (out)
			{
				voltage_out = OUT_CENTER_LEVEL_VOLTAGE + center_to_peak_voltage_out * out_pos_gain[(int)(sn->attack_decay_cap_voltage * 10)],
				voltage_out = min(voltage_out, OUT_HIGH_CLIP_THRESHOLD);
			}
			else
			{
				voltage_out = OUT_CENTER_LEVEL_VOLTAGE + center_to_peak_voltage_out * out_neg_gain[(int)(sn->attack_decay_cap_voltage * 10)],
				voltage_out = max(voltage_out, OUT_LOW_CLIP_THRESHOLD);
			}
		}
		else
		{
			/* disabled */
			voltage_out = OUT_CENTER_LEVEL_VOLTAGE;
		}


		/* convert it to a signed 16-bit sample,
           -32767 = OUT_LOW_CLIP_THRESHOLD
                0 = OUT_CENTER_LEVEL_VOLTAGE
            32767 = 2 * OUT_CENTER_LEVEL_VOLTAGE + OUT_LOW_CLIP_THRESHOLD

                      / Vout - Vmin    \
            sample = |  ----------- - 1 | * 32767
                      \ Vcen - Vmin    /
         */
		*buffer++ = (((voltage_out - OUT_LOW_CLIP_THRESHOLD) / (OUT_CENTER_LEVEL_VOLTAGE - OUT_LOW_CLIP_THRESHOLD)) - 1) * 32767;

		if (LOG_WAV && LOG_WAV_ENABLED_ONLY && !sn->enable)
		{
			INT16 log_data_l;
			INT16 log_data_r;

			switch (LOG_WAV_VALUE_L)
			{
			case 0:
				log_data_l = LOG_WAV_GAIN_FACTOR * voltage_out;
				log_data_r = LOG_WAV_GAIN_FACTOR * voltage_out;
				break;
			case 1:
				log_data_l = LOG_WAV_GAIN_FACTOR * sn->enable;
				log_data_r = LOG_WAV_GAIN_FACTOR * sn->enable;
				break;
			case 2:
				log_data_l = LOG_WAV_GAIN_FACTOR * sn->one_shot_cap_voltage;
				log_data_r = LOG_WAV_GAIN_FACTOR * sn->one_shot_cap_voltage;
				break;
			case 3:
				log_data_l = LOG_WAV_GAIN_FACTOR * sn->attack_decay_cap_voltage;
				log_data_r = LOG_WAV_GAIN_FACTOR * sn->attack_decay_cap_voltage;
				break;
			case 4:
				log_data_l = LOG_WAV_GAIN_FACTOR * sn->slf_cap_voltage;
				log_data_r = LOG_WAV_GAIN_FACTOR * sn->slf_cap_voltage;
				break;
			case 5:
				log_data_l = LOG_WAV_GAIN_FACTOR * sn->vco_cap_voltage;
				log_data_r = LOG_WAV_GAIN_FACTOR * sn->vco_cap_voltage;
				break;
			case 6:
				log_data_l = LOG_WAV_GAIN_FACTOR * sn->noise_filter_cap_voltage;
				log_data_r = LOG_WAV_GAIN_FACTOR * sn->noise_filter_cap_voltage;
				break;
			}

			add_wav_data(sn, log_data_l, log_data_r);
		}
	}
}



/*****************************************************************************
 *
 *  State saving
 *
 *****************************************************************************/

static void state_save_register(device_t *device, sn76477_state *sn)
{
	device->save_item(NAME(sn->enable));
	device->save_item(NAME(sn->envelope_mode));
	device->save_item(NAME(sn->vco_mode));
	device->save_item(NAME(sn->mixer_mode));

	device->save_item(NAME(sn->one_shot_res));
	device->save_item(NAME(sn->one_shot_cap));
	device->save_item(NAME(sn->one_shot_cap_voltage_ext));

	device->save_item(NAME(sn->slf_res));
	device->save_item(NAME(sn->slf_cap));
	device->save_item(NAME(sn->slf_cap_voltage_ext));

	device->save_item(NAME(sn->vco_voltage));
	device->save_item(NAME(sn->vco_res));
	device->save_item(NAME(sn->vco_cap));
	device->save_item(NAME(sn->vco_cap_voltage_ext));

	device->save_item(NAME(sn->noise_clock_res));
	device->save_item(NAME(sn->noise_clock_ext));
	device->save_item(NAME(sn->noise_clock));
	device->save_item(NAME(sn->noise_filter_res));
	device->save_item(NAME(sn->noise_filter_cap));
	device->save_item(NAME(sn->noise_filter_cap_voltage_ext));

	device->save_item(NAME(sn->attack_res));
	device->save_item(NAME(sn->decay_res));
	device->save_item(NAME(sn->attack_decay_cap));
	device->save_item(NAME(sn->attack_decay_cap_voltage_ext));

	device->save_item(NAME(sn->amplitude_res));
	device->save_item(NAME(sn->feedback_res));
	device->save_item(NAME(sn->pitch_voltage));

	device->save_item(NAME(sn->one_shot_cap_voltage));
	device->save_item(NAME(sn->one_shot_running_ff));

	device->save_item(NAME(sn->slf_cap_voltage));
	device->save_item(NAME(sn->slf_out_ff));

	device->save_item(NAME(sn->vco_cap_voltage));
	device->save_item(NAME(sn->vco_out_ff));
	device->save_item(NAME(sn->vco_alt_pos_edge_ff));

	device->save_item(NAME(sn->noise_filter_cap_voltage));
	device->save_item(NAME(sn->real_noise_bit_ff));
	device->save_item(NAME(sn->filtered_noise_bit_ff));
	device->save_item(NAME(sn->noise_gen_count));

	device->save_item(NAME(sn->attack_decay_cap_voltage));

	device->save_item(NAME(sn->rng));
}



/*****************************************************************************
 *
 *  Sound interface glue functions
 *
 *****************************************************************************/

static DEVICE_START( sn76477 )
{
	sn76477_state *sn = get_safe_token(device);
	sn76477_interface *intf;


#if TEST_MODE == 0
	intf = (sn76477_interface *)device->static_config();
#else
	intf = &test_interface;
#endif


	sn->device = device;

	sn->channel = device->machine().sound().stream_alloc(*device, 0, 1, device->machine().sample_rate(), sn, SN76477_update);

	if (device->clock() > 0)
	{
		sn->sample_rate = device->clock();
	}
	else
	{
		sn->sample_rate = device->machine().sample_rate();
	}

	intialize_noise(sn);

	/* set up interface values */
	_SN76477_enable_w(sn, intf->enable);
	_SN76477_vco_w(sn, intf->vco);
	_SN76477_mixer_a_w(sn, intf->mixer_a);
	_SN76477_mixer_b_w(sn, intf->mixer_b);
	_SN76477_mixer_c_w(sn, intf->mixer_c);
	_SN76477_envelope_1_w(sn, intf->envelope_1);
	_SN76477_envelope_2_w(sn, intf->envelope_2);
	_SN76477_one_shot_res_w(sn, intf->one_shot_res);
	_SN76477_one_shot_cap_w(sn, intf->one_shot_cap);
	_SN76477_slf_res_w(sn, intf->slf_res);
	_SN76477_slf_cap_w(sn, intf->slf_cap);
	_SN76477_vco_res_w(sn, intf->vco_res);
	_SN76477_vco_cap_w(sn, intf->vco_cap);
	_SN76477_vco_voltage_w(sn, intf->vco_voltage);
	_SN76477_noise_clock_res_w(sn, intf->noise_clock_res);
	_SN76477_noise_filter_res_w(sn, intf->noise_filter_res);
	_SN76477_noise_filter_cap_w(sn, intf->noise_filter_cap);
	_SN76477_decay_res_w(sn, intf->decay_res);
	_SN76477_attack_res_w(sn, intf->attack_res);
	_SN76477_attack_decay_cap_w(sn, intf->attack_decay_cap);
	_SN76477_amplitude_res_w(sn, intf->amplitude_res);
	_SN76477_feedback_res_w(sn, intf->feedback_res);
	_SN76477_pitch_voltage_w(sn, intf->pitch_voltage);

	sn->one_shot_cap_voltage = ONE_SHOT_CAP_VOLTAGE_MIN;
	sn->slf_cap_voltage = SLF_CAP_VOLTAGE_MIN;
	sn->vco_cap_voltage = VCO_CAP_VOLTAGE_MIN;
	sn->noise_filter_cap_voltage = NOISE_CAP_VOLTAGE_MIN;
	sn->attack_decay_cap_voltage = AD_CAP_VOLTAGE_MIN;

	state_save_register(device, sn);

	log_complete_state(sn);

	if (LOG_WAV)
		open_wav_file(sn);
}


static DEVICE_STOP( sn76477 )
{
	sn76477_state *sn = get_safe_token(device);

	if (LOG_WAV)
		close_wav_file(sn);
}


DEVICE_GET_INFO( sn76477 )
{
	switch (state)
	{
	case DEVINFO_INT_TOKEN_BYTES:	info->i = sizeof(sn76477_state); break;
	case DEVINFO_FCT_START:			info->start = DEVICE_START_NAME( sn76477 ); break;
	case DEVINFO_FCT_STOP:			info->stop = DEVICE_STOP_NAME( sn76477 ); break;
	case DEVINFO_STR_NAME:			strcpy(info->s, "SN76477"); break;
	case DEVINFO_STR_FAMILY:	strcpy(info->s, "Analog"); break;
	case DEVINFO_STR_VERSION:	strcpy(info->s, "2.1"); break;
	case DEVINFO_STR_SOURCE_FILE:		strcpy(info->s, __FILE__); break;
	case DEVINFO_STR_CREDITS:	strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break;
	}
}


DEFINE_LEGACY_SOUND_DEVICE(SN76477, sn76477);
