Chapter 8 · Motifs: Naming and Reusing Material

In Chapter 7 you learned to write harmony down as a value — a progression you build once, inspect, shape, and place. This chapter does the same for melody and rhythm: you name a short figure as a Motif, transform it with a small algebra, and play it under the moving chord.

A motif is the smallest unit of musical material — a four-note hook, a clave, a bass cell. In Subsequence it’s a value, exactly like a progression: you store it once at the top of your file, and every transform hands you back a new motif instead of changing the one you wrote. That single rule — build once, transform into copies — is what lets a whole part grow from one idea without ever losing the original. It’s the same separation you already know: describe the material first, decide how it sounds second.

8.1 What a Motif is (an immutable value)

A Motif is a frozen figure: a handful of timed notes and a length in beats. It is not a pattern, and it makes no sound by itself — just as a progression is not a chord part until something places it. You build a motif, look at it, reshape it, and only then hand it to a pattern to be played.

The defining property is that a motif is immutable. Once built, it never changes. Every method that “edits” a motif — transpose it, reverse it, vary its last note — leaves the original exactly as it was and returns a brand-new motif with the change applied. This is the same contract a Progression follows (§7.3): transforms return new values, the original is sacred.

Important

A motif is a value you store, not a pattern that runs. You typically build your motifs once, at module level (the top of your file), give them clear names, and reuse them across patterns — precisely the module-level reusable values habit from §2.4. A pattern function still re-runs every cycle, but the motifs it places are fixed; what changes cycle to cycle is the chord they land on.

Like a progression, a motif prints itself, so you can check what you wrote before rendering a note. print(m) calls its .describe():

import subsequence
from subsequence import motif, Motif
import subsequence.constants.instruments.gm_drums as gm_drums
import subsequence.constants.midi_notes as notes

hook = motif([5, 6, 5, 3])    # a four-note figure in scale degrees
print(hook.describe())
Motif 4 beats [^5@0, ^6@1, ^5@2, ^3@3]

Read the printout as “a four-beat figure: degree 5 at beat 0, degree 6 at beat 1, degree 5 at beat 2, degree 3 at beat 3.” The ^5 is Subsequence’s compact label for scale degree 5 — the figure stores degrees, not fixed pitches, so it’s a specification resolved late, the same idea you met for a single pitch in §5.5 and for a whole chord in §6.5. We’ll see exactly what those degrees resolve to in §8.5.

Reference

motif(), Motif

8.2 Building one: motif(), degrees, notes, steps

There are three everyday ways to write a figure down, depending on whether you’re thinking in scale degrees, fixed pitches, or a drum grid. Each is a factory that returns a Motif.

Scale degrees — motif([...]). The lowercase motif(...) is the shortcut you just used: a melody as 1-based scale degrees, one per beat by default. Degree 1 is the tonic, 8 is the tonic an octave up. This is the relative-first form — write the shape, and it transposes to any key for free.

hook = motif([5, 6, 5, 3])           # one degree per beat
print(hook.describe())
Motif 4 beats [^5@0, ^6@1, ^5@2, ^3@3]

motif([5, 6, 5, 3]) is exactly Motif.degrees([5, 6, 5, 3]) — the lowercase name is just the shortcut, the same way progression([...]) shortcuts the Progression factory. A None in the list is a rest (the beat slot still advances), and you can pin the timing yourself with a beats= list and the lengths with durations=:

# A syncopated figure: a rest in the second slot, the rest on chosen beats.
synced = Motif.degrees([5, None, 6, 1], beats=[0, 1, 1.5, 3])
print(synced.describe())
Motif 4 beats [^5@0, ^6@1.5, ^1@3]

Absolute pitches — Motif.notes([...]). When you want fixed MIDI note numbers rather than key-relative degrees — a sampled riff, a melody you already know in concrete pitches — use Motif.notes(...). Here 60 is middle C, and again None is a rest:

