Imported existing sources

This commit is contained in:
Daniel Brunner
2018-12-16 22:19:06 +01:00
parent b848581174
commit 619ec483e7
69 changed files with 7961 additions and 36 deletions

102
.gitignore vendored
View File

@@ -1,43 +1,73 @@
# C++ objects and libs # This file is used to ignore files which are generated
*.slo # ----------------------------------------------------------------------------
*.lo
*.o
*.a
*.la
*.lai
*.so
*.dll
*.dylib
# Qt-es *~
object_script.*.Release *.autosave
object_script.*.Debug *.a
*_plugin_import.cpp *.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.cache
/.qmake.stash /.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 # qtcreator generated files
target_wrapper.* *.pro.user*
# QtCreator # xemacs temporary files
*.autosave *.flc
# QtCreator Qml # Vim temporary files
*.qmlproject.user .*.swp
*.qmlproject.user.*
# 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*

5
CMakeLists.txt Normal file
View File

@@ -0,0 +1,5 @@
project(DbNeuralNet)
add_subdirectory(nesemu)
add_subdirectory(nescorelib)
add_subdirectory(nesguilib)

75
nescorelib/CMakeLists.txt Normal file
View File

@@ -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})

View File

@@ -0,0 +1,10 @@
#include "bandai.h"
Bandai::Bandai(NesEmulator &emu, const Rom &rom) :
Board(emu, rom)
{
}
Bandai::~Bandai()
{
}

View File

@@ -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();
};

582
nescorelib/boards/board.cpp Normal file
View File

@@ -0,0 +1,582 @@
#include "board.h"
// system includes
#include <algorithm>
// 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;
}

174
nescorelib/boards/board.h Normal file
View File

@@ -0,0 +1,174 @@
#pragma once
#include "nescorelib_global.h"
// Qt includes
#include <QtGlobal>
#include <QString>
#include <QVector>
// system includes
#include <array>
// 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<std::size_t L>
struct RamPage {
std::array<quint8, L> 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<quint8, 0x400> ram {};
int index {}; // The index of NMT RAM block in the area
};
QVector<RamPage<0x1000> > m_prgRam {};
std::array<AreaBlk, 16> m_prgAreaBlk {}; // Starting from 4xxx to Fxxx, each entry here configure a 8kb block area
QVector<RamPage<0x400> > m_chrRam {};
std::array<AreaBlk, 8> m_chrAreaBlk {}; // Starting from 0000 to F3FF, each entry here configure a 4kb block area
std::array<NmtRam, 4> m_nmtRam {};
int m_oldVramAddress {};
int m_newVramAddress {};
int m_ppuCyclesTimer {};
protected:
NesEmulator &m_emu;
const Rom m_rom;
private:
bool m_sramSaveRequired {};
};

59
nescorelib/boards/ffe.cpp Normal file
View File

@@ -0,0 +1,59 @@
#include "ffe.h"
// Qt includes
#include <QDataStream>
// 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;
}

20
nescorelib/boards/ffe.h Normal file
View File

@@ -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;
};

View File

@@ -0,0 +1,10 @@
#include "mmc2.h"
Mmc2::Mmc2(NesEmulator &emu, const Rom &rom) :
Board(emu, rom)
{
}
Mmc2::~Mmc2()
{
}

11
nescorelib/boards/mmc2.h Normal file
View File

@@ -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();
};

View File

@@ -0,0 +1,10 @@
#include "namcot106.h"
Namcot106::Namcot106(NesEmulator &emu, const Rom &rom) :
Board(emu, rom)
{
}
Namcot106::~Namcot106()
{
}

View File

@@ -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();
};

622
nescorelib/emu/apu.cpp Normal file
View File

