2015-07-14 21:50:14 -04:00
# pragma once
# include "stdafx.h"
# include "IMemoryHandler.h"
2015-07-21 23:05:27 -04:00
# include "EmulationSettings.h"
2015-07-14 21:50:14 -04:00
enum class FrameType
{
None = 0 ,
QuarterFrame = 1 ,
HalfFrame = 2 ,
} ;
2015-07-14 23:35:30 -04:00
class ApuFrameCounter : public IMemoryHandler , public Snapshotable
2015-07-14 21:50:14 -04:00
{
private :
2017-03-31 22:14:16 -04:00
const int32_t _stepCyclesNtsc [ 2 ] [ 6 ] = { { 7457 , 14913 , 22371 , 29828 , 29829 , 29830 } ,
{ 7457 , 14913 , 22371 , 29829 , 37281 , 37282 } } ;
const int32_t _stepCyclesPal [ 2 ] [ 6 ] = { { 8313 , 16627 , 24939 , 33252 , 33253 , 33254 } ,
{ 8313 , 16627 , 24939 , 33253 , 41565 , 41566 } } ;
const FrameType _frameType [ 2 ] [ 6 ] = { { FrameType : : QuarterFrame , FrameType : : HalfFrame , FrameType : : QuarterFrame , FrameType : : None , FrameType : : HalfFrame , FrameType : : None } ,
{ FrameType : : QuarterFrame , FrameType : : HalfFrame , FrameType : : QuarterFrame , FrameType : : None , FrameType : : HalfFrame , FrameType : : None } } ;
2018-07-01 15:21:05 -04:00
shared_ptr < Console > _console ;
2017-03-31 22:14:16 -04:00
int32_t _stepCycles [ 2 ] [ 6 ] ;
2015-07-21 23:05:27 -04:00
NesModel _nesModel ;
2015-07-19 01:30:13 -04:00
int32_t _previousCycle ;
uint32_t _currentStep ;
uint32_t _stepMode ; //0: 4-step mode, 1: 5-step mode
bool _inhibitIRQ ;
2016-05-18 20:42:26 -04:00
uint8_t _blockFrameCounterTick ;
int16_t _newValue ;
int8_t _writeDelayCounter ;
2015-07-14 21:50:14 -04:00
public :
2018-07-01 15:21:05 -04:00
ApuFrameCounter ( shared_ptr < Console > console )
2015-07-14 21:50:14 -04:00
{
2018-07-01 15:21:05 -04:00
_console = console ;
2015-07-19 01:30:13 -04:00
Reset ( false ) ;
2015-07-14 21:50:14 -04:00
}
2018-07-01 15:21:05 -04:00
2015-07-19 01:30:13 -04:00
void Reset ( bool softReset )
2015-07-14 23:35:30 -04:00
{
2017-12-22 23:14:17 -05:00
_nesModel = NesModel : : Auto ;
2017-03-10 21:29:48 -05:00
_previousCycle = 0 ;
2015-07-19 01:30:13 -04:00
//"After reset: APU mode in $4017 was unchanged", so we need to keep whatever value _stepMode has for soft resets
if ( ! softReset ) {
_stepMode = 0 ;
}
2015-07-14 23:35:30 -04:00
_currentStep = 0 ;
2017-03-10 21:29:48 -05:00
//"After reset or power-up, APU acts as if $4017 were written with $00 from 9 to 12 clocks before first instruction begins."
//This is emulated in the CPU::Reset function
//Reset acts as if $00 was written to $4017
2017-03-31 22:18:38 -04:00
_newValue = _stepMode ? 0x80 : 0x00 ;
2017-03-10 21:29:48 -05:00
_writeDelayCounter = 3 ;
2015-07-14 23:35:30 -04:00
_inhibitIRQ = false ;
2016-05-18 20:42:26 -04:00
_blockFrameCounterTick = 0 ;
2015-07-14 23:35:30 -04:00
}
2016-12-17 23:14:47 -05:00
void StreamState ( bool saving ) override
2015-07-14 23:35:30 -04:00
{
2019-11-10 17:35:29 -05:00
Stream ( _previousCycle , _currentStep , _stepMode , _inhibitIRQ , _nesModel , _blockFrameCounterTick , _writeDelayCounter , _newValue ) ;
2015-07-21 23:05:27 -04:00
if ( ! saving ) {
SetNesModel ( _nesModel ) ;
}
}
void SetNesModel ( NesModel model )
{
2017-03-31 22:14:16 -04:00
if ( _nesModel ! = model ) {
2015-07-21 23:05:27 -04:00
_nesModel = model ;
2016-01-30 19:33:32 -05:00
switch ( model ) {
2018-06-25 15:56:05 -04:00
case NesModel : : Auto :
//Auto should never be set here
break ;
2016-01-30 19:33:32 -05:00
case NesModel : : NTSC :
case NesModel : : Dendy :
2017-03-31 22:14:16 -04:00
memcpy ( _stepCycles , _stepCyclesNtsc , sizeof ( _stepCycles ) ) ;
2016-01-30 19:33:32 -05:00
break ;
case NesModel : : PAL :
2017-03-31 22:14:16 -04:00
memcpy ( _stepCycles , _stepCyclesPal , sizeof ( _stepCycles ) ) ;
2016-01-30 19:33:32 -05:00
break ;
}
2015-07-21 23:05:27 -04:00
}
}
2015-07-14 23:35:30 -04:00
2015-07-19 01:30:13 -04:00
uint32_t Run ( int32_t & cyclesToRun )
2015-07-14 21:50:14 -04:00
{
uint32_t cyclesRan ;
2016-05-18 20:42:26 -04:00
2015-07-14 21:50:14 -04:00
if ( _previousCycle + cyclesToRun > = _stepCycles [ _stepMode ] [ _currentStep ] ) {
if ( ! _inhibitIRQ & & _stepMode = = 0 & & _currentStep > = 3 ) {
//Set irq on the last 3 cycles for 4-step mode
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > SetIrqSource ( IRQSource : : FrameCounter ) ;
2015-07-14 21:50:14 -04:00
}
FrameType type = _frameType [ _stepMode ] [ _currentStep ] ;
2016-05-18 20:42:26 -04:00
if ( type ! = FrameType : : None & & ! _blockFrameCounterTick ) {
2018-07-01 15:21:05 -04:00
_console - > GetApu ( ) - > FrameCounterTick ( type ) ;
2016-05-18 20:42:26 -04:00
//Do not allow writes to 4017 to clock the frame counter for the next cycle (i.e this odd cycle + the following even cycle)
_blockFrameCounterTick = 2 ;
2015-07-14 21:50:14 -04:00
}
2016-06-01 20:11:28 -04:00
if ( _stepCycles [ _stepMode ] [ _currentStep ] < _previousCycle ) {
//This can happen when switching from PAL to NTSC, which can cause a freeze (endless loop in APU)
cyclesRan = 0 ;
} else {
cyclesRan = _stepCycles [ _stepMode ] [ _currentStep ] - _previousCycle ;
}
2015-07-14 21:50:14 -04:00
cyclesToRun - = cyclesRan ;
_currentStep + + ;
if ( _currentStep = = 6 ) {
_currentStep = 0 ;
_previousCycle = 0 ;
} else {
_previousCycle + = cyclesRan ;
}
} else {
cyclesRan = cyclesToRun ;
cyclesToRun = 0 ;
_previousCycle + = cyclesRan ;
}
2016-05-18 20:42:26 -04:00
if ( _newValue > = 0 ) {
_writeDelayCounter - - ;
if ( _writeDelayCounter = = 0 ) {
//Apply new value after the appropriate number of cycles has elapsed
_stepMode = ( ( _newValue & 0x80 ) = = 0x80 ) ? 1 : 0 ;
_writeDelayCounter = - 1 ;
_currentStep = 0 ;
_previousCycle = 0 ;
_newValue = - 1 ;
if ( _stepMode & & ! _blockFrameCounterTick ) {
//"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."
2018-07-01 15:21:05 -04:00
_console - > GetApu ( ) - > FrameCounterTick ( FrameType : : HalfFrame ) ;
2016-05-18 20:42:26 -04:00
_blockFrameCounterTick = 2 ;
}
}
}
if ( _blockFrameCounterTick > 0 ) {
_blockFrameCounterTick - - ;
}
2015-07-14 21:50:14 -04:00
return cyclesRan ;
}
2019-01-25 20:39:22 -05:00
bool NeedToRun ( uint32_t cyclesToRun )
2015-07-14 21:50:14 -04:00
{
2019-01-25 20:39:22 -05:00
//Run APU when:
// -A new value is pending
// -The "blockFrameCounterTick" process is running
// -We're at the before-last or last tick of the current step
return _newValue > = 0 | | _blockFrameCounterTick > 0 | | ( _previousCycle + ( int32_t ) cyclesToRun > = _stepCycles [ _stepMode ] [ _currentStep ] - 1 ) ;
2015-07-14 21:50:14 -04:00
}
2016-12-17 23:14:47 -05:00
void GetMemoryRanges ( MemoryRanges & ranges ) override
2015-07-14 21:50:14 -04:00
{
2015-07-29 22:10:34 -04:00
ranges . AddHandler ( MemoryOperation : : Write , 0x4017 ) ;
2015-07-14 21:50:14 -04:00
}
2016-12-17 23:14:47 -05:00
uint8_t ReadRAM ( uint16_t addr ) override
2015-07-14 21:50:14 -04:00
{
return 0 ;
}
2016-12-17 23:14:47 -05:00
void WriteRAM ( uint16_t addr , uint8_t value ) override
2015-07-14 21:50:14 -04:00
{
2018-07-01 15:21:05 -04:00
_console - > GetApu ( ) - > Run ( ) ;
2016-05-18 20:42:26 -04:00
_newValue = value ;
2015-07-14 21:50:14 -04:00
2015-07-19 01:30:13 -04:00
//Reset sequence after $4017 is written to
2018-07-01 15:21:05 -04:00
if ( _console - > GetCpu ( ) - > GetCycleCount ( ) & 0x01 ) {
2015-07-19 01:30:13 -04:00
//"If the write occurs between APU cycles, the effects occur 4 CPU cycles after the write cycle. "
2016-05-18 20:42:26 -04:00
_writeDelayCounter = 4 ;
2019-11-10 17:35:29 -05:00
} else {
//"If the write occurs during an APU cycle, the effects occur 3 CPU cycles after the $4017 write cycle"
_writeDelayCounter = 3 ;
2015-07-19 01:30:13 -04:00
}
2015-07-14 21:50:14 -04:00
2016-05-18 20:42:26 -04:00
_inhibitIRQ = ( value & 0x40 ) = = 0x40 ;
if ( _inhibitIRQ ) {
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > ClearIrqSource ( IRQSource : : FrameCounter ) ;
2015-07-14 21:50:14 -04:00
}
}
2017-08-30 18:31:27 -04:00
ApuFrameCounterState GetState ( )
{
ApuFrameCounterState state ;
state . IrqEnabled = ! _inhibitIRQ ;
2018-01-01 23:23:18 -05:00
state . SequencePosition = std : : min < uint8_t > ( _currentStep , _stepMode ? 5 : 4 ) ;
2017-08-30 18:31:27 -04:00
state . FiveStepMode = _stepMode = = 1 ;
return state ;
}
2015-07-14 21:50:14 -04:00
} ;