VOLTAreference

Reference / Work in Studio

See a signal on its control

Work in Studio

Open in the workbench →
On this page · 5 sections

Read a control's changing value directly in the workbench using the signal overlay.

What it is#

The current Rust workbench paints source-span feedback without reflowing text: playing pattern atoms and modulated spans glow in the editor. Playback source identities keep entry and import buffers separate and drop changed spans. The richer inline-widget system described next was a CodeMirror adapter for the deleted TypeScript workbench.

Historical browser surface (removed 2026-08-10)#

The deleted CodeMirror adapter decorated ordinary code from compiled IR source_offsets: routing-chain badges at a binding's head, an A/B ghost of a just-edited numeric literal, modulator-shape glyphs, modulation-range bars, an active-line tint, default-off live-value readouts, and the inferred pattern-unit chip (Patterns with units Stage 3). See git history for the implementation; none of it is a current browser surface.

The playing-step highlight (native)#

The native workbench resolves the active-line tint down to the atom: while the transport runs, the exact step each audible track is sounding — the bd, the c4, one atom inside !"c2 ~ c2 ~" — lights in that track's lane colour, fading over a ~90 ms release tail. It paints in the song editor, in every bench notebook cell (matching the cell's own LED lane) and in the Patterns pane's fields.

Playing steps#

