Sepearate Generation
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
"""Versioned production vocabulary. This module never reads audition inputs."""
|
||||
from __future__ import annotations
|
||||
import contextlib
|
||||
import contextvars
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from fractions import Fraction as F
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1] / 'data' / 'composition'
|
||||
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')
|
||||
return _load(tuple((name, (Path(root)/(name+'.json')).read_text()) for name in names))
|
||||
|
||||
@functools.lru_cache(maxsize=16)
|
||||
def _load(contents):
|
||||
result = {}
|
||||
for name, text in contents:
|
||||
value = json.loads(text)
|
||||
if value.get('version') != 1 or not value.get('provenance') or not value.get('license'):
|
||||
raise ValueError(f'{name}: unsupported or unprovenanced production vocabulary')
|
||||
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')
|
||||
result[name] = value
|
||||
return result
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def _default():
|
||||
return {'data': load(), 'role': '', 'settings': {}}
|
||||
|
||||
def current():
|
||||
return ACTIVE.get() or _default()
|
||||
|
||||
def data(name):
|
||||
return current()['data'][name]
|
||||
|
||||
def role(name=None):
|
||||
active = current()
|
||||
name = name or active['role']
|
||||
return {**active['data']['roles']['roles'].get(name, {}), **active['settings']}
|
||||
|
||||
@contextlib.contextmanager
|
||||
def using(resources, name, settings=None):
|
||||
token = ACTIVE.set({'data': resources, 'role': name, 'settings': settings or {}})
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
ACTIVE.reset(token)
|
||||
|
||||
def fingerprint(value):
|
||||
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
|
||||
|
||||
def rhythm_cells(tempo):
|
||||
limits = data('roles')['difficulty']
|
||||
for cell in data('rhythms')['cells']:
|
||||
difficulty = cell.get('difficulty')
|
||||
if difficulty and tempo > limits[difficulty + '_max_bpm']:
|
||||
continue
|
||||
cursor, notes = F(), []
|
||||
for value in cell['values']:
|
||||
rest = value.startswith('r:')
|
||||
duration = F(value[2:] if rest else value)
|
||||
if duration <= 0:
|
||||
raise ValueError(cell['id'] + ': nonpositive rhythm duration')
|
||||
if not rest:
|
||||
notes.append((cursor, cursor + duration))
|
||||
cursor += duration
|
||||
if difficulty and len(notes) > limits['burst_attacks']:
|
||||
continue
|
||||
yield cell['id'], tuple(notes), cursor
|
||||
Reference in New Issue
Block a user