Cleanupf
ci / checks (push) Successful in 1h15m45s

This commit is contained in:
khodowany
2026-09-23 23:54:19 -05:00
parent a0faa9b565
commit da5640291a
24 changed files with 366 additions and 233 deletions
+28 -6
View File
@@ -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')