Rhythm Selection & Cleanup
ci / checks (push) Successful in 1h16m10s

This commit is contained in:
khodowany
2026-09-26 02:50:22 -05:00
parent 4d6348d354
commit 9fd5abd075
91 changed files with 4692 additions and 2484 deletions
+19 -19
View File
@@ -1,39 +1,39 @@
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
import pathlib
import typing
from generator.score import load_json, measure_notes
from generator.parts import PART_TYPES
from generator.model import fraction, pitch_name
import generator.model
import generator.parts
import generator.score
def _arranged_staff(document: dict[str, Any]) -> dict[str, Any]:
def _arranged_staff(document: dict[str, typing.Any]) -> dict[str, typing.Any]:
staves = document.get("staves")
if not isinstance(staves, list) or not staves or not isinstance(staves[0], dict):
raise ValueError("generated score document must contain an arranged staff")
return staves[0]
def _measure_summary(staff: dict[str, Any], measure: dict[str, Any], measure_number: int) -> dict[str, Any]:
notes = measure_notes(measure)
summary: dict[str, Any] = {
def _measure_summary(staff: dict[str, typing.Any], measure: dict[str, typing.Any], measure_number: int) -> dict[str, typing.Any]:
notes = generator.score.measure_notes(measure)
summary: dict[str, typing.Any] = {
"measure": measure_number,
"section_label": str(measure.get("section_label", "")),
"harmonies": [{"onset": str(fraction(h["onset"])), "symbol": h["symbol"]} for h in measure["harmonies"]],
"duration": str(fraction(measure["duration"])),
"harmonies": [{"onset": str(generator.model.fraction(h["onset"])), "symbol": h["symbol"]} for h in measure["harmonies"]],
"duration": str(generator.model.fraction(measure["duration"])),
"slash_count": sum(1 for note in notes if note.get("kind", "note") == "note" and note.get("notehead") == "slash"),
}
if staff.get("arrangement_part_type") == "vocal":
summary["vocal_skeleton"] = [
f'{fraction(note["onset"])}+{fraction(note["duration"])} '
f'{generator.model.fraction(note["onset"])}+{generator.model.fraction(note["duration"])} '
+ note['written']['type'] + '.' * note['written'].get('dots', 0)
+ ' ' + note['kind'] + ' ' + ','.join(pitch_name(p) + str(p['octave']) for p in note['pitches'])
+ ' ' + note['kind'] + ' ' + ','.join(generator.model.pitch_name(p) + str(p['octave']) for p in note['pitches'])
+ ' ' + note.get('lyric', '')
for note in notes
]
return summary
def performance_document_summary(document: dict[str, Any]) -> dict[str, Any]:
def performance_document_summary(document: dict[str, typing.Any]) -> dict[str, typing.Any]:
metadata = document.get("metadata")
if not isinstance(metadata, dict):
metadata = {}
@@ -60,13 +60,13 @@ def performance_document_summary(document: dict[str, Any]) -> dict[str, Any]:
"measures": measure_summaries,
}
def performance_tree_summary(root: Path) -> dict[str, Any]:
def performance_tree_summary(root: pathlib.Path) -> dict[str, typing.Any]:
return {
path.name: performance_document_summary(load_json(path))
path.name: performance_document_summary(generator.score.load_json(path))
for path in sorted(root.glob("*.json"))
}
def validate_expected_performance_summary(expected_path: Path, generated_songs_dir: Path) -> None:
def validate_expected_performance_summary(expected_path: pathlib.Path, generated_songs_dir: pathlib.Path) -> None:
if not expected_path.exists():
raise ValueError(f"missing generator compatibility summary: {expected_path}")
with expected_path.open("r", encoding="utf-8") as stream:
@@ -85,10 +85,10 @@ def validate_expected_performance_summary(expected_path: Path, generated_songs_d
raise ValueError(f"performance generator drift in {name}; update generator logic or refresh the expected summary intentionally")
raise ValueError("performance generator drift")
def validate_practice_parts(generated_practice_dir: Path) -> None:
def validate_practice_parts(generated_practice_dir: pathlib.Path) -> None:
if not generated_practice_dir.exists():
return
expected_names = {part + '.json' for part in PART_TYPES}
expected_names = {part + '.json' for part in generator.parts.PART_TYPES}
for exercise_dir in sorted(path for path in generated_practice_dir.iterdir() if path.is_dir()):
json_names = {path.name for path in exercise_dir.glob("*.json")}
if json_names != expected_names: