Chapter 9 · Phrases and Motivic Development

In Chapter 8 you named a short figure as a Motif and learned that its transforms hand you new values; now we grow one of those motifs into a multi-bar phrase — a sentence, a period, a development plan — place its per-cycle window in a pattern, and regenerate a bar or two while keeping the rest.

A motif is a single gesture; a phrase is a sentence made of gestures. Where the last chapter gave you the words, this one gives you the grammar that turns “five, six, five, three” into eight bars that state an idea, push it, and land. And because a phrase is a value — frozen, printable, immutable, exactly like the Motif and Progression before it — you can develop it, inspect it, and surgically edit one bar without disturbing the others. That last move, selective regeneration, is the manual-to-generate-and-back round trip this whole Part has been building toward.

9.1 From motif to phrase

A Phrase is a frozen sequence of Motifs — and crucially, it keeps the seams between them. Where a Motif is one figure, a Phrase remembers that it is “this two-bar idea, then that one, then a third” — and that segmentation is the unit of editing. It is what every builder, development plan, and selective reroll in this chapter operates on.

You already met the smallest way to make one in Chapter 8: the + operator joins two motifs into a two-segment phrase, and * repeats one. Start there. Here is a two-bar basic idea — a melody as scale degrees, the relative form from §5.1 — and the simplest phrase you can build from it, the idea stated and then answered:

import subsequence
from subsequence import motif, sentence, period, Phrase

# A two-bar basic idea: degrees, one per beat, with a rest at beat 4.
idea = motif([5, 6, 5, 3, None, 1, 2, 3])
print(idea.describe())

# `+` makes a two-segment Phrase; the second segment is the idea, varied.
simple = idea + idea.vary(notes=1, seed=4)
print(simple.describe())
Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
Phrase 16 beats, 2 segments
  1. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
  2. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^2@7]

Read the printout the way you read a progression’s .describe() in §7.2: one line per segment, each itself a one-line motif summary. The ^5@0 notation is “degree 5, at beat 0” — the same degree content from the last chapter, now laid out across the phrase. The two segments are identical except for their last note (^3 became ^2), because vary() moved a single tail pitch and left everything else — rhythm, the other notes, the rest — untouched.

Note

A Phrase keeps its seams; a Motif erases them. idea + idea gives a two-segment Phrase (16 beats, two motifs you can still address individually); idea.then(idea) from §8.4 gives one 16-beat Motif with no internal boundary. The seam is not cosmetic — replace, reroll, and every development plan in this chapter act on segments, so when you want a thing you can later edit bar by bar, keep it a Phrase. To collapse a phrase into one flat motif when you no longer need the seams, call .flatten() (§9.5).

Building phrases by hand with + and vary() works, but for the shapes a musician already knows by name — the sentence, the period — Subsequence gives you one-call builders. That is the next section.

Reference

Phrase

9.2 Classical builders: sentence() and period()

Two phrase shapes are old enough to have names in every theory book, and Subsequence ships both as thin combinators. They take a basic idea and a cadence, and hand you back a finished phrase — segmentation, contrast, and close all in place. They are subsequence.sentence(...) and subsequence.period(...), importable directly.

The sentence: idea, idea, drive, close

A sentence is four units: the basic idea stated twice (the presentation), a contrasting continuation that develops the material, and a cadential unit whose tail lands on the cadence. An eight-bar sentence from a two-bar idea is the textbook proportion.

idea = motif([5, 6, 5, 3, None, 1, 2, 3])    # the two-bar basic idea again
verse = sentence(idea, bars=8, cadence="open", seed=11)
print(verse.describe())
Phrase 32 beats, 4 segments
  1. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
  2. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
  3. Motif 8 beats [^6@0, ^6@1, ^5@2, ^5@3, ^1@5, ^2@6, ^5@7]
  4. Motif 8 beats [^6@0, ^6@1, ^6@2, ^5@3, ^1@5, ^2@6, ^5@7]

