Lightningbeam/lightningbeam-ui/lightningbeam-core
Skyler Lehmkuhl f957b01dcf Cycle recording phase 1: transport loop region
Adds a cycle (loop) region: a range on the timeline ruler that the
transport wraps at during playback. This is the substrate for
GarageBand-style multi-take cycle recording (phases 2 and 3).

The region is authored in BEATS so it stays put musically across tempo
changes. It lives on the Document (saved in the .beam, serde-defaulted
so old files load) and is edited through SetCycleRegionAction, so it is
undoable and marks the document modified like any other edit.

Backend:
- Engine gains loop_region/loop_enabled plus a wrap at the single
  playhead-advance point in process(). The wrap is phase-preserving
  (modulo, so an overshoot larger than the loop can't strand the
  playhead outside the region) and gated on playhead >= 0 so a count-in
  pre-roll never wraps.
- Sounding voices are deliberately NOT reset at the wrap the way
  Command::Seek does, since that would chop sustain and reverb tails at
  every pass.
- MidiRecordingState::wrap_at_cycle writes note-offs for held notes at
  the region end and re-opens them at the region start, so a key held
  across the boundary can't end up with a negative duration or hang.
- loop_bounds_frozen freezes the region's sample bounds for the duration
  of an *audio* recording. Phrased positively on audio so future
  multi-track recording inherits it: MIDI is beat-segmented and
  tempo-invariant, but audio is segmented geometrically and we have no
  time-stretch, so cross-tempo audio takes wouldn't be compable anyway.
- Command::Play jumps to loop_start when starting from outside the
  region; starting inside it plays from where you are.

Editor:
- Cycle lane along the bottom of the ruler (bottom, so it doesn't cover
  the bar numbers), painted inside render_ruler under the ticks. Only
  exists while looping is armed; with cycle off the ruler is entirely
  the playhead scrubber, as before.
- Drag to create/move/resize with a three-zone hit test, previewed
  locally and committed as ONE action on release. Driven off raw pointer
  state rather than an egui Response: the lane sits inside the timeline's
  content response, and a second widget on the same pixels just contests
  hover every frame.
- snap_to_grid/quantize_grid_size take a min_grid_px "visual coarseness"
  parameter instead of a hardcoded constant, with two named profiles:
  SNAP_PX_FINE for the playhead and clip edges, SNAP_PX_CYCLE (coarser)
  for the cycle region, so loops land on bars rather than odd
  subdivisions.
- Cycle toggle button using the Lucide repeat glyph.

Cargo.lock picks up the 1.0.9-alpha version bump it missed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 02:13:06 -04:00
..
assets/fonts Add text layers 2026-06-27 18:14:47 -04:00
src Cycle recording phase 1: transport loop region 2026-07-13 02:13:06 -04:00
tests Repair test suite + fix sample key-range overlap bug 2026-06-23 19:07:06 -04:00
Cargo.toml SVG export: emit text layers as real glyph outlines 2026-07-09 07:10:59 -04:00