Revamp
This commit is contained in:
+31
-4
@@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
import contextlib
|
||||
import contextvars
|
||||
import dataclasses
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
@@ -14,7 +15,7 @@ ACTIVE = contextvars.ContextVar('composition_vocabulary', default=None)
|
||||
def load(root=ROOT):
|
||||
# Content, not just the directory, keys memoization. Long-lived individual
|
||||
# role builds must observe edited production data without restarting Python.
|
||||
names = ('roles', 'grammar', 'harmony', 'lead_ins', 'rhythms')
|
||||
names = ('roles', 'grammar', 'harmony', 'lead_ins', 'rhythms', 'melodic_vocabulary')
|
||||
return _load(tuple((name, (Path(root)/(name+'.json')).read_text()) for name in names))
|
||||
|
||||
@functools.lru_cache(maxsize=16)
|
||||
@@ -28,6 +29,21 @@ def _load(contents):
|
||||
if len({v['id'] for v in entries}) != len(entries):
|
||||
raise ValueError(f'{name}: duplicate vocabulary ID')
|
||||
result[name] = value
|
||||
cells=result['rhythms']['cells']+result['melodic_vocabulary']['cells']
|
||||
if len({c['id'] for c in cells})!=len(cells):
|
||||
raise ValueError('Duplicate rhythmic identity across production vocabularies')
|
||||
for c in cells:
|
||||
if c['purpose'] not in ('entrance','continue','connect','answer','arrival'):
|
||||
raise ValueError(c['id']+': unknown rhythmic purpose')
|
||||
paths=result['melodic_vocabulary']['chord_paths']
|
||||
if len({p['id'] for p in paths})!=len(paths):raise ValueError('Duplicate chord-path operator')
|
||||
for p in paths:
|
||||
if p['direction'] not in ('up','down','toward') or p['members'] not in ('basic','defining'):
|
||||
raise ValueError(p['id']+': invalid chord traversal')
|
||||
if not p['member_steps'] or any(type(s)!=int or not 1<=abs(s)<=3 for s in p['member_steps']):
|
||||
raise ValueError(p['id']+': invalid chord-member step')
|
||||
if not p['prefix_attacks'] or any(n not in (2,3,4) for n in p['prefix_attacks']) or not 1<=p['max_interval']<=12:
|
||||
raise ValueError(p['id']+': unsupported bounded chord prefix')
|
||||
return result
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
@@ -56,9 +72,20 @@ def using(resources, name, settings=None):
|
||||
def fingerprint(value):
|
||||
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
|
||||
|
||||
def rhythm_cells(tempo):
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class RhythmCell:
|
||||
id: str
|
||||
pattern: tuple
|
||||
duration: F
|
||||
purpose: str
|
||||
difficulty: str | None = None
|
||||
|
||||
|
||||
def rhythm_cells(tempo, purpose=None):
|
||||
limits = data('roles')['difficulty']
|
||||
for cell in data('rhythms')['cells']:
|
||||
for cell in (*data('rhythms')['cells'],*data('melodic_vocabulary')['cells']):
|
||||
if purpose is not None and cell['purpose'] != purpose:
|
||||
continue
|
||||
difficulty = cell.get('difficulty')
|
||||
if difficulty and tempo > limits[difficulty + '_max_bpm']:
|
||||
continue
|
||||
@@ -73,4 +100,4 @@ def rhythm_cells(tempo):
|
||||
cursor += duration
|
||||
if difficulty and len(notes) > limits['burst_attacks']:
|
||||
continue
|
||||
yield cell['id'], tuple(notes), cursor
|
||||
yield RhythmCell(cell['id'], tuple(notes), cursor, cell['purpose'], difficulty)
|
||||
|
||||
Reference in New Issue
Block a user