Further work on SPU implementation

This commit is contained in:
Connor McLaughlin 2019-10-11 02:20:21 +10:00
parent bc51cc6d7d
commit f852b8dd90
9 changed files with 710 additions and 20 deletions

View file

@ -1,6 +1,7 @@
#include "host_interface.h" #include "host_interface.h"
#include "YBaseLib/ByteStream.h" #include "YBaseLib/ByteStream.h"
#include "YBaseLib/Log.h" #include "YBaseLib/Log.h"
#include "common/audio_stream.h"
#include "system.h" #include "system.h"
Log_SetChannel(HostInterface); Log_SetChannel(HostInterface);

View file

@ -2,6 +2,8 @@
#include "types.h" #include "types.h"
#include <memory> #include <memory>
class AudioStream;
namespace GL { namespace GL {
class Texture; class Texture;
} }
@ -14,6 +16,8 @@ public:
HostInterface(); HostInterface();
virtual ~HostInterface(); virtual ~HostInterface();
AudioStream* GetAudioStream() const { return m_audio_stream.get(); }
bool InitializeSystem(const char* filename, const char* exp1_filename); 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; 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); bool SaveState(const char* filename);
protected: protected:
std::unique_ptr<AudioStream> m_audio_stream;
std::unique_ptr<System> m_system; std::unique_ptr<System> m_system;
bool m_running = false; bool m_running = false;
}; };

View file

