Headless examples
crates/engine/examples holds integration checks that run a real Session with the ALSA
null output device (silent, no clock, so it runs as fast as it can). The *_check examples
print one PASS <what> or FAIL <what> line per check, and all of them except tempo_check
exit with status 1 when a check fails; for tempo_check, look for FAIL in the output
(... | grep -c FAIL). undo_check and import_check print what they did for you to read
instead.
Most take an audio file and a scratch directory, which must be an absolute path and is used as the project folder (never point it at your real projects):
cargo run -q -p daw-engine --example stack_check -- /path/to/tone.wav /tmp/marbble-scratchscripts/headless-checks.sh builds the examples once and runs every check that needs no
third-party plug-ins (its CHECKS list; register a new check there), each in its own folder
under a scratch directory, with a test tone from the test_tone example. A check that runs
longer than HEADLESS_CHECK_TIMEOUT seconds (600 by default) fails instead of stalling. It’s what CI runs, so run it the same way before sending an engine
change. It also runs on macOS: where there is no ALSA, the engine runs on a silent stand-in
device at the configured rate and buffer size instead of ALSA’s null output:
scripts/headless-checks.sh # all of them, in a temporary folderscripts/headless-checks.sh takes_check fx_check # just thesescripts/headless-checks.sh --scratch /tmp/marbble-checks # keep the projects and logs thereIt runs the checks as on a fresh CI runner: HOME and the XDG_* folders point into the
scratch folder, and the PipeWire, PulseAudio, JACK and D-Bus session variables are cleared,
so the checks find no audio server, no settings and no plug-in cache, and never touch yours.
That makes the plug-in parts of fx_check, automation_check and bounce_check print SKIP;
pdc_check, freeze_check and onoff_vst_check need real plug-ins, and undo_check and import_check only
print what they did for you to read, so those four aren’t in the script; run them (and the
plug-in parts) by hand. multiout_check and vst_undo_check also want real plug-ins but find them by themselves, so they’re
in the script, running where the plug-ins are installed and printing SKIP elsewhere. Every other *_check in the table below is. A check counts as failed when it exits non-zero or prints a line
starting with FAIL; each one’s output is in <scratch>/<check>/output.log, and the scratch
folder is kept when anything failed.
| Example | Arguments | What it checks |
|---|---|---|
fx_check |
audio file, scratch dir | Every built-in effect in the registry: it loads and processes, parameters set, save, reload, undo and duplicate, automation drives it, latency reaches PDC, side-chains key it (live and in isolated renders), meters reach the tick, freezing bakes it in; plug-in and Channel EQ copy and paste, presets. The VST3 paste checks need ZaMaximX2, and print SKIP without it. |
fx_play_check |
audio file, scratch dir | The modulation, distortion and Gain effects: each plays live, renders sanely, and a bounce of its frozen track matches the live bounce; a synced Tremolo locks to the song position; the Distortion’s oversampling latency is compensated (at 0 % mix it is the dry signal, in time); Gain’s invert and mono switches null a centred source. |
insert_order_check |
audio file, scratch dir | Reordering Audio FX inserts: the engine runs the new order, instances keep running (same ids, nothing reloaded), undo and redo restore it, moving or copying to another strip makes room there. |
stack_check |
audio file, scratch dir | Track stacks (folder and summing), groups and region edits, by the levels the meters report. |
vca_check |
audio file, scratch dir | VCA faders and pre-fader side-chain keys in offline renders: two tracks on a VCA at -6 dB render 6 dB lower, muting the VCA silences them, undo restores the level, automated member volume combines with the VCA, a frozen track and Bounce follow it; a compressor keyed pre-fader from a track with its fader at -∞ still compresses. |
loop_check |
audio file, scratch dir | Looping regions (playback, trim, move, convert, split, join) and hidden tracks. |
sends_check |
audio file, scratch dir | Sends pre and post fader, aux buses, output routing, solo through routing, loop refusal. |
send_auto_check |
audio file, scratch dir | A send-level automation lane changes what reaches the aux. |
bounce_check |
audio file, scratch dir | Bounce, Export All Tracks, Export Region and Bounce in Place: ranges, tails, formats, normalize, placement. |
strip_check |
audio file, scratch dir | Channel strip settings: a saved setting loads onto two tracks (new instances, same values, the EQ) as one undo step; the gain-reduction meter of compressed strips reaches the tick; a clipping track’s clip indicator holds a peak over 0 dBFS through quiet playback until it (or every one) is reset, and a bounce leaves them alone. Saved settings go to the scratch folder. |
export_check |
audio file, scratch dir | AIFF and FLAC bounces decode to the same samples as the WAV bounce (and within quantization of a float one); FLAC refuses float; Export Selected Tracks writes just the selection, folder stacks standing for their subtracks. |
dragmode_check |
audio file, scratch dir | The No Overlap, Shuffle L and Shuffle R drag modes and Move Automation with Regions through the session calls the Tracks area makes: where regions end up, that moved automation is what plays, and that each edit is one undo step. |
crossfade_check |
audio file, scratch dir | Fade curves, Speed Up and Slow Down, and X, EqP and X S crossfades, measured in offline renders of synthetic files (X sums to unity, EqP to constant power); butt fades and gaps; the X-Fade drag mode with undo and redo; and that playback, Bounce, Export, Bounce in Place, Freeze, Join and save/reopen all sound the same. |
range_edit_check |
audio file, scratch dir | Audio Track Editor range edits and Split at Transients: what plays after each edit, and that one undo reverts it. |
automation_check |
audio file, scratch dir, optional plug-in name and parameter name | Reading volume and pan lanes, Off mode, writing in Write and Touch, undo; with a plug-in from the cache, one of its parameters too (SKIP when it isn’t in the cache). |
autolanes_check |
audio file, scratch dir | Automation curves, Stereo Out automation and stack automation in offline renders: a curved volume ramp follows its curve sample by sample, a straight one is unchanged by curves existing, Stereo Out’s volume lane shapes the bounce, summing and folder stacks’ lanes shape their subtracks, and each edit undoes. |
pdc_check |
audio file, scratch dir | Plug-in delay compensation with a real latent plug-in, measured by cross-correlating offline renders. Needs ZaMaximX2 (zam-plugins) in the plug-in cache. |
onoff_vst_check |
audio file, scratch dir | Track On/Off with real VST3s (ZaMaximX2, TONE3000), live: suspended plug-ins play again, fast switching, delay compensation. |
freeze_check |
audio file, scratch dir | Freeze: a frozen instrument track with a latent plug-in sounds as it did live, plug-ins are unloaded, edits are refused, undo, redo, unfreeze and reopening restore the right state. Needs ZaMaximX2. |
takes_check |
audio file, scratch dir | Take folders, comping, cycle and Autopunch recording layout (the null device has no input, so takes are silent), and markers. |
comping_check |
scratch dir | Several comps per folder (create, switch, rename, delete, with undo; each renders as it should); a MIDI cycle recording with “Create take folders” makes a take per pass, and choosing a take changes what plays; a take folder crossfaded with the next region, and a loop with Crossfade Loop Repetitions, render without a click; Crossfade Time sets the X-Fade mode’s crossfades and the comp boundaries. |
recovery_check |
audio file (unused), scratch dir | Crash recovery: a child process records (24-bit plain with two tracks, then 32-bit cycle) and is killed with SIGKILL mid-take; reopening offers and restores the takes at the right position and length (a take folder for the cycle), repairs the WAVs and clears the journal. A normal stop autosaves at once without touching project.json. |
countin_check |
scratch dir | Recording with a count-in from bar 1, where the transport runs at negative positions, three times without an engine error. |
tempo_check |
scratch dir | The tempo map and signature changes: the click across changes, plug-in transport positions, bar numbering, Move MIDI Regions with Tempo, quantize, a MIDI file round trip, undo, and that a project without changes keeps the old timing exactly. |
midi_check |
scratch dir | Piano Roll edits, muted notes, controller playback and chasing, split/join/trim with controllers, MIDI file export and import. |
controller_check |
audio file, scratch dir | Controller assignments with messages fed through the hardware input path (inject_midi_from): learning the last touched parameter, volume on the fader taper, a built-in effect parameter, Pickup, ranges, buttons (Mute, Cycle, Play), automation written in Touch, assigned messages kept from the instrument, a missing target, saving and loading, delete and cancel. |
chase_check |
scratch dir | Chase through the built-in synth: playback started inside a long note sounds at once, and so do a jump into one and a bounce that starts inside one, but not with Chase: Notes off; a controller before the playhead is in effect only with Chase: Controllers on. |
midi_replace_check |
scratch dir | MIDI recorded (through the Musical Typing path) over a region: Replace in a cycle clears the cycle’s notes and controllers and restores the controller value after it, undoes in one step; Merge keeps the old notes; the control bar’s Replace button overrides “Create a new region”; Replace without a cycle clears from the record start to the stop. |
region_params_check |
audio file, scratch dir | Region playback parameters in offline renders of the built-in synth and the tone: quantize, transpose, velocity, dynamics and gate play exactly the notes worked out by hand; delays in ms and ticks (across a tempo change, and before a region’s start); quantize after a tempo change; an audio region’s delay; Apply Region Parameters plays the same and undoes in one step; coalesced undo; saving. |
projects_check |
audio file, scratch dir | Track alternatives (new, duplicate, switch, rename, undo and redo, what plays, save and reopen); backups kept on save and pruned, Revert to and undoing it; project alternatives (new, switch saving first, reopen, rename, delete, backups per alternative); a user template round trip. |
arrangement_check |
scratch dir | The Arrangement track: three sections with regions on two tracks (one across two section edges) and volume automation. Moving section 3 before section 2, copying section 1 and deleting section 2 with its content cut and move the regions and automation as expected; offline renders play the old audio in the new order (sample for sample without automation, within 0.5 dB with it); one undo restores each. |
arrange_cmds_check |
scratch dir | The arranging commands with the locators at 1–1.5 s over regions and volume automation on two tracks: Insert Silence, Cut Section and Repeat Section move the content in time and the render plays the old audio with a gap, without the section or with it twice; Insert Cut Section after a cut restores the project; Remove Gaps, Remove Overlaps and Paste Replace lay regions out as Logic does; Strip Silence splits a tone with gaps into one region per note, playing as before; one undo reverts each. |
pattern_check |
scratch dir | Pattern regions (Step Sequencer) in offline renders of the built-in synth: straight, swung, polymetric and other-rate rows, ties, a looped region and a tempo change play exactly the notes worked out by hand; a painted run of steps undoes in one step; Convert to MIDI Region and back render identically; saving. |
editing_check |
audio file, scratch dir | Snap Edits to Zero Crossings (a split off and on lands within 5 ms on a near-zero sample; a range split at both ends), a region gain drag sent as the Tracks area does (heard at -12 dB, one undo step), and a nudge in bars and beats across a 120 to 90 BPM change. |
pdc_glide_check |
scratch dir | Delay compensation changes while playing: a sine plays on one track while the Overdrive (38 frames), then the Linear Phase EQ (2047), are inserted on another and then removed, mid-render with the transport running (Session::render_with_edit), and a pre-fader send into an aux changes delay the same way. No step may exceed the sine’s own (the output reports what an instant jump would have stepped), and after the 15 ms glide the output is the sine exactly as far moved as the delay. |
instrument_check |
scratch dir | A built-in instrument (Marbble Tre) through the real session: loading, a MIDI region’s render, two renders bit-identical, parameter edits, automation, undo and redo, factory and user presets, save and reopen, duplicates having differences of their own, Freeze with its tail, Bounce in Place, a bounce’s tail, Track Off, copy and paste, strip settings, MIDI learn, chase and live MIDI, a held key when the target moves or it’s bypassed. That Marbble Synth still loads and plays. |
linphase_check |
scratch dir | The Linear Phase EQ: flat, at each Quality, it reports its latency and plays the dry signal in time; with the same settings as a Channel EQ its magnitude at eleven frequencies matches within 0.25 dB, and each sine burst comes out as the dry one scaled (no phase shift), unlike the Channel EQ’s. |
restore_check |
scratch dir | Every feature in one project (examples/common/all_features.rs): each step (new effects, Linear Phase EQ and its latency, a pre-fader key, VCAs, folder, curved and Stereo Out automation, pattern regions, region parameters, take folders and comps, a MIDI take folder, loop crossfades, an arrangement move, marquee delete, Strip Silence, a zero-crossing split, track alternatives, a section cut) undone and redone with bit-identical renders and documents; all undone and all redone; save and reopen identical; an older project (the new fields stripped) opens with their defaults and renders the same. |
soak_check |
scratch dir, optional edit count, seed and thread count (4; 1 for the callback alone) | Feature interactions: the all-features project, then a few hundred edits drawn at random (fixed seed) from the session’s public edit methods, all with the null device’s callback playing every block through the worker pool. After each edit the task graph must fit the engine’s routes, and every 40 a render on the pool must match the serial loop’s bit for bit. After each the project must hold together (unique ids, positive lengths, comps in order, arrangement tiling, VCAs and keys that exist, delay compensation that matches the plug-ins); some are undone and redone with renders; every 50 the project saves and reopens with the same render; at the end undoing everything gives the first render back bit for bit. |
onoff_check |
audio file, scratch dir | Track On/Off in offline renders: an Off track leaves the mix (and comes back bit for bit), with Mute independent; a folder and a summing stack take their subtracks; an Off aux passes nothing while its senders play on; a side-chain key from an Off track is silence; a latent track leaves delay compensation, also mid-render with no click; a frozen track plays nothing; Freeze and Bounce in Place refuse and exports skip; saved and reopened; live, an Off track’s meter falls to nothing. |
vst_sidechain_check |
audio file, scratch dir | Side-chain inputs of real VST3s, skipped when LSP’s plug-ins aren’t installed: bus layouts printed; Sidechain Compressor and Gate rendered with a loud key, a silent key and no source; a mono aux’s mix-down; no click and unchanged settings around the setActive cycle; switching while playing, off and bypassed; Off sources, loops, undo, a deleted source, freeze, Bounce in Place, duplicating, channel strip settings, Stereo Out, delay compensation, reopening, a missing plug-in and a project from before side-chains. |
multiout_check |
scratch dir | Multi-output instruments with a real plug-in, LSP’s Multi-Sampler x12 DirectOut with sines loaded through its state; prints SKIP without it (it looks in the standard VST3 folders, so it needs no cache). A note reaches the aux on its direct output, sample for sample as the main output, and not the other aux; the aux’s fader, mute and solo; main and aux add up in phase with latency on either side and with a later feeder; Track Off, bypass, Bounce, Export; Freeze and Bounce in Place refused; deleting and undoing both ways; a one-output instrument in the slot; pasted strip settings; duplicates; reopening with the plug-in and without; live on its own thread, buses switched on and off while playing with no click, the other tracks unbroken and no allocation on the engine’s thread. |
vst_undo_check |
audio file, scratch dir | Undo for edits in a VST3’s own editor with LSP’s Compressor, ZamCompX2 and TONE3000, each SKIP when it isn’t installed. No editor can open, so it calls IComponentHandler as one would (drive_vst_handler), and reads back the parameter and the saved state and measures renders and live meters. One gesture is one step with no getState during it; bursts without begin and end; a preset load’s restartComponent; echoes, host-set values, automation playing, read-only and self-moving parameters make none; Touch’s pass keeps the move; Off, bypassed, frozen and removed plug-ins; undo and redo fifty times while playing. Prints what getState costs. |
parallel_check |
scratch dir | Multi-core processing: a project with everything the engine has (audio and instrument tracks, summing stacks, auxes, sends before and after the fader, side-chain keys in built-in effects and VST3s, a multi-output aux, delay compensation, Off, frozen and muted tracks, automation, VCAs, solo, the metronome, input monitoring, Stereo Out’s inserts) plays through renders and through Engine::process, with edits at fixed blocks, on 2 to 8 threads at four buffer sizes, and must match the serial loop bit for bit. VST3s are LSP’s, skipped where not installed; TONE3000 isn’t used, since it depends on timing. Real-time workers, where the system grants them, must live through blocks back to back. Works on any core count. |
rt_alloc_check |
scratch dir | Real-time safety: plays the all-features project through Engine::process on its own thread with a counting allocator, then again with every block on four threads, counting the workers as well, and checks that no block allocates or frees once warmed up, while playing, with a real VST3 keyed by another track where LSP’s plug-ins are installed, through a latency change and its glide, edits, plug-ins and tracks coming and going, undo and redo, locating, cycling and stopping. |
undo_check |
audio file | Undo and redo through a sequence of edits. |
import_check |
project dir, files… | Imports files onto new tracks and reports what was created. |
test_tone |
output .wav path |
Not a check: writes the test tone the checks play (3 s of 440 Hz at -21.5 dBFS, stereo, 32-bit float, 48 kHz). |
demo_project |
output .marbble path |
Writes a small demo project (synthesized drums, a plucked part, a MIDI region) for trying the UI. |
showcase_project |
output .marbble path |
Writes the 24-bar project the website’s screenshots show: synthesized drums, shaker and guitar, all six synth models and Marbble Sampler, inserts, two aux returns with sends, a VCA, arrangement markers, an automation lane and an audio take folder with a swiped comp. Built-in plug-ins only. |
sampler_check |
scratch dir | Marbble Sampler through the real session: files at several rates and depths mapped by their names or chromatically, renders in tune and repeating, velocity layers, edits with undo and redo, old maps freed, recording from a simulated input (through feed_input_for_checks, as a device’s callback would call the engine: this shows the path from the engine’s input to a playable zone, not a real interface), trimming, Convert to Sampler Track whole and sliced, save and reopen, originals deleted, the project moved, Save As, a missing sample, reusable instruments, Freeze, Track Off, copying, an SFZ it generates. Sets its own HOME inside the scratch folder. |
sampler_bench |
scratch dir, optional seconds per measurement (default 3) | Not a check: times Marbble Sampler per block at full polyphony, at 512 and 128 frames, and how long loads take. Run it with --release. |
instrument_bench |
optional seconds per measurement (default 4) | Not a check: times the built-in instruments per block, at 512 and 128 frames, with one voice and many. Run it with --release. |
fx_bench |
optional block size (default 512) | Not a check: times every built-in effect per block at 48 kHz (worst and average) against the block’s real-time budget. Run it with --release. |
hosts |
none | Lists every audio host and its devices. |
input_level |
[--reset] [device-id] |
Prints (or resets to 100%) the system mixer volume of a recording device (Linux). |
smoke |
none | Lists devices, records two seconds from the default input through a plug-in and reports the region. Uses real devices. |
crates/vst3-host/examples:
| Example | Arguments | What it does |
|---|---|---|
probe |
optional name filter | Scans installed VST3 plug-ins, instantiates each and runs a few blocks through it. |
editor |
name substring, seconds | Opens one plug-in’s editor with a small poll() run loop (no GTK), for debugging editor embedding. |
ThreadSanitizer
Section titled “ThreadSanitizer”The worker pool’s tests and checks can run under ThreadSanitizer. It takes a toolchain with its runtime
and the standard library’s source (a rustup stable has both, RUSTC_BOOTSTRAP=1 allows the unstable
flags), since an uninstrumented standard library gives false alarms:
RUSTC_BOOTSTRAP=1 RUSTFLAGS='-Zsanitizer=thread -Cunsafe-allow-abi-mismatch=sanitizer' \ cargo -Zbuild-std test --target x86_64-unknown-linux-gnu -p daw-engine --lib dsp:: -- --test-threads=1The same flags build parallel_check and soak_check. LSP’s plug-ins aren’t instrumented, and report
races inside their own loading and teardown, on no worker thread. Miri can’t run this: it isn’t in the
flake, and real plug-ins and threads at real-time pace aren’t its kind of work.
When writing a new headless check, copy the pattern in stack_check.rs: a Session on ALSA
alsa:null output, s.dir pointed at the scratch folder, the cycle turned on so playback
loops, and about 60 ms of sleep after stop() before the next play().