[NES] apu fixes and triangle generator
This commit is contained in:
parent
ec66663dbc
commit
8a0cd52a5a
|
@ -1,6 +1,9 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Sound;
|
||||
|
||||
//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
|
||||
|
@ -13,6 +16,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
{
|
||||
public class APU : ISoundProvider
|
||||
{
|
||||
public static bool CFG_USE_METASPU = true;
|
||||
public static bool CFG_DECLICK = true;
|
||||
|
||||
NES nes;
|
||||
public APU(NES nes)
|
||||
{
|
||||
|
@ -26,15 +32,20 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
{0,1,1,1,1,0,0,0}, //(50%)
|
||||
{1,0,0,1,1,1,1,1}, //(25% negated (75%))
|
||||
};
|
||||
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
|
||||
};
|
||||
|
||||
|
||||
|
||||
class PulseUnit
|
||||
{
|
||||
public PulseUnit(int unit) { this.unit = unit; }
|
||||
public int unit;
|
||||
|
||||
//reg0
|
||||
int duty, length_halt, envelope_constant, envelope_cnt_value;
|
||||
int duty_cnt, env_loop, env_constant, env_cnt_value;
|
||||
//reg1
|
||||
int sweep_en, sweep_period, negate, shiftcount;
|
||||
//reg2/3
|
||||
|
@ -50,10 +61,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
switch(addr)
|
||||
{
|
||||
case 0:
|
||||
envelope_cnt_value = val & 0xF;
|
||||
envelope_constant = (val >> 4) & 1;
|
||||
length_halt = (val >> 5) & 1;
|
||||
duty = (val >> 6) & 3;
|
||||
env_cnt_value = val & 0xF;
|
||||
env_constant = (val >> 4) & 1;
|
||||
env_loop = (val >> 5) & 1;
|
||||
duty_cnt = (val >> 6) & 3;
|
||||
break;
|
||||
case 1:
|
||||
shiftcount = val & 7;
|
||||
|
@ -63,28 +74,29 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
break;
|
||||
case 2:
|
||||
timer_reload_value = (timer_reload_value & ~0xFF) | val;
|
||||
timer_raw_reload_value = timer_reload_value;
|
||||
calc_sweep_unit();
|
||||
break;
|
||||
case 3:
|
||||
len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F];
|
||||
timer_reload_value = (timer_reload_value & 0xFF) | ((val & 0x07) << 8);
|
||||
timer_raw_reload_value = timer_reload_value;
|
||||
sq_seq = 0;
|
||||
timer_raw_reload_value = timer_reload_value * 2;
|
||||
duty_step = 0;
|
||||
timer_counter = timer_raw_reload_value;
|
||||
calc_sweep_unit();
|
||||
env_start_flag = 1;
|
||||
//serves as a useful note-on diagnostic
|
||||
Console.WriteLine("{0} timer_reload_value: {1}", unit, timer_reload_value);
|
||||
//Console.WriteLine("{0} timer_reload_value: {1}", unit, timer_reload_value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int swp_val_result;
|
||||
bool swp_silence;
|
||||
int sq_seq;
|
||||
int duty_step;
|
||||
int timer_counter;
|
||||
public int sample;
|
||||
int envelope_value;
|
||||
|
||||
int env_start_flag, env_divider, env_counter, env_output;
|
||||
|
||||
void calc_sweep_unit()
|
||||
{
|
||||
|
@ -102,7 +114,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
|
||||
}
|
||||
|
||||
public void clock_sweep()
|
||||
public void clock_length_and_sweep()
|
||||
{
|
||||
//(as well as length counter)
|
||||
if(sweep_en==1)
|
||||
|
@ -111,12 +123,35 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
|
||||
public void clock_env()
|
||||
{
|
||||
if (env_start_flag == 1)
|
||||
{
|
||||
env_start_flag = 0;
|
||||
env_divider = (env_cnt_value + 1);
|
||||
env_counter = 15;
|
||||
}
|
||||
else
|
||||
{
|
||||
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--;
|
||||
}
|
||||
}
|
||||
if (env_constant == 1)
|
||||
env_output = env_cnt_value;
|
||||
else env_output = env_counter;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
envelope_value = 15; //HAX
|
||||
|
||||
//sweep units are figured out during memory writes to the regs
|
||||
//that set the timer, length counter are figured out in the
|
||||
//writes and frame counter, and envelope is set through the memory
|
||||
|
@ -125,44 +160,131 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
timer_counter--;
|
||||
if (timer_counter == 0)
|
||||
{
|
||||
sq_seq = (sq_seq + 1) & 7;
|
||||
duty_step = (duty_step + 1) & 7;
|
||||
//reload timer
|
||||
timer_counter = timer_raw_reload_value + 2;
|
||||
}
|
||||
if (PULSE_DUTY[duty,sq_seq] == 1) //we are outputting something
|
||||
if (PULSE_DUTY[duty_cnt, duty_step] == 1) //we are outputting something
|
||||
{
|
||||
if (envelope_constant==1)
|
||||
sample = envelope_cnt_value;
|
||||
else
|
||||
sample = envelope_value;
|
||||
sample = env_output;
|
||||
|
||||
if (swp_silence)
|
||||
sample = 0;
|
||||
|
||||
if (len_cnt==0) //length counter is 0
|
||||
sample = 0; //silenced
|
||||
//if (len_cnt==0) //length counter is 0
|
||||
// sample = 0; //silenced
|
||||
}
|
||||
else
|
||||
sample = 0; //duty cycle is 0, silenced.
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
PulseUnit[] pulse = { new PulseUnit(0), new PulseUnit(1) };
|
||||
|
||||
class TriangleUnit
|
||||
{
|
||||
//reg0
|
||||
int linear_counter_reload, control_flag;
|
||||
//reg1 (n/a)
|
||||
//reg2/3
|
||||
int timer_cnt, length_counter_load, halt_flag;
|
||||
|
||||
public void WriteReg(int addr, byte val)
|
||||
{
|
||||
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;
|
||||
break;
|
||||
case 3:
|
||||
timer_cnt = (timer_cnt & 0xFF) | ((val & 0x7) << 8);
|
||||
timer_cnt_reload = timer_cnt + 1;
|
||||
length_counter_load = (val>>3)&0x1F;
|
||||
halt_flag = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int linear_counter, timer, timer_cnt_reload;
|
||||
int seq;
|
||||
public int sample;
|
||||
|
||||
public void Run()
|
||||
{
|
||||
//when clocked by timer
|
||||
//seq steps forward
|
||||
//except when linear counter or
|
||||
//length counter is 0
|
||||
|
||||
bool en = length_counter_load != 0 && linear_counter != 0;
|
||||
|
||||
//length counter and linear counter
|
||||
//is clocked in frame counter.
|
||||
if (en)
|
||||
{
|
||||
timer--;
|
||||
if (timer == 0)
|
||||
{
|
||||
seq = (seq + 1) & 0x1F;
|
||||
timer = timer_cnt_reload;
|
||||
}
|
||||
if(CFG_DECLICK)
|
||||
sample = TRIANGLE_TABLE[(seq+8)&0x1F];
|
||||
else
|
||||
sample = TRIANGLE_TABLE[seq];
|
||||
}
|
||||
else
|
||||
sample = 0;
|
||||
}
|
||||
|
||||
|
||||
public void clock_length_and_sweep()
|
||||
{
|
||||
}
|
||||
|
||||
public void clock_linear_counter()
|
||||
{
|
||||
// Console.WriteLine("linear_counter: {0}", linear_counter);
|
||||
if (halt_flag == 1)
|
||||
{
|
||||
timer = timer_cnt_reload;
|
||||
linear_counter = linear_counter_reload;
|
||||
}
|
||||
else if (linear_counter != 0)
|
||||
{
|
||||
linear_counter--;
|
||||
}
|
||||
if (control_flag == 0)
|
||||
{
|
||||
halt_flag = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PulseUnit[] pulse = { new PulseUnit(0), new PulseUnit(1) };
|
||||
TriangleUnit triangle = new TriangleUnit();
|
||||
|
||||
int sequencer_counter, sequencer_step, sequencer_mode, sequencer_irq_inhibit, sequencer_irq_flag;
|
||||
void sequencer_reset()
|
||||
{
|
||||
sequencer_counter = 0;
|
||||
sequencer_step = 0;
|
||||
sequencer_check();
|
||||
sequencer_step = 1;
|
||||
if(sequencer_mode == 1) sequencer_check();
|
||||
}
|
||||
|
||||
//21477272 master clock
|
||||
//1789772 cpu clock (master / 12)
|
||||
//240 apu clock (master / 89490) = (cpu / 7457)
|
||||
void sequencer_tick()
|
||||
{
|
||||
sequencer_counter++;
|
||||
//this figure is not valid for PAL. it must be recalculated
|
||||
if (sequencer_counter != 89490) return;
|
||||
if (sequencer_counter != 7457) return;
|
||||
sequencer_counter = 0;
|
||||
sequencer_step++;
|
||||
sequencer_check();
|
||||
|
@ -175,12 +297,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
case 0: //4-step
|
||||
pulse[0].clock_env();
|
||||
pulse[1].clock_env();
|
||||
if (sequencer_step == 1 || sequencer_step == 3)
|
||||
triangle.clock_linear_counter();
|
||||
if (sequencer_step == 2 || sequencer_step == 4)
|
||||
{
|
||||
pulse[0].clock_sweep();
|
||||
pulse[1].clock_sweep();
|
||||
pulse[0].clock_length_and_sweep();
|
||||
pulse[1].clock_length_and_sweep();
|
||||
triangle.clock_length_and_sweep();
|
||||
}
|
||||
if (sequencer_step == 3)
|
||||
if (sequencer_step == 4)
|
||||
{
|
||||
if (sequencer_irq_inhibit == 0)
|
||||
{
|
||||
|
@ -192,17 +316,19 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
}
|
||||
break;
|
||||
case 1: //5-step
|
||||
if (sequencer_step != 4)
|
||||
if (sequencer_step != 5)
|
||||
{
|
||||
pulse[0].clock_env();
|
||||
pulse[1].clock_env();
|
||||
triangle.clock_linear_counter();
|
||||
}
|
||||
if (sequencer_step == 0 || sequencer_step == 2)
|
||||
if (sequencer_step == 1 || sequencer_step == 3)
|
||||
{
|
||||
pulse[0].clock_sweep();
|
||||
pulse[1].clock_sweep();
|
||||
pulse[0].clock_length_and_sweep();
|
||||
pulse[1].clock_length_and_sweep();
|
||||
triangle.clock_length_and_sweep();
|
||||
}
|
||||
if (sequencer_step == 4)
|
||||
if (sequencer_step == 5)
|
||||
sequencer_step = 0;
|
||||
break;
|
||||
}
|
||||
|
@ -211,12 +337,17 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
|
||||
public void WriteReg(int addr, byte val)
|
||||
{
|
||||
if (addr >= 0x4000 && addr <= 0x4003)
|
||||
pulse[0].WriteReg(addr - 0x4000, val);
|
||||
if (addr >= 0x4004 && addr <= 0x4007)
|
||||
pulse[1].WriteReg(addr - 0x4004, val);
|
||||
switch (addr)
|
||||
{
|
||||
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;
|
||||
case 0x4015:
|
||||
pulse[0].lenctr_en = (val & 1);
|
||||
pulse[1].lenctr_en = ((val>>1) & 1);
|
||||
|
@ -249,9 +380,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
{
|
||||
pulse[0].Run();
|
||||
pulse[1].Run();
|
||||
triangle.Run();
|
||||
|
||||
int mix = pulse[0].sample;
|
||||
int mix = 0;
|
||||
mix += pulse[0].sample;
|
||||
mix += pulse[1].sample;
|
||||
mix += triangle.sample;
|
||||
|
||||
EmitSample(mix);
|
||||
|
||||
|
@ -264,32 +398,49 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
//changes to affect it on the *next* cycle.
|
||||
}
|
||||
|
||||
int accumulate;
|
||||
double accumulate;
|
||||
double timer;
|
||||
Queue<int> squeue = new Queue<int>();
|
||||
int last_hwsamp;
|
||||
void EmitSample(int samp)
|
||||
{
|
||||
int this_samp = samp;
|
||||
const double kMixRate = 44100.0/1789772.0;
|
||||
const double kInvMixRate = (1 / kMixRate);
|
||||
timer += kMixRate;
|
||||
for(;;)
|
||||
{
|
||||
if (timer <= 1)
|
||||
{
|
||||
accumulate += samp;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
timer -= 1;
|
||||
int outsamp = (int)(accumulate / kInvMixRate);
|
||||
squeue.Enqueue(outsamp);
|
||||
accumulate -= (int)(outsamp*kInvMixRate);
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
accumulate *= 600;
|
||||
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;
|
||||
}
|
||||
|
||||
MetaspuSoundProvider metaspu = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_Z);
|
||||
|
||||
void ISoundProvider.GetSamples(short[] samples)
|
||||
{
|
||||
if(CFG_USE_METASPU)
|
||||
metaspu.GetSamples(samples);
|
||||
else
|
||||
MyGetSamples(samples);
|
||||
}
|
||||
|
||||
//static BinaryWriter bw = new BinaryWriter(File.OpenWrite("d:\\out.raw"));
|
||||
void MyGetSamples(short[] samples)
|
||||
{
|
||||
//Console.WriteLine("a: {0} with todo: {1}",squeue.Count,samples.Length/2);
|
||||
|
||||
|
@ -299,9 +450,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
if (squeue.Count != 0)
|
||||
samp = squeue.Dequeue();
|
||||
|
||||
//if(samp != 0) Console.WriteLine("samp: {0}", samp);
|
||||
samples[i*2+0] = (short)(samp * 256);
|
||||
samples[i*2+1] = (short)(samp * 256);
|
||||
samples[i*2+0] = (short)(samp);
|
||||
samples[i*2+1] = (short)(samp);
|
||||
//bw.Write((short)samp);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ using System.IO;
|
|||
using System.Collections.Generic;
|
||||
using BizHawk.Emulation.CPUs.M6502;
|
||||
|
||||
//TODO - redo all timekeeping in terms of master clock
|
||||
|
||||
namespace BizHawk.Emulation.Consoles.Nintendo
|
||||
{
|
||||
|
||||
|
|
|
@ -5,8 +5,13 @@ namespace BizHawk.Emulation.Sound
|
|||
{
|
||||
public class MetaspuSoundProvider : ISoundProvider
|
||||
{
|
||||
public ISynchronizingAudioBuffer buffer = Metaspu.metaspu_construct(ESynchMethod.ESynchMethod_Z);
|
||||
public ISynchronizingAudioBuffer buffer;
|
||||
public MetaspuSoundProvider(ESynchMethod method)
|
||||
{
|
||||
buffer = Metaspu.metaspu_construct(method);
|
||||
}
|
||||
public MetaspuSoundProvider()
|
||||
: this(ESynchMethod.ESynchMethod_Z)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -19,6 +24,7 @@ namespace BizHawk.Emulation.Sound
|
|||
public interface ISynchronizingAudioBuffer
|
||||
{
|
||||
void enqueue_samples(short[] buf, int samples_provided);
|
||||
void enqueue_sample(short left, short right);
|
||||
|
||||
//returns the number of samples actually supplied, which may not match the number requested
|
||||
int output_samples(short[] buf, int samples_requested);
|
||||
|
@ -63,6 +69,11 @@ namespace BizHawk.Emulation.Sound
|
|||
//adjustobuf(200,1000)
|
||||
bool mixqueue_go = false;
|
||||
|
||||
public void enqueue_sample(short left, short right)
|
||||
{
|
||||
adjustobuf.enqueue(left, right);
|
||||
}
|
||||
|
||||
public void enqueue_samples(short[] buf, int samples_provided)
|
||||
{
|
||||
int ctr = 0;
|
||||
|
@ -267,6 +278,11 @@ namespace BizHawk.Emulation.Sound
|
|||
}
|
||||
}
|
||||
|
||||
public void enqueue_sample(short left, short right)
|
||||
{
|
||||
sampleQueue.Add(new ssamp(left,right));
|
||||
}
|
||||
|
||||
public int output_samples(short[] buf, int samples_requested)
|
||||
{
|
||||
Console.WriteLine("{0} {1}", samples_requested, sampleQueue.Count); //add this line
|
||||
|
|
Loading…
Reference in New Issue