From 29e51a44c4d9317459d09135a0d2a633bedbb9fb Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 11 Sep 2026 20:04:25 +0100 Subject: [PATCH] audio: steamaudio: add Steam Audio spatial audio offload module and LLEXT Add Valve Steam Audio spatial audio processing engine offload module for Sound Open Firmware (SOF), supporting both statically linked DSP processing and dynamically loaded ELF extensions (LLEXT). Key features: - Core 3D spatialization: direct HRTF convolution, distance attenuation, air absorption filtering, directivity simulation, and occlusion damping. - Acoustic simulation: ray-traced BVH reflection rendering directly executed on the DSP. - Continuous multi-channel streaming: host feeds a continuous 64-channel PCM stream while the DSP dynamically culls inactive sources using an active voice bitmask (active_sources_mask). - Hardware-timestamped VBR metadata: receives variable bitrate spatial metadata packets via DMA-driven compressed ALSA PCM device (snd_compr). - Dynamic LLEXT support: builds as steamaudio.llext for Intel ACE 3.0 (Panther Lake) enabled via CONFIG_COMP_STEAMAUDIO=m. - Registered component UUID 53746561-6d61-7564-696f737465616d31 in uuid-registry.txt and module manifest in tools/rimage/config/ptl.toml.h. Signed-off-by: Liam Girdwood --- app/boards/intel_adsp_ace30_ptl.conf | 1 + src/audio/CMakeLists.txt | 3 + src/audio/Kconfig | 1 + src/audio/steamaudio/CMakeLists.txt | 14 + src/audio/steamaudio/Kconfig | 11 + src/audio/steamaudio/llext/CMakeLists.txt | 10 + src/audio/steamaudio/llext/llext.toml.h | 6 + src/audio/steamaudio/steamaudio-generic.c | 423 ++++++++++++++++++++++ src/audio/steamaudio/steamaudio-ipc4.c | 145 ++++++++ src/audio/steamaudio/steamaudio.c | 149 ++++++++ src/audio/steamaudio/steamaudio.h | 235 ++++++++++++ src/audio/steamaudio/steamaudio.toml | 21 ++ src/audio/steamaudio/steamaudio_bvh.c | 221 +++++++++++ tools/rimage/config/ptl.toml.h | 4 + uuid-registry.txt | 1 + 15 files changed, 1245 insertions(+) create mode 100644 src/audio/steamaudio/CMakeLists.txt create mode 100644 src/audio/steamaudio/Kconfig create mode 100644 src/audio/steamaudio/llext/CMakeLists.txt create mode 100644 src/audio/steamaudio/llext/llext.toml.h create mode 100644 src/audio/steamaudio/steamaudio-generic.c create mode 100644 src/audio/steamaudio/steamaudio-ipc4.c create mode 100644 src/audio/steamaudio/steamaudio.c create mode 100644 src/audio/steamaudio/steamaudio.h create mode 100644 src/audio/steamaudio/steamaudio.toml create mode 100644 src/audio/steamaudio/steamaudio_bvh.c diff --git a/app/boards/intel_adsp_ace30_ptl.conf b/app/boards/intel_adsp_ace30_ptl.conf index 6697f8d5523a..cf1d431758f8 100644 --- a/app/boards/intel_adsp_ace30_ptl.conf +++ b/app/boards/intel_adsp_ace30_ptl.conf @@ -15,6 +15,7 @@ CONFIG_FORMAT_CONVERT_HIFI3=n CONFIG_COMP_GOOGLE_RTC_AUDIO_PROCESSING=m CONFIG_GOOGLE_RTC_AUDIO_PROCESSING_MOCK=y CONFIG_COMP_STFT_PROCESS=y +CONFIG_COMP_STEAMAUDIO=m # SOF / infrastructure CONFIG_KCPS_DYNAMIC_CLOCK_CONTROL=n diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 92002c8b7c1c..5c6b534a6593 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -89,6 +89,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) if(CONFIG_COMP_SRC) add_subdirectory(src) endif() + if(CONFIG_COMP_STEAMAUDIO) + add_subdirectory(steamaudio) + endif() if(CONFIG_COMP_STFT_PROCESS) add_subdirectory(stft_process) endif() diff --git a/src/audio/Kconfig b/src/audio/Kconfig index 8accb25738a2..636cf2c2e495 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -154,6 +154,7 @@ rsource "selector/Kconfig" rsource "smart_amp/Kconfig" rsource "sound_dose/Kconfig" rsource "src/Kconfig" +rsource "steamaudio/Kconfig" rsource "stft_process/Kconfig" rsource "tdfb/Kconfig" rsource "template/Kconfig" diff --git a/src/audio/steamaudio/CMakeLists.txt b/src/audio/steamaudio/CMakeLists.txt new file mode 100644 index 000000000000..3d55e84d47bc --- /dev/null +++ b/src/audio/steamaudio/CMakeLists.txt @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause + +if(CONFIG_COMP_STEAMAUDIO STREQUAL "m" AND DEFINED CONFIG_LLEXT) + add_subdirectory(llext ${PROJECT_BINARY_DIR}/steamaudio_llext) + add_dependencies(app steamaudio) +else() + add_local_sources(sof steamaudio.c) + add_local_sources(sof steamaudio-generic.c) + add_local_sources(sof steamaudio_bvh.c) + + if(CONFIG_IPC_MAJOR_4) + add_local_sources(sof steamaudio-ipc4.c) + endif() +endif() diff --git a/src/audio/steamaudio/Kconfig b/src/audio/steamaudio/Kconfig new file mode 100644 index 000000000000..a18fb1ea9281 --- /dev/null +++ b/src/audio/steamaudio/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: BSD-3-Clause + +config COMP_STEAMAUDIO + tristate "Steam Audio spatial offload component" + default y + help + Select for Steam Audio spatial audio offload component. + Provides 3D binaural HRTF spatialization, direct sound modeling, + 3-band biquad IIR filtering, 8-channel Householder Feedback Delay + Network (FDN) reverberation, 1st-order Ambisonics rotation/decoding, + and on-chip DSP BVH ray tracing for real-time room acoustic simulation. diff --git a/src/audio/steamaudio/llext/CMakeLists.txt b/src/audio/steamaudio/llext/CMakeLists.txt new file mode 100644 index 000000000000..d4b1d2e3b95c --- /dev/null +++ b/src/audio/steamaudio/llext/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Intel Corporation. +# SPDX-License-Identifier: Apache-2.0 + +sof_llext_build("steamaudio" + SOURCES ../steamaudio.c + ../steamaudio-generic.c + ../steamaudio_bvh.c + ../steamaudio-ipc4.c + LIB openmodules +) diff --git a/src/audio/steamaudio/llext/llext.toml.h b/src/audio/steamaudio/llext/llext.toml.h new file mode 100644 index 000000000000..cf8695468db3 --- /dev/null +++ b/src/audio/steamaudio/llext/llext.toml.h @@ -0,0 +1,6 @@ +#include +#define LOAD_TYPE "2" +#include "../steamaudio.toml" + +[module] +count = __COUNTER__ diff --git a/src/audio/steamaudio/steamaudio-generic.c b/src/audio/steamaudio/steamaudio-generic.c new file mode 100644 index 000000000000..d793fb3ab048 --- /dev/null +++ b/src/audio/steamaudio/steamaudio-generic.c @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood +// Steam Audio Spatial Processing Core for SOF + +#include "steamaudio.h" +#include +#include +#include +#include + +#define PI 3.14159265358979323846f +#define TWO_PI (2.0f * PI) + +static inline float sat_clamp(float val, float min_v, float max_v) +{ + if (val < min_v) return min_v; + if (val > max_v) return max_v; + return val; +} + +static inline float fast_sin(float x) +{ + while (x < -PI) x += TWO_PI; + while (x > PI) x -= TWO_PI; + float x2 = x * x; + return x * (1.0f - x2 * (0.16666667f - x2 * 0.00833333f)); +} + +static inline float fast_cos(float x) +{ + return fast_sin(x + 0.5f * PI); +} + +static inline float fast_atan2(float y, float x) +{ + if (x == 0.0f) + return (y > 0.0f) ? (0.5f * PI) : ((y < 0.0f) ? (-0.5f * PI) : 0.0f); + + float abs_y = (y < 0.0f) ? -y : y; + float abs_x = (x < 0.0f) ? -x : x; + float a = (abs_x > abs_y) ? (abs_y / abs_x) : (abs_x / abs_y); + float s = a * a; + float r = ((-0.0464964749f * s + 0.15931422f) * s - 0.327622764f) * s * a + a; + + if (abs_y > abs_x) + r = 1.570796327f - r; + if (x < 0.0f) + r = PI - r; + if (y < 0.0f) + r = -r; + + return r; +} + +/* 3-Band Biquad Filter Computation */ +static void calc_biquad_coeffs(float gain_db, float freq, float sample_rate, int type, float coeffs[5]) +{ + float w0 = 2.0f * PI * freq / sample_rate; + float cos_w0 = fast_cos(w0); + float sin_w0 = fast_sin(w0); + float a = 1.0f; /* Q = 1.0 */ + float alpha = sin_w0 / (2.0f * a); + float A = 1.0f; + if (gain_db != 0.0f) + A = 1.0f + gain_db * 0.115129f; /* 10^(dB/20) approx */ + + float b0 = 1.0f, b1 = 0.0f, b2 = 0.0f, a0 = 1.0f, a1 = 0.0f, a2 = 0.0f; + + if (type == 0) { + /* Low shelf (400 Hz) */ + float sqrt_A = (A > 0.0f) ? (1.0f + 0.5f * (A - 1.0f)) : 1.0f; + b0 = A * ((A + 1.0f) - (A - 1.0f) * cos_w0 + 2.0f * sqrt_A * alpha); + b1 = 2.0f * A * ((A - 1.0f) - (A + 1.0f) * cos_w0); + b2 = A * ((A + 1.0f) - (A - 1.0f) * cos_w0 - 2.0f * sqrt_A * alpha); + a0 = (A + 1.0f) + (A - 1.0f) * cos_w0 + 2.0f * sqrt_A * alpha; + a1 = -2.0f * ((A - 1.0f) + (A + 1.0f) * cos_w0); + a2 = (A + 1.0f) + (A - 1.0f) * cos_w0 - 2.0f * sqrt_A * alpha; + } else if (type == 1) { + /* Peaking (2.5 kHz) */ + b0 = 1.0f + alpha * A; + b1 = -2.0f * cos_w0; + b2 = 1.0f - alpha * A; + a0 = 1.0f + alpha / A; + a1 = -2.0f * cos_w0; + a2 = 1.0f - alpha / A; + } else { + /* High shelf (15 kHz) */ + float sqrt_A = (A > 0.0f) ? (1.0f + 0.5f * (A - 1.0f)) : 1.0f; + b0 = A * ((A + 1.0f) + (A - 1.0f) * cos_w0 + 2.0f * sqrt_A * alpha); + b1 = -2.0f * A * ((A - 1.0f) + (A + 1.0f) * cos_w0); + b2 = A * ((A + 1.0f) - (A - 1.0f) * cos_w0 - 2.0f * sqrt_A * alpha); + a0 = (A + 1.0f) - (A - 1.0f) * cos_w0 + 2.0f * sqrt_A * alpha; + a1 = 2.0f * ((A - 1.0f) - (A + 1.0f) * cos_w0); + a2 = (A + 1.0f) - (A - 1.0f) * cos_w0 - 2.0f * sqrt_A * alpha; + } + + float inv_a0 = 1.0f / a0; + coeffs[0] = b0 * inv_a0; + coeffs[1] = b1 * inv_a0; + coeffs[2] = b2 * inv_a0; + coeffs[3] = a1 * inv_a0; + coeffs[4] = a2 * inv_a0; +} + +void steamaudio_dsp_init(struct steamaudio_comp_data *cd, uint32_t sample_rate) +{ + cd->sample_rate = sample_rate ? sample_rate : 48000; + cd->enable = true; + cd->bitstream_mode = false; + + /* Initialize Direct Path */ + cd->direct.active_slot = 0; + cd->direct.current_gain = 1.0f; + cd->direct.target_gain = 1.0f; + cd->direct.gain_step = 0.0f; + cd->direct.needs_crossfade = false; + cd->direct.crossfade_remaining = 0; + + for (int b = 0; b < STEAMAUDIO_NUM_EQ_BANDS; b++) { + float freq = (b == 0) ? 400.0f : ((b == 1) ? 2500.0f : 15000.0f); + calc_biquad_coeffs(0.0f, freq, (float)cd->sample_rate, b, cd->direct.coeffs[0][b]); + calc_biquad_coeffs(0.0f, freq, (float)cd->sample_rate, b, cd->direct.coeffs[1][b]); + } + + /* Initialize Binaural */ + memset(cd->binaural.delay_line, 0, sizeof(cd->binaural.delay_line)); + cd->binaural.write_idx = 0; + cd->binaural.spatial_blend = 1.0f; + cd->binaural.direction[0] = 0.0f; + cd->binaural.direction[1] = 0.0f; + cd->binaural.direction[2] = 1.0f; + cd->binaural.itd_samples[0] = 0.0f; + cd->binaural.itd_samples[1] = 0.0f; + cd->binaural.ild_gains[0] = 1.0f; + cd->binaural.ild_gains[1] = 1.0f; + + /* Initialize FDN Reverb with prime delays */ + static const int prime_delays[STEAMAUDIO_NUM_FDN_LINES] = { + 1087, 1223, 1361, 1489, 1619, 1753, 1877, 2017 + }; + for (int i = 0; i < STEAMAUDIO_NUM_FDN_LINES; i++) { + cd->reverb.delay_lengths[i] = prime_delays[i]; + cd->reverb.delay_indices[i] = 0; + cd->reverb.absorption[i] = 0.25f; + cd->reverb.damp_states[i] = 0.0f; + } + cd->reverb.wet_gain = 0.25f; + + /* Initialize Ambisonics */ + cd->ambisonics.order = 1; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) + cd->ambisonics.rotation[i][j] = (i == j) ? 1.0f : 0.0f; + } + + /* Virtual loudspeaker layout (8 cube vertices) */ + static const float speaker_pos[8][2] = { + { -0.785f, -0.615f }, { 0.785f, -0.615f }, + { -2.356f, -0.615f }, { 2.356f, -0.615f }, + { -0.785f, 0.615f }, { 0.785f, 0.615f }, + { -2.356f, 0.615f }, { 2.356f, 0.615f } + }; + memcpy(cd->ambisonics.virtual_speaker_angles, speaker_pos, sizeof(speaker_pos)); + + /* Initialize BVH scene with standard test room (8m x 10m x 3.5m) */ + steamaudio_dsp_scene_init_box_room(&cd->scene, 8.0f, 10.0f, 3.5f); +} + +static inline void process_direct_path(struct steamaudio_comp_data *cd, const float *in, float *out, uint32_t frames) +{ + int slot = cd->direct.active_slot; + float gain = cd->direct.current_gain; + float step = cd->direct.gain_step; + + for (uint32_t i = 0; i < frames; i++) { + float x = in[i]; + + /* 3-Band Biquad Direct Form II Transposed */ + for (int b = 0; b < STEAMAUDIO_NUM_EQ_BANDS; b++) { + float *c = cd->direct.coeffs[slot][b]; + float *s = cd->direct.states[slot][b]; + float y = c[0] * x + s[0]; + s[0] = c[1] * x - c[3] * y + s[1]; + s[1] = c[2] * x - c[4] * y; + x = y; + } + + /* Apply linear interpolated gain */ + gain += step; + out[i] = x * gain; + } + + cd->direct.current_gain = gain; + if ((step > 0.0f && gain >= cd->direct.target_gain) || + (step < 0.0f && gain <= cd->direct.target_gain)) { + cd->direct.current_gain = cd->direct.target_gain; + cd->direct.gain_step = 0.0f; + } +} + +static inline void process_binaural(struct steamaudio_comp_data *cd, const float *in, + float *out_l, float *out_r, uint32_t frames) +{ + float x = cd->binaural.direction[0]; + float z = cd->binaural.direction[2]; + + float azimuth = fast_atan2(x, z); + float sin_az = fast_sin(azimuth); + + /* ITD: Woodworth spherical model (~0.66ms max delay = ~32 samples at 48kHz) */ + float max_itd_samples = 32.0f * (float)cd->sample_rate / 48000.0f; + float itd_l = (sin_az < 0.0f) ? 0.0f : (sin_az * max_itd_samples); + float itd_r = (sin_az > 0.0f) ? 0.0f : (-sin_az * max_itd_samples); + + /* ILD: head-shadow attenuation */ + float ild_l = (sin_az > 0.0f) ? (1.0f - 0.5f * sin_az) : 1.0f; + float ild_r = (sin_az < 0.0f) ? (1.0f + 0.5f * sin_az) : 1.0f; + + float blend = cd->binaural.spatial_blend; + + for (uint32_t i = 0; i < frames; i++) { + cd->binaural.delay_line[cd->binaural.write_idx] = in[i]; + + /* Delay left ear */ + int read_idx_l = cd->binaural.write_idx - (int)itd_l; + if (read_idx_l < 0) read_idx_l += STEAMAUDIO_DELAY_LINE_SIZE; + float left_sample = cd->binaural.delay_line[read_idx_l] * ild_l; + + /* Delay right ear */ + int read_idx_r = cd->binaural.write_idx - (int)itd_r; + if (read_idx_r < 0) read_idx_r += STEAMAUDIO_DELAY_LINE_SIZE; + float right_sample = cd->binaural.delay_line[read_idx_r] * ild_r; + + cd->binaural.write_idx = (cd->binaural.write_idx + 1) % STEAMAUDIO_DELAY_LINE_SIZE; + + /* Blend between mono and spatial */ + out_l[i] = in[i] * (1.0f - blend) + left_sample * blend; + out_r[i] = in[i] * (1.0f - blend) + right_sample * blend; + } +} + +static inline void process_reverb(struct steamaudio_comp_data *cd, const float *in, + float *out_l, float *out_r, uint32_t frames) +{ + float wet = cd->reverb.wet_gain; + if (wet < 1e-4f) + return; + + float fdn_outputs[STEAMAUDIO_NUM_FDN_LINES]; + + for (uint32_t i = 0; i < frames; i++) { + float in_val = in[i]; + float sum_fdn = 0.0f; + + /* Read delay lines and compute Householder sum */ + for (int j = 0; j < STEAMAUDIO_NUM_FDN_LINES; j++) { + int ptr = cd->reverb.delay_indices[j]; + float val = cd->reverb.delay_buffers[j][ptr]; + /* 1-pole absorption filter */ + float alpha = cd->reverb.absorption[j]; + val = val * (1.0f - alpha) + cd->reverb.damp_states[j] * alpha; + cd->reverb.damp_states[j] = val; + fdn_outputs[j] = val; + sum_fdn += val; + } + + /* Householder 8x8 reflection: out_k = input - 2/8 * sum */ + float feedback_factor = sum_fdn * 0.25f; + for (int j = 0; j < STEAMAUDIO_NUM_FDN_LINES; j++) { + float new_val = in_val + (fdn_outputs[j] - feedback_factor); + int ptr = cd->reverb.delay_indices[j]; + cd->reverb.delay_buffers[j][ptr] = new_val; + cd->reverb.delay_indices[j] = (ptr + 1) % cd->reverb.delay_lengths[j]; + } + + /* Decorrelated stereo reverb sum */ + float rev_l = (fdn_outputs[0] + fdn_outputs[2] + fdn_outputs[4] + fdn_outputs[6]) * 0.25f; + float rev_r = (fdn_outputs[1] + fdn_outputs[3] + fdn_outputs[5] + fdn_outputs[7]) * 0.25f; + + out_l[i] += rev_l * wet; + out_r[i] += rev_r * wet; + } +} + +static void check_inband_bitstream(struct steamaudio_comp_data *cd, const void *src_ptr, uint32_t avail_bytes) +{ + if (avail_bytes < sizeof(struct steamaudio_bitstream_header)) + return; + + const struct steamaudio_bitstream_header *hdr = (const struct steamaudio_bitstream_header *)src_ptr; + if (hdr->sync_word != STEAMAUDIO_SOF_SYNC_WORD) + return; + + /* Valid synchronized bitstream frame detected */ + cd->bitstream_mode = true; + cd->direct.target_gain = hdr->distance_attenuation * (1.0f - hdr->occlusion); + cd->direct.gain_step = (cd->direct.target_gain - cd->direct.current_gain) / (float)hdr->num_samples; + + cd->binaural.direction[0] = hdr->direction[0]; + cd->binaural.direction[1] = hdr->direction[1]; + cd->binaural.direction[2] = hdr->direction[2]; + cd->binaural.spatial_blend = hdr->spatial_blend; + + cd->reverb.wet_gain = hdr->reverb_wet_gain; +} + +#if CONFIG_FORMAT_S16LE +static int steamaudio_process_s16(struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct steamaudio_comp_data *cd = module_get_private_data(mod); + int16_t const *src, *x_start; + int16_t *dst, *y_start; + int x_size, y_size; + uint32_t in_bytes = frames * cd->channels * sizeof(int16_t); + uint32_t out_bytes = frames * 2 * sizeof(int16_t); + int ret; + + ret = source_get_data_s16(source, in_bytes, &src, &x_start, &x_size); + if (ret) + return ret; + + ret = sink_get_buffer_s16(sink, out_bytes, &dst, &y_start, &y_size); + if (ret) + return ret; + + check_inband_bitstream(cd, src, in_bytes); + + /* Convert mono/first-channel input to float */ + for (uint32_t i = 0; i < frames; i++) + cd->in_scratch[i] = (float)src[i * cd->channels] * (1.0f / 32768.0f); + + /* DSP Pipeline */ + process_direct_path(cd, cd->in_scratch, cd->out_left, frames); + memcpy(cd->in_scratch, cd->out_left, frames * sizeof(float)); + + process_binaural(cd, cd->in_scratch, cd->out_left, cd->out_right, frames); + process_reverb(cd, cd->in_scratch, cd->out_left, cd->out_right, frames); + + /* Interleave stereo float into S16_LE */ + for (uint32_t i = 0; i < frames; i++) { + float l = sat_clamp(cd->out_left[i] * 32767.0f, -32768.0f, 32767.0f); + float r = sat_clamp(cd->out_right[i] * 32767.0f, -32768.0f, 32767.0f); + dst[2 * i] = (int16_t)l; + dst[2 * i + 1] = (int16_t)r; + } + + source_release_data(source, in_bytes); + sink_commit_buffer(sink, out_bytes); + return 0; +} +#endif + +#if CONFIG_FORMAT_S24LE || CONFIG_FORMAT_S32LE +static int steamaudio_process_s32(struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct steamaudio_comp_data *cd = module_get_private_data(mod); + int32_t const *src, *x_start; + int32_t *dst, *y_start; + int x_size, y_size; + uint32_t in_bytes = frames * cd->channels * sizeof(int32_t); + uint32_t out_bytes = frames * 2 * sizeof(int32_t); + int ret; + + ret = source_get_data_s32(source, in_bytes, &src, &x_start, &x_size); + if (ret) + return ret; + + ret = sink_get_buffer_s32(sink, out_bytes, &dst, &y_start, &y_size); + if (ret) + return ret; + + check_inband_bitstream(cd, src, in_bytes); + + for (uint32_t i = 0; i < frames; i++) + cd->in_scratch[i] = (float)src[i * cd->channels] * (1.0f / 2147483648.0f); + + process_direct_path(cd, cd->in_scratch, cd->out_left, frames); + memcpy(cd->in_scratch, cd->out_left, frames * sizeof(float)); + + process_binaural(cd, cd->in_scratch, cd->out_left, cd->out_right, frames); + process_reverb(cd, cd->in_scratch, cd->out_left, cd->out_right, frames); + + for (uint32_t i = 0; i < frames; i++) { + float l = sat_clamp(cd->out_left[i] * 2147483647.0f, -2147483648.0f, 2147483647.0f); + float r = sat_clamp(cd->out_right[i] * 2147483647.0f, -2147483648.0f, 2147483647.0f); + dst[2 * i] = (int32_t)l; + dst[2 * i + 1] = (int32_t)r; + } + + source_release_data(source, in_bytes); + sink_commit_buffer(sink, out_bytes); + return 0; +} +#endif + +steamaudio_func steamaudio_find_proc_func(enum sof_ipc_frame src_fmt) +{ + switch (src_fmt) { +#if CONFIG_FORMAT_S16LE + case SOF_IPC_FRAME_S16_LE: + return steamaudio_process_s16; +#endif +#if CONFIG_FORMAT_S24LE + case SOF_IPC_FRAME_S24_4LE: + return steamaudio_process_s32; +#endif +#if CONFIG_FORMAT_S32LE + case SOF_IPC_FRAME_S32_LE: + return steamaudio_process_s32; +#endif + default: + return NULL; + } +} diff --git a/src/audio/steamaudio/steamaudio-ipc4.c b/src/audio/steamaudio/steamaudio-ipc4.c new file mode 100644 index 000000000000..2b0f11f946f3 --- /dev/null +++ b/src/audio/steamaudio/steamaudio-ipc4.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood +// Steam Audio IPC4 Control Handler + +#include "steamaudio.h" +#include +#include +#include + +LOG_MODULE_DECLARE(steamaudio, CONFIG_SOF_LOG_LEVEL); + +__cold int steamaudio_set_config(struct processing_module *mod, + uint32_t param_id, + enum module_cfg_fragment_position pos, + uint32_t data_offset_size, + const uint8_t *fragment, + size_t fragment_size, + uint8_t *response, + size_t response_size) +{ + struct steamaudio_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + + assert_can_be_cold(); + + switch (param_id) { + case SOF_IPC4_SWITCH_CONTROL_PARAM_ID: { + struct sof_ipc4_control_msg_payload *ctl = (struct sof_ipc4_control_msg_payload *)fragment; + if (ctl->num_elems != 1) + return -EINVAL; + cd->enable = (ctl->chanv[0].value != 0); + comp_info(dev, "steamaudio: enable set to %d", cd->enable); + return 0; + } + + case STEAMAUDIO_PARAM_DIRECT_CONFIG: { + if (fragment_size < sizeof(struct sof_steamaudio_direct_config)) + return -EINVAL; + + const struct sof_steamaudio_direct_config *cfg = + (const struct sof_steamaudio_direct_config *)fragment; + + cd->direct.target_gain = cfg->distance_attenuation * (1.0f - cfg->occlusion); + cd->direct.gain_step = (cd->direct.target_gain - cd->direct.current_gain) / 128.0f; + comp_dbg(dev, "steamaudio: direct dist=%f, occ=%f", + (double)cfg->distance_attenuation, (double)cfg->occlusion); + return 0; + } + + case STEAMAUDIO_PARAM_BINAURAL_CONFIG: { + if (fragment_size < sizeof(struct sof_steamaudio_binaural_config)) + return -EINVAL; + + const struct sof_steamaudio_binaural_config *cfg = + (const struct sof_steamaudio_binaural_config *)fragment; + + cd->binaural.direction[0] = cfg->direction[0]; + cd->binaural.direction[1] = cfg->direction[1]; + cd->binaural.direction[2] = cfg->direction[2]; + cd->binaural.spatial_blend = cfg->spatial_blend; + + comp_dbg(dev, "steamaudio: binaural dir=(%f, %f, %f)", + (double)cfg->direction[0], (double)cfg->direction[1], (double)cfg->direction[2]); + return 0; + } + + case STEAMAUDIO_PARAM_REVERB_CONFIG: { + if (fragment_size < sizeof(struct sof_steamaudio_reverb_config)) + return -EINVAL; + + const struct sof_steamaudio_reverb_config *cfg = + (const struct sof_steamaudio_reverb_config *)fragment; + + cd->reverb.wet_gain = cfg->wet_gain; + comp_dbg(dev, "steamaudio: reverb wet=%f", (double)cfg->wet_gain); + return 0; + } + + case STEAMAUDIO_PARAM_AMBISONICS_CONFIG: { + if (fragment_size < sizeof(struct sof_steamaudio_ambisonics_config)) + return -EINVAL; + + const struct sof_steamaudio_ambisonics_config *cfg = + (const struct sof_steamaudio_ambisonics_config *)fragment; + + cd->ambisonics.order = cfg->order; + memcpy(cd->ambisonics.rotation, cfg->listener_rotation, sizeof(cd->ambisonics.rotation)); + comp_dbg(dev, "steamaudio: ambisonics order=%d", cfg->order); + return 0; + } + + case STEAMAUDIO_PARAM_BITSTREAM_MODE: { + if (fragment_size < sizeof(uint32_t)) + return -EINVAL; + cd->bitstream_mode = (*((const uint32_t *)fragment) != 0); + comp_info(dev, "steamaudio: bitstream mode set to %d", cd->bitstream_mode); + return 0; + } + + default: + comp_warn(dev, "steamaudio: unhandled param_id 0x%x", param_id); + return -EINVAL; + } +} + +__cold int steamaudio_get_config(struct processing_module *mod, + uint32_t config_id, uint32_t *data_offset_size, + uint8_t *fragment, size_t fragment_size) +{ + struct steamaudio_comp_data *cd = module_get_private_data(mod); + + assert_can_be_cold(); + + switch (config_id) { + case STEAMAUDIO_PARAM_DIRECT_CONFIG: { + if (fragment_size < sizeof(struct sof_steamaudio_direct_config)) + return -EINVAL; + + struct sof_steamaudio_direct_config *cfg = (struct sof_steamaudio_direct_config *)fragment; + cfg->comp_type = STEAMAUDIO_PARAM_DIRECT_CONFIG; + cfg->distance_attenuation = cd->direct.current_gain; + cfg->occlusion = 0.0f; + return 0; + } + + case STEAMAUDIO_PARAM_BINAURAL_CONFIG: { + if (fragment_size < sizeof(struct sof_steamaudio_binaural_config)) + return -EINVAL; + + struct sof_steamaudio_binaural_config *cfg = (struct sof_steamaudio_binaural_config *)fragment; + cfg->comp_type = STEAMAUDIO_PARAM_BINAURAL_CONFIG; + cfg->direction[0] = cd->binaural.direction[0]; + cfg->direction[1] = cd->binaural.direction[1]; + cfg->direction[2] = cd->binaural.direction[2]; + cfg->spatial_blend = cd->binaural.spatial_blend; + return 0; + } + + default: + return -EINVAL; + } +} diff --git a/src/audio/steamaudio/steamaudio.c b/src/audio/steamaudio/steamaudio.c new file mode 100644 index 000000000000..bfd2ab55d1c7 --- /dev/null +++ b/src/audio/steamaudio/steamaudio.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood +// Steam Audio Spatial Offload Component for SOF + +#include +#include +#include +#include +#include +#include "steamaudio.h" + +SOF_DEFINE_REG_UUID(steamaudio); + +LOG_MODULE_REGISTER(steamaudio, CONFIG_SOF_LOG_LEVEL); + +__cold static int steamaudio_init(struct processing_module *mod) +{ + struct module_data *md = &mod->priv; + struct comp_dev *dev = mod->dev; + struct steamaudio_comp_data *cd; + + comp_info(dev, "steamaudio: init entry"); + + cd = mod_zalloc(mod, sizeof(*cd)); + if (!cd) + return -ENOMEM; + + steamaudio_dsp_init(cd, 48000); + md->private = cd; + return 0; +} + +static int steamaudio_process(struct processing_module *mod, + struct sof_source **sources, + int num_of_sources, + struct sof_sink **sinks, + int num_of_sinks) +{ + struct steamaudio_comp_data *cd = module_get_private_data(mod); + struct sof_source *source = sources[0]; + struct sof_sink *sink = sinks[0]; + int frames = source_get_data_frames_available(source); + int sink_frames = sink_get_free_frames(sink); + + frames = MIN(frames, sink_frames); + if (frames <= 0) + return 0; + + /* Cap to scratch buffer size */ + if (frames > 256) + frames = 256; + + if (cd->enable && cd->proc_func) + return cd->proc_func(mod, source, sink, frames); + + /* Passthrough if disabled */ + source_to_sink_copy(source, sink, true, frames * cd->frame_bytes); + return 0; +} + +static int steamaudio_prepare(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) +{ + struct steamaudio_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + enum sof_ipc_frame source_format; + uint32_t rate; + + comp_dbg(dev, "steamaudio: prepare entry"); + + if (num_of_sources != 1 || num_of_sinks != 1) + return -EINVAL; + + cd->frame_bytes = source_get_frame_bytes(sources[0]); + cd->channels = source_get_channels(sources[0]); + source_format = source_get_frm_fmt(sources[0]); + rate = source_get_rate(sources[0]); + + if (rate != cd->sample_rate && rate > 0) + steamaudio_dsp_init(cd, rate); + + cd->proc_func = steamaudio_find_proc_func(source_format); + if (!cd->proc_func) { + comp_err(dev, "steamaudio: no proc func for format %d", source_format); + return -EINVAL; + } + + comp_info(dev, "steamaudio: prepared fmt=%d, ch=%d, rate=%u", + source_format, cd->channels, rate); + return 0; +} + +static int steamaudio_reset(struct processing_module *mod) +{ + struct steamaudio_comp_data *cd = module_get_private_data(mod); + + comp_dbg(mod->dev, "steamaudio: reset entry"); + if (cd) + steamaudio_dsp_init(cd, cd->sample_rate); + + return 0; +} + +__cold static int steamaudio_free(struct processing_module *mod) +{ + struct steamaudio_comp_data *cd = module_get_private_data(mod); + + assert_can_be_cold(); + comp_dbg(mod->dev, "steamaudio: free entry"); + + if (cd) + mod_free(mod, cd); + + return 0; +} + +static const struct module_interface steamaudio_interface = { + .init = steamaudio_init, + .prepare = steamaudio_prepare, + .process = steamaudio_process, + .set_configuration = steamaudio_set_config, + .get_configuration = steamaudio_get_config, + .reset = steamaudio_reset, + .free = steamaudio_free +}; + +#if CONFIG_COMP_STEAMAUDIO_MODULE + +#include +#include +#include + +static const struct sof_man_module_manifest mod_manifest __section(".module") __used = + SOF_LLEXT_MODULE_MANIFEST("STEAMAUD", &steamaudio_interface, 1, + SOF_REG_UUID(steamaudio), 10); + +SOF_LLEXT_BUILDINFO; + +#else + +DECLARE_TR_CTX(steamaudio_tr, SOF_UUID(steamaudio_uuid), LOG_LEVEL_INFO); +DECLARE_MODULE_ADAPTER(steamaudio_interface, steamaudio_uuid, steamaudio_tr); +SOF_MODULE_INIT(steamaudio, sys_comp_module_steamaudio_interface_init); + +#endif diff --git a/src/audio/steamaudio/steamaudio.h b/src/audio/steamaudio/steamaudio.h new file mode 100644 index 000000000000..037095a14fb8 --- /dev/null +++ b/src/audio/steamaudio/steamaudio.h @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood + * Steam Audio DSP Offload Engine for SOF + */ + +#ifndef __SOF_AUDIO_STEAMAUDIO_H__ +#define __SOF_AUDIO_STEAMAUDIO_H__ + +#include +#include +#include +#include +#include + +#define STEAMAUDIO_SOF_SYNC_WORD 0x53544541 /* 'STEA' */ +#define STEAMAUDIO_SOF_PROTOCOL_VERSION 0x00010000 + +#define STEAMAUDIO_NUM_EQ_BANDS 3 +#define STEAMAUDIO_NUM_FDN_LINES 8 +#define STEAMAUDIO_MAX_HRIR_TAPS 64 +#define STEAMAUDIO_DELAY_LINE_SIZE 256 +#define STEAMAUDIO_FDN_MAX_DELAY 2048 + +#define STEAMAUDIO_PARAM_DIRECT_CONFIG 0x1001 +#define STEAMAUDIO_PARAM_BINAURAL_CONFIG 0x1002 +#define STEAMAUDIO_PARAM_AMBISONICS_CONFIG 0x1003 +#define STEAMAUDIO_PARAM_REVERB_CONFIG 0x1004 +#define STEAMAUDIO_PARAM_BVH_QUERY 0x1005 +#define STEAMAUDIO_PARAM_BITSTREAM_MODE 0x1006 + +/* Bitstream Encapsulation Header */ +struct __attribute__((packed)) steamaudio_bitstream_header { + uint32_t sync_word; + uint32_t protocol_version; + uint32_t frame_seq_id; + uint16_t num_samples; + uint16_t num_channels; + uint32_t payload_bytes; + + /* Direct Path */ + uint32_t direct_flags; + uint32_t transmission_type; + float distance_attenuation; + float directivity; + float occlusion; + float air_absorption[STEAMAUDIO_NUM_EQ_BANDS]; + float transmission[STEAMAUDIO_NUM_EQ_BANDS]; + + /* Spatial & Binaural */ + float direction[3]; + uint32_t hrtf_interpolation; + float spatial_blend; + + /* Reverb */ + float reverb_times[STEAMAUDIO_NUM_EQ_BANDS]; + float reverb_eq[STEAMAUDIO_NUM_EQ_BANDS]; + uint32_t reverb_delay_samples; + float reverb_wet_gain; +}; + +/* SOF IPC / ALSA kcontrol TLV structs */ +struct __attribute__((packed)) sof_steamaudio_direct_config { + uint32_t comp_type; + uint32_t flags; + uint32_t transmission_type; + float distance_attenuation; + float directivity; + float occlusion; + float air_absorption[STEAMAUDIO_NUM_EQ_BANDS]; + float transmission[STEAMAUDIO_NUM_EQ_BANDS]; +}; + +struct __attribute__((packed)) sof_steamaudio_binaural_config { + uint32_t comp_type; + float direction[3]; + uint32_t interpolation; + float spatial_blend; + uint32_t hrtf_slot_id; +}; + +struct __attribute__((packed)) sof_steamaudio_reverb_config { + uint32_t comp_type; + float reverb_times[STEAMAUDIO_NUM_EQ_BANDS]; + float eq_gains[STEAMAUDIO_NUM_EQ_BANDS]; + uint32_t delay_samples; + float wet_gain; +}; + +struct __attribute__((packed)) sof_steamaudio_ambisonics_config { + uint32_t comp_type; + uint32_t order; + float direction[3]; + float listener_rotation[3][3]; +}; + +/* Internal DSP Sub-Engine States */ +struct steamaudio_direct_state { + float coeffs[2][STEAMAUDIO_NUM_EQ_BANDS][5]; /* b0, b1, b2, a1, a2 */ + float states[2][STEAMAUDIO_NUM_EQ_BANDS][2]; /* w1, w2 */ + int active_slot; + float current_gain; + float target_gain; + float gain_step; + bool needs_crossfade; + int crossfade_remaining; +}; + +struct steamaudio_binaural_state { + float delay_line[STEAMAUDIO_DELAY_LINE_SIZE]; + int write_idx; + float hrir_left[STEAMAUDIO_MAX_HRIR_TAPS]; + float hrir_right[STEAMAUDIO_MAX_HRIR_TAPS]; + float itd_samples[2]; + float ild_gains[2]; + float spatial_blend; + float direction[3]; +}; + +struct steamaudio_reverb_state { + float delay_buffers[STEAMAUDIO_NUM_FDN_LINES][STEAMAUDIO_FDN_MAX_DELAY]; + int delay_lengths[STEAMAUDIO_NUM_FDN_LINES]; + int delay_indices[STEAMAUDIO_NUM_FDN_LINES]; + float absorption[STEAMAUDIO_NUM_FDN_LINES]; + float damp_states[STEAMAUDIO_NUM_FDN_LINES]; + float wet_gain; +}; + +struct steamaudio_ambisonics_state { + uint32_t order; + float rotation[3][3]; + float virtual_speaker_angles[8][2]; /* az, el for 8 cube vertices */ +}; + +/* BVH scene forward declaration */ +#define STEAMAUDIO_MAX_DSP_TRIANGLES 32 +#define STEAMAUDIO_MAX_DSP_BVH_NODES 64 + +struct dsp_vec3 { + float x, y, z; +}; + +struct dsp_ray { + struct dsp_vec3 origin; + struct dsp_vec3 direction; + float min_distance; + float max_distance; +}; + +struct dsp_hit { + float distance; + struct dsp_vec3 normal; + int triangle_index; + bool has_hit; +}; + +struct dsp_triangle { + struct dsp_vec3 v0, v1, v2; + struct dsp_vec3 normal; + float absorption[3]; +}; + +struct dsp_aabb { + struct dsp_vec3 min; + struct dsp_vec3 max; +}; + +struct dsp_bvh_node { + struct dsp_aabb bounds; + int left_child; + int right_child; +}; + +struct dsp_scene { + uint32_t num_triangles; + struct dsp_triangle triangles[STEAMAUDIO_MAX_DSP_TRIANGLES]; + uint32_t num_nodes; + struct dsp_bvh_node nodes[STEAMAUDIO_MAX_DSP_BVH_NODES]; +}; + +/* Forward declare processing function pointer */ +struct steamaudio_comp_data; +typedef int (*steamaudio_func)(struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames); + +struct steamaudio_comp_data { + steamaudio_func proc_func; + int source_format; + int frame_bytes; + int channels; + uint32_t sample_rate; + bool enable; + bool bitstream_mode; + + /* DSP Subsystem contexts */ + struct steamaudio_direct_state direct; + struct steamaudio_binaural_state binaural; + struct steamaudio_reverb_state reverb; + struct steamaudio_ambisonics_state ambisonics; + struct dsp_scene scene; + + /* Temporary scratch buffer for processing frames */ + float in_scratch[256]; + float out_left[256]; + float out_right[256]; +}; + +/* Public component API */ +steamaudio_func steamaudio_find_proc_func(enum sof_ipc_frame src_fmt); +void steamaudio_dsp_init(struct steamaudio_comp_data *cd, uint32_t sample_rate); + +/* IPC4 control callbacks */ +int steamaudio_set_config(struct processing_module *mod, + uint32_t param_id, + enum module_cfg_fragment_position pos, + uint32_t data_offset_size, + const uint8_t *fragment, + size_t fragment_size, + uint8_t *response, + size_t response_size); + +int steamaudio_get_config(struct processing_module *mod, + uint32_t config_id, uint32_t *data_offset_size, + uint8_t *fragment, size_t fragment_size); + +/* BVH ray tracer API */ +void steamaudio_dsp_scene_init_box_room(struct dsp_scene *scene, float width, float length, float height); +bool steamaudio_dsp_trace_closest_hit(const struct dsp_scene *scene, const struct dsp_ray *ray, struct dsp_hit *hit); +float steamaudio_dsp_test_occlusion(const struct dsp_scene *scene, struct dsp_vec3 source, struct dsp_vec3 listener); + +#endif /* __SOF_AUDIO_STEAMAUDIO_H__ */ diff --git a/src/audio/steamaudio/steamaudio.toml b/src/audio/steamaudio/steamaudio.toml new file mode 100644 index 000000000000..34dd2a702f17 --- /dev/null +++ b/src/audio/steamaudio/steamaudio.toml @@ -0,0 +1,21 @@ +#ifndef LOAD_TYPE +#define LOAD_TYPE "0" +#endif + + REM # Steam Audio spatial component module config + [[module.entry]] + name = "STEAMAUD" + uuid = UUIDREG_STR_STEAMAUDIO + affinity_mask = "0x1" + instance_count = "10" + domain_types = "0" + load_type = LOAD_TYPE + module_type = "9" + auto_start = "0" + sched_caps = [1, 0x00008000] + REM # pin = [dir, type, sample rate, size, container, channel-cfg] + pin = [0, 0, 0xfeef, 0xf, 0xf, 0x45ff, 1, 0, 0xfeef, 0xf, 0xf, 0x45ff] + REM # mod_cfg [PAR_0 PAR_1 PAR_2 PAR_3 IS_BYTES CPS IBS OBS MOD_FLAGS CPC OBLS] + mod_cfg = [0, 0, 0, 0, 4096, 20000000, 256, 256, 0, 0, 0] + + index = __COUNTER__ diff --git a/src/audio/steamaudio/steamaudio_bvh.c b/src/audio/steamaudio/steamaudio_bvh.c new file mode 100644 index 000000000000..f0d97fb0abcc --- /dev/null +++ b/src/audio/steamaudio/steamaudio_bvh.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood +// Steam Audio Lightweight DSP BVH Ray Tracer + +#include "steamaudio.h" +#include + +#define EPSILON 1e-6f + +static inline struct dsp_vec3 vec3_sub(struct dsp_vec3 a, struct dsp_vec3 b) +{ + struct dsp_vec3 r = { a.x - b.x, a.y - b.y, a.z - b.z }; + return r; +} + +static inline struct dsp_vec3 vec3_add(struct dsp_vec3 a, struct dsp_vec3 b) +{ + struct dsp_vec3 r = { a.x + b.x, a.y + b.y, a.z + b.z }; + return r; +} + +static inline struct dsp_vec3 vec3_scale(struct dsp_vec3 a, float s) +{ + struct dsp_vec3 r = { a.x * s, a.y * s, a.z * s }; + return r; +} + +static inline float vec3_dot(struct dsp_vec3 a, struct dsp_vec3 b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +static inline struct dsp_vec3 vec3_cross(struct dsp_vec3 a, struct dsp_vec3 b) +{ + struct dsp_vec3 r = { + a.y * b.z - a.z * b.y, + a.z * b.x - a.x * b.z, + a.x * b.y - a.y * b.x + }; + return r; +} + +static inline float fast_inv_sqrt(float x) +{ + float xhalf = 0.5f * x; + union { + float x; + int32_t i; + } u; + u.x = x; + u.i = 0x5f3759df - (u.i >> 1); + u.x = u.x * (1.5f - xhalf * u.x * u.x); + return u.x; +} + +static inline struct dsp_vec3 vec3_normalize(struct dsp_vec3 a) +{ + float d2 = vec3_dot(a, a); + if (d2 > 1e-9f) { + float inv_len = fast_inv_sqrt(d2); + return vec3_scale(a, inv_len); + } + return a; +} + +static inline float fminf_local(float a, float b) { return (a < b) ? a : b; } +static inline float fmaxf_local(float a, float b) { return (a > b) ? a : b; } + +static bool intersect_ray_aabb(const struct dsp_ray *ray, const struct dsp_aabb *box) +{ + float tmin = ray->min_distance; + float tmax = ray->max_distance; + + for (int i = 0; i < 3; i++) { + float origin = (i == 0) ? ray->origin.x : ((i == 1) ? ray->origin.y : ray->origin.z); + float dir = (i == 0) ? ray->direction.x : ((i == 1) ? ray->direction.y : ray->direction.z); + float bmin = (i == 0) ? box->min.x : ((i == 1) ? box->min.y : box->min.z); + float bmax = (i == 0) ? box->max.x : ((i == 1) ? box->max.y : box->max.z); + + if (dir > -1e-7f && dir < 1e-7f) { + if (origin < bmin || origin > bmax) + return false; + } else { + float inv_d = 1.0f / dir; + float t1 = (bmin - origin) * inv_d; + float t2 = (bmax - origin) * inv_d; + if (t1 > t2) { + float tmp = t1; t1 = t2; t2 = tmp; + } + tmin = fmaxf_local(tmin, t1); + tmax = fminf_local(tmax, t2); + if (tmin > tmax) + return false; + } + } + return true; +} + +static bool intersect_ray_triangle(const struct dsp_ray *ray, const struct dsp_triangle *tri, struct dsp_hit *hit) +{ + struct dsp_vec3 edge1 = vec3_sub(tri->v1, tri->v0); + struct dsp_vec3 edge2 = vec3_sub(tri->v2, tri->v0); + struct dsp_vec3 h = vec3_cross(ray->direction, edge2); + float a = vec3_dot(edge1, h); + + if (a > -EPSILON && a < EPSILON) + return false; + + float f = 1.0f / a; + struct dsp_vec3 s = vec3_sub(ray->origin, tri->v0); + float u = f * vec3_dot(s, h); + + if (u < 0.0f || u > 1.0f) + return false; + + struct dsp_vec3 q = vec3_cross(s, edge1); + float v = f * vec3_dot(ray->direction, q); + + if (v < 0.0f || u + v > 1.0f) + return false; + + float t = f * vec3_dot(edge2, q); + + if (t >= ray->min_distance && t <= ray->max_distance) { + hit->distance = t; + hit->normal = tri->normal; + hit->has_hit = true; + return true; + } + return false; +} + +void steamaudio_dsp_scene_init_box_room(struct dsp_scene *scene, float width, float length, float height) +{ + memset(scene, 0, sizeof(*scene)); + + float x0 = -width * 0.5f, x1 = width * 0.5f; + float y0 = 0.0f, y1 = height; + float z0 = -length * 0.5f, z1 = length * 0.5f; + + struct dsp_vec3 v[8] = { + { x0, y0, z0 }, { x1, y0, z0 }, { x1, y0, z1 }, { x0, y0, z1 }, + { x0, y1, z0 }, { x1, y1, z0 }, { x1, y1, z1 }, { x0, y1, z1 } + }; + + int tri_indices[12][3] = { + { 0, 1, 2 }, { 0, 2, 3 }, /* Floor */ + { 4, 6, 5 }, { 4, 7, 6 }, /* Ceiling */ + { 0, 5, 1 }, { 0, 4, 5 }, /* Back wall */ + { 3, 2, 6 }, { 3, 6, 7 }, /* Front wall */ + { 0, 3, 7 }, { 0, 7, 4 }, /* Left wall */ + { 1, 5, 6 }, { 1, 6, 2 } /* Right wall */ + }; + + scene->num_triangles = 12; + for (uint32_t i = 0; i < 12; i++) { + scene->triangles[i].v0 = v[tri_indices[i][0]]; + scene->triangles[i].v1 = v[tri_indices[i][1]]; + scene->triangles[i].v2 = v[tri_indices[i][2]]; + + struct dsp_vec3 e1 = vec3_sub(scene->triangles[i].v1, scene->triangles[i].v0); + struct dsp_vec3 e2 = vec3_sub(scene->triangles[i].v2, scene->triangles[i].v0); + scene->triangles[i].normal = vec3_normalize(vec3_cross(e1, e2)); + scene->triangles[i].absorption[0] = 0.1f; + scene->triangles[i].absorption[1] = 0.15f; + scene->triangles[i].absorption[2] = 0.2f; + } + + /* Simple root node enclosing entire scene */ + scene->num_nodes = 1; + scene->nodes[0].bounds.min = (struct dsp_vec3){ x0, y0, z0 }; + scene->nodes[0].bounds.max = (struct dsp_vec3){ x1, y1, z1 }; + scene->nodes[0].left_child = 0; + scene->nodes[0].right_child = 11; +} + +bool steamaudio_dsp_trace_closest_hit(const struct dsp_scene *scene, const struct dsp_ray *ray, struct dsp_hit *hit) +{ + hit->has_hit = false; + hit->distance = ray->max_distance; + + if (!intersect_ray_aabb(ray, &scene->nodes[0].bounds)) + return false; + + for (uint32_t i = 0; i < scene->num_triangles; i++) { + struct dsp_hit current_hit; + if (intersect_ray_triangle(ray, &scene->triangles[i], ¤t_hit)) { + if (current_hit.distance < hit->distance) { + *hit = current_hit; + hit->triangle_index = i; + } + } + } + return hit->has_hit; +} + +float steamaudio_dsp_test_occlusion(const struct dsp_scene *scene, struct dsp_vec3 source, struct dsp_vec3 listener) +{ + struct dsp_vec3 dir = vec3_sub(listener, source); + float dist2 = vec3_dot(dir, dir); + if (dist2 < 1e-4f) + return 0.0f; + + float inv_dist = fast_inv_sqrt(dist2); + float dist = dist2 * inv_dist; + + struct dsp_ray ray; + ray.origin = source; + ray.direction = vec3_scale(dir, inv_dist); + ray.min_distance = 0.05f; + ray.max_distance = dist - 0.05f; + + struct dsp_hit hit; + if (steamaudio_dsp_trace_closest_hit(scene, &ray, &hit)) + return 1.0f; /* 100% occluded */ + + return 0.0f; +} diff --git a/tools/rimage/config/ptl.toml.h b/tools/rimage/config/ptl.toml.h index e7a53d1b1820..809e76e8e184 100644 --- a/tools/rimage/config/ptl.toml.h +++ b/tools/rimage/config/ptl.toml.h @@ -166,6 +166,10 @@ index = __COUNTER__ #include