Display ▸ Editor & notebook ▸ Playing steps picks how loud the lights are:

  • Off — no playing-step highlight.
  • Subtle — a soft band behind the step and a 2 px bar beneath an exact atom (the look before 2026-11-06).
  • Vivid (default) — a stronger band and a 3 px bar; while a note's gate is open, the step's own glyphs light in its track colour (lifted toward the theme's brightest ink and held to at least 4.5:1 against the band); and each attack throws a ring that springs out from the step and fades over ~0.15 s. Only held steps recolour their text and paint at full strength; the release tail keeps a band that dims on a steeper curve, so on a fast pattern (a 16th at 132 bpm is ~110 ms, the tail ~300 ms) the step sounding now stands apart from the dimming trail behind it, and a repeated step re-flashes on every attack instead of reading as one long light. Subtle keeps the old equal-strength tail.

The choice persists as steps=0|subtle in the session state (absent means Vivid; steps=0 is also what the old on/off switch wrote for off). The shell publishes it on the egui context each frame (playhead::set_step_style), so every surface that paints lights follows it.

Track colour#

Display ▸ Editor & notebook ▸ Track colour picks how much of the track colour you see:

  • Off — every playing step lights in the theme's secondary accent.
  • While playing (default) — the lights above, in each track's colour.
  • Always — as While playing, and at rest every pattern literal a track plays is painted in that track's lane colour, quotes included, on a faint wash with a rule beneath. A literal two or more tracks play reads in plain ink, and its wash and rule split into one stripe per owning track (up to four), so a shared riff shows every player's colour at once. A literal no track plays stays in its syntax colour.

Ownership comes from the compiled module's event provenance (Playhead::owner_spans), not from playback, so it is there before anything sounds and covers branches a chooser or <…> alternation has not reached yet. It follows edits the way the lights do, and it is grouped by the literal that holds the atoms. Interpolated literals (!"…${x}…") are left uncoloured. The source editor and import tabs paint it; bench notebook cells don't yet. The choice persists as trackcol=off|always in the session state (absent means While playing).

Source and notebook views share the same playback activity. The engine records accepted note gates after timing and branch selection, including swing, push, once, and scene scheduling. Each gate carries its own chooseBy alternative, so an older held note keeps its identity when the next cycle changes branches. The display uses the engine's output-latency-adjusted clock. Short notes remain visible for at least 75 ms; the UI then adds its roughly 90 ms release fade. This represents scheduled note gates, not measured sound or effect tails.

A written-timeline song lights the same way. A region track's own event list is empty by design — its music lives in the content it places — so each accepted gate also names its region's content, and the step lights resolve through that content's provenance: motif placed by region 0bars 8bars (motif |> fast 2) lights motif's atoms in the receiving track's colour. A pattern placed by several regions lights from each; a repeat = [start, length] fold lights the atom of the content event each folded copy came from. Track colour Always counts a receiver as an owner of every literal its regions place.

The Rack case's sequencer face reads the same join. A SEQ panel that feeds its voice directly lights its written steps by identity and draws the branch a chooser is actually playing; one behind transforms (play keys (riff |> fast 2)) lights the as-written steps whose source atom is sounding — an atom expanded to several steps (c4*4) lights every step it wrote — and draws no playhead, since its period is not the track's. The song case's thumbnail of a written-timeline track draws the content sounding (else the first it places) and lights its events, without a playhead.

The notebook lights by source span, not by dividing a cycle into equally sized cells. Weighted notes, transformed arrange children, named arrange references, and patterns shared by several tracks therefore follow the same activity as the source editor. Read-only chord, parameter, and compound cells can light; repeated/generated sub-cells sharing one source atom light as a group rather than inventing an exact sub-step. The piano roll's selected consumer remains its inspection context; it does not limit source activity from other tracks.

Compiler provenance distinguishes exact atoms (halo plus underline) from containing expressions (halo only). Combinators that rebuild events keep the written step's provenance: !"0 ~ 2 3" |> slice 4 "breaks" and chop light the index or sample atom that sounded, not the whole literal (fixed 2026-11-06). Measured on the 72 playable favourites over 12 s each (offline engine, 124,672 sounding events), every event resolves to source and 96.9 % to an exact atom; the rest have no single written step — a pattern built by map/a lambda (hexatonic), or inv's merged gaps under stitch. Named references can glow alongside their original atoms. Interpolated and generated patterns use expression feedback when a precise original atom is unavailable. Import editors use their own module's coordinate space. Coincident spans whose file cannot be identified unambiguously fall back conservatively instead of decorating a guessed atom.

The compiled text is an immutable revision identity. Unchanged spans survive insertions and formatting; changed atoms lose their lights immediately, even while the engine still plays the previous successful compile. Across a larger semantic edit, the mapping conservatively keeps only unchanged surrounding regions. Velocity scales brightness; muted and soloed-away tracks stay dark.

How it's implemented#

  • compiler/src/playback.rs captures an optional source sidecar during the audio lowering pass. Hosts opt in; ordinary compiler audio JSON is unchanged. Events retain atom, argument, and containing-expression provenance through pattern transformations. The sidecar carries original document texts and event tables for every track and alternative.
  • engine/src/playback.rs holds a fixed 128-event history per running track. The binary viz frame appends at most 512 visible events, with track, event, branch, sample start/end, and velocity; the PC01 trailer adds each one's written-timeline content (PlayedEvent::content, an index into the IR's timelineContents, u32::MAX for an ordinary track), and a region event's index is into that content's events (TrackPlan::written_event maps a folded copy back). This adds no callback allocations. The recent-note list (PN01) rides the case buffer (CaseScratch::recent) to keep the stack-copied viz scratch under its ceiling. At those bounds extremely dense activity can lose visual entries; audio is unaffected. Seeks, stops, and module replacement discard old activity. Each accepted gate is also written to a separate 4096-slot accepted-event stream (engine/src/events_stream.rs, EngineHandle::events_since) that Link a note to its source reads for occurrence history; the overlay does not depend on it.
  • ui/src/playhead.rs joins activity with source identity (for_each_sounding, the one join every surface reads), maps revisions, and maintains the shared decay. ui/src/rackcase/seq.rs reads the same join for the sequencer faces; ide::ReplEvent::atom ties a probed (as-written) lane's notes to their source atoms. ui/src/editor.rs paints it; ui/src/bench.rs rebases it into notebook cells, including when source text is collapsed. ui/src/app/shell.rs also projects lights into import editors. The same Rust code runs in native and browser workbenches.
  • Older payloads retain a literal-only adapter using active_events; new payloads distinguish an authoritative empty activity list from absent data.
  • ui/tests/playback_activity.rs exercises the real compiler, scheduler and binary wire through the source/notebook join, including all eight sc-0* guitar songs and written-timeline regions (plain, placed twice, folded). ui/tests/rack_case_seq.rs feeds a real engine's accepted gates to the Rack case's sequencer faces. ui/tests/playhead.rs covers the workbench and notebook seams.

Notes & limits#

  • Native decoration keyed on IR source_offset is gated to local-buffer synths: nodes from imported modules carry foreign byte offsets and must not decorate the importing buffer. Within a local declaration the compiler guarantees local offsets: an inlined imported definition is relabelled to its use, and an imported pattern's events keep only their file-qualified playback anchor.
  • Spectrum thumbnails (Stage 5) and cross-reference ribbons (Stage 8) are not built; a real per-binding scope lives in the Inspect a patch, not here (the spec's §13 carve-out).