phosphor-tui 0.3.5

Terminal UI frontend for the Phosphor DAW
Documentation
//! App methods: transport.

use super::*;

impl App {
    /// Stop playback and silence all instruments. Called on pause, stop,
    /// and stop-recording. Prevents notes from ringing after playback ends.
    pub(crate) fn stop_playback(&mut self) {
        let was_recording = self.engine.transport.is_recording();
        self.engine.transport.pause();
        if was_recording {
            self.nav.recording_grace = self.nav.tracks.iter().filter(|t| t.armed).count();
        }
        self.engine.panic();
    }


    pub(crate) fn sync_loop_to_transport(&self) {
        use crate::debug_log as dbg;
        let le = &self.nav.loop_editor;
        self.engine.transport.set_loop_range(le.start_ticks(), le.end_ticks());
        if le.enabled != self.engine.transport.is_looping() {
            self.engine.transport.toggle_loop();
        }
        dbg::system(&format!(
            "loop sync: editor_enabled={} transport_looping={} range={}..{} ticks (bars {})",
            le.enabled, self.engine.transport.is_looping(),
            le.start_ticks(), le.end_ticks(), le.display(),
        ));
    }


    pub(crate) fn log_transport_state(&self) {
        use crate::debug_log as dbg;
        let t = &self.engine.transport;
        dbg::transport(
            t.is_playing(), t.is_recording(), t.is_looping(),
            t.position_ticks(), t.loop_start(), t.loop_end(),
        );
    }

    /// Toggle loop recording on the current track.
    /// First press: arms track, sets loop range, rewinds, starts record+play.
    /// Second press: stops recording, commits clip.

    /// Toggle loop recording on the current track.
    /// First press: arms track, sets loop range, rewinds, starts record+play.
    /// Second press: stops recording, commits clip.
    pub(crate) fn toggle_loop_record(&mut self) {
        let is_recording = self.engine.transport.is_recording()
            && self.engine.transport.is_playing();

        if is_recording {
            self.engine.transport.stop_loop_record();
            self.nav.recording_grace = self.nav.tracks.iter().filter(|t| t.armed).count();
            self.engine.panic(); // silence all notes
        } else {
            // Make sure current track is armed and has a synth
            if let Some(track) = self.nav.tracks.get(self.nav.track_cursor) {
                if !track.is_live() {
                    tracing::info!("Cannot record on a non-instrument track");
                    return;
                }
            } else {
                return;
            }

            // Arm the track if not already
            if let Some(track) = self.nav.tracks.get_mut(self.nav.track_cursor) {
                track.armed = true;
                track.sync_to_audio();
            }

            // Ensure this track is selected for MIDI
            self.nav.show_current_track_controls();

            // Sync loop range from editor to transport, then start
            self.sync_loop_to_transport();
            self.engine.transport.start_loop_record();
            tracing::info!(
                "Loop recording started: bars {}..{} (ticks {}..{})",
                self.engine.transport.loop_start() / (Transport::PPQ * 4) + 1,
                self.engine.transport.loop_end() / (Transport::PPQ * 4),
                self.engine.transport.loop_start(),
                self.engine.transport.loop_end(),
            );
        }
    }
}