Chord Families
This commit is contained in:
+21
-5
@@ -10,14 +10,18 @@ import typing
|
||||
import generator.fretboard
|
||||
import generator.model
|
||||
import generator.pitch
|
||||
import generator.chord_families
|
||||
import generator.vocabulary
|
||||
|
||||
PREFERRED_MAX_FRET = 12
|
||||
ELECTRIC_STRINGS = ((3, 2, 1), (4, 3, 2, 1))
|
||||
|
||||
class VoicingCost(typing.NamedTuple):
|
||||
string_shortfall: int = 0
|
||||
melody_string: int = 0
|
||||
covered_melody: int = 0
|
||||
covering_tones: int = 0
|
||||
string_shortfall: int = 0
|
||||
family_penalty: int = 0
|
||||
effort: int = 0
|
||||
missing_tones: int = 0
|
||||
def plus(self, other: VoicingCost) -> VoicingCost:
|
||||
@@ -26,8 +30,12 @@ class VoicingCost(typing.NamedTuple):
|
||||
def moved(self, distance: int) -> VoicingCost:
|
||||
return self._replace(effort=self.effort + distance)
|
||||
|
||||
@functools.lru_cache(maxsize=8192)
|
||||
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)
|
||||
|
||||
@functools.lru_cache(maxsize=8192)
|
||||
def _voicing_cost(positions, profile, allowed, melody, families):
|
||||
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
|
||||
@@ -36,10 +44,16 @@ def voicing_cost(positions: tuple[generator.fretboard.TabPosition, ...], profile
|
||||
effort = 2 * max(0, len(frets) - 1) + 3 * span
|
||||
else:
|
||||
effort = 2 * max(0, max(frets, default=0) - PREFERRED_MAX_FRET) + span
|
||||
return VoicingCost(max(0, 4 - len(positions)) if profile == 'electric' else 0, int(bool(above)), above, effort, len(set(allowed) - classes))
|
||||
family_penalty = dict(families).get(generator.chord_families.electric(positions), 0) if positions and families else 0
|
||||
melody_string = min((p.string_number - 1 for p in positions if melody and p.midi == max(melody)), default=0)
|
||||
return VoicingCost(melody_string=melody_string, string_shortfall=max(0, 4 - len(positions)) if profile == 'electric' else 0, covered_melody=int(bool(above)), covering_tones=above, family_penalty=family_penalty, effort=effort, missing_tones=len(set(allowed) - classes))
|
||||
|
||||
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)
|
||||
|
||||
@functools.lru_cache(maxsize=256)
|
||||
def chord_positions(requirements: generator.fretboard.ChordRequirements, prune: bool = True, profile: str = '', melody: tuple[int, ...] = ()) -> tuple[tuple[generator.fretboard.TabPosition, ...], ...]:
|
||||
def _chord_positions(requirements, prune, profile, melody, families):
|
||||
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]
|
||||
|
||||
@@ -57,7 +71,9 @@ def chord_positions(requirements: generator.fretboard.ChordRequirements, prune:
|
||||
|
||||
if profile:
|
||||
by_string = {string: options[i] for i, (string, _) in enumerate(generator.fretboard.STANDARD_TUNING)}
|
||||
for strings in ELECTRIC_STRINGS:
|
||||
# A lower source melody can be the top of a three-note adaptation on
|
||||
# strings 2-4. It need not be buried beneath an added first-string tone.
|
||||
for strings in (*ELECTRIC_STRINGS, (4, 3, 2)) if melody else ELECTRIC_STRINGS:
|
||||
for selected in itertools.product(*(by_string[s] for s in strings)):
|
||||
if not generator.fretboard.within_fret_span(selected) or not generator.fretboard.has_fret_anchor(selected) or not requirements.covers({p.midi % 12 for p in selected}) or not set(melody) <= {p.midi for p in selected}:
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user