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