Audio (TTS/STT)
Audio (TTS/STT)
The library provides comprehensive Text-to-Speech (TTS) and Speech-to-Text (STT) capabilities. These APIs are request-based and work best for files or bounded generation. For live speech-to-speech, streaming transcription, or continuous translation, use the OpenAI Realtime API or xAI Voice Agent API.
Text-to-Speech
Basic Usage
import { Connector, TextToSpeech, Vendor } from 'oneringai';
// Setup connector
Connector.create({
name: 'openai',
vendor: Vendor.OpenAI,
auth: { type: 'api_key', apiKey: process.env.OPENAI_API_KEY! },
});
// Create TTS instance
const tts = TextToSpeech.create({
connector: 'openai',
model: 'tts-1-hd', // High-quality model
voice: 'nova', // Female voice
});
// Synthesize to Buffer
const response = await tts.synthesize('Hello, world!');
console.log(response.audio); // Buffer
console.log(response.format); // 'mp3'
// Synthesize to file
await tts.toFile('Hello, world!', './output.mp3');
Voice Options
// Available voices
const voices = await tts.listVoices();
// [
// { id: 'alloy', name: 'Alloy', gender: 'neutral', isDefault: true },
// { id: 'echo', name: 'Echo', gender: 'male' },
// { id: 'fable', name: 'Fable', gender: 'neutral', accent: 'british' },
// { id: 'onyx', name: 'Onyx', gender: 'male' },
// { id: 'nova', name: 'Nova', gender: 'female' },
// { id: 'shimmer', name: 'Shimmer', gender: 'female' },
// ...
// ]
// Synthesize with specific voice
const audio = await tts.synthesize('Hello', { voice: 'echo' });
Custom Voices (OpenAI)
Create OpenAI custom voices through the connector-first OpenAIVoices API.
Prompt-designed voices work only with GPT-Live. Voices intended for TTS or
Realtime must be created from an audio sample and an existing consent-recording
ID. Custom voice access and the required api.voices.read/api.voices.write
permissions are organization-gated.
import { createReadStream } from 'node:fs';
import { OpenAIVoices, TextToSpeech } from 'oneringai';
const voices = new OpenAIVoices('openai');
const customVoice = await voices.create({
type: 'audio_sample',
name: 'Brand narrator',
audio_sample: createReadStream('./brand-narrator.wav'),
consent: 'cons_1234abcd',
});
const branded = TextToSpeech.create({
connector: 'openai',
model: 'gpt-4o-mini-tts',
voice: customVoice.id,
});
await branded.toFile('Spoken in your bespoke voice.', './brand.mp3');
// A stored audio-sample voice can also be selected per call.
await branded.synthesize('Different copy.', { voice: 'voice_5678efgh' });
How it's detected: any string starting with voice_ is forwarded as a
custom-voice reference; everything else is treated as a built-in voice name
(alloy, ash, ballad, coral, echo, fable, onyx, nova, sage,
shimmer, verse, marin, cedar). tts.listVoices() returns only the
built-ins. Keep returned custom-voice IDs in trusted application storage; do
not place voice samples, consent recordings, or credentials in prompts.
For Google custom voices, use the connector-first GoogleVoices lifecycle API
rather than tts.listVoices(). Google TTS accepts voice_…, voicekey_…, and
resource-prefixed voices/… identifiers returned by that API.
Audio Formats
// Supported formats: mp3, opus, aac, flac, wav, pcm
const mp3 = await tts.synthesize('Hello', { format: 'mp3' });
const wav = await tts.synthesize('Hello', { format: 'wav' });
const flac = await tts.synthesize('Hello', { format: 'flac' });
Speed Control
// Speed range: 0.25 (slow) to 4.0 (fast)
const slow = await tts.synthesize('Speaking slowly', { speed: 0.5 });
const normal = await tts.synthesize('Normal speed', { speed: 1.0 });
const fast = await tts.synthesize('Speaking fast', { speed: 2.0 });
Instruction Steering (gpt-4o-mini-tts)
The gpt-4o-mini-tts model supports instruction steering for emotional control:
const tts = TextToSpeech.create({
connector: 'openai',
model: 'gpt-4o-mini-tts',
voice: 'nova',
});
const audio = await tts.synthesize('I\'m so happy to see you!', {
vendorOptions: {
instructions: 'Speak with enthusiasm and joy, like greeting an old friend.',
},
});
Model Introspection
// Get model information
const info = tts.getModelInfo();
console.log(info.capabilities.features.instructionSteering); // true for gpt-4o-mini-tts
// Check feature support
const canSteer = tts.supportsFeature('instructionSteering');
const canStream = tts.supportsFeature('streaming');
// Get supported formats
const formats = tts.getSupportedFormats(); // ['mp3', 'opus', 'aac', ...]
// List available models
const models = tts.listAvailableModels();
Streaming TTS
For real-time voice applications, TTS audio can be streamed as chunks arrive from the API instead of waiting for the entire response to buffer. This is useful for voice assistants and interactive agents.
import { TextToSpeech } from 'oneringai';
const tts = TextToSpeech.create({
connector: 'openai',
model: 'tts-1-hd',
voice: 'nova',
});
// Check if provider supports streaming
console.log(tts.supportsStreaming('pcm')); // true for OpenAI
// Stream PCM audio chunks
for await (const chunk of tts.synthesizeStream('Hello, world!', { format: 'pcm' })) {
if (chunk.audio.length > 0) {
// Process raw PCM data (24kHz, 16-bit signed LE, mono)
playPCMChunk(chunk.audio);
}
if (chunk.isFinal) break;
}
// Non-streaming providers gracefully fall back to buffered synthesis
// (yields a single chunk with isFinal: true)
VoiceStream wraps an agent's text stream and interleaves audio events:
import { VoiceStream } from 'oneringai';
const voice = VoiceStream.create({
ttsConnector: 'openai',
ttsModel: 'tts-1-hd',
voice: 'nova',
format: 'mp3', // MP3 recommended for broad compatibility
streaming: false, // Set true + format 'pcm' for low-latency streaming
});
// Wraps agent stream — text events pass through, audio events interleaved
for await (const event of voice.wrap(agent.stream('Tell me a story'))) {
if (event.type === 'response.output_text.delta') {
process.stdout.write(event.delta);
} else if (event.type === 'response.audio_chunk.ready') {
playAudio(event.audio_base64, event.format);
}
}
Streaming notes:
- MP3 format (default) is recommended — best compatibility and quality
- PCM format enables streaming mode but browser playback is experimental
- Streaming providers (OpenAI and xAI) yield chunks as they arrive from the API
- Non-streaming providers fall back to buffered synthesis automatically
- VoiceStream accumulates small API chunks into ~125ms buffers before emitting events
xAI TTS and WebSocket streaming
Use a Grok connector with the endpoint model id xai-tts. xAI accepts expressive
tags such as [laugh] and [pause], custom voice ids, BCP-47 language codes,
telephony codecs, and bidirectional WebSocket streaming.
For buffered synthesis, vendorOptions.output_format.codec may override the
normalized format; TTSResponse.format follows xAI's response Content-Type
or JSON content_type, so returned WAV/PCM/telephony bytes are never mislabeled
with the caller's earlier default.
Connector.create({
name: 'xai',
vendor: Vendor.Grok,
auth: { type: 'api_key', apiKey: process.env.XAI_API_KEY! },
});
const xaiTTS = TextToSpeech.create({
connector: 'xai', model: 'xai-tts', voice: 'eve',
});
for await (const chunk of xaiTTS.synthesizeStream('[laugh] Hello!', {
format: 'mulaw',
vendorOptions: {
language: 'en',
sample_rate: 8000,
optimize_streaming_latency: 2, // xAI accepts levels 0, 1, and 2
},
})) {
if (chunk.audio.length) sendToPhone(chunk.audio);
}
Speech-to-Text
Basic Usage
import { Connector, SpeechToText, Vendor } from 'oneringai';
// Setup connector
Connector.create({
name: 'openai',
vendor: Vendor.OpenAI,
auth: { type: 'api_key', apiKey: process.env.OPENAI_API_KEY! },
});
// Create STT instance
const stt = SpeechToText.create({
connector: 'openai',
model: 'gpt-transcribe',
});
// Transcribe from file path
const result = await stt.transcribeFile('./audio.mp3');
console.log(result.text);
// Transcribe from Buffer
import * as fs from 'fs/promises';
const audioBuffer = await fs.readFile('./audio.mp3');
const result2 = await stt.transcribe(audioBuffer);
When a Buffer contains a normal audio container, OneRingAI detects WAV, AIFF,
MP3, FLAC, Ogg, WebM, MP4/M4A, or AAC bytes and preserves that format. For a
headerless raw buffer, provide the normalized wire metadata. AAC ADTS headers,
including the F0/F8 CRC-protected variants and F1/F9 unprotected
variants, are checked before MPEG frame sync so AAC is never mislabeled as MP3:
const telephoneAudio = await stt.transcribe(pcm16le8kBuffer, {
encoding: 'pcm', // 'pcm', 'mulaw', or 'alaw'
sampleRate: 8000,
});
Raw input otherwise defaults to signed 16-bit little-endian PCM at 16 kHz.
This is especially important for Twilio audio, which is 8 kHz; TextPipeline
sets that metadata automatically after the adapter decodes PCMU to PCM. When
OpenAI receives raw μ-law or A-law, OneRingAI wraps it in a non-PCM WAV with an
18-byte WAVEFORMATEX chunk and the required zero cbSize; PCM keeps the
standard 16-byte fmt chunk.
Timestamps
// Whisper remains available when word/segment timestamp formats are required.
const timestampedStt = SpeechToText.create({
connector: 'openai',
model: 'whisper-1',
});
// Word-level timestamps
const withWords = await timestampedStt.transcribeWithTimestamps(audioBuffer, 'word');
console.log(withWords.words);
// [
// { word: 'Hello', start: 0.0, end: 0.5 },
// { word: 'world', start: 0.6, end: 1.1 },
// ]
// Segment-level timestamps
const withSegments = await timestampedStt.transcribeWithTimestamps(audioBuffer, 'segment');
console.log(withSegments.segments);
// [
// { id: 0, text: 'Hello world.', start: 0.0, end: 1.5 },
// ]
Google's dedicated gemini-3.5-transcribe model uses the Interactions API and
supports automatic language detection, word/segment timestamps, custom
vocabulary, and speaker diarization. gemini-3.5-transcribe-live is the
bidirectional WebSocket model; the high-level file provider uses the non-live
model. Google output is plain text, so json, verbose_json, srt, and vtt
are rejected instead of being silently ignored:
const googleResult = await googleStt.transcribe(audio, {
language: 'en',
includeTimestamps: true,
timestampGranularity: 'segment',
vendorOptions: {
customVocabulary: ['OneRingAI'],
diarizationMode: 'speaker',
},
});
Translation
Translate audio to English:
const stt = SpeechToText.create({
connector: 'openai',
model: 'whisper-1',
language: 'fr', // default source-language hint for transcribe and translate
});
// Translate French audio to English
const english = await stt.translate(frenchAudioBuffer);
const germanHint = await stt.translate(germanAudioBuffer, { language: 'de' });
console.log(english.text); // English translation
translate() forwards the instance default unless a per-call language is
provided. The language is a source-language hint; the selected model/provider
still determines the translation target.
Output Formats
// JSON (default)
const json = await stt.transcribe(audio, { outputFormat: 'json' });
// Plain text
const text = await stt.transcribe(audio, { outputFormat: 'text' });
// Subtitles (SRT format)
const srt = await stt.transcribe(audio, { outputFormat: 'srt' });
// WebVTT format
const vtt = await stt.transcribe(audio, { outputFormat: 'vtt' });
// Verbose JSON (includes all metadata)
const verbose = await stt.transcribe(audio, { outputFormat: 'verbose_json' });
Language Hints
// Provide language hint for better accuracy
const result = await stt.transcribe(audio, { language: 'fr' }); // French
const result2 = await stt.transcribe(audio, { language: 'es' }); // Spanish
xAI live streaming transcription
SpeechToText.transcribeStream() is available when the selected provider
implements IStreamingSpeechToTextProvider. It accepts real-time-paced raw
audio chunks and yields created, transcript, and done events. xAI supports
PCM16, G.711 µ-law/A-law, interim results, keyterms, diarization, multichannel
audio, VAD endpointing, and Smart Turn.
const liveSTT = SpeechToText.create({ connector: 'xai', model: 'xai-stt' });
for await (const event of liveSTT.transcribeStream(microphoneChunks(), {
sampleRate: 16000,
encoding: 'pcm',
interimResults: true,
language: 'en',
vendorOptions: {
keyterms: ['OneRingAI'],
smart_turn: 0.7,
smart_turn_timeout: 3000,
},
})) {
if (event.type === 'transcript') {
renderTranscript(event.text ?? '', { final: event.isFinal });
}
}
The library waits for transcript.created before sending binary frames and
sends audio.done after the input iterator completes. The iterator must pace
audio in real time; buffered audio is not automatically delayed. Yield
{ type: 'finalize' } (or include channel) for push-to-talk utterance
finalization without closing the session.
Model Introspection
// Get model information
const info = stt.getModelInfo();
console.log(info.capabilities.features.diarization); // Speaker identification
// Check feature support
const supportsTranslation = stt.supportsFeature('translation');
const supportsDiarization = stt.supportsFeature('diarization');
// Get supported formats
const inputFormats = stt.getSupportedInputFormats();
const outputFormats = stt.getSupportedOutputFormats();
// Get timestamp granularities
const granularities = stt.getTimestampGranularities(); // model-specific
Available Models
These tables describe checked-in registry metadata. Prices are estimates rather than live quotes, and account/region access must be verified separately. Prefer registry introspection for application model pickers.
TTS Models
| Model | Provider | Features | Pricing |
|---|---|---|---|
gpt-4o-mini-tts |
OpenAI | Instruction steering, emotions | $0.015 / 1K chars |
tts-1 |
OpenAI | Fast, low-latency | $0.015 / 1K chars |
tts-1-hd |
OpenAI | High-quality audio | $0.030 / 1K chars |
gemini-3.8-flash-tts |
Preferred creative/studio TTS, voice design and replication | $1/M input tokens; $20/M output tokens | |
gemini-3.8-flash-lite-tts |
Preferred low-latency/high-throughput TTS and voice replication | $0.50/M input tokens; $6/M output tokens | |
gemini-3.1-flash-tts-preview |
Legacy controllable low-latency TTS | token priced | |
gemini-2.5-flash-preview-tts |
Deprecated; migrate to Gemini 3.8 Flash-Lite TTS | token priced | |
gemini-2.5-pro-preview-tts |
Deprecated; migrate to Gemini 3.8 Flash TTS | token priced | |
xai-tts |
xAI | REST/WebSocket, expressive tags, custom voices, telephony codecs | $0.015 / 1K chars |
STT Models
| Model | Provider | Features | Price/minute |
|---|---|---|---|
gpt-transcribe |
OpenAI | Current accurate file transcription | $0.0045 |
gpt-4o-mini-transcribe |
OpenAI | Economical file transcription | $0.003 |
gpt-live-transcribe |
OpenAI | Recommended realtime streaming transcription, latency control, vocabulary/language hints | $0.017 |
gpt-realtime-whisper |
OpenAI | Low-latency realtime streaming transcription | $0.017 |
gpt-4o-transcribe |
OpenAI | Superior accuracy | $0.006 |
gpt-4o-transcribe-diarize |
OpenAI | Speaker identification | $0.012 |
whisper-1 |
OpenAI | Legacy-compatible general-purpose transcription | $0.006 |
whisper-large-v3 |
Groq | High-accuracy multilingual Whisper, word/segment timestamps | $0.00185 |
whisper-large-v3-turbo |
Groq | Current cost-optimized multilingual Whisper | $0.00067 |
gemini-3.5-transcribe |
File transcription, word timestamps, vocabulary, diarization | ~$0.005 | |
gemini-3.5-transcribe-live |
Bidirectional WebSocket transcription | ~$0.009 | |
grok-voice-transcribe-2.0 |
xAI | Current REST + streaming transcription, diarization, multichannel | $0.00167 REST / $0.00333 stream |
xai-stt |
xAI | Legacy alias; migrate to Grok Voice Transcribe 2.0 | $0.00167 REST / $0.00333 stream |
Voice Assistant Pipeline
Combine TTS and STT for a voice assistant:
import { Connector, Agent, TextToSpeech, SpeechToText, Vendor } from 'oneringai';
// Setup
Connector.create({
name: 'openai',
vendor: Vendor.OpenAI,
auth: { type: 'api_key', apiKey: process.env.OPENAI_API_KEY! },
});
const stt = SpeechToText.create({ connector: 'openai', model: 'gpt-transcribe' });
const agent = Agent.create({ connector: 'openai', model: 'gpt-5.6-terra' });
const tts = TextToSpeech.create({ connector: 'openai', model: 'tts-1-hd', voice: 'nova' });
// Voice assistant pipeline
async function voiceAssistant(audioInput: Buffer): Promise<Buffer> {
// 1. Speech → Text
const transcription = await stt.transcribe(audioInput);
console.log('User said:', transcription.text);
// 2. Text → AI Response
if (!transcription.text) throw new Error('No transcript returned');
const response = await agent.run(transcription.text);
console.log('Agent response:', response.output_text);
// 3. Text → Speech
const audioResponse = await tts.synthesize(response.output_text ?? '');
return audioResponse.audio;
}
Cost Estimation
import { calculateTTSCost, calculateSTTCost } from 'oneringai';
// TTS cost (per 1,000 characters)
const ttsCost = calculateTTSCost('tts-1-hd', 5000); // 5000 characters
console.log(`TTS cost: $${ttsCost}`); // $0.15
// STT cost (per minute)
const sttCost = calculateSTTCost('gpt-transcribe', 300); // 5 minutes
console.log(`STT cost: $${sttCost}`); // $0.0225
// Groq is much cheaper for STT
const groqCost = calculateSTTCost('whisper-large-v3', 300); // 5 minutes
console.log(`Groq STT cost: $${groqCost}`); // $0.0025
const xaiStreaming = calculateSTTCost('xai-stt', 300, { streaming: true });