{"id":27634966,"url":"https://github.com/willwade/js-tts-wrapper","last_synced_at":"2026-04-11T10:12:12.187Z","repository":{"id":286747126,"uuid":"962293851","full_name":"willwade/js-tts-wrapper","owner":"willwade","description":"JS TTS (Text to Speech) Wrapper Module enabling TTS for all TTS ","archived":false,"fork":false,"pushed_at":"2026-02-20T12:48:18.000Z","size":23227,"stargazers_count":15,"open_issues_count":4,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-20T14:41:30.653Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/willwade.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"docs/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-04-08T00:11:49.000Z","updated_at":"2026-02-20T12:48:22.000Z","dependencies_parsed_at":null,"dependency_job_id":"94ca3de2-d85b-4f6e-90df-1c48d9fcb79f","html_url":"https://github.com/willwade/js-tts-wrapper","commit_stats":null,"previous_names":["willwade/js-tts-wrapper"],"tags_count":69,"template":false,"template_full_name":null,"purl":"pkg:github/willwade/js-tts-wrapper","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willwade%2Fjs-tts-wrapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willwade%2Fjs-tts-wrapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willwade%2Fjs-tts-wrapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willwade%2Fjs-tts-wrapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/willwade","download_url":"https://codeload.github.com/willwade/js-tts-wrapper/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willwade%2Fjs-tts-wrapper/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31532339,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T16:28:08.000Z","status":"ssl_error","status_checked_at":"2026-04-07T16:28:06.951Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-04-23T19:40:06.711Z","updated_at":"2026-04-11T10:12:12.174Z","avatar_url":"https://github.com/willwade.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# js-tts-wrapper\n\nA JavaScript/TypeScript library that provides a unified API for working with multiple cloud-based Text-to-Speech (TTS) services. Inspired by [py3-TTS-Wrapper](https://github.com/willwade/tts-wrapper), it simplifies the use of services like Azure, Google Cloud, IBM Watson, and ElevenLabs.\n\n## Table of Contents\n\n- [Features](#features)\n- [Supported TTS Engines](#supported-tts-engines)\n- [Installation](#installation)\n  - [Installation](#installation-1)\n  - [Using npm scripts](#using-npm-scripts)\n- [Quick Start](#quick-start)\n- [Core Functionality](#core-functionality)\n  - [Voice Management](#voice-management)\n  - [Text Synthesis](#text-synthesis)\n  - [Audio Playback](#audio-playback)\n  - [File Output](#file-output)\n  - [Event Handling](#event-handling)\n- [SSML Support](#ssml-support)\n- [Speech Markdown Support](#speech-markdown-support)\n- [Engine-Specific Examples](#engine-specific-examples)\n- [Browser Support](#browser-support)\n- [API Reference](#api-reference)\n- [Contributing](#contributing)\n- [License](#license)\n- [Examples and Demos](#examples-and-demos)\n\n\n## Features\n\n- **Unified API**: Consistent interface across multiple TTS providers.\n- **SSML Support**: Use Speech Synthesis Markup Language to enhance speech synthesis\n- **Speech Markdown**: Optional support for easier speech markup\n- **Voice Selection**: Easily browse and select from available voices\n- **Streaming Synthesis**: Stream audio as it's being synthesized\n- **Playback Control**: Pause, resume, and stop audio playback\n- **Word Boundaries**: Get callbacks for word timing (where supported)\n- **File Output**: Save synthesized speech to audio files\n- **Browser Support**: Works in both Node.js (server) and browser environments (see engine support table below)\n\n## Supported TTS Engines\n\n| Factory Name | Class Name | Environment | Provider | Dependencies |\n|--------------|------------|-------------|----------|-------------|\n| `azure` | `AzureTTSClient` | Both | Microsoft Azure Cognitive Services | `@azure/cognitiveservices-speechservices`, `microsoft-cognitiveservices-speech-sdk` |\n| `google` | `GoogleTTSClient` | Both | Google Cloud Text-to-Speech | `@google-cloud/text-to-speech` |\n| `elevenlabs` | `ElevenLabsTTSClient` | Both | ElevenLabs | `node-fetch@2` (Node.js only) |\n| `watson` | `WatsonTTSClient` | Both | IBM Watson | None (uses fetch API) |\n| `openai` | `OpenAITTSClient` | Both | OpenAI | `openai` |\n| `modelslab` | `ModelsLabTTSClient` | Both | ModelsLab | None (uses fetch API) |\n| `upliftai` | `UpliftAITTSClient` | Both | UpLiftAI | None (uses fetch API) |\n| `playht` | `PlayHTTTSClient` | Both | PlayHT | `node-fetch@2` (Node.js only) |\n| `polly` | `PollyTTSClient` | Both | Amazon Web Services | `@aws-sdk/client-polly` |\n| `sherpaonnx` | `SherpaOnnxTTSClient` | Node.js | k2-fsa/sherpa-onnx | `sherpa-onnx-node`, `decompress`, `decompress-bzip2`, `decompress-tarbz2`, `decompress-targz`, `tar-stream` |\n| `sherpaonnx-wasm` | `SherpaOnnxWasmTTSClient` | Browser | k2-fsa/sherpa-onnx | None (WASM included) |\n| `espeak` | `EspeakNodeTTSClient` | Node.js | eSpeak NG | `text2wav` |\n| `espeak-wasm` | `EspeakBrowserTTSClient` | Both | eSpeak NG | `mespeak` (Node.js) or meSpeak.js (browser) |\n| `sapi` | `SAPITTSClient` | Node.js | Windows Speech API (SAPI) | None (uses PowerShell) |\n| `witai` | `WitAITTSClient` | Both | Wit.ai | None (uses fetch API) |\n| `cartesia` | `CartesiaTTSClient` | Both | Cartesia | None (uses fetch API) |\n| `deepgram` | `DeepgramTTSClient` | Both | Deepgram | None (uses fetch API) |\n| `hume` | `HumeTTSClient` | Both | Hume AI | None (uses fetch API) |\n| `xai` | `XAITTSClient` | Both | xAI (Grok) | None (uses fetch API) |\n| `fishaudio` | `FishAudioTTSClient` | Both | Fish Audio | None (uses fetch API) |\n| `mistral` | `MistralTTSClient` | Both | Mistral AI | None (uses fetch API) |\n| `murf` | `MurfTTSClient` | Both | Murf AI | None (uses fetch API) |\n| `unrealspeech` | `UnrealSpeechTTSClient` | Both | Unreal Speech | None (uses fetch API) |\n| `resemble` | `ResembleTTSClient` | Both | Resemble AI | None (uses fetch API) |\n\n**Factory Name**: Use with `createTTSClient('factory-name', credentials)`\n**Class Name**: Use with direct import `import { ClassName } from 'js-tts-wrapper'`\n**Environment**: Node.js = server-side only, Browser = browser-compatible, Both = works in both environments\n\n\n### Important: SherpaONNX is optional and does not affect other engines\n\n- Importing `js-tts-wrapper` does NOT load `sherpa-onnx-node`.\n- Cloud engines (Azure, Google, Polly, OpenAI, etc.) work without any SherpaONNX packages installed.\n- Only when you instantiate `SherpaOnnxTTSClient` (Node-only) will the library look for `sherpa-onnx-node` and its platform package. If SherpaONNX is not installed, the Sherpa engine will gracefully warn/fallback, and other engines remain unaffected.\n- See the Installation section below for how to install SherpaONNX dependencies only if you plan to use that engine.\n\n### Timing and Audio Format Capabilities\n\n#### Word Boundary and Timing Support\n\n| Engine | Word Boundaries | Timing Source | Character-Level | Accuracy |\n|--------|----------------|---------------|-----------------|----------|\n| **ElevenLabs** | ✅ | **Real API data** | ✅ **NEW!** | **High** |\n| **Azure** | ✅ | **Real API data** | ❌ | **High** |\n| **Google** | ✅ | Estimated | ❌ | Low |\n| **Watson** | ✅ | Estimated | ❌ | Low |\n| **UpLiftAI** | ✅ | Estimated | ❌ | Low |\n| **OpenAI** | ✅ | Estimated | ❌ | Low |\n| **WitAI** | ✅ | Estimated | ❌ | Low |\n| **PlayHT** | ✅ | Estimated | ❌ | Low |\n| **Polly** | ✅ | Estimated | ❌ | Low |\n| **eSpeak** | ✅ | Estimated | ❌ | Low |\n| **eSpeak-WASM** | ✅ | Estimated | ❌ | Low |\n| **SherpaOnnx** | ✅ | Estimated | ❌ | Low |\n| **SherpaOnnx-WASM** | ✅ | Estimated | ❌ | Low |\n| **SAPI** | ✅ | Estimated | ❌ | Low |\n| **Cartesia** | ✅ | Estimated | ❌ | Low |\n| **Deepgram** | ✅ | Estimated | ❌ | Low |\n| **Hume** | ✅ | Estimated | ❌ | Low |\n| **xAI** | ✅ | Estimated | ❌ | Low |\n| **Fish Audio** | ✅ | Estimated | ❌ | Low |\n| **Mistral** | ✅ | Estimated | ❌ | Low |\n| **Murf** | ✅ | Estimated | ❌ | Low |\n| **Unreal Speech** | ✅ | Estimated | ❌ | Low |\n| **Resemble** | ✅ | Estimated | ❌ | Low |\n\n**Character-Level Timing**: Only ElevenLabs provides precise character-level timing data via the `/with-timestamps` endpoint, enabling the most accurate word highlighting and speech synchronization.\n\n#### Audio Format Conversion Support\n\n| Engine | Native Format | WAV Support | MP3 Conversion | Conversion Method |\n|--------|---------------|-------------|----------------|-------------------|\n| **All Engines** | Varies | ✅ | ✅ | Pure JavaScript (lamejs) |\n\n**Format Conversion**: All engines support WAV and MP3 output through automatic format conversion. The wrapper uses pure JavaScript conversion (lamejs) when FFmpeg is not available, ensuring cross-platform compatibility without external dependencies.\n\n## Installation\n\nThe library uses a modular approach where TTS engine-specific dependencies are optional. You can install the package and its dependencies as follows:\n\n### npm install (longer route but more explicit)\n\n```bash\n# Install the base package\nnpm install js-tts-wrapper\n\n# Install dependencies for specific engines\nnpm install @azure/cognitiveservices-speechservices microsoft-cognitiveservices-speech-sdk  # For Azure\nnpm install @google-cloud/text-to-speech  # For Google Cloud\nnpm install @aws-sdk/client-polly  # For AWS Polly\nnpm install node-fetch@2  # For ElevenLabs and PlayHT\nnpm install openai  # For OpenAI\nnpm install sherpa-onnx-node decompress decompress-bzip2 decompress-tarbz2 decompress-targz tar-stream  # For SherpaOnnx\nnpm install text2wav  # For eSpeak NG (Node.js)\nnpm install mespeak  # For eSpeak NG-WASM (Node.js)\nnpm install say  # For System TTS (Node.js)\nnpm install sound-play pcm-convert  # For Node.js audio playback\n```\n\n### Using npm scripts\n\nAfter installing the base package, you can use the npm scripts provided by the package to install specific engine dependencies:\n\n```bash\n# Navigate to your project directory where js-tts-wrapper is installed\ncd your-project\n\n# Install Azure dependencies\nnpx js-tts-wrapper@latest run install:azure\n\n# Install SherpaOnnx dependencies\nnpx js-tts-wrapper@latest run install:sherpaonnx\n\n# Install eSpeak NG dependencies (Node.js)\nnpx js-tts-wrapper@latest run install:espeak\n\n# Install eSpeak NG-WASM dependencies (Node.js)\nnpx js-tts-wrapper@latest run install:espeak-wasm\n\n# Install System TTS dependencies (Node.js)\nnpx js-tts-wrapper@latest run install:system\n\n# Install Node.js audio playback dependencies\nnpx js-tts-wrapper@latest run install:node-audio\n\n# Install all development dependencies\nnpx js-tts-wrapper@latest run install:all-dev\n```\n\n## Quick Start\n\n### Direct Instantiation\n\n#### ESM (ECMAScript Modules)\n\n```javascript\nimport { AzureTTSClient } from 'js-tts-wrapper';\n\n// Initialize the client with your credentials\nconst tts = new AzureTTSClient({\n  subscriptionKey: 'your-subscription-key',\n  region: 'westeurope'\n});\n\n// List available voices\nconst voices = await tts.getVoices();\nconsole.log(voices);\n\n// Set a voice\ntts.setVoice('en-US-AriaNeural');\n\n// Speak some text\nawait tts.speak('Hello, world!');\n\n// Use SSML for more control\nconst ssml = '\u003cspeak\u003eHello \u003cbreak time=\"500ms\"/\u003e world!\u003c/speak\u003e';\nawait tts.speak(ssml);\n```\n\n#### CommonJS\n\n```javascript\nconst { AzureTTSClient } = require('js-tts-wrapper');\n\n// Initialize the client with your credentials\nconst tts = new AzureTTSClient({\n  subscriptionKey: 'your-subscription-key',\n  region: 'westeurope'\n});\n\n// Use async/await within an async function\nasync function runExample() {\n  // List available voices\n  const voices = await tts.getVoices();\n  console.log(voices);\n\n  // Set a voice\n  tts.setVoice('en-US-AriaNeural');\n\n  // Speak some text\n  await tts.speak('Hello, world!');\n\n  // Use SSML for more control\n  const ssml = '\u003cspeak\u003eHello \u003cbreak time=\"500ms\"/\u003e world!\u003c/speak\u003e';\n  await tts.speak(ssml);\n}\n\nrunExample().catch(console.error);\n```\n\n### Using the Factory Pattern\n\nThe library provides a factory function to create TTS clients dynamically based on the engine name:\n\n#### ESM (ECMAScript Modules)\n\n```javascript\nimport { createTTSClient } from 'js-tts-wrapper';\n\n// Create a TTS client using the factory function\nconst tts = createTTSClient('azure', {\n  subscriptionKey: 'your-subscription-key',\n  region: 'westeurope'\n});\n\n// Use the client as normal\nawait tts.speak('Hello from the factory pattern!');\n```\n\n#### CommonJS\n\n```javascript\nconst { createTTSClient } = require('js-tts-wrapper');\n\n// Create a TTS client using the factory function\nconst tts = createTTSClient('azure', {\n  subscriptionKey: 'your-subscription-key',\n  region: 'westeurope'\n});\n\nasync function runExample() {\n  // Use the client as normal\n  await tts.speak('Hello from the factory pattern!');\n}\n\nrunExample().catch(console.error);\n```\n\nThe factory supports all engines: `'azure'`, `'google'`, `'polly'`, `'elevenlabs'`, `'openai'`, `'modelslab'`, `'playht'`, `'watson'`, `'witai'`, `'sherpaonnx'`, `'sherpaonnx-wasm'`, `'espeak'`, `'espeak-wasm'`, `'sapi'`, `'cartesia'`, `'deepgram'`, `'hume'`, `'xai'`, `'fishaudio'`, `'mistral'`, `'murf'`, `'unrealspeech'`, `'resemble'`, etc.\n\n## Core Functionality\n\nAll TTS engines in js-tts-wrapper implement a common set of methods and features through the AbstractTTSClient class. This ensures consistent behavior across different providers.\n\n### Voice Management\n\n```typescript\n// Get all available voices\nconst voices = await tts.getVoices();\n\n// Get voices for a specific language\nconst englishVoices = await tts.getVoicesByLanguage('en-US');\n\n// Set the voice to use\ntts.setVoice('en-US-AriaNeural');\n```\n\nThe library includes a robust [Language Normalization](docs/LANGUAGE_NORMALIZATION.md) system that standardizes language codes across different TTS engines. This allows you to:\n\n- Use BCP-47 codes (e.g., 'en-US') or ISO 639-3 codes (e.g., 'eng') interchangeably\n- Get consistent language information regardless of the TTS engine\n- Filter voices by language using any standard format\n\n### Credential Validation\n\nAll TTS engines support standardized credential validation to help you verify your setup before making requests:\n\n```typescript\n// Basic validation - returns boolean\nconst isValid = await tts.checkCredentials();\nif (!isValid) {\n  console.error('Invalid credentials!');\n}\n\n// Detailed validation - returns comprehensive status\nconst status = await tts.getCredentialStatus();\nconsole.log(status);\n/*\n{\n  valid: true,\n  engine: 'openai',\n  environment: 'node',\n  requiresCredentials: true,\n  credentialTypes: ['apiKey'],\n  message: 'openai credentials are valid and 10 voices are available'\n}\n*/\n```\n\n**Engine Requirements:**\n- **Cloud engines** (OpenAI, Azure, Google, etc.): Require API keys/credentials\n- **Local engines** (eSpeak, SAPI, SherpaOnnx): No credentials needed\n- **Environment-specific**: Some engines work only in Node.js or browser\n\nSee the [Credential Validation Guide](docs/CREDENTIAL_VALIDATION.md) for detailed requirements and troubleshooting.\n\n### Text Synthesis\n\n```typescript\n// Convert text to audio bytes (Uint8Array)\nconst audioBytes = await tts.synthToBytes('Hello, world!');\n\n// Stream synthesis with word boundary information\nconst { audioStream, wordBoundaries } = await tts.synthToBytestream('Hello, world!');\n```\n\n### Audio Playback\n\n```typescript\n// Traditional text synthesis and playback\nawait tts.speak('Hello, world!');\n\n// NEW: Play audio from different sources without re-synthesizing\n// Play from file\nawait tts.speak({ filename: 'path/to/audio.mp3' });\n\n// Play from audio bytes\nconst audioBytes = await tts.synthToBytes('Hello, world!');\nawait tts.speak({ audioBytes: audioBytes });\n\n// Play from audio stream\nconst { audioStream } = await tts.synthToBytestream('Hello, world!');\nawait tts.speak({ audioStream: audioStream });\n\n// All input types work with speakStreamed too\nawait tts.speakStreamed({ filename: 'path/to/audio.mp3' });\n\n// Playback control\ntts.pause();  // Pause playback\ntts.resume(); // Resume playback\ntts.stop();   // Stop playback\n\n// Stream synthesis and play with word boundary callbacks\nawait tts.startPlaybackWithCallbacks('Hello world', (word, start, end) =\u003e {\n  console.log(`Word: ${word}, Start: ${start}s, End: ${end}s`);\n});\n```\n\n#### Benefits of Multi-Source Audio Playback\n\n- **Avoid Double Synthesis**: Use `synthToFile()` to save audio, then play the same file with `speak({ filename })` without re-synthesizing\n- **Platform Independent**: Works consistently across browser and Node.js environments\n- **Efficient Reuse**: Play the same audio bytes or stream multiple times without regenerating\n- **Flexible Input**: Choose the most convenient input source for your use case\n\n\u003e **Note**: Audio playback with `speak()` and `speakStreamed()` methods is supported in both browser environments and Node.js environments with the optional `sound-play` package installed. To enable Node.js audio playback, install the required packages with `npm install sound-play pcm-convert` or use the npm script `npx js-tts-wrapper@latest run install:node-audio`.\n\n### File Output\n\n```typescript\n// Save synthesized speech to a file\nawait tts.synthToFile('Hello, world!', 'output', 'mp3');\n```\n\n### Event Handling\n\n```typescript\n// Register event handlers\ntts.on('start', () =\u003e console.log('Speech started'));\ntts.on('end', () =\u003e console.log('Speech ended'));\ntts.on('boundary', (word, start, end) =\u003e {\n  console.log(`Word: ${word}, Start: ${start}s, End: ${end}s`);\n});\n\n// Alternative event connection\ntts.connect('onStart', () =\u003e console.log('Speech started'));\ntts.connect('onEnd', () =\u003e console.log('Speech ended'));\n```\n\n### Word Boundary Events and Timing\n\nWord boundary events provide precise timing information for speech synchronization, word highlighting, and interactive applications.\n\n#### Basic Word Boundary Usage\n\n```typescript\n// Enable word boundary events\ntts.on('boundary', (word, startTime, endTime) =\u003e {\n  console.log(`\"${word}\" spoken from ${startTime}s to ${endTime}s`);\n});\n\nawait tts.speak('Hello world, this is a test.');\n// Output:\n// \"Hello\" spoken from 0.000s to 0.300s\n// \"world,\" spoken from 0.300s to 0.600s\n// \"this\" spoken from 0.600s to 0.900s\n// ...\n```\n\n#### Advanced Timing with Character-Level Precision (ElevenLabs)\n\n```typescript\n// ElevenLabs: Enable character-level timing for maximum accuracy\nconst tts = createTTSClient('elevenlabs');\n\n// Method 1: Using synthToBytestream with timestamps\nconst result = await tts.synthToBytestream('Hello world', {\n  useTimestamps: true\n});\n\nconsole.log(`Generated ${result.wordBoundaries.length} word boundaries:`);\nresult.wordBoundaries.forEach(wb =\u003e {\n  const startSec = wb.offset / 10000;\n  const durationSec = wb.duration / 10000;\n  console.log(`\"${wb.text}\": ${startSec}s - ${startSec + durationSec}s`);\n});\n\n// Method 2: Using enhanced callback support\nawait tts.startPlaybackWithCallbacks('Hello world', (word, start, end) =\u003e {\n  console.log(`Precise timing: \"${word}\" from ${start}s to ${end}s`);\n});\n```\n\n#### Real-Time Word Highlighting Example\n\n```typescript\n// Example: Real-time word highlighting for accessibility\nconst textElement = document.getElementById('text');\nconst words = 'Hello world, this is a test.'.split(' ');\nlet wordIndex = 0;\n\ntts.on('boundary', (word, startTime, endTime) =\u003e {\n  // Highlight current word\n  if (wordIndex \u003c words.length) {\n    textElement.innerHTML = words.map((w, i) =\u003e\n      i === wordIndex ? `\u003cmark\u003e${w}\u003c/mark\u003e` : w\n    ).join(' ');\n    wordIndex++;\n  }\n});\n\nawait tts.speak('Hello world, this is a test.', { useWordBoundary: true });\n```\n\n## SSML Support\n\nThe library provides comprehensive SSML (Speech Synthesis Markup Language) support with engine-specific capabilities:\n\n### SSML-Supported Engines\n\nThe following engines **support SSML**:\n- **Google Cloud TTS** - Full SSML support with all elements\n- **Microsoft Azure** - Full SSML support with voice-specific features\n- **Amazon Polly** - Dynamic SSML support based on voice engine type (standard/long-form: full, neural/generative: limited)\n- **WitAI** - Full SSML support\n- **SAPI (Windows)** - Full SSML support\n- **eSpeak/eSpeak-WASM** - SSML support with subset of elements\n\n### Non-SSML Engines\n\nThe following engines **automatically strip SSML tags** and convert to plain text:\n- **ElevenLabs** - SSML tags are removed, plain text is synthesized\n- **OpenAI** - SSML tags are removed, plain text is synthesized\n- **PlayHT** - SSML tags are removed, plain text is synthesized\n- **ModelsLab** - SSML tags are removed, plain text is synthesized\n- **SherpaOnnx/SherpaOnnx-WASM** - SSML tags are removed, plain text is synthesized\n- **Cartesia** - SSML tags removed; audio tags (`[laugh]`, `[sigh]`, etc.) mapped to `\u003cemotion\u003e` for sonic-3, stripped for others\n- **Deepgram** - SSML tags are removed, plain text is synthesized\n- **Hume** - SSML tags are removed, plain text is synthesized\n- **xAI** - SSML tags are removed; audio tags passed natively for grok-tts\n- **Fish Audio** - SSML tags removed; audio tags passed natively for s2-pro\n- **Mistral** - SSML tags are removed, plain text is synthesized\n- **Murf** - SSML tags are removed, plain text is synthesized\n- **Unreal Speech** - SSML tags are removed, plain text is synthesized\n- **Resemble** - SSML tags are removed, plain text is synthesized\n\n### Usage Examples\n\n```typescript\n// Use SSML directly (works with supported engines)\nconst ssml = `\n\u003cspeak\u003e\n  \u003cprosody rate=\"slow\" pitch=\"low\"\u003e\n    This text will be spoken slowly with a low pitch.\n  \u003c/prosody\u003e\n  \u003cbreak time=\"500ms\"/\u003e\n  \u003cemphasis level=\"strong\"\u003eThis text is emphasized.\u003c/emphasis\u003e\n\u003c/speak\u003e\n`;\nawait tts.speak(ssml);\n\n// Or use the SSML builder\nconst ssmlText = tts.ssml\n  .prosody({ rate: 'slow', pitch: 'low' }, 'This text will be spoken slowly with a low pitch.')\n  .break(500)\n  .emphasis('strong', 'This text is emphasized.')\n  .toString();\n\nawait tts.speak(ssmlText);\n```\n\n### Engine-Specific SSML Notes\n\n- **Amazon Polly**: SSML support varies by voice engine type:\n  - **Standard voices**: Full SSML support including all tags\n  - **Long-form voices**: Full SSML support including all tags\n  - **Neural voices**: Limited SSML support (no emphasis, limited prosody)\n  - **Generative voices**: Limited SSML support (partial tag support)\n  - The library automatically detects voice engine types and handles SSML appropriately\n- **Microsoft Azure**: Supports voice-specific SSML elements and custom voice tags\n  - Supports MS-specific tags like `\u003cmstts:express-as\u003e` for emotional styles\n  - The library automatically injects the required `xmlns:mstts` namespace when needed\n- **Google Cloud**: Supports the most comprehensive set of SSML elements\n- **WitAI**: Full SSML support according to W3C specification\n- **SAPI**: Windows-native SSML support with system voice capabilities\n- **eSpeak**: Supports SSML subset including prosody, breaks, and emphasis elements\n\n### Raw SSML Pass-Through\n\nSpeech Markdown and the built-in SSML helpers cover most use cases, but there are times when you need to send hand-crafted SSML—custom namespaces, experimental tags, or markup generated by another tool. In those cases you can use the `rawSSML` flag to bypass Speech Markdown conversion and SSML validation:\n\n```typescript\n// Example: Azure multi-speaker dialog, currently easier to author as raw SSML\nconst azureDialogSSML = `\u003cspeak xmlns:mstts=\"https://www.w3.org/2001/mstts\"\u003e\n  \u003cmstts:dialog\u003e\n    \u003cmstts:dialogturn speaker=\"narrator\"\u003e\n      Welcome to the demo.\n    \u003c/mstts:dialogturn\u003e\n    \u003cmstts:dialogturn speaker=\"character\"\u003e\n      Hey there! This turn uses a different voice.\n    \u003c/mstts:dialogturn\u003e\n  \u003c/mstts:dialog\u003e\n\u003c/speak\u003e`;\n\nawait tts.speak(azureDialogSSML, { rawSSML: true });\n\n// When rawSSML=true the wrapper will:\n// 1. Skip Speech Markdown conversion\n// 2. Skip SSML validation / normalization\n// 3. Pass the SSML through unchanged (aside from ensuring \u003cspeak\u003e exists)\n```\n\n**Important:** When you opt into `rawSSML`, you are responsible for producing provider-compliant SSML. The wrapper only wraps the payload with `\u003cspeak\u003e` if missing and adds obvious namespaces, but it does not attempt to sanitize or validate the markup.\n\nNeed to mix-and-match? Convert Speech Markdown to SSML yourself (using `speechmarkdown-js` directly) and then send it through `rawSSML: true` to avoid duplicate parsing:\n\n```typescript\nimport { SpeechMarkdown } from \"speechmarkdown-js\";\n\nconst markdown = \"(Hello!)[excited:\\\"1.5\\\"] with (characters)[character:superhero]\";\nconst ssml = await SpeechMarkdown.toSSML(markdown, { platform: \"microsoft-azure\" });\n\nawait tts.speak(ssml, { rawSSML: true });\n```\n\nIf you hit a Speech Markdown feature gap, consider contributing upstream—the library powers our conversion pipeline, so improvements there benefit every js-tts-wrapper user.\n\n## Speech Markdown Support\n\nThe library supports Speech Markdown for easier speech formatting across **all engines**. Speech Markdown is powered by the **[speechmarkdown-js](https://github.com/speechmarkdown/speechmarkdown-js)** library, which provides comprehensive platform-specific support.\n\n### How Speech Markdown Works\n\n- **SSML-supported engines**: Speech Markdown is converted to SSML (with platform-specific optimizations), then processed natively\n- **Non-SSML engines**: Speech Markdown is converted to SSML, then SSML tags are stripped to plain text\n\n### Platform-Specific Features\n\nThe speechmarkdown-js library ships dedicated formatters for every major provider:\n\n- **Microsoft Azure**: Automatic `mstts` namespace injection, inline `\u003clang\u003e` sections, and 27 `mstts:express-as` styles (excited, chat, newscaster, customerservice, etc.) with optional `styledegree` intensity. Section modifiers such as `#[excited]` are supported as long as you leave a blank line before the section and close it with `#[defaults]` (or another section tag).\n- **Amazon Polly**: Emotional styles and neural/standard voice effects that map cleanly onto Polly’s SSML dialect.\n- **Google Cloud**: Google Assistant style tags, multi-language voices, and automatic `\u003clang\u003e` handling.\n- **ElevenLabs**: A formatter that emits ElevenLabs’ prompt markup (`\u003cbreak time=\"…\"\u003e`, IPA phonemes, etc.), so you can feed Speech Markdown directly into ElevenLabs if you bypass the wrapper or use `rawSSML`.\n- **WitAI / Microsoft SAPI / IBM Watson / W3C**: Full SSML support for their respective dialects.\n- **And more**: See the [speechmarkdown-js README](https://github.com/speechmarkdown/speechmarkdown-js?tab=readme-ov-file#speechmarkdown-js) for the complete, always up-to-date list.\n\n### Usage\n\n```typescript\n// Use Speech Markdown with any engine\nconst markdown =\n  \"Hello [500ms] world! ++This text is emphasized++ (slowly)[rate:\\\"slow\\\"] (high)[pitch:\\\"high\\\"] (loudly)[volume:\\\"loud\\\"]\";\nawait tts.speak(markdown, { useSpeechMarkdown: true });\n\n// If you omit useSpeechMarkdown, the wrapper auto-enables it when Speech Markdown syntax is detected.\n\n// Platform-specific Speech Markdown features\n// Azure: Section modifiers map to mstts:express-as\nconst azureMarkdown = `\n\n#[excited]\nThis entire section is excited!\nMultiple sentences work too.\n\n#[defaults]\nBack to the neutral voice.\n`;\nawait azureTts.speak(azureMarkdown, { useSpeechMarkdown: true });\n\n// Speech Markdown works with all engines\nconst ttsGoogle = new TTSClient('google');\nconst ttsElevenLabs = new TTSClient('elevenlabs');\n\n// Both will handle Speech Markdown appropriately\nawait ttsGoogle.speak(markdown, { useSpeechMarkdown: true });     // Converts to SSML\nawait ttsElevenLabs.speak(markdown, { useSpeechMarkdown: true }); // Uses the ElevenLabs formatter (tags are stripped before hitting the API)\n\n// Need ElevenLabs prompt markup?\n// Convert directly and pass through rawSSML or your own API client:\nimport { SpeechMarkdown as SMSpeechMarkdown } from \"speechmarkdown-js\";\nconst elevenLabsMarkup = await new SMSpeechMarkdown().toSSML(markdown, { platform: \"elevenlabs\" });\nawait elevenLabsClient.speak(elevenLabsMarkup, { rawSSML: true });\n```\n\n### Supported Speech Markdown Elements\n\n- `[500ms]` or `[break:\"500ms\"]` - Pauses/breaks\n- `++text++` or `+text+` - Text emphasis\n- `(text)[rate:\"slow\"]` - Speech rate control\n- `(text)[pitch:\"high\"]` - Pitch control\n- `(text)[volume:\"loud\"]` - Volume control\n- **Platform-specific**: See [speechmarkdown-js documentation](https://github.com/speechmarkdown/speechmarkdown-js) for platform-specific features like Azure's express-as styles\n\n\n### Node \u0026 CI: Configuring the Speech Markdown Converter\n\nThe full **speechmarkdown-js** converter now loads by default in both Node and browser environments. If you need to opt out (for very small lambda bundles or for deterministic tests), you can:\n\n```bash\n# Disable globally\nSPEECHMARKDOWN_DISABLE=true npm test\n\n# Or force-enable/disable explicitly\nSPEECHMARKDOWN_ENABLE=false npm test\nSPEECHMARKDOWN_ENABLE=true npm test\n```\n\nOr disable/enable programmatically:\n\n```ts\nimport { SpeechMarkdown } from \"js-tts-wrapper\";\n\nSpeechMarkdown.configureSpeechMarkdown({ enabled: false }); // fallback-only\nSpeechMarkdown.configureSpeechMarkdown({ enabled: true });  // ensure full parser\n```\n\nAlternatively, you can import the function directly:\n\n```ts\nimport { configureSpeechMarkdown } from \"js-tts-wrapper\";\n\nconfigureSpeechMarkdown({ enabled: true }); // ensure full parser\n```\n\nWhen disabled, js-tts-wrapper falls back to the lightweight built-in converter (suitable for basic `[break]` patterns). Re-enable it to regain advanced tags (Azure express-as, Polly styles, google:style, etc.).\n\n### Engine Compatibility\n\n| Engine | Speech Markdown Support | Processing Method |\n|--------|------------------------|-------------------|\n| Google Cloud TTS | ✅ Full | → SSML → Native processing |\n| Microsoft Azure | ✅ Full | → SSML → Native processing |\n| Amazon Polly | ✅ Full | → SSML → Dynamic processing (engine-dependent) |\n| WitAI | ✅ Full | → SSML → Native processing |\n| SAPI | ✅ Full | → SSML → Native processing |\n| eSpeak | ✅ Full | → SSML → Native processing |\n| ElevenLabs | ✅ Converted | → SSML → Plain text |\n| OpenAI | ✅ Converted | → SSML → Plain text |\n| PlayHT | ✅ Converted | → SSML → Plain text |\n| SherpaOnnx | ✅ Converted | → SSML → Plain text |\n| Cartesia | ✅ Converted | → SSML → Plain text |\n| Deepgram | ✅ Converted | → SSML → Plain text |\n| Hume | ✅ Converted | → SSML → Plain text |\n| xAI | ✅ Converted | → SSML → Plain text |\n| Fish Audio | ✅ Converted | → SSML → Plain text |\n| Mistral | ✅ Converted | → SSML → Plain text |\n| Murf | ✅ Converted | → SSML → Plain text |\n| Unreal Speech | ✅ Converted | → SSML → Plain text |\n| Resemble | ✅ Converted | → SSML → Plain text |\n\n### Speech Markdown vs Raw SSML: When to Use Each\n\nThe library provides two complementary approaches for controlling speech synthesis:\n\n| Approach | Use Case | Example |\n|----------|----------|---------|\n| **Speech Markdown** | Easy, readable syntax for common features | `(Hello!)[excited:\"1.5\"]` |\n| **Raw SSML** | Direct control, advanced features, provider-specific tags | `\u003cmstts:express-as style=\"friendly\"\u003eHello!\u003c/mstts:express-as\u003e` |\n\n**Speech Markdown Flow:**\n```\nSpeech Markdown → speechmarkdown-js → Platform-specific SSML → Provider\n```\n\n**Raw SSML Flow:**\n```\nRaw SSML → Minimal processing → Provider\n```\n\n**When to use Speech Markdown:**\n- You want readable, maintainable code\n- You're using common features (breaks, emphasis, rate, pitch, volume)\n- You want platform-specific optimizations automatically applied\n- You want the same code to work across multiple TTS engines\n\n**When to use Raw SSML with `rawSSML: true`:**\n- You need advanced provider-specific features (e.g., Azure's mstts:dialog for multi-speaker)\n- You're working with SSML generated by other tools\n- You need fine-grained control over SSML structure\n- You want to bypass validation for experimental features\n\n**Combining both approaches:**\n```typescript\n// Use speechmarkdown-js directly for advanced features\nimport { SpeechMarkdown } from 'speechmarkdown-js';\n\nconst markdown = \"(This is exciting!)[excited:\\\"1.5\\\"] with (multi-speaker)[mstts:dialog]\";\nconst ssml = await SpeechMarkdown.toSSML(markdown, \"microsoft-azure\");\n\n// Pass the result with rawSSML to bypass wrapper validation\nawait tts.speak(ssml, { rawSSML: true });\n```\n\n## Engine-Specific Examples\n\nEach TTS engine has its own specific setup. Here are examples for each supported engine in both ESM and CommonJS formats:\n\n### Azure\n\n#### ESM\n```javascript\nimport { AzureTTSClient } from 'js-tts-wrapper';\n\nconst tts = new AzureTTSClient({\n  subscriptionKey: 'your-subscription-key',\n  region: 'westeurope'\n});\n\nawait tts.speak('Hello from Azure!');\n```\n\n#### CommonJS\n```javascript\nconst { AzureTTSClient } = require('js-tts-wrapper');\n\nconst tts = new AzureTTSClient({\n  subscriptionKey: 'your-subscription-key',\n  region: 'westeurope'\n});\n\n// Inside an async function\nawait tts.speak('Hello from Azure!');\n```\n\n### Google Cloud\n\nNote: Google Cloud TTS supports both authentication methods — Service Account (Node SDK) and API key (REST, browser‑safe).\n\n\n#### ESM\n```javascript\nimport { GoogleTTSClient } from 'js-tts-wrapper';\n\nconst tts = new GoogleTTSClient({\n  keyFilename: '/path/to/service-account-key.json'\n});\n\nawait tts.speak('Hello from Google Cloud!');\n```\n\n#### CommonJS\n```javascript\nconst { GoogleTTSClient } = require('js-tts-wrapper');\n\nconst tts = new GoogleTTSClient({\n  keyFilename: '/path/to/service-account-key.json'\n});\n\n// Inside an async function\nawait tts.speak('Hello from Google Cloud!');\n```\n\n#### API key mode (Node or Browser)\n\nGoogle Cloud Text-to-Speech also supports an API key over the REST API. This is browser-safe and requires no service account file. Restrict the key in Google Cloud Console (enable only Text-to-Speech API and restrict by HTTP referrer for browser use).\n\nESM (Node or Browser):\n```javascript\nimport { GoogleTTSClient } from 'js-tts-wrapper';\n\nconst tts = new GoogleTTSClient({\n  apiKey: process.env.GOOGLECLOUDTTS_API_KEY || 'your-api-key',\n  // optional defaults\n  voiceId: 'en-US-Wavenet-D',\n  lang: 'en-US'\n});\n\nawait tts.speak('Hello from Google TTS with API key!');\n```\n\nCommonJS (Node):\n```javascript\nconst { GoogleTTSClient } = require('js-tts-wrapper');\n\nconst tts = new GoogleTTSClient({\n  apiKey: process.env.GOOGLECLOUDTTS_API_KEY || 'your-api-key'\n});\n\n(async () =\u003e {\n  await tts.speak('Hello from Google TTS with API key!');\n})();\n```\n\nNotes:\n- REST v1 does not return word timepoints; the wrapper provides estimated timings for boundary events.\n- For true timings, use service account credentials (Node) where the beta client can be used.\n- Environment variable supported by examples/tests: `GOOGLECLOUDTTS_API_KEY`.\n\n\n### AWS Polly\n\n#### ESM\n```javascript\nimport { PollyTTSClient } from 'js-tts-wrapper';\n\nconst tts = new PollyTTSClient({\n  region: 'us-east-1',\n  accessKeyId: 'your-access-key-id',\n  secretAccessKey: 'your-secret-access-key'\n});\n\nawait tts.speak('Hello from AWS Polly!');\n```\n\n#### CommonJS\n```javascript\nconst { PollyTTSClient } = require('js-tts-wrapper');\n\nconst tts = new PollyTTSClient({\n  region: 'us-east-1',\n  accessKeyId: 'your-access-key-id',\n  secretAccessKey: 'your-secret-access-key'\n});\n\n// Inside an async function\nawait tts.speak('Hello from AWS Polly!');\n```\n\n### ElevenLabs\n\n#### ESM\n```javascript\nimport { ElevenLabsTTSClient } from 'js-tts-wrapper';\n\nconst tts = new ElevenLabsTTSClient({\n  apiKey: 'your-api-key'\n});\n\nawait tts.speak('Hello from ElevenLabs!');\n```\n\n#### CommonJS\n```javascript\nconst { ElevenLabsTTSClient } = require('js-tts-wrapper');\n\nconst tts = new ElevenLabsTTSClient({\n  apiKey: 'your-api-key'\n});\n\n// Inside an async function\nawait tts.speak('Hello from ElevenLabs!');\n```\n\n### OpenAI\n\n#### ESM\n```javascript\nimport { OpenAITTSClient } from 'js-tts-wrapper';\n\nconst tts = new OpenAITTSClient({\n  apiKey: 'your-api-key'\n});\n\nawait tts.speak('Hello from OpenAI!');\n```\n\n#### CommonJS\n```javascript\nconst { OpenAITTSClient } = require('js-tts-wrapper');\n\nconst tts = new OpenAITTSClient({\n  apiKey: 'your-api-key'\n});\n\n// Inside an async function\nawait tts.speak('Hello from OpenAI!');\n```\n\n### PlayHT\n\n#### ESM\n```javascript\nimport { PlayHTTTSClient } from 'js-tts-wrapper';\n\nconst tts = new PlayHTTTSClient({\n  apiKey: 'your-api-key',\n  userId: 'your-user-id'\n});\n\nawait tts.speak('Hello from PlayHT!');\n```\n\n#### CommonJS\n```javascript\nconst { PlayHTTTSClient } = require('js-tts-wrapper');\n\nconst tts = new PlayHTTTSClient({\n  apiKey: 'your-api-key',\n  userId: 'your-user-id'\n});\n\n// Inside an async function\nawait tts.speak('Hello from PlayHT!');\n```\n\n### IBM Watson\n\n#### ESM\n```javascript\nimport { WatsonTTSClient } from 'js-tts-wrapper';\n\nconst tts = new WatsonTTSClient({\n  apiKey: 'your-api-key',\n  region: 'us-south',\n  instanceId: 'your-instance-id'\n});\n\nawait tts.speak('Hello from IBM Watson!');\n```\n\n#### CommonJS\n```javascript\nconst { WatsonTTSClient } = require('js-tts-wrapper');\n\nconst tts = new WatsonTTSClient({\n  apiKey: 'your-api-key',\n  region: 'us-south',\n  instanceId: 'your-instance-id'\n});\n\n// Inside an async function\nawait tts.speak('Hello from IBM Watson!');\n```\n\n### Wit.ai\n\n#### ESM\n```javascript\nimport { WitAITTSClient } from 'js-tts-wrapper';\n\nconst tts = new WitAITTSClient({\n  token: 'your-wit-ai-token'\n});\n\nawait tts.speak('Hello from Wit.ai!');\n```\n\n#### CommonJS\n```javascript\nconst { WitAITTSClient } = require('js-tts-wrapper');\n\nconst tts = new WitAITTSClient({\n  token: 'your-wit-ai-token'\n});\n\n// Inside an async function\nawait tts.speak('Hello from Wit.ai!');\n```\n\n### SherpaOnnx (Offline TTS)\n\n#### ESM\n```javascript\nimport { SherpaOnnxTTSClient } from 'js-tts-wrapper';\n\nconst tts = new SherpaOnnxTTSClient();\n// The client will automatically download models when needed\n\nawait tts.speak('Hello from SherpaOnnx!');\n```\n\n#### CommonJS\n```javascript\nconst { SherpaOnnxTTSClient } = require('js-tts-wrapper');\n\nconst tts = new SherpaOnnxTTSClient();\n// The client will automatically download models when needed\n\n// Inside an async function\nawait tts.speak('Hello from SherpaOnnx!');\n```\n\n\u003e **Note**: SherpaOnnx is a server-side only engine and requires specific environment setup. See the [SherpaOnnx documentation](docs/sherpaonnx.md) for details on setup and configuration. For browser environments, use [SherpaOnnx-WASM](docs/sherpaonnx-wasm.md) instead.\n\n### eSpeak NG (Node.js)\n\n#### ESM\n```javascript\nimport { EspeakNodeTTSClient } from 'js-tts-wrapper';\n\nconst tts = new EspeakNodeTTSClient();\n\nawait tts.speak('Hello from eSpeak NG!');\n```\n\n#### CommonJS\n```javascript\nconst { EspeakNodeTTSClient } = require('js-tts-wrapper');\n\nconst tts = new EspeakNodeTTSClient();\n\n// Inside an async function\nawait tts.speak('Hello from eSpeak NG!');\n```\n\n\u003e **Note**: This engine uses the `text2wav` package and is designed for Node.js environments only. For browser environments, use the eSpeak NG Browser engine instead.\n\n### eSpeak NG (Browser)\n\n#### ESM\n```javascript\nimport { EspeakBrowserTTSClient } from 'js-tts-wrapper';\n\nconst tts = new EspeakBrowserTTSClient();\n\nawait tts.speak('Hello from eSpeak NG Browser!');\n```\n\n#### CommonJS\n```javascript\nconst { EspeakBrowserTTSClient } = require('js-tts-wrapper');\n\nconst tts = new EspeakBrowserTTSClient();\n\n// Inside an async function\nawait tts.speak('Hello from eSpeak NG Browser!');\n```\n\n\u003e **Note**: This engine works in both Node.js (using the `mespeak` package) and browser environments (using meSpeak.js). For browser use, include meSpeak.js in your HTML before using this engine.\n\n#### Backward Compatibility\n\nFor backward compatibility, the old class names are still available:\n- `EspeakTTSClient` (alias for `EspeakNodeTTSClient`)\n- `EspeakWasmTTSClient` (alias for `EspeakBrowserTTSClient`)\n\nHowever, we recommend using the new, clearer names in new code.\n\n### Windows SAPI (Windows-only)\n\n#### ESM\n```javascript\nimport { SAPITTSClient } from 'js-tts-wrapper';\n\nconst tts = new SAPITTSClient();\n\nawait tts.speak('Hello from Windows SAPI!');\n```\n\n#### CommonJS\n```javascript\nconst { SAPITTSClient } = require('js-tts-wrapper');\n\nconst tts = new SAPITTSClient();\n\n// Inside an async function\nawait tts.speak('Hello from Windows SAPI!');\n```\n\n\u003e **Note**: This engine is **Windows-only**\n\n### Cartesia\n\n```javascript\nimport { CartesiaTTSClient } from 'js-tts-wrapper';\n\nconst tts = new CartesiaTTSClient({ apiKey: 'your-api-key' });\nawait tts.setVoice('sonic-3'); // or 'sonic-2'\nawait tts.speak('Hello from Cartesia!');\n```\n\n\u003e Audio tags like `[laugh]`, `[sigh]` are mapped to `\u003cemotion\u003e` SSML for sonic-3, stripped for other models.\n\n### Deepgram\n\n```javascript\nimport { DeepgramTTSClient } from 'js-tts-wrapper';\n\nconst tts = new DeepgramTTSClient({ apiKey: 'your-api-key' });\nawait tts.setVoice('aura-2-asteria-en');\nawait tts.speak('Hello from Deepgram!');\n```\n\n\u003e Uses a static voice list. Model and voice are combined in the URL parameter.\n\n### Hume AI\n\n```javascript\nimport { HumeTTSClient } from 'js-tts-wrapper';\n\nconst tts = new HumeTTSClient({ apiKey: 'your-api-key' });\nawait tts.setVoice('ito'); // or any Hume voice name\nawait tts.speak('Hello from Hume!');\n```\n\n\u003e Supports `octave-2` and `octave-1` models. Streaming uses a separate `/tts/stream/file` endpoint.\n\n### xAI (Grok)\n\n```javascript\nimport { XAITTSClient } from 'js-tts-wrapper';\n\nconst tts = new XAITTSClient({ apiKey: 'your-api-key' });\nawait tts.speak('Hello from xAI!');\n```\n\n\u003e Native audio tag passthrough for grok-tts model. Language can be configured via properties.\n\n### Fish Audio\n\n```javascript\nimport { FishAudioTTSClient } from 'js-tts-wrapper';\n\nconst tts = new FishAudioTTSClient({ apiKey: 'your-api-key' });\nawait tts.setVoice('your-voice-reference-id');\nawait tts.speak('Hello from Fish Audio!');\n```\n\n\u003e Model ID is passed as a header. Audio tags passed natively for s2-pro model.\n\n### Mistral\n\n```javascript\nimport { MistralTTSClient } from 'js-tts-wrapper';\n\nconst tts = new MistralTTSClient({ apiKey: 'your-api-key' });\nawait tts.speak('Hello from Mistral!');\n```\n\n\u003e Uses SSE streaming with base64 audio chunks. Non-streaming returns base64 JSON.\n\n### Murf\n\n```javascript\nimport { MurfTTSClient } from 'js-tts-wrapper';\n\nconst tts = new MurfTTSClient({ apiKey: 'your-api-key' });\nawait tts.setVoice('en-US-natalie');\nawait tts.speak('Hello from Murf!');\n```\n\n\u003e Two models: GEN2 (base64 response) and FALCON (binary streaming). Uses static voice list.\n\n### Unreal Speech\n\n```javascript\nimport { UnrealSpeechTTSClient } from 'js-tts-wrapper';\n\nconst tts = new UnrealSpeechTTSClient({ apiKey: 'your-api-key' });\nawait tts.setVoice('Scarlett');\nawait tts.speak('Hello from Unreal Speech!');\n```\n\n\u003e Non-streaming uses two-step URI-based flow. Streaming returns audio directly.\n\n### Resemble\n\n```javascript\nimport { ResembleTTSClient } from 'js-tts-wrapper';\n\nconst tts = new ResembleTTSClient({ apiKey: 'your-api-key' });\nawait tts.setVoice('your-voice-id');\nawait tts.speak('Hello from Resemble!');\n```\n\n\u003e Non-streaming returns base64 JSON. Streaming returns raw binary audio.\n\n## API Reference\n\n### Factory Function\n\n| Function | Description | Return Type |\n|--------|-------------|-------------|\n| `createTTSClient(engine, credentials)` | Create a TTS client for the specified engine | `AbstractTTSClient` |\n\n### Common Methods (All Engines)\n\n| Method | Description | Return Type |\n|--------|-------------|-------------|\n| `getVoices()` | Get all available voices | `Promise\u003cUnifiedVoice[]\u003e` |\n| `getVoicesByLanguage(language)` | Get voices for a specific language | `Promise\u003cUnifiedVoice[]\u003e` |\n| `setVoice(voiceId, lang?)` | Set the voice to use | `void` |\n| `synthToBytes(text, options?)` | Convert text to audio bytes | `Promise\u003cUint8Array\u003e` |\n| `synthToBytestream(text, options?)` | Stream synthesis with word boundaries | `Promise\u003c{audioStream, wordBoundaries}\u003e` |\n| `speak(text, options?)` | Synthesize and play audio | `Promise\u003cvoid\u003e` |\n| `speakStreamed(text, options?)` | Stream synthesis and play | `Promise\u003cvoid\u003e` |\n| `synthToFile(text, filename, format?, options?)` | Save synthesized speech to a file | `Promise\u003cvoid\u003e` |\n| `startPlaybackWithCallbacks(text, callback, options?)` | Play with word boundary callbacks | `Promise\u003cvoid\u003e` |\n| `pause()` | Pause audio playback | `void` |\n| `resume()` | Resume audio playback | `void` |\n| `stop()` | Stop audio playback | `void` |\n| `on(event, callback)` | Register event handler | `void` |\n| `connect(event, callback)` | Connect to event | `void` |\n| `checkCredentials()` | Check if credentials are valid | `Promise\u003cboolean\u003e` |\n| `checkCredentialsDetailed()` | Check if credentials are valid with detailed response | `Promise\u003cCredentialsCheckResult\u003e` |\n| `getProperty(propertyName)` | Get a property value | `PropertyType` |\n| `setProperty(propertyName, value)` | Set a property value | `void` |\n\nThe `checkCredentialsDetailed()` method returns a `CredentialsCheckResult` object with the following properties:\n```typescript\n{\n  success: boolean;    // Whether the credentials are valid\n  error?: string;      // Error message if credentials are invalid\n  voiceCount?: number; // Number of voices available if credentials are valid\n}\n```\n\n### SSML Builder Methods\n\nThe `ssml` property provides a builder for creating SSML:\n\n| Method | Description |\n|--------|-------------|\n| `prosody(attrs, text)` | Add prosody element |\n| `break(time)` | Add break element |\n| `emphasis(level, text)` | Add emphasis element |\n| `sayAs(interpretAs, text)` | Add say-as element |\n| `phoneme(alphabet, ph, text)` | Add phoneme element |\n| `sub(alias, text)` | Add substitution element |\n| `toString()` | Convert to SSML string |\n\n## Browser Support\n\nThe library works in both Node.js and browser environments. In browsers, use the ESM or UMD bundle:\n\n```html\n\u003c!-- Using ES modules (recommended) --\u003e\n\u003cscript type=\"module\"\u003e\n  import { SherpaOnnxWasmTTSClient } from 'js-tts-wrapper/browser';\n\n  // Create a new SherpaOnnx WebAssembly TTS client\n  const ttsClient = new SherpaOnnxWasmTTSClient();\n\n  // Initialize the WebAssembly module\n  await ttsClient.initializeWasm('./sherpaonnx-wasm/sherpaonnx.js');\n\n  // Get available voices\n  const voices = await ttsClient.getVoices();\n  console.log(`Found ${voices.length} voices`);\n\n  // Set the voice\n  await ttsClient.setVoice(voices[0].id);\n\n  // Speak some text\n  await ttsClient.speak('Hello, world!');\n\u003c/script\u003e\n```\n\n### SherpaOnnx-WASM (Browser) – options and capabilities\n\n- Auto-load WASM: pass either `wasmBaseUrl` (directory with sherpaonnx.js + .wasm) or `wasmPath` (full glue JS URL). The runtime loads the glue and points Module.locateFile to fetch the .wasm.\n- Models index: set `mergedModelsUrl` to your hosted merged_models.json (defaults to ./data/merged_models.json when available).\n- Capabilities: each client exposes `client.capabilities` to help UIs filter engines.\n\n```html\n\u003cscript type=\"module\"\u003e\n  import { SherpaOnnxWasmTTSClient } from 'js-tts-wrapper/browser';\n  const tts = new SherpaOnnxWasmTTSClient({\n    wasmBaseUrl: '/assets/sherpaonnx',            // or: wasmPath: '/assets/sherpaonnx/sherpaonnx.js'\n    mergedModelsUrl: '/assets/data/merged_models.json',\n  });\n  console.log(tts.capabilities); // { browserSupported: true, nodeSupported: false, needsWasm: true }\n  await tts.speak('Hello from SherpaONNX WASM');\n\u003c/script\u003e\n```\n\n#### Hosted WASM assets (optional)\n\nFor convenience, we publish prebuilt SherpaONNX TTS WebAssembly files to a separate assets repository. You can use these as a quick-start base URL, or self-host them for production.\n\n- Default CDN base (via jsDelivr):\n  - https://cdn.jsdelivr.net/gh/willwade/js-tts-wrapper-assets@main/sherpaonnx/tts/vocoder-models\n  - Files included (loader-only build: no .data file):\n    - sherpa-onnx-tts.js (glue; sometimes named sherpa-onnx.js depending on upstream tag)\n    - sherpa-onnx-wasm-main-tts.wasm (or sherpa-onnx-wasm-main.wasm)\n    - sherpa-onnx-wasm-main-tts.js (or sherpa-onnx-wasm-main.js)\n\n\n- Models index (merged_models.json):\n  - Canonical latest: https://cdn.jsdelivr.net/gh/willwade/js-tts-wrapper-assets@main/sherpaonnx/models/merged_models.json\n  - Snapshot for this WASM tag: https://cdn.jsdelivr.net/gh/willwade/js-tts-wrapper-assets@main/sherpaonnx/tts/\u003csherpa_tag\u003e/merged_models.json\n\n- Example (using hosted artifacts):\n\n```html\n\u003cscript type=\"module\"\u003e\n  import { SherpaOnnxWasmTTSClient } from 'js-tts-wrapper/browser';\n\n  const base = 'https://cdn.jsdelivr.net/gh/willwade/js-tts-wrapper-assets@main/sherpaonnx/tts/\u003csherpa_tag\u003e';\n\n  const tts = new SherpaOnnxWasmTTSClient({\n    // Prefer explicit glue filename from upstream\n    wasmPath: `${base}/sherpa-onnx-tts.js`,\n    // Use canonical models index (or the per-tag snapshot URL)\n    mergedModelsUrl: 'https://cdn.jsdelivr.net/gh/willwade/js-tts-wrapper-assets@main/sherpaonnx/models/merged_models.json',\n  });\n\n  await tts.speak('Hello from SherpaONNX WASM');\n\u003c/script\u003e\n```\n\nNotes:\n\n#### Hosting on Hugging Face (avoids jsDelivr 50 MB cap)\n\nYou can self-host the loader-only WASM on Hugging Face (recommended for large artifacts):\n\n- Create a Dataset or Model repo, e.g. datasets/willwade/js-tts-wrapper-wasm\n- Upload these files into a folder like sherpaonnx/tts/vocoder-models:\n  - sherpa-onnx-tts.js\n  - sherpa-onnx-wasm-main-tts.wasm\n  - (optionally) sherpa-onnx-wasm-main-tts.js\n- Optional: also upload merged_models.json to sherpaonnx/models/merged_models.json\n- Use the Hugging Face raw URLs with the “resolve” path:\n  - wasmPath: https://huggingface.co/datasets/your-user/your-repo/resolve/main/sherpaonnx/tts/vocoder-models/sherpa-onnx-tts.js\n  - mergedModelsUrl: https://huggingface.co/datasets/your-user/your-repo/resolve/main/sherpaonnx/models/merged_models.json\n\nExample:\n\n```html\n\u003cscript type=\"module\"\u003e\n  import { SherpaOnnxWasmTTSClient } from 'js-tts-wrapper/browser';\n  const tts = new SherpaOnnxWasmTTSClient({\n    wasmPath: 'https://huggingface.co/datasets/your-user/your-repo/resolve/main/sherpaonnx/tts/vocoder-models/sherpa-onnx-tts.js',\n    mergedModelsUrl: 'https://huggingface.co/datasets/your-user/your-repo/resolve/main/sherpaonnx/models/merged_models.json'\n  });\n  await tts.speak('Hello from SherpaONNX WASM hosted on HF');\n\u003c/script\u003e\n```\n\nNotes:\n- Hugging Face supports large files via Git LFS and serves them over a global CDN with proper CORS.\n- The glue JS will fetch the .wasm next to it automatically; ensure correct MIME types are served (HF does this by default).\n- For best performance, keep models separate and load them at runtime via their original URLs (or mirror selected ones to HF if needed).\n\n- For production, we recommend self-hosting to ensure stable availability and correct MIME types (application/wasm for .wasm, text/javascript for .js). If your server uses different filenames, just point `wasmPath` at your glue JS file; the runtime will find the .wasm next to it.\n- Our engine also accepts `wasmBaseUrl` if you host with filenames matching your environment; when using the upstream build outputs shown above, `wasmPath` is the safest choice.\n\n## Examples and Demos\n\n- Vue.js Browser Demo (recommended for browsers)\n  - Path: examples/vue-browser-demo/\n  - Run locally:\n    - cd examples/vue-browser-demo\n    - npm install\n    - npm run dev\n  - Notes: The Vite config aliases js-tts-wrapper/browser to the workspace source for local development. For production, you can import from the published package directly.\n\n- Browser Unified Test Page (quick, no build)\n  - Path: examples/browser-unified-test.html\n  - Open this file directly in a modern browser. It exercises multiple engines and shows real-time word highlighting.\n\n- Node.js CLI Demo\n  - Path: examples/node-demo/\n  - Run: node demo.mjs [--engine \u003cname\u003e] [--text \"Hello\"]\n  - Shows boundary callbacks and file/bytes synthesis from Node. Engines requiring credentials read them from environment variables.\n\nSherpaONNX notes\n- Browser (WASM): The demos accept wasmPath/mergedModelsUrl. You can use the hosted assets shown in the Browser Support section (jsDelivr or Hugging Face resolve URLs).\n- Node (native): If SHERPAONNX_MODEL_PATH is not set, the wrapper now defaults to a Kokoro English model (kokoro-en-en-19) and will auto-download on first use.\n\n\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Optional Dependencies\n\nThe library uses a peer dependencies approach to minimize the installation footprint. You can install only the dependencies you need for the engines you plan to use.\n\n```bash\n# Install the base package\nnpm install js-tts-wrapper\n\n# Install dependencies for specific engines\nnpm install @azure/cognitiveservices-speechservices microsoft-cognitiveservices-speech-sdk  # For Azure TTS\nnpm install @google-cloud/text-to-speech  # For Google TTS\nnpm install @aws-sdk/client-polly  # For AWS Polly\nnpm install openai  # For OpenAI TTS\nnpm install sherpa-onnx-node decompress decompress-bzip2 decompress-tarbz2 decompress-targz tar-stream  # For SherpaOnnx TTS\nnpm install text2wav  # For eSpeak NG (Node.js)\nnpm install mespeak  # For eSpeak NG-WASM (Node.js)\n\n# Install dependencies for Node.js audio playback\nnpm install sound-play speaker pcm-convert  # For audio playback in Node.js\n```\n\nYou can also use the npm scripts provided by the package to install specific engine dependencies:\n\n```bash\n# Navigate to your project directory where js-tts-wrapper is installed\ncd your-project\n\n# Install specific engine dependencies\nnpx js-tts-wrapper@latest run install:azure\nnpx js-tts-wrapper@latest run install:google\nnpx js-tts-wrapper@latest run install:polly\nnpx js-tts-wrapper@latest run install:openai\nnpx js-tts-wrapper@latest run install:sherpaonnx\nnpx js-tts-wrapper@latest run install:espeak\nnpx js-tts-wrapper@latest run install:espeak-wasm\nnpx js-tts-wrapper@latest run install:system\n\n# Install Node.js audio playback dependencies\nnpx js-tts-wrapper@latest run install:node-audio\n\n# Install all development dependencies\nnpx js-tts-wrapper@latest run install:all-dev\n```\n\n## Node.js Audio Playback\n\nThe library supports audio playback in Node.js environments with the optional `sound-play` package. This allows you to use the `speak()` and `speakStreamed()` methods in Node.js applications, just like in browser environments.\n\nTo enable Node.js audio playback:\n\n1. Install the required dependencies:\n   ```bash\n   npm install sound-play pcm-convert\n   ```\n\n   Or use the npm script:\n   ```bash\n   npx js-tts-wrapper@latest run install:node-audio\n   ```\n\n2. Use the TTS client as usual:\n   ```typescript\n   import { TTSFactory } from 'js-tts-wrapper';\n\n   const tts = TTSFactory.createTTSClient('mock');\n\n   // Play audio in Node.js\n   await tts.speak('Hello, world!');\n   ```\n\nIf the `sound-play` package is not installed, the library will fall back to providing informative messages and suggest installing the package.\n\n## Testing and Troubleshooting\n\n### Unified Test Runner\n\nThe library includes a comprehensive unified test runner that supports multiple testing modes and engines:\n\n```bash\n# Basic usage - test all engines\nnode examples/unified-test-runner.js\n\n# Test a specific engine\nnode examples/unified-test-runner.js [engine-name]\n\n# Test with different modes\nnode examples/unified-test-runner.js [engine-name] --mode=[MODE]\n```\n\n### Available Test Modes\n\n| Mode | Description | Usage |\n|------|-------------|-------|\n| `basic` | Basic synthesis tests (default) | `node examples/unified-test-runner.js azure` |\n| `audio` | Audio-only tests with playback | `PLAY_AUDIO=true node examples/unified-test-runner.js azure --mode=audio` |\n| `playback` | Playback control tests (pause/resume/stop) | `node examples/unified-test-runner.js azure --mode=playback` |\n| `features` | Comprehensive feature tests | `node examples/unified-test-runner.js azure --mode=features` |\n| `example` | Full examples with SSML, streaming, word boundaries | `node examples/unified-test-runner.js azure --mode=example` |\n| `debug` | Debug mode for troubleshooting | `node examples/unified-test-runner.js sherpaonnx --mode=debug` |\n| `stream` | Streaming tests with real-time playback | `PLAY_AUDIO=true node examples/unified-test-runner.js playht --mode=stream` |\n\n### Testing Audio Playback\n\nTo test audio playback with any TTS engine, use the `PLAY_AUDIO` environment variable:\n\n```bash\n# Test a specific engine with audio playback\nPLAY_AUDIO=true node examples/unified-test-runner.js [engine-name] --mode=audio\n\n# Examples:\nPLAY_AUDIO=true node examples/unified-test-runner.js witai --mode=audio\nPLAY_AUDIO=true node examples/unified-test-runner.js azure --mode=audio\nPLAY_AUDIO=true node examples/unified-test-runner.js polly --mode=audio\nPLAY_AUDIO=true node examples/unified-test-runner.js system --mode=audio\n```\n\n### SherpaOnnx Specific Testing\n\nSherpaOnnx requires special environment setup. Use the helper script:\n\n```bash\n# Test SherpaOnnx with audio playback\nPLAY_AUDIO=true node scripts/run-with-sherpaonnx.cjs examples/unified-test-runner.js sherpaonnx --mode=audio\n\n# Debug SherpaOnnx issues\nnode scripts/run-with-sherpaonnx.cjs examples/unified-test-runner.js sherpaonnx --mode=debug\n\n# Use npm scripts (recommended)\nnpm run example:sherpaonnx:mac\nPLAY_AUDIO=true npm run example:sherpaonnx:mac\n```\n\n### Using npm Scripts\n\nThe package provides convenient npm scripts for testing specific engines:\n\n```bash\n# Test specific engines using npm scripts\nnpm run example:azure\nnpm run example:google\nnpm run example:polly\nnpm run example:openai\nnpm run example:elevenlabs\nnpm run example:playht\nnpm run example:system\nnpm run example:sherpaonnx:mac  # For SherpaOnnx with environment setup\n\n# With audio playback\nPLAY_AUDIO=true npm run example:azure\nPLAY_AUDIO=true npm run example:system\nPLAY_AUDIO=true npm run example:sherpaonnx:mac\n```\n\n### Getting Help\n\nFor detailed help and available options:\n\n```bash\n# Show help and available engines\nnode examples/unified-test-runner.js --help\n\n# Show available test modes\nnode examples/unified-test-runner.js --mode=help\n```\n\n### Audio Format Conversion\n\nThe library includes **automatic format conversion** for engines that don't natively support the requested format:\n\n```javascript\n// Request MP3, get MP3 if supported, WAV with warning if not\nconst audioBytes = await client.synthToBytes(\"Hello world\", { format: \"mp3\" });\nawait client.synthToFile(\"Hello world\", \"output\", \"mp3\");\nawait client.speak(\"Hello world\", { format: \"mp3\" });\n```\n\n**Supported Formats**: WAV, MP3, OGG\n\n**Engine Behavior**:\n- **Native Support**: Azure, Polly, PlayHT support multiple formats natively\n- **Automatic Conversion**: SAPI, SherpaOnnx convert from WAV when possible\n- **Graceful Fallback**: Returns native format with helpful warnings when conversion isn't available\n\n**Environment Support**:\n- **Node.js**: Full format conversion support (install `ffmpeg` for advanced conversions)\n- **Browser**: Engines return their native format (no conversion)\n\n### Common Issues\n\n1. **No Audio in Node.js**: Install audio dependencies with `npm install sound-play speaker pcm-convert`\n2. **SherpaOnnx Not Working**: Use the helper script and ensure environment variables are set correctly\n3. **WitAI Audio Issues**: The library automatically handles WitAI's raw PCM format conversion\n4. **Sample Rate Issues**: Different engines use different sample rates (WitAI: 24kHz, Polly: 16kHz) - this is handled automatically\n5. **Format Conversion**: Install `ffmpeg` for advanced audio format conversion in Node.js\n\nFor detailed troubleshooting, see the [docs/](docs/) directory, especially:\n- [SherpaOnnx Documentation](docs/sherpaonnx.md)\n- [SherpaOnnx Troubleshooting](docs/sherpaonnx-troubleshooting.md)\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillwade%2Fjs-tts-wrapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwillwade%2Fjs-tts-wrapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillwade%2Fjs-tts-wrapper/lists"}