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
dialogue_output_model = true # NPC sounds play through the game's dialogue output model
voice_slider = true # voice lines follow the game's Voice volume
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)
voice_log = "off" # pack-author transcript: off | player | all
| 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. Where a sound sits in the speakers is dialogue_output_model, not the flags. |
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 = each voice follows its speaker, so it is placed and rolled off like a real NPC line (see dialogue_output_model). 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. |
dialogue_output_model |
bool | true |
A sound that follows an NPC (a voice with voice_3d on, or any Play* call given an actor) plays through the game's own Dialogue Output Model 3D default object, the output model a real NPC line uses. The engine then places it in the speakers where the speaker stands and rolls it off with distance by that model's curve (vanilla: 70 to 3000 units; sound overhauls change it). A sound built from a bare file has no output model of its own: false restores the playback before 0.9.30, centered and at full level wherever the speaker is, with voice_attenuation as the only falloff. The player's own lines are left centered either way. A/B it live with autest outmodel. |
voice_slider |
bool | true |
Voice lines (the PlayVoice family, PlayFileWithLipSync, PlayFolderWithLipSync) are scaled by the game's Voice volume (Settings, Audio), the way real dialogue is, so they stay level with it, with or without an actor to follow. PlaySFX, PlayFile and PlayFolder are left alone, and so is a PlayVoice that finds no voice folder and falls back to the sfx slot or the [sfx] table. The slider is read when a line starts. false = voice lines ignore the slider, as before 0.9.30. A/B it live with autest voiceslider. |
voice_attenuation |
bool | false |
Distance-based volume falloff for follow-positioned sounds (voice + SFX) that the engine does not roll off itself, which is all of them while dialogue_output_model is false. Loose-file playback has no sound-descriptor rolloff curve, so such a sound across the room otherwise plays at full volume. When on, each of them is scaled by the player-to-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). |
voice_log |
string | "off" |
Pack-author playback transcript. Writes a second log file beside AudioUtil.log — AudioUtil_Voices.log — with one line per voice line: the category a consumer asked for, the facts it carried, the slot + folder resolution actually landed on, and the exact wav that played. It answers the question AudioUtil.log cannot: "my pack has a folder for this beat, so why did I hear a stock moan?" A line that resolved out of the speaker's own slot is marked !, so a category-name or alias mismatch reads as a column of ! instead of as unexplained silence; MISS lines record calls where nothing played at all. "player" logs the player character only (the usual authoring case), "all" every speaker. Toggle live with autest voicelog — no reload; re-running it restarts the file, so it can capture a single scene. |
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. A mod that voices an unvoiced topic itself lifts this for its own lines with dialogueGuard = false on PlayVoiceFromSlotEx / PlayFileWithLipSyncEx. |
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, "D" = the tagged layout, anything else (incl. unset) = "A"; first letter only, case-insensitive. 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