Skip to main content

STCoreV2

The next-generation Sound Tracing core with modular acoustic path search and lock-free data delivery.

STCoreV2 is a C++ library that synthesizes impulse responses (IRs) and spatial audio in real time from meshes, materials, listeners, and sound sources.

Overview

ItemValue
Baseline branchfeat/lock-free-hybrid-v2-reverb (lock-free reverb)
Primary languageC++ (C++17 build, C API exposed)
Build systemCMake 3.22+
PlatformsmacOS · Windows · Linux
Web buildEmscripten support (EMSCRIPTEN_KEEPALIVE export)
AcceleratorBuilt-in BVH or external callback such as a game engine BVH
Max Path Depth16 (EXA_MAX_DEPTH)
TestsGoogle Test (unit + benchmark)
StatusActive development

Architecture

Scene


SceneSnapshot ── lock-free per-tick snapshot
│ (TripleBuffer · POD · immutable)

┌─────────────────────────────────────────────┐
│ Propagation │
│ ├ IAccelerator │
│ │ ├ Internal BVH │
│ │ └ ExternalAccelerator (callback) │
│ └ Path Modules (IPathModule) │
│ ├ SpecularReflectionModule │
│ ├ UTDDiffractionModule │
│ ├ DiffuseOffsetModule │
│ └ StaticReverbModule │
│ (+ ScatterHandoff, ComparisonReport) │
└─────────────────────────────────────────────┘


Auralizator (filter / frequence / HRTF / states)


Audio Output

The engine has four main layers.

  • Scene → Snapshot — every tick, scene state is captured into a POD-based SceneSnapshot and delivered lock-free to propagation and render threads through TripleBuffer.
  • Propagation (modular) — four modules run on the common IPathModule interface, making it possible to swap algorithms and compare numeric behavior. The accelerator can use an internal BVH or delegate to a host-side BVH through callbacks.
  • Auralizator — tracked paths are synthesized into IRs, then filters, frequency decomposition, and HRTF are applied to produce spatial audio.
  • Audio Output — channel-mapped output is returned to the caller.

Module Layout

exaSound/
├── src/
│ ├── core/ # engine core
│ │ ├── EngineConfig # engine settings
│ │ ├── SceneSnapshot # immutable per-tick snapshot
│ │ ├── SnapshotBuilder # snapshot builder
│ │ ├── ExternalAccelerator # external BVH callback
│ │ ├── IAccelerator # accelerator abstraction
│ │ ├── Handle / Ref / FixedPool / PoolAllocator
│ │ ├── ThreadAffinity / Telemetry
│ │ └── PropagationResult
│ ├── propagation/
│ │ ├── Propagator # top-level dispatch
│ │ ├── RGC # Ray Generation Cluster
│ │ ├── module/ # Path Module abstraction
│ │ │ ├── IPathModule # common interface
│ │ │ ├── PathModuleRegistry
│ │ │ ├── reflection/ # SpecularReflectionModule
│ │ │ ├── diffraction/ # UTDDiffractionModule
│ │ │ ├── diffuse/ # DiffuseOffsetModule
│ │ │ └── reverb/ # StaticReverbModule
│ │ ├── UTDDiffraction.hpp # UTD formula
│ │ └── Ray/ # ray and plane utilities
│ ├── auralizator/ # auralization
│ │ ├── core / filter / frequence / HRTF / states
│ ├── scene/
│ │ ├── SoundObject # Object / Mesh / Preprocessing
│ │ └── BVH # Native BVH/TLAS
│ ├── math/, utils/, objects/, config/
│ ├── exasound.h # C++ main header
│ └── exasoundC.h # **public C API**
├── tests/ # Google Test
└── demo/ # demos

Public C API

The public interface is a C linkage API (exasoundC.h, about 120 exports).

CategoryRepresentative functions
LifecycleexaInit, exaReset, exaGetVersion, exaGetPathTypeCount
SceneexaNewScene, exaTickScene, exaSceneAddObject/Source/Listener
ObjectexaNewObject, exaObjectSetPosition/Rotation/Scale/Mesh, exaObjectSetUpdateType
MeshexaNewMesh, exaMeshSetData, exaMeshUpdateVertices, exaMeshRefit, exaMeshSetMaterial
MaterialexaAddSoundMaterial, exaSetSoundMaterial
SoundSourceexaNewSoundSource, exaSoundSourceSetPosition/Direction/Velocity/Intensity
Listener (basic)exaNewListener, exaListenerSetPosition/Orientation/Velocity, exaListenerSetRayCount/RayDepth
Listener (HRTF)exaInit loads the default HRTF once; listener renderers share it
RendererexaCreateRenderer, exaRenderSound, exaRemoveRenderer
ResultsexaGetValidPathCount, exaGetValidPaths, exaGetSortedIRDatas
Diagnostics/visualizationexaPropagatorGetGuidePlanes/MirrorPositions, exaPropagatorGetProfile, exaGetStatistics, exaGetMemoryTraceSnapshot, exaGetLastError

Getting Started

Requirements

  • CMake 3.22 or newer
  • C++17-compatible compiler
  • macOS · Windows · Linux

Build

cd exaSound
cmake -S . -B build
cmake --build build

With tests:

cmake -S . -B build -DBUILD_TESTS=ON
cmake --build build --target unit_tests
./build/tests/unit/unit_tests

Minimal Usage Scenario (Pseudocode)

#include "exasoundC.h"

// 1. Initialize the engine
exaInit();

// 2. Create a scene
int sceneID = exaNewScene();

