@@ -2,68 +2,45 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from generator.score import copy_source_voices, make_arranged_document, make_measure, reidentify, slugify, source_staff, write_json
|
||||
from generator.guitar import place_guitar
|
||||
from generator.musicxml import load_musicxml
|
||||
from generator.practice.guitar import transpose_guitar_practice
|
||||
from generator.transposition import TARGET_KEYS
|
||||
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) -> dict:
|
||||
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)
|
||||
source_measures = staff.get("measures")
|
||||
if not isinstance(source_measures, list):
|
||||
raise ValueError("practice source must contain measures")
|
||||
measures: list[dict] = []
|
||||
for index, measure in enumerate(source_measure for source_measure in source_measures if isinstance(source_measure, dict)):
|
||||
voices = copy_source_voices(measure, strip_lyrics=True)
|
||||
output_measure = make_measure(
|
||||
f"measure-{index + 1}",
|
||||
measure,
|
||||
[],
|
||||
section_label=str(measure.get("section_label", "")),
|
||||
)
|
||||
output_measure['voices'] = voices
|
||||
for chord in output_measure['harmonies']:
|
||||
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)
|
||||
measures.append(output_measure)
|
||||
tuplets = reidentify(measures, staff.get('tuplets', []))
|
||||
result = make_arranged_document(
|
||||
document,
|
||||
staff,
|
||||
staff_id="staff-arranged-practice",
|
||||
staff_name="Practice",
|
||||
role="guitar",
|
||||
arrangement_part_type="practice",
|
||||
measures=measures,
|
||||
tuplets=tuplets,
|
||||
octave_shifts=staff.get('octave_shifts', []),
|
||||
)
|
||||
result['metadata'].setdefault('authored_key_signature', document['metadata']['key_signature'])
|
||||
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 key_file_name(key: str) -> str:
|
||||
return slugify(key.replace("#", "sharp")) + ".json"
|
||||
|
||||
def generate_key_variants(source_path: Path, output_dir: Path) -> int:
|
||||
base_document = practice_document(load_musicxml(source_path))
|
||||
count = 0
|
||||
for key in TARGET_KEYS:
|
||||
try:
|
||||
variant = transpose_guitar_practice(base_document, key)
|
||||
except ValueError as error:
|
||||
raise ValueError(f'{source_path}, target key {key}: {error}') from error
|
||||
write_json(output_dir / source_path.stem / key_file_name(key), variant)
|
||||
count += 1
|
||||
return count
|
||||
|
||||
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.glob('*.xml'))
|
||||
for path in source_dir.rglob('*.xml'):
|
||||
parts = path.relative_to(source_dir).parts
|
||||
if len(parts) != 1 and (len(parts) != 2 or parts[0] != 'songs'):
|
||||
raise ValueError(f'{path}: practice sources belong directly under the data directory')
|
||||
sources = sorted(source_dir.rglob('*.xml'))
|
||||
for path in sources:
|
||||
if len(path.stem) != 3 or not path.stem.isascii() or not path.stem.isdecimal() or int(path.stem) == 0:
|
||||
raise ValueError(f'{path}: practice sources require a three-digit positive filename such as 001.xml')
|
||||
return sum(generate_key_variants(path, output_dir) 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)
|
||||
|
||||
Reference in New Issue
Block a user