AxibugEmuOnline/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_MMC5.cs

416 lines
13 KiB
C#
Raw Normal View History

2024-08-06 19:50:33 +08:00
using Codice.CM.Client.Differences;
using System;
2024-07-26 17:52:33 +08:00
namespace VirtualNes.Core
{
public class APU_MMC5 : APU_INTERFACE
{
2024-08-06 19:50:33 +08:00
public const int RECTANGLE_VOL_SHIFT = 8;
public const int DAOUT_VOL_SHIFT = 6;
2024-08-05 11:52:43 +08:00
SYNCRECTANGLE sch0 = new SYNCRECTANGLE();
SYNCRECTANGLE sch1 = new SYNCRECTANGLE();
2024-08-06 19:50:33 +08:00
RECTANGLE ch0 = new RECTANGLE();
RECTANGLE ch1 = new RECTANGLE();
2024-08-05 11:52:43 +08:00
2024-08-06 19:50:33 +08:00
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()
2024-07-26 17:52:33 +08:00
{
2024-08-06 19:50:33 +08:00
// 仮設定
Reset(APU_INTERFACE.APU_CLOCK, 22050);
2024-07-26 17:52:33 +08:00
}
2024-08-06 19:50:33 +08:00
public override void Reset(float fClock, int nRate)
2024-07-26 17:52:33 +08:00
{
2024-08-06 19:50:33 +08:00
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);
}
2024-07-26 17:52:33 +08:00
}
2024-08-06 19:50:33 +08:00
public override void Setup(float fClock, int nRate)
2024-07-26 17:52:33 +08:00
{
2024-08-06 19:50:33 +08:00
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;
2024-07-26 17:52:33 +08:00
}
2024-08-06 19:50:33 +08:00
public override void Write(ushort addr, byte data)
2024-07-26 17:52:33 +08:00
{
2024-08-06 19:50:33 +08:00
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;
}
2024-07-26 17:52:33 +08:00
}
internal void SyncWrite(ushort addr, byte data)
{
2024-08-06 19:50:33 +08:00
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;
}
2024-07-26 17:52:33 +08:00
}
2024-08-05 11:52:43 +08:00
internal byte SyncRead(ushort addr)
{
byte data = 0;
if (addr == 0x5015)
{
if ((sch0.enable != 0) && sch0.vbl_length > 0) data |= (1 << 0);
if ((sch1.enable != 0) && sch1.vbl_length > 0) data |= (1 << 1);
}
return data;
}
2024-08-06 19:50:33 +08:00
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;
}
public override int GetFreq(int channel)
{
if (channel == 0 || channel == 1)
{
RECTANGLE ch = null;
if (channel == 0) ch = ch0;
else ch = ch1;
if (ch.enable == 0 || ch.vbl_length <= 0)
return 0;
if (ch.freq < INT2FIX(8))
return 0;
if (ch.fixed_envelope != 0)
{
if (ch.volume == 0)
return 0;
}
else
{
if ((0x0F - ch.env_vol) == 0)
return 0;
}
return (int)(256.0f * cpu_clock / (FIX2INT(ch.freq) * 16.0f));
}
return 0;
}
2024-08-06 19:50:33 +08:00
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;
}
2024-08-05 11:52:43 +08:00
public class SYNCRECTANGLE
{
// For sync
public byte[] reg = new byte[4];
public byte enable;
public byte holdnote;
public byte[] dummy = new byte[2];
public int vbl_length;
2024-08-06 19:50:33 +08:00
public void ZeroMemory()
{
Array.Clear(reg, 0, reg.Length);
enable = 0;
holdnote = 0;
Array.Clear(dummy, 0, dummy.Length);
vbl_length = 0;
}
}
2024-08-06 19:50:33 +08:00
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;
2024-08-05 11:52:43 +08:00
}
2024-07-26 17:52:33 +08:00
}
}