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,127 @@
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_WEAK = subsequence.chord_graphs.WEIGHT_WEAK
13
+
14
+ # Dorian-specific weight for the signature i ↔ IV plagal motion.
15
+ WEIGHT_DORIAN = 5
16
+
17
+
18
+ class DorianMinor (subsequence.chord_graphs.ChordGraph):
19
+
20
+ """Dorian-flavored minor harmony with a natural sixth degree.
21
+
22
+ The natural 6th (instead of the aeolian b6) gives minor harmony a
23
+ warmer, more hopeful quality. The IV chord (major subdominant in a
24
+ minor key) is the defining Dorian sound. No harmonic-minor dominant V
25
+ is used - the graph stays purely modal.
26
+
27
+ Good for lo-fi, neo-soul, chill electronic, jazz, and funk.
28
+ """
29
+
30
+ def __init__ (self, include_dominant_7th: bool = False) -> None:
31
+
32
+ """Configure whether to include dominant seventh chords.
33
+
34
+ Defaults to False because Dorian is modal - dominant 7ths
35
+ introduce functional tonal pull that weakens the modal feel.
36
+ """
37
+
38
+ self.include_dominant_7th = include_dominant_7th
39
+
40
+ def build (self, key_name: str) -> typing.Tuple[subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord], subsequence.chords.Chord]:
41
+
42
+ """Build a Dorian minor-key graph for the given key."""
43
+
44
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
45
+
46
+ # Dorian scale: 0, 2, 3, 5, 7, 9, 10
47
+ # Degrees: i, ii, bIII, IV, v, vi°, bVII
48
+ tonic = subsequence.chords.Chord(root_pc=key_pc, quality="minor")
49
+ supertonic = subsequence.chords.Chord(root_pc=(key_pc + 2) % 12, quality="minor")
50
+ mediant = subsequence.chords.Chord(root_pc=(key_pc + 3) % 12, quality="major")
51
+ subdominant = subsequence.chords.Chord(root_pc=(key_pc + 5) % 12, quality="major")
52
+ natural_dominant = subsequence.chords.Chord(root_pc=(key_pc + 7) % 12, quality="minor")
53
+ submediant_dim = subsequence.chords.Chord(root_pc=(key_pc + 9) % 12, quality="diminished")
54
+ subtonic = subsequence.chords.Chord(root_pc=(key_pc + 10) % 12, quality="major")
55
+
56
+ graph: subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord] = subsequence.weighted_graph.WeightedGraph()
57
+
58
+ # --- The Dorian signature: i ↔ IV ---
59
+ graph.add_transition(tonic, subdominant, WEIGHT_DORIAN)
60
+ graph.add_transition(subdominant, tonic, WEIGHT_DORIAN)
61
+
62
+ # --- Tonic departures ---
63
+ graph.add_transition(tonic, supertonic, WEIGHT_COMMON)
64
+ graph.add_transition(tonic, subtonic, WEIGHT_COMMON)
65
+ graph.add_transition(tonic, natural_dominant, WEIGHT_WEAK)
66
+ graph.add_transition(tonic, mediant, WEIGHT_WEAK)
67
+
68
+ # --- Supertonic departures ---
69
+ graph.add_transition(supertonic, natural_dominant, WEIGHT_MEDIUM)
70
+ graph.add_transition(supertonic, subdominant, WEIGHT_COMMON)
71
+ graph.add_transition(supertonic, tonic, WEIGHT_WEAK)
72
+
73
+ # --- Mediant (bIII) departures ---
74
+ graph.add_transition(mediant, subdominant, WEIGHT_COMMON)
75
+ graph.add_transition(mediant, subtonic, WEIGHT_COMMON)
76
+ graph.add_transition(mediant, tonic, WEIGHT_WEAK)
77
+
78
+ # --- Subdominant departures (beyond → tonic) ---
79
+ graph.add_transition(subdominant, natural_dominant, WEIGHT_COMMON)
80
+ graph.add_transition(subdominant, subtonic, WEIGHT_WEAK)
81
+ graph.add_transition(subdominant, submediant_dim, WEIGHT_WEAK) # IV → vi° (passing dim; vi° resolves to v / i)
82
+
83
+ # --- Natural dominant (v) departures ---
84
+ graph.add_transition(natural_dominant, tonic, WEIGHT_MEDIUM)
85
+ graph.add_transition(natural_dominant, subdominant, WEIGHT_COMMON)
86
+ graph.add_transition(natural_dominant, mediant, WEIGHT_WEAK)
87
+
88
+ # --- Subtonic (bVII) departures ---
89
+ graph.add_transition(subtonic, tonic, WEIGHT_MEDIUM)
90
+ graph.add_transition(subtonic, subdominant, WEIGHT_COMMON)
91
+ graph.add_transition(subtonic, supertonic, WEIGHT_WEAK)
92
+
93
+ # --- Submediant dim (vi°) connectors ---
94
+ graph.add_transition(submediant_dim, natural_dominant, WEIGHT_MEDIUM)
95
+ graph.add_transition(submediant_dim, tonic, WEIGHT_WEAK)
96
+
97
+ if self.include_dominant_7th:
98
+ # Harmonic minor dominant - optional tonal color.
99
+ dominant_7th = subsequence.chords.Chord(root_pc=(key_pc + 7) % 12, quality="dominant_7th")
100
+
101
+ graph.add_transition(natural_dominant, dominant_7th, WEIGHT_WEAK)
102
+ graph.add_transition(dominant_7th, tonic, WEIGHT_STRONG)
103
+
104
+ return graph, tonic
105
+
106
+ def gravity_sets (self, key_name: str) -> typing.Tuple[typing.Set[subsequence.chords.Chord], typing.Set[subsequence.chords.Chord]]:
107
+
108
+ """Return Dorian diatonic and functional chord sets."""
109
+
110
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
111
+
112
+ diatonic: typing.Set[subsequence.chords.Chord] = set(
113
+ subsequence.chord_graphs.build_diatonic_chords(
114
+ subsequence.intervals.scale_pitch_classes(key_pc, "dorian"),
115
+ subsequence.intervals.DORIAN_QUALITIES
116
+ )
117
+ )
118
+
119
+ # Functional set: i, IV (Dorian signature), v, bVII.
120
+ functional: typing.Set[subsequence.chords.Chord] = set()
121
+
122
+ functional.add(subsequence.chords.Chord(root_pc=key_pc, quality="minor"))
123
+ functional.add(subsequence.chords.Chord(root_pc=(key_pc + 5) % 12, quality="major"))
124
+ functional.add(subsequence.chords.Chord(root_pc=(key_pc + 7) % 12, quality="minor"))
125
+ functional.add(subsequence.chords.Chord(root_pc=(key_pc + 10) % 12, quality="major"))
126
+
127
+ return diatonic, functional
@@ -0,0 +1,102 @@
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_COMMON = subsequence.chord_graphs.WEIGHT_COMMON
11
+ WEIGHT_DECEPTIVE = subsequence.chord_graphs.WEIGHT_DECEPTIVE
12
+ WEIGHT_WEAK = subsequence.chord_graphs.WEIGHT_WEAK
13
+
14
+
15
+ class DiatonicMajor (subsequence.chord_graphs.ChordGraph):
16
+
17
+ """Single-key functional major harmony graph."""
18
+
19
+ def __init__ (self, include_dominant_7th: bool = True) -> None:
20
+
21
+ """Configure whether to include dominant seventh chords."""
22
+
23
+ self.include_dominant_7th = include_dominant_7th
24
+
25
+ def build (self, key_name: str) -> typing.Tuple[subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord], subsequence.chords.Chord]:
26
+
27
+ """Build the graph for a given major key."""
28
+
29
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
30
+
31
+ chords = subsequence.chord_graphs.build_diatonic_chords(
32
+ subsequence.intervals.scale_pitch_classes(key_pc, "ionian"),
33
+ subsequence.intervals.IONIAN_QUALITIES
34
+ )
35
+
36
+ tonic = chords[0]
37
+ supertonic = chords[1]
38
+ mediant = chords[2]
39
+ subdominant = chords[3]
40
+ dominant = chords[4]
41
+ submediant = chords[5]
42
+ leading = chords[6]
43
+
44
+ graph: subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord] = subsequence.weighted_graph.WeightedGraph()
45
+
46
+ graph.add_transition(tonic, subdominant, WEIGHT_COMMON)
47
+ graph.add_transition(tonic, dominant, WEIGHT_COMMON)
48
+ graph.add_transition(tonic, submediant, WEIGHT_COMMON)
49
+ graph.add_transition(tonic, supertonic, WEIGHT_WEAK)
50
+
51
+ graph.add_transition(supertonic, dominant, WEIGHT_STRONG)
52
+
53
+ graph.add_transition(mediant, submediant, WEIGHT_COMMON)
54
+ graph.add_transition(mediant, subdominant, WEIGHT_WEAK)
55
+
56
+ graph.add_transition(subdominant, dominant, WEIGHT_STRONG, label="open")
57
+ graph.add_transition(subdominant, supertonic, WEIGHT_COMMON)
58
+ # The plagal close (IV → I, the "Amen") — outside strict functional
59
+ # motion, so weak; it also makes the "soft" cadence formula walkable.
60
+ graph.add_transition(subdominant, tonic, WEIGHT_WEAK, label="soft")
61
+
62
+ graph.add_transition(dominant, tonic, WEIGHT_STRONG, label="strong")
63
+ graph.add_transition(dominant, submediant, WEIGHT_DECEPTIVE, label="fakeout")
64
+
65
+ graph.add_transition(submediant, supertonic, WEIGHT_COMMON)
66
+ graph.add_transition(submediant, subdominant, WEIGHT_COMMON)
67
+ graph.add_transition(submediant, dominant, WEIGHT_WEAK)
68
+
69
+ graph.add_transition(leading, tonic, WEIGHT_STRONG)
70
+
71
+ # Make the two remaining diatonic triads reachable as occasional colour —
72
+ # they were defined with resolving edges but nothing led into them:
73
+ # vi → iii — the descending-thirds sequence (I–V–vi–iii–IV).
74
+ # IV → vii° — predominant into the leading-tone triad (a dominant
75
+ # substitute), which then resolves via its vii° → I edge.
76
+ graph.add_transition(submediant, mediant, WEIGHT_WEAK)
77
+ graph.add_transition(subdominant, leading, WEIGHT_WEAK)
78
+
79
+ if self.include_dominant_7th:
80
+ # Decision path: optional dominant seventh color for stronger cadences.
81
+ dominant_7th = subsequence.chords.Chord(root_pc=dominant.root_pc, quality="dominant_7th")
82
+
83
+ graph.add_transition(dominant, dominant_7th, WEIGHT_WEAK)
84
+ graph.add_transition(dominant_7th, tonic, WEIGHT_STRONG, label="strong")
85
+ graph.add_transition(dominant_7th, submediant, WEIGHT_DECEPTIVE, label="fakeout")
86
+
87
+ return graph, tonic
88
+
89
+ def gravity_sets (self, key_name: str) -> typing.Tuple[typing.Set[subsequence.chords.Chord], typing.Set[subsequence.chords.Chord]]:
90
+
91
+ """Return major-key diatonic and functional chord sets."""
92
+
93
+ return subsequence.chord_graphs._major_key_gravity_sets(key_name)
94
+
95
+
96
+ 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]:
97
+
98
+ """Build a functional major-key graph and return it with the tonic chord."""
99
+
100
+ graph_obj = DiatonicMajor(include_dominant_7th=include_dominant_7th)
101
+
102
+ return graph_obj.build(key_name)
@@ -0,0 +1,88 @@
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
+ # Corpus-tendency transition table for a major key — relative frequencies in
10
+ # the spirit of Hooktheory's Theorytab statistics (pop/rock songwriting), a
11
+ # baked table, NOT a textbook functional-flow rulebook. Each row is a
12
+ # per-source distribution: the weights compare targets of ONE chord. Diatonic
13
+ # triads only (I ii iii IV V vi vii°), indexed 1–7 by scale degree.
14
+ #
15
+ # The well-known empirical tendencies it encodes: I leans to V/IV/vi; V resolves
16
+ # strongly to I and deceptively to vi; IV→I and IV→V; vi→IV/V/ii; ii→V; iii is
17
+ # rare and usually heads to vi or IV; vii°→I.
18
+ _DEGREE_TRANSITIONS: typing.Dict[int, typing.List[typing.Tuple[int, int]]] = {
19
+ 1: [(5, 8), (4, 8), (6, 7), (2, 4), (3, 2), (7, 1)],
20
+ 2: [(5, 9), (4, 4), (1, 3), (6, 2), (7, 1), (3, 1)],
21
+ 3: [(6, 7), (4, 7), (1, 3), (2, 2), (5, 2)],
22
+ 4: [(1, 8), (5, 7), (6, 4), (2, 4), (3, 1)],
23
+ 5: [(1, 9), (6, 7), (4, 6), (2, 2), (3, 1)],
24
+ 6: [(4, 7), (5, 6), (2, 5), (1, 4), (3, 2)],
25
+ 7: [(1, 9), (6, 3), (5, 2), (3, 1)],
26
+ }
27
+
28
+
29
+ class HooktheoryMajor (subsequence.chord_graphs.ChordGraph):
30
+
31
+ """Major-key graph weighted by pop/rock corpus tendencies.
32
+
33
+ The same seven diatonic triads as :class:`DiatonicMajor`, but the
34
+ transition weights follow real songwriting frequencies (Hooktheory-informed)
35
+ rather than textbook function — so the walk gravitates to the four-chord
36
+ loops that dominate popular music (I–V–vi–IV and its rotations) and uses
37
+ iii sparingly. Optionally colours the dominant with a seventh.
38
+ """
39
+
40
+ def __init__ (self, include_dominant_7th: bool = True) -> None:
41
+
42
+ """Configure whether to add a dominant-seventh colour on V."""
43
+
44
+ self.include_dominant_7th = include_dominant_7th
45
+
46
+ def build (self, key_name: str) -> typing.Tuple[subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord], subsequence.chords.Chord]:
47
+
48
+ """Build the corpus-weighted graph for a given major key."""
49
+
50
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
51
+
52
+ chords = subsequence.chord_graphs.build_diatonic_chords(
53
+ subsequence.intervals.scale_pitch_classes(key_pc, "ionian"),
54
+ subsequence.intervals.IONIAN_QUALITIES
55
+ )
56
+
57
+ graph: subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord] = subsequence.weighted_graph.WeightedGraph()
58
+
59
+ for source_degree, targets in _DEGREE_TRANSITIONS.items():
60
+ source = chords[source_degree - 1]
61
+ for target_degree, weight in targets:
62
+ graph.add_transition(source, chords[target_degree - 1], weight)
63
+
64
+ if self.include_dominant_7th:
65
+ # A V7 colour the corpus reaches from V and that resolves home or
66
+ # deceptively, mirroring the V row's strongest moves.
67
+ dominant = chords[4]
68
+ dominant_7th = subsequence.chords.Chord(root_pc=dominant.root_pc, quality="dominant_7th")
69
+
70
+ graph.add_transition(dominant, dominant_7th, subsequence.chord_graphs.WEIGHT_MEDIUM)
71
+ graph.add_transition(dominant_7th, chords[0], 9) # V7 → I
72
+ graph.add_transition(dominant_7th, chords[5], 7) # V7 → vi (deceptive)
73
+ graph.add_transition(dominant_7th, chords[3], 6) # V7 → IV
74
+
75
+ return graph, chords[0]
76
+
77
+ def gravity_sets (self, key_name: str) -> typing.Tuple[typing.Set[subsequence.chords.Chord], typing.Set[subsequence.chords.Chord]]:
78
+
79
+ """Return major-key diatonic and functional chord sets."""
80
+
81
+ return subsequence.chord_graphs._major_key_gravity_sets(key_name)
82
+
83
+
84
+ def build_graph (key_name: str, include_dominant_7th: bool = True) -> typing.Tuple[subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord], subsequence.chords.Chord]:
85
+
86
+ """Build a corpus-weighted major-key graph and return it with the tonic chord."""
87
+
88
+ return HooktheoryMajor(include_dominant_7th=include_dominant_7th).build(key_name)
@@ -0,0 +1,130 @@
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
+ WEIGHT_WEAK = subsequence.chord_graphs.WEIGHT_WEAK
14
+
15
+ # Lydian-specific weight for the signature I ↔ II shimmer.
16
+ WEIGHT_LYDIAN = 5
17
+
18
+
19
+ class LydianMajor (subsequence.chord_graphs.ChordGraph):
20
+
21
+ """Lydian-flavored major harmony with a raised fourth degree.
22
+
23
+ The raised 4th (#4) gives a bright, floating quality. The II chord
24
+ (major, a whole step above tonic) is the defining Lydian sound -
25
+ the I ↔ II "shimmer" is the strongest motion in the graph. The
26
+ natural IV chord is absent entirely, keeping the harmony purely modal.
27
+
28
+ Good for ambient, cinematic, and progressive electronic music.
29
+ """
30
+
31
+ def __init__ (self, include_dominant_7th: bool = True) -> None:
32
+
33
+ """Configure whether to include dominant seventh chords."""
34
+
35
+ self.include_dominant_7th = include_dominant_7th
36
+
37
+ def build (self, key_name: str) -> typing.Tuple[subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord], subsequence.chords.Chord]:
38
+
39
+ """Build a Lydian major-key graph for the given key."""
40
+
41
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
42
+
43
+ # Lydian scale: 0, 2, 4, 6, 7, 9, 11
44
+ # Degrees: I, II, iii, #iv°, V, vi, vii
45
+ tonic = subsequence.chords.Chord(root_pc=key_pc, quality="major")
46
+ supertonic = subsequence.chords.Chord(root_pc=(key_pc + 2) % 12, quality="major")
47
+ mediant = subsequence.chords.Chord(root_pc=(key_pc + 4) % 12, quality="minor")
48
+ sharp_four_dim = subsequence.chords.Chord(root_pc=(key_pc + 6) % 12, quality="diminished")
49
+ dominant = subsequence.chords.Chord(root_pc=(key_pc + 7) % 12, quality="major")
50
+ submediant = subsequence.chords.Chord(root_pc=(key_pc + 9) % 12, quality="minor")
51
+ leading = subsequence.chords.Chord(root_pc=(key_pc + 11) % 12, quality="minor")
52
+
53
+ graph: subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord] = subsequence.weighted_graph.WeightedGraph()
54
+
55
+ # --- The Lydian shimmer: I ↔ II ---
56
+ graph.add_transition(tonic, supertonic, WEIGHT_LYDIAN)
57
+ graph.add_transition(supertonic, tonic, WEIGHT_LYDIAN)
58
+
59
+ # --- Tonic departures ---
60
+ graph.add_transition(tonic, dominant, WEIGHT_COMMON)
61
+ graph.add_transition(tonic, submediant, WEIGHT_COMMON)
62
+ graph.add_transition(tonic, mediant, WEIGHT_WEAK)
63
+
64
+ # --- Supertonic departures (beyond → tonic) ---
65
+ graph.add_transition(supertonic, dominant, WEIGHT_COMMON)
66
+ graph.add_transition(supertonic, mediant, WEIGHT_WEAK)
67
+
68
+ # --- Mediant departures ---
69
+ graph.add_transition(mediant, submediant, WEIGHT_COMMON)
70
+ graph.add_transition(mediant, tonic, WEIGHT_WEAK)
71
+
72
+ # --- Dominant departures ---
73
+ graph.add_transition(dominant, tonic, WEIGHT_STRONG)
74
+ graph.add_transition(dominant, submediant, WEIGHT_DECEPTIVE)
75
+
76
+ # --- Submediant departures ---
77
+ graph.add_transition(submediant, supertonic, WEIGHT_COMMON)
78
+ graph.add_transition(submediant, dominant, WEIGHT_COMMON)
79
+ graph.add_transition(submediant, tonic, WEIGHT_WEAK)
80
+
81
+ # --- Leading tone (vii) departures ---
82
+ graph.add_transition(leading, tonic, WEIGHT_STRONG)
83
+ graph.add_transition(leading, supertonic, WEIGHT_WEAK)
84
+
85
+ # --- #iv° connectors ---
86
+ graph.add_transition(sharp_four_dim, dominant, WEIGHT_MEDIUM)
87
+ graph.add_transition(sharp_four_dim, tonic, WEIGHT_WEAK)
88
+
89
+ # Make the two diatonic chords that had only outgoing edges reachable, as
90
+ # occasional colour:
91
+ # I → #iv° — passing into the signature raised-4 dim (→ V / I).
92
+ # V → vii — into the minor leading-tone triad, which resolves vii → I.
93
+ graph.add_transition(tonic, sharp_four_dim, WEIGHT_WEAK)
94
+ graph.add_transition(dominant, leading, WEIGHT_WEAK)
95
+
96
+ if self.include_dominant_7th:
97
+ dominant_7th = subsequence.chords.Chord(root_pc=dominant.root_pc, quality="dominant_7th")
98
+
99
+ graph.add_transition(dominant, dominant_7th, WEIGHT_WEAK)
100
+ graph.add_transition(dominant_7th, tonic, WEIGHT_STRONG)
101
+ graph.add_transition(dominant_7th, submediant, WEIGHT_DECEPTIVE)
102
+
103
+ return graph, tonic
104
+
105
+ def gravity_sets (self, key_name: str) -> typing.Tuple[typing.Set[subsequence.chords.Chord], typing.Set[subsequence.chords.Chord]]:
106
+
107
+ """Return Lydian diatonic and functional chord sets."""
108
+
109
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
110
+
111
+ diatonic: typing.Set[subsequence.chords.Chord] = set(
112
+ subsequence.chord_graphs.build_diatonic_chords(
113
+ subsequence.intervals.scale_pitch_classes(key_pc, "lydian"),
114
+ subsequence.intervals.LYDIAN_QUALITIES
115
+ )
116
+ )
117
+
118
+ # Add dominant 7th to diatonic set.
119
+ dominant_7th = subsequence.chords.Chord(root_pc=(key_pc + 7) % 12, quality="dominant_7th")
120
+ diatonic.add(dominant_7th)
121
+
122
+ # Functional set: I, II (Lydian signature), V.
123
+ functional: typing.Set[subsequence.chords.Chord] = set()
124
+
125
+ functional.add(subsequence.chords.Chord(root_pc=key_pc, quality="major"))
126
+ functional.add(subsequence.chords.Chord(root_pc=(key_pc + 2) % 12, quality="major"))
127
+ functional.add(subsequence.chords.Chord(root_pc=(key_pc + 7) % 12, quality="major"))
128
+ functional.add(dominant_7th)
129
+
130
+ return diatonic, functional
@@ -0,0 +1,98 @@
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_WEAK = subsequence.chord_graphs.WEIGHT_WEAK
13
+ WEIGHT_MIXOLYDIAN = 5
14
+
15
+
16
+ class Mixolydian (subsequence.chord_graphs.ChordGraph):
17
+
18
+ """Major-mode chord graph with a flat seventh degree.
19
+
20
+ Scale: 1, 2, 3, 4, 5, 6, b7.
21
+ Chords: I (major), ii (minor), iii° (diminished), IV (major),
22
+ v (minor), vi (minor), bVII (major).
23
+
24
+ The I ↔ bVII shuttle is the defining colour. The minor v avoids
25
+ dominant function, keeping progressions open and unresolved.
26
+ Common in EDM, synthwave, and rock-influenced electronic music.
27
+ """
28
+
29
+ def build (self, key_name: str) -> typing.Tuple[subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord], subsequence.chords.Chord]:
30
+
31
+ """Build a Mixolydian mode graph."""
32
+
33
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
34
+
35
+ tonic = subsequence.chords.Chord(root_pc=key_pc, quality="major")
36
+ supertonic = subsequence.chords.Chord(root_pc=(key_pc + 2) % 12, quality="minor")
37
+ mediant = subsequence.chords.Chord(root_pc=(key_pc + 4) % 12, quality="diminished")
38
+ subdominant = subsequence.chords.Chord(root_pc=(key_pc + 5) % 12, quality="major")
39
+ minor_dominant = subsequence.chords.Chord(root_pc=(key_pc + 7) % 12, quality="minor")
40
+ submediant = subsequence.chords.Chord(root_pc=(key_pc + 9) % 12, quality="minor")
41
+ flat_seven = subsequence.chords.Chord(root_pc=(key_pc + 10) % 12, quality="major")
42
+
43
+ graph: subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord] = subsequence.weighted_graph.WeightedGraph()
44
+
45
+ # --- I ↔ bVII: the Mixolydian shuttle ---
46
+ graph.add_transition(tonic, flat_seven, WEIGHT_MIXOLYDIAN)
47
+ graph.add_transition(flat_seven, tonic, WEIGHT_MIXOLYDIAN)
48
+
49
+ # --- Plagal motion ---
50
+ graph.add_transition(subdominant, tonic, WEIGHT_STRONG)
51
+ graph.add_transition(tonic, subdominant, WEIGHT_MEDIUM)
52
+
53
+ # --- bVII ↔ IV: common EDM chain ---
54
+ graph.add_transition(flat_seven, subdominant, WEIGHT_MEDIUM)
55
+ graph.add_transition(subdominant, flat_seven, WEIGHT_COMMON)
56
+
57
+ # --- Minor dominant ---
58
+ graph.add_transition(minor_dominant, tonic, WEIGHT_MEDIUM)
59
+ graph.add_transition(tonic, minor_dominant, WEIGHT_COMMON)
60
+
61
+ # --- Supertonic ---
62
+ graph.add_transition(supertonic, minor_dominant, WEIGHT_COMMON)
63
+ graph.add_transition(supertonic, subdominant, WEIGHT_COMMON)
64
+ graph.add_transition(tonic, supertonic, WEIGHT_WEAK)
65
+
66
+ # --- Submediant ---
67
+ graph.add_transition(submediant, flat_seven, WEIGHT_COMMON)
68
+ graph.add_transition(submediant, subdominant, WEIGHT_WEAK)
69
+ graph.add_transition(tonic, submediant, WEIGHT_WEAK)
70
+ graph.add_transition(minor_dominant, submediant, WEIGHT_WEAK)
71
+
72
+ # --- Mediant (diminished - rare, colour) ---
73
+ graph.add_transition(mediant, subdominant, WEIGHT_WEAK)
74
+ graph.add_transition(mediant, tonic, WEIGHT_WEAK)
75
+ graph.add_transition(supertonic, mediant, WEIGHT_WEAK)
76
+
77
+ return graph, tonic
78
+
79
+ def gravity_sets (self, key_name: str) -> typing.Tuple[typing.Set[subsequence.chords.Chord], typing.Set[subsequence.chords.Chord]]:
80
+
81
+ """Return Mixolydian diatonic and functional chord sets."""
82
+
83
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
84
+
85
+ diatonic: typing.Set[subsequence.chords.Chord] = set(
86
+ subsequence.chord_graphs.build_diatonic_chords(
87
+ subsequence.intervals.scale_pitch_classes(key_pc, "mixolydian"),
88
+ subsequence.intervals.MIXOLYDIAN_QUALITIES
89
+ )
90
+ )
91
+
92
+ # Functional: I, IV, bVII (the primary colour chords).
93
+ functional: typing.Set[subsequence.chords.Chord] = set()
94
+ functional.add(subsequence.chords.Chord(root_pc=key_pc, quality="major"))
95
+ functional.add(subsequence.chords.Chord(root_pc=(key_pc + 5) % 12, quality="major"))
96
+ functional.add(subsequence.chords.Chord(root_pc=(key_pc + 10) % 12, quality="major"))
97
+
98
+ return diatonic, functional
@@ -0,0 +1,76 @@
1
+ import typing
2
+
3
+ import subsequence.chord_graphs
4
+ import subsequence.chords
5
+ import subsequence.weighted_graph
6
+
7
+
8
+ WEIGHT_STRONG = subsequence.chord_graphs.WEIGHT_STRONG
9
+ WEIGHT_MEDIUM = subsequence.chord_graphs.WEIGHT_MEDIUM
10
+ WEIGHT_WEAK = subsequence.chord_graphs.WEIGHT_WEAK
11
+
12
+
13
+ class PhrygianMinor (subsequence.chord_graphs.ChordGraph):
14
+
15
+ """Minor chord graph utilizing Phrygian and Plagal motion.
16
+
17
+ Consists of four minor chords: i, bii, iv, v.
18
+ The Phrygian bII (major) is replaced here by a minor bii for a darker,
19
+ more modal sound often found in techno and minimal repetition.
20
+ The Phrygian cadence (bii -> i) is the primary resolution.
21
+ """
22
+
23
+ def build (self, key_name: str) -> typing.Tuple[subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord], subsequence.chords.Chord]:
24
+
25
+ """Build a minimal all-minor Phrygian graph."""
26
+
27
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
28
+
29
+ # Four chords, all minor.
30
+ tonic = subsequence.chords.Chord(root_pc=key_pc, quality="minor")
31
+ flat_two = subsequence.chords.Chord(root_pc=(key_pc + 1) % 12, quality="minor")
32
+ subdominant = subsequence.chords.Chord(root_pc=(key_pc + 5) % 12, quality="minor")
33
+ natural_dominant = subsequence.chords.Chord(root_pc=(key_pc + 7) % 12, quality="minor")
34
+
35
+ graph: subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord] = subsequence.weighted_graph.WeightedGraph()
36
+
37
+ # --- Tonic departures ---
38
+ graph.add_transition(tonic, flat_two, WEIGHT_MEDIUM)
39
+ graph.add_transition(tonic, subdominant, WEIGHT_MEDIUM)
40
+ graph.add_transition(tonic, natural_dominant, WEIGHT_WEAK)
41
+
42
+ # --- Phrygian cadence: bii -> i (strongest resolution) ---
43
+ graph.add_transition(flat_two, tonic, WEIGHT_STRONG)
44
+ graph.add_transition(flat_two, natural_dominant, WEIGHT_WEAK)
45
+
46
+ # --- Plagal motion ---
47
+ graph.add_transition(subdominant, tonic, WEIGHT_STRONG)
48
+ graph.add_transition(subdominant, natural_dominant, WEIGHT_MEDIUM)
49
+
50
+ # --- Natural dominant departures ---
51
+ graph.add_transition(natural_dominant, tonic, WEIGHT_MEDIUM)
52
+ graph.add_transition(natural_dominant, flat_two, WEIGHT_WEAK)
53
+
54
+ return graph, tonic
55
+
56
+ def gravity_sets (self, key_name: str) -> typing.Tuple[typing.Set[subsequence.chords.Chord], typing.Set[subsequence.chords.Chord]]:
57
+
58
+ """Return all-minor diatonic and functional chord sets."""
59
+
60
+ key_pc = subsequence.chord_graphs.validate_key_name(key_name)
61
+
62
+ # All four chords are diatonic to this palette.
63
+ diatonic: typing.Set[subsequence.chords.Chord] = set()
64
+
65
+ for interval in [0, 1, 5, 7]:
66
+ root_pc = (key_pc + interval) % 12
67
+ diatonic.add(subsequence.chords.Chord(root_pc=root_pc, quality="minor"))
68
+
69
+ # Functional set: tonic, bii (Phrygian), subdominant.
70
+ functional: typing.Set[subsequence.chords.Chord] = set()
71
+
72
+ for interval in [0, 1, 5]:
73
+ root_pc = (key_pc + interval) % 12
74
+ functional.add(subsequence.chords.Chord(root_pc=root_pc, quality="minor"))
75
+
76
+ return diatonic, functional