From 64f7dc8148b15bbd802ef577ded31320bbe69253 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 24 Jan 2022 20:18:37 +0100 Subject: [PATCH] Add some of the building blocks for param handling --- Cargo.lock | 16 ++++++ Cargo.toml | 1 + src/atomic.rs | 42 ++++++++++++++++ src/lib.rs | 26 +++++++--- src/params.rs | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 213 insertions(+), 8 deletions(-) create mode 100644 Cargo.lock create mode 100644 src/atomic.rs create mode 100644 src/params.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..99916ca5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "atomic_float" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62af46d040ba9df09edc6528dae9d8e49f5f3e82f55b7d2ec31a733c38dbc49d" + +[[package]] +name = "nih-plugs" +version = "0.1.0" +dependencies = [ + "atomic_float", +] diff --git a/Cargo.toml b/Cargo.toml index ca32901e..ba75fafe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ authors = ["Robbert van der Helm "] license = "GPL-3.0-or-later" [dependencies] +atomic_float = "0.1.0" diff --git a/src/atomic.rs b/src/atomic.rs new file mode 100644 index 00000000..d8678ea6 --- /dev/null +++ b/src/atomic.rs @@ -0,0 +1,42 @@ +// nih-plugs: plugins, but rewritten in Rust +// Copyright (C) 2022 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use atomic_float::AtomicF32; +use std::sync::atomic::AtomicI32; + +// Type families galore! +pub trait AtomicType { + /// An atomic version of this type with interior mutability. + type AtomicType; + + fn new_atomic(self) -> Self::AtomicType; +} + +impl AtomicType for f32 { + type AtomicType = AtomicF32; + + fn new_atomic(self) -> AtomicF32 { + AtomicF32::new(self) + } +} + +impl AtomicType for i32 { + type AtomicType = AtomicI32; + + fn new_atomic(self) -> AtomicI32 { + AtomicI32::new(self) + } +} diff --git a/src/lib.rs b/src/lib.rs index 1b4a90c9..34ae8e4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,18 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); - } -} +// nih-plugs: plugins, but rewritten in Rust +// Copyright (C) 2022 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pub mod atomic; +pub mod params; diff --git a/src/params.rs b/src/params.rs new file mode 100644 index 00000000..9f5275c2 --- /dev/null +++ b/src/params.rs @@ -0,0 +1,136 @@ +// nih-plugs: plugins, but rewritten in Rust +// Copyright (C) 2022 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::atomic::AtomicType; + +/// Describes a single normalized parameter and also stores its value. +pub enum Param { + FloatParam(PlainParam), + IntParam(PlainParam), +} + +/// A distribution for a parameter's range. Probably need to add some forms of skewed ranges and +/// maybe a callback based implementation at some point. +#[derive(Debug)] +pub enum Range { + Linear { min: T, max: T }, +} + +trait Normalize { + /// Normalize an unnormalized value. Will be clamped to the bounds of the range if the + /// normalized value exceeds `[0, 1]`. + fn normalize(&self, unnormalized: T) -> f32; + + /// Unnormalize a normalized value. Will be clamped to `[0, 1]` if the unnormalized value would + /// exceed that range. + fn unnormalize(&self, normalized: f32) -> T; +} + +/// A numerical parameter that's stored unnormalized. The range is used for the normalization +/// process. +pub struct PlainParam { + /// The field's current, normalized value. Should be initialized with the default value using + /// `T::new_atomic(...)` ([AtomicType::new_atomic]). + pub value: ::AtomicType, + + /// The distribution of the parameter's values. + pub range: Range, + /// The parameter's human readable display name. + pub name: &'static str, + /// The parameter value's unit, added after `value_to_string` if that is set. + pub unit: &'static str, + /// Optional custom conversion function from an **unnormalized** value to a string. + pub value_to_string: Option String>>, + /// Optional custom conversion function from a string to an **unnormalized** value. + pub string_to_value: Option T>>, +} + +impl Normalize for Range { + fn normalize(&self, unnormalized: f32) -> f32 { + match &self { + Range::Linear { min, max } => (unnormalized - min) / (max - min), + } + } + + fn unnormalize(&self, normalized: f32) -> f32 { + match &self { + Range::Linear { min, max } => (normalized * (max - min)) + min, + } + } +} + +impl Normalize for Range { + fn normalize(&self, unnormalized: i32) -> f32 { + match &self { + Range::Linear { min, max } => (unnormalized - min) as f32 / (max - min) as f32, + } + } + + fn unnormalize(&self, normalized: f32) -> i32 { + match &self { + Range::Linear { min, max } => (normalized * (max - min) as f32) as i32 + min, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_linear_float_range() -> Range { + Range::Linear { + min: 10.0, + max: 20.0, + } + } + + fn make_linear_int_range() -> Range { + Range::Linear { min: -10, max: 10 } + } + + #[test] + fn range_normalize_linear_float() { + let range = make_linear_float_range(); + assert_eq!(range.normalize(17.5), 0.75); + } + + #[test] + fn range_normalize_linear_int() { + let range = make_linear_int_range(); + assert_eq!(range.normalize(-5), 0.25); + } + + #[test] + fn range_unnormalize_linear_float() { + let range = make_linear_float_range(); + assert_eq!(range.unnormalize(0.25), 12.5); + } + + #[test] + fn range_unnormalize_linear_int() { + let range = make_linear_int_range(); + assert_eq!(range.unnormalize(0.75), 5); + } +} + +fn foo(param: &Param) -> f32 { + match param { + Param::FloatParam(p) => p + .range + .normalize(p.value.load(std::sync::atomic::Ordering::Relaxed)), + Param::IntParam(_) => todo!(), + } +}