Add symmetrically and asymmetrically skewed ranges
This is super useful. I'm sure the symmetrical implementation can be optimized a lot, but my math-fu was not strong enough today.
This commit is contained in:
parent
740868a10c
commit
7752ce6771
|
@ -16,11 +16,31 @@
|
||||||
|
|
||||||
//! Different ranges for numeric parameters.
|
//! Different ranges for numeric parameters.
|
||||||
|
|
||||||
/// A distribution for a parameter's range. Probably need to add some forms of skewed ranges and
|
/// A distribution for a parameter's range. All range endpoints are inclusive.
|
||||||
/// maybe a callback based implementation at some point.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Range<T> {
|
pub enum Range<T> {
|
||||||
|
/// The values are uniformly distributed between `min` and `max`.
|
||||||
Linear { min: T, max: T },
|
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<T>`. Higher kinded
|
/// A normalizable range for type `T`, where `self` is expected to be a type `R<T>`. Higher kinded
|
||||||
|
@ -51,6 +71,34 @@ impl NormalizebleRange<f32> for Range<f32> {
|
||||||
fn normalize(&self, plain: f32) -> f32 {
|
fn normalize(&self, plain: f32) -> f32 {
|
||||||
match &self {
|
match &self {
|
||||||
Range::Linear { min, max } => (plain - min) / (max - min),
|
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)
|
.clamp(0.0, 1.0)
|
||||||
}
|
}
|
||||||
|
@ -59,6 +107,30 @@ impl NormalizebleRange<f32> for Range<f32> {
|
||||||
let normalized = normalized.clamp(0.0, 1.0);
|
let normalized = normalized.clamp(0.0, 1.0);
|
||||||
match &self {
|
match &self {
|
||||||
Range::Linear { min, max } => (normalized * (max - min)) + min,
|
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<i32> for Range<i32> {
|
||||||
fn normalize(&self, plain: i32) -> f32 {
|
fn normalize(&self, plain: i32) -> f32 {
|
||||||
match &self {
|
match &self {
|
||||||
Range::Linear { min, max } => (plain - min) as f32 / (max - min) as f32,
|
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)
|
.clamp(0.0, 1.0)
|
||||||
}
|
}
|
||||||
|
@ -75,6 +170,29 @@ impl NormalizebleRange<i32> for Range<i32> {
|
||||||
let normalized = normalized.clamp(0.0, 1.0);
|
let normalized = normalized.clamp(0.0, 1.0);
|
||||||
match &self {
|
match &self {
|
||||||
Range::Linear { min, max } => (normalized * (max - min) as f32).round() as i32 + min,
|
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 }
|
Range::Linear { min: -10, max: 10 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_skewed_float_range(factor: f32) -> Range<f32> {
|
||||||
|
Range::Skewed {
|
||||||
|
min: 10.0,
|
||||||
|
max: 20.0,
|
||||||
|
factor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_skewed_int_range(factor: f32) -> Range<i32> {
|
||||||
|
Range::Skewed {
|
||||||
|
min: -10,
|
||||||
|
max: 10,
|
||||||
|
factor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_symmetrical_skewed_float_range(factor: f32) -> Range<f32> {
|
||||||
|
Range::SymmetricalSkewed {
|
||||||
|
min: 10.0,
|
||||||
|
max: 20.0,
|
||||||
|
factor,
|
||||||
|
center: 12.5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_symmetrical_skewed_int_range(factor: f32) -> Range<i32> {
|
||||||
|
Range::SymmetricalSkewed {
|
||||||
|
min: -10,
|
||||||
|
max: 10,
|
||||||
|
factor,
|
||||||
|
center: -3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod linear {
|
||||||
|
use super::super::*;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn range_normalize_linear_float() {
|
fn range_normalize_float() {
|
||||||
let range = make_linear_float_range();
|
let range = make_linear_float_range();
|
||||||
assert_eq!(range.normalize(17.5), 0.75);
|
assert_eq!(range.normalize(17.5), 0.75);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn range_normalize_linear_int() {
|
fn range_normalize_int() {
|
||||||
let range = make_linear_int_range();
|
let range = make_linear_int_range();
|
||||||
assert_eq!(range.normalize(-5), 0.25);
|
assert_eq!(range.normalize(-5), 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn range_unnormalize_linear_float() {
|
fn range_unnormalize_float() {
|
||||||
let range = make_linear_float_range();
|
let range = make_linear_float_range();
|
||||||
assert_eq!(range.unnormalize(0.25), 12.5);
|
assert_eq!(range.unnormalize(0.25), 12.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn range_unnormalize_linear_int() {
|
fn range_unnormalize_int() {
|
||||||
let range = make_linear_int_range();
|
let range = make_linear_int_range();
|
||||||
assert_eq!(range.unnormalize(0.75), 5);
|
assert_eq!(range.unnormalize(0.75), 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn range_unnormalize_linear_int_rounding() {
|
fn range_unnormalize_int_rounding() {
|
||||||
let range = make_linear_int_range();
|
let range = make_linear_int_range();
|
||||||
assert_eq!(range.unnormalize(0.73), 5);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -636,6 +636,8 @@ impl<P: Plugin> IEditController for Wrapper<'_, P> {
|
||||||
ParamPtr::FloatParam(_) => 0,
|
ParamPtr::FloatParam(_) => 0,
|
||||||
ParamPtr::IntParam(p) => match (**p).range {
|
ParamPtr::IntParam(p) => match (**p).range {
|
||||||
crate::param::Range::Linear { min, max } => max - min,
|
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,
|
ParamPtr::BoolParam(_) => 1,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue