Draft 2 (failed)

This commit is contained in:
msqr1
2024-01-18 23:47:10 -08:00
parent 97ba7ee1b8
commit 91a21271d5
22 changed files with 350 additions and 299 deletions

19
.gitignore vendored
View File

@@ -1,10 +1,11 @@
.vscode
test.sh
index.html
model.tzst
clapack-wasm
kaldi
libarchive
vosk-api
zstd
test.cc
vosk-api
kaldi
test
test.sh
libarchive
.vscode
clapack-wasm
node_modules
package.json
package-lock.json

File diff suppressed because one or more lines are too long

View File

@@ -12,34 +12,6 @@
var Module = {};
// Node.js support
var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string';
if (ENVIRONMENT_IS_NODE) {
// Create as web-worker-like an environment as we can.
var nodeWorkerThreads = require('worker_threads');
var parentPort = nodeWorkerThreads.parentPort;
parentPort.on('message', (data) => onmessage({ data: data }));
var fs = require('fs');
var vm = require('vm');
Object.assign(global, {
self: global,
require,
Module,
location: {
href: __filename
},
Worker: nodeWorkerThreads.Worker,
importScripts: (f) => vm.runInThisContext(fs.readFileSync(f, 'utf8'), {filename: f}),
postMessage: (msg) => parentPort.postMessage(msg),
performance: global.performance || { now: Date.now },
});
}
// Thread-local guard variable for one-time init of the JS state
var initializedJS = false;
@@ -49,11 +21,6 @@ function assert(condition, text) {
function threadPrintErr() {
var text = Array.prototype.slice.call(arguments).join(' ');
// See https://github.com/emscripten-core/emscripten/issues/14804
if (ENVIRONMENT_IS_NODE) {
fs.writeSync(2, text + '\n');
return;
}
console.error(text);
}
function threadAlert() {
@@ -97,6 +64,7 @@ function handleMessage(e) {
// And add a callback for when the runtime is initialized.
self.startWorker = (instance) => {
Module = instance;
// Notify the main thread that this thread has loaded.
postMessage({ 'cmd': 'loaded' });
// Process any messages that were queued before the thread was ready.
@@ -133,6 +101,7 @@ function handleMessage(e) {
importScripts(objectUrl);
URL.revokeObjectURL(objectUrl);
}
loadBR(Module);
} else if (e.data.cmd === 'run') {
// Pass the thread address to wasm to store it for fast access.
Module['__emscripten_thread_init'](e.data.pthread_ptr, /*is_main=*/0, /*is_runtime=*/0, /*can_block=*/1);

View File

@@ -25,7 +25,7 @@ spkModel.init(url, storepath, id)
- ***delete***: Delete self and free resources
#### Events
- ***ready***: The model is ready to be put into a recognizer via the constructor, or setSpkModel() for SpkModel.
- ***error***: An error occured, check the event's **details** property.
- ***error***: An error occured, check the event's "details" property.
### Recognizer
```
recognizer = new Recognizer()
@@ -43,11 +43,11 @@ recognizer.init(model)
- ***setMaxAlternatives***: Set the max number of alternatives for result event (default: false)
- ***setGrm***: Add grammar to the recognizer (default: none)
- ***setSpkModel***: Set the speaker model of the recognizer (default: none)
- ***delete***: Delete self and free resources
- ***delete***: Call stop, delete self and free all resource
#### Events
- ***partialResult***: There is a partial recognition result, check the event's **details** property
- ***result***: There is a full recognition result, check the event's **details** property
- ***error***: An error occured, check the event's **details** property.
- ***partialResult***: There is a partial recognition result, check the event's "details" property
- ***result***: There is a full recognition result, check the event's "details" property
- ***error***: An error occured, check the event's "details" property.
## Other key points
### IMPORTANT
- You MUST call delete() on objects at the end of its usage. Or put:
@@ -56,7 +56,8 @@ recognizer.init(model)
__genericObj__.objects.forEach(obj => obj.delete())
```
at the end of your program to automatically do that. We have to do this because Emscripten doesn't call destructors. See [here](https://emscripten.org/docs/getting_started/FAQ.html#what-does-exiting-the-runtime-mean-why-don-t-atexit-s-run).
- To be safe, always handle the API through events by adding all event listener before calling init().
- To be safe, always handle the API through events by adding all event listener before calling init() on objects.
- Always call init on the regular Model object before calling init on the recognizer. SpkModel can be init and set later.
### Guarantees
- If an error occurs (error event is fired), no changes was made, and no other dependent events will fire. For example, if an error occur while loading the model, the "ready" event won't fire in order to prevent executing code on a nonexistent model.
### Limitations compared to vosk-browser:

13
index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<script src="BrowserRecognizer.js" defer></script>
<script type="module" src="src/genericObj.js" defer></script>
<script src="src/model.js" defer></script>
<script src="src/spkModel.js" defer></script>
<script src="src/recognizer.js" defer></script>
</head>
<script>
</script>
</html>

View File

@@ -8,6 +8,8 @@
# Total build time is around 45 minutes, mostly from building Kaldi
sudo apt install shtool libtool autogen autotools-dev pkg-config make &&
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash &&
nvm install 20.11.0 &&
SRC=$(realpath src) &&
KALDI=$(realpath kaldi) &&
@@ -52,7 +54,7 @@ echo "PACKAGE_VERSION = 1.8.0" >> $OPENFST/Makefile &&
cd $KALDI/src &&
git apply $SRC/kaldi.patch &&
CXXFLAGS="-O3 -msse3 -mssse3 -msse4.1 -msse4.2 -mavx -msimd128 -UHAVE_EXECINFO_H -pthread -flto" LDFLAGS="-O3 -sERROR_ON_UNDEFINED_SYMBOLS=0 -lembind -pthread -flto" emconfigure ./configure --use-cuda=no --with-cudadecoder=no --static --static-math=yes --static-fst=yes --double-precision=yes --debug-level=0 --clapack-root=$CLAPACK_WASM --host=WASM &&
CXXFLAGS="-O3 -msimd128 -UHAVE_EXECINFO_H -pthread -flto" LDFLAGS="-O3 -sERROR_ON_UNDEFINED_SYMBOLS=0 -lembind -pthread -flto" emconfigure ./configure --use-cuda=no --with-cudadecoder=no --static --static-math=yes --static-fst=yes --debug-level=0 --clapack-root=$CLAPACK_WASM --host=WASM &&
emmake make online2 lm rnnlm &&
cd $VOSK/src &&
@@ -61,4 +63,4 @@ em++ -pthread -O3 -flto -I. -I$KALDI/src -I$OPENFST/include $VOSK_FILES -c &&
emar -rcs vosk.a ${VOSK_FILES//.cc/.o} &&
cd $SRC
em++ -O3 genericObj.cc genericModel.cc model.cc spkModel.cc recognizer.cc bindings.cc -sWASMFS -sWASM_BIGINT -sSUPPORT_BIG_ENDIAN -sSINGLE_FILE -sINITIAL_MEMORY=300mb -sASYNCIFY -sPTHREAD_POOL_SIZE=2 -pthread --no-entry -flto --post-js genericObj.js --post-js model.js --post-js spkModel.js --post-js recognizer.js -I. -I$LIBARCHIVE/include -I$VOSK/src -L$LIBARCHIVE/lib -larchive -L$ZSTD/lib -lzstd -L$KALDI/src -l:online2/kaldi-online2.a -l:decoder/kaldi-decoder.a -l:ivector/kaldi-ivector.a -l:gmm/kaldi-gmm.a -l:tree/kaldi-tree.a -l:feat/kaldi-feat.a -l:cudamatrix/kaldi-cudamatrix.a -l:lat/kaldi-lat.a -l:lm/kaldi-lm.a -l:rnnlm/kaldi-rnnlm.a -l:hmm/kaldi-hmm.a -l:nnet3/kaldi-nnet3.a -l:transform/kaldi-transform.a -l:matrix/kaldi-matrix.a -l:fstext/kaldi-fstext.a -l:util/kaldi-util.a -l:base/kaldi-base.a -L$OPENFST/lib -l:libfst.a -l:libfstngram.a -L$CLAPACK_WASM -l:CBLAS/lib/cblas.a -l:CLAPACK-3.2.1/lapack.a -l:CLAPACK-3.2.1/libcblaswr.a -l:f2c_BLAS-3.8.0/blas.a -l:libf2c/libf2c.a -L$VOSK/src -l:vosk.a -lopfs.js -lembind -lopenal -o BrowserRecognizer.js
em++ -O3 genericObj.cc genericModel.cc model.cc spkModel.cc recognizer.cc bindings.cc -sWASMFS -sWASM_BIGINT -sSUPPORT_BIG_ENDIAN -sSINGLE_FILE -sMODULARIZE -sASYNCIFY -sEXPORT_NAME=loadBR -sENVIRONMENT=web,worker -sINITIAL_MEMORY=300mb -sPTHREAD_POOL_SIZE=2 -pthread -flto -I. -I$LIBARCHIVE/include -I$VOSK/src -L$LIBARCHIVE/lib -larchive -L$ZSTD/lib -lzstd -L$KALDI/src -l:online2/kaldi-online2.a -l:decoder/kaldi-decoder.a -l:ivector/kaldi-ivector.a -l:gmm/kaldi-gmm.a -l:tree/kaldi-tree.a -l:feat/kaldi-feat.a -l:cudamatrix/kaldi-cudamatrix.a -l:lat/kaldi-lat.a -l:lm/kaldi-lm.a -l:rnnlm/kaldi-rnnlm.a -l:hmm/kaldi-hmm.a -l:nnet3/kaldi-nnet3.a -l:transform/kaldi-transform.a -l:matrix/kaldi-matrix.a -l:fstext/kaldi-fstext.a -l:util/kaldi-util.a -l:base/kaldi-base.a -L$OPENFST/lib -l:libfst.a -l:libfstngram.a -L$CLAPACK_WASM -l:CBLAS/lib/cblas.a -l:CLAPACK-3.2.1/lapack.a -l:CLAPACK-3.2.1/libcblaswr.a -l:f2c_BLAS-3.8.0/blas.a -l:libf2c/libf2c.a -L$VOSK/src -l:vosk.a -lopfs.js -lembind -lopenal -o ../BrowserRecognizer.js

View File

@@ -2,6 +2,13 @@
#include "model.h"
#include "recognizer.h"
using namespace emscripten;
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__")
@@ -14,7 +21,6 @@ EMSCRIPTEN_BINDINGS() {
.constructor<model*, int, int>(allow_raw_pointers())
.function("start", &recognizer::start, allow_raw_pointers())
.function("stop", &recognizer::stop, allow_raw_pointers())
.function("deinit", &recognizer::deinit, allow_raw_pointers())
.function("setWords", &recognizer::setWords, allow_raw_pointers())
.function("setPartialWords", &recognizer::setPartialWords, allow_raw_pointers())
.function("setGrm", &recognizer::setGrm, allow_raw_pointers())

16
src/clapack-wasm.patch Normal file
View File

@@ -0,0 +1,16 @@
diff --git a/libf2c/main.c b/libf2c/main.c
index d95fdc9..ac82f68 100644
--- a/libf2c/main.c
+++ b/libf2c/main.c
@@ -105,9 +105,9 @@ char **xargv;
int
#ifdef KR_headers
-main(argc, argv) int argc; char **argv;
+m(argc, argv) int argc; char **argv;
#else
-main(int argc, char **argv)
+m(int argc, char **argv)
#endif
{
xargc = argc;

View File

@@ -1,21 +1,9 @@
#include "genericModel.h"
bool genericModel::first = true;
genericModel::genericModel(const std::string &url, const std::string& storepath, const std::string &id, int index) : url(url), id(id), genericObj(index) {
fs::current_path("/opfs");
fs::create_directories(storepath);
fs::current_path(storepath);
if(first) {
vosk_set_log_level(0);
int res{};
std::thread t{[&res](){
res = wasmfs_create_directory("opfs",0777,wasmfs_create_opfs_backend());
}};
t.join();
if(res == 1){
fireEv("error", "Unable to create OPFS directory");
return;
}
first = false;
}
}
bool genericModel::checkId(const std::string& id) {
std::ifstream file {"id", std::ifstream::binary};
@@ -28,42 +16,46 @@ bool genericModel::checkId(const std::string& id) {
file.read(&oldid[0], size);
return id.compare(oldid) == 0 ? true : false;
}
bool genericModel::loadModel() {
bool genericModel::loadModel(const std::string& storepath) {
if(!checkModel() || !checkId(id)) {
if(emscripten_wget(url.c_str(),"opfs/model.tzst") == 1) {
char filename[] {"/opfs/XXXXXX.tzst"};
close(mkostemps(filename, 5, O_PATH));
if(emscripten_wget(url.c_str(),filename) == 1) {
fireEv("error", "Unable to fetch model");
return false;
}
if(!extractModel()) {
if(!extractModel(filename)) {
fireEv("error", "Unable to extract model");
return false;
}
fs::remove("opfs/model.tzst");
fs::remove(filename);
if(!checkModel()) {
fireEv("error", "Model URL contains invalid model files");
fs::remove_all(".");
fs::current_path("/opfs");
fs::remove_all(storepath);
return false;
}
std::ofstream idFile("id");
if(!idFile.is_open()) {
fireEv("error", "Unable to write new id");
fs::remove_all(".");
fs::remove_all(storepath);
return false;
}
idFile << id;
}
return true;
}
bool genericModel::extractModel() {
bool genericModel::extractModel(char* name) {
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, "opfs/model.tzst",10240);
archive_read_open_filename(src, name,10240);
if(archive_errno(src) != 0) return false;
while (archive_read_next_header(src, &entry) == ARCHIVE_OK) {
path = archive_entry_pathname(entry);
// Strip first component
archive_entry_set_pathname(entry, path.substr(path.find("/")).c_str());
if(archive_errno(src) != 0) return false;
archive_read_extract(src, entry, ARCHIVE_EXTRACT_UNLINK);

View File

@@ -6,6 +6,7 @@
#include <fstream>
#include <thread>
#include <fcntl.h>
#include <vosk_api.h>
#include <archive.h>
#include <archive_entry.h>
@@ -15,12 +16,11 @@
namespace fs = std::filesystem;
struct genericModel : genericObj {
static bool first;
const std::string url{};
const std::string id{};
static bool extractModel();
static bool extractModel(char *name);
static bool checkId(const std::string& id);
virtual bool checkModel() = 0;
bool loadModel();
genericModel(const std::string& url, const std::string& storepath, const std::string& id, int index);
bool loadModel(const std::string& storepath);
genericModel(const std::string &url, const std::string &storepath, const std::string &id, int index);
};

View File

@@ -1,13 +1,11 @@
#include "genericObj.h"
void genericObj::fireEv(const char *type, const char *content) {
if(content == nullptr) {
EM_ASM({
__genericObj__.objects[$0].dispatchEvent(new Event(UTF8ToString($1)));
},this->index, type);
return;
}
EM_ASM({
__genericObj__.objects[$0].dispatchEvent(new CustomEvent(UTF8ToString($0), {"details" : UTF8ToString($1)}));
if($0 === 0) {
__genericObj__.objects[$0].dispatchEvent(new Event(UTF8ToString($1)));
return;
}
__genericObj__.objects[$0].dispatchEvent(new CustomEvent(UTF8ToString($1), {"details" : UTF8ToString($2)}));
},this->index, type, content);
}

View File

@@ -1 +1,2 @@
class __genericObj__ {static objects = []}
class __genericObj__ {static objects = []}

View File

@@ -25,18 +25,7 @@ index 4573e24f1..4af4e73ea 100644
MultiThreadable::~MultiThreadable() {
// default implementation does nothing
diff --git a/src/base/kaldi-types.h b/src/base/kaldi-types.h
index 7ebf4f853..2f5979e42 100644
--- a/src/base/kaldi-types.h
+++ b/src/base/kaldi-types.h
@@ -20,6 +20,7 @@
#ifndef KALDI_BASE_KALDI_TYPES_H_
#define KALDI_BASE_KALDI_TYPES_H_ 1
+#define KALDI_DOUBLEPRECISION 1
namespace kaldi {
// TYPEDEFS ..................................................................
diff --git a/src/ivector/ivector-extractor.cc b/src/ivector/ivector-extractor.cc
index c3a122281..71d37256d 100644
--- a/src/ivector/ivector-extractor.cc

View File

@@ -1,7 +1,7 @@
#include "model.h"
model::model(const std::string &url, const std::string& storepath, const std::string& id, int index) : genericModel(url, id, storepath, index) {
if(!loadModel()) return;
if(!loadModel(storepath)) return;
mdl = vosk_model_new(".");
if(mdl == nullptr) {
fireEv("error", "Unable to initialize model");
@@ -9,7 +9,9 @@ model::model(const std::string &url, const std::string& storepath, const std::st
}
fireEv("ready");
};
model::~model() {
vosk_model_free(mdl);
}
bool model::checkModel() {
return fs::exists("am/final.mdl") &&
fs::exists("conf/mfcc.conf") &&

View File

@@ -5,6 +5,7 @@ struct model : genericModel {
bool checkModel();
VoskModel* mdl{};
model(const std::string &url, const std::string& storepath, const std::string& id, int index);
~model();
};

View File

@@ -3,12 +3,10 @@ class Model extends EventTarget{
super()
}
init(url, storepath, id) {
this.obj = new Module.__model__(url, storepath, id, __genericObj__.objects.length);
this.obj = new BrowserRecognizer.__model__(url, storepath, id, __genericObj__.objects.length);
__genericObj__.objects.push(this)
}
delete() {
this.obj.then(() => {
this.obj.delete()
})
this.obj.delete()
}
}

View File

@@ -7,11 +7,6 @@ void recognizer::stop() {
controller.clear(std::memory_order_relaxed);
controller.notify_all();
}
void recognizer::deinit() {
done.test_and_set(std::memory_order_relaxed);
done.notify_all();
stop();
}
recognizer::recognizer(model* mdl, int sampleRate, int index) : genericObj(index) {
mic = alcCaptureOpenDevice("Emscripten OpenAL capture",sampleRate, AL_FORMAT_MONO16, 22480);
if(alcGetError(mic) != 0) {
@@ -25,32 +20,15 @@ recognizer::recognizer(model* mdl, int sampleRate, int index) : genericObj(index
}
main();
}
void recognizer::main() {
char buffer[22480];
int sample{};
fireEv("ready");
while(!done.test()) {
controller.wait(done.test(std::memory_order_relaxed), std::memory_order_relaxed);
alcCaptureStart(mic);
while(controller.test()) {
alcGetIntegerv(mic, ALC_CAPTURE_SAMPLES, sizeof(int), &sample);
alcCaptureSamples(mic, buffer, sample);
switch(vosk_recognizer_accept_waveform(rec, buffer, 22480)) {
case 0:
fireEv("result", vosk_recognizer_result(rec));
break;
case 1:
fireEv("partialResult", vosk_recognizer_partial_result(rec));
break;
default:
fireEv("error", "Recognition result error");
}
}
alcCaptureStop(mic);
}
recognizer::~recognizer() {
done.test_and_set(std::memory_order_relaxed);
done.notify_all();
stop();
vosk_recognizer_free(rec);
alcCaptureCloseDevice(mic);
}
void recognizer::acceptWaveForm() {
}
void recognizer::setGrm(const std::string& grm) {
vosk_recognizer_set_grm(rec, grm.c_str());
}

View File

@@ -19,13 +19,11 @@ namespace fs = std::filesystem;
struct recognizer : genericObj {
VoskRecognizer* rec{};
ALCdevice* mic{};
std::atomic_flag done {false};
std::atomic_flag controller{false};
void main();
void acceptWaveForm();
recognizer(model* model, int sampleRate, int index);
~recognizer();
void start();
void stop();
void deinit();
void setSpkModel(spkModel* model);
void setGrm(const std::string& grm);
void setWords(bool words);

View File

@@ -4,7 +4,7 @@ class Recognizer extends EventTarget {
}
init(model) {
ctx = new (AudioContext || webkitAudioContext)()
new Module.__recognizer__(model.obj,ctx.sampleRate,__genericObj__.objects.length)
new BrowserRecognizer.__recognizer__(model.obj,ctx.sampleRate,__genericObj__.objects.length)
ctx.close()
__genericObj__.objects.push(this)
}
@@ -15,7 +15,6 @@ class Recognizer extends EventTarget {
this.obj.stop()
}
delete() {
this.obj.deinit()
this.obj.delete()
}
setWords(words) {

View File

@@ -1,12 +1,15 @@
#include "spkModel.h"
spkModel::spkModel(const std::string &url, const std::string& storepath, const std::string& id, int index) : genericModel(url, storepath, id, index) {
if(!loadModel()) return;
if(!loadModel(storepath)) return;
mdl = vosk_spk_model_new(".");
if(mdl == nullptr) {
fireEv("error", "Unable to initialize speaker model");
}
fireEv("ready");
};
spkModel::~spkModel() {
vosk_spk_model_free(mdl);
}
bool spkModel::checkModel() {
return fs::exists("mfcc.conf") &&
fs::exists("final.ext.raw") &&

View File

@@ -3,8 +3,9 @@
struct spkModel : genericModel {
bool checkModel();
spkModel(const std::string &url, const std::string& storepath, const std::string& id, const int index);
VoskSpkModel* mdl{};
spkModel(const std::string &url, const std::string& storepath, const std::string& id, const int index);
~spkModel();
};

View File

@@ -3,7 +3,7 @@ class SpkModel extends EventTarget{
super()
}
init(url, storepath, id) {
this.obj = new Module.__spkModel__(url, storepath, id, __genericObj__.objects.length)
this.obj = new BrowserRecognizer.__spkModel__(url, storepath, id, __genericObj__.objects.length)
__genericObj__.objects.push(this)
}
delete() {