GB: APU - Implement extra length counter clocking bug when enabling length counter

Passes 03-trigger test
This commit is contained in:
Sour 2020-06-09 18:34:10 -04:00
parent 03f8361437
commit 1be95417b8
11 changed files with 693 additions and 545 deletions

View file

@ -287,8 +287,11 @@
<ClCompile Include="GbDmaController.cpp" />
<ClCompile Include="GbEventManager.cpp" />
<ClCompile Include="GbMemoryManager.cpp" />
<ClCompile Include="GbNoiseChannel.cpp" />
<ClCompile Include="GbPpu.cpp" />
<ClCompile Include="GbSquareChannel.cpp" />
<ClCompile Include="GbTimer.cpp" />
<ClCompile Include="GbWaveChannel.cpp" />
<ClCompile Include="NecDspDebugger.cpp" />
<ClCompile Include="EmuSettings.cpp" />
<ClCompile Include="EventManager.cpp" />

View file

@ -947,6 +947,15 @@
<ClCompile Include="GbAssembler.cpp">
<Filter>Debugger\Assembler</Filter>
</ClCompile>
<ClCompile Include="GbSquareChannel.cpp">
<Filter>GB\APU</Filter>
</ClCompile>
<ClCompile Include="GbNoiseChannel.cpp">
<Filter>GB\APU</Filter>
</ClCompile>
<ClCompile Include="GbWaveChannel.cpp">
<Filter>GB\APU</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="SNES">

View file

@ -256,6 +256,28 @@ void GbApu::Write(uint16_t addr, uint8_t value)
}
}
template<typename T>
void GbApu::ProcessLengthEnableFlag(uint8_t value, T &length, bool &lengthEnabled, bool &enabled)
{
bool newLengthEnabled = (value & 0x40) != 0;
if(newLengthEnabled && !lengthEnabled && (_state.FrameSequenceStep & 0x01) == 1) {
//"Extra length clocking occurs when writing to NRx4 when the frame sequencer's next step is one that doesn't clock
//the length counter. In this case, if the length counter was PREVIOUSLY disabled and now enabled and the length counter
//is not zero, it is decremented. If this decrement makes it zero and trigger is clear, the channel is disabled."
if(length > 0) {
length--;
if(length == 0) {
if(value & 0x80) {
length = sizeof(T) == 1 ? 0x3F : 0xFF;
} else {
enabled = false;
}
}
}
}
lengthEnabled = newLengthEnabled;
}
void GbApu::Serialize(Serializer& s)
{
s.Stream(
@ -271,3 +293,6 @@ void GbApu::Serialize(Serializer& s)
s.Stream(&_wave);
s.Stream(&_noise);
}
template void GbApu::ProcessLengthEnableFlag<uint8_t>(uint8_t value, uint8_t& length, bool& lengthEnabled, bool& enabled);
template void GbApu::ProcessLengthEnableFlag<uint16_t>(uint8_t value, uint16_t& length, bool& lengthEnabled, bool& enabled);

View file

@ -20,10 +20,10 @@ private:
Gameboy* _gameboy = nullptr;
SoundMixer* _soundMixer = nullptr;
GbSquareChannel _square1;
GbSquareChannel _square2;
GbWaveChannel _wave;
GbNoiseChannel _noise;
GbSquareChannel _square1 = GbSquareChannel(this);
GbSquareChannel _square2 = GbSquareChannel(this);
GbWaveChannel _wave = GbWaveChannel(this);
GbNoiseChannel _noise = GbNoiseChannel(this);
int16_t* _soundBuffer = nullptr;
blip_t* _leftChannel = nullptr;
@ -49,5 +49,7 @@ public:
uint8_t Read(uint16_t addr);
void Write(uint16_t addr, uint8_t value);
template<typename T> void ProcessLengthEnableFlag(uint8_t value, T& length, bool& lengthEnabled, bool& enabled);
void Serialize(Serializer& s) override;
};
};

192
Core/GbNoiseChannel.cpp Normal file
View file

