flawed-avatar 0.2.1
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.
- package/assets/animations/idle/Breathing Idle.fbx +0 -0
- package/assets/animations/idle/look away gesture.fbx +0 -0
- package/assets/animations/idle/weight shift.fbx +0 -0
- package/assets/animations/speaking/Agreeing.fbx +0 -0
- package/assets/animations/speaking/Talking (1).fbx +0 -0
- package/assets/animations/speaking/Talking (2).fbx +0 -0
- package/assets/animations/speaking/Talking (3).fbx +0 -0
- package/assets/animations/speaking/Talking.fbx +0 -0
- package/assets/animations/speaking/head nod yes.fbx +0 -0
- package/assets/animations/thinking/Thinking.fbx +0 -0
- package/assets/animations/thinking/thoughtful head shake.fbx +0 -0
- package/assets/animations/working/acknowledging.fbx +0 -0
- package/assets/animations/working/lengthy head nod.fbx +0 -0
- package/assets/icon.png +0 -0
- package/assets/models/CaptainLobster.vrm +0 -0
- package/assets/models/default-avatar.vrm +0 -0
- package/dist/chat-preload.cjs +87 -0
- package/dist/chat-renderer-bundle/chat-index.html +16 -0
- package/dist/chat-renderer-bundle/chat-renderer.js +355 -0
- package/dist/chat-renderer-bundle/styles/base.css +106 -0
- package/dist/chat-renderer-bundle/styles/chat.css +516 -0
- package/dist/chat-renderer-bundle/styles/components/button.css +221 -0
- package/dist/chat-renderer-bundle/styles/components/indicator.css +216 -0
- package/dist/chat-renderer-bundle/styles/components/input.css +139 -0
- package/dist/chat-renderer-bundle/styles/components/toast.css +204 -0
- package/dist/chat-renderer-bundle/styles/controls.css +279 -0
- package/dist/chat-renderer-bundle/styles/settings.css +310 -0
- package/dist/chat-renderer-bundle/styles/tokens.css +220 -0
- package/dist/chat-renderer-bundle/styles/utilities.css +349 -0
- package/dist/main/main/display-utils.d.ts +12 -0
- package/dist/main/main/display-utils.js +29 -0
- package/dist/main/main/gateway-client.d.ts +13 -0
- package/dist/main/main/gateway-client.js +265 -0
- package/dist/main/main/main.d.ts +1 -0
- package/dist/main/main/main.js +157 -0
- package/dist/main/main/persistence/chat-store.d.ts +8 -0
- package/dist/main/main/persistence/chat-store.js +110 -0
- package/dist/main/main/persistence/file-store.d.ts +17 -0
- package/dist/main/main/persistence/file-store.js +183 -0
- package/dist/main/main/persistence/index.d.ts +8 -0
- package/dist/main/main/persistence/index.js +8 -0
- package/dist/main/main/persistence/migrations.d.ts +23 -0
- package/dist/main/main/persistence/migrations.js +191 -0
- package/dist/main/main/persistence/settings-store.d.ts +32 -0
- package/dist/main/main/persistence/settings-store.js +174 -0
- package/dist/main/main/persistence/types.d.ts +72 -0
- package/dist/main/main/persistence/types.js +69 -0
- package/dist/main/main/settings-broadcast.d.ts +3 -0
- package/dist/main/main/settings-broadcast.js +9 -0
- package/dist/main/main/stdin-listener.d.ts +15 -0
- package/dist/main/main/stdin-listener.js +27 -0
- package/dist/main/main/tray.d.ts +3 -0
- package/dist/main/main/tray.js +59 -0
- package/dist/main/main/window-manager.d.ts +23 -0
- package/dist/main/main/window-manager.js +232 -0
- package/dist/main/main/window.d.ts +3 -0
- package/dist/main/main/window.js +528 -0
- package/dist/main/shared/config.d.ts +91 -0
- package/dist/main/shared/config.js +111 -0
- package/dist/main/shared/ipc-channels.d.ts +54 -0
- package/dist/main/shared/ipc-channels.js +68 -0
- package/dist/main/shared/types.d.ts +6 -0
- package/dist/main/shared/types.js +1 -0
- package/dist/preload.cjs +256 -0
- package/dist/renderer-bundle/index.html +63 -0
- package/dist/renderer-bundle/renderer.js +100734 -0
- package/dist/renderer-bundle/styles/base.css +106 -0
- package/dist/renderer-bundle/styles/chat.css +516 -0
- package/dist/renderer-bundle/styles/components/button.css +221 -0
- package/dist/renderer-bundle/styles/components/indicator.css +216 -0
- package/dist/renderer-bundle/styles/components/input.css +139 -0
- package/dist/renderer-bundle/styles/components/toast.css +204 -0
- package/dist/renderer-bundle/styles/controls.css +279 -0
- package/dist/renderer-bundle/styles/settings.css +310 -0
- package/dist/renderer-bundle/styles/tokens.css +220 -0
- package/dist/renderer-bundle/styles/utilities.css +349 -0
- package/index.ts +32 -0
- package/openclaw.plugin.json +22 -0
- package/package.json +45 -0
- package/src/electron-launcher.ts +63 -0
- package/src/main/chat-preload.cjs +87 -0
- package/src/main/display-utils.ts +39 -0
- package/src/main/gateway-client.ts +312 -0
- package/src/main/main.ts +169 -0
- package/src/main/persistence/chat-store.ts +143 -0
- package/src/main/persistence/file-store.ts +221 -0
- package/src/main/persistence/index.ts +69 -0
- package/src/main/persistence/migrations.ts +232 -0
- package/src/main/persistence/settings-store.ts +219 -0
- package/src/main/persistence/types.ts +107 -0
- package/src/main/preload.cjs +256 -0
- package/src/main/settings-broadcast.ts +13 -0
- package/src/main/settings-preload.cjs +153 -0
- package/src/main/stdin-listener.ts +34 -0
- package/src/main/tray.ts +65 -0
- package/src/main/window-manager.ts +298 -0
- package/src/main/window.ts +614 -0
- package/src/renderer/audio/audio-player.ts +161 -0
- package/src/renderer/audio/frequency-analyzer.ts +104 -0
- package/src/renderer/audio/index.ts +36 -0
- package/src/renderer/audio/kokoro-model-loader.ts +128 -0
- package/src/renderer/audio/kokoro-tts-service.ts +370 -0
- package/src/renderer/audio/lip-sync-profile.json +1 -0
- package/src/renderer/audio/phoneme-mapper.ts +120 -0
- package/src/renderer/audio/tts-controller.ts +344 -0
- package/src/renderer/audio/tts-service-factory.ts +75 -0
- package/src/renderer/audio/tts-service.ts +16 -0
- package/src/renderer/audio/types.ts +120 -0
- package/src/renderer/audio/web-speech-tts.ts +177 -0
- package/src/renderer/audio/wlipsync-analyzer.ts +145 -0
- package/src/renderer/avatar/animation-loader.ts +114 -0
- package/src/renderer/avatar/animator.ts +322 -0
- package/src/renderer/avatar/expressions.ts +165 -0
- package/src/renderer/avatar/eye-gaze.ts +255 -0
- package/src/renderer/avatar/eye-saccades.ts +133 -0
- package/src/renderer/avatar/hover-awareness.ts +125 -0
- package/src/renderer/avatar/ibl-enhancer.ts +163 -0
- package/src/renderer/avatar/lip-sync.ts +258 -0
- package/src/renderer/avatar/mixamo-retarget.ts +169 -0
- package/src/renderer/avatar/pixel-transparency.ts +65 -0
- package/src/renderer/avatar/scene.ts +70 -0
- package/src/renderer/avatar/spring-bones.ts +27 -0
- package/src/renderer/avatar/state-machine.ts +117 -0
- package/src/renderer/avatar/vrm-loader.ts +71 -0
- package/src/renderer/chat-window/chat-index.html +16 -0
- package/src/renderer/chat-window/chat-renderer.ts +28 -0
- package/src/renderer/index.html +63 -0
- package/src/renderer/renderer.ts +329 -0
- package/src/renderer/settings-window/settings-controls.ts +223 -0
- package/src/renderer/settings-window/settings-index.html +16 -0
- package/src/renderer/settings-window/settings-panel.ts +346 -0
- package/src/renderer/settings-window/settings-renderer.ts +5 -0
- package/src/renderer/styles/base.css +106 -0
- package/src/renderer/styles/chat.css +516 -0
- package/src/renderer/styles/components/button.css +221 -0
- package/src/renderer/styles/components/indicator.css +216 -0
- package/src/renderer/styles/components/input.css +139 -0
- package/src/renderer/styles/components/toast.css +204 -0
- package/src/renderer/styles/controls.css +279 -0
- package/src/renderer/styles/settings.css +310 -0
- package/src/renderer/styles/tokens.css +220 -0
- package/src/renderer/styles/utilities.css +349 -0
- package/src/renderer/types/avatar-bridge.d.ts +86 -0
- package/src/renderer/types/chat-bridge.d.ts +37 -0
- package/src/renderer/types/settings-bridge.d.ts +54 -0
- package/src/renderer/ui/chat-bubble.ts +435 -0
- package/src/renderer/ui/icons.ts +47 -0
- package/src/renderer/ui/typing-indicator.ts +41 -0
- package/src/service.ts +163 -0
- package/src/shared/config.ts +135 -0
- package/src/shared/ipc-channels.ts +81 -0
- package/src/shared/types.ts +7 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kokoro.js TTS service implementation.
|
|
3
|
+
* Uses local ONNX model for high-quality, offline text-to-speech.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TTSService, TTSEvents, TTSVoice, TTSEngineType } from "./types.js";
|
|
7
|
+
import { KOKORO_VOICES, KOKORO_DEFAULT_VOICE } from "./types.js";
|
|
8
|
+
import { createAudioPlayer, type AudioPlayer } from "./audio-player.js";
|
|
9
|
+
import { getKokoroLoader, disposeKokoroLoader } from "./kokoro-model-loader.js";
|
|
10
|
+
|
|
11
|
+
interface PendingSegment {
|
|
12
|
+
text: string;
|
|
13
|
+
cancelled: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface GeneratedAudio {
|
|
17
|
+
audio: Float32Array;
|
|
18
|
+
sampleRate: number;
|
|
19
|
+
text: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const MAX_PENDING_SEGMENTS = 50;
|
|
23
|
+
const MAX_SEGMENT_CHARS = 1000; // Larger chunks = fewer generation calls
|
|
24
|
+
const PREFETCH_COUNT = 3; // Pre-generate this many segments ahead
|
|
25
|
+
const MIN_BUFFER_BEFORE_PLAY = 2; // Wait for this many segments before starting playback
|
|
26
|
+
|
|
27
|
+
export function createKokoroTTSService(events: TTSEvents): TTSService {
|
|
28
|
+
let currentVoice = KOKORO_DEFAULT_VOICE;
|
|
29
|
+
let lastSpokenIndex = 0;
|
|
30
|
+
let speaking = false;
|
|
31
|
+
let disposed = false;
|
|
32
|
+
|
|
33
|
+
const pendingSegments: PendingSegment[] = [];
|
|
34
|
+
const readyAudio: GeneratedAudio[] = []; // Pre-generated audio ready to play
|
|
35
|
+
let isGenerating = false;
|
|
36
|
+
let isPlaying = false;
|
|
37
|
+
let audioPlayer: AudioPlayer | null = null;
|
|
38
|
+
|
|
39
|
+
function getPlayer(): AudioPlayer {
|
|
40
|
+
if (!audioPlayer) {
|
|
41
|
+
audioPlayer = createAudioPlayer({
|
|
42
|
+
onPlaybackStart: () => {
|
|
43
|
+
if (disposed) return;
|
|
44
|
+
speaking = true;
|
|
45
|
+
events.onStart();
|
|
46
|
+
},
|
|
47
|
+
onPlaybackEnd: () => {
|
|
48
|
+
if (disposed) return;
|
|
49
|
+
isPlaying = false;
|
|
50
|
+
// Play next ready audio immediately
|
|
51
|
+
playNext();
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return audioPlayer;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Fire estimated word boundary events based on audio duration.
|
|
60
|
+
* Kokoro doesn't provide word-level timestamps, so we estimate.
|
|
61
|
+
*/
|
|
62
|
+
function fireEstimatedBoundaryEvents(text: string, audioDurationSec: number): void {
|
|
63
|
+
if (disposed) return;
|
|
64
|
+
|
|
65
|
+
const words = text.match(/[\w'-]+/g) || [];
|
|
66
|
+
if (words.length === 0) return;
|
|
67
|
+
|
|
68
|
+
const totalChars = words.reduce((sum, w) => sum + w.length, 0);
|
|
69
|
+
let charOffset = 0;
|
|
70
|
+
let timeOffset = 0;
|
|
71
|
+
|
|
72
|
+
for (const word of words) {
|
|
73
|
+
const wordDuration = (word.length / totalChars) * audioDurationSec * 1000;
|
|
74
|
+
|
|
75
|
+
// Find char index in original text
|
|
76
|
+
const charIndex = text.indexOf(word, charOffset);
|
|
77
|
+
if (charIndex !== -1) {
|
|
78
|
+
charOffset = charIndex + word.length;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Schedule boundary event
|
|
82
|
+
const capturedWord = word;
|
|
83
|
+
const capturedCharIndex = charIndex !== -1 ? charIndex : charOffset;
|
|
84
|
+
const capturedTimeOffset = timeOffset;
|
|
85
|
+
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
if (disposed) return;
|
|
88
|
+
events.onBoundary(capturedWord, capturedCharIndex, capturedWord.length);
|
|
89
|
+
}, capturedTimeOffset);
|
|
90
|
+
|
|
91
|
+
timeOffset += wordDuration;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Split text into larger segments for fewer generation calls.
|
|
97
|
+
* Tries to split at paragraph/sentence boundaries when possible.
|
|
98
|
+
*/
|
|
99
|
+
function splitIntoSegments(text: string): string[] {
|
|
100
|
+
const trimmed = text.trim();
|
|
101
|
+
if (!trimmed) return [];
|
|
102
|
+
|
|
103
|
+
// If text is short enough, return as single segment
|
|
104
|
+
if (trimmed.length <= MAX_SEGMENT_CHARS) {
|
|
105
|
+
return [trimmed];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Split into paragraphs first, then sentences if needed
|
|
109
|
+
const segments: string[] = [];
|
|
110
|
+
const paragraphs = trimmed.split(/\n\n+/);
|
|
111
|
+
|
|
112
|
+
let currentChunk = "";
|
|
113
|
+
|
|
114
|
+
for (const para of paragraphs) {
|
|
115
|
+
const paraText = para.trim();
|
|
116
|
+
if (!paraText) continue;
|
|
117
|
+
|
|
118
|
+
// If adding this paragraph would exceed limit, save current and start new
|
|
119
|
+
if (currentChunk && (currentChunk.length + paraText.length + 2) > MAX_SEGMENT_CHARS) {
|
|
120
|
+
if (currentChunk.trim()) {
|
|
121
|
+
segments.push(currentChunk.trim());
|
|
122
|
+
}
|
|
123
|
+
currentChunk = paraText;
|
|
124
|
+
} else {
|
|
125
|
+
currentChunk = currentChunk ? currentChunk + "\n\n" + paraText : paraText;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Add remaining chunk
|
|
130
|
+
if (currentChunk.trim()) {
|
|
131
|
+
segments.push(currentChunk.trim());
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return segments;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Generate audio for the next pending segment and add to ready queue.
|
|
139
|
+
* Runs in background, keeps prefetching ahead.
|
|
140
|
+
*/
|
|
141
|
+
async function generateNext(): Promise<void> {
|
|
142
|
+
if (disposed || isGenerating) return;
|
|
143
|
+
|
|
144
|
+
// Check if we have enough pre-generated audio
|
|
145
|
+
if (readyAudio.length >= PREFETCH_COUNT) return;
|
|
146
|
+
|
|
147
|
+
// Find next non-cancelled segment
|
|
148
|
+
let segment: PendingSegment | undefined;
|
|
149
|
+
while (pendingSegments.length > 0) {
|
|
150
|
+
segment = pendingSegments.shift();
|
|
151
|
+
if (segment && !segment.cancelled) break;
|
|
152
|
+
segment = undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!segment) return; // No more segments to generate
|
|
156
|
+
|
|
157
|
+
isGenerating = true;
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const loader = getKokoroLoader();
|
|
161
|
+
const model = await loader.getModel();
|
|
162
|
+
|
|
163
|
+
if (disposed || segment.cancelled) {
|
|
164
|
+
isGenerating = false;
|
|
165
|
+
generateNext(); // Try next segment
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(`[Kokoro] Generating: "${segment.text.slice(0, 40)}..." (ready=${readyAudio.length}, pending=${pendingSegments.length})`);
|
|
170
|
+
|
|
171
|
+
const result = await model.generate(segment.text, { voice: currentVoice });
|
|
172
|
+
|
|
173
|
+
if (disposed || segment.cancelled) {
|
|
174
|
+
isGenerating = false;
|
|
175
|
+
generateNext();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Add to ready queue
|
|
180
|
+
readyAudio.push({
|
|
181
|
+
audio: result.audio,
|
|
182
|
+
sampleRate: result.sampling_rate,
|
|
183
|
+
text: segment.text,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
isGenerating = false;
|
|
187
|
+
|
|
188
|
+
// Start playback if not already playing
|
|
189
|
+
if (!isPlaying) {
|
|
190
|
+
playNext();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Continue prefetching
|
|
194
|
+
generateNext();
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error("[Kokoro] Generation error:", error);
|
|
197
|
+
events.onError(error instanceof Error ? error.message : "Kokoro generation failed");
|
|
198
|
+
isGenerating = false;
|
|
199
|
+
// Continue with next segment
|
|
200
|
+
generateNext();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Play the next ready audio segment.
|
|
206
|
+
*/
|
|
207
|
+
function playNext(): void {
|
|
208
|
+
if (disposed || isPlaying) return;
|
|
209
|
+
|
|
210
|
+
// If we haven't started playing yet, wait for minimum buffer
|
|
211
|
+
// (unless there's nothing more to generate)
|
|
212
|
+
const moreToGenerate = isGenerating || pendingSegments.length > 0;
|
|
213
|
+
if (!speaking && moreToGenerate && readyAudio.length < MIN_BUFFER_BEFORE_PLAY) {
|
|
214
|
+
// Wait for more buffer before starting
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const audio = readyAudio.shift();
|
|
219
|
+
|
|
220
|
+
if (!audio) {
|
|
221
|
+
// No ready audio - check if we're still generating
|
|
222
|
+
if (moreToGenerate) {
|
|
223
|
+
// Still generating, will be called again when audio is ready
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// All done
|
|
227
|
+
if (speaking) {
|
|
228
|
+
speaking = false;
|
|
229
|
+
events.onEnd();
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
isPlaying = true;
|
|
235
|
+
|
|
236
|
+
// Fire boundary events for this segment
|
|
237
|
+
const audioDurationSec = audio.audio.length / audio.sampleRate;
|
|
238
|
+
fireEstimatedBoundaryEvents(audio.text, audioDurationSec);
|
|
239
|
+
|
|
240
|
+
// Play audio
|
|
241
|
+
getPlayer().play(audio.audio, audio.sampleRate);
|
|
242
|
+
|
|
243
|
+
// Trigger more generation while playing
|
|
244
|
+
generateNext();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Queue text for speaking.
|
|
249
|
+
*/
|
|
250
|
+
function queueSegments(text: string): void {
|
|
251
|
+
if (disposed || !text.trim()) return;
|
|
252
|
+
|
|
253
|
+
const segments = splitIntoSegments(text);
|
|
254
|
+
|
|
255
|
+
// Limit pending segments to prevent memory issues
|
|
256
|
+
const availableSlots = MAX_PENDING_SEGMENTS - pendingSegments.length;
|
|
257
|
+
const toAdd = segments.slice(0, availableSlots);
|
|
258
|
+
|
|
259
|
+
for (const seg of toAdd) {
|
|
260
|
+
pendingSegments.push({ text: seg, cancelled: false });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Start generating (will also start playback when ready)
|
|
264
|
+
generateNext();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Cancel all pending and current speech.
|
|
269
|
+
*/
|
|
270
|
+
function cancelAll(): void {
|
|
271
|
+
// Mark all pending as cancelled
|
|
272
|
+
for (const segment of pendingSegments) {
|
|
273
|
+
segment.cancelled = true;
|
|
274
|
+
}
|
|
275
|
+
pendingSegments.length = 0;
|
|
276
|
+
|
|
277
|
+
// Clear ready audio queue
|
|
278
|
+
readyAudio.length = 0;
|
|
279
|
+
|
|
280
|
+
// Stop current playback
|
|
281
|
+
if (audioPlayer) {
|
|
282
|
+
audioPlayer.stop();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
speaking = false;
|
|
286
|
+
isPlaying = false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
speak(text: string): void {
|
|
291
|
+
if (disposed || !text.trim()) return;
|
|
292
|
+
|
|
293
|
+
// Cancel current and queue new
|
|
294
|
+
cancelAll();
|
|
295
|
+
queueSegments(text);
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
speakDelta(fullText: string): void {
|
|
299
|
+
if (disposed) return;
|
|
300
|
+
|
|
301
|
+
// Only speak the new portion
|
|
302
|
+
if (fullText.length <= lastSpokenIndex) return;
|
|
303
|
+
|
|
304
|
+
const newText = fullText.slice(lastSpokenIndex).trim();
|
|
305
|
+
if (!newText) return;
|
|
306
|
+
|
|
307
|
+
// Update tracking
|
|
308
|
+
lastSpokenIndex = fullText.length;
|
|
309
|
+
|
|
310
|
+
// Queue new segments
|
|
311
|
+
queueSegments(newText);
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
cancel(): void {
|
|
315
|
+
cancelAll();
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
isSpeaking(): boolean {
|
|
319
|
+
return speaking || isPlaying || isGenerating || readyAudio.length > 0 || pendingSegments.length > 0;
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
resetSpokenIndex(): void {
|
|
323
|
+
lastSpokenIndex = 0;
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
dispose(): void {
|
|
327
|
+
disposed = true;
|
|
328
|
+
cancelAll();
|
|
329
|
+
|
|
330
|
+
if (audioPlayer) {
|
|
331
|
+
audioPlayer.dispose();
|
|
332
|
+
audioPlayer = null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Note: We don't dispose the model loader here as it's shared
|
|
336
|
+
// Call disposeKokoroLoader() separately when app shuts down
|
|
337
|
+
|
|
338
|
+
lastSpokenIndex = 0;
|
|
339
|
+
speaking = false;
|
|
340
|
+
isPlaying = false;
|
|
341
|
+
isGenerating = false;
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
getEngineType(): TTSEngineType {
|
|
345
|
+
return "kokoro";
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
getVoices(): TTSVoice[] {
|
|
349
|
+
return KOKORO_VOICES;
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
setVoice(voiceId: string): void {
|
|
353
|
+
const voice = KOKORO_VOICES.find((v) => v.id === voiceId);
|
|
354
|
+
if (voice) {
|
|
355
|
+
currentVoice = voiceId;
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
getCurrentVoice(): string | null {
|
|
360
|
+
return currentVoice;
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
getAudioPlayer(): AudioPlayer | null {
|
|
364
|
+
return audioPlayer;
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Re-export for cleanup
|
|
370
|
+
export { disposeKokoroLoader };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "jsonPath": "/home/steamvr/projects/wLipSync/www/profile.json", "mfccNum": 12, "mfccDataCount": 12, "melFilterBankChannels": 30, "targetSampleRate": 16000, "sampleCount": 1024, "useStandardization": false, "compareMethod": 2, "mfccs": [{ "name": "A", "mfccCalibrationDataList": [{ "array": [94.40318298339844, 0.32245922088623049, -65.5116195678711, -29.537851333618165, 4.888294219970703, 14.523965835571289, -32.6411247253418, 1.6505765914916993, -9.960077285766602, -5.7025322914123539, -11.886154174804688, -24.35236358642578] }, { "array": [94.40318298339844, 0.32245922088623049, -65.5116195678711, -29.537851333618165, 4.888294219970703, 14.523965835571289, -32.6411247253418, 1.6505765914916993, -9.960077285766602, -5.7025322914123539, -11.886154174804688, -24.35236358642578] }, { "array": [102.16287231445313, -3.3587560653686525, -65.58428192138672, -25.24440574645996, 3.224522590637207, 12.005892753601075, -29.293079376220704, 0.6378564834594727, -10.817683219909668, -1.3263540267944337, -14.543159484863282, -24.169780731201173] }, { "array": [99.35592651367188, -3.681424140930176, -65.20439910888672, -23.45950698852539, 6.205645561218262, 14.96288013458252, -29.882709503173829, 0.6733551025390625, -7.077619552612305, -3.5570802688598635, -14.427347183227539, -23.340003967285158] }, { "array": [99.35592651367188, -3.681424140930176, -65.20439910888672, -23.45950698852539, 6.205645561218262, 14.96288013458252, -29.882709503173829, 0.6733551025390625, -7.077619552612305, -3.5570802688598635, -14.427347183227539, -23.340003967285158] }, { "array": [104.24951171875, -2.8328847885131838, -66.59016418457031, -22.962886810302736, 5.519782066345215, 16.50394058227539, -32.338768005371097, 5.820473670959473, -10.59586238861084, -2.7398462295532228, -12.5281400680542, -24.459365844726564] }, { "array": [104.24951171875, -2.8328847885131838, -66.59016418457031, -22.962886810302736, 5.519782066345215, 16.50394058227539, -32.338768005371097, 5.820473670959473, -10.59586238861084, -2.7398462295532228, -12.5281400680542, -24.459365844726564] }, { "array": [95.58644104003906, -5.775191307067871, -61.220008850097659, -24.658382415771486, 4.4112701416015629, 13.673284530639649, -25.223039627075197, -2.0546646118164064, -6.887641906738281, -5.683987617492676, -11.20918083190918, -23.215322494506837] }, { "array": [98.24864196777344, -3.8367862701416017, -62.34006118774414, -24.563793182373048, 4.608433723449707, 16.228965759277345, -28.992279052734376, 2.1237001419067385, -9.07174015045166, -4.581008434295654, -10.662440299987793, -26.19581413269043] }, { "array": [102.7921142578125, -4.580304145812988, -62.531837463378909, -26.292770385742189, 7.911410331726074, 17.136384963989259, -31.118263244628908, 5.196089744567871, -10.010396957397461, -0.8527965545654297, -12.346561431884766, -23.580944061279298] }, { "array": [102.12345123291016, -1.5254135131835938, -62.21220397949219, -26.728734970092775, 10.62057876586914, 16.918357849121095, -28.815664291381837, 3.6714258193969728, -9.673786163330079, -0.7385025024414063, -9.717185020446778, -27.09702491760254] }, { "array": [102.12345123291016, -1.5254135131835938, -62.21220397949219, -26.728734970092775, 10.62057876586914, 16.918357849121095, -28.815664291381837, 3.6714258193969728, -9.673786163330079, -0.7385025024414063, -9.717185020446778, -27.09702491760254] }] }, { "name": "I", "mfccCalibrationDataList": [{ "array": [14.441835403442383, 52.400115966796878, 57.377838134765628, -33.70046615600586, -12.751934051513672, -15.709930419921875, -41.381065368652347, -12.190519332885743, -2.863154411315918, -8.727733612060547, 2.6656012535095217, 1.4855976104736329] }, { "array": [14.441835403442383, 52.400115966796878, 57.377838134765628, -33.70046615600586, -12.751934051513672, -15.709930419921875, -41.381065368652347, -12.190519332885743, -2.863154411315918, -8.727733612060547, 2.6656012535095217, 1.4855976104736329] }, { "array": [15.294279098510743, 50.07628631591797, 57.262847900390628, -31.748844146728517, -13.642471313476563, -13.48408031463623, -41.535011291503909, -16.863862991333009, -1.739903450012207, -9.32723331451416, 8.31618881225586, -1.779850959777832] }, { "array": [15.685098648071289, 49.68647766113281, 58.447113037109378, -32.513519287109378, -16.664287567138673, -13.78364372253418, -40.48309326171875, -16.04582405090332, -3.5356569290161135, -8.654275894165039, 10.645575523376465, -2.556441307067871] }, { "array": [15.685098648071289, 49.68647766113281, 58.447113037109378, -32.513519287109378, -16.664287567138673, -13.78364372253418, -40.48309326171875, -16.04582405090332, -3.5356569290161135, -8.654275894165039, 10.645575523376465, -2.556441307067871] }, { "array": [15.685098648071289, 49.68647766113281, 58.447113037109378, -32.513519287109378, -16.664287567138673, -13.78364372253418, -40.48309326171875, -16.04582405090332, -3.5356569290161135, -8.654275894165039, 10.645575523376465, -2.556441307067871] }, { "array": [15.225826263427735, 43.872196197509769, 52.844512939453128, -32.51786804199219, -17.806241989135743, -10.609650611877442, -40.13084411621094, -11.58648681640625, -5.082568168640137, -14.396997451782227, 6.896979331970215, -0.785430908203125] }, { "array": [15.571629524230957, 48.634883880615237, 59.5339469909668, -34.59955596923828, -21.959871292114259, -10.298498153686524, -39.11286926269531, -11.998537063598633, -8.433327674865723, -10.80599594116211, 8.789299011230469, -1.884697437286377] }, { "array": [15.571629524230957, 48.634883880615237, 59.5339469909668, -34.59955596923828, -21.959871292114259, -10.298498153686524, -39.11286926269531, -11.998537063598633, -8.433327674865723, -10.80599594116211, 8.789299011230469, -1.884697437286377] }, { "array": [15.571629524230957, 48.634883880615237, 59.5339469909668, -34.59955596923828, -21.959871292114259, -10.298498153686524, -39.11286926269531, -11.998537063598633, -8.433327674865723, -10.80599594116211, 8.789299011230469, -1.884697437286377] }, { "array": [19.23290252685547, 47.433998107910159, 54.90937423706055, -33.7783203125, -13.836353302001954, -5.141571044921875, -39.34584045410156, -13.409493446350098, -4.945652008056641, -12.960502624511719, 12.210061073303223, 0.5807018280029297] }, { "array": [19.124774932861329, 46.46723937988281, 53.41281509399414, -34.65093994140625, -18.181049346923829, -7.733134746551514, -45.67931365966797, -10.64135456085205, -2.624391555786133, -15.708955764770508, 7.3649187088012699, -5.627689361572266] }] }, { "name": "U", "mfccCalibrationDataList": [{ "array": [83.38372802734375, 42.39790725708008, 27.812450408935548, 10.696150779724121, -13.612553596496582, -32.487091064453128, -35.2574348449707, -6.425739288330078, -4.214997291564941, -6.896385669708252, -3.49631404876709, 4.997060775756836] }, { "array": [103.65653228759766, 38.661563873291019, 30.985050201416017, 17.432451248168947, -14.383820533752442, -39.810001373291019, -39.63761901855469, 1.2333955764770508, -4.217883110046387, -3.005303382873535, -6.272947311401367, 4.751875877380371] }, { "array": [103.44242095947266, 45.238216400146487, 27.622669219970704, 18.682138442993165, -16.854982376098634, -39.85029602050781, -34.15940856933594, -2.7482595443725588, -3.7410573959350588, -1.0625238418579102, -4.215768814086914, 6.514510154724121] }, { "array": [103.44242095947266, 45.238216400146487, 27.622669219970704, 18.682138442993165, -16.854982376098634, -39.85029602050781, -34.15940856933594, -2.7482595443725588, -3.7410573959350588, -1.0625238418579102, -4.215768814086914, 6.514510154724121] }, { "array": [99.99739837646485, 39.48998260498047, 26.057384490966798, 23.26814079284668, -16.25522804260254, -38.15496063232422, -35.70051574707031, -1.2821111679077149, -2.3941946029663088, -0.15543842315673829, -4.757769584655762, 2.198577880859375] }, { "array": [99.99739837646485, 39.48998260498047, 26.057384490966798, 23.26814079284668, -16.25522804260254, -38.15496063232422, -35.70051574707031, -1.2821111679077149, -2.3941946029663088, -0.15543842315673829, -4.757769584655762, 2.198577880859375] }, { "array": [107.09538269042969, 37.704010009765628, 17.795482635498048, 21.882326126098634, -14.739266395568848, -36.407527923583987, -37.95854949951172, -1.4393510818481446, -1.9593324661254883, -0.7294750213623047, -7.93386173248291, 3.9560585021972658] }, { "array": [103.5069351196289, 35.08988952636719, 23.21630859375, 23.947580337524415, -14.157055854797364, -38.546836853027347, -39.75208282470703, 0.516876220703125, -2.715259552001953, -4.0768208503723148, -4.716378211975098, 4.662134170532227] }, { "array": [103.5069351196289, 35.08988952636719, 23.21630859375, 23.947580337524415, -14.157055854797364, -38.546836853027347, -39.75208282470703, 0.516876220703125, -2.715259552001953, -4.0768208503723148, -4.716378211975098, 4.662134170532227] }, { "array": [103.5069351196289, 35.08988952636719, 23.21630859375, 23.947580337524415, -14.157055854797364, -38.546836853027347, -39.75208282470703, 0.516876220703125, -2.715259552001953, -4.0768208503723148, -4.716378211975098, 4.662134170532227] }, { "array": [98.43344116210938, 35.42580032348633, 29.2958984375, 24.73729133605957, -15.485936164855957, -44.676483154296878, -39.978858947753909, 0.5548343658447266, -2.2034664154052736, -3.485844612121582, -7.421210289001465, 6.30616569519043] }, { "array": [94.0390625, 36.81925582885742, 24.73573875427246, 22.579418182373048, -14.354126930236817, -37.92849349975586, -44.69046401977539, 0.7474861145019531, -3.3195743560791017, -3.9850082397460939, -5.991059303283691, 6.134122848510742] }] }, { "name": "E", "mfccCalibrationDataList": [{ "array": [60.52040481567383, 14.444153785705567, 50.91899108886719, 6.730878829956055, -58.12107467651367, -16.403745651245118, -25.244909286499025, 5.399906158447266, -7.63681697845459, -2.4964828491210939, 7.271292209625244, 1.7322711944580079] }, { "array": [59.327247619628909, 14.82846450805664, 51.402244567871097, 5.413976669311523, -55.603240966796878, -15.348665237426758, -25.923606872558595, 3.0006580352783205, -4.183259963989258, -3.4587841033935549, 7.941498756408691, 3.4499120712280275] }, { "array": [59.327247619628909, 14.82846450805664, 51.402244567871097, 5.413976669311523, -55.603240966796878, -15.348665237426758, -25.923606872558595, 3.0006580352783205, -4.183259963989258, -3.4587841033935549, 7.941498756408691, 3.4499120712280275] }, { "array": [64.61061096191406, 7.8438310623168949, 54.753726959228519, 11.154451370239258, -62.99680709838867, -10.397377967834473, -36.124359130859378, 12.57413387298584, -6.086113452911377, -3.032306671142578, 10.453157424926758, -0.00012826919555664063] }, { "array": [59.8906135559082, 13.646936416625977, 53.14240646362305, 11.346290588378907, -60.17724609375, -15.942718505859375, -29.547088623046876, 8.241331100463868, -6.8904523849487309, -3.6554131507873537, 14.714229583740235, -2.811859607696533] }, { "array": [53.77520751953125, 6.071747779846191, 47.870723724365237, 5.943275451660156, -51.11442184448242, -16.625276565551759, -24.842336654663087, 4.076478004455566, -8.835965156555176, -4.306196689605713, 13.907751083374024, -0.6555676460266113] }, { "array": [53.77520751953125, 6.071747779846191, 47.870723724365237, 5.943275451660156, -51.11442184448242, -16.625276565551759, -24.842336654663087, 4.076478004455566, -8.835965156555176, -4.306196689605713, 13.907751083374024, -0.6555676460266113] }, { "array": [53.77520751953125, 6.071747779846191, 47.870723724365237, 5.943275451660156, -51.11442184448242, -16.625276565551759, -24.842336654663087, 4.076478004455566, -8.835965156555176, -4.306196689605713, 13.907751083374024, -0.6555676460266113] }, { "array": [62.81553649902344, 6.203365325927734, 48.45057678222656, 8.571174621582032, -53.907508850097659, -16.376169204711915, -25.989578247070314, 5.736949920654297, -8.150140762329102, -5.895424842834473, 13.745902061462403, -4.22935676574707] }, { "array": [53.43303680419922, 7.019550323486328, 43.32084655761719, 7.639513969421387, -49.81471633911133, -18.708377838134767, -21.690540313720704, 0.34458446502685549, -8.689970970153809, -3.96992826461792, 11.29841423034668, -4.165286540985107] }, { "array": [56.29218673706055, 7.115049362182617, 47.741546630859378, 4.102975845336914, -50.46143341064453, -18.235626220703126, -22.557659149169923, 6.925202369689941, -13.170380592346192, -1.0300326347351075, 8.813325881958008, -3.2347850799560549] }, { "array": [56.29218673706055, 7.115049362182617, 47.741546630859378, 4.102975845336914, -50.46143341064453, -18.235626220703126, -22.557659149169923, 6.925202369689941, -13.170380592346192, -1.0300326347351075, 8.813325881958008, -3.2347850799560549] }] }, { "name": "O", "mfccCalibrationDataList": [{ "array": [108.12348937988281, 57.48288345336914, 0.23154354095458985, -31.144771575927736, -38.093109130859378, -18.33026885986328, -11.250101089477539, -0.7636222839355469, -1.371236801147461, -9.181392669677735, -1.6202507019042969, -7.501105308532715] }, { "array": [108.12348937988281, 57.48288345336914, 0.23154354095458985, -31.144771575927736, -38.093109130859378, -18.33026885986328, -11.250101089477539, -0.7636222839355469, -1.371236801147461, -9.181392669677735, -1.6202507019042969, -7.501105308532715] }, { "array": [120.48326873779297, 61.383270263671878, -2.6785545349121095, -32.3900146484375, -40.94635772705078, -12.681024551391602, -10.979912757873536, -0.7160100936889648, -2.9078426361083986, -12.300739288330079, 1.8719825744628907, -6.2853875160217289] }, { "array": [109.58128356933594, 58.46321105957031, 0.1479501724243164, -31.080230712890626, -34.73053741455078, -17.423336029052736, -10.221624374389649, 0.3863086700439453, -1.2579708099365235, -7.037452220916748, -2.894855499267578, -7.0971550941467289] }, { "array": [109.58128356933594, 58.46321105957031, 0.1479501724243164, -31.080230712890626, -34.73053741455078, -17.423336029052736, -10.221624374389649, 0.3863086700439453, -1.2579708099365235, -7.037452220916748, -2.894855499267578, -7.0971550941467289] }, { "array": [116.19681549072266, 60.3573112487793, 0.07350921630859375, -32.384727478027347, -34.47290802001953, -16.373615264892579, -14.695085525512696, 0.3102083206176758, 0.28844451904296877, -8.948881149291993, -3.104994773864746, -9.900266647338868] }, { "array": [116.19681549072266, 60.3573112487793, 0.07350921630859375, -32.384727478027347, -34.47290802001953, -16.373615264892579, -14.695085525512696, 0.3102083206176758, 0.28844451904296877, -8.948881149291993, -3.104994773864746, -9.900266647338868] }, { "array": [98.63448333740235, 48.661781311035159, 2.394181251525879, -28.785797119140626, -31.548860549926759, -18.37759017944336, -14.998208999633789, -1.8050260543823243, -2.018402099609375, -4.584748268127441, -5.160560607910156, -7.968695163726807] }, { "array": [124.25032043457031, 59.27610397338867, 2.7454710006713869, -36.72577667236328, -38.65552520751953, -3.116687774658203, -24.24558448791504, 0.5085678100585938, 2.3633852005004885, -10.51361083984375, 1.7447805404663087, -13.22685432434082] }, { "array": [87.89213562011719, 45.08750534057617, 4.292821884155273, -26.482845306396486, -30.386096954345704, -20.410654067993165, -11.817208290100098, -3.1270408630371095, -1.1370172500610352, -6.159217357635498, -3.454045295715332, -5.7265400886535648] }, { "array": [87.89213562011719, 45.08750534057617, 4.292821884155273, -26.482845306396486, -30.386096954345704, -20.410654067993165, -11.817208290100098, -3.1270408630371095, -1.1370172500610352, -6.159217357635498, -3.454045295715332, -5.7265400886535648] }, { "array": [87.89213562011719, 45.08750534057617, 4.292821884155273, -26.482845306396486, -30.386096954345704, -20.410654067993165, -11.817208290100098, -3.1270408630371095, -1.1370172500610352, -6.159217357635498, -3.454045295715332, -5.7265400886535648] }] }, { "name": "S", "mfccCalibrationDataList": [{ "array": [-94.29214477539063, 9.299236297607422, -21.6169376373291, 4.123956203460693, 1.3645498752593995, -2.339733839035034, 12.92388916015625, -7.490560531616211, 10.520170211791993, -5.4832611083984379, 2.1621110439300539, 2.6961774826049806] }, { "array": [-94.29214477539063, 9.299236297607422, -21.6169376373291, 4.123956203460693, 1.3645498752593995, -2.339733839035034, 12.92388916015625, -7.490560531616211, 10.520170211791993, -5.4832611083984379, 2.1621110439300539, 2.6961774826049806] }, { "array": [-92.13811492919922, 8.712703704833985, -17.194181442260743, 4.387561798095703, 2.288078784942627, -9.562786102294922, 9.854814529418946, -7.843216896057129, 8.969221115112305, -7.9670305252075199, 0.38271141052246096, 2.731431007385254] }, { "array": [-92.13811492919922, 8.712703704833985, -17.194181442260743, 4.387561798095703, 2.288078784942627, -9.562786102294922, 9.854814529418946, -7.843216896057129, 8.969221115112305, -7.9670305252075199, 0.38271141052246096, 2.731431007385254] }, { "array": [-90.87933349609375, 12.622742652893067, -12.25172233581543, 6.775156497955322, 6.892632007598877, -4.708589553833008, 10.558273315429688, -10.194192886352539, 10.907587051391602, 0.7259445190429688, 6.631969451904297, 1.2803831100463868] }, { "array": [-90.87933349609375, 12.622742652893067, -12.25172233581543, 6.775156497955322, 6.892632007598877, -4.708589553833008, 10.558273315429688, -10.194192886352539, 10.907587051391602, 0.7259445190429688, 6.631969451904297, 1.2803831100463868] }, { "array": [-94.86066436767578, 18.40726089477539, -5.981902599334717, 7.446142196655273, 11.998884201049805, 1.1316719055175782, 5.726372718811035, -9.411809921264649, 9.966836929321289, 0.6692547798156738, 3.0949947834014894, -0.5439543724060059] }, { "array": [-94.86066436767578, 18.40726089477539, -5.981902599334717, 7.446142196655273, 11.998884201049805, 1.1316719055175782, 5.726372718811035, -9.411809921264649, 9.966836929321289, 0.6692547798156738, 3.0949947834014894, -0.5439543724060059] }, { "array": [-94.86066436767578, 18.40726089477539, -5.981902599334717, 7.446142196655273, 11.998884201049805, 1.1316719055175782, 5.726372718811035, -9.411809921264649, 9.966836929321289, 0.6692547798156738, 3.0949947834014894, -0.5439543724060059] }, { "array": [-100.9681396484375, 15.002283096313477, -4.994745254516602, 0.22259855270385743, -0.15606117248535157, -1.8661277294158936, 1.5652005672454835, -13.30648422241211, 12.554527282714844, -2.990779399871826, 3.5510034561157228, 5.119507312774658] }, { "array": [-100.9681396484375, 15.002283096313477, -4.994745254516602, 0.22259855270385743, -0.15606117248535157, -1.8661277294158936, 1.5652005672454835, -13.30648422241211, 12.554527282714844, -2.990779399871826, 3.5510034561157228, 5.119507312774658] }, { "array": [-101.28047943115235, 14.962509155273438, -10.988410949707032, 3.6384878158569338, -1.4698257446289063, -4.758091449737549, -1.3547701835632325, -12.941855430603028, 3.3519961833953859, -5.5131611824035648, 9.386914253234864, 3.8310816287994386] }] }, { "name": "A", "mfccCalibrationDataList": [{ "array": [4.20286750793457, -73.493896484375, -24.746726989746095, -41.51460266113281, 36.48657989501953, -18.2531795501709, -42.99116516113281, 25.612823486328126, -18.336681365966798, -15.366691589355469, -4.867555618286133, -8.545194625854493] }, { "array": [6.645953178405762, -70.99688720703125, -23.49920082092285, -40.70307922363281, 35.09113311767578, -19.63579750061035, -41.851219177246097, 26.548370361328126, -20.361244201660158, -15.091900825500489, -5.332237243652344, -7.199653625488281] }, { "array": [6.645953178405762, -70.99688720703125, -23.49920082092285, -40.70307922363281, 35.09113311767578, -19.63579750061035, -41.851219177246097, 26.548370361328126, -20.361244201660158, -15.091900825500489, -5.332237243652344, -7.199653625488281] }, { "array": [3.4860363006591799, -73.30689239501953, -21.244325637817384, -38.725765228271487, 41.22803497314453, -15.975458145141602, -42.09079360961914, 27.367385864257814, -19.52243995666504, -16.397735595703126, -3.7795209884643556, -4.958502292633057] }, { "array": [0.09921550750732422, -70.22235107421875, -20.888980865478517, -33.53620910644531, 48.01523208618164, -13.365336418151856, -42.88488006591797, 26.451934814453126, -13.952343940734864, -16.65743064880371, -1.894343376159668, -1.8812918663024903] }, { "array": [0.09921550750732422, -70.22235107421875, -20.888980865478517, -33.53620910644531, 48.01523208618164, -13.365336418151856, -42.88488006591797, 26.451934814453126, -13.952343940734864, -16.65743064880371, -1.894343376159668, -1.8812918663024903] }, { "array": [0.09921550750732422, -70.22235107421875, -20.888980865478517, -33.53620910644531, 48.01523208618164, -13.365336418151856, -42.88488006591797, 26.451934814453126, -13.952343940734864, -16.65743064880371, -1.894343376159668, -1.8812918663024903] }, { "array": [-4.117016792297363, -72.00682830810547, -19.490331649780275, -34.48163986206055, 51.383995056152347, -9.989368438720704, -41.8690185546875, 23.824867248535158, -11.14547061920166, -14.500547409057618, -2.504335403442383, 1.0616645812988282] }, { "array": [-3.0772790908813478, -77.95940399169922, -21.169992446899415, -43.19057846069336, 47.594181060791019, -14.239681243896485, -43.05297088623047, 23.796558380126954, -16.033828735351564, -14.308491706848145, -4.90071964263916, -0.6597251892089844] }, { "array": [-3.0772790908813478, -77.95940399169922, -21.169992446899415, -43.19057846069336, 47.594181060791019, -14.239681243896485, -43.05297088623047, 23.796558380126954, -16.033828735351564, -14.308491706848145, -4.90071964263916, -0.6597251892089844] }, { "array": [0.27674388885498049, -74.16876220703125, -19.241043090820314, -43.69765853881836, 45.94886779785156, -15.777923583984375, -40.226318359375, 25.209468841552736, -19.91909408569336, -14.123311042785645, -6.749327659606934, -2.186051368713379] }, { "array": [1.313084602355957, -70.17343139648438, -20.149150848388673, -40.1507568359375, 43.281288146972659, -17.598236083984376, -39.989742279052737, 19.475574493408204, -18.73434066772461, -15.377893447875977, -3.6761083602905275, -2.3720903396606447] }] }, { "name": "I", "mfccCalibrationDataList": [{ "array": [-43.23860549926758, 15.659211158752442, 48.96119689941406, -84.8009033203125, -2.832998275756836, -25.101383209228517, -19.283388137817384, -1.3310365676879883, -3.9067935943603517, -5.5547566413879398, -24.206161499023439, 19.690078735351564] }, { "array": [-43.23860549926758, 15.659211158752442, 48.96119689941406, -84.8009033203125, -2.832998275756836, -25.101383209228517, -19.283388137817384, -1.3310365676879883, -3.9067935943603517, -5.5547566413879398, -24.206161499023439, 19.690078735351564] }, { "array": [-43.23860549926758, 15.659211158752442, 48.96119689941406, -84.8009033203125, -2.832998275756836, -25.101383209228517, -19.283388137817384, -1.3310365676879883, -3.9067935943603517, -5.5547566413879398, -24.206161499023439, 19.690078735351564] }, { "array": [-35.28649139404297, 11.94938850402832, 46.56689453125, -80.19161987304688, -5.332358360290527, -16.747013092041017, -17.014617919921876, -6.106320381164551, -3.5759763717651369, -11.297148704528809, -19.73563575744629, 19.95285415649414] }, { "array": [-35.28649139404297, 11.94938850402832, 46.56689453125, -80.19161987304688, -5.332358360290527, -16.747013092041017, -17.014617919921876, -6.106320381164551, -3.5759763717651369, -11.297148704528809, -19.73563575744629, 19.95285415649414] }, { "array": [-33.85117721557617, 12.748700141906739, 48.141944885253909, -75.80270385742188, -0.9702749252319336, -14.077031135559082, -15.911153793334961, -4.433165073394775, -4.00740909576416, -9.756240844726563, -20.910476684570314, 18.42697525024414] }, { "array": [-29.72378921508789, 13.039468765258789, 47.7448616027832, -74.11089324951172, 0.8275318145751953, -16.012189865112306, -17.36796760559082, -1.0376081466674805, -4.8292741775512699, -6.667880058288574, -23.82168960571289, 16.032718658447267] }, { "array": [-29.259418487548829, 11.963765144348145, 41.54622268676758, -75.67298889160156, -2.0329294204711916, -20.586360931396486, -21.125045776367189, 0.5504961013793945, -5.419882774353027, -6.314876556396484, -25.0130615234375, 14.262750625610352] }, { "array": [-29.259418487548829, 11.963765144348145, 41.54622268676758, -75.67298889160156, -2.0329294204711916, -20.586360931396486, -21.125045776367189, 0.5504961013793945, -5.419882774353027, -6.314876556396484, -25.0130615234375, 14.262750625610352] }, { "array": [-28.383831024169923, 13.103409767150879, 39.94292449951172, -81.08953857421875, -4.134577751159668, -21.590072631835939, -23.217021942138673, -0.3798789978027344, -5.0724334716796879, -5.94474983215332, -26.63843536376953, 16.777332305908204] }, { "array": [-30.048954010009767, 16.332015991210939, 44.49327850341797, -81.91828155517578, -4.171995162963867, -19.618621826171876, -20.534595489501954, -0.9673957824707031, -3.2188777923583986, -6.572293758392334, -26.59181785583496, 18.48187255859375] }, { "array": [-30.987979888916017, 14.425168991088868, 48.951114654541019, -82.33119201660156, -4.302616119384766, -17.642169952392579, -19.921981811523439, -1.7414522171020508, -1.7319145202636719, -6.870545387268066, -22.85688591003418, 19.475980758666993] }] }, { "name": "U", "mfccCalibrationDataList": [{ "array": [35.53300476074219, -11.371288299560547, 16.699298858642579, -39.32943344116211, 2.8931827545166017, -39.35669708251953, -22.81580924987793, -8.255973815917969, -7.3601884841918949, 3.866161346435547, -36.18340301513672, 0.44779300689697268] }, { "array": [34.92810821533203, -12.66100025177002, 19.551185607910158, -38.589942932128909, 1.5609407424926758, -42.781375885009769, -22.405025482177736, -4.380008220672607, -9.27183723449707, 5.952349662780762, -38.181243896484378, 1.4162702560424805] }, { "array": [34.508079528808597, -10.0599946975708, 18.88445281982422, -34.42189407348633, -0.8881950378417969, -42.52379608154297, -20.37147331237793, -0.6865062713623047, -8.89057731628418, 6.381434440612793, -34.816715240478519, 3.530543327331543] }, { "array": [34.508079528808597, -10.0599946975708, 18.88445281982422, -34.42189407348633, -0.8881950378417969, -42.52379608154297, -20.37147331237793, -0.6865062713623047, -8.89057731628418, 6.381434440612793, -34.816715240478519, 3.530543327331543] }, { "array": [37.634185791015628, -14.223724365234375, 21.262420654296876, -39.50825119018555, 0.31142520904541018, -43.201324462890628, -19.136680603027345, -0.7461652755737305, -8.253379821777344, 9.12716293334961, -33.483253479003909, 9.340311050415039] }, { "array": [38.28554916381836, -15.814170837402344, 19.503570556640626, -40.19694900512695, -0.7106151580810547, -45.373783111572269, -20.631587982177736, -2.2332963943481447, -6.609874725341797, 8.43967342376709, -33.560943603515628, 8.549976348876954] }, { "array": [38.28554916381836, -15.814170837402344, 19.503570556640626, -40.19694900512695, -0.7106151580810547, -45.373783111572269, -20.631587982177736, -2.2332963943481447, -6.609874725341797, 8.43967342376709, -33.560943603515628, 8.549976348876954] }, { "array": [36.15192413330078, -19.43944549560547, 19.22710609436035, -40.229862213134769, -1.7119722366333008, -44.58899688720703, -22.39651870727539, -4.6873579025268559, -5.184035301208496, 5.601207733154297, -35.313777923583987, 6.856324195861816] }, { "array": [33.697601318359378, -19.53946304321289, 18.151920318603517, -38.29360580444336, -1.9536991119384766, -39.185218811035159, -21.181903839111329, -8.092260360717774, -6.441320419311523, 3.8472461700439455, -32.4590950012207, 8.79825210571289] }, { "array": [31.768585205078126, -17.137840270996095, 15.726847648620606, -36.897605895996097, -3.8221397399902345, -35.492652893066409, -19.920631408691408, -10.421395301818848, -7.471479415893555, 1.7216854095458985, -25.726573944091798, 12.090950965881348] }, { "array": [31.768585205078126, -17.137840270996095, 15.726847648620606, -36.897605895996097, -3.8221397399902345, -35.492652893066409, -19.920631408691408, -10.421395301818848, -7.471479415893555, 1.7216854095458985, -25.726573944091798, 12.090950965881348] }, { "array": [32.6759033203125, -15.928326606750489, 16.0853271484375, -34.126686096191409, -6.689325332641602, -33.79350662231445, -19.242847442626954, -10.59890079498291, -10.992877006530762, -2.9957642555236818, -23.329811096191408, 14.231583595275879] }] }, { "name": "U", "mfccCalibrationDataList": [{ "array": [50.45172882080078, -7.723395347595215, 32.294891357421878, -11.293773651123047, -22.777332305908204, -36.17817687988281, -17.044910430908204, -3.7672786712646486, -14.233147621154786, -17.250513076782228, -19.240345001220704, -6.971443176269531] }, { "array": [53.59954071044922, -9.874463081359864, 35.237457275390628, -11.929043769836426, -23.915904998779298, -38.07780838012695, -15.71041202545166, -6.737283706665039, -12.06786060333252, -14.436643600463868, -20.401880264282228, -7.594654560089111] }, { "array": [57.36424255371094, -14.57245922088623, 35.10681915283203, -13.044787406921387, -24.196090698242189, -36.5896110534668, -16.161855697631837, -10.228910446166993, -10.43470287322998, -10.997936248779297, -20.92641830444336, -7.631929397583008] }, { "array": [57.65166473388672, -16.92430305480957, 33.022151947021487, -12.667181968688965, -21.69562530517578, -32.90369415283203, -14.275350570678711, -10.332601547241211, -10.544659614562989, -6.801647186279297, -22.47809410095215, -8.94782543182373] }, { "array": [49.60702133178711, -9.94367504119873, 25.04156494140625, -9.98995304107666, -21.814006805419923, -28.99759292602539, -16.792327880859376, -9.5930814743042, -11.523345947265625, -7.568509578704834, -20.688356399536134, -10.5545015335083] }, { "array": [53.433990478515628, -10.947863578796387, 30.426055908203126, -10.506410598754883, -22.997278213500978, -31.270254135131837, -16.90880584716797, -9.606002807617188, -10.785472869873047, -9.04238510131836, -22.82281494140625, -12.42250919342041] }, { "array": [57.11727523803711, -14.77857780456543, 33.16322326660156, -13.437588691711426, -25.450626373291017, -33.84945297241211, -17.6593017578125, -11.152002334594727, -12.203851699829102, -9.72322940826416, -26.653217315673829, -12.098143577575684] }, { "array": [58.65357208251953, -15.040183067321778, 32.92494583129883, -12.598053932189942, -24.03311538696289, -33.4146728515625, -15.565327644348145, -11.081777572631836, -10.47522258758545, -9.327695846557618, -28.43020248413086, -11.224303245544434] }, { "array": [53.888484954833987, -13.592279434204102, 31.6711368560791, -9.440587043762207, -23.316177368164064, -35.3663444519043, -19.26239776611328, -13.472862243652344, -11.312352180480957, -7.2335309982299809, -25.466888427734376, -8.924440383911133] }, { "array": [54.30769348144531, -13.315286636352539, 26.960655212402345, -11.682543754577637, -23.105655670166017, -32.507049560546878, -17.883333206176759, -10.961587905883789, -11.377249717712403, -7.610130786895752, -24.951541900634767, -9.38066577911377] }, { "array": [57.15829849243164, -13.771659851074219, 31.29568099975586, -9.493865966796875, -23.708837509155275, -33.85169982910156, -19.99985122680664, -12.555244445800782, -14.447962760925293, -7.608822822570801, -23.899951934814454, -6.755941867828369] }, { "array": [47.781307220458987, -14.279045104980469, 23.228328704833986, -14.190330505371094, -23.399112701416017, -34.95072555541992, -21.406070709228517, -10.15461254119873, -14.686234474182129, -9.932022094726563, -22.141719818115236, -8.354757308959961] }] }, { "name": "E", "mfccCalibrationDataList": [{ "array": [7.131360054016113, -36.049346923828128, 45.31159210205078, -45.00178527832031, -15.53347110748291, 1.3071203231811524, 2.57974910736084, -1.8762083053588868, -25.02313995361328, -22.275257110595704, -16.383546829223634, -17.868574142456056] }, { "array": [7.131360054016113, -36.049346923828128, 45.31159210205078, -45.00178527832031, -15.53347110748291, 1.3071203231811524, 2.57974910736084, -1.8762083053588868, -25.02313995361328, -22.275257110595704, -16.383546829223634, -17.868574142456056] }, { "array": [11.067873001098633, -36.16781234741211, 45.893943786621097, -43.60368347167969, -15.017866134643555, -1.2796411514282227, 2.090773582458496, -2.00726318359375, -22.139572143554689, -22.111957550048829, -16.975831985473634, -17.187711715698243] }, { "array": [9.000146865844727, -36.58183288574219, 46.39983367919922, -45.5433349609375, -15.245804786682129, -2.223395347595215, 4.547385215759277, -1.7552833557128907, -24.691539764404298, -22.836109161376954, -15.247169494628907, -15.090204238891602] }, { "array": [6.99749755859375, -37.912235260009769, 46.3832893371582, -47.186683654785159, -13.492773056030274, -2.4662675857543947, 5.109303951263428, -1.4368247985839844, -25.965919494628908, -23.327587127685548, -14.712173461914063, -15.369802474975586] }, { "array": [8.53531551361084, -38.04619598388672, 47.71860885620117, -47.188323974609378, -12.696022987365723, 0.9119815826416016, 5.812184810638428, 1.9683570861816407, -28.441104888916017, -21.203857421875, -16.284610748291017, -15.28965950012207] }, { "array": [8.53531551361084, -38.04619598388672, 47.71860885620117, -47.188323974609378, -12.696022987365723, 0.9119815826416016, 5.812184810638428, 1.9683570861816407, -28.441104888916017, -21.203857421875, -16.284610748291017, -15.28965950012207] }, { "array": [10.728363037109375, -37.86867141723633, 48.31739044189453, -47.47754669189453, -12.409613609313965, -0.4410533905029297, 5.663397789001465, 3.449146270751953, -27.80557632446289, -20.967662811279298, -15.19067096710205, -14.877350807189942] }, { "array": [10.065123558044434, -36.660072326660159, 44.76460266113281, -47.56492233276367, -12.322249412536621, -2.3014774322509767, 3.445328712463379, 3.1899805068969728, -25.734312057495118, -21.715232849121095, -13.482653617858887, -17.041053771972658] }, { "array": [8.782588958740235, -36.92534255981445, 44.4034309387207, -46.98899841308594, -8.84085464477539, -3.2711610794067385, 2.8354501724243166, 3.1592531204223635, -24.939672470092775, -21.54747772216797, -15.242938041687012, -17.53165054321289] }, { "array": [6.508709907531738, -36.80110168457031, 42.792266845703128, -46.29795837402344, -6.919498443603516, -3.788252830505371, 2.5329980850219728, 4.23353385925293, -24.539836883544923, -21.791210174560548, -16.75529670715332, -18.159324645996095] }, { "array": [6.508709907531738, -36.80110168457031, 42.792266845703128, -46.29795837402344, -6.919498443603516, -3.788252830505371, 2.5329980850219728, 4.23353385925293, -24.539836883544923, -21.791210174560548, -16.75529670715332, -18.159324645996095] }] }, { "name": "O", "mfccCalibrationDataList": [{ "array": [53.443660736083987, -12.53360652923584, -26.871780395507814, -71.98885345458985, -5.861575126647949, -1.7120800018310547, -32.90825653076172, 23.514209747314454, -10.125606536865235, -11.977684020996094, 9.883563041687012, -5.261895179748535] }, { "array": [55.69048309326172, -10.316678047180176, -26.289718627929689, -74.05561828613281, -4.348355293273926, -1.1102313995361329, -32.498268127441409, 22.016807556152345, -11.227481842041016, -11.753466606140137, 10.583023071289063, -3.583785057067871] }, { "array": [55.69048309326172, -10.316678047180176, -26.289718627929689, -74.05561828613281, -4.348355293273926, -1.1102313995361329, -32.498268127441409, 22.016807556152345, -11.227481842041016, -11.753466606140137, 10.583023071289063, -3.583785057067871] }, { "array": [56.333030700683597, -9.761429786682129, -24.525028228759767, -75.67504119873047, -2.9411144256591799, -1.0509262084960938, -32.58983612060547, 20.397789001464845, -13.52730941772461, -10.221673965454102, 8.806441307067871, -6.1672868728637699] }, { "array": [58.425559997558597, -13.048576354980469, -26.326568603515626, -77.33265686035156, 0.8971290588378906, -0.13758087158203126, -34.79779052734375, 22.524978637695314, -15.138383865356446, -9.027335166931153, 10.94324779510498, -6.867808818817139] }, { "array": [57.16611099243164, -17.380069732666017, -26.70465087890625, -76.53448486328125, 3.203751564025879, 1.6217775344848633, -36.67759323120117, 24.14405059814453, -12.522785186767579, -8.60572338104248, 13.969680786132813, -4.909186840057373] }, { "array": [55.17543029785156, -22.491680145263673, -25.568838119506837, -74.10460662841797, 2.1666202545166017, 4.1397705078125, -37.21670913696289, 21.997055053710939, -11.680967330932618, -8.396781921386719, 13.454421997070313, -3.452665328979492] }, { "array": [55.18680953979492, -23.176528930664064, -23.64011573791504, -69.64533233642578, 1.9893865585327149, 7.129931449890137, -35.871803283691409, 19.410160064697267, -13.193324089050293, -8.643393516540528, 10.173726081848145, -3.2846717834472658] }, { "array": [55.18680953979492, -23.176528930664064, -23.64011573791504, -69.64533233642578, 1.9893865585327149, 7.129931449890137, -35.871803283691409, 19.410160064697267, -13.193324089050293, -8.643393516540528, 10.173726081848145, -3.2846717834472658] }, { "array": [52.92599105834961, -22.969005584716798, -25.62136459350586, -65.79485321044922, 0.01132965087890625, 5.457650184631348, -33.96955871582031, 16.389381408691408, -14.026248931884766, -7.287093162536621, 11.159339904785157, -3.7541093826293947] }, { "array": [51.816688537597659, -22.891870498657228, -29.805912017822267, -68.66901397705078, -1.9484624862670899, 1.6389532089233399, -33.593971252441409, 17.095460891723634, -13.046170234680176, -8.923750877380371, 13.052698135375977, -5.068996429443359] }, { "array": [54.392459869384769, -23.71658706665039, -31.424976348876954, -75.520263671875, -4.738470077514648, -0.12287521362304688, -36.41456604003906, 18.125713348388673, -14.02833366394043, -11.63118839263916, 12.234237670898438, -4.546117782592773] }] }] }
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure function module for mapping words to viseme frames.
|
|
3
|
+
* Used by TTS to drive lip sync based on speech boundary events.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type Viseme = "aa" | "ih" | "ou" | "ee" | "oh";
|
|
7
|
+
|
|
8
|
+
export interface VisemeFrame {
|
|
9
|
+
viseme: Viseme;
|
|
10
|
+
duration: number; // ms
|
|
11
|
+
weight: number; // 0-1
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_WEIGHT = 0.8;
|
|
15
|
+
const MIN_FRAME_DURATION = 40; // ms, minimum for smooth animation
|
|
16
|
+
|
|
17
|
+
// Phoneme patterns - order matters (more specific patterns first)
|
|
18
|
+
const PHONEME_RULES: Array<{ pattern: RegExp; visemes: Viseme[] }> = [
|
|
19
|
+
// Common endings
|
|
20
|
+
{ pattern: /tion$/i, visemes: ["oh", "ih"] },
|
|
21
|
+
{ pattern: /sion$/i, visemes: ["oh", "ih"] },
|
|
22
|
+
{ pattern: /ing$/i, visemes: ["ih"] },
|
|
23
|
+
{ pattern: /ed$/i, visemes: ["ee"] },
|
|
24
|
+
{ pattern: /ly$/i, visemes: ["ih", "ee"] },
|
|
25
|
+
|
|
26
|
+
// Common beginnings
|
|
27
|
+
{ pattern: /^th/i, visemes: ["oh"] },
|
|
28
|
+
{ pattern: /^ch/i, visemes: ["ee"] },
|
|
29
|
+
{ pattern: /^sh/i, visemes: ["ee"] },
|
|
30
|
+
{ pattern: /^wh/i, visemes: ["ou"] },
|
|
31
|
+
|
|
32
|
+
// Vowel combinations
|
|
33
|
+
{ pattern: /oo/gi, visemes: ["ou"] },
|
|
34
|
+
{ pattern: /ee/gi, visemes: ["ee"] },
|
|
35
|
+
{ pattern: /ea/gi, visemes: ["ee"] },
|
|
36
|
+
{ pattern: /ai/gi, visemes: ["ee"] },
|
|
37
|
+
{ pattern: /ay/gi, visemes: ["ee"] },
|
|
38
|
+
{ pattern: /ow/gi, visemes: ["oh", "ou"] },
|
|
39
|
+
{ pattern: /ou/gi, visemes: ["aa", "ou"] },
|
|
40
|
+
{ pattern: /oi/gi, visemes: ["oh", "ee"] },
|
|
41
|
+
{ pattern: /oy/gi, visemes: ["oh", "ee"] },
|
|
42
|
+
|
|
43
|
+
// Single vowels
|
|
44
|
+
{ pattern: /[aá]/gi, visemes: ["aa"] },
|
|
45
|
+
{ pattern: /[eé]/gi, visemes: ["ee"] },
|
|
46
|
+
{ pattern: /[ií]/gi, visemes: ["ih"] },
|
|
47
|
+
{ pattern: /[oó]/gi, visemes: ["oh"] },
|
|
48
|
+
{ pattern: /[uú]/gi, visemes: ["ou"] },
|
|
49
|
+
{ pattern: /y$/i, visemes: ["ee"] },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Convert a word to a sequence of viseme frames.
|
|
54
|
+
* Pure function - no side effects.
|
|
55
|
+
*
|
|
56
|
+
* @param word - The word to convert
|
|
57
|
+
* @param durationMs - Total duration for this word in milliseconds
|
|
58
|
+
* @returns Array of viseme frames
|
|
59
|
+
*/
|
|
60
|
+
export function wordToVisemes(word: string, durationMs: number): VisemeFrame[] {
|
|
61
|
+
if (!word || durationMs <= 0) return [];
|
|
62
|
+
|
|
63
|
+
const visemes: Viseme[] = [];
|
|
64
|
+
let remaining = word.toLowerCase();
|
|
65
|
+
|
|
66
|
+
// Apply rules to extract visemes
|
|
67
|
+
for (const rule of PHONEME_RULES) {
|
|
68
|
+
const matches = remaining.match(rule.pattern);
|
|
69
|
+
if (matches) {
|
|
70
|
+
for (const _match of matches) {
|
|
71
|
+
visemes.push(...rule.visemes);
|
|
72
|
+
}
|
|
73
|
+
// Remove matched portions for more specific rules
|
|
74
|
+
remaining = remaining.replace(rule.pattern, "");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Fallback: if no visemes extracted, use vowel-based approach
|
|
79
|
+
if (visemes.length === 0) {
|
|
80
|
+
const vowels = word.match(/[aeiouáéíóú]/gi) || [];
|
|
81
|
+
for (const v of vowels) {
|
|
82
|
+
const lower = v.toLowerCase();
|
|
83
|
+
if (lower === "a" || lower === "á") visemes.push("aa");
|
|
84
|
+
else if (lower === "e" || lower === "é") visemes.push("ee");
|
|
85
|
+
else if (lower === "i" || lower === "í") visemes.push("ih");
|
|
86
|
+
else if (lower === "o" || lower === "ó") visemes.push("oh");
|
|
87
|
+
else if (lower === "u" || lower === "ú") visemes.push("ou");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// If still no visemes, use a default mouth shape
|
|
92
|
+
if (visemes.length === 0) {
|
|
93
|
+
visemes.push("aa");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Distribute duration across visemes
|
|
97
|
+
const frameDuration = Math.max(MIN_FRAME_DURATION, durationMs / visemes.length);
|
|
98
|
+
|
|
99
|
+
return visemes.map((viseme) => ({
|
|
100
|
+
viseme,
|
|
101
|
+
duration: frameDuration,
|
|
102
|
+
weight: DEFAULT_WEIGHT,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Estimate word duration based on character count and speaking rate.
|
|
108
|
+
*
|
|
109
|
+
* @param word - The word to estimate
|
|
110
|
+
* @param rateMultiplier - Speaking rate (1.0 = normal, 0.5 = slow, 1.5 = fast)
|
|
111
|
+
* @returns Estimated duration in milliseconds
|
|
112
|
+
*/
|
|
113
|
+
export function estimateWordDuration(word: string, rateMultiplier = 1.0): number {
|
|
114
|
+
// Average speaking rate: ~150 words/min = ~400ms per word
|
|
115
|
+
// Adjust by character count (longer words take longer)
|
|
116
|
+
const baseMs = 300;
|
|
117
|
+
const perCharMs = 30;
|
|
118
|
+
const rawDuration = baseMs + word.length * perCharMs;
|
|
119
|
+
return Math.round(rawDuration / rateMultiplier);
|
|
120
|
+
}
|