// 3. Register a mesh and assign material
int meshID = exaNewMesh();
exaMeshSetData(meshID, vertices, vertexCount, indices, indexCount);
exaMeshSetMaterial(meshID, materialIndex);

// 4. Attach the mesh to an object and add it to the scene
int objID = exaNewObject();
exaObjectSetMesh(objID, meshID);
exaObjectSetPosition(objID, 0.f, 0.f, 0.f);
exaSceneAddObject(sceneID, objID);

// 5. Configure source and listener
int srcID = exaNewSoundSource();
exaSoundSourceSetPosition(srcID, 1.f, 1.f, 0.f);
exaSoundSourceSetIntensity(srcID, 1.0f);
exaSceneAddSource(sceneID, srcID);

int listenerID = exaNewListener();
exaListenerSetPosition(listenerID, -1.f, 1.f, 0.f);
exaListenerSetRayCount(listenerID, 4096);
exaListenerSetRayDepth(listenerID, 16); // up to EXA_MAX_DEPTH = 16
exaSceneAddListener(sceneID, listenerID);

// 6. Simulate and render audio each frame
for (;;) {
exaTickScene(sceneID, deltaTime);
exaRenderSound(/* render args */);
// Query results through exaGetValidPaths / exaGetSortedIRDatas
}

// 7. Clean up
exaReset();

This is pseudocode that shows the API flow. Check exasoundC.h for the actual signatures.

Core Concepts

Path Module Structure

Propagation algorithms are separated into four modules inside the internal pipeline.

ModulePath typeLocation
SpecularReflectionModulespecular reflectionpropagation/module/reflection/
UTDDiffractionModulediffraction (UTD)propagation/module/diffraction/
DiffuseOffsetModulescatteringpropagation/module/diffuse/
StaticReverbModulestatic reverbpropagation/module/reverb/

Each module implements IPathModule and runs in two phases.

  1. Phase 1 — buildSetupPlanes: construct SetupPlanes from guide ray results
  2. Phase 2 — validatePaths: trace and validate paths, then write valid paths to the output buffer

Path state is handed from Specular to Diffuse through ScatterHandoffEntry.

Lock-Free Snapshot (SceneSnapshot)

Scene state is captured every tick as an immutable POD snapshot.

  • All structures are flat arrays, non-virtual, and free of heap pointers, so values can be copied into TripleBuffer slots
  • Propagation and audio threads read separate slots lock-free
  • Geometry is managed through separate BVH double buffers (Phase 3)

This structure allows simulation and rendering to proceed safely on multiple threads without mutexes.

Material Model

SoundTriangle directly stores absorption and transmission. There is no separate Material ID/pointer model, and reflection follows this rule.

reflection = 1 - (absorption + transmission)

ExaRayHit exposes a materialId field, so ray casts can directly identify the hit material.

Ray Count and Ray Depth

Ray count and maximum reflection depth are configured per listener.

FunctionEffect
exaListenerSetRayCount(id, n)Set ray count
exaListenerSetRayDepth(id, d)Set maximum depth (1 ≤ d ≤ EXA_MAX_DEPTH = 16)

The current build caps path depth at EXA_MAX_DEPTH = 16.

Diffuse Scattering Options

ExaSTOption includes scattering parameters for fine-tuning the simulation.

FieldMeaning
diffuseEnabledEnable/disable scattering
diffuseStartDepthReflection depth where scattering starts, default 5
diffuseMaxOffsetRadiusScattering offset radius
diffuseCurveA/B/CScattering curve coefficients for distance and angle
guideDiffuseEnabledUse guide diffuse rays
guideDiffuseListenerHeadRadiusListener head radius, default 0.0875 m

HRTF

exaInit(); // loads the embedded default HRTF once
exaNewListener(); // renderer starts with the engine default HRTF
exaReset(); // releases renderer state and the default HRTF

Querying Results

FormFunction
Valid paths for visualization/debuggingexaGetValidPathCount, exaGetValidPaths
Sorted IR for convolution inputexaGetSortedIRDatas
Guide Plane / Mirror Position diagnosticsexaPropagatorGetGuidePlanes, exaPropagatorGetMirrorPositions

ExaPathData stores pos[0]=source, pos[1..N]=hit points, and pos[N+1]=listener.

Object Update Type

exaObjectSetUpdateType(objID, updateType);
// 0 = EXA_OBJECT_UPDATE_STATIC : no runtime TLAS/BLAS updates
// 1 = EXA_OBJECT_UPDATE_REFIT : deformation - refit BLAS + TLAS bounds
// 2 = EXA_OBJECT_UPDATE_REBUILD : topology changes - rebuild
// 3 = EXA_OBJECT_UPDATE_DYNAMIC : transform-only - refresh TLAS instance

Static objects avoid per-frame BVH refit cost.

Refit is the policy for geometry whose vertices move while the topology stays fixed, such as skinned animation. The vertices themselves are uploaded through the mesh-side two-call protocol.

exaMeshUpdateVertices(meshID, vertices, numVertices); // CPU skinning path
exaMeshRefit(meshID); // refit the mesh BVH

numVertices must exactly equal the vertex count passed to exaMeshSetData; a mismatch is rejected with EXA_ERR_INVALID_ARG. When the topology (triangle indices) changes, refit does not apply: rebuild through exaMeshSetData and set the object update type to Rebuild.

Diagnostics and Statistics

FunctionUse
exaGetStatistics()Ray, path, and timing statistics
exaPropagatorGetProfile(sceneID)Per-stage propagation profile
exaPropagatorGetGuidePlanes/MirrorPositionsInternal algorithm state
exaGetMemoryTraceSnapshot()Memory usage snapshot
exaGetLastError()Last error message

References