实现APU_N106

This commit is contained in:
ALIENJACK\alien 2024-08-07 18:31:11 +08:00
parent b812a854aa
commit 70b6014fdb
12 changed files with 448 additions and 286 deletions

View File

@ -1192,5 +1192,222 @@ namespace VirtualNes.Core
break; break;
} }
} }
public class RECTANGLE
{
public byte[] reg = new byte[4]; // register
public byte enable; // enable
public byte holdnote; // holdnote
public byte volume; // volume
public byte complement;
// For Render
public int phaseacc;
public int freq;
public int freqlimit;
public int adder;
public int duty;
public int len_count;
public int nowvolume;
// For Envelope
public byte env_fixed;
public byte env_decay;
public byte env_count;
public byte dummy0;
public int env_vol;
// For Sweep
public byte swp_on;
public byte swp_inc;
public byte swp_shift;
public byte swp_decay;
public byte swp_count;
public byte[] dummy1 = new byte[3];
// For sync;
public byte[] sync_reg = new byte[4];
public byte sync_output_enable;
public byte sync_enable;
public byte sync_holdnote;
public byte dummy2;
public int sync_len_count;
public void ZeroMemory()
{
Array.Clear(reg, 0, reg.Length);
enable = 0;
holdnote = 0;
volume = 0;
complement = 0;
phaseacc = 0;
freq = 0;
freqlimit = 0;
adder = 0;
duty = 0;
len_count = 0;
nowvolume = 0;
env_fixed = 0;
env_decay = 0;
env_count = 0;
dummy0 = 0;
env_vol = 0;
swp_on = 0;
swp_inc = 0;
swp_shift = 0;
swp_decay = 0;
swp_count = 0;
Array.Clear(dummy1, 0, dummy1.Length);
Array.Clear(sync_reg, 0, sync_reg.Length);
sync_output_enable = 0;
sync_enable = 0;
sync_holdnote = 0;
dummy2 = 0;
sync_len_count = 0;
}
}
public class TRIANGLE
{
public byte[] reg = new byte[4];
public byte enable;
public byte holdnote;
public byte counter_start;
public byte dummy0;
public int phaseacc;
public int freq;
public int len_count;
public int lin_count;
public int adder;
public int nowvolume;
// For sync;
public byte[] sync_reg = new byte[4];
public byte sync_enable;
public byte sync_holdnote;
public byte sync_counter_start;
// public byte dummy1;
public int sync_len_count;
public int sync_lin_count;
internal void ZeroMemory()
{
Array.Clear(reg, 0, reg.Length);
enable = 0;
holdnote = 0;
counter_start = 0;
dummy0 = 0;
phaseacc = 0;
freq = 0;
len_count = 0;
lin_count = 0;
adder = 0;
nowvolume = 0;
Array.Clear(sync_reg, 0, sync_reg.Length);
sync_enable = 0;
sync_holdnote = 0;
sync_counter_start = 0;
sync_len_count = 0;
sync_lin_count = 0;
}
}
public class DPCM
{
public byte[] reg = new byte[4];
public byte enable;
public byte looping;
public byte cur_byte;
public byte dpcm_value;
public int freq;
public int phaseacc;
public int output;
public ushort address, cache_addr;
public int dmalength, cache_dmalength;
public int dpcm_output_real, dpcm_output_fake, dpcm_output_old, dpcm_output_offset;
// For sync
public byte[] sync_reg = new byte[4];
public byte sync_enable;
public byte sync_looping;
public byte sync_irq_gen;
public byte sync_irq_enable;
public int sync_cycles, sync_cache_cycles;
public int sync_dmalength, sync_cache_dmalength;
}
public class NOISE
{
public byte[] reg = new byte[4]; // register
public byte enable; // enable
public byte holdnote; // holdnote
public byte volume; // volume
public byte xor_tap;
public int shift_reg;
// For Render
public int phaseacc;
public int freq;
public int len_count;
public int nowvolume;
public int output;
// For Envelope
public byte env_fixed;
public byte env_decay;
public byte env_count;
public byte dummy0;
public int env_vol;
// For sync;
public byte[] sync_reg = new byte[4];
public byte sync_output_enable;
public byte sync_enable;
public byte sync_holdnote;
public byte dummy1;
public int sync_len_count;
internal void ZeroMemory()
{
Array.Clear(reg, 0, reg.Length);
enable = 0;
holdnote = 0;
volume = 0;
xor_tap = 0;
shift_reg = 0;
phaseacc = 0;
freq = 0;
len_count = 0;
nowvolume = 0;
output = 0;
env_fixed = 0;
env_decay = 0;
env_count = 0;
dummy0 = 0;
env_vol = 0;
Array.Clear(sync_reg, 0, sync_reg.Length);
sync_output_enable = 0;
sync_enable = 0;
sync_holdnote = 0;
dummy1 = 0;
sync_len_count = 0;
}
}
} }
} }

