#include "lcs_sas.hpp" #include "psprecomp/common.hpp" #include #include #include #include #include #include namespace lcs { namespace { void set_success(psprecomp::AllegrexContext &ctx) { ctx.set_gpr(2, 0u); } enum class SasVoiceType : std::uint8_t { Off, Vag, Noise, }; enum class SasEnvelopePhase : std::uint8_t { Attack, Decay, Sustain, Release, Off, }; struct SasVoiceState { SasVoiceType type{SasVoiceType::Off}; std::uint32_t data_address{}; std::int32_t data_size{}; bool loop{}; std::int32_t noise_frequency{}; std::int32_t pitch{0x1000}; std::int32_t left_volume{}; std::int32_t right_volume{}; std::int32_t effect_left_volume{}; std::int32_t effect_right_volume{}; std::array adsr_rates{}; std::array adsr_modes{0, 1, 1, 1}; std::int32_t sustain_level{}; std::uint32_t simple_adsr1{}; std::uint32_t simple_adsr2{}; bool adsr_configured{}; SasEnvelopePhase envelope_phase{SasEnvelopePhase::Off}; std::uint32_t key_on_delay_samples{}; bool on{}; bool playing{}; bool paused{}; std::uint32_t envelope_height{}; std::uint64_t total_samples{}; std::uint64_t remaining_samples{}; std::uint32_t decode_offset{}; std::int32_t history1{}; std::int32_t history2{}; std::array block_samples{}; std::uint32_t block_position{28u}; std::uint32_t loop_start_offset{}; std::int32_t loop_start_history1{}; std::int32_t loop_start_history2{}; bool loop_start_valid{}; bool finished{}; std::int16_t current_sample{}; std::int16_t next_sample{}; bool current_sample_valid{}; bool next_sample_valid{}; std::uint32_t pitch_accumulator{}; std::uint32_t noise_lfsr{0x13579BDFu}; std::uint32_t noise_phase{}; std::int16_t noise_sample{}; }; struct SasReverbState { std::int32_t type{-1}; std::int32_t delay{}; std::int32_t feedback{}; std::uint32_t left_volume{}; std::uint32_t right_volume{}; bool dry{true}; bool wet{}; std::vector history_left; std::vector history_right; std::size_t history_cursor{}; }; struct SasState { bool initialized{}; std::uint32_t core_address{}; std::uint32_t grain_size{}; std::uint32_t max_voices{32u}; std::uint32_t output_mode{}; std::uint32_t sample_rate{44100u}; std::array voices{}; SasReverbState reverb{}; }; SasState sas_state{}; std::uint64_t sas_core_mix_calls{}; std::uint64_t sas_core_with_mix_calls{}; bool sas_audio_diagnostics_enabled() { static const bool enabled = std::getenv("PSPRECOMP_AUDIO_DIAG") != nullptr || std::getenv("PSPRECOMP_SAS_DIAG") != nullptr; return enabled; } std::size_t sas_playing_voice_count() { return static_cast(std::count_if( sas_state.voices.begin(), sas_state.voices.end(), [](const SasVoiceState &voice) { return voice.playing && !voice.paused; })); } void sas_log_mix_checkpoint(const char *kind, std::uint64_t count) { if (!sas_audio_diagnostics_enabled()) return; if (count <= 8u || (count % 256u) == 0u) { std::cerr << "[sas] " << kind << " call=" << count << " voices=" << sas_playing_voice_count() << " dry=" << sas_state.reverb.dry << " wet=" << sas_state.reverb.wet << " effect_type=" << sas_state.reverb.type << " grain=" << sas_state.grain_size << "\n"; } } constexpr std::uint32_t kSasErrorInvalidGrain = 0x80420001u; constexpr std::uint32_t kSasErrorInvalidMaxVoices = 0x80420002u; constexpr std::uint32_t kSasErrorInvalidOutputMode = 0x80420003u; constexpr std::uint32_t kSasErrorInvalidSampleRate = 0x80420004u; constexpr std::uint32_t kSasErrorBadAddress = 0x80420005u; constexpr std::uint32_t kSasErrorInvalidVoice = 0x80420010u; constexpr std::uint32_t kSasErrorInvalidNoiseFrequency = 0x80420011u; constexpr std::uint32_t kSasErrorInvalidPitch = 0x80420012u; constexpr std::uint32_t kSasErrorInvalidAdsrMode = 0x80420013u; constexpr std::uint32_t kSasErrorInvalidParameter = 0x80420014u; constexpr std::uint32_t kSasErrorInvalidLoop = 0x80420015u; constexpr std::uint32_t kSasErrorVoicePaused = 0x80420016u; constexpr std::uint32_t kSasErrorInvalidVolume = 0x80420018u; constexpr std::uint32_t kSasErrorInvalidAdsrRate = 0x80420019u; constexpr std::uint32_t kSasErrorReverbType = 0x80420020u; constexpr std::uint32_t kSasErrorReverbFeedback = 0x80420021u; constexpr std::uint32_t kSasErrorReverbDelay = 0x80420022u; constexpr std::uint32_t kSasErrorReverbVolume = 0x80420023u; constexpr std::uint32_t kSasErrorNotInitialized = 0x80420100u; constexpr std::uint32_t kSasEnvelopeMaximum = 0x40000000u; bool sas_valid_core(std::uint32_t core) noexcept { return sas_state.initialized && core == sas_state.core_address; } SasVoiceState *sas_voice(std::uint32_t core, std::int32_t voice, psprecomp::AllegrexContext &ctx) { if (!sas_valid_core(core)) { ctx.set_gpr(2, kSasErrorNotInitialized); return nullptr; } if (voice < 0 || voice >= 32) { ctx.set_gpr(2, kSasErrorInvalidVoice); return nullptr; } return &sas_state.voices[static_cast(voice)]; } void sas_reset_voice_duration(SasVoiceState &voice) noexcept { if (voice.type == SasVoiceType::Vag && voice.data_size > 0) { voice.total_samples = static_cast(voice.data_size / 16) * 28u; voice.remaining_samples = voice.total_samples; } else { voice.total_samples = 0u; voice.remaining_samples = 0u; } } void sas_reset_decoder(SasVoiceState &voice) noexcept { voice.decode_offset = 0u; voice.history1 = 0; voice.history2 = 0; voice.block_position = 28u; voice.loop_start_offset = 0u; voice.loop_start_history1 = 0; voice.loop_start_history2 = 0; voice.loop_start_valid = false; voice.finished = false; voice.current_sample = 0; voice.next_sample = 0; voice.current_sample_valid = false; voice.next_sample_valid = false; voice.pitch_accumulator = 0u; voice.noise_lfsr = 0x13579BDFu; voice.noise_phase = 0u; voice.noise_sample = 0; sas_reset_voice_duration(voice); } constexpr std::uint32_t kSasFallbackAttackSamples = 8u; constexpr std::uint32_t kSasFallbackReleaseSamples = 32u; constexpr std::uint32_t kSasFallbackAttackStep = kSasEnvelopeMaximum / kSasFallbackAttackSamples; constexpr std::uint32_t kSasFallbackReleaseStep = kSasEnvelopeMaximum / kSasFallbackReleaseSamples; std::int64_t sas_walk_envelope_curve(std::int64_t height, std::int32_t mode, std::int32_t rate) noexcept { const std::int64_t r = std::max(0, rate); switch (mode) { case 0: return height + r; case 1: return height - r; case 2: return height + (height <= static_cast(kSasEnvelopeMaximum) * 3 / 4 ? r : r / 4); case 3: { std::int64_t delta = height - static_cast(kSasEnvelopeMaximum); delta += ((-delta) * r) >> 32; return delta + kSasEnvelopeMaximum - (r + 3) / 4; } case 4: { std::int64_t delta = height - static_cast(kSasEnvelopeMaximum); delta += ((-delta) * r) >> 32; return delta + kSasEnvelopeMaximum + 0x4000; } case 5: return r; default: return height; } } std::uint32_t sas_step_envelope(SasVoiceState &voice) noexcept { if (!voice.playing) return 0u; if (!voice.adsr_configured) { if (voice.on) { if (voice.envelope_height < kSasEnvelopeMaximum) { voice.envelope_height = std::min( kSasEnvelopeMaximum, voice.envelope_height + kSasFallbackAttackStep); } } else if (voice.envelope_height <= kSasFallbackReleaseStep) { voice.envelope_height = 0u; voice.envelope_phase = SasEnvelopePhase::Off; voice.playing = false; } else { voice.envelope_height -= kSasFallbackReleaseStep; } return voice.envelope_height; } if (voice.key_on_delay_samples != 0u) { --voice.key_on_delay_samples; voice.envelope_height = 0u; return 0u; } if (!voice.on && voice.envelope_phase != SasEnvelopePhase::Off) voice.envelope_phase = SasEnvelopePhase::Release; std::int64_t height = voice.envelope_height; switch (voice.envelope_phase) { case SasEnvelopePhase::Attack: height = sas_walk_envelope_curve(height, voice.adsr_modes[0], voice.adsr_rates[0]); if (height >= static_cast(kSasEnvelopeMaximum) || height < 0) { height = kSasEnvelopeMaximum; voice.envelope_phase = SasEnvelopePhase::Decay; } break; case SasEnvelopePhase::Decay: height = sas_walk_envelope_curve(height, voice.adsr_modes[1], voice.adsr_rates[1]); if (height <= voice.sustain_level) { height = std::max(0, voice.sustain_level); voice.envelope_phase = SasEnvelopePhase::Sustain; } break; case SasEnvelopePhase::Sustain: height = sas_walk_envelope_curve(height, voice.adsr_modes[2], voice.adsr_rates[2]); if (height <= 0) { height = 0; voice.envelope_phase = SasEnvelopePhase::Release; } else if (height > static_cast(kSasEnvelopeMaximum)) { height = kSasEnvelopeMaximum; } break; case SasEnvelopePhase::Release: height = sas_walk_envelope_curve(height, voice.adsr_modes[3], voice.adsr_rates[3]); if (height <= 0) { height = 0; voice.envelope_phase = SasEnvelopePhase::Off; voice.playing = false; } break; case SasEnvelopePhase::Off: height = 0; voice.playing = false; break; } height = std::clamp(height, 0, kSasEnvelopeMaximum); voice.envelope_height = static_cast(height); return voice.envelope_height; } std::int32_t sas_simple_rate(std::uint32_t value) noexcept { value &= 0x7Fu; if (value == 0x7Fu) return 0; const std::uint64_t base = static_cast(7u - (value & 3u)) << 26u; const std::uint64_t rate = base >> (value >> 2u); return static_cast(std::max(1u, rate)); } std::int32_t sas_exponent_rate(std::uint32_t value) noexcept { value &= 0x7Fu; if (value == 0x7Fu) return 0; const std::uint64_t base = static_cast(7u - (value & 3u)) << 24u; const std::uint64_t rate = base >> (value >> 2u); return static_cast(std::max(1u, rate)); } void sas_decode_simple_adsr(SasVoiceState &voice) noexcept { const std::uint32_t a1 = voice.simple_adsr1; const std::uint32_t a2 = voice.simple_adsr2; voice.adsr_rates[0] = sas_simple_rate(a1 >> 8u); voice.adsr_modes[0] = (a1 & 0x8000u) == 0u ? 0 : 2; const std::uint32_t decay = (a1 >> 4u) & 0x0Fu; voice.adsr_rates[1] = decay == 0u ? 0x7FFFFFFF : static_cast(0x80000000u >> decay); voice.adsr_modes[1] = 3; voice.adsr_modes[2] = static_cast((a2 >> 14u) & 3u); voice.adsr_rates[2] = voice.adsr_modes[2] == 3 ? sas_exponent_rate(a2 >> 6u) : sas_simple_rate(a2 >> 6u); const std::uint32_t release = a2 & 0x1Fu; voice.adsr_modes[3] = (a2 & 0x20u) == 0u ? 1 : 3; if (release == 31u) { voice.adsr_rates[3] = 0; } else if (voice.adsr_modes[3] == 1) { if (release == 30u) voice.adsr_rates[3] = 0x40000000; else if (release == 29u) voice.adsr_rates[3] = 1; else voice.adsr_rates[3] = static_cast(0x10000000u >> release); } else { voice.adsr_rates[3] = release == 0u ? 0x7FFFFFFF : static_cast(0x80000000u >> release); } voice.sustain_level = static_cast(((a1 & 0x0Fu) + 1u) << 26u); voice.adsr_configured = true; } constexpr std::int32_t kVagFilter0[16] = { 0, 60, 115, 98, 122, 0, 0, 52, 55, 60, 0, 0, 0, 2, 125, 0 }; constexpr std::int32_t kVagFilter1[16] = { 0, 0, -52, -55, -60, 0, 0, 0, -2, -125, 0, -91, 0, -216, -6, -151 }; bool sas_decode_next_block(const psprecomp::GuestMemory &memory, SasVoiceState &voice) { if (voice.data_address == 0u || voice.data_size <= 0 || voice.finished) return false; const auto rewind_loop = [&]() { voice.decode_offset = voice.loop_start_valid ? voice.loop_start_offset : 0u; voice.remaining_samples = voice.total_samples; }; if (voice.decode_offset + 16u > static_cast(voice.data_size)) { if (!voice.loop) return false; rewind_loop(); } const std::uint32_t relative_offset = voice.decode_offset; const std::uint32_t base = voice.data_address + relative_offset; if (!memory.contains(base, 16u)) return false; const std::uint32_t header = memory.aot_load8(base); const std::uint32_t flags = memory.aot_load8(base + 1u); const std::int32_t history_before_1 = voice.history1; const std::int32_t history_before_2 = voice.history2; std::int32_t shift = static_cast(header & 0x0Fu); std::int32_t filter = static_cast((header >> 4u) & 0x0Fu); filter &= 0x0F; shift &= 0x0F; if (flags == 7u) { voice.finished = true; return false; } for (std::uint32_t index = 0u; index < 28u; ++index) { const std::uint32_t byte = memory.aot_load8(base + 2u + index / 2u); const std::uint32_t nibble = (index & 1u) != 0u ? (byte >> 4u) : (byte & 0x0Fu); std::int32_t sample = static_cast(nibble << 12u); if (sample & 0x8000) sample = static_cast(sample | 0xFFFF0000u); sample >>= shift; sample += (voice.history1 * kVagFilter0[filter] + voice.history2 * kVagFilter1[filter] + 32) / 64; sample = std::clamp(sample, -32768, 32767); voice.block_samples[index] = static_cast(sample); voice.history2 = voice.history1; voice.history1 = sample; } if (flags == 6u) { voice.loop_start_offset = relative_offset; voice.loop_start_history1 = history_before_1; voice.loop_start_history2 = history_before_2; voice.loop_start_valid = true; } voice.decode_offset += 16u; voice.block_position = 0u; if (flags == 3u) { if (voice.loop) rewind_loop(); else voice.finished = true; } else if (flags == 1u) { voice.finished = true; } return true; } bool sas_fetch_vag_sample(const psprecomp::GuestMemory &memory, SasVoiceState &voice, std::int16_t &sample) { if (voice.block_position >= 28u) { if (voice.finished || !sas_decode_next_block(memory, voice)) return false; } sample = voice.block_samples[voice.block_position++]; if (voice.remaining_samples != 0u) --voice.remaining_samples; return true; } bool sas_prepare_sample_pair(const psprecomp::GuestMemory &memory, SasVoiceState &voice) { if (!voice.current_sample_valid) { if (!sas_fetch_vag_sample(memory, voice, voice.current_sample)) return false; voice.current_sample_valid = true; } if (!voice.next_sample_valid) { std::int16_t next{}; if (sas_fetch_vag_sample(memory, voice, next)) { voice.next_sample = next; voice.next_sample_valid = true; } } return true; } std::int32_t sas_render_vag_sample(const psprecomp::GuestMemory &memory, SasVoiceState &voice) { if (!sas_prepare_sample_pair(memory, voice)) { voice.playing = false; voice.on = false; voice.envelope_height = 0u; return 0; } const std::int32_t current = voice.current_sample; const std::int32_t next = voice.next_sample_valid ? voice.next_sample : current; const std::int32_t sample = current + ((next - current) * static_cast(voice.pitch_accumulator)) / 0x1000; const std::uint32_t pitch = voice.pitch < 0 ? 0u : static_cast(voice.pitch); voice.pitch_accumulator += pitch; while (voice.pitch_accumulator >= 0x1000u && voice.playing) { voice.pitch_accumulator -= 0x1000u; if (!voice.next_sample_valid) { voice.playing = false; voice.on = false; voice.envelope_height = 0u; break; } voice.current_sample = voice.next_sample; voice.current_sample_valid = true; std::int16_t following{}; if (sas_fetch_vag_sample(memory, voice, following)) { voice.next_sample = following; voice.next_sample_valid = true; } else { voice.next_sample_valid = false; } } return sample; } std::int32_t sas_render_noise_sample(SasVoiceState &voice) noexcept { voice.noise_phase += static_cast(voice.noise_frequency + 1); while (voice.noise_phase >= 64u) { voice.noise_phase -= 64u; const std::uint32_t feedback = ((voice.noise_lfsr >> 0u) ^ (voice.noise_lfsr >> 1u) ^ (voice.noise_lfsr >> 21u) ^ (voice.noise_lfsr >> 31u)) & 1u; voice.noise_lfsr = (voice.noise_lfsr >> 1u) | (feedback << 31u); voice.noise_sample = (voice.noise_lfsr & 1u) != 0u ? 12288 : -12288; } return voice.noise_sample; } void sas_render_voice(const psprecomp::GuestMemory &memory, SasVoiceState &voice, std::vector &dry_mix, std::vector &effect_send, std::uint32_t frames) { if (!voice.playing || voice.paused || voice.type == SasVoiceType::Off) return; for (std::uint32_t frame = 0u; frame < frames && voice.playing; ++frame) { const std::uint32_t envelope = sas_step_envelope(voice); if (!voice.playing || envelope == 0u) continue; std::int32_t sample = 0; if (voice.type == SasVoiceType::Vag) sample = sas_render_vag_sample(memory, voice); else if (voice.type == SasVoiceType::Noise) sample = sas_render_noise_sample(voice); const auto accumulate = [&](std::vector &target, std::int32_t left_volume, std::int32_t right_volume) { const std::int64_t left_gain = (static_cast(left_volume) * envelope) >> 30; const std::int64_t right_gain = (static_cast(right_volume) * envelope) >> 30; target[frame * 2u] += static_cast((sample * left_gain) >> 12); target[frame * 2u + 1u] += static_cast((sample * right_gain) >> 12); }; accumulate(dry_mix, voice.left_volume, voice.right_volume); accumulate(effect_send, voice.effect_left_volume, voice.effect_right_volume); } } void sas_render_buses(const psprecomp::GuestMemory &memory, std::uint32_t frames, std::vector &dry_mix, std::vector &effect_send) { dry_mix.assign(static_cast(frames) * 2u, 0); effect_send.assign(static_cast(frames) * 2u, 0); for (auto &voice : sas_state.voices) sas_render_voice(memory, voice, dry_mix, effect_send, frames); } void sas_process_effect_send(const std::vector &effect_send, std::vector &wet_mix, std::uint32_t frames) { wet_mix.assign(static_cast(frames) * 2u, 0); if (!sas_state.reverb.wet) return; const std::int64_t global_left = sas_state.reverb.left_volume; const std::int64_t global_right = sas_state.reverb.right_volume; if (sas_state.reverb.type < 0) { for (std::uint32_t frame = 0u; frame < frames; ++frame) { wet_mix[frame * 2u] = static_cast( (static_cast(effect_send[frame * 2u]) * global_left) >> 12); wet_mix[frame * 2u + 1u] = static_cast( (static_cast(effect_send[frame * 2u + 1u]) * global_right) >> 12); } return; } constexpr std::size_t kEffectHistoryFrames = 16384u; auto &reverb = sas_state.reverb; if (reverb.history_left.size() != kEffectHistoryFrames) { reverb.history_left.assign(kEffectHistoryFrames, 0); reverb.history_right.assign(kEffectHistoryFrames, 0); reverb.history_cursor = 0u; } const std::size_t type_offset = static_cast(std::clamp(reverb.type, 0, 8)) * 73u; const std::size_t delay_frames = std::clamp( 64u + type_offset + static_cast(reverb.delay) * 24u, 1u, kEffectHistoryFrames - 1u); const std::int64_t feedback = std::clamp(reverb.feedback, 0, 127); for (std::uint32_t frame = 0u; frame < frames; ++frame) { const std::size_t read_index = (reverb.history_cursor + kEffectHistoryFrames - delay_frames) % kEffectHistoryFrames; const std::int64_t delayed_left = reverb.history_left[read_index]; const std::int64_t delayed_right = reverb.history_right[read_index]; const std::int64_t input_left = effect_send[frame * 2u]; const std::int64_t input_right = effect_send[frame * 2u + 1u]; const std::int64_t effect_left = input_left + delayed_left; const std::int64_t effect_right = input_right + delayed_right; const std::int64_t next_left = input_left + (delayed_left * feedback) / 128; const std::int64_t next_right = input_right + (delayed_right * feedback) / 128; reverb.history_left[reverb.history_cursor] = static_cast( std::clamp(next_left, -0x7FFFFF, 0x7FFFFF)); reverb.history_right[reverb.history_cursor] = static_cast( std::clamp(next_right, -0x7FFFFF, 0x7FFFFF)); reverb.history_cursor = (reverb.history_cursor + 1u) % kEffectHistoryFrames; wet_mix[frame * 2u] = static_cast((effect_left * global_left) >> 12); wet_mix[frame * 2u + 1u] = static_cast((effect_right * global_right) >> 12); } } void sas_mix_into(psprecomp::Runtime &rt, std::uint32_t output, std::uint32_t frames, bool include_input = false, std::uint32_t input_left = 0x1000u, std::uint32_t input_right = 0x1000u) { static thread_local std::vector dry_mix; static thread_local std::vector effect_send; static thread_local std::vector wet_mix; sas_render_buses(rt.memory(), frames, dry_mix, effect_send); sas_process_effect_send(effect_send, wet_mix, frames); for (std::uint32_t frame = 0u; frame < frames; ++frame) { std::int64_t l = 0; std::int64_t r = 0; if (include_input) { const auto input_l = static_cast( rt.memory().aot_load16(output + frame * 4u)); const auto input_r = static_cast( rt.memory().aot_load16(output + frame * 4u + 2u)); l += (static_cast(input_l) * input_left) >> 12; r += (static_cast(input_r) * input_right) >> 12; } if (sas_state.reverb.dry) { l += dry_mix[frame * 2u]; r += dry_mix[frame * 2u + 1u]; } if (sas_state.reverb.wet) { l += wet_mix[frame * 2u]; r += wet_mix[frame * 2u + 1u]; } rt.memory().store16(output + frame * 4u, static_cast( static_cast(std::clamp(l, -32768, 32767)))); rt.memory().store16(output + frame * 4u + 2u, static_cast( static_cast(std::clamp(r, -32768, 32767)))); } } void sas_mix_raw(psprecomp::Runtime &rt, std::uint32_t output, std::uint32_t frames) { static thread_local std::vector dry_mix; static thread_local std::vector effect_send; sas_render_buses(rt.memory(), frames, dry_mix, effect_send); const std::uint32_t left_base = output; const std::uint32_t right_base = output + frames * 2u; const std::uint32_t send_left_base = output + frames * 4u; const std::uint32_t send_right_base = output + frames * 6u; for (std::uint32_t frame = 0u; frame < frames; ++frame) { const auto store = [&](std::uint32_t base, std::int32_t value) { rt.memory().store16(base + frame * 2u, static_cast( static_cast(std::clamp(value, -32768, 32767)))); }; store(left_base, dry_mix[frame * 2u]); store(right_base, dry_mix[frame * 2u + 1u]); store(send_left_base, effect_send[frame * 2u]); store(send_right_base, effect_send[frame * 2u + 1u]); } } } void register_sas_hle(psprecomp::Runtime &runtime) { runtime.register_hle("sceSasCore", 0x42778A9Fu, [](psprecomp::Runtime &rt, psprecomp::AllegrexContext &ctx) { const std::uint32_t core = ctx.gpr[4]; const std::uint32_t grain = ctx.gpr[5]; const std::uint32_t max_voices = ctx.gpr[6]; const std::uint32_t output_mode = ctx.gpr[7]; const std::uint32_t sample_rate = ctx.gpr[8]; if ((core & 0x3Fu) != 0u || !rt.memory().contains(core, 64u)) { ctx.set_gpr(2, kSasErrorBadAddress); return; } if (max_voices == 0u || max_voices > 32u) { ctx.set_gpr(2, kSasErrorInvalidMaxVoices); return; } if (grain < 0x40u || grain > 0x800u || (grain & 0x1Fu) != 0u) { ctx.set_gpr(2, kSasErrorInvalidGrain); return; } if (output_mode > 1u) { ctx.set_gpr(2, kSasErrorInvalidOutputMode); return; } if (sample_rate != 44100u) { ctx.set_gpr(2, kSasErrorInvalidSampleRate); return; } sas_state = SasState{}; sas_core_mix_calls = 0u; sas_core_with_mix_calls = 0u; sas_state.initialized = true; sas_state.core_address = core; sas_state.grain_size = grain; sas_state.max_voices = 32u; sas_state.output_mode = output_mode; sas_state.sample_rate = sample_rate; for (auto &voice : sas_state.voices) voice.pitch = 0x1000; rt.memory().zero(core, 64u); set_success(ctx); }); runtime.register_hle("sceSasCore", 0x99944089u, [](psprecomp::Runtime &rt, psprecomp::AllegrexContext &ctx) { auto *voice = sas_voice(ctx.gpr[4], static_cast(ctx.gpr[5]), ctx); if (!voice) return; const std::uint32_t address = ctx.gpr[6]; std::int32_t size = static_cast(ctx.gpr[7]); const std::int32_t loop = static_cast(ctx.gpr[8]); if (size == 0 || (static_cast(size) & 0xFu) != 0u) { ctx.set_gpr(2, kSasErrorInvalidParameter); return; } if (loop != 0 && loop != 1) { ctx.set_gpr(2, kSasErrorInvalidLoop); return; } if (size < 0) size = 0; if (size > 0 && !rt.memory().contains(address, static_cast(size))) { set_success(ctx); return; } voice->type = SasVoiceType::Vag; voice->data_address = address; voice->data_size = size; voice->loop = loop != 0; sas_reset_decoder(*voice); if (voice->on) voice->playing = true; set_success(ctx); }); runtime.register_hle("sceSasCore", 0xB7660A23u, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { auto *voice = sas_voice(ctx.gpr[4], static_cast(ctx.gpr[5]), ctx); if (!voice) return; const std::int32_t frequency = static_cast(ctx.gpr[6]); if (frequency < 0 || frequency >= 64) { ctx.set_gpr(2, kSasErrorInvalidNoiseFrequency); return; } voice->type = SasVoiceType::Noise; voice->noise_frequency = frequency; sas_reset_decoder(*voice); if (voice->on) voice->playing = true; set_success(ctx); }); runtime.register_hle("sceSasCore", 0xAD84D37Fu, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { auto *voice = sas_voice(ctx.gpr[4], static_cast(ctx.gpr[5]), ctx); if (!voice) return; const std::int32_t pitch = static_cast(ctx.gpr[6]); if (pitch < 0 || pitch > 0x4000) { ctx.set_gpr(2, kSasErrorInvalidPitch); return; } voice->pitch = pitch; set_success(ctx); }); runtime.register_hle("sceSasCore", 0x440CA7D8u, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { auto *voice = sas_voice(ctx.gpr[4], static_cast(ctx.gpr[5]), ctx); if (!voice) return; const std::array volumes{ static_cast(ctx.gpr[6]), static_cast(ctx.gpr[7]), static_cast(ctx.gpr[8]), static_cast(ctx.gpr[9])}; for (const auto volume : volumes) { if (static_cast(volume) < -0x1000ll || static_cast(volume) > 0x1000ll) { ctx.set_gpr(2, kSasErrorInvalidVolume); return; } } voice->left_volume = volumes[0]; voice->right_volume = volumes[1]; voice->effect_left_volume = volumes[2]; voice->effect_right_volume = volumes[3]; if (sas_audio_diagnostics_enabled() && (volumes[2] != 0 || volumes[3] != 0)) { std::cerr << "[sas] volume voice=" << static_cast(ctx.gpr[5]) << " dry=" << volumes[0] << "," << volumes[1] << " effect=" << volumes[2] << "," << volumes[3] << "\n"; } set_success(ctx); }); runtime.register_hle("sceSasCore", 0x019B25EBu, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { auto *voice = sas_voice(ctx.gpr[4], static_cast(ctx.gpr[5]), ctx); if (!voice) return; const std::uint32_t flags = ctx.gpr[6] & 0xFu; const std::array rates{ static_cast(ctx.gpr[7]), static_cast(ctx.gpr[8]), static_cast(ctx.gpr[9]), static_cast(ctx.gpr[10])}; for (std::size_t i = 0; i < rates.size(); ++i) { if ((flags & (1u << i)) != 0u && rates[i] < 0) { ctx.set_gpr(2, kSasErrorInvalidAdsrRate); return; } } for (std::size_t i = 0; i < rates.size(); ++i) if ((flags & (1u << i)) != 0u) voice->adsr_rates[i] = rates[i]; if (flags != 0u) voice->adsr_configured = true; set_success(ctx); }); runtime.register_hle("sceSasCore", 0x9EC3676Au, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { auto *voice = sas_voice(ctx.gpr[4], static_cast(ctx.gpr[5]), ctx); if (!voice) return; const std::uint32_t flags = ctx.gpr[6] & 0xFu; std::array modes{ static_cast(ctx.gpr[7] & 0x7FFFFFFFu), static_cast(ctx.gpr[8] & 0x7FFFFFFFu), static_cast(ctx.gpr[9] & 0x7FFFFFFFu), static_cast(ctx.gpr[10] & 0x7FFFFFFFu)}; const bool invalid_attack = modes[0] > 5 || (modes[0] & 1) != 0; const bool invalid_decay = modes[1] > 5 || (modes[1] & 1) != 1; const bool invalid_sustain = modes[2] > 5; const bool invalid_release = modes[3] > 5 || (modes[3] & 1) != 1; const std::array invalid{invalid_attack, invalid_decay, invalid_sustain, invalid_release}; for (std::size_t i = 0; i < invalid.size(); ++i) { if ((flags & (1u << i)) != 0u && invalid[i]) { ctx.set_gpr(2, kSasErrorInvalidAdsrMode); return; } } for (std::size_t i = 0; i < modes.size(); ++i) if ((flags & (1u << i)) != 0u) voice->adsr_modes[i] = modes[i]; if (flags != 0u) voice->adsr_configured = true; set_success(ctx); }); runtime.register_hle("sceSasCore", 0x5F9529F6u, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { auto *voice = sas_voice(ctx.gpr[4], static_cast(ctx.gpr[5]), ctx); if (!voice) return; voice->sustain_level = static_cast(ctx.gpr[6]); voice->adsr_configured = true; set_success(ctx); }); runtime.register_hle("sceSasCore", 0xCBCD4F79u, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { auto *voice = sas_voice(ctx.gpr[4], static_cast(ctx.gpr[5]), ctx); if (!voice) return; if (((ctx.gpr[7] >> 13u) & 1u) != 0u) { ctx.set_gpr(2, kSasErrorInvalidAdsrMode); return; } voice->simple_adsr1 = ctx.gpr[6] & 0xFFFFu; voice->simple_adsr2 = ctx.gpr[7] & 0xFFFFu; sas_decode_simple_adsr(*voice); set_success(ctx); }); runtime.register_hle("sceSasCore", 0x76F01ACAu, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { auto *voice = sas_voice(ctx.gpr[4], static_cast(ctx.gpr[5]), ctx); if (!voice) return; if (voice->paused || voice->on) { ctx.set_gpr(2, kSasErrorVoicePaused); return; } sas_reset_decoder(*voice); voice->on = true; voice->playing = voice->type != SasVoiceType::Off; voice->envelope_height = 0u; voice->envelope_phase = SasEnvelopePhase::Attack; voice->key_on_delay_samples = voice->adsr_configured ? (voice->type == SasVoiceType::Vag ? 33u : 32u) : 0u; if (sas_audio_diagnostics_enabled()) std::cerr << "[sas] keyon voice=" << static_cast(ctx.gpr[5]) << " type=" << static_cast(voice->type) << " pitch=" << voice->pitch << " loop=" << voice->loop << "\n"; set_success(ctx); }); runtime.register_hle("sceSasCore", 0xA0CF2FA4u, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { auto *voice = sas_voice(ctx.gpr[4], static_cast(ctx.gpr[5]), ctx); if (!voice) return; if (voice->paused || !voice->on) { ctx.set_gpr(2, kSasErrorVoicePaused); return; } voice->on = false; voice->envelope_phase = SasEnvelopePhase::Release; if (sas_audio_diagnostics_enabled()) std::cerr << "[sas] keyoff voice=" << static_cast(ctx.gpr[5]) << " loop=" << voice->loop << " release_mode=" << voice->adsr_modes[3] << " release_rate=" << voice->adsr_rates[3] << " height=" << voice->envelope_height << "\n"; set_success(ctx); }); runtime.register_hle("sceSasCore", 0x787D04D5u, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { if (!sas_valid_core(ctx.gpr[4])) { ctx.set_gpr(2, kSasErrorNotInitialized); return; } std::uint32_t mask = ctx.gpr[5]; const bool pause = ctx.gpr[6] != 0u; for (std::size_t i = 0; i < sas_state.voices.size(); ++i) if ((mask & (1u << i)) != 0u) sas_state.voices[i].paused = pause; set_success(ctx); }); runtime.register_hle("sceSasCore", 0x2C8E6AB3u, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { if (!sas_valid_core(ctx.gpr[4])) { ctx.set_gpr(2, kSasErrorNotInitialized); return; } std::uint32_t flags = 0u; for (std::size_t i = 0; i < sas_state.voices.size(); ++i) if (sas_state.voices[i].paused) flags |= 1u << i; ctx.set_gpr(2, flags); }); runtime.register_hle("sceSasCore", 0x68A46B95u, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { if (!sas_valid_core(ctx.gpr[4])) { ctx.set_gpr(2, kSasErrorNotInitialized); return; } std::uint32_t flags = 0u; for (std::size_t i = 0; i < sas_state.voices.size(); ++i) if (!sas_state.voices[i].playing) flags |= 1u << i; ctx.set_gpr(2, flags); }); runtime.register_hle("sceSasCore", 0x74AE582Au, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { auto *voice = sas_voice(ctx.gpr[4], static_cast(ctx.gpr[5]), ctx); if (!voice) return; ctx.set_gpr(2, voice->envelope_height); }); runtime.register_hle("sceSasCore", 0x33D4AB37u, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { if (!sas_valid_core(ctx.gpr[4])) { ctx.set_gpr(2, kSasErrorNotInitialized); return; } const std::int32_t type = static_cast(ctx.gpr[5]); if (type < -1 || type > 8) { ctx.set_gpr(2, kSasErrorReverbType); return; } if (sas_state.reverb.type != type) { sas_state.reverb.type = type; sas_state.reverb.history_left.clear(); sas_state.reverb.history_right.clear(); sas_state.reverb.history_cursor = 0u; } set_success(ctx); }); runtime.register_hle("sceSasCore", 0x267A6DD2u, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { if (!sas_valid_core(ctx.gpr[4])) { ctx.set_gpr(2, kSasErrorNotInitialized); return; } const std::int32_t delay = static_cast(ctx.gpr[5]); const std::int32_t feedback = static_cast(ctx.gpr[6]); if (delay < 0 || delay >= 128) { ctx.set_gpr(2, kSasErrorReverbDelay); return; } if (feedback < 0 || feedback >= 128) { ctx.set_gpr(2, kSasErrorReverbFeedback); return; } sas_state.reverb.delay = delay; sas_state.reverb.feedback = feedback; set_success(ctx); }); runtime.register_hle("sceSasCore", 0xD5A229C9u, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { if (!sas_valid_core(ctx.gpr[4])) { ctx.set_gpr(2, kSasErrorNotInitialized); return; } if (ctx.gpr[5] > 0x1000u || ctx.gpr[6] > 0x1000u) { ctx.set_gpr(2, kSasErrorReverbVolume); return; } sas_state.reverb.left_volume = ctx.gpr[5]; sas_state.reverb.right_volume = ctx.gpr[6]; set_success(ctx); }); runtime.register_hle("sceSasCore", 0xF983B186u, [](psprecomp::Runtime &, psprecomp::AllegrexContext &ctx) { if (!sas_valid_core(ctx.gpr[4])) { ctx.set_gpr(2, kSasErrorNotInitialized); return; } sas_state.reverb.dry = ctx.gpr[5] != 0u; sas_state.reverb.wet = ctx.gpr[6] != 0u; set_success(ctx); }); runtime.register_hle("sceSasCore", 0xA3589D81u, [](psprecomp::Runtime &rt, psprecomp::AllegrexContext &ctx) { if (!sas_valid_core(ctx.gpr[4])) { ctx.set_gpr(2, kSasErrorNotInitialized); return; } const std::uint32_t output = ctx.gpr[5]; const std::size_t bytes = static_cast(sas_state.grain_size) * (sas_state.output_mode == 0u ? 4u : 8u); if (!rt.memory().contains(output, bytes)) { ctx.set_gpr(2, kSasErrorInvalidParameter); return; } ++sas_core_mix_calls; sas_log_mix_checkpoint("core", sas_core_mix_calls); if (sas_state.output_mode == 0u) sas_mix_into(rt, output, sas_state.grain_size); else sas_mix_raw(rt, output, sas_state.grain_size); set_success(ctx); }); runtime.register_hle("sceSasCore", 0x50A14DFCu, [](psprecomp::Runtime &rt, psprecomp::AllegrexContext &ctx) { if (!sas_valid_core(ctx.gpr[4])) { ctx.set_gpr(2, kSasErrorNotInitialized); return; } if (sas_state.output_mode == 1u) { ctx.set_gpr(2, 0x800001FFu); return; } const std::uint32_t inout = ctx.gpr[5]; const std::size_t bytes = static_cast(sas_state.grain_size) * 4u; if (!rt.memory().contains(inout, bytes)) { ctx.set_gpr(2, kSasErrorInvalidParameter); return; } const std::uint32_t input_left = ctx.gpr[6]; const std::uint32_t input_right = ctx.gpr[7]; if (input_left > 0x1000u || input_right > 0x1000u) { ctx.set_gpr(2, kSasErrorInvalidVolume); return; } ++sas_core_with_mix_calls; sas_log_mix_checkpoint("core-with-mix", sas_core_with_mix_calls); sas_mix_into(rt, inout, sas_state.grain_size, true, input_left, input_right); set_success(ctx); }); } }