+39
-9
@@ -2,12 +2,14 @@
|
||||
from __future__ import annotations
|
||||
import contextlib
|
||||
import contextvars
|
||||
import copy
|
||||
import dataclasses
|
||||
import fractions
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
import pathlib
|
||||
import fractions
|
||||
import types
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1] / 'data' / 'composition'
|
||||
ACTIVE = contextvars.ContextVar('composition_vocabulary', default=None)
|
||||
@@ -16,7 +18,7 @@ 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', 'harmony', 'lead_ins', 'accompaniment')
|
||||
return _load(tuple((name, (pathlib.Path(root) / (name + '.json')).read_text()) for name in names))
|
||||
return copy.deepcopy(_load(tuple((name, (pathlib.Path(root) / (name + '.json')).read_text()) for name in names)))
|
||||
|
||||
@functools.lru_cache(maxsize=16)
|
||||
def _load(contents):
|
||||
@@ -105,26 +107,54 @@ def _load(contents):
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def _default():
|
||||
return {'data': load(), 'role': '', 'settings': {}}
|
||||
return CompositionContext.create(load())
|
||||
|
||||
def _freeze(value):
|
||||
if isinstance(value, dict):
|
||||
return types.MappingProxyType({key: _freeze(item) for key, item in value.items()})
|
||||
if isinstance(value, list):
|
||||
return tuple(_freeze(item) for item in value)
|
||||
return value
|
||||
|
||||
@functools.lru_cache(maxsize=32)
|
||||
def _frozen_json(contents):
|
||||
return _freeze(json.loads(contents))
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class CompositionContext:
|
||||
"""Owned, immutable, pickleable policy inputs with stable iteration order."""
|
||||
resources_json: str
|
||||
name: str = ''
|
||||
settings_json: str = '{}'
|
||||
@classmethod
|
||||
def create(cls, resources, name='', settings=None):
|
||||
return cls(json.dumps(resources), name, json.dumps(settings or {}))
|
||||
def data(self, name):
|
||||
return _frozen_json(self.resources_json)[name]
|
||||
def role(self, name=None):
|
||||
return {**self.data('roles')['roles'].get(name or self.name, {}), **_frozen_json(self.settings_json)}
|
||||
def accompaniment(self, part):
|
||||
value = self.data('accompaniment')
|
||||
return {**{key: item for key, item in value.items() if key != 'instruments'}, **value['instruments'][part]}
|
||||
|
||||
def current():
|
||||
return ACTIVE.get() or _default()
|
||||
|
||||
def data(name):
|
||||
return current()['data'][name]
|
||||
return current().data(name)
|
||||
|
||||
def accompaniment(part, resources=None):
|
||||
value = (resources or current()['data'])['accompaniment']
|
||||
if resources is None:
|
||||
return current().accompaniment(part)
|
||||
value = resources['accompaniment']
|
||||
return {**{k: v for k, v in value.items() if k != 'instruments'}, **value['instruments'][part]}
|
||||
|
||||
def role(name=None):
|
||||
active = current()
|
||||
name = name or active['role']
|
||||
return {**active['data']['roles']['roles'].get(name, {}), **active['settings']}
|
||||
return current().role(name)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def using(resources, name, settings=None):
|
||||
token = ACTIVE.set({'data': resources, 'role': name, 'settings': settings or {}})
|
||||
token = ACTIVE.set(CompositionContext.create(resources, name, settings))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user