+16
-20
@@ -12,28 +12,25 @@ import generator.score
|
||||
|
||||
PART_TYPES = ('acoustic', 'electric', 'vocal')
|
||||
|
||||
def source_paths(directory: pathlib.Path, *, practice: bool = False) -> list[pathlib.Path]:
|
||||
def source_paths(directory: pathlib.Path) -> list[pathlib.Path]:
|
||||
if not directory.is_dir():
|
||||
raise ValueError(f'{"practice" if practice else "performance"} source directory not found: {directory}')
|
||||
raise ValueError(f'performance source directory not found: {directory}')
|
||||
paths = sorted(directory.rglob('*.xml'))
|
||||
identities = {}
|
||||
for path in paths:
|
||||
if practice:
|
||||
if path.parent != directory or len(path.stem) != 4 or not path.stem.isascii() or not path.stem.isdecimal():
|
||||
raise ValueError(f'{path}: practice sources require a four-digit filename directly under data/practice')
|
||||
elif path.parent != directory:
|
||||
if path.parent != directory:
|
||||
raise ValueError(f'{path}: performance sources belong directly under data/songs')
|
||||
identity = path.stem if practice else generator.score.slugify(path.stem)
|
||||
identity = generator.score.slugify(path.stem)
|
||||
if identity in identities:
|
||||
raise ValueError(f'{identities[identity]} and {path}: conflicting output path {identity}-acoustic.json')
|
||||
identities[identity] = path
|
||||
return paths
|
||||
|
||||
def prepare_document(document: dict, *, practice: bool = False) -> dict:
|
||||
validate_source(document, practice=practice)
|
||||
def prepare_document(document: dict) -> dict:
|
||||
validate_source(document)
|
||||
return generator.pitch.concert_document(document, octave_transposing=True)
|
||||
|
||||
def generated_voice(measure: dict, role: str, previous: dict | None) -> list[dict]:
|
||||
def generated_voice(measure: dict, role: str) -> list[dict]:
|
||||
length = generator.model.fraction(measure['duration'])
|
||||
if role == 'rest':
|
||||
return [generator.model.make_event('rest', fractions.Fraction(), length, 'rest', written={'type': 'whole', 'dots': 0}) | {'measure_rest': True}]
|
||||
@@ -55,8 +52,7 @@ def generated_voice(measure: dict, role: str, previous: dict | None) -> list[dic
|
||||
beat_index += 1
|
||||
return events
|
||||
|
||||
def validate_source(document: dict, *, practice: bool = False) -> None:
|
||||
voices = set()
|
||||
def validate_source(document: dict) -> None:
|
||||
for measure in generator.score.source_staff(document)['measures']:
|
||||
for state in [measure, *measure.get('attributes', [])]:
|
||||
if state.get('clef', {}).get('sign') in ('TAB', 'percussion'):
|
||||
@@ -64,26 +60,26 @@ def validate_source(document: dict, *, practice: bool = False) -> None:
|
||||
if 'staff_details' in state:
|
||||
generator.guitar.validate_tuning(state, measure['id'])
|
||||
for voice in measure['voices']:
|
||||
voices.add(voice['id'])
|
||||
for note in voice['notes']:
|
||||
if practice and len(note['pitches']) > 1:
|
||||
raise ValueError(f'{measure["id"]}: practice requires a monophonic melody, not chord stacks')
|
||||
if any('tablature' in p or 'unpitched' in p for p in note['pitches']):
|
||||
raise ValueError(f'{measure["id"]}: authored TAB and percussion are unsupported')
|
||||
if practice and len(voices) > 1:
|
||||
raise ValueError('practice requires one monophonic voice')
|
||||
|
||||
def build_measure(source: dict, identifier: str, role: str, part: str, previous: dict | None, label: str = '', rhythm: generator.rhythm.Bar | tuple[generator.rhythm.Bar, generator.rhythm.Bar] | None = None, *, melody_octave: bool = False) -> dict:
|
||||
def build_measure(source: dict, identifier: str, role: str, part: str, previous: dict | None, label: str = '', rhythm: generator.rhythm.Bar | tuple[generator.rhythm.Bar, generator.rhythm.Bar] | None = None) -> dict:
|
||||
voices = generator.score.copy_source_voices(source, strip_lyrics=role == 'melody') if role in ('vocal', 'melody') else None
|
||||
if role == 'accompaniment' and part == 'acoustic':
|
||||
voices = [{'id': lane, 'notes': generator.accompaniment.realize(source, bar, previous, lane=lane)} for lane, bar in zip(('upper', 'bass'), (rhythm[1], rhythm[0]))]
|
||||
for voice in voices:
|
||||
for note in voice['notes']:
|
||||
note.update(voice=voice['id'], stem='down' if voice['id'] == 'bass' else 'up')
|
||||
notes = [n for v in voices for n in v['notes']] if voices else generator.accompaniment.realize(source, rhythm, previous, lane='electric') if rhythm is not None else generated_voice(source, role, previous)
|
||||
if voices:
|
||||
notes = [note for voice in voices for note in voice['notes']]
|
||||
elif rhythm is not None:
|
||||
notes = generator.accompaniment.realize(source, rhythm, previous, lane='electric')
|
||||
else:
|
||||
notes = generated_voice(source, role)
|
||||
if role in ('melody', 'vocal'):
|
||||
for note in notes:
|
||||
octaves = 2 if melody_octave and role == 'melody' else 1
|
||||
octaves = 2 if role == 'melody' else 1
|
||||
note['pitches'] = [generator.pitch.shift_pitch(p, 7 * octaves, 12 * octaves) for p in note['pitches']]
|
||||
if role == 'melody':
|
||||
note['_melody'] = True
|
||||
|
||||
Reference in New Issue
Block a user