You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+224Lines changed: 224 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -897,7 +897,10 @@ composition.form(my_form())
897
897
|`progress`|`float`|`bar / bars` (0.0 → ~1.0) |
898
898
|`first_bar`|`bool`| True on the first bar of the section |
899
899
|`last_bar`|`bool`| True on the last bar of the section |
900
+
|`ending`|`bool`| True on the last bar before a *different* section (repeats don't count) |
900
901
|`next_section`|`str?`| Name of the upcoming section, or `None` at the end |
902
+
|`energy`|`float`| The section's energy payload (0.5 unless a bound `Form` says otherwise) |
903
+
|`key`|`str?`| The section's key override, or `None` (the composition key) |
901
904
902
905
`next_section` is pre-decided when the current section begins (graph mode picks probabilistically; list mode peeks the iterator). Use it for lead-ins:
903
906
@@ -915,6 +918,121 @@ A performer or code can override the pre-decided next section with `composition.
915
918
916
919
For a `beats=4` pattern in 4/4, they're always equal. For a `beats=8` pattern, `p.cycle` is half `p.bar` (the pattern runs once every two bars). For a `beats=2` pattern, `p.cycle` is double `p.bar`. Use `p.bar` for composition-wide synchronisation (e.g. "fire on bar 8") and `p.cycle` for pattern-local variation (e.g. "every 4th rebuild of this pattern").
917
920
921
+
### Form values: Section and Form
922
+
923
+
A form can also be a **value** — a frozen `Form` of `Section`s, each carrying its payload (`bars`, `energy`, an optional `key` override). Values are inspectable and editable before binding:
form = form.replace(3, bars=8) # 1-based slots; field edits or whole Sections
934
+
form = form.with_energy({"chorus": 0.95})
935
+
936
+
composition.form(form, at_end="stop")
937
+
```
938
+
939
+
`at_end=` names what happens when a sequence form runs out: `"stop"` (the form finishes — the default), `"hold"` (the final section repeats until you navigate away), or `"loop"` (start over; `loop=True` is sugar for it).
940
+
941
+
**Form freeze.** A graph form can be frozen into an editable value — the walk is seeded, so the frozen path is exactly the path the live graph would have played:
942
+
943
+
```python
944
+
composition.form({...}, start="intro") # the graph
945
+
path = composition.form_freeze() # walk → editable Form (until a terminal section)
946
+
path = path.replace(2, bars=24) # stretch the verse
947
+
composition.form(path, at_end="stop") # rebind the edited value
948
+
```
949
+
950
+
**Navigation works on every navigable form**: `composition.form_jump("chorus")` and `composition.form_next("chorus")` work on graph forms *and* sequence forms (the jump lands on the next occurrence of the name, wrapping). Only generator forms cannot be navigated.
951
+
952
+
**`Section.key`** and **`Section.scale`** re-anchor the section — see [How key resolution works](#how-key-resolution-works) below for the full model and the modulation story.
953
+
954
+
### How key resolution works
955
+
956
+
Whether the key moves a piece of content depends on **how you spelled it** — that is the contract. There are three intents:
957
+
958
+
| Intent | How you spell it | Resolves against | Does a key move it? |
|**Key-relative**| scale degrees (`motif([1,5,6])`), romans (`"V"`, `[1,4,5]`), generated relative material | a key + scale |**Yes**|
962
+
|**Chord-relative**|`ChordTone("third")`, `Approach(...)`, the `fit` dial | the sounding/next chord | No — it tracks the chord, whatever key that chord is in |
963
+
964
+
So `progression(["Am", "F"])` never moves (you named exact chords), `progression([1, 6])` follows the key (you wrote intervals), and `ChordTone("root")` plays the root of whatever chord is sounding regardless of key. The same applies everywhere — a motif, a chord part, a generated phrase — so you never have to remember per-feature exceptions.
965
+
966
+
**The key a relative thing resolves against is layered**, most specific wins:
967
+
968
+
```
969
+
Section.key > form key (form(key=...) / Form(key=...)) > Composition(key=...)
970
+
```
971
+
972
+
Key and scale/mode resolve independently, so a section can move the tonic, the mode, or both.
973
+
974
+
**Modulation — the truck-driver's key change.** Because a numbered progression is key-relative, the *same* progression bound to two sections in two keys plays in two keys — chords and melody move together:
975
+
976
+
```python
977
+
S = subsequence.Section
978
+
composition.form(subsequence.Form([
979
+
S("chorus", 8),
980
+
S("chorus_final", 8, key="D"), # up a tone for the last chorus
981
+
]))
982
+
983
+
prog = subsequence.progression([1, 5, 6, 4]) # written once, in numbers
984
+
composition.section_chords("chorus", prog) # plays in C (the composition key)
985
+
composition.section_chords("chorus_final", prog) # the SAME value, now in D
986
+
```
987
+
988
+
If you wanted the chords to *stay put* under a moving melody, you'd spell them absolute — `["C", "G", "Am", "F"]` — and they wouldn't budge. That choice is yours, made by how you write the chords.
989
+
990
+
Two boundaries worth knowing:
991
+
992
+
-**The live graph engine stays in the composition key.**`harmony(style="...")` generates chords in one key for the whole piece — it doesn't modulate per section (a stateful walk doesn't transpose mid-stream). To modulate generated harmony, write the section's changes (relative or absolute) with `section_chords`.
993
+
-**A re-keyed section that runs out of written chords falls through to the live engine in the composition key.** If `Section("verse", bars=8, key="D")` has only 4 bars of `section_chords` bound, bars 5–8 hand off to composition-key harmony. Bind the full section length if you want it all in the section's key. (Rare, but documented so it's never a surprise.)
994
+
995
+
### Energy: the arranging dial
996
+
997
+
`composition.energy({...})` sets a per-section energy level — one plain dict, with `(start, end)` tuples interpolating across a section (the build gesture):
p.bresenham("conga", 7) # silent until the energy reaches 0.8
1015
+
```
1016
+
1017
+
The dict **overrides** any energy payload carried by bound `Section` values (the dict is the later, performance-level dial). `min_energy` composes with `mute()`/`unmute()` — a performer mute always wins.
1018
+
1019
+
### Section boundaries: transitions and the section event
1020
+
1021
+
`composition.transition()` declares boundary material in one line — an automatic fill before a section change, or a mute over the approach:
1022
+
1023
+
```python
1024
+
composition.transition(before="*", fill=FILL, channel=10, beat=2.0) # any change: fill in the last bar
1025
+
composition.transition(before="drop", mute=["pads"], beats=4) # pads drop out approaching the drop
1026
+
```
1027
+
1028
+
`before` names the incoming section, or `"*"` for any *different* section (repeats don't fire it). Fills play in the final bar, starting at `beat`; their drum names resolve through the rule's `drum_note_map=` or, if omitted, a map borrowed from a registered pattern on the same channel. Mutes are bar-granular (`beats` rounds up to whole bars) and reopen at the boundary.
1029
+
1030
+
`composition.on_section(fn)` fires on every section change (one lookahead-beat early, in time to affect the new section's first patterns), receiving the new `SectionInfo` — or `None` when the form finishes:
1031
+
1032
+
```python
1033
+
composition.on_section(lambdainfo: print(f"now: {info.name if info else'end'}"))
1034
+
```
1035
+
918
1036
#### Bar-cycle position - `p.bar_cycle(length)`
919
1037
920
1038
For bar-position logic, `p.bar_cycle(length)` replaces raw modulo arithmetic with readable musical vocabulary:
@@ -1503,6 +1621,112 @@ def keys (p):
1503
1621
1504
1622
**Fixed or breathing.**`comp.chords()` realises its phrase once, so it repeats identically. `p.progression()` realises on each rebuild, so omitting `seed=` lets the lengths breathe from cycle to cycle (still reproducible under the composition's own `seed=`); pass `seed=` for a fixed phrase. Velocity humanises per voice via the usual `velocity=(low, high)` convention.
1505
1623
1624
+
### Presets
1625
+
1626
+
Curated starting points — recognizable material you reach for by name and then bend.
1627
+
1628
+
**Genre progressions.**`progression("name")` is a named, key-relative loop:
1629
+
1630
+
```python
1631
+
verse = subsequence.progression("trance_epic") # i VI III VII — epic minor
1632
+
chorus = subsequence.progression("doo_wop") # I vi IV V
1633
+
blues = subsequence.progression("twelve_bar_blues")
1634
+
```
1635
+
1636
+
About two dozen ship — `pop_axis`, `doo_wop`, `andalusian`, `mixolydian_vamp`, `ii_v_i`, `pachelbel`, `rhythm_changes_a`, and more (an unknown name lists them all). They're ordinary progressions: spice them, `.cadence()` them, bind them to sections — and because they're key-relative, they follow whatever key they're bound to.
1637
+
1638
+
**World rhythms.**`Motif.preset("name", pitch=...)` is a timeline at its exact pulse positions (from Toussaint's *Geometry of Musical Rhythm*):
1639
+
1640
+
```python
1641
+
clave = subsequence.Motif.preset("son_clave_3_2", pitch="rim") # the 3-2 son clave
1642
+
bell = subsequence.Motif.preset("bembe", pitch="bell") # the 12-pulse standard pattern
1643
+
```
1644
+
1645
+
The clave family (son/rumba/bossa, 2-3 and 3-2), tresillo/cinquillo, the West-African bell timelines (shiko, gahu, soukous, bembé, fume-fume), and samba all ship. They're Motifs — transform, `&`-stack, and place them like any other.
1646
+
1647
+
**Role bundles.**`subsequence.roles` holds taste-default parameter bundles you splat and override (no role API — just data):
1648
+
1649
+
```python
1650
+
comp.phrase_part(channel=2, part="bass", **subsequence.roles.BASS) # low, locked to chord tones
`elaborate(depth)` approaches every chord by a backward cycle-of-fifths dominant chain (Steedman's jazz/blues grammar) — `depth` is how many fifth-steps back it reaches. Its flagship is the 12-bar blues elaborated more each chorus:
1657
+
1658
+
```python
1659
+
blues = subsequence.progression("twelve_bar_blues").resolve("C")
1660
+
chorus1 = blues # plain: C7 … F7 … G7 …
1661
+
chorus2 = blues.elaborate(1) # a V7 before each chord (G7 C7 …)
`depth=0` is the bare progression; the result is concrete (resolve or bind under a key first), and each chord keeps its decorations on its subdivided slot.
1667
+
1668
+
### Sieves and rhythm measures
1669
+
1670
+
For the experimental composer: `sieve()` is Xenakis's integer-set kernel — residual classes that serve as custom scales, non-octave pitch pools, rhythm grids, or bar-selection masks alike:
1671
+
1672
+
```python
1673
+
from subsequence import sieve, residual_class as rc
1674
+
1675
+
sieve([(12, 0), (12, 2), (12, 4), (12, 5), (12, 7), (12, 9), (12, 11)], hi=12) # the major scale
1676
+
sieve([(5, 0), (7, 1)], lo=60, hi=96) # a non-octave pitch pool
1677
+
((rc(2, 0) | rc(3, 0)) &~rc(4, 1)).evaluate(hi=24) # the full union/intersection/complement algebra
1678
+
```
1679
+
1680
+
And Toussaint's rhythm measures analyse a list of onset pulses: `rhythmic_evenness(onsets, grid)` (how close to maximally even — the Euclidean rhythms score ~1.0), `offbeatness(onsets, grid)` (onsets on intrinsically off-beat pulses), `syncopation(onsets, grid)` (weighted pull away from the strong beats).
1681
+
1682
+
### Cookbook
1683
+
1684
+
A few recipes that compose the primitives above — patterns, not features.
1685
+
1686
+
**Per-section tempo** — ease the BPM at each section boundary (`on_section` + the eased `target_bpm`):
0 commit comments