@ -0,0 +1,192 @@
#include "stdafx.h"
#include "GbNoiseChannel.h"
#include "GbApu.h"
GbNoiseChannel::GbNoiseChannel(GbApu* apu)
{
_apu = apu;
}
GbNoiseState GbNoiseChannel::GetState()
{
return _state;
}
bool GbNoiseChannel::Enabled()
{
return _state.Enabled;
}
void GbNoiseChannel::Disable()
{
uint8_t len = _state.Length;
_state = {};
_state.Length = len;
}
void GbNoiseChannel::ClockLengthCounter()
{
if(_state.LengthEnabled && _state.Length > 0) {
_state.Length--;
if(_state.Length == 0) {
//"Length becoming 0 should clear status"
_state.Enabled = false;
}
}
}
void GbNoiseChannel::ClockEnvelope()
{
if(_state.EnvTimer > 0) {
_state.EnvTimer--;
if(_state.EnvTimer == 0) {
if(_state.EnvRaiseVolume && _state.Volume < 0x0F) {
_state.Volume++;
} else if(!_state.EnvRaiseVolume && _state.Volume > 0) {
_state.Volume--;
}
_state.EnvTimer = _state.EnvPeriod;
}
}
}
uint8_t GbNoiseChannel::GetOutput()
{
return _state.Output;
}
uint32_t GbNoiseChannel::GetPeriod()
{
if(_state.Divisor == 0) {
return 8 << _state.PeriodShift;
} else {
return (16 * _state.Divisor) << _state.PeriodShift;
}
}
void GbNoiseChannel::Exec(uint32_t clocksToRun)
{
if(_state.PeriodShift >= 14) {
//Using a noise channel clock shift of 14 or 15 results in the LFSR receiving no clocks.
return;
}
_state.Timer -= clocksToRun;
if(_state.Enabled) {
_state.Output = ((_state.ShiftRegister & 0x01) ^ 0x01) * _state.Volume;
} else {
_state.Output = 0;
}
if(_state.Timer == 0) {
_state.Timer = GetPeriod();
//When clocked by the frequency timer, the low two bits (0 and 1) are XORed, all bits are shifted right by one,
//and the result of the XOR is put into the now-empty high bit.
uint16_t shiftedValue = _state.ShiftRegister >> 1;
uint8_t xorResult = (_state.ShiftRegister & 0x01) ^ (shiftedValue & 0x01);
_state.ShiftRegister = (xorResult << 14) | shiftedValue;
if(_state.ShortWidthMode) {
//If width mode is 1 (NR43), the XOR result is ALSO put into bit 6 AFTER the shift, resulting in a 7-bit LFSR.
_state.ShiftRegister &= ~0x40;
_state.ShiftRegister |= (xorResult << 6);
}
}
}
uint8_t GbNoiseChannel::Read(uint16_t addr)
{
constexpr uint8_t openBusBits[5] = { 0xFF, 0xFF, 0x00, 0x00, 0xBF };
uint8_t value = 0;
switch(addr) {
case 2:
value = (
(_state.EnvVolume << 4) |
(_state.EnvRaiseVolume ? 0x08 : 0) |
_state.EnvPeriod
);
break;
case 3:
value = (
(_state.PeriodShift << 4) |
(_state.ShortWidthMode ? 0x08 : 0) |
_state.Divisor
);
break;
case 4: value = _state.LengthEnabled ? 0x40 : 0; break;
}
return value | openBusBits[addr];
}
void GbNoiseChannel::Write(uint16_t addr, uint8_t value)
{
switch(addr) {
case 0: break;
case 1:
_state.Length = 64 - (value & 0x3F);
break;
case 2:
_state.EnvPeriod = value & 0x07;
_state.EnvRaiseVolume = (value & 0x08) != 0;
_state.EnvVolume = (value & 0xF0) >> 4;
if(!(value & 0xF8)) {
_state.Enabled = false;
}
break;
case 3:
_state.PeriodShift = (value & 0xF0) >> 4;
_state.ShortWidthMode = (value & 0x08) != 0;
_state.Divisor = value & 0x07;
break;
case 4: {
if(value & 0x80) {
//Writing a value to NRx4 with bit 7 set causes the following things to occur :
//Channel is enabled, if volume is not 0 or raise volume flag is set
_state.Enabled = _state.EnvRaiseVolume || _state.EnvVolume > 0;
//Frequency timer is reloaded with period.
_state.Timer = GetPeriod();
//Noise channel's LFSR bits are all set to 1.
_state.ShiftRegister = 0x7FFF;
//If length counter is zero, it is set to 64 (256 for wave channel).
if(_state.Length == 0) {
_state.Length = 64;
_state.LengthEnabled = false;
}
//Volume envelope timer is reloaded with period.
_state.EnvTimer = _state.EnvPeriod;
//Channel volume is reloaded from NRx2.
_state.Volume = _state.EnvVolume;
}
_apu->ProcessLengthEnableFlag(value, _state.Length, _state.LengthEnabled, _state.Enabled);
break;
}
}
}
void GbNoiseChannel::Serialize(Serializer& s)
{
s.Stream(
_state.Volume, _state.EnvVolume, _state.EnvRaiseVolume, _state.EnvPeriod, _state.EnvTimer,
_state.ShiftRegister, _state.PeriodShift, _state.Divisor, _state.ShortWidthMode,
_state.Length, _state.LengthEnabled, _state.Enabled, _state.Timer, _state.Output
);
}

View file

