summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author m1macrophage <168948267+m1macrophage@users.noreply.github.com>2025-04-10 02:04:35 -0700
committer GitHub <noreply@github.com>2025-04-10 11:04:35 +0200
commite0ea6961f75e06e2fd84ae1ebbb0305a52dda206 (patch)
tree159d76d55993c896e8515d4a6e284d49faf0f724
parent6bc01c176c519c870aaa3da8f4b370bad63ca9ab (diff)
sound/dac76.cpp: Emulating multiplying capability. (#13577)
* Added support for reference current (multiplying capability). * Added support for voltage output. * Added support for streaming reference current. * Used those capabilities in oberheim/dmx.cpp and linn/linndrum.cpp.
-rw-r--r--src/devices/sound/dac76.cpp53
-rw-r--r--src/devices/sound/dac76.h42
-rw-r--r--src/mame/linn/linndrum.cpp45
-rw-r--r--src/mame/oberheim/dmx.cpp20
4 files changed, 112 insertions, 48 deletions
diff --git a/src/devices/sound/dac76.cpp b/src/devices/sound/dac76.cpp
index 1c73d7b02c1..6e2065dd2cc 100644
--- a/src/devices/sound/dac76.cpp
+++ b/src/devices/sound/dac76.cpp
@@ -38,12 +38,38 @@ dac76_device::dac76_device(const machine_config &mconfig, const char *tag, devic
device_t(mconfig, DAC76, tag, owner, clock),
device_sound_interface(mconfig, *this),
m_stream(nullptr),
+ m_streaming_iref(false),
+ m_voltage_output(false),
+ m_r_pos(1.0F),
+ m_r_neg(1.0F),
m_chord(0),
m_step(0),
- m_sb(false)
+ m_sb(false),
+ m_fixed_iref(1.0F)
{
}
+void dac76_device::configure_streaming_iref(bool streaming_iref)
+{
+ m_streaming_iref = streaming_iref;
+}
+
+void dac76_device::configure_voltage_output(float i2v_r_pos, float i2v_r_neg)
+{
+ m_voltage_output = true;
+ m_r_pos = i2v_r_pos;
+ m_r_neg = i2v_r_neg;
+}
+
+void dac76_device::set_fixed_iref(float iref)
+{
+ if (m_fixed_iref == iref)
+ return;
+ if (m_stream != nullptr)
+ m_stream->update();
+ m_fixed_iref = iref;
+}
+
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
@@ -51,12 +77,14 @@ dac76_device::dac76_device(const machine_config &mconfig, const char *tag, devic
void dac76_device::device_start()
{
// create sound stream
- m_stream = stream_alloc(0, 1, machine().sample_rate() * 8);
+ const int input_count = m_streaming_iref ? 1 : 0;
+ m_stream = stream_alloc(input_count, 1, machine().sample_rate() * 8);
// register for save states
save_item(NAME(m_chord));
save_item(NAME(m_step));
save_item(NAME(m_sb));
+ save_item(NAME(m_fixed_iref));
}
//-------------------------------------------------
@@ -85,5 +113,24 @@ void dac76_device::sound_stream_update(sound_stream &stream, std::vector<read_st
vout *= (m_sb ? +1 : -1);
// range is 0-8031, normalize to 0-1 range
- outputs[0].fill(stream_buffer::sample_t(vout) * (1.0 / 8031.0));
+ stream_buffer::sample_t y = stream_buffer::sample_t(vout) * (1.0 / 8031.0);
+
+ if (m_voltage_output)
+ {
+ static constexpr const float FULL_SCALE_MULT = 3.8F; // From datasheet.
+ y *= ((y >= 0) ? m_r_pos : m_r_neg) * FULL_SCALE_MULT;
+ }
+
+ write_stream_view &out = outputs[0];
+ if (m_streaming_iref)
+ {
+ const read_stream_view &iref = inputs[0];
+ const int n = out.samples();
+ for (int i = 0; i < n; ++i)
+ out.put(i, iref.get(i) * y);
+ }
+ else
+ {
+ out.fill(m_fixed_iref * y);
+ }
}
diff --git a/src/devices/sound/dac76.h b/src/devices/sound/dac76.h
index a59901a10dc..462355b0b8f 100644
--- a/src/devices/sound/dac76.h
+++ b/src/devices/sound/dac76.h
@@ -4,7 +4,7 @@
PMI DAC-76 COMDAC
- Companding D/A Converter
+ Companding Multiplying D/A Converter
Equivalent to the AM6070, which is an "improved pin-for-pin replacement for
DAC-76" (according to the AM6070 datasheet).
@@ -20,6 +20,19 @@
B6 8 | | 11 VR+
B7 9 |_______| 10 VLC
+ Given:
+ - Iref = current flowing into VR+
+ - X = DAC value, normalized to [-1, 1]
+ - E/D (pin 1) is low
+
+ The output will be:
+ - IOD+ = (X > 0) ? (3.8 * Iref * abs(X)) : 0
+ - IOD- = (X < 0) ? (3.8 * Iref * abs(X)) : 0
+
+ The outputs are typically converted to voltages and summed into a single
+ signal by a current-to-voltage converter (I2V) consisting of an op-amp and
+ two resistors.
+
***************************************************************************/
#ifndef MAME_SOUND_DAC76_H
@@ -39,6 +52,25 @@ public:
// construction/destruction
dac76_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+ // When streaming is enabled, the Iref will be obtained from the input sound
+ // stream. Otherwise, the value set with `set_fixed_iref` will be used.
+ void configure_streaming_iref(bool streaming_iref);
+
+ // By default, the control current (Iref) is treated as normalized ([0, 1],
+ // defaults to 1), and the sound output is normalized to [-1, 1].
+ //
+ // When in "voltage output" mode, Iref (fixed or streaming) should be the
+ // current flowing into pin 11, and the output will be a voltage stream.
+ // `r_pos` is the feedback resistor of the I2V, which is also connected to
+ // IOD+ (pin 16).
+ // `r_neg` is the resistor to ground connected to IOD- (pin 17).
+ // Note that r_pos connects to the "-" input of the I2V op-amp, and r_neg to
+ // the "+" input.
+ void configure_voltage_output(float i2v_r_pos, float i2v_r_neg);
+
+ // Reference current. Ignored when streaming Iref mode is enabled.
+ void set_fixed_iref(float iref);
+
// chord
void b1_w(int state) { m_chord &= ~(1 << 2); m_chord |= (state << 2); }
void b2_w(int state) { m_chord &= ~(1 << 1); m_chord |= (state << 1); }
@@ -67,9 +99,17 @@ private:
sound_stream *m_stream;
+ // configuration
+ bool m_streaming_iref;
+ bool m_voltage_output;
+ float m_r_pos;
+ float m_r_neg;
+
+ // state
uint8_t m_chord; // 3-bit
uint8_t m_step; // 4-bit
bool m_sb;
+ float m_fixed_iref;
};
// device type definition
diff --git a/src/mame/linn/linndrum.cpp b/src/mame/linn/linndrum.cpp
index a3d848db99f..e4385a820e5 100644
--- a/src/mame/linn/linndrum.cpp
+++ b/src/mame/linn/linndrum.cpp
@@ -209,7 +209,6 @@ protected:
private:
static void write_dac(dac76_device& dac, u8 sample);
- static float get_dac_scaler(float iref);
static s32 get_ls267_freq(const std::array<s32, 2>& freq_range_hz, float cv);
static float get_snare_tom_pitch_cv(float v);
@@ -243,7 +242,6 @@ private:
required_memory_region m_sidestick_samples; // 2732 ROMs (U78).
required_device<timer_device> m_snare_timer; // 74L627 (U80A).
required_device<dac76_device> m_snare_dac; // AM6070 (U92).
- required_device<filter_volume_device> m_snare_volume; // R69, R72, R71, R70.
required_device<filter_volume_device> m_snare_out; // U90A (CD4053) pin 12 (ax).
required_device<filter_volume_device> m_sidestick_out; // U90A (CD4053) pin 13 (ay).
required_device<timer_device> m_click_timer; // 556 (U65A).
@@ -305,6 +303,9 @@ private:
static constexpr const float VCC = 5; // Volts.
static constexpr const float MUX_DAC_IREF = VPLUS / (RES_K(15) + RES_K(15)); // R55 + R57.
static constexpr const float TOM_DAC_IREF = MUX_DAC_IREF; // Configured in the same way.
+ // All DAC current-to-voltage converter resistors, for both positive and
+ // negative values, are 2.49 KOhm.
+ static constexpr const float R_DAC_I2V = RES_K(2.49); // R58, R59, R127, R126, tom DAC I2V (missing designation).
// Constants for hi hat envelope generator circuit.
static constexpr const float HAT_C22 = CAP_U(1);
@@ -361,7 +362,6 @@ linndrum_audio_device::linndrum_audio_device(const machine_config &mconfig, cons
, m_sidestick_samples(*this, ":sample_sidestick")
, m_snare_timer(*this, "snare_sidestick_timer")
, m_snare_dac(*this, "snare_sidestick_dac")
- , m_snare_volume(*this, "snare_sidestick_volume")
, m_snare_out(*this, "snare_output")
, m_sidestick_out(*this, "sidestick_output")
, m_click_timer(*this, "click_timer")
@@ -465,12 +465,9 @@ void linndrum_audio_device::snare_w(u8 data)
const float v2 = BIT(data, 2) ? VCC : 0;
const float v3 = BIT(data, 3) ? VCC : 0;
const float iref = (a * VPLUS + b * v2 + c * v3) / (R0 * (a + b + c + d));
+ m_snare_dac->set_fixed_iref(iref);
- const float gain = get_dac_scaler(iref);
- m_snare_volume->set_gain(gain);
-
- LOGMASKED(LOG_STROBES, "Strobed snare / sidestick: %02x (iref: %f, gain: %f)\n",
- data, iref, gain);
+ LOGMASKED(LOG_STROBES, "Strobed snare / sidestick: %02x (iref: %f)\n", data, iref);
}
void linndrum_audio_device::tom_w(u8 data)
@@ -572,8 +569,10 @@ void linndrum_audio_device::device_add_mconfig(machine_config &config)
for (int voice = 0; voice < NUM_MUX_VOICES; ++voice)
{
DAC76(config, m_mux_dac[voice], 0); // AM6070 (U88).
+ m_mux_dac[voice]->configure_voltage_output(R_DAC_I2V, R_DAC_I2V); // R58, R59.
+ m_mux_dac[voice]->set_fixed_iref(MUX_DAC_IREF);
FILTER_VOLUME(config, m_mux_volume[voice]); // CD4053 (U90), R60, R62 (see mux_drum_w()).
- m_mux_dac[voice]->add_route(0, m_mux_volume[voice], get_dac_scaler(MUX_DAC_IREF));
+ m_mux_dac[voice]->add_route(0, m_mux_volume[voice], 1.0);
}
TIMER(config, m_hat_trigger_timer).configure_generic(FUNC(linndrum_audio_device::hat_trigger_timer_tick)); // LM556 (U37B).
@@ -586,17 +585,15 @@ void linndrum_audio_device::device_add_mconfig(machine_config &config)
TIMER(config, m_snare_timer).configure_generic(FUNC(linndrum_audio_device::snare_timer_tick)); // 74LS627 (U80A).
DAC76(config, m_snare_dac, 0); // AM6070 (U92)
- FILTER_VOLUME(config, m_snare_volume); // See snare_w().
- // DAC output scaling is incorporated in m_snare_volume's gain.
- m_snare_dac->add_route(0, m_snare_volume, 1.0);
+ m_snare_dac->configure_voltage_output(R_DAC_I2V, R_DAC_I2V); // R127, R126.
// The DAC's current outputs are processed by a current-to-voltage converter
// that embeds an RC filter. This consists of an op-amp (U103), R127 and C65
// (for positive voltages), and R126 and C31 (for negative voltages). The
// two resistors and capacitors have the same value.
auto &snare_dac_filter = FILTER_RC(config, "snare_sidestick_dac_filter");
- snare_dac_filter.set_lowpass(RES_K(2.49), CAP_P(2700)); // R127-C65, R126-C31. Cutoff: ~23.7KHz.
- m_snare_volume->add_route(0, snare_dac_filter, 1.0);
+ snare_dac_filter.set_lowpass(R_DAC_I2V, CAP_P(2700)); // R127-C65, R126-C31. Cutoff: ~23.7KHz.
+ m_snare_dac->add_route(0, snare_dac_filter, 1.0);
FILTER_VOLUME(config, m_snare_out);
FILTER_VOLUME(config, m_sidestick_out);
@@ -612,10 +609,14 @@ void linndrum_audio_device::device_add_mconfig(machine_config &config)
TIMER(config, m_tom_timer).configure_generic(FUNC(linndrum_audio_device::tom_timer_tick)); // 74LS627 (U77B).
DAC76(config, m_tom_dac, 0); // AM6070 (U82).
+ // Schematic is missing the second resistor, but that's almost certainly an error.
+ // It is also missing component designations.
+ m_tom_dac->configure_voltage_output(R_DAC_I2V, R_DAC_I2V);
+ m_tom_dac->set_fixed_iref(TOM_DAC_IREF);
for (int i = 0; i < NUM_TOM_VOICES; ++i)
{
FILTER_VOLUME(config, m_tom_out[i]); // One of U87'S (CD4051) outputs.
- m_tom_dac->add_route(0, m_tom_out[i], get_dac_scaler(TOM_DAC_IREF));
+ m_tom_dac->add_route(0, m_tom_out[i], 1.0);
}
// *** Mixer.
@@ -711,20 +712,6 @@ void linndrum_audio_device::write_dac(dac76_device &dac, u8 sample)
dac.b7_w(BIT(sample, 0));
}
-float linndrum_audio_device::get_dac_scaler(float iref)
-{
- // Given the reference current into the DAC, computes the scaler that needs
- // to be applied to the dac76_device output, to convert it to a voltage.
-
- // The maximum output current on each of the "+" and "-" outputs of the
- // AM6070 is `3.8 * Iref`, according to the datasheet.
- // That current gets converted to a voltage by an op-amp configured as
- // a current-to-voltage converter (I2V). All I2Vs on the LinnDrum use
- // 2.49K resistors for both the "+" and "-" current outputs of the DAC.
-
- return 3.8F * iref * float(RES_K(2.49));
-}
-
s32 linndrum_audio_device::get_ls267_freq(const std::array<s32, 2>& freq_range, float cv)
{
// The relationship between CV and frequency is approximately linear. The
diff --git a/src/mame/oberheim/dmx.cpp b/src/mame/oberheim/dmx.cpp
index bc3634bd981..84b448a75a4 100644
--- a/src/mame/oberheim/dmx.cpp
+++ b/src/mame/oberheim/dmx.cpp
@@ -83,7 +83,6 @@ Usage notes:
#include "sound/mixer.h"
#include "sound/spkrdev.h"
#include "sound/va_eg.h"
-#include "sound/va_vca.h"
#include "video/dl1416.h"
#include "speaker.h"
@@ -207,7 +206,6 @@ private:
required_device<timer_device> m_timer; // 555, U5.
required_device<dac76_device> m_dac; // AM6070, U8. Compatible with DAC76.
- required_device<va_vca_device> m_dac_mult; // AM6070 is a multiplying DAC.
optional_device<va_rc_eg_device> m_eg; // Volume envelope generator. Input to U8 Iref.
required_device_array<filter_biquad_device, 3> m_filters;
@@ -234,7 +232,6 @@ dmx_voice_card_device::dmx_voice_card_device(const machine_config &mconfig, cons
, device_sound_interface(mconfig, *this)
, m_timer(*this, "555_u5")
, m_dac(*this, "dac_u8")
- , m_dac_mult(*this, "dac_mult_u8")
, m_eg(*this, "envelope_generator")
, m_filters(*this, "aa_sk_filter_%d", 0)
, m_config(config)
@@ -249,7 +246,6 @@ dmx_voice_card_device::dmx_voice_card_device(const machine_config &mconfig, cons
, device_sound_interface(mconfig, *this)
, m_timer(*this, "555_u5")
, m_dac(*this, "dac_u8")
- , m_dac_mult(*this, "dac_mult_u8")
, m_eg(*this, "envelope_generator")
, m_filters(*this, "aa_sk_filter_%d", 0)
// Need non-zero entries for the filter for validation to pass.
@@ -281,7 +277,7 @@ void dmx_voice_card_device::trigger(bool tr0, bool tr1)
if (has_decay()) // Gain is controled by an envelope generator.
m_eg->set_instant_v(iref);
else // Constant gain.
- m_dac_mult->set_fixed_cv(iref);
+ m_dac->set_fixed_iref(iref);
LOGMASKED(LOG_SOUND, "Trigger: (%d, %d) %d, %f uA\n", tr0, tr1, m_trigger_mode, iref * 1e6F);
}
@@ -295,23 +291,17 @@ void dmx_voice_card_device::set_pitch_adj(s32 t1_percent)
void dmx_voice_card_device::device_add_mconfig(machine_config &config)
{
- // This multiplier relates the DAC reference current to the output voltage.
- // Iout = dac value (normalized to -1 - 1) * 3.8 * Iref.
- // Vout = Iout * R_current_to_voltage_converter.
- static constexpr const double DAC_MULTIPLIER = 3.8 * RES_K(2.4); // R16, R11 on voice card.
static constexpr const double SK_R3 = RES_M(999.99);
static constexpr const double SK_R4 = RES_R(0.001);
TIMER(config, m_timer).configure_generic(FUNC(dmx_voice_card_device::clock_callback));
- DAC76(config, m_dac, 0U);
- VA_VCA(config, m_dac_mult);
- m_dac->add_route(0, m_dac_mult, DAC_MULTIPLIER);
+ DAC76(config, m_dac, 0U).configure_voltage_output(RES_K(2.4), RES_K(2.4)); // R16, R11 on voice card.
if (has_decay())
{
VA_RC_EG(config, m_eg).set_c(m_config.c3);
- m_dac_mult->configure_streaming_cv(true);
- m_eg->add_route(0, m_dac_mult, 1.0);
+ m_dac->configure_streaming_iref(true);
+ m_eg->add_route(0, m_dac, 1.0);
}
FILTER_BIQUAD(config, m_filters[0]).opamp_sk_lowpass_setup(
@@ -324,7 +314,7 @@ void dmx_voice_card_device::device_add_mconfig(machine_config &config)
m_config.filter.r19, m_config.filter.r20, SK_R3, SK_R4,
m_config.filter.c8, m_config.filter.c9);
- m_dac_mult->add_route(0, m_filters[0], 1.0);
+ m_dac->add_route(0, m_filters[0], 1.0);
m_filters[0]->add_route(0, m_filters[1], 1.0);
m_filters[1]->add_route(0, m_filters[2], 1.0);
m_filters[2]->add_route(0, *this, 1.0);