Facade API
Facade는 일반 웹 애플리케이션의 권장 진입점입니다. SoundTrace가 장면 수명주기를
관리하고 Listener, Source, Mesh가 앱의 3D 상태를 표현합니다.
기본 흐름
const sound = await SoundTrace.create(audioContext, options);
sound.listener.setPose(listenerPose);
const mesh = sound.addMesh(meshOptions);
const source = sound.addSource(sourceOptions);
const spatial = await source.play(inputNode);
spatial.connect(sound.output).connect(audioContext.destination);
await sound.update(0);
ST와 worker-hosted MT에서 이 호출 흐름은 같습니다. MT의 동기 native getter 대신
debugSnapshot() 같은 비동기 readback을 사용합니다.
SoundTrace 옵션
| 옵션 | 기본값 | 설명 |
|---|---|---|
mode | 미지정 | 'single_thread', 'multi_thread', 'gpu' 중 하나 |
thread | 'auto' | 고급 WASM 선택: 'auto', 'st', 'mt'; mode가 우선 |
quality | 'balanced' | 'fast', 'balanced', 'quality' |
throughput | 미지정 | MT worker 예산: 'low', 'medium', 'max' |
coordinateBasis | 코어 좌표계 | 렌더러 좌표계를 SDK 좌표계로 변환 |
coreBaseUrl | 패키지 내부 | st/, mt/가 들어 있는 코어 URL |
assetBaseUrl | 패키지 내부 | 재질과 HRTF asset URL |
propagationThreadCount | 엔진 기본값 | MT propagation thread 수의 저수준 override |
defaultMeshBuild | 엔진 기본값 | addMesh()가 사용할 기본 BVH build 옵션 |
sceneRatio | 1.0 | 장면 길이 단위당 미터. 지오메트리 사전 스케일과 혼용 금지(이중 스케일) |
autoLoadMaterials | true | 기본 재질을 로드해 이름 기반 매핑 활성화 |
transmissionModel | 'surface' | 직접음이 재질을 투과할 때의 에너지 감쇠 모델. 재질 투과 모델 참고 |
debug | false | 초기화 진단 로그 출력 |
Three.js는 카메라가 -Z를 바라보므로 다음 basis로 시작할 수 있습니다.
const sound = await SoundTrace.create(audioContext, {
mode: 'single_thread',
coordinateBasis: {
right: [-1, 0, 0],
up: [0, 1, 0],
forward: [0, 0, -1],
},
});
주요 API
SoundTrace
| API | 설명 |
|---|---|
SoundTrace.create(ctx, options?) | 엔진을 생성하고 로드 (생성자 + load()) |
output / audioContext | master output node와 앱이 넘긴 AudioContext |
listener | 장면의 단일 청취자 |
addMesh(options) | 음향 geometry 추가 |
removeMesh(mesh) | geometry 제거 |
addSource(options) | 공간 음원 추가 |
setQuality(tier) | 품질 preset 변경 |
setAudioOption(options) | block과 출력 channel override |
loadHrtf(mode, source?) | packaged 또는 custom HRTF 로드 |
loadMaterialAssets() | 재질 테이블을 수동 로드 (autoLoadMaterials: false일 때) |
enableGpu() | WebGPU 전파를 켜고 성공 여부를 반환. 미지원이면 false + CPU 유지 |
update(dt?) | 장면을 갱신하고 propagation 실행 |
debugSnapshot(options?) | MT 호환 비동기 진단 snapshot |
getStatistics(options?) | valid path·ray·메모리 통계 (async) |
getGpuStats() | GPU dispatch/fallback 카운터 (async) |
getIRs() | 최근 propagation의 path별 impulse response (async) |
renderMonoImpulseResponse(source, sec) | 오프라인 mono IR 렌더. 출력 채널이 1일 때만 사용 |
reset() | 엔진 상태 초기화 (async) |
dispose() | SDK가 소유한 리소스 해제. 멱등, using과 호환 |
Listener
sound.listener
.setPose({ position: [0, 1.6, 0], orientation: [0, 0, 0, 1] })
.setRenderOptions({ hrtfQuality: 'medium' });
장면에는 listener가 하나 있습니다. listener는 SoundTrace가 소유하므로 별도로
dispose하지 않습니다.
출력 렌더러는 setOutputMode()로 바꿉니다. 기본값 'hrtf'는 바이노럴 렌더러이고,
'speaker'는 내부 Ambisonic 스피커 렌더러(1ch/2ch)를 선택합니다. HRTF 모드와
로드한 HRTF 테이블은 'hrtf' 출력에만 적용됩니다.
sound.listener.setOutputMode('speaker');
Source
const source = sound.addSource({
position: [2, 1, -1],
gain: 1,
paths: {
direct: true,
reflection: true,
diffraction: true,
reverberation: true,
},
});
source.setPose({ position: [1, 1, -2] });
source.setGain(0.8);
source.setPathEnabled('reverberation', false);
play(input, channels?)는 입력을 연결한 AudioWorkletNode를 반환합니다. 출력은
앱이 sound.output 또는 다른 Web Audio graph에 연결합니다.
거리 감쇠
addSource()는 5종 path 전부에 기본 거리 감쇠 계수 { constant: 1, linear: 0, quadratic: 1 }을 적용합니다.
gain = 1 / (constant + linear * distance + quadratic * distance^2)
즉 기본 곡선은 1 / (1 + distance²)입니다. constant = 1이 거리 0 부근의 폭주를
막고, quadratic = 1이 역제곱에 가까운 감쇠를 만듭니다.
지향성
음원에 방향성을 주려면 각도별 대역 감쇠 테이블을 등록하고 활성화합니다.
source.setDirection([0, 0, -1]);
source.setDirectivityTable(anglesDeg, attenDbPerBand);
source.setDirectivityEnabled(true);
Mesh
const mesh = sound.addMesh({
vertices: geometry.attributes.position.array,
indices: geometry.index.array,
material: 'concrete',
});
mesh.setPose({
position: object.position.toArray(),
orientation: object.quaternion.toArray(),
scale: object.scale.toArray(),
});
indices는 메시 전체가 하나의 재질을 사용할 때 편리합니다. 면별 재질이 필요하면
{ a, b, c, materialIndex } 형태의 triangles를 전달하세요.
생성 후에도 재질과 갱신 정책을 바꿀 수 있습니다.
| API | 설명 |
|---|---|
setMaterial(material) | 메시 전체 재질 교체 |
setMaterialRange(triStart, triCount, material) | triangle 범위별 재질 교체 |
setUpdateType(type) / getUpdateType() | 'static', 'refit', 'rebuild', 'dynamic' |
setPose(pose) | position, orientation, scale 갱신 |
dispose() | 메시와 collider 해제 |
움직이지 않는 벽은 'static', vertex만 바뀌는 geometry는 'refit', topology가 바뀌면
'rebuild'를 사용합니다.
재질 투과 모델
transmissionModel은 직접음이 벽 같은 재질을 통과할 때 에너지를 잃는 방식을
선택합니다.
| 값 | 동작 |
|---|---|
'surface' (기본) | 표면을 지날 때마다 재질의 투과 계수를 한 번 적용. 벽 두께와 무관 |
'solid' | 두께 인식: ray가 solid 내부를 지나는 거리에 재질별 per-band 두께를 적용. 두꺼운 벽이 더 많이 차단 |
const sound = await SoundTrace.create(audioContext, {
mode: 'single_thread',
transmissionModel: 'solid',
});
'solid'는 두께가 저작된 재질만 전환하며, 두께가 없는 재질은 'surface' 동작을
유지합니다. 기본 재질 테이블에서는 22종 중 15종이 두께를 포함합니다.
opt-in 옵션입니다 — 벽 너머 음원의 크기가 달라지므로, 기존 장면의 사운드를 유지해야 한다면 기본값을 그대로 두세요.
전파 path cache
모든 세션은 propagation path cache가 켜진 상태로 시작합니다(고정 seed, 크기 512).
ST와 MT 양쪽에서 첫 프레임 이전에 적용되며 reset() 이후에도 다시 적용되므로,
캐시를 켜기 위해 별도로 호출할 API는 없습니다.
프레임 업데이트
pose 변경은 빠르게 기록할 수 있지만, propagation update는 동시에 하나만 실행하는 것이 안전합니다.
let updateInFlight: Promise<number> | undefined;
function frame(dt: number) {
if (!updateInFlight) {
updateInFlight = sound.update(dt).finally(() => {
updateInFlight = undefined;
});
}
}
수명주기
SoundTrace, Source, Mesh는 dispose()를 지원합니다. SoundTrace.dispose()는
listener를 포함해 SDK가 소유한 리소스를 정리하며 여러 번 호출해도 안전합니다.
source.dispose();
mesh.dispose();
sound.dispose();