58 lines
1.9 KiB
Python
58 lines
1.9 KiB
Python
"""Sounding-register references and hard solo instrument domains."""
|
|
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
import functools
|
|
from fractions import Fraction as F
|
|
|
|
import generator.fretboard
|
|
import generator.vocabulary
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
class Instrument:
|
|
part: str = ''
|
|
strings: tuple[int, ...] = (1, 2, 3, 4, 5, 6)
|
|
reference: int | None = None
|
|
melody_range: tuple[int, int] | None = None
|
|
|
|
@property
|
|
def register(self):
|
|
tuning = [p for s, p in generator.fretboard.STANDARD_TUNING if s in self.strings]
|
|
return min(tuning) + 1, max(tuning) + 12
|
|
|
|
@functools.lru_cache(maxsize=4096)
|
|
def permits(self, number):
|
|
return any(p.string_number in self.strings for p in generator.fretboard.single_positions_for_pitch(F(number)))
|
|
|
|
def cost(self, number):
|
|
return .5 * min(1., max(0, abs(number - self.reference) - 7) / 12) if self.reference is not None else 0.
|
|
|
|
|
|
UNRESTRICTED = Instrument()
|
|
|
|
|
|
def weighted_center(notes):
|
|
values = sorted((n.pitch.number(), n.end - n.start) for n in notes)
|
|
total = sum((d for _, d in values), F())
|
|
elapsed = F()
|
|
for number, duration in values:
|
|
elapsed += duration
|
|
if elapsed >= total / 2:
|
|
return number
|
|
return 60
|
|
|
|
|
|
def for_part(part, analysis=None):
|
|
if part not in ('acoustic', 'electric'):
|
|
raise ValueError('unknown solo instrument: ' + part)
|
|
strings = (1, 2, 3, 4, 5, 6)
|
|
if analysis is None or not analysis.melody:
|
|
return Instrument(part, strings)
|
|
# Source identity is independent of sounding register. Roles have soft,
|
|
# overlapping centers; all legal positions remain available to both guitars.
|
|
center=generator.vocabulary.role(part+'_solo').get('register_center',57 if part=='acoustic' else 62)
|
|
notes=tuple(n for n in analysis.melody if n.end>analysis.body_offset)
|
|
return Instrument(part,strings,center,
|
|
(min(n.pitch.number() for n in notes),max(n.pitch.number() for n in notes)))
|