View File

@ -1,26 +1,245 @@
namespace VirtualNes.Core using RECTANGLE = VirtualNes.Core.APU_VRC6.RECTANGLE;
using SAWTOOTH = VirtualNes.Core.APU_VRC6.SAWTOOTH;
namespace VirtualNes.Core
{ {
public class APU_N106 : APU_INTERFACE public class APU_N106 : APU_INTERFACE
{ {
RECTANGLE ch0 = new RECTANGLE();
RECTANGLE ch1 = new RECTANGLE();
SAWTOOTH ch2 = new SAWTOOTH();
float cpu_clock;
int cycle_rate;
public APU_N106()
{
Reset(APU_CLOCK, 22050);
}
public override void Reset(float fClock, int nRate) public override void Reset(float fClock, int nRate)
{ {
//todo : 实现 ch0.ZeroMemory();
ch1.ZeroMemory();
ch2.ZeroMemory();
Setup(fClock, nRate);
} }
public override void Setup(float fClock, int nRate) public override void Setup(float fClock, int nRate)
{ {
//todo : 实现 cpu_clock = fClock;
cycle_rate = (int)(fClock * 65536.0f / nRate);
} }
public override void Write(ushort addr, byte data) public override void Write(ushort addr, byte data)
{ {
//todo : 实现 switch (addr)
{
// VRC6 CH0 rectangle
case 0x9000:
ch0.reg[0] = data;
ch0.gate = (byte)(data & 0x80);
ch0.volume = (byte)(data & 0x0F);
ch0.duty_pos = (byte)((data >> 4) & 0x07);
break;
case 0x9001:
ch0.reg[1] = data;
ch0.freq = INT2FIX((((ch0.reg[2] & 0x0F) << 8) | data) + 1);
break;
case 0x9002:
ch0.reg[2] = data;
ch0.enable = (byte)(data & 0x80);
ch0.freq = INT2FIX((((data & 0x0F) << 8) | ch0.reg[1]) + 1);
break;
// VRC6 CH1 rectangle
case 0xA000:
ch1.reg[0] = data;
ch1.gate = (byte)(data & 0x80);
ch1.volume = (byte)(data & 0x0F);
ch1.duty_pos = (byte)((data >> 4) & 0x07);
break;
case 0xA001:
ch1.reg[1] = data;
ch1.freq = INT2FIX((((ch1.reg[2] & 0x0F) << 8) | data) + 1);
break;
case 0xA002:
ch1.reg[2] = data;
ch1.enable = (byte)(data & 0x80);
ch1.freq = INT2FIX((((data & 0x0F) << 8) | ch1.reg[1]) + 1);
break;
// VRC6 CH2 sawtooth
case 0xB000:
ch2.reg[1] = data;
ch2.phaseaccum = (byte)(data & 0x3F);
break;
case 0xB001:
ch2.reg[1] = data;
ch2.freq = INT2FIX((((ch2.reg[2] & 0x0F) << 8) | data) + 1);
break;
case 0xB002:
ch2.reg[2] = data;
ch2.enable = (byte)(data & 0x80);
ch2.freq = INT2FIX((((data & 0x0F) << 8) | ch2.reg[1]) + 1);
// ch2.adder = 0; // クリアするとノイズの原因になる
// ch2.accum = 0; // クリアするとノイズの原因になる
break;
}
} }
public override int Process(int channel) public override int Process(int channel)
{ {
//todo : 实现 switch (channel)
{
case 0:
return RectangleRender(ch0);
case 1:
return RectangleRender(ch1);
case 2:
return SawtoothRender(ch2);
}
return 0; return 0;
} }
public override int GetFreq(int channel)
{
if (channel == 0 || channel == 1)
{
RECTANGLE ch;
if (channel == 0) ch = ch0;
else ch = ch1;
if (ch.enable == 0 || ch.gate != 0 || ch.volume == 0)
return 0;
if (ch.freq < INT2FIX(8))
return 0;
return (int)((256.0f * cpu_clock / (FIX2INT(ch.freq) * 16.0f)));
}
if (channel == 2)
{
SAWTOOTH ch = ch2;
if (ch.enable == 0 || ch.phaseaccum == 0)
return 0;
if (ch.freq < INT2FIX(8))
return 0;
return (int)(256.0f * cpu_clock / (FIX2INT(ch.freq) * 14.0f));
}
return 0;
}
int RectangleRender(RECTANGLE ch)
{
// Enable?
if (ch.enable == 0)
{
ch.output_vol = 0;
ch.adder = 0;
return ch.output_vol;
}
// Digitized output
if (ch.gate != 0)
{
ch.output_vol = ch.volume << APU_VRC6.RECTANGLE_VOL_SHIFT;
return ch.output_vol;
}
// 一定以上の周波数は処理しない(無駄)
if (ch.freq < INT2FIX(8))
{
ch.output_vol = 0;
return ch.output_vol;
}
ch.phaseacc -= cycle_rate;
if (ch.phaseacc >= 0)
return ch.output_vol;
int output = ch.volume << APU_VRC6.RECTANGLE_VOL_SHIFT;
if (ch.freq > cycle_rate)
{
// add 1 step
ch.phaseacc += ch.freq;
ch.adder = (byte)((ch.adder + 1) & 0x0F);
if (ch.adder <= ch.duty_pos)
ch.output_vol = output;
else
ch.output_vol = -output;
}
else
{
// average calculate
int num_times, total;
num_times = total = 0;
while (ch.phaseacc < 0)
{
ch.phaseacc += ch.freq;
ch.adder = (byte)((ch.adder + 1) & 0x0F);
if (ch.adder <= ch.duty_pos)
total += output;
else
total += -output;
num_times++;
}
ch.output_vol = total / num_times;
}
return ch.output_vol;
}
int SawtoothRender(SAWTOOTH ch)
{
// Digitized output
if (ch.enable == 0)
{
ch.output_vol = 0;
return ch.output_vol;
}
// 一定以上の周波数は処理しない(無駄)
if (ch.freq < INT2FIX(9))
{
return ch.output_vol;
}
ch.phaseacc -= cycle_rate / 2;
if (ch.phaseacc >= 0)
return ch.output_vol;
if (ch.freq > cycle_rate / 2)
{
// add 1 step
ch.phaseacc += ch.freq;
if (++ch.adder >= 7)
{
ch.adder = 0;
ch.accum = 0;
}
ch.accum += ch.phaseaccum;
ch.output_vol = ch.accum << APU_VRC6.SAWTOOTH_VOL_SHIFT;
}
else
{
// average calculate
int num_times, total;
num_times = total = 0;
while (ch.phaseacc < 0)
{
ch.phaseacc += ch.freq;
if (++ch.adder >= 7)
{
ch.adder = 0;
ch.accum = 0;
}
ch.accum += ch.phaseaccum;
total += ch.accum << APU_VRC6.SAWTOOTH_VOL_SHIFT;
num_times++;
}
ch.output_vol = (total / num_times);
}
return ch.output_vol;
}
} }
} }

