diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index fcbec0e90..5e05db749 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -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); diff --git a/src/core/host_interface.h b/src/core/host_interface.h index 000727c1a..073588ea7 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -2,6 +2,8 @@ #include "types.h" #include +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 m_audio_stream; + std::unique_ptr m_system; bool m_running = false; }; diff --git a/src/core/spu.cpp b/src/core/spu.cpp index 55aa8773d..103f56c5c 100644 --- a/src/core/spu.cpp +++ b/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 Log_SetChannel(SPU); +static s16 Clamp16(s32 value) +{ + return static_cast(std::clamp(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 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(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 filter_table_pos = {{0, 60, 115, 98, 122}}; + static constexpr std::array filter_table_neg = {{0, 0, -52, -55, -60}}; + + // pre-lookup + const u8 shift = block.shift_filter.shift; + const u8 filter_index = std::min(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(ZeroExtend32(nibble))); + sample >>= shift; + + sample += ((last_samples[0] * filter_pos) + (last_samples[1] * filter_neg) + 32) / 64; + + out_samples[i] = + static_cast(std::clamp(sample, std::numeric_limits::min(), std::numeric_limits::max())); + + state[1] = state[0]; + state[0] = sample; + } +} + +std::tuple SPU::SampleVoice(u32 voice_index) +{ + Voice& voice = m_voices[voice_index]; + if (!voice.key_on) + return std::make_tuple(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(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 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. +} diff --git a/src/core/spu.h b/src/core/spu.h index 91a211ad0..8566a3d97 100644 --- a/src/core/spu.h +++ b/src/core/spu.h @@ -3,6 +3,7 @@ #include "types.h" #include +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 mode; }; -#if 0 + union ADSRRegister + { + u32 bits; + struct + { + u16 bits_low; + u16 bits_high; + }; + + BitField sustain_level; + BitField decay_shift; + BitField attack_step; + BitField attack_shift; + BitField attack_exponential; + + BitField release_shift; + BitField release_exponential; + BitField sustain_step; + BitField sustain_shift; + BitField sustain_direction_decrease; + BitField sustain_exponential; + }; + + union VolumeRegister + { + u16 bits; + + BitField sweep_mode; + BitField fixed_volume; // divided by 2 + + BitField sweep_exponential; + BitField sweep_direction_decrease; + BitField sweep_phase_negative; + BitField sweep_shift; + BitField 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 interpolation_index; + BitField sample_index; + }; + + struct ADPCMBlock + { + union + { + u8 bits; + + BitField shift; + BitField filter; + } shift_filter; + union + { + u8 bits; + + BitField loop_end; + BitField loop_repeat; + BitField 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 current_block_samples; + std::array previous_block_last_samples; + std::array adpcm_state; - std::array 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 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 m_voices{}; std::array m_ram{}; }; \ No newline at end of file diff --git a/src/core/system.cpp b/src/core/system.cpp index 9d2447d1b..8e4605756 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -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) diff --git a/src/core/system.h b/src/core/system.h index d81c8aec9..b0f5fed48 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -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; } diff --git a/src/duckstation/main.cpp b/src/duckstation/main.cpp index 2a89900e4..df0c8a535 100644 --- a/src/duckstation/main.cpp +++ b/src/duckstation/main.cpp @@ -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); diff --git a/src/duckstation/sdl_interface.cpp b/src/duckstation/sdl_interface.cpp index 65b4e992c..a4d9989ff 100644 --- a/src/duckstation/sdl_interface.cpp +++ b/src/duckstation/sdl_interface.cpp @@ -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 #include #include @@ -173,11 +175,26 @@ void main() return true; } +bool SDLInterface::CreateAudioStream() +{ + m_audio_stream = std::make_unique(); + if (!m_audio_stream->Reconfigure(44100, 2)) + { + Panic("Failed to open audio stream"); + return false; + } + + return true; +} + std::unique_ptr SDLInterface::Create() { std::unique_ptr intf = std::make_unique(); - 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 (;;) diff --git a/src/duckstation/sdl_interface.h b/src/duckstation/sdl_interface.h index 7fa0ba9d5..855eb174a 100644 --- a/src/duckstation/sdl_interface.h +++ b/src/duckstation/sdl_interface.h @@ -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;