diff --git a/Core/NES/Mappers/FDS/Fds.cpp b/Core/NES/Mappers/FDS/Fds.cpp index a8da6fecd..e12a75f32 100644 --- a/Core/NES/Mappers/FDS/Fds.cpp +++ b/Core/NES/Mappers/FDS/Fds.cpp @@ -254,6 +254,13 @@ void Fds::ProcessAutoDiskInsert() } } +/**TODO: + - Proper byte transfer flag handling (set every 1792 master cycles, or 149+1/3 CPU cycles, under normal conditions) + - Should allow for accurate handling of level 2->3 load bug in Ai Senshi Nicol + - Verify End of Head handling (may affect Kosodate Gokko and FMC Disk Card Checker) + - DRAM refresh watchdog implementation (must track PRG-RAM vs external access cycles...) + (Ongoing research, please consult TakuikaNinja for further details) +**/ void Fds::ProcessCpuClock() { BaseProcessCpuClock(); @@ -389,9 +396,35 @@ void Fds::UpdateCrc(uint8_t value) } } +void Fds::SetFdsControlReg(uint8_t value) +{ + _motorOn = (value & 0x01) == 0x01; + _resetTransfer = (value & 0x02) == 0x02; + _readMode = (value & 0x04) == 0x04; + SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical); + _crcControl = (value & 0x10) == 0x10; + //TODO $4025 bit 5 is unknown, all known software sets it to 1 + _diskReady = (value & 0x40) == 0x40; + _diskIrqEnabled = (value & 0x80) == 0x80; + + //Writing to $4025 clears IRQ according to FCEUX, puNES & Nintendulator + //Fixes issues in some unlicensed games (error $20 at power on) + //TODO This probably depends on the bits set? + _cpu->ClearIrqSource(IRQSource::FdsDisk); +} + void Fds::WriteRegister(uint16_t addr, uint8_t value) { - if((!_diskRegEnabled && addr >= 0x4024 && addr <= 0x4026) || (!_soundRegEnabled && addr >= 0x4040)) { + if(!_diskRegEnabled && addr >= 0x4024 && addr <= 0x4026) { + return; + } + + /**Only $4080 (volume envelope) seems to consistently deny writes during audio reset + TODO: + - $4085 (mod counter) denies writes too, but there is an unknown delay before being forced to 0 + - Determine $4088 (mod table write) behaviour while in audio reset state + **/ + if(!_soundRegEnabled && (addr == 0x4080 || addr == 0x4085 || addr == 0x4088)) { return; } @@ -417,40 +450,47 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) case 0x4023: _diskRegEnabled = (value & 0x01) == 0x01; + //TODO This is actually an audio reset, rename this variable in Fds.h _soundRegEnabled = (value & 0x02) == 0x02; if(!_diskRegEnabled) { _irqEnabled = false; + //Disabling disk registers forces $4025 = $06 (bit 3 reflected in $4030 reads) + SetFdsControlReg(0x06); + //Disabling disk registers forces $4026 (external connector) = 0x7F + _extConWriteReg = 0x7F; _cpu->ClearIrqSource(IRQSource::External); _cpu->ClearIrqSource(IRQSource::FdsDisk); } + + /**TODO Determine/implement audio reset behaviour, should probably go in FdsAudio: + - Proper method of resetting modulation state ($4085 write below doesn't always work) + - Reset wave accumulator to 0 + - Mod table appears to init with (or decay to) all 0s? + - There seems to be some kind of analogue "resume" window? + (Ongoing research, please consult TakuikaNinja for further details) + **/ + if(!_soundRegEnabled) { + _audio->WriteRegister(0x4080, 0x80); //Based on instant muting + _audio->WriteRegister(0x4085, 0x00); //Based on $4097 state + } break; case 0x4024: _writeDataReg = value; _transferComplete = false; - //Unsure about clearing irq here: FCEUX/Nintendulator don't do this, puNES does. + //Needed by some Super Magic Card games, which use this IRQ for raster effects _cpu->ClearIrqSource(IRQSource::FdsDisk); break; case 0x4025: - _motorOn = (value & 0x01) == 0x01; - _resetTransfer = (value & 0x02) == 0x02; - _readMode = (value & 0x04) == 0x04; - SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical); - _crcControl = (value & 0x10) == 0x10; - //Bit 6 is not used, always 1 - _diskReady = (value & 0x40) == 0x40; - _diskIrqEnabled = (value & 0x80) == 0x80; - - //Writing to $4025 clears IRQ according to FCEUX, puNES & Nintendulator - //Fixes issues in some unlicensed games (error $20 at power on) - _cpu->ClearIrqSource(IRQSource::FdsDisk); + SetFdsControlReg(value); break; case 0x4026: - _extConWriteReg = value; + //External connector only wires 7 bits + _extConWriteReg = value & 0x7F; break; default: @@ -464,24 +504,29 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) uint8_t Fds::ReadRegister(uint16_t addr) { uint8_t value = _memoryManager->GetOpenBus(); - if(_soundRegEnabled && addr >= 0x4040) { + if(addr >= 0x4040) { return _audio->ReadRegister(addr); - } else if(_diskRegEnabled && addr <= 0x4033) { + } else if(addr <= 0x4033) { switch(addr) { case 0x4030: - //These 3 pins are open bus + //These 2 pins are open bus value &= 0x24; - + /**TODO: + - DRAM refresh watchdog IRQ status is returned in bit 1, needs implementation + - End of Head is returned in bit 6, needs verification + **/ value |= _cpu->HasIrqSource(IRQSource::External) ? 0x01 : 0x00; - value |= _transferComplete ? 0x02 : 0x00; + //value |= _transferComplete ? 0x02 : 0x00; value |= GetMirroringType() == MirroringType::Horizontal ? 0x08 : 0; value |= _useQdFormat && _badCrc ? 0x10 : 0x00; //value |= _endOfHead ? 0x40 : 0x00; - //value |= _diskRegEnabled ? 0x80 : 0x00; + value |= _transferComplete ? 0x80 : 0x00; - _transferComplete = false; _cpu->ClearIrqSource(IRQSource::External); - _cpu->ClearIrqSource(IRQSource::FdsDisk); + + //Byte transfer flag is NOT cleared by this register! + //_transferComplete = false; + //_cpu->ClearIrqSource(IRQSource::FdsDisk); return value; case 0x4031: @@ -521,7 +566,7 @@ uint8_t Fds::ReadRegister(uint16_t addr) case 0x4033: //Always return good battery - return _extConWriteReg; + return _extConWriteReg | 0x80; } } diff --git a/Core/NES/Mappers/FDS/Fds.h b/Core/NES/Mappers/FDS/Fds.h index 584156010..aa5fec47e 100644 --- a/Core/NES/Mappers/FDS/Fds.h +++ b/Core/NES/Mappers/FDS/Fds.h @@ -85,7 +85,7 @@ class Fds : public BaseMapper uint32_t GetWorkRamPageSize() override { return 0x8000; } uint32_t GetWorkRamSize() override { return 0x8000; } uint16_t RegisterStartAddress() override { return 0x4020; } - uint16_t RegisterEndAddress() override { return 0x4092; } + uint16_t RegisterEndAddress() override { return 0x4097; } bool AllowRegisterRead() override { return true; } bool EnableCpuClockHook() override { return true; } @@ -106,6 +106,7 @@ class Fds : public BaseMapper void ProcessCpuClock() override; void UpdateCrc(uint8_t value); + void SetFdsControlReg(uint8_t value); void WriteRegister(uint16_t addr, uint8_t value) override; uint8_t ReadRegister(uint16_t addr) override; @@ -127,4 +128,4 @@ class Fds : public BaseMapper bool IsDiskInserted(); bool IsAutoInsertDiskEnabled(); -}; \ No newline at end of file +}; diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 9aa3f41f9..cce59ac5c 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -20,15 +20,17 @@ void FdsAudio::Serialize(Serializer& s) SV(_disableEnvelopes); SV(_haltWaveform); SV(_masterVolume); - SV(_waveOverflowCounter); + SV(_waveAccumulator); + SV(_waveM2Counter); SV(_wavePitch); SV(_wavePosition); SV(_lastOutput); + SV(_lastGain); } void FdsAudio::ClockAudio() { - int frequency = _volume.GetFrequency(); + uint16_t frequency = _volume.GetFrequency(); if(!_haltWaveform && !_disableEnvelopes) { _volume.TickEnvelope(); if(_mod.TickEnvelope()) { @@ -36,28 +38,46 @@ void FdsAudio::ClockAudio() } } - if(_mod.TickModulator()) { + //TODO Check if modulator and wave units are ticked on the same M2 cycle + //(the FDS stuff is probably ticked by the RAM adapter's crystal - we can't really simulate that here) + if(_mod.TickModulator(_haltWaveform)) { //Modulator was ticked, update wave pitch _mod.UpdateOutput(frequency); } - UpdateOutput(); + if(++_waveM2Counter == 16) { + if(_haltWaveform) { + _waveAccumulator = 0; + } else { + if(!_waveWriteEnabled) { + _waveAccumulator += (frequency * _mod.GetOutput()) & 0xFFFFF; + if(_waveAccumulator > 0xFFFFFF) { + _waveAccumulator -= 0x1000000; + } - if(!_haltWaveform && frequency + _mod.GetOutput() > 0) { - _waveOverflowCounter += frequency + _mod.GetOutput(); - if(_waveOverflowCounter < frequency + _mod.GetOutput()) { - _wavePosition = (_wavePosition + 1) & 0x3F; + _wavePosition = (_waveAccumulator >> 18) & 0x3F; + } } + _waveM2Counter = 0; } + UpdateOutput(); } void FdsAudio::UpdateOutput() { + //The wave unit continues to run, but the output is held at its previous value if(_waveWriteEnabled) { return; } - uint32_t level = std::min((int)_volume.GetGain(), 32) * WaveVolumeTable[_masterVolume]; + //"Changes to the volume envelope only take effect while the wavetable + // pointer (top 6 bits of wave accumulator) is 0." + if(_wavePosition == 0) { + _lastGain = _volume.GetGain(); + } + uint32_t level = std::min(_lastGain, uint8_t(32)) * WaveVolumeTable[_masterVolume]; + //TODO Volume ramp is actually nonlinear, masked by PWM + 2KHz lowpass + //(would likely kill performance for a miniscule audio accuracy improvement) uint8_t outputLevel = (_waveTable[_wavePosition] * level) / 1152; if(_lastOutput != outputLevel) { @@ -81,12 +101,55 @@ uint8_t FdsAudio::ReadRegister(uint16_t addr) //"When writing is disabled ($4089.7), reading anywhere in 4040-407F returns the value at the current wave position" value |= _waveTable[_wavePosition]; } - } else if(addr == 0x4090) { - value &= 0xC0; - value |= _volume.GetGain(); - } else if(addr == 0x4092) { - value &= 0xC0; - value |= _mod.GetGain(); + } else if(addr >= 0x4090) { + switch(addr) { + case 0x4090: + value &= 0xC0; + value |= _volume.GetGain(); + break; + + case 0x4091: + //Wave accumulator + //value &= 0xC0; + value = (_waveAccumulator >> 12) & 0xFF; + break; + + case 0x4092: + value &= 0xC0; + value |= _mod.GetGain(); + break; + + case 0x4093: + //Mod accumulator + value &= 0x80; + value |= (_mod.GetModAccumulator() >> 5) & 0x7F; + break; + + case 0x4094: + //Wave pitch intermediate result + //value &= 0xC0; + value = (_mod.GetOutput() >> 4) & 0xFF; + break; + + case 0x4095: + //Mod counter increment (lower nybble) + //TODO Determine upper nybble + value &= 0xC0; + value |= _mod.GetModIncrement(); + break; + + case 0x4096: + //Wavetable value + //TODO PWM masking + value &= 0xC0; + value |= _waveTable[_wavePosition] & 0x3F; + break; + + case 0x4097: + value &= 0x80; + value |= _mod.GetCounter() & 0x7F; + break; + } } return value; @@ -174,14 +237,13 @@ void FdsAudio::GetMapperStateEntries(vector& entries) entries.push_back(MapperStateEntry("$4084.7", "Envelope Disabled", _mod.IsEnvelopeDisabled(), MapperStateValueType::Bool)); int8_t modCounter = _mod.GetCounter(); - entries.push_back(MapperStateEntry("$4085.0-6", "Counter", std::to_string(modCounter), modCounter < 0 ? (modCounter + 128) : modCounter)); + entries.push_back(MapperStateEntry("$4085.0-6", "Counter", std::to_string(modCounter), (modCounter & 0x7F))); entries.push_back(MapperStateEntry("$4086/7.0-11", "Frequency", _mod.GetFrequency(), MapperStateValueType::Number16)); - //todo emulation logic + this based on new info - //entries.push_back(MapperStateEntry("$4087.6", "???", false, MapperStateValueType::Bool)); + entries.push_back(MapperStateEntry("$4087.6", "Force Tick Modulator", _mod.GetForceCarryOut(), MapperStateValueType::Bool)); - entries.push_back(MapperStateEntry("$4087.7", "Disabled", _mod.IsModulationDisabled(), MapperStateValueType::Bool)); + entries.push_back(MapperStateEntry("$4087.7", "Counter Disabled", _mod.IsModCounterDisabled(), MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("", "Gain", _mod.GetGain(), MapperStateValueType::Number8)); entries.push_back(MapperStateEntry("", "Mod Output", std::to_string(_mod.GetOutput()))); @@ -190,4 +252,14 @@ void FdsAudio::GetMapperStateEntries(vector& entries) entries.push_back(MapperStateEntry("$4089.7", "Wave Write Enabled", _waveWriteEnabled, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$408A", "Envelope Speed Multiplier", _volume.GetMasterSpeed(), MapperStateValueType::Number8)); + + entries.push_back(MapperStateEntry("$4090-$4097", "Audio Debug")); + entries.push_back(MapperStateEntry("$4090.0-5", "Volume Gain", _volume.GetGain(), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4091", "Wave Accumulator", ((_waveAccumulator >> 12) & 0xFF), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4092.0-5", "Mod Gain", _mod.GetGain(), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4093.0-6", "Mod Accumulator", ((_mod.GetModAccumulator() >> 5) & 0x7F), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4094", "Mod Counter * Gain", ((_mod.GetOutput() >> 4) & 0xFF), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4095.0-3", "Mod Counter Increment", _mod.GetModIncrement(), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4096.0-5", "Wavetable Value", (_waveTable[_wavePosition] & 0x3F), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4097.0-6", "Mod Counter Value", std::to_string(modCounter), (modCounter & 0x7F))); } diff --git a/Core/NES/Mappers/FDS/FdsAudio.h b/Core/NES/Mappers/FDS/FdsAudio.h index ce570aa5b..38fa456e1 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.h +++ b/Core/NES/Mappers/FDS/FdsAudio.h @@ -27,11 +27,13 @@ class FdsAudio : public BaseExpansionAudio uint8_t _masterVolume = 0; //Internal values - uint16_t _waveOverflowCounter = 0; + uint32_t _waveAccumulator = 0; //24-bit accumulator + uint8_t _waveM2Counter = 0; int32_t _wavePitch = 0; uint8_t _wavePosition = 0; uint8_t _lastOutput = 0; + uint8_t _lastGain = 0; protected: void Serialize(Serializer& s) override; @@ -46,4 +48,4 @@ class FdsAudio : public BaseExpansionAudio void WriteRegister(uint16_t addr, uint8_t value); void GetMapperStateEntries(vector& entries); -}; \ No newline at end of file +}; diff --git a/Core/NES/Mappers/FDS/ModChannel.h b/Core/NES/Mappers/FDS/ModChannel.h index a6bbddf55..75b0c3e73 100644 --- a/Core/NES/Mappers/FDS/ModChannel.h +++ b/Core/NES/Mappers/FDS/ModChannel.h @@ -5,30 +5,47 @@ class ModChannel : public BaseFdsChannel { private: - const int32_t ModReset = 0xFF; - const int32_t _modLut[8] = { 0, 1, 2, 4, ModReset, -4, -2, -1 }; + const int16_t _modReset = 0xFF; + const int16_t _modLut[8] = { 0, 1, 2, 4, _modReset, -4, -2, -1 }; int8_t _counter = 0; - bool _modulationDisabled = false; + bool _modCounterDisabled = false; + bool _forceCarryOut = false; - uint8_t _modTable[64] = {}; + uint32_t _modAccumulator = 0; //18-bit accumulator + uint8_t _modM2Counter = 0; + uint8_t _modTable[32] = {}; uint8_t _modTablePosition = 0; - uint16_t _overflowCounter = 0; - int32_t _output = 0; + int8_t _output = 0; protected: void Serialize(Serializer& s) override { BaseFdsChannel::Serialize(s); - SVArray(_modTable, 64); + SVArray(_modTable, 32); SV(_counter); - SV(_modulationDisabled); + SV(_modCounterDisabled); + SV(_forceCarryOut); SV(_modTablePosition); - SV(_overflowCounter); + SV(_modAccumulator); + SV(_modM2Counter); SV(_output); } + void IncrementAccumulator(uint32_t value) + { + _modAccumulator += value; + if(_modAccumulator > 0x3FFFF) { + _modAccumulator -= 0x40000; + } + } + + void UpdateModPosition() + { + _modTablePosition = (_modAccumulator >> 13) & 0x1F; + } + public: virtual void WriteReg(uint16_t addr, uint8_t value) override { @@ -42,9 +59,12 @@ class ModChannel : public BaseFdsChannel break; case 0x4087: BaseFdsChannel::WriteReg(addr, value); - _modulationDisabled = (value & 0x80) == 0x80; - if(_modulationDisabled) { - _overflowCounter = 0; + _modCounterDisabled = (value & 0x80) == 0x80; + // "4087.6 forces a carry out from bit 11." + _forceCarryOut = (value & 0x40) == 0x40; + if(_modCounterDisabled) { + // "Bits 0-12 are reset by 4087.7=1. Bits 13-17 have no reset." + _modAccumulator &= 0x3E000; } break; } @@ -53,10 +73,11 @@ class ModChannel : public BaseFdsChannel void WriteModTable(uint8_t value) { //"This register has no effect unless the mod unit is disabled via the high bit of $4087." - if(_modulationDisabled) { - _modTable[_modTablePosition & 0x3F] = value & 0x07; - _modTable[(_modTablePosition + 1) & 0x3F] = value & 0x07; - _modTablePosition = (_modTablePosition + 2) & 0x3F; + if(_modCounterDisabled) { + //"Writing $4088 increments the address (bits 13-17) when 4087.7=1." + _modTable[_modTablePosition] = value & 0x07; + IncrementAccumulator(0x2000); + UpdateModPosition(); } } @@ -70,65 +91,48 @@ class ModChannel : public BaseFdsChannel } } - bool IsEnabled() + bool TickModulator(bool haltWaveform) { - return !_modulationDisabled && _frequency > 0; - } - - bool TickModulator() - { - if(IsEnabled()) { - _overflowCounter += _frequency; - - if(_overflowCounter < _frequency) { - //Overflowed, tick the modulator - int32_t offset = _modLut[_modTable[_modTablePosition]]; - UpdateCounter(offset == ModReset ? 0 : _counter + offset); - - _modTablePosition = (_modTablePosition + 1) & 0x3F; + //$4083.7 also stops the mod table accumulator + if(_frequency > 0 && !haltWaveform && ++_modM2Counter == 16) { + //"The mod table counter is stopped, that's all. + //The freq mod formula is ALWAYS in effect, 4084/4085 still modify the wave frequency." + if(!_modCounterDisabled) { + IncrementAccumulator(_frequency); + UpdateModPosition(); + } - return true; + //"On a carry out from bit 11, update the mod counter (increment $4085 with modtable)." + //"4087.6 forces a carry out from bit 11." + if((_modAccumulator & 0xFFF) < _frequency || _forceCarryOut) { + int16_t offset = _modLut[_modTable[_modTablePosition]]; + UpdateCounter(offset == _modReset ? 0 : _counter + offset); } + + _modM2Counter = 0; + return true; } return false; } void UpdateOutput(uint16_t volumePitch) { - //Code from NesDev Wiki - + // code from new info by loopy + // https://forums.nesdev.org/viewtopic.php?p=232662#p232662 // pitch = $4082/4083 (12-bit unsigned pitch value) // counter = $4085 (7-bit signed mod counter) // gain = $4084 (6-bit unsigned mod gain) - // 1. multiply counter by gain, lose lowest 4 bits of result but "round" in a strange way int32_t temp = _counter * _gain; - int32_t remainder = temp & 0xF; - temp >>= 4; - if(remainder > 0 && (temp & 0x80) == 0) { - temp += _counter < 0 ? -1 : 2; + if((temp & 0x0f) && !(temp & 0x800)) { + temp += 0x20; } - - // 2. wrap if a certain range is exceeded - if(temp >= 192) { - temp -= 256; - } else if(temp < -64) { - temp += 256; - } - - // 3. multiply result by pitch, then round to nearest while dropping 6 bits - temp = volumePitch * temp; - remainder = temp & 0x3F; - temp >>= 6; - if(remainder >= 32) { - temp += 1; - } - - // final mod result is in temp + temp += 0x400; + temp = (temp >> 4) & 0xff; _output = temp; } - int32_t GetOutput() + int8_t GetOutput() { return _output; } @@ -138,8 +142,24 @@ class ModChannel : public BaseFdsChannel return _counter; } - bool IsModulationDisabled() + uint32_t GetModAccumulator() + { + return _modAccumulator; + } + + bool GetForceCarryOut() + { + return _forceCarryOut; + } + + bool IsModCounterDisabled() + { + return _modCounterDisabled; + } + + uint8_t GetModIncrement() { - return _modulationDisabled; + int16_t offset = _modLut[_modTable[_modTablePosition]]; + return offset == _modReset ? 0x0C : offset & 0x0F; } }; diff --git a/Core/NES/Mappers/NSF/NsfMapper.cpp b/Core/NES/Mappers/NSF/NsfMapper.cpp index 945c2b57d..9e891583d 100644 --- a/Core/NES/Mappers/NSF/NsfMapper.cpp +++ b/Core/NES/Mappers/NSF/NsfMapper.cpp @@ -70,7 +70,7 @@ void NsfMapper::InitMapper(RomData& romData) } if(_nsfHeader.SoundChips & NsfSoundChips::FDS) { - AddRegisterRange(0x4040, 0x4092, MemoryOperation::Any); + AddRegisterRange(0x4040, 0x4097, MemoryOperation::Any); } //Reset/IRQ vector @@ -227,7 +227,7 @@ void NsfMapper::ProcessCpuClock() uint8_t NsfMapper::ReadRegister(uint16_t addr) { - if((_nsfHeader.SoundChips & NsfSoundChips::FDS) && addr >= 0x4040 && addr <= 0x4092) { + if((_nsfHeader.SoundChips & NsfSoundChips::FDS) && addr >= 0x4040 && addr <= 0x4097) { return _fdsAudio->ReadRegister(addr); } else if((_nsfHeader.SoundChips & NsfSoundChips::Namco) && addr >= 0x4800 && addr <= 0x4FFF) { return _namcoAudio->ReadRegister(addr);