diff --git a/Core/APU.cpp b/Core/APU.cpp index 271ed734..231c21dd 100644 --- a/Core/APU.cpp +++ b/Core/APU.cpp @@ -36,6 +36,8 @@ APU::APU(MemoryManager* memoryManager) _memoryManager->RegisterIODevice(_triangleChannel.get()); _memoryManager->RegisterIODevice(_noiseChannel.get()); _memoryManager->RegisterIODevice(_deltaModulationChannel.get()); + + Reset(false); } APU::~APU() @@ -87,14 +89,16 @@ void APU::WriteRAM(uint16_t addr, uint8_t value) { //$4015 write Run(); + + //Writing to $4015 clears the DMC interrupt flag. + //This needs to be done before setting the enabled flag for the DMC (because doing so can trigger an IRQ) + CPU::ClearIRQSource(IRQSource::DMC); + _squareChannel[0]->SetEnabled((value & 0x01) == 0x01); _squareChannel[1]->SetEnabled((value & 0x02) == 0x02); _triangleChannel->SetEnabled((value & 0x04) == 0x04); _noiseChannel->SetEnabled((value & 0x08) == 0x08); _deltaModulationChannel->SetEnabled((value & 0x10) == 0x10); - - //Writing to $4015 clears the DMC interrupt flag. - CPU::ClearIRQSource(IRQSource::DMC); } void APU::GetMemoryRanges(MemoryRanges &ranges) @@ -110,7 +114,7 @@ void APU::Run() //-At the end of a frame //-Before APU registers are read/written to //-When a DMC or FrameCounter interrupt needs to be fired - uint32_t cyclesToRun = _currentCycle - _previousCycle; + int32_t cyclesToRun = _currentCycle - _previousCycle; while(_previousCycle < _currentCycle) { _previousCycle += _frameCounter->Run(cyclesToRun); @@ -128,8 +132,12 @@ void APU::StaticRun() Instance->Run(); } -bool APU::IrqPending(uint32_t currentCycle) +bool APU::NeedToRun(uint32_t currentCycle) { + if(_squareChannel[0]->NeedToRun() || _squareChannel[1]->NeedToRun() || _triangleChannel->NeedToRun() || _noiseChannel->NeedToRun() || _deltaModulationChannel->NeedToRun()) { + return true; + } + uint32_t cyclesToRun = currentCycle - _previousCycle; if(_frameCounter->IrqPending(cyclesToRun)) { return true; @@ -165,7 +173,7 @@ void APU::Exec() } _currentCycle = 0; _previousCycle = 0; - } else if(IrqPending(_currentCycle)) { + } else if(NeedToRun(_currentCycle)) { Run(); } } @@ -178,16 +186,16 @@ void APU::StopAudio() } -void APU::Reset() +void APU::Reset(bool softReset) { _currentCycle = 0; _previousCycle = 0; - _squareChannel[0]->Reset(); - _squareChannel[1]->Reset(); - _triangleChannel->Reset(); - _noiseChannel->Reset(); - _deltaModulationChannel->Reset(); - _frameCounter->Reset(); + _squareChannel[0]->Reset(softReset); + _squareChannel[1]->Reset(softReset); + _triangleChannel->Reset(softReset); + _noiseChannel->Reset(softReset); + _deltaModulationChannel->Reset(softReset); + _frameCounter->Reset(softReset); } void APU::StreamState(bool saving) diff --git a/Core/APU.h b/Core/APU.h index ad52ed97..20a13315 100644 --- a/Core/APU.h +++ b/Core/APU.h @@ -20,8 +20,8 @@ class APU : public Snapshotable, public IMemoryHandler static IAudioDevice* AudioDevice; static APU* Instance; - uint32_t _previousCycle = 0; - uint32_t _currentCycle = 0; + uint32_t _previousCycle; + uint32_t _currentCycle; vector> _squareChannel; unique_ptr _triangleChannel; @@ -35,7 +35,7 @@ class APU : public Snapshotable, public IMemoryHandler MemoryManager* _memoryManager; private: - bool IrqPending(uint32_t currentCycle); + bool NeedToRun(uint32_t currentCycle); void Run(); static void FrameCounterTick(FrameType type); @@ -52,7 +52,7 @@ class APU : public Snapshotable, public IMemoryHandler APU(MemoryManager* memoryManager); ~APU(); - void Reset(); + void Reset(bool softReset); static void RegisterAudioDevice(IAudioDevice *audioDevice) { diff --git a/Core/ApuEnvelope.h b/Core/ApuEnvelope.h index c46f8bbb..0eac520d 100644 --- a/Core/ApuEnvelope.h +++ b/Core/ApuEnvelope.h @@ -44,9 +44,9 @@ protected: } public: - virtual void Reset() + virtual void Reset(bool softReset) { - ApuLengthCounter::Reset(); + ApuLengthCounter::Reset(softReset); _constantVolume = false; _volume = 0; diff --git a/Core/ApuFrameCounter.h b/Core/ApuFrameCounter.h index c1b8e383..84f147df 100644 --- a/Core/ApuFrameCounter.h +++ b/Core/ApuFrameCounter.h @@ -13,16 +13,16 @@ enum class FrameType class ApuFrameCounter : public IMemoryHandler, public Snapshotable { private: - const vector> _stepCycles = { { { 7457, 14913, 22371, 29828, 29829, 29830}, + const vector> _stepCycles = { { { 7457, 14913, 22371, 29828, 29829, 29830}, { 7457, 14913, 22371, 29829, 37281, 37282} } }; const vector> _frameType = { { { FrameType::QuarterFrame, FrameType::HalfFrame, FrameType::QuarterFrame, FrameType::None, FrameType::HalfFrame, FrameType::None }, { FrameType::QuarterFrame, FrameType::HalfFrame, FrameType::QuarterFrame, FrameType::None, FrameType::HalfFrame, FrameType::None } } }; - int32_t _nextIrqCycle = 29828; - uint32_t _previousCycle = 0; - uint32_t _currentStep = 0; - uint32_t _stepMode = 0; //0: 4-step mode, 1: 5-step mode - bool _inhibitIRQ = false; + int32_t _nextIrqCycle; + int32_t _previousCycle; + uint32_t _currentStep; + uint32_t _stepMode; //0: 4-step mode, 1: 5-step mode + bool _inhibitIRQ; void (*_callback)(FrameType); @@ -30,27 +30,36 @@ public: ApuFrameCounter(void (*frameCounterTickCallback)(FrameType)) { _callback = frameCounterTickCallback; + Reset(false); } - void Reset() + void Reset(bool softReset) { _nextIrqCycle = 29828; - _previousCycle = 0; + + //"After reset or power-up, APU acts as if $4017 were written with $00 from 9 to 12 clocks before first instruction begins." + //Because of the 3-4 sequence reset delay, 9-12 clocks turns into 6-7 + _previousCycle = 6; + + //"After reset: APU mode in $4017 was unchanged", so we need to keep whatever value _stepMode has for soft resets + if(!softReset) { + _stepMode = 0; + } + _currentStep = 0; - _stepMode = 0; _inhibitIRQ = false; } void StreamState(bool saving) { Stream(_nextIrqCycle); - Stream(_previousCycle); + Stream(_previousCycle); Stream(_currentStep); Stream(_stepMode); Stream(_inhibitIRQ); } - uint32_t Run(uint32_t &cyclesToRun) + uint32_t Run(int32_t &cyclesToRun) { uint32_t cyclesRan; @@ -120,12 +129,18 @@ public: _nextIrqCycle = 29828; } - //Reset sequence when $4017 is written to - _previousCycle = 0; + //Reset sequence after $4017 is written to + if(CPU::GetRelativeCycleCount() & 0x01) { + //"If the write occurs during an APU cycle, the effects occur 3 CPU cycles after the $4017 write cycle" + _previousCycle = -3; + } else { + //"If the write occurs between APU cycles, the effects occur 4 CPU cycles after the write cycle. " + _previousCycle = -4; + } _currentStep = 0; if(_stepMode == 1) { - //Writing to $4017 with bit 7 set will immediately generate a clock for both the quarter frame and the half frame units, regardless of what the sequencer is doing. + //"Writing to $4017 with bit 7 set will immediately generate a clock for both the quarter frame and the half frame units, regardless of what the sequencer is doing." _callback(FrameType::HalfFrame); } } diff --git a/Core/ApuLengthCounter.h b/Core/ApuLengthCounter.h index 15fdecc2..6c561f87 100644 --- a/Core/ApuLengthCounter.h +++ b/Core/ApuLengthCounter.h @@ -7,14 +7,16 @@ class ApuLengthCounter : public BaseApuChannel<15> private: const vector _lcLookupTable = { { 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30 } }; bool _enabled = false; + bool _newHaltValue; protected: - bool _lengthCounterHalt = false; - uint8_t _lengthCounter = 0; + bool _lengthCounterHalt; + uint8_t _lengthCounter; void InitializeLengthCounter(bool haltFlag) { - _lengthCounterHalt = haltFlag; + SetRunFlag(); + _newHaltValue = haltFlag; } void LoadLengthCounter(uint8_t value) @@ -29,13 +31,24 @@ public: { } - virtual void Reset() + virtual void Reset(bool softReset) { - BaseApuChannel::Reset(); + BaseApuChannel::Reset(softReset); - _enabled = false; - _lengthCounterHalt = false; - _lengthCounter = 0; + if(softReset) { + _enabled = false; + if(GetChannel() != AudioChannel::Triangle) { + //"At reset, length counters should be enabled, triangle unaffected" + _lengthCounterHalt = false; + _lengthCounter = 0; + _newHaltValue = false; + } + } else { + _enabled = false; + _lengthCounterHalt = false; + _lengthCounter = 0; + _newHaltValue = false; + } } virtual void StreamState(bool saving) @@ -44,6 +57,7 @@ public: Stream(_enabled); Stream(_lengthCounterHalt); + Stream(_newHaltValue); Stream(_lengthCounter); } @@ -52,6 +66,12 @@ public: return _lengthCounter > 0; } + virtual void Run(uint32_t targetCycle) + { + _lengthCounterHalt = _newHaltValue; + BaseApuChannel::Run(targetCycle); + } + void TickLengthCounter() { if(_lengthCounter > 0 && !_lengthCounterHalt) { diff --git a/Core/BaseApuChannel.h b/Core/BaseApuChannel.h index 93731da3..841ae043 100644 --- a/Core/BaseApuChannel.h +++ b/Core/BaseApuChannel.h @@ -9,11 +9,13 @@ class BaseApuChannel : public IMemoryHandler, public Snapshotable { private: unique_ptr> _synth; - uint16_t _lastOutput = 0; - uint32_t _previousCycle = 0; Blip_Buffer *_buffer; + + uint16_t _lastOutput; + uint32_t _previousCycle; AudioChannel _channel; double _baseVolume; + bool _needToRun; protected: uint16_t _timer = 0; @@ -31,6 +33,16 @@ protected: _synth->volume(_baseVolume * EmulationSettings::GetChannelVolume(_channel) * 2); } + AudioChannel GetChannel() + { + return _channel; + } + + void SetRunFlag() + { + _needToRun = true; + } + public: virtual void Clock() = 0; virtual bool GetStatus() = 0; @@ -41,15 +53,16 @@ public: _buffer = buffer; _synth.reset(new Blip_Synth()); - Reset(); + Reset(false); } - virtual void Reset() + virtual void Reset(bool softReset) { _timer = 0; _period = 0; _lastOutput = 0; _previousCycle = 0; + _needToRun = false; _buffer->clear(); } @@ -65,8 +78,14 @@ public: } } + bool NeedToRun() + { + return _needToRun; + } + virtual void Run(uint32_t targetCycle) { + _needToRun = false; while(_previousCycle < targetCycle) { if(_timer == 0) { Clock(); diff --git a/Core/Console.cpp b/Core/Console.cpp index 347f0f62..03265d68 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -113,7 +113,7 @@ void Console::ResetComponents(bool softReset) Movie::Stop(); _ppu->Reset(); - _apu->Reset(); + _apu->Reset(softReset); _cpu->Reset(softReset); _memoryManager->Reset(softReset); diff --git a/Core/DeltaModulationChannel.h b/Core/DeltaModulationChannel.h index 45a49dab..cdf1c683 100644 --- a/Core/DeltaModulationChannel.h +++ b/Core/DeltaModulationChannel.h @@ -93,9 +93,9 @@ public: SetVolume(0.42545); } - virtual void Reset() + virtual void Reset(bool softReset) { - BaseApuChannel::Reset(); + BaseApuChannel::Reset(softReset); _sampleAddr = 0; _sampleLength = 0; @@ -161,7 +161,10 @@ public: case 0: //4010 _irqEnabled = (value & 0x80) == 0x80; _loopFlag = (value & 0x40) == 0x40; - _period = _dmcPeriodLookupTable[value & 0x0F]; + + //"The rate determines for how many CPU cycles happen between changes in the output level during automatic delta-encoded sample playback." + //Because BaseApuChannel does not decrement when setting _timer, we need to actually set the value to 1 less than the lookup table + _period = _dmcPeriodLookupTable[value & 0x0F] - 1; if(!_irqEnabled) { CPU::ClearIRQSource(IRQSource::DMC); diff --git a/Core/NoiseChannel.h b/Core/NoiseChannel.h index 1dc699c5..63de9c53 100644 --- a/Core/NoiseChannel.h +++ b/Core/NoiseChannel.h @@ -42,9 +42,9 @@ public: SetVolume(0.0741); } - virtual void Reset() + virtual void Reset(bool softReset) { - ApuEnvelope::Reset(); + ApuEnvelope::Reset(softReset); _shiftRegister = 1; _modeFlag = false; diff --git a/Core/SquareChannel.h b/Core/SquareChannel.h index ddae8505..059248ba 100644 --- a/Core/SquareChannel.h +++ b/Core/SquareChannel.h @@ -83,9 +83,9 @@ public: _isChannel1 = isChannel1; } - virtual void Reset() + virtual void Reset(bool softReset) { - ApuEnvelope::Reset(); + ApuEnvelope::Reset(softReset); _duty = 0; _dutyPos = 0; @@ -181,6 +181,6 @@ public: void Run(uint32_t targetCycle) { UpdateTargetPeriod(false); - BaseApuChannel::Run(targetCycle); + ApuLengthCounter::Run(targetCycle); } }; \ No newline at end of file diff --git a/Core/TriangleChannel.h b/Core/TriangleChannel.h index 02c18fa7..a7652316 100644 --- a/Core/TriangleChannel.h +++ b/Core/TriangleChannel.h @@ -35,9 +35,9 @@ public: SetVolume(0.12765); } - virtual void Reset() + virtual void Reset(bool softReset) { - ApuLengthCounter::Reset(); + ApuLengthCounter::Reset(softReset); _linearCounter = 0; _linearCounterReload = 0;