diff --git a/js/api.js b/js/api.js index 8a93477..715f9b5 100644 --- a/js/api.js +++ b/js/api.js @@ -383,9 +383,10 @@ class Decoder { this.assert_initialized(); const pcm_bytes = pcm.length * pcm.BYTES_PER_ELEMENT; const pcm_addr = Module._malloc(pcm_bytes); - const pcm_u8 = new Uint8Array(pcm.buffer); - // Emscripten documentation fails to mention that this - // function specifically takes a Uint8Array + // This Javascript API is rather stupid. DO NOT forget byteOffset and length. + const pcm_u8 = new Uint8Array(pcm.buffer, pcm.byteOffset, pcm_bytes); + // Emscripten documentation (what documentation) fails to + // mention that this function specifically takes a Uint8Array writeArrayToMemory(pcm_u8, pcm_addr); const rv = Module._decoder_process_float32(this.cdecoder, pcm_addr, pcm_bytes / 4, no_search, full_utt); diff --git a/js/package.json b/js/package.json index dc66855..966e353 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "soundswallower", - "version": "0.4.1", + "version": "0.4.2", "description": "An even smaller speech recognizer", "main": "soundswallower.js", "scripts": { diff --git a/js/soundswallower.spec.js b/js/soundswallower.spec.js index 72b841c..32dc034 100644 --- a/js/soundswallower.spec.js +++ b/js/soundswallower.spec.js @@ -1,3 +1,4 @@ +/* A bunch of junk to make this work in Node and Web */ const ENVIRONMENT_IS_WEB = typeof(window) !== "undefined"; let assert, load_binary_file; if (ENVIRONMENT_IS_WEB) { @@ -20,36 +21,37 @@ else { } assert = require('assert'); } -const ssjs = {}; + +const soundswallower = {}; before(async () => { if (ENVIRONMENT_IS_WEB) { - await createModule(ssjs); + await createModule(soundswallower); } else { - await require("./soundswallower.js")(ssjs); + await require("./soundswallower.js")(soundswallower); } }); describe("Test initialization", () => { it("Should load the WASM module", () => { - assert.ok(ssjs); + assert.ok(soundswallower); }); }); describe("Test decoder initialization", () => { it("Should initialize the decoder", async () => { - let decoder = new ssjs.Decoder({ + let decoder = new soundswallower.Decoder({ fsg: "testdata/goforward.fsg", samprate: 16000, }); assert.ok(decoder); assert.equal(decoder.get_config("hmm"), - ssjs.get_model_path(ssjs.defaultModel)); + soundswallower.get_model_path(soundswallower.defaultModel)); decoder.delete(); }); }); describe("Test configuration as JSON", () => { it("Should contain default configuration", () => { - let decoder = new ssjs.Decoder({ + let decoder = new soundswallower.Decoder({ fsg: "testdata/goforward.fsg", samprate: 16000, }); @@ -58,14 +60,14 @@ describe("Test configuration as JSON", () => { let config = JSON.parse(json); assert.ok(config); assert.equal(config.hmm, - ssjs.get_model_path(ssjs.defaultModel)); + soundswallower.get_model_path(soundswallower.defaultModel)); assert.equal(config.loglevel, "WARN"); assert.equal(config.fsg, "testdata/goforward.fsg"); }); }); describe("Test acoustic model loading", () => { it('Should load acoustic model', async () => { - let decoder = new ssjs.Decoder(); + let decoder = new soundswallower.Decoder(); await decoder.init_featparams(); await decoder.init_fe(); await decoder.init_feat(); @@ -77,29 +79,34 @@ describe("Test acoustic model loading", () => { }); describe("Test decoding", () => { it('Should recognize "go forward ten meters"', async () => { - let decoder = new ssjs.Decoder({ + let decoder = new soundswallower.Decoder({ fsg: "testdata/goforward.fsg", samprate: 16000, }); await decoder.initialize(); let pcm = await load_binary_file("testdata/goforward-float32.raw"); await decoder.start(); - await decoder.process(pcm, false, true); + // 128-sample buffers, because fuck you, that's why + for (let pos = 0; pos < pcm.length; pos += 128) { + let len = pcm.length - pos; + if (len > 128) + len = 128; + await decoder.process(pcm.subarray(pos, pos + len), false, false); + } await decoder.stop(); assert.equal("go forward ten meters", decoder.get_hyp()); let hypseg = decoder.get_hypseg(); let hypseg_words = [] for (const seg of hypseg) { assert.ok(seg.end >= seg.start); - hypseg_words.push(seg.word); + if (seg.word != "" && seg.word != "(NULL)") + hypseg_words.push(seg.word); } - assert.deepStrictEqual(hypseg_words, - ["", "go", "forward", - "(NULL)", "ten", "meters"]); + assert.equal(hypseg_words.join(" "), "go forward ten meters"); decoder.delete(); }); it('Should accept Float32Array as well as UInt8Array', async () => { - let decoder = new ssjs.Decoder({ + let decoder = new soundswallower.Decoder({ fsg: "testdata/goforward.fsg", samprate: 16000 }); @@ -113,7 +120,7 @@ describe("Test decoding", () => { decoder.delete(); }); it('Should align "go forward ten meters"', async () => { - let decoder = new ssjs.Decoder({ + let decoder = new soundswallower.Decoder({ samprate: 16000, }); await decoder.initialize(); @@ -154,7 +161,7 @@ describe("Test decoding", () => { }); describe("Test dictionary and FSG", () => { it('Should recognize "_go _forward _ten _meters"', async () => { - let decoder = new ssjs.Decoder({samprate: 16000}); + let decoder = new soundswallower.Decoder({samprate: 16000}); decoder.unset_config("dict"); await decoder.initialize(); await decoder.add_word("_go", "G OW", false); @@ -177,7 +184,7 @@ describe("Test dictionary and FSG", () => { }); describe("Test reinitialization", () => { it('Should recognize "go forward ten meters"', async () => { - let decoder = new ssjs.Decoder({samprate: 16000}); + let decoder = new soundswallower.Decoder({samprate: 16000}); await decoder.initialize(); await decoder.add_word("_go", "G OW", false); await decoder.add_word("_forward", "F AO R W ER D", false); @@ -190,7 +197,7 @@ describe("Test reinitialization", () => { {from: 3, to: 4, prob: 1.0, word: "_meters"} ]); decoder.set_config("dict", - ssjs.get_model_path(ssjs.defaultModel) + "/dict.txt"); + soundswallower.get_model_path(soundswallower.defaultModel) + "/dict.txt"); decoder.set_config("fsg", "testdata/goforward.fsg"); await decoder.initialize(); let pcm = await load_binary_file("testdata/goforward-float32.raw"); @@ -203,7 +210,7 @@ describe("Test reinitialization", () => { }); describe("Test loading model for other language", () => { it('Should recognize "avance de dix mètres"', async () => { - let decoder = new ssjs.Decoder({hmm: ssjs.get_model_path("fr-fr"), + let decoder = new soundswallower.Decoder({hmm: soundswallower.get_model_path("fr-fr"), samprate: 16000}); await decoder.initialize(); await decoder.set_fsg("goforward", 0, 4, [ @@ -234,7 +241,7 @@ describe("Test loading model for other language", () => { }); describe("Test JSGF", () => { it('Should recognize "yo gimme four large all dressed pizzas"', async () => { - let decoder = new ssjs.Decoder({ + let decoder = new soundswallower.Decoder({ jsgf: "testdata/pizza.gram", samprate: 16000 }); @@ -249,7 +256,7 @@ describe("Test JSGF", () => { }); describe("Test JSGF string", () => { it('Should recognize "yo gimme four large all dressed pizzas"', async () => { - let decoder = new ssjs.Decoder({samprate: 16000}); + let decoder = new soundswallower.Decoder({samprate: 16000}); await decoder.initialize(); await decoder.set_jsgf(`#JSGF V1.0; grammar pizza; @@ -273,7 +280,7 @@ public = [] [] [] [] [