riff = Motif.notes([64, 65, 64, 60])   # E F E C, exact pitches
print(riff.describe())
Motif 4 beats [64@0, 65@1, 64@2, 60@3]

Important

Degrees follow the key; absolute notes don’t. A motif([5, 6, 5, 3]) stores degrees, so it plays “the 5th, 6th, 5th, 3rd of whatever key the composition is in” — change the key and it transposes itself. Motif.notes([64, 65, 64, 60]) stores MIDI numbers, so it always plays E–F–E–C regardless of key. Reach for degrees when you want the figure to live inside the harmony, and for notes when you mean those exact pitches. (Degrees above the octave are fine — degree 9 is the 2nd a register up — but paste a MIDI list like [64, 65] into motif() by mistake and it raises: a value that large can only be a MIDI number, not a scale degree, so the slip fails loudly instead of squealing octaves up.)

A drum/grid figure — Motif.steps(...). For percussion you think in grid steps, exactly like p.hit_steps from §1.4. Motif.steps(...) takes a list of 0-based step indices (sixteenths by default) and the drum to place on them — a rhythmic cell you can name and reuse:

clave_cell = Motif.steps([0, 3, 6, 10, 12], pitches="side_stick")
print(clave_cell.describe())
Motif 4 beats [side_stick@0, side_stick@0.75, side_stick@1.5, side_stick@2.5, side_stick@3]

The step indices became beat positions (step 3 of a sixteenth grid is beat 0.75), and the drum name rides along untouched — drum names resolve through the pattern’s drum map at placement, just as they do in a hand-written beat.

Three ways to write a figure

Factory

You write

Reach for it when

motif([...]) / Motif.degrees

1-based scale degrees

You want a melody that lives in the key and transposes for free (the default).

Motif.notes([...])

Absolute MIDI numbers

You mean those exact pitches, key or no key.

Motif.steps([...], pitches=...)

0-based grid steps + a drum

You’re writing a rhythmic/percussion cell, hit_steps-style.

Reference

degrees(), notes(), steps()

8.3 Transforms return NEW values

Here is the heart of working with motifs, and the one idea to hold onto: every transform returns a new motif and leaves the original untouched. You never modify a motif in place; you derive variations from it. A whole melody’s worth of material comes from one stored figure plus a few of these calls.

Watch the original survive a transform:

hook = motif([5, 6, 5, 3])
higher = hook.transpose(steps=2)       # everything up two scale steps

print("derived: ", higher.describe())
print("original:", hook.describe())    # ← unchanged
derived:  Motif 4 beats [^7@0, ^8@1, ^7@2, ^5@3]
original: Motif 4 beats [^5@0, ^6@1, ^5@2, ^3@3]

hook.transpose(steps=2) handed back a new figure a third higher; hook itself is byte-for-byte what you wrote. That’s immutability doing its job — higher and hook are two independent values, both available, neither aliasing the other.

These are the five transforms you’ll reach for first. Each takes a motif and returns a fresh one:

transpose(...) — move the pitches. The keyword names the unit: transpose(steps=2) moves diatonically (along the scale, the sequencing move), while transpose(semitones=12) moves chromatically (by half-steps, e.g. up an octave). You must pass exactly one.

hook = motif([5, 6, 5, 3])
print(hook.transpose(steps=2).describe())       # diatonic: degrees shift along the scale
Motif 4 beats [^7@0, ^8@1, ^7@2, ^5@3]

invert(...) — mirror the contour. Flip the figure upside-down around a pivot (by default, the first note’s pitch): where the original rose, the inversion falls. This is the classic motivic-development move that keeps the rhythm and the intervals but turns the shape over.

print(hook.invert().describe())     # mirror around the first note (degree 5)
Motif 4 beats [^5@0, ^4@1, ^5@2, ^7@3]

reverse() — mirror in time. Play the figure backwards: the last note becomes the first. The pitches are unchanged; their order is flipped.

print(hook.reverse().describe())
Motif 4 beats [^3@0, ^5@1, ^6@2, ^5@3]

