speaker-calibration 2.1.16 → 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
@@ -48,10 +48,16 @@
48
48
  </div>
49
49
  <div class="row">
50
50
  <div class="form-check form-switch">
51
- <input class="form-check-input" type="checkbox" id="flexSwitchCheckIR" checked />
51
+ <input class="form-check-input" type="checkbox" id="flexSwitchCheckIR" />
52
52
  <label class="form-check-label" for="flexSwitchCheckIR">Impulse Response</label>
53
53
  </div>
54
54
  </div>
55
+ <div class="row">
56
+ <div class="form-check form-switch">
57
+ <input class="form-check-input" type="checkbox" id="flexSwitchCheckCombo" checked />
58
+ <label class="form-check-label" for="flexSwitchCheckCombo">Combination</label>
59
+ </div>
60
+ </div>
55
61
  <div class="row">
56
62
  <div class="form-check form-switch">
57
63
  <input class="form-check-input" type="checkbox" id="flexSwitchCheckDownload" />
@@ -1,13 +1,14 @@
1
1
  window.onload = () => {
2
2
  const flexSwitchCheckIR = document.getElementById('flexSwitchCheckIR');
3
3
  const flexSwitchCheckVolume = document.getElementById('flexSwitchCheckVolume');
4
+ const flexSwitchCheckCombo = document.getElementById('flexSwitchCheckCombo');
4
5
  const previousCaptureCSV = document.getElementById('previous-capture-csv');
5
6
  const iirCSV = document.getElementById('iir-csv');
6
7
  const playAndRecord = document.getElementById('flexSwitchPlayAndRecord');
7
8
  const sendToServerButton = document.getElementById('sendToServerButton');
8
9
  const wavFile = document.getElementById('wav-file');
9
10
 
10
- const {Speaker, VolumeCalibration, ImpulseResponseCalibration} = speakerCalibrator;
11
+ const {Speaker, VolumeCalibration, ImpulseResponseCalibration, CombinationCalibration} = speakerCalibrator;
11
12
 
12
13
  const normalize = (min, max) => {
13
14
  var delta = max - min;
@@ -147,10 +148,17 @@ window.onload = () => {
147
148
 
148
149
  flexSwitchCheckIR.onchange = () => {
149
150
  flexSwitchCheckVolume.checked = !flexSwitchCheckIR.checked;
151
+ flexSwitchCheckCombo.checked = !flexSwitchCheckIR.checked
150
152
  };
151
153
 
152
154
  flexSwitchCheckVolume.onchange = () => {
153
155
  flexSwitchCheckIR.checked = !flexSwitchCheckVolume.checked;
156
+ flexSwitchCheckCombo.checked = !flexSwitchCheckVolume.checked;
157
+ };
158
+
159
+ flexSwitchCheckCombo.onchange = () => {
160
+ flexSwitchCheckIR.checked = !flexSwitchCheckCombo.checked;
161
+ flexSwitchCheckVolume.checked = !flexSwitchCheckCombo.checked;
154
162
  };
155
163
 
156
164
  const useSpeakerCalibrator = async (calibrationLevel = 0, iir = null) => {
@@ -202,6 +210,33 @@ window.onload = () => {
202
210
  }
203
211
  };
204
212
 
213
+ const runCombinationCalibration = async calibrationLevel => {
214
+ const calibratorParams = {
215
+ numCaptures: document.getElementById('numCapturesInput').value,
216
+ numMLSPerCapture: document.getElementById('numMLSPerCaptureInput').value,
217
+ mlsOrder: document.getElementById('mlsOrder').value,
218
+ download: document.getElementById('flexSwitchCheckDownload').checked,
219
+ };
220
+
221
+ const calibrator = new CombinationCalibration(calibratorParams);
222
+
223
+ calibrator.on('update', ({message, ...rest}) => {
224
+ updateTarget.innerHTML = message;
225
+ });
226
+
227
+ try {
228
+ if (calibrationLevel == 0) {
229
+ invertedIR = await Speaker.startCalibration(speakerParameters, calibrator);
230
+ console.log({invertedIR});
231
+ await useIRResult(invertedIR);
232
+ } else {
233
+ await Speaker.testIIR(speakerParameters, calibrator, iir);
234
+ }
235
+ } catch (err) {
236
+ calibrationResult.innerText = `${err.name}: ${err.message}`;
237
+ }
238
+ };
239
+
205
240
  const runVolumeCalibration = async () => {
206
241
  const calibrator = new VolumeCalibration({});
207
242
  calibrator.on('update', ({message, ...rest}) => {
@@ -218,8 +253,10 @@ window.onload = () => {
218
253
 
219
254
  if (flexSwitchCheckIR.checked) {
220
255
  runImpulseResponseCalibration(calibrationLevel);
221
- } else {
256
+ } else if (flexSwitchCheckVolume.checked){
222
257
  runVolumeCalibration();
258
+ } else {
259
+ runCombinationCalibration(calibrationLevel);
223
260
  }
224
261
  };
225
262
 
package/dist/main.js CHANGED
@@ -697,7 +697,7 @@ eval("function hex2rgba (hex) {\n if (typeof hex === 'number') {\n hex = hex
697
697
  /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
698
698
 
699
699
  "use strict";
700
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"Listener\": function() { return /* reexport safe */ _peer_connection_listener__WEBPACK_IMPORTED_MODULE_0__[\"default\"]; },\n/* harmony export */ \"Speaker\": function() { return /* reexport safe */ _peer_connection_speaker__WEBPACK_IMPORTED_MODULE_1__[\"default\"]; },\n/* harmony export */ \"VolumeCalibration\": function() { return /* reexport safe */ _tasks_volume_volume__WEBPACK_IMPORTED_MODULE_2__[\"default\"]; },\n/* harmony export */ \"ImpulseResponseCalibration\": function() { return /* reexport safe */ _tasks_impulse_response_impulseResponse__WEBPACK_IMPORTED_MODULE_3__[\"default\"]; },\n/* harmony export */ \"UnsupportedDeviceError\": function() { return /* reexport safe */ _peer_connection_peerErrors__WEBPACK_IMPORTED_MODULE_4__.UnsupportedDeviceError; },\n/* harmony export */ \"MissingSpeakerIdError\": function() { return /* reexport safe */ _peer_connection_peerErrors__WEBPACK_IMPORTED_MODULE_4__.MissingSpeakerIdError; },\n/* harmony export */ \"CalibrationTimedOutError\": function() { return /* reexport safe */ _peer_connection_peerErrors__WEBPACK_IMPORTED_MODULE_4__.CalibrationTimedOutError; }\n/* harmony export */ });\n/* harmony import */ var _peer_connection_listener__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./peer-connection/listener */ \"./src/peer-connection/listener.js\");\n/* harmony import */ var _peer_connection_speaker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./peer-connection/speaker */ \"./src/peer-connection/speaker.js\");\n/* harmony import */ var _tasks_volume_volume__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tasks/volume/volume */ \"./src/tasks/volume/volume.js\");\n/* harmony import */ var _tasks_impulse_response_impulseResponse__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./tasks/impulse-response/impulseResponse */ \"./src/tasks/impulse-response/impulseResponse.js\");\n/* harmony import */ var _peer_connection_peerErrors__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./peer-connection/peerErrors */ \"./src/peer-connection/peerErrors.js\");\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/main.js?");
700
+ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"Listener\": function() { return /* reexport safe */ _peer_connection_listener__WEBPACK_IMPORTED_MODULE_0__[\"default\"]; },\n/* harmony export */ \"Speaker\": function() { return /* reexport safe */ _peer_connection_speaker__WEBPACK_IMPORTED_MODULE_1__[\"default\"]; },\n/* harmony export */ \"VolumeCalibration\": function() { return /* reexport safe */ _tasks_volume_volume__WEBPACK_IMPORTED_MODULE_2__[\"default\"]; },\n/* harmony export */ \"ImpulseResponseCalibration\": function() { return /* reexport safe */ _tasks_impulse_response_impulseResponse__WEBPACK_IMPORTED_MODULE_3__[\"default\"]; },\n/* harmony export */ \"UnsupportedDeviceError\": function() { return /* reexport safe */ _peer_connection_peerErrors__WEBPACK_IMPORTED_MODULE_5__.UnsupportedDeviceError; },\n/* harmony export */ \"MissingSpeakerIdError\": function() { return /* reexport safe */ _peer_connection_peerErrors__WEBPACK_IMPORTED_MODULE_5__.MissingSpeakerIdError; },\n/* harmony export */ \"CalibrationTimedOutError\": function() { return /* reexport safe */ _peer_connection_peerErrors__WEBPACK_IMPORTED_MODULE_5__.CalibrationTimedOutError; },\n/* harmony export */ \"CombinationCalibration\": function() { return /* reexport safe */ _tasks_combination_combination__WEBPACK_IMPORTED_MODULE_4__[\"default\"]; }\n/* harmony export */ });\n/* harmony import */ var _peer_connection_listener__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./peer-connection/listener */ \"./src/peer-connection/listener.js\");\n/* harmony import */ var _peer_connection_speaker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./peer-connection/speaker */ \"./src/peer-connection/speaker.js\");\n/* harmony import */ var _tasks_volume_volume__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tasks/volume/volume */ \"./src/tasks/volume/volume.js\");\n/* harmony import */ var _tasks_impulse_response_impulseResponse__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./tasks/impulse-response/impulseResponse */ \"./src/tasks/impulse-response/impulseResponse.js\");\n/* harmony import */ var _tasks_combination_combination__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./tasks/combination/combination */ \"./src/tasks/combination/combination.js\");\n/* harmony import */ var _peer_connection_peerErrors__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./peer-connection/peerErrors */ \"./src/peer-connection/peerErrors.js\");\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/main.js?");
701
701
 
702
702
  /***/ }),
703
703
 
@@ -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
 
@@ -789,6 +789,28 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _myE
789
789
 
790
790
  /***/ }),
791
791
 
792
+ /***/ "./src/tasks/combination/combination.js":
793
+ /*!**********************************************!*\
794
+ !*** ./src/tasks/combination/combination.js ***!
795
+ \**********************************************/
796
+ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
797
+
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 /** @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
+
801
+ /***/ }),
802
+
803
+ /***/ "./src/tasks/combination/mlsGen/mlsGenInterface.js":
804
+ /*!*********************************************************!*\
805
+ !*** ./src/tasks/combination/mlsGen/mlsGenInterface.js ***!
806
+ \*********************************************************/
807
+ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
808
+
809
+ "use strict";
810
+ eval("__webpack_require__.r(__webpack_exports__);\n/* eslint-disable prefer-destructuring */\r\n/* eslint-disable dot-notation */\r\n// eslint-disable-next-line import/extensions\r\nconst createMLSGenModule = __webpack_require__(/*! ../../../../dist/mlsGen.js */ \"./dist/mlsGen.js\");\r\n\r\n/**\r\n * MLSGenInterface provides a class for interfacing with the MLSGen WASM module.\r\n */\r\nclass MlsGenInterface {\r\n /** @private */\r\n #mlsOrder;\r\n\r\n /** @private */\r\n #WASMInstance; // the WASM module instance\r\n\r\n /** @private */\r\n #MLSGenInstance; // the MLSGen object instance\r\n\r\n /**\r\n * Creates an instance of MlsGenInterface.\r\n * Makes a call to the WASM glue code to load the WASM module.\r\n *\r\n * @param WASMInstance\r\n * @param mlsOrder\r\n * @param sourceSamplingRate\r\n * @param sinkSamplingRate\r\n * @example\r\n */\r\n constructor(WASMInstance, mlsOrder, sourceSamplingRate, sinkSamplingRate) {\r\n this.#mlsOrder = mlsOrder;\r\n this.#WASMInstance = WASMInstance;\r\n\r\n console.warn('initializing MLSGen, need to manually garbage collect');\r\n this.#MLSGenInstance = new this.#WASMInstance['MLSGen'](\r\n mlsOrder,\r\n sourceSamplingRate,\r\n sinkSamplingRate\r\n );\r\n }\r\n\r\n /**\r\n * Factory function that provide an asynchronous function that fetches the WASM module\r\n * and returns a promise that resolves when the module is loaded.\r\n *\r\n * @param mlsOrder\r\n * @param sourceSamplingRate - The sampling rate of the source audio.\r\n * @param sinkSamplingRate - The sampling rate of the sink audio.\r\n * @returns MlsGenInterface.\r\n * @example\r\n */\r\n static factory = async (mlsOrder, sourceSamplingRate, sinkSamplingRate) => {\r\n if (sourceSamplingRate === undefined || sinkSamplingRate === undefined) {\r\n throw new Error('sourceSamplingRate and sinkSamplingRate must be defined');\r\n }\r\n return new MlsGenInterface(\r\n await createMLSGenModule().then(instance => instance),\r\n mlsOrder,\r\n sourceSamplingRate,\r\n sinkSamplingRate\r\n );\r\n };\r\n\r\n /**\r\n * A Higher-Order function that takes an async callback function that access the MLSGen object,\r\n * providing safe garbage collection.\r\n *\r\n * @param func\r\n * @param args\r\n * @param funcsWithParams\r\n * @example\r\n */\r\n withGarbageCollection = async funcsWithParams => {\r\n try {\r\n for (let i = 0; i < funcsWithParams.length; i += 1) {\r\n const funcWithParams = funcsWithParams[i];\r\n // eslint-disable-next-line no-await-in-loop\r\n await funcWithParams();\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n } finally {\r\n // garbage collect\r\n if (\r\n this !== undefined &&\r\n this !== null &&\r\n this.#MLSGenInstance !== undefined &&\r\n this.#MLSGenInstance !== null\r\n ) {\r\n this.#MLSGenInstance['Destruct'](); // Call the destructor\r\n this.#MLSGenInstance['delete'](); // Delete the object\r\n console.warn(`GARBAGE COLLECTION: deleted MLSGen`);\r\n this.#WASMInstance['doLeakCheck'](); // Check for memory leaks\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Calculate and return the Impulse Response of the recorded signal.\r\n *\r\n * @returns\r\n * @example\r\n */\r\n getImpulseResponse = () => this.#MLSGenInstance['getImpulseResponse']();\r\n\r\n /**\r\n * Given a recorded MLS signal, this function sets the recordedSignal property of the MLSGen object.\r\n *\r\n * @param signals\r\n * @example\r\n */\r\n setRecordedSignals = signals => {\r\n // get memory view\r\n const averagedSignals = this.average(signals);\r\n const recordedSignalsMemoryView = this.#MLSGenInstance['setRecordedSignalsMemoryView'](\r\n averagedSignals.byteLength\r\n );\r\n for (let i = 0; i < averagedSignals.length; i += 1) {\r\n recordedSignalsMemoryView[i] = averagedSignals[i];\r\n }\r\n };\r\n\r\n /**\r\n * Calculate the Maximum Length Sequence (MLS) with period P = 2^N - 1\r\n * using the MLSGen WASM module.\r\n *\r\n * @example\r\n */\r\n getMLS = () => this.#MLSGenInstance['getMLS']();\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (MlsGenInterface);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/combination/mlsGen/mlsGenInterface.js?");
811
+
812
+ /***/ }),
813
+
792
814
  /***/ "./src/tasks/impulse-response/impulseResponse.js":
