Nothing is working but it is on the right track! ASYNCIFY removed, manual waiting instead and some proxying logic

This commit is contained in:
msqr1
2024-01-28 00:09:16 -08:00
parent efb56e19f9
commit 8796f35445
22 changed files with 239 additions and 7375 deletions

View File

@@ -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")

View File

@@ -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);

View File

@@ -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
View 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
View 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();

View File

@@ -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()

View File

@@ -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
}
})

View File

28
src/processor.js Normal file
View 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)
}

View File

@@ -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();

View File

@@ -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);