From 834d69e0f86481e6843f51cba602329b46bd1db2 Mon Sep 17 00:00:00 2001 From: Jamie McCadden Date: Tue, 2 Sep 2025 18:14:35 +0100 Subject: [PATCH 1/2] harddriv.h: AM6012/TL084 DAC analog path emulation state Added AM6012/TL084 DAC analog path emulation state. Emulating AM6012/TL084 DAC based off info from Atari schematics. Cleans up audio pops/clicks especialy during car crashes. --- src/mame/atari/harddriv.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/mame/atari/harddriv.h b/src/mame/atari/harddriv.h index fe9591dd8a29d..f69e12f002d34 100644 --- a/src/mame/atari/harddriv.h +++ b/src/mame/atari/harddriv.h @@ -571,6 +571,20 @@ class harddriv_sound_board_device : public device_t uint16_t m_comram[0x400/2]{}; uint64_t m_last_bio_cycles = 0; + + // --- AM6012/TL084 DAC analog path emulation state --- + emu_timer* m_am6012_timer = nullptr; // mixer timer (e.g., 96 kHz) + uint16_t m_am6012_code = 0x0800; // latched 12-bit DAC code (MSB inverted, low nibble ignored) + int m_prev_out12 = 0x0800; // last filtered 12-bit sample + int32_t m_dac_lp1 = (0x0800 << 8); // fixed-point 24.8 filters + int32_t m_dac_lp2 = (0x0800 << 8); + int32_t m_dac_lp3 = (0x0800 << 8); + uint8_t m_mute_ramp = 255; // 0..255 soft mute ramp + uint8_t m_dac_muted = 0; // boolean + uint16_t m_dither_lfsr = 0xACE1; // dither LFSR + + TIMER_CALLBACK_MEMBER(am6012_stream_tick); + void update_68k_interrupts(); TIMER_CALLBACK_MEMBER( delayed_68k_w ); From 725d8d1df8cc43ead82eae95318c0a8e90d67a33 Mon Sep 17 00:00:00 2001 From: Jamie McCadden Date: Tue, 2 Sep 2025 18:49:24 +0100 Subject: [PATCH 2/2] harddriv_a.cpp: AM6012/TL084 DAC analog emulation Added AM6012/TL084 DAC analog emulation. Emulating AM6012/TL084 DAC based off info from Atari schematics. Cleans up audio pops/clicks and distortion especialy during car crashes. --- src/mame/atari/harddriv_a.cpp | 81 +++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/src/mame/atari/harddriv_a.cpp b/src/mame/atari/harddriv_a.cpp index d11db2cdfaea3..011009bbaf402 100644 --- a/src/mame/atari/harddriv_a.cpp +++ b/src/mame/atari/harddriv_a.cpp @@ -54,6 +54,27 @@ harddriv_sound_board_device::harddriv_sound_board_device(const machine_config &m void harddriv_sound_board_device::device_start() { + // Allocate and start AM6012 DAC mixer timer at 96 kHz (hardware-faithful) + m_am6012_timer = timer_alloc(FUNC(harddriv_sound_board_device::am6012_stream_tick), this); + m_am6012_timer->adjust(attotime::from_hz(96000), 0, attotime::from_hz(96000)); + + // Initialize analog-emulation state + m_am6012_code = 0x0800; + m_prev_out12 = 0x0800; + m_dac_lp1 = m_dac_lp2 = m_dac_lp3 = (0x0800 << 8); + m_mute_ramp = 255; + m_dac_muted = 0; + m_dither_lfsr = 0xACE1; + + // Save-state support + save_item(NAME(m_am6012_code)); + save_item(NAME(m_prev_out12)); + save_item(NAME(m_dac_lp1)); + save_item(NAME(m_dac_lp2)); + save_item(NAME(m_dac_lp3)); + save_item(NAME(m_mute_ramp)); + save_item(NAME(m_dac_muted)); + save_item(NAME(m_dither_lfsr)); } //------------------------------------------------- @@ -64,6 +85,8 @@ void harddriv_sound_board_device::device_reset() { m_last_bio_cycles = 0; m_sounddsp->set_input_line(INPUT_LINE_HALT, ASSERT_LINE); + // Ensure we start unmuted cleanly (no click) + m_mute_ramp = 255; } /************************************* @@ -326,8 +349,8 @@ int harddriv_sound_board_device::hdsnddsp_get_bio() void harddriv_sound_board_device::hdsnddsp_dac_w(uint16_t data) { - /* /DACL */ - m_dac->write((data >> 4) ^ 0x800); // schematics show d0-3 are ignored & the msb is inverted + /* /DACL : latch 12-bit AM6012 code */ + m_am6012_code = uint16_t(((data ^ 0x8000) >> 4) & 0x0fff); // schematics show d0-3 are ignored & the msb is inverted } @@ -340,8 +363,8 @@ void harddriv_sound_board_device::hdsnddsp_comport_w(uint16_t data) void harddriv_sound_board_device::hdsnddsp_mute_w(uint16_t data) { - /* mute DAC audio, D0=1 */ - logerror("%s:mute DAC=%d\n", machine().describe_context(), data); + /* mute DAC audio, D0=1 (soft-ramped in stream) */ + m_dac_muted = (data & 1) ? 1 : 0; } @@ -426,6 +449,56 @@ void harddriv_sound_board_device::driversnd_dsp_io_map(address_map &map) } +//------------------------------------------------- +// am6012_stream_tick - generate DAC output +// Emulates the Driver Sound board analog chain: +// AM6012 (12-bit multiplying DAC, MSB inverted) -> +// TL084 I/V + 2-pole active LPF + extra pole -> +// soft mute ramp and tiny TPDF dither. +//------------------------------------------------- + +TIMER_CALLBACK_MEMBER(harddriv_sound_board_device::am6012_stream_tick) +{ + // Soft mute glide (slow to avoid clicks) + if (m_dac_muted) { if (m_mute_ramp > 0) m_mute_ramp -= 1; } + else { if (m_mute_ramp < 255) m_mute_ramp += 1; } + + // 3-pole low-pass: two main poles + one gentle pole + // Defaults tuned for ~10–12 kHz band edge at 96 kHz mixer + const int K12 = 176; // ≈ 10.5 kHz per pole + const int K3 = 96; // gentle cleanup pole + + int target = int(m_am6012_code) << 8; // 12-bit to 24.8 fixed-point + + m_dac_lp1 += ((target - m_dac_lp1) * K12) >> 8; + m_dac_lp2 += ((m_dac_lp1 - m_dac_lp2) * K12) >> 8; + m_dac_lp3 += ((m_dac_lp2 - m_dac_lp3) * K3 ) >> 8; + + int filtered = m_dac_lp3 >> 8; // back to 12-bit domain + + // Slew limit per sample to catch fast spikes without dulling transients + const int maxstep = 0x36; // tuned with TL084 behavior in mind + int delta = filtered - m_prev_out12; + if (delta > maxstep) filtered = m_prev_out12 + maxstep; + if (delta < -maxstep) filtered = m_prev_out12 - maxstep; + m_prev_out12 = filtered; + + // Apply soft mute around midscale (0x800) + int centered = filtered - 0x0800; + centered = (centered * int(m_mute_ramp)) / 255; + + // TPDF dither: ±1 LSB using 16-bit LFSR (poly 0xB400) + m_dither_lfsr = (m_dither_lfsr >> 1) ^ (uint16_t)(-(m_dither_lfsr & 1) & 0xB400u); + int tpdf = int(m_dither_lfsr & 1) - int((m_dither_lfsr >> 1) & 1); + int out12 = centered + 0x0800 + tpdf; + + if (out12 < 0) out12 = 0; else if (out12 > 0x0fff) out12 = 0x0fff; + + // Present final code to the DAC device (routed to speaker in config) + if (m_dac) m_dac->write(uint16_t(out12)); +} + + //------------------------------------------------- // device_add_mconfig - add device configuration //-------------------------------------------------