Files
product-vamp/generator/solo/analysis.py
T
khodowany 0e68f5364b Cleanup
2026-10-04 18:48:02 -05:00

45 lines
2.2 KiB
Python

"""Immutable source events, meter boundaries and tempo. No solo construction."""
from __future__ import annotations
import dataclasses, fractions
import generator.model, generator.score, generator.solo.types
@dataclasses.dataclass(frozen=True)
class Analysis:
boundaries: tuple
tempo: fractions.Fraction
melody: tuple
body_offset: fractions.Fraction = fractions.Fraction()
def attacks(document: dict) -> tuple[generator.solo.types.Note, ...]:
"""Collapse tied tones before extracting monophonic melody fragments."""
result, pending = [], {}
absolute = fractions.Fraction()
for measure in generator.score.source_staff(document)['measures']:
for event in generator.score.measure_notes(measure):
if event.get('grace') or event.get('cue'):
continue
for tone in event['pitches']:
number = generator.model.pitch_number(tone)
if number.denominator != 1:
continue
start = absolute + generator.model.fraction(event['onset'])
end = start + generator.model.fraction(tone['duration'])
pitch = generator.solo.types.Pitch(tone['letter'], generator.model.alteration(tone['alter']), tone['octave'])
source = pending.pop(tone.get('tie_from'), None)
if source is not None:
result[source] = dataclasses.replace(result[source], end=end, sources=(*result[source].sources, event['id']))
else:
source = len(result)
result.append(generator.solo.types.Note(start, end, pitch, (event['id'],)))
if 'tie_to' in tone:
pending[tone['id']] = source
absolute += generator.model.fraction(measure['duration'])
return tuple(sorted(result, key=lambda n: (n.start, -n.pitch.number(), n.sources)))
def analyze(document):
measures=generator.score.source_staff(document)['measures']
boundaries=[fractions.Fraction()]
for m in measures:boundaries.append(boundaries[-1]+generator.model.fraction(m['duration']))
tempo=next((generator.model.fraction(t['bpm'])*generator.model.written_length(t['written']) for m in measures for t in m.get('tempos',[]) if 'bpm' in t),fractions.Fraction(120))
return Analysis(tuple(boundaries),tempo,attacks(document),boundaries[generator.score.body_start_measure_index(measures)])