From 8a0cd52a5acfd5420eb85bab2377302568052322 Mon Sep 17 00:00:00 2001 From: zeromus Date: Sun, 13 Mar 2011 08:13:32 +0000 Subject: [PATCH] [NES] apu fixes and triangle generator --- .../Consoles/Nintendo/NES/APU.cs | 273 ++++++++++++++---- .../Consoles/Nintendo/NES/NES.cs | 2 + BizHawk.Emulation/Sound/Utilities/Metaspu.cs | 18 +- 3 files changed, 231 insertions(+), 62 deletions(-) diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs index 70a445ad43..2b703f123f 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs @@ -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 squeue = new Queue(); + 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); } } diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs index 77a06d6efc..788de4efa5 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs @@ -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 { diff --git a/BizHawk.Emulation/Sound/Utilities/Metaspu.cs b/BizHawk.Emulation/Sound/Utilities/Metaspu.cs index 180a0d1f1e..f34ab32eeb 100644 --- a/BizHawk.Emulation/Sound/Utilities/Metaspu.cs +++ b/BizHawk.Emulation/Sound/Utilities/Metaspu.cs @@ -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