DSP: Convert accelerator to a C++ class

Slightly cleaner, allows DSP accelerator behaviour to be
added to both HLE and LLE pretty easily, and makes the accelerator
easier to unit test.

I chose to include all accelerator state as private members, and
to expose state that is accessible via registers with getters/setters.
It's more verbose, yes, but it makes it very clear what is part of
the accelerator state and what isn't (e.g. coefs).

This works quite well for registers, since the accelerator can do
whatever it wants internally. For example, the start/end/current
addresses are masked -- having a getter/setter makes it easier to
enforce the mask.
This commit is contained in:
Léo Lam 2017-09-20 10:52:57 +02:00
parent 017bfcda2b
commit 003dba5275
7 changed files with 256 additions and 143 deletions

View File

@ -4,76 +4,61 @@
#include "Core/DSP/DSPAccelerator.h"
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/MathUtil.h"
#include "Core/DSP/DSPCore.h"
#include "Core/DSP/DSPHWInterface.h"
#include "Core/DSP/DSPHost.h"
namespace DSP
{
u16 dsp_read_aram_d3()
u16 Accelerator::ReadD3()
{
// Zelda ucode reads ARAM through 0xffd3.
const u32 EndAddress = (g_dsp.ifx_regs[DSP_ACEAH] << 16) | g_dsp.ifx_regs[DSP_ACEAL];
u32 Address = (g_dsp.ifx_regs[DSP_ACCAH] << 16) | g_dsp.ifx_regs[DSP_ACCAL];
u16 val = 0;
switch (g_dsp.ifx_regs[DSP_FORMAT])
switch (m_sample_format)
{
case 0x5: // u8 reads
val = Host::ReadHostMemory(Address);
Address++;
val = ReadMemory(m_current_address);
m_current_address++;
break;
case 0x6: // u16 reads
val = (Host::ReadHostMemory(Address * 2) << 8) | Host::ReadHostMemory(Address * 2 + 1);
Address++;
val = (ReadMemory(m_current_address * 2) << 8) | ReadMemory(m_current_address * 2 + 1);
m_current_address++;
break;
default:
ERROR_LOG(DSPLLE, "dsp_read_aram_d3() - unknown format 0x%x", g_dsp.ifx_regs[DSP_FORMAT]);
ERROR_LOG(DSPLLE, "dsp_read_aram_d3() - unknown format 0x%x", m_sample_format);
break;
}
if (Address >= EndAddress)
if (m_current_address >= m_end_address)
{
// Set address back to start address. (never seen this here!)
Address = (g_dsp.ifx_regs[DSP_ACSAH] << 16) | g_dsp.ifx_regs[DSP_ACSAL];
m_current_address = m_start_address;
}
g_dsp.ifx_regs[DSP_ACCAH] = Address >> 16;
g_dsp.ifx_regs[DSP_ACCAL] = Address & 0xffff;
return val;
}
void dsp_write_aram_d3(u16 value)
void Accelerator::WriteD3(u16 value)
{
// Zelda ucode writes a bunch of zeros to ARAM through d3 during
// initialization. Don't know if it ever does it later, too.
// Pikmin 2 Wii writes non-stop to 0x10008000-0x1000801f (non-zero values too)
// Zelda TP Wii writes non-stop to 0x10000000-0x1000001f (non-zero values too)
u32 Address = (g_dsp.ifx_regs[DSP_ACCAH] << 16) | g_dsp.ifx_regs[DSP_ACCAL];
switch (g_dsp.ifx_regs[DSP_FORMAT])
switch (m_sample_format)
{
case 0xA: // u16 writes
Host::WriteHostMemory(value >> 8, Address * 2);
Host::WriteHostMemory(value & 0xFF, Address * 2 + 1);
Address++;
WriteMemory(m_current_address * 2, value >> 8);
WriteMemory(m_current_address * 2 + 1, value & 0xFF);
m_current_address++;
break;
default:
ERROR_LOG(DSPLLE, "dsp_write_aram_d3() - unknown format 0x%x", g_dsp.ifx_regs[DSP_FORMAT]);
ERROR_LOG(DSPLLE, "dsp_write_aram_d3() - unknown format 0x%x", m_sample_format);
break;
}
g_dsp.ifx_regs[DSP_ACCAH] = Address >> 16;
g_dsp.ifx_regs[DSP_ACCAL] = Address & 0xffff;
}
u16 ReadAccelerator(u32 start_address, u32 end_address, u32* current_address, u16 sample_format,
s16* yn1, s16* yn2, u16* pred_scale, s16* coefs,
std::function<void()> end_exception)
u16 Accelerator::Read(s16* coefs)
{
u16 val;
u8 step_size_bytes = 0;
@ -84,18 +69,18 @@ u16 ReadAccelerator(u32 start_address, u32 end_address, u32* current_address, u1
// extension and do/do not use ADPCM. It also remains to be figured out
// whether there's a difference between the usual accelerator "read
// address" and 0xd3.
switch (sample_format)
switch (m_sample_format)
{
case 0x00: // ADPCM audio
{
// ADPCM decoding, not much to explain here.
if ((*current_address & 15) == 0)
if ((m_current_address & 15) == 0)
{
*pred_scale = Host::ReadHostMemory((*current_address & ~15) >> 1);
*current_address += 2;
m_pred_scale = ReadMemory((m_current_address & ~15) >> 1);
m_current_address += 2;
}
switch (end_address & 15)
switch (m_end_address & 15)
{
case 0: // Tom and Jerry
step_size_bytes = 1;
@ -108,45 +93,44 @@ u16 ReadAccelerator(u32 start_address, u32 end_address, u32* current_address, u1
break;
}
int scale = 1 << (*pred_scale & 0xF);
int coef_idx = (*pred_scale >> 4) & 0x7;
int scale = 1 << (m_pred_scale & 0xF);
int coef_idx = (m_pred_scale >> 4) & 0x7;
s32 coef1 = coefs[coef_idx * 2 + 0];
s32 coef2 = coefs[coef_idx * 2 + 1];
int temp = (*current_address & 1) ? (Host::ReadHostMemory(*current_address >> 1) & 0xF) :
(Host::ReadHostMemory(*current_address >> 1) >> 4);
int temp = (m_current_address & 1) ? (ReadMemory(m_current_address >> 1) & 0xF) :
(ReadMemory(m_current_address >> 1) >> 4);
if (temp >= 8)
temp -= 16;
s32 val32 = (scale * temp) + ((0x400 + coef1 * *yn1 + coef2 * *yn2) >> 11);
s32 val32 = (scale * temp) + ((0x400 + coef1 * m_yn1 + coef2 * m_yn2) >> 11);
val = static_cast<s16>(MathUtil::Clamp<s32>(val32, -0x7FFF, 0x7FFF));
*yn2 = *yn1;
*yn1 = val;
*current_address += 1;
m_yn2 = m_yn1;
m_yn1 = val;
m_current_address += 1;
break;
}
case 0x0A: // 16-bit PCM audio
val = (Host::ReadHostMemory(*current_address * 2) << 8) |
Host::ReadHostMemory(*current_address * 2 + 1);
*yn2 = *yn1;
*yn1 = val;
val = (ReadMemory(m_current_address * 2) << 8) | ReadMemory(m_current_address * 2 + 1);
m_yn2 = m_yn1;
m_yn1 = val;
step_size_bytes = 2;
*current_address += 1;
m_current_address += 1;
break;
case 0x19: // 8-bit PCM audio
val = Host::ReadHostMemory(*current_address) << 8;
*yn2 = *yn1;
*yn1 = val;
val = ReadMemory(m_current_address) << 8;
m_yn2 = m_yn1;
m_yn1 = val;
step_size_bytes = 2;
*current_address += 1;
m_current_address += 1;
break;
default:
ERROR_LOG(DSPLLE, "dsp_read_accelerator() - unknown format 0x%x", g_dsp.ifx_regs[DSP_FORMAT]);
ERROR_LOG(DSPLLE, "dsp_read_accelerator() - unknown format 0x%x", m_sample_format);
step_size_bytes = 2;
*current_address += 1;
m_current_address += 1;
val = 0;
break;
}
@ -160,30 +144,63 @@ u16 ReadAccelerator(u32 start_address, u32 end_address, u32* current_address, u1
// Somehow, YN1 and YN2 must be initialized with their "loop" values,
// so yeah, it seems likely that we should raise an exception to let
// the DSP program do that, at least if DSP_FORMAT == 0x0A.
if (*current_address == (end_address + step_size_bytes - 1))
if (m_current_address == (m_end_address + step_size_bytes - 1))
{
// Set address back to start address.
*current_address = start_address;
end_exception();
m_current_address = m_start_address;
OnEndException();
}
SetCurrentAddress(m_current_address);
return val;
}
u16 dsp_read_accelerator()
void Accelerator::DoState(PointerWrap& p)
{
const u32 start_address = (g_dsp.ifx_regs[DSP_ACSAH] << 16) | g_dsp.ifx_regs[DSP_ACSAL];
const u32 end_address = (g_dsp.ifx_regs[DSP_ACEAH] << 16) | g_dsp.ifx_regs[DSP_ACEAL];
u32 current_address = (g_dsp.ifx_regs[DSP_ACCAH] << 16) | g_dsp.ifx_regs[DSP_ACCAL];
p.Do(m_start_address);
p.Do(m_end_address);
p.Do(m_current_address);
p.Do(m_sample_format);
p.Do(m_yn1);
p.Do(m_yn2);
p.Do(m_pred_scale);
}
auto end_address_reached = [] { DSPCore_SetException(EXP_ACCOV); };
const u16 val = ReadAccelerator(
start_address, end_address, &current_address, g_dsp.ifx_regs[DSP_FORMAT],
reinterpret_cast<s16*>(&g_dsp.ifx_regs[DSP_YN1]),
reinterpret_cast<s16*>(&g_dsp.ifx_regs[DSP_YN2]), &g_dsp.ifx_regs[DSP_PRED_SCALE],
reinterpret_cast<s16*>(&g_dsp.ifx_regs[DSP_COEF_A1_0]), end_address_reached);
constexpr u32 START_END_ADDRESS_MASK = 0x3fffffff;
constexpr u32 CURRENT_ADDRESS_MASK = 0xbfffffff;
gdsp_ifx_write(DSP_ACCAH, current_address >> 16);
gdsp_ifx_write(DSP_ACCAL, current_address & 0xffff);
return val;
void Accelerator::SetStartAddress(u32 address)
{
m_start_address = address & START_END_ADDRESS_MASK;
}
void Accelerator::SetEndAddress(u32 address)
{
m_end_address = address & START_END_ADDRESS_MASK;
}
void Accelerator::SetCurrentAddress(u32 address)
{
m_current_address = address & CURRENT_ADDRESS_MASK;
}
void Accelerator::SetSampleFormat(u16 format)
{
m_sample_format = format;
}
void Accelerator::SetYn1(s16 yn1)
{
m_yn1 = yn1;
}
void Accelerator::SetYn2(s16 yn2)
{
m_yn2 = yn2;
}
void Accelerator::SetPredScale(u16 pred_scale)
{
m_pred_scale = pred_scale;
}
} // namespace DSP

View File

@ -4,18 +4,51 @@
#pragma once
#include <functional>
#include "Common/CommonTypes.h"
class PointerWrap;
namespace DSP
{
u16 ReadAccelerator(u32 start_address, u32 end_address, u32* current_address, u16 sample_format,
s16* yn1, s16* yn2, u16* pred_scale, s16* coefs,
std::function<void()> end_exception);
class Accelerator
{
public:
virtual ~Accelerator() = default;
u16 dsp_read_accelerator();
u16 Read(s16* coefs);
// Zelda ucode reads ARAM through 0xffd3.
u16 ReadD3();
void WriteD3(u16 value);
u16 dsp_read_aram_d3();
void dsp_write_aram_d3(u16 value);
u32 GetStartAddress() const { return m_start_address; }
u32 GetEndAddress() const { return m_end_address; }
u32 GetCurrentAddress() const { return m_current_address; }
u16 GetSampleFormat() const { return m_sample_format; }
s16 GetYn1() const { return m_yn1; }
s16 GetYn2() const { return m_yn2; }
u16 GetPredScale() const { return m_pred_scale; }
void SetStartAddress(u32 address);
void SetEndAddress(u32 address);
void SetCurrentAddress(u32 address);
void SetSampleFormat(u16 format);
void SetYn1(s16 yn1);
void SetYn2(s16 yn2);
void SetPredScale(u16 pred_scale);
void DoState(PointerWrap& p);
protected:
virtual void OnEndException() = 0;
virtual u8 ReadMemory(u32 address) = 0;
virtual void WriteMemory(u32 address, u8 value) = 0;
// DSP accelerator registers.
u32 m_start_address = 0;
u32 m_end_address = 0;
u32 m_current_address = 0;
u16 m_sample_format = 0;
s16 m_yn1 = 0;
s16 m_yn2 = 0;
u16 m_pred_scale = 0;
};
} // namespace DSP

View File

@ -15,12 +15,14 @@
#include "Common/MemoryUtil.h"
#include "Common/MsgHandler.h"
#include "Core/DSP/DSPAccelerator.h"
#include "Core/DSP/DSPAnalyzer.h"
#include "Core/DSP/DSPHWInterface.h"
#include "Core/DSP/DSPHost.h"
#include "Core/DSP/Interpreter/DSPIntUtil.h"
#include "Core/DSP/Interpreter/DSPInterpreter.h"
#include "Core/DSP/Jit/DSPEmitter.h"
#include "Core/HW/DSP.h"
namespace DSP
{
@ -111,11 +113,21 @@ static void DSPCore_FreeMemoryPages()
g_dsp.irom = g_dsp.iram = g_dsp.dram = g_dsp.coef = nullptr;
}
class LLEAccelerator final : public Accelerator
{
protected:
u8 ReadMemory(u32 address) override { return Host::ReadHostMemory(address); }
void WriteMemory(u32 address, u8 value) override { Host::WriteHostMemory(value, address); }
void OnEndException() override { DSPCore_SetException(EXP_ACCOV); }
};
bool DSPCore_Init(const DSPInitOptions& opts)
{
g_dsp.step_counter = 0;
g_init_hax = false;
g_dsp.accelerator = std::make_unique<LLEAccelerator>();
g_dsp.irom = static_cast<u16*>(Common::AllocateMemoryPages(DSP_IROM_BYTE_SIZE));
g_dsp.iram = static_cast<u16*>(Common::AllocateMemoryPages(DSP_IRAM_BYTE_SIZE));
g_dsp.dram = static_cast<u16*>(Common::AllocateMemoryPages(DSP_DRAM_BYTE_SIZE));

View File

@ -16,6 +16,8 @@
namespace DSP
{
class Accelerator;
namespace JIT
{
namespace x86
@ -299,6 +301,8 @@ struct SDSP
// Accelerator / DMA / other hardware registers. Not GPRs.
std::array<u16, 256> ifx_regs;
std::unique_ptr<Accelerator> accelerator;
// When state saving, all of the above can just be memcpy'd into the save state.
// The below needs special handling.
u16* iram;

View File

@ -136,10 +136,6 @@ void gdsp_ifx_write(u32 addr, u16 val)
g_dsp.ifx_regs[DSP_DSBL] = 0;
break;
case DSP_ACDATA1: // Accelerator write (Zelda type) - "UnkZelda"
dsp_write_aram_d3(val);
break;
case DSP_GAIN:
if (val)
{
@ -151,21 +147,45 @@ void gdsp_ifx_write(u32 addr, u16 val)
case DSP_DSCR:
g_dsp.ifx_regs[addr & 0xFF] = val;
break;
/*
case DSP_ACCAL:
dsp_step_accelerator();
break;
*/
// Masking occurs for the start and end addresses as soon as the registers are written to.
case DSP_ACSAH:
case DSP_ACEAH:
g_dsp.ifx_regs[addr & 0xff] = val & 0x3fff;
g_dsp.accelerator->SetStartAddress(val << 16 |
static_cast<u16>(g_dsp.accelerator->GetStartAddress()));
break;
case DSP_ACSAL:
g_dsp.accelerator->SetStartAddress(
static_cast<u16>(g_dsp.accelerator->GetStartAddress() >> 16) << 16 | val);
break;
case DSP_ACEAH:
g_dsp.accelerator->SetEndAddress(val << 16 |
static_cast<u16>(g_dsp.accelerator->GetEndAddress()));
break;
case DSP_ACEAL:
g_dsp.accelerator->SetEndAddress(
static_cast<u16>(g_dsp.accelerator->GetEndAddress() >> 16) << 16 | val);
break;
// This also happens for the current address, but with a different mask.
case DSP_ACCAH:
g_dsp.ifx_regs[addr & 0xff] = val & 0xbfff;
g_dsp.accelerator->SetCurrentAddress(val << 16 |
static_cast<u16>(g_dsp.accelerator->GetCurrentAddress()));
break;
case DSP_ACCAL:
g_dsp.accelerator->SetCurrentAddress(
static_cast<u16>(g_dsp.accelerator->GetCurrentAddress() >> 16) << 16 | val);
break;
case DSP_FORMAT:
g_dsp.accelerator->SetSampleFormat(val);
break;
case DSP_YN1:
g_dsp.accelerator->SetYn1(val);
break;
case DSP_YN2:
g_dsp.accelerator->SetYn2(val);
break;
case DSP_PRED_SCALE:
g_dsp.accelerator->SetPredScale(val);
break;
case DSP_ACDATA1: // Accelerator write (Zelda type) - "UnkZelda"
g_dsp.accelerator->WriteD3(val);
break;
default:
@ -208,11 +228,30 @@ static u16 _gdsp_ifx_read(u16 addr)
case DSP_DSCR:
return g_dsp.ifx_regs[addr & 0xFF];
case DSP_ACSAH:
return static_cast<u16>(g_dsp.accelerator->GetStartAddress() >> 16);
case DSP_ACSAL:
return static_cast<u16>(g_dsp.accelerator->GetStartAddress());
case DSP_ACEAH:
return static_cast<u16>(g_dsp.accelerator->GetEndAddress() >> 16);
case DSP_ACEAL:
return static_cast<u16>(g_dsp.accelerator->GetEndAddress());
case DSP_ACCAH:
return static_cast<u16>(g_dsp.accelerator->GetCurrentAddress() >> 16);
case DSP_ACCAL:
return static_cast<u16>(g_dsp.accelerator->GetCurrentAddress());
case DSP_FORMAT:
return g_dsp.accelerator->GetSampleFormat();
case DSP_YN1:
return g_dsp.accelerator->GetYn1();
case DSP_YN2:
return g_dsp.accelerator->GetYn2();
case DSP_PRED_SCALE:
return g_dsp.accelerator->GetPredScale();
case DSP_ACCELERATOR: // ADPCM Accelerator reads
return dsp_read_accelerator();
return g_dsp.accelerator->Read(reinterpret_cast<s16*>(&g_dsp.ifx_regs[DSP_COEF_A1_0]));
case DSP_ACDATA1: // Accelerator reads (Zelda type) - "UnkZelda"
return dsp_read_aram_d3();
return g_dsp.accelerator->ReadD3();
default:
if ((addr & 0xff) >= 0xa0)

View File

@ -13,6 +13,7 @@
#endif
#include <functional>
#include <memory>
#include "Common/CommonTypes.h"
#include "Common/MathUtil.h"
@ -163,58 +164,34 @@ void DumpPB(const PB_TYPE& pb)
#endif
// Simulated accelerator state.
static u32 acc_loop_addr, acc_end_addr;
static u32* acc_cur_addr;
static PB_TYPE* acc_pb;
static bool acc_end_reached;
// Sets up the simulated accelerator.
void AcceleratorSetup(PB_TYPE* pb, u32* cur_addr)
class HLEAccelerator final : public Accelerator
{
acc_pb = pb;
// Masking occurs for the start and end addresses as soon as the registers are written to.
acc_loop_addr = HILO_TO_32(pb->audio_addr.loop_addr) & 0x3fffffff;
acc_end_addr = HILO_TO_32(pb->audio_addr.end_addr) & 0x3fffffff;
acc_cur_addr = cur_addr;
// It also happens for the current address, but with a different mask.
*acc_cur_addr &= 0xbfffffff;
acc_end_reached = false;
}
// Reads a sample from the accelerator. Also handles looping and
// disabling streams that reached the end (this is done by an exception raised
// by the accelerator on real hardware).
u16 AcceleratorGetSample()
protected:
void OnEndException() override
{
// See below for explanations about acc_end_reached.
if (acc_end_reached)
return 0;
auto end_address_reached = [] {
// loop back to loop_addr.
*acc_cur_addr = acc_loop_addr;
if (acc_pb->audio_addr.looping)
{
// Set the ADPCM info to continue processing at loop_addr.
//
// For some reason, yn1 and yn2 aren't set if the voice is not of
// stream type. This is what the AX UCode does and I don't really
// know why.
acc_pb->adpcm.pred_scale = acc_pb->adpcm_loop_info.pred_scale;
SetPredScale(acc_pb->adpcm_loop_info.pred_scale);
if (!acc_pb->is_stream)
{
acc_pb->adpcm.yn1 = acc_pb->adpcm_loop_info.yn1;
acc_pb->adpcm.yn2 = acc_pb->adpcm_loop_info.yn2;
SetYn1(acc_pb->adpcm_loop_info.yn1);
SetYn2(acc_pb->adpcm_loop_info.yn2);
}
#ifdef AX_GC
else
{
// Refresh YN1 and YN2. This indirectly causes the accelerator to resume reads.
SetYn1(GetYn1());
SetYn2(GetYn2());
#ifdef AX_GC
// If we're streaming, increment the loop counter.
acc_pb->loop_counter++;
}
#endif
}
}
else
{
// Non looping voice reached the end -> running = 0.
@ -229,11 +206,38 @@ u16 AcceleratorGetSample()
acc_end_reached = true;
#endif
}
}
u8 ReadMemory(u32 address) override { return ReadARAM(address); }
void WriteMemory(u32 address, u8 value) override { WriteARAM(value, address); }
};
return ReadAccelerator(acc_loop_addr, acc_end_addr, acc_cur_addr,
acc_pb->audio_addr.sample_format, &acc_pb->adpcm.yn1, &acc_pb->adpcm.yn2,
&acc_pb->adpcm.pred_scale, acc_pb->adpcm.coefs, end_address_reached);
static std::unique_ptr<Accelerator> s_accelerator = std::make_unique<HLEAccelerator>();
// Sets up the simulated accelerator.
void AcceleratorSetup(PB_TYPE* pb)
{
acc_pb = pb;
s_accelerator->SetStartAddress(HILO_TO_32(pb->audio_addr.loop_addr));
s_accelerator->SetEndAddress(HILO_TO_32(pb->audio_addr.end_addr));
s_accelerator->SetCurrentAddress(HILO_TO_32(pb->audio_addr.cur_addr));
s_accelerator->SetSampleFormat(pb->audio_addr.sample_format);
s_accelerator->SetYn1(pb->adpcm.yn1);
s_accelerator->SetYn2(pb->adpcm.yn2);
s_accelerator->SetPredScale(pb->adpcm.pred_scale);
acc_end_reached = false;
}
// Reads a sample from the accelerator. Also handles looping and
// disabling streams that reached the end (this is done by an exception raised
// by the accelerator on real hardware).
u16 AcceleratorGetSample()
{
// See below for explanations about acc_end_reached.
if (acc_end_reached)
return 0;
return s_accelerator->Read(acc_pb->adpcm.coefs);
}
// Reads samples from the input callback, resamples them to <count> samples at
@ -375,8 +379,7 @@ u32 ResampleAudio(std::function<s16(u32)> input_callback, s16* output, u32 count
// if required.
void GetInputSamples(PB_TYPE& pb, s16* samples, u16 count, const s16* coeffs)
{
u32 cur_addr = HILO_TO_32(pb.audio_addr.cur_addr);
AcceleratorSetup(&pb, &cur_addr);
AcceleratorSetup(&pb);
if (coeffs)
coeffs += pb.coef_select * 0x200;
@ -385,9 +388,12 @@ void GetInputSamples(PB_TYPE& pb, s16* samples, u16 count, const s16* coeffs)
pb.src.cur_addr_frac, HILO_TO_32(pb.src.ratio), pb.src_type, coeffs);
pb.src.cur_addr_frac = (curr_pos & 0xFFFF);
// Update current position in the PB.
pb.audio_addr.cur_addr_hi = static_cast<u16>(cur_addr >> 16) & 0xbfff;
pb.audio_addr.cur_addr_lo = static_cast<u16>(cur_addr);
// Update current position, YN1, YN2 and pred scale in the PB.
pb.audio_addr.cur_addr_hi = static_cast<u16>(s_accelerator->GetCurrentAddress() >> 16);
pb.audio_addr.cur_addr_lo = static_cast<u16>(s_accelerator->GetCurrentAddress());
pb.adpcm.yn1 = s_accelerator->GetYn1();
pb.adpcm.yn2 = s_accelerator->GetYn2();
pb.adpcm.pred_scale = s_accelerator->GetPredScale();
}
// Add samples to an output buffer, with optional volume ramping.

View File

@ -17,6 +17,7 @@
#include "Common/Thread.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/DSP/DSPAccelerator.h"
#include "Core/DSP/DSPCaptureLogger.h"
#include "Core/DSP/DSPCore.h"
#include "Core/DSP/DSPHWInterface.h"
@ -71,6 +72,7 @@ void DSPLLE::DoState(PointerWrap& p)
p.Do(g_dsp.step_counter);
p.DoArray(g_dsp.ifx_regs);
g_dsp.accelerator->DoState(p);
p.Do(g_dsp.mbox[0]);
p.Do(g_dsp.mbox[1]);
Common::UnWriteProtectMemory(g_dsp.iram, DSP_IRAM_BYTE_SIZE, false);