Skip to content

Repository files navigation

One graph, multi renderer, multi player: browser editor, mobile Safari, native macOS and Android, three.js via XR Publisher

audiocrate - One graph. Multi render. Multi player.

A Web Audio scene graph, extendable AudioMaterial system, and a serializable DSP graph that the same interpreter runs offline and in an AudioWorklet.

Inspired by how three.js is organized: a scene of objects, materials you can write yourself, and loaders. Not an editor. This repo also has editor/, an example app that is not in the npm package.

[Demo] Editor live demo: audiocrate.homecrate.app

[Demo] Three.js implementation via XR Publisher Plugin demo: audiocrate.homecrate.app/xr-publisher

homecrate for iOS/macOS/visionOS: homecrate.app

npm install audiocrate

No bundler plugin. No peer dependencies.


What it is

Web Audio is a graph of platform nodes (BiquadFilterNode, GainNode, and the rest). Custom DSP means writing an AudioWorkletProcessor, then a parameter system, a message protocol, and an offline path around it.

Libraries on top of that often ship an application: their sounds, their timeline, their track model.

Audiocrate is the layer underneath that.

Shape

three.js Audiocrate
Scene AudioScene
Mesh (geometry + material) Clip (buffer + AudioMaterial)
Material / ShaderMaterial AudioMaterial / one with an ASL graph
WebGLRenderer WebAudioRenderer

The mapping is structural, not a port. Object3D is a transform hierarchy; Track and Bus are mixer nodes (volume, pan, mute, inserts). Positions live on scene.spatial. You do not call renderer.render(scene): live playback is on the scene, and OfflineRenderer takes an AudioMaterial graph.

Audio also needs a transport with one scheduling origin and a tempo map. The audio thread is an isolated context with no imports and a hard deadline, which is why kernels and measurement work the way they do.

AudioMaterials

import { AudioMaterial, param, filter, osc, env } from 'audiocrate';

const bell = new AudioMaterial({
  name: 'Bell',
  params: {
    pitch: param.range(50, 2000, { default: 440, unit: 'Hz' }),
    cutoff: param.range(200, 12000, { default: 3000, unit: 'Hz' }),
  },
  graph: ({ params }) =>
    filter.lowpass(
      osc({ freq: params.pitch, type: 'sine' })
        .mul(env.adsr({ attack: 0.005, decay: 0.4, sustain: 0, release: 0.1 }).trigger(1)),
      { cutoff: params.cutoff },
    ),
});

A parameter schema and a signal graph, both plain data. From that:

  • Real-time playback, compiled into one AudioWorklet per voice, not one AudioNode per operation.
  • Offline rendering through the same interpreter.
  • An inspector model. describeAudioMaterial(bell) returns the faders, ranges, and units. Audiocrate does not draw them.
  • Serialization. The graph is JSON.

A new AudioMaterial does not require a change inside this library.

Rendering

import { OfflineRenderer } from 'audiocrate';

const { samples, sampleRate } = OfflineRenderer.render(bell.graph, {
  duration: 0.5,
  sampleRate: 48000,
  params: { pitch: 440, cutoff: 3000 },
});
import { WebAudioRenderer } from 'audiocrate';

const ctx = new AudioContext();
const renderer = new WebAudioRenderer(ctx);
const voice = await renderer.createVoice(bell.graph);
voice.node.connect(ctx.destination);
voice.noteOn({ pitch: 440 });

Same graph. Same interpreter. The offline path is not a second implementation.

Scenes

import { AudioScene, Track, Clip, Time } from 'audiocrate';

const scene = new AudioScene();
await scene.start();

const guitar = scene.addTrack(new Track({ name: 'Guitar' }));
guitar.pan = 0.3;
guitar.materials.add(bell);
guitar.addClip(new Clip({ buffer }), { at: Time.bars(2, 1, 0) });

scene.transport.play();

Time is structured (Time.bars(2, 1, 0)). One scheduling origin, a tempo map, and a transport that offline and live rendering share.

Same graph, more than one place

A second interpreter exists in Swift (examples/CrateASL). A conformance gate renders 83 graphs covering all 70 node kinds through both, comparing sample by sample to a tolerance of 5e-6. Adding a node kind to one implementation and not the other fails the build on both sides.

Size

Minified, bundled by esbuild from the published tarball. Run node scripts/measure-size.mjs to reproduce. The imports behind each row are in that script.

What you import Bundled
audiocrate/theory alone 1.1 KB
Scene graph plus offline rendering 73 KB
Everything including real-time audio 181 KB

The AudioWorklet is 101 KB of that last row. A worklet realm cannot import, so it carries the whole interpreter as a string and minifying does not shrink it. Apps that never play real-time audio tree-shake it away. That is the difference between the second row and the third.

The worklet and your bundler

An AudioWorkletProcessor runs with no imports and no network. It has to arrive as one self-contained script fetched from a URL, and bundlers do not agree on how to produce one.

Audiocrate ships the worklet as a string and mints URLs at runtime, trying blob: then two flavours of data: in order. Engines disagree about which schemes a worklet may load. There is nothing to configure and no plugin to install.

To serve it as a cacheable asset, import audiocrate/worklet and pass workletUrl to WebAudioRenderer.

Also included

Theory Notes, scales, chords, key detection, pitch tracking. Standalone, no audio engine.
Spatial Positional sources and listener maths.
Collaboration Shared session state over a transport you supply. Audiocrate does not open a socket.
Hooks Named extension points for a host application.
Testing Offline render-diff harness and wav encode/decode.

Documentation

docs/ is the reading order. Hosted copy: https://homecrate.app/docs/crate/.

Philosophy Design rules and scope
Scenes and time Scenes, tracks, clips, transport, tempo maps
AudioMaterials Parameters, graphs, plugins, assets
The Audio Shader Language Nodes, graphs, live inputs, channels, measurement taps
Kernels Block-shaped DSP, portable wasm modules
Component library Shipped AudioMaterials and nodes
Theory Notes, scales, chords, keys, audio to notes
Spatial Placing sound in 3D, listener rotation, binaural decode
Collaboration Session state, discrete edits, the shared clock, and host-local devices
Hooks Extending a host without forking Audiocrate
Conformance How cross-implementation agreement is enforced
Editor Example AudioMaterial graph editor (not published)
Examples Custom-kernel plugins, the XR host adapter, and the Swift interpreter

What Audiocrate is not

  • Not a DAW. The published package has no UI. editor/ lives in this repository and is not shipped on npm.
  • Not a plugin format. It hosts DSP. It does not replace AU, VST, or similar standards.
  • Not a cloud service. Nothing here requires a server. The collaboration layer takes a transport you supply.
  • Not a sample library. Core ships primitives: filters, envelopes, delays, the usual building blocks. Custom kernels that need a worklet entry live in examples/ and are not published.

Status

0.1.0, first public release. Expect breaking changes in minor versions until 1.0.

Known limits, also listed in the docs: the conformance gate renders only fixed parameters, mobile voice budgets are estimated rather than profiled, and the kernel path has one shipped implementation.

License

MIT. See LICENSE.

About

One graph, multirender, multiplayer.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages