1412 lines
44 KiB
C#
1412 lines
44 KiB
C#
|
||
using System;
|
||
|
||
namespace VirtualNes.Core
|
||
{
|
||
public class APU_INTERNAL : APU_INTERFACE
|
||
{
|
||
// 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
|
||
};
|
||
|
||
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,
|
||
};
|
||
|
||
private NES nes;
|
||
// Frame Counter
|
||
private int FrameCycle;
|
||
private int FrameCount;
|
||
private int FrameType;
|
||
private byte FrameIRQ;
|
||
private byte FrameIRQoccur;
|
||
|
||
// 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();
|
||
|
||
// Sound
|
||
private float cpu_clock;
|
||
private int sampling_rate;
|
||
private int cycle_rate;
|
||
|
||
// $4015 Reg
|
||
private byte reg4015, sync_reg4015;
|
||
|
||
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];
|
||
|
||
public void SetParent(NES parent)
|
||
{
|
||
nes = parent;
|
||
}
|
||
|
||
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)
|
||
{
|
||
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;
|
||
}
|
||
|
||
private void UpdateFrame()
|
||
{
|
||
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;
|
||
}
|
||
|
||
public override void Reset(float fClock, int nRate)
|
||
{
|
||
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;
|
||
}
|
||
|
||
public override void Setup(float fClock, int nRate)
|
||
{
|
||
cpu_clock = fClock;
|
||
sampling_rate = nRate;
|
||
|
||
cycle_rate = (int)(fClock * 65536.0f / nRate);
|
||
}
|
||
|
||
public override void Write(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:
|
||
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;
|
||
}
|
||
}
|
||
|
||
public override int Process(int channel)
|
||
{
|
||
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;
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|