Run Supertonic 3 text-to-speech directly in Swift. Generate speech locally on macOS, iOS, Android, and Linux with 31 languages, ten voices, and language-agnostic na mode.
- CPU inference with audio returned as Float32 samples or a ready-to-save WAV.
- Zero Swift package dependencies. ONNX Runtime is included through a binary artifact on Apple platforms and bundled libraries on Linux.
- Automatic model download and caching, plus direct loading of bundled models for fully offline apps.
- An actor-based API that reuses model sessions and serializes synthesis safely.
Listen to the language samples.
The first Supertonic.load() downloads approximately 400 MB of model assets. Subsequent loads reuse the cache. Speech synthesis runs on the device; text stays local.
Apps can download the voices before the first playback and display byte progress:
let assets = try await ModelAssets.download { progress in
print("\(progress.downloadedBytes) / \(progress.totalBytes) bytes")
}
let synthesizer = try Supertonic(assets: assets)Cancel the calling task to stop the download. Completed files are retained and reused on retry; an interrupted file is downloaded again. ModelAssets.totalDownloadBytes, assets.downloadedBytes, and assets.isComplete let an app check the download size and local availability without making network requests. The progress callback can run off the main actor; dispatch UI updates to the main actor.
Requires Swift 6.0 or later. Add the package and its library product to Package.swift:
.package(
url: "https://github.com/onethreeeseven/supertonic.swift.git",
from: "0.6.1"
).target(
name: "YourApp",
dependencies: [
.product(name: "Supertonic", package: "supertonic.swift")
]
)Speak without saving an audio file:
import Supertonic
let synthesizer = try await Supertonic.load()
try await synthesizer.speak("안녕하세요.", in: .korean)speak synthesizes in memory, waits until playback finishes, and releases the audio. It accepts the same language, voice, and options as synthesize. A new playback on the same synthesizer replaces the previous one. Use await synthesizer.stop() to stop playback, or cancel the calling task to interrupt synthesis and playback. Playback interruption throws CancellationError.
On iOS, configure your app's AVAudioSession for its intended playback behavior. On Linux, playback uses the system ALSA library (libasound.so.2) and its default output device; synthesis itself does not require ALSA.
To save a WAV:
import Foundation
import Supertonic
let synthesizer = try await Supertonic.load()
let audio = try await synthesizer.synthesize(
"Hello world.",
in: .english
)
try audio.write(to: URL(fileURLWithPath: "hello.wav"))Keep the synthesizer around for later requests. audio.samples, audio.sampleRate, and audio.duration are available for custom playback or processing. WAV output is mono, 16-bit PCM at the model's sample rate, 44,100 Hz.
Each recording below is synthesized in one continuous model invocation. The supplied sentences fit in one chunk, as checked by the library's tests, so these samples contain no concatenation or inserted inter-chunk silence. Names, time notation, percentages, technical vocabulary, and punctuation test the model's pronunciation. Model output is used without pronunciation repairs.
The inline players use audio-only MP4 files with AAC audio. Download the MP4 samples and original WAV files, or reproduce the samples and inspect measurements. All samples use F1, high quality (16 steps), speed 1.05, and seed 42. Expand a transcript to read the exact input. If playback starts muted, use the player's speaker control.
en.mp4
Transcript
At 14:35, Elena Rossi said a 3.7% rise in Zürich's 2025 humidity readings does not prove causation.
ko.mp4
Transcript
14시 35분, 엘레나 로시 박사는 취리히의 2025년 습도 측정값이 3.7% 올랐지만 인과관계가 입증된 것은 아니라고 설명했습니다.
ja.mp4
Transcript
14時35分、エレナ・ロッシ博士は、チューリヒの2025年の湿度が3.7%上昇しても因果関係の証明にはならないと説明しました。
ar.mp4
Transcript
عند الساعة 14:35، أوضحت إلينا روسي أن ارتفاع الرطوبة بنسبة 3.7% في زيورخ عام 2025 لا يثبت علاقة سببية.
bg.mp4
Transcript
В 14:35 Елена Роси каза, че ръстът на влажността с 3,7% в Цюрих през 2025 година не доказва причинност.
cs.mp4
Transcript
Ve 14:35 Elena Rossi uvedla, že růst vlhkosti o 3,7% v Curychu v roce 2025 neprokazuje příčinnou souvislost.
da.mp4
Transcript
Klokken 14:35 sagde Elena Rossi, at 3,7% højere luftfugtighed i Zürich i 2025 ikke beviser årsagssammenhæng.
de.mp4
Transcript
Um 14:35 erklärte Elena Rossi, dass 3,7% mehr Feuchtigkeit in Zürich im Jahr 2025 keine Kausalität beweisen.
el.mp4
Transcript
Στις 14:35 η Έλενα Ρόσι είπε ότι η αύξηση υγρασίας κατά 3,7% στη Ζυρίχη το 2025 δεν αποδεικνύει αιτιότητα.
es.mp4
Transcript
A las 14:35, Elena Rossi dijo que un aumento del 3,7% de humedad en Zúrich en 2025 no prueba causalidad.
et.mp4
Transcript
Kell 14:35 ütles Elena Rossi, et Zürichi niiskuse 3,7% kasv aastal 2025 ei tõesta põhjuslikku seost.
fi.mp4
Transcript
Kello 14:35 Elena Rossi sanoi, ettei Zürichin kosteuden 3,7% nousu vuonna 2025 todista syy-yhteyttä.
fr.mp4
Transcript
À 14:35, Elena Rossi a expliqué qu'une hausse de 3,7% de l'humidité à Zurich en 2025 ne prouve aucune causalité.
hi.mp4
Transcript
14:35 बजे एलेना रोसी ने कहा कि ज़्यूरिख में 2025 में नमी की 3.7% वृद्धि कारण और परिणाम का संबंध सिद्ध नहीं करती।
hr.mp4
Transcript
U 14:35 Elena Rossi rekla je da porast vlage od 3,7% u Zürichu ne dokazuje uzročno-posljedičnu vezu.
hu.mp4
Transcript
14:35-kor Elena Rossi szerint a zürichi páratartalom 2025-ös, 3,7%-os emelkedése nem bizonyít oksági kapcsolatot.
id.mp4
Transcript
Pukul 14:35, Elena Rossi mengatakan kenaikan kelembapan 3,7% di Zürich pada 2025 tidak membuktikan sebab akibat.
it.mp4
Transcript
Alle 14:35, Elena Rossi ha spiegato che l'aumento del 3,7% di umidità a Zurigo nel 2025 non prova causalità.
lt.mp4
Transcript
14:35 Elena Rossi sakė, kad drėgmės padidėjimas 3,7% Ciuriche 2025 metais neįrodo priežastinio ryšio.
lv.mp4
Transcript
Pulksten 14:35 Elena Rosi sacīja, ka mitruma pieaugums par 3,7% Cīrihē nepierāda cēloņsakarību.
nl.mp4
Transcript
Om 14:35 zei Elena Rossi dat 3,7% meer luchtvochtigheid in Zürich in 2025 geen oorzakelijk verband bewijst.
pl.mp4
Transcript
O 14:35 Elena Rossi powiedziała, że wzrost wilgotności o 3,7% w Zurychu w 2025 roku nie dowodzi przyczynowości.
pt.mp4
Transcript
Às 14:35, Elena Rossi disse que o aumento de 3,7% da humidade em Zurique em 2025 não prova causalidade.
ro.mp4
Transcript
La 14:35, Elena Rossi a spus că o creștere a umidității cu 3,7% la Zürich în 2025 nu dovedește cauzalitate.
ru.mp4
Transcript
В 14:35 Елена Росси сказала, что рост влажности на 3,7% в Цюрихе за 2025 год не доказывает причинность.
sk.mp4
Transcript
O 14:35 Elena Rossi povedala, že nárast vlhkosti o 3,7% v Zürichu v roku 2025 nedokazuje príčinnú súvislosť.
sl.mp4
Transcript
Ob 14:35 je Elena Rossi dejala, da 3,7% več vlage v Zürichu leta 2025 ne dokazuje vzročne povezave.
sv.mp4
Transcript
Klockan 14:35 sade Elena Rossi att 3,7% högre luftfuktighet i Zürich år 2025 inte bevisar ett orsakssamband.
tr.mp4
Transcript
Saat 14:35'te Elena Rossi, Zürih'te 2025 yılında nemin %3,7 artmasının nedenselliği kanıtlamadığını söyledi.
uk.mp4
Transcript
О 14:35 Елена Россі сказала, що зростання вологості на 3,7% у Цюриху за 2025 рік не доводить причинності.
vi.mp4
Transcript
Lúc 14:35, Elena Rossi nói độ ẩm tăng 3,7% ở Zürich năm 2025 không chứng minh quan hệ nhân quả.
na.mp4
Transcript
14시 35분, 엘레나 로시 박사는 취리히의 습도가 3.7% 올랐다고 설명하며, a rise in humidity does not prove causation이라고 덧붙였습니다.
The same single-chunk English, Korean, and Japanese sentences were generated with an explicit tag and with na. Each generation time is the median of three runs on an Apple M4 Pro CPU with two inference threads after model warmup, excluding model loading and file encoding. These timings measure generation speed; listen to the recordings to compare pronunciation.
| Input | Explicit tag: generation / audio | na: generation / audio |
|---|---|---|
| English | 3.30s / 10.71s | 3.57s / 11.58s |
| Korean | 3.89s / 12.78s | 3.97s / 13.14s |
| Japanese | 4.04s / 12.63s | 3.63s / 12.06s |
en-na.mp4
Transcript
At 14:35, Elena Rossi said a 3.7% rise in Zürich's 2025 humidity readings does not prove causation.
ko-na.mp4
Transcript
14시 35분, 엘레나 로시 박사는 취리히의 2025년 습도 측정값이 3.7% 올랐지만 인과관계가 입증된 것은 아니라고 설명했습니다.
ja-na.mp4
Transcript
14時35分、エレナ・ロッシ博士は、チューリヒの2025年の湿度が3.7%上昇しても因果関係の証明にはならないと説明しました。
Use an explicit language when you know it:
let audio = try await synthesizer.synthesize(
"안녕하세요.",
in: .korean
)The language argument is required. When the language is unknown, pass .unspecified explicitly to send the model's na tag:
let audio = try await synthesizer.synthesize("안녕하세요. Hello world.", in: .unspecified)na lets the model process text without an explicit language selection. Pronunciation quality outside its training coverage is not guaranteed.
Parse a BCP 47 code and choose a fallback explicitly:
let language = SynthesisLanguage(languageCode: "ko-KR") ?? .unspecified
let audio = try await synthesizer.synthesize("안녕하세요.", in: language)Unknown codes return nil. Empty codes and na resolve to .unspecified.
| Code | Language | Code | Language |
|---|---|---|---|
en |
English | ko |
Korean |
ja |
Japanese | ar |
Arabic |
bg |
Bulgarian | cs |
Czech |
da |
Danish | de |
German |
el |
Greek | es |
Spanish |
et |
Estonian | fi |
Finnish |
fr |
French | hi |
Hindi |
hr |
Croatian | hu |
Hungarian |
id |
Indonesian | it |
Italian |
lt |
Lithuanian | lv |
Latvian |
nl |
Dutch | pl |
Polish |
pt |
Portuguese | ro |
Romanian |
ru |
Russian | sk |
Slovak |
sl |
Slovenian | sv |
Swedish |
tr |
Turkish | uk |
Ukrainian |
vi |
Vietnamese |
Choose from .female1 through .female5 or .male1 through .male5. These correspond to the model's F1–F5 and M1–M5 voice styles; Voice.allCases lists all ten. The default is .female1.
let audio = try await synthesizer.synthesize(
"Welcome back.",
in: .english,
with: .male1,
options: SynthesisOptions(
quality: .high,
speed: 1.1,
seed: 42
)
)| Option | Default | Behavior |
|---|---|---|
quality |
.balanced |
.fast: 4 denoising steps; .balanced: 8; .high: 16 |
speed |
1.05 |
Higher values speak faster; bounded to 0.5–2.0 |
seed |
Random | A fixed seed reproduces the initial noise for repeatable generation |
Non-finite speeds use the default. Empty text produces empty audio. Long text is grouped at sentence boundaries, with Korean and Japanese targeting 120 characters and other language tags targeting 300, as in the reference implementation. Sentences exceeding that target are split at whitespace; words, numbers, and unspaced runs stay intact. Separate chunks have a 0.3-second gap. Cancelling the calling task stops synthesis between chunks and denoising steps; an individual ONNX operation finishes before cancellation is checked.
Load a Supertonic 3 voice-style JSON once, then pass the voice to either speak or synthesize:
let voice = try CustomVoice(contentsOf: voiceURL)
try await synthesizer.speak("안녕하세요.", in: .korean, with: voice)
let audio = try await synthesizer.synthesize("Welcome back.", in: .english, with: voice)Use CustomVoice(data: jsonData) for data from a download or app resource. The initializer validates the tensor shapes, nested data, and finite values. The loaded voice owns its data, so the source file can be removed after loading. No extra model or dependency is needed.
Use a JSON exported for Supertonic 3, or one of its bundled voice_styles/*.json files. The JSON contains numeric voice features; it does not accept a recording or a natural-language voice description. Importing a profile enables local synthesis with that voice; creating a profile from a recording requires a separate voice-cloning pipeline.
Choose a persistent model directory when your app should retain downloaded assets outside the operating system's cache:
let synthesizer = try await Supertonic.load(from: modelDirectory)You can also download assets ahead of the first speech request:
let assets = try await ModelAssets.download(to: modelDirectory)
let synthesizer = try Supertonic(assets: assets)For an app bundle or an installation with no network access, distribute the model's onnx/ and voice_styles/ directories together with its license. Load those files directly:
let assets = ModelAssets(directory: bundledModelDirectory)
let synthesizer = try Supertonic(assets: assets)This initializer performs no network access. The directory should contain:
models/
├── LICENSE
├── onnx/
│ ├── tts.json
│ ├── unicode_indexer.json
│ ├── duration_predictor.onnx
│ ├── text_encoder.onnx
│ ├── vector_estimator.onnx
│ └── vocoder.onnx
└── voice_styles/
├── F1.json … F5.json
└── M1.json … M5.json
Downloads first use Hugging Face and automatically fall back to our preserved model release on network errors, unavailable files, or invalid download sizes. Both sources contain revision aafc6e32416a594460b32413efc49d7fe4ce6d46. The preserved release includes the original license, a source manifest, SHA-256 checksums, and supertonic-3-aafc6e32416a.tar.gz for offline installation. Extract the archive and pass its models/ directory to ModelAssets(directory:).
Concurrent requests within one process share a download, and retries reuse completed files. Each file is staged before installation. Validation checks HTTP status and expected file size; it does not verify cryptographic content hashes.
| Platform | Minimum | ONNX Runtime delivery |
|---|---|---|
| macOS | 14; Apple Silicon or Intel | Official XCFramework fetched by SwiftPM |
| iOS | 15; device and simulator | Official XCFramework linked into the app |
| Linux | x86_64 or ARM64; glibc 2.27+ | Shared libraries included as package resources |
| Android | API 28+ with the Swift Android SDK | Official ONNX Runtime Android AAR packaged in the app |
The inference runtime is ONNX Runtime 1.24.2. Apple builds link the system C++ and CoreML libraries. Linux builds load the bundled CPU runtime and require a glibc distribution. Both Linux architectures together add approximately 39 MB to a source checkout; Apple builds exclude those resources.
On Apple platforms and Linux, using the package requires no Python environment or separately installed ONNX Runtime. Model weights are distributed separately from the source repository.
Add the library to your Swift Android target and package ONNX Runtime in your Android app:
dependencies {
implementation("com.microsoft.onnxruntime:onnxruntime-android:1.24.2")
}The AAR supplies libonnxruntime.so for the device ABI. The Swift library loads it from the app's native library search path. speak uses Android's AAudio output directly and releases its audio stream when playback finishes or is cancelled. Synthesis and playback use the same Swift API as Apple platforms and Linux.
From a checkout of this repository:
swift run supertonic "Hello world." hello.wav en F1
swift run supertonic "안녕하세요." hello.wav na M1 /path/to/modelssupertonic <text> <output.wav> [language=na] [voice=F1] [model-directory]
swift test
SUPERTONIC_TEST_MODELS=/path/to/models swift testThe model-enabled tests synthesize speech for every language tag, including na, and check for finite, non-silent audio. CI runs unit tests, first-download generation, and model-enabled tests on macOS and Linux x86_64/ARM64. It also compiles the library for an iOS device target.
See CONTRIBUTING.md for development setup, testing, and pull request guidelines. Use the issue forms to report a bug or propose a feature.
Support maintenance, platform testing, and model preservation through GitHub Sponsors.
| Component | License |
|---|---|
| Swift code and inference adaptation | MIT |
| ONNX Runtime | MIT, with third-party notices |
| Supertonic 3 model weights | OpenRAIL-M |
Text preprocessing and the inference contract follow Supertone's reference implementation. Its copyright notice is retained. The model license is included in this repository and downloaded with the model assets.