use crate::engine::PatternChange; use crate::model::{FollowUp, LaunchQuantization, PatternSpeed, SyncMode}; use std::collections::{HashMap, HashSet}; #[derive(Clone)] pub struct StagedChange { pub change: PatternChange, pub quantization: LaunchQuantization, pub sync_mode: SyncMode, } #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum StagedMuteChange { ToggleMute { bank: usize, pattern: usize }, ToggleSolo { bank: usize, pattern: usize }, } pub struct StagedPropChange { pub name: Option, pub description: Option, pub length: Option, pub speed: PatternSpeed, pub quantization: LaunchQuantization, pub sync_mode: SyncMode, pub follow_up: FollowUp, } pub struct PlaybackState { pub playing: bool, pub staged_changes: Vec, pub queued_changes: Vec, pub staged_mute_changes: HashSet, pub staged_prop_changes: HashMap<(usize, usize), StagedPropChange>, pub muted: HashSet<(usize, usize)>, pub soloed: HashSet<(usize, usize)>, } impl Default for PlaybackState { fn default() -> Self { Self { playing: true, staged_changes: Vec::new(), queued_changes: Vec::new(), staged_mute_changes: HashSet::new(), staged_prop_changes: HashMap::new(), muted: HashSet::new(), soloed: HashSet::new(), } } } impl PlaybackState { pub fn toggle(&mut self) { self.playing = !self.playing; } pub fn clear_queues(&mut self) { self.staged_changes.clear(); self.queued_changes.clear(); self.staged_prop_changes.clear(); } pub fn has_staged_props(&self, bank: usize, pattern: usize) -> bool { self.staged_prop_changes.contains_key(&(bank, pattern)) } pub fn stage_mute(&mut self, bank: usize, pattern: usize) { let change = StagedMuteChange::ToggleMute { bank, pattern }; if self.staged_mute_changes.contains(&change) { self.staged_mute_changes.remove(&change); } else { self.staged_mute_changes.insert(change); } } pub fn stage_solo(&mut self, bank: usize, pattern: usize) { let change = StagedMuteChange::ToggleSolo { bank, pattern }; if self.staged_mute_changes.contains(&change) { self.staged_mute_changes.remove(&change); } else { self.staged_mute_changes.insert(change); } } pub fn clear_staged_mutes(&mut self) { self.staged_mute_changes.retain(|c| !matches!(c, StagedMuteChange::ToggleMute { .. })); } pub fn clear_staged_solos(&mut self) { self.staged_mute_changes.retain(|c| !matches!(c, StagedMuteChange::ToggleSolo { .. })); } pub fn clear_mutes(&mut self) { self.clear_staged_mutes(); self.muted.clear(); } pub fn clear_solos(&mut self) { self.clear_staged_solos(); self.soloed.clear(); } pub fn has_staged_mute(&self, bank: usize, pattern: usize) -> bool { self.staged_mute_changes.contains(&StagedMuteChange::ToggleMute { bank, pattern }) } pub fn has_staged_solo(&self, bank: usize, pattern: usize) -> bool { self.staged_mute_changes.contains(&StagedMuteChange::ToggleSolo { bank, pattern }) } pub fn toggle_mute(&mut self, bank: usize, pattern: usize) { let key = (bank, pattern); if !self.muted.remove(&key) { self.muted.insert(key); } } pub fn toggle_solo(&mut self, bank: usize, pattern: usize) { let key = (bank, pattern); if !self.soloed.remove(&key) { self.soloed.insert(key); } } pub fn is_muted(&self, bank: usize, pattern: usize) -> bool { self.muted.contains(&(bank, pattern)) } pub fn is_soloed(&self, bank: usize, pattern: usize) -> bool { self.soloed.contains(&(bank, pattern)) } pub fn has_armed(&self) -> bool { !self.staged_changes.is_empty() || !self.staged_mute_changes.is_empty() || !self.staged_prop_changes.is_empty() } pub fn armed_summary(&self) -> Option { let play = self.staged_changes.iter().filter(|c| matches!(c.change, PatternChange::Start { .. })).count(); let stop = self.staged_changes.iter().filter(|c| matches!(c.change, PatternChange::Stop { .. })).count(); let mute = self.staged_mute_changes.iter().filter(|c| matches!(c, StagedMuteChange::ToggleMute { .. })).count(); let solo = self.staged_mute_changes.iter().filter(|c| matches!(c, StagedMuteChange::ToggleSolo { .. })).count(); let props = self.staged_prop_changes.len(); let parts: Vec = [ (play, "play"), (stop, "stop"), (mute, "mute"), (solo, "solo"), (props, "props"), ] .into_iter() .filter(|(n, _)| *n > 0) .map(|(n, label)| format!("{n} {label}")) .collect(); if parts.is_empty() { None } else { Some(parts.join(", ")) } } pub fn is_effectively_muted(&self, bank: usize, pattern: usize) -> bool { if self.muted.contains(&(bank, pattern)) { return true; } !self.soloed.is_empty() && !self.soloed.contains(&(bank, pattern)) } }