mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
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)
This commit is contained in:
parent
85070a6dc2
commit
929d4dcc20
12 changed files with 124 additions and 33 deletions
|
@ -40,6 +40,7 @@ public:
|
|||
|
||||
if((value & 0xF8) == 0) {
|
||||
state.Enabled = false;
|
||||
state.Output = 0;
|
||||
} else {
|
||||
//No zombie mode for GBA? (or maybe it behaves differently.)
|
||||
//Using the GB implementation of zombie mode causes sound issues in some games
|
||||
|
|
|
@ -47,7 +47,9 @@ void GbaNoiseChannel::ClockLengthCounter()
|
|||
|
||||
void GbaNoiseChannel::UpdateOutput()
|
||||
{
|
||||
_state.Output = ((_state.ShiftRegister & 0x01) ^ 0x01) * _state.Volume;
|
||||
if(_state.Enabled) {
|
||||
_state.Output = ((_state.ShiftRegister & 0x01) ^ 0x01) * _state.Volume;
|
||||
}
|
||||
}
|
||||
|
||||
void GbaNoiseChannel::ClockEnvelope()
|
||||
|
@ -160,6 +162,7 @@ void GbaNoiseChannel::Write(uint16_t addr, uint8_t value)
|
|||
break;
|
||||
|
||||
case 4: {
|
||||
bool prevEnabled = _state.Enabled;
|
||||
if(value & 0x80) {
|
||||
//Writing a value to NRx4 with bit 7 set causes the following things to occur :
|
||||
|
||||
|
@ -191,6 +194,14 @@ void GbaNoiseChannel::Write(uint16_t addr, uint8_t value)
|
|||
_state.Timer += (_state.Divisor == 1) ? 4 : -4;
|
||||
}
|
||||
|
||||
//Channel volume is reloaded from NRx2.
|
||||
_state.Volume = _state.EnvVolume;
|
||||
|
||||
if(_state.Enabled) {
|
||||
//Immediately update output if channel was enabled
|
||||
UpdateOutput();
|
||||
}
|
||||
|
||||
//Channel is enabled, if volume is not 0 or raise volume flag is set
|
||||
_state.Enabled = _state.EnvRaiseVolume || _state.EnvVolume > 0;
|
||||
_apu->UpdateEnabledChannels();
|
||||
|
@ -207,12 +218,14 @@ void GbaNoiseChannel::Write(uint16_t addr, uint8_t value)
|
|||
//Volume envelope timer is reloaded with period.
|
||||
_state.EnvTimer = _state.EnvPeriod;
|
||||
_state.EnvStopped = false;
|
||||
|
||||
//Channel volume is reloaded from NRx2.
|
||||
_state.Volume = _state.EnvVolume;
|
||||
}
|
||||
|
||||
_state.LengthEnabled = (value & 0x40);
|
||||
|
||||
if(!_state.Enabled && prevEnabled) {
|
||||
_state.Output = 0;
|
||||
UpdateOutput();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,6 +95,10 @@ void GbaSquareChannel::ClockLengthCounter()
|
|||
|
||||
void GbaSquareChannel::UpdateOutput()
|
||||
{
|
||||
if(!_state.Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_state.Output = _dutySequences[_state.Duty][(_state.DutyPos - 1) & 0x07] * _state.Volume;
|
||||
}
|
||||
|
||||
|
@ -217,6 +221,7 @@ void GbaSquareChannel::Write(uint16_t addr, uint8_t value)
|
|||
break;
|
||||
|
||||
case 4: {
|
||||
bool prevEnabled = _state.Enabled;
|
||||
_state.Frequency = (_state.Frequency & 0xFF) | ((value & 0x07) << 8);
|
||||
|
||||
if(value & 0x80) {
|
||||
|
@ -278,6 +283,11 @@ void GbaSquareChannel::Write(uint16_t addr, uint8_t value)
|
|||
}
|
||||
|
||||
_state.LengthEnabled = (value & 0x40);
|
||||
|
||||
if(!_state.Enabled && prevEnabled) {
|
||||
_state.Output = 0;
|
||||
UpdateOutput();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,10 @@ double GbaWaveChannel::GetOutput()
|
|||
|
||||
void GbaWaveChannel::UpdateOutput()
|
||||
{
|
||||
if(!_state.Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_state.OverrideVolume) {
|
||||
_state.Output = _state.SampleBuffer * 3 / 4;
|
||||
} else if(_state.Volume) {
|
||||
|
@ -160,6 +164,7 @@ void GbaWaveChannel::Write(uint16_t addr, uint8_t value)
|
|||
break;
|
||||
|
||||
case 4: {
|
||||
bool prevEnabled = _state.Enabled;
|
||||
_state.Frequency = (_state.Frequency & 0xFF) | ((value & 0x07) << 8);
|
||||
|
||||
if(value & 0x80) {
|
||||
|
@ -186,6 +191,11 @@ void GbaWaveChannel::Write(uint16_t addr, uint8_t value)
|
|||
}
|
||||
|
||||
_state.LengthEnabled = (value & 0x40);
|
||||
|
||||
if(!_state.Enabled && prevEnabled) {
|
||||
_state.Output = 0;
|
||||
UpdateOutput();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,30 +105,7 @@ void GbApu::Run()
|
|||
_noise->Exec(minTimer);
|
||||
|
||||
_clockCounter += minTimer;
|
||||
|
||||
int16_t leftOutput = (
|
||||
(_square1->GetOutput() * (int32_t)(cfg.Square1Vol & _state.EnableLeftSq1) / 100) +
|
||||
(_square2->GetOutput() * (int32_t)(cfg.Square2Vol & _state.EnableLeftSq2) / 100) +
|
||||
(_wave->GetOutput() * (int32_t)(cfg.WaveVol & _state.EnableLeftWave) / 100) +
|
||||
(_noise->GetOutput() * (int32_t)(cfg.NoiseVol & _state.EnableLeftNoise) / 100)
|
||||
) * (_state.LeftVolume + 1) * 40;
|
||||
|
||||
if(_prevLeftOutput != leftOutput) {
|
||||
blip_add_delta(_leftChannel, _clockCounter, leftOutput - _prevLeftOutput);
|
||||
_prevLeftOutput = leftOutput;
|
||||
}
|
||||
|
||||
int16_t rightOutput = (
|
||||
(_square1->GetOutput() * (int32_t)(cfg.Square1Vol & _state.EnableRightSq1) / 100) +
|
||||
(_square2->GetOutput() * (int32_t)(cfg.Square2Vol & _state.EnableRightSq2) / 100) +
|
||||
(_wave->GetOutput() * (int32_t)(cfg.WaveVol & _state.EnableRightWave) / 100) +
|
||||
(_noise->GetOutput() * (int32_t)(cfg.NoiseVol & _state.EnableRightNoise) / 100)
|
||||
) * (_state.RightVolume + 1) * 40;
|
||||
|
||||
if(_prevRightOutput != rightOutput) {
|
||||
blip_add_delta(_rightChannel, _clockCounter, rightOutput - _prevRightOutput);
|
||||
_prevRightOutput = rightOutput;
|
||||
}
|
||||
UpdateOutput(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,6 +114,33 @@ void GbApu::Run()
|
|||
}
|
||||
}
|
||||
|
||||
void GbApu::UpdateOutput(GameboyConfig& cfg)
|
||||
{
|
||||
int16_t leftOutput = (
|
||||
(_square1->GetOutput() * (int32_t)(cfg.Square1Vol & _state.EnableLeftSq1) / 100) +
|
||||
(_square2->GetOutput() * (int32_t)(cfg.Square2Vol & _state.EnableLeftSq2) / 100) +
|
||||
(_wave->GetOutput() * (int32_t)(cfg.WaveVol & _state.EnableLeftWave) / 100) +
|
||||
(_noise->GetOutput() * (int32_t)(cfg.NoiseVol & _state.EnableLeftNoise) / 100)
|
||||
) * (_state.LeftVolume + 1) * 40;
|
||||
|
||||
if(_prevLeftOutput != leftOutput) {
|
||||
blip_add_delta(_leftChannel, _clockCounter, leftOutput - _prevLeftOutput);
|
||||
_prevLeftOutput = leftOutput;
|
||||
}
|
||||
|
||||
int16_t rightOutput = (
|
||||
(_square1->GetOutput() * (int32_t)(cfg.Square1Vol & _state.EnableRightSq1) / 100) +
|
||||
(_square2->GetOutput() * (int32_t)(cfg.Square2Vol & _state.EnableRightSq2) / 100) +
|
||||
(_wave->GetOutput() * (int32_t)(cfg.WaveVol & _state.EnableRightWave) / 100) +
|
||||
(_noise->GetOutput() * (int32_t)(cfg.NoiseVol & _state.EnableRightNoise) / 100)
|
||||
) * (_state.RightVolume + 1) * 40;
|
||||
|
||||
if(_prevRightOutput != rightOutput) {
|
||||
blip_add_delta(_rightChannel, _clockCounter, rightOutput - _prevRightOutput);
|
||||
_prevRightOutput = rightOutput;
|
||||
}
|
||||
}
|
||||
|
||||
void GbApu::PlayQueuedAudio()
|
||||
{
|
||||
blip_end_frame(_leftChannel, _clockCounter);
|
||||
|
@ -350,6 +354,9 @@ void GbApu::Write(uint16_t addr, uint8_t value)
|
|||
_wave->WriteRam(addr, value);
|
||||
break;
|
||||
}
|
||||
|
||||
//Update APU output - some writes can immediately change the output
|
||||
UpdateOutput(_settings->GetGameboyConfig());
|
||||
}
|
||||
|
||||
uint8_t GbApu::InternalReadCgbRegister(uint16_t addr)
|
||||
|
|
|
@ -10,6 +10,7 @@ class Emulator;
|
|||
class Gameboy;
|
||||
class SoundMixer;
|
||||
class EmuSettings;
|
||||
struct GameboyConfig;
|
||||
|
||||
class GbApu : public ISerializable
|
||||
{
|
||||
|
@ -46,6 +47,8 @@ private:
|
|||
uint8_t InternalRead(uint16_t addr);
|
||||
uint8_t InternalReadCgbRegister(uint16_t addr);
|
||||
|
||||
void UpdateOutput(GameboyConfig& cfg);
|
||||
|
||||
public:
|
||||
GbApu();
|
||||
virtual ~GbApu();
|
||||
|
|
|
@ -40,6 +40,7 @@ public:
|
|||
|
||||
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
|
||||
|
|
|
@ -45,7 +45,9 @@ void GbNoiseChannel::ClockLengthCounter()
|
|||
|
||||
void GbNoiseChannel::UpdateOutput()
|
||||
{
|
||||
_state.Output = ((_state.ShiftRegister & 0x01) ^ 0x01) * _state.Volume;
|
||||
if(_state.Enabled) {
|
||||
_state.Output = ((_state.ShiftRegister & 0x01) ^ 0x01) * _state.Volume;
|
||||
}
|
||||
}
|
||||
|
||||
void GbNoiseChannel::ClockEnvelope()
|
||||
|
@ -158,6 +160,7 @@ void GbNoiseChannel::Write(uint16_t addr, uint8_t value)
|
|||
break;
|
||||
|
||||
case 4: {
|
||||
bool prevEnabled = _state.Enabled;
|
||||
if(value & 0x80) {
|
||||
//Writing a value to NRx4 with bit 7 set causes the following things to occur :
|
||||
|
||||
|
@ -189,6 +192,14 @@ void GbNoiseChannel::Write(uint16_t addr, uint8_t value)
|
|||
_state.Timer += (_state.Divisor == 1) ? 4 : -4;
|
||||
}
|
||||
|
||||
//Channel volume is reloaded from NRx2.
|
||||
_state.Volume = _state.EnvVolume;
|
||||
|
||||
if(_state.Enabled) {
|
||||
//Immediately update output if channel was enabled
|
||||
UpdateOutput();
|
||||
}
|
||||
|
||||
//Channel is enabled, if volume is not 0 or raise volume flag is set
|
||||
_state.Enabled = _state.EnvRaiseVolume || _state.EnvVolume > 0;
|
||||
|
||||
|
@ -204,12 +215,14 @@ void GbNoiseChannel::Write(uint16_t addr, uint8_t value)
|
|||
//Volume envelope timer is reloaded with period.
|
||||
_state.EnvTimer = _state.EnvPeriod;
|
||||
_state.EnvStopped = false;
|
||||
|
||||
//Channel volume is reloaded from NRx2.
|
||||
_state.Volume = _state.EnvVolume;
|
||||
}
|
||||
|
||||
_apu->ProcessLengthEnableFlag(value, _state.Length, _state.LengthEnabled, _state.Enabled);
|
||||
|
||||
if(!_state.Enabled && prevEnabled) {
|
||||
_state.Output = 0;
|
||||
UpdateOutput();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,15 @@ void GbSquareChannel::ClockLengthCounter()
|
|||
|
||||
void GbSquareChannel::UpdateOutput()
|
||||
{
|
||||
_state.Output = _dutySequences[_state.Duty][(_state.DutyPos - 1) & 0x07] * _state.Volume;
|
||||
if(!_state.Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_state.FirstStep) {
|
||||
_state.Output = 0;
|
||||
} else {
|
||||
_state.Output = _dutySequences[_state.Duty][(_state.DutyPos - 1) & 0x07] * _state.Volume;
|
||||
}
|
||||
}
|
||||
|
||||
void GbSquareChannel::ClockEnvelope()
|
||||
|
@ -147,6 +155,7 @@ void GbSquareChannel::Exec(uint32_t clocksToRun)
|
|||
if(_state.Timer == 0) {
|
||||
_state.Timer = (2048 - _state.Frequency) * 4;
|
||||
_state.DutyPos = (_state.DutyPos + 1) & 0x07;
|
||||
_state.FirstStep = false;
|
||||
UpdateOutput();
|
||||
}
|
||||
}
|
||||
|
@ -214,6 +223,7 @@ void GbSquareChannel::Write(uint16_t addr, uint8_t value)
|
|||
break;
|
||||
|
||||
case 4: {
|
||||
bool prevEnabled = _state.Enabled;
|
||||
_state.Frequency = (_state.Frequency & 0xFF) | ((value & 0x07) << 8);
|
||||
|
||||
if(value & 0x80) {
|
||||
|
@ -239,6 +249,11 @@ void GbSquareChannel::Write(uint16_t addr, uint8_t value)
|
|||
|
||||
//"Channel is enabled, if volume is not 0 or raise volume flag is set"
|
||||
_state.Enabled = _state.EnvRaiseVolume || _state.EnvVolume > 0;
|
||||
if(_state.Enabled && !prevEnabled) {
|
||||
//Don't update output until first timer tick
|
||||
//This is needed to get the correct volume in Daiku no Gen-san
|
||||
_state.FirstStep = true;
|
||||
}
|
||||
|
||||
//"If length counter is zero, it is set to 64 (256 for wave channel)."
|
||||
if(_state.Length == 0) {
|
||||
|
@ -274,6 +289,11 @@ void GbSquareChannel::Write(uint16_t addr, uint8_t value)
|
|||
}
|
||||
|
||||
_apu->ProcessLengthEnableFlag(value, _state.Length, _state.LengthEnabled, _state.Enabled);
|
||||
|
||||
if(!_state.Enabled && prevEnabled) {
|
||||
_state.Output = 0;
|
||||
UpdateOutput();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -286,5 +306,6 @@ void GbSquareChannel::Serialize(Serializer& s)
|
|||
SV(_state.Length); SV(_state.LengthEnabled); SV(_state.Enabled); SV(_state.Timer); SV(_state.DutyPos); SV(_state.Output);
|
||||
SV(_state.SweepNegateCalcDone); SV(_state.EnvStopped);
|
||||
SV(_state.SweepUpdateDelay);
|
||||
SV(_state.FirstStep);
|
||||
SV(_dac);
|
||||
}
|
||||
|
|
|
@ -81,6 +81,10 @@ double GbWaveChannel::GetOutput()
|
|||
|
||||
void GbWaveChannel::UpdateOutput()
|
||||
{
|
||||
if(!_state.Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_state.Volume) {
|
||||
_state.Output = _state.SampleBuffer >> (_state.Volume - 1);
|
||||
} else {
|
||||
|
@ -159,6 +163,7 @@ void GbWaveChannel::Write(uint16_t addr, uint8_t value)
|
|||
break;
|
||||
|
||||
case 4: {
|
||||
bool prevEnabled = _state.Enabled;
|
||||
_state.Frequency = (_state.Frequency & 0xFF) | ((value & 0x07) << 8);
|
||||
|
||||
if(value & 0x80) {
|
||||
|
@ -189,6 +194,11 @@ void GbWaveChannel::Write(uint16_t addr, uint8_t value)
|
|||
}
|
||||
|
||||
_apu->ProcessLengthEnableFlag(value, _state.Length, _state.LengthEnabled, _state.Enabled);
|
||||
|
||||
if(!_state.Enabled && prevEnabled) {
|
||||
_state.Output = 0;
|
||||
UpdateOutput();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -270,6 +270,7 @@ struct GbSquareState
|
|||
bool LengthEnabled;
|
||||
|
||||
bool Enabled;
|
||||
bool FirstStep;
|
||||
uint8_t DutyPos;
|
||||
uint8_t Output;
|
||||
};
|
||||
|
|
|
@ -232,6 +232,7 @@ public struct GbSquareState
|
|||
[MarshalAs(UnmanagedType.I1)] public bool LengthEnabled;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)] public bool Enabled;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool FirstStep;
|
||||
public byte DutyPos;
|
||||
public byte Output;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue