"""Realized gesture identities and their explicit development relationships.""" from __future__ import annotations import dataclasses import fractions import generator.solo.harmonic import generator.solo.scoring import generator.solo.types def reference(plan, gesture, realized): if not realized or gesture is None or gesture.relationship == 'statement': return plan.motif # Repetition returns to the introduced identity; extension/answer develops # the most recently heard related gesture, never an unrelated catalog winner. return realized[0] if gesture.relationship == 'repeat_with_harmonic_adaptation' else realized[-1] def local_notes(notes, start, end): return tuple(dataclasses.replace(n, start=n.start - start, end=min(n.end, end) - start, position=None, continuation=False) for n in notes if start <= n.start < end) def complete(gesture, reference, notes, harmony): actual = local_notes(notes, gesture.start, gesture.end) sounded = any(n.start < gesture.end and n.end > gesture.start for n in notes) active = any(s.scale and s.start < gesture.end and s.end > gesture.start for s in harmony.spans) if not active: return True, 0.0, None if gesture.relationship == 'close': return sounded, 0.0, None required = min(len(reference.notes), 2) if len(actual) < required: return False, 1.0, None fidelity = generator.solo.scoring.motif_fidelity(actual, reference, gesture.treatment) if gesture.treatment == 'rhythmic_reference': expected = tuple((n.start, min(n.end, gesture.end - gesture.start)) for n in reference.notes if n.start < gesture.end - gesture.start) if tuple((n.start, n.end) for n in actual) != expected: return False, 1.0, None elif gesture.relationship != 'statement' and fidelity >= 0.75: return False, fidelity, None degrees = [] for n in actual: span = harmony.at(gesture.start + n.start) degrees.append(dict(span.roles.degrees).get(n.pitch.number() % 12, ()) if span else ()) result = generator.solo.types.RealizedMotif(gesture.id, reference.id, actual, gesture.end - gesture.start, gesture.relationship, gesture.treatment, tuple(degrees), gesture.start) return True, fidelity, result def interval_steps(reference, harmony, source_start=None): result = [] source_start = getattr(reference, 'source_start', source_start or fractions.Fraction()) for first, second in zip(reference.notes, reference.notes[1:]): span = harmony.at((source_start or fractions.Fraction()) + first.start) try: step = generator.solo.types.scale_index(span.scale, second.pitch.number()) - generator.solo.types.scale_index(span.scale, first.pitch.number()) if span and span.scale else None except ValueError: step = None result.append((step, second.pitch.number() - first.pitch.number())) return tuple(result)