Files
product-vamp/generator/solo/motifs.py
T
khodowany 5414caf30a
ci / checks (push) Successful in 1h53m40s
Soloing
2026-10-01 11:23:47 -05:00

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)