Files
Cagire/crates/project/src/project.rs
2026-01-28 13:54:29 +01:00

354 lines
9.1 KiB
Rust

use std::path::PathBuf;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::{DEFAULT_LENGTH, MAX_BANKS, MAX_PATTERNS, MAX_STEPS};
#[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,
];
pub fn multiplier(&self) -> f64 {
self.num as f64 / self.denom as f64
}
pub fn label(&self) -> String {
if self.denom == 1 {
format!("{}x", self.num)
} else {
format!("{}/{}x", self.num, self.denom)
}
}
pub fn next(&self) -> Self {
let current = self.multiplier();
Self::PRESETS
.iter()
.find(|p| p.multiplier() > current + 0.0001)
.copied()
.unwrap_or(*self)
}
pub fn prev(&self) -> Self {
let current = self.multiplier();
Self::PRESETS
.iter()
.rev()
.find(|p| p.multiplier() < current - 0.0001)
.copied()
.unwrap_or(*self)
}
pub fn from_label(s: &str) -> Option<Self> {
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::<f64>() {
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
(self.num, self.denom).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for PatternSpeed {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[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,
}),
}
}
}
#[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
pub enum LaunchQuantization {
Immediate,
Beat,
#[default]
Bar,
Bars2,
Bars4,
Bars8,
}
impl LaunchQuantization {
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 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,
}
}
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,
}
}
}
#[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
pub enum SyncMode {
#[default]
Reset,
PhaseLock,
}
impl SyncMode {
pub fn label(&self) -> &'static str {
match self {
Self::Reset => "Reset",
Self::PhaseLock => "Phase-Lock",
}
}
pub fn toggle(&self) -> Self {
match self {
Self::Reset => Self::PhaseLock,
Self::PhaseLock => Self::Reset,
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Step {
pub active: bool,
pub script: String,
#[serde(skip)]
pub command: Option<String>,
#[serde(default)]
pub source: Option<usize>,
}
impl Default for Step {
fn default() -> Self {
Self {
active: true,
script: String::new(),
command: None,
source: None,
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Pattern {
pub steps: Vec<Step>,
pub length: usize,
#[serde(default)]
pub speed: PatternSpeed,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub quantization: LaunchQuantization,
#[serde(default)]
pub sync_mode: SyncMode,
}
impl Default for Pattern {
fn default() -> Self {
Self {
steps: (0..MAX_STEPS).map(|_| Step::default()).collect(),
length: DEFAULT_LENGTH,
speed: PatternSpeed::default(),
name: None,
quantization: LaunchQuantization::default(),
sync_mode: SyncMode::default(),
}
}
}
impl Pattern {
pub fn step(&self, index: usize) -> Option<&Step> {
self.steps.get(index)
}
pub fn step_mut(&mut self, index: usize) -> Option<&mut Step> {
self.steps.get_mut(index)
}
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;
}
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;
} else {
return current;
}
} else {
return index;
}
}
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())
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Bank {
pub patterns: Vec<Pattern>,
#[serde(default)]
pub name: Option<String>,
}
impl Default for Bank {
fn default() -> Self {
Self {
patterns: (0..MAX_PATTERNS).map(|_| Pattern::default()).collect(),
name: None,
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Project {
pub banks: Vec<Bank>,
#[serde(default)]
pub sample_paths: Vec<PathBuf>,
#[serde(default = "default_tempo")]
pub tempo: f64,
}
fn default_tempo() -> f64 {
120.0
}
impl Default for Project {
fn default() -> Self {
Self {
banks: (0..MAX_BANKS).map(|_| Bank::default()).collect(),
sample_paths: Vec::new(),
tempo: default_tempo(),
}
}
}
impl Project {
pub fn pattern_at(&self, bank: usize, pattern: usize) -> &Pattern {
&self.banks[bank].patterns[pattern]
}
pub fn pattern_at_mut(&mut self, bank: usize, pattern: usize) -> &mut Pattern {
&mut self.banks[bank].patterns[pattern]
}
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);
}
}
}
}