subsequence.definitions ======================= .. py:module:: subsequence.definitions .. autoapi-nested-parse:: Project definitions loader — a shared name-to-number vocabulary file. A music project can keep one small YAML file (any filename — ``project.yaml``, ``kit.yaml``) mapping human names to MIDI numbers, shared between Subsequence and the Subsample sampler. Both tools read the same file, so renumbering a sound or a controller is one edit and both follow on their next reload. The file format is the only contract — neither application depends on the other. The file is a flat mapping of sections, each mapping names to whole numbers: notes: ride_edge_soft: 53 dawn_chorus_pheasant: 60 cc: sampler_release: 21 channels: kit: 10 birds: 3 programs: brushes: 1 nrpn: filter_env_amount: 1042 Sections and value ranges (inclusive): - ``notes`` — MIDI note numbers, 0-127. - ``cc`` — controller numbers, 0-127. - ``channels`` — MIDI channels as musicians count them, 1-16. - ``programs`` — program-change numbers as sent on the wire (0-based), 0-127. - ``nrpn`` — 14-bit NRPN parameter numbers, 0-16383. Subsequence-specific; Subsample ignores this section. Names must match ``[a-z][a-z0-9_]*`` — lowercase letters, digits, underscores; no dots, no leading digit. Unknown top-level sections are silently ignored, so either tool can grow a new section without breaking the other. An empty file, an absent section, and a null section are all valid. Every failure raises ``ValueError`` naming the file, section, and offending entry — the same checks, in the same order, as Subsample applies, so a bad file fails the same way in both tools. Caveats worth knowing: - ``channels`` values are always user-facing 1-16 (the cross-tool contract). They pass straight into ``channel=`` under the default numbering; if your composition sets ``zero_indexed_channels=True``, subtract 1 yourself. - YAML silently collapses duplicate keys — the last duplicate wins, and neither tool can detect it. - Names resolve exactly as written (lowercase) in ``drum_note_map`` / ``cc_name_map`` lookups. Merging over a stock map, the later dict wins: ``{**gm_drums.GM_DRUM_MAP, **defs.notes}`` lets your names shadow GM ones. - A ``subsequence.load_definitions(...)`` call at the top of a watched file re-runs on every live reload, so the definitions re-read naturally. .. rubric:: Example .. code-block:: python import subsequence defs = subsequence.load_definitions("project.yaml") comp = subsequence.Composition(bpm=100) @comp.pattern(channel=defs.channels["birds"], drum_note_map=defs.notes, cc_name_map=defs.cc) def birds (b): b.hit("dawn_chorus_pheasant", beats=[0, 2.5]) b.cc("sampler_release", 64) Module Contents --------------- .. py:class:: Definitions The name-to-number tables read from a project definitions file. One plain ``dict`` per section, always present — an absent or null section is an empty dict. The dicts merge directly into the existing parameters: ``notes`` into ``drum_note_map=``, ``cc`` into ``cc_name_map=``, ``nrpn`` into ``nrpn_name_map=``, while ``channels`` values feed ``channel=`` and ``programs`` values feed ``p.program_change()``. The dataclass is frozen (attributes cannot be reassigned) but the dicts themselves are ordinary mutable dicts, so they can be merged and extended freely. .. rubric:: Example .. code-block:: python defs = subsequence.load_definitions("project.yaml") defs.channels["birds"] # 3 defs.notes # {"ride_edge_soft": 53, ...} .. py:function:: load_definitions(path: Union[str, pathlib.Path]) -> Definitions Load and validate a project definitions file. Reads the YAML file at ``path`` and returns a :class:`Definitions` whose ``notes`` / ``cc`` / ``channels`` / ``programs`` / ``nrpn`` dicts merge straight into pattern parameters. See the module docstring for the file format, the value ranges, and the shared-vocabulary contract with the Subsample sampler. Validation is strict inside the sections listed above and lenient outside them: unknown top-level sections are ignored, while a bad name, a non-whole number (including YAML ``true``/``false``), or an out-of-range value is rejected with an error naming the file, section, and entry. :param path: The definitions file, as a path string or ``pathlib.Path``. :returns: A :class:`Definitions` with one name-to-number dict per section. :raises ValueError: If the file is missing, unreadable, or not valid YAML; if the top level or a consumed section is not a mapping; or if a name or value inside a consumed section is invalid. File-system and YAML errors are wrapped so this is the only error type raised. .. rubric:: Example .. code-block:: python defs = subsequence.load_definitions("project.yaml") @comp.pattern(channel=defs.channels["kit"], drum_note_map=defs.notes) def kit (p): p.hit("ride_edge_soft", beats=[1, 3])