The four segments are exactly the sentence anatomy. Segments 1 and 2 are your idea, verbatim — that is the presentation, the figure stated and immediately restated. Segment 3 keeps the idea’s rhythm but re-pitches it: a recognisable but new continuation. Segment 4 is the cadential unit, and because we asked for cadence="open", its last pitched note lands on degree 5 (^5@7) — the half-close, the musical comma that leaves the door ajar for whatever comes next.

The cadence argument is the same vocabulary you met for harmony in §7.5, here aiming the melody’s last note rather than rewriting chords:

cadence=

Lands on

The feeling

"strong"

degree 1

The full stop — the tune comes home. (Theory: authentic.)

"open"

degree 5

A question, a comma — hangs unresolved. (Half.)

"soft"

degree 1

Also home, the gentler close. (Plagal.)

"fakeout"

degree 1

The melodic close still aims home; the swerve is harmonic. (Deceptive.)

Important

A cadence has two halves, and sentence() writes only the melodic one. The builder re-aims the tune’s last note (degree 5 for "open", degree 1 for the closes). The harmony’s cadence — the V→I or IV→V under it — is a separate call: prog.cadence("open") from §7.5, or cadence= on the live engine. Pair the two and the melody and chords arrive at the cadence together; use one without the other and only half the punctuation lands. They are written separately on purpose — a melody can ask a question over a harmony that answers, and that tension is a tool.

The period: question, then answer

A period is two halves of the same material that differ only at their close. The first half — the antecedent — ends open, on a half-close; the second — the consequent — restates it but ends home. The open-then-closed contrast is the period. Because nothing is generated (only the two tail notes re-aim), period() is fully deterministic and takes no seed.

# A two-bar antecedent (length=8 makes the trailing rest explicit).
antecedent = motif([3, 4, 5, 1, None, 6, 5, 4], length=8)
question_answer = period(antecedent)
print(question_answer.describe())
Phrase 16 beats, 2 segments
  1. Motif 8 beats [^3@0, ^4@1, ^5@2, ^1@3, ^6@5, ^5@6, ^5@7]
  2. Motif 8 beats [^3@0, ^4@1, ^5@2, ^1@3, ^6@5, ^5@6, ^1@7]

The two segments are identical right up to the last note: segment 1 ends on degree 5 (^5@7, the half-close question), segment 2 ends on degree 1 (^1@7, the answer home). That single-note difference, repeated across two restated bars, is the entire gesture — and it reads straight out of the printout.

Tip

period() takes a Phrase, not only a Motif — and keeps its segmentation. Pass a multi-segment antecedent (say, a sentence(...) or an a + b phrase) and only the last segment’s tail re-aims; every interior seam is preserved through both halves. That is how you build a sixteen-bar period out of an eight-bar sentence: period(sentence(idea, bars=8, seed=3)).

Reference

sentence(), period()

9.3 Development plans: Phrase.develop

sentence and period are two named shapes; Phrase.develop(...) is the general engine underneath them. You hand it a motif, a phrase length in bars, and a plan — and it grows the motif into a phrase by that plan. There are two ways to write the plan, and they follow the standard-form rule you have seen throughout the guide: a sequence of things is a Python list.

A plan as a list of unit labels

The literal form is a list of unit labels, one per segment. The first label is the motif you gave; each new label is a generated contrast unit (the source’s rhythm, freshly re-pitched); a repeated label restates whatever that label already named. So ["a", "a", "a", "b"] is “state it three times, then contrast” — the classic AAAB drive into a new fourth bar:

idea = motif([5, 6, 5, 3, None, 1, 2, 3])
aaab = Phrase.develop(idea, bars=8, plan=["a", "a", "a", "b"], seed=11)
print(aaab.describe())
Phrase 32 beats, 4 segments
  1. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
  2. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
  3. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
  4. Motif 8 beats [^7@0, ^6@1, ^5@2, ^1@3, ^1@5, ^2@6, ^5@7]

