Skip to content

Add Pipe TTS model support - #2591

Closed
sienaiwun wants to merge 3 commits into
k2-fsa:masterfrom
sienaiwun:master
Closed

sienaiwun wants to merge 3 commits into
k2-fsa:masterfrom
sienaiwun:master

Conversation

@sienaiwun

@sienaiwun sienaiwun commented Sep 11, 2025 •

Copy link
Copy Markdown
Contributor

This PR adds support for the Pipe TTS model.

Usage Example

You can now run the Piper TTS model with sherpa-onnx-offline-tts as follows:

sherpa-onnx-offline-tts
--piper-model=./piper-model/alan/en_GB-alan-medium.onnx
--piper-model-config-file=./piper-model/alan/en_GB-alan-medium.onnx.json
--piper-data-dir=./sherpa-onnx/vits-piper-en_US-amy-low/espeak-ng-data
--output-filename=./generated.wav
"how old are you?"

This will generate a speech waveform (generated.wav) from the given text using the Piper TTS model.

Summary by CodeRabbit

  • New Features
    • Added Piper-based offline TTS with phonemization, multi-speaker support, per-phrase synthesis, and progress/cancellation callbacks.
  • Examples
    • Added a C++ example demonstrating Piper TTS usage and WAV output.
  • Documentation
    • CLI help updated with Piper and VITS usage examples and flags.
  • Chores
    • Build system extended to include Piper components when TTS is enabled.
  • Improvements
    • Enhanced logging and diagnostics for offline TTS.

@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Sep 11, 2025
@coderabbitai

coderabbitai Bot commented Sep 11, 2025 •

Copy link
Copy Markdown

Walkthrough

Adds Piper-based offline TTS: new Piper model config, model/metadata classes, phonemization and phoneme→ID mapping, voice loading and synthesis, a Piper OfflineTts implementation (with Android support), CMake additions, and a new C++ example; also updates CLI usage text.

Changes

Cohort / File(s) Summary
Build & examples
cxx-api-examples/CMakeLists.txt, cxx-api-examples/piper-tts-cxx-api.cc
Adds piper-tts-cxx-api example target and example program demonstrating OfflineTts Piper usage and WAV output.
TTS factory & API surface
sherpa-onnx/c-api/cxx-api.h, sherpa-onnx/csrc/offline-tts-impl.cc
Adds piper field to OfflineTtsModelConfig and wires factory to create OfflineTtsPiperImpl when Piper model is set.
Model config registration
sherpa-onnx/csrc/offline-tts-model-config.h, sherpa-onnx/csrc/offline-tts-model-config.cc
Registers, validates, and serializes Piper sub-config within OfflineTtsModelConfig; constructor updated to hold Piper config.
Piper model config files
sherpa-onnx/csrc/offline-tts-piper-model-config.h, .../offline-tts-piper-model-config.cc
New config struct for Piper model (model, model_config_file, data_dir) with Register/Validate/ToString.
Piper model & metadata
sherpa-onnx/csrc/offline-tts-piper-model.h, .../offline-tts-piper-model.cc, .../offline-tts-piper-model-meta-data.h, .../offline-tts-piper-model-meta-data.cc
Adds OfflineTtsPiperModel with ONNX Runtime session handling, metadata parsing (JSON/ONNX/defaults), and metadata ToString implementation.
Piper backend implementation
sherpa-onnx/csrc/offline-tts-piper-impl.h, .../offline-tts-piper-impl.cc
Implements OfflineTtsPiperImpl: frontend/espeak init, voice config loading, phonemization→IDs, per-phrase synthesis, progress callbacks, and Android asset support.
Phonemization & phoneme→ID mapping
sherpa-onnx/csrc/piper-phonemize.h, .../piper-phonemize.cc, sherpa-onnx/csrc/piper-phoneme-ids.h, .../piper-phoneme-ids.cc
Adds espeak-backed phonemization, UTF-8 utilities, default phoneme maps, and phonemes_to_ids conversion with BOS/EOS/pad handling and missing-phoneme reporting.
Voice synthesis core
sherpa-onnx/csrc/piper-voice.h, .../piper-voice.cc
Adds Piper voice surface: ModelSession, Phonemize/Synthesis/Model configs, JSON parsing, loadVoice, synthesize, and textToAudio pipeline with timing and callback support.
Build sources
sherpa-onnx/csrc/CMakeLists.txt
Includes new Piper source files in TTS sources when SHERPA_ONNX_ENABLE_TTS is enabled.
CLI usage text
sherpa-onnx/csrc/sherpa-onnx-offline-tts.cc
Adds Piper (and VITS) example command lines to help/usage message.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Example as piper-tts-cxx-api
  participant API as OfflineTts (C++ API)
  participant Impl as OfflineTtsPiperImpl
  participant Voice as Piper::Voice
  participant Phon as Piper Phonemizer
  participant Map as Phoneme→ID
  participant Model as Piper ONNX Model

  User->>Example: run with text + Piper config
  Example->>API: OfflineTts::Create(config)
  API->>Impl: construct Piper backend
  Impl->>Voice: loadVoice(model, config)
  Note right of Voice: parse JSON, init ONNX session

  Example->>API: Generate(text, sid, speed, callback)
  API->>Impl: Generate(...)
  Impl->>Phon: phonemize(text)
  Phon-->>Impl: phrases of phonemes
  Impl->>Map: phonemes_to_ids(phrase)
  Map-->>Impl: phoneme ID sequence
  loop per phrase
    Impl->>Model: Run(ids, speaker, speed)
    Model-->>Impl: audio chunk
    Impl->>Example: optional progress callback(chunk)
  end
  Impl-->>API: GeneratedAudio
  API-->>Example: write WAV, finish
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Suggested reviewers

  • csukuangfj

Pre-merge checks (1 passed, 2 warnings)

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title is short and indicates adding TTS model support, which matches the PR intent, but it contains a typo: "Pipe" instead of the actual model name "Piper" used throughout the changeset. This misnaming is factually inaccurate and may confuse reviewers or reviewers searching history for "Piper" changes. Because the title does not correctly identify the primary change, it should be corrected before merge. Rename the PR to "Add Piper TTS model support" to correct the typo; alternatively use a slightly more descriptive title like "Add Piper offline TTS backend and examples" if you want to emphasize scope, and update the PR description to consistently use "Piper".
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Poem

A rabbit scans the build with glee,
Piper hums and phonemes flee.
IDs hop, WAVs bloom bright,
ONNX sings into the night.
Carrots cheer — the voice runs free! 🥕🎵

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 22

🧹 Nitpick comments (31)
sherpa-onnx/csrc/offline-tts-piper-model-meta-data.h (1)

8-11: Unify phoneme-id map type with Piper utilities

The metadata uses unordered_map<char32_t,int64_t>, but Piper’s phoneme ID utilities use piper::PhonemeIdMap (mapping phoneme -> vector). Aligning types avoids conversions and preserves multi-ID mappings.

 #include <cstdint>
 #include <string>
