+28
-6
@@ -1,14 +1,36 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fractions import Fraction
|
||||
from pathlib import Path
|
||||
|
||||
from generator.guitar import validate_tuning
|
||||
from generator.model import beat_lengths, fraction, make_event
|
||||
from generator.pitch import shift_pitch
|
||||
from generator.score import copy_source_voices, make_measure, measure_notes, source_staff
|
||||
from generator.pitch import concert_document, shift_pitch
|
||||
from generator.score import copy_source_voices, make_measure, slugify, source_staff
|
||||
|
||||
PART_TYPES = ('acoustic', 'electric', 'vocal')
|
||||
|
||||
def source_paths(directory: Path, *, practice: bool = False) -> list[Path]:
|
||||
if not directory.is_dir():
|
||||
raise ValueError(f'{"practice" if practice else "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) != 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')
|
||||
elif path.parent != directory:
|
||||
raise ValueError(f'{path}: performance sources belong directly under data/songs')
|
||||
identity = path.stem if practice else 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)
|
||||
return concert_document(document, octave_transposing=True)
|
||||
|
||||
def propagated_chords(measures: list[dict], *, include_optional: bool = True) -> list[dict | None]:
|
||||
result = []
|
||||
current = None
|
||||
@@ -34,8 +56,9 @@ def generated_voice(measure: dict, role: str, previous: dict | None) -> list[dic
|
||||
common_time = measure['time_signature'] == '4/4' and not any('meter' in a for a in measure.get('attributes', []))
|
||||
if role == 'rhythm' and common_time:
|
||||
attacks = [(Fraction(n, 2), Fraction(1, 2)) for n in (0, 3, 4, 5, 7)]
|
||||
elif role == 'accompaniment' and common_time:
|
||||
attacks = [(Fraction(1, 2), Fraction(1, 2))]
|
||||
elif role == 'accompaniment':
|
||||
boundaries = sorted({Fraction(), length} | {fraction(h['onset']) for h in measure['harmonies'] if fraction(h['onset']) < length})
|
||||
attacks = [(start, end - start) for start, end in zip(boundaries, boundaries[1:])]
|
||||
else:
|
||||
attacks = []
|
||||
states = [(Fraction(), measure['meter'])]
|
||||
@@ -59,11 +82,10 @@ def generated_voice(measure: dict, role: str, previous: dict | None) -> list[dic
|
||||
events.append(make_event(f'rest-{len(events)}', cursor, onset - cursor, 'rest'))
|
||||
duration = min(duration, length - onset)
|
||||
chord = active_chord(measure, onset, previous)
|
||||
source_attack = any(fraction(n['onset']) == onset and n['kind'] == 'note' and not n.get('grace') and any(not p.get('tie_from') for p in n['pitches']) for n in measure_notes(measure))
|
||||
if role == 'slash':
|
||||
event = make_event(f'slash-{len(events)}', onset, duration, 'note')
|
||||
event['notehead'] = 'slash'
|
||||
elif not chord or role == 'accompaniment' and source_attack:
|
||||
elif not chord:
|
||||
event = make_event(f'rest-{len(events)}', onset, duration, 'rest')
|
||||
else:
|
||||
event = make_event(f'chord-{len(events)}', onset, duration, 'note')
|
||||
|
||||
Reference in New Issue
Block a user