2011-03-13 00:34:24 +00:00
|
|
|
|
using System;
|
2011-03-13 08:13:32 +00:00
|
|
|
|
using System.IO;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
using BizHawk.Emulation.Sound;
|
|
|
|
|
|
2011-03-13 00:34:24 +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-06-10 05:02:06 +00:00
|
|
|
|
//TODO - refactor length counter to be separate component
|
|
|
|
|
|
2011-03-13 00:34:24 +00:00
|
|
|
|
namespace BizHawk.Emulation.Consoles.Nintendo
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
partial class NES
|
|
|
|
|
{
|
|
|
|
|
public class APU : ISoundProvider
|
|
|
|
|
{
|
2011-03-13 08:13:32 +00:00
|
|
|
|
public static bool CFG_USE_METASPU = true;
|
|
|
|
|
public static bool CFG_DECLICK = true;
|
|
|
|
|
|
2011-03-13 00:34:24 +00:00
|
|
|
|
NES nes;
|
|
|
|
|
public APU(NES nes)
|
|
|
|
|
{
|
|
|
|
|
this.nes = nes;
|
2011-06-14 08:32:08 +00:00
|
|
|
|
dmc = new DMCUnit(this);
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-06-14 08:32:08 +00:00
|
|
|
|
static int[] DMC_RATE_NTSC = { 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 };
|
2011-03-13 00:34:24 +00:00
|
|
|
|
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 = {
|
|
|
|
|
{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%))
|
|
|
|
|
};
|
2011-03-13 08:13:32 +00:00
|
|
|
|
static byte[] TRIANGLE_TABLE =
|
|
|
|
|
{
|
|
|
|
|
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
|
|
|
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
|
|
|
|
|
};
|
2011-03-20 04:43:27 +00:00
|
|
|
|
static int[] NOISE_TABLE =
|
|
|
|
|
{
|
|
|
|
|
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 //NTSC
|
|
|
|
|
//4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778 //PAL
|
|
|
|
|
};
|
2011-03-13 00:34:24 +00:00
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
|
2011-03-13 00:34:24 +00:00
|
|
|
|
class PulseUnit
|
|
|
|
|
{
|
|
|
|
|
public PulseUnit(int unit) { this.unit = unit; }
|
|
|
|
|
public int unit;
|
|
|
|
|
|
|
|
|
|
//reg0
|
2011-03-13 08:13:32 +00:00
|
|
|
|
int duty_cnt, env_loop, env_constant, env_cnt_value;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
//reg1
|
2011-03-19 09:12:56 +00:00
|
|
|
|
int sweep_en, sweep_divider_cnt, sweep_negate, sweep_shiftcount;
|
|
|
|
|
bool sweep_reload;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
//reg2/3
|
|
|
|
|
int len_cnt;
|
|
|
|
|
int timer_raw_reload_value, timer_reload_value;
|
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
//misc..
|
|
|
|
|
int lenctr_en;
|
|
|
|
|
|
2011-06-10 05:02:06 +00:00
|
|
|
|
public void SyncState(Serializer ser)
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
public bool IsLenCntNonZero() { return len_cnt > 0; }
|
2011-03-13 00:34:24 +00:00
|
|
|
|
|
|
|
|
|
public void WriteReg(int addr, byte val)
|
|
|
|
|
{
|
|
|
|
|
//Console.WriteLine("write pulse {0:X} {1:X}", addr, val);
|
|
|
|
|
switch(addr)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
2011-03-13 08:13:32 +00:00
|
|
|
|
env_cnt_value = val & 0xF;
|
|
|
|
|
env_constant = (val >> 4) & 1;
|
|
|
|
|
env_loop = (val >> 5) & 1;
|
|
|
|
|
duty_cnt = (val >> 6) & 3;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
break;
|
|
|
|
|
case 1:
|
2011-03-19 09:12:56 +00:00
|
|
|
|
sweep_shiftcount = val & 7;
|
|
|
|
|
sweep_negate = (val >> 3) & 1;
|
|
|
|
|
sweep_divider_cnt = (val >> 4) & 7;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
sweep_en = (val >> 7) & 1;
|
2011-03-19 09:12:56 +00:00
|
|
|
|
sweep_reload = true;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
timer_reload_value = (timer_reload_value & ~0xFF) | val;
|
2011-03-19 09:12:56 +00:00
|
|
|
|
timer_raw_reload_value = timer_reload_value * 2 + 2;
|
|
|
|
|
//if (unit == 1) Console.WriteLine("{0} timer_reload_value: {1}", unit, timer_reload_value);
|
2011-03-13 00:34:24 +00:00
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F];
|
|
|
|
|
timer_reload_value = (timer_reload_value & 0xFF) | ((val & 0x07) << 8);
|
2011-03-19 09:12:56 +00:00
|
|
|
|
timer_raw_reload_value = timer_reload_value * 2 + 2;
|
|
|
|
|
//duty_step = 0; //?just a guess?
|
2011-03-13 01:40:09 +00:00
|
|
|
|
timer_counter = timer_raw_reload_value;
|
2011-03-13 08:13:32 +00:00
|
|
|
|
env_start_flag = 1;
|
2011-03-19 09:12:56 +00:00
|
|
|
|
|
|
|
|
|
//allow the lenctr_en to kill the len_cnt
|
|
|
|
|
set_lenctr_en(lenctr_en);
|
|
|
|
|
|
2011-03-13 00:34:24 +00:00
|
|
|
|
//serves as a useful note-on diagnostic
|
2011-03-19 09:12:56 +00:00
|
|
|
|
//if(unit==1) Console.WriteLine("{0} timer_reload_value: {1}", unit, timer_reload_value);
|
2011-03-13 00:34:24 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
public void set_lenctr_en(int value)
|
|
|
|
|
{
|
|
|
|
|
lenctr_en = value;
|
|
|
|
|
//if the length counter is not enabled, then we must disable the length system in this way
|
|
|
|
|
if (lenctr_en == 0) len_cnt = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-20 23:46:03 +00:00
|
|
|
|
//state
|
2011-03-19 09:12:56 +00:00
|
|
|
|
int swp_divider_counter;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
bool swp_silence;
|
2011-03-13 08:13:32 +00:00
|
|
|
|
int duty_step;
|
2011-03-13 01:40:09 +00:00
|
|
|
|
int timer_counter;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
public int sample;
|
2011-03-20 23:46:03 +00:00
|
|
|
|
bool duty_value;
|
2011-03-13 08:13:32 +00:00
|
|
|
|
|
|
|
|
|
int env_start_flag, env_divider, env_counter, env_output;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
public void clock_length_and_sweep()
|
2011-03-13 00:34:24 +00:00
|
|
|
|
{
|
2011-03-19 09:12:56 +00:00
|
|
|
|
//this should be optimized to update only when `timer_reload_value` changes
|
|
|
|
|
int sweep_shifter = timer_reload_value >> sweep_shiftcount;
|
|
|
|
|
if (sweep_negate == 1)
|
|
|
|
|
sweep_shifter = ~sweep_shifter + unit;
|
|
|
|
|
sweep_shifter += timer_reload_value;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
//this sweep logic is always enabled:
|
|
|
|
|
swp_silence = (timer_reload_value < 8 || (sweep_shifter > 0x7FF && sweep_negate == 0));
|
2011-03-13 00:34:24 +00:00
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
//does enable only block the pitch bend? does the clocking proceed?
|
|
|
|
|
if (sweep_en == 1)
|
|
|
|
|
{
|
|
|
|
|
//clock divider
|
|
|
|
|
if (swp_divider_counter != 0) swp_divider_counter--;
|
|
|
|
|
if (swp_divider_counter == 0)
|
|
|
|
|
{
|
|
|
|
|
swp_divider_counter = sweep_divider_cnt + 1;
|
|
|
|
|
|
|
|
|
|
//divider was clocked: process sweep pitch bend
|
|
|
|
|
if (sweep_shiftcount != 0 && !swp_silence)
|
|
|
|
|
{
|
|
|
|
|
timer_reload_value = sweep_shifter;
|
|
|
|
|
timer_raw_reload_value = timer_reload_value * 2 + 2;
|
|
|
|
|
}
|
|
|
|
|
//TODO - does this change the user's reload value or the latched reload value?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//handle divider reload, after clocking happens
|
|
|
|
|
if (sweep_reload)
|
|
|
|
|
{
|
|
|
|
|
swp_divider_counter = sweep_divider_cnt + 1;
|
|
|
|
|
sweep_reload = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//env_loopdoubles as "halt length counter"
|
|
|
|
|
if (env_loop == 0 && len_cnt > 0)
|
|
|
|
|
len_cnt--;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void clock_env()
|
|
|
|
|
{
|
2011-03-13 08:13:32 +00:00
|
|
|
|
if (env_start_flag == 1)
|
|
|
|
|
{
|
|
|
|
|
env_start_flag = 0;
|
|
|
|
|
env_divider = (env_cnt_value + 1);
|
|
|
|
|
env_counter = 15;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2011-03-19 09:12:56 +00:00
|
|
|
|
if(env_divider != 0) env_divider--;
|
2011-03-13 08:13:32 +00:00
|
|
|
|
if (env_divider == 0)
|
|
|
|
|
{
|
|
|
|
|
env_divider = (env_cnt_value + 1);
|
|
|
|
|
if (env_counter == 0)
|
|
|
|
|
{
|
|
|
|
|
if (env_loop == 1)
|
|
|
|
|
{
|
|
|
|
|
env_counter = 15;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else env_counter--;
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Run()
|
|
|
|
|
{
|
2011-09-24 17:14:55 +00:00
|
|
|
|
if (env_constant == 1)
|
|
|
|
|
env_output = env_cnt_value;
|
|
|
|
|
else env_output = env_counter;
|
|
|
|
|
|
2011-03-20 04:43:27 +00:00
|
|
|
|
if (timer_counter > 0) timer_counter--;
|
2011-03-20 23:46:03 +00:00
|
|
|
|
if (timer_counter == 0 && timer_raw_reload_value!=0)
|
2011-03-13 00:34:24 +00:00
|
|
|
|
{
|
2011-03-13 08:13:32 +00:00
|
|
|
|
duty_step = (duty_step + 1) & 7;
|
2011-03-20 23:46:03 +00:00
|
|
|
|
duty_value = PULSE_DUTY[duty_cnt, duty_step] == 1;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
//reload timer
|
2011-03-19 09:12:56 +00:00
|
|
|
|
timer_counter = timer_raw_reload_value;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
2011-03-20 23:46:03 +00:00
|
|
|
|
if (duty_value) //we are outputting something
|
2011-03-13 00:34:24 +00:00
|
|
|
|
{
|
2011-03-13 08:13:32 +00:00
|
|
|
|
sample = env_output;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
|
|
|
|
|
if (swp_silence)
|
|
|
|
|
sample = 0;
|
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
if (len_cnt==0) //length counter is 0
|
|
|
|
|
sample = 0; //silenced
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
sample = 0; //duty cycle is 0, silenced.
|
|
|
|
|
|
|
|
|
|
}
|
2011-03-13 08:13:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-03-20 04:43:27 +00:00
|
|
|
|
class NoiseUnit
|
|
|
|
|
{
|
|
|
|
|
//reg0 (sweep)
|
|
|
|
|
int env_cnt_value, env_loop, env_constant;
|
|
|
|
|
|
|
|
|
|
//reg2 (mode and period)
|
|
|
|
|
int mode_cnt, period_cnt;
|
|
|
|
|
|
|
|
|
|
//reg3 (length counter and envelop trigger)
|
|
|
|
|
int len_cnt;
|
|
|
|
|
|
|
|
|
|
//set from apu:
|
|
|
|
|
int lenctr_en;
|
|
|
|
|
|
|
|
|
|
//state
|
|
|
|
|
int shift_register = 1;
|
|
|
|
|
int timer_counter;
|
|
|
|
|
public int sample;
|
|
|
|
|
int env_output, env_start_flag, env_divider, env_counter;
|
2011-03-20 20:42:12 +00:00
|
|
|
|
bool noise_bit = true;
|
2011-03-20 04:43:27 +00:00
|
|
|
|
|
2011-06-10 05:02:06 +00:00
|
|
|
|
public void SyncState(Serializer ser)
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
ser.Sync("mode_cnt", ref mode_cnt);
|
|
|
|
|
ser.Sync("period_cnt", ref period_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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-03-20 04:43:27 +00:00
|
|
|
|
public bool IsLenCntNonZero() { return len_cnt > 0; }
|
|
|
|
|
|
|
|
|
|
public void WriteReg(int addr, byte val)
|
|
|
|
|
{
|
|
|
|
|
switch (addr)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
env_cnt_value = val & 0xF;
|
|
|
|
|
env_constant = (val >> 4) & 1;
|
|
|
|
|
env_loop = (val>>5)&1;
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
period_cnt = NOISE_TABLE[val & 0xF];
|
|
|
|
|
mode_cnt = (val>>7)&1;
|
|
|
|
|
//Console.WriteLine("noise period: {0}, vol: {1}", (val & 0xF), env_cnt_value);
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F];
|
|
|
|
|
set_lenctr_en(lenctr_en);
|
|
|
|
|
env_start_flag = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void set_lenctr_en(int value)
|
|
|
|
|
{
|
|
|
|
|
lenctr_en = value;
|
|
|
|
|
//Console.WriteLine("noise lenctr_en: " + lenctr_en);
|
|
|
|
|
//if the length counter is not enabled, then we must disable the length system in this way
|
|
|
|
|
if (lenctr_en == 0) len_cnt = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-24 17:14:55 +00:00
|
|
|
|
public void clock_env()
|
2011-03-20 04:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
if (env_start_flag == 1)
|
|
|
|
|
{
|
|
|
|
|
env_start_flag = 0;
|
|
|
|
|
env_divider = (env_cnt_value + 1);
|
|
|
|
|
env_counter = 15;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (env_divider != 0) env_divider--;
|
|
|
|
|
if (env_divider == 0)
|
|
|
|
|
{
|
|
|
|
|
env_divider = (env_cnt_value + 1);
|
|
|
|
|
if (env_counter == 0)
|
|
|
|
|
{
|
|
|
|
|
if (env_loop == 1)
|
|
|
|
|
{
|
|
|
|
|
env_counter = 15;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else env_counter--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-24 17:14:55 +00:00
|
|
|
|
}
|
|
|
|
|
public void clock_length_and_sweep()
|
|
|
|
|
{
|
|
|
|
|
|
2011-03-20 04:43:27 +00:00
|
|
|
|
if (len_cnt > 0 && env_loop == 0)
|
|
|
|
|
len_cnt--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Run()
|
|
|
|
|
{
|
2011-09-24 17:14:55 +00:00
|
|
|
|
if (env_constant == 1)
|
|
|
|
|
env_output = env_cnt_value;
|
|
|
|
|
else env_output = env_counter;
|
|
|
|
|
|
2011-03-20 04:43:27 +00:00
|
|
|
|
if (timer_counter > 0) timer_counter--;
|
2011-03-20 20:42:12 +00:00
|
|
|
|
if (timer_counter == 0 && period_cnt != 0)
|
2011-03-20 04:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
//reload timer
|
|
|
|
|
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);
|
2011-03-20 20:42:12 +00:00
|
|
|
|
noise_bit = (shift_register & 1)!=0;
|
2011-03-20 04:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-03-20 20:42:12 +00:00
|
|
|
|
if (noise_bit || len_cnt==0) sample = 0;
|
|
|
|
|
else
|
|
|
|
|
sample = env_output;
|
2011-03-20 04:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
class TriangleUnit
|
|
|
|
|
{
|
|
|
|
|
//reg0
|
|
|
|
|
int linear_counter_reload, control_flag;
|
|
|
|
|
//reg1 (n/a)
|
|
|
|
|
//reg2/3
|
2011-06-10 05:02:06 +00:00
|
|
|
|
int timer_cnt, halt_flag, len_cnt;
|
|
|
|
|
|
|
|
|
|
//misc..
|
|
|
|
|
int lenctr_en;
|
|
|
|
|
int linear_counter, timer, timer_cnt_reload;
|
2011-06-14 08:32:08 +00:00
|
|
|
|
int seq = 15;
|
2011-06-10 05:02:06 +00:00
|
|
|
|
public int sample;
|
|
|
|
|
|
|
|
|
|
public void SyncState(Serializer ser)
|
|
|
|
|
{
|
|
|
|
|
ser.Sync("linear_counter_reload", ref linear_counter_reload);
|
|
|
|
|
ser.Sync("control_flag", ref control_flag);
|
|
|
|
|
ser.Sync("timer_cnt", ref timer_cnt);
|
|
|
|
|
ser.Sync("halt_flag", ref halt_flag);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsLenCntNonZero() { return len_cnt > 0; }
|
|
|
|
|
|
|
|
|
|
public void set_lenctr_en(int value)
|
|
|
|
|
{
|
|
|
|
|
lenctr_en = value;
|
|
|
|
|
//if the length counter is not enabled, then we must disable the length system in this way
|
|
|
|
|
if (lenctr_en == 0) len_cnt = 0;
|
|
|
|
|
}
|
2011-03-13 08:13:32 +00:00
|
|
|
|
|
|
|
|
|
public void WriteReg(int addr, byte val)
|
|
|
|
|
{
|
2011-06-14 08:32:08 +00:00
|
|
|
|
//Console.WriteLine("tri writes addr={0}, val={1:x2}", addr, val);
|
2011-03-13 08:13:32 +00:00
|
|
|
|
switch (addr)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
linear_counter_reload = (val & 0x7F);
|
|
|
|
|
control_flag = (val >> 7) & 1;
|
|
|
|
|
break;
|
|
|
|
|
case 1: break;
|
|
|
|
|
case 2:
|
|
|
|
|
timer_cnt = (timer_cnt & ~0xFF) | val;
|
2011-03-20 03:32:43 +00:00
|
|
|
|
timer_cnt_reload = timer_cnt + 1;
|
2011-03-13 08:13:32 +00:00
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
timer_cnt = (timer_cnt & 0xFF) | ((val & 0x7) << 8);
|
|
|
|
|
timer_cnt_reload = timer_cnt + 1;
|
2011-06-10 05:02:06 +00:00
|
|
|
|
len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F];
|
2011-03-13 08:13:32 +00:00
|
|
|
|
halt_flag = 1;
|
2011-06-10 05:02:06 +00:00
|
|
|
|
|
|
|
|
|
//allow the lenctr_en to kill the len_cnt
|
|
|
|
|
set_lenctr_en(lenctr_en);
|
2011-03-13 08:13:32 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
2011-03-20 03:32:43 +00:00
|
|
|
|
//Console.WriteLine("tri timer_reload_value: {0}", timer_cnt_reload);
|
2011-03-13 08:13:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Run()
|
|
|
|
|
{
|
|
|
|
|
//when clocked by timer
|
|
|
|
|
//seq steps forward
|
|
|
|
|
//except when linear counter or
|
|
|
|
|
//length counter is 0
|
|
|
|
|
|
2011-09-24 17:36:48 +00:00
|
|
|
|
//dont stop the triangle channel until its level is 0. makes it sound nicer.
|
|
|
|
|
bool need_declick = (seq != 16 && seq != 15);
|
|
|
|
|
bool en = len_cnt != 0 && linear_counter != 0 || need_declick;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
//length counter and linear counter
|
|
|
|
|
//is clocked in frame counter.
|
|
|
|
|
if (en)
|
|
|
|
|
{
|
2011-03-20 03:32:43 +00:00
|
|
|
|
if(timer>0) timer--;
|
2011-03-13 08:13:32 +00:00
|
|
|
|
if (timer == 0)
|
|
|
|
|
{
|
|
|
|
|
seq = (seq + 1) & 0x1F;
|
|
|
|
|
timer = timer_cnt_reload;
|
|
|
|
|
}
|
2011-03-20 03:32:43 +00:00
|
|
|
|
//if(CFG_DECLICK)
|
2011-06-14 08:32:08 +00:00
|
|
|
|
//sample = TRIANGLE_TABLE[(seq+8)&0x1F];
|
2011-03-20 03:32:43 +00:00
|
|
|
|
//else
|
2011-03-13 08:13:32 +00:00
|
|
|
|
sample = TRIANGLE_TABLE[seq];
|
|
|
|
|
}
|
2011-06-14 08:32:08 +00:00
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void clock_length_and_sweep()
|
|
|
|
|
{
|
2011-06-10 05:02:06 +00:00
|
|
|
|
//env_loopdoubles as "halt length counter"
|
2011-09-24 17:36:48 +00:00
|
|
|
|
if (len_cnt > 0 && halt_flag == 0)
|
2011-06-10 05:02:06 +00:00
|
|
|
|
len_cnt--;
|
2011-03-13 08:13:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void clock_linear_counter()
|
|
|
|
|
{
|
|
|
|
|
// Console.WriteLine("linear_counter: {0}", linear_counter);
|
|
|
|
|
if (halt_flag == 1)
|
|
|
|
|
{
|
|
|
|
|
linear_counter = linear_counter_reload;
|
|
|
|
|
}
|
|
|
|
|
else if (linear_counter != 0)
|
|
|
|
|
{
|
|
|
|
|
linear_counter--;
|
|
|
|
|
}
|
2011-06-14 08:32:08 +00:00
|
|
|
|
|
|
|
|
|
//declick when the sound begins
|
|
|
|
|
//if (halt_flag == 1 && control_flag == 0)
|
|
|
|
|
//{
|
|
|
|
|
// seq = 16;
|
2011-09-03 15:50:46 +00:00
|
|
|
|
// Console.WriteLine("declicked triangle");
|
2011-06-14 08:32:08 +00:00
|
|
|
|
//}
|
|
|
|
|
|
2011-09-03 15:50:46 +00:00
|
|
|
|
//declick on end of sound
|
2011-06-14 08:32:08 +00:00
|
|
|
|
//bool en = len_cnt != 0 && linear_counter != 0;
|
|
|
|
|
//if (!en)
|
|
|
|
|
// if (sample < 0) sample++; else if (sample > 0) sample--;
|
|
|
|
|
|
|
|
|
|
halt_flag = control_flag;
|
|
|
|
|
}
|
|
|
|
|
} //class TriangleUnit
|
|
|
|
|
|
|
|
|
|
class DMCUnit
|
|
|
|
|
{
|
|
|
|
|
APU apu;
|
|
|
|
|
public DMCUnit(APU apu)
|
|
|
|
|
{
|
|
|
|
|
this.apu = apu;
|
|
|
|
|
out_silence = true;
|
|
|
|
|
timer_reload = DMC_RATE_NTSC[0];
|
|
|
|
|
sample_buffer_filled = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool irq_enabled;
|
|
|
|
|
bool loop_flag;
|
|
|
|
|
int timer_reload;
|
|
|
|
|
|
|
|
|
|
int timer;
|
|
|
|
|
int user_address, user_length;
|
|
|
|
|
int sample_address, sample_length, sample_buffer;
|
|
|
|
|
bool sample_buffer_filled;
|
|
|
|
|
|
|
|
|
|
int out_shift, out_bits_remaining, out_deltacounter;
|
|
|
|
|
bool out_silence;
|
|
|
|
|
|
|
|
|
|
public int sample;
|
|
|
|
|
|
|
|
|
|
public void SyncState(Serializer ser)
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
ser.Sync("sample", ref sample);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Run()
|
|
|
|
|
{
|
|
|
|
|
if (timer > 0) timer--;
|
|
|
|
|
if (timer == 0)
|
2011-03-13 08:13:32 +00:00
|
|
|
|
{
|
2011-06-14 08:32:08 +00:00
|
|
|
|
timer = timer_reload;
|
|
|
|
|
Clock();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SyncSample()
|
|
|
|
|
{
|
|
|
|
|
sample = (out_deltacounter - 64) / 4;
|
|
|
|
|
//Console.WriteLine("dmc sample: {0}", sample);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Clock()
|
|
|
|
|
{
|
|
|
|
|
if (!out_silence)
|
|
|
|
|
{
|
|
|
|
|
//apply current sample bit to delta counter
|
|
|
|
|
if (out_shift.Bit(0))
|
|
|
|
|
{
|
|
|
|
|
if (out_deltacounter < 126)
|
|
|
|
|
out_deltacounter += 2;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (out_deltacounter > 1)
|
|
|
|
|
out_deltacounter -= 2;
|
|
|
|
|
}
|
|
|
|
|
SyncSample();
|
|
|
|
|
out_shift >>= 1;
|
|
|
|
|
//apu.nes.LogLine("dmc out sample: {0}", out_deltacounter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (out_bits_remaining==0)
|
|
|
|
|
{
|
|
|
|
|
out_bits_remaining = 7;
|
|
|
|
|
if (sample_length > 0)
|
|
|
|
|
{
|
|
|
|
|
if (sample_buffer_filled)
|
|
|
|
|
{
|
|
|
|
|
out_silence = false;
|
|
|
|
|
out_shift = sample_buffer;
|
|
|
|
|
sample_buffer_filled = false;
|
|
|
|
|
//TODO - cpu/apu DMC reads need to be emulated better!
|
|
|
|
|
}
|
|
|
|
|
Fetch();
|
|
|
|
|
}
|
|
|
|
|
else out_silence = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
out_bits_remaining--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void set_lenctr_en(bool en)
|
|
|
|
|
{
|
|
|
|
|
if (!en)
|
|
|
|
|
//disable playback
|
|
|
|
|
sample_length = 0;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//only start playback if sample length is 0 (playback is stopped
|
|
|
|
|
if (sample_length == 0)
|
|
|
|
|
{
|
|
|
|
|
sample_address = user_address;
|
|
|
|
|
sample_length = user_length;
|
|
|
|
|
out_deltacounter = 64;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//irq is acknowledged or sure to be clear, in either case
|
|
|
|
|
apu.dmc_irq = false;
|
|
|
|
|
apu.SyncIRQ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsLenCntNonZero()
|
|
|
|
|
{
|
|
|
|
|
return sample_length != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void WriteReg(int addr, byte val)
|
|
|
|
|
{
|
|
|
|
|
//Console.WriteLine("DMC writes addr={0}, val={1:x2}", addr, val);
|
|
|
|
|
switch (addr)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
irq_enabled = val.Bit(7);
|
|
|
|
|
loop_flag = val.Bit(6);
|
|
|
|
|
timer_reload = DMC_RATE_NTSC[val & 0xF];
|
|
|
|
|
if (!irq_enabled) apu.dmc_irq = false;
|
|
|
|
|
apu.SyncIRQ();
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
out_deltacounter = val & 0x7F;
|
|
|
|
|
//apu.nes.LogLine("~~ out_deltacounter set to {0}", out_deltacounter);
|
|
|
|
|
SyncSample();
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
user_address = 0xC000 | (val << 6);
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
user_length = (val << 4) + 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Fetch()
|
|
|
|
|
{
|
|
|
|
|
sample_buffer = apu.nes.ReadMemory((ushort)sample_address);
|
|
|
|
|
sample_buffer_filled = true;
|
|
|
|
|
sample_address = (ushort)(sample_address + 1);
|
|
|
|
|
sample_length--;
|
|
|
|
|
if (sample_length == 0)
|
|
|
|
|
{
|
|
|
|
|
if (loop_flag)
|
|
|
|
|
{
|
|
|
|
|
sample_address = user_address;
|
|
|
|
|
sample_length = user_length;
|
|
|
|
|
}
|
|
|
|
|
else if (irq_enabled) apu.dmc_irq = true;
|
2011-03-13 08:13:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-06-10 05:02:06 +00:00
|
|
|
|
public void SyncState(Serializer ser)
|
|
|
|
|
{
|
|
|
|
|
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);
|
2011-06-14 08:32:08 +00:00
|
|
|
|
ser.Sync("sequencer_irq", ref sequencer_irq);
|
|
|
|
|
ser.Sync("dmc_irq", ref dmc_irq);
|
2011-06-10 05:02:06 +00:00
|
|
|
|
pulse[0].SyncState(ser);
|
|
|
|
|
pulse[1].SyncState(ser);
|
|
|
|
|
triangle.SyncState(ser);
|
|
|
|
|
noise.SyncState(ser);
|
2011-06-14 08:32:08 +00:00
|
|
|
|
dmc.SyncState(ser);
|
2011-06-10 05:02:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
PulseUnit[] pulse = { new PulseUnit(0), new PulseUnit(1) };
|
|
|
|
|
TriangleUnit triangle = new TriangleUnit();
|
2011-03-20 04:43:27 +00:00
|
|
|
|
NoiseUnit noise = new NoiseUnit();
|
2011-06-14 08:32:08 +00:00
|
|
|
|
DMCUnit dmc;
|
|
|
|
|
bool sequencer_irq;
|
|
|
|
|
public bool dmc_irq;
|
|
|
|
|
|
|
|
|
|
public void RunDMCFetch()
|
|
|
|
|
{
|
|
|
|
|
dmc.Fetch();
|
|
|
|
|
}
|
2011-03-13 08:13:32 +00:00
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
int sequencer_counter, sequencer_step, sequencer_mode, sequencer_irq_inhibit;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
void sequencer_reset()
|
|
|
|
|
{
|
|
|
|
|
sequencer_counter = 0;
|
2011-09-24 17:14:55 +00:00
|
|
|
|
|
|
|
|
|
if (sequencer_mode == 1)
|
|
|
|
|
{
|
|
|
|
|
sequencer_step = 5;
|
|
|
|
|
sequencer_check();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
sequencer_step = 1;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
2011-03-13 08:13:32 +00:00
|
|
|
|
|
|
|
|
|
//21477272 master clock
|
|
|
|
|
//1789772 cpu clock (master / 12)
|
|
|
|
|
//240 apu clock (master / 89490) = (cpu / 7457)
|
2011-03-13 00:34:24 +00:00
|
|
|
|
void sequencer_tick()
|
|
|
|
|
{
|
|
|
|
|
sequencer_counter++;
|
|
|
|
|
//this figure is not valid for PAL. it must be recalculated
|
2011-03-13 08:13:32 +00:00
|
|
|
|
if (sequencer_counter != 7457) return;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
sequencer_counter = 0;
|
|
|
|
|
sequencer_step++;
|
|
|
|
|
sequencer_check();
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-14 08:32:08 +00:00
|
|
|
|
public void SyncIRQ()
|
|
|
|
|
{
|
|
|
|
|
nes.irq_apu = sequencer_irq | dmc_irq;
|
|
|
|
|
//if (nes.irq_apu) Console.WriteLine("apu irq");
|
|
|
|
|
nes.sync_irq();
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-13 00:34:24 +00:00
|
|
|
|
void sequencer_check()
|
|
|
|
|
{
|
2011-06-14 08:32:08 +00:00
|
|
|
|
//Console.WriteLine("sequencer mode {0} step {1}", sequencer_mode, sequencer_step);
|
2011-03-13 00:34:24 +00:00
|
|
|
|
switch (sequencer_mode)
|
|
|
|
|
{
|
|
|
|
|
case 0: //4-step
|
|
|
|
|
pulse[0].clock_env();
|
|
|
|
|
pulse[1].clock_env();
|
2011-03-13 08:13:32 +00:00
|
|
|
|
triangle.clock_linear_counter();
|
2011-03-20 04:43:27 +00:00
|
|
|
|
noise.clock_env();
|
2011-03-13 08:13:32 +00:00
|
|
|
|
if (sequencer_step == 2 || sequencer_step == 4)
|
2011-03-13 00:34:24 +00:00
|
|
|
|
{
|
2011-03-13 08:13:32 +00:00
|
|
|
|
pulse[0].clock_length_and_sweep();
|
|
|
|
|
pulse[1].clock_length_and_sweep();
|
|
|
|
|
triangle.clock_length_and_sweep();
|
2011-03-20 04:43:27 +00:00
|
|
|
|
noise.clock_length_and_sweep();
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
2011-03-13 08:13:32 +00:00
|
|
|
|
if (sequencer_step == 4)
|
2011-03-13 00:34:24 +00:00
|
|
|
|
{
|
|
|
|
|
if (sequencer_irq_inhibit == 0)
|
|
|
|
|
{
|
2011-06-14 08:32:08 +00:00
|
|
|
|
sequencer_irq = true;
|
|
|
|
|
SyncIRQ();
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
2011-09-24 17:14:55 +00:00
|
|
|
|
sequencer_step = 1;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 1: //5-step
|
2011-09-24 17:14:55 +00:00
|
|
|
|
if (sequencer_step != 4)
|
2011-03-13 00:34:24 +00:00
|
|
|
|
{
|
|
|
|
|
pulse[0].clock_env();
|
|
|
|
|
pulse[1].clock_env();
|
2011-03-13 08:13:32 +00:00
|
|
|
|
triangle.clock_linear_counter();
|
2011-09-24 17:14:55 +00:00
|
|
|
|
noise.clock_env();
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
2011-09-24 17:14:55 +00:00
|
|
|
|
if (sequencer_step == 2 || sequencer_step == 5)
|
2011-03-13 00:34:24 +00:00
|
|
|
|
{
|
2011-03-13 08:13:32 +00:00
|
|
|
|
pulse[0].clock_length_and_sweep();
|
|
|
|
|
pulse[1].clock_length_and_sweep();
|
|
|
|
|
triangle.clock_length_and_sweep();
|
2011-03-20 04:43:27 +00:00
|
|
|
|
noise.clock_length_and_sweep();
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
2011-03-13 08:13:32 +00:00
|
|
|
|
if (sequencer_step == 5)
|
2011-09-24 17:14:55 +00:00
|
|
|
|
sequencer_step = 1;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void WriteReg(int addr, byte val)
|
|
|
|
|
{
|
2011-09-24 17:14:55 +00:00
|
|
|
|
//Console.WriteLine("{0:X4} = {1:X2}", addr, val);
|
2011-03-13 00:34:24 +00:00
|
|
|
|
switch (addr)
|
|
|
|
|
{
|
2011-03-13 08:13:32 +00:00
|
|
|
|
case 0x4000: case 0x4001: case 0x4002: case 0x4003:
|
|
|
|
|
pulse[0].WriteReg(addr - 0x4000, val);
|
|
|
|
|
break;
|
|
|
|
|
case 0x4004: case 0x4005: case 0x4006: case 0x4007:
|
|
|
|
|
pulse[1].WriteReg(addr - 0x4004, val);
|
|
|
|
|
break;
|
|
|
|
|
case 0x4008: case 0x4009: case 0x400A: case 0x400B:
|
|
|
|
|
triangle.WriteReg(addr - 0x4008, val);
|
|
|
|
|
break;
|
2011-03-20 04:43:27 +00:00
|
|
|
|
case 0x400C: case 0x400D: case 0x400E: case 0x400F:
|
|
|
|
|
noise.WriteReg(addr - 0x400C, val);
|
|
|
|
|
break;
|
2011-06-14 08:32:08 +00:00
|
|
|
|
case 0x4010: case 0x4011: case 0x4012: case 0x4013:
|
|
|
|
|
dmc.WriteReg(addr - 0x4010, val);
|
|
|
|
|
break;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
case 0x4015:
|
2011-03-19 09:12:56 +00:00
|
|
|
|
pulse[0].set_lenctr_en(val & 1);
|
|
|
|
|
pulse[1].set_lenctr_en((val >> 1) & 1);
|
2011-06-10 05:02:06 +00:00
|
|
|
|
triangle.set_lenctr_en((val >> 2) & 1);
|
2011-03-20 04:43:27 +00:00
|
|
|
|
noise.set_lenctr_en((val >> 3) & 1);
|
2011-06-14 08:32:08 +00:00
|
|
|
|
dmc.set_lenctr_en(val.Bit(4));
|
2011-03-13 00:34:24 +00:00
|
|
|
|
break;
|
|
|
|
|
case 0x4017:
|
|
|
|
|
sequencer_mode = (val>>7)&1;
|
2011-03-19 09:12:56 +00:00
|
|
|
|
sequencer_irq_inhibit = (val >> 6) & 1;
|
|
|
|
|
if (sequencer_irq_inhibit == 1)
|
2011-06-14 08:32:08 +00:00
|
|
|
|
{
|
|
|
|
|
sequencer_irq = false;
|
|
|
|
|
SyncIRQ();
|
|
|
|
|
}
|
2011-03-13 00:34:24 +00:00
|
|
|
|
sequencer_reset();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte ReadReg(int addr)
|
|
|
|
|
{
|
|
|
|
|
switch (addr)
|
|
|
|
|
{
|
2011-03-19 09:12:56 +00:00
|
|
|
|
case 0x4015:
|
|
|
|
|
{
|
|
|
|
|
//notice a missing bit here. should properly emulate with empty 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.
|
2011-06-14 08:32:08 +00:00
|
|
|
|
int dmc_nonzero = dmc.IsLenCntNonZero() ? 1 : 0;
|
2011-03-20 04:43:27 +00:00
|
|
|
|
int noise_nonzero = noise.IsLenCntNonZero() ? 1 : 0;
|
2011-06-10 05:02:06 +00:00
|
|
|
|
int tri_nonzero = triangle.IsLenCntNonZero() ? 1 : 0;
|
2011-03-19 09:12:56 +00:00
|
|
|
|
int pulse1_nonzero = pulse[1].IsLenCntNonZero() ? 1 : 0;
|
|
|
|
|
int pulse0_nonzero = pulse[0].IsLenCntNonZero() ? 1 : 0;
|
2011-06-14 08:32:08 +00:00
|
|
|
|
int ret = ((dmc_irq?1:0) << 7) | ((sequencer_irq?1:0) << 6) | (dmc_nonzero << 4) | (noise_nonzero << 3) | (tri_nonzero<<2) | (pulse1_nonzero<<1) | (pulse0_nonzero);
|
|
|
|
|
sequencer_irq = false;
|
|
|
|
|
SyncIRQ();
|
2011-03-19 09:12:56 +00:00
|
|
|
|
return (byte)ret;
|
|
|
|
|
}
|
2011-03-13 00:34:24 +00:00
|
|
|
|
default:
|
2011-06-14 08:32:08 +00:00
|
|
|
|
//don't return 0xFF here or SMB will break
|
2011-03-20 02:25:47 +00:00
|
|
|
|
return 0x00;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Run(int cycles)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < cycles; i++)
|
|
|
|
|
RunOne();
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
public void DiscardSamples()
|
|
|
|
|
{
|
|
|
|
|
metaspu.buffer.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-20 20:42:12 +00:00
|
|
|
|
public void RunOne()
|
2011-03-13 00:34:24 +00:00
|
|
|
|
{
|
|
|
|
|
pulse[0].Run();
|
|
|
|
|
pulse[1].Run();
|
2011-03-13 08:13:32 +00:00
|
|
|
|
triangle.Run();
|
2011-03-20 04:43:27 +00:00
|
|
|
|
noise.Run();
|
2011-06-14 08:32:08 +00:00
|
|
|
|
dmc.Run();
|
2011-03-13 00:34:24 +00:00
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
int mix = 0;
|
|
|
|
|
mix += pulse[0].sample;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
mix += pulse[1].sample;
|
2011-03-13 08:13:32 +00:00
|
|
|
|
mix += triangle.sample;
|
2011-06-14 08:32:08 +00:00
|
|
|
|
mix += noise.sample>>1;
|
|
|
|
|
mix += dmc.sample;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
|
|
|
|
|
EmitSample(mix);
|
|
|
|
|
|
2011-03-13 01:40:09 +00:00
|
|
|
|
sequencer_tick();
|
2011-03-21 01:49:20 +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.
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
double accumulate;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
double timer;
|
|
|
|
|
Queue<int> squeue = new Queue<int>();
|
2011-03-13 08:13:32 +00:00
|
|
|
|
int last_hwsamp;
|
2011-03-20 03:32:43 +00:00
|
|
|
|
int panic_sample, panic_count;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
void EmitSample(int samp)
|
|
|
|
|
{
|
2011-03-20 03:32:43 +00:00
|
|
|
|
//kill the annoying hum that is a consequence of the shitty code below
|
|
|
|
|
if (samp == panic_sample)
|
|
|
|
|
panic_count++;
|
|
|
|
|
else panic_count = 0;
|
|
|
|
|
if (panic_count > 178977)
|
|
|
|
|
samp = 0;
|
|
|
|
|
else
|
|
|
|
|
panic_sample = samp;
|
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
int this_samp = samp;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
const double kMixRate = 44100.0/1789772.0;
|
|
|
|
|
const double kInvMixRate = (1 / kMixRate);
|
|
|
|
|
timer += kMixRate;
|
2011-03-13 08:13:32 +00:00
|
|
|
|
accumulate += samp;
|
|
|
|
|
if (timer <= 1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
accumulate -= samp;
|
|
|
|
|
timer -= 1;
|
|
|
|
|
double ratio = (timer / kMixRate);
|
|
|
|
|
double fractional = (this_samp - last_hwsamp) * ratio;
|
|
|
|
|
double factional_remainder = (this_samp - last_hwsamp) * (1-ratio);
|
|
|
|
|
accumulate += fractional;
|
|
|
|
|
|
2011-06-14 08:32:08 +00:00
|
|
|
|
accumulate *= 436; //32768/(15*4) -- adjust later for other sound channels
|
2011-03-13 08:13:32 +00:00
|
|
|
|
int outsamp = (int)(accumulate / kInvMixRate);
|
|
|
|
|
if (CFG_USE_METASPU)
|
|
|
|
|
metaspu.buffer.enqueue_sample((short)outsamp, (short)outsamp);
|
|
|
|
|
else squeue.Enqueue(outsamp);
|
|
|
|
|
accumulate = factional_remainder;
|
|
|
|
|
|
|
|
|
|
last_hwsamp = this_samp;
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-08-14 01:42:54 +00:00
|
|
|
|
MetaspuSoundProvider metaspu = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V);
|
2011-09-04 04:38:11 +00:00
|
|
|
|
public int MaxVolume { get; set; } // not supported
|
2011-03-13 08:13:32 +00:00
|
|
|
|
|
2011-03-13 00:34:24 +00:00
|
|
|
|
void ISoundProvider.GetSamples(short[] samples)
|
2011-03-13 08:13:32 +00:00
|
|
|
|
{
|
2011-03-20 03:32:43 +00:00
|
|
|
|
if (CFG_USE_METASPU)
|
|
|
|
|
{
|
2011-03-13 08:13:32 +00:00
|
|
|
|
metaspu.GetSamples(samples);
|
2011-04-06 05:31:33 +00:00
|
|
|
|
//foreach(short sample in samples) bw.Write((short)sample);
|
2011-03-20 03:32:43 +00:00
|
|
|
|
}
|
2011-03-13 08:13:32 +00:00
|
|
|
|
else
|
|
|
|
|
MyGetSamples(samples);
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-14 08:32:08 +00:00
|
|
|
|
//static BinaryWriter bw = new BinaryWriter(new FileStream("d:\\out.raw",FileMode.Create,FileAccess.Write,FileShare.Read));
|
2011-03-13 08:13:32 +00:00
|
|
|
|
void MyGetSamples(short[] samples)
|
2011-03-13 00:34:24 +00:00
|
|
|
|
{
|
|
|
|
|
//Console.WriteLine("a: {0} with todo: {1}",squeue.Count,samples.Length/2);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < samples.Length/2; i++)
|
|
|
|
|
{
|
|
|
|
|
int samp = 0;
|
|
|
|
|
if (squeue.Count != 0)
|
|
|
|
|
samp = squeue.Dequeue();
|
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
samples[i*2+0] = (short)(samp);
|
|
|
|
|
samples[i*2+1] = (short)(samp);
|
|
|
|
|
//bw.Write((short)samp);
|
2011-03-13 00:34:24 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} //class APU
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|