Files
2026-10-05 19:44:33 -05:00

85 lines
3.3 KiB
Python

from __future__ import annotations
import fractions
import generator.model
def active_chord(measure: dict, onset: fractions.Fraction, previous: dict | None) -> dict | None:
active = previous
for chord in measure['harmonies']:
if generator.model.fraction(chord['onset']) > onset:
break
active = chord
return active if active and active['kind'] != 'none' else None
def carried_chords(measures: list[dict], initial: dict | None = None) -> list[dict | None]:
result = []
for measure in measures:
result.append(initial)
if measure['harmonies']:
initial = measure['harmonies'][-1]
return result
def same_chord(left: dict | None, right: dict | None) -> bool:
return left is not None and right is not None and all(left.get(k) == right.get(k) for k in ('root', 'kind', 'degrees', 'bass', 'inversion'))
def normalize_beams(events: list[dict]) -> None:
for level in sorted({beam['level'] for event in events for beam in event.get('beams', [])}):
groups, group = [], []
for event in events:
beam = next((b for b in event.get('beams', []) if b['level'] == level), None)
if not beam or beam['type'] in ('begin', 'forward hook', 'backward hook'):
groups.append(group)
group = []
if beam:
group.append((event, beam))
if beam['type'] in ('end', 'forward hook', 'backward hook'):
groups.append(group)
group = []
groups.append(group)
for group in groups:
for index, (event, beam) in enumerate(group):
if len(group) > 1:
beam['type'] = 'begin' if index == 0 else 'end' if index == len(group) - 1 else 'continue'
elif level > 1 and any(b['level'] == level - 1 for b in event['beams']):
beam['type'] = 'backward hook' if beam['type'] in ('end', 'backward hook') else 'forward hook'
else:
event['beams'].remove(beam)
for event in events:
if not event.get('beams'):
event.pop('beams', None)
def written_durations(length: fractions.Fraction):
while length:
written = generator.model.written_for(length)
duration = length
if 'ratio' in written and length.denominator & (length.denominator - 1) == 0:
duration = next((size for size in generator.model.TYPE_LENGTHS.values() if size < length), length)
written = generator.model.written_for(duration)
yield duration, written
length -= duration
def connect_continuations(document: dict) -> bool:
previous = {}
changed = False
for measure in document['staves'][0]['measures']:
if measure.get('section_label'):
previous.clear()
for voice in measure['voices']:
for note in voice['notes']:
continuation = note.pop('_rhythm_continuation', False)
source_note = previous.get(voice['id'])
if continuation and source_note and source_note['pitches'] and note['pitches']:
old = {(p['tablature']['string'], p['tablature']['fret']): p for p in source_note['pitches']}
for tone in note['pitches']:
source = old[(tone['tablature']['string'], tone['tablature']['fret'])]
tone['tie_from'] = source['id']
source['tie_to'] = tone['id']
changed = True
elif continuation:
note.update(kind='rest', pitches=[])
note.pop('beams', None)
changed = True
previous[voice['id']] = note
return changed