vary(...) — the smallest change. Keep the rhythm, nudge a note or two by a small melodic step. By default it varies the tail (the last note) — the loosest possible restatement, the way a player never repeats a lick quite identically. Because it makes a random choice, pass a seed= so the variation is the same on every run (the repeatability habit from §4.6):

print(hook.vary(notes=1, seed=4).describe())    # same figure, new last note
Motif 4 beats [^5@0, ^6@1, ^5@2, ^2@3]

answer(...) — turn a call into a response. Re-aim the last note to a stable degree so the figure sounds like it’s coming home. By default it lands on degree 1 (the tonic — a full close); pass to=5 for a half-close that hangs open. This is the melodic counterpart to a cadence, and it pairs perfectly with the + operator below to write a question-and-answer pair.

print(hook.answer().describe())       # tail re-aimed to the tonic (degree 1)
print(hook.answer(to=5).describe())   # tail re-aimed to degree 5 (half-close)
Motif 4 beats [^5@0, ^6@1, ^5@2, ^1@3]
Motif 4 beats [^5@0, ^6@1, ^5@2, ^5@3]
The five everyday transforms

Transform

What it returns (a NEW motif)

transpose(steps=N) / transpose(semitones=N)

The figure moved — diatonically (steps=) or chromatically (semitones=).

invert(pivot=None)

The contour mirrored around a pivot (default: the first note).

reverse()

The figure played backwards in time.

vary(notes=N, seed=...)

The same rhythm with a few pitches nudged — the loosest restatement.

answer(to=1)

The tail re-aimed to a stable degree — call becomes response.

Tip

These chain, like the progression transforms. Each call returns a motif, so the next call hangs right off it: hook.transpose(steps=2).reverse().vary(notes=1, seed=1) reads left-to-right as a recipe — raise it, flip it in time, then nudge a note. It’s the same “learn one verb, predict the rest” fluency you met for generators in §4.5 and for harmony in §7.4. And because the source motif is never mutated, you can spin off as many independent variations from it as you like.

Note

Why some transforms refuse some content. transpose(steps=), invert(), and answer() all work on scale degrees — they need a degree to move along the scale or re-aim home. Call them on a drum figure (a Motif.steps(...) of "side_stick") and they raise, on purpose: a “transposed” drum name would be a different instrument, not a transposition, and a drum has no tonic to answer to. For rhythmic material reach for reverse() and the timing transforms — rotate (shift in time, wrapping), stretch (scale the tempo feel), quantize (snap onsets to a grid) — which only touch when a note sounds, never its pitch. (transpose(semitones=) is the chromatic move, but it needs a pitch to shift: it works on absolute-MIDI melodies and degrees, and still raises on drum names.)

8.4 Combining: then / stack / slice and the + / * operators

A motif is small by design. You build longer and richer material by combining motifs — gluing them end to end, layering them, or windowing out a fragment. Three methods and two operators cover it, and they split cleanly into “sequential” (one after another) and “parallel” (at the same time).

then(other) — glue end to end into ONE motif. a.then(b) plays a, then b right after it, fused into a single longer motif. Use it when you want one seamless figure built from two cells:

call = motif([5, 6, 5, 3])
response = call.answer()             # same figure, landing on the tonic
phrase = call.then(response)         # one 8-beat figure: call, then response
print(phrase.describe())
Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^5@4, ^6@5, ^5@6, ^1@7]

There’s the whole figure in one motif: the call at beats 0–3, then the response shifted to beats 4–7, its tail now on degree 1 (the tonic). Eight beats long, no seam — then fused the two cells into a single value.

+ — sequential, but keeping the seam. The + operator also puts one motif after another, but it produces a Phrase — a sequence of motifs that remembers the boundary between them, rather than fusing them. (A Phrase is the subject of Chapter 9; for now, think “a motif that knows its segments.”)

