from __future__ import annotations import pathlib 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, *, 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, plan: generator.accompaniment.Plan | None) -> dict: if part_type not in generator.parts.PART_TYPES: raise ValueError(f'unknown practice part: {part_type}') staff = generator.score.source_staff(document) previous = generator.accompaniment.carried_chords(staff['measures']) role = {'acoustic': 'accompaniment', 'electric': 'melody', 'vocal': 'vocal'}[part_type] 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 = 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': generator.scat.add_scat(result) else: generator.guitar.place_guitar(result) if generator.accompaniment.connect_continuations(result): generator.guitar.place_guitar(result) return result 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 = 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 generator.parts.PART_TYPES: try: 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(generator.parts.PART_TYPES)