summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices
diff options
context:
space:
mode:
author Aaron Giles <aaron@aarongiles.com>2020-09-19 02:47:47 -0700
committer Aaron Giles <aaron@aarongiles.com>2020-09-19 02:47:47 -0700
commit3dd50371d4385a8e9cebb4cdc38501f8079c713b (patch)
tree8f02dfba429881f71f4fa216766815530492355f /src/devices
parent0d0175f59f8f68f09f4563aaab6a1836e2aa5eff (diff)
cem3394: switch to alternate filter that is more stable
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/sound/cem3394.cpp129
1 files changed, 109 insertions, 20 deletions
diff --git a/src/devices/sound/cem3394.cpp b/src/devices/sound/cem3394.cpp
index 5b288d2d700..b9f41cefad2 100644
--- a/src/devices/sound/cem3394.cpp
+++ b/src/devices/sound/cem3394.cpp
@@ -4,12 +4,7 @@
Curtis Electromusic Specialties CEM3394 µP-Controllable Synthesizer Voice
- This driver handles CEM-3394 analog synth chip. Very crudely.
-
- Still to do:
- - adjust the overall volume when multiple waves are being generated
- - filter internal sound
- - support resonance (don't understand how it works)
+ This driver handles CEM-3394 analog synth chip.
***************************************************************************/
@@ -19,6 +14,18 @@
#include <algorithm>
+// various filter implementations to play with; currently SVTRAP works best
+#define FILTER_TYPE_NONE (0)
+#define FILTER_TYPE_SVTRAP (1)
+#define FILTER_TYPE_ESQ1 (2)
+
+#define FILTER_TYPE FILTER_TYPE_SVTRAP
+
+
+// logging
+#define LOG_CONTROL_CHANGES (0)
+
+
// use 0.25 as the base volume for pulses
static constexpr double PULSE_VOLUME = 0.25;
@@ -147,33 +154,91 @@ cem3394_device::cem3394_device(const machine_config &mconfig, const char *tag, d
//-------------------------------------------------
-// sound_stream_update - generate sound to the mix
-// buffer in mono
+// filter - apply the lowpass filter at the given
+// cutoff frequency
//-------------------------------------------------
+#if (FILTER_TYPE == FILTER_TYPE_NONE)
+
+double cem3394_device::filter(double input, double cutoff)
+{
+ return input;
+}
+
+#elif (FILTER_TYPE == FILTER_TYPE_SVTRAP)
+
+double cem3394_device::filter(double input, double cutoff)
+{
+ // clamp cutoff to useful range, 50Hz-20kHz
+ cutoff = std::min(std::max(cutoff, 50.0), 20000.0);
+
+ // clamp resonance to below 1.0 to prevent runaway behavior; when clamping,
+ // also apply an (arbitrary) scale factor to the output since we're close
+ // to resonance and the datasheet indicates there is an amplitude correction
+ // in this case
+ double outscale = 1.0;
+ double res = m_filter_resonance;
+ if (res > 0.99)
+ res = 0.99, outscale = 0.5;
+
+ // core filter implementation
+ double g = tan(M_PI * cutoff * m_inv_sample_rate);
+ double k = 2.0 - 2.0 * res;
+ double a1 = 1.0 / (1.0 + g * (g + k));
+ double a2 = g * a1;
+ double a3 = g * a2;
+ double v3 = input - m_filter_out[1];
+ double v1 = a1 * m_filter_out[0] + a2 * v3;
+ double v2 = m_filter_out[1] + a2 * m_filter_out[0] + a3 * v3;
+ m_filter_out[0] = 2 * v1 - m_filter_out[0];
+ m_filter_out[1] = 2 * v2 - m_filter_out[1];
+
+ // lowpass output is equal to v2
+ double output = v2 * outscale;
+
+ // catch any NaNs
+ if (std::isnan(output))
+ {
+ logerror("NAN - vco: %6.0f cutoff: %6.0f res: %.5f output: %.5f\n", m_vco_step / m_inv_sample_rate, cutoff, m_filter_resonance, output);
+ output = 0;
+ m_filter_out[0] = m_filter_out[1] = 0;
+ }
+
+ // if we go out of range, scale down to 1.0 and also scale our
+ // feedback terms to help us stay in control
+ else if (fabs(output) > 1.0)
+ {
+ double scale = 1.0 / fabs(output);
+ output *= scale;
+ m_filter_out[0] *= scale;
+ m_filter_out[1] *= scale;
+ }
+ return output;
+}
+
+#elif (FILTER_TYPE == FILTER_TYPE_ESQ1)
+
double cem3394_device::filter(double input, double cutoff)
{
- // clamp cutoff to useful range, 20Hz-20kHz
- cutoff = std::max(std::min(cutoff, 20000.0), 20.0);
+ // clamp cutoff to useful range, 50Hz-20kHz
+ cutoff = std::min(std::max(cutoff, 50.0), 20000.0);
// clamp resonance to 0.95 to prevent infinite gain
- double r = 4.0 * std::min(m_filter_resonance, 0.95);
+ double r = 4.0 * std::min(res, 0.95);
+ // core filter implementation
double g = 2 * M_PI * cutoff;
double zc = g / tan(g/2 * m_inv_sample_rate);
-
double gzc = zc / g;
double gzc2 = gzc * gzc;
double gzc3 = gzc2 * gzc;
double gzc4 = gzc3 * gzc;
double r1 = 1 + r;
-
double a0 = r1;
double a1 = 4 * r1;
double a2 = 6 * r1;
double a3 = 4 * r1;
double a4 = r1;
-
double b0 = r1 + 4 * gzc + 6 * gzc2 + 4 * gzc3 + gzc4;
double b1 = 4 * (r1 + 2 * gzc - 2 * gzc3 - gzc4);
double b2 = 6 * (r1 - 2 * gzc2 + gzc4);
@@ -184,15 +249,18 @@ double cem3394_device::filter(double input, double cutoff)
+ m_filter_in[0] * a1 + m_filter_in[1] * a2 + m_filter_in[2] * a3 + m_filter_in[3] * a4
- m_filter_out[0] * b1 - m_filter_out[1] * b2 - m_filter_out[2] * b3 - m_filter_out[3] * b4) / b0;
-// sound_assert(!std::isnan(output));
-// sound_assert(output >= -1.5 && output <= 1.5);
- if (output < -1.5 || output > 1.5 || std::isnan(output))
+ // catch NaNs
+ if (std::isnan(output))
{
- if (m_filter_out[0] > -1.5 && m_filter_out[0] < 1.5)
- printf("cutoff: %6.0f res: %.5f output: %.5f\n", cutoff, m_filter_resonance, output);
+ logerror("NAN - vco: %6.0f cutoff: %6.0f res: %.5f output: %.5f\n", m_vco_step / m_inv_sample_rate, cutoff, m_filter_resonance, output);
+ output = 0;
}
- if (std::isnan(output)) output = 0;
+ // if output goes significantly out of range, scale it down
+ else if (fabs(output) > 10.0)
+ output = 10.0;
+
+ // update memories
m_filter_in[3] = m_filter_in[2];
m_filter_in[2] = m_filter_in[1];
m_filter_in[1] = m_filter_in[0];
@@ -203,6 +271,7 @@ double cem3394_device::filter(double input, double cutoff)
m_filter_out[1] = m_filter_out[0];
m_filter_out[0] = output;
+ // clamp to range and return
if (output < -1.0)
output = -1.0;
else if (output > 1.0)
@@ -210,6 +279,18 @@ double cem3394_device::filter(double input, double cutoff)
return output;
}
+#else
+
+#error Unknown FILTER_TYPE
+
+#endif
+
+
+//-------------------------------------------------
+// sound_stream_update - generate sound to the mix
+// buffer in mono
+//-------------------------------------------------
+
void cem3394_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
{
auto &external = inputs[0];
@@ -370,6 +451,7 @@ void cem3394_device::set_voltage(int input, double voltage)
case VCO_FREQUENCY:
temp = m_vco_zero_freq * pow(2.0, -voltage * (1.0 / 0.75));
m_vco_step = temp * m_inv_sample_rate;
+ if (LOG_CONTROL_CHANGES) logerror("VCO_FREQ=%6.3fV -> freq=%f\n", voltage, temp);
break;
// wave select determines triangle/sawtooth enable
@@ -381,6 +463,7 @@ void cem3394_device::set_voltage(int input, double voltage)
m_wave_select |= WAVE_TRIANGLE | WAVE_SAWTOOTH;
else if (voltage >= 2.3 && voltage <= 3.9)
m_wave_select |= WAVE_SAWTOOTH;
+ if (LOG_CONTROL_CHANGES) logerror("WAVE_SEL=%6.3fV -> tri=%d saw=%d\n", voltage, (m_wave_select & WAVE_TRIANGLE) ? 1 : 0, (m_wave_select & WAVE_SAWTOOTH) ? 1 : 0);
break;
// pulse width determines duty cycle; 0.0 means 0%, 2.0 means 100%
@@ -397,11 +480,13 @@ void cem3394_device::set_voltage(int input, double voltage)
m_pulse_width = MINIMUM_WIDTH + (MAXIMUM_WIDTH - MINIMUM_WIDTH) * m_pulse_width;
m_wave_select |= WAVE_PULSE;
}
+ if (LOG_CONTROL_CHANGES) logerror("PULSE_WI=%6.3fV -> raw=%f adj=%f\n", voltage, voltage * 0.5, m_pulse_width);
break;
// final gain is pretty self-explanatory; 0.0 means ~90dB, 4.0 means 0dB
case FINAL_GAIN:
m_volume = compute_db_volume(voltage);
+ if (LOG_CONTROL_CHANGES) logerror("TOT_GAIN=%6.3fV -> vol=%f\n", voltage, m_volume);
break;
// mixer balance is a pan between the external input and the internal input
@@ -417,11 +502,13 @@ void cem3394_device::set_voltage(int input, double voltage)
m_mixer_internal = compute_db_volume(3.55 - 0.45 * (voltage * 0.25));
m_mixer_external = compute_db_volume(3.55 + voltage);
}
+ if (LOG_CONTROL_CHANGES) logerror(" BALANCE=%6.3fV -> int=%f ext=%f\n", voltage, m_mixer_internal, m_mixer_external);
break;
// filter frequency varies from -3.0 to +4.0, at 0.375V/octave
case FILTER_FREQENCY:
m_filter_frequency = m_filter_zero_freq * pow(2.0, -voltage * (1.0 / 0.375));
+ if (LOG_CONTROL_CHANGES) logerror("FLT_FREQ=%6.3fV -> freq=%f\n", voltage, m_filter_frequency);
break;
// modulation depth is 0.01*freq at 0V and 2.0*freq at 3.5V
@@ -432,6 +519,7 @@ void cem3394_device::set_voltage(int input, double voltage)
m_filter_modulation = 1.99;
else
m_filter_modulation = (voltage * (1.0 / 3.5)) * 1.98 + 0.01;
+ if (LOG_CONTROL_CHANGES) logerror("FLT_MODU=%6.3fV -> mod=%f\n", voltage, m_filter_modulation);
break;
// this is not yet implemented
@@ -442,6 +530,7 @@ void cem3394_device::set_voltage(int input, double voltage)
m_filter_resonance = 1.0;
else
m_filter_resonance = voltage * (1.0 / 2.5);
+ if (LOG_CONTROL_CHANGES) logerror("FLT_RESO=%6.3fV -> mod=%f\n", voltage, m_filter_resonance);
break;
}
}