subsequence.easing ================== .. py:module:: subsequence.easing .. autoapi-nested-parse:: Easing functions for transitions and ramps. Easing functions map a normalised progress value *t* in [0, 1] to an eased output in [0, 1]. They are used by ``conductor.line()``, ``target_bpm()``, ``cc_ramp()``, and ``pitch_bend_ramp()`` to shape how a value moves from a start to an end over time. Pass a name string or a plain callable to any ``shape`` parameter: composition.conductor.line("filter", 0, 1, 64, shape="ease_in_out") composition.target_bpm(140, bars=8, shape="s_curve") p.cc_ramp(74, 0, 127, shape="exponential") # Custom callable — receives and returns a float in [0, 1]: p.cc_ramp(74, 0, 127, shape=lambda t: t ** 0.5) Available shapes: "linear" Constant rate (default). "ease_in" Slow start, accelerates — fade-ins, building tension. "ease_out" Fast start, decelerates — fade-outs, natural decay. "ease_in_out" Smooth S-curve (Hermite smoothstep) — BPM changes, crossfades. "exponential" Very slow start, rapid end (cubic) — filter sweeps. "logarithmic" Rapid start, very gradual end (cubic) — volume fades. "s_curve" Smoother S-curve (Perlin smootherstep) — long, gentle transitions. All functions satisfy f(0) = 0 and f(1) = 1 and are monotonically non-decreasing. Input outside [0, 1] is not defined. Module Contents --------------- .. py:class:: EasedValue(initial: Optional[float] = None) Smoothly interpolates between discrete data updates. When external data arrives in snapshots — API polls, sensor readings, OSC messages — jumping instantly to each new value often sounds jarring. ``EasedValue`` remembers the previous value and provides a smooth, eased interpolation to the new one over a normalised progress window. A typical use-case is a ``composition.schedule()`` function that writes to ``composition.data`` every *N* bars, paired with a pattern that reads the smoothed value on every rebuild: Example:: # Module level: create one per data field you want to smooth. iss_lat = subsequence.easing.EasedValue(initial=0.5) # Scheduled task (fires every 16 bars): def fetch_data (p): new_lat = get_latest_latitude() # 0.0–1.0 iss_lat.update(new_lat) # Pattern (rebuilds every bar). 16-bar cycle matches the schedule. @composition.pattern(channel=0, length=4) def drums (p): progress = (p.cycle % 16) / 16 # 0 → 1 over one fetch cycle velocity = int(100 * iss_lat.get(progress)) p.hit_steps("kick_1", range(16), velocity=velocity) :param initial: Optional starting value. If provided, the first call to :meth:`update` will ease from this initial value. If omitted, the first call to :meth:`update` will instantly set both the *previous* and *current* values to the new target, preventing an unintended transition from a default value. .. py:method:: get(progress: float, shape: Union[str, EasingFn] = 'ease_in_out') -> float Return the interpolated value at *progress* through the transition. :param progress: How far through the current transition, in [0, 1]. ``0.0`` returns the previous value; ``1.0`` returns the current target. Typically computed as ``(p.cycle % N) / N`` where *N* is the number of pattern cycles per data-fetch cycle. :param shape: Easing shape name (see :data:`EASING_FUNCTIONS`) or a callable ``f(t) -> t`` in [0, 1]. Defaults to ``"ease_in_out"`` (Hermite smoothstep). :returns: The interpolated float between the previous and current value. .. py:method:: update(value: float) -> None Accept a new target value. The current value becomes the new *previous* baseline, and *value* becomes the target that :meth:`get` interpolates toward. If no ``initial`` value was provided at construction, the very first call to this method sets both *previous* and *current* to *value*. :param value: The new target, typically a normalised float in [0, 1] (though any numeric range is accepted as long as consumers interpret it consistently). .. py:property:: current :type: float The most recently set target value (after the last :meth:`update`). .. py:property:: delta :type: float Signed change between the previous and current value. Positive means the value rose on the last :meth:`update`; negative means it fell; zero means it was unchanged. The magnitude reflects the size of the jump. This property is constant across all pattern rebuilds within one fetch cycle, making it straightforward to branch on direction without worrying about sample-level fluctuations: Example:: # Choose arpeggio direction based on which way the value is moving. direction = "up" if iss_lat.delta >= 0 else "down" p.arpeggio(pitches, spacing=0.25, direction=direction) # Scale an effect by how large the change was. urgency = abs(iss_lat.delta) # 0.0 = stable, larger = big jump .. py:property:: previous :type: float The value that was current before the last :meth:`update`. .. py:function:: ease_in(t: float) -> float Quadratic ease-in: slow start, accelerates toward the end. .. py:function:: ease_in_out(t: float) -> float Hermite smoothstep S-curve: smooth start and end, faster in the middle. .. py:function:: ease_out(t: float) -> float Quadratic ease-out: fast start, decelerates toward the end. .. py:function:: exponential(t: float) -> float Cubic ease-in: very slow start with rapid acceleration. Approximates a perceptually linear response for audio parameters like filter cutoff, where the human ear's logarithmic sensitivity means a slow early ramp sounds more even. .. py:function:: get_easing(shape: Union[str, EasingFn]) -> EasingFn Return the easing function for *shape*. *shape* may be a name string (see :data:`EASING_FUNCTIONS`) or any callable that maps a float in [0, 1] to a float in [0, 1]. Raises :class:`ValueError` for unknown string names. .. py:function:: linear(t: float) -> float No transformation — constant rate of change. .. py:function:: logarithmic(t: float) -> float Cubic ease-out: rapid initial change that tapers to a gradual end. Useful for decay shapes and volume fades where most of the audible change happens early and the tail fades imperceptibly. .. py:function:: map_value(value: float, in_min: float = 0.0, in_max: float = 1.0, out_min: float = 0.0, out_max: float = 1.0, shape: Union[str, EasingFn] = 'linear', clamp: bool = True) -> float Map a value from an input range to an output range, with optional easing. Linearly maps *value* from the range ``[in_min, in_max]`` into a normalised progress ratio [0.0, 1.0], applies the designated easing curve, and interpolates that eased ratio into the output range ``[out_min, out_max]``. This is particularly useful for musically scaling raw generative outputs (which usually fall between 0.0 and 1.0) into MIDI ranges like pitch or velocity, while automatically applying musical volume or tension curves. :param value: The raw input to scale. :param in_min: The lower bound of the input's expected range. :param in_max: The upper bound of the input's expected range. :param out_min: The lower bound of the mapped output range. :param out_max: The upper bound of the mapped output range. :param shape: The easing curve to apply to the mapped ratio before outputting (e.g. "linear", "ease_in_out"). See :func:`get_easing` for all available shapes. :param clamp: If True (the default), values outside the input range will be clamped so they never exceed the output bounds. Essential for ensuring MIDI values don't break valid ranges. :returns: The mapped and eased value as a float. .. py:function:: ramp(n: int, low: float = 0.0, high: float = 1.0, shape: Union[str, EasingFn] = 'linear') -> List[float] Return a list of *n* values eased from *low* to *high*. This is the batch equivalent of :func:`map_value` for the common case of generating a fixed-length sequence that sweeps from one value to another. Useful for velocity ramps, density envelopes, filter sweeps, or any per-step value that should follow a curve. :param n: Number of values to generate. :param low: The value at the first step (t = 0). :param high: The value at the last step (t = 1). :param shape: Easing curve name or callable (see :data:`EASING_FUNCTIONS`). :returns: ``[int(v) for v in easing.ramp(p.grid, 50, 100, "ease_in_out")]`` :rtype: ``List[float]`` of length *n*. For MIDI velocity use, cast to int Example:: # Snare roll that swells into a hit velocities = [int(v) for v in easing.ramp(p.grid, 30, 100, "ease_in")] p.sequence(steps=range(16), pitches="snare_1", velocities=velocities) # Ghost fill velocity envelope passed directly (float values accepted) p.ghost_fill("snare_1", 1, velocity=easing.ramp(p.grid, 20, 80, "ease_out"), bias="sixteenths", no_overlap=True) .. py:function:: s_curve(t: float) -> float Perlin smootherstep: a smoother S-curve than ease_in_out. Has zero first *and* second derivatives at t=0 and t=1, eliminating the subtle acceleration jerk at the boundaries. Best for long, slow transitions where the smoothness is perceptible.