Package Exports
- @trap_stevo/voxen
Readme
๐ @trap_stevo/voxen
Universal Sound Intelligence Layer.
Every sound. Every space. One voice.
Unifies interface audio, spatial environments, and intelligent sound behavior inside one modular, extensible system.
From reactive UI cues to full 3D soundfieldsโaudio flows with precision, elegance, and control.
โจ Features
- ๐งญ Universal Spatial Audio โ 3D/2D positional playback (HRTF/equal-power) with distance rolloffs & cones
- ๐งฉ Plugin Architecture โ Built-ins: Occlusion, Proximity, Limiter, Reverb + custom DSP extensions
- ๐๏ธ Layered Mixing โ Arbitrary buses (
"layer:<name>") with smooth volume automation - ๐ฆ Direct Sources & Streaming โ
load()for direct refs; auto-decoding +<audio>streaming cache - ๐ง AI Hearing Propagation โ
propagate()/cancelPropagation()waves; occlusion-aware intensity;heardevent - ๐ฆ Audience Targeting โ Include/exclude/byTag/custom predicates per play/propagation
- ๐ Optimized Core โ Node pooling (Gain/Panner), audibility culling, guarded listener updates
- ๐๏ธ Soundboards โ Declarative registries with hierarchical
extendsand versioning - ๐งพ Rich Events โ Play/fade/updates, plugin diagnostics, preload + propagation lifecycle
โ๏ธ Constructor
import { Voxen } from "@trap_stevo/voxen";
const voxen = new Voxen({
cullHz: 60
});Options
| Option | Type | Default | Description |
|---|---|---|---|
| cullHz | number | 30 | Tick rate for listener and plugin updates |
๐ง Core Concepts
- Listener โ Defines hearing point. Multiple listeners allowed; one active at a time.
- Soundboard โ Groups related sounds; supports overrides and inheritance.
- Layer โ Controls volume and mix per domain (UI, world, music).
- Plugin โ Extends system with DSP or behavioral logic.
- Propagation โ Expanding hearing waves for AI detection and stealth simulation.
โ๏ธ Core Methods
| Method | Description | Params (summary) | Returns | Async |
|---|---|---|---|---|
resume() |
Unlocks and resumes the AudioContext. | โ | Promise<void> |
โ |
destroy() |
Stops all sounds, clears timers/caches, closes context. | โ | void |
โ |
on(type, handler) |
Subscribes to an event. | type, handler(payload) |
() => void |
โ |
off(type, handler) |
Unsubscribes an event handler. | type, handler |
void |
โ |
registerPlugin(plugin) |
Registers a custom plugin. | { name, ... } |
void |
โ |
enableReverb(options?) |
Enables built-in reverb bus. | { mix?, ir? } |
Promise<void> |
โ |
disableReverb() |
Disables built-in reverb. | โ | void |
โ |
enableLimiter(options?) |
Enables built-in limiter. | { threshold?, knee?, ratio?, attack?, release? } |
void |
โ |
disableLimiter() |
Disables built-in limiter. | โ | void |
โ |
enableOcclusion(options?) |
Enables occlusion (raycast-aware). | { raycast?, getListenerPosition?, maxAttenuation?, smoothing? } |
void |
โ |
disableOcclusion() |
Disables occlusion. | โ | void |
โ |
setProximityTriggers(options) |
Starts/updates proximity trigger loop. | { getListenerPosition, getInstancePosition?, triggers[], tickHz?, distance? } |
void |
โ |
registerSoundboard(board) |
Registers a soundboard. | { name, items, extends? } |
void |
โ |
useSoundboards(names) |
Activates soundboards (topmost wins). | string[] |
void |
โ |
contains(id) |
Checks if an active soundboard has id. |
string |
boolean |
โ |
preload(ids) |
Prepares/decodes assets by ids. | string[] |
Promise<void> |
โ |
addListener(listener) |
Adds a listener. | { id, position, orientation?, tags? } |
void |
โ |
updateListener(id, patch) |
Patches listener fields. | string, { position?, orientation?, tags? } |
void |
โ |
removeListener(id) |
Removes a listener. | string |
void |
โ |
setActiveListener(id) |
Sets active listener and applies to context. | string |
void |
โ |
load(soundId, sourceRef, defaults?) |
Registers & prepares a direct sound. | string, SourceRef, { loop?, volume?, pitch?, distance? } |
Promise<void> |
โ |
play(idOrDirect, opts?) |
Plays by soundboard id or direct source. | `string | DirectRef, PlayOptions` |
Promise<string> (instanceId) |
stop(instanceId, opts?) |
Stops an instance (optional fade). | string, { fadeOut? } |
void |
โ |
updateInstance(instanceId, patch) |
Updates volume/pitch/spatial origin. | string, { volume?, pitch?, spatial? } |
void |
โ |
getActiveInstances() |
Lists active instance ids. | โ | string[] |
โ |
setVolume(target, value) |
Sets "master" or "layer:<name>" volume. |
string, 0..1 |
void |
โ |
ensureLayer(name, node?) |
Ensures a named layer exists (Gain by default). | string, AudioNode? |
void |
โ |
propagate(options) |
Starts an expanding hearing wave. | PropagateOptions |
string (token) |
โ |
cancelPropagation(token, extra?) |
Cancels an in-flight wave. | string, { natural? } |
void |
โ |
๐ Events
| Event | Description |
|---|---|
play |
Fires when playback starts. |
ended |
Fires when playback stops. |
audibility |
Signals sound visibility to listener. |
layerVolumeChange |
Occurs when layer volume changes. |
pluginError |
Reports plugin failure. |
fadeStart |
Marks fade-in or fade-out begin. |
fadeEnd |
Marks fade completion. |
instanceUpdate |
Reflects runtime parameter change. |
layerMute |
Toggles layer mute state. |
preloadComplete |
Confirms preload success. |
contextStateChange |
Indicates context resume or suspend. |
propagateStart |
Emitted when a sound wave begins. |
heard |
Emitted when a listener perceives a propagated sound. |
propagateEnd |
Fired when propagation completes or is canceled. |
๐ Parameter Reference
This section defines the shapes and defaults of Voxenโs primary configuration objects. All objects are plain JSON-like structures; functions are allowed where noted (e.g., for live positions).
๐ค Common Scalar & Utility Types
| Type | Meaning |
|---|---|
Seconds |
Number in seconds (e.g., 0.25) |
Hertz |
Number in Hz (e.g., 60) |
Decibels |
Number in dBFS for WebAudio nodes (e.g., -3) |
Linear01 |
Number clamped to 0..1 |
Vec3 |
{ x:number, y:number, z:number } |
Vec3Fn |
() => Vec3 (evaluated per frame/tick) |
TimeFn<T> |
`T |
๐๏ธ Sound Sources
SourceRef
A reference to media to be decoded or streamed.
| Field | Type | Required | Notes |
|---|---|---|---|
kind |
"url" | "element" | "buffer" |
โ | How audio is provided |
href |
string |
โฌ
when kind:"url" |
Absolute/relative URL |
element |
HTMLMediaElement |
โฌ
when kind:"element" |
<audio> or <video> |
buffer |
AudioBuffer |
โฌ
when kind:"buffer" |
Pre-decoded PCM |
stream |
boolean |
โ | If true, uses media element streaming path |
loop |
boolean |
โ | Default loop behavior (can be overridden) |
mime |
string |
โ | Hint (e.g., "audio/ogg") |
DirectRef
Play without a soundboard entry.
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string |
โ | Logical id for this sound |
sources |
SourceRef[] |
โ | One or more fallbacks |
meta |
Partial<PlayDefaults> |
โ | Default volume/pitch/distance/loop |
๐งญ Spatial & Distance
DistanceModel
"inverse" | "linear" | "exponential"DistanceOptions
| Field | Type | Default | Notes |
|---|---|---|---|
refDistance |
number |
1 |
Distance at which volume is 100% |
maxDistance |
number |
10000 |
Beyond this, no further attenuation |
rolloff |
DistanceModel |
"inverse" |
Attenuation curve |
coneInner |
number |
360 |
Degrees of full volume |
coneOuter |
number |
360 |
Degrees at outer cone edge |
coneOuterGain |
Linear01 |
0 |
Gain at outer cone edge |
SpatialOptions
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
origin |
TimeFn<Vec3> |
โ | โ | World position of emitter |
forward |
TimeFn<Vec3> |
โ | {0,0,1} |
Emitter forward vector |
up |
TimeFn<Vec3> |
โ | {0,1,0} |
Emitter up vector |
distance |
DistanceOptions |
โ | see above | Distance/cone controls |
hrtf |
boolean |
โ | true |
If false, uses equal-power panner |
stereo |
boolean |
โ | false |
If true, bypasses HRTF for stereo sources |
๐๏ธ Playback
FadeOptions
| Field | Type | Default | Notes |
|---|---|---|---|
in |
Seconds |
0 |
Fade-in duration |
out |
Seconds |
0 |
Fade-out duration |
PlayDefaults
| Field | Type | Default | Notes |
|---|---|---|---|
volume |
Linear01 |
1.0 |
Base gain |
pitch |
number |
1.0 |
PlaybackRate |
loop |
boolean |
false |
Looping behavior |
distance |
DistanceOptions |
โ | If spatialized |
PlayOptions
| Field | Type | Default | Notes |
|---|---|---|---|
layer |
string |
"master" |
Route to "layer:<name>" |
volume |
Linear01 |
inherits | Overrides default |
pitch |
number |
1.0 |
0.5 = down octave, 2.0 = up octave |
loop |
boolean |
inherits | โ |
spatial |
SpatialOptions |
โ | Enables 3D/2D panning |
fade |
FadeOptions |
{} |
Smooth in/out |
tags |
string[] |
[] |
Arbitrary metadata tags |
audience |
Audience |
โ | Targeting rules (see below) |
onEnd |
() => void |
โ | Convenience callback |
updateInstance(instanceId, patch)
| Field | Type | Notes |
|---|---|---|
volume |
Linear01 |
Smooth internal ramping |
pitch |
number |
PlaybackRate update |
spatial |
Partial<SpatialOptions> |
Live move/aim updates |
layer |
string |
Re-route to a different bus |
๐งโ๐ฆป Audience Targeting
Use to filter who can hear a sound or who participates in a propagation wave.
Audience
A discriminated union:
type Audience =
| { kind:"all" }
| { kind:"include"; ids:string[] }
| { kind:"exclude"; ids:string[] }
| { kind:"byTag"; tags:string[] }
| { kind:"predicate"; test:(meta:{ id:string; tags?:string[]; extra?:any }) => boolean }- When used for playback, audience is applied at instance connect time.
- When used for propagate(), audience is sampled per wavefront step.
๐ฐ๏ธ Propagation (AI Hearing)
PropagateOptions
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
origin |
TimeFn<Vec3> |
โ | โ | Source of the wave |
power |
number |
โ | โ | Scalar โloudnessโ that maps to effective radius |
maxRadius |
number |
โ | โ | Upper bound for distance check |
velocity |
number |
โ | 343 |
m/s (speed of sound at ~20ยฐC) |
sampleHz |
Hertz |
โ | 30 |
Wave stepping rate |
decay |
Linear01 |
โ | 0.0 |
Per-meter loss beyond distance model |
occlusionAware |
boolean |
โ | false |
Enables raycast penalty |
occlusionPenalty |
Linear01 |
โ | 0.5 |
Additional attenuation when blocked |
audience |
Audience |
โ | {kind:"all"} |
Target listeners/entities |
tags |
string[] |
โ | [] |
Labels attached to heard events |
token |
string |
โ | auto | Return value; can be supplied for idempotence |
Events (Propagation)
propagateStartโ{ token, origin, power, maxRadius }heardโ{ token, listenerId, distance, intensity, tags }propagateEndโ{ token, reason:"completed"|"canceled" }
๐งฒ Proximity Triggers
setProximityTriggers(options)
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
getListenerPosition |
() => Vec3 |
โ | โ | Primary tracked subject |
getInstancePosition |
(id:string) => Vec3 |
โ | โ | Resolve instance anchor |
triggers |
ProximityTrigger[] |
โ | โ | Zones to monitor |
tickHz |
Hertz |
โ | 30 |
Polling rate |
distance |
`{ metric?:"euclidean" | "manhattan" }` | โ | "euclidean" |
ProximityTrigger
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
id |
string |
โ | โ | Unique zone id |
position |
TimeFn<Vec3> |
โ | โ | Center of zone |
radius |
number |
โ | โ | Meters |
hysteresis |
number |
โ | 0.0 |
Entry/exit stability band |
cooldown |
Seconds |
โ | 0.0 |
Rate limit events |
enterPlay |
{ soundId:string, options?:PlayOptions } |
โ | โ | Fire on first entry |
exitPlay |
{ soundId:string, options?:PlayOptions } |
โ | โ | Fire on first exit |
tags |
string[] |
โ | [] |
Zone metadata |
Events:
proximityEnterโ{ id, position }proximityExitโ{ id, position }
๐งฑ Occlusion Plugin
enableOcclusion(options)
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
raycast |
(from:Vec3, to:Vec3) => boolean |
โ | โ | true if blocked |
getListenerPosition |
() => Vec3 |
โ | โ | For primary listener |
maxAttenuation |
Linear01 |
โ | 0.55 |
Max gain drop when blocked |
smoothing |
Seconds |
โ | 0.08 |
Attack/release toward target |
bypassLayers |
string[] |
โ | [] |
Skip occlusion on these buses |
๐งฐ Limiter Plugin
enableLimiter(options)
| Field | Type | Default | Notes |
|---|---|---|---|
threshold |
Decibels |
-1.0 |
Compressor threshold |
knee |
Decibels |
0.0 |
Soft knee amount |
ratio |
number |
8.0 |
Compression ratio |
attack |
Seconds |
0.005 |
Attack time |
release |
Seconds |
0.100 |
Release time |
makeupGain |
Decibels |
0.0 |
Optional post-comp gain |
bypassLayers |
string[] |
[] |
Do not route these layers through limiter (alias: excludeLayers) |
Recommended: bypass UI, keep world/music under control.
engine.enableLimiter({ threshold : -3, ratio : 12, attack : 0.003, release : 0.120, bypassLayers : ["ui"] });
๐ซ๏ธ Reverb Bus
enableReverb(options)
| Field | Type | Default | Notes |
|---|---|---|---|
mix |
Linear01 |
0.2 |
Wet contribution on master |
ir |
`AudioBuffer | URL` | builtin |
preDelay |
Seconds |
0.0 |
Optional pre-delay |
decay |
Seconds |
1.2 |
Tail shaping (when using algorithmic IR) |
bypassLayers |
string[] |
[] |
Do not send these layers to reverb |
๐๏ธ Mixing & Layers
setVolume("master", value)โ Set master gain.setVolume("layer:<name>", value)โ Set per-bus gain.ensureLayer(name, node?)โ Create a custom bus (defaultGainNode); you can pass a pre-wiredAudioNodefor advanced routing (e.g., side-chains).
๐ Listeners
addListener(listener)
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
id |
string |
โ | โ | Unique id |
position |
TimeFn<Vec3> |
โ | โ | Ear position |
orientation.forward |
TimeFn<Vec3> |
โ | {0,0,-1} |
Facing |
orientation.up |
TimeFn<Vec3> |
โ | {0,1,0} |
Up vector |
tags |
string[] |
โ | [] |
Audience/meta |
setActiveListener(id) switches the engine to drive WebAudioโs AudioListener from this definition.
๐งฉ Plugin Hook Context (ctx)
Hook argument available to beforeConnect, afterConnect, frame, onStop.
type PluginCtx = {
engine : Voxen;
audioCtx : AudioContext;
nodes : {
source : AudioNode;
gain : GainNode;
layer : GainNode; // or custom node if provided
master : GainNode;
};
layer : string; // "master" or "layer:<name>"
contextTime : number; // audioCtx.currentTime
instanceId : string; // current play instance
meta? : any; // item meta / tags
};๐ก Events (Payload Shapes)
playโ{ instanceId, id, layer, tags? }endedโ{ instanceId, id, reason:"natural"|"stop"|"error" }audibilityโ{ instanceId, audible:boolean }layerVolumeChangeโ{ layer, value }pluginErrorโ{ plugin, error }fadeStartโ{ instanceId, kind:"in"|"out", duration }fadeEndโ{ instanceId, kind:"in"|"out" }instanceUpdateโ{ instanceId, patch }preloadCompleteโ{ ids:string[] }contextStateChangeโ{ state:"running"|"suspended"|"closed" }proximityEnter/proximityExitโ{ id, position }propagateStart/heard/propagateEndโ see Propagation above
๐ฆ Installation
npm install @trap_stevo/voxen
# or
yarn add @trap_stevo/voxenโก Quick Start & Examples
Example 1 โ UI Click
import { Voxen } from "@trap_stevo/voxen";
const voxen = new Voxen();
voxen.registerSoundboard({
name: "ui",
items: [
{
id: "ui/click",
sources: [{ kind: "url", href: "https://upload.wikimedia.org/wikipedia/commons/2/26/Computer_mouse_single_click.ogg" }],
meta: { volume: 0.85 }
}
]
});
voxen.useSoundboards(["ui"]);
await voxen.preload(["ui/click"]);
await voxen.resume();
document.addEventListener("click", () => voxen.play("ui/click", { layer: "ui" }));Example 2 โ 3D Fountain Ambience
voxen.addListener({
id: "listener",
position: () => ({ x: camera.x, y: camera.y, z: camera.z }),
orientation: {
forward: () => camera.forward(),
up: () => ({ x: 0, y: 1, z: 0 })
}
});
voxen.setActiveListener("listener");
voxen.registerSoundboard({
name: "scene",
items: [
{
id: "env/fountain",
sources: [{ kind: "url", href: "https://upload.wikimedia.org/wikipedia/commons/5/54/Fountainnoise.ogg" }],
meta: { loop: true, volume: 0.85, distance: { refDistance: 1, maxDistance: 45 } }
}
]
});
voxen.useSoundboards(["scene"]);
await voxen.preload(["env/fountain"]);
await voxen.play("env/fountain", {
spatial: {
origin: () => ({ x: 6, y: 0.5, z: -10 }),
distance: { refDistance: 1, maxDistance: 45 },
hrtf: true
},
layer: "ambient",
fade: { in: 1.0 }
});Example 3 โ Proximity + Occlusion + AI Propagation
voxen.enableOcclusion({
raycast: (from, to) => performRaycast(from, to),
getListenerPosition: () => player.position,
maxAttenuation: 0.55,
smoothing: 0.08
});
voxen.setProximityTriggers({
getListenerPosition: () => player.position,
triggers: [
{
id: "pickup-zone",
position: { x: 5, y: 0, z: -8 },
radius: 1.5,
enterPlay: { soundId: "ui/click", options: { layer: "ui" } }
}
],
tickHz: 45
});
// AI Hearing Example
voxen.on("heard", e => console.log("AI heard:", e.listenerId, e.tags));
voxen.propagate({
origin: () => player.position,
power: 1,
maxRadius: 40,
velocity: 343,
sampleHz: 30,
audience: { kind: "byTag", tags: ["ai"] },
occlusionAware: true,
occlusionPenalty: 0.6
});Example 4 โ Layer Mixing & Live Updates
voxen.setVolume("layer:ambient", 0.3);
voxen.updateInstance(fountainId, { pitch: 1.15 });
voxen.stop(fountainId, { fadeOut: 0.5 });๐งฉ Custom Plugin API
Custom DSP, analyzers, or automation logic can slot directly into the playback chain.
Configuration Hooks
| Hook | Trigger | Description |
|---|---|---|
onRegister({ engine, audioCtx }) |
Plugin registration | Prepare nodes or parameters. |
beforeConnect(ctx, node) |
Before connection | Insert filters, delays, or custom nodes. |
afterConnect(ctx) |
After connection | Run post-setup logic. |
frame(ctx, dt) |
Every tick (cullHz) |
Update dynamic parameters. |
onStop(ctx) |
Sound stop | Clean resources. |
Usage Example
const EchoPlugin = {
name: "custom:echo",
onRegister({ audioCtx }) {
this.delay = audioCtx.createDelay();
this.delay.delayTime.value = 0.3;
this.feedback = audioCtx.createGain();
this.feedback.gain.value = 0.4;
this.delay.connect(this.feedback);
this.feedback.connect(this.delay);
},
beforeConnect(ctx, node) {
node.connect(this.delay);
return this.delay;
},
frame(ctx, dt) {
const g = ctx.nodes.gain.gain.value;
this.feedback.gain.setTargetAtTime(g * 0.4, ctx.nodes.master.contextTime, 0.05);
},
onStop() {
this.delay.disconnect();
this.feedback.disconnect();
}
};
voxen.registerPlugin(EchoPlugin);โก Performance & Integration Notes
๐๏ธ Plugin Chain Order
Source โ [Plugin.beforeConnect nodes] โ preGain โ gain โ layer โ master โ destinationUse beforeConnect for filters or delays.
Use custom layers for post-processing or global DSP.
๐งฎ Frame Efficiency
Set cullHz to match simulation tick:
- 90 Hz for rhythmic or rapid feedback loops
- 30โ45 Hz for dense spatial environments
๐ง Memory & Pooling
Gain and Panner nodes reuse pooled memory.
Inactive nodes disconnect automatically.
Streamed sources skip redundant decoding.
โก Latency Control
Chromium engines reach sub-5 ms latency with preloaded buffers.
Call .preload() before playback to prevent decode delays.
Trigger .resume() once after user gesture to unlock playback.
๐น๏ธ Integration Guidelines
Three.js
- Update listener in render loop via
updateListener(). - Pass
camera.getWorldPosition()andcamera.getWorldDirection().
Babylon.js
- Hook into
camera.onViewMatrixChangedObservable.
React / DOM
- Resume audio after click:
useEffect(() => { const click = () => voxen.play("ui/click"); document.addEventListener("click", click); return () => document.removeEventListener("click", click); }, []);
Physics Engines
- Provide world coordinates in
spatial.origin. - Interpolation handled internally.
๐งญ Debugging
Monitor pluginError for DSP exceptions.
Audit getActiveInstances() for pool usage.
Trace audibility for live diagnostics:
voxen.on("audibility", e => console.log("Audible:", e.audible));๐ Best Practice Summary
| Area | Action |
|---|---|
| Update Rate | Maintain 30โ60 Hz listener updates |
| Buffering | Preload every large asset |
| Layers | Separate UI, World, Music buses |
| Plugins | Register once; reuse globally |
| Stop | Fade out softly to avoid clicks |
| Environment | Use 48 kHz audio contexts for consistency |
๐ License
See License in LICENSE.md
๐ Orchestrate Sound. One Voice.
Unify UI clicks, spatial worlds, and cinematic layers into one deterministic audio fabric. From web apps to 3D games, Voxen delivers low-latency playback, precise spatialization, and event-driven controlโso sound design scales with your vision, not your constraints. Every sound. Every space. One voice.