mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
-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)
430 lines
No EOL
12 KiB
C++
430 lines
No EOL
12 KiB
C++
#include "pch.h"
|
|
#include "Gameboy/APU/GbApu.h"
|
|
#include "Gameboy/Gameboy.h"
|
|
#include "Gameboy/GbTimer.h"
|
|
#include "Shared/Emulator.h"
|
|
#include "Shared/EmuSettings.h"
|
|
#include "Shared/Audio/SoundMixer.h"
|
|
#include "Utilities/Serializer.h"
|
|
|
|
GbApu::GbApu()
|
|
{
|
|
_soundBuffer = new int16_t[GbApu::MaxSamples * 2];
|
|
memset(_soundBuffer, 0, GbApu::MaxSamples * 2 * sizeof(int16_t));
|
|
|
|
_leftChannel = blip_new(GbApu::MaxSamples);
|
|
_rightChannel = blip_new(GbApu::MaxSamples);
|
|
}
|
|
|
|
void GbApu::Init(Emulator* emu, Gameboy* gameboy)
|
|
{
|
|
_square1.reset(new GbSquareChannel(this));
|
|
_square2.reset(new GbSquareChannel(this));
|
|
_wave.reset(new GbWaveChannel(this, gameboy));
|
|
_noise.reset(new GbNoiseChannel(this));
|
|
|
|
_prevLeftOutput = 0;
|
|
_prevRightOutput = 0;
|
|
_clockCounter = 0;
|
|
_prevClockCount = 0;
|
|
|
|
_emu = emu;
|
|
_settings = emu->GetSettings();
|
|
_soundMixer = emu->GetSoundMixer();
|
|
_gameboy = gameboy;
|
|
_state = {};
|
|
|
|
blip_clear(_leftChannel);
|
|
blip_clear(_rightChannel);
|
|
|
|
blip_set_rates(_leftChannel, GbApu::ApuFrequency, GbApu::SampleRate);
|
|
blip_set_rates(_rightChannel, GbApu::ApuFrequency, GbApu::SampleRate);
|
|
}
|
|
|
|
GbApu::~GbApu()
|
|
{
|
|
blip_delete(_leftChannel);
|
|
blip_delete(_rightChannel);
|
|
delete[] _soundBuffer;
|
|
}
|
|
|
|
GbApuDebugState GbApu::GetState()
|
|
{
|
|
GbApuDebugState state;
|
|
state.Common = _state;
|
|
state.Square1 = _square1->GetState();
|
|
state.Square2 = _square2->GetState();
|
|
state.Wave = _wave->GetState();
|
|
state.Noise = _noise->GetState();
|
|
return state;
|
|
}
|
|
|
|
bool GbApu::IsOddApuCycle()
|
|
{
|
|
return ((_gameboy->GetApuCycleCount() - _powerOnCycle) & 0x02) != 0;
|
|
}
|
|
|
|
uint64_t GbApu::GetElapsedApuCycles()
|
|
{
|
|
return _gameboy->GetApuCycleCount() - _powerOnCycle;
|
|
}
|
|
|
|
void GbApu::Run()
|
|
{
|
|
uint64_t clockCount = _gameboy->GetApuCycleCount();
|
|
uint32_t clocksToRun = (uint32_t)(clockCount - _prevClockCount);
|
|
_prevClockCount = clockCount;
|
|
|
|
GameboyConfig& cfg = _settings->GetGameboyConfig();
|
|
|
|
if(!_state.ApuEnabled) {
|
|
_clockCounter += clocksToRun;
|
|
} else {
|
|
while(clocksToRun > 0) {
|
|
uint32_t minTimer = 250;
|
|
if(clocksToRun < minTimer) {
|
|
minTimer = clocksToRun;
|
|
}
|
|
if(_square1->GetState().Timer < minTimer) {
|
|
minTimer = _square1->GetState().Timer;
|
|
}
|
|
if(_square2->GetState().Timer < minTimer) {
|
|
minTimer = _square2->GetState().Timer;
|
|
}
|
|
if(_wave->GetState().Timer < minTimer) {
|
|
minTimer = _wave->GetState().Timer;
|
|
}
|
|
if(_noise->GetState().Timer < minTimer) {
|
|
minTimer = _noise->GetState().Timer;
|
|
}
|
|
|
|
clocksToRun -= minTimer;
|
|
_square1->Exec(minTimer);
|
|
_square2->Exec(minTimer);
|
|
_wave->Exec(minTimer);
|
|
_noise->Exec(minTimer);
|
|
|
|
_clockCounter += minTimer;
|
|
UpdateOutput(cfg);
|
|
}
|
|
}
|
|
|
|
if(!_gameboy->IsSgb() && _clockCounter >= 20000) {
|
|
PlayQueuedAudio();
|
|
}
|
|
}
|
|
|
|
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);
|
|
blip_end_frame(_rightChannel, _clockCounter);
|
|
|
|
uint32_t sampleCount = (uint32_t)blip_read_samples(_leftChannel, _soundBuffer, GbApu::MaxSamples, 1);
|
|
blip_read_samples(_rightChannel, _soundBuffer + 1, GbApu::MaxSamples, 1);
|
|
_soundMixer->PlayAudioBuffer(_soundBuffer, sampleCount, GbApu::SampleRate);
|
|
_clockCounter = 0;
|
|
}
|
|
|
|
void GbApu::GetSoundSamples(int16_t* &samples, uint32_t& sampleCount)
|
|
{
|
|
Run();
|
|
blip_end_frame(_leftChannel, _clockCounter);
|
|
blip_end_frame(_rightChannel, _clockCounter);
|
|
|
|
sampleCount = (uint32_t)blip_read_samples(_leftChannel, _soundBuffer, GbApu::MaxSamples, 1);
|
|
blip_read_samples(_rightChannel, _soundBuffer + 1, GbApu::MaxSamples, 1);
|
|
samples = _soundBuffer;
|
|
_clockCounter = 0;
|
|
}
|
|
|
|
void GbApu::ClockFrameSequencer()
|
|
{
|
|
Run();
|
|
|
|
if(!_state.ApuEnabled) {
|
|
return;
|
|
}
|
|
|
|
if(_skipFirstEventCounter && --_skipFirstEventCounter) {
|
|
return;
|
|
}
|
|
|
|
if((_state.FrameSequenceStep & 0x01) == 0) {
|
|
_square1->ClockLengthCounter();
|
|
_square2->ClockLengthCounter();
|
|
_wave->ClockLengthCounter();
|
|
_noise->ClockLengthCounter();
|
|
|
|
if((_state.FrameSequenceStep & 0x03) == 2) {
|
|
_square1->ClockSweepUnit();
|
|
}
|
|
} else if(_state.FrameSequenceStep == 7) {
|
|
_square1->ClockEnvelope();
|
|
_square2->ClockEnvelope();
|
|
_noise->ClockEnvelope();
|
|
}
|
|
|
|
_state.FrameSequenceStep = (_state.FrameSequenceStep + 1) & 0x07;
|
|
}
|
|
|
|
uint8_t GbApu::Peek(uint16_t addr)
|
|
{
|
|
return InternalRead(addr);
|
|
}
|
|
|
|
uint8_t GbApu::Read(uint16_t addr)
|
|
{
|
|
Run();
|
|
return InternalRead(addr);
|
|
}
|
|
|
|
uint8_t GbApu::InternalRead(uint16_t addr)
|
|
{
|
|
switch(addr) {
|
|
case 0xFF10: case 0xFF11: case 0xFF12: case 0xFF13: case 0xFF14:
|
|
return _square1->Read(addr - 0xFF10);
|
|
|
|
case 0xFF16: case 0xFF17: case 0xFF18: case 0xFF19:
|
|
return _square2->Read(addr - 0xFF15);
|
|
|
|
case 0xFF1A: case 0xFF1B: case 0xFF1C: case 0xFF1D: case 0xFF1E:
|
|
return _wave->Read(addr - 0xFF1A);
|
|
|
|
case 0xFF20: case 0xFF21: case 0xFF22: case 0xFF23:
|
|
return _noise->Read(addr - 0xFF1F);
|
|
|
|
case 0xFF24:
|
|
return (
|
|
(_state.ExtAudioLeftEnabled ? 0x80 : 0) |
|
|
(_state.LeftVolume << 4) |
|
|
(_state.ExtAudioRightEnabled ? 0x08 : 0) |
|
|
_state.RightVolume
|
|
);
|
|
|
|
case 0xFF25:
|
|
return (
|
|
(_state.EnableLeftNoise ? 0x80 : 0) |
|
|
(_state.EnableLeftWave ? 0x40 : 0) |
|
|
(_state.EnableLeftSq2 ? 0x20 : 0) |
|
|
(_state.EnableLeftSq1 ? 0x10 : 0) |
|
|
(_state.EnableRightNoise ? 0x08 : 0) |
|
|
(_state.EnableRightWave ? 0x04 : 0) |
|
|
(_state.EnableRightSq2 ? 0x02 : 0) |
|
|
(_state.EnableRightSq1 ? 0x01 : 0)
|
|
);
|
|
|
|
case 0xFF26:
|
|
return (
|
|
(_state.ApuEnabled ? 0x80 : 0) |
|
|
0x70 | //open bus
|
|
((_state.ApuEnabled && _noise->Enabled()) ? 0x08 : 0) |
|
|
((_state.ApuEnabled && _wave->Enabled()) ? 0x04 : 0) |
|
|
((_state.ApuEnabled && _square2->Enabled()) ? 0x02 : 0) |
|
|
((_state.ApuEnabled && _square1->Enabled()) ? 0x01 : 0)
|
|
);
|
|
|
|
case 0xFF30: case 0xFF31: case 0xFF32: case 0xFF33: case 0xFF34: case 0xFF35: case 0xFF36: case 0xFF37:
|
|
case 0xFF38: case 0xFF39: case 0xFF3A: case 0xFF3B: case 0xFF3C: case 0xFF3D: case 0xFF3E: case 0xFF3F:
|
|
return _wave->ReadRam(addr);
|
|
}
|
|
|
|
//Open bus
|
|
return 0xFF;
|
|
}
|
|
|
|
void GbApu::Write(uint16_t addr, uint8_t value)
|
|
{
|
|
Run();
|
|
|
|
if(!_state.ApuEnabled) {
|
|
if(addr == 0xFF11 || addr == 0xFF16 || addr == 0xFF20) {
|
|
//Allow writes to length counter, but not the upper 2 bits (square duty)
|
|
value &= 0x3F;
|
|
} else if(addr < 0xFF26 && addr != 0xFF1B) {
|
|
//Ignore all writes to these registers when APU is disabled
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch(addr) {
|
|
case 0xFF10: case 0xFF11: case 0xFF12: case 0xFF13: case 0xFF14:
|
|
_square1->Write(addr - 0xFF10, value);
|
|
break;
|
|
|
|
case 0xFF16: case 0xFF17: case 0xFF18: case 0xFF19:
|
|
_square2->Write(addr - 0xFF15, value); //Same as square1, but without a sweep unit
|
|
break;
|
|
|
|
case 0xFF1A: case 0xFF1B: case 0xFF1C: case 0xFF1D: case 0xFF1E:
|
|
_wave->Write(addr - 0xFF1A, value);
|
|
break;
|
|
|
|
case 0xFF20: case 0xFF21: case 0xFF22: case 0xFF23:
|
|
_noise->Write(addr - 0xFF1F, value);
|
|
break;
|
|
|
|
case 0xFF24:
|
|
_state.ExtAudioLeftEnabled = (value & 0x80) != 0;
|
|
_state.LeftVolume = (value & 0x70) >> 4;
|
|
_state.ExtAudioRightEnabled = (value & 0x08) != 0;
|
|
_state.RightVolume = (value & 0x07);
|
|
break;
|
|
|
|
case 0xFF25:
|
|
_state.EnableLeftNoise = (value & 0x80) ? 0xFF : 0;
|
|
_state.EnableLeftWave = (value & 0x40) ? 0xFF : 0;
|
|
_state.EnableLeftSq2 = (value & 0x20) ? 0xFF : 0;
|
|
_state.EnableLeftSq1 = (value & 0x10) ? 0xFF : 0;
|
|
|
|
_state.EnableRightNoise = (value & 0x08) ? 0xFF : 0;
|
|
_state.EnableRightWave = (value & 0x04) ? 0xFF : 0;
|
|
_state.EnableRightSq2 = (value & 0x02) ? 0xFF : 0;
|
|
_state.EnableRightSq1 = (value & 0x01) ? 0xFF : 0;
|
|
break;
|
|
|
|
case 0xFF26: {
|
|
bool apuEnabled = (value & 0x80) != 0;
|
|
if(_state.ApuEnabled != apuEnabled) {
|
|
if(!apuEnabled) {
|
|
_square1->Disable();
|
|
_square2->Disable();
|
|
_wave->Disable();
|
|
_noise->Disable();
|
|
Write(0xFF24, 0);
|
|
Write(0xFF25, 0);
|
|
} else {
|
|
//"starting the APU while bit 4 of the DIV register is set causes the APU to skip the first DIV-APU event"
|
|
//Based on the results of the div_*_10 tests in SameSuite, when the first event is skipped, the
|
|
//"length enable" glitch will be trigerred until the event after the skipped one occurs.
|
|
//This uses a counter to reproduce this behavior
|
|
_skipFirstEventCounter = _gameboy->GetTimer()->IsFrameSequencerBitSet() ? 2 : 0;
|
|
|
|
//"When powered on, the frame sequencer is reset so that the next step will be 0,
|
|
//the square duty units are reset to the first step of the waveform, and the wave channel's sample buffer is reset to 0."
|
|
_state.FrameSequenceStep = 0;
|
|
|
|
_powerOnCycle = _gameboy->GetApuCycleCount();
|
|
|
|
_square1->Disable();
|
|
_square2->Disable();
|
|
_noise->Disable();
|
|
_wave->Disable();
|
|
|
|
if(_gameboy->IsCgb()) {
|
|
//Length counters are reset to 0 at power on
|
|
_square1->ResetLengthCounter();
|
|
_square2->ResetLengthCounter();
|
|
_wave->ResetLengthCounter();
|
|
_noise->ResetLengthCounter();
|
|
}
|
|
}
|
|
_state.ApuEnabled = apuEnabled;
|
|
}
|
|
break;
|
|
}
|
|
case 0xFF30: case 0xFF31: case 0xFF32: case 0xFF33: case 0xFF34: case 0xFF35: case 0xFF36: case 0xFF37:
|
|
case 0xFF38: case 0xFF39: case 0xFF3A: case 0xFF3B: case 0xFF3C: case 0xFF3D: case 0xFF3E: case 0xFF3F:
|
|
_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)
|
|
{
|
|
switch(addr) {
|
|
case 0xFF76: return _square1->GetRawOutput() | (_square2->GetRawOutput() << 4);
|
|
case 0xFF77: return _wave->GetRawOutput() | (_noise->GetRawOutput() << 4);
|
|
}
|
|
|
|
//Should not be called
|
|
return 0;
|
|
}
|
|
|
|
uint8_t GbApu::PeekCgbRegister(uint16_t addr)
|
|
{
|
|
return InternalReadCgbRegister(addr);
|
|
}
|
|
|
|
uint8_t GbApu::ReadCgbRegister(uint16_t addr)
|
|
{
|
|
Run();
|
|
return InternalReadCgbRegister(addr);
|
|
}
|
|
|
|
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 || _skipFirstEventCounter)) {
|
|
//"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)
|
|
{
|
|
if(s.IsSaving()) {
|
|
Run();
|
|
} else {
|
|
_clockCounter = 0;
|
|
blip_clear(_leftChannel);
|
|
blip_clear(_rightChannel);
|
|
}
|
|
|
|
SV(_state.ApuEnabled); SV(_state.FrameSequenceStep);
|
|
SV(_state.EnableLeftSq1); SV(_state.EnableLeftSq2); SV(_state.EnableLeftWave); SV(_state.EnableLeftNoise);
|
|
SV(_state.EnableRightSq1); SV(_state.EnableRightSq2); SV(_state.EnableRightWave); SV(_state.EnableRightNoise);
|
|
SV(_state.LeftVolume); SV(_state.RightVolume); SV(_state.ExtAudioLeftEnabled); SV(_state.ExtAudioRightEnabled);
|
|
SV(_prevLeftOutput); SV(_prevRightOutput); SV(_clockCounter); SV(_prevClockCount); SV(_skipFirstEventCounter);
|
|
SV(_powerOnCycle);
|
|
|
|
SV(_square1);
|
|
SV(_square2);
|
|
SV(_wave);
|
|
SV(_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); |