diff --git a/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp b/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp index c8e2b19..9ee849e 100644 --- a/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp +++ b/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp @@ -341,6 +341,7 @@ public: enabled = true; squareAlternate = 0; lastChannel = 0xFF; + filteredFreqHz = 0.0; } [[nodiscard]] uint8_t read(uint16_t addr) const { @@ -366,6 +367,9 @@ public: for (std::size_t i = 0; i < kWaveRamSize; ++i) regs[kWaveOffset + i] = wave[i]; regs[kPowerIndex] = static_cast(value & 0x80U); + squareAlternate = 0; + lastChannel = 0xFF; + filteredFreqHz = 0.0; } return; } @@ -423,11 +427,14 @@ public: static constexpr uint8_t kInitialWave[kInitialWaveCount] = {0xAC, 0xDD, 0xDA, 0x48, 0x36, 0x02, 0xCF, 0x16, 0x2C, 0x04, 0xE5, 0x2C, 0xAC, 0xDD, 0xDA, 0x48}; + enum class Channel : uint8_t { Square1 = 0, Square2 = 1, Wave = 2, Noise = 3 }; + GameboyApp* owner = nullptr; std::array regs{}; bool enabled = true; mutable uint8_t squareAlternate = 0; mutable uint8_t lastChannel = 0xFF; + mutable double filteredFreqHz = 0.0; static constexpr bool inRange(uint16_t addr) { return addr >= kBaseAddr && addr <= (kBaseAddr + static_cast(kRegisterCount) - 1); @@ -447,17 +454,40 @@ public: return 131072.0 / denom; } + [[nodiscard]] static double snapNoiseFrequency(double freq) { + static constexpr double kNoisePreferredHz[] = {650.0, 820.0, 990.0, 1200.0, 1500.0, + 1850.0, 2200.0, 2600.0, 3100.0, 3600.0}; + if (!(freq > 0.0)) + return freq; + double bestFreq = freq; + double bestDiff = 1.0e9; + for (double target: kNoisePreferredHz) { + double diff = freq - target; + if (diff < 0.0) + diff = -diff; + if (diff < bestDiff) { + bestDiff = diff; + bestFreq = target; + } + } + if (bestDiff <= 500.0) + return bestFreq; + return freq; + } + // Mixer: compute best single-tone approximation for the buzzer. // Returns true if a tone is suggested, with outFreqHz set. public: bool computeEffectiveTone(uint32_t& outFreqHz, uint8_t& outLoudness) const { const uint8_t nr50 = reg(kVolumeAddr); const uint8_t master = static_cast(std::max(nr50 & 0x07U, (nr50 >> 4) & 0x07U)); - if (master == 0) + if (master == 0) { + filteredFreqHz = 0.0; + lastChannel = 0xFF; return false; + } const uint8_t routing = reg(kRoutingAddr); - enum class Channel : uint8_t { Square1 = 0, Square2 = 1, Wave = 2, Noise = 3 }; struct Candidate { double freq; uint8_t loud; @@ -465,9 +495,8 @@ public: Channel channel; }; - Candidate candidates[4]; - std::size_t candidateCount = 0; - + Candidate candidates[4]; + std::size_t candidateCount = 0; constexpr std::size_t kMaxCandidates = sizeof(candidates) / sizeof(candidates[0]); auto pushCandidate = [&](double freq, uint8_t loud, int prio, Channel channel) { @@ -506,14 +535,20 @@ public: if ((reg(kPowerAddr) & 0x04U) != 0 && (reg(kCh3EnableAddr) & 0x80U) != 0) { const uint8_t levelSel = (reg(kCh3LevelAddr) >> 5) & 0x03U; const bool routed = ((routing & 0x44U) != 0); - if (levelSel != 0 && routed) { + uint8_t loudBase = 0; + if (levelSel == 1) + loudBase = 16; + else if (levelSel == 2) + loudBase = 8; + else if (levelSel == 3) + loudBase = 4; + if (levelSel != 0 && routed && loudBase != 0) { const uint16_t raw = static_cast(((reg(kCh3TriggerAddr) & 0x07U) << 8) | reg(kCh3FreqLoAddr)); if (raw < 2048U) { - const double denom = static_cast(2048U - raw); - const double freq = 2097152.0 / denom; - const uint8_t loudBase = (levelSel == 1 ? 16 : levelSel == 2 ? 8 : 4); - const uint8_t loud = static_cast(loudBase * master); + const double denom = static_cast(2048U - raw); + const double freq = 2097152.0 / denom; + const uint8_t loud = static_cast(loudBase * master); pushCandidate(freq, loud, 2, Channel::Wave); } } @@ -532,15 +567,18 @@ public: const int div = divLut[dividerId]; double freq = 4194304.0 / (static_cast(div) * std::pow(2.0, static_cast(shift + 1))); - freq = std::clamp(freq, 600.0, 3200.0); + freq = snapNoiseFrequency(std::clamp(freq, 600.0, 3600.0)); const uint8_t loud = static_cast(vol4 * master); pushCandidate(freq, loud, 1, Channel::Noise); } } #endif - if (candidateCount == 0) + if (candidateCount == 0) { + lastChannel = 0xFF; + filteredFreqHz = 0.0; return false; + } const Candidate* squareCandidates[2] = {nullptr, nullptr}; const Candidate* bestOther = nullptr; @@ -590,7 +628,27 @@ public: if (!best) return false; - const double clamped = std::clamp(best->freq, 40.0, 5500.0); + double selectedFreq = best->freq; + if (!(selectedFreq > 0.0) || !std::isfinite(selectedFreq)) + return false; + + const double prevFiltered = filteredFreqHz; + if (!(prevFiltered > 0.0) || !std::isfinite(prevFiltered) || + static_cast(best->channel) != lastChannel) { + filteredFreqHz = selectedFreq; + } else { + double diff = selectedFreq - prevFiltered; + if (diff < 0.0 && -diff > 1200.0) + filteredFreqHz = selectedFreq; + else if (diff > 1200.0) + filteredFreqHz = selectedFreq; + else { + double alpha = (best->channel == Channel::Noise) ? 0.45 : 0.35; + filteredFreqHz = prevFiltered + (diff * alpha); + } + } + + const double clamped = std::clamp(filteredFreqHz, 40.0, 5500.0); outFreqHz = static_cast(clamped + 0.5); outLoudness = best->loud; lastChannel = static_cast(best->channel);