//! Project, Bank, Pattern, and Step structs with serialization. use std::path::PathBuf; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::{DEFAULT_LENGTH, MAX_BANKS, MAX_PATTERNS, MAX_STEPS}; /// Speed multiplier for a pattern, expressed as a rational fraction. #[derive(Clone, Copy, PartialEq, Eq)] pub struct PatternSpeed { pub num: u8, pub denom: u8, } impl PatternSpeed { pub const EIGHTH: Self = Self { num: 1, denom: 8 }; pub const FIFTH: Self = Self { num: 1, denom: 5 }; pub const QUARTER: Self = Self { num: 1, denom: 4 }; pub const THIRD: Self = Self { num: 1, denom: 3 }; pub const HALF: Self = Self { num: 1, denom: 2 }; pub const TWO_THIRDS: Self = Self { num: 2, denom: 3 }; pub const NORMAL: Self = Self { num: 1, denom: 1 }; pub const DOUBLE: Self = Self { num: 2, denom: 1 }; pub const QUAD: Self = Self { num: 4, denom: 1 }; pub const OCTO: Self = Self { num: 8, denom: 1 }; const PRESETS: &[Self] = &[ Self::EIGHTH, Self::FIFTH, Self::QUARTER, Self::THIRD, Self::HALF, Self::TWO_THIRDS, Self::NORMAL, Self::DOUBLE, Self::QUAD, Self::OCTO, ]; /// Return the speed as a floating-point multiplier. pub fn multiplier(&self) -> f64 { self.num as f64 / self.denom as f64 } /// Format as a human-readable label (e.g. "2x", "1/4x"). pub fn label(&self) -> String { if self.denom == 1 { format!("{}x", self.num) } else { format!("{}/{}x", self.num, self.denom) } } /// Return the next faster preset, or self if already at maximum. pub fn next(&self) -> Self { let current = self.multiplier(); Self::PRESETS .iter() .find(|p| p.multiplier() > current + 0.0001) .copied() .unwrap_or(*self) } /// Return the next slower preset, or self if already at minimum. pub fn prev(&self) -> Self { let current = self.multiplier(); Self::PRESETS .iter() .rev() .find(|p| p.multiplier() < current - 0.0001) .copied() .unwrap_or(*self) } /// Parse a speed label like "2x" or "1/4x" into a `PatternSpeed`. pub fn from_label(s: &str) -> Option { let s = s.trim().trim_end_matches('x'); if let Some((num, denom)) = s.split_once('/') { let num: u8 = num.parse().ok()?; let denom: u8 = denom.parse().ok()?; if denom == 0 { return None; } return Some(Self { num, denom }); } if let Ok(val) = s.parse::() { if val <= 0.0 || val > 255.0 { return None; } if (val - val.round()).abs() < 0.0001 { return Some(Self { num: val.round() as u8, denom: 1, }); } for denom in 1..=16u8 { let num = val * denom as f64; if (num - num.round()).abs() < 0.0001 && (1.0..=255.0).contains(&num) { return Some(Self { num: num.round() as u8, denom, }); } } } None } } impl Default for PatternSpeed { fn default() -> Self { Self::NORMAL } } impl Serialize for PatternSpeed { fn serialize(&self, serializer: S) -> Result { (self.num, self.denom).serialize(serializer) } } impl<'de> Deserialize<'de> for PatternSpeed { fn deserialize>(deserializer: D) -> Result { #[derive(Deserialize)] #[serde(untagged)] enum SpeedFormat { Tuple((u8, u8)), Legacy(String), } match SpeedFormat::deserialize(deserializer)? { SpeedFormat::Tuple((num, denom)) => Ok(Self { num, denom }), SpeedFormat::Legacy(s) => Ok(match s.as_str() { "Eighth" => Self::EIGHTH, "Quarter" => Self::QUARTER, "Half" => Self::HALF, "Normal" => Self::NORMAL, "Double" => Self::DOUBLE, "Quad" => Self::QUAD, "Octo" => Self::OCTO, _ => Self::NORMAL, }), } } } /// Quantization grid for launching patterns. #[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)] pub enum LaunchQuantization { Immediate, Beat, #[default] Bar, Bars2, Bars4, Bars8, } impl LaunchQuantization { /// Human-readable label for display. pub fn label(&self) -> &'static str { match self { Self::Immediate => "Immediate", Self::Beat => "Beat", Self::Bar => "1 Bar", Self::Bars2 => "2 Bars", Self::Bars4 => "4 Bars", Self::Bars8 => "8 Bars", } } pub fn short_label(&self) -> &'static str { match self { Self::Immediate => "Imm", Self::Beat => "Bt", Self::Bar => "1B", Self::Bars2 => "2B", Self::Bars4 => "4B", Self::Bars8 => "8B", } } /// Cycle to the next longer quantization, clamped at `Bars8`. pub fn next(&self) -> Self { match self { Self::Immediate => Self::Beat, Self::Beat => Self::Bar, Self::Bar => Self::Bars2, Self::Bars2 => Self::Bars4, Self::Bars4 => Self::Bars8, Self::Bars8 => Self::Bars8, } } /// Cycle to the next shorter quantization, clamped at `Immediate`. pub fn prev(&self) -> Self { match self { Self::Immediate => Self::Immediate, Self::Beat => Self::Immediate, Self::Bar => Self::Beat, Self::Bars2 => Self::Bar, Self::Bars4 => Self::Bars2, Self::Bars8 => Self::Bars4, } } } /// How a pattern synchronizes when launched: restart or phase-lock. #[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)] pub enum SyncMode { #[default] Reset, PhaseLock, } impl SyncMode { /// Human-readable label for display. pub fn label(&self) -> &'static str { match self { Self::Reset => "Reset", Self::PhaseLock => "Phase-Lock", } } pub fn short_label(&self) -> &'static str { match self { Self::Reset => "Rst", Self::PhaseLock => "Plk", } } /// Toggle between Reset and PhaseLock. pub fn toggle(&self) -> Self { match self { Self::Reset => Self::PhaseLock, Self::PhaseLock => Self::Reset, } } } /// What happens when a pattern finishes: loop, stop, or chain to another. #[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)] pub enum FollowUp { #[default] Loop, Stop, Chain { bank: usize, pattern: usize }, } impl FollowUp { /// Human-readable label for display. pub fn label(&self) -> &'static str { match self { Self::Loop => "Loop", Self::Stop => "Stop", Self::Chain { .. } => "Chain", } } /// Cycle forward through follow-up modes. pub fn next_mode(&self) -> Self { match self { Self::Loop => Self::Stop, Self::Stop => Self::Chain { bank: 0, pattern: 0 }, Self::Chain { .. } => Self::Loop, } } /// Cycle backward through follow-up modes. pub fn prev_mode(&self) -> Self { match self { Self::Loop => Self::Chain { bank: 0, pattern: 0 }, Self::Stop => Self::Loop, Self::Chain { .. } => Self::Stop, } } } fn is_default_follow_up(f: &FollowUp) -> bool { *f == FollowUp::default() } /// Single step in a pattern, holding a Forth script and optional metadata. #[derive(Clone, Serialize, Deserialize)] pub struct Step { pub active: bool, pub script: String, #[serde(default)] pub source: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub name: Option, } impl Step { /// True if all fields are at their default values. pub fn is_default(&self) -> bool { self.active && self.script.is_empty() && self.source.is_none() && self.name.is_none() } /// True if the script is non-empty. pub fn has_content(&self) -> bool { !self.script.is_empty() } } impl Default for Step { fn default() -> Self { Self { active: true, script: String::new(), source: None, name: None, } } } /// Sequence of steps with playback settings (speed, quantization, sync, follow-up). #[derive(Clone)] pub struct Pattern { pub steps: Vec, pub length: usize, pub speed: PatternSpeed, pub name: Option, pub description: Option, pub quantization: LaunchQuantization, pub sync_mode: SyncMode, pub follow_up: FollowUp, } #[derive(Serialize, Deserialize)] struct SparseStep { i: usize, #[serde(default = "default_active", skip_serializing_if = "is_true")] active: bool, #[serde(default, skip_serializing_if = "String::is_empty")] script: String, #[serde(default, skip_serializing_if = "Option::is_none")] source: Option, #[serde(default, skip_serializing_if = "Option::is_none")] name: Option, } fn default_active() -> bool { true } fn is_true(v: &bool) -> bool { *v } #[derive(Serialize, Deserialize)] struct SparsePattern { steps: Vec, length: usize, #[serde(default)] speed: PatternSpeed, #[serde(default, skip_serializing_if = "Option::is_none")] name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] description: Option, #[serde(default, skip_serializing_if = "is_default_quantization")] quantization: LaunchQuantization, #[serde(default, skip_serializing_if = "is_default_sync_mode")] sync_mode: SyncMode, #[serde(default, skip_serializing_if = "is_default_follow_up")] follow_up: FollowUp, } fn is_default_quantization(q: &LaunchQuantization) -> bool { *q == LaunchQuantization::default() } fn is_default_sync_mode(s: &SyncMode) -> bool { *s == SyncMode::default() } #[derive(Deserialize)] struct LegacyPattern { steps: Vec, length: usize, #[serde(default)] speed: PatternSpeed, #[serde(default)] name: Option, #[serde(default)] description: Option, #[serde(default)] quantization: LaunchQuantization, #[serde(default)] sync_mode: SyncMode, #[serde(default)] follow_up: FollowUp, } impl Serialize for Pattern { fn serialize(&self, serializer: S) -> Result { let sparse_steps: Vec = self .steps .iter() .enumerate() .filter(|(_, step)| !step.is_default()) .map(|(i, step)| SparseStep { i, active: step.active, script: step.script.clone(), source: step.source, name: step.name.clone(), }) .collect(); let sparse = SparsePattern { steps: sparse_steps, length: self.length, speed: self.speed, name: self.name.clone(), description: self.description.clone(), quantization: self.quantization, sync_mode: self.sync_mode, follow_up: self.follow_up, }; sparse.serialize(serializer) } } impl<'de> Deserialize<'de> for Pattern { fn deserialize>(deserializer: D) -> Result { #[derive(Deserialize)] #[serde(untagged)] enum PatternFormat { Sparse(SparsePattern), Legacy(LegacyPattern), } match PatternFormat::deserialize(deserializer)? { PatternFormat::Sparse(sparse) => { let mut steps: Vec = (0..MAX_STEPS).map(|_| Step::default()).collect(); for ss in sparse.steps { if ss.i < MAX_STEPS { steps[ss.i] = Step { active: ss.active, script: ss.script, source: ss.source, name: ss.name, }; } } Ok(Pattern { steps, length: sparse.length, speed: sparse.speed, name: sparse.name, description: sparse.description, quantization: sparse.quantization, sync_mode: sparse.sync_mode, follow_up: sparse.follow_up, }) } PatternFormat::Legacy(legacy) => Ok(Pattern { steps: legacy.steps, length: legacy.length, speed: legacy.speed, name: legacy.name, description: legacy.description, quantization: legacy.quantization, sync_mode: legacy.sync_mode, follow_up: legacy.follow_up, }), } } } impl Default for Pattern { fn default() -> Self { Self { steps: (0..MAX_STEPS).map(|_| Step::default()).collect(), length: DEFAULT_LENGTH, speed: PatternSpeed::default(), name: None, description: None, quantization: LaunchQuantization::default(), sync_mode: SyncMode::default(), follow_up: FollowUp::default(), } } } impl Pattern { /// Borrow a step by index. pub fn step(&self, index: usize) -> Option<&Step> { self.steps.get(index) } /// Mutably borrow a step by index. pub fn step_mut(&mut self, index: usize) -> Option<&mut Step> { self.steps.get_mut(index) } /// Set the active length, clamped to `[1, MAX_STEPS]`. pub fn set_length(&mut self, length: usize) { let length = length.clamp(1, MAX_STEPS); while self.steps.len() < length { self.steps.push(Step::default()); } self.length = length; } /// Follow the source chain from `index` to find the originating step. pub fn resolve_source(&self, index: usize) -> usize { let mut current = index; for _ in 0..self.steps.len() { if let Some(step) = self.steps.get(current) { if let Some(source) = step.source { current = source as usize; } else { return current; } } else { return index; } } index } /// Return the script at the resolved source of `index`. pub fn resolve_script(&self, index: usize) -> Option<&str> { let source_idx = self.resolve_source(index); self.steps.get(source_idx).map(|s| s.script.as_str()) } /// Count active-length steps that have a script or a source reference. pub fn content_step_count(&self) -> usize { self.steps[..self.length] .iter() .filter(|s| s.has_content() || s.source.is_some()) .count() } } /// Collection of patterns forming a bank. #[derive(Clone, Serialize, Deserialize)] pub struct Bank { pub patterns: Vec, #[serde(default)] pub name: Option, } impl Bank { /// Count patterns that contain at least one non-empty step. pub fn content_pattern_count(&self) -> usize { self.patterns .iter() .filter(|p| p.content_step_count() > 0) .count() } } impl Default for Bank { fn default() -> Self { Self { patterns: (0..MAX_PATTERNS).map(|_| Pattern::default()).collect(), name: None, } } } /// Top-level project: banks, tempo, sample paths, and prelude script. #[derive(Clone, Serialize, Deserialize)] pub struct Project { pub banks: Vec, #[serde(default)] pub sample_paths: Vec, #[serde(default = "default_tempo")] pub tempo: f64, #[serde(default)] pub playing_patterns: Vec<(usize, usize)>, #[serde(default)] pub prelude: String, #[serde(default)] pub script: String, #[serde(default)] pub script_speed: PatternSpeed, #[serde(default = "default_script_length")] pub script_length: usize, } fn default_tempo() -> f64 { 120.0 } fn default_script_length() -> usize { 16 } impl Default for Project { fn default() -> Self { Self { banks: (0..MAX_BANKS).map(|_| Bank::default()).collect(), sample_paths: Vec::new(), tempo: default_tempo(), playing_patterns: Vec::new(), prelude: String::new(), script: String::new(), script_speed: PatternSpeed::default(), script_length: default_script_length(), } } } impl Project { /// Borrow a pattern by bank and pattern index. pub fn pattern_at(&self, bank: usize, pattern: usize) -> &Pattern { &self.banks[bank].patterns[pattern] } /// Mutably borrow a pattern by bank and pattern index. pub fn pattern_at_mut(&mut self, bank: usize, pattern: usize) -> &mut Pattern { &mut self.banks[bank].patterns[pattern] } /// Pad banks, patterns, and steps to their maximum sizes after deserialization. pub fn normalize(&mut self) { self.banks.resize_with(MAX_BANKS, Bank::default); for bank in &mut self.banks { bank.patterns.resize_with(MAX_PATTERNS, Pattern::default); for pattern in &mut bank.patterns { pattern.steps.resize_with(MAX_STEPS, Step::default); } } } }