#include "apu.h" // Qt includes #include // system includes #include // local includes #include "nesemulator.h" #include "emusettings.h" Apu::Apu(NesEmulator &emu) : QObject(&emu), m_emu(emu), m_dmc(*this), m_nos(*this), m_sq1(*this), m_sq2(*this), m_trl(*this), m_lowPassFilter(0.815686), // 14 KHz m_highPassFilter1(0.999835), // 90 Hz m_highPassFilter2(0.996039) // 442 Hz { } void Apu::hardReset() { m_regIoDb = 0; m_regIoAddr = 0; m_regAccessHappened = false; m_regAccessW = false; m_seqMode = false; m_oddCycle = false; m_cycleFt = 0; m_cycleE = 4; m_cycleF = 4; m_cycleL = 4; m_oddL = false; m_checkIrq = false; m_doEnv = false; m_doLength = false; m_sq1.apuSq1HardReset(); m_sq2.apuSq2HardReset(); m_nos.apuNosHardReset(); m_dmc.apuDmcHardReset(); m_trl.apuTrlHardReset(); m_irqEnabled = true; m_irqFlag = false; m_timer = 0.; m_audioX = 0.; m_audioX1 = 0.; m_audioY = 0.; m_audioYClocks = 0.; m_highPassFilter1.reset(); m_highPassFilter2.reset(); m_lowPassFilter.reset(); } void Apu::softReset() { m_regIoDb = 0; m_regIoAddr = 0; m_regAccessHappened = false; m_regAccessW = false; m_seqMode = false; m_oddCycle = false; m_cycleFt = 0; m_cycleE = 4; m_cycleF = 4; m_cycleL = 4; m_oddL = false; m_checkIrq = false; m_doEnv = false; m_doLength = false; m_irqEnabled = true; m_irqFlag = false; m_sq1.apuSq1SoftReset(); m_sq2.apuSq2SoftReset(); m_trl.apuTrlSoftReset(); m_nos.apuNosSoftReset(); m_dmc.apuDmcSoftReset(); } quint8 Apu::_ioRead(const quint16 address) { static constexpr std::array apuRegReadFunc = []() { std::array apuRegReadFunc {}; for(std::size_t i = 0; i < 0x20; i++) apuRegReadFunc[i] = &Apu::blankAccess; apuRegReadFunc[0x15] = &Apu::read4015; apuRegReadFunc[0x16] = &Apu::read4016; apuRegReadFunc[0x17] = &Apu::read4017; return apuRegReadFunc; }(); if(address >= 0x4020) return m_emu.memory().board()->readEx(address); else { m_regIoAddr = address & 0x1F; m_regAccessHappened = true; m_regAccessW = false; (this->*apuRegReadFunc[m_regIoAddr])(); return m_regIoDb; } } quint8 Apu::ioRead(const quint16 address) { auto result = _ioRead(address); return result; } void Apu::ioWrite(const quint16 address, const quint8 value) { if(address >= 0x4020) m_emu.memory().board()->writeEx(address, value); else { m_regIoDb = value; m_regIoAddr = address & 0x1F; m_regAccessHappened = true; m_regAccessW = true; } } void Apu::blankAccess() { } void Apu::onRegister4014() { if(!m_regAccessW) return; // oam dma m_emu.dma().setOamAddress(m_regIoDb << 8); m_emu.dma().assertOamDma(); } void Apu::onRegister4015() { if(!m_regAccessW) { // on reads, do the effects we know m_irqFlag = false; m_emu.interrupts().removeFlag(Interrupts::IRQ_APU); } else { // do a normal write m_sq1.apuSq1On4015(); m_sq2.apuSq2On4015(); m_nos.apuNosOn4015(); m_trl.apuTrlOn4015(); m_dmc.apuDmcOn4015(); } } void Apu::onRegister4016() { // Only writes accepted if(m_regAccessW) { if(m_inputStrobe && m_regIoDb == 0) m_emu.ports().updatePorts(); m_inputStrobe = m_regIoDb; } else m_emu.ports().setPort0(m_emu.ports().port0() >> 1); } void Apu::onRegister4017() { if(m_regAccessW) { m_seqMode = (m_regIoDb & 0x80) != 0; m_irqEnabled = (m_regIoDb & 0x40) == 0; // Reset counters m_cycleE = -1; m_cycleL = -1; m_cycleF = -1; m_oddL = false; // Clock immediately ? m_doLength = m_seqMode; m_doEnv = m_seqMode; // Reset irq m_checkIrq = false; if(!m_irqEnabled) { m_irqFlag = false; m_emu.interrupts().removeFlag(Interrupts::IRQ_APU); } } else m_emu.ports().setPort1(m_emu.ports().port1() >> 1); } void Apu::read4015() { m_regIoDb = m_regIoDb & 0x20; // Channels enable m_sq1.apuSq1Read4015(); m_sq2.apuSq2Read4015(); m_nos.apuNosRead4015(); m_trl.apuTrlRead4015(); m_dmc.apuDmcRead4015(); // IRQ if(m_irqFlag) m_regIoDb = (m_regIoDb & 0xBF) | 0x40; if(m_irqDeltaOccur) m_regIoDb = (m_regIoDb & 0x7F) | 0x80; } void Apu::read4016() { m_regIoDb = m_emu.ports().port0() & 1; } void Apu::read4017() { m_regIoDb = m_emu.ports().port1() & 1; } void Apu::clock() { m_oddCycle = !m_oddCycle; if(m_doEnv) clockEnvelope(); if(m_doLength) clockDuration(); if(!m_oddCycle) { // IRQ m_cycleF++; if(m_cycleF >= m_freqF) { m_cycleF = -1; m_checkIrq = true; m_cycleFt = 3; } // Envelope m_cycleE++; if(m_cycleE >= m_freqE) { m_cycleE = -1; // Clock envelope and other units except when: // 1 the seq mode is set // 2 it is the time of irq check clock if(m_checkIrq) { if(!m_seqMode) { // this is the 3rd step of mode 0, do a reset m_doEnv = true; } else { // the next step will be the 4th step of mode 1 // so, shorten the step then do a reset m_cycleE = 4; } } else m_doEnv = true; } // Length m_cycleL++; if(m_cycleL >= m_freqL) { m_oddL = !m_oddL; m_cycleL = m_oddL ? -2 : -1; // Clock duration and sweep except when: // 1 the seq mode is set // 2 it is the time of irq check clock if(m_checkIrq && m_seqMode) { m_cycleL = 3730;// Next step will be after 7456 - 3730 = 3726 cycles, 2 cycles shorter than e freq m_oddL = true; } else { m_doLength = true; } } if(m_regAccessHappened) { m_regAccessHappened = false; switch(m_regIoAddr) { case 0: m_sq1.apuOnRegister4000(); break; case 1: m_sq1.apuOnRegister4001(); break; case 2: m_sq1.apuOnRegister4002(); break; case 3: m_sq1.apuOnRegister4003(); break; case 4: m_sq2.apuOnRegister4004(); break; case 5: m_sq2.apuOnRegister4005(); break; case 6: m_sq2.apuOnRegister4006(); break; case 7: m_sq2.apuOnRegister4007(); break; case 8: m_trl.apuOnRegister4008(); break; case 9: m_trl.apuOnRegister4009(); break; case 10: m_trl.apuOnRegister400A(); break; case 11: m_trl.apuOnRegister400B(); break; case 12: m_nos.apuOnRegister400C(); break; case 13: m_nos.apuOnRegister400D(); break; case 14: m_nos.apuOnRegister400E(); break; case 15: m_nos.apuOnRegister400F(); break; case 16: m_dmc.apuOnRegister4010(); break; case 17: m_dmc.apuOnRegister4011(); break; case 18: m_dmc.apuOnRegister4012(); break; case 19: m_dmc.apuOnRegister4013(); break; case 20: onRegister4014(); break; case 21: onRegister4015(); break; case 22: onRegister4016(); break; case 23: onRegister4017(); break; case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: blankAccess(); break; } } m_sq1.apuSq1Clock(); m_sq2.apuSq2Clock(); m_nos.apuNosClock(); if(m_emu.memory().board()->enableExternalSound()) m_emu.memory().board()->onApuClock(); //apuUpdatePlayback(); } m_trl.apuTrlClock(); m_dmc.apuDmcClock(); if(m_emu.memory().board()->enableExternalSound()) m_emu.memory().board()->onApuClockSingle(); updatePlayback(); if(m_checkIrq) { if(!m_seqMode) checkIrq(); // This is stupid ... :( m_cycleFt--; if(m_cycleFt == 0) m_checkIrq = false; } } void Apu::clockDuration() { m_sq1.apuSq1ClockLength(); m_sq2.apuSq2ClockLength(); m_nos.apuNosClockLength(); m_trl.apuTrlClockLength(); if(m_emu.memory().board()->enableExternalSound()) m_emu.memory().board()->onApuClockDuration(); m_doLength = false; } void Apu::clockEnvelope() { m_sq1.apuSq1ClockEnvelope(); m_sq2.apuSq2ClockEnvelope(); m_nos.apuNosClockEnvelope(); m_trl.apuTrlClockEnvelope(); if(m_emu.memory().board()->enableExternalSound()) m_emu.memory().board()->onApuClockEnvelope(); m_doEnv = false; } void Apu::checkIrq() { if(m_irqEnabled) m_irqFlag = true; if(m_irqFlag) m_emu.interrupts().addFlag(Interrupts::IRQ_APU); } void Apu::updatePlayback() { static constexpr std::array audioPulseTable = []() constexpr { std::array audioPulseTable {}; for(std::size_t i = 1; i < 32; i++) audioPulseTable[i] = 95.52 / (8128. / i + 100.); return audioPulseTable; }(); static constexpr std::array audioTndTable = []() constexpr { std::array audioTndTable {}; for(std::size_t i = 1; i < 204; i++) audioTndTable[i] = 163.67 / (24329. / i + 100.); return audioTndTable; }(); // Collect the sample m_pulseOut = audioPulseTable[m_sq1.output() + m_sq2.output()]; m_tndOut = audioTndTable[(3 * m_trl.output()) + (2 * m_nos.output()) + m_dmc.output()]; m_audioX = m_pulseOut + m_tndOut; if(m_emu.memory().board()->enableExternalSound()) { m_audioX += m_emu.memory().board()->apuGetSample(); m_audioX /= 2; } m_audioX *= EmuSettings::Audio::internalAmplitude; // An implementation of the "Band-Limited Sound Synthesis" algorithm. // http://www.slack.net/~ant/bl-synth // Shay Green (swap to e-mail) // No sure about this, it just add the sample difference in each step. // Maybe here it is acting just like a low-pass filter ... if(m_audioX != m_audioX1) { if(m_audioX > m_audioX1) m_audioY += m_audioX - m_audioX1; else m_audioY += m_audioX1 - m_audioX; m_audioX = m_audioX1; } m_audioYClocks++; m_timer++; const auto audiotimerratio = m_freqL * 2. * 120. / m_sampleRate; if(m_timer >= audiotimerratio) { // Clock by output sample rate. m_timer -= audiotimerratio; // Get the sum of the samples m_audioY /= m_audioYClocks; // Do filtering, add 2 high-pass and one low-pass filters. See http://wiki.nesdev.com/w/index.php/APUMixer double audioDcY; audioDcY = m_highPassFilter2.doFiltering(m_audioY);// 442 Hz audioDcY = m_highPassFilter1.doFiltering(audioDcY);// 90 Hz audioDcY = m_lowPassFilter.doFiltering(audioDcY);// 14 KHz audioDcY = std::clamp(audioDcY, double(-EmuSettings::Audio::internalPeekLimit), double(EmuSettings::Audio::internalPeekLimit)); m_samples.append(audioDcY); m_audioY = 0; m_audioYClocks = 0; } } void Apu::writeState(QDataStream &dataStream) const { dataStream << m_regIoDb << m_regIoAddr << m_regAccessHappened << m_regAccessW << m_oddCycle << m_irqEnabled << m_irqFlag << m_irqDeltaOccur << m_seqMode << m_cycleF << m_cycleE << m_cycleL << m_oddL << m_cycleFt << m_checkIrq << m_doEnv << m_doLength << m_inputStrobe; m_sq1.apuSq1WriteState(dataStream); m_sq2.apuSq2WriteState(dataStream); m_nos.apuNosWriteState(dataStream); m_trl.apuTrlWriteState(dataStream); m_dmc.apuDmcWriteState(dataStream); } void Apu::readState(QDataStream &dataStream) { dataStream >> m_regIoDb >> m_regIoAddr >> m_regAccessHappened >> m_regAccessW >> m_oddCycle >> m_irqEnabled >> m_irqFlag >> m_irqDeltaOccur >> m_seqMode >> m_cycleF >> m_cycleE >> m_cycleL >> m_oddL >> m_cycleFt >> m_checkIrq >> m_doEnv >> m_doLength >> m_inputStrobe; m_sq1.apuSq1ReadState(dataStream); m_sq2.apuSq2ReadState(dataStream); m_nos.apuNosReadState(dataStream); m_trl.apuTrlReadState(dataStream); m_dmc.apuDmcReadState(dataStream); } void Apu::flush() { m_timer = 0; Q_EMIT samplesFinished(m_samples); m_samples.clear(); } bool Apu::oddCycle() const { return m_oddCycle; } NesEmulator &Apu::emu() { return m_emu; } const NesEmulator &Apu::emu() const { return m_emu; } ApuDmc &Apu::dmc() { return m_dmc; } const ApuDmc &Apu::dmc() const { return m_dmc; } ApuNos &Apu::nos() { return m_nos; } const ApuNos &Apu::nos() const { return m_nos; } ApuSq1 &Apu::sq1() { return m_sq1; } const ApuSq1 &Apu::sq1() const { return m_sq1; } ApuSq2 &Apu::sq2() { return m_sq2; } const ApuSq2 &Apu::sq2() const { return m_sq2; } ApuTrl &Apu::trl() { return m_trl; } const ApuTrl &Apu::trl() const { return m_trl; } void Apu::setIrqDeltaOccur(bool irqDeltaOccur) { m_irqDeltaOccur = irqDeltaOccur; } bool Apu::regAccessW() const { return m_regAccessW; } quint8 Apu::regIoDb() const { return m_regIoDb; } void Apu::setRegIoDb(quint8 regIoDb) { m_regIoDb = regIoDb; } bool Apu::regAccessHappened() const { return m_regAccessHappened; } quint8 Apu::regIoAddr() const { return m_regIoAddr; } qint32 Apu::sampleRate() const { return m_sampleRate; } void Apu::setSampleRate(qint32 sampleRate) { m_sampleRate = sampleRate; } const std::array, 4> Apu::m_sqDutyCycleSequences { std::array { 0, 1, 0, 0, 0, 0, 0, 0 }, // 12.5% std::array { 0, 1, 1, 0, 0, 0, 0, 0 }, // 25.0% std::array { 0, 1, 1, 1, 1, 0, 0, 0 }, // 50.0% std::array { 1, 0, 0, 1, 1, 1, 1, 1 }, // 75.0% (25.0% negated) }; const std::array Apu::m_sqDurationTable { 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E, };