pair = call + response       # a two-segment Phrase, not one fused Motif
print(pair.describe())
Phrase 8 beats, 2 segments
  1. Motif 4 beats [^5@0, ^6@1, ^5@2, ^3@3]
  2. Motif 4 beats [^5@0, ^6@1, ^5@2, ^1@3]

Important

then fuses; + keeps the seam. Both place b after a. a.then(b) returns one Motif with the boundary erased — useful when you want a single indivisible figure. a + b returns a Phrase that still knows where a ends and b begins — useful when you’ll later develop or reroll the parts independently (Chapter 9). Same musical result on playback; different handle on the structure.

* — repetition. Multiply a motif to repeat it. Like +, it produces a Phrase (a chain of identical segments), so the repeats stay individually addressable:

vamp = call * 2              # the call, twice — a 2-segment Phrase
print("length:", vamp.length, "beats")
length: 8.0 beats

stack(other) — layer two motifs at once. Where then and + are sequential, stack is parallel: it merges two motifs so they sound together, returning one motif containing both. This is how you put a counter-line under a melody, or two percussion voices in one cell:

melody  = motif([5, 6, 5, 3])
counter = motif([3, 4, 3, 1])      # a line a third below
both = melody.stack(counter)       # the two played at the same time
print(both.describe())
Motif 4 beats [^3@0, ^5@0, ^4@1, ^6@1, ^3@2, ^5@2, ^1@3, ^3@3]

The two figures now share every beat — degree 3 and degree 5 at beat 0, and so on. stack is also spelled with the & operator (melody & counter), exactly as then’s sequential cousin + mirrors it. The length is the longer of the two — a short gesture stacked under a long figure plays once and stops, no looping.

slice(start, end) — window out a fragment. Take just part of a motif, with the beats shifted so the window starts at 0. Reach for it to grab the first cell of a longer idea, or to develop only the tail:

opening = call.slice(0, 2)         # just the first two beats
print(opening.describe())
Motif 2 beats [^5@0, ^6@1]
Combining motifs

Operation

Result

What it does

a.then(b)

Motif

Sequential: glue b after a into one fused figure.

a + b

Phrase

Sequential: b after a, keeping the seam (segments preserved).

m * n

Phrase

Repeat m n times (a chain of segments).

a.stack(b) / a & b

Motif

Parallel: a and b sound at the same time.

m.slice(start, end)

Motif

A window onto m, re-zeroed to start at beat 0.

Reference

then(), stack(), slice()

8.5 Placing under harmony: p.motif, root, fit

A motif is still just a value — nothing has sounded yet. To play it, hand it to a pattern with p.motif(m, beat=...). This is the seam between the material (the stored value) and the performance (the running pattern), exactly like p.progression for harmony.

Here is the running arrangement carried forward: the GM drum loop, plus a lead that plays our stored hook under the live chord engine from Chapter 6. The motif is built once at module level; the pattern just places it each cycle.

import subsequence
from subsequence import motif
import subsequence.constants.instruments.gm_drums as gm_drums
import subsequence.constants.midi_notes as notes

HOOK = motif([5, 6, 5, 3, 1])     # the named material, built once

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

@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
    p.hit_steps("kick_1", [0, 4, 8, 12], velocity=100)
    p.hit_steps("snare_1", [4, 12], velocity=95)
    p.hit_steps("hi_hat_closed", range(16), velocity=70)

@composition.pattern(channel=4, beats=4)
def lead(p):
    p.motif(HOOK, beat=0, root=notes.A4)   # place the figure, near A4

composition.render(bars=4, filename="motif-lead.mid")

Two arguments steer the placement:

  • beat= is where in the bar the figure starts. beat=0 is the downbeat; beat=2 would drop it onto beat 3.

  • root= is the register anchor for scale-degree resolution. The figure’s degrees resolve against the composition’s key and scale, voiced near this MIDI note — notes.A4 puts the melody up in a lead register, notes.A2 down in the bass. It’s the same “give me this, around here” idea as chord.root_note(near) in §6.2: the degrees decide which notes, you decide where.

Note

This is “a pitch resolved late,” one more time. HOOK stores degrees 5 6 5 3 1 — abstract until placed. At render, p.motif(HOOK, root=notes.A4) resolves them against A minor near A4, producing concrete MIDI notes. Change the composition’s key= and the same HOOK plays a transposed melody, with no edit to the motif — the structural-vs-sonic distinction from §5.5 and §6.5, now for a whole figure instead of one note.

Snapping melody to the chord: the fit dial

By default, a hand-written degree plays exactly the degree you wrote — it follows the key, but it does not bend toward the current chord. Sometimes that’s what you want; sometimes you’d rather the melody lean into the harmony, landing on chord tones on the strong beats. The fit= dial does that, with a probability from 0.0 to 1.0:

import subsequence
from subsequence import motif
import subsequence.constants.midi_notes as notes

HOOK = motif([5, 6, 5, 3, 1])

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

@composition.pattern(channel=4, beats=4)
def lead(p):
    # On strong beats, snap to the nearest chord tone — the melody hugs the chord.
    p.motif(HOOK, beat=0, root=notes.A4, fit=1.0)

composition.render(bars=4, filename="motif-fit.mid")

With fit=1.0, every degree that lands on a strong beat snaps to the nearest tone of the chord sounding under it; at fit=0.5 it snaps about half the time, for a looser feel; at fit=0.0 (the default for hand-written motifs) it never snaps and your degrees play verbatim.

Important

Typed degrees are sacred — fit is opt-in. A degree you wrote by hand defaults to fit=0.0: it plays exactly as written, following the key but never silently rewritten by the chord. You opt in to chord-snapping with fit= at the placement call — it’s a deliberate dial, not a default. (Motifs that Subsequence generates for you carry a gentle fit=0.7, since they have no authored intent to protect. Chord-relative content always tracks the chord regardless of fit, because asking for “the third of the chord” is already a harmony reading.)

Tip

fit needs a chord to snap to. It does nothing without harmony — call composition.harmony(...) (a style, or a bound progression) first, exactly as in Chapters 6 and 7. With no chord context, fit= is quietly inert and your degrees play as written.

Reference

motif()

8.6 Capturing placed notes back: p.capture

Sometimes you want to go the other way: place a figure, let Subsequence resolve all its degrees and chord-snapping into concrete notes, and then read those notes back out as a new motif — to layer them an octave down, hand-edit them, or freeze them. p.capture(beat, span) does exactly that: it reads the notes placed so far in the current pattern and returns them as a Motif.

Here the lead places the hook, then captures the resolved notes and doubles them an octave lower as a second voice:

import subsequence
from subsequence import motif
import subsequence.constants.midi_notes as notes

HOOK = motif([5, 6, 5, 3, 1])

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

@composition.pattern(channel=4, beats=4)
def doubled_lead(p):
    p.motif(HOOK, beat=0, root=notes.A4)        # place the figure up high
    echo = p.capture(beat=0, span=4)            # read those notes back out
    p.motif(echo, beat=0, root=notes.A3)        # play them again, an octave down

composition.render(bars=4, filename="motif-capture.mid")

p.capture(beat=0, span=4) grabs the four-beat window from the start of the bar. What comes back is an absolute-MIDI motif — the degrees have already resolved to concrete pitches against the chord, so the captured figure is fixed notes, not degrees.

Important

Capture is absolute and lossy — by design. The degrees, chord tones, and fit-snapping that produced the placed notes have already resolved by the time you capture, so the returned motif holds plain MIDI numbers (like Motif.notes([...])), not the relative specs you started from. Timing is snapped to the MIDI grid, and control gestures (CC, pitch-bend) aren’t captured. The intended round trip is place → capture → hand-edit → re-place, not a way to recover your original degree spec. If you want the relative figure, keep the value you built; if you want the sounded result as material, capture it.

Note

