File size: 2,887 Bytes
efe3ae2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import WorkerInstance from '$lib/workers/kokoro.worker?worker';

export class KokoroWorker {
	private worker: Worker | null = null;
	private initialized: boolean = false;
	private dtype: string;
	private requestQueue: Array<{
		text: string;
		voice: string;
		resolve: (value: string) => void;
		reject: (reason: any) => void;
	}> = [];
	private processing = false; // To track if a request is being processed

	constructor(dtype: string = 'fp32') {
		this.dtype = dtype;
	}

	public async init() {
		if (this.worker) {
			console.warn('KokoroWorker is already initialized.');
			return;
		}

		this.worker = new WorkerInstance();

		// Handle worker messages
		this.worker.onmessage = (event) => {
			const { status, error, audioUrl } = event.data;

			if (status === 'init:complete') {
				this.initialized = true;
			} else if (status === 'init:error') {
				console.error(error);
				this.initialized = false;
			} else if (status === 'generate:complete') {
				// Resolve promise from queue
				const request = this.requestQueue.shift();
				if (request) {
					request.resolve(audioUrl);
					this.processNextRequest(); // Process next request in queue
				}
			} else if (status === 'generate:error') {
				const request = this.requestQueue.shift();
				if (request) {
					request.reject(new Error(error));
					this.processNextRequest(); // Continue processing next in queue
				}
			}
		};

		return new Promise<void>((resolve, reject) => {
			this.worker!.postMessage({
				type: 'init',
				payload: { dtype: this.dtype }
			});

			const handleMessage = (event: MessageEvent) => {
				if (event.data.status === 'init:complete') {
					this.worker!.removeEventListener('message', handleMessage);
					this.initialized = true;
					resolve();
				} else if (event.data.status === 'init:error') {
					this.worker!.removeEventListener('message', handleMessage);
					reject(new Error(event.data.error));
				}
			};

			this.worker!.addEventListener('message', handleMessage);
		});
	}

	public async generate({ text, voice }: { text: string; voice: string }): Promise<string> {
		if (!this.initialized || !this.worker) {
			throw new Error('KokoroTTS Worker is not initialized yet.');
		}

		return new Promise<string>((resolve, reject) => {
			this.requestQueue.push({ text, voice, resolve, reject });
			if (!this.processing) {
				this.processNextRequest();
			}
		});
	}

	private processNextRequest() {
		if (this.requestQueue.length === 0) {
			this.processing = false;
			return;
		}

		this.processing = true;
		const { text, voice } = this.requestQueue[0]; // Get first request but don't remove yet
		this.worker!.postMessage({ type: 'generate', payload: { text, voice } });
	}

	public terminate() {
		if (this.worker) {
			this.worker.terminate();
			this.worker = null;
			this.initialized = false;
			this.requestQueue = [];
			this.processing = false;
		}
	}
}