Chord Families

This commit is contained in:
khodowany
2026-10-06 02:57:37 -05:00
parent df83cae38c
commit e71385dad1
16 changed files with 590 additions and 126 deletions
+27 -20
View File
@@ -76,27 +76,34 @@ def prepare(document: dict) -> None:
generator.accompaniment.normalize_beams(notes)
def combine(document: dict) -> None:
"""Write prepared bass/upper attacks as one voice with shared stems and rests."""
"""One rhythmic voice; split held pitches into ties rather than release them."""
originals = {p['id']: p for m in document['staves'][0]['measures'] for v in m['voices'] for n in v['notes'] for p in n['pitches']}
edges = [(p['id'], p['tie_to']) for p in originals.values() if p.get('tie_to')]
fragments = {}
for measure in measures(document):
length = generator.model.fraction(measure['duration'])
attacks = sorted((n for v in measure['voices'] for n in v['notes'] if n['pitches']), key=lambda n: generator.model.fraction(n['onset']))
notes, cursor = [], fractions.Fraction()
def rest(end):
nonlocal cursor
for duration, written in generator.accompaniment.written_durations(end - cursor):
notes.append(generator.model.make_event(f'{measure["id"]}_acoustic_{len(notes) + 1}', cursor, duration, 'rest', written=written) | {'voice': 'voice_arranged'})
cursor += duration
for start, group in itertools.groupby(attacks, key=lambda n: generator.model.fraction(n['onset'])):
rest(start)
group = list(group)
pitches = sorted((p for n in group for p in n['pitches']), key=lambda p: (generator.model.pitch_number(p), p['tablature']['string']))
pitches = [{k: v for k, v in p.items() if k != 'stem'} for p in pitches]
duration = generator.model.fraction(group[0]['duration'])
notes.append(generator.model.make_event(f'{measure["id"]}_acoustic_{len(notes) + 1}', start, duration, 'note', pitches, group[0]['written']) | {'voice': 'voice_arranged'})
cursor = start + duration
rest(length)
if not attacks and length == generator.model.meter_length(measure['meter']):
sounding = [(generator.model.fraction(n['onset']), generator.model.fraction(n['onset']) + generator.model.fraction(p['duration']), p)
for v in measure['voices'] for n in v['notes'] for p in n['pitches']]
boundaries = sorted({fractions.Fraction(), length, *(t for a, b, _ in sounding for t in (a, b))})
notes = []
for a, b in zip(boundaries, boundaries[1:]):
active = sorted((p for start, end, p in sounding if start <= a < end), key=lambda p: (generator.model.pitch_number(p), p['tablature']['string']))
for duration, written in generator.accompaniment.written_durations(b - a):
pitches = [{k: v for k, v in p.items() if k not in ('tie_from', 'tie_to', 'stem')} for p in active]
note = generator.model.make_event(f'{measure["id"]}_acoustic_{len(notes) + 1}', a, duration, 'note' if pitches else 'rest', pitches, written)
note['voice'] = 'voice_arranged'
for old, new in zip(active, note['pitches']):
fragments.setdefault(old['id'], []).append(new)
notes.append(note)
a += duration
if not sounding and length == generator.model.meter_length(measure['meter']):
notes = [generator.model.make_event(f'{measure["id"]}_acoustic_1', fractions.Fraction(), length, 'rest', written={'type': 'whole', 'dots': 0}) | {'voice': 'voice_arranged', 'measure_rest': True}]
measure['voices'] = [{'id': 'voice_arranged', 'notes': notes}]
for pieces in fragments.values():
for a, b in zip(pieces, pieces[1:]):
a['tie_to'], b['tie_from'] = b['id'], a['id']
for start, end in edges:
if start in fragments or end in fragments:
a = fragments[start][-1] if start in fragments else originals[start]
b = fragments[end][0] if end in fragments else originals[end]
a['tie_to'], b['tie_from'] = b['id'], a['id']