subsequence.easing¶
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¶
- class subsequence.easing.EasedValue(initial: float | None = None)[source]¶
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.
EasedValueremembers 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 tocomposition.dataevery 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)
- Parameters:
initial – Optional starting value. If provided, the first call to
update()will ease from this initial value. If omitted, the first call toupdate()will instantly set both the previous and current values to the new target, preventing an unintended transition from a default value.
- get(progress: float, shape: str | EasingFn = 'ease_in_out') float[source]¶
Return the interpolated value at progress through the transition.
- Parameters:
progress – How far through the current transition, in [0, 1].
0.0returns the previous value;1.0returns the current target. Typically computed as(p.cycle % N) / Nwhere N is the number of pattern cycles per data-fetch cycle.shape – Easing shape name (see
EASING_FUNCTIONS) or a callablef(t) -> tin [0, 1]. Defaults to"ease_in_out"(Hermite smoothstep).
- Returns:
The interpolated float between the previous and current value.
- update(value: float) None[source]¶
Accept a new target value.
The current value becomes the new previous baseline, and value becomes the target that
get()interpolates toward. If noinitialvalue was provided at construction, the very first call to this method sets both previous and current to value.- Parameters:
value – The new target, typically a normalised float in [0, 1] (though any numeric range is accepted as long as consumers interpret it consistently).
- property delta: float[source]¶
Signed change between the previous and current value.
Positive means the value rose on the last
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
- subsequence.easing.ease_in(t: float) float[source]¶
Quadratic ease-in: slow start, accelerates toward the end.
- subsequence.easing.ease_in_out(t: float) float[source]¶
Hermite smoothstep S-curve: smooth start and end, faster in the middle.
- subsequence.easing.ease_out(t: float) float[source]¶
Quadratic ease-out: fast start, decelerates toward the end.
- subsequence.easing.exponential(t: float) float[source]¶
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.
- subsequence.easing.get_easing(shape: str | EasingFn) EasingFn[source]¶
Return the easing function for shape.
shape may be a name string (see
EASING_FUNCTIONS) or any callable that maps a float in [0, 1] to a float in [0, 1].Raises
ValueErrorfor unknown string names.
- subsequence.easing.logarithmic(t: float) float[source]¶
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.
- subsequence.easing.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: str | EasingFn = 'linear', clamp: bool = True) float[source]¶
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.
- Parameters:
value – The raw input to scale.
in_min – The lower bound of the input’s expected range.
in_max – The upper bound of the input’s expected range.
out_min – The lower bound of the mapped output range.
out_max – The upper bound of the mapped output range.
shape – The easing curve to apply to the mapped ratio before outputting (e.g. “linear”, “ease_in_out”). See
get_easing()for all available shapes.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.
- subsequence.easing.ramp(n: int, low: float = 0.0, high: float = 1.0, shape: str | EasingFn = 'linear') List[float][source]¶
Return a list of n values eased from low to high.
This is the batch equivalent of
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.- Parameters:
n – Number of values to generate.
low – The value at the first step (t = 0).
high – The value at the last step (t = 1).
shape – Easing curve name or callable (see
EASING_FUNCTIONS).
- Returns:
[int(v) for v in easing.ramp(p.grid, 50, 100, "ease_in_out")]- Return type:
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)