cutan.impacts.stroke

The stroke: impact events -> one continuous height curve h(t).

h is the object’s height above its impact position, in units of a full stroke: 0 is contact (or, in the air, the turning point) and 1 a full preparation. Every object in cutan.impacts.objects maps h AFFINELY onto one animated property (a stick’s rotation, a ball’s y), which is what makes the curve below exactly the curve the renderer draws: an eased tween of the property IS the same easing of h.

The curve is a chain of StrokeSegments built only from the quadratic easings the cutout runtime already has, and each is chosen for its physics:

  • surface — the object falls with ease_in (constant acceleration, like gravity) and reaches h = 0 at full speed, then leaves with ease_out: a velocity reversal AT the impact. Contact is visible in position (the object stops at the surface) and as a kink in velocity.

  • air — no surface. The fall is ease_in and then a short ease_out brake of brake seconds that brings the object to rest at h = 0; it then leaves with ease_in_out. The executed “impact” is that turning point — the moment the stroke’s lowest point is reached — and the peak speed comes brake seconds BEFORE it. The brake’s distance is chosen so velocity is continuous into it (d = apex * brake / fall), so the only discontinuity is in acceleration — a muscle stopping a limb, not a collision.

In both kinds the speed at the peak is 2 * apex / fall.

>>> from cutan.impacts.performance import perform
>>> events = perform(120, beats=3)
>>> s = build_stroke(events, kind="surface", duration=2.0)
>>> [s.h(e.t_impact) for e in events]
[0.0, 0.0, 0.0]
>>> round(s.h(0.0), 6), round(s.h(0.75), 6)   # at rest, then the apex between hits
(1.0, 1.0)
>>> a = build_stroke(events, kind="air", duration=2.0)
>>> [round(a.velocity(e.t_impact), 9) for e in events]   # the air stroke TURNS
[0.0, 0.0, 0.0]

Module Attributes

DEFAULT_RISE

Longest rise after an impact, and longest fall into one (seconds).

DEFAULT_BRAKE

The air stroke's braking time before its turning point (seconds).

Functions

build_stroke(events, *[, kind, rise, fall, ...])

Chain rise / hold / fall segments through every executed impact.

Classes

Stroke(kind, duration, segments, kinematics)

The whole curve, plus the kinematics of every impact on it.

StrokeKinematics(index, kind, t_impact, ...)

How the object moved into one impact.

StrokeSegment(t0, t1, h0, h1, easing)

h goes from h0 to h1 over [t0, t1] under easing.

StrokeTiming(rise, fall, brake)

One impact's own timings (seconds): the fall into it, the air brake before it, and the rise out of it (cutan#27: stroke-shape variability, drawn per stroke).

Exceptions

StrokeError

A stroke that cannot be built from these events.

cutan.impacts.stroke.DEFAULT_BRAKE: float = 0.03

The air stroke’s braking time before its turning point (seconds).

cutan.impacts.stroke.DEFAULT_RISE: float = 0.18

Longest rise after an impact, and longest fall into one (seconds). A slower tempo holds at the apex between them instead of floating: a drummer’s stroke takes about as long at 60 BPM as at 120, it is the wait that changes.

class cutan.impacts.stroke.Stroke(kind, duration, segments, kinematics)[source]

Bases: object

The whole curve, plus the kinematics of every impact on it.

h(t)[source]

Stroke height at scene time t (clamped to the clip).

Return type:

float

velocity(t)[source]

dh/dt at t; at a segment boundary, the LATER segment’s value.

Return type:

float

exception cutan.impacts.stroke.StrokeError[source]

Bases: ValueError

A stroke that cannot be built from these events.

class cutan.impacts.stroke.StrokeKinematics(index, kind, t_impact, t_peak_speed, peak_speed, apex, fall, brake, rise=0.0)[source]

Bases: object

How the object moved into one impact.

t_peak_speed equals t_impact for a surface impact and precedes it by brake for an air impact; peak_speed is in stroke heights per second (multiply by the object’s stroke extent for pixels or radians).

rise: float

The rise out of this impact as executed (the next stroke’s preparation).

class cutan.impacts.stroke.StrokeSegment(t0, t1, h0, h1, easing)[source]

Bases: object

h goes from h0 to h1 over [t0, t1] under easing.

class cutan.impacts.stroke.StrokeTiming(rise, fall, brake)[source]

Bases: object

One impact’s own timings (seconds): the fall into it, the air brake before it, and the rise out of it (cutan#27: stroke-shape variability, drawn per stroke). Each is the longest it may take: a fast tempo still shortens it to fit.

cutan.impacts.stroke.build_stroke(events, *, kind='surface', duration, rise=0.18, fall=0.18, brake=0.03, rest_height=1.0, timings=None)[source]

Chain rise / hold / fall segments through every executed impact.

The object starts and ends at rest_height; before impact k it is raised to events[k].amplitude (a bigger preparation, a harder hit). Segments tile [0, duration] exactly, and every impact is a segment boundary at precisely t_impact. timings gives each impact its own rise, fall and brake (one per event, cutan#27); without it every stroke takes rise, fall and brake.

Return type:

Stroke