.NET client library for the audio.cpp server protocol: text-to-speech, transcription, alignment, live (duplex) routes, generic task execution, batches, recipes, voices, and model management.
- NuGet:
Mabs.AudioCppClient(0.0.2) - Target frameworks:
netstandard2.0,net10.0 - Dependencies:
System.Text.Jsononly (Microsoft.Bcl.AsyncInterfaceson netstandard2.0) - No OpenAI dependency; speaks the audio.cpp wire protocol directly
- Server default port is
8080(--port); the examples below assume a server on that port.
using Mabs.AudioCpp;
using Mabs.AudioCpp.Models;
AudioCppClient client = new AudioCppClient("http://127.0.0.1:8080");
// Health
AudioCppHealth health = await client.GetHealthAsync();
// Speech synthesis (returns WAV/PCM/MP3 bytes as the server produced them)
SpeechResult speech = await client.Speech.SynthesizeAsync(new SpeechRequest
{
Model = "kokoro",
Input = "Hello from audio.cpp",
Voice = "af_bella",
});
File.WriteAllBytes("hello.wav", speech.Audio);
// speech.Timing carries X-AudioCPP-Wall-Ms / Audio-Duration-Ms / RTF.
// Transcription from a server-local path
TranscriptionResult text = await client.Transcriptions.TranscribeAsync(new TranscriptionRequest
{
Model = "parakeet",
AudioPath = "/data/hello.wav",
});
Console.WriteLine(text.Text);
// Transcription by upload (multipart/form-data, OpenAI-compatible)
using FileStream wav = File.OpenRead("hello.wav");
TranscriptionResult uploaded = await client.Transcriptions.TranscribeAsync(new TranscriptionRequest
{
Model = "parakeet",
AudioFile = wav,
AudioFileName = "hello.wav",
});Streaming (C# 8+ / .NET targets — IAsyncEnumerable):
using Stream out = File.OpenWrite("out.pcm");
await foreach (SpeechStreamEvent evt in client.Speech.StreamAsync(new SpeechStreamRequest
{
Request = new SpeechRequest { Model = "stream-tts", Input = "Tell me a story" },
ReturnText = true,
}))
{
if (evt.Kind == SpeechStreamEventKind.AudioDelta) out.Write(evt.Pcm!);
if (evt.Kind == SpeechStreamEventKind.TextDelta) Console.Write(evt.Text);
}On netstandard2.0, IAsyncEnumerable streaming is available via Microsoft.Bcl.AsyncInterfaces,
and every streaming method also has a callback overload (Func<TEvent, Task> onEvent) for
callers without await foreach language support.
| Area | Method | Route | Client call |
|---|---|---|---|
| Health | GET | /health |
GetHealthAsync() |
| Models | GET | /v1/models |
ListModelsAsync(includeParams, includeSessionOptions) |
| Model load | POST | /v1/models/load |
LoadModelAsync(ModelLoadRequest) (needs --ui-management; Path or PackageId) |
| Model unload | POST | /v1/tasks/unload_models |
UnloadModelsAsync(ids) |
| Unload all models | POST | /v1/tasks/unload_all_models |
UnloadAllModelsAsync() |
| Voices | GET | /v1/audio/voices |
ListVoicesAsync(model?) |
| Recipes | GET | /v1/recipes |
ListRecipesAsync() |
| Recipe run | POST | /v1/recipes/run |
Recipes.RunAsync(...) / RunStreamAsync(...) |
| Speech | POST | /v1/audio/speech |
Speech.SynthesizeAsync(...) → SpeechResult / SynthesizeJsonAsync → base64 envelope |
| Speech stream | POST | /v1/audio/speech (SSE) |
Speech.StreamAsync(...) / StreamPcmAsync |
| Live speech | POST | /v1/audio/speech/live (duplex SSE) |
Speech.SpeakLiveAsync(request, pcmStream) |
| Transcription | POST | /v1/audio/transcriptions |
Transcriptions.TranscribeAsync(...) (JSON path or multipart upload) |
| Transcription details | POST | /v1/audio/transcriptions/details |
TranscribeDetailsAsync(...) |
| Transcription stream | POST (SSE) | same | TranscribeStreamAsync(...) |
| Live transcription | POST | /v1/audio/transcriptions/live (duplex) |
TranscribeLiveAsync(request, pcmStream) |
| Batch transcription | POST (SSE) | /v1/batches/transcriptions |
TranscribeBatchAsync(...) |
| Alignment | POST (multipart) | /v1/audio/alignments |
Alignments.RunAsync(...) |
| Task run | POST | /v1/tasks/run |
Tasks.RunAsync(...) |
| Task stream | POST | /v1/tasks/stream |
Tasks.StreamAsync(...) (StreamFormat = "sse" streams; null uses the buffered JSON envelope) |
| Task batch | POST (SSE) | /v1/tasks/batch |
Tasks.BatchAsync(...) |
- Live routes are duplex.
SpeakLiveAsync/TranscribeLiveAsyncPOST raw PCM (Transfer-Encoding: chunked) and read SSE on the same connection: feed the request stream as your microphone produces data. Parameters travel as query string; the body is audio bytes only. - Streaming speech requires PCM. The client forces
response_format=pcmforStreamAsyncbecause the server rejects other formats on the SSE path. AudioCppExceptioncarries the server'serror.message,error.type, and HTTP status. On error-terminated SSE streams the exception is thrown mid-iteration.- Unknown models return HTTP 500 (
type=server_error) onv0.9.3andv0.9.4, despite older docs saying 404. The client surfaces whatever the server sends. - Model load by package id (
v0.9.4, native-model-manager builds):ModelLoadRequest.PackageIdreplacesPathand the server resolves the load target from the package registry. Sending both, or a non-string/emptypackage_id, is 400invalid_request_error; an unknown or not-installed package is 404not_found. The client enforces the either-or rule locally inValidate(). include_artifacts(v0.9.4): top-level flag onTaskStreamRequestandRecipeRunRequest(defaultfalse). When set, stream events carry anartifactsarray and artifact-only events count towardttft_ms.- Options map (
Dictionary<string, string>) passes model-specific request options through verbatim; the client throws locally if a typed property and an option key collide, instead of silently picking one. - Seed is a
stringso full uint64 values round-trip without JSON number precision loss. SseEvent.Datais exposed asstring(notbyte[]): the server always writes UTF-8 JSON data lines; decode at use if you need bytes.- Cached results rebuild with
Parse(string json).TranscriptionResult,TranscriptionDetailsResult,AlignmentResult,TaskResultandSpeechJsonResulteach expose a publicParsethat rebuilds the typed result from a stored response body, so a sidecar cache keyed by the analyzed audio needs no private JSON parsing:AlignmentResult result = AlignmentResult.Parse(File.ReadAllText(sidecarPath));. Base64 audio, named outputs and artifacts decode as on the live path. Model load/unload results have noParse— they describe server state, which a cache cannot answer.
examples/AudioCpp.Demo proves each behavior against a live server:
dotnet run --project examples/AudioCpp.Demo -- health http://127.0.0.1:8080
dotnet run --project examples/AudioCpp.Demo -- speech http://127.0.0.1:8080 kokoro "Hello from audio.cpp" --out /tmp/speech.wav
dotnet run --project examples/AudioCpp.Demo -- speech-stream http://127.0.0.1:8080 kokoro "Hello from audio.cpp" --out /tmp/speech.pcm # SSE; model must be configured mode=streaming
dotnet run --project examples/AudioCpp.Demo -- transcribe http://127.0.0.1:8080 parakeet /tmp/speech.wav --multipart
Also available: models, voices, load, unload, recipes, transcribe-details, and the
error-shape probes (speech-error, speech-stream-error, tasks-run-error, recipe-run-error).
Run the built dll directly (dotnet examples/AudioCpp.Demo/bin/Debug/net10.0/AudioCpp.Demo.dll …)
when passing flags, since dotnet run option parsing can eat app arguments.
dotnet build AudioCpp.slnx
dotnet test tests/AudioCpp.Tests # 75 tests, fake handler, no network
dotnet pack src/AudioCpp -c Release # produces Mabs.AudioCppClient.0.0.3.nupkg