Files
khodowany 3f63b255ac
ci / checks (push) Failing after 23m26s
Fix Playability
2026-10-08 19:04:06 -05:00

73 lines
3.5 KiB
Python

"""The recorded three-bar hold, expressed once as source-derived score time."""
import copy
import fractions
import generator.accompaniment
import generator.acoustic_voicing
import generator.arrange.ending_plan
import generator.comping
import generator.fretboard
import generator.model
import generator.parts
import generator.score
import generator.vocabulary
def chord(source):
effective = [{**m, 'harmonies': [h for h in m['harmonies'] if not h.get('chord_optional')]} for m in source]
prior = generator.accompaniment.carried_chords(effective)[-1]
return generator.accompaniment.active_chord(effective[-1], generator.model.fraction(effective[-1]['duration']), prior)
def append(source, output, part, *, sounding=False, center=55, composition=None, incoming=None):
composition = composition or generator.vocabulary.current()
if not source or not output:
return
model = generator.arrange.ending_plan.template(source)
harmony = chord(source)
grip = ()
if sounding:
if harmony is None:
raise ValueError('Ending: final performed harmony is missing or N.C.; cannot invent a closing chord')
options = generator.comping.voicings(generator.comping.semantic_key(harmony), part, composition=composition)
recent = {}
for m in reversed(output):
for n in sorted(generator.score.measure_notes(m), key=lambda n: generator.model.fraction(n['onset']), reverse=True):
for p in n['pitches']:
if 'tablature' in p:
tab = p['tablature']
recent.setdefault(tab['string'], generator.fretboard.TabPosition(tab['string'], tab['fret'], int(generator.model.pitch_number(p)) - 12))
if len(recent) >= 4:
break
previous = tuple(recent[s] for s in sorted(recent, reverse=True))
if part == 'acoustic':
bass = next((recent[s] for s in (5, 6) if s in recent), None)
previous = generator.acoustic_voicing.Voicing(bass, *(recent[s] for s in (4, 3, 2))) if bass and all(s in recent for s in (4, 3, 2)) else ()
if incoming is not None:
previous, hand = incoming
else:
hand = None
costs = {v: generator.comping.local_cost(part, generator.comping.semantic_key(harmony), v, center, composition=composition) for v in options}
path, _, _ = generator.comping.select_grips((options,), (costs,), part, 'ending-position', left=previous, left_hand=hand)
grip = path[0]
length = generator.model.fraction(model['duration'])
previous = None
for index in range(generator.arrange.ending_plan.BARS):
measure = generator.parts.build_measure(model, f'measure_{len(output) + 1}', 'rest', part, None, 'Ending' if index == 0 else '')
measure['ending_sustain'] = True
if index == 0 and harmony:
measure['harmonies'] = [{**copy.deepcopy(harmony), 'onset': generator.model.rational(0)}]
measure['harmonies'][0].pop('chord_optional', None)
if grip:
notes, cursor = [], fractions.Fraction()
for duration, written in generator.accompaniment.written_durations(length):
n = generator.model.make_event(f'ending_{part}_{index}_{len(notes)}', cursor, duration, 'note', generator.fretboard.chord_pitches(harmony, grip), written)
n['voice'] = 'voice_arranged'
if previous:
for a, b in zip(previous['pitches'], n['pitches']):
a['tie_to'], b['tie_from'] = b['id'], a['id']
notes.append(n)
previous = n
cursor += duration
measure['voices'] = [{'id': 'voice_arranged', 'notes': notes}]
output.append(measure)
generator.score.reidentify(output[-generator.arrange.ending_plan.BARS:], [])