Testbed

This page lists what the repository implements and, for each component, which parts of the specification it exercises. Use it to find a worked example of a clause before writing one, and to see which clauses nothing yet exercises, listed at the end. Section numbers such as "contract 3.2" refer to host-plugin-contract.md; the other documents are named in full.

A component exercises a clause when it is the code that does what the clause says, or the plugin whose behaviour depends on it. Being covered by a test is a separate question, and the tests directory answers it.

Hosts

Five programs load Jigs, and a sixth packages them for someone else's host.

Host What it is Exercises
Jiggy (web/app/, src/) The browser host: tracks and a mixer, a plugin rack with generated panels, an arrangement of MIDI and audio clips against a looping transport, undo and redo, sessions saved as Turtle or zip, a catalogue search, an agent surface. Most of the contract, sections 1 to 12. messaging.md in both directions. latency.md sections 1, 3 and 4. project-format.md. webmcp.md. plugin-collections.md section 3. wam.md, loading a WAM as a foreign plugin.
jig, the jalv equivalent (bin/jig.js) Lists the local plugins, shows one plugin's ports, draws its generated panel, renders audio, and screenshots the panel. Rendering delegates to the reference host's chain, so the two cannot disagree. Contract 3.1 through the shared render, 5.1 and 5.3 through the generated panel, which is the same createPanel the browser host uses.
Reference host (bin/host.js, src/host/ReferenceHost.js) Loads a chain of plugins by IRI, or from a local directory, in Node, and renders it to a WAV file. No browser. A composite plugin that is a straight chain is flattened into the chain and one with a branch is refused by name (nested-plugins.md section 9.3). Contract 3.1 end to end, including integrity (3.2) and compiling inside the processor (3.3), through src/testing/OfflineHost.js, which runs the real processor code.
JigDAW Adapter (native/jigdaw-adapter/) A VST3, CLAP and LV2 plugin, built with DPF, with a second shell in JUCE. It loads a chain of Jigs by IRI and runs their WebAssembly directly, with no JavaScript. module-abi.md versions 1 and 2, including splitting a block larger than jig_max_frames and jig_latency_frames reporting, checked by latency_test.cpp against Quefrency's 2047 frames. Contract 1.1 and 1.2 (fetch by IRI), 3.2 (integrity). It is an independent reading of the profile format, with its own Turtle parser. It refuses a composite plugin, saying that it is one.
REAPER render script (reaper/jigdaw-render.lua) A ReaScript that asks for plugin IRIs and notes, drives the reference host, and drops the rendered WAV onto a new track. Not yet run against a real REAPER. Nothing beyond the reference host; it is a route into it.
WAM export (bin/wam.js, src/wam/WamModule.js) Packages a Jig as a Web Audio Module 2.0, so a WAM host can load it. wam.md, "The other direction". Contract 3.1, 3.2 and 9.1 as a WAM host sees them.

Plugins

There are 32 worked plugins in plugins/, each a directory holding profile.json, a build script, the processor and a generated profile.ttl. They were written to cover different parts of the contract, and each one's row says which.

