diff --git a/rnnoise/.eslintrc.js b/rnnoise/.eslintrc.js
new file mode 100644
index 00000000..3d8c056f
--- /dev/null
+++ b/rnnoise/.eslintrc.js
@@ -0,0 +1,9 @@
+module.exports = {
+ ignorePatterns: ['signal/', 'utils/'],
+ globals: {
+ 'Module': 'readonly',
+ 'numpy': 'readonly',
+ 'BigInt64Array': 'readonly',
+ 'BigUint64Array': 'readonly',
+ },
+};
diff --git a/rnnoise/audio/babbel.wav b/rnnoise/audio/babbel.wav
new file mode 100644
index 00000000..3e978ae3
Binary files /dev/null and b/rnnoise/audio/babbel.wav differ
diff --git a/rnnoise/audio/car.wav b/rnnoise/audio/car.wav
new file mode 100644
index 00000000..b1da3b10
Binary files /dev/null and b/rnnoise/audio/car.wav differ
diff --git a/rnnoise/audio/street.wav b/rnnoise/audio/street.wav
new file mode 100644
index 00000000..66c29202
Binary files /dev/null and b/rnnoise/audio/street.wav differ
diff --git a/rnnoise/index.html b/rnnoise/index.html
new file mode 100644
index 00000000..375c6073
--- /dev/null
+++ b/rnnoise/index.html
@@ -0,0 +1,74 @@
+
+
+
+
+ WebNN RNNoise Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/rnnoise/main.js b/rnnoise/main.js
new file mode 100644
index 00000000..e2f6fdd4
--- /dev/null
+++ b/rnnoise/main.js
@@ -0,0 +1,203 @@
+import {Processer} from './processer.js';
+import {RNNoise} from './rnnoise.js';
+
+let runtime = false;
+const batchSize = 1;
+const rnnoise = new RNNoise('./model/', batchSize); // Frames is fixed at 100
+
+const sampleAudios = [{
+ name: 'babbel',
+ url: './audio/babbel.wav',
+}, {
+ name: 'car',
+ url: './audio/car.wav',
+}, {
+ name: 'street',
+ url: './audio/street.wav',
+}];
+
+const audioName = document.getElementById('audio-name');
+const modelInfo = document.getElementById('info');
+const DenoiseInfo = document.getElementById('denoise-info');
+const fileInput = document.getElementById('file-input');
+const originalAudio = document.getElementById('original-audio');
+const denoisedAudio = document.getElementById('denoised-audio');
+const recorderWorker = new Worker('./utils/recorderWorker.js');
+
+recorderWorker.postMessage({
+ command: 'init',
+ config: {sampleRate: 48000, numChannels: 1},
+});
+
+recorderWorker.onmessage = function(e) {
+ const blob = e.data;
+ denoisedAudio.src = URL.createObjectURL(blob);
+};
+
+Module.onRuntimeInitialized = function() {
+ runtime = true;
+ console.log('WASM Runtime Ready.');
+};
+
+function getUrlById(audioList, id) {
+ for (const audio of Object.values(audioList).flat()) {
+ if (id === audio.name) {
+ return audio.url;
+ }
+ }
+ return null;
+}
+
+function log(infoElement, message, sep = false, append = true) {
+ infoElement.innerHTML = (append ? infoElement.innerHTML : '') + message +
+ (sep ? '
' : '');
+}
+
+originalAudio.onplay = () => {
+ denoisedAudio.pause();
+};
+
+denoisedAudio.onplay = () => {
+ originalAudio.pause();
+};
+
+async function denoise() {
+ const audioData = [];
+ const audioContext = new AudioContext({sampleRate: 48000});
+ const sampleRate = audioContext.sampleRate;
+ const steps = 48000;
+ let vadInitialHiddenStateBuffer = new Float32Array(
+ 1 * batchSize * 24,
+ ).fill(0);
+ let noiseInitialHiddenStateBuffer = new Float32Array(
+ 1 * batchSize * 48,
+ ).fill(0);
+ let denoiseInitialHiddenStateBuffer = new Float32Array(
+ 1 * batchSize * 96,
+ ).fill(0);
+
+ if (audioContext.state != 'running') {
+ audioContext.resume().then(function() {
+ console.log('audioContext resumed.');
+ });
+ }
+ const analyser = new Processer(audioContext, originalAudio);
+ const pcm = await analyser.getAudioPCMData();
+ const frames = Math.ceil(pcm.length / steps);
+ const lastFrameSize = pcm.length - steps * (frames - 1);
+
+ const processStart = performance.now();
+ for (let i = 0; i < frames; i++) {
+ let framePCM;
+ if (i != (frames - 1)) {
+ framePCM = pcm.subarray(i * steps, i * steps + sampleRate);
+ } else {
+ framePCM = new Float32Array(sampleRate).fill(0);
+ for (let j = 0; j < lastFrameSize; j++) {
+ framePCM[j] = pcm[i * sampleRate + j];
+ }
+ }
+ let start = performance.now();
+ const features = analyser.preProcessing(framePCM);
+ const preProcessingTime = (performance.now() - start).toFixed(2);
+ const inputBuffer = new Float32Array(features);
+ start = performance.now();
+ const outputs = await rnnoise.compute(
+ inputBuffer, vadInitialHiddenStateBuffer,
+ noiseInitialHiddenStateBuffer, denoiseInitialHiddenStateBuffer,
+ );
+ const executionTime = (performance.now() - start).toFixed(2);
+ // rnnoise.dispose();
+ vadInitialHiddenStateBuffer = outputs.vadGruYH.buffer;
+ noiseInitialHiddenStateBuffer = outputs.noiseGruYH.buffer;
+ denoiseInitialHiddenStateBuffer = outputs.denoiseGruYH.buffer;
+
+ start = performance.now();
+ const output = analyser.postProcessing(outputs.denoiseOutput.buffer);
+ const postProcessingTime = (performance.now() - start).toFixed(2);
+ if (i == 0) {
+ audioData.push(...output);
+ } else {
+ audioData.push(...output.slice(sampleRate - steps));
+ }
+
+ log(DenoiseInfo,
+ `Denoising... ` +
+ `(${Math.ceil((i + 1) / frames * 100)}%)
` +
+ ` - preProcessing time: ` +
+ `${preProcessingTime} ms.
` +
+ ` - RNNoise compute time: ` +
+ `${executionTime} ms.
` +
+ ` - postProcessing time: ` +
+ `${postProcessingTime} ms.`, true, false);
+ }
+ const processTime = (performance.now() - processStart).toFixed(2);
+ log(DenoiseInfo, `Done. Processed ${frames * 100} ` +
+ `frames in ${processTime} ms.`, true);
+
+
+ // Send the denoised audio data for wav encoding.
+ recorderWorker.postMessage({
+ command: 'clear',
+ });
+ recorderWorker.postMessage({
+ command: 'record',
+ buffer: [new Float32Array(audioData)],
+ });
+ recorderWorker.postMessage({
+ command: 'exportWAV',
+ type: 'audio/wav',
+ });
+}
+
+$('.dropdown-item').click(async (e) => {
+ const audioId = $(e.target).attr('id');
+ if (audioId == 'browse') {
+ const evt = document.createEvent('MouseEvents');
+ evt.initEvent('click', true, false);
+ fileInput.dispatchEvent(evt);
+ } else {
+ const audioUrl = getUrlById(sampleAudios, audioId);
+ log(audioName,
+ audioUrl.substring(audioUrl.lastIndexOf('/') + 1), false, false);
+ originalAudio.src = audioUrl;
+ denoisedAudio.src = '';
+ await denoise();
+ }
+});
+
+fileInput.addEventListener('input', (event) => {
+ log(audioName, event.target.files[0].name, false, false);
+ const reader = new FileReader();
+ reader.onload = async function(e) {
+ originalAudio.src = e.target.result;
+ denoisedAudio.src = '';
+ await denoise();
+ };
+ reader.readAsDataURL(event.target.files[0]);
+});
+
+window.onload = async function() {
+ log(modelInfo, `Creating RNNoise with input shape ` +
+ `[${batchSize} (batch_size) x 100 (frames) x 42].`, true);
+ log(modelInfo, '- Loading model...');
+ let start = performance.now();
+ await rnnoise.load();
+ const loadingTime = (performance.now() - start).toFixed(2);
+ console.log(`loading elapsed time: ${loadingTime} ms`);
+ log(modelInfo,
+ `done in ${loadingTime} ms.`, true);
+ log(modelInfo, '- Compiling model...');
+ start = performance.now();
+ await rnnoise.compile();
+ const compilationTime = (performance.now() - start).toFixed(2);
+ console.log(`compilation elapsed time: ${compilationTime} ms`);
+ log(modelInfo,
+ `done in ${compilationTime} ms.`, true);
+ while (runtime == false) {
+ console.log('Wait for DSP library to complete loading...');
+ }
+ log(modelInfo, '- DSP library Loaded.', true);
+ log(modelInfo, 'RNNoise is ready.');
+ $('#choose-audio').attr('disabled', false);
+};
diff --git a/rnnoise/model/denoise_gru_B.npy b/rnnoise/model/denoise_gru_B.npy
new file mode 100644
index 00000000..3655bd8b
Binary files /dev/null and b/rnnoise/model/denoise_gru_B.npy differ
diff --git a/rnnoise/model/denoise_gru_R.npy b/rnnoise/model/denoise_gru_R.npy
new file mode 100644
index 00000000..de5ee556
Binary files /dev/null and b/rnnoise/model/denoise_gru_R.npy differ
diff --git a/rnnoise/model/denoise_gru_W.npy b/rnnoise/model/denoise_gru_W.npy
new file mode 100644
index 00000000..a455116d
Binary files /dev/null and b/rnnoise/model/denoise_gru_W.npy differ
diff --git a/rnnoise/model/denoise_output_bias_0.npy b/rnnoise/model/denoise_output_bias_0.npy
new file mode 100644
index 00000000..7d4d2c8b
Binary files /dev/null and b/rnnoise/model/denoise_output_bias_0.npy differ
diff --git a/rnnoise/model/denoise_output_kernel_0.npy b/rnnoise/model/denoise_output_kernel_0.npy
new file mode 100644
index 00000000..5b5cc10e
Binary files /dev/null and b/rnnoise/model/denoise_output_kernel_0.npy differ
diff --git a/rnnoise/model/input_dense_bias_0.npy b/rnnoise/model/input_dense_bias_0.npy
new file mode 100644
index 00000000..610ec690
Binary files /dev/null and b/rnnoise/model/input_dense_bias_0.npy differ
diff --git a/rnnoise/model/input_dense_kernel_0.npy b/rnnoise/model/input_dense_kernel_0.npy
new file mode 100644
index 00000000..dc2a5e66
Binary files /dev/null and b/rnnoise/model/input_dense_kernel_0.npy differ
diff --git a/rnnoise/model/noise_gru_B.npy b/rnnoise/model/noise_gru_B.npy
new file mode 100644
index 00000000..ffc59ce0
Binary files /dev/null and b/rnnoise/model/noise_gru_B.npy differ
diff --git a/rnnoise/model/noise_gru_R.npy b/rnnoise/model/noise_gru_R.npy
new file mode 100644
index 00000000..55323055
Binary files /dev/null and b/rnnoise/model/noise_gru_R.npy differ
diff --git a/rnnoise/model/noise_gru_W.npy b/rnnoise/model/noise_gru_W.npy
new file mode 100644
index 00000000..214b3b32
Binary files /dev/null and b/rnnoise/model/noise_gru_W.npy differ
diff --git a/rnnoise/model/vad_gru_B.npy b/rnnoise/model/vad_gru_B.npy
new file mode 100644
index 00000000..37258153
Binary files /dev/null and b/rnnoise/model/vad_gru_B.npy differ
diff --git a/rnnoise/model/vad_gru_R.npy b/rnnoise/model/vad_gru_R.npy
new file mode 100644
index 00000000..0c2f14ca
Binary files /dev/null and b/rnnoise/model/vad_gru_R.npy differ
diff --git a/rnnoise/model/vad_gru_W.npy b/rnnoise/model/vad_gru_W.npy
new file mode 100644
index 00000000..60390336
Binary files /dev/null and b/rnnoise/model/vad_gru_W.npy differ
diff --git a/rnnoise/processer.js b/rnnoise/processer.js
new file mode 100644
index 00000000..ecfae757
--- /dev/null
+++ b/rnnoise/processer.js
@@ -0,0 +1,57 @@
+'use strict';
+
+export class Processer {
+ constructor(audioContext, audioElement) {
+ this.audioContext = audioContext;
+ this.audioElement = audioElement;
+ }
+
+ async getAudioPCMData() {
+ const request = new Request(this.audioElement.src);
+ const response = await fetch(request);
+ const audioFileData = await response.arrayBuffer();
+ const audioDecodeData =
+ await this.audioContext.decodeAudioData(audioFileData);
+ const audioPCMData = audioDecodeData.getChannelData(0);
+
+ return audioPCMData;
+ }
+
+ preProcessing(pcm) {
+ const pcmLength = 48000;
+ const featuresLength = 4200;
+ const pcmPtr = Module._malloc(4 * pcmLength);
+ for (let i = 0; i < pcmLength; i++) {
+ Module.HEAPF32[pcmPtr / 4 + i] = pcm[i];
+ }
+ const getFeatures = Module.cwrap('pre_processing', 'number', ['number']);
+ const featuresPtr = getFeatures(pcmPtr);
+ const features = [];
+
+ for (let i = 0; i < featuresLength; i++) {
+ features[i] = Module.HEAPF32[(featuresPtr >> 2) + i];
+ }
+ Module._free(pcmPtr, featuresPtr);
+
+ return features;
+ }
+
+ postProcessing(gains) {
+ const audioLength = 48000;
+ const gainsLength = 2200;
+ const gainsPtr = Module._malloc(4 * gainsLength);
+ for (let i = 0; i < gainsLength; i++) {
+ Module.HEAPF32[gainsPtr / 4 + i] = gains[i];
+ }
+ const getAudio = Module.cwrap('post_processing', 'number', ['number']);
+ const audioPtr = getAudio(gainsPtr);
+ const audio = [];
+
+ for (let i = 0; i < audioLength; i++) {
+ audio[i] = Module.HEAPF32[(audioPtr >> 2) + i];
+ }
+ Module._free(gainsPtr, audioPtr);
+
+ return audio;
+ }
+}
diff --git a/rnnoise/rnnoise.js b/rnnoise/rnnoise.js
new file mode 100644
index 00000000..dd61aabd
--- /dev/null
+++ b/rnnoise/rnnoise.js
@@ -0,0 +1,187 @@
+'use strict';
+
+const nn = navigator.ml.getNeuralNetworkContext();
+
+function sizeOfShape(shape) {
+ return shape.reduce((a, b) => {
+ return a * b;
+ });
+}
+
+export class RNNoise {
+ constructor(url, batchSize) {
+ this.url_ = url;
+ this.batchSize_ = batchSize;
+ this.frames_ = 100;
+ this.model_ = null;
+ this.compilation_ = null;
+ this.builder = null;
+ }
+
+ async buildConstantByNpy(fileName) {
+ const dataTypeMap = new Map([
+ ['f2', {type: 'float16', array: Uint16Array}],
+ ['f4', {type: 'float32', array: Float32Array}],
+ ['f8', {type: 'float64', array: Float64Array}],
+ ['i1', {type: 'int8', array: Int8Array}],
+ ['i2', {type: 'int16', array: Int16Array}],
+ ['i4', {type: 'int32', array: Int32Array}],
+ ['i8', {type: 'int64', array: BigInt64Array}],
+ ['u1', {type: 'uint8', array: Uint8Array}],
+ ['u2', {type: 'uint16', array: Uint16Array}],
+ ['u4', {type: 'uint32', array: Uint32Array}],
+ ['u8', {type: 'uint64', array: BigUint64Array}],
+ ]);
+ const response = await fetch(this.url_ + fileName);
+ const buffer = await response.arrayBuffer();
+ const npArray = new numpy.Array(new Uint8Array(buffer));
+ if (!dataTypeMap.has(npArray.dataType)) {
+ throw new Error(`Data type ${npArray.dataType} is not supported.`);
+ }
+ const dimensions = npArray.shape;
+ const type = dataTypeMap.get(npArray.dataType).type;
+ const TypedArrayConstructor = dataTypeMap.get(npArray.dataType).array;
+ const typedArray = new TypedArrayConstructor(sizeOfShape(dimensions));
+ const dataView = new DataView(npArray.data.buffer);
+ const littleEndian = npArray.byteOrder === '<';
+ for (let i = 0; i < sizeOfShape(dimensions); ++i) {
+ typedArray[i] = dataView[`get` + type[0].toUpperCase() + type.substr(1)](
+ i * TypedArrayConstructor.BYTES_PER_ELEMENT, littleEndian);
+ }
+ return this.builder.constant({type, dimensions}, typedArray);
+ }
+
+ async load() {
+ this.builder = nn.createModelBuilder();
+
+ const inputDenseKernel0 = await this.buildConstantByNpy(
+ 'input_dense_kernel_0.npy');
+ const inputDenseBias0 = await this.buildConstantByNpy(
+ 'input_dense_bias_0.npy');
+ const vadGruW = await this.buildConstantByNpy(
+ 'vad_gru_W.npy');
+ const vadGruR = await this.buildConstantByNpy(
+ 'vad_gru_R.npy');
+ const vadGruBData = await this.buildConstantByNpy(
+ 'vad_gru_B.npy');
+ const noiseGruW = await this.buildConstantByNpy(
+ 'noise_gru_W.npy');
+ const noiseGruR = await this.buildConstantByNpy(
+ 'noise_gru_R.npy');
+ const noiseGruBData = await this.buildConstantByNpy(
+ 'noise_gru_B.npy');
+ const denoiseGruW = await this.buildConstantByNpy(
+ 'denoise_gru_W.npy');
+ const denoiseGruR = await this.buildConstantByNpy(
+ 'denoise_gru_R.npy');
+ const denoiseGruBData = await this.buildConstantByNpy(
+ 'denoise_gru_B.npy');
+ const denoiseOutputKernel0 = await this.buildConstantByNpy(
+ 'denoise_output_kernel_0.npy');
+ const denoiseOutputBias0 = await this.buildConstantByNpy(
+ 'denoise_output_bias_0.npy');
+
+ const input = this.builder.input(
+ 'input', {type: 'float32', dimensions: [this.batchSize_, 100, 42]});
+ const inputDense0 = this.builder.matmul(input, inputDenseKernel0);
+ const biasedTensorName2 = this.builder.add(inputDense0, inputDenseBias0);
+ const inputDenseTanh0 = this.builder.tanh(biasedTensorName2);
+ const vadGruX = this.builder.transpose(
+ inputDenseTanh0, {permutation: [1, 0, 2]});
+ // hiddenSize = 24
+ const vadGruB = this.builder.slice(
+ vadGruBData, [0], [3 * 24], {axes: [1]});
+ const vadGruRB = this.builder.slice(
+ vadGruBData, [3 * 24], [-1], {axes: [1]});
+ const vadGruInitialH = this.builder.input(
+ 'vadGruInitialH',
+ {type: 'float32', dimensions: [1, this.batchSize_, 24]});
+ const [vadGruYH, vadGruY] = this.builder.gru(
+ vadGruX, vadGruW, vadGruR, this.frames_, 24, {
+ bias: vadGruB, recurrentBias: vadGruRB,
+ initialHiddenState: vadGruInitialH,
+ returnSequence: true, resetAfter: false,
+ activations: ['sigmoid', 'relu'],
+ });
+ const vadGruYTransposed = this.builder.transpose(
+ vadGruY, {permutation: [2, 0, 1, 3]});
+ const vadGruTranspose1 = this.builder.reshape(
+ vadGruYTransposed, [-1, 100, 24]);
+ const concatenate1 = this.builder.concat(
+ [inputDenseTanh0, vadGruTranspose1, input], 2);
+ const noiseGruX = this.builder.transpose(
+ concatenate1, {permutation: [1, 0, 2]});
+ // hiddenSize = 48
+ const noiseGruB = this.builder.slice(
+ noiseGruBData, [0], [3 * 48], {axes: [1]});
+ const noiseGruRB = this.builder.slice(
+ noiseGruBData, [3 * 48], [-1], {axes: [1]});
+ const noiseGruInitialH = this.builder.input(
+ 'noiseGruInitialH',
+ {type: 'float32', dimensions: [1, this.batchSize_, 48]});
+ const [noiseGruYH, noiseGruY] = this.builder.gru(
+ noiseGruX, noiseGruW, noiseGruR, this.frames_, 48, {
+ bias: noiseGruB, recurrentBias: noiseGruRB,
+ initialHiddenState: noiseGruInitialH,
+ returnSequence: true, resetAfter: false,
+ activations: ['sigmoid', 'relu'],
+ });
+ const noiseGruYTransposed = this.builder.transpose(
+ noiseGruY, {permutation: [2, 0, 1, 3]});
+ const noiseGruTranspose1 = this.builder.reshape(
+ noiseGruYTransposed, [-1, 100, 48]);
+ const concatenate2 = this.builder.concat(
+ [vadGruTranspose1, noiseGruTranspose1, input], 2);
+ const denoiseGruX = this.builder.transpose(
+ concatenate2, {permutation: [1, 0, 2]});
+ // hiddenSize = 96
+ const denoiseGruB = this.builder.slice(
+ denoiseGruBData, [0], [3 * 96], {axes: [1]});
+ const denoiseGruRB = this.builder.slice(
+ denoiseGruBData, [3 * 96], [-1], {axes: [1]});
+ const denoiseGruInitialH = this.builder.input(
+ 'denoiseGruInitialH',
+ {type: 'float32', dimensions: [1, this.batchSize_, 96]});
+ const [denoiseGruYH, denoiseGruY] = this.builder.gru(
+ denoiseGruX, denoiseGruW, denoiseGruR, this.frames_, 96, {
+ bias: denoiseGruB, recurrentBias: denoiseGruRB,
+ initialHiddenState: denoiseGruInitialH,
+ returnSequence: true, resetAfter: false,
+ activations: ['sigmoid', 'relu'],
+ });
+ const denoiseGruYTransposed = this.builder.transpose(
+ denoiseGruY, {permutation: [2, 0, 1, 3]});
+ const denoiseGruTranspose1 = this.builder.reshape(
+ denoiseGruYTransposed, [-1, 100, 96]);
+ const denoiseOutput0 = this.builder.matmul(
+ denoiseGruTranspose1, denoiseOutputKernel0);
+ const biasedTensorName = this.builder.add(
+ denoiseOutput0, denoiseOutputBias0);
+ const denoiseOutput = this.builder.sigmoid(
+ biasedTensorName);
+
+ this.model_ = this.builder.createModel(
+ {denoiseOutput, vadGruYH, noiseGruYH, denoiseGruYH});
+ }
+
+ async compile(options) {
+ this.compilation_ = await this.model_.compile(options);
+ }
+
+ async compute(
+ inputBuffer, vadGruInitialHBuffer,
+ noiseGruInitialHBuffer, denoiseGruInitialHBuffer) {
+ const inputs = {
+ input: {buffer: inputBuffer},
+ vadGruInitialH: {buffer: vadGruInitialHBuffer},
+ noiseGruInitialH: {buffer: noiseGruInitialHBuffer},
+ denoiseGruInitialH: {buffer: denoiseGruInitialHBuffer},
+ };
+ const outputs = await this.compilation_.compute(inputs);
+ return outputs;
+ }
+
+ dispose() {
+ this.compilation_.dispose();
+ }
+}
diff --git a/rnnoise/signal/signal.js b/rnnoise/signal/signal.js
new file mode 100644
index 00000000..50bae944
--- /dev/null
+++ b/rnnoise/signal/signal.js
@@ -0,0 +1 @@
+var Module=typeof Module!=="undefined"?Module:{};var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=function shell_read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);if(typeof module!=="undefined"){module["exports"]=Module}process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);quit_=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}if(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime;if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(typeof WebAssembly!=="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS=0;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}function getCFunc(ident){var func=Module["_"+ident];assert(func,"Cannot call unknown function "+ident+", make sure it is exported");return func}function ccall(ident,returnType,argTypes,args,opts){var toC={"string":function(str){var ret=0;if(str!==null&&str!==undefined&&str!==0){var len=(str.length<<2)+1;ret=stackAlloc(len);stringToUTF8(str,ret,len)}return ret},"array":function(arr){var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string")return UTF8ToString(ret);if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}var WASM_PAGE_SIZE=65536;function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;if(Module["wasmMemory"]){wasmMemory=Module["wasmMemory"]}else{wasmMemory=new WebAssembly.Memory({"initial":INITIAL_INITIAL_MEMORY/WASM_PAGE_SIZE,"maximum":2147483648/WASM_PAGE_SIZE})}if(wasmMemory){buffer=wasmMemory.buffer}INITIAL_INITIAL_MEMORY=buffer.byteLength;updateGlobalBufferAndViews(buffer);var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what+="";err(what);ABORT=true;EXITSTATUS=1;what="abort("+what+"). Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);throw e}function hasPrefix(str,prefix){return String.prototype.startsWith?str.startsWith(prefix):str.indexOf(prefix)===0}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return hasPrefix(filename,dataURIPrefix)}var fileURIPrefix="file://";function isFileURI(filename){return hasPrefix(filename,fileURIPrefix)}var wasmBinaryFile="signal.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(){try{if(wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(wasmBinaryFile)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&typeof fetch==="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary()})}return Promise.resolve().then(getBinary)}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmTable=Module["asm"]["d"];removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiatedSource(output){receiveInstance(output["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiatedSource,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiatedSource)})})}else{return instantiateArrayBuffer(receiveInstantiatedSource)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync();return{}}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){wasmTable.get(func)()}else{wasmTable.get(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}Module["callRuntimeCallbacks"]=callRuntimeCallbacks;function demangle(func){return func}Module["demangle"]=demangle;function demangleAll(text){var regex=/\b_Z[\w\d_]+/g;return text.replace(regex,function(x){var y=demangle(x);return x===y?x:y+" ["+x+"]"})}Module["demangleAll"]=demangleAll;function dynCallLegacy(sig,ptr,args){if(args&&args.length){return Module["dynCall_"+sig].apply(null,[ptr].concat(args))}return Module["dynCall_"+sig].call(null,ptr)}Module["dynCallLegacy"]=dynCallLegacy;function dynCall(sig,ptr,args){if(sig.indexOf("j")!=-1){return dynCallLegacy(sig,ptr,args)}return wasmTable.get(ptr).apply(null,args)}Module["dynCall"]=dynCall;function jsStackTrace(){var error=new Error;if(!error.stack){try{throw new Error}catch(e){error=e}if(!error.stack){return"(no stack trace available)"}}return error.stack.toString()}Module["jsStackTrace"]=jsStackTrace;function stackTrace(){var js=jsStackTrace();if(Module["extraStackTrace"])js+="\n"+Module["extraStackTrace"]();return demangleAll(js)}Module["stackTrace"]=stackTrace;function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}Module["_emscripten_memcpy_big"]=_emscripten_memcpy_big;function _emscripten_get_heap_size(){return HEAPU8.length}Module["_emscripten_get_heap_size"]=_emscripten_get_heap_size;function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}Module["emscripten_realloc_buffer"]=emscripten_realloc_buffer;function _emscripten_resize_heap(requestedSize){requestedSize=requestedSize>>>0;var oldSize=_emscripten_get_heap_size();var maxHeapSize=2147483648;if(requestedSize>maxHeapSize){return false}var minHeapSize=16777216;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(minHeapSize,requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}Module["_emscripten_resize_heap"]=_emscripten_resize_heap;__ATINIT__.push({func:function(){___wasm_call_ctors()}});var asmLibraryArg={"b":_emscripten_memcpy_big,"c":_emscripten_resize_heap,"a":wasmMemory};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["e"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["f"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["g"]).apply(null,arguments)};var _pre_processing=Module["_pre_processing"]=function(){return(_pre_processing=Module["_pre_processing"]=Module["asm"]["h"]).apply(null,arguments)};var _post_processing=Module["_post_processing"]=function(){return(_post_processing=Module["_post_processing"]=Module["asm"]["i"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["j"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["k"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["l"]).apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return(stackAlloc=Module["stackAlloc"]=Module["asm"]["m"]).apply(null,arguments)};Module["cwrap"]=cwrap;var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0)return;function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}noExitRuntime=true;run();
diff --git a/rnnoise/signal/signal.wasm b/rnnoise/signal/signal.wasm
new file mode 100755
index 00000000..66f9e5a8
Binary files /dev/null and b/rnnoise/signal/signal.wasm differ
diff --git a/rnnoise/utils/numpy.js b/rnnoise/utils/numpy.js
new file mode 100644
index 00000000..c40b2803
--- /dev/null
+++ b/rnnoise/utils/numpy.js
@@ -0,0 +1,299 @@
+/* jshint esversion: 6 */
+
+var numpy = numpy || {};
+
+numpy.Array = class {
+
+ constructor(buffer) {
+ if (buffer) {
+ const reader = new numpy.Reader(buffer);
+ const signature = [ 0x93, 0x4E, 0x55, 0x4D, 0x50, 0x59 ];
+ if (!reader.bytes(6).every((v, i) => v == signature[i])) {
+ throw new numpy.Error('Invalid signature.');
+ }
+ const major = reader.byte();
+ const minor = reader.byte();
+ if (major !== 1 && minor !== 0) {
+ throw new numpy.Error("Invalid version '" + [ major, minor ].join('.') + "'.");
+ }
+ const header = JSON.parse(reader.string().trim().replace(/'/g, '"').replace("False", "false").replace("(", "[").replace(/,*\),*/g, "]"));
+ if (header.fortran_order) {
+ throw new numpy.Error("Fortran order is not supported.'");
+ }
+ if (!header.descr || header.descr.length < 2) {
+ throw new numpy.Error("Missing property 'descr'.");
+ }
+ if (!header.shape) {
+ throw new numpy.Error("Missing property 'shape'.");
+ }
+ this._shape = header.shape;
+ this._byteOrder = header.descr[0];
+ switch (this._byteOrder) {
+ case '|': {
+ this._dataType = header.descr.substring(1);
+ this._data = reader.bytes(reader.size - reader.position);
+ break;
+ }
+ case '>':
+ case '<': {
+ if (header.descr.length !== 3) {
+ throw new numpy.Error("Unsupported data type '" + header.descr + "'.");
+ }
+ this._dataType = header.descr.substring(1);
+ const size = parseInt(header.descr[2], 10) * this._shape.reduce((a, b) => a * b, 1);
+ this._data = reader.bytes(size);
+ break;
+ }
+ default:
+ throw new numpy.Error("Unsupported data type '" + header.descr + "'.");
+ }
+ }
+ }
+
+ get data() {
+ return this._data;
+ }
+
+ set data(value) {
+ this._data = value;
+ }
+
+ get dataType() {
+ return this._dataType;
+ }
+
+ set dataType(value) {
+ this._dataType = value;
+ }
+
+ get shape() {
+ return this._shape;
+ }
+
+ set shape(value) {
+ this._shape = value;
+ }
+
+ get byteOrder() {
+ return this._byteOrder;
+ }
+
+ set byteOrder(value) {
+ this._byteOrder = value;
+ }
+
+ toBuffer() {
+
+ const writer = new numpy.Writer();
+
+ writer.bytes([ 0x93, 0x4E, 0x55, 0x4D, 0x50, 0x59 ]); // '\\x93NUMPY'
+ writer.byte(1); // major
+ writer.byte(0); // minor
+
+ const context = {
+ itemSize: 1,
+ position: 0,
+ dataType: this._dataType,
+ byteOrder: this._byteOrder || '<',
+ shape: this._shape,
+ descr: '',
+ };
+
+ if (context.byteOrder !== '<' && context.byteOrder !== '>') {
+ throw new numpy.Error("Unknown byte order '" + this._byteOrder + "'.");
+ }
+ if (context.dataType.length !== 2 || (context.dataType[0] !== 'f' && context.dataType[0] !== 'i' && context.dataType[0] !== 'u')) {
+ throw new numpy.Error("Unsupported data type '" + this._dataType + "'.");
+ }
+
+ context.itemSize = parseInt(context.dataType[1], 10);
+
+ let shape = '';
+ switch (this._shape.length) {
+ case 0:
+ throw new numpy.Error('Invalid shape.');
+ case 1:
+ shape = '(' + this._shape[0].toString() + ',)';
+ break;
+ default:
+ shape = '(' + this._shape.map((dimension) => dimension.toString()).join(', ') + ')';
+ break;
+ }
+
+ const properties = [
+ "'descr': '" + context.byteOrder + context.dataType + "'",
+ "'fortran_order': False",
+ "'shape': " + shape
+ ];
+ let header = '{ ' + properties.join(', ') + ' }';
+ header += ' '.repeat(16 - ((header.length + 2 + 8 + 1) & 0x0f)) + '\n';
+ writer.string(header);
+
+ const size = context.itemSize * this._shape.reduce((a, b) => a * b);
+ context.data = new Uint8Array(size);
+ context.view = new DataView(context.data.buffer, context.data.byteOffset, size);
+ numpy.Array._encodeDimension(context, this._data, 0);
+ writer.bytes(context.data);
+
+ return writer.toBuffer();
+ }
+
+ static _encodeDimension(context, data, dimension) {
+ const size = context.shape[dimension];
+ const littleEndian = context.byteOrder === '<';
+ if (dimension == context.shape.length - 1) {
+ for (let i = 0; i < size; i++) {
+ switch (context.dataType) {
+ case 'f2':
+ context.view.setFloat16(context.position, data[i], littleEndian);
+ break;
+ case 'f4':
+ context.view.setFloat32(context.position, data[i], littleEndian);
+ break;
+ case 'f8':
+ context.view.setFloat64(context.position, data[i], littleEndian);
+ break;
+ case 'i1':
+ context.view.setInt8(context.position, data[i], littleEndian);
+ break;
+ case 'i2':
+ context.view.setInt16(context.position, data[i], littleEndian);
+ break;
+ case 'i4':
+ context.view.setInt32(context.position, data[i], littleEndian);
+ break;
+ case 'i8':
+ context.view.setInt64(context.position, data[i], littleEndian);
+ break;
+ case 'u1':
+ context.view.setUint8(context.position, data[i], littleEndian);
+ break;
+ case 'u2':
+ context.view.setUint16(context.position, data[i], littleEndian);
+ break;
+ case 'u4':
+ context.view.setUint32(context.position, data[i], littleEndian);
+ break;
+ case 'u8':
+ context.view.setUint64(context.position, data[i], littleEndian);
+ break;
+ }
+ context.position += context.itemSize;
+ }
+ }
+ else {
+ for (let j = 0; j < size; j++) {
+ numpy.Array._encodeDimension(context, data[j], dimension + 1);
+ }
+ }
+ }
+};
+
+numpy.Reader = class {
+
+ constructor(buffer) {
+ this._buffer = buffer;
+ this._position = 0;
+ }
+
+ get position() {
+ return this._position;
+ }
+
+ get size() {
+ return this._buffer.length;
+ }
+
+ byte() {
+ return this._buffer[this._position++];
+ }
+
+ bytes(size) {
+ const value = this._buffer.slice(this._position, this._position + size);
+ this._position += size;
+ return value;
+ }
+
+ uint16() {
+ return this.byte() | (this.byte() << 8);
+ }
+
+ string() {
+ const size = this.uint16();
+ let value = '';
+ for (let i = 0; i < size; i++) {
+ value += String.fromCharCode(this.byte());
+ }
+ return value;
+ }
+};
+
+numpy.Writer = class {
+
+ constructor() {
+ this._length = 0;
+ this._head = null;
+ this._tail = null;
+ }
+
+ byte(value) {
+ this.bytes([ value ]);
+ }
+
+ uint16(value) {
+ this.bytes([ value & 0xff, (value >> 8) & 0xff ]);
+ }
+
+ bytes(values) {
+ const array = new Uint8Array(values.length);
+ for (let i = 0; i < values.length; i++) {
+ array[i] = values[i];
+ }
+ this._write(array);
+ }
+
+ string(value) {
+ this.uint16(value.length);
+ const array = new Uint8Array(value.length);
+ for (let i = 0; i < value.length; i++) {
+ array[i] = value.charCodeAt(i);
+ }
+ this._write(array);
+ }
+
+ _write(array) {
+ const node = { buffer: array, next: null };
+ if (this._tail) {
+ this._tail.next = node;
+ }
+ else {
+ this._head = node;
+ }
+ this._tail = node;
+ this._length += node.buffer.length;
+ }
+
+ toBuffer() {
+ const array = new Uint8Array(this._length);
+ let position = 0;
+ let head = this._head;
+ while (head != null) {
+ array.set(head.buffer, position);
+ position += head.buffer.length;
+ head = head.next;
+ }
+ return array;
+ }
+};
+
+numpy.Error = class extends Error {
+
+ constructor(message) {
+ super(message);
+ this.name = 'NumPy Error';
+ }
+};
+
+if (typeof module !== 'undefined' && typeof module.exports === 'object') {
+ module.exports.Array = numpy.Array;
+}
\ No newline at end of file
diff --git a/rnnoise/utils/recorderWorker.js b/rnnoise/utils/recorderWorker.js
new file mode 100644
index 00000000..a24bb45d
--- /dev/null
+++ b/rnnoise/utils/recorderWorker.js
@@ -0,0 +1,147 @@
+var recLength = 0,
+ recBuffers = [],
+ sampleRate,
+ numChannels;
+
+this.onmessage = function(e){
+ switch(e.data.command){
+ case 'init':
+ init(e.data.config);
+ break;
+ case 'record':
+ record(e.data.buffer);
+ break;
+ case 'exportWAV':
+ exportWAV(e.data.type);
+ break;
+ case 'getBuffer':
+ getBuffer();
+ break;
+ case 'clear':
+ clear();
+ break;
+ }
+};
+
+function init(config){
+ sampleRate = config.sampleRate;
+ numChannels = config.numChannels;
+ initBuffers();
+}
+
+function record(inputBuffer){
+ for (var channel = 0; channel < numChannels; channel++){
+ recBuffers[channel].push(inputBuffer[channel]);
+ }
+ recLength += inputBuffer[0].length;
+}
+
+function exportWAV(type){
+ var buffers = [];
+ for (var channel = 0; channel < numChannels; channel++){
+ buffers.push(mergeBuffers(recBuffers[channel], recLength));
+ }
+ if (numChannels === 2){
+ var interleaved = interleave(buffers[0], buffers[1]);
+ } else {
+ var interleaved = buffers[0];
+ }
+ var dataview = encodeWAV(interleaved);
+ var audioBlob = new Blob([dataview], { type: type });
+
+ this.postMessage(audioBlob);
+}
+
+function getBuffer(){
+ var buffers = [];
+ for (var channel = 0; channel < numChannels; channel++){
+ buffers.push(mergeBuffers(recBuffers[channel], recLength));
+ }
+ this.postMessage(buffers);
+}
+
+function clear(){
+ recLength = 0;
+ recBuffers = [];
+ initBuffers();
+}
+
+function initBuffers(){
+ for (var channel = 0; channel < numChannels; channel++){
+ recBuffers[channel] = [];
+ }
+}
+
+function mergeBuffers(recBuffers, recLength){
+ var result = new Float32Array(recLength);
+ var offset = 0;
+ for (var i = 0; i < recBuffers.length; i++){
+ result.set(recBuffers[i], offset);
+ offset += recBuffers[i].length;
+ }
+ return result;
+}
+
+function interleave(inputL, inputR){
+ var length = inputL.length + inputR.length;
+ var result = new Float32Array(length);
+
+ var index = 0,
+ inputIndex = 0;
+
+ while (index < length){
+ result[index++] = inputL[inputIndex];
+ result[index++] = inputR[inputIndex];
+ inputIndex++;
+ }
+ return result;
+}
+
+function floatTo16BitPCM(output, offset, input){
+ for (var i = 0; i < input.length; i++, offset+=2){
+ var s = Math.max(-1, Math.min(1, input[i]));
+ output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
+ }
+}
+
+function writeString(view, offset, string){
+ for (var i = 0; i < string.length; i++){
+ view.setUint8(offset + i, string.charCodeAt(i));
+ }
+}
+
+function encodeWAV(samples){
+ var buffer = new ArrayBuffer(44 + samples.length * 2);
+ var view = new DataView(buffer);
+
+ /* RIFF identifier */
+ writeString(view, 0, 'RIFF');
+ /* RIFF chunk length */
+ view.setUint32(4, 36 + samples.length * 2, true);
+ /* RIFF type */
+ writeString(view, 8, 'WAVE');
+ /* format chunk identifier */
+ writeString(view, 12, 'fmt ');
+ /* format chunk length */
+ view.setUint32(16, 16, true);
+ /* sample format (raw) */
+ view.setUint16(20, 1, true);
+ /* channel count */
+ view.setUint16(22, numChannels, true);
+ /* sample rate */
+ view.setUint32(24, sampleRate, true);
+ /* byte rate (sample rate * block align) */
+ view.setUint32(28, sampleRate * 4, true);
+ /* block align (channel count * bytes per sample) */
+ view.setUint16(32, numChannels * 2, true);
+ /* bits per sample */
+ view.setUint16(34, 16, true);
+ /* data chunk identifier */
+ writeString(view, 36, 'data');
+ /* data chunk length */
+ view.setUint32(40, samples.length * 2, true);
+
+ floatTo16BitPCM(view, 44, samples);
+
+ return view;
+}