@@ -0,0 +1,622 @@
#include "apu.h"
// Qt includes
#include <QDataStream>
// system includes
#include <algorithm>
// 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<void (Apu::*)(), 0x20> apuRegReadFunc = []() {
std::array<void (Apu::*)(), 0x20> 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<qreal, 32> audioPulseTable = []() constexpr {
std::array<qreal, 32> audioPulseTable {};
for(std::size_t i = 1; i < 32; i++)
audioPulseTable[i] = 95.52 / (8128. / i + 100.);
return audioPulseTable;
}();
static constexpr std::array<qreal, 204> audioTndTable = []() constexpr {
std::array<qreal, 204> 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 <hotpop.com@blargg> (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<std::array<quint8, 8>, 4> Apu::m_sqDutyCycleSequences {
std::array<quint8, 8> { 0, 1, 0, 0, 0, 0, 0, 0 }, // 12.5%
std::array<quint8, 8> { 0, 1, 1, 0, 0, 0, 0, 0 }, // 25.0%
std::array<quint8, 8> { 0, 1, 1, 1, 1, 0, 0, 0 }, // 50.0%
std::array<quint8, 8> { 1, 0, 0, 1, 1, 1, 1, 1 }, // 75.0% (25.0% negated)
};
const std::array<quint8, 32> 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,
};

149
nescorelib/emu/apu.h Normal file
View File

@@ -0,0 +1,149 @@
#pragma once
#include "nescorelib_global.h"
#include <QObject>
// Qt includes
#include <QtGlobal>
#include <QVector>
// system includes
#include <array>
// 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<std::array<quint8, 8>, 4> m_sqDutyCycleSequences;
static const std::array<quint8, 32> 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 };
};

217
nescorelib/emu/apudmc.cpp Normal file
View File

@@ -0,0 +1,217 @@
#include "apudmc.h"
// Qt includes
#include <QDataStream>
// 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<qint32, 32> freqTable = [](){
switch(EmuSettings::region)
{
case EmuRegion::NTSC:
return std::array<qint32, 32> {
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54
};
case EmuRegion::PALB:
return std::array<qint32, 32> {
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50
};
case EmuRegion::DENDY:
return std::array<qint32, 32> {
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;
}

55
nescorelib/emu/apudmc.h Normal file
View File

@@ -0,0 +1,55 @@
#pragma once
#include "nescorelib_global.h"
// Qt includes
#include <QtGlobal>
// 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 {};
};

220
nescorelib/emu/apunos.cpp Normal file
View File

@@ -0,0 +1,220 @@
#include "apunos.h"
// Qt includes
#include <QDataStream>
// 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<qint32, 16> freqTable = [](){
switch(EmuSettings::region)
{
case EmuRegion::NTSC:
return std::array<qint32, 16> {
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068
};
case EmuRegion::PALB:
return std::array<qint32, 16> {
4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778
};
case EmuRegion::DENDY:
return std::array<qint32, 16> {
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;
}

61
nescorelib/emu/apunos.h Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include "nescorelib_global.h"
// Qt includes
#include <QtGlobal>
// 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 {};
};

219
nescorelib/emu/apusq1.cpp Normal file
View File

@@ -0,0 +1,219 @@
#include "apusq1.h"
// Qt includes
#include <QDataStream>
// 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;
}

70
nescorelib/emu/apusq1.h Normal file
View File

@@ -0,0 +1,70 @@
#pragma once
#include "nescorelib_global.h"
// Qt includes
#include <QtGlobal>
// 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 {};
};

216
nescorelib/emu/apusq2.cpp Normal file
View File

@@ -0,0 +1,216 @@
#include "apusq2.h"
// Qt includes
#include <QDataStream>
// 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;
}

70
nescorelib/emu/apusq2.h Normal file
View File

@@ -0,0 +1,70 @@
#pragma once
#include "nescorelib_global.h"
// Qt includes
#include <QtGlobal>
// 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 {};
};

174
nescorelib/emu/aputrl.cpp Normal file
View File

@@ -0,0 +1,174 @@
#include "aputrl.h"
// Qt includes
#include <QDataStream>
// 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<quint8, 32> 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;
}

54
nescorelib/emu/aputrl.h Normal file
View File

@@ -0,0 +1,54 @@
#pragma once
#include "nescorelib_global.h"
// Qt includes
#include <QtGlobal>
// 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 {};
};

1268
nescorelib/emu/cpu.cpp Normal file

File diff suppressed because it is too large Load Diff

114
nescorelib/emu/cpu.h Normal file
View File

@@ -0,0 +1,114 @@
#pragma once
#include "nescorelib_global.h"
// Qt includes
#include <QtGlobal>
// 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 {};
};

199
nescorelib/emu/dma.cpp Normal file
View File

@@ -0,0 +1,199 @@
#include "dma.h"
// Qt includes
#include <QDataStream>
// 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;
}

44
nescorelib/emu/dma.h Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
#include "nescorelib_global.h"
// Qt includes
#include <QtGlobal>
// 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 {};
};

View File

@@ -0,0 +1,69 @@
#include "interrupts.h"
// Qt includes
#include <QDataStream>
// 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;
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include "nescorelib_global.h"
// Qt includes
#include <QtGlobal>
// 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 {};
};

