Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/scripts/test-dot-net.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ set -ex

cd dotnet-examples/

cd ./vad-non-streaming-funasr-nano
cd ./pocket-tts-zero-shot
./run.sh
ls -lh
rm -rf sherpa-onnx-pocket-*

cd ../vad-non-streaming-funasr-nano
./run-ten-vad.sh
rm -fv *.onnx

Expand Down
4 changes: 2 additions & 2 deletions dotnet-examples/kitten-tts-play/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ static void Main(string[] args)
// https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/blockingcollection-overview
var dataItems = new BlockingCollection<float[]>();

var MyCallback = (IntPtr samples, int n, float progress) =>
var myCallback = (IntPtr samples, int n, float progress) =>
{
Console.WriteLine($"Progress {progress*100}%");

Expand Down Expand Up @@ -166,7 +166,7 @@ IntPtr userData

stream.Start();

var callback = new OfflineTtsCallbackProgress(MyCallback);
var callback = new OfflineTtsCallbackProgress(myCallback);

var audio = tts.GenerateWithCallbackProgress(text, speed, sid, callback);
var outputFilename = "./generated-kitten-0.wav";
Expand Down
2 changes: 1 addition & 1 deletion dotnet-examples/kitten-tts/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ static void TestEn()
float[] data = new float[n];
Marshal.Copy(samples, data, 0, n);
// You can process samples here, e.g., play them.
// See ../kitten-tts-playback for how to play them
// See ../kitten-tts-play for how to play them
Console.WriteLine($"Progress {progress*100}%");

// 1 means to keep generating
Expand Down
4 changes: 2 additions & 2 deletions dotnet-examples/offline-tts-play/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ private static void Run(Options options)
// https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/blockingcollection-overview
var dataItems = new BlockingCollection<float[]>();

var MyCallback = (IntPtr samples, int n) =>
var myCallback = (IntPtr samples, int n) =>
{
float[] data = new float[n];

Expand Down Expand Up @@ -314,7 +314,7 @@ IntPtr userData

stream.Start();

var callback = new OfflineTtsCallback(MyCallback);
var callback = new OfflineTtsCallback(myCallback);

var audio = tts.GenerateWithCallback(options.Text, speed, sid, callback);
var ok = audio.SaveToWaveFile(options.OutputFilename);
Expand Down
205 changes: 205 additions & 0 deletions dotnet-examples/pocket-tts-zero-shot-play/Program.cs
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.";
Comment on lines +48 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For multi-line strings, using a verbatim string literal (@"...") is cleaner and more readable than concatenating multiple string literals with +.

    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[]>();

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disposable 'BlockingCollection<Single[]>' is created but not disposed.

Suggested change
var dataItems = new BlockingCollection<float[]>();
var dataItems = new BlockingCollection<float[]>();
AppDomain.CurrentDomain.ProcessExit += (_, __) => dataItems.Dispose();

Copilot uses AI. Check for mistakes.

var myCallback = (IntPtr samples, int n, float progress, IntPtr arg) =>
{
Console.WriteLine($"Progress {progress*100}%");

float[] data = new float[n];

Marshal.Copy(samples, data, 0, n);

dataItems.Add(data);

// 1 means to keep generating
// 0 means to stop generating
return 1;

};


var playFinished = false;

float[]? lastSampleArray = null;
int lastIndex = 0; // not played

PortAudioSharp.Stream.Callback playCallback = (IntPtr input, IntPtr output,
UInt32 frameCount,
ref StreamCallbackTimeInfo timeInfo,
StreamCallbackFlags statusFlags,
IntPtr userData
) =>
{
if (dataItems.IsCompleted && lastSampleArray == null && lastIndex == 0)
{
Console.WriteLine($"Finished playing");
playFinished = true;
return StreamCallbackResult.Complete;
}
Comment on lines +104 to +121

Copilot AI Feb 9, 2026

Copy link

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.

Copilot uses AI. Check for mistakes.

int expected = Convert.ToInt32(frameCount);
int i = 0;

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;
}
Comment on lines +130 to +154

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This block of code has a critical bug and a performance issue.

  1. 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.
  2. Performance: Using LINQ methods like Skip(), Take(), and ToArray() 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;
        }


if (dataItems.Count != 0)
{
lastSampleArray = dataItems.Take();
lastIndex = 0;
}
Comment on lines +126 to +160

Copilot AI Feb 9, 2026

Copy link

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.

Suggested change
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 uses AI. Check for mistakes.
}

if (i < expected)
{
int sizeInBytes = (expected - i) * 4;
Marshal.Copy(new byte[sizeInBytes], 0, IntPtr.Add(output, i * sizeof(float)), sizeInBytes);
}

return StreamCallbackResult.Continue;
};

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
Comment on lines +172 to +201

Copilot AI Feb 9, 2026

Copy link

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.

Suggested change
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
}

Copilot uses AI. Check for mistakes.
}
Comment on lines +199 to +202

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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:

  1. Replace var playFinished = false; with using var finishedEvent = new System.Threading.ManualResetEvent(false);.
  2. In the playCallback, replace playFinished = true; with finishedEvent.Set();.
  3. Replace this while loop with a single call: finishedEvent.WaitOne();.

}
}

Comment on lines +172 to +205

Copilot AI Feb 9, 2026

Copy link

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.

Suggested change
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
}
}
}
}

Copilot uses AI. Check for mistakes.
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="*" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a wildcard * for package versions is generally discouraged as it can lead to unexpected build breaks when a new, potentially breaking, version of the package is released. It is safer to pin to a specific version or use a controlled version range (e.g., [2.0.0, 3.0.0)).

</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
</ItemGroup>

</Project>
10 changes: 10 additions & 0 deletions dotnet-examples/pocket-tts-zero-shot-play/run.sh
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
82 changes: 82 additions & 0 deletions dotnet-examples/pocket-tts-zero-shot/Program.cs
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For multi-line strings, using a verbatim string literal (@"...") is cleaner and more readable than concatenating multiple string literals with +.

    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) =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to C# naming conventions, local variable names should use camelCase. Please rename MyCallback to myCallback.

    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;
};
Comment on lines +52 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Naming inconsistency: MyCallback should be myCallback.

This PR specifically renames MyCallback → myCallback in both offline-tts-play/Program.cs and kitten-tts-play/Program.cs to follow C# camelCase convention for local variables. This new file uses MyCallback, which is inconsistent with those changes.

Additionally, lines 54–55 allocate and populate float[] data but never use it—this is dead code that wastes allocations on every callback invocation. If the intent is to demonstrate where sample processing would happen, a comment alone would suffice.

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
In `@dotnet-examples/pocket-tts-zero-shot/Program.cs` around lines 52 - 63, Rename
the local callback variable MyCallback to myCallback to match camelCase
convention used in other examples and update any references to it; inside the
callback (the lambda assigned to myCallback) remove the unused allocation and
Marshal.Copy of float[] data (or replace it with a single-line comment like "//
process samples here (e.g., Marshal.Copy into a buffer and play)"), leaving the
Console.WriteLine and the return 1 intact so the callback still reports progress
and signals continue.


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}");
}
}
}

15 changes: 15 additions & 0 deletions dotnet-examples/pocket-tts-zero-shot/pocket-tts-zero-shot.csproj
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>
Loading