use anvil_core::{Mode, Settings, Source, KICK_OFFSET_MAX}; use common_params::PitchParams; 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, #[id = "note"] Note, } impl From for Source { fn from(s: SourceParam) -> Self { match s { SourceParam::Audio => Source::Audio, SourceParam::Midi => Source::Midi, SourceParam::Note => Source::Note, } } } impl From for SourceParam { fn from(s: Source) -> Self { match s { Source::Audio => SourceParam::Audio, Source::Midi => SourceParam::Midi, Source::Note => SourceParam::Note, } } } #[derive(Enum, Debug, PartialEq, Eq, Clone, Copy)] pub enum ModeParam { #[id = "replace"] Replace, #[id = "enhance"] Enhance, } impl From for Mode { fn from(m: ModeParam) -> Self { match m { ModeParam::Replace => Mode::Replace, ModeParam::Enhance => Mode::Enhance, } } } impl From for ModeParam { fn from(m: Mode) -> Self { match m { Mode::Replace => ModeParam::Replace, Mode::Enhance => ModeParam::Enhance, } } } 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, #[id = "source"] pub source: EnumParam, #[nested(group = "Pitch")] pub pitch: PitchParams, #[id = "mode"] pub mode: EnumParam, #[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), pitch: PitchParams::default(), mode: EnumParam::new("Mode", ModeParam::Replace), 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: self.source.value().into(), pitch: self.pitch.pitch(), mode: self.mode.value().into(), 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, &|| { setter.begin_set_parameter(&self.source); setter.set_parameter(&self.source, new.source.into()); setter.end_set_parameter(&self.source); }); once(new.mode != old.mode, &|| { setter.begin_set_parameter(&self.mode); setter.set_parameter(&self.mode, new.mode.into()); setter.end_set_parameter(&self.mode); }); self.pitch.apply(setter, &old.pitch, &new.pitch); } 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)); }