Skip to content

Commit

Permalink
GB: Refactor apu envelope code to reduce duplicated code
Browse files Browse the repository at this point in the history
  • Loading branch information
SourMesen committed Feb 1, 2024
1 parent e7ac876 commit cd7e277
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 116 deletions.
1 change: 1 addition & 0 deletions Core/Core.vcxproj
Expand Up @@ -26,6 +26,7 @@
<ClInclude Include="Debugger\FrozenAddressManager.h" />
<ClInclude Include="Debugger\StepBackManager.h" />
<ClInclude Include="Gameboy\APU\GbChannelDac.h" />
<ClInclude Include="Gameboy\APU\GbEnvelope.h" />
<ClInclude Include="Gameboy\Carts\GbHuc1.h" />
<ClInclude Include="Gameboy\Carts\GbMbc3Rtc.h" />
<ClInclude Include="Gameboy\Carts\GbMbc6.h" />
Expand Down
3 changes: 3 additions & 0 deletions Core/Core.vcxproj.filters
Expand Up @@ -2712,6 +2712,9 @@
<ClInclude Include="Gameboy\APU\GbWaveChannel.h">
<Filter>Gameboy\APU</Filter>
</ClInclude>
<ClInclude Include="Gameboy\APU\GbEnvelope.h">
<Filter>Gameboy\APU</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Shared\Video\RotateFilter.cpp">
Expand Down
87 changes: 87 additions & 0 deletions Core/Gameboy/APU/GbEnvelope.h
@@ -0,0 +1,87 @@
#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;
} 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();
}
};
45 changes: 3 additions & 42 deletions Core/Gameboy/APU/GbNoiseChannel.cpp
@@ -1,6 +1,7 @@
#include "pch.h"
#include "Gameboy/APU/GbNoiseChannel.h"
#include "Gameboy/APU/GbApu.h"
#include "Gameboy/APU/GbEnvelope.h"

GbNoiseChannel::GbNoiseChannel(GbApu* apu)
{
Expand Down Expand Up @@ -49,24 +50,7 @@ void GbNoiseChannel::UpdateOutput()

void GbNoiseChannel::ClockEnvelope()
{
if(_state.EnvTimer > 0 && _state.EnvPeriod > 0 && !_state.EnvStopped) {
_state.EnvTimer--;

if(_state.EnvTimer == 0) {
if(_state.EnvRaiseVolume && _state.Volume < 0x0F) {
_state.Volume++;
} else if(!_state.EnvRaiseVolume && _state.Volume > 0) {
_state.Volume--;
} else {
_state.EnvStopped = true;
}

//Based on the channel_4_volume_div test, clocking the envelope updates the output immediately
UpdateOutput();

_state.EnvTimer = _state.EnvPeriod;
}
}
GbEnvelope::ClockEnvelope(_state, *this);
}

uint8_t GbNoiseChannel::GetRawOutput()
Expand Down Expand Up @@ -163,30 +147,7 @@ void GbNoiseChannel::Write(uint16_t addr, uint8_t value)
break;

case 2: {
if(_state.EnvPeriod == 0 && !_state.EnvStopped) {
//"If the old envelope period was zero and the envelope is still doing automatic updates, volume is incremented by 1"
_state.Volume++;
} else if(!_state.EnvRaiseVolume) {
//"otherwise if the envelope was in subtract mode, volume is incremented by 2"
_state.Volume += 2;
}

bool raiseVolume = (value & 0x08) != 0;
if(raiseVolume != _state.EnvRaiseVolume) {
//"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 after the above operations."
_state.Volume &= 0xF;

_state.EnvPeriod = value & 0x07;
_state.EnvRaiseVolume = (value & 0x08) != 0;
_state.EnvVolume = (value & 0xF0) >> 4;

if(!(value & 0xF8)) {
_state.Enabled = false;
}
GbEnvelope::WriteRegister(_state, value, *this);
break;
}

Expand Down
3 changes: 1 addition & 2 deletions Core/Gameboy/APU/GbNoiseChannel.h
Expand Up @@ -13,13 +13,12 @@ class GbNoiseChannel final : public ISerializable
GbNoiseState _state = {};
GbChannelDac _dac = {};
GbApu* _apu = nullptr;

void UpdateOutput();

public:
GbNoiseChannel(GbApu* apu);
GbNoiseState& GetState();

void UpdateOutput();
bool Enabled();
void Disable();
void ResetLengthCounter();
Expand Down
73 changes: 3 additions & 70 deletions Core/Gameboy/APU/GbSquareChannel.cpp
@@ -1,6 +1,7 @@
#include "pch.h"
#include "Gameboy/APU/GbSquareChannel.h"
#include "Gameboy/APU/GbApu.h"
#include "Gameboy/APU/GbEnvelope.h"

GbSquareChannel::GbSquareChannel(GbApu* apu)
{
Expand Down Expand Up @@ -96,29 +97,7 @@ void GbSquareChannel::UpdateOutput()

void GbSquareChannel::ClockEnvelope()
{
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 test)
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--;
}
}
}
GbEnvelope::ClockEnvelope(_state, *this);
}

uint8_t GbSquareChannel::GetRawOutput()
Expand Down Expand Up @@ -225,53 +204,7 @@ void GbSquareChannel::Write(uint16_t addr, uint8_t value)
break;

case 2: {
bool raiseVolume = (value & 0x08) != 0;
uint8_t period = value & 0x07;

if((value & 0xF8) == 0) {
_state.Enabled = false;
} 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;

GbEnvelope::WriteRegister(_state, value, *this);
UpdateOutput();
break;
}
Expand Down
3 changes: 1 addition & 2 deletions Core/Gameboy/APU/GbSquareChannel.h
Expand Up @@ -21,13 +21,12 @@ class GbSquareChannel final : public ISerializable
GbChannelDac _dac = {};
GbApu* _apu = nullptr;

void UpdateOutput();

public:
GbSquareChannel(GbApu* apu);

GbSquareState& GetState();

void UpdateOutput();
bool Enabled();
void Disable();
void ResetLengthCounter();
Expand Down

0 comments on commit cd7e277

Please sign in to comment.