Chord Families
This commit is contained in:
+27
-20
@@ -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']
|
||||
|
||||
Reference in New Issue
Block a user