Plugin What it is Exercises
Pulse An eight voice subtractive synthesiser, in Rust. MIDI in, audio out. Module ABI version 1 with its note exports. Contract 4.2, an instrument with no audio input. Contract 6, incoming events located by stream position, with a bounded queue that reports dropped (6.3, messaging.md 1.4). latency.md section 5: the only plugin declaring jig:tailFrames, reported in ready for the actual rate, which the reference host renders past.
Cascade A Schroeder plate reverb, in Rust, and the worked example the others are compared with. Module ABI version 1. Contract 5.3, widgets following the declaration's shape: a scale point enumeration drawn as a selector and a toggled port drawn as a switch. The native adapter's chain tests load it.
Dynamix A compressor/expander, limiter and clipper in series, in Rust, with an external key input. Contract 4.2, two audio inputs, the second a sidechain that is optional when unconnected. 14 parameters under module ABI version 1.
BassGen A bass line generator in time with the session, in Rust. MIDI in, MIDI out, no audio. Module ABI version 2: MIDI out, the transport block and its valid bits, and zero audio outputs, which a browser host must keep rendered anyway. Contract 6 outgoing events (jig:MidiOut) and contract 7, transport.
DrumGen A drum pattern generator in time with the session, in Rust, ported from the downspout VST3 of the same name. Control MIDI in, drum MIDI out, no audio. Module ABI version 2 with MIDI in carrying control changes only (trn:ControlMidi, the Conductor CC map), 21 parameters with trigger ports fired on the rising edge, and loop-boundary mutation under transport.
DrumKit A synthesised drum instrument in Rust, ported from the downspout VST3 of the same name. Drum MIDI in, stereo audio out. Module ABI version 2 with MIDI in as whole frame-stamped event records interleaved per sample, 75 parameters, and the first instrument with per-voice mute switches drawn from lv2:toggled.
MelGen A phrase-aware melody generator in time with the session, in Rust, ported from the downspout VST3 of the same name. MIDI and control MIDI in, melody MIDI out, no audio. Module ABI version 2 with MIDI in for follow notes and the Conductor CC map, period forms and answer derivations over 23 scales, and three trigger ports (New, Notes, Rhythm) separating full, note-only and rhythm-only regeneration.
Ground A long-form bass section planner in time with the session, in Rust, ported from the downspout VST3 of the same name. Control MIDI in, bass MIDI out, no audio. Module ABI version 2 with a 32-phrase form plan, per-phrase role override ports, in-place phrase surgery instead of a whole-form scratch buffer, and the beat flywheel the generator ports share.
Cadence A MIDI harmonizer that learns a chord cycle from what it hears, in Rust, ported from the downspout VST3 of the same name. MIDI in, MIDI and harmony MIDI out, no audio. Module ABI version 2 with chord fitting by dynamic programming over scored candidates, comp hits planned from learned timing, the first trn:HarmonyMidi producer in the tree, and the DP tables in engine state rather than the WebAssembly stack.
Counterpointer A MIDI processor that learns an incoming cycle and answers it with a monophonic counter-melody, in Rust, ported from the downspout VST3 of the same name (design). MIDI in, MIDI and melody MIDI out, no audio. Module ABI version 2 with segment capture against the transport grid, candidate scoring over scale membership, consonance and voice leading, a strict fugal answering path at high Regularity and Counter, embellish hits inside a segment, and a Bass Descend response mode. The second trn:MelodyMidi producer in the tree.
Canticle A twelve-voice polyphonic tonal instrument in Rust, ported from the downspout VST3 of the same name (design). MIDI, melody MIDI and harmony MIDI in, stereo audio out. Module ABI version 1 with note on/off, five model layers over one voice allocator with release tails and stealing, envelope times and detune from polynomial exp without libm, equal-power pan from Newton sqrt, CC 120/123 folded to all-notes-off in the processor, and the worst-case tail in the profile with the actual figure in ready.
8-Bit 8asterd Unmodified firmware for three AY-3-8910 chips, compiled from C++. Module ABI version 2 MIDI in, as whole event records. 42 parameters, generated from the firmware's own list, with scale points and values shown as the device's panel shows them. Parameters that also answer to MIDI CCs.
Mop An OPL3 FM General MIDI instrument in C, with its AdLib bank embedded. MIDI in, stereo audio out, drums on channel 10. Module ABI version 2 MIDI in, as whole event records released per sample. 128 General MIDI programs as one enumeration, a voice cap defaulting to eight of the eighteen the chip has, and every other parameter answering to a MIDI CC.
Boost A gain stage in C++, meant to be copied: everything beyond one line of DSP is ABI wiring. Module ABI version 1 at its smallest. trn:Utility.
Stomp rack A boost into a tremolo into a plate reverb, as one plugin, built from Boost, Tremolo and Cascade by reference and pinned to each. No code of its own. nested-plugins.md and contract section 14: a composite plugin, its exposed controls, its pins (tests/host/StompRackPins.test.js fails when a member is rebuilt), bundling and the catalogue.
Ferrite A neural amp model in series with a convolution cabinet, in Rust, depending on nam-rs. Two jig:asset resources marked jig:userReplaceable, verified like the module (contract 3.2) and replaced while running (messaging.md 1.2, asset). State (contract 8, messaging.md stateRequest and state): the only plugin that answers a state request.
JigDAW Gain Trim, One-Pole Filter, Soft Clipper Three REAPER JSFX effects converted by bin/jsfx-import.js, run by the shared bytecode interpreter in plugins/_jsfx-runtime/. A module with no jig:abi, private to its processor, which module-abi.md says a native host must refuse. The compiled script carried as a jig:asset. Converting from another plugin format.
Tremolo A sine tremolo in plain JavaScript, with no WebAssembly module. jig:module being optional. The first plugin with its own jig:ui: a sandboxed frame on another origin (contract 9.1) speaking messaging.md section 2, with ready, parameter, gesture and resize. Contract 5.2: the only plugin declaring an a-rate port, read per sample. Messaging.md 2.4: the only interface drawing the processor's own snapshots, a live gain readout.
Squelch A resonant lowpass swept by an envelope follower, in plain JavaScript. A second plugin with no module, and the one the "Acid" preset chains after BassGen and Pulse.
Quefrency A cepstral formant and pitch shifter, in Rust (design). latency.md sections 1 and 3: a constant latency, reported in ready for the actual sample rate. Module ABI version 2 MIDI in on an audio effect, taking trn:ControlMidi. The host's rule that a plugin taking only control changes gets no keyboard or clip.
Lookahead A switchable lookahead delay in plain JavaScript, direct or held back by 512 frames (design). latency.md section 2: the only plugin whose latency changes, reporting the actual figure in ready and posting latency with fromFrame on change, with the worst case in the profile. The host half: the dispatcher recompiles compensation and retimes the moved delays scheduled against fromFrame.
Dice A MIDI probability gate in plain JavaScript, with no WebAssembly module (design). Contract section 8: genuine non-parameter state (the xorshift32 generator) answered over the state channel, and the joint round trip with Ferrite proving two state replies route by token.
MIDI Filter Steers MIDI in plain JavaScript: a channel filter, a channel remap, transpose and a note range, so two of them split a keyboard. Contract section 6 (MIDI over the message port, delivered by stream position, and kept at its own frame), and that a note-off follows the note-on it answers even when a setting changes mid-note. Stateless: it answers no state request.
Parameq A six-band stereo parametric equalizer in C++, with peak, shelf, pass and notch shapes per band. Module ABI version 1 with 31 parameters. The second plugin with its own jig:ui: a graphic equalizer in the manner of Reaper's ReaEQ, drawing the combined response from the same cookbook formulae the module uses, with one draggable node per band, rendering from the host echo rather than its own input.

