Sepearate Generation
This commit is contained in:
@@ -35,26 +35,6 @@ def voice(source, document, measures, part, initial=None, source_offset=0):
|
||||
generator.acoustic.combine(result)
|
||||
else:
|
||||
generator.guitar.place_guitar(result)
|
||||
# Melody grips remain optional candidates from the same source position.
|
||||
# Only genuine chord tones are copied: exposed melody tensions must not
|
||||
# become a repeated accompaniment tension behind every solo.
|
||||
sections = form.full_arrangement_sections(generator.score.source_staff(source)['measures'], part)
|
||||
first = sum(s.measure_count for s in sections if s.section_index < 2)
|
||||
head = document['staves'][0]['measures'][first + source_offset:first + source_offset + len(measures)]
|
||||
previous = generator.accompaniment.carried_chords(measures, initial)
|
||||
links = []
|
||||
for i, measure in enumerate(measures):
|
||||
if i >= len(head): break
|
||||
for note in generator.score.measure_notes(measure):
|
||||
if not note['pitches']: continue
|
||||
chord = generator.accompaniment.active_chord(measure, generator.model.fraction(note['onset']), previous[i])
|
||||
requirements = generator.fretboard.chord_requirements(chord)
|
||||
allowed = set(requirements.allowed)
|
||||
matches = [n for n in generator.score.measure_notes(head[i]) if len(n['pitches']) >= 3
|
||||
and {int(generator.model.pitch_number(p)) % 12 for p in n['pitches']} <= allowed
|
||||
and requirements.covers({int(generator.model.pitch_number(p)) % 12 for p in n['pitches']})]
|
||||
links.append(generator.electric.VoicingLink(note, tuple(matches)))
|
||||
generator.electric.reuse_voicings(links)
|
||||
generator.model.validate_document(result)
|
||||
generator.fretboard.validate_document(result)
|
||||
return result['staves'][0]['measures']
|
||||
|
||||
@@ -57,7 +57,7 @@ def plan_arrangement(document: dict, rhythms: generator.rhythm.Library) -> dict[
|
||||
canonical.acoustic_upper[start:end], canonical.electric[start:end])
|
||||
return result
|
||||
|
||||
def _arrange_document(document: dict, part_type: str, plans: dict[int, generator.accompaniment.Plan], *, scales: generator.scales.library.Library | None = None, reports: list[str] | None = None, source_name: str = '') -> dict:
|
||||
def _arrange_document(document: dict, part_type: str, plans: dict[int, generator.accompaniment.Plan], *, scales: generator.scales.library.Library | None = None, reports: list[str] | None = None, source_name: str = '', owned=None, accompaniment=None) -> dict:
|
||||
if part_type not in generator.parts.PART_TYPES:
|
||||
raise ValueError(f'unknown arrangement part: {part_type}')
|
||||
staff = generator.score.source_staff(document)
|
||||
@@ -72,30 +72,36 @@ def _arrange_document(document: dict, part_type: str, plans: dict[int, generator
|
||||
source_starts.append(source_time)
|
||||
source_time += generator.model.fraction(m['duration'])
|
||||
for index, section in enumerate(sections):
|
||||
role = section.role if owned is None or section.role in owned else 'rest'
|
||||
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):
|
||||
m = section_measures[si]
|
||||
rhythm = None
|
||||
if section.role == 'accompaniment':
|
||||
if role == 'accompaniment' and accompaniment is None:
|
||||
plan = plans[section.section_index]
|
||||
rhythm = (plan.acoustic_bass[len(segment)], plan.acoustic_upper[len(segment)]) if part_type == 'acoustic' else plan.electric[len(segment)]
|
||||
result = generator.parts.build_measure(m, f'measure_{len(output) + len(segment) + 1}', section.role, part_type, previous[si], section.label if not segment else '', rhythm)
|
||||
if role == 'accompaniment' and accompaniment is not None:
|
||||
result = accompaniment(m, previous[si], si)
|
||||
result['id'] = f'measure_{len(output) + len(segment) + 1}'
|
||||
result['section_label'] = section.label if not segment else ''
|
||||
else:
|
||||
result = generator.parts.build_measure(m, f'measure_{len(output) + len(segment) + 1}', role, part_type, previous[si], section.label if not segment else '', rhythm)
|
||||
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))
|
||||
segment_length = sum((generator.model.fraction(m['duration']) for m in segment), fractions.Fraction())
|
||||
if section.role == 'slash':
|
||||
solos.append(generator.scales.timeline.Solo(absolute, absolute + segment_length, section.label))
|
||||
if section.role in ('vocal', 'melody'):
|
||||
if role in ('vocal', 'melody'):
|
||||
start = source_starts[section.source_measure_index]
|
||||
for shift in staff.get('octave_shifts', []):
|
||||
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)})
|
||||
pickup = None
|
||||
if section.role in ('vocal', 'melody') and generator.arrange.pickup.enters(section, source) and output:
|
||||
if 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:
|
||||
@@ -115,22 +121,20 @@ def _arrange_document(document: dict, part_type: str, plans: dict[int, generator
|
||||
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 == 'acoustic':
|
||||
if part_type == 'acoustic' and accompaniment is None and (owned is None or 'accompaniment' in owned):
|
||||
generator.acoustic.prepare(result)
|
||||
generator.acoustic_voicing.place(result)
|
||||
generator.acoustic.combine(result)
|
||||
elif part_type == 'electric':
|
||||
elif part_type == 'electric' and accompaniment is None and (owned is None or 'melody' in owned or 'accompaniment' in owned):
|
||||
generator.melody.harmonize(result)
|
||||
voicing_links = generator.electric.link_voicings(source, voicing_origins)
|
||||
generator.guitar.place_guitar(result)
|
||||
if generator.accompaniment.connect_continuations(result):
|
||||
generator.guitar.place_guitar(result)
|
||||
generator.electric.reuse_voicings(voicing_links)
|
||||
# Source labels identify the head while arranging; published labels identify
|
||||
# the complete performance, including its lead-in and repeated choruses.
|
||||
for number, measure in enumerate(output, 1):
|
||||
measure['number'] = str(number)
|
||||
if part_type != 'vocal':
|
||||
if part_type != 'vocal' and owned is None:
|
||||
generator.arrange.accompaniment.install(document, result, plans)
|
||||
generator.fretboard.validate_document(result)
|
||||
if scales and solos:
|
||||
@@ -139,28 +143,6 @@ def _arrange_document(document: dict, part_type: str, plans: dict[int, generator
|
||||
reports.append(report)
|
||||
return result
|
||||
|
||||
def generate_performance_scores(source_dir: pathlib.Path, output_dir: pathlib.Path, *, rhythms: generator.rhythm.Library, scales: generator.scales.library.Library, sequences: generator.solo.sequences.Library, references=()) -> int:
|
||||
count = 0
|
||||
for path in generator.parts.source_paths(source_dir):
|
||||
try:
|
||||
document = generator.parts.prepare_document(generator.musicxml.load_musicxml(path))
|
||||
plans = plan_arrangement(document, rhythms)
|
||||
except ValueError as error:
|
||||
raise ValueError(f'{path}: {error}') from error
|
||||
parts = {}
|
||||
for part in generator.parts.PART_TYPES:
|
||||
try:
|
||||
parts[part] = _arrange_document(document, part, plans)
|
||||
except ValueError as error:
|
||||
raise ValueError(f'{path}, part {part}: {error}') from error
|
||||
try:
|
||||
report = generator.solo.arrangement.apply(document, parts, rhythms=rhythms, scales=scales, sequences=sequences, references=references)
|
||||
except ValueError as error:
|
||||
raise ValueError(f'{path}: {error}') from error
|
||||
for part, result in parts.items():
|
||||
generator.score.write_json(output_dir / f'{generator.score.slugify(path.stem)}_{part}.json', result)
|
||||
count += 1
|
||||
report_path = output_dir.parent / 'analysis' / f'{generator.score.slugify(path.stem)}.txt'
|
||||
report_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
report_path.write_text(document['metadata']['title'] + '\nSource: ' + path.name + '\n\n' + report, encoding='utf-8')
|
||||
return count
|
||||
def generate_performance_scores(source_dir: pathlib.Path, output_dir: pathlib.Path, *, rhythms=None, scales=None, sequences=None, composition_jobs=2, cache_dir=None, progress=False) -> int:
|
||||
from generator.production import generate_tree
|
||||
return generate_tree(source_dir, output_dir, jobs=composition_jobs, cache_dir=cache_dir, progress=progress)
|
||||
|
||||
Reference in New Issue
Block a user