50 lines
2.8 KiB
Python
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)})
|