Combinators
Pattern transforms are free functions, not methods (Ramda-style). A class
that implements Pattern shouldn’t have to implement forty chaining methods to
be first-class — so slow(2, p) reads instead of p.slow(2). Any pattern
composes with any other.
import { stack, sequence, slow, fast, gain, chance, swing, humanize } from "@barline/core";All of these are pure and deterministic. The ones that need randomness
(chance, humanize) draw from ctx.rng(seed), so visualization and playback
never disagree.
stack(...patterns)
Section titled “stack(...patterns)”Play several patterns in parallel. The result’s length is the longest input.
stack( euclidean(3, 16, { note: "F3" }), euclidean(3, 16, { note: "Ab3" }), euclidean(3, 16, { note: "C4" }),); // an F-minor stab chord on one euclidean rhythmsequence(...patterns)
Section titled “sequence(...patterns)”Play patterns one after another, looping the concatenation.
const phraseA = notes(["F3", "Ab3", "C4", "Bb3"], beats(0.5));const phraseB = notes(["F3", "Ab3", "C4", "Eb4"], beats(0.5));sequence(phraseA, phraseB); // two 1-bar phrases, 2 bars per cycleslow(factor, pattern) / fast(factor, pattern)
Section titled “slow(factor, pattern) / fast(factor, pattern)”Stretch or compress time. slow(2, p) takes twice as long; fast(2, p) plays
twice as fast (fast is exactly slow(1 / factor, …)).
const hymn = slow(2, sequence(phraseA, phraseB)); // half speed → 4 bars per cyclehats.play(fast(2, crescRoll)); // double-time turnaround rollgain(factor, pattern)
Section titled “gain(factor, pattern)”Scale every velocity, clamped to [0, 1]. (Distinct from the play-handle
.gain() modifier — gain() the combinator transforms the pattern.)
hats.play(gain(0.25, r`x*16`)); // sixteen hits, all at a quarter velocitychance(probability, pattern)
Section titled “chance(probability, pattern)”Drop events with probability 1 - probability, deterministically per event
(seeded by offset). Thins a pattern without making it jitter between frames.
hats.play(chance(0.6, at([2, 6, 10, 14]))); // ~60% of the offbeat hats surviveperc.play(chance(0.4, scatter)); // sparse textureswing(amount, pattern)
Section titled “swing(amount, pattern)”Musical 16th-note swing. Every off-16th — an event on the second 16th of an
8th-note pair — is delayed by amount × 1/12 beats. amount = 1 lands exactly
on the triplet position (classic MPC feel); downbeats, straight 8ths, and
triplets are untouched. amount clamps to 0..1.
hats.play(swing(0.6, r`x x x x x x x x x x x x x x x x`));It is window-consistent: the underlying pattern is queried on a back-widened window so a delayed event near the bar end is still reported correctly.
humanize({ timing?, velocity? }, pattern)
Section titled “humanize({ timing?, velocity? }, pattern)”Per-event jitter, seeded per event so it is deterministic.
timing— a gaussian standard deviation in beats (Box–Muller, clamped to ±3σ).0.01–0.03is a subtle human feel on a 16th grid.velocity— a uniform spread0..1; each velocity moves by ±spread/2, clamped to[0, 1].
clap.play(humanize({ timing: 0.02, velocity: 0.15 }, backbeat()));