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, ...] = (),
)
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, and a trailing rest is meaningful: left to itself
it runs to the next whole beat at or after the last slot the figure
occupies, silent slots included, so a bar-long idea ending in silence is
a bar long. A length that would leave an event past the end is refused
rather than dropping it, while a note ringing past the end is fine - that
is a tie over the barline.
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
property Motif.fit: typing.Optional[float]
The fit the motif's notes share, or None when they have none or differ.
Fit belongs to each note (MotifEvent.fit): generate gives
the notes it makes 0.7, and notes written by hand have none. This reads
it back for a whole motif - 0.7 for a generated one, None for a written
one or a mix - and placement never reads it: each note plays its own.
A motif-wide fit lent a generated line's dial to every note then()
or & put beside it, and lost it wherever a motif was rebuilt (#3458).
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: one value for every note, or a list of one per note.velocitiesalso takes a(low, high)range, drawn per note.
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= sets which pitches the
walk may use. A scale name is also the scale its degrees count in, so
the line is exact in a composition using that scale, and under any
other scale its degrees map step for step, as a typed motif's do: a
"minor_pentatonic" line sounds its own notes in a minor
pentatonic piece, and minor-scale degrees in a plain minor one. An
interval list is a mask over major or minor instead ([0, 4, 7]
plays C E G in C major), unless it is exactly one of the built-in
scales. 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. In any other metre pass a length in bars ofp.bar_beats.scale: A scale name; a list of intervals above the tonic, from 0 to 12 (12 is the octave, the same as 0); or an explicit MIDI pitch pool, every note above 12.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: ...}. A degree of the line's own scale, so withscale="minor_pentatonic"the fifth is 4. 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 note: the tonic for the full closes and the fakeout, the fifth for the open half, whichever degree of the scale holds it. 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).velocitiesalso takes a(low, high)range, drawn per note.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 rebuilt fromscale=on the state's key, whose tonic is degree 1 and the note the NIR closure rule lands on (pass an explicit pool toscale=instead). A history played in another register moves by whole octaves to lie beside the pool.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. Beats shift so the window starts at 0.
A note keeps its whole duration even where that runs past the end of the window. The sequencer already lets a note ring into the next cycle, and cutting it here made a two-beat note at beat 3 last one beat under a four-beat pattern and two under an eight-beat one - the same phrase, played differently for no musical reason (#3010).
A ramp that began before the window is resumed, not dropped. It
comes back as the piece of itself that falls inside, carrying where in
the whole curve that piece sits, so it plays its own stretch of the
real sweep. An eight-beat cc_ramp(74, 0, 127) walked four beats at
a time used to send the first half and then nothing, and the filter sat
at 64 for the rest of the piece.
Motif.reverse
Motif.reverse() -> Motif
Mirror the figure in time; ramps swap direction (a rising sweep falls).
Onsets mirror around the downbeat, as p.reverse() does, so a figure
on the grid stays on it and reversing twice gives it back: notes on
beats 0, 1, 2 and 3 come back on 0, 3, 2 and 1. A ramp runs the other
way over its mirrored span with its curve mirrored too, so an
ease_in sweep comes back as an ease_out one, and a slice of a
ramp plays the mirrored slice. A control write holds its value until
the next one, so the steps of a stepped gesture come back in reverse
order; a write on the length itself closes the gesture, and stays.
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.)
An onset that snaps to the end of the motif moves to its start, since the end is the next downbeat: hits at 1, 2 and 3.8 in four beats, snapped to half beats, play at 0, 1 and 2.
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[typing.Union[int, Degree]] = 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.
By default the first note is the pivot, and it stays exactly where it
is. Degrees mirror in scale steps, so a note two steps above the pivot
lands two steps below it, an octave higher lands an octave lower, and
a line may go below the tonic. A note on the pivot's own step keeps
its accidental; every other accidental flips, sharp to flat, so the
mirror keeps to the key: in C major [^3b1, ^5, ^1] (E♭ G C)
becomes E♭ C G. Inverting twice gives back the motif.
An int pivot is a MIDI note for MIDI content and a scale step for
degree content; a Degree pivot also gives an octave and an
accidental. A motif mixing MIDI notes and degrees raises, since one
pivot cannot mirror both.
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). In any other metre passp.bar_beats, which is one bar of the composition's.
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-segmented at whichever original boundaries fall inside it.
The window is taken from the whole timeline and then divided up, rather
than each segment being sliced against its own bounds. The difference
is what happens to something that crosses an internal boundary: a note
ringing over it, or a ramp sweeping across it, used to lose everything
past the edge of the segment it started in - which undid
rotate's promise that a note may ring past its segment, and
meant phrase.slice(0, phrase.length) was not the phrase (#3010).
Each event belongs to the segment its onset falls in, and goes there whole.
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[typing.Union[int, Degree]] = 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.
None is a rest, and it holds its slot: motif([1, 3, 5, None]) is
four beats long, not three, so a figure that ends in silence still loops
on the bar it was written for.
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). In any other metre passp.bar_beats, which is one bar of the composition's.
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 (4 by default). In any other metre passp.bar_beats.
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).
A line can also go below the tonic: 0 is the step just under it and -1
the one under that, so transpose(steps=-1) and invert() work on
a melody that starts on the tonic. A 0 typed into a melody is still
refused, as the slip it usually is: motif([0, 1, 2]) raises.
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.
Normalise 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,
fit: typing.Optional[float] = 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.
fit is how likely the note is to move to a chord tone when it lands
on a strong beat, 0.0 to 1.0. Motif.generate gives each note it
makes a fit of 0.7; a note written by hand has none and plays exactly as
written. The fit travels with the note through every transform, so a
generated line keeps its fit however it is cut, joined or stacked, and
never lends it to a written one.
Members: beat, duration, fit, 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
MotifEvent.fit
MotifEvent.fit: typing.Optional[float] = 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,
shape_from: float = 0.0,
shape_to: 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.
shape_from and shape_to say which part of the whole gesture this
is, as fractions of it: 0.0 and 1.0 for all of it. A ramp that a phrase's
window cuts through keeps the whole ramp's start, end and shape,
and plays only its own stretch of that curve, from shape_from to
shape_to, rather than a fresh ramp between two sampled values.
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, is_partial, probability, shape, shape_from, shape_to, 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
ControlEvent.shape_from
ControlEvent.shape_from: float = 0.0
ControlEvent.shape_to
ControlEvent.shape_to: float = 1.0
ControlEvent.is_partial
property ControlEvent.is_partial: bool
True when this is a piece of a longer gesture rather than all of one.