2017-06-13 14:43:34 +00:00
// TODO - so many integers in the square wave output keep us from exactly unbiasing the waveform. also other waves probably. consider improving the unbiasing.
// ALSO - consider whether we should even be doing it: the nonlinear-mixing behaviour probably depends on those biases being there.
// if we have a better high-pass filter somewhere then we might could cope with the weird biases
// (mix higher integer precision with the non-linear mixer and then highpass filter befoure outputting s16s)
2012-07-15 00:09:34 +00:00
2017-06-13 14:43:34 +00:00
// http://wiki.nesdev.com/w/index.php/APU_Mixer_Emulation
// http://wiki.nesdev.com/w/index.php/APU
// http://wiki.nesdev.com/w/index.php/APU_Pulse
// sequencer ref: http://wiki.nesdev.com/w/index.php/APU_Frame_Counter
2011-03-13 00:34:24 +00:00
2017-06-13 14:43:34 +00:00
// TODO - refactor length counter to be separate component
2011-06-10 05:02:06 +00:00
2013-11-14 13:15:41 +00:00
using System ;
using System.Collections.Generic ;
2013-10-27 22:07:40 +00:00
using BizHawk.Common ;
2014-07-03 15:35:50 +00:00
using BizHawk.Common.NumberExtensions ;
2012-07-15 00:09:34 +00:00
2013-11-14 13:15:41 +00:00
namespace BizHawk.Emulation.Cores.Nintendo.NES
2011-03-13 00:34:24 +00:00
{
2016-06-29 13:37:47 +00:00
public sealed class APU
2011-03-13 00:34:24 +00:00
{
2017-09-07 14:27:36 +00:00
public int m_vol = 1 ;
2012-07-12 23:13:22 +00:00
2016-06-29 13:37:47 +00:00
public int dmc_dma_countdown = - 1 ;
public bool call_from_write ;
2016-06-21 13:20:52 +00:00
2015-01-16 02:07:24 +00:00
public bool recalculate = false ;
2012-12-09 03:13:47 +00:00
2015-01-16 02:07:24 +00:00
NES nes ;
public APU ( NES nes , APU old , bool pal )
{
this . nes = nes ;
dmc = new DMCUnit ( this , pal ) ;
2017-04-20 21:26:49 +00:00
if ( pal )
{
sequencer_lut = sequencer_lut_pal ;
}
else
{
sequencer_lut = sequencer_lut_ntsc ;
}
2015-01-16 02:07:24 +00:00
noise = new NoiseUnit ( this , pal ) ;
triangle = new TriangleUnit ( this ) ;
pulse [ 0 ] = new PulseUnit ( this , 0 ) ;
pulse [ 1 ] = new PulseUnit ( this , 1 ) ;
if ( old ! = null )
2011-03-13 00:34:24 +00:00
{
2017-09-07 14:27:36 +00:00
m_vol = old . m_vol ;
2011-03-13 00:34:24 +00:00
}
2015-01-16 02:07:24 +00:00
}
static int [ ] DMC_RATE_NTSC = { 428 , 380 , 340 , 320 , 286 , 254 , 226 , 214 , 190 , 160 , 142 , 128 , 106 , 84 , 72 , 54 } ;
static int [ ] DMC_RATE_PAL = { 398 , 354 , 316 , 298 , 276 , 236 , 210 , 198 , 176 , 148 , 132 , 118 , 98 , 78 , 66 , 50 } ;
static int [ ] LENGTH_TABLE = { 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 } ;
static byte [ , ] PULSE_DUTY = {
2017-06-13 14:43:34 +00:00
{ 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 } , // (12.5%)
{ 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 } , // (25%)
{ 0 , 1 , 1 , 1 , 1 , 0 , 0 , 0 } , // (50%)
{ 1 , 0 , 0 , 1 , 1 , 1 , 1 , 1 } , // (25% negated (75%))
2015-01-16 02:07:24 +00:00
} ;
2016-06-29 13:37:47 +00:00
static byte [ ] TRIANGLE_TABLE =
2015-01-16 02:07:24 +00:00
{
15 , 14 , 13 , 12 , 11 , 10 , 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 ,
2017-04-20 21:26:49 +00:00
0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15
2015-01-16 02:07:24 +00:00
} ;
2016-06-29 13:37:47 +00:00
static int [ ] NOISE_TABLE_NTSC =
2015-01-16 02:07:24 +00:00
{
4 , 8 , 16 , 32 , 64 , 96 , 128 , 160 , 202 , 254 , 380 , 508 , 762 , 1016 , 2034 , 4068
} ;
2016-06-29 13:37:47 +00:00
static int [ ] NOISE_TABLE_PAL =
2015-01-16 02:07:24 +00:00
{
4 , 7 , 14 , 30 , 60 , 88 , 118 , 148 , 188 , 236 , 354 , 472 , 708 , 944 , 1890 , 3778
} ;
2011-03-13 00:34:24 +00:00
2015-01-16 02:07:24 +00:00
2015-05-08 00:56:46 +00:00
public sealed class PulseUnit
2015-01-16 02:07:24 +00:00
{
public PulseUnit ( APU apu , int unit ) { this . unit = unit ; this . apu = apu ; }
public int unit ;
APU apu ;
2017-06-13 14:43:34 +00:00
// reg0
2015-01-16 02:07:24 +00:00
int duty_cnt , env_loop , env_constant , env_cnt_value ;
2016-07-01 21:43:09 +00:00
public bool len_halt ;
2017-06-13 14:43:34 +00:00
// reg1
2015-01-16 02:07:24 +00:00
int sweep_en , sweep_divider_cnt , sweep_negate , sweep_shiftcount ;
bool sweep_reload ;
2017-06-13 14:43:34 +00:00
// reg2/3
2015-01-16 02:07:24 +00:00
int len_cnt ;
2015-05-08 00:56:46 +00:00
public int timer_raw_reload_value , timer_reload_value ;
2015-01-16 02:07:24 +00:00
2017-06-13 14:43:34 +00:00
// misc..
2015-01-16 02:07:24 +00:00
int lenctr_en ;
public void SyncState ( Serializer ser )
2012-12-03 19:55:14 +00:00
{
2015-01-16 02:07:24 +00:00
ser . BeginSection ( "Pulse" + unit ) ;
ser . Sync ( "duty_cnt" , ref duty_cnt ) ;
ser . Sync ( "env_loop" , ref env_loop ) ;
ser . Sync ( "env_constant" , ref env_constant ) ;
ser . Sync ( "env_cnt_value" , ref env_cnt_value ) ;
2016-07-01 21:43:09 +00:00
ser . Sync ( "len_halt" , ref len_halt ) ;
2015-01-16 02:07:24 +00:00
ser . Sync ( "sweep_en" , ref sweep_en ) ;
ser . Sync ( "sweep_divider_cnt" , ref sweep_divider_cnt ) ;
ser . Sync ( "sweep_negate" , ref sweep_negate ) ;
ser . Sync ( "sweep_shiftcount" , ref sweep_shiftcount ) ;
ser . Sync ( "sweep_reload" , ref sweep_reload ) ;
ser . Sync ( "len_cnt" , ref len_cnt ) ;
ser . Sync ( "timer_raw_reload_value" , ref timer_raw_reload_value ) ;
ser . Sync ( "timer_reload_value" , ref timer_reload_value ) ;
ser . Sync ( "lenctr_en" , ref lenctr_en ) ;
ser . Sync ( "swp_divider_counter" , ref swp_divider_counter ) ;
ser . Sync ( "swp_silence" , ref swp_silence ) ;
ser . Sync ( "duty_step" , ref duty_step ) ;
ser . Sync ( "timer_counter" , ref timer_counter ) ;
ser . Sync ( "sample" , ref sample ) ;
ser . Sync ( "duty_value" , ref duty_value ) ;
ser . Sync ( "env_start_flag" , ref env_start_flag ) ;
ser . Sync ( "env_divider" , ref env_divider ) ;
ser . Sync ( "env_counter" , ref env_counter ) ;
ser . Sync ( "env_output" , ref env_output ) ;
ser . EndSection ( ) ;
}
2011-03-13 00:34:24 +00:00
2015-01-16 02:07:24 +00:00
public bool IsLenCntNonZero ( ) { return len_cnt > 0 ; }
2012-03-25 09:25:27 +00:00
2015-01-16 02:07:24 +00:00
public void WriteReg ( int addr , byte val )
2011-03-13 00:34:24 +00:00
{
2017-06-13 14:43:34 +00:00
// Console.WriteLine("write pulse {0:X} {1:X}", addr, val);
2015-01-16 02:07:24 +00:00
switch ( addr )
2011-06-10 05:02:06 +00:00
{
2015-01-16 02:07:24 +00:00
case 0 :
env_cnt_value = val & 0xF ;
env_constant = ( val > > 4 ) & 1 ;
env_loop = ( val > > 5 ) & 1 ;
duty_cnt = ( val > > 6 ) & 3 ;
break ;
case 1 :
sweep_shiftcount = val & 7 ;
sweep_negate = ( val > > 3 ) & 1 ;
sweep_divider_cnt = ( val > > 4 ) & 7 ;
sweep_en = ( val > > 7 ) & 1 ;
sweep_reload = true ;
break ;
case 2 :
2016-07-03 01:33:37 +00:00
timer_reload_value = ( timer_reload_value & 0x700 ) | val ;
2015-01-16 02:07:24 +00:00
timer_raw_reload_value = timer_reload_value * 2 + 2 ;
2017-06-13 14:43:34 +00:00
// if (unit == 1) Console.WriteLine("{0} timer_reload_value: {1}", unit, timer_reload_value);
2015-01-16 02:07:24 +00:00
break ;
case 3 :
2016-07-02 02:31:06 +00:00
if ( apu . len_clock_active )
{
if ( len_cnt = = 0 )
{
len_cnt = LENGTH_TABLE [ ( val > > 3 ) & 0x1F ] + 1 ;
}
} else
{
len_cnt = LENGTH_TABLE [ ( val > > 3 ) & 0x1F ] ;
}
2015-01-16 02:07:24 +00:00
timer_reload_value = ( timer_reload_value & 0xFF ) | ( ( val & 0x07 ) < < 8 ) ;
timer_raw_reload_value = timer_reload_value * 2 + 2 ;
2016-07-03 01:33:37 +00:00
duty_step = 0 ;
2015-01-16 02:07:24 +00:00
env_start_flag = 1 ;
2011-06-10 05:02:06 +00:00
2017-06-13 14:43:34 +00:00
// allow the lenctr_en to kill the len_cnt
2015-01-16 02:07:24 +00:00
set_lenctr_en ( lenctr_en ) ;
2011-03-13 00:34:24 +00:00
2017-06-13 14:43:34 +00:00
// serves as a useful note-on diagnostic
// if(unit==1) Console.WriteLine("{0} timer_reload_value: {1}", unit, timer_reload_value);
2015-01-16 02:07:24 +00:00
break ;
2011-03-13 00:34:24 +00:00
}
2015-01-16 02:07:24 +00:00
}
2011-03-13 00:34:24 +00:00
2015-01-16 02:07:24 +00:00
public void set_lenctr_en ( int value )
{
lenctr_en = value ;
2017-06-13 14:43:34 +00:00
// if the length counter is not enabled, then we must disable the length system in this way
2015-01-16 02:07:24 +00:00
if ( lenctr_en = = 0 ) len_cnt = 0 ;
}
2011-03-19 09:12:56 +00:00
2017-06-13 14:43:34 +00:00
// state
2015-01-16 02:07:24 +00:00
int swp_divider_counter ;
bool swp_silence ;
int duty_step ;
int timer_counter ;
public int sample ;
bool duty_value ;
2011-03-13 08:13:32 +00:00
2015-05-08 00:56:46 +00:00
int env_start_flag , env_divider , env_counter ;
public int env_output ;
2011-03-13 00:34:24 +00:00
2015-01-16 02:07:24 +00:00
public void clock_length_and_sweep ( )
{
2017-06-13 14:43:34 +00:00
// this should be optimized to update only when `timer_reload_value` changes
2015-01-16 02:07:24 +00:00
int sweep_shifter = timer_reload_value > > sweep_shiftcount ;
if ( sweep_negate = = 1 )
2016-07-03 01:33:37 +00:00
sweep_shifter = - sweep_shifter + unit ;
2015-01-16 02:07:24 +00:00
sweep_shifter + = timer_reload_value ;
2011-03-13 00:34:24 +00:00
2017-06-13 14:43:34 +00:00
// this sweep logic is always enabled:
2016-07-03 01:33:37 +00:00
swp_silence = ( timer_reload_value < 8 | | ( sweep_shifter > 0x7FF ) ) ; // && sweep_negate == 0));
2011-03-13 00:34:24 +00:00
2017-06-13 14:43:34 +00:00
// does enable only block the pitch bend? does the clocking proceed?
2015-01-16 02:07:24 +00:00
if ( sweep_en = = 1 )
{
2017-06-13 14:43:34 +00:00
// clock divider
2015-01-16 02:07:24 +00:00
if ( swp_divider_counter ! = 0 ) swp_divider_counter - - ;
if ( swp_divider_counter = = 0 )
2011-03-19 09:12:56 +00:00
{
2015-01-16 02:07:24 +00:00
swp_divider_counter = sweep_divider_cnt + 1 ;
2011-03-19 09:12:56 +00:00
2017-06-13 14:43:34 +00:00
// divider was clocked: process sweep pitch bend
2015-01-16 02:07:24 +00:00
if ( sweep_shiftcount ! = 0 & & ! swp_silence )
2011-03-19 09:12:56 +00:00
{
2015-01-16 02:07:24 +00:00
timer_reload_value = sweep_shifter ;
timer_raw_reload_value = ( timer_reload_value < < 1 ) + 2 ;
2011-03-19 09:12:56 +00:00
}
2017-06-13 14:43:34 +00:00
// TODO - does this change the user's reload value or the latched reload value?
2011-03-19 09:12:56 +00:00
}
2012-03-25 09:25:27 +00:00
2017-06-13 14:43:34 +00:00
// handle divider reload, after clocking happens
2015-01-16 02:07:24 +00:00
if ( sweep_reload )
{
swp_divider_counter = sweep_divider_cnt + 1 ;
sweep_reload = false ;
}
2011-03-13 00:34:24 +00:00
}
2017-06-13 14:43:34 +00:00
// env_loopdoubles as "halt length counter"
2016-07-01 21:43:09 +00:00
if ( ( env_loop = = 0 | | len_halt ) & & len_cnt > 0 )
2015-01-16 02:07:24 +00:00
len_cnt - - ;
}
public void clock_env ( )
{
if ( env_start_flag = = 1 )
{
env_start_flag = 0 ;
2016-07-03 01:33:37 +00:00
env_divider = env_cnt_value ;
2015-01-16 02:07:24 +00:00
env_counter = 15 ;
}
else
2011-03-13 00:34:24 +00:00
{
2016-07-03 01:33:37 +00:00
if ( env_divider ! = 0 )
2011-03-13 08:13:32 +00:00
{
2016-07-03 01:33:37 +00:00
env_divider - - ;
} else if ( env_divider = = 0 )
{
env_divider = env_cnt_value ;
2015-01-16 02:07:24 +00:00
if ( env_counter = = 0 )
2011-03-13 08:13:32 +00:00
{
2015-01-16 02:07:24 +00:00
if ( env_loop = = 1 )
2011-03-13 08:13:32 +00:00
{
2015-01-16 02:07:24 +00:00
env_counter = 15 ;
2011-03-13 08:13:32 +00:00
}
}
2015-01-16 02:07:24 +00:00
else env_counter - - ;
2011-03-13 08:13:32 +00:00
}
2011-03-13 00:34:24 +00:00
}
2015-01-16 02:07:24 +00:00
}
2011-03-13 00:34:24 +00:00
2015-01-16 02:07:24 +00:00
public void Run ( )
{
if ( env_constant = = 1 )
env_output = env_cnt_value ;
else env_output = env_counter ;
2011-09-24 17:14:55 +00:00
2015-01-16 02:07:24 +00:00
if ( timer_counter > 0 ) timer_counter - - ;
if ( timer_counter = = 0 & & timer_raw_reload_value ! = 0 )
{
2016-07-03 01:33:37 +00:00
if ( duty_step = = 7 )
{
duty_step = 0 ;
} else
{
duty_step + + ;
}
2015-01-16 02:07:24 +00:00
duty_value = PULSE_DUTY [ duty_cnt , duty_step ] = = 1 ;
2017-06-13 14:43:34 +00:00
// reload timer
2015-01-16 02:07:24 +00:00
timer_counter = timer_raw_reload_value ;
}
2012-12-09 03:13:47 +00:00
2015-01-16 02:07:24 +00:00
int newsample ;
2012-12-09 03:13:47 +00:00
2017-06-13 14:43:34 +00:00
if ( duty_value ) // high state of duty cycle
2015-01-16 02:07:24 +00:00
{
newsample = env_output ;
if ( swp_silence | | len_cnt = = 0 )
newsample = 0 ; // silenced
}
else
2017-06-13 14:43:34 +00:00
newsample = 0 ; // duty cycle is 0, silenced.
2011-03-13 00:34:24 +00:00
2017-06-13 14:43:34 +00:00
// newsample -= env_output >> 1; //unbias
2015-01-16 02:07:24 +00:00
if ( newsample ! = sample )
{
apu . recalculate = true ;
sample = newsample ;
2011-03-13 00:34:24 +00:00
}
2011-03-13 08:13:32 +00:00
}
2015-05-08 00:56:46 +00:00
public bool Debug_IsSilenced
{
get
{
if ( swp_silence | | len_cnt = = 0 )
return true ;
else return false ;
}
}
public int Debug_DutyType
{
get
{
return duty_cnt ;
}
}
public int Debug_Volume
{
get
{
return env_output ;
}
}
2015-01-16 02:07:24 +00:00
}
2011-03-13 08:13:32 +00:00
2015-05-08 00:56:46 +00:00
public sealed class NoiseUnit
2015-01-16 02:07:24 +00:00
{
APU apu ;
2012-12-09 03:13:47 +00:00
2017-06-13 14:43:34 +00:00
// reg0 (sweep)
2015-01-16 02:07:24 +00:00
int env_cnt_value , env_loop , env_constant ;
2016-07-01 21:43:09 +00:00
public bool len_halt ;
2011-03-20 04:43:27 +00:00
2017-06-13 14:43:34 +00:00
// reg2 (mode and period)
2015-01-16 02:07:24 +00:00
int mode_cnt , period_cnt ;
2011-03-20 04:43:27 +00:00
2017-06-13 14:43:34 +00:00
// reg3 (length counter and envelop trigger)
2015-01-16 02:07:24 +00:00
int len_cnt ;
2011-03-20 04:43:27 +00:00
2017-06-13 14:43:34 +00:00
// set from apu:
2015-01-16 02:07:24 +00:00
int lenctr_en ;
2011-03-20 04:43:27 +00:00
2017-06-13 14:43:34 +00:00
// state
2015-01-16 02:07:24 +00:00
int shift_register = 1 ;
int timer_counter ;
public int sample ;
int env_output , env_start_flag , env_divider , env_counter ;
bool noise_bit = true ;
2011-03-20 04:43:27 +00:00
2015-01-16 02:07:24 +00:00
int [ ] NOISE_TABLE ;
2012-12-03 19:55:14 +00:00
2015-01-16 02:07:24 +00:00
public NoiseUnit ( APU apu , bool pal )
{
this . apu = apu ;
NOISE_TABLE = pal ? NOISE_TABLE_PAL : NOISE_TABLE_NTSC ;
}
2012-12-03 19:55:14 +00:00
2015-05-08 00:56:46 +00:00
public bool Debug_IsSilenced
{
get
{
if ( len_cnt = = 0 ) return true ;
else return false ;
}
}
public int Debug_Period
{
get
{
return period_cnt ;
}
}
public int Debug_Volume
{
get
{
return env_output ;
}
}
2015-01-16 02:07:24 +00:00
public void SyncState ( Serializer ser )
{
ser . BeginSection ( "Noise" ) ;
ser . Sync ( "env_cnt_value" , ref env_cnt_value ) ;
ser . Sync ( "env_loop" , ref env_loop ) ;
ser . Sync ( "env_constant" , ref env_constant ) ;
ser . Sync ( "mode_cnt" , ref mode_cnt ) ;
ser . Sync ( "period_cnt" , ref period_cnt ) ;
2016-07-01 21:43:09 +00:00
ser . Sync ( "len_halt" , ref len_halt ) ;
2015-01-16 02:07:24 +00:00
ser . Sync ( "len_cnt" , ref len_cnt ) ;
ser . Sync ( "lenctr_en" , ref lenctr_en ) ;
ser . Sync ( "shift_register" , ref shift_register ) ;
ser . Sync ( "timer_counter" , ref timer_counter ) ;
ser . Sync ( "sample" , ref sample ) ;
ser . Sync ( "env_output" , ref env_output ) ;
ser . Sync ( "env_start_flag" , ref env_start_flag ) ;
ser . Sync ( "env_divider" , ref env_divider ) ;
ser . Sync ( "env_counter" , ref env_counter ) ;
ser . Sync ( "noise_bit" , ref noise_bit ) ;
ser . EndSection ( ) ;
}
2011-06-10 05:02:06 +00:00
2015-01-16 02:07:24 +00:00
public bool IsLenCntNonZero ( ) { return len_cnt > 0 ; }
2011-03-20 04:43:27 +00:00
2015-01-16 02:07:24 +00:00
public void WriteReg ( int addr , byte val )
{
switch ( addr )
2011-03-20 04:43:27 +00:00
{
2015-01-16 02:07:24 +00:00
case 0 :
env_cnt_value = val & 0xF ;
env_constant = ( val > > 4 ) & 1 ;
2017-06-13 14:43:34 +00:00
// we want to delay a halt until after a length clock if they happen on the same cycle
2016-07-01 21:43:09 +00:00
if ( env_loop = = 0 & & ( ( val > > 5 ) & 1 ) = = 1 )
{
len_halt = true ;
}
2015-01-16 02:07:24 +00:00
env_loop = ( val > > 5 ) & 1 ;
break ;
case 1 :
break ;
case 2 :
period_cnt = NOISE_TABLE [ val & 0xF ] ;
mode_cnt = ( val > > 7 ) & 1 ;
2017-06-13 14:43:34 +00:00
// Console.WriteLine("noise period: {0}, vol: {1}", (val & 0xF), env_cnt_value);
2015-01-16 02:07:24 +00:00
break ;
case 3 :
2016-07-02 02:31:06 +00:00
if ( apu . len_clock_active )
{
if ( len_cnt = = 0 )
{
len_cnt = LENGTH_TABLE [ ( val > > 3 ) & 0x1F ] + 1 ;
}
}
else
{
len_cnt = LENGTH_TABLE [ ( val > > 3 ) & 0x1F ] ;
}
2015-01-16 02:07:24 +00:00
set_lenctr_en ( lenctr_en ) ;
env_start_flag = 1 ;
break ;
2011-03-20 04:43:27 +00:00
}
2015-01-16 02:07:24 +00:00
}
2011-03-20 04:43:27 +00:00
2015-01-16 02:07:24 +00:00
public void set_lenctr_en ( int value )
{
lenctr_en = value ;
2017-06-13 14:43:34 +00:00
// Console.WriteLine("noise lenctr_en: " + lenctr_en);
// if the length counter is not enabled, then we must disable the length system in this way
2015-01-16 02:07:24 +00:00
if ( lenctr_en = = 0 ) len_cnt = 0 ;
}
public void clock_env ( )
{
if ( env_start_flag = = 1 )
2011-03-20 04:43:27 +00:00
{
2015-01-16 02:07:24 +00:00
env_start_flag = 0 ;
env_divider = ( env_cnt_value + 1 ) ;
env_counter = 15 ;
2011-03-20 04:43:27 +00:00
}
2015-01-16 02:07:24 +00:00
else
2011-03-20 04:43:27 +00:00
{
2015-01-16 02:07:24 +00:00
if ( env_divider ! = 0 ) env_divider - - ;
if ( env_divider = = 0 )
2011-03-20 04:43:27 +00:00
{
env_divider = ( env_cnt_value + 1 ) ;
2015-01-16 02:07:24 +00:00
if ( env_counter = = 0 )
2011-03-20 04:43:27 +00:00
{
2015-01-16 02:07:24 +00:00
if ( env_loop = = 1 )
2011-03-20 04:43:27 +00:00
{
2015-01-16 02:07:24 +00:00
env_counter = 15 ;
2011-03-20 04:43:27 +00:00
}
}
2015-01-16 02:07:24 +00:00
else env_counter - - ;
2011-03-20 04:43:27 +00:00
}
2011-09-24 17:14:55 +00:00
}
2015-01-16 02:07:24 +00:00
}
2017-06-08 18:35:13 +00:00
2015-01-16 02:07:24 +00:00
public void clock_length_and_sweep ( )
{
2011-03-20 04:43:27 +00:00
2016-07-01 21:43:09 +00:00
if ( len_cnt > 0 & & ( env_loop = = 0 | | len_halt ) )
2015-01-16 02:07:24 +00:00
len_cnt - - ;
2011-03-20 04:43:27 +00:00
}
2015-01-16 02:07:24 +00:00
public void Run ( )
2011-03-13 08:13:32 +00:00
{
2015-01-16 02:07:24 +00:00
if ( env_constant = = 1 )
env_output = env_cnt_value ;
else env_output = env_counter ;
2011-06-10 05:02:06 +00:00
2015-01-16 02:07:24 +00:00
if ( timer_counter > 0 ) timer_counter - - ;
if ( timer_counter = = 0 & & period_cnt ! = 0 )
2011-06-10 05:02:06 +00:00
{
2017-06-13 14:43:34 +00:00
// reload timer
2015-01-16 02:07:24 +00:00
timer_counter = period_cnt ;
int feedback_bit ;
if ( mode_cnt = = 1 ) feedback_bit = ( shift_register > > 6 ) & 1 ;
else feedback_bit = ( shift_register > > 1 ) & 1 ;
int feedback = feedback_bit ^ ( shift_register & 1 ) ;
shift_register > > = 1 ;
shift_register & = ~ ( 1 < < 14 ) ;
shift_register | = ( feedback < < 14 ) ;
noise_bit = ( shift_register & 1 ) ! = 0 ;
2011-06-10 05:02:06 +00:00
}
2011-03-13 08:13:32 +00:00
2015-01-16 02:07:24 +00:00
int newsample ;
if ( len_cnt = = 0 ) newsample = 0 ;
else if ( noise_bit ) newsample = env_output ; // switched, was 0?
else newsample = 0 ;
if ( newsample ! = sample )
2011-03-13 08:13:32 +00:00
{
2015-01-16 02:07:24 +00:00
apu . recalculate = true ;
sample = newsample ;
2011-03-13 08:13:32 +00:00
}
2015-01-16 02:07:24 +00:00
}
}
2011-03-13 08:13:32 +00:00
2015-05-08 00:56:46 +00:00
public sealed class TriangleUnit
2015-01-16 02:07:24 +00:00
{
2017-06-13 14:43:34 +00:00
// reg0
2015-01-16 02:07:24 +00:00
int linear_counter_reload , control_flag ;
2017-06-13 14:43:34 +00:00
// reg1 (n/a)
// reg2/3
2018-03-07 12:58:37 +00:00
int timer_cnt , reload_flag , len_cnt ;
2016-07-01 21:43:09 +00:00
public bool halt_2 ;
2017-06-13 14:43:34 +00:00
// misc..
2015-01-16 02:07:24 +00:00
int lenctr_en ;
int linear_counter , timer , timer_cnt_reload ;
2017-06-08 18:35:13 +00:00
int seq = 0 ;
2015-01-16 02:07:24 +00:00
public int sample ;
2011-06-14 08:32:08 +00:00
2015-01-16 02:07:24 +00:00
APU apu ;
public TriangleUnit ( APU apu ) { this . apu = apu ; }
2011-03-13 08:13:32 +00:00
2015-01-16 02:07:24 +00:00
public void SyncState ( Serializer ser )
{
ser . BeginSection ( "Triangle" ) ;
ser . Sync ( "linear_counter_reload" , ref linear_counter_reload ) ;
ser . Sync ( "control_flag" , ref control_flag ) ;
ser . Sync ( "timer_cnt" , ref timer_cnt ) ;
2018-03-07 12:58:37 +00:00
ser . Sync ( "halt_flag" , ref reload_flag ) ;
2015-01-16 02:07:24 +00:00
ser . Sync ( "len_cnt" , ref len_cnt ) ;
ser . Sync ( "lenctr_en" , ref lenctr_en ) ;
ser . Sync ( "linear_counter" , ref linear_counter ) ;
ser . Sync ( "timer" , ref timer ) ;
ser . Sync ( "timer_cnt_reload" , ref timer_cnt_reload ) ;
ser . Sync ( "seq" , ref seq ) ;
ser . Sync ( "sample" , ref sample ) ;
ser . EndSection ( ) ;
}
public bool IsLenCntNonZero ( ) { return len_cnt > 0 ; }
public void set_lenctr_en ( int value )
{
lenctr_en = value ;
2017-06-13 14:43:34 +00:00
// if the length counter is not enabled, then we must disable the length system in this way
2015-01-16 02:07:24 +00:00
if ( lenctr_en = = 0 ) len_cnt = 0 ;
}
2012-03-25 09:25:27 +00:00
2015-01-16 02:07:24 +00:00
public void WriteReg ( int addr , byte val )
{
2017-06-13 14:43:34 +00:00
// Console.WriteLine("tri writes addr={0}, val={1:x2}", addr, val);
2018-03-07 12:58:37 +00:00
2015-01-16 02:07:24 +00:00
switch ( addr )
2011-03-13 08:13:32 +00:00
{
2015-01-16 02:07:24 +00:00
case 0 :
linear_counter_reload = ( val & 0x7F ) ;
control_flag = ( val > > 7 ) & 1 ;
break ;
case 1 : break ;
case 2 :
timer_cnt = ( timer_cnt & ~ 0xFF ) | val ;
timer_cnt_reload = timer_cnt + 1 ;
break ;
case 3 :
timer_cnt = ( timer_cnt & 0xFF ) | ( ( val & 0x7 ) < < 8 ) ;
timer_cnt_reload = timer_cnt + 1 ;
2016-07-02 02:31:06 +00:00
if ( apu . len_clock_active )
{
if ( len_cnt = = 0 )
{
len_cnt = LENGTH_TABLE [ ( val > > 3 ) & 0x1F ] + 1 ;
}
}
else
{
len_cnt = LENGTH_TABLE [ ( val > > 3 ) & 0x1F ] ;
}
2018-03-07 12:58:37 +00:00
reload_flag = 1 ;
2015-01-16 02:07:24 +00:00
2017-06-13 14:43:34 +00:00
// allow the lenctr_en to kill the len_cnt
2015-01-16 02:07:24 +00:00
set_lenctr_en ( lenctr_en ) ;
break ;
2011-03-13 08:13:32 +00:00
}
2017-06-13 14:43:34 +00:00
// Console.WriteLine("tri timer_reload_value: {0}", timer_cnt_reload);
2015-01-16 02:07:24 +00:00
}
2011-03-13 08:13:32 +00:00
2015-05-08 00:56:46 +00:00
public bool Debug_IsSilenced
{
get
{
2016-06-29 13:37:47 +00:00
bool en = len_cnt ! = 0 & & linear_counter ! = 0 ;
return ! en ;
2015-05-08 00:56:46 +00:00
}
}
public int Debug_PeriodValue
{
get
{
return timer_cnt ;
}
}
2015-01-16 02:07:24 +00:00
public void Run ( )
{
2017-06-13 14:43:34 +00:00
// when clocked by timer, seq steps forward
// except when linear counter or length counter is 0
2017-06-08 18:35:13 +00:00
bool en = len_cnt ! = 0 & & linear_counter ! = 0 ;
2015-01-16 02:07:24 +00:00
2017-06-08 18:35:13 +00:00
bool do_clock = false ;
if ( timer > 0 ) timer - - ;
if ( timer = = 0 )
{
do_clock = true ;
timer = timer_cnt_reload ;
}
if ( en & & do_clock )
2011-03-13 08:13:32 +00:00
{
2015-01-16 02:07:24 +00:00
int newsample ;
2017-06-08 18:35:13 +00:00
seq = ( seq + 1 ) & 0x1F ;
newsample = TRIANGLE_TABLE [ seq ] ;
2016-06-29 13:37:47 +00:00
2017-06-13 14:43:34 +00:00
// special hack: frequently, games will use the maximum frequency triangle in order to mute it
// apparently this results in the DAC for the triangle wave outputting a steady level at about 7.5
// so we'll emulate it at the digital level
2015-01-16 02:07:24 +00:00
if ( timer_cnt_reload = = 1 ) newsample = 8 ;
if ( newsample ! = sample )
2011-03-13 08:13:32 +00:00
{
2015-01-16 02:07:24 +00:00
apu . recalculate = true ;
sample = newsample ;
2011-03-13 08:13:32 +00:00
}
2015-01-16 02:07:24 +00:00
}
}
2011-06-14 08:32:08 +00:00
2015-01-16 02:07:24 +00:00
public void clock_length_and_sweep ( )
{
2017-06-13 14:43:34 +00:00
// env_loopdoubles as "halt length counter"
2018-03-07 12:58:37 +00:00
if ( len_cnt > 0 & & control_flag = = 0 )
2015-01-16 02:07:24 +00:00
len_cnt - - ;
}
2011-06-14 08:32:08 +00:00
2015-01-16 02:07:24 +00:00
public void clock_linear_counter ( )
2011-06-14 08:32:08 +00:00
{
2018-03-07 12:58:37 +00:00
//Console.WriteLine("linear_counter: {0}", linear_counter);
if ( reload_flag = = 1 )
2015-01-16 02:07:24 +00:00
{
linear_counter = linear_counter_reload ;
}
else if ( linear_counter ! = 0 )
2011-06-14 08:32:08 +00:00
{
2015-01-16 02:07:24 +00:00
linear_counter - - ;
2011-06-14 08:32:08 +00:00
}
2018-03-07 12:58:37 +00:00
if ( control_flag = = 0 ) { reload_flag = 0 ; }
2015-01-16 02:07:24 +00:00
}
2017-06-13 14:43:34 +00:00
} // class TriangleUnit
2011-06-14 08:32:08 +00:00
2015-01-16 02:07:24 +00:00
sealed class DMCUnit
{
APU apu ;
int [ ] DMC_RATE ;
public DMCUnit ( APU apu , bool pal )
{
this . apu = apu ;
out_silence = true ;
DMC_RATE = pal ? DMC_RATE_PAL : DMC_RATE_NTSC ;
timer_reload = DMC_RATE [ 0 ] ;
2016-06-21 13:20:52 +00:00
timer = timer_reload ;
2015-01-16 02:07:24 +00:00
sample_buffer_filled = false ;
out_deltacounter = 64 ;
out_bits_remaining = 0 ;
}
2011-06-14 08:32:08 +00:00
2015-01-16 02:07:24 +00:00
bool irq_enabled ;
bool loop_flag ;
int timer_reload ;
2017-06-13 14:43:34 +00:00
// dmc delay per visual 2a03
2016-06-29 13:37:47 +00:00
int delay ;
2016-07-03 20:18:25 +00:00
// this timer never stops, ever, so it is convenient to use for even/odd timing used elsewhere
public int timer ;
2016-06-21 13:20:52 +00:00
int user_address ;
2016-06-29 13:37:47 +00:00
public uint user_length , sample_length ;
2016-06-21 13:20:52 +00:00
int sample_address , sample_buffer ;
2015-01-16 02:07:24 +00:00
bool sample_buffer_filled ;
int out_shift , out_bits_remaining , out_deltacounter ;
bool out_silence ;
2011-06-14 08:32:08 +00:00
2015-01-16 02:07:24 +00:00
public int sample { get { return out_deltacounter /* - 64*/ ; } }
public void SyncState ( Serializer ser )
{
ser . BeginSection ( "DMC" ) ;
ser . Sync ( "irq_enabled" , ref irq_enabled ) ;
ser . Sync ( "loop_flag" , ref loop_flag ) ;
ser . Sync ( "timer_reload" , ref timer_reload ) ;
ser . Sync ( "timer" , ref timer ) ;
ser . Sync ( "user_address" , ref user_address ) ;
ser . Sync ( "user_length" , ref user_length ) ;
ser . Sync ( "sample_address" , ref sample_address ) ;
ser . Sync ( "sample_length" , ref sample_length ) ;
ser . Sync ( "sample_buffer" , ref sample_buffer ) ;
ser . Sync ( "sample_buffer_filled" , ref sample_buffer_filled ) ;
ser . Sync ( "out_shift" , ref out_shift ) ;
ser . Sync ( "out_bits_remaining" , ref out_bits_remaining ) ;
ser . Sync ( "out_deltacounter" , ref out_deltacounter ) ;
ser . Sync ( "out_silence" , ref out_silence ) ;
2016-06-29 13:37:47 +00:00
ser . Sync ( "dmc_call_delay" , ref delay ) ;
2015-01-16 02:07:24 +00:00
ser . EndSection ( ) ;
}
public void Run ( )
{
if ( timer > 0 ) timer - - ;
if ( timer = = 0 )
2011-06-14 08:32:08 +00:00
{
2015-01-16 02:07:24 +00:00
timer = timer_reload ;
Clock ( ) ;
2012-09-29 08:39:59 +00:00
}
2016-06-16 12:43:28 +00:00
2017-06-13 14:43:34 +00:00
// Any time the sample buffer is in an empty state and bytes remaining is not zero, the following occur:
2016-06-21 13:20:52 +00:00
// also note that the halt for DMC DMA occurs on APU cycles only (hence the timer check)
2016-06-30 22:30:12 +00:00
if ( ! sample_buffer_filled & & sample_length > 0 & & apu . dmc_dma_countdown = = - 1 & & delay = = 0 )
{
// calls from write take one less cycle, but start on a write instead of a read
if ( ! apu . call_from_write )
{
2016-10-11 13:10:27 +00:00
if ( timer % 2 = = 1 )
2016-06-30 22:30:12 +00:00
{
delay = 3 ;
} else
{
delay = 2 ;
}
2017-06-13 14:43:34 +00:00
}
else
2016-06-30 22:30:12 +00:00
{
2016-10-11 13:10:27 +00:00
if ( timer % 2 = = 1 )
2016-06-30 22:30:12 +00:00
{
delay = 2 ;
}
else
{
delay = 3 ;
}
}
}
2016-06-29 13:37:47 +00:00
// I did some tests in Visual 2A03 and there seems to be some delay betwen when a DMC is first needed and when the
// process to execute the DMA starts. The details are not currently known, but it seems to be a 2 cycle delay
2016-06-30 22:30:12 +00:00
if ( delay ! = 0 )
2016-06-29 13:37:47 +00:00
{
delay - - ;
if ( delay = = 0 )
{
if ( ! apu . call_from_write )
{
apu . dmc_dma_countdown = 4 ;
}
else
{
apu . dmc_dma_countdown = 3 ;
apu . call_from_write = false ;
}
}
}
}
2012-09-29 08:39:59 +00:00
2015-01-16 02:07:24 +00:00
void Clock ( )
{
2017-06-13 14:43:34 +00:00
// If the silence flag is clear, bit 0 of the shift register is applied to the counter as follows:
// if bit 0 is clear and the delta-counter is greater than 1, the counter is decremented by 2;
// otherwise, if bit 0 is set and the delta-counter is less than 126, the counter is incremented by 2
2015-01-16 02:07:24 +00:00
if ( ! out_silence )
2011-06-14 08:32:08 +00:00
{
2017-06-13 14:43:34 +00:00
// apply current sample bit to delta counter
2015-01-16 02:07:24 +00:00
if ( out_shift . Bit ( 0 ) )
2011-06-14 08:32:08 +00:00
{
2015-01-16 02:07:24 +00:00
if ( out_deltacounter < 126 )
out_deltacounter + = 2 ;
2011-06-14 08:32:08 +00:00
}
2015-01-16 02:07:24 +00:00
else
2011-06-14 08:32:08 +00:00
{
2015-01-16 02:07:24 +00:00
if ( out_deltacounter > 1 )
out_deltacounter - = 2 ;
2011-06-14 08:32:08 +00:00
}
2017-06-13 14:43:34 +00:00
// Console.WriteLine("dmc out sample: {0}", out_deltacounter);
2015-01-16 02:07:24 +00:00
apu . recalculate = true ;
2011-06-14 08:32:08 +00:00
}
2017-06-13 14:43:34 +00:00
// The right shift register is clocked.
2015-01-16 02:07:24 +00:00
out_shift > > = 1 ;
2017-06-13 14:43:34 +00:00
// The bits-remaining counter is decremented. If it becomes zero, a new cycle is started.
2015-01-16 02:07:24 +00:00
if ( out_bits_remaining = = 0 )
2011-06-14 08:32:08 +00:00
{
2017-06-13 14:43:34 +00:00
// The bits-remaining counter is loaded with 8.
2015-01-16 02:07:24 +00:00
out_bits_remaining = 7 ;
2017-06-13 14:43:34 +00:00
// If the sample buffer is empty then the silence flag is set
2015-01-16 02:07:24 +00:00
if ( ! sample_buffer_filled )
2012-09-29 22:19:49 +00:00
{
2015-01-16 02:07:24 +00:00
out_silence = true ;
2012-09-29 22:19:49 +00:00
}
2011-06-14 08:32:08 +00:00
else
2017-06-13 14:43:34 +00:00
// otherwise, the silence flag is cleared and the sample buffer is emptied into the shift register.
2011-06-14 08:32:08 +00:00
{
2015-01-16 02:07:24 +00:00
out_silence = false ;
out_shift = sample_buffer ;
sample_buffer_filled = false ;
2011-06-14 08:32:08 +00:00
}
}
2015-01-16 02:07:24 +00:00
else out_bits_remaining - - ;
}
2011-06-14 08:32:08 +00:00
2015-01-16 02:07:24 +00:00
public void set_lenctr_en ( bool en )
{
if ( ! en )
2011-06-14 08:32:08 +00:00
{
2017-06-13 14:43:34 +00:00
// If the DMC bit is clear, the DMC bytes remaining will be set to 0
// and the DMC will silence when it empties.
2015-01-16 02:07:24 +00:00
sample_length = 0 ;
2017-06-13 14:43:34 +00:00
}
2015-01-16 02:07:24 +00:00
else
2011-06-14 08:32:08 +00:00
{
2017-06-13 14:43:34 +00:00
// only start playback if playback is stopped
// Console.Write(sample_length); Console.Write(" "); Console.Write(sample_buffer_filled); Console.Write(" "); Console.Write(apu.dmc_irq); Console.Write("\n");
2011-06-14 08:32:08 +00:00
if ( sample_length = = 0 )
{
2015-01-16 02:07:24 +00:00
sample_address = user_address ;
sample_length = user_length ;
2016-06-21 13:20:52 +00:00
2011-03-13 08:13:32 +00:00
}
2016-06-29 13:37:47 +00:00
if ( ! sample_buffer_filled )
{
// apparently the dmc is different if called from a cpu write, let's try
apu . call_from_write = true ;
}
2011-03-13 08:13:32 +00:00
}
2011-03-13 00:34:24 +00:00
2017-06-13 14:43:34 +00:00
// irq is acknowledged or sure to be clear, in either case
2015-01-16 02:07:24 +00:00
apu . dmc_irq = false ;
apu . SyncIRQ ( ) ;
2011-06-10 05:02:06 +00:00
}
2015-01-16 02:07:24 +00:00
public bool IsLenCntNonZero ( )
2011-06-14 08:32:08 +00:00
{
2015-01-16 02:07:24 +00:00
return sample_length ! = 0 ;
2011-06-14 08:32:08 +00:00
}
2011-03-13 08:13:32 +00:00
2015-01-16 02:07:24 +00:00
public void WriteReg ( int addr , byte val )
2011-03-13 00:34:24 +00:00
{
2017-06-13 14:43:34 +00:00
// Console.WriteLine("DMC writes addr={0}, val={1:x2}", addr, val);
2015-01-16 02:07:24 +00:00
switch ( addr )
2011-09-24 17:14:55 +00:00
{
2015-01-16 02:07:24 +00:00
case 0 :
irq_enabled = val . Bit ( 7 ) ;
loop_flag = val . Bit ( 6 ) ;
timer_reload = DMC_RATE [ val & 0xF ] ;
if ( ! irq_enabled ) apu . dmc_irq = false ;
2017-06-13 14:43:34 +00:00
// apu.dmc_irq = false;
2015-01-16 02:07:24 +00:00
apu . SyncIRQ ( ) ;
break ;
case 1 :
out_deltacounter = val & 0x7F ;
2017-06-13 14:43:34 +00:00
// apu.nes.LogLine("~~ out_deltacounter set to {0}", out_deltacounter);
2015-01-16 02:07:24 +00:00
apu . recalculate = true ;
break ;
case 2 :
user_address = 0xC000 | ( val < < 6 ) ;
break ;
case 3 :
2016-06-21 13:20:52 +00:00
user_length = ( ( uint ) val < < 4 ) + 1 ;
2015-01-16 02:07:24 +00:00
break ;
2011-09-24 17:14:55 +00:00
}
2011-03-13 00:34:24 +00:00
}
2011-03-13 08:13:32 +00:00
2015-01-16 02:07:24 +00:00
public void Fetch ( )
2011-03-13 00:34:24 +00:00
{
2016-06-21 13:20:52 +00:00
if ( sample_length ! = 0 )
{
sample_buffer = apu . nes . ReadMemory ( ( ushort ) sample_address ) ;
sample_buffer_filled = true ;
sample_address = ( ushort ) ( sample_address + 1 ) ;
2017-06-13 14:43:34 +00:00
// Console.WriteLine(sample_length);
// Console.WriteLine(user_length);
2016-06-30 22:30:12 +00:00
sample_length - - ;
2017-06-13 14:43:34 +00:00
// apu.pending_length_change = 1;
2016-06-21 13:20:52 +00:00
}
2016-06-30 22:30:12 +00:00
if ( sample_length = = 0 )
2012-03-25 09:25:27 +00:00
{
2015-01-16 02:07:24 +00:00
if ( loop_flag )
{
sample_address = user_address ;
sample_length = user_length ;
}
else if ( irq_enabled ) apu . dmc_irq = true ;
2012-03-25 09:25:27 +00:00
}
2017-06-13 14:43:34 +00:00
// Console.WriteLine("fetching dmc byte: {0:X2}", sample_buffer);
2011-03-13 00:34:24 +00:00
}
2015-01-16 02:07:24 +00:00
}
public void SyncState ( Serializer ser )
{
ser . Sync ( "irq_pending" , ref irq_pending ) ;
ser . Sync ( "dmc_irq" , ref dmc_irq ) ;
ser . Sync ( "pending_reg" , ref pending_reg ) ;
ser . Sync ( "pending_val" , ref pending_val ) ;
ser . Sync ( "sequencer_counter" , ref sequencer_counter ) ;
ser . Sync ( "sequencer_step" , ref sequencer_step ) ;
ser . Sync ( "sequencer_mode" , ref sequencer_mode ) ;
ser . Sync ( "sequencer_irq_inhibit;" , ref sequencer_irq_inhibit ) ;
ser . Sync ( "sequencer_irq" , ref sequencer_irq ) ;
ser . Sync ( "sequence_reset_pending" , ref sequence_reset_pending ) ;
ser . Sync ( "sequencer_irq_clear_pending" , ref sequencer_irq_clear_pending ) ;
ser . Sync ( "sequencer_irq_assert" , ref sequencer_irq_assert ) ;
2016-06-21 13:20:52 +00:00
ser . Sync ( "dmc_dma_countdown" , ref dmc_dma_countdown ) ;
2016-06-29 13:37:47 +00:00
ser . Sync ( "sample_length_delay" , ref pending_length_change ) ;
ser . Sync ( "dmc_called_from_write" , ref call_from_write ) ;
2016-06-30 22:30:12 +00:00
ser . Sync ( "sequencer_tick_delay" , ref seq_tick ) ;
ser . Sync ( "seq_val_to_apply" , ref seq_val ) ;
ser . Sync ( "sequencer_irq_flag" , ref sequencer_irq_flag ) ;
2016-07-02 02:31:06 +00:00
ser . Sync ( "len_clock_active" , ref len_clock_active ) ;
2016-06-30 22:30:12 +00:00
2015-01-16 02:07:24 +00:00
pulse [ 0 ] . SyncState ( ser ) ;
pulse [ 1 ] . SyncState ( ser ) ;
triangle . SyncState ( ser ) ;
noise . SyncState ( ser ) ;
dmc . SyncState ( ser ) ;
SyncIRQ ( ) ;
}
2015-05-08 00:56:46 +00:00
public PulseUnit [ ] pulse = new PulseUnit [ 2 ] ;
public TriangleUnit triangle ;
public NoiseUnit noise ;
2015-01-16 02:07:24 +00:00
DMCUnit dmc ;
bool irq_pending ;
bool dmc_irq ;
int pending_reg = - 1 ;
2017-06-12 20:25:21 +00:00
bool doing_tick_quarter = false ;
2015-01-16 02:07:24 +00:00
byte pending_val = 0 ;
2016-06-30 22:30:12 +00:00
public int seq_tick ;
public byte seq_val ;
2016-07-02 02:31:06 +00:00
public bool len_clock_active ;
2015-01-16 02:07:24 +00:00
2016-06-30 22:30:12 +00:00
int sequencer_counter , sequencer_step , sequencer_mode , sequencer_irq_inhibit , sequencer_irq_assert ;
bool sequencer_irq , sequence_reset_pending , sequencer_irq_clear_pending , sequencer_irq_flag ;
2015-01-16 02:07:24 +00:00
public void RunDMCFetch ( )
{
dmc . Fetch ( ) ;
}
2011-03-13 00:34:24 +00:00
2017-04-20 21:26:49 +00:00
int [ ] [ ] sequencer_lut = new int [ 2 ] [ ] ;
static int [ ] [ ] sequencer_lut_ntsc = new int [ ] [ ] {
2016-07-03 01:33:37 +00:00
new int [ ] { 7457 , 14913 , 22371 , 29830 } ,
new int [ ] { 7457 , 14913 , 22371 , 29830 , 37282 }
2015-01-16 02:07:24 +00:00
} ;
2017-04-20 21:26:49 +00:00
static int [ ] [ ] sequencer_lut_pal = new int [ ] [ ] {
new int [ ] { 8313 , 16627 , 24939 , 33254 } ,
new int [ ] { 8313 , 16627 , 24939 , 33254 , 41566 }
} ;
2016-06-30 22:30:12 +00:00
void sequencer_write_tick ( byte val )
{
if ( seq_tick > 0 )
{
seq_tick - - ;
2017-06-12 20:25:21 +00:00
if ( seq_tick = = 0 )
{
2017-06-13 14:43:34 +00:00
sequencer_mode = ( val > > 7 ) & 1 ;
// Console.WriteLine("apu 4017 = {0:X2}", val);
// check if we will be doing the extra frame ticks or not
if ( sequencer_mode = = 1 )
2017-06-12 20:25:21 +00:00
{
if ( ! doing_tick_quarter )
{
QuarterFrame ( ) ;
HalfFrame ( ) ;
}
}
2016-06-30 22:30:12 +00:00
sequencer_irq_inhibit = ( val > > 6 ) & 1 ;
if ( sequencer_irq_inhibit = = 1 )
{
sequencer_irq_flag = false ;
}
2017-06-13 14:43:34 +00:00
sequencer_counter = 0 ;
sequencer_step = 0 ;
2016-06-30 22:30:12 +00:00
}
}
}
2015-01-16 02:07:24 +00:00
void sequencer_tick ( )
{
sequencer_counter + + ;
2017-04-20 21:26:49 +00:00
if ( sequencer_mode = = 0 & & sequencer_counter = = sequencer_lut [ 0 ] [ 3 ] - 1 )
2011-03-13 00:34:24 +00:00
{
2016-06-30 22:30:12 +00:00
if ( sequencer_irq_inhibit = = 0 )
{
sequencer_irq_assert = 2 ;
sequencer_irq_flag = true ;
}
HalfFrame ( ) ;
}
2017-04-20 21:26:49 +00:00
if ( sequencer_mode = = 0 & & sequencer_counter = = sequencer_lut [ 0 ] [ 3 ] - 2 & & sequencer_irq_inhibit = = 0 )
2016-06-30 22:30:12 +00:00
{
//sequencer_irq_assert = 2;
sequencer_irq_flag = true ;
}
2017-04-20 21:26:49 +00:00
if ( sequencer_mode = = 1 & & sequencer_counter = = sequencer_lut [ 1 ] [ 4 ] - 1 )
2016-06-30 22:30:12 +00:00
{
HalfFrame ( ) ;
2015-01-16 02:07:24 +00:00
}
if ( sequencer_lut [ sequencer_mode ] [ sequencer_step ] ! = sequencer_counter )
return ;
sequencer_check ( ) ;
}
2012-03-25 09:25:27 +00:00
2015-01-16 02:07:24 +00:00
public void SyncIRQ ( )
{
irq_pending = sequencer_irq | dmc_irq ;
}
2012-03-25 09:25:27 +00:00
2015-01-16 02:07:24 +00:00
void sequencer_check ( )
{
2017-06-13 14:43:34 +00:00
// Console.WriteLine("sequencer mode {0} step {1}", sequencer_mode, sequencer_step);
2015-01-16 02:07:24 +00:00
bool quarter , half , reset ;
switch ( sequencer_mode )
{
2017-06-13 14:43:34 +00:00
case 0 : // 4-step
2015-01-16 02:07:24 +00:00
quarter = true ;
2016-06-30 22:30:12 +00:00
half = sequencer_step = = 1 ;
2015-01-16 02:07:24 +00:00
reset = sequencer_step = = 3 ;
if ( reset & & sequencer_irq_inhibit = = 0 )
{
2017-06-13 14:43:34 +00:00
// Console.WriteLine("{0} {1,5} set irq_assert", nes.Frame, sequencer_counter);
// sequencer_irq_assert = 2;
2016-06-30 22:30:12 +00:00
sequencer_irq_flag = true ;
2015-01-16 02:07:24 +00:00
}
break ;
2012-03-25 09:25:27 +00:00
2017-06-13 14:43:34 +00:00
case 1 : // 5-step
2015-01-16 02:07:24 +00:00
quarter = sequencer_step ! = 3 ;
2016-06-30 22:30:12 +00:00
half = sequencer_step = = 1 ;
2015-01-16 02:07:24 +00:00
reset = sequencer_step = = 4 ;
break ;
2012-03-25 09:25:27 +00:00
2015-01-16 02:07:24 +00:00
default :
throw new InvalidOperationException ( ) ;
2012-03-25 09:25:27 +00:00
}
2015-01-16 02:07:24 +00:00
if ( reset )
2012-03-25 09:25:27 +00:00
{
2015-01-16 02:07:24 +00:00
sequencer_counter = 0 ;
sequencer_step = 0 ;
2011-03-13 00:34:24 +00:00
}
2015-01-16 02:07:24 +00:00
else sequencer_step + + ;
2011-03-13 00:34:24 +00:00
2015-01-16 02:07:24 +00:00
if ( quarter ) QuarterFrame ( ) ;
if ( half ) HalfFrame ( ) ;
}
2011-03-13 00:34:24 +00:00
2015-01-16 02:07:24 +00:00
void HalfFrame ( )
{
2017-06-12 20:25:21 +00:00
doing_tick_quarter = true ;
2015-01-16 02:07:24 +00:00
pulse [ 0 ] . clock_length_and_sweep ( ) ;
pulse [ 1 ] . clock_length_and_sweep ( ) ;
triangle . clock_length_and_sweep ( ) ;
noise . clock_length_and_sweep ( ) ;
}
2012-04-14 13:51:26 +00:00
2015-01-16 02:07:24 +00:00
void QuarterFrame ( )
{
2017-06-12 20:25:21 +00:00
doing_tick_quarter = true ;
2015-01-16 02:07:24 +00:00
pulse [ 0 ] . clock_env ( ) ;
pulse [ 1 ] . clock_env ( ) ;
triangle . clock_linear_counter ( ) ;
noise . clock_env ( ) ;
}
2012-03-25 09:25:27 +00:00
2015-01-16 02:07:24 +00:00
public void NESSoftReset ( )
{
2017-06-13 14:43:34 +00:00
// need to study what happens to apu and stuff..
2015-01-16 02:07:24 +00:00
sequencer_irq = false ;
2016-06-30 22:30:12 +00:00
sequencer_irq_flag = false ;
2015-01-16 02:07:24 +00:00
_WriteReg ( 0x4015 , 0 ) ;
2016-07-02 02:31:06 +00:00
2017-06-13 14:43:34 +00:00
// for 4017, its as if the last value written gets rewritten
2016-07-01 21:43:09 +00:00
sequencer_mode = ( seq_val > > 7 ) & 1 ;
sequencer_irq_inhibit = ( seq_val > > 6 ) & 1 ;
if ( sequencer_irq_inhibit = = 1 )
{
sequencer_irq_flag = false ;
}
sequencer_counter = 0 ;
sequencer_step = 0 ;
}
public void NESHardReset ( )
{
// "at power on it is as if $00 was written to $4017 9-12 cycles before the reset vector"
2017-06-17 23:02:21 +00:00
// that translates to a starting value for the counter of -3
sequencer_counter = - 1 ;
2015-01-16 02:07:24 +00:00
}
2011-03-13 00:34:24 +00:00
2015-01-16 02:07:24 +00:00
public void WriteReg ( int addr , byte val )
{
pending_reg = addr ;
pending_val = val ;
}
2012-11-02 19:28:00 +00:00
2015-01-16 02:07:24 +00:00
void _WriteReg ( int addr , byte val )
{
//Console.WriteLine("{0:X4} = {1:X2}", addr, val);
int index = addr - 0x4000 ;
int reg = index & 3 ;
int channel = index > > 2 ;
switch ( channel )
2011-03-13 00:34:24 +00:00
{
2016-06-29 13:37:47 +00:00
case 0 :
pulse [ 0 ] . WriteReg ( reg , val ) ;
2015-01-16 02:07:24 +00:00
break ;
2016-06-29 13:37:47 +00:00
case 1 :
pulse [ 1 ] . WriteReg ( reg , val ) ;
2015-01-16 02:07:24 +00:00
break ;
2016-06-29 13:37:47 +00:00
case 2 :
triangle . WriteReg ( reg , val ) ;
2015-01-16 02:07:24 +00:00
break ;
2016-06-29 13:37:47 +00:00
case 3 :
noise . WriteReg ( reg , val ) ;
2015-01-16 02:07:24 +00:00
break ;
2016-06-29 13:37:47 +00:00
case 4 :
dmc . WriteReg ( reg , val ) ;
2015-01-16 02:07:24 +00:00
break ;
case 5 :
if ( addr = = 0x4015 )
{
pulse [ 0 ] . set_lenctr_en ( val & 1 ) ;
pulse [ 1 ] . set_lenctr_en ( ( val > > 1 ) & 1 ) ;
triangle . set_lenctr_en ( ( val > > 2 ) & 1 ) ;
noise . set_lenctr_en ( ( val > > 3 ) & 1 ) ;
dmc . set_lenctr_en ( val . Bit ( 4 ) ) ;
2016-06-29 13:37:47 +00:00
2015-01-16 02:07:24 +00:00
}
else if ( addr = = 0x4017 )
{
2016-07-03 20:18:25 +00:00
if ( dmc . timer % 2 = = 0 )
2012-03-25 09:25:27 +00:00
{
2016-07-03 20:18:25 +00:00
seq_tick = 3 ;
2016-06-30 22:30:12 +00:00
} else
{
2016-07-03 20:18:25 +00:00
seq_tick = 4 ;
2012-11-02 19:28:00 +00:00
}
2016-06-30 22:30:12 +00:00
seq_val = val ;
2015-01-16 02:07:24 +00:00
}
break ;
2012-11-02 19:28:00 +00:00
}
2015-01-16 02:07:24 +00:00
}
public byte PeekReg ( int addr )
{
switch ( addr )
2012-11-02 19:28:00 +00:00
{
2015-01-16 02:07:24 +00:00
case 0x4015 :
{
//notice a missing bit here. should properly emulate with empty / Data bus
//if an interrupt flag was set at the same moment of the read, it will read back as 1 but it will not be cleared.
int dmc_nonzero = dmc . IsLenCntNonZero ( ) ? 1 : 0 ;
int noise_nonzero = noise . IsLenCntNonZero ( ) ? 1 : 0 ;
int tri_nonzero = triangle . IsLenCntNonZero ( ) ? 1 : 0 ;
int pulse1_nonzero = pulse [ 1 ] . IsLenCntNonZero ( ) ? 1 : 0 ;
int pulse0_nonzero = pulse [ 0 ] . IsLenCntNonZero ( ) ? 1 : 0 ;
2016-06-30 22:30:12 +00:00
int ret = ( ( dmc_irq ? 1 : 0 ) < < 7 ) | ( ( sequencer_irq_flag ? 1 : 0 ) < < 6 ) | ( dmc_nonzero < < 4 ) | ( noise_nonzero < < 3 ) | ( tri_nonzero < < 2 ) | ( pulse1_nonzero < < 1 ) | ( pulse0_nonzero ) ;
2015-01-16 02:07:24 +00:00
return ( byte ) ret ;
}
default :
2017-06-13 14:43:34 +00:00
// don't return 0xFF here or SMB will break
2015-01-16 02:07:24 +00:00
return 0x00 ;
2011-03-13 00:34:24 +00:00
}
2015-01-16 02:07:24 +00:00
}
2011-03-13 00:34:24 +00:00
2015-01-16 02:07:24 +00:00
public byte ReadReg ( int addr )
{
switch ( addr )
2011-03-13 00:34:24 +00:00
{
2015-01-16 02:07:24 +00:00
case 0x4015 :
2012-03-25 09:25:27 +00:00
{
2015-01-16 02:07:24 +00:00
byte ret = PeekReg ( 0x4015 ) ;
2017-06-13 14:43:34 +00:00
// Console.WriteLine("{0} {1,5} $4015 clear irq, was at {2}", nes.Frame, sequencer_counter, sequencer_irq);
2016-06-30 22:30:12 +00:00
sequencer_irq_flag = false ;
2012-03-25 09:25:27 +00:00
SyncIRQ ( ) ;
2015-01-16 02:07:24 +00:00
return ret ;
2012-03-25 09:25:27 +00:00
}
2015-01-16 02:07:24 +00:00
default :
2017-06-13 14:43:34 +00:00
// don't return 0xFF here or SMB will break
2015-01-16 02:07:24 +00:00
return 0x00 ;
}
}
2012-03-25 09:25:27 +00:00
2015-05-08 00:56:46 +00:00
public Action DebugCallback ;
public int DebugCallbackDivider ;
public int DebugCallbackTimer ;
2016-06-29 13:37:47 +00:00
int pending_length_change ;
2012-03-25 09:25:27 +00:00
2016-06-29 13:37:47 +00:00
public void RunOne ( bool read )
{
if ( read )
2012-12-09 03:13:47 +00:00
{
2016-06-29 13:37:47 +00:00
pulse [ 0 ] . Run ( ) ;
pulse [ 1 ] . Run ( ) ;
triangle . Run ( ) ;
noise . Run ( ) ;
dmc . Run ( ) ;
2016-07-01 21:43:09 +00:00
pulse [ 0 ] . len_halt = false ;
pulse [ 1 ] . len_halt = false ;
noise . len_halt = false ;
2016-06-29 13:37:47 +00:00
}
else
{
if ( pending_length_change > 0 )
2012-12-09 03:13:47 +00:00
{
2016-06-29 13:37:47 +00:00
pending_length_change - - ;
if ( pending_length_change = = 0 )
{
dmc . sample_length - - ;
}
2012-12-09 03:13:47 +00:00
}
2016-06-30 22:30:12 +00:00
EmitSample ( ) ;
2017-06-13 14:43:34 +00:00
// we need to predict if there will be a length clock here, because the sequencer ticks last, but the
2016-07-02 02:31:06 +00:00
// timer reload shouldn't happen if length clock and write happen simultaneously
// I'm not sure if we can avoid this by simply processing the sequencer first
// but at the moment that would break everything, so this is good enough for now
2017-04-20 21:26:49 +00:00
if ( sequencer_counter = = ( sequencer_lut [ 0 ] [ 1 ] - 1 ) | |
( sequencer_counter = = sequencer_lut [ 0 ] [ 3 ] - 2 & & sequencer_mode = = 0 ) | |
( sequencer_counter = = sequencer_lut [ 1 ] [ 4 ] - 2 & & sequencer_mode = = 1 ) )
2016-07-02 02:31:06 +00:00
{
len_clock_active = true ;
}
2017-06-13 14:43:34 +00:00
// handle writes
// notes: this set up is a bit convoluded at the moment, mainly because APU behaviour is not entirely understood
// in partiuclar, there are several clock pulses affecting the APU, and when new written are latched is not known in detail
// the current code simply matches known behaviour
2016-07-01 21:43:09 +00:00
if ( pending_reg ! = - 1 )
{
2017-06-12 20:25:21 +00:00
if ( pending_reg = = 0x4015 | | pending_reg = = 0x4015 | | pending_reg = = 0x4003 | | pending_reg = = 0x4007 )
2016-07-01 21:43:09 +00:00
{
_WriteReg ( pending_reg , pending_val ) ;
pending_reg = - 1 ;
}
2016-07-03 20:18:25 +00:00
else if ( dmc . timer % 2 = = 0 )
2016-07-01 21:43:09 +00:00
{
_WriteReg ( pending_reg , pending_val ) ;
pending_reg = - 1 ;
}
}
2012-12-14 15:17:14 +00:00
2016-07-02 02:31:06 +00:00
len_clock_active = false ;
2016-06-30 22:30:12 +00:00
sequencer_tick ( ) ;
sequencer_write_tick ( seq_val ) ;
2017-06-12 20:25:21 +00:00
doing_tick_quarter = false ;
2016-06-30 22:30:12 +00:00
if ( sequencer_irq_assert > 0 ) {
sequencer_irq_assert - - ;
if ( sequencer_irq_assert = = 0 )
{
sequencer_irq = true ;
}
2016-07-03 20:18:25 +00:00
}
2016-06-30 22:30:12 +00:00
SyncIRQ ( ) ;
2017-12-06 00:36:02 +00:00
nes . _irq_apu = irq_pending ;
2017-10-26 13:58:24 +00:00
2017-06-13 14:43:34 +00:00
// since the units run concurrently, the APU frame sequencer is ran last because
// it can change the ouput values of the pulse/triangle channels
// we want the changes to affect it on the *next* cycle.
2016-06-29 13:37:47 +00:00
2016-07-02 02:31:06 +00:00
if ( sequencer_irq_flag = = false )
sequencer_irq = false ;
2016-06-29 13:37:47 +00:00
if ( DebugCallbackDivider ! = 0 )
{
if ( DebugCallbackTimer = = 0 )
{
if ( DebugCallback ! = null )
DebugCallback ( ) ;
DebugCallbackTimer = DebugCallbackDivider ;
}
else DebugCallbackTimer - - ;
2015-05-08 00:56:46 +00:00
2016-06-29 13:37:47 +00:00
}
2015-05-08 00:56:46 +00:00
}
2015-01-16 02:07:24 +00:00
}
2012-12-09 03:13:47 +00:00
2015-01-16 02:07:24 +00:00
public struct Delta
{
public uint time ;
public int value ;
public Delta ( uint time , int value )
2012-12-14 22:29:27 +00:00
{
2015-01-16 02:07:24 +00:00
this . time = time ;
this . value = value ;
2012-12-14 22:29:27 +00:00
}
2015-01-16 02:07:24 +00:00
}
2017-06-08 18:35:13 +00:00
2015-01-16 02:07:24 +00:00
public List < Delta > dlist = new List < Delta > ( ) ;
/// <summary>only call in board.ClockCPU()</summary>
/// <param name="value"></param>
public void ExternalQueue ( int value )
{
// sampleclock is incremented right before board.ClockCPU()
dlist . Add ( new Delta ( sampleclock - 1 , value ) ) ;
}
public uint sampleclock = 0 ;
int oldmix = 0 ;
2012-12-14 22:29:27 +00:00
2015-01-16 02:07:24 +00:00
void EmitSample ( )
{
if ( recalculate )
2011-03-13 08:13:32 +00:00
{
2015-01-16 02:07:24 +00:00
recalculate = false ;
int s_pulse0 = pulse [ 0 ] . sample ;
int s_pulse1 = pulse [ 1 ] . sample ;
int s_tri = triangle . sample ;
int s_noise = noise . sample ;
int s_dmc = dmc . sample ;
2017-06-08 18:35:13 +00:00
2017-06-13 14:43:34 +00:00
// int s_ext = 0; //gamepak
2015-01-16 02:07:24 +00:00
/ *
if ( ! EnableSquare1 ) s_pulse0 = 0 ;
if ( ! EnableSquare2 ) s_pulse1 = 0 ;
if ( ! EnableTriangle ) s_tri = 0 ;
if ( ! EnableNoise ) s_noise = 0 ;
if ( ! EnableDMC ) s_dmc = 0 ;
* /
2016-06-29 13:37:47 +00:00
2017-06-13 14:43:34 +00:00
// more properly correct
2016-07-03 01:33:37 +00:00
float pulse_out , tnd_out ;
if ( s_pulse0 = = 0 & & s_pulse1 = = 0 )
pulse_out = 0 ;
else pulse_out = 95.88f / ( ( 8128.0f / ( s_pulse0 + s_pulse1 ) ) + 100.0f ) ;
if ( s_tri = = 0 & & s_noise = = 0 & & s_dmc = = 0 )
tnd_out = 0 ;
else tnd_out = 159.79f / ( 1 / ( ( s_tri / 8227.0f ) + ( s_noise / 12241.0f /* * NOISEADJUST*/ ) + ( s_dmc / 22638.0f ) ) + 100 ) ;
float output = pulse_out + tnd_out ;
2017-06-13 14:43:34 +00:00
// output = output * 2 - 1;
// this needs to leave enough headroom for straying DC bias due to the DMC unit getting stuck outputs. smb3 is bad about that.
2017-09-07 14:27:36 +00:00
int mix = ( int ) ( 20000 * output * ( 1 + m_vol / 5 ) ) ;
2015-01-16 02:07:24 +00:00
dlist . Add ( new Delta ( sampleclock , mix - oldmix ) ) ;
oldmix = mix ;
2011-03-13 00:34:24 +00:00
}
2015-01-16 02:07:24 +00:00
sampleclock + + ;
}
2011-03-13 00:34:24 +00:00
}
2016-10-07 17:59:00 +00:00
}