Appendix G · Learn One Verb, Predict the Rest

The contract the whole API keeps — so you can guess an unfamiliar method instead of looking it up.

The guide hands you dozens of p. verbs, and §4.5 made a promise about them: learn the shape of one and you can predict the rest. This appendix is that promise written out in full — the small set of conventions every verb obeys, gathered in one place. None of it is new; it’s the pattern under everything you’ve already used. Read it once and an unfamiliar method stops being a lookup and becomes a good guess.

G.1 The chord verbs share one front

The four ways to voice a chord — block it, strum it, roll it, or spell it out — are deliberately the same call with one word changed. p.chord, p.strum, p.arpeggio, and p.broken_chord (§6.4, §7.7) all lead with the same front: a chord (or a list of pitches), a root register, and the familiar velocity, count, inversion, and beat. So “play this chord as a block, a roll, or a strum” is a one-word swap:

import subsequence
import subsequence.constants.midi_notes as notes

composition = subsequence.Composition(bpm=120, key="A", scale="minor")
composition.harmony(style="aeolian_minor", cycle_beats=4)

# Same front — chord, root, count, velocity — two gestures, one verb apart.
@composition.pattern(channel=3, beats=4)
def block(p, chord):
    p.chord(chord, root=notes.A3, count=4, velocity=80)

@composition.pattern(channel=4, beats=4)
def rolled(p, chord):
    p.arpeggio(chord, root=notes.A3, count=4, velocity=80, spacing=0.25)

composition.render(bars=2, filename="verb-swap.mid")

The tail diverges only where the gesture genuinely differs: strum adds a spacing and a direction, arpeggio a span/spacing/direction, and broken_chord an explicit order list in place of a count. Learn the front once and every chord verb is already half-familiar.

One naming note: p.arpeggio’s first argument is notes, not chord — because an arpeggio is happy with either a chord or a plain list of pitches. The chord form voices the live chord (§6.4); handed a bare list — say the keys a player is holding (§14.3) — it rolls exactly those. The argument is named for the general case.

G.2 (low, high) is one random draw per note

Anywhere a velocity= accepts a tuple, the two numbers are a range and each note rolls its own value inside it — velocity=(60, 90) gives sixteen hi-hats sixteen different dynamics (§1.5). A plain integer is fixed; the tuple is the humanising draw. The reading never changes from verb to verb, so you never have to wonder whether a given (low, high) means “ramp across the bar”, “pick one value for the line”, or “draw per note” — it is always one independent draw per note.

G.3 One knob for repeatability: seed=

Every generator makes its random choices through a stream you can pin with a single friendly knob: seed= (an integer) makes a call reproducible (§4.6, §11.2). For the rarer case where you want to share one stream across calls or steer it yourself, the advanced rng= takes a random.Random you built. When more than one is in play the precedence is fixed and worth holding onto:

rng= beats seed= beats the pattern’s own p.rng.

Pass both rng= and seed= on one call and Subsequence warns you that seed= was ignored — express one intention, not two. Most of the time you want none of them and let the whole-piece seed= on the Composition carry everything.

G.4 probability is the chance a note plays

Wherever a placement is gated by chance, probability is the likelihood a note sounds1.0 is certain, 0.5 keeps about half (§4.6). The one exception is the dropout() transform (§12.6): it removes notes, so its probability is the chance a note is dropped. The verb name carries the direction — dropout(0.1) loses about a tenth — so the inversion never surprises you once you’ve read the name.

Three near-neighbours sit close to probability and each means something distinct; keeping them straight is most of fluency with the generators:

Four dials that are easy to confuse

Dial

What it sets

probability

The chance a note plays (1.0 = certain) — or, on dropout(), the chance it is removed.

density

The fill fraction of a texture — how much of the grid a ghost_fill or bresenham_poly voice takes up.

amount

Thinning depth — how aggressively thin prunes what is already there.

percent

Swing angle50 straight, 57 a gentle shuffle, 67 hard triplet swing.

G.5 Beats at the surface, steps on the grid

Everything you write speaks one of two time units, and which is which is predictable. The API verbs work in beatsbeat=, spacing=, and duration= are all in quarter-note beats, fractions welcome (§3.1). The step methods work in grid stepshit_steps, sequence, and the decorator’s steps= count 0-indexed slots on a fixed grid (§1.4, §3.2). Underneath both the clock runs at a fixed 24 pulses per beat, which you almost never touch — but it is why a beat divides cleanly into halves, thirds, and quarters. To work on a non-quarter grid, pair steps= with step_duration= on the pattern decorator.

G.6 The velocity buckets

When a verb places notes and you don’t name a velocity, it reaches for a sensible default keyed to the kind of material — louder for a foreground hit, quieter for a texture sitting under it. The five defaults live in subsequence.constants.velocity, so you can read or reuse them by name instead of scattering magic numbers:

Default velocity by material

Material

Constant

Value

Single notes and hits

DEFAULT_VELOCITY

100

Chords

DEFAULT_CHORD_VELOCITY

90

Generative lines

DEFAULT_GENERATIVE_VELOCITY

80

Cellular automata

DEFAULT_CA_VELOCITY

60

Ghost layers

GHOST_FILL_VELOCITY

35

>>> from subsequence.constants import velocity
>>> (velocity.DEFAULT_VELOCITY, velocity.DEFAULT_CHORD_VELOCITY,
...  velocity.DEFAULT_GENERATIVE_VELOCITY, velocity.DEFAULT_CA_VELOCITY,
...  velocity.GHOST_FILL_VELOCITY)
(100, 90, 80, 60, 35)

These are starting points, not rules — every verb still takes an explicit velocity= (a number, or a (low, high) tuple, §G.2) when you want something else. But the defaults mean a generated line sits politely under a hit, and a ghost layer under both, before you touch a single number.

G.7 Lenient with names, strict with numbers

Subsequence is forgiving about a name it doesn’t recognise and strict about a number it can’t place — because the two failures mean different things. A drum or voice name the current device’s map lacks is dropped with a one-time warning, and the rest of the pattern plays on (§1.2): a mistyped "kick_99" shouldn’t silence your whole kit. But an unmapped CC, NRPN, or RPN name raises (§13.1, §13.3) — a wrong control number is a genuine mistake, one that would write the wrong synth parameter, so it fails loudly rather than quietly doing the wrong thing.

G.8 Builders chain; accessors return data

The last rule tells you, for any method, whether you can keep going. A verb that places or transforms notes returns the builder, so it chains left to right — p.euclidean(...).swing(...).legato() reads as a recipe (§4.5). A method that reads something — p.capture(...), p.held_notes(), p.section_motif(...) — returns plain data (a motif, a list, a value), because there is nothing left to build onto. When you’re unsure whether a call chains, ask what it does: place or shape, and it hands you the builder back; read, and it hands you the answer.


That is the whole contract — one shared front for the chord verbs, one meaning for (low, high), one knob for repeatability, a consistent reading of probability, two clear time units, sensible velocity defaults, lenient names but strict numbers, and a chain-or-read rule you can apply to any method. Hold these and the API Reference reads less like a list to memorise and more like a language you already speak. For the exhaustive signatures see Appendix D; for this idea in its first, smallest form, §4.5.