Spaces:
Running
speed slider now scales pred_dur (was playback-rate only)
Browse filesRe-exported v0.2 ONNX with speed as a third dynamic input
(float32[1]); KModel.forward_with_tokens divides
duration_proj output by speed before rounding, so 0.85x actually
allocates +18% frames per phoneme — consonants get room to breathe
instead of being crammed and played back slow.
Wiring:
- model.ts: synthesize(ids, refS, speed) passes a Tensor("float32",
[speed], [1]) as the 'speed' ONNX input.
- main.ts: pulls speedEl.value into the synthesize call.
audioEl.playbackRate stays at 1.0 — the buffer is already at the
requested pacing, so per-word timings line up with playback.
Side effect: re-export with torch 2.6 produced ~22% longer audio at
speed=1.0 than the previous TORCH26 export (Pearson r 0.992 vs old —
same audio shape, slight numerical drift from the InstanceNorm-train
warning). Net: at speed=0.85, perceived pacing comparable to old
slider=0.85 setup.
- assets/{index-C7URAddU.js → index--nWWJ2d_.js} +2 -2
- index.html +1 -1
- src/main.ts +6 -2
- src/model.ts +5 -4
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:453cab6ad3dfbb505afe87d21bd0fb4f905e8cf2295e5e17d7b4f1e2ae25a71b
|
| 3 |
+
size 19065680
|
|
@@ -17,7 +17,7 @@
|
|
| 17 |
rel="stylesheet"
|
| 18 |
/>
|
| 19 |
|
| 20 |
-
<script type="module" crossorigin src="/assets/index-
|
| 21 |
<link rel="stylesheet" crossorigin href="/assets/index-BdhRcMoO.css">
|
| 22 |
</head>
|
| 23 |
<body>
|
|
|
|
| 17 |
rel="stylesheet"
|
| 18 |
/>
|
| 19 |
|
| 20 |
+
<script type="module" crossorigin src="/assets/index--nWWJ2d_.js"></script>
|
| 21 |
<link rel="stylesheet" crossorigin href="/assets/index-BdhRcMoO.css">
|
| 22 |
</head>
|
| 23 |
<body>
|
|
@@ -499,7 +499,7 @@ async function onSynth() {
|
|
| 499 |
const idx = Math.min(tok.inputIds.length - 1, 509);
|
| 500 |
const refS = voicepack.slice(idx * 256, (idx + 1) * 256);
|
| 501 |
|
| 502 |
-
const result = await session.synthesize(tok.inputIds, refS);
|
| 503 |
sampleRate = result.sampleRate;
|
| 504 |
const PAD_SAMPLES = Math.floor(sampleRate * INTER_CHUNK_PAD_SEC);
|
| 505 |
const { phonemes, words } = buildTimings(tok, result.predDur, sampleRate);
|
|
@@ -568,7 +568,11 @@ async function onSynth() {
|
|
| 568 |
currentAudioURL = URL.createObjectURL(wavBlob);
|
| 569 |
audioEl.src = currentAudioURL;
|
| 570 |
audioEl.preservesPitch = true;
|
| 571 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 572 |
|
| 573 |
const phonemes = allPhonemes;
|
| 574 |
const words = allWords;
|
|
|
|
| 499 |
const idx = Math.min(tok.inputIds.length - 1, 509);
|
| 500 |
const refS = voicepack.slice(idx * 256, (idx + 1) * 256);
|
| 501 |
|
| 502 |
+
const result = await session.synthesize(tok.inputIds, refS, speed);
|
| 503 |
sampleRate = result.sampleRate;
|
| 504 |
const PAD_SAMPLES = Math.floor(sampleRate * INTER_CHUNK_PAD_SEC);
|
| 505 |
const { phonemes, words } = buildTimings(tok, result.predDur, sampleRate);
|
|
|
|
| 568 |
currentAudioURL = URL.createObjectURL(wavBlob);
|
| 569 |
audioEl.src = currentAudioURL;
|
| 570 |
audioEl.preservesPitch = true;
|
| 571 |
+
// Speed is now applied at the model level (passed to ONNX as a float32[1]
|
| 572 |
+
// input that scales the predictor's per-phoneme duration before rounding).
|
| 573 |
+
// The audio buffer is already at the requested pacing — playbackRate stays
|
| 574 |
+
// at 1.0 so per-word timings line up with what the user hears.
|
| 575 |
+
audioEl.playbackRate = 1.0;
|
| 576 |
|
| 577 |
const phonemes = allPhonemes;
|
| 578 |
const words = allWords;
|
|
@@ -78,12 +78,13 @@ export class KokoroSession {
|
|
| 78 |
return new KokoroSession(model, hasWebGpu);
|
| 79 |
}
|
| 80 |
|
| 81 |
-
async synthesize(inputIds: number[], refS: Float32Array): Promise<SynthesizeResult> {
|
| 82 |
const ids = BigInt64Array.from(inputIds.map(v => BigInt(v)));
|
| 83 |
-
const idTensor
|
| 84 |
-
const refTensor
|
|
|
|
| 85 |
|
| 86 |
-
const out = await this.model({ input_ids: idTensor, ref_s: refTensor });
|
| 87 |
|
| 88 |
// transformers.js wraps ort outputs as Tensor objects; unwrap .data
|
| 89 |
const audio = out.audio.data as Float32Array;
|
|
|
|
| 78 |
return new KokoroSession(model, hasWebGpu);
|
| 79 |
}
|
| 80 |
|
| 81 |
+
async synthesize(inputIds: number[], refS: Float32Array, speed: number = 1.0): Promise<SynthesizeResult> {
|
| 82 |
const ids = BigInt64Array.from(inputIds.map(v => BigInt(v)));
|
| 83 |
+
const idTensor = new Tensor("int64", ids, [1, ids.length]);
|
| 84 |
+
const refTensor = new Tensor("float32", refS, [1, 256]);
|
| 85 |
+
const speedTensor = new Tensor("float32", new Float32Array([speed]), [1]);
|
| 86 |
|
| 87 |
+
const out = await this.model({ input_ids: idTensor, ref_s: refTensor, speed: speedTensor });
|
| 88 |
|
| 89 |
// transformers.js wraps ort outputs as Tensor objects; unwrap .data
|
| 90 |
const audio = out.audio.data as Float32Array;
|