Source code for subsequence.chord_graphs
"""
Chord graph builders for different harmonic transition models.
"""
import abc
import typing
import subsequence.chords
import subsequence.intervals
import subsequence.weighted_graph
# Shared transition weights used by all chord graph builders.
WEIGHT_STRONG = 6
WEIGHT_MEDIUM = 4
WEIGHT_COMMON = 3
WEIGHT_DECEPTIVE = 2
WEIGHT_WEAK = 1
[docs]
class ChordGraph (abc.ABC):
"""Abstract base for chord transition graphs."""
@abc.abstractmethod
[docs]
def build (self, key_name: str) -> typing.Tuple[subsequence.weighted_graph.WeightedGraph[subsequence.chords.Chord], subsequence.chords.Chord]:
"""Build the weighted graph and return it with the tonic chord."""
...
@abc.abstractmethod
[docs]
def gravity_sets (self, key_name: str) -> typing.Tuple[typing.Set[subsequence.chords.Chord], typing.Set[subsequence.chords.Chord]]:
"""Return (diatonic_set, functional_set) for key gravity weighting."""
...
[docs]
def validate_key_name (key_name: str) -> int:
"""Validate a key name and return its pitch class.
Raises ValueError if the key name is not recognised.
Parameters:
key_name: Note name (e.g., ``"C"``, ``"F#"``, ``"Bb"``)
Returns:
Pitch class integer (0-11)
"""
return subsequence.chords.key_name_to_pc(key_name)
[docs]
def build_diatonic_chords (scale_pcs: typing.List[int], degree_qualities: typing.List[str]) -> typing.List[subsequence.chords.Chord]:
"""Build chords for each scale degree.
Parameters:
scale_pcs: Pitch class for each degree (e.g., from ``scale_pitch_classes(key_pc, mode)``)
degree_qualities: Chord quality for each degree (e.g., ``["major", "minor", ...]``)
Returns:
List of Chord objects, one per scale degree
"""
chords: typing.List[subsequence.chords.Chord] = []
for root_pc, quality in zip(scale_pcs, degree_qualities):
chords.append(subsequence.chords.Chord(root_pc=root_pc, quality=quality))
return chords
def _major_key_gravity_sets (key_name: str) -> typing.Tuple[typing.Set[subsequence.chords.Chord], typing.Set[subsequence.chords.Chord]]:
"""Return diatonic and functional chord sets for a major key."""
key_pc = validate_key_name(key_name)
scale_pcs = subsequence.intervals.scale_pitch_classes(key_pc, "ionian")
chords = build_diatonic_chords(scale_pcs, subsequence.intervals.IONIAN_QUALITIES)
diatonic: typing.Set[subsequence.chords.Chord] = set(chords)
# Functional set: I, ii, V, V7.
function_intervals = [0, 2, 7]
function_qualities = ["major", "minor", "major"]
function_chords: typing.Set[subsequence.chords.Chord] = set()
for interval, quality in zip(function_intervals, function_qualities):
root_pc = (key_pc + interval) % 12
function_chords.add(subsequence.chords.Chord(root_pc=root_pc, quality=quality))
dominant_7th = subsequence.chords.Chord(root_pc=(key_pc + 7) % 12, quality="dominant_7th")
function_chords.add(dominant_7th)
diatonic.add(dominant_7th)
return diatonic, function_chords