Skip to main content

🎯 Lokutor C# SDK

The Lokutor C# SDK handles the integration of Lokutor TTS for .NET applications, including support for streaming and asynchronous execution.

Installation

Using the .NET CLI:
dotnet add package Lokutor
Using Package Manager:
Install-Package Lokutor

Initialization

using Lokutor;

// Initialize with API key
var client = new LokutorClient("YOUR_API_KEY");

// Or with custom configuration
var client = new LokutorClient(new LokutorClientOptions
{
    ApiKey = "YOUR_API_KEY",
    BaseUrl = "https://api.lokutor.ai",
    Timeout = TimeSpan.FromSeconds(30)
});

🎙️ Text-to-Speech (TTS)

1. Simple Synthesis

Returns a TtsResponse object with audio data and metadata.
var response = await client.SynthesizeAsync(
    text: "Hello from C#!",
    voiceId: "F1",
    quality: "medium",
    speed: 1.0f,
    outputFormat: "mp3_22050",
    includeVisemes: false,
    language: "en"
);

Console.WriteLine($"Audio length: {response.AudioBase64.Length}");
Console.WriteLine($"Duration: {response.Duration}s");
Console.WriteLine($"Sample rate: {response.SampleRate}Hz");

// Decode and save audio
var audioBytes = Convert.FromBase64String(response.AudioBase64);
await File.WriteAllBytesAsync("output.mp3", audioBytes);
Parameters:
  • text (required): Text to synthesize (1-50,000 characters)
  • voiceId (required): Voice ID - M1, M2, F1, F2
  • quality: ultra_fast, fast, medium, high, ultra_high (default: medium)
  • speed: Speech speed multiplier, 0.5-2.0 (default: 1.05)
  • outputFormat: pcm_22050, mp3_22050, ulaw_8000 (default: pcm_22050)
  • includeVisemes: Include viseme timing data (default: false)
  • language: en or es (default: en)

2. Streaming Audio

Returns a System.IO.Stream, suitable for real-time playback or writing to disk without buffering the entire file. Note: Requires voiceId as a parameter.
using (var stream = await client.StreamAsync(
    voiceId: "M1",
    text: "This is a streaming example.",
    quality: "ultra_fast",
    speed: 1.0f,
    outputFormat: "pcm_22050"
))
using (var fileStream = File.Create("output.pcm"))
{
    await stream.CopyToAsync(fileStream);
}

// Or process chunks in real-time
using (var stream = await client.StreamAsync("F1", "Streaming audio..."))
{
    var buffer = new byte[4096];
    int bytesRead;
    while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
    {
        // Process audio chunk
        ProcessAudioChunk(buffer, bytesRead);
    }
}
Note: The streaming endpoint requires voiceId as a parameter because it’s part of the API path: POST /api/tts/{voice_id}/stream

3. Async Long-Form Jobs

For very long texts, use async synthesis jobs.
// Start async job
var task = await client.CreateAsyncJobAsync(
    voiceId: "M1",
    text: "This is a very long text that will be processed asynchronously...",
    quality: "high",
    outputFormat: "mp3_22050"
);

Console.WriteLine($"Task ID: {task.TaskId}");
Console.WriteLine($"Status: {task.Status}");

// Poll for completion
while (true)
{
    var status = await client.GetTaskStatusAsync(task.TaskId);
    Console.WriteLine($"Status: {status.Status}, Progress: {status.Progress:P0}");
    
    if (status.Status == "completed")
    {
        Console.WriteLine($"Download URL: {status.DownloadUrl}");
        
        // Download the result
        var audioData = await client.DownloadTaskResultAsync(task.TaskId);
        await File.WriteAllBytesAsync("output.mp3", audioData);
        break;
    }
    else if (status.Status == "failed")
    {
        Console.WriteLine($"Error: {status.Error}");
        break;
    }
    
    await Task.Delay(1000);
}

// Cancel a task if needed
await client.CancelTaskAsync(task.TaskId);
Note: The async endpoint requires voiceId as a parameter: POST /api/tts/{voice_id}/async

🎭 Voices & Models

// Get Voices
var voices = await client.GetVoicesAsync();
foreach (var voice in voices)
{
    Console.WriteLine($"{voice.Name} ({voice.VoiceId})");
    Console.WriteLine($"  Category: {voice.Category}");
    Console.WriteLine($"  Description: {voice.Description}");
}

// Get Models
var models = await client.GetModelsAsync();
foreach (var model in models)
{
    Console.WriteLine($"{model.Name} ({model.ModelId})");
    Console.WriteLine($"  Languages: {string.Join(", ", model.SupportedLanguages)}");
}
Available Voices:
  • M1 - Male voice 1
  • M2 - Male voice 2
  • F1 - Female voice 1
  • F2 - Female voice 2
Supported Languages:
  • en - English
  • es - Spanish

