Formatting
ci / checks (push) Successful in 42m54s

This commit is contained in:
khodowany
2026-09-15 00:06:02 -05:00
parent 44e100985a
commit fe42d811fa
81 changed files with 4349 additions and 3340 deletions
+17 -27
View File
@@ -6,22 +6,18 @@ from dataclasses import dataclass
from generator import harmony
from generator.model import LETTERS, NATURAL, key_from_fifths, key_label, parse_pitch_name, pitch_number
@dataclass(frozen=True)
class KeyProfile:
name: str
root: int
fifths: int
KEY_PROFILES = tuple(KeyProfile(name, root, fifths) for root, (name, fifths) in enumerate((('C',0),('Db',-5),('D',2),('Eb',-3),('E',4),('F',-1),('Gb',-6),('G',1),('Ab',-4),('A',3),('Bb',-2),('B',5))))
KEY_PROFILES = tuple(KeyProfile(name, root, fifths) for root, (name, fifths) in enumerate((('C', 0), ('Db', -5), ('D', 2), ('Eb', -3), ('E', 4), ('F', -1), ('Gb', -6), ('G', 1), ('Ab', -4), ('A', 3), ('Bb', -2), ('B', 5))))
TARGET_KEYS = tuple(p.name for p in KEY_PROFILES)
def mod12(value: int) -> int:
return value % 12
def find_key(name: str) -> KeyProfile:
tonic = parse_pitch_name(name)
for fifths in range(-14, 15):
@@ -30,11 +26,9 @@ def find_key(name: str) -> KeyProfile:
return KeyProfile(name, (NATURAL[tonic['letter']] + tonic['alter']) % 12, fifths)
raise ValueError(f'unsupported key signature: {name}')
def signed_interval(source: KeyProfile, target: KeyProfile) -> int:
value = (target.root-source.root) % 12
return value-12 if value > 6 else value
value = (target.root - source.root) % 12
return value - 12 if value > 6 else value
def interval_between(source: dict, target: dict) -> tuple[int, int]:
semitones = (NATURAL[target['letter']] + target['alter'] - NATURAL[source['letter']] - source['alter']) % 12
@@ -45,7 +39,6 @@ def interval_between(source: dict, target: dict) -> tuple[int, int]:
steps -= 7
return steps, semitones
def shift_pitch(pitch: dict, steps: int, semitones: int) -> dict:
output = copy.deepcopy(pitch)
original_octave = pitch.get('octave', 4)
@@ -53,32 +46,30 @@ def shift_pitch(pitch: dict, steps: int, semitones: int) -> dict:
octave, index = divmod(diatonic, 7)
output['letter'] = LETTERS[index]
output['octave'] = octave
output['alter'] = pitch_number({**pitch, 'octave':original_octave}) + semitones - (12 * (octave+1) + NATURAL[output['letter']])
output['alter'] = pitch_number({**pitch, 'octave': original_octave}) + semitones - (12 * (octave + 1) + NATURAL[output['letter']])
if 'octave' not in pitch:
output.pop('octave')
if 'accidental_display' in output:
output['accidental_display']['name'] = {-3:'triple-flat',-2:'flat-flat',-1:'flat',0:'natural',1:'sharp',2:'double-sharp',3:'triple-sharp'}.get(output['alter'], 'compound')
output['accidental_display']['name'] = {-3: 'triple-flat', -2: 'flat-flat', -1: 'flat', 0: 'natural', 1: 'sharp', 2: 'double-sharp', 3: 'triple-sharp'}.get(output['alter'], 'compound')
return output
def shift_key(key: dict, steps: int, semitones: int) -> dict:
tonic = shift_pitch(key['tonic'], steps, semitones) if key.get('tonic') else None
alterations = [0] * 7
for index, letter in enumerate(LETTERS):
p = shift_pitch({'letter':letter, 'alter':key['alterations'][index]}, steps, semitones)
p = shift_pitch({'letter': letter, 'alter': key['alterations'][index]}, steps, semitones)
alterations[LETTERS.index(p['letter'])] = p['alter']
result = {**copy.deepcopy(key), 'tonic':tonic, 'alterations':alterations}
result = {**copy.deepcopy(key), 'tonic': tonic, 'alterations': alterations}
if not key.get('custom') and tonic:
for fifths in range(-14,15):
for fifths in range(-14, 15):
candidate = key_from_fifths(fifths, key['mode'])
if candidate['tonic']==tonic and candidate['alterations']==alterations:
if candidate['tonic'] == tonic and candidate['alterations'] == alterations:
result['fifths'] = fifths
break
else:
result['custom'] = True
return result
def transpose_document(document: dict, target_key_name: str) -> dict:
output = copy.deepcopy(document)
metadata = output['metadata']
@@ -99,7 +90,7 @@ def transpose_document(document: dict, target_key_name: str) -> dict:
if 'key' in change:
change['key'] = shift_key(change['key'], steps, semitones)
for h in measure.get('harmonies', []):
for field in ('root','bass'):
for field in ('root', 'bass'):
if field in h:
h[field] = shift_pitch(h[field], steps, semitones)
h['symbol'] = harmony.symbol(h)
@@ -108,11 +99,10 @@ def transpose_document(document: dict, target_key_name: str) -> dict:
note['pitches'] = [shift_pitch(p, steps, semitones) for p in note.get('pitches', [])]
return output
def concert_document(document: dict) -> dict:
"""Normalize MusicXML instrument transposition before concert-pitch arranging."""
output = copy.deepcopy(document)
zero = {'diatonic':0, 'chromatic':0, 'octave_change':0}
zero = {'diatonic': 0, 'chromatic': 0, 'octave_change': 0}
for staff in output['staves']:
for measure in staff['measures']:
initial = measure['transpose']
@@ -124,21 +114,21 @@ def concert_document(document: dict) -> dict:
for change in changes:
if fraction(change['onset']) <= onset and 'transpose' in change:
state = change['transpose']
return state['diatonic']+7*state['octave_change'], state['chromatic']+12*state['octave_change']
return state['diatonic'] + 7 * state['octave_change'], state['chromatic'] + 12 * state['octave_change']
from generator.model import fraction
measure['key'] = shift_key(measure['key'], *interval(0))
measure['key_signature'] = key_label(measure['key'])
for n in measure['voices'][0]['notes']:
n['pitches'] = [shift_pitch(p,*interval(fraction(n['onset']))) for p in n['pitches']]
n['pitches'] = [shift_pitch(p, *interval(fraction(n['onset']))) for p in n['pitches']]
for h in measure['harmonies']:
for field in ('root','bass'):
for field in ('root', 'bass'):
if field in h:
h[field] = shift_pitch(h[field],*interval(fraction(h['onset'])))
h[field] = shift_pitch(h[field], *interval(fraction(h['onset'])))
h['symbol'] = harmony.symbol(h)
for change in measure.get('attributes',[]):
for change in measure.get('attributes', []):
if 'key' in change:
change['key'] = shift_key(change['key'],*interval(fraction(change['onset'])))
change['key'] = shift_key(change['key'], *interval(fraction(change['onset'])))
if 'transpose' in change:
change['transpose'] = copy.deepcopy(zero)
measure['transpose'] = copy.deepcopy(zero)