#pragma once #include "common/bitfield.h" #include "common/fifo_queue.h" #include "types.h" #include class AudioStream; class StateWrapper; class System; class DMA; class InterruptController; class SPU { public: SPU(); ~SPU(); void Initialize(System* system, DMA* dma, InterruptController* interrupt_controller); void Reset(); bool DoState(StateWrapper& sw); u16 ReadRegister(u32 offset); void WriteRegister(u32 offset, u16 value); void DMARead(u32* words, u32 word_count); void DMAWrite(const u32* words, u32 word_count); void Execute(TickCount ticks); // Render statistics debug window. void DrawDebugStateWindow(); // External input from CD controller. void AddCDAudioSample(s16 left, s16 right) { m_cd_audio_buffer.Push(left); m_cd_audio_buffer.Push(right); } void EnsureCDAudioSpace(u32 num_samples); 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 static constexpr s16 ADSR_MIN_VOLUME = 0; static constexpr s16 ADSR_MAX_VOLUME = 0x7FFF; static constexpr u32 CD_AUDIO_SAMPLE_BUFFER_SIZE = 44100 * 2; static constexpr u32 CAPTURE_BUFFER_SIZE_PER_CHANNEL = 0x400; enum class RAMTransferMode : u8 { Stopped = 0, ManualWrite = 1, DMAWrite = 2, DMARead = 3 }; union SPUCNT { u16 bits; BitField enable; BitField mute_n; BitField noise_frequency_shift; BitField noise_frequency_step; BitField reverb_master_enable; BitField irq9_enable; BitField ram_transfer_mode; BitField external_audio_reverb; BitField cd_audio_reverb; BitField external_audio_enable; BitField cd_audio_enable; BitField mode; }; union SPUSTAT { u16 bits; BitField second_half_capture_buffer; BitField transfer_busy; BitField dma_read_request; BitField dma_write_request; BitField dma_read_write_request; BitField irq9_flag; BitField mode; }; union TransferControl { u16 bits; BitField mode; }; 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; s16 GetVolume() { return fixed_volume * 2; } }; // 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; s16 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; }; union ADPCMFlags { u8 bits; BitField loop_end; BitField loop_repeat; BitField loop_start; }; struct ADPCMBlock { union { u8 bits; BitField shift; BitField filter; } shift_filter; ADPCMFlags flags; u8 data[NUM_SAMPLES_PER_ADPCM_BLOCK / 2]; // For both 4bit and 8bit ADPCM, reserved shift values 13..15 will act same as shift=9). u8 GetShift() const { const u8 shift = shift_filter.shift; return (shift > 12) ? 9 : shift; } u8 GetFilter() const { return std::min(shift_filter.filter, 4); } u8 GetNibble(u32 index) const { return (data[index / 2] >> ((index % 2) * 4)) & 0x0F; } }; enum class ADSRPhase : u8 { Off = 0, Attack = 1, Decay = 2, Sustain = 3, Release = 4 }; struct ADSRTarget { s32 level; s16 step; u8 shift; bool decreasing; bool exponential; }; struct Voice { u16 current_address; VoiceRegisters regs; VoiceCounter counter; ADPCMFlags current_block_flags; std::array current_block_samples; std::array previous_block_last_samples; std::array adpcm_last_samples; s32 last_amplitude; ADSRPhase adsr_phase; ADSRTarget adsr_target; TickCount adsr_ticks; TickCount adsr_ticks_remaining; s16 adsr_step; bool has_samples; bool IsOn() const { return adsr_phase != ADSRPhase::Off; } void KeyOn(); void KeyOff(); void DecodeBlock(const ADPCMBlock& block); s16 SampleBlock(s32 index) const; s16 Interpolate() const; // Switches to the specified phase, filling in target. void SetADSRPhase(ADSRPhase phase); // Updates the ADSR volume/phase. void TickADSR(); }; static constexpr s16 Clamp16(s32 value) { return (value < -0x8000) ? -0x8000 : (value > 0x7FFF) ? 0x7FFF : static_cast(value); } static constexpr s32 ApplyVolume(s32 sample, s16 volume) { return (sample * s32(volume)) >> 15; } static ADSRPhase GetNextADSRPhase(ADSRPhase phase); bool IsVoiceReverbEnabled(u32 i) const { return (m_reverb_on_register & (u32(1) << i)) != 0; } bool IsPitchModulationEnabled(u32 i) const { return (i > 0 && ((m_pitch_modulation_enable_register & (u32(1) << i)) != 0)); } u16 ReadVoiceRegister(u32 offset); void WriteVoiceRegister(u32 offset, u16 value); void UpdateDMARequest(); u16 RAMTransferRead(); void RAMTransferWrite(u16 value); void CheckRAMIRQ(u32 address); void WriteToCaptureBuffer(u32 index, s16 value); void IncrementCaptureBufferPosition(); void ReadADPCMBlock(u16 address, ADPCMBlock* block); std::tuple SampleVoice(u32 voice_index); void GenerateSample(); System* m_system = nullptr; DMA* m_dma = nullptr; InterruptController* m_interrupt_controller = nullptr; SPUCNT m_SPUCNT = {}; SPUSTAT m_SPUSTAT = {}; TransferControl m_transfer_control = {}; u16 m_transfer_address_reg = 0; u32 m_transfer_address = 0; u16 m_irq_address = 0; u16 m_capture_buffer_position = 0; VolumeRegister m_main_volume_left = {}; VolumeRegister m_main_volume_right = {}; s16 m_cd_audio_volume_left = 0; s16 m_cd_audio_volume_right = 0; u32 m_key_on_register = 0; u32 m_key_off_register = 0; u32 m_endx_register = 0; u32 m_reverb_on_register = 0; u32 m_noise_mode_register = 0; u32 m_pitch_modulation_enable_register = 0; TickCount m_ticks_carry = 0; std::array m_voices{}; std::array m_ram{}; InlineFIFOQueue m_cd_audio_buffer; };