@ -4,191 +4,31 @@
#include "../Utilities/ISerializable.h"
#include "../Utilities/Serializer.h"
class GbApu;
class GbNoiseChannel : public ISerializable
{
private:
GbNoiseState _state = {};
GbApu* _apu = nullptr;
public:
GbNoiseState GetState()
{
return _state;
}
GbNoiseChannel(GbApu* apu);
GbNoiseState GetState();
bool Enabled()
{
return _state.Enabled;
}
bool Enabled();
void Disable();
void Disable()
{
uint8_t len = _state.Length;
_state = {};
_state.Length = len;
}
void ClockLengthCounter();
void ClockEnvelope();
void ClockLengthCounter()
{
if(_state.LengthEnabled && _state.Length > 0) {
_state.Length--;
if(_state.Length == 0) {
//"Length becoming 0 should clear status"
_state.Enabled = false;
}
}
}
uint8_t GetOutput();
uint32_t GetPeriod();
void ClockEnvelope()
{
if(_state.EnvTimer > 0) {
_state.EnvTimer--;
void Exec(uint32_t clocksToRun);
if(_state.EnvTimer == 0) {
if(_state.EnvRaiseVolume && _state.Volume < 0x0F) {
_state.Volume++;
} else if(!_state.EnvRaiseVolume && _state.Volume > 0) {
_state.Volume--;
}
uint8_t Read(uint16_t addr);
void Write(uint16_t addr, uint8_t value);
_state.EnvTimer = _state.EnvPeriod;
}
}
}
uint8_t GetOutput()
{
return _state.Output;
}
uint32_t GetPeriod()
{
if(_state.Divisor == 0) {
return 8 << _state.PeriodShift;
} else {
return (16 * _state.Divisor) << _state.PeriodShift;
}
}
void Exec(uint32_t clocksToRun)
{
if(_state.PeriodShift >= 14) {
//Using a noise channel clock shift of 14 or 15 results in the LFSR receiving no clocks.
return;
}
_state.Timer -= clocksToRun;
if(_state.Enabled) {
_state.Output = ((_state.ShiftRegister & 0x01) ^ 0x01) * _state.Volume;
} else {
_state.Output = 0;
}
if(_state.Timer == 0) {
_state.Timer = GetPeriod();
//When clocked by the frequency timer, the low two bits (0 and 1) are XORed, all bits are shifted right by one,
//and the result of the XOR is put into the now-empty high bit.
uint16_t shiftedValue = _state.ShiftRegister >> 1;
uint8_t xorResult = (_state.ShiftRegister & 0x01) ^ (shiftedValue & 0x01);
_state.ShiftRegister = (xorResult << 14) | shiftedValue;
if(_state.ShortWidthMode) {
//If width mode is 1 (NR43), the XOR result is ALSO put into bit 6 AFTER the shift, resulting in a 7-bit LFSR.
_state.ShiftRegister &= ~0x40;
_state.ShiftRegister |= (xorResult << 6);
}
}
}
uint8_t Read(uint16_t addr)
{
constexpr uint8_t openBusBits[5] = { 0xFF, 0xFF, 0x00, 0x00, 0xBF };
uint8_t value = 0;
switch(addr) {
case 2:
value = (
(_state.EnvVolume << 4) |
(_state.EnvRaiseVolume ? 0x08 : 0) |
_state.EnvPeriod
);
break;
case 3:
value = (
(_state.PeriodShift << 4) |
(_state.ShortWidthMode ? 0x08 : 0) |
_state.Divisor
);
break;
case 4: value = _state.LengthEnabled ? 0x40 : 0; break;
}
return value | openBusBits[addr];
}
void Write(uint16_t addr, uint8_t value)
{
switch(addr) {
case 0: break;
case 1:
_state.Length = 64 - (value & 0x3F);
break;
case 2:
_state.EnvPeriod = value & 0x07;
_state.EnvRaiseVolume = (value & 0x08) != 0;
_state.EnvVolume = (value & 0xF0) >> 4;
if(!(value & 0xF8)) {
_state.Enabled = false;
}
break;
case 3:
_state.PeriodShift = (value & 0xF0) >> 4;
_state.ShortWidthMode = (value & 0x08) != 0;
_state.Divisor = value & 0x07;
break;
case 4:
_state.LengthEnabled = (value & 0x40) != 0;
if(value & 0x80) {
//Writing a value to NRx4 with bit 7 set causes the following things to occur :
//Channel is enabled, if volume is not 0 or raise volume flag is set
_state.Enabled = _state.EnvRaiseVolume || _state.EnvVolume > 0;
//Frequency timer is reloaded with period.
_state.Timer = GetPeriod();
//Noise channel's LFSR bits are all set to 1.
_state.ShiftRegister = 0x7FFF;
//If length counter is zero, it is set to 64 (256 for wave channel).
if(_state.Length == 0) {
_state.Length = 64;
}
//Volume envelope timer is reloaded with period.
_state.EnvTimer = _state.EnvPeriod;
//Channel volume is reloaded from NRx2.
_state.Volume = _state.EnvVolume;
}
break;
}
}
void Serialize(Serializer& s) override
{
s.Stream(
_state.Volume, _state.EnvVolume, _state.EnvRaiseVolume, _state.EnvPeriod, _state.EnvTimer,
_state.ShiftRegister, _state.PeriodShift, _state.Divisor, _state.ShortWidthMode,
_state.Length, _state.LengthEnabled, _state.Enabled, _state.Timer, _state.Output
);
}
void Serialize(Serializer& s) override;
};

