2022-02-02 07:06:13 +11:00
|
|
|
//! Different ranges for numeric parameters.
|
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
/// A distribution for a floating point parameter's range. All range endpoints are inclusive.
|
2022-02-02 07:06:13 +11:00
|
|
|
#[derive(Debug)]
|
2022-03-04 05:24:40 +11:00
|
|
|
pub enum FloatRange {
|
2022-02-02 09:37:28 +11:00
|
|
|
/// The values are uniformly distributed between `min` and `max`.
|
2022-03-04 05:24:40 +11:00
|
|
|
Linear { min: f32, max: f32 },
|
2022-02-02 09:37:28 +11:00
|
|
|
/// The range is skewed by a factor. Values above 1.0 will make the end of the range wider,
|
2022-03-04 07:22:20 +11:00
|
|
|
/// while values between 0 and 1 will skew the range towards the start. Use
|
2022-03-04 09:05:01 +11:00
|
|
|
/// [`FloatRange::skew_factor()`] for a more intuitively way to calculate the skew factor where
|
2022-03-04 07:22:20 +11:00
|
|
|
/// positive values skew the range towards the end while negative values skew the range toward
|
|
|
|
/// the start.
|
2022-03-04 05:24:40 +11:00
|
|
|
Skewed { min: f32, max: f32, factor: f32 },
|
2022-03-04 09:05:01 +11:00
|
|
|
/// The same as [`FloatRange::Skewed`], but with the skewing happening from a central point.
|
|
|
|
/// This central point is rescaled to be at 50% of the parameter's range for convenience of use.
|
|
|
|
/// Git blame this comment to find a version that doesn't do this.
|
2022-02-02 09:37:28 +11:00
|
|
|
SymmetricalSkewed {
|
2022-03-04 05:24:40 +11:00
|
|
|
min: f32,
|
|
|
|
max: f32,
|
2022-02-02 09:37:28 +11:00
|
|
|
factor: f32,
|
2022-03-04 05:24:40 +11:00
|
|
|
center: f32,
|
2022-02-02 09:37:28 +11:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
/// 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)]
|
|
|
|
pub enum IntRange {
|
|
|
|
/// The values are uniformly distributed between `min` and `max`.
|
|
|
|
Linear { min: i32, max: i32 },
|
2022-02-02 07:06:13 +11:00
|
|
|
}
|
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
impl Default for FloatRange {
|
2022-02-02 07:06:13 +11:00
|
|
|
fn default() -> Self {
|
|
|
|
Self::Linear { min: 0.0, max: 1.0 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
impl Default for IntRange {
|
2022-02-02 07:06:13 +11:00
|
|
|
fn default() -> Self {
|
|
|
|
Self::Linear { min: 0, max: 1 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
impl FloatRange {
|
2022-03-04 09:05:01 +11:00
|
|
|
/// Calculate a skew factor for [`FloatRange::Skewed`] and [`FloatRange::SymmetricalSkewed`].
|
2022-03-04 07:22:20 +11:00
|
|
|
/// Positive values make the end of the range wider while negative make the start of the range
|
|
|
|
/// wider.
|
2022-03-04 05:24:40 +11:00
|
|
|
pub fn skew_factor(factor: f32) -> f32 {
|
|
|
|
2.0f32.powf(factor)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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 {
|
2022-02-02 07:06:13 +11:00
|
|
|
match &self {
|
2022-03-04 05:24:40 +11:00
|
|
|
FloatRange::Linear { min, max } => (plain - min) / (max - min),
|
|
|
|
FloatRange::Skewed { min, max, factor } => ((plain - min) / (max - min)).powf(*factor),
|
|
|
|
FloatRange::SymmetricalSkewed {
|
2022-02-02 09:37:28 +11:00
|
|
|
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();
|
2022-02-02 10:05:50 +11:00
|
|
|
(scaled_proportion.powf(*factor) * 0.5) + 0.5
|
2022-02-02 09:37:28 +11:00
|
|
|
} 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();
|
2022-02-02 10:05:50 +11:00
|
|
|
(1.0 - inverted_scaled_proportion.powf(*factor)) * 0.5
|
2022-02-02 09:37:28 +11:00
|
|
|
}
|
|
|
|
}
|
2022-02-02 07:06:13 +11:00
|
|
|
}
|
|
|
|
.clamp(0.0, 1.0)
|
|
|
|
}
|
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
/// Unnormalize a normalized value. Will be clamped to `[0, 1]` if the plain, unnormalized value
|
|
|
|
/// would exceed that range.
|
|
|
|
pub fn unnormalize(&self, normalized: f32) -> f32 {
|
2022-02-02 07:06:13 +11:00
|
|
|
let normalized = normalized.clamp(0.0, 1.0);
|
|
|
|
match &self {
|
2022-03-04 05:24:40 +11:00
|
|
|
FloatRange::Linear { min, max } => (normalized * (max - min)) + min,
|
|
|
|
FloatRange::Skewed { min, max, factor } => {
|
2022-02-02 09:37:28 +11:00
|
|
|
(normalized.powf(factor.recip()) * (max - min)) + min
|
|
|
|
}
|
2022-03-04 05:24:40 +11:00
|
|
|
FloatRange::SymmetricalSkewed {
|
2022-02-02 09:37:28 +11:00
|
|
|
min,
|
|
|
|
max,
|
|
|
|
factor,
|
|
|
|
center,
|
|
|
|
} => {
|
|
|
|
// Reconstructing the subranges works the same as with the normal skewed ranges
|
|
|
|
let center_proportion = (center - min) / (max - min);
|
2022-02-02 10:05:50 +11:00
|
|
|
let skewed_proportion = if normalized > 0.5 {
|
|
|
|
let scaled_proportion = (normalized - 0.5) * 2.0;
|
2022-02-02 09:37:28 +11:00
|
|
|
(scaled_proportion.powf(factor.recip()) * (1.0 - center_proportion))
|
|
|
|
+ center_proportion
|
|
|
|
} else {
|
2022-02-02 10:05:50 +11:00
|
|
|
let inverted_scaled_proportion = (0.5 - normalized) * 2.0;
|
|
|
|
(1.0 - inverted_scaled_proportion.powf(factor.recip())) * center_proportion
|
2022-02-02 09:37:28 +11:00
|
|
|
};
|
|
|
|
|
|
|
|
(skewed_proportion * (max - min)) + min
|
|
|
|
}
|
2022-02-02 07:06:13 +11:00
|
|
|
}
|
|
|
|
}
|
2022-02-14 03:52:54 +11:00
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
/// 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 {
|
2022-02-14 03:52:54 +11:00
|
|
|
let (min, max) = match &self {
|
2022-03-04 05:24:40 +11:00
|
|
|
FloatRange::Linear { min, max } => (min, max),
|
|
|
|
FloatRange::Skewed { min, max, .. } => (min, max),
|
|
|
|
FloatRange::SymmetricalSkewed { min, max, .. } => (min, max),
|
2022-02-14 03:52:54 +11:00
|
|
|
};
|
|
|
|
|
|
|
|
((value / step_size).round() * step_size).clamp(*min, *max)
|
|
|
|
}
|
2022-02-02 07:06:13 +11:00
|
|
|
}
|
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
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 {
|
2022-02-02 07:06:13 +11:00
|
|
|
match &self {
|
2022-03-04 05:24:40 +11:00
|
|
|
IntRange::Linear { min, max } => (plain - min) as f32 / (max - min) as f32,
|
2022-02-02 07:06:13 +11:00
|
|
|
}
|
|
|
|
.clamp(0.0, 1.0)
|
|
|
|
}
|
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
/// Unnormalize a normalized value. Will be clamped to `[0, 1]` if the plain, unnormalized value
|
|
|
|
/// would exceed that range.
|
|
|
|
pub fn unnormalize(&self, normalized: f32) -> i32 {
|
2022-02-02 07:06:13 +11:00
|
|
|
let normalized = normalized.clamp(0.0, 1.0);
|
|
|
|
match &self {
|
2022-03-04 05:24:40 +11:00
|
|
|
IntRange::Linear { min, max } => (normalized * (max - min) as f32).round() as i32 + min,
|
2022-02-02 07:06:13 +11:00
|
|
|
}
|
|
|
|
}
|
2022-02-14 03:52:54 +11:00
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
/// The number of steps in this range, if it is stepped. Used for the host's generic UI.
|
|
|
|
pub fn step_count(&self) -> Option<usize> {
|
2022-03-03 23:55:54 +11:00
|
|
|
match self {
|
2022-03-04 05:24:40 +11:00
|
|
|
IntRange::Linear { min, max } => Some((max - min) as usize),
|
2022-03-03 23:55:54 +11:00
|
|
|
}
|
|
|
|
}
|
2022-02-02 07:06:13 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
fn make_linear_float_range() -> FloatRange {
|
|
|
|
FloatRange::Linear {
|
2022-02-02 07:06:13 +11:00
|
|
|
min: 10.0,
|
|
|
|
max: 20.0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
fn make_linear_int_range() -> IntRange {
|
|
|
|
IntRange::Linear { min: -10, max: 10 }
|
2022-02-02 07:06:13 +11:00
|
|
|
}
|
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
fn make_skewed_float_range(factor: f32) -> FloatRange {
|
|
|
|
FloatRange::Skewed {
|
2022-02-02 09:37:28 +11:00
|
|
|
min: 10.0,
|
|
|
|
max: 20.0,
|
|
|
|
factor,
|
|
|
|
}
|
2022-02-02 07:06:13 +11:00
|
|
|
}
|
|
|
|
|
2022-03-04 05:24:40 +11:00
|
|
|
fn make_symmetrical_skewed_float_range(factor: f32) -> FloatRange {
|
|
|
|
FloatRange::SymmetricalSkewed {
|
2022-02-02 09:37:28 +11:00
|
|
|
min: 10.0,
|
|
|
|
max: 20.0,
|
|
|
|
factor,
|
|
|
|
center: 12.5,
|
|
|
|
}
|
2022-02-02 07:06:13 +11:00
|
|
|
}
|
|
|
|
|
2022-02-14 03:52:54 +11:00
|
|
|
#[test]
|
|
|
|
fn step_size() {
|
|
|
|
// These are weird step sizes, but if it works here then it will work for anything
|
|
|
|
let range = make_linear_float_range();
|
2022-02-14 04:07:19 +11:00
|
|
|
// XXX: We round to decimal places when outputting, but not when snapping to steps
|
|
|
|
assert_eq!(range.snap_to_step(13.0, 4.73), 14.190001);
|
2022-02-14 03:52:54 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn step_size_clamping() {
|
|
|
|
let range = make_linear_float_range();
|
|
|
|
assert_eq!(range.snap_to_step(10.0, 4.73), 10.0);
|
|
|
|
assert_eq!(range.snap_to_step(20.0, 6.73), 20.0);
|
|
|
|
}
|
|
|
|
|
2022-02-02 09:37:28 +11:00
|
|
|
mod linear {
|
|
|
|
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::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn range_normalize_float() {
|
2022-03-04 05:24:40 +11:00
|
|
|
let range = make_skewed_float_range(FloatRange::skew_factor(-2.0));
|
2022-02-05 04:42:42 +11:00
|
|
|
assert_eq!(range.normalize(17.5), 0.9306049);
|
2022-02-02 09:37:28 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn range_unnormalize_float() {
|
2022-03-04 05:24:40 +11:00
|
|
|
let range = make_skewed_float_range(FloatRange::skew_factor(-2.0));
|
2022-02-05 04:42:42 +11:00
|
|
|
assert_eq!(range.unnormalize(0.9306049), 17.5);
|
2022-02-02 09:37:28 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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_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)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mod symmetrical_skewed {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn range_normalize_float() {
|
2022-03-04 05:24:40 +11:00
|
|
|
let range = make_symmetrical_skewed_float_range(FloatRange::skew_factor(-2.0));
|
2022-02-02 10:05:50 +11:00
|
|
|
assert_eq!(range.normalize(17.5), 0.951801);
|
2022-02-02 09:37:28 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn range_unnormalize_float() {
|
2022-03-04 05:24:40 +11:00
|
|
|
let range = make_symmetrical_skewed_float_range(FloatRange::skew_factor(-2.0));
|
2022-02-02 10:05:50 +11:00
|
|
|
assert_eq!(range.unnormalize(0.951801), 17.5);
|
2022-02-02 09:37:28 +11:00
|
|
|
}
|
2022-02-02 07:06:13 +11:00
|
|
|
}
|
|
|
|
}
|