Files
product-vamp/generator/solo/selection.py
T
2026-10-05 16:40:22 -05:00

50 lines
2.8 KiB
Python

"""One complete-event evaluator. No learned or constructor-family rewards."""
import collections
import fractions
import generator.solo.scoring
import generator.solo.types
VERSION = 'melodic-phrases-1'
WEIGHTS = {'continuity': 2, 'repetition': 3, 'interruption': 2, 'activity': 2,
'harmony': 2, 'register': 1, 'identity': 3}
def signature(notes):
notes = generator.solo.types.sounding_notes(notes)
if not notes:
return ()
return tuple((n.start-notes[0].start, n.end-n.start, n.pitch.number()-notes[0].pitch.number()) for n in notes)
def evaluate(notes, start, end, harmony, *, incoming=(), home=(), purpose='develop', contracts=(), center=60):
notes = generator.solo.types.sounding_notes(notes)
duration = end-start
if duration <= 0:
raise ValueError('Evaluation requires a positive span')
checked = harmony.evaluate(notes, expressions=contracts, until=end, complete=True)
sounding = sum((n.end-n.start for n in notes), fractions.Fraction())
pitches = [n.pitch.number() for n in notes]
joined = [n.pitch.number() for n in incoming[-1:]]+pitches
intervals = [b-a for a,b in zip(joined, joined[1:])]
continuity = sum(max(0, abs(d)-7) for d in intervals)/max(1,len(intervals))
oscillation = sum(a == -b == c and a != 0 for a,b,c in zip(intervals,intervals[1:],intervals[2:]))
figures = collections.Counter(tuple(pitches[i:i+4]) for i in range(max(0,len(pitches)-3)))
repetition = (oscillation+sum(a==b for a,b in zip(pitches,pitches[1:]))+sum(max(0,n-2) for n in figures.values()))/max(1,len(notes))
interruption = sum(a.end<b.start and a.end-a.start<1 and b.end-b.start<1 for a,b in zip(notes,notes[1:]))/max(1,len(notes))
density = fractions.Fraction(len(notes)*4,1)/duration
active = any(s.scale and s.start<end and s.end>start for s in harmony.spans)
activity = max(0,density-7)+(2 if active and not notes else 0)
if notes and sounding<duration/3:
activity += (duration/3-sounding)/duration
identity = 0
if home and purpose == 'return':
sample = notes[:len(home)]
identity = 2 if len(sample)!=len(home) else sum((a[0],a[1])!=(b[0],b[1]) or (a[2]>0)!=(b[2]>0) for a,b in zip(signature(home),signature(sample)))/len(home)
register = sum(float(n.end-n.start)*max(0,abs(n.pitch.number()-center)-10) for n in notes)/max(1,float(sounding))
values = dict(continuity=continuity,repetition=repetition,interruption=interruption,
activity=activity,harmony=checked.exposure,register=register,identity=identity)
raw = {k:fractions.Fraction(str(v)).limit_denominator(1000000) for k,v in values.items()}
contributions = {k:v*WEIGHTS[k] for k,v in raw.items()}
return generator.solo.scoring.Evaluation(VERSION,checked.valid,raw,dict(WEIGHTS),contributions,
sum(contributions.values(),fractions.Fraction()),{'span':str(duration),'attacks':len(notes),'sounding':str(sounding)})