From e37f8e3dc812d3a334e5b130f4bdf8779bdca9b0 Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Mon, 13 Oct 2025 00:39:26 +0200 Subject: [PATCH] a little better sound --- Firmware/sdk/apps/gameboy/src/gameboy_app.cpp | 164 +++++++++++++++--- 1 file changed, 143 insertions(+), 21 deletions(-) diff --git a/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp b/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp index 9ee849e..6f58d3b 100644 --- a/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp +++ b/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp @@ -342,6 +342,8 @@ public: squareAlternate = 0; lastChannel = 0xFF; filteredFreqHz = 0.0; + lastSquareRaw = {0, 0}; + squareStable = {0, 0}; } [[nodiscard]] uint8_t read(uint16_t addr) const { @@ -370,6 +372,8 @@ public: squareAlternate = 0; lastChannel = 0xFF; filteredFreqHz = 0.0; + lastSquareRaw = {0, 0}; + squareStable = {0, 0}; } return; } @@ -431,10 +435,12 @@ public: GameboyApp* owner = nullptr; std::array regs{}; - bool enabled = true; - mutable uint8_t squareAlternate = 0; - mutable uint8_t lastChannel = 0xFF; - mutable double filteredFreqHz = 0.0; + bool enabled = true; + mutable uint8_t squareAlternate = 0; + mutable uint8_t lastChannel = 0xFF; + mutable double filteredFreqHz = 0.0; + mutable std::array lastSquareRaw{}; + mutable std::array squareStable{}; static constexpr bool inRange(uint16_t addr) { return addr >= kBaseAddr && addr <= (kBaseAddr + static_cast(kRegisterCount) - 1); @@ -442,10 +448,12 @@ public: [[nodiscard]] uint8_t reg(uint16_t addr) const { return regs[static_cast(addr - kBaseAddr)]; } - [[nodiscard]] double squareFrequency(int channelIndex) const { + [[nodiscard]] double squareFrequency(int channelIndex, uint16_t* outRaw = nullptr) const { const uint16_t freqLoAddr = (channelIndex == 0) ? kCh1FreqLoAddr : kCh2FreqLoAddr; const uint16_t freqHiAddr = (channelIndex == 0) ? kCh1TriggerAddr : kCh2TriggerAddr; const uint16_t raw = static_cast(((reg(freqHiAddr) & 0x07U) << 8) | reg(freqLoAddr)); + if (outRaw) + *outRaw = raw; if (raw >= 2048U) return 0.0; const double denom = static_cast(2048U - raw); @@ -499,6 +507,25 @@ public: std::size_t candidateCount = 0; constexpr std::size_t kMaxCandidates = sizeof(candidates) / sizeof(candidates[0]); + // Track how stable each square channel's raw frequency is so we can bias selection. + auto updateSquareHistory = [&](int idx, uint16_t raw) { + if (raw == 0 || raw >= 2048U) { + squareStable[static_cast(idx)] = 0; + lastSquareRaw[static_cast(idx)] = 0; + return; + } + if (lastSquareRaw[static_cast(idx)] == raw) { + const uint8_t current = squareStable[static_cast(idx)]; + if (current < 0xFD) + squareStable[static_cast(idx)] = static_cast(current + 1); + } else { + lastSquareRaw[static_cast(idx)] = raw; + squareStable[static_cast(idx)] = 0; + } + }; + + bool squareActive[2] = {false, false}; + auto pushCandidate = [&](double freq, uint8_t loud, int prio, Channel channel) { if (candidateCount >= kMaxCandidates) return; @@ -513,9 +540,17 @@ public: const uint8_t vol4 = (env >> 4) & 0x0FU; const bool routed = ((routing & 0x11U) != 0); if (vol4 && routed) { - const double freq = squareFrequency(0); + uint16_t raw = 0; + const double freq = squareFrequency(0, &raw); const uint8_t loud = static_cast(vol4 * master); - pushCandidate(freq, loud, 3, Channel::Square1); + if (freq > 0.0) { + squareActive[0] = true; + updateSquareHistory(0, raw); + pushCandidate(freq, loud, 3, Channel::Square1); + } else { + squareStable[0] = 0; + lastSquareRaw[0] = 0; + } } } #endif @@ -525,9 +560,17 @@ public: const uint8_t vol4 = (env >> 4) & 0x0FU; const bool routed = ((routing & 0x22U) != 0); if (vol4 && routed) { - const double freq = squareFrequency(1); + uint16_t raw = 0; + const double freq = squareFrequency(1, &raw); const uint8_t loud = static_cast(vol4 * master); - pushCandidate(freq, loud, 3, Channel::Square2); + if (freq > 0.0) { + squareActive[1] = true; + updateSquareHistory(1, raw); + pushCandidate(freq, loud, 3, Channel::Square2); + } else { + squareStable[1] = 0; + lastSquareRaw[1] = 0; + } } } #endif @@ -580,8 +623,18 @@ public: return false; } + for (int idx = 0; idx < 2; ++idx) { + if (!squareActive[idx]) { + squareStable[static_cast(idx)] = 0; + lastSquareRaw[static_cast(idx)] = 0; + } + } + const Candidate* squareCandidates[2] = {nullptr, nullptr}; + const Candidate* waveCandidate = nullptr; + bool waveBass = false; const Candidate* bestOther = nullptr; + int bestOtherScore = -1; for (std::size_t i = 0; i < candidateCount; ++i) { const Candidate* cand = &candidates[i]; @@ -589,11 +642,27 @@ public: 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; + else { + if (cand->channel == Channel::Wave) { + waveCandidate = cand; + waveBass = (cand->freq > 0.0 && cand->freq < 220.0); + } + int score = static_cast(cand->loud); + if (waveBass) { + if (cand->channel == Channel::Wave) + score += 3; + else if (cand->channel == Channel::Noise) + score -= 1; + } + if (score < 0) + score = 0; + if (!bestOther || score > bestOtherScore || + (score == bestOtherScore && cand->prio > bestOther->prio) || + (score == bestOtherScore && cand->prio == bestOther->prio && cand->freq > bestOther->freq)) { + bestOther = cand; + bestOtherScore = score; + } + } } const Candidate* bestSquare = nullptr; @@ -602,13 +671,31 @@ public: 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 { + const int stable0 = static_cast(squareStable[0]); + const int stable1 = static_cast(squareStable[1]); + const int stableMargin = waveBass ? 0 : 2; + if (stable0 > stable1 + stableMargin) + bestSquare = squareCandidates[0]; + else if (stable1 > stable0 + stableMargin) + bestSquare = squareCandidates[1]; + else if (loudDiff > 2) { bestSquare = (squareCandidates[0]->loud > squareCandidates[1]->loud) ? squareCandidates[0] : squareCandidates[1]; + } else { + if (waveBass && stable0 != stable1) + bestSquare = (stable0 >= stable1) ? squareCandidates[0] : squareCandidates[1]; + if (!bestSquare && stable0 <= 1 && stable1 <= 1) { + if (lastChannel == static_cast(Channel::Square1)) + bestSquare = squareCandidates[0]; + else if (lastChannel == static_cast(Channel::Square2)) + bestSquare = squareCandidates[1]; + } + if (!bestSquare) { + const Candidate* preferred = + (squareAlternate & 1U) ? squareCandidates[1] : squareCandidates[0]; + bestSquare = preferred; + squareAlternate ^= 1U; + } } } else if (squareCandidates[0] || squareCandidates[1]) { bestSquare = squareCandidates[0] ? squareCandidates[0] : squareCandidates[1]; @@ -618,13 +705,48 @@ public: 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 && + int bestScore = static_cast(best->loud); + int otherScore = static_cast(bestOther->loud); + if (waveBass) { + if (best->channel == Channel::Wave) + bestScore += 3; + else if (best->channel == Channel::Noise) + bestScore -= 1; + else if (best->channel == Channel::Square1 || best->channel == Channel::Square2) + bestScore -= 2; + if (bestOther->channel == Channel::Wave) + otherScore += 3; + else if (bestOther->channel == Channel::Noise) + otherScore -= 1; + else if (bestOther->channel == Channel::Square1 || bestOther->channel == Channel::Square2) + otherScore -= 2; + } + if (bestScore < 0) + bestScore = 0; + if (otherScore < 0) + otherScore = 0; + if (otherScore > bestScore || (otherScore == bestScore && bestOther->prio > best->prio) || + (otherScore == bestScore && bestOther->prio == best->prio && static_cast(bestOther->channel) == lastChannel && static_cast(best->channel) != lastChannel)) best = bestOther; } + if (waveBass && waveCandidate && best && best->channel != Channel::Wave) { + int waveScore = static_cast(waveCandidate->loud) + 3; + int bestScore = static_cast(best->loud); + if (best->channel == Channel::Noise) + bestScore -= 1; + else if (best->channel == Channel::Square1 || best->channel == Channel::Square2) + bestScore -= 2; + if (waveScore < 0) + waveScore = 0; + if (bestScore < 0) + bestScore = 0; + if (waveScore >= bestScore) + best = waveCandidate; + } + if (!best) return false;