@@ -9,8 +9,8 @@ import generator.pitch
|
||||
|
||||
NAMES = {
|
||||
'major': ('Ionian', 'Dorian', 'Phrygian', 'Lydian', 'Mixolydian', 'Aeolian', 'Locrian'),
|
||||
'melodic-minor': ('Melodic Minor', 'Dorian b2', 'Lydian Augmented', 'Lydian Dominant', 'Mixolydian b6', 'Locrian natural 2', 'Altered'),
|
||||
'harmonic-minor': ('Harmonic Minor', 'Locrian natural 6', 'Ionian #5', 'Dorian #4', 'Phrygian Dominant', 'Lydian #2', 'Ultra Locrian'),
|
||||
'melodic_minor': ('Melodic Minor', 'Dorian b2', 'Lydian Augmented', 'Lydian Dominant', 'Mixolydian b6', 'Locrian natural 2', 'Altered'),
|
||||
'harmonic_minor': ('Harmonic Minor', 'Locrian natural 6', 'Ionian #5', 'Dorian #4', 'Phrygian Dominant', 'Lydian #2', 'Ultra Locrian'),
|
||||
}
|
||||
ROOTS = ('C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B')
|
||||
|
||||
@@ -76,17 +76,17 @@ def read_pattern(path: pathlib.Path, family: str, mode: int) -> Pattern:
|
||||
return Pattern(family, mode, tuple(int(n) for n in intervals[:-1]))
|
||||
|
||||
def load_library(root: pathlib.Path) -> Library:
|
||||
expected = {root / f'{family}-scale-modes' / f'{mode:04}.xml' for family in NAMES for mode in range(1, 8)}
|
||||
expected = {root / f'{family}_scale_modes' / f'{mode:04}.xml' for family in NAMES for mode in range(1, 8)}
|
||||
actual = set(root.rglob('*.xml'))
|
||||
if actual != expected:
|
||||
missing, unexpected = sorted(expected - actual), sorted(actual - expected)
|
||||
raise ValueError(f'{root}: expected all 21 scale patterns; missing: {missing}; unexpected: {unexpected}')
|
||||
patterns = tuple(read_pattern(root / f'{family}-scale-modes' / f'{mode:04}.xml', family, mode) for family in NAMES for mode in range(1, 8))
|
||||
patterns = tuple(read_pattern(root / f'{family}_scale_modes' / f'{mode:04}.xml', family, mode) for family in NAMES for mode in range(1, 8))
|
||||
for family in NAMES:
|
||||
modes = [pattern for pattern in patterns if pattern.family == family]
|
||||
parent = modes[0].intervals
|
||||
for index, pattern in enumerate(modes):
|
||||
rotation = tuple((parent[(index + n) % 7] - parent[index]) % 12 for n in range(7))
|
||||
if pattern.intervals != rotation:
|
||||
raise ValueError(f'{root / (family + "-scale-modes") / f"{pattern.mode:04}.xml"}: pattern is not mode {pattern.mode} of its family parent')
|
||||
raise ValueError(f'{root / (family + "_scale_modes") / f"{pattern.mode:04}.xml"}: pattern is not mode {pattern.mode} of its family parent')
|
||||
return Library(patterns)
|
||||
|
||||
@@ -22,7 +22,7 @@ def annotate(document: dict, solos: list[generator.scales.timeline.Solo], librar
|
||||
if previous is not None and previous['label'] == label and generator.model.fraction(previous['end']) == choice.span.start:
|
||||
previous['end'] = generator.model.rational(choice.span.end)
|
||||
else:
|
||||
previous = {'id': f'scale-{len(spans) + 1}', 'start': generator.model.rational(choice.span.start), 'end': generator.model.rational(choice.span.end), 'label': label, 'matched': choice.scale is not None}
|
||||
previous = {'id': f'scale_{len(spans) + 1}', 'start': generator.model.rational(choice.span.start), 'end': generator.model.rational(choice.span.end), 'label': label, 'matched': choice.scale is not None}
|
||||
spans.append(previous)
|
||||
if choice.scale is None:
|
||||
print(f'{source}, {staff["name"]}, source measure {choice.span.source_measure}, beat {choice.span.beat}: {choice.reason} Chord: {generator.harmony.symbol(choice.span.chord)}', flush=True)
|
||||
|
||||
@@ -27,22 +27,22 @@ def destination(chord: dict, following: dict | None, library: generator.scales.l
|
||||
if following and dominant(chord) and (root_number(chord) - root_number(following)) % 12 == 7:
|
||||
third = generator.harmony.degrees(following).get(3)
|
||||
if third in (-1, 0):
|
||||
return library.scale('harmonic-minor' if third == -1 else 'major', 1, generator.model.pitch_name(following['root']))
|
||||
return library.scale('harmonic_minor' if third == -1 else 'major', 1, generator.model.pitch_name(following['root']))
|
||||
return None
|
||||
|
||||
def preference(chord: dict, following: dict | None, after: dict | None, home: generator.scales.library.Scale, library: generator.scales.library.Library) -> tuple[generator.scales.library.Scale, str]:
|
||||
if any(root_number(item).denominator != 1 for item in (chord, following, after) if item):
|
||||
return home, 'non-semitone harmony cannot establish a destination in the permitted catalog'
|
||||
if target := destination(chord, following, library):
|
||||
return target, 'dominant approaching ' + generator.model.pitch_name(following['root']) + (' minor' if target.pattern.family == 'harmonic-minor' else ' major')
|
||||
return target, 'dominant approaching ' + generator.model.pitch_name(following['root']) + (' minor' if target.pattern.family == 'harmonic_minor' else ' major')
|
||||
degrees = generator.harmony.degrees(chord)
|
||||
if following and degrees.get(3) == -1 and degrees.get(7) == -1 and dominant(following) and (root_number(following) - root_number(chord)) % 12 == 5:
|
||||
tonic = generator.pitch.shift_pitch({**chord['root'], 'octave': 4}, -1, -2)
|
||||
target = destination(following, after, library) or library.scale('harmonic-minor' if degrees.get(5) == -1 else 'major', 1, generator.model.pitch_name(tonic))
|
||||
target = destination(following, after, library) or library.scale('harmonic_minor' if degrees.get(5) == -1 else 'major', 1, generator.model.pitch_name(tonic))
|
||||
if set(generator.harmony.pitch_classes(chord)) | set(generator.harmony.pitch_classes(following)) <= target.pitches:
|
||||
return target, 'shared ' + ('minor ii-V' if target.pattern.family == 'harmonic-minor' else 'major ii-V') + ' destination ' + target.root
|
||||
return target, 'shared ' + ('minor ii-V' if target.pattern.family == 'harmonic_minor' else 'major ii-V') + ' destination ' + target.root
|
||||
if following and degrees.get(3) == -1 and degrees.get(5) == -1 and degrees.get(7) == -2 and (root_number(following) - root_number(chord)) % 12 == 1:
|
||||
return library.scale('harmonic-minor', 1, generator.model.pitch_name(following['root'])), 'leading-tone diminished resolution to ' + generator.model.pitch_name(following['root'])
|
||||
return library.scale('harmonic_minor', 1, generator.model.pitch_name(following['root'])), 'leading-tone diminished resolution to ' + generator.model.pitch_name(following['root'])
|
||||
return home, 'declared tonal center ' + home.label()
|
||||
|
||||
def following_indices(timeline: tuple[generator.scales.timeline.ChordSpan, ...]) -> list[int | None]:
|
||||
|
||||
Reference in New Issue
Block a user