A universal TTS (Text-to-Speech) provider interface with unified expressive markup syntax. Write once, synthesize anywhere.
Interactive script editor with autocomplete and playground to test all providers side-by-side. Write expressive text, compare voices, and generate audio instantly—no API setup required.
Expressive voice models have reached near-human quality. Over the past year, both open-source and commercial TTS providers have exploded with models that let creators control emotion, expressiveness, and delivery with precision. They now handle tone, emotion, and non-verbal sounds (laughs, sighs, whispers) at incredible quality.
However, each provider has their own interface, API, studio, and markup syntax. That makes it hard to experiment, switch, fallback, compare, or mix outputs between models.
Wave Form Provider solves this by providing a unified interface that works across all the best voice models. Write your script once using a simple, consistent syntax, and let the library handle the provider-specific compilation.
- Unified Syntax: One markup language works across all TTS providers
- Provider Agnostic: Switch providers without rewriting your text
- Expressive Control: Add emotions, actions, speed, and more
- Visual Playground: Test and compare providers at waveformstudio.app
- Type Safe: Full type hints and async support
- Well Tested: 147+ tests across all providers
Install from PyPI:
pip install wave-form-providerOr install from source:
# Clone the repository
git clone https://github.com/phodonou/wave_form_provider.git
cd wave_form_provider
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install dependencies
pip install -r requirements.txtSign up for a Cartesia Sonic API key at cartesia.ai/sonic before running the example.
Set CARTESIA_API_KEY in your environment (or pass api_key="...") and run the script below:
import asyncio
from wave_form_provider.providers import CartesiaProvider
async def main():
provider = CartesiaProvider() # Reads CARTESIA_API_KEY from env, or pass api_key="..."
response = await provider.synthesize(
voice_id="6ccbfb76-1fc6-48f7-b71d-91ac6298247b",
text="Hello there! [laughter] (excited) This is amazing!"
)
with open("output_cartesia.mp3", "wb") as f:
f.write(response.audio)
asyncio.run(main())- Cartesia
- Hume
- Inworld
- ElevenLabs
- Google Gemini
- OpenAI
- Orpheus
💡 Try it live: Test the syntax at waveformstudio.app and hear the results instantly across all providers.
The syntax is simple: write what you want to say and how you want to say it using a universal format. Use [] for things that can be inserted into speech, like actions. Use () to dictate how to say the subsequent speech. The library automatically compiles this into the right format for each TTS provider.
Actions that happen during speech:
"Hello! [laugh] How are you? [sigh]"
"That's interesting [pause] tell me more."Common actions: [laugh], [chuckle], [sigh], [gasp], [pause], [long pause]
Control how the text is spoken:
"(excited) I got the job! (sad) But I have to move.""(fast) Quick announcement: (slow) Now speaking slowly.""(quiet) Whisper this. (shout) Shout this!""My name is (spell) Bob."text = "Hello! [laugh] (excited) I have great news! (fast) Let me tell you more."The library automatically compiles the unified syntax for each provider:
- Actions: Passed through as
[action] - Emotions: Compiled to
<emotion value="angry" /> - Speed: Maps to
<speed ratio="X"/>((slow)→ 0.6,(fast)→ 1.3,(really fast)→ 1.5) - Volume: Maps to
<volume ratio="X"/>((quiet)→ 0.5,(loud)→ 1.5,(shout)→ 2.0) - Pauses: Maps to
<break time="X"/>((pause)→ 1s,(long pause)→ 2s) - Special:
(spell)word→<spell>word</spell>
- Actions: Added to
descriptionfield - Emotions: Added to
descriptionfield - Speed: Maps to
speedparameter ((slow)→ 0.6,(fast)→ 1.5,(really fast)→ 2.0) - Volume: Not supported
- Pauses:
[pause]at end →trailing_silence: 2,[long pause]→trailing_silence: 4. Preserved in text when in the middle
- Actions: Passed through as
[action] - Emotions: Prepended as
[emotion]to each segment - Speed: Maps to
speakingRateparameter ((slow)→ 0.7,(fast)→ 1.3,(really fast)→ 1.5) - Volume: Not supported
- Pauses: Not supported
- Actions: Converted
[action]→[action](preserved) - Emotions: Converted
(emotion)→[emotion] - Speed: Converted
(speed)→[speed](provider interprets) - Volume: Converted
(volume)→[volume](provider interprets) - Pauses: Converted
[pause]→[pause](preserved)
- Actions: Converted
[action]→[action](preserved) - Emotions: Converted
(emotion)→[emotion] - Speed: Converted
(speed)→[speed](provider interprets) - Volume: Converted
(volume)→[volume](provider interprets) - Pauses: Converted
[pause]→[pause](preserved)
- Actions: Converted
[action]→<action> - Emotions: Stripped (not supported)
- Speed: Stripped (not supported)
- Volume: Stripped (not supported)
- Pauses: Converted
[pause]→<pause>
- Actions: Controlled via
style_guidanceparameter - Emotions: Controlled via
style_guidanceparameter - Speed: Controlled via
style_guidanceparameter - Volume: Controlled via
style_guidanceparameter - Pauses: Controlled via
style_guidanceparameter - Note: All markup is stripped from text. Use natural language in
style_guidancelike "speak with excitement and laugh occasionally"
The fastest way to experiment is in the visual playground at waveformstudio.app. Compare providers side-by-side, test different voices, and hear the results instantly.
Import any provider directly:
from wave_form_provider.providers import CartesiaProvider, ElevenLabsProvider, HumeProvider
# Use Cartesia
cartesia = CartesiaProvider()
response = await cartesia.synthesize(voice_id="...", text="...")
# Use ElevenLabs
elevenlabs = ElevenLabsProvider(api_key="...")
response = await elevenlabs.synthesize(voice_id="...", text="...")Generate speech from text and return audio bytes.
async def synthesize(
voice_id: str, # Voice ID from provider (get from provider's dashboard/docs)
text: str, # Text to synthesize (supports unified syntax)
style_guidance: Optional[str] = None, # Natural language style guidance (provider-specific)
seed: Optional[float] = None, # Random seed for reproducibility
creativity: float = 0.5, # Creativity/variation (0.0-1.0, default 0.5)
) -> SynthesisResponseReturns: SynthesisResponse object with:
response.audio-bytes: Audio data (MP3, WAV, etc. depending on provider)response.metadata-SynthesisMetadataobject containing:voice_id: The voice usedmodel: Model namesize_bytes: Audio file sizestreaming: AlwaysFalseforsynthesize()duration_seconds: Audio duration (if available)sample_rate: Sample rate in Hz (if available)
Example:
response = await provider.synthesize(
voice_id="voice-123",
text="Hello! [laugh] (excited) This is amazing!",
creativity=0.7
)
# Save audio
with open("output.mp3", "wb") as f:
f.write(response.audio)
# Access metadata
print(f"Generated {response.metadata.size_bytes} bytes")
print(f"Model: {response.metadata.model}")Generate speech with streaming audio chunks (not yet implemented for most providers).
async def synthesize_stream(
voice_id: str,
text: str,
style_guidance: Optional[str] = None,
seed: Optional[float] = None,
creativity: float = 0.5,
) -> SynthesisStreamResponseReturns: SynthesisStreamResponse with audio as an AsyncIterator[bytes].
Tip: Use Waveform Studio to browse and test voices from all providers in one place.
Voice IDs are provider-specific. Get them from:
- Cartesia: Cartesia Dashboard
- ElevenLabs: ElevenLabs Voice Library
- Hume: Hume Dashboard
- OpenAI: OpenAI Voice Models
- Google Gemini: Google Cloud Console
- Inworld: Inworld Studio
- Orpheus: Use voice names like
"tara","dan","josh","emma"(see Replicate model docs)
Providers may raise:
ValueError: Invalid parameters (e.g., missing API key, invalid voice_id)RuntimeError: API request failed or synthesis errorImportError: Provider dependencies not installed
try:
response = await provider.synthesize(voice_id="...", text="...")
except ValueError as e:
print(f"Invalid input: {e}")
except RuntimeError as e:
print(f"Synthesis failed: {e}")Set API keys via environment variables:
export CARTESIA_API_KEY="your-key"
export HUME_API_KEY="your-key"
export INWORLD_API_KEY="your-key"
export ELEVENLABS_API_KEY="your-key"
export OPENAI_API_KEY="your-key"
export REPLICATE_API_TOKEN="your-key" # For Orpheus
export GOOGLE_GENERATIVE_AI_API_KEY="your-key"# Run all tests
pytest tests/
# Run specific provider tests
pytest tests/test_cartesia_provider.py -v
- Generate proper documentation
- Publish to PyPI as installable package
- Create web playground
- Streaming support
- Different lang support
- CLI interface
- Auto chunk and re-stitch based on character limit
- Multi speaker support
- Get audio back along with timestamps
- Audio format conversion utilities
- Cost tracking utilities
- More OSS providers
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
MIT License - see LICENSE file for details
Built with love for the voice AI community. Special thanks to all the TTS provider teams for their amazing APIs.
