Tab Generation
ci / checks (push) Successful in 1h19m14s

This commit is contained in:
khodowany
2026-09-20 16:20:43 -05:00
parent 68ded4d8c8
commit cf7c0c010c
423 changed files with 1422 additions and 1332 deletions
+20 -1
View File
@@ -202,6 +202,19 @@ def validate_document(document: dict) -> None:
raise ValueError('invalid unpitched display position')
else:
pitch_number(p)
if staves[0].get('tablature'):
from generator.guitar import MAX_FRET, MIN_FRET, STANDARD_TUNING
from generator.pitch import sounding_pitch
tab = p.get('tablature', {})
string, fret = tab.get('string'), tab.get('fret')
if type(string) is not int or type(fret) is not int or string not in dict(STANDARD_TUNING) or not MIN_FRET <= fret <= MAX_FRET:
raise ValueError(f'{p["id"]}: invalid generated tablature position')
state = dict(measure)
for change in measure.get('attributes', []):
if fraction(change['onset']) <= onset:
state.update(change)
if dict(STANDARD_TUNING)[string] + fret != sounding_pitch(p, state):
raise ValueError(f'{p["id"]}: tablature disagrees with sounding pitch')
pd = fraction(p['duration'])
if pd < 0 or (pd == 0 and not note.get('grace')) or pd > duration or not p['id'] or p['id'] in tones:
raise ValueError('invalid chord-tone duration or duplicate ID')
@@ -222,7 +235,7 @@ def validate_document(document: dict) -> None:
raise ValueError('key alterations must contain seven exact alterations')
for value in state['key']['alterations']:
alteration(value)
if 'clef' in state and (state['clef']['sign'] not in ('G', 'F', 'C', 'percussion', 'TAB', 'none') or not 1 <= state['clef']['line'] <= 6 or not -3 <= state['clef']['octave_change'] <= 3):
if 'clef' in state and (state['clef']['sign'] not in ('G', 'F', 'C', 'percussion', 'none') or not 1 <= state['clef']['line'] <= 6 or not -3 <= state['clef']['octave_change'] <= 3):
raise ValueError('invalid clef')
absolute += length
for identifier, (p, start, duration, voice) in tones.items():
@@ -233,6 +246,8 @@ def validate_document(document: dict) -> None:
same_pitch = all(p.get(k) == other.get(k) for k in ('unpitched', 'instrument')) if 'unpitched' in p else all(p.get(k) == other.get(k) for k in ('letter', 'alter', 'octave'))
if other.get('tie_from') != identifier or not same_pitch or next_voice != voice or next_start != start + duration:
raise ValueError(f'invalid tie: {identifier} -> {target}')
if staves[0].get('tablature') and p['tablature'] != other['tablature']:
raise ValueError(f'{identifier}: tied guitar tones must retain their string and fret')
if source := p.get('tie_from'):
if source not in tones or tones[source][0].get('tie_to') != identifier:
raise ValueError(f'dangling incoming tie: {identifier}')
@@ -250,6 +265,10 @@ def validate_document(document: dict) -> None:
raise ValueError('invalid tuplet appearance')
span_ids.add(span['id'])
for note in events.values():
for verse in note.get('lyrics', []):
start = tones[note['pitches'][0]['id']][1] if note['pitches'] else Fraction()
if 'extend_to' in verse and not start < fraction(verse['extend_to']) <= absolute:
raise ValueError('lyric extension endpoint is outside the score')
if any(t not in span_ids for t in note.get('tuplet_ids', [])):
raise ValueError('event refers to an unknown tuplet')
if tremolo := note.get('tremolo'):