VOLTAreference

Reference / Work in Studio

Read a rhythm as a circle

Work in Studio

Open in the workbench →
On this page · 4 sections

See a repeating pattern around a wheel to compare event positions, accents and relationships between cycles.

What it is#

The module's rhythm drawn as a circle: one revolution per bar, a beat ring inside, one ring of attack-to-attack arcs per audible track (a pattern longer than the bar laps onto concentric sub-rings, so a group that crosses the barline visibly continues), the attacks joined into a polygon, a hand per ring, and an n / m count of the pulse inside the sounding group. It lives as a compact wheel ▸ dock on any bench track cell. The Timeline panel's wheel mode is off the switch for now (the panel offers cycles | map; a session saved with tlmode=wheel reopens on the cycle lanes), so the Timeline surface below describes the mode as built, not a control you can currently reach. Renderer only — no engine or compiler seam.

Surface#

Timeline ▸ wheel (hidden for now — see above). The chip beside cycles/map (persisted as tlmode=wheel; the older tlmap=1 still reads as the map). The header:

  • unit chips bar · cycle · 2 bars · 4 bars (persisted as tlunit=): bar folds everything to one bar (longer patterns lap, shorter repeat); cycle lets each ring revolve at its own period — the polyrhythm view — and a cross-rhythm (3 : 4) adds realign every 12 beats · next in 5.2; the phrase units put two or four bars on one turn;
  • the meter (4/4, 7/8; under a meterMap, meter map · N beats here, folded at the bar the transport is in);
  • one chip per ring in its Timeline lane colour — click to focus a ring (the count follows it, the others fade); the default focus is the busiest ring;
  • the count n / m with the ring's grid in words (8ths, 16ths, 8th triplets, 5:4, or free when the attacks share no grid up to 96/beat).

On the wheel: hover a group for name · 3 8ths · beat 4 → 6 · lap 1/2 · pitch · vel · chord of N; click a group to move the caret to its mini-notation atom (honoured only inside a !"…" literal of the live buffer); click a beat-ring pulse to seek there. Stopped, the static picture stays and the live count is parked unless a cycle is being inspected.

Bench ▸ track cell ▸ wheel ▸. One ring, its polygon, the count; while the transport is stopped, ←/→ step the hand a grid unit at a time and atom ↗ jumps to the atom under the hand.

Both wheels share a cycle picker with the pattern roll and Bay explorer. Previous/next or selecting a pattern cycle parks the hand for inspection and emits no transport command. Follow resumes the track's engine clock. Ring focus follows the selected consuming track; selecting a wheel group shares its source atom with the written/result views. State belongs to the live document, and source anchors expire on edits. See Pattern rolls and phrase editing.

-- examples/songs/pyramid.volta — the study the wheel was built on:
-- 3-3-4-3-3 on eighths over two bars of 4/4, so the chord ring laps twice
-- and the four carries across the barline.
piano = patch {
  source = saw (freq pitch) + (triangle (freq (pitch + 12Semitones)) |> mods.gain 0.25),
  ampEnv = adsr 4ms 900ms 0.15 500ms,
  cutoff = 1400Hz,
  gain = vel * 0.45
}
pyramidChords = !"<[[f#3,a#3,c#4] ~ ~ [e3,g#3,b3] ~ ~ [e3,g#3,c#4] ~] [~ ~ [f#3,a#3,c#4] ~ ~ [g#3,c#4,e4] ~ ~]>"
pulse  = sound "bd bd bd bd" |> gain 0.6
chords = play piano pyramidChords

How it's implemented#

  • Model — ui/src/cyclewheel/model.rs: WheelModel::build(info, unit, bar_beats) over ModuleInfo. Attacks are sounding onsets (a chord's simultaneous events are one attack); a group runs attack → next attack, wrapping the last to the first; the held duration is a lighter inner band. A ring folds at the smallest whole number of both its period and its revolution (fold_beats, LCM search; past MAX_LAPS = 8 laps the ring draws one revolution, cut, and says so); the grid is the coarsest denominator (1…96 per beat) every attack, the period and the revolution sit on — exact, because IR onsets are grid-exact and swing/nudge/humanize are scheduler-side track fields. Chooser tracks fold every deck and the renderer shows the engine-latched one (TrackFrame::active_alternative, the Timeline's rule). count_at/hand_at give the readout and hand for an absolute beat.
  • Renderer — ui/src/cyclewheel/mod.rs: WheelUi::ui (Timeline mode) and WheelUi::compact_ui (bench dock). Arcs are quad meshes (an annular sector is concave; epaint's filled paths are convex-only). Each ring's hand and count run on the TRACK's own beat axis — the frame's per-track cycle + phase × period, which already carry clock (p/q) ratios and |> at anchors — so a track that is not running has no hand; the beat ring runs on song_clock.beat. Both are extrapolated ≤ 120 ms between viz frames at bar_len / bar_seconds and frozen when the frame time stops advancing (a held transport must not drift). The attack flash rides active_events on the group the hand is in (plain tracks only — chooser decks index their own event lists); the audibility gate is the playing- step highlight's (strips mute/solo). Under phrase units the beat ring carries p × n pulses; a pulse click seeks through the meter map. Colours: timeline:: lane_color per ring, theme tokens elsewhere; low-power drops glow, trail and the between-frame repaint. Every readout carries a widget_info name (cycle wheel · N rings · bar · 4/4, cycle count n / m, focus ring <track>, wheel step forward/back).
  • Mounts — ui/src/timeline.rs (TimelineMode, the chips, the mount), ui/src/app/prefs.rs (tlmode / tlunit shell state), ui/src/app/ rightcol.rs → navigate::jump_to_live_atom (the click-to-atom gate via playhead::in_pattern_literal), ui/src/bench.rs (open_wheel_dock / wheel_states, the chip beside dyn ▸, pending_mod_jump for the atom).
  • Harness — VOLTA_TIMELINE_MODE=wheel (+ VOLTA_WHEEL_UNIT=) parks the Timeline on the wheel for volta-shot; VOLTA_BENCH_WHEEL=<track> captures the compact dock as wheel.png. Tests: ui/tests/cycle_wheel.rs (mode switch + persistence, the count under a driven clock across the barline-crossing four, the atom-jump gate, the bench dock + stepper) and the model's unit tests (Pyramid Song, euclid, chord, alternation, triplets, free onsets, 3:4 fold, lap cap, choosers, phrase units).
  • Song — examples/songs/pyramid.volta (golden pyramid.json; picker tag study).

Static export (a shareable frame set)#

volta-shot is deterministic, so a frame sequence is a build artefact:

for i in $(seq 0 59); do
  VOLTA_TIMELINE_MODE=wheel cargo run --profile fast -p volta-studio --bin volta-shot -- \
    examples/songs/pyramid.volta --views source --frames $((120 + i * 3)) --settle 0 \
    --size 1000x1000 --out-dir /tmp/wheel/$i
done
ffmpeg -framerate 20 -i /tmp/wheel/%d/source.png -vf "crop=560:560:960:260" wheel.gif

(--frames is the transport warm-up in 60 Hz frames, so consecutive shots are 50 ms of song apart; adjust the crop to the Timeline pane.)

Notes & limits#

  • Groups are attack-to-attack (what one counts), not event durations; a held note shows as the inner band. Overlapping notes of different lengths keep only the longest as the band.
  • Past eight laps in bar mode the ring is cut at one revolution and its chip says so; switch to 2 bars/4 bars or cycle.
  • A cycle_beats = 3 track in 4/4 folds at 12 beats (three laps) and the header reports the realignment; periods that are plain multiples of each other (4 : 8) say nothing.
  • Under a meter map the unit is the bar at the transport's position, so the fold can change mid-song; the header names the bar length.
  • Arrangement-walk tracks (arrange/at/once) are drawn as loops of their pattern — the wheel does not know the schedule.
  • Swing/nudge/humanize move the audible attack after the drawn vertex; no ghost vertex yet (Read a rhythm as a circle §5).
  • The browser build has the wheel (the viz frame reaches the wasm UI); the exact trigger-tick ring does not, and is not needed.