/** * 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.data || !message.data.text) { self.postMessage({ type: 'error', error: 'No text provided for generation' }); return; } // Store voice options if (message.data.voice) voiceOptions.voice = message.data.voice; if (message.data.speed) voiceOptions.speed = message.data.speed; // Generate speech generateSpeech(message.data.text) .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) { // Load the Kokoro script self.importScripts('/js/kokoro-js.js'); if (!self.kokoro || !self.kokoro.KokoroTTS) { throw new Error('Kokoro failed to load correctly'); } kokoroLoaded = true; } // Create a new Kokoro instance for this generation // We can't easily transfer the instance from the main thread, so we create it here const kokoroTTS = self.kokoro.KokoroTTS; // Create instance using from_pretrained const tts = await kokoroTTS.from_pretrained("onnx-community/Kokoro-82M-v1.0-ONNX", { dtype: "fp32", device: "wasm", cache: true // Use cache to speed up subsequent loads }); // Generate speech const result = await tts.generate(text, { voice: voiceOptions.voice, speed: voiceOptions.speed }); // Send the result back to the main thread // We can't transfer the Float32Array directly, so let's transfer the buffer const audioBuffer = result.audio.buffer; self.postMessage({ type: 'generated', result: { audio: audioBuffer, sampling_rate: result.sampling_rate } }, [audioBuffer]); // Transfer the buffer for better performance } catch (error) { throw error; } finally { isProcessing = false; } }