@@ -45,6 +45,7 @@ class Result:
|
||||
exposure:float
|
||||
pending:tuple=()
|
||||
error:str=''
|
||||
contracts:tuple=()
|
||||
|
||||
class Harmony:
|
||||
def __init__(self,spans,profile=None):
|
||||
@@ -77,12 +78,34 @@ class Harmony:
|
||||
if a.end!=n.start or n.end!=b.start:return ''
|
||||
left=n.pitch.number()-a.pitch.number();right=b.pitch.number()-n.pitch.number()
|
||||
return 'neighbor' if 0<abs(left)<=2 and left==-right else 'passing' if 0<abs(left)<=2 and 0<abs(right)<=2 and left*right>0 else ''
|
||||
def evaluate(self,notes,*,until=None,complete=False,expressions=()):
|
||||
@functools.lru_cache(maxsize=8192)
|
||||
def _ordinary(self,start,end,number):
|
||||
covered=start
|
||||
for s in self.spans:
|
||||
a,b=max(start,s.start),min(end,s.end)
|
||||
if a>=b:continue
|
||||
if a!=covered or s.chord=='N.C.':return False
|
||||
covered=b;pc=number%12
|
||||
if pc not in s.roles.basic|s.roles.specified and (pc not in s.pitches or end-start>=2):return False
|
||||
return covered==end and end>start
|
||||
def ordinary(self,n):return self._ordinary(n.start,n.end,n.pitch.number())
|
||||
@staticmethod
|
||||
def identified(n):
|
||||
return n if n.event_id else dataclasses.replace(n,event_id=f'event:{n.start}:{n.end}:{n.pitch.label()}')
|
||||
def evaluate(self,notes,*,until=None,complete=False,expressions=(),infer=True):
|
||||
from generator.solo.expressions import validate_frontier
|
||||
notes=types.sounding_notes(notes)
|
||||
notes=tuple(self.identified(n) for n in types.sounding_notes(notes))
|
||||
expressions=tuple(dataclasses.replace(c,events=tuple(self.identified(n) for n in c.events),
|
||||
target=self.identified(c.target),preparation=self.identified(c.preparation) if c.preparation else None) for c in expressions)
|
||||
frontier=until if until is not None else max((n.end for n in notes),default=F())
|
||||
permissions,pending,error=validate_frontier(notes,expressions,self,frontier,complete or until is None)
|
||||
if error:return Result(False,0,error=error)
|
||||
if infer and not pending and (complete or until is None) and any(not self.ordinary(n) and n.event_id not in permissions for n in notes):
|
||||
from generator.solo.recognition import recognize
|
||||
expressions,error=recognize(notes,self,expressions)
|
||||
if error:return Result(False,0,error=error)
|
||||
permissions,pending,error=validate_frontier(notes,expressions,self,frontier,True)
|
||||
if error:return Result(False,0,error=error)
|
||||
exposed=sounded=F()
|
||||
for n in notes:
|
||||
covered=n.start;duration=n.end-n.start;sounded+=duration
|
||||
@@ -93,14 +116,15 @@ class Harmony:
|
||||
pc=n.pitch.number()%12
|
||||
if pc in s.roles.basic|s.roles.specified or permission:continue
|
||||
if pc not in s.pitches:return Result(False,0,error=f'{n.event_id}: unsupported chromatic event')
|
||||
if duration>=2 and n.role!='quote':return Result(False,0,error=f'{n.event_id}: sustained nonchord tone needs an explicit treatment')
|
||||
if duration>=2:return Result(False,0,error=f'{n.event_id}: sustained nonchord tone needs an explicit treatment')
|
||||
exposed+=(b-a)/4
|
||||
if covered!=n.end:return Result(False,0,error=f'{n.event_id}: incomplete harmonic coverage')
|
||||
return Result(True,float(exposed/max(F(1,1000),sounded)),pending)
|
||||
return Result(True,float(exposed/max(F(1,1000),sounded)),pending,contracts=tuple(expressions))
|
||||
def describe(self,notes,expressions=()):
|
||||
from generator.solo.expressions import validate
|
||||
permissions,error=validate(notes,expressions,self)
|
||||
if error:raise ValueError(error)
|
||||
notes=tuple(self.identified(n) for n in types.sounding_notes(notes))
|
||||
checked=self.evaluate(notes,expressions=expressions,complete=True)
|
||||
if not checked.valid:raise ValueError(checked.error)
|
||||
permissions={n.event_id:c for c in checked.contracts for n in c.events}
|
||||
return [{'start':str(a),'end':str(b),'attack':str(n.start),'sounding_duration':str(n.end-n.start),
|
||||
'pitch':n.pitch.label(),'chord':s.chord,'function':self.function(n.pitch.number(),s),
|
||||
'expression':permissions[n.event_id].kind if n.event_id in permissions else '',
|
||||
|
||||
Reference in New Issue
Block a user