# Resonance Sound Pack Architecture > Self-describing sound packs that extend the creature classifier. ## Overview Resonance uses a **plug-in architecture** for sound packs. Each pack is a self-contained overlay that declares what creature subtypes it handles and provides the sound files for those creatures. When installed, the pack extends the module's creature vocabulary. When removed, creatures gracefully degrade to generic type-level sounds. No Resonance code changes are required for new packs. ## How it works ``` Actor → Classifier → MonsterVocalMap → SoundResolver → Audio Playback ↑ classifierBindings from pack manifest ``` 1. **Classifier** (`ionrift-library/creatureClassifier.js`) analyses a Foundry actor's type, traits, and description. Returns `{ type, subtype, sound }`. 2. **MonsterVocalMap** (`data/maps/MonsterVocalMap.js`) routes the classifier output to a sound event key. Checks: - Dynamic bindings from pack manifests (highest priority) - Static `SUBTYPE_VOCAL_MAP` (built-in creature types) - Prefix fallback (`beast_canine_domestic` → `beast_canine`) 3. **SoundResolver** resolves the key to an audio clip path from the merged binding pool (all enabled packs contribute). 4. If no binding exists for the key, the fallback chain fires: `MONSTER_VAMPIRE` → `MONSTER_UNDEAD` → `CORE_MONSTER_PAIN` ## Pack manifest contract A sound pack lives in the overlay directory: ``` ionrift-data/overlays/ionrift-resonance/{sublayer}/ ├── manifest.json (required) ├── bindings.json (required) ├── overlay-manifest.json (required for Patreon Library overlays) └── sounds/ (audio files) ``` ### manifest.json ```json { "id": "ionrift-soundpack-bone-dust", "packId": "ionrift-soundpack-bone-dust", "name": "Bone & Dust Creature SFX", "version": "1.0.0", "author": "Ionrift", "description": "Creature vocal expansion for catacombs and ruins.", "systems": ["dnd5e", "pf2e", "daggerheart"], "tier": "Initiate", "packType": "sfx", "format": "zip", "contentTypes": [".mp3"], "classifierBindings": { "undead_vampire": "MONSTER_VAMPIRE", "undead_mummy": "MONSTER_MUMMY" } } ``` ### classifierBindings (the plug-in contract) The `classifierBindings` field maps classifier subtype composite keys to sound event keys. When SoundPackLoader loads the pack, it registers these mappings in its dynamic classifier registry. MonsterVocalMap checks this registry before the static map. **Key format:** `{type}_{subtype}` — matches the composite key built from the classifier's `type` and `subtype` fields. **Value format:** Sound event key string (e.g. `MONSTER_VAMPIRE`). Must match a key in the pack's `bindings.json`. ### bindings.json Standard Resonance binding format. Each key maps to an array of sound clip objects: ```json { "version": "1.0", "bindings": { "MONSTER_VAMPIRE": [ { "id": "sounds/monsters/vampire/MONSTER_VAMPIRE_01.mp3", "name": "Vampire (01)", "type": "local" }, { "id": "sounds/monsters/vampire/MONSTER_VAMPIRE_02.mp3", "name": "Vampire (02)", "type": "local" } ] } } ``` Paths are relative to the pack root. SoundPackLoader resolves them to full paths automatically. ## Graceful degradation When a pack is **not installed**, the classifier still produces the subtype (e.g. `undead_vampire`). The resolution chain: 1. Dynamic classifier binding → empty (no pack) → skip 2. Static SUBTYPE_VOCAL_MAP → no entry for `undead_vampire` → skip 3. `classification.sound` → `MONSTER_VAMPIRE` (from classifier) → no binding → skip 4. `MONSTER_UNDEAD` → core pack has binding → **plays generic undead** When the pack **is installed**: 1. Dynamic classifier binding → `MONSTER_VAMPIRE` → binding found → **plays vampire clip** ## Attack and Spell Attack Bindings Packs can ship per-monster attack vocalizations alongside pain/death vocals. The `attackBindings` field in `manifest.json` declares: - **`attack`** — Phase 1 (ASK) sound key for basic/melee attacks. Played when the creature uses a non-spell item. Takes priority over the synthesised composite key (`*_BITE`, `*_CLAW`, etc.), giving packs explicit control over what "attacking" sounds like for their creature. - **`spellAttacks`** — ordered list of spell matchers. Only consulted when the item is a spell and the actor is an NPC with a recognized creature classification. First matching entry wins. ### attackBindings schema ```json "attackBindings": { "MONSTER_RAT": { "attack": "MONSTER_RAT_ATTACK" }, "MONSTER_LICH": { "attack": "MONSTER_LICH_ATTACK", "spellAttacks": [ { "key": "MONSTER_LICH_SPELL_TOUCH", "match": { "school": ["nec"], "delivery": "touch" }, "overrideSpellEffect": true }, { "key": "MONSTER_LICH_SPELL_CAST", "match": {}, "overrideSpellEffect": false } ] } } ``` ### spellAttacks matcher fields | Field | Type | Description | |---|---|---| | `key` | `string` | Sound event key to play when this matcher hits. Must have a binding in `bindings.json`. | | `match` | `object` | Criteria object. All specified fields must match (AND). Empty `{}` = catch-all. | | `match.school` | `string[]` | Spell school abbreviations (`nec`, `evo`, `abj`, `div`, `con`, `enc`, `ill`, `tra`). | | `match.delivery` | `string` | `"touch"` · `"ranged"` · `"save"`. System-neutral abstraction over action type. | | `overrideSpellEffect` | `boolean` | See below. | ### delivery abstraction `delivery` maps to the system's action type so pack manifests stay system-neutral: | Delivery | dnd5e actionType | Meaning | |---|---|---| | `"touch"` | `msak` | Melee spell attack (Vampiric Touch, Shocking Grasp) | | `"ranged"` | `rsak` | Ranged spell attack (Fire Bolt, Ray of Frost) | | `"save"` | `save` | Save-based (Fireball, Hold Person) | ### overrideSpellEffect behaviour Controls what happens in Phase 1 (ASK) when a spell is cast: **`overrideSpellEffect: true`** — Monster vocal *replaces* the spell effect sound. Use for close-range spells where the creature IS the effect (e.g. Vampiric Touch — the lich's dark hand is the whole event, no separate spell blast sound makes sense). ``` Lich casts Vampiric Touch: MONSTER_LICH_SPELL_TOUCH plays. No SPELL_VOID. ``` **`overrideSpellEffect: false`** — Monster vocal plays first, then the spell effect sound follows with a short stagger (default 250ms, configurable via `MONSTER_SPELL_EFFECT_DELAY` orchestrator offset). Use for ranged or area spells where the creature vocalizes an incantation and the spell effect is a separate event. ``` Lich casts Fireball: MONSTER_LICH_SPELL_CAST plays, then SPELL_FIRE 250ms later. ``` **No match** — If no spell matcher hits (or the matched key has no audio binding), the standard spell school flow fires unchanged. ### Sound file conventions for attack/spell clips - Directory: `sounds/monsters/{creature}/` - Naming: - Basic attacks: `MONSTER_{TYPE}_ATTACK_{NN}.mp3` - Spell vocalizations: `MONSTER_{TYPE}_SPELL_{NN}.mp3` or `MONSTER_{TYPE}_SPELL_{VARIANT}_{NN}.mp3` - Duration: 0.5–2.5 seconds (slightly longer than pain vocals — attack/cast buildup) - Count: 3 clips minimum for pool variety ## Sound file conventions - Directory: `sounds/monsters/{creature}/` - Naming: `MONSTER_{TYPE}_{NN}.mp3` (zero-padded, e.g. `01`, `02`) - Format: MP3, 44.1kHz, mono or stereo - Duration: 0.5–2.0 seconds (vocal stingers, not loops) - Count: 4–5 clips per creature for pool variety ## Adding a new creature pack 1. **Check the classifier** — does it already produce a subtype for your creature? Check `dnd5eData.js` and `classifierData.js`. If not, add the subtype (kernel change, requires lib version bump). 2. **Create the pack directory** in the workshop: ``` ionrift-pack-workshop/packs/resonance/standard/{pack-name}/ ``` 3. **Write manifest.json** with `classifierBindings` mapping your subtypes to sound keys. 4. **Write bindings.json** with sound clip entries for each key. 5. **Add sound files** to `sounds/monsters/{creature}/`. 6. **Test** — create a token with the creature name, attack it, verify the correct vocal plays. Disable the overlay, verify it falls back to the parent type sound. ## ElevenLabs compliance This architecture demonstrates that sounds are functional parameters of an automated classification system, not a browsable library: 1. **No user selection.** The classifier analyses the actor automatically. 2. **Gameplay-triggered.** Sounds fire on attack/damage hooks. 3. **Packs extend classification.** They refine the system's output fidelity, not provide standalone content. 4. **No standalone value.** Sound files are keyed to binding IDs and cannot be browsed or used outside Resonance's pipeline.