VOLTAreference

Reference / Work in Studio

See patterns and signals

Work in Studio

Open in the workbench →
On this page · 4 sections

Inspect Volta values with viewers that reveal timing, waveforms, spectra and other properties of the music.

Per-instance viewers for builtin calls in the bench notebook (design: See patterns and signals, all four stages shipped + review-hardened 2026-08-20).

Surface#

In a synth cell, every viewer-eligible node call gets a dashed underline (3 px on / 3 off; the env editor's dots and the mod overlay's solid rule are distinct families, layered smallest-extent-wins: mod strips under node strips under env heads — a node strip that would exactly shadow a visible mod strip shrinks to its head token, so both stay clickable). In a track cell, compressor-family insert stages and granularize inserts get the same entry. Clicking toggles a compact card docked under the cell: header kind · op · id/slot · ⏵ src ×, one ~54 px body.

Registry (compiler-owned, volta_compiler::viewer):

kindopsbody
waveOsc As3340 Pulse Pulsar FmOperatorlive-animated cycle (amplitude = node level peak-held over ~8 frames, scroll rate = sounding pitch, note readout — held + dimmed between notes), level strip; true waveform after ⏵. A Pulsar scrolls a train of its authored pulsaret (RackNode::nums = cycles · shape · burst · rest, shape = window) and docks the pulse laboratory below the card
filterFilter Svf Ladder Diode Korg35 Formantthe engine's own biquad response + cutoff/Q readout (live-streamed cutoff when an Hz input is playing)
triggerOnsetsflux vs adaptive threshold (probe), fire count aggregated across voices, block-exact fire ticks
randomNoise Crackle RandomUniform Drift Chaos Flicker Simautoscaled drift trace
spectrumAdditive AdditiveSpectrum AdditiveResynth PartialsSource Wavetable Modal ModalFit Couplepartial ladder; real spectrum after ⏵
dynamicsthe nine EffectState::gr_db reportersper-SLOT gain reduction hanging from 0 dB, live + peak readout
unisonstructural — ≥2 same-span wave siblings (a desugared supersaw)the detuned copies overlaid, scrolling at spread rates (they visibly beat), ×N + note readout
shaperDrive Tube Clip Fold Bitcrushthe stage's transfer curve (bitcrush draws its staircase) with the live output dot riding it
followerFollowerthe emitted control envelope on a fixed 0..1 scale, area-filled, live readout
grainsany node carrying grain metadata (granular sources); the Granularize track insertthe shared grain inspector (ui/src/grain_ui.rs): real grain onsets, overlap and capture history — see Inspect and edit
counterCount Toggle OnCount Choose Holdthe trigger algebra's state, in the shape the op means: count a tabular number + the staircase of n cells, toggle a two-state switch, onCount n divider dots + a pass lamp, choose the table's own cells with the selected one lit, hold the latched number + a history trace

Deliberately absent: the envelope family (envedit owns those tokens), Sum (dominance lives in the rack), non-GR inserts (no live scalar until the per-insert tap ships), and the trigger algebra's non-stepping neighbours (Latch, QuantizeTo, Schmitt, HoldFor, CycleRand, NoteRand — they reshape a value rather than step through states).

Identity & data#

Synth cards key (decl, span-ordinal), track cards (decl, chain slot); targets re-resolve from the CURRENT ModuleInfo every frame and all card state clears on module generation. Insert spans must both land in the track's cell AND spell the effect's head token (the import-provenance gate — lambda-built chains carry foreign offsets).

