from __future__ import annotations import dataclasses import typing import generator.score @dataclasses.dataclass(frozen=True) class ArrangementSection: label: str source_measure_index: int measure_count: int section_index: int role: typing.Literal["accompaniment", "melody", "vocal", "rest", "slash"] def phrase_aware_edge_measure_count(body_measure_count: int) -> int: if body_measure_count >= 8: return 8 if body_measure_count >= 4: return 4 return body_measure_count def section_role(part_type: str, section_index: int) -> typing.Literal["accompaniment", "melody", "vocal", "rest", "slash"]: if part_type == "acoustic": return "slash" if section_index == 3 else "accompaniment" if part_type == "electric": if section_index == 4: return "slash" if section_index == 2: return "melody" if section_index in {1, 5, 6}: return "accompaniment" return "rest" return "vocal" if section_index in {1, 5, 6} else "rest" def full_arrangement_sections(measures: list[dict], part_type: str) -> list[ArrangementSection]: if not measures: return [] body_start = generator.score.body_start_measure_index(measures) if body_start >= len(measures): return [] body_measure_count = len(measures) - body_start edge_measure_count = phrase_aware_edge_measure_count(body_measure_count) edge_source_measure = body_start + body_measure_count - edge_measure_count specs = ( ("Rhythm Lead-in", edge_source_measure, edge_measure_count, 0), ("Vocal Melody", body_start, body_measure_count, 1), ("Guitar Melody", body_start, body_measure_count, 2), ("Acoustic Solo", body_start, body_measure_count, 3), ("Electric Solo", body_start, body_measure_count, 4), ("Vocal Melody", body_start, body_measure_count, 5), ("Ending", edge_source_measure, edge_measure_count, 6), ) return [ ArrangementSection(label=label, source_measure_index=start, measure_count=count, section_index=index, role=section_role(part_type, index)) for label, start, count, index in specs if count > 0 ]