Lightningbeam/lightningbeam-ui/lightningbeam-editor/src
Skyler Lehmkuhl a5cdbfd0fd Type the time domains: no raw f64 in any time-carrying API
Three bugs in a row came from the same root: a time value crossing an API
boundary as a bare f64, with the caller and the callee disagreeing about
whether it meant seconds or beats. Recording landed at the wrong time,
MIDI clips grew too fast, and a 1-second split played back as half a
second. Each was "obviously" one domain at the call site and read as the
other on the far side. This makes the mismatch a compile error.

Backend API — every time-carrying f64 is gone:
- Commands: Seek/SetOffset/SetTrimStart/SetTrimEnd -> Seconds; MoveClip/
  ExtendClip/CreateMidiClip/AddMidiNote/AddLoadedMidiClip/
  UpdateMidiClipNotes/AddMidiClipSync and all four automation commands ->
  Beats; TrimClip -> TrimRange.
- Events/queries: PlaybackPosition, WaveformChunksReady's time range,
  AudioFileReady::duration, PoolFileInfo, get_playhead_seconds -> Seconds.
- Serialized: MidiClipData::duration and AutomationKeyframeData::time ->
  Beats. Both newtypes are #[serde(transparent)], so the .beam on-disk
  format is unchanged.
- Several controller methods ALREADY took Beats and unwrapped it to shove
  into the command — the newtype was being discarded at the very boundary
  it existed to protect.

TrimRange, for the domain-polymorphic case: a clip's content time is
SECONDS for sampled audio but BEATS for MIDI, so a single newtype can't
express it (there was even a comment in engine.rs saying so, and that
rationalization is what let the bug through). A domain-tagged enum can.
The engine rejects a range whose domain doesn't match the track, and the
range is built from the clip (clip.trim_range()) so callers can't pick
the wrong variant.

ContentTime, for the trim fields: ClipInstance::trim_start/trim_end are
content times, and were the last untyped f64 — the actual root of the
split bug. ContentTime is deliberately a DEAD END: no .to_seconds(), no
.to_beats(), no arithmetic with Seconds or Beats. Content times combine
freely with each other (same clip, same domain — safe), so the ~100
passthrough sites cost nothing; the only exit is resolving against the
clip that knows the domain (AudioClip::resolve_content_time /
Document::resolve_content_time / ClipDuration::same_domain). Mixing
domains no longer compiles.

Two more live bugs the types surfaced:
- ClipInstance::effective_duration_beats took a SECONDS clip duration and
  subtracted trim_start from it. For a TRIMMED MIDI clip that subtracted
  a beats offset from a seconds duration, so the clip's timeline length
  was wrong at any tempo but 60 BPM. Untrimmed clips happened to work,
  which is why it hid. It now takes a ClipDuration and resolves in the
  clip's own domain: beats content carries over directly (tempo-
  invariant), wall-clock content converts at the clip's position.
  Regression test asserts a clip trimmed to beats 2..6 is 4 beats long at
  60/90/120 BPM.
- Trim validation clamped a content-domain trim against a wall-clock gap.
  gap_to_content/content_to_secs now convert at the clip's position.

Also folds two more copies of the backend add-logic into
BackendContext::add_clip_instance (split and remove_clip_instances both
re-add clips), so the trim/duration conversions live in exactly one place
instead of four.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 09:31:04 -04:00
..
export Type the time domains: no raw f64 in any time-carrying API 2026-07-14 09:31:04 -04:00
mobile Cycle recording phase 1: transport loop region 2026-07-13 02:13:06 -04:00
panes Type the time domains: no raw f64 in any time-carrying API 2026-07-14 09:31:04 -04:00
preferences Clean up build warnings 2026-07-09 13:24:32 -04:00
tools Add drawing tablet input support 2026-03-11 10:58:30 -04:00
widgets make sample load menus consistent 2026-02-16 19:12:21 -05:00
config.rs Fix FLAC export end-to-end (real backend path) + smart tag defaults 2026-07-09 13:09:54 -04:00
cqt_gpu.rs Add clip split and duplicate commands 2026-02-15 02:11:57 -05:00
curve_editor.rs Clean up build warnings 2026-06-21 16:48:59 -04:00
custom_cursor.rs Add tool skeletons 2026-03-06 07:22:50 -05:00
debug_overlay.rs Composite grouped/nested video on the GPU path 2026-06-22 17:38:30 -04:00
default_instrument.rs clean up some compiler warnings 2025-12-03 06:39:26 -05:00
effect_thumbnails.rs Fix gamma handling and improve brush canvas performance 2026-06-16 08:32:39 -04:00
gpu_brush.rs Address code-review findings across export, decode, and data model 2026-06-26 17:47:32 -04:00
gpu_timer.rs Composite grouped/nested video on the GPU path 2026-06-22 17:38:30 -04:00
hw_video.rs hw: surface import_raw failures (diagnose washed-out 10-bit HDR) 2026-06-26 03:59:39 -04:00
keymap.rs Onion skinning (raster): toggle + tinted ghosts of the active layer 2026-06-20 23:05:57 -04:00
main.rs Type the time domains: no raw f64 in any time-carrying API 2026-07-14 09:31:04 -04:00
menu.rs Onion skinning (raster): toggle + tinted ghosts of the active layer 2026-06-20 23:05:57 -04:00
notifications.rs Address code-review findings across export, decode, and data model 2026-06-26 17:47:32 -04:00
nv12_blit.rs nv12: HDR-correct input — PQ/HLG EOTF + BT.2020→709 gamut (Stage A pt 1) 2026-06-26 02:47:09 -04:00
raster_tool.rs Clean up build warnings 2026-06-21 16:48:59 -04:00
sample_import.rs Repair test suite + fix sample key-range overlap bug 2026-06-23 19:07:06 -04:00
sample_import_dialog.rs Fit modal dialogs to the phone screen on mobile 2026-07-01 06:28:04 -04:00
svg_import.rs Address code-review findings across export, decode, and data model 2026-06-26 17:47:32 -04:00
tablet.rs fix build issues 2026-06-02 13:41:10 -04:00
test_mode.rs Clean up build warnings 2026-06-21 16:48:59 -04:00
theme.rs Unify colors via theme CSS variables; theme egui visuals 2026-07-01 07:08:00 -04:00
theme_render.rs Clean up build warnings 2026-06-21 16:48:59 -04:00
waveform_gpu.rs Stream audio & video from .beam container; waveform LOD pyramid + persistence 2026-06-17 13:52:38 -04:00