255
Core/GbSquareChannel.cpp Normal file
View file

@ -0,0 +1,255 @@
#include "stdafx.h"
#include "GbSquareChannel.h"
#include "GbApu.h"
GbSquareChannel::GbSquareChannel(GbApu* apu)
{
_apu = apu;
}
GbSquareState GbSquareChannel::GetState()
{
return _state;
}
bool GbSquareChannel::Enabled()
{
return _state.Enabled;
}
void GbSquareChannel::Disable()
{
uint8_t len = _state.Length;
_state = {};
_state.Length = len;
}
void GbSquareChannel::ClockSweepUnit()
{
if(!_state.SweepEnabled) {
return;
}
if(_state.SweepTimer > 0 && _state.SweepPeriod > 0) {
_state.SweepTimer--;
if(_state.SweepTimer == 0) {
_state.SweepTimer = _state.SweepPeriod;
//"When it generates a clock and the sweep's internal enabled flag is set and the sweep period is not zero, a new frequency is calculated and the overflow"
uint16_t newFreq = GetSweepTargetFrequency();
if(_state.SweepShift > 0 && newFreq < 2048) {
//"If the new frequency is 2047 or less and the sweep shift is not zero, this new frequency is written back to the shadow frequency and square 1's frequency in NR13 and NR14,"
_state.Frequency = _state.SweepFreq;
_state.SweepFreq = newFreq;
newFreq = GetSweepTargetFrequency();
if(newFreq >= 2048) {
//"then frequency calculation and overflow check are run AGAIN immediately using this new value, but this second new frequency is not written back."
_state.SweepEnabled = false;
_state.Enabled = false;
}
} else {
_state.SweepEnabled = false;
_state.Enabled = false;
}
}
}
}
uint16_t GbSquareChannel::GetSweepTargetFrequency()
{
uint16_t shiftResult = (_state.SweepFreq >> _state.SweepShift);
if(_state.SweepNegate) {
return _state.SweepFreq - shiftResult;
} else {
return _state.SweepFreq + shiftResult;
}
}
void GbSquareChannel::ClockLengthCounter()
{
if(_state.LengthEnabled && _state.Length > 0) {
_state.Length--;
if(_state.Length == 0) {
//"Length becoming 0 should clear status"
_state.Enabled = false;
}
}
}
void GbSquareChannel::ClockEnvelope()
{
if(_state.EnvTimer > 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;
}
_state.EnvTimer = _state.EnvPeriod;
}
}
}
uint8_t GbSquareChannel::GetOutput()
{
return _state.Output;
}
void GbSquareChannel::Exec(uint32_t clocksToRun)
{
_state.Timer -= clocksToRun;
if(_state.Enabled) {
_state.Output = _dutySequences[_state.Duty][_state.DutyPos] * _state.Volume;
} else {
_state.Output = 0;
}
if(_state.Timer == 0) {
_state.Timer = (2048 - _state.Frequency) * 4;
_state.DutyPos = (_state.DutyPos + 1) & 0x07;
}
}
uint8_t GbSquareChannel::Read(uint16_t addr)
{
constexpr uint8_t openBusBits[5] = { 0x80, 0x3F, 0x00, 0xFF, 0xBF };
uint8_t value = 0;
switch(addr) {
case 0:
value = (
(_state.SweepPeriod << 4) |
(_state.SweepNegate ? 0x08 : 0) |
_state.SweepShift
);
break;
case 1: value = _state.Duty << 6; break;
case 2:
value = (
(_state.EnvVolume << 4) |
(_state.EnvRaiseVolume ? 0x08 : 0) |
_state.EnvPeriod
);
break;
case 4: value = _state.LengthEnabled ? 0x40 : 0; break;
}
return value | openBusBits[addr];
}
void GbSquareChannel::Write(uint16_t addr, uint8_t value)
{
switch(addr) {
case 0:
_state.SweepShift = value & 0x07;
_state.SweepNegate = (value & 0x08) != 0;
_state.SweepPeriod = (value & 0x70) >> 4;
break;
case 1:
_state.Length = 64 - (value & 0x3F);
_state.Duty = (value & 0xC0) >> 6;
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 = raiseVolume;
_state.EnvVolume = (value & 0xF0) >> 4;
if(!(value & 0xF8)) {
_state.Enabled = false;
}
break;
}
case 3:
_state.Frequency = (_state.Frequency & 0x700) | value;
break;
case 4: {
_state.Frequency = (_state.Frequency & 0xFF) | ((value & 0x07) << 8);
if(value & 0x80) {
//"Writing a value to NRx4 with bit 7 set causes the following things to occur :"
//"Channel is enabled, if volume is not 0 or raise volume flag is set"
_state.Enabled = _state.EnvRaiseVolume || _state.EnvVolume > 0;
//Frequency timer is reloaded with period.
_state.Timer = (2048 - _state.Frequency) * 4;
//"If length counter is zero, it is set to 64 (256 for wave channel)."
if(_state.Length == 0) {
_state.Length = 64;
_state.LengthEnabled = false;
}
//"Volume envelope timer is reloaded with period."
_state.EnvTimer = _state.EnvPeriod;
_state.EnvStopped = false;
//"Channel volume is reloaded from NRx2."
_state.Volume = _state.EnvVolume;
//Sweep-related
//"During a trigger event, several things occur:
//Square 1's frequency is copied to the shadow register.
//The sweep timer is reloaded.
//The internal enabled flag is set if either the sweep period or shift are non-zero, cleared otherwise.
//If the sweep shift is non-zero, frequency calculation and the overflow check are performed immediately."
_state.SweepFreq = _state.Frequency;
_state.SweepTimer = _state.SweepPeriod;
_state.SweepEnabled = _state.SweepPeriod > 0 || _state.SweepShift > 0;
if(_state.SweepShift > 0) {
_state.SweepFreq = GetSweepTargetFrequency();
if(_state.SweepFreq > 2047) {
_state.SweepEnabled = false;
_state.Enabled = false;
}
}
}
_apu->ProcessLengthEnableFlag(value, _state.Length, _state.LengthEnabled, _state.Enabled);
break;
}
}
}
void GbSquareChannel::Serialize(Serializer& s)
{
s.Stream(
_state.SweepPeriod, _state.SweepNegate, _state.SweepShift, _state.SweepTimer, _state.SweepEnabled, _state.SweepFreq,
_state.Volume, _state.EnvVolume, _state.EnvRaiseVolume, _state.EnvPeriod, _state.EnvTimer, _state.Duty, _state.Frequency,
_state.Length, _state.LengthEnabled, _state.Enabled, _state.Timer, _state.DutyPos, _state.Output
);
}

