Mesen2/Core/PCE/CdRom/PceAdpcm.cpp
Sour d0a3db20d8 PCE: Fixed ADPCM audio decoding issues
-Output is a 12-bit value and should wrap
-"Magnitude"/"step size" should be reset to 0 when playback starts

This fixes sound effects in NEXZR that were broken by the previous (incorrect) fix and also broken because _magnitude was not being reset to 0 when each sample started playing
2025-03-12 16:46:32 +09:00

381 lines
9 KiB
C++

#include "pch.h"
#include "PCE/CdRom/PceAdpcm.h"
#include "PCE/CdRom/PceScsiBus.h"
#include "PCE/CdRom/PceCdRom.h"
#include "PCE/PceConsole.h"
#include "PCE/PceTypes.h"
#include "PCE/PceConstants.h"
#include "Shared/Emulator.h"
#include "Shared/EmuSettings.h"
#include "Shared/MessageManager.h"
#include "Utilities/HexUtilities.h"
#include "Utilities/Serializer.h"
using namespace ScsiSignal;
PceAdpcm::PceAdpcm(PceConsole* console, Emulator* emu, PceCdRom* cdrom, PceScsiBus* scsi)
{
_state = {};
_console = console;
_cdrom = cdrom;
_scsi = scsi;
_emu = emu;
_ram = new uint8_t[0x10000];
console->InitializeRam(_ram, 0x10000);
emu->RegisterMemory(MemoryType::PceAdpcmRam, _ram, 0x10000);
}
PceAdpcm::~PceAdpcm()
{
delete[] _ram;
}
void PceAdpcm::Reset()
{
_state.WriteClockCounter = 0;
_state.ReadClockCounter = 0;
_state.ReadAddress = 0;
_state.WriteAddress = 0;
_state.AddressPort = 0;
SetEndReached(false);
SetHalfReached(false);
_state.AdpcmLength = 0;
_state.Nibble = false;
_currentOutput = 2048;
_magnitude = 0;
}
void PceAdpcm::SetHalfReached(bool value)
{
if(_state.HalfReached != value) {
_state.HalfReached = value;
if(value) {
_cdrom->SetIrqSource(PceCdRomIrqSource::Adpcm);
} else {
_cdrom->ClearIrqSource(PceCdRomIrqSource::Adpcm);
}
}
}
void PceAdpcm::SetEndReached(bool value)
{
if(_state.EndReached != value) {
_state.EndReached = value;
if(value) {
_cdrom->SetIrqSource(PceCdRomIrqSource::Stop);
} else {
_cdrom->ClearIrqSource(PceCdRomIrqSource::Stop);
}
}
}
bool PceAdpcm::IsLengthLatchEnabled()
{
return (_state.Control & 0x10) != 0;
}
void PceAdpcm::ProcessFlags()
{
if(IsLengthLatchEnabled()) {
_state.AdpcmLength = _state.AddressPort;
SetEndReached(false);
}
if(_state.Control & 0x80) {
Reset();
}
if(_state.Control & 0x20) {
_needExec = true;
}
}
void PceAdpcm::SetControl(uint8_t value)
{
if((value & 0x02) && !(_state.Control & 0x02)) {
_state.WriteAddress = _state.AddressPort - ((value & 0x01) ? 0 : 1);
LogDebug("[ADPCM] Update write addr");
}
if((value & 0x08) && !(_state.Control & 0x08)) {
_state.ReadAddress = _state.AddressPort - ((value & 0x04) ? 0 : 1);
LogDebug("[ADPCM] Update read addr");
}
_state.Control = value;
ProcessFlags();
}
void PceAdpcm::ProcessReadOperation()
{
_state.ReadClockCounter--;
if(_state.ReadClockCounter == 0) {
_state.ReadBuffer = _ram[_state.ReadAddress];
_emu->ProcessMemoryAccess<CpuType::Pce, MemoryType::PceAdpcmRam, MemoryOperationType::Read>(_state.ReadAddress, _state.ReadBuffer);
_state.ReadAddress++;
if(!IsLengthLatchEnabled()) {
if(_state.AdpcmLength > 0) {
_state.AdpcmLength--;
SetHalfReached(_state.AdpcmLength < 0x8000);
} else {
SetEndReached(true);
SetHalfReached(false);
}
}
}
}
void PceAdpcm::ProcessWriteOperation()
{
_state.WriteClockCounter--;
if(_state.WriteClockCounter == 0) {
_emu->ProcessMemoryAccess<CpuType::Pce, MemoryType::PceAdpcmRam, MemoryOperationType::Write>(_state.WriteAddress, _state.WriteBuffer);
_ram[_state.WriteAddress] = _state.WriteBuffer;
_state.WriteAddress++;
if(_state.AdpcmLength == 0) {
SetEndReached(true);
}
SetHalfReached(_state.AdpcmLength < 0x8000);
if(!IsLengthLatchEnabled()) {
_state.AdpcmLength = (_state.AdpcmLength + 1) & 0x1FFFF;
}
}
}
void PceAdpcm::ProcessDmaRequest()
{
if(_dmaWriteCounter) {
if(--_dmaWriteCounter == 0) {
if(!_state.WriteClockCounter) {
_state.WriteClockCounter = GetClocksToNextSlot(false);
_state.WriteBuffer = _scsi->GetDataPort();
_scsi->SetAckWithAutoClear();
if(!_scsi->IsDataBlockReady()) {
//Reset bit 0 only
//Resetting bit 1 breaks Record of Lodoss War (freezes when start is pressed)
//Not resetting bit 0 breaks Seiya Monogatari - Anearth (freezes on logo)
_state.DmaControl &= ~0x01;
}
} else {
_dmaWriteCounter++;
}
}
} else if(!_scsi->CheckSignal(Ack) && !_scsi->CheckSignal(Cd) && _scsi->CheckSignal(Io) && _scsi->CheckSignal(Req)) {
//Some delay is required here according to test rom
//Valid range is 10-14 (higher or lower fails the test)
_dmaWriteCounter = 12;
}
}
uint8_t PceAdpcm::GetClocksToNextSlot(bool forRead)
{
uint64_t slotIndex = _console->GetMasterClock() / 9;
uint8_t slotType = slotIndex & 0x03;
uint8_t gap;
switch(slotType) {
default: case 0: gap = forRead ? 3 : 1; break; //Refresh slot (ADPCM uses DRAM - needs periodic refresh)
case 1: gap = forRead ? 2 : 1; break; //Write slot
case 2: gap = forRead ? 1 : 3; break; //Write slot
case 3: gap = forRead ? 4 : 2; break; //Read slot
}
uint64_t nextSlotClock = (slotIndex + gap) * 9;
return (nextSlotClock - _console->GetMasterClock()) / 3;
}
void PceAdpcm::Exec()
{
//Called every 3 master clocks
ProcessFlags();
if(_state.Playing || (_state.Control & 0x20)) {
_nextSampleCounter += 3;
if(_nextSampleCounter >= _clocksPerSample) {
PlaySample();
_nextSampleCounter -= _clocksPerSample;
}
}
if(_state.ReadClockCounter > 0) {
ProcessReadOperation();
}
if(_state.WriteClockCounter > 0) {
ProcessWriteOperation();
}
bool dmaRequested = (_state.DmaControl & 0x03) != 0;
if(dmaRequested) {
ProcessDmaRequest();
}
ProcessFlags();
_needExec = _state.Playing || (_state.Control & 0x20) || _state.ReadClockCounter || _state.WriteClockCounter || dmaRequested || _dmaWriteCounter;
}
void PceAdpcm::Write(uint16_t addr, uint8_t value)
{
switch(addr & 0x3FF) {
case 0x08: _state.AddressPort = (_state.AddressPort & 0xFF00) | value; break;
case 0x09: _state.AddressPort = (_state.AddressPort & 0xFF) | (value << 8); break;
case 0x0A:
_state.WriteClockCounter = GetClocksToNextSlot(false);
_state.WriteBuffer = value;
_needExec = true;
break;
case 0x0B:
if(!_scsi->IsDataBlockReady()) {
//Bit 0 can't be set if a transfer isn't already running
value &= ~0x01;
}
_state.DmaControl = value;
_needExec = true;
LogDebugIf((value & 0x03), "[ADPCM] DMA");
break;
case 0x0D: SetControl(value); break;
case 0x0E: {
_state.PlaybackRate = value;
double freq = 32000.0 / (16 - (_state.PlaybackRate & 0x0F));
_clocksPerSample = PceConstants::MasterClockRate / freq;
break;
}
default:
LogDebug("Write unimplemented ADPCM register: " + HexUtilities::ToHex(addr) + " = " + HexUtilities::ToHex(value));
break;
}
}
uint8_t PceAdpcm::Read(uint16_t addr)
{
switch(addr & 0x3FF) {
case 0x0A:
_state.ReadClockCounter = GetClocksToNextSlot(true);
_needExec = true;
return _state.ReadBuffer;
case 0x0B: return _state.DmaControl;
case 0x0C:
return (
(_state.EndReached ? 0x01 : 0) |
(_state.WriteClockCounter ? 0x04 : 0) |
(_state.Playing ? 0x08 : 0) |
(_state.ReadClockCounter ? 0x80 : 0)
);
case 0x0D:
return _state.Control;
case 0x0E: return _state.PlaybackRate;
default:
LogDebug("Read unimplemented ADPCM register: " + HexUtilities::ToHex(addr));
return 0;
}
}
void PceAdpcm::PlaySample()
{
if(_state.Control & 0x80) {
//Reset flag is enabled
_state.Playing = (_state.Control & 0x20) != 0;
return;
}
if((_state.Control & 0x40) && _state.AdpcmLength == 0) {
_state.Control &= ~0x20;
}
if(!(_state.Control & 0x20)) {
_state.Playing = false;
return;
}
if(!_state.Playing) {
_state.Playing = true;
_currentOutput = 2048;
_magnitude = 0;
}
uint8_t data;
if(_state.Nibble) {
_state.Nibble = false;
data = _ram[_state.ReadAddress] & 0x0F;
_state.ReadAddress++;
_state.AdpcmLength = (_state.AdpcmLength - 1) & 0x1FFFF;
} else {
_state.Nibble = true;
data = (_ram[_state.ReadAddress] >> 4) & 0x0F;
}
uint8_t value = data & 0x07;
int8_t sign = data & 0x08 ? -1 : 1;
int16_t adjustment = _stepSize[(_magnitude << 3) | value] * sign;
_currentOutput = (_currentOutput + adjustment) & 0xFFF;
_magnitude = std::clamp(_magnitude + _stepFactor[value], 0, 48);
SetHalfReached(_state.AdpcmLength <= 0x8000);
if(_state.AdpcmLength == 0) {
SetEndReached(true);
}
int16_t out = (_currentOutput - 2048) * 10;
_samplesToPlay.push_back(out);
_samplesToPlay.push_back(out);
}
void PceAdpcm::MixAudio(int16_t* out, uint32_t sampleCount, uint32_t sampleRate)
{
double freq = 32000.0 / (16 - (_state.PlaybackRate & 0x0F));
double volume = _cdrom->GetAudioFader().GetVolume(PceAudioFaderTarget::Adpcm);
_resampler.SetVolume(_emu->GetSettings()->GetPcEngineConfig().AdpcmVolume / 100.0 * volume);
_resampler.SetSampleRates(freq, sampleRate);
_resampler.Resample<true>(_samplesToPlay.data(), (uint32_t)_samplesToPlay.size() / 2, out, sampleCount, _state.Playing);
_samplesToPlay.clear();
}
void PceAdpcm::Serialize(Serializer& s)
{
SVArray(_ram, 0x10000);
SV(_state.Nibble);
SV(_state.ReadAddress);
SV(_state.WriteAddress);
SV(_state.AddressPort);
SV(_state.DmaControl);
SV(_state.Control);
SV(_state.PlaybackRate);
SV(_state.AdpcmLength);
SV(_state.EndReached);
SV(_state.HalfReached);
SV(_state.Playing);
SV(_state.ReadBuffer);
SV(_state.ReadClockCounter);
SV(_state.WriteBuffer);
SV(_state.WriteClockCounter);
SV(_currentOutput);
SV(_magnitude);
SV(_clocksPerSample);
SV(_nextSampleCounter);
SV(_dmaWriteCounter);
if(!s.IsSaving()) {
_needExec = true;
}
}