subsequence.definitions¶
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:
channelsvalues are always user-facing 1-16 (the cross-tool contract). They pass straight intochannel=under the default numbering; if your composition setszero_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_maplookups. 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.
Example
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¶
- class subsequence.definitions.Definitions[source]¶
The name-to-number tables read from a project definitions file.
One plain
dictper section, always present — an absent or null section is an empty dict. The dicts merge directly into the existing parameters:notesintodrum_note_map=,ccintocc_name_map=,nrpnintonrpn_name_map=, whilechannelsvalues feedchannel=andprogramsvalues feedp.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.
Example
defs = subsequence.load_definitions("project.yaml") defs.channels["birds"] # 3 defs.notes # {"ride_edge_soft": 53, ...}
- subsequence.definitions.load_definitions(path: str | pathlib.Path) Definitions[source]¶
Load and validate a project definitions file.
Reads the YAML file at
pathand returns aDefinitionswhosenotes/cc/channels/programs/nrpndicts 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.- Parameters:
path – The definitions file, as a path string or
pathlib.Path.- Returns:
A
Definitionswith 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.
Example
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])