Files
product-vamp/generator/scales/report.py
T
khodowany 28fc67fba1
ci / checks (push) Successful in 1h16m41s
Soloing & Measures
2026-10-01 14:05:51 -05:00

45 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import generator.harmony
import generator.model
import generator.pitch
def chord_notes(chord: dict) -> tuple[tuple[int, str], ...]:
return tuple((degree, generator.model.pitch_name(generator.pitch.shift_pitch({**chord['root'], 'octave': 4}, degree - 1, generator.harmony.degree_semitones(degree, alter)))) for degree, alter in generator.harmony.degrees(chord).items())
def passage(part: str, solo, choices, library) -> str:
lines = [f'{part.capitalize()}: {solo.label}', 'Positions use the score’s measure labels and quarter-note beats; sequential measure indices are also shown.', '']
lines.append('Scale map:')
previous = None
for choice in choices:
label = choice.scale.label() if choice.scale else 'No exact match within the permitted catalog.' if choice.span.chord else 'N.C.'
if label != previous:
lines.append(f' Measure {choice.span.source_measure}, beat {choice.span.beat}: {label}')
lines.append(' ' + choice.reason)
previous = label
lines.extend(('', 'Chord detail:'))
for choice in choices:
span, scale = choice.span, choice.scale
if span.chord is None:
continue
chord = span.chord
notes = chord_notes(chord)
required = [name for _, name in notes]
if bass := chord.get('bass'):
required.append(generator.model.pitch_name(bass))
targets = [name for degree, name in notes if degree in (3, 7)] or [name for _, name in notes]
lines.append(f'Measure {span.source_measure}, beat {span.beat}; sequential measure {span.measure}; offset {span.start} through {span.end}')
lines.append(' Chord: ' + generator.harmony.symbol(chord) + '; required pitches: ' + ', '.join(dict.fromkeys(required)))
if scale:
root = generator.model.pitch_number({**chord['root'], 'octave': 4}) % 12
degree, mode = library.chord_mode(scale, generator.model.pitch_name(chord['root'])) if root in scale.pitches else (None, None)
lines.append(' Parent: ' + scale.label() + ' [' + ', '.join(scale.notes) + ']')
lines.append(f' Parent degree: {degree or "omitted root"}; chord-root mode: {mode or "not assigned"}')
lines.append(' Chord-tone targets: ' + ', '.join(targets))
if generator.model.pitch_name(chord['root']) == scale.root:
lines.append(' Tonic resolution target: ' + scale.root)
else:
lines.append(' No exact match within the permitted catalog.')
lines.extend((' Reason: ' + choice.reason, ''))
return '\n'.join(lines)