Mesen2/Core/Gameboy/APU/GbEnvelope.h
Sour 929d4dcc20 GB: Fixed APU emulation issues
-Super Mario Land 2 - Pops in menu are fixed by immediately updating APU output after a write
-Perfect Dark - Low voice volume is fixed by having the correct output when the channels are disabled (but DAC is still enabled)
-Daiku no Gen - Low voice volume is fixed by keeping square channel output to digital 0 (= analog 1) until its first tick after being enabled (the game does not let the channel tick at all while the voice sample is playing)
2025-03-21 23:03:01 +09:00

88 lines
No EOL
2.5 KiB
C++

#pragma once
#include "pch.h"
class GbEnvelope
{
public:
template<typename T, typename U>
static void ClockEnvelope(T& state, U& channel)
{
uint8_t timer = state.EnvTimer;
if(state.EnvTimer == 0 || --state.EnvTimer == 0) {
if(state.EnvPeriod > 0 && !state.EnvStopped) {
if(state.EnvRaiseVolume && state.Volume < 0x0F) {
state.Volume++;
} else if(!state.EnvRaiseVolume && state.Volume > 0) {
state.Volume--;
} else {
state.EnvStopped = true;
}
//Clocking envelope should update output immediately (based on div_trigger_volume/channel_4_volume_div tests)
channel.UpdateOutput();
state.EnvTimer = state.EnvPeriod;
if(timer == 0) {
//When the timer was already 0 (because period was 0), it looks like the next
//clock occurs earlier than expected.
//This fixes the last test result in channel_1_nrx2_glitch (but may be incorrect)
state.EnvTimer--;
}
}
}
}
template<typename T, typename U>
static void WriteRegister(T& state, uint8_t value, U& channel)
{
bool raiseVolume = (value & 0x08) != 0;
uint8_t period = value & 0x07;
if((value & 0xF8) == 0) {
state.Enabled = false;
state.Output = 0;
} else {
//This implementation of the Zombie mode behavior differs from the description
//found here: https://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware
//Instead, it's based on the behavior of the channel_1_nrx2_glitch test and
//and SameBoy's implementation of the glitch
bool preventIncrement = false;
if(raiseVolume != state.EnvRaiseVolume) {
if(raiseVolume) {
if(!state.EnvStopped && state.EnvPeriod == 0) {
state.Volume ^= 0x0F;
} else {
state.Volume = 14 - state.Volume;
}
preventIncrement = true;
} else {
//"If the mode was changed (add to subtract or subtract to add), volume is set to 16 - volume."
state.Volume = 16 - state.Volume;
}
//"Only the low 4 bits of volume are kept"
state.Volume &= 0xF;
}
if(!state.EnvStopped && !preventIncrement) {
if(state.EnvPeriod == 0 && (period || raiseVolume)) {
if(raiseVolume) {
//"If the old envelope period was zero and the envelope is still doing automatic updates, volume is incremented by 1"
state.Volume++;
} else {
state.Volume--;
}
//"Only the low 4 bits of volume are kept"
state.Volume &= 0xF;
}
}
}
state.EnvPeriod = period;
state.EnvRaiseVolume = raiseVolume;
state.EnvVolume = (value & 0xF0) >> 4;
channel.UpdateOutput();
}
};