//! Persisted editor choices. Takes are not part of plugin state; exported files are. use serde::{Deserialize, Serialize}; use std::path::PathBuf; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] pub enum Mode { /// Cut on the bar/beat grid. #[default] Grid, /// Cut where a new hit starts. Transient, } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] pub enum Division { Quarter, Eighth, #[default] Sixteenth, EighthTriplet, SixteenthTriplet, } impl Division { pub const ALL: [Division; 5] = [ Division::Quarter, Division::Eighth, Division::Sixteenth, Division::EighthTriplet, Division::SixteenthTriplet, ]; /// Slices per quarter note. pub fn per_quarter(self) -> u32 { match self { Division::Quarter => 1, Division::Eighth => 2, Division::Sixteenth => 4, Division::EighthTriplet => 3, Division::SixteenthTriplet => 6, } } pub fn label(self) -> &'static str { match self { Division::Quarter => "1/4", Division::Eighth => "1/8", Division::Sixteenth => "1/16", Division::EighthTriplet => "1/8T", Division::SixteenthTriplet => "1/16T", } } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(default)] pub struct Settings { pub bars: u32, pub mode: Mode, pub division: Division, /// Transient sensitivity, 0–100 %. pub sensitivity: f32, /// Fade length at both slice edges in milliseconds. pub declick: f32, /// Peak-normalise each slice to −0.3 dBFS. pub normalize: bool, /// Remove leading silence below −60 dBFS. pub trim: bool, /// Also write a wavetable from the selected slice. pub wavetable: bool, /// Save every captured take's loop to the `takes` subfolder at once. pub keep_takes: bool, /// Export folder; empty selects the default. pub folder: String, } impl Default for Settings { fn default() -> Self { Self { bars: 4, mode: Mode::Grid, division: Division::Sixteenth, sensitivity: 50., declick: 2., normalize: true, trim: true, wavetable: false, keep_takes: true, folder: String::new(), } } } impl Settings { pub const BARS: [u32; 5] = [1, 2, 4, 8, 16]; /// Clamps values from old or hand-edited state into their ranges. pub fn sanitize(&mut self) { if !Self::BARS.contains(&self.bars) { self.bars = 4; } self.sensitivity = if self.sensitivity.is_finite() { self.sensitivity.clamp(0., 100.) } else { 50. }; self.declick = if self.declick.is_finite() { self.declick.clamp(0., 20.) } else { 2. }; } pub fn folder(&self) -> PathBuf { if !self.folder.trim().is_empty() { return PathBuf::from(self.folder.trim()); } default_folder() } } pub fn default_folder() -> PathBuf { if cfg!(windows) { if let Some(home) = std::env::var_os("USERPROFILE") { return PathBuf::from(home).join("Documents").join("Crucible"); } } match std::env::var_os("HOME") { Some(home) => PathBuf::from(home).join("Crucible"), None => PathBuf::from("Crucible"), } }