Config Reference¶
Every table and key in the config — the base Data\SKSE\Plugins\AudioUtil\AudioUtil.toml and any AudioUtil\config\*.toml overlays, which are merged into one effective config. Defaults shown are the DLL's internal defaults, applied when a key is absent from every file. All keys/names are normalized (case- and space-insensitive).
Paths written 'Sound\...' are Data-relative. Single-quoted TOML literal strings are used throughout so backslashes need no escaping.
[general]¶
Base-only section
[general] (along with [ppa], the [lipsync] scalar tuning, [captions], and the [gag] enable/default_category toggles) is a global section, read only from the base AudioUtil.toml. If a config\*.toml overlay sets any of these, it is ignored with a warning in AudioUtil.log. The additive exceptions inside those tables — [lipsync] block_categories and [gag] keywords/items — do merge from every file. See the merge rules.
[general]
log_level = "info" # trace | debug | info | warn | error
sound_flags = 0x1A # BuildSoundDataFromFile flags
sound_priority = 128
default_female_slot = "F0"
default_male_slot = "M0"
default_creature_slot = "C0" # unrouted creatures; "" = silent (never borrow a human slot)
pc_female_slot = "" # reserved player slot; empty = no reservation
pc_male_slot = ""
voice_3d = true # 3D-position voices at the speaker; false = flat/2D
voice_no_interrupt = false # skip a new line while its channel is still playing
voice_attenuation = true # distance-based volume falloff for follow-positioned sounds
attenuation_near = 200.0 # full volume within this distance (~3 m)
attenuation_far = 1800.0 # volume reaches the floor at/beyond this distance (~26 m)
attenuation_floor = 0.05 # minimum volume factor for far sounds (0.0 = silent)
| Key | Type | Default | Meaning |
|---|---|---|---|
log_level |
string | "info" |
Verbosity of AudioUtil.log. |
sound_flags |
int | 0x1A |
BuildSoundDataFromFile flags. Sweep with DebugPlayFile if audio is silent or not 3D. |
sound_priority |
int | 128 |
Sound priority passed to the engine. |
sfx_slot |
slot id | "SFX0" |
The [[slot]] whose categories PlaySFX resolves before the flat [sfx] table (see SFX). "" = table only. |
default_female_slot |
slot id | "F1" |
Slot for unrouted female actors. |
default_male_slot |
slot id | "M1" |
Slot for unrouted male actors. Creatures never reach it — see below. |
default_creature_slot |
slot id | "" |
Slot for an unrouted creature (an actor whose race carries no ActorTypeNPC keyword). Creatures never fall through to default_male_slot or its blind scan: everything those can pick is an 'F'/'M' human voice pack, so an unmapped frostbite spider would otherwise speak human lines. "" = the creature resolves to nothing and stays silent, and GetSlotForActor returns "" so a script can test before playing. Explicit routing (a [race_map] hint, an [npc_overrides] pin, PlayVoiceFromSlot) is unaffected. |
pc_female_slot |
slot id | "" |
Reserved for the player; no NPC ever resolves to it. |
pc_male_slot |
slot id | "" |
Same, for a male PC. |
voice_3d |
bool | true |
true = 3D-position each voice at the speaker (distance attenuation). false = play flat/2D at full volume so every speaker is equally audible. Lipsync is unaffected either way. |
voice_no_interrupt |
bool | false |
true = when a PlayVoice names a channel still playing a line, skip the new line instead of cutting the old one off (per channel — different speakers still overlap). SFX and PlayFile/PlayFolder are unaffected. |
voice_attenuation |
bool | false |
Distance-based volume falloff for follow-positioned sounds (voice + SFX). Loose-file playback has no sound-descriptor rolloff curve, so voice_3d positions a sound but the engine barely lowers its volume with distance — a scene across the room otherwise plays at full volume. When on, each follow-positioned instance is scaled by the player→speaker distance, re-sampled every 250 ms while the line plays so movement tracks. |
attenuation_near |
float | 200.0 |
Full volume within this distance (game units, ~70/m — so ~3 m). |
attenuation_far |
float | 1800.0 |
Volume reaches attenuation_floor at/beyond this distance (~26 m). Between near and far the curve is an inverse-distance rolloff (−6 dB per doubling, the point-source law) renormalized to land on the floor at far. |
attenuation_floor |
float | 0.05 |
Minimum volume factor for far sounds (0.0 = silent beyond attenuation_far). |
fuz_cache_max_mb |
int | 256 |
Size cap for Sound\AudioUtilFuzCache\ (each played .fuz decodes its audio there once, as PCM wav — see PlayFile). Checked once per launch and on ReloadConfig; oldest files deleted first. ~88 KB per second of audio, so the default holds on the order of a thousand voice lines. 0 = unlimited. Deleting the folder by hand is always safe — it rebuilds on demand. |
[ppa]¶
| Key | Type | Default | Meaning |
|---|---|---|---|
enable |
bool | true (internal) |
Gate the Accurate Penetration bridge. The shipped neutral TOML sets false; the bridge also needs the PPA plugin present. |
event_rate_ms |
int | 2000 |
Min interval per receiver for AudioUtilPPA_Update mod events. Floored at 1000 ms (values below are clamped up). Context-bit changes still fire immediately. |
[lipsync]¶
Scalars base-only; block_categories is additive
The scalar tuning below (enable, gain, attack_ms, release_ms, min_level, block_in_dialogue) is base-only — an overlay that sets it is ignored with a warning. block_categories is the exception: it merges from every file (base + overlays, union), so an add-on can mark its own category pools mouth-still without owning the base config.
[lipsync]
enable = true
gain = 1.0 # 0.0-2.0 mouth-open strength
attack_ms = 30 # how fast the mouth opens toward a louder level
release_ms = 90 # how fast it closes on quiet / clip end
min_level = 0.04 # envelope levels below this keep the mouth closed
block_in_dialogue = true # never lipsync an actor while they talk to the player
block_categories = ["Slurp"] # never drive the mouth (additive — merges across overlays)
| Key | Type | Default | Meaning |
|---|---|---|---|
enable |
bool | true |
Amplitude-envelope lipsync master switch. |
gain |
float | 1.0 |
Mouth-open strength, 0.0–2.0 (1.0 = envelope as-is). |
attack_ms |
int | 30 |
Opening speed toward a louder level. |
release_ms |
int | 90 |
Closing speed on quiet / clip end. |
min_level |
float | 0.04 |
Envelope levels below this keep the mouth closed. |
block_in_dialogue |
bool | true |
Suppress lipsync for an actor while they're in a dialogue with the player — the game's own dialogue/voice system drives that mouth from the real voice file, so AudioUtil stays off it to avoid fighting. Checked when the line starts and re-checked on a 500 ms throttle, so a conversation started mid-line hands the mouth back. |
block_categories |
string list | (none) | Additive (merges from the base + every overlay). Requested categories that never drive lipsync — the line plays mouth-still. Matched on the requested name (normalized), before aliasing, across every slot. For pools that aren't vocalization (oral SFX / slurping) or where another system owns the mouth (a climax/ahegao face). Same effect as passing blockLipSync=true to PlayVoice, but declared once in config. |
Runtime overrides: SetLipSyncEnabled / SetLipSyncGain. ReloadConfig restores these TOML values.
Lipsync is suppressed automatically for a gagged actor — when the speaker wears a marker configured in [gag], the device owns the mouth and the DLL won't drive it (checked when the line starts and re-checked on a 500 ms throttle, so a gag equipped mid-line hands the mouth over). No mouth-open threshold to tune.
[captions]¶
On-screen captions for played lines. When a wav has a same-named .toml sidecar next to it (moan_03.wav + moan_03.toml) holding per-language text, the text for the configured language is shown as a regular game subtitle attributed to the speaking actor while the line plays (and an AudioUtil_Caption mod event is sent — see the API page). No sidecar = no caption, so leaving this enabled is safe; voice packs opt in per file by shipping sidecars. Sidecars are loose files only (not read out of BSAs).
# Sound\SLOVE\F1\Moan\moan_03.toml — sidecar for moan_03.wav
en = "Does this count as extra follower duty?"
ru = "Это считается дополнительной службой?"
[captions]
enable = true
language = "auto" # sidecar key to read; "auto" = follow the game's sLanguage ini
hud = true # false = only send the mod event (consumer draws its own UI)
| Key | Type | Default | Meaning |
|---|---|---|---|
enable |
bool | true |
Captions master switch (base-only, like all scalars here). |
language |
string | "auto" |
Which sidecar key to read. "auto" maps the game's sLanguage ini setting to a two-letter code (ENGLISH → en, RUSSIAN → ru, GERMAN → de, …; an unmapped language matches by its lowercased full name). Any other value is used verbatim (lowercased), so packs may invent their own keys. A missing localization falls back to the sidecar's en text. |
hud |
bool | true |
false = never inject the HUD subtitle, only send the AudioUtil_Caption mod event — for a consumer mod that renders captions with its own widget (pair with GetHandleCaption). |
Runtime override: SetCaptionsEnabled. ReloadConfig restores the TOML value (and re-resolves the language + re-reads edited sidecars).
[gag]¶
Gag / deepthroat / mouth-owning devices. When a speaking actor wears any configured gag marker — a keywords keyword or a specific items item form — AudioUtil (1) routes their voice through the slot's gag_slot so a muffled clip plays instead of the clear line, and (2) suppresses lipsync (see above). Detection is native — the DLL reads the actor's worn items, no Papyrus wiring.
Reach for keywords first: one keyword matches a whole device family. Use items for a one-off gag that carries no usable keyword — you point straight at the worn item form (usually an ARMO).
Dormant unless keywords or items is set, so the SFW-neutral default is unaffected.
[gag]
enable = true
default_category = "GagMoan" # muffled catch-all for categories the gag slot lacks
keywords = [ # 'Plugin.esp|FormID' (hex), like [npc_overrides] keys
'Devious Devices - Assets.esm|7EB8',
'ZaZAnimationPack.esm|8A4D',
'Toys.esm|8C2',
]
items = [ # specific worn item forms, same 'Plugin.esp|FormID' form
'MyGagMod.esp|000800',
]
| Key | Type | Default | Meaning |
|---|---|---|---|
enable |
bool | true |
Master switch. Feature is also inert while both keywords and items are empty. |
default_category |
category | "" |
Played from the gag slot when the requested category has no audio there — a muffled catch-all so a gagged actor never leaks a clear line (and never falls silent). Empty = no catch-all. |
keywords |
list of 'Plugin.esp\|FormID' |
(none) | Worn keywords that mark an actor as gagged. Hex form id, 0x optional; same format as [npc_overrides] keys. A keyword whose plugin isn't in the load order is skipped (one debug line), so listing optional mods is harmless. |
items |
list of 'Plugin.esp\|FormID' |
(none) | Specific worn item forms (usually ARMO) that mark an actor as gagged — for a gag with no keyword to match. Same form-id format and same skip-if-missing behavior as keywords. |
See Resolution → gag redirect for how the redirect interacts with the category chain.
[[slot]]¶
An array of tables — one per voice pack. See Overview → three ways and Resolution.
[[slot]]
id = "M4"
sex = "male" # "male" | "female" | "all"
path = 'Sound\fx\MyMod\M4' # scanned for <Category>\*.wav subfolders
fallback = "M1" # optional: backfill empty categories from this slot
gag_slot = "M4gag" # optional: muffled parallel slot used when gagged
# variation = "B" # optional schema label (see below); default "A"
| Key | Type | Meaning |
|---|---|---|
id |
string | Slot id ("F1", "M4", "C2", …). |
sex |
string | "male", "female", or "all". "all" is sex-neutral: it matches either sex on explicit routes ([race_map]/[voicetype_map]/[npc_overrides]) but is skipped by the blind default-by-sex fallback. For the category layer it shares the male aliases/[category_fallbacks.male] (where creature/neutral fallbacks are authored) and skips [male_only_remap]. Use it for creature slots (a creature's reported sex is unreliable) and the sfx slot. Plain "male"/"female" otherwise; creatures otherwise read as male. |
path |
path | Folder scanned for <Category>\*.wav subfolders. Optional if the slot is defined purely by explicit categories. Loose files only. |
fallback |
slot id | Optional. Per-category backfill slot when a category resolves to nothing here. Chains capped at 4 hops. |
gag_slot |
slot id | Optional. A parallel slot (another [[slot]], same category names, muffled audio) used instead of this one when the speaking actor is gagged. See [gag]. |
variation |
string | Optional per-slot schema label, surfaced to Papyrus via GetSlotVariation. "B" = the alternate layout, anything else (incl. unset) = "A". AudioUtil does not interpret it — resolution, fallback and scanning are unchanged regardless of value. It exists purely for a consumer mod whose voice packs ship in more than one folder/category layout, so the consumer can read it per resolved slot and gate its own category routing. Leave unset if all packs share one layout. |
[slot.categories] |
table | Optional explicit categories (below). Win over same-named scanned folders. |
[slot.categories] — explicit categories¶
A category value may be an array (a file list) or a string (one folder):
[[slot]]
id = "C1"
sex = "male"
[slot.categories]
# array: explicit file list — needs no loose files, may point into a BSA
BattleCry = [
'Sound\FX\NPC\Giant\AttackVocal\NPC_Giant_AttackVocal_01.wav',
'Sound\FX\NPC\Giant\AttackVocal\NPC_Giant_AttackVocal_02.wav',
]
# string: one folder scanned for this category ('Sound\...' = full Data path,
# otherwise relative to the slot's path). Loose files only.
WarCry = 'Sound\fx\SomeOtherMod\shouts'
Rally = 'Sound\fx\SomeOtherMod\shouts'
- Array (file list) — bypasses the filesystem scan, so it can reference BSA-packed audio (loose files still win over archives at play time). List a file more than once to weight it — a clip listed twice is drawn about twice as often, and the shuffle bag's no-back-to-back guard still keeps it from playing twice in a row.
- String (folder) — scanned like a slot folder; lets several categories share one pool of files without copies. Loose files only.
Slot resolution tables¶
Full behavior in Voice & Category Resolution.
[voicetype_remap] # rename a voicetype (one hop) before slot lookup
enable = true
MaleGuard = "MaleNord" # values are voicetype names, not slot ids
[voicetype_map] # voicetype -> slot id, or a list of candidates
MaleEvenToned = "M1"
MaleBandit = ["M3", "M4"]
[race_map] # race-editor-id substring -> slot id(s); longest hint wins
Nord = "M4"
Troll = "C1"
[npc_overrides] # 'Plugin.esp|FormID' -> slot; ESL: last 3 hex digits
'MyFollower.esp|000D62' = "F2"
| Table | Key | Value |
|---|---|---|
[voicetype_remap] |
enable (bool) + voicetype name |
Target voicetype name (must be a [voicetype_map] key). |
[voicetype_map] |
voicetype name | Slot id or list of slot ids (spread deterministically per actor). |
[race_map] |
race-id substring | Slot id or list. Longest matching hint wins. |
[npc_overrides] |
'Plugin.esp\|FormID' |
Slot id. Checked before voicetype/race. |
Category layer tables¶
Full behavior in Category resolution.
[category_aliases.female] # script name -> on-disk folder
BattleCry = "War Shout"
[category_aliases.male]
# ...
[male_only_remap] # male slots only: female-engine category -> male category
ComfortLines = "Calm Lines"
[category_fallbacks.female] # substitute when a category has no folder (chained)
Whisper = "Murmur"
[category_fallbacks.male]
# ...
| Table | Applies to | Meaning |
|---|---|---|
[category_aliases.female] / .male |
that sex's slots | Rename a requested category to the actual folder name. |
[male_only_remap] |
male slots only | Substitute a male category for a female-engine category name. |
[category_fallbacks.female] / .male |
that sex's slots | Substitute when a category resolves to no folder. Chained since 0.9.18 (max 8 hops): a substitute that also has no folder falls through to its own entry, so the table can be a ladder. The full ladder is walked in one slot before the slot's fallback slot. |
[tags]¶
Defines the tag vocabulary for tag-scored pools: named axes of mutually-exclusive tokens, each axis with a match weight (≤ 64 tokens total). The plugin ships none — no [tags] anywhere means the whole tag layer is dormant (category folders scan flat; PlayVoiceTagged ≡ PlayVoice). Additive across the base + overlays like slots/sfx, so several consumer mods can ship their own axes and coexist: same-named axes union their tokens (no file can remove another's), weight is last-writer-wins in sorted filename order.
[tags]
mood = { tokens = ["calm", "angry", "afraid"], weight = 8 }
intensity = { tokens = ["soft", "intense"], weight = 4 }
target = { tokens = ["human", "beast", "undead"], weight = 1 }
See Tag-Scored Pools for how packs tag files and how a call's facts pick a pool.
[groups]¶
Startup volumes, 0.0–1.0. Startup state only — a consumer mod overrides at runtime via SetGroupVolume (AudioUtil ships no MCM of its own). Groups are created on first use; these are just the conventional ones.
SFX — the sfx slot + [sfx] table¶
PlaySFX(name, ...) plays a named shuffle-bag pool, and a voice category with no folder falls through to an sfx pool of the same name (last resort). A name resolves in two places, in order:
- A category of the sfx slot — id
SFX0by default, setsfx_slotin[general]to rename (""disables it). Since it's a normal[[slot]], each sfx pool can use any category form — a scanned folder, an explicit file list (BSA-capable), or a folder ref. Preferred, and the only way to give an sfx pool BSA-packed audio. - The
[sfx]table — flatname = folder, loose files only. The value is a fullSound\...Data-relative path, same as a slot path.
Names are matched directly (no category_aliases/fallbacks). The slot's sex is irrelevant to sfx lookup.
[[slot]]
id = "SFX0" # PlaySFX resolves categories here first
sex = "all" # sex-neutral (irrelevant to sfx lookup)
[slot.categories]
Clap = 'Sound\fx\MyMod\SFX\Clap' # scanned folder
Slap = ['Sound\fx\MyMod\SFX\slap01.wav'] # explicit list — may point into a BSA
[sfx] # legacy flat table, still supported
Thud = 'Sound\fx\MyMod\thuds' # full Data path (loose files only)
Clap = 'Sound\fx\MyMod\SFX\Clap' # full Data path