ssmc (Speech Synthesis Markup Compiler) turns SSML into SSMIR, a three-stage intermediate representation: IR, events, and a speech plan.
SSML → Preprocessor → SSMIR ir
ir → Interpreter → SSMIR events
events → Compiler → SSMIR plan
The version number is only in ssmc/VERSION. The shipped tree is build/dist/ssmc-<version>/{ssmc,LICENSE,locales/}. Locale sources live in ssmc/share/locales/; see NOTICE.
Podman or Docker. The build script installs a pinned Zig inside a throwaway Alpine image. The host does not need Zig, g++, Flex, or Bison.
./build # musl-static CLI + goldens
./build test # compile and run goldensOutput: ../build/dist/ssmc-<version>/ (statically linked ssmc plus a copy of share/). Run from that directory so ./locales resolves.
ssmc --version prints the same number as VERSION. Runtime locales are ./locales, --locale=DIR, or SSMC_LOCALE.
ssmc file.ssml # SSMIR plan (default)
ssmc --emit=ir file.ssml # preprocessor IR
ssmc --emit=events file.ssml # interpreter events
ssmc --emit=plan file.ssml # compiled speech plan
ssmc --locale=locales file.ssml # CLDR-shaped locale directory
ssmc --version--locale (or SSMC_LOCALE) is a locales root (locales/) or a language dir (locales/en). xml:lang / en-US selects en.
Every dump is a JSON object with a shared envelope:
{
"ssmir": "1.0",
"stage": "ir",
"lang": "en-US"
}stage is ir, events, or plan.
ir keeps the original func / text / ops model: tags are func entries with integer ids and an attrs object; each text span lists the funcs that apply (ops, innermost first). Empty val marks a <break>. <?xml?>, comments, and a wrapping <ssml> element are dropped.
events are those spans after say-as, sub, and phoneme are applied. ops stay for traceability.
plan is a linear, backend-neutral sequence of silence (ms) and speak (text, plus optional emphasis, rate, pitch, volume, ph).
| Tag | Behavior |
|---|---|
break |
time (1s, 500ms) or strength → silence |
emphasis / prosody |
attach level / rate / pitch / volume to following text |
say-as |
W3C Note types. Numbers/dates/times are spelled with CLDR-shaped RBNF + month files we author in share/locales/<lang>/ (not a CMUdict dump, not official CLDR). Unknown interpret-as leaves the text unchanged. Time uses RBNF cardinals plus %%am / %%pm / %%oh / %%oclock particles. |
phoneme |
copy ph (and alphabet) onto the event |
sub |
replace text with alias |
p / s / speak / voice |
nesting / language only |
There is no CMUdict lookup. Pronunciation comes from <phoneme ph="…">.
tests/goldens/sample.ssml compiles to a plan with 1s / 2s silences, emphasized and slow spans, a spoken date and time, and IPA from <phoneme>.