"""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:], [])