FDS: Fixed sound emulation (fixes Bio Miracle)

This commit is contained in:
Souryo 2017-05-04 18:17:46 -04:00
parent 3b34a7c113
commit 5ccbe95a55
4 changed files with 96 additions and 65 deletions

View file

@ -29,47 +29,47 @@ public:
virtual void WriteReg(uint16_t addr, uint8_t value)
{
switch(addr & 0x03) {
case 0:
switch(addr) {
case 0x4080:
_speed = value & 0x3F;
_volumeIncrease = (value & 0x40) == 0x40;
_envelopeOff = (value & 0x80) == 0x80;
//"Writing to this register immediately resets the clock timer that ticks the volume envelope (delaying the next tick slightly)."
_timer = 8 * (_speed + 1) * _masterSpeed;
ResetTimer();
if(_envelopeOff) {
//Envelope is off, gain = speed
_gain = _speed;
}
break;
case 2:
case 0x4082:
_frequency = (_frequency & 0x0F00) | value;
break;
case 3:
case 0x4083:
_frequency = (_frequency & 0xFF) | ((value & 0x0F) << 8);
break;
}
}
void TickEnvelope(bool envelopesDisabled)
bool TickEnvelope()
{
if(_envelopeOff) {
_gain = _speed;
} else if(!envelopesDisabled && _speed > 0) {
if(_timer > 0) {
_timer--;
} else {
_timer = 8 * (_speed + 1) * _masterSpeed;
if(!_envelopeOff && _masterSpeed > 0) {
_timer--;
if(_timer == 0) {
ResetTimer();
if(_volumeIncrease) {
if(_gain < 32) {
_gain++;
}
} else {
if(_gain > 0) {
_gain--;
}
if(_volumeIncrease && _gain < 32) {
_gain++;
} else if(!_volumeIncrease && _gain > 0) {
_gain--;
}
return true;
}
}
return false;
}
uint8_t GetGain()
@ -81,4 +81,9 @@ public:
{
return _frequency;
}
void ResetTimer()
{
_timer = 8 * (_speed + 1) * _masterSpeed;
}
};

View file

@ -285,7 +285,9 @@ bool FDS::IsDiskInserted()
uint8_t FDS::ReadRegister(uint16_t addr)
{
uint8_t value = 0;
if(_diskRegEnabled && addr < 0x4040 || _soundRegEnabled && addr >= 0x4040) {
if(_soundRegEnabled && addr >= 0x4040) {
return _audio->ReadRegister(addr);
} else if(_diskRegEnabled && addr <= 0x4033) {
switch(addr) {
case 0x4030:
value |= CPU::HasIRQSource(IRQSource::External) ? 0x01 : 0x00;
@ -313,9 +315,6 @@ uint8_t FDS::ReadRegister(uint16_t addr)
case 0x4033:
//Always return good battery
return 0x80 & _extConWriteReg;
default:
return _audio->ReadRegister(addr);
}
}

View file

@ -47,41 +47,46 @@ protected:
void ClockAudio() override
{
//"The envelopes are not ticked while the waveform is halted."
_volume.TickEnvelope(_disableEnvelopes || _haltWaveform);
_mod.TickEnvelope(_disableEnvelopes || _haltWaveform);
if(_mod.IsEnabled()) {
if(_mod.TickModulator()) {
//Modulator was ticked, update wave pitch
_wavePitch = _mod.GetWavePitch(_volume.GetFrequency());
int frequency = _volume.GetFrequency();
if(!_haltWaveform && !_disableEnvelopes) {
_volume.TickEnvelope();
if(_mod.TickEnvelope()) {
_mod.UpdateOutput(frequency);
}
} else {
_wavePitch = 0;
}
if(_mod.TickModulator()) {
//Modulator was ticked, update wave pitch
_mod.UpdateOutput(frequency);
}
if(_haltWaveform) {
//"The high bit of this register halts the waveform and resets its phase to 0. Note that if halted it will output the constant value at $4040"
//"writes to the volume register $4080 or master volume $4089 will affect the output."
_wavePosition = 0;
}
int32_t freq = _volume.GetFrequency() + _wavePitch;
if(freq > 0 && !_waveWriteEnabled) {
_waveOverflowCounter += freq;
if(_waveOverflowCounter < freq) {
//Overflow, tick
uint32_t level = std::min((int)_volume.GetGain(), 32) * WaveVolumeTable[_masterVolume];
uint8_t outputLevel = (_waveTable[_wavePosition] * level) / 1152;
APU::AddExpansionAudioDelta(AudioChannel::FDS, outputLevel - _lastOutput);
_lastOutput = outputLevel;
UpdateOutput();
} else {
UpdateOutput();
_wavePosition = (_wavePosition + 1) & 0x3F;
if(frequency + _mod.GetOutput() > 0 && !_waveWriteEnabled) {
_waveOverflowCounter += frequency + _mod.GetOutput();
if(_waveOverflowCounter < frequency + _mod.GetOutput()) {
_wavePosition = (_wavePosition + 1) & 0x3F;
}
}
}
}
void UpdateOutput()
{
uint32_t level = std::min((int)_volume.GetGain(), 32) * WaveVolumeTable[_masterVolume];
uint8_t outputLevel = (_waveTable[_wavePosition] * level) / 1152;
if(_lastOutput != outputLevel) {
APU::AddExpansionAudioDelta(AudioChannel::FDS, outputLevel - _lastOutput);
_lastOutput = outputLevel;
}
}
public:
uint8_t ReadRegister(uint16_t addr)
{
@ -113,6 +118,10 @@ public:
case 0x4083:
_disableEnvelopes = (value & 0x40) == 0x40;
_haltWaveform = (value & 0x80) == 0x80;
if(_disableEnvelopes) {
_volume.ResetTimer();
_mod.ResetTimer();
}
_volume.WriteReg(addr, value);
break;

View file

@ -14,6 +14,7 @@ private:
uint8_t _modTable[64];
uint8_t _modTablePosition = 0;
uint16_t _overflowCounter = 0;
int32_t _output = 0;
protected:
void StreamState(bool saving) override
@ -21,17 +22,28 @@ protected:
BaseFdsChannel::StreamState(saving);
ArrayInfo<uint8_t> modTable = { _modTable, 64 };
Stream(_counter, _modulationDisabled, _modTablePosition, _overflowCounter, modTable);
Stream(_counter, _modulationDisabled, _modTablePosition, _overflowCounter, modTable, _output);
}
public:
virtual void WriteReg(uint16_t addr, uint8_t value) override
{
switch(addr & 0x03) {
case 1: UpdateCounter(value & 0x7F); break;
case 3: _modulationDisabled = (value & 0x80) == 0x80; break;
switch(addr) {
case 0x4084:
case 0x4086:
BaseFdsChannel::WriteReg(addr, value);
break;
case 0x4085:
UpdateCounter(_counter & 0x7F);
break;
case 0x4087:
BaseFdsChannel::WriteReg(addr, value);
_modulationDisabled = (value & 0x80) == 0x80;
if(_modulationDisabled) {
_overflowCounter = 0;
}
break;
}
BaseFdsChannel::WriteReg(addr, value);
}
void WriteModTable(uint8_t value)
@ -61,22 +73,23 @@ public:
bool TickModulator()
{
_overflowCounter += _frequency;
if(IsEnabled()) {
_overflowCounter += _frequency;
if(_overflowCounter < _frequency) {
//Overflowed, tick the modulator
int32_t offset = _modLut[_modTable[_modTablePosition]];
UpdateCounter(offset == ModReset ? 0 : _counter + offset);
if(_overflowCounter < _frequency) {
//Overflowed, tick the modulator
int32_t offset = _modLut[_modTable[_modTablePosition]];
UpdateCounter(offset == ModReset ? 0 : _counter + offset);
_modTablePosition = (_modTablePosition + 1) & 0x3F;
_modTablePosition = (_modTablePosition + 1) & 0x3F;
return true;
return true;
}
}
return false;
}
int32_t GetWavePitch(uint16_t volumePitch)
void UpdateOutput(uint16_t volumePitch)
{
//Code from NesDev Wiki
@ -108,6 +121,11 @@ public:
}
// final mod result is in temp
return temp;
_output = temp;
}
int32_t GetOutput()
{
return IsEnabled() ? _output : 0;
}
};