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,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
|