49 lines
2.4 KiB
Python
49 lines
2.4 KiB
Python
"""One authoritative evaluation of completed phrases, never guitar travel."""
|
|
from dataclasses import dataclass
|
|
from fractions import Fraction as F
|
|
from generator import vocabulary
|
|
from generator.solo import types
|
|
|
|
@dataclass(frozen=True)
|
|
class Evaluation:
|
|
version: str
|
|
valid: bool
|
|
raw: dict
|
|
weights: dict
|
|
contributions: dict
|
|
total: float
|
|
denominators: dict
|
|
|
|
def signature(notes):
|
|
ns=types.sounding_notes(notes)
|
|
if not ns:return ()
|
|
origin=ns[0].start;pitch=ns[0].pitch.number()
|
|
return tuple((n.start-origin,n.end-n.start,n.pitch.number()-pitch) for n in ns)
|
|
|
|
def evaluate(phrase,incoming,history,center,weights=None):
|
|
ns=types.sounding_notes(phrase.notes); span=float(phrase.end-phrase.start)
|
|
weights=dict(vocabulary.data('solo')['weights'] if weights is None else weights)
|
|
if set(weights)!={'purpose','continuity','repetition','register'}:raise ValueError('Unknown musical contribution')
|
|
if not ns:
|
|
valid=phrase.purpose=='space';raw=dict.fromkeys(weights,0.)
|
|
else:
|
|
valid=True
|
|
movement=[b.pitch.number()-a.pitch.number() for a,b in zip(ns,ns[1:])]
|
|
# Declared expansion permits arpeggio leaps; avoid unprepared octave flailing.
|
|
purpose=sum(max(0,abs(v)-7)/12 for v in movement)/max(1,len(movement))
|
|
oscillation=sum(a==-b==c and a!=0 for a,b,c in zip(movement,movement[1:],movement[2:]))
|
|
purpose+=max(0,oscillation-1)/max(1,len(movement))
|
|
if phrase.destination:
|
|
prior=next((n for n in reversed(ns) if n.end<=phrase.destination.earliest),None)
|
|
if prior:purpose+=max(0,abs(prior.pitch.number()-phrase.destination.pitch.number())-3)/12
|
|
continuity=max(0,abs(ns[0].pitch.number()-incoming.pitch.number())-5)/12 if incoming else 0.
|
|
current=signature(ns[:6]); repetition=0.
|
|
for age,old in enumerate(reversed(history[-4:])):
|
|
if len(current)>=3 and current==signature(old.notes[:6]):repetition+=1/(age+1)
|
|
if phrase.purpose=='return':repetition=max(0,repetition-.5)
|
|
duration=sum(float(n.end-n.start) for n in ns)
|
|
register=sum(float(n.end-n.start)*max(0,abs(n.pitch.number()-center)-10)/12 for n in ns)/max(duration,.001)
|
|
raw={'purpose':min(1,purpose),'continuity':min(1,continuity),'repetition':min(1,repetition),'register':min(1,register)}
|
|
contributions={k:raw[k]*weights[k] for k in weights}
|
|
return Evaluation('phrase-musical-1',valid,raw,weights,contributions,sum(contributions.values()) if valid else float('inf'),{'span':span,'attacks':len(ns),'sounding':sum(float(n.end-n.start) for n in ns)})
|