From 07186b4b735ca262ae7b9ca01f6408c6bb09ef40 Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Mon, 13 Oct 2025 00:17:16 +0200 Subject: [PATCH] sound upgrade --- Firmware/sdk/apps/gameboy/src/gameboy_app.cpp | 159 ++++++++++++------ 1 file changed, 109 insertions(+), 50 deletions(-) diff --git a/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp b/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp index 765424e..c8e2b19 100644 --- a/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp +++ b/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp @@ -339,6 +339,8 @@ public: regs[kWaveOffset + i] = kInitialWave[i]; regs[kPowerIndex] = 0x80; enabled = true; + squareAlternate = 0; + lastChannel = 0xFF; } [[nodiscard]] uint8_t read(uint16_t addr) const { @@ -396,6 +398,13 @@ public: static constexpr uint16_t kCh2EnvAddr = 0xFF17; static constexpr uint16_t kCh2FreqLoAddr = 0xFF18; static constexpr uint16_t kCh2TriggerAddr = 0xFF19; + static constexpr uint16_t kCh3EnableAddr = 0xFF1A; + static constexpr uint16_t kCh3LevelAddr = 0xFF1C; + static constexpr uint16_t kCh3FreqLoAddr = 0xFF1D; + static constexpr uint16_t kCh3TriggerAddr = 0xFF1E; + static constexpr uint16_t kCh4EnvAddr = 0xFF21; + static constexpr uint16_t kCh4PolyAddr = 0xFF22; + static constexpr uint16_t kCh4TriggerAddr = 0xFF23; static constexpr uint16_t kVolumeAddr = 0xFF24; static constexpr uint16_t kRoutingAddr = 0xFF25; static constexpr uint16_t kWaveBase = 0xFF30; @@ -416,7 +425,9 @@ public: GameboyApp* owner = nullptr; std::array regs{}; - bool enabled = true; + bool enabled = true; + mutable uint8_t squareAlternate = 0; + mutable uint8_t lastChannel = 0xFF; static constexpr bool inRange(uint16_t addr) { return addr >= kBaseAddr && addr <= (kBaseAddr + static_cast(kRegisterCount) - 1); @@ -440,101 +451,149 @@ public: // Returns true if a tone is suggested, with outFreqHz set. public: bool computeEffectiveTone(uint32_t& outFreqHz, uint8_t& outLoudness) const { - // Master volume and routing const uint8_t nr50 = reg(kVolumeAddr); const uint8_t master = static_cast(std::max(nr50 & 0x07U, (nr50 >> 4) & 0x07U)); if (master == 0) 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; int prio; - } best{0.0, 0, 0}; + Channel channel; + }; + + 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) { + if (candidateCount >= kMaxCandidates) + return; + if (!std::isfinite(freq) || freq <= 10.0 || loud == 0) + return; + candidates[candidateCount++] = Candidate{freq, loud, prio, channel}; + }; #if GB_BUZZER_ENABLE_CH1 - // CH1 square with sweep - if (reg(kPowerAddr) & 0x01U) { + if ((reg(kPowerAddr) & 0x01U) != 0) { const uint8_t env = reg(kCh1EnvAddr); const uint8_t vol4 = (env >> 4) & 0x0FU; const bool routed = ((routing & 0x11U) != 0); if (vol4 && routed) { - const double f = squareFrequency(0); - if (std::isfinite(f) && f > 10.0) { - uint8_t loud = static_cast(vol4 * master); - if (loud > best.loud) - best = {f, loud, 3}; - } + const double freq = squareFrequency(0); + const uint8_t loud = static_cast(vol4 * master); + pushCandidate(freq, loud, 3, Channel::Square1); } } #endif #if GB_BUZZER_ENABLE_CH2 - // CH2 square - if (reg(kPowerAddr) & 0x02U) { + if ((reg(kPowerAddr) & 0x02U) != 0) { const uint8_t env = reg(kCh2EnvAddr); const uint8_t vol4 = (env >> 4) & 0x0FU; const bool routed = ((routing & 0x22U) != 0); if (vol4 && routed) { - const double f = squareFrequency(1); - if (std::isfinite(f) && f > 10.0) { - uint8_t loud = static_cast(vol4 * master); - if (loud >= best.loud) - best = {f, loud, 2}; - } + const double freq = squareFrequency(1); + const uint8_t loud = static_cast(vol4 * master); + pushCandidate(freq, loud, 3, Channel::Square2); } } #endif #if GB_BUZZER_ENABLE_CH3 - // CH3 wave (approximate as square at its base frequency scaled by level) - if (reg(kPowerAddr) & 0x04U) { - const uint8_t nr32 = regs[0xFF1C - kBaseAddr]; - const uint8_t levelSel = (nr32 >> 5) & 0x03U; // 0=0,1=100%,2=50%,3=25% + 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) { const uint16_t raw = - static_cast(((reg(kCh2TriggerAddr) & 0x07U) << 8) | reg(kCh2FreqLoAddr)); - // Use wave constants: 2097152 / (2048 - N) + static_cast(((reg(kCh3TriggerAddr) & 0x07U) << 8) | reg(kCh3FreqLoAddr)); if (raw < 2048U) { - const double denom = static_cast(2048U - raw); - double f = 2097152.0 / denom; - if (std::isfinite(f) && f > 10.0) { - uint8_t loudBase = (levelSel == 1 ? 16 : levelSel == 2 ? 8 : 4); - uint8_t loud = static_cast(loudBase * master); - if (loud > best.loud) - best = {f, loud, 1}; - } + 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); + pushCandidate(freq, loud, 2, Channel::Wave); } } } #endif #if GB_BUZZER_ENABLE_CH4 - // CH4 noise (approximate as pitched noise -> pick a center frequency from regs) - if (reg(kPowerAddr) & 0x08U) { + if ((reg(kPowerAddr) & 0x08U) != 0) { const bool routed = ((routing & 0x88U) != 0); - const uint8_t env = regs[0xFF21 - kBaseAddr]; + const uint8_t env = reg(kCh4EnvAddr); const uint8_t vol4 = (env >> 4) & 0x0FU; if (vol4 && routed) { - const uint8_t nr43 = regs[0xFF22 - kBaseAddr]; - const uint8_t s = (nr43 >> 4) & 0x0FU; // shift clock frequency - const uint8_t d = nr43 & 0x07U; // divider code (0->8) + const uint8_t nr43 = reg(kCh4PolyAddr); + const uint8_t shift = (nr43 >> 4) & 0x0FU; + const uint8_t dividerId = nr43 & 0x07U; static const int divLut[8] = {8, 16, 32, 48, 64, 80, 96, 112}; - const int div = divLut[d]; - double f = 4194304.0 / (static_cast(div) * std::pow(2.0, s + 1)); - // clamp to sensible buzzer range; noise sounds better around 1-3k - f = std::clamp(f, 600.0, 3200.0); - uint8_t loud = static_cast(vol4 * master); - if (loud > best.loud) - best = {f, loud, 0}; + 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); + const uint8_t loud = static_cast(vol4 * master); + pushCandidate(freq, loud, 1, Channel::Noise); } } #endif - if (best.loud == 0 || !std::isfinite(best.freq)) + + if (candidateCount == 0) return false; - // Clamp final freq to buzzer range - const double clamped = std::clamp(best.freq, 40.0, 5500.0); + + const Candidate* squareCandidates[2] = {nullptr, nullptr}; + const Candidate* bestOther = nullptr; + + for (std::size_t i = 0; i < candidateCount; ++i) { + const Candidate* cand = &candidates[i]; + if (cand->channel == Channel::Square1) + squareCandidates[0] = cand; + else if (cand->channel == Channel::Square2) + squareCandidates[1] = cand; + else if (!bestOther || cand->loud > bestOther->loud || + (cand->loud == bestOther->loud && cand->prio > bestOther->prio) || + (cand->loud == bestOther->loud && cand->prio == bestOther->prio && + cand->freq > bestOther->freq)) + bestOther = cand; + } + + const Candidate* bestSquare = nullptr; + if (squareCandidates[0] && squareCandidates[1]) { + int loudDiff = + static_cast(squareCandidates[0]->loud) - static_cast(squareCandidates[1]->loud); + if (loudDiff < 0) + loudDiff = -loudDiff; + if (loudDiff <= 2) { + const Candidate* preferred = (squareAlternate & 1U) ? squareCandidates[1] : squareCandidates[0]; + bestSquare = preferred; + squareAlternate ^= 1U; + } else { + bestSquare = (squareCandidates[0]->loud > squareCandidates[1]->loud) ? squareCandidates[0] + : squareCandidates[1]; + } + } else if (squareCandidates[0] || squareCandidates[1]) { + bestSquare = squareCandidates[0] ? squareCandidates[0] : squareCandidates[1]; + } + + const Candidate* best = bestSquare; + if (!best) + best = bestOther; + else if (bestOther) { + if (bestOther->loud > best->loud || (bestOther->loud == best->loud && bestOther->prio > best->prio) || + (bestOther->loud == best->loud && bestOther->prio == best->prio && + static_cast(bestOther->channel) == lastChannel && + static_cast(best->channel) != lastChannel)) + best = bestOther; + } + + if (!best) + return false; + + const double clamped = std::clamp(best->freq, 40.0, 5500.0); outFreqHz = static_cast(clamped + 0.5); - outLoudness = best.loud; + outLoudness = best->loud; + lastChannel = static_cast(best->channel); return true; } };