Check and Windows preview / check (anvil) (push) Successful in 2m8s
Check and Windows preview / check (crucible) (push) Successful in 1m32s
Check and Windows preview / check (lathe) (push) Successful in 2m13s
Check and Windows preview / check (patina) (push) Successful in 2m5s
Check and Windows preview / check (rivet) (push) Successful in 1m34s
Check and Windows preview / check (temper) (push) Successful in 2m12s
Check and Windows preview / previews (push) Successful in 1m19s
Check and Windows preview / windows (push) Successful in 2m13s
Independent plugin release / publish (push) Successful in 8s
Independent plugin release / plan (push) Successful in 6s
Independent plugin release / build (push) Successful in 2m57s
Lathe places EQ bands on harmonics 1-32 of the played note, so a cut on harmonic 3 stays on harmonic 3 on every note. Temper normalises each note's level before an oversampled shaper and tunes its floor, push and bright filters to the note, so every note distorts the same. The note tracker moves from Anvil into common-dsp, together with a state-variable filter, 4x oversampler and harmonic meter. Anvil's behaviour and editor render are unchanged. docs/ideas.md collects all plugin ideas and their status.
107 lines
3.4 KiB
Rust
107 lines
3.4 KiB
Rust
//! Host-independent controls. Gains in dB, times in milliseconds.
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Editable harmonics: the fundamental is harmonic 1.
|
|
pub const HARMONICS: usize = 32;
|
|
pub const GAIN_MIN: f32 = -24.;
|
|
pub const GAIN_MAX: f32 = 12.;
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
|
|
pub enum Source {
|
|
/// Pitch and note starts are detected from the incoming bass.
|
|
#[default]
|
|
Audio,
|
|
/// Notes come from MIDI note input.
|
|
Midi,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct Settings {
|
|
pub source: Source,
|
|
/// Gain per harmonic, index 0 = fundamental.
|
|
pub gains: [f32; HARMONICS],
|
|
/// dB per octave of harmonic number, added on top of `gains`. Harmonic 1 is the pivot.
|
|
pub tilt: f32,
|
|
/// Positive lifts odd harmonics and lowers even ones by half this amount each.
|
|
pub odd_even: f32,
|
|
/// Gain of the regions halfway between harmonics: noise, beating, inharmonic grit.
|
|
pub between: f32,
|
|
/// Bandwidth of every band as a percentage of the fundamental.
|
|
pub width: f32,
|
|
/// Number of harmonics that are shaped; higher ones pass unchanged.
|
|
pub harmonics: u32,
|
|
/// Retune time when the pitch slides or bends.
|
|
pub smooth: f32,
|
|
/// Detection threshold in dBFS for note starts (Audio source).
|
|
pub gate: f32,
|
|
/// Round detected pitch to the nearest semitone.
|
|
pub snap: bool,
|
|
pub mix: f32,
|
|
pub output: f32,
|
|
pub bypass: bool,
|
|
}
|
|
impl Default for Settings {
|
|
fn default() -> Self {
|
|
Self {
|
|
source: Source::Audio,
|
|
gains: [0.; HARMONICS],
|
|
tilt: 0.,
|
|
odd_even: 0.,
|
|
between: 0.,
|
|
width: 20.,
|
|
harmonics: 24,
|
|
smooth: 10.,
|
|
gate: -42.,
|
|
snap: true,
|
|
mix: 100.,
|
|
output: 0.,
|
|
bypass: false,
|
|
}
|
|
}
|
|
}
|
|
impl Settings {
|
|
pub fn validate(&self) -> Result<(), &'static str> {
|
|
let finite = [
|
|
self.tilt,
|
|
self.odd_even,
|
|
self.between,
|
|
self.width,
|
|
self.smooth,
|
|
self.gate,
|
|
self.mix,
|
|
self.output,
|
|
]
|
|
.iter()
|
|
.chain(self.gains.iter())
|
|
.all(|v| v.is_finite());
|
|
if !finite {
|
|
return Err("non-finite setting");
|
|
}
|
|
let ok = self.gains.iter().all(|g| (GAIN_MIN..=GAIN_MAX).contains(g))
|
|
&& (-12.0..=12.).contains(&self.tilt)
|
|
&& (-12.0..=12.).contains(&self.odd_even)
|
|
&& (-24.0..=12.).contains(&self.between)
|
|
&& (5.0..=50.).contains(&self.width)
|
|
&& (8..=HARMONICS as u32).contains(&self.harmonics)
|
|
&& (0.0..=100.).contains(&self.smooth)
|
|
&& (-60.0..=-20.).contains(&self.gate)
|
|
&& (0.0..=100.).contains(&self.mix)
|
|
&& (-12.0..=12.).contains(&self.output);
|
|
if ok {
|
|
Ok(())
|
|
} else {
|
|
Err("setting out of range")
|
|
}
|
|
}
|
|
/// Gain applied to harmonic `k` (1-based), with tilt and odd/even included.
|
|
pub fn effective(&self, k: usize) -> f32 {
|
|
if k == 0 || k > self.harmonics as usize || k > HARMONICS {
|
|
return 0.;
|
|
}
|
|
let parity = if k % 2 == 1 { 0.5 } else { -0.5 };
|
|
(self.gains[k - 1] + self.tilt * (k as f32).log2() + self.odd_even * parity)
|
|
.clamp(-30., 18.)
|
|
}
|
|
}
|