Rhythm Selection & Cleanup
ci / checks (push) Successful in 1h16m10s

This commit is contained in:
khodowany
2026-09-26 02:50:22 -05:00
parent 4d6348d354
commit 9fd5abd075
91 changed files with 4692 additions and 2484 deletions
+32 -23
View File
@@ -1,44 +1,53 @@
from __future__ import annotations
from pathlib import Path
import pathlib
from generator.guitar import place_guitar
from generator.musicxml import load_musicxml
from generator.parts import PART_TYPES, build_measure, prepare_document, propagated_chords, source_paths
from generator.scat import add_scat
from generator.score import make_arranged_document, reidentify, source_staff, write_json
import generator.accompaniment
import generator.guitar
import generator.musicxml
import generator.parts
import generator.rhythm
import generator.scat
import generator.score
def practice_document(document: dict, part_type: str) -> dict:
return _practice_document(prepare_document(document, practice=True), part_type)
def practice_document(document: dict, part_type: str, *, rhythms: generator.rhythm.Library) -> dict:
document = generator.parts.prepare_document(document, practice=True)
plan = generator.accompaniment.plan_section(generator.score.source_staff(document)['measures'], rhythms, electric=False) if part_type == 'acoustic' else None
return _practice_document(document, part_type, plan)
def _practice_document(document: dict, part_type: str) -> dict:
if part_type not in PART_TYPES:
def _practice_document(document: dict, part_type: str, plan: generator.accompaniment.Plan | None) -> dict:
if part_type not in generator.parts.PART_TYPES:
raise ValueError(f'unknown practice part: {part_type}')
staff = source_staff(document)
previous = propagated_chords(staff['measures'])
staff = generator.score.source_staff(document)
previous = generator.accompaniment.carried_chords(staff['measures'])
role = {'acoustic': 'accompaniment', 'electric': 'melody', 'vocal': 'vocal'}[part_type]
measures = [build_measure(m, f'measure-{i + 1}', role, part_type, previous[i], m.get('section_label', '')) for i, m in enumerate(staff['measures'])]
if part_type == 'acoustic' and plan is None:
raise ValueError('acoustic practice requires a rhythm plan')
measures = [generator.parts.build_measure(m, f'measure-{i + 1}', role, part_type, previous[i], m.get('section_label', ''), plan.acoustic[i] if part_type == 'acoustic' else None) for i, m in enumerate(staff['measures'])]
for measure in measures:
for chord in measure['harmonies']:
chord.pop('chord_optional', None)
tuplets = reidentify(measures, staff.get('tuplets', []))
result = make_arranged_document(document, staff, staff_id='staff-practice-' + part_type, staff_name=part_type.capitalize(), role='vocal' if part_type == 'vocal' else 'guitar', arrangement_part_type=part_type, measures=measures, tuplets=tuplets, octave_shifts=staff.get('octave_shifts', []) if role in ('melody', 'vocal') else [])
tuplets = generator.score.reidentify(measures, staff.get('tuplets', []))
result = generator.score.make_arranged_document(document, staff, staff_id='staff-practice-' + part_type, staff_name=part_type.capitalize(), role='vocal' if part_type == 'vocal' else 'guitar', arrangement_part_type=part_type, measures=measures, tuplets=tuplets, octave_shifts=staff.get('octave_shifts', []) if role in ('melody', 'vocal') else [])
if part_type == 'vocal':
add_scat(result)
generator.scat.add_scat(result)
else:
place_guitar(result)
generator.guitar.place_guitar(result)
if generator.accompaniment.connect_continuations(result):
generator.guitar.place_guitar(result)
return result
def generate_practice_scores(source_dir: Path, output_dir: Path) -> int:
sources = source_paths(source_dir, practice=True)
def generate_practice_scores(source_dir: pathlib.Path, output_dir: pathlib.Path, *, rhythms: generator.rhythm.Library) -> int:
sources = generator.parts.source_paths(source_dir, practice=True)
for path in sources:
try:
document = prepare_document(load_musicxml(path), practice=True)
document = generator.parts.prepare_document(generator.musicxml.load_musicxml(path), practice=True)
plan = generator.accompaniment.plan_section(generator.score.source_staff(document)['measures'], rhythms, electric=False)
except ValueError as error:
raise ValueError(f'{path}: {error}') from error
for part in PART_TYPES:
for part in generator.parts.PART_TYPES:
try:
write_json(output_dir / path.stem / (part + '.json'), _practice_document(document, part))
generator.score.write_json(output_dir / path.stem / (part + '.json'), _practice_document(document, part, plan))
except ValueError as error:
raise ValueError(f'{path}, part {part}: {error}') from error
return len(sources) * len(PART_TYPES)
return len(sources) * len(generator.parts.PART_TYPES)