///|
/// Bounds used to keep generated motion within a safe output range.
pub struct MotionBounds {
  minimum : Double
  maximum : Double
} derive(Debug)

///|
pub fn MotionBounds::new(
  minimum : Double,
  maximum : Double,
) -> MotionBounds raise MotionError {
  if maximum < minimum {
    raise MotionError::InvalidTime(maximum)
  }
  { minimum, maximum }
}

///|
pub fn MotionBounds::minimum(self : MotionBounds) -> Double {
  self.minimum
}

///|
pub fn MotionBounds::maximum(self : MotionBounds) -> Double {
  self.maximum
}

///|
pub fn MotionBounds::clamp(self : MotionBounds, value : Double) -> Double {
  if value < self.minimum {
    self.minimum
  } else if value > self.maximum {
    self.maximum
  } else {
    value
  }
}

///|
pub fn clamp_values(
  values : Array[Double],
  bounds : MotionBounds,
) -> Array[Double] {
  values.map(fn(value) { bounds.clamp(value) })
}

///|
pub fn blend_values(
  first : Array[Double],
  second : Array[Double],
  amount : Double,
) -> Array[Double] {
  let count = first.length().min(second.length())
  let weight = clamp01(amount)
  let result : Array[Double] = []
  for i in 0.. Double {
  if maximum <= minimum {
    minimum
  } else {
    let center = (minimum + maximum) / 2.0
    let half = (maximum - minimum) / 2.0
    let normalized = (value - center) / half
    let strength = if softness <= 0.0 { 1.0 } else { softness }
    center + half * (normalized / (1.0 + @math.pow(normalized.abs(), strength)))
  }
}

///|
pub fn enforce_monotonic(
  values : Array[Double],
  increasing : Bool,
) -> Array[Double] {
  if values.length() == 0 {
    return []
  }
  let result : Array[Double] = [values[0]]
  for i in 1..