Imported existing sources
This commit is contained in:
102
.gitignore
vendored
102
.gitignore
vendored
@@ -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
5
CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
project(DbNeuralNet)
|
||||||
|
|
||||||
|
add_subdirectory(nesemu)
|
||||||
|
add_subdirectory(nescorelib)
|
||||||
|
add_subdirectory(nesguilib)
|
75
nescorelib/CMakeLists.txt
Normal file
75
nescorelib/CMakeLists.txt
Normal 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})
|
10
nescorelib/boards/bandai.cpp
Normal file
10
nescorelib/boards/bandai.cpp
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#include "bandai.h"
|
||||||
|
|
||||||
|
Bandai::Bandai(NesEmulator &emu, const Rom &rom) :
|
||||||
|
Board(emu, rom)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Bandai::~Bandai()
|
||||||
|
{
|
||||||
|
}
|
11
nescorelib/boards/bandai.h
Normal file
11
nescorelib/boards/bandai.h
Normal 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
582
nescorelib/boards/board.cpp
Normal 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
174
nescorelib/boards/board.h
Normal 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
59
nescorelib/boards/ffe.cpp
Normal 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
20
nescorelib/boards/ffe.h
Normal 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;
|
||||||
|
};
|
10
nescorelib/boards/mmc2.cpp
Normal file
10
nescorelib/boards/mmc2.cpp
Normal 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
11
nescorelib/boards/mmc2.h
Normal 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();
|
||||||
|
};
|
10
nescorelib/boards/namcot106.cpp
Normal file
10
nescorelib/boards/namcot106.cpp
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#include "namcot106.h"
|
||||||
|
|
||||||
|
Namcot106::Namcot106(NesEmulator &emu, const Rom &rom) :
|
||||||
|
Board(emu, rom)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Namcot106::~Namcot106()
|
||||||
|
{
|
||||||
|
}
|
11
nescorelib/boards/namcot106.h
Normal file
11
nescorelib/boards/namcot106.h
Normal 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
622
nescorelib/emu/apu.cpp
Normal 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
149
nescorelib/emu/apu.h
Normal 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
217
nescorelib/emu/apudmc.cpp
Normal 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
55
nescorelib/emu/apudmc.h
Normal 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
220
nescorelib/emu/apunos.cpp
Normal 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
61
nescorelib/emu/apunos.h
Normal 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
219
nescorelib/emu/apusq1.cpp
Normal 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
70
nescorelib/emu/apusq1.h
Normal 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
216
nescorelib/emu/apusq2.cpp
Normal 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
70
nescorelib/emu/apusq2.h
Normal 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
174
nescorelib/emu/aputrl.cpp
Normal 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
54
nescorelib/emu/aputrl.h
Normal 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
1268
nescorelib/emu/cpu.cpp
Normal file
File diff suppressed because it is too large
Load Diff
114
nescorelib/emu/cpu.h
Normal file
114
nescorelib/emu/cpu.h
Normal 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
199
nescorelib/emu/dma.cpp
Normal 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
44
nescorelib/emu/dma.h
Normal 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 {};
|
||||||
|
};
|
69
nescorelib/emu/interrupts.cpp
Normal file
69
nescorelib/emu/interrupts.cpp
Normal 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;
|
||||||
|
}
|
45
nescorelib/emu/interrupts.h
Normal file
45
nescorelib/emu/interrupts.h
Normal 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
234
nescorelib/emu/memory.cpp
Normal 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
68
nescorelib/emu/memory.h
Normal 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
53
nescorelib/emu/ports.cpp
Normal 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
42
nescorelib/emu/ports.h
Normal 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
983
nescorelib/emu/ppu.cpp
Normal 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
187
nescorelib/emu/ppu.h
Normal 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
158
nescorelib/emusettings.h
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
nescorelib/enums/chrarea.h
Normal file
13
nescorelib/enums/chrarea.h
Normal 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,
|
||||||
|
};
|
8
nescorelib/enums/emuregion.h
Normal file
8
nescorelib/enums/emuregion.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
enum class EmuRegion
|
||||||
|
{
|
||||||
|
NTSC,
|
||||||
|
PALB,
|
||||||
|
DENDY
|
||||||
|
};
|
25
nescorelib/enums/mirroring.h
Normal file
25
nescorelib/enums/mirroring.h
Normal 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
|
||||||
|
};
|
9
nescorelib/enums/ntarea.h
Normal file
9
nescorelib/enums/ntarea.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
enum class NTArea
|
||||||
|
{
|
||||||
|
Area2000NT0 = 0,
|
||||||
|
Area2400NT1 = 1,
|
||||||
|
Area2800NT2 = 2,
|
||||||
|
Area2C00NT3 = 3,
|
||||||
|
};
|
17
nescorelib/enums/prgarea.h
Normal file
17
nescorelib/enums/prgarea.h
Normal 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
|
||||||
|
};
|
10
nescorelib/inputprovider.h
Normal file
10
nescorelib/inputprovider.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
class InputProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void update() = 0;
|
||||||
|
virtual quint8 getData() const = 0;
|
||||||
|
};
|
11
nescorelib/mappers/mapper000.cpp
Normal file
11
nescorelib/mappers/mapper000.cpp
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#include "mapper000.h"
|
||||||
|
|
||||||
|
QString Mapper000::name() const
|
||||||
|
{
|
||||||
|
return QStringLiteral("NROM");
|
||||||
|
}
|
||||||
|
|
||||||
|
quint8 Mapper000::mapper() const
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
13
nescorelib/mappers/mapper000.h
Normal file
13
nescorelib/mappers/mapper000.h
Normal 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;
|
||||||
|
};
|
216
nescorelib/mappers/mapper001.cpp
Normal file
216
nescorelib/mappers/mapper001.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
nescorelib/mappers/mapper001.h
Normal file
41
nescorelib/mappers/mapper001.h
Normal 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;
|
||||||
|
};
|
23
nescorelib/mappers/mapper002.cpp
Normal file
23
nescorelib/mappers/mapper002.cpp
Normal 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);
|
||||||
|
}
|
16
nescorelib/mappers/mapper002.h
Normal file
16
nescorelib/mappers/mapper002.h
Normal 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;
|
||||||
|
};
|
17
nescorelib/mappers/mapper003.cpp
Normal file
17
nescorelib/mappers/mapper003.cpp
Normal 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);
|
||||||
|
}
|
15
nescorelib/mappers/mapper003.h
Normal file
15
nescorelib/mappers/mapper003.h
Normal 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;
|
||||||
|
};
|
178
nescorelib/mappers/mapper004.cpp
Normal file
178
nescorelib/mappers/mapper004.cpp
Normal 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);
|
||||||
|
}
|
44
nescorelib/mappers/mapper004.h
Normal file
44
nescorelib/mappers/mapper004.h
Normal 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;
|
||||||
|
};
|
9
nescorelib/nescorelib_global.h
Normal file
9
nescorelib/nescorelib_global.h
Normal 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
183
nescorelib/nesemulator.cpp
Normal 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
70
nescorelib/nesemulator.h
Normal 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
69
nescorelib/rom.cpp
Normal 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
32
nescorelib/rom.h
Normal 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);
|
||||||
|
};
|
21
nescorelib/soundhighpassfilter.cpp
Normal file
21
nescorelib/soundhighpassfilter.cpp
Normal 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;
|
||||||
|
}
|
17
nescorelib/soundhighpassfilter.h
Normal file
17
nescorelib/soundhighpassfilter.h
Normal 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;
|
||||||
|
};
|
21
nescorelib/soundlowpassfilter.cpp
Normal file
21
nescorelib/soundlowpassfilter.cpp
Normal 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;
|
||||||
|
}
|
17
nescorelib/soundlowpassfilter.h
Normal file
17
nescorelib/soundlowpassfilter.h
Normal 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
16
nesemu/CMakeLists.txt
Normal 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
141
nesemu/main.cpp
Normal 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
21
nesguilib/CMakeLists.txt
Normal 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})
|
24
nesguilib/gamepadinput.cpp
Normal file
24
nesguilib/gamepadinput.cpp
Normal 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
20
nesguilib/gamepadinput.h
Normal 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 {};
|
||||||
|
};
|
9
nesguilib/nesguilib_global.h
Normal file
9
nesguilib/nesguilib_global.h
Normal 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
|
Reference in New Issue
Block a user