Remove OPFS, use cache API, map index onto [A-Z] for path. Change to build twice, can't hack anymore :(
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
#### The file Vosklet.js in this folder, used by the examples and the outer [README.md](../README.md), has been set to decompress manually using ```DecompressionStream``` because I can't set a third-party (Github's) server response header. You can utilize this if you run into the same situation. Otherwise, please use the outer Vosklet.js instead.
|
||||
#### The file Vosklet.js in this folder, used by the examples and the outer [README.md](../README.md), has been set to decompress explicitly using ```DecompressionStream``` (instead of implicit browser decompression) because I can't set a third-party (Github's) server response header. You can utilize this if you run into the same situation. Otherwise, please use the outer Vosklet.js instead.
|
||||
|
||||
#### The motivation is that it will work right away when put into a HTML file. You can just make a local copy and try everything out quickly
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
152
Examples/Wrapper.js
Normal file
152
Examples/Wrapper.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @fileoverview
|
||||
* @suppress {undefinedVars|checkTypes}
|
||||
*/
|
||||
|
||||
if(ENVIRONMENT_IS_WEB) {
|
||||
|
||||
// 'var' to expose this outside the if
|
||||
var objs = [];
|
||||
var events = ['status', 'partialResult', 'result'];
|
||||
let processorURL = URL.createObjectURL(new Blob(['(', (() => {
|
||||
registerProcessor('VoskletTransferer', class extends AudioWorkletProcessor {
|
||||
constructor(opts) {
|
||||
super();
|
||||
this.filled = 0;
|
||||
this.bufSize = opts.processorOptions[0];
|
||||
this.buf = new Float32Array(this.bufSize);
|
||||
}
|
||||
process(inputs) {
|
||||
if(inputs[0][0]) {
|
||||
this.buf.set(inputs[0][0], this.filled);
|
||||
this.filled += 128;
|
||||
if(this.filled >= this.bufSize) {
|
||||
this.filled = 0;
|
||||
this.port.postMessage(this.buf, [this.buf.buffer]);
|
||||
this.buf = new Float32Array(this.bufSize);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
})
|
||||
}).toString(), ')()'], { type: 'text/javascript' }));
|
||||
class CommonModel extends EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
objs.push(this);
|
||||
}
|
||||
delete() {
|
||||
this.obj.delete();
|
||||
}
|
||||
static async mk(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 cache = await caches.open('Vosklet');
|
||||
let res = await cache.match(storepath);
|
||||
let tar;
|
||||
if(typeof res == 'undefined' || res.headers.get('id') != id) {
|
||||
// Caching already handled explicitly
|
||||
res = await fetch(url, { cache: 'no-store' });
|
||||
if (!res.ok) throw 'Unable to fetch model, status: ' + res.status;
|
||||
await cache.put(storepath, new Response(
|
||||
res.clone().body, { headers: { 'id': id } }
|
||||
));
|
||||
}
|
||||
tar = await new Response(res.body.pipeThrough(new DecompressionStream('gzip'))).arrayBuffer();
|
||||
let tarStart = _malloc(tar.byteLength);
|
||||
HEAPU8.set(new Uint8Array(tar), tarStart);
|
||||
mdl.obj = new Module['CommonModel'](objs.length - 1, normalMdl, tarStart, tar.byteLength);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
class Recognizer extends EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
// Closure workaround to prevent acceptWaveform from getting removed
|
||||
this['acceptWaveform'] = audioData => {
|
||||
let start = _malloc(audioData.length * 4);
|
||||
HEAPF32.set(audioData, start / 4);
|
||||
this.obj['acceptWaveform'](start, audioData.length);
|
||||
}
|
||||
objs.push(this);
|
||||
return new Proxy(this, {
|
||||
get(self, prop, _) {
|
||||
if(self[prop] == undefined && self.obj[prop] == undefined) return;
|
||||
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;
|
||||
}
|
||||
})
|
||||
}
|
||||
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 mk(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 = {
|
||||
'cleanUp': async () => {
|
||||
for(let obj of objs) await obj.delete();
|
||||
URL.revokeObjectURL(processorURL);
|
||||
},
|
||||
|
||||
'createTransferer': async (ctx, bufSize) => {
|
||||
await ctx.audioWorklet.addModule(processorURL);
|
||||
return new AudioWorkletNode(ctx, 'VoskletTransferer', {
|
||||
channelCountMode: 'explicit',
|
||||
numberOfInputs: 1,
|
||||
numberOfOutputs: 0,
|
||||
channelCount: 1,
|
||||
processorOptions: [bufSize]
|
||||
});
|
||||
},
|
||||
|
||||
'createModel': (url, storepath, id) =>
|
||||
CommonModel.mk(url, storepath, id, true),
|
||||
|
||||
'createSpkModel': (url, storepath, id) =>
|
||||
CommonModel.mk(url, storepath, id, false),
|
||||
|
||||
'createRecognizer': (model, sampleRate) =>
|
||||
Recognizer.mk(model.obj, sampleRate, 1),
|
||||
|
||||
'createRecognizerWithGrm': (model, sampleRate, grammar) =>
|
||||
Recognizer.mk(model.obj, sampleRate, 3, grammar, null),
|
||||
|
||||
'createRecognizerWithSpkModel': (model, sampleRate, spkModel) =>
|
||||
Recognizer.mk(model.obj, sampleRate, 2, null, spkModel.obj)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,19 +5,19 @@
|
||||
<script>
|
||||
async function start() {
|
||||
// All data is collected and transfered to the main thread so the AudioContext won't output anything. Set sinkId type to none to save power
|
||||
let ctx = new AudioContext({sinkId: {type: "none"}})
|
||||
let module = await loadVosklet()
|
||||
let ctx = new AudioContext({sinkId: {type: "none"}});
|
||||
let module = await loadVosklet();
|
||||
let model = await module.createModel("https://ccoreilly.github.io/vosk-browser/models/vosk-model-small-en-us-0.15.tar.gz", "English", "vosk-model-small-en-us-0.15")
|
||||
let recognizer = await module.createRecognizer(model, ctx.sampleRate)
|
||||
let recognizer = await module.createRecognizer(model, ctx.sampleRate);
|
||||
|
||||
// Listen for result and partial result
|
||||
recognizer.addEventListener("result", ev => console.log("Result: ", ev.detail))
|
||||
recognizer.addEventListener("partialResult", ev => console.log("Partial result: ", ev.detail))
|
||||
recognizer.addEventListener("partialResult", ev => console.log("Partial result: ", ev.detail));
|
||||
|
||||
// Fetch, decode, and recognize the .wav
|
||||
let wav = await fetch("https://cdn.jsdelivr.net/gh/msqr1/Vosklet/examples/1to10-en.wav")
|
||||
let audioBuf = await ctx.decodeAudioData(await wav.arrayBuffer())
|
||||
recognizer.acceptWaveform(audioBuf.getChannelData(0))
|
||||
let wav = await fetch("https://cdn.jsdelivr.net/gh/msqr1/Vosklet/examples/1to10-en.wav");
|
||||
let audioBuf = await ctx.decodeAudioData(await wav.arrayBuffer());
|
||||
recognizer.acceptWaveform(audioBuf.getChannelData(0));
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<script>
|
||||
async function start() {
|
||||
// All data is collected and transfered to the main thread so the AudioContext won't output anything. Set sinkId type to none to save power
|
||||
let ctx = new AudioContext({sinkId: {type: "none"}})
|
||||
let ctx = new AudioContext({sinkId: {type: "none"}});
|
||||
|
||||
// Setup microphone
|
||||
let micNode = ctx.createMediaStreamSource(await navigator.mediaDevices.getUserMedia({
|
||||
@@ -15,25 +15,25 @@
|
||||
noiseSuppression: true,
|
||||
channelCount: 1
|
||||
},
|
||||
}))
|
||||
}));
|
||||
|
||||
// Load Vosklet module, model and recognizer
|
||||
let module = await loadVosklet()
|
||||
let model = await module.createModel("https://ccoreilly.github.io/vosk-browser/models/vosk-model-small-en-us-0.15.tar.gz", "English", "vosk-model-small-en-us-0.15")
|
||||
let recognizer = await module.createRecognizer(model, ctx.sampleRate)
|
||||
let module = await loadVosklet();
|
||||
let model = await module.createModel("https://ccoreilly.github.io/vosk-browser/models/vosk-model-small-en-us-0.15.tar.gz", "English", "vosk-model-small-en-us-0.15");
|
||||
let recognizer = await module.createRecognizer(model, ctx.sampleRate);
|
||||
|
||||
// Listen for result and partial result
|
||||
recognizer.addEventListener("result", ev => console.log("Result: ", ev.detail))
|
||||
recognizer.addEventListener("partialResult", ev => console.log("Partial result: ", ev.detail))
|
||||
recognizer.addEventListener("result", ev => console.log("Result: ", ev.detail));
|
||||
recognizer.addEventListener("partialResult", ev => console.log("Partial result: ", ev.detail));
|
||||
|
||||
// Create a transferer node to get audio data on the main thread
|
||||
let transferer = await module.createTransferer(ctx, 128 * 150)
|
||||
let transferer = await module.createTransferer(ctx, 128 * 150);
|
||||
|
||||
// Recognize data on arrival
|
||||
transferer.port.onmessage = ev => recognizer.acceptWaveform(ev.data)
|
||||
transferer.port.onmessage = ev => recognizer.acceptWaveform(ev.data);
|
||||
|
||||
// Connect transferer to microphone
|
||||
micNode.connect(transferer)
|
||||
micNode.connect(transferer);
|
||||
}
|
||||
</script>
|
||||
<!-- Start and create audio context only as a result of user's action -->
|
||||
|
||||
Reference in New Issue
Block a user