Skip to content

Fix no audio: send raw SAPI XML instead of a W3C SSML document - #6

Open
stiio wants to merge 1 commit into
Osmodium:mainfrom
stiio:fix/ssml-speak-wrapper
Open

Fix no audio: send raw SAPI XML instead of a W3C SSML document#6
stiio wants to merge 1 commit into
Osmodium:mainfrom
stiio:fix/ssml-speak-wrapper

Conversation

@stiio

@stiio stiio commented Aug 1, 2026

Copy link
Copy Markdown

Problem

On Pathfinder: Kingmaker the mod produced no speech at all — not for dialog, not for the Preview "Play" button — while the Wrath of the Righteous build works. Voice enumeration worked (all voices listed) and the native WindowsVoice.dll loaded fine, so the voice stack itself was healthy.

Root cause

WindowsSpeech.SpeakInternal wraps every utterance in a W3C SSML document:

<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" ...>
  <voice required="Name=..."><pitch absmiddle="..."/>...text...</voice>
</speak>

This mixes W3C SSML (the <speak xmlns=".../synthesis"> root) with SAPI‑proprietary tags (<voice required="Name=...">, <pitch absmiddle>, …). Once SAPI sees the W3C namespace it parses in W3C mode, where:

  1. <voice required="Name=..."> is not recognized (W3C uses <voice name="...">), so the configured voice is ignored and playback falls back to the system default voice.
  2. If that default is a neural voice (e.g. via NaturalVoiceSAPIAdapter), the embedded engine rejects the markup with SPERR_UNSUPPORTED_FORMAT (0x80045003) → no audio.

The WotR build avoids this because its main Speak/SpeakDialog/SpeakPreview call WindowsVoiceUnity.Speak directly (raw SAPI XML), whereas the Kingmaker build routes everything through SpeakInternal (which adds the wrapper).

Fix

Don't wrap in <speak> in SpeakInternal; send the raw SAPI XML fragment as-is (SPF_IS_XML accepts a <voice .../> fragment).

Verified (native SAPI, reproduced outside the game)

Markup Voice Result
W3C <speak> wrapper neural (Dariya Native) 0x80045003
W3C <speak> wrapper RHVoice (Elena) ✅ speaks, but required= ignored
raw SAPI XML (this PR) neural ✅ speaks
raw SAPI XML (this PR) RHVoice ✅ speaks, voice selection honored

In-game after the fix: dialog auto-play and the Preview button work, and the voice picker actually changes the voice.

Note: SpeakBegin/SpeakEnd are now unused — left in for a minimal diff; happy to remove if preferred.

SpeakInternal wrapped every utterance in a W3C SSML document
(<speak xmlns="http://www.w3.org/2001/10/synthesis"> ... </speak>). Inside that
document SAPI does not honor the proprietary <voice required="Name=..."> tag, so
the configured voice was never applied and playback silently fell back to the
system default voice. When that default is a neural voice (e.g. exposed through
NaturalVoiceSAPIAdapter) the embedded engine additionally rejects the markup with
SPERR_UNSUPPORTED_FORMAT (0x80045003), so nothing is spoken at all.

Send the raw SAPI XML fragment as-is (the Wrath of the Righteous version already
does this for its main Speak/SpeakDialog/SpeakPreview paths). This restores voice
selection and makes both classic SAPI5 and neural voices audible.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@Osmodium

Osmodium commented Aug 4, 2026

Copy link
Copy Markdown
Owner

I'll have a look at this soon, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants