57 lines
2.8 KiB
Python
57 lines
2.8 KiB
Python
"""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)
|