use super::*;
impl Maolan {
pub(super) fn handle_response_timing_state_action(&mut self, action: &Action) -> bool {
match action {
Action::Play => {
self.playing = true;
self.paused = false;
self.last_playback_tick = Some(Instant::now());
true
}
Action::Pause => {
self.playing = true;
self.paused = true;
self.last_playback_tick = None;
true
}
Action::Stop => {
self.playing = false;
self.paused = false;
self.last_playback_tick = None;
self.track_automation_runtime.clear();
self.touch_automation_overrides.clear();
self.touch_active_keys.clear();
self.latch_automation_overrides.clear();
true
}
Action::BeginSessionRestore => {
self.session_restore_in_progress = true;
self.last_autosave_snapshot = None;
self.pending_autosave_recovery = None;
self.pending_open_session_dir = None;
self.state.blocking_write().undo_track_indices.clear();
true
}
Action::EndSessionRestore => {
self.session_restore_in_progress = false;
self.last_autosave_snapshot = None;
self.pending_autosave_recovery = None;
self.pending_open_session_dir = None;
true
}
Action::TransportPosition(sample) => {
self.pending_transport_position = None;
self.transport_samples = *sample as f64;
if self.playing && !self.paused {
self.last_playback_tick = Some(Instant::now());
}
if !self.playing && self.recording_preview_start_sample.is_some() {
self.stop_recording_preview();
}
true
}
Action::TransportPositionAt {
sample,
after_frames,
} => {
if self.playing && !self.paused {
let delay_s = *after_frames as f64 / self.playback_rate_hz.max(1.0);
self.pending_transport_position =
Some((Instant::now() + Duration::from_secs_f64(delay_s), *sample));
} else {
self.transport_samples = *sample as f64;
self.pending_transport_position = None;
}
true
}
Action::SetLoopEnabled(enabled) => {
self.loop_enabled = *enabled && self.loop_range_samples.is_some();
self.pending_transport_position = None;
true
}
Action::SetLoopRange(range) => {
self.loop_range_samples = *range;
self.loop_enabled = range.is_some();
self.pending_transport_position = None;
true
}
Action::SetPunchEnabled(enabled) => {
self.punch_enabled = *enabled && self.punch_range_samples.is_some();
true
}
Action::SetPunchRange(range) => {
self.punch_range_samples = *range;
self.punch_enabled = range.is_some();
true
}
Action::SetMetronomeEnabled(enabled) => {
self.metronome_enabled = *enabled;
self.state.blocking_write().metronome_enabled = *enabled;
true
}
Action::SetTempo(bpm) => {
let bpm = (*bpm as f32).clamp(20.0, 300.0);
let mut state = self.state.blocking_write();
let (base_bpm, _, _) = Self::timing_at_sample(&state, 0);
state.tempo = base_bpm;
self.tempo_input = format!("{:.2}", bpm);
self.last_sent_tempo_bpm = Some(bpm as f64);
true
}
Action::SetTimeSignature {
numerator,
denominator,
} => {
let mut state = self.state.blocking_write();
let incoming_num = (*numerator).clamp(1, 16) as u8;
let incoming_den = match *denominator {
2 => 2,
4 => 4,
8 => 8,
16 => 16,
_ => 4,
};
let (_, base_num, base_den) = Self::timing_at_sample(&state, 0);
state.time_signature_num = base_num;
state.time_signature_denom = base_den;
self.time_signature_num_input = incoming_num.to_string();
self.time_signature_denom_input = incoming_den.to_string();
self.last_sent_time_signature = Some((incoming_num as u16, incoming_den as u16));
true
}
Action::SetTempoMap {
tempo_points,
time_signature_points,
} => {
let mut state = self.state.blocking_write();
state.tempo_points = tempo_points
.iter()
.map(|p| crate::state::TempoPoint {
sample: p.sample,
bpm: p.bpm as f32,
})
.collect();
state.time_signature_points = time_signature_points
.iter()
.map(|p| crate::state::TimeSignaturePoint {
sample: p.sample,
numerator: p.numerator as u8,
denominator: p.denominator as u8,
})
.collect();
let (base_bpm, base_num, base_den) = Self::timing_at_sample(&state, 0);
state.tempo = base_bpm;
state.time_signature_num = base_num;
state.time_signature_denom = base_den;
self.tempo_input = format!("{:.2}", base_bpm);
self.time_signature_num_input = base_num.to_string();
self.time_signature_denom_input = base_den.to_string();
self.last_sent_tempo_bpm = Some(base_bpm as f64);
self.last_sent_time_signature = Some((base_num as u16, base_den as u16));
true
}
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gui::TrackAutomationRuntime;
use std::collections::{HashMap, HashSet};
#[test]
fn play_response_sets_transport_running_state() {
let mut app = Maolan {
paused: true,
..Maolan::default()
};
assert!(app.handle_response_timing_state_action(&Action::Play));
assert!(app.playing);
assert!(!app.paused);
assert!(app.last_playback_tick.is_some());
}
#[test]
fn stop_response_clears_transport_and_automation_runtime_state() {
let mut app = Maolan {
playing: true,
paused: true,
recording_preview_start_sample: Some(32),
recording_preview_sample: Some(64),
..Maolan::default()
};
app.last_playback_tick = Some(Instant::now());
app.track_automation_runtime
.insert("Track".to_string(), TrackAutomationRuntime::default());
app.touch_automation_overrides
.insert("Track".to_string(), HashMap::new());
app.touch_active_keys
.insert("Track".to_string(), HashSet::new());
app.latch_automation_overrides
.insert("Track".to_string(), HashMap::new());
assert!(app.handle_response_timing_state_action(&Action::Stop));
assert!(!app.playing);
assert!(!app.paused);
assert!(app.last_playback_tick.is_none());
assert!(app.track_automation_runtime.is_empty());
assert!(app.touch_automation_overrides.is_empty());
assert!(app.touch_active_keys.is_empty());
assert!(app.latch_automation_overrides.is_empty());
assert!(app.recording_preview_start_sample.is_some());
assert!(app.recording_preview_sample.is_some());
}
#[test]
fn transport_position_when_stopped_clears_recording_preview() {
let mut app = Maolan {
playing: false,
recording_preview_start_sample: Some(32),
recording_preview_sample: Some(64),
..Maolan::default()
};
assert!(app.handle_response_timing_state_action(&Action::TransportPosition(0)));
assert!(app.recording_preview_start_sample.is_none());
assert!(app.recording_preview_sample.is_none());
}
#[test]
fn stopped_transport_position_response_updates_visible_position() {
let mut app = Maolan {
transport_samples: 128.0,
..Maolan::default()
};
assert!(app.handle_response_timing_state_action(&Action::TransportPosition(0)));
assert_eq!(app.transport_samples, 0.0);
assert!(app.last_playback_tick.is_none());
}
}