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) <noreply@anthropic.com>
This commit is contained in:
Skyler Lehmkuhl 2026-07-11 14:10:44 -04:00
parent 0050c18623
commit b5766672ee
3 changed files with 13 additions and 12 deletions

View File

@ -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

View File

@ -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" => {

View File

@ -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);