"""Exact rhythmic groups for generated material; imported music is not quantized. Binary boundaries may be displaced together. Tuplet runs must return to their entry subdivision phase before binary material resumes. A search boundary is not a rhythmic boundary and must not be used to clip a group. """ from __future__ import annotations import dataclasses from fractions import Fraction as F @dataclasses.dataclass(frozen=True) class RhythmGroup: start: F end: F family: str events: tuple[tuple[F, F], ...] displacement: F = F() source: str = '' def describe(pattern, *, origin=F(), source='', imported=False): if not pattern:return () if imported: return (RhythmGroup(origin, origin+pattern[-1][1], 'source', pattern, source=source),) result=[]; i=0 while i