The first three segments are the idea verbatim (the three "a" labels), and the fourth — the single "b" — is a contrast unit that kept the rhythm but moved several pitches. Because labels are just names, you control the form: ["a", "b", "a", "b"] alternates, ["a", "a", "b", "a"] puts the contrast third, and `[“a”]

  • 3 + [“b”]is the same AAAB written with Python list arithmetic. Thebars=` spreads evenly across however many units the plan asks for — eight bars over four units is two bars each.

Important

A plan is a list, because a letter-string is a sequence of labels. Write plan=["a", "a", "a", "b"], never plan="aaab". A bare string means something else entirely (a recipe name, below), and Subsequence will reject an ambiguous letter-string loudly rather than guess — plan="aaab" raises a ValueError that points you to the list form. This is the same “a sequence is a list” standard form that made a rhythm [0, 4, 8, 12] back in §1.4 and a progression [1, 6, 4, 5] in §7.1.

A plan as a recipe name

When a plan’s logic is richer than “restate or contrast”, it gets a name instead — a bare string naming a curated recipe. The built-in one is "call_response": call, answer (the call with its tail re-aimed home), call again, then a varied answer.

lead = Phrase.develop(idea, bars=8, plan="call_response", seed=11)
print(lead.describe())
Phrase 32 beats, 4 segments
  1. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
  2. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^1@7]
  3. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
  4. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^2@7]

Segments 1 and 3 are the call; segment 2 is the answer (its tail re-aimed to ^1, home); segment 4 is the varied answer (^2 — the answer, nudged). The bare string "call_response" is the only thing that distinguishes a recipe name from a label list, which is exactly why a multi-label plan must be a list.

Note

develop() wants a seed=. Any plan that generates contrast units (a label list with a new label, or a recipe) makes random pitch choices, so a phrase built without a seed is a fresh walk every time the file reloads — which breaks live coding. Pass seed= (any number) and the phrase is the same value on every run, the repeatability habit from §4.6. Without one, develop() warns. period() is the exception: it generates nothing, so it needs no seed.

Note

Under the hood: sentence and period are develop with the close baked in. A sentence(...) is a four-unit development (idea, idea, continuation, cadential) whose last unit’s tail is re-aimed to the cadence degree; a period(...) is a two-unit plan whose two tails differ at the close. Both record their recipe — the source motif, the plan, the seed — inside the phrase, which is what makes the selective reroll of §9.5 possible. Hand-written phrases and transformed ones carry no recipe, because their notes no longer came from a generator; there would be nothing honest to regenerate from.

Reference

develop()

9.4 Placing a phrase: p.phrase, align

A phrase, like every value in this Part, makes no sound until something places it. In a pattern you place a single motif with p.motif(...) (from §8.5); you place a phrase with p.phrase(value, ...) — and it does one clever thing automatically: it plays only this cycle’s window of the phrase, advancing through it cycle by cycle.

Here is the running arrangement carried forward — the GM drum loop and a chord-following bass from Part IV — joined by an eight-bar lead phrase placed on a two-bar pattern. The harmony is a progression bound to the engine, exactly as in §7.6:

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

composition = subsequence.Composition(bpm=120, key="A", scale="minor")
composition.harmony(progression=progression([1, 6, 4, 5]))

idea = motif([5, 6, 5, 3, None, 1, 2, 3])
lead_line = sentence(idea, bars=8, cadence="open", seed=11)   # an 8-bar phrase, stored once

@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=6, beats=4)
def bass(p, chord):
    p.note(chord.bass_note(notes.A2), beat=0, duration=3.5, velocity=90)

@composition.pattern(channel=4, bars=2)
def lead(p):
    p.phrase(lead_line, root=notes.A4)   # this cycle's 2-bar window of the 8-bar phrase

composition.render(bars=8, filename="phrase-lead.mid")

The lead pattern is two bars long but the phrase is eight. p.phrase handles the mismatch by windowing: cycle 0 plays bars 1–2 of the phrase, cycle 1 plays bars 3–4, and so on, looping back to the start after bar 8. You do not track position yourself — it is computed fresh each cycle from the engine’s own counters, so it is correct under live reload, render, and jumps, with no stored state. A pattern shorter than its phrase simply walks through it.

