Nothing is working but it is on the right track! ASYNCIFY removed, manual waiting instead and some proxying logic
This commit is contained in:
@@ -3,22 +3,6 @@
|
||||
#include "recognizer.h"
|
||||
#include <emscripten/bind.h>
|
||||
using namespace emscripten;
|
||||
void throwJS(const char* msg, bool err = false) {
|
||||
EM_ASM({
|
||||
if($1) {
|
||||
throw Error(UTF8ToString)
|
||||
return
|
||||
}
|
||||
throw UTF8ToString($0)
|
||||
},msg, err);
|
||||
}
|
||||
int main() {
|
||||
//vosk_set_log_level(-1);
|
||||
std::thread t{[](){
|
||||
wasmfs_create_directory("/opfs",0777,wasmfs_create_opfs_backend());
|
||||
}};
|
||||
t.detach();
|
||||
}
|
||||
EMSCRIPTEN_BINDINGS() {
|
||||
function("setLogLevel", &vosk_set_log_level, allow_raw_pointers());
|
||||
class_<model>("Model")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "genericModel.h"
|
||||
|
||||
fetchData::fetchData(const std::string& storepath, bool* successful, std::atomic_flag* blocker, genericModel* self) : storepath(storepath), successful(successful), blocker(blocker), self(self) {};
|
||||
genericModel::genericModel(const std::string &url, const std::string& storepath, const std::string &id) : url(url), id(id) {
|
||||
fs::current_path("/opfs");
|
||||
fs::create_directories(storepath);
|
||||
@@ -7,9 +7,7 @@ genericModel::genericModel(const std::string &url, const std::string& storepath,
|
||||
}
|
||||
bool genericModel::checkId(const std::string& id) {
|
||||
std::ifstream file {"id", std::ifstream::binary};
|
||||
if(!file.is_open()) {
|
||||
return false;
|
||||
};
|
||||
if(!file.is_open()) return false;
|
||||
long long size {file.seekg(0, std::ios::end).tellg()};
|
||||
std::string oldid(size, ' ');
|
||||
file.seekg(0);
|
||||
@@ -17,41 +15,47 @@ bool genericModel::checkId(const std::string& id) {
|
||||
return id.compare(oldid) == 0 ? true : false;
|
||||
}
|
||||
bool genericModel::loadModel(const std::string& storepath) {
|
||||
if(!checkModel() || !checkId(id)) {
|
||||
char filename[] {"/opfs/XXXXXX.tzst"};
|
||||
close(mkostemps(filename, 5, O_PATH));
|
||||
if(emscripten_wget(url.c_str(),filename) == 1) {
|
||||
throwJS("Unable to fetch model");
|
||||
return false;
|
||||
}
|
||||
if(!extractModel(filename)) {
|
||||
if(checkModel() && checkId(id)) return true;
|
||||
std::atomic_flag blocker{};
|
||||
bool successful{};
|
||||
fetchData data{storepath, &successful, &blocker, this};
|
||||
emscripten_async_wget2(url.c_str(), "A_fIlEnAmE_tHaT_dOeS_nOt_CoNfLiCt.tzst", "GET", nullptr, (void*)&data, [](unsigned handle, void* arg, const char* fname){
|
||||
fetchData* data = (fetchData*)arg;
|
||||
if(!extractModel()) {
|
||||
throwJS("Unable to extract model");
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
fs::remove(filename);
|
||||
if(!checkModel()) {
|
||||
fs::remove(fname);
|
||||
if(!data->self->checkModel()) {
|
||||
throwJS("Model URL contains invalid model files");
|
||||
fs::current_path("/opfs");
|
||||
fs::remove_all(storepath);
|
||||
return false;
|
||||
fs::remove_all(data->storepath);
|
||||
return;
|
||||
}
|
||||
std::ofstream idFile("id");
|
||||
if(!idFile.is_open()) {
|
||||
throwJS("Unable to write new id");
|
||||
fs::remove_all(storepath);
|
||||
return false;
|
||||
fs::current_path("/opfs");
|
||||
fs::remove_all(data->storepath);
|
||||
return;
|
||||
}
|
||||
idFile << id;
|
||||
}
|
||||
return true;
|
||||
idFile << data->self->id;
|
||||
*data->successful = true;
|
||||
data->blocker->notify_one();
|
||||
}, [](unsigned handle, void* arg, int status) {
|
||||
throwJS("Unable to fetch model");
|
||||
((fetchData*)arg)->blocker->notify_one();
|
||||
}, nullptr);
|
||||
blocker.wait(false, std::memory_order_relaxed);
|
||||
return successful;
|
||||
}
|
||||
bool genericModel::extractModel(char* name) {
|
||||
bool genericModel::extractModel() {
|
||||
std::string path{};
|
||||
archive* src {archive_read_new()};
|
||||
archive_entry* entry {};
|
||||
archive_read_support_filter_all(src);
|
||||
archive_read_support_format_all(src);
|
||||
archive_read_open_filename(src, name,10240);
|
||||
archive_read_open_filename(src, "A_fIlEnAmE_tHaT_dOeS_nOt_CoNfLiCt.tzst",10240);
|
||||
if(archive_errno(src) != 0) return false;
|
||||
while (archive_read_next_header(src, &entry) == ARCHIVE_OK) {
|
||||
path = archive_entry_pathname(entry);
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
#pragma once
|
||||
#include "global.h"
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <vosk_api.h>
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
#include <emscripten/wasmfs.h>
|
||||
#include <emscripten.h>
|
||||
extern void throwJS(const char* msg, bool err = false);
|
||||
extern void throwJS(const char* msg, bool err);
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
struct genericModel {
|
||||
const std::string url{};
|
||||
const std::string id{};
|
||||
static bool extractModel(char *name);
|
||||
static bool extractModel();
|
||||
static bool checkId(const std::string& id);
|
||||
virtual bool checkModel() = 0;
|
||||
bool loadModel(const std::string& storepath);
|
||||
genericModel(const std::string &url, const std::string &storepath, const std::string &id);
|
||||
};
|
||||
struct fetchData {
|
||||
const std::string storepath{};
|
||||
std::atomic_flag* blocker{};
|
||||
bool* successful{};
|
||||
genericModel* self{};
|
||||
fetchData(const std::string& storepath, bool* successful, std::atomic_flag* blocker, genericModel* self);
|
||||
};
|
||||
|
||||
17
src/global.cc
Normal file
17
src/global.cc
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "global.h"
|
||||
void throwJS(const char* msg, bool err) {
|
||||
EM_ASM({
|
||||
if($1) {
|
||||
throw Error(UTF8ToString($0));
|
||||
return;
|
||||
}
|
||||
throw UTF8ToString($0);
|
||||
},msg, err);
|
||||
}
|
||||
int main() {
|
||||
//vosk_set_log_level(-1);
|
||||
std::thread t{[](){
|
||||
wasmfs_create_directory("/opfs",0777,wasmfs_create_opfs_backend());
|
||||
}};
|
||||
t.detach();
|
||||
}
|
||||
9
src/global.h
Normal file
9
src/global.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/wasmfs.h>
|
||||
|
||||
void throwJS(const char* msg, bool err = false);
|
||||
int main();
|
||||
@@ -1,29 +1,34 @@
|
||||
// @externs
|
||||
let objs = []
|
||||
class Recognizer extends EventTarget {
|
||||
constructor(rec) {
|
||||
super()
|
||||
this.obj = rec
|
||||
objs.push(this)
|
||||
this.ptr = Module._malloc(512)
|
||||
this.arr = Module.HEAPF32.subarray(this.ptr, this.ptr+512)
|
||||
}
|
||||
getNode(ctx) {
|
||||
let channel = new MessageChannel()
|
||||
this.node = new AudioWorkletNode(ctx, 'BRProcessor', { channelCount: 1, numberOfInputs: 1, numberOfOutputs: 1 })
|
||||
node.port.postMessage({cmd : "init", ptr: this.ptr},[channel.port1])
|
||||
channel.port1.onmessage = (ev) => {
|
||||
this.obj.acceptWaveForm(this.ptr, 512)
|
||||
}
|
||||
return this.node
|
||||
async getNode(ctx, channelIndex = 0) {
|
||||
if(typeof this.node === "undefined") {
|
||||
let msgChannel = new MessageChannel()
|
||||
ctx.AudioWorklet.addModule("src/processor.js")
|
||||
this.node = new AudioWorkletNode(ctx, 'BRProcessor', { channelCountMode: "max", numberOfInputs: 1, numberOfOutputs: 1 })
|
||||
this.node.port.postMessage({cmd : "init", ptr: this.ptr, channel: channelIndex}, [msgChannel.port1])
|
||||
msgChannel.port1.onmessage = (ev) => {
|
||||
this.obj.acceptWaveForm()
|
||||
}
|
||||
return this.node
|
||||
}
|
||||
}
|
||||
recognize(buf) {
|
||||
buf.copyFromChannel()
|
||||
this.obj.acceptWaveForm(this.ptr, 512)
|
||||
recognize(buf, channelIndex = 0) {
|
||||
buf.copyFromChannel(this.arr, channelIndex)
|
||||
this.obj.acceptWaveForm()
|
||||
}
|
||||
delete() {
|
||||
this.obj.delete()
|
||||
if(typeof this.node !== "undefined") {
|
||||
this.node.port.postMessage({cmd : "deinit"})
|
||||
}
|
||||
Module.free(this.ptr)
|
||||
}
|
||||
setWords(words) {
|
||||
this.obj.setWords(words)
|
||||
@@ -46,7 +51,6 @@ class Recognizer extends EventTarget {
|
||||
}
|
||||
Module.deleteAll = () => {
|
||||
objs.forEach(obj => obj.delete())
|
||||
ctx.close()
|
||||
}
|
||||
Module.makeModel = async (url, path, id) => {
|
||||
let mdl
|
||||
@@ -71,11 +75,11 @@ Module.makeSpkModel = async (url, path, id) => {
|
||||
}
|
||||
objs.push(mdl)
|
||||
return mdl
|
||||
}, ctx.AudioWorklet
|
||||
Module.makeRecognizer = async (model) => {
|
||||
}
|
||||
Module.makeRecognizer = async (model, sampleRate) => {
|
||||
let rec
|
||||
try {
|
||||
rec = new Module.recognizer(model, ctx.sampleRate, objs.length)
|
||||
rec = new Module.recognizer(model, sampleRate, objs.length)
|
||||
}
|
||||
catch(e) {
|
||||
rec.delete()
|
||||
27
src/pre2.js
27
src/pre2.js
@@ -1,27 +0,0 @@
|
||||
// A copy and pass processor
|
||||
registerProcessor("BRProcessor", class extends AudioWorkletProcessor {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
this.ret = true
|
||||
this.port.onmessage = (ev) => {
|
||||
switch(ev.cmd) {
|
||||
case "init":
|
||||
this.recognizerPort = ev.ports[0]
|
||||
this.wasmMem = new Float32Array(WebAssembly.Memory.buffer).subarray(ev.ptr, ev.ptr+512)
|
||||
this.channel = ev.channel
|
||||
this.input = ev.input
|
||||
break
|
||||
case "deinit":
|
||||
this.ret = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
process(inputs, outputs, params) {
|
||||
if(!this.ret) return false;
|
||||
inputs[this.input].copyFromChannel(this.wasmMem, this.channel)
|
||||
outputs = inputs
|
||||
this.recognizerPort.postMessage(".") // A
|
||||
return true
|
||||
}
|
||||
})
|
||||
28
src/processor.js
Normal file
28
src/processor.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// A copy and pass processor, check if already registered
|
||||
if(typeof BRProcessor === "undefined") {
|
||||
var BRProcessor = class extends AudioWorkletProcessor {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
this.done = false
|
||||
this.port.onmessage = (ev) => {
|
||||
switch(ev.cmd) {
|
||||
case "init":
|
||||
this.recognizerPort = ev.ports[0]
|
||||
this.wasmMem = new Float32Array(WebAssembly.Memory.buffer).subarray(ev.ptr, ev.ptr+512)
|
||||
break
|
||||
case "deinit":
|
||||
this.done = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
process(inputs, outputs, params) {
|
||||
if(this.done) return false;
|
||||
inputs[0].copyFromChannel(this.wasmMem, this.channel)
|
||||
this.recognizerPort.postMessage(".") // Basically an empty message
|
||||
outputs = inputs
|
||||
return true
|
||||
}
|
||||
}
|
||||
registerProcessor("BRProcessor", BRProcessor)
|
||||
}
|
||||
@@ -6,31 +6,35 @@ recognizer::recognizer(model* mdl, float sampleRate, int index) : index(index) {
|
||||
return;
|
||||
}
|
||||
controller.lock();
|
||||
std::thread t{[this](){
|
||||
std::thread t{[this](const pthread_t& caller){
|
||||
while(!done.test()) {
|
||||
controller.lock();
|
||||
if(!done.test()) {
|
||||
switch(vosk_recognizer_accept_waveform_f(rec, dataPtr, 512)) {
|
||||
case 0:
|
||||
fireEv("result", vosk_recognizer_result(rec));
|
||||
fireEv("result", vosk_recognizer_result(rec), caller);
|
||||
break;
|
||||
case 1:
|
||||
fireEv("partialResult", vosk_recognizer_partial_result(rec));
|
||||
fireEv("partialResult", vosk_recognizer_partial_result(rec), caller);
|
||||
}
|
||||
}
|
||||
}
|
||||
}};
|
||||
},pthread_self()};
|
||||
t.detach();
|
||||
}
|
||||
recognizer::~recognizer() {
|
||||
done.test_and_set(std::memory_order_relaxed);
|
||||
controller.unlock();
|
||||
vosk_recognizer_free(rec);
|
||||
free(dataPtr);
|
||||
}
|
||||
void recognizer::fireEv(const char *type, const char *content) {
|
||||
EM_ASM({
|
||||
recognizers[$0].dispatchEvent(new CustomEvent(UTF8ToString($1), {"details" : UTF8ToString($2)}));
|
||||
},this->index, type, content);
|
||||
void recognizer::fireEv(const char *type, const char *content, const pthread_t& caller) {
|
||||
static ProxyingQueue pq{};
|
||||
pq.proxyAsync(caller, [&](){
|
||||
EM_ASM({
|
||||
objs[$0].dispatchEvent(new CustomEvent(UTF8ToString($1), {"details" : UTF8ToString($2)}));
|
||||
},index, type, content);
|
||||
});
|
||||
}
|
||||
void recognizer::acceptWaveForm() {
|
||||
controller.unlock();
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
#pragma once
|
||||
#include "model.h"
|
||||
#include "spkModel.h"
|
||||
#include "global.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
#include <emscripten/wasmfs.h>
|
||||
#include <emscripten/webaudio.h>
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
extern void throwJS(const char* msg, bool err = false);
|
||||
#include <emscripten/proxying.h>
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using namespace emscripten;
|
||||
struct recognizer {
|
||||
std::atomic_flag done{};
|
||||
std::mutex controller{};
|
||||
@@ -24,7 +21,7 @@ struct recognizer {
|
||||
recognizer(model* model, float sampleRate, int index);
|
||||
~recognizer();
|
||||
void acceptWaveForm();
|
||||
void fireEv(const char* type, const char* content);
|
||||
void fireEv(const char* type, const char* content, const pthread_t& caller);
|
||||
void setSpkModel(spkModel* model);
|
||||
void setGrm(const std::string& grm);
|
||||
void setWords(bool words);
|
||||
|
||||
Reference in New Issue
Block a user