mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 15:17:48 +01:00
sound upgrade
This commit is contained in:
@@ -339,6 +339,8 @@ public:
|
|||||||
regs[kWaveOffset + i] = kInitialWave[i];
|
regs[kWaveOffset + i] = kInitialWave[i];
|
||||||
regs[kPowerIndex] = 0x80;
|
regs[kPowerIndex] = 0x80;
|
||||||
enabled = true;
|
enabled = true;
|
||||||
|
squareAlternate = 0;
|
||||||
|
lastChannel = 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] uint8_t read(uint16_t addr) const {
|
[[nodiscard]] uint8_t read(uint16_t addr) const {
|
||||||
@@ -396,6 +398,13 @@ public:
|
|||||||
static constexpr uint16_t kCh2EnvAddr = 0xFF17;
|
static constexpr uint16_t kCh2EnvAddr = 0xFF17;
|
||||||
static constexpr uint16_t kCh2FreqLoAddr = 0xFF18;
|
static constexpr uint16_t kCh2FreqLoAddr = 0xFF18;
|
||||||
static constexpr uint16_t kCh2TriggerAddr = 0xFF19;
|
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 kVolumeAddr = 0xFF24;
|
||||||
static constexpr uint16_t kRoutingAddr = 0xFF25;
|
static constexpr uint16_t kRoutingAddr = 0xFF25;
|
||||||
static constexpr uint16_t kWaveBase = 0xFF30;
|
static constexpr uint16_t kWaveBase = 0xFF30;
|
||||||
@@ -416,7 +425,9 @@ public:
|
|||||||
|
|
||||||
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 lastChannel = 0xFF;
|
||||||
|
|
||||||
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);
|
||||||
@@ -440,101 +451,149 @@ public:
|
|||||||
// 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 {
|
||||||
// Master volume and routing
|
|
||||||
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)
|
||||||
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;
|
||||||
int prio;
|
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
|
#if GB_BUZZER_ENABLE_CH1
|
||||||
// CH1 square with sweep
|
if ((reg(kPowerAddr) & 0x01U) != 0) {
|
||||||
if (reg(kPowerAddr) & 0x01U) {
|
|
||||||
const uint8_t env = reg(kCh1EnvAddr);
|
const uint8_t env = reg(kCh1EnvAddr);
|
||||||
const uint8_t vol4 = (env >> 4) & 0x0FU;
|
const uint8_t vol4 = (env >> 4) & 0x0FU;
|
||||||
const bool routed = ((routing & 0x11U) != 0);
|
const bool routed = ((routing & 0x11U) != 0);
|
||||||
if (vol4 && routed) {
|
if (vol4 && routed) {
|
||||||
const double f = squareFrequency(0);
|
const double freq = squareFrequency(0);
|
||||||
if (std::isfinite(f) && f > 10.0) {
|
const uint8_t loud = static_cast<uint8_t>(vol4 * master);
|
||||||
uint8_t loud = static_cast<uint8_t>(vol4 * master);
|
pushCandidate(freq, loud, 3, Channel::Square1);
|
||||||
if (loud > best.loud)
|
|
||||||
best = {f, loud, 3};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if GB_BUZZER_ENABLE_CH2
|
#if GB_BUZZER_ENABLE_CH2
|
||||||
// CH2 square
|
if ((reg(kPowerAddr) & 0x02U) != 0) {
|
||||||
if (reg(kPowerAddr) & 0x02U) {
|
|
||||||
const uint8_t env = reg(kCh2EnvAddr);
|
const uint8_t env = reg(kCh2EnvAddr);
|
||||||
const uint8_t vol4 = (env >> 4) & 0x0FU;
|
const uint8_t vol4 = (env >> 4) & 0x0FU;
|
||||||
const bool routed = ((routing & 0x22U) != 0);
|
const bool routed = ((routing & 0x22U) != 0);
|
||||||
if (vol4 && routed) {
|
if (vol4 && routed) {
|
||||||
const double f = squareFrequency(1);
|
const double freq = squareFrequency(1);
|
||||||
if (std::isfinite(f) && f > 10.0) {
|
const uint8_t loud = static_cast<uint8_t>(vol4 * master);
|
||||||
uint8_t loud = static_cast<uint8_t>(vol4 * master);
|
pushCandidate(freq, loud, 3, Channel::Square2);
|
||||||
if (loud >= best.loud)
|
|
||||||
best = {f, loud, 2};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if GB_BUZZER_ENABLE_CH3
|
#if GB_BUZZER_ENABLE_CH3
|
||||||
// CH3 wave (approximate as square at its base frequency scaled by level)
|
if ((reg(kPowerAddr) & 0x04U) != 0 && (reg(kCh3EnableAddr) & 0x80U) != 0) {
|
||||||
if (reg(kPowerAddr) & 0x04U) {
|
const uint8_t levelSel = (reg(kCh3LevelAddr) >> 5) & 0x03U;
|
||||||
const uint8_t nr32 = regs[0xFF1C - kBaseAddr];
|
|
||||||
const uint8_t levelSel = (nr32 >> 5) & 0x03U; // 0=0,1=100%,2=50%,3=25%
|
|
||||||
const bool routed = ((routing & 0x44U) != 0);
|
const bool routed = ((routing & 0x44U) != 0);
|
||||||
if (levelSel != 0 && routed) {
|
if (levelSel != 0 && routed) {
|
||||||
const uint16_t raw =
|
const uint16_t raw =
|
||||||
static_cast<uint16_t>(((reg(kCh2TriggerAddr) & 0x07U) << 8) | reg(kCh2FreqLoAddr));
|
static_cast<uint16_t>(((reg(kCh3TriggerAddr) & 0x07U) << 8) | reg(kCh3FreqLoAddr));
|
||||||
// Use wave constants: 2097152 / (2048 - N)
|
|
||||||
if (raw < 2048U) {
|
if (raw < 2048U) {
|
||||||
const double denom = static_cast<double>(2048U - raw);
|
const double denom = static_cast<double>(2048U - raw);
|
||||||
double f = 2097152.0 / denom;
|
const double freq = 2097152.0 / denom;
|
||||||
if (std::isfinite(f) && f > 10.0) {
|
const uint8_t loudBase = (levelSel == 1 ? 16 : levelSel == 2 ? 8 : 4);
|
||||||
uint8_t loudBase = (levelSel == 1 ? 16 : levelSel == 2 ? 8 : 4);
|
const uint8_t loud = static_cast<uint8_t>(loudBase * master);
|
||||||
uint8_t loud = static_cast<uint8_t>(loudBase * master);
|
pushCandidate(freq, loud, 2, Channel::Wave);
|
||||||
if (loud > best.loud)
|
|
||||||
best = {f, loud, 1};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if GB_BUZZER_ENABLE_CH4
|
#if GB_BUZZER_ENABLE_CH4
|
||||||
// CH4 noise (approximate as pitched noise -> pick a center frequency from regs)
|
if ((reg(kPowerAddr) & 0x08U) != 0) {
|
||||||
if (reg(kPowerAddr) & 0x08U) {
|
|
||||||
const bool routed = ((routing & 0x88U) != 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;
|
const uint8_t vol4 = (env >> 4) & 0x0FU;
|
||||||
if (vol4 && routed) {
|
if (vol4 && routed) {
|
||||||
const uint8_t nr43 = regs[0xFF22 - kBaseAddr];
|
const uint8_t nr43 = reg(kCh4PolyAddr);
|
||||||
const uint8_t s = (nr43 >> 4) & 0x0FU; // shift clock frequency
|
const uint8_t shift = (nr43 >> 4) & 0x0FU;
|
||||||
const uint8_t d = nr43 & 0x07U; // divider code (0->8)
|
const uint8_t dividerId = nr43 & 0x07U;
|
||||||
static const int divLut[8] = {8, 16, 32, 48, 64, 80, 96, 112};
|
static const int divLut[8] = {8, 16, 32, 48, 64, 80, 96, 112};
|
||||||
const int div = divLut[d];
|
const int div = divLut[dividerId];
|
||||||
double f = 4194304.0 / (static_cast<double>(div) * std::pow(2.0, s + 1));
|
double freq =
|
||||||
// clamp to sensible buzzer range; noise sounds better around 1-3k
|
4194304.0 / (static_cast<double>(div) * std::pow(2.0, static_cast<double>(shift + 1)));
|
||||||
f = std::clamp(f, 600.0, 3200.0);
|
freq = std::clamp(freq, 600.0, 3200.0);
|
||||||
uint8_t loud = static_cast<uint8_t>(vol4 * master);
|
const uint8_t loud = static_cast<uint8_t>(vol4 * master);
|
||||||
if (loud > best.loud)
|
pushCandidate(freq, loud, 1, Channel::Noise);
|
||||||
best = {f, loud, 0};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (best.loud == 0 || !std::isfinite(best.freq))
|
|
||||||
|
if (candidateCount == 0)
|
||||||
return false;
|
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<int>(squareCandidates[0]->loud) - static_cast<int>(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<uint8_t>(bestOther->channel) == lastChannel &&
|
||||||
|
static_cast<uint8_t>(best->channel) != lastChannel))
|
||||||
|
best = bestOther;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!best)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const double clamped = std::clamp(best->freq, 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);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user