mirror of
https://github.com/holub/mame
synced 2025-04-20 15:32:45 +03:00
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.
This commit is contained in:
parent
6bc01c176c
commit
e0ea6961f7
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user