Three data planes, no polling beyond what existed: viz node_values (interest-gated; open cards fill the 8-synth MAX_VIZ_NODE_VOICES budget after inspect/env docks — a card past the cap says "live slots full" instead of sitting dark), the probe trailer (VizProbeRaw {kind, a, b, vals[4], events} — NODE = detector internals keyed by the owning voice's start_sample, INSERT = per-slot GR, always on), and the trig-event ring (TrigEvent, block-stamped on the audible clock, pumped by every read_viz_frame, drained destructively once per bench frame).

Probe values are block RMS and per voice. An oscillator's RMS is read before the envelope, so it reads high for as long as the voice lives; the Rack case reads the CF01 all-voice sums for what is heard. A one-block trigger mostly misses the plane (see counters below). Names are resolved in the encoder, against the name table of the frame's own generation. Preview taps belong to an audition, node_values to a song voice.

Counters read the value plane (story-notebook Stage 2). count, toggle, choose and hold are their state — the node's output value — so their cards need no new seam: they sample the same interest-gated node_values as every other card. Three consequences the faces spell out: that value is a block RMS, so a bipolar latch arrives as |value| (the hold face says |latched|); counter state is per-VOICE, so between notes the plane carries nothing and the card HOLDS its last reading dimmed rather than pushing the honest zero a level would want; and a card also reads its first input off the same plane (crate::nodeviewer::CounterSrc) — the selector choose rounds, and, when the input is an onset detector, that detector's engine-true per-voice fire counter, which IS onCount's divider phase (seen = fires % n, both born with the same voice). Without that counter onCount falls back to rising edges of the sampled rings, which undercounts: publish_viz runs per 128-sample block and the UI reads one snapshot per display frame, so a one-block-wide trigger lands about one frame in six. The last exact detector count is retained between notes too, so a held divider does not jump back to a sampled zero during each gap.

Audition is a separate reading. Wave cards label their animated oscillator shape as a preview; the play button displays a captured waveform. Counter play buttons sustain one note for eight seconds and display that audition’s signed signal and current value, independent of the song’s sampled voice and live-slot budget. Keep any source tracks playing for onsets to hear them. Each counter still resets with its voice: Tallyman’s colour chapter uses a four-bar colourStudy chord to demonstrate the divider; its arranged short stabs cannot accumulate three kicks.

The phase trailer (2026-09-01, Mixing tracks and buses §6.6) is a fourth, unconditional plane on the same frame — broadband + three band correlations and a 64-point (l, r) scatter, decoded to frame::PhaseFrame. It is master state, not per-node, so it has no viewer kind and no interest gate: it feeds the mixer MASTER strip and the monitor view's PHASE panel (ui/src/phase.rs) directly. Like every trailer here it is append-only — a frame from an older engine leaves phase: None, and the panel reads "unavailable" rather than a manufactured 0.0.

The tagged trailers after it (GM01, PN01, AR01, LV01, CF01) are declared, in order, in viz::VIZ_FRAME_TRAILERS (engine/src/viz.rs). Both decoders refuse bytes left over after the last section they read: the Rust frame::decode_frame returns an "undecoded byte(s)" error and runtime/src/viz-frame.ts throws. ui/tests/roundtrip.rs encodes a frame with every section populated into runtime/src/viz-frame.fixture.json (stale fails; UPDATE_SNAPSHOTS=1 rewrites it), and runtime/src/viz-frame-fixture.test.ts decodes that fixture to the last byte, so a new trailer fails npm test until the page's decoder reads it.

Hooks & tests#

VOLTA_BENCH_NODEVIEW=<decl>[:<count>] → node-viewer.png (volta-shot); VOLTA_BENCH_NODEVIEW_BIG=<decl>[:<count>] opens them through the header's big chip (2× body, display-size readouts — Tell a story with a song). Suites: ui/tests/nodeviewer.rs (strips/toggle/close/env-overlap, both span shapes, the counter family's five ops + the nums seam), compiler/tests/insert_spans.rs, engine trig_ring_carries_track_and_node_fires + comment_shifted_chain_still_reuses, probe decode tests in ui/src/frame.rs and viewer.rs registry tests.

Known gaps#

Browser: the wasm engine exports drain_trig but the worklet glue is unwired (cards fall back to the probe view; trigger ring dead in web). Prefix-form and lambda-shared spans degrade gracefully (design §7). Bus chains carry no spans. A silent patch has no onsets detector to watch (design §5's hoist deferral).