-#include <unordered_map>
+#include "sherpa-onnx/csrc/piper-phoneme-ids.h"
@@
-  // Phoneme ID mapping
-  std::unordered_map<char32_t, int64_t> phoneme_id_map;
+  // Phoneme ID mapping (keep consistent with piper::PhonemeIdMap)
+  piper::PhonemeIdMap phoneme_id_map;

Nit: consider renaming intersperse_pad to interspersePad to match Piper config naming for easier mental mapping.

Also applies to: 32-34

sherpa-onnx/csrc/offline-tts-impl.cc (1)

53-55: Wire-in Piper creation path — LGTM (minor message nit)
Factory branching for Piper mirrors other models correctly.

Optional: unify the error message here (“Please provide a tts model.”) with Validate()’s phrasing (“Please provide exactly one tts model.”) for consistency.

Also applies to: 76-78

sherpa-onnx/csrc/offline-tts-model-config.h (1)

29-29: Changing default debug=true is a behavior change; confirm intent or keep false for backward compatibility.

This flips the default verbosity for all OfflineTts backends. If not intentional, revert to false; otherwise, document in release notes.

Apply this diff to revert if unintended:

-  bool debug = true;
+  bool debug = false;
cxx-api-examples/piper-tts-cxx-api.cc (1)

45-47: Minor: comment contradicts value (debug=1 prints).

Comment says “set to 0 to hide,” but the example sets 1. Either set to 0 or reword the comment.

sherpa-onnx/csrc/offline-tts-piper-model-config.cc (1)

13-22: CLI option descriptions — small polish.

Consider aligning phrasing with other backends (e.g., “Path to Piper ONNX model” / “Path to Piper model .json”).

sherpa-onnx/csrc/offline-tts-piper-model-config.h (1)

14-27: Header is fine; consider adding brief field comments for parity with other configs.

Add 1-line comments for model/model_config_file/data_dir to improve self-documentation.

sherpa-onnx/csrc/piper-phoneme-ids.cc (3)

16-16: Make the default map const and use a conventional kPrefix name.

Prevents accidental mutation and clarifies intent.

Apply:

