Skip to content

Add Pascal API examples for ZipVoice models - #3351

Merged
csukuangfj merged 2 commits into
k2-fsa:masterfrom
csukuangfj:pascal-api-zipvoice
Mar 18, 2026
Merged

csukuangfj merged 2 commits into
k2-fsa:masterfrom
csukuangfj:pascal-api-zipvoice

Conversation

@csukuangfj

@csukuangfj csukuangfj commented Mar 18, 2026 •

Copy link
Copy Markdown
Collaborator

https://github.com/k2-fsa/sherpa-onnx/actions/runs/23187281508/job/67373909595

6a008548aaca5f6d9c56b91aaa3e6882

From ChatGPT:

What you’re seeing is a classic cross-language floating-point exception policy mismatch—and your symptoms match it almost perfectly:

  • ✅ Works on Ubuntu ARM64
  • ❌ Fails on x64 (Linux, macOS, Windows)
  • ❌ Raises: EOverflow: Floating point overflow (Pascal exception)

🔍 What’s really happening

You are mixing:

  • C code → typically runs with masked FP exceptions (silent, returns Inf/NaN)
  • Pascal (Delphi / Free Pascal) → often runs with unmasked exceptions (raises EOverflow, EDivByZero, etc.)

👉 On x64 platforms, the floating-point unit (SSE/AVX via MXCSR register) is strictly used and shared globally.

👉 On ARM64:

  • FP exception trapping is usually disabled by default
  • So everything behaves like C (silent handling)

⚠️ Why it crashes only on x64

On x64:

  • Pascal runtime sets something like:

    SetExceptionMask([]); // unmask → raise exceptions
  • Your C code (or a library) does something like:

    double x = huge_value * huge_value; // overflow

    👉 In C mindset: OK → returns Inf
    👉 In Pascal mindset: ❌ raises EOverflow

💥 Result: crash in Pascal even though C thinks it’s fine


🧠 The deeper issue: shared FPU state

The floating-point control state (MXCSR on x86_64) includes:

  • Exception masks
  • Rounding modes
  • Status flags

👉 This state is:

  • Global (per thread)
  • Shared between C and Pascal

So:

If Pascal unmasks exceptions, your C code is now running under Pascal’s strict rules.


🔬 Why ARM64 behaves differently

On ARM64 (Ubuntu):

  • FP exceptions are almost always non-trapping
  • Overflow → Inf, no exception
  • So both C and Pascal behave “quietly”

👉 That’s why your code “works” there


💥 Typical triggers

You’ll see this with:

  • Large exponentials (exp, pow)
  • Multiplying large doubles
  • Invalid math (sqrt(-1) in real domain)
  • Division by very small numbers

✅ Fix strategies

🔧 Option 1: Make Pascal behave like C (most common fix)

Mask FP exceptions in Pascal:

uses Math;

SetExceptionMask([
  exInvalidOp,
  exDenormalized,
  exZeroDivide,
  exOverflow,
  exUnderflow,
  exPrecision
]);

👉 This makes Pascal:

  • Return Inf / NaN
  • NOT raise EOverflow

🔧 Option 2: Temporarily mask around C calls

If you want Pascal strictness elsewhere:

var
  oldMask: TExceptionMask;
begin
  oldMask := GetExceptionMask;
  SetExceptionMask([exAllArithmeticExceptions]);

  // call C code here

  SetExceptionMask(oldMask);
end;

🔧 Option 3: Fix on the C side (less reliable)

You can try to enforce masking in C:

#include <fenv.h>

fedisableexcept(FE_ALL_EXCEPT);

⚠️ Problem:

  • Pascal may override it later
  • Not portable across OS/compiler

🔧 Option 4: Detect and clamp values (robust approach)

Before risky operations:

if (x > MAX_SAFE_VALUE) x = MAX_SAFE_VALUE;

Or check results:

if (isinf(result) || isnan(result)) {
    // handle error
}

🧪 Debug tip

Enable exceptions deliberately to find the culprit:

SetExceptionMask([]); // unmask everything

👉 Then you’ll see exactly:

  • Which operation overflows
  • Where the mismatch starts

🧩 Key takeaway

This is not a “bug in your math”—it’s a policy conflict:

Platform Default Behavior
C (most systems) Masked (silent)
Pascal Often unmasked (strict)
ARM64 Masked (hardware default)
x64 Depends → often strict in Pascal

🧠 One-line summary

Your C code assumes “overflow is OK,” but Pascal on x64 says “overflow is an error” — and the shared FPU state makes Pascal win.


If you want, I can help you pinpoint the exact line causing the overflow (e.g., by instrumenting your code or analyzing a snippet).

Summary by CodeRabbit

  • New Features

    • Added ZipVoice Chinese/English zero-shot text-to-speech support
    • Added real-time progress reporting during TTS generation
  • Documentation

    • Updated README with new ZipVoice example documentation
  • Improvements

    • Enhanced memory management and stability in the API layer

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 18, 2026
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the Pascal API examples by integrating a new demonstration for ZipVoice Chinese/English zero-shot Text-to-Speech models. A key aspect of this change is the resolution of a complex cross-language compatibility issue concerning floating-point exception handling between C and Pascal on x64 systems, ensuring the TTS functionality operates robustly across different environments. Additionally, the request includes general improvements to the Pascal API's internal structure and memory management, alongside the introduction of progress reporting for TTS generation.

Highlights

  • New ZipVoice TTS Example: Introduced new Pascal example files and a shell script to demonstrate the use of ZipVoice Chinese/English zero-shot Text-to-Speech models.
  • Floating-Point Exception Fix: Implemented a critical fix for a cross-language floating-point exception policy mismatch between C and Pascal on x64 platforms by masking floating-point exceptions in the Pascal API wrapper.
  • API Refactoring and Improvements: Refactored the Generate methods within the Pascal TTS API to improve memory management, code structure, and ensure proper handling of reference audio data and string parameters.
  • Progress Callback Integration: Added progress callback functionality to the Pascal TTS examples, allowing for real-time feedback during speech generation.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/pascal.yaml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@csukuangfj
csukuangfj requested a review from Copilot March 18, 2026 09:50
@coderabbitai

coderabbitai Bot commented Mar 18, 2026 •

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR introduces ZipVoice Chinese/English text-to-speech support to the Pascal API binding. It includes refactored callback type signatures in the core Pascal wrapper, a new ZipVoice example application with progress reporting, an automated build/run script, and supporting documentation and workflow updates.

Changes

Cohort / File(s) Summary
Workflow & Configuration
.github/workflows/pascal.yaml, pascal-api-examples/tts/.gitignore
Added GitHub Actions workflow step to run and test ZipVoice TTS with cleanup. Added gitignore pattern for generated artifacts.
Documentation
pascal-api-examples/tts/README.md
Updated README table to reference new run-zipvoice-zh-en.sh example script.
Pascal API Core
sherpa-onnx/pascal-api/sherpa_onnx.pas
Removed pointer typedefs for callbacks; modified callback parameter types from pointer to function type. Introduced ExtractGeneratedAudio helper for audio post-processing. Enhanced memory management for ReferenceAudio in GenerationConfig flow with try/finally blocks. Added exception mask initialization for floating-point overflow handling. Updated PSherpaOnnxGenerationConfig typedef.
Pascal Example Programs
pascal-api-examples/tts/pocket-en.pas, pascal-api-examples/tts/zipvoice-zh-en.pas
Added ProgressCallback to pocket-en.pas and wired it to the Generate method. Created new zipvoice-zh-en.pas demonstrating offline TTS with ZipVoice models, including config setup, speaker info retrieval, and reference audio handling.
Build & Execution Script
pascal-api-examples/tts/run-zipvoice-zh-en.sh
New Bash script handling Sherpa-ONNX CMake build, model/vocoder downloads, Free Pascal compilation, and library path configuration for ZipVoice TTS execution.

