246 lines
7.6 KiB
C#
246 lines
7.6 KiB
C#
|
using RECTANGLE = VirtualNes.Core.APU_VRC6.RECTANGLE;
|
|||
|
using SAWTOOTH = VirtualNes.Core.APU_VRC6.SAWTOOTH;
|
|||
|
|
|||
|
namespace VirtualNes.Core
|
|||
|
{
|
|||
|
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)
|
|||
|
{
|
|||
|
ch0.ZeroMemory();
|
|||
|
ch1.ZeroMemory();
|
|||
|
ch2.ZeroMemory();
|
|||
|
|
|||
|
Setup(fClock, nRate);
|
|||
|
}
|
|||
|
|
|||
|
public override void Setup(float fClock, int nRate)
|
|||
|
{
|
|||
|
cpu_clock = fClock;
|
|||
|
cycle_rate = (int)(fClock * 65536.0f / nRate);
|
|||
|
}
|
|||
|
|
|||
|
public override void Write(ushort addr, byte data)
|
|||
|
{
|
|||
|
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)
|
|||
|
{
|
|||
|
switch (channel)
|
|||
|
{
|
|||
|
case 0:
|
|||
|
return RectangleRender(ch0);
|
|||
|
case 1:
|
|||
|
return RectangleRender(ch1);
|
|||
|
case 2:
|
|||
|
return SawtoothRender(ch2);
|
|||
|
}
|
|||
|
|
|||
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|