793
815
  /*!*******************************************************!*\
794
816
  !*** ./src/tasks/impulse-response/impulseResponse.js ***!
@@ -796,7 +818,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _myE
796
818
  /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
797
819
 
798
820
  "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/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?");
800
822
 
801
823
  /***/ }),
802
824
 
@@ -818,7 +840,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* eslint-disable prefer-dest
818
840
  /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
819
841
 
820
842
  "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 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?");
822
844
 
823
845
  /***/ }),
824
846
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speaker-calibration",
3
- "version": "2.1.16",
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.6.2",
35
+ "np": "^7.7.0",
36
36
  "path-browserify": "^1.0.1",
37
37
  "peerjs": "^1.3.2",
38
38
  "qrcode": "^1.4.4",
package/src/main.js CHANGED
@@ -3,6 +3,7 @@ import Speaker from './peer-connection/speaker';
3
3
 
4
4
  import VolumeCalibration from './tasks/volume/volume';
5
5
  import ImpulseResponseCalibration from './tasks/impulse-response/impulseResponse';
6
+ import CombinationCalibration from './tasks/combination/combination';
6
7
 
7
8
  import {
8
9
  UnsupportedDeviceError,
@@ -18,4 +19,5 @@ export {
18
19
  UnsupportedDeviceError,
19
20
  MissingSpeakerIdError,
20
21
  CalibrationTimedOutError,
22
+ CombinationCalibration,
21
23
  };
@@ -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;