Load any audio plugin. Process any audio. No DAW required.
Host VST3 and CLAP plugins from JavaScript — as native modules or Web Audio nodes. Format is transparent: point at a file, get audio out. Part of audiojs.
import { load } from '@audio/host'
const reverb = load('reverb.vst3')
const gain = load('gain.clap')
const output = reverb.processAll([left, right])- Format-transparent —
@audio/hostdetects.vst3/.clapand routes to the right backend - AudioWorklet — register any plugin as
AudioWorkletProcessor, chain with other nodes - Zero config — point at a plugin file, get audio out
- Parameters — enumerate, read, write plugin parameters
- State — save/restore plugin state as binary blob
- Cross-platform — macOS, Linux, Windows via prebuilt binaries
- 128-sample blocks — matches Web Audio
BLOCK_SIZEexactly, zero reblocking
npm install @audio/host # universal — installs available format hostsOr install format hosts individually:
npm install @audio/host-vst # VST3 only
npm install @audio/host-clap # CLAP onlyPrebuilt native binaries install automatically for your platform. Falls back to local node-gyp compilation if unavailable.
import { scan } from '@audio/host'
const plugins = scan()
// [
// { path: '/Library/Audio/.../REV-X_HALL.vst3', name: 'REV-X Hall', vendor: 'Yamaha Corporation', format: 'vst3' },
// { path: '/Library/Audio/.../gain.clap', name: 'Test Gain', vendor: 'audiojs', format: 'clap' },
// ...
// ]
// Or scan specific directories
scan(['/my/custom/plugin/dir'])Scan probes each plugin in a subprocess — a crashed or misbehaving plugin won't take down your process.
import { load } from '@audio/host'
const plugin = load('/path/to/plugin.vst3', {
sampleRate: 44100, // default
channels: 2, // default
blockSize: 128 // default — matches Web Audio BLOCK_SIZE
})
console.log(plugin.name) // 'REV-X Hall'
console.log(plugin.vendor) // 'Yamaha Corporation'
// Process full buffers (handles block splitting internally)
const output = plugin.processAll([leftChannel, rightChannel])
// Or process one block at a time
plugin.process([inL, inR], [outL, outR])
plugin.close()VST3 getParam/setParam values are normalized to 0–1; descriptor min/max
are in plugin units. setParam updates the controller immediately and sends
the latest value to the processor at the next block boundary. Multiple writes
to the same parameter before processing coalesce. Sample-offset scheduling
is not exposed by this API.
const params = plugin.params
// [{ id: 84, name: 'Threshold', min: 0, max: 1, defaultValue: 0 }, ...]
plugin.setParam(84, 0.5)
plugin.getParam(84) // 0.5Any plugin as a standard AudioWorkletNode:
import { AudioContext, AudioWorkletNode, AudioWorkletProcessor } from 'web-audio-api'
import { register } from '@audio/host'
const ctx = new AudioContext()
await ctx.audioWorklet.addModule(register('reverb.vst3', AudioWorkletProcessor))
await ctx.audioWorklet.addModule(register('gain.clap', AudioWorkletProcessor))
const src = ctx.createBufferSource()
const reverb = new AudioWorkletNode(ctx, 'REV-X Hall')
const gain = new AudioWorkletNode(ctx, 'Test Gain')
src.connect(reverb).connect(gain).connect(ctx.destination)
src.start()import { readFileSync } from 'fs'
import decode from '@audio/decode'
import speaker from '@audio/speaker'
import { load } from '@audio/host'
const audio = await decode(readFileSync('input.wav'))
const plugin = load('plugin.vst3', { sampleRate: audio.sampleRate })
const output = plugin.processAll(audio.channelData)
plugin.close()
// Play result
const write = speaker({ sampleRate: audio.sampleRate, channels: 2, bitDepth: 32 })
const buf = Buffer.alloc(output[0].length * 2 * 4)
for (let i = 0; i < output[0].length; i++)
for (let c = 0; c < 2; c++)
buf.writeFloatLE(output[c][i], (i * 2 + c) * 4)
write(buf, () => write.flush(() => write.close()))| Package | Description |
|---|---|
@audio/host |
Universal entry — auto-detects format, routes to installed host |
@audio/host-vst |
VST3 host (C++) |
@audio/host-clap |
CLAP host (C) |
Prebuilt binaries per platform (installed automatically via optionalDependencies):
@audio/host-{vst,clap}-{darwin-arm64,darwin-x64,linux-x64,linux-arm64,win32-x64}
Scan standard system paths (or custom dirs) for installed plugins. Each plugin is probed in a subprocess — crashes are isolated. Returns [{ path, name, vendor, format }].
Load a plugin. Format detected from file extension.
| Option | Default | Description |
|---|---|---|
sampleRate |
44100 |
Sample rate in Hz |
channels |
2 |
Number of audio channels |
blockSize |
128 |
Samples per process block |
Returns:
| Property / Method | Description |
|---|---|
name |
Plugin name |
vendor |
Plugin vendor |
blockSize |
Block size used |
params |
[{ id, name, min, max, defaultValue }] |
getParam(id) |
Get normalized parameter value |
setParam(id, val) |
Set normalized parameter value |
process(in, out) |
Process one block — arrays of Float32Array |
processAll(channels) |
Process full buffers — returns Float32Array[] |
getState() |
Plugin state as Buffer |
setState(buf) |
Restore plugin state |
close() |
Release resources |
Register plugin as AudioWorkletProcessor for use with addModule:
await ctx.audioWorklet.addModule(register(path, AudioWorkletProcessor))
const node = new AudioWorkletNode(ctx, pluginName)Array of supported extensions based on installed packages, e.g. ['.vst3', '.clap'].
Where are my plugins?
| OS | VST3 | CLAP |
|---|---|---|
| macOS | /Library/Audio/Plug-Ins/VST3/ |
/Library/Audio/Plug-Ins/CLAP/ |
| Linux | ~/.vst3/ or /usr/lib/vst3/ |
~/.clap/ or /usr/lib/clap/ |
| Windows | C:\Program Files\Common Files\VST3\ |
C:\Program Files\Common Files\CLAP\ |
Can I use this in the browser?
Not for native plugins — they're compiled machine code. CLAP-to-WASM is planned (CLAP is pure C, compiles via Emscripten). WAM support planned as @audio/host-wam.
Does a crashed plugin crash my process?
Currently yes. Plugin isolation is planned.
What about GUIs?
Not supported. This is a headless host. Use a DAW for plugin UIs.
What about MIDI?
Planned — note on/off, CC, pitch bend routed to plugin event input.
- Plugins are platform-native. A macOS
.vst3won't run on Windows. Each platform needs its own plugins installed. - Some plugins need activation. Licensed plugins (Waves, iZotope) may fail without auth. Free plugins (Airwindows, Bertom, TDR) work directly.
- Null parameter changes. Some plugins dereference
inputParameterChangeswithout null-checking. If a plugin crashes onprocess(), this may be why. - State is opaque.
getState()returns the plugin's internal binary format, not portable across versions.
MIT
"I am the ability in man." — Bhagavad Gita 7.8