plugins/_jsfx-runtime/ is not a plugin itself: it is the interpreter the three converted JSFX plugins copy.

Host components, by layer

These are the modules the hosts are built from, in the layers architecture.md describes. Each is named with the clauses it implements.

Loading a plugin

  • PluginLoader.js: contract 3.1 in order, stopping at the first failure; dereferences the IRI (1.2).
  • Capabilities.js: contract 2, every trn:requires answered before any code is fetched, including shared memory (2.3).
  • Integrity.js: contract 3.2, every fetched resource checked against its digest before use.
  • Instantiate.js: contract 3.1 steps 4 to 8, from fetch to ready (messaging.md 1.3).
  • Parameters.js: contract 5.1, processor descriptors and panel controls derived from one lv2:port declaration.
  • LoadError.js and Inspections.js: contract 10.1, a failure names its step; 10.3, load outcomes recorded as jig:Inspection.
  • StateCodec.js: contract 8.2, a plugin's state as the string literal jig:nodeState.
  • PluginCheck.js: a pre-publish check that a plugin's code produces bounded audio, beyond what validation and digests can say.

Foreign plugins

The model and its operations

  • Project.js: the session as project-format.md describes it, with editor metadata kept apart from execution metadata.
  • Endpoints.js: which ports a node has, so the interface and the dispatcher refuse the same impossible connections.
  • OpDispatcher.js: contract 9.3, every operation once, for the editor and the agent surface alike. Also the host end of messaging.md 2.3 and 2.4.
  • UndoHistory.js and OpenProject.js: undo and redo; opening a saved session or a preset through one sequence.

RDF

