subsequence 0.6.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. subsequence/__init__.py +231 -0
  2. subsequence/__main__.py +24 -0
  3. subsequence/assets/web/index.html +345 -0
  4. subsequence/cadences.py +113 -0
  5. subsequence/chord_graphs/__init__.py +100 -0
  6. subsequence/chord_graphs/aeolian_minor.py +158 -0
  7. subsequence/chord_graphs/chromatic_mediant.py +113 -0
  8. subsequence/chord_graphs/diminished.py +97 -0
  9. subsequence/chord_graphs/dorian_minor.py +127 -0
  10. subsequence/chord_graphs/functional_major.py +102 -0
  11. subsequence/chord_graphs/hooktheory_major.py +88 -0
  12. subsequence/chord_graphs/lydian_major.py +130 -0
  13. subsequence/chord_graphs/mixolydian.py +98 -0
  14. subsequence/chord_graphs/phrygian_minor.py +76 -0
  15. subsequence/chord_graphs/suspended.py +109 -0
  16. subsequence/chord_graphs/turnaround_global.py +157 -0
  17. subsequence/chord_graphs/whole_tone.py +77 -0
  18. subsequence/chords.py +419 -0
  19. subsequence/composition.py +6099 -0
  20. subsequence/conductor.py +238 -0
  21. subsequence/constants/__init__.py +24 -0
  22. subsequence/constants/durations.py +37 -0
  23. subsequence/constants/instruments/__init__.py +13 -0
  24. subsequence/constants/instruments/gm_cc.py +46 -0
  25. subsequence/constants/instruments/gm_drums.py +53 -0
  26. subsequence/constants/instruments/gm_instruments.py +32 -0
  27. subsequence/constants/instruments/roland_tr8s.py +320 -0
  28. subsequence/constants/instruments/vermona_drm1_drums.py +87 -0
  29. subsequence/constants/midi_notes.py +29 -0
  30. subsequence/constants/pulses.py +17 -0
  31. subsequence/constants/velocity.py +22 -0
  32. subsequence/definitions.py +232 -0
  33. subsequence/display.py +617 -0
  34. subsequence/easing.py +347 -0
  35. subsequence/event_emitter.py +109 -0
  36. subsequence/form_state.py +665 -0
  37. subsequence/forms.py +257 -0
  38. subsequence/groove.py +323 -0
  39. subsequence/harmonic_rhythm.py +83 -0
  40. subsequence/harmonic_state.py +352 -0
  41. subsequence/harmony.py +197 -0
  42. subsequence/held_notes.py +91 -0
  43. subsequence/helpers/__init__.py +0 -0
  44. subsequence/helpers/network.py +58 -0
  45. subsequence/helpers/wing.py +430 -0
  46. subsequence/intervals.py +436 -0
  47. subsequence/keystroke.py +249 -0
  48. subsequence/link_clock.py +128 -0
  49. subsequence/live_client.py +187 -0
  50. subsequence/live_reloader.py +298 -0
  51. subsequence/live_server.py +161 -0
  52. subsequence/melodic_state.py +483 -0
  53. subsequence/midi.py +97 -0
  54. subsequence/midi_utils.py +329 -0
  55. subsequence/mini_notation.py +164 -0
  56. subsequence/motifs.py +2356 -0
  57. subsequence/osc.py +194 -0
  58. subsequence/pattern.py +363 -0
  59. subsequence/pattern_algorithmic.py +2010 -0
  60. subsequence/pattern_builder.py +2589 -0
  61. subsequence/pattern_midi.py +1208 -0
  62. subsequence/progressions.py +1913 -0
  63. subsequence/py.typed +0 -0
  64. subsequence/roles.py +63 -0
  65. subsequence/sequence_utils.py +3123 -0
  66. subsequence/sequencer.py +2086 -0
  67. subsequence/tuning.py +453 -0
  68. subsequence/voicings.py +151 -0
  69. subsequence/web_ui.py +337 -0
  70. subsequence/weighted_graph.py +156 -0
  71. subsequence-0.6.4.dist-info/METADATA +208 -0
  72. subsequence-0.6.4.dist-info/RECORD +78 -0
  73. subsequence-0.6.4.dist-info/WHEEL +5 -0
  74. subsequence-0.6.4.dist-info/entry_points.txt +2 -0
  75. subsequence-0.6.4.dist-info/licenses/LICENSE +661 -0
  76. subsequence-0.6.4.dist-info/scm_file_list.json +193 -0
  77. subsequence-0.6.4.dist-info/scm_version.json +8 -0
  78. subsequence-0.6.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,109 @@
