cutan.impacts.performance¶
The performance: a tempo grid, and when each impact was intended and executed.
Three times are kept apart on purpose, because scoring a sub-frame estimator is meaningless if they blur together:
t_grid— the INTENDED time: where the beat grid (aTempoMap) put the event. What a quantiser would snap to.t_impact— the EXECUTED time, in continuous seconds: the grid time plus a humanisation offset. This is when the object actually hits (or, in the air, turns); the motion is built so it happens at exactly this float, never on a frame.what the frames show — not here at all; that is
cutan.impacts.truth, after aan.frame_clock.FrameClockhas sampled the motion.
>>> events = perform(120, beats=4)
>>> [e.t_grid for e in events]
[0.5, 1.0, 1.5, 2.0]
>>> all(e.t_impact == e.t_grid for e in events) # no humanisation by default
True
A tempo change is a TempoMap with more than one point — here an
accelerando from 60 to 120 BPM over four beats, so the gaps shrink:
>>> ts = [e.t_grid for e in perform([(0, 60), (4, 120)], beats=5, lead_in=0.0)]
>>> [round(b - a, 3) for a, b in zip(ts, ts[1:])]
[0.893, 0.729, 0.617, 0.534]
Module Attributes
Seconds before the first grid beat. |
|
A humanisation offset is clamped to this fraction of the gap to each neighbouring grid event, so executed impacts can never swap order or collide — whatever the jitter's standard deviation. |
|
|
Functions
|
Offsets from an AR(1) Gaussian process: |
|
The impacts of |
|
Coerce a bpm, a |
Classes
|
One impact: where the grid put it and when it was executed. |
|
Tempo as a function of beat: piecewise-linear BPM between |
Exceptions
A performance description that cannot be played. |
- cutan.impacts.performance.DEFAULT_LEAD_IN: float = 0.5¶
Seconds before the first grid beat. Long enough for the first stroke’s preparation and for any humanisation to pull the first impact early.
- cutan.impacts.performance.Humanizer¶
(grid_times, rng) -> offsets, in seconds, one per grid time. The seam for timing models richer thangaussian_humanizer()(a learned groove, a drummer’s measured microtiming).alias of
Callable[[Sequence[float],object],Sequence[float]]
- class cutan.impacts.performance.ImpactEvent(index, beat, t_grid, t_impact, amplitude)[source]¶
Bases:
objectOne impact: where the grid put it and when it was executed.
- cutan.impacts.performance.MAX_OFFSET_FRACTION: float = 0.4¶
A humanisation offset is clamped to this fraction of the gap to each neighbouring grid event, so executed impacts can never swap order or collide — whatever the jitter’s standard deviation.
- exception cutan.impacts.performance.PerformanceError[source]¶
Bases:
ValueErrorA performance description that cannot be played.
- class cutan.impacts.performance.TempoMap(points)[source]¶
Bases:
objectTempo as a function of beat: piecewise-linear BPM between
(beat, bpm)points.Constant before the first point and after the last. Linear in BEATS, not in seconds, which is how a score writes an accelerando (“speed up over these four bars”); the time of a beat is the exact integral of
60 / bpm.>>> TempoMap(((0, 120),)).time_of(3) 1.5 >>> m = TempoMap(((0, 60), (4, 120))) >>> round(m.time_of(4), 6) # 4 * 60/(120-60) * ln(120/60) 2.772589 >>> m.bpm_at(2), m.bpm_at(10) (90.0, 120.0)
- cutan.impacts.performance.gaussian_humanizer(sd=0.0, *, rho=0.0, bias=0.0)[source]¶
Offsets from an AR(1) Gaussian process:
o[k] = bias + rho*(o[k-1]-bias) + e.sdis the STATIONARY standard deviation of the offsets (seconds) — the innovation is scaled bysqrt(1 - rho**2)so changingrhochanges how the timing wanders, not how far.rho = 0is independent jitter;rhonear 1 is a player who drifts ahead or behind for several beats.biasis a constant lead (negative) or lag (positive).>>> import numpy as np >>> h = gaussian_humanizer(0.01, rho=0.5) >>> offs = h([0.0] * 20000, np.random.default_rng(0)) >>> round(float(np.std(offs)), 3) 0.01 >>> gaussian_humanizer()([0.0, 1.0], None) [0.0, 0.0]
- cutan.impacts.performance.perform(tempo=100.0, *, beats=16, subdivision=1, pattern=(1.0,), lead_in=0.5, humanizer=None, seed=0)[source]¶
The impacts of
beatsbeats ofpattern, ontempo’s grid.subdivisiongrid steps per beat;patternis cycled over the steps, one value per step:0is a rest, anything in(0, 1]is a hit of that relative stroke height (an accent is a bigger stroke).lead_inshifts beat 0 to that many seconds into the clip.humanizerturns grid times into offsets (default: none); each offset is then clamped toMAX_OFFSET_FRACTIONof the gap to either neighbour, and the clamped value is what the event records — the ground truth is what was executed.>>> [e.beat for e in perform(120, beats=2, subdivision=2, pattern=(1, 0))] [0.0, 1.0] >>> [e.amplitude for e in perform(120, beats=1, subdivision=4, pattern=(1, .5))] [1.0, 0.5, 1.0, 0.5]- Return type: