File size: 2,398 Bytes
0626c04
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { APP_CONFIG } from './config'
import { configureOrt, createTtsSessions, ensureWebGpuSupport, fetchCondEmb, fetchTokenizerJson } from './onnx'
import { runFinnishPipeline, type TtsSessions } from './pipeline'
import { createTokenizer, type BrowserTokenizer } from './tokenizer'
import type { WorkerRequest, WorkerResponse } from './types'

let sessionsPromise: Promise<TtsSessions> | null = null
let tokenizerPromise: Promise<BrowserTokenizer> | null = null
let condEmbPromise: Promise<Float32Array> | null = null
const workerScope = self as typeof self & {
  postMessage: (message: WorkerResponse, transfer?: Transferable[]) => void
  onmessage: ((event: MessageEvent<WorkerRequest>) => void | Promise<void>) | null
}

function postMessageToMain(message: WorkerResponse, transfer: Transferable[] = []): void {
  workerScope.postMessage(message, transfer)
}

function reportStatus(message: string): void {
  postMessageToMain({ type: 'status', message })
}

async function ensureInitialized(): Promise<{
  sessions: TtsSessions
  tokenizer: BrowserTokenizer
  condEmb: Float32Array
}> {
  ensureWebGpuSupport()
  configureOrt()

  sessionsPromise ??= createTtsSessions(reportStatus)
  tokenizerPromise ??= fetchTokenizerJson().then((json) => createTokenizer(json))
  condEmbPromise ??= fetchCondEmb()

  const [sessions, tokenizer, condEmb] = await Promise.all([
    sessionsPromise,
    tokenizerPromise,
    condEmbPromise,
  ])
  postMessageToMain({ type: 'ready' })
  return { sessions, tokenizer, condEmb }
}

async function handleSpeakRequest(message: Extract<WorkerRequest, { type: 'speak' }>): Promise<void> {
  const { sessions, tokenizer, condEmb } = await ensureInitialized()
  const result = await runFinnishPipeline({
    sessions,
    tokenizer,
    condEmb,
    referenceAudio: message.referenceAudio,
    text: message.text,
    reportStatus,
  })

  postMessageToMain(
    {
      type: 'result',
      audio: result.audio,
      sampleRate: APP_CONFIG.sampleRate,
      speechTokenCount: result.speechTokenCount,
    },
    [result.audio.buffer],
  )
}

workerScope.onmessage = async (event: MessageEvent<WorkerRequest>) => {
  try {
    if (event.data.type === 'speak') {
      await handleSpeakRequest(event.data)
    }
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error)
    postMessageToMain({ type: 'error', message })
  }
}