📊 Error Handling

The SDK throws a LokutorApiException for any non-successful response from the Lokutor API.
using Lokutor.Exceptions;

try 
{
    var response = await client.SynthesizeAsync(
        text: "Hello",
        voiceId: "M1",
        quality: "high"
    );
}
catch (LokutorApiException ex)
{
    Console.WriteLine($"Status Code: {ex.StatusCode}");
    Console.WriteLine($"Error: {ex.Message}");
    
    if (ex.StatusCode == 401)
    {
        Console.WriteLine("Authentication failed - check your API key");
    }
    else if (ex.StatusCode == 429)
    {
        Console.WriteLine("Rate limit exceeded - please wait");
    }
    else if (ex.StatusCode == 400)
    {
        Console.WriteLine("Invalid request parameters");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Unexpected error: {ex.Message}");
}

🏗️ Models

Voice

public class Voice
{
    public string VoiceId { get; set; }
    public string Name { get; set; }
    public string Category { get; set; }
    public Dictionary<string, string> Labels { get; set; }
    public string Description { get; set; }
    public string PreviewUrl { get; set; }
}

TtsResponse

public class TtsResponse
{
    public string AudioBase64 { get; set; }
    public double Duration { get; set; }
    public int SampleRate { get; set; }
    public string Format { get; set; }
    public List<Viseme> Visemes { get; set; }
}

Viseme

public class Viseme
{
    public int Id { get; set; }          // Azure standard index (0-21)
    public int OffsetMs { get; set; }    // Offset from start of audio chunk
}

TaskResponse

public class TaskResponse
{
    public string TaskId { get; set; }
    public string Status { get; set; }
    public double EstimatedDuration { get; set; }
    public string Message { get; set; }
}

TaskStatusResponse

public class TaskStatusResponse
{
    public string TaskId { get; set; }
    public string Status { get; set; }  // "pending", "processing", "completed", "failed"
    public double Progress { get; set; }  // 0.0 to 1.0
    public double CreatedAt { get; set; }
    public double? CompletedAt { get; set; }
    public Dictionary<string, object> Result { get; set; }
    public string Error { get; set; }
    public string DownloadUrl { get; set; }
}

Model

public class Model
{
    public string ModelId { get; set; }
    public string Name { get; set; }
    public bool CanDoTextToSpeech { get; set; }
    public bool CanDoVoiceConversion { get; set; }
    public List<string> SupportedLanguages { get; set; }
    public string Description { get; set; }
}

Complete Example

using System;
using System.IO;
using System.Threading.Tasks;
using Lokutor;
using Lokutor.Exceptions;

namespace LokutorExample
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // Initialize client
            var client = new LokutorClient("YOUR_API_KEY");
            
            try
            {
                // Synthesize speech
                var response = await client.SynthesizeAsync(
                    text: "Welcome to Lokutor! This is a high-quality text-to-speech system.",
                    voiceId: "F1",
                    quality: "high",
                    speed: 1.0f,
                    outputFormat: "mp3_22050",
                    includeVisemes: true,
                    language: "en"
                );
                
                // Decode and save audio
                var audioBytes = Convert.FromBase64String(response.AudioBase64);
                await File.WriteAllBytesAsync("output.mp3", audioBytes);
                
                Console.WriteLine($"✅ Audio saved! Duration: {response.Duration:F2}s");
                
                // Print visemes if available
                if (response.Visemes != null && response.Visemes.Count > 0)
                {
                    Console.WriteLine($"📊 Visemes: {response.Visemes.Count} frames");
                    for (int i = 0; i < Math.Min(5, response.Visemes.Count); i++)
                    {
                        var viseme = response.Visemes[i];
                        Console.WriteLine($"  - ID: {viseme.Id}, Offset: {viseme.OffsetMs}ms");
                    }
                }
            }
            catch (LokutorApiException ex)
            {
                Console.WriteLine($"❌ API Error: {ex.Message} (Status: {ex.StatusCode})");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"❌ Error: {ex.Message}");
            }
        }
    }
}

Dependency Injection

The SDK supports dependency injection for ASP.NET Core applications:
// Startup.cs or Program.cs
services.AddSingleton<ILokutorClient>(sp => 
    new LokutorClient(Configuration["Lokutor:ApiKey"])
);

// Controller
public class SpeechController : ControllerBase
{
    private readonly ILokutorClient _lokutor;
    
    public SpeechController(ILokutorClient lokutor)
    {
        _lokutor = lokutor;
    }
    
    [HttpPost("synthesize")]
    public async Task<IActionResult> Synthesize([FromBody] SynthesizeRequest request)
    {
        var response = await _lokutor.SynthesizeAsync(
            text: request.Text,
            voiceId: request.VoiceId,
            quality: request.Quality
        );
        
        return Ok(response);
    }
}