Check and Windows preview / check (anvil) (push) Canceled after 0s
Check and Windows preview / check (braze) (push) Canceled after 0s
Check and Windows preview / check (crucible) (push) Canceled after 0s
Check and Windows preview / check (knurl) (push) Canceled after 0s
Check and Windows preview / check (lathe) (push) Canceled after 0s
Check and Windows preview / check (patina) (push) Canceled after 0s
Check and Windows preview / check (planish) (push) Canceled after 0s
Check and Windows preview / check (rivet) (push) Canceled after 0s
Check and Windows preview / check (temper) (push) Canceled after 0s
Check and Windows preview / check (weld) (push) Canceled after 0s
Check and Windows preview / previews (push) Canceled after 0s
Check and Windows preview / windows (push) Canceled after 0s
Anvil gets a sidechain input named Kick. With Kick lock on, kicks are found on the undelayed sidechain, so with the lookahead each hit ducks the sub (Duck, Release) when the delayed kick plays and restarts the sub at Phase, shifted by Offset (-30 to +30 ms, also before the kick). The Audition steps the offset from -30 to +30 ms, 4 kicks each, and measures how kick and sub sum below 150 Hz after each hit; Keep takes the offset heard. On a 55 Hz kick burst against a 55 Hz sub the sum peaks at 0 ms (+2.4 dB) and nearly again one period away (+/-20 ms), so the bars check the choice by ear rather than make it.
243 lines
8.3 KiB
Rust
243 lines
8.3 KiB
Rust
use anvil_core::{Settings, Source, KICK_OFFSET_MAX};
|
|
use nih_plug::prelude::*;
|
|
use nih_plug_egui::EguiState;
|
|
use std::sync::Arc;
|
|
|
|
#[derive(Enum, Debug, PartialEq, Eq, Clone, Copy)]
|
|
pub enum SourceParam {
|
|
#[id = "audio"]
|
|
Audio,
|
|
#[id = "midi"]
|
|
#[name = "MIDI"]
|
|
Midi,
|
|
}
|
|
fn linear(name: &str, v: f32, min: f32, max: f32, unit: &'static str) -> FloatParam {
|
|
FloatParam::new(name, v, FloatRange::Linear { min, max }).with_unit(unit)
|
|
}
|
|
#[derive(Params)]
|
|
pub struct AnvilParams {
|
|
#[persist = "editor"]
|
|
pub editor: Arc<EguiState>,
|
|
#[id = "source"]
|
|
pub source: EnumParam<SourceParam>,
|
|
#[id = "level"]
|
|
pub level: FloatParam,
|
|
#[id = "split"]
|
|
pub split: FloatParam,
|
|
#[id = "dynamics"]
|
|
pub dynamics: FloatParam,
|
|
#[id = "drive"]
|
|
pub drive: FloatParam,
|
|
#[id = "ghost"]
|
|
pub ghost: FloatParam,
|
|
#[id = "ghost_from"]
|
|
pub ghost_from: FloatParam,
|
|
#[id = "octave"]
|
|
pub octave: IntParam,
|
|
#[id = "glide"]
|
|
pub glide: FloatParam,
|
|
#[id = "phase"]
|
|
pub phase: FloatParam,
|
|
#[id = "gate"]
|
|
pub gate: FloatParam,
|
|
#[id = "keep"]
|
|
pub keep: FloatParam,
|
|
#[id = "output"]
|
|
pub output: FloatParam,
|
|
#[id = "retrigger"]
|
|
pub retrigger: BoolParam,
|
|
#[id = "snap"]
|
|
pub snap: BoolParam,
|
|
#[id = "bypass"]
|
|
pub bypass: BoolParam,
|
|
#[id = "kick_lock"]
|
|
pub kick_lock: BoolParam,
|
|
#[id = "kick_duck"]
|
|
pub kick_duck: FloatParam,
|
|
#[id = "kick_release"]
|
|
pub kick_release: FloatParam,
|
|
#[id = "kick_offset"]
|
|
pub kick_offset: FloatParam,
|
|
}
|
|
impl Default for AnvilParams {
|
|
fn default() -> Self {
|
|
let d = Settings::default();
|
|
Self {
|
|
editor: EguiState::from_size(anvil_ui::WIDTH, anvil_ui::HEIGHT),
|
|
source: EnumParam::new("Pitch source", SourceParam::Audio),
|
|
level: linear("Sub level", d.level, -30., 0., " dBFS"),
|
|
split: FloatParam::new(
|
|
"Split",
|
|
d.split,
|
|
FloatRange::Skewed {
|
|
min: 40.,
|
|
max: 200.,
|
|
factor: FloatRange::skew_factor(-1.),
|
|
},
|
|
)
|
|
.with_unit(" Hz"),
|
|
dynamics: linear("Dynamics", d.dynamics, 0., 100., " %"),
|
|
drive: linear("Drive", d.drive, 0., 100., " %"),
|
|
ghost: linear("Ghost", d.ghost, 0., 100., " %"),
|
|
ghost_from: FloatParam::new(
|
|
"Ghost from",
|
|
d.ghost_from,
|
|
FloatRange::Skewed {
|
|
min: 150.,
|
|
max: 600.,
|
|
factor: FloatRange::skew_factor(-1.),
|
|
},
|
|
)
|
|
.with_unit(" Hz"),
|
|
octave: IntParam::new("Octave", d.octave, IntRange::Linear { min: -2, max: 1 }),
|
|
glide: linear("Glide", d.glide, 0., 200., " ms"),
|
|
phase: linear("Phase", d.phase, 0., 360., "°"),
|
|
gate: linear("Gate", d.gate, -60., -20., " dB"),
|
|
keep: linear("Keep", d.keep, 0., 100., " %"),
|
|
output: linear("Output", d.output, -12., 12., " dB"),
|
|
retrigger: BoolParam::new("Retrigger", d.retrigger),
|
|
snap: BoolParam::new("Snap to semitone", d.snap),
|
|
bypass: BoolParam::new("Bypass", false).make_bypass(),
|
|
kick_lock: BoolParam::new("Kick lock", d.kick_lock),
|
|
kick_duck: linear("Kick duck", d.kick_duck, 0., 24., " dB"),
|
|
kick_release: linear("Kick release", d.kick_release, 10., 300., " ms"),
|
|
kick_offset: linear(
|
|
"Kick offset",
|
|
d.kick_offset,
|
|
-KICK_OFFSET_MAX,
|
|
KICK_OFFSET_MAX,
|
|
" ms",
|
|
),
|
|
}
|
|
}
|
|
}
|
|
impl AnvilParams {
|
|
pub fn settings(&self) -> Settings {
|
|
Settings {
|
|
source: match self.source.value() {
|
|
SourceParam::Audio => Source::Audio,
|
|
SourceParam::Midi => Source::Midi,
|
|
},
|
|
level: self.level.value(),
|
|
split: self.split.value(),
|
|
dynamics: self.dynamics.value(),
|
|
drive: self.drive.value(),
|
|
ghost: self.ghost.value(),
|
|
ghost_from: self.ghost_from.value(),
|
|
octave: self.octave.value(),
|
|
glide: self.glide.value(),
|
|
phase: self.phase.value(),
|
|
gate: self.gate.value(),
|
|
keep: self.keep.value(),
|
|
output: self.output.value(),
|
|
retrigger: self.retrigger.value(),
|
|
snap: self.snap.value(),
|
|
bypass: self.bypass.value(),
|
|
kick_lock: self.kick_lock.value(),
|
|
kick_duck: self.kick_duck.value(),
|
|
kick_release: self.kick_release.value(),
|
|
kick_offset: self.kick_offset.value(),
|
|
}
|
|
}
|
|
/// Pushes every changed editor value to the host. Continuous controls hold one
|
|
/// gesture while the pointer is down; switches are set in a single gesture.
|
|
pub fn apply(
|
|
&self,
|
|
setter: &ParamSetter,
|
|
old: &Settings,
|
|
new: &Settings,
|
|
dragging: bool,
|
|
open: &mut [bool; 14],
|
|
) {
|
|
let floats: [(&FloatParam, f32, f32); 13] = [
|
|
(&self.level, old.level, new.level),
|
|
(&self.split, old.split, new.split),
|
|
(&self.dynamics, old.dynamics, new.dynamics),
|
|
(&self.drive, old.drive, new.drive),
|
|
(&self.glide, old.glide, new.glide),
|
|
(&self.phase, old.phase, new.phase),
|
|
(&self.gate, old.gate, new.gate),
|
|
(&self.keep, old.keep, new.keep),
|
|
(&self.ghost, old.ghost, new.ghost),
|
|
(&self.ghost_from, old.ghost_from, new.ghost_from),
|
|
(&self.kick_duck, old.kick_duck, new.kick_duck),
|
|
(&self.kick_release, old.kick_release, new.kick_release),
|
|
(&self.kick_offset, old.kick_offset, new.kick_offset),
|
|
];
|
|
let mut continuous = |i: usize, changed: bool, set: &dyn Fn()| {
|
|
if changed {
|
|
if !open[i] {
|
|
open[i] = true;
|
|
set_begin(setter, self, i);
|
|
}
|
|
set();
|
|
}
|
|
if !dragging && open[i] {
|
|
set_end(setter, self, i);
|
|
open[i] = false;
|
|
}
|
|
};
|
|
for (i, (p, a, b)) in floats.into_iter().enumerate() {
|
|
continuous(i, a != b, &|| setter.set_parameter(p, b));
|
|
}
|
|
continuous(13, old.output != new.output, &|| {
|
|
setter.set_parameter(&self.output, new.output)
|
|
});
|
|
let once = |changed: bool, f: &dyn Fn()| {
|
|
if changed {
|
|
f();
|
|
}
|
|
};
|
|
once(new.octave != old.octave, &|| {
|
|
setter.begin_set_parameter(&self.octave);
|
|
setter.set_parameter(&self.octave, new.octave);
|
|
setter.end_set_parameter(&self.octave);
|
|
});
|
|
for (p, a, b) in [
|
|
(&self.retrigger, old.retrigger, new.retrigger),
|
|
(&self.snap, old.snap, new.snap),
|
|
(&self.bypass, old.bypass, new.bypass),
|
|
(&self.kick_lock, old.kick_lock, new.kick_lock),
|
|
] {
|
|
once(a != b, &|| {
|
|
setter.begin_set_parameter(p);
|
|
setter.set_parameter(p, b);
|
|
setter.end_set_parameter(p);
|
|
});
|
|
}
|
|
once(new.source != old.source, &|| {
|
|
let v = match new.source {
|
|
Source::Audio => SourceParam::Audio,
|
|
Source::Midi => SourceParam::Midi,
|
|
};
|
|
setter.begin_set_parameter(&self.source);
|
|
setter.set_parameter(&self.source, v);
|
|
setter.end_set_parameter(&self.source);
|
|
});
|
|
}
|
|
fn continuous(&self, i: usize) -> &FloatParam {
|
|
[
|
|
&self.level,
|
|
&self.split,
|
|
&self.dynamics,
|
|
&self.drive,
|
|
&self.glide,
|
|
&self.phase,
|
|
&self.gate,
|
|
&self.keep,
|
|
&self.ghost,
|
|
&self.ghost_from,
|
|
&self.kick_duck,
|
|
&self.kick_release,
|
|
&self.kick_offset,
|
|
&self.output,
|
|
][i]
|
|
}
|
|
}
|
|
fn set_begin(setter: &ParamSetter, p: &AnvilParams, i: usize) {
|
|
setter.begin_set_parameter(p.continuous(i));
|
|
}
|
|
fn set_end(setter: &ParamSetter, p: &AnvilParams, i: usize) {
|
|
setter.end_set_parameter(p.continuous(i));
|
|
}
|