A wireframe spatial audio field: seven sound objects orbiting a listener at the origin over a polar distance grid, with four moving-head beams sweeping the space from a truss above.

Headless · Deterministic · Scriptable

Playwright exposes a real browser engine to be scripted and run headless. This exposes a real DAW.

Define a session, load instruments, sequence MIDI, mix, automate and render — from a script, a terminal, or an AI agent. The same inputs produce the same bytes, every run.

  • 221 JSON-RPC methods crates/hdaw-protocol/tests/devex.rs — census test
  • 90/112 milestones complete grep -c 'done\*\*' docs/spec/07-milestones.md
  • 19 builtin devices 7 instruments + 12 effects — hdaw-engine/src/plan.rs
  • 4 plugin standards hosted crates/hdaw-host-{vst3,clap,lv2,au}
  • 21 Rust crates ls crates/
For developers and agent builders

A render is a pure function of its inputs.

The session document is versioned canonical JSON with a published schema. Give the engine the same document, the same media and the same plugin state, and you get the same file back — not a similar one. Playing it is inside that guarantee, because the realtime path is the same engine and a test says so sample by sample.

An oscilloscope screen in the dark: a single cyan trace across an etched graticule, in a brushed aluminium bezel with an amber pilot lamp beside it.
The measurement code that proves the engine correct is the same code a caller uses to judge a take.
$ hdaw render landing.json a.wav
$ hdaw render landing.json b.wav --threads 1
$ hdaw render landing.json c.wav --threads 4

$ shasum -a 256 a.wav b.wav c.wav
c2fb3d872171448cc96abdc58d78fd8cd78b05dc1c3302194d8cfd4a03db3b07  a.wav
c2fb3d872171448cc96abdc58d78fd8cd78b05dc1c3302194d8cfd4a03db3b07  b.wav
c2fb3d872171448cc96abdc58d78fd8cd78b05dc1c3302194d8cfd4a03db3b07  c.wav

Serial, four threads, all cores. Thread count changes how long the render takes and nothing about what comes out.

clip.add_midi · notes_compact

4 notes · 8 beats

The clip the transcript above builds, exactly: keys 57, 60, 64, 67 at velocities 10492. This is the musical content of the render that came back as c2fb3d87, three times running.

one note · seven lanes

note_expression

  • pitch a whole tone, as an envelope rather than a bend on the channel
    +200 c MPE
  • pressure how hard the key is held after it is down
    14-bit MPE
  • timbre the MPE slide axis
    CC 74 MPE
  • volume per-note, not the track fader
    14-bit reported
  • pan this note, not the strip
    14-bit reported
  • vibrato depth, addressed on its own
    14-bit reported
  • expression the seventh axis
    14-bit reported
Native path
7 carried
Over MIDI 1.0 MPE
3 carried
Reported back
4 named and counted
Rendered through the builtin instrument, all seven arrive and the render's drop report comes back empty. Exported through the MPE projection, MIDI 1.0 can carry three — so the engine returns {volume, pan, vibrato, expression}, one omission counted per note-lane, rather than losing them quietly. Same document both times, hashed to ef9857cb. Lane values are exact rationals interpolated in integer arithmetic; there is no float on this path.

Determinism, with its limits stated

For a given build on a given target architecture, the same session document, media and plugin state produce byte-identical output.

Cross-machine reproducibility is a separate claim, resting on a pinned reference build: Rust 1.88.0, a committed lockfile, codegen-units = 1, and no target-cpu=native.

One edge stays open and is documented as open: sin and powf on the render path still call the system libm, which those pins do not cover.

docs/spec/04-determinism.md

221 methods, census-locked

A test asserts the registry length and the published manifest length are both exactly 221. Adding a method to the wire without updating the census fails the build, so the surface cannot drift quietly between releases.

crates/hdaw-protocol/tests/devex.rs

An agent is a first-class driver

The MCP server is a thin translation layer over the same registry: 223 tools, being the 221 wire methods plus two composite helpers. Rendering returns loudness, true peak, clipping and spectral balance, so a caller that cannot listen can still judge the take.

crates/hdaw-mcp — hdaw mcp

Four plugin standards

  • VST3 pure Rust over the MIT-relicensed SDK 3.8 interfaces native per-note
  • CLAP in-process, semantic normalised parameter addressing native per-note, or MPE
  • LV2 via lilv; ControlPort params addressed by symbol MPE projection
  • Audio Unit the C AudioComponent API through generated bindings MPE projection

In-process, or out-of-process behind a sandbox when a plugin is untrusted or prone to crashing. Parameters are addressed by stable identifier, never by bare index. Per-note expression goes native where the format has it and is projected to MPE where it does not — chosen in the document, never guessed, and refused rather than degraded.

It plays, and playing is the same engine

A test drives the audio callback at 481-frame buffers and compares every sample's bits against the offline render of the same document — inside an allocation guard, so the callback is proven allocation-free in the same breath. Multicore playback runs that same fork/join from the callback.

Xrun-free operation is not claimed. The counter is honest, and vendor plugin code runs in the daemon's address space while playing — crash isolation is given up on the realtime path, deliberately and in writing.

realtime_and_offline_session_paths_are_byte_identical

Freeze is keyed by exactly what it renders

A frozen track's capture is cached under the canonical hash of its freeze projection — and that projection is literally the document the freeze renders. Mute, pan, sends and their automation normalise out of the key because they are not in the capture, so a stale or colliding entry is not a bug that was fixed but a state that cannot be constructed. Unfreeze is a hash round-trip.

The key carries the tail window and a DSP epoch, so changing the engine's arithmetic can never serve you an old capture.

crates/hdaw-protocol/src/freeze.rs

For musicians and producers

Seven instruments in the box. Every plugin you already own.

Aurora, Borealis, Australis, Pulsar, the sampler and the two Lyras render with no vendor binary anywhere in the chain. When you want your own, it hosts them — VST3, CLAP, LV2 and Audio Units, in-process or sandboxed.

  1. 01

    Aurora

    virtual-analog polysynth

    16-voice subtractive core, self-oscillating ladder and warm filters, through-zero FM

    167 params

  2. 02

    Borealis

    wavetable synth

    two full tone generators into one filter; wavetables are baked and content-hashed

    176 params

  3. 03

    Australis

    six-operator FM

    all 32 DX algorithms, on an owned deterministic math core

    298 params

  4. 04

    Pulsar

    drum synthesis

    808 and 909 lineage, with per-step parameter locks the machine decides it can carry

    21 machines

  5. 05

    Sampler

    keyboard-mapped sampler

    key and velocity crossfades, reverse and one-shot zones, SFZ import

    81 params

  6. 06

    Lyra

    plucked string

    a delay loop tuned to the note, damped so the highs go first — physics supplies the rest

    3 params

  7. 07

    Lyra Bass

    bass instrument

    the same string model, playable range MIDI 23 to 67

    3 params

australis · operator routing

32 algorithms

Algorithm
Carriers

Select

Six operators; amber ones reach the output, cyan ones only modulate. The dashed edge is feedback, the one delayed path the engine keeps separate from the rest. Routing comes from TOPOLOGIES in australis/alg.rs, itself a transcription of Dexed's fm_core.cc pinned to a named commit — so this is the wiring the renderer uses, not a diagram redrawn from a manual.
A mixing console channel strip in raking light: brushed aluminium panels, etched scale markings, a row of amber indicator lamps and one narrow cyan segment readout.
Analogue-feeling music, digitally exact. The engine sums in a fixed order, so character is a choice rather than an accident.

Twelve builtin effects · 267 parameters

  • limiter 8
  • eq 73
  • compressor 19
  • reverb 16
  • delay 18
  • saturate 30
  • modulate 17
  • multiband_compressor 47
  • transient_shaper 7
  • imager 10
  • autofilter 15
  • distortion 7

Every one automatable, every one deterministic, and none of them a thin wrapper — the EQ alone carries 73 parameters across eight bands. A finished mix can render end to end without loading anything you did not write.

crates/hdaw-session/src/*_params.rs

compressor · transfer curve

drag or arrow-key

Threshold
Ratio
Knee
Model
Input against output in dBFS; the shaded gap is the gain reduction. All four gain computers are ported from compressor.rs:389–485, so the shape moves the way the audio would rather than the way a textbook says it should — . As set, it takes off a signal hitting 0 dBFS. A transfer curve is a steady-state view: attack, release and each model's own follower are real and are not in this picture.

Spectral balance · measured

render → analysis

-34.7 sub
-7.2 bass
-3.7 low_mid
-41.9 mid
-70.0 high_mid
-85.4 high
-96.6 air
Integrated
−10.5 LUFS
True peak
−7.33 dBTP
Clipped
0 samples
Returned by the render in the transcript above, not modelled here. A caller who cannot listen gets loudness, true peak, clipping and this balance back with the audio — which is how an agent judges a take. Almost no energy above mid, because the take is one FM synth playing four notes.

It knows music, not only MIDI

A deterministic theory layer sits between what you ask for and the notes that come out. Ask for a groove, a progression, a string section or a guitar part and you get idiomatic writing — the same writing, every time you ask.

  • 77 grooves queried from theory.grooves
  • 106 chord progressions across simple, complex and advanced
  • 21 drum machines in Pulsar with per-step parameter locks
A hardware synthesiser front panel at a shallow angle: a row of metal knobs with white index lines over etched scale arcs, two amber lamps and a cyan indicator.
  • Guitar voicings, grips, strums and articulation on a real fretboard model
  • Bass walking lines and pattern banks, written as a bass player would
  • Strings section writing with the expressive MIDI real libraries expect
  • Orchestra an 18-row sounding-pitch catalog with hard and soft ranges
  • Melody motif development, analysis and reharmonisation
  • Beats kits, patterns and songs, not just notes dropped on a grid

And you can play into it

Audio and MIDI capture through the device layer, with punch regions, open-ended takes, and loop-cycle passes stacking onto their own tracks. Monitoring cannot colour what you recorded: capture reads the input before the monitor fold exists, so the bytes are the same whether you were listening or not — by construction, not by test.

A take is stored as content, under its own SHA-256, never in a cache that something else may clear. And a take is never quietly lost — stop finishes it, closing the session refuses while one is in flight, and even an unclean daemon exit prints the frame count it is discarding.

transport.record · take.list · take.splice · take.commit

  • sha-256 take identity stored as content, not cache
  • 0 monitor bleed structural, not measured
  • 0 silent losses even on an unclean exit
For partners and industry

One engine. The sound, the light, and the clock that runs both.

The determinism that makes a mix reproducible makes a show reproducible. Lighting, SMPTE timecode, show control and 3D spatial audio are not integrations bolted to a DAW. They are methods on the same wire, in the same document.

Six moving-head beams sweeping a dark volume from an overhead truss, their pools crossing on the floor below.

LTC · 25 fps

Drag to scrub

Biphase-mark encoded, rendered as audio on a protected lane.

Twenty-four heads, one universe

Fixtures are patched by universe and address, aimed at spatial objects, and armed for live sACN or Art-Net emission. The chase below runs off the same clock as the mix — one document, one transport, one set of bytes.

dmx.patch · dmx.aim_set · dmx.arm

Universe 1 · 24 heads

Click to latch · swatch to recolour

Grand master

100%

Colour

  • dmx.aim_set

    A moving light can follow a sound

    Bind a fixture to a spatial object and the beam tracks it, with an optional proximity intensity law. The same object the renderer pans is the object the light points at. Not two systems synchronised — one object, heard and lit.

  • ltc.add_lane

    Timecode is a signal, so the engine renders it

    SMPTE linear timecode is biphase-mark encoded audio. It comes out of the same deterministic render path as the music, on a protected output lane, byte-exact and EBU-tested.

  • show.bake

    A show is a build artifact

    Bake the session into a self-contained show directory and a canonical tar with its own SHA-256. Verify it later without loading or mutating anything. The show you tested is the show that runs.

  • dmx.arm

    Real fixtures, real protocols

    GDTF profiles and MVR scenes import into a content-addressed fixture library. Universes arm for live sACN and Art-Net emission with priority and sequence counters, and MSC and OSC cues compile off the same clock.

Nineteen percent of the surface is not audio

Every method the engine speaks, grouped by what it drives. The stage namespaces are not a side project sitting next to the DAW — they interleave with it, addressed the same way, hashed into the same document, covered by the same determinism.

A lighting control desk in a darkened room: rows of faders and square buttons backlit in magenta, with a narrow cyan readout above them.

Method census — docs/schema/rpc-manifest.json

221

  • clip 38
  • beat 15
  • dmx 14
  • theory 14
  • session 12
  • transport 10
  • chain 9
  • track 9
  • plugin 7
  • tempo_clock 7
  • userpreset 7
  • vca 6
  • external 5
  • showcontrol 5
  • synth 5
  • cue 4
  • sampler 4
  • wavetable 4
  • analysis 3
  • arrange 3
  • bus 3
  • container 3
  • ltc 3
  • spatial 3
  • take 3
  • automation 2
  • marker 2
  • media 2
  • midi 2
  • render 2
  • room 2
  • show 2
  • smf 2
  • timecode 2
  • audio 1
  • eq 1
  • keysig 1
  • param 1
  • tab 1
  • tempo 1
  • timesig 1
  • Audio and musical structure
  • Light, space and show timing
  • Session, transport and plugins

90 / 112

Milestones complete, each one specified before it was built and recorded in the same document that plans the next. What is left is written down.

docs/spec/07-milestones.md

Start with the specification.

It is the source of truth, not a summary of one. Architecture, the exact scope of the determinism guarantee, and every milestone with its status are all written down in the same place the engine is planned from.

One command

hdaw render session.json mix.wav

Same document, same media, same plugin state — same file, every time you run it.