from __future__ import annotations from pathlib import Path from generator.guitar import place_guitar from generator.musicxml import load_musicxml from generator.parts import PART_TYPES, build_measure, propagated_chords, validate_source from generator.pitch import concert_document from generator.scat import add_scat from generator.score import make_arranged_document, reidentify, source_staff, write_json def practice_document(document: dict, part_type: str) -> dict: if part_type not in PART_TYPES: raise ValueError(f'unknown practice part: {part_type}') validate_source(document, practice=True) document = concert_document(document, octave_transposing=True) staff = source_staff(document) previous = propagated_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'])] 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 []) if part_type == 'vocal': add_scat(result) else: place_guitar(result) return result def generate_practice_scores(source_dir: Path, output_dir: Path) -> int: if not source_dir.is_dir(): raise ValueError(f'practice source directory not found: {source_dir}') sources = sorted(source_dir.rglob('*.xml')) for path in sources: if path.parent != source_dir or len(path.stem) != 3 or not path.stem.isascii() or not path.stem.isdecimal(): raise ValueError(f'{path}: practice sources require a three-digit filename directly under data/practice') for path in sources: document = load_musicxml(path) for part in PART_TYPES: try: write_json(output_dir / path.stem / (part + '.json'), practice_document(document, part)) except ValueError as error: raise ValueError(f'{path}, part {part}: {error}') from error return len(sources) * len(PART_TYPES)