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.
- subsequence/__init__.py +231 -0
- subsequence/__main__.py +24 -0
- subsequence/assets/web/index.html +345 -0
- subsequence/cadences.py +113 -0
- subsequence/chord_graphs/__init__.py +100 -0
- subsequence/chord_graphs/aeolian_minor.py +158 -0
- subsequence/chord_graphs/chromatic_mediant.py +113 -0
- subsequence/chord_graphs/diminished.py +97 -0
- subsequence/chord_graphs/dorian_minor.py +127 -0
- subsequence/chord_graphs/functional_major.py +102 -0
- subsequence/chord_graphs/hooktheory_major.py +88 -0
- subsequence/chord_graphs/lydian_major.py +130 -0
- subsequence/chord_graphs/mixolydian.py +98 -0
- subsequence/chord_graphs/phrygian_minor.py +76 -0
- subsequence/chord_graphs/suspended.py +109 -0
- subsequence/chord_graphs/turnaround_global.py +157 -0
- subsequence/chord_graphs/whole_tone.py +77 -0
- subsequence/chords.py +419 -0
- subsequence/composition.py +6099 -0
- subsequence/conductor.py +238 -0
- subsequence/constants/__init__.py +24 -0
- subsequence/constants/durations.py +37 -0
- subsequence/constants/instruments/__init__.py +13 -0
- subsequence/constants/instruments/gm_cc.py +46 -0
- subsequence/constants/instruments/gm_drums.py +53 -0
- subsequence/constants/instruments/gm_instruments.py +32 -0
- subsequence/constants/instruments/roland_tr8s.py +320 -0
- subsequence/constants/instruments/vermona_drm1_drums.py +87 -0
- subsequence/constants/midi_notes.py +29 -0
- subsequence/constants/pulses.py +17 -0
- subsequence/constants/velocity.py +22 -0
- subsequence/definitions.py +232 -0
- subsequence/display.py +617 -0
- subsequence/easing.py +347 -0
- subsequence/event_emitter.py +109 -0
- subsequence/form_state.py +665 -0
- subsequence/forms.py +257 -0
- subsequence/groove.py +323 -0
- subsequence/harmonic_rhythm.py +83 -0
- subsequence/harmonic_state.py +352 -0
- subsequence/harmony.py +197 -0
- subsequence/held_notes.py +91 -0
- subsequence/helpers/__init__.py +0 -0
- subsequence/helpers/network.py +58 -0
- subsequence/helpers/wing.py +430 -0
- subsequence/intervals.py +436 -0
- subsequence/keystroke.py +249 -0
- subsequence/link_clock.py +128 -0
- subsequence/live_client.py +187 -0
- subsequence/live_reloader.py +298 -0
- subsequence/live_server.py +161 -0
- subsequence/melodic_state.py +483 -0
- subsequence/midi.py +97 -0
- subsequence/midi_utils.py +329 -0
- subsequence/mini_notation.py +164 -0
- subsequence/motifs.py +2356 -0
- subsequence/osc.py +194 -0
- subsequence/pattern.py +363 -0
- subsequence/pattern_algorithmic.py +2010 -0
- subsequence/pattern_builder.py +2589 -0
- subsequence/pattern_midi.py +1208 -0
- subsequence/progressions.py +1913 -0
- subsequence/py.typed +0 -0
- subsequence/roles.py +63 -0
- subsequence/sequence_utils.py +3123 -0
- subsequence/sequencer.py +2086 -0
- subsequence/tuning.py +453 -0
- subsequence/voicings.py +151 -0
- subsequence/web_ui.py +337 -0
- subsequence/weighted_graph.py +156 -0
- subsequence-0.6.4.dist-info/METADATA +208 -0
- subsequence-0.6.4.dist-info/RECORD +78 -0
- subsequence-0.6.4.dist-info/WHEEL +5 -0
- subsequence-0.6.4.dist-info/entry_points.txt +2 -0
- subsequence-0.6.4.dist-info/licenses/LICENSE +661 -0
- subsequence-0.6.4.dist-info/scm_file_list.json +193 -0
- subsequence-0.6.4.dist-info/scm_version.json +8 -0
- 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
|