Appendix B — The Sample Instrument Manifest
This is the reference for the instrument format Chapter 5 builds: a JSON list
and a folder of WAV files. It’s the input to music_engine.ZoneSampler, which
is also available under the historical name ExsSampler so older scripts keep
working. Neither name grants rights to anybody’s sample library — the teaching
workflow generates its own recordings for exactly that reason.
Folder layout
instrument/
manifest.json
provenance.json
tone-48-soft.wav
tone-48-bright.wav
tone-60-soft.wav
tone-60-bright.wav
tone-72-soft.wav
tone-72-bright.wav
Run python scripts/generate_sampler_demo.py to create this under
Tracks/sampler-demo/. All six sounds are synthesized locally.
Zone fields
manifest.json is a list of objects:
[
{
"name": "Tone 60 soft",
"root": 60,
"keylo": 54,
"keyhi": 65,
"vello": 1,
"velhi": 79,
"group": "Synth tones",
"file": "tone-60-soft.wav"
}
]
The real demonstration has six of these; the one above is abbreviated, so it doesn’t show full keyboard and velocity coverage.
| Field | Meaning |
|---|---|
name |
Descriptive zone label |
root |
MIDI pitch recorded in the WAV |
keylo, keyhi |
Inclusive MIDI note range selecting this zone |
vello, velhi |
Inclusive velocity range selecting this zone |
group |
Optional articulation label, usable for group filtering |
file |
WAV filename relative to the instrument directory |
Use MIDI notes 0–127 and note-on velocities from 1 to 127. A note-on with velocity zero is treated as a note-off, a MIDI convention that predates most of us and isn’t going anywhere.
Aim for complete coverage of the notes and velocities your score actually uses. The player does have nearest-root and velocity fallbacks, and they will keep you from silence — but a fallback is a rescue, not a mapping decision, and it tends to sound like one.
If several zones match a note, deterministic mode selects the first; otherwise the engine picks among them with its random generator, so supply a seeded generator when you want repeatable performances. Group filters match substrings in group labels, which means labels that accidentally contain one another will accidentally match. Name them apart.
Audio and event conventions
WAV input may be integer PCM or floating point, and source sample rates are
converted to the rendering rate. Stereo preservation is optional:
stereo_output=True returns two channels in channel-by-frame order. SciPy
wants frame-by-channel data when writing a stereo WAV, so transpose the
rendered array before you write it — this is the same trap as Chapter 3’s, from
the other direction.
Events take the form (sample_position, kind, channel, note, value), where
kind is on or off. An optional sixth value identifies a voice. Without
explicit voice IDs, overlapping notes on the same channel and pitch are matched
first-in, first-out. Negative event times are rejected outright.
Two things this player does not do. It reads each zone’s WAV as the whole
source, ignoring the start and end offsets that older container formats
used — so prepare each recording as its own trimmed WAV file. And it implements
no sustain-loop metadata, which means a note can’t be held longer than its
recording.
Provenance
provenance.json is a companion record, not something the sampler reads. The
example stores its synthesis method, sample rate and WAV hashes. For external
recordings, keep the source and permission records alongside as well — a
permissive code licence has nothing to say about the terms governing somebody
else’s recordings, however tidy it would be if it did.