๐ @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:" ) with smooth volume automation
- ๐ฆ
Direct Sources & Streaming โ
load() for direct refs; auto-decoding +
streaming cache
- ๐ง
AI Hearing Propagation โ
propagate() /
cancelPropagation() waves; occlusion-aware intensity;
heard event
- ๐ฆ
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
extends and versioning
- ๐งพ
Rich Events โ Play/fade/updates, plugin diagnostics, preload + propagation lifecycle
---
โ๏ธ Constructor
``
js
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
| โ
|
| 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 | โ
|
| 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 | โ
|
| 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 | โ
|
| play(idOrDirect, opts?) | Plays by soundboard id or direct source. | string | DirectRef, PlayOptions | Promise (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:" 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).
$3
| 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) โ static value or function return |
---
$3
#### 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" | or |
| 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 | โ | Default volume/pitch/distance/loop |
---
$3
#### 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 | โ
| โ | World position of emitter |
| forward | TimeFn | โ | {0,0,1} | Emitter forward vector |
| up | TimeFn | โ | {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 |
---
$3
#### 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:" |
| 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 | Live move/aim updates |
| layer | string | Re-route to a different bus |
---
$3
Use to filter who can hear a sound or who participates in a propagation wave.
#### Audience
A discriminated union:
`ts
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.
---
$3
#### PropagateOptions
| Field | Type | Required | Default | Notes |
|------|------|----------|---------|------|
| origin | TimeFn | โ
| โ | 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" }
---
$3
#### 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" | Distance metric |
#### ProximityTrigger
| Field | Type | Required | Default | Notes |
|------|------|----------|---------|------|
| id | string | โ
| โ | Unique zone id |
| position | TimeFn | โ
| โ | 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 }
---
$3
#### 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 |
---
$3
#### 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.
> `js
> engine.enableLimiter({
> threshold : -3, ratio : 12, attack : 0.003, release : 0.120,
> bypassLayers : ["ui"]
> });
> `
---
$3
#### enableReverb(options)
| Field | Type | Default | Notes |
|------|------|---------|------|
| mix | Linear01 | 0.2 | Wet contribution on master |
| ir | AudioBuffer | URL | builtin | Convolution impulse response |
| 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 |
---
$3
- setVolume("master", value) โ Set master gain.
- setVolume("layer:", value) โ Set per-bus gain.
- ensureLayer(name, node?) โ Create a custom bus (default GainNode); you can pass a pre-wired AudioNode for advanced routing (e.g., side-chains).
---
$3
#### addListener(listener)
| Field | Type | Required | Default | Notes |
|------|------|----------|---------|------|
| id | string | โ
| โ | Unique id |
| position | TimeFn | โ
| โ | Ear position |
| orientation.forward | TimeFn | โ | {0,0,-1} | Facing |
| orientation.up | TimeFn | โ | {0,1,0} | Up vector |
| tags | string[] | โ | [] | Audience/meta |
setActiveListener(id) switches the engine to drive WebAudioโs AudioListener from this definition.
---
$3
Hook argument available to beforeConnect, afterConnect, frame, onStop.
`ts
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:"
contextTime : number; // audioCtx.currentTime
instanceId : string; // current play instance
meta? : any; // item meta / tags
};
`
---
$3
- 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
`bash
npm install @trap_stevo/voxen
or
yarn add @trap_stevo/voxen
`
---
โก Quick Start & Examples
$3
`js
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" }));
`
---
$3
`js
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 }
});
`
---
$3
`js
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
});
`
---
$3
`js
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.
$3
| 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. |
$3
`js
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
$3
`
Source โ [Plugin.beforeConnect nodes] โ preGain โ gain โ layer โ master โ destination
`
Use beforeConnect for filters or delays.
Use custom layers for post-processing or global DSP.
$3
Set cullHz to match simulation tick:
- 90 Hz for rhythmic or rapid feedback loops
- 30โ45 Hz for dense spatial environments
$3
Gain and Panner nodes reuse pooled memory.
Inactive nodes disconnect automatically.
Streamed sources skip redundant decoding.
$3
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.
$3
Three.js
- Update listener in render loop via updateListener().
- Pass camera.getWorldPosition() and camera.getWorldDirection().
Babylon.js
- Hook into camera.onViewMatrixChangedObservable.
React / DOM
- Resume audio after click:
`js
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.
$3
Monitor pluginError for DSP exceptions.
Audit getActiveInstances() for pool usage.
Trace audibility for live diagnostics:
`js
voxen.on("audibility", e => console.log("Audible:", e.audible));
``
$3
| 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.