Fret Limit
This commit is contained in:
@@ -11,6 +11,7 @@ import generator.pitch
|
||||
STANDARD_TUNING = ((6, 40), (5, 45), (4, 50), (3, 55), (2, 59), (1, 64))
|
||||
MIN_FRET = 1
|
||||
MAX_FRET = 20
|
||||
MAX_SINGLE_FRET = 12
|
||||
MAX_FRET_SPAN = 3
|
||||
MAX_ANCHOR_FRET = 12
|
||||
WRITTEN_GUITAR_OCTAVE_OFFSET = 12
|
||||
@@ -33,6 +34,48 @@ def positions_for_pitch(midi: fractions.Fraction) -> tuple[TabPosition, ...]:
|
||||
return ()
|
||||
return tuple(TabPosition(string, int(midi - open_midi), int(midi)) for string, open_midi in STANDARD_TUNING if MIN_FRET <= midi - open_midi <= MAX_FRET)
|
||||
|
||||
def single_positions_for_pitch(midi: fractions.Fraction) -> tuple[TabPosition, ...]:
|
||||
return tuple(p for p in positions_for_pitch(midi) if p.fret <= MAX_SINGLE_FRET)
|
||||
|
||||
def single_register(register: tuple[int, int]) -> tuple[int, int]:
|
||||
return max(register[0], min(p + MIN_FRET for _, p in STANDARD_TUNING)), min(register[1], max(p + MAX_SINGLE_FRET for _, p in STANDARD_TUNING))
|
||||
|
||||
def playable_attack(tones) -> bool:
|
||||
"""Check a played note/chord, including tails after shorter tones release.
|
||||
|
||||
Each item is a (position, sounding duration) pair. Independent parts and
|
||||
unsounded members of an underlying grip cannot supply the chord's anchor.
|
||||
"""
|
||||
tones = tuple(tones)
|
||||
for time in {0, *(duration for _, duration in tones if duration > 0)}:
|
||||
positions = tuple(p for p, duration in tones if duration > time or time == duration == 0)
|
||||
if not positions:
|
||||
continue
|
||||
if any(p not in positions_for_pitch(fractions.Fraction(p.midi)) for p in positions) or len({p.string_number for p in positions}) != len(positions) or not within_fret_span(positions):
|
||||
return False
|
||||
if len(positions) == 1:
|
||||
if positions[0].fret > MAX_SINGLE_FRET:
|
||||
return False
|
||||
elif not has_fret_anchor(positions):
|
||||
return False
|
||||
return True
|
||||
|
||||
def validate_document(document: dict) -> None:
|
||||
"""Validate generated guitar policy separately from the score wire format."""
|
||||
for staff in document['staves']:
|
||||
if not staff.get('tablature'):
|
||||
continue
|
||||
for measure in staff['measures']:
|
||||
for voice in measure['voices']:
|
||||
for note in voice['notes']:
|
||||
tones = []
|
||||
for pitch in note['pitches']:
|
||||
tab = pitch['tablature']
|
||||
position = TabPosition(tab['string'], tab['fret'], dict(STANDARD_TUNING)[tab['string']] + tab['fret'])
|
||||
tones.append((position, generator.model.fraction(pitch['duration'])))
|
||||
if not playable_attack(tones):
|
||||
raise ValueError(f'{measure["id"]}, event {note["id"]}: single notes require frets {MIN_FRET}-{MAX_SINGLE_FRET}; each sounding chord requires an anchor in {MIN_FRET}-{MAX_ANCHOR_FRET} and at most {MAX_FRET_SPAN} consecutive fret positions, including held/tied tails')
|
||||
|
||||
def validate_tuning(state: dict, location: str) -> None:
|
||||
details = state['staff_details']
|
||||
if details.get('capo', 0):
|
||||
|
||||
Reference in New Issue
Block a user