View File

@ -245,7 +245,7 @@ namespace VirtualNes.Core
return ch.output_vol; return ch.output_vol;
} }
private class RECTANGLE public class RECTANGLE
{ {
public byte[] reg = new byte[3]; public byte[] reg = new byte[3];
@ -276,7 +276,7 @@ namespace VirtualNes.Core
} }
} }
private class SAWTOOTH public class SAWTOOTH
{ {
public byte[] reg = new byte[3]; public byte[] reg = new byte[3];

View File

@ -1,28 +0,0 @@
namespace VirtualNes.Core
{
public class DPCM
{
public byte[] reg = new byte[4];
public byte enable;
public byte looping;
public byte cur_byte;
public byte dpcm_value;
public int freq;
public int phaseacc;
public int output;
public ushort address, cache_addr;
public int dmalength, cache_dmalength;
public int dpcm_output_real, dpcm_output_fake, dpcm_output_old, dpcm_output_offset;
// For sync
public byte[] sync_reg = new byte[4];
public byte sync_enable;
public byte sync_looping;
public byte sync_irq_gen;
public byte sync_irq_enable;
public int sync_cycles, sync_cache_cycles;
public int sync_dmalength, sync_cache_dmalength;
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: e6289a516ac91b541b2b1807bb07e2b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,68 +0,0 @@
using System;
namespace VirtualNes.Core
{
public class NOISE
{
public byte[] reg = new byte[4]; // register
public byte enable; // enable
public byte holdnote; // holdnote
public byte volume; // volume
public byte xor_tap;
public int shift_reg;
// For Render
public int phaseacc;
public int freq;
public int len_count;
public int nowvolume;
public int output;
// For Envelope
public byte env_fixed;
public byte env_decay;
public byte env_count;
public byte dummy0;
public int env_vol;
// For sync;
public byte[] sync_reg = new byte[4];
public byte sync_output_enable;
public byte sync_enable;
public byte sync_holdnote;
public byte dummy1;
public int sync_len_count;
internal void ZeroMemory()
{
Array.Clear(reg, 0, reg.Length);
enable = 0;
holdnote = 0;
volume = 0;
xor_tap = 0;
shift_reg = 0;
phaseacc = 0;
freq = 0;
len_count = 0;
nowvolume = 0;
output = 0;
env_fixed = 0;
env_decay = 0;
env_count = 0;
dummy0 = 0;
env_vol = 0;
Array.Clear(sync_reg, 0, sync_reg.Length);
sync_output_enable = 0;
sync_enable = 0;
sync_holdnote = 0;
dummy1 = 0;
sync_len_count = 0;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 8680ce7dbdceb504dbda3b98dbdb1297
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,85 +0,0 @@
using System;
namespace VirtualNes.Core
{
public class RECTANGLE
{
public byte[] reg = new byte[4]; // register
public byte enable; // enable
public byte holdnote; // holdnote
public byte volume; // volume
public byte complement;
// For Render
public int phaseacc;
public int freq;
public int freqlimit;
public int adder;
public int duty;
public int len_count;
public int nowvolume;
// For Envelope
public byte env_fixed;
public byte env_decay;
public byte env_count;
public byte dummy0;
public int env_vol;
// For Sweep
public byte swp_on;
public byte swp_inc;
public byte swp_shift;
public byte swp_decay;
public byte swp_count;
public byte[] dummy1 = new byte[3];
// For sync;
public byte[] sync_reg = new byte[4];
public byte sync_output_enable;
public byte sync_enable;
public byte sync_holdnote;
public byte dummy2;
public int sync_len_count;
public void ZeroMemory()
{
Array.Clear(reg, 0, reg.Length);
enable = 0;
holdnote = 0;
volume = 0;
complement = 0;
phaseacc = 0;
freq = 0;
freqlimit = 0;
adder = 0;
duty = 0;
len_count = 0;
nowvolume = 0;
env_fixed = 0;
env_decay = 0;
env_count = 0;
dummy0 = 0;
env_vol = 0;
swp_on = 0;
swp_inc = 0;
swp_shift = 0;
swp_decay = 0;
swp_count = 0;
Array.Clear(dummy1, 0, dummy1.Length);
Array.Clear(sync_reg, 0, sync_reg.Length);
sync_output_enable = 0;
sync_enable = 0;
sync_holdnote = 0;
dummy2 = 0;
sync_len_count = 0;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 6e50831f6c445fe489d7e1737269296e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,54 +0,0 @@
using System;
namespace VirtualNes.Core
{
public class TRIANGLE
{
public byte[] reg = new byte[4];
public byte enable;
public byte holdnote;
public byte counter_start;
public byte dummy0;
public int phaseacc;
public int freq;
public int len_count;
public int lin_count;
public int adder;
public int nowvolume;
// For sync;
public byte[] sync_reg = new byte[4];
public byte sync_enable;
public byte sync_holdnote;
public byte sync_counter_start;
// public byte dummy1;
public int sync_len_count;
public int sync_lin_count;
internal void ZeroMemory()
{
Array.Clear(reg, 0, reg.Length);
enable = 0;
holdnote = 0;
counter_start = 0;
dummy0 = 0;
phaseacc = 0;
freq = 0;
len_count = 0;
lin_count = 0;
adder = 0;
nowvolume = 0;
Array.Clear(sync_reg, 0, sync_reg.Length);
sync_enable = 0;
sync_holdnote = 0;
sync_counter_start = 0;
sync_len_count = 0;
sync_lin_count = 0;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 4ed2788da33fe474facc1d7ce1b34d03
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1157,6 +1157,11 @@ namespace VirtualNes.Core
bExtMono = bMode; bExtMono = bMode;
} }
internal int GetScanlineNo()
{
return ScanlineNo;
}
public struct Sprite public struct Sprite
{ {
public byte y public byte y