85 lines
3.3 KiB
Python
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
|