#pragma once #include namespace psxsplash { /// Maximum number of audio clips that can be loaded in a scene static constexpr int MAX_AUDIO_CLIPS = 32; /// Maximum SPU voices (hardware limit) static constexpr int MAX_VOICES = 24; /// SPU RAM is 512KB total (0x00000-0x7FFFF). /// First 0x1000 bytes reserved for capture buffers. /// psyqo places a 16-byte silent dummy sample at 0x1000. /// User clips start at 0x1010. /// /// Upper bound is 0x10000 (64KB) because psyqo::SPU::playADPCM() /// takes a uint16_t for the SPU RAM address. static constexpr uint32_t SPU_RAM_START = 0x1010; static constexpr uint32_t SPU_RAM_END = 0x10000; /// Default ADSR: instant attack, sustain at max, ~46ms linear release. /// Lower 16-bit (AD): attack linear shift=0 step=0("+7"), decay shift=0, /// sustain level=0xF (max -> decay skipped) /// Upper 16-bit (SR): sustain linear increase shift=0 step=0("+7"), /// release linear shift=10 (~46ms to zero) static constexpr uint32_t DEFAULT_ADSR = 0x000A000F; /// Descriptor for a loaded audio clip in SPU RAM struct AudioClip { uint32_t spuAddr; // Byte address in SPU RAM uint32_t size; // Size of ADPCM data in bytes uint16_t sampleRate; // Original sample rate in Hz bool loop; // Whether this clip should loop bool loaded; // Whether this slot is valid }; /// Manages SPU voices and audio clip playback. /// /// Uses psyqo::SPU for all hardware interaction: initialization, /// DMA uploads, voice allocation (via currentVolume check), playback /// (playADPCM), and silencing (silenceChannels). /// /// init() /// loadClip(index, data, size, rate, loop) -> bool /// play(clipIndex) -> channel /// play(clipIndex, volume, pan) -> channel /// stopVoice(channel) /// stopAll() /// setVoiceVolume(channel, vol, pan) /// /// Volume is 0-128 (0=silent, 128=max). Pan is 0-127 (64=center). class AudioManager { public: /// Initialize SPU hardware and reset state void init(); /// Upload ADPCM data to SPU RAM and register as clip index. /// Data must be 16-byte aligned (SPU ADPCM block size). Returns true on success. bool loadClip(int clipIndex, const uint8_t* adpcmData, uint32_t sizeBytes, uint16_t sampleRate, bool loop); /// Play a clip by index. Returns channel (0-23), or -1 if full. /// Volume: 0-128 (128=max). Pan: 0 (left) to 127 (right), 64 = center. int play(int clipIndex, int volume = 128, int pan = 64); /// Stop a specific channel (returned from play()) void stopVoice(int channel); /// Stop all playing channels void stopAll(); /// Set volume/pan on a playing channel void setVoiceVolume(int channel, int volume, int pan = 64); /// Get total SPU RAM used by loaded clips (for visualization) uint32_t getUsedSPURam() const { return m_nextAddr - SPU_RAM_START; } /// Get total SPU RAM available uint32_t getTotalSPURam() const { return SPU_RAM_END - SPU_RAM_START; } /// Get number of loaded clips int getLoadedClipCount() const; /// Reset all clips and free SPU RAM (call on scene unload) void reset(); private: /// Convert 0-128 volume to hardware 0-0x3FFF (fixed-volume mode) static uint16_t volToHw(int v); AudioClip m_clips[MAX_AUDIO_CLIPS]; uint32_t m_nextAddr = SPU_RAM_START; // Bump allocator for SPU RAM }; } // namespace psxsplash