summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/sound/sn76477.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/sound/sn76477.c')
-rw-r--r--trunk/src/emu/sound/sn76477.c2487
1 files changed, 2487 insertions, 0 deletions
diff --git a/trunk/src/emu/sound/sn76477.c b/trunk/src/emu/sound/sn76477.c
new file mode 100644
index 00000000000..ab38fd5a615
--- /dev/null
+++ b/trunk/src/emu/sound/sn76477.c
@@ -0,0 +1,2487 @@
+/*****************************************************************************
+
+ 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((state & 0x01) == state)
+#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);
+ }
+}
+
+
+WRITE_LINE_DEVICE_HANDLER( sn76477_enable_w )
+{
+#if TEST_MODE == 0
+ sn76477_state *sn = get_safe_token(device);
+
+ CHECK_CHIP_NUM_AND_BOOLEAN;
+
+ SN76477_test_enable_w(sn, state);
+#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);
+}
+
+
+WRITE_LINE_DEVICE_HANDLER( sn76477_mixer_a_w )
+{
+#if TEST_MODE == 0
+ sn76477_state *sn = get_safe_token(device);
+
+ CHECK_CHIP_NUM_AND_BOOLEAN;
+
+ if (state != ((sn->mixer_mode >> 0) & 0x01))
+ {
+ sn->channel->update();
+
+ _SN76477_mixer_a_w(sn, state);
+
+ 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);
+}
+
+
+WRITE_LINE_DEVICE_HANDLER( sn76477_mixer_b_w )
+{
+#if TEST_MODE == 0
+ sn76477_state *sn = get_safe_token(device);
+
+ CHECK_CHIP_NUM_AND_BOOLEAN;
+
+ if (state != ((sn->mixer_mode >> 1) & 0x01))
+ {
+ sn->channel->update();
+
+ _SN76477_mixer_b_w(sn, state);
+
+ 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);
+}
+
+
+WRITE_LINE_DEVICE_HANDLER( sn76477_mixer_c_w )
+{
+#if TEST_MODE == 0
+ sn76477_state *sn = get_safe_token(device);
+
+ CHECK_CHIP_NUM_AND_BOOLEAN;
+
+ if (state != ((sn->mixer_mode >> 2) & 0x01))
+ {
+ sn->channel->update();
+
+ _SN76477_mixer_c_w(sn, state);
+
+ 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);
+}
+
+
+WRITE_LINE_DEVICE_HANDLER( sn76477_envelope_1_w )
+{
+#if TEST_MODE == 0
+ sn76477_state *sn = get_safe_token(device);
+
+ CHECK_CHIP_NUM_AND_BOOLEAN;
+
+ if (state != ((sn->envelope_mode >> 0) & 0x01))
+ {
+ sn->channel->update();
+
+ _SN76477_envelope_1_w(sn, state);
+
+ 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);
+}
+
+
+WRITE_LINE_DEVICE_HANDLER( sn76477_envelope_2_w )
+{
+#if TEST_MODE == 0
+ sn76477_state *sn = get_safe_token(device);
+
+ CHECK_CHIP_NUM_AND_BOOLEAN;
+
+ if (state != ((sn->envelope_mode >> 1) & 0x01))
+ {
+ sn->channel->update();
+
+ _SN76477_envelope_2_w(sn, state);
+
+ log_envelope_mode(sn);
+ }
+#endif
+}
+
+
+
+/*****************************************************************************
+ *
+ * Set VCO select input
+ *
+ *****************************************************************************/
+
+static void _SN76477_vco_w(sn76477_state *sn, UINT32 data)
+{
+ sn->vco_mode = data;
+}
+
+
+WRITE_LINE_DEVICE_HANDLER( sn76477_vco_w )
+{
+#if TEST_MODE == 0
+ sn76477_state *sn = get_safe_token(device);
+
+ CHECK_CHIP_NUM_AND_BOOLEAN;
+
+ if (state != sn->vco_mode)
+ {
+ sn->channel->update();
+
+ _SN76477_vco_w(sn, state);
+
+ 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
+ *
+ *****************************************************************************/
+
+WRITE_LINE_DEVICE_HANDLER( sn76477_noise_clock_w )
+{
+#if TEST_MODE == 0
+ sn76477_state *sn = get_safe_token(device);
+
+ CHECK_CHIP_NUM_AND_BOOLEAN;
+
+ if (state != sn->noise_clock)
+ {
+ sn->noise_clock = state;
+
+ /* 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);