diff options
Diffstat (limited to 'src/devices/sound/va_eg.cpp')
| -rw-r--r-- | src/devices/sound/va_eg.cpp | 188 |
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") |
