Files
2026-09-24 19:34:17 -03:00

978 lines
41 KiB
C++

#include "lcs_sas.hpp"
#include "psprecomp/common.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <vector>
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<std::int32_t, 4> adsr_rates{};
std::array<std::int32_t, 4> 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<std::int16_t, 28> 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<std::int32_t> history_left;
std::vector<std::int32_t> 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<SasVoiceState, 32> 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::size_t>(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<std::size_t>(voice)];
}
void sas_reset_voice_duration(SasVoiceState &voice) noexcept {
if (voice.type == SasVoiceType::Vag && voice.data_size > 0) {
voice.total_samples = static_cast<std::uint64_t>(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<std::int64_t>(0, rate);
switch (mode) {
case 0:
return height + r;
case 1:
return height - r;
case 2:
return height + (height <= static_cast<std::int64_t>(kSasEnvelopeMaximum) * 3 / 4
? r : r / 4);
case 3: {
std::int64_t delta = height - static_cast<std::int64_t>(kSasEnvelopeMaximum);
delta += ((-delta) * r) >> 32;
return delta + kSasEnvelopeMaximum - (r + 3) / 4;
}
case 4: {
std::int64_t delta = height - static_cast<std::int64_t>(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<std::uint32_t>(
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<std::int64_t>(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<std::int64_t>(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<std::int64_t>(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<std::int64_t>(height, 0, kSasEnvelopeMaximum);
voice.envelope_height = static_cast<std::uint32_t>(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<std::uint64_t>(7u - (value & 3u)) << 26u;
const std::uint64_t rate = base >> (value >> 2u);
return static_cast<std::int32_t>(std::max<std::uint64_t>(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<std::uint64_t>(7u - (value & 3u)) << 24u;
const std::uint64_t rate = base >> (value >> 2u);
return static_cast<std::int32_t>(std::max<std::uint64_t>(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<std::int32_t>(0x80000000u >> decay);
voice.adsr_modes[1] = 3;
voice.adsr_modes[2] = static_cast<std::int32_t>((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<std::int32_t>(0x10000000u >> release);
} else {
voice.adsr_rates[3] = release == 0u ? 0x7FFFFFFF :
static_cast<std::int32_t>(0x80000000u >> release);
}
voice.sustain_level = static_cast<std::int32_t>(((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<std::uint32_t>(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<std::int32_t>(header & 0x0Fu);
std::int32_t filter = static_cast<std::int32_t>((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<std::int32_t>(nibble << 12u);
if (sample & 0x8000) sample = static_cast<std::int32_t>(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<std::int16_t>(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<std::int32_t>(voice.pitch_accumulator)) / 0x1000;
const std::uint32_t pitch = voice.pitch < 0 ? 0u : static_cast<std::uint32_t>(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<std::uint32_t>(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<std::int32_t> &dry_mix,
std::vector<std::int32_t> &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<std::int32_t> &target,
std::int32_t left_volume,
std::int32_t right_volume) {
const std::int64_t left_gain =
(static_cast<std::int64_t>(left_volume) * envelope) >> 30;
const std::int64_t right_gain =
(static_cast<std::int64_t>(right_volume) * envelope) >> 30;
target[frame * 2u] += static_cast<std::int32_t>((sample * left_gain) >> 12);
target[frame * 2u + 1u] += static_cast<std::int32_t>((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<std::int32_t> &dry_mix,
std::vector<std::int32_t> &effect_send) {
dry_mix.assign(static_cast<std::size_t>(frames) * 2u, 0);
effect_send.assign(static_cast<std::size_t>(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<std::int32_t> &effect_send,
std::vector<std::int32_t> &wet_mix,
std::uint32_t frames) {
wet_mix.assign(static_cast<std::size_t>(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<std::int32_t>(
(static_cast<std::int64_t>(effect_send[frame * 2u]) * global_left) >> 12);
wet_mix[frame * 2u + 1u] = static_cast<std::int32_t>(
(static_cast<std::int64_t>(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::size_t>(std::clamp(reverb.type, 0, 8)) * 73u;
const std::size_t delay_frames = std::clamp<std::size_t>(
64u + type_offset + static_cast<std::size_t>(reverb.delay) * 24u,
1u, kEffectHistoryFrames - 1u);
const std::int64_t feedback = std::clamp<std::int32_t>(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::int32_t>(
std::clamp<std::int64_t>(next_left, -0x7FFFFF, 0x7FFFFF));
reverb.history_right[reverb.history_cursor] = static_cast<std::int32_t>(
std::clamp<std::int64_t>(next_right, -0x7FFFFF, 0x7FFFFF));
reverb.history_cursor = (reverb.history_cursor + 1u) % kEffectHistoryFrames;
wet_mix[frame * 2u] = static_cast<std::int32_t>((effect_left * global_left) >> 12);
wet_mix[frame * 2u + 1u] = static_cast<std::int32_t>((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<std::int32_t> dry_mix;
static thread_local std::vector<std::int32_t> effect_send;
static thread_local std::vector<std::int32_t> 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<std::int16_t>(
rt.memory().aot_load16(output + frame * 4u));
const auto input_r = static_cast<std::int16_t>(
rt.memory().aot_load16(output + frame * 4u + 2u));
l += (static_cast<std::int64_t>(input_l) * input_left) >> 12;
r += (static_cast<std::int64_t>(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<std::uint16_t>(
static_cast<std::int16_t>(std::clamp<std::int64_t>(l, -32768, 32767))));
rt.memory().store16(output + frame * 4u + 2u, static_cast<std::uint16_t>(
static_cast<std::int16_t>(std::clamp<std::int64_t>(r, -32768, 32767))));
}
}
void sas_mix_raw(psprecomp::Runtime &rt, std::uint32_t output, std::uint32_t frames) {
static thread_local std::vector<std::int32_t> dry_mix;
static thread_local std::vector<std::int32_t> 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<std::uint16_t>(
static_cast<std::int16_t>(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<std::int32_t>(ctx.gpr[5]), ctx);
if (!voice) return;
const std::uint32_t address = ctx.gpr[6];
std::int32_t size = static_cast<std::int32_t>(ctx.gpr[7]);
const std::int32_t loop = static_cast<std::int32_t>(ctx.gpr[8]);
if (size == 0 || (static_cast<std::uint32_t>(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<std::size_t>(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<std::int32_t>(ctx.gpr[5]), ctx);
if (!voice) return;
const std::int32_t frequency = static_cast<std::int32_t>(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<std::int32_t>(ctx.gpr[5]), ctx);
if (!voice) return;
const std::int32_t pitch = static_cast<std::int32_t>(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<std::int32_t>(ctx.gpr[5]), ctx);
if (!voice) return;
const std::array<std::int32_t, 4> volumes{
static_cast<std::int32_t>(ctx.gpr[6]), static_cast<std::int32_t>(ctx.gpr[7]),
static_cast<std::int32_t>(ctx.gpr[8]), static_cast<std::int32_t>(ctx.gpr[9])};
for (const auto volume : volumes) {
if (static_cast<std::int64_t>(volume) < -0x1000ll || static_cast<std::int64_t>(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<std::int32_t>(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<std::int32_t>(ctx.gpr[5]), ctx);
if (!voice) return;
const std::uint32_t flags = ctx.gpr[6] & 0xFu;
const std::array<std::int32_t, 4> rates{
static_cast<std::int32_t>(ctx.gpr[7]), static_cast<std::int32_t>(ctx.gpr[8]),
static_cast<std::int32_t>(ctx.gpr[9]), static_cast<std::int32_t>(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<std::int32_t>(ctx.gpr[5]), ctx);
if (!voice) return;
const std::uint32_t flags = ctx.gpr[6] & 0xFu;
std::array<std::int32_t, 4> modes{
static_cast<std::int32_t>(ctx.gpr[7] & 0x7FFFFFFFu), static_cast<std::int32_t>(ctx.gpr[8] & 0x7FFFFFFFu),
static_cast<std::int32_t>(ctx.gpr[9] & 0x7FFFFFFFu), static_cast<std::int32_t>(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<bool, 4> 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<std::int32_t>(ctx.gpr[5]), ctx);
if (!voice) return;
voice->sustain_level = static_cast<std::int32_t>(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<std::int32_t>(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<std::int32_t>(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<std::int32_t>(ctx.gpr[5])
<< " type=" << static_cast<int>(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<std::int32_t>(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<std::int32_t>(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<std::int32_t>(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<std::int32_t>(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<std::int32_t>(ctx.gpr[5]);
const std::int32_t feedback = static_cast<std::int32_t>(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<std::size_t>(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<std::size_t>(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);
});
}
}