@ -1,17 +1,26 @@
#include "spu.h" #include "spu.h"
#include "YBaseLib/Log.h" #include "YBaseLib/Log.h"
#include "common/audio_stream.h"
#include "common/state_wrapper.h" #include "common/state_wrapper.h"
#include "dma.h" #include "dma.h"
#include "host_interface.h"
#include "interrupt_controller.h" #include "interrupt_controller.h"
#include "system.h" #include "system.h"
#include <imgui.h>
Log_SetChannel(SPU); Log_SetChannel(SPU);
static s16 Clamp16(s32 value)
{
return static_cast<s16>(std::clamp<s32>(value, -32768, 32767));
}
SPU::SPU() = default; SPU::SPU() = default;
SPU::~SPU() = default; SPU::~SPU() = default;
bool SPU::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller) bool SPU::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller)
{ {
m_audio_stream = system->GetHostInterface()->GetAudioStream();
m_system = system; m_system = system;
m_dma = dma; m_dma = dma;
m_interrupt_controller = interrupt_controller; m_interrupt_controller = interrupt_controller;
@ -37,6 +46,9 @@ bool SPU::DoState(StateWrapper& sw)
u16 SPU::ReadRegister(u32 offset) u16 SPU::ReadRegister(u32 offset)
{ {
if (offset < (0x1F801D80 - SPU_BASE))
return ReadVoiceRegister(offset);
switch (offset) switch (offset)
{ {
case 0x1F801DA6 - SPU_BASE: case 0x1F801DA6 - SPU_BASE:
@ -52,9 +64,21 @@ u16 SPU::ReadRegister(u32 offset)
return m_SPUCNT.bits; return m_SPUCNT.bits;
case 0x1F801DAE - SPU_BASE: 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; 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: default:
Log_ErrorPrintf("Unknown SPU register read: offset 0x%X (address 0x%08X)", offset, offset | SPU_BASE); Log_ErrorPrintf("Unknown SPU register read: offset 0x%X (address 0x%08X)", offset, offset | SPU_BASE);
return UINT16_C(0xFFFF); return UINT16_C(0xFFFF);
@ -63,13 +87,19 @@ u16 SPU::ReadRegister(u32 offset)
void SPU::WriteRegister(u32 offset, u16 value) void SPU::WriteRegister(u32 offset, u16 value)
{ {
if (offset < (0x1F801D80 - SPU_BASE))
{
WriteVoiceRegister(offset, value);
return;
}
switch (offset) switch (offset)
{ {
case 0x1F801DA6 - SPU_BASE: case 0x1F801DA6 - SPU_BASE:
{ {
Log_DebugPrintf("SPU transfer address register <- 0x%04X", ZeroExtend32(value)); Log_DebugPrintf("SPU transfer address register <- 0x%04X", ZeroExtend32(value));
m_transfer_address_reg = value; m_transfer_address_reg = value;
m_transfer_address = ZeroExtend32(value) * 8; m_transfer_address = (ZeroExtend32(value) << VOICE_ADDRESS_SHIFT) & RAM_MASK;
return; return;
} }
@ -89,6 +119,82 @@ void SPU::WriteRegister(u32 offset, u16 value)
return; 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 // read-only registers
case 0x1F801DAE - SPU_BASE: 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() u32 SPU::DMARead()
{ {
const u16 lsb = RAMTransferRead(); const u16 lsb = RAMTransferRead();
@ -135,6 +324,333 @@ u16 SPU::RAMTransferRead()
void SPU::RAMTransferWrite(u16 value) 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)); std::memcpy(&m_ram[m_transfer_address], &value, sizeof(value));
m_transfer_address = (m_transfer_address + sizeof(value)) & RAM_MASK; 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.
}

View file

@ -3,6 +3,7 @@
#include "types.h" #include "types.h"
#include <array> #include <array>
class AudioStream;
class StateWrapper; class StateWrapper;
class System; class System;
@ -12,6 +13,8 @@ class InterruptController;
class SPU class SPU
{ {
public: public:
using SampleFormat = s16;
SPU(); SPU();
~SPU(); ~SPU();
@ -25,14 +28,28 @@ public:
u32 DMARead(); u32 DMARead();
void DMAWrite(u32 value); void DMAWrite(u32 value);
void Execute(TickCount ticks);
// Render statistics debug window.
void DrawDebugWindow();
// Manipulating debug options.
void DrawDebugMenu();
private: private:
static constexpr u32 RAM_SIZE = 512 * 1024; static constexpr u32 RAM_SIZE = 512 * 1024;
static constexpr u32 RAM_MASK = RAM_SIZE - 1; static constexpr u32 RAM_MASK = RAM_SIZE - 1;
static constexpr u32 SPU_BASE = 0x1F801C00; 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 enum class RAMTransferMode : u8
{ {
Stopped =0, Stopped = 0,
ManualWrite = 1, ManualWrite = 1,
DMAWrite = 2, DMAWrite = 2,
DMARead = 3 DMARead = 3
@ -70,24 +87,133 @@ private:
BitField<u16, u8, 0, 6> mode; 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 struct Voice
{ {
static constexpr u32 NUM_REGS = 8; u16 current_address;
static constexpr u32 NUM_FLAGS = 6; 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(); void UpdateDMARequest();
u16 RAMTransferRead(); u16 RAMTransferRead();
void RAMTransferWrite(u16 value); 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; System* m_system = nullptr;
DMA* m_dma = nullptr; DMA* m_dma = nullptr;
InterruptController* m_interrupt_controller = nullptr; InterruptController* m_interrupt_controller = nullptr;
AudioStream* m_audio_stream = nullptr;
bool m_debug_window_open = true;
SPUCNT m_SPUCNT = {}; SPUCNT m_SPUCNT = {};
SPUSTAT m_SPUSTAT = {}; SPUSTAT m_SPUSTAT = {};
@ -95,5 +221,14 @@ private:
u16 m_transfer_address_reg = 0; u16 m_transfer_address_reg = 0;
u32 m_transfer_address = 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{}; std::array<u8, RAM_SIZE> m_ram{};
}; };

View file

@ -288,6 +288,7 @@ void System::Synchronize()
m_cdrom->Execute(pending_ticks); m_cdrom->Execute(pending_ticks);
m_pad->Execute(pending_ticks); m_pad->Execute(pending_ticks);
m_dma->Execute(pending_ticks); m_dma->Execute(pending_ticks);
m_spu->Execute(pending_ticks);
} }
void System::SetDowncount(TickCount downcount) void System::SetDowncount(TickCount downcount)

View file

@ -33,6 +33,7 @@ public:
CPU::Core* GetCPU() const { return m_cpu.get(); } CPU::Core* GetCPU() const { return m_cpu.get(); }
Bus* GetBus() const { return m_bus.get(); } Bus* GetBus() const { return m_bus.get(); }
GPU* GetGPU() const { return m_gpu.get(); } GPU* GetGPU() const { return m_gpu.get(); }
SPU* GetSPU() const { return m_spu.get(); }
u32 GetFrameNumber() const { return m_frame_number; } u32 GetFrameNumber() const { return m_frame_number; }
u32 GetInternalFrameNumber() const { return m_internal_frame_number; } u32 GetInternalFrameNumber() const { return m_internal_frame_number; }

View file

@ -91,7 +91,7 @@ int main(int argc, char* argv[])
// const LOGLEVEL level = LOGLEVEL_DEV; // const LOGLEVEL level = LOGLEVEL_DEV;
// const LOGLEVEL level = LOGLEVEL_PROFILE; // const LOGLEVEL level = LOGLEVEL_PROFILE;
// g_pLog->SetConsoleOutputParams(true, nullptr, level); // g_pLog->SetConsoleOutputParams(true, nullptr, level);
g_pLog->SetConsoleOutputParams(true, "Pad SPU", level); g_pLog->SetConsoleOutputParams(true, "Pad", level);
g_pLog->SetFilterLevel(level); g_pLog->SetFilterLevel(level);
#else #else
g_pLog->SetConsoleOutputParams(true, nullptr, LOGLEVEL_DEBUG); g_pLog->SetConsoleOutputParams(true, nullptr, LOGLEVEL_DEBUG);

View file

@ -5,8 +5,10 @@
#include "core/digital_controller.h" #include "core/digital_controller.h"
#include "core/gpu.h" #include "core/gpu.h"
#include "core/memory_card.h" #include "core/memory_card.h"
#include "core/spu.h"
#include "core/system.h" #include "core/system.h"
#include "icon.h" #include "icon.h"
#include "sdl_audio_stream.h"
#include <cinttypes> #include <cinttypes>
#include <glad.h> #include <glad.h>
#include <imgui.h> #include <imgui.h>
@ -173,11 +175,26 @@ void main()
return true; 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> SDLInterface::Create()
{ {
std::unique_ptr<SDLInterface> intf = std::make_unique<SDLInterface>(); 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 nullptr;
}
return intf; return intf;
} }
@ -476,6 +493,8 @@ void SDLInterface::DrawImGui()
if (m_show_gpu_statistics) if (m_show_gpu_statistics)
m_system->GetGPU()->DrawStatistics(); m_system->GetGPU()->DrawStatistics();
m_system->GetSPU()->DrawDebugWindow();
DrawOSDMessages(); DrawOSDMessages();
ImGui::Render(); ImGui::Render();
@ -569,6 +588,13 @@ void SDLInterface::DrawMainMenuBar()
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("SPU"))
{
m_system->GetSPU()->DrawDebugMenu();
ImGui::EndMenu();
}
ImGui::EndMenu(); ImGui::EndMenu();
} }
@ -689,6 +715,8 @@ void SDLInterface::ConnectDevices()
void SDLInterface::Run() void SDLInterface::Run()
{ {
m_audio_stream->PauseOutput(false);
while (m_running) while (m_running)
{ {
for (;;) for (;;)

View file

@ -13,6 +13,7 @@
class System; class System;
class DigitalController; class DigitalController;
class MemoryCard; class MemoryCard;
class AudioStream;
class SDLInterface : public HostInterface class SDLInterface : public HostInterface
{ {
@ -47,6 +48,7 @@ private:
bool CreateGLContext(); bool CreateGLContext();
bool CreateImGuiContext(); bool CreateImGuiContext();
bool CreateGLResources(); bool CreateGLResources();
bool CreateAudioStream();
// We only pass mouse input through if it's grabbed // We only pass mouse input through if it's grabbed
bool IsWindowFullscreen() const; bool IsWindowFullscreen() const;