diff options
author | 2025-04-10 02:04:35 -0700 | |
---|---|---|
committer | 2025-04-10 11:04:35 +0200 | |
commit | e0ea6961f75e06e2fd84ae1ebbb0305a52dda206 (patch) | |
tree | 159d76d55993c896e8515d4a6e284d49faf0f724 | |
parent | 6bc01c176c519c870aaa3da8f4b370bad63ca9ab (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.cpp | 53 | ||||
-rw-r--r-- | src/devices/sound/dac76.h | 42 | ||||
-rw-r--r-- | src/mame/linn/linndrum.cpp | 45 | ||||
-rw-r--r-- | src/mame/oberheim/dmx.cpp | 20 |
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); |