The arguments mirror p.motif so the two read as one verb:

Argument

What it does

value

The Phrase to place (a Motif works too — it places its window directly).

root=

The register anchor for scale-degree resolution: the tonic lands at its nearest instance to this MIDI note, and the melody keeps its written contour from there. notes.A4 puts the lead up in a singable register.

fit=

The chord-tones-on-strong-beats dial, 0.01.0 (from §8.5): resolved degrees landing on strong beats snap to the nearest chord tone with this probability. Defaults to the phrase’s own fit.

align=

How position is counted: "pattern" (the default) counts pattern cycles; "section" counts bars within the current form section.

offset=

Beats added to the computed position — a phase shift into the phrase.

Note

fit and “the same notes sound different under different chords.” Your idea is written as scale degrees, and degrees resolve late against the key — and, with fit active, against the chord under each strong beat, exactly the specification-resolved-late idea from §5.5 and §6.5. By default a hand-written phrase has no fit (typed degrees are sacred — they play as written), so the lead above floats over the chords on the key’s scale tones. Pass fit=1.0 to p.phrase and every strong-beat degree snaps to a chord tone, locking the melody to the harmony; somewhere in between blends the two. It is one dial between “the tune I wrote” and “the tune the chords want.”

The other align mode points forward. With align="pattern" the phrase advances by pattern cycle, which is what you want for a lead that simply loops. With align="section" the phrase position is read from the bar within the current form section, so the phrase restarts when the section does — the natural choice once a track has verses and choruses. Forms and sections are Chapter 10; align="section" is mentioned here only so you know the seam exists.

Important

One stored phrase, placed live — the value/canvas split holds. lead_line is built once at module level: it is an immutable value, frozen the moment sentence(...) returns. The pattern function lead is the per-cycle canvas (the rebuild loop from Chapter 2), and p.phrase reads a window of the stored value into it each cycle. You never mutate lead_line; you place it. This is the same discipline as a Progression or a Motif — material is a value you keep, p is the bar you fill from it.

Reference

phrase()

9.5 Selective regeneration: reroll/replace while keeping the rest

Here is the payoff of segmentation. You have an eight-bar phrase you mostly like, but bar 7 is not landing. You do not want to regenerate the whole thing and lose the seven bars that work. A Phrase lets you regenerate one region and keep the rest — the surgical edit that makes generate-then-tweak a real workflow rather than a slot machine.

reroll — regenerate a region from the recipe

.reroll(bar=...) (or the plural bars=[...]) regenerates only the named bars, keeping their rhythm and their boundary pitches. Within each rerolled bar the first and last pitched notes stay put (they pin the bar to its neighbours) and only the interior pitches re-roll, drawing fresh choices from the phrase’s recipe. Take the open-cadence sentence from §9.2 and reroll just bar 7:

lead_line = sentence(idea, bars=8, cadence="open", seed=11)
fixed = lead_line.reroll(bar=7, seed=4)
print(fixed.describe())
Phrase 32 beats, 4 segments
  1. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
  2. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
  3. Motif 8 beats [^6@0, ^6@1, ^5@2, ^5@3, ^1@5, ^2@6, ^5@7]
  4. Motif 8 beats [^6@0, ^8@1, ^5@2, ^5@3, ^1@5, ^2@6, ^5@7]

Compare against the original sentence in §9.2: segments 1–3 are byte-for-byte identical, and in segment 4 (which is bars 7–8) only the interior of bar 7 moved — the second note went from ^6 to ^8 while the bar’s boundary notes held. Seven bars you liked, untouched; one bar, freshly dealt. Change the seed= and you get a different bar 7; the rest never budges.

Important

reroll needs a recipe — so it works on generated phrases, not hand-built ones. A phrase made by sentence, period, or Phrase.develop carries its recipe (the source motif, plan, and seed), so reroll knows what stream to draw fresh pitches from. A phrase you wrote by hand with +, or one you transformed (transposed, reversed, sliced) after generating, carries no recipe — its notes no longer come from a generator, so there is nothing honest to regenerate. Calling reroll on such a phrase raises a ValueError telling you to edit segments with replace() instead. Reroll regenerates; replace substitutes.

