Files
product-vamp/generator/harmonic_time.py
T
khodowany ffdbe48258
ci / checks (push) Failing after 43m34s
Validation
2026-10-05 20:33:14 -05:00

49 lines
1.8 KiB
Python

from __future__ import annotations
import dataclasses
import fractions
import generator.model
def solo_source(staff):
"""Source-form pickup context, shared by training and arranged choruses.
The arrangement repeats the source form before each solo. Only an unlabelled
pickup inherits its preceding source-form harmony. Scores without a pickup
retain their ordinary initial-harmony checks. Explicit pickup harmony
(including N.C.) still takes precedence in read().
"""
from generator.score import body_start_measure_index
measures=staff['measures'];initial=None
if body_start_measure_index(measures):
initial=next((h for m in reversed(measures) for h in reversed(m['harmonies'])),None)
return read(staff,initial=initial)
@dataclasses.dataclass(frozen=True)
class ChordSpan:
start: fractions.Fraction
end: fractions.Fraction
measure: int
source_measure: str
beat: fractions.Fraction
key: dict
chord: dict | None
def read(staff: dict, *, initial: dict | None = None) -> tuple[ChordSpan, ...]:
result = []
absolute = fractions.Fraction()
active = initial
for number, measure in enumerate(staff['measures'], 1):
length = generator.model.fraction(measure['duration'])
harmonies = {generator.model.fraction(h['onset']): h for h in measure['harmonies']}
changes = {generator.model.fraction(a['onset']): a['key'] for a in measure.get('attributes', []) if 'key' in a}
boundaries = sorted({fractions.Fraction(), length} | set(harmonies) | set(changes))
key = measure['key']
for start, end in zip(boundaries, boundaries[1:]):
key = changes.get(start, key)
active = harmonies.get(start, active)
result.append(ChordSpan(absolute + start, absolute + end, number, str(measure['number']), start + 1, key, active if active and active['kind'] != 'none' else None))
absolute += length
return tuple(result)