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; } } }