VOLTAreference

Reference / Work in Studio

Tell a story with a song

Work in Studio

Open in the workbench →
On this page · 7 sections

Present music as a readable notebook of prose, code and musical views that explain how the piece unfolds.

Held from the first release. Bench (the declaration notebook and the Patch Bay board) has no menu, header or shortcut entry in this release; the piano roll it hosts still opens from a track's Edit notes.

A song file that is its own introduction: markdown prose in the -- voice between the declarations, -- ▸ chapter headings that also stage the file for Learn, live cards on the trigger algebra, and a story preset that makes the bench notebook read as a page. Design spec: Tell a story with a song. The prototype it reproduces is the hosted intro page built 2026-09-02 around Tallyman (scripts/intro-page/).

The format#

A story is an ordinary .volta file. Its top-level -- comment runs (the ones benchmodel.rs::find_prose_blocks already turns into prose cells — outside every declaration span, ended by a truly blank line) are markdown-ish:

Syntax (after the -- prefix)Renders as
# T … ###### Theadings, H1 largest
▸ T (the Learn step marker -- ▸ T at column 0)a level-2 heading; the ▸ stays visible but dim
paragraphs, blank-line separatedbody text
- item (or * / +) / 1. itemlists with a hanging marker
> quotea quote on a left hairline
```volta … ```display code (never compiled)
---a hairline rule
`code`, **strong**, *emph*, [text](url)inline runs; other URLs are text with the URL on hover
[text](volta:…)a Notebook command link: volta:cell/<name> scrolls to a cell, volta:viewer/<decl>/<Op>[/<n>] opens that viewer (big under story), volta:play/<track> starts a track, volta:scene/<name> selects a scene, volta:stop stops; an unknown target reports "story target unavailable"

Raw HTML is text. Comment runs inside a declaration stay part of that cell. Decoration-only runs (---- banners with no alphanumerics) are still dropped. Editing a prose cell edits the raw stripped text and writes the -- prefixes back, as before.

.volta.md (Write music with explanations) still ships as a format and corpus; nothing new is built on it — -- prose is the one literate format.

The default Quiet presentation keeps each cell's name, transport and Tools menu in a fixed header. Tools opens the existing musical editors, history, explanation and cell management. Musical controls stay beside their editor. Simple inline play and sound strings have editable step rows; melodic patterns also show a pitch thumbnail. Edit notes opens the piano roll, including its source-cycle selector. Patch cells initially show one live viewer. Show source / Hide source changes the presentation without changing the song.

An authored chapter can gather declarations from anywhere in the file:

-- ▸ The drums count themselves
-- @notebook id counting
-- @notebook controls murk
-- @notebook cells kickPat kickT tally droneT
-- @notebook viewer tally Count
-- @notebook viewer tally Toggle
-- @notebook big tally Svf 1
-- @notebook listen kickT snareT droneT
-- @notebook loop 1 5
-- @notebook open
-- Remove a kick and watch the counter wait.

These are ordinary comments; the compiler and musical source are unchanged. Instructions are hidden in rendered prose and retained by edit notes. Explore chapter or the chapter picker stages its cells and viewers. Wide chapters put the explanation and controls beside the live viewers; narrow windows use a single column. Full song returns to the document.

  • id gives a chapter a stable identity. Use a Volta identifier.
  • controls lists macro cells; cells lists featured declarations in order.
  • viewer opens an operation in a declaration; its optional occurrence is one-based and defaults to 1. big opens that viewer at its larger size.
  • listen lists the tracks to hear together, including any tracks read by followers or onset counters. Listen to chapter starts that context.
  • loop gives one-based bar positions with an exclusive end: 1 5 is the first four bars. open makes the chapter the initial view for new readers.

Each cell's uses / feeds links navigate to related declarations. Together keeps its immediate relationships in one view. Make chapter writes that view into source; Save chapter layout updates the current chapter's viewer choices. The prose editor can then give it a title and explanation.

Experiment snapshots the current song and live macro values. Edit notes or controls, select a passage, and use Loop passage while working. Compare by ear renders that passage from the snapshot and current source, then exposes Before / After. Earlier playback is rendered too, so counters, envelopes and effect tails enter the passage with their history intact. Each version follows its own tempo and meter. Comparison supports up to 60 seconds within the first four minutes. It renders the listed tracks together; it does not walk the song's arrangement. Re-render after another edit. An older comparison becomes unavailable immediately.

Keep retains the edits. Revert restores the snapshot through the editor's guarded source write. A source change outside the Notebook disables Revert so it cannot overwrite unrelated work. Finishing a listening session restores the prior running tracks, position and transport state. Leaving Notebook ends its temporary listening session; unfinished experiments remain in memory per song.

The chapter presentation travels in .volta source. Personal choices—chapter, scroll, hidden source, folds, piano rolls and selected cycles, tools, viewers and viewer sizes—are stored separately for each document, using its full path. The shell retains up to 24 document workspaces across restarts; experiment snapshots themselves are session-only.

The cumulative contract#

Every -- ▸ prefix of a story must compile on its own (Learn's rule: the BUILD strip appends one chapter at a time). Declaration order satisfies it when each chapter only refers to names above it; Tallyman's eight chapters do.

Tallyman, the first story#

examples/songs/tallyman.volta (2026-09-02): a # The drums count themselves header, then eight -- ▸ chapters in file order — three lines set the room · patches are voices · patterns are time · signals listen to each other · the drums count themselves · every third kick, a new colour · the console is a value too · the song is the form. No code line changed (compiled music is identical; source locations shift with the prose). Open it in the notebook with story on.

Counter cards#

compiler/src/viewer.rs registers ViewerKind::Counter (label counter, blurb "watch this counter step") for the IR ops Count | Toggle | OnCount | Choose | Hold. In a synth cell each call site gets the dashed viewer underline like every registered op; a nested count (onsets kickT) 4 puts the onsets trigger strip on top of the counter's hit area, and the topmost strip wins the click (bind the trigger once — trig = onsets kickT — when you want both cards). Card header: counter · <op> · n<id>.

ui/src/nodeviewer.rs::counter_body dispatches on the op:

opface
countthe value as a large tabular number, mod n, and a staircase of n cells with the current one lit (an unbounded count shows the number alone)
togglea two-state switch
onCount nn divider dots filled by fires since the last pass, plus a fire lamp
choose [..]the table's cells with the selected index lit and the selected value read out ([i] v)
holdthe latched value as a number with a short history trace

Data comes from the existing planes, no engine change: count/toggle/ choose/hold are block-constant state, so the interest-gated viz node_values (a block RMS) carries them exactly — but the RMS drops the sign, so a Bipolar hold reads |latched|, and choose prefers its selector's value for the index over matching the table by magnitude. onsets is high for one 128-sample block and the UI samples one snapshot per frame, so rising edges alone would miss most fires; onCount's dots therefore read the source detector's engine-true per-voice fire counter (fires % n, both born with the voice) and fall back to sampled rising edges for non-Onsets triggers. Counter state is per-voice, so between notes the card holds its last reading dimmed (the wave card's rule) instead of the fabricated zero other cards push. moduleinfo.rs::RackNode::nums carries the inline constants the faces need (Choose.table, Count.modulo, OnCount.n). Tests: ui/tests/nodeviewer.rs (underlines on all five ops, open/close, two cards on one cell, the nums seam) and the registry tests.

The story preset#

The bench top strip's story chip (menu: Story presentation), persisted as the shell-state token benchstory=1 (story_pref() / set_story). While it is on:

  • quiet cells are forced on — the notebook's own quiet value is remembered and handed back when story goes off, and only that borrowed value persists as benchquiet (quiet_persist_pref()), so a shell state saved under story never promotes the forced quiet into a preference; the guided course's course_loud copy is never touched;
  • prose reads as a page — ProseStyle::notebook (the Body type role at the active density, 13.5 px at Standard; a 70-character measure; H1 1.7×) becomes ProseStyle::story (1.3× that body; 62 characters; H1 2.05×); H2 stays 1.35× and H3+ 1.15× of the preset's body, and the gaps between blocks scale with it;
  • the song-time strip is pinned open (map_strip_ui: expanded = story || map_open.unwrap_or(has_arrange)), with the user's own collapse state untouched underneath.

Every viewer card header has a big chip (accessibility name big viewer <decl> <ordinal>; state Bench::big_nodeview, cleared with the module generation and swept with the live-card retain pass). Big allocates twice the body height and widens the card to CARD_MAX_W_BIG (640 px). Counter faces thread an explicit scale through every fixed pixel; the Onsets card's fire count leaves its caption and becomes a display number (the "kicks so far" tally, from ProbeView::fires); wave, filter, follower, random, spectrum, dynamics and shaper bodies were already proportional and read the scale back off the rect for their readouts.

Headless: VOLTA_STATE="benchstory=1" puts a volta-shot bench capture in story mode; VOLTA_BENCH_NODEVIEW_BIG=<decl>[:<count>] opens cards big, beside the existing VOLTA_BENCH_NODEVIEW. Tests: bench_story_preset_stages_the_notebook_as_a_page, bench_story_hands_back_a_quiet_the_user_chose (ui/tests/behavior.rs), big_chip_doubles_the_card_body (ui/tests/nodeviewer.rs).

Code map#

ui/src/md.rs (prose parser) · ui/src/bench.rs (prose_cell_ui, ProseStyle, the story preset, big_nodeview) · ui/src/notebook.rs (volta: links) and ui/src/notebook/ (chapters, experiments, workspaces) · ui/src/app/prefs.rs (benchstory) · compiler/src/viewer.rs (ViewerKind::Counter) · ui/src/nodeviewer.rs (counter_body, big) · ui/src/moduleinfo.rs (RackNode::nums) · examples/songs/tallyman.volta · design spec Tell a story with a song (design/shipped/).