From 8d9dfd5469a84de4db2066600270de7134062e33 Mon Sep 17 00:00:00 2001 From: zeromus Date: Tue, 14 Jun 2011 08:32:08 +0000 Subject: [PATCH] [NES] pcm sound channel --- .../Consoles/Nintendo/NES/APU.cs | 248 ++++++++++++++++-- .../Consoles/Nintendo/NES/Boards/AVE-NINA.cs | 5 +- BizHawk.Emulation/Util.cs | 6 + 3 files changed, 241 insertions(+), 18 deletions(-) diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs index 465f71131a..ae8f517a48 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs @@ -25,8 +25,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo public APU(NES nes) { this.nes = nes; + dmc = new DMCUnit(this); } + static int[] DMC_RATE_NTSC = { 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 }; 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%) @@ -386,7 +388,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo //misc.. int lenctr_en; int linear_counter, timer, timer_cnt_reload; - int seq; + int seq = 15; public int sample; public void SyncState(Serializer ser) @@ -416,6 +418,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo public void WriteReg(int addr, byte val) { + //Console.WriteLine("tri writes addr={0}, val={1:x2}", addr, val); switch (addr) { case 0: @@ -447,7 +450,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo //except when linear counter or //length counter is 0 - bool en = len_cnt != 0 && linear_counter != 0; + bool en = len_cnt != 0 && linear_counter != 0 || (seq != 16 && seq != 15); //length counter and linear counter //is clocked in frame counter. @@ -460,10 +463,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo timer = timer_cnt_reload; } //if(CFG_DECLICK) - // sample = TRIANGLE_TABLE[(seq+8)&0x1F]; + //sample = TRIANGLE_TABLE[(seq+8)&0x1F]; //else sample = TRIANGLE_TABLE[seq]; } + } @@ -485,9 +489,192 @@ namespace BizHawk.Emulation.Consoles.Nintendo { linear_counter--; } - if (control_flag == 0) + + //declick when the sound begins + //if (halt_flag == 1 && control_flag == 0) + //{ + // seq = 16; + // Console.WriteLine("declicked triangle"); + //} + + ////declick on end of sound + //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) { - halt_flag = 0; + 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; } } } @@ -498,15 +685,26 @@ namespace BizHawk.Emulation.Consoles.Nintendo 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("dmc_irq", ref dmc_irq); pulse[0].SyncState(ser); pulse[1].SyncState(ser); triangle.SyncState(ser); noise.SyncState(ser); + dmc.SyncState(ser); } PulseUnit[] pulse = { new PulseUnit(0), new PulseUnit(1) }; TriangleUnit triangle = new TriangleUnit(); NoiseUnit noise = new NoiseUnit(); + DMCUnit dmc; + bool sequencer_irq; + public bool dmc_irq; + + public void RunDMCFetch() + { + dmc.Fetch(); + } int sequencer_counter, sequencer_step, sequencer_mode, sequencer_irq_inhibit; void sequencer_reset() @@ -529,8 +727,16 @@ namespace BizHawk.Emulation.Consoles.Nintendo sequencer_check(); } + public void SyncIRQ() + { + nes.irq_apu = sequencer_irq | dmc_irq; + //if (nes.irq_apu) Console.WriteLine("apu irq"); + nes.sync_irq(); + } + void sequencer_check() { + //Console.WriteLine("sequencer mode {0} step {1}", sequencer_mode, sequencer_step); switch (sequencer_mode) { case 0: //4-step @@ -549,7 +755,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo { if (sequencer_irq_inhibit == 0) { - nes.irq_apu = true; + sequencer_irq = true; + SyncIRQ(); } sequencer_step = 0; } @@ -560,7 +767,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo pulse[0].clock_env(); pulse[1].clock_env(); triangle.clock_linear_counter(); - noise.clock_env(); + //noise.clock_env(); } if (sequencer_step == 1 || sequencer_step == 3) { @@ -578,6 +785,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo public void WriteReg(int addr, byte val) { + //Console.WriteLine("apu writereg {0:x4}", addr); switch (addr) { case 0x4000: case 0x4001: case 0x4002: case 0x4003: @@ -592,17 +800,24 @@ namespace BizHawk.Emulation.Consoles.Nintendo case 0x400C: case 0x400D: case 0x400E: case 0x400F: noise.WriteReg(addr - 0x400C, val); break; + case 0x4010: case 0x4011: case 0x4012: case 0x4013: + dmc.WriteReg(addr - 0x4010, val); + break; case 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)); break; case 0x4017: sequencer_mode = (val>>7)&1; sequencer_irq_inhibit = (val >> 6) & 1; if (sequencer_irq_inhibit == 1) - nes.irq_apu = false; + { + sequencer_irq = false; + SyncIRQ(); + } sequencer_reset(); break; } @@ -616,17 +831,18 @@ namespace BizHawk.Emulation.Consoles.Nintendo { //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. - int dmc_irq_flag = 0; //todo - int dmc_nonzero = 0; //todo + 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; - int ret = (dmc_irq_flag << 7) | ((nes.irq_apu?1:0) << 6) | (dmc_nonzero << 4) | (noise_nonzero << 3) | (tri_nonzero<<2) | (pulse1_nonzero<<1) | (pulse0_nonzero); - nes.irq_apu = false; + 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(); return (byte)ret; } default: + //don't return 0xFF here or SMB will break return 0x00; } } @@ -648,12 +864,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo pulse[1].Run(); triangle.Run(); noise.Run(); + dmc.Run(); int mix = 0; mix += pulse[0].sample; mix += pulse[1].sample; mix += triangle.sample; - mix += noise.sample; + mix += noise.sample>>1; + mix += dmc.sample; EmitSample(mix); @@ -694,7 +912,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo double factional_remainder = (this_samp - last_hwsamp) * (1-ratio); accumulate += fractional; - accumulate *= 540; //32768/(15*4) -- adjust later for other sound channels + accumulate *= 436; //32768/(15*4) -- adjust later for other sound channels int outsamp = (int)(accumulate / kInvMixRate); if (CFG_USE_METASPU) metaspu.buffer.enqueue_sample((short)outsamp, (short)outsamp); @@ -717,7 +935,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo MyGetSamples(samples); } - //static BinaryWriter bw = new BinaryWriter(File.OpenWrite("d:\\out.raw")); + //static BinaryWriter bw = new BinaryWriter(new FileStream("d:\\out.raw",FileMode.Create,FileAccess.Write,FileShare.Read)); void MyGetSamples(short[] samples) { //Console.WriteLine("a: {0} with todo: {1}",squeue.Count,samples.Length/2); diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/AVE-NINA.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/AVE-NINA.cs index ea52e6bde7..24f3701815 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/AVE-NINA.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/AVE-NINA.cs @@ -97,7 +97,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo { //configuration int prg_bank_mask_32k, chr_bank_mask_8k; - bool mirror_control_enabled; + //bool mirror_control_enabled; //state int chr_bank_8k, prg_bank_32k; @@ -145,8 +145,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo chr_bank_8k = ((value >> 3) & 7); prg_bank_32k &= prg_bank_mask_32k; prg_bank_32k &= chr_bank_mask_8k; - if (mirror_control_enabled) - SetMirrorType(value.Bit(7) ? EMirrorType.Vertical : EMirrorType.Horizontal); + //if (mirror_control_enabled) SetMirrorType(value.Bit(7) ? EMirrorType.Vertical : EMirrorType.Horizontal); //NES.LogLine("chr={0:X2}, prg={1:X2}, with val={2:X2}", chr_reg, prg_reg, value); break; } diff --git a/BizHawk.Emulation/Util.cs b/BizHawk.Emulation/Util.cs index c7a50dcf9a..c08c36c65e 100644 --- a/BizHawk.Emulation/Util.cs +++ b/BizHawk.Emulation/Util.cs @@ -93,6 +93,12 @@ namespace BizHawk return (b & (1 << index)) != 0; } + public static bool Bit(this int b, int index) + { + return (b & (1 << index)) != 0; + } + + public static string GetPrecedingString(this string str, string value) { int index = str.IndexOf(value);