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

1412 lines
44 KiB
C#
Raw Normal View History

2024-08-16 10:20:00 +08:00
2024-08-02 16:50:16 +08:00
using System;
2024-07-23 18:31:59 +08:00
namespace VirtualNes.Core
{
public class APU_INTERNAL : APU_INTERFACE
{
2024-08-02 16:50:16 +08:00
// Volume shift
public const int RECTANGLE_VOL_SHIFT = 8;
public const int TRIANGLE_VOL_SHIFT = 9;
public const int NOISE_VOL_SHIFT = 8;
public const int DPCM_VOL_SHIFT = 8;
// Tables
static public int[] freq_limit = new int[8]
{
0x03FF, 0x0555, 0x0666, 0x071C, 0x0787, 0x07C1, 0x07E0, 0x07F0
};
static public int[] duty_lut = new int[4]
{
2, 4, 8, 12
};
static public int[] noise_freq = new int[16]{
4, 8, 16, 32, 64, 96, 128, 160,
202, 254, 380, 508, 762, 1016, 2034, 4068
};
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-08-02 16:50:16 +08:00
// Sound
private float cpu_clock;
private int sampling_rate;
private int cycle_rate;
2024-07-25 18:34:52 +08:00
// $4015 Reg
private byte reg4015, sync_reg4015;
2024-07-25 14:03:52 +08:00
2024-08-02 16:50:16 +08:00
private const int TONEDATA_MAX = 16;
private const int TONEDATA_LEN = 32;
private const int CHANNEL_MAX = 3;
private const int TONE_MAX = 4;
bool[] bToneTableEnable = new bool[TONEDATA_MAX];
int[,] ToneTable = new int[TONEDATA_MAX, TONEDATA_LEN];
int[,] ChannelTone = new int[CHANNEL_MAX, TONE_MAX];
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)
{
2024-08-02 16:50:16 +08:00
ch0.ZeroMemory();
ch1.ZeroMemory();
ch2.ZeroMemory();
ch3.ZeroMemory();
Array.Clear(bToneTableEnable, 0, bToneTableEnable.Length);
Array.Clear(ToneTable, 0, ToneTable.Length);
Array.Clear(ChannelTone, 0, ChannelTone.Length);
reg4015 = sync_reg4015 = 0;
// Sweep complement
ch0.complement = 0x00;
ch1.complement = 0xFF;
// Noise shift register
ch3.shift_reg = 0x4000;
Setup(fClock, nRate);
// $4011<31>ϳ<EFBFBD><CFB3>ڻ<EFBFBD><DABB><EFBFBD><EFBFBD>ʤ<EFBFBD>
ushort addr;
for (addr = 0x4000; addr <= 0x4010; addr++)
{
Write(addr, 0x00);
SyncWrite(addr, 0x00);
}
// Write( 0x4001, 0x08 ); // Reset<65>r<EFBFBD><72>inc<6E><63><EFBFBD>`<60>ɤˤʤ<CBA4>?
// Write( 0x4005, 0x08 ); // Reset<65>r<EFBFBD><72>inc<6E><63><EFBFBD>`<60>ɤˤʤ<CBA4>?
Write(0x4012, 0x00);
Write(0x4013, 0x00);
Write(0x4015, 0x00);
SyncWrite(0x4012, 0x00);
SyncWrite(0x4013, 0x00);
SyncWrite(0x4015, 0x00);
// $4017<31>ϕ<EFBFBD><CF95><EFBFBD><EFBFBD>z<EFBFBD>ߤdz<DFA4><C7B3>ڻ<EFBFBD><DABB><EFBFBD><EFBFBD>ʤ<EFBFBD>(<28><><EFBFBD>ڥ<EFBFBD><DAA5>`<60>ɤ<EFBFBD>0<EFBFBD>Ǥ<EFBFBD><C7A4><EFBFBD><EFBFBD>Τ<EFBFBD><CEA4>ڴ<EFBFBD><DAB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>եȤ<D5A5><C8A4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>)
FrameIRQ = 0xC0;
FrameCycle = 0;
FrameIRQoccur = 0;
FrameCount = 0;
FrameType = 0;
2024-07-23 18:31:59 +08:00
}
public override void Setup(float fClock, int nRate)
{
2024-08-02 16:50:16 +08:00
cpu_clock = fClock;
sampling_rate = nRate;
cycle_rate = (int)(fClock * 65536.0f / nRate);
2024-07-23 18:31:59 +08:00
}
public override void Write(ushort addr, byte data)
{
2024-08-02 16:50:16 +08:00
switch (addr)
{
// CH0,1 rectangle
case 0x4000:
case 0x4001:
case 0x4002:
case 0x4003:
case 0x4004:
case 0x4005:
case 0x4006:
case 0x4007:
WriteRectangle((addr < 0x4004) ? 0 : 1, addr, data);
break;
// CH2 triangle
case 0x4008:
case 0x4009:
case 0x400A:
case 0x400B:
WriteTriangle(addr, data);
break;
// CH3 noise
case 0x400C:
case 0x400D:
case 0x400E:
case 0x400F:
WriteNoise(addr, data);
break;
// CH4 DPCM
case 0x4010:
case 0x4011:
case 0x4012:
case 0x4013:
WriteDPCM(addr, data);
break;
case 0x4015:
reg4015 = data;
if ((data & (1 << 0)) == 0)
{
ch0.enable = 0;
ch0.len_count = 0;
}
if ((data & (1 << 1)) == 0)
{
ch1.enable = 0;
ch1.len_count = 0;
}
if ((data & (1 << 2)) == 0)
{
ch2.enable = 0;
ch2.len_count = 0;
ch2.lin_count = 0;
ch2.counter_start = 0;
}
if ((data & (1 << 3)) == 0)
{
ch3.enable = 0;
ch3.len_count = 0;
}
if ((data & (1 << 4)) == 0)
{
ch4.enable = 0;
ch4.dmalength = 0;
}
else
{
ch4.enable = 0xFF;
if (ch4.dmalength == 0)
{
ch4.address = ch4.cache_addr;
ch4.dmalength = ch4.cache_dmalength;
ch4.phaseacc = 0;
}
}
break;
case 0x4017:
break;
// VirtuaNES<45><53><EFBFBD>Хݩ`<60><>
case 0x4018:
UpdateRectangle(ch0, data);
UpdateRectangle(ch1, data);
UpdateTriangle(data);
UpdateNoise(data);
break;
default:
break;
}
}
private void UpdateNoise(int type)
{
if (ch3.enable == 0 || ch3.len_count <= 0)
return;
// Update Length
if (ch3.holdnote == 0)
{
// Holdnote
if ((type & 1) == 0 && ch3.len_count != 0)
{
ch3.len_count--;
}
}
// Update Envelope
if (ch3.env_count != 0)
{
ch3.env_count--;
}
if (ch3.env_count == 0)
{
ch3.env_count = ch3.env_decay;
// Holdnote
if (ch3.holdnote != 0)
{
ch3.env_vol = (ch3.env_vol - 1) & 0x0F;
}
else if (ch3.env_vol != 0)
{
ch3.env_vol--;
}
}
if (ch3.env_fixed == 0)
{
ch3.nowvolume = ch3.env_vol << RECTANGLE_VOL_SHIFT;
}
}
private void UpdateTriangle(int type)
{
if (ch2.enable == 0)
return;
if ((type & 1) == 0 && ch2.holdnote == 0)
{
if (ch2.len_count != 0)
{
ch2.len_count--;
}
}
// if( !ch2.len_count ) {
// ch2.lin_count = 0;
// }
// Update Length/Linear
if (ch2.counter_start != 0)
{
ch2.lin_count = ch2.reg[0] & 0x7F;
}
else if (ch2.lin_count != 0)
{
ch2.lin_count--;
}
if (ch2.holdnote == 0 && ch2.lin_count != 0)
{
ch2.counter_start = 0;
}
}
private void UpdateRectangle(RECTANGLE ch, int type)
{
if (ch.enable == 0 || ch.len_count <= 0)
return;
// Update Length/Sweep
if ((type & 1) == 0)
{
// Update Length
if (ch.len_count != 0 && ch.holdnote == 0)
{
// Holdnote
if (ch.len_count != 0)
{
ch.len_count--;
}
}
// Update Sweep
if (ch.swp_on != 0 && ch.swp_shift != 0)
{
if (ch.swp_count != 0)
{
ch.swp_count--;
}
if (ch.swp_count == 0)
{
ch.swp_count = ch.swp_decay;
if (ch.swp_inc != 0)
{
// Sweep increment(to higher frequency)
if (ch.complement == 0)
ch.freq += ~(ch.freq >> ch.swp_shift); // CH 0
else
ch.freq -= (ch.freq >> ch.swp_shift); // CH 1
}
else
{
// Sweep decrement(to lower frequency)
ch.freq += (ch.freq >> ch.swp_shift);
}
}
}
}
// Update Envelope
if (ch.env_count != 0)
{
ch.env_count--;
}
if (ch.env_count == 0)
{
ch.env_count = ch.env_decay;
// Holdnote
if (ch.holdnote != 0)
{
ch.env_vol = (ch.env_vol - 1) & 0x0F;
}
else if (ch.env_vol != 0)
{
ch.env_vol--;
}
}
if (ch.env_fixed == 0)
{
ch.nowvolume = ch.env_vol << RECTANGLE_VOL_SHIFT;
}
}
private void WriteDPCM(ushort addr, byte data)
{
ch4.reg[addr & 3] = data;
switch (addr & 3)
{
case 0:
ch4.freq = INT2FIX(nes.GetVideoMode() ? dpcm_cycles_pal[data & 0x0F] : dpcm_cycles[data & 0x0F]);
// ch4.freq = INT2FIX( dpcm_cycles[data&0x0F] );
//// ch4.freq = INT2FIX( (dpcm_cycles[data&0x0F]-((data&0x0F)^0x0F)*2-2) );
ch4.looping = (byte)(data & 0x40);
break;
case 1:
ch4.dpcm_value = (byte)((data & 0x7F) >> 1);
break;
case 2:
ch4.cache_addr = (ushort)(0xC000 + (ushort)(data << 6));
break;
case 3:
ch4.cache_dmalength = ((data << 4) + 1) << 3;
break;
}
}
private void WriteNoise(ushort addr, byte data)
{
ch3.reg[addr & 3] = data;
switch (addr & 3)
{
case 0:
ch3.holdnote = (byte)(data & 0x20);
ch3.volume = (byte)(data & 0x0F);
ch3.env_fixed = (byte)(data & 0x10);
ch3.env_decay = (byte)((data & 0x0F) + 1);
break;
case 1: // Unused
break;
case 2:
ch3.freq = INT2FIX(noise_freq[data & 0x0F]);
ch3.xor_tap = (byte)((data & 0x80) != 0 ? 0x40 : 0x02);
break;
case 3: // Master
ch3.len_count = vbl_length[data >> 3] * 2;
ch3.env_vol = 0x0F;
ch3.env_count = (byte)(ch3.env_decay + 1);
if ((reg4015 & (1 << 3)) != 0)
ch3.enable = 0xFF;
break;
}
}
private void WriteTriangle(ushort addr, byte data)
{
ch2.reg[addr & 3] = data;
switch (addr & 3)
{
case 0:
ch2.holdnote = (byte)(data & 0x80);
break;
case 1: // Unused
break;
case 2:
ch2.freq = INT2FIX(((ch2.reg[3] & 0x07) << 8) + data + 1);
break;
case 3: // Master
ch2.freq = INT2FIX((((data & 0x07) << 8) + ch2.reg[2] + 1));
ch2.len_count = vbl_length[data >> 3] * 2;
ch2.counter_start = 0x80;
if ((reg4015 & (1 << 2)) != 0)
ch2.enable = 0xFF;
break;
}
}
private void WriteRectangle(int no, ushort addr, byte data)
{
RECTANGLE ch = (no == 0) ? ch0 : ch1;
ch.reg[addr & 3] = data;
switch (addr & 3)
{
case 0:
ch.holdnote = (byte)(data & 0x20);
ch.volume = (byte)(data & 0x0F);
ch.env_fixed = (byte)(data & 0x10);
ch.env_decay = (byte)((data & 0x0F) + 1);
ch.duty = duty_lut[data >> 6];
break;
case 1:
ch.swp_on = (byte)(data & 0x80);
ch.swp_inc = (byte)(data & 0x08);
ch.swp_shift = (byte)(data & 0x07);
ch.swp_decay = (byte)(((data >> 4) & 0x07) + 1);
ch.freqlimit = freq_limit[data & 0x07];
break;
case 2:
ch.freq = (ch.freq & (~0xFF)) + data;
break;
case 3: // Master
ch.freq = ((data & 0x07) << 8) + (ch.freq & 0xFF);
ch.len_count = vbl_length[data >> 3] * 2;
ch.env_vol = 0x0F;
ch.env_count = (byte)(ch.env_decay + 1);
ch.adder = 0;
if ((reg4015 & (1 << no)) != 0)
ch.enable = 0xFF;
break;
}
2024-07-23 18:31:59 +08:00
}
public override int Process(int channel)
{
2024-08-02 16:50:16 +08:00
switch (channel)
{
case 0:
return RenderRectangle(ch0);
case 1:
return RenderRectangle(ch1);
case 2:
return RenderTriangle();
case 3:
return RenderNoise();
case 4:
return RenderDPCM();
default:
return 0;
}
}
private int RenderDPCM()
{
if (ch4.dmalength != 0)
{
ch4.phaseacc -= cycle_rate;
while (ch4.phaseacc < 0)
{
ch4.phaseacc += ch4.freq;
if ((ch4.dmalength & 7) == 0)
{
ch4.cur_byte = nes.Read(ch4.address);
if (0xFFFF == ch4.address)
ch4.address = 0x8000;
else
ch4.address++;
}
if ((--ch4.dmalength) == 0)
{
if (ch4.looping != 0)
{
ch4.address = ch4.cache_addr;
ch4.dmalength = ch4.cache_dmalength;
}
else
{
ch4.enable = 0;
break;
}
}
// positive delta
if ((ch4.cur_byte & (1 << ((ch4.dmalength & 7) ^ 7))) != 0)
{
if (ch4.dpcm_value < 0x3F)
ch4.dpcm_value += 1;
}
else
{
// negative delta
if (ch4.dpcm_value > 1)
ch4.dpcm_value -= 1;
}
}
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ץ<EFBFBD><D7A5>Υ<EFBFBD><CEA5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>å<EFBFBD>(TEST)
ch4.dpcm_output_real = ((ch4.reg[1] & 0x01) + ch4.dpcm_value * 2) - 0x40;
if (Math.Abs(ch4.dpcm_output_real - ch4.dpcm_output_fake) <= 8)
{
ch4.dpcm_output_fake = ch4.dpcm_output_real;
ch4.output = ch4.dpcm_output_real << DPCM_VOL_SHIFT;
}
else
{
if (ch4.dpcm_output_real > ch4.dpcm_output_fake)
ch4.dpcm_output_fake += 8;
else
ch4.dpcm_output_fake -= 8;
ch4.output = ch4.dpcm_output_fake << DPCM_VOL_SHIFT;
}
return ch4.output;
}
private int RenderNoise()
{
if (ch3.enable == 0 || ch3.len_count <= 0)
return 0;
if (ch3.env_fixed != 0)
{
ch3.nowvolume = ch3.volume << RECTANGLE_VOL_SHIFT;
}
int vol = 256 - ((ch4.reg[1] & 0x01) + ch4.dpcm_value * 2);
ch3.phaseacc -= cycle_rate;
if (ch3.phaseacc >= 0)
return ch3.output * vol / 256;
if (ch3.freq > cycle_rate)
{
ch3.phaseacc += ch3.freq;
if (NoiseShiftreg(ch3.xor_tap))
ch3.output = ch3.nowvolume;
else
ch3.output = -ch3.nowvolume;
return ch3.output * vol / 256;
}
int num_times, total;
num_times = total = 0;
while (ch3.phaseacc < 0)
{
ch3.phaseacc += ch3.freq;
if (NoiseShiftreg(ch3.xor_tap))
ch3.output = ch3.nowvolume;
else
ch3.output = -ch3.nowvolume;
total += ch3.output;
num_times++;
}
return (total / num_times) * vol / 256;
}
private bool NoiseShiftreg(byte xor_tap)
{
int bit0, bit14;
bit0 = ch3.shift_reg & 1;
if ((ch3.shift_reg & xor_tap) != 0) bit14 = bit0 ^ 1;
else bit14 = bit0 ^ 0;
ch3.shift_reg >>= 1;
ch3.shift_reg |= (bit14 << 14);
return (bit0 ^ 1) != 0;
}
private int RenderTriangle()
{
int vol;
if (Supporter.Config.sound.bDisableVolumeEffect)
{
vol = 256;
}
else
{
vol = 256 - ((ch4.reg[1] & 0x01) + ch4.dpcm_value * 2);
}
if (ch2.enable == 0 || (ch2.len_count <= 0) || (ch2.lin_count <= 0))
{
return ch2.nowvolume * vol / 256;
}
if (ch2.freq < INT2FIX(8))
{
return ch2.nowvolume * vol / 256;
}
if (!(Supporter.Config.sound.bChangeTone && ChannelTone[2, 0] != 0))
{
ch2.phaseacc -= cycle_rate;
if (ch2.phaseacc >= 0)
{
return ch2.nowvolume * vol / 256;
}
if (ch2.freq > cycle_rate)
{
ch2.phaseacc += ch2.freq;
ch2.adder = (ch2.adder + 1) & 0x1F;
if (ch2.adder < 0x10)
{
ch2.nowvolume = (ch2.adder & 0x0F) << TRIANGLE_VOL_SHIFT;
}
else
{
ch2.nowvolume = (0x0F - (ch2.adder & 0x0F)) << TRIANGLE_VOL_SHIFT;
}
return ch2.nowvolume * vol / 256;
}
// <20><><EFBFBD><EFBFBD>ƽ<EFBFBD><C6BD>
int num_times, total;
num_times = total = 0;
while (ch2.phaseacc < 0)
{
ch2.phaseacc += ch2.freq;
ch2.adder = (ch2.adder + 1) & 0x1F;
if (ch2.adder < 0x10)
{
ch2.nowvolume = (ch2.adder & 0x0F) << TRIANGLE_VOL_SHIFT;
}
else
{
ch2.nowvolume = (0x0F - (ch2.adder & 0x0F)) << TRIANGLE_VOL_SHIFT;
}
total += ch2.nowvolume;
num_times++;
}
return (total / num_times) * vol / 256;
}
else
{
int x = ChannelTone[2, 0] - 1;
int pTone = 0;
ch2.phaseacc -= cycle_rate;
if (ch2.phaseacc >= 0)
{
return ch2.nowvolume * vol / 256;
}
if (ch2.freq > cycle_rate)
{
ch2.phaseacc += ch2.freq;
ch2.adder = (ch2.adder + 1) & 0x1F;
var temp = ToneTable[x, pTone + (ch2.adder & 0x1F)];
ch2.nowvolume = temp * 0x0F;
return ch2.nowvolume * vol / 256;
}
// <20><><EFBFBD><EFBFBD>ƽ<EFBFBD><C6BD>
int num_times, total;
num_times = total = 0;
while (ch2.phaseacc < 0)
{
ch2.phaseacc += ch2.freq;
ch2.adder = (ch2.adder + 1) & 0x1F;
var temp = ToneTable[x, pTone + (ch2.adder & 0x1F)];
total += temp * 0x0F;
num_times++;
}
return (total / num_times) * vol / 256;
}
}
private int RenderRectangle(RECTANGLE ch)
{
if (ch.enable == 0 || ch.len_count <= 0)
return 0;
// Channel disable?
if ((ch.freq < 8) || (ch.swp_inc == 0 && ch.freq > ch.freqlimit))
{
return 0;
}
if (ch.env_fixed != 0)
{
ch.nowvolume = ch.volume << RECTANGLE_VOL_SHIFT;
}
int volume = ch.nowvolume;
if (!(Supporter.Config.sound.bChangeTone && (ChannelTone[(ch.complement == 0) ? 0 : 1, ch.reg[0] >> 6]) != 0))
{
// <20>a<EFBFBD>g<EFBFBD>I<EFBFBD><49>
double total;
double sample_weight = ch.phaseacc;
if (sample_weight > cycle_rate)
{
sample_weight = cycle_rate;
}
total = (ch.adder < ch.duty) ? sample_weight : -sample_weight;
int freq = INT2FIX(ch.freq + 1);
ch.phaseacc -= cycle_rate;
while (ch.phaseacc < 0)
{
ch.phaseacc += freq;
ch.adder = (ch.adder + 1) & 0x0F;
sample_weight = freq;
if (ch.phaseacc > 0)
{
sample_weight -= ch.phaseacc;
}
total += (ch.adder < ch.duty) ? sample_weight : -sample_weight;
}
return (int)(volume * total / cycle_rate + 0.5);
}
else
{
int x = ChannelTone[(ch.complement == 0) ? 0 : 1, ch.reg[0] >> 6] - 1;
int pTone = 0;
// <20><><EFBFBD>Ÿo<C29F><6F>
ch.phaseacc -= cycle_rate * 2;
if (ch.phaseacc >= 0)
{
var temp = ToneTable[x, pTone + (ch.adder & 0x1F)];
return temp * volume / ((1 << RECTANGLE_VOL_SHIFT) / 2);
}
// 1<><31><EFBFBD>ƥåפ<C3A5><D7A4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
int freq = INT2FIX(ch.freq + 1);
if (freq > cycle_rate * 2)
{
ch.phaseacc += freq;
ch.adder = (ch.adder + 1) & 0x1F;
var temp = ToneTable[x, pTone + (ch.adder & 0x1F)];
return temp * volume / ((1 << RECTANGLE_VOL_SHIFT) / 2);
}
// <20><><EFBFBD><EFBFBD>ƽ<EFBFBD><C6BD>
int num_times, total;
num_times = total = 0;
while (ch.phaseacc < 0)
{
ch.phaseacc += freq;
ch.adder = (ch.adder + 1) & 0x1F;
var temp = ToneTable[x, pTone + (ch.adder & 0x1F)];
total += temp * volume / ((1 << RECTANGLE_VOL_SHIFT) / 2);
num_times++;
}
return total / num_times;
}
2024-07-23 18:31:59 +08:00
}
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-08-07 18:31:11 +08:00
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;
}
}
2024-07-23 18:31:59 +08:00
}
}