From 8952b842b1fa3e33556fe96fbeac5f2609efe72e Mon Sep 17 00:00:00 2001 From: "ALIENJACK\\alien" Date: Tue, 6 Aug 2024 19:50:33 +0800 Subject: [PATCH] =?UTF-8?q?APU=E8=A1=A5=E5=85=A8=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assets/VirtualNes.Core/APU.cs | 11 +- .../Assets/VirtualNes.Core/ApuEX/APU_MMC5.cs | 349 +++++++++++++++++- .../Assets/VirtualNes.Core/ApuEX/APU_VRC7.cs | 80 +++- .../VirtualNes.Core/CoreLibs/Emu2413.cs | 61 ++- .../Assets/VirtualNes.Core/PAD.cs | 2 - 5 files changed, 483 insertions(+), 20 deletions(-) diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs index 1558bf2..51acc7b 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs @@ -60,7 +60,16 @@ namespace VirtualNes.Core m_bMute[i] = true; } - public void Dispose() { } + public void Dispose() + { + @internal.Dispose(); + vrc6.Dispose(); + vrc7.Dispose(); + mmc5.Dispose(); + fds.Dispose(); + n106.Dispose(); + fme7.Dispose(); + } private int[] vol = new int[24]; static double cutofftemp = (2.0 * 3.141592653579 * 40.0); diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_MMC5.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_MMC5.cs index 0f9c16d..6b12383 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_MMC5.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_MMC5.cs @@ -1,36 +1,219 @@ -using System; +using Codice.CM.Client.Differences; +using System; namespace VirtualNes.Core { public class APU_MMC5 : APU_INTERFACE { + public const int RECTANGLE_VOL_SHIFT = 8; + public const int DAOUT_VOL_SHIFT = 6; + SYNCRECTANGLE sch0 = new SYNCRECTANGLE(); SYNCRECTANGLE sch1 = new SYNCRECTANGLE(); + RECTANGLE ch0 = new RECTANGLE(); + RECTANGLE ch1 = new RECTANGLE(); + + byte reg5010; + byte reg5011; + byte reg5015; + byte sync_reg5015; + int FrameCycle; + float cpu_clock; + int cycle_rate; + + // Tables + static int[] vbl_length = new int[32]; + static int[] duty_lut = new int[4]; + + static int[] decay_lut = new int[16]; + static int[] vbl_lut = new int[32]; + + public APU_MMC5() + { + // 仮設定 + Reset(APU_INTERFACE.APU_CLOCK, 22050); + } public override void Reset(float fClock, int nRate) { - //todo : 实现 + sch0.ZeroMemory(); + sch1.ZeroMemory(); + + reg5010 = reg5011 = reg5015 = 0; + + sync_reg5015 = 0; + FrameCycle = 0; + + Setup(fClock, nRate); + + for (ushort addr = 0x5000; addr <= 0x5015; addr++) + { + Write(addr, 0); + } } public override void Setup(float fClock, int nRate) { - //todo : 实现 + cpu_clock = fClock; + cycle_rate = (int)(fClock * 65536.0f / nRate); + + // Create Tables + int i; + int samples = (int)(nRate / 60.0f); + for (i = 0; i < 16; i++) + decay_lut[i] = (i + 1) * samples * 5; + for (i = 0; i < 32; i++) + vbl_lut[i] = vbl_length[i] * samples * 5; } public override void Write(ushort addr, byte data) { - //todo : 实现 - } - - public override int Process(int channel) - { - //todo : 实现 - return 0; + switch (addr) + { + // MMC5 CH0 rectangle + case 0x5000: + ch0.reg[0] = data; + ch0.volume = (byte)(data & 0x0F); + ch0.holdnote = (byte)(data & 0x20); + ch0.fixed_envelope = (byte)(data & 0x10); + ch0.env_decay = decay_lut[data & 0x0F]; + ch0.duty_flip = duty_lut[data >> 6]; + break; + case 0x5001: + ch0.reg[1] = data; + break; + case 0x5002: + ch0.reg[2] = data; + ch0.freq = INT2FIX(((ch0.reg[3] & 0x07) << 8) + data + 1); + break; + case 0x5003: + ch0.reg[3] = data; + ch0.vbl_length = vbl_lut[data >> 3]; + ch0.env_vol = 0; + ch0.freq = INT2FIX(((data & 0x07) << 8) + ch0.reg[2] + 1); + if ((reg5015 & 0x01) != 0) + ch0.enable = 0xFF; + break; + // MMC5 CH1 rectangle + case 0x5004: + ch1.reg[0] = data; + ch1.volume = (byte)(data & 0x0F); + ch1.holdnote = (byte)(data & 0x20); + ch1.fixed_envelope = (byte)(data & 0x10); + ch1.env_decay = decay_lut[data & 0x0F]; + ch1.duty_flip = duty_lut[data >> 6]; + break; + case 0x5005: + ch1.reg[1] = data; + break; + case 0x5006: + ch1.reg[2] = data; + ch1.freq = INT2FIX(((ch1.reg[3] & 0x07) << 8) + data + 1); + break; + case 0x5007: + ch1.reg[3] = data; + ch1.vbl_length = vbl_lut[data >> 3]; + ch1.env_vol = 0; + ch1.freq = INT2FIX(((data & 0x07) << 8) + ch1.reg[2] + 1); + if ((reg5015 & 0x02) != 0) + ch1.enable = 0xFF; + break; + case 0x5010: + reg5010 = data; + break; + case 0x5011: + reg5011 = data; + break; + case 0x5012: + case 0x5013: + case 0x5014: + break; + case 0x5015: + reg5015 = data; + if ((reg5015 & 0x01) != 0) + { + ch0.enable = 0xFF; + } + else + { + ch0.enable = 0; + ch0.vbl_length = 0; + } + if ((reg5015 & 0x02) != 0) + { + ch1.enable = 0xFF; + } + else + { + ch1.enable = 0; + ch1.vbl_length = 0; + } + break; + } } internal void SyncWrite(ushort addr, byte data) { - //todo : 实现 + switch (addr) + { + // MMC5 CH0 rectangle + case 0x5000: + sch0.reg[0] = data; + sch0.holdnote = (byte)(data & 0x20); + break; + case 0x5001: + case 0x5002: + sch0.reg[addr & 3] = data; + break; + case 0x5003: + sch0.reg[3] = data; + sch0.vbl_length = vbl_length[data >> 3]; + if ((sync_reg5015 & 0x01) != 0) + sch0.enable = 0xFF; + break; + // MMC5 CH1 rectangle + case 0x5004: + sch1.reg[0] = data; + sch1.holdnote = (byte)(data & 0x20); + break; + case 0x5005: + case 0x5006: + sch1.reg[addr & 3] = data; + break; + case 0x5007: + sch1.reg[3] = data; + sch1.vbl_length = vbl_length[data >> 3]; + if ((sync_reg5015 & 0x02) != 0) + sch1.enable = 0xFF; + break; + case 0x5010: + case 0x5011: + case 0x5012: + case 0x5013: + case 0x5014: + break; + case 0x5015: + sync_reg5015 = data; + if ((sync_reg5015 & 0x01) != 0) + { + sch0.enable = 0xFF; + } + else + { + sch0.enable = 0; + sch0.vbl_length = 0; + } + if ((sync_reg5015 & 0x02) != 0) + { + sch1.enable = 0xFF; + } + else + { + sch1.enable = 0; + sch1.vbl_length = 0; + } + break; + } } internal byte SyncRead(ushort addr) @@ -46,6 +229,118 @@ namespace VirtualNes.Core return data; } + public override bool Sync(int cycles) + { + FrameCycle += cycles; + if (FrameCycle >= 7457 * 5 / 2) + { + FrameCycle -= 7457 * 5 / 2; + + if (sch0.enable != 0 && sch0.holdnote == 0) + { + if ((sch0.vbl_length) != 0) + { + sch0.vbl_length--; + } + } + if (sch1.enable != 0 && sch1.holdnote == 0) + { + if ((sch1.vbl_length) != 0) + { + sch1.vbl_length--; + } + } + } + + return false; + } + + public override int Process(int channel) + { + switch (channel) + { + case 0: + return RectangleRender(ch0); + case 1: + return RectangleRender(ch1); + case 2: + return reg5011 << DAOUT_VOL_SHIFT; + } + + return 0; + } + + private int RectangleRender(RECTANGLE ch) + { + if (ch.enable == 0 || ch.vbl_length <= 0) + return 0; + + // vbl length counter + if (ch.holdnote == 0) + ch.vbl_length -= 5; + + // envelope unit + ch.env_phase -= 5 * 4; + while (ch.env_phase < 0) + { + ch.env_phase += ch.env_decay; + if ((ch.holdnote) != 0) + ch.env_vol = (byte)((ch.env_vol + 1) & 0x0F); + else if (ch.env_vol < 0x0F) + ch.env_vol++; + } + + if (ch.freq < INT2FIX(8)) + return 0; + + int volume; + if ((ch.fixed_envelope) != 0) + volume = ch.volume; + else + volume = (0x0F - ch.env_vol); + + int output = volume << RECTANGLE_VOL_SHIFT; + + ch.phaseacc -= cycle_rate; + if (ch.phaseacc >= 0) + { + if (ch.adder < ch.duty_flip) + ch.output_vol = output; + else + ch.output_vol = -output; + return ch.output_vol; + } + + if (ch.freq > cycle_rate) + { + ch.phaseacc += ch.freq; + ch.adder = (ch.adder + 1) & 0x0F; + if (ch.adder < ch.duty_flip) + ch.output_vol = output; + else + ch.output_vol = -output; + } + else + { + // 加重平均 + int num_times, total; + num_times = total = 0; + while (ch.phaseacc < 0) + { + ch.phaseacc += ch.freq; + ch.adder = (ch.adder + 1) & 0x0F; + if (ch.adder < ch.duty_flip) + total += output; + else + total -= output; + num_times++; + } + ch.output_vol = total / num_times; + } + + return ch.output_vol; + } + public class SYNCRECTANGLE { // For sync @@ -54,6 +349,38 @@ namespace VirtualNes.Core public byte holdnote; public byte[] dummy = new byte[2]; public int vbl_length; + + public void ZeroMemory() + { + Array.Clear(reg, 0, reg.Length); + enable = 0; + holdnote = 0; + Array.Clear(dummy, 0, dummy.Length); + vbl_length = 0; + } + } + + public class RECTANGLE + { + public byte[] reg = new byte[4]; + public byte enable; + + public int vbl_length; + + public int phaseacc; + public int freq; + + public int output_vol; + public byte fixed_envelope; + public byte holdnote; + public byte volume; + + public byte env_vol; + public int env_phase; + public int env_decay; + + public int adder; + public int duty_flip; } } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_VRC7.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_VRC7.cs index 448ddd0..d7acd97 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_VRC7.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_VRC7.cs @@ -1,25 +1,95 @@ -namespace VirtualNes.Core +using System; +using System.Net; + +namespace VirtualNes.Core { public class APU_VRC7 : APU_INTERFACE { + OPLL VRC7_OPLL; + byte address; + + public APU_VRC7() + { + Emu2413API.OPLL_init(3579545, 22050); // 仮のサンプリングレート + VRC7_OPLL = Emu2413API.OPLL_new(); + + if (VRC7_OPLL != null) + { + Emu2413API.OPLL_reset(VRC7_OPLL); + Emu2413API.OPLL_reset_patch(VRC7_OPLL, Emu2413API.OPLL_VRC7_TONE); + VRC7_OPLL.masterVolume = 128; + } + + // 仮設定 + Reset(APU_CLOCK, 22050); + } + + public override void Dispose() + { + if (VRC7_OPLL != null) + { + Emu2413API.OPLL_delete(VRC7_OPLL); + VRC7_OPLL = null; + // OPLL_close(); // 無くても良い(中身無し) + } + } + public override void Reset(float fClock, int nRate) { - //todo : 实现 + if (VRC7_OPLL != null) + { + Emu2413API.OPLL_reset(VRC7_OPLL); + Emu2413API.OPLL_reset_patch(VRC7_OPLL, Emu2413API.OPLL_VRC7_TONE); + VRC7_OPLL.masterVolume = 128; + } + + address = 0; + + Setup(fClock, nRate); } public override void Setup(float fClock, int nRate) { - //todo : 实现 + Emu2413API.OPLL_setClock((UInt32)(fClock * 2.0f), (UInt32)nRate); } public override void Write(ushort addr, byte data) { - //todo : 实现 + if (VRC7_OPLL != null) + { + if (addr == 0x9010) + { + address = data; + } + else if (addr == 0x9030) + { + Emu2413API.OPLL_writeReg(VRC7_OPLL, address, data); + } + } } public override int Process(int channel) { - //todo : 实现 + if (VRC7_OPLL != null) + return Emu2413API.OPLL_calc(VRC7_OPLL); + + return 0; + } + + float[] blkmul = { 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f }; + public override int GetFreq(int channel) + { + if (VRC7_OPLL != null && channel < 8) + { + int fno = ((VRC7_OPLL.reg[0x20 + channel] & 0x01) << 8) + VRC7_OPLL.reg[0x10 + channel]; + int blk = (VRC7_OPLL.reg[0x20 + channel] >> 1) & 0x07; + + if ((VRC7_OPLL.reg[0x20 + channel] & 0x10) != 0) + { + return (int)((256.0d * (double)fno * blkmul[blk]) / ((double)(1 << 18) / (3579545.0 / 72.0))); + } + } + return 0; } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/Emu2413.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/Emu2413.cs index 964fa3b..d37e6ec 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/Emu2413.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/Emu2413.cs @@ -80,10 +80,36 @@ namespace VirtualNes.Core public UInt32 noiseB_idx; public UInt32 noiseA_dphase; public UInt32 noiseB_dphase; + + public int masterVolume; /* 0min -- 64 -- 127 max (Liner) */ } public static class Emu2413API { + /* Bits for Pitch and Amp modulator */ + public const int PM_PG_BITS = 8; + public const int PM_PG_WIDTH = 1 << PM_PG_BITS; + public const int PM_DP_BITS = 16; + public const int PM_DP_WIDTH = (1 << PM_DP_BITS); + public const int AM_PG_BITS = 8; + public const int AM_PG_WIDTH = (1 << AM_PG_BITS); + public const int AM_DP_BITS = 16; + public const int AM_DP_WIDTH = (1 << AM_DP_BITS); + + /* PM table is calcurated by PM_AMP * pow(2,PM_DEPTH*sin(x)/1200) */ + public const int PM_AMP_BITS = 8; + public const int PM_AMP = (1 << PM_AMP_BITS); + + /* PM speed(Hz) and depth(cent) */ + public const double PM_SPEED = 6.4d; + public const double PM_DEPTH = 13.75d; + + public const int OPLL_2413_TONE = 0; + public const int OPLL_VRC7_TONE = 1; + + static int[] pmtable = new int[PM_PG_WIDTH]; + static int[] amtable = new int[AM_PG_WIDTH]; + public static void OPLL_init(UInt32 c, UInt32 r) { makePmTable(); @@ -97,7 +123,7 @@ namespace VirtualNes.Core OPLL_setClock(c, r); } - private static void OPLL_setClock(uint c, uint r) + internal static void OPLL_setClock(uint c, uint r) { throw new NotImplementedException(); } @@ -138,6 +164,39 @@ namespace VirtualNes.Core } private static void makePmTable() + { + int i; + + for (i = 0; i < PM_PG_WIDTH; i++) + pmtable[i] = (int)(PM_AMP * Math.Pow(2, PM_DEPTH * Math.Sin(2.0 * Math.PI * i / PM_PG_WIDTH) / 1200)); + } + + internal static OPLL OPLL_new() + { + throw new NotImplementedException(); + } + + internal static void OPLL_reset(OPLL vRC7_OPLL) + { + throw new NotImplementedException(); + } + + internal static void OPLL_reset_patch(OPLL vRC7_OPLL, int oPLL_VRC7_TONE) + { + throw new NotImplementedException(); + } + + internal static void OPLL_delete(OPLL vRC7_OPLL) + { + throw new NotImplementedException(); + } + + internal static void OPLL_writeReg(OPLL opll, UInt32 reg, UInt32 data) + { + throw new NotImplementedException(); + } + + internal static int OPLL_calc(OPLL opll) { throw new NotImplementedException(); } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs index f9b6ccf..e718c66 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs @@ -437,8 +437,6 @@ namespace VirtualNes.Core } } - - private byte SyncSub(int no, ControllerState state) { ushort bit = 0;