Voice Settings and Overrides
Create voice packs that replace framework voice playback during PPA-tracked sex scenes. Voice presets can match actor sex, race, base actor, scene context, penetrated site, depth, speed, and girth, then play shuffled audio with optional lip sync.
Overview
Voice configs are independent TOML files. Each file can define global voice timing settings, one or more [[VoicePreset]] entries, or both.
At runtime, PPA evaluates the presets for each tracked actor in a sex scene. When a preset matches and one of its triggers fires, PPA plays one sound from that preset and disables the framework voice for that actor.
Use predicates to decide who and when a preset applies. Use triggers to decide how often matching presets can speak. Use priority to decide which matching preset wins when several triggers fire at once.
Config Files
Place voice config files directly in:
Data/SKSE/Plugins/ppa-voice-configs/
Every direct child with a .toml extension is loaded. Files are sorted by filename, then resolved in that order. A file can force other files to resolve first with a top-level Inherits array.
Inherits = ["00_shared_voice_base.toml"]
| File Field | Type | Description |
|---|---|---|
| Inherits | string[] | Optional top-level list of other voice config filenames. Parent paths are resolved relative to ppa-voice-configs. |
| [Settings] | table | Optional global voice tuning. These values are not per-preset. |
| [[VoicePreset]] | array | One or more voice presets to append to the global preset list. |
[Settings] is one global snapshot. Each resolved file replaces it with that file's settings or defaults, so keep global voice tuning in a late-loading file such as zzz_voice_globals.toml and avoid later voice files that omit [Settings].
Audio and Lip Files
Sounds entries are paths relative to the game's Data folder. Wildcards support one * per path and expand alphabetically at load time.
Sounds = [ "Sound\\PPA\\Voices\\Female\\moan_*.wav", "Sound\\PPA\\Voices\\Female\\single_line.wav", ]
For lip sync, place a matching .lip file next to the audio using the same relative stem. For Sound\PPA\Voices\Female\moan_01.wav, PPA looks for Sound\PPA\Voices\Female\moan_01.lip.
Wildcard directories that do not exist expand to nothing. Non-wildcard sound paths are kept as written and can still fail later if the audio file cannot be opened by Skyrim.
Quick Start
This is the smallest useful voice preset: it matches female actors and plays one random sound every two seconds while no other PPA voice line is active.
[[VoicePreset]] Name = "Female interval voice" Priority = 0 Sounds = ["Sound\\PPA\\Voices\\Female\\moan_*.wav"] [VoicePreset.Predicates] Sex = [1] [[VoicePreset.Triggers]] Type = "Interval" Interval = 2.0 RandomChance = 70 MinDelay = 1.0
Global Settings
Global settings control volume and thrust detection for every voice preset.
| Setting | Default | Description |
|---|---|---|
| MasterVolume | 1.0 | Volume applied to every PPA voice sound handle. |
| VelocitySmoothFactor | 0.3 | Smoothing factor for depth velocity used by thrust detection. |
| ThrustStartVelocity | 5.0 | Smoothed forward velocity required before an OnThrust trigger can fire. |
| ThrustMinWithdrawalRatio | 0.65 | Required withdrawal depth as a ratio of the previous stroke peak before a new thrust can be detected. |
| ThrustMinPeakVelocity | 8.0 | Minimum peak velocity required during the current forward stroke. |
| ThrustMinDepth | 0.5 | Minimum penetration depth required before a thrust event can be accepted. |
[Settings] MasterVolume = 0.85 VelocitySmoothFactor = 0.35 ThrustStartVelocity = 5.0 ThrustMinWithdrawalRatio = 0.65 ThrustMinPeakVelocity = 8.0 ThrustMinDepth = 0.5
Voice Presets
A preset contains the audio pool, matching rules, trigger rules, and priority for one type of voice line.
| Preset Field | Type | Description |
|---|---|---|
| Name | string | Optional label used for human organization. |
| Priority | int | Higher priority wins when multiple matching presets fire in the same frame. Default is 0. |
| Sounds | string[] | Data-relative sound paths. Empty presets never play. |
| [VoicePreset.Predicates] | table | All configured predicate groups must pass. |
| [[VoicePreset.Triggers]] | array | One or more trigger rules. If omitted, the preset gets one default interval trigger at two seconds. |
Sounds inside a preset use a shuffle bag. PPA plays every sound once before reshuffling, and avoids immediately repeating the last sound when there is more than one choice.
Predicates
Predicates are optional. If a preset has no predicates, it can match any tracked actor. When predicates are present, every predicate must pass before triggers are considered.
| Predicate | Type | Description |
|---|---|---|
| Sex | int[] | Matches actor sex. Skyrim values are 0 male and 1 female. |
| Races | string[] | Matches the actor's race FormID. |
| BaseIDs | string[] | Matches the actor base FormID. Use this for a specific NPC voice override. |
| PenetrationDepthMin / Max | float | Requires max penetration depth to fall within the range. The predicate is only created when max is greater than min. |
| SpeedMin / Max | float | Requires tracked penetration speed. If SpeedMax is omitted or not greater than min, the check becomes minimum-only. |
| PenisGirthMin / Max | float | Requires max penetrator girth to fall within the range. Set both min and max. |
| PenetratedSites | string[] | Matches active penetrated sites. Values are Vagina, Anus, Mouth, HandL, and HandR. |
| PenetratingSites | string[] | Parsed by the loader, but the current voice runtime does not populate penetrating-site data. Prefer PenetratedSites. |
| Contexts | string[] | Requires scene context flags such as Vaginal, Anal, Oral, Aggressive, FemDom, Loving, Dirty, Boobjob, Handjob, Footjob, or Masturbation. |
String predicates for sites and contexts can be negated with !. Non-negated site values in the same array are ORed together, then negated values are applied as extra must-not-match checks.
[VoicePreset.Predicates] Sex = [1] Races = ["Skyrim.esm|013745"] PenetratedSites = ["Mouth", "!Anus"] Contexts = ["Oral", "!Aggressive"] PenetrationDepthMin = 0.4 PenetrationDepthMax = 2.2 PenisGirthMin = 0.8 PenisGirthMax = 1.6
Use PluginName|LocalID, for example Skyrim.esm|013745. In voice configs, a FormID without a plugin name defaults to Skyrim.esm.
The parser accepts Hands, but active hand interactions are expanded to HandL and HandR for evaluation. Use both explicit hand values when you want either hand.
Triggers
A preset can contain multiple triggers. If any trigger fires while the preset matches, the preset becomes a candidate to play.
| Trigger Field | Default | Description |
|---|---|---|
| Type | Interval | Interval, OnThrust, or Random. Unknown values fall back to Interval. |
| Interval | 2.0 | Seconds between checks for Interval triggers. |
| RandomChance | 100.0 | For Interval and OnThrust, percent chance when the event happens. For Random, approximate percent chance per second. |
| MinDelay | 0.0 | Minimum seconds after this trigger successfully played before it can fire again. |
[[VoicePreset.Triggers]] Type = "OnThrust" RandomChance = 55 MinDelay = 1.25 [[VoicePreset.Triggers]] Type = "Interval" Interval = 4.0 RandomChance = 30
OnThrust uses depth changes from active sites and the global thrust settings. The config loader does not currently parse a per-trigger site field, so an OnThrust trigger watches any detected thrust for the matching actor.
Runtime Selection
PPA builds an evaluation context for each tracked actor in a sex scene.
Presets are sorted highest priority first after all files load. Equal priorities keep file load order.
When no PPA voice line is active, every matching preset's triggers are evaluated.
If multiple presets fire in the same frame, the highest priority candidate plays.
While a PPA voice line is active, trigger timers keep advancing but no new line starts until the current line ends.
Use higher priority for specific cases such as oral, aggressive, named NPC, or creature-race lines. Use lower priority for broad fallback moans.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| No voice system activity | No loaded preset contains sounds, or ppa-voice-configs does not exist. | Create the folder and add at least one .toml file with a non-empty Sounds array. |
| Preset never matches | Predicate is too strict, wrong FormID syntax, or context tags are not present for that animation. | Start with only Sex and PenetratedSites, then add constraints one at a time. |
| Wildcard finds nothing | The wildcard directory is missing under Data or the prefix/suffix does not match filenames. | Test with one explicit sound path before switching to wildcards. |
| Globals seem reset | A later-loaded file omitted [Settings], replacing globals with defaults. | Move global tuning to a last-loading file and avoid later voice TOML files. |
| No lip sync | The matching .lip file is missing or has a different stem. | Place the .lip beside the audio path using the exact same filename stem. |
Example: Base Voice Pack
Broad female fallback lines with one interval trigger.
[[VoicePreset]] Name = "Female fallback" Priority = 0 Sounds = ["Sound\\PPA\\Voices\\Female\\fallback_*.wav"] [VoicePreset.Predicates] Sex = [1] [[VoicePreset.Triggers]] Type = "Interval" Interval = 2.6 RandomChance = 65 MinDelay = 1.0
Example: Oral Thrust Lines
This higher-priority preset only applies during oral scenes where the actor's mouth is the penetrated site.
Inherits = ["00_female_base.toml"] [[VoicePreset]] Name = "Female oral thrusts" Priority = 50 Sounds = ["Sound\\PPA\\Voices\\Female\\oral_thrust_*.wav"] [VoicePreset.Predicates] Sex = [1] PenetratedSites = ["Mouth"] Contexts = ["Oral"] PenetrationDepthMin = 0.45 PenetrationDepthMax = 3.0 [[VoicePreset.Triggers]] Type = "OnThrust" RandomChance = 45 MinDelay = 0.9
Example: Actor Override
Use BaseIDs for a specific NPC or mod-added actor. This preset wins over the broad fallback by priority.
[[VoicePreset]] Name = "Specific NPC voice" Priority = 100 Sounds = ["Sound\\PPA\\Voices\\UniqueNPC\\line_*.wav"] [VoicePreset.Predicates] BaseIDs = ["MyFollowerPlugin.esp|000D62"] PenetratedSites = ["Vagina", "Anus"] [[VoicePreset.Triggers]] Type = "Random" RandomChance = 18 MinDelay = 3.0
Example: Global Tuning
Put global tuning in a file that loads after the preset files.
This file contains only global settings. Because it loads last, later files do not reset the voice globals back to defaults.
[Settings] MasterVolume = 0.8 VelocitySmoothFactor = 0.32 ThrustStartVelocity = 5.5 ThrustMinWithdrawalRatio = 0.65 ThrustMinPeakVelocity = 8.5 ThrustMinDepth = 0.5