@@ -7,6 +7,7 @@ import pathlib
|
||||
import generator.accompaniment
|
||||
import generator.acoustic
|
||||
import generator.arrange.form
|
||||
import generator.arrange.pickup
|
||||
import generator.electric
|
||||
import generator.guitar
|
||||
import generator.melody
|
||||
@@ -23,21 +24,25 @@ def arrange_document(document: dict, part_type: str, *, rhythms: generator.rhyth
|
||||
plans = plan_arrangement(document, rhythms) if part_type != 'vocal' else {}
|
||||
return _arrange_document(document, part_type, plans)
|
||||
|
||||
def section_source(source: list[dict], final: bool) -> list[dict]:
|
||||
def section_source(source: list[dict], section, next_section=None) -> list[dict]:
|
||||
result = copy.deepcopy(source)
|
||||
for measure in result:
|
||||
measure['harmonies'] = [h for h in measure['harmonies'] if not final or not h.get('chord_optional')]
|
||||
measure['harmonies'] = [h for h in measure['harmonies'] if section.section_index != FINAL_SECTION_INDEX or not h.get('chord_optional')]
|
||||
for chord in measure['harmonies']:
|
||||
chord.pop('chord_optional', None)
|
||||
if generator.arrange.pickup.enters(next_section, source):
|
||||
destination = result[section.source_measure_index + section.measure_count - 1]
|
||||
generator.arrange.pickup.harmonies(destination, result[0])
|
||||
return result
|
||||
|
||||
def plan_arrangement(document: dict, rhythms: generator.rhythm.Library) -> dict[int, generator.accompaniment.Plan]:
|
||||
source = generator.score.source_staff(document)['measures']
|
||||
result = {}
|
||||
for section in generator.arrange.form.full_arrangement_sections(source, 'acoustic'):
|
||||
sections = generator.arrange.form.full_arrangement_sections(source, 'acoustic')
|
||||
for index, section in enumerate(sections):
|
||||
if section.role == 'slash':
|
||||
continue
|
||||
measures = section_source(source, section.section_index == FINAL_SECTION_INDEX)
|
||||
measures = section_source(source, section, sections[index + 1] if index + 1 < len(sections) else None)
|
||||
previous = generator.accompaniment.carried_chords(measures)
|
||||
start = section.source_measure_index
|
||||
result[section.section_index] = generator.accompaniment.plan_section(measures[start:start + section.measure_count], rhythms, initial=previous[start], electric=section.section_index in (1, 5, 6))
|
||||
@@ -56,8 +61,8 @@ def _arrange_document(document: dict, part_type: str, plans: dict[int, generator
|
||||
for m in source:
|
||||
source_starts.append(source_time)
|
||||
source_time += generator.model.fraction(m['duration'])
|
||||
for section in sections:
|
||||
section_measures = section_source(source, section.section_index == FINAL_SECTION_INDEX)
|
||||
for index, section in enumerate(sections):
|
||||
section_measures = section_source(source, section, sections[index + 1] if index + 1 < len(sections) else None)
|
||||
previous = generator.accompaniment.carried_chords(section_measures)
|
||||
segment = []
|
||||
for si in range(section.source_measure_index, section.source_measure_index + section.measure_count):
|
||||
@@ -70,7 +75,6 @@ def _arrange_document(document: dict, part_type: str, plans: dict[int, generator
|
||||
segment.append(result)
|
||||
if part_type == 'electric' and section.role in ('melody', 'accompaniment'):
|
||||
voicing_origins.append(generator.electric.MeasureOrigin(source_starts[si], section.role, result))
|
||||
identity_segment = segment
|
||||
segment_length = sum((generator.model.fraction(m['duration']) for m in segment), fractions.Fraction())
|
||||
if section.role in ('vocal', 'melody'):
|
||||
start = source_starts[section.source_measure_index]
|
||||
@@ -78,42 +82,25 @@ def _arrange_document(document: dict, part_type: str, plans: dict[int, generator
|
||||
low, high = max(start, generator.model.fraction(shift['start'])), min(start + segment_length, generator.model.fraction(shift['end']))
|
||||
if low < high:
|
||||
octave_shifts.append({**shift, 'id': f'section-{section.section_index}-{shift["id"]}', 'start': generator.model.rational(absolute + low - start), 'end': generator.model.rational(absolute + high - start)})
|
||||
if part_type == 'vocal' and section.section_index in (1, 5) and section.source_measure_index == 1 and source[0].get('implicit') and output:
|
||||
pickup = generator.parts.build_measure(source[0], source[0]['id'], 'vocal', part_type, None)
|
||||
pickup = None
|
||||
if section.role in ('vocal', 'melody') and generator.arrange.pickup.enters(section, source) and output:
|
||||
pickup = generator.parts.build_measure(source[0], f'pickup-{section.section_index}', section.role, part_type, None)
|
||||
tuplets.extend(generator.score.reidentify(([pickup] if pickup else []) + segment, staff.get('tuplets', [])))
|
||||
if pickup:
|
||||
destination = output[-1]
|
||||
pad = generator.model.fraction(destination['duration']) - generator.model.fraction(pickup['duration'])
|
||||
if pad < 0:
|
||||
raise ValueError('pickup is longer than the preceding arrangement measure')
|
||||
voices = generator.score.copy_source_voices(pickup, strip_lyrics=False)
|
||||
for voice in voices:
|
||||
for n in voice['notes']:
|
||||
n['onset'] = generator.model.rational(generator.model.fraction(n['onset']) + pad)
|
||||
if pad:
|
||||
voice['notes'].insert(0, generator.model.make_event(f'pickup-rest-{voice["id"]}', fractions.Fraction(), pad, 'rest') | {'voice': voice['id']})
|
||||
destination['voices'] = voices
|
||||
identity_segment = [destination, *segment]
|
||||
if pickup['harmonies']:
|
||||
destination['harmonies'] = [h for h in destination['harmonies'] if generator.model.fraction(h['onset']) < pad]
|
||||
for h in pickup['harmonies']:
|
||||
h['onset'] = generator.model.rational(generator.model.fraction(h['onset']) + pad)
|
||||
destination['harmonies'].append(h)
|
||||
state = {k: copy.deepcopy(pickup[k]) for k in ('key', 'clef', 'meter', 'transpose')}
|
||||
if pad:
|
||||
destination['attributes'].append({'onset': generator.model.rational(pad), **state})
|
||||
else:
|
||||
destination.update(state)
|
||||
destination['key_signature'] = generator.model.key_label(state['key'])
|
||||
destination['time_signature'] = generator.model.meter_label(state['meter'])
|
||||
for change in pickup.get('attributes', []):
|
||||
destination['attributes'].append({**change, 'onset': generator.model.rational(generator.model.fraction(change['onset']) + pad)})
|
||||
pad = generator.arrange.pickup.offset(destination, pickup)
|
||||
onset = generator.arrange.pickup.splice(destination, pickup)
|
||||
if part_type == 'electric':
|
||||
voicing_origins = [generator.electric.MeasureOrigin(o.onset, o.role, o.measure, o.start, onset if o.measure is destination else o.end) for o in voicing_origins]
|
||||
voicing_origins.append(generator.electric.MeasureOrigin(-pad, 'melody', destination, onset))
|
||||
for shift in staff.get('octave_shifts', []):
|
||||
low, high = generator.model.fraction(shift['start']), min(generator.model.fraction(pickup['duration']), generator.model.fraction(shift['end']))
|
||||
low, high = max(onset - pad, generator.model.fraction(shift['start'])), min(generator.model.fraction(pickup['duration']), generator.model.fraction(shift['end']))
|
||||
if low < high:
|
||||
offset = absolute - generator.model.fraction(pickup['duration'])
|
||||
octave_shifts.append({**shift, 'id': f'pickup-{section.section_index}-{shift["id"]}', 'start': generator.model.rational(offset + low), 'end': generator.model.rational(offset + high)})
|
||||
tuplets.extend(generator.score.reidentify(identity_segment, staff.get('tuplets', [])))
|
||||
output.extend(segment)
|
||||
absolute += segment_length
|
||||
tuplets = generator.arrange.pickup.retain_links(output, tuplets)
|
||||
result = generator.score.make_arranged_document(document, staff, staff_id='staff-arranged-' + part_type, staff_name=part_type.capitalize(), role='vocal' if part_type == 'vocal' else 'guitar', arrangement_part_type=part_type, measures=output, tuplets=tuplets, octave_shifts=octave_shifts)
|
||||
result['metadata'].setdefault('tempo', 124)
|
||||
if part_type != 'vocal':
|
||||
|
||||
Reference in New Issue
Block a user