rust-tts-wrapper 1.4.0

Cross-platform TTS wrapper with C API — mirrors js-tts-wrapper / SwiftTTSWrapper
# 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

| Area | Symbols |
|---|---|
| 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:

| Platform | Targets | Engines | Artifact |
|---|---|---|---|
| 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

| Directory | Package | Loads via | Test suite |
|---|---|---|---|
| [`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`).