+19
-17
@@ -8,17 +8,18 @@ legacy fallback. A bounded restart handles an exhausted/entirely silent sample.
|
||||
from __future__ import annotations
|
||||
import collections
|
||||
import dataclasses
|
||||
import hashlib
|
||||
import random
|
||||
import math
|
||||
import fractions
|
||||
import hashlib
|
||||
import math
|
||||
import random
|
||||
|
||||
import generator.harmony
|
||||
import generator.model
|
||||
import generator.vocabulary
|
||||
import generator.solo.expressions
|
||||
import generator.solo.groups
|
||||
import generator.solo.learned
|
||||
import generator.solo.types
|
||||
import generator.harmony
|
||||
import generator.vocabulary
|
||||
|
||||
class SearchExhausted(ValueError):
|
||||
pass
|
||||
@@ -35,7 +36,8 @@ class State:
|
||||
def event_key(events): return tuple((a, b, -1 if p is None else p) for a, b, p in events)
|
||||
|
||||
class Decoder:
|
||||
def __init__(self, analysis, harmony, model, role, start, end, *, expressions_enabled=True, attempts=4, seed=''):
|
||||
def __init__(self, analysis, harmony, model, role, start, end, *, expressions_enabled=True, attempts=4, seed='', composition=None):
|
||||
self.composition = composition or generator.vocabulary.current()
|
||||
if type(attempts) is not int or not 1 <= attempts <= 16:
|
||||
raise ValueError('attempts must be between 1 and 16')
|
||||
self.analysis, self.harmony, self.model, self.role = analysis, harmony, model, role
|
||||
@@ -45,7 +47,7 @@ class Decoder:
|
||||
self.seed = seed or hashlib.sha256(repr((source, role, generator.solo.learned.VERSION)).encode()).hexdigest()
|
||||
self.expressions_enabled = expressions_enabled
|
||||
self.low, self.high = harmony.profile.register
|
||||
limits = generator.vocabulary.data('roles')['difficulty']
|
||||
limits = self.composition.data('roles')['difficulty']
|
||||
self.durations = tuple(d for d in model.durations if 0 < d <= 4 and d.denominator in (1, 2, 4) and
|
||||
(d.denominator != 4 or analysis.tempo <= limits['sixteenth_max_bpm']))
|
||||
self.allow_triplets = analysis.tempo <= limits['triplet_max_bpm']
|
||||
@@ -92,12 +94,12 @@ class Decoder:
|
||||
bases = []
|
||||
regions = [(None, n)]
|
||||
regions.extend((s.start, dataclasses.replace(n, start=s.start)) for s in self.harmony.spans
|
||||
if n.start < s.start < n.end and s.start - n.start <= fractions.Fraction(generator.vocabulary.data('harmony')['anticipation_max']))
|
||||
if n.start < s.start < n.end and s.start - n.start <= fractions.Fraction(self.composition.data('harmony')['anticipation_max']))
|
||||
for boundary, arrived in regions:
|
||||
exposed = [s for s, _, _ in self.harmony.segments(arrived) if n.pitch.number() % 12 not in s.roles.basic | s.roles.specified]
|
||||
if not exposed:
|
||||
continue
|
||||
for rule in generator.vocabulary.data('harmony')['colors']:
|
||||
for rule in self.composition.data('harmony')['colors']:
|
||||
if arrived.end - arrived.start > fractions.Fraction(rule['max_beats']):
|
||||
continue
|
||||
if any(s.chord == 'N.C.' or not generator.solo.expressions.eligible_color(rule, s, self.harmony) or
|
||||
@@ -139,7 +141,7 @@ class Decoder:
|
||||
if self.harmony.evaluate((n,), complete=True, expressions=(c,), infer=False).valid:
|
||||
return tuple([c])
|
||||
for arriving in self.harmony.spans:
|
||||
if n.start < arriving.start < n.end and arriving.start - n.start <= fractions.Fraction(generator.vocabulary.data('harmony')['anticipation_max']):
|
||||
if n.start < arriving.start < n.end and arriving.start - n.start <= fractions.Fraction(self.composition.data('harmony')['anticipation_max']):
|
||||
c = generator.solo.types.ExpressionContract(n.event_id + ':anticipation', 'anticipation', 'early_chord_tone', (n,), n, arriving.chord, arriving.start)
|
||||
if self.harmony.evaluate((n,), complete=True, expressions=(c,), infer=False).valid:
|
||||
return tuple([c])
|
||||
@@ -175,9 +177,9 @@ class Decoder:
|
||||
for rule, boundary in self.color_bases(n):
|
||||
if rule.get('resolve'):
|
||||
for gap in (fractions.Fraction(), fractions.Fraction(1, 2), fractions.Fraction(1)):
|
||||
if gap <= fractions.Fraction(generator.vocabulary.data('harmony')['resolution_gap']):
|
||||
if gap <= fractions.Fraction(self.composition.data('harmony')['resolution_gap']):
|
||||
requests.append(('color', rule, boundary, n.end + gap, (-2, -1, 1, 2)))
|
||||
delay = generator.vocabulary.data('lead_ins')['delayed_approach']
|
||||
delay = self.composition.data('lead_ins')['delayed_approach']
|
||||
if n.end - n.start <= fractions.Fraction(delay['max_note']) and n.pitch.number() % 12 not in self.harmony.permitted(n.start, n.end):
|
||||
for arrival in map(fractions.Fraction, delay['arrivals']):
|
||||
if n.end <= n.start + arrival <= n.start + fractions.Fraction(delay['deadline']):
|
||||
@@ -219,7 +221,7 @@ class Decoder:
|
||||
if self.expressions_enabled and not self.harmony.ordinary(n):
|
||||
for events, ns, contracts in self.resolutions(n):
|
||||
add(events, ns, contracts)
|
||||
rule = generator.vocabulary.data('lead_ins')['passing']
|
||||
rule = self.composition.data('lead_ins')['passing']
|
||||
if duration <= fractions.Fraction(rule['max_note']):
|
||||
for step in rule['steps']:
|
||||
p = number + step
|
||||
@@ -235,8 +237,8 @@ class Decoder:
|
||||
c = generator.solo.types.ExpressionContract(n.event_id + ':passing', 'passing', rule['id'], (n,), target, self.harmony.at(end).chord)
|
||||
add(((time, end, number), (end, end + hold, p)), (n, target), (c, *extra))
|
||||
if self.expressions_enabled:
|
||||
limits = generator.vocabulary.data('roles')['difficulty']
|
||||
for rule in generator.vocabulary.data('lead_ins')['patterns']:
|
||||
limits = self.composition.data('roles')['difficulty']
|
||||
for rule in self.composition.data('lead_ins')['patterns']:
|
||||
difficulty = rule.get('difficulty')
|
||||
if difficulty and self.analysis.tempo > limits[difficulty + '_max_bpm']:
|
||||
continue
|
||||
@@ -313,9 +315,9 @@ class Decoder:
|
||||
return None
|
||||
contracts = tuple(dataclasses.replace(c, preparation=state.notes[-1]) if c.kind in ('connection', 'passing') and c.preparation is None else c for c in contracts)
|
||||
if self.harmony.ordinary(state.notes[-1]):
|
||||
_, error = generator.solo.expressions.validate((state.notes[-1], *notes), contracts, self.harmony)
|
||||
_, error = generator.solo.expressions.validate((state.notes[-1], *notes), contracts, self.harmony, composition=self.composition)
|
||||
else:
|
||||
_, error = generator.solo.expressions.validate((*state.notes, *notes), (*state.contracts, *contracts), self.harmony)
|
||||
_, error = generator.solo.expressions.validate((*state.notes, *notes), (*state.contracts, *contracts), self.harmony, composition=self.composition)
|
||||
if error:
|
||||
return None
|
||||
history = state.history
|
||||
|
||||
Reference in New Issue
Block a user