summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author m1macrophage <168948267+m1macrophage@users.noreply.github.com>2026-04-12 07:23:17 -0700
committer GitHub <noreply@github.com>2026-04-12 16:23:17 +0200
commit0f5872aa2eeb46ae87c6a715946cce1704749d84 (patch)
treea72bd7ac2a5280069db889a87f2d39ab3f68c0a6
parent13493035a704249a425828ae0316eb4dcab0b551 (diff)
sound/va_vca.cpp: support for differential inputs. prophet5.cpp: VCA balance trimmers. (#15215)
-rw-r--r--src/devices/sound/va_vca.cpp236
-rw-r--r--src/devices/sound/va_vca.h121
-rw-r--r--src/mame/sequential/prophet5.cpp102
3 files changed, 345 insertions, 114 deletions
diff --git a/src/devices/sound/va_vca.cpp b/src/devices/sound/va_vca.cpp
index b74d6f57b2f..621f13b10ef 100644
--- a/src/devices/sound/va_vca.cpp
+++ b/src/devices/sound/va_vca.cpp
@@ -25,13 +25,18 @@ va_vca_device::va_vca_device(const machine_config &mconfig, device_type type, co
, device_sound_interface(mconfig, *this)
, m_stream(nullptr)
, m_fixed_gain(1.0F)
+ , m_fixed_inv_input(0.0F)
{
}
+void va_vca_device::update()
+{
+ if (m_stream)
+ m_stream->update();
+}
+
void va_vca_device::set_fixed_gain_cv(float gain_cv)
{
- if (!m_stream)
- fatalerror("%s: set_fixed_gain_cv() cannot be called before device_start()\n", tag());
if (BIT(get_sound_requested_inputs_mask(), INPUT_GAIN))
fatalerror("%s: Cannot set a fixed gain CV when streaming it.\n", tag());
@@ -39,73 +44,83 @@ void va_vca_device::set_fixed_gain_cv(float gain_cv)
if (gain == m_fixed_gain)
return;
- m_stream->update();
+ update();
m_fixed_gain = gain;
}
+void va_vca_device::set_fixed_inv_input(float x)
+{
+ if (BIT(get_sound_requested_inputs_mask(), INPUT_AUDIO_INV))
+ fatalerror("%s: Cannot set the inverting input when streaming it.\n", tag());
+
+ if (x == m_fixed_inv_input)
+ return;
+
+ update();
+ m_fixed_inv_input = x;
+}
+
+float va_vca_device::diff(float p, float m) const
+{
+ return p - m;
+}
+
float va_vca_device::cv_to_gain(float cv) const
{
return cv;
}
-float va_vca_device::distorted(float s) const
+u32 va_vca_device::preferred_sample_rate() const
{
- fatalerror("A va_vca_device subclass enabled distortion without implementing the distorted() function.\n");
- return s;
+ return SAMPLE_RATE_OUTPUT_ADAPTIVE;
}
void va_vca_device::device_start()
{
- if (get_sound_requested_inputs_mask() != 0x01 && get_sound_requested_inputs_mask() != 0x03)
- fatalerror("%s: Input 0 must be connected, input 1 can optionally be connected. No other inputs allowed.\n", tag());
+ if (!BIT(get_sound_requested_inputs_mask() , INPUT_AUDIO))
+ fatalerror("%s: Input 0 must be connected.", tag());
+ if (get_sound_requested_inputs_mask() & ~u64(0x07))
+ fatalerror("%s: Only inputs 0-2 can be connected.", tag());
+
+ m_stream = stream_alloc(get_sound_requested_inputs(), 1, preferred_sample_rate());
- // Upsample when using distortion, to reduce aliasing.
- const u32 sample_rate = has_distortion() ? std::max(96000, machine().sample_rate()) : SAMPLE_RATE_OUTPUT_ADAPTIVE;
- m_stream = stream_alloc(get_sound_requested_inputs(), 1, sample_rate);
save_item(NAME(m_fixed_gain));
+ save_item(NAME(m_fixed_inv_input));
}
void va_vca_device::sound_stream_update(sound_stream &stream)
{
- if (has_distortion())
- {
- if (BIT(get_sound_requested_inputs_mask(), INPUT_GAIN))
- {
- for (int i = 0; i < stream.samples(); i++)
- stream.put(0, i, distorted(stream.get(INPUT_AUDIO, i)) * cv_to_gain(stream.get(INPUT_GAIN, i)));
- }
- else
- {
- for (int i = 0; i < stream.samples(); i++)
- stream.put(0, i, distorted(stream.get(INPUT_AUDIO, i)) * m_fixed_gain);
- }
- }
- else
+ const int n = stream.samples();
+ const bool streaming_gain = BIT(get_sound_requested_inputs_mask(), INPUT_GAIN);
+ const bool streaming_inv = BIT(get_sound_requested_inputs_mask(), INPUT_AUDIO_INV);
+
+ for (int i = 0; i < n; ++i)
{
- if (BIT(get_sound_requested_inputs_mask(), INPUT_GAIN))
- {
- for (int i = 0; i < stream.samples(); i++)
- stream.put(0, i, stream.get(INPUT_AUDIO, i) * cv_to_gain(stream.get(INPUT_GAIN, i)));
- }
- else
- {
- for (int i = 0; i < stream.samples(); i++)
- stream.put(0, i, stream.get(INPUT_AUDIO, i) * m_fixed_gain);
- }
+ const float plus = stream.get(INPUT_AUDIO, i);
+ const float minus = streaming_inv ? stream.get(INPUT_AUDIO_INV, i) : m_fixed_inv_input;
+ const float gain = streaming_gain ? cv_to_gain(stream.get(INPUT_GAIN, i)) : m_fixed_gain;
+ stream.put(0, i, gain * diff(plus, minus));
}
}
ca3280_vca_device::ca3280_vca_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: va_vca_device(mconfig, CA3280_VCA, tag, owner, 0)
- , m_input_scale(1)
+ , m_plus_scale(1)
+ , m_minus_scale(1)
, m_output_scale(1)
{
}
-ca3280_vca_device &ca3280_vca_device::configure_input_divider(float r_in, float r_gnd)
+ca3280_vca_device &ca3280_vca_device::configure_plus_divider(float r_in, float r_gnd)
+{
+ m_plus_scale = r_gnd / (r_in + r_gnd);
+ return *this;
+}
+
+ca3280_vca_device &ca3280_vca_device::configure_minus_divider(float r_in, float r_gnd)
{
- m_input_scale = r_gnd / (r_in + r_gnd);
+ m_minus_scale = r_gnd / (r_in + r_gnd);
return *this;
}
@@ -126,25 +141,32 @@ ca3280_vca_device &ca3280_vca_device::configure_voltage_output(float r_out)
// DVIn ~ (Vin+ - Vin-). Vin- is typically 0V.
// VT ~ Thermal voltage. A temperature-dependent constant.
+float ca3280_vca_device::diff(float p, float m) const
+{
+ constexpr float VT = 25.7E-3; // Thermal voltage at 25 degrees C.
+ return tanhf((m_plus_scale * p - m_minus_scale * m) / (2 * VT));
+}
+
float ca3280_vca_device::cv_to_gain(float cv) const
{
// cv = Iabc.
return CA3280_IOUT_MAX_SCALE * cv * m_output_scale;
}
-float ca3280_vca_device::distorted(float s) const
+u32 ca3280_vca_device::preferred_sample_rate() const
{
- constexpr float VT = 25.7E-3; // Thermal voltage at 25 degrees C.
- return tanhf((s * m_input_scale) / (2 * VT));
+ // Upsample to reduce aliasing due to tanh() distortion.
+ return std::max(96000, machine().sample_rate());
}
-
ca3280_vca_lin_device::ca3280_vca_lin_device(const machine_config &mconfig, const char *tag, device_t *owner, float i_d)
: va_vca_device(mconfig, CA3280_VCA_LIN, tag, owner, 0)
- , m_i_d_inv(1.0F / i_d)
- , m_input_scale(1)
+ , m_id(i_d)
, m_output_scale(1)
, m_cv_scale(1)
+ , m_rp(-1)
+ , m_rm(-1)
+ , m_r_inv(-1)
{
update_cv_scale();
}
@@ -159,13 +181,6 @@ ca3280_vca_lin_device::ca3280_vca_lin_device(const machine_config &mconfig, cons
{
}
-ca3280_vca_lin_device &ca3280_vca_lin_device::configure_voltage_input(float r_in)
-{
- m_input_scale = 1.0F / r_in;
- update_cv_scale();
- return *this;
-}
-
ca3280_vca_lin_device &ca3280_vca_lin_device::configure_voltage_output(float r_out)
{
// output voltage = output_current * R, where R is the resistor to ground
@@ -175,28 +190,127 @@ ca3280_vca_lin_device &ca3280_vca_lin_device::configure_voltage_output(float r_o
return *this;
}
-// The output current for a linearized OTA can be determined by:
-// Iout = Ioutmax * Iin / Id
-// For the CA3280:
-// Ioutmax = CA3280_IOUT_MAX_SCALE * Iabc
+ca3280_vca_lin_device &ca3280_vca_lin_device::set_rplus(float r)
+{
+ assert(r > 0);
+ if (r == m_rp)
+ return *this;
+ update();
+ m_rp = r;
+ m_r_inv = 1.0F / (m_rp + m_rm);
+ return *this;
+}
+
+ca3280_vca_lin_device &ca3280_vca_lin_device::set_rminus(float r)
+{
+ assert(r > 0);
+ if (r == m_rm)
+ return *this;
+ update();
+ m_rm = r;
+ m_r_inv = 1.0F / (m_rp + m_rm);
+ return *this;
+}
+
+// The output current for a linearized OTA is:
+// Iout = Idiff * s * Iabc / Id
// Where:
+// s ~ an OTA-specific scaling factor (e.g. CA3280_IOUT_MAX_SCALE)
// Iabc ~ transconductance (gain) control current.
-// Iin ~ current at in+, assuming in- is connected to ground (via a
-// resistor).
// Id ~ diode current. The current supplied to the Id pin.
+// Idiff ~ differential input current. See diff() below.
+// Returns (s * Iabc / Id), possibly scaled to account for the current-to-voltage
+// conversion, if so configured.
float ca3280_vca_lin_device::cv_to_gain(float cv) const
{
// cv = Iabc.
- // m_cv_scale incorporates 1 / Id, along with the optional factors to
- // convert the input voltage to a current, and the output current to a
- // a voltage (see update_cv_scale() and the various configure_*() methods).
return cv * m_cv_scale;
}
void ca3280_vca_lin_device::update_cv_scale()
{
- m_cv_scale = CA3280_IOUT_MAX_SCALE * m_input_scale * m_i_d_inv * m_output_scale;
+ m_cv_scale = CA3280_IOUT_MAX_SCALE * m_output_scale / m_id;
+}
+
+// Idiff = Im - Ip
+// Where:
+// Im ~ current flowing out of the "-" input.
+// Ip ~ current flowing out of the "+" input.
+//
+// See also page 3 (Background section) of
+// http://www.openmusiclabs.com/files/otadist.pdf
+//
+// This diagram shows the part of the OTA that generates Im and Ip. While this
+// circuit is typical of OTAs with linearizing diodes (e.g. LM13700), the
+// CA3280's relevant circuit is more complex (see datasheet). But it is
+// functionally similar.
+//
+// Id
+// OTA | <- Vtop
+// -----------------
+// | | |
+// | +---+---+ |
+// | | | |
+// | D D |
+// | v v |
+// | | | |
+// -----------------
+// +| |-
+// | |
+// Ip | Rp Rm | Im
+// v | | v
+// Vp Vm
+//
+// [1] Id = Ip + Im => Im = Id - Ip
+// [2] VDp = Vt * log(Ip/Is)
+// [3] VDm = Vt * log(Im/Is)
+// [4] Vtop = Vp + Ip * Rp + VDp
+// [5] Vtop = Vm + Im * Rm + VDm
+// [6] Is = Diode saturation current.
+//
+// Use [4] to solve for Ip and [5] to solve for Im, using the other equations to
+// substitue variables:
+// Ip = (Id * Rm - (Vp - Vm) + Vt * log(Im/Ip)) / (Rp + Rm)
+// Im = (Id * Rp + (vp - Vm) + Vt * log(Ip/Im)) / (Rp + Rm)
+//
+// For arbitrary Rp and Rm, the above do not have closed form solutions.
+// However, in typical applications, Id * Rm >> Vt * log(Im/Ip) and
+// Id * Rp >> Vt * log(Ip/Im), so we can pretend the diodes are not there to get
+// the approximations:
+// Ip ~= (Id * Rm - (Vp - Vm)) / (Rp + Rm)
+// Im ~= (Id * Rp + (vp - Vm)) / (Rp + Rm)
+// Idiff ~= Im - Ip
+//
+// If Rp = Rm = R, then the log terms cancel out and we get:
+// Idiff = Im - Ip = (Vp - Vm) / R
+//
+// Importantly, when Rp = Rm (which is typical), we don't need the
+// "Id * Rp >> ..." assumptions to hold, so emulation accuracy is higher.
+//
+float ca3280_vca_lin_device::diff(float p, float m) const
+{
+ // p = Vp, m = Vm
+ assert(m_rp > 0 && m_rm > 0 && m_r_inv > 0);
+ const float dv = p - m;
+ const float ip = (m_id * m_rm - dv) * m_r_inv;
+ const float im = (m_id * m_rp + dv) * m_r_inv;
+ return im - ip;
+}
+
+void ca3280_vca_lin_device::device_start()
+{
+ va_vca_device::device_start();
+ save_item(NAME(m_rp));
+ save_item(NAME(m_rm));
+ save_item(NAME(m_r_inv));
+}
+
+void ca3280_vca_lin_device::device_reset()
+{
+ va_vca_device::device_reset();
+ if (m_rp <= 0 || m_rm <= 0 || m_r_inv <= 0)
+ fatalerror("%s requires both set_rplus() and set_rminus() to have been called.\n", tag());
}
float ca3280_vca_lin_device::i_d(float r, float v_r, float v_minus)
diff --git a/src/devices/sound/va_vca.h b/src/devices/sound/va_vca.h
index 2ae9dbe9073..3938fd0ee19 100644
--- a/src/devices/sound/va_vca.h
+++ b/src/devices/sound/va_vca.h
@@ -44,6 +44,9 @@ DECLARE_DEVICE_TYPE(CA3280_VCA_LIN, ca3280_vca_lin_device)
// (input 1), by a device in va_eg.h, for example. When the cv is provided via a
// stream, this is also a ring modulator.
//
+// This can also emulate a differential amplifier, by supplying a fixed or
+// streaming inverting input.
+//
// The CV for this device is simply the gain (output = gain * input).
class va_vca_device : public device_t, public device_sound_interface
{
@@ -52,6 +55,7 @@ public:
{
INPUT_AUDIO = 0,
INPUT_GAIN,
+ INPUT_AUDIO_INV,
};
va_vca_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0) ATTR_COLD;
@@ -60,12 +64,24 @@ public:
// each (sub)class documentation.
void set_fixed_gain_cv(float gain_cv);
+ // Sets the inverting (-) input to a fixed value. Defaults to 0.
+ void set_fixed_inv_input(float x);
+
+ // Applies input differencing. This happens before gain is applied.
+ // Subclasses can override diff() to apply additional processing such as
+ // prescaling and distortion.
+ // Public because it is useful for calibrating and debugging.
+ // p ~ non-inverting input ("plus").
+ // m ~ inverting input ("minus").
+ virtual float diff(float p, float m) const;
+
protected:
va_vca_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) ATTR_COLD;
+ void update();
+
virtual float cv_to_gain(float cv) const;
- virtual float distorted(float s) const;
- virtual bool has_distortion() const { return false; }
+ virtual u32 preferred_sample_rate() const;
void device_start() override ATTR_COLD;
void sound_stream_update(sound_stream &stream) override;
@@ -73,6 +89,7 @@ protected:
private:
sound_stream *m_stream;
float m_fixed_gain;
+ float m_fixed_inv_input;
};
@@ -84,27 +101,30 @@ private:
// considered linear for very small input voltages (~[-0.02 - 0.02]). See
// figure 3B in the datasheet.
//
-// ______ Iabc
-// | \ |
-// A --- Rin ---+--- B --- |+ \ |
-// | | \ |
-// Rgnd | \ |
-// | | OO---- C ----+
-// GND | / |
-// | / Rout
-// +--------- |- Id / |
-// | |___|__/ GND
-// Rgnd |
-// | |
-// GND V-
+// ______ Iabc
+// | \ |
+// A --- Rin+ ---+--- B --- |+ \ |
+// | | \ |
+// Rgnd+ | \ |
+// | | OO---- F ----+
+// GND | / |
+// | / Rout
+// [D -- Rin- --]+--- E --- |- Id / |
+// | |___|__/ GND
+// Rgnd- |
+// | |
+// GND V-
//
// The streaming input should either be the voltage at point B, or at point A,
// depending on how the object is configured.
//
+// An (optional) inverting input (fixed or streaming) is also supported. It
+// should be the voltage at point E, or at point D, depending on configuration.
+//
// The gain CV (fixed or streaming) should be the control current (Iabc), in
// Amperes.
//
-// The streaming output will be the output current at point C (the CA3280 is a
+// The streaming output will be the output current at point F (the CA3280 is a
// current source), but the object can be configured to output a voltage instead.
//
class ca3280_vca_device : public va_vca_device
@@ -112,22 +132,27 @@ class ca3280_vca_device : public va_vca_device
public:
ca3280_vca_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0) ATTR_COLD;
- // By default, the streaming input should be the voltage at point B.
- // This method will configure the input to be the voltage at point A.
- ca3280_vca_device &configure_input_divider(float r_in, float r_gnd) ATTR_COLD;
+ // By default, the inputs should be the voltages at points B and E.
+ // A common setup is to have voltage dividers at the input(s), which can be
+ // configured by these methods. If dividers are configured, the inputs
+ // should be the voltages at points A and D.
+ ca3280_vca_device &configure_plus_divider(float r_in, float r_gnd) ATTR_COLD;
+ ca3280_vca_device &configure_minus_divider(float r_in, float r_gnd) ATTR_COLD;
- // By default, the streaming output will be the current at point C. This
- // method will configure the output to be the voltage at point C.
+ // By default, the streaming output will be the current at point F. This
+ // method will configure the output to be the voltage at point F.
ca3280_vca_device &configure_voltage_output(float r_out) ATTR_COLD;
+ float diff(float p, float m) const override;
+
protected:
float cv_to_gain(float cv) const override;
- float distorted(float s) const override;
- bool has_distortion() const override { return true; }
+ u32 preferred_sample_rate() const override;
private:
// Configuration. Not needed in save state.
- float m_input_scale;
+ float m_plus_scale;
+ float m_minus_scale;
float m_output_scale;
};
@@ -136,36 +161,31 @@ private:
// to a current source, often a resistor to the positive supply.
//
// In this configuration, the CA3280 is linear for a wide voltage range, but it
-// will hard-clip when Iout approaches Iabc (TODO: clipping is not currently
+// will hard-clip when Iin approaches Id (TODO: clipping is not currently
// emulated). See figure 3A in the datasheet.
//
-// Note that, in contrast to the non-linearized configuration above, it is more
-// appropriate to treat the inputs as current inputs, rather than voltage inputs.
-//
// ______ Iabc
// | \ |
-// A --- Rin+ --- B --- |+ \ |
+// A ---- Rin+ ---- |+ \ |
// | \ |
// | \ |
-// | OO---- C ----+
+// | OO---- F ----+
// | / |
// | / Rout
-// GND --- Rin- --- |- Id / |
+// B ---- Rin- ---- |- Id / |
// |___|__/ GND
// |
// + ---- Rd --- V+
//
-// The streaming input should be the *current* at point B (the + input is at near
-// ground potential). But it can be configured to be the voltage at point A.
+// Usually, Rin+ = Rin- and B = GND. When that's the case:
+// In = V_A / Rin+ and Iout = In * 0.82 * Iabc / Id.
//
// The gain CV (fixed or streaming) should be the control current (Iabc), in
// Amperes.
//
-// The streaming output will be the output current at point C (the CA3280 is a
+// The streaming output will be the output current at point F (the CA3280 is a
// current source), but the object can be configured to output a voltage instead.
//
-// This implementation assumes Rin- == Rin+, which is typical.
-//
class ca3280_vca_lin_device : public va_vca_device
{
public:
@@ -182,27 +202,40 @@ public:
// Don't use.
ca3280_vca_lin_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) ATTR_COLD;
- // By default, the streaming input should be the *current* at point B.
- // This method will configure the input to be the voltage at point A.
- ca3280_vca_lin_device &configure_voltage_input(float r_in) ATTR_COLD;
-
- // By default, the streaming output will be the current at point C. This
- // method will configure the output to be the voltage at point C.
+ // By default, the streaming output will be the current at point F. This
+ // method will configure the output to be the voltage at point F.
ca3280_vca_lin_device &configure_voltage_output(float r_out) ATTR_COLD;
+ // These set Rin+ and Rin-. They must be called at least once and they
+ // can change at runtime.
+ // If the input voltage is applied though a resistor network, such as a
+ // trimming circuit, use the Thevenin-equivalent resistance. Remember to
+ // also feed the Thevenin-equivalent voltage to the input(s) in that case.
+ ca3280_vca_lin_device &set_rplus(float r);
+ ca3280_vca_lin_device &set_rminus(float r);
+
+ float diff(float p, float m) const override;
+
protected:
float cv_to_gain(float cv) const override;
+ void device_start() override ATTR_COLD;
+ void device_reset() override ATTR_COLD;
+
private:
void update_cv_scale();
static float i_d(float r, float v_r, float v_minus) ATTR_COLD;
// Configuration. Not needed in save state.
- const float m_i_d_inv;
- float m_input_scale;
+ const float m_id;
float m_output_scale;
float m_cv_scale;
+
+ // state
+ float m_rp;
+ float m_rm;
+ float m_r_inv;
};
#endif // MAME_SOUND_VA_VCA_H
diff --git a/src/mame/sequential/prophet5.cpp b/src/mame/sequential/prophet5.cpp
index d3eeb964749..eb238bfd81b 100644
--- a/src/mame/sequential/prophet5.cpp
+++ b/src/mame/sequential/prophet5.cpp
@@ -105,6 +105,32 @@ double normalized(const required_ioport &input)
return double(input->read()) / double(input->field(1)->maxval());
}
+// Returns the Thevenin-equivalent resistance (Req) and voltage (Veq) looking
+// from point X to the circuit on the left.
+//
+// +15V
+// |
+// Rtrim---Rwiper---+---X -----> Veq---Req---X
+// | |
+// | Rgnd
+// | |
+// -15V GND
+//
+// This circuit is used for balance and offset trimming on OTAs and op-amps.
+std::pair<double, double> balance_rveq(const required_ioport &trimmer, double r_wiper, double r_gnd)
+{
+ constexpr double TRIMMER_R_MAX = RES_K(100);
+ const double r_bottom = normalized(trimmer) * TRIMMER_R_MAX;
+ const double r_top = TRIMMER_R_MAX - r_bottom;
+
+ const double v_top = VPLUS * RES_VOLTAGE_DIVIDER(r_top, RES_2_PARALLEL(r_bottom, r_wiper + r_gnd));
+ const double v_bottom = VMINUS * RES_VOLTAGE_DIVIDER(r_bottom, RES_2_PARALLEL(r_top, r_wiper + r_gnd));
+ const double v_eq = (v_top + v_bottom) * RES_VOLTAGE_DIVIDER(r_wiper, r_gnd);
+ const double r_eq = RES_2_PARALLEL(r_gnd, r_wiper + RES_2_PARALLEL(r_top, r_bottom));
+
+ return std::pair<double, double>(r_eq, v_eq);
+}
+
// A voltage-to-current converter based on a PNP transistor. Converts control
// voltages to control currents for CA3280 OTAs.
//
@@ -222,10 +248,13 @@ public:
DECLARE_INPUT_CHANGED_MEMBER(filter_trimmer_changed) { update_filter_freq_calibration(); }
DECLARE_INPUT_CHANGED_MEMBER(volume_trimmer_changed) { m_volume_changed_cb(0); }
+ DECLARE_INPUT_CHANGED_MEMBER(voice_balance_trimmer_changed) { voice_update_balance_calibration(); }
const char *trimmer_name_volume() const ATTR_COLD { return m_volume_name.c_str(); }
const char *trimmer_name_filt_scale() const ATTR_COLD { return m_filt_scale_name.c_str(); }
const char *trimmer_name_filt_offset() const ATTR_COLD { return m_filt_offset_name.c_str(); }
+ const char *trimmer_name_filt_env_bal() const ATTR_COLD { return m_filt_env_bal_name.c_str(); }
+ const char *trimmer_name_vca_bal() const ATTR_COLD { return m_vca_bal_name.c_str(); }
protected:
void device_add_mconfig(machine_config &config) override ATTR_COLD;
@@ -237,10 +266,13 @@ protected:
private:
void update_filter_freq_calibration();
+ void voice_update_balance_calibration();
const std::string m_volume_name;
const std::string m_filt_scale_name;
const std::string m_filt_offset_name;
+ const std::string m_filt_env_bal_name;
+ const std::string m_vca_bal_name;
device_sound_interface *const m_filt_sum_cv;
device_sound_interface *const m_noise;
@@ -259,11 +291,13 @@ private:
required_device<va_scale_offset_device> m_filt_freq_offset;
required_device<cem3320_lpf4_device> m_vcf; // U469
- required_ioport m_volume; // R4529
devcb_write8 m_volume_changed_cb;
+ required_ioport m_volume; // R4529
required_ioport m_filt_scale; // R4133
required_ioport m_filt_offset; // R4501
+ required_ioport m_filt_env_bal; // R495
+ required_ioport m_vca_bal; // R4520
};
INPUT_PORTS_START(prophet5_voice_trimmers)
@@ -282,6 +316,14 @@ INPUT_PORTS_START(prophet5_voice_trimmers)
PORT_START("trimmer_filt_offset")
PORT_ADJUSTER(82, voice.trimmer_name_filt_offset())
PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_voice_device::filter_trimmer_changed), 0)
+
+ PORT_START("trimmer_filt_env_balance")
+ PORT_ADJUSTER(69, voice.trimmer_name_filt_env_bal())
+ PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_voice_device::voice_balance_trimmer_changed), 0);
+
+ PORT_START("trimmer_vca_balance")
+ PORT_ADJUSTER(64, voice.trimmer_name_vca_bal())
+ PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(prophet5_voice_device::voice_balance_trimmer_changed), 0);
INPUT_PORTS_END
} // anonymous namespace
@@ -299,6 +341,8 @@ prophet5_voice_device::prophet5_voice_device(
, m_volume_name(util::string_format("%s TRIMMER: VOLUME", strmakeupper(basetag())))
, m_filt_scale_name(util::string_format("%s TRIMMER: FILT SCALE", strmakeupper(basetag())))
, m_filt_offset_name(util::string_format("%s TRIMMER: FILT OFFSET", strmakeupper(basetag())))
+ , m_filt_env_bal_name(util::string_format("%s TRIMMER: FILT ENV BALANCE", strmakeupper(basetag())))
+ , m_vca_bal_name(util::string_format("%s TRIMMER: VCA BALANCE", strmakeupper(basetag())))
, m_filt_sum_cv(filt_sum_cv)
, m_noise(noise)
, m_stream(nullptr)
@@ -311,10 +355,12 @@ prophet5_voice_device::prophet5_voice_device(
, m_filt_freq_smr_lpf(*this, "filt_freq_summer_lpf")
, m_filt_freq_offset(*this, "filt_freq_offset")
, m_vcf(*this, "vcf")
- , m_volume(*this, "trimmer_volume")
, m_volume_changed_cb(*this)
+ , m_volume(*this, "trimmer_volume")
, m_filt_scale(*this, "trimmer_filt_scale")
, m_filt_offset(*this, "trimmer_filt_offset")
+ , m_filt_env_bal(*this, "trimmer_filt_env_balance")
+ , m_vca_bal(*this, "trimmer_vca_balance")
{
}
@@ -348,12 +394,13 @@ void prophet5_voice_device::device_add_mconfig(machine_config &config)
m_filt_sum_cv->add_route(0, m_filt_freq_smr, 1.0 / RES_K(100)); // R4143 (1%)
// The filter has its own ADSR envelope generator. Its voltage output is fed
- // to a VCA, which controls the envelope amount applied to the filter's
- // cutoff frequency. The output of the VCA is a current.
+ // to a VCA ("filt env amt"), which controls the envelope amount applied to
+ // the filter's cutoff frequency. The output of the VCA is a current.
CEM3310(config, m_filt_eg, RES_K(24.3), CAP_U(0.039)) // U417, R440 (1%), C443 (5%)
.add_route(0, m_filt_eg_vca, 1.0);
CA3280_VCA_LIN(config, m_filt_eg_vca, RES_K(121), VPLUS, VMINUS) // U422B, R451 (1%)
- .configure_voltage_input(RES_K(47.5)) // R452 (1%)
+ .set_rplus(RES_K(47.5)) // R452 (1%)
+ // rminus is set in voice_update_balance_calibration().
.add_route(0, m_filt_freq_smr, 1.0);
// TODO: Emulate the "polymod" filter modulation source.
@@ -385,7 +432,8 @@ void prophet5_voice_device::device_add_mconfig(machine_config &config)
.add_route(0, m_vca, VCF_OUT_GAIN);
CA3280_VCA_LIN(config, m_vca, RES_K(68), VPLUS, VMINUS) // R4546
- .configure_voltage_input(RES_K(20)) // R4548
+ .set_rplus(RES_K(20)) // R4548
+ // r_minus is set in voice_update_balance_calibration().
.add_route(0, *this, 1.0);
}
@@ -402,6 +450,7 @@ void prophet5_voice_device::device_start()
void prophet5_voice_device::device_reset()
{
update_filter_freq_calibration();
+ voice_update_balance_calibration();
}
void prophet5_voice_device::sound_stream_update(sound_stream &stream)
@@ -462,6 +511,23 @@ void prophet5_voice_device::update_filter_freq_calibration()
LOGMASKED(LOG_CALIBRATION | LOG_FILTER, "%s: Filter frequency: %f\n", tag(), m_vcf->get_freq());
}
+void prophet5_voice_device::voice_update_balance_calibration()
+{
+ std::pair<double, double> rv_equiv;
+
+ rv_equiv = balance_rveq(m_filt_env_bal, RES_K(475), RES_K(47.5)); // R453 (1%), R450 (1%)
+ m_filt_eg_vca->set_rminus(rv_equiv.first);
+ m_filt_eg_vca->set_fixed_inv_input(rv_equiv.second);
+ LOGMASKED(LOG_CALIBRATION, "%s: Filt env balance R: %f, V-: %f - Idiff: %e\n",
+ tag(), rv_equiv.first, rv_equiv.second, m_filt_eg_vca->diff(0, rv_equiv.second));
+
+ rv_equiv = balance_rveq(m_vca_bal, RES_K(200), RES_K(20)); // R4536, R4547
+ m_vca->set_rminus(rv_equiv.first);
+ m_vca->set_fixed_inv_input(rv_equiv.second);
+ LOGMASKED(LOG_CALIBRATION, "%s: VCA balance R: %f, V-: %f - Idiff: %e\n",
+ tag(), rv_equiv.first, rv_equiv.second, m_vca->diff(0, rv_equiv.second));
+}
+
namespace {
@@ -487,6 +553,7 @@ public:
DECLARE_INPUT_CHANGED_MEMBER(mod_wheel_changed) { update_wmod_amount(); }
DECLARE_INPUT_CHANGED_MEMBER(filter_cv_in_changed) { update_filter_fixed_cvs(); }
DECLARE_INPUT_CHANGED_MEMBER(master_volume_changed) { update_master_volume(); }
+ DECLARE_INPUT_CHANGED_MEMBER(balance_trimmer_changed) { update_balance_calibration(); }
protected:
void device_add_mconfig(machine_config &config) override ATTR_COLD;
@@ -506,6 +573,7 @@ private:
void update_filter_fixed_cvs();
void update_voice_volume();
void update_master_volume();
+ void update_balance_calibration();
required_device<va_ota_eg_device> m_glide_eg; // U381A (CA3280) + surrounding components.
required_device<ca3280_vca_device> m_mod_noise_vca; // U378B
@@ -529,6 +597,7 @@ private:
required_ioport m_amp_cv_in; // J7
required_ioport m_filter_cv_in; // J6
required_ioport m_mod_wheel; // R2
+ required_ioport m_lfo_bal; // R3141
bool m_lfo_ramp_s; // U377A pin 13
bool m_lfo_sqr_s; // U377B pin 5
@@ -588,6 +657,7 @@ prophet5_audio_device::prophet5_audio_device(const machine_config &mconfig, cons
, m_amp_cv_in(*this, ":cv_in_amp")
, m_filter_cv_in(*this, ":cv_in_filter")
, m_mod_wheel(*this, ":wheel_mod")
+ , m_lfo_bal(*this, ":trimmer_lfo_balance")
, m_lfo_ramp_s(false)
, m_lfo_sqr_s(false)
, m_lfo_tri_s(false)
@@ -639,7 +709,7 @@ void prophet5_audio_device::device_add_mconfig(machine_config &config)
.set_lowpass(R393, CAP_U(0.01)) // C372
.add_route(0, m_mod_noise_vca, 1.0);
CA3280_VCA(config, m_mod_noise_vca) // U378B
- .configure_input_divider(RES_K(20), RES_R(330)) // R3119, R3120
+ .configure_plus_divider(RES_K(20), RES_R(330)) // R3119, R3120
.add_route(0, m_wmod, 1.0);
// LFO modulation source.
@@ -705,7 +775,7 @@ void prophet5_audio_device::device_add_mconfig(machine_config &config)
.set_rc(filter_rc_device::HIGHPASS, R4131 + R4132, 0, 0, CAP_U(0.1)) // C458
.add_route(0, m_noise_vca, 1.0);
CA3280_VCA(config, m_noise_vca)
- .configure_input_divider(R4131, R4132) // Scaled amplitude: +/- ~0.36V.
+ .configure_plus_divider(R4131, R4132) // Scaled amplitude: +/- ~0.36V.
.configure_voltage_output(RES_K(10)); // R4129
// The 5 voices. The output of each voice device is a current, but it is
@@ -745,8 +815,9 @@ void prophet5_audio_device::device_add_mconfig(machine_config &config)
// the master volume knob and, if connected, the external AMPLIFIER CV IN
// input (J7). See update_master_volume().
CA3280_VCA_LIN(config, m_master_vol_vca, RES_K(68), VPLUS, VMINUS) // R4561
- .configure_voltage_input(RES_K(15)) // R4564
.configure_voltage_output(RES_K(20)) // R4562
+ .set_rplus(RES_K(15)) // R4564
+ .set_rminus(RES_K(15)) // R4563 - from parts list. Value in the schematic is unclear.
.add_route(0, "dcblock", 1.0);
// Output stage.
@@ -778,6 +849,7 @@ void prophet5_audio_device::device_reset()
update_filter_fixed_cvs();
update_voice_volume();
update_master_volume();
+ update_balance_calibration();
}
void prophet5_audio_device::select_a440_w(int state)
@@ -1329,6 +1401,14 @@ void prophet5_audio_device::update_master_volume()
volume, cv_in_connected, max_vol_cv, vol_cv, vol_cc);
}
+void prophet5_audio_device::update_balance_calibration()
+{
+ const std::pair<double, double> rv_equiv = balance_rveq(m_lfo_bal, RES_M(2.2), RES_R(330)); // R3151, R3152
+ m_lfo_vca->set_fixed_inv_input(rv_equiv.second);
+ LOGMASKED(LOG_CALIBRATION, "LFO Balance V-: %f, Vdiff: %f\n",
+ rv_equiv.second, m_lfo_vca->diff(0, rv_equiv.second));
+}
+
namespace {
@@ -2261,6 +2341,10 @@ INPUT_PORTS_START(prophet5)
// An input of 0V has the same effect as the input not being connected.
PORT_ADJUSTER(0, "CV IN: FILTER")
PORT_CHANGED_MEMBER(AUDIO_TAG, FUNC(prophet5_audio_device::filter_cv_in_changed), 0)
+
+ PORT_START("trimmer_lfo_balance")
+ PORT_ADJUSTER(50, "TRIMMER: LFO BALANCE")
+ PORT_CHANGED_MEMBER(AUDIO_TAG, FUNC(prophet5_audio_device::balance_trimmer_changed), 0)
INPUT_PORTS_END
ROM_START(prophet5rev30)