From c9a7a14a4b440ed1ed4476c987d2c472f64006f7 Mon Sep 17 00:00:00 2001 From: David Huggins-Daines Date: Mon, 19 Dec 2022 11:30:00 -0500 Subject: [PATCH 01/13] feat!: make API cleaner and more idiomatic --- js/soundswallower.d.ts | 44 +++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/js/soundswallower.d.ts b/js/soundswallower.d.ts index c74840b..d41e20c 100644 --- a/js/soundswallower.d.ts +++ b/js/soundswallower.d.ts @@ -16,18 +16,11 @@ export class Decoder { no_search?: boolean, full_utt?: boolean ): number; - get_hyp(): string; - get_hypseg(): Array; - get_alignment_json(start?: number, align_level?: number): string; + get_sentence(): string; + get_alignment(start?: number, align_level?: number): Array; lookup_word(word: string): string; - add_word(word: string, pron: string, update?: boolean): number; - set_fsg( - name: string, - start_state: number, - final_state: number, - transitions: Array - ): void; - set_jsgf(jsgf_string: string, toprule?: string): void; + add_words(words: Array, update?: boolean): void; + set_grammar(jsgf_string: string, toprule?: string): void; set_align_text(text: string): void; spectrogram(pcm: Float32Array | Uint8Array): FeatureBuffer; } @@ -40,16 +33,15 @@ export class Endpointer { process(frame: Float32Array): Float32Array; end_stream(frame: Float32Array): Float32Array; } -export interface Transition { - from: number; - to: number; - prob?: number; - word?: string; +export interface Word { + word: string; + pron: string; } export interface Segment { - start: number; - end: number; - word: string; + s: number; + d: number; + t: string; + w?: Array; } export interface Config { [key: string]: string | number | boolean; @@ -67,13 +59,13 @@ export interface SoundSwallowerModule extends EmscriptenModule { new (config?: Config): Decoder; }; Endpointer: { - new ( - sample_rate: number, - frame_length?: number, - mode?: number, - window?: number, - ratio?: number - ): Endpointer; + new (config?: { + samprate: number; + frame_length?: number; + mode?: number; + window?: number; + ratio?: number; + }): Endpointer; }; } declare const createModule: EmscriptenModuleFactory; From 6c744242ce87d164d6ca1f984f48ab1768d79dd4 Mon Sep 17 00:00:00 2001 From: David Huggins-Daines Date: Mon, 19 Dec 2022 14:20:31 -0500 Subject: [PATCH 02/13] fix: add strict --- js/api.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/api.js b/js/api.js index 40412fa..e6d7712 100644 --- a/js/api.js +++ b/js/api.js @@ -1,5 +1,4 @@ -// SoundSwallower JavaScript API code. - +"use strict"; const ARG_INTEGER = 1 << 1; const ARG_FLOATING = 1 << 2; const ARG_STRING = 1 << 3; From 24aaaff709c70bcaa97f6c3ae57de74939632c74 Mon Sep 17 00:00:00 2001 From: David Huggins-Daines Date: Mon, 19 Dec 2022 14:21:16 -0500 Subject: [PATCH 03/13] fix!: remove a Node-ism --- js/api.js | 12 ++---------- model/index.js | 2 -- 2 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 model/index.js diff --git a/js/api.js b/js/api.js index e6d7712..2ce304f 100644 --- a/js/api.js +++ b/js/api.js @@ -7,17 +7,9 @@ const ARG_BOOLEAN = 1 << 4; const DEFAULT_MODEL = "en-us"; // User can specify a default model, or none at all -if (typeof Module.defaultModel === "undefined") { - Module.defaultModel = DEFAULT_MODEL; -} +if (Module.defaultModel === undefined) Module.defaultModel = DEFAULT_MODEL; // User can also specify the base URL for models -if (typeof Module.modelBase === "undefined") { - if (ENVIRONMENT_IS_WEB) { - Module.modelBase = "model/"; - } else { - Module.modelBase = require("./model/index.js"); - } -} +if (Module.modelBase === undefined) Module.modelBase = "model/"; /** * Speech recognizer object. diff --git a/model/index.js b/model/index.js deleted file mode 100644 index 770e2ec..0000000 --- a/model/index.js +++ /dev/null @@ -1,2 +0,0 @@ -module.exports = __dirname; - From 94e6946e7c96780b5f6b291a3bc71c6d7383e6ca Mon Sep 17 00:00:00 2001 From: David Huggins-Daines Date: Mon, 19 Dec 2022 14:21:42 -0500 Subject: [PATCH 04/13] refactor: slight modernization --- js/api.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/api.js b/js/api.js index 2ce304f..8a68c9a 100644 --- a/js/api.js +++ b/js/api.js @@ -25,7 +25,7 @@ class Decoder { */ constructor(config) { this.initialized = false; - if (typeof config === "undefined") config = {}; + if (config === undefined) config = {}; if (Module.defaultModel !== null && config.hmm === undefined) config.hmm = Module.get_model_path(Module.defaultModel); const cjson = allocateUTF8(JSON.stringify(config)); @@ -61,7 +61,7 @@ class Decoder { const type = Module._config_typeof(cconfig, ckey); if (type == 0) { Module._free(ckey); - throw new ReferenceError("Unknown cmd_ln parameter " + key); + throw new ReferenceError(`Unknown configuration parameter ${key}`); } if (type & ARG_STRING) { const cval = allocateUTF8(val); @@ -89,7 +89,7 @@ class Decoder { const type = Module._config_typeof(cconfig, ckey); if (type == 0) { Module._free(ckey); - throw new ReferenceError("Unknown cmd_ln parameter " + key); + throw new ReferenceError(`Unknown configuration parameter ${key}`); } Module._config_set(cconfig, ckey, 0, type); Module._free(ckey); @@ -106,7 +106,7 @@ class Decoder { const type = Module._config_typeof(cconfig, ckey); if (type == 0) { Module._free(ckey); - throw new ReferenceError("Unknown cmd_ln parameter " + key); + throw new ReferenceError(`Unknown configuration parameter ${key}`); } let rv; if (type & ARG_STRING) { @@ -122,7 +122,7 @@ class Decoder { } Module._free(ckey); if (rv === undefined) - throw new TypeError("Unsupported type " + type + " for parameter" + key); + throw new TypeError(`Unsupported type ${type} for parameter ${key}`); return rv; } /** From 8af4d39703a266dcb601eaea516f7d6054ca0097 Mon Sep 17 00:00:00 2001 From: David Huggins-Daines Date: Mon, 19 Dec 2022 14:21:53 -0500 Subject: [PATCH 05/13] refactor: formatting change --- js/api.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/js/api.js b/js/api.js index 8a68c9a..1a66ed8 100644 --- a/js/api.js +++ b/js/api.js @@ -324,9 +324,8 @@ class Decoder { this.assert_initialized(); const fe = await this.init_fe(); const fcb = await this.init_feat(); - if (Module._acmod_reinit_feat(this.cacmod, fe, fcb) < 0) { + if (Module._acmod_reinit_feat(this.cacmod, fe, fcb) < 0) throw new Error("Failed to reinitialize audio parameters"); - } } /** @@ -334,9 +333,8 @@ class Decoder { */ start() { this.assert_initialized(); - if (Module._decoder_start_utt(this.cdecoder) < 0) { + if (Module._decoder_start_utt(this.cdecoder) < 0) throw new Error("Failed to start utterance processing"); - } } /** @@ -344,9 +342,8 @@ class Decoder { */ stop() { this.assert_initialized(); - if (Module._decoder_end_utt(this.cdecoder) < 0) { + if (Module._decoder_end_utt(this.cdecoder) < 0) throw new Error("Failed to stop utterance processing"); - } } /** From 1d5651cd668a34245ec64dc68818c34cb208951a Mon Sep 17 00:00:00 2001 From: David Huggins-Daines Date: Mon, 19 Dec 2022 14:22:48 -0500 Subject: [PATCH 06/13] fix!: another nodeism gone --- js/api.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/js/api.js b/js/api.js index 1a66ed8..4365f84 100644 --- a/js/api.js +++ b/js/api.js @@ -821,7 +821,6 @@ async function load_to_s3file(path) { throw new Error("Failed to fetch " + path + " :" + response.statusText); } else { const fs = require("fs/promises"); - // FIXME: Should read directly to emscripten memory... how? const blob = await fs.readFile(path); blob_u8 = new Uint8Array(blob.buffer); } @@ -849,16 +848,11 @@ async function load_to_s3file(path) { * @param {string} subpath path to model directory or parameter * file, e.g. "en-us", "en-us/variances", etc * @returns {string} concatenated path. Note this is a simple string - * concatenation on the Web, so ensure that `modelBase` has a trailing - * slash if it is a directory. + * concatenation, so ensure that `modelBase` has a trailing slash if it is a + * directory. */ function get_model_path(subpath) { - if (ENVIRONMENT_IS_WEB) { - return Module.modelBase + subpath; - } else { - const path = require("path"); - return path.join(Module.modelBase, subpath); - } + return Module.modelBase + subpath; } Module.get_model_path = get_model_path; From d466666c6fa7d25d1e9c4046b914ee7053d17158 Mon Sep 17 00:00:00 2001 From: David Huggins-Daines Date: Mon, 19 Dec 2022 14:23:18 -0500 Subject: [PATCH 07/13] feat!: large API cleanup --- js/api.js | 167 +++++++++++--------------------------- js/soundswallower.spec.js | 149 ++++++++++++++-------------------- 2 files changed, 109 insertions(+), 207 deletions(-) diff --git a/js/api.js b/js/api.js index 4365f84..3a29278 100644 --- a/js/api.js +++ b/js/api.js @@ -352,7 +352,7 @@ class Decoder { * the range [-1.0, 1.0]. * @returns Number of frames processed. */ - process(pcm, no_search = false, full_utt = false) { + process_audio(pcm, no_search = false, full_utt = false) { this.assert_initialized(); const pcm_bytes = pcm.length * pcm.BYTES_PER_ELEMENT; const pcm_addr = Module._malloc(pcm_bytes); @@ -379,55 +379,31 @@ class Decoder { * Get the currently recognized text. * @returns {string} Currently recognized text. */ - get_hyp() { + get_text() { this.assert_initialized(); return UTF8ToString(Module._decoder_hyp(this.cdecoder, 0)); } /** - * Get the current recognition result as a word segmentation. - * @returns {Array} Array of Objects for the words - * recognized, each with the keys `word`, `start` and `end`. + * Get the current recognition result as a word (and possibly phone) segmentation. + * @param {Object} config + * @param {number} config.start Start time to add to returned segment times. + * @param {number} config.align_level 0 for no word alignments only, 1 for wor + and phone alignments, 2 for word, phone and state alignments. + * @returns {Array} Array of segments for the words recognized, each + * with the keys `t`, `b` and `d`, for text, start time, and duration, + * respectively. */ - get_hypseg() { + get_alignment({ start = 0.0, align_level = 0 } = {}) { this.assert_initialized(); - let itor = Module._decoder_seg_iter(this.cdecoder); - const config = Module._decoder_config(this.cdecoder); - const frate = this.get_config("frate"); - const seg = []; - while (itor != 0) { - const frames = Module._malloc(8); - Module._seg_iter_frames(itor, frames, frames + 4); - const start_frame = getValue(frames, "i32"); - const end_frame = getValue(frames + 4, "i32"); - Module._free(frames); - const seg_item = { - word: UTF8ToString(Module._seg_iter_word(itor)), - start: start_frame / frate, - end: end_frame / frate, - }; - seg.push(seg_item); - itor = Module._seg_iter_next(itor); - } - return seg; - } - - /** - * Run alignment and get word and phone segmentation as JSON - * @param start Start time to add to returned segment times. - * @param align_level 0 for no subword alignments, 1 for phone - * alignments, 2 for phone and state alignments. - * @returns JSON with a detailed word and phone-level alignment. - */ - get_alignment_json(start = 0.0, align_level = 1) { - this.assert_initialized(); - /* FIXME: This could block for some time, decompose it. */ + if (align_level > 2) throw new Error(`Invalid align_level ${align_level}`); const cjson = Module._decoder_result_json( this.cdecoder, start, align_level ); - return UTF8ToString(cjson); + const json = UTF8ToString(cjson); + return JSON.parse(json); } /** @@ -446,79 +422,33 @@ class Decoder { } /** - * Add a word to the pronunciation dictionary. - * @param {string} word Text of word to add. - * @param {string} pron Pronunciation of word as space-separated list of phonemes. - * @param {number} update Update decoder immediately (set to - * false when adding a list of words, except for the last word). - */ - add_word(word, pron, update = true) { - this.assert_initialized(); - const cword = allocateUTF8(word); - const cpron = allocateUTF8(pron); - const wid = Module._decoder_add_word(this.cdecoder, cword, cpron, update); - Module._free(cword); - Module._free(cpron); - if (wid < 0) - throw new Error( - "Failed to add word " + - word + - " with pronunciation " + - pron + - " to the dictionary." - ); - return wid; - } - - /** - * Set recognition grammar from a list of transitions. - * @param {string} name Name of grammar. - * @param {number} start_state Index of starting state. - * @param {number} final_state Index of ending state. - * @param {Array} transitions Array of transitions, each - * of which is an Object with the keys `from`, `to`, `word`, and - * `prob`. The word must exist in the dictionary. + * Add words to the pronunciation dictionary. + * + * Example: + * + * decoder.add_words(["hello", "H EH L OW"], ["world", "W ER L D"]); + * + * @param (...Array) words Any number of 2-element arrays containing the + * word text in position 0 and a string with whitespace-separated + * phones in position 1. */ - set_fsg(name, start_state, final_state, transitions) { + add_words(...words) { this.assert_initialized(); - const logmath = Module._decoder_logmath(this.cdecoder); - const config = Module._decoder_config(this.cdecoder); - const lw = this.get_config("lw"); - let n_state = 0; - for (const t of transitions) { - n_state = Math.max(n_state, t.from, t.to); - } - n_state++; - const fsg = ccall( - "fsg_model_init", - "number", - ["string", "number", "number", "number"], - [name, logmath, lw, n_state] - ); - Module._fsg_set_states(fsg, start_state, final_state); - for (const t of transitions) { - let logprob = 0; - if ("prob" in t) { - logprob = Module._logmath_log(logmath, t.prob); - } - if ("word" in t) { - const wid = ccall( - "fsg_model_word_add", - "number", - ["number", "string"], - [fsg, t.word] + for (let i = 0; i < words.length; ++i) { + const [text, pron] = words[i]; + if (text === undefined || pron === undefined) + throw new Error( + `Word at position ${i} has missing text or pronunciation` ); - if (wid == -1) { - Module._fsg_model_free(fsg); - throw new Error(`Failed to add word ${t.word} to FSG`); - } - Module._fsg_model_trans_add(fsg, t.from, t.to, logprob, wid); - } else { - Module._fsg_model_null_trans_add(fsg, t.from, t.to, logprob); - } + const ctext = allocateUTF8(text); + const cpron = allocateUTF8(pron); + const update = i == words.length - 1; + const wid = Module._decoder_add_word(this.cdecoder, ctext, cpron, update); + Module._free(ctext); + Module._free(cpron); + if (wid < 0) + throw new Error(`Failed to add "${word}:${pron}" to the dictionary`); } - if (Module._decoder_set_fsg(this.cdecoder, fsg) != 0) - throw new Error("Failed to set FSG in decoder"); } /** @@ -527,7 +457,7 @@ class Decoder { * @param {string} [toprule] Name of starting rule for grammar, * if not specified, the first public rule will be used. */ - set_jsgf(jsgf_string, toprule = null) { + set_grammar(jsgf_string, toprule = null) { this.assert_initialized(); const logmath = Module._decoder_logmath(this.cdecoder); const config = Module._decoder_config(this.cdecoder); @@ -613,31 +543,32 @@ class Endpointer { /** * Create the endpointer * - * @param {number} [sample_rate] Sampling rate of the input audio. - * @param {number} [frame_length] Length in seconds of an input + * @param {Object} config + * @param {number} config.samprate Sampling rate of the input audio. + * @param {number} config.frame_length Length in seconds of an input * frame, must be 0.01, 0.02, or 0.03. - * @param {number} [mode] Aggressiveness of voice activity detction, + * @param {number} config.mode Aggressiveness of voice activity detction, * must be 0, 1, 2, or 3. Higher numbers will create "tighter" * endpoints at the possible expense of clipping the start of * utterances. - * @param {number} [window] Length in seconds of the window used to + * @param {number} config.window Length in seconds of the window used to * make a speech/non-speech decision. - * @param {number} [ratio] Ratio of `window` that must be detected as + * @param {number} config.ratio Ratio of `window` that must be detected as * speech (or not speech) in order to trigger decision. * @throws {Error} on invalid parameters. */ - constructor( - sample_rate, + constructor({ + samprate, frame_length = 0.03, mode = 0, window = 0.3, - ratio = 0.9 - ) { + ratio = 0.9, + } = {}) { this.cep = Module._endpointer_init( window, ratio, mode, - sample_rate, + samprate, frame_length ); if (this.cep == 0) throw new Error("Invalid endpointer or VAD parameters"); diff --git a/js/soundswallower.spec.js b/js/soundswallower.spec.js index 464b6ca..2da4a84 100644 --- a/js/soundswallower.spec.js +++ b/js/soundswallower.spec.js @@ -19,6 +19,18 @@ if (ENVIRONMENT_IS_WEB) { assert = require("assert"); } +function check_alignment(hypseg, text) { + let hypseg_words = []; + let prev = -1; + for (const { t, b, d } of hypseg.w) { + assert.ok(d > 0); + assert.ok(b > prev); + prev = b; + if (t != "" && t != "(NULL)") hypseg_words.push(t); + } + assert.equal(hypseg_words.join(" "), text); +} + const soundswallower = {}; before(async () => { if (ENVIRONMENT_IS_WEB) { @@ -89,18 +101,11 @@ describe("Test decoding", () => { for (let pos = 0; pos < pcm.length; pos += 128) { let len = pcm.length - pos; if (len > 128) len = 128; - decoder.process(pcm.subarray(pos, pos + len), false, false); + decoder.process_audio(pcm.subarray(pos, pos + len), false, false); } 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); - if (seg.word != "" && seg.word != "(NULL)") - hypseg_words.push(seg.word); - } - assert.equal(hypseg_words.join(" "), "go forward ten meters"); + assert.equal("go forward ten meters", decoder.get_text()); + check_alignment(decoder.get_alignment(), "go forward ten meters"); decoder.delete(); }); it("Should accept Float32Array as well as UInt8Array", async () => { @@ -112,9 +117,9 @@ describe("Test decoding", () => { let pcm = await load_binary_file("testdata/goforward-float32.raw"); let pcm32 = new Float32Array(pcm.buffer); decoder.start(); - decoder.process(pcm32, false, true); + decoder.process_audio(pcm32, false, true); decoder.stop(); - assert.equal("go forward ten meters", decoder.get_hyp()); + assert.equal("go forward ten meters", decoder.get_text()); decoder.delete(); }); it('Should align "go forward ten meters"', async () => { @@ -125,19 +130,12 @@ describe("Test decoding", () => { let pcm = await load_binary_file("testdata/goforward-float32.raw"); decoder.set_align_text("go forward ten meters"); decoder.start(); - decoder.process(pcm, false, true); + decoder.process_audio(pcm, false, true); 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); - if (seg.word != "" && seg.word != "(NULL)") - hypseg_words.push(seg.word); - } - assert.equal(hypseg_words.join(" "), "go forward ten meters"); - let jseg = decoder.get_alignment_json(); - let result = JSON.parse(jseg); + assert.equal("go forward ten meters", decoder.get_text()); + check_alignment(decoder.get_alignment(), "go forward ten meters"); + + let result = decoder.get_alignment({ align_level: 1 }); assert.ok(result); assert.equal(result.t, "go forward ten meters"); let json_words = []; @@ -163,21 +161,18 @@ describe("Test dictionary and FSG", () => { let decoder = new soundswallower.Decoder({ samprate: 16000 }); decoder.unset_config("dict"); await decoder.initialize(); - decoder.add_word("_go", "G OW", false); - decoder.add_word("_forward", "F AO R W ER D", false); - decoder.add_word("_ten", "T EH N", false); - decoder.add_word("_meters", "M IY T ER Z", true); - decoder.set_fsg("goforward", 0, 4, [ - { from: 0, to: 1, prob: 1.0, word: "_go" }, - { from: 1, to: 2, prob: 1.0, word: "_forward" }, - { from: 2, to: 3, prob: 1.0, word: "_ten" }, - { from: 3, to: 4, prob: 1.0, word: "_meters" }, - ]); + decoder.add_words( + ["_go", "G OW"], + ["_forward", "F AO R W ER D"], + ["_ten", "T EH N"], + ["_meters", "M IY T ER Z"] + ); + decoder.set_align_text("_go _forward _ten _meters"); let pcm = await load_binary_file("testdata/goforward-float32.raw"); decoder.start(); - decoder.process(pcm, false, true); + decoder.process_audio(pcm, false, true); decoder.stop(); - assert.equal("_go _forward _ten _meters", decoder.get_hyp()); + assert.equal("_go _forward _ten _meters", decoder.get_text()); decoder.delete(); }); }); @@ -185,16 +180,13 @@ describe("Test reinitialization", () => { it('Should recognize "go forward ten meters"', async () => { let decoder = new soundswallower.Decoder({ samprate: 16000 }); await decoder.initialize(); - decoder.add_word("_go", "G OW", false); - decoder.add_word("_forward", "F AO R W ER D", false); - decoder.add_word("_ten", "T EH N", false); - decoder.add_word("_meters", "M IY T ER Z", true); - decoder.set_fsg("goforward", 0, 4, [ - { from: 0, to: 1, prob: 1.0, word: "_go" }, - { from: 1, to: 2, prob: 1.0, word: "_forward" }, - { from: 2, to: 3, prob: 1.0, word: "_ten" }, - { from: 3, to: 4, prob: 1.0, word: "_meters" }, - ]); + decoder.add_words( + ["_go", "G OW"], + ["_forward", "F AO R W ER D"], + ["_ten", "T EH N"], + ["_meters", "M IY T ER Z"] + ); + decoder.set_align_text("_go _forward _ten _meters"); decoder.set_config( "dict", soundswallower.get_model_path(soundswallower.defaultModel) + "/dict.txt" @@ -203,9 +195,9 @@ describe("Test reinitialization", () => { await decoder.initialize(); let pcm = await load_binary_file("testdata/goforward-float32.raw"); decoder.start(); - decoder.process(pcm, false, true); + decoder.process_audio(pcm, false, true); decoder.stop(); - assert.equal("go forward ten meters", decoder.get_hyp()); + assert.equal("go forward ten meters", decoder.get_text()); decoder.delete(); }); }); @@ -216,29 +208,16 @@ describe("Test loading model for other language", () => { samprate: 16000, }); await decoder.initialize(); - decoder.set_fsg("goforward", 0, 4, [ - { from: 0, to: 1, prob: 0.5, word: "avance" }, - { from: 0, to: 1, prob: 0.5, word: "recule" }, - { from: 1, to: 2, prob: 0.1, word: "d'" }, - { from: 1, to: 2, prob: 0.9, word: "de" }, - { from: 2, to: 3, prob: 0.1, word: "un" }, - { from: 2, to: 3, prob: 0.1, word: "deux" }, - { from: 2, to: 3, prob: 0.1, word: "trois" }, - { from: 2, to: 3, prob: 0.1, word: "quatre" }, - { from: 2, to: 3, prob: 0.1, word: "cinq" }, - { from: 2, to: 3, prob: 0.1, word: "six" }, - { from: 2, to: 3, prob: 0.1, word: "sept" }, - { from: 2, to: 3, prob: 0.1, word: "huit" }, - { from: 2, to: 3, prob: 0.1, word: "neuf" }, - { from: 2, to: 3, prob: 0.1, word: "dix" }, - { from: 3, to: 4, prob: 0.1, word: "mètre" }, - { from: 3, to: 4, prob: 0.9, word: "mètres" }, - ]); + decoder.set_grammar(`#JSGF V1.0; +grammar avance; +public = (avance | recule) (d' | de) (mètre | mètres); + = un | deux | trois | quatre | cinq | six | sept | huit | neuf | dix; +`); let pcm = await load_binary_file("testdata/goforward_fr-float32.raw"); decoder.start(); - decoder.process(pcm, false, true); + decoder.process_audio(pcm, false, true); decoder.stop(); - assert.equal("avance de dix mètres", decoder.get_hyp()); + assert.equal("avance de dix mètres", decoder.get_text()); decoder.delete(); }); }); @@ -251,9 +230,9 @@ describe("Test JSGF", () => { await decoder.initialize(); let pcm = await load_binary_file("testdata/pizza-float32.raw"); decoder.start(); - decoder.process(pcm, false, true); + decoder.process_audio(pcm, false, true); decoder.stop(); - assert.equal("yo gimme four large all dressed pizzas", decoder.get_hyp()); + assert.equal("yo gimme four large all dressed pizzas", decoder.get_text()); decoder.delete(); }); }); @@ -261,7 +240,7 @@ describe("Test JSGF string", () => { it('Should recognize "yo gimme four large all dressed pizzas"', async () => { let decoder = new soundswallower.Decoder({ samprate: 16000 }); await decoder.initialize(); - decoder.set_jsgf(`#JSGF V1.0; + decoder.set_grammar(`#JSGF V1.0; grammar pizza; public = [] [] [] [] [