Rhythm Selection & Cleanup
ci / checks (push) Successful in 1h16m10s

This commit is contained in:
khodowany
2026-09-26 02:50:22 -05:00
parent 4d6348d354
commit 9fd5abd075
91 changed files with 4692 additions and 2484 deletions
+17 -17
View File
@@ -1,21 +1,21 @@
from __future__ import annotations
from fractions import Fraction
import fractions
from generator.model import beat_lengths, fraction, rational
import generator.model
def pulse(measure: dict, onset: Fraction) -> tuple[Fraction | None, bool]:
start, meter = Fraction(), measure['meter']
def pulse(measure: dict, onset: fractions.Fraction) -> tuple[fractions.Fraction | None, bool]:
start, meter = fractions.Fraction(), measure['meter']
for change in measure.get('attributes', []):
if fraction(change['onset']) <= onset and 'meter' in change:
start, meter = fraction(change['onset']), change['meter']
beats = beat_lengths(meter)
if generator.model.fraction(change['onset']) <= onset and 'meter' in change:
start, meter = generator.model.fraction(change['onset']), change['meter']
beats = generator.model.beat_lengths(meter)
if not beats:
return None, False
# Pickups occupy the final beats of their meter.
phase = onset - start
if measure.get('implicit') and not start:
phase += max(Fraction(), sum(beats) - fraction(measure['duration']))
phase += max(fractions.Fraction(), sum(beats) - generator.model.fraction(measure['duration']))
phase %= sum(beats)
for beat in beats:
if phase < beat:
@@ -23,7 +23,7 @@ def pulse(measure: dict, onset: Fraction) -> tuple[Fraction | None, bool]:
phase -= beat
raise ValueError('invalid meter pulse')
def syllable(duration: Fraction, beat: Fraction | None, onbeat: bool, final: bool, previous: str) -> str:
def syllable(duration: fractions.Fraction, beat: fractions.Fraction | None, onbeat: bool, final: bool, previous: str) -> str:
if duration >= (beat if beat is not None else 1):
return 'doo'
if final:
@@ -33,7 +33,7 @@ def syllable(duration: Fraction, beat: Fraction | None, onbeat: bool, final: boo
def add_scat(document: dict) -> None:
events, pitches = [], {}
absolute = Fraction()
absolute = fractions.Fraction()
boundaries = set()
for measure in document['staves'][0]['measures']:
if measure.get('section_label'):
@@ -42,34 +42,34 @@ def add_scat(document: dict) -> None:
for note in voice['notes']:
for field in ('lyrics', 'lyric', 'lyric_notation'):
note.pop(field, None)
onset = absolute + fraction(note['onset'])
onset = absolute + generator.model.fraction(note['onset'])
for tone in note['pitches']:
pitches[tone['id']] = (note, tone, onset)
events.append((note, measure, onset))
absolute += fraction(measure['duration'])
absolute += generator.model.fraction(measure['duration'])
attacks = [(n, m, t) for n, m, t in events if n['pitches'] and not n.get('cue') and not n['pitches'][0].get('cue') and not n['pitches'][0].get('tie_from')]
previous, previous_end = '', None
for index, (note, measure, onset) in enumerate(attacks):
tone = note['pitches'][0]
end = onset + fraction(tone['duration'])
end = onset + generator.model.fraction(tone['duration'])
chain = []
while target := tone.get('tie_to'):
continuation, tone, time = pitches[target]
chain.append(continuation)
end = time + fraction(tone['duration'])
end = time + generator.model.fraction(tone['duration'])
following = attacks[index + 1][2] if index + 1 < len(attacks) else None
final = following is None or following > end or any(onset < b <= following for b in boundaries)
if previous_end is None or onset > previous_end or onset in boundaries:
previous = ''
beat, onbeat = pulse(measure, fraction(note['onset']))
beat, onbeat = pulse(measure, generator.model.fraction(note['onset']))
text = syllable(end - onset, beat, onbeat, final, previous)
values = [{'kind': 'syllabic', 'text': 'single', 'attributes': {}}, {'kind': 'text', 'text': text, 'attributes': {}}]
verse = {'number': '1', 'syllables': values}
if chain:
values.append({'kind': 'extend', 'text': '', 'attributes': {'type': 'start'}})
verse['extend_to'] = rational(end)
verse['extend_to'] = generator.model.rational(end)
for position, continuation in enumerate(chain):
continuation['lyrics'] = [{'number': '1', 'syllables': [{'kind': 'extend', 'text': '', 'attributes': {'type': 'stop' if position == len(chain) - 1 else 'continue'}}], 'extend_to': rational(end)}]
continuation['lyrics'] = [{'number': '1', 'syllables': [{'kind': 'extend', 'text': '', 'attributes': {'type': 'stop' if position == len(chain) - 1 else 'continue'}}], 'extend_to': generator.model.rational(end)}]
note['lyrics'] = [verse]
note['lyric'] = text
note['lyric_notation'] = {'text': text, 'syllabic': 'single', 'extend': 'start' if chain else ''}