View file

@ -4,6 +4,8 @@
#include "../Utilities/ISerializable.h"
#include "../Utilities/Serializer.h"
class GbApu;
class GbSquareChannel : public ISerializable
{
private:
@ -15,248 +17,32 @@ private:
};
GbSquareState _state = {};
GbApu* _apu = nullptr;
public:
GbSquareState GetState()
{
return _state;
}
GbSquareChannel(GbApu* apu);
bool Enabled()
{
return _state.Enabled;
}
GbSquareState GetState();
void Disable()
{
uint8_t len = _state.Length;
_state = {};
_state.Length = len;
}
bool Enabled();
void ClockSweepUnit()
{
if(!_state.SweepEnabled) {
return;
}
void Disable();
if(_state.SweepTimer > 0 && _state.SweepPeriod > 0) {
_state.SweepTimer--;
if(_state.SweepTimer == 0) {
_state.SweepTimer = _state.SweepPeriod;
void ClockSweepUnit();
//When it generates a clock and the sweep's internal enabled flag is set and the sweep period is not zero, a new frequency is calculated and the overflow
uint16_t newFreq = GetSweepTargetFrequency();
uint16_t GetSweepTargetFrequency();
if(_state.SweepShift > 0 && newFreq < 2048) {
//If the new frequency is 2047 or less and the sweep shift is not zero, this new frequency is written back to the shadow frequency and square 1's frequency in NR13 and NR14,
_state.Frequency = _state.SweepFreq;
_state.SweepFreq = newFreq;
newFreq = GetSweepTargetFrequency();
if(newFreq >= 2048) {
//then frequency calculation and overflow check are run AGAIN immediately using this new value, but this second new frequency is not written back.
_state.SweepEnabled = false;
_state.Enabled = false;
}
} else {
_state.SweepEnabled = false;
_state.Enabled = false;
}
}
}
}
void ClockLengthCounter();
uint16_t GetSweepTargetFrequency()
{
uint16_t shiftResult = (_state.SweepFreq >> _state.SweepShift);
if(_state.SweepNegate) {
return _state.SweepFreq - shiftResult;
} else {
return _state.SweepFreq + shiftResult;
}
}
void ClockEnvelope();
void ClockLengthCounter()
{
if(_state.LengthEnabled && _state.Length > 0) {
_state.Length--;
if(_state.Length == 0) {
//"Length becoming 0 should clear status"
_state.Enabled = false;
}
}
}
uint8_t GetOutput();
void ClockEnvelope()
{
if(_state.EnvTimer > 0 && !_state.EnvStopped) {
_state.EnvTimer--;
void Exec(uint32_t clocksToRun);
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;
}
uint8_t Read(uint16_t addr);
_state.EnvTimer = _state.EnvPeriod;
}
}
}
void Write(uint16_t addr, uint8_t value);
uint8_t GetOutput()
{
return _state.Output;
}
void Exec(uint32_t clocksToRun)
{
_state.Timer -= clocksToRun;
if(_state.Enabled) {
_state.Output = _dutySequences[_state.Duty][_state.DutyPos] * _state.Volume;
} else {
_state.Output = 0;
}
if(_state.Timer == 0) {
_state.Timer = (2048 - _state.Frequency) * 4;
_state.DutyPos = (_state.DutyPos + 1) & 0x07;
}
}
uint8_t Read(uint16_t addr)
{
constexpr uint8_t openBusBits[5] = { 0x80, 0x3F, 0x00, 0xFF, 0xBF };
uint8_t value = 0;
switch(addr) {
case 0:
value = (
(_state.SweepPeriod << 4) |
(_state.SweepNegate ? 0x08 : 0) |
_state.SweepShift
);
break;
case 1: value = _state.Duty << 6; break;
case 2:
value = (
(_state.EnvVolume << 4) |
(_state.EnvRaiseVolume ? 0x08 : 0) |
_state.EnvPeriod
);
break;
case 4: value = _state.LengthEnabled ? 0x40 : 0; break;
}
return value | openBusBits[addr];
}
void Write(uint16_t addr, uint8_t value)
{
switch(addr) {
case 0:
_state.SweepShift = value & 0x07;
_state.SweepNegate = (value & 0x08) != 0;
_state.SweepPeriod = (value & 0x70) >> 4;
break;
case 1:
_state.Length = 64 - (value & 0x3F);
_state.Duty = (value & 0xC0) >> 6;
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 = raiseVolume;
_state.EnvVolume = (value & 0xF0) >> 4;
if(!(value & 0xF8)) {
_state.Enabled = false;
}
break;
}
case 3:
_state.Frequency = (_state.Frequency & 0x700) | value;
break;
case 4:
_state.Frequency = (_state.Frequency & 0xFF) | ((value & 0x07) << 8);
_state.LengthEnabled = (value & 0x40) != 0;
if(value & 0x80) {
//Writing a value to NRx4 with bit 7 set causes the following things to occur :
//Channel is enabled, if volume is not 0 or raise volume flag is set
_state.Enabled = _state.EnvRaiseVolume || _state.EnvVolume > 0;
//Frequency timer is reloaded with period.
_state.Timer = (2048 - _state.Frequency) * 4;
//If length counter is zero, it is set to 64 (256 for wave channel).
if(_state.Length == 0) {
_state.Length = 64;
}
//Volume envelope timer is reloaded with period.
_state.EnvTimer = _state.EnvPeriod;
_state.EnvStopped = false;
//Channel volume is reloaded from NRx2.
_state.Volume = _state.EnvVolume;
//Sweep-related
//During a trigger event, several things occur:
//Square 1's frequency is copied to the shadow register.
//The sweep timer is reloaded.
//The internal enabled flag is set if either the sweep period or shift are non-zero, cleared otherwise.
//If the sweep shift is non-zero, frequency calculation and the overflow check are performed immediately.
_state.SweepFreq = _state.Frequency;
_state.SweepTimer = _state.SweepPeriod;
_state.SweepEnabled = _state.SweepPeriod > 0 || _state.SweepShift > 0;
if(_state.SweepShift > 0) {
_state.SweepFreq = GetSweepTargetFrequency();
if(_state.SweepFreq > 2047) {
_state.SweepEnabled = false;
_state.Enabled = false;
}
}
}
break;
}
}
void Serialize(Serializer& s) override
{
s.Stream(
_state.SweepPeriod, _state.SweepNegate, _state.SweepShift, _state.SweepTimer, _state.SweepEnabled, _state.SweepFreq,
_state.Volume, _state.EnvVolume, _state.EnvRaiseVolume, _state.EnvPeriod, _state.EnvTimer, _state.Duty, _state.Frequency,
_state.Length, _state.LengthEnabled, _state.Enabled, _state.Timer, _state.DutyPos, _state.Output
);
}
void Serialize(Serializer& s) override;
};

