Files
ai.interactive.fiction/public/js/kokoro-worker.js
T

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;
}
}