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

443 lines
14 KiB
C#
Raw Normal View History

2024-07-23 18:31:59 +08:00
namespace VirtualNes.Core
{
public class APU_INTERNAL : APU_INTERFACE
{
2024-07-25 18:34:52 +08:00
private static int[] vbl_length = new int[32]
{
5, 127, 10, 1, 19, 2, 40, 3,
80, 4, 30, 5, 7, 6, 13, 7,
6, 8, 12, 9, 24, 10, 48, 11,
96, 12, 36, 13, 8, 14, 16, 15,
};
private static int[] dpcm_cycles_pal = new int[16]
{
397, 353, 315, 297, 265, 235, 209, 198,
176, 148, 131, 118, 98, 78, 66, 50,
};
private static int[] dpcm_cycles = new int[16]
{
428, 380, 340, 320, 286, 254, 226, 214,
190, 160, 142, 128, 106, 85, 72, 54,
};
2024-07-23 18:31:59 +08:00
private NES nes;
2024-07-25 14:03:52 +08:00
// Frame Counter
2024-07-25 11:03:58 +08:00
private int FrameCycle;
2024-07-25 14:03:52 +08:00
private int FrameCount;
private int FrameType;
private byte FrameIRQ;
2024-07-25 11:03:58 +08:00
private byte FrameIRQoccur;
2024-07-23 18:31:59 +08:00
2024-07-25 14:03:52 +08:00
// Channels
private RECTANGLE ch0 = new RECTANGLE();
private RECTANGLE ch1 = new RECTANGLE();
private TRIANGLE ch2 = new TRIANGLE();
private NOISE ch3 = new NOISE();
private DPCM ch4 = new DPCM();
2024-07-25 18:34:52 +08:00
// $4015 Reg
private byte reg4015, sync_reg4015;
2024-07-25 14:03:52 +08:00
2024-07-23 18:31:59 +08:00
public void SetParent(NES parent)
{
nes = parent;
}
2024-07-25 11:03:58 +08:00
public override bool Sync(int cycles)
{
FrameCycle -= cycles * 2;
if (FrameCycle <= 0)
{
FrameCycle += 14915;
UpdateFrame();
}
var result = FrameIRQoccur | (SyncUpdateDPCM(cycles) ? 1 : 0);
return result != 0;
}
private bool SyncUpdateDPCM(int cycles)
{
2024-07-26 17:52:33 +08:00
bool bIRQ = false;
if (ch4.sync_enable != 0)
{
ch4.sync_cycles -= cycles;
while (ch4.sync_cycles < 0)
{
ch4.sync_cycles += ch4.sync_cache_cycles;
if (ch4.sync_dmalength != 0)
{
// if( !(--ch4.sync_dmalength) ) {
if (--ch4.sync_dmalength < 2)
{
if (ch4.sync_looping != 0)
{
ch4.sync_dmalength = ch4.sync_cache_dmalength;
}
else
{
ch4.sync_dmalength = 0;
if (ch4.sync_irq_gen != 0)
{
ch4.sync_irq_enable = 0xFF;
nes.cpu.SetIRQ(CPU.IRQ_DPCM);
}
}
}
}
}
}
if (ch4.sync_irq_enable != 0)
{
bIRQ = true;
}
return bIRQ;
2024-07-25 11:03:58 +08:00
}
private void UpdateFrame()
{
2024-07-25 18:34:52 +08:00
if (FrameCount == 0)
{
if ((FrameIRQ & 0xC0) == 0 && nes.GetFrameIRQmode())
{
FrameIRQoccur = 0xFF;
nes.cpu.SetIRQ(CPU.IRQ_FRAMEIRQ);
}
}
if (FrameCount == 3)
{
if ((FrameIRQ & 0x80) != 0)
{
FrameCycle += 14915;
}
}
// Counters Update
nes.Write(0x4018, (byte)FrameCount);
FrameCount = (FrameCount + 1) & 3;
2024-07-25 11:03:58 +08:00
}
2024-07-23 18:31:59 +08:00
public override void Reset(float fClock, int nRate)
{
throw new System.NotImplementedException();
}
public override void Setup(float fClock, int nRate)
{
throw new System.NotImplementedException();
}
public override void Write(ushort addr, byte data)
{
throw new System.NotImplementedException();
}
public override int Process(int channel)
{
throw new System.NotImplementedException();
}
2024-07-25 14:03:52 +08:00
internal byte SyncRead(ushort addr)
{
byte data = (byte)(addr >> 8);
if (addr == 0x4015)
{
data = 0;
if ((ch0.sync_enable != 0) && ch0.sync_len_count > 0) data |= (1 << 0);
if ((ch1.sync_enable != 0) && ch1.sync_len_count > 0) data |= (1 << 1);
if ((ch2.sync_enable != 0) && ch2.sync_len_count > 0) data |= (1 << 2);
if ((ch3.sync_enable != 0) && ch3.sync_len_count > 0) data |= (1 << 3);
if ((ch4.sync_enable != 0) && (ch4.sync_dmalength != 0)) data |= (1 << 4);
if (FrameIRQoccur != 0) data |= (1 << 6);
if (ch4.sync_irq_enable != 0) data |= (1 << 7);
FrameIRQoccur = 0;
nes.cpu.ClrIRQ(CPU.IRQ_FRAMEIRQ);
}
if (addr == 0x4017)
{
if (FrameIRQoccur != 0)
{
data = 0;
}
else
{
data |= (1 << 6);
}
}
return data;
}
2024-07-25 18:34:52 +08:00
internal void SyncWrite(ushort addr, byte data)
{
switch (addr)
{
// CH0,1 rectangle
case 0x4000:
case 0x4001:
case 0x4002:
case 0x4003:
case 0x4004:
case 0x4005:
case 0x4006:
case 0x4007:
SyncWriteRectangle((addr < 0x4004) ? 0 : 1, addr, data);
break;
// CH2 triangle
case 0x4008:
case 0x4009:
case 0x400A:
case 0x400B:
SyncWriteTriangle(addr, data);
break;
// CH3 noise
case 0x400C:
case 0x400D:
case 0x400E:
case 0x400F:
SyncWriteNoise(addr, data);
break;
// CH4 DPCM
case 0x4010:
case 0x4011:
case 0x4012:
case 0x4013:
SyncWriteDPCM(addr, data);
break;
case 0x4015:
sync_reg4015 = data;
if ((data & (1 << 0)) == 0)
{
ch0.sync_enable = 0;
ch0.sync_len_count = 0;
}
if ((data & (1 << 1)) == 0)
{
ch1.sync_enable = 0;
ch1.sync_len_count = 0;
}
if ((data & (1 << 2)) == 0)
{
ch2.sync_enable = 0;
ch2.sync_len_count = 0;
ch2.sync_lin_count = 0;
ch2.sync_counter_start = 0;
}
if ((data & (1 << 3)) == 0)
{
ch3.sync_enable = 0;
ch3.sync_len_count = 0;
}
if ((data & (1 << 4)) == 0)
{
ch4.sync_enable = 0;
ch4.sync_dmalength = 0;
ch4.sync_irq_enable = 0;
nes.cpu.ClrIRQ(CPU.IRQ_DPCM);
}
else
{
ch4.sync_enable = 0xFF;
if (ch4.sync_dmalength == 0)
{
// ch4.sync_cycles = ch4.sync_cache_cycles;
ch4.sync_dmalength = ch4.sync_cache_dmalength;
ch4.sync_cycles = 0;
}
}
break;
case 0x4017:
SyncWrite4017(data);
break;
// VirtuaNES<45>ŗL<C597>|<7C>[<5B>g
case 0x4018:
SyncUpdateRectangle(ch0, data);
SyncUpdateRectangle(ch1, data);
SyncUpdateTriangle(data);
SyncUpdateNoise(data);
break;
default:
break;
}
}
private void SyncUpdateNoise(int type)
{
if (ch3.sync_enable == 0 || ch3.sync_len_count <= 0)
return;
// Update Length
if (ch3.sync_len_count != 0 && ch3.sync_holdnote == 0)
{
if ((type & 1) == 0 && ch3.sync_len_count != 0)
{
ch3.sync_len_count--;
}
}
}
private void SyncUpdateTriangle(int type)
{
if (ch2.sync_enable == 0)
return;
if ((type & 1) == 0 && ch2.sync_holdnote == 0)
{
if (ch2.sync_len_count != 0)
{
ch2.sync_len_count--;
}
}
// Update Length/Linear
if (ch2.sync_counter_start != 0)
{
ch2.sync_lin_count = ch2.sync_reg[0] & 0x7F;
}
else if (ch2.sync_lin_count != 0)
{
ch2.sync_lin_count--;
}
if (ch2.sync_holdnote == 0 && ch2.sync_lin_count != 0)
{
ch2.sync_counter_start = 0;
}
}
private void SyncUpdateRectangle(RECTANGLE ch, int type)
{
if (ch.sync_enable == 0 || ch.sync_len_count <= 0)
return;
// Update Length
if (ch.sync_len_count != 0 && ch.sync_holdnote == 0)
{
if ((type & 1) == 0 && ch.sync_len_count != 0)
{
ch.sync_len_count--;
}
}
}
private void SyncWrite4017(byte data)
{
FrameCycle = 0;
FrameIRQ = data;
FrameIRQoccur = 0;
nes.cpu.ClrIRQ(CPU.IRQ_FRAMEIRQ);
FrameType = (data & 0x80) != 0 ? 1 : 0;
FrameCount = 0;
if ((data & 0x80) > 0)
{
UpdateFrame();
}
FrameCount = 1;
FrameCycle = 14915;
}
private void SyncWriteDPCM(ushort addr, byte data)
{
ch4.reg[addr & 3] = data;
switch (addr & 3)
{
case 0:
ch4.sync_cache_cycles = nes.GetVideoMode() ? dpcm_cycles_pal[data & 0x0F] * 8 : dpcm_cycles[data & 0x0F] * 8;
ch4.sync_looping = (byte)(data & 0x40);
ch4.sync_irq_gen = (byte)(data & 0x80);
if (ch4.sync_irq_gen == 0)
{
ch4.sync_irq_enable = 0;
nes.cpu.ClrIRQ(CPU.IRQ_DPCM);
}
break;
case 1:
break;
case 2:
break;
case 3:
ch4.sync_cache_dmalength = (data << 4) + 1;
break;
}
}
private void SyncWriteNoise(ushort addr, byte data)
{
ch3.sync_reg[addr & 3] = data;
switch (addr & 3)
{
case 0:
ch3.sync_holdnote = (byte)(data & 0x20);
break;
case 1:
break;
case 2:
break;
case 3: // Master
ch3.sync_len_count = vbl_length[data >> 3] * 2;
if ((sync_reg4015 & (1 << 3)) != 0)
ch3.sync_enable = 0xFF;
break;
}
}
private void SyncWriteTriangle(ushort addr, byte data)
{
ch2.sync_reg[addr & 3] = data;
switch (addr & 3)
{
case 0:
ch2.sync_holdnote = (byte)(data & 0x80);
break;
case 1:
break;
case 2:
break;
case 3: // Master
ch2.sync_len_count = vbl_length[ch2.sync_reg[3] >> 3] * 2;
ch2.sync_counter_start = 0x80;
if ((sync_reg4015 & (1 << 2)) != 0)
ch2.sync_enable = 0xFF;
break;
}
}
private void SyncWriteRectangle(int no, ushort addr, byte data)
{
RECTANGLE ch = (no == 0) ? ch0 : ch1;
ch.sync_reg[addr & 3] = data;
switch (addr & 3)
{
case 0:
ch.sync_holdnote = (byte)(data & 0x20);
break;
case 1:
case 2:
break;
case 3: // Master
ch.sync_len_count = vbl_length[data >> 3] * 2;
if ((sync_reg4015 & (1 << no)) != 0)
ch.sync_enable = 0xFF;
break;
}
}
2024-07-23 18:31:59 +08:00
}
}