AudioUtil (core player)¶
Scriptname AudioUtil Hidden — the native folder-based audio player. Read Overview & Concepts first for the handle / volume / group / channel model that every Play* call shares.
All name and key matching (categories, slots, groups, SFX names) is case- and space-insensitive: "Battle Cry" == "BattleCry" == "battlecry".
Core¶
GetAPIVersion¶
API version of the loaded DLL, for compatibility checks. 0 = DLL not installed. Increases only when signatures/behavior change incompatibly. Currently 1.
ReloadConfig¶
Re-read AudioUtil.toml and rescan all slot folders in-game — no restart needed. Returns false if the file failed to parse, in which case the previous config stays active. Console: cgf "AudioUtil.ReloadConfig".
Playback¶
PlayVoice¶
int Function PlayVoice(Actor akActor, string category, float volume = 1.0, string group = "", string channel = "", bool blockLipSync = false) global native
Play a voice line for an actor. Resolves which voice pack (slot) the actor uses, then which folder inside it the category names, then shuffle-picks a wav. Returns a handle; 0 if no slot or no audio resolved.
- Slot resolution and category resolution are described in full under Voice & Category Resolution.
blockLipSync = trueplays this one line without moving the speaker's mouth — a per-call opt-out. If your mod owns the actor's face across a whole scene, pass ittrueon every line while the face is up (see the tip under lipsync).
int h = AudioUtil.PlayVoice(npc, "BattleCry")
AudioUtil.PlayVoice(npc, "Taunt", 0.8, "npc_voice") ; + volume, group
AudioUtil.PlayVoice(npc, "Taunt", 1.0, "npc_voice", "npc_main") ; channel: replaces prior line
PlayVoiceFromSlot¶
int Function PlayVoiceFromSlot(string slot, string category, Actor akFollow, float volume = 1.0, string group = "", string channel = "", bool blockLipSync = false) global native
Same as PlayVoice, but the slot is named explicitly ("F1", "M4", "C2", …) instead of resolved from an actor — for samples, tests, or when the caller already decided the voice. akFollow only provides the 3D position.
PlaySFX¶
int Function PlaySFX(string sfxName, Actor akFollow, float volume = 1.0, string group = "sfx", string channel = "") global native
Play a named SFX. sfxName resolves first as a category of the sfx slot (SFX0 by default; set via [general] sfx_slot), then the flat [sfx] table — so an sfx pool can use a scanned folder, an explicit file list (BSA-capable), or a folder ref, exactly like a voice category. See SFX resolution. Defaults into the sfx group so SFX volume/ducking applies unless you override it.
PlayFile¶
int Function PlayFile(string dataRelativePath, Actor akFollow, float volume = 1.0, string group = "", string channel = "") global native
Play one specific file. Path is relative to Data\ ('Sound\fx\MyMod\a.wav') and may resolve to a loose file or to audio packed inside a BSA — the engine's resource loader handles both (loose wins over archive).
PlayFolder¶
int Function PlayFolder(string dataRelativeFolder, Actor akFollow, float volume = 1.0, string group = "", string channel = "") global native
Play a random file from a loose folder (scanned on first use, then cached as a shuffle bag).
Folder scans can't see into BSAs
PlayFolder (and folder-string categories, and the [sfx] table) scan the loose filesystem only. For BSA-packed audio use PlayFile or an explicit [slot.categories] file list in the TOML — those go through the engine's resource loader, which resolves archives.
Handles¶
IsHandlePlaying¶
True while the instance is audible. Also true during a short startup grace (~0.4 s) right after Play* returns: the engine starts streams asynchronously and briefly reports "not playing" for a sound that is about to start.
StopHandle¶
Stop the instance now. Returns true if it was alive and stopped.
GetHandleDuration¶
Clip length in seconds. May return 0.0 while the stream is still being prepared — see WaitForHandle for how it copes with that.
SetHandleVolume¶
Change one instance's base volume mid-play (group multipliers still apply).
Groups and channels¶
Conventional group names: pc_high / pc_low (player voice), partner_high / partner_low (NPC voice), sfx (effects), oneshot (ambient one-shots). Any other name works — groups are created on first use.
SetGroupVolume¶
Set a group's volume (0.0–1.0). Applies immediately to playing members and to everything started later. MCM sliders typically call this — the TOML [groups] values are only the startup state.
DuckGroup / UnduckGroup¶
Function DuckGroup(string group, float factor = 0.0) global native
Function UnduckGroup(string group) global native
DuckGroup temporarily scales a group by factor (0.0 = silent, 1.0 = no duck) without touching its stored volume — e.g. duck background voice while an important line plays. Ducks do not stack; the last factor wins. UnduckGroup restores the group to its normal volume (equivalent to factor 1.0).
StopGroup / StopAllAudio / StopChannel¶
Function StopGroup(string group) global native
Function StopAllAudio() global native
Function StopChannel(string channel) global native
StopGroup— stop every playing instance in the group.StopAllAudio— stop everything AudioUtil is playing and clear all channel bindings. Does not touch group volumes or the config.StopChannel— stop whatever occupies the channel (if anything) and free it.
Lipsync¶
PlayVoice / PlayVoiceFromSlot automatically move the speaking actor's mouth in sync with the clip's loudness: the DLL reads the wav's amplitude envelope and drives the MFG Aah / BigAah phonemes per frame. Works for loose PCM wav files; xwm or BSA-packed audio plays normally but skips the mouth. Configure defaults in the TOML [lipsync] table.
Lipsync is also suppressed automatically for a gagged actor (one wearing a device configured in the TOML [gag] table) — the device owns the mouth, so the DLL won't fight it. It's likewise suppressed while an actor is in a dialogue with the player (the game's dialogue/voice system drives that mouth), toggleable via [lipsync] block_in_dialogue (default on). No Papyrus call needed for either.
Playing nice with expression mods
Only the two phonemes above are touched, and they are zeroed when the clip ends. If your mod sets phonemes itself (e.g. an expression cycler), skip your own mouth writes for an actor while IsLipSyncActive(actor) is true to avoid fighting over the jaw. If your mod fully owns the face (an overlay that sets its own expression), pass blockLipSync = true on the lines you play for that actor while the face is up so AudioUtil never touches its mouth (see the tip above).
IsLipSyncActive¶
True while AudioUtil is driving this actor's mouth (a voice line with a readable envelope is playing and lipsync is enabled).
StopLipSync¶
Fade this actor's mouth closed now; the audio itself keeps playing.
SetLipSyncEnabled / IsLipSyncEnabled¶
Function SetLipSyncEnabled(bool enable) global native
bool Function IsLipSyncEnabled() global native
Master switch (runtime; the TOML value is restored on ReloadConfig). Turning it off fades all active mouths closed.
SetLipSyncGain¶
Mouth-open strength, 0.0–2.0 (1.0 = envelope as-is). For MCM sliders.
Owning an actor's face across many lines
There is no standing per-actor lipsync block. If your mod takes over an actor's face (an ahegao / expression overlay), pass blockLipSync = true on each PlayVoice for that actor while the face is up — decide it per call from your own face-ownership state. Because it's decided per line rather than latched, there's no state to leak or to be cleared on load, and two systems that both play lines for the same actor can't clobber each other's block. For a whole category that should never lipsync (oral SFX, climax pools), list it in [lipsync] block_categories instead.
Introspection¶
GetSlotForActor¶
Which slot id PlayVoice would resolve for this actor right now ("M4", "F1", …; "" if none). Useful for debugging voice assignment.
GetCategoryFileCount¶
Number of playable files behind slot/category after full category resolution (aliases/fallbacks applied). 0 = a PlayVoice would fail.
CategoryExists¶
True if slot/category resolves to at least one file. Cheap way to guard optional content ("play the rare line only if the pack ships it").
Debug¶
DebugPlayFile¶
int Function DebugPlayFile(string dataRelativePath, Actor akFollow, int flags, int priority) global native
Play a file with explicit BuildSoundDataFromFile flags/priority instead of the TOML's sound_flags / sound_priority — for experimenting when a file is silent or behaves oddly. Not for production use. If a sound won't play or isn't 3D-positioned, sweep values here.
Wrappers¶
These are non-native Papyrus helpers (they live in AudioUtil.psc, not the DLL). They block the calling Papyrus thread by polling IsHandlePlaying + Utility.Wait.
WaitForHandle¶
Block until the instance finishes, or a safety timeout: clip duration + 1 s, or 30 s when the duration isn't known yet. Returns immediately for dead/invalid handles. Polls every 0.1 s, so expect up to ~0.1 s of overshoot past the actual clip end.
PlayVoiceAndWait / PlaySFXAndWait¶
Function PlayVoiceAndWait(Actor akActor, string category, float volume = 1.0, string group = "", string channel = "") global
Function PlaySFXAndWait(string sfxName, Actor akFollow, float volume = 1.0, string group = "sfx", string channel = "") global
Play, then block until the line finishes. Same arguments and resolution as PlayVoice / PlaySFX; the handle is discarded.
Play¶
Function Play(string category, Actor akActor, bool waitForCompletion = true, float volume = 1.0, string group = "", string channel = "") global
Drop-in equivalent of a legacy PlaySound(Sound form, Actor, wait) helper: a category string instead of a Sound form, with optional blocking.