73 lines
3.5 KiB
Python
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:], [])
|