mirror of
https://github.com/holub/mame
synced 2025-04-09 18:17:44 +03:00
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
This commit is contained in:
parent
44b8ae58c6
commit
3fb2594c99
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<ppu2c0x_device> m_ppu;
|
||||
required_device<nesapu_device> m_sound;
|
||||
required_device<mmc5_sound_device> m_sound;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
315
src/devices/sound/mmc5.cpp
Normal file
315
src/devices/sound/mmc5.cpp
Normal file
@ -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<read_stream_view> const &inputs, std::vector<write_stream_view> &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);
|
||||
}
|
||||
}
|
75
src/devices/sound/mmc5.h
Normal file
75
src/devices/sound/mmc5.h
Normal file
@ -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<read_stream_view> const &inputs, std::vector<write_stream_view> &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
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user