From b932ae9bf1b1027bd02b37947160c3229bffb51e Mon Sep 17 00:00:00 2001 From: Persune Date: Wed, 21 Jun 2023 14:59:41 +0800 Subject: [PATCH 1/8] Implement nonlinear FDS DAC --- Core/Core.vcxproj | 1 + Core/Core.vcxproj.filters | 3 ++ Core/NES/Mappers/FDS/FDS_LUT_norm.h | 66 +++++++++++++++++++++++++++++ Core/NES/Mappers/FDS/FdsAudio.cpp | 14 +++++- Core/NES/Mappers/FDS/FdsAudio.h | 1 + 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 Core/NES/Mappers/FDS/FDS_LUT_norm.h diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index f77c076bb..ee71d89e8 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -64,6 +64,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 2af307f63..bda5512f7 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -2586,6 +2586,9 @@ NES\Mappers\Nintendo + + NES\Mappers\FDS + SMS diff --git a/Core/NES/Mappers/FDS/FDS_LUT_norm.h b/Core/NES/Mappers/FDS/FDS_LUT_norm.h new file mode 100644 index 000000000..a1a3cca1a --- /dev/null +++ b/Core/NES/Mappers/FDS/FDS_LUT_norm.h @@ -0,0 +1,66 @@ +const double FDS_LUT_norm[64] = { + 0x0.0p+0, + 0x1.77b430407afedp-7, + 0x1.7e2aa81b97e9fp-6, + 0x1.2da90104d3dc4p-5, + 0x1.7ed0f4d85503ap-5, + 0x1.f430c23ecee18p-5, + 0x1.2e9098c0665e9p-4, + 0x1.74ab006de21c2p-4, + 0x1.7e3542e86fbbcp-4, + 0x1.c092303db0e69p-4, + 0x1.f557315bc0168p-4, + 0x1.21ceb92bfc91ap-3, + 0x1.2fbcf45b9abb3p-3, + 0x1.581118ece6c4ep-3, + 0x1.76afe96c2ec4ap-3, + 0x1.a73db37503d66p-3, + 0x1.7f9ae26f64b87p-3, + 0x1.a81865a47a694p-3, + 0x1.c2726a5c6ef2fp-3, + 0x1.f2a26a782760ep-3, + 0x1.f6f3be61fe8eap-3, + 0x1.1390497c2e4c8p-2, + 0x1.2332f3d58a09bp-2, + 0x1.401aea4f84050p-2, + 0x1.30fa612fd4d39p-2, + 0x1.4aa9de2acf7e5p-2, + 0x1.59c98f464eaf0p-2, + 0x1.789ff0bdaa8bap-2, + 0x1.7881ed9082a92p-2, + 0x1.97b1d532d81efp-2, + 0x1.aa52a5a4902afp-2, + 0x1.cfca0a46b48d0p-2, + 0x1.828e8ba6bbf75p-2, + 0x1.9ef4acccd5142p-2, + 0x1.ac4a9edf343dfp-2, + 0x1.cda33076754abp-2, + 0x1.c6d0f934e7331p-2, + 0x1.e76090f4a1bc3p-2, + 0x1.f7fd6650fccd7p-2, + 0x1.0f8bf9ced01bdp-1, + 0x1.fcd39af7c2ea4p-2, + 0x1.0f33bc0b11a65p-1, + 0x1.1716820ad7099p-1, + 0x1.2b4de2f6643c7p-1, + 0x1.26f113799ef6fp-1, + 0x1.3a7266ddb78d0p-1, + 0x1.44acfa8be91e8p-1, + 0x1.5cd66fcdcffcbp-1, + 0x1.363ebba2bccbdp-1, + 0x1.48e9d524e778ep-1, + 0x1.50ca6d96684fep-1, + 0x1.67702468d91e6p-1, + 0x1.607e459ef629cp-1, + 0x1.76452811c00ecp-1, + 0x1.808d2047987b5p-1, + 0x1.9b1d6358d9181p-1, + 0x1.8133b8ea21423p-1, + 0x1.97a1b75bf1fc5p-1, + 0x1.a17a76473ca14p-1, + 0x1.bd0b29e264ceep-1, + 0x1.b5e4f43e4829bp-1, + 0x1.d09406d3ada2cp-1, + 0x1.de5be31b5d2ecp-1, + 0x1.0000000000000p+0 +}; diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 0dd4ee81e..32b1d0c6c 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -8,6 +8,7 @@ #include "NES/APU/NesApu.h" #include "NES/APU/BaseExpansionAudio.h" #include "Utilities/Serializer.h" +#include "FDS_LUT_norm.h" void FdsAudio::Serialize(Serializer& s) { @@ -51,8 +52,8 @@ void FdsAudio::ClockAudio() void FdsAudio::UpdateOutput() { - uint32_t level = std::min((int)_volume.GetGain(), 32) * WaveVolumeTable[_masterVolume]; - uint8_t outputLevel = (_waveTable[_wavePosition] * level) / 1152; + uint32_t level = std::min((int)_volume.GetGain(), 32); + uint8_t outputLevel = uint8_t(DACTable[_waveTable[_wavePosition]][_masterVolume] * level); if(_lastOutput != outputLevel) { @@ -63,6 +64,15 @@ void FdsAudio::UpdateOutput() FdsAudio::FdsAudio(NesConsole* console) : BaseExpansionAudio(console) { + // initialize DAC LUT + // data comes from plgDavid's DC capture of an FDS's DAC output + // data capture shared from the NESDev server + + for(int masterlevel = 0; masterlevel < 4; masterlevel++) + for(int wavelevel = 0; wavelevel < 64; wavelevel++) + DACTable[wavelevel][masterlevel] = float( + (FDS_LUT_norm[wavelevel] * 64.0 * double(WaveVolumeTable[masterlevel])) / 1152.0 + ); } uint8_t FdsAudio::ReadRegister(uint16_t addr) diff --git a/Core/NES/Mappers/FDS/FdsAudio.h b/Core/NES/Mappers/FDS/FdsAudio.h index abc116586..7de596aa0 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.h +++ b/Core/NES/Mappers/FDS/FdsAudio.h @@ -13,6 +13,7 @@ class FdsAudio : public BaseExpansionAudio { private: const uint32_t WaveVolumeTable[4] = { 36, 24, 17, 14 }; + double DACTable[64][4] = {}; //Register values uint8_t _waveTable[64] = {}; From ca8947adb192c952c1d8b8e3946f987ef3e94a7c Mon Sep 17 00:00:00 2001 From: Persune Date: Fri, 1 Dec 2023 11:21:09 +0800 Subject: [PATCH 2/8] Use 32-bit floating point instead of 64-bit --- Core/NES/Mappers/FDS/FDS_LUT_norm.h | 130 ++++++++++++++-------------- Core/NES/Mappers/FDS/FdsAudio.cpp | 4 +- Core/NES/Mappers/FDS/FdsAudio.h | 2 +- 3 files changed, 67 insertions(+), 69 deletions(-) diff --git a/Core/NES/Mappers/FDS/FDS_LUT_norm.h b/Core/NES/Mappers/FDS/FDS_LUT_norm.h index a1a3cca1a..b9fb175b3 100644 --- a/Core/NES/Mappers/FDS/FDS_LUT_norm.h +++ b/Core/NES/Mappers/FDS/FDS_LUT_norm.h @@ -1,66 +1,66 @@ -const double FDS_LUT_norm[64] = { - 0x0.0p+0, - 0x1.77b430407afedp-7, - 0x1.7e2aa81b97e9fp-6, - 0x1.2da90104d3dc4p-5, - 0x1.7ed0f4d85503ap-5, - 0x1.f430c23ecee18p-5, - 0x1.2e9098c0665e9p-4, - 0x1.74ab006de21c2p-4, - 0x1.7e3542e86fbbcp-4, - 0x1.c092303db0e69p-4, - 0x1.f557315bc0168p-4, - 0x1.21ceb92bfc91ap-3, - 0x1.2fbcf45b9abb3p-3, - 0x1.581118ece6c4ep-3, - 0x1.76afe96c2ec4ap-3, - 0x1.a73db37503d66p-3, - 0x1.7f9ae26f64b87p-3, - 0x1.a81865a47a694p-3, - 0x1.c2726a5c6ef2fp-3, - 0x1.f2a26a782760ep-3, - 0x1.f6f3be61fe8eap-3, - 0x1.1390497c2e4c8p-2, - 0x1.2332f3d58a09bp-2, - 0x1.401aea4f84050p-2, - 0x1.30fa612fd4d39p-2, - 0x1.4aa9de2acf7e5p-2, - 0x1.59c98f464eaf0p-2, - 0x1.789ff0bdaa8bap-2, - 0x1.7881ed9082a92p-2, - 0x1.97b1d532d81efp-2, - 0x1.aa52a5a4902afp-2, - 0x1.cfca0a46b48d0p-2, - 0x1.828e8ba6bbf75p-2, - 0x1.9ef4acccd5142p-2, - 0x1.ac4a9edf343dfp-2, - 0x1.cda33076754abp-2, - 0x1.c6d0f934e7331p-2, - 0x1.e76090f4a1bc3p-2, - 0x1.f7fd6650fccd7p-2, - 0x1.0f8bf9ced01bdp-1, - 0x1.fcd39af7c2ea4p-2, - 0x1.0f33bc0b11a65p-1, - 0x1.1716820ad7099p-1, - 0x1.2b4de2f6643c7p-1, - 0x1.26f113799ef6fp-1, - 0x1.3a7266ddb78d0p-1, - 0x1.44acfa8be91e8p-1, - 0x1.5cd66fcdcffcbp-1, - 0x1.363ebba2bccbdp-1, - 0x1.48e9d524e778ep-1, - 0x1.50ca6d96684fep-1, - 0x1.67702468d91e6p-1, - 0x1.607e459ef629cp-1, - 0x1.76452811c00ecp-1, - 0x1.808d2047987b5p-1, - 0x1.9b1d6358d9181p-1, - 0x1.8133b8ea21423p-1, - 0x1.97a1b75bf1fc5p-1, - 0x1.a17a76473ca14p-1, - 0x1.bd0b29e264ceep-1, - 0x1.b5e4f43e4829bp-1, - 0x1.d09406d3ada2cp-1, - 0x1.de5be31b5d2ecp-1, - 0x1.0000000000000p+0 +const float FDS_LUT_norm[64] = { + 0.0, + 0.011465572752058506, + 0.0233255997300148, + 0.03682375326752663, + 0.04673049971461296, + 0.061058409512043, + 0.07386837154626846, + 0.09098339825868607, + 0.09331251680850983, + 0.10951442271471024, + 0.12239760905504227, + 0.1415075808763504, + 0.1483096331357956, + 0.16800136864185333, + 0.18295270204544067, + 0.20666064321994781, + 0.18730713427066803, + 0.20707780122756958, + 0.2199448049068451, + 0.2434738427400589, + 0.24558208882808685, + 0.26910510659217834, + 0.28437408804893494, + 0.312602698802948, + 0.29783013463020325, + 0.32291364669799805, + 0.33768296241760254, + 0.3677976429462433, + 0.36768317222595215, + 0.3981393277645111, + 0.4163309335708618, + 0.4529191851615906, + 0.3774969279766083, + 0.40523025393486023, + 0.41825342178344727, + 0.45081785321235657, + 0.44415655732154846, + 0.4759543240070343, + 0.4921776056289673, + 0.5303648114204407, + 0.4969009757041931, + 0.5296915769577026, + 0.5450936555862427, + 0.5845786333084106, + 0.576058030128479, + 0.6141541004180908, + 0.6341322660446167, + 0.6813235282897949, + 0.6059473752975464, + 0.6424090266227722, + 0.6577944159507751, + 0.7020274996757507, + 0.6884633898735046, + 0.7309963703155518, + 0.7510766983032227, + 0.802958607673645, + 0.7523477077484131, + 0.7961556911468506, + 0.8153874278068542, + 0.869225800037384, + 0.8552623987197876, + 0.9073793292045593, + 0.9342948198318481, + 1.0 }; diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 32b1d0c6c..64af6b30b 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -70,9 +70,7 @@ FdsAudio::FdsAudio(NesConsole* console) : BaseExpansionAudio(console) for(int masterlevel = 0; masterlevel < 4; masterlevel++) for(int wavelevel = 0; wavelevel < 64; wavelevel++) - DACTable[wavelevel][masterlevel] = float( - (FDS_LUT_norm[wavelevel] * 64.0 * double(WaveVolumeTable[masterlevel])) / 1152.0 - ); + DACTable[wavelevel][masterlevel] = (FDS_LUT_norm[wavelevel] * 64.0 * float(WaveVolumeTable[masterlevel])) / 1152.0; } uint8_t FdsAudio::ReadRegister(uint16_t addr) diff --git a/Core/NES/Mappers/FDS/FdsAudio.h b/Core/NES/Mappers/FDS/FdsAudio.h index 7de596aa0..bd00f1fd3 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.h +++ b/Core/NES/Mappers/FDS/FdsAudio.h @@ -13,7 +13,7 @@ class FdsAudio : public BaseExpansionAudio { private: const uint32_t WaveVolumeTable[4] = { 36, 24, 17, 14 }; - double DACTable[64][4] = {}; + float DACTable[64][4] = {}; //Register values uint8_t _waveTable[64] = {}; From 5b8b63b09fbc73e2b1d89575b4efa9032d35cb96 Mon Sep 17 00:00:00 2001 From: Persune Date: Thu, 18 Apr 2024 22:02:44 +0800 Subject: [PATCH 3/8] Update FDS emulation based on loopy's research https://forums.nesdev.org/viewtopic.php?p=232662#p232662 --- Core/NES/Mappers/FDS/BaseFdsChannel.h | 25 ++++--- Core/NES/Mappers/FDS/FdsAudio.cpp | 35 ++++++---- Core/NES/Mappers/FDS/FdsAudio.h | 3 +- Core/NES/Mappers/FDS/ModChannel.h | 97 +++++++++++++++++---------- 4 files changed, 99 insertions(+), 61 deletions(-) diff --git a/Core/NES/Mappers/FDS/BaseFdsChannel.h b/Core/NES/Mappers/FDS/BaseFdsChannel.h index 0bdd4ad04..e72a59770 100644 --- a/Core/NES/Mappers/FDS/BaseFdsChannel.h +++ b/Core/NES/Mappers/FDS/BaseFdsChannel.h @@ -55,19 +55,22 @@ class BaseFdsChannel : public ISerializable } } - bool TickEnvelope() + virtual bool TickEnvelope(uint8_t wavePosition) { - if(!_envelopeOff && _masterSpeed > 0) { - _timer--; - if(_timer == 0) { - ResetTimer(); - - if(_volumeIncrease && _gain < 32) { - _gain++; - } else if(!_volumeIncrease && _gain > 0) { - _gain--; + // "Changes to the volume envelope only take effect while the wavetable + // pointer (top 6 bits of wave accumulator) is 0." + if(wavePosition == 0) { + if(!_envelopeOff && _masterSpeed > 0) { + _timer--; + if(_timer == 0) { + ResetTimer(); + if(_volumeIncrease && _gain < 32) { + _gain++; + } else if(!_volumeIncrease && _gain > 0) { + _gain--; + } + return true; } - return true; } } return false; diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 64af6b30b..59eaf0dd1 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -17,15 +17,15 @@ void FdsAudio::Serialize(Serializer& s) SVArray(_waveTable, 64); SV(_volume); SV(_mod); - SV(_waveWriteEnabled); SV(_disableEnvelopes); SV(_haltWaveform); SV(_masterVolume); SV(_waveOverflowCounter); SV(_wavePitch); SV(_wavePosition); SV(_lastOutput); + SV(_waveWriteEnabled); SV(_disableEnvelopes); SV(_haltWaveform); SV(_masterVolume); SV(_waveAccumulator); SV(_waveM2Counter); SV(_wavePitch); SV(_wavePosition); SV(_lastOutput); } void FdsAudio::ClockAudio() { int frequency = _volume.GetFrequency(); if(!_haltWaveform && !_disableEnvelopes) { - _volume.TickEnvelope(); - if(_mod.TickEnvelope()) { + _volume.TickEnvelope(_wavePosition); + if(_mod.TickEnvelope(_wavePosition)) { _mod.UpdateOutput(frequency); } } @@ -36,15 +36,20 @@ void FdsAudio::ClockAudio() } if(_haltWaveform) { - _wavePosition = 0; + _waveAccumulator = 0; UpdateOutput(); } else { UpdateOutput(); - if(frequency + _mod.GetOutput() > 0 && !_waveWriteEnabled) { - _waveOverflowCounter += frequency + _mod.GetOutput(); - if(_waveOverflowCounter < frequency + _mod.GetOutput()) { - _wavePosition = (_wavePosition + 1) & 0x3F; + if(!_waveWriteEnabled) { + if(++_waveM2Counter == 16) { + _waveAccumulator += (frequency * _mod.GetOutput()) & 0xFFFFF; + if(_waveAccumulator > 0xFFFFFF) { + _waveAccumulator &= 0xFFFFFF; + } + + _wavePosition = (_waveAccumulator >> 18) & 0x3F; + _waveM2Counter = 0; } } } @@ -87,9 +92,17 @@ uint8_t FdsAudio::ReadRegister(uint16_t addr) } else if(addr == 0x4090) { value &= 0xC0; value |= _volume.GetGain(); + } else if(addr == 0x4091) { + // Wave accumulator + value &= 0xC0; + value |= (_waveAccumulator >> 12) & 0xFF; } else if(addr == 0x4092) { value &= 0xC0; value |= _mod.GetGain(); + } else if(addr == 0x4093) { + // Mod accumulator + value &= 0xC0; + value |= _mod.GetModAccumulator() & 0x7F; } return value; @@ -158,8 +171,7 @@ void FdsAudio::GetMapperStateEntries(vector& entries) entries.push_back(MapperStateEntry("$4082/3.0-11", "Frequency", _volume.GetFrequency(), MapperStateValueType::Number16)); entries.push_back(MapperStateEntry("$4083.6", "Volume/Mod Envelopes Disabled", _disableEnvelopes, MapperStateValueType::Bool)); - //todo emulation logic + this based on new info - //entries.push_back(MapperStateEntry("$4083.7", "Halt Wave Form", _haltWaveform, MapperStateValueType::Bool)); + entries.push_back(MapperStateEntry("$4083.7", "Halt Wave Form", _haltWaveform, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("", "Gain", _volume.GetGain(), MapperStateValueType::Number8)); @@ -173,8 +185,7 @@ void FdsAudio::GetMapperStateEntries(vector& entries) 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("", "Gain", _mod.GetGain(), MapperStateValueType::Number8)); diff --git a/Core/NES/Mappers/FDS/FdsAudio.h b/Core/NES/Mappers/FDS/FdsAudio.h index bd00f1fd3..895cff4cb 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.h +++ b/Core/NES/Mappers/FDS/FdsAudio.h @@ -28,7 +28,8 @@ 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; diff --git a/Core/NES/Mappers/FDS/ModChannel.h b/Core/NES/Mappers/FDS/ModChannel.h index 4374ed88e..3189e669e 100644 --- a/Core/NES/Mappers/FDS/ModChannel.h +++ b/Core/NES/Mappers/FDS/ModChannel.h @@ -10,10 +10,12 @@ class ModChannel : public BaseFdsChannel int8_t _counter = 0; bool _modulationDisabled = false; + bool _forceCarryOut = false; + uint32_t _modAccumulator = 0; //18-bit accumulator + uint8_t _modM2Counter = 0; uint8_t _modTable[64] = {}; uint8_t _modTablePosition = 0; - uint16_t _overflowCounter = 0; int32_t _output = 0; protected: @@ -22,7 +24,7 @@ class ModChannel : public BaseFdsChannel BaseFdsChannel::Serialize(s); SVArray(_modTable, 64); - SV(_counter); SV(_modulationDisabled); SV(_modTablePosition); SV(_overflowCounter); SV(_output); + SV(_counter); SV(_modulationDisabled); SV(_forceCarryOut); SV(_modTablePosition); SV(_modAccumulator); SV(_modM2Counter); SV(_output); } public: @@ -39,20 +41,44 @@ class ModChannel : public BaseFdsChannel case 0x4087: BaseFdsChannel::WriteReg(addr, value); _modulationDisabled = (value & 0x80) == 0x80; + // "4087.6 forces a carry out from bit 11." + _forceCarryOut = (value & 0x40) == 0x40; if(_modulationDisabled) { - _overflowCounter = 0; + // "Bits 0-12 are reset by 4087.7=1. Bits 13-17 have no reset." + _modAccumulator &= 0x3F000; } break; } } + virtual bool TickEnvelope(uint8_t wavePosition) override + { + if(!_envelopeOff && _masterSpeed > 0) { + _timer--; + if(_timer == 0) { + ResetTimer(); + + if(_volumeIncrease && _gain < 32) { + _gain++; + } else if(!_volumeIncrease && _gain > 0) { + _gain--; + } + return true; + } + } + return false; + } + 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; + // "ghost" modtable address bit(0) makes mod unit step thru each entry twice + _modTable[_modTablePosition] = value & 0x07; _modTable[(_modTablePosition + 1) & 0x3F] = value & 0x07; - _modTablePosition = (_modTablePosition + 2) & 0x3F; + // "Writing $4088 increments the address (bits 13-17) when 4087.7=1." + _modAccumulator += 0x2000; + _modTablePosition = (_modAccumulator >> 12) & 0x3F; } } @@ -74,15 +100,21 @@ class ModChannel : public BaseFdsChannel 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); + if(++_modM2Counter == 16) { + _modAccumulator += _frequency; + if(_modAccumulator > 0x3FFFF) { + _modAccumulator &= 0x3FFFF; + } - _modTablePosition = (_modTablePosition + 1) & 0x3F; + // "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) { + int32_t offset = _modLut[_modTable[_modTablePosition]]; + UpdateCounter(offset == ModReset ? 0 : _counter + offset); + _modTablePosition = (_modAccumulator >> 12) & 0x3F; + } + _modM2Counter = 0; return true; } } @@ -91,36 +123,17 @@ class ModChannel : public BaseFdsChannel 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; - } - - // 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 + if((temp & 0x0f) && !(temp & 0x800)) + temp += 0x20; + temp += 0x400; + temp = (temp >> 4) & 0xff; _output = temp; } @@ -134,6 +147,16 @@ class ModChannel : public BaseFdsChannel return _counter; } + uint32_t GetModAccumulator() + { + return _modAccumulator; + } + + bool GetForceCarryOut() + { + return _forceCarryOut; + } + bool IsModulationDisabled() { return _modulationDisabled; From 0c44085d932e0faa7f377c6c50c495b1b0d9cd1f Mon Sep 17 00:00:00 2001 From: Persune Date: Fri, 19 Apr 2024 03:15:51 +0800 Subject: [PATCH 4/8] Add more FDS audio registers --- Core/NES/Mappers/FDS/FdsAudio.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 59eaf0dd1..03e3730ef 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -102,7 +102,19 @@ uint8_t FdsAudio::ReadRegister(uint16_t addr) } else if(addr == 0x4093) { // Mod accumulator value &= 0xC0; - value |= _mod.GetModAccumulator() & 0x7F; + value |= (_mod.GetModAccumulator() >> 5) & 0x7F; + } else if(addr == 0x4094) { + // wave pitch intermediate result + value &= 0xC0; + value |= (_mod.GetOutput() >> 4) & 0xFF; + } else if(addr == 0x4096) { + // wavetable position + value &= 0xC0; + value |= _wavePosition & 0x3F; + } else if(addr == 0x4097) { + // mod counter value + value &= 0xC0; + value |= _mod.GetCounter() & 0x7F; } return value; From 9c9afebcfd9caf4658cf14392897ad18d6d9a134 Mon Sep 17 00:00:00 2001 From: Persune Date: Fri, 19 Apr 2024 05:11:22 +0800 Subject: [PATCH 5/8] Refactor modulation table indexing --- Core/NES/Mappers/FDS/FdsAudio.cpp | 2 +- Core/NES/Mappers/FDS/ModChannel.h | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 03e3730ef..4fd5e05e9 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -45,7 +45,7 @@ void FdsAudio::ClockAudio() if(++_waveM2Counter == 16) { _waveAccumulator += (frequency * _mod.GetOutput()) & 0xFFFFF; if(_waveAccumulator > 0xFFFFFF) { - _waveAccumulator &= 0xFFFFFF; + _waveAccumulator -= 0x1000000; } _wavePosition = (_waveAccumulator >> 18) & 0x3F; diff --git a/Core/NES/Mappers/FDS/ModChannel.h b/Core/NES/Mappers/FDS/ModChannel.h index 3189e669e..c13ebc383 100644 --- a/Core/NES/Mappers/FDS/ModChannel.h +++ b/Core/NES/Mappers/FDS/ModChannel.h @@ -14,7 +14,7 @@ class ModChannel : public BaseFdsChannel uint32_t _modAccumulator = 0; //18-bit accumulator uint8_t _modM2Counter = 0; - uint8_t _modTable[64] = {}; + uint8_t _modTable[32] = {}; uint8_t _modTablePosition = 0; int32_t _output = 0; @@ -23,7 +23,7 @@ class ModChannel : public BaseFdsChannel { BaseFdsChannel::Serialize(s); - SVArray(_modTable, 64); + SVArray(_modTable, 32); SV(_counter); SV(_modulationDisabled); SV(_forceCarryOut); SV(_modTablePosition); SV(_modAccumulator); SV(_modM2Counter); SV(_output); } @@ -75,10 +75,10 @@ class ModChannel : public BaseFdsChannel if(_modulationDisabled) { // "ghost" modtable address bit(0) makes mod unit step thru each entry twice _modTable[_modTablePosition] = value & 0x07; - _modTable[(_modTablePosition + 1) & 0x3F] = value & 0x07; + _modTable[_modTablePosition] = value & 0x07; // "Writing $4088 increments the address (bits 13-17) when 4087.7=1." _modAccumulator += 0x2000; - _modTablePosition = (_modAccumulator >> 12) & 0x3F; + _modTablePosition = (_modAccumulator >> 13) & 0x1F; } } @@ -103,7 +103,7 @@ class ModChannel : public BaseFdsChannel if(++_modM2Counter == 16) { _modAccumulator += _frequency; if(_modAccumulator > 0x3FFFF) { - _modAccumulator &= 0x3FFFF; + _modAccumulator -= 0x40000; } // "On a carry out from bit 11, update the mod counter (increment $4085 with modtable)." @@ -111,7 +111,7 @@ class ModChannel : public BaseFdsChannel if((_modAccumulator & 0xFFF) < _frequency || _forceCarryOut) { int32_t offset = _modLut[_modTable[_modTablePosition]]; UpdateCounter(offset == ModReset ? 0 : _counter + offset); - _modTablePosition = (_modAccumulator >> 12) & 0x3F; + _modTablePosition = (_modAccumulator >> 13) & 0x1F; } _modM2Counter = 0; From 2852b9eb58e25fb59cbe25a0c816ee16481c69bc Mon Sep 17 00:00:00 2001 From: Persune Date: Sat, 20 Apr 2024 00:36:47 +0800 Subject: [PATCH 6/8] Fix intermittent modulator bug --- Core/NES/Mappers/FDS/BaseFdsChannel.h | 2 +- Core/NES/Mappers/FDS/FdsAudio.cpp | 4 +-- Core/NES/Mappers/FDS/ModChannel.h | 42 ++++++++++++++++----------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/Core/NES/Mappers/FDS/BaseFdsChannel.h b/Core/NES/Mappers/FDS/BaseFdsChannel.h index e72a59770..7d99d4e00 100644 --- a/Core/NES/Mappers/FDS/BaseFdsChannel.h +++ b/Core/NES/Mappers/FDS/BaseFdsChannel.h @@ -55,7 +55,7 @@ class BaseFdsChannel : public ISerializable } } - virtual bool TickEnvelope(uint8_t wavePosition) + bool TickEnvelope(uint8_t wavePosition) { // "Changes to the volume envelope only take effect while the wavetable // pointer (top 6 bits of wave accumulator) is 0." diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 4fd5e05e9..b8482740f 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -22,10 +22,10 @@ void FdsAudio::Serialize(Serializer& s) void FdsAudio::ClockAudio() { - int frequency = _volume.GetFrequency(); + uint16_t frequency = _volume.GetFrequency(); if(!_haltWaveform && !_disableEnvelopes) { _volume.TickEnvelope(_wavePosition); - if(_mod.TickEnvelope(_wavePosition)) { + if(_mod.TickEnvelope()) { _mod.UpdateOutput(frequency); } } diff --git a/Core/NES/Mappers/FDS/ModChannel.h b/Core/NES/Mappers/FDS/ModChannel.h index c13ebc383..0b7b65642 100644 --- a/Core/NES/Mappers/FDS/ModChannel.h +++ b/Core/NES/Mappers/FDS/ModChannel.h @@ -5,8 +5,8 @@ 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; @@ -16,7 +16,7 @@ class ModChannel : public BaseFdsChannel uint8_t _modM2Counter = 0; uint8_t _modTable[32] = {}; uint8_t _modTablePosition = 0; - int32_t _output = 0; + uint8_t _output = 0; protected: void Serialize(Serializer& s) override @@ -27,6 +27,19 @@ class ModChannel : public BaseFdsChannel SV(_counter); SV(_modulationDisabled); SV(_forceCarryOut); SV(_modTablePosition); 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 { @@ -45,13 +58,13 @@ class ModChannel : public BaseFdsChannel _forceCarryOut = (value & 0x40) == 0x40; if(_modulationDisabled) { // "Bits 0-12 are reset by 4087.7=1. Bits 13-17 have no reset." - _modAccumulator &= 0x3F000; + _modAccumulator &= 0x3E000; } break; } } - virtual bool TickEnvelope(uint8_t wavePosition) override + bool TickEnvelope() { if(!_envelopeOff && _masterSpeed > 0) { _timer--; @@ -73,12 +86,10 @@ class ModChannel : public BaseFdsChannel { //"This register has no effect unless the mod unit is disabled via the high bit of $4087." if(_modulationDisabled) { - // "ghost" modtable address bit(0) makes mod unit step thru each entry twice - _modTable[_modTablePosition] = value & 0x07; - _modTable[_modTablePosition] = value & 0x07; // "Writing $4088 increments the address (bits 13-17) when 4087.7=1." - _modAccumulator += 0x2000; - _modTablePosition = (_modAccumulator >> 13) & 0x1F; + _modTable[_modTablePosition] = value & 0x07; + IncrementAccumulator(0x2000); + UpdateModPosition(); } } @@ -101,17 +112,14 @@ class ModChannel : public BaseFdsChannel { if(IsEnabled()) { if(++_modM2Counter == 16) { - _modAccumulator += _frequency; - if(_modAccumulator > 0x3FFFF) { - _modAccumulator -= 0x40000; - } + IncrementAccumulator(_frequency); // "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) { - int32_t offset = _modLut[_modTable[_modTablePosition]]; + int16_t offset = _modLut[_modTable[_modTablePosition]]; UpdateCounter(offset == ModReset ? 0 : _counter + offset); - _modTablePosition = (_modAccumulator >> 13) & 0x1F; + UpdateModPosition(); } _modM2Counter = 0; @@ -137,7 +145,7 @@ class ModChannel : public BaseFdsChannel _output = temp; } - int32_t GetOutput() + uint8_t GetOutput() { return _output; } From 7bb81c38d21ddfd75be7dff0c4c1eb8f2371d041 Mon Sep 17 00:00:00 2001 From: Persune Date: Sat, 20 Apr 2024 05:33:52 +0800 Subject: [PATCH 7/8] Volume takes effect when wave position == 0 --- Core/NES/Mappers/FDS/BaseFdsChannel.h | 24 ++++++++++-------------- Core/NES/Mappers/FDS/FDS_LUT_norm.h | 2 +- Core/NES/Mappers/FDS/FdsAudio.cpp | 22 +++++++++++++++------- Core/NES/Mappers/FDS/FdsAudio.h | 1 + 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/Core/NES/Mappers/FDS/BaseFdsChannel.h b/Core/NES/Mappers/FDS/BaseFdsChannel.h index 7d99d4e00..2cc886613 100644 --- a/Core/NES/Mappers/FDS/BaseFdsChannel.h +++ b/Core/NES/Mappers/FDS/BaseFdsChannel.h @@ -55,22 +55,18 @@ class BaseFdsChannel : public ISerializable } } - bool TickEnvelope(uint8_t wavePosition) + bool TickEnvelope() { - // "Changes to the volume envelope only take effect while the wavetable - // pointer (top 6 bits of wave accumulator) is 0." - if(wavePosition == 0) { - if(!_envelopeOff && _masterSpeed > 0) { - _timer--; - if(_timer == 0) { - ResetTimer(); - if(_volumeIncrease && _gain < 32) { - _gain++; - } else if(!_volumeIncrease && _gain > 0) { - _gain--; - } - return true; + if(!_envelopeOff && _masterSpeed > 0) { + _timer--; + if(_timer == 0) { + ResetTimer(); + if(_volumeIncrease && _gain < 32) { + _gain++; + } else if(!_volumeIncrease && _gain > 0) { + _gain--; } + return true; } } return false; diff --git a/Core/NES/Mappers/FDS/FDS_LUT_norm.h b/Core/NES/Mappers/FDS/FDS_LUT_norm.h index b9fb175b3..902d81594 100644 --- a/Core/NES/Mappers/FDS/FDS_LUT_norm.h +++ b/Core/NES/Mappers/FDS/FDS_LUT_norm.h @@ -1,4 +1,4 @@ -const float FDS_LUT_norm[64] = { +constexpr float FDS_LUT_norm[64] = { 0.0, 0.011465572752058506, 0.0233255997300148, diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index b8482740f..0e500e65e 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -17,14 +17,14 @@ void FdsAudio::Serialize(Serializer& s) SVArray(_waveTable, 64); SV(_volume); SV(_mod); - SV(_waveWriteEnabled); SV(_disableEnvelopes); SV(_haltWaveform); SV(_masterVolume); SV(_waveAccumulator); SV(_waveM2Counter); SV(_wavePitch); SV(_wavePosition); SV(_lastOutput); + SV(_waveWriteEnabled); SV(_disableEnvelopes); SV(_haltWaveform); SV(_masterVolume); SV(_waveAccumulator); SV(_waveM2Counter); SV(_wavePitch); SV(_wavePosition); SV(_lastOutput); SV(_lastGain); } void FdsAudio::ClockAudio() { uint16_t frequency = _volume.GetFrequency(); if(!_haltWaveform && !_disableEnvelopes) { - _volume.TickEnvelope(_wavePosition); + _volume.TickEnvelope(); if(_mod.TickEnvelope()) { _mod.UpdateOutput(frequency); } @@ -57,9 +57,15 @@ void FdsAudio::ClockAudio() void FdsAudio::UpdateOutput() { - uint32_t level = std::min((int)_volume.GetGain(), 32); - uint8_t outputLevel = uint8_t(DACTable[_waveTable[_wavePosition]][_masterVolume] * level); + // "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(); + } + uint8_t level = std::min(_lastGain, uint8_t(32)); + // volume level is PWM, but can be approximated linearly + uint8_t outputLevel = uint8_t(DACTable[_waveTable[_wavePosition]][_masterVolume] * float(level)); if(_lastOutput != outputLevel) { _console->GetApu()->AddExpansionAudioDelta(AudioChannel::FDS, outputLevel - _lastOutput); @@ -72,10 +78,12 @@ FdsAudio::FdsAudio(NesConsole* console) : BaseExpansionAudio(console) // initialize DAC LUT // data comes from plgDavid's DC capture of an FDS's DAC output // data capture shared from the NESDev server - - for(int masterlevel = 0; masterlevel < 4; masterlevel++) - for(int wavelevel = 0; wavelevel < 64; wavelevel++) + // TODO: generate data based from FDS decap DAC schematics + for(int masterlevel = 0; masterlevel < 4; masterlevel++) { + for(int wavelevel = 0; wavelevel < 64; wavelevel++) { DACTable[wavelevel][masterlevel] = (FDS_LUT_norm[wavelevel] * 64.0 * float(WaveVolumeTable[masterlevel])) / 1152.0; + } + } } uint8_t FdsAudio::ReadRegister(uint16_t addr) diff --git a/Core/NES/Mappers/FDS/FdsAudio.h b/Core/NES/Mappers/FDS/FdsAudio.h index 895cff4cb..df9f74467 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.h +++ b/Core/NES/Mappers/FDS/FdsAudio.h @@ -34,6 +34,7 @@ class FdsAudio : public BaseExpansionAudio uint8_t _wavePosition = 0; uint8_t _lastOutput = 0; + uint8_t _lastGain = 0; protected: void Serialize(Serializer& s) override; From 06e5eb4a17ad518784add09d0e23f33b67bc19ba Mon Sep 17 00:00:00 2001 From: Persune Date: Sat, 20 Apr 2024 23:02:28 +0800 Subject: [PATCH 8/8] $4087.7 stops mod counter, refactor M2 counter --- Core/NES/Mappers/FDS/FdsAudio.cpp | 20 +++++------ Core/NES/Mappers/FDS/ModChannel.h | 59 ++++++++++++++++--------------- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 0e500e65e..34bfaa960 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -30,29 +30,29 @@ void FdsAudio::ClockAudio() } } - if(_mod.TickModulator()) { + // TODO: check if modulator and wave units are ticked on the same M2 cycle + if(_mod.TickModulator(_haltWaveform)) { //Modulator was ticked, update wave pitch _mod.UpdateOutput(frequency); } - if(_haltWaveform) { - _waveAccumulator = 0; - UpdateOutput(); - } else { - UpdateOutput(); + if(++_waveM2Counter == 16) { + if(_haltWaveform) { + _waveAccumulator = 0; + } else { - if(!_waveWriteEnabled) { - if(++_waveM2Counter == 16) { + if(!_waveWriteEnabled) { _waveAccumulator += (frequency * _mod.GetOutput()) & 0xFFFFF; if(_waveAccumulator > 0xFFFFFF) { _waveAccumulator -= 0x1000000; } _wavePosition = (_waveAccumulator >> 18) & 0x3F; - _waveM2Counter = 0; } } + _waveM2Counter = 0; } + UpdateOutput(); } void FdsAudio::UpdateOutput() @@ -207,7 +207,7 @@ void FdsAudio::GetMapperStateEntries(vector& entries) 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.IsModulationCounterDisabled(), MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("", "Gain", _mod.GetGain(), MapperStateValueType::Number8)); entries.push_back(MapperStateEntry("", "Mod Output", std::to_string(_mod.GetOutput()))); diff --git a/Core/NES/Mappers/FDS/ModChannel.h b/Core/NES/Mappers/FDS/ModChannel.h index 0b7b65642..bafa219a8 100644 --- a/Core/NES/Mappers/FDS/ModChannel.h +++ b/Core/NES/Mappers/FDS/ModChannel.h @@ -9,7 +9,7 @@ class ModChannel : public BaseFdsChannel 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; uint32_t _modAccumulator = 0; //18-bit accumulator @@ -24,7 +24,7 @@ class ModChannel : public BaseFdsChannel BaseFdsChannel::Serialize(s); SVArray(_modTable, 32); - SV(_counter); SV(_modulationDisabled); SV(_forceCarryOut); SV(_modTablePosition); SV(_modAccumulator); SV(_modM2Counter); SV(_output); + SV(_counter); SV(_modCounterDisabled); SV(_forceCarryOut); SV(_modTablePosition); SV(_modAccumulator); SV(_modM2Counter); SV(_output); } void IncrementAccumulator(uint32_t value) @@ -53,10 +53,10 @@ class ModChannel : public BaseFdsChannel break; case 0x4087: BaseFdsChannel::WriteReg(addr, value); - _modulationDisabled = (value & 0x80) == 0x80; + _modCounterDisabled = (value & 0x80) == 0x80; // "4087.6 forces a carry out from bit 11." _forceCarryOut = (value & 0x40) == 0x40; - if(_modulationDisabled) { + if(_modCounterDisabled) { // "Bits 0-12 are reset by 4087.7=1. Bits 13-17 have no reset." _modAccumulator &= 0x3E000; } @@ -85,7 +85,7 @@ 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) { + if(_modCounterDisabled) { // "Writing $4088 increments the address (bits 13-17) when 4087.7=1." _modTable[_modTablePosition] = value & 0x07; IncrementAccumulator(0x2000); @@ -95,36 +95,39 @@ class ModChannel : public BaseFdsChannel void UpdateCounter(int8_t value) { - _counter = value; - if(_counter >= 64) { - _counter -= 128; - } else if(_counter < -64) { - _counter += 128; + // "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) { + _counter = value; + if(_counter >= 64) { + _counter -= 128; + } else if(_counter < -64) { + _counter += 128; + } } } bool IsEnabled() { - return !_modulationDisabled && _frequency > 0; + return _frequency > 0; } - bool TickModulator() + bool TickModulator(bool haltWaveform) { - if(IsEnabled()) { - if(++_modM2Counter == 16) { - IncrementAccumulator(_frequency); - - // "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); - UpdateModPosition(); - } - - _modM2Counter = 0; - return true; + // $4083.7 also stops the mod table accumulator + if(IsEnabled() && !haltWaveform && ++_modM2Counter == 16) { + IncrementAccumulator(_frequency); + + // "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); + UpdateModPosition(); } + + _modM2Counter = 0; + return true; } return false; } @@ -165,8 +168,8 @@ class ModChannel : public BaseFdsChannel return _forceCarryOut; } - bool IsModulationDisabled() + bool IsModulationCounterDisabled() { - return _modulationDisabled; + return _modCounterDisabled; } };