Skip to content

feat: add Supertonic 3 text-to-speech model#1300

Open
IgorSwat wants to merge 5 commits into
mainfrom
@is/supertonic-tts
Open

feat: add Supertonic 3 text-to-speech model#1300
IgorSwat wants to merge 5 commits into
mainfrom
@is/supertonic-tts

Conversation

@IgorSwat

@IgorSwat IgorSwat commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds a full pipeline and API for Supertonic 3 model. It also refactors the native TTS code to make it better organized across 2 different text-to-speech pipelines.

Introduces a breaking change?

  • Yes
  • No

Type of change

  • Bug fix (change which fixes an issue)
  • New feature (change which adds functionality)
  • Documentation update (improves or adds clarity to existing documentation)
  • Other (chores, tests, code style improvements etc.)

Tested on

  • iOS
  • Android

Testing instructions

Run demo app in apps/speech.

Screenshots

Related issues

Checklist

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the documentation accordingly
  • My changes generate no new warnings

Additional notes

Needs to be tested on iOS. Also, the typescript API is in a somewhat PoC state and will most likely need some changes.

@IgorSwat IgorSwat requested a review from chmjkb July 6, 2026 08:45
@IgorSwat IgorSwat added the model Issues related to exporting, improving, fixing ML models label Jul 6, 2026
@IgorSwat IgorSwat requested review from barhanc and Copilot and removed request for Copilot July 6, 2026 09:41
@IgorSwat IgorSwat requested a review from msluszniak July 6, 2026 09:42

@barhanc barhanc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tested example app and it works fine.

Comment on lines -122 to -123
auto partition = partitioner_.partition(phonemes, context_.inputTokensLimit,
Partitioner::Mode::MIN_BREAKS);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The new TextPartitioner only supports a single partitioning logic (equivalent to Kokoro's old MIN_LATENCY mode), the offline/batch generate call originally used Partitioner::Mode::MIN_BREAKS to split the text. Is this deliberate or accidental regression?

Comment on lines +301 to +306
void Supertonic::unload() noexcept {
durationPredictor_.unload();
textEncoder_.unload();
vectorEstimator_.unload();
vocoder_.unload();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

unload() does not wait for or join the detached thread, and instead immediately frees the models, the background thread may still be running synthesize(...) or processing the loop. When the background thread invokes the next ExecuTorch model run, it will access freed/unloaded memory and crash the application instantly.

- `model` of type [`TextToSpeechModelSources`](../../06-api-reference/type-aliases/TextToSpeechModelSources.md) — model configuration.
- [`voiceSource`](../../06-api-reference/interfaces/TextToSpeechModelConfig.md#voicesource) of type [`ResourceSource`](../../06-api-reference/type-aliases/ResourceSource.md) — the voice tensor used for synthesis.
- [`phonemizerConfig`](../../06-api-reference/interfaces/TextToSpeechModelConfig.md#phonemizerconfig) of type [`TextToSpeechPhonemizerConfig`](../../06-api-reference/interfaces/TextToSpeechPhonemizerConfig.md) — Kokoro only: phonemizer configuration. Unused by Supertonic.
- [`lang`](../../06-api-reference/interfaces/TextToSpeechModelConfig.md#lang) of type [`TextToSpeechSupertonicLanguage`](../../06-api-reference/type-aliases/TextToSpeechSupertonicLanguage.md) — Supertonic only: default language token (e.g. `'en'`, `'na'`). Unused by Kokoro.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Language which is typed as sth strictly related to supertonic, generally incorrect abstraction. If adding this in a correct way causes huge rewrite please ignore it since we will introduce the model in rewritten flow in v0.10.0 either way.

Comment on lines +116 to +121
The module provides two ways to generate speech. The available parameters differ by model family:

| Parameter | Kokoro | Supertonic |
| ------------ | ------ | ---------- |
| `speed` | ✓ | |
| `phonemize` | ✓ | |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as above.

Comment on lines +97 to +101
// Kokoro — language-specific voice bundle:
// const tts = await TextToSpeechModule.fromModelName(
// models.text_to_speech.kokoro.en_us.heart(),
// (progress) => console.log(progress)
// );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we really want to keep this one for Kokoro as comment? Maybe drop it. Same for all such cases in this file.

Comment on lines +108 to +112
'Hello from ExecuTorch!',
1.0,
true,
8,
'en'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

maybe adding args names as inline commets next to the passed values might be more readable. Same for all such cases in this file.

Comment on lines +239 to +256
if constexpr (meta::SameAs<
Model, models::text_to_speech::supertonic::Supertonic>) {
addFunctions(
JSI_EXPORT_FUNCTION(ModelHostObject<Model>, unload, "unload"));
addFunctions(JSI_EXPORT_FUNCTION(ModelHostObject<Model>,
promiseHostFunction<&Model::stream>,
"stream"));
addFunctions(JSI_EXPORT_FUNCTION(
ModelHostObject<Model>, synchronousHostFunction<&Model::streamStop>,
"streamStop"));
addFunctions(JSI_EXPORT_FUNCTION(
ModelHostObject<Model>, synchronousHostFunction<&Model::streamInsert>,
"streamInsert"));
addFunctions(JSI_EXPORT_FUNCTION(
ModelHostObject<Model>, synchronousHostFunction<&Model::streamFlush>,
"streamFlush"));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These are exactly the same as in kokoro, you can just modify the if condition.

return {audio.begin(), audio.end()};
}

std::vector<float> Supertonic::generate(std::u32string input, float speed,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't this one const method?

*/
class TextEncoder : public BaseModel {
public:
TextEncoder(const std::string &modelSource,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

explicit

}
}

bool isAsciiSpace(char32_t c) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

noexcept

Comment on lines +155 to +163
for (char32_t c : s) {
if (isAsciiSpace(c)) {
if (!prevSpace) {
cleaned.push_back(U' ');
}
prevSpace = true;
} else {
cleaned.push_back(c);
prevSpace = false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
for (char32_t c : s) {
if (isAsciiSpace(c)) {
if (!prevSpace) {
cleaned.push_back(U' ');
}
prevSpace = true;
} else {
cleaned.push_back(c);
prevSpace = false;
for (char32_t c : s) {
prevSpace = isAsciiSpace(c);
if (prevSpace) {
if (!prevSpace) {
cleaned.push_back(U' ');
}
} else {
cleaned.push_back(c);


// Mask to silence invalid latent frames during the flow loop.
std::vector<float> latentMask(static_cast<size_t>(L), 0.0F);
std::fill(latentMask.begin(), latentMask.begin() + latentValid, 1.0F);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use ranges here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

model Issues related to exporting, improving, fixing ML models

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants