# Language bindings for the rust-tts-wrapper C ABI
Every binding wraps the **flat C ABI** declared in
[`include/tts_wrapper.h`](../include/tts_wrapper.h) (generated by cbindgen —
`cargo build` refreshes it). One library, one ABI, five ways to reach it.
> **ABI stability:** none promised yet. The surface tracks the Rust crate
> minor version; breaking changes are announced in release notes (the
> boundary callback was consolidated in v0.4.1, for example). All consumers
> are in-repo or in projects we maintain.
## The surface at a glance
| Lifecycle | `tts_create`, `tts_destroy` |
| Synthesis | `tts_speak`, `tts_speak_ssml`, `tts_speak_sync`, `tts_synth_to_bytes`, `tts_free_bytes` |
| Control | `tts_stop`, `tts_pause`, `tts_resume` |
| Settings | `tts_set_voice`, `tts_set_rate`, `tts_set_pitch`, `tts_set_volume` |
| Events | `tts_set_on_audio`, `tts_set_on_boundary` (7-arg incl. `estimated`), `tts_set_on_mark`, `tts_set_on_viseme`, `tts_set_on_start`, `tts_set_on_end`, `tts_set_on_error` |
| Enumeration | `tts_get_voices`/`tts_free_voices`, `tts_get_engine_count`, `tts_get_engines`/`tts_free_engines` |
| Errors | `tts_get_last_error` (null ctx → global error; `NULL` = no error) |
Conventions that hold across the ABI:
- **Null-hardening:** every setter accepts a null ctx as a no-op; every
out-pointer API returns non-zero instead of crashing on null args.
- **Ownership:** arrays and byte buffers returned by the ABI are freed by
the matching `tts_free_*`; error strings are borrowed from the ctx.
- **Callbacks:** all callbacks are cdecl C function pointers taking a
`void *userdata`; out-parameters are annotated in the header docs.
## Building the library
```sh
cargo build --release --no-default-features --features system,cloud # Linux
cargo build --release --no-default-features --features avsynth,cloud # macOS
cargo build --release --no-default-features --features sapi,cloud # Windows
```
Artifacts: `librust_tts_wrapper.so` / `librust_tts_wrapper.dylib` /
`rust_tts_wrapper.dll` (+ `.a` staticlib). Prebuilt binaries ship with each
[GitHub release](https://github.com/AACTools/rust-tts-wrapper/releases)
for every supported platform:
| Linux | x86_64, aarch64 | system+cloud (+sherpaonnx x86_64) | `.so` + `.a` |
| macOS | x86_64, aarch64 | avsynth+cloud (+sherpaonnx) | `.dylib` + `.a` |
| Windows | x86_64, aarch64, i686 | sapi+cloud (+sherpaonnx) | `.dll` + `.lib` |
| Android | arm64-v8a, armeabi-v7a, x86_64, x86 (API 24+) | cloud | `.so` + `.a` |
| iOS | arm64 device, arm64 simulator | cloud | `RustTtsWrapper.xcframework` + `.a` |
Mobile notes: the cloud feature is pure Rust (rustls — no OpenSSL), so the
NDK/Apple builds need no system dependencies. The iOS XCFrameworks bundle
the device + simulator staticlibs with the C header; drag them into Xcode.
## The bindings
| [`c/`](c/) | none (reference) | link-time, `-lrust_tts_wrapper` | `make test` (C harness, `-Wall -Wextra -Werror`) |
| [`python/`](python/) | `tts_wrapper.py` | ctypes `CDLL` | inline ctypes smoke |
| [`dotnet/`](dotnet/) | `RustTtsWrapper.Bindings` (NuGet) | P/Invoke (+ `TTS_WRAPPER_LIB` resolver, `runtimes/{rid}/native`) | `dotnet test` (xunit) |
| [`swift/`](swift/) | SwiftPM `RustTtsWrapper` | link-time (`TTS_WRAPPER_LIB_DIR`), module `CRustTtsWrapper` | `swift test` (XCTest) |
| [`nodejs/`](nodejs/) | `@aactools/tts-wrapper` (npm) | koffi dlopen (`TTS_WRAPPER_LIB` or search path) | `npm test` (node:test) |
All five suites assert the **same contract** (engine enumeration, lifecycle,
setters, callback registration, deterministic failure with a dummy cloud
key), so a regression in the ABI trips every suite, and the C harness
additionally verifies the header compiles as clean C11.
## Running the suites
```sh
# 1. Rust conformance (the ABI itself)
cargo test --test ffi_conformance --test ffi_lifecycle --test ffi_safety
# 2. C harness
(cd bindings/c && make test)
# 3. Node
(cd bindings/nodejs && npm install && npm test)
# 4. .NET (needs the dotnet SDK)
(cd bindings/dotnet && dotnet test)
# with an explicit library: TTS_WRAPPER_LIB=/path/to/lib.so dotnet test
# 5. Swift (macOS; needs Xcode toolchain)
(cd bindings/swift && TTS_WRAPPER_LIB_DIR=$PWD/../../target/debug swift test)
```
CI (`.github/workflows/bindings.yml`) runs the full matrix on every push to
main and on PRs.
## Which binding should I use?
- **Rust app/plugin:** depend on the crate directly; the C ABI exists for
non-Rust hosts.
- **C/C++ app:** link the cdylib, include `tts_wrapper.h`. See
`bindings/c/tts_abi_harness.c` for a complete worked example.
- **Python:** `bindings/python/tts_wrapper.py` (ctypes, zero dependencies).
- **.NET / AAC apps on Windows:** the NuGet package bundles the x64 + x86
DLLs; `TtsClient` is the low-level client, `RustTtsClient` is a
drop-in `DotNetTtsWrapper.AbstractTtsClient` adapter.
- **Apple platforms:** SwiftPM package; in an Xcode app build the staticlib
and import the header as module `rust_tts_wrapper`.
- **Node/Electron:** `@aactools/tts-wrapper` (koffi-based; no node-gyp).
## Engine feature sets
Engines are compile-time cargo features; the `tts_get_engines` enumeration
reflects what was compiled in. The CI matrices build:
- Linux: `system,cloud` (speech-dispatcher + cloud engines)
- macOS: `avsynth,cloud`
- Windows: `sapi,cloud`
Credentials are passed as a JSON object string at `tts_create` time; each
engine's required keys are listed in `tts_get_engines`
(`credential_keys_json`).