1
+ import typing
2
+
3
+ import subsequence.chord_graphs
4
+ import subsequence.chords
5
+ import subsequence.weighted_graph
6
+
7
+
8
+ WEIGHT_MEDIUM = subsequence.chord_graphs.WEIGHT_MEDIUM
9
+ WEIGHT_COMMON = subsequence.chord_graphs.WEIGHT_COMMON
10
+ WEIGHT_WEAK = subsequence.chord_graphs.WEIGHT_WEAK
11
+
12
+ # Suspended-specific weight for same-root sus2 ↔ sus4 colour changes.
13
+ WEIGHT_COLOUR = 5
14
+
15
+
16
+ class Suspended (subsequence.chord_graphs.ChordGraph):
17
+
18
+ """Open harmony using suspended chords - no major or minor thirds.
19
+
20
+ All chords are sus2 or sus4, creating ambiguous, open textures.
21
+ Same-root sus2 ↔ sus4 movement (colour change without root movement)
22
+ is heavily weighted. Root movement favors fourths/fifths and whole
23
+ steps. A single minor tonic provides occasional grounding.
24
+
25
+ Good for ambient, post-rock, drone, and minimalist electronic music.
26
+ """
27
+
28
+ def build (self, key_name: str) -> typing.Tuple[subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord], subsequence.chords.Chord]:
29
+
30
+ """Build a suspended-chord graph with open, ambiguous harmony."""
31
+
32
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
33
+
34
+ # Core suspended chords.
35
+ tonic_sus2 = subsequence.chords.Chord(root_pc=key_pc, quality="sus2")
36
+ tonic_sus4 = subsequence.chords.Chord(root_pc=key_pc, quality="sus4")
37
+ sub_sus2 = subsequence.chords.Chord(root_pc=(key_pc + 5) % 12, quality="sus2")
38
+ sub_sus4 = subsequence.chords.Chord(root_pc=(key_pc + 5) % 12, quality="sus4")
39
+ dom_sus2 = subsequence.chords.Chord(root_pc=(key_pc + 7) % 12, quality="sus2")
40
+ dom_sus4 = subsequence.chords.Chord(root_pc=(key_pc + 7) % 12, quality="sus4")
41
+ subtonic_sus2 = subsequence.chords.Chord(root_pc=(key_pc + 10) % 12, quality="sus2")
42
+
43
+ # Minor tonic as the one "resolved" chord.
44
+ tonic_minor = subsequence.chords.Chord(root_pc=key_pc, quality="minor")
45
+
46
+ graph: subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord] = subsequence.weighted_graph.WeightedGraph()
47
+
48
+ # --- Same-root colour changes (sus2 ↔ sus4) ---
49
+ graph.add_transition(tonic_sus2, tonic_sus4, WEIGHT_COLOUR)
50
+ graph.add_transition(tonic_sus4, tonic_sus2, WEIGHT_COLOUR)
51
+ graph.add_transition(sub_sus2, sub_sus4, WEIGHT_COLOUR)
52
+ graph.add_transition(sub_sus4, sub_sus2, WEIGHT_COLOUR)
53
+ graph.add_transition(dom_sus2, dom_sus4, WEIGHT_COLOUR)
54
+ graph.add_transition(dom_sus4, dom_sus2, WEIGHT_COLOUR)
55
+
56
+ # --- Root motion by fourths/fifths ---
57
+ graph.add_transition(tonic_sus2, sub_sus2, WEIGHT_MEDIUM)
58
+ graph.add_transition(tonic_sus4, sub_sus4, WEIGHT_MEDIUM)
59
+ graph.add_transition(sub_sus2, tonic_sus2, WEIGHT_MEDIUM)
60
+ graph.add_transition(sub_sus4, tonic_sus4, WEIGHT_MEDIUM)
61
+
62
+ graph.add_transition(tonic_sus2, dom_sus2, WEIGHT_COMMON)
63
+ graph.add_transition(tonic_sus4, dom_sus4, WEIGHT_COMMON)
64
+ graph.add_transition(dom_sus2, tonic_sus2, WEIGHT_MEDIUM)
65
+ graph.add_transition(dom_sus4, tonic_sus4, WEIGHT_MEDIUM)
66
+
67
+ # --- bVII whole-step motion ---
68
+ graph.add_transition(tonic_sus2, subtonic_sus2, WEIGHT_COMMON)
69
+ graph.add_transition(subtonic_sus2, tonic_sus2, WEIGHT_COMMON)
70
+ graph.add_transition(subtonic_sus2, sub_sus2, WEIGHT_WEAK)
71
+ graph.add_transition(sub_sus2, subtonic_sus2, WEIGHT_WEAK)
72
+
73
+ # --- Minor tonic as resolution ---
74
+ graph.add_transition(tonic_sus4, tonic_minor, WEIGHT_WEAK)
75
+ graph.add_transition(dom_sus4, tonic_minor, WEIGHT_WEAK)
76
+ graph.add_transition(tonic_minor, tonic_sus2, WEIGHT_MEDIUM)
77
+ graph.add_transition(tonic_minor, tonic_sus4, WEIGHT_COMMON)
78
+
79
+ # --- Cross-root colour movement ---
80
+ graph.add_transition(sub_sus2, dom_sus4, WEIGHT_WEAK)
81
+ graph.add_transition(dom_sus2, sub_sus4, WEIGHT_WEAK)
82
+
83
+ return graph, tonic_sus2
84
+
85
+ def gravity_sets (self, key_name: str) -> typing.Tuple[typing.Set[subsequence.chords.Chord], typing.Set[subsequence.chords.Chord]]:
86
+
87
+ """Return suspended diatonic and functional chord sets."""
88
+
89
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
90
+
91
+ # All chords in the graph form the diatonic set.
92
+ diatonic: typing.Set[subsequence.chords.Chord] = set()
93
+
94
+ for interval in [0, 5, 7, 10]:
95
+ diatonic.add(subsequence.chords.Chord(root_pc=(key_pc + interval) % 12, quality="sus2"))
96
+
97
+ for interval in [0, 5, 7]:
98
+ diatonic.add(subsequence.chords.Chord(root_pc=(key_pc + interval) % 12, quality="sus4"))
99
+
100
+ diatonic.add(subsequence.chords.Chord(root_pc=key_pc, quality="minor"))
101
+
102
+ # Functional set: tonic sus2/sus4 and the minor resolution.
103
+ functional: typing.Set[subsequence.chords.Chord] = set()
104
+
105
+ functional.add(subsequence.chords.Chord(root_pc=key_pc, quality="sus2"))
106
+ functional.add(subsequence.chords.Chord(root_pc=key_pc, quality="sus4"))
107
+ functional.add(subsequence.chords.Chord(root_pc=key_pc, quality="minor"))
108
+
109
+ return diatonic, functional
@@ -0,0 +1,157 @@
1
+ import typing
2
+
3
+ import subsequence.chord_graphs
4
+ import subsequence.chords
5
+ import subsequence.intervals
6
+ import subsequence.weighted_graph
7
+
8
+
9
+ WEIGHT_STRONG = subsequence.chord_graphs.WEIGHT_STRONG
10
+ WEIGHT_MEDIUM = subsequence.chord_graphs.WEIGHT_MEDIUM
11
+ WEIGHT_COMMON = subsequence.chord_graphs.WEIGHT_COMMON
12
+ WEIGHT_DECEPTIVE = subsequence.chord_graphs.WEIGHT_DECEPTIVE
13
+
14
+
15
+ def _build_major_key_chords (key_pc: int) -> typing.Dict[str, subsequence.chords.Chord]:
16
+
17
+ """Return common functional chords for a major key root."""
18
+
19
+ chords = subsequence.chord_graphs.build_diatonic_chords(
20
+ subsequence.intervals.scale_pitch_classes(key_pc, "ionian"),
21
+ subsequence.intervals.IONIAN_QUALITIES
22
+ )
23
+
24
+ return {
25
+ "I": chords[0],
26
+ "ii": chords[1],
27
+ "iii": chords[2],
28
+ "IV": chords[3],
29
+ "V": chords[4],
30
+ "vi": chords[5],
31
+ "vii": chords[6],
32
+ }
33
+
34
+
35
+ def _add_turnaround_edges (
36
+ graph: subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord],
37
+ chords: typing.Dict[str, subsequence.chords.Chord],
38
+ include_dominant_7th: bool
39
+ ) -> None:
40
+
41
+ """Add ii-V-I style edges for a single major key."""
42
+
43
+ tonic = chords["I"]
44
+ supertonic = chords["ii"]
45
+ submediant = chords["vi"]
46
+ subdominant = chords["IV"]
47
+ dominant = chords["V"]
48
+ dominant_7th = subsequence.chords.Chord(root_pc=dominant.root_pc, quality="dominant_7th")
49
+
50
+ # Decision path: allow the dominant seventh to drive stronger resolutions.
51
+ dominant_target = dominant_7th if include_dominant_7th else dominant
52
+
53
+ graph.add_transition(tonic, submediant, WEIGHT_COMMON)
54
+ graph.add_transition(submediant, supertonic, WEIGHT_COMMON)
55
+ graph.add_transition(supertonic, dominant_target, WEIGHT_STRONG)
56
+ graph.add_transition(dominant_target, tonic, WEIGHT_STRONG)
57
+
58
+ graph.add_transition(tonic, subdominant, WEIGHT_MEDIUM)
59
+ graph.add_transition(tonic, dominant, WEIGHT_MEDIUM)
60
+
61
+ if include_dominant_7th:
62
+ # Decision path: deceptive resolution only applies to dominant sevenths.
63
+ graph.add_transition(dominant_7th, submediant, WEIGHT_DECEPTIVE)
64
+
65
+
66
+ def _add_minor_turnaround (
67
+ graph: subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord],
68
+ key_pc: int,
69
+ minor_turnaround_weight: float,
70
+ include_dominant_7th: bool
71
+ ) -> None:
72
+
73
+ """Add a minor ii-V-I turnaround using a weight multiplier."""
74
+
75
+ if minor_turnaround_weight <= 0:
76
+ # Decision path: weight of zero disables minor turnarounds entirely.
77
+ return
78
+
79
+ scale_pcs = subsequence.intervals.scale_pitch_classes(key_pc, "aeolian")
80
+ supertonic_pc = scale_pcs[1]
81
+ dominant_pc = scale_pcs[4]
82
+ tonic_pc = scale_pcs[0]
83
+
84
+ supertonic = subsequence.chords.Chord(root_pc=supertonic_pc, quality="half_diminished_7th")
85
+ dominant_7th = subsequence.chords.Chord(root_pc=dominant_pc, quality="dominant_7th")
86
+ tonic_minor = subsequence.chords.Chord(root_pc=tonic_pc, quality="minor")
87
+ tonic_major = subsequence.chords.Chord(root_pc=tonic_pc, quality="major")
88
+
89
+ minor_weight_strong = max(1, int(round(WEIGHT_STRONG * minor_turnaround_weight)))
90
+ minor_weight_entry = max(1, int(round(WEIGHT_MEDIUM * minor_turnaround_weight)))
91
+
92
+ # Entry edges — without these the iio7 chords are unreachable and the
93
+ # minor turnaround can never start. The tonic minor aliases existing
94
+ # diatonic nodes (e.g. C minor is the ii of Bb major), and the parallel
95
+ # major tonic is each key's own I, so both hooks connect the minor
96
+ # turnaround to the core ii-V-I graph.
97
+ graph.add_transition(tonic_minor, supertonic, minor_weight_strong)
98
+ graph.add_transition(tonic_major, supertonic, minor_weight_entry)
99
+
100
+ if include_dominant_7th:
101
+ graph.add_transition(supertonic, dominant_7th, minor_weight_strong)
102
+ graph.add_transition(dominant_7th, tonic_minor, minor_weight_strong)
103
+
104
+ else:
105
+ # Decision path: if dominant sevenths are disabled, resolve via the triad.
106
+ dominant = subsequence.chords.Chord(root_pc=dominant_pc, quality="major")
107
+ graph.add_transition(supertonic, dominant, minor_weight_strong)
108
+ graph.add_transition(dominant, tonic_minor, minor_weight_strong)
109
+
110
+
111
+ class TurnaroundModulation (subsequence.chord_graphs.ChordGraph):
112
+
113
+ """Global ii-V-I turnaround graph enabling modulation between all keys."""
114
+
115
+ def __init__ (self, include_dominant_7th: bool = True, minor_turnaround_weight: float = 0.0) -> None:
116
+
117
+ """Configure dominant sevenths and minor turnaround strength."""
118
+
119
+ if minor_turnaround_weight < 0 or minor_turnaround_weight > 1:
120
+ raise ValueError("Minor turnaround weight must be between 0 and 1")
121
+
122
+ self.include_dominant_7th = include_dominant_7th
123
+ self.minor_turnaround_weight = minor_turnaround_weight
124
+
125
+ def build (self, key_name: str) -> typing.Tuple[subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord], subsequence.chords.Chord]:
126
+
127
+ """Build the global turnaround graph for all 12 keys."""
128
+
129
+ tonic_pc = subsequence.chord_graphs.validate_key_name(key_name)
130
+
131
+ graph: subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord] = subsequence.weighted_graph.WeightedGraph()
132
+
133
+ for key_pc in range(12):
134
+ chords = _build_major_key_chords(key_pc)
135
+ _add_turnaround_edges(graph, chords, self.include_dominant_7th)
136
+ _add_minor_turnaround(graph, key_pc, self.minor_turnaround_weight, self.include_dominant_7th)
137
+ tonic = subsequence.chords.Chord(root_pc=tonic_pc, quality="major")
138
+
139
+ return graph, tonic
140
+
141
+ def gravity_sets (self, key_name: str) -> typing.Tuple[typing.Set[subsequence.chords.Chord], typing.Set[subsequence.chords.Chord]]:
142
+
143
+ """Return major-key diatonic and functional chord sets."""
144
+
145
+ return subsequence.chord_graphs._major_key_gravity_sets(key_name)
146
+
147
+
148
+ def build_graph (key_name: str, include_dominant_7th: bool = True, minor_turnaround_weight: float = 0.0) -> typing.Tuple[subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord], subsequence.chords.Chord]:
149
+
150
+ """Build a global turnaround graph and return it with the chosen key tonic."""
151
+
152
+ graph_obj = TurnaroundModulation(
153
+ include_dominant_7th = include_dominant_7th,
154
+ minor_turnaround_weight = minor_turnaround_weight
155
+ )
156
+
157
+ return graph_obj.build(key_name)
@@ -0,0 +1,77 @@
1
+ import typing
2
+
3
+ import subsequence.chord_graphs
4
+ import subsequence.chords
5
+ import subsequence.weighted_graph
6
+
7
+
8
+ WEIGHT_STEP = 4
9
+ WEIGHT_THIRD = 3
10
+ WEIGHT_LEAP = 2
11
+
12
+
13
+ class WholeTone (subsequence.chord_graphs.ChordGraph):
14
+
15
+ """Symmetrical whole-tone chord graph.
16
+
17
+ Scale: 0, 2, 4, 6, 8, 10 (six equally-spaced pitches).
18
+ All chords are augmented triads. Every position is equivalent -
19
+ there is no functional hierarchy, only proximity.
20
+
21
+ Step-wise motion (whole step) is weighted highest, thirds next,
22
+ larger leaps lowest. The result is a drifting, dreamlike quality
23
+ with no sense of resolution. Useful for IDM, ambient, and
24
+ experimental electronic music.
25
+ """
26
+
27
+ def build (self, key_name: str) -> typing.Tuple[subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord], subsequence.chords.Chord]:
28
+
29
+ """Build a fully-connected whole-tone graph."""
30
+
31
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
32
+
33
+ # Six augmented triads, each a whole step apart.
34
+ roots = [(key_pc + i) % 12 for i in range(0, 12, 2)]
35
+ chords = [subsequence.chords.Chord(root_pc=r, quality="augmented") for r in roots]
36
+
37
+ graph: subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord] = subsequence.weighted_graph.WeightedGraph()
38
+
39
+ # Fully connected: every chord links to every other chord.
40
+ for i, source in enumerate(chords):
41
+ for j, target in enumerate(chords):
42
+ if i == j:
43
+ continue
44
+
45
+ # Distance in whole-tone steps (1-3, since the scale is symmetric).
46
+ step_distance = min(abs(i - j), 6 - abs(i - j))
47
+
48
+ if step_distance == 1:
49
+ weight = WEIGHT_STEP
50
+ elif step_distance == 2:
51
+ weight = WEIGHT_THIRD
52
+ else:
53
+ weight = WEIGHT_LEAP
54
+
55
+ graph.add_transition(source, target, weight)
56
+
57
+ tonic = chords[0]
58
+
59
+ return graph, tonic
60
+
61
+ def gravity_sets (self, key_name: str) -> typing.Tuple[typing.Set[subsequence.chords.Chord], typing.Set[subsequence.chords.Chord]]:
62
+
63
+ """Return whole-tone diatonic and functional chord sets."""
64
+
65
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
66
+
67
+ roots = [(key_pc + i) % 12 for i in range(0, 12, 2)]
68
+ diatonic: typing.Set[subsequence.chords.Chord] = set()
69
+
70
+ for r in roots:
71
+ diatonic.add(subsequence.chords.Chord(root_pc=r, quality="augmented"))
72
+
73
+ # Minimal functional pull - only the tonic augmented chord.
74
+ functional: typing.Set[subsequence.chords.Chord] = set()
75
+ functional.add(subsequence.chords.Chord(root_pc=key_pc, quality="augmented"))
76
+
77
+ return diatonic, functional