From 619ec483e7a6104f9ddbc9bbd4f685beae1b1f83 Mon Sep 17 00:00:00 2001 From: Daniel Brunner <0xFEEDC0DE64@gmail.com> Date: Sun, 16 Dec 2018 22:19:06 +0100 Subject: [PATCH] Imported existing sources --- .gitignore | 102 ++- CMakeLists.txt | 5 + nescorelib/CMakeLists.txt | 75 ++ nescorelib/boards/bandai.cpp | 10 + nescorelib/boards/bandai.h | 11 + nescorelib/boards/board.cpp | 582 +++++++++++++ nescorelib/boards/board.h | 174 ++++ nescorelib/boards/ffe.cpp | 59 ++ nescorelib/boards/ffe.h | 20 + nescorelib/boards/mmc2.cpp | 10 + nescorelib/boards/mmc2.h | 11 + nescorelib/boards/namcot106.cpp | 10 + nescorelib/boards/namcot106.h | 11 + nescorelib/emu/apu.cpp | 622 ++++++++++++++ nescorelib/emu/apu.h | 149 ++++ nescorelib/emu/apudmc.cpp | 217 +++++ nescorelib/emu/apudmc.h | 55 ++ nescorelib/emu/apunos.cpp | 220 +++++ nescorelib/emu/apunos.h | 61 ++ nescorelib/emu/apusq1.cpp | 219 +++++ nescorelib/emu/apusq1.h | 70 ++ nescorelib/emu/apusq2.cpp | 216 +++++ nescorelib/emu/apusq2.h | 70 ++ nescorelib/emu/aputrl.cpp | 174 ++++ nescorelib/emu/aputrl.h | 54 ++ nescorelib/emu/cpu.cpp | 1268 ++++++++++++++++++++++++++++ nescorelib/emu/cpu.h | 114 +++ nescorelib/emu/dma.cpp | 199 +++++ nescorelib/emu/dma.h | 44 + nescorelib/emu/interrupts.cpp | 69 ++ nescorelib/emu/interrupts.h | 45 + nescorelib/emu/memory.cpp | 234 +++++ nescorelib/emu/memory.h | 68 ++ nescorelib/emu/ports.cpp | 53 ++ nescorelib/emu/ports.h | 42 + nescorelib/emu/ppu.cpp | 983 +++++++++++++++++++++ nescorelib/emu/ppu.h | 187 ++++ nescorelib/emusettings.h | 158 ++++ nescorelib/enums/chrarea.h | 13 + nescorelib/enums/emuregion.h | 8 + nescorelib/enums/mirroring.h | 25 + nescorelib/enums/ntarea.h | 9 + nescorelib/enums/prgarea.h | 17 + nescorelib/inputprovider.h | 10 + nescorelib/mappers/mapper000.cpp | 11 + nescorelib/mappers/mapper000.h | 13 + nescorelib/mappers/mapper001.cpp | 216 +++++ nescorelib/mappers/mapper001.h | 41 + nescorelib/mappers/mapper002.cpp | 23 + nescorelib/mappers/mapper002.h | 16 + nescorelib/mappers/mapper003.cpp | 17 + nescorelib/mappers/mapper003.h | 15 + nescorelib/mappers/mapper004.cpp | 178 ++++ nescorelib/mappers/mapper004.h | 44 + nescorelib/nescorelib_global.h | 9 + nescorelib/nesemulator.cpp | 183 ++++ nescorelib/nesemulator.h | 70 ++ nescorelib/rom.cpp | 69 ++ nescorelib/rom.h | 32 + nescorelib/soundhighpassfilter.cpp | 21 + nescorelib/soundhighpassfilter.h | 17 + nescorelib/soundlowpassfilter.cpp | 21 + nescorelib/soundlowpassfilter.h | 17 + nesemu/CMakeLists.txt | 16 + nesemu/main.cpp | 141 ++++ nesguilib/CMakeLists.txt | 21 + nesguilib/gamepadinput.cpp | 24 + nesguilib/gamepadinput.h | 20 + nesguilib/nesguilib_global.h | 9 + 69 files changed, 7961 insertions(+), 36 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 nescorelib/CMakeLists.txt create mode 100644 nescorelib/boards/bandai.cpp create mode 100644 nescorelib/boards/bandai.h create mode 100644 nescorelib/boards/board.cpp create mode 100644 nescorelib/boards/board.h create mode 100644 nescorelib/boards/ffe.cpp create mode 100644 nescorelib/boards/ffe.h create mode 100644 nescorelib/boards/mmc2.cpp create mode 100644 nescorelib/boards/mmc2.h create mode 100644 nescorelib/boards/namcot106.cpp create mode 100644 nescorelib/boards/namcot106.h create mode 100644 nescorelib/emu/apu.cpp create mode 100644 nescorelib/emu/apu.h create mode 100644 nescorelib/emu/apudmc.cpp create mode 100644 nescorelib/emu/apudmc.h create mode 100644 nescorelib/emu/apunos.cpp create mode 100644 nescorelib/emu/apunos.h create mode 100644 nescorelib/emu/apusq1.cpp create mode 100644 nescorelib/emu/apusq1.h create mode 100644 nescorelib/emu/apusq2.cpp create mode 100644 nescorelib/emu/apusq2.h create mode 100644 nescorelib/emu/aputrl.cpp create mode 100644 nescorelib/emu/aputrl.h create mode 100644 nescorelib/emu/cpu.cpp create mode 100644 nescorelib/emu/cpu.h create mode 100644 nescorelib/emu/dma.cpp create mode 100644 nescorelib/emu/dma.h create mode 100644 nescorelib/emu/interrupts.cpp create mode 100644 nescorelib/emu/interrupts.h create mode 100644 nescorelib/emu/memory.cpp create mode 100644 nescorelib/emu/memory.h create mode 100644 nescorelib/emu/ports.cpp create mode 100644 nescorelib/emu/ports.h create mode 100644 nescorelib/emu/ppu.cpp create mode 100644 nescorelib/emu/ppu.h create mode 100644 nescorelib/emusettings.h create mode 100644 nescorelib/enums/chrarea.h create mode 100644 nescorelib/enums/emuregion.h create mode 100644 nescorelib/enums/mirroring.h create mode 100644 nescorelib/enums/ntarea.h create mode 100644 nescorelib/enums/prgarea.h create mode 100644 nescorelib/inputprovider.h create mode 100644 nescorelib/mappers/mapper000.cpp create mode 100644 nescorelib/mappers/mapper000.h create mode 100644 nescorelib/mappers/mapper001.cpp create mode 100644 nescorelib/mappers/mapper001.h create mode 100644 nescorelib/mappers/mapper002.cpp create mode 100644 nescorelib/mappers/mapper002.h create mode 100644 nescorelib/mappers/mapper003.cpp create mode 100644 nescorelib/mappers/mapper003.h create mode 100644 nescorelib/mappers/mapper004.cpp create mode 100644 nescorelib/mappers/mapper004.h create mode 100644 nescorelib/nescorelib_global.h create mode 100644 nescorelib/nesemulator.cpp create mode 100644 nescorelib/nesemulator.h create mode 100644 nescorelib/rom.cpp create mode 100644 nescorelib/rom.h create mode 100644 nescorelib/soundhighpassfilter.cpp create mode 100644 nescorelib/soundhighpassfilter.h create mode 100644 nescorelib/soundlowpassfilter.cpp create mode 100644 nescorelib/soundlowpassfilter.h create mode 100644 nesemu/CMakeLists.txt create mode 100644 nesemu/main.cpp create mode 100644 nesguilib/CMakeLists.txt create mode 100644 nesguilib/gamepadinput.cpp create mode 100644 nesguilib/gamepadinput.h create mode 100644 nesguilib/nesguilib_global.h diff --git a/.gitignore b/.gitignore index 5291a38..fab7372 100644 --- a/.gitignore +++ b/.gitignore @@ -1,43 +1,73 @@ -# C++ objects and libs -*.slo -*.lo -*.o -*.a -*.la -*.lai -*.so -*.dll -*.dylib +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- -# Qt-es -object_script.*.Release -object_script.*.Debug -*_plugin_import.cpp +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc /.qmake.cache /.qmake.stash -*.pro.user -*.pro.user.* -*.qbs.user -*.qbs.user.* -*.moc -moc_*.cpp -moc_*.h -qrc_*.cpp -ui_*.h -*.qmlc -*.jsc -Makefile* -*build-* -# Qt unit tests -target_wrapper.* +# qtcreator generated files +*.pro.user* -# QtCreator -*.autosave +# xemacs temporary files +*.flc -# QtCreator Qml -*.qmlproject.user -*.qmlproject.user.* +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe -# QtCreator CMake -CMakeLists.txt.user* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d58996e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,5 @@ +project(DbNeuralNet) + +add_subdirectory(nesemu) +add_subdirectory(nescorelib) +add_subdirectory(nesguilib) diff --git a/nescorelib/CMakeLists.txt b/nescorelib/CMakeLists.txt new file mode 100644 index 0000000..0e3af1f --- /dev/null +++ b/nescorelib/CMakeLists.txt @@ -0,0 +1,75 @@ +find_package(Qt5Core CONFIG REQUIRED) + +set(HEADERS + emusettings.h + inputprovider.h + nescorelib_global.h + nesemulator.h + rom.h + soundhighpassfilter.h + soundlowpassfilter.h + boards/bandai.h + boards/board.h + boards/ffe.h + boards/mmc2.h + boards/namcot106.h + emu/apudmc.h + emu/apu.h + emu/apunos.h + emu/apusq1.h + emu/apusq2.h + emu/aputrl.h + emu/cpu.h + emu/dma.h + emu/interrupts.h + emu/memory.h + emu/ports.h + emu/ppu.h + enums/chrarea.h + enums/emuregion.h + enums/mirroring.h + enums/ntarea.h + enums/prgarea.h + mappers/mapper000.h + mappers/mapper001.h + mappers/mapper002.h + mappers/mapper003.h + mappers/mapper004.h +) + +set(SOURCES + nesemulator.cpp + rom.cpp + soundhighpassfilter.cpp + soundlowpassfilter.cpp + boards/bandai.cpp + boards/board.cpp + boards/ffe.cpp + boards/mmc2.cpp + boards/namcot106.cpp + emu/apu.cpp + emu/apudmc.cpp + emu/apunos.cpp + emu/apusq1.cpp + emu/apusq2.cpp + emu/aputrl.cpp + emu/cpu.cpp + emu/dma.cpp + emu/interrupts.cpp + emu/memory.cpp + emu/ports.cpp + emu/ppu.cpp + mappers/mapper000.cpp + mappers/mapper001.cpp + mappers/mapper002.cpp + mappers/mapper003.cpp + mappers/mapper004.cpp +) + +add_library(nescorelib ${HEADERS} ${SOURCES}) + +target_compile_definitions(nescorelib PRIVATE NESCORELIB_LIBRARY) + +target_link_libraries(nescorelib Qt5::Core dbcorelib) + +target_include_directories(nescorelib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/nescorelib/boards/bandai.cpp b/nescorelib/boards/bandai.cpp new file mode 100644 index 0000000..a362d95 --- /dev/null +++ b/nescorelib/boards/bandai.cpp @@ -0,0 +1,10 @@ +#include "bandai.h" + +Bandai::Bandai(NesEmulator &emu, const Rom &rom) : + Board(emu, rom) +{ +} + +Bandai::~Bandai() +{ +} diff --git a/nescorelib/boards/bandai.h b/nescorelib/boards/bandai.h new file mode 100644 index 0000000..050b2f0 --- /dev/null +++ b/nescorelib/boards/bandai.h @@ -0,0 +1,11 @@ +#pragma once + +#include "nescorelib_global.h" +#include "board.h" + +class NESCORELIB_EXPORT Bandai : public Board +{ +public: + explicit Bandai(NesEmulator &emu, const Rom &rom); + virtual ~Bandai(); +}; diff --git a/nescorelib/boards/board.cpp b/nescorelib/boards/board.cpp new file mode 100644 index 0000000..a6783e6 --- /dev/null +++ b/nescorelib/boards/board.cpp @@ -0,0 +1,582 @@ +#include "board.h" + +// system includes +#include + +// local includes +#include "rom.h" + +Board::Board(NesEmulator &emu, const Rom &rom) : + m_emu(emu), + m_rom(rom) +{ + // PRG RAM + m_sramSaveRequired = false; + + /* foreach from db */ + { + const int SIZE = prgRam8KbDefaultBlkCount() * 8; + const bool BATTERY = true; + + if (BATTERY) + m_sramSaveRequired = true; + + if (SIZE > 0) + { + int kb4_count = SIZE / 2; + for (int i = 0; i < kb4_count; i++) + m_prgRam.append({ + /* .ram = */ {}, + /* .enabled = */ true, + /* .writeable = */ true, + /* .battery = */ BATTERY + }); + } + } + + // TRAINER, it should be copied into the RAM blk at 0x7000. In this case, it should be copied into ram blk 3 + if (rom.hasTrainer) + std::copy(std::begin(rom.trainer), std::end(rom.trainer), std::begin(m_prgRam[3].ram)); + + // CHR RAM + // Map 8 Kb for now + const int chr_ram_banks_1k = chrRom1KbDefaultBlkCount(); // from db + m_chrRam.reserve(chr_ram_banks_1k); + for (int i = 0; i < chr_ram_banks_1k; i++) + m_chrRam.append({ + /* .ram = */ {}, + /* .enabled = */ true, + /* .writeable = */ true, + /* .battery = */ false + }); + + // 6 Nametables +} + +Board::~Board() +{ +} + +QString Board::name() const +{ + throw std::runtime_error("has not been implemented"); +} + +quint8 Board::mapper() const +{ + throw std::runtime_error("has not been implemented"); +} + +void Board::hardReset() +{ + // Use the configuration of mapper 0 (NRAM). + // PRG Switching + // Toggle ram/rom + // Switch 16KB ram, from 0x4000 into 0x7000 + toggle16kPrgRam(true, PRGArea::Area4000); + switch16kPrg(0, PRGArea::Area4000); + // Switch 32KB rom, from 0x8000 into 0xF000 + toggle32kPrgRam(false, PRGArea::Area8000); + switch32kPrg(0, PRGArea::Area8000); + + // CHR Switching + // Pattern tables + toggle8kChrRam((chrRom1KbCount() == 0)); + switch8kChr(0); + + // Nametables + switch1kNmt(m_rom.mirroring); +} + +void Board::softReset() +{ +} + +quint8 Board::_readEx(quint16 address) +{ + int prgTmpArea = address >> 12 & 0xF; + if (m_prgAreaBlk[prgTmpArea].ram) + { + int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRam4KbMask(); + if (m_prgRam[prgTmpIndex].enabled) + return m_prgRam[prgTmpIndex].ram[address & 0xFFF]; + else + return 0; + } + else + { + int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRom4KbMask(); + return m_rom.prg[prgTmpIndex][address & 0xFFF]; + } +} + +quint8 Board::readEx(quint16 address) +{ + auto result = _readEx(address); + return result; +} + +void Board::writeEx(quint16 address, quint8 value) +{ + int prgTmpArea = address >> 12 & 0xF; + if (m_prgAreaBlk[prgTmpArea].ram) + { + int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRam4KbMask(); + + if (m_prgRam[prgTmpIndex].enabled) + if (m_prgRam[prgTmpIndex].writeable) + m_prgRam[prgTmpIndex].ram[address & 0xFFF] = value; + } +} + +quint8 Board::_readSrm(quint16 address) +{ + int prgTmpArea = address >> 12 & 0xF; + if (m_prgAreaBlk[prgTmpArea].ram) + { + int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRam4KbMask(); + if (m_prgRam[prgTmpIndex].enabled) + return m_prgRam[prgTmpIndex].ram[address & 0xFFF]; + else + return 0; + } + else + { + int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRom4KbMask(); + return m_rom.prg[prgTmpIndex][address & 0xFFF]; + } +} + +quint8 Board::readSrm(quint16 address) +{ + auto result = _readSrm(address); + return result; +} + +void Board::writeSrm(quint16 address, quint8 value) +{ + int prgTmpArea = address >> 12 & 0xF; + if (m_prgAreaBlk[prgTmpArea].ram) + { + int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRam4KbMask(); + + if (m_prgRam[prgTmpIndex].enabled) + if (m_prgRam[prgTmpIndex].writeable) + m_prgRam[prgTmpIndex].ram[address & 0xFFF] = value; + } +} + +quint8 Board::_readPrg(quint16 address) +{ + int prgTmpArea = address >> 12 & 0xF; + if (m_prgAreaBlk[prgTmpArea].ram) + { + const int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRam4KbMask(); + const int temp = address & 0xFFF; + if (m_prgRam[prgTmpIndex].enabled) + return m_prgRam[prgTmpIndex].ram[temp]; + else + return 0; + } + else + { + const int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRom4KbMask(); + const int temp = address & 0xFFF; + return m_rom.prg[prgTmpIndex][temp]; + } + + /* + if (IsGameGenieActive) + { + foreach (GameGenieCode code in GameGenieCodes) + { + if (!code.Enabled) + continue; + + if (code.Address != addr) + continue; + + if (!code.IsCompare || code.Compare == return_value) + return code.Value; + } + } + */ +} + +quint8 Board::readPrg(quint16 address) +{ + auto result = _readPrg(address); + return result; +} + +void Board::writePrg(quint16 address, quint8 value) +{ + int prgTmpArea = address >> 12 & 0xF; + if (m_prgAreaBlk[prgTmpArea].ram) + { + int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRam4KbMask(); + + if (m_prgRam[prgTmpIndex].enabled) + if (m_prgRam[prgTmpIndex].writeable) + m_prgRam[prgTmpIndex].ram[address & 0xFFF] = value; + } +} + +quint8 Board::_readChr(quint16 address) +{ + // 00-07 means patterntables + // 08-11 means nametables, should not included + // 12-15 nametables mirrors, should not included as well + int chrTmpArea = (address >> 10) & 0x7;// 0x0000 - 0x1FFF, 0-7. + int chrTmpIndex = m_chrAreaBlk[chrTmpArea].index; + if (m_chrAreaBlk[chrTmpArea].ram) + { + chrTmpIndex &= chrRam1KbMask(); + if (m_chrRam[chrTmpIndex].enabled) + return m_chrRam[chrTmpIndex].ram[address & 0x3FF]; + else + return 0; + } + else + { + chrTmpIndex &= chrRom1KbMask(); + return m_rom.chr[chrTmpIndex][address & 0x3FF]; + } +} + +quint8 Board::readChr(quint16 address) +{ + auto result = _readChr(address); + return result; +} + +void Board::writeChr(quint16 address, quint8 value) +{ + // 00-07 means patterntables + // 08-11 means nametables, should not included + // 12-15 nametables mirrors, should not included as well + int chrTmpArea = (address >> 10) & 0x7;// 0x0000 - 0x1FFF, 0-7. + + if (m_chrAreaBlk[chrTmpArea].ram) + { + int chrTmpIndex = m_chrAreaBlk[chrTmpArea].index & chrRam1KbMask(); + if (m_chrRam[chrTmpIndex].enabled) + if (m_chrRam[chrTmpIndex].writeable) + m_chrRam[chrTmpIndex].ram[address & 0x3FF] = value; + } +} + +quint8 Board::_readNmt(quint16 address) +{ + int nmtTmpArea = (address >> 10) & 0x3;// 0x2000 - 0x2C00, 0-3. + int nmtTmpIndex = m_nmtRam[nmtTmpArea].index; + + return m_nmtRam[nmtTmpIndex].ram[address & 0x3FF]; +} + +quint8 Board::readNmt(quint16 address) +{ + auto result = _readNmt(address); + return result; +} + +void Board::writeNmt(quint16 address, quint8 value) +{ + int nmtTmpArea = (address >> 10) & 0x3;// 0x2000 - 0x2C00, 0-3. + int nmtTmpIndex = m_nmtRam[nmtTmpArea].index; + + m_nmtRam[nmtTmpIndex].ram[address & 0x3FF] = value; +} + +void Board::onPpuAddressUpdate(quint16 address) +{ + if (ppuA12ToggleTimerEnabled()) + { + m_oldVramAddress = m_newVramAddress; + m_newVramAddress = address & 0x1000; + if (ppuA12TogglesOnRaisingEdge()) + { + if (m_oldVramAddress < m_newVramAddress) + { + if (m_ppuCyclesTimer > 8) + onPpuA12RaisingEdge(); + + m_ppuCyclesTimer = 0; + } + } + else + { + if (m_oldVramAddress > m_newVramAddress) + { + if (m_ppuCyclesTimer > 8) + onPpuA12RaisingEdge(); + + m_ppuCyclesTimer = 0; + } + } + } +} + +void Board::onCpuClock() +{ +} + +void Board::onPpuClock() +{ + if (ppuA12ToggleTimerEnabled()) + m_ppuCyclesTimer++; +} + +void Board::onPpuA12RaisingEdge() +{ +} + +void Board::onPpuScanlineTick() +{ +} + +void Board::onApuClockDuration() +{ +} + +void Board::onApuClockEnvelope() +{ +} + +void Board::onApuClockSingle() +{ +} + +void Board::onApuClock() +{ +} + +double Board::apuGetSample() const +{ + return 0.; +} + +void Board::apuApplyChannelsSettings() +{ +} + +void Board::readState(QDataStream &dataStream) +{ + Q_UNUSED(dataStream) +} + +void Board::writeState(QDataStream &dataStream) const +{ + Q_UNUSED(dataStream) +} + +int Board::prgRam8KbDefaultBlkCount() const +{ + return 1; +} + +int Board::chrRom1KbDefaultBlkCount() const +{ + return 8; +} + +bool Board::ppuA12ToggleTimerEnabled() const +{ + return false; +} + +bool Board::ppuA12TogglesOnRaisingEdge() const +{ + return false; +} + +void Board::switch4kPrg(int index, PRGArea area) +{ + m_prgAreaBlk[int(area)].index = index; +} + +void Board::switch8kPrg(int index, PRGArea area) +{ + index *= 2; + m_prgAreaBlk[int(area)].index = index; + m_prgAreaBlk[int(area) + 1].index = index + 1; +} + +void Board::switch16kPrg(int index, PRGArea area) +{ + index *= 4; + m_prgAreaBlk[int(area)].index = index; + m_prgAreaBlk[int(area) + 1].index = index + 1; + m_prgAreaBlk[int(area) + 2].index = index + 2; + m_prgAreaBlk[int(area) + 3].index = index + 3; +} + +void Board::switch32kPrg(int index, PRGArea area) +{ + index *= 8; + m_prgAreaBlk[int(area)].index = index; + m_prgAreaBlk[int(area) + 1].index = index + 1; + m_prgAreaBlk[int(area) + 2].index = index + 2; + m_prgAreaBlk[int(area) + 3].index = index + 3; + m_prgAreaBlk[int(area) + 4].index = index + 4; + m_prgAreaBlk[int(area) + 5].index = index + 5; + m_prgAreaBlk[int(area) + 6].index = index + 6; + m_prgAreaBlk[int(area) + 7].index = index + 7; +} + +void Board::toggle4kPrgRam(bool ram, PRGArea area) +{ + m_prgAreaBlk[int(area)].ram = ram; +} + +void Board::toggle8kPrgRam(bool ram, PRGArea area) +{ + m_prgAreaBlk[int(area)].ram = ram; + m_prgAreaBlk[int(area) + 1].ram = ram; +} + +void Board::toggle16kPrgRam(bool ram, PRGArea area) +{ + m_prgAreaBlk[int(area)].ram = ram; + m_prgAreaBlk[int(area) + 1].ram = ram; + m_prgAreaBlk[int(area) + 2].ram = ram; + m_prgAreaBlk[int(area) + 3].ram = ram; +} + +void Board::toggle32kPrgRam(bool ram, PRGArea area) +{ + m_prgAreaBlk[int(area)].ram = ram; + m_prgAreaBlk[int(area) + 1].ram = ram; + m_prgAreaBlk[int(area) + 2].ram = ram; + m_prgAreaBlk[int(area) + 3].ram = ram; + m_prgAreaBlk[int(area) + 4].ram = ram; + m_prgAreaBlk[int(area) + 5].ram = ram; + m_prgAreaBlk[int(area) + 6].ram = ram; + m_prgAreaBlk[int(area) + 7].ram = ram; +} + +void Board::togglePrgRamEnable(bool enable) +{ + for(auto &page : m_prgRam) + page.enabled = enable; +} + +void Board::togglePrgRamWritableEnable(bool enable) +{ + for(auto &page : m_prgRam) + page.writeable = enable; +} + +void Board::toggle4kPrgRamEnabled(bool enable, int index) +{ + m_prgRam[index].enabled = enable; +} + +void Board::toggle4kPrgRamWritable(bool enable, int index) +{ + m_prgRam[index].writeable = enable; +} + +void Board::toggle4kPrgRamBattery(bool enable, int index) +{ + m_prgRam[index].battery = enable; +} + +void Board::switch1kChr(int index, CHRArea area) +{ + m_chrAreaBlk[int(area)].index = index; +} + +void Board::switch2kChr(int index, CHRArea area) +{ + index *= 2; + m_chrAreaBlk[int(area)].index = index; + m_chrAreaBlk[int(area) + 1].index = index + 1; +} + +void Board::switch4kChr(int index, CHRArea area) +{ + index *= 4; + m_chrAreaBlk[int(area)].index = index; + m_chrAreaBlk[int(area) + 1].index = index + 1; + m_chrAreaBlk[int(area) + 2].index = index + 2; + m_chrAreaBlk[int(area) + 3].index = index + 3; +} + +void Board::switch8kChr(int index) +{ + index *= 8; + for(int i = 0; i < 8; i++) + m_chrAreaBlk[i].index = index + i; +} + +void Board::toggle1kChrRam(bool ram, CHRArea area) +{ + m_chrAreaBlk[int(area)].ram = ram; +} + +void Board::toggle2kChrRam(bool ram, CHRArea area) +{ + m_chrAreaBlk[int(area)].ram = ram; + m_chrAreaBlk[int(area) + 1].ram = ram; +} + +void Board::toggle4kChrRam(bool ram, CHRArea area) +{ + m_chrAreaBlk[int(area)].ram = ram; + m_chrAreaBlk[int(area) + 1].ram = ram; + m_chrAreaBlk[int(area) + 2].ram = ram; + m_chrAreaBlk[int(area) + 3].ram = ram; +} + +void Board::toggle8kChrRam(bool ram) +{ + for(auto &areaBlk : m_chrAreaBlk) + areaBlk.ram = ram; +} + +void Board::toggle1kChrRamEnabled(bool enable, int index) +{ + m_chrRam[index].enabled = enable; +} + +void Board::toggle1kChrRamWritable(bool enable, int index) +{ + m_chrRam[index].writeable = enable; +} + +void Board::toggleChrRamWritableEnable(bool enable) +{ + for (auto &ramPage : m_chrRam) + ramPage.writeable = enable; +} + +void Board::toggle1kChrRamBattery(bool enable, int index) +{ + m_chrRam[index].battery = enable; +} + +void Board::switch1kNmt(int index, quint8 area) +{ + m_nmtRam[area].index = index; +} + +void Board::switch1kNmt(Mirroring mirroring) +{ + // Mirroring value: + // 0000 0000 + // ddcc bbaa + // aa: index for area 0x2000 + // bb: index for area 0x2400 + // cc: index for area 0x2800 + // dd: index for area 0x2C00 + m_nmtRam[0].index = int(mirroring) & 0x3; + m_nmtRam[1].index = (int(mirroring) >> 2) & 0x3; + m_nmtRam[2].index = (int(mirroring) >> 4) & 0x3; + m_nmtRam[3].index = (int(mirroring) >> 6) & 0x3; +} + +bool Board::enableExternalSound() const +{ + return false; +} diff --git a/nescorelib/boards/board.h b/nescorelib/boards/board.h new file mode 100644 index 0000000..b3d1418 --- /dev/null +++ b/nescorelib/boards/board.h @@ -0,0 +1,174 @@ +#pragma once + +#include "nescorelib_global.h" + +// Qt includes +#include +#include +#include + +// system includes +#include + +// local includes +#include "rom.h" +#include "enums/prgarea.h" +#include "enums/chrarea.h" + +// forward declarations +class QDataStream; + +class NesEmulator; + +class NESCORELIB_EXPORT Board +{ + Q_DISABLE_COPY(Board) + +public: + explicit Board(NesEmulator &emu, const Rom &rom); + virtual ~Board(); + + virtual QString name() const; + virtual quint8 mapper() const; + + virtual void hardReset(); + virtual void softReset(); + + virtual quint8 _readEx(quint16 address); + virtual quint8 readEx(quint16 address); + virtual void writeEx(quint16 address, quint8 value); + virtual quint8 _readSrm(quint16 address); + virtual quint8 readSrm(quint16 address); + virtual void writeSrm(quint16 address, quint8 value); + virtual quint8 _readPrg(quint16 address); + virtual quint8 readPrg(quint16 address); + virtual void writePrg(quint16 address, quint8 value); + virtual quint8 _readChr(quint16 address); + virtual quint8 readChr(quint16 address); + virtual void writeChr(quint16 address, quint8 value); + virtual quint8 _readNmt(quint16 address); + virtual quint8 readNmt(quint16 address); + virtual void writeNmt(quint16 address, quint8 value); + + virtual void onPpuAddressUpdate(quint16 address); + virtual void onCpuClock(); + virtual void onPpuClock(); + virtual void onPpuA12RaisingEdge(); + virtual void onPpuScanlineTick(); + virtual void onApuClockDuration(); + virtual void onApuClockEnvelope(); + virtual void onApuClockSingle(); + virtual void onApuClock(); + virtual double apuGetSample() const; + virtual void apuApplyChannelsSettings(); + + virtual void readState(QDataStream &dataStream); + virtual void writeState(QDataStream &dataStream) const; + + virtual bool enableExternalSound() const; + +protected: + virtual int prgRam8KbDefaultBlkCount() const; + virtual int chrRom1KbDefaultBlkCount() const; + virtual bool ppuA12ToggleTimerEnabled() const; + virtual bool ppuA12TogglesOnRaisingEdge() const; + + void switch4kPrg(int index, PRGArea area); + void switch8kPrg(int index, PRGArea area); + void switch16kPrg(int index, PRGArea area); + void switch32kPrg(int index, PRGArea area); + void toggle4kPrgRam(bool ram, PRGArea area); + void toggle8kPrgRam(bool ram, PRGArea area); + void toggle16kPrgRam(bool ram, PRGArea area); + void toggle32kPrgRam(bool ram, PRGArea area); + void togglePrgRamEnable(bool enable); + void togglePrgRamWritableEnable(bool enable); + void toggle4kPrgRamEnabled(bool enable, int index); + void toggle4kPrgRamWritable(bool enable, int index); + void toggle4kPrgRamBattery(bool enable, int index); + void switch1kChr(int index, CHRArea area); + void switch2kChr(int index, CHRArea area); + void switch4kChr(int index, CHRArea area); + void switch8kChr(int index); + void toggle1kChrRam(bool ram, CHRArea area); + void toggle2kChrRam(bool ram, CHRArea area); + void toggle4kChrRam(bool ram, CHRArea area); + void toggle8kChrRam(bool ram); + void toggle1kChrRamEnabled(bool enable, int index); + void toggle1kChrRamWritable(bool enable, int index); + void toggleChrRamWritableEnable(bool enable); + void toggle1kChrRamBattery(bool enable, int index); + void switch1kNmt(int index, quint8 area); + void switch1kNmt(Mirroring mirroring); + + int prgRom4KbCount() const { return m_rom.prg.size(); } + int prgRom4KbMask() const { return prgRom4KbCount() - 1; } + int prgRom8KbCount() const { return prgRom4KbCount() / 2; } + int prgRom8KbMask() const { return prgRom8KbCount() - 1; } + int prgRom16KbCount() const { return prgRom4KbCount() / 4; } + int prgRom16KbMask() const { return prgRom16KbCount() - 1; } + int prgRom32KbCount() const { return prgRom4KbCount() / 8; } + int prgRom32KbMask() const { return prgRom32KbCount() - 1; } + int prgRam4KbCount() const { return m_prgRam.size(); } + int prgRam4KbMask() const { return prgRam4KbCount() - 1; } + int prgRam8KbCount() const { return prgRam4KbCount() / 2; } + int prgRam8KbMask() const { return prgRam8KbCount() - 1; } + int prgRam16KbCount() const { return prgRam4KbCount() / 4; } + int prgRam16KbMask() const { return prgRam16KbCount() - 1; } + int prgRam32KbCount() const { return prgRam4KbCount() / 8; } + int prgRam32KbMask() const { return prgRam32KbCount() - 1; } + int chrRom1KbCount() const { return m_rom.chr.size(); } + int chrRom1KbMask() const { return chrRom1KbCount() - 1; } + int chrRom2KbCount() const { return chrRom1KbCount() / 2; } + int chrRom2KbMask() const { return chrRom2KbCount() - 1; } + int chrRom4KbCount() const { return chrRom1KbCount() / 4; } + int chrRom4KbMask() const { return chrRom4KbCount() - 1; } + int chrRom8KbCount() const { return chrRom1KbCount() / 8; } + int chrRom8KbMask() const { return chrRom8KbCount() - 1; } + int chrRam1KbCount() const { return m_chrRam.size(); } + int chrRam1KbMask() const { return chrRam1KbCount() - 1; } + int chrRam2KbCount() const { return chrRam1KbCount() / 2; } + int chrRam2KbMask() const { return chrRam2KbCount() - 1; } + int chrRam4KbCount() const { return chrRam1KbCount() / 4; } + int chrRam4KbMask() const { return chrRam4KbCount() - 1; } + int chrRam8KbCount() const { return chrRam1KbCount() / 8; } + int chrRam8KbMask() const { return chrRam8KbCount() - 1; } + + template + struct RamPage { + std::array ram {}; // The prg RAM blocks, 4KB (0x1000) each. + bool enabled {}; // Indicates if a block is enabled (disabled ram blocks cannot be accessed, either read nor write) + bool writeable {}; // Indicates if a block is writable (false means writes are not accepted even if this block is RAM) + bool battery {}; // Indicates if a block is battery (RAM block battery will be saved to file on emu shutdown) + }; + + struct AreaBlk { + bool ram {}; // Indicates if a blk is RAM (true) or ROM (false) + int index {}; // The index of RAM/ROM block in the area + }; + + struct NmtRam { + std::array ram {}; + int index {}; // The index of NMT RAM block in the area + }; + + QVector > m_prgRam {}; + std::array m_prgAreaBlk {}; // Starting from 4xxx to Fxxx, each entry here configure a 8kb block area + + QVector > m_chrRam {}; + std::array m_chrAreaBlk {}; // Starting from 0000 to F3FF, each entry here configure a 4kb block area + + std::array m_nmtRam {}; + + int m_oldVramAddress {}; + int m_newVramAddress {}; + int m_ppuCyclesTimer {}; + +protected: + NesEmulator &m_emu; + const Rom m_rom; + +private: + + bool m_sramSaveRequired {}; +}; diff --git a/nescorelib/boards/ffe.cpp b/nescorelib/boards/ffe.cpp new file mode 100644 index 0000000..f342015 --- /dev/null +++ b/nescorelib/boards/ffe.cpp @@ -0,0 +1,59 @@ +#include "ffe.h" + +// Qt includes +#include + +// local includes +#include "nesemulator.h" + +Ffe::Ffe(NesEmulator &emu, const Rom &rom) : + Board(emu, rom) +{ +} + +Ffe::~Ffe() +{ +} + +void Ffe::writeEx(quint16 address, quint8 value) +{ + switch (address) + { + case 0x4501: + m_irqEnable = false; + m_emu.interrupts().removeFlag(Interrupts::IRQ_BOARD); + break; + case 0x4502: + m_irqCounter = (m_irqCounter & 0xFF00) | value; + break; + case 0x4503: + m_irqEnable = true; + m_irqCounter = (m_irqCounter & 0x00FF) | (value << 8); + break; + } +} + +void Ffe::onCpuClock() +{ + if (m_irqEnable) + { + m_irqCounter++; + if (m_irqCounter >= 0xFFFF) + { + m_irqCounter = 0; + m_emu.interrupts().addFlag(Interrupts::IRQ_BOARD); + } + } +} + +void Ffe::readState(QDataStream &dataStream) +{ + Board::readState(dataStream); + dataStream >> m_irqEnable >> m_irqCounter; +} + +void Ffe::writeState(QDataStream &dataStream) const +{ + Board::writeState(dataStream); + dataStream << m_irqEnable << m_irqCounter; +} diff --git a/nescorelib/boards/ffe.h b/nescorelib/boards/ffe.h new file mode 100644 index 0000000..dd534cd --- /dev/null +++ b/nescorelib/boards/ffe.h @@ -0,0 +1,20 @@ +#pragma once + +#include "nescorelib_global.h" +#include "board.h" + +class NESCORELIB_EXPORT Ffe : public Board +{ +public: + explicit Ffe(NesEmulator &emu, const Rom &rom); + virtual ~Ffe(); + + void writeEx(quint16 address, quint8 value) Q_DECL_OVERRIDE; + void onCpuClock() Q_DECL_OVERRIDE; + void readState(QDataStream &dataStream) Q_DECL_OVERRIDE; + void writeState(QDataStream &dataStream) const Q_DECL_OVERRIDE; + +private: + bool m_irqEnable; + int m_irqCounter; +}; diff --git a/nescorelib/boards/mmc2.cpp b/nescorelib/boards/mmc2.cpp new file mode 100644 index 0000000..6ad69f7 --- /dev/null +++ b/nescorelib/boards/mmc2.cpp @@ -0,0 +1,10 @@ +#include "mmc2.h" + +Mmc2::Mmc2(NesEmulator &emu, const Rom &rom) : + Board(emu, rom) +{ +} + +Mmc2::~Mmc2() +{ +} diff --git a/nescorelib/boards/mmc2.h b/nescorelib/boards/mmc2.h new file mode 100644 index 0000000..e9695ec --- /dev/null +++ b/nescorelib/boards/mmc2.h @@ -0,0 +1,11 @@ +#pragma once + +#include "nescorelib_global.h" +#include "board.h" + +class NESCORELIB_EXPORT Mmc2 : public Board +{ +public: + explicit Mmc2(NesEmulator &emu, const Rom &rom); + virtual ~Mmc2(); +}; diff --git a/nescorelib/boards/namcot106.cpp b/nescorelib/boards/namcot106.cpp new file mode 100644 index 0000000..02b6c96 --- /dev/null +++ b/nescorelib/boards/namcot106.cpp @@ -0,0 +1,10 @@ +#include "namcot106.h" + +Namcot106::Namcot106(NesEmulator &emu, const Rom &rom) : + Board(emu, rom) +{ +} + +Namcot106::~Namcot106() +{ +} diff --git a/nescorelib/boards/namcot106.h b/nescorelib/boards/namcot106.h new file mode 100644 index 0000000..12ee67d --- /dev/null +++ b/nescorelib/boards/namcot106.h @@ -0,0 +1,11 @@ +#pragma once + +#include "nescorelib_global.h" +#include "board.h" + +class NESCORELIB_EXPORT Namcot106 : public Board +{ +public: + Namcot106(NesEmulator &emu, const Rom &rom); + virtual ~Namcot106(); +}; diff --git a/nescorelib/emu/apu.cpp b/nescorelib/emu/apu.cpp new file mode 100644 index 0000000..3adf916 --- /dev/null +++ b/nescorelib/emu/apu.cpp @@ -0,0 +1,622 @@ +#include "apu.h" + +// Qt includes +#include + +// system includes +#include + +// local includes +#include "nesemulator.h" +#include "emusettings.h" + +Apu::Apu(NesEmulator &emu) : + QObject(&emu), + m_emu(emu), + m_dmc(*this), + m_nos(*this), + m_sq1(*this), + m_sq2(*this), + m_trl(*this), + m_lowPassFilter(0.815686), // 14 KHz + m_highPassFilter1(0.999835), // 90 Hz + m_highPassFilter2(0.996039) // 442 Hz +{ +} + +void Apu::hardReset() +{ + m_regIoDb = 0; + m_regIoAddr = 0; + m_regAccessHappened = false; + m_regAccessW = false; + m_seqMode = false; + m_oddCycle = false; + m_cycleFt = 0; + m_cycleE = 4; + m_cycleF = 4; + m_cycleL = 4; + m_oddL = false; + m_checkIrq = false; + m_doEnv = false; + m_doLength = false; + + m_sq1.apuSq1HardReset(); + m_sq2.apuSq2HardReset(); + m_nos.apuNosHardReset(); + m_dmc.apuDmcHardReset(); + m_trl.apuTrlHardReset(); + + m_irqEnabled = true; + m_irqFlag = false; + + m_timer = 0.; + m_audioX = 0.; + m_audioX1 = 0.; + m_audioY = 0.; + m_audioYClocks = 0.; + + m_highPassFilter1.reset(); + m_highPassFilter2.reset(); + m_lowPassFilter.reset(); +} + +void Apu::softReset() +{ + m_regIoDb = 0; + m_regIoAddr = 0; + m_regAccessHappened = false; + m_regAccessW = false; + m_seqMode = false; + m_oddCycle = false; + m_cycleFt = 0; + m_cycleE = 4; + m_cycleF = 4; + m_cycleL = 4; + m_oddL = false; + m_checkIrq = false; + m_doEnv = false; + m_doLength = false; + + m_irqEnabled = true; + m_irqFlag = false; + + m_sq1.apuSq1SoftReset(); + m_sq2.apuSq2SoftReset(); + m_trl.apuTrlSoftReset(); + m_nos.apuNosSoftReset(); + m_dmc.apuDmcSoftReset(); +} + +quint8 Apu::_ioRead(const quint16 address) +{ + static constexpr std::array apuRegReadFunc = []() { + std::array apuRegReadFunc {}; + for(std::size_t i = 0; i < 0x20; i++) + apuRegReadFunc[i] = &Apu::blankAccess; + apuRegReadFunc[0x15] = &Apu::read4015; + apuRegReadFunc[0x16] = &Apu::read4016; + apuRegReadFunc[0x17] = &Apu::read4017; + return apuRegReadFunc; + }(); + + if(address >= 0x4020) + return m_emu.memory().board()->readEx(address); + else + { + m_regIoAddr = address & 0x1F; + m_regAccessHappened = true; + m_regAccessW = false; + (this->*apuRegReadFunc[m_regIoAddr])(); + return m_regIoDb; + } +} + +quint8 Apu::ioRead(const quint16 address) +{ + auto result = _ioRead(address); + return result; +} + +void Apu::ioWrite(const quint16 address, const quint8 value) +{ + if(address >= 0x4020) + m_emu.memory().board()->writeEx(address, value); + else + { + m_regIoDb = value; + m_regIoAddr = address & 0x1F; + m_regAccessHappened = true; + m_regAccessW = true; + } +} + +void Apu::blankAccess() +{ +} + +void Apu::onRegister4014() +{ + if(!m_regAccessW) + return; + + // oam dma + m_emu.dma().setOamAddress(m_regIoDb << 8); + m_emu.dma().assertOamDma(); +} + +void Apu::onRegister4015() +{ + if(!m_regAccessW) + { + // on reads, do the effects we know + m_irqFlag = false; + m_emu.interrupts().removeFlag(Interrupts::IRQ_APU); + } + else + { + // do a normal write + m_sq1.apuSq1On4015(); + m_sq2.apuSq2On4015(); + m_nos.apuNosOn4015(); + m_trl.apuTrlOn4015(); + m_dmc.apuDmcOn4015(); + } +} + +void Apu::onRegister4016() +{ + // Only writes accepted + if(m_regAccessW) + { + if(m_inputStrobe && m_regIoDb == 0) + m_emu.ports().updatePorts(); + + m_inputStrobe = m_regIoDb; + } + else + m_emu.ports().setPort0(m_emu.ports().port0() >> 1); +} + +void Apu::onRegister4017() +{ + if(m_regAccessW) + { + m_seqMode = (m_regIoDb & 0x80) != 0; + m_irqEnabled = (m_regIoDb & 0x40) == 0; + + // Reset counters + m_cycleE = -1; + m_cycleL = -1; + m_cycleF = -1; + m_oddL = false; + // Clock immediately ? + m_doLength = m_seqMode; + m_doEnv = m_seqMode; + // Reset irq + m_checkIrq = false; + + if(!m_irqEnabled) + { + m_irqFlag = false; + m_emu.interrupts().removeFlag(Interrupts::IRQ_APU); + } + } + else + m_emu.ports().setPort1(m_emu.ports().port1() >> 1); +} + +void Apu::read4015() +{ + m_regIoDb = m_regIoDb & 0x20; + + // Channels enable + m_sq1.apuSq1Read4015(); + m_sq2.apuSq2Read4015(); + m_nos.apuNosRead4015(); + m_trl.apuTrlRead4015(); + m_dmc.apuDmcRead4015(); + + // IRQ + if(m_irqFlag) + m_regIoDb = (m_regIoDb & 0xBF) | 0x40; + if(m_irqDeltaOccur) + m_regIoDb = (m_regIoDb & 0x7F) | 0x80; +} + +void Apu::read4016() +{ + m_regIoDb = m_emu.ports().port0() & 1; +} + +void Apu::read4017() +{ + m_regIoDb = m_emu.ports().port1() & 1; +} + +void Apu::clock() +{ + m_oddCycle = !m_oddCycle; + + if(m_doEnv) + clockEnvelope(); + + if(m_doLength) + clockDuration(); + + if(!m_oddCycle) + { + // IRQ + m_cycleF++; + if(m_cycleF >= m_freqF) + { + m_cycleF = -1; + m_checkIrq = true; + m_cycleFt = 3; + } + + // Envelope + m_cycleE++; + if(m_cycleE >= m_freqE) + { + m_cycleE = -1; + // Clock envelope and other units except when: + // 1 the seq mode is set + // 2 it is the time of irq check clock + if(m_checkIrq) + { + if(!m_seqMode) + { + // this is the 3rd step of mode 0, do a reset + m_doEnv = true; + } + else + { + // the next step will be the 4th step of mode 1 + // so, shorten the step then do a reset + m_cycleE = 4; + } + } + else + m_doEnv = true; + } + + // Length + m_cycleL++; + if(m_cycleL >= m_freqL) + { + m_oddL = !m_oddL; + + m_cycleL = m_oddL ? -2 : -1; + + // Clock duration and sweep except when: + // 1 the seq mode is set + // 2 it is the time of irq check clock + if(m_checkIrq && m_seqMode) + { + m_cycleL = 3730;// Next step will be after 7456 - 3730 = 3726 cycles, 2 cycles shorter than e freq + m_oddL = true; + } + else + { + m_doLength = true; + } + } + + if(m_regAccessHappened) + { + m_regAccessHappened = false; + switch(m_regIoAddr) + { + case 0: m_sq1.apuOnRegister4000(); break; + case 1: m_sq1.apuOnRegister4001(); break; + case 2: m_sq1.apuOnRegister4002(); break; + case 3: m_sq1.apuOnRegister4003(); break; + case 4: m_sq2.apuOnRegister4004(); break; + case 5: m_sq2.apuOnRegister4005(); break; + case 6: m_sq2.apuOnRegister4006(); break; + case 7: m_sq2.apuOnRegister4007(); break; + case 8: m_trl.apuOnRegister4008(); break; + case 9: m_trl.apuOnRegister4009(); break; + case 10: m_trl.apuOnRegister400A(); break; + case 11: m_trl.apuOnRegister400B(); break; + case 12: m_nos.apuOnRegister400C(); break; + case 13: m_nos.apuOnRegister400D(); break; + case 14: m_nos.apuOnRegister400E(); break; + case 15: m_nos.apuOnRegister400F(); break; + case 16: m_dmc.apuOnRegister4010(); break; + case 17: m_dmc.apuOnRegister4011(); break; + case 18: m_dmc.apuOnRegister4012(); break; + case 19: m_dmc.apuOnRegister4013(); break; + case 20: onRegister4014(); break; + case 21: onRegister4015(); break; + case 22: onRegister4016(); break; + case 23: onRegister4017(); break; + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: blankAccess(); break; + } + } + + m_sq1.apuSq1Clock(); + m_sq2.apuSq2Clock(); + m_nos.apuNosClock(); + + if(m_emu.memory().board()->enableExternalSound()) + m_emu.memory().board()->onApuClock(); + + //apuUpdatePlayback(); + } + + m_trl.apuTrlClock(); + m_dmc.apuDmcClock(); + + if(m_emu.memory().board()->enableExternalSound()) + m_emu.memory().board()->onApuClockSingle(); + + updatePlayback(); + + if(m_checkIrq) + { + if(!m_seqMode) + checkIrq(); + + // This is stupid ... :( + m_cycleFt--; + if(m_cycleFt == 0) + m_checkIrq = false; + } +} + +void Apu::clockDuration() +{ + m_sq1.apuSq1ClockLength(); + m_sq2.apuSq2ClockLength(); + m_nos.apuNosClockLength(); + m_trl.apuTrlClockLength(); + if(m_emu.memory().board()->enableExternalSound()) + m_emu.memory().board()->onApuClockDuration(); + m_doLength = false; +} + +void Apu::clockEnvelope() +{ + m_sq1.apuSq1ClockEnvelope(); + m_sq2.apuSq2ClockEnvelope(); + m_nos.apuNosClockEnvelope(); + m_trl.apuTrlClockEnvelope(); + if(m_emu.memory().board()->enableExternalSound()) + m_emu.memory().board()->onApuClockEnvelope(); + m_doEnv = false; +} + +void Apu::checkIrq() +{ + if(m_irqEnabled) + m_irqFlag = true; + if(m_irqFlag) + m_emu.interrupts().addFlag(Interrupts::IRQ_APU); +} + +void Apu::updatePlayback() +{ + static constexpr std::array audioPulseTable = []() constexpr { + std::array audioPulseTable {}; + for(std::size_t i = 1; i < 32; i++) + audioPulseTable[i] = 95.52 / (8128. / i + 100.); + return audioPulseTable; + }(); + + static constexpr std::array audioTndTable = []() constexpr { + std::array audioTndTable {}; + for(std::size_t i = 1; i < 204; i++) + audioTndTable[i] = 163.67 / (24329. / i + 100.); + return audioTndTable; + }(); + + // Collect the sample + m_pulseOut = audioPulseTable[m_sq1.output() + m_sq2.output()]; + m_tndOut = audioTndTable[(3 * m_trl.output()) + (2 * m_nos.output()) + m_dmc.output()]; + + m_audioX = m_pulseOut + m_tndOut; + + if(m_emu.memory().board()->enableExternalSound()) + { + m_audioX += m_emu.memory().board()->apuGetSample(); + m_audioX /= 2; + } + + m_audioX *= EmuSettings::Audio::internalAmplitude; + + // An implementation of the "Band-Limited Sound Synthesis" algorithm. + // http://www.slack.net/~ant/bl-synth + // Shay Green (swap to e-mail) + // No sure about this, it just add the sample difference in each step. + // Maybe here it is acting just like a low-pass filter ... + if(m_audioX != m_audioX1) + { + if(m_audioX > m_audioX1) + m_audioY += m_audioX - m_audioX1; + else + m_audioY += m_audioX1 - m_audioX; + + m_audioX = m_audioX1; + } + + m_audioYClocks++; + m_timer++; + + const auto audiotimerratio = m_freqL * 2. * 120. / m_sampleRate; + + if(m_timer >= audiotimerratio) + { + // Clock by output sample rate. + m_timer -= audiotimerratio; + + // Get the sum of the samples + m_audioY /= m_audioYClocks; + + // Do filtering, add 2 high-pass and one low-pass filters. See http://wiki.nesdev.com/w/index.php/APUMixer + double audioDcY; + audioDcY = m_highPassFilter2.doFiltering(m_audioY);// 442 Hz + audioDcY = m_highPassFilter1.doFiltering(audioDcY);// 90 Hz + audioDcY = m_lowPassFilter.doFiltering(audioDcY);// 14 KHz + audioDcY = std::clamp(audioDcY, double(-EmuSettings::Audio::internalPeekLimit), double(EmuSettings::Audio::internalPeekLimit)); + + Q_EMIT sampleFinished(audioDcY); + + m_audioY = 0; + m_audioYClocks = 0; + } +} + +void Apu::writeState(QDataStream &dataStream) const +{ + dataStream << m_regIoDb << m_regIoAddr << m_regAccessHappened << m_regAccessW << m_oddCycle << m_irqEnabled << m_irqFlag << m_irqDeltaOccur + << m_seqMode << m_cycleF << m_cycleE << m_cycleL << m_oddL << m_cycleFt << m_checkIrq << m_doEnv << m_doLength << m_inputStrobe; + + m_sq1.apuSq1WriteState(dataStream); + m_sq2.apuSq2WriteState(dataStream); + m_nos.apuNosWriteState(dataStream); + m_trl.apuTrlWriteState(dataStream); + m_dmc.apuDmcWriteState(dataStream); +} + +void Apu::readState(QDataStream &dataStream) +{ + dataStream >> m_regIoDb >> m_regIoAddr >> m_regAccessHappened >> m_regAccessW >> m_oddCycle >> m_irqEnabled >> m_irqFlag >> m_irqDeltaOccur + >> m_seqMode >> m_cycleF >> m_cycleE >> m_cycleL >> m_oddL >> m_cycleFt >> m_checkIrq >> m_doEnv >> m_doLength >> m_inputStrobe; + + m_sq1.apuSq1ReadState(dataStream); + m_sq2.apuSq2ReadState(dataStream); + m_nos.apuNosReadState(dataStream); + m_trl.apuTrlReadState(dataStream); + m_dmc.apuDmcReadState(dataStream); +} + +void Apu::setTimer(double timer) +{ + m_timer = timer; +} + +bool Apu::oddCycle() const +{ + return m_oddCycle; +} + +NesEmulator &Apu::emu() +{ + return m_emu; +} + +const NesEmulator &Apu::emu() const +{ + return m_emu; +} + +ApuDmc &Apu::dmc() +{ + return m_dmc; +} + +const ApuDmc &Apu::dmc() const +{ + return m_dmc; +} + +ApuNos &Apu::nos() +{ + return m_nos; +} + +const ApuNos &Apu::nos() const +{ + return m_nos; +} + +ApuSq1 &Apu::sq1() +{ + return m_sq1; +} + +const ApuSq1 &Apu::sq1() const +{ + return m_sq1; +} + +ApuSq2 &Apu::sq2() +{ + return m_sq2; +} + +const ApuSq2 &Apu::sq2() const +{ + return m_sq2; +} + +ApuTrl &Apu::trl() +{ + return m_trl; +} + +const ApuTrl &Apu::trl() const +{ + return m_trl; +} + +void Apu::setIrqDeltaOccur(bool irqDeltaOccur) +{ + m_irqDeltaOccur = irqDeltaOccur; +} + +bool Apu::regAccessW() const +{ + return m_regAccessW; +} + +quint8 Apu::regIoDb() const +{ + return m_regIoDb; +} + +void Apu::setRegIoDb(quint8 regIoDb) +{ + m_regIoDb = regIoDb; +} + +bool Apu::regAccessHappened() const +{ + return m_regAccessHappened; +} + +quint8 Apu::regIoAddr() const +{ + return m_regIoAddr; +} + +qint32 Apu::sampleRate() const +{ + return m_sampleRate; +} + +void Apu::setSampleRate(qint32 sampleRate) +{ + m_sampleRate = sampleRate; +} + +const std::array, 4> Apu::m_sqDutyCycleSequences { + std::array { 0, 1, 0, 0, 0, 0, 0, 0 }, // 12.5% + std::array { 0, 1, 1, 0, 0, 0, 0, 0 }, // 25.0% + std::array { 0, 1, 1, 1, 1, 0, 0, 0 }, // 50.0% + std::array { 1, 0, 0, 1, 1, 1, 1, 1 }, // 75.0% (25.0% negated) +}; + +const std::array Apu::m_sqDurationTable { + 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, + 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E, +}; diff --git a/nescorelib/emu/apu.h b/nescorelib/emu/apu.h new file mode 100644 index 0000000..9a88918 --- /dev/null +++ b/nescorelib/emu/apu.h @@ -0,0 +1,149 @@ +#pragma once + +#include "nescorelib_global.h" +#include + +// Qt includes +#include +#include + +// system includes +#include + +// local includes +#include "apudmc.h" +#include "apunos.h" +#include "apusq1.h" +#include "apusq2.h" +#include "aputrl.h" +#include "soundlowpassfilter.h" +#include "soundhighpassfilter.h" + +// forward declarations +class QDataStream; +class NesEmulator; + +class NESCORELIB_EXPORT Apu : public QObject +{ + Q_OBJECT + +public: + explicit Apu(NesEmulator &emu); + + void hardReset(); + void softReset(); + + quint8 _ioRead(const quint16 address); + quint8 ioRead(const quint16 address); + void ioWrite(const quint16 address, const quint8 value); + + // io + void blankAccess(); + void onRegister4014(); + void onRegister4015(); + void onRegister4016(); + void onRegister4017(); + void read4015(); + void read4016(); + void read4017(); + + void clock(); + void clockDuration(); + void clockEnvelope(); + void checkIrq(); + void updatePlayback(); + + void writeState(QDataStream &dataStream) const; + void readState(QDataStream &dataStream); + + void setTimer(double timer); + + bool oddCycle() const; + + NesEmulator &emu(); + const NesEmulator &emu() const; + + ApuDmc &dmc(); + const ApuDmc &dmc() const; + ApuNos &nos(); + const ApuNos &nos() const; + ApuSq1 &sq1(); + const ApuSq1 &sq1() const; + ApuSq2 &sq2(); + const ApuSq2 &sq2() const; + ApuTrl &trl(); + const ApuTrl &trl() const; + + void setIrqDeltaOccur(bool irqDeltaOccur); + + bool regAccessW() const; + + quint8 regIoDb() const; + void setRegIoDb(quint8 regIoDb); + + bool regAccessHappened() const; + + quint8 regIoAddr() const; + + qint32 sampleRate() const; + void setSampleRate(qint32 sampleRate); + + static const std::array, 4> m_sqDutyCycleSequences; + static const std::array m_sqDurationTable; + +Q_SIGNALS: + void sampleFinished(qint32 sample); + +private: + NesEmulator &m_emu; + + ApuDmc m_dmc; + ApuNos m_nos; + ApuSq1 m_sq1; + ApuSq2 m_sq2; + ApuTrl m_trl; + + //DATA REG + quint8 m_regIoDb {}; //The data bus + quint8 m_regIoAddr {}; //The address bus + bool m_regAccessHappened {}; // Triggers when cpu accesses apu bus. + bool m_regAccessW {}; //True= write access, False= Read access. + + bool m_oddCycle {}; + bool m_irqEnabled {}; + bool m_irqFlag {}; + bool m_irqDeltaOccur {}; + + bool m_seqMode {}; + static constexpr qint32 m_freqF = 14914; //IRQ clock frequency + static constexpr qint32 m_freqL = 7456; //Length counter clock + static constexpr qint32 m_freqE = 3728; //Envelope counter clock + qint32 m_cycleF {}; + qint32 m_cycleFt {}; + qint32 m_cycleE {}; + qint32 m_cycleL {}; + bool m_oddL {}; + bool m_checkIrq {}; + bool m_doEnv {}; + bool m_doLength {}; + + //DAC + double m_pulseOut {}; + double m_tndOut {}; + + //Output values + double m_audioX {}; + double m_audioX1 {}; + double m_audioY {}; + double m_audioYClocks {}; + static constexpr double m_audioDcR = 0.995; + double m_timer {}; + + SoundLowPassFilter m_lowPassFilter; + SoundHighPassFilter m_highPassFilter1; + SoundHighPassFilter m_highPassFilter2; + + bool m_inputStrobe {}; + + qint32 m_sampleRate { 44100 }; +}; diff --git a/nescorelib/emu/apudmc.cpp b/nescorelib/emu/apudmc.cpp new file mode 100644 index 0000000..ca1526a --- /dev/null +++ b/nescorelib/emu/apudmc.cpp @@ -0,0 +1,217 @@ +#include "apudmc.h" + +// Qt includes +#include + +// local includes +#include "emusettings.h" +#include "apu.h" +#include "nesemulator.h" + +ApuDmc::ApuDmc(Apu &apu) : + m_apu(apu) +{ +} + +void ApuDmc::apuDmcHardReset() +{ + m_apuDmcOutputA = 0; + m_apuDmcOutput = 0; + m_apuDmcPeriodDevider = 0; + m_apuDmcLoopFlag = false; + m_apuDmcRateIndex = 0; + + m_apuDmcIrqEnabled = false; + m_apuDmcDmaAddr = 0xC000; + m_apuDmcAddrRefresh = 0xC000; + m_apuDmcSizeRefresh = 0; + m_apuDmcDmaBits = 1; + m_apuDmcDmaByte = 1; + m_apuDmcPeriodDevider = 0; + m_apuDmcDmaEnabled = false; + m_apuDmcBufferFull = false; + m_apuDmcDmaSize = 0; +} + +void ApuDmc::apuDmcSoftReset() +{ + apuDmcHardReset(); +} + +void ApuDmc::apuDmcClock() +{ + static constexpr std::array freqTable = [](){ + switch(EmuSettings::region) + { + case EmuRegion::NTSC: + return std::array { + 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 + }; + case EmuRegion::PALB: + return std::array { + 398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50 + }; + case EmuRegion::DENDY: + return std::array { + 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 + }; + } + }(); + + if (--m_apuDmcPeriodDevider <= 0) + { + m_apuDmcPeriodDevider = freqTable[m_apuDmcRateIndex]; + + if (m_apuDmcDmaEnabled) + { + if ((m_apuDmcDmaByte & 0x01) != 0) + { + if (m_apuDmcOutputA <= 0x7D) + m_apuDmcOutputA += 2; + } + else + { + if (m_apuDmcOutputA >= 0x02) + m_apuDmcOutputA -= 2; + } + m_apuDmcDmaByte >>= 1; + } + + if (--m_apuDmcDmaBits == 0) + { + m_apuDmcDmaBits = 8; + if (m_apuDmcBufferFull) + { + m_apuDmcBufferFull = false; + m_apuDmcDmaEnabled = true; + m_apuDmcDmaByte = m_apuDmcDmaBuffer; + // RDY ? + if (m_apuDmcDmaSize > 0) + m_apu.emu().dma().assertDmcDma(); + } + else + m_apuDmcDmaEnabled = false; + } + + if (EmuSettings::Audio::ChannelEnabled::DMC) + m_apuDmcOutput = m_apuDmcOutputA; + } +} + +void ApuDmc::apuDmcDoDma() +{ + m_apuDmcBufferFull = true; + + m_apuDmcDmaBuffer = m_apu.emu().memory().read(m_apuDmcDmaAddr); + + if (m_apuDmcDmaAddr == 0xFFFF) + m_apuDmcDmaAddr = 0x8000; + else + m_apuDmcDmaAddr++; + + if (m_apuDmcDmaSize > 0) + m_apuDmcDmaSize--; + + if (m_apuDmcDmaSize == 0) + { + if (m_apuDmcLoopFlag) + { + m_apuDmcDmaSize = m_apuDmcSizeRefresh; + m_apuDmcDmaAddr = m_apuDmcAddrRefresh; + } + else if (m_apuDmcIrqEnabled) + { + m_apu.emu().interrupts().addFlag(Interrupts::IRQ_DMC); + m_apu.setIrqDeltaOccur(true); + } + } +} + +void ApuDmc::apuOnRegister4010() +{ + if (!m_apu.regAccessW()) + return; + + m_apuDmcIrqEnabled = m_apu.regIoDb() & 0x80; + m_apuDmcLoopFlag = m_apu.regIoDb() & 0x40; + + if (!m_apuDmcIrqEnabled) + { + m_apu.setIrqDeltaOccur(false); + m_apu.emu().interrupts().removeFlag(Interrupts::IRQ_DMC); + } + + m_apuDmcRateIndex = m_apu.regIoDb() & 0x0F; +} + +void ApuDmc::apuOnRegister4011() +{ + if (!m_apu.regAccessW()) + return; + + m_apuDmcOutputA = m_apu.regIoDb() & 0x7F; +} + +void ApuDmc::apuOnRegister4012() +{ + if (!m_apu.regAccessW()) + return; + + m_apuDmcAddrRefresh = (m_apu.regIoDb() << 6) | 0xC000; +} + +void ApuDmc::apuOnRegister4013() +{ + if (!m_apu.regAccessW()) + return; + + m_apuDmcSizeRefresh = (m_apu.regIoDb() << 4) | 0x0001; +} + +void ApuDmc::apuDmcOn4015() +{ + // DMC + if (m_apu.regIoDb() & 0x10) + { + if (m_apuDmcDmaSize == 0) + { + m_apuDmcDmaSize = m_apuDmcSizeRefresh; + m_apuDmcDmaAddr = m_apuDmcAddrRefresh; + } + } + else + m_apuDmcDmaSize = 0; + + // Disable DMC IRQ + m_apu.setIrqDeltaOccur(false); + m_apu.emu().interrupts().removeFlag(Interrupts::IRQ_DMC); + + // RDY ? + if (!m_apuDmcBufferFull && m_apuDmcDmaSize > 0) + m_apu.emu().dma().assertDmcDma(); +} + +void ApuDmc::apuDmcRead4015() +{ + if (m_apuDmcDmaSize > 0) + m_apu.setRegIoDb((m_apu.regIoDb() & 0xEF) | 0x10); +} + +void ApuDmc::apuDmcWriteState(QDataStream &dataStream) const +{ + dataStream << m_apuDmcOutputA << m_apuDmcOutput << m_apuDmcPeriodDevider << m_apuDmcIrqEnabled << m_apuDmcLoopFlag + << m_apuDmcRateIndex << m_apuDmcAddrRefresh << m_apuDmcSizeRefresh << m_apuDmcDmaEnabled << m_apuDmcDmaByte + << m_apuDmcDmaBits << m_apuDmcBufferFull << m_apuDmcDmaBuffer << m_apuDmcDmaSize << m_apuDmcDmaAddr; +} + +void ApuDmc::apuDmcReadState(QDataStream &dataStream) +{ + dataStream >> m_apuDmcOutputA >> m_apuDmcOutput >> m_apuDmcPeriodDevider >> m_apuDmcIrqEnabled >> m_apuDmcLoopFlag + >> m_apuDmcRateIndex >> m_apuDmcAddrRefresh >> m_apuDmcSizeRefresh >> m_apuDmcDmaEnabled >> m_apuDmcDmaByte + >> m_apuDmcDmaBits >> m_apuDmcBufferFull >> m_apuDmcDmaBuffer >> m_apuDmcDmaSize >> m_apuDmcDmaAddr; +} + +qint32 ApuDmc::output() const +{ + return m_apuDmcOutput; +} diff --git a/nescorelib/emu/apudmc.h b/nescorelib/emu/apudmc.h new file mode 100644 index 0000000..7bd8a25 --- /dev/null +++ b/nescorelib/emu/apudmc.h @@ -0,0 +1,55 @@ +#pragma once + +#include "nescorelib_global.h" + +// Qt includes +#include + +// forward declarations +class QDataStream; +class Apu; + +class NESCORELIB_EXPORT ApuDmc +{ +public: + explicit ApuDmc(Apu &apu); + + void apuDmcHardReset(); + void apuDmcSoftReset(); + + void apuDmcClock(); + void apuDmcDoDma(); + + void apuOnRegister4010(); + void apuOnRegister4011(); + void apuOnRegister4012(); + void apuOnRegister4013(); + + void apuDmcOn4015(); + void apuDmcRead4015(); + + void apuDmcWriteState(QDataStream &dataStream) const; + void apuDmcReadState(QDataStream &dataStream); + + qint32 output() const; + +private: + Apu &m_apu; + + qint32 m_apuDmcOutputA {}; + qint32 m_apuDmcOutput {}; + qint32 m_apuDmcPeriodDevider {}; + bool m_apuDmcIrqEnabled {}; + bool m_apuDmcLoopFlag {}; + quint8 m_apuDmcRateIndex {}; + quint16 m_apuDmcAddrRefresh {}; + qint32 m_apuDmcSizeRefresh {}; + + bool m_apuDmcDmaEnabled {}; + quint8 m_apuDmcDmaByte {}; + qint32 m_apuDmcDmaBits {}; + bool m_apuDmcBufferFull {}; + quint8 m_apuDmcDmaBuffer {}; + qint32 m_apuDmcDmaSize {}; + quint16 m_apuDmcDmaAddr {}; +}; diff --git a/nescorelib/emu/apunos.cpp b/nescorelib/emu/apunos.cpp new file mode 100644 index 0000000..95d3e62 --- /dev/null +++ b/nescorelib/emu/apunos.cpp @@ -0,0 +1,220 @@ +#include "apunos.h" + +// Qt includes +#include + +// local includes +#include "emusettings.h" +#include "apu.h" + +ApuNos::ApuNos(Apu &apu) : + m_apu(apu) +{ +} + +void ApuNos::apuNosHardReset() +{ + m_apuNosLengthHalt = false; + m_apuNosConstantVolumeEnvelope = false; + m_apuNosVolumeDeviderPeriod = 0; + m_apuNosShiftReg = 1; + m_apuNosTimer = 0; + m_apuNosMode = false; + m_apuNosPeriodDevider = 0; + m_apuNosLengthEnabled = false; + m_apuNosLengthCounter = 0; + m_apuNosEnvelopeStartFlag = false; + m_apuNosEnvelopeDevider = 0; + m_apuNosEnvelopeDecayLevelCounter = 0; + m_apuNosEnvelope = 0; + m_apuNosOutput = 0; + m_apuNosFeedback = 0; + m_apuNosIgnoreReload = false; +} + +void ApuNos::apuNosSoftReset() +{ + apuNosHardReset(); +} + +void ApuNos::apuNosClock() +{ + m_apuNosPeriodDevider--; + if (m_apuNosPeriodDevider > 0) + return; + + m_apuNosPeriodDevider = m_apuNosTimer; + + if (m_apuNosMode) + m_apuNosFeedback = ((m_apuNosShiftReg >> 6) & 0x1) ^ (m_apuNosShiftReg & 0x1); + else + m_apuNosFeedback = ((m_apuNosShiftReg >> 1) & 0x1) ^ (m_apuNosShiftReg & 0x1); + m_apuNosShiftReg >>= 1; + m_apuNosShiftReg = (m_apuNosShiftReg & 0x3FFF) | ((m_apuNosFeedback & 1) << 14); + + if (m_apuNosLengthCounter > 0 && ((m_apuNosShiftReg & 1) == 0)) + { + if (EmuSettings::Audio::ChannelEnabled::NOZ) + m_apuNosOutput = m_apuNosEnvelope; + } + else + m_apuNosOutput = 0; +} + +void ApuNos::apuNosClockLength() +{ + if (m_apuNosLengthCounter > 0 && !m_apuNosLengthHalt) + { + m_apuNosLengthCounter--; + if (m_apu.regAccessHappened()) + { + // This is not a hack, there is some hidden mechanism in the nes, that do reload and clock stuff + if (m_apu.regIoAddr() == 0xF && m_apu.regAccessW()) + { + m_apuNosIgnoreReload = true; + } + } + } +} + +void ApuNos::apuNosClockEnvelope() +{ + if (m_apuNosEnvelopeStartFlag) + { + m_apuNosEnvelopeStartFlag = false; + m_apuNosEnvelopeDecayLevelCounter = 15; + m_apuNosEnvelopeDevider = m_apuNosVolumeDeviderPeriod + 1; + } + else + { + if (m_apuNosEnvelopeDevider > 0) + m_apuNosEnvelopeDevider--; + else + { + m_apuNosEnvelopeDevider = m_apuNosVolumeDeviderPeriod + 1; + if (m_apuNosEnvelopeDecayLevelCounter > 0) + m_apuNosEnvelopeDecayLevelCounter--; + else if (m_apuNosLengthHalt) + m_apuNosEnvelopeDecayLevelCounter = 0xF; + } + } + m_apuNosEnvelope = m_apuNosConstantVolumeEnvelope ? m_apuNosVolumeDeviderPeriod : m_apuNosEnvelopeDecayLevelCounter; +} + +void ApuNos::apuOnRegister400C() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + m_apuNosVolumeDeviderPeriod = m_apu.regIoDb() & 0xF; + m_apuNosLengthHalt = (m_apu.regIoDb() & 0x20) != 0; + m_apuNosConstantVolumeEnvelope = m_apu.regIoDb() & 0x10; + + m_apuNosEnvelope = m_apuNosConstantVolumeEnvelope ? m_apuNosVolumeDeviderPeriod : m_apuNosEnvelopeDecayLevelCounter; +} + +void ApuNos::apuOnRegister400D() +{ +} + +void ApuNos::apuOnRegister400E() +{ + static constexpr std::array freqTable = [](){ + switch(EmuSettings::region) + { + case EmuRegion::NTSC: + return std::array { + 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 + }; + case EmuRegion::PALB: + return std::array { + 4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778 + }; + case EmuRegion::DENDY: + return std::array { + 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 + }; + } + }(); + + // Only writes accepted + if (!m_apu.regAccessW()) + return; + + m_apuNosTimer = freqTable[m_apu.regIoDb() & 0x0F] / 2; + + m_apuNosMode = (m_apu.regIoDb() & 0x80) == 0x80; +} + +void ApuNos::apuOnRegister400F() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + + if (m_apuNosLengthEnabled && !m_apuNosIgnoreReload) + m_apuNosLengthCounter = m_apu.m_sqDurationTable[m_apu.regIoDb() >> 3]; + if (m_apuNosIgnoreReload) + m_apuNosIgnoreReload = false; + m_apuNosEnvelopeStartFlag = true; +} + +void ApuNos::apuNosOn4015() +{ + m_apuNosLengthEnabled = (m_apu.regIoDb() & 0x08) != 0; + if (!m_apuNosLengthEnabled) + m_apuNosLengthCounter = 0; +} + +void ApuNos::apuNosRead4015() +{ + if (m_apuNosLengthCounter > 0) + m_apu.setRegIoDb((m_apu.regIoDb() & 0xF7) | 0x08); +} + +void ApuNos::apuNosWriteState(QDataStream &dataStream) const +{ + dataStream + << m_apuNosLengthHalt + << m_apuNosConstantVolumeEnvelope + << m_apuNosVolumeDeviderPeriod + << m_apuNosTimer + << m_apuNosMode + << m_apuNosPeriodDevider + << m_apuNosLengthEnabled + << m_apuNosLengthCounter + << m_apuNosEnvelopeStartFlag + << m_apuNosEnvelopeDevider + << m_apuNosEnvelopeDecayLevelCounter + << m_apuNosEnvelope + << m_apuNosOutput + << m_apuNosShiftReg + << m_apuNosFeedback + << m_apuNosIgnoreReload; +} + +void ApuNos::apuNosReadState(QDataStream &dataStream) +{ + dataStream + >> m_apuNosLengthHalt + >> m_apuNosConstantVolumeEnvelope + >> m_apuNosVolumeDeviderPeriod + >> m_apuNosTimer + >> m_apuNosMode + >> m_apuNosPeriodDevider + >> m_apuNosLengthEnabled + >> m_apuNosLengthCounter + >> m_apuNosEnvelopeStartFlag + >> m_apuNosEnvelopeDevider + >> m_apuNosEnvelopeDecayLevelCounter + >> m_apuNosEnvelope + >> m_apuNosOutput + >> m_apuNosShiftReg + >> m_apuNosFeedback + >> m_apuNosIgnoreReload; +} + +qint32 ApuNos::output() const +{ + return m_apuNosOutput; +} diff --git a/nescorelib/emu/apunos.h b/nescorelib/emu/apunos.h new file mode 100644 index 0000000..1a298f6 --- /dev/null +++ b/nescorelib/emu/apunos.h @@ -0,0 +1,61 @@ +#pragma once + +#include "nescorelib_global.h" + +// Qt includes +#include + +// forward declarations +class QDataStream; +class Apu; + +class NESCORELIB_EXPORT ApuNos +{ +public: + explicit ApuNos(Apu &apu); + + void apuNosHardReset(); + void apuNosSoftReset(); + + void apuNosClock(); + void apuNosClockLength(); + void apuNosClockEnvelope(); + + void apuOnRegister400C(); + void apuOnRegister400D(); + void apuOnRegister400E(); + void apuOnRegister400F(); + + void apuNosOn4015(); + void apuNosRead4015(); + + void apuNosWriteState(QDataStream &dataStream) const; + void apuNosReadState(QDataStream &dataStream); + + qint32 output() const; + +private: + Apu &m_apu; + + // Reg 1 + bool m_apuNosLengthHalt {}; + bool m_apuNosConstantVolumeEnvelope {}; + quint8 m_apuNosVolumeDeviderPeriod {}; + + // Reg 3 + quint16 m_apuNosTimer {}; + bool m_apuNosMode {}; + + // Controls + qint32 m_apuNosPeriodDevider {}; + bool m_apuNosLengthEnabled {}; + qint32 m_apuNosLengthCounter {}; + bool m_apuNosEnvelopeStartFlag {}; + quint8 m_apuNosEnvelopeDevider {}; + quint8 m_apuNosEnvelopeDecayLevelCounter {}; + quint8 m_apuNosEnvelope {}; + qint32 m_apuNosOutput {}; + qint32 m_apuNosShiftReg {}; + qint32 m_apuNosFeedback {}; + bool m_apuNosIgnoreReload {}; +}; diff --git a/nescorelib/emu/apusq1.cpp b/nescorelib/emu/apusq1.cpp new file mode 100644 index 0000000..80d4d13 --- /dev/null +++ b/nescorelib/emu/apusq1.cpp @@ -0,0 +1,219 @@ +#include "apusq1.h" + +// Qt includes +#include + +// local includes +#include "emusettings.h" +#include "apu.h" + +ApuSq1::ApuSq1(Apu &apu) : + m_apu(apu) +{ +} + +void ApuSq1::apuSq1HardReset() +{ + m_apuSq1DutyCycle = 0; + m_apuSq1LengthHalt = false; + m_apuSq1ConstantVolumeEnvelope = false; + m_apuSq1VolumeDeviderPeriod = 0; + m_apuSq1SweepEnable = false; + m_apuSq1SweepDeviderPeriod = 0; + m_apuSq1SweepNegate = false; + m_apuSq1SweepShiftCount = 0; + m_apuSq1Timer = 0; + m_apuSq1PeriodDevider = 0; + m_apuSq1Seqencer = 0; + m_apuSq1LengthEnabled = false; + m_apuSq1LengthCounter = 0; + m_apuSq1EnvelopeStartFlag = false; + m_apuSq1EnvelopeDevider = 0; + m_apuSq1EnvelopeDecayLevelCounter = 0; + m_apuSq1Envelope = 0; + m_apuSq1SweepCounter = 0; + m_apuSq1SweepReload = false; + m_apuSq1SweepChange = 0; + m_apuSq1ValidFreq = false; + m_apuSq1Output = 0; + m_apuSq1IgnoreReload = false; +} + +void ApuSq1::apuSq1SoftReset() +{ + apuSq1HardReset(); +} + +void ApuSq1::apuSq1Clock() +{ + m_apuSq1PeriodDevider--; + if (m_apuSq1PeriodDevider <= 0) + { + m_apuSq1PeriodDevider = m_apuSq1Timer + 1; + m_apuSq1Seqencer = (m_apuSq1Seqencer + 1) & 0x7; + if (m_apuSq1LengthCounter > 0 && m_apuSq1ValidFreq) + { + if (EmuSettings::Audio::ChannelEnabled::SQ1) + m_apuSq1Output = m_apu.m_sqDutyCycleSequences[m_apuSq1DutyCycle][m_apuSq1Seqencer] * m_apuSq1Envelope; + } + else + m_apuSq1Output = 0; + } +} + +void ApuSq1::apuSq1ClockLength() +{ + if (m_apuSq1LengthCounter > 0 && !m_apuSq1LengthHalt) + { + m_apuSq1LengthCounter--; + if (m_apu.regAccessHappened()) + { + // This is not a hack, there is some hidden mechanism in the nes, that do reload and clock stuff + if (m_apu.regIoAddr() == 0x3 && m_apu.regAccessW()) + { + m_apuSq1IgnoreReload = true; + } + } + } + + // Sweep unit + m_apuSq1SweepCounter--; + if (m_apuSq1SweepCounter == 0) + { + m_apuSq1SweepCounter = m_apuSq1SweepDeviderPeriod + 1; + if (m_apuSq1SweepEnable && m_apuSq1SweepShiftCount > 0 && m_apuSq1ValidFreq) + { + m_apuSq1SweepChange = m_apuSq1Timer >> m_apuSq1SweepShiftCount; + m_apuSq1Timer += m_apuSq1SweepNegate ? ~m_apuSq1SweepChange : m_apuSq1SweepChange; + apuSq1CalculateValidFreq(); + } + } + + if (m_apuSq1SweepReload) + { + m_apuSq1SweepCounter = m_apuSq1SweepDeviderPeriod + 1; + m_apuSq1SweepReload = false; + } +} + +void ApuSq1::apuSq1ClockEnvelope() +{ + if (m_apuSq1EnvelopeStartFlag) + { + m_apuSq1EnvelopeStartFlag = false; + m_apuSq1EnvelopeDecayLevelCounter = 15; + m_apuSq1EnvelopeDevider = m_apuSq1VolumeDeviderPeriod + 1; + } + else + { + if (m_apuSq1EnvelopeDevider > 0) + m_apuSq1EnvelopeDevider--; + else + { + m_apuSq1EnvelopeDevider = m_apuSq1VolumeDeviderPeriod + 1; + if (m_apuSq1EnvelopeDecayLevelCounter > 0) + m_apuSq1EnvelopeDecayLevelCounter--; + else if (m_apuSq1LengthHalt) + m_apuSq1EnvelopeDecayLevelCounter = 0xF; + } + } + m_apuSq1Envelope = m_apuSq1ConstantVolumeEnvelope ? m_apuSq1VolumeDeviderPeriod : m_apuSq1EnvelopeDecayLevelCounter; +} + +void ApuSq1::apuOnRegister4000() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + m_apuSq1DutyCycle = (m_apu.regIoDb() & 0xC0) >> 6; + m_apuSq1VolumeDeviderPeriod = m_apu.regIoDb() & 0xF; + m_apuSq1LengthHalt = m_apu.regIoDb() & 0x20; + m_apuSq1ConstantVolumeEnvelope = m_apu.regIoDb() & 0x10; + + m_apuSq1Envelope = m_apuSq1ConstantVolumeEnvelope ? m_apuSq1VolumeDeviderPeriod : m_apuSq1EnvelopeDecayLevelCounter; +} + +void ApuSq1::apuOnRegister4001() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + + m_apuSq1SweepEnable = (m_apu.regIoDb() & 0x80) == 0x80; + m_apuSq1SweepDeviderPeriod = (m_apu.regIoDb() >> 4) & 7; + m_apuSq1SweepNegate = (m_apu.regIoDb() & 0x8) == 0x8; + m_apuSq1SweepShiftCount = m_apu.regIoDb() & 7; + m_apuSq1SweepReload = true; + apuSq1CalculateValidFreq(); +} + +void ApuSq1::apuOnRegister4002() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + + m_apuSq1Timer = (m_apuSq1Timer & 0xFF00) | m_apu.regIoDb(); + apuSq1CalculateValidFreq(); +} + +void ApuSq1::apuOnRegister4003() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + + m_apuSq1Timer = (m_apuSq1Timer & 0x00FF) | ((m_apu.regIoDb() & 0x7) << 8); + + if (m_apuSq1LengthEnabled && !m_apuSq1IgnoreReload) + { + m_apuSq1LengthCounter = m_apu.m_sqDurationTable[m_apu.regIoDb() >> 3]; + } + + m_apuSq1IgnoreReload = false; + + m_apuSq1Seqencer = 0; + m_apuSq1EnvelopeStartFlag = true; + apuSq1CalculateValidFreq(); +} + +void ApuSq1::apuSq1On4015() +{ + m_apuSq1LengthEnabled = m_apu.regIoDb() & 0x01; + if (!m_apuSq1LengthEnabled) + m_apuSq1LengthCounter = 0; +} + +void ApuSq1::apuSq1Read4015() +{ + if (m_apuSq1LengthCounter > 0) + m_apu.setRegIoDb((m_apu.regIoDb() & 0xFE) | 0x01); +} + +void ApuSq1::apuSq1CalculateValidFreq() +{ + m_apuSq1ValidFreq = (m_apuSq1Timer >= 0x8) && ((m_apuSq1SweepNegate) || (((m_apuSq1Timer + (m_apuSq1Timer >> m_apuSq1SweepShiftCount)) & 0x800) == 0)); +} + +void ApuSq1::apuSq1WriteState(QDataStream &dataStream) const +{ + dataStream << m_apuSq1DutyCycle << m_apuSq1LengthHalt << m_apuSq1ConstantVolumeEnvelope << m_apuSq1VolumeDeviderPeriod + << m_apuSq1SweepEnable << m_apuSq1SweepDeviderPeriod << m_apuSq1SweepNegate << m_apuSq1SweepShiftCount << m_apuSq1Timer + << m_apuSq1PeriodDevider << m_apuSq1Seqencer << m_apuSq1LengthEnabled << m_apuSq1LengthCounter << m_apuSq1EnvelopeStartFlag + << m_apuSq1EnvelopeDevider << m_apuSq1EnvelopeDecayLevelCounter << m_apuSq1Envelope << m_apuSq1SweepCounter + << m_apuSq1SweepReload << m_apuSq1SweepChange << m_apuSq1ValidFreq << m_apuSq1Output << m_apuSq1IgnoreReload; +} + +void ApuSq1::apuSq1ReadState(QDataStream &dataStream) +{ + dataStream >> m_apuSq1DutyCycle >> m_apuSq1LengthHalt >> m_apuSq1ConstantVolumeEnvelope >> m_apuSq1VolumeDeviderPeriod + >> m_apuSq1SweepEnable >> m_apuSq1SweepDeviderPeriod >> m_apuSq1SweepNegate >> m_apuSq1SweepShiftCount >> m_apuSq1Timer + >> m_apuSq1PeriodDevider >> m_apuSq1Seqencer >> m_apuSq1LengthEnabled >> m_apuSq1LengthCounter >> m_apuSq1EnvelopeStartFlag + >> m_apuSq1EnvelopeDevider >> m_apuSq1EnvelopeDecayLevelCounter >> m_apuSq1Envelope >> m_apuSq1SweepCounter + >> m_apuSq1SweepReload >> m_apuSq1SweepChange >> m_apuSq1ValidFreq >> m_apuSq1Output >> m_apuSq1IgnoreReload; +} + +qint32 ApuSq1::output() const +{ + return m_apuSq1Output; +} diff --git a/nescorelib/emu/apusq1.h b/nescorelib/emu/apusq1.h new file mode 100644 index 0000000..cc28da7 --- /dev/null +++ b/nescorelib/emu/apusq1.h @@ -0,0 +1,70 @@ +#pragma once + +#include "nescorelib_global.h" + +// Qt includes +#include + +// forward declarations +class QDataStream; +class Apu; + +class NESCORELIB_EXPORT ApuSq1 +{ +public: + explicit ApuSq1(Apu &apu); + + void apuSq1HardReset(); + void apuSq1SoftReset(); + + void apuSq1Clock(); + void apuSq1ClockLength(); + void apuSq1ClockEnvelope(); + + void apuOnRegister4000(); + void apuOnRegister4001(); + void apuOnRegister4002(); + void apuOnRegister4003(); + + void apuSq1On4015(); + void apuSq1Read4015(); + + void apuSq1CalculateValidFreq(); + + void apuSq1WriteState(QDataStream &dataStream) const; + void apuSq1ReadState(QDataStream &dataStream); + + qint32 output() const; + +private: + Apu &m_apu; + + // Reg 1 + quint8 m_apuSq1DutyCycle {}; + bool m_apuSq1LengthHalt {}; + bool m_apuSq1ConstantVolumeEnvelope {}; + quint8 m_apuSq1VolumeDeviderPeriod {}; + // Reg 2 + bool m_apuSq1SweepEnable {}; + quint8 m_apuSq1SweepDeviderPeriod {}; + bool m_apuSq1SweepNegate {}; + quint8 m_apuSq1SweepShiftCount {}; + // Reg 3 + qint32 m_apuSq1Timer {}; + + // Controls + qint32 m_apuSq1PeriodDevider {}; + quint8 m_apuSq1Seqencer {}; + bool m_apuSq1LengthEnabled {}; + qint32 m_apuSq1LengthCounter {}; + bool m_apuSq1EnvelopeStartFlag {}; + quint8 m_apuSq1EnvelopeDevider {}; + quint8 m_apuSq1EnvelopeDecayLevelCounter {}; + quint8 m_apuSq1Envelope {}; + qint32 m_apuSq1SweepCounter {}; + bool m_apuSq1SweepReload {}; + qint32 m_apuSq1SweepChange {}; + bool m_apuSq1ValidFreq {}; + qint32 m_apuSq1Output {}; + bool m_apuSq1IgnoreReload {}; +}; diff --git a/nescorelib/emu/apusq2.cpp b/nescorelib/emu/apusq2.cpp new file mode 100644 index 0000000..b3034e4 --- /dev/null +++ b/nescorelib/emu/apusq2.cpp @@ -0,0 +1,216 @@ +#include "apusq2.h" + +// Qt includes +#include + +// local includes +#include "emusettings.h" +#include "apu.h" + +ApuSq2::ApuSq2(Apu &apu) : + m_apu(apu) +{ +} + +void ApuSq2::apuSq2HardReset() +{ + m_apuSq2DutyCycle = 0; + m_apuSq2LengthHalt = false; + m_apuSq2ConstantVolumeEnvelope = false; + m_apuSq2VolumeDeviderPeriod = 0; + m_apuSq2SweepEnable = false; + m_apuSq2SweepDeviderPeriod = 0; + m_apuSq2SweepNegate = false; + m_apuSq2SweepShiftCount = 0; + m_apuSq2Timer = 0; + m_apuSq2PeriodDevider = 0; + m_apuSq2Seqencer = 0; + m_apuSq2LengthEnabled = false; + m_apuSq2LengthCounter = 0; + m_apuSq2EnvelopeStartFlag = false; + m_apuSq2EnvelopeDevider = 0; + m_apuSq2EnvelopeDecayLevelCounter = 0; + m_apuSq2Envelope = 0; + m_apuSq2SweepCounter = 0; + m_apuSq2SweepReload = false; + m_apuSq2SweepChange = 0; + m_apuSq2ValidFreq = false; + m_apuSq2Output = 0; + m_apuSq2IgnoreReload = false; +} + +void ApuSq2::apuSq2SoftReset() +{ + apuSq2HardReset(); +} + +void ApuSq2::apuSq2Clock() +{ + m_apuSq2PeriodDevider--; + if (m_apuSq2PeriodDevider <= 0) + { + m_apuSq2PeriodDevider = m_apuSq2Timer + 1; + m_apuSq2Seqencer = (m_apuSq2Seqencer + 1) & 0x7; + if (m_apuSq2LengthCounter > 0 && m_apuSq2ValidFreq) + { + if (EmuSettings::Audio::ChannelEnabled::SQ2) + m_apuSq2Output = m_apu.m_sqDutyCycleSequences[m_apuSq2DutyCycle][m_apuSq2Seqencer] * m_apuSq2Envelope; + } + else + m_apuSq2Output = 0; + } +} + +void ApuSq2::apuSq2ClockLength() +{ + if (m_apuSq2LengthCounter > 0 && !m_apuSq2LengthHalt) + { + m_apuSq2LengthCounter--; + if (m_apu.regAccessHappened()) + { + // This is not a hack, there is some hidden mechanism in the nes, that do reload and clock stuff + if (m_apu.regIoAddr() == 0x7 && m_apu.regAccessW()) + m_apuSq2IgnoreReload = true; + } + } + + // Sweep unit + m_apuSq2SweepCounter--; + if (m_apuSq2SweepCounter == 0) + { + m_apuSq2SweepCounter = m_apuSq2SweepDeviderPeriod + 1; + if (m_apuSq2SweepEnable && m_apuSq2SweepShiftCount > 0 && m_apuSq2ValidFreq) + { + m_apuSq2SweepChange = m_apuSq2Timer >> m_apuSq2SweepShiftCount; + m_apuSq2Timer += m_apuSq2SweepNegate ? -m_apuSq2SweepChange : m_apuSq2SweepChange; + apuSq2CalculateValidFreq(); + } + } + else if (m_apuSq2SweepReload) + { + m_apuSq2SweepCounter = m_apuSq2SweepDeviderPeriod + 1; + m_apuSq2SweepReload = false; + } +} + +void ApuSq2::apuSq2ClockEnvelope() +{ + if (m_apuSq2EnvelopeStartFlag) + { + m_apuSq2EnvelopeStartFlag = false; + m_apuSq2EnvelopeDecayLevelCounter = 15; + m_apuSq2EnvelopeDevider = m_apuSq2VolumeDeviderPeriod + 1; + } + else + { + if (m_apuSq2EnvelopeDevider > 0) + m_apuSq2EnvelopeDevider--; + else + { + m_apuSq2EnvelopeDevider = m_apuSq2VolumeDeviderPeriod + 1; + if (m_apuSq2EnvelopeDecayLevelCounter > 0) + m_apuSq2EnvelopeDecayLevelCounter--; + else if (m_apuSq2LengthHalt) + m_apuSq2EnvelopeDecayLevelCounter = 0xF; + } + } + + m_apuSq2Envelope = m_apuSq2ConstantVolumeEnvelope ? m_apuSq2VolumeDeviderPeriod : m_apuSq2EnvelopeDecayLevelCounter; +} + +void ApuSq2::apuOnRegister4004() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + + m_apuSq2DutyCycle = (m_apu.regIoDb() & 0xC0) >> 6; + m_apuSq2VolumeDeviderPeriod = m_apu.regIoDb() & 0xF; + m_apuSq2LengthHalt = m_apu.regIoDb() & 0x20; + m_apuSq2ConstantVolumeEnvelope = m_apu.regIoDb() & 0x10; + + m_apuSq2Envelope = m_apuSq2ConstantVolumeEnvelope ? m_apuSq2VolumeDeviderPeriod : m_apuSq2EnvelopeDecayLevelCounter; +} + +void ApuSq2::apuOnRegister4005() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + + m_apuSq2SweepEnable = (m_apu.regIoDb() & 0x80) == 0x80; + m_apuSq2SweepDeviderPeriod = (m_apu.regIoDb() >> 4) & 7; + m_apuSq2SweepNegate = (m_apu.regIoDb() & 0x8) == 0x8; + m_apuSq2SweepShiftCount = m_apu.regIoDb() & 7; + m_apuSq2SweepReload = true; + apuSq2CalculateValidFreq(); +} + +void ApuSq2::apuOnRegister4006() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + + m_apuSq2Timer = (m_apuSq2Timer & 0xFF00) | m_apu.regIoDb(); + apuSq2CalculateValidFreq(); +} + +void ApuSq2::apuOnRegister4007() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + + m_apuSq2Timer = (m_apuSq2Timer & 0x00FF) | ((m_apu.regIoDb() & 0x7) << 8); + + if (m_apuSq2LengthEnabled && !m_apuSq2IgnoreReload) + m_apuSq2LengthCounter = m_apu.m_sqDurationTable[m_apu.regIoDb() >> 3]; + if (m_apuSq2IgnoreReload) + m_apuSq2IgnoreReload = false; + + m_apuSq2Seqencer = 0; + m_apuSq2EnvelopeStartFlag = true; + apuSq2CalculateValidFreq(); +} + +void ApuSq2::apuSq2On4015() +{ + m_apuSq2LengthEnabled = (m_apu.regIoDb() & 0x02) != 0; + if (!m_apuSq2LengthEnabled) + m_apuSq2LengthCounter = 0; +} + +void ApuSq2::apuSq2Read4015() +{ + if (m_apuSq2LengthCounter > 0) + m_apu.setRegIoDb((m_apu.regIoDb() & 0xFD) | 0x02); +} + +void ApuSq2::apuSq2CalculateValidFreq() +{ + m_apuSq2ValidFreq = (m_apuSq2Timer >= 0x8) && ((m_apuSq2SweepNegate) || (((m_apuSq2Timer + (m_apuSq2Timer >> m_apuSq2SweepShiftCount)) & 0x800) == 0)); +} + +void ApuSq2::apuSq2WriteState(QDataStream &dataStream) const +{ + dataStream + << m_apuSq2DutyCycle << m_apuSq2LengthHalt << m_apuSq2ConstantVolumeEnvelope << m_apuSq2VolumeDeviderPeriod << m_apuSq2SweepEnable + << m_apuSq2SweepDeviderPeriod << m_apuSq2SweepNegate << m_apuSq2SweepShiftCount << m_apuSq2Timer << m_apuSq2PeriodDevider << m_apuSq2Seqencer + << m_apuSq2LengthEnabled << m_apuSq2LengthCounter << m_apuSq2EnvelopeStartFlag << m_apuSq2EnvelopeDevider << m_apuSq2EnvelopeDecayLevelCounter + << m_apuSq2Envelope << m_apuSq2SweepCounter << m_apuSq2SweepReload << m_apuSq2SweepChange << m_apuSq2ValidFreq << m_apuSq2Output << m_apuSq2IgnoreReload; +} + +void ApuSq2::apuSq2ReadState(QDataStream &dataStream) +{ + dataStream + >> m_apuSq2DutyCycle >> m_apuSq2LengthHalt >> m_apuSq2ConstantVolumeEnvelope >> m_apuSq2VolumeDeviderPeriod >> m_apuSq2SweepEnable + >> m_apuSq2SweepDeviderPeriod >> m_apuSq2SweepNegate >> m_apuSq2SweepShiftCount >> m_apuSq2Timer >> m_apuSq2PeriodDevider >> m_apuSq2Seqencer + >> m_apuSq2LengthEnabled >> m_apuSq2LengthCounter >> m_apuSq2EnvelopeStartFlag >> m_apuSq2EnvelopeDevider >> m_apuSq2EnvelopeDecayLevelCounter + >> m_apuSq2Envelope >> m_apuSq2SweepCounter >> m_apuSq2SweepReload >> m_apuSq2SweepChange >> m_apuSq2ValidFreq >> m_apuSq2Output >> m_apuSq2IgnoreReload; +} + +qint32 ApuSq2::output() const +{ + return m_apuSq2Output; +} diff --git a/nescorelib/emu/apusq2.h b/nescorelib/emu/apusq2.h new file mode 100644 index 0000000..48755f2 --- /dev/null +++ b/nescorelib/emu/apusq2.h @@ -0,0 +1,70 @@ +#pragma once + +#include "nescorelib_global.h" + +// Qt includes +#include + +// forward declarations +class QDataStream; +class Apu; + +class NESCORELIB_EXPORT ApuSq2 +{ +public: + explicit ApuSq2(Apu &apu); + + void apuSq2HardReset(); + void apuSq2SoftReset(); + + void apuSq2Clock(); + void apuSq2ClockLength(); + void apuSq2ClockEnvelope(); + + void apuOnRegister4004(); + void apuOnRegister4005(); + void apuOnRegister4006(); + void apuOnRegister4007(); + + void apuSq2On4015(); + void apuSq2Read4015(); + + void apuSq2CalculateValidFreq(); + + void apuSq2WriteState(QDataStream &dataStream) const; + void apuSq2ReadState(QDataStream &dataStream); + + qint32 output() const; + +private: + Apu &m_apu; + + // Reg 1 + quint8 m_apuSq2DutyCycle {}; + bool m_apuSq2LengthHalt {}; + bool m_apuSq2ConstantVolumeEnvelope {}; + quint8 m_apuSq2VolumeDeviderPeriod {}; + // Reg 2 + bool m_apuSq2SweepEnable {}; + quint8 m_apuSq2SweepDeviderPeriod {}; + bool m_apuSq2SweepNegate {}; + quint8 m_apuSq2SweepShiftCount {}; + // Reg 3 + qint32 m_apuSq2Timer {}; + + // Controls + qint32 m_apuSq2PeriodDevider {}; + quint8 m_apuSq2Seqencer {}; + bool m_apuSq2LengthEnabled {}; + qint32 m_apuSq2LengthCounter {}; + bool m_apuSq2EnvelopeStartFlag {}; + quint8 m_apuSq2EnvelopeDevider {}; + quint8 m_apuSq2EnvelopeDecayLevelCounter {}; + quint8 m_apuSq2Envelope {}; + qint32 m_apuSq2SweepCounter {}; + bool m_apuSq2SweepReload {}; + qint32 m_apuSq2SweepChange {}; + bool m_apuSq2ValidFreq {}; + qint32 m_apuSq2Output {}; + bool m_apuSq2IgnoreReload {}; +}; diff --git a/nescorelib/emu/aputrl.cpp b/nescorelib/emu/aputrl.cpp new file mode 100644 index 0000000..92f8257 --- /dev/null +++ b/nescorelib/emu/aputrl.cpp @@ -0,0 +1,174 @@ +#include "aputrl.h" + +// Qt includes +#include + +// local includes +#include "emusettings.h" +#include "apu.h" + +ApuTrl::ApuTrl(Apu &apu) : + m_apu(apu) +{ +} + +void ApuTrl::apuTrlHardReset() +{ + m_apuTrlLinerControlFlag = false; + m_apuTrlLinerControlReload = 0; + m_apuTrlTimer = 0; + m_apuTrlLengthEnabled = false; + m_apuTrlLengthCounter = 0; + m_apuTrlLinerControlReloadFlag = false; + m_apuTrlLinerCounter = 0; + m_apuTrlOutput = 0; + m_apuTrlPeriodDevider = 0; + m_apuTrlStep = 0; + m_apuTrlIgnoreReload = false; +} + +void ApuTrl::apuTrlSoftReset() +{ + apuTrlHardReset(); +} + +void ApuTrl::apuTrlClock() +{ + static constexpr std::array stepSeq { + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + }; + + m_apuTrlPeriodDevider--; + if (m_apuTrlPeriodDevider <= 0) + { + m_apuTrlPeriodDevider = m_apuTrlTimer + 1; + + if (m_apuTrlLengthCounter > 0 && m_apuTrlLinerCounter > 0) + { + if (m_apuTrlTimer >= 4) + { + m_apuTrlStep++; + m_apuTrlStep &= 0x1F; + if (EmuSettings::Audio::ChannelEnabled::TRL) + m_apuTrlOutput = stepSeq[m_apuTrlStep]; + } + } + } +} + +void ApuTrl::apuTrlClockLength() +{ + if (m_apuTrlLengthCounter > 0 && !m_apuTrlLinerControlFlag) + { + m_apuTrlLengthCounter--; + if (m_apu.regAccessHappened()) + { + // This is not a hack, there is some hidden mechanism in the nes, that do reload and clock stuff + if (m_apu.regIoAddr() == 0xB && m_apu.regAccessW()) + { + m_apuTrlIgnoreReload = true; + } + } + } +} + +void ApuTrl::apuTrlClockEnvelope() +{ + if (m_apuTrlLinerControlReloadFlag) + { + m_apuTrlLinerCounter = m_apuTrlLinerControlReload; + } + else + { + if (m_apuTrlLinerCounter > 0) + m_apuTrlLinerCounter--; + } + if (!m_apuTrlLinerControlFlag) + m_apuTrlLinerControlReloadFlag = false; +} + +void ApuTrl::apuOnRegister4008() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + m_apuTrlLinerControlFlag = (m_apu.regIoDb() & 0x80) == 0x80; + m_apuTrlLinerControlReload = m_apu.regIoDb() & 0x7F; +} + +void ApuTrl::apuOnRegister4009() +{ +} + +void ApuTrl::apuOnRegister400A() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + m_apuTrlTimer = (m_apuTrlTimer & 0x7F00) | m_apu.regIoDb(); +} + +void ApuTrl::apuOnRegister400B() +{ + // Only writes accepted + if (!m_apu.regAccessW()) + return; + m_apuTrlTimer = (m_apuTrlTimer & 0x00FF) | (m_apu.regIoDb() & 0x7) << 8; + + if (m_apuTrlLengthEnabled && !m_apuTrlIgnoreReload) + m_apuTrlLengthCounter = m_apu.m_sqDurationTable[m_apu.regIoDb() >> 3]; + if (m_apuTrlIgnoreReload) + m_apuTrlIgnoreReload = false; + m_apuTrlLinerControlReloadFlag = true; +} + +void ApuTrl::apuTrlOn4015() +{ + m_apuTrlLengthEnabled = (m_apu.regIoDb() & 0x04) != 0; + if (!m_apuTrlLengthEnabled) + m_apuTrlLengthCounter = 0; +} + +void ApuTrl::apuTrlRead4015() +{ + if (m_apuTrlLengthCounter > 0) + m_apu.setRegIoDb((m_apu.regIoDb() & 0xFB) | 0x04); +} + +void ApuTrl::apuTrlWriteState(QDataStream &dataStream) const +{ + dataStream + << m_apuTrlLinerControlFlag + << m_apuTrlLinerControlReload + << m_apuTrlTimer + << m_apuTrlLengthEnabled + << m_apuTrlLengthCounter + << m_apuTrlLinerControlReloadFlag + << m_apuTrlLinerCounter + << m_apuTrlOutput + << m_apuTrlPeriodDevider + << m_apuTrlStep + << m_apuTrlIgnoreReload; +} + +void ApuTrl::apuTrlReadState(QDataStream &dataStream) +{ + dataStream + >> m_apuTrlLinerControlFlag + >> m_apuTrlLinerControlReload + >> m_apuTrlTimer + >> m_apuTrlLengthEnabled + >> m_apuTrlLengthCounter + >> m_apuTrlLinerControlReloadFlag + >> m_apuTrlLinerCounter + >> m_apuTrlOutput + >> m_apuTrlPeriodDevider + >> m_apuTrlStep + >> m_apuTrlIgnoreReload; +} + +qint32 ApuTrl::output() const +{ + return m_apuTrlOutput; +} diff --git a/nescorelib/emu/aputrl.h b/nescorelib/emu/aputrl.h new file mode 100644 index 0000000..2372679 --- /dev/null +++ b/nescorelib/emu/aputrl.h @@ -0,0 +1,54 @@ +#pragma once + +#include "nescorelib_global.h" + +// Qt includes +#include + +// forward declarations +class QDataStream; +class Apu; + +class NESCORELIB_EXPORT ApuTrl +{ +public: + explicit ApuTrl(Apu &apu); + + void apuTrlHardReset(); + void apuTrlSoftReset(); + + void apuTrlClock(); + void apuTrlClockLength(); + void apuTrlClockEnvelope(); + + void apuOnRegister4008(); + void apuOnRegister4009(); + void apuOnRegister400A(); + void apuOnRegister400B(); + + void apuTrlOn4015(); + void apuTrlRead4015(); + + void apuTrlWriteState(QDataStream &dataStream) const; + void apuTrlReadState(QDataStream &dataStream); + + qint32 output() const; + +private: + Apu &m_apu; + + // Reg1 + bool m_apuTrlLinerControlFlag {}; + quint8 m_apuTrlLinerControlReload {}; + // Reg2 + quint16 m_apuTrlTimer {}; + + bool m_apuTrlLengthEnabled {}; + quint8 m_apuTrlLengthCounter {}; + bool m_apuTrlLinerControlReloadFlag {}; + quint8 m_apuTrlLinerCounter {}; + qint32 m_apuTrlOutput {}; + qint32 m_apuTrlPeriodDevider {}; + qint32 m_apuTrlStep {}; + bool m_apuTrlIgnoreReload {}; +}; diff --git a/nescorelib/emu/cpu.cpp b/nescorelib/emu/cpu.cpp new file mode 100644 index 0000000..80f5bbb --- /dev/null +++ b/nescorelib/emu/cpu.cpp @@ -0,0 +1,1268 @@ +#include "cpu.h" + +// Qt includes +#include + +// local includes +#include "nesemulator.h" + +Cpu::Cpu(NesEmulator &emu) : + m_emu(emu) +{ +} + +quint8 Cpu::getRegisterP() const +{ + return (m_flagN ? 0x80 : 0) | + (m_flagV ? 0x40 : 0) | + (m_flagD ? 0x08 : 0) | + (m_flagI ? 0x04 : 0) | + (m_flagZ ? 0x02 : 0) | + (m_flagC ? 0x01 : 0) | 0x20; +} + +void Cpu::setRegisterP(const quint8 value) +{ + m_flagN = value & 0x80; + m_flagV = value & 0x40; + m_flagD = value & 0x08; + m_flagI = value & 0x04; + m_flagZ = value & 0x02; + m_flagC = value & 0x01; +} + +quint8 Cpu::getRegisterPb() const +{ + return (m_flagN ? 0x80 : 0) | + (m_flagV ? 0x40 : 0) | + (m_flagD ? 0x08 : 0) | + (m_flagI ? 0x04 : 0) | + (m_flagZ ? 0x02 : 0) | + (m_flagC ? 0x01 : 0) | 0x30; +} + +void Cpu::clock() +{ + static constexpr std::array cpuAddressings { + // 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 + // 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF + /*0x0*/&Cpu::imp____, &Cpu::indX_r_, &Cpu::imA____, &Cpu::indX_w_, &Cpu::zpg_r__, &Cpu::zpg_r__, &Cpu::zpg_rw_, &Cpu::zpg_w__, + &Cpu::imA____, &Cpu::imm____, &Cpu::imA____, &Cpu::imm____, &Cpu::abs_r__, &Cpu::abs_r__, &Cpu::abs_rw_, &Cpu::abs_w__, // 0x0 + /*0x1*/&Cpu::imp____, &Cpu::indY_r_, &Cpu::imp____, &Cpu::indY_w_, &Cpu::zpgX_r_, &Cpu::zpgX_r_, &Cpu::zpgX_rw, &Cpu::zpgX_w_, + &Cpu::imA____, &Cpu::absY_r_, &Cpu::imA____, &Cpu::absY_w_, &Cpu::absX_r_, &Cpu::absX_r_, &Cpu::absX_rw, &Cpu::absX_w_, // 0x1 + /*0x2*/&Cpu::imp____, &Cpu::indX_r_, &Cpu::imA____, &Cpu::indX_w_, &Cpu::zpg_r__, &Cpu::zpg_r__, &Cpu::zpg_rw_, &Cpu::zpg_w__, + &Cpu::imA____, &Cpu::imm____, &Cpu::imA____, &Cpu::imm____, &Cpu::abs_r__, &Cpu::abs_r__, &Cpu::abs_rw_, &Cpu::abs_w__, // 0x2 + /*0x3*/&Cpu::imp____, &Cpu::indY_r_, &Cpu::imp____, &Cpu::indY_w_, &Cpu::zpgX_r_, &Cpu::zpgX_r_, &Cpu::zpgX_rw, &Cpu::zpgX_w_, + &Cpu::imA____, &Cpu::absY_r_, &Cpu::imA____, &Cpu::absY_w_, &Cpu::absX_r_, &Cpu::absX_r_, &Cpu::absX_rw, &Cpu::absX_w_, // 0x3 + /*0x4*/&Cpu::imA____, &Cpu::indX_r_, &Cpu::imA____, &Cpu::indX_w_, &Cpu::zpg_r__, &Cpu::zpg_r__, &Cpu::zpg_rw_, &Cpu::zpg_w__, + &Cpu::imA____, &Cpu::imm____, &Cpu::imA____, &Cpu::imm____, &Cpu::abs_w__, &Cpu::abs_r__, &Cpu::abs_rw_, &Cpu::abs_w__, // 0x4 + /*0x5*/&Cpu::imp____, &Cpu::indY_r_, &Cpu::imp____, &Cpu::indY_w_, &Cpu::zpgX_r_, &Cpu::zpgX_r_, &Cpu::zpgX_rw, &Cpu::zpgX_w_, + &Cpu::imA____, &Cpu::absY_r_, &Cpu::imA____, &Cpu::absY_w_, &Cpu::absX_r_, &Cpu::absX_r_, &Cpu::absX_rw, &Cpu::absX_w_, // 0x5 + /*0x6*/&Cpu::imA____, &Cpu::indX_r_, &Cpu::imA____, &Cpu::indX_w_, &Cpu::zpg_r__, &Cpu::zpg_r__, &Cpu::zpg_rw_, &Cpu::zpg_w__, + &Cpu::imA____, &Cpu::imm____, &Cpu::imA____, &Cpu::imm____, &Cpu::imp____, &Cpu::abs_r__, &Cpu::abs_rw_, &Cpu::abs_w__, // 0x6 + /*0x7*/&Cpu::imp____, &Cpu::indY_r_, &Cpu::imp____, &Cpu::indY_w_, &Cpu::zpgX_r_, &Cpu::zpgX_r_, &Cpu::zpgX_rw, &Cpu::zpgX_w_, + &Cpu::imA____, &Cpu::absY_r_, &Cpu::imA____, &Cpu::absY_w_, &Cpu::absX_r_, &Cpu::absX_r_, &Cpu::absX_rw, &Cpu::absX_w_, // 0x7 + /*0x8*/&Cpu::imm____, &Cpu::indX_w_, &Cpu::imm____, &Cpu::indX_w_, &Cpu::zpg_w__, &Cpu::zpg_w__, &Cpu::zpg_w__, &Cpu::zpg_w__, + &Cpu::imA____, &Cpu::imm____, &Cpu::imA____, &Cpu::imm____, &Cpu::abs_w__, &Cpu::abs_w__, &Cpu::abs_w__, &Cpu::abs_w__, // 0x8 + /*0x9*/&Cpu::imp____, &Cpu::indY_w_, &Cpu::imp____, &Cpu::indY_w_, &Cpu::zpgX_w_, &Cpu::zpgX_w_, &Cpu::zpgY_w_, &Cpu::zpgY_w_, + &Cpu::imA____, &Cpu::absY_w_, &Cpu::imA____, &Cpu::absY_w_, &Cpu::abs_w__, &Cpu::absX_w_, &Cpu::abs_w__, &Cpu::absY_w_, // 0x9 + /*0xA*/&Cpu::imm____, &Cpu::indX_r_, &Cpu::imm____, &Cpu::indX_r_, &Cpu::zpg_r__, &Cpu::zpg_r__, &Cpu::zpg_r__, &Cpu::zpg_r__, + &Cpu::imA____, &Cpu::imm____, &Cpu::imA____, &Cpu::imm____, &Cpu::abs_r__, &Cpu::abs_r__, &Cpu::abs_r__, &Cpu::abs_r__, // 0xA + /*0xB*/&Cpu::imp____, &Cpu::indY_r_, &Cpu::imp____, &Cpu::indY_r_, &Cpu::zpgX_r_, &Cpu::zpgX_r_, &Cpu::zpgY_r_, &Cpu::zpgY_r_, + &Cpu::imA____, &Cpu::absY_r_, &Cpu::imA____, &Cpu::absY_r_, &Cpu::absX_r_, &Cpu::absX_r_, &Cpu::absY_r_, &Cpu::absY_r_, // 0xB + /*0xC*/&Cpu::imm____, &Cpu::indX_r_, &Cpu::imm____, &Cpu::indX_r_, &Cpu::zpg_r__, &Cpu::zpg_r__, &Cpu::zpg_rw_, &Cpu::zpg_r__, + &Cpu::imA____, &Cpu::imm____, &Cpu::imA____, &Cpu::imm____, &Cpu::abs_r__, &Cpu::abs_r__, &Cpu::abs_rw_, &Cpu::abs_r__, // 0xC + /*0xD*/&Cpu::imp____, &Cpu::indY_r_, &Cpu::imp____, &Cpu::indY_rw, &Cpu::zpgX_r_, &Cpu::zpgX_r_, &Cpu::zpgX_rw, &Cpu::zpgX_rw, + &Cpu::imA____, &Cpu::absY_r_, &Cpu::imA____, &Cpu::absY_rw, &Cpu::absX_r_, &Cpu::absX_r_, &Cpu::absX_rw, &Cpu::absX_rw, // 0xD + /*0xE*/&Cpu::imm____, &Cpu::indX_r_, &Cpu::imm____, &Cpu::indX_w_, &Cpu::zpg_r__, &Cpu::zpg_r__, &Cpu::zpg_rw_, &Cpu::zpg_w__, + &Cpu::imA____, &Cpu::imm____, &Cpu::imA____, &Cpu::imm____, &Cpu::abs_r__, &Cpu::abs_r__, &Cpu::abs_rw_, &Cpu::abs_w__, // 0xE + /*0xF*/&Cpu::imp____, &Cpu::indY_r_, &Cpu::imp____, &Cpu::indY_w_, &Cpu::zpgX_r_, &Cpu::zpgX_r_, &Cpu::zpgX_rw, &Cpu::zpgX_w_, + &Cpu::imA____, &Cpu::absY_r_, &Cpu::imA____, &Cpu::absY_w_, &Cpu::absX_r_, &Cpu::absX_r_, &Cpu::absX_rw, &Cpu::absX_w_, // 0xF + }; + + static constexpr std::array cpuInstructions { + // 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 + // 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF + /*0x0*/&Cpu::brk__, &Cpu::ora__, &Cpu::nop__, &Cpu::slo__, &Cpu::nop__, &Cpu::ora__, &Cpu::asl_m, &Cpu::slo__, + &Cpu::php__, &Cpu::ora__, &Cpu::asl_a, &Cpu::anc__, &Cpu::nop__, &Cpu::ora__, &Cpu::asl_m, &Cpu::slo__, // 0x0 + /*0x1*/&Cpu::bpl__, &Cpu::ora__, &Cpu::nop__, &Cpu::slo__, &Cpu::nop__, &Cpu::ora__, &Cpu::asl_m, &Cpu::slo__, + &Cpu::clc__, &Cpu::ora__, &Cpu::nop__, &Cpu::slo__, &Cpu::nop__, &Cpu::ora__, &Cpu::asl_m, &Cpu::slo__, // 0x1 + /*0x2*/&Cpu::jsr__, &Cpu::and__, &Cpu::nop__, &Cpu::rla__, &Cpu::bit__, &Cpu::and__, &Cpu::rol_m, &Cpu::rla__, + &Cpu::plp__, &Cpu::and__, &Cpu::rol_a, &Cpu::anc__, &Cpu::bit__, &Cpu::and__, &Cpu::rol_m, &Cpu::rla__, // 0x2 + /*0x3*/&Cpu::bmi__, &Cpu::and__, &Cpu::nop__, &Cpu::rla__, &Cpu::nop__, &Cpu::and__, &Cpu::rol_m, &Cpu::rla__, + &Cpu::sec__, &Cpu::and__, &Cpu::nop__, &Cpu::rla__, &Cpu::nop__, &Cpu::and__, &Cpu::rol_m, &Cpu::rla__, // 0x3 + /*0x4*/&Cpu::rti__, &Cpu::eor__, &Cpu::nop__, &Cpu::sre__, &Cpu::nop__, &Cpu::eor__, &Cpu::lsr_m, &Cpu::sre__, + &Cpu::pha__, &Cpu::eor__, &Cpu::lsr_a, &Cpu::alr__, &Cpu::jmp__, &Cpu::eor__, &Cpu::lsr_m, &Cpu::sre__, // 0x4 + /*0x5*/&Cpu::bvm__, &Cpu::eor__, &Cpu::nop__, &Cpu::sre__, &Cpu::nop__, &Cpu::eor__, &Cpu::lsr_m, &Cpu::sre__, + &Cpu::cli__, &Cpu::eor__, &Cpu::nop__, &Cpu::sre__, &Cpu::nop__, &Cpu::eor__, &Cpu::lsr_m, &Cpu::sre__, // 0x5 + /*0x6*/&Cpu::rts__, &Cpu::adc__, &Cpu::nop__, &Cpu::rra__, &Cpu::nop__, &Cpu::adc__, &Cpu::ror_m, &Cpu::rra__, + &Cpu::pla__, &Cpu::adc__, &Cpu::ror_a, &Cpu::arr__, &Cpu::jmp_i, &Cpu::adc__, &Cpu::ror_m, &Cpu::rra__, // 0x6 + /*0x7*/&Cpu::bvs__, &Cpu::adc__, &Cpu::nop__, &Cpu::rra__, &Cpu::nop__, &Cpu::adc__, &Cpu::ror_m, &Cpu::rra__, + &Cpu::sei__, &Cpu::adc__, &Cpu::nop__, &Cpu::rra__, &Cpu::nop__, &Cpu::adc__, &Cpu::ror_m, &Cpu::rra__, // 0x7 + /*0x8*/&Cpu::nop__, &Cpu::sta__, &Cpu::nop__, &Cpu::sax__, &Cpu::sty__, &Cpu::sta__, &Cpu::stx__, &Cpu::sax__, + &Cpu::dey__, &Cpu::nop__, &Cpu::txa__, &Cpu::xaa__, &Cpu::sty__, &Cpu::sta__, &Cpu::stx__, &Cpu::sax__, // 0x8 + /*0x9*/&Cpu::bcc__, &Cpu::sta__, &Cpu::nop__, &Cpu::ahc__, &Cpu::sty__, &Cpu::sta__, &Cpu::stx__, &Cpu::sax__, + &Cpu::tya__, &Cpu::sta__, &Cpu::txs__, &Cpu::xas__, &Cpu::shy__, &Cpu::sta__, &Cpu::shx__, &Cpu::ahc__, // 0x9 + /*0xA*/&Cpu::ldy__, &Cpu::lda__, &Cpu::ldx__, &Cpu::lax__, &Cpu::ldy__, &Cpu::lda__, &Cpu::ldx__, &Cpu::lax__, + &Cpu::tay__, &Cpu::lda__, &Cpu::tax__, &Cpu::lax__, &Cpu::ldy__, &Cpu::lda__, &Cpu::ldx__, &Cpu::lax__, // 0xA + /*0xB*/&Cpu::bcs__, &Cpu::lda__, &Cpu::nop__, &Cpu::lax__, &Cpu::ldy__, &Cpu::lda__, &Cpu::ldx__, &Cpu::lax__, + &Cpu::clv__, &Cpu::lda__, &Cpu::tsx__, &Cpu::lar__, &Cpu::ldy__, &Cpu::lda__, &Cpu::ldx__, &Cpu::lax__, // 0xB + /*0xC*/&Cpu::cpy__, &Cpu::cmp__, &Cpu::nop__, &Cpu::dcp__, &Cpu::cpy__, &Cpu::cmp__, &Cpu::dec__, &Cpu::dcp__, + &Cpu::iny__, &Cpu::cmp__, &Cpu::dex__, &Cpu::axs__, &Cpu::cpy__, &Cpu::cmp__, &Cpu::dec__, &Cpu::dcp__, // 0xC + /*0xD*/&Cpu::bne__, &Cpu::cmp__, &Cpu::nop__, &Cpu::dcp__, &Cpu::nop__, &Cpu::cmp__, &Cpu::dec__, &Cpu::dcp__, + &Cpu::cld__, &Cpu::cmp__, &Cpu::nop__, &Cpu::dcp__, &Cpu::nop__, &Cpu::cmp__, &Cpu::dec__, &Cpu::dcp__, // 0xD + /*0xE*/&Cpu::cpx__, &Cpu::sdc__, &Cpu::nop__, &Cpu::isc__, &Cpu::cpx__, &Cpu::sdc__, &Cpu::inc__, &Cpu::isc__, + &Cpu::inx__, &Cpu::sdc__, &Cpu::nop__, &Cpu::sdc__, &Cpu::cpx__, &Cpu::sdc__, &Cpu::inc__, &Cpu::isc__, // 0xE + /*0xF*/&Cpu::beq__, &Cpu::sdc__, &Cpu::nop__, &Cpu::isc__, &Cpu::nop__, &Cpu::sdc__, &Cpu::inc__, &Cpu::isc__, + &Cpu::sed__, &Cpu::sdc__, &Cpu::nop__, &Cpu::isc__, &Cpu::nop__, &Cpu::sdc__, &Cpu::inc__, &Cpu::isc__, // 0xF + }; + + m_opcode = m_emu.memory().read(m_regPc.v); + m_regPc.v++; + + (this->*cpuAddressings[m_opcode])(); + (this->*cpuInstructions[m_opcode])(); + + //handle interrupts + if(m_irqPin || m_nmiPin) + { + m_emu.memory().read(m_regPc.v); + m_emu.memory().read(m_regPc.v); + interrupt(); + } +} + +void Cpu::hardReset() +{ + m_regA = 0; + m_regX = 0; + m_regY = 0; + + m_regSp.v = 0x01FD; + + m_regPc.l = m_emu.memory().board()->readPrg(0xFFFC); + m_regPc.h = m_emu.memory().board()->readPrg(0xFFFD); + + setRegisterP(0); + m_flagI = true; + m_regEa.v = 0; + m_opcode = 0; + + //interrupts + m_irqPin = false; + m_nmiPin = false; + m_suspendNmi = false; + m_suspendIrq = false; + m_emu.interrupts().setFlags(0); +} + +void Cpu::softReset() +{ + m_flagI = true; + m_regSp.v -= 3; + + m_regPc.l = m_emu.memory().board()->readPrg(0xFFFC); + m_regPc.h = m_emu.memory().board()->readPrg(0xFFFD); +} + +void Cpu::interrupt() +{ + push(m_regPc.h); + push(m_regPc.l); + + push(m_opcode == 0 ? getRegisterPb() : getRegisterP()); + + // pins are detected during φ2 of previous cycle (before push about 2 ppu + // cycles) + const auto temp = m_emu.interrupts().vector(); + + // THEORY: + // Once the vector requested, the interrupts are suspended and cleared + // by setting the I flag and clearing the nmi detect flag. Also, the nmi + // detection get suspended for 2 cycles while pulling PC, irq still can + // be detected but will not be taken since I is set. + m_suspendNmi = true; + m_flagI = true; + m_nmiPin = false; + + m_regPc.l = m_emu.memory().read(temp); + m_regPc.h = m_emu.memory().read(temp + 1); + + m_suspendNmi = false; +} + +void Cpu::branch(const bool condition) +{ + const auto temp = m_emu.memory().read(m_regPc.v++); + + if (condition) { + m_suspendIrq = true; + m_emu.memory().read(m_regPc.v); + m_regPc.l += temp; + m_suspendIrq = false; + if (temp >= 0x80) { + if (m_regPc.l >= temp) { + m_emu.memory().read(m_regPc.v); + m_regPc.h--; + } + } else { + if (m_regPc.l < temp) { + m_emu.memory().read(m_regPc.v); + m_regPc.h++; + } + } + } +} + +void Cpu::push(const quint8 value) +{ + m_emu.memory().write(m_regSp.v--, value); +} + +quint8 Cpu::_pull() +{ + return m_emu.memory().read(++m_regSp.v); +} + +quint8 Cpu::pull() +{ + auto result = _pull(); + return result; +} + +void Cpu::imp____() +{ + // No addressing mode ... +} + +void Cpu::indX_r_() +{ + CpuRegister temp; + temp.h = 0;// the zero page boundary crossing is not handled. + temp.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// CLock 1 + m_emu.memory().read(temp.v);// Clock 2 + temp.l += m_regX; + + m_regEa.l = m_emu.memory().read(temp.v);// Clock 3 + temp.l++; + + m_regEa.h = m_emu.memory().read(temp.v);// Clock 4 + + m_m = m_emu.memory().read(m_regEa.v); +} + +void Cpu::indX_w_() +{ + CpuRegister temp; + temp.h = 0;// the zero page boundary crossing is not handled. + temp.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// CLock 1 + m_emu.memory().read(temp.v);// Clock 2 + temp.l += m_regX; + + m_regEa.l = m_emu.memory().read(temp.v);// Clock 3 + temp.l++; + + m_regEa.h = m_emu.memory().read(temp.v);// Clock 4 +} + +void Cpu::indX_rw() +{ + CpuRegister temp; + temp.h = 0;// the zero page boundary crossing is not handled. + temp.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// CLock 1 + m_emu.memory().read(temp.v);// Clock 2 + temp.l += m_regX; + + m_regEa.l = m_emu.memory().read(temp.v);// Clock 3 + temp.l++; + + m_regEa.h = m_emu.memory().read(temp.v);// Clock 4 + + m_m = m_emu.memory().read(m_regEa.v); +} + +void Cpu::indY_r_() +{ + CpuRegister temp; + temp.h = 0;// the zero page boundary crossing is not handled. + temp.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// CLock 1 + m_regEa.l = m_emu.memory().read(temp.v);// Clock 3 + temp.l++;// Clock 2 + m_regEa.h = m_emu.memory().read(temp.v);// Clock 4 + + m_regEa.l += m_regY; + + m_m = m_emu.memory().read(m_regEa.v); + if (m_regEa.l < m_regY) + { + m_regEa.h++; + m_m = m_emu.memory().read(m_regEa.v); + } +} + +void Cpu::indY_w_() +{ + CpuRegister temp; + temp.h = 0;// the zero page boundary crossing is not handled. + temp.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// CLock 1 + + m_regEa.l = m_emu.memory().read(temp.v); + temp.l++;// Clock 2 + + m_regEa.h = m_emu.memory().read(temp.v);// Clock 2 + m_regEa.l += m_regY; + + m_m = m_emu.memory().read(m_regEa.v);// Clock 3 + if (m_regEa.l < m_regY) + m_regEa.h++; +} + +void Cpu::indY_rw() +{ + CpuRegister temp; + temp.h = 0;// the zero page boundary crossing is not handled. + temp.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// CLock 1 + m_regEa.l = m_emu.memory().read(temp.v); + temp.l++;// Clock 2 + m_regEa.h = m_emu.memory().read(temp.v);// Clock 2 + + m_regEa.l += m_regY; + + m_emu.memory().read(m_regEa.v);// Clock 3 + if (m_regEa.l < m_regY) + m_regEa.h++; + + m_m = m_emu.memory().read(m_regEa.v);// Clock 3 +} + +void Cpu::zpg_r__() +{ + m_regEa.h = 0; + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_m = m_emu.memory().read(m_regEa.v);// Clock 3 +} + +void Cpu::zpg_w__() +{ + m_regEa.h = 0; + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 +} + +void Cpu::zpg_rw_() +{ + m_regEa.h = 0; + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_m = m_emu.memory().read(m_regEa.v);// Clock 3 +} + +void Cpu::zpgX_r_() +{ + m_regEa.h = 0; + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_emu.memory().read(m_regEa.v);// Clock 2 + m_regEa.l += m_regX; + m_m = m_emu.memory().read(m_regEa.v);// Clock 3 +} + +void Cpu::zpgX_w_() +{ + m_regEa.h = 0; + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_emu.memory().read(m_regEa.v);// Clock 2 + m_regEa.l += m_regX; +} + +void Cpu::zpgX_rw() +{ + m_regEa.h = 0; + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_emu.memory().read(m_regEa.v);// Clock 2 + m_regEa.l += m_regX; + m_m = m_emu.memory().read(m_regEa.v);// Clock 3 +} + +void Cpu::zpgY_r_() +{ + m_regEa.h = 0; + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_emu.memory().read(m_regEa.v);// Clock 2 + m_regEa.l += m_regY; + m_m = m_emu.memory().read(m_regEa.v);// Clock 3 +} + +void Cpu::zpgY_w_() +{ + m_regEa.h = 0; + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_emu.memory().read(m_regEa.v);// Clock 2 + m_regEa.l += m_regY; +} + +void Cpu::zpgY_rw() +{ + m_regEa.h = 0; + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_emu.memory().read(m_regEa.v);// Clock 2 + m_regEa.l += m_regY; + m_m = m_emu.memory().read(m_regEa.v);// Clock 3 +} + +void Cpu::imm____() +{ + m_m = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 +} + +void Cpu::imA____() +{ + m_emu.memory().read(m_regPc.v); +} + +void Cpu::abs_r__() +{ + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_regEa.h = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 2 + m_m = m_emu.memory().read(m_regEa.v);// Clock 3 +} + +void Cpu::abs_w__() +{ + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_regEa.h = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 2 +} + +void Cpu::abs_rw_() +{ + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_regEa.h = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 2 + m_m = m_emu.memory().read(m_regEa.v);// Clock 3 +} + +void Cpu::absX_r_() +{ + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_regEa.h = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 2 + + m_regEa.l += m_regX; + + m_m = m_emu.memory().read(m_regEa.v);// Clock 3 + if (m_regEa.l < m_regX) + { + m_regEa.h++; + m_m = m_emu.memory().read(m_regEa.v);// Clock 4 + } +} + +void Cpu::absX_w_() +{ + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_regEa.h = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 2 + + m_regEa.l += m_regX; + + m_m = m_emu.memory().read(m_regEa.v);// Clock 4 + if (m_regEa.l < m_regX) + m_regEa.h++; +} + +void Cpu::absX_rw() +{ + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_regEa.h = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 2 + + m_regEa.l += m_regX; + + m_emu.memory().read(m_regEa.v);// Clock 3 + if (m_regEa.l < m_regX) + m_regEa.h++; + + m_m = m_emu.memory().read(m_regEa.v);// Clock 4 +} + +void Cpu::absY_r_() +{ + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_regEa.h = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 2 + + m_regEa.l += m_regY; + + m_m = m_emu.memory().read(m_regEa.v);// Clock 4 + if (m_regEa.l < m_regY) + { + m_regEa.h++; + m_m = m_emu.memory().read(m_regEa.v);// Clock 4 + } +} + +void Cpu::absY_w_() +{ + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_regEa.h = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 2 + + m_regEa.l += m_regY; + + m_m = m_emu.memory().read(m_regEa.v);// Clock 4 + if (m_regEa.l < m_regY) + m_regEa.h++; +} + +void Cpu::absY_rw() +{ + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 1 + m_regEa.h = m_emu.memory().read(m_regPc.v); + m_regPc.v++;// Clock 2 + + m_regEa.l += m_regY; + + m_m = m_emu.memory().read(m_regEa.v);// Clock 4 + if (m_regEa.l < m_regY) + m_regEa.h++; + + m_m = m_emu.memory().read(m_regEa.v);// Clock 4 +} + +void Cpu::adc__() +{ + const qint32 temp = m_regA + m_m + (m_flagC ? 1 : 0); + + m_flagV = (temp ^ m_regA) & (temp ^ m_m) & 0x80; + m_flagN = temp & 0x80; + m_flagZ = (temp & 0xFF) == 0; + m_flagC = temp >> 0x8; + + m_regA = temp; +} + +void Cpu::ahc__() +{ + m_emu.memory().write(m_regEa.v, m_regA & m_regX & 7); +} + +void Cpu::alr__() +{ + m_regA &= m_m; + + m_flagC = m_regA & 0x01; + + m_regA >>= 1; + + m_flagN = m_regA & 0x80; + m_flagZ = m_regA == 0; +} + +void Cpu::anc__() +{ + m_regA &= m_m; + m_flagN = m_regA & 0x80; + m_flagZ = m_regA == 0; + m_flagC = m_regA & 0x80; +} + +void Cpu::and__() +{ + m_regA &= m_m; + m_flagN = (m_regA & 0x80) == 0x80; + m_flagZ = (m_regA == 0); +} + +void Cpu::arr__() +{ + m_regA = ((m_m & m_regA) >> 1) | (m_flagC ? 0x80 : 0x00); + + m_flagZ = (m_regA & 0xFF) == 0; + m_flagN = m_regA & 0x80; + m_flagC = m_regA & 0x40; + m_flagV = (m_regA << 1 ^ m_regA) & 0x40; +} + +void Cpu::axs__() +{ + const qint32 temp = (m_regA & m_regX) - m_m; + + m_flagN = temp & 0x80; + m_flagZ = (temp & 0xFF) == 0; + m_flagC = ~temp >> 8; + + m_regX = temp; +} + +void Cpu::asl_m() +{ + m_flagC = (m_m & 0x80) == 0x80; + m_emu.memory().write(m_regEa.v, m_m); + + m_m = (m_m << 1) & 0xFE; + + m_emu.memory().write(m_regEa.v, m_m); + + m_flagN = (m_m & 0x80) == 0x80; + m_flagZ = m_m == 0; +} + +void Cpu::asl_a() +{ + m_flagC = (m_regA & 0x80) == 0x80; + + m_regA = (m_regA << 1) & 0xFE; + + m_flagN = (m_regA & 0x80) == 0x80; + m_flagZ = m_regA == 0; +} + +void Cpu::bcc__() +{ + branch(!m_flagC); +} + +void Cpu::bcs__() +{ + branch(m_flagC); +} + +void Cpu::beq__() +{ + branch(m_flagZ); +} + +void Cpu::bit__() +{ + m_flagN = m_m & 0x80; + m_flagV = m_m & 0x40; + m_flagZ = (m_m & m_regA) == 0; +} + +void Cpu::brk__() +{ + m_emu.memory().read(m_regPc.v); + m_regPc.v++; + interrupt(); +} + +void Cpu::bpl__() +{ + branch(!m_flagN); +} + +void Cpu::bne__() +{ + branch(!m_flagZ); +} + +void Cpu::bmi__() +{ + branch(m_flagN); +} + +void Cpu::bvm__() +{ + branch(!m_flagV); +} + +void Cpu::bvs__() +{ + branch(m_flagV); +} + +void Cpu::sed__() +{ + m_flagD = true; +} + +void Cpu::clc__() +{ + m_flagC = false; +} + +void Cpu::cld__() +{ + m_flagD = false; +} + +void Cpu::clv__() +{ + m_flagV = false; +} + +void Cpu::cmp__() +{ + const qint32 temp = m_regA - m_m; + m_flagN = (temp & 0x80) == 0x80; + m_flagC = m_regA >= m_m; + m_flagZ = temp == 0; +} + +void Cpu::cpx__() +{ + const qint32 temp = m_regX - m_m; + m_flagN = (temp & 0x80) == 0x80; + m_flagC = m_regX >= m_m; + m_flagZ = temp == 0; +} + +void Cpu::cpy__() +{ + const qint32 temp = m_regY - m_m; + m_flagN = (temp & 0x80) == 0x80; + m_flagC = m_regY >= m_m; + m_flagZ = temp == 0; +} + +void Cpu::cli__() +{ + m_flagI = false; +} + +void Cpu::dcp__() +{ + m_emu.memory().write(m_regEa.v, m_m--); + m_emu.memory().write(m_regEa.v, m_m); + + const qint32 temp = m_regA - m_m; + + m_flagN = temp & 0x80; + m_flagZ = temp == 0; + m_flagC = ~temp >> 8; +} + +void Cpu::dec__() +{ + m_emu.memory().write(m_regEa.v, m_m--); + m_emu.memory().write(m_regEa.v, m_m); + m_flagN = (m_m & 0x80) == 0x80; + m_flagZ = m_m == 0; +} + +void Cpu::dey__() +{ + m_flagZ = --m_regY == 0; + m_flagN = (m_regY & 0x80) == 0x80; +} + +void Cpu::dex__() +{ + m_flagZ = --m_regX == 0; + m_flagN = (m_regX & 0x80) == 0x80; +} + +void Cpu::eor__() +{ + m_regA ^= m_m; + m_flagN = (m_regA & 0x80) == 0x80; + m_flagZ = m_regA == 0; +} + +void Cpu::inc__() +{ + m_emu.memory().write(m_regEa.v, m_m++); + m_emu.memory().write(m_regEa.v, m_m); + m_flagN = (m_m & 0x80) == 0x80; + m_flagZ = m_m == 0; +} + +void Cpu::inx__() +{ + m_flagZ = ++m_regX == 0; + m_flagN = (m_regX & 0x80) == 0x80; +} + +void Cpu::iny__() +{ + m_flagN = (++m_regY & 0x80) == 0x80; + m_flagZ = m_regY == 0; +} + +void Cpu::isc__() +{ + quint8 temp0 = m_emu.memory().read(m_regEa.v); + + m_emu.memory().write(m_regEa.v, temp0); + temp0++; + m_emu.memory().write(m_regEa.v, temp0); + + const qint32 temp1 = temp0 ^ 0xFF; + const qint32 temp2 = (m_regA + temp1 + (m_flagC ? 1 : 0)); + + m_flagN = temp2 & 0x80; + m_flagV = (temp2 ^ m_regA) & (temp2 ^ temp1) & 0x80; + m_flagZ = (temp2 & 0xFF) == 0; + m_flagC = temp2 >> 0x8; + m_regA = temp2; +} + +void Cpu::jmp__() +{ + m_regPc.v = m_regEa.v; +} + +void Cpu::jmp_i() +{ + // Fetch pointer + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++; + m_regEa.h = m_emu.memory().read(m_regPc.v); + + m_regPc.l = m_emu.memory().read(m_regEa.v); + m_regEa.l++; // only increment the low byte, causing the "JMP ($nnnn)" bug + m_regPc.h = m_emu.memory().read(m_regEa.v); +} + +void Cpu::jsr__() +{ + m_regEa.l = m_emu.memory().read(m_regPc.v); + m_regPc.v++; + + // Store EAL at SP, see http://users.telenet.be/kim1-6502/6502/proman.html (see the JSR part) + m_emu.memory().write(m_regSp.v, m_regEa.l); + + push(m_regPc.h); + push(m_regPc.l); + + m_regEa.h = m_emu.memory().read(m_regPc.v); + m_regPc.v = m_regEa.v; +} + +void Cpu::lar__() +{ + m_regSp.l &= m_m; + m_regA = m_regSp.l; + m_regX = m_regSp.l; + + m_flagN = m_regSp.l & 0x80; + m_flagZ = (m_regSp.l & 0xFF) == 0; +} + +void Cpu::lax__() +{ + m_regX = m_regA = m_m; + + m_flagN = m_regX & 0x80; + m_flagZ = (m_regX & 0xFF) == 0; +} + +void Cpu::lda__() +{ + m_regA = m_m; + m_flagN = (m_regA & 0x80) == 0x80; + m_flagZ = m_regA == 0; +} + +void Cpu::ldx__() +{ + m_regX = m_m; + m_flagN = (m_regX & 0x80) == 0x80; + m_flagZ = m_regX == 0; +} + +void Cpu::ldy__() +{ + m_regY = m_m; + m_flagN = (m_regY & 0x80) == 0x80; + m_flagZ = m_regY == 0; +} + +void Cpu::lsr_a() +{ + m_flagC = m_regA & 1; + m_regA >>= 1; + m_flagZ = m_regA == 0; + m_flagN = m_regA & 0x80; +} + +void Cpu::lsr_m() +{ + m_flagC = m_m & 1; + m_emu.memory().write(m_regEa.v, m_m); + m_m >>= 1; + + m_emu.memory().write(m_regEa.v, m_m); + m_flagZ = m_m == 0; + m_flagN = m_m & 0x80; +} + +void Cpu::nop__() +{ + // Do nothing. +} + +void Cpu::ora__() +{ + m_regA |= m_m; + m_flagN = (m_regA & 0x80) == 0x80; + m_flagZ = m_regA == 0; +} + +void Cpu::pha__() +{ + push(m_regA); +} + +void Cpu::php__() +{ + push(getRegisterPb()); +} + +void Cpu::pla__() +{ + m_emu.memory().read(m_regSp.v); + m_regA = pull(); + m_flagN = (m_regA & 0x80) == 0x80; + m_flagZ = m_regA == 0; +} + +void Cpu::plp__() +{ + m_emu.memory().read(m_regSp.v); + setRegisterP(pull()); +} + +void Cpu::rla__() +{ + const quint8 temp0 = m_emu.memory().read(m_regEa.v); + + m_emu.memory().write(m_regEa.v, temp0); + + const quint8 temp1 = (temp0 << 1) | (m_flagC ? 0x01 : 0x00); + + m_emu.memory().write(m_regEa.v, temp1); + + m_flagN = temp1 & 0x80; + m_flagZ = (temp1 & 0xFF) == 0; + m_flagC = temp0 & 0x80; + + m_regA &= temp1; + m_flagN = m_regA & 0x80; + m_flagZ = (m_regA & 0xFF) == 0; +} + +void Cpu::rol_a() +{ + const quint8 temp = (m_regA << 1) | (m_flagC ? 0x01 : 0x00); + + m_flagN = temp & 0x80; + m_flagZ = (temp & 0xFF) == 0; + m_flagC = m_regA & 0x80; + + m_regA = temp; +} + +void Cpu::rol_m() +{ + m_emu.memory().write(m_regEa.v, m_m); + + const quint8 temp = (m_m << 1) | (m_flagC ? 0x01 : 0x00); + + m_emu.memory().write(m_regEa.v, temp); + m_flagN = temp & 0x80; + m_flagZ = (temp & 0xFF) == 0; + m_flagC = m_m & 0x80; +} + +void Cpu::ror_a() +{ + const quint8 temp = (m_regA >> 1) | (m_flagC ? 0x80 : 0x00); + + m_flagN = temp & 0x80; + m_flagZ = (temp & 0xFF) == 0; + m_flagC = m_regA & 0x01; + + m_regA = temp; +} + +void Cpu::ror_m() +{ + m_emu.memory().write(m_regEa.v, m_m); + + const quint8 temp = (m_m >> 1) | (m_flagC ? 0x80 : 0x00); + m_emu.memory().write(m_regEa.v, temp); + + m_flagN = temp & 0x80; + m_flagZ = (temp & 0xFF) == 0; + m_flagC = m_m & 0x01; +} + +void Cpu::rra__() +{ + const quint8 cpu_byte_temp = m_emu.memory().read(m_regEa.v); + + m_emu.memory().write(m_regEa.v, cpu_byte_temp); + + const quint8 cpu_dummy = (cpu_byte_temp >> 1) | (m_flagC ? 0x80 : 0x00); + + m_emu.memory().write(m_regEa.v, cpu_dummy); + + m_flagN = cpu_dummy & 0x80; + m_flagZ = (cpu_dummy & 0xFF) == 0; + m_flagC = cpu_byte_temp & 0x01; + + int cpu_int_temp = (m_regA + cpu_dummy + (m_flagC ? 1 : 0)); + + m_flagN = cpu_int_temp & 0x80; + m_flagV = (cpu_int_temp ^ m_regA) & (cpu_int_temp ^ cpu_dummy) & 0x80; + m_flagZ = (cpu_int_temp & 0xFF) == 0; + m_flagC = cpu_int_temp >> 0x8; + m_regA = cpu_int_temp; +} + +void Cpu::rti__() +{ + m_emu.memory().read(m_regSp.v); + + setRegisterP(pull()); + + m_regPc.l = pull(); + m_regPc.h = pull(); +} + +void Cpu::rts__() +{ + m_emu.memory().read(m_regSp.v); + m_regPc.l = pull(); + m_regPc.h = pull(); + + m_regPc.v++; + + m_emu.memory().read(m_regPc.v); +} + +void Cpu::sax__() +{ + m_emu.memory().write(m_regEa.v, m_regX & m_regA); +} + +void Cpu::sdc__() +{ + m_m ^= 0xFF; + + const qint32 temp = (m_regA + m_m + (m_flagC ? 1 : 0)); + + m_flagN = temp & 0x80; + m_flagV = (temp ^ m_regA) & (temp ^ m_m) & 0x80; + m_flagZ = (temp & 0xFF) == 0; + m_flagC = temp >> 0x8; + m_regA = temp; +} + +void Cpu::sec__() +{ + m_flagC = true; +} + +void Cpu::sei__() +{ + m_flagI = true; +} + +void Cpu::shx__() +{ + const quint8 cpu_byte_temp = m_regX & (m_regEa.h + 1); + + m_emu.memory().read(m_regEa.v); + m_regEa.l += m_regY; + + if (m_regEa.l < m_regY) + m_regEa.h = cpu_byte_temp; + + m_emu.memory().write(m_regEa.v, cpu_byte_temp); +} + +void Cpu::shy__() +{ + const quint8 temp = m_regY & (m_regEa.h + 1); + + m_emu.memory().read(m_regEa.v); + m_regEa.l += m_regX; + + if (m_regEa.l < m_regX) + m_regEa.h = temp; + + m_emu.memory().write(m_regEa.v, temp); +} + +void Cpu::slo__() +{ + quint8 cpu_byte_temp = m_emu.memory().read(m_regEa.v); + + m_flagC = cpu_byte_temp & 0x80; + + m_emu.memory().write(m_regEa.v, cpu_byte_temp); + + cpu_byte_temp <<= 1; + + m_emu.memory().write(m_regEa.v, cpu_byte_temp); + + m_flagN = cpu_byte_temp & 0x80; + m_flagZ = (cpu_byte_temp & 0xFF) == 0; + + m_regA |= cpu_byte_temp; + m_flagN = m_regA & 0x80; + m_flagZ = (m_regA & 0xFF) == 0; +} + +void Cpu::sre__() +{ + quint8 cpu_byte_temp = m_emu.memory().read(m_regEa.v); + + m_flagC = cpu_byte_temp & 0x01; + + m_emu.memory().write(m_regEa.v, cpu_byte_temp); + + cpu_byte_temp >>= 1; + + m_emu.memory().write(m_regEa.v, cpu_byte_temp); + + m_flagN = cpu_byte_temp & 0x80; + m_flagZ = (cpu_byte_temp & 0xFF) == 0; + + m_regA ^= cpu_byte_temp; + m_flagN = m_regA & 0x80; + m_flagZ = (m_regA & 0xFF) == 0; +} + +void Cpu::sta__() +{ + m_emu.memory().write(m_regEa.v, m_regA); +} + +void Cpu::stx__() +{ + m_emu.memory().write(m_regEa.v, m_regX); +} + +void Cpu::sty__() +{ + m_emu.memory().write(m_regEa.v, m_regY); +} + +void Cpu::tax__() +{ + m_regX = m_regA; + m_flagN = (m_regX & 0x80) == 0x80; + m_flagZ = m_regX == 0; +} + +void Cpu::tay__() +{ + m_regY = m_regA; + m_flagN = (m_regY & 0x80) == 0x80; + m_flagZ = m_regY == 0; +} + +void Cpu::tsx__() +{ + m_regX = m_regSp.l; + m_flagN = m_regX & 0x80; + m_flagZ = m_regX == 0; +} + +void Cpu::txa__() +{ + m_regA = m_regX; + m_flagN = (m_regA & 0x80) == 0x80; + m_flagZ = m_regA == 0; +} + +void Cpu::txs__() +{ + m_regSp.l = m_regX; +} + +void Cpu::tya__() +{ + m_regA = m_regY; + m_flagN = (m_regA & 0x80) == 0x80; + m_flagZ = m_regA == 0; +} + +void Cpu::xaa__() +{ + m_regA = m_regX & m_m; + m_flagN = m_regA & 0x80; + m_flagZ = (m_regA & 0xFF) == 0; +} + +void Cpu::xas__() +{ + m_regSp.l = m_regA & m_regX /*& ((dummyVal >> 8) + 1) */; + m_emu.memory().write(m_regEa.v, m_regSp.l); +} + +void Cpu::writeState(QDataStream &dataStream) const +{ + dataStream << m_regPc.v << m_regSp.v << m_regEa.v << m_regA << m_regX << m_regY + << m_flagN << m_flagV << m_flagD << m_flagI << m_flagZ << m_flagC + << m_m << m_opcode << m_irqPin << m_nmiPin << m_suspendNmi << m_suspendIrq; +} + +void Cpu::readState(QDataStream &dataStream) +{ + dataStream >> m_regPc.v >> m_regSp.v >> m_regEa.v >> m_regA >> m_regX >> m_regY + >> m_flagN >> m_flagV >> m_flagD >> m_flagI >> m_flagZ >> m_flagC + >> m_m >> m_opcode >> m_irqPin >> m_nmiPin >> m_suspendNmi >> m_suspendIrq; +} + +bool Cpu::suspendNmi() const +{ + return m_suspendNmi; +} + +bool Cpu::suspendIrq() const +{ + return m_suspendIrq; +} + +bool Cpu::flagI() const +{ + return m_flagI; +} + +bool Cpu::nmiPin() const +{ + return m_nmiPin; +} + +void Cpu::setNmiPin(bool nmiPin) +{ + m_nmiPin = nmiPin; +} + +bool Cpu::irqPin() const +{ + return m_irqPin; +} + +void Cpu::setIrqPin(bool irqPin) +{ + m_irqPin = irqPin; +} diff --git a/nescorelib/emu/cpu.h b/nescorelib/emu/cpu.h new file mode 100644 index 0000000..1a62a30 --- /dev/null +++ b/nescorelib/emu/cpu.h @@ -0,0 +1,114 @@ +#pragma once + +#include "nescorelib_global.h" + +// Qt includes +#include + +// forward declarations +class NesEmulator; +class QDataStream; + +class NESCORELIB_EXPORT Cpu +{ + union CpuRegister + { + quint16 v; + struct { + quint8 l; + quint8 h; + }; + }; + +public: + explicit Cpu(NesEmulator &emu); + + quint8 getRegisterP() const; + void setRegisterP(const quint8 value); + quint8 getRegisterPb() const; + + void clock(); + + void hardReset(); + void softReset(); + + void interrupt(); + void branch(const bool condition); + void push(const quint8 value); + quint8 _pull(); + quint8 pull(); + + void writeState(QDataStream &dataStream) const; + void readState(QDataStream &dataStream); + + bool suspendNmi() const; + bool suspendIrq() const; + bool flagI() const; + + bool nmiPin() const; + void setNmiPin(bool nmiPin); + + bool irqPin() const; + void setIrqPin(bool irqPin); + +private: + // addressing modes + void imp____(); void zpgX_r_(); void abs_rw_(); + void indX_r_(); void zpgX_w_(); void absX_r_(); + void indX_w_(); void zpgX_rw(); void absX_w_(); + void indX_rw(); void zpgY_r_(); void absX_rw(); + void indY_r_(); void zpgY_w_(); void absY_r_(); + void indY_w_(); void zpgY_rw(); void absY_w_(); + void indY_rw(); void imm____(); void absY_rw(); + void zpg_r__(); void imA____(); + void zpg_w__(); void abs_r__(); + void zpg_rw_(); void abs_w__(); + + // instructions + void adc__(); void clc__(); void lax__(); void sax__(); + void ahc__(); void cld__(); void lda__(); void sdc__(); + void alr__(); void clv__(); void ldx__(); void sec__(); + void anc__(); void cmp__(); void ldy__(); void sei__(); + void and__(); void cpx__(); void lsr_a(); void shx__(); + void arr__(); void cpy__(); void lsr_m(); void shy__(); + void axs__(); void cli__(); void nop__(); void slo__(); + void asl_m(); void dcp__(); void ora__(); void sre__(); + void asl_a(); void dec__(); void pha__(); void sta__(); + void bcc__(); void dey__(); void php__(); void stx__(); + void bcs__(); void dex__(); void pla__(); void sty__(); + void beq__(); void eor__(); void plp__(); void tax__(); + void bit__(); void inc__(); void rla__(); void tay__(); + void brk__(); void inx__(); void rol_a(); void tsx__(); + void bpl__(); void iny__(); void rol_m(); void txa__(); + void bne__(); void isc__(); void ror_a(); void txs__(); + void bmi__(); void jmp__(); void ror_m(); void tya__(); + void bvm__(); void jmp_i(); void rra__(); void xaa__(); + void bvs__(); void jsr__(); void rti__(); void xas__(); + void sed__(); void lar__(); void rts__(); + + NesEmulator &m_emu; + + CpuRegister m_regPc {}; + CpuRegister m_regSp {}; + CpuRegister m_regEa {}; + + quint8 m_regA {}; + quint8 m_regX {}; + quint8 m_regY {}; + + //flags + bool m_flagN {}; + bool m_flagV {}; + bool m_flagD {}; + bool m_flagI {}; + bool m_flagZ {}; + bool m_flagC {}; + + quint8 m_m {}; + quint8 m_opcode {}; + + bool m_irqPin {}; + bool m_nmiPin {}; + bool m_suspendNmi {}; + bool m_suspendIrq {}; +}; diff --git a/nescorelib/emu/dma.cpp b/nescorelib/emu/dma.cpp new file mode 100644 index 0000000..1663572 --- /dev/null +++ b/nescorelib/emu/dma.cpp @@ -0,0 +1,199 @@ +#include "dma.h" + +// Qt includes +#include + +// local includes +#include "nesemulator.h" + +Dma::Dma(NesEmulator &emu) : + m_emu(emu) +{ +} + +void Dma::hardReset() +{ + m_dmcDmaWaitCycles = 0; + m_oamDmaWaitCycles = 0; + m_isOamDma = false; + m_dmcOn = false; + m_oamOn = false; + m_dmcOccurring = false; + m_oamOccurring = false; + m_oamFinishCounter = 0; + m_oamAddress = 0; + m_oamCycle = 0; + m_latch = 0; +} + +void Dma::softReset() +{ + m_dmcDmaWaitCycles = 0; + m_oamDmaWaitCycles = 0; + m_isOamDma = false; + m_dmcOn = false; + m_oamOn = false; + m_dmcOccurring = false; + m_oamOccurring = false; + m_oamFinishCounter = 0; + m_oamAddress = 0; + m_oamCycle = 0; + m_latch = 0; +} + +void Dma::assertDmcDma() +{ + if(m_oamOccurring) + { + if(m_oamCycle < 508) + // OAM DMA is occurring here + m_dmcDmaWaitCycles = m_emu.memory().busRw() ? 1 : 0; + else + { + // Here the oam dma is about to finish + // Remaining cycles of oam dma determines the dmc dma waiting cycles. + m_dmcDmaWaitCycles = 4 - (512 - m_oamCycle); + } + } + else if(m_dmcOccurring) + { + // DMC occurring now !!? is that possible ? + // Anyway, let's ignore this call ! + return; + } + else + { + // Nothing occurring, initialize brand new dma + // DMC DMA depends on r/w flag forthe wait cycles. + m_dmcDmaWaitCycles = m_emu.memory().busRw() ? 3 : 2; + // After 2 cycles of oam dma, add extra cycle forthe waiting. + if(m_oamFinishCounter == 3) + m_dmcDmaWaitCycles++; + } + m_isOamDma = false; + m_dmcOn = true; +} + +void Dma::assertOamDma() +{ + if(m_oamOccurring) + return; + // Setup + // OAM DMA depends on apu odd timer forodd cycles + m_oamDmaWaitCycles = m_emu.apu().oddCycle() ? 1 : 2; + m_isOamDma = true; + m_oamOn = true; +} + +void Dma::clock() +{ + if(m_oamFinishCounter > 0) + m_oamFinishCounter--; + + if(!m_emu.memory().busRw()) // Clocks only on reads + { + if(m_dmcDmaWaitCycles > 0) + m_dmcDmaWaitCycles--; + if(m_oamDmaWaitCycles > 0) + m_oamDmaWaitCycles--; + return; + } + + if(m_dmcOn) + { + m_dmcOccurring = true; + // This is it ! + m_dmcOn = false; + // Do wait cycles (extra reads) + if(m_dmcDmaWaitCycles > 0) + { + if(m_emu.memory().busAddress() == 0x4016 || m_emu.memory().busAddress() == 0x4017) + { + m_emu.memory().read(m_emu.memory().busAddress()); + m_dmcDmaWaitCycles--; + + while(m_dmcDmaWaitCycles > 0) + { + m_emu.emuClockComponents(); + m_dmcDmaWaitCycles--; + } + } + else + { + while(m_dmcDmaWaitCycles > 0) + { + m_emu.memory().read(m_emu.memory().busAddress()); + m_dmcDmaWaitCycles--; + } + } + } + + // Do DMC DMA + m_emu.apu().dmc().apuDmcDoDma(); + + m_dmcOccurring = false; + } + + if(m_oamOn) + { + m_oamOccurring = true; + // This is it ! pause the cpu + m_oamOn = false; + // Do wait cycles (extra reads) + if(m_oamDmaWaitCycles > 0) + { + if(m_emu.memory().busAddress() == 0x4016 || m_emu.memory().busAddress() == 0x4017) + { + m_emu.memory().read(m_emu.memory().busAddress()); + m_oamDmaWaitCycles--; + + while(m_oamDmaWaitCycles > 0) + { + m_emu.emuClockComponents(); + m_oamDmaWaitCycles--; + } + } + else + { + while(m_oamDmaWaitCycles > 0) + { + m_emu.memory().read(m_emu.memory().busAddress()); + m_oamDmaWaitCycles--; + } + } + } + + // Do OAM DMA + m_oamCycle = 0; + for(auto i = 0; i < 256; i++) + { + m_latch = m_emu.memory().read(m_oamAddress); + m_oamCycle++; + m_emu.memory().write(0x2004, m_latch); + m_oamCycle++; + m_oamAddress++; + m_oamAddress = m_oamAddress & 0xFFFF; + } + + m_oamCycle = 0; + m_oamFinishCounter = 5; + m_oamOccurring = false; + } +} + +void Dma::writeState(QDataStream &dataStream) const +{ + dataStream << m_dmcDmaWaitCycles << m_oamDmaWaitCycles << m_isOamDma << m_dmcOn << m_oamOn << m_dmcOccurring + << m_oamOccurring << m_oamFinishCounter << m_oamAddress << m_oamCycle << m_latch; +} + +void Dma::readState(QDataStream &dataStream) +{ + dataStream >> m_dmcDmaWaitCycles >> m_oamDmaWaitCycles >> m_isOamDma >> m_dmcOn >> m_oamOn >> m_dmcOccurring + >> m_oamOccurring >> m_oamFinishCounter >> m_oamAddress >> m_oamCycle >> m_latch; +} + +void Dma::setOamAddress(quint16 oamAddress) +{ + m_oamAddress = oamAddress; +} diff --git a/nescorelib/emu/dma.h b/nescorelib/emu/dma.h new file mode 100644 index 0000000..3dd4b4f --- /dev/null +++ b/nescorelib/emu/dma.h @@ -0,0 +1,44 @@ +#pragma once + +#include "nescorelib_global.h" + +// Qt includes +#include + +// forward declarations +class NesEmulator; +class QDataStream; + +class NESCORELIB_EXPORT Dma +{ +public: + explicit Dma(NesEmulator &emu); + + void hardReset(); + void softReset(); + + void assertDmcDma(); + void assertOamDma(); + + void clock(); + + void writeState(QDataStream &dataStream) const; + void readState(QDataStream &dataStream); + + void setOamAddress(quint16 oamAddress); + +private: + NesEmulator &m_emu; + + qint32 m_dmcDmaWaitCycles {}; + qint32 m_oamDmaWaitCycles {}; + bool m_isOamDma {}; + bool m_dmcOn {}; + bool m_oamOn {}; + bool m_dmcOccurring {}; + bool m_oamOccurring {}; + qint32 m_oamFinishCounter {}; + quint16 m_oamAddress {}; + qint32 m_oamCycle {}; + quint8 m_latch {}; +}; diff --git a/nescorelib/emu/interrupts.cpp b/nescorelib/emu/interrupts.cpp new file mode 100644 index 0000000..565ca4e --- /dev/null +++ b/nescorelib/emu/interrupts.cpp @@ -0,0 +1,69 @@ +#include "interrupts.h" + +// Qt includes +#include + +// local includes +#include "nesemulator.h" + +Interrupts::Interrupts(NesEmulator &emu) : + m_emu(emu) +{ +} + +void Interrupts::pollStatus() +{ + if(!m_emu.cpu().suspendNmi()) + { + //The edge detector, see ifnmi occurred. + if(m_ppuNmiCurrent && !m_ppuNmiOld) //Raising edge, set nmi request + m_emu.cpu().setNmiPin(true); + m_ppuNmiCurrent = false; //NMI detected or not, low both lines forthis form ___|-|__ + m_ppuNmiOld = false; + } + + if(!m_emu.cpu().suspendIrq()) + m_emu.cpu().setIrqPin(!m_emu.cpu().flagI() && m_flags); // irq level detector + + m_vector = m_emu.cpu().nmiPin() ? 0xFFFA : 0xFFFE; +} + +void Interrupts::writeState(QDataStream &dataStream) const +{ + dataStream << m_flags << m_ppuNmiCurrent << m_ppuNmiOld << m_vector; +} + +void Interrupts::readState(QDataStream &dataStream) +{ + dataStream >> m_flags >> m_ppuNmiCurrent >> m_ppuNmiOld >> m_vector; +} + +qint32 Interrupts::flags() const +{ + return m_flags; +} + +void Interrupts::addFlag(IrqFlag flag) +{ + m_flags |= flag; +} + +void Interrupts::removeFlag(IrqFlag flag) +{ + m_flags &= ~flag; +} + +void Interrupts::setFlags(qint32 flags) +{ + m_flags = flags; +} + +quint16 Interrupts::vector() const +{ + return m_vector; +} + +void Interrupts::setNmiCurrent(bool nmiCurrent) +{ + m_ppuNmiCurrent = nmiCurrent; +} diff --git a/nescorelib/emu/interrupts.h b/nescorelib/emu/interrupts.h new file mode 100644 index 0000000..f7e166b --- /dev/null +++ b/nescorelib/emu/interrupts.h @@ -0,0 +1,45 @@ +#pragma once + +#include "nescorelib_global.h" + +// Qt includes +#include + +// forward declarations +class NesEmulator; +class QDataStream; + +class NESCORELIB_EXPORT Interrupts +{ +public: + explicit Interrupts(NesEmulator &emu); + + void pollStatus(); + + void writeState(QDataStream &dataStream) const; + void readState(QDataStream &dataStream); + + enum IrqFlag { + IRQ_APU = 1, + IRQ_DMC = 2, + IRQ_BOARD = 8 + }; + + qint32 flags() const; + void addFlag(IrqFlag flag); + void removeFlag(IrqFlag flag); + void setFlags(qint32 flags); + + quint16 vector() const; + + void setNmiCurrent(bool nmiCurrent); + +private: + NesEmulator &m_emu; + + qint32 m_flags {}; //Determines that IRQ flags (pins) + + bool m_ppuNmiCurrent {}; //Represents the current NMI pin (connected to ppu) + bool m_ppuNmiOld {}; //Represents the old status if NMI pin, used to generate NMI in raising edge + quint16 m_vector {}; +}; diff --git a/nescorelib/emu/memory.cpp b/nescorelib/emu/memory.cpp new file mode 100644 index 0000000..764c423 --- /dev/null +++ b/nescorelib/emu/memory.cpp @@ -0,0 +1,234 @@ +#include "memory.h" + +// Qt includes +#include + +// dbcorelib includes +#include "utils/datastreamutils.h" + +// local includes +#include "nesemulator.h" +#include "rom.h" +#include "mappers/mapper000.h" +#include "mappers/mapper001.h" +#include "mappers/mapper002.h" +#include "mappers/mapper003.h" +#include "mappers/mapper004.h" + +Memory::Memory(NesEmulator &emu) : + m_emu(emu) +{ +} + +std::unique_ptr Memory::getBoard(const Rom &rom) +{ + switch(rom.mapperNumber) + { + case 0: return std::make_unique(m_emu, rom); + case 1: return std::make_unique(m_emu, rom); + case 2: return std::make_unique(m_emu, rom); + case 3: return std::make_unique(m_emu, rom); + case 4: return std::make_unique(m_emu, rom); + } + + throw std::runtime_error(QString("unknown mapper %0").arg(rom.mapperNumber).toStdString()); +} + +void Memory::initialize(const Rom &rom) +{ + m_board = getBoard(rom); + m_board->mapper(); +} + +void Memory::hardReset() +{ + m_wram.fill(0); + m_wram[0x08] = 0xF7; + m_wram[0x09] = 0xEF; + m_wram[0x0A] = 0xDF; + m_wram[0x0F] = 0xBF; + + loadSram(); + + reloadGameGenieCodes(); + + m_board->hardReset(); +} + +void Memory::loadSram() +{ + //TODO +} + +void Memory::reloadGameGenieCodes() +{ + //TODO +} + +quint8 Memory::_readWRam(const quint16 address) +{ + return m_wram[wramAddressToIndex(address & 0x7FF)]; +} + +quint8 Memory::readWRam(const quint16 address) +{ + auto result = _readWRam(address); + return result; +} + +void Memory::writeWRam(const quint16 address, const quint8 value) +{ + m_wram[address & 0x7FF] = value; +} + +quint8 Memory::_read(quint16 address) +{ + m_busRw = true; + m_busAddress = address; + m_emu.emuClockComponents(); + + const auto roundAddress = [](quint16 address) { return (address & 0xF000) >> 12; }; + switch(roundAddress(address)) + { + case roundAddress(0x0000): + case roundAddress(0x1000): + return readWRam(address); + case roundAddress(0x2000): + case roundAddress(0x3000): + return m_emu.ppu().ioRead(address); + case roundAddress(0x4000): + return m_emu.apu().ioRead(address); + case roundAddress(0x5000): + return readEx(address); + case roundAddress(0x6000): + case roundAddress(0x7000): + return readSrm(address); + case roundAddress(0x8000): + case roundAddress(0x9000): + case roundAddress(0xA000): + case roundAddress(0xB000): + case roundAddress(0xC000): + case roundAddress(0xD000): + case roundAddress(0xE000): + case roundAddress(0xF000): + return readPrg(address); + } + + qFatal("undefined read"); + return 0; +} + +quint8 Memory::read(quint16 address) +{ + auto result = _read(address); + return result; +} + +void Memory::write(quint16 address, quint8 value) +{ + m_busRw = false; + m_busAddress = address; + m_emu.emuClockComponents(); + + const auto roundAddress = [](quint16 address) { return (address & 0xF000) >> 12; }; + switch(roundAddress(address)) + { + case roundAddress(0x0000): + case roundAddress(0x1000): + writeWRam(address, value); + break; + case roundAddress(0x2000): + case roundAddress(0x3000): + m_emu.ppu().ioWrite(address, value); + break; + case roundAddress(0x4000): + m_emu.apu().ioWrite(address, value); + break; + case roundAddress(0x5000): + writeEx(address, value); + break; + case roundAddress(0x6000): + case roundAddress(0x7000): + writeSrm(address, value); + break; + case roundAddress(0x8000): + case roundAddress(0x9000): + case roundAddress(0xA000): + case roundAddress(0xB000): + case roundAddress(0xC000): + case roundAddress(0xD000): + case roundAddress(0xE000): + case roundAddress(0xF000): + writePrg(address, value); + break; + default: + qFatal("undefined write"); + } +} + +quint8 Memory::readEx(const quint16 address) +{ + return m_board->readEx(address); +} + +void Memory::writeEx(const quint16 address, const quint8 value) +{ + m_board->writeEx(address, value); +} + +quint8 Memory::readSrm(const quint16 address) +{ + return m_board->readSrm(address); +} + +void Memory::writeSrm(const quint16 address, const quint8 value) +{ + m_board->writeSrm(address, value); +} + +quint8 Memory::readPrg(const quint16 address) +{ + return m_board->readPrg(address); +} + +void Memory::writePrg(const quint16 address, const quint8 value) +{ + m_board->writePrg(address, value); +} + +void Memory::readState(QDataStream &dataStream) +{ + dataStream >> m_wram >> m_busRw >> m_busAddress; + m_board->readState(dataStream); +} + +void Memory::writeState(QDataStream &dataStream) const +{ + dataStream << m_wram << m_busRw << m_busAddress; + m_board->writeState(dataStream); +} + +Board *Memory::board() +{ + return m_board.get(); +} + +const Board *Memory::board() const +{ + return m_board.get(); +} + +bool Memory::busRw() const +{ + return m_busRw; +} + +quint16 Memory::busAddress() const +{ + return m_busAddress; +} + +const std::array &Memory::wram() const +{ + return m_wram; +} diff --git a/nescorelib/emu/memory.h b/nescorelib/emu/memory.h new file mode 100644 index 0000000..85f00f2 --- /dev/null +++ b/nescorelib/emu/memory.h @@ -0,0 +1,68 @@ +#pragma once + +#include "nescorelib_global.h" + +// Qt includes +#include + +// system includes +#include + +// local includes +#include "boards/board.h" + +// forward declarations +class QDataStream; +class NesEmulator; +class Board; +struct Rom; + +class NESCORELIB_EXPORT Memory +{ +public: + explicit Memory(NesEmulator &emu); + + std::unique_ptr getBoard(const Rom &rom); + + void initialize(const Rom &rom); + void hardReset(); + + void loadSram(); + void reloadGameGenieCodes(); + + quint8 _readWRam(const quint16 address); + quint8 readWRam(const quint16 address); + void writeWRam(const quint16 address, const quint8 value); + + quint8 _read(quint16 address); + quint8 read(quint16 address); + void write(quint16 address, quint8 value); + + quint8 readEx(const quint16 address); + void writeEx(const quint16 address, const quint8 value); + quint8 readSrm(const quint16 address); + void writeSrm(const quint16 address, const quint8 value); + quint8 readPrg(const quint16 address); + void writePrg(const quint16 address, const quint8 value); + + void readState(QDataStream &dataStream); + void writeState(QDataStream &dataStream) const; + + Board *board(); + const Board *board() const; + + bool busRw() const; + quint16 busAddress() const; + + static constexpr std::size_t wramAddressToIndex(quint16 address) { return address & 0x7FF; } + const std::array &wram() const; + +private: + NesEmulator &m_emu; + + std::array m_wram {}; + std::unique_ptr m_board {}; + + bool m_busRw {}; + quint16 m_busAddress {}; +}; diff --git a/nescorelib/emu/ports.cpp b/nescorelib/emu/ports.cpp new file mode 100644 index 0000000..45f9778 --- /dev/null +++ b/nescorelib/emu/ports.cpp @@ -0,0 +1,53 @@ +#include "ports.h" + +// Qt includes +#include + +Ports::Ports(NesEmulator &emu) : + m_emu(emu) +{ +} + +void Ports::update() +{ + for(auto &input : m_inputs) + if(input) + input->update(); +} + +void Ports::updatePorts() +{ + const auto getData = [this](int index){ return m_inputs[index] ? m_inputs[index]->getData() : 0; }; + m_port0 = getData(2) << 8 | getData(0) | 0x01010000; + m_port1 = getData(3) << 8 | getData(1) | 0x02020000; +} + +void Ports::portWriteState(QDataStream &dataStream) const +{ + dataStream << m_port0 << m_port1; +} + +void Ports::portReadState(QDataStream &dataStream) +{ + dataStream >> m_port0 >> m_port1; +} + +quint32 Ports::port0() const +{ + return m_port0; +} + +void Ports::setPort0(quint32 port0) +{ + m_port0 = port0; +} + +quint32 Ports::port1() const +{ + return m_port1; +} + +void Ports::setPort1(quint32 port1) +{ + m_port1 = port1; +} diff --git a/nescorelib/emu/ports.h b/nescorelib/emu/ports.h new file mode 100644 index 0000000..8279d5b --- /dev/null +++ b/nescorelib/emu/ports.h @@ -0,0 +1,42 @@ +#pragma once + +#include "nescorelib_global.h" + +// Qt includes +#include + +// system includes +#include + +// local includes +#include "inputprovider.h" + +// forward declarations +class NesEmulator; +class QDataStream; + +class NESCORELIB_EXPORT Ports +{ +public: + explicit Ports(NesEmulator &emu); + + void update(); + void updatePorts(); + + void portWriteState(QDataStream &dataStream) const; + void portReadState(QDataStream &dataStream); + + quint32 port0() const; + void setPort0(quint32 port0); + + quint32 port1() const; + void setPort1(quint32 port1); + +private: + NesEmulator &m_emu; + + std::array, 4> m_inputs {}; + + quint32 m_port0 {}; + quint32 m_port1 {}; +}; diff --git a/nescorelib/emu/ppu.cpp b/nescorelib/emu/ppu.cpp new file mode 100644 index 0000000..c0dd4b4 --- /dev/null +++ b/nescorelib/emu/ppu.cpp @@ -0,0 +1,983 @@ +#include "ppu.h" + +// Qt includes +#include + +// dbcorelib includes +#include "utils/datastreamutils.h" + +// local includes +#include "nesemulator.h" +#include "emusettings.h" + +Ppu::Ppu(NesEmulator &emu) : + QObject(&emu), + m_emu(emu) +{ +} + +void Ppu::hardReset() +{ + m_ppuReg2001Grayscale = 0xF3; + + // oam + m_ppuOamBank.fill(0); + m_ppuOamBankSecondary.fill(0); + oamReset(); + + // pallettes + m_ppuPaletteBank = { + 0x09, 0x01, 0x00, 0x01, 0x00, 0x02, 0x02, 0x0D, 0x08, 0x10, 0x08, 0x24, 0x00, 0x00, 0x04, 0x2C, // Bkg palette + 0x09, 0x01, 0x34, 0x03, 0x00, 0x04, 0x00, 0x14, 0x08, 0x3A, 0x00, 0x02, 0x00, 0x20, 0x2C, 0x08 // Spr palette + }; + //ppu_palette = PaletteFileWrapper.LoadFile(""); + + m_ppuColorAnd = m_ppuReg2001Grayscale | m_ppuReg2001Emphasis; + + m_ppuRegIoDb = 0; + m_ppuRegIoAddr = 0; + m_ppuRegAccessHappened = false; + m_ppuRegAccessW = false; + + m_ppuReg2000VramAddressIncreament = 1; + m_ppuReg2000SpritePatternTableAddressFor8x8Sprites = 0; + m_ppuReg2000BackgroundPatternTableAddress = 0; + m_ppuReg2000SpriteSize = 0; + m_ppuReg2000Vbi = false; + + m_ppuReg2001ShowBackgroundInLeftmost8PixelsOfScreen = false; + m_ppuReg2001ShowSpritesInLeftmost8PixelsOfScreen = false; + m_ppuReg2001ShowBackground = false; + m_ppuReg2001ShowSprites = false; + m_ppuReg2001Grayscale = 0; + m_ppuReg2001Emphasis = 0; + + m_ppuReg2002SpriteOverflow = false; + m_ppuReg2002Sprite0Hit = false; + m_ppuReg2002VblankStartedFlag = false; + + m_ppuReg2003OamAddr = 0; + + m_ppuIsSprfetch = false; +} + +void Ppu::clock() +{ + static constexpr std::array ppuVClocks = []() { + std::array ppuVClocks {}; + + for(std::size_t i = 0; i < SCREEN_HEIGHT; i++) + ppuVClocks[i] = &Ppu::scanlineRender; + + ppuVClocks[SCREEN_HEIGHT] = &Ppu::scanlineVBlank; + + if(EmuSettings::region == EmuRegion::DENDY) + for(std::size_t i = 241; i <= 290; i++) + ppuVClocks[i] = &Ppu::scanlineVBlank; + + ppuVClocks[EmuSettings::ppuClockVBlankStart] = &Ppu::scanlineVBlankStart; + + for(std::size_t i = EmuSettings::ppuClockVBlankStart + 1; i <= EmuSettings::ppuClockVBlankEnd - 1; i++) + ppuVClocks[i] = &Ppu::scanlineVBlank; + + ppuVClocks[EmuSettings::ppuClockVBlankEnd] = &Ppu::scanlineVBlankEnd; + + return ppuVClocks; + }(); + + static constexpr std::array ppuRegUpdateFuncs { + &Ppu::onRegister2000, &Ppu::onRegister2001, &Ppu::onRegister2002, &Ppu::onRegister2003, + &Ppu::onRegister2004, &Ppu::onRegister2005, &Ppu::onRegister2006, &Ppu::onRegister2007 + }; + + m_emu.memory().board()->onPpuClock(); + + // Clock a scanline + const auto callback = ppuVClocks[m_ppuClockV]; + (this->*callback)(); + + // Advance + if(m_ppuClockH == 340) + { + m_emu.memory().board()->onPpuScanlineTick(); + + // Advance scanline ... + if(m_ppuClockV == EmuSettings::ppuClockVBlankEnd) + { + m_ppuClockV = 0; + + Q_EMIT frameFinished(m_ppuScreenPixels); + } + else + m_ppuClockV++; + m_ppuClockH = -1; + } + m_ppuClockH++; + + /* THEORY: + * After read/write (io access at 0x200x), the registers take effect at the end of the ppu cycle. + * That's why we have the vbl and nmi effects on writing at 0x2000 and reading from 0x2002. + * Same forother registers. First, when cpu access the IO at 0x200x, a data register sets from cpu written value. + * At the end of the next ppu clock, ppu check out what happened in IO, then updates flags and other stuff. + * forwrites, first cpu sets the data bus, then AT THE END OF PPU CYCLE, update flags and data from the data bus to use at the next cycle. + * forreads, first update the data bus with flags and data, then return the value to cpu. + * After that, AT THE END OF PPU CYCLE, ppu acknowledge the read: flags get reset, data updated ...etc + */ + if(m_ppuRegAccessHappened) + { + m_ppuRegAccessHappened = false; + (this->*ppuRegUpdateFuncs[m_ppuRegIoAddr])(); + } +} + +void Ppu::scanlineRender() +{ + static constexpr std::array ppuBkgFetches { + &Ppu::bkgFetch0, &Ppu::bkgFetch1, &Ppu::bkgFetch2, &Ppu::bkgFetch3, + &Ppu::bkgFetch4, &Ppu::bkgFetch5, &Ppu::bkgFetch6, &Ppu::bkgFetch7 + }; + + static constexpr std::array ppuSprFetches { + &Ppu::bkgFetch0, &Ppu::bkgFetch1, &Ppu::bkgFetch2, &Ppu::bkgFetch3, + &Ppu::sprFetch0, &Ppu::sprFetch1, &Ppu::sprFetch2, &Ppu::sprFetch3 + }; + + static constexpr std::array ppuOamPhases { + &Ppu::oamPhase0, &Ppu::oamPhase1, &Ppu::oamPhase2, &Ppu::oamPhase3, &Ppu::oamPhase4, + &Ppu::oamPhase5, &Ppu::oamPhase6, &Ppu::oamPhase7, &Ppu::oamPhase8 + }; + + // 0 - 239 scanlines and pre-render scanline 261 + if(m_ppuClockH > 0) + { + if(m_ppuReg2001ShowBackground || m_ppuReg2001ShowSprites) + { + if(m_ppuClockH < SCREEN_WIDTH + 1) + { + // H clocks 1 - 256 + // OAM evaluation doesn't occur on pre-render scanline. + if(m_ppuClockV != EmuSettings::ppuClockVBlankEnd) + { + // Sprite evaluation + if(m_ppuClockH < 65) + { + m_ppuOamBankSecondary[(m_ppuClockH - 1) & 0x1F] = 0xFF; + } + else + { + if(m_ppuClockH == 65) + oamReset(); + + if((m_ppuClockH & 1) == 1) + oamEvFetch(); + else + (this->*ppuOamPhases[m_ppuPhaseIndex])(); + + if(m_ppuClockH == SCREEN_WIDTH) + oamClear(); + } + } + + // BKG fetches + (this->*ppuBkgFetches[(m_ppuClockH - 1) & 7])(); + + if(m_ppuClockH < SCREEN_WIDTH + 1) + renderPixel(); + } + else if(m_ppuClockH < 321) + { + // H clocks 256 - 320 + // BKG garbage fetches and sprite fetches + (this->*ppuSprFetches[(m_ppuClockH - 1) & 7])(); + + if(m_ppuClockH == SCREEN_WIDTH + 1) + m_ppuVramAddr = (m_ppuVramAddr & 0x7BE0) | (m_ppuVramAddrTemp & 0x041F); + + if(m_ppuClockV == EmuSettings::ppuClockVBlankEnd && m_ppuClockH >= 280 && m_ppuClockH <= 304) + m_ppuVramAddr = (m_ppuVramAddr & 0x041F) | (m_ppuVramAddrTemp & 0x7BE0); + } + else + { + // 321 - 340 + // BKG dummy fetch + (this->*ppuBkgFetches[(m_ppuClockH - 1) & 7])(); + } + } + else if(m_ppuClockV < SCREEN_HEIGHT && m_ppuClockH < SCREEN_WIDTH + 1) + { + // Rendering is off, draw color at vram address ifit in range 0x3F00 - 0x3FFF + if((m_ppuVramAddr & 0x3F00) == 0x3F00) + { + if((m_ppuVramAddr & 0x03) == 0) + { + const auto index1 = m_ppuClockH - 1 + (m_ppuClockV * SCREEN_WIDTH); + const auto index2 = m_ppuPaletteBank[m_ppuVramAddr & 0x0C] & m_ppuColorAnd; + const auto value = EmuSettings::Video::palette[index2]; + m_ppuScreenPixels[index1] = value; + } + else + { + const auto index1 = m_ppuClockH - 1 + (m_ppuClockV * SCREEN_WIDTH); + const auto index2 = m_ppuPaletteBank[m_ppuVramAddr & 0x1F] & m_ppuColorAnd; + const auto value = EmuSettings::Video::palette[index2]; + m_ppuScreenPixels[index1] = value; + } + } + else + { + const auto index1 = m_ppuClockH - 1 + (m_ppuClockV * SCREEN_WIDTH); + const auto index2 = m_ppuPaletteBank[0] & m_ppuColorAnd; + const auto value = EmuSettings::Video::palette[index2]; + m_ppuScreenPixels[index1] = value; + } + } + }// else is the idle clock +} + +void Ppu::scanlineVBlankStart() +{ + // This is scanline 241 + m_ppuIsNmiTime = m_ppuClockH >= 1 && m_ppuClockH <= 4; + if(m_ppuIsNmiTime)// 0 - 3 + { + if(m_ppuClockH == 1) + m_ppuReg2002VblankStartedFlag = true; + + m_emu.interrupts().setNmiCurrent(m_ppuReg2002VblankStartedFlag & m_ppuReg2000Vbi); + } +} + +void Ppu::scanlineVBlankEnd() +{ + // This is scanline 261, also called pre-render line + m_ppuIsNmiTime = m_ppuClockH >= 1 && m_ppuClockH <= 4; + + // Pre-render line is here ! + if(m_ppuClockH > 0) + { + // Do a pre-render + scanlineRender(); + + if(m_ppuClockH == 1) + { + // Clear vbl flag + m_ppuReg2002Sprite0Hit = false; + m_ppuReg2002VblankStartedFlag = false; + m_ppuReg2002SpriteOverflow = false; + } + + if(EmuSettings::ppuUseOddCycle) + { + if(m_ppuClockH == 339) + { + // ODD Cycle + m_ppuUseOddSwap = !m_ppuUseOddSwap; + if(!m_ppuUseOddSwap & (m_ppuReg2001ShowBackground || m_ppuReg2001ShowSprites)) + m_ppuClockH++; + } + } + } +} + +void Ppu::scanlineVBlank() +{ +} + +void Ppu::bkgFetch0() +{ + // Calculate NT address + m_ppuBkgfetchNtAddr = 0x2000 | (m_ppuVramAddr & 0x0FFF); + m_emu.memory().board()->onPpuAddressUpdate(m_ppuBkgfetchNtAddr); +} + +void Ppu::bkgFetch1() +{ + // Fetch NT data + m_ppuBkgfetchNtData = m_emu.memory().board()->readNmt(m_ppuBkgfetchNtAddr); +} + +void Ppu::bkgFetch2() +{ + // Calculate AT address + m_ppuBkgfetchAtAddr = 0x23C0 | (m_ppuVramAddr & 0xC00) | ((m_ppuVramAddr >> 4) & 0x38) | ((m_ppuVramAddr >> 2) & 0x7); + m_emu.memory().board()->onPpuAddressUpdate(m_ppuBkgfetchAtAddr); +} + +void Ppu::bkgFetch3() +{ + // Fetch AT data + m_ppuBkgfetchAtData = m_emu.memory().board()->readNmt(m_ppuBkgfetchAtAddr); + m_ppuBkgfetchAtData = m_ppuBkgfetchAtData >> ((m_ppuVramAddr >> 4 & 0x04) | (m_ppuVramAddr & 0x02)); +} + +void Ppu::bkgFetch4() +{ + // Calculate tile low-bit address + m_ppuBkgfetchLbAddr = m_ppuReg2000BackgroundPatternTableAddress | (m_ppuBkgfetchNtData << 4) | (m_ppuVramAddr >> 12 & 7); + m_emu.memory().board()->onPpuAddressUpdate(m_ppuBkgfetchLbAddr); +} + +void Ppu::bkgFetch5() +{ + // Fetch tile low-bit data + m_ppuBkgfetchLbData = m_emu.memory().board()->readChr(m_ppuBkgfetchLbAddr); +} + +void Ppu::bkgFetch6() +{ + // Calculate tile high-bit address + m_ppuBkgfetchHbAddr = m_ppuReg2000BackgroundPatternTableAddress | (m_ppuBkgfetchNtData << 4) | 8 | (m_ppuVramAddr >> 12 & 7); + m_emu.memory().board()->onPpuAddressUpdate(m_ppuBkgfetchHbAddr); +} + +void Ppu::bkgFetch7() +{ + // Fetch tile high-bit data + m_ppuBkgfetchHbData = m_emu.memory().board()->readChr(m_ppuBkgfetchHbAddr); + + const auto ppuBkgRenderPos = m_ppuClockH > 320 ? m_ppuClockH - 327 : m_ppuClockH + 9; + + // Rendering background pixel + for(auto i = 0; i < 8; i++) + { + const auto temp = ((m_ppuBkgfetchAtData << 2) & 0xC) | (m_ppuBkgfetchLbData >> 7 & 1) | (m_ppuBkgfetchHbData >> 6 & 2); + if((temp & 3) != 0) + m_ppuBkgPixels[i + ppuBkgRenderPos] = temp; + else + m_ppuBkgPixels[i + ppuBkgRenderPos] = 0; + m_ppuBkgfetchLbData <<= 1; + m_ppuBkgfetchHbData <<= 1; + } + + // Increments + if(m_ppuClockH == SCREEN_WIDTH) + { + // Increment Y + if((m_ppuVramAddr & 0x7000) != 0x7000) + m_ppuVramAddr += 0x1000; + else + { + m_ppuVramAddr ^= 0x7000; + + switch (m_ppuVramAddr & 0x3E0) + { + case 0x3A0: m_ppuVramAddr ^= 0xBA0; break; + case 0x3E0: m_ppuVramAddr ^= 0x3E0; break; + default: m_ppuVramAddr += 0x20; break; + } + } + } + else + { + // Increment X + if((m_ppuVramAddr & 0x001F) == 0x001F) + m_ppuVramAddr ^= 0x041F; + else + m_ppuVramAddr++; + } +} + +void Ppu::sprFetch0() +{ + m_ppuSprfetchSlot = (((m_ppuClockH - 1) >> 3) & 7); + m_ppuSprfetchYData = m_ppuOamBankSecondary[(m_ppuSprfetchSlot * 4)]; + m_ppuSprfetchTData = m_ppuOamBankSecondary[(m_ppuSprfetchSlot * 4) + 1]; + m_ppuSprfetchAtData = m_ppuOamBankSecondary[(m_ppuSprfetchSlot * 4) + 2]; + m_ppuSprfetchXData = m_ppuOamBankSecondary[(m_ppuSprfetchSlot * 4) + 3]; + + const auto pputempcomparator = (m_ppuClockV - m_ppuSprfetchYData) ^ ((m_ppuSprfetchAtData & 0x80) != 0 ? 0x0F : 0x00); + if(m_ppuReg2000SpriteSize == 0x10) + m_ppuSprfetchLbAddr = (m_ppuSprfetchTData << 0x0C & 0x1000) | (m_ppuSprfetchTData << 0x04 & 0x0FE0) | + (pputempcomparator << 0x01 & 0x0010) | (pputempcomparator & 0x0007); + else + m_ppuSprfetchLbAddr = m_ppuReg2000SpritePatternTableAddressFor8x8Sprites | (m_ppuSprfetchTData << 0x04) | (pputempcomparator & 0x0007); + + m_emu.memory().board()->onPpuAddressUpdate(m_ppuSprfetchLbAddr); +} + +void Ppu::sprFetch1() +{ + // Fetch tile low-bit data + m_ppuIsSprfetch = true; + m_ppuSprfetchLbData = m_emu.memory().board()->readChr(m_ppuSprfetchLbAddr); + m_ppuIsSprfetch = false; +} + +void Ppu::sprFetch2() +{ + m_ppuSprfetchHbAddr = m_ppuSprfetchLbAddr | 0x08; + m_emu.memory().board()->onPpuAddressUpdate(m_ppuSprfetchHbAddr); +} + +void Ppu::sprFetch3() +{ + if(m_ppuSprfetchXData == 255) + return; + + // Fetch tile high-bit data + m_ppuIsSprfetch = true; + m_ppuSprfetchHbData = m_emu.memory().board()->readChr(m_ppuSprfetchHbAddr); + m_ppuIsSprfetch = false; + + // Render the sprite + int ppuBkgRenderPos = m_ppuSprfetchXData; + + if((m_ppuSprfetchAtData & 0x40) == 0) + { + // Rendering sprite pixel + for(auto i = 0; i < 8; i++) + { + if(ppuBkgRenderPos < 255) + { + const auto temp = ((m_ppuSprfetchAtData << 2) & 0xC) | (m_ppuSprfetchLbData >> 7 & 1) | (m_ppuSprfetchHbData >> 6 & 2); + if((temp & 3) != 0 && (m_ppuSprPixels[ppuBkgRenderPos] & 3) == 0) + m_ppuSprPixels[ppuBkgRenderPos] = temp; + + if(m_ppuSprfetchSlot == 0 && m_ppuSprite0ShouldHit) + { + //m_ppuSprite0ShouldHit = false; + m_ppuSprPixels[ppuBkgRenderPos] |= 0x4000;// Sprite 0 + } + + if((m_ppuSprfetchAtData & 0x20) == 0) + m_ppuSprPixels[ppuBkgRenderPos] |= 0x8000; + + m_ppuSprfetchLbData <<= 1; + m_ppuSprfetchHbData <<= 1; + + ppuBkgRenderPos++; + } + } + } + else + { + // H flip + for(auto i = 0; i < 8; i++) + { + if(ppuBkgRenderPos < 255) + { + const auto temp = ((m_ppuSprfetchAtData << 2) & 0xC) | (m_ppuSprfetchLbData & 1) | (m_ppuSprfetchHbData << 1 & 2); + if((temp & 3) && (m_ppuSprPixels[ppuBkgRenderPos] & 3) == 0) + m_ppuSprPixels[ppuBkgRenderPos] = temp; + + if(m_ppuSprfetchSlot == 0 && m_ppuSprite0ShouldHit) + { + //m_ppuSprite0ShouldHit = false; + m_ppuSprPixels[ppuBkgRenderPos] |= 0x4000;// Sprite 0 + } + + if((m_ppuSprfetchAtData & 0x20) == 0) + m_ppuSprPixels[ppuBkgRenderPos] |= 0x8000; + + m_ppuSprfetchLbData >>= 1; + m_ppuSprfetchHbData >>= 1; + + ppuBkgRenderPos++; + } + } + } +} + +void Ppu::oamReset() +{ + m_ppuOamEvN = 0; + m_ppuOamEvM = 0; + m_ppuOamevSlot = 0; + m_ppuPhaseIndex = 0; + m_ppuSprite0ShouldHit = false; +} + +void Ppu::oamClear() +{ + m_ppuSprPixels.fill(0); +} + +void Ppu::oamEvFetch() +{ + m_ppuFetchData = m_ppuOamBank[(m_ppuOamEvN * 4) + m_ppuOamEvM]; +} + +void Ppu::oamPhase0() +{ + m_ppuOamevCompare = m_ppuClockV >= m_ppuFetchData && m_ppuClockV < m_ppuFetchData + m_ppuReg2000SpriteSize; + + // Check ifread data in range + if(m_ppuOamevCompare) + { + m_ppuOamBankSecondary[(m_ppuOamevSlot * 4)] = m_ppuFetchData; + m_ppuOamEvM = 1; + m_ppuPhaseIndex++; + if(m_ppuOamEvN == 0) + m_ppuSprite0ShouldHit = true; + } + else + { + m_ppuOamEvM = 0; + m_ppuOamEvN++; + if(m_ppuOamEvN == 64) + { + m_ppuOamEvN = 0; + m_ppuPhaseIndex = 8; + } + } +} + +void Ppu::oamPhase1() +{ + m_ppuOamBankSecondary[(m_ppuOamevSlot * 4) + m_ppuOamEvM] = m_ppuFetchData; + m_ppuOamEvM = 2; + m_ppuPhaseIndex++; +} + +void Ppu::oamPhase2() +{ + m_ppuOamBankSecondary[(m_ppuOamevSlot * 4) + m_ppuOamEvM] = m_ppuFetchData; + m_ppuOamEvM = 3; + m_ppuPhaseIndex++; +} + +void Ppu::oamPhase3() +{ + m_ppuOamBankSecondary[(m_ppuOamevSlot * 4) + m_ppuOamEvM] = m_ppuFetchData; + m_ppuOamEvM = 0; + m_ppuOamEvN++; + m_ppuOamevSlot++; + + if(m_ppuOamEvN == 64) + { + m_ppuOamEvN = 0; + m_ppuPhaseIndex = 8; + } + else if(m_ppuOamevSlot < 8) + m_ppuPhaseIndex = 0; + else if(m_ppuOamevSlot == 8) + m_ppuPhaseIndex = 4; +} + +void Ppu::oamPhase4() +{ + m_ppuOamevCompare = m_ppuClockV >= m_ppuFetchData && + m_ppuClockV < m_ppuFetchData + m_ppuReg2000SpriteSize; + + // Check ifread data in range + if(m_ppuOamevCompare) + { + m_ppuOamEvM = 1; + m_ppuPhaseIndex++; + m_ppuReg2002SpriteOverflow = true; + } + else + { + m_ppuOamEvM++; + + if(m_ppuOamEvM == 4) + m_ppuOamEvM = 0; + + m_ppuOamEvN++; + + if(m_ppuOamEvN == 64) + { + m_ppuOamEvN = 0; + m_ppuPhaseIndex = 8; + } + else + m_ppuPhaseIndex = 4; + } +} + +void Ppu::oamPhase5() +{ + m_ppuOamEvM = 2; + m_ppuPhaseIndex++; +} + +void Ppu::oamPhase6() +{ + m_ppuOamEvM = 3; + m_ppuPhaseIndex++; +} + +void Ppu::oamPhase7() +{ + m_ppuOamEvM = 0; + m_ppuOamEvN++; + if(m_ppuOamEvN == 64) + { + m_ppuOamEvN = 0; + m_ppuPhaseIndex = 8; + } +} + +void Ppu::oamPhase8() +{ + m_ppuOamEvN++; + if(m_ppuOamEvN >= 64) + m_ppuOamEvN = 0; +} + +void Ppu::renderPixel() +{ + if(m_ppuClockV == EmuSettings::ppuClockVBlankEnd) + return; + + const auto ppuRenderX = m_ppuClockH - 1; + + int ppuBkgCurrentPixel; + int ppuSprCurrentPixel; + + // Get the pixels. + if(ppuRenderX < 8) + { + // This area is not rendered + if(m_ppuReg2001ShowBackgroundInLeftmost8PixelsOfScreen) + ppuBkgCurrentPixel = 0x3F00 | m_ppuBkgPixels[ppuRenderX + m_ppuVramFinex + 1]; + else + ppuBkgCurrentPixel = 0x3F00; + + if(m_ppuReg2001ShowSpritesInLeftmost8PixelsOfScreen && m_ppuClockV != 0) + ppuSprCurrentPixel = 0x3F10 | (m_ppuSprPixels[ppuRenderX] & 0xFF); + else + ppuSprCurrentPixel = 0x3F10; + } + else + { + if(!m_ppuReg2001ShowBackground) + ppuBkgCurrentPixel = 0x3F00; + else + ppuBkgCurrentPixel = 0x3F00 | m_ppuBkgPixels[ppuRenderX + m_ppuVramFinex + 1]; + + if(!m_ppuReg2001ShowSprites || m_ppuClockV == 0) + ppuSprCurrentPixel = 0x3F10; + else + ppuSprCurrentPixel = 0x3F10 | (m_ppuSprPixels[ppuRenderX] & 0xFF); + + } + + int ppuCurrentPixel; + + if((ppuBkgCurrentPixel & 3) == 0) + { + ppuCurrentPixel = ppuSprCurrentPixel; + goto render; + } + + if((ppuSprCurrentPixel & 3) == 0) + { + ppuCurrentPixel = ppuBkgCurrentPixel; + goto render; + } + + // Use priority + if(m_ppuSprPixels[ppuRenderX] & 0x8000) + ppuCurrentPixel = ppuSprCurrentPixel; + else + ppuCurrentPixel = ppuBkgCurrentPixel; + + // Sprite 0 Hit + if(m_ppuSprPixels[ppuRenderX] & 0x4000) + m_ppuReg2002Sprite0Hit = true; + +render: + if((ppuCurrentPixel & 0x03) == 0) + { + const auto index1 = ppuRenderX + (m_ppuClockV * SCREEN_WIDTH); + const auto index2 = m_ppuPaletteBank[ppuCurrentPixel & 0x0C] & m_ppuColorAnd; + const auto value = EmuSettings::Video::palette[index2]; + m_ppuScreenPixels[index1] = value; + } + else + { + const auto index1 = ppuRenderX + (m_ppuClockV * SCREEN_WIDTH); + const auto index2 = m_ppuPaletteBank[ppuCurrentPixel & 0x1F] & m_ppuColorAnd; + const auto value = EmuSettings::Video::palette[index2]; + m_ppuScreenPixels[index1] = value; + } +} + +quint8 Ppu::_ioRead(const quint16 address) +{ + static constexpr std::array ppuRegReadFuncs { + &Ppu::read2000, &Ppu::read2001, &Ppu::read2002, &Ppu::read2003, + &Ppu::read2004, &Ppu::read2005, &Ppu::read2006, &Ppu::read2007 + }; + + // PPU IO Registers. Emulating bus here. + m_ppuRegIoAddr = address & 0x7; + m_ppuRegAccessHappened = true; + m_ppuRegAccessW = false; + + (this->*ppuRegReadFuncs[m_ppuRegIoAddr])(); + + return m_ppuRegIoDb; +} + +quint8 Ppu::ioRead(const quint16 address) +{ + auto result = _ioRead(address); + return result; +} + +void Ppu::ioWrite(const quint16 address, const quint8 value) +{ + // PPU IO Registers. Emulating bus here. + m_ppuRegIoAddr = address & 0x7; + m_ppuRegIoDb = value; + m_ppuRegAccessW = true; + m_ppuRegAccessHappened = true; +} + +void Ppu::onRegister2000() +{ + // Only writes accepted + if(!m_ppuRegAccessW) + return; + + // Update vram address + m_ppuVramAddrTemp = (m_ppuVramAddrTemp & 0x73FF) | ((m_ppuRegIoDb & 0x3) << 10); + + if((m_ppuRegIoDb & 4) != 0) + m_ppuReg2000VramAddressIncreament = 32; + else + m_ppuReg2000VramAddressIncreament = 1; + + if((m_ppuRegIoDb & 0x8) != 0) + m_ppuReg2000SpritePatternTableAddressFor8x8Sprites = 0x1000; + else + m_ppuReg2000SpritePatternTableAddressFor8x8Sprites = 0x0000; + + if((m_ppuRegIoDb & 0x10) != 0) + m_ppuReg2000BackgroundPatternTableAddress = 0x1000; + else + m_ppuReg2000BackgroundPatternTableAddress = 0x0000; + + if((m_ppuRegIoDb & 0x20) != 0) + m_ppuReg2000SpriteSize = 0x0010; + else + m_ppuReg2000SpriteSize = 0x0008; + + if(!m_ppuReg2000Vbi && ((m_ppuRegIoDb & 0x80) != 0)) + { + if(m_ppuReg2002VblankStartedFlag)// Special case ! NMI can be enabled anytime ifvbl already set + m_emu.interrupts().setNmiCurrent(true); + } + + m_ppuReg2000Vbi = m_ppuRegIoDb & 0x80; + + if(!m_ppuReg2000Vbi)// NMI disable effect only at vbl set period (HClock between 1 and 3) + if(m_ppuIsNmiTime)// 0 - 3 + m_emu.interrupts().setNmiCurrent(false); +} + +void Ppu::onRegister2001() +{ + // Only writes accepted + if(!m_ppuRegAccessW) + return; + + m_ppuReg2001ShowBackgroundInLeftmost8PixelsOfScreen = m_ppuRegIoDb & 0x2; + m_ppuReg2001ShowSpritesInLeftmost8PixelsOfScreen = m_ppuRegIoDb & 0x4; + m_ppuReg2001ShowBackground = m_ppuRegIoDb & 0x8; + m_ppuReg2001ShowSprites = m_ppuRegIoDb & 0x10; + + m_ppuReg2001Grayscale = m_ppuRegIoDb & 0x01 ? 0x30 : 0x3F; + m_ppuReg2001Emphasis = (m_ppuRegIoDb & 0xE0) << 1; + + m_ppuColorAnd = m_ppuReg2001Grayscale | m_ppuReg2001Emphasis; +} + +void Ppu::onRegister2002() +{ + // Only reads accepted + if(m_ppuRegAccessW) + return; + + m_ppuVramFlipFlop = false; + m_ppuReg2002VblankStartedFlag = false; + + if(m_ppuClockV == EmuSettings::ppuClockVBlankStart) + m_emu.interrupts().setNmiCurrent(m_ppuReg2002VblankStartedFlag & m_ppuReg2000Vbi); +} + +void Ppu::onRegister2003() +{ + // Only writes accepted + if(!m_ppuRegAccessW) + return; + + m_ppuReg2003OamAddr = m_ppuRegIoDb; +} + +void Ppu::onRegister2004() +{ + if(m_ppuRegAccessW) + { + // ON Writes + m_ppuOamBank[m_ppuReg2003OamAddr] = m_ppuRegIoDb; + m_ppuReg2003OamAddr = (m_ppuReg2003OamAddr + 1) & 0xFF; + } + // Nothing happens on reads +} + +void Ppu::onRegister2005() +{ + // Only writes accepted + if(!m_ppuRegAccessW) + return; + + if(!m_ppuVramFlipFlop) + { + m_ppuVramAddrTemp = (m_ppuVramAddrTemp & 0x7FE0) | ((m_ppuRegIoDb & 0xF8) >> 3); + m_ppuVramFinex = m_ppuRegIoDb & 0x07; + } + else + m_ppuVramAddrTemp = (m_ppuVramAddrTemp & 0x0C1F) | ((m_ppuRegIoDb & 0x7) << 12) | ((m_ppuRegIoDb & 0xF8) << 2); + + m_ppuVramFlipFlop = !m_ppuVramFlipFlop; +} + +void Ppu::onRegister2006() +{ + // Only writes accepted + if(!m_ppuRegAccessW) + return; + + if(!m_ppuVramFlipFlop) + m_ppuVramAddrTemp = (m_ppuVramAddrTemp & 0x00FF) | ((m_ppuRegIoDb & 0x3F) << 8); + else + { + m_ppuVramAddrTemp = (m_ppuVramAddrTemp & 0x7F00) | m_ppuRegIoDb; + m_ppuVramAddr = m_ppuVramAddrTemp; + m_emu.memory().board()->onPpuAddressUpdate(m_ppuVramAddr); + } + + m_ppuVramFlipFlop = !m_ppuVramFlipFlop; +} + +void Ppu::onRegister2007() +{ + if(m_ppuRegAccessW) + { + m_ppuVramAddrAccessTemp = m_ppuVramAddr & 0x3FFF; + + // ON Writes + if(m_ppuVramAddrAccessTemp < 0x2000) + m_emu.memory().board()->writeChr(m_ppuVramAddrAccessTemp, m_ppuRegIoDb); + else if(m_ppuVramAddrAccessTemp < 0x3F00) + m_emu.memory().board()->writeNmt(m_ppuVramAddrAccessTemp, m_ppuRegIoDb); + else + { + if(m_ppuVramAddrAccessTemp & 3) + m_ppuPaletteBank[m_ppuVramAddrAccessTemp & 0x1F] = m_ppuRegIoDb; + else + m_ppuPaletteBank[m_ppuVramAddrAccessTemp & 0x0C] = m_ppuRegIoDb; + } + } + else + { + // ON Reads + if((m_ppuVramAddr & 0x3F00) == 0x3F00) + m_ppuVramAddrAccessTemp = m_ppuVramAddr & 0x2FFF; + else + m_ppuVramAddrAccessTemp = m_ppuVramAddr & 0x3FFF; + + // Update the vram data bus + if(m_ppuVramAddrAccessTemp < 0x2000) + m_ppuVramData = m_emu.memory().board()->readChr(m_ppuVramAddrAccessTemp); + else if(m_ppuVramAddrAccessTemp < 0x3F00) + m_ppuVramData = m_emu.memory().board()->readNmt(m_ppuVramAddrAccessTemp); + } + + m_ppuVramAddr = (m_ppuVramAddr + m_ppuReg2000VramAddressIncreament) & 0x7FFF; + m_emu.memory().board()->onPpuAddressUpdate(m_ppuVramAddr); +} + +void Ppu::read2000() +{ +} + +void Ppu::read2001() +{ +} + +void Ppu::read2002() +{ + m_ppuRegIoDb = (m_ppuRegIoDb & 0xDF) | (m_ppuReg2002SpriteOverflow ? 0x20 : 0x0); + m_ppuRegIoDb = (m_ppuRegIoDb & 0xBF) | (m_ppuReg2002Sprite0Hit ? 0x40 : 0x0); + m_ppuRegIoDb = (m_ppuRegIoDb & 0x7F) | (m_ppuReg2002VblankStartedFlag ? 0x80 : 0x0); +} + +void Ppu::read2003() +{ +} + +void Ppu::read2004() +{ + m_ppuRegIoDb = m_ppuOamBank[m_ppuReg2003OamAddr]; +} + +void Ppu::read2005() +{ +} + +void Ppu::read2006() +{ +} + +void Ppu::read2007() +{ + m_ppuVramAddrAccessTemp = m_ppuVramAddr & 0x3FFF; + if(m_ppuVramAddrAccessTemp < 0x3F00) + // Reading will put the vram data bus into the io bus, + // then later it will transfer the data from vram datas bus into io data bus. + // This causes the 0x2007 dummy reads effect. + m_ppuRegIoDb = m_ppuVramData; + else + { + // Reading from palettes puts the value in the io bus immediately + if(m_ppuVramAddrAccessTemp & 3) + m_ppuRegIoDb = m_ppuPaletteBank[m_ppuVramAddrAccessTemp & 0x1F]; + else + m_ppuRegIoDb = m_ppuPaletteBank[m_ppuVramAddrAccessTemp & 0x0C]; + } +} + +bool Ppu::isRenderingOn() const +{ + return m_ppuReg2001ShowBackground || m_ppuReg2001ShowSprites; +} + +bool Ppu::isInRender() const +{ + return m_ppuClockV < SCREEN_HEIGHT || m_ppuClockV == EmuSettings::ppuClockVBlankEnd; +} + +void Ppu::readState(QDataStream &dataStream) +{ + dataStream >> m_ppuClockH >> m_ppuClockV >> m_ppuUseOddSwap >> m_ppuIsNmiTime >> m_ppuOamBank >> m_ppuOamBankSecondary >> m_ppuPaletteBank >> m_ppuRegIoDb + >> m_ppuRegIoAddr >> m_ppuRegAccessHappened >> m_ppuRegAccessW >> m_ppuReg2000VramAddressIncreament >> m_ppuReg2000SpritePatternTableAddressFor8x8Sprites + >> m_ppuReg2000BackgroundPatternTableAddress >> m_ppuReg2000SpriteSize >> m_ppuReg2000Vbi >> m_ppuReg2001ShowBackgroundInLeftmost8PixelsOfScreen + >> m_ppuReg2001ShowSpritesInLeftmost8PixelsOfScreen >> m_ppuReg2001ShowBackground >> m_ppuReg2001ShowSprites >> m_ppuReg2001Grayscale >> m_ppuReg2001Emphasis + >> m_ppuReg2002SpriteOverflow >> m_ppuReg2002Sprite0Hit >> m_ppuReg2002VblankStartedFlag >> m_ppuReg2003OamAddr >> m_ppuVramAddr >> m_ppuVramData >> m_ppuVramAddrTemp + >> m_ppuVramAddrAccessTemp >> m_ppuVramFlipFlop >> m_ppuVramFinex >> m_ppuBkgfetchNtAddr >> m_ppuBkgfetchNtData >> m_ppuBkgfetchAtAddr >> m_ppuBkgfetchAtData + >> m_ppuBkgfetchLbAddr >> m_ppuBkgfetchLbData >> m_ppuBkgfetchHbAddr >> m_ppuBkgfetchHbData >> m_ppuSprfetchSlot >> m_ppuSprfetchYData >> m_ppuSprfetchTData + >> m_ppuSprfetchAtData >> m_ppuSprfetchXData >> m_ppuSprfetchLbAddr >> m_ppuSprfetchLbData >> m_ppuSprfetchHbAddr >> m_ppuSprfetchHbData >> m_ppuColorAnd + >> m_ppuOamEvN >> m_ppuOamEvM >> m_ppuOamevCompare >> m_ppuOamevSlot >> m_ppuFetchData >> m_ppuPhaseIndex >> m_ppuSprite0ShouldHit; +} + +void Ppu::writeState(QDataStream &dataStream) const +{ + dataStream << m_ppuClockH << m_ppuClockV << m_ppuUseOddSwap << m_ppuIsNmiTime << m_ppuOamBank << m_ppuOamBankSecondary << m_ppuPaletteBank << m_ppuRegIoDb + << m_ppuRegIoAddr << m_ppuRegAccessHappened << m_ppuRegAccessW << m_ppuReg2000VramAddressIncreament << m_ppuReg2000SpritePatternTableAddressFor8x8Sprites + << m_ppuReg2000BackgroundPatternTableAddress << m_ppuReg2000SpriteSize << m_ppuReg2000Vbi << m_ppuReg2001ShowBackgroundInLeftmost8PixelsOfScreen + << m_ppuReg2001ShowSpritesInLeftmost8PixelsOfScreen << m_ppuReg2001ShowBackground << m_ppuReg2001ShowSprites << m_ppuReg2001Grayscale << m_ppuReg2001Emphasis + << m_ppuReg2002SpriteOverflow << m_ppuReg2002Sprite0Hit << m_ppuReg2002VblankStartedFlag << m_ppuReg2003OamAddr << m_ppuVramAddr << m_ppuVramData << m_ppuVramAddrTemp + << m_ppuVramAddrAccessTemp << m_ppuVramFlipFlop << m_ppuVramFinex << m_ppuBkgfetchNtAddr << m_ppuBkgfetchNtData << m_ppuBkgfetchAtAddr << m_ppuBkgfetchAtData + << m_ppuBkgfetchLbAddr << m_ppuBkgfetchLbData << m_ppuBkgfetchHbAddr << m_ppuBkgfetchHbData << m_ppuSprfetchSlot << m_ppuSprfetchYData << m_ppuSprfetchTData + << m_ppuSprfetchAtData << m_ppuSprfetchXData << m_ppuSprfetchLbAddr << m_ppuSprfetchLbData << m_ppuSprfetchHbAddr << m_ppuSprfetchHbData << m_ppuColorAnd + << m_ppuOamEvN << m_ppuOamEvM << m_ppuOamevCompare << m_ppuOamevSlot << m_ppuFetchData << m_ppuPhaseIndex << m_ppuSprite0ShouldHit; +} + +const std::array &Ppu::screenPixels() const +{ + return m_ppuScreenPixels; +} diff --git a/nescorelib/emu/ppu.h b/nescorelib/emu/ppu.h new file mode 100644 index 0000000..e3b4af4 --- /dev/null +++ b/nescorelib/emu/ppu.h @@ -0,0 +1,187 @@ +#pragma once + +#include "nescorelib_global.h" +#include + +// Qt includes +#include + +// system includes +#include + +// forward declarations +class NesEmulator; +class QDataStream; + +class NESCORELIB_EXPORT Ppu : public QObject +{ + Q_OBJECT + +public: + static constexpr quint32 SCREEN_WIDTH = 256; + static constexpr quint32 SCREEN_HEIGHT = 240; + + explicit Ppu(NesEmulator &emu); + + void hardReset(); + void clock(); + + // scanlines + void scanlineRender(); + void scanlineVBlankStart(); + void scanlineVBlankEnd(); + void scanlineVBlank(); + + // bkg fetches + void bkgFetch0(); + void bkgFetch1(); + void bkgFetch2(); + void bkgFetch3(); + void bkgFetch4(); + void bkgFetch5(); + void bkgFetch6(); + void bkgFetch7(); + + // spr fetches + void sprFetch0(); + void sprFetch1(); + void sprFetch2(); + void sprFetch3(); + + // oam evaluation + void oamReset(); + void oamClear(); + void oamEvFetch(); + void oamPhase0(); + void oamPhase1(); + void oamPhase2(); + void oamPhase3(); + void oamPhase4(); + void oamPhase5(); + void oamPhase6(); + void oamPhase7(); + void oamPhase8(); + + void renderPixel(); + + // io + quint8 _ioRead(const quint16 address); + quint8 ioRead(const quint16 address); + void ioWrite(const quint16 address, const quint8 value); + + void onRegister2000(); + void onRegister2001(); + void onRegister2002(); + void onRegister2003(); + void onRegister2004(); + void onRegister2005(); + void onRegister2006(); + void onRegister2007(); + void read2000(); + void read2001(); + void read2002(); + void read2003(); + void read2004(); + void read2005(); + void read2006(); + void read2007(); + + bool isRenderingOn() const; + bool isInRender() const; + + void readState(QDataStream &dataStream); + void writeState(QDataStream &dataStream) const; + + const std::array &screenPixels() const; + +Q_SIGNALS: + void frameFinished(const std::array &frame); + +private: + NesEmulator &m_emu; + + std::array m_ppuBkgPixels {}; + std::array m_ppuSprPixels {}; + std::array m_ppuScreenPixels {}; + + // Clocks + qint32 m_ppuClockH {}; + quint16 m_ppuClockV {}; + bool m_ppuUseOddSwap {}; + bool m_ppuIsNmiTime {}; + + // Memory + std::array m_ppuOamBank {}; + std::array m_ppuOamBankSecondary {}; + std::array m_ppuPaletteBank {}; + + // Data Reg + quint8 m_ppuRegIoDb {}; //The data bus + quint8 m_ppuRegIoAddr {}; //The address bus (only first 3 bits are used, will be ranged 0-7) + bool m_ppuRegAccessHappened {}; //Triggers when cpu accesses ppu bus. + bool m_ppuRegAccessW {}; //True= write access, False= Read access. + + // 0x2000 register values + quint8 m_ppuReg2000VramAddressIncreament {}; + quint16 m_ppuReg2000SpritePatternTableAddressFor8x8Sprites {}; + quint16 m_ppuReg2000BackgroundPatternTableAddress {}; + quint8 m_ppuReg2000SpriteSize {}; + bool m_ppuReg2000Vbi {}; + + // 0x2001 register values + bool m_ppuReg2001ShowBackgroundInLeftmost8PixelsOfScreen {}; + bool m_ppuReg2001ShowSpritesInLeftmost8PixelsOfScreen {}; + bool m_ppuReg2001ShowBackground {}; + bool m_ppuReg2001ShowSprites {}; + qint32 m_ppuReg2001Grayscale {}; + qint32 m_ppuReg2001Emphasis {}; + + // 0x2002 register values. + bool m_ppuReg2002SpriteOverflow {}; + bool m_ppuReg2002Sprite0Hit {}; + bool m_ppuReg2002VblankStartedFlag {}; + + // 0x2003 register values. + quint8 m_ppuReg2003OamAddr {}; + + // VRAM + quint16 m_ppuVramAddr {}; + quint8 m_ppuVramData {}; + quint16 m_ppuVramAddrTemp {}; + quint16 m_ppuVramAddrAccessTemp {}; + bool m_ppuVramFlipFlop {}; + quint8 m_ppuVramFinex {}; + + // Fetches + quint16 m_ppuBkgfetchNtAddr {}; + quint8 m_ppuBkgfetchNtData {}; + quint16 m_ppuBkgfetchAtAddr {}; + quint8 m_ppuBkgfetchAtData {}; + quint16 m_ppuBkgfetchLbAddr {}; + quint8 m_ppuBkgfetchLbData {}; + quint16 m_ppuBkgfetchHbAddr {}; + quint8 m_ppuBkgfetchHbData {}; + + qint32 m_ppuSprfetchSlot {}; + quint8 m_ppuSprfetchYData {}; + quint8 m_ppuSprfetchTData {}; + quint8 m_ppuSprfetchAtData {}; + quint8 m_ppuSprfetchXData {}; + quint16 m_ppuSprfetchLbAddr {}; + quint8 m_ppuSprfetchLbData {}; + quint16 m_ppuSprfetchHbAddr {}; + quint8 m_ppuSprfetchHbData {}; + + bool m_ppuIsSprfetch {}; + + // Renderer helper + qint32 m_ppuColorAnd {}; + // OAM + quint8 m_ppuOamEvN {}; + quint8 m_ppuOamEvM {}; + bool m_ppuOamevCompare {}; + quint8 m_ppuOamevSlot {}; + quint8 m_ppuFetchData {}; + quint8 m_ppuPhaseIndex {}; + bool m_ppuSprite0ShouldHit {}; +}; diff --git a/nescorelib/emusettings.h b/nescorelib/emusettings.h new file mode 100644 index 0000000..7ef32d3 --- /dev/null +++ b/nescorelib/emusettings.h @@ -0,0 +1,158 @@ +#pragma once + +#include + +#include +#include + +#include "enums/emuregion.h" + +namespace EmuSettings +{ + constexpr EmuRegion region = EmuRegion::PALB; + + constexpr double emuTimeTargetFps = region == EmuRegion::NTSC ? 60.0988 : 50.; + constexpr double emuTimeFramePeriod = 1000. / emuTimeTargetFps; + + constexpr quint16 ppuClockVBlankStart = region == EmuRegion::DENDY ? 291 : 241; + constexpr quint16 ppuClockVBlankEnd = region == EmuRegion::NTSC ? 261 : 311; + constexpr bool ppuUseOddCycle = region == EmuRegion::NTSC; + + constexpr bool frameLimiterEnabled = false; + + namespace Video + { + constexpr float saturation = 2.0f; + constexpr float hue_tweak = 0.0f; + constexpr float contrast = 1.4f; + constexpr float brightness = 1.070f; + constexpr float gamma = 2.0f; + + // Voltage levels, relative to synch voltage + constexpr float black = 0.518f; + constexpr float white = 1.962f; + constexpr float attenuation = 0.746f; + + constexpr std::array levels + { + 0.350f, 0.518f, 0.962f, 1.550f, // Signal low + 1.094f, 1.506f, 1.962f, 1.962f // Signal high + }; + + constexpr qint32 makeRGBcolor(qint32 pixel) + { + // The input value is a NES color index (with de-emphasis bits). + // We need RGB values. Convert the index into RGB. + // For most part, this process is described at: + // http://wiki.nesdev.com/w/index.php/NTSC_video + + // Decode the color index + const qint32 color = (pixel & 0x0F); + const qint32 level = color < 0xE ? (pixel >> 4) & 3 : 1; + + const float lo = levels[level + ((color == 0x0) ? 4 : 0)]; + const float hi = levels[level + ((color <= 0xC) ? 4 : 0)]; + + // Calculate the luma and chroma by emulating the relevant circuits: + float y = 0.0f; + float i = 0.0f; + float q = 0.0f; + + for (int p = 0; p < 12; p++) // 12 clock cycles per pixel. + { + const auto wave = [](const int p, const int color) { + return (color + p + 8) % 12 < 6; + }; + + // NES NTSC modulator (square wave between two voltage levels): + auto spot = wave(p, color) ? hi : lo; + + // De-emphasis bits attenuate a part of the signal: + if (((pixel & 0x040) && wave(p, 0xC)) || + ((pixel & 0x080) && wave(p, 0x4)) || + ((pixel & 0x100) && wave(p, 0x8))) + spot *= attenuation; + + // Normalize: + float v = (spot - black) / (white - black); + + // Ideal TV NTSC demodulator: + // Apply contrast/brightness + v = (v - 0.5F) * contrast + 0.5F; + v *= brightness / 12.0F; + + y += v; +// if constexpr (EmuSettings::region == EmuRegion::NTSC) +// { + i += v * std::cos((M_PI / 6.0) * (p + hue_tweak)); + q += v * std::sin((M_PI / 6.0) * (p + hue_tweak)); +// } +// else +// { +// // PAL hue is rotated by 15° from NTSC +// i += v * std::cos((M_PI / 6.0) * (p + 0.5F + hue_tweak)); +// q += v * std::sin((M_PI / 6.0) * (p + 0.5F + hue_tweak)); +// } + } + + i *= saturation; + q *= saturation; + + const auto gammafix = [](const float f, const float gamma){ + return f < 0.0f ? 0.0f : std::pow(f, 2.2f / gamma); + }; + + const auto clamp = [](const float v)->int { + return v < 0 ? 0 : v > 255 ? 255 : v; + }; + + // Convert YIQ into RGB according to FCC-sanctioned conversion matrix. + return + 0x10000 * clamp(255 * gammafix(y + 0.946882F * i + 0.623557F * q, gamma)) + + 0x00100 * clamp(255 * gammafix(y - 0.274788F * i - 0.635691F * q, gamma)) + + 0x00001 * clamp(255 * gammafix(y - 1.108545F * i + 1.709007F * q, gamma)); + } + + constexpr std::array palette = [](){ + std::array pal {}; + + for (int i = 0; i < 512; i++) + pal[i] = makeRGBcolor(i) | (0xFF << 24); + + return pal; + }(); + } + + namespace Audio + { + constexpr int volume = 100; + constexpr int internalAmplitude = 285; + constexpr int internalPeekLimit = 124; + + namespace ChannelEnabled + { + constexpr bool SQ1 = true; + constexpr bool SQ2 = true; + constexpr bool NOZ = true; + constexpr bool TRL = true; + constexpr bool DMC = true; + constexpr bool MMC5_SQ1 = true; + constexpr bool MMC5_SQ2 = true; + constexpr bool MMC5_PCM = true; + constexpr bool VRC6_SQ1 = true; + constexpr bool VRC6_SQ2 = true; + constexpr bool VRC6_SAW = true; + constexpr bool SUN1 = true; + constexpr bool SUN2 = true; + constexpr bool SUN3 = true; + constexpr bool NMT1 = true; + constexpr bool NMT2 = true; + constexpr bool NMT3 = true; + constexpr bool NMT4 = true; + constexpr bool NMT5 = true; + constexpr bool NMT6 = true; + constexpr bool NMT7 = true; + constexpr bool NMT8 = true; + } + } +} diff --git a/nescorelib/enums/chrarea.h b/nescorelib/enums/chrarea.h new file mode 100644 index 0000000..d85e67f --- /dev/null +++ b/nescorelib/enums/chrarea.h @@ -0,0 +1,13 @@ +#pragma once + +enum class CHRArea +{ + Area0000 = 0, + Area0400 = 1, + Area0800 = 2, + Area0C00 = 3, + Area1000 = 4, + Area1400 = 5, + Area1800 = 6, + Area1C00 = 7, +}; diff --git a/nescorelib/enums/emuregion.h b/nescorelib/enums/emuregion.h new file mode 100644 index 0000000..3cd431b --- /dev/null +++ b/nescorelib/enums/emuregion.h @@ -0,0 +1,8 @@ +#pragma once + +enum class EmuRegion +{ + NTSC, + PALB, + DENDY +}; diff --git a/nescorelib/enums/mirroring.h b/nescorelib/enums/mirroring.h new file mode 100644 index 0000000..dd9b3b3 --- /dev/null +++ b/nescorelib/enums/mirroring.h @@ -0,0 +1,25 @@ +#pragma once + +enum class Mirroring +{ + // Mirroring value: + // 0000 0000 + // ddcc bbaa + // aa: index for area 0x2000 + // bb: index for area 0x2400 + // cc: index for area 0x2800 + // dd: index for area 0x2C00 + /* + ( D C B A) +Horz: $50 (%01 01 00 00) +Vert: $44 (%01 00 01 00) +1ScA: $00 (%00 00 00 00) +1ScB: $55 (%01 01 01 01) +Full: $E4 (%11 10 01 00) + */ + Horizontal = 0x50, + Vertical = 0x44, + OneScA = 0x00, + OneScB = 0x55, + Full = 0xE4 +}; diff --git a/nescorelib/enums/ntarea.h b/nescorelib/enums/ntarea.h new file mode 100644 index 0000000..c4a65a4 --- /dev/null +++ b/nescorelib/enums/ntarea.h @@ -0,0 +1,9 @@ +#pragma once + +enum class NTArea +{ + Area2000NT0 = 0, + Area2400NT1 = 1, + Area2800NT2 = 2, + Area2C00NT3 = 3, +}; diff --git a/nescorelib/enums/prgarea.h b/nescorelib/enums/prgarea.h new file mode 100644 index 0000000..5c53b7f --- /dev/null +++ b/nescorelib/enums/prgarea.h @@ -0,0 +1,17 @@ +#pragma once + +enum class PRGArea +{ + Area4000 = 4, + Area5000 = 5, + Area6000 = 6, + Area7000 = 7, + Area8000 = 8, + Area9000 = 9, + AreaA000 = 10, + AreaB000 = 11, + AreaC000 = 12, + AreaD000 = 13, + AreaE000 = 14, + AreaF000 = 15 +}; diff --git a/nescorelib/inputprovider.h b/nescorelib/inputprovider.h new file mode 100644 index 0000000..e3b648f --- /dev/null +++ b/nescorelib/inputprovider.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class InputProvider +{ +public: + virtual void update() = 0; + virtual quint8 getData() const = 0; +}; diff --git a/nescorelib/mappers/mapper000.cpp b/nescorelib/mappers/mapper000.cpp new file mode 100644 index 0000000..084a882 --- /dev/null +++ b/nescorelib/mappers/mapper000.cpp @@ -0,0 +1,11 @@ +#include "mapper000.h" + +QString Mapper000::name() const +{ + return QStringLiteral("NROM"); +} + +quint8 Mapper000::mapper() const +{ + return 0; +} diff --git a/nescorelib/mappers/mapper000.h b/nescorelib/mappers/mapper000.h new file mode 100644 index 0000000..b1d4be8 --- /dev/null +++ b/nescorelib/mappers/mapper000.h @@ -0,0 +1,13 @@ +#pragma once + +#include "nescorelib_global.h" +#include "boards/board.h" + +class NESCORELIB_EXPORT Mapper000 : public Board +{ +public: + using Board::Board; + + QString name() const Q_DECL_OVERRIDE; + quint8 mapper() const Q_DECL_OVERRIDE; +}; diff --git a/nescorelib/mappers/mapper001.cpp b/nescorelib/mappers/mapper001.cpp new file mode 100644 index 0000000..6c7dac5 --- /dev/null +++ b/nescorelib/mappers/mapper001.cpp @@ -0,0 +1,216 @@ +#include "mapper001.h" + +#include "utils/datastreamutils.h" + +QString Mapper001::name() const +{ + return QStringLiteral("MMC1"); +} + +quint8 Mapper001::mapper() const +{ + return 1; +} + + +void Mapper001::hardReset() +{ + Board::hardReset(); + + m_cpuCycles = 0; + + // Registers + m_addressReg = 0; + m_reg = { 0x0C, 0, 0, 0 }; + m_reg[0] = 0x0C; + m_flagC = false; + m_flagS = true; + m_flagP = true; + m_prgHijackedBit = 0; + + // Buffers + m_buffer = 0; + m_shift = 0; + + //if (Chips.Contains("MMC1B") || Chips.Contains("MMC1B2")) + // TogglePRGRAMEnable(false); + + m_enableWramEnable = true; // !Chips.Contains("MMC1A"); + + // use hijacked + m_useHijacked = (prgRom16KbMask() & 0x10) == 0x10; + if (m_useHijacked) + m_prgHijackedBit = 0x10; + + // SRAM Switch ? + m_useSramSwitch = false; + if (prgRam8KbCount() > 0) + { + m_useSramSwitch = true; + m_sramSwitchMask = m_useHijacked ? 0x08 : 0x18; + m_sramSwitchMask &= prgRam8KbMask() << 3; + + if (m_sramSwitchMask == 0) + m_useSramSwitch = false; + } + + switch16kPrg(0xF | m_prgHijackedBit, PRGArea::AreaC000); +} + +void Mapper001::writePrg(quint16 address, quint8 value) +{ + // Too close writes ignored ! + if (m_cpuCycles > 0) + return; + + m_cpuCycles = 3;// Make save cycles ... + //Temporary reg port ($8000-FFFF): + //[r... ...d] + //r = reset flag + //d = data bit + + //r is set + if ((value & 0x80) == 0x80) + { + m_reg[0] |= 0x0C;//bits 2,3 of reg $8000 are set (16k PRG mode, $8000 swappable) + m_flagS = true; + m_flagP = true; + m_shift = 0; + m_buffer = 0;//hidden temporary reg is reset + return; + } + //d is set + if ((value & 0x01) == 0x01) + m_buffer |= 1 << m_shift; //'d' proceeds as the next bit written in the 5-bit sequence + if (++m_shift < 5) + return; + // If this completes the 5-bit sequence: + // - temporary reg is copied to actual internal reg (which reg depends on the last address written to) + m_addressReg = (address & 0x7FFF) >> 13; + m_reg[m_addressReg] = m_buffer; + + // - temporary reg is reset (so that next write is the "first" write) + m_shift = 0; + m_buffer = 0; + + // Update internal registers ... + switch (m_addressReg) + { + case 0:// $8000-9FFF [Flags and mirroring] + // Flags + m_flagC = m_reg[0] & 0x10; + m_flagP = m_reg[0] & 0x08; + m_flagS = m_reg[0] & 0x04; + updatePRG(); + updateCHR(); + // Mirroring + switch (m_reg[0] & 3) + { + case 0: switch1kNmt(Mirroring::OneScA); break; + case 1: switch1kNmt(Mirroring::OneScB); break; + case 2: switch1kNmt(Mirroring::Vertical); break; + case 3: switch1kNmt(Mirroring::Horizontal); break; + } + break; + case 1:// $A000-BFFF [CHR REG 0] + // CHR + if (!m_flagC) + switch8kChr(m_reg[1] >> 1); + else + switch4kChr(m_reg[1], CHRArea::Area0000); + // SRAM + if (m_useSramSwitch) + switch8kPrg((m_reg[1] & m_sramSwitchMask) >> 3, PRGArea::Area6000); + // PRG hijack + if (m_useHijacked) + { + m_prgHijackedBit = m_reg[1] & 0x10; + updatePRG(); + } + break; + case 2:// $C000-DFFF [CHR REG 1] + // CHR + if (m_flagC) + switch4kChr(m_reg[2], CHRArea::Area1000); + // SRAM + if (m_useSramSwitch) + switch8kPrg((m_reg[2] & m_sramSwitchMask) >> 3, PRGArea::Area6000); + // PRG hijack + if (m_useHijacked) + { + m_prgHijackedBit = m_reg[2] & 0x10; + updatePRG(); + } + break; + case 3:// $E000-FFFF [PRG REG] + if (m_enableWramEnable) + togglePrgRamEnable((m_reg[3] & 0x10) == 0); + updatePRG(); + break; + } +} + +void Mapper001::onCpuClock() +{ + if (m_cpuCycles > 0) + m_cpuCycles--; +} + +void Mapper001::readState(QDataStream &dataStream) +{ + Board::readState(dataStream); + dataStream >> m_reg >> m_shift >> m_buffer >> m_flagP >> m_flagC >> m_flagS >> m_enableWramEnable + >> m_prgHijackedBit >> m_useHijacked >> m_useSramSwitch >> m_cpuCycles; +} + +void Mapper001::writeState(QDataStream &dataStream) const +{ + Board::writeState(dataStream); + dataStream << m_reg << m_shift << m_buffer << m_flagP << m_flagC << m_flagS << m_enableWramEnable + << m_prgHijackedBit << m_useHijacked << m_useSramSwitch << m_cpuCycles; +} + +int Mapper001::prgRam8KbDefaultBlkCount() const +{ + return 4; +} + +int Mapper001::chrRom1KbDefaultBlkCount() const +{ + return 64; +} + +void Mapper001::updateCHR() +{ + if (!m_flagC) + switch8kChr(m_reg[1] >> 1); + else + { + switch4kChr(m_reg[1], CHRArea::Area0000); + switch4kChr(m_reg[2], CHRArea::Area1000); + } + // SRAM + if (m_useSramSwitch) + switch8kPrg((m_reg[1] & m_sramSwitchMask) >> 3, PRGArea::Area6000); +} + +void Mapper001::updatePRG() +{ + if (!m_flagP) + { + switch32kPrg(((m_reg[3] & 0xF) | m_prgHijackedBit) >> 1, PRGArea::Area8000); + } + else + { + if (m_flagS) + { + switch16kPrg((m_reg[3] & 0xF) | m_prgHijackedBit, PRGArea::Area8000); + switch16kPrg(0xF | m_prgHijackedBit, PRGArea::AreaC000); + } + else + { + switch16kPrg(m_prgHijackedBit, PRGArea::Area8000); + switch16kPrg((m_reg[3] & 0xF) | m_prgHijackedBit, PRGArea::AreaC000); + } + } +} diff --git a/nescorelib/mappers/mapper001.h b/nescorelib/mappers/mapper001.h new file mode 100644 index 0000000..26ea083 --- /dev/null +++ b/nescorelib/mappers/mapper001.h @@ -0,0 +1,41 @@ +#pragma once + +#include "nescorelib_global.h" +#include "boards/board.h" + +class NESCORELIB_EXPORT Mapper001 : public Board +{ +public: + using Board::Board; + + QString name() const Q_DECL_OVERRIDE; + quint8 mapper() const Q_DECL_OVERRIDE; + + void hardReset() Q_DECL_OVERRIDE; + void writePrg(quint16 address, quint8 value) Q_DECL_OVERRIDE; + void onCpuClock() Q_DECL_OVERRIDE; + void readState(QDataStream &dataStream) Q_DECL_OVERRIDE; + void writeState(QDataStream &dataStream) const Q_DECL_OVERRIDE; + +protected: + int prgRam8KbDefaultBlkCount() const Q_DECL_OVERRIDE; + int chrRom1KbDefaultBlkCount() const Q_DECL_OVERRIDE; + +private: + void updateCHR(); + void updatePRG(); + + int m_addressReg; + std::array m_reg; + quint8 m_shift {}; + quint8 m_buffer {}; + bool m_flagP; + bool m_flagC; + bool m_flagS; + bool m_enableWramEnable; + int m_prgHijackedBit; + bool m_useHijacked; + bool m_useSramSwitch; + int m_sramSwitchMask; + int m_cpuCycles; +}; diff --git a/nescorelib/mappers/mapper002.cpp b/nescorelib/mappers/mapper002.cpp new file mode 100644 index 0000000..b46e2cd --- /dev/null +++ b/nescorelib/mappers/mapper002.cpp @@ -0,0 +1,23 @@ +#include "mapper002.h" + +QString Mapper002::name() const +{ + return QStringLiteral("UxROM"); +} + +quint8 Mapper002::mapper() const +{ + return 2; +} + +void Mapper002::hardReset() +{ + Board::hardReset(); + switch16kPrg(prgRom16KbMask(), PRGArea::AreaC000); +} + +void Mapper002::writePrg(quint16 address, quint8 value) +{ + Q_UNUSED(address) + switch16kPrg(value, PRGArea::Area8000); +} diff --git a/nescorelib/mappers/mapper002.h b/nescorelib/mappers/mapper002.h new file mode 100644 index 0000000..c387376 --- /dev/null +++ b/nescorelib/mappers/mapper002.h @@ -0,0 +1,16 @@ +#pragma once + +#include "nescorelib_global.h" +#include "boards/board.h" + +class NESCORELIB_EXPORT Mapper002 : public Board +{ +public: + using Board::Board; + + QString name() const Q_DECL_OVERRIDE; + quint8 mapper() const Q_DECL_OVERRIDE; + + void hardReset() Q_DECL_OVERRIDE; + void writePrg(quint16 address, quint8 value) Q_DECL_OVERRIDE; +}; diff --git a/nescorelib/mappers/mapper003.cpp b/nescorelib/mappers/mapper003.cpp new file mode 100644 index 0000000..e958a9a --- /dev/null +++ b/nescorelib/mappers/mapper003.cpp @@ -0,0 +1,17 @@ +#include "mapper003.h" + +QString Mapper003::name() const +{ + return QStringLiteral("CNROM"); +} + +quint8 Mapper003::mapper() const +{ + return 3; +} + +void Mapper003::writePrg(quint16 address, quint8 value) +{ + // Bus conflicts !! + switch8kChr(readPrg(address) & value); +} diff --git a/nescorelib/mappers/mapper003.h b/nescorelib/mappers/mapper003.h new file mode 100644 index 0000000..f16fcfe --- /dev/null +++ b/nescorelib/mappers/mapper003.h @@ -0,0 +1,15 @@ +#pragma once + +#include "nescorelib_global.h" +#include "boards/board.h" + +class NESCORELIB_EXPORT Mapper003 : public Board +{ +public: + using Board::Board; + + QString name() const Q_DECL_OVERRIDE; + quint8 mapper() const Q_DECL_OVERRIDE; + + void writePrg(quint16 address, quint8 value) Q_DECL_OVERRIDE; +}; diff --git a/nescorelib/mappers/mapper004.cpp b/nescorelib/mappers/mapper004.cpp new file mode 100644 index 0000000..4fafa83 --- /dev/null +++ b/nescorelib/mappers/mapper004.cpp @@ -0,0 +1,178 @@ +#include "mapper004.h" + +// local includes +#include "nesemulator.h" +#include "utils/datastreamutils.h" + +QString Mapper004::name() const +{ + return QStringLiteral("MMC3"); +} + +quint8 Mapper004::mapper() const +{ + return 4; +} + +void Mapper004::hardReset() +{ + Board::hardReset(); + + // Flags + m_flagC = false; + m_flagP = false; + m_address8001 = 0; + + m_prgReg[0] = 0; + m_prgReg[1] = 1; + m_prgReg[2] = prgRom8KbMask()-1; + m_prgReg[3] = prgRom8KbMask(); + + setupPRG(); + + // CHR + for (int i = 0; i < 6; i++) + m_chrReg[i] = 0; + + // IRQ + m_irqEnabled = false; + m_irqCounter = 0; + m_irqReload = 0xFF; + m_oldIrqCounter = 0; + m_mmc3AltBehavior = false; + m_irqClear = false; + +// switch (GameCartInfo.chip_type[0].ToLower()) +// { +// case "mmc3a": mmc3_alt_behavior = true; break; +// case "mmc3b": mmc3_alt_behavior = false; break; +// case "mmc3c": mmc3_alt_behavior = false; break; +// } +} + +void Mapper004::writePrg(quint16 address, quint8 value) +{ + switch (address & 0xE001) + { + case 0x8000: + m_address8001 = value & 0x7; + m_flagC = (value & 0x80) != 0; + m_flagP = (value & 0x40) != 0; + setupCHR(); + setupPRG(); break; + case 0x8001: + switch (m_address8001) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + m_chrReg[m_address8001] = value; setupCHR(); + break; + case 6: + case 7: + m_prgReg[m_address8001 - 6] = value & prgRom8KbMask(); + setupPRG(); + break; + } + break; + case 0xA000: + if (m_rom.mirroring != Mirroring::Full) + switch1kNmt((value & 1) == 1 ? Mirroring::Horizontal : Mirroring::Vertical); + break; + case 0xA001: + togglePrgRamEnable(value & 0x80); + togglePrgRamWritableEnable((value & 0x40) == 0); + break; + case 0xC000: + m_irqReload = value; + break; + case 0xC001: + if (m_mmc3AltBehavior) + m_irqClear = true; + m_irqCounter = 0; + break; + case 0xE000: + m_irqEnabled = false; + m_emu.interrupts().removeFlag(Interrupts::IRQ_BOARD); + break; + case 0xE001: + m_irqEnabled = true; + break; + } +} + +void Mapper004::onPpuA12RaisingEdge() +{ + m_oldIrqCounter = m_irqCounter; + + if (m_irqCounter == 0 || m_irqClear) + m_irqCounter = m_irqReload; + else + m_irqCounter = m_irqCounter - 1; + + if ((!m_mmc3AltBehavior || m_oldIrqCounter != 0 || m_irqClear) && m_irqCounter == 0 && m_irqEnabled) + m_emu.interrupts().addFlag(Interrupts::IRQ_BOARD); + + m_irqClear = false; +} + +void Mapper004::readState(QDataStream &dataStream) +{ + Board::readState(dataStream); + + dataStream >> m_flagC >> m_flagP >> m_address8001 >> m_chrReg >> m_prgReg + // IRQ + >> m_irqEnabled >> m_irqCounter >> m_oldIrqCounter >> m_irqReload >> m_irqClear >> m_mmc3AltBehavior; +} + +void Mapper004::writeState(QDataStream &dataStream) const +{ + Board::writeState(dataStream); + + dataStream << m_flagC << m_flagP << m_address8001 << m_chrReg << m_prgReg + // IRQ + << m_irqEnabled << m_irqCounter << m_oldIrqCounter << m_irqReload << m_irqClear << m_mmc3AltBehavior; +} + +bool Mapper004::ppuA12ToggleTimerEnabled() const +{ + return true; +} + +bool Mapper004::ppuA12TogglesOnRaisingEdge() const +{ + return true; +} + +void Mapper004::setupCHR() +{ + if (!m_flagC) + { + switch2kChr(m_chrReg[0] >> 1, CHRArea::Area0000); + switch2kChr(m_chrReg[1] >> 1, CHRArea::Area0800); + switch1kChr(m_chrReg[2], CHRArea::Area1000); + switch1kChr(m_chrReg[3], CHRArea::Area1400); + switch1kChr(m_chrReg[4], CHRArea::Area1800); + switch1kChr(m_chrReg[5], CHRArea::Area1C00); + } + else + { + switch2kChr(m_chrReg[0] >> 1, CHRArea::Area1000); + switch2kChr(m_chrReg[1] >> 1, CHRArea::Area1800); + switch1kChr(m_chrReg[2], CHRArea::Area0000); + switch1kChr(m_chrReg[3], CHRArea::Area0400); + switch1kChr(m_chrReg[4], CHRArea::Area0800); + switch1kChr(m_chrReg[5], CHRArea::Area0C00); + } +} + +void Mapper004::setupPRG() +{ + switch8kPrg(m_prgReg[m_flagP ? 2 : 0], PRGArea::Area8000); + switch8kPrg(m_prgReg[1], PRGArea::AreaA000); + switch8kPrg(m_prgReg[m_flagP ? 0 : 2], PRGArea::AreaC000); + switch8kPrg(m_prgReg[3], PRGArea::AreaE000); +} diff --git a/nescorelib/mappers/mapper004.h b/nescorelib/mappers/mapper004.h new file mode 100644 index 0000000..0473c4d --- /dev/null +++ b/nescorelib/mappers/mapper004.h @@ -0,0 +1,44 @@ +#pragma once + +#include "nescorelib_global.h" +#include "boards/board.h" + +class NESCORELIB_EXPORT Mapper004 : public Board +{ +public: + using Board::Board; + + QString name() const Q_DECL_OVERRIDE; + quint8 mapper() const Q_DECL_OVERRIDE; + + void hardReset() Q_DECL_OVERRIDE; + + void writePrg(quint16 address, quint8 value) Q_DECL_OVERRIDE; + + void onPpuA12RaisingEdge() Q_DECL_OVERRIDE; + + void readState(QDataStream &dataStream) Q_DECL_OVERRIDE; + void writeState(QDataStream &dataStream) const Q_DECL_OVERRIDE; + +protected: + bool ppuA12ToggleTimerEnabled() const Q_DECL_OVERRIDE; + bool ppuA12TogglesOnRaisingEdge() const Q_DECL_OVERRIDE; + +private: + void setupCHR(); + void setupPRG(); + + bool m_flagC; + bool m_flagP; + int m_address8001; + std::array m_chrReg; + std::array m_prgReg; + + // IRQ + bool m_irqEnabled; + quint8 m_irqCounter; + int m_oldIrqCounter; + quint8 m_irqReload; + bool m_irqClear; + bool m_mmc3AltBehavior; +}; diff --git a/nescorelib/nescorelib_global.h b/nescorelib/nescorelib_global.h new file mode 100644 index 0000000..2580a52 --- /dev/null +++ b/nescorelib/nescorelib_global.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#if defined(NESCORELIB_LIBRARY) +# define NESCORELIB_EXPORT Q_DECL_EXPORT +#else +# define NESCORELIB_EXPORT Q_DECL_IMPORT +#endif diff --git a/nescorelib/nesemulator.cpp b/nescorelib/nesemulator.cpp new file mode 100644 index 0000000..3814f95 --- /dev/null +++ b/nescorelib/nesemulator.cpp @@ -0,0 +1,183 @@ +#include "nesemulator.h" + +// system includes +#include +#include + +// local includes +#include "emusettings.h" + +NesEmulator::NesEmulator() : + m_apu(*this), + m_cpu(*this), + m_dma(*this), + m_interrupts(*this), + m_memory(*this), + m_ports(*this), + m_ppu(*this) +{ + QObject::connect(&m_ppu, &Ppu::frameFinished, this, &NesEmulator::frameFinished); +} + +void NesEmulator::load(const Rom &rom) +{ + m_memory.initialize(rom); + + hardReset(); + + if(m_memory.board()->enableExternalSound()) + m_memory.board()->apuApplyChannelsSettings(); +} + +void NesEmulator::hardReset() +{ + m_memory.hardReset(); + m_cpu.hardReset(); + m_ppu.hardReset(); + m_apu.hardReset(); + m_dma.hardReset(); +} + +void NesEmulator::softReset() +{ + m_cpu.softReset(); + m_apu.softReset(); +} + +void NesEmulator::emuClockFrame() +{ + m_frameFinished = false; + while(!m_frameFinished) + m_cpu.clock(); + + /* + m_emuTimeFrame = QDateTime::currentMSecsSinceEpoch() - m_emuTimePrevious; + + if (EmuSettings::frameLimiterEnabled) + { + const auto emuTimeDead = EmuSettings::emuTimeFramePeriod - m_emuTimeFrame; + if (emuTimeDead > 0) + { + // 1 Sleep thread + QThread::msleep(std::floor(emuTimeDead * 1000)); + + // 2 Kill remaining time + m_emuTimeFrameImmediate = QDateTime::currentMSecsSinceEpoch() - m_emuTimePrevious; + while (EmuSettings::emuTimeFramePeriod - m_emuTimeFrameImmediate > 0) + m_emuTimeFrameImmediate = QDateTime::currentMSecsSinceEpoch() - m_emuTimePrevious; + } + } + else + m_emuTimeFrameImmediate = QDateTime::currentMSecsSinceEpoch() - m_emuTimePrevious; + m_emuTimePrevious = QDateTime::currentMSecsSinceEpoch(); + */ +} + +void NesEmulator::emuClockComponents() +{ + m_ppu.clock(); + m_interrupts.pollStatus(); + m_ppu.clock(); + m_ppu.clock(); + m_apu.clock(); + m_dma.clock(); + m_memory.board()->onCpuClock(); +} + +void NesEmulator::writeState(QDataStream &dataStream) const +{ + m_apu.writeState(dataStream); + m_cpu.writeState(dataStream); + m_dma.writeState(dataStream); + m_interrupts.writeState(dataStream); + m_memory.writeState(dataStream); + m_ports.portWriteState(dataStream); + m_ppu.writeState(dataStream); +} + +void NesEmulator::readState(QDataStream &dataStream) +{ + m_apu.readState(dataStream); + m_cpu.readState(dataStream); + m_dma.readState(dataStream); + m_interrupts.readState(dataStream); + m_memory.readState(dataStream); + m_ports.portReadState(dataStream); + m_ppu.readState(dataStream); +} + +Apu &NesEmulator::apu() +{ + return m_apu; +} + +const Apu &NesEmulator::apu() const +{ + return m_apu; +} + +Cpu &NesEmulator::cpu() +{ + return m_cpu; +} + +const Cpu &NesEmulator::cpu() const +{ + return m_cpu; +} + +Dma &NesEmulator::dma() +{ + return m_dma; +} + +const Dma &NesEmulator::dma() const +{ + return m_dma; +} + +Interrupts &NesEmulator::interrupts() +{ + return m_interrupts; +} + +const Interrupts &NesEmulator::interrupts() const +{ + return m_interrupts; +} + +Memory &NesEmulator::memory() +{ + return m_memory; +} + +const Memory &NesEmulator::memory() const +{ + return m_memory; +} + +Ports &NesEmulator::ports() +{ + return m_ports; +} + +const Ports &NesEmulator::ports() const +{ + return m_ports; +} + +Ppu &NesEmulator::ppu() +{ + return m_ppu; +} + +const Ppu &NesEmulator::ppu() const +{ + return m_ppu; +} + +void NesEmulator::frameFinished() +{ + m_apu.setTimer(0.); + m_frameFinished = true; +} diff --git a/nescorelib/nesemulator.h b/nescorelib/nesemulator.h new file mode 100644 index 0000000..3ed58bb --- /dev/null +++ b/nescorelib/nesemulator.h @@ -0,0 +1,70 @@ +#pragma once + +#include "nescorelib_global.h" +#include + +// system includes +#include +#include + +// local includes +#include "emu/apu.h" +#include "emu/cpu.h" +#include "emu/dma.h" +#include "emu/interrupts.h" +#include "emu/memory.h" +#include "emu/ports.h" +#include "emu/ppu.h" + +// forward declarations +class QDataStream; + +struct Rom; + +class NESCORELIB_EXPORT NesEmulator : public QObject +{ + Q_OBJECT + +public: + explicit NesEmulator(); + + void load(const Rom &rom); + + void hardReset(); + void softReset(); + + void emuClockFrame(); + void emuClockComponents(); + + void writeState(QDataStream &dataStream) const; + void readState(QDataStream &dataStream); + + Apu &apu(); + const Apu &apu() const; + Cpu &cpu(); + const Cpu &cpu() const; + Dma &dma(); + const Dma &dma() const; + Interrupts &interrupts(); + const Interrupts &interrupts() const; + Memory &memory(); + const Memory &memory() const; + Ports &ports(); + const Ports &ports() const; + Ppu &ppu(); + const Ppu &ppu() const; + +private Q_SLOTS: + void frameFinished(); + +private: + Apu m_apu; + Cpu m_cpu; + Dma m_dma; + Interrupts m_interrupts; + Memory m_memory; + Ports m_ports; + Ppu m_ppu; + + bool m_frameFinished; +}; diff --git a/nescorelib/rom.cpp b/nescorelib/rom.cpp new file mode 100644 index 0000000..d27838b --- /dev/null +++ b/nescorelib/rom.cpp @@ -0,0 +1,69 @@ +#include "rom.h" + +// Qt includes +#include +#include + +// system includes +#include +#include + +Rom Rom::fromFile(const QString &path) +{ + QFile file(path); + if(!file.open(QIODevice::ReadOnly)) + throw std::runtime_error(QString("cannot open file %0 because %1").arg(file.fileName(), file.errorString()).toStdString()); + + std::array header {}; + + if(file.read(reinterpret_cast(&header[0]), 16) != 16) + throw std::runtime_error("rom is not long enough"); + + if(header[0] != 'N' || header[1] != 'E' || header[2] != 'S' || header[3] != 0x1A) + throw std::runtime_error("wrong header"); + + Rom rom; + rom.prgCount = header[4]; + rom.chrCount = header[5]; + rom.mirroring = Mirroring(0); + switch (header[6] & 0x09) + { + case 0: rom.mirroring = Mirroring::Horizontal; break; + case 1: rom.mirroring = Mirroring::Vertical; break; + case 8: + case 9: rom.mirroring = Mirroring::Full; break; + } + rom.hasBattery = header[6] & 0x0F; + rom.hasTrainer = header[6] & 0x04; + + auto temp0 = header[6] >> 4; + auto temp1 = header[7] & 0x0F; + auto temp2 = header[7] & 0xF0; + + rom.mapperNumber = temp0; + if(temp1 == 0) + rom.mapperNumber |= temp2; + + rom.isVsUnisystem = header[7] & 0x01; + rom.isPlaychoice10 = header[7] & 0x02; + if(rom.hasTrainer) + { + if(file.read(reinterpret_cast(&rom.trainer[0]), 512) != 512) + throw std::runtime_error("rom is not long enough fortrainer"); + } + + rom.prg.resize(rom.prgCount * 4); + + for(int i = 0; i < rom.prg.size(); i++) + { + if(file.read(reinterpret_cast(&rom.prg[i][0]), 0x1000) != 0x1000) + throw std::runtime_error("rom is not long enough forprg"); + } + + rom.chr.resize(rom.chrCount * 8); + for(int i = 0; i < rom.chr.size(); i++) + if(file.read(reinterpret_cast(&rom.chr[i][0]), 0x400) != 0x400) + throw std::runtime_error("rom is not long enough forchr"); + + return rom; +} diff --git a/nescorelib/rom.h b/nescorelib/rom.h new file mode 100644 index 0000000..9c8bc19 --- /dev/null +++ b/nescorelib/rom.h @@ -0,0 +1,32 @@ +#pragma once + +#include "nescorelib_global.h" + +// Qt includes +#include +#include + +// system includes +#include +#include + +// local includes +#include "enums/mirroring.h" + +struct NESCORELIB_EXPORT Rom +{ + int prgCount; + int chrCount; + Mirroring mirroring; + bool hasBattery; + bool hasTrainer; + int mapperNumber; + bool isVsUnisystem; + bool isPlaychoice10; + + QVector > prg; + QVector > chr; + std::array trainer; + + static Rom fromFile(const QString &path); +}; diff --git a/nescorelib/soundhighpassfilter.cpp b/nescorelib/soundhighpassfilter.cpp new file mode 100644 index 0000000..8b929f4 --- /dev/null +++ b/nescorelib/soundhighpassfilter.cpp @@ -0,0 +1,21 @@ +#include "soundhighpassfilter.h" + +SoundHighPassFilter::SoundHighPassFilter(const double k) : + m_k(k) +{ + reset(); +} + +void SoundHighPassFilter::reset() +{ + m_x = 0.; + m_y = 0.; +} + +double SoundHighPassFilter::doFiltering(const double sample) +{ + const auto filtered = (m_y * m_k) + (sample - m_x); + m_x = sample; + m_y = filtered; + return filtered; +} diff --git a/nescorelib/soundhighpassfilter.h b/nescorelib/soundhighpassfilter.h new file mode 100644 index 0000000..9e3547b --- /dev/null +++ b/nescorelib/soundhighpassfilter.h @@ -0,0 +1,17 @@ +#pragma once + +#include "nescorelib_global.h" + +class NESCORELIB_EXPORT SoundHighPassFilter +{ +public: + SoundHighPassFilter(const double k); + + void reset(); + double doFiltering(const double sample); + +private: + const double m_k; + double m_x; + double m_y; +}; diff --git a/nescorelib/soundlowpassfilter.cpp b/nescorelib/soundlowpassfilter.cpp new file mode 100644 index 0000000..007c1ea --- /dev/null +++ b/nescorelib/soundlowpassfilter.cpp @@ -0,0 +1,21 @@ +#include "soundlowpassfilter.h" + +SoundLowPassFilter::SoundLowPassFilter(const double k) : + m_k(k) +{ + reset(); +} + +void SoundLowPassFilter::reset() +{ + m_x = 0.; + m_y = 0.; +} + +double SoundLowPassFilter::doFiltering(const double sample) +{ + const auto filtered = (sample - m_y) * m_k; + m_x = sample; + m_y = filtered; + return filtered; +} diff --git a/nescorelib/soundlowpassfilter.h b/nescorelib/soundlowpassfilter.h new file mode 100644 index 0000000..df765b2 --- /dev/null +++ b/nescorelib/soundlowpassfilter.h @@ -0,0 +1,17 @@ +#pragma once + +#include "nescorelib_global.h" + +class NESCORELIB_EXPORT SoundLowPassFilter +{ +public: + SoundLowPassFilter(const double k); + + void reset(); + double doFiltering(const double sample); + +private: + const double m_k; + double m_y; + double m_x; +}; diff --git a/nesemu/CMakeLists.txt b/nesemu/CMakeLists.txt new file mode 100644 index 0000000..cb71abb --- /dev/null +++ b/nesemu/CMakeLists.txt @@ -0,0 +1,16 @@ +find_package(Qt5Core CONFIG REQUIRED) +find_package(Qt5Gui CONFIG REQUIRED) +find_package(Qt5Widgets CONFIG REQUIRED) +find_package(Qt5Multimedia CONFIG REQUIRED) +find_package(Qt5Gamepad CONFIG REQUIRED) + +set(HEADERS +) + +set(SOURCES + main.cpp +) + +add_executable(nesemu ${HEADERS} ${SOURCES}) + +target_link_libraries(nesemu Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Gamepad Qt5::Multimedia dbcorelib dbguilib nescorelib nesguilib) diff --git a/nesemu/main.cpp b/nesemu/main.cpp new file mode 100644 index 0000000..426cbf1 --- /dev/null +++ b/nesemu/main.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// dbcorelib includes +#include "waverecorder.h" +#include "fifostream.h" +#include "utils/datastreamutils.h" + +// dbguilib includes +#include "canvaswidget.h" + +// nescorelib includes +#include "nesemulator.h" +#include "emusettings.h" + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + NesEmulator emulator; + + const QString path = app.arguments().count() > 1 ? app.arguments().at(1) : QFileDialog::getOpenFileName(nullptr, "Select ROM file...", QString(), "ROM file (*.nes)"); + + if(path.isEmpty()) + return 0; + + Rom rom; + + try { + rom = Rom::fromFile(path); + emulator.load(rom); + } catch (const std::exception &e) { + QMessageBox::warning(nullptr, "Error while loading rom!", QString::fromStdString(e.what())); + return 1; + } + + // Audio recorder + WaveRecorder recorder(1, emulator.apu().sampleRate(), "sound.wav"); + QObject::connect(&emulator.apu(), &Apu::sampleFinished, &recorder, &WaveRecorder::addSample); + + // Live audio playback + FifoStream stream; + QObject::connect(&emulator.apu(), &Apu::sampleFinished, [&stream](qint32 sample){ + QByteArray buf; + buf.reserve(sizeof(qint32)); + + QDataStream dataStream(&buf, QIODevice::WriteOnly); + dataStream.setByteOrder(QDataStream::BigEndian); + + dataStream << sample; + + Q_ASSERT(buf.size() == sizeof(qint32)); + + stream.write(buf); + }); + + // Display + CanvasWidget canvas; + canvas.setWindowTitle(QString("%0 - Mapper: %1").arg(QFileInfo(path).fileName()).arg(rom.mapperNumber)); + canvas.show(); + quint64 frameCounter {}; + QObject::connect(&emulator.ppu(), &Ppu::frameFinished, [&canvas, &frameCounter](const std::array &frame){ + canvas.setImage(QImage(reinterpret_cast(&frame[0]), Ppu::SCREEN_WIDTH, Ppu::SCREEN_HEIGHT, QImage::Format_RGB32)); + + QFile file(QString("frames/%0.bmp").arg(frameCounter++)); + if(!file.open(QIODevice::WriteOnly)) + return; + + constexpr quint32 bitmapSize = Ppu::SCREEN_WIDTH * Ppu::SCREEN_HEIGHT * sizeof(quint32); + + QDataStream dataStream(&file); + dataStream.setByteOrder(QDataStream::LittleEndian); + + //BMP Header + dataStream << quint16(0x4D42); //BM-Header + dataStream << quint32(54 + bitmapSize); //File size + dataStream << quint32(0); //Unused + dataStream << quint32(54); //Offset to bitmap data + + //DIB Header + dataStream << quint32(40); //DIP Header size + dataStream << Ppu::SCREEN_WIDTH; //width + dataStream << Ppu::SCREEN_HEIGHT; //height + dataStream << quint16(1); //Number of color planes + dataStream << quint16(32); //Bits per pixel + dataStream << quint32(0); //No compression; + dataStream << bitmapSize; //Size of bitmap data + dataStream << quint32(2835); //Horizontal print resolution + dataStream << quint32(2835); //Horizontal print resolution + dataStream << quint32(0); //Number of colors in palette + dataStream << quint32(0); //Important colors + + dataStream << frame; + }); + + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, &emulator, &NesEmulator::emuClockFrame); + timer.setInterval(EmuSettings::emuTimeFramePeriod); + + // HACK: overclocking a little bit when audio device reaches near end + QObject::connect(&emulator.ppu(), &Ppu::frameFinished, [&stream, &timer](){ + int speed = EmuSettings::emuTimeFramePeriod; + if(stream.bytesAvailable() / sizeof(qint32) < 4096) + speed = double(speed) * 0.9; + + if(timer.interval() != speed) + timer.setInterval(speed); + }); + + QAudioFormat format; + format.setSampleRate(emulator.apu().sampleRate()); + format.setChannelCount(1); + format.setSampleSize(sizeof(qint32) * 8); + format.setCodec("audio/pcm"); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setSampleType(QAudioFormat::SignedInt); + + { + QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); + if (!info.isFormatSupported(format)) + { + qFatal("Your soundcard does not support the needed output format!"); + return -1; + } + } + + QAudioOutput output(format); + output.start(&stream); + timer.start(); + + return app.exec(); +} diff --git a/nesguilib/CMakeLists.txt b/nesguilib/CMakeLists.txt new file mode 100644 index 0000000..547f2c0 --- /dev/null +++ b/nesguilib/CMakeLists.txt @@ -0,0 +1,21 @@ +find_package(Qt5Core CONFIG REQUIRED) +find_package(Qt5Gui CONFIG REQUIRED) +find_package(Qt5Widgets CONFIG REQUIRED) +find_package(Qt5Gamepad CONFIG REQUIRED) + +set(HEADERS + gamepadinput.h + nesguilib_global.h +) + +set(SOURCES + gamepadinput.cpp +) + +add_library(nesguilib ${HEADERS} ${SOURCES}) + +target_compile_definitions(nesguilib PRIVATE NESGUILIB_LIBRARY) + +target_link_libraries(nesguilib Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Gamepad nescorelib) + +target_include_directories(nesguilib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/nesguilib/gamepadinput.cpp b/nesguilib/gamepadinput.cpp new file mode 100644 index 0000000..46e856e --- /dev/null +++ b/nesguilib/gamepadinput.cpp @@ -0,0 +1,24 @@ +#include "gamepadinput.h" + +GamepadInput::GamepadInput(int deviceId) : + m_gamepad(deviceId) +{ +} + +void GamepadInput::update() +{ + m_data = 0; + if(m_gamepad.buttonA()) m_data += 1; + if(m_gamepad.buttonX()) m_data += 2; + if(m_gamepad.buttonSelect()) m_data += 4; + if(m_gamepad.buttonStart()) m_data += 8; + if(m_gamepad.buttonUp()) m_data += 16; + if(m_gamepad.buttonDown()) m_data += 32; + if(m_gamepad.buttonLeft()) m_data += 64; + if(m_gamepad.buttonRight()) m_data += 128; +} + +quint8 GamepadInput::getData() const +{ + return m_data; +} diff --git a/nesguilib/gamepadinput.h b/nesguilib/gamepadinput.h new file mode 100644 index 0000000..732e2ba --- /dev/null +++ b/nesguilib/gamepadinput.h @@ -0,0 +1,20 @@ +#pragma once + +#include "nesguilib_global.h" +#include "inputprovider.h" + +// Qt includes +#include + +class NESGUILIB_EXPORT GamepadInput : public InputProvider +{ +public: + explicit GamepadInput(int deviceId = 0); + + void update(); + quint8 getData() const; + +private: + QGamepad m_gamepad; + quint8 m_data {}; +}; diff --git a/nesguilib/nesguilib_global.h b/nesguilib/nesguilib_global.h new file mode 100644 index 0000000..5291c74 --- /dev/null +++ b/nesguilib/nesguilib_global.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#if defined(NESGUILIB_LIBRARY) +# define NESGUILIB_EXPORT Q_DECL_EXPORT +#else +# define NESGUILIB_EXPORT Q_DECL_IMPORT +#endif