234
nescorelib/emu/memory.cpp Normal file
View File

@@ -0,0 +1,234 @@
#include "memory.h"
// Qt includes
#include <QDataStream>
// 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<Board> Memory::getBoard(const Rom &rom)
{
switch(rom.mapperNumber)
{
case 0: return std::make_unique<Mapper000>(m_emu, rom);
case 1: return std::make_unique<Mapper001>(m_emu, rom);
case 2: return std::make_unique<Mapper002>(m_emu, rom);
case 3: return std::make_unique<Mapper003>(m_emu, rom);
case 4: return std::make_unique<Mapper004>(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<quint8, 0x0800> &Memory::wram() const
{
return m_wram;
}

68
nescorelib/emu/memory.h Normal file
View File

@@ -0,0 +1,68 @@
#pragma once
#include "nescorelib_global.h"
// Qt includes
#include <QtGlobal>
// system includes
#include <memory>
// 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<Board> 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<quint8, 0x0800> &wram() const;
private:
NesEmulator &m_emu;
std::array<quint8, 0x0800> m_wram {};
std::unique_ptr<Board> m_board {};
bool m_busRw {};
quint16 m_busAddress {};
};

53
nescorelib/emu/ports.cpp Normal file
View File

@@ -0,0 +1,53 @@
#include "ports.h"
// Qt includes
#include <QDataStream>
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;
}

42
nescorelib/emu/ports.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include "nescorelib_global.h"
// Qt includes
#include <QtGlobal>
// system includes
#include <memory>
// 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<std::unique_ptr<InputProvider>, 4> m_inputs {};
quint32 m_port0 {};
quint32 m_port1 {};
};

983
nescorelib/emu/ppu.cpp Normal file
View File

@@ -0,0 +1,983 @@
#include "ppu.h"
// Qt includes
#include <QDataStream>
// 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<void (Ppu::*)(), 320> ppuVClocks = []() {
std::array<void (Ppu::*)(), 320> 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<void (Ppu::*)(), 8> 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<void (Ppu::*)(), 8> ppuBkgFetches {
&Ppu::bkgFetch0, &Ppu::bkgFetch1, &Ppu::bkgFetch2, &Ppu::bkgFetch3,
&Ppu::bkgFetch4, &Ppu::bkgFetch5, &Ppu::bkgFetch6, &Ppu::bkgFetch7
};
static constexpr std::array<void (Ppu::*)(), 8> ppuSprFetches {
&Ppu::bkgFetch0, &Ppu::bkgFetch1, &Ppu::bkgFetch2, &Ppu::bkgFetch3,
&Ppu::sprFetch0, &Ppu::sprFetch1, &Ppu::sprFetch2, &Ppu::sprFetch3
};
static constexpr std::array<void (Ppu::*)(), 9> 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<void (Ppu::*)(), 8> 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<qint32, Ppu::SCREEN_WIDTH*Ppu::SCREEN_HEIGHT> &Ppu::screenPixels() const
{
return m_ppuScreenPixels;
}

187
nescorelib/emu/ppu.h Normal file
View File

@@ -0,0 +1,187 @@
#pragma once
#include "nescorelib_global.h"
#include <QObject>
// Qt includes
#include <QtGlobal>
// system includes
#include <array>
// 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<qint32, SCREEN_WIDTH*SCREEN_HEIGHT> &screenPixels() const;
Q_SIGNALS:
void frameFinished(const std::array<qint32, SCREEN_WIDTH*SCREEN_HEIGHT> &frame);
private:
NesEmulator &m_emu;
std::array<quint8, 512> m_ppuBkgPixels {};
std::array<qint32, SCREEN_WIDTH> m_ppuSprPixels {};
std::array<qint32, SCREEN_WIDTH*SCREEN_HEIGHT> m_ppuScreenPixels {};
// Clocks
qint32 m_ppuClockH {};
quint16 m_ppuClockV {};
bool m_ppuUseOddSwap {};
bool m_ppuIsNmiTime {};
// Memory
std::array<quint8, SCREEN_WIDTH> m_ppuOamBank {};
std::array<quint8, 32> m_ppuOamBankSecondary {};
std::array<quint8, 32> 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 {};
};

158
nescorelib/emusettings.h Normal file
View File

@@ -0,0 +1,158 @@
#pragma once
#include <QtGlobal>
#include <array>
#include <cmath>
#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<float, 8> 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<qint32, 512> palette = [](){
std::array<qint32, 512> 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;
}
}
}

