"""Bounded timing revisions of complete realized gestures, with fixed pitches.""" from __future__ import annotations import dataclasses from fractions import Fraction as F from generator.solo import drafts, harmonic, types def fragmentation(notes): isolated = sum(n.end-n.start <= F(1,2) and a.end < n.start and n.end < b.start for a,n,b in zip(notes, notes[1:], notes[2:])) gaps = sum(0 < b.start-a.end <= 1 for a,b in zip(notes, notes[1:])) return isolated + .1*gaps def revise(draft, harmony, treatments=()): notes=draft.notes; edits=[] if len(notes)<3: return draft, () protected=set(draft.protected)|set(draft.structural)|{n.event_id for n in notes if n.role=='quote'} # An identifying rhythm also protects its internal rests. protected.update(e for feature in draft.features if 'rhythm' in feature.preserves for e in feature.events) for _ in range(2): proposals=[] for i,(a,n,b) in enumerate(zip(notes,notes[1:],notes[2:]),1): if n.event_id in protected or n.end-n.start>1: continue if not (a.end=8: break valid=[] for actual in proposals[:8]: if fragmentation(actual)>=fragmentation(notes): continue if not drafts.contract_valid(draft,actual)[0]: continue if not harmony.evaluate(actual,treatments,until=draft.end,complete=True).valid: continue if not harmonic.anchors_valid(draft.anchors,actual,draft.end,complete=True,closure='closed')[0]: continue valid.append(actual) if not valid: break actual=min(valid,key=lambda ns:(fragmentation(ns),tuple((n.start,n.end,n.pitch.number()) for n in ns))) changed=[(a,b) for a,b in zip(notes,actual) if (a.start,a.end)!=(b.start,b.end)] edits.append(types.DraftEdit('connected_rhythm',min(min(a.start,b.start) for a,b in changed), max(max(a.end,b.end) for a,b in changed),tuple(a.event_id for a,b in changed), tuple(b.event_id for a,b in changed),'same pitch sequence and attack count; connect an editable gesture')) notes=actual transforms=tuple(dataclasses.replace(t,rhythm=tuple((n.start-t.start,n.end-t.start) for n in notes if n.event_id in t.events)) if not t.preserves else t for t in draft.transformations) return dataclasses.replace(draft,notes=notes,transformations=transforms),tuple(edits)