Skip to content

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):

Terminal window
cargo run -q -p daw-engine --example stack_check -- /path/to/tone.wav /tmp/marbble-scratch

scripts/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:

Terminal window
scripts/headless-checks.sh # all of them, in a temporary folder
scripts/headless-checks.sh takes_check fx_check # just these
scripts/headless-checks.sh --scratch /tmp/marbble-checks # keep the projects and logs there

It 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.

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:

Terminal window
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=1

The 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().