even better sound

This commit is contained in:
2025-10-13 00:27:45 +02:00
parent 07186b4b73
commit df7c4ff3b9

View File

@@ -341,6 +341,7 @@ public:
enabled = true; enabled = true;
squareAlternate = 0; squareAlternate = 0;
lastChannel = 0xFF; lastChannel = 0xFF;
filteredFreqHz = 0.0;
} }
[[nodiscard]] uint8_t read(uint16_t addr) const { [[nodiscard]] uint8_t read(uint16_t addr) const {
@@ -366,6 +367,9 @@ public:
for (std::size_t i = 0; i < kWaveRamSize; ++i) for (std::size_t i = 0; i < kWaveRamSize; ++i)
regs[kWaveOffset + i] = wave[i]; regs[kWaveOffset + i] = wave[i];
regs[kPowerIndex] = static_cast<uint8_t>(value & 0x80U); regs[kPowerIndex] = static_cast<uint8_t>(value & 0x80U);
squareAlternate = 0;
lastChannel = 0xFF;
filteredFreqHz = 0.0;
} }
return; return;
} }
@@ -423,11 +427,14 @@ public:
static constexpr uint8_t kInitialWave[kInitialWaveCount] = {0xAC, 0xDD, 0xDA, 0x48, 0x36, 0x02, 0xCF, 0x16, static constexpr uint8_t kInitialWave[kInitialWaveCount] = {0xAC, 0xDD, 0xDA, 0x48, 0x36, 0x02, 0xCF, 0x16,
0x2C, 0x04, 0xE5, 0x2C, 0xAC, 0xDD, 0xDA, 0x48}; 0x2C, 0x04, 0xE5, 0x2C, 0xAC, 0xDD, 0xDA, 0x48};
enum class Channel : uint8_t { Square1 = 0, Square2 = 1, Wave = 2, Noise = 3 };
GameboyApp* owner = nullptr; GameboyApp* owner = nullptr;
std::array<uint8_t, kRegisterCount> regs{}; std::array<uint8_t, kRegisterCount> regs{};
bool enabled = true; bool enabled = true;
mutable uint8_t squareAlternate = 0; mutable uint8_t squareAlternate = 0;
mutable uint8_t lastChannel = 0xFF; mutable uint8_t lastChannel = 0xFF;
mutable double filteredFreqHz = 0.0;
static constexpr bool inRange(uint16_t addr) { static constexpr bool inRange(uint16_t addr) {
return addr >= kBaseAddr && addr <= (kBaseAddr + static_cast<uint16_t>(kRegisterCount) - 1); return addr >= kBaseAddr && addr <= (kBaseAddr + static_cast<uint16_t>(kRegisterCount) - 1);
@@ -447,17 +454,40 @@ public:
return 131072.0 / denom; 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. // Mixer: compute best single-tone approximation for the buzzer.
// Returns true if a tone is suggested, with outFreqHz set. // Returns true if a tone is suggested, with outFreqHz set.
public: public:
bool computeEffectiveTone(uint32_t& outFreqHz, uint8_t& outLoudness) const { bool computeEffectiveTone(uint32_t& outFreqHz, uint8_t& outLoudness) const {
const uint8_t nr50 = reg(kVolumeAddr); const uint8_t nr50 = reg(kVolumeAddr);
const uint8_t master = static_cast<uint8_t>(std::max(nr50 & 0x07U, (nr50 >> 4) & 0x07U)); const uint8_t master = static_cast<uint8_t>(std::max(nr50 & 0x07U, (nr50 >> 4) & 0x07U));
if (master == 0) if (master == 0) {
filteredFreqHz = 0.0;
lastChannel = 0xFF;
return false; return false;
}
const uint8_t routing = reg(kRoutingAddr); const uint8_t routing = reg(kRoutingAddr);
enum class Channel : uint8_t { Square1 = 0, Square2 = 1, Wave = 2, Noise = 3 };
struct Candidate { struct Candidate {
double freq; double freq;
uint8_t loud; uint8_t loud;
@@ -465,9 +495,8 @@ public:
Channel channel; Channel channel;
}; };
Candidate candidates[4]; Candidate candidates[4];
std::size_t candidateCount = 0; std::size_t candidateCount = 0;
constexpr std::size_t kMaxCandidates = sizeof(candidates) / sizeof(candidates[0]); constexpr std::size_t kMaxCandidates = sizeof(candidates) / sizeof(candidates[0]);
auto pushCandidate = [&](double freq, uint8_t loud, int prio, Channel channel) { 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) { if ((reg(kPowerAddr) & 0x04U) != 0 && (reg(kCh3EnableAddr) & 0x80U) != 0) {
const uint8_t levelSel = (reg(kCh3LevelAddr) >> 5) & 0x03U; const uint8_t levelSel = (reg(kCh3LevelAddr) >> 5) & 0x03U;
const bool routed = ((routing & 0x44U) != 0); 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 = const uint16_t raw =
static_cast<uint16_t>(((reg(kCh3TriggerAddr) & 0x07U) << 8) | reg(kCh3FreqLoAddr)); static_cast<uint16_t>(((reg(kCh3TriggerAddr) & 0x07U) << 8) | reg(kCh3FreqLoAddr));
if (raw < 2048U) { if (raw < 2048U) {
const double denom = static_cast<double>(2048U - raw); const double denom = static_cast<double>(2048U - raw);
const double freq = 2097152.0 / denom; const double freq = 2097152.0 / denom;
const uint8_t loudBase = (levelSel == 1 ? 16 : levelSel == 2 ? 8 : 4); const uint8_t loud = static_cast<uint8_t>(loudBase * master);
const uint8_t loud = static_cast<uint8_t>(loudBase * master);
pushCandidate(freq, loud, 2, Channel::Wave); pushCandidate(freq, loud, 2, Channel::Wave);
} }
} }
@@ -532,15 +567,18 @@ public:
const int div = divLut[dividerId]; const int div = divLut[dividerId];
double freq = double freq =
4194304.0 / (static_cast<double>(div) * std::pow(2.0, static_cast<double>(shift + 1))); 4194304.0 / (static_cast<double>(div) * std::pow(2.0, static_cast<double>(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<uint8_t>(vol4 * master); const uint8_t loud = static_cast<uint8_t>(vol4 * master);
pushCandidate(freq, loud, 1, Channel::Noise); pushCandidate(freq, loud, 1, Channel::Noise);
} }
} }
#endif #endif
if (candidateCount == 0) if (candidateCount == 0) {
lastChannel = 0xFF;
filteredFreqHz = 0.0;
return false; return false;
}
const Candidate* squareCandidates[2] = {nullptr, nullptr}; const Candidate* squareCandidates[2] = {nullptr, nullptr};
const Candidate* bestOther = nullptr; const Candidate* bestOther = nullptr;
@@ -590,7 +628,27 @@ public:
if (!best) if (!best)
return false; 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<uint8_t>(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<uint32_t>(clamped + 0.5); outFreqHz = static_cast<uint32_t>(clamped + 0.5);
outLoudness = best->loud; outLoudness = best->loud;
lastChannel = static_cast<uint8_t>(best->channel); lastChannel = static_cast<uint8_t>(best->channel);