From f007945335005c6450fc5465aac7cdbdd1f287d8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 19 Jul 2022 20:18:45 +0200 Subject: [PATCH] Add reversed ranges --- BREAKING_CHANGES.md | 6 ++ src/param/float.rs | 19 +---- src/param/integer.rs | 4 +- src/param/range.rs | 167 ++++++++++++++++++++++++++++++++++--------- 4 files changed, 145 insertions(+), 51 deletions(-) diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 13004bea..7cf80b0f 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -6,6 +6,12 @@ new and what's changed, this document lists all breaking changes in reverse chronological order. If a new feature did not require any changes to existing code then it will not be listed here. +## [2022-07-18] + +- `IntRange` and `FloatRange` no longer have min/max methods and instead have + next/previous step methods. This is for better compatibility with the new + reversed ranges. + ## [2022-07-06] - The block smoothing API has been reworked. Instead of `Smoother`s having their diff --git a/src/param/float.rs b/src/param/float.rs index e013bc8a..4129747c 100644 --- a/src/param/float.rs +++ b/src/param/float.rs @@ -139,26 +139,11 @@ impl Param for FloatParam { } fn previous_step(&self, from: Self::Plain) -> Self::Plain { - // This one's slightly more involved. We'll split the normalized range up into 100 segments, - // but if `self.step_size` is set then we'll use that. Ideally we might want to split the - // range up into at most 100 segments, falling back to the step size if the total number of - // steps would be smaller than that, but since ranges can be nonlienar that's a bit - // difficult to pull off. - // TODO: At some point, implement the above mentioned step size quantization - match self.step_size { - Some(step_size) => from - step_size, - None => self.preview_plain(self.preview_normalized(from) - 0.01), - } - .clamp(self.range.min(), self.range.max()) + self.range.previous_step(from, self.step_size) } fn next_step(&self, from: Self::Plain) -> Self::Plain { - // See above - match self.step_size { - Some(step_size) => from + step_size, - None => self.preview_plain(self.preview_normalized(from) + 0.01), - } - .clamp(self.range.min(), self.range.max()) + self.range.next_step(from, self.step_size) } fn normalized_value_to_string(&self, normalized: f32, include_unit: bool) -> String { diff --git a/src/param/integer.rs b/src/param/integer.rs index fac104cc..95bf1313 100644 --- a/src/param/integer.rs +++ b/src/param/integer.rs @@ -131,11 +131,11 @@ impl Param for IntParam { } fn previous_step(&self, from: Self::Plain) -> Self::Plain { - (from - 1).clamp(self.range.min(), self.range.max()) + self.range.previous_step(from) } fn next_step(&self, from: Self::Plain) -> Self::Plain { - (from + 1).clamp(self.range.min(), self.range.max()) + self.range.next_step(from) } fn normalized_value_to_string(&self, normalized: f32, include_unit: bool) -> String { diff --git a/src/param/range.rs b/src/param/range.rs index b3ba7636..9ad1fc66 100644 --- a/src/param/range.rs +++ b/src/param/range.rs @@ -20,15 +20,19 @@ pub enum FloatRange { factor: f32, center: f32, }, + /// A reversed range that goes from high to low instead of from low to high. + Reversed(&'static FloatRange), } /// A distribution for an integer parameter's range. All range endpoints are inclusive. Only linear /// ranges are supported for integers since hosts expect discrete parameters to have a fixed step /// size. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum IntRange { /// The values are uniformly distributed between `min` and `max`. Linear { min: i32, max: i32 }, + /// A reversed range that goes from high to low instead of from low to high. + Reversed(&'static IntRange), } impl FloatRange { @@ -42,7 +46,7 @@ impl FloatRange { /// Normalize a plain, unnormalized value. Will be clamped to the bounds of the range if the /// normalized value exceeds `[0, 1]`. pub fn normalize(&self, plain: f32) -> f32 { - match &self { + match self { FloatRange::Linear { min, max } => (plain.clamp(*min, *max) - min) / (max - min), FloatRange::Skewed { min, max, factor } => { ((plain.clamp(*min, *max) - min) / (max - min)).powf(*factor) @@ -73,6 +77,7 @@ impl FloatRange { (1.0 - inverted_scaled_proportion.powf(*factor)) * 0.5 } } + FloatRange::Reversed(range) => 1.0 - range.normalize(plain), } } @@ -80,7 +85,7 @@ impl FloatRange { /// would exceed that range. pub fn unnormalize(&self, normalized: f32) -> f32 { let normalized = normalized.clamp(0.0, 1.0); - match &self { + match self { FloatRange::Linear { min, max } => (normalized * (max - min)) + min, FloatRange::Skewed { min, max, factor } => { (normalized.powf(factor.recip()) * (max - min)) + min @@ -104,36 +109,57 @@ impl FloatRange { (skewed_proportion * (max - min)) + min } + FloatRange::Reversed(range) => range.unnormalize(1.0 - normalized), } } - /// The minimum value in this range. - pub fn min(&self) -> f32 { + /// The range's previous discrete step from a certain value with a certain step size. If the step + /// size is not set, then the normalized range is split into 100 segments instead. + pub fn previous_step(&self, from: f32, step_size: Option) -> f32 { + // This one's slightly more involved than the integer version. We'll split the normalized + // range up into 100 segments, but if `self.step_size` is set then we'll use that. Ideally + // we might want to split the range up into at most 100 segments, falling back to the step + // size if the total number of steps would be smaller than that, but since ranges can be + // nonlienar that's a bit difficult to pull off. + // TODO: At some point, implement the above mentioned step size quantization match self { - FloatRange::Linear { min, .. } - | FloatRange::Skewed { min, .. } - | FloatRange::SymmetricalSkewed { min, .. } => *min, + FloatRange::Linear { min, max } + | FloatRange::Skewed { min, max, .. } + | FloatRange::SymmetricalSkewed { min, max, .. } => match step_size { + Some(step_size) => from - step_size, + None => self.unnormalize(self.normalize(from) - 0.01), + } + .clamp(*min, *max), + FloatRange::Reversed(range) => range.next_step(from, step_size), } } - /// The maximum value in this range. - pub fn max(&self) -> f32 { + /// The range's next discrete step from a certain value with a certain step size. If the step + /// size is not set, then the normalized range is split into 100 segments instead. + pub fn next_step(&self, from: f32, step_size: Option) -> f32 { + // See above match self { - FloatRange::Linear { max, .. } - | FloatRange::Skewed { max, .. } - | FloatRange::SymmetricalSkewed { max, .. } => *max, + FloatRange::Linear { min, max } + | FloatRange::Skewed { min, max, .. } + | FloatRange::SymmetricalSkewed { min, max, .. } => match step_size { + Some(step_size) => from + step_size, + None => self.unnormalize(self.normalize(from) + 0.01), + } + .clamp(*min, *max), + FloatRange::Reversed(range) => range.previous_step(from, step_size), } } /// Snap a vlue to a step size, clamping to the minimum and maximum value of the range. pub fn snap_to_step(&self, value: f32, step_size: f32) -> f32 { - let (min, max) = match &self { - FloatRange::Linear { min, max } => (min, max), - FloatRange::Skewed { min, max, .. } => (min, max), - FloatRange::SymmetricalSkewed { min, max, .. } => (min, max), - }; - - ((value / step_size).round() * step_size).clamp(*min, *max) + match self { + FloatRange::Linear { min, max } + | FloatRange::Skewed { min, max, .. } + | FloatRange::SymmetricalSkewed { min, max, .. } => { + ((value / step_size).round() * step_size).clamp(*min, *max) + } + FloatRange::Reversed(range) => range.snap_to_step(value, step_size), + } } } @@ -141,8 +167,9 @@ impl IntRange { /// Normalize a plain, unnormalized value. Will be clamped to the bounds of the range if the /// normalized value exceeds `[0, 1]`. pub fn normalize(&self, plain: i32) -> f32 { - match &self { + match self { IntRange::Linear { min, max } => (plain - min) as f32 / (max - min) as f32, + IntRange::Reversed(range) => 1.0 - range.normalize(plain), } .clamp(0.0, 1.0) } @@ -151,22 +178,25 @@ impl IntRange { /// would exceed that range. pub fn unnormalize(&self, normalized: f32) -> i32 { let normalized = normalized.clamp(0.0, 1.0); - match &self { + match self { IntRange::Linear { min, max } => (normalized * (max - min) as f32).round() as i32 + min, + IntRange::Reversed(range) => range.unnormalize(1.0 - normalized), } } - /// The minimum value in this range. - pub fn min(&self) -> i32 { + /// The range's previous discrete step from a certain value. + pub fn previous_step(&self, from: i32) -> i32 { match self { - IntRange::Linear { min, .. } => *min, + IntRange::Linear { min, max } => (from - 1).clamp(*min, *max), + IntRange::Reversed(range) => range.next_step(from), } } - /// The maximum value in this range. - pub fn max(&self) -> i32 { + /// The range's next discrete step from a certain value. + pub fn next_step(&self, from: i32) -> i32 { match self { - IntRange::Linear { max, .. } => *max, + IntRange::Linear { min, max } => (from + 1).clamp(*min, *max), + IntRange::Reversed(range) => range.previous_step(from), } } @@ -174,6 +204,15 @@ impl IntRange { pub fn step_count(&self) -> usize { match self { IntRange::Linear { min, max } => (max - min) as usize, + IntRange::Reversed(range) => range.step_count(), + } + } + + /// If this range is wrapped in an adapter, like `Reversed`, then return the wrapped range. + pub fn inner_range(&self) -> Self { + match self { + IntRange::Linear { .. } => *self, + IntRange::Reversed(range) => range.inner_range(), } } } @@ -182,18 +221,18 @@ impl IntRange { mod tests { use super::*; - fn make_linear_float_range() -> FloatRange { + const fn make_linear_float_range() -> FloatRange { FloatRange::Linear { min: 10.0, max: 20.0, } } - fn make_linear_int_range() -> IntRange { + const fn make_linear_int_range() -> IntRange { IntRange::Linear { min: -10, max: 10 } } - fn make_skewed_float_range(factor: f32) -> FloatRange { + const fn make_skewed_float_range(factor: f32) -> FloatRange { FloatRange::Skewed { min: 10.0, max: 20.0, @@ -201,7 +240,7 @@ mod tests { } } - fn make_symmetrical_skewed_float_range(factor: f32) -> FloatRange { + const fn make_symmetrical_skewed_float_range(factor: f32) -> FloatRange { FloatRange::SymmetricalSkewed { min: 10.0, max: 20.0, @@ -307,4 +346,68 @@ mod tests { assert_eq!(range.unnormalize(0.951801), 17.5); } } + + mod reversed_linear { + use super::*; + + #[test] + fn range_normalize_int() { + const WRAPPED_RANGE: IntRange = make_linear_int_range(); + let range = IntRange::Reversed(&WRAPPED_RANGE); + assert_eq!(range.normalize(-5), 1.0 - 0.25); + } + + #[test] + fn range_unnormalize_int() { + const WRAPPED_RANGE: IntRange = make_linear_int_range(); + let range = IntRange::Reversed(&WRAPPED_RANGE); + assert_eq!(range.unnormalize(1.0 - 0.75), 5); + } + + #[test] + fn range_unnormalize_int_rounding() { + const WRAPPED_RANGE: IntRange = make_linear_int_range(); + let range = IntRange::Reversed(&WRAPPED_RANGE); + assert_eq!(range.unnormalize(1.0 - 0.73), 5); + } + } + + mod reversed_skewed { + use super::*; + + #[test] + fn range_normalize_float() { + const WRAPPED_RANGE: FloatRange = make_skewed_float_range(0.25); + let range = FloatRange::Reversed(&WRAPPED_RANGE); + assert_eq!(range.normalize(17.5), 1.0 - 0.9306049); + } + + #[test] + fn range_unnormalize_float() { + const WRAPPED_RANGE: FloatRange = make_skewed_float_range(0.25); + let range = FloatRange::Reversed(&WRAPPED_RANGE); + assert_eq!(range.unnormalize(1.0 - 0.9306049), 17.5); + } + + #[test] + fn range_normalize_linear_equiv_float() { + const WRAPPED_LINEAR_RANGE: FloatRange = make_linear_float_range(); + const WRAPPED_SKEWED_RANGE: FloatRange = make_skewed_float_range(1.0); + let linear_range = FloatRange::Reversed(&WRAPPED_LINEAR_RANGE); + let skewed_range = FloatRange::Reversed(&WRAPPED_SKEWED_RANGE); + assert_eq!(linear_range.normalize(17.5), skewed_range.normalize(17.5)); + } + + #[test] + fn range_unnormalize_linear_equiv_float() { + const WRAPPED_LINEAR_RANGE: FloatRange = make_linear_float_range(); + const WRAPPED_SKEWED_RANGE: FloatRange = make_skewed_float_range(1.0); + let linear_range = FloatRange::Reversed(&WRAPPED_LINEAR_RANGE); + let skewed_range = FloatRange::Reversed(&WRAPPED_SKEWED_RANGE); + assert_eq!( + linear_range.unnormalize(0.25), + skewed_range.unnormalize(0.25) + ); + } + } }