Why capture at all, if it’s lossy? Because the sounded result is sometimes exactly the material you want. A generated or chord-fitted melody resolves to specific notes you couldn’t easily have written by hand; capturing freezes that happy accident into a value you can transpose, reverse, or stack like any other motif. It’s the melodic echo of composition.freeze(...) for harmony in §7.6: let the system resolve something live, then keep the result.

Reference

capture()

8.7 Presets & world rhythms

You don’t have to write every rhythmic cell from scratch. Motif.preset(name) hands you a famous world rhythm — claves, bell patterns, tresillo — as a ready-made motif. These are the timelines that anchor Afro-Cuban and West-African music, placed at their exact canonical pulse positions:

clave = Motif.preset("son_clave_3_2")    # the 3-2 son clave
print(clave.describe())
Motif 4 beats [claves@0, claves@0.75, claves@1.5, claves@2.5, claves@3]

Each preset comes with a sensible default General-MIDI voice (the son clave uses "claves"), so it sounds correct against the standard drum map with no pitch=. Override the voice — and play it on a different drum — by passing pitch=:

bembe = Motif.preset("bembe", pitch="cowbell")   # the 12-pulse bembé bell
print(bembe.describe())
Motif 4 beats [cowbell@0, cowbell@0.666667, cowbell@1.33333, cowbell@1.66667, cowbell@2.33333, cowbell@3, cowbell@3.66667]

Notice the bembé’s onsets sit on a 12-pulse cycle (the beats land on thirds: 0.667, 1.333, …), where the clave is on the familiar 16-pulse sixteenth grid — each preset declares its own grid, so the geometry is authentic.

Because a preset is just a Motif, every transform and combiner from this chapter applies. Here the running drum loop gets a son-clave side-stick layered on top — the preset placed straight into a pattern with p.motif:

import subsequence
from subsequence import Motif
import subsequence.constants.instruments.gm_drums as gm_drums

composition = subsequence.Composition(bpm=120, key="A", scale="minor")

CLAVE = Motif.preset("son_clave_2_3", pitch="side_stick")   # 2-3 son clave

@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
    p.hit_steps("kick_1", [0, 4, 8, 12], velocity=100)
    p.hit_steps("snare_1", [4, 12], velocity=95)
    p.hit_steps("hi_hat_closed", range(16), velocity=70)
    p.motif(CLAVE, beat=0)        # lay the clave timeline over the beat

composition.render(bars=4, filename="motif-clave.mid")

The available presets, grouped by family:

World-rhythm presets

Family

Preset names

Cuban clave (16-pulse)

"son_clave_3_2", "son_clave_2_3", "rumba_clave_3_2", "rumba_clave_2_3", "bossa_nova_clave"

Tresillo / cinquillo (the 3-3-2 family)

"tresillo", "tresillo_16", "cinquillo"

West-African / Cuban 4-4 bell (16-pulse)

"shiko", "soukous", "gahu", "samba_necklace"

12-pulse bell timelines

"bembe", "bembe_euclidean", "fume_fume"

Tip

A preset is a rhythm with a default voice — so it’s the percussion sibling of a degree melody. Want the clave’s timing but a different sound? Pass pitch=. Want its timing on a pitched part — a bassline that grooves to the clave? .pitched(...) re-aims every onset at one pitch spec (a chord tone like "root", or a MIDI note), turning the rhythm skeleton into a melodic line you can place under the harmony. An unknown preset name raises a ValueError listing every valid one, so a typo fails loudly.

Reference

preset(), pitched()


You can now treat melody and rhythm the way Chapter 7 taught you to treat harmony: name a figure as an immutable Motif, transform it into a family of variations that never touch the original, combine those sequentially or in parallel, and place them under the moving chord with p.motif — snapping to the harmony with fit, or capturing the resolved result back out with p.capture. In Chapter 9 we let these motifs grow: stringing them into phrases and developing them with a plan, so a whole melodic part unfolds from a single stored idea.