153
Core/GbWaveChannel.cpp Normal file
View file

@ -0,0 +1,153 @@
#include "stdafx.h"
#include "GbWaveChannel.h"
#include "GbApu.h"
GbWaveChannel::GbWaveChannel(GbApu* apu)
{
_apu = apu;
}
GbWaveState GbWaveChannel::GetState()
{
return _state;
}
bool GbWaveChannel::Enabled()
{
return _state.Enabled;
}
void GbWaveChannel::Disable()
{
uint16_t len = _state.Length;
uint8_t ram[0x10];
memcpy(ram, _state.Ram, sizeof(ram));
_state = {};
_state.Length = len;
memcpy(_state.Ram, ram, sizeof(ram));
}
void GbWaveChannel::ClockLengthCounter()
{
if(_state.LengthEnabled && _state.Length > 0) {
_state.Length--;
if(_state.Length == 0) {
//"Length becoming 0 should clear status"
_state.Enabled = false;
}
}
}
uint8_t GbWaveChannel::GetOutput()
{
return _state.Output;
}
void GbWaveChannel::Exec(uint32_t clocksToRun)
{
_state.Timer -= clocksToRun;
//The DAC receives the current value from the upper/lower nibble of the sample buffer, shifted right by the volume control.
if(_state.Volume && _state.Enabled) {
_state.Output = _state.SampleBuffer >> (_state.Volume - 1);
} else {
_state.Output = 0;
}
if(_state.Timer == 0) {
//The wave channel's frequency timer period is set to (2048-frequency)*2.
_state.Timer = (2048 - _state.Frequency) * 2;
//When the timer generates a clock, the position counter is advanced one sample in the wave table,
//looping back to the beginning when it goes past the end,
_state.Position = (_state.Position + 1) & 0x1F;
//then a sample is read into the sample buffer from this NEW position.
if(_state.Position & 0x01) {
_state.SampleBuffer = _state.Ram[_state.Position >> 1] & 0x0F;
} else {
_state.SampleBuffer = _state.Ram[_state.Position >> 1] >> 4;
}
}
}
uint8_t GbWaveChannel::Read(uint16_t addr)
{
constexpr uint8_t openBusBits[5] = { 0x7F, 0xFF, 0x9F, 0xFF, 0xBF };
uint8_t value = 0;
switch(addr) {
case 0: value = _state.DacEnabled ? 0x80 : 0; break;
case 2: value = _state.Volume << 5; break;
case 4: value = _state.LengthEnabled ? 0x40 : 0; break;
}
return value | openBusBits[addr];
}
void GbWaveChannel::Write(uint16_t addr, uint8_t value)
{
switch(addr) {
case 0:
_state.DacEnabled = (value & 0x80) != 0;
_state.Enabled &= _state.DacEnabled;
break;
case 1:
_state.Length = 256 - value;
break;
case 2:
_state.Volume = (value & 0x60) >> 5;
break;
case 3:
_state.Frequency = (_state.Frequency & 0x700) | value;
break;
case 4: {
_state.Frequency = (_state.Frequency & 0xFF) | ((value & 0x07) << 8);
if(value & 0x80) {
//Start playback
//Channel is enabled, if DAC is enabled
_state.Enabled = _state.DacEnabled;
//Frequency timer is reloaded with period.
_state.Timer = (2048 - _state.Frequency) * 2;
//If length counter is zero, it is set to 64 (256 for wave channel).
if(_state.Length == 0) {
_state.Length = 256;
_state.LengthEnabled = false;
}
//Wave channel's position is set to 0 but sample buffer is NOT refilled.
_state.Position = 0;
}
_apu->ProcessLengthEnableFlag(value, _state.Length, _state.LengthEnabled, _state.Enabled);
break;
}
}
}
void GbWaveChannel::WriteRam(uint16_t addr, uint8_t value)
{
_state.Ram[addr & 0x0F] = value;
}
uint8_t GbWaveChannel::ReadRam(uint16_t addr)
{
return _state.Ram[addr & 0x0F];
}
void GbWaveChannel::Serialize(Serializer& s)
{
s.Stream(
_state.DacEnabled, _state.SampleBuffer, _state.Position, _state.Volume, _state.Frequency,
_state.Length, _state.LengthEnabled, _state.Enabled, _state.Timer, _state.Output
);
s.StreamArray(_state.Ram, 0x10);
}

View file

@ -4,151 +4,31 @@
#include "../Utilities/ISerializable.h"
#include "../Utilities/Serializer.h"
class GbApu;
class GbWaveChannel : public ISerializable
{
private:
GbWaveState _state = {};
GbApu* _apu = nullptr;
public:
GbWaveState GetState()
{
return _state;
}
GbWaveChannel(GbApu* apu);
bool Enabled()
{
return _state.Enabled;
}
GbWaveState GetState();
bool Enabled();
void Disable();
uint8_t GetOutput();
void Disable()
{
uint16_t len = _state.Length;
uint8_t ram[0x10];
memcpy(ram, _state.Ram, sizeof(ram));
_state = {};
_state.Length = len;
memcpy(_state.Ram, ram, sizeof(ram));
}
void ClockLengthCounter();
void ClockLengthCounter()
{
if(_state.LengthEnabled && _state.Length > 0) {
_state.Length--;
if(_state.Length == 0) {
//"Length becoming 0 should clear status"
_state.Enabled = false;
}
}
}
void Exec(uint32_t clocksToRun);
uint8_t GetOutput()
{
return _state.Output;
}
uint8_t Read(uint16_t addr);
void Write(uint16_t addr, uint8_t value);
void Exec(uint32_t clocksToRun)
{
_state.Timer -= clocksToRun;
void WriteRam(uint16_t addr, uint8_t value);
uint8_t ReadRam(uint16_t addr);
//The DAC receives the current value from the upper/lower nibble of the sample buffer, shifted right by the volume control.
if(_state.Volume && _state.Enabled) {
_state.Output = _state.SampleBuffer >> (_state.Volume - 1);
} else {
_state.Output = 0;
}
if(_state.Timer == 0) {
//The wave channel's frequency timer period is set to (2048-frequency)*2.
_state.Timer = (2048 - _state.Frequency) * 2;
//When the timer generates a clock, the position counter is advanced one sample in the wave table,
//looping back to the beginning when it goes past the end,
_state.Position = (_state.Position + 1) & 0x1F;
//then a sample is read into the sample buffer from this NEW position.
if(_state.Position & 0x01) {
_state.SampleBuffer = _state.Ram[_state.Position >> 1] & 0x0F;
} else {
_state.SampleBuffer = _state.Ram[_state.Position >> 1] >> 4;
}
}
}
uint8_t Read(uint16_t addr)
{
constexpr uint8_t openBusBits[5] = { 0x7F, 0xFF, 0x9F, 0xFF, 0xBF };
uint8_t value = 0;
switch(addr) {
case 0: value = _state.DacEnabled ? 0x80 : 0; break;
case 2: value = _state.Volume << 5; break;
case 4: value = _state.LengthEnabled ? 0x40 : 0; break;
}
return value | openBusBits[addr];
}
void Write(uint16_t addr, uint8_t value)
{
switch(addr) {
case 0:
_state.DacEnabled = (value & 0x80) != 0;
_state.Enabled &= _state.DacEnabled;
break;
case 1:
_state.Length = 256 - value;
break;
case 2:
_state.Volume = (value & 0x60) >> 5;
break;
case 3:
_state.Frequency = (_state.Frequency & 0x700) | value;
break;
case 4:
_state.Frequency = (_state.Frequency & 0xFF) | ((value & 0x07) << 8);
_state.LengthEnabled = (value & 0x40) != 0;
if(value & 0x80) {
//Start playback
//Channel is enabled, if DAC is enabled
_state.Enabled = _state.DacEnabled;
//Frequency timer is reloaded with period.
_state.Timer = (2048 - _state.Frequency) * 2;
//If length counter is zero, it is set to 64 (256 for wave channel).
if(_state.Length == 0) {
_state.Length = 256;
}
//Wave channel's position is set to 0 but sample buffer is NOT refilled.
_state.Position = 0;
}
break;
}
}
void WriteRam(uint16_t addr, uint8_t value)
{
_state.Ram[addr & 0x0F] = value;
}
uint8_t ReadRam(uint16_t addr)
{
return _state.Ram[addr & 0x0F];
}
void Serialize(Serializer& s) override
{
s.Stream(
_state.DacEnabled, _state.SampleBuffer, _state.Position, _state.Volume, _state.Frequency,
_state.Length, _state.LengthEnabled, _state.Enabled, _state.Timer, _state.Output
);
s.StreamArray(_state.Ram, 0x10);
}
void Serialize(Serializer& s) override;
};

View file

@ -68,6 +68,9 @@ SOURCES_CXX := $(LIBRETRO_DIR)/libretro.cpp \
$(CORE_DIR)/GbDmaController.cpp \
$(CORE_DIR)/GbPpu.cpp \
$(CORE_DIR)/GbApu.cpp \
$(CORE_DIR)/GbNoiseChannel.cpp \
$(CORE_DIR)/GbSquareChannel.cpp \
$(CORE_DIR)/GbWaveChannel.cpp \
$(CORE_DIR)/GbTimer.cpp \
$(CORE_DIR)/GbMemoryManager.cpp \
$(CORE_DIR)/GbDebugger.cpp \