From 3fb2594c997f4a1d85ba22046bc28af0816f1319 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 31 Mar 2025 04:53:31 +0900 Subject: [PATCH] devices/sound/mmc5.cpp: Implement MMC5 sound emulation (#13487) * Implement MMC5 sound emulation - Heavily based from devices/sound/nes_apu.cpp, Adjusted to differences compares to NES APU and MMC5. * bus/nes/mmc5,cpp: Fix save state support, Implement MMC5 sound * bus/nes/nes_slot.h: Fix save state support * sound/nes_defs.h: Fix save state support --- scripts/src/sound.lua | 12 ++ src/devices/bus/nes/mmc5.cpp | 80 ++++++--- src/devices/bus/nes/mmc5.h | 36 ++-- src/devices/bus/nes/nes_slot.h | 16 +- src/devices/sound/mmc5.cpp | 315 +++++++++++++++++++++++++++++++++ src/devices/sound/mmc5.h | 75 ++++++++ src/devices/sound/nes_defs.h | 74 +++++++- 7 files changed, 549 insertions(+), 59 deletions(-) create mode 100644 src/devices/sound/mmc5.cpp create mode 100644 src/devices/sound/mmc5.h diff --git a/scripts/src/sound.lua b/scripts/src/sound.lua index fe2322a5bcc..22996ed36b0 100644 --- a/scripts/src/sound.lua +++ b/scripts/src/sound.lua @@ -1776,3 +1776,15 @@ if (SOUNDS["GT155"]~=null) then MAME_DIR .. "src/devices/sound/gt155.h", } end + +--------------------------------------------------- +-- Nintendo MMC5 Sound +--@src/devices/sound/mmc5.h,SOUNDS["MMC5"] = true +--------------------------------------------------- + +if (SOUNDS["MMC5"]~=null) then + files { + MAME_DIR .. "src/devices/sound/mmc5.cpp", + MAME_DIR .. "src/devices/sound/mmc5.h", + } +end diff --git a/src/devices/bus/nes/mmc5.cpp b/src/devices/bus/nes/mmc5.cpp index 7d534e5e77a..691bd0ae345 100644 --- a/src/devices/bus/nes/mmc5.cpp +++ b/src/devices/bus/nes/mmc5.cpp @@ -34,7 +34,7 @@ #define LAST_CHR_REG_A 0 #define LAST_CHR_REG_B 1 -static const int m_mmc5_attrib[4] = {0x00, 0x55, 0xaa, 0xff}; +static const uint8_t m_mmc5_attrib[4] = {0x00, 0x55, 0xaa, 0xff}; //------------------------------------------------- // constructor @@ -45,11 +45,11 @@ DEFINE_DEVICE_TYPE(NES_EXROM, nes_exrom_device, "nes_exrom", "NES Cart ExROM (MM nes_exrom_device::nes_exrom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : nes_nrom_device(mconfig, NES_EXROM, tag, owner, clock), m_irq_count(0) - , m_irq_status(0), m_irq_enable(0), m_mult1(0), m_mult2(0), m_mmc5_scanline(0), m_vrom_page_a(0), m_vrom_page_b(0), m_floodtile(0), m_floodattr(0) + , m_irq_status(0), m_irq_enable(false), m_pcm_irq(false), m_mult1(0), m_mult2(0), m_mmc5_scanline(0), m_vrom_page_a(0), m_vrom_page_b(0), m_floodtile(0), m_floodattr(0) , m_prg_mode(0), m_chr_mode(0), m_wram_protect_1(0), m_wram_protect_2(0), m_exram_control(0), m_wram_base(0), m_last_chr(0), m_ex1_chr(0) , m_split_chr(0), m_ex1_bank(0), m_ex1_attrib(0), m_high_chr(0), m_split_scr(0), m_split_rev(0), m_split_ctrl(0), m_split_yst(0), m_split_bank(0), m_vcount(0) , m_ppu(*this, ":ppu") // FIXME: this dependency should not exist - , m_sound(*this, "mmc5snd") // FIXME: this is a hack, it should be separated device, similar not same as NES APU!!! + , m_sound(*this, "mmc5snd") { } @@ -64,6 +64,7 @@ void nes_exrom_device::device_start() save_item(NAME(m_irq_count)); save_item(NAME(m_irq_status)); save_item(NAME(m_irq_enable)); + save_item(NAME(m_pcm_irq)); save_item(NAME(m_mult1)); save_item(NAME(m_mult2)); save_item(NAME(m_vrom_page_a)); @@ -104,7 +105,8 @@ void nes_exrom_device::pcb_reset() m_irq_count = 0; m_irq_status = 0; - m_irq_enable = 0; + m_irq_enable = false; + m_pcm_irq = false; m_mult1 = m_mult2 = 0; m_vrom_page_a = m_vrom_page_b = 0; @@ -237,16 +239,20 @@ void nes_exrom_device::update_prg() } } +void nes_exrom_device::update_irq() +{ + set_irq_line(((BIT(m_irq_status, 7) && m_irq_enable) && m_pcm_irq) ? ASSERT_LINE : CLEAR_LINE); +} + + void nes_exrom_device::hblank_irq(int scanline, bool vblank, bool blanked) { m_vcount = scanline; if (scanline == m_irq_count) { - if (m_irq_enable) - set_irq_line(ASSERT_LINE); - m_irq_status = 0xff; + update_irq(); } // "In Frame" flag @@ -257,6 +263,13 @@ void nes_exrom_device::hblank_irq(int scanline, bool vblank, bool blanked) } +void nes_exrom_device::pcm_irq(int state) +{ + m_pcm_irq = state; + update_irq(); +} + + void nes_exrom_device::set_mirror(int page, int src) { switch (src) @@ -280,7 +293,7 @@ void nes_exrom_device::set_mirror(int page, int src) inline bool nes_exrom_device::in_split() { - int tile = m_ppu->get_tilenum(); + int const tile = m_ppu->get_tilenum(); if (tile < 34) { @@ -294,7 +307,7 @@ inline bool nes_exrom_device::in_split() uint8_t nes_exrom_device::nt_r(offs_t offset) { - int page = ((offset & 0xc00) >> 10); + int const page = ((offset & 0xc00) >> 10); switch (m_nt_src[page]) { @@ -316,16 +329,16 @@ uint8_t nes_exrom_device::nt_r(offs_t offset) // but it does not work yet if (m_split_scr && !(m_exram_control & 0x02) && in_split()) { - int tile = m_ppu->get_tilenum(); + int const tile = m_ppu->get_tilenum(); if ((offset & 0x3ff) >= 0x3c0) { - int pos = (((m_split_yst + m_vcount) & ~0x1f) | (tile & 0x1f)) >> 2; + int const pos = (((m_split_yst + m_vcount) & ~0x1f) | (tile & 0x1f)) >> 2; return m_exram[0x3c0 | pos]; } else { - int pos = (((m_split_yst + m_vcount) & 0xf8) << 2) | (tile & 0x1f); + int const pos = (((m_split_yst + m_vcount) & 0xf8) << 2) | (tile & 0x1f); return m_exram[pos]; } } @@ -347,7 +360,7 @@ uint8_t nes_exrom_device::nt_r(offs_t offset) void nes_exrom_device::nt_w(offs_t offset, uint8_t data) { - int page = ((offset & 0xc00) >> 10); + int const page = ((offset & 0xc00) >> 10); if (!m_nt_writable[page]) return; @@ -393,19 +406,19 @@ inline uint8_t nes_exrom_device::base_chr_r(int bank, uint32_t offset) inline uint8_t nes_exrom_device::split_chr_r(uint32_t offset) { - uint32_t helper = (m_split_bank * 0x1000) + (offset & 0x3f8) + (m_split_yst & 7); + uint32_t const helper = (m_split_bank * 0x1000) + (offset & 0x3f8) + (m_split_yst & 7); return m_vrom[helper & (m_vrom_size - 1)]; } inline uint8_t nes_exrom_device::bg_ex1_chr_r(uint32_t offset) { - uint32_t helper = (m_ex1_bank * 0x1000) + (offset & 0xfff); + uint32_t const helper = (m_ex1_bank * 0x1000) + (offset & 0xfff); return m_vrom[helper & (m_vrom_size - 1)]; } uint8_t nes_exrom_device::chr_r(offs_t offset) { - int bank = offset >> 10; + int const bank = offset >> 10; // Extended Attribute Mode (Ex1) does affect BG drawing even for 8x16 sprites (JustBreed uses it extensively!) // However, if a game enables Ex1 but does not write a new m_ex1_bank, I'm not sure here we get the correct behavior @@ -437,6 +450,9 @@ uint8_t nes_exrom_device::read_l(offs_t offset) LOG("exrom read_l, offset: %04x\n", offset); offset += 0x100; + if ((offset >= 0x1000) && (offset <= 0x1015)) + return m_sound->read(offset & 0x1f); + if ((offset >= 0x1c00) && (offset <= 0x1fff)) { // EXRAM @@ -453,7 +469,7 @@ uint8_t nes_exrom_device::read_l(offs_t offset) if (!machine().side_effects_disabled()) { m_irq_status &= ~0x80; - set_irq_line(CLEAR_LINE); + update_irq(); } return value; @@ -489,7 +505,7 @@ void nes_exrom_device::write_l(offs_t offset, uint8_t data) m_exram[offset - 0x1c00] = data; else if (m_exram_control != 0x03) // Modes 0,1 = write data in frame / write 0 otherwise { - if (m_irq_status & 0x40) + if (BIT(m_irq_status, 6)) m_exram[offset - 0x1c00] = data; else m_exram[offset - 0x1c00] = 0x00; @@ -609,7 +625,7 @@ void nes_exrom_device::write_l(offs_t offset, uint8_t data) break; case 0x1204: - m_irq_enable = data & 0x80; + m_irq_enable = BIT(data, 7); LOG("MMC5 irq enable: %02x\n", data); break; @@ -636,7 +652,7 @@ uint8_t nes_exrom_device::read_m(offs_t offset) LOG("exrom read_m, offset: %04x\n", offset); if (!m_battery.empty() && !m_prgram.empty()) // 2 chips present: first is BWRAM, second is WRAM { - if (m_wram_base & 0x04) + if (BIT(m_wram_base, 2)) return m_prgram[(offset + (m_wram_base & 0x03) * 0x2000) & (m_prgram.size() - 1)]; else return m_battery[(offset + (m_wram_base & 0x03) * 0x2000) & (m_battery.size() - 1)]; @@ -665,23 +681,30 @@ void nes_exrom_device::write_m(offs_t offset, uint8_t data) uint8_t nes_exrom_device::read_h(offs_t offset) { LOG("exrom read_h, offset: %04x\n", offset); - int bank = offset / 0x2000; + int const bank = offset / 0x2000; + uint8_t ret = 0; if (bank < 3 && offset >= bank * 0x2000 && offset < (bank + 1) * 0x2000 && m_prg_ram_mapped[bank]) { if (!m_battery.empty() && m_ram_hi_banks[bank] < 4) - return m_battery[((m_ram_hi_banks[bank] * 0x2000) + (offset & 0x1fff)) & (m_battery.size() - 1)]; + ret = m_battery[((m_ram_hi_banks[bank] * 0x2000) + (offset & 0x1fff)) & (m_battery.size() - 1)]; else if (!m_prgram.empty()) - return m_prgram[(((m_ram_hi_banks[bank] & 3) * 0x2000) + (offset & 0x1fff)) & (m_prgram.size() - 1)]; + ret = m_prgram[(((m_ram_hi_banks[bank] & 3) * 0x2000) + (offset & 0x1fff)) & (m_prgram.size() - 1)]; } - - return hi_access_rom(offset); + else + ret = hi_access_rom(offset); + if (!machine().side_effects_disabled()) + { + if (BIT(~offset, 14)) + m_sound->pcm_w(ret); + } + return ret; } void nes_exrom_device::write_h(offs_t offset, uint8_t data) { LOG("exrom write_h, offset: %04x, data: %02x\n", offset, data); - int bank = offset / 0x2000; + int const bank = offset / 0x2000; if (m_wram_protect_1 != 0x02 || m_wram_protect_2 != 0x01 || bank == 3 || !m_prg_ram_mapped[bank]) return; @@ -700,6 +723,7 @@ void nes_exrom_device::device_add_mconfig(machine_config &config) // additional sound hardware SPEAKER(config, "addon").front_center(); - // TODO: temporary; will be separated device - NES_APU(config, m_sound, XTAL(21'477'272)/12).add_route(ALL_OUTPUTS, "addon", 0.90); + MMC5_SOUND(config, m_sound, XTAL(21'477'272)/12); + m_sound->irq().set(FUNC(nes_exrom_device::pcm_irq)); + m_sound->add_route(ALL_OUTPUTS, "addon", 0.90); } diff --git a/src/devices/bus/nes/mmc5.h b/src/devices/bus/nes/mmc5.h index 692b96e6db8..98be5dcfcd6 100644 --- a/src/devices/bus/nes/mmc5.h +++ b/src/devices/bus/nes/mmc5.h @@ -7,7 +7,7 @@ #include "nxrom.h" -#include "sound/nes_apu.h" // temp hack to pass the additional sound regs to APU... +#include "sound/mmc5.h" #include "video/ppu2c0x.h" // this has to be included so that IRQ functions can access ppu2c0x_device::BOTTOM_VISIBLE_SCANLINE @@ -42,6 +42,9 @@ protected: void set_mirror(int page, int src); void update_prg(); + void update_irq(); + void pcm_irq(int state); + inline uint8_t base_chr_r(int bank, uint32_t offset); inline uint8_t split_chr_r(uint32_t offset); inline uint8_t bg_ex1_chr_r(uint32_t offset); @@ -49,24 +52,25 @@ protected: uint16_t m_irq_count; uint8_t m_irq_status; - int m_irq_enable; + bool m_irq_enable; + bool m_pcm_irq; - int m_mult1, m_mult2; + int32_t m_mult1, m_mult2; - int m_mmc5_scanline; - int m_vrom_page_a; - int m_vrom_page_b; + int32_t m_mmc5_scanline; + int32_t m_vrom_page_a; + int32_t m_vrom_page_b; uint16_t m_vrom_bank[12]; // MMC5 has 10bit wide VROM regs! - int m_floodtile; - int m_floodattr; + uint8_t m_floodtile; + uint8_t m_floodattr; - int m_prg_mode; // $5100 - int m_chr_mode; // $5101 - int m_wram_protect_1; // $5102 - int m_wram_protect_2; // $5103 - int m_exram_control; // $5104 - int m_wram_base; // $5113 + uint8_t m_prg_mode; // $5100 + uint8_t m_chr_mode; // $5101 + uint8_t m_wram_protect_1; // $5102 + uint8_t m_wram_protect_2; // $5103 + uint8_t m_exram_control; // $5104 + uint8_t m_wram_base; // $5113 uint8_t m_last_chr; uint8_t m_ex1_chr; @@ -84,7 +88,7 @@ protected: uint8_t m_split_ctrl; // $5200 uint8_t m_split_yst; // $5201 uint8_t m_split_bank; // $5202 - int m_vcount; + int32_t m_vcount; // MMC-5 contains 1K of internal ram uint8_t m_exram[0x400]; @@ -94,7 +98,7 @@ protected: // int m_nes_vram_sprite[8]; required_device m_ppu; - required_device m_sound; + required_device m_sound; }; diff --git a/src/devices/bus/nes/nes_slot.h b/src/devices/bus/nes/nes_slot.h index 743ee43f077..61ba5d0e78c 100644 --- a/src/devices/bus/nes/nes_slot.h +++ b/src/devices/bus/nes/nes_slot.h @@ -302,7 +302,7 @@ protected: int m_outer_chr_size; int m_smd133_addr; - int m_mirroring; + int32_t m_mirroring; bool m_pcb_ctrl_mirror, m_four_screen_vram, m_has_trainer; bool m_x1_005_alt_mirroring; // temp hack for two kind of mirroring in Taito X1-005 boards (to be replaced with pin checking) bool m_bus_conflict; @@ -314,7 +314,7 @@ public: inline int prg_8k_bank_num(int bank); inline void update_prg_banks(int prg_bank_start, int prg_bank_end); memory_bank *m_prg_bank_mem[4]; - int m_prg_bank[4]; + int32_t m_prg_bank[4]; uint32_t m_prg_chunks; uint32_t m_prg_mask; @@ -330,12 +330,12 @@ public: // CHR - int m_chr_source; // global source for the 8 VROM banks + int32_t m_chr_source; // global source for the 8 VROM banks //these were previously called chr_map. they are a quick banking structure, //because some of these change multiple times per scanline! - int m_chr_src[8]; //defines source of base pointer - int m_chr_orig[8]; //defines offset of 0x400 byte segment at base pointer + int32_t m_chr_src[8]; //defines source of base pointer + int32_t m_chr_orig[8]; //defines offset of 0x400 byte segment at base pointer uint8_t *m_chr_access[8]; //source translated + origin -> valid pointer! uint32_t m_vrom_chunks; @@ -367,9 +367,9 @@ public: // NameTable & Mirroring //these were previously called nt_page. they are a quick banking structure for a maximum of 4K of RAM/ROM/ExRAM - int m_nt_src[4]; - int m_nt_orig[4]; - int m_nt_writable[4]; + int32_t m_nt_src[4]; + int32_t m_nt_orig[4]; + int32_t m_nt_writable[4]; uint8_t *m_nt_access[4]; //quick banking structure for a maximum of 4K of RAM/ROM/ExRAM void set_nt_page(int page, int source, int bank, int writable); diff --git a/src/devices/sound/mmc5.cpp b/src/devices/sound/mmc5.cpp new file mode 100644 index 00000000000..2659e36535c --- /dev/null +++ b/src/devices/sound/mmc5.cpp @@ -0,0 +1,315 @@ +// license:GPL-2.0+ +// copyright-holders:Matthew Conte +/***************************************************************************** + + MAME/MESS NES APU CORE + + Based on the Nofrendo/Nosefart NES RP2A03 sound emulation core written by + Matthew Conte (matt@conte.com) and redesigned for use in MAME/MESS by + Who Wants to Know? (wwtk@mail.com) + + This core is written with the advise and consent of Matthew Conte and is + released under the GNU Public License. + + timing notes: + master = 21477270 + 2A03 clock = master/12 + sequencer = master/89490 or CPU/7457 + + ***************************************************************************** + + mmc5.cpp + + MMC5 sound emulation core, heavily based from sound/nes_apu.cpp. + + *****************************************************************************/ + +#include "emu.h" +#include "mmc5.h" + +DEFINE_DEVICE_TYPE(MMC5_SOUND, mmc5_sound_device, "mmc5_sound", "Nintendo MMC5 (sound)") + +mmc5_sound_device::mmc5_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) + : device_t(mconfig, MMC5_SOUND, tag, owner, clock) + , device_sound_interface(mconfig, *this) + , m_samps_per_sync(0) + , m_stream(nullptr) + , m_irq_handler(*this) +{ +} + + +void mmc5_sound_device::device_reset() +{ + write(0x15, 0x00); +} + +void mmc5_sound_device::device_clock_changed() +{ + calculate_rates(); +} + +void mmc5_sound_device::calculate_rates() +{ + m_samps_per_sync = m_frame_clocks / 4; + + // initialize sample times in terms of vsyncs + for (int i = 0; i < SYNCS_MAX1; i++) + { + m_vbl_times[i] = vbl_length[i] * m_samps_per_sync / 4; // twice as fast as NES APU + m_sync_times1[i] = m_samps_per_sync * (i + 1); + } + + int const rate = clock() / 4; + + if (m_stream != nullptr) + m_stream->set_sample_rate(rate); + else + m_stream = stream_alloc(0, 1, rate); +} + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void mmc5_sound_device::device_start() +{ + m_frame_clocks = 29830; + + calculate_rates(); + + // calculate mixer output + /* + pulse channel output (same as NES APU?): + + 95.88 + ----------------------- + 8128 + ----------------- + 100 + pulse 1 + pulse 2 + + */ + for (int i = 0; i < 31; i++) + { + stream_buffer::sample_t pulse_out = (i == 0) ? 0.0 : 95.88 / ((8128.0 / i) + 100.0); + m_square_lut[i] = pulse_out; + } + + /* register for save */ + for (int i = 0; i < 2; i++) + { + save_item(NAME(m_core.squ[i].regs), i); + save_item(NAME(m_core.squ[i].vbl_length), i); + save_item(NAME(m_core.squ[i].freq), i); + save_item(NAME(m_core.squ[i].phaseacc), i); + save_item(NAME(m_core.squ[i].env_phase), i); + save_item(NAME(m_core.squ[i].adder), i); + save_item(NAME(m_core.squ[i].env_vol), i); + save_item(NAME(m_core.squ[i].enabled), i); + save_item(NAME(m_core.squ[i].output), i); + } + + save_item(NAME(m_core.pcm.regs)); + save_item(NAME(m_core.pcm.irq_enabled)); + save_item(NAME(m_core.pcm.irq_line)); + save_item(NAME(m_core.pcm.output)); +} + + +/* TODO: sound channels should *ALL* have DC volume decay */ + +/* OUTPUT SQUARE WAVE SAMPLE (VALUES FROM 0 to +15) */ +void mmc5_sound_device::tick_square(mmc5_sound_t::square_t &chan) +{ + /* reg0: 0-3=volume, 4=envelope, 5=hold, 6-7=duty cycle + ** reg2: 8 bits of freq + ** reg3: 0-2=high freq, 7-4=vbl length counter + */ + + if (!chan.enabled) + { + chan.output = 0; + return; + } + + /* enveloping */ + int const env_delay = m_sync_times1[chan.regs[0] & 0x0f]; + + /* decay is at a rate of (env_regs + 1) / 240 secs */ + chan.env_phase -= 4; + while (chan.env_phase < 0) + { + chan.env_phase += env_delay; + if (BIT(chan.regs[0], 5)) + chan.env_vol = (chan.env_vol + 1) & 15; + else if (chan.env_vol < 15) + chan.env_vol++; + } + + /* vbl length counter */ + if (chan.vbl_length > 0 && BIT(~chan.regs[0], 5)) + chan.vbl_length--; + + if (!chan.vbl_length) + { + chan.output = 0; + return; + } + + chan.phaseacc -= 4; + + while (chan.phaseacc < 0) + { + chan.phaseacc += (chan.freq >> 16); + chan.adder = (chan.adder + 1) & 0x0f; + } + + if (BIT(chan.regs[0], 4)) /* fixed volume */ + chan.output = chan.regs[0] & 0x0f; + else + chan.output = 0x0f - chan.env_vol; + + chan.output *= BIT(duty_lut[chan.regs[0] >> 6], 7 - BIT(chan.adder, 1, 3)); +} + +/* WRITE REGISTER VALUE */ +void mmc5_sound_device::write(offs_t offset, u8 data) +{ + m_stream->update(); + + int chan = BIT(offset, 2); + + switch (offset) + { + /* squares */ + case mmc5_sound_t::WRA0: + case mmc5_sound_t::WRB0: + m_core.squ[chan].regs[0] = data; + break; + + case mmc5_sound_t::WRA2: + case mmc5_sound_t::WRB2: + m_core.squ[chan].regs[2] = data; + if (m_core.squ[chan].enabled) + m_core.squ[chan].freq = ((((m_core.squ[chan].regs[3] & 7) << 8) + data) + 1) << 16; + break; + + case mmc5_sound_t::WRA3: + case mmc5_sound_t::WRB3: + m_core.squ[chan].regs[3] = data; + + if (m_core.squ[chan].enabled) + { + m_core.squ[chan].vbl_length = m_vbl_times[data >> 3]; + m_core.squ[chan].env_vol = 0; + m_core.squ[chan].freq = ((((data & 7) << 8) + m_core.squ[chan].regs[2]) + 1) << 16; + } + + break; + + /* PCM */ + case mmc5_sound_t::WRE0: + m_core.pcm.regs[0] = data; + m_core.pcm.irq_enabled = BIT(data, 7); + m_irq_handler(m_core.pcm.irq_line & m_core.pcm.irq_enabled); + break; + + case mmc5_sound_t::WRE1: /* PCM output */ + m_core.pcm.regs[1] = data; + if (BIT(~m_core.pcm.regs[0], 0)) + { + if (data == 0x00) + { + m_core.pcm.irq_line = true; + m_irq_handler(m_core.pcm.irq_line & m_core.pcm.irq_enabled); + } + else + m_core.pcm.output = data; + } + break; + + case mmc5_sound_t::SMASK: + if (BIT(data, 0)) + m_core.squ[0].enabled = true; + else + { + m_core.squ[0].enabled = false; + m_core.squ[0].vbl_length = 0; + } + + if (BIT(data, 1)) + m_core.squ[1].enabled = true; + else + { + m_core.squ[1].enabled = false; + m_core.squ[1].vbl_length = 0; + } + break; + default: +#ifdef MAME_DEBUG +logerror("invalid mmc5 sound write: $%02X at $%04X\n", data, offset); +#endif + break; + } +} + +// Read registers +u8 mmc5_sound_device::read(offs_t offset) +{ + m_stream->update(); + + u8 readval = 0; + switch (offset) + { + case mmc5_sound_t::WRE0: + readval |= (m_core.pcm.irq_line & m_core.pcm.irq_enabled) ? 0x80 : 0; + if (!machine().side_effects_disabled()) + { + m_core.pcm.irq_line = false; + m_irq_handler(m_core.pcm.irq_line & m_core.pcm.irq_enabled); + } + break; + case mmc5_sound_t::SMASK: + if (m_core.squ[0].vbl_length > 0) + readval |= 0x01; + + if (m_core.squ[1].vbl_length > 0) + readval |= 0x02; + break; + default: + if (!machine().side_effects_disabled()) + logerror("%s: Invalid mmc5 sound read at $%02x\n", offset); + break; + } + + return readval; +} + +// Write PCM data +void mmc5_sound_device::pcm_w(u8 data) +{ + if (BIT(m_core.pcm.regs[0], 0)) + m_core.pcm.output = data; +} + + +//------------------------------------------------- +// sound_stream_update - handle a stream update +//------------------------------------------------- + +void mmc5_sound_device::sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) +{ + stream_buffer::sample_t accum = 0.0; + auto &output = outputs[0]; + + for (int sampindex = 0; sampindex < output.samples(); sampindex++) + { + tick_square(m_core.squ[0]); + tick_square(m_core.squ[1]); + + accum = m_square_lut[m_core.squ[0].output + m_core.squ[1].output]; + accum += stream_buffer::sample_t(m_core.pcm.output) / 255.0f; + + output.put(sampindex, -accum); + } +} diff --git a/src/devices/sound/mmc5.h b/src/devices/sound/mmc5.h new file mode 100644 index 00000000000..a8f0ea9f406 --- /dev/null +++ b/src/devices/sound/mmc5.h @@ -0,0 +1,75 @@ +// license:GPL-2.0+ +// copyright-holders:Matthew Conte +/***************************************************************************** + + MAME/MESS NES APU CORE + + Based on the Nofrendo/Nosefart NES RP2A03 sound emulation core written by + Matthew Conte (matt@conte.com) and redesigned for use in MAME/MESS by + Who Wants to Know? (wwtk@mail.com) + + This core is written with the advise and consent of Matthew Conte and is + released under the GNU Public License. + + ***************************************************************************** + + mmc5.h + + MMC5 sound emulation core. + + *****************************************************************************/ + +#ifndef MAME_SOUND_MMC5_H +#define MAME_SOUND_MMC5_H + +#pragma once + + +#include "nes_defs.h" + +class mmc5_sound_device : public device_t, + public device_sound_interface +{ +public: + mmc5_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock); + + // configuration helpers + auto irq() { return m_irq_handler.bind(); } + + u8 read(offs_t offset); + void write(offs_t offset, u8 data); + void pcm_w(u8 data); + +protected: + // device-level overrides + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + virtual void device_clock_changed() override; + + // sound stream update overrides + virtual void sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) override; + +private: + /* GLOBAL CONSTANTS */ + static constexpr unsigned SYNCS_MAX1 = 0x20; + + // internal state + mmc5_sound_t m_core; /* Sound core */ + u32 m_samps_per_sync; /* Number of samples per vsync */ + u32 m_vbl_times[SYNCS_MAX1]; /* VBL durations in samples */ + u32 m_sync_times1[SYNCS_MAX1]; /* Samples per sync table */ + stream_buffer::sample_t m_square_lut[31]; // Non-linear Square wave output LUT + + sound_stream *m_stream; + devcb_write_line m_irq_handler; + + u16 m_frame_clocks; + + void calculate_rates(); + void tick_square(mmc5_sound_t::square_t &chan); +}; + + +DECLARE_DEVICE_TYPE(MMC5_SOUND, mmc5_sound_device) + +#endif // MAME_SOUND_MMC5_H diff --git a/src/devices/sound/nes_defs.h b/src/devices/sound/nes_defs.h index 7756a9b3d18..1b9a2013ee7 100644 --- a/src/devices/sound/nes_defs.h +++ b/src/devices/sound/nes_defs.h @@ -40,8 +40,8 @@ struct apu_t } u8 regs[4]; - int vbl_length = 0; - int freq = 0; + s32 vbl_length = 0; + s32 freq = 0; float phaseacc = 0.0; float env_phase = 0.0; float sweep_phase = 0.0; @@ -61,10 +61,10 @@ struct apu_t } u8 regs[4]; /* regs[1] unused */ - int linear_length = 0; + s32 linear_length = 0; bool linear_reload = false; - int vbl_length = 0; - int write_latency = 0; + s32 vbl_length = 0; + s32 write_latency = 0; float phaseacc = 0.0; u8 adder = 0; bool counter_started = false; @@ -83,7 +83,7 @@ struct apu_t u8 regs[4]; /* regs[1] unused */ u16 lfsr = 1; - int vbl_length = 0; + s32 vbl_length = 0; float phaseacc = 0.0; float env_phase = 0.0; u8 env_vol = 0; @@ -103,7 +103,7 @@ struct apu_t u8 regs[4]; u32 address = 0; u32 length = 0; - int bits_left = 0; + s32 bits_left = 0; float phaseacc = 0.0; u8 cur_byte = 0; bool enabled = false; @@ -146,6 +146,66 @@ struct apu_t bool frame_irq_occurred = false; }; + +struct mmc5_sound_t +{ + /* CHANNEL TYPE DEFINITIONS */ + + /* Square Wave */ + struct square_t + { + square_t() + { + for (auto & elem : regs) + elem = 0; + } + + u8 regs[4]; + s32 vbl_length = 0; + s32 freq = 0; + float phaseacc = 0.0; + float env_phase = 0.0; + u8 adder = 0; + u8 env_vol = 0; + bool enabled = false; + u8 output = 0; + }; + + /* DPCM Wave */ + struct pcm_t + { + pcm_t() + { + for (auto & elem : regs) + elem = 0; + } + + u8 regs[2]; + bool irq_enabled = false; + bool irq_line = false; + u8 output = 0; + }; + + + /* REGISTER DEFINITIONS */ + static constexpr unsigned WRA0 = 0x00; + static constexpr unsigned WRA1 = 0x01; + static constexpr unsigned WRA2 = 0x02; + static constexpr unsigned WRA3 = 0x03; + static constexpr unsigned WRB0 = 0x04; + static constexpr unsigned WRB1 = 0x05; + static constexpr unsigned WRB2 = 0x06; + static constexpr unsigned WRB3 = 0x07; + static constexpr unsigned WRE0 = 0x10; + static constexpr unsigned WRE1 = 0x11; + static constexpr unsigned SMASK = 0x15; + + /* Sound channels */ + square_t squ[2]; + pcm_t pcm; +}; + + /* CONSTANTS */ /* vblank length table used for squares, triangle, noise */