123 lines
3.1 KiB
JavaScript
123 lines
3.1 KiB
JavaScript
/**
|
|
* Kokoro Web Worker
|
|
* Handles TTS processing in a separate thread to keep UI responsive
|
|
*/
|
|
|
|
// Global variables
|
|
let kokoroLoaded = false;
|
|
let isProcessing = false;
|
|
let voiceOptions = {
|
|
voice: 'bf_alice',
|
|
speed: 1.0
|
|
};
|
|
|
|
// Initialize when receiving init message
|
|
self.onmessage = function(e) {
|
|
const message = e.data;
|
|
|
|
try {
|
|
switch (message.type) {
|
|
case 'init':
|
|
// Just acknowledge initialization - actual model loading happens on first generate call
|
|
self.postMessage({ type: 'ready' });
|
|
break;
|
|
|
|
case 'generate':
|
|
if (!message.text) {
|
|
self.postMessage({
|
|
type: 'error',
|
|
error: 'No text provided for generation'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Store voice options
|
|
if (message.voice) voiceOptions.voice = message.voice;
|
|
if (message.speed) voiceOptions.speed = message.speed;
|
|
|
|
// Generate speech
|
|
generateSpeech(message.text)
|
|
.then(result => {
|
|
self.postMessage({
|
|
type: 'generated',
|
|
result: result
|
|
}, [result.audio.buffer]);
|
|
})
|
|
.catch(error => {
|
|
self.postMessage({
|
|
type: 'error',
|
|
error: `Generation error: ${error.message || error}`
|
|
});
|
|
});
|
|
break;
|
|
|
|
default:
|
|
self.postMessage({
|
|
type: 'error',
|
|
error: `Unknown message type: ${message.type}`
|
|
});
|
|
}
|
|
} catch (error) {
|
|
self.postMessage({
|
|
type: 'error',
|
|
error: `Worker error: ${error.message || error}`
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Generate speech from text
|
|
* @param {string} text - Text to convert to speech
|
|
*/
|
|
async function generateSpeech(text) {
|
|
if (isProcessing) {
|
|
throw new Error('Already processing another request');
|
|
}
|
|
|
|
isProcessing = true;
|
|
|
|
try {
|
|
// Load Kokoro if not already loaded
|
|
if (!kokoroLoaded) {
|
|
try {
|
|
// Load the Kokoro script
|
|
self.importScripts('/js/kokoro.js');
|
|
|
|
if (!self.Kokoro) {
|
|
throw new Error('Kokoro failed to load correctly');
|
|
}
|
|
|
|
kokoroLoaded = true;
|
|
console.log('Kokoro loaded in worker');
|
|
} catch (loadError) {
|
|
console.error('Error loading Kokoro in worker:', loadError);
|
|
throw new Error(`Failed to load Kokoro: ${loadError.message}`);
|
|
}
|
|
}
|
|
|
|
// Generate speech using Kokoro
|
|
const result = await self.Kokoro(text, {
|
|
voice: voiceOptions.voice,
|
|
speed: voiceOptions.speed,
|
|
autoPlay: false
|
|
});
|
|
|
|
// Extract audio data
|
|
const audioContext = new (self.AudioContext || self.webkitAudioContext)();
|
|
const audioBuffer = await audioContext.decodeAudioData(result.buffer);
|
|
|
|
// Get audio data as Float32Array
|
|
const audioData = audioBuffer.getChannelData(0);
|
|
|
|
// Return the result
|
|
return {
|
|
audio: audioData,
|
|
sampling_rate: audioBuffer.sampleRate
|
|
};
|
|
} catch (error) {
|
|
console.error('Error generating speech in worker:', error);
|
|
throw error;
|
|
} finally {
|
|
isProcessing = false;
|
|
}
|
|
} |