Speech Enhancement
Remove background noise or keep one voice with the Python, TypeScript, Rust, C++ and Dart SDKs
client.enhance sends speech to Clarity (clarity-1) and returns it cleaned:
background noise removed, or, when you pass a speaker sample, only that person's
voice kept. What the two tasks do, the free month, the paid plan and the limits
are in the Speech enhancement guide; the wire
protocol is in the API reference.
generate and stream are async; generate_sync and stream_sync are their
blocking twins and take the same arguments.
It works in Node.js and in the browser. Only reading and writing files by path
needs Node.js; in the browser pass a Blob, File, ArrayBuffer or
Uint8Array.
Not available in the Java SDK yet; use the REST and WebSocket endpoints in the Speech enhancement API reference.
client.enhance() is async like every Rust SDK call; stream returns a
futures::Stream of PCM chunks.
client.enhance() methods block and are thread-safe; stream hands each enhanced
chunk to a callback on the calling thread while a sender thread uploads the input.
It works in Dart and Flutter, including Flutter web. Only reading and writing files
by path needs package:kugelaudio/io.dart; elsewhere pass the WAV bytes as
Audio(bytes, filename: ...).
Enhance a recording
import asyncio
from kugelaudio import KugelAudio, load_audio
client = KugelAudio(api_key="YOUR_API_KEY")
async def main():
result = await client.enhance.generate(load_audio("call.wav"), model="clarity-1")
print(result.duration, "seconds")
result.save("call-clean.wav")
asyncio.run(main())
# Without asyncio:
result = client.enhance.generate_sync(load_audio("call.wav"), model="clarity-1")
result.save("call-clean.wav")import { KugelAudio, loadAudio } from 'kugelaudio';
const client = new KugelAudio({ apiKey: 'YOUR_API_KEY' });
const result = await client.enhance.generate(await loadAudio('call.wav'), {
model: 'clarity-1',
});
console.log(result.duration, 'seconds');
await result.save('call-clean.wav'); // Node.js only
// In the browser, pass the file straight from an <input type="file"> and play the result
const file = input.files![0];
const cleaned = await client.enhance.generate(file, { model: 'clarity-1' });
audioElement.src = URL.createObjectURL(cleaned.toBlob());// Not available in the Java SDK yet: POST /v1/audio/enhance
// (see the Speech enhancement API reference).use kugelaudio::{load_audio, EnhanceOptions, KugelAudio};
let client = KugelAudio::new("YOUR_API_KEY")?;
let result = client
.enhance()
.generate(&load_audio("call.wav")?, &EnhanceOptions::new("clarity-1"))
.await?;
println!("{} seconds", result.duration());
result.save("call-clean.wav")?;#include <kugelaudio/kugelaudio.hpp>
kugelaudio::Client client{"YOUR_API_KEY"};
kugelaudio::EnhanceOptions options;
options.model = "clarity-1";
kugelaudio::EnhancedAudio result =
client.enhance().generate(kugelaudio::load_audio("call.wav"), options);
std::cout << result.duration() << " seconds\n";
result.save("call-clean.wav");import 'package:kugelaudio/io.dart';
final client = KugelAudio(apiKey: 'YOUR_API_KEY');
final result = await client.enhance.generate(
await loadAudioFile('call.wav'),
model: 'clarity-1',
);
print('${result.duration} long');
await result.saveWav('call-clean.wav'); // io.dart
// In Flutter, including the web, pass the WAV bytes you already have
// (recordingBytes: a Uint8List, for example from a file picker)
final cleaned = await client.enhance.generate(
Audio(recordingBytes, filename: 'call.wav'),
model: 'clarity-1',
);
final playable = cleaned.toWav(); // WAV bytes for your audio playerKeep one voice
Pass a 2 to 8 second sample of the person to keep as the speaker. The SDK then
switches to target_speaker_extraction; see
A good speaker sample.
result = client.enhance.generate_sync(
load_audio("meeting.wav"),
model="clarity-1",
speaker=load_audio("speaker.wav"),
)
result.save("one-voice.wav")const result = await client.enhance.generate(await loadAudio('meeting.wav'), {
model: 'clarity-1',
speaker: await loadAudio('speaker.wav'),
});
await result.save('one-voice.wav');// Not available in the Java SDK yet: send a speaker sample to POST /v1/audio/enhance.let options = EnhanceOptions::new("clarity-1").speaker(load_audio("speaker.wav")?);
let result = client.enhance().generate(&load_audio("meeting.wav")?, &options).await?;
result.save("one-voice.wav")?;kugelaudio::EnhanceOptions options;
options.model = "clarity-1";
options.speaker = kugelaudio::load_audio("speaker.wav");
kugelaudio::EnhancedAudio result =
client.enhance().generate(kugelaudio::load_audio("meeting.wav"), options);
result.save("one-voice.wav");final result = await client.enhance.generate(
await loadAudioFile('meeting.wav'),
model: 'clarity-1',
speaker: await loadAudioFile('speaker.wav'),
);
await result.saveWav('one-voice.wav');generate
async def generate(audio: Audio, *, model: str, speaker: Audio | None = None) -> EnhancedAudio
def generate_sync(audio: Audio, *, model: str, speaker: Audio | None = None) -> EnhancedAudio| Argument | Type | Description |
|---|---|---|
audio | Audio | The recording, from load_audio. Any WAV the API accepts: 16-, 24- or 32-bit integer or 32-bit float PCM, mono or stereo, 8 to 48 kHz, at most 300 seconds. |
model | str | Required. "clarity-1". |
speaker | Audio | None | A 2 to 8 second sample of one voice, from load_audio. Given: only that voice is kept. Omitted: noise is removed and every voice is kept. |
Returns an EnhancedAudio with the same duration as the input.
The request is bounded by the client's timeout (default 60 seconds).
Every generate call on one client reuses the same kept-alive connection, so
only the first request pays for connecting.
generate(audio: AudioInput, options: EnhanceGenerateOptions): Promise<EnhancedAudio>| Argument | Type | Description |
|---|---|---|
audio | AudioInput | The recording: await loadAudio(...), or the WAV file itself as a Blob, File, ArrayBuffer or Uint8Array. Any WAV the API accepts: 16-, 24- or 32-bit integer or 32-bit float PCM, mono or stereo, 8 to 48 kHz, at most 300 seconds. File paths are not accepted here; load them with loadAudio. |
options.model | string | Required. 'clarity-1'. |
options.speaker | AudioInput | Optional. A 2 to 8 second sample of one voice. Given: only that voice is kept. Omitted: noise is removed and every voice is kept. |
Returns an EnhancedAudio with the same duration as the input.
Every generate call on one client reuses the kept-alive connections of
fetch, so only the first request pays for connecting.
Not available in the Java SDK yet; see Enhance a recording in the API reference.
pub async fn generate(&self, audio: &Audio, options: &EnhanceOptions) -> Result<EnhancedAudio>| Argument | Type | Description |
|---|---|---|
audio | &Audio | The recording, from load_audio. Any WAV the API accepts: 16-, 24- or 32-bit integer or 32-bit float PCM, mono or stereo, 8 to 48 kHz, at most 300 seconds. |
options | &EnhanceOptions | EnhanceOptions::new("clarity-1"); the model is required. .speaker(audio) adds a 2 to 8 second sample of one voice: only that voice is kept. Without it, noise is removed and every voice is kept. |
Returns an EnhancedAudio with the same duration as the input.
The request is bounded by the client's timeout (default 60 seconds), and every
generate call on one client reuses the same kept-alive connection.
EnhancedAudio generate(const AudioFile& audio, const EnhanceOptions& options) const;| Argument | Type | Description |
|---|---|---|
audio | const AudioFile& | The recording, from load_audio. Any WAV the API accepts: 16-, 24- or 32-bit integer or 32-bit float PCM, mono or stereo, 8 to 48 kHz, at most 300 seconds. |
options.model | std::string | Required. "clarity-1". |
options.speaker | std::optional<AudioFile> | A 2 to 8 second sample of one voice, from load_audio. Set: only that voice is kept. Unset: noise is removed and every voice is kept. |
Returns an EnhancedAudio with the same duration as the input.
The request is bounded by the client's timeout (default 60 seconds).
Future<EnhancedAudio> generate(Audio audio, {required String model, Audio? speaker})| Argument | Type | Description |
|---|---|---|
audio | Audio | The recording: await loadAudioFile(path) from io.dart, or Audio(bytes, filename: ...). Any WAV the API accepts: 16-, 24- or 32-bit integer or 32-bit float PCM, mono or stereo, 8 to 48 kHz, at most 300 seconds. |
model | String | Required. 'clarity-1'. |
speaker | Audio? | A 2 to 8 second sample of one voice. Given: only that voice is kept. Omitted: noise is removed and every voice is kept. |
Returns an EnhancedAudio with the same duration as the input.
The request is bounded by the client's timeout (default 60 seconds).
prewarm
Readies enhancement ahead of time. Enhancement capacity scales down when
nobody uses it, so the first request after a quiet period can wait several
seconds; prewarm sends the
warm-up request,
which starts enhancement in the background and returns at once, and it opens
the connection the next request reuses. Call it as soon as you know audio is
coming, for example when the user opens the recording screen.
await client.enhance.prewarm()
result = await client.enhance.generate(load_audio("call.wav"), model="clarity-1")await client.enhance.prewarm();
const result = await client.enhance.generate(await loadAudio('call.wav'), { model: 'clarity-1' });// Not available in the Java SDK yet: send the warm-up request from the API reference.client.enhance().prewarm().await;
let result = client
.enhance()
.generate(&load_audio("call.wav")?, &EnhanceOptions::new("clarity-1"))
.await?;client.enhance().prewarm();
kugelaudio::EnhancedAudio result =
client.enhance().generate(kugelaudio::load_audio("call.wav"), options);await client.enhance.prewarm();
final result = await client.enhance.generate(
await loadAudioFile('call.wav'),
model: 'clarity-1',
);The warm-up is not billed and does not count against your rate limits. It never
fails your code: a network error, a timeout, or a refused warm-up such as 401 for
a bad API key is logged as a warning (console.warn in TypeScript, tracing in
Rust, the log_handler in C++, package:logging in Dart), and the first request
connects as usual and raises its own error.
async def prewarm() -> None
def prewarm_sync() -> NoneA connection that sits idle for 50 seconds is closed. prewarm warms the
connection generate uses and prewarm_sync the one generate_sync uses. The
LiveKit and Pipecat enhancement filters call it for you when they start.
prewarm(): Promise<void>fetch closes connections that sit idle for a few seconds, so call prewarm()
shortly before the audio arrives.
Not available in the Java SDK yet.
pub async fn prewarm(&self)Returns nothing and never fails; a failed warm-up is logged through tracing.
void prewarm() const;Never throws; a failed warm-up is logged as a warning.
Future<void> prewarm()Never throws; a failed warm-up is logged as a warning on the kugelaudio logger.
Enhance live audio
stream opens a WebSocket, sends your audio in the background while you iterate,
and yields enhanced mono 16-bit PCM at 24 kHz as it is produced. Iteration ends once
the last input chunk has been enhanced; the total output matches the input's
duration.
import asyncio
from kugelaudio import KugelAudio, load_audio_stream
client = KugelAudio(api_key="YOUR_API_KEY")
async def main():
enhanced = bytearray()
audio = load_audio_stream("call.wav")
async for chunk in client.enhance.stream(audio, model="clarity-1"):
enhanced += chunk # mono PCM16 at 24 kHz, as it arrives
asyncio.run(main())
# stream_sync is the blocking form; it sends from a background thread
for chunk in client.enhance.stream_sync(load_audio_stream("call.wav"), model="clarity-1"):
print(len(chunk), "bytes")import { KugelAudio, loadAudio, loadAudioStream } from 'kugelaudio';
const client = new KugelAudio({ apiKey: 'YOUR_API_KEY' });
const input = await loadAudioStream('call.wav');
for await (const chunk of client.enhance.stream(input, { model: 'clarity-1' })) {
console.log(chunk.length, 'bytes'); // mono PCM16 at 24 kHz, as Uint8Array chunks
}// Not available in the Java SDK yet: use the /v1/audio/enhance/stream WebSocket
// (see the Speech enhancement API reference).use futures_util::StreamExt;
use kugelaudio::{load_audio_stream, EnhanceOptions, KugelAudio};
let client = KugelAudio::new("YOUR_API_KEY")?;
let mut enhanced = Vec::new();
let input = load_audio_stream("call.wav", 0.1)?;
let mut output = client.enhance().stream(input, &EnhanceOptions::new("clarity-1"));
while let Some(chunk) = output.next().await {
enhanced.extend_from_slice(&chunk?); // mono PCM16 at 24 kHz, as it arrives
}kugelaudio::Client client{"YOUR_API_KEY"};
kugelaudio::EnhanceOptions options;
options.model = "clarity-1";
kugelaudio::Bytes enhanced;
client.enhance().stream(kugelaudio::load_audio_stream("call.wav"), options,
[&enhanced](const kugelaudio::Bytes& pcm) {
// mono PCM16 at 24 kHz, as it arrives
enhanced.insert(enhanced.end(), pcm.begin(), pcm.end());
});import 'package:kugelaudio/io.dart';
final client = KugelAudio(apiKey: 'YOUR_API_KEY');
final input = await loadAudioStreamFile('call.wav'); // or AudioStream.fromWav(bytes)
await for (final chunk in client.enhance.stream(input, model: 'clarity-1')) {
print('${chunk.length} bytes'); // mono PCM16 at 24 kHz, as Uint8List chunks
}Add a speaker sample (speaker=load_audio("speaker.wav"),
speaker: await loadAudio('speaker.wav'), .speaker(load_audio("speaker.wav")?),
options.speaker = kugelaudio::load_audio("speaker.wav"),
speaker: await loadAudioFile('speaker.wav')) to keep one voice.
Raw PCM input
Audio that is not a WAV file, such as a live capture, goes in as any iterable (or async iterable) of mono 16-bit little-endian PCM chunks, with its sample rate:
def pcm_chunks(path, chunk_bytes=3200): # 0.1 s of mono PCM16 at 16 kHz
with open(path, "rb") as f:
while chunk := f.read(chunk_bytes):
yield chunk
for chunk in client.enhance.stream_sync(
pcm_chunks("call.pcm"), model="clarity-1", sample_rate=16000
):
print(len(chunk), "bytes")async function* capture(): AsyncGenerator<Uint8Array> {
// yield mono PCM16 chunks at 16 kHz as your source produces them
}
for await (const chunk of client.enhance.stream(capture(), {
model: 'clarity-1',
sampleRate: 16000,
})) {
play(chunk);
}// Not available in the Java SDK yet.use kugelaudio::PcmInput;
// capture: any futures::Stream of Vec<u8> mono PCM16 chunks at 16 kHz
let input = PcmInput::new(capture, 16_000);
let mut output = client.enhance().stream(input, &EnhanceOptions::new("clarity-1"));
while let Some(chunk) = output.next().await {
play(&chunk?);
}#include <fstream>
// The source is pulled on a sender thread; return std::nullopt at the end
std::ifstream file{"call.pcm", std::ios::binary};
kugelaudio::PcmSource source = [&file]() -> std::optional<kugelaudio::Bytes> {
kugelaudio::Bytes chunk(3200); // 0.1 s of mono PCM16 at 16 kHz
file.read(reinterpret_cast<char*>(chunk.data()), static_cast<std::streamsize>(chunk.size()));
chunk.resize(static_cast<std::size_t>(file.gcount()));
if (chunk.empty()) return std::nullopt;
return chunk;
};
client.enhance().stream(source, 16000, options,
[](const kugelaudio::Bytes& pcm) { play(pcm); });// capture: any Stream<List<int>> of mono PCM16 chunks at 16 kHz
await for (final chunk in client.enhance.stream(
capture,
model: 'clarity-1',
sampleRate: 16000,
)) {
play(chunk);
}In Python the chunks are bytes (stream_sync takes only synchronous iterables);
in TypeScript they are Uint8Array or ArrayBuffer; in Rust Vec<u8> items of a
PcmInput (PcmInput::from_chunks takes chunks already in memory); in C++ the
Bytes your PcmSource returns; in Dart List<int> events of a stream. Chunks of
at most one second work best; longer ones are split before sending.
stream
def stream(
audio: AudioStream | Iterable[bytes] | AsyncIterable[bytes],
*,
model: str,
sample_rate: int | None = None,
speaker: Audio | None = None,
) -> AsyncIterator[bytes]
def stream_sync(
audio: AudioStream | Iterable[bytes],
*,
model: str,
sample_rate: int | None = None,
speaker: Audio | None = None,
) -> Iterator[bytes]| Argument | Type | Description |
|---|---|---|
audio | AudioStream, or an iterable of bytes | An AudioStream from load_audio_stream, or raw mono PCM16 chunks. stream_sync does not take async iterables. |
model | str | Required. "clarity-1". |
sample_rate | int | None | Sample rate of raw chunks, 8000 to 48000. Required for raw chunks; taken from an AudioStream, and must match it if you pass both. |
speaker | Audio | None | A 2 to 8 second sample of one voice, from load_audio. |
Breaking out of the loop closes the connection and stops sending. With stream,
wrap the iterator in contextlib.aclosing to make that cleanup immediate. The
client's timeout bounds the wait for the stream to become ready.
stream(
audio: AudioStream | Iterable<PcmChunk> | AsyncIterable<PcmChunk>,
options: EnhanceStreamOptions,
): AsyncGenerator<Uint8Array, void, undefined>| Argument | Type | Description |
|---|---|---|
audio | AudioStream, or an iterable of PcmChunk | An AudioStream from loadAudioStream, or raw mono PCM16 chunks. |
options.model | string | Required. 'clarity-1'. |
options.sampleRate | number | Sample rate of raw chunks, 8000 to 48000. Required for raw chunks; taken from an AudioStream, and must match it if you pass both. |
options.speaker | AudioInput | Optional. A 2 to 8 second sample of one voice. |
Breaking out of the loop closes the connection and stops sending. The client's
timeout bounds the wait for the stream to become ready.
Not available in the Java SDK yet; see Enhance live audio in the API reference.
pub fn stream(
&self,
input: impl Into<PcmInput>,
options: &EnhanceOptions,
) -> BoxStream<'static, Result<Vec<u8>>>| Argument | Type | Description |
|---|---|---|
input | impl Into<PcmInput> | An AudioStream from load_audio_stream, or a PcmInput of raw mono PCM16 chunks with their sample rate (8000 to 48000). |
options | &EnhanceOptions | EnhanceOptions::new("clarity-1"), optionally with .speaker(audio). |
Input is sent from a background task while you read. Dropping the stream closes the
connection and stops sending. The client's timeout bounds the wait for the stream
to become ready.
void stream(const AudioStream& audio, const EnhanceOptions& options,
const PcmCallback& on_pcm) const;
void stream(const PcmSource& source, int sample_rate, const EnhanceOptions& options,
const PcmCallback& on_pcm) const;| Argument | Type | Description |
|---|---|---|
audio | const AudioStream& | An AudioStream from load_audio_stream. |
source, sample_rate | PcmSource, int | Raw mono PCM16 chunks pulled on a sender thread (return std::nullopt at the end), and their sample rate, 8000 to 48000. |
options | const EnhanceOptions& | model (required) and an optional speaker. |
on_pcm | PcmCallback | Receives each enhanced chunk on the calling thread. |
stream returns once the last input was enhanced. The client's timeout bounds the
wait for the stream to become ready.
Stream<Uint8List> stream(
Stream<List<int>> audio, {
required String model,
int? sampleRate,
Audio? speaker,
})| Argument | Type | Description |
|---|---|---|
audio | Stream<List<int>> | An AudioStream, or any stream of raw mono PCM16 chunks. |
model | String | Required. 'clarity-1'. |
sampleRate | int? | Sample rate of raw chunks, 8000 to 48000. Required for raw chunks; taken from an AudioStream, and must match it if you pass both. |
speaker | Audio? | A 2 to 8 second sample of one voice. |
Input is sent while you listen. Cancelling the subscription closes the connection.
The client's timeout bounds the wait for the stream to become ready.
Several audios on one connection
Each stream call opens its own WebSocket. To enhance one audio after another,
for example one utterance at a time in a voice agent, open a session instead:
it keeps one connection warm and sends each audio over it, so only the first
pays for connecting. connect() opens it ahead of the first audio.
async with client.enhance.session() as session: # connects here
for path in ("first.wav", "second.wav"):
async for chunk in session.stream(load_audio_stream(path), model="clarity-1"):
play(chunk)const session = client.enhance.session();
await session.connect();
try {
for (const path of ['first.wav', 'second.wav']) {
const input = await loadAudioStream(path);
for await (const chunk of session.stream(input, { model: 'clarity-1' })) {
play(chunk);
}
}
} finally {
await session.close();
}// Not available in the Java SDK yet.let options = EnhanceOptions::new("clarity-1");
let mut session = client.enhance().session();
session.connect().await?;
for path in ["first.wav", "second.wav"] {
let mut output = session.stream(load_audio_stream(path, 0.1)?, &options);
while let Some(chunk) = output.next().await {
play(&chunk?);
}
}
session.close().await;kugelaudio::EnhanceSession session = client.enhance().session();
session.connect();
for (const char* path : {"first.wav", "second.wav"}) {
session.stream(kugelaudio::load_audio_stream(path), options,
[](const kugelaudio::Bytes& pcm) { play(pcm); });
}
session.close(); // the destructor closes it toofinal session = client.enhance.session();
await session.connect();
try {
for (final path in ['first.wav', 'second.wav']) {
final input = await loadAudioStreamFile(path);
await for (final chunk in session.stream(input, model: 'clarity-1')) {
play(chunk);
}
}
} finally {
await session.close();
}session.stream takes the same arguments as stream, and each call may use a
different speaker or sample rate. Streams on one session run one after another;
each is a request of its own for rate limits
and billing. If the server closed the connection in the meantime (60 seconds
without audio, one hour after it opened, or a restart), the next stream
reconnects without an error. Breaking out of a stream closes the connection;
the next stream opens a new one. The
WebSocket reference lists the
session limits.
Loading audio
The loaders read a WAV file for generate and for the speaker sample, or a 16-bit
WAV as a chunked stream for stream. A loaded file is sent as-is and decoded by the
server, so every supported WAV format works for generate.
load_audio
def load_audio(source: str | os.PathLike | bytes) -> AudioLoads a WAV file, from a path or its bytes, for generate and for speaker=.
The returned Audio has:
| Field | Type | Description |
|---|---|---|
data | bytes | The WAV file bytes. |
filename | str | File name sent with the upload ("audio.wav" for bytes). |
duration | float | None | Duration in seconds when the header can be read locally, else None (for example float WAVs). |
load_audio_stream
def load_audio_stream(source: str | os.PathLike | bytes, *, chunk_seconds: float = 0.1) -> AudioStreamLoads a 16-bit PCM WAV for stream. Stereo and multi-channel audio is mixed
down to mono, and the sample rate is read from the file. chunk_seconds (greater
than 0, at most 1) sets the length of each chunk; chunks are sent as fast as the
connection allows, and small ones let enhanced audio start coming back sooner.
The returned AudioStream has sample_rate (Hz) and duration (seconds), and
iterating it yields the bytes chunks; iterating again starts from the
beginning. For 24-bit or float WAVs, use generate instead or convert to 16-bit.
loadAudio
loadAudio(source: AudioSource): Promise<LoadedAudio>Loads a WAV file for generate and for speaker. source is a path (Node.js
only) or the WAV file as a Blob, File, ArrayBuffer or Uint8Array. The
returned LoadedAudio has:
| Field | Type | Description |
|---|---|---|
data | Uint8Array | The WAV file bytes. |
filename | string | File name sent with the upload ('audio.wav' when there is none). |
duration | number | undefined | Duration in seconds when the header can be read locally. |
loadAudioStream
loadAudioStream(source: AudioSource, options?: LoadAudioStreamOptions): Promise<AudioStream>Loads a 16-bit integer PCM WAV for stream. Stereo and multi-channel audio
is mixed down to mono, and the sample rate is read from the file.
options.chunkSeconds (greater than 0, at most 1, default 0.1) sets the length
of each chunk; chunks are sent as fast as the connection allows, and small ones
let enhanced audio start coming back sooner.
The returned AudioStream has sampleRate (Hz) and duration (seconds), and
iterating it (with for or for await) yields Uint8Array chunks; iterating
again starts from the beginning. For 24-bit or float WAVs, use generate
instead or convert to 16-bit.
Not available in the Java SDK yet.
load_audio
pub fn load_audio(path: impl AsRef<Path>) -> Result<Audio>Loads a WAV file for generate and for .speaker(...).
Audio::from_wav_bytes(bytes, filename) does the same for bytes in memory. The
returned Audio has the public fields data (the WAV file bytes) and filename,
and duration() (Option<f64> seconds, when the header can be read locally).
load_audio_stream
pub fn load_audio_stream(path: impl AsRef<Path>, chunk_seconds: f64) -> Result<AudioStream>Loads a 16-bit PCM WAV for stream. Stereo and multi-channel audio is mixed
down to mono, and the sample rate is read from the file. chunk_seconds (greater
than 0, at most 1; 0.1 is a good default) sets the length of each chunk.
AudioStream::from_wav_bytes(&bytes, chunk_seconds) and
AudioStream::from_pcm(pcm, sample_rate, chunk_seconds) build one from memory.
The returned AudioStream has sample_rate(), duration() (seconds), chunks()
and pcm(). For 24-bit or float WAVs, use generate instead or convert to 16-bit.
load_audio
AudioFile load_audio(const std::filesystem::path& path);
AudioFile load_audio(Bytes wav_bytes, std::string filename = "audio.wav");Loads a WAV file, from a path or its bytes, for generate and for
options.speaker. The returned AudioFile has data (the WAV file bytes),
filename, and duration (std::optional<double> seconds, when the header can be
read locally).
load_audio_stream
AudioStream load_audio_stream(const std::filesystem::path& path, double chunk_seconds = 0.1);
AudioStream load_audio_stream(const Bytes& wav_bytes, double chunk_seconds = 0.1);Loads a 16-bit PCM WAV for stream. Stereo and multi-channel audio is mixed
down to mono, and the sample rate is read from the file. chunk_seconds (greater
than 0, at most 1) sets the length of each chunk. AudioStream(pcm, sample_rate, chunk_seconds) wraps raw mono PCM16 you already have.
The returned AudioStream has sample_rate(), duration() (seconds), chunks()
and pcm(). For 24-bit or float WAVs, use generate instead or convert to 16-bit.
Audio and loadAudioFile
Audio(Uint8List data, {String filename = 'audio.wav'})
Future<Audio> loadAudioFile(String path) // package:kugelaudio/io.dartAn Audio wraps the WAV file bytes for generate and for speaker:; they are sent
as they are. It has data, filename, and duration (a Duration?, when the
header can be read locally). loadAudioFile reads a file where dart:io exists.
AudioStream and loadAudioStreamFile
AudioStream.fromWav(Uint8List wav, {double chunkSeconds = 0.1})
AudioStream(Uint8List pcm, {required int sampleRate, double chunkSeconds = 0.1})
Future<AudioStream> loadAudioStreamFile(String path, {double chunkSeconds = 0.1}) // io.dartAudioStream.fromWav parses a 16-bit PCM WAV for stream. Stereo and
multi-channel audio is mixed down to mono, and the sample rate is read from the
file. chunkSeconds sets the length of each chunk.
An AudioStream is a Stream<Uint8List> with sampleRate and duration; every
listen replays the audio from the start. For 24-bit or float WAVs, use generate
instead or convert to 16-bit.
EnhancedAudio
What generate returns: mono 16-bit PCM at 24 kHz.
| Member | Type | Description |
|---|---|---|
audio | bytes | Raw mono PCM16 samples, little-endian. |
sample_rate | int | 24000. |
duration | float | Duration in seconds. |
wav | bytes | The audio as WAV file bytes. |
save(path) | method | Writes the audio to path as a WAV file. |
| Member | Type | Description |
|---|---|---|
audio | Uint8Array | Raw mono PCM16 samples, little-endian. |
sampleRate | number | 24000. |
duration | number | Duration in seconds. |
wav | Uint8Array | The audio as WAV file bytes. |
toBlob() | Blob | The audio as an audio/wav Blob, for example for an <audio> element. |
save(path) | Promise<void> | Writes the audio to path as a WAV file. Node.js only. |
The package also exports ENHANCED_SAMPLE_RATE (24000),
TASK_NOISE_REMOVAL, TASK_TARGET_SPEAKER_EXTRACTION and the types
AudioInput, PcmChunk, AudioSource, EnhanceGenerateOptions,
EnhanceStreamOptions and LoadAudioStreamOptions.
Not available in the Java SDK yet.
| Member | Type | Description |
|---|---|---|
audio | Vec<u8> | Raw mono PCM16 samples, little-endian. |
sample_rate | u32 | 24000. |
duration() | f64 | Duration in seconds. |
to_wav() | Vec<u8> | The audio as WAV file bytes. |
save(path) | Result<()> | Writes the audio to path as a WAV file. |
The crate also exports ENHANCED_SAMPLE_RATE (24000).
| Member | Type | Description |
|---|---|---|
audio | Bytes | Raw mono PCM16 samples, little-endian. |
sample_rate | int | 24000. |
duration() | double | Duration in seconds. |
wav() | Bytes | The audio as WAV file bytes. |
save(path) | void | Writes the audio to path as a WAV file. |
The library also exports kugelaudio::kEnhancedSampleRate (24000).
| Member | Type | Description |
|---|---|---|
audio | Uint8List | Raw mono PCM16 samples, little-endian. |
sampleRate | int | 24000. |
duration | Duration | Duration of the audio. |
toWav() | Uint8List | The audio as WAV file bytes, for example for an audio player. |
saveWav(path) | Future<File> | Writes the audio to path as a WAV file. From package:kugelaudio/io.dart. |
The package also exports enhancedSampleRate (24000), taskNoiseRemoval and
taskTargetSpeakerExtraction.
Errors
Raised by the SDK itself, not the server:
| Exception | When |
|---|---|
TypeError | audio (for generate) or speaker is not an Audio, for example a path string. Wrap it in load_audio(...). |
ValidationError | model is empty; stream got a path or WAV bytes (use load_audio_stream); raw chunks without sample_rate, or a sample_rate that does not match the AudioStream; a chunk that is not bytes; load_audio_stream got a WAV that is not 16-bit, or a chunk_seconds outside (0, 1]; an unreadable or empty file. |
Raised from the server's answer, all subclasses of KugelAudioError with
status_code, error_code, request_id and retry_after:
ValidationError (the server rejected the input), RateLimitError,
InsufficientCreditsError, AuthenticationError and
KugelAudioConnectionError.
A stream refused while connecting reports both 402s with error_code
INSUFFICIENT_CREDITS; on the free month it means the month is over.
Thrown by the SDK itself, not the server:
| Error | When |
|---|---|
TypeError | audio (for generate) or speaker is not audio, for example a path string. Wrap it in await loadAudio(...). |
ValidationError | model is empty; stream got a WAV file or LoadedAudio (use loadAudioStream); raw chunks without sampleRate, or a sampleRate that does not match the AudioStream; a chunk that is not binary; loadAudioStream got a WAV that is not 16-bit integer PCM, or a chunkSeconds outside (0, 1]; an unreadable or empty file. |
KugelAudioError | A file path was used outside Node.js. |
Thrown from the server's answer, all subclasses of KugelAudioError with
statusCode, errorCode, requestId and retryAfter: ValidationError (the
server rejected the input), RateLimitError, InsufficientCreditsError,
AuthenticationError and ConnectionError.
In Node.js, a stream refused while connecting reports both 402s with
errorCode INSUFFICIENT_CREDITS; on the free month it means the month is
over. A browser's WebSocket does not reveal why an upgrade was refused, so in
the browser every refusal while connecting (a bad key, a limit, the free month,
capacity) arrives as ConnectionError. Call generate once to see the reason.
Not available in the Java SDK yet.
Returned by the SDK itself, not the server, as Error::Validation: model is
empty; a WAV file that cannot be read or is empty; load_audio_stream got a WAV
that is not 16-bit PCM, or a chunk_seconds outside (0, 1].
Returned from the server's answer, each carrying status_code(), error_code(),
request_id() and retry_after(): Error::Validation (the server rejected the
input), Error::RateLimit, Error::InsufficientCredits, Error::Authentication
and Error::Connection.
Thrown by the SDK itself, not the server, as ValidationError: model is empty; a
WAV file that cannot be read or is empty; load_audio_stream got a WAV that is not
16-bit PCM, or a chunk_seconds outside (0, 1].
Thrown from the server's answer, all subclasses of KugelAudioError with
status_code(), error_code(), request_id() and retry_after():
ValidationError (the server rejected the input), RateLimitError,
InsufficientCreditsError, AuthenticationError and ConnectionError.
Thrown by the SDK itself, not the server, as ValidationException: model is
empty; raw chunks without sampleRate, or a sampleRate that does not match the
AudioStream; AudioStream.fromWav got a WAV that is not 16-bit PCM.
Thrown from the server's answer, all subclasses of KugelAudioException with
statusCode, errorCode, requestId and retryAfter: ValidationException (the
server rejected the input), RateLimitException, InsufficientCreditsException,
AuthenticationException and ConnectionException. In the browser, every refusal
while connecting a stream arrives as ConnectionException, because a browser's
WebSocket does not reveal why an upgrade was refused. Call generate once to see
the reason.
What each server error means and what to do is in Limits and errors.
from kugelaudio import InsufficientCreditsError, RateLimitError
try:
result = client.enhance.generate_sync(load_audio("call.wav"), model="clarity-1")
except RateLimitError as e:
print("retry in", e.retry_after, "seconds") # None for the concurrency limit
except InsufficientCreditsError as e:
print(e.error_code, e.message) # INSUFFICIENT_CREDITS or FREE_PERIOD_ENDEDimport { InsufficientCreditsError, RateLimitError } from 'kugelaudio';
try {
await client.enhance.generate(await loadAudio('call.wav'), { model: 'clarity-1' });
} catch (e) {
if (e instanceof RateLimitError) {
console.log('retry in', e.retryAfter, 'seconds'); // undefined for the concurrency limit
} else if (e instanceof InsufficientCreditsError) {
console.log(e.errorCode, e.message); // INSUFFICIENT_CREDITS or FREE_PERIOD_ENDED
} else {
throw e;
}
}// Not available in the Java SDK yet.use kugelaudio::Error;
let options = EnhanceOptions::new("clarity-1");
match client.enhance().generate(&load_audio("call.wav")?, &options).await {
Ok(result) => result.save("call-clean.wav")?,
// None for the concurrency limit
Err(Error::RateLimit(e)) => println!("retry in {:?} seconds", e.retry_after),
// INSUFFICIENT_CREDITS or FREE_PERIOD_ENDED
Err(Error::InsufficientCredits(e)) => println!("{:?} {}", e.error_code, e.message),
Err(e) => return Err(e),
}try {
client.enhance().generate(kugelaudio::load_audio("call.wav"), options);
} catch (const kugelaudio::RateLimitError& e) {
// retry_after() is empty for the concurrency limit
std::cout << "retry in " << e.retry_after().value_or(0) << " seconds\n";
} catch (const kugelaudio::InsufficientCreditsError& e) {
// INSUFFICIENT_CREDITS or FREE_PERIOD_ENDED
std::cout << e.error_code().value_or("") << ' ' << e.message() << '\n';
}try {
await client.enhance.generate(await loadAudioFile('call.wav'), model: 'clarity-1');
} on RateLimitException catch (e) {
print('retry in ${e.retryAfter}'); // null for the concurrency limit
} on InsufficientCreditsException catch (e) {
print('${e.errorCode} ${e.message}'); // INSUFFICIENT_CREDITS or FREE_PERIOD_ENDED
}Next steps
- Speech enhancement guide: tasks, free month, paid plan and limits
- Speech enhancement API: REST and WebSocket wire protocol
- LiveKit and Pipecat: enhancement as a voice-agent input filter