Mesen/Core/NsfMapper.cpp

480 lines
14 KiB
C++

#include "stdafx.h"
#include <random>
#include "NsfMapper.h"
#include "CPU.h"
#include "Console.h"
#include "MemoryManager.h"
NsfMapper* NsfMapper::_instance;
NsfMapper::NsfMapper()
{
_instance = this;
}
NsfMapper::~NsfMapper()
{
if(_instance == this) {
_instance = nullptr;
_console->GetSettings()->DisableOverclocking(false);
_console->GetSettings()->ClearFlags(EmulationFlags::NsfPlayerEnabled);
}
}
NsfMapper * NsfMapper::GetInstance()
{
return _instance;
}
void NsfMapper::InitMapper()
{
_console->GetSettings()->DisableOverclocking(true);
_console->GetSettings()->ClearFlags(EmulationFlags::Paused);
_console->GetSettings()->SetFlags(EmulationFlags::NsfPlayerEnabled);
_mmc5Audio.reset(new MMC5Audio(_console));
_vrc6Audio.reset(new Vrc6Audio(_console));
_vrc7Audio.reset(new Vrc7Audio(_console));
_fdsAudio.reset(new FdsAudio(_console));
_namcoAudio.reset(new Namco163Audio(_console));
_sunsoftAudio.reset(new Sunsoft5bAudio(_console));
SetCpuMemoryMapping(0x3F00, 0x3FFF, GetWorkRam() + 0x2000, MemoryAccessType::Read);
memcpy(GetWorkRam() + 0x2000, _nsfBios, 0x100);
//Clear all register settings
RemoveRegisterRange(0x0000, 0xFFFF, MemoryOperation::Any);
AddRegisterRange(0x3E00, 0x3E13, MemoryOperation::Read);
AddRegisterRange(0x3E10, 0x3E13, MemoryOperation::Write);
//NSF registers
AddRegisterRange(0x5FF6, 0x5FFF, MemoryOperation::Write);
}
void NsfMapper::SetNesModel(NesModel model)
{
if(model != _model) {
//Cheat a bit and change the IRQ reload value when the model changes to adjust tempo
switch(model) {
default:
case NesModel::NTSC: _irqReloadValue = _ntscSpeed; break;
case NesModel::PAL: _irqReloadValue = _palSpeed; break;
case NesModel::Dendy: _irqReloadValue = _dendySpeed; break;
}
_model = model;
}
}
void NsfMapper::InitMapper(RomData& romData)
{
_nsfHeader = romData.Info.NsfInfo;
_hasBankSwitching = HasBankSwitching();
if(!_hasBankSwitching) {
//Update bank config to allow BIOS to select the right banks on init
int8_t startBank = (_nsfHeader.LoadAddress / 0x1000);
for(int32_t i = 0; i < (int32_t)GetPRGPageCount(); i++) {
if((startBank + i) > 0x0F) {
break;
}
if(startBank + i - 8 >= 0) {
_nsfHeader.BankSetup[startBank + i - 8] = i;
}
}
}
_songNumber = _nsfHeader.StartingSong - 1;
_ntscSpeed = (uint16_t)(_nsfHeader.PlaySpeedNtsc * (CPU::ClockRateNtsc / 1000000.0));
_palSpeed = (uint16_t)(_nsfHeader.PlaySpeedPal * (CPU::ClockRatePal / 1000000.0));
_dendySpeed = (uint16_t)(_nsfHeader.PlaySpeedPal * (CPU::ClockRateDendy / 1000000.0));
if(_nsfHeader.SoundChips & NsfSoundChips::MMC5) {
AddRegisterRange(0x5000, 0x5015, MemoryOperation::Write); //Registers
AddRegisterRange(0x5205, 0x5206, MemoryOperation::Any); //Multiplication
SetCpuMemoryMapping(0x5C00, 0x5FFF, GetWorkRam() + 0x3000, MemoryAccessType::ReadWrite); //Exram
}
if(_nsfHeader.SoundChips & NsfSoundChips::VRC6) {
AddRegisterRange(0x9000, 0x9003, MemoryOperation::Write);
AddRegisterRange(0xA000, 0xA002, MemoryOperation::Write);
AddRegisterRange(0xB000, 0xB002, MemoryOperation::Write);
}
if(_nsfHeader.SoundChips & NsfSoundChips::VRC7) {
AddRegisterRange(0x9010, 0x9010, MemoryOperation::Write);
AddRegisterRange(0x9030, 0x9030, MemoryOperation::Write);
}
if(_nsfHeader.SoundChips & NsfSoundChips::Namco) {
AddRegisterRange(0x4800, 0x4FFF, MemoryOperation::Any);
AddRegisterRange(0xF800, 0xFFFF, MemoryOperation::Write);
}
if(_nsfHeader.SoundChips & NsfSoundChips::Sunsoft) {
AddRegisterRange(0xC000, 0xFFFF, MemoryOperation::Write);
}
if(_nsfHeader.SoundChips & NsfSoundChips::FDS) {
AddRegisterRange(0x4040, 0x4092, MemoryOperation::Any);
}
}
void NsfMapper::Reset(bool softReset)
{
if(!softReset) {
_songNumber = _nsfHeader.StartingSong - 1;
}
_needInit = true;
_irqEnabled = false;
_irqCounter = 0;
_irqReloadValue = 0;
_irqStatus = NsfIrqType::None;
_allowSilenceDetection = false;
_trackEndCounter = -1;
_trackEnded = false;
_trackFadeCounter = -1;
InternalSelectTrack(_songNumber, false);
//Reset/IRQ vector
AddRegisterRange(0xFFFC, 0xFFFF, MemoryOperation::Read);
}
void NsfMapper::GetMemoryRanges(MemoryRanges& ranges)
{
BaseMapper::GetMemoryRanges(ranges);
//Allows us to override the PPU's range (0x3E00 - 0x3FFF)
ranges.SetAllowOverride();
ranges.AddHandler(MemoryOperation::Read, 0x3E00, 0x3FFF);
ranges.AddHandler(MemoryOperation::Write, 0x3E00, 0x3FFF);
}
bool NsfMapper::HasBankSwitching()
{
for(int i = 0; i < 8; i++) {
if(_nsfHeader.BankSetup[i] != 0) {
return true;
}
}
return false;
}
void NsfMapper::TriggerIrq(NsfIrqType type)
{
if(type == NsfIrqType::Init) {
_trackEnded = false;
}
_debugIrqStatus = type;
_irqStatus = type;
_console->GetCpu()->SetIrqSource(IRQSource::External);
}
void NsfMapper::ClearIrq()
{
_irqStatus = NsfIrqType::None;
_console->GetCpu()->ClearIrqSource(IRQSource::External);
}
void NsfMapper::ClockLengthAndFadeCounters()
{
if(_trackEndCounter > 0) {
_trackEndCounter--;
if(_trackEndCounter == 0) {
_trackEnded = true;
}
}
if((_trackEndCounter < 0 || _allowSilenceDetection) && _console->GetSettings()->GetNsfAutoDetectSilenceDelay() > 0) {
//No track length specified
if(_console->GetSoundMixer()->GetMuteFrameCount() * SoundMixer::CycleLength > _silenceDetectDelay) {
//Auto detect end of track after AutoDetectSilenceDelay (in ms) has gone by without sound
_trackEnded = true;
_console->GetSoundMixer()->ResetMuteFrameCount();
}
}
if(_trackEnded) {
if(_trackFadeCounter > 0) {
if(_fadeLength != 0) {
double fadeRatio = (double)_trackFadeCounter / (double)_fadeLength * 1.2;
_console->GetSoundMixer()->SetFadeRatio(std::max(0.0, fadeRatio - 0.2));
}
_trackFadeCounter--;
}
if(_trackFadeCounter <= 0) {
SelectNextTrack();
}
}
}
void NsfMapper::SelectNextTrack()
{
if(!_console->GetSettings()->CheckFlag(EmulationFlags::NsfRepeat)) {
if(_console->GetSettings()->CheckFlag(EmulationFlags::NsfShuffle)) {
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<> dist(0, _nsfHeader.TotalSongs - 1);
_songNumber = dist(mt);
} else {
_songNumber = (_songNumber + 1) % _nsfHeader.TotalSongs;
}
}
InternalSelectTrack(_songNumber);
_trackEnded = false;
}
void NsfMapper::ProcessCpuClock()
{
if(_console->IsDebuggerAttached()) {
shared_ptr<Debugger> debugger = _console->GetDebugger(false);
if(debugger) {
uint16_t programCounter = _console->GetCpu()->GetPC();
if(_debugIrqStatus == NsfIrqType::Init && programCounter == _nsfHeader.InitAddress) {
_debugIrqStatus = NsfIrqType::None;
if(debugger->CheckFlag(DebuggerFlags::BreakOnInit)) {
debugger->Step(1);
}
} else if(_debugIrqStatus == NsfIrqType::Play && programCounter == _nsfHeader.PlayAddress) {
_debugIrqStatus = NsfIrqType::None;
if(debugger->CheckFlag(DebuggerFlags::BreakOnPlay)) {
debugger->Step(1);
}
}
}
}
_console->GetCpu()->SetIrqMask(_console->GetSettings()->GetNsfDisableApuIrqs() ? (uint8_t)IRQSource::External : 0xFF);
if(_needInit) {
TriggerIrq(NsfIrqType::Init);
_needInit = false;
}
if(_irqEnabled) {
_irqCounter--;
if(_irqCounter == 0) {
_irqCounter = _irqReloadValue;
TriggerIrq(NsfIrqType::Play);
}
}
ClockLengthAndFadeCounters();
if(_nsfHeader.SoundChips & NsfSoundChips::MMC5) {
_mmc5Audio->Clock();
}
if(_nsfHeader.SoundChips & NsfSoundChips::VRC6) {
_vrc6Audio->Clock();
}
if(_nsfHeader.SoundChips & NsfSoundChips::VRC7) {
_vrc7Audio->Clock();
}
if(_nsfHeader.SoundChips & NsfSoundChips::Namco) {
_namcoAudio->Clock();
}
if(_nsfHeader.SoundChips & NsfSoundChips::Sunsoft) {
_sunsoftAudio->Clock();
}
if(_nsfHeader.SoundChips & NsfSoundChips::FDS) {
_fdsAudio->Clock();
}
}
uint8_t NsfMapper::ReadRegister(uint16_t addr)
{
if((_nsfHeader.SoundChips & NsfSoundChips::FDS) && addr >= 0x4040 && addr <= 0x4092) {
return _fdsAudio->ReadRegister(addr);
} else if((_nsfHeader.SoundChips & NsfSoundChips::Namco) && addr >= 0x4800 && addr <= 0x4FFF) {
return _namcoAudio->ReadRegister(addr);
} else {
switch(addr) {
case 0x3E00: return _nsfHeader.InitAddress & 0xFF;
case 0x3E01: return (_nsfHeader.InitAddress >> 8) & 0xFF;
case 0x3E02: return _nsfHeader.PlayAddress & 0xFF;
case 0x3E03: return (_nsfHeader.PlayAddress >> 8) & 0xFF;
case 0x3E04:
case 0x3E05:
switch(_model) {
default:
case NesModel::NTSC: return _ntscSpeed & 0xFF;
case NesModel::PAL: return _palSpeed & 0xFF;
case NesModel::Dendy: return _dendySpeed & 0xFF;
}
break;
case 0x3E06:
case 0x3E07:
switch(_model) {
default:
case NesModel::NTSC: return (_ntscSpeed >> 8) & 0xFF;
case NesModel::PAL: return (_palSpeed >> 8) & 0xFF;
case NesModel::Dendy: return (_dendySpeed >> 8) & 0xFF;
}
break;
case 0x3E08: case 0x3E09: case 0x3E0A: case 0x3E0B:
case 0x3E0C: case 0x3E0D: case 0x3E0E: case 0x3E0F:
return _nsfHeader.BankSetup[addr & 0x07];
case 0x3E10: return _songNumber;
case 0x3E11: return _model == NesModel::PAL ? 0x01 : 0x00;
case 0x3E12: {
NsfIrqType result = _irqStatus;
ClearIrq();
return (uint8_t)result;
}
case 0x3E13: return _nsfHeader.SoundChips & 0x3F;
case 0x5205: return (_mmc5MultiplierValues[0] * _mmc5MultiplierValues[1]) & 0xFF;
case 0x5206: return (_mmc5MultiplierValues[0] * _mmc5MultiplierValues[1]) >> 8;
//Reset/irq vectors
case 0xFFFC: case 0xFFFD: case 0xFFFE: case 0xFFFF:
return _nsfBios[addr & 0xFF];
}
}
return _console->GetMemoryManager()->GetOpenBus();
}
void NsfMapper::WriteRegister(uint16_t addr, uint8_t value)
{
if((_nsfHeader.SoundChips & NsfSoundChips::FDS) && addr >= 0x4040 && addr <= 0x4092) {
_fdsAudio->WriteRegister(addr, value);
} else if((_nsfHeader.SoundChips & NsfSoundChips::MMC5) && addr >= 0x5000 && addr <= 0x5015) {
_mmc5Audio->WriteRegister(addr, value);
} else if((_nsfHeader.SoundChips & NsfSoundChips::Namco) && ((addr >= 0x4800 && addr <= 0x4FFF) || (addr >= 0xF800 && addr <= 0xFFFF))) {
_namcoAudio->WriteRegister(addr, value);
//Technically we should call this, but doing so breaks some NSFs
/*if(addr >= 0xF800 && _nsfHeader.SoundChips & NsfSoundChips::Sunsoft) {
_sunsoftAudio.WriteRegister(addr, value);
}*/
} else if((_nsfHeader.SoundChips & NsfSoundChips::Sunsoft) && addr >= 0xC000 && addr <= 0xFFFF) {
_sunsoftAudio->WriteRegister(addr, value);
} else {
switch(addr) {
case 0x3E10: _irqReloadValue = (_irqReloadValue & 0xFF00) | value; break;
case 0x3E11: _irqReloadValue = (_irqReloadValue & 0xFF) | (value << 8); break;
case 0x3E12:
_irqCounter = _irqReloadValue * 5;
_irqEnabled = (value > 0);
break;
case 0x3E13:
_irqCounter = _irqReloadValue;
break;
//MMC5 multiplication
case 0x5205: _mmc5MultiplierValues[0] = value; break;
case 0x5206: _mmc5MultiplierValues[1] = value; break;
case 0x5FF6: case 0x5FF7: {
uint16_t addrOffset = (addr == 0x5FF7 ? 0x1000 : 0x0000);
if(value == 0xFF || value == 0xFE) {
if(!_hasBankSwitching && _nsfHeader.LoadAddress < 0x7000) {
//Load address = 0x6000, put bank 0 at $6000
SetCpuMemoryMapping(0x6000 + addrOffset, 0x6FFF + addrOffset, value & 0x01, PrgMemoryType::PrgRom, MemoryAccessType::ReadWrite);
} else if(!_hasBankSwitching && addrOffset == 0x1000 && _nsfHeader.LoadAddress < 0x8000) {
//Load address = 0x7000, put bank 0 at $7000
SetCpuMemoryMapping(0x6000 + addrOffset, 0x6FFF + addrOffset, 0, PrgMemoryType::PrgRom, MemoryAccessType::ReadWrite);
} else {
//Set ram at $6000/7000 (default behavior)
SetCpuMemoryMapping(0x6000 + addrOffset, 0x6FFF + addrOffset, value & 0x01, PrgMemoryType::WorkRam);
}
} else {
SetCpuMemoryMapping(0x6000 + addrOffset, 0x6FFF + addrOffset, value, PrgMemoryType::PrgRom, MemoryAccessType::ReadWrite);
}
break;
}
case 0x5FF8: case 0x5FF9: case 0x5FFA: case 0x5FFB:
case 0x5FFC: case 0x5FFD: case 0x5FFE: case 0x5FFF:
SetCpuMemoryMapping(0x8000 + (addr & 0x07) * 0x1000, 0x8FFF + (addr & 0x07) * 0x1000, value, PrgMemoryType::PrgRom, (addr <= 0x5FFD && (_nsfHeader.SoundChips & NsfSoundChips::FDS)) ? MemoryAccessType::ReadWrite : MemoryAccessType::Read);
break;
case 0x9000: case 0x9001: case 0x9002: case 0x9003: case 0xA000: case 0xA001: case 0xA002: case 0xB000: case 0xB001: case 0xB002:
_vrc6Audio->WriteRegister(addr, value);
break;
case 0x9010: case 0x9030:
_vrc7Audio->WriteReg(addr, value);
break;
}
}
}
uint32_t NsfMapper::GetClockRate()
{
return ((_nsfHeader.Flags & 0x01) ? CPU::ClockRatePal : CPU::ClockRateNtsc);
}
void NsfMapper::InternalSelectTrack(uint8_t trackNumber, bool requestReset)
{
_songNumber = trackNumber;
if(requestReset) {
//Need to change track while running
//Some NSFs keep the interrupt flag on at all times, preventing us from triggering an IRQ to change tracks
//Forcing the console to reset ensures changing tracks always works, even with a bad NSF file
_console->Reset(true);
} else {
//Selecting tracking after a reset
_console->GetSoundMixer()->SetFadeRatio(1.0);
//Set track length/fade counters (NSFe)
if(_nsfHeader.TrackLength[trackNumber] >= 0) {
_trackEndCounter = (int32_t)((double)_nsfHeader.TrackLength[trackNumber] / 1000.0 * GetClockRate());
_allowSilenceDetection = false;
} else if(_nsfHeader.TotalSongs > 1) {
//Only apply a maximum duration to multi-track NSFs
//Single track NSFs will loop or restart after a portion of silence
//Substract 1 sec from default track time to account for 1 sec default fade time
_trackEndCounter = (_console->GetSettings()->GetNsfMoveToNextTrackTime() - 1) * GetClockRate();
_allowSilenceDetection = true;
}
if(_nsfHeader.TrackFade[trackNumber] >= 0) {
_trackFadeCounter = (int32_t)((double)_nsfHeader.TrackFade[trackNumber] / 1000.0 * GetClockRate());
} else {
//Default to 1 sec fade if none is specified (negative number)
_trackFadeCounter = GetClockRate();
}
_silenceDetectDelay = (uint32_t)((double)_console->GetSettings()->GetNsfAutoDetectSilenceDelay() / 1000.0 * GetClockRate());
_fadeLength = _trackFadeCounter;
TriggerIrq(NsfIrqType::Init);
}
}
void NsfMapper::SelectTrack(uint8_t trackNumber)
{
if(trackNumber < _nsfHeader.TotalSongs) {
InternalSelectTrack(trackNumber);
}
}
uint8_t NsfMapper::GetCurrentTrack()
{
return _songNumber;
}
NsfHeader NsfMapper::GetNsfHeader()
{
return _nsfHeader;
}
ConsoleFeatures NsfMapper::GetAvailableFeatures()
{
return ConsoleFeatures::Nsf;
}