Improved APU accuracy - passes most APU tests

This commit is contained in:
Souryo 2015-07-19 01:30:13 -04:00
parent af749cb6ad
commit 84f735b6a4
11 changed files with 121 additions and 56 deletions

View File

@ -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)

View File

@ -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<unique_ptr<SquareChannel>> _squareChannel;
unique_ptr<TriangleChannel> _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)
{

View File

@ -44,9 +44,9 @@ protected:
}
public:
virtual void Reset()
virtual void Reset(bool softReset)
{
ApuLengthCounter::Reset();
ApuLengthCounter::Reset(softReset);
_constantVolume = false;
_volume = 0;

View File

@ -13,16 +13,16 @@ enum class FrameType
class ApuFrameCounter : public IMemoryHandler, public Snapshotable
{
private:
const vector<vector<uint32_t>> _stepCycles = { { { 7457, 14913, 22371, 29828, 29829, 29830},
const vector<vector<int32_t>> _stepCycles = { { { 7457, 14913, 22371, 29828, 29829, 29830},
{ 7457, 14913, 22371, 29829, 37281, 37282} } };
const vector<vector<FrameType>> _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<int32_t>(_nextIrqCycle);
Stream<uint32_t>(_previousCycle);
Stream<int32_t>(_previousCycle);
Stream<uint32_t>(_currentStep);
Stream<uint32_t>(_stepMode);
Stream<bool>(_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);
}
}

View File

@ -7,14 +7,16 @@ class ApuLengthCounter : public BaseApuChannel<15>
private:
const vector<uint8_t> _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<bool>(_enabled);
Stream<bool>(_lengthCounterHalt);
Stream<bool>(_newHaltValue);
Stream<uint8_t>(_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) {

View File

@ -9,11 +9,13 @@ class BaseApuChannel : public IMemoryHandler, public Snapshotable
{
private:
unique_ptr<Blip_Synth<blip_good_quality, range>> _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<blip_good_quality, range>());
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();

View File

@ -113,7 +113,7 @@ void Console::ResetComponents(bool softReset)
Movie::Stop();
_ppu->Reset();
_apu->Reset();
_apu->Reset(softReset);
_cpu->Reset(softReset);
_memoryManager->Reset(softReset);

View File

@ -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);

View File

@ -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;

View File

@ -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);
}
};

View File

@ -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;