Files
Vosklet/src/Wrapper.js
2024-09-02 23:09:34 -07:00

166 lines
5.3 KiB
JavaScript

let objs = []
let events = ["status", "partialResult", "result"]
let processorURL = URL.createObjectURL(new Blob(['(', (() => {
registerProcessor("VoskletTransferer", class extends AudioWorkletProcessor {
constructor(opts) {
super()
this.count = 0
this.maxCount = opts.processorOptions.maxCount
this.buffer = new Float32Array(this.maxCount * 128)
}
process(inputs) {
if(!inputs[0][0]) return true
this.buffer.set(inputs[0][0], this.count * 128)
this.count++
if(this.count >= this.maxCount) {
this.count = 0
this.port.postMessage(this.buffer, [this.buffer.buffer])
this.buffer = new Float32Array(this.maxCount * 128)
}
return true
}
})
}).toString(), ')()'], { type : "text/javascript" }))
Module.cleanUp = async () => {
for(let obj of objs) await obj.delete()
URL.revokeObjectURL(processorURL)
}
Module.createTransferer = async (ctx, bufferSize) => {
await ctx.audioWorklet.addModule(processorURL)
return new AudioWorkletNode(ctx, "VoskletTransferer", {
channelCountMode : "explicit",
numberOfInputs : 1,
numberOfOutputs : 0,
channelCount : 1,
processorOptions : { maxCount: bufferSize / 128 }
})
}
getFileHandle = async (path, create = false) => {
let components = path.split("/")
let prevDir = await navigator.storage.getDirectory()
for(let component of components.slice(0, -1)) prevDir = await prevDir.getDirectoryHandle(component, { create : create })
return prevDir.getFileHandle(components[components.length - 1], { create : create })
}
class CommonModel extends EventTarget {
constructor() {
super()
objs.push(this)
}
delete() {
this.obj.delete()
}
static async create(url, storepath, id, normalMdl) {
let mdl = new CommonModel()
let result = new Promise((resolve, reject) => {
mdl.addEventListener("status", ev => {
if(!ev.detail) {
if(normalMdl) mdl.findWord = (word) => mdl.obj.findWord(word)
resolve(mdl)
}
else reject(ev.detail)
}, { once : true })
})
let tar
try {
let dataFile = await (await getFileHandle(storepath + "/model.tgz")).getFile()
let idFile = await (await getFileHandle(storepath + "/id")).getFile()
if(await idFile.text() != id) throw ""
tar = await new Response(dataFile.stream().pipeThrough(new DecompressionStream("gzip"))).arrayBuffer()
}
catch {
try {
let res = await fetch(url)
if (!res.ok) throw "Unable to download model"
let teedBody = res.body.tee()
let newDataFile = await (await getFileHandle(storepath + "/model.tgz", true)).createWritable()
await newDataFile.write(await new Response(teedBody[0].pipeThrough(new CompressionStream("gzip"))).arrayBuffer())
await newDataFile.close()
let newIDFile = await (await getFileHandle(storepath + "/id", true)).createWritable()
await newIDFile.write(id)
await newIDFile.close()
tar = await new Response(teedBody[1]).arrayBuffer()
}
catch(e) {
mdl.delete()
throw e
}
}
let tarStart = _malloc(tar.byteLength)
HEAPU8.set(new Uint8Array(tar), tarStart)
mdl.obj = new Module.CommonModel(objs.length - 1, normalMdl, "/" + storepath, id, tarStart, tar.byteLength)
return result
}
}
Module.createModel = async (url, storepath, id) => {
return CommonModel.create(url, storepath, id, true)
}
Module.createSpkModel = async (url, storepath, id) => {
return CommonModel.create(url, storepath, id, false)
}
class Recognizer extends EventTarget {
constructor() {
super()
objs.push(this)
return new Proxy(this, {
get(self, prop, _) {
if(self[prop] == undefined && self.obj[prop] == undefined) return undefined
let p = self[prop]
if(p) return p.bind ? p.bind(self) : p
p = self.obj[prop]
return p.bind ? p.bind(self.obj) : p
}
})
}
acceptWaveform(audioData) {
let start = _malloc(audioData.length * 4)
HEAPF32.set(audioData, start / 4)
this.obj.acceptWaveform(start, audioData.length)
}
async delete(processCurrent = false) {
let result = new Promise((resolve, _) => this.addEventListener("status", _ => {
this.obj.delete()
resolve()
}, { once : true }))
this.obj.safeDelete(processCurrent)
return result
}
static async create(model, sampleRate, mode, grammar, spkModel) {
let rec = new Recognizer()
let result = new Promise((resolve, reject) => {
rec.addEventListener("status", ev => {
if(!ev.detail) resolve(rec)
else reject(ev.detail)
}, { once : true })
})
switch(mode) {
case 1:
rec.obj = new Module.Recognizer(objs.length - 1, sampleRate, model)
break
case 2:
rec.obj = new Module.Recognizer(objs.length -1, sampleRate, model, spkModel)
break
default:
rec.obj = new Module.Recognizer(objs.length - 1, sampleRate, model, grammar, 0)
}
return result
}
}
Module.createRecognizer = (model, sampleRate) => {
return Recognizer.create(model.obj, sampleRate, 1)
}
Module.createRecognizerWithSpkModel = (model, sampleRate, spkModel) => {
return Recognizer.create(model.obj, sampleRate, 2, null, spkModel.obj)
}
Module.createRecognizerWithGrm = (model, sampleRate, grammar) => {
return Recognizer.create(model.obj, sampleRate, 3, grammar, null)
}