From b5766672ee353980a3de257e4b2b2f4a37fed9fc Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Sat, 11 Jul 2026 14:10:44 -0400 Subject: [PATCH] Fix recording start position: pass beats, not seconds, to the backend The reported bug (a second recording lands early and overlaps the first) survived the timeline type refactor: start_recording/create_midi_clip/ start_midi_recording took f64 and wrapped Beats(x) internally, so the type boundary stopped at the method and the timeline handed them *shared.playback_time (seconds). At 120 BPM a 5s playhead (=10 beats) was recorded at beat 5 = 2.5s. Type all three backend methods to take Beats so the caller must convert; the timeline now converts the seconds playhead once (start_beats) and passes it to every recording command and the placeholder clip. TUI debug caller updated. Co-Authored-By: Claude Opus 4.8 (1M context) --- daw-backend/src/audio/engine.rs | 12 ++++++------ daw-backend/src/tui/mod.rs | 2 +- .../lightningbeam-editor/src/panes/timeline.rs | 11 ++++++----- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/daw-backend/src/audio/engine.rs b/daw-backend/src/audio/engine.rs index a7dcab5..7581025 100644 --- a/daw-backend/src/audio/engine.rs +++ b/daw-backend/src/audio/engine.rs @@ -3607,10 +3607,10 @@ impl EngineController { } /// Create a new MIDI clip on a track - pub fn create_midi_clip(&mut self, track_id: TrackId, start_time: f64, duration: f64) -> MidiClipId { + pub fn create_midi_clip(&mut self, track_id: TrackId, start_time: Beats, duration: Beats) -> MidiClipId { // Peek at the next clip ID that will be used let clip_id = self.next_midi_clip_id.load(Ordering::Relaxed); - let _ = self.command_tx.push(Command::CreateMidiClip(track_id, start_time, duration)); + let _ = self.command_tx.push(Command::CreateMidiClip(track_id, start_time.beats_to_f64(), duration.beats_to_f64())); clip_id } @@ -3739,8 +3739,8 @@ impl EngineController { } /// Start recording on a track - pub fn start_recording(&mut self, track_id: TrackId, start_time: f64) { - let _ = self.command_tx.push(Command::StartRecording(track_id, Beats(start_time))); + pub fn start_recording(&mut self, track_id: TrackId, start_time: Beats) { + let _ = self.command_tx.push(Command::StartRecording(track_id, start_time)); } /// Stop the current recording @@ -3759,8 +3759,8 @@ impl EngineController { } /// Start MIDI recording on a track - pub fn start_midi_recording(&mut self, track_id: TrackId, clip_id: MidiClipId, start_time: f64) { - let _ = self.command_tx.push(Command::StartMidiRecording(track_id, clip_id, Beats(start_time))); + pub fn start_midi_recording(&mut self, track_id: TrackId, clip_id: MidiClipId, start_time: Beats) { + let _ = self.command_tx.push(Command::StartMidiRecording(track_id, clip_id, start_time)); } /// Stop the current MIDI recording diff --git a/daw-backend/src/tui/mod.rs b/daw-backend/src/tui/mod.rs index 3dc57bc..85d19bc 100644 --- a/daw-backend/src/tui/mod.rs +++ b/daw-backend/src/tui/mod.rs @@ -830,7 +830,7 @@ fn execute_command( app.next_clip_id += 1; app.add_clip(track_id, clip_id, start_time, duration, format!("Clip {}", clip_id), Vec::new()); - controller.create_midi_clip(track_id, start_time, duration); + controller.create_midi_clip(track_id, crate::Beats(start_time), crate::Beats(duration)); app.set_status(format!("Created MIDI clip on track {} at {:.2}s for {:.2}s", track_id, start_time, duration)); } "loadmidi" => { diff --git a/lightningbeam-ui/lightningbeam-editor/src/panes/timeline.rs b/lightningbeam-ui/lightningbeam-editor/src/panes/timeline.rs index b67ffe2..8274f1d 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/panes/timeline.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/panes/timeline.rs @@ -1127,6 +1127,9 @@ impl TimelinePane { shared.recording_layer_ids.clear(); + // The backend records in the beats domain; start_time is the seconds playhead. + let start_beats = shared.action_executor.document().tempo_map().seconds_to_beats(Seconds(start_time)); + // Step 4: Dispatch recording for each candidate for &(layer_id, ref cat, _) in &candidates { match cat { @@ -1150,7 +1153,7 @@ impl TimelinePane { } if let Some(controller_arc) = shared.audio_controller { let mut controller = controller_arc.lock().unwrap(); - controller.start_recording(track_id, start_time); + controller.start_recording(track_id, start_beats); println!("🎤 Started audio recording on track {:?} at {:.2}s", track_id, start_time); } shared.recording_layer_ids.push(layer_id); @@ -1162,8 +1165,8 @@ impl TimelinePane { if let Some(&track_id) = shared.layer_to_track_map.get(&layer_id) { if let Some(controller_arc) = shared.audio_controller { let mut controller = controller_arc.lock().unwrap(); - let clip_id = controller.create_midi_clip(track_id, start_time, 0.0); - controller.start_midi_recording(track_id, clip_id, start_time); + let clip_id = controller.create_midi_clip(track_id, start_beats, Beats::ZERO); + controller.start_midi_recording(track_id, clip_id, start_beats); shared.recording_clips.insert(layer_id, clip_id); println!("🎹 Started MIDI recording on track {:?} at {:.2}s, clip_id={}", track_id, start_time, clip_id); @@ -1174,8 +1177,6 @@ impl TimelinePane { *shared.recording_clips.get(&layer_id).unwrap_or(&0), daw_backend::Beats::ZERO); let doc_clip_id = shared.action_executor.document_mut().add_audio_clip(doc_clip); - let start_beats = shared.action_executor.document().tempo_map() - .seconds_to_beats(Seconds(start_time)); let clip_instance = ClipInstance::new(doc_clip_id) .with_timeline_start(start_beats);