streaming-vits 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abrar Ahmed
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,266 @@
1
+ Metadata-Version: 2.4
2
+ Name: streaming-vits
3
+ Version: 0.1.0
4
+ Summary: Streaming inference for VITS/piper TTS — a drop-in replacement for sherpa-onnx-offline-tts that starts playing before synthesis finishes
5
+ License: MIT
6
+ Keywords: tts,vits,piper,streaming,onnx,sherpa-onnx,speech
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: numpy>=1.21
16
+ Requires-Dist: onnx>=1.14
17
+ Requires-Dist: onnxruntime>=1.15
18
+ Provides-Extra: play
19
+ Requires-Dist: sounddevice>=0.4; extra == "play"
20
+ Provides-Extra: fast-phonemes
21
+ Requires-Dist: piper-phonemize>=1.1; extra == "fast-phonemes"
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=7; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # streaming-vits
27
+
28
+ Streaming inference for VITS / [piper](https://github.com/rhasspy/piper) TTS models.
29
+
30
+ Feed in a whole paragraph, get audio out incrementally — and the result is
31
+ **numerically identical** to a full-paragraph forward pass. No sentence
32
+ splitting, no prosody loss.
33
+
34
+ A drop-in replacement for `sherpa-onnx-offline-tts`: same model file, same flags,
35
+ same WAV. It just starts producing audio before synthesis finishes.
36
+
37
+ ```diff
38
+ - sherpa-onnx-offline-tts \
39
+ + streaming-vits-offline-tts \
40
+ --vits-model=./en_US-libritts_r-medium.onnx \
41
+ --vits-tokens=./tokens.txt \
42
+ --vits-data-dir=./espeak-ng-data \
43
+ --num-threads=4 --sid=0 \
44
+ --output-filename=./test-0.wav \
45
+ "The quick brown fox jumped skillfully over the lazy dog..."
46
+ ```
47
+
48
+ ```
49
+ Elapsed seconds: 1.012
50
+ Audio duration: 9.532 s
51
+ Real-time factor (RTF): 1.012/9.532 = 0.106
52
+ Time to first audio: 141 ms <- 9.5 s of audio, first sound in 141 ms
53
+ ```
54
+
55
+ Add `--play` to hear it live as it generates.
56
+
57
+ ## Why this is possible
58
+
59
+ VITS is not autoregressive, so streaming looks like it shouldn't work. But
60
+ `SynthesizerTrn.infer()` splits cleanly at the monotonic alignment:
61
+
62
+ | half | what it does | context needed | share of compute |
63
+ |---|---|---|---|
64
+ | **frontend** | text encoder → duration predictor → alignment → prior expansion → `z_p` | global, over text | ~2% |
65
+ | **decoder** | flow → HiFi-GAN | **finite receptive field over frames** | ~98% |
66
+
67
+ Everything that carries paragraph-level prosody lives in the cheap frontend,
68
+ which runs once on the whole input. The expensive half is a plain CNN over the
69
+ frame axis — no recurrence, no global attention — so it can be evaluated in
70
+ chunks, and with enough overlap context the chunks concatenate *exactly*.
71
+
72
+ This is the opposite trade-off from sentence chunking. Splitting text throws away
73
+ the global context that produces good prosody. Splitting in *frame* space costs
74
+ nothing, because the frame-space network cannot see beyond its receptive field
75
+ anyway.
76
+
77
+ One detail makes it exact rather than approximate: both `RandomNormalLike` nodes
78
+ (duration noise and prior noise) land in the frontend, so all sampling happens
79
+ once, for every frame, before any chunk is decoded. Two overlapping chunk decodes
80
+ necessarily see identical noise on the frames they share.
81
+
82
+ ## Install
83
+
84
+ ```bash
85
+ pip install streaming-vits
86
+
87
+ # phonemization backend (one of):
88
+ brew install espeak-ng # macOS
89
+ apt install espeak-ng # debian/ubuntu
90
+ pip install piper-phonemize # faster, no subprocess
91
+
92
+ pip install 'streaming-vits[play]' # optional: live playback
93
+ ```
94
+
95
+ Point it at any piper voice — the same tarballs sherpa-onnx uses:
96
+
97
+ ```bash
98
+ wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-piper-en_US-libritts_r-medium.tar.bz2
99
+ tar xf vits-piper-en_US-libritts_r-medium.tar.bz2
100
+ ```
101
+
102
+ The monolithic `.onnx` is split into a frontend and a decoder graph on first use
103
+ and cached next to the model. You don't have to do anything.
104
+
105
+ ## Calibrate for your device
106
+
107
+ The chunk schedule is the whole game for latency, and the right one depends on
108
+ how fast the device is. Run this once per device:
109
+
110
+ ```bash
111
+ streaming-vits calibrate \
112
+ --vits-model=./en_US-libritts_r-medium.onnx \
113
+ --vits-tokens=./tokens.txt \
114
+ --vits-data-dir=./espeak-ng-data \
115
+ --num-threads=4
116
+ ```
117
+
118
+ It measures two different things:
119
+
120
+ **Margins** are a property of the model's weights — how much context does the
121
+ decoder actually need before its output stops changing? Swept empirically,
122
+ because it is not guessable: trained weights use far more of their nominal
123
+ receptive field than an untrained network suggests.
124
+
125
+ ```
126
+ [1/3] receptive field (model property, device independent)
127
+ seed 1234 -- deterministic across runs
128
+ lookahead rel RMS err
129
+ 4 32.835%
130
+ 8 11.672%
131
+ 12 4.197%
132
+ 16 1.047%
133
+ 20 0.174%
134
+ 24 0.006%
135
+ -> left 34, right 28 (325 ms lookahead)
136
+ ```
137
+
138
+ The frontend sampling is pinned to a fixed seed for this sweep (`--seed`), so
139
+ repeated calibrations of the same model give the same margins. Normal synthesis
140
+ is unaffected and stays stochastic.
141
+
142
+ **Schedule** is a property of the device. An affine cost model
143
+ `decode(n) = α + β·(n + margins)` is fitted, then the schedule that reaches the
144
+ speaker soonest without ever starving playback is solved for:
145
+
146
+ ```
147
+ [2/3] decode cost on this device
148
+ fit: 1.26 ms/call + 0.442 ms/frame (a frame is 11.61 ms of audio)
149
+
150
+ predicted time to first audio 162 ms
151
+ steady-state RTF per chunk 0.052
152
+ survives a device 2.0x slower than this one
153
+ steady-state decode overhead 1.21x (the ramp costs more early)
154
+ ```
155
+
156
+ `--safety N` sets how much slower than measured to plan for; the prebuffer is
157
+ sized so headroom is a guarantee rather than an observation. Raise it on devices
158
+ with contended CPUs.
159
+
160
+ Margins are reproducible run to run, but the *schedule* is not quite: it depends
161
+ on wall-clock timings, which move with machine load. Calibrate on an otherwise
162
+ idle device, and treat `--safety` as the thing that absorbs the rest.
163
+
164
+ The profile is written next to the model and picked up automatically.
165
+
166
+ ## Results
167
+
168
+ `en_US-libritts_r-medium`, 4 threads, M-series Mac, 15.2 s utterance:
169
+
170
+ ```
171
+ non-streaming 0.701s RTF 0.046 <- you wait this long before any sound
172
+
173
+ time to first audio 0.163s (4.3x sooner than non-streaming)
174
+ total wall clock 1.301s RTF 0.085 (1.86x the work)
175
+ playback no underrun
176
+ ```
177
+
178
+ Time-to-first-audio for the non-streaming path scales with the length of the
179
+ paragraph. For the streamed path it is constant, so the gap widens the more you
180
+ ask it to say.
181
+
182
+ Equivalence, on the real weights:
183
+
184
+ ```
185
+ $ streaming-vits verify --vits-model=en_US-libritts_r-medium.onnx ...
186
+ max |streamed - full| = 8.00e-05 OK
187
+
188
+ $ streaming-vits verify --vits-model=es_MX-claude-high.onnx ...
189
+ max |streamed - full| = 1.79e-05 OK
190
+ ```
191
+
192
+ ## Commands
193
+
194
+ | command | what it does |
195
+ |---|---|
196
+ | `streaming-vits-offline-tts …` | drop-in for `sherpa-onnx-offline-tts` |
197
+ | `streaming-vits speak … --play` | synthesise, optionally play live |
198
+ | `streaming-vits calibrate …` | benchmark this device, write a profile |
199
+ | `streaming-vits bench …` | streamed vs non-streaming latency |
200
+ | `streaming-vits verify …` | check chunked output still equals a full decode |
201
+ | `streaming-vits info …` | show split graphs and active profile |
202
+ | `streaming-vits clear-cache …` | delete cached split graphs |
203
+
204
+ Accepted-and-ignored sherpa flags (`--vits-lexicon`, `--vits-dict-dir`,
205
+ `--tts-rule-fsts`, `--max-num-sentences`) print a note rather than failing, so
206
+ existing command lines keep working.
207
+
208
+ ## Python API
209
+
210
+ ```python
211
+ from streaming_vits import StreamingTTS
212
+
213
+ tts = StreamingTTS("en_US-libritts_r-medium.onnx", tokens_path="tokens.txt")
214
+
215
+ for chunk in tts.stream("Hello there. This plays before it has finished."):
216
+ speaker.write(chunk.audio) # float32, mono, tts.sample_rate
217
+ print(chunk.index, chunk.frames, chunk.ready_at)
218
+
219
+ audio = tts.synthesize("Same thing, one array.")
220
+ ```
221
+
222
+ ## Limits
223
+
224
+ - **Extra work.** Each chunk re-decodes its margin frames: ~1.2× in steady
225
+ state, more during the ramp. Cached-state streaming convolutions would cut
226
+ this to ~1.05× but are considerably more implementation.
227
+ - **TTFA is dominated by the frontend**, not by chunk size. On the numbers above
228
+ the frontend is ~130 ms of a 162 ms budget; smaller first chunks will not help.
229
+ Speeding up the stochastic duration predictor is the next lever.
230
+ - **Streaming text *in* is a separate problem.** The duration predictor needs the
231
+ whole utterance before any audio exists. If text arrives from an LLM token
232
+ stream you still chunk at clause level — but you can feed the previous clause
233
+ as context and discard its audio to soften the seam.
234
+ - **The bundled phonemizer is pragmatic.** It shells out to `espeak-ng` per
235
+ sentence and re-inserts punctuation, where piper drives libespeak-ng directly
236
+ and gets clause terminators back. Install `piper-phonemize` for the faithful
237
+ path. This does not affect the streaming claim — `verify` compares on identical
238
+ phoneme ids.
239
+ - Verified on two piper voices: `en_US-libritts_r-medium` (904 speakers) and
240
+ `es_MX-claude-high` (single speaker, different exporter version). Other VITS
241
+ exports should work if the graph has the same seam; `streaming-vits info` will
242
+ tell you before you rely on it.
243
+
244
+ ## How the split is found
245
+
246
+ `streaming_vits/graph.py` locates the seam structurally rather than by hardcoded
247
+ node names: find the last `RandomNormalLike` (the prior noise), walk forward to
248
+ the `Add` that forms `z_p`, then collect whatever the downstream subgraph still
249
+ needs from upstream. It also reads the decoder config back off the weights —
250
+ piper's `config.json` does not record it, and it varies by voice
251
+ (`libritts_r-medium` uses 3 upsample stages, `[8,8,4]`, not the 4 in the
252
+ reference VITS config).
253
+
254
+ Two things are deliberately not read from node names, because those vary between
255
+ piper exporter versions even when the weights do not: the upsample config comes
256
+ from `dec.ups.N.weight`, and the seam comes from graph topology. Single-speaker
257
+ voices have no speaker embedding and no `sid` input, so the decoder takes two
258
+ inputs instead of three; that is detected rather than configured.
259
+
260
+ `research/` holds the original proof of concept, including `verify_streaming.py`,
261
+ which proves the same equivalence against the reference PyTorch VITS
262
+ implementation independently of ONNX.
263
+
264
+ ## License
265
+
266
+ MIT
@@ -0,0 +1,241 @@
1
+ # streaming-vits
2
+
3
+ Streaming inference for VITS / [piper](https://github.com/rhasspy/piper) TTS models.
4
+
5
+ Feed in a whole paragraph, get audio out incrementally — and the result is
6
+ **numerically identical** to a full-paragraph forward pass. No sentence
7
+ splitting, no prosody loss.
8
+
9
+ A drop-in replacement for `sherpa-onnx-offline-tts`: same model file, same flags,
10
+ same WAV. It just starts producing audio before synthesis finishes.
11
+
12
+ ```diff
13
+ - sherpa-onnx-offline-tts \
14
+ + streaming-vits-offline-tts \
15
+ --vits-model=./en_US-libritts_r-medium.onnx \
16
+ --vits-tokens=./tokens.txt \
17
+ --vits-data-dir=./espeak-ng-data \
18
+ --num-threads=4 --sid=0 \
19
+ --output-filename=./test-0.wav \
20
+ "The quick brown fox jumped skillfully over the lazy dog..."
21
+ ```
22
+
23
+ ```
24
+ Elapsed seconds: 1.012
25
+ Audio duration: 9.532 s
26
+ Real-time factor (RTF): 1.012/9.532 = 0.106
27
+ Time to first audio: 141 ms <- 9.5 s of audio, first sound in 141 ms
28
+ ```
29
+
30
+ Add `--play` to hear it live as it generates.
31
+
32
+ ## Why this is possible
33
+
34
+ VITS is not autoregressive, so streaming looks like it shouldn't work. But
35
+ `SynthesizerTrn.infer()` splits cleanly at the monotonic alignment:
36
+
37
+ | half | what it does | context needed | share of compute |
38
+ |---|---|---|---|
39
+ | **frontend** | text encoder → duration predictor → alignment → prior expansion → `z_p` | global, over text | ~2% |
40
+ | **decoder** | flow → HiFi-GAN | **finite receptive field over frames** | ~98% |
41
+
42
+ Everything that carries paragraph-level prosody lives in the cheap frontend,
43
+ which runs once on the whole input. The expensive half is a plain CNN over the
44
+ frame axis — no recurrence, no global attention — so it can be evaluated in
45
+ chunks, and with enough overlap context the chunks concatenate *exactly*.
46
+
47
+ This is the opposite trade-off from sentence chunking. Splitting text throws away
48
+ the global context that produces good prosody. Splitting in *frame* space costs
49
+ nothing, because the frame-space network cannot see beyond its receptive field
50
+ anyway.
51
+
52
+ One detail makes it exact rather than approximate: both `RandomNormalLike` nodes
53
+ (duration noise and prior noise) land in the frontend, so all sampling happens
54
+ once, for every frame, before any chunk is decoded. Two overlapping chunk decodes
55
+ necessarily see identical noise on the frames they share.
56
+
57
+ ## Install
58
+
59
+ ```bash
60
+ pip install streaming-vits
61
+
62
+ # phonemization backend (one of):
63
+ brew install espeak-ng # macOS
64
+ apt install espeak-ng # debian/ubuntu
65
+ pip install piper-phonemize # faster, no subprocess
66
+
67
+ pip install 'streaming-vits[play]' # optional: live playback
68
+ ```
69
+
70
+ Point it at any piper voice — the same tarballs sherpa-onnx uses:
71
+
72
+ ```bash
73
+ wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-piper-en_US-libritts_r-medium.tar.bz2
74
+ tar xf vits-piper-en_US-libritts_r-medium.tar.bz2
75
+ ```
76
+
77
+ The monolithic `.onnx` is split into a frontend and a decoder graph on first use
78
+ and cached next to the model. You don't have to do anything.
79
+
80
+ ## Calibrate for your device
81
+
82
+ The chunk schedule is the whole game for latency, and the right one depends on
83
+ how fast the device is. Run this once per device:
84
+
85
+ ```bash
86
+ streaming-vits calibrate \
87
+ --vits-model=./en_US-libritts_r-medium.onnx \
88
+ --vits-tokens=./tokens.txt \
89
+ --vits-data-dir=./espeak-ng-data \
90
+ --num-threads=4
91
+ ```
92
+
93
+ It measures two different things:
94
+
95
+ **Margins** are a property of the model's weights — how much context does the
96
+ decoder actually need before its output stops changing? Swept empirically,
97
+ because it is not guessable: trained weights use far more of their nominal
98
+ receptive field than an untrained network suggests.
99
+
100
+ ```
101
+ [1/3] receptive field (model property, device independent)
102
+ seed 1234 -- deterministic across runs
103
+ lookahead rel RMS err
104
+ 4 32.835%
105
+ 8 11.672%
106
+ 12 4.197%
107
+ 16 1.047%
108
+ 20 0.174%
109
+ 24 0.006%
110
+ -> left 34, right 28 (325 ms lookahead)
111
+ ```
112
+
113
+ The frontend sampling is pinned to a fixed seed for this sweep (`--seed`), so
114
+ repeated calibrations of the same model give the same margins. Normal synthesis
115
+ is unaffected and stays stochastic.
116
+
117
+ **Schedule** is a property of the device. An affine cost model
118
+ `decode(n) = α + β·(n + margins)` is fitted, then the schedule that reaches the
119
+ speaker soonest without ever starving playback is solved for:
120
+
121
+ ```
122
+ [2/3] decode cost on this device
123
+ fit: 1.26 ms/call + 0.442 ms/frame (a frame is 11.61 ms of audio)
124
+
125
+ predicted time to first audio 162 ms
126
+ steady-state RTF per chunk 0.052
127
+ survives a device 2.0x slower than this one
128
+ steady-state decode overhead 1.21x (the ramp costs more early)
129
+ ```
130
+
131
+ `--safety N` sets how much slower than measured to plan for; the prebuffer is
132
+ sized so headroom is a guarantee rather than an observation. Raise it on devices
133
+ with contended CPUs.
134
+
135
+ Margins are reproducible run to run, but the *schedule* is not quite: it depends
136
+ on wall-clock timings, which move with machine load. Calibrate on an otherwise
137
+ idle device, and treat `--safety` as the thing that absorbs the rest.
138
+
139
+ The profile is written next to the model and picked up automatically.
140
+
141
+ ## Results
142
+
143
+ `en_US-libritts_r-medium`, 4 threads, M-series Mac, 15.2 s utterance:
144
+
145
+ ```
146
+ non-streaming 0.701s RTF 0.046 <- you wait this long before any sound
147
+
148
+ time to first audio 0.163s (4.3x sooner than non-streaming)
149
+ total wall clock 1.301s RTF 0.085 (1.86x the work)
150
+ playback no underrun
151
+ ```
152
+
153
+ Time-to-first-audio for the non-streaming path scales with the length of the
154
+ paragraph. For the streamed path it is constant, so the gap widens the more you
155
+ ask it to say.
156
+
157
+ Equivalence, on the real weights:
158
+
159
+ ```
160
+ $ streaming-vits verify --vits-model=en_US-libritts_r-medium.onnx ...
161
+ max |streamed - full| = 8.00e-05 OK
162
+
163
+ $ streaming-vits verify --vits-model=es_MX-claude-high.onnx ...
164
+ max |streamed - full| = 1.79e-05 OK
165
+ ```
166
+
167
+ ## Commands
168
+
169
+ | command | what it does |
170
+ |---|---|
171
+ | `streaming-vits-offline-tts …` | drop-in for `sherpa-onnx-offline-tts` |
172
+ | `streaming-vits speak … --play` | synthesise, optionally play live |
173
+ | `streaming-vits calibrate …` | benchmark this device, write a profile |
174
+ | `streaming-vits bench …` | streamed vs non-streaming latency |
175
+ | `streaming-vits verify …` | check chunked output still equals a full decode |
176
+ | `streaming-vits info …` | show split graphs and active profile |
177
+ | `streaming-vits clear-cache …` | delete cached split graphs |
178
+
179
+ Accepted-and-ignored sherpa flags (`--vits-lexicon`, `--vits-dict-dir`,
180
+ `--tts-rule-fsts`, `--max-num-sentences`) print a note rather than failing, so
181
+ existing command lines keep working.
182
+
183
+ ## Python API
184
+
185
+ ```python
186
+ from streaming_vits import StreamingTTS
187
+
188
+ tts = StreamingTTS("en_US-libritts_r-medium.onnx", tokens_path="tokens.txt")
189
+
190
+ for chunk in tts.stream("Hello there. This plays before it has finished."):
191
+ speaker.write(chunk.audio) # float32, mono, tts.sample_rate
192
+ print(chunk.index, chunk.frames, chunk.ready_at)
193
+
194
+ audio = tts.synthesize("Same thing, one array.")
195
+ ```
196
+
197
+ ## Limits
198
+
199
+ - **Extra work.** Each chunk re-decodes its margin frames: ~1.2× in steady
200
+ state, more during the ramp. Cached-state streaming convolutions would cut
201
+ this to ~1.05× but are considerably more implementation.
202
+ - **TTFA is dominated by the frontend**, not by chunk size. On the numbers above
203
+ the frontend is ~130 ms of a 162 ms budget; smaller first chunks will not help.
204
+ Speeding up the stochastic duration predictor is the next lever.
205
+ - **Streaming text *in* is a separate problem.** The duration predictor needs the
206
+ whole utterance before any audio exists. If text arrives from an LLM token
207
+ stream you still chunk at clause level — but you can feed the previous clause
208
+ as context and discard its audio to soften the seam.
209
+ - **The bundled phonemizer is pragmatic.** It shells out to `espeak-ng` per
210
+ sentence and re-inserts punctuation, where piper drives libespeak-ng directly
211
+ and gets clause terminators back. Install `piper-phonemize` for the faithful
212
+ path. This does not affect the streaming claim — `verify` compares on identical
213
+ phoneme ids.
214
+ - Verified on two piper voices: `en_US-libritts_r-medium` (904 speakers) and
215
+ `es_MX-claude-high` (single speaker, different exporter version). Other VITS
216
+ exports should work if the graph has the same seam; `streaming-vits info` will
217
+ tell you before you rely on it.
218
+
219
+ ## How the split is found
220
+
221
+ `streaming_vits/graph.py` locates the seam structurally rather than by hardcoded
222
+ node names: find the last `RandomNormalLike` (the prior noise), walk forward to
223
+ the `Add` that forms `z_p`, then collect whatever the downstream subgraph still
224
+ needs from upstream. It also reads the decoder config back off the weights —
225
+ piper's `config.json` does not record it, and it varies by voice
226
+ (`libritts_r-medium` uses 3 upsample stages, `[8,8,4]`, not the 4 in the
227
+ reference VITS config).
228
+
229
+ Two things are deliberately not read from node names, because those vary between
230
+ piper exporter versions even when the weights do not: the upsample config comes
231
+ from `dec.ups.N.weight`, and the seam comes from graph topology. Single-speaker
232
+ voices have no speaker embedding and no `sid` input, so the decoder takes two
233
+ inputs instead of three; that is detected rather than configured.
234
+
235
+ `research/` holds the original proof of concept, including `verify_streaming.py`,
236
+ which proves the same equivalence against the reference PyTorch VITS
237
+ implementation independently of ONNX.
238
+
239
+ ## License
240
+
241
+ MIT
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "streaming-vits"
7
+ version = "0.1.0"
8
+ description = "Streaming inference for VITS/piper TTS — a drop-in replacement for sherpa-onnx-offline-tts that starts playing before synthesis finishes"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ keywords = ["tts", "vits", "piper", "streaming", "onnx", "sherpa-onnx", "speech"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Multimedia :: Sound/Audio :: Speech",
19
+ ]
20
+ dependencies = [
21
+ "numpy>=1.21",
22
+ "onnx>=1.14",
23
+ "onnxruntime>=1.15",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ play = ["sounddevice>=0.4"]
28
+ fast-phonemes = ["piper-phonemize>=1.1"]
29
+ dev = ["pytest>=7"]
30
+
31
+ [project.scripts]
32
+ streaming-vits = "streaming_vits.cli:main"
33
+ streaming-vits-offline-tts = "streaming_vits.cli:main_offline_tts"
34
+
35
+ [tool.setuptools.packages.find]
36
+ include = ["streaming_vits*"]
37
+
38
+ [tool.pytest.ini_options]
39
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,17 @@
1
+ """Streaming inference for VITS / piper TTS models.
2
+
3
+ Whole paragraph in, audio out incrementally, numerically identical to a
4
+ full-paragraph forward pass -- no sentence splitting, no prosody loss.
5
+
6
+ from streaming_vits import StreamingTTS
7
+
8
+ tts = StreamingTTS("en_US-libritts_r-medium.onnx", tokens_path="tokens.txt")
9
+ for chunk in tts.stream("Hello there, this plays before it finishes."):
10
+ play(chunk.audio)
11
+ """
12
+
13
+ from .engine import Chunk, StreamingTTS
14
+ from .profile import Profile
15
+
16
+ __version__ = "0.1.0"
17
+ __all__ = ["StreamingTTS", "Chunk", "Profile", "__version__"]