Comping Gen
This commit is contained in:
+30
-9
@@ -23,7 +23,7 @@ class VoicingCost(typing.NamedTuple):
|
||||
covering_tones: int = 0
|
||||
string_shortfall: int = 0
|
||||
family_penalty: int = 0
|
||||
effort: int = 0
|
||||
effort: fractions.Fraction = fractions.Fraction()
|
||||
missing_tones: int = 0
|
||||
def plus(self, other: VoicingCost) -> VoicingCost:
|
||||
return VoicingCost(*(a + b for a, b in zip(self, other)))
|
||||
@@ -31,18 +31,38 @@ class VoicingCost(typing.NamedTuple):
|
||||
def moved(self, distance: int) -> VoicingCost:
|
||||
return self._replace(effort=self.effort + distance)
|
||||
|
||||
def voicing_cost(positions: tuple[generator.fretboard.TabPosition, ...], profile: str = '', allowed: tuple[int, ...] = (), melody: tuple[int, ...] = ()) -> VoicingCost:
|
||||
families = tuple(generator.vocabulary.accompaniment('electric')['voicing_families']['rank'].items()) if profile == 'electric' else ()
|
||||
return _voicing_cost(positions, profile, allowed, melody, families)
|
||||
def voicing_preference(role, center=None):
|
||||
"""Immutable role settings for both selection and scored-retrieval caches."""
|
||||
settings = generator.vocabulary.role(role)
|
||||
policy = settings.get('voicing', {})
|
||||
center = settings.get('center', 53 if role.startswith('acoustic') else 60) if center is None else center
|
||||
result = tuple(fractions.Fraction(str(v)) for v in (center, policy.get('register_weight', 1), policy.get('high_position_weight', 1), policy.get('comfortable_max_fret', 9)))
|
||||
if result[1] < 0 or result[2] < 0 or result[3] < 1:
|
||||
raise ValueError('voicing preference requires nonnegative weights and a positive fret threshold')
|
||||
return result
|
||||
|
||||
@functools.lru_cache(maxsize=8192)
|
||||
def _voicing_cost(positions, profile, allowed, melody, families):
|
||||
def voicing_preferences(positions, preference):
|
||||
if not positions or not preference:
|
||||
return fractions.Fraction(), fractions.Fraction()
|
||||
center, register_weight, position_weight, threshold = preference
|
||||
register = abs(fractions.Fraction(sum(p.midi for p in positions), len(positions)) - center)
|
||||
position = max(fractions.Fraction(), fractions.Fraction(sum(p.fret for p in positions), len(positions)) - threshold)
|
||||
return register_weight * register, position_weight * position
|
||||
|
||||
def voicing_cost(positions: tuple[generator.fretboard.TabPosition, ...], profile: str = '', allowed: tuple[int, ...] = (), melody: tuple[int, ...] = ()) -> VoicingCost:
|
||||
families = tuple(generator.vocabulary.accompaniment('electric')['voicing_families']['rank'].items()) if profile == 'electric' else ()
|
||||
preference = voicing_preference('electric_melody') if profile == 'electric' and melody else ()
|
||||
return _voicing_cost(positions, profile, allowed, melody, families, preference)
|
||||
|
||||
@functools.lru_cache(maxsize=8192)
|
||||
def _voicing_cost(positions, profile, allowed, melody, families, preference):
|
||||
frets = {p.fret for p in positions}
|
||||
classes = {p.midi % 12 for p in positions}
|
||||
above = sum(p.midi > max(melody) for p in positions) if melody else 0
|
||||
span = compactness(positions)
|
||||
if profile == 'electric':
|
||||
effort = 2 * max(0, len(frets) - 1) + 3 * span
|
||||
effort = 2 * max(0, len(frets) - 1) + 3 * span + sum(voicing_preferences(positions, preference))
|
||||
else:
|
||||
effort = 2 * max(0, max(frets, default=0) - PREFERRED_MAX_FRET) + span
|
||||
family_penalty = dict(families).get(generator.chord_families.electric(positions), 0) if positions and families else 0
|
||||
@@ -51,10 +71,11 @@ def _voicing_cost(positions, profile, allowed, melody, families):
|
||||
|
||||
def chord_positions(requirements: generator.fretboard.ChordRequirements, prune: bool = True, profile: str = '', melody: tuple[int, ...] = ()) -> tuple[tuple[generator.fretboard.TabPosition, ...], ...]:
|
||||
families = tuple(generator.vocabulary.accompaniment('electric')['voicing_families']['rank'].items()) if profile == 'electric' else ()
|
||||
return _chord_positions(requirements, prune, profile, melody, families)
|
||||
preference = voicing_preference('electric_melody') if profile == 'electric' and melody else ()
|
||||
return _chord_positions(requirements, prune, profile, melody, families, preference)
|
||||
|
||||
@functools.lru_cache(maxsize=256)
|
||||
def _chord_positions(requirements, prune, profile, melody, families):
|
||||
def _chord_positions(requirements, prune, profile, melody, families, preference):
|
||||
result = []
|
||||
options = [tuple(generator.fretboard.TabPosition(string, fret, open_midi + fret) for fret in range(generator.fretboard.MIN_FRET, generator.fretboard.MAX_FRET + 1) if (open_midi + fret) % 12 in requirements.allowed or open_midi + fret in melody) for string, open_midi in generator.fretboard.STANDARD_TUNING]
|
||||
|
||||
@@ -90,7 +111,7 @@ def _chord_positions(requirements, prune, profile, melody, families):
|
||||
best = {}
|
||||
for positions in result:
|
||||
center = position_center(positions)
|
||||
rank = (voicing_cost(positions, profile, requirements.allowed, melody), positions)
|
||||
rank = (_voicing_cost(positions, profile, requirements.allowed, melody, families, preference), positions)
|
||||
if center not in best or rank < best[center][0]:
|
||||
best[center] = (rank, positions)
|
||||
return tuple(sorted(item[1] for item in best.values()))
|
||||
|
||||
Reference in New Issue
Block a user