subsequence.motifs
The API reference for Motif, Phrase, motif, sentence, period, Degree, ChordTone, Approach, MotifEvent, and ControlEvent.
Motif
class Motif(
events: typing.Tuple[MotifEvent, ...],
length: float,
controls: typing.Tuple[ControlEvent, ...] = (),
fit: typing.Optional[float] = None,
)
An immutable musical figure: timed note events + control gestures + a length in beats.
Construct via the classmethods (degrees, notes,
hits, steps, euclidean, the control-gesture
constructors, or from_events) rather than positionally.
length is explicit — a trailing rest is meaningful.
Members: accent, answer, cc, cc_ramp, controls, degrees, describe, empty, euclidean, events, fit, from_events, generate, hits, invert, join, length, notes, nrpn, nrpn_ramp, onsets, osc, osc_ramp, pitch_bend, pitch_bend_ramp, pitched, preset, quantize, reverse, rhythm, rotate, rpn, rpn_ramp, slice, stack, steps, stretch, then, transpose, vary, with_velocity
Motif.events
Motif.events: typing.Tuple[MotifEvent, ...]
Motif.length
Motif.length: float
Motif.controls
Motif.controls: typing.Tuple[ControlEvent, ...] = ()
Motif.fit
Motif.fit: typing.Optional[float] = None
Motif.empty
classmethod Motif.empty() -> Motif
The empty motif (zero events, zero length) — the identity for then.
Motif.from_events
classmethod Motif.from_events(
events: typing.Iterable[MotifEvent],
length: typing.Optional[float] = None,
controls: typing.Iterable[ControlEvent] = (),
) -> Motif
Build a motif from explicit events (power use; length defaults to the next whole beat).
Motif.degrees
classmethod Motif.degrees(
degrees: typing.List[typing.Union[int, Degree, None]],
beats: typing.Optional[typing.List[float]] = None,
velocities: typing.Any = _DEFAULT_VELOCITY,
durations: typing.Any = 1.0,
probabilities: typing.Any = 1.0,
length: typing.Optional[float] = None,
) -> Motif
A melody written as 1-based scale degrees, one per beat by default.
Elements are ints (1 = tonic, 8 = tonic an octave up), None for a
rest (the beat slot still advances), or Degree for octave/
chromatic detail. Resolved against key + scale at placement.
Durations default to a full beat (each note holds its slot).
Motif.notes
classmethod Motif.notes(
notes: typing.List[typing.Union[int, None]],
beats: typing.Optional[typing.List[float]] = None,
velocities: typing.Any = _DEFAULT_VELOCITY,
durations: typing.Any = 1.0,
probabilities: typing.Any = 1.0,
length: typing.Optional[float] = None,
) -> Motif
A melody written as absolute MIDI note numbers (60 = middle C); None = rest.
Motif.hits
classmethod Motif.hits(
pitch: typing.Union[int, str],
beats: typing.List[float],
length: typing.Optional[float] = None,
velocities: typing.Any = _DEFAULT_VELOCITY,
durations: typing.Any = 0.1,
probabilities: typing.Any = 1.0,
) -> Motif
One pitch (usually a drum name) at a list of beat positions — the hit() convention.
Motif.steps
classmethod Motif.steps(
steps: typing.List[int],
pitches: typing.Any,
velocities: typing.Any = _DEFAULT_VELOCITY,
durations: typing.Any = 0.1,
probabilities: typing.Any = 1.0,
step_duration: float = 0.25,
length: typing.Optional[float] = None,
) -> Motif
Grid placement — the sequence() convention: steps are 0-based
grid indices (sixteenths by default), pitches a scalar or
parallel list of MIDI ints or drum names.
Motif.euclidean
classmethod Motif.euclidean(
pulses: int,
steps: int,
pitch: typing.Union[int, str],
length: float = 4.0,
velocities: typing.Any = _DEFAULT_VELOCITY,
durations: typing.Any = 0.1,
probabilities: typing.Any = 1.0,
) -> Motif
A euclidean rhythm as a value: pulses spread evenly across steps over length beats.
Motif.preset
classmethod Motif.preset(
name: str,
pitch: typing.Optional[typing.Union[int, str]] = None,
length: float = 4.0,
velocities: typing.Any = _DEFAULT_VELOCITY,
durations: typing.Any = 0.1,
probabilities: typing.Any = 1.0,
) -> Motif
A named world-rhythm timeline as a value — Motif.preset("son_clave_3_2").
Looks a curated timeline up in the world-rhythm table (clave family, West-African bell patterns, tresillo/cinquillo, samba) and lays its onsets across length beats. Onset positions are exact pulse indices from Toussaint's "The Geometry of Musical Rhythm"; each preset declares its own grid (16 for the clave/4-4 timelines, 12 for the bell patterns) and a default drum voice.
Parameters
name: A preset name (KeyError-style ValueError lists them all).pitch: The voice — a drum name or MIDI int; defaults to the preset's General-MIDI voice ("claves","cowbell","side_stick","low_conga"), so it sounds against the standard GM drum map without apitch=.length: Total beats the cycle spans (4 = one common-time bar).velocities / durations / probabilities: The parallel-list params.
Returns
- A drum/pitched
Motifof the timeline's onsets.
Raises
ValueError: If name is not a known preset.
Example
clave = subsequence.Motif.preset("son_clave_3_2") # GM "claves"
bell = subsequence.Motif.preset("bembe", pitch="cowbell") # 12-pulse
Motif.cc
classmethod Motif.cc(
control: typing.Union[int, str],
values: typing.List[int],
beats: typing.List[float],
length: typing.Optional[float] = None,
probabilities: typing.Any = 1.0,
) -> Motif
Discrete CC writes at beat positions — mirrors p.cc(); names resolve at placement.
Motif.cc_ramp
classmethod Motif.cc_ramp(
control: typing.Union[int, str],
start: int,
end: int,
beat_start: float = 0.0,
beat_end: typing.Optional[float] = None,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
length: typing.Optional[float] = None,
probability: float = 1.0,
) -> Motif
A CC value swept start → end over a beat range — mirrors p.cc_ramp().
Motif.pitch_bend
classmethod Motif.pitch_bend(
values: typing.List[float],
beats: typing.List[float],
length: typing.Optional[float] = None,
probabilities: typing.Any = 1.0,
) -> Motif
Discrete pitch-bend writes (-1.0 to 1.0) at beat positions — mirrors p.pitch_bend().
Motif.pitch_bend_ramp
classmethod Motif.pitch_bend_ramp(
start: float,
end: float,
beat_start: float = 0.0,
beat_end: typing.Optional[float] = None,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
length: typing.Optional[float] = None,
probability: float = 1.0,
) -> Motif
Pitch bend swept start → end (-1.0 to 1.0) over a beat range — mirrors p.pitch_bend_ramp().
Motif.nrpn
classmethod Motif.nrpn(
parameter: typing.Union[int, str],
values: typing.List[int],
beats: typing.List[float],
fine: bool = False,
null_reset: bool = True,
length: typing.Optional[float] = None,
probabilities: typing.Any = 1.0,
) -> Motif
Discrete NRPN parameter writes at beat positions — mirrors p.nrpn().
Motif.nrpn_ramp
classmethod Motif.nrpn_ramp(
parameter: typing.Union[int, str],
start: int,
end: int,
beat_start: float = 0.0,
beat_end: typing.Optional[float] = None,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
fine: bool = True,
null_reset: bool = True,
length: typing.Optional[float] = None,
probability: float = 1.0,
) -> Motif
An NRPN value swept over a beat range — mirrors p.nrpn_ramp().
Motif.rpn
classmethod Motif.rpn(
parameter: typing.Union[int, subsequence.declarations.RpnParameter],
values: typing.List[int],
beats: typing.List[float],
fine: bool = False,
null_reset: bool = True,
length: typing.Optional[float] = None,
probabilities: typing.Any = 1.0,
) -> Motif
Discrete RPN parameter writes at beat positions — mirrors p.rpn().
Motif.rpn_ramp
classmethod Motif.rpn_ramp(
parameter: typing.Union[int, subsequence.declarations.RpnParameter],
start: int,
end: int,
beat_start: float = 0.0,
beat_end: typing.Optional[float] = None,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
fine: bool = True,
null_reset: bool = True,
length: typing.Optional[float] = None,
probability: float = 1.0,
) -> Motif
An RPN value swept over a beat range — mirrors p.rpn_ramp().
Motif.osc
classmethod Motif.osc(
address: str,
values: typing.List[float],
beats: typing.List[float],
length: typing.Optional[float] = None,
probabilities: typing.Any = 1.0,
) -> Motif
Discrete OSC float sends at beat positions — mirrors p.osc().
Motif.osc_ramp
classmethod Motif.osc_ramp(
address: str,
start: float,
end: float,
beat_start: float = 0.0,
beat_end: typing.Optional[float] = None,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
length: typing.Optional[float] = None,
probability: float = 1.0,
) -> Motif
An OSC float swept over a beat range — mirrors p.osc_ramp().
Motif.then
Motif.then(other: Motif) -> Motif
Closed sequential concat: glue other after this motif into ONE longer motif.
Motif.join
classmethod Motif.join(motifs: typing.Iterable[Motif]) -> Motif
Fold a list of motifs into one with then (empty list → Motif.empty()).
Motif.generate
classmethod Motif.generate(
rhythm: typing.Any,
length: typing.Optional[float] = None,
scale: typing.Optional[typing.Union[str, typing.Sequence[int]]] = None,
contour: typing.Optional[str] = None,
end_on: typing.Optional[typing.Union[int, Degree]] = None,
cadence: typing.Optional[str] = None,
pins: typing.Optional[typing.Dict[int, typing.Union[int, Degree]]] = None,
max_pitches: typing.Optional[int] = None,
velocities: typing.Any = _DEFAULT_VELOCITY,
durations: typing.Any = 0.25,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
state: typing.Optional[typing.Any] = None,
nir_strength: float = 0.5,
pitch_diversity: float = 0.6,
tessitura_strength: float = 0.6,
) -> Motif
Generate a melodic motif — rhythm first, pitches walked, a value out.
The melody engine emitting a value: you give the rhythm (an onset list in beats, or another motif whose rhythm to borrow — cross-pattern rhythm reuse is shared values); the engine walks pitches over it through the soft scoring factors (NIR expectation, contour envelope, tessitura regression, diversity), honouring any pins.
The result emits scale degrees (resolved at placement against the
composition key/scale), so a generated hook transposes, varies, and
develops like a hand-written one. scale= constrains candidate
choice only: a name or interval list masks which pitches the walk
may use, spelled relative to its best-fit reference (major or minor)
— bind it in a composition whose scale matches that family and
resolution is exact. An explicit MIDI pitch pool (a list of note
numbers) switches to absolute output (the sieve/atonal path).
Parameters
rhythm: Onset beats ([0, 1, 1.5, 1.75, 2.5]) or a Motif (its onsets are borrowed).length: Motif length in beats; defaults to the onsets rounded up to a whole 4-beat bar.scale: A scale name, an interval list, or an explicit MIDI pitch pool.None= the plain seven degrees.contour: Envelope shaping the line's height over its span —"arch","valley","ascending","descending".end_on: Degree the line must end on — sugar forpins={-1: ...}. Degree semantics: raises with an explicit MIDI pool (pin the exact note instead).cadence: A cadence name ("strong"/"soft"/"open"/"fakeout") — the line closes on that cadence's melodic degree (1 for the full closes and the fakeout, 5 for the open half). Sugar forend_on=; conflicts with it, and raises with an explicit MIDI pool likeend_on=.pins:{position: degree}— 1-based note positions (-1= the last, the Python idiom); the engine fills between. With an explicit MIDI pool there are no degrees to read, so each pin is the exact MIDI note to play (Degreepins raise).max_pitches: Cap on distinct pitches (a tight pool is a hook); keeps the most central candidates.velocities / durations: Scalar or per-note list (the parallel- list convention).seed: Seed for the walk (required or warned — module-level nondeterminism breaks live reload).rng: Explicit stream (overridesseed).state: AMelodicStatewhose dials, scoring factors, and melodic history seed the walk. It is copied — building a value never mutates a module-level live object. The candidate pool is not carried over: it is always rebuilt fromscale=(pass an explicit pool there instead), though the state's key still sets the tonic that the NIR closure rule lands on.nir_strength / pitch_diversity / tessitura_strength: The walk's dials when nostateis given.
Example
hook = subsequence.Motif.generate(
rhythm=[0, 1, 1.5, 1.75, 2.5], scale="minor_pentatonic",
contour="arch", end_on=1, seed=7,
)
Motif.stack
Motif.stack(other: typing.Union[Motif, Phrase]) -> Motif
Parallel merge (the spelled form of &): event union, length = max.
No implicit tiling — a short gesture stacked under a long figure plays once. Phrase operands flatten first.
Motif.slice
Motif.slice(start: float, end: float) -> Motif
A window onto the motif, on its own authority: events starting outside are dropped; durations and ramp spans truncate at the cut (a truncated ramp ends at its interpolated cut value). Beats shift so the window starts at 0.
Motif.reverse
Motif.reverse() -> Motif
Mirror the figure in time; ramps swap direction (a rising sweep falls).
Motif.rotate
Motif.rotate(beats: float) -> Motif
Shift every onset by beats, wrapping modulo the length (spans ride along).
Motif.stretch
Motif.stretch(factor: float) -> Motif
Scale time by factor (2.0 = half-time feel): beats, durations, spans, and length.
Motif.quantize
Motif.quantize(grid: float) -> Motif
Snap note onsets to the nearest multiple of grid beats (control gestures untouched).
An onset exactly midway between grid lines snaps LATER (round half
up) — every midpoint moves the same way, the predictable behaviour
for a musician. (Python's own round() is half-to-even, which
made exact midpoints snap in alternating directions.)
Motif.accent
Motif.accent(beat: float, amount: int = 20) -> Motif
Add amount velocity to every note at the given beat position (0-based beats).
Motif.with_velocity
Motif.with_velocity(
velocity: subsequence.declarations.VelocityValue,
) -> Motif
Replace every note's velocity (an int, or a (low, high) random range).
Motif.vary
Motif.vary(
notes: int = 1,
position: str = 'end',
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
keep_contour: bool = False,
) -> Motif
Replace a few pitches, preserving the rhythm — the smallest variation.
Rhythm, velocities, durations, rests, and control gestures are untouched; only the chosen notes' pitches move (by a small melodic nudge: scale steps for degrees, semitones for MIDI ints).
Parameters
notes: How many pitched notes to vary (clamped to what exists).position: Which notes —"end"(the tail, the default),"start", or"anywhere"(drawn from the stream).seed: Seed for the variation. A standalone vary without a seed warns — module-level nondeterminism breaks live reload.rng: An explicit random stream (overridesseed; used by recipe machinery).keep_contour: When True, the variation preserves the line's CSEG — every varied note keeps its rank relations with every other note, so the melodic shape is identical (the motif-identity guard). Where no nudge can preserve the contour, that note stays unchanged — shape wins over motion.
Example
answer = call.vary(notes=1, seed=4) # same figure, new tail note
Motif.answer
Motif.answer(to: typing.Union[int, Degree] = 1) -> Motif
Call → response: re-aim the tail to a stable degree.
The classic consequent move — the figure repeats but its last pitched
note lands home (degree 1 by default; pass to=5 for a half-close,
or a full Degree for register control). Everything else —
rhythm, the other pitches, velocities, controls — is untouched.
Degree content only: absolute MIDI has no degrees to re-aim (build
the call with motif([...])), and drums raise.
Motif.pitched
Motif.pitched(spec: PitchSpec) -> Motif
Replace every pitch with one spec — a kick rhythm becomes a bass line.
"root" / "third" / "fifth" / "seventh" become chord
tones; any other string is a drum name; ints are MIDI; Degree /
ChordTone / Approach pass through.
Motif.rhythm
Motif.rhythm() -> Motif
Strip pitches (and control gestures): a reusable rhythmic skeleton.
Timing, velocities, durations, and probabilities survive; re-pitch
with pitched before placement (placing a skeleton raises).
Motif.onsets
Motif.onsets() -> typing.List[float]
The note onset beats, in order — ready for rhythm-first generation.
Motif.transpose
Motif.transpose(
steps: typing.Optional[int] = None,
semitones: typing.Optional[int] = None,
) -> Motif
Transpose pitched content; the keyword names the unit.
steps= moves scale degrees diatonically (the sequencing move) and
raises on absolute-MIDI or drum content; semitones= is the
literal chromatic form for MIDI ints and degrees. Drum motifs raise
on both — a transposed drum name is a different instrument, not a
transposition — and a captured drum raises too, because its number
still remembers which instrument it came from.
Motif.invert
Motif.invert(pivot: typing.Optional[int] = None) -> Motif
Mirror pitches around a pivot: MIDI content around a MIDI pivot, degree content around a degree pivot (default: the first note's pitch). Drum motifs raise, captured ones included.
Motif.describe
Motif.describe() -> str
A readable one-line summary: length, notes (pitch@beat), and control gestures.
Phrase
class Phrase(
segments: typing.Iterable[Motif],
recipe: typing.Optional[_PhraseRecipe] = None,
)
A sequence of Motifs with segmentation preserved.
Segmentation is the unit of editing — it is what development and
per-region regeneration operate on. flatten() erases it into one
long Motif. Length is the sum of segment lengths.
A phrase made by develop carries its recipe, so
reroll can regenerate a region; transforms and hand edits
return recipe-less phrases (their notes no longer come from the
recipe, so there is nothing honest to regenerate from).
Coerce any iterable of Motifs.
Members: describe, develop, flatten, invert, length, pitched, quantize, recipe, replace, reroll, reverse, rhythm, rotate, segments, slice, stack, stretch, transpose, with_velocity
Phrase.segments
Phrase.segments: typing.Tuple[Motif, ...]
Phrase.recipe
Phrase.recipe: typing.Optional[_PhraseRecipe]
Phrase.length
property Phrase.length: float
Total length in beats (sum of segment lengths).
Phrase.develop
classmethod Phrase.develop(
motif: Motif,
bars: int = 8,
plan: typing.Optional[typing.Union[typing.Sequence[str], str]] = None,
seed: typing.Optional[int] = None,
beats_per_bar: float = 4.0,
) -> Phrase
Grow a motif into a phrase by a plan — the phrase generator.
plan follows the standard form. The literal form is a list of
unit labels — plan=["a", "a", "a", "b"], equivalently
["a"] * 3 + ["b"]: the first label is the given motif, each new
label is a generated contrast unit (the source's rhythm, freshly
re-pitched), a repeated label is a restatement, and bars spreads
evenly across the units. A bare string is a recipe name from
the curated table — plan="call_response" (call, answer, call,
varied answer) — reserved for plans whose semantics exceed a label
skeleton. A letter string is not a plan: a sequence of labels is a
sequence, so it is a list.
The result carries its recipe, so reroll can regenerate a
region later.
Parameters
motif: The source unit (its length must bebars / len(units)bars — the plan's units tile the phrase exactly).bars: Phrase length in bars (must divide evenly by the unit count).plan: A list of unit labels, or a recipe name.seed: Seed for the generated units. Without one, develop() warns — module-level nondeterminism breaks live reload.beats_per_bar: Bar size in beats (the value is context-free; 4 is the common-time default).
Example
call = subsequence.motif([5, 6, 5, 3, None, 1, 2, 3])
lead = subsequence.Phrase.develop(call, bars=8, plan="call_response", seed=11)
Phrase.reroll
Phrase.reroll(
bar: typing.Optional[int] = None,
bars: typing.Optional[typing.Sequence[int]] = None,
seed: typing.Optional[int] = None,
) -> Phrase
Regenerate only the named bars — rhythm and boundary pitches kept.
Within each named bar, the first and last pitched notes stay (the
boundary pins) and the interior pitches re-roll from a fresh per-bar
stream salted by seed= (an unseeded call draws a fresh salt, so
each call genuinely differs); onsets, durations, velocities, rests,
drums, and control gestures are untouched. Segmentation and the
recipe survive, so rerolls compose.
Only a phrase that carries a recipe can reroll — a hand-written or transformed phrase raises loudly (its notes no longer come from a generator, so regenerating them would invent music).
Parameters
bar: A single 1-based bar to reroll.bars: A list of 1-based bars (the paired plural spelling).seed: Seed for the new pitches (salted per bar). Without one, reroll() warns.
Example
lead = lead.reroll(bar=7, seed=4) # only bar 7; rhythm + boundaries kept
Phrase.flatten
Phrase.flatten() -> Motif
Erase segmentation: one long Motif (the monoid homomorphism onto then).
Phrase.stack
Phrase.stack(other: typing.Union[Motif, Phrase]) -> Motif
The spelled form of & — flattens, then merges.
Phrase.slice
Phrase.slice(start: float, end: float) -> Phrase
A window; re-segments at the cut points (partial segments are sliced).
Phrase.replace
Phrase.replace(position: int, motif: Motif) -> Phrase
Replace the segment at a 1-based position (musicians count from one).
Phrase.reverse
Phrase.reverse() -> Phrase
Reverse the whole timeline: segments reverse order AND each reverses internally.
Phrase.rotate
Phrase.rotate(beats: float) -> Phrase
Rotate the whole timeline modulo the total length, then re-segment at the original boundaries.
Phrase.stretch
Phrase.stretch(factor: float) -> Phrase
Scale time in every segment (lengths scale with them).
Phrase.quantize
Phrase.quantize(grid: float) -> Phrase
Snap note onsets segment-wise.
Phrase.with_velocity
Phrase.with_velocity(
velocity: subsequence.declarations.VelocityValue,
) -> Phrase
Replace every note's velocity, segment-wise.
Phrase.pitched
Phrase.pitched(spec: PitchSpec) -> Phrase
Replace every pitch, segment-wise.
Phrase.rhythm
Phrase.rhythm() -> Phrase
Strip pitches segment-wise: a phrase-shaped skeleton.
Phrase.transpose
Phrase.transpose(
steps: typing.Optional[int] = None,
semitones: typing.Optional[int] = None,
) -> Phrase
Transpose every segment (see Motif.transpose).
Phrase.invert
Phrase.invert(pivot: typing.Optional[int] = None) -> Phrase
Mirror pitches in every segment around one pivot (see Motif.invert).
Phrase.describe
Phrase.describe() -> str
A readable summary: total length and each segment on its own line.
motif
motif(
degrees: typing.List[typing.Union[int, Degree, None]],
beats: typing.Optional[typing.List[float]] = None,
velocities: typing.Any = _DEFAULT_VELOCITY,
durations: typing.Any = 1.0,
probabilities: typing.Any = 1.0,
length: typing.Optional[float] = None,
) -> Motif
The lowercase shortcut: a melody as 1-based scale degrees.
subsequence.motif([5, 6, 5, 3]) is Motif.degrees([5, 6, 5, 3]) —
relative pitch is the primary form. For absolute MIDI note numbers use
Motif.notes([64, 65, 64, 60]); implausibly large ints here raise so
a pasted MIDI list fails loud instead of squealing octaves up.
sentence
sentence(
motif: Motif,
bars: int = 8,
cadence: str = 'strong',
seed: typing.Optional[int] = None,
beats_per_bar: float = 4.0,
) -> Phrase
The classical sentence, as a thin combinator — idea, idea, drive, close.
Four units: the basic idea stated twice (the presentation), a generated contrast unit (the continuation — the source's rhythm, freshly re-pitched), and a second contrast unit whose tail lands on the cadence's close degree (the cadential close). An 8-bar sentence from a 2-bar idea is the textbook proportion; a shorter idea tiles up to the unit size first.
The melodic side of a cadence only — pair it with the harmonic side
(prog.cadence(), Progression.generate(cadence=), or
request_cadence()) and the two arrive together.
Parameters
motif: The basic idea (degree content — the close re-aims a degree).bars: Sentence length (must divide evenly across the 4 units).cadence: The close —"strong"lands on 1,"open"on 5,"soft"/"fakeout"on 1 (theory aliases accepted).seed: Seed for the generated continuation units (seed-or-warn).beats_per_bar: Bar size in beats (context-free; 4 is the default).
Example
idea = subsequence.motif([5, 6, 5, 3, None, 1, 2, 3])
verse_lead = subsequence.sentence(idea, bars=8, cadence="open", seed=11)
period
period(
antecedent: typing.Union[Motif, Phrase],
cadence: str = 'strong',
beats_per_bar: float = 4.0,
) -> Phrase
The classical period, as a thin combinator — question, then answer.
Two halves: the antecedent with its tail re-aimed to the open half-close (degree 5 — the question), then the same material restated with its tail on the cadence's close degree (the answer). The two halves differ exactly at their closes — the open/closed contrast is the period.
Deterministic: no notes are generated, only the two tail notes re-aim
(so there is no seed). Vary the consequent yourself for a looser
restatement: period(a).reroll(bar=7, seed=4).
Parameters
antecedent: The first half — a Motif, or a Phrase whose segmentation is kept (only its last segment's tail re-aims).cadence: The consequent's close —"strong"lands on 1 (theory aliases accepted).beats_per_bar: Bar size in beats, recorded forreroll()windows.
Example
idea = subsequence.motif([3, 4, 5, 1, None, 6, 5, 4], length=8)
lead = subsequence.period(idea) # 16 beats: half-close, then home
Degree
class Degree(step: int, octave: int = 0, chroma: int = 0)
A scale degree — 1-based, resolved against key + scale at placement.
Degree 1 is the tonic; 8 is the tonic an octave up (steps may exceed the
scale length and resolve into higher octaves). octave shifts whole
octaves; chroma is a chromatic offset in semitones (+1 = sharpened).
Degree.step
Degree.step: int
Degree.octave
Degree.octave: int = 0
Degree.chroma
Degree.chroma: int = 0
ChordTone
class ChordTone(index_or_name: typing.Union[int, str], octave: int = 0)
An index into the current chord's tones — 1-based, resolved at placement.
Accepts an int (1 = root, 2 = third, ...) or one of the names
"root" / "third" / "fifth" / "seventh". octave
shifts whole octaves.
Normalize a tone name to its 1-based index.
ChordTone.index
ChordTone.index: int
ChordTone.octave
ChordTone.octave: int = 0
Approach
class Approach(target: typing.Union[int, Degree, ChordTone])
A half-step approach into a target pitch at the next chord boundary.
Resolves at placement, one semitone below its target (the leading-tone
approach); a ChordTone target reads the NEXT chord through the
harmony window, so the approach lands as the harmony arrives.
Approach.target
Approach.target: typing.Union[int, Degree, ChordTone]
MotifEvent
class MotifEvent(
beat: float,
pitch: PitchSpec,
velocity: subsequence.declarations.VelocityValue = _DEFAULT_VELOCITY,
duration: float = 0.25,
probability: float = 1.0,
origin: typing.Optional[str] = None,
)
One timed note event inside a Motif.
pitch is a specification: an absolute MIDI int, a drum name string,
a Degree, ChordTone, or Approach — or None
for a pitch-stripped skeleton event (see Motif.rhythm), which
must be re-pitched via Motif.pitched before placement.
velocity is an int or a (low, high) random-range tuple.
origin names the drum a pitch was resolved from. Only
capture sets it —
capture reads notes back as absolute MIDI, so "kick" arrives here as
36 — and it is what lets the pitch-moving methods go on refusing a
drum they can no longer see.
Members: beat, duration, origin, pitch, probability, velocity
MotifEvent.beat
MotifEvent.beat: float
MotifEvent.pitch
MotifEvent.pitch: PitchSpec
MotifEvent.velocity
MotifEvent.velocity: subsequence.declarations.VelocityValue = _DEFAULT_VELOCITY
MotifEvent.duration
MotifEvent.duration: float = 0.25
MotifEvent.probability
MotifEvent.probability: float = 1.0
MotifEvent.origin
MotifEvent.origin: typing.Optional[str] = None
ControlEvent
class ControlEvent(
beat: float,
signal: ControlSignal,
start: float,
end: typing.Optional[float] = None,
span: float = 0.0,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
probability: float = 1.0,
)
One timed control gesture inside a Motif: a discrete write or a shaped ramp.
A discrete write has end=None and span=0.0; a ramp interpolates
start → end over span beats through the easing shape.
Pulse density (resolution=) is deliberately not stored here — beats
and shapes are music; MIDI traffic density is set at the placement call.
Members: beat, end, probability, shape, signal, span, start
ControlEvent.beat
ControlEvent.beat: float
ControlEvent.signal
ControlEvent.signal: ControlSignal
ControlEvent.start
ControlEvent.start: float
ControlEvent.end
ControlEvent.end: typing.Optional[float] = None
ControlEvent.span
ControlEvent.span: float = 0.0
ControlEvent.shape
ControlEvent.shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear'
ControlEvent.probability
ControlEvent.probability: float = 1.0