diff --git a/src/param/range.rs b/src/param/range.rs index f5f2d002..f3502d90 100644 --- a/src/param/range.rs +++ b/src/param/range.rs @@ -16,11 +16,31 @@ //! Different ranges for numeric parameters. -/// 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. +/// A distribution for a parameter's range. All range endpoints are inclusive. #[derive(Debug)] pub enum Range { + /// The values are uniformly distributed between `min` and `max`. Linear { min: T, max: T }, + /// The range is skewed by a factor. Values above 1.0 will make the end of the range wider, + /// while values between 0 and 1 will skew the range towards the start. Use [Range::skew_factor] + /// for a more intuitively way to calculate the skew factor where positive values skew the range + /// towards the end while negative values skew the range toward the start. + Skewed { min: T, max: T, factor: f32 }, + /// The same as [Range::Skewed], but with the skewing happening from a central point. + SymmetricalSkewed { + min: T, + max: T, + factor: f32, + center: T, + }, +} + +impl Range<()> { + /// Calculate a skew factor for [Range::Skewed] and [Range::SymmetricalSkewed]. Positive values + /// make the end of the range wider while negative make the start of the range wider. + pub fn skew_factor(factor: f32) -> f32 { + 2.0f32.powf(factor) + } } /// A normalizable range for type `T`, where `self` is expected to be a type `R`. Higher kinded @@ -51,6 +71,34 @@ impl NormalizebleRange for Range { fn normalize(&self, plain: f32) -> f32 { match &self { Range::Linear { min, max } => (plain - min) / (max - min), + Range::Skewed { min, max, factor } => ((plain - min) / (max - min)).powf(*factor), + Range::SymmetricalSkewed { + min, + max, + factor, + center, + } => { + // There's probably a much faster equivalent way to write this. Also, I have no clue + // how I managed to implement this correctly on the first try. + let unscaled_proportion = (plain - min) / (max - min); + let center_proportion = (center - min) / (max - min); + if unscaled_proportion > center_proportion { + // The part above the center gets normalized to a [0, 1] range, skewed, and then + // unnormalized and scaled back to the original [center_proportion, 1] range + let scaled_proportion = (unscaled_proportion - center_proportion) + * (1.0 - center_proportion).recip(); + (scaled_proportion.powf(*factor) * (1.0 - center_proportion)) + + center_proportion + } else { + // The part below the center gets scaled, inverted (so the range is [0, 1] where + // 0 corresponds to the center proportion and 1 corresponds to the orignal + // normalized 0 value), skewed, inverted back again, and then scaled back to the + // original range + let inverted_scaled_proportion = + (center_proportion - unscaled_proportion) * (center_proportion).recip(); + (1.0 - inverted_scaled_proportion.powf(*factor)) * (center_proportion) + } + } } .clamp(0.0, 1.0) } @@ -59,6 +107,30 @@ impl NormalizebleRange for Range { let normalized = normalized.clamp(0.0, 1.0); match &self { Range::Linear { min, max } => (normalized * (max - min)) + min, + Range::Skewed { min, max, factor } => { + (normalized.powf(factor.recip()) * (max - min)) + min + } + Range::SymmetricalSkewed { + min, + max, + factor, + center, + } => { + // Reconstructing the subranges works the same as with the normal skewed ranges + let center_proportion = (center - min) / (max - min); + let skewed_proportion = if normalized > center_proportion { + let scaled_proportion = + (normalized - center_proportion) * (1.0 - center_proportion).recip(); + (scaled_proportion.powf(factor.recip()) * (1.0 - center_proportion)) + + center_proportion + } else { + let inverted_scaled_proportion = + (center_proportion - normalized) * (center_proportion).recip(); + (1.0 - inverted_scaled_proportion.powf(factor.recip())) * (center_proportion) + }; + + (skewed_proportion * (max - min)) + min + } } } } @@ -67,6 +139,29 @@ impl NormalizebleRange for Range { fn normalize(&self, plain: i32) -> f32 { match &self { Range::Linear { min, max } => (plain - min) as f32 / (max - min) as f32, + Range::Skewed { min, max, factor } => { + ((plain - min) as f32 / (max - min) as f32).powf(*factor) + } + Range::SymmetricalSkewed { + min, + max, + factor, + center, + } => { + // See the comments in the float version + let unscaled_proportion = (plain - min) as f32 / (max - min) as f32; + let center_proportion = (center - min) as f32 / (max - min) as f32; + if unscaled_proportion > center_proportion { + let scaled_proportion = (unscaled_proportion - center_proportion) + * (1.0 - center_proportion).recip(); + (scaled_proportion.powf(*factor) * (1.0 - center_proportion)) + + center_proportion + } else { + let inverted_scaled_proportion = + (center_proportion - unscaled_proportion) * (center_proportion).recip(); + (1.0 - inverted_scaled_proportion.powf(*factor)) * (center_proportion) + } + } } .clamp(0.0, 1.0) } @@ -75,6 +170,29 @@ impl NormalizebleRange for Range { let normalized = normalized.clamp(0.0, 1.0); match &self { Range::Linear { min, max } => (normalized * (max - min) as f32).round() as i32 + min, + Range::Skewed { min, max, factor } => { + (normalized.powf(factor.recip()) * (max - min) as f32).round() as i32 + min + } + Range::SymmetricalSkewed { + min, + max, + factor, + center, + } => { + let center_proportion = (center - min) as f32 / (max - min) as f32; + let skewed_proportion = if normalized > center_proportion { + let scaled_proportion = + (normalized - center_proportion) * (1.0 - center_proportion).recip(); + (scaled_proportion.powf(factor.recip()) * (1.0 - center_proportion)) + + center_proportion + } else { + let inverted_scaled_proportion = + (center_proportion - normalized) * (center_proportion).recip(); + (1.0 - inverted_scaled_proportion.powf(factor.recip())) * (center_proportion) + }; + + (skewed_proportion * (max - min) as f32).round() as i32 + min + } } } } @@ -94,33 +212,174 @@ mod tests { 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); + fn make_skewed_float_range(factor: f32) -> Range { + Range::Skewed { + min: 10.0, + max: 20.0, + factor, + } } - #[test] - fn range_normalize_linear_int() { - let range = make_linear_int_range(); - assert_eq!(range.normalize(-5), 0.25); + fn make_skewed_int_range(factor: f32) -> Range { + Range::Skewed { + min: -10, + max: 10, + factor, + } } - #[test] - fn range_unnormalize_linear_float() { - let range = make_linear_float_range(); - assert_eq!(range.unnormalize(0.25), 12.5); + fn make_symmetrical_skewed_float_range(factor: f32) -> Range { + Range::SymmetricalSkewed { + min: 10.0, + max: 20.0, + factor, + center: 12.5, + } } - #[test] - fn range_unnormalize_linear_int() { - let range = make_linear_int_range(); - assert_eq!(range.unnormalize(0.75), 5); + fn make_symmetrical_skewed_int_range(factor: f32) -> Range { + Range::SymmetricalSkewed { + min: -10, + max: 10, + factor, + center: -3, + } } - #[test] - fn range_unnormalize_linear_int_rounding() { - let range = make_linear_int_range(); - assert_eq!(range.unnormalize(0.73), 5); + mod linear { + use super::super::*; + use super::*; + + #[test] + fn range_normalize_float() { + let range = make_linear_float_range(); + assert_eq!(range.normalize(17.5), 0.75); + } + + #[test] + fn range_normalize_int() { + let range = make_linear_int_range(); + assert_eq!(range.normalize(-5), 0.25); + } + + #[test] + fn range_unnormalize_float() { + let range = make_linear_float_range(); + assert_eq!(range.unnormalize(0.25), 12.5); + } + + #[test] + fn range_unnormalize_int() { + let range = make_linear_int_range(); + assert_eq!(range.unnormalize(0.75), 5); + } + + #[test] + fn range_unnormalize_int_rounding() { + let range = make_linear_int_range(); + assert_eq!(range.unnormalize(0.73), 5); + } + } + + mod skewed { + use super::super::*; + use super::*; + + #[test] + fn range_normalize_float() { + let range = make_skewed_float_range(Range::skew_factor(-2.0)); + assert_eq!(range.normalize(17.5), 0.9306048591020996); + } + + #[test] + fn range_normalize_int() { + let range = make_skewed_int_range(Range::skew_factor(-2.0)); + assert_eq!(range.normalize(-5), 0.7071067811865476); + } + + #[test] + fn range_unnormalize_float() { + let range = make_skewed_float_range(Range::skew_factor(-2.0)); + assert_eq!(range.unnormalize(0.9306048591020996), 17.5); + } + + #[test] + fn range_unnormalize_int() { + let range = make_skewed_int_range(Range::skew_factor(-2.0)); + assert_eq!(range.unnormalize(0.7071067811865476), -5); + } + + #[test] + fn range_normalize_linear_equiv_float() { + let linear_range = make_linear_float_range(); + let skewed_range = make_skewed_float_range(1.0); + assert_eq!(linear_range.normalize(17.5), skewed_range.normalize(17.5)); + } + + #[test] + fn range_normalize_linear_equiv_int() { + let linear_range = make_linear_int_range(); + let skewed_range = make_skewed_int_range(1.0); + assert_eq!(linear_range.normalize(-5), skewed_range.normalize(-5)); + } + + #[test] + fn range_unnormalize_linear_equiv_float() { + let linear_range = make_linear_float_range(); + let skewed_range = make_skewed_float_range(1.0); + assert_eq!( + linear_range.unnormalize(0.25), + skewed_range.unnormalize(0.25) + ); + } + + #[test] + fn range_unnormalize_linear_equiv_int() { + let linear_range = make_linear_int_range(); + let skewed_range = make_skewed_int_range(1.0); + assert_eq!( + linear_range.unnormalize(0.25), + skewed_range.unnormalize(0.25) + ); + } + + #[test] + fn range_unnormalize_linear_equiv_int_rounding() { + let linear_range = make_linear_int_range(); + let skewed_range = make_skewed_int_range(1.0); + assert_eq!( + linear_range.unnormalize(0.73), + skewed_range.unnormalize(0.73) + ); + } + } + + mod symmetrical_skewed { + use super::super::*; + use super::*; + + #[test] + fn range_normalize_float() { + let range = make_symmetrical_skewed_float_range(Range::skew_factor(-2.0)); + assert_eq!(range.normalize(17.5), 0.9277015); + } + + #[test] + fn range_normalize_int() { + let range = make_symmetrical_skewed_int_range(Range::skew_factor(-2.0)); + assert_eq!(range.normalize(-5), 0.09411134); + } + + #[test] + fn range_unnormalize_float() { + let range = make_symmetrical_skewed_float_range(Range::skew_factor(-2.0)); + assert_eq!(range.unnormalize(0.9277015), 17.5); + } + + #[test] + fn range_unnormalize_int() { + let range = make_symmetrical_skewed_int_range(Range::skew_factor(-2.0)); + assert_eq!(range.unnormalize(0.09411134), -5); + } } } diff --git a/src/wrapper/vst3.rs b/src/wrapper/vst3.rs index 46742770..7b445ead 100644 --- a/src/wrapper/vst3.rs +++ b/src/wrapper/vst3.rs @@ -636,6 +636,8 @@ impl IEditController for Wrapper<'_, P> { ParamPtr::FloatParam(_) => 0, ParamPtr::IntParam(p) => match (**p).range { crate::param::Range::Linear { min, max } => max - min, + crate::param::Range::Skewed { min, max, .. } => max - min, + crate::param::Range::SymmetricalSkewed { min, max, .. } => max - min, }, ParamPtr::BoolParam(_) => 1, };