Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
/* eslint-disable prefer-const */
import { Request } from 'http'
import AudioOut from 'pins/audioout'
import Timer from 'timer'
import config from 'mc/config'
/* global trace, SharedArrayBuffer */
const BYTE_LENGTH = 1024
const BUF_INDICES = 8
const TIMEOUT = 10000
let buffer
let view
let promise
let handler
function enqueueWait(audioOut, ...values) {
/* keep retrying until queue is not full */
// eslint-disable-next-line no-constant-condition
while (true) {
try {
audioOut.enqueue(...values)
break
} catch (e) {
if (e.message !== '(host): queue full') {
throw e
}
}
}
}
class TTS {
static async speak(string) {
if (handler != null) {
return -1
}
let audioOut = new AudioOut({
streams: 1,
bitsPerSample: 16,
numChannels: 1,
sampleRate: 22050,
})
if (buffer == null) {
buffer = new SharedArrayBuffer(BYTE_LENGTH * BUF_INDICES + 4)
view = new DataView(buffer)
}
const host = config.tts?.host || '127.0.0.1'
const port = config.tts?.port || 8080
promise = new Promise((resolve, reject) => {
let bufIdx = 0
let numFragments = 0
let bytes = 0
const cleanup = () => {
trace('cleaning up\n')
if (handler != null) {
Timer.clear(handler)
handler = null
}
audioOut.close()
audioOut = null
}
const onFinish = () => {
trace('onFinish\n')
cleanup()
resolve(bytes)
}
const onReject = () => {
trace('onReject\n')
cleanup()
reject(-1)
}
handler = Timer.set(() => {
handler = null
onReject()
}, TIMEOUT)
audioOut.start()
audioOut.enqueue(0, AudioOut.Flush)
audioOut.enqueue(0, AudioOut.Volume, 128)
audioOut.callback = () => {
trace(`all samples played. ${numFragments} fragments, ${bytes} bytes\n`)
Timer.set(onFinish, 0)
}
const request = new Request({
host,
port,
path: `/api/tts?text=${string.replaceAll(' ', '%20')}`,
method: 'GET',
})
let idx = 0
request.callback = function (message) {
if (Request.responseFragment === message) {
let chunk = this.read(ArrayBuffer)
let len = chunk.byteLength
bytes += len
// copy chunk to shared array buffer
let sourceView = new DataView(chunk)
let offset = bufIdx * BYTE_LENGTH
let i = numFragments === 0 ? Math.min(128, len) : 0
if (numFragments < BUF_INDICES) {
for (; i < len; i++) {
if (idx >= BYTE_LENGTH) {
enqueueWait(audioOut, 0, AudioOut.RawSamples, buffer, 1, offset >> 1, BYTE_LENGTH >> 1)
bufIdx = (bufIdx + 1) % BUF_INDICES
offset = bufIdx * BYTE_LENGTH
idx = 0
}
view.setUint8(offset + idx, sourceView.getUint8(i))
idx += 1
}
} else {
for (; i < len - 1; i += 2) {
if (idx >= BYTE_LENGTH) {
enqueueWait(audioOut, 0, AudioOut.RawSamples, buffer, 1, offset >> 1, BYTE_LENGTH >> 1)
bufIdx = (bufIdx + 1) % BUF_INDICES
offset = bufIdx * BYTE_LENGTH
idx = 0
}
view.setUint16(offset + idx, sourceView.getUint16(i, true), true)
idx += 2
}
if (len % 2 > 0) {
// last one byte
view.setUint8(offset + idx, sourceView.getUint8(i))
idx += 1
}
}
numFragments += 1
}
if (Request.responseComplete == message) {
if (idx > 0) {
enqueueWait(audioOut, 0, AudioOut.RawSamples, buffer, 1, (bufIdx * BYTE_LENGTH) >> 1, idx >> 1)
}
trace('audioout enqueue\n')
enqueueWait(audioOut, 0, AudioOut.Callback, 0)
}
if (message < 0) {
trace('exception occured: ' + message + '\n')
onReject()
}
}
})
return promise
}
}
export default TTS
5882cd3f8This file is pinned to the selected release. Download the original to open it in your design tool.