mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 13:55:38 +00:00
Further work on SPU implementation
This commit is contained in:
parent
bc51cc6d7d
commit
f852b8dd90
|
@ -1,6 +1,7 @@
|
|||
#include "host_interface.h"
|
||||
#include "YBaseLib/ByteStream.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "common/audio_stream.h"
|
||||
#include "system.h"
|
||||
Log_SetChannel(HostInterface);
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
#include "types.h"
|
||||
#include <memory>
|
||||
|
||||
class AudioStream;
|
||||
|
||||
namespace GL {
|
||||
class Texture;
|
||||
}
|
||||
|
@ -14,6 +16,8 @@ public:
|
|||
HostInterface();
|
||||
virtual ~HostInterface();
|
||||
|
||||
AudioStream* GetAudioStream() const { return m_audio_stream.get(); }
|
||||
|
||||
bool InitializeSystem(const char* filename, const char* exp1_filename);
|
||||
|
||||
virtual void SetDisplayTexture(GL::Texture* texture, u32 offset_x, u32 offset_y, u32 width, u32 height, float aspect_ratio) = 0;
|
||||
|
@ -26,6 +30,8 @@ public:
|
|||
bool SaveState(const char* filename);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<AudioStream> m_audio_stream;
|
||||
|
||||
std::unique_ptr<System> m_system;
|
||||
bool m_running = false;
|
||||
};
|
||||
|
|
520
src/core/spu.cpp
520
src/core/spu.cpp
|
@ -1,17 +1,26 @@
|
|||
#include "spu.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "common/audio_stream.h"
|
||||
#include "common/state_wrapper.h"
|
||||
#include "dma.h"
|
||||
#include "host_interface.h"
|
||||
#include "interrupt_controller.h"
|
||||
#include "system.h"
|
||||
#include <imgui.h>
|
||||
Log_SetChannel(SPU);
|
||||
|
||||
static s16 Clamp16(s32 value)
|
||||
{
|
||||
return static_cast<s16>(std::clamp<s32>(value, -32768, 32767));
|
||||
}
|
||||
|
||||
SPU::SPU() = default;
|
||||
|
||||
SPU::~SPU() = default;
|
||||
|
||||
bool SPU::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller)
|
||||
{
|
||||
m_audio_stream = system->GetHostInterface()->GetAudioStream();
|
||||
m_system = system;
|
||||
m_dma = dma;
|
||||
m_interrupt_controller = interrupt_controller;
|
||||
|
@ -37,6 +46,9 @@ bool SPU::DoState(StateWrapper& sw)
|
|||
|
||||
u16 SPU::ReadRegister(u32 offset)
|
||||
{
|
||||
if (offset < (0x1F801D80 - SPU_BASE))
|
||||
return ReadVoiceRegister(offset);
|
||||
|
||||
switch (offset)
|
||||
{
|
||||
case 0x1F801DA6 - SPU_BASE:
|
||||
|
@ -52,9 +64,21 @@ u16 SPU::ReadRegister(u32 offset)
|
|||
return m_SPUCNT.bits;
|
||||
|
||||
case 0x1F801DAE - SPU_BASE:
|
||||
Log_DebugPrintf("SPU status register -> 0x%04X", ZeroExtend32(m_SPUCNT.bits));
|
||||
// Log_DebugPrintf("SPU status register -> 0x%04X", ZeroExtend32(m_SPUCNT.bits));
|
||||
return m_SPUSTAT.bits;
|
||||
|
||||
case 0x1F801D88 - SPU_BASE:
|
||||
return Truncate16(m_key_on_register);
|
||||
|
||||
case 0x1F801D8A - SPU_BASE:
|
||||
return Truncate16(m_key_on_register >> 16);
|
||||
|
||||
case 0x1F801D8C - SPU_BASE:
|
||||
return Truncate16(m_key_off_register);
|
||||
|
||||
case 0x1F801D8E - SPU_BASE:
|
||||
return Truncate16(m_key_off_register >> 16);
|
||||
|
||||
default:
|
||||
Log_ErrorPrintf("Unknown SPU register read: offset 0x%X (address 0x%08X)", offset, offset | SPU_BASE);
|
||||
return UINT16_C(0xFFFF);
|
||||
|
@ -63,13 +87,19 @@ u16 SPU::ReadRegister(u32 offset)
|
|||
|
||||
void SPU::WriteRegister(u32 offset, u16 value)
|
||||
{
|
||||
if (offset < (0x1F801D80 - SPU_BASE))
|
||||
{
|
||||
WriteVoiceRegister(offset, value);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (offset)
|
||||
{
|
||||
case 0x1F801DA6 - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU transfer address register <- 0x%04X", ZeroExtend32(value));
|
||||
m_transfer_address_reg = value;
|
||||
m_transfer_address = ZeroExtend32(value) * 8;
|
||||
m_transfer_address = (ZeroExtend32(value) << VOICE_ADDRESS_SHIFT) & RAM_MASK;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -89,6 +119,82 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
return;
|
||||
}
|
||||
|
||||
case 0x1F801D88 - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU key on low <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_key_on_register = (m_key_on_register & 0xFFFF0000) | ZeroExtend32(value);
|
||||
|
||||
u16 bits = value;
|
||||
for (u32 i = 0; i < 16; i++)
|
||||
{
|
||||
if (bits & 0x01)
|
||||
{
|
||||
Log_DebugPrintf("Voice %u key on", i);
|
||||
m_voices[i].KeyOn();
|
||||
}
|
||||
bits >>= 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1F801D8A - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU key on high <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_key_on_register = (m_key_on_register & 0x0000FFFF) | (ZeroExtend32(value) << 16);
|
||||
|
||||
u16 bits = value;
|
||||
for (u32 i = 16; i < NUM_VOICES; i++)
|
||||
{
|
||||
if (bits & 0x01)
|
||||
{
|
||||
Log_DebugPrintf("Voice %u key on", i);
|
||||
m_voices[i].KeyOn();
|
||||
}
|
||||
bits >>= 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1F801D8C - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU key off low <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_key_on_register = (m_key_on_register & 0xFFFF0000) | ZeroExtend32(value);
|
||||
|
||||
u16 bits = value;
|
||||
for (u32 i = 0; i < 16; i++)
|
||||
{
|
||||
if (bits & 0x01)
|
||||
{
|
||||
Log_DebugPrintf("Voice %u key off", i);
|
||||
m_voices[i].KeyOff();
|
||||
}
|
||||
bits >>= 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1F801D8E - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU key off high <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_key_on_register = (m_key_on_register & 0x0000FFFF) | (ZeroExtend32(value) << 16);
|
||||
|
||||
u16 bits = value;
|
||||
for (u32 i = 16; i < NUM_VOICES; i++)
|
||||
{
|
||||
if (bits & 0x01)
|
||||
{
|
||||
Log_DebugPrintf("Voice %u key off", i);
|
||||
m_voices[i].KeyOff();
|
||||
}
|
||||
bits >>= 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// read-only registers
|
||||
case 0x1F801DAE - SPU_BASE:
|
||||
{
|
||||
|
@ -104,6 +210,89 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
}
|
||||
}
|
||||
|
||||
u16 SPU::ReadVoiceRegister(u32 offset)
|
||||
{
|
||||
const u32 reg_index = (offset & 0x0F) / 2;
|
||||
const u32 voice_index = ((offset >> 4) & 0x1F);
|
||||
Assert(voice_index < 24);
|
||||
|
||||
return m_voices[voice_index].regs.index[reg_index];
|
||||
}
|
||||
|
||||
void SPU::WriteVoiceRegister(u32 offset, u16 value)
|
||||
{
|
||||
// per-voice registers
|
||||
const u32 reg_index = (offset & 0x0F);
|
||||
const u32 voice_index = ((offset >> 4) & 0x1F);
|
||||
Assert(voice_index < 24);
|
||||
|
||||
switch (reg_index)
|
||||
{
|
||||
case 0x00: // volume left
|
||||
{
|
||||
Log_DebugPrintf("SPU voice %u volume left <- 0x%04X", voice_index, value);
|
||||
m_voices[voice_index].regs.volume_left.bits = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x02: // volume right
|
||||
{
|
||||
Log_DebugPrintf("SPU voice %u volume right <- 0x%04X", voice_index, value);
|
||||
m_voices[voice_index].regs.volume_right.bits = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x04: // sample rate
|
||||
{
|
||||
Log_DebugPrintf("SPU voice %u ADPCM sample rate <- 0x%04X", voice_index, value);
|
||||
m_voices[voice_index].regs.adpcm_sample_rate = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x06: // start address
|
||||
{
|
||||
Log_DebugPrintf("SPU voice %u ADPCM start address <- 0x%04X", voice_index, value);
|
||||
m_voices[voice_index].regs.adpcm_start_address = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x08: // adsr low
|
||||
{
|
||||
Log_WarningPrintf("SPU voice %u ADSR low <- 0x%04X", voice_index, value);
|
||||
m_voices[voice_index].regs.adsr.bits_low = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0A: // adsr high
|
||||
{
|
||||
Log_WarningPrintf("SPU voice %u ADSR high <- 0x%04X", voice_index, value);
|
||||
m_voices[voice_index].regs.adsr.bits_high = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0C: // adsr volume
|
||||
{
|
||||
Log_DebugPrintf("SPU voice %u ADSR volume <- 0x%04X", voice_index, value);
|
||||
m_voices[voice_index].regs.adsr_volume = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0E: // repeat address
|
||||
{
|
||||
Log_DebugPrintf("SPU voice %u ADPCM repeat address <- 0x%04X", voice_index, value);
|
||||
m_voices[voice_index].regs.adpcm_repeat_address = value;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
Log_ErrorPrintf("Unknown SPU voice %u register write: offset 0x%X (address 0x%08X) value 0x%04X", offset,
|
||||
voice_index, offset | SPU_BASE, ZeroExtend32(value));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u32 SPU::DMARead()
|
||||
{
|
||||
const u16 lsb = RAMTransferRead();
|
||||
|
@ -135,6 +324,333 @@ u16 SPU::RAMTransferRead()
|
|||
|
||||
void SPU::RAMTransferWrite(u16 value)
|
||||
{
|
||||
Log_TracePrintf("SPU RAM @ 0x%08X (voice 0x%04X) <- 0x%04X", m_transfer_address,
|
||||
m_transfer_address >> VOICE_ADDRESS_SHIFT, ZeroExtend32(value));
|
||||
std::memcpy(&m_ram[m_transfer_address], &value, sizeof(value));
|
||||
m_transfer_address = (m_transfer_address + sizeof(value)) & RAM_MASK;
|
||||
}
|
||||
|
||||
void SPU::Execute(TickCount ticks)
|
||||
{
|
||||
TickCount num_samples = (ticks + m_ticks_carry) / SYSCLK_TICKS_PER_SPU_TICK;
|
||||
m_ticks_carry = (ticks + m_ticks_carry) % SYSCLK_TICKS_PER_SPU_TICK;
|
||||
if (num_samples == 0 || !m_SPUCNT.enable)
|
||||
return;
|
||||
|
||||
for (TickCount i = 0; i < num_samples; i++)
|
||||
GenerateSample();
|
||||
}
|
||||
|
||||
void SPU::Voice::KeyOn()
|
||||
{
|
||||
current_address = regs.adpcm_start_address;
|
||||
has_samples = false;
|
||||
key_on = true;
|
||||
}
|
||||
|
||||
void SPU::Voice::KeyOff()
|
||||
{
|
||||
has_samples = false;
|
||||
key_on = false;
|
||||
}
|
||||
|
||||
void SPU::Voice::DecodeBlock()
|
||||
{
|
||||
previous_block_last_samples[2] = current_block_samples[NUM_SAMPLES_PER_ADPCM_BLOCK - 1];
|
||||
previous_block_last_samples[1] = current_block_samples[NUM_SAMPLES_PER_ADPCM_BLOCK - 2];
|
||||
previous_block_last_samples[0] = current_block_samples[NUM_SAMPLES_PER_ADPCM_BLOCK - 3];
|
||||
DecodeADPCMBlock(current_block, current_block_samples.data(), adpcm_state.data());
|
||||
}
|
||||
|
||||
SPU::SampleFormat SPU::Voice::SampleBlock(s32 index) const
|
||||
{
|
||||
if (index < 0)
|
||||
{
|
||||
DebugAssert(index >= -3);
|
||||
return previous_block_last_samples[index + 3];
|
||||
}
|
||||
|
||||
return current_block_samples[index];
|
||||
}
|
||||
|
||||
s32 SPU::Voice::Interpolate() const
|
||||
{
|
||||
static constexpr std::array<s32, 0x200> gauss = {{
|
||||
-0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, //
|
||||
-0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, //
|
||||
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, //
|
||||
0x0001, 0x0001, 0x0001, 0x0002, 0x0002, 0x0002, 0x0003, 0x0003, //
|
||||
0x0003, 0x0004, 0x0004, 0x0005, 0x0005, 0x0006, 0x0007, 0x0007, //
|
||||
0x0008, 0x0009, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, //
|
||||
0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0015, 0x0016, 0x0018, // entry
|
||||
0x0019, 0x001B, 0x001C, 0x001E, 0x0020, 0x0021, 0x0023, 0x0025, // 000..07F
|
||||
0x0027, 0x0029, 0x002C, 0x002E, 0x0030, 0x0033, 0x0035, 0x0038, //
|
||||
0x003A, 0x003D, 0x0040, 0x0043, 0x0046, 0x0049, 0x004D, 0x0050, //
|
||||
0x0054, 0x0057, 0x005B, 0x005F, 0x0063, 0x0067, 0x006B, 0x006F, //
|
||||
0x0074, 0x0078, 0x007D, 0x0082, 0x0087, 0x008C, 0x0091, 0x0096, //
|
||||
0x009C, 0x00A1, 0x00A7, 0x00AD, 0x00B3, 0x00BA, 0x00C0, 0x00C7, //
|
||||
0x00CD, 0x00D4, 0x00DB, 0x00E3, 0x00EA, 0x00F2, 0x00FA, 0x0101, //
|
||||
0x010A, 0x0112, 0x011B, 0x0123, 0x012C, 0x0135, 0x013F, 0x0148, //
|
||||
0x0152, 0x015C, 0x0166, 0x0171, 0x017B, 0x0186, 0x0191, 0x019C, //
|
||||
0x01A8, 0x01B4, 0x01C0, 0x01CC, 0x01D9, 0x01E5, 0x01F2, 0x0200, //
|
||||
0x020D, 0x021B, 0x0229, 0x0237, 0x0246, 0x0255, 0x0264, 0x0273, //
|
||||
0x0283, 0x0293, 0x02A3, 0x02B4, 0x02C4, 0x02D6, 0x02E7, 0x02F9, //
|
||||
0x030B, 0x031D, 0x0330, 0x0343, 0x0356, 0x036A, 0x037E, 0x0392, //
|
||||
0x03A7, 0x03BC, 0x03D1, 0x03E7, 0x03FC, 0x0413, 0x042A, 0x0441, //
|
||||
0x0458, 0x0470, 0x0488, 0x04A0, 0x04B9, 0x04D2, 0x04EC, 0x0506, //
|
||||
0x0520, 0x053B, 0x0556, 0x0572, 0x058E, 0x05AA, 0x05C7, 0x05E4, // entry
|
||||
0x0601, 0x061F, 0x063E, 0x065C, 0x067C, 0x069B, 0x06BB, 0x06DC, // 080..0FF
|
||||
0x06FD, 0x071E, 0x0740, 0x0762, 0x0784, 0x07A7, 0x07CB, 0x07EF, //
|
||||
0x0813, 0x0838, 0x085D, 0x0883, 0x08A9, 0x08D0, 0x08F7, 0x091E, //
|
||||
0x0946, 0x096F, 0x0998, 0x09C1, 0x09EB, 0x0A16, 0x0A40, 0x0A6C, //
|
||||
0x0A98, 0x0AC4, 0x0AF1, 0x0B1E, 0x0B4C, 0x0B7A, 0x0BA9, 0x0BD8, //
|
||||
0x0C07, 0x0C38, 0x0C68, 0x0C99, 0x0CCB, 0x0CFD, 0x0D30, 0x0D63, //
|
||||
0x0D97, 0x0DCB, 0x0E00, 0x0E35, 0x0E6B, 0x0EA1, 0x0ED7, 0x0F0F, //
|
||||
0x0F46, 0x0F7F, 0x0FB7, 0x0FF1, 0x102A, 0x1065, 0x109F, 0x10DB, //
|
||||
0x1116, 0x1153, 0x118F, 0x11CD, 0x120B, 0x1249, 0x1288, 0x12C7, //
|
||||
0x1307, 0x1347, 0x1388, 0x13C9, 0x140B, 0x144D, 0x1490, 0x14D4, //
|
||||
0x1517, 0x155C, 0x15A0, 0x15E6, 0x162C, 0x1672, 0x16B9, 0x1700, //
|
||||
0x1747, 0x1790, 0x17D8, 0x1821, 0x186B, 0x18B5, 0x1900, 0x194B, //
|
||||
0x1996, 0x19E2, 0x1A2E, 0x1A7B, 0x1AC8, 0x1B16, 0x1B64, 0x1BB3, //
|
||||
0x1C02, 0x1C51, 0x1CA1, 0x1CF1, 0x1D42, 0x1D93, 0x1DE5, 0x1E37, //
|
||||
0x1E89, 0x1EDC, 0x1F2F, 0x1F82, 0x1FD6, 0x202A, 0x207F, 0x20D4, //
|
||||
0x2129, 0x217F, 0x21D5, 0x222C, 0x2282, 0x22DA, 0x2331, 0x2389, // entry
|
||||
0x23E1, 0x2439, 0x2492, 0x24EB, 0x2545, 0x259E, 0x25F8, 0x2653, // 100..17F
|
||||
0x26AD, 0x2708, 0x2763, 0x27BE, 0x281A, 0x2876, 0x28D2, 0x292E, //
|
||||
0x298B, 0x29E7, 0x2A44, 0x2AA1, 0x2AFF, 0x2B5C, 0x2BBA, 0x2C18, //
|
||||
0x2C76, 0x2CD4, 0x2D33, 0x2D91, 0x2DF0, 0x2E4F, 0x2EAE, 0x2F0D, //
|
||||
0x2F6C, 0x2FCC, 0x302B, 0x308B, 0x30EA, 0x314A, 0x31AA, 0x3209, //
|
||||
0x3269, 0x32C9, 0x3329, 0x3389, 0x33E9, 0x3449, 0x34A9, 0x3509, //
|
||||
0x3569, 0x35C9, 0x3629, 0x3689, 0x36E8, 0x3748, 0x37A8, 0x3807, //
|
||||
0x3867, 0x38C6, 0x3926, 0x3985, 0x39E4, 0x3A43, 0x3AA2, 0x3B00, //
|
||||
0x3B5F, 0x3BBD, 0x3C1B, 0x3C79, 0x3CD7, 0x3D35, 0x3D92, 0x3DEF, //
|
||||
0x3E4C, 0x3EA9, 0x3F05, 0x3F62, 0x3FBD, 0x4019, 0x4074, 0x40D0, //
|
||||
0x412A, 0x4185, 0x41DF, 0x4239, 0x4292, 0x42EB, 0x4344, 0x439C, //
|
||||
0x43F4, 0x444C, 0x44A3, 0x44FA, 0x4550, 0x45A6, 0x45FC, 0x4651, //
|
||||
0x46A6, 0x46FA, 0x474E, 0x47A1, 0x47F4, 0x4846, 0x4898, 0x48E9, //
|
||||
0x493A, 0x498A, 0x49D9, 0x4A29, 0x4A77, 0x4AC5, 0x4B13, 0x4B5F, //
|
||||
0x4BAC, 0x4BF7, 0x4C42, 0x4C8D, 0x4CD7, 0x4D20, 0x4D68, 0x4DB0, //
|
||||
0x4DF7, 0x4E3E, 0x4E84, 0x4EC9, 0x4F0E, 0x4F52, 0x4F95, 0x4FD7, // entry
|
||||
0x5019, 0x505A, 0x509A, 0x50DA, 0x5118, 0x5156, 0x5194, 0x51D0, // 180..1FF
|
||||
0x520C, 0x5247, 0x5281, 0x52BA, 0x52F3, 0x532A, 0x5361, 0x5397, //
|
||||
0x53CC, 0x5401, 0x5434, 0x5467, 0x5499, 0x54CA, 0x54FA, 0x5529, //
|
||||
0x5558, 0x5585, 0x55B2, 0x55DE, 0x5609, 0x5632, 0x565B, 0x5684, //
|
||||
0x56AB, 0x56D1, 0x56F6, 0x571B, 0x573E, 0x5761, 0x5782, 0x57A3, //
|
||||
0x57C3, 0x57E2, 0x57FF, 0x581C, 0x5838, 0x5853, 0x586D, 0x5886, //
|
||||
0x589E, 0x58B5, 0x58CB, 0x58E0, 0x58F4, 0x5907, 0x5919, 0x592A, //
|
||||
0x593A, 0x5949, 0x5958, 0x5965, 0x5971, 0x597C, 0x5986, 0x598F, //
|
||||
0x5997, 0x599E, 0x59A4, 0x59A9, 0x59AD, 0x59B0, 0x59B2, 0x59B3 //
|
||||
}};
|
||||
|
||||
const u8 i = counter.interpolation_index;
|
||||
const s32 s = static_cast<s32>(ZeroExtend32(counter.sample_index.GetValue()));
|
||||
|
||||
s32 out = gauss[0x0FF - i] * s32(SampleBlock(s - 3));
|
||||
out += gauss[0x1FF - i] * s32(SampleBlock(s - 2));
|
||||
out += gauss[0x100 + i] * s32(SampleBlock(s - 1));
|
||||
out += gauss[0x000 + i] * s32(SampleBlock(s - 0));
|
||||
return out;
|
||||
}
|
||||
|
||||
void SPU::ReadADPCMBlock(u16 address, ADPCMBlock* block)
|
||||
{
|
||||
u32 ram_address = (ZeroExtend32(address) * 8) & RAM_MASK;
|
||||
|
||||
// 16 bytes, so 2 8-byte blocks for the interrupt check
|
||||
if (m_SPUCNT.irq9_enable)
|
||||
{
|
||||
if (m_irq_address == address || m_irq_address == (address + 1))
|
||||
{
|
||||
Log_DebugPrintf("SPU IRQ at address 0x%08X", ram_address);
|
||||
m_SPUSTAT.irq9_flag = true;
|
||||
m_interrupt_controller->InterruptRequest(InterruptController::IRQ::SPU);
|
||||
}
|
||||
}
|
||||
|
||||
// fast path - no wrap-around
|
||||
if ((ram_address + sizeof(ADPCMBlock)) <= RAM_SIZE)
|
||||
{
|
||||
std::memcpy(block, &m_ram[ram_address], sizeof(ADPCMBlock));
|
||||
return;
|
||||
}
|
||||
|
||||
block->shift_filter.bits = m_ram[ram_address];
|
||||
ram_address = (ram_address + 1) & RAM_MASK;
|
||||
block->flags.bits = m_ram[ram_address];
|
||||
ram_address = (ram_address + 1) & RAM_MASK;
|
||||
for (u32 i = 0; i < 14; i++)
|
||||
{
|
||||
block->data[i] = m_ram[ram_address];
|
||||
ram_address = (ram_address + 1) & RAM_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
void SPU::DecodeADPCMBlock(const ADPCMBlock& block, SampleFormat out_samples[NUM_SAMPLES_PER_ADPCM_BLOCK], s32 state[2])
|
||||
{
|
||||
static constexpr std::array<s32, 5> filter_table_pos = {{0, 60, 115, 98, 122}};
|
||||
static constexpr std::array<s32, 5> filter_table_neg = {{0, 0, -52, -55, -60}};
|
||||
|
||||
// pre-lookup
|
||||
const u8 shift = block.shift_filter.shift;
|
||||
const u8 filter_index = std::min<u8>(block.shift_filter.filter, 4);
|
||||
const s32 filter_pos = filter_table_pos[filter_index];
|
||||
const s32 filter_neg = filter_table_neg[filter_index];
|
||||
s32 last_samples[2] = {state[0], state[1]};
|
||||
|
||||
// samples
|
||||
for (u32 i = 0; i < NUM_SAMPLES_PER_ADPCM_BLOCK; i++)
|
||||
{
|
||||
const u8 nibble = (block.data[i / 2] >> (4 * (i % 2))) & 0x0F;
|
||||
s32 sample = SignExtendN<4, s32>(static_cast<s32>(ZeroExtend32(nibble)));
|
||||
sample >>= shift;
|
||||
|
||||
sample += ((last_samples[0] * filter_pos) + (last_samples[1] * filter_neg) + 32) / 64;
|
||||
|
||||
out_samples[i] =
|
||||
static_cast<s16>(std::clamp<s32>(sample, std::numeric_limits<s16>::min(), std::numeric_limits<s16>::max()));
|
||||
|
||||
state[1] = state[0];
|
||||
state[0] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<SPU::SampleFormat, SPU::SampleFormat> SPU::SampleVoice(u32 voice_index)
|
||||
{
|
||||
Voice& voice = m_voices[voice_index];
|
||||
if (!voice.key_on)
|
||||
return std::make_tuple<s16, s16>(0, 0);
|
||||
|
||||
if (!voice.has_samples)
|
||||
{
|
||||
ReadADPCMBlock(voice.current_address, &voice.current_block);
|
||||
voice.DecodeBlock();
|
||||
voice.has_samples = true;
|
||||
|
||||
if (voice.current_block.flags.loop_start)
|
||||
{
|
||||
Log_DebugPrintf("Voice %u loop start @ 0x%08X", voice_index, ZeroExtend32(voice.current_address));
|
||||
voice.regs.adpcm_repeat_address = voice.current_address;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Pulse modulation
|
||||
u16 step = voice.regs.adpcm_sample_rate;
|
||||
step = std::min<u16>(step, 0x4000);
|
||||
voice.counter.bits += step;
|
||||
|
||||
if (voice.counter.sample_index >= NUM_SAMPLES_PER_ADPCM_BLOCK)
|
||||
{
|
||||
// next block
|
||||
voice.counter.sample_index -= NUM_SAMPLES_PER_ADPCM_BLOCK;
|
||||
voice.has_samples = false;
|
||||
|
||||
// handle flags
|
||||
if (voice.current_block.flags.loop_end)
|
||||
{
|
||||
if (!voice.current_block.flags.loop_repeat)
|
||||
{
|
||||
Log_DebugPrintf("Voice %u loop end+mute @ 0x%08X", voice_index, ZeroExtend32(voice.current_address));
|
||||
m_endx_register |= (u32(1) << voice_index);
|
||||
voice.KeyOff();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log_DebugPrintf("Voice %u loop end+repeat @ 0x%08X", voice_index, ZeroExtend32(voice.current_address));
|
||||
voice.current_address = voice.regs.adpcm_repeat_address;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
voice.current_address += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Volume
|
||||
s32 sample = voice.Interpolate();
|
||||
return std::make_tuple(Clamp16(sample), Clamp16(sample));
|
||||
}
|
||||
|
||||
void SPU::GenerateSample()
|
||||
{
|
||||
s32 left_sum = 0;
|
||||
s32 right_sum = 0;
|
||||
for (u32 i = 0; i < NUM_VOICES; i++)
|
||||
{
|
||||
const auto [left, right] = SampleVoice(i);
|
||||
left_sum += left;
|
||||
right_sum += right;
|
||||
}
|
||||
|
||||
Log_DebugPrintf("SPU sample %d %d", left_sum, right_sum);
|
||||
AudioStream::SampleType samples[2] = {Clamp16(left_sum), Clamp16(right_sum)};
|
||||
m_audio_stream->WriteSamples(samples, countof(samples));
|
||||
}
|
||||
|
||||
void SPU::DrawDebugWindow()
|
||||
{
|
||||
if (!m_debug_window_open)
|
||||
return;
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(700, 400), ImGuiCond_FirstUseEver);
|
||||
if (!ImGui::Begin("SPU State", &m_debug_window_open))
|
||||
{
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
// draw voice states
|
||||
if (ImGui::CollapsingHeader("Voice State", ImGuiTreeNodeFlags_DefaultOpen))
|
||||
{
|
||||
static constexpr u32 NUM_COLUMNS = 11;
|
||||
|
||||
ImGui::Columns(NUM_COLUMNS);
|
||||
|
||||
// headers
|
||||
static constexpr std::array<const char*, NUM_COLUMNS> column_titles = {
|
||||
{"#", "InterpIndex", "SampleIndex", "CurAddr", "StartAddr", "RepeatAddr", "SampleRate", "VolLeft", "VolRight",
|
||||
"ADSR", "ADSRVol"}};
|
||||
for (u32 i = 0; i < NUM_COLUMNS; i++)
|
||||
{
|
||||
ImGui::TextUnformatted(column_titles[i]);
|
||||
ImGui::NextColumn();
|
||||
}
|
||||
|
||||
// states
|
||||
for (u32 voice_index = 0; voice_index < NUM_VOICES; voice_index++)
|
||||
{
|
||||
const Voice& v = m_voices[voice_index];
|
||||
ImVec4 color = v.key_on ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
ImGui::TextColored(color, "%u", ZeroExtend32(voice_index));
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%u", ZeroExtend32(v.counter.interpolation_index.GetValue()));
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%u", ZeroExtend32(v.counter.sample_index.GetValue()));
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%04X", ZeroExtend32(v.current_address));
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.adpcm_start_address));
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.adpcm_repeat_address));
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%.2f", (float(v.regs.adpcm_sample_rate) / 16383.0f) * 44100.0f);
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.volume_left.bits));
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.volume_right.bits));
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%08X", v.regs.adsr.bits);
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.adsr_volume));
|
||||
ImGui::NextColumn();
|
||||
}
|
||||
|
||||
ImGui::Columns(1);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void SPU::DrawDebugMenu()
|
||||
{
|
||||
// TODO: Show RAM, etc.
|
||||
}
|
||||
|
|
147
src/core/spu.h
147
src/core/spu.h
|
@ -3,6 +3,7 @@
|
|||
#include "types.h"
|
||||
#include <array>
|
||||
|
||||
class AudioStream;
|
||||
class StateWrapper;
|
||||
|
||||
class System;
|
||||
|
@ -12,6 +13,8 @@ class InterruptController;
|
|||
class SPU
|
||||
{
|
||||
public:
|
||||
using SampleFormat = s16;
|
||||
|
||||
SPU();
|
||||
~SPU();
|
||||
|
||||
|
@ -25,14 +28,28 @@ public:
|
|||
u32 DMARead();
|
||||
void DMAWrite(u32 value);
|
||||
|
||||
void Execute(TickCount ticks);
|
||||
|
||||
// Render statistics debug window.
|
||||
void DrawDebugWindow();
|
||||
|
||||
// Manipulating debug options.
|
||||
void DrawDebugMenu();
|
||||
|
||||
private:
|
||||
static constexpr u32 RAM_SIZE = 512 * 1024;
|
||||
static constexpr u32 RAM_MASK = RAM_SIZE - 1;
|
||||
static constexpr u32 SPU_BASE = 0x1F801C00;
|
||||
static constexpr u32 NUM_VOICES = 24;
|
||||
static constexpr u32 NUM_VOICE_REGISTERS = 8;
|
||||
static constexpr u32 VOICE_ADDRESS_SHIFT = 3;
|
||||
static constexpr u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28;
|
||||
static constexpr u32 SAMPLE_RATE = 44100;
|
||||
static constexpr u32 SYSCLK_TICKS_PER_SPU_TICK = MASTER_CLOCK / SAMPLE_RATE; // 0x300
|
||||
|
||||
enum class RAMTransferMode : u8
|
||||
{
|
||||
Stopped =0,
|
||||
Stopped = 0,
|
||||
ManualWrite = 1,
|
||||
DMAWrite = 2,
|
||||
DMARead = 3
|
||||
|
@ -70,24 +87,133 @@ private:
|
|||
BitField<u16, u8, 0, 6> mode;
|
||||
};
|
||||
|
||||
#if 0
|
||||
union ADSRRegister
|
||||
{
|
||||
u32 bits;
|
||||
struct
|
||||
{
|
||||
u16 bits_low;
|
||||
u16 bits_high;
|
||||
};
|
||||
|
||||
BitField<u32, u8, 0, 4> sustain_level;
|
||||
BitField<u32, u8, 4, 4> decay_shift;
|
||||
BitField<u32, u8, 8, 2> attack_step;
|
||||
BitField<u32, u8, 10, 6> attack_shift;
|
||||
BitField<u32, bool, 15, 1> attack_exponential;
|
||||
|
||||
BitField<u32, u8, 16, 5> release_shift;
|
||||
BitField<u32, bool, 21, 1> release_exponential;
|
||||
BitField<u32, u8, 22, 2> sustain_step;
|
||||
BitField<u32, u8, 24, 5> sustain_shift;
|
||||
BitField<u32, bool, 30, 1> sustain_direction_decrease;
|
||||
BitField<u32, bool, 31, 1> sustain_exponential;
|
||||
};
|
||||
|
||||
union VolumeRegister
|
||||
{
|
||||
u16 bits;
|
||||
|
||||
BitField<u16, bool, 15, 1> sweep_mode;
|
||||
BitField<u16, u16, 0, 15> fixed_volume; // divided by 2
|
||||
|
||||
BitField<u16, bool, 14, 1> sweep_exponential;
|
||||
BitField<u16, bool, 13, 1> sweep_direction_decrease;
|
||||
BitField<u16, bool, 12, 1> sweep_phase_negative;
|
||||
BitField<u16, u8, 2, 5> sweep_shift;
|
||||
BitField<u16, u8, 0, 2> sweep_step;
|
||||
};
|
||||
|
||||
// organized so we can replace this with a u16 array in the future
|
||||
union VoiceRegisters
|
||||
{
|
||||
u16 index[NUM_VOICE_REGISTERS];
|
||||
|
||||
struct
|
||||
{
|
||||
VolumeRegister volume_left;
|
||||
VolumeRegister volume_right;
|
||||
|
||||
u16 adpcm_sample_rate; // VxPitch
|
||||
u16 adpcm_start_address; // multiply by 8
|
||||
|
||||
ADSRRegister adsr;
|
||||
u16 adsr_volume;
|
||||
|
||||
u16 adpcm_repeat_address; // multiply by 8
|
||||
};
|
||||
};
|
||||
|
||||
union VoiceCounter
|
||||
{
|
||||
// promoted to u32 because of overflow
|
||||
u32 bits;
|
||||
|
||||
BitField<u32, u8, 4, 8> interpolation_index;
|
||||
BitField<u32, u8, 12, 5> sample_index;
|
||||
};
|
||||
|
||||
struct ADPCMBlock
|
||||
{
|
||||
union
|
||||
{
|
||||
u8 bits;
|
||||
|
||||
BitField<u8, u8, 0, 4> shift;
|
||||
BitField<u8, u8, 4, 3> filter;
|
||||
} shift_filter;
|
||||
union
|
||||
{
|
||||
u8 bits;
|
||||
|
||||
BitField<u8, bool, 0, 1> loop_end;
|
||||
BitField<u8, bool, 1, 1> loop_repeat;
|
||||
BitField<u8, bool, 2, 1> loop_start;
|
||||
} flags;
|
||||
|
||||
u8 data[NUM_SAMPLES_PER_ADPCM_BLOCK / 2];
|
||||
|
||||
u8 ReadSample(u32 index) const { return (data[index / 2] >> ((index % 2) * 4)) & 0x0F; }
|
||||
};
|
||||
|
||||
struct Voice
|
||||
{
|
||||
static constexpr u32 NUM_REGS = 8;
|
||||
static constexpr u32 NUM_FLAGS = 6;
|
||||
u16 current_address;
|
||||
VoiceRegisters regs;
|
||||
VoiceCounter counter;
|
||||
ADPCMBlock current_block; // TODO Drop this after decoding
|
||||
std::array<SampleFormat, NUM_SAMPLES_PER_ADPCM_BLOCK> current_block_samples;
|
||||
std::array<SampleFormat, 3> previous_block_last_samples;
|
||||
std::array<s32, 2> adpcm_state;
|
||||
|
||||
std::array<u16, NUM_REGS> regs;
|
||||
bool has_samples;
|
||||
bool key_on;
|
||||
|
||||
void KeyOn();
|
||||
void KeyOff();
|
||||
|
||||
void DecodeBlock();
|
||||
SampleFormat SampleBlock(s32 index) const;
|
||||
s32 Interpolate() const;
|
||||
};
|
||||
#endif
|
||||
|
||||
u16 ReadVoiceRegister(u32 offset);
|
||||
void WriteVoiceRegister(u32 offset, u16 value);
|
||||
|
||||
void UpdateDMARequest();
|
||||
u16 RAMTransferRead();
|
||||
void RAMTransferWrite(u16 value);
|
||||
|
||||
void ReadADPCMBlock(u16 address, ADPCMBlock* block);
|
||||
static void DecodeADPCMBlock(const ADPCMBlock& block, SampleFormat* out_samples, s32* state);
|
||||
std::tuple<SampleFormat, SampleFormat> SampleVoice(u32 voice_index);
|
||||
void GenerateSample();
|
||||
|
||||
System* m_system = nullptr;
|
||||
DMA* m_dma = nullptr;
|
||||
InterruptController* m_interrupt_controller = nullptr;
|
||||
AudioStream* m_audio_stream = nullptr;
|
||||
bool m_debug_window_open = true;
|
||||
|
||||
SPUCNT m_SPUCNT = {};
|
||||
SPUSTAT m_SPUSTAT = {};
|
||||
|
@ -95,5 +221,14 @@ private:
|
|||
u16 m_transfer_address_reg = 0;
|
||||
u32 m_transfer_address = 0;
|
||||
|
||||
u16 m_irq_address = 0;
|
||||
|
||||
u32 m_key_on_register = 0;
|
||||
u32 m_key_off_register = 0;
|
||||
u32 m_endx_register = 0;
|
||||
|
||||
TickCount m_ticks_carry = 0;
|
||||
|
||||
std::array<Voice, NUM_VOICES> m_voices{};
|
||||
std::array<u8, RAM_SIZE> m_ram{};
|
||||
};
|
|
@ -288,6 +288,7 @@ void System::Synchronize()
|
|||
m_cdrom->Execute(pending_ticks);
|
||||
m_pad->Execute(pending_ticks);
|
||||
m_dma->Execute(pending_ticks);
|
||||
m_spu->Execute(pending_ticks);
|
||||
}
|
||||
|
||||
void System::SetDowncount(TickCount downcount)
|
||||
|
|
|
@ -33,6 +33,7 @@ public:
|
|||
CPU::Core* GetCPU() const { return m_cpu.get(); }
|
||||
Bus* GetBus() const { return m_bus.get(); }
|
||||
GPU* GetGPU() const { return m_gpu.get(); }
|
||||
SPU* GetSPU() const { return m_spu.get(); }
|
||||
|
||||
u32 GetFrameNumber() const { return m_frame_number; }
|
||||
u32 GetInternalFrameNumber() const { return m_internal_frame_number; }
|
||||
|
|
|
@ -91,7 +91,7 @@ int main(int argc, char* argv[])
|
|||
// const LOGLEVEL level = LOGLEVEL_DEV;
|
||||
// const LOGLEVEL level = LOGLEVEL_PROFILE;
|
||||
// g_pLog->SetConsoleOutputParams(true, nullptr, level);
|
||||
g_pLog->SetConsoleOutputParams(true, "Pad SPU", level);
|
||||
g_pLog->SetConsoleOutputParams(true, "Pad", level);
|
||||
g_pLog->SetFilterLevel(level);
|
||||
#else
|
||||
g_pLog->SetConsoleOutputParams(true, nullptr, LOGLEVEL_DEBUG);
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
#include "core/digital_controller.h"
|
||||
#include "core/gpu.h"
|
||||
#include "core/memory_card.h"
|
||||
#include "core/spu.h"
|
||||
#include "core/system.h"
|
||||
#include "icon.h"
|
||||
#include "sdl_audio_stream.h"
|
||||
#include <cinttypes>
|
||||
#include <glad.h>
|
||||
#include <imgui.h>
|
||||
|
@ -173,11 +175,26 @@ void main()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SDLInterface::CreateAudioStream()
|
||||
{
|
||||
m_audio_stream = std::make_unique<SDLAudioStream>();
|
||||
if (!m_audio_stream->Reconfigure(44100, 2))
|
||||
{
|
||||
Panic("Failed to open audio stream");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<SDLInterface> SDLInterface::Create()
|
||||
{
|
||||
std::unique_ptr<SDLInterface> intf = std::make_unique<SDLInterface>();
|
||||
if (!intf->CreateSDLWindow() || !intf->CreateGLContext() || !intf->CreateImGuiContext() || !intf->CreateGLResources())
|
||||
if (!intf->CreateSDLWindow() || !intf->CreateGLContext() || !intf->CreateImGuiContext() ||
|
||||
!intf->CreateGLResources() || !intf->CreateAudioStream())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return intf;
|
||||
}
|
||||
|
@ -312,15 +329,15 @@ bool SDLInterface::HandleSDLEvent(const SDL_Event* event)
|
|||
break;
|
||||
|
||||
case SDL_SCANCODE_TAB:
|
||||
{
|
||||
// Window framebuffer has to be bound to call SetSwapInterval.
|
||||
GLint current_fbo = 0;
|
||||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
SDL_GL_SetSwapInterval(pressed ? 0 : 1);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
|
||||
}
|
||||
break;
|
||||
{
|
||||
// Window framebuffer has to be bound to call SetSwapInterval.
|
||||
GLint current_fbo = 0;
|
||||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
SDL_GL_SetSwapInterval(pressed ? 0 : 1);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
@ -476,6 +493,8 @@ void SDLInterface::DrawImGui()
|
|||
if (m_show_gpu_statistics)
|
||||
m_system->GetGPU()->DrawStatistics();
|
||||
|
||||
m_system->GetSPU()->DrawDebugWindow();
|
||||
|
||||
DrawOSDMessages();
|
||||
|
||||
ImGui::Render();
|
||||
|
@ -569,6 +588,13 @@ void SDLInterface::DrawMainMenuBar()
|
|||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("SPU"))
|
||||
{
|
||||
m_system->GetSPU()->DrawDebugMenu();
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
|
@ -689,6 +715,8 @@ void SDLInterface::ConnectDevices()
|
|||
|
||||
void SDLInterface::Run()
|
||||
{
|
||||
m_audio_stream->PauseOutput(false);
|
||||
|
||||
while (m_running)
|
||||
{
|
||||
for (;;)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
class System;
|
||||
class DigitalController;
|
||||
class MemoryCard;
|
||||
class AudioStream;
|
||||
|
||||
class SDLInterface : public HostInterface
|
||||
{
|
||||
|
@ -47,6 +48,7 @@ private:
|
|||
bool CreateGLContext();
|
||||
bool CreateImGuiContext();
|
||||
bool CreateGLResources();
|
||||
bool CreateAudioStream();
|
||||
|
||||
// We only pass mouse input through if it's grabbed
|
||||
bool IsWindowFullscreen() const;
|
||||
|
|
Loading…
Reference in a new issue