Repository navigation
Add C# API for PocketTTS. #3162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5faa6a9
4bcae11
f554e77
0b8535f
fbd1686
b7ccda8
2b011d9
23c5785
4b0b0e4
2ee19b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,205 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Copyright (c) 2026 Xiaomi Corporation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This file shows how to use a non-streaming PocketTTS model | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // for text-to-speech | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Please refer to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // https://k2-fsa.github.io/sherpa/onnx/tts/pocket.html | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // to download pre-trained models | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using PortAudioSharp; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using SherpaOnnx; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Collections.Concurrent; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Runtime.InteropServices; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class PocketTtsDemo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static void Main(string[] args) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TestEn(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static void TestEn() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var config = new OfflineTtsConfig(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.Model.Pocket.LmFlow = "./sherpa-onnx-pocket-tts-int8-2026-01-26/lm_flow.int8.onnx"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.Model.Pocket.LmMain = "./sherpa-onnx-pocket-tts-int8-2026-01-26/lm_main.int8.onnx"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.Model.Pocket.Encoder = "./sherpa-onnx-pocket-tts-int8-2026-01-26/encoder.onnx"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.Model.Pocket.Decoder = "./sherpa-onnx-pocket-tts-int8-2026-01-26/decoder.int8.onnx"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.Model.Pocket.TextConditioner = "./sherpa-onnx-pocket-tts-int8-2026-01-26/text_conditioner.onnx"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.Model.Pocket.VocabJson = "./sherpa-onnx-pocket-tts-int8-2026-01-26/vocab.json"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.Model.Pocket.TokenScoresJson = "./sherpa-onnx-pocket-tts-int8-2026-01-26/token_scores.json"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.Model.NumThreads = 2; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.Model.Debug = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.Model.Provider = "cpu"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OfflineTtsGenerationConfig genConfig = new OfflineTtsGenerationConfig(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var referenceWaveFilename = "./sherpa-onnx-pocket-tts-int8-2026-01-26/test_wavs/bria.wav"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var reader = new WaveReader(referenceWaveFilename); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| genConfig.ReferenceAudio = reader.Samples; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| genConfig.ReferenceSampleRate= reader.SampleRate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| genConfig.Extra["max_reference_audio_len"] = 12; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var tts = new OfflineTts(config); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var text = "Today as always, men fall into two groups: slaves and free men. Whoever " + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "does not have two-thirds of his day for himself, is a slave, whatever " + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "he may be: a statesman, a businessman, an official, or a scholar. " + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Friends fell out often because life was changing so fast. The easiest " + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "thing in the world was to lose touch with someone."; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Console.WriteLine(PortAudio.VersionInfo.versionText); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PortAudio.Initialize(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Console.WriteLine($"Number of devices: {PortAudio.DeviceCount}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (int i = 0; i != PortAudio.DeviceCount; ++i) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Console.WriteLine($" Device {i}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DeviceInfo deviceInfo = PortAudio.GetDeviceInfo(i); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Console.WriteLine($" Name: {deviceInfo.name}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Console.WriteLine($" Max output channels: {deviceInfo.maxOutputChannels}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Console.WriteLine($" Default sample rate: {deviceInfo.defaultSampleRate}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int deviceIndex = PortAudio.DefaultOutputDevice; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (deviceIndex == PortAudio.NoDevice) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Console.WriteLine("No default output device found. Please use ../offline-tts instead"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Environment.Exit(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var info = PortAudio.GetDeviceInfo(deviceIndex); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Console.WriteLine(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Console.WriteLine($"Use output default device {deviceIndex} ({info.name})"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var param = new StreamParameters(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| param.device = deviceIndex; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| param.channelCount = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| param.sampleFormat = SampleFormat.Float32; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| param.suggestedLatency = info.defaultLowOutputLatency; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| param.hostApiSpecificStreamInfo = IntPtr.Zero; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/blockingcollection-overview | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var dataItems = new BlockingCollection<float[]>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var dataItems = new BlockingCollection<float[]>(); | |
| var dataItems = new BlockingCollection<float[]>(); | |
| AppDomain.CurrentDomain.ProcessExit += (_, __) => dataItems.Dispose(); |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
playFinished is written from the PortAudio callback thread and read from the main thread without any synchronization. This is a data race and the main thread may never observe the update. Use Volatile.Write/Read, Interlocked.Exchange, or a ManualResetEventSlim/TaskCompletionSource to signal completion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block of code has a critical bug and a performance issue.
- Bug: The
return StreamCallbackResult.Continue;on line 144 causes the callback to exit prematurely after filling only part of the audio buffer. This will result in audio glitches, as the rest of the buffer is not filled. - Performance: Using LINQ methods like
Skip(),Take(), andToArray()inside an audio callback (lines 135 and 147) is inefficient due to repeated memory allocations in a performance-sensitive context.
The suggested code below fixes the bug and improves performance by using Marshal.Copy with an offset, avoiding intermediate array allocations.
if (lastSampleArray != null)
{
int remaining = lastSampleArray.Length - lastIndex;
int toCopy = Math.Min(needed, remaining);
Marshal.Copy(lastSampleArray, lastIndex, IntPtr.Add(output, i * sizeof(float)), toCopy);
lastIndex += toCopy;
i += toCopy;
if (lastIndex >= lastSampleArray.Length)
{
lastSampleArray = null;
lastIndex = 0;
}
continue;
}
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PortAudio callback uses dataItems.Count followed by dataItems.Take() inside the real-time audio thread. This has a race where Take() can block if the collection becomes empty after the Count check, which can glitch/hang audio. Use TryTake(out item, 0) (or a lock-free ring buffer) and never block in the audio callback.
| while ((lastSampleArray != null || dataItems.Count != 0) && (i < expected)) | |
| { | |
| int needed = expected - i; | |
| if (lastSampleArray != null) | |
| { | |
| int remaining = lastSampleArray.Length - lastIndex; | |
| if (remaining >= needed) | |
| { | |
| float[] this_block = lastSampleArray.Skip(lastIndex).Take(needed).ToArray(); | |
| lastIndex += needed; | |
| if (lastIndex == lastSampleArray.Length) | |
| { | |
| lastSampleArray = null; | |
| lastIndex = 0; | |
| } | |
| Marshal.Copy(this_block, 0, IntPtr.Add(output, i * sizeof(float)), needed); | |
| return StreamCallbackResult.Continue; | |
| } | |
| float[] this_block2 = lastSampleArray.Skip(lastIndex).Take(remaining).ToArray(); | |
| lastIndex = 0; | |
| lastSampleArray = null; | |
| Marshal.Copy(this_block2, 0, IntPtr.Add(output, i * sizeof(float)), remaining); | |
| i += remaining; | |
| continue; | |
| } | |
| if (dataItems.Count != 0) | |
| { | |
| lastSampleArray = dataItems.Take(); | |
| lastIndex = 0; | |
| } | |
| while (i < expected) | |
| { | |
| if (lastSampleArray == null) | |
| { | |
| if (!dataItems.TryTake(out lastSampleArray, 0)) | |
| { | |
| // No more data available right now; remaining samples will be zero-filled below. | |
| break; | |
| } | |
| lastIndex = 0; | |
| } | |
| int needed = expected - i; | |
| int remaining = lastSampleArray.Length - lastIndex; | |
| if (remaining >= needed) | |
| { | |
| float[] this_block = lastSampleArray.Skip(lastIndex).Take(needed).ToArray(); | |
| lastIndex += needed; | |
| if (lastIndex == lastSampleArray.Length) | |
| { | |
| lastSampleArray = null; | |
| lastIndex = 0; | |
| } | |
| Marshal.Copy(this_block, 0, IntPtr.Add(output, i * sizeof(float)), needed); | |
| return StreamCallbackResult.Continue; | |
| } | |
| float[] this_block2 = lastSampleArray.Skip(lastIndex).Take(remaining).ToArray(); | |
| lastIndex = 0; | |
| lastSampleArray = null; | |
| Marshal.Copy(this_block2, 0, IntPtr.Add(output, i * sizeof(float)), remaining); | |
| i += remaining; |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disposable 'Stream' is created but not disposed.
| PortAudioSharp.Stream stream = new PortAudioSharp.Stream(inParams: null, outParams: param, sampleRate: tts.SampleRate, | |
| framesPerBuffer: 0, | |
| streamFlags: StreamFlags.ClipOff, | |
| callback: playCallback, | |
| userData: IntPtr.Zero | |
| ); | |
| stream.Start(); | |
| var callback = new OfflineTtsCallbackProgressWithArg(myCallback); | |
| var audio = tts.GenerateWithConfig(text, genConfig, callback); | |
| var outputFilename = "./generated-pocket-en-play.wav"; | |
| var ok = audio.SaveToWaveFile(outputFilename); | |
| if (ok) | |
| { | |
| Console.WriteLine($"Wrote to {outputFilename} succeeded!"); | |
| } | |
| else | |
| { | |
| Console.WriteLine($"Failed to write {outputFilename}"); | |
| } | |
| dataItems.CompleteAdding(); | |
| while (!playFinished) | |
| { | |
| Thread.Sleep(100); // 100ms | |
| using (PortAudioSharp.Stream stream = new PortAudioSharp.Stream(inParams: null, outParams: param, sampleRate: tts.SampleRate, | |
| framesPerBuffer: 0, | |
| streamFlags: StreamFlags.ClipOff, | |
| callback: playCallback, | |
| userData: IntPtr.Zero | |
| )) | |
| { | |
| stream.Start(); | |
| var callback = new OfflineTtsCallbackProgressWithArg(myCallback); | |
| var audio = tts.GenerateWithConfig(text, genConfig, callback); | |
| var outputFilename = "./generated-pocket-en-play.wav"; | |
| var ok = audio.SaveToWaveFile(outputFilename); | |
| if (ok) | |
| { | |
| Console.WriteLine($"Wrote to {outputFilename} succeeded!"); | |
| } | |
| else | |
| { | |
| Console.WriteLine($"Failed to write {outputFilename}"); | |
| } | |
| dataItems.CompleteAdding(); | |
| while (!playFinished) | |
| { | |
| Thread.Sleep(100); // 100ms | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This while loop with Thread.Sleep is a form of busy-waiting, which is inefficient as it consumes CPU cycles unnecessarily. A better approach is to use a synchronization primitive like System.Threading.ManualResetEvent to wait for the playback to finish.
To implement this:
- Replace
var playFinished = false;withusing var finishedEvent = new System.Threading.ManualResetEvent(false);. - In the
playCallback, replaceplayFinished = true;withfinishedEvent.Set();. - Replace this
whileloop with a single call:finishedEvent.WaitOne();.
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disposable 'Stream' is created but not disposed.
| PortAudioSharp.Stream stream = new PortAudioSharp.Stream(inParams: null, outParams: param, sampleRate: tts.SampleRate, | |
| framesPerBuffer: 0, | |
| streamFlags: StreamFlags.ClipOff, | |
| callback: playCallback, | |
| userData: IntPtr.Zero | |
| ); | |
| stream.Start(); | |
| var callback = new OfflineTtsCallbackProgressWithArg(MyCallback); | |
| var audio = tts.GenerateWithConfig(text, genConfig, callback); | |
| var outputFilename = "./generated-pocket-en-paly.wav"; | |
| var ok = audio.SaveToWaveFile(outputFilename); | |
| if (ok) | |
| { | |
| Console.WriteLine($"Wrote to {outputFilename} succeeded!"); | |
| } | |
| else | |
| { | |
| Console.WriteLine($"Failed to write {outputFilename}"); | |
| } | |
| dataItems.CompleteAdding(); | |
| while (!playFinished) | |
| { | |
| Thread.Sleep(100); // 100ms | |
| } | |
| } | |
| } | |
| using (PortAudioSharp.Stream stream = new PortAudioSharp.Stream(inParams: null, outParams: param, sampleRate: tts.SampleRate, | |
| framesPerBuffer: 0, | |
| streamFlags: StreamFlags.ClipOff, | |
| callback: playCallback, | |
| userData: IntPtr.Zero | |
| )) | |
| { | |
| stream.Start(); | |
| var callback = new OfflineTtsCallbackProgressWithArg(MyCallback); | |
| var audio = tts.GenerateWithConfig(text, genConfig, callback); | |
| var outputFilename = "./generated-pocket-en-paly.wav"; | |
| var ok = audio.SaveToWaveFile(outputFilename); | |
| if (ok) | |
| { | |
| Console.WriteLine($"Wrote to {outputFilename} succeeded!"); | |
| } | |
| else | |
| { | |
| Console.WriteLine($"Failed to write {outputFilename}"); | |
| } | |
| dataItems.CompleteAdding(); | |
| while (!playFinished) | |
| { | |
| Thread.Sleep(100); // 100ms | |
| } | |
| } | |
| } | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net8.0</TargetFramework> | ||
| <RootNamespace>pocket_tts_zero_shot_play</RootNamespace> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="PortAudioSharp2" Version="*" /> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\Common\Common.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| #!/usr/bin/env bash | ||
| set -ex | ||
|
|
||
| if [ ! -f ./sherpa-onnx-pocket-tts-int8-2026-01-26/encoder.onnx ]; then | ||
| curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/sherpa-onnx-pocket-tts-int8-2026-01-26.tar.bz2 | ||
| tar xvf sherpa-onnx-pocket-tts-int8-2026-01-26.tar.bz2 | ||
| rm sherpa-onnx-pocket-tts-int8-2026-01-26.tar.bz2 | ||
| fi | ||
|
|
||
| dotnet run |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| // Copyright (c) 2026 Xiaomi Corporation | ||
| // | ||
| // This file shows how to use a non-streaming PocketTTS model | ||
| // for text-to-speech | ||
| // Please refer to | ||
| // https://k2-fsa.github.io/sherpa/onnx/tts/pocket.html | ||
| // and | ||
| // https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models | ||
| // to download pre-trained models | ||
| using SherpaOnnx; | ||
| using System.Runtime.InteropServices; | ||
|
|
||
| class PocketTtsDemo | ||
| { | ||
| static void Main(string[] args) | ||
| { | ||
|
|
||
| TestEn(); | ||
| } | ||
|
|
||
| static void TestEn() | ||
| { | ||
| var config = new OfflineTtsConfig(); | ||
| config.Model.Pocket.LmFlow = "./sherpa-onnx-pocket-tts-int8-2026-01-26/lm_flow.int8.onnx"; | ||
| config.Model.Pocket.LmMain = "./sherpa-onnx-pocket-tts-int8-2026-01-26/lm_main.int8.onnx"; | ||
| config.Model.Pocket.Encoder = "./sherpa-onnx-pocket-tts-int8-2026-01-26/encoder.onnx"; | ||
| config.Model.Pocket.Decoder = "./sherpa-onnx-pocket-tts-int8-2026-01-26/decoder.int8.onnx"; | ||
| config.Model.Pocket.TextConditioner = "./sherpa-onnx-pocket-tts-int8-2026-01-26/text_conditioner.onnx"; | ||
| config.Model.Pocket.VocabJson = "./sherpa-onnx-pocket-tts-int8-2026-01-26/vocab.json"; | ||
| config.Model.Pocket.TokenScoresJson = "./sherpa-onnx-pocket-tts-int8-2026-01-26/token_scores.json"; | ||
|
|
||
| config.Model.NumThreads = 2; | ||
| config.Model.Debug = 1; | ||
| config.Model.Provider = "cpu"; | ||
|
|
||
| OfflineTtsGenerationConfig genConfig = new OfflineTtsGenerationConfig(); | ||
|
|
||
| var referenceWaveFilename = "./sherpa-onnx-pocket-tts-int8-2026-01-26/test_wavs/bria.wav"; | ||
| var reader = new WaveReader(referenceWaveFilename); | ||
|
|
||
| genConfig.ReferenceAudio = reader.Samples; | ||
| genConfig.ReferenceSampleRate = reader.SampleRate; | ||
| genConfig.Extra["max_reference_audio_len"] = 12; | ||
|
|
||
| var tts = new OfflineTts(config); | ||
| var text = "Today as always, men fall into two groups: slaves and free men. Whoever " + | ||
| "does not have two-thirds of his day for himself, is a slave, whatever " + | ||
| "he may be: a statesman, a businessman, an official, or a scholar. " + | ||
| "Friends fell out often because life was changing so fast. The easiest " + | ||
| "thing in the world was to lose touch with someone."; | ||
|
Comment on lines
+46
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For multi-line strings, using a verbatim string literal ( var text = @"Today as always, men fall into two groups: slaves and free men. Whoever does not have two-thirds of his day for himself, is a slave, whatever he may be: a statesman, a businessman, an official, or a scholar. Friends fell out often because life was changing so fast. The easiest thing in the world was to lose touch with someone."; |
||
|
|
||
| var MyCallback = (IntPtr samples, int n, float progress, IntPtr arg) => | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| { | ||
| float[] data = new float[n]; | ||
| Marshal.Copy(samples, data, 0, n); | ||
| // You can process samples here, e.g., play them. | ||
| // See ../pocket-tts-zero-shot-play for how to play them | ||
| Console.WriteLine($"Progress {progress*100}%"); | ||
|
|
||
| // 1 means to keep generating | ||
| // 0 means to stop generating | ||
| return 1; | ||
| }; | ||
|
Comment on lines
+52
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Naming inconsistency: This PR specifically renames Additionally, lines 54–55 allocate and populate Suggested fix- var MyCallback = (IntPtr samples, int n, float progress, IntPtr arg) =>
+ var myCallback = (IntPtr samples, int n, float progress, IntPtr arg) =>
{
- float[] data = new float[n];
- Marshal.Copy(samples, data, 0, n);
// You can process samples here, e.g., play them.
// See ../pocket-tts-zero-shot-play for how to play them
Console.WriteLine($"Progress {progress*100}%");
// 1 means to keep generating
// 0 means to stop generating
return 1;
};
- var callback = new OfflineTtsCallbackProgressWithArg(MyCallback);
+ var callback = new OfflineTtsCallbackProgressWithArg(myCallback);🤖 Prompt for AI Agents |
||
|
|
||
| var callback = new OfflineTtsCallbackProgressWithArg(MyCallback); | ||
|
|
||
| var audio = tts.GenerateWithConfig(text, genConfig, callback); | ||
|
|
||
| var outputFilename = "./generated-pocket-en.wav"; | ||
| var ok = audio.SaveToWaveFile(outputFilename); | ||
|
|
||
| if (ok) | ||
| { | ||
| Console.WriteLine($"Wrote to {outputFilename} succeeded!"); | ||
| } | ||
| else | ||
| { | ||
| Console.WriteLine($"Failed to write {outputFilename}"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net8.0</TargetFramework> | ||
| <RootNamespace>pocket_tts_zero_shot</RootNamespace> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\Common\Common.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For multi-line strings, using a verbatim string literal (
@"...") is cleaner and more readable than concatenating multiple string literals with+.