Compiling and playing

  • GraphCompiler.js: latency.md sections 3 and 4, compensating delay on parallel paths and refusing a cycle with no delay in it.
  • Engine.js: one AudioWorkletNode per plugin; contract 10.2, a failed plugin muted while the rest keep playing. Loads a composite plugin whole or not at all, contract 14.
  • CompositeResolver.js, CompositeExpansion.js, CompositeState.js: nested-plugins.md, the tree check before any code is fetched, the expansion of a composite into its members for the compiler, and the state a composite saves. Each rule is listed beside its test in that document's section 11.
  • CompositeUnpack.js, CompositePack.js and CompositeWriter.js: nested-plugins.md section 9.4, taking a composite apart into its members and describing a selection as a composite profile, checked by taking the reference rack apart, packing what came out and hearing that the result is the rack again.
  • EventRouter.js: contract 6.1, MIDI carried by the host between processors.
  • Scheduler.js: contract 6.2, clip notes sent ahead at absolute stream positions.
  • Transport.js: contract 7, frames, seconds and beats across a tempo map.
  • ClipPlayer.js: audio clips, on the message thread.

Interface

Catalogue and agents

Converting from other formats

Tools

Tool What it does Exercises
npm run validate (bin/validate.js) Validates RDF against vocabs/shapes.ttl. plugin-profiles.md, "Validating".
bin/write-profile.js Generates profile.ttl from profile.json, with digests of the files on disk and every resource and scale point named. Contract 3.2; plugin-profiles.md, "Resources are named, never blank".
npm run check-wasm-abi (bin/check-wasm-abi.js) Reads a compiled module's import section, and decodes every instruction to find a memory.grow reachable from an ABI export. module-abi.md calling sequence step 1 (no imports) and the rule that memory is not grown after jig_init.
npm run check-plugin (bin/check-plugin.js) Renders a plugin and reports whether it produced bounded audio. What validation cannot: that the code does something.
bin/bundle.js, bin/verify.js, bin/keys.js Make a flattened profile and a .jig archive, open one and say what is known about it, and manage a signing key. plugin-bundles.md sections 2, 4, 5 and 6.
npm run build:index (bin/build-plugin-index.js) Generates plugins/index.json and the collection web/collections/jigdaw.ttl from the profiles on disk. plugin-collections.md section 2.
npm run build:gallery (bin/build-gallery.js) Screenshots every plugin's generated panel and writes the gallery page at web/gallery.html, served at /gallery.html. Contract 9.1: the generated panel is the expected case, and this is every one of them.
npm run serve (bin/serve.js) A development server that serves what a host and a plugin origin must, including CORS. Contract 1.3.
bin/build-vocab-site.js, deploy/ The vocabulary's human-readable page, and the nginx configuration that serves the namespace. namespace.md.
npm run mcp-bridge (bin/mcp-bridge.js) An MCP endpoint on loopback whose tools run in an open Jiggy page. webmcp.md, "The local bridge".
bin/jsfx-import.js Converts a JSFX effect into a Jig. Contract 3.1 for a generated processor.
bin/wam-fixtures.js Builds what the foreign plugin probe page needs. Contract 12, in a real browser.
bin/check-suites.js Fails when a test directory is missing from the vitest include list. A guard on the tests themselves.

Reference documents

  • examples/: a reference document and a counterexample for each kind the shapes check: a profile, a foreign plugin, a collection, a provenance record and a project. A reference must validate and a counterexample must violate every constraint once, which is how a constraint that never fires is found.
  • examples/session-project.ttl and web/presets/: sessions in project-format.md, opened through the same code as a saved file.
  • vocabs/jigdaw.ttl and vocabs/shapes.ttl: the vocabulary and the SHACL shapes. Where they and the contract disagree, the contract governs.

What nothing exercises yet

These clauses are specified and, as of 2026-09-29, either no plugin depends on them or no host acts on them. Each is a place where the specification is untested by use.

  • Composite plugins, beyond one browser run (nested-plugins.md). The tree check, the expansion, the engine, the dispatcher, bundling and verification are exercised against the real boost, tremolo and cascade plugins, headless, and Jiggy's page was driven with one in Chrome on 2026-10-02 (load, generated panel, exposed controls, signal path, a stale pin refused, remove and undo), and again for a clip played from the transport, a session saved and reopened, and the Routing and Mixer tabs. Nothing has played one on a phone, and the native adapter, a serial chain, refuses one by name.
  • Shared memory (contract 2.3). The host offers jig:SharedMemory; no plugin requires it.
  • State from more than one plugin (contract 8). Ferrite is the only plugin that answers a state request.
  • MIDI from outside the page. Jiggy takes Web MIDI input (src/host/MidiInput.js) into an armed track, or the selected one, as note and controller messages stamped with the audio clock's frame. It has only been driven with a stand-in controller, never a real device, so the permission prompt and hot-plugging are untried against hardware.