Files
bass-foundry/plugins/crucible/core/src/settings.rs
T
nvrl 6d868a63c4 feat(crucible): keep every captured take
With Keep takes on (default), each capture is written to
<folder>/takes/ as a WAV named by time, tempo and length, so a take
is not lost when the next capture replaces it. Files are never
overwritten: a clash gets a numbered suffix. Saving runs off the UI
thread and the status line shows the path or the error.
2026-11-06 12:24:10 +01:00

124 lines
3.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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"),
}
}