@@ -5,9 +5,10 @@ Existing expression validators prove each candidate; deterministic set-cover
|
||||
search chooses a mutually consistent explanation, never edits the notes.
|
||||
"""
|
||||
import fractions
|
||||
import generator.vocabulary
|
||||
|
||||
import generator.solo.expressions
|
||||
import generator.solo.types
|
||||
import generator.vocabulary
|
||||
|
||||
def signature(c):
|
||||
return (c.kind, c.rule, tuple(n.event_id for n in c.events), c.target.event_id,
|
||||
@@ -20,7 +21,8 @@ def merge(*groups):
|
||||
result.setdefault(signature(c), c)
|
||||
return tuple(result.values())
|
||||
|
||||
def recognize(notes, harmony, provided=(), *, budget=4096):
|
||||
def recognize(notes, harmony, provided=(), *, budget=4096, composition=None):
|
||||
composition = composition or generator.vocabulary.current()
|
||||
required = {n.event_id for n in notes if not harmony.ordinary(n)}
|
||||
fixed = {n.event_id for c in provided for n in c.events}
|
||||
if required <= fixed:
|
||||
@@ -46,7 +48,7 @@ def recognize(notes, harmony, provided=(), *, budget=4096):
|
||||
if not (covered & required):
|
||||
return False
|
||||
combined = merge(provided, bundle)
|
||||
_, error = generator.solo.expressions.validate(notes, combined, harmony)
|
||||
_, error = generator.solo.expressions.validate(notes, combined, harmony, composition=composition)
|
||||
if error:
|
||||
return False
|
||||
candidates[tuple(signature(a) for a in bundle)] = (bundle, covered)
|
||||
@@ -57,11 +59,11 @@ def recognize(notes, harmony, provided=(), *, budget=4096):
|
||||
for i, n in enumerate(notes):
|
||||
if n.event_id not in required:
|
||||
continue
|
||||
future = [t for t in notes[i + 1:] if n.end <= t.start <= n.end + fractions.Fraction(generator.vocabulary.data('harmony')['resolution_gap'])
|
||||
future = [t for t in notes[i + 1:] if n.end <= t.start <= n.end + fractions.Fraction(composition.data('harmony')['resolution_gap'])
|
||||
and 0 < abs(t.pitch.number() - n.pitch.number()) <= 2]
|
||||
regions = [(None, harmony.at(n.start))]
|
||||
regions.extend((s.start, s) for s in harmony.spans if n.start < s.start < n.end
|
||||
and s.start - n.start <= fractions.Fraction(generator.vocabulary.data('harmony')['anticipation_max']))
|
||||
and s.start - n.start <= fractions.Fraction(composition.data('harmony')['anticipation_max']))
|
||||
for boundary, span in regions:
|
||||
if not span or span.chord == 'N.C.':
|
||||
continue
|
||||
@@ -69,7 +71,7 @@ def recognize(notes, harmony, provided=(), *, budget=4096):
|
||||
c = contract('anticipation', 'early_chord_tone', (n,), n, boundary)
|
||||
if add(c):
|
||||
single.setdefault(n.event_id, []).append((c,))
|
||||
for rule in generator.vocabulary.data('harmony')['colors']:
|
||||
for rule in composition.data('harmony')['colors']:
|
||||
if not generator.solo.expressions.eligible_color(rule, span, harmony) and boundary is not None:
|
||||
continue
|
||||
kind = 'anticipated_color' if boundary is not None else 'color'
|
||||
@@ -85,7 +87,7 @@ def recognize(notes, harmony, provided=(), *, budget=4096):
|
||||
target = notes[i + 1]
|
||||
for s in harmony.spans:
|
||||
if n.start < s.start < n.end:
|
||||
for rule in generator.vocabulary.data('harmony').get('suspensions', []):
|
||||
for rule in composition.data('harmony').get('suspensions', []):
|
||||
add(contract('suspension', rule['id'], (n,), target, s.start))
|
||||
elif s.start == n.end == target.start and n.pitch.number() == target.pitch.number():
|
||||
add(contract('anticipation', 'early_reattack', (n,), target, s.start))
|
||||
@@ -99,7 +101,7 @@ def recognize(notes, harmony, provided=(), *, budget=4096):
|
||||
targets.append((None, support))
|
||||
elif c.kind == 'anticipation':
|
||||
targets.append((c.boundary, support))
|
||||
for rule in generator.vocabulary.data('lead_ins')['patterns']:
|
||||
for rule in composition.data('lead_ins')['patterns']:
|
||||
size = len(rule['semitones'])
|
||||
if i < size:
|
||||
continue
|
||||
@@ -113,7 +115,7 @@ def recognize(notes, harmony, provided=(), *, budget=4096):
|
||||
add(contract(rule['kind'], rule['id'], ns, target, boundary, preparation), extras)
|
||||
# Delayed approaches may include real space or intervening valid music.
|
||||
# They still have an exact semitone target and a finite deadline.
|
||||
rule = generator.vocabulary.data('lead_ins')['delayed_approach']
|
||||
rule = composition.data('lead_ins')['delayed_approach']
|
||||
for n in reversed(notes[:i]):
|
||||
if target.start - n.start > fractions.Fraction(rule['deadline']):
|
||||
break
|
||||
@@ -124,8 +126,8 @@ def recognize(notes, harmony, provided=(), *, budget=4096):
|
||||
if n.event_id in required:
|
||||
for left in [(), *single.get(before.event_id, ())]:
|
||||
for right in [(), *single.get(target.event_id, ())]:
|
||||
add(contract('passing', generator.vocabulary.data('lead_ins')['passing']['id'], (n,), target, preparation=before), merge(left, right))
|
||||
for rule in generator.vocabulary.data('lead_ins')['blues']:
|
||||
add(contract('passing', composition.data('lead_ins')['passing']['id'], (n,), target, preparation=before), merge(left, right))
|
||||
for rule in composition.data('lead_ins')['blues']:
|
||||
add(contract('blues', rule['id'], (n,), target, preparation=before))
|
||||
options = {identifier: [] for identifier in sorted(required - fixed, key=lambda identifier: (by_id[identifier].start, identifier))}
|
||||
for bundle, covered in candidates.values():
|
||||
@@ -152,7 +154,7 @@ def recognize(notes, harmony, provided=(), *, budget=4096):
|
||||
identifier = min(remaining, key=lambda x: (len(options[x]), by_id[x].start, x))
|
||||
for bundle, added in options[identifier]:
|
||||
combined = merge(chosen, bundle)
|
||||
_, error = generator.solo.expressions.validate(notes, combined, harmony)
|
||||
_, error = generator.solo.expressions.validate(notes, combined, harmony, composition=composition)
|
||||
if error:
|
||||
continue
|
||||
result = solve(combined, covered | added)
|
||||
|
||||
Reference in New Issue
Block a user