Sequence Diagram

sequenceDiagram
    participant Client as Pascal Client
    participant ONNX as Sherpa ONNX<br/>(C Library)
    participant Models as Model Files
    participant Audio as Audio Output

    Client->>Models: Load tokens, encoder,<br/>decoder, vocoder
    Client->>ONNX: Create OfflineTtsConfig<br/>(with model paths)
    Client->>ONNX: Build OfflineTts instance
    
    Client->>Audio: Load reference audio
    Client->>ONNX: Prepare GenerationConfig<br/>(reference text/audio)
    
    Client->>ONNX: Call Generate with<br/>ProgressCallback
    
    loop During Generation
        ONNX->>Client: Invoke ProgressCallback<br/>(progress %)
        Client->>Client: Print progress
    end
    
    ONNX->>Client: Return Generated Audio
    Client->>Audio: Write WAV file
    Client->>ONNX: Free TTS instance
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

size:M

Poem

🐰 A pascal voice takes flight,
ZipVoice whispers through the night,
Progress callbacks dance and sing,
Chinese echoes, memories ring!
From tokens vast to audio sweet,
ONNX and Pascal now complete!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Pascal API examples for ZipVoice models' directly and clearly summarizes the main objective of the PR, which is to introduce new Pascal API examples for ZipVoice TTS models.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

The pull request introduces a new Pascal example for ZipVoice Chinese/English zero-shot TTS, including a new run-zipvoice-zh-en.sh script and a zipvoice-zh-en.pas program. It also updates the pocket-en.pas example to use a progress callback. Significant improvements were made to the Pascal API bindings (sherpa_onnx.pas) by adding an initialization block to set the floating-point exception mask, which fixes EOverflow errors and ensures consistent behavior with the C API. Memory management for ReferenceAudio was made more robust by using GetMem and FreeMem within a try..finally block to prevent memory leaks. Additionally, redundant explicit pointer type aliases for callback functions were removed, improving type correctness and clarity in the API definitions.

Comment on lines +3391 to +3396
initialization
{ Match the C API's default behavior. PocketTTS can raise FP overflow flags
during native inference on some platforms, and Free Pascal would otherwise
surface them as EOverflow.
See also https://github.com/k2-fsa/sherpa-onnx/pull/3351
}

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

Adding the initialization block to set the floating-point exception mask is a critical fix. As detailed in the PR description, this addresses the cross-language floating-point exception policy mismatch on x64 platforms, preventing EOverflow errors and ensuring consistent behavior with the C API. This significantly improves the stability of the Pascal bindings.

Comment on lines +2975 to +2999
ReferenceAudio := GenerationConfig.ReferenceAudio;
CReferenceAudio := nil;
C.ReferenceAudio := nil;
C.ReferenceAudioLen := Length(ReferenceAudio);
if C.ReferenceAudioLen > 0 then
begin
GetMem(CReferenceAudio, C.ReferenceAudioLen * SizeOf(Single));
Move(ReferenceAudio[0], CReferenceAudio[0], C.ReferenceAudioLen * SizeOf(Single));
C.ReferenceAudio := CReferenceAudio;
end;
C.ReferenceSampleRate:= GenerationConfig.ReferenceSampleRate;
C.ReferenceText := PAnsiChar(GenerationConfig.ReferenceText);
ReferenceText := GenerationConfig.ReferenceText;
C.ReferenceText := PAnsiChar(ReferenceText);
C.NumSteps := GenerationConfig.NumSteps;
C.Extra := PAnsiChar(GenerationConfig.Extra);

Result := Default(TSherpaOnnxGeneratedAudio);

