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​