Add reversed ranges
This commit is contained in:
parent
0168af7008
commit
f007945335
4 changed files with 145 additions and 51 deletions
|
@ -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
|
chronological order. If a new feature did not require any changes to existing
|
||||||
code then it will not be listed here.
|
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]
|
## [2022-07-06]
|
||||||
|
|
||||||
- The block smoothing API has been reworked. Instead of `Smoother`s having their
|
- The block smoothing API has been reworked. Instead of `Smoother`s having their
|
||||||
|
|
|
@ -139,26 +139,11 @@ impl Param for FloatParam {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn previous_step(&self, from: Self::Plain) -> Self::Plain {
|
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,
|
self.range.previous_step(from, self.step_size)
|
||||||
// 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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_step(&self, from: Self::Plain) -> Self::Plain {
|
fn next_step(&self, from: Self::Plain) -> Self::Plain {
|
||||||
// See above
|
self.range.next_step(from, self.step_size)
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalized_value_to_string(&self, normalized: f32, include_unit: bool) -> String {
|
fn normalized_value_to_string(&self, normalized: f32, include_unit: bool) -> String {
|
||||||
|
|
|
@ -131,11 +131,11 @@ impl Param for IntParam {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn previous_step(&self, from: Self::Plain) -> Self::Plain {
|
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 {
|
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 {
|
fn normalized_value_to_string(&self, normalized: f32, include_unit: bool) -> String {
|
||||||
|
|
|
@ -20,15 +20,19 @@ pub enum FloatRange {
|
||||||
factor: f32,
|
factor: f32,
|
||||||
center: 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
|
/// 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
|
/// ranges are supported for integers since hosts expect discrete parameters to have a fixed step
|
||||||
/// size.
|
/// size.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum IntRange {
|
pub enum IntRange {
|
||||||
/// The values are uniformly distributed between `min` and `max`.
|
/// The values are uniformly distributed between `min` and `max`.
|
||||||
Linear { min: i32, max: i32 },
|
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 {
|
impl FloatRange {
|
||||||
|
@ -42,7 +46,7 @@ impl FloatRange {
|
||||||
/// Normalize a plain, unnormalized value. Will be clamped to the bounds of the range if the
|
/// Normalize a plain, unnormalized value. Will be clamped to the bounds of the range if the
|
||||||
/// normalized value exceeds `[0, 1]`.
|
/// normalized value exceeds `[0, 1]`.
|
||||||
pub fn normalize(&self, plain: f32) -> f32 {
|
pub fn normalize(&self, plain: f32) -> f32 {
|
||||||
match &self {
|
match self {
|
||||||
FloatRange::Linear { min, max } => (plain.clamp(*min, *max) - min) / (max - min),
|
FloatRange::Linear { min, max } => (plain.clamp(*min, *max) - min) / (max - min),
|
||||||
FloatRange::Skewed { min, max, factor } => {
|
FloatRange::Skewed { min, max, factor } => {
|
||||||
((plain.clamp(*min, *max) - min) / (max - min)).powf(*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
|
(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.
|
/// would exceed that range.
|
||||||
pub fn unnormalize(&self, normalized: f32) -> f32 {
|
pub fn unnormalize(&self, normalized: f32) -> f32 {
|
||||||
let normalized = normalized.clamp(0.0, 1.0);
|
let normalized = normalized.clamp(0.0, 1.0);
|
||||||
match &self {
|
match self {
|
||||||
FloatRange::Linear { min, max } => (normalized * (max - min)) + min,
|
FloatRange::Linear { min, max } => (normalized * (max - min)) + min,
|
||||||
FloatRange::Skewed { min, max, factor } => {
|
FloatRange::Skewed { min, max, factor } => {
|
||||||
(normalized.powf(factor.recip()) * (max - min)) + min
|
(normalized.powf(factor.recip()) * (max - min)) + min
|
||||||
|
@ -104,36 +109,57 @@ impl FloatRange {
|
||||||
|
|
||||||
(skewed_proportion * (max - min)) + min
|
(skewed_proportion * (max - min)) + min
|
||||||
}
|
}
|
||||||
|
FloatRange::Reversed(range) => range.unnormalize(1.0 - normalized),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The minimum value in this range.
|
/// The range's previous discrete step from a certain value with a certain step size. If the step
|
||||||
pub fn min(&self) -> f32 {
|
/// 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>) -> 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 {
|
match self {
|
||||||
FloatRange::Linear { min, .. }
|
FloatRange::Linear { min, max }
|
||||||
| FloatRange::Skewed { min, .. }
|
| FloatRange::Skewed { min, max, .. }
|
||||||
| FloatRange::SymmetricalSkewed { min, .. } => *min,
|
| 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.
|
/// The range's next discrete step from a certain value with a certain step size. If the step
|
||||||
pub fn max(&self) -> f32 {
|
/// 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>) -> f32 {
|
||||||
|
// See above
|
||||||
match self {
|
match self {
|
||||||
FloatRange::Linear { max, .. }
|
FloatRange::Linear { min, max }
|
||||||
| FloatRange::Skewed { max, .. }
|
| FloatRange::Skewed { min, max, .. }
|
||||||
| FloatRange::SymmetricalSkewed { max, .. } => *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.
|
/// 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 {
|
pub fn snap_to_step(&self, value: f32, step_size: f32) -> f32 {
|
||||||
let (min, max) = match &self {
|
match self {
|
||||||
FloatRange::Linear { min, max } => (min, max),
|
FloatRange::Linear { min, max }
|
||||||
FloatRange::Skewed { min, max, .. } => (min, max),
|
| FloatRange::Skewed { min, max, .. }
|
||||||
FloatRange::SymmetricalSkewed { min, max, .. } => (min, max),
|
| FloatRange::SymmetricalSkewed { min, max, .. } => {
|
||||||
};
|
((value / step_size).round() * step_size).clamp(*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
|
/// Normalize a plain, unnormalized value. Will be clamped to the bounds of the range if the
|
||||||
/// normalized value exceeds `[0, 1]`.
|
/// normalized value exceeds `[0, 1]`.
|
||||||
pub fn normalize(&self, plain: i32) -> f32 {
|
pub fn normalize(&self, plain: i32) -> f32 {
|
||||||
match &self {
|
match self {
|
||||||
IntRange::Linear { min, max } => (plain - min) as f32 / (max - min) as f32,
|
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)
|
.clamp(0.0, 1.0)
|
||||||
}
|
}
|
||||||
|
@ -151,22 +178,25 @@ impl IntRange {
|
||||||
/// would exceed that range.
|
/// would exceed that range.
|
||||||
pub fn unnormalize(&self, normalized: f32) -> i32 {
|
pub fn unnormalize(&self, normalized: f32) -> i32 {
|
||||||
let normalized = normalized.clamp(0.0, 1.0);
|
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::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.
|
/// The range's previous discrete step from a certain value.
|
||||||
pub fn min(&self) -> i32 {
|
pub fn previous_step(&self, from: i32) -> i32 {
|
||||||
match self {
|
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.
|
/// The range's next discrete step from a certain value.
|
||||||
pub fn max(&self) -> i32 {
|
pub fn next_step(&self, from: i32) -> i32 {
|
||||||
match self {
|
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 {
|
pub fn step_count(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
IntRange::Linear { min, max } => (max - min) as usize,
|
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 {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn make_linear_float_range() -> FloatRange {
|
const fn make_linear_float_range() -> FloatRange {
|
||||||
FloatRange::Linear {
|
FloatRange::Linear {
|
||||||
min: 10.0,
|
min: 10.0,
|
||||||
max: 20.0,
|
max: 20.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_linear_int_range() -> IntRange {
|
const fn make_linear_int_range() -> IntRange {
|
||||||
IntRange::Linear { min: -10, max: 10 }
|
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 {
|
FloatRange::Skewed {
|
||||||
min: 10.0,
|
min: 10.0,
|
||||||
max: 20.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 {
|
FloatRange::SymmetricalSkewed {
|
||||||
min: 10.0,
|
min: 10.0,
|
||||||
max: 20.0,
|
max: 20.0,
|
||||||
|
@ -307,4 +346,68 @@ mod tests {
|
||||||
assert_eq!(range.unnormalize(0.951801), 17.5);
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue