public void RunOne() { if (data.IRQ_delay != 0x7FFF) { if (data.IRQ_delay > 0) { --data.IRQ_delay.Value; } else { sequencer_irq = true; SyncIRQ(); data.IRQ_delay.Value = 0x7FFF; } } if (data.frame_delay > 0) { --data.frame_delay.Value; } else { bool Do240 = true, Do120 = false; data.frame_delay.Value += frame_period; switch (data.frame.Value++) { case 0: if (!(bool)data.IRQdisable && !(bool)data.FiveCycleDivider) { data.IRQ_delay.Value = frame_period * 4 + 2; } // passthru goto case 2; case 2: Do120 = true; break; case 1: data.frame_delay.Value -= 2; break; case 3: data.frame.Value = 0; if ((bool)data.FiveCycleDivider) { data.frame_delay.Value += frame_period - 6; } break; } // Some events are invoked at 96 Hz or 120 Hz rate. Others, 192 Hz or 240 Hz. for (int c = 0; c < 4; ++c) { APUchannel ch = channels[c]; int wl = ch.WaveLength; // 96/120 Hz events: if (Do120) { // Length tick (all channels except DMC, but different disable bit for triangle wave) if ((bool)ch.length_counter && !(c == 2 ? (bool)ch.LinearCounterDisable : (bool)ch.LengthCounterDisable)) { ch.length_counter.Value -= 1; } // Sweep tick (square waves only) int ref_ch_sweep_delay = ch.sweep_delay; if (c < 2 && count(ref ref_ch_sweep_delay, ch.SweepRate)) { if (wl >= 8 && (bool)ch.SweepEnable && (bool)ch.SweepShift) { int s = wl >> ch.SweepShift; wl += ((bool)ch.SweepDecrease ? ((c != 0) ? -s : ~s) : s); if (wl < 0x800) { ch.WaveLength.Value = (uint)wl; } } } ch.sweep_delay.Value = ref_ch_sweep_delay; } // 240/192 Hz events: if (Do240) { // Linear tick (triangle wave only) (all ticks) if (c == 2) { ch.linear_counter.Value = (bool)ch.LinearCounterDisable ? ch.LinearCounterInit : (ch.linear_counter > 0 ? ch.linear_counter - 1 : 0); } // Envelope tick (square and noise channels) (all ticks) int ref_ch_env_delay = ch.env_delay; if (c != 2 && count(ref ref_ch_env_delay, ch.EnvDecayRate)) { if (ch.envelope > 0 || (bool)ch.EnvDecayLoopEnable) { ch.envelope.Value = (ch.envelope - 1) & 15; } } ch.env_delay.Value = ref_ch_env_delay; } } } // Mix the audio: Get the momentary sample from each channel and mix them. // #define s(c) tick<c>() //v = [](float m,float n, float d) { return n!=0.f ? m/n : d; }; Func <float, float, float, float> v = (float m, float n, float d) => (n != 0.0f ? m / n : d); short sample = (short)(30000 * ( // Square 0 and 1 v(95.88f, (100.0f + v(8128.0f, tick(0) + tick(1), -100.0f)), 0.0f) // Triangle, noise, DMC + v(159.79f, (100.0f + v(1.0f, tick(2) / 8227.0f + tick(3) / 12241.0f + tick(4) / 22638.0f, -1000.0f)), 0.0f) // GamePak audio (these volume values are bogus, but sound acceptable) + v(95.88f, (100.0f + v(32512.0f, /*GamePak::ExtAudio()*/ 0, -100.0f)), 0.0f) - 0.5f )); EmitSample(sample); ////this (and the similar line below) is a crude hack ////we should be generating logic to suppress the $4015 clear when the assert signal is set instead ////be sure to test "apu_test" if you mess with this //sequencer_irq |= sequencer_irq_assert; }
void WriteC(int chno, int index, byte value) { APUchannel ch = channels[chno]; switch (index) { case 0: if ((bool)ch.LinearCounterDisable) { ch.linear_counter.Value = value & 0x7F; } ch.reg0.Value = value; break; case 1: ch.reg1.Value = value; ch.sweep_delay.Value = ch.SweepRate; break; case 2: ch.reg2.Value = value; break; case 3: ch.reg3.Value = value; if (data.ChannelsEnabled[chno] != 0) { ch.length_counter.Value = LengthCounters[ch.LengthCounterInit]; } ch.linear_counter.Value = ch.LinearCounterInit; ch.env_delay.Value = ch.EnvDecayRate; ch.envelope.Value = 15; if (index < 8) { ch.phase.Value = 0; } break; case 0x12: ch.reg0.Value = value; ch.address.Value = ((int)ch.reg0 | 0x300) << 6; break; case 0x10: ch.reg3.Value = value; ch.WaveLength.Value = (uint)(DMCperiods[value & 0x0F] - 1); if (!(bool)ch.IRQenable) { dmc_irq = false; SyncIRQ(); } break; case 0x13: // sample length ch.reg1.Value = value; if (ch.length_counter == 0) { ch.length_counter.Value = ch.PCMlength * 16 + 1; } break; case 0x11: // dac value ch.linear_counter.Value = value & 0x7F; break; case 0x15: for (int c = 0; c < 5; ++c) { data.ChannelsEnabled[c] = (uint)((value >> c) & 1); //noteworthy tweak } for (int c = 0; c < 5; ++c) { if (data.ChannelsEnabled[c] == 0) { channels[c].length_counter.Value = 0; } else if (c == 4 && channels[c].length_counter == 0) { APUchannel chh = channels[c]; chh.length_counter.Value = chh.PCMlength * 16 + 1; chh.address.Value = ((int)chh.reg0 | 0x300) << 6; chh.phase.Value = 0; } } //CPU::reg.APU_DMC_IRQ = false; dmc_irq = false; SyncIRQ(); break; case 0x17: data.IRQdisable.Value = (uint)(value & 0x40); data.FiveCycleDivider.Value = (uint)(value & 0x80); // apu_test 1-len_ctr: Writing $80 to $4017 should clock length immediately // But Writing $00 to $4017 shouldn't clock length immediately data.frame_delay.Value &= 1; data.frame.Value = 0; data.IRQ_delay.Value = 0x7FFF; if ((bool)data.IRQdisable) { sequencer_irq = false; SyncIRQ(); } if (!(bool)data.FiveCycleDivider) { data.frame.Value = 1; data.frame_delay.Value += frame_period; if (!(bool)data.IRQdisable) { data.IRQ_delay.Value = data.frame_delay + frame_period * 3 + 1 - 3; // ^ "- 3" makes apu_test "4-jitter" not complain // that "Frame irq is set too late" } } break; } }
int tick(int c) { APUchannel ch = channels[c]; int wl = ch.WaveLength; if (c != 4) { ++wl; } if (c < 2) { wl *= 2; } if (c == 3) { wl = NoisePeriods[ch.NoiseFreq]; } //if(c != 4) wl = wl * (IO::UISpeed); // ^ Match to the UI speed (but don't for DPCM, because it would skew the timings) int volume = (bool)ch.length_counter ? (bool)ch.EnvDecayDisable ? (int)ch.FixedVolume : ch.envelope : 0; // Sample may change at wavelen intervals. int ref_S = ch.level; int ref_ch_wave_counter = ch.wave_counter; if (!(data.ChannelsEnabled[c] != 0) || !count(ref ref_ch_wave_counter, wl)) { ch.wave_counter.Value = ref_ch_wave_counter; return(ref_S); } ch.wave_counter.Value = ref_ch_wave_counter; switch (c) { case 0: default: case 1: // Square wave. With four different 8-step binary waveforms (32 bits of data total). ch.phase.Value++; if (wl < 8) { return(ref_S); } if ((bool)ch.SweepEnable && !(bool)ch.SweepDecrease) { if (wl + (wl >> ch.SweepShift) >= 0x800) { return(ref_S); } } return(ref_S = ch.level.Value = (0xF33C0C04u & (1u << (ch.phase % 8 + ch.DutyCycle * 8))) != 0 ? volume : 0); case 2: // Triangle wave if ((bool)ch.length_counter && (bool)ch.linear_counter && wl >= 3) { ++ch.phase.Value; } return(ref_S = ch.level.Value = (ch.phase & 15) ^ (((ch.phase & 16) != 0) ? 15 : 0)); case 3: // Noise: Linear feedback shift register if (!(bool)ch.hold) { ch.hold.Value = 1; } ch.hold.Value = (ch.hold >> 1) | (((ch.hold ^ (ch.hold >> ((bool)ch.NoiseType ? 6 : 1))) & 1) << 14); return(ref_S = ch.level.Value = ((ch.hold & 1) != 0) ? 0 : volume); case 4: // Delta modulation channel (DMC) // hold = 8 bit value // phase = number of bits buffered // length_counter = if (wl == 0) { return(ref_S); } if (ch.phase == 0) // Nothing in sample buffer? { if (!(bool)ch.length_counter && (bool)ch.LoopEnabled) // Loop? { ch.length_counter.Value = ch.PCMlength * 16 + 1; ch.address.Value = (int)((ch.reg0 | 0x300) << 6); } if (ch.length_counter > 0) // Load next 8 bits if available { //==========================TODO============== DMC COST===================== //for(unsigned t = data.DMC_CycleCost; t > 1; --t) // CPU::RB(u16(ch.address) | 0x8000); // timing ch.hold.Value = nes.ReadMemory((ushort)((ch.address.Value++) | 0x8000)); // Fetch byte ch.phase.Value = 8; --ch.length_counter.Value; } else // Otherwise, disable channel or issue IRQ { if ((bool)ch.IRQenable) { //CPU::reg.APU_DMC_IRQ = true; dmc_irq = true; SyncIRQ(); } data.ChannelsEnabled[4] = 0; } } if (ch.phase != 0) // Update the signal if sample buffer nonempty { int v = ch.linear_counter; if (((ch.hold << --ch.phase.Value) & 0x80) != 0) { v += 2; } else { v -= 2; } if (v >= 0 && v <= 0x7F) { ch.linear_counter.Value = v; } } return(ref_S = ch.level = ch.linear_counter); } }