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 + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+ +
+ +
+
Original audio:
+ +
+
+
+
+
+
+
+
+
+
+
Denoised audio:
+ +
+
+
+
+
+ + + + + + + + + + + \ 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; +}