2015-07-25 11:59:18 -04:00
# include "stdafx.h"
# include "DeltaModulationChannel.h"
# include "APU.h"
# include "CPU.h"
2016-12-23 13:56:45 -05:00
# include "Console.h"
2017-04-29 08:29:56 -04:00
# include "MemoryManager.h"
2015-07-25 11:59:18 -04:00
2018-07-01 15:21:05 -04:00
DeltaModulationChannel : : DeltaModulationChannel ( AudioChannel channel , shared_ptr < Console > console , SoundMixer * mixer ) : BaseApuChannel ( channel , console , mixer )
2015-07-25 11:59:18 -04:00
{
}
void DeltaModulationChannel : : Reset ( bool softReset )
{
BaseApuChannel : : Reset ( softReset ) ;
2016-06-21 18:58:22 -04:00
2019-01-26 11:38:22 -05:00
if ( ! softReset ) {
//At power on, the sample address is set to $C000 and the sample length is set to 1
//Resetting does not reset their value
_sampleAddr = 0xC000 ;
_sampleLength = 1 ;
}
2015-07-25 11:59:18 -04:00
_outputLevel = 0 ;
_irqEnabled = false ;
_loopFlag = false ;
_currentAddr = 0 ;
_bytesRemaining = 0 ;
_readBuffer = 0 ;
_bufferEmpty = true ;
_shiftRegister = 0 ;
2016-01-02 14:45:37 -05:00
_bitsRemaining = 8 ;
2015-07-25 11:59:18 -04:00
_silenceFlag = true ;
_needToRun = false ;
2016-01-03 11:42:01 -05:00
2017-07-17 19:35:16 -04:00
_lastValue4011 = 0 ;
2016-01-03 11:42:01 -05:00
//Not sure if this is accurate, but it seems to make things better rather than worse (for dpcmletterbox)
//"On the real thing, I think the power-on value is 428 (or the equivalent at least - it uses a linear feedback shift register), though only the even/oddness should matter for this test."
_period = ( GetNesModel ( ) = = NesModel : : NTSC ? _dmcPeriodLookupTableNtsc : _dmcPeriodLookupTablePal ) [ 0 ] - 1 ;
2016-05-18 20:51:54 -04:00
//Make sure the DMC doesn't tick on the first cycle - this is part of what keeps Sprite/DMC DMA tests working while fixing dmc_pitch.
_timer = _period ;
2015-07-25 11:59:18 -04:00
}
void DeltaModulationChannel : : InitSample ( )
{
_currentAddr = _sampleAddr ;
_bytesRemaining = _sampleLength ;
_needToRun = _bytesRemaining > 0 ;
}
void DeltaModulationChannel : : StartDmcTransfer ( )
{
if ( _bufferEmpty & & _bytesRemaining > 0 ) {
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > StartDmcTransfer ( ) ;
2015-07-25 11:59:18 -04:00
}
}
2019-11-10 17:35:29 -05:00
uint16_t DeltaModulationChannel : : GetDmcReadAddress ( )
{
return _currentAddr ;
}
void DeltaModulationChannel : : SetDmcReadBuffer ( uint8_t value )
2015-07-25 11:59:18 -04:00
{
if ( _bytesRemaining > 0 ) {
2019-11-10 17:35:29 -05:00
_readBuffer = value ;
2015-07-25 11:59:18 -04:00
_bufferEmpty = false ;
2019-01-26 11:45:26 -05:00
//"The address is incremented; if it exceeds $FFFF, it is wrapped around to $8000."
2015-07-25 11:59:18 -04:00
_currentAddr + + ;
2019-01-26 11:45:26 -05:00
if ( _currentAddr = = 0 ) {
_currentAddr = 0x8000 ;
}
2015-07-25 11:59:18 -04:00
_bytesRemaining - - ;
if ( _bytesRemaining = = 0 ) {
_needToRun = false ;
if ( _loopFlag ) {
//Looped sample should never set IRQ flag
InitSample ( ) ;
} else if ( _irqEnabled ) {
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > SetIrqSource ( IRQSource : : DMC ) ;
2015-07-25 11:59:18 -04:00
}
}
}
}
void DeltaModulationChannel : : Clock ( )
{
if ( ! _silenceFlag ) {
if ( _shiftRegister & 0x01 ) {
if ( _outputLevel < = 125 ) {
_outputLevel + = 2 ;
}
} else {
if ( _outputLevel > = 2 ) {
_outputLevel - = 2 ;
}
}
_shiftRegister > > = 1 ;
}
_bitsRemaining - - ;
if ( _bitsRemaining = = 0 ) {
_bitsRemaining = 8 ;
if ( _bufferEmpty ) {
_silenceFlag = true ;
} else {
_silenceFlag = false ;
_shiftRegister = _readBuffer ;
_bufferEmpty = true ;
StartDmcTransfer ( ) ;
}
}
AddOutput ( _outputLevel ) ;
}
void DeltaModulationChannel : : StreamState ( bool saving )
{
BaseApuChannel : : StreamState ( saving ) ;
2019-01-09 20:23:22 -05:00
Stream ( _sampleAddr , _sampleLength , _outputLevel , _irqEnabled , _loopFlag , _currentAddr , _bytesRemaining , _readBuffer , _bufferEmpty , _shiftRegister , _bitsRemaining , _silenceFlag , _needToRun ) ;
2015-07-25 11:59:18 -04:00
}
bool DeltaModulationChannel : : IrqPending ( uint32_t cyclesToRun )
{
if ( _irqEnabled & & _bytesRemaining > 0 ) {
uint32_t cyclesToEmptyBuffer = ( _bitsRemaining + ( _bytesRemaining - 1 ) * 8 ) * _period ;
if ( cyclesToRun > = cyclesToEmptyBuffer ) {
return true ;
}
}
return false ;
}
bool DeltaModulationChannel : : GetStatus ( )
{
return _bytesRemaining > 0 ;
}
void DeltaModulationChannel : : GetMemoryRanges ( MemoryRanges & ranges )
{
2015-07-29 22:10:34 -04:00
ranges . AddHandler ( MemoryOperation : : Write , 0x4010 , 0x4013 ) ;
2015-07-25 11:59:18 -04:00
}
void DeltaModulationChannel : : WriteRAM ( uint16_t addr , uint8_t value )
{
2018-07-01 15:21:05 -04:00
_console - > GetApu ( ) - > Run ( ) ;
2015-07-25 11:59:18 -04:00
switch ( addr & 0x03 ) {
case 0 : //4010
_irqEnabled = ( value & 0x80 ) = = 0x80 ;
_loopFlag = ( value & 0x40 ) = = 0x40 ;
//"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 = ( GetNesModel ( ) = = NesModel : : NTSC ? _dmcPeriodLookupTableNtsc : _dmcPeriodLookupTablePal ) [ value & 0x0F ] - 1 ;
if ( ! _irqEnabled ) {
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > ClearIrqSource ( IRQSource : : DMC ) ;
2015-07-25 11:59:18 -04:00
}
break ;
2016-12-04 23:31:52 -05:00
case 1 : { //4011
2017-07-17 19:35:16 -04:00
uint8_t newValue = value & 0x7F ;
2016-12-04 23:31:52 -05:00
uint8_t previousLevel = _outputLevel ;
2017-07-17 19:35:16 -04:00
_outputLevel = newValue ;
2016-12-04 23:31:52 -05:00
2018-07-13 22:19:26 -04:00
if ( _console - > GetSettings ( ) - > CheckFlag ( EmulationFlags : : ReduceDmcPopping ) & & abs ( _outputLevel - previousLevel ) > 50 ) {
2016-12-04 23:31:52 -05:00
//Reduce popping sounds for 4011 writes
_outputLevel - = ( _outputLevel - previousLevel ) / 2 ;
}
2016-01-14 01:21:09 -05:00
//4011 applies new output right away, not on the timer's reload. This fixes bad DMC sound when playing through 4011.
AddOutput ( _outputLevel ) ;
2016-12-04 23:31:52 -05:00
2017-07-17 19:35:16 -04:00
if ( _lastValue4011 ! = value & & newValue > 0 ) {
2018-07-01 15:21:05 -04:00
_console - > SetNextFrameOverclockStatus ( true ) ;
2016-06-21 18:58:22 -04:00
}
2017-07-17 19:35:16 -04:00
_lastValue4011 = newValue ;
2015-07-25 11:59:18 -04:00
break ;
2016-12-04 23:31:52 -05:00
}
2015-07-25 11:59:18 -04:00
case 2 : //4012
_sampleAddr = 0xC000 | ( ( uint32_t ) value < < 6 ) ;
2017-04-29 16:11:22 -04:00
if ( value > 0 ) {
2018-07-01 15:21:05 -04:00
_console - > SetNextFrameOverclockStatus ( false ) ;
2017-04-29 16:11:22 -04:00
}
2015-07-25 11:59:18 -04:00
break ;
case 3 : //4013
_sampleLength = ( value < < 4 ) | 0x0001 ;
2017-04-29 16:11:22 -04:00
if ( value > 0 ) {
2018-07-01 15:21:05 -04:00
_console - > SetNextFrameOverclockStatus ( false ) ;
2017-04-29 16:11:22 -04:00
}
2015-07-25 11:59:18 -04:00
break ;
}
}
void DeltaModulationChannel : : SetEnabled ( bool enabled )
{
if ( ! enabled ) {
_bytesRemaining = 0 ;
_needToRun = false ;
} else if ( _bytesRemaining = = 0 ) {
InitSample ( ) ;
2020-05-14 17:18:23 -04:00
//Delay a number of cycles based on odd/even cycles
//Allows behavior to match dmc_dma_start_test
if ( ( _console - > GetCpu ( ) - > GetCycleCount ( ) & 0x01 ) = = 0 ) {
_needInit = 2 ;
} else {
_needInit = 3 ;
}
2015-07-25 11:59:18 -04:00
}
}
bool DeltaModulationChannel : : NeedToRun ( )
{
2020-05-14 17:18:23 -04:00
if ( _needInit > 0 ) {
_needInit - - ;
if ( _needInit = = 0 ) {
StartDmcTransfer ( ) ;
}
2019-11-10 17:35:29 -05:00
}
2015-07-25 11:59:18 -04:00
return _needToRun ;
}
2017-08-30 18:31:27 -04:00
ApuDmcState DeltaModulationChannel : : GetState ( )
{
ApuDmcState state ;
state . BytesRemaining = _bytesRemaining ;
state . IrqEnabled = _irqEnabled ;
state . Loop = _loopFlag ;
state . OutputVolume = _lastOutput ;
state . Period = _period ;
2018-01-01 23:23:18 -05:00
state . Timer = _timer ;
2018-07-01 15:21:05 -04:00
state . SampleRate = ( double ) _console - > GetCpu ( ) - > GetClockRate ( GetNesModel ( ) ) / ( _period + 1 ) ;
2017-08-30 18:31:27 -04:00
state . SampleAddr = _sampleAddr ;
state . SampleLength = _sampleLength ;
return state ;
2015-07-25 11:59:18 -04:00
}