View File

@@ -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,
};

View File

@@ -0,0 +1,8 @@
#pragma once
enum class EmuRegion
{
NTSC,
PALB,
DENDY
};

View File

@@ -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
};

View File

@@ -0,0 +1,9 @@
#pragma once
enum class NTArea
{
Area2000NT0 = 0,
Area2400NT1 = 1,
Area2800NT2 = 2,
Area2C00NT3 = 3,
};

View File

@@ -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
};

View File

@@ -0,0 +1,10 @@
#pragma once
#include <QtGlobal>
class InputProvider
{
public:
virtual void update() = 0;
virtual quint8 getData() const = 0;
};

View File

@@ -0,0 +1,11 @@
#include "mapper000.h"
QString Mapper000::name() const
{
return QStringLiteral("NROM");
}
quint8 Mapper000::mapper() const
{
return 0;
}

View File

@@ -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;
};

View File

@@ -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);
}
}
}

View File

@@ -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<quint8, 4> 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;
};

View File

@@ -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);
}

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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<int, 6> m_chrReg;
std::array<int, 4> m_prgReg;
// IRQ
bool m_irqEnabled;
quint8 m_irqCounter;
int m_oldIrqCounter;
quint8 m_irqReload;
bool m_irqClear;
bool m_mmc3AltBehavior;
};

View File

@@ -0,0 +1,9 @@
#pragma once
#include <QtGlobal>
#if defined(NESCORELIB_LIBRARY)
# define NESCORELIB_EXPORT Q_DECL_EXPORT
#else
# define NESCORELIB_EXPORT Q_DECL_IMPORT
#endif

183
nescorelib/nesemulator.cpp Normal file
View File

@@ -0,0 +1,183 @@
#include "nesemulator.h"
// system includes
#include <cmath>
#include <algorithm>
// 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;
}

70
nescorelib/nesemulator.h Normal file
View File

@@ -0,0 +1,70 @@
#pragma once
#include "nescorelib_global.h"
#include <QObject>
// system includes
#include <array>
#include <memory>
// 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;
};

69
nescorelib/rom.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include "rom.h"
// Qt includes
#include <QFile>
#include <QFileInfo>
// system includes
#include <stdexcept>
#include <array>
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<quint8, 16> header {};
if(file.read(reinterpret_cast<char*>(&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<char*>(&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<char*>(&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<char*>(&rom.chr[i][0]), 0x400) != 0x400)
throw std::runtime_error("rom is not long enough forchr");
return rom;
}

32
nescorelib/rom.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#include "nescorelib_global.h"
// Qt includes
#include <QVector>
#include <QByteArray>
// system includes
#include <optional>
#include <array>
// 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<std::array<quint8, 0x1000> > prg;
QVector<std::array<quint8, 0x400> > chr;
std::array<quint8, 512> trainer;
static Rom fromFile(const QString &path);
};

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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;
};

16
nesemu/CMakeLists.txt Normal file
View File

@@ -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)

141
nesemu/main.cpp Normal file
View File

@@ -0,0 +1,141 @@
#include <QApplication>
#include <QFileDialog>
#include <QMessageBox>
#include <QByteArray>
#include <QDataStream>
#include <QImage>
#include <QFile>
#include <QTimer>
#include <QAudioFormat>
#include <QAudioDeviceInfo>
#include <QAudioOutput>
// 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<qint32,Ppu::SCREEN_WIDTH*Ppu::SCREEN_HEIGHT> &frame){
canvas.setImage(QImage(reinterpret_cast<const uchar*>(&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();
}

21
nesguilib/CMakeLists.txt Normal file
View File

@@ -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})

View File

@@ -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;
}

20
nesguilib/gamepadinput.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include "nesguilib_global.h"
#include "inputprovider.h"
// Qt includes
#include <QGamepad>
class NESGUILIB_EXPORT GamepadInput : public InputProvider
{
public:
explicit GamepadInput(int deviceId = 0);
void update();
quint8 getData() const;
private:
QGamepad m_gamepad;
quint8 m_data {};
};

View File

@@ -0,0 +1,9 @@
#pragma once
#include <QtGlobal>
#if defined(NESGUILIB_LIBRARY)
# define NESGUILIB_EXPORT Q_DECL_EXPORT
#else
# define NESGUILIB_EXPORT Q_DECL_IMPORT
#endif