mirror of
https://github.com/libretro/Mesen.git
synced 2025-02-21 00:51:02 +00:00
Improved APU accuracy - passes most APU tests
This commit is contained in:
parent
af749cb6ad
commit
84f735b6a4
34
Core/APU.cpp
34
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)
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -44,9 +44,9 @@ protected:
|
||||
}
|
||||
|
||||
public:
|
||||
virtual void Reset()
|
||||
virtual void Reset(bool softReset)
|
||||
{
|
||||
ApuLengthCounter::Reset();
|
||||
ApuLengthCounter::Reset(softReset);
|
||||
|
||||
_constantVolume = false;
|
||||
_volume = 0;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -113,7 +113,7 @@ void Console::ResetComponents(bool softReset)
|
||||
Movie::Stop();
|
||||
|
||||
_ppu->Reset();
|
||||
_apu->Reset();
|
||||
_apu->Reset(softReset);
|
||||
_cpu->Reset(softReset);
|
||||
_memoryManager->Reset(softReset);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user