replace — substitute one segment wholesale

When you want to swap a bar for something specific rather than re-roll it, .replace(position, motif) substitutes the segment at a 1-based position (musicians count from one) and leaves the rest alone. It is the surgical edit that works on any phrase, generated or not:

contrast = motif([1, 1, 1, 1, None, 7, 6, 5])    # a hand-written replacement bar
edited = lead_line.replace(3, contrast)
print(edited.describe())
Phrase 32 beats, 4 segments
  1. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
  2. Motif 8 beats [^5@0, ^6@1, ^5@2, ^3@3, ^1@5, ^2@6, ^3@7]
  3. Motif 8 beats [^1@0, ^1@1, ^1@2, ^1@3, ^7@5, ^6@6, ^5@7]
  4. Motif 8 beats [^6@0, ^6@1, ^6@2, ^5@3, ^1@5, ^2@6, ^5@7]

Segment 3 is now exactly the contrast motif; segments 1, 2, and 4 are unchanged. reroll and replace are the two ends of the editing dial: reroll keeps the material and re-rolls its details, replace throws a segment out and puts a new one in.

flatten and reverse — whole-phrase shapes

Two more transforms round out the phrase toolkit, and like every transform in this Part they return a new value, leaving the original frozen.

.flatten() erases the seams: it collapses the phrase into a single long Motif. Reach for it when you are done editing and want one figure to hand to p.motif, or to stack under another part with &:

flat = lead_line.flatten()
print(flat.length, "beats,", "one motif now")
32.0 beats, one motif now

.reverse() mirrors the whole timeline — the segments reverse order and each one reverses internally, so the last note of the phrase becomes the first. On a Motif it does the same to a single figure:

retrograde = idea.reverse()
print(retrograde.describe())
Motif 8 beats [^3@0, ^2@1, ^1@2, ^3@4, ^5@5, ^6@6, ^5@7]

The idea 5 6 5 3 · 1 2 3 came back as its mirror image in time — the retrograde, a development device as old as counterpoint, here a one-word transform.

The round trip, placed

Putting the editing tools onto the canvas: build a phrase, reroll the bar that bothers you, and place the edited value. The pattern is unchanged from §9.4 — only the stored value is now the rerolled one:

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

composition = subsequence.Composition(bpm=120, key="A", scale="minor")
composition.harmony(progression=progression([1, 6, 4, 5]))

idea = motif([5, 6, 5, 3, None, 1, 2, 3])
lead_line = sentence(idea, bars=8, cadence="open", seed=11).reroll(bar=7, seed=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("hi_hat_closed", range(16), velocity=70)

@composition.pattern(channel=4, bars=2)
def lead(p):
    p.phrase(lead_line, root=notes.A4, fit=0.6)   # blend the written tune toward the chords

composition.render(bars=8, filename="phrase-reroll.mid")

That is the full manual-to-generate-and-back loop on a phrase: write a basic idea by hand, grow it with a builder, audition it, reroll or replace the bars that miss, and place the result — keeping every bar you already liked. Each transform handed you a new value; the original idea and the unedited sentence(...) are still exactly as you wrote them.

Tip

reroll, replace, and vary compose, because each returns a phrase. Chain them — sentence(idea, bars=8, seed=3).reroll(bar=5, seed=9).replace(4, tag) — and each step builds on the last, the same fluent style as the progression transforms in §7.4. When you finally .flatten(), you have one motif carrying the whole edit history’s result, ready for p.motif or a parallel stack.


You can now grow a single motif into a multi-bar phrase — by the classical sentence and period, or a development plan of your own — inspect it with print, place its per-cycle window with p.phrase, and regenerate a bar or two while keeping the rest, all on immutable values you store once and never mutate. So far a phrase loops on its own pattern. Next we give the track a shape: in Chapter 10 we build forms and sections, set energy as the arranging dial, and bind harmony, motifs, and phrases to named sections so a verse and a chorus can carry different material.