Generate Speech
One-shot speech generation, generation parameters, word timestamps, models, and audio utilities
Basic generation
Generate complete audio and receive it all at once:
audio = client.tts.generate(
text="Hello, this is a test of the KugelAudio text-to-speech system.",
model_id="kugel-3.5",
voice_id=1071,
language="en",
)
# Audio properties
print(f"Duration: {audio.duration_seconds:.2f}s")
print(f"Samples: {audio.samples}")
print(f"Sample rate: {audio.sample_rate} Hz")
print(f"Generation time: {audio.generation_ms:.0f}ms")
print(f"RTF: {audio.rtf:.2f}") # Real-time factor
# Save to WAV file
audio.save("output.wav")
# Get raw PCM bytes
pcm_data = audio.audio
# Get WAV bytes (with header)
wav_bytes = audio.to_wav_bytes()
# Get float32 samples in [-1.0, 1.0]
samples = audio.to_float32()
# Save raw PCM instead of WAV
audio.save("output.pcm", format="raw")const audio = await client.tts.generate({
text: 'Hello, this is a test of the KugelAudio text-to-speech system.',
modelId: 'kugel-3.5',
voiceId: 1071,
language: 'en',
});
// Audio properties
console.log(`Duration: ${audio.durationMs}ms`);
console.log(`Samples: ${audio.samples}`);
console.log(`Sample rate: ${audio.sampleRate} Hz`);
console.log(`Generation time: ${audio.generationMs}ms`);
console.log(`RTF: ${audio.rtf}`); // Real-time factor
// audio.audio is an ArrayBuffer in the requested output format (PCM16 by default)import com.kugelaudio.sdk.GenerateRequest;
import com.kugelaudio.sdk.AudioResponse;
AudioResponse audio = client.tts().generate(
GenerateRequest.builder("Hello, this is a test of the KugelAudio text-to-speech system.")
.modelId("kugel-3.5") // Canonical production model (see /models)
.voiceId(1071) // Required for successful synthesis
.cfgScale(2.0) // Guidance scale (1.2-2.5)
.temperature(0.5) // Sampling variance 0.0-1.0 (omit for server default)
.maxNewTokens(2048)
.sampleRate(24000)
.normalize(true) // Enable text normalization (default)
.language("en") // Language for normalization
.wordTimestamps(false)
.speed(1.0) // Playback speed 0.8-1.2 (pitch-preserving WSOLA)
.build()
);
// Audio properties
System.out.printf("Duration: %.2fms%n", audio.getDurationMs());
System.out.println("Samples: " + audio.getTotalSamples());
System.out.println("Sample rate: " + audio.getSampleRate() + " Hz");
System.out.printf("Generation time: %.0fms%n", audio.getGenerationMs());
System.out.printf("RTF: %.2f%n", audio.getRtf());
// Save to WAV file
audio.saveWav(java.nio.file.Path.of("output.wav"));
// Get raw PCM16 bytes (signed 16-bit little-endian, mono)
byte[] pcmData = audio.getAudio();
// Get WAV bytes with header (in-memory)
byte[] wavBytes = audio.toWavBytes();
// Get normalised float samples [-1.0, 1.0]
float[] floatData = audio.toFloat32();use kugelaudio::TtsRequest;
let request = TtsRequest::new("Hello, this is a test of the KugelAudio text-to-speech system.")
.model_id("kugel-3.5")
.voice_id(1071)
.language("en");
let audio = client.tts().generate(&request).await?;
// Audio properties (REST reports no generation time or RTF; stream() does)
println!("Duration: {:.2}s", audio.duration_seconds());
println!("Samples: {}", audio.samples);
println!("Sample rate: {} Hz", audio.sample_rate);
// Save to WAV file
audio.save_wav("output.wav")?;
// Get raw PCM bytes
let pcm_data: &[u8] = &audio.audio;
// Get WAV bytes (with header)
let wav_bytes: Vec<u8> = audio.to_wav();
// Get float32 samples in [-1.0, 1.0]
let samples: Vec<f32> = audio.to_f32()?;kugelaudio::GenerateRequest request;
request.text = "Hello, this is a test of the KugelAudio text-to-speech system.";
request.model_id = "kugel-3.5";
request.voice_id = 1071;
request.language = "en";
kugelaudio::AudioResponse audio = client.tts().generate(request);
// Audio properties (REST reports no generation time or RTF; stream() does)
std::cout << "Duration: " << audio.duration_seconds() << "s\n";
std::cout << "Samples: " << audio.samples << '\n';
std::cout << "Sample rate: " << audio.sample_rate << " Hz\n";
// Save to WAV file
audio.save_wav("output.wav");
// Get raw PCM bytes (signed 16-bit little-endian, mono)
const kugelaudio::Bytes& pcm_data = audio.audio;
// Get WAV bytes (with header)
kugelaudio::Bytes wav_bytes = audio.to_wav();final audio = await client.tts.generate(
text: 'Hello, this is a test of the KugelAudio text-to-speech system.',
modelId: 'kugel-3.5',
voiceId: 1071,
language: 'en',
);
// Audio properties (REST reports no generation time or RTF; stream() does)
print('Duration: ${audio.durationMs}ms');
print('Samples: ${audio.samples}');
print('Sample rate: ${audio.sampleRate} Hz');
// Save to WAV file (package:kugelaudio/io.dart)
await audio.saveWav('output.wav');
// Get raw PCM bytes
final Uint8List pcmData = audio.audio;
// Get WAV bytes (with header)
final Uint8List wavBytes = audio.toWav();
// Get float32 samples in [-1.0, 1.0)
final Float32List samples = audio.toFloat32();The float, WAV and decoding helpers expect PCM16 output. For ulaw_8000 or
alaw_8000, use the audio bytes as raw G.711 data. The Rust, C++ and Dart WAV
helpers also write G.711 audio as a G.711 WAV file.
Pass numeric voice IDs. To use a voice handle, resolve it with
GET /v1/voices/{handle} and pass its id.
Generation parameters
What each parameter does, with defaults and ranges, is documented once in Generation parameters. The SDKs take the same parameters on generation and on one-shot streaming:
Snake_case keyword arguments on generate(), generate_async(), stream(), and
stream_async():
text, model_id, voice_id, cfg_scale, temperature, max_new_tokens,
sample_rate, output_format, normalize, language, word_timestamps,
speed, dictionary_ids, project_id.
voice_iddefaults toNonein the signature, but synthesis fails withMISSING_VOICE_IDwithout it.temperature=Noneleaves the value unset so the server applies its default.- Without
project_id, a project API key applies its own project's dictionaries; setproject_idto use another project's. See Dictionaries at synthesis.
CamelCase fields of GenerateOptions (see Types):
text, modelId, voiceId, cfgScale, temperature, maxNewTokens,
sampleRate, outputFormat, normalize, language, wordTimestamps,
speed, projectId, dictionaryIds.
voiceIdis optional in the type, but synthesis fails withMISSING_VOICE_IDwithout it.- When you set
outputFormat, also set the matchingsampleRate(for exampleoutputFormat: 'pcm_16000'withsampleRate: 16000) soaudio.sampleRatedescribes the audio you received. projectIdanddictionaryIdsselect pronunciation dictionaries; see Dictionaries at synthesis.AudioResponsecarries no usage. For per-request usage, callstream()and readstats.usageinonFinal.
Methods of GenerateRequest.builder(text), used by generate() and stream():
.modelId, .voiceId, .cfgScale, .temperature, .maxNewTokens,
.sampleRate, .outputFormat, .normalize, .language, .wordTimestamps,
.speed, .projectId, .dictionaryIds.
.voiceId(int)is optional in the builder, but synthesis fails withMISSING_VOICE_IDwithout it.- Omit
.temperature(...)for the server default. .outputFormat(...)acceptspcm_8000,pcm_16000,pcm_22050,pcm_24000,ulaw_8000, oralaw_8000.- With a project API key, the dictionaries of the key's project apply without
.projectId(long); set it to use another project's dictionaries. See Dictionaries at synthesis. AudioResponse.getUsage()reports the request's usage; see SessionUsage.
Setters of TtsRequest::new(text), used by generate() and stream():
.model_id, .voice_id, .cfg_scale, .temperature, .max_new_tokens,
.sample_rate, .output_format, .normalize, .language, .word_timestamps,
.speed, .dictionary_ids, .project_id.
voice_idis optional in the type, but synthesis fails withMISSING_VOICE_IDwithout it.- Leave
.temperature(...)out for the server default. .output_format(...)takes anOutputFormat(OutputFormat::Ulaw8000,OutputFormat::Pcm16000, ...). The response reports the rate and encoding it received.generate()rejects.word_timestamps(true): the REST endpoint has no word timestamps. Usestream()andcollect_response(), as in Word timestamps.generate()returns no usage, generation time or RTF;stream()reports them.
Fields of kugelaudio::GenerateRequest, used by generate() and stream():
text, model_id, voice_id, cfg_scale, temperature, max_new_tokens,
sample_rate, output_format, normalize, language, word_timestamps,
speed, dictionary_ids, project_id.
voice_idis astd::optional, but synthesis fails withMISSING_VOICE_IDwithout it.- Leave
temperatureunset for the server default. output_formattakes a token such askugelaudio::output_format::kUlaw8000. The response reports the rate and encoding it received.generate()throwsValidationErrorwhenword_timestampsis set: the REST endpoint has no word timestamps. Usestream(), as in Word timestamps.generate()returns no usage, generation time or RTF;stream()reports them.
Named arguments of generate() and stream():
text, modelId, voiceId, cfgScale, temperature, maxNewTokens,
sampleRate, outputFormat, normalize, language, speed, dictionaryIds,
projectId, plus wordTimestamps on stream() only.
voiceIdis nullable, but synthesis fails withMISSING_VOICE_IDwithout it.- Leave
temperatureout for the server default. outputFormattakes a token such asOutputFormat.ulaw8000. The response reports the rate and encoding it received.generate()returns no word timestamps, usage, generation time or RTF;stream(...).collect()reports them.
Async generation
import asyncio
async def main():
audio = await client.tts.generate_async(
text="Async generation example.",
model_id="kugel-3.5",
voice_id=1071,
)
audio.save("async_output.wav")
asyncio.run(main())// Every TypeScript SDK call is already asynchronous: generate() returns a Promise.
const audio = await client.tts.generate({
text: 'Async generation example.',
modelId: 'kugel-3.5',
voiceId: 1071,
});// The Java SDK has no async variant: generate() blocks the calling thread
// until the audio is complete (bounded by the client's timeout).// Every Rust SDK call is already asynchronous: generate() returns a future
// that runs on your Tokio runtime.
let audio = client
.tts()
.generate(&TtsRequest::new("Async generation example.").voice_id(1071))
.await?;
audio.save_wav("async_output.wav")?;#include <future>
// The C++ SDK has no async variant: generate() blocks the calling thread.
// The client is thread-safe, so run the call on another thread when needed.
std::future<kugelaudio::AudioResponse> pending = std::async(std::launch::async, [&client] {
kugelaudio::GenerateRequest request;
request.text = "Async generation example.";
request.voice_id = 1071;
return client.tts().generate(request);
});
pending.get().save_wav("async_output.wav");// Every Dart SDK call is already asynchronous: generate() returns a Future.
final audio = await client.tts.generate(
text: 'Async generation example.',
voiceId: 1071,
);Playing audio in the browser
Not applicable: the Python SDK runs on your server. Save the audio or forward its bytes to your client.
import { createWavBlob } from 'kugelaudio';
const audio = await client.tts.generate({
text: 'Hello, world!',
modelId: 'kugel-3.5',
voiceId: 1071,
});
// Create WAV blob for playback
const wavBlob = createWavBlob(audio.audio, audio.sampleRate);
const url = URL.createObjectURL(wavBlob);
// Play with Audio element
const audioElement = new Audio(url);
audioElement.play();
// Or with Web Audio API
const audioContext = new AudioContext();
const arrayBuffer = await wavBlob.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.start();Never ship a project API key to a browser: generate on your backend and send the audio to the page (see Browser support).
Not applicable: the Java SDK runs on your server. Save the audio or forward its bytes to your client.
Not applicable: the Rust SDK runs on your server or device. Save the audio or forward its bytes to your client.
Not applicable: the C++ SDK runs on your server or device. Save the audio or forward its bytes to your client.
In Flutter, including Flutter web, audio.toWav() gives the WAV file bytes that any
audio player package accepting in-memory bytes can play. The SDK leaves playback to
you.
Never ship a project API key to a browser: generate on your backend and send the audio to the page (see Browser support).
Word timestamps
Request word-level time alignments alongside audio, for subtitle synchronization, lip-sync, or barge-in handling. For timestamps while streaming, see Word timestamps in streaming.
audio = client.tts.generate(
text="Hello, how are you today?",
model_id="kugel-3.5",
voice_id=1071,
word_timestamps=True,
)
# Access word timestamps from the response
for ts in audio.word_timestamps:
print(f"{ts.word}: {ts.start_ms}ms - {ts.end_ms}ms (score: {ts.score:.2f})")
# Example output:
# Hello: 0ms - 320ms (score: 1.00)
# how: 350ms - 480ms (score: 1.00)
# are: 500ms - 580ms (score: 1.00)
# you: 600ms - 720ms (score: 1.00)
# today: 750ms - 1100ms (score: 1.00)
# Word timestamps are also available with async generation
audio = await client.tts.generate_async(
text="Hello, world!",
model_id="kugel-3.5",
voice_id=1071,
word_timestamps=True,
)
for ts in audio.word_timestamps:
print(f"{ts.word}: {ts.start_ms}-{ts.end_ms}ms")const audio = await client.tts.generate({
text: 'Hello, how are you today?',
modelId: 'kugel-3.5',
voiceId: 1071,
wordTimestamps: true,
});
// Access word timestamps from the response
for (const ts of audio.wordTimestamps) {
console.log(`${ts.word}: ${ts.startMs}ms - ${ts.endMs}ms (score: ${ts.score.toFixed(2)})`);
}
// Example output:
// Hello: 0ms - 320ms (score: 1.00)
// how: 350ms - 480ms (score: 1.00)
// are: 500ms - 580ms (score: 1.00)
// you: 600ms - 720ms (score: 1.00)
// today: 750ms - 1100ms (score: 1.00)import com.kugelaudio.sdk.WordTimestamp;
AudioResponse audio = client.tts().generate(
GenerateRequest.builder("Hello, how are you today?")
.modelId("kugel-3.5")
.voiceId(1071)
.language("en")
.wordTimestamps(true)
.build()
);
for (WordTimestamp ts : audio.getWordTimestamps()) {
System.out.printf("%s: %dms - %dms (score: %.2f)%n",
ts.getWord(), ts.getStartMs(), ts.getEndMs(), ts.getScore());
}
// Hello: 0ms - 320ms (score: 1.00)
// how: 350ms - 480ms (score: 1.00)// The REST generate() has no word timestamps: stream the request and collect it
let request = TtsRequest::new("Hello, how are you today?")
.model_id("kugel-3.5")
.voice_id(1071)
.language("en")
.word_timestamps(true);
let audio = client.tts().stream(&request).await?.collect_response().await?;
for ts in &audio.word_timestamps {
println!("{}: {}ms - {}ms (score: {:.2})", ts.word, ts.start_ms, ts.end_ms, ts.score);
}
// Hello: 0ms - 320ms (score: 1.00)
// how: 350ms - 480ms (score: 1.00)// The REST generate() has no word timestamps: stream() returns them collected
kugelaudio::GenerateRequest request;
request.text = "Hello, how are you today?";
request.model_id = "kugel-3.5";
request.voice_id = 1071;
request.language = "en";
request.word_timestamps = true;
kugelaudio::AudioResponse audio = client.tts().stream(request);
for (const auto& ts : audio.word_timestamps) {
std::cout << ts.word << ": " << ts.start_ms << "ms - " << ts.end_ms << "ms\n";
}
// Hello: 0ms - 320ms
// how: 350ms - 480ms// The REST generate() has no word timestamps: stream the request and collect it
final audio = await client.tts
.stream(
text: 'Hello, how are you today?',
modelId: 'kugel-3.5',
voiceId: 1071,
language: 'en',
wordTimestamps: true,
)
.collect();
for (final ts in audio.wordTimestamps) {
print('${ts.word}: ${ts.startMs}ms - ${ts.endMs}ms '
'(score: ${ts.score.toStringAsFixed(2)})');
}
// Hello: 0ms - 320ms (score: 1.00)
// how: 350ms - 480ms (score: 1.00)Models
List the available models:
models = client.models.list()
for model in models:
print(f"{model.id}: {model.name}")
print(f" Description: {model.description}")
print(f" Max Input: {model.max_input_length} characters")
print(f" Sample Rate: {model.sample_rate} Hz")const models = await client.models.list();
for (const model of models) {
console.log(`${model.id}: ${model.name}`);
console.log(` Description: ${model.description}`);
console.log(` Max Input: ${model.maxInputLength} characters`);
console.log(` Sample Rate: ${model.sampleRate} Hz`);
}import com.kugelaudio.sdk.Model;
import java.util.List;
List<Model> models = client.models().list();
for (Model model : models) {
System.out.println(model.getId() + ": " + model.getName());
System.out.println(" Description: " + model.getDescription());
System.out.println(" Max Input: " + model.getMaxInputLength() + " characters");
System.out.println(" Sample Rate: " + model.getSampleRate() + " Hz");
}let models = client.models().list().await?;
for model in &models {
println!("{}: {}", model.id, model.name);
println!(" Description: {}", model.description.as_deref().unwrap_or(""));
println!(" Max Input: {:?} characters", model.max_input_length);
println!(" Sample Rate: {:?} Hz", model.sample_rate);
}std::vector<kugelaudio::Model> models = client.models().list();
for (const auto& model : models) {
std::cout << model.id << ": " << model.name << '\n';
std::cout << " Description: " << model.description << '\n';
std::cout << " Max Input: " << model.max_input_length << " characters\n";
std::cout << " Sample Rate: " << model.sample_rate << " Hz\n";
}final models = await client.models.list();
for (final model in models) {
print('${model.id}: ${model.name}');
print(' Description: ${model.description}');
print(' Max Input: ${model.maxInputLength} characters');
print(' Sample Rate: ${model.sampleRate} Hz');
}Audio utilities
Helpers for converting and saving audio outside a response object:
The conversions live on the response objects: AudioResponse.save(),
to_wav_bytes(), to_float32(), and AudioChunk.to_float32(). See
AudioResponse.
import { base64ToArrayBuffer, decodePCM16, createWavFile, createWavBlob } from 'kugelaudio';
// Base64 string (a streamed chunk's audio) to ArrayBuffer
const buffer = base64ToArrayBuffer(chunk.audio);
// Base64 PCM16 to Float32Array for the Web Audio API
const floatData = decodePCM16(chunk.audio);
// A WAV file from PCM16 data
const wavBuffer = createWavFile(pcmArrayBuffer, 24000);
// A playable Blob from PCM16 data
const blob = createWavBlob(pcmArrayBuffer, 24000);
const url = URL.createObjectURL(blob);client.tts.toReadable() (Node.js only) turns a TTS stream into a Node.js
Readable; see Pipe into a Node Readable.
The AudioFormats utility class provides codec helpers:
import com.kugelaudio.sdk.AudioFormats;
// Write PCM16 data directly to a WAV file
AudioFormats.writePcm16Wav(Path.of("out.wav"), pcmBytes, 24000, (short) 1);
// Get audio duration in milliseconds
int durationMs = AudioFormats.durationMs(pcmBytes, 24000, 16, 1);
// Convert PCM16 to G.711 (for telephony)
byte[] ulaw = AudioFormats.pcm16ToUlaw(pcmBytes);
byte[] alaw = AudioFormats.pcm16ToAlaw(pcmBytes);
// Convert G.711 back to PCM16
byte[] pcm = AudioFormats.ulawToPcm16(ulawBytes);
byte[] pcmFromAlaw = AudioFormats.alawToPcm16(alawBytes);use kugelaudio::{parse_wav, pcm16_to_f32, wav_bytes, AudioEncoding};
// A WAV file from PCM16 (or G.711) data
let wav = wav_bytes(&chunk.audio, chunk.sample_rate, AudioEncoding::PcmS16le);
// PCM16 to f32 samples in [-1.0, 1.0)
let samples: Vec<f32> = pcm16_to_f32(&chunk.audio);
// Read a WAV header: format, channels, rate, bits and the data range
let info = parse_wav(&wav)?;
println!("{} Hz, {:?}s", info.sample_rate, info.duration());AudioChunk and AudioResponse also have to_f32(), and AudioResponse has
to_wav() and save_wav(path).
// A WAV file from PCM16 (or G.711) data, in memory or on disk
kugelaudio::Bytes wav = kugelaudio::to_wav(chunk.audio, chunk.sample_rate, chunk.encoding);
kugelaudio::write_wav("out.wav", chunk.audio, chunk.sample_rate, chunk.encoding);
// Bytes per sample of an encoding: 2 for "pcm_s16le", 1 for "mulaw" and "alaw"
int width = kugelaudio::bytes_per_sample(chunk.encoding);AudioResponse also has to_wav() and save_wav(path). The C++ SDK has no float
conversion; samples are signed 16-bit little-endian.
// A WAV file from PCM16 (or G.711) data
final wav = wavBytes(chunk.audio, sampleRate: chunk.sampleRate, encoding: chunk.encoding);
// PCM16 to Float32List samples in [-1.0, 1.0), for visualizers or audio graphs
final samples = pcm16ToFloat32(chunk.audio);
// Read a WAV header: format, channels, rate, bits and the data
final parsed = parseWav(wav);
print('${parsed.sampleRate} Hz, ${parsed.duration}');AudioChunk and AudioResponse also have toFloat32() and AudioResponse has
toWav(); saveWav(path) comes with package:kugelaudio/io.dart.
Next steps
- Streaming: receive audio chunks as they are generated, and streaming sessions for LLM output
- Text normalization: languages and spell tags
- Types and errors:
AudioResponse,WordTimestamp, and errors