summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/va_eg.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/va_eg.cpp')
-rw-r--r--src/devices/sound/va_eg.cpp188
1 files changed, 186 insertions, 2 deletions
diff --git a/src/devices/sound/va_eg.cpp b/src/devices/sound/va_eg.cpp
index 009d739bd0a..70e09efdea5 100644
--- a/src/devices/sound/va_eg.cpp
+++ b/src/devices/sound/va_eg.cpp
@@ -8,7 +8,6 @@
// The envelope is considered completed after this many time constants.
static constexpr const float TIME_CONSTANTS_TO_END = 10;
-DEFINE_DEVICE_TYPE(VA_RC_EG, va_rc_eg_device, "va_rc_eg", "RC-based Envelope Generator")
va_rc_eg_device::va_rc_eg_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, VA_RC_EG, tag, owner, clock)
@@ -130,7 +129,7 @@ void va_rc_eg_device::sound_stream_update(sound_stream &stream)
assert(stream.input_count() == 0 && stream.output_count() == 1);
attotime t = stream.start_time();
- if (t >= m_t_end_approx)
+ if (converged(t))
{
// Avoid expensive get_v() calls if the envelope stage has completed.
stream.fill(0, m_v_end);
@@ -158,3 +157,188 @@ void va_rc_eg_device::snapshot()
}
m_t_end_approx = m_t_start + attotime::from_double(TIME_CONSTANTS_TO_END * m_r * m_c);
}
+
+
+cem3310_device::cem3310_device(const machine_config &mconfig, const char *tag, device_t *owner, float rx, float cx)
+ : device_t(mconfig, CEM3310, tag, owner, 0)
+ , device_sound_interface(mconfig, *this)
+ , m_rx(rx)
+ , m_cx(cx)
+ , m_stream(nullptr)
+ , m_attack_timer(nullptr)
+ , m_rc(*this, "rc")
+ , m_phase(PHASE_RELEASE)
+ , m_gate(false)
+ , m_attack_cv(0)
+ , m_decay_cv(0)
+ , m_sustain_cv(0)
+ , m_release_cv(0)
+{
+}
+
+cem3310_device::cem3310_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : cem3310_device(mconfig, tag, owner, RES_K(24), CAP_U(0.039)) // Example values in the datasheet.
+{
+}
+
+void cem3310_device::attack_w(float cv)
+{
+ if (cv == m_attack_cv)
+ return;
+ m_stream->update();
+ m_attack_cv = cv;
+ update_rc();
+}
+
+void cem3310_device::decay_w(float cv)
+{
+ if (cv == m_decay_cv)
+ return;
+ m_stream->update();
+ m_decay_cv = cv;
+ update_rc();
+}
+
+void cem3310_device::sustain_w(float cv)
+{
+ if (cv == m_sustain_cv)
+ return;
+ m_stream->update();
+ m_sustain_cv = cv;
+ update_rc();
+}
+
+void cem3310_device::release_w(float cv)
+{
+ if (cv == m_release_cv)
+ return;
+ m_stream->update();
+ m_release_cv = cv;
+ update_rc();
+}
+
+void cem3310_device::gate_w(int state)
+{
+ const bool gate = bool(state);
+ if (gate == m_gate)
+ return;
+
+ m_stream->update();
+ if (!m_gate && gate)
+ m_phase = PHASE_ATTACK;
+ else
+ m_phase = PHASE_RELEASE;
+ m_gate = gate;
+ update_rc();
+}
+
+void cem3310_device::device_add_mconfig(machine_config &config)
+{
+ VA_RC_EG(config, m_rc).set_c(m_cx);
+}
+
+void cem3310_device::device_start()
+{
+ m_stream = stream_alloc(0, 1, SAMPLE_RATE_OUTPUT_ADAPTIVE);
+ m_attack_timer = timer_alloc(FUNC(cem3310_device::attack_timer_tick), this);
+
+ save_item(NAME(m_phase));
+ save_item(NAME(m_gate));
+ save_item(NAME(m_attack_cv));
+ save_item(NAME(m_decay_cv));
+ save_item(NAME(m_sustain_cv));
+ save_item(NAME(m_release_cv));
+}
+
+void cem3310_device::device_reset()
+{
+ update_rc();
+}
+
+void cem3310_device::sound_stream_update(sound_stream &stream)
+{
+ attotime t = stream.start_time();
+ if (m_rc->converged(t))
+ {
+ // Avoid repeated calls to get_v() if the envelope stage has completed.
+ stream.fill(0, m_rc->get_v(t));
+ return;
+ }
+
+ const int n = stream.samples();
+ const attotime dt = stream.sample_period();
+ for (int i = 0; i < n; ++i, t += dt)
+ stream.put(0, i, m_rc->get_v(t));
+}
+
+void cem3310_device::update_rc()
+{
+ float target_v = 0;
+ float rate_cv = 0;
+ switch (m_phase)
+ {
+ case PHASE_ATTACK:
+ target_v = VZ;
+ rate_cv = m_attack_cv;
+ break;
+ case PHASE_DECAY:
+ target_v = m_sustain_cv;
+ rate_cv = m_decay_cv;
+ break;
+ case PHASE_RELEASE:
+ target_v = 0;
+ rate_cv = m_release_cv;
+ break;
+ default:
+ fatalerror("%s: Unrecognized EG phase\n", tag());
+ }
+
+ // According to the datasheet, the equivalent RC constant is:
+ // RC = Rx * Cx * exp(-VC / VT)
+ // where VC is the rate CV of the current EG phase, and VT is the thermal voltage.
+ // m_rc is configured with Cx as its capacitor. So to achieve the required
+ // time constant, we set its resistor to:
+ // R = Rx * exp(-VC / VT)
+ m_rc->set_r(m_rx * expf(-rate_cv / VT));
+ m_rc->set_target_v(target_v);
+
+ // If in the attack phase, set up a timer to switch to the decay phase once
+ // peak voltage is reached.
+ m_attack_timer->reset();
+ if (m_phase == PHASE_ATTACK)
+ {
+ const attotime dt = m_rc->get_dt(VP);
+ if (!dt.is_never())
+ {
+ m_attack_timer->adjust(dt);
+ }
+ else
+ {
+ // Extremely unlikely. Requires update_rc() getting called at
+ // exactly the time when the attack phase ends. Using 'assert' to
+ // ensure this gets noticed in debug builds, as it likely points to
+ // a bug.
+ assert(false);
+ logerror("Voltage (%f) greater than 5V during attack phase.\n", m_rc->get_v());
+ attack_timer_tick(0); // Enter decay phase immediately.
+ }
+ }
+}
+
+TIMER_CALLBACK_MEMBER(cem3310_device::attack_timer_tick)
+{
+ assert(m_phase == PHASE_ATTACK);
+ if (m_phase != PHASE_ATTACK)
+ {
+ logerror("Attack timer elapsed when not in attack phase.\n");
+ return;
+ }
+
+ m_stream->update();
+ m_phase = PHASE_DECAY;
+ update_rc();
+}
+
+
+DEFINE_DEVICE_TYPE(VA_RC_EG, va_rc_eg_device, "va_rc_eg", "RC-based Envelope Generator")
+DEFINE_DEVICE_TYPE(CEM3310, cem3310_device, "cem3310", "CEM3310 Envelope Generator")