-static PhonemeIdMap DEFAULT_PHONEME_ID_MAP = {
+static const PhonemeIdMap kDefaultPhonemeIdMap = {

196-206: Gracefully handle missing BOS/PAD/EOS in custom maps.

Using at() will throw if a custom map omits these keys. Add count checks and warn/fallback to defaults.

I can patch this to log a warning and skip BOS/EOS (or fall back to kDefaultPhonemeIdMap) when keys are missing.


212-229: Minor perf: reserve and use find().

  • Reserve approximate capacity for phonemeIds when interspersePad=true.
  • Use find() to avoid double lookups.
sherpa-onnx/csrc/offline-tts-piper-model.h (3)

12-15: Harden Android guards and avoid unnecessary JNI include in a public header

  • Use defined checks to avoid surprises when ANDROID_API is unset on non-Android builds.
  • Drop android/asset_manager_jni.h from the header; it’s not needed for AAssetManager* and drags JNI deps into all includers.
-#if __ANDROID_API__ >= 9
-#include "android/asset_manager.h"
-#include "android/asset_manager_jni.h"
-#endif
+#if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ >= 9)
+#include "android/asset_manager.h"
+#endif
...
-#if __ANDROID_API__ >= 9
+#if defined(__ANDROID_API__) && (__ANDROID_API__ >= 9)
   OfflineTtsPiperModel(AAssetManager *mgr,
                        const OfflineTtsModelConfig &config);
 #endif

Also applies to: 27-31


23-55: Make copy/move semantics explicit

The unique_ptr member makes the class non-copyable implicitly; be explicit to prevent accidental copies and clarify intent.

 class OfflineTtsPiperModel {
  public:
   explicit OfflineTtsPiperModel(const OfflineTtsModelConfig &config);
+  OfflineTtsPiperModel(const OfflineTtsPiperModel&) = delete;
+  OfflineTtsPiperModel& operator=(const OfflineTtsPiperModel&) = delete;
+  OfflineTtsPiperModel(OfflineTtsPiperModel&&) noexcept = default;
+  OfflineTtsPiperModel& operator=(OfflineTtsPiperModel&&) noexcept = default;

34-45: Clarify ownership/expectations for Ort::Value input

Run takes phoneme_ids by value and will std::move it; document that the caller should not reuse phoneme_ids after calling Run and that it must be a CPU tensor with shape (T,). Minor doc tweak.

sherpa-onnx/csrc/offline-tts-piper-impl.h (1)

12-15: Same Android guard tightening; remove JNI include

Mirror the header changes here to keep build flags consistent and avoid unnecessary JNI includes.

-#if __ANDROID_API__ >= 9
-#include "android/asset_manager.h"
-#include "android/asset_manager_jni.h"
-#endif
+#if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ >= 9)
+#include "android/asset_manager.h"
+#endif
...
-#if __ANDROID_API__ >= 9
+#if defined(__ANDROID_API__) && (__ANDROID_API__ >= 9)
   OfflineTtsPiperImpl(AAssetManager *mgr, const OfflineTtsConfig &config);
 #endif

Also applies to: 29-31

sherpa-onnx/csrc/piper-phonemize.cc (5)

40-42: Case-folding is a stub

Returning lowercase is a placeholder. If this path is used for TextPhonemes, consider ICU/utf8proc for proper case folding.


98-103: Clear output on failure or guard partial results

On failure, phonemes may contain partial data. Either clear it before returning false or document partial results are possible.

 bool phonemize(const std::string& text, const PhonemizeConfig& config,
                std::vector<std::vector<Phoneme>>& phonemes) {
-  
+  phonemes.clear();

113-120: Use info/debug logs instead of error logs

These are normal operational messages; LOGE is noisy.

-  SHERPA_ONNX_LOGE("Using espeak-ng voice from loaded config: %s", espeak_config.voice.c_str());
+  SHERPA_ONNX_LOGI("Using espeak-ng voice: %s", espeak_config.voice.c_str());
...
-    SHERPA_ONNX_LOGE("Phonemized text into %zu sentence(s)", phonemes.size());
+    SHERPA_ONNX_LOGI("Phonemized into %zu sentence(s)", phonemes.size());

25-32: Make DEFAULT_PHONEME_MAP immutable unless runtime mutation is intended

If this is static configuration, prefer const (or function-local static) to avoid accidental mutation and data races.

-std::map<std::string, PhonemeMap> DEFAULT_PHONEME_MAP = {
+const std::map<std::string, PhonemeMap> DEFAULT_PHONEME_MAP = {
     {"pt-br", {{U'c', {U'k'}}}}
 };

166-203: Make config parameter const and minor cleanup

The function doesn’t mutate config; take by const&, and use back() for clarity.

-void phonemize_codepoints(const std::string& text, CodepointsPhonemeConfig& config,
+void phonemize_codepoints(const std::string& text, const CodepointsPhonemeConfig& config,
                          std::vector<std::vector<Phoneme>>& phonemes) {
...
-  phonemes.emplace_back();
-  auto sentPhonemes = &phonemes[phonemes.size() - 1];
+  phonemes.emplace_back();
+  auto* sentPhonemes = &phonemes.back();

Note: Update the declaration in piper-phonemize.h accordingly.

sherpa-onnx/csrc/offline-tts-piper-impl.cc (3)

180-183: Lambda capture can be optimized

The lambda captures sentence_audio_buffer by reference, which is correct, but the callback pattern could be clearer.

Consider making the capture more explicit:

-      auto audio_callback = [&sentence_audio_buffer](std::vector<float>&& audio) {
+      auto audio_callback = [&sentence_audio_buffer](std::vector<float>&& audio) -> void {
         sentence_audio_buffer.insert(sentence_audio_buffer.end(), 
                                     audio.begin(), audio.end());
       };

255-256: Warning message might be too severe

The warning about missing piper-data-dir could be misleading if the model doesn't require espeak-ng initialization.

Consider using a debug-level log instead:

   } else {
-    SHERPA_ONNX_LOGE("Warning: No piper-data-dir provided. Phonemization may fail.");
+    if (config_.model.debug) {
+      SHERPA_ONNX_LOGE("No piper-data-dir provided. Using model's built-in phonemization if available.");
+    }
   }

268-273: File reading error handling could be more specific

The error messages don't distinguish between file not found vs. read permission errors.

Consider adding more detailed error information when file reading fails to help with debugging.

sherpa-onnx/csrc/piper-phoneme-ids.h (2)

40-42: Make config parameter const.

phonemes_to_ids doesn’t mutate PhonemeIdConfig; prefer const-ref.

Apply:

-void phonemes_to_ids(const std::vector<Phoneme> &phonemes, PhonemeIdConfig &config,
+void phonemes_to_ids(const std::vector<Phoneme> &phonemes, const PhonemeIdConfig &config,
                      std::vector<PhonemeId> &phonemeIds,
                      std::map<Phoneme, std::size_t> &missingPhonemes);

9-11: Drop unused include (minor).

isn’t used in this header.

Apply:

-#include <string>
sherpa-onnx/csrc/piper-voice.cc (6)

277-281: Match printf-format type for ids[0].

%lld expects long long; cast explicitly to avoid format warnings/UB on some platforms.

Apply:

-          SHERPA_ONNX_LOGE("Parsed Unicode phoneme U+%04X -> %lld", 
-                          static_cast<uint32_t>(phoneme_char), ids[0]);
+          SHERPA_ONNX_LOGE("Parsed Unicode phoneme U+%04X -> %lld",
+                           static_cast<uint32_t>(phoneme_char),
+                           static_cast<long long>(ids[0]));

401-404: Keep input names in sync with provided tensors.

When speakerId is absent, still declaring "sid" in names is brittle. Build names conditionally to match inputTensors size.

Apply:

-  // Input and output names
-  std::vector<const char*> inputNames = {"input", "input_lengths", "scales", "sid"};
+  // Input and output names (match tensors)
+  std::vector<const char*> inputNames = {"input", "input_lengths", "scales"};
+  if (synthesisConfig.speakerId) {
+    inputNames.push_back("sid");
+  }
   std::vector<const char*> outputNames = {"output"};

Also applies to: 395-399


552-554: Use empty() for clarity.

Minor readability tweak.

Apply:

-      if (phrasePhonemes[phraseIdx]->size() <= 0) {
+      if (phrasePhonemes[phraseIdx]->empty()) {
         continue;
       }

35-41: Session options likely hurt performance; make configurable.

Disabling all graph opts, mem arena, and patterns will slow inference. Consider enabling at least ORT_ENABLE_BASIC and keeping arena/patterns on, gated by config/env.

Example:

-    options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_DISABLE_ALL);
-    options.DisableCpuMemArena();
-    options.DisableMemPattern(); 
+    options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_BASIC);
+    // Keep arenas/patterns enabled by default; allow disabling via config flags if needed.

Also applies to: 313-322


329-347: Propagate meta/config flags to PhonemeIdConfig.

Currently idConfig uses defaults (interspersePad/addBos/addEos). Read these from model config/meta to match Piper behavior.

Would you like me to thread OfflineTtsPiperModelMetaData.intersperse_pad/pad_id/bos_id/eos_id into PhonemeIdConfig here?

Also applies to: 495-561


431-436: Log levels: use info/debug for normal flow.

SHERPA_ONNX_LOGE is for errors; most of these are status logs.

Apply, e.g., replace with SHERPA_ONNX_LOGI or SHERPA_ONNX_LOGD where available.

Also applies to: 579-581, 612-616

sherpa-onnx/csrc/piper-voice.h (2)

29-42: Don’t hard-disable ORT optimizations by default.

Prefer a config knob; basic optimizations and arenas materially improve TTS throughput.

See proposed diff in piper-voice.cc to align defaults.


137-146: Tighten API: align input name handling with model I/O.

Document that "sid" is optional and only supplied when present; consider exposing an introspection API to query inputs from ModelSession.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 04a98ca and 454cb20.

📒 Files selected for processing (22)
  • cxx-api-examples/CMakeLists.txt (1 hunks)
  • cxx-api-examples/piper-tts-cxx-api.cc (1 hunks)
  • sherpa-onnx/c-api/cxx-api.h (1 hunks)
  • sherpa-onnx/csrc/CMakeLists.txt (1 hunks)
  • sherpa-onnx/csrc/offline-tts-impl.cc (3 hunks)
  • sherpa-onnx/csrc/offline-tts-model-config.cc (3 hunks)
  • sherpa-onnx/csrc/offline-tts-model-config.h (3 hunks)
  • sherpa-onnx/csrc/offline-tts-piper-impl.cc (1 hunks)
  • sherpa-onnx/csrc/offline-tts-piper-impl.h (1 hunks)
  • sherpa-onnx/csrc/offline-tts-piper-model-config.cc (1 hunks)
  • sherpa-onnx/csrc/offline-tts-piper-model-config.h (1 hunks)
  • sherpa-onnx/csrc/offline-tts-piper-model-meta-data.cc (1 hunks)
  • sherpa-onnx/csrc/offline-tts-piper-model-meta-data.h (1 hunks)
  • sherpa-onnx/csrc/offline-tts-piper-model.cc (1 hunks)
  • sherpa-onnx/csrc/offline-tts-piper-model.h (1 hunks)
  • sherpa-onnx/csrc/piper-phoneme-ids.cc (1 hunks)
  • sherpa-onnx/csrc/piper-phoneme-ids.h (1 hunks)
  • sherpa-onnx/csrc/piper-phonemize.cc (1 hunks)
  • sherpa-onnx/csrc/piper-phonemize.h (1 hunks)
  • sherpa-onnx/csrc/piper-voice.cc (1 hunks)
  • sherpa-onnx/csrc/piper-voice.h (1 hunks)
  • sherpa-onnx/csrc/sherpa-onnx-offline-tts.cc (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (16)
cxx-api-examples/piper-tts-cxx-api.cc (1)
sherpa-onnx/csrc/offline-tts-impl.cc (6)
  • Create (40-60)
  • Create (40-41)
  • Create (63-82)
  • Create (63-64)
  • Create (85-86)
  • Create (90-91)
sherpa-onnx/csrc/offline-tts-piper-model-config.h (4)
sherpa-onnx/csrc/offline-tts-model-config.h (1)
  • sherpa_onnx (18-58)
sherpa-onnx/csrc/offline-tts-kokoro-model-config.h (1)
  • sherpa_onnx (12-61)
sherpa-onnx/csrc/offline-tts-matcha-model-config.h (1)
  • sherpa_onnx (12-54)
sherpa-onnx/csrc/offline-tts-kitten-model-config.h (1)
  • sherpa_onnx (12-41)
sherpa-onnx/csrc/offline-tts-piper-model-meta-data.cc (2)
sherpa-onnx/csrc/offline-tts-model-config.cc (2)
  • ToString (64-79)
  • ToString (64-64)
sherpa-onnx/csrc/offline-tts-piper-model-config.cc (2)
  • ToString (36-45)
  • ToString (36-36)
sherpa-onnx/csrc/piper-phonemize.h (3)
sherpa-onnx/csrc/piper-phoneme-ids.h (2)
  • sherpa_onnx (15-45)
  • piper (16-44)
sherpa-onnx/csrc/piper-voice.h (2)
  • sherpa_onnx (19-151)
  • piper (20-71)
sherpa-onnx/csrc/piper-phonemize.cc (4)
  • phonemize (99-164)
  • phonemize (99-100)
  • phonemize_codepoints (166-203)
  • phonemize_codepoints (166-167)
sherpa-onnx/csrc/offline-tts-model-config.h (3)
sherpa-onnx/csrc/piper-phoneme-ids.h (1)
  • piper (16-44)
sherpa-onnx/csrc/piper-phonemize.h (1)
  • piper (15-38)
sherpa-onnx/csrc/piper-voice.h (1)
  • piper (20-71)
sherpa-onnx/csrc/piper-phoneme-ids.h (2)
sherpa-onnx/csrc/piper-phonemize.h (2)
  • sherpa_onnx (14-60)
  • piper (15-38)
sherpa-onnx/csrc/piper-phoneme-ids.cc (2)
  • phonemes_to_ids (187-254)
  • phonemes_to_ids (187-189)
sherpa-onnx/csrc/offline-tts-piper-model.h (4)
sherpa-onnx/csrc/offline-tts-model-config.h (1)
  • sherpa_onnx (18-58)
sherpa-onnx/csrc/offline-tts-piper-impl.h (1)
  • sherpa_onnx (23-53)
sherpa-onnx/csrc/offline-tts-piper-model-meta-data.h (1)
  • sherpa_onnx (12-40)
sherpa-onnx/csrc/offline-tts-piper-model.cc (13)
  • OfflineTtsPiperModel (335-336)
  • OfflineTtsPiperModel (339-341)
  • OfflineTtsPiperModel (344-344)
  • Run (346-349)
  • Run (346-347)
  • phoneme_ids (57-71)
  • phoneme_ids (57-57)
  • Allocator (351-353)
  • Allocator (351-351)
  • GetMetaData (355-357)
  • GetMetaData (355-355)
  • Impl (27-39)
  • Impl (42-54)
sherpa-onnx/csrc/offline-tts-piper-impl.cc (4)
sherpa-onnx/csrc/offline-tts-piper-impl.h (1)
  • OfflineTtsPiperImpl (25-51)
sherpa-onnx/csrc/piper-phonemize.cc (2)
  • phonemize (99-164)
  • phonemize (99-100)
sherpa-onnx/csrc/piper-phoneme-ids.cc (2)
  • phonemes_to_ids (187-254)
  • phonemes_to_ids (187-189)
sherpa-onnx/csrc/piper-voice.cc (4)
  • synthesize (352-438)
  • synthesize (352-354)
  • loadVoice (324-350)
  • loadVoice (324-325)
sherpa-onnx/csrc/piper-voice.cc (2)
sherpa-onnx/csrc/piper-phonemize.cc (4)
  • phonemize (99-164)
  • phonemize (99-100)
  • phonemize_codepoints (166-203)
  • phonemize_codepoints (166-167)
sherpa-onnx/csrc/piper-phoneme-ids.cc (2)
  • phonemes_to_ids (187-254)
  • phonemes_to_ids (187-189)
sherpa-onnx/csrc/piper-phonemize.cc (1)
sherpa-onnx/csrc/offline-tts-piper-impl.cc (1)
  • CallPhonemizeEspeak (37-39)
sherpa-onnx/csrc/offline-tts-piper-model.cc (2)
sherpa-onnx/csrc/ten-vad-model.cc (1)
  • meta_data (257-314)
sherpa-onnx/csrc/offline-tts-piper-model.h (1)
  • OfflineTtsPiperModel (23-55)
sherpa-onnx/csrc/offline-tts-piper-impl.h (6)
sherpa-onnx/csrc/offline-tts-model-config.h (1)
  • sherpa_onnx (18-58)
sherpa-onnx/csrc/offline-tts-piper-model-config.h (1)
  • sherpa_onnx (12-33)
sherpa-onnx/csrc/offline-tts-piper-model.h (2)
  • sherpa_onnx (21-57)
  • OfflineTtsPiperModel (23-55)
sherpa-onnx/csrc/piper-voice.h (2)
  • sherpa_onnx (19-151)
  • piper (20-71)
sherpa-onnx/csrc/offline-tts-impl.h (1)
  • OfflineTtsImpl (18-52)
sherpa-onnx/csrc/offline-tts-frontend.h (1)
  • OfflineTtsFrontend (38-56)
sherpa-onnx/csrc/offline-tts-model-config.cc (3)
sherpa-onnx/csrc/piper-phoneme-ids.h (1)
  • piper (16-44)
sherpa-onnx/csrc/piper-phonemize.h (1)
  • piper (15-38)
sherpa-onnx/csrc/piper-voice.h (1)
  • piper (20-71)
sherpa-onnx/csrc/piper-voice.h (5)
sherpa-onnx/csrc/offline-tts-piper-impl.h (1)
  • sherpa_onnx (23-53)
sherpa-onnx/csrc/offline-tts-piper-model-meta-data.h (1)
  • sherpa_onnx (12-40)
sherpa-onnx/csrc/piper-phoneme-ids.h (2)
  • sherpa_onnx (15-45)
  • piper (16-44)
sherpa-onnx/csrc/piper-phonemize.h (2)
  • sherpa_onnx (14-60)
  • piper (15-38)
sherpa-onnx/csrc/piper-voice.cc (10)
  • loadVoice (324-350)
  • loadVoice (324-325)
  • textToAudio (440-617)
  • textToAudio (440-442)
  • synthesize (352-438)
  • synthesize (352-354)
  • parsePhonemeIdMapFromJson (129-290)
  • parsePhonemeIdMapFromJson (129-129)
  • parseEspeakVoiceFromJson (49-127)
  • parseEspeakVoiceFromJson (49-49)
sherpa-onnx/csrc/offline-tts-piper-model-meta-data.h (4)
sherpa-onnx/csrc/offline-tts-piper-model.h (1)
  • sherpa_onnx (21-57)
sherpa-onnx/csrc/piper-phoneme-ids.h (1)
  • sherpa_onnx (15-45)
sherpa-onnx/csrc/piper-voice.h (1)
  • sherpa_onnx (19-151)
sherpa-onnx/csrc/offline-tts-matcha-model-meta-data.h (1)
  • sherpa_onnx (11-28)
sherpa-onnx/csrc/offline-tts-piper-model-config.cc (2)
sherpa-onnx/csrc/offline-tts-model-config.cc (6)
  • Register (11-27)
  • Register (11-11)
  • Validate (29-62)
  • Validate (29-29)
  • ToString (64-79)
  • ToString (64-64)
sherpa-onnx/csrc/offline-tts-vits-model-config.cc (1)
  • Register (12-115)
🔇 Additional comments (16)
sherpa-onnx/csrc/offline-tts-impl.cc (1)

22-22: Include Piper backend — LGTM
The include adds the Piper path cleanly with no side effects.

sherpa-onnx/c-api/cxx-api.h (1)

427-427: Incorrect — OfflineTtsPiperModelConfig already defined; include existing header instead of duplicating

OfflineTtsPiperModelConfig is defined in sherpa-onnx/csrc/offline-tts-piper-model-config.h; do not add the duplicate struct — add/include that header in sherpa-onnx/c-api/cxx-api.h (or ensure it's already included).

Likely an incorrect or invalid review comment.

cxx-api-examples/CMakeLists.txt (1)

151-152: Ensure example source exists and is wired in docs

cxx-api-examples/piper-tts-cxx-api.cc exists. Add or update distribution/examples docs (README.md or docs/) to reference the piper-tts-cxx-api target/example.

sherpa-onnx/csrc/offline-tts-piper-model-meta-data.cc (1)

11-29: ToString implementation — LGTM

Clear, matches metadata fields, and consistent with other ToString() outputs.

sherpa-onnx/csrc/offline-tts-model-config.cc (3)

17-17: Register Piper flags — LGTM
Registration mirrors other model configs.


55-57: Validate Piper path — LGTM
Short-circuiting to piper.Validate() matches existing patterns.


73-73: Include Piper in ToString — LGTM
Keeps config dumps complete for debugging.

sherpa-onnx/csrc/offline-tts-model-config.h (3)

13-13: Piper config wire-up looks correct.

Header include is appropriate and matches usage elsewhere.


26-26: New piper member added in model config — LGTM.

Consistent with factory checks in OfflineTtsImpl.


39-47: Constructor signature expanded — verify all call sites & public API/ABI

Repo search shows many bindings/examples construct OfflineTtsModelConfig (scripts/*, kotlin-api/Tts.kt, flutter/sherpa_onnx/lib/src/tts.dart, java-api, dotnet, python examples) and I found no internal C++ call sites that pass the positional ctor. Changing the C++ ctor is ABI-breaking for external consumers — either add a compatibility overload/default for the new piper parameter in sherpa-onnx/csrc/offline-tts-model-config.h or update all public bindings and bump the public API/ABI.

sherpa-onnx/csrc/piper-phoneme-ids.cc (2)

15-185: Source provenance/license check for the default mapping.

If adapted from Piper, ensure license headers/attribution satisfy upstream’s license.


1-11: Include dependency on Phoneme definition — verified.
piper-phoneme-ids.h directly includes piper-phonemize.h (sherpa-onnx/csrc/piper-phoneme-ids.h:13), and piper-phonemize.h defines typedef char32_t Phoneme; (sherpa-onnx/csrc/piper-phonemize.h:17).

sherpa-onnx/csrc/offline-tts-piper-impl.h (1)

50-51: Confirm mutability of voice_data_ is intentional

voice_data_ is mutable; ensure it’s only mutated for stats/caching needed by const Generate. If more than that, consider removing const from Generate or narrowing what’s mutable.

sherpa-onnx/csrc/piper-phonemize.cc (1)

116-118: Verify ::piper::Phoneme and sherpa_onnx::piper::Phoneme are identical and ABI-safe

Location: sherpa-onnx/csrc/piper-phonemize.cc:116-118

ripgrep returned "No files were searched" — typedefs not found; confirm both aliases resolve to the same underlying type (e.g., char32_t) and are ABI-compatible.

  • Add compile-time guard: static_assert(std::is_same_v<::piper::Phoneme, sherpa_onnx::piper::Phoneme>);
  • If they differ, convert before calling sherpa_onnx::CallPhonemizeEspeak or unify the typedef.
sherpa-onnx/csrc/piper-phonemize.h (1)

1-63: Well-structured header file

The header file is properly organized with include guards, clear namespace hierarchy, and appropriate forward declarations. The type definitions and configuration structures are well-documented.

sherpa-onnx/csrc/offline-tts-piper-model.cc (1)

297-317: Hardcoded phoneme mappings might not match actual model requirements

The default phoneme ID mappings are hardcoded and might not match what specific Piper models expect.

These default mappings should be verified against actual Piper model requirements. Consider loading defaults from a configuration file or documenting the source of these mappings.

Comment on lines +54 to +63
auto tts = OfflineTts::Create(config);
int32_t sid = 0;
float speed = 1.0; // larger -> faster in speech speed

#if 0
// If you don't want to use a callback, then please enable this branch
GeneratedAudio audio = tts.Generate(text, sid, speed);
#else
GeneratedAudio audio = tts.Generate(text, sid, speed, ProgressCallback);
#endif

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

Use pointer semantics for OfflineTts and guard Create() result.

OfflineTts::Create typically returns a pointer-like object; current code uses dot-call and misses a null check.

Apply:

-  auto tts = OfflineTts::Create(config);
-  int32_t sid = 0;
-  float speed = 1.0;  // larger -> faster in speech speed
+  auto tts = OfflineTts::Create(config);
+  if (!tts) {
+    fprintf(stderr, "Failed to create OfflineTts (invalid config?)\n");
+    return 1;
+  }
+  int32_t sid = 0;
+  float speed = 1.0;  // larger -> faster in speech speed

And call through the pointer:

-  GeneratedAudio audio = tts.Generate(text, sid, speed);
+  GeneratedAudio audio = tts->Generate(text, sid, speed);
-  GeneratedAudio audio = tts.Generate(text, sid, speed, ProgressCallback);
+  GeneratedAudio audio = tts->Generate(text, sid, speed, ProgressCallback);

If the API requires a user_data argument for the callback, pass nullptr as the final arg.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In cxx-api-examples/piper-tts-cxx-api.cc around lines 54-63, OfflineTts::Create
likely returns a pointer-like object that must be null-checked and used with
pointer semantics; update the code to store the Create() result in a
pointer/unique_ptr, check it for null and handle the error (log/return) before
proceeding, replace dot calls (tts.Generate) with pointer dereference syntax
(tts->Generate), and if the Generate callback signature requires a user_data
parameter, pass nullptr as the final argument when calling Generate with
ProgressCallback.

Comment on lines +204 to 211
offline-tts-piper-impl.cc
offline-tts-piper-model-config.cc
offline-tts-piper-model.cc
offline-tts-piper-model-meta-data.cc
piper-phoneme-ids.cc
piper-phonemize.cc
piper-voice.cc
offline-tts-vits-model-config.cc

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Piper sources added — looks good; verify external dependency availability

The Piper sources are correctly appended under SHERPA_ONNX_ENABLE_TTS. Ensure the external/library target piper_phonemize is available in all CI presets (Linux/macOS/Windows) to avoid link errors.

Suggested quick checks:


🏁 Script executed:

#!/bin/bash
# Confirm sources exist
fd -a 'offline-tts-piper-|piper-phoneme|piper-phonemize|piper-voice' sherpa-onnx/csrc

# Confirm the CMake target or import for piper_phonemize exists
rg -nP "add_library\(|target_link_libraries\([^)]*piper_phonemize" -C2

Length of output: 16158


Piper sources added — piper_phonemize CMake target missing; add/import it or ensure CI provides it

  • Sources were added to sherpa-onnx/csrc/CMakeLists.txt (files present under sherpa-onnx/csrc/).
  • Repo search found no CMake add_library/target_link_libraries/import for "piper_phonemize" — builds will likely fail with link errors.
  • Action: define/import a piper_phonemize target (or add appropriate find_package/install steps) and ensure all CI presets (Linux/macOS/Windows) provide the dependency.
🤖 Prompt for AI Agents
In sherpa-onnx/csrc/CMakeLists.txt around lines 204 to 211, new Piper source
files were added but there is no CMake target named piper_phonemize, which will
cause linker errors; fix by declaring a piper_phonemize target (add_library or
add_executable as appropriate) that includes the piper_phonemize source and any
other Piper sources that belong to that component, set
target_include_directories and target_link_libraries to the correct internal and
external dependencies, export/install the target (install(TARGETS ...) and
export(...) if the project exports targets), and update the CI presets or
platform-specific CMake logic to ensure the dependency is available on
Linux/macOS/Windows (or replace with find_package()/add_subdirectory() if the
phonemize component is provided externally).

Comment on lines +52 to +54
if (!LoadVoiceConfig()) {
SHERPA_ONNX_LOGE("Failed to load voice configuration");
}

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

Continue initialization despite configuration loading failure

The constructor logs an error but continues execution when LoadVoiceConfig() fails. This could lead to runtime crashes when trying to use an improperly initialized model.

Consider throwing an exception or properly handling the failure:

   if (!LoadVoiceConfig()) {
     SHERPA_ONNX_LOGE("Failed to load voice configuration");
+    throw std::runtime_error("Failed to load voice configuration");
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!LoadVoiceConfig()) {
SHERPA_ONNX_LOGE("Failed to load voice configuration");
}
if (!LoadVoiceConfig()) {
SHERPA_ONNX_LOGE("Failed to load voice configuration");
throw std::runtime_error("Failed to load voice configuration");
}
🤖 Prompt for AI Agents
In sherpa-onnx/csrc/offline-tts-piper-impl.cc around lines 52 to 54, the
constructor only logs an error when LoadVoiceConfig() fails and continues
execution which can cause crashes later; modify the constructor to handle the
failure explicitly by either throwing a descriptive std::runtime_error (or
another appropriate exception type used in the project) including the failure
context, or by returning/setting an initialization-failed flag and aborting
further setup so that downstream methods cannot be used on an uninitialized
object; ensure resources allocated before the call are cleaned up before
throwing or that the object is left in a safe, non-usable state.

Comment on lines +65 to +67
if (!LoadVoiceConfig()) {
SHERPA_ONNX_LOGE("Failed to load voice configuration");
}

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

Duplicate error handling logic in Android constructor

Same issue as above - the Android constructor also continues after configuration loading failure.

Apply the same fix as suggested for the standard constructor.

🤖 Prompt for AI Agents
In sherpa-onnx/csrc/offline-tts-piper-impl.cc around lines 65-67, the Android
constructor logs a failure when LoadVoiceConfig() returns false but then
continues initialization; mirror the fix applied to the standard constructor by,
immediately after logging the error, marking the object as uninitialized (call
the same SetInitialized(false) or ok_ = false used in the other constructor) and
exit the constructor body early (i.e., return) to stop further initialization.

Comment on lines +105 to +108
if (!sherpa_onnx::piper::phonemize(text, voice_data_.phonemizeConfig, phonemes)) {
SHERPA_ONNX_LOGE("Failed to phonemize text");
return result;
}

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

Missing null check for voice configuration

The code uses voice_data_.phonemizeConfig without verifying if it was successfully loaded. If LoadVoiceConfig() failed in the constructor, this could cause undefined behavior.

Add a check before using the voice configuration:

+  if (voice_data_.phonemizeConfig.voice.empty()) {
+    SHERPA_ONNX_LOGE("Voice configuration not loaded");
+    return result;
+  }
+
   // Phonemize text using loaded voice configuration
   if (!sherpa_onnx::piper::phonemize(text, voice_data_.phonemizeConfig, phonemes)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!sherpa_onnx::piper::phonemize(text, voice_data_.phonemizeConfig, phonemes)) {
SHERPA_ONNX_LOGE("Failed to phonemize text");
return result;
}
if (voice_data_.phonemizeConfig.voice.empty()) {
SHERPA_ONNX_LOGE("Voice configuration not loaded");
return result;
}
// Phonemize text using loaded voice configuration
if (!sherpa_onnx::piper::phonemize(text, voice_data_.phonemizeConfig, phonemes)) {
SHERPA_ONNX_LOGE("Failed to phonemize text");
return result;
}
🤖 Prompt for AI Agents
In sherpa-onnx/csrc/offline-tts-piper-impl.cc around lines 105 to 108, the code
calls sherpa_onnx::piper::phonemize(text, voice_data_.phonemizeConfig, phonemes)
without verifying that voice_data_.phonemizeConfig was successfully loaded; add
a pre-check that voice_data_.phonemizeConfig is valid (e.g., not null/empty or
the optional/flag set by LoadVoiceConfig()), and if it's invalid log an error
with SHERPA_ONNX_LOGE and return the current result (or an appropriate error
result) instead of calling phonemize; alternatively, attempt to reload the voice
config and only call phonemize when the config is confirmed valid.

Comment on lines +7 to +13
#include <algorithm>
#include <chrono>
#include <sstream>

#include "sherpa-onnx/csrc/macros.h"

namespace sherpa_onnx {

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

Include for uint8_t usage.

uint8_t is used in UTF-8 decoding; add the header to avoid build breaks.

Apply:

 #include <algorithm>
 #include <chrono>
+#include <cstdint>
 #include <sstream>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include <algorithm>
#include <chrono>
#include <sstream>
#include "sherpa-onnx/csrc/macros.h"
namespace sherpa_onnx {
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <sstream>
#include "sherpa-onnx/csrc/macros.h"
namespace sherpa_onnx {
🤖 Prompt for AI Agents
In sherpa-onnx/csrc/piper-voice.cc around lines 7 to 13, the file uses uint8_t
for UTF-8 decoding but doesn't include <cstdint>, which can cause build
failures; add the header by inserting #include <cstdint> with the other includes
so uint8_t is defined before its usage.

Comment on lines +16 to +46
// JSON parsing helper functions - parse phoneme_id_map and espeak config from JSON
bool parsePhonemizeConfig(const std::string& configData, PhonemizeConfig& phonemizeConfig) {
phonemizeConfig.phonemeType = PhonemizerPhonemes;

// Parse espeak voice from JSON config
if (!parseEspeakVoiceFromJson(configData, phonemizeConfig.voice)) {
SHERPA_ONNX_LOGE("Failed to parse espeak voice from config, using default");
phonemizeConfig.voice = "en-us"; // Fallback default
}

// Parse phoneme_id_map from JSON config
if (!parsePhonemeIdMapFromJson(configData, phonemizeConfig.phonemeIdMap)) {
SHERPA_ONNX_LOGE("Failed to parse phoneme_id_map from config, using defaults");
// Fallback to default phoneme ID map
phonemizeConfig.phonemeIdMap = {
{U'_', {0}}, {U'^', {1}}, {U'$', {2}}, {U' ', {3}},
{U'!', {4}}, {U'\'', {5}}, {U'(', {6}}, {U')', {7}},
{U',', {8}}, {U'-', {9}}, {U'.', {10}}, {U':', {11}},
{U';', {12}}, {U'?', {13}}
};
}

if (phonemizeConfig.phonemeIdMap.empty()) {
SHERPA_ONNX_LOGE("Warning: Empty phoneme_id_map loaded from config");
return false;
}

SHERPA_ONNX_LOGE("Loaded %zu phoneme mappings from config", phonemizeConfig.phonemeIdMap.size());
SHERPA_ONNX_LOGE("Using espeak voice: %s", phonemizeConfig.voice.c_str());
return true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace handwritten JSON parsing with a real JSON library.

Manual brace/quote scanning is fragile (strings with braces, escaped quotes, whitespace). Parse failures will be silent and lead to wrong configs (e.g., partial phoneme maps).

I can replace parsePhonemizeConfig/parseEspeakVoiceFromJson/parsePhonemeIdMapFromJson with nlohmann::json or RapidJSON and wire fields: espeak.voice, phoneme_id_map (UTF-8 keys), intersperse_pad, pad/bos/eos IDs, sample_rate, num_speakers, noise params. Want me to draft that?

Also applies to: 48-127, 129-291

Comment on lines +202 to +213
// Parse comma-separated IDs
std::istringstream iss(ids_str);
std::string id_token;
while (std::getline(iss, id_token, ',')) {
// Remove whitespace
id_token.erase(std::remove_if(id_token.begin(), id_token.end(), ::isspace), id_token.end());
if (!id_token.empty()) {
try {
ids.push_back(std::stoll(id_token));
} catch (const std::exception& e) {
SHERPA_ONNX_LOGE("Failed to parse phoneme ID: %s", id_token.c_str());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix undefined behavior with std::isspace on signed char.

Passing char to ::isspace is UB for non-ASCII. Cast to unsigned char.

Apply:

-        // Remove whitespace
-        id_token.erase(std::remove_if(id_token.begin(), id_token.end(), ::isspace), id_token.end());
+        // Remove whitespace (avoid UB)
+        auto is_ws = [](unsigned char c) { return std::isspace(c); };
+        id_token.erase(std::remove_if(id_token.begin(), id_token.end(),
+                                      [&](char c){ return is_ws(static_cast<unsigned char>(c)); }),
+                       id_token.end());
@@
-      // Remove whitespace
-      id_str.erase(std::remove_if(id_str.begin(), id_str.end(), ::isspace), id_str.end());
+      // Remove whitespace (avoid UB)
+      auto is_ws2 = [](unsigned char c) { return std::isspace(c); };
+      id_str.erase(std::remove_if(id_str.begin(), id_str.end(),
+                                  [&](char c){ return is_ws2(static_cast<unsigned char>(c)); }),
+                   id_str.end());

Also applies to: 224-226

🤖 Prompt for AI Agents
In sherpa-onnx/csrc/piper-voice.cc around lines 202 to 213 (and also apply the
same change at lines 224-226), using ::isspace directly on char can cause
undefined behavior for signed char on non-ASCII input; change the call to cast
each character to unsigned char before calling ::isspace (e.g., replace
::isspace with ::isspace(static_cast<unsigned char>(c)) inside the remove_if
predicate) so whitespace removal is safe for non-ASCII bytes.

Comment on lines +9 to +14
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>

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

Include missing headers (, ).

std::function and fixed-width integers are used in this header.

Apply:

 #include <map>
 #include <memory>
 #include <optional>
+#include <functional>
+#include <cstdint>
 #include <string>
 #include <vector>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <optional>
#include <functional>
#include <cstdint>
#include <string>
#include <vector>
🤖 Prompt for AI Agents
In sherpa-onnx/csrc/piper-voice.h around lines 9 to 14, the header uses
std::function and fixed-width integer types but doesn't include <functional> or
<cstdint>; add #include <functional> and #include <cstdint> alongside the
existing includes (e.g., after <memory> or <optional>) so the types are declared
and the header compiles cleanly.

Comment on lines +20 to +21
--piper-model=D:/Sound/PiperModel/alan/en_GB-alan-medium.onnx --piper-model-config-file=D:/Sound/PiperModel/alan/en_GB-alan-medium.onnx.json --piper-data-dir=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low --debug=1 --output-filename="D:\Sound\sherpa-onnx\generated.wav" "How are you?"
--vits-model=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low/en_US-amy-low.onnx --vits-tokens=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low/tokens.txt --vits-data-dir=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low/espeak-ng-data --output-filename=./generated.wav --debug=1 "How are you?"

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

Fix Piper example path and align usage style

  • The Piper example likely needs espeak-ng-data; otherwise phonemizer lookup fails. Also, the help later advises using single quotes, but these examples use double quotes. Consider clarifying that Windows uses double quotes and Linux/macOS use single quotes, or provide both.

Apply this minimal correction to the data dir in the example:

- --piper-data-dir=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low --debug=1 --output-filename="D:\Sound\sherpa-onnx\generated.wav" "How are you?" 
+ --piper-data-dir=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low/espeak-ng-data --debug=1 --output-filename="D:\Sound\sherpa-onnx\generated.wav" "How are you?"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
--piper-model=D:/Sound/PiperModel/alan/en_GB-alan-medium.onnx --piper-model-config-file=D:/Sound/PiperModel/alan/en_GB-alan-medium.onnx.json --piper-data-dir=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low --debug=1 --output-filename="D:\Sound\sherpa-onnx\generated.wav" "How are you?"
--vits-model=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low/en_US-amy-low.onnx --vits-tokens=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low/tokens.txt --vits-data-dir=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low/espeak-ng-data --output-filename=./generated.wav --debug=1 "How are you?"
--piper-model=D:/Sound/PiperModel/alan/en_GB-alan-medium.onnx --piper-model-config-file=D:/Sound/PiperModel/alan/en_GB-alan-medium.onnx.json --piper-data-dir=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low/espeak-ng-data --debug=1 --output-filename="D:\Sound\sherpa-onnx\generated.wav" "How are you?"
--vits-model=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low/en_US-amy-low.onnx --vits-tokens=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low/tokens.txt --vits-data-dir=D:/Sound/sherpa-onnx/vits-piper-en_US-amy-low/espeak-ng-data --output-filename=./generated.wav --debug=1 "How are you?"
🤖 Prompt for AI Agents
In sherpa-onnx/csrc/sherpa-onnx-offline-tts.cc around lines 20-21, the Piper
example uses the vits data dir for --piper-data-dir and inconsistent quoting;
update the Piper example to point --piper-data-dir to the espeak-ng-data
subdirectory (same as VITS) so phonemizer lookups succeed, and align the
help/example quoting by either showing both variants or noting “use double
quotes on Windows, single quotes on Linux/macOS” so examples match platform
conventions.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 454cb20 and 6840abc.

📒 Files selected for processing (2)
  • sherpa-onnx/c-api/cxx-api.h (1 hunks)
  • sherpa-onnx/csrc/offline-tts-model-config.h (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • sherpa-onnx/c-api/cxx-api.h
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
  • GitHub Check: ubuntu-latest Debug shared tts-OFF
  • GitHub Check: ubuntu-latest Release shared tts-OFF
  • GitHub Check: ubuntu-24.04 3.12
  • GitHub Check: ubuntu-24.04 3.9
  • GitHub Check: ubuntu-24.04 3.11
  • GitHub Check: ubuntu-24.04 3.10
  • GitHub Check: ubuntu-24.04 3.8
  • GitHub Check: ubuntu-24.04 3.13
  • GitHub Check: Release shared-OFF tts-OFF
  • GitHub Check: Debug shared-OFF tts-ON
  • GitHub Check: Release shared-ON tts-ON
  • GitHub Check: Release shared-OFF tts-ON
  • GitHub Check: Release shared-ON tts-OFF
  • GitHub Check: Debug shared-OFF tts-OFF
  • GitHub Check: Debug shared-ON tts-ON
  • GitHub Check: Debug shared-ON tts-OFF
🔇 Additional comments (3)
sherpa-onnx/csrc/offline-tts-model-config.h (3)

13-13: Add Piper config header — LGTM

Consistent with existing model-config includes; required since OfflineTtsPiperModelConfig is a by-value member.


47-50: Initializer list order matches declaration — good

No -Wreorder risk; construction is deterministic.


26-26: piper wiring verified; confirm intentional ABI break

  • Wiring present: piper.Register(po) in sherpa-onnx/csrc/offline-tts-model-config.cc (~line 17); piper.Validate() is called when piper.model is non-empty (~lines 55–56); piper.ToString() is included in ToString (~line 73).
  • Action: adding this member changes the struct ABI — confirm this is an intended breaking change. If this header is part of a public ABI, bump SONAME/package version or document/notify consumers.

Comment on lines +39 to 41
const OfflineTtsPiperModelConfig &piper,
int32_t num_threads, bool debug,
const std::string &provider)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Constructor parameter order is source-breaking; add a back-compat overload

Existing call-sites passing (vits, matcha, kokoro, zipvoice, kitten, num_threads, debug, provider) will fail since piper was inserted before num_threads. Add an overload with the original signature that delegates, preserving source compatibility.

@@
   OfflineTtsModelConfig(const OfflineTtsVitsModelConfig &vits,
                         const OfflineTtsMatchaModelConfig &matcha,
                         const OfflineTtsKokoroModelConfig &kokoro,
                         const OfflineTtsZipvoiceModelConfig &zipvoice,
                         const OfflineTtsKittenModelConfig &kitten,
-                        const OfflineTtsPiperModelConfig &piper,
+                        const OfflineTtsPiperModelConfig &piper,
                         int32_t num_threads, bool debug,
                         const std::string &provider)
       : vits(vits),
         matcha(matcha),
         kokoro(kokoro),
         zipvoice(zipvoice),
         kitten(kitten),
         piper(piper),
         num_threads(num_threads),
         debug(debug),
         provider(provider) {}
+
+  // Back-compat overload: preserves the old parameter order.
+  OfflineTtsModelConfig(const OfflineTtsVitsModelConfig &vits,
+                        const OfflineTtsMatchaModelConfig &matcha,
+                        const OfflineTtsKokoroModelConfig &kokoro,
+                        const OfflineTtsZipvoiceModelConfig &zipvoice,
+                        const OfflineTtsKittenModelConfig &kitten,
+                        int32_t num_threads, bool debug,
+                        const std::string &provider)
+      : OfflineTtsModelConfig(vits, matcha, kokoro, zipvoice, kitten,
+                              OfflineTtsPiperModelConfig{}, num_threads, debug,
+                              provider) {}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const OfflineTtsPiperModelConfig &piper,
int32_t num_threads, bool debug,
const std::string &provider)
OfflineTtsModelConfig(const OfflineTtsVitsModelConfig &vits,
const OfflineTtsMatchaModelConfig &matcha,
const OfflineTtsKokoroModelConfig &kokoro,
const OfflineTtsZipvoiceModelConfig &zipvoice,
const OfflineTtsKittenModelConfig &kitten,
const OfflineTtsPiperModelConfig &piper,
int32_t num_threads, bool debug,
const std::string &provider)
: vits(vits),
matcha(matcha),
kokoro(kokoro),
zipvoice(zipvoice),
kitten(kitten),
piper(piper),
num_threads(num_threads),
debug(debug),
provider(provider) {}
// Back-compat overload: preserves the old parameter order.
OfflineTtsModelConfig(const OfflineTtsVitsModelConfig &vits,
const OfflineTtsMatchaModelConfig &matcha,
const OfflineTtsKokoroModelConfig &kokoro,
const OfflineTtsZipvoiceModelConfig &zipvoice,
const OfflineTtsKittenModelConfig &kitten,
int32_t num_threads, bool debug,
const std::string &provider)
: OfflineTtsModelConfig(vits, matcha, kokoro, zipvoice, kitten,
OfflineTtsPiperModelConfig{}, num_threads, debug,
provider) {}
🤖 Prompt for AI Agents
sherpa-onnx/csrc/offline-tts-model-config.h around lines 39-41: the constructor
parameter order changed by inserting 'piper' before 'num_threads', breaking
existing call-sites; add a backward-compatible overload that keeps the original
signature (i.e., without the 'piper' parameter in that position) and implement
it to delegate to the new constructor (construct or forward default/empty piper
as appropriate) so existing callers continue to work while new API remains
available.

@csukuangfj

Copy link
Copy Markdown
Collaborator

Thank you for your contribution!

But we have already supported Piper TTS models. You can find the doc at
https://k2-fsa.github.io/sherpa/onnx/tts/all/
and
https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/vits.html#vits-piper-en-us-libritts-r-medium-english-904-speakers

Piper TTS models are based on VITS, and we have already supported VITS models in sherpa-onnx.

@sienaiwun sienaiwun closed this Sep 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants