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