Audio := SherpaOnnxOfflineTtsGenerateWithConfig(Self.Handle, PAnsiChar(Text),
@C, Callback, Arg);
Extra := GenerationConfig.Extra;
C.Extra := PAnsiChar(Extra);

if Audio = nil then
Exit;

SetLength(Result.Samples, Audio^.N);
Result.SampleRate := Audio^.SampleRate;

if Audio^.N > 0 then
Move(Audio^.Samples[0], Result.Samples[0], Audio^.N * SizeOf(Single));
Audio := nil;
try
Audio := SherpaOnnxOfflineTtsGenerateWithConfig(Self.Handle, PAnsiChar(Text),
@C, Callback, Arg);
finally
if CReferenceAudio <> nil then
FreeMem(CReferenceAudio);
end;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The handling of ReferenceAudio by allocating memory with GetMem and freeing it with FreeMem within a try..finally block is a robust improvement. This ensures proper memory management when converting Pascal's dynamic array (array of Single) to a C-compatible pointer (pcfloat) and prevents memory leaks, which is critical for stability.


PSherpaOnnxGeneratedAudioCallbackWithArg = ^TSherpaOnnxGeneratedAudioCallbackWithArg;

TSherpaOnnxGeneratedAudioCallbackWithArg = function(

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

The removal of the explicit pointer type alias PSherpaOnnxGeneratedAudioCallbackWithArg is a good correction. In Free Pascal, function types are already treated as pointers when used in parameter lists for external functions, making the explicit pointer alias redundant and potentially misleading.


PSherpaOnnxGeneratedAudioProgressCallbackWithArg = ^TSherpaOnnxGeneratedAudioProgressCallbackWithArg;

TSherpaOnnxGeneratedAudioProgressCallbackWithArg = function(

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

Similarly, removing the explicit pointer type alias PSherpaOnnxGeneratedAudioProgressCallbackWithArg is a correct and consistent change, aligning with Free Pascal's handling of function types.

function Generate(Text: AnsiString; SpeakerId: Integer;
Speed: Single;
Callback: PSherpaOnnxGeneratedAudioCallbackWithArg;
Callback: TSherpaOnnxGeneratedAudioCallbackWithArg;

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

Changing the Callback parameter type from PSherpaOnnxGeneratedAudioCallbackWithArg to TSherpaOnnxGeneratedAudioCallbackWithArg directly uses the function type, which is the correct approach for passing callback functions in Free Pascal to C-style APIs. This aligns with the removal of the redundant pointer type alias.

function Generate(Text: AnsiString;
GenerationConfig: TSherpaOnnxGenerationConfig;
Callback: PSherpaOnnxGeneratedAudioProgressCallbackWithArg;
Callback: TSherpaOnnxGeneratedAudioProgressCallbackWithArg;

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 change correctly updates the Callback parameter type to TSherpaOnnxGeneratedAudioProgressCallbackWithArg, ensuring consistency with Free Pascal's function pointer handling and the previous type alias removal.

implementation

uses
Math,

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

Adding Math to the uses clause is necessary to use SetExceptionMask, which is crucial for handling floating-point exceptions as described in the PR body. This ensures the required functionality is available.

end;

PSherpaOnnxGenerationConfig = ^TSherpaOnnxGenerationConfig;
PSherpaOnnxGenerationConfig = ^SherpaOnnxGenerationConfig;

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

Correcting the pointer type alias from ^TSherpaOnnxGenerationConfig to ^SherpaOnnxGenerationConfig is important for type correctness. SherpaOnnxGenerationConfig is the record type, and the pointer should refer to it directly.

function SherpaOnnxOfflineTtsGenerateWithCallbackWithArg(Tts: Pointer;
Text: PAnsiChar; Sid: cint32; Speed: cfloat;
Callback: PSherpaOnnxGeneratedAudioCallbackWithArg;
Callback: TSherpaOnnxGeneratedAudioCallbackWithArg;

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

Updating the Callback parameter type to TSherpaOnnxGeneratedAudioCallbackWithArg for the external function declaration ensures that the C API correctly receives the Pascal function pointer. This is consistent with the other callback type corrections.

function SherpaOnnxOfflineTtsGenerateWithConfig(Tts: Pointer;
Text: PAnsiChar; config: PSherpaOnnxGenerationConfig;
Callback: PSherpaOnnxGeneratedAudioProgressCallbackWithArg;
Callback: TSherpaOnnxGeneratedAudioProgressCallbackWithArg;

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 change correctly updates the Callback parameter type to TSherpaOnnxGeneratedAudioProgressCallbackWithArg for the external function, maintaining consistency across the API definitions.

@csukuangfj
csukuangfj merged commit 535ea52 into k2-fsa:master Mar 18, 2026
1 check passed
@csukuangfj
csukuangfj deleted the pascal-api-zipvoice branch March 18, 2026 09:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a Pascal ZipVoice (zh-en) TTS example and updates the Pascal bindings to better interoperate with the C API during inference (notably around floating-point exception handling).

Changes:

  • Add ZipVoice zh-en Pascal example + runner script and integrate it into CI.
  • Refactor OfflineTTS audio extraction and update callback typing in Pascal bindings.
  • Mask floating-point exceptions in the Pascal unit initialization to avoid EOverflow during native inference.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
sherpa-onnx/pascal-api/sherpa_onnx.pas Refactors TTS generation plumbing, adjusts callback types, copies reference audio for C interop, and masks FP exceptions at init.
pascal-api-examples/tts/zipvoice-zh-en.pas New ZipVoice Chinese/English zero-shot TTS example using progress callback and reference audio.
pascal-api-examples/tts/run-zipvoice-zh-en.sh Builds/links shared lib, downloads ZipVoice + vocoder assets, compiles and runs the new example.
pascal-api-examples/tts/pocket-en.pas Adds a progress callback and wires it into generation.
pascal-api-examples/tts/README.md Documents the new ZipVoice example runner.
pascal-api-examples/tts/.gitignore Ignores the new ZipVoice example binary.
.github/workflows/pascal.yaml Runs the new ZipVoice example in the Pascal workflow and cleans up artifacts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +2975 to +2984
ReferenceAudio := GenerationConfig.ReferenceAudio;
CReferenceAudio := nil;
C.ReferenceAudio := nil;
C.ReferenceAudioLen := Length(ReferenceAudio);
if C.ReferenceAudioLen > 0 then
begin
GetMem(CReferenceAudio, C.ReferenceAudioLen * SizeOf(Single));
Move(ReferenceAudio[0], CReferenceAudio[0], C.ReferenceAudioLen * SizeOf(Single));
C.ReferenceAudio := CReferenceAudio;
end;
Comment on lines +3391 to +3398
initialization
{ Match the C API's default behavior. PocketTTS can raise FP overflow flags
during native inference on some platforms, and Free Pascal would otherwise
surface them as EOverflow.
See also https://github.com/k2-fsa/sherpa-onnx/pull/3351
}
SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow,
exUnderflow, exPrecision]);
Comment on lines +5 to +6
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
SHERPA_ONNX_DIR=$(cd $SCRIPT_DIR/../.. && pwd)

echo "SHERPA_ONNX_DIR: $SHERPA_ONNX_DIR"

if [[ ! -f ../../build/install/lib/libsherpa-onnx-c-api.dylib && ! -f ../../build/install/lib/libsherpa-onnx-c-api.so && ! -f ../../build/install/lib/sherpa-onnx-c-api.dll ]]; then
Comment on lines +38 to +42
fpc \
-dSHERPA_ONNX_USE_SHARED_LIBS \
-Fu$SHERPA_ONNX_DIR/sherpa-onnx/pascal-api \
-Fl$SHERPA_ONNX_DIR/build/install/lib \
./zipvoice-zh-en.pas
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants