Soloing
ci / checks (push) Successful in 50m39s

This commit is contained in:
khodowany
2026-10-01 06:31:14 -05:00
parent ce695a626a
commit eae432aa83
29 changed files with 1402 additions and 158 deletions
+31
View File
@@ -0,0 +1,31 @@
from __future__ import annotations
import dataclasses
import generator.harmony
import generator.model
import generator.scales.report
import generator.scales.selection
import generator.scales.timeline
import generator.solo.types
def describe(solo: generator.solo.types.Solo, scales, measure_offset=0, time_offset=0) -> str:
region = generator.scales.timeline.Solo(time_offset, time_offset + solo.end, solo.part.capitalize() + ' Solo')
positioned = tuple(dataclasses.replace(c, span=dataclasses.replace(c.span, start=c.span.start + time_offset, end=c.span.end + time_offset, measure=c.span.measure + measure_offset)) for c in solo.choices)
lines = [generator.scales.report.passage(solo.part, region, positioned, scales), f'Phrase composition: {solo.part}', f'Placement reference tempo: {solo.tempo} quarter notes/minute. Runtime tempo changes are not a comfort guarantee.', 'Times below are quarter-note offsets from the start of this solo; pitches are sounding.']
for phrase in solo.phrases:
plan = phrase.plan
lines.extend(('', f'{plan.id}: {plan.start}-{plan.end}; {plan.relationship}; motif {plan.motif.id}', f' Origin: {plan.motif.origin}; source events: ' + ', '.join(s for n in plan.motif.notes for s in n.sources)))
if plan.target:
lines.append(f' Destination: {plan.target.chord}; {plan.target.reason}; arrival {plan.target.earliest}-{plan.target.latest}')
for decision in phrase.decisions:
lines.append(f' {decision.start}-{decision.end}: {decision.source}; {decision.treatment}')
if not decision.notes:
lines.append(' Silence')
for note in decision.notes:
choice = next(c for c in solo.choices if c.span.start <= note.start < c.span.end)
root = generator.scales.selection.root_number(choice.span.chord)
degrees = [str(d) for d, a in generator.harmony.degrees(choice.span.chord).items() if (root + generator.harmony.degree_semitones(d, a)) % 12 == note.pitch.number() % 12]
function = 'chord degree ' + '/'.join(degrees) if degrees else 'in-collection connection'
lines.append(f' {note.start}-{note.end}: {note.pitch.label()}; {note.role}; {generator.harmony.symbol(choice.span.chord)}, {function}; {choice.scale.label()}; string {note.position.string_number}, fret {note.position.fret}')
return '\n'.join(lines) + '\n'