// ZPWR-DAW — DOCS

Report GitHub

>_ZPWR-DAW

A full DAW arranger engine, extracted as a standalone library (formerly zpwr-clip-engine). One generalized canvas grid — one renderer, one interaction model, one value model — bound to a domain (notes / arranger / automation / triggers); a pure C++ ClipEngine with a swung audio-thread step clock; a C ABI so Rust hosts drive the same engine the JUCE plugins drive; and byte-identical C++/JS Type-0 MIDI export.

Overview

zpwr-daw started as a per-layer piano-roll pattern sequencer and grew into a two-view DAW: an Arrangement timeline (tracks, clips, sections, tempo/meter maps, markers, breakpoint automation) and a Session clip launcher (scenes, follow actions), plus MIDI/JSON export and an in-progress JUCE audio-clip layer.

It is a reusable component with two halves: a host-agnostic web UI (the FL-Studio-style canvas grid) and a C++ engine (pattern model, transport, step clock glue) reached directly from C++ and over a C ABI (FFI) from Rust. The frontend never knows whether it runs inside JUCE or Tauri — it calls an injected nf transport object. Playback runs on a native audio-thread step clock when the host wires it (minimise-proof) and falls back to a JS timer otherwise.

  • One grid engine — one canvas renderer + one interaction model + a value model, bound to a domain; zero duplicated gesture code.
  • N domains — notes (piano-roll), arranger (DAW arrangement), automation (ALS section-overrides), triggers (ztranslator).
  • Two transport bridges — JUCE WebBrowserComponent native fns and Tauri invoke, behind one nf interface.
  • Pure C++ ClipEngine — JUCE-free pattern model + swung step clock + event queue.
  • C ABI + Rust bindingsextern "C" over ClipEngine; Rust hosts drive the same engine the plugins do.
  • MIDI export — byte-identical C++ and JS Type-0 Standard MIDI File writers.
  • Also a full video editor — a video track type on the same timeline, with the whole Adobe Premiere Pro feature surface ported (0 gap). See below.
  • Fully modular signal path — every track auto-owns a stereo patch graph, master/aux/global-mod buses are graphs too, and every param is a mod-matrix target (INVENTIONS.md #65). See below.
  • Note-stream generative MIDI — Euclidean / Arpeggiator / ChordEngine / Scale + cellular-automata note generators as ModuleInfoT<NoteStream> nodes on the same graph core. See below.
  • Ships three ways — standalone app, VST3 plugin, and embedded in an arbitrary GUI host off the same clip/automation timeline (INVENTIONS.md #64). See below.

Video Editor (Premiere coverage)

Because the arranger timeline already moves notes and audio along one shared clock, adding a video clip kind and a video track type turned the DAW into a non-linear video editor on the same canvas — making zpwr-daw the first DAW that is also a full video editor. The whole Adobe Premiere Pro feature surface was then built out against it, area by area, until 0 features remain gap.

  • Edit — video tracks, blade/razor, ripple/insert/overwrite, slip/roll/slide/rate-stretch trims, 3-/4-point editing from a source monitor, JKL shuttle, SMPTE drop-frame timecode, markers, sync/track lock, A/V link, multicam.
  • Preview — a program monitor that seeks the playhead frame each tick and pops out into a draggable player; filmstrip thumbnails, safe-margin guides, master + per-track VU meters, playback-resolution frame-skip.
  • Composite & grade — Motion/Opacity/Crop/Flip with keyframes + easing, blend modes, ellipse/rect/pen masks (keyframable), luma/track-matte/chroma keying, adjustment layers, a Lumetri-style color stack.
  • Finish — titles & Essential Graphics, transitions, optical-flow time interpolation, and an FCP-XML / Media Encoder export path.

The video editor lives in the embedded clip engine (libs/zpwr-clip-engine/webui). Every feature is mapped to its exact file:line in the Adobe Premiere Pro Port Report; the 2 partial rows are shipped-with-a-limitation (native sample-accurate audio DSP, Productions multi-user sync), not missing.

Ships Three Ways (Embeddable Arranger)

One codebase, three distributions: a standalone app, a VST3 plugin, and embedded inside any GUI app — audio or not (traderview → trades, ztranslator → translations, Audio-Haxor → stryke on clips). This is the embeddable-arranger claim (INVENTIONS.md #64): a complete two-view arranger (Arrangement + Session, clips, breakpoint automation, tempo/meter maps) that runs standalone, as a VST3 inside another DAW, and mounts in an arbitrary host off the same clip/automation timeline.

Closest prior art isn't a clean match: NI Maschine is a groovebox tied to their hardware workflow (by NI's own words, "never a full DAW"); Komplete Kontrol is a plugin host, not a DAW; Tracktion Engine is a compile-time library, not a loadable plugin. A general-purpose full DAW arranger that runs as a plugin and embeds in arbitrary GUI apps — including non-audio ones — has no clean dup found.

Honest caveats from the ledger: "None found" is owned as a claim, not proven; and the non-audio embeds (traderview → trades, ztranslator → translations) are aspirational design intent — those app repos don't yet mount the clip engine.

Generalized Grid Engine

The grid engine lives under webui/grid/: one renderer + one interaction model + one value model, bound to a domain. The domain supplies everything that differs (lanes, time-axis cells, value type, which gestures apply, serialize shape); the engine supplies everything shared (render loop, hit-test, all gestures, popover bulk-edit, persistence).

The merge detail that matters: value.type selects the edit-drag axis — 'length' → horizontal right-edge note resize (notes), 'unit' → vertical top-band value height (automation), 'bool' → click toggle (triggers).

import { createGrid } from "./grid/index.js";
import { createNotesDomain } from "./grid/domains/notes.js";
import { createJuceBridge } from "./grid/transport/juce-bridge.js";

const { nf } = createJuceBridge({ prefix: uiPrefix });   // or createTauriBridge()
const grid = createGrid({
    canvas: document.getElementById("clip-grid"),
    domain: createNotesDomain({ getSteps, getPerBeat, layer: 0 }),
    store: localStorage,
    storageKey: "zfx_clip_notes_L0",
    onChange: (model) => nf("clipSeqPattern")(JSON.stringify(model.serialize())),
});
grid.setPlayhead(currentStep);   // drive the playhead column from the transport

Grid Domains

A domain supplies lanes(), cells() over the time axis, a value spec (type/min/max/step), capabilities (which gestures apply), and serialize/deserialize. The bindings under webui/grid/domains/:

DomainLanes × cellsValue → serialize shape
notes.jspitch lanes × step cellsnote length → the CLIP piano-roll, [{s,l,n,len,v}] ClipSeq shape
arranger.jstracks × barscells = clip ids → the DAW arrangement; double-click a clip drills into its notes
automation.jsmacro lanes × 8-bar blocksvalue 0..1 → the ALS section-overrides timeline, {param:{bar:value}} shape, resizable sections
triggers.jsaction lanes × time slotsvalue = bool → ztranslator (provisional, pending the ztranslator contract)
autolanes.jsMPE/CC lanesper-block automation (Pitch Bend / CC74 / Channel Pressure as their real MIDI messages)
launcher.jsscene gridSession clip launcher (scenes, follow actions)

Transport Bridges

The frontend isolates host calls behind nf(name)(...args). Two adapters under webui/grid/transport/ produce an nf, so the frontend never knows which host it runs in:

  • juce-bridge.jsnf resolves to Juce.getNativeFunction(prefix + name), i.e. the existing clipSeq* native fns from registerClipSeqFns. No backend change for the JUCE plugins.
  • tauri-bridge.jsnf resolves to a wrapper over Tauri invoke('clip_seq_' + name, {...}). The Rust command handler forwards to the C ABI.

Pattern/transport JSON is identical across both bridges (defined in ClipSeq.h). Playhead readback: JUCE polls clipSeqStep on rAF; Tauri polls invoke('clip_seq_step') or listens for a Rust-emitted event.

Slot Sequencer

webui/grid/sequencer.js is a JS step clock that walks the time axis and fires every set cell per slot (swing-aware). It drives the triggers domain and is the JS-fallback clock for any host without a native engine. Native sequencer glue lives in include/zpc/ClipSeq.h: the clipSeq* host contract (ClipSeqHooks) plus a templated helper that registers the matching native functions on a JUCE WebBrowserComponent options builder.

#include <zpc/ClipSeq.h>

zpc::ClipSeqHooks clip;
clip.pattern   = [this] (const juce::String& json) { /* parse + stage pattern */ };
clip.transport = [this] (int steps, double bpm, double perBeat, float swing,
                         int swingUnit, bool loop, bool perLayer, int target) { /* … */ };
clip.play      = [this] (bool on) { /* start / stop the processBlock step clock */ };
clip.step      = [this] { return currentStep; };

rootObject->setProperty ("hasClipSeq", clip.wired());   // advertise availability to the page
options = zpc::registerClipSeqFns (std::move (options), clip, pfx);

C++ Engine (ClipEngine)

include/zpc/ClipEngine.h is the pure C++ engine: a JUCE-free pattern model + swung step clock + event queue. Timing/length semantics are ported verbatim from the clip.js fallback sequencer, so the C++, the C ABI, and the JS clock all advance identically. It holds the pattern + transport, advances a step clock, exposes currentStep(), and queues per-step note on/off events — no WebBrowserComponent dependency.

The note-stream module pack (include/zpc/midi/, src/midi/) — Chord / Arp / Scale / Euclidean / Game-of-Life / Brian-Brain / Langton plus the chord & scale engines — are ModuleInfoT<NoteStream> modules templated on the signal-agnostic graph core from zpwr-patch-core (vendored under libs/zpwr-patch-core; only juce_core beyond that). Detailed below.

Arranger & Clip-Engine Internals

The transport core is zpc::ClipEngine (include/zpc/ClipEngine.h) — JUCE-free, a single header the C ABI compiles. It holds the pattern ([{s,l,n,len,v}]) and the transport, and advances a swung step clock ported verbatim from the clip.js fallback sequencer so native and JS playback match to the sample:

  • step duration = 60 / bpm / perBeat seconds (the grid note division);
  • swing delays the off-beat half of the swing timebase and shortens the return so the cycle length stays constant (stepDur(i) = the gap after step i);
  • note length is held in steps — a note at step s length L fires its note-off at the top of step s+L.

advance(seconds) accumulates dt and fires every step boundary it crosses (with a runaway-dt guard), queuing ClipEvent note-on/off records; a host without an audio callback pulls them with pollEvents(out, max) (the Tauri timer path), while a JUCE host sequences inside processBlock. renderMidi(repeats) reuses the pattern + transport to emit a Type-0 SMF. setPatternJson() is a tolerant scanner for the fixed clip shape (any key order, integer or float); a fractional .als-import start rounds to the nearest step here (the JS clock plays it exactly from the bl beat-length field).

Native host contract. zpc::ClipSeqHooks (include/zpc/ClipSeq.h) is four std::functions — pattern / transport / play / step — and registerClipSeqFns() appends the matching clipSeq* native functions onto a JUCE WebBrowserComponent options builder (templated so the header needs only juce_core). hooks.wired() drives the page's hasClipSeq; a null play means no native sequencer and the page falls back to its JS clock. The audio-thread clock is immune to the timer throttling browsers impose on minimised/hidden windows — the "minimise-proof" playback.

Audio-clip layer (include/zpc/AudioClip.h, ClipAudio.h) — the audio half: zpc::AudioClipPlayer decodes files into clips and streams them as voices inside processBlock, with per-trigger gain, linear + equal-power fades, rate repitch (linear-interp resample), pitch-preserving WSOLA time-stretch (cached per (handle, ratio)), ProTools slip, and Ableton warp markers. trigger() runs on the message thread (any WSOLA pre-render happens there); process() runs on the audio thread; the handoff is a lock-free juce::AbstractFifo, and the clip vector is grow-only so a sounding voice's shared_ptr never frees on the audio thread. The DSP math underneath is factored into JUCE-free primitives (include/zpc/AudioDsp.h): slipStartSample, equal-power crossfadeGains (cos/sin), the invertible piecewise-linear WarpMap (arrangement ↔ source time), wsolaStretch, and the CompLane take-selection model.

Honest status: those primitives are headless-verified (tests/audio_dsp_test.cpp, tests/audio_clip_render_test.cpp — real WAV decode → trigger → process with Goertzel/timing assertions), but subjective audible quality stays a GAP until the synth's processBlock is built and a human hears it. Take recording (vs selection) and warp capture need a live audio input and stay host-side gaps.

Note-Stream Generative MIDI Engine

The generative side (include/zpc/midi/) is a second zpwr-patch-core host whose cable signal is a note-event stream, not audio. NoteStream = std::vector<NoteEvent> (sorted by sample offset) is the signal type; every generator/transform is a zpc::ModuleInfoT<NoteStream> node, so the same graph core that runs the audio FX runs the MIDI FX — dynamic blocks, summed cables, the mod matrix, topo eval, lock-free edits, JSON, and the shared WebEditor come for free (MidiModules.h). registerMidiModules(reg, dict) installs the 23 module bodies (ported verbatim from the former hand-rolled MidiPatchGraph); defaultMidiPatch() wires the showcase Chord → Arp patch; convertLegacyMidiPatch() upgrades the pre-zpc JSON schema.

NoteEvent carries On / Off / CC / Bend / Pressure plus a detune field (microtuning in semitones → per-channel pitch-bend on output), so modules process CC, pitch-bend and channel-pressure in-stream, not just note on/off (MidiTypes.h). MidiHostCtx gives every module the per-block Transport snapshot, a deterministic xorshift RNG seed, and the shared ChordDictionary / ChordEngine; MidiNoteState (one per node) holds each generator's private DSP state.

  • Euclidean (Euclidean.h) — Bjorklund's algorithm distributes pulses hits as evenly as possible over steps slots (with a rotate offset), the maximally-even rhythm primitive. E(3,8)x..x..x. (tresillo). Also available as the arp's per-step gate overlay.
  • Arpeggiator (Arpeggiator.h) — a stateful note-traversal engine over the held pool. Modes: Up / Down / Up-Down / Down-Up / Converge / Diverge / As-Played / Random / Chord. Each of up to kMaxSteps (16) columns is an ArpStep with its own enable, velocity, gate (>1 overlaps), transpose, ratchet (1..8 sub-hits), probability and tie. ArpParams adds musical Division (straight/dotted/triplet), 1..4 octave span + octave mode, swing, latch, the Euclidean gate overlay, and timing/velocity humanize.
  • ChordEngine (ChordEngine.h) — stateless voicing: one input note → a sorted, de-duplicated chord with inversion (0..4), sub-octave doubling (0..2), spread (0..2), transpose, per-voice strum delay (ms), an optional per-key chord map (Chromatic / Circle-of-Fifths / Lowest-Note layout), and an optional scale lock. Chord types come from ChordDictionary (triads, sixths, sevenths, extended 9/11/13, suspended, added-tone, altered/jazz, quartal/cluster — count queried at runtime, never hardcoded).
  • Scale (Scale.h) — a mode catalog (major/minor family, church modes, pentatonic/blues, whole-tone/diminished/augmented, Hungarian/Japanese/Egyptian/Spanish) plus a quantize() that snaps arbitrary notes onto the nearest in-scale pitch, driving the arp's scale-locked harmony.
  • Cellular-automata note generators — three pure std::uint64_t cores on an 8×8 toroidal grid (cell = bit y*8+x): Game of Life (GameOfLife.h, B3/S23), Brian's Brain (BrianBrain.h, ready/firing/dying), and Langton's Ant (Langton.h, turn-flip-move). A read column advances one step per clock, and set cells in that column emit notes — non-repeating evolving patterns. MidiNoteState also carries Turing shift-register, Polymeter, Counterpoint, Bassline, and Scoop/Fall pitch-bend-sweep state for the rest of the 23-module pack.

The JUCE bridge is separate (NoteGraphHost.h, host-only): midiToNoteEvents() parses a juce::MidiBuffer into the stream, emitNoteStreamToMidi() renders a stream back to MIDI (microtuning → per-channel bend), and runNoteGraphLayers() drives every non-muted/solo-respecting layer for one audio block.

cmake --build build --target MidiModulesTest
./build/MidiModulesTest_artefacts/*/MidiModulesTest

Modular Stereo Audio Engine

The modular claim (INVENTIONS.md #65): not a fixed channel-strip mixer with a modular device bolted on, but a DAW whose entire signal path is a user-patchable graph — every track auto-owns a layer, each layer is a stereo patch graph hosting oscillators / FX / plugins, and master / aux / global-mod buses are themselves patch graphs. Because all of these are the same zpwr-patch-core graph, every param is a mod-matrix target: the same block/cable/mod-route model that routes signal also routes modulation, and the synth panel + mod matrix are generated from that one patch.

Every track auto-owns a layer (its note/MIDI graph) and a stereo audio graphzpc::StereoGraph (PatchEngineT<StereoSample>), where one cable carries an L/R pair. It's a separate graph alongside the note-stream MIDI graph and the mono float graph, shared across all four products (libs/zpwr-patch-core):

  • mono FX run in stereo for freewrapMonoAsStereo runs any of the ~3.5k mono blocks once per channel with independent L/R state. registerStereoModules wraps every mono block except Oscillators (excluded) and the Plugin host (already stereo → registerStereoPluginBlock, a native stereo node, never dual-mono-wrapped).
  • per-track renderprocessBlock feeds each layer's note output to that track's instrument Plugin node, runs the stereo graph for the block, and sums it into the master with the layer's gain · pan (equal-power) · width (mid/side).
  • mod → mix — the global mod control patch (LFOs/envs/MIDI/soft-keys) and each layer's own mod routes modulate Master + per-layer Vol/Pan/Width (the GLOBAL MOD / LAYER MOD patch-panel tabs).
  • cue + DJ crossfader — a 2nd stereo Cue output for headphone/preview, plus an automatable Crossfade param: tracks pick an A/B group (LayerProps.xfade), the main mix crossfades A↔B (equal-power), and cued tracks sum to the Cue bus at full level independent of the crossfader.

Status: the engine path (graph, wrapper, stereo Plugin host, per-track render, mod, cue/crossfade) is compile/link-verified, and the in-app editor already exists — a 2nd WebEditor (namespaced trk, bound to the per-track audioEngine) drives the same modular patch panel against a track's stereo graph. Each track's whole stereo graph (instrument node + FX blocks + cables + mod routes) round-trips through save/reload (tracksToJson/restoreTracksFromJson serialize the full PatchDef per track). It still sums silence until a track's graph hosts an instrument/source, and the one remaining step is not new code but a JUCE build + a human listening to confirm audible output — autonomous ticks compile-verify the path (they can't hear it). "None found" is owned as a claim, not proven.

C ABI + Rust Bindings

The pure C++ ClipEngine is exposed to Rust over an extern "C" surface (include/zpc/capi/clip_engine.h, src/capi/clip_engine.cpp) so Audio-Haxor / ztranslator drive the same engine the JUCE plugins drive. The -sys crate compiles the JUCE-free clip_engine.cpp directly with cc — no CMake or JUCE needed for the Rust path. The safe wrapper crate sits on top.

use zpwr_clip_engine::ClipEngine;
let mut e = ClipEngine::new();
e.set_pattern(r#"[{"s":0,"l":0,"n":60,"len":2,"v":100}]"#);
e.set_transport(4, 120.0, 4.0, 0.0, 1, /*loop*/ true, /*per_layer*/ false, /*target*/ 0);
e.play(true);
e.advance(0.125);                       // one step at 120 bpm / 1/16
for ev in e.poll_events() { /* route note on/off to the synth */ }

C/C++ consumers can instead link the CMake target zpwr::clip_engine_capi (static) or build -DZPC_BUILD_CAPI_SHARED=ON for a shared library.

Arrangement & Session

Two views over the same engine:

  • Arrangement — a timeline of tracks, clips, sections, tempo/meter maps, markers and breakpoint automation. The arranger domain renders tracks × bars with cells = clip ids; double-click a clip drills into its notes.
  • Session — a clip launcher with scenes and follow actions (launcher.js).

The detailed per-feature coverage across Ableton Live, FL Studio, Logic, Cubase/Studio One, Bitwig and Pro Tools — every PORTED row cited to its file:line — is in the DAW Arranger Port Report.

MIDI / JSON Export

Any pattern renders to a Type-0 Standard MIDI File — the universal DAW interchange format — so the sequencer's output drops straight into Ableton, FL, Logic, Reaper, etc. One step = one grid cell; perBeat is steps-per-quarter (the clip.js note division); layer → MIDI channel; repeats renders the pattern back-to-back N times. The C++ (include/zpc/MidiFile.h) and JS (webui/grid/export/midi.js) writers emit byte-identical files, verified by round-trip parsing in the test suites.

// Rust / native
e.write_midi_file("clip.mid", /*repeats*/ 4)?;     // or e.render_midi(4) -> Vec<u8>

// Web (grid frontend)
downloadMidi(patternToMidi(grid.serialize(), { bpm, perBeat, steps, repeats: 4 }), "clip.mid");

// C ABI
zpc_clip_render_midi(engine, repeats, &data, &len);   // free with zpc_bytes_free

Build / Layout

Point ZPC_JUCE_DIR at a JUCE checkout. Standalone builds compile the two graph-core translation units from the submodule directly, so no separate zpwr_patch_core lib is required; when embedded in a host that already defines zpwr_patch_core, this links that target instead.

# standalone test
cmake -S . -B build -DZPC_JUCE_DIR=/path/to/JUCE
cmake --build build --target MidiModulesTest
./build/MidiModulesTest_artefacts/*/MidiModulesTest

# pure-logic grid tests (headless)
node --test webui/grid/tests/grid.test.mjs

# Rust bindings
cargo test --manifest-path bindings/rust/zpwr-clip-engine/Cargo.toml
PathRole
webui/grid/Generalized canvas grid engine (renderer + interaction model + value model)
webui/grid/domains/Domain bindings — notes, arranger, automation, triggers, autolanes, launcher
webui/grid/transport/JUCE and Tauri transport bridges (one nf interface)
webui/grid/sequencer.jsSwing-aware JS step clock; JS-fallback clock
webui/clip/Legacy DOM piano-roll (clip.js); stays live until consumers cut over
include/zpc/ClipEngine.hPure C++ engine — pattern model + swung step clock + event queue
include/zpc/ClipSeq.hNative sequencer glue — ClipSeqHooks + registerClipSeqFns
include/zpc/AudioClip.h, ClipAudio.hAudio-clip layer — AudioClipPlayer voices (WSOLA / slip / warp) + ClipAudioHooks
include/zpc/AudioDsp.hJUCE-free audio DSP primitives — slip / equal-power crossfade / WarpMap / WSOLA / comp-take
include/zpc/MidiFile.hType-0 Standard MIDI File writer (C++)
include/zpc/capi/clip_engine.hC ABI (extern "C") over ClipEngine
src/capi/clip_engine.cppC ABI implementation
include/zpc/midi/, src/midi/Note-stream module pack (Chord / Arp / Scale / Euclidean / cellular automata)
bindings/rust/zpwr-clip-engine-sys (raw decls, builds the C ABI via cc) + zpwr-clip-engine (safe wrapper)
libs/zpwr-patch-coreVendored signal-agnostic graph core submodule
© MenkeTechnologies — the DAW arranger engine of the MenkeTechnologies audio stack, shared across zpwr-synth · zpwr-midi-fx · zpwr-fx · Audio-Haxor · ztranslator.
References: Engineering Report · DAW Arranger Port Report · DAW Features Port Report · Source.