speaker-calibration 2.1.17 → 2.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Procfile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
web: node example/server.js
|
package/dist/main.js
CHANGED
|
@@ -763,7 +763,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var qrco
|
|
|
763
763
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
764
764
|
|
|
765
765
|
"use strict";
|
|
766
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! axios */ \"./node_modules/axios/index.js\");\n/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(axios__WEBPACK_IMPORTED_MODULE_0__);\n\r\n/**\r\n *\r\n */\r\nclass PythonServerAPI {\r\n static PYTHON_SERVER_URL = 'https://easyeyes-python-flask-server.herokuapp.com';\r\n\r\n static TEST_SERVER_URL = 'http://127.0.0.1:5000';\r\n /**\r\n * @param data- -\r\n * g = inverted impulse response, when convolved with the impulse\r\n * reponse, they cancel out.\r\n * @param data.payload\r\n * @param data.sampleRate\r\n * @param data.P\r\n * @param data-.payload\r\n * @param data-.sampleRate\r\n * @param data-.P\r\n * @returns\r\n * @example\r\n */\r\n getImpulseResponse = async ({mls, payload, sampleRate, P}) => {\r\n const task = 'impulse-response';\r\n let res = null;\r\n\r\n console.log({payload});\r\n\r\n const data = JSON.stringify({\r\n task,\r\n payload,\r\n 'sample-rate': sampleRate,\r\n mls,\r\n P,\r\n });\r\n\r\n await axios__WEBPACK_IMPORTED_MODULE_0___default()({\r\n method: 'post',\r\n baseURL: PythonServerAPI.PYTHON_SERVER_URL,\r\n url: `/task/${task}`,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n data,\r\n })\r\n .then(response => {\r\n res = response;\r\n })\r\n .catch(error => {\r\n throw error;\r\n });\r\n return res.data[task];\r\n };\r\n\r\n getPSD = async ({unconv_rec, conv_rec}) => {\r\n const task = 'psd';\r\n let res = null;\r\n\r\n const data = JSON.stringify({\r\n task,\r\n unconv_rec,\r\n conv_rec,\r\n });\r\n\r\n await axios__WEBPACK_IMPORTED_MODULE_0___default()({\r\n method: 'post',\r\n baseURL: PythonServerAPI.PYTHON_SERVER_URL,\r\n url: `/task/${task}`,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n data,\r\n })\r\n .then(response => {\r\n res = response;\r\n })\r\n .catch(error => {\r\n throw error;\r\n });\r\n return res.data[task];\r\n };\r\n\r\n getInverseImpulseResponse = async ({payload,mls,lowHz,highHz}) => {\r\n const task = 'inverse-impulse-response';\r\n let res = null;\r\n\r\n console.log({payload});\r\n\r\n const data = JSON.stringify({\r\n task,\r\n payload,\r\n mls,\r\n lowHz,\r\n highHz\r\n });\r\n\r\n await axios__WEBPACK_IMPORTED_MODULE_0___default()({\r\n method: 'post',\r\n baseURL: PythonServerAPI.PYTHON_SERVER_URL,\r\n url: `/task/${task}`,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n data,\r\n })\r\n .then(response => {\r\n res = response;\r\n })\r\n .catch(error => {\r\n throw error;\r\n });\r\n\r\n return res.data[task];\r\n };\r\n\r\n getVolumeCalibration = async ({payload, sampleRate, lCalib}) => {\r\n const task = 'volume';\r\n let res = null;\r\n\r\n console.log({payload});\r\n\r\n const data = JSON.stringify({\r\n task,\r\n payload,\r\n 'sample-rate': sampleRate,\r\n lCalib,\r\n });\r\n\r\n await axios__WEBPACK_IMPORTED_MODULE_0___default()({\r\n method: 'post',\r\n baseURL: PythonServerAPI.PYTHON_SERVER_URL,\r\n url: `/task/${task}`,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n data,\r\n })\r\n .then(response => {\r\n res = response;\r\n })\r\n .catch(error => {\r\n throw error;\r\n });\r\n\r\n return res.data[task];\r\n };\r\n\r\n getVolumeCalibrationParameters = async ({inDBValues, outDBSPLValues, lCalib}) => {\r\n const task = 'volume-parameters';\r\n let res = null;\r\n\r\n const data = JSON.stringify({\r\n task,\r\n inDBValues,\r\n outDBSPLValues,\r\n lCalib,\r\n });\r\n\r\n await axios__WEBPACK_IMPORTED_MODULE_0___default()({\r\n method: 'post',\r\n baseURL: PythonServerAPI.PYTHON_SERVER_URL, //server\r\n url: `/task/${task}`,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n data,\r\n })\r\n .then(response => {\r\n res = response;\r\n })\r\n .catch(error => {\r\n throw error;\r\n });\r\n\r\n // console.log(res.data[task]);\r\n //below is an example of res.data[task]\r\n //{\r\n // R: 16.56981076554259,\r\n // RMSError: 1.9289162528535229\r\n // T: -47.79799120884434,\r\n // W: 61.0485247483732,\r\n // backgroundDBSPL: 43.88233142069752,\r\n // gainDBSPL: -128.24742161208985\r\n //}\r\n return res.data[task];\r\n };\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (PythonServerAPI);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/server/PythonServerAPI.js?");
|
|
766
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! axios */ \"./node_modules/axios/index.js\");\n/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(axios__WEBPACK_IMPORTED_MODULE_0__);\n\r\n/**\r\n *\r\n */\r\nclass PythonServerAPI {\r\n static PYTHON_SERVER_URL = 'https://easyeyes-python-flask-server.herokuapp.com';\r\n\r\n static TEST_SERVER_URL = 'http://127.0.0.1:5000';\r\n\r\n /** @private */\r\n MAX_RETRY_COUNT = 3;\r\n /** @private */\r\n RETRY_DELAY_MS = 1000;\r\n /**\r\n * @param data- -\r\n * g = inverted impulse response, when convolved with the impulse\r\n * reponse, they cancel out.\r\n * @param data.payload\r\n * @param data.sampleRate\r\n * @param data.P\r\n * @param data-.payload\r\n * @param data-.sampleRate\r\n * @param data-.P\r\n * @returns\r\n * @example\r\n */\r\n getImpulseResponse = async ({mls, payload, sampleRate, P}) => {\r\n const task = 'impulse-response';\r\n let res = null;\r\n\r\n console.log({payload});\r\n\r\n const data = JSON.stringify({\r\n task,\r\n payload,\r\n 'sample-rate': sampleRate,\r\n mls,\r\n P,\r\n });\r\n\r\n await axios__WEBPACK_IMPORTED_MODULE_0___default()({\r\n method: 'post',\r\n baseURL: PythonServerAPI.PYTHON_SERVER_URL,\r\n url: `/task/${task}`,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n data,\r\n })\r\n .then(response => {\r\n res = response;\r\n })\r\n .catch(error => {\r\n throw error;\r\n });\r\n return res.data[task];\r\n };\r\n\r\n getPSD = async ({unconv_rec, conv_rec}) => {\r\n const task = 'psd';\r\n let res = null;\r\n\r\n const data = JSON.stringify({\r\n task,\r\n unconv_rec,\r\n conv_rec,\r\n });\r\n\r\n await axios__WEBPACK_IMPORTED_MODULE_0___default()({\r\n method: 'post',\r\n baseURL: PythonServerAPI.PYTHON_SERVER_URL,\r\n url: `/task/${task}`,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n data,\r\n })\r\n .then(response => {\r\n res = response;\r\n })\r\n .catch(error => {\r\n throw error;\r\n });\r\n return res.data[task];\r\n };\r\n \r\n getPSDWithRetry = async ({ unconv_rec, conv_rec }) => {\r\n let retryCount = 0;\r\n let response = null;\r\n \r\n while (retryCount < this.MAX_RETRY_COUNT) {\r\n try {\r\n response = await this.getPSD({ unconv_rec, conv_rec });\r\n // If the request is successful, break out of the loop\r\n break;\r\n } catch (error) {\r\n console.error(`Error occurred. Retrying... (${retryCount + 1}/${this.MAX_RETRY_COUNT})`);\r\n retryCount++;\r\n await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY_MS));\r\n }\r\n }\r\n \r\n if (response) {\r\n return response;\r\n } else {\r\n throw new Error(`Failed to get PSD after ${this.MAX_RETRY_COUNT} attempts.`);\r\n }\r\n };\r\n \r\n\r\n getInverseImpulseResponse = async ({payload,mls,lowHz,highHz}) => {\r\n const task = 'inverse-impulse-response';\r\n let res = null;\r\n\r\n console.log({payload});\r\n\r\n const data = JSON.stringify({\r\n task,\r\n payload,\r\n mls,\r\n lowHz,\r\n highHz\r\n });\r\n\r\n await axios__WEBPACK_IMPORTED_MODULE_0___default()({\r\n method: 'post',\r\n baseURL: PythonServerAPI.PYTHON_SERVER_URL,\r\n url: `/task/${task}`,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n data,\r\n })\r\n .then(response => {\r\n res = response;\r\n })\r\n .catch(error => {\r\n throw error;\r\n });\r\n\r\n return res.data[task];\r\n };\r\n\r\ngetInverseImpulseResponseWithRetry = async ({ payload, mls, lowHz, highHz }) => {\r\n let retryCount = 0;\r\n let response = null;\r\n\r\n while (retryCount < this.MAX_RETRY_COUNT) {\r\n try {\r\n response = await this.getInverseImpulseResponse({ payload, mls, lowHz, highHz });\r\n // If the request is successful, break out of the loop\r\n break;\r\n } catch (error) {\r\n console.error(`Error occurred. Retrying... (${retryCount + 1}/${this.MAX_RETRY_COUNT})`);\r\n retryCount++;\r\n await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY_MS));\r\n }\r\n }\r\n\r\n if (response) {\r\n return response;\r\n } else {\r\n throw new Error(`Failed to get inverse impulse response after ${this.MAX_RETRY_COUNT} attempts.`);\r\n }\r\n};\r\n\r\n\r\n getVolumeCalibration = async ({payload, sampleRate, lCalib}) => {\r\n const task = 'volume';\r\n let res = null;\r\n\r\n console.log({payload});\r\n\r\n const data = JSON.stringify({\r\n task,\r\n payload,\r\n 'sample-rate': sampleRate,\r\n lCalib,\r\n });\r\n\r\n await axios__WEBPACK_IMPORTED_MODULE_0___default()({\r\n method: 'post',\r\n baseURL: PythonServerAPI.PYTHON_SERVER_URL,\r\n url: `/task/${task}`,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n data,\r\n })\r\n .then(response => {\r\n res = response;\r\n })\r\n .catch(error => {\r\n throw error;\r\n });\r\n\r\n return res.data[task];\r\n };\r\n\r\n getVolumeCalibrationParameters = async ({inDBValues, outDBSPLValues, lCalib}) => {\r\n const task = 'volume-parameters';\r\n let res = null;\r\n\r\n const data = JSON.stringify({\r\n task,\r\n inDBValues,\r\n outDBSPLValues,\r\n lCalib,\r\n });\r\n\r\n await axios__WEBPACK_IMPORTED_MODULE_0___default()({\r\n method: 'post',\r\n baseURL: PythonServerAPI.PYTHON_SERVER_URL, //server\r\n url: `/task/${task}`,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n data,\r\n })\r\n .then(response => {\r\n res = response;\r\n })\r\n .catch(error => {\r\n throw error;\r\n });\r\n\r\n // console.log(res.data[task]);\r\n //below is an example of res.data[task]\r\n //{\r\n // R: 16.56981076554259,\r\n // RMSError: 1.9289162528535229\r\n // T: -47.79799120884434,\r\n // W: 61.0485247483732,\r\n // backgroundDBSPL: 43.88233142069752,\r\n // gainDBSPL: -128.24742161208985\r\n //}\r\n return res.data[task];\r\n };\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (PythonServerAPI);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/server/PythonServerAPI.js?");
|
|
767
767
|
|
|
768
768
|
/***/ }),
|
|
769
769
|
|
|
@@ -796,7 +796,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _myE
|
|
|
796
796
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
797
797
|
|
|
798
798
|
"use strict";
|
|
799
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../audioCalibrator */ \"./src/tasks/audioCalibrator.js\");\n/* harmony import */ var _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mlsGen/mlsGenInterface */ \"./src/tasks/combination/mlsGen/mlsGenInterface.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../utils */ \"./src/utils.js\");\n\r\n\r\n\r\n\r\n\r\n/**\r\n *\r\n */\r\nclass Combination extends _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n /**\r\n * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.\r\n *\r\n * @param {Object<boolean, number, number, number>} calibratorParams - paramter object\r\n * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures\r\n * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated\r\n * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform\r\n * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture\r\n */\r\n constructor({download = false, mlsOrder = 18, numCaptures = 3, numMLSPerCapture = 4, lowHz = 20, highHz = 10000}) {\r\n super(numCaptures, numMLSPerCapture);\r\n this.#mlsOrder = parseInt(mlsOrder, 10);\r\n this.#P = 2 ** mlsOrder - 1;\r\n this.#download = download;\r\n this.#mls = [];\r\n this.#lowHz = lowHz;\r\n this.#highHz = highHz;\r\n }\r\n\r\n /** @private */\r\n stepNum = 0;\r\n\r\n /** @private */\r\n totalSteps = 25;\r\n\r\n /** @private */\r\n #download;\r\n\r\n /** @private */\r\n #mlsGenInterface;\r\n\r\n /** @private */\r\n #mlsBufferView;\r\n\r\n /** @private */\r\n invertedImpulseResponse = null;\r\n\r\n /** @private */\r\n impulseResponses = [];\r\n\r\n /** @private */\r\n #mlsOrder;\r\n\r\n /** @private */\r\n #lowHz;\r\n\r\n /** @private */\r\n #highHz;\r\n\r\n /** @private */\r\n #mls;\r\n\r\n /** @private */\r\n #P;\r\n\r\n /** @private */\r\n #audioContext;\r\n\r\n /** @private */\r\n TAPER_SECS = 5;\r\n\r\n /** @private */\r\n offsetGainNode;\r\n\r\n /** @private */\r\n convolution;\r\n ////////////////////////volume\r\n /** @private */\r\n #CALIBRATION_TONE_FREQUENCY = 1000; // Hz\r\n\r\n /** @private */\r\n #CALIBRATION_TONE_TYPE = 'sine';\r\n\r\n /** @private */\r\n #CALIBRATION_TONE_DURATION = 5; // seconds\r\n\r\n /** @private */\r\n outDBSPL = null;\r\n THD = null;\r\n outDBSPL1000 = null;\r\n\r\n /** @private */\r\n TAPER_SECS = 0.010; // seconds\r\n\r\n /** .\r\n * .\r\n * .\r\n * Sends all the computed impulse responses to the backend server for processing\r\n *\r\n * @returns sets the resulting inverted impulse response to the class property\r\n * @example\r\n */\r\n sendImpulseResponsesToServerForProcessing = async () => {\r\n const computedIRs = await Promise.all(this.impulseResponses);\r\n const filteredComputedIRs = computedIRs.filter(element => {\r\n return element != undefined;\r\n });\r\n const mls = this.#mls;\r\n const lowHz = this.#lowHz;\r\n const highHz = this.#highHz;\r\n this.stepNum += 1;\r\n this.emit('update', {message: `All Hz Calibration Step ${this.stepNum}/${this.totalSteps}: computing the IIR...`});\r\n return this.pyServerAPI\r\n .getInverseImpulseResponse({\r\n payload: filteredComputedIRs.slice(0, this.numCaptures),\r\n mls,\r\n lowHz,\r\n highHz\r\n })\r\n .then(res => {\r\n console.log(res);\r\n this.stepNum += 1;\r\n this.emit('update', {message: `All Hz Calibration Step ${this.stepNum}/${this.totalSteps}: done computing the IIR...`});\r\n this.invertedImpulseResponse = res[\"iir\"];\r\n this.convolution = res[\"convolution\"];\r\n })\r\n .catch(err => {\r\n // this.emit('InvertedImpulseResponse', {res: false});\r\n console.error(err);\r\n });\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing\r\n *\r\n * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed\r\n * @example\r\n */\r\n sendRecordingToServerForProcessing = signalCsv => {\r\n const allSignals = this.getAllRecordedSignals();\r\n const numSignals = allSignals.length;\r\n const mls = this.#mls;\r\n const payload =\r\n signalCsv && signalCsv.length > 0 ? (0,_utils__WEBPACK_IMPORTED_MODULE_2__.csvToArray)(signalCsv) : allSignals[numSignals - 1];\r\n console.log('sending rec');\r\n this.stepNum += 1;\r\n this.emit('update', {message: `All Hz Calibration Step ${this.stepNum}/${this.totalSteps}: computing the IR of the last recording...`});\r\n this.impulseResponses.push(\r\n this.pyServerAPI\r\n .getImpulseResponse({\r\n sampleRate: this.sourceSamplingRate || 96000,\r\n payload,\r\n mls,\r\n P: this.#P,\r\n })\r\n .then(res => {\r\n if (this.numSuccessfulCaptured < this.numCaptures) {\r\n this.numSuccessfulCaptured += 1;\r\n console.log(\"num succ capt: \" + this.numSuccessfulCaptured);\r\n this.stepNum += 1;\r\n this.emit('update', {\r\n message: `All Hz Calibration Step ${this.stepNum}/${this.totalSteps}: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`,\r\n });\r\n }\r\n return res;\r\n })\r\n .catch(err => {\r\n console.error(err);\r\n })\r\n );\r\n };\r\n\r\n /**\r\n * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number\r\n * of MLS periods defined in the constructor.\r\n *\r\n * @example\r\n */\r\n #awaitDesiredMLSLength = async () => {\r\n // seconds per MLS = P / SR\r\n // await N * P / SR\r\n this.stepNum += 1;\r\n this.emit('update', {\r\n message: `All Hz Calibration Step ${this.stepNum}/${this.totalSteps}: sampling the calibration signal...`,\r\n });\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)((this.#P / this.sourceSamplingRate) * this.numMLSPerCapture);\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state\r\n *\r\n * @example\r\n */\r\n #awaitSignalOnset = async () => {\r\n this.stepNum += 1;\r\n this.emit('update', {\r\n message: `All Hz Calibration Step ${this.stepNum}/${this.totalSteps}: waiting for the signal to stabilize...`,\r\n });\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(this.TAPER_SECS);\r\n };\r\n\r\n /**\r\n * Called immediately after a recording is captured. Used to process the resulting signal\r\n * whether by sending the result to a server or by computing a result locally.\r\n *\r\n * @example\r\n */\r\n #afterMLSRecord = () => {\r\n console.log('after record');\r\n this.sendRecordingToServerForProcessing();\r\n };\r\n\r\n #afterMLSwIIRRecord = () => {\r\n if (this.numSuccessfulCaptured < this.numCaptures) {\r\n this.numSuccessfulCaptured += 1;\r\n this.stepNum += 1;\r\n this.emit('update', {\r\n message: `All Hz Calibration Step ${this.stepNum}/${this.totalSteps}: ${this.numSuccessfulCaptured} recordings of convolved MLS captured`,\r\n });\r\n }\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Created an S Curver Buffer to taper the signal onset\r\n *\r\n * @param {*} length\r\n * @param {*} phase\r\n * @returns\r\n * @example\r\n */\r\n static createSCurveBuffer = (length, phase) => {\r\n const curve = new Float32Array(length);\r\n let i;\r\n for (i = 0; i < length; i += 1) {\r\n // scale the curve to be between 0-1\r\n curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\r\n }\r\n return curve;\r\n };\r\n\r\n static createInverseSCurveBuffer = (length, phase) => {\r\n const curve = new Float32Array(length);\r\n let i;\r\n let j = length - 1;\r\n for (i = 0; i < length; i += 1) {\r\n // scale the curve to be between 0-1\r\n curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;\r\n j -= 1;\r\n }\r\n return curve;\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @param CALIBRATION_TONE_FREQUENCY\r\n * @private\r\n * @example\r\n */\r\n #createPureTonenNode = CALIBRATION_TONE_FREQUENCY => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n\r\n oscilator.frequency.value = CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = 'sine';\r\n gainNode.gain.value = 0.04;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @param dataBuffer\r\n * @private\r\n * @example\r\n */\r\n #createCalibrationNodeFromBuffer = dataBuffer => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const buffer = audioContext.createBuffer(\r\n 1, // number of channels\r\n dataBuffer.length,\r\n audioContext.sampleRate // sample rate\r\n );\r\n\r\n const data = buffer.getChannelData(0); // get data\r\n // fill the buffer with our data\r\n try {\r\n for (let i = 0; i < dataBuffer.length; i += 1) {\r\n data[i] = dataBuffer[i]*.1;\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n console.log(\"mls second, same?\");\r\n console.log(data);\r\n const onsetGainNode = audioContext.createGain();\r\n this.offsetGainNode = audioContext.createGain();\r\n const source = audioContext.createBufferSource();\r\n\r\n source.buffer = buffer;\r\n source.loop = true;\r\n source.connect(onsetGainNode);\r\n onsetGainNode.connect(this.offsetGainNode);\r\n this.offsetGainNode.connect(audioContext.destination);\r\n\r\n const onsetCurve = Combination.createSCurveBuffer(this.sourceSamplingRate, Math.PI / 2);\r\n onsetGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);\r\n this.addCalibrationNode(source);\r\n };\r\n\r\n /**\r\n * Given a data buffer, creates the required calibration node\r\n *\r\n * @param {*} dataBufferArray\r\n * @example\r\n */\r\n #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {\r\n if (dataBufferArray.length === 1) {\r\n console.log('data buffer aray');\r\n console.log(dataBufferArray);\r\n this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);\r\n } else {\r\n throw new Error('The length of the data buffer array must be 1');\r\n }\r\n\r\n \r\n };\r\n\r\n /**\r\n * function to put MLS filtered IIR data obtained from\r\n * python server into our audio buffer to be played aloud\r\n */\r\n #putInPythonConv = () => {\r\n const audioCtx = this.makeNewSourceAudioContextConvolved();\r\n const buffer = audioCtx.createBuffer(\r\n 1, // number of channels\r\n this.convolution.length,\r\n audioCtx.sampleRate // sample rate\r\n );\r\n\r\n const data = buffer.getChannelData(0); // get data\r\n // fill the buffer with our data\r\n try {\r\n for (let i = 0; i < this.convolution.length; i += 1) {\r\n data[i] = this.convolution[i];\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n const source = audioCtx.createBufferSource();\r\n\r\n source.buffer = buffer;\r\n source.loop = true;\r\n source.connect(audioCtx.destination);\r\n\r\n this.addCalibrationNodeConvolved(source);\r\n }\r\n \r\n /**\r\n * Creates an audio context and plays it for a few seconds.\r\n *\r\n * @private\r\n * @returns - Resolves when the audio is done playing.\r\n * @example\r\n */\r\n #playCalibrationAudio = () => {\r\n this.calibrationNodes[0].start(0);\r\n this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);\r\n this.stepNum += 1;\r\n this.emit('update', {message: `All Hz Calibration Step ${this.stepNum}/${this.totalSteps}: playing the calibration tone...`});\r\n }; \r\n\r\n\r\n #playCalibrationAudioConvolved = () => {\r\n this.calibrationNodesConvolved[0].start(0);\r\n this.stepNum += 1;\r\n this.emit('update',{message: `All Hz Calibration Step ${this.stepNum}/${this.totalSteps}: playing the convolved calibration tone...`})\r\n }\r\n\r\n /** .\r\n * .\r\n * .\r\n * Stops the audio with tapered offset\r\n *\r\n * @example\r\n */\r\n #stopCalibrationAudio = () => {\r\n this.offsetGainNode.gain.setValueAtTime(\r\n this.offsetGainNode.gain.value,\r\n this.sourceAudioContext.currentTime\r\n );\r\n\r\n this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContext.currentTime, 0.5);\r\n this.calibrationNodes[0].stop(0);\r\n this.sourceAudioContext.close();\r\n this.stepNum += 1;\r\n this.emit('update', {message: `All Hz Calibration Step ${this.stepNum}/${this.totalSteps}: stopping the calibration tone...`});\r\n };\r\n\r\n #stopCalibrationAudioConvolved = () => {\r\n this.offsetGainNode.gain.setValueAtTime(\r\n this.offsetGainNode.gain.value,\r\n this.sourceAudioContextConvolved.currentTime\r\n );\r\n\r\n this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContextConvolved.currentTime, 0.5);\r\n //this.calibrationNodesConvolved[0].stop(0);\r\n console.log(\"right before closing volved audio context\");\r\n this.sourceAudioContextConvolved.close();\r\n this.stepNum += 1;\r\n this.emit('update', {message: `All Hz Calibration Step ${this.stepNum}/${this.totalSteps}: stopping the convolved calibration tone...`});\r\n\r\n }\r\n\r\n playMLSwithIIR = async (stream, iir) => {\r\n console.log('play mls with iir');\r\n this.invertedImpulseResponse = iir;\r\n // initialize the MLSGenInterface object with it's factory method\r\n \r\n await _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__[\"default\"].factory(\r\n this.#mlsOrder,\r\n this.sinkSamplingRate,\r\n this.sourceSamplingRate\r\n ).then(mlsGenInterface => {\r\n this.#mlsGenInterface = mlsGenInterface;\r\n this.#mlsBufferView = this.#mlsGenInterface.getMLS();\r\n });\r\n\r\n console.log('after mls factory'); //works up to here.\r\n console.log(this.#mls);\r\n // after intializating, start the calibration steps with garbage collection\r\n await this.#mlsGenInterface.withGarbageCollection([\r\n () =>\r\n this.calibrationSteps(\r\n stream,\r\n this.#playCalibrationAudioConvolved, // play audio func (required)\r\n this.#putInPythonConv, // before play func\r\n this.#awaitSignalOnset, // before record\r\n () => this.numSuccessfulCaptured < this.numCaptures,\r\n this.#awaitDesiredMLSLength, // during record\r\n this.#afterMLSwIIRRecord, // after record\r\n 'filtered'\r\n ),\r\n ]);\r\n };\r\n\r\n /**\r\n * Public method to start the calibration process. Objects intialized from webassembly allocate new memory\r\n * and must be manually freed. This function is responsible for intializing the MlsGenInterface,\r\n * and wrapping the calibration steps with a garbage collection safe gaurd.\r\n *\r\n * @public\r\n * @param stream - The stream of audio from the Listener.\r\n * @example\r\n */\r\n startCalibrationImpulseResponse = async stream => {\r\n // initialize the MLSGenInterface object with it's factory method\r\n await _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__[\"default\"].factory(\r\n this.#mlsOrder,\r\n this.sinkSamplingRate,\r\n this.sourceSamplingRate\r\n ).then(mlsGenInterface => {\r\n this.#mlsGenInterface = mlsGenInterface;\r\n this.#mlsBufferView = this.#mlsGenInterface.getMLS();\r\n });\r\n\r\n // after intializating, start the calibration steps with garbage collection\r\n await this.#mlsGenInterface.withGarbageCollection([\r\n () =>\r\n this.calibrationSteps(\r\n stream,\r\n this.#playCalibrationAudio, // play audio func (required)\r\n this.#setCalibrationNodesFromBuffer, // before play func\r\n this.#awaitSignalOnset, // before record\r\n () => this.numSuccessfulCaptured < this.numCaptures, // loop while true\r\n this.#awaitDesiredMLSLength, // during record\r\n this.#afterMLSRecord, // after record\r\n 'unfiltered'\r\n ),\r\n ]);\r\n\r\n this.#stopCalibrationAudio();\r\n\r\n // at this stage we've captured all the required signals,\r\n // and have received IRs for each one\r\n // so let's send all the IRs to the server to be converted to a single IIR\r\n await this.sendImpulseResponsesToServerForProcessing();\r\n\r\n this.numSuccessfulCaptured = 0;\r\n // debugging function, use to test the result of the IIR\r\n await this.playMLSwithIIR(stream, this.invertedImpulseResponse);\r\n this.#stopCalibrationAudioConvolved();\r\n\r\n let recs = this.getAllRecordedSignals();\r\n let conv_recs = this.getAllFilteredRecordedSignals();\r\n let unconv_rec = recs[0];\r\n let conv_rec = conv_recs[0];\r\n\r\n let results = await this.pyServerAPI\r\n .getPSD({\r\n unconv_rec,\r\n conv_rec,\r\n })\r\n .then(res => {\r\n return res;\r\n })\r\n .catch(err => {\r\n console.error(err);\r\n })\r\n\r\n let iir_and_plots = {\r\n \"iir\": this.invertedImpulseResponse,\r\n \"x_unconv\": results[\"x_unconv\"],\r\n \"y_unconv\": results[\"y_unconv\"],\r\n \"x_conv\": results[\"x_conv\"],\r\n \"y_conv\": results[\"y_conv\"]\r\n }\r\n if (this.#download) {\r\n this.downloadSingleUnfilteredRecording();\r\n this.downloadSingleFilteredRecording();\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.#mls,\"MLS.csv\");\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.convolution,'python_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.invertedImpulseResponse,'IIR.csv');\r\n const computedIRagain = await Promise.all(this.impulseResponses)\r\n .then(res => {\r\n for (let i = 0; i < res.length; i++){\r\n if (res[i] != undefined){\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(res[i], `IR_${i}`);\r\n }\r\n }\r\n })\r\n }\r\n\r\n return iir_and_plots;\r\n };\r\n\r\n //////////////////////volume\r\n\r\n handleIncomingData = data => {\r\n console.log('Received data: ', data);\r\n if (data.type === 'soundGainDBSPL') {\r\n this.soundGainDBSPL = data.value;\r\n } else {\r\n throw new Error(`Unknown data type: ${data.type}`);\r\n }\r\n };\r\n\r\n createSCurveBuffer = (onSetBool=true) => {\r\n\r\n const curve = new Float32Array(this.TAPER_SECS*this.sourceSamplingRate+1);\r\n const frequency = 1 / (4 * this.TAPER_SECS);\r\n let j = 0;\r\n for (let i = 0; i < this.TAPER_SECS*this.sourceSamplingRate+1; i += 1) {\r\n const phase = 2 * Math.PI * frequency * j;\r\n const onsetTaper = Math.pow(Math.sin(phase) , 2);\r\n const offsetTaper = Math.pow(Math.cos(phase) , 2);\r\n curve[i] = onSetBool? onsetTaper : offsetTaper;\r\n j += (1 / this.sourceSamplingRate);\r\n } \r\n return curve;\r\n };\r\n\r\n #getTruncatedSignal = (left = 3.5, right = 4.5) => {\r\n const start = Math.floor(left * this.sourceSamplingRate);\r\n const end = Math.floor(right * this.sourceSamplingRate);\r\n const result = Array.from(this.getLastRecordedSignal().slice(start, end));\r\n\r\n /**\r\n * function to check that capture was properly made\r\n * @param {*} list\r\n */\r\n const checkResult = list => {\r\n const setItem = new Set(list);\r\n if (setItem.size === 1 && setItem.has(0)) {\r\n console.warn(\r\n 'The last capture failed, all recorded signal is zero',\r\n this.getAllRecordedSignals()\r\n );\r\n }\r\n if (setItem.size === 0) {\r\n console.warn('The last capture failed, no recorded signal');\r\n }\r\n };\r\n checkResult(result);\r\n return result;\r\n };\r\n\r\n /** \r\n * \r\n * \r\n Construct a calibration Node with the calibration parameters and given gain value\r\n * @param {*} gainValue\r\n * */\r\n #createCalibrationToneWithGainValue = gainValue => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n const taperGainNode = audioContext.createGain();\r\n const offsetGainNode = audioContext.createGain();\r\n const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;\r\n\r\n oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = this.#CALIBRATION_TONE_TYPE;\r\n gainNode.gain.value = gainValue;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(taperGainNode);\r\n const onsetCurve = this.createSCurveBuffer();\r\n taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);\r\n taperGainNode.connect(offsetGainNode);\r\n const offsetCurve = this.createSCurveBuffer(false);\r\n offsetGainNode.gain.setValueCurveAtTime(offsetCurve, (totalDuration-this.TAPER_SECS), this.TAPER_SECS);\r\n offsetGainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #createCalibrationNode = () => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n\r\n oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = this.#CALIBRATION_TONE_TYPE;\r\n gainNode.gain.value = 0.04;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n #playCalibrationAudioVolume = async () => {\r\n const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;\r\n\r\n this.calibrationNodes[0].start(0);\r\n this.calibrationNodes[0].stop(totalDuration);\r\n console.log(`Playing a buffer of ${this.#CALIBRATION_TONE_DURATION} seconds of audio`);\r\n console.log(`Waiting a total of ${totalDuration} seconds`);\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(totalDuration);\r\n };\r\n\r\n #sendToServerForProcessing = (lCalib = 104.92978421490648) => {\r\n console.log('Sending data to server');\r\n this.pyServerAPI\r\n .getVolumeCalibration({\r\n sampleRate: this.sourceSamplingRate,\r\n payload: this.#getTruncatedSignal(),\r\n lCalib: lCalib,\r\n })\r\n .then(res => {\r\n if (this.outDBSPL === null) {\r\n this.outDBSPL = res['outDbSPL'];\r\n this.outDBSPL1000 = res['outDbSPL1000'];\r\n this.THD = res['thd'];\r\n }\r\n })\r\n .catch(err => {\r\n console.warn(err);\r\n });\r\n };\r\n\r\n startCalibrationVolume = async (stream, gainValues, lCalib = 104.92978421490648) => {\r\n const trialIterations = gainValues.length;\r\n const thdValues = [];\r\n const inDBValues = [];\r\n let inDB = 0;\r\n const outDBSPLValues = [];\r\n const outDBSPL1000Values = [];\r\n\r\n // do one calibration that will be discarded\r\n const soundLevelToDiscard = -60;\r\n const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);\r\n this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});\r\n do {\r\n // eslint-disable-next-line no-await-in-loop\r\n await this.volumeCalibrationSteps(\r\n stream,\r\n this.#playCalibrationAudioVolume,\r\n this.#createCalibrationToneWithGainValue,\r\n this.#sendToServerForProcessing,\r\n gainToDiscard,\r\n lCalib //todo make this a class parameter\r\n );\r\n } while (this.outDBSPL === null);\r\n //reset the values\r\n this.outDBSPL = null;\r\n this.outDBSPL = null;\r\n this.outDBSPL1000 = null;\r\n this.THD = null;\r\n\r\n // run the calibration at different gain values provided by the user\r\n for (let i = 0; i < trialIterations; i++) {\r\n //convert gain to DB and add to inDB\r\n inDB = Math.log10(gainValues[i]) * 20;\r\n // precision to 1 decimal place\r\n inDB = Math.round(inDB * 10) / 10;\r\n inDBValues.push(inDB);\r\n this.emit('update', {message: `1000 Hz Calibration: Sound Level ${inDB} dB`});\r\n do {\r\n // eslint-disable-next-line no-await-in-loop\r\n await this.volumeCalibrationSteps(\r\n stream,\r\n this.#playCalibrationAudioVolume,\r\n this.#createCalibrationToneWithGainValue,\r\n this.#sendToServerForProcessing,\r\n gainValues[i],\r\n lCalib //todo make this a class parameter\r\n );\r\n } while (this.outDBSPL === null);\r\n outDBSPL1000Values.push(this.outDBSPL1000);\r\n thdValues.push(this.THD);\r\n outDBSPLValues.push(this.outDBSPL);\r\n\r\n this.outDBSPL = null;\r\n this.outDBSPL1000 = null;\r\n this.THD = null;\r\n }\r\n\r\n // get the volume calibration parameters from the server\r\n const parameters = await this.pyServerAPI\r\n .getVolumeCalibrationParameters({\r\n inDBValues: inDBValues,\r\n outDBSPLValues: outDBSPL1000Values,\r\n lCalib: lCalib,\r\n })\r\n .then(res => {\r\n return res;\r\n });\r\n const result = {\r\n parameters: parameters,\r\n inDBValues: inDBValues,\r\n outDBSPLValues: outDBSPLValues,\r\n outDBSPL1000Values: outDBSPL1000Values,\r\n thdValues: thdValues,\r\n };\r\n\r\n return result;\r\n };\r\n\r\n startCalibration = async (stream, gainValues, lCalib = 104.92978421490648) => {\r\n let volumeResults = await this.startCalibrationVolume(stream, gainValues, lCalib = 104.92978421490648);\r\n let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);\r\n console.log(volumeResults);\r\n console.log(impulseResponseResults);\r\n const total_results = {...volumeResults, ...impulseResponseResults};\r\n console.log('total');\r\n console.log(total_results);\r\n return total_results;\r\n }\r\n\r\n\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (Combination);\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/combination/combination.js?");
|
|
799
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../audioCalibrator */ \"./src/tasks/audioCalibrator.js\");\n/* harmony import */ var _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mlsGen/mlsGenInterface */ \"./src/tasks/combination/mlsGen/mlsGenInterface.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../utils */ \"./src/utils.js\");\n\r\n\r\n\r\n\r\n\r\n/**\r\n *\r\n */\r\nclass Combination extends _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n /**\r\n * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.\r\n *\r\n * @param {Object<boolean, number, number, number>} calibratorParams - paramter object\r\n * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures\r\n * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated\r\n * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform\r\n * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture\r\n */\r\n constructor({download = false, mlsOrder = 18, numCaptures = 3, numMLSPerCapture = 4, lowHz = 20, highHz = 10000}) {\r\n super(numCaptures, numMLSPerCapture);\r\n this.#mlsOrder = parseInt(mlsOrder, 10);\r\n this.#P = 2 ** mlsOrder - 1;\r\n this.#download = download;\r\n this.#mls = [];\r\n this.#lowHz = lowHz;\r\n this.#highHz = highHz;\r\n }\r\n\r\n /** @private */\r\n stepNum = 0;\r\n\r\n /** @private */\r\n totalSteps = 25;\r\n\r\n /** @private */\r\n #download;\r\n\r\n /** @private */\r\n #mlsGenInterface;\r\n\r\n /** @private */\r\n #mlsBufferView;\r\n\r\n /** @private */\r\n invertedImpulseResponse = null;\r\n\r\n /** @private */\r\n impulseResponses = [];\r\n\r\n /** @private */\r\n #mlsOrder;\r\n\r\n /** @private */\r\n #lowHz;\r\n\r\n /** @private */\r\n #highHz;\r\n\r\n /** @private */\r\n #mls;\r\n\r\n /** @private */\r\n #P;\r\n\r\n /** @private */\r\n #audioContext;\r\n\r\n /** @private */\r\n TAPER_SECS = 5;\r\n\r\n /** @private */\r\n offsetGainNode;\r\n\r\n /** @private */\r\n convolution;\r\n ////////////////////////volume\r\n /** @private */\r\n #CALIBRATION_TONE_FREQUENCY = 1000; // Hz\r\n\r\n /** @private */\r\n #CALIBRATION_TONE_TYPE = 'sine';\r\n\r\n /** @private */\r\n #CALIBRATION_TONE_DURATION = 5; // seconds\r\n\r\n /** @private */\r\n outDBSPL = null;\r\n THD = null;\r\n outDBSPL1000 = null;\r\n\r\n /** @private */\r\n TAPER_SECS = 0.010; // seconds\r\n\r\n /** @private */\r\n status_denominator = 8; \r\n\r\n /** @private */\r\n status_numerator = 0; \r\n\r\n /** @private */\r\n percent_complete = 0; \r\n\r\n /** @private */\r\n status = ``;\r\n\r\n /**@private */\r\n status_literal = `<div style=\"display: flex; justify-content: center;\"><div style=\"width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;\"><div style=\"width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;\"></div></div></div>`;\r\n\r\n /**generate string template that gets reevaluated as variable increases */\r\n generateTemplate = () => {\r\n if (this.percent_complete > 100){\r\n this.percent_complete = 100;\r\n }\r\n const template = `<div style=\"display: flex; justify-content: center;\"><div style=\"width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;\"><div style=\"width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;\"></div></div></div>`;\r\n return template;\r\n }\r\n\r\n /** increment numerator and percent for status bar */\r\n incrementStatusBar = () => {\r\n this.status_numerator += 1;\r\n this.percent_complete = (this.status_numerator/this.status_denominator)*100;\r\n }\r\n\r\n /** .\r\n * .\r\n * .\r\n * Sends all the computed impulse responses to the backend server for processing\r\n *\r\n * @returns sets the resulting inverted impulse response to the class property\r\n * @example\r\n */\r\n sendImpulseResponsesToServerForProcessing = async () => {\r\n const computedIRs = await Promise.all(this.impulseResponses);\r\n const filteredComputedIRs = computedIRs.filter(element => {\r\n return element != undefined;\r\n });\r\n const mls = this.#mls;\r\n const lowHz = this.#lowHz;\r\n const highHz = this.#highHz;\r\n this.stepNum += 1;\r\n console.log('send impulse responses to server: ' + this.stepNum);\r\n this.status = `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n return this.pyServerAPI\r\n .getInverseImpulseResponseWithRetry({\r\n payload: filteredComputedIRs.slice(0, this.numCaptures),\r\n mls,\r\n lowHz,\r\n highHz\r\n })\r\n .then(res => {\r\n console.log(res);\r\n this.stepNum += 1;\r\n console.log('got impulse response ' + this.stepNum);\r\n this.incrementStatusBar();\r\n this.status = `All Hz Calibration: done computing the IIR...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n this.invertedImpulseResponse = res[\"iir\"];\r\n this.convolution = res[\"convolution\"];\r\n })\r\n .catch(err => {\r\n // this.emit('InvertedImpulseResponse', {res: false});\r\n console.error(err);\r\n });\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing\r\n *\r\n * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed\r\n * @example\r\n */\r\n sendRecordingToServerForProcessing = signalCsv => {\r\n const allSignals = this.getAllRecordedSignals();\r\n const numSignals = allSignals.length;\r\n const mls = this.#mls;\r\n const payload =\r\n signalCsv && signalCsv.length > 0 ? (0,_utils__WEBPACK_IMPORTED_MODULE_2__.csvToArray)(signalCsv) : allSignals[numSignals - 1];\r\n console.log('sending rec');\r\n this.stepNum += 1;\r\n console.log('send rec ' + this.stepNum);\r\n this.status = `All Hz Calibration Step: computing the IR of the last recording...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n this.impulseResponses.push(\r\n this.pyServerAPI\r\n .getImpulseResponse({\r\n sampleRate: this.sourceSamplingRate || 96000,\r\n payload,\r\n mls,\r\n P: this.#P,\r\n })\r\n .then(res => {\r\n if (this.numSuccessfulCaptured < this.numCaptures) {\r\n this.numSuccessfulCaptured += 1;\r\n console.log(\"num succ capt: \" + this.numSuccessfulCaptured);\r\n this.stepNum += 1;\r\n console.log('got impulse response ' + this.stepNum);\r\n this.incrementStatusBar();\r\n this.status = `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {\r\n message: this.status,\r\n });\r\n return res;\r\n }\r\n \r\n })\r\n .catch(err => {\r\n console.error(err);\r\n })\r\n );\r\n };\r\n\r\n /**\r\n * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number\r\n * of MLS periods defined in the constructor.\r\n *\r\n * @example\r\n */\r\n #awaitDesiredMLSLength = async () => {\r\n // seconds per MLS = P / SR\r\n // await N * P / SR\r\n this.stepNum += 1;\r\n console.log('await desired length ' + this.stepNum);\r\n this.status = `All Hz Calibration: sampling the calibration signal...`.toString() + this.generateTemplate();\r\n this.emit('update', {\r\n message: this.status,\r\n });\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)((this.#P / this.sourceSamplingRate) * this.numMLSPerCapture);\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state\r\n *\r\n * @example\r\n */\r\n #awaitSignalOnset = async () => {\r\n this.stepNum += 1;\r\n console.log('await signal onset ' + this.stepNum);\r\n this.status = `All Hz Calibration: waiting for the signal to stabilize...`.toString() + this.generateTemplate();\r\n this.emit('update', {\r\n message: this.status,\r\n });\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(this.TAPER_SECS);\r\n };\r\n\r\n /**\r\n * Called immediately after a recording is captured. Used to process the resulting signal\r\n * whether by sending the result to a server or by computing a result locally.\r\n *\r\n * @example\r\n */\r\n #afterMLSRecord = () => {\r\n console.log('after record');\r\n this.sendRecordingToServerForProcessing();\r\n };\r\n\r\n #afterMLSwIIRRecord = () => {\r\n if (this.numSuccessfulCaptured < 1) {\r\n this.numSuccessfulCaptured += 1;\r\n this.stepNum += 1;\r\n this.incrementStatusBar();\r\n console.log('after mls w iir record for some reason add numSucc capt ' + this.stepNum);\r\n this.status = `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {\r\n message: this.status,\r\n });\r\n }\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Created an S Curver Buffer to taper the signal onset\r\n *\r\n * @param {*} length\r\n * @param {*} phase\r\n * @returns\r\n * @example\r\n */\r\n static createSCurveBuffer = (length, phase) => {\r\n const curve = new Float32Array(length);\r\n let i;\r\n for (i = 0; i < length; i += 1) {\r\n // scale the curve to be between 0-1\r\n curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\r\n }\r\n return curve;\r\n };\r\n\r\n static createInverseSCurveBuffer = (length, phase) => {\r\n const curve = new Float32Array(length);\r\n let i;\r\n let j = length - 1;\r\n for (i = 0; i < length; i += 1) {\r\n // scale the curve to be between 0-1\r\n curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;\r\n j -= 1;\r\n }\r\n return curve;\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @param CALIBRATION_TONE_FREQUENCY\r\n * @private\r\n * @example\r\n */\r\n #createPureTonenNode = CALIBRATION_TONE_FREQUENCY => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n\r\n oscilator.frequency.value = CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = 'sine';\r\n gainNode.gain.value = 0.04;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @param dataBuffer\r\n * @private\r\n * @example\r\n */\r\n #createCalibrationNodeFromBuffer = dataBuffer => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const buffer = audioContext.createBuffer(\r\n 1, // number of channels\r\n dataBuffer.length,\r\n audioContext.sampleRate // sample rate\r\n );\r\n\r\n const data = buffer.getChannelData(0); // get data\r\n // fill the buffer with our data\r\n try {\r\n for (let i = 0; i < dataBuffer.length; i += 1) {\r\n data[i] = dataBuffer[i]*.1;\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n console.log(\"mls second, same?\");\r\n console.log(data);\r\n const onsetGainNode = audioContext.createGain();\r\n this.offsetGainNode = audioContext.createGain();\r\n const source = audioContext.createBufferSource();\r\n\r\n source.buffer = buffer;\r\n source.loop = true;\r\n source.connect(onsetGainNode);\r\n onsetGainNode.connect(this.offsetGainNode);\r\n this.offsetGainNode.connect(audioContext.destination);\r\n\r\n const onsetCurve = Combination.createSCurveBuffer(this.sourceSamplingRate, Math.PI / 2);\r\n onsetGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);\r\n this.addCalibrationNode(source);\r\n };\r\n\r\n /**\r\n * Given a data buffer, creates the required calibration node\r\n *\r\n * @param {*} dataBufferArray\r\n * @example\r\n */\r\n #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {\r\n if (dataBufferArray.length === 1) {\r\n console.log('data buffer aray');\r\n console.log(dataBufferArray);\r\n this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);\r\n } else {\r\n throw new Error('The length of the data buffer array must be 1');\r\n }\r\n\r\n \r\n };\r\n\r\n /**\r\n * function to put MLS filtered IIR data obtained from\r\n * python server into our audio buffer to be played aloud\r\n */\r\n #putInPythonConv = () => {\r\n const audioCtx = this.makeNewSourceAudioContextConvolved();\r\n const buffer = audioCtx.createBuffer(\r\n 1, // number of channels\r\n this.convolution.length,\r\n audioCtx.sampleRate // sample rate\r\n );\r\n\r\n const data = buffer.getChannelData(0); // get data\r\n // fill the buffer with our data\r\n try {\r\n for (let i = 0; i < this.convolution.length; i += 1) {\r\n data[i] = this.convolution[i];\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n const source = audioCtx.createBufferSource();\r\n\r\n source.buffer = buffer;\r\n source.loop = true;\r\n source.connect(audioCtx.destination);\r\n\r\n this.addCalibrationNodeConvolved(source);\r\n }\r\n \r\n /**\r\n * Creates an audio context and plays it for a few seconds.\r\n *\r\n * @private\r\n * @returns - Resolves when the audio is done playing.\r\n * @example\r\n */\r\n #playCalibrationAudio = () => {\r\n this.calibrationNodes[0].start(0);\r\n this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);\r\n this.stepNum += 1;\r\n console.log('play calibration audio ' + this.stepNum);\r\n this.status = `All Hz Calibration: playing the calibration tone...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n }; \r\n\r\n\r\n #playCalibrationAudioConvolved = () => {\r\n this.calibrationNodesConvolved[0].start(0);\r\n this.stepNum += 1;\r\n console.log('play convolved audio ' + this.stepNum);\r\n this.status = `All Hz Calibration: playing the convolved calibration tone...`.toString() + this.generateTemplate().toString();\r\n this.emit('update',{message: this.status})\r\n }\r\n\r\n /** .\r\n * .\r\n * .\r\n * Stops the audio with tapered offset\r\n *\r\n * @example\r\n */\r\n #stopCalibrationAudio = () => {\r\n this.offsetGainNode.gain.setValueAtTime(\r\n this.offsetGainNode.gain.value,\r\n this.sourceAudioContext.currentTime\r\n );\r\n\r\n this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContext.currentTime, 0.5);\r\n this.calibrationNodes[0].stop(0);\r\n this.sourceAudioContext.close();\r\n this.stepNum += 1;\r\n console.log('stop calibratoin audio ' + this.stepNum);\r\n this.status = `All Hz Calibration: stopping the calibration tone...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n };\r\n\r\n #stopCalibrationAudioConvolved = () => {\r\n this.offsetGainNode.gain.setValueAtTime(\r\n this.offsetGainNode.gain.value,\r\n this.sourceAudioContextConvolved.currentTime\r\n );\r\n\r\n this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContextConvolved.currentTime, 0.5);\r\n //this.calibrationNodesConvolved[0].stop(0);\r\n console.log(\"right before closing volved audio context\");\r\n this.sourceAudioContextConvolved.close();\r\n this.stepNum += 1;\r\n console.log('stop convolved calibration audio ' + this.stepNum);\r\n this.status = `All Hz Calibration: stopping the convolved calibration tone...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n\r\n }\r\n\r\n playMLSwithIIR = async (stream, iir) => {\r\n console.log('play mls with iir');\r\n this.invertedImpulseResponse = iir;\r\n // initialize the MLSGenInterface object with it's factory method\r\n \r\n await _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__[\"default\"].factory(\r\n this.#mlsOrder,\r\n this.sinkSamplingRate,\r\n this.sourceSamplingRate\r\n ).then(mlsGenInterface => {\r\n this.#mlsGenInterface = mlsGenInterface;\r\n this.#mlsBufferView = this.#mlsGenInterface.getMLS();\r\n });\r\n\r\n console.log('after mls factory'); //works up to here.\r\n console.log(this.#mls);\r\n // after intializating, start the calibration steps with garbage collection\r\n await this.#mlsGenInterface.withGarbageCollection([\r\n () =>\r\n this.calibrationSteps(\r\n stream,\r\n this.#playCalibrationAudioConvolved, // play audio func (required)\r\n this.#putInPythonConv, // before play func\r\n this.#awaitSignalOnset, // before record\r\n () => this.numSuccessfulCaptured < 1,\r\n this.#awaitDesiredMLSLength, // during record\r\n this.#afterMLSwIIRRecord, // after record\r\n 'filtered'\r\n ),\r\n ]);\r\n };\r\n\r\n /**\r\n * Public method to start the calibration process. Objects intialized from webassembly allocate new memory\r\n * and must be manually freed. This function is responsible for intializing the MlsGenInterface,\r\n * and wrapping the calibration steps with a garbage collection safe gaurd.\r\n *\r\n * @public\r\n * @param stream - The stream of audio from the Listener.\r\n * @example\r\n */\r\n startCalibrationImpulseResponse = async stream => {\r\n // initialize the MLSGenInterface object with it's factory method\r\n await _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__[\"default\"].factory(\r\n this.#mlsOrder,\r\n this.sinkSamplingRate,\r\n this.sourceSamplingRate\r\n ).then(mlsGenInterface => {\r\n this.#mlsGenInterface = mlsGenInterface;\r\n this.#mlsBufferView = this.#mlsGenInterface.getMLS();\r\n });\r\n\r\n // after intializating, start the calibration steps with garbage collection\r\n await this.#mlsGenInterface.withGarbageCollection([\r\n () =>\r\n this.calibrationSteps(\r\n stream,\r\n this.#playCalibrationAudio, // play audio func (required)\r\n this.#setCalibrationNodesFromBuffer, // before play func\r\n this.#awaitSignalOnset, // before record\r\n () => this.numSuccessfulCaptured < this.numCaptures, // loop while true\r\n this.#awaitDesiredMLSLength, // during record\r\n this.#afterMLSRecord, // after record\r\n 'unfiltered'\r\n ),\r\n ]);\r\n\r\n this.#stopCalibrationAudio();\r\n\r\n // at this stage we've captured all the required signals,\r\n // and have received IRs for each one\r\n // so let's send all the IRs to the server to be converted to a single IIR\r\n await this.sendImpulseResponsesToServerForProcessing();\r\n\r\n this.numSuccessfulCaptured = 0;\r\n // debugging function, use to test the result of the IIR\r\n await this.playMLSwithIIR(stream, this.invertedImpulseResponse);\r\n this.#stopCalibrationAudioConvolved();\r\n\r\n let recs = this.getAllRecordedSignals();\r\n let conv_recs = this.getAllFilteredRecordedSignals();\r\n let unconv_rec = recs[0];\r\n let conv_rec = conv_recs[0];\r\n\r\n this.status = `All Hz Calibration: computing PSD graphs...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n\r\n let results = await this.pyServerAPI\r\n .getPSDWithRetry({\r\n unconv_rec,\r\n conv_rec,\r\n })\r\n .then(res => {\r\n this.incrementStatusBar();\r\n this.status = `All Hz Calibration: done computing the PSD graphs...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n return res;\r\n })\r\n .catch(err => {\r\n console.error(err);\r\n })\r\n\r\n let iir_and_plots = {\r\n \"iir\": this.invertedImpulseResponse,\r\n \"x_unconv\": results[\"x_unconv\"],\r\n \"y_unconv\": results[\"y_unconv\"],\r\n \"x_conv\": results[\"x_conv\"],\r\n \"y_conv\": results[\"y_conv\"]\r\n }\r\n if (this.#download) {\r\n this.downloadSingleUnfilteredRecording();\r\n this.downloadSingleFilteredRecording();\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.#mls,\"MLS.csv\");\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.convolution,'python_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.invertedImpulseResponse,'IIR.csv');\r\n const computedIRagain = await Promise.all(this.impulseResponses)\r\n .then(res => {\r\n for (let i = 0; i < res.length; i++){\r\n if (res[i] != undefined){\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(res[i], `IR_${i}`);\r\n }\r\n }\r\n })\r\n }\r\n\r\n return iir_and_plots;\r\n };\r\n\r\n //////////////////////volume\r\n\r\n handleIncomingData = data => {\r\n console.log('Received data: ', data);\r\n if (data.type === 'soundGainDBSPL') {\r\n this.soundGainDBSPL = data.value;\r\n } else {\r\n throw new Error(`Unknown data type: ${data.type}`);\r\n }\r\n };\r\n\r\n createSCurveBuffer = (onSetBool=true) => {\r\n\r\n const curve = new Float32Array(this.TAPER_SECS*this.sourceSamplingRate+1);\r\n const frequency = 1 / (4 * this.TAPER_SECS);\r\n let j = 0;\r\n for (let i = 0; i < this.TAPER_SECS*this.sourceSamplingRate+1; i += 1) {\r\n const phase = 2 * Math.PI * frequency * j;\r\n const onsetTaper = Math.pow(Math.sin(phase) , 2);\r\n const offsetTaper = Math.pow(Math.cos(phase) , 2);\r\n curve[i] = onSetBool? onsetTaper : offsetTaper;\r\n j += (1 / this.sourceSamplingRate);\r\n } \r\n return curve;\r\n };\r\n\r\n #getTruncatedSignal = (left = 3.5, right = 4.5) => {\r\n const start = Math.floor(left * this.sourceSamplingRate);\r\n const end = Math.floor(right * this.sourceSamplingRate);\r\n const result = Array.from(this.getLastRecordedSignal().slice(start, end));\r\n\r\n /**\r\n * function to check that capture was properly made\r\n * @param {*} list\r\n */\r\n const checkResult = list => {\r\n const setItem = new Set(list);\r\n if (setItem.size === 1 && setItem.has(0)) {\r\n console.warn(\r\n 'The last capture failed, all recorded signal is zero',\r\n this.getAllRecordedSignals()\r\n );\r\n }\r\n if (setItem.size === 0) {\r\n console.warn('The last capture failed, no recorded signal');\r\n }\r\n };\r\n checkResult(result);\r\n return result;\r\n };\r\n\r\n /** \r\n * \r\n * \r\n Construct a calibration Node with the calibration parameters and given gain value\r\n * @param {*} gainValue\r\n * */\r\n #createCalibrationToneWithGainValue = gainValue => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n const taperGainNode = audioContext.createGain();\r\n const offsetGainNode = audioContext.createGain();\r\n const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;\r\n\r\n oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = this.#CALIBRATION_TONE_TYPE;\r\n gainNode.gain.value = gainValue;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(taperGainNode);\r\n const onsetCurve = this.createSCurveBuffer();\r\n taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);\r\n taperGainNode.connect(offsetGainNode);\r\n const offsetCurve = this.createSCurveBuffer(false);\r\n offsetGainNode.gain.setValueCurveAtTime(offsetCurve, (totalDuration-this.TAPER_SECS), this.TAPER_SECS);\r\n offsetGainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #createCalibrationNode = () => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n\r\n oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = this.#CALIBRATION_TONE_TYPE;\r\n gainNode.gain.value = 0.04;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n #playCalibrationAudioVolume = async () => {\r\n const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;\r\n\r\n this.calibrationNodes[0].start(0);\r\n this.calibrationNodes[0].stop(totalDuration);\r\n console.log(`Playing a buffer of ${this.#CALIBRATION_TONE_DURATION} seconds of audio`);\r\n console.log(`Waiting a total of ${totalDuration} seconds`);\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(totalDuration);\r\n };\r\n\r\n #sendToServerForProcessing = (lCalib = 104.92978421490648) => {\r\n console.log('Sending data to server');\r\n this.pyServerAPI\r\n .getVolumeCalibration({\r\n sampleRate: this.sourceSamplingRate,\r\n payload: this.#getTruncatedSignal(),\r\n lCalib: lCalib,\r\n })\r\n .then(res => {\r\n if (this.outDBSPL === null) {\r\n this.incrementStatusBar();\r\n this.outDBSPL = res['outDbSPL'];\r\n this.outDBSPL1000 = res['outDbSPL1000'];\r\n this.THD = res['thd'];\r\n }\r\n })\r\n .catch(err => {\r\n console.warn(err);\r\n });\r\n };\r\n\r\n startCalibrationVolume = async (stream, gainValues, lCalib = 104.92978421490648) => {\r\n const trialIterations = gainValues.length;\r\n this.status_denominator += trialIterations;\r\n const thdValues = [];\r\n const inDBValues = [];\r\n let inDB = 0;\r\n const outDBSPLValues = [];\r\n const outDBSPL1000Values = []; \r\n\r\n // do one calibration that will be discarded\r\n const soundLevelToDiscard = -60;\r\n const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);\r\n this.status = `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() + this.generateTemplate().toString();\r\n //this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});\r\n this.emit('update', {message: this.status});\r\n \r\n do {\r\n // eslint-disable-next-line no-await-in-loop\r\n await this.volumeCalibrationSteps(\r\n stream,\r\n this.#playCalibrationAudioVolume,\r\n this.#createCalibrationToneWithGainValue,\r\n this.#sendToServerForProcessing,\r\n gainToDiscard,\r\n lCalib //todo make this a class parameter\r\n );\r\n } while (this.outDBSPL === null);\r\n //reset the values\r\n //this.incrementStatusBar();\r\n\r\n this.outDBSPL = null;\r\n this.outDBSPL = null;\r\n this.outDBSPL1000 = null;\r\n this.THD = null;\r\n\r\n // run the calibration at different gain values provided by the user\r\n for (let i = 0; i < trialIterations; i++) {\r\n //convert gain to DB and add to inDB\r\n inDB = Math.log10(gainValues[i]) * 20;\r\n // precision to 1 decimal place\r\n inDB = Math.round(inDB * 10) / 10;\r\n inDBValues.push(inDB);\r\n console.log('next update');\r\n this.status = `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n do {\r\n // eslint-disable-next-line no-await-in-loop\r\n await this.volumeCalibrationSteps(\r\n stream,\r\n this.#playCalibrationAudioVolume,\r\n this.#createCalibrationToneWithGainValue,\r\n this.#sendToServerForProcessing,\r\n gainValues[i],\r\n lCalib //todo make this a class parameter\r\n );\r\n } while (this.outDBSPL === null);\r\n outDBSPL1000Values.push(this.outDBSPL1000);\r\n thdValues.push(this.THD);\r\n outDBSPLValues.push(this.outDBSPL);\r\n\r\n this.outDBSPL = null;\r\n this.outDBSPL1000 = null;\r\n this.THD = null;\r\n }\r\n\r\n // get the volume calibration parameters from the server\r\n const parameters = await this.pyServerAPI\r\n .getVolumeCalibrationParameters({\r\n inDBValues: inDBValues,\r\n outDBSPLValues: outDBSPL1000Values,\r\n lCalib: lCalib,\r\n })\r\n .then(res => {\r\n this.incrementStatusBar();\r\n return res;\r\n });\r\n const result = {\r\n parameters: parameters,\r\n inDBValues: inDBValues,\r\n outDBSPLValues: outDBSPLValues,\r\n outDBSPL1000Values: outDBSPL1000Values,\r\n thdValues: thdValues,\r\n };\r\n \r\n\r\n return result;\r\n };\r\n\r\n startCalibration = async (stream, gainValues, lCalib = 104.92978421490648) => {\r\n let volumeResults = await this.startCalibrationVolume(stream, gainValues, lCalib = 104.92978421490648);\r\n let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);\r\n console.log(volumeResults);\r\n console.log(impulseResponseResults);\r\n const total_results = {...volumeResults, ...impulseResponseResults};\r\n console.log('total');\r\n console.log(total_results);\r\n return total_results;\r\n }\r\n\r\n\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (Combination);\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/combination/combination.js?");
|
|
800
800
|
|
|
801
801
|
/***/ }),
|
|
802
802
|
|
|
@@ -818,7 +818,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* eslint-disable prefer-dest
|
|
|
818
818
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
819
819
|
|
|
820
820
|
"use strict";
|
|
821
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../audioCalibrator */ \"./src/tasks/audioCalibrator.js\");\n/* harmony import */ var _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mlsGen/mlsGenInterface */ \"./src/tasks/impulse-response/mlsGen/mlsGenInterface.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../utils */ \"./src/utils.js\");\n\r\n\r\n\r\n\r\n\r\n/**\r\n *\r\n */\r\nclass ImpulseResponse extends _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n /**\r\n * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.\r\n *\r\n * @param {Object<boolean, number, number, number>} calibratorParams - paramter object\r\n * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures\r\n * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated\r\n * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform\r\n * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture\r\n */\r\n constructor({download = false, mlsOrder = 18, numCaptures = 3, numMLSPerCapture = 4, lowHz = 20, highHz = 10000}) {\r\n super(numCaptures, numMLSPerCapture);\r\n this.#mlsOrder = parseInt(mlsOrder, 10);\r\n this.#P = 2 ** mlsOrder - 1;\r\n this.#download = download;\r\n this.#mls = [];\r\n this.#lowHz = lowHz;\r\n this.#highHz = highHz;\r\n }\r\n\r\n /** @private */\r\n stepNum = 0;\r\n\r\n /** @private */\r\n totalSteps = 25;\r\n\r\n /** @private */\r\n #download;\r\n\r\n /** @private */\r\n #mlsGenInterface;\r\n\r\n /** @private */\r\n #mlsBufferView;\r\n\r\n /** @private */\r\n invertedImpulseResponse = null;\r\n\r\n /** @private */\r\n impulseResponses = [];\r\n\r\n /** @private */\r\n #mlsOrder;\r\n\r\n /** @private */\r\n #lowHz;\r\n\r\n /** @private */\r\n #highHz;\r\n\r\n /** @private */\r\n #mls;\r\n\r\n /** @private */\r\n #P;\r\n\r\n /** @private */\r\n #audioContext;\r\n\r\n /** @private */\r\n TAPER_SECS = 5;\r\n\r\n /** @private */\r\n offsetGainNode;\r\n\r\n /** @private */\r\n convolution;\r\n\r\n /** .\r\n * .\r\n * .\r\n * Sends all the computed impulse responses to the backend server for processing\r\n *\r\n * @returns sets the resulting inverted impulse response to the class property\r\n * @example\r\n */\r\n sendImpulseResponsesToServerForProcessing = async () => {\r\n const computedIRs = await Promise.all(this.impulseResponses);\r\n const filteredComputedIRs = computedIRs.filter(element => {\r\n return element != undefined;\r\n });\r\n const mls = this.#mls;\r\n const lowHz = this.#lowHz;\r\n const highHz = this.#highHz;\r\n this.stepNum += 1;\r\n this.emit('update', {message: `Step ${this.stepNum}/${this.totalSteps}: computing the IIR...`});\r\n return this.pyServerAPI\r\n .getInverseImpulseResponse({\r\n payload: filteredComputedIRs.slice(0, this.numCaptures),\r\n mls,\r\n lowHz,\r\n highHz\r\n })\r\n .then(res => {\r\n console.log(res);\r\n this.stepNum += 1;\r\n this.emit('update', {message: `Step ${this.stepNum}/${this.totalSteps}: done computing the IIR...`});\r\n this.invertedImpulseResponse = res[\"iir\"];\r\n this.convolution = res[\"convolution\"];\r\n })\r\n .catch(err => {\r\n // this.emit('InvertedImpulseResponse', {res: false});\r\n console.error(err);\r\n });\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing\r\n *\r\n * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed\r\n * @example\r\n */\r\n sendRecordingToServerForProcessing = signalCsv => {\r\n const allSignals = this.getAllRecordedSignals();\r\n const numSignals = allSignals.length;\r\n const mls = this.#mls;\r\n const payload =\r\n signalCsv && signalCsv.length > 0 ? (0,_utils__WEBPACK_IMPORTED_MODULE_2__.csvToArray)(signalCsv) : allSignals[numSignals - 1];\r\n console.log('sending rec');\r\n this.stepNum += 1;\r\n this.emit('update', {message: `Step: ${this.stepNum}/${this.totalSteps}: computing the IR of the last recording...`});\r\n this.impulseResponses.push(\r\n this.pyServerAPI\r\n .getImpulseResponse({\r\n sampleRate: this.sourceSamplingRate || 96000,\r\n payload,\r\n mls,\r\n P: this.#P,\r\n })\r\n .then(res => {\r\n if (this.numSuccessfulCaptured < this.numCaptures) {\r\n this.numSuccessfulCaptured += 1;\r\n console.log(\"num succ capt: \" + this.numSuccessfulCaptured);\r\n this.stepNum += 1;\r\n this.emit('update', {\r\n message: `Step: ${this.stepNum}/${this.totalSteps}: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`,\r\n });\r\n }\r\n return res;\r\n })\r\n .catch(err => {\r\n console.error(err);\r\n })\r\n );\r\n };\r\n\r\n /**\r\n * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number\r\n * of MLS periods defined in the constructor.\r\n *\r\n * @example\r\n */\r\n #awaitDesiredMLSLength = async () => {\r\n // seconds per MLS = P / SR\r\n // await N * P / SR\r\n this.stepNum += 1;\r\n this.emit('update', {\r\n message: `Step ${this.stepNum}/${this.totalSteps}: sampling the calibration signal...`,\r\n });\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)((this.#P / this.sourceSamplingRate) * this.numMLSPerCapture);\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state\r\n *\r\n * @example\r\n */\r\n #awaitSignalOnset = async () => {\r\n this.stepNum += 1;\r\n this.emit('update', {\r\n message: `Step ${this.stepNum}/${this.totalSteps}: waiting for the signal to stabilize...`,\r\n });\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(this.TAPER_SECS);\r\n };\r\n\r\n /**\r\n * Called immediately after a recording is captured. Used to process the resulting signal\r\n * whether by sending the result to a server or by computing a result locally.\r\n *\r\n * @example\r\n */\r\n #afterMLSRecord = () => {\r\n console.log('after record');\r\n this.sendRecordingToServerForProcessing();\r\n };\r\n\r\n #afterMLSwIIRRecord = () => {\r\n if (this.numSuccessfulCaptured < this.numCaptures) {\r\n this.numSuccessfulCaptured += 1;\r\n this.stepNum += 1;\r\n this.emit('update', {\r\n message: `Step ${this.stepNum}/${this.totalSteps}: ${this.numSuccessfulCaptured} recordings of convolved MLS captured`,\r\n });\r\n }\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Created an S Curver Buffer to taper the signal onset\r\n *\r\n * @param {*} length\r\n * @param {*} phase\r\n * @returns\r\n * @example\r\n */\r\n static createSCurveBuffer = (length, phase) => {\r\n const curve = new Float32Array(length);\r\n let i;\r\n for (i = 0; i < length; i += 1) {\r\n // scale the curve to be between 0-1\r\n curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\r\n }\r\n return curve;\r\n };\r\n\r\n static createInverseSCurveBuffer = (length, phase) => {\r\n const curve = new Float32Array(length);\r\n let i;\r\n let j = length - 1;\r\n for (i = 0; i < length; i += 1) {\r\n // scale the curve to be between 0-1\r\n curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;\r\n j -= 1;\r\n }\r\n return curve;\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @param CALIBRATION_TONE_FREQUENCY\r\n * @private\r\n * @example\r\n */\r\n #createPureTonenNode = CALIBRATION_TONE_FREQUENCY => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n\r\n oscilator.frequency.value = CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = 'sine';\r\n gainNode.gain.value = 0.04;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @param dataBuffer\r\n * @private\r\n * @example\r\n */\r\n #createCalibrationNodeFromBuffer = dataBuffer => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const buffer = audioContext.createBuffer(\r\n 1, // number of channels\r\n dataBuffer.length,\r\n audioContext.sampleRate // sample rate\r\n );\r\n\r\n const data = buffer.getChannelData(0); // get data\r\n // fill the buffer with our data\r\n try {\r\n for (let i = 0; i < dataBuffer.length; i += 1) {\r\n data[i] = dataBuffer[i]*.1;\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n console.log(\"mls second, same?\");\r\n console.log(data);\r\n const onsetGainNode = audioContext.createGain();\r\n this.offsetGainNode = audioContext.createGain();\r\n const source = audioContext.createBufferSource();\r\n\r\n source.buffer = buffer;\r\n source.loop = true;\r\n source.connect(onsetGainNode);\r\n onsetGainNode.connect(this.offsetGainNode);\r\n this.offsetGainNode.connect(audioContext.destination);\r\n\r\n const onsetCurve = ImpulseResponse.createSCurveBuffer(this.sourceSamplingRate, Math.PI / 2);\r\n onsetGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);\r\n this.addCalibrationNode(source);\r\n };\r\n\r\n /**\r\n * Given a data buffer, creates the required calibration node\r\n *\r\n * @param {*} dataBufferArray\r\n * @example\r\n */\r\n #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {\r\n if (dataBufferArray.length === 1) {\r\n console.log('data buffer aray');\r\n console.log(dataBufferArray);\r\n this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);\r\n } else {\r\n throw new Error('The length of the data buffer array must be 1');\r\n }\r\n\r\n \r\n };\r\n\r\n /**\r\n * function to put MLS filtered IIR data obtained from\r\n * python server into our audio buffer to be played aloud\r\n */\r\n #putInPythonConv = () => {\r\n const audioCtx = this.makeNewSourceAudioContextConvolved();\r\n const buffer = audioCtx.createBuffer(\r\n 1, // number of channels\r\n this.convolution.length,\r\n audioCtx.sampleRate // sample rate\r\n );\r\n\r\n const data = buffer.getChannelData(0); // get data\r\n // fill the buffer with our data\r\n try {\r\n for (let i = 0; i < this.convolution.length; i += 1) {\r\n data[i] = this.convolution[i];\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n const source = audioCtx.createBufferSource();\r\n\r\n source.buffer = buffer;\r\n source.loop = true;\r\n source.connect(audioCtx.destination);\r\n\r\n this.addCalibrationNodeConvolved(source);\r\n }\r\n \r\n /**\r\n * Creates an audio context and plays it for a few seconds.\r\n *\r\n * @private\r\n * @returns - Resolves when the audio is done playing.\r\n * @example\r\n */\r\n #playCalibrationAudio = () => {\r\n this.calibrationNodes[0].start(0);\r\n this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);\r\n this.stepNum += 1;\r\n this.emit('update', {message: `Step: ${this.stepNum}/${this.totalSteps}: playing the calibration tone...`});\r\n }; \r\n\r\n\r\n #playCalibrationAudioConvolved = () => {\r\n this.calibrationNodesConvolved[0].start(0);\r\n this.stepNum += 1;\r\n this.emit('update',{message: `Step: ${this.stepNum}/${this.totalSteps}: playing the convolved calibration tone...`})\r\n }\r\n\r\n /** .\r\n * .\r\n * .\r\n * Stops the audio with tapered offset\r\n *\r\n * @example\r\n */\r\n #stopCalibrationAudio = () => {\r\n this.offsetGainNode.gain.setValueAtTime(\r\n this.offsetGainNode.gain.value,\r\n this.sourceAudioContext.currentTime\r\n );\r\n\r\n this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContext.currentTime, 0.5);\r\n this.calibrationNodes[0].stop(0);\r\n this.sourceAudioContext.close();\r\n this.stepNum += 1;\r\n this.emit('update', {message: `Step ${this.stepNum}/${this.totalSteps}: stopping the calibration tone...`});\r\n };\r\n\r\n #stopCalibrationAudioConvolved = () => {\r\n this.offsetGainNode.gain.setValueAtTime(\r\n this.offsetGainNode.gain.value,\r\n this.sourceAudioContextConvolved.currentTime\r\n );\r\n\r\n this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContextConvolved.currentTime, 0.5);\r\n //this.calibrationNodesConvolved[0].stop(0);\r\n console.log(\"right before closing volved audio context\");\r\n this.sourceAudioContextConvolved.close();\r\n this.stepNum += 1;\r\n this.emit('update', {message: `Step ${this.stepNum}/${this.totalSteps}: stopping the convolved calibration tone...`});\r\n\r\n }\r\n\r\n playMLSwithIIR = async (stream, iir) => {\r\n console.log('play mls with iir');\r\n this.invertedImpulseResponse = iir;\r\n // initialize the MLSGenInterface object with it's factory method\r\n \r\n await _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__[\"default\"].factory(\r\n this.#mlsOrder,\r\n this.sinkSamplingRate,\r\n this.sourceSamplingRate\r\n ).then(mlsGenInterface => {\r\n this.#mlsGenInterface = mlsGenInterface;\r\n this.#mlsBufferView = this.#mlsGenInterface.getMLS();\r\n });\r\n\r\n console.log('after mls factory'); //works up to here.\r\n console.log(this.#mls);\r\n // after intializating, start the calibration steps with garbage collection\r\n await this.#mlsGenInterface.withGarbageCollection([\r\n () =>\r\n this.calibrationSteps(\r\n stream,\r\n this.#playCalibrationAudioConvolved, // play audio func (required)\r\n this.#putInPythonConv, // before play func\r\n this.#awaitSignalOnset, // before record\r\n () => this.numSuccessfulCaptured < this.numCaptures,\r\n this.#awaitDesiredMLSLength, // during record\r\n this.#afterMLSwIIRRecord, // after record\r\n 'filtered'\r\n ),\r\n ]);\r\n };\r\n\r\n /**\r\n * Public method to start the calibration process. Objects intialized from webassembly allocate new memory\r\n * and must be manually freed. This function is responsible for intializing the MlsGenInterface,\r\n * and wrapping the calibration steps with a garbage collection safe gaurd.\r\n *\r\n * @public\r\n * @param stream - The stream of audio from the Listener.\r\n * @example\r\n */\r\n startCalibration = async stream => {\r\n // initialize the MLSGenInterface object with it's factory method\r\n await _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__[\"default\"].factory(\r\n this.#mlsOrder,\r\n this.sinkSamplingRate,\r\n this.sourceSamplingRate\r\n ).then(mlsGenInterface => {\r\n this.#mlsGenInterface = mlsGenInterface;\r\n this.#mlsBufferView = this.#mlsGenInterface.getMLS();\r\n });\r\n\r\n // after intializating, start the calibration steps with garbage collection\r\n await this.#mlsGenInterface.withGarbageCollection([\r\n () =>\r\n this.calibrationSteps(\r\n stream,\r\n this.#playCalibrationAudio, // play audio func (required)\r\n this.#setCalibrationNodesFromBuffer, // before play func\r\n this.#awaitSignalOnset, // before record\r\n () => this.numSuccessfulCaptured < this.numCaptures, // loop while true\r\n this.#awaitDesiredMLSLength, // during record\r\n this.#afterMLSRecord, // after record\r\n 'unfiltered'\r\n ),\r\n ]);\r\n\r\n this.#stopCalibrationAudio();\r\n\r\n // at this stage we've captured all the required signals,\r\n // and have received IRs for each one\r\n // so let's send all the IRs to the server to be converted to a single IIR\r\n await this.sendImpulseResponsesToServerForProcessing();\r\n\r\n this.numSuccessfulCaptured = 0;\r\n // debugging function, use to test the result of the IIR\r\n await this.playMLSwithIIR(stream, this.invertedImpulseResponse);\r\n this.#stopCalibrationAudioConvolved();\r\n\r\n let recs = this.getAllRecordedSignals();\r\n let conv_recs = this.getAllFilteredRecordedSignals();\r\n let unconv_rec = recs[0];\r\n let conv_rec = conv_recs[0];\r\n\r\n let results = await this.pyServerAPI\r\n .getPSD({\r\n unconv_rec,\r\n conv_rec,\r\n })\r\n .then(res => {\r\n return res;\r\n })\r\n .catch(err => {\r\n console.error(err);\r\n })\r\n\r\n let iir_and_plots = {\r\n \"iir\": this.invertedImpulseResponse,\r\n \"x_unconv\": results[\"x_unconv\"],\r\n \"y_unconv\": results[\"y_unconv\"],\r\n \"x_conv\": results[\"x_conv\"],\r\n \"y_conv\": results[\"y_conv\"]\r\n }\r\n if (this.#download) {\r\n this.downloadSingleUnfilteredRecording();\r\n this.downloadSingleFilteredRecording();\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.#mls,\"MLS.csv\");\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.convolution,'python_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.invertedImpulseResponse,'IIR.csv');\r\n const computedIRagain = await Promise.all(this.impulseResponses)\r\n .then(res => {\r\n for (let i = 0; i < res.length; i++){\r\n if (res[i] != undefined){\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(res[i], `IR_${i}`);\r\n }\r\n }\r\n })\r\n }\r\n\r\n return iir_and_plots;\r\n };\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (ImpulseResponse);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/impulse-response/impulseResponse.js?");
|
|
821
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../audioCalibrator */ \"./src/tasks/audioCalibrator.js\");\n/* harmony import */ var _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mlsGen/mlsGenInterface */ \"./src/tasks/impulse-response/mlsGen/mlsGenInterface.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../utils */ \"./src/utils.js\");\n\r\n\r\n\r\n\r\n\r\n/**\r\n *\r\n */\r\nclass ImpulseResponse extends _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n /**\r\n * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.\r\n *\r\n * @param {Object<boolean, number, number, number>} calibratorParams - paramter object\r\n * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures\r\n * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated\r\n * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform\r\n * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture\r\n */\r\n constructor({download = false, mlsOrder = 18, numCaptures = 3, numMLSPerCapture = 4, lowHz = 20, highHz = 10000}) {\r\n super(numCaptures, numMLSPerCapture);\r\n this.#mlsOrder = parseInt(mlsOrder, 10);\r\n this.#P = 2 ** mlsOrder - 1;\r\n this.#download = download;\r\n this.#mls = [];\r\n this.#lowHz = lowHz;\r\n this.#highHz = highHz;\r\n }\r\n\r\n /** @private */\r\n stepNum = 0;\r\n\r\n /** @private */\r\n totalSteps = 25;\r\n\r\n /** @private */\r\n #download;\r\n\r\n /** @private */\r\n #mlsGenInterface;\r\n\r\n /** @private */\r\n #mlsBufferView;\r\n\r\n /** @private */\r\n invertedImpulseResponse = null;\r\n\r\n /** @private */\r\n impulseResponses = [];\r\n\r\n /** @private */\r\n #mlsOrder;\r\n\r\n /** @private */\r\n #lowHz;\r\n\r\n /** @private */\r\n #highHz;\r\n\r\n /** @private */\r\n #mls;\r\n\r\n /** @private */\r\n #P;\r\n\r\n /** @private */\r\n #audioContext;\r\n\r\n /** @private */\r\n TAPER_SECS = 5;\r\n\r\n /** @private */\r\n offsetGainNode;\r\n\r\n /** @private */\r\n convolution;\r\n\r\n /** @private */\r\n status_denominator = 6; \r\n\r\n /** @private */\r\n status_numerator = 0; \r\n\r\n /** @private */\r\n percent_complete = 0; \r\n\r\n /** @private */\r\n status = ``;\r\n\r\n /**generate string template that gets reevaluated as variable increases */\r\n generateTemplate = () => {\r\n if (this.percent_complete > 100){\r\n this.percent_complete = 100;\r\n }\r\n const template = `<div style=\"display: flex; justify-content: center;\"><div style=\"width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;\"><div style=\"width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;\"></div></div></div>`;\r\n return template;\r\n }\r\n\r\n /** increment numerator and percent for status bar */\r\n incrementStatusBar = () => {\r\n this.status_numerator += 1;\r\n this.percent_complete = (this.status_numerator/this.status_denominator)*100;\r\n }\r\n\r\n\r\n /** .\r\n * .\r\n * .\r\n * Sends all the computed impulse responses to the backend server for processing\r\n *\r\n * @returns sets the resulting inverted impulse response to the class property\r\n * @example\r\n */\r\n sendImpulseResponsesToServerForProcessing = async () => {\r\n const computedIRs = await Promise.all(this.impulseResponses);\r\n const filteredComputedIRs = computedIRs.filter(element => {\r\n return element != undefined;\r\n });\r\n const mls = this.#mls;\r\n const lowHz = this.#lowHz;\r\n const highHz = this.#highHz;\r\n this.stepNum += 1;\r\n this.status = `computing the IIR...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n return this.pyServerAPI\r\n .getInverseImpulseResponseWithRetry({\r\n payload: filteredComputedIRs.slice(0, this.numCaptures),\r\n mls,\r\n lowHz,\r\n highHz\r\n })\r\n .then(res => {\r\n console.log(res);\r\n this.stepNum += 1;\r\n this.incrementStatusBar();\r\n this.status = `done computing the IIR...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n this.invertedImpulseResponse = res[\"iir\"];\r\n this.convolution = res[\"convolution\"];\r\n })\r\n .catch(err => {\r\n // this.emit('InvertedImpulseResponse', {res: false});\r\n console.error(err);\r\n });\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing\r\n *\r\n * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed\r\n * @example\r\n */\r\n sendRecordingToServerForProcessing = signalCsv => {\r\n const allSignals = this.getAllRecordedSignals();\r\n const numSignals = allSignals.length;\r\n const mls = this.#mls;\r\n const payload =\r\n signalCsv && signalCsv.length > 0 ? (0,_utils__WEBPACK_IMPORTED_MODULE_2__.csvToArray)(signalCsv) : allSignals[numSignals - 1];\r\n console.log('sending rec');\r\n this.stepNum += 1;\r\n this.status = `computing the IR of the last recording...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n this.impulseResponses.push(\r\n this.pyServerAPI\r\n .getImpulseResponse({\r\n sampleRate: this.sourceSamplingRate || 96000,\r\n payload,\r\n mls,\r\n P: this.#P,\r\n })\r\n .then(res => {\r\n if (this.numSuccessfulCaptured < this.numCaptures) {\r\n this.numSuccessfulCaptured += 1;\r\n console.log(\"num succ capt: \" + this.numSuccessfulCaptured);\r\n this.stepNum += 1;\r\n this.incrementStatusBar();\r\n this.status = `${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n return res;\r\n }\r\n })\r\n .catch(err => {\r\n console.error(err);\r\n })\r\n );\r\n };\r\n\r\n /**\r\n * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number\r\n * of MLS periods defined in the constructor.\r\n *\r\n * @example\r\n */\r\n #awaitDesiredMLSLength = async () => {\r\n // seconds per MLS = P / SR\r\n // await N * P / SR\r\n this.stepNum += 1;\r\n this.status = `sampling the calibration signal...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)((this.#P / this.sourceSamplingRate) * this.numMLSPerCapture);\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state\r\n *\r\n * @example\r\n */\r\n #awaitSignalOnset = async () => {\r\n this.stepNum += 1;\r\n this.status = `waiting for the signal to stabilize...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(this.TAPER_SECS);\r\n };\r\n\r\n /**\r\n * Called immediately after a recording is captured. Used to process the resulting signal\r\n * whether by sending the result to a server or by computing a result locally.\r\n *\r\n * @example\r\n */\r\n #afterMLSRecord = () => {\r\n console.log('after record');\r\n this.sendRecordingToServerForProcessing();\r\n };\r\n\r\n #afterMLSwIIRRecord = () => {\r\n if (this.numSuccessfulCaptured < 1) {\r\n this.numSuccessfulCaptured += 1;\r\n this.stepNum += 1;\r\n this.incrementStatusBar();\r\n this.status = `${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n }\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Created an S Curver Buffer to taper the signal onset\r\n *\r\n * @param {*} length\r\n * @param {*} phase\r\n * @returns\r\n * @example\r\n */\r\n static createSCurveBuffer = (length, phase) => {\r\n const curve = new Float32Array(length);\r\n let i;\r\n for (i = 0; i < length; i += 1) {\r\n // scale the curve to be between 0-1\r\n curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\r\n }\r\n return curve;\r\n };\r\n\r\n static createInverseSCurveBuffer = (length, phase) => {\r\n const curve = new Float32Array(length);\r\n let i;\r\n let j = length - 1;\r\n for (i = 0; i < length; i += 1) {\r\n // scale the curve to be between 0-1\r\n curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;\r\n j -= 1;\r\n }\r\n return curve;\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @param CALIBRATION_TONE_FREQUENCY\r\n * @private\r\n * @example\r\n */\r\n #createPureTonenNode = CALIBRATION_TONE_FREQUENCY => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n\r\n oscilator.frequency.value = CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = 'sine';\r\n gainNode.gain.value = 0.04;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @param dataBuffer\r\n * @private\r\n * @example\r\n */\r\n #createCalibrationNodeFromBuffer = dataBuffer => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const buffer = audioContext.createBuffer(\r\n 1, // number of channels\r\n dataBuffer.length,\r\n audioContext.sampleRate // sample rate\r\n );\r\n\r\n const data = buffer.getChannelData(0); // get data\r\n // fill the buffer with our data\r\n try {\r\n for (let i = 0; i < dataBuffer.length; i += 1) {\r\n data[i] = dataBuffer[i]*.1;\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n console.log(\"mls second, same?\");\r\n console.log(data);\r\n const onsetGainNode = audioContext.createGain();\r\n this.offsetGainNode = audioContext.createGain();\r\n const source = audioContext.createBufferSource();\r\n\r\n source.buffer = buffer;\r\n source.loop = true;\r\n source.connect(onsetGainNode);\r\n onsetGainNode.connect(this.offsetGainNode);\r\n this.offsetGainNode.connect(audioContext.destination);\r\n\r\n const onsetCurve = ImpulseResponse.createSCurveBuffer(this.sourceSamplingRate, Math.PI / 2);\r\n onsetGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);\r\n this.addCalibrationNode(source);\r\n };\r\n\r\n /**\r\n * Given a data buffer, creates the required calibration node\r\n *\r\n * @param {*} dataBufferArray\r\n * @example\r\n */\r\n #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {\r\n if (dataBufferArray.length === 1) {\r\n this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);\r\n } else {\r\n throw new Error('The length of the data buffer array must be 1');\r\n }\r\n\r\n \r\n };\r\n\r\n /**\r\n * function to put MLS filtered IIR data obtained from\r\n * python server into our audio buffer to be played aloud\r\n */\r\n #putInPythonConv = () => {\r\n const audioCtx = this.makeNewSourceAudioContextConvolved();\r\n const buffer = audioCtx.createBuffer(\r\n 1, // number of channels\r\n this.convolution.length,\r\n audioCtx.sampleRate // sample rate\r\n );\r\n\r\n const data = buffer.getChannelData(0); // get data\r\n // fill the buffer with our data\r\n try {\r\n for (let i = 0; i < this.convolution.length; i += 1) {\r\n data[i] = this.convolution[i];\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n const source = audioCtx.createBufferSource();\r\n\r\n source.buffer = buffer;\r\n source.loop = true;\r\n source.connect(audioCtx.destination);\r\n\r\n this.addCalibrationNodeConvolved(source);\r\n }\r\n \r\n /**\r\n * Creates an audio context and plays it for a few seconds.\r\n *\r\n * @private\r\n * @returns - Resolves when the audio is done playing.\r\n * @example\r\n */\r\n #playCalibrationAudio = () => {\r\n this.calibrationNodes[0].start(0);\r\n this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);\r\n this.stepNum += 1;\r\n this.status = `playing the calibration tone...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n }; \r\n\r\n\r\n #playCalibrationAudioConvolved = () => {\r\n this.calibrationNodesConvolved[0].start(0);\r\n this.stepNum += 1;\r\n this.status = `playing the convolved calibration tone...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n }\r\n\r\n /** .\r\n * .\r\n * .\r\n * Stops the audio with tapered offset\r\n *\r\n * @example\r\n */\r\n #stopCalibrationAudio = () => {\r\n this.offsetGainNode.gain.setValueAtTime(\r\n this.offsetGainNode.gain.value,\r\n this.sourceAudioContext.currentTime\r\n );\r\n\r\n this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContext.currentTime, 0.5);\r\n this.calibrationNodes[0].stop(0);\r\n this.sourceAudioContext.close();\r\n this.stepNum += 1;\r\n this.status = `stopping the calibration tone...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n };\r\n\r\n #stopCalibrationAudioConvolved = () => {\r\n this.offsetGainNode.gain.setValueAtTime(\r\n this.offsetGainNode.gain.value,\r\n this.sourceAudioContextConvolved.currentTime\r\n );\r\n\r\n this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContextConvolved.currentTime, 0.5);\r\n this.sourceAudioContextConvolved.close();\r\n this.stepNum += 1;\r\n this.status = `stopping the convolved calibration tone...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n\r\n }\r\n\r\n playMLSwithIIR = async (stream, iir) => {\r\n this.invertedImpulseResponse = iir;\r\n // initialize the MLSGenInterface object with it's factory method\r\n \r\n await _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__[\"default\"].factory(\r\n this.#mlsOrder,\r\n this.sinkSamplingRate,\r\n this.sourceSamplingRate\r\n ).then(mlsGenInterface => {\r\n this.#mlsGenInterface = mlsGenInterface;\r\n this.#mlsBufferView = this.#mlsGenInterface.getMLS();\r\n });\r\n\r\n // after intializating, start the calibration steps with garbage collection\r\n await this.#mlsGenInterface.withGarbageCollection([\r\n () =>\r\n this.calibrationSteps(\r\n stream,\r\n this.#playCalibrationAudioConvolved, // play audio func (required)\r\n this.#putInPythonConv, // before play func\r\n this.#awaitSignalOnset, // before record\r\n () => this.numSuccessfulCaptured < 1, // < this.numCaptures\r\n this.#awaitDesiredMLSLength, // during record\r\n this.#afterMLSwIIRRecord, // after record\r\n 'filtered'\r\n ),\r\n ]);\r\n };\r\n\r\n /**\r\n * Public method to start the calibration process. Objects intialized from webassembly allocate new memory\r\n * and must be manually freed. This function is responsible for intializing the MlsGenInterface,\r\n * and wrapping the calibration steps with a garbage collection safe gaurd.\r\n *\r\n * @public\r\n * @param stream - The stream of audio from the Listener.\r\n * @example\r\n */\r\n startCalibration = async stream => {\r\n // initialize the MLSGenInterface object with it's factory method\r\n await _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__[\"default\"].factory(\r\n this.#mlsOrder,\r\n this.sinkSamplingRate,\r\n this.sourceSamplingRate\r\n ).then(mlsGenInterface => {\r\n this.#mlsGenInterface = mlsGenInterface;\r\n this.#mlsBufferView = this.#mlsGenInterface.getMLS();\r\n });\r\n\r\n // after intializating, start the calibration steps with garbage collection\r\n await this.#mlsGenInterface.withGarbageCollection([\r\n () =>\r\n this.calibrationSteps(\r\n stream,\r\n this.#playCalibrationAudio, // play audio func (required)\r\n this.#setCalibrationNodesFromBuffer, // before play func\r\n this.#awaitSignalOnset, // before record\r\n () => this.numSuccessfulCaptured < this.numCaptures, // loop while true\r\n this.#awaitDesiredMLSLength, // during record\r\n this.#afterMLSRecord, // after record\r\n 'unfiltered'\r\n ),\r\n ]);\r\n\r\n this.#stopCalibrationAudio();\r\n\r\n // at this stage we've captured all the required signals,\r\n // and have received IRs for each one\r\n // so let's send all the IRs to the server to be converted to a single IIR\r\n await this.sendImpulseResponsesToServerForProcessing();\r\n\r\n this.numSuccessfulCaptured = 0;\r\n // debugging function, use to test the result of the IIR\r\n await this.playMLSwithIIR(stream, this.invertedImpulseResponse);\r\n this.#stopCalibrationAudioConvolved();\r\n\r\n let recs = this.getAllRecordedSignals();\r\n let conv_recs = this.getAllFilteredRecordedSignals();\r\n let unconv_rec = recs[0];\r\n let conv_rec = conv_recs[0];\r\n\r\n this.status = `computing the PSD graphs...`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n\r\n let results = await this.pyServerAPI\r\n .getPSDWithRetry({\r\n unconv_rec,\r\n conv_rec,\r\n })\r\n .then(res => {\r\n this.incrementStatusBar();\r\n this.status = `done computing the PSD graphs`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n return res;\r\n })\r\n .catch(err => {\r\n console.error(err);\r\n })\r\n\r\n let iir_and_plots = {\r\n \"iir\": this.invertedImpulseResponse,\r\n \"x_unconv\": results[\"x_unconv\"],\r\n \"y_unconv\": results[\"y_unconv\"],\r\n \"x_conv\": results[\"x_conv\"],\r\n \"y_conv\": results[\"y_conv\"]\r\n }\r\n\r\n if (this.#download) {\r\n this.downloadSingleUnfilteredRecording();\r\n this.downloadSingleFilteredRecording();\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.#mls,\"MLS.csv\");\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.convolution,'python_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.invertedImpulseResponse,'IIR.csv');\r\n const computedIRagain = await Promise.all(this.impulseResponses)\r\n .then(res => {\r\n for (let i = 0; i < res.length; i++){\r\n if (res[i] != undefined){\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(res[i], `IR_${i}`);\r\n }\r\n }\r\n })\r\n }\r\n\r\n return iir_and_plots;\r\n };\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (ImpulseResponse);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/impulse-response/impulseResponse.js?");
|
|
822
822
|
|
|
823
823
|
/***/ }),
|
|
824
824
|
|
|
@@ -840,7 +840,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* eslint-disable prefer-dest
|
|
|
840
840
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
841
841
|
|
|
842
842
|
"use strict";
|
|
843
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../audioCalibrator */ \"./src/tasks/audioCalibrator.js\");\n/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! axios */ \"./node_modules/axios/index.js\");\n/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(axios__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../utils */ \"./src/utils.js\");\n\r\n\r\n\r\n\r\n/**\r\n *\r\n */\r\nclass Volume extends _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n /**\r\n *\r\n * @param root0\r\n * @param root0.download\r\n * @param root0.numCalibrationRounds\r\n * @param root0.numCalibrationNodes\r\n * @example\r\n */\r\n\r\n /** @private */\r\n #CALIBRATION_TONE_FREQUENCY = 1000; // Hz\r\n\r\n /** @private */\r\n #CALIBRATION_TONE_TYPE = 'sine';\r\n\r\n /** @private */\r\n #CALIBRATION_TONE_DURATION = 5; // seconds\r\n\r\n /** @private */\r\n outDBSPL = null;\r\n THD = null;\r\n outDBSPL1000 = null;\r\n\r\n /** @private */\r\n TAPER_SECS = 0.010; // seconds\r\n\r\n handleIncomingData = data => {\r\n console.log('Received data: ', data);\r\n if (data.type === 'soundGainDBSPL') {\r\n this.soundGainDBSPL = data.value;\r\n } else {\r\n throw new Error(`Unknown data type: ${data.type}`);\r\n }\r\n };\r\n\r\n createSCurveBuffer = (onSetBool=true) => {\r\n\r\n const curve = new Float32Array(this.TAPER_SECS*this.sourceSamplingRate+1);\r\n const frequency = 1 / (4 * this.TAPER_SECS);\r\n let j = 0;\r\n for (let i = 0; i < this.TAPER_SECS*this.sourceSamplingRate+1; i += 1) {\r\n const phase = 2 * Math.PI * frequency * j;\r\n const onsetTaper = Math.pow(Math.sin(phase) , 2);\r\n const offsetTaper = Math.pow(Math.cos(phase) , 2);\r\n curve[i] = onSetBool? onsetTaper : offsetTaper;\r\n j += (1 / this.sourceSamplingRate);\r\n } \r\n return curve;\r\n };\r\n\r\n #getTruncatedSignal = (left = 3.5, right = 4.5) => {\r\n const start = Math.floor(left * this.sourceSamplingRate);\r\n const end = Math.floor(right * this.sourceSamplingRate);\r\n const result = Array.from(this.getLastRecordedSignal().slice(start, end));\r\n\r\n /**\r\n * function to check that capture was properly made\r\n * @param {*} list\r\n */\r\n const checkResult = list => {\r\n const setItem = new Set(list);\r\n if (setItem.size === 1 && setItem.has(0)) {\r\n console.warn(\r\n 'The last capture failed, all recorded signal is zero',\r\n this.getAllRecordedSignals()\r\n );\r\n }\r\n if (setItem.size === 0) {\r\n console.warn('The last capture failed, no recorded signal');\r\n }\r\n };\r\n checkResult(result);\r\n return result;\r\n };\r\n\r\n /** \r\n * \r\n * \r\n Construct a calibration Node with the calibration parameters and given gain value\r\n * @param {*} gainValue\r\n * */\r\n #createCalibrationToneWithGainValue = gainValue => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n const taperGainNode = audioContext.createGain();\r\n const offsetGainNode = audioContext.createGain();\r\n const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;\r\n\r\n oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = this.#CALIBRATION_TONE_TYPE;\r\n gainNode.gain.value = gainValue;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(taperGainNode);\r\n const onsetCurve = this.createSCurveBuffer();\r\n taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);\r\n taperGainNode.connect(offsetGainNode);\r\n const offsetCurve = this.createSCurveBuffer(false);\r\n offsetGainNode.gain.setValueCurveAtTime(offsetCurve, (totalDuration-this.TAPER_SECS), this.TAPER_SECS);\r\n offsetGainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #createCalibrationNode = () => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n\r\n oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = this.#CALIBRATION_TONE_TYPE;\r\n gainNode.gain.value = 0.04;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n #playCalibrationAudio = async () => {\r\n const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;\r\n\r\n this.calibrationNodes[0].start(0);\r\n this.calibrationNodes[0].stop(totalDuration);\r\n console.log(`Playing a buffer of ${this.#CALIBRATION_TONE_DURATION} seconds of audio`);\r\n console.log(`Waiting a total of ${totalDuration} seconds`);\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(totalDuration);\r\n };\r\n\r\n #sendToServerForProcessing = (lCalib = 104.92978421490648) => {\r\n console.log('Sending data to server');\r\n this.pyServerAPI\r\n .getVolumeCalibration({\r\n sampleRate: this.sourceSamplingRate,\r\n payload: this.#getTruncatedSignal(),\r\n lCalib: lCalib,\r\n })\r\n .then(res => {\r\n if (this.outDBSPL === null) {\r\n this.outDBSPL = res['outDbSPL'];\r\n this.outDBSPL1000 = res['outDbSPL1000'];\r\n this.THD = res['thd'];\r\n }\r\n })\r\n .catch(err => {\r\n console.warn(err);\r\n });\r\n };\r\n\r\n startCalibration = async (stream, gainValues, lCalib = 104.92978421490648) => {\r\n const trialIterations = gainValues.length;\r\n const thdValues = [];\r\n const inDBValues = [];\r\n let inDB = 0;\r\n const outDBSPLValues = [];\r\n const outDBSPL1000Values = [];\r\n\r\n // do one calibration that will be discarded\r\n const soundLevelToDiscard = -60;\r\n const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);\r\n this.emit('update', {message: `Sound Level: ${soundLevelToDiscard} dB`});\r\n do {\r\n // eslint-disable-next-line no-await-in-loop\r\n await this.volumeCalibrationSteps(\r\n stream,\r\n this.#playCalibrationAudio,\r\n this.#createCalibrationToneWithGainValue,\r\n this.#sendToServerForProcessing,\r\n gainToDiscard,\r\n lCalib //todo make this a class parameter\r\n );\r\n } while (this.outDBSPL === null);\r\n //reset the values\r\n this.outDBSPL = null;\r\n this.outDBSPL = null;\r\n this.outDBSPL1000 = null;\r\n this.THD = null;\r\n\r\n // run the calibration at different gain values provided by the user\r\n for (let i = 0; i < trialIterations; i++) {\r\n //convert gain to DB and add to inDB\r\n inDB = Math.log10(gainValues[i]) * 20;\r\n // precision to 1 decimal place\r\n inDB = Math.round(inDB * 10) / 10;\r\n inDBValues.push(inDB);\r\n this.emit('update', {message: `Sound Level: ${inDB} dB`});\r\n do {\r\n // eslint-disable-next-line no-await-in-loop\r\n await this.volumeCalibrationSteps(\r\n stream,\r\n this.#playCalibrationAudio,\r\n this.#createCalibrationToneWithGainValue,\r\n this.#sendToServerForProcessing,\r\n gainValues[i],\r\n lCalib //todo make this a class parameter\r\n );\r\n } while (this.outDBSPL === null);\r\n outDBSPL1000Values.push(this.outDBSPL1000);\r\n thdValues.push(this.THD);\r\n outDBSPLValues.push(this.outDBSPL);\r\n\r\n this.outDBSPL = null;\r\n this.outDBSPL1000 = null;\r\n this.THD = null;\r\n }\r\n\r\n // get the volume calibration parameters from the server\r\n const parameters = await this.pyServerAPI\r\n .getVolumeCalibrationParameters({\r\n inDBValues: inDBValues,\r\n outDBSPLValues: outDBSPL1000Values,\r\n lCalib: lCalib,\r\n })\r\n .then(res => {\r\n return res;\r\n });\r\n const result = {\r\n parameters: parameters,\r\n inDBValues: inDBValues,\r\n outDBSPLValues: outDBSPLValues,\r\n outDBSPL1000Values: outDBSPL1000Values,\r\n thdValues: thdValues,\r\n };\r\n\r\n return result;\r\n };\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (Volume);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/volume/volume.js?");
|
|
843
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../audioCalibrator */ \"./src/tasks/audioCalibrator.js\");\n/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! axios */ \"./node_modules/axios/index.js\");\n/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(axios__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../utils */ \"./src/utils.js\");\n\r\n\r\n\r\n\r\n/**\r\n *\r\n */\r\nclass Volume extends _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n /**\r\n *\r\n * @param root0\r\n * @param root0.download\r\n * @param root0.numCalibrationRounds\r\n * @param root0.numCalibrationNodes\r\n * @example\r\n */\r\n\r\n /** @private */\r\n #CALIBRATION_TONE_FREQUENCY = 1000; // Hz\r\n\r\n /** @private */\r\n #CALIBRATION_TONE_TYPE = 'sine';\r\n\r\n /** @private */\r\n #CALIBRATION_TONE_DURATION = 5; // seconds\r\n\r\n /** @private */\r\n outDBSPL = null;\r\n THD = null;\r\n outDBSPL1000 = null;\r\n\r\n /** @private */\r\n TAPER_SECS = 0.010; // seconds\r\n\r\n /** @private */\r\n status_denominator = 2; \r\n\r\n /** @private */\r\n status_numerator = 0; \r\n\r\n /** @private */\r\n percent_complete = 0; \r\n\r\n /** @private */\r\n status = ``;\r\n\r\n /**generate string template that gets reevaluated as variable increases */\r\n generateTemplate = () => {\r\n if (this.percent_complete > 100){\r\n this.percent_complete = 100;\r\n }\r\n const template = `<div style=\"display: flex; justify-content: center;\"><div style=\"width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;\"><div style=\"width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;\"></div></div></div>`;\r\n return template;\r\n }\r\n\r\n /** increment numerator and percent for status bar */\r\n incrementStatusBar = () => {\r\n this.status_numerator += 1;\r\n this.percent_complete = (this.status_numerator/this.status_denominator)*100;\r\n }\r\n\r\n handleIncomingData = data => {\r\n console.log('Received data: ', data);\r\n if (data.type === 'soundGainDBSPL') {\r\n this.soundGainDBSPL = data.value;\r\n } else {\r\n throw new Error(`Unknown data type: ${data.type}`);\r\n }\r\n };\r\n\r\n createSCurveBuffer = (onSetBool=true) => {\r\n\r\n const curve = new Float32Array(this.TAPER_SECS*this.sourceSamplingRate+1);\r\n const frequency = 1 / (4 * this.TAPER_SECS);\r\n let j = 0;\r\n for (let i = 0; i < this.TAPER_SECS*this.sourceSamplingRate+1; i += 1) {\r\n const phase = 2 * Math.PI * frequency * j;\r\n const onsetTaper = Math.pow(Math.sin(phase) , 2);\r\n const offsetTaper = Math.pow(Math.cos(phase) , 2);\r\n curve[i] = onSetBool? onsetTaper : offsetTaper;\r\n j += (1 / this.sourceSamplingRate);\r\n } \r\n return curve;\r\n };\r\n\r\n #getTruncatedSignal = (left = 3.5, right = 4.5) => {\r\n const start = Math.floor(left * this.sourceSamplingRate);\r\n const end = Math.floor(right * this.sourceSamplingRate);\r\n const result = Array.from(this.getLastRecordedSignal().slice(start, end));\r\n\r\n /**\r\n * function to check that capture was properly made\r\n * @param {*} list\r\n */\r\n const checkResult = list => {\r\n const setItem = new Set(list);\r\n if (setItem.size === 1 && setItem.has(0)) {\r\n console.warn(\r\n 'The last capture failed, all recorded signal is zero',\r\n this.getAllRecordedSignals()\r\n );\r\n }\r\n if (setItem.size === 0) {\r\n console.warn('The last capture failed, no recorded signal');\r\n }\r\n };\r\n checkResult(result);\r\n return result;\r\n };\r\n\r\n /** \r\n * \r\n * \r\n Construct a calibration Node with the calibration parameters and given gain value\r\n * @param {*} gainValue\r\n * */\r\n #createCalibrationToneWithGainValue = gainValue => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n const taperGainNode = audioContext.createGain();\r\n const offsetGainNode = audioContext.createGain();\r\n const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;\r\n\r\n oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = this.#CALIBRATION_TONE_TYPE;\r\n gainNode.gain.value = gainValue;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(taperGainNode);\r\n const onsetCurve = this.createSCurveBuffer();\r\n taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);\r\n taperGainNode.connect(offsetGainNode);\r\n const offsetCurve = this.createSCurveBuffer(false);\r\n offsetGainNode.gain.setValueCurveAtTime(offsetCurve, (totalDuration-this.TAPER_SECS), this.TAPER_SECS);\r\n offsetGainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #createCalibrationNode = () => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n\r\n oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = this.#CALIBRATION_TONE_TYPE;\r\n gainNode.gain.value = 0.04;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n #playCalibrationAudio = async () => {\r\n const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;\r\n\r\n this.calibrationNodes[0].start(0);\r\n this.calibrationNodes[0].stop(totalDuration);\r\n console.log(`Playing a buffer of ${this.#CALIBRATION_TONE_DURATION} seconds of audio`);\r\n console.log(`Waiting a total of ${totalDuration} seconds`);\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(totalDuration);\r\n };\r\n\r\n #sendToServerForProcessing = (lCalib = 104.92978421490648) => {\r\n console.log('Sending data to server');\r\n this.pyServerAPI\r\n .getVolumeCalibration({\r\n sampleRate: this.sourceSamplingRate,\r\n payload: this.#getTruncatedSignal(),\r\n lCalib: lCalib,\r\n })\r\n .then(res => {\r\n if (this.outDBSPL === null) {\r\n this.incrementStatusBar();\r\n this.outDBSPL = res['outDbSPL'];\r\n this.outDBSPL1000 = res['outDbSPL1000'];\r\n this.THD = res['thd'];\r\n }\r\n })\r\n .catch(err => {\r\n console.warn(err);\r\n });\r\n };\r\n\r\n startCalibration = async (stream, gainValues, lCalib = 104.92978421490648) => {\r\n const trialIterations = gainValues.length;\r\n this.status_denominator += trialIterations;\r\n const thdValues = [];\r\n const inDBValues = [];\r\n let inDB = 0;\r\n const outDBSPLValues = [];\r\n const outDBSPL1000Values = [];\r\n\r\n // do one calibration that will be discarded\r\n const soundLevelToDiscard = -60;\r\n const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);\r\n this.status = `Sound Level: ${soundLevelToDiscard} dB`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n do {\r\n // eslint-disable-next-line no-await-in-loop\r\n await this.volumeCalibrationSteps(\r\n stream,\r\n this.#playCalibrationAudio,\r\n this.#createCalibrationToneWithGainValue,\r\n this.#sendToServerForProcessing,\r\n gainToDiscard,\r\n lCalib //todo make this a class parameter\r\n );\r\n } while (this.outDBSPL === null);\r\n //reset the values\r\n this.outDBSPL = null;\r\n this.outDBSPL = null;\r\n this.outDBSPL1000 = null;\r\n this.THD = null;\r\n\r\n // run the calibration at different gain values provided by the user\r\n for (let i = 0; i < trialIterations; i++) {\r\n //convert gain to DB and add to inDB\r\n inDB = Math.log10(gainValues[i]) * 20;\r\n // precision to 1 decimal place\r\n inDB = Math.round(inDB * 10) / 10;\r\n inDBValues.push(inDB);\r\n this.status = `Sound Level: ${inDB} dB`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n do {\r\n // eslint-disable-next-line no-await-in-loop\r\n await this.volumeCalibrationSteps(\r\n stream,\r\n this.#playCalibrationAudio,\r\n this.#createCalibrationToneWithGainValue,\r\n this.#sendToServerForProcessing,\r\n gainValues[i],\r\n lCalib //todo make this a class parameter\r\n );\r\n } while (this.outDBSPL === null);\r\n outDBSPL1000Values.push(this.outDBSPL1000);\r\n thdValues.push(this.THD);\r\n outDBSPLValues.push(this.outDBSPL);\r\n\r\n this.outDBSPL = null;\r\n this.outDBSPL1000 = null;\r\n this.THD = null;\r\n }\r\n\r\n // get the volume calibration parameters from the server\r\n \r\n const parameters = await this.pyServerAPI\r\n .getVolumeCalibrationParameters({\r\n inDBValues: inDBValues,\r\n outDBSPLValues: outDBSPL1000Values,\r\n lCalib: lCalib,\r\n })\r\n .then(res => {\r\n this.incrementStatusBar();\r\n this.status = `done with 1000 Hz calibration`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n return res;\r\n });\r\n const result = {\r\n parameters: parameters,\r\n inDBValues: inDBValues,\r\n outDBSPLValues: outDBSPLValues,\r\n outDBSPL1000Values: outDBSPL1000Values,\r\n thdValues: thdValues,\r\n };\r\n\r\n return result;\r\n };\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (Volume);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/volume/volume.js?");
|
|
844
844
|
|
|
845
845
|
/***/ }),
|
|
846
846
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "speaker-calibration",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.18",
|
|
4
4
|
"description": "Speaker calibration library for auditory testing",
|
|
5
5
|
"main": "dist/main.js",
|
|
6
6
|
"directories": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"axios": "^0.27.2",
|
|
34
34
|
"fftw-js": "^0.1.4",
|
|
35
|
-
"np": "^7.
|
|
35
|
+
"np": "^7.7.0",
|
|
36
36
|
"path-browserify": "^1.0.1",
|
|
37
37
|
"peerjs": "^1.3.2",
|
|
38
38
|
"qrcode": "^1.4.4",
|
|
@@ -6,6 +6,11 @@ class PythonServerAPI {
|
|
|
6
6
|
static PYTHON_SERVER_URL = 'https://easyeyes-python-flask-server.herokuapp.com';
|
|
7
7
|
|
|
8
8
|
static TEST_SERVER_URL = 'http://127.0.0.1:5000';
|
|
9
|
+
|
|
10
|
+
/** @private */
|
|
11
|
+
MAX_RETRY_COUNT = 3;
|
|
12
|
+
/** @private */
|
|
13
|
+
RETRY_DELAY_MS = 1000;
|
|
9
14
|
/**
|
|
10
15
|
* @param data- -
|
|
11
16
|
* g = inverted impulse response, when convolved with the impulse
|
|
@@ -78,6 +83,30 @@ class PythonServerAPI {
|
|
|
78
83
|
});
|
|
79
84
|
return res.data[task];
|
|
80
85
|
};
|
|
86
|
+
|
|
87
|
+
getPSDWithRetry = async ({ unconv_rec, conv_rec }) => {
|
|
88
|
+
let retryCount = 0;
|
|
89
|
+
let response = null;
|
|
90
|
+
|
|
91
|
+
while (retryCount < this.MAX_RETRY_COUNT) {
|
|
92
|
+
try {
|
|
93
|
+
response = await this.getPSD({ unconv_rec, conv_rec });
|
|
94
|
+
// If the request is successful, break out of the loop
|
|
95
|
+
break;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(`Error occurred. Retrying... (${retryCount + 1}/${this.MAX_RETRY_COUNT})`);
|
|
98
|
+
retryCount++;
|
|
99
|
+
await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY_MS));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (response) {
|
|
104
|
+
return response;
|
|
105
|
+
} else {
|
|
106
|
+
throw new Error(`Failed to get PSD after ${this.MAX_RETRY_COUNT} attempts.`);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
81
110
|
|
|
82
111
|
getInverseImpulseResponse = async ({payload,mls,lowHz,highHz}) => {
|
|
83
112
|
const task = 'inverse-impulse-response';
|
|
@@ -112,6 +141,30 @@ class PythonServerAPI {
|
|
|
112
141
|
return res.data[task];
|
|
113
142
|
};
|
|
114
143
|
|
|
144
|
+
getInverseImpulseResponseWithRetry = async ({ payload, mls, lowHz, highHz }) => {
|
|
145
|
+
let retryCount = 0;
|
|
146
|
+
let response = null;
|
|
147
|
+
|
|
148
|
+
while (retryCount < this.MAX_RETRY_COUNT) {
|
|
149
|
+
try {
|
|
150
|
+
response = await this.getInverseImpulseResponse({ payload, mls, lowHz, highHz });
|
|
151
|
+
// If the request is successful, break out of the loop
|
|
152
|
+
break;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(`Error occurred. Retrying... (${retryCount + 1}/${this.MAX_RETRY_COUNT})`);
|
|
155
|
+
retryCount++;
|
|
156
|
+
await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY_MS));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (response) {
|
|
161
|
+
return response;
|
|
162
|
+
} else {
|
|
163
|
+
throw new Error(`Failed to get inverse impulse response after ${this.MAX_RETRY_COUNT} attempts.`);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
|
|
115
168
|
getVolumeCalibration = async ({payload, sampleRate, lCalib}) => {
|
|
116
169
|
const task = 'volume';
|
|
117
170
|
let res = null;
|
|
@@ -91,6 +91,36 @@ class Combination extends AudioCalibrator {
|
|
|
91
91
|
/** @private */
|
|
92
92
|
TAPER_SECS = 0.010; // seconds
|
|
93
93
|
|
|
94
|
+
/** @private */
|
|
95
|
+
status_denominator = 8;
|
|
96
|
+
|
|
97
|
+
/** @private */
|
|
98
|
+
status_numerator = 0;
|
|
99
|
+
|
|
100
|
+
/** @private */
|
|
101
|
+
percent_complete = 0;
|
|
102
|
+
|
|
103
|
+
/** @private */
|
|
104
|
+
status = ``;
|
|
105
|
+
|
|
106
|
+
/**@private */
|
|
107
|
+
status_literal = `<div style="display: flex; justify-content: center;"><div style="width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;"><div style="width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;"></div></div></div>`;
|
|
108
|
+
|
|
109
|
+
/**generate string template that gets reevaluated as variable increases */
|
|
110
|
+
generateTemplate = () => {
|
|
111
|
+
if (this.percent_complete > 100){
|
|
112
|
+
this.percent_complete = 100;
|
|
113
|
+
}
|
|
114
|
+
const template = `<div style="display: flex; justify-content: center;"><div style="width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;"><div style="width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;"></div></div></div>`;
|
|
115
|
+
return template;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** increment numerator and percent for status bar */
|
|
119
|
+
incrementStatusBar = () => {
|
|
120
|
+
this.status_numerator += 1;
|
|
121
|
+
this.percent_complete = (this.status_numerator/this.status_denominator)*100;
|
|
122
|
+
}
|
|
123
|
+
|
|
94
124
|
/** .
|
|
95
125
|
* .
|
|
96
126
|
* .
|
|
@@ -108,9 +138,11 @@ class Combination extends AudioCalibrator {
|
|
|
108
138
|
const lowHz = this.#lowHz;
|
|
109
139
|
const highHz = this.#highHz;
|
|
110
140
|
this.stepNum += 1;
|
|
111
|
-
|
|
141
|
+
console.log('send impulse responses to server: ' + this.stepNum);
|
|
142
|
+
this.status = `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
|
|
143
|
+
this.emit('update', {message: this.status});
|
|
112
144
|
return this.pyServerAPI
|
|
113
|
-
.
|
|
145
|
+
.getInverseImpulseResponseWithRetry({
|
|
114
146
|
payload: filteredComputedIRs.slice(0, this.numCaptures),
|
|
115
147
|
mls,
|
|
116
148
|
lowHz,
|
|
@@ -119,7 +151,10 @@ class Combination extends AudioCalibrator {
|
|
|
119
151
|
.then(res => {
|
|
120
152
|
console.log(res);
|
|
121
153
|
this.stepNum += 1;
|
|
122
|
-
|
|
154
|
+
console.log('got impulse response ' + this.stepNum);
|
|
155
|
+
this.incrementStatusBar();
|
|
156
|
+
this.status = `All Hz Calibration: done computing the IIR...`.toString() + this.generateTemplate().toString();
|
|
157
|
+
this.emit('update', {message: this.status});
|
|
123
158
|
this.invertedImpulseResponse = res["iir"];
|
|
124
159
|
this.convolution = res["convolution"];
|
|
125
160
|
})
|
|
@@ -145,7 +180,9 @@ class Combination extends AudioCalibrator {
|
|
|
145
180
|
signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
|
|
146
181
|
console.log('sending rec');
|
|
147
182
|
this.stepNum += 1;
|
|
148
|
-
|
|
183
|
+
console.log('send rec ' + this.stepNum);
|
|
184
|
+
this.status = `All Hz Calibration Step: computing the IR of the last recording...`.toString() + this.generateTemplate().toString();
|
|
185
|
+
this.emit('update', {message: this.status});
|
|
149
186
|
this.impulseResponses.push(
|
|
150
187
|
this.pyServerAPI
|
|
151
188
|
.getImpulseResponse({
|
|
@@ -159,11 +196,15 @@ class Combination extends AudioCalibrator {
|
|
|
159
196
|
this.numSuccessfulCaptured += 1;
|
|
160
197
|
console.log("num succ capt: " + this.numSuccessfulCaptured);
|
|
161
198
|
this.stepNum += 1;
|
|
199
|
+
console.log('got impulse response ' + this.stepNum);
|
|
200
|
+
this.incrementStatusBar();
|
|
201
|
+
this.status = `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() + this.generateTemplate().toString();
|
|
162
202
|
this.emit('update', {
|
|
163
|
-
message:
|
|
203
|
+
message: this.status,
|
|
164
204
|
});
|
|
205
|
+
return res;
|
|
165
206
|
}
|
|
166
|
-
|
|
207
|
+
|
|
167
208
|
})
|
|
168
209
|
.catch(err => {
|
|
169
210
|
console.error(err);
|
|
@@ -181,8 +222,10 @@ class Combination extends AudioCalibrator {
|
|
|
181
222
|
// seconds per MLS = P / SR
|
|
182
223
|
// await N * P / SR
|
|
183
224
|
this.stepNum += 1;
|
|
225
|
+
console.log('await desired length ' + this.stepNum);
|
|
226
|
+
this.status = `All Hz Calibration: sampling the calibration signal...`.toString() + this.generateTemplate();
|
|
184
227
|
this.emit('update', {
|
|
185
|
-
message:
|
|
228
|
+
message: this.status,
|
|
186
229
|
});
|
|
187
230
|
await sleep((this.#P / this.sourceSamplingRate) * this.numMLSPerCapture);
|
|
188
231
|
};
|
|
@@ -196,8 +239,10 @@ class Combination extends AudioCalibrator {
|
|
|
196
239
|
*/
|
|
197
240
|
#awaitSignalOnset = async () => {
|
|
198
241
|
this.stepNum += 1;
|
|
242
|
+
console.log('await signal onset ' + this.stepNum);
|
|
243
|
+
this.status = `All Hz Calibration: waiting for the signal to stabilize...`.toString() + this.generateTemplate();
|
|
199
244
|
this.emit('update', {
|
|
200
|
-
message:
|
|
245
|
+
message: this.status,
|
|
201
246
|
});
|
|
202
247
|
await sleep(this.TAPER_SECS);
|
|
203
248
|
};
|
|
@@ -214,11 +259,14 @@ class Combination extends AudioCalibrator {
|
|
|
214
259
|
};
|
|
215
260
|
|
|
216
261
|
#afterMLSwIIRRecord = () => {
|
|
217
|
-
if (this.numSuccessfulCaptured <
|
|
262
|
+
if (this.numSuccessfulCaptured < 1) {
|
|
218
263
|
this.numSuccessfulCaptured += 1;
|
|
219
264
|
this.stepNum += 1;
|
|
265
|
+
this.incrementStatusBar();
|
|
266
|
+
console.log('after mls w iir record for some reason add numSucc capt ' + this.stepNum);
|
|
267
|
+
this.status = `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() + this.generateTemplate().toString();
|
|
220
268
|
this.emit('update', {
|
|
221
|
-
message:
|
|
269
|
+
message: this.status,
|
|
222
270
|
});
|
|
223
271
|
}
|
|
224
272
|
};
|
|
@@ -378,14 +426,18 @@ class Combination extends AudioCalibrator {
|
|
|
378
426
|
this.calibrationNodes[0].start(0);
|
|
379
427
|
this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);
|
|
380
428
|
this.stepNum += 1;
|
|
381
|
-
|
|
429
|
+
console.log('play calibration audio ' + this.stepNum);
|
|
430
|
+
this.status = `All Hz Calibration: playing the calibration tone...`.toString() + this.generateTemplate().toString();
|
|
431
|
+
this.emit('update', {message: this.status});
|
|
382
432
|
};
|
|
383
433
|
|
|
384
434
|
|
|
385
435
|
#playCalibrationAudioConvolved = () => {
|
|
386
436
|
this.calibrationNodesConvolved[0].start(0);
|
|
387
437
|
this.stepNum += 1;
|
|
388
|
-
|
|
438
|
+
console.log('play convolved audio ' + this.stepNum);
|
|
439
|
+
this.status = `All Hz Calibration: playing the convolved calibration tone...`.toString() + this.generateTemplate().toString();
|
|
440
|
+
this.emit('update',{message: this.status})
|
|
389
441
|
}
|
|
390
442
|
|
|
391
443
|
/** .
|
|
@@ -405,7 +457,9 @@ class Combination extends AudioCalibrator {
|
|
|
405
457
|
this.calibrationNodes[0].stop(0);
|
|
406
458
|
this.sourceAudioContext.close();
|
|
407
459
|
this.stepNum += 1;
|
|
408
|
-
|
|
460
|
+
console.log('stop calibratoin audio ' + this.stepNum);
|
|
461
|
+
this.status = `All Hz Calibration: stopping the calibration tone...`.toString() + this.generateTemplate().toString();
|
|
462
|
+
this.emit('update', {message: this.status});
|
|
409
463
|
};
|
|
410
464
|
|
|
411
465
|
#stopCalibrationAudioConvolved = () => {
|
|
@@ -419,7 +473,9 @@ class Combination extends AudioCalibrator {
|
|
|
419
473
|
console.log("right before closing volved audio context");
|
|
420
474
|
this.sourceAudioContextConvolved.close();
|
|
421
475
|
this.stepNum += 1;
|
|
422
|
-
|
|
476
|
+
console.log('stop convolved calibration audio ' + this.stepNum);
|
|
477
|
+
this.status = `All Hz Calibration: stopping the convolved calibration tone...`.toString() + this.generateTemplate().toString();
|
|
478
|
+
this.emit('update', {message: this.status});
|
|
423
479
|
|
|
424
480
|
}
|
|
425
481
|
|
|
@@ -447,7 +503,7 @@ class Combination extends AudioCalibrator {
|
|
|
447
503
|
this.#playCalibrationAudioConvolved, // play audio func (required)
|
|
448
504
|
this.#putInPythonConv, // before play func
|
|
449
505
|
this.#awaitSignalOnset, // before record
|
|
450
|
-
() => this.numSuccessfulCaptured <
|
|
506
|
+
() => this.numSuccessfulCaptured < 1,
|
|
451
507
|
this.#awaitDesiredMLSLength, // during record
|
|
452
508
|
this.#afterMLSwIIRRecord, // after record
|
|
453
509
|
'filtered'
|
|
@@ -507,12 +563,18 @@ class Combination extends AudioCalibrator {
|
|
|
507
563
|
let unconv_rec = recs[0];
|
|
508
564
|
let conv_rec = conv_recs[0];
|
|
509
565
|
|
|
566
|
+
this.status = `All Hz Calibration: computing PSD graphs...`.toString() + this.generateTemplate().toString();
|
|
567
|
+
this.emit('update', {message: this.status});
|
|
568
|
+
|
|
510
569
|
let results = await this.pyServerAPI
|
|
511
|
-
.
|
|
570
|
+
.getPSDWithRetry({
|
|
512
571
|
unconv_rec,
|
|
513
572
|
conv_rec,
|
|
514
573
|
})
|
|
515
574
|
.then(res => {
|
|
575
|
+
this.incrementStatusBar();
|
|
576
|
+
this.status = `All Hz Calibration: done computing the PSD graphs...`.toString() + this.generateTemplate().toString();
|
|
577
|
+
this.emit('update', {message: this.status});
|
|
516
578
|
return res;
|
|
517
579
|
})
|
|
518
580
|
.catch(err => {
|
|
@@ -667,6 +729,7 @@ class Combination extends AudioCalibrator {
|
|
|
667
729
|
})
|
|
668
730
|
.then(res => {
|
|
669
731
|
if (this.outDBSPL === null) {
|
|
732
|
+
this.incrementStatusBar();
|
|
670
733
|
this.outDBSPL = res['outDbSPL'];
|
|
671
734
|
this.outDBSPL1000 = res['outDbSPL1000'];
|
|
672
735
|
this.THD = res['thd'];
|
|
@@ -679,16 +742,20 @@ class Combination extends AudioCalibrator {
|
|
|
679
742
|
|
|
680
743
|
startCalibrationVolume = async (stream, gainValues, lCalib = 104.92978421490648) => {
|
|
681
744
|
const trialIterations = gainValues.length;
|
|
745
|
+
this.status_denominator += trialIterations;
|
|
682
746
|
const thdValues = [];
|
|
683
747
|
const inDBValues = [];
|
|
684
748
|
let inDB = 0;
|
|
685
749
|
const outDBSPLValues = [];
|
|
686
|
-
const outDBSPL1000Values = [];
|
|
750
|
+
const outDBSPL1000Values = [];
|
|
687
751
|
|
|
688
752
|
// do one calibration that will be discarded
|
|
689
753
|
const soundLevelToDiscard = -60;
|
|
690
754
|
const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);
|
|
691
|
-
this.
|
|
755
|
+
this.status = `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() + this.generateTemplate().toString();
|
|
756
|
+
//this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});
|
|
757
|
+
this.emit('update', {message: this.status});
|
|
758
|
+
|
|
692
759
|
do {
|
|
693
760
|
// eslint-disable-next-line no-await-in-loop
|
|
694
761
|
await this.volumeCalibrationSteps(
|
|
@@ -701,6 +768,8 @@ class Combination extends AudioCalibrator {
|
|
|
701
768
|
);
|
|
702
769
|
} while (this.outDBSPL === null);
|
|
703
770
|
//reset the values
|
|
771
|
+
//this.incrementStatusBar();
|
|
772
|
+
|
|
704
773
|
this.outDBSPL = null;
|
|
705
774
|
this.outDBSPL = null;
|
|
706
775
|
this.outDBSPL1000 = null;
|
|
@@ -713,7 +782,9 @@ class Combination extends AudioCalibrator {
|
|
|
713
782
|
// precision to 1 decimal place
|
|
714
783
|
inDB = Math.round(inDB * 10) / 10;
|
|
715
784
|
inDBValues.push(inDB);
|
|
716
|
-
|
|
785
|
+
console.log('next update');
|
|
786
|
+
this.status = `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() + this.generateTemplate().toString();
|
|
787
|
+
this.emit('update', {message: this.status});
|
|
717
788
|
do {
|
|
718
789
|
// eslint-disable-next-line no-await-in-loop
|
|
719
790
|
await this.volumeCalibrationSteps(
|
|
@@ -742,6 +813,7 @@ class Combination extends AudioCalibrator {
|
|
|
742
813
|
lCalib: lCalib,
|
|
743
814
|
})
|
|
744
815
|
.then(res => {
|
|
816
|
+
this.incrementStatusBar();
|
|
745
817
|
return res;
|
|
746
818
|
});
|
|
747
819
|
const result = {
|
|
@@ -751,6 +823,7 @@ class Combination extends AudioCalibrator {
|
|
|
751
823
|
outDBSPL1000Values: outDBSPL1000Values,
|
|
752
824
|
thdValues: thdValues,
|
|
753
825
|
};
|
|
826
|
+
|
|
754
827
|
|
|
755
828
|
return result;
|
|
756
829
|
};
|
|
@@ -74,6 +74,34 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
74
74
|
/** @private */
|
|
75
75
|
convolution;
|
|
76
76
|
|
|
77
|
+
/** @private */
|
|
78
|
+
status_denominator = 6;
|
|
79
|
+
|
|
80
|
+
/** @private */
|
|
81
|
+
status_numerator = 0;
|
|
82
|
+
|
|
83
|
+
/** @private */
|
|
84
|
+
percent_complete = 0;
|
|
85
|
+
|
|
86
|
+
/** @private */
|
|
87
|
+
status = ``;
|
|
88
|
+
|
|
89
|
+
/**generate string template that gets reevaluated as variable increases */
|
|
90
|
+
generateTemplate = () => {
|
|
91
|
+
if (this.percent_complete > 100){
|
|
92
|
+
this.percent_complete = 100;
|
|
93
|
+
}
|
|
94
|
+
const template = `<div style="display: flex; justify-content: center;"><div style="width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;"><div style="width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;"></div></div></div>`;
|
|
95
|
+
return template;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** increment numerator and percent for status bar */
|
|
99
|
+
incrementStatusBar = () => {
|
|
100
|
+
this.status_numerator += 1;
|
|
101
|
+
this.percent_complete = (this.status_numerator/this.status_denominator)*100;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
77
105
|
/** .
|
|
78
106
|
* .
|
|
79
107
|
* .
|
|
@@ -91,9 +119,10 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
91
119
|
const lowHz = this.#lowHz;
|
|
92
120
|
const highHz = this.#highHz;
|
|
93
121
|
this.stepNum += 1;
|
|
94
|
-
this.
|
|
122
|
+
this.status = `computing the IIR...`.toString() + this.generateTemplate().toString();
|
|
123
|
+
this.emit('update', {message: this.status});
|
|
95
124
|
return this.pyServerAPI
|
|
96
|
-
.
|
|
125
|
+
.getInverseImpulseResponseWithRetry({
|
|
97
126
|
payload: filteredComputedIRs.slice(0, this.numCaptures),
|
|
98
127
|
mls,
|
|
99
128
|
lowHz,
|
|
@@ -102,7 +131,9 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
102
131
|
.then(res => {
|
|
103
132
|
console.log(res);
|
|
104
133
|
this.stepNum += 1;
|
|
105
|
-
this.
|
|
134
|
+
this.incrementStatusBar();
|
|
135
|
+
this.status = `done computing the IIR...`.toString() + this.generateTemplate().toString();
|
|
136
|
+
this.emit('update', {message: this.status});
|
|
106
137
|
this.invertedImpulseResponse = res["iir"];
|
|
107
138
|
this.convolution = res["convolution"];
|
|
108
139
|
})
|
|
@@ -128,7 +159,8 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
128
159
|
signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
|
|
129
160
|
console.log('sending rec');
|
|
130
161
|
this.stepNum += 1;
|
|
131
|
-
this.
|
|
162
|
+
this.status = `computing the IR of the last recording...`.toString() + this.generateTemplate().toString();
|
|
163
|
+
this.emit('update', {message: this.status});
|
|
132
164
|
this.impulseResponses.push(
|
|
133
165
|
this.pyServerAPI
|
|
134
166
|
.getImpulseResponse({
|
|
@@ -142,11 +174,11 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
142
174
|
this.numSuccessfulCaptured += 1;
|
|
143
175
|
console.log("num succ capt: " + this.numSuccessfulCaptured);
|
|
144
176
|
this.stepNum += 1;
|
|
145
|
-
this.
|
|
146
|
-
|
|
147
|
-
});
|
|
177
|
+
this.incrementStatusBar();
|
|
178
|
+
this.status = `${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() + this.generateTemplate().toString();
|
|
179
|
+
this.emit('update', {message: this.status});
|
|
180
|
+
return res;
|
|
148
181
|
}
|
|
149
|
-
return res;
|
|
150
182
|
})
|
|
151
183
|
.catch(err => {
|
|
152
184
|
console.error(err);
|
|
@@ -164,9 +196,8 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
164
196
|
// seconds per MLS = P / SR
|
|
165
197
|
// await N * P / SR
|
|
166
198
|
this.stepNum += 1;
|
|
167
|
-
this.
|
|
168
|
-
|
|
169
|
-
});
|
|
199
|
+
this.status = `sampling the calibration signal...`.toString() + this.generateTemplate().toString();
|
|
200
|
+
this.emit('update', {message: this.status});
|
|
170
201
|
await sleep((this.#P / this.sourceSamplingRate) * this.numMLSPerCapture);
|
|
171
202
|
};
|
|
172
203
|
|
|
@@ -179,9 +210,8 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
179
210
|
*/
|
|
180
211
|
#awaitSignalOnset = async () => {
|
|
181
212
|
this.stepNum += 1;
|
|
182
|
-
this.
|
|
183
|
-
|
|
184
|
-
});
|
|
213
|
+
this.status = `waiting for the signal to stabilize...`.toString() + this.generateTemplate().toString();
|
|
214
|
+
this.emit('update', {message: this.status});
|
|
185
215
|
await sleep(this.TAPER_SECS);
|
|
186
216
|
};
|
|
187
217
|
|
|
@@ -197,12 +227,12 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
197
227
|
};
|
|
198
228
|
|
|
199
229
|
#afterMLSwIIRRecord = () => {
|
|
200
|
-
if (this.numSuccessfulCaptured <
|
|
230
|
+
if (this.numSuccessfulCaptured < 1) {
|
|
201
231
|
this.numSuccessfulCaptured += 1;
|
|
202
232
|
this.stepNum += 1;
|
|
203
|
-
this.
|
|
204
|
-
|
|
205
|
-
});
|
|
233
|
+
this.incrementStatusBar();
|
|
234
|
+
this.status = `${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() + this.generateTemplate().toString();
|
|
235
|
+
this.emit('update', {message: this.status});
|
|
206
236
|
}
|
|
207
237
|
};
|
|
208
238
|
|
|
@@ -309,8 +339,6 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
309
339
|
*/
|
|
310
340
|
#setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {
|
|
311
341
|
if (dataBufferArray.length === 1) {
|
|
312
|
-
console.log('data buffer aray');
|
|
313
|
-
console.log(dataBufferArray);
|
|
314
342
|
this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);
|
|
315
343
|
} else {
|
|
316
344
|
throw new Error('The length of the data buffer array must be 1');
|
|
@@ -361,14 +389,16 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
361
389
|
this.calibrationNodes[0].start(0);
|
|
362
390
|
this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);
|
|
363
391
|
this.stepNum += 1;
|
|
364
|
-
this.
|
|
392
|
+
this.status = `playing the calibration tone...`.toString() + this.generateTemplate().toString();
|
|
393
|
+
this.emit('update', {message: this.status});
|
|
365
394
|
};
|
|
366
395
|
|
|
367
396
|
|
|
368
397
|
#playCalibrationAudioConvolved = () => {
|
|
369
398
|
this.calibrationNodesConvolved[0].start(0);
|
|
370
399
|
this.stepNum += 1;
|
|
371
|
-
this.
|
|
400
|
+
this.status = `playing the convolved calibration tone...`.toString() + this.generateTemplate().toString();
|
|
401
|
+
this.emit('update', {message: this.status});
|
|
372
402
|
}
|
|
373
403
|
|
|
374
404
|
/** .
|
|
@@ -388,7 +418,8 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
388
418
|
this.calibrationNodes[0].stop(0);
|
|
389
419
|
this.sourceAudioContext.close();
|
|
390
420
|
this.stepNum += 1;
|
|
391
|
-
this.
|
|
421
|
+
this.status = `stopping the calibration tone...`.toString() + this.generateTemplate().toString();
|
|
422
|
+
this.emit('update', {message: this.status});
|
|
392
423
|
};
|
|
393
424
|
|
|
394
425
|
#stopCalibrationAudioConvolved = () => {
|
|
@@ -398,16 +429,14 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
398
429
|
);
|
|
399
430
|
|
|
400
431
|
this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContextConvolved.currentTime, 0.5);
|
|
401
|
-
//this.calibrationNodesConvolved[0].stop(0);
|
|
402
|
-
console.log("right before closing volved audio context");
|
|
403
432
|
this.sourceAudioContextConvolved.close();
|
|
404
433
|
this.stepNum += 1;
|
|
405
|
-
this.
|
|
434
|
+
this.status = `stopping the convolved calibration tone...`.toString() + this.generateTemplate().toString();
|
|
435
|
+
this.emit('update', {message: this.status});
|
|
406
436
|
|
|
407
437
|
}
|
|
408
438
|
|
|
409
439
|
playMLSwithIIR = async (stream, iir) => {
|
|
410
|
-
console.log('play mls with iir');
|
|
411
440
|
this.invertedImpulseResponse = iir;
|
|
412
441
|
// initialize the MLSGenInterface object with it's factory method
|
|
413
442
|
|
|
@@ -420,8 +449,6 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
420
449
|
this.#mlsBufferView = this.#mlsGenInterface.getMLS();
|
|
421
450
|
});
|
|
422
451
|
|
|
423
|
-
console.log('after mls factory'); //works up to here.
|
|
424
|
-
console.log(this.#mls);
|
|
425
452
|
// after intializating, start the calibration steps with garbage collection
|
|
426
453
|
await this.#mlsGenInterface.withGarbageCollection([
|
|
427
454
|
() =>
|
|
@@ -430,7 +457,7 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
430
457
|
this.#playCalibrationAudioConvolved, // play audio func (required)
|
|
431
458
|
this.#putInPythonConv, // before play func
|
|
432
459
|
this.#awaitSignalOnset, // before record
|
|
433
|
-
() => this.numSuccessfulCaptured < this.numCaptures
|
|
460
|
+
() => this.numSuccessfulCaptured < 1, // < this.numCaptures
|
|
434
461
|
this.#awaitDesiredMLSLength, // during record
|
|
435
462
|
this.#afterMLSwIIRRecord, // after record
|
|
436
463
|
'filtered'
|
|
@@ -490,12 +517,18 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
490
517
|
let unconv_rec = recs[0];
|
|
491
518
|
let conv_rec = conv_recs[0];
|
|
492
519
|
|
|
520
|
+
this.status = `computing the PSD graphs...`.toString() + this.generateTemplate().toString();
|
|
521
|
+
this.emit('update', {message: this.status});
|
|
522
|
+
|
|
493
523
|
let results = await this.pyServerAPI
|
|
494
|
-
.
|
|
524
|
+
.getPSDWithRetry({
|
|
495
525
|
unconv_rec,
|
|
496
526
|
conv_rec,
|
|
497
527
|
})
|
|
498
528
|
.then(res => {
|
|
529
|
+
this.incrementStatusBar();
|
|
530
|
+
this.status = `done computing the PSD graphs`.toString() + this.generateTemplate().toString();
|
|
531
|
+
this.emit('update', {message: this.status});
|
|
499
532
|
return res;
|
|
500
533
|
})
|
|
501
534
|
.catch(err => {
|
|
@@ -509,6 +542,7 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
509
542
|
"x_conv": results["x_conv"],
|
|
510
543
|
"y_conv": results["y_conv"]
|
|
511
544
|
}
|
|
545
|
+
|
|
512
546
|
if (this.#download) {
|
|
513
547
|
this.downloadSingleUnfilteredRecording();
|
|
514
548
|
this.downloadSingleFilteredRecording();
|
|
@@ -32,6 +32,33 @@ class Volume extends AudioCalibrator {
|
|
|
32
32
|
/** @private */
|
|
33
33
|
TAPER_SECS = 0.010; // seconds
|
|
34
34
|
|
|
35
|
+
/** @private */
|
|
36
|
+
status_denominator = 2;
|
|
37
|
+
|
|
38
|
+
/** @private */
|
|
39
|
+
status_numerator = 0;
|
|
40
|
+
|
|
41
|
+
/** @private */
|
|
42
|
+
percent_complete = 0;
|
|
43
|
+
|
|
44
|
+
/** @private */
|
|
45
|
+
status = ``;
|
|
46
|
+
|
|
47
|
+
/**generate string template that gets reevaluated as variable increases */
|
|
48
|
+
generateTemplate = () => {
|
|
49
|
+
if (this.percent_complete > 100){
|
|
50
|
+
this.percent_complete = 100;
|
|
51
|
+
}
|
|
52
|
+
const template = `<div style="display: flex; justify-content: center;"><div style="width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;"><div style="width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;"></div></div></div>`;
|
|
53
|
+
return template;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** increment numerator and percent for status bar */
|
|
57
|
+
incrementStatusBar = () => {
|
|
58
|
+
this.status_numerator += 1;
|
|
59
|
+
this.percent_complete = (this.status_numerator/this.status_denominator)*100;
|
|
60
|
+
}
|
|
61
|
+
|
|
35
62
|
handleIncomingData = data => {
|
|
36
63
|
console.log('Received data: ', data);
|
|
37
64
|
if (data.type === 'soundGainDBSPL') {
|
|
@@ -152,6 +179,7 @@ class Volume extends AudioCalibrator {
|
|
|
152
179
|
})
|
|
153
180
|
.then(res => {
|
|
154
181
|
if (this.outDBSPL === null) {
|
|
182
|
+
this.incrementStatusBar();
|
|
155
183
|
this.outDBSPL = res['outDbSPL'];
|
|
156
184
|
this.outDBSPL1000 = res['outDbSPL1000'];
|
|
157
185
|
this.THD = res['thd'];
|
|
@@ -164,6 +192,7 @@ class Volume extends AudioCalibrator {
|
|
|
164
192
|
|
|
165
193
|
startCalibration = async (stream, gainValues, lCalib = 104.92978421490648) => {
|
|
166
194
|
const trialIterations = gainValues.length;
|
|
195
|
+
this.status_denominator += trialIterations;
|
|
167
196
|
const thdValues = [];
|
|
168
197
|
const inDBValues = [];
|
|
169
198
|
let inDB = 0;
|
|
@@ -173,7 +202,8 @@ class Volume extends AudioCalibrator {
|
|
|
173
202
|
// do one calibration that will be discarded
|
|
174
203
|
const soundLevelToDiscard = -60;
|
|
175
204
|
const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);
|
|
176
|
-
this.
|
|
205
|
+
this.status = `Sound Level: ${soundLevelToDiscard} dB`.toString() + this.generateTemplate().toString();
|
|
206
|
+
this.emit('update', {message: this.status});
|
|
177
207
|
do {
|
|
178
208
|
// eslint-disable-next-line no-await-in-loop
|
|
179
209
|
await this.volumeCalibrationSteps(
|
|
@@ -198,7 +228,8 @@ class Volume extends AudioCalibrator {
|
|
|
198
228
|
// precision to 1 decimal place
|
|
199
229
|
inDB = Math.round(inDB * 10) / 10;
|
|
200
230
|
inDBValues.push(inDB);
|
|
201
|
-
this.
|
|
231
|
+
this.status = `Sound Level: ${inDB} dB`.toString() + this.generateTemplate().toString();
|
|
232
|
+
this.emit('update', {message: this.status});
|
|
202
233
|
do {
|
|
203
234
|
// eslint-disable-next-line no-await-in-loop
|
|
204
235
|
await this.volumeCalibrationSteps(
|
|
@@ -220,6 +251,7 @@ class Volume extends AudioCalibrator {
|
|
|
220
251
|
}
|
|
221
252
|
|
|
222
253
|
// get the volume calibration parameters from the server
|
|
254
|
+
|
|
223
255
|
const parameters = await this.pyServerAPI
|
|
224
256
|
.getVolumeCalibrationParameters({
|
|
225
257
|
inDBValues: inDBValues,
|
|
@@ -227,6 +259,9 @@ class Volume extends AudioCalibrator {
|
|
|
227
259
|
lCalib: lCalib,
|
|
228
260
|
})
|
|
229
261
|
.then(res => {
|
|
262
|
+
this.incrementStatusBar();
|
|
263
|
+
this.status = `done with 1000 Hz calibration`.toString() + this.generateTemplate().toString();
|
|
264
|
+
this.emit('update', {message: this.status});
|
|
230
265
|
return res;
|
|
231
266
|
});
|
|
232
267
|
const result = {
|