This commit is contained in:
khodowany
2026-10-06 21:28:42 -05:00
parent e15dc31568
commit c7f6a30a13
30 changed files with 2957 additions and 490 deletions
+39 -20
View File
@@ -23,25 +23,44 @@ def _load(contents):
result = {}
for name, text in contents:
value = json.loads(text)
if value.get('version') != (3 if name == 'accompaniment' else 1) or not value.get('provenance') or not value.get('license'):
if value.get('version') != (4 if name == 'accompaniment' else 1) or not value.get('provenance') or not value.get('license'):
raise ValueError(f'{name}: unsupported or unprovenanced production vocabulary')
if name == 'accompaniment':
if fractions.Fraction(value['grid']) != fractions.Fraction(1, 2):
raise ValueError('accompaniment requires an eighth-note placement grid')
for part, lanes in (('electric', ('electric',)), ('acoustic', ('bass', 'upper'))):
signatures = {}
for part in ('electric', 'acoustic'):
policy = value['instruments'][part]
if fractions.Fraction(policy['attack_spacing']) < fractions.Fraction(1, 2):
raise ValueError('accompaniment attacks must be at least an eighth apart')
for lane in lanes:
layer = policy['layers'][lane]
if not 0 <= layer['attacks'][0] <= layer['attacks'][1] <= 3 or not layer['families']:
raise ValueError('invalid accompaniment attack range or duration families')
for family in layer['families']:
durations = policy['families'][family]
if not durations or any(fractions.Fraction(d) <= 0 for d in durations):
raise ValueError('accompaniment durations must be positive')
if part == 'acoustic' and any(fractions.Fraction(d) not in (fractions.Fraction(1, 2), 1) for d in durations):
raise ValueError('acoustic rhythm uses quarter and eighth notes')
identifiers = set()
signatures[part] = set()
for pattern in policy['patterns']:
if pattern['id'] in identifiers or not pattern['family']:
raise ValueError('duplicate or unnamed accompaniment pattern')
identifiers.add(pattern['id'])
lengths = tuple(map(fractions.Fraction, pattern['lengths']))
if not 1 <= len(lengths) <= 2 or any(n <= 0 for n in lengths):
raise ValueError('patterns require one or two positive measure lengths')
# Validate musical events before any measure projection or ties.
previous = fractions.Fraction()
onsets = []
for row in pattern['events']:
onset, duration = fractions.Fraction(row['onset']), fractions.Fraction(row['duration'])
if onset < previous or duration <= 0 or onset + duration > sum(lengths):
raise ValueError('overlapping or out-of-bounds accompaniment pattern')
if onset.denominator > 2 or duration.denominator > 2:
raise ValueError('comping vocabulary uses a quarter/eighth grid')
if part == 'acoustic':
if row['layer'] == 'combined':
if duration <= 1:
raise ValueError('combined acoustic sustain must exceed one beat')
elif row['layer'] not in ('bass', 'upper') or duration not in (fractions.Fraction(1, 2), 1):
raise ValueError('acoustic short attacks require bass/upper quarter/eighth notes')
elif row['layer'] != 'electric':
raise ValueError('electric pattern must contain electric chord attacks')
if row.get('anticipate') and row['layer'] not in ('upper', 'electric'):
raise ValueError('only upper/electric chords can anticipate')
previous = onset + duration
onsets.append(onset)
if len(lengths) == 2:
signatures[part].add((lengths, tuple(onsets)))
families = policy['voicing_families']
if not families.get('provenance'):
raise ValueError('guitar voicing families require provenance')
@@ -49,16 +68,16 @@ def _load(contents):
if set(families['rank']) != {'drop-two', 'close-position', 'compact-open', 'compact-doubled', 'three-note-shell'} or any(type(v) is not int or not 0 <= v <= 2 for v in families['rank'].values()):
raise ValueError('invalid electric family ranking')
else:
if set(map(fractions.Fraction, policy['allowed_durations'])) != {fractions.Fraction(1, 2), fractions.Fraction(1)} or policy['connect_holds']:
raise ValueError('acoustic rhythm must retain short quarter/eighth attacks')
for kind in ('triad', 'tetrad', 'extended'):
if set(families[kind]) != {'5/1', '5/5', '6/1', '6/5'}:
raise ValueError('acoustic families require root/fifth bass on both bass strings')
for orders in families[kind].values():
if not orders or any(len(o) != 3 or set(o) - {'root', 'fifth', 'guide', 'terminal', 'color'} for o in orders):
raise ValueError('invalid acoustic upper chord order')
if not 1 <= value['lane_candidates'] <= 64 or not 1 <= value['measure_candidates'] <= 64:
raise ValueError('accompaniment candidate budgets must be 1–64')
if signatures['acoustic'] & signatures['electric']:
raise ValueError('acoustic/electric two-bar onset patterns must be distinct')
if not 1 <= value['pattern_candidates'] <= 64:
raise ValueError('accompaniment pattern budget must be 1–64')
entries = value.get('cells', value.get('patterns', value.get('colors', value.get('transformations', []))))
if len({v['id'] for v in entries}) != len(entries):
raise ValueError(f'{name}: duplicate vocabulary ID')