speaker-calibration 2.1.25 → 2.1.26

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.
@@ -31,24 +31,7 @@
31
31
  <![endif]-->
32
32
  <div class="container">
33
33
  <h1>Sound Calibration</h1>
34
- <p class="lead" id="message">
35
- We will now calibrate your computer's loudspeaker to enable accurate sound production.
36
-
37
- <br /><br />REMOVE HEADPHONES. It won't work with headphones. If you're using headphones,
38
- disconnect them now so your computer uses its loudspeaker.
39
-
40
- <br /><br />UNBLOCK THE MICROPHONE. When this phone is held in its usual vertical
41
- orientation, the microphone is in the BOTTOM edge. For accurate sound calibration, make sure
42
- nothing blocks the microphone. Remove any protective case.
43
-
44
- <br /><br />LAY IT ON THE TABLE, AIMING MICROPHONE AT COMPUTER. First, rotate this phone to
45
- landscape orientation, so display is short and wide. This text will rotate to remain
46
- upright. Now place the phone on the table to the right or left of your computer, aiming the
47
- microphone at your computer.
48
-
49
- <br /><br />WHEN READY, CLICK OK. Then a pop up will appear here asking for your permission
50
- to use the microphone. When you grant permission the calibration will begin.
51
- </p>
34
+ <p class="lead" id="message"></p>
52
35
  <div>
53
36
  <div class="col-6">
54
37
  <button id="calibrationBeginButton" type="button" class="btn btn-primary">Ok</button>
@@ -58,6 +41,7 @@
58
41
  </div>
59
42
  </div>
60
43
  </div>
44
+ <script type="module" src="./listener.js"></script>
61
45
  <script type="text/javascript">
62
46
  const listenerParameters = {
63
47
  targetElementId: 'display',
@@ -69,6 +53,16 @@
69
53
  document.querySelector('#message').innerText =
70
54
  'Calibration in progress, please hold your phone steady.';
71
55
  };
56
+ // enter key press
57
+ document.addEventListener('keydown', event => {
58
+ if (event.key === 'Enter') {
59
+ window.listener = new speakerCalibrator.Listener(listenerParameters);
60
+ console.log(window.listener);
61
+ document.querySelector('#calibrationBeginButton').style.display = 'none';
62
+ document.querySelector('#message').innerText =
63
+ 'Calibration in progress, please hold your phone steady.';
64
+ }
65
+ });
72
66
  </script>
73
67
  <script
74
68
  src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
@@ -0,0 +1,25 @@
1
+ // get element with id message
2
+ import {phrases} from './i18n.js';
3
+ const message = document.getElementById('message');
4
+ message.style.lineHeight = '2.5rem';
5
+ // get url query parameters
6
+ const urlParams = new URLSearchParams(window.location.search);
7
+
8
+ // get isSmartPhone query parameter
9
+ const isSmartPhone = urlParams.get('isSmartPhone');
10
+
11
+ switch (isSmartPhone) {
12
+ case 'true':
13
+ message.innerHTML =
14
+ `${phrases.RC_removeHeadphones['en-US']} ${phrases.RC_getPhoneMicrophoneReady['en-US']}`.replace(
15
+ /\n/g,
16
+ '<br>'
17
+ );
18
+ break;
19
+ case 'false':
20
+ message.innerHTML =
21
+ `${phrases.RC_removeHeadphones['en-US']} ${phrases.RC_getUSBMicrophoneReady['en-US']}`.replace(
22
+ /\n/g,
23
+ '<br>'
24
+ );
25
+ }
package/dist/main.js CHANGED
@@ -785,7 +785,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
785
785
  /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
786
786
 
787
787
  "use strict";
788
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var qrcode__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! qrcode */ \"./node_modules/qrcode/lib/browser.js\");\n/* harmony import */ var _audioPeer__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./audioPeer */ \"./src/peer-connection/audioPeer.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils */ \"./src/utils.js\");\n/* harmony import */ var _peerErrors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./peerErrors */ \"./src/peer-connection/peerErrors.js\");\n/* harmony import */ var _config_firebase__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../config/firebase */ \"./src/config/firebase.js\");\n/* harmony import */ var firebase_database__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! firebase/database */ \"./node_modules/firebase/database/dist/esm/index.esm.js\");\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n/**\r\n * @class Handles the speaker's side of the connection. Responsible for initiating the connection,\r\n * rendering the QRCode, and answering the call.\r\n * @augments AudioPeer\r\n */\r\nclass Speaker extends _audioPeer__WEBPACK_IMPORTED_MODULE_1__[\"default\"] {\r\n /**\r\n * Takes the url of the current site and a target element where html elements will be appended.\r\n *\r\n * @param params - See type definition for initParameters.\r\n * @param Calibrator - An instance of the AudioCalibrator class, should not use AudioCalibrator directly, instead use an extended class available in /tasks/.\r\n * @param CalibratorInstance\r\n * @example\r\n */\r\n constructor(params, CalibratorInstance) {\r\n super(params);\r\n this.siteUrl += '/listener?';\r\n this.ac = CalibratorInstance;\r\n this.result = null;\r\n this.debug = params?.debug ?? false;\r\n this.isSmartPhone = params?.isSmartPhone ?? false;\r\n\r\n /* Set up callbacks that handle any events related to our peer object. */\r\n this.peer.on('open', this.#onPeerOpen);\r\n this.peer.on('connection', this.#onPeerConnection);\r\n this.peer.on('close', this.#onPeerClose);\r\n this.peer.on('disconnected', this.#onPeerDisconnected);\r\n this.peer.on('error', this.#onPeerError);\r\n }\r\n\r\n static getMicrophoneNamesFromDatabase = async () => {\r\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_5__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_4__[\"default\"]);\r\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_5__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_5__.child)(dbRef, 'Microphone'));\r\n if (snapshot.exists()) {\r\n return Object.keys(snapshot.val());\r\n }\r\n return null;\r\n };\r\n\r\n /**\r\n * Async factory method that creates the Speaker object, and returns a promise that resolves to the result of the calibration.\r\n *\r\n * @param params - The parameters to be passed to the peer object.\r\n * @param Calibrator - The class that defines the calibration process.\r\n * @param CalibratorInstance\r\n * @param timeOut - The amount of time to wait before timing out the connection (in milliseconds).\r\n * @public\r\n * @example\r\n */\r\n static startCalibration = async (params, CalibratorInstance, timeOut = 180000) => {\r\n window.speaker = new Speaker(params, CalibratorInstance);\r\n const {speaker} = window;\r\n\r\n // wrap the calibration process in a promise so we can await it\r\n return new Promise((resolve, reject) => {\r\n // when a call is received\r\n speaker.peer.on('call', async call => {\r\n // Answer the call (one way)\r\n call.answer();\r\n speaker.#removeUIElems();\r\n speaker.#showSpinner();\r\n speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));\r\n // when we start receiving audio\r\n call.on('stream', async stream => {\r\n window.localStream = stream;\r\n window.localAudio.srcObject = stream;\r\n window.localAudio.autoplay = false;\r\n\r\n // if the sinkSamplingRate is not set sleep\r\n while (!speaker.ac.sampleRatesSet()) {\r\n console.log('SinkSamplingRate is undefined, sleeping');\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1);\r\n }\r\n // resolve when we have a result\r\n speaker.result = await speaker.ac.startCalibration(\r\n stream,\r\n params.gainValues,\r\n params.ICalib,\r\n params.knownIR,\r\n params.microphoneName,\r\n params.calibrateSoundCheck\r\n );\r\n speaker.#removeUIElems();\r\n resolve(speaker.result);\r\n });\r\n // if we do not receive a result within the timeout, reject\r\n setTimeout(() => {\r\n reject(\r\n new _peerErrors__WEBPACK_IMPORTED_MODULE_3__.CalibrationTimedOutError(\r\n `Calibration failed to produce a result after ${\r\n timeOut / 1000\r\n } seconds. Please try again.`\r\n )\r\n );\r\n }, timeOut);\r\n });\r\n });\r\n };\r\n\r\n static testIIR = async (params, CalibratorInstance, IIR, timeOut = 180000) => {\r\n window.speaker = new Speaker(params, CalibratorInstance);\r\n const {speaker} = window;\r\n\r\n // wrap the calibration process in a promise so we can await it\r\n return new Promise((resolve, reject) => {\r\n // when a call is received\r\n speaker.peer.on('call', async call => {\r\n // Answer the call (one way)\r\n call.answer();\r\n speaker.#removeUIElems();\r\n speaker.#showSpinner();\r\n speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));\r\n // when we start receiving audio\r\n call.on('stream', async stream => {\r\n window.localStream = stream;\r\n window.localAudio.srcObject = stream;\r\n window.localAudio.autoplay = false;\r\n\r\n // if the sinkSamplingRate is not set sleep\r\n while (!speaker.ac.sampleRatesSet()) {\r\n console.log('SinkSamplingRate is undefined, sleeping');\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1);\r\n }\r\n // resolve when we have a result\r\n speaker.result = await speaker.ac.playMLSwithIIR(stream, IIR);\r\n speaker.#removeUIElems();\r\n resolve(speaker.result);\r\n });\r\n // if we do not receive a result within the timeout, reject\r\n setTimeout(() => {\r\n reject(\r\n new _peerErrors__WEBPACK_IMPORTED_MODULE_3__.CalibrationTimedOutError(\r\n `Calibration failed to produce a result after ${\r\n timeOut / 1000\r\n } seconds. Please try again.`\r\n )\r\n );\r\n }, timeOut);\r\n });\r\n });\r\n };\r\n\r\n /**\r\n * Called after the peer conncection has been opened.\r\n * Generates a QR code for the connection and displays it.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #showQRCode = () => {\r\n // Get query string, the URL parameters to specify a Listener\r\n const queryStringParameters = {\r\n speakerPeerId: this.peer.id,\r\n };\r\n const queryString = this.queryStringFromObject(queryStringParameters);\r\n const uri = this.siteUrl + queryString;\r\n\r\n if (this.isSmartPhone) {\r\n // Display QR code for the participant to scan\r\n const qrCanvas = document.createElement('canvas');\r\n qrCanvas.setAttribute('id', 'qrCanvas');\r\n console.log(uri);\r\n qrcode__WEBPACK_IMPORTED_MODULE_0__.toCanvas(qrCanvas, uri, error => {\r\n if (error) console.error(error);\r\n });\r\n document.getElementById(this.targetElement).appendChild(qrCanvas);\r\n } else {\r\n // show the link to the user\r\n // If specified HTML Id is available, show QR code there\r\n if (document.getElementById(this.targetElement)) {\r\n const linkTag = document.createElement('a');\r\n linkTag.setAttribute('href', uri);\r\n linkTag.innerHTML = 'Click here to start calibration';\r\n linkTag.target = '_blank';\r\n document.getElementById(this.targetElement).appendChild(linkTag);\r\n // document.getElementById(this.targetElement).appendChild(qrCanvas);\r\n }\r\n }\r\n // or just print it to console\r\n console.log('TEST: Peer reachable at: ', uri);\r\n };\r\n\r\n #showSpinner = () => {\r\n const spinner = document.createElement('div');\r\n spinner.className = 'spinner-border ml-auto';\r\n spinner.role = 'status';\r\n spinner.ariaHidden = 'true';\r\n document.getElementById(this.targetElement).appendChild(spinner);\r\n };\r\n\r\n #removeUIElems = () => {\r\n const parent = document.getElementById(this.targetElement);\r\n while (parent.firstChild) {\r\n parent.firstChild.remove();\r\n }\r\n };\r\n\r\n /**\r\n * Called when the peer connection is opened.\r\n * Saves the peer id and calls the QR code generator.\r\n *\r\n * @param peerId - The peer id of the peer connection.\r\n * @param id\r\n * @private\r\n * @example\r\n */\r\n #onPeerOpen = id => {\r\n // Workaround for peer.reconnect deleting previous id\r\n if (id === null) {\r\n console.error('Received null id from peer open');\r\n this.peer.id = this.lastPeerId;\r\n } else {\r\n this.lastPeerId = this.peer.id;\r\n }\r\n\r\n if (id !== this.peer.id) {\r\n console.warn('DEBUG Check you assumption that id === this.peer.id');\r\n }\r\n\r\n this.#showQRCode();\r\n };\r\n\r\n /**\r\n * Called when the peer connection is established.\r\n * Enforces a single connection.\r\n *\r\n * @param connection - The connection object.\r\n * @private\r\n * @example\r\n */\r\n #onPeerConnection = connection => {\r\n // Allow only a single connection\r\n if (this.conn && this.conn.open) {\r\n connection.on('open', () => {\r\n connection.send('Already connected to another client');\r\n setTimeout(() => {\r\n connection.close();\r\n }, 500);\r\n });\r\n return;\r\n }\r\n\r\n this.conn = connection;\r\n console.log('Connected to: ', this.conn.peer);\r\n this.#ready();\r\n };\r\n\r\n /**\r\n * Called when the peer connection is closed.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #onPeerClose = () => {\r\n this.conn = null;\r\n console.log('Connection destroyed');\r\n };\r\n\r\n /**\r\n * Called when the peer connection is disconnected.\r\n * Attempts to reconnect.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #onPeerDisconnected = () => {\r\n console.log('Connection lost. Please reconnect');\r\n\r\n // Workaround for peer.reconnect deleting previous id\r\n this.peer.id = this.lastPeerId;\r\n // eslint-disable-next-line no-underscore-dangle\r\n this.peer._lastServerId = this.lastPeerId;\r\n this.peer.reconnect();\r\n };\r\n\r\n /**\r\n * Called when the peer connection encounters an error.\r\n *\r\n * @param error\r\n * @private\r\n * @example\r\n */\r\n #onPeerError = error => {\r\n // TODO: check if this function is needed or not\r\n console.error(error);\r\n };\r\n\r\n /**\r\n * Called when data is received from the peer connection.\r\n *\r\n * @param data\r\n * @private\r\n * @example\r\n */\r\n #onIncomingData = data => {\r\n // enforce object type\r\n if (\r\n !Object.prototype.hasOwnProperty.call(data, 'name') ||\r\n !Object.prototype.hasOwnProperty.call(data, 'payload')\r\n ) {\r\n console.error('Received malformed data: ', data);\r\n return;\r\n }\r\n\r\n switch (data.name) {\r\n case 'samplingRate':\r\n this.ac.setSamplingRates(data.payload);\r\n break;\r\n case 'deviceType':\r\n this.ac.setDeviceType(data.payload);\r\n break;\r\n case 'deviceName':\r\n this.ac.setDeviceName(data.payload);\r\n break;\r\n case _peerErrors__WEBPACK_IMPORTED_MODULE_3__.UnsupportedDeviceError.name:\r\n case _peerErrors__WEBPACK_IMPORTED_MODULE_3__.MissingSpeakerIdError.name:\r\n throw data.payload;\r\n break;\r\n default:\r\n break;\r\n }\r\n };\r\n\r\n /**\r\n * Called when the peer connection is #ready.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #ready = () => {\r\n // Perform callback with data\r\n this.conn.on('data', this.#onIncomingData);\r\n this.conn.on('close', () => {\r\n console.log('Connection reset<br>Awaiting connection...');\r\n this.conn = null;\r\n });\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Debug method for downloading the recorded audio\r\n *\r\n * @public\r\n * @example\r\n */\r\n downloadData = () => {\r\n this.ac.downloadData();\r\n };\r\n}\r\n\r\n/* \r\nReferenced links:\r\nhttps://stackoverflow.com/questions/28016664/when-you-pass-this-as-an-argument/28016676#28016676\r\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes\r\nhttps://stackoverflow.com/questions/879152/how-do-i-make-javascript-beep [3]\r\n*/\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (Speaker);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/peer-connection/speaker.js?");
788
+ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var qrcode__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! qrcode */ \"./node_modules/qrcode/lib/browser.js\");\n/* harmony import */ var _audioPeer__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./audioPeer */ \"./src/peer-connection/audioPeer.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils */ \"./src/utils.js\");\n/* harmony import */ var _peerErrors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./peerErrors */ \"./src/peer-connection/peerErrors.js\");\n/* harmony import */ var _config_firebase__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../config/firebase */ \"./src/config/firebase.js\");\n/* harmony import */ var firebase_database__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! firebase/database */ \"./node_modules/firebase/database/dist/esm/index.esm.js\");\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n/**\r\n * @class Handles the speaker's side of the connection. Responsible for initiating the connection,\r\n * rendering the QRCode, and answering the call.\r\n * @augments AudioPeer\r\n */\r\nclass Speaker extends _audioPeer__WEBPACK_IMPORTED_MODULE_1__[\"default\"] {\r\n /**\r\n * Takes the url of the current site and a target element where html elements will be appended.\r\n *\r\n * @param params - See type definition for initParameters.\r\n * @param Calibrator - An instance of the AudioCalibrator class, should not use AudioCalibrator directly, instead use an extended class available in /tasks/.\r\n * @param CalibratorInstance\r\n * @example\r\n */\r\n constructor(params, CalibratorInstance) {\r\n super(params);\r\n this.siteUrl += '/listener?';\r\n this.ac = CalibratorInstance;\r\n this.result = null;\r\n this.debug = params?.debug ?? false;\r\n this.isSmartPhone = params?.isSmartPhone ?? false;\r\n\r\n /* Set up callbacks that handle any events related to our peer object. */\r\n this.peer.on('open', this.#onPeerOpen);\r\n this.peer.on('connection', this.#onPeerConnection);\r\n this.peer.on('close', this.#onPeerClose);\r\n this.peer.on('disconnected', this.#onPeerDisconnected);\r\n this.peer.on('error', this.#onPeerError);\r\n }\r\n\r\n static getMicrophoneNamesFromDatabase = async () => {\r\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_5__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_4__[\"default\"]);\r\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_5__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_5__.child)(dbRef, 'Microphone'));\r\n if (snapshot.exists()) {\r\n return Object.keys(snapshot.val());\r\n }\r\n return null;\r\n };\r\n\r\n /**\r\n * Async factory method that creates the Speaker object, and returns a promise that resolves to the result of the calibration.\r\n *\r\n * @param params - The parameters to be passed to the peer object.\r\n * @param Calibrator - The class that defines the calibration process.\r\n * @param CalibratorInstance\r\n * @param timeOut - The amount of time to wait before timing out the connection (in milliseconds).\r\n * @public\r\n * @example\r\n */\r\n static startCalibration = async (params, CalibratorInstance, timeOut = 180000) => {\r\n window.speaker = new Speaker(params, CalibratorInstance);\r\n const {speaker} = window;\r\n\r\n // wrap the calibration process in a promise so we can await it\r\n return new Promise((resolve, reject) => {\r\n // when a call is received\r\n speaker.peer.on('call', async call => {\r\n // Answer the call (one way)\r\n call.answer();\r\n speaker.#removeUIElems();\r\n speaker.#showSpinner();\r\n speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));\r\n // when we start receiving audio\r\n call.on('stream', async stream => {\r\n window.localStream = stream;\r\n window.localAudio.srcObject = stream;\r\n window.localAudio.autoplay = false;\r\n\r\n // if the sinkSamplingRate is not set sleep\r\n while (!speaker.ac.sampleRatesSet()) {\r\n console.log('SinkSamplingRate is undefined, sleeping');\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1);\r\n }\r\n // resolve when we have a result\r\n speaker.result = await speaker.ac.startCalibration(\r\n stream,\r\n params.gainValues,\r\n params.ICalib,\r\n params.knownIR,\r\n params.microphoneName,\r\n params.calibrateSoundCheck,\r\n params.isSmartPhone\r\n );\r\n speaker.#removeUIElems();\r\n resolve(speaker.result);\r\n });\r\n // if we do not receive a result within the timeout, reject\r\n setTimeout(() => {\r\n reject(\r\n new _peerErrors__WEBPACK_IMPORTED_MODULE_3__.CalibrationTimedOutError(\r\n `Calibration failed to produce a result after ${\r\n timeOut / 1000\r\n } seconds. Please try again.`\r\n )\r\n );\r\n }, timeOut);\r\n });\r\n });\r\n };\r\n\r\n static testIIR = async (params, CalibratorInstance, IIR, timeOut = 180000) => {\r\n window.speaker = new Speaker(params, CalibratorInstance);\r\n const {speaker} = window;\r\n\r\n // wrap the calibration process in a promise so we can await it\r\n return new Promise((resolve, reject) => {\r\n // when a call is received\r\n speaker.peer.on('call', async call => {\r\n // Answer the call (one way)\r\n call.answer();\r\n speaker.#removeUIElems();\r\n speaker.#showSpinner();\r\n speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));\r\n // when we start receiving audio\r\n call.on('stream', async stream => {\r\n window.localStream = stream;\r\n window.localAudio.srcObject = stream;\r\n window.localAudio.autoplay = false;\r\n\r\n // if the sinkSamplingRate is not set sleep\r\n while (!speaker.ac.sampleRatesSet()) {\r\n console.log('SinkSamplingRate is undefined, sleeping');\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1);\r\n }\r\n // resolve when we have a result\r\n speaker.result = await speaker.ac.playMLSwithIIR(stream, IIR);\r\n speaker.#removeUIElems();\r\n resolve(speaker.result);\r\n });\r\n // if we do not receive a result within the timeout, reject\r\n setTimeout(() => {\r\n reject(\r\n new _peerErrors__WEBPACK_IMPORTED_MODULE_3__.CalibrationTimedOutError(\r\n `Calibration failed to produce a result after ${\r\n timeOut / 1000\r\n } seconds. Please try again.`\r\n )\r\n );\r\n }, timeOut);\r\n });\r\n });\r\n };\r\n\r\n /**\r\n * Called after the peer conncection has been opened.\r\n * Generates a QR code for the connection and displays it.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #showQRCode = () => {\r\n // Get query string, the URL parameters to specify a Listener\r\n const queryStringParameters = {\r\n speakerPeerId: this.peer.id,\r\n isSmartPhone: this.isSmartPhone,\r\n };\r\n const queryString = this.queryStringFromObject(queryStringParameters);\r\n const uri = this.siteUrl + queryString;\r\n\r\n if (this.isSmartPhone) {\r\n // Display QR code for the participant to scan\r\n const qrCanvas = document.createElement('canvas');\r\n qrCanvas.setAttribute('id', 'qrCanvas');\r\n console.log(uri);\r\n qrcode__WEBPACK_IMPORTED_MODULE_0__.toCanvas(qrCanvas, uri, error => {\r\n if (error) console.error(error);\r\n });\r\n document.getElementById(this.targetElement).appendChild(qrCanvas);\r\n } else {\r\n // show the link to the user\r\n // If specified HTML Id is available, show QR code there\r\n if (document.getElementById(this.targetElement)) {\r\n const linkTag = document.createElement('a');\r\n linkTag.setAttribute('href', uri);\r\n linkTag.innerHTML = 'Click here to start calibration';\r\n linkTag.target = '_blank';\r\n document.getElementById(this.targetElement).appendChild(linkTag);\r\n // document.getElementById(this.targetElement).appendChild(qrCanvas);\r\n }\r\n }\r\n // or just print it to console\r\n console.log('TEST: Peer reachable at: ', uri);\r\n };\r\n\r\n #showSpinner = () => {\r\n const spinner = document.createElement('div');\r\n spinner.className = 'spinner-border ml-auto';\r\n spinner.role = 'status';\r\n spinner.ariaHidden = 'true';\r\n document.getElementById(this.targetElement).appendChild(spinner);\r\n };\r\n\r\n #removeUIElems = () => {\r\n const parent = document.getElementById(this.targetElement);\r\n while (parent.firstChild) {\r\n parent.firstChild.remove();\r\n }\r\n };\r\n\r\n /**\r\n * Called when the peer connection is opened.\r\n * Saves the peer id and calls the QR code generator.\r\n *\r\n * @param peerId - The peer id of the peer connection.\r\n * @param id\r\n * @private\r\n * @example\r\n */\r\n #onPeerOpen = id => {\r\n // Workaround for peer.reconnect deleting previous id\r\n if (id === null) {\r\n console.error('Received null id from peer open');\r\n this.peer.id = this.lastPeerId;\r\n } else {\r\n this.lastPeerId = this.peer.id;\r\n }\r\n\r\n if (id !== this.peer.id) {\r\n console.warn('DEBUG Check you assumption that id === this.peer.id');\r\n }\r\n\r\n this.#showQRCode();\r\n };\r\n\r\n /**\r\n * Called when the peer connection is established.\r\n * Enforces a single connection.\r\n *\r\n * @param connection - The connection object.\r\n * @private\r\n * @example\r\n */\r\n #onPeerConnection = connection => {\r\n // Allow only a single connection\r\n if (this.conn && this.conn.open) {\r\n connection.on('open', () => {\r\n connection.send('Already connected to another client');\r\n setTimeout(() => {\r\n connection.close();\r\n }, 500);\r\n });\r\n return;\r\n }\r\n\r\n this.conn = connection;\r\n console.log('Connected to: ', this.conn.peer);\r\n this.#ready();\r\n };\r\n\r\n /**\r\n * Called when the peer connection is closed.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #onPeerClose = () => {\r\n this.conn = null;\r\n console.log('Connection destroyed');\r\n };\r\n\r\n /**\r\n * Called when the peer connection is disconnected.\r\n * Attempts to reconnect.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #onPeerDisconnected = () => {\r\n console.log('Connection lost. Please reconnect');\r\n\r\n // Workaround for peer.reconnect deleting previous id\r\n this.peer.id = this.lastPeerId;\r\n // eslint-disable-next-line no-underscore-dangle\r\n this.peer._lastServerId = this.lastPeerId;\r\n this.peer.reconnect();\r\n };\r\n\r\n /**\r\n * Called when the peer connection encounters an error.\r\n *\r\n * @param error\r\n * @private\r\n * @example\r\n */\r\n #onPeerError = error => {\r\n // TODO: check if this function is needed or not\r\n console.error(error);\r\n };\r\n\r\n /**\r\n * Called when data is received from the peer connection.\r\n *\r\n * @param data\r\n * @private\r\n * @example\r\n */\r\n #onIncomingData = data => {\r\n // enforce object type\r\n if (\r\n !Object.prototype.hasOwnProperty.call(data, 'name') ||\r\n !Object.prototype.hasOwnProperty.call(data, 'payload')\r\n ) {\r\n console.error('Received malformed data: ', data);\r\n return;\r\n }\r\n\r\n switch (data.name) {\r\n case 'samplingRate':\r\n this.ac.setSamplingRates(data.payload);\r\n break;\r\n case 'deviceType':\r\n this.ac.setDeviceType(data.payload);\r\n break;\r\n case 'deviceName':\r\n this.ac.setDeviceName(data.payload);\r\n break;\r\n case _peerErrors__WEBPACK_IMPORTED_MODULE_3__.UnsupportedDeviceError.name:\r\n case _peerErrors__WEBPACK_IMPORTED_MODULE_3__.MissingSpeakerIdError.name:\r\n throw data.payload;\r\n break;\r\n default:\r\n break;\r\n }\r\n };\r\n\r\n /**\r\n * Called when the peer connection is #ready.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #ready = () => {\r\n // Perform callback with data\r\n this.conn.on('data', this.#onIncomingData);\r\n this.conn.on('close', () => {\r\n console.log('Connection reset<br>Awaiting connection...');\r\n this.conn = null;\r\n });\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Debug method for downloading the recorded audio\r\n *\r\n * @public\r\n * @example\r\n */\r\n downloadData = () => {\r\n this.ac.downloadData();\r\n };\r\n}\r\n\r\n/* \r\nReferenced links:\r\nhttps://stackoverflow.com/questions/28016664/when-you-pass-this-as-an-argument/28016676#28016676\r\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes\r\nhttps://stackoverflow.com/questions/879152/how-do-i-make-javascript-beep [3]\r\n*/\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (Speaker);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/peer-connection/speaker.js?");
789
789
 
790
790
  /***/ }),
791
791
 
@@ -829,7 +829,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _myE
829
829
  /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
830
830
 
831
831
  "use strict";
832
- 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/* harmony import */ var _config_firebase__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../config/firebase */ \"./src/config/firebase.js\");\n/* harmony import */ var firebase_database__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! firebase/database */ \"./node_modules/firebase/database/dist/esm/index.esm.js\");\n\r\n\r\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({\r\n download = false,\r\n mlsOrder = 18,\r\n numCaptures = 3,\r\n numMLSPerCapture = 4,\r\n lowHz = 20,\r\n highHz = 10000,\r\n }) {\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 componentInvertedImpulseResponse = null;\r\n\r\n /** @private */\r\n systemInvertedImpulseResponse = null;\r\n\r\n //averaged and subtracted ir returned from calibration used to calculated iir\r\n /** @private */\r\n ir = 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 componentConvolution;\r\n\r\n /** @private */\r\n systemConvolution;\r\n\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.01; // 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 /**@private */\r\n componentIR = null;\r\n\r\n /**@private */\r\n systemIR = null;\r\n\r\n /**@private */\r\n _calibrateSoundCheck = '';\r\n\r\n deviceType = null;\r\n\r\n deviceName = null;\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 setDeviceType = deviceType => {\r\n this.deviceType = deviceType;\r\n };\r\n\r\n setDeviceName = deviceName => {\r\n this.deviceName = deviceName;\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 sendSystemImpulseResponsesToServerForProcessing = 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 componentIRGains = this.componentIR['Gain'];\r\n //const componentIRFreqs = this.componentIR['Freq'];\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 =\r\n `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 .getSystemInverseImpulseResponseWithRetry({\r\n payload: filteredComputedIRs.slice(0, this.numCaptures),\r\n mls,\r\n lowHz,\r\n highHz,\r\n sampleRate: this.sourceSamplingRate || 96000,\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 =\r\n `All Hz Calibration: done computing the IIR...`.toString() +\r\n this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n this.systemInvertedImpulseResponse = res['iir'];\r\n this.systemIR = res['ir']\r\n //this.componentIR['Gain'] = res['ir'];\r\n //this.componentIR['Freq'] = res['frequencies'];\r\n this.systemConvolution = 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 * .\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 sendComponentImpulseResponsesToServerForProcessing = 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 componentIRGains = this.componentIR['Gain'];\r\n const componentIRFreqs = this.componentIR['Freq'];\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 =\r\n `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 .getComponentInverseImpulseResponseWithRetry({\r\n payload: filteredComputedIRs.slice(0, this.numCaptures),\r\n mls,\r\n lowHz,\r\n highHz,\r\n componentIRGains,\r\n componentIRFreqs,\r\n sampleRate: this.sourceSamplingRate || 96000,\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 =\r\n `All Hz Calibration: done computing the IIR...`.toString() +\r\n this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n this.componentInvertedImpulseResponse = res['iir'];\r\n this.componentIR['Gain'] = res['ir'];\r\n this.componentIR['Freq'] = res['frequencies'];\r\n this.componentConvolution = 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 =\r\n `All Hz Calibration Step: computing the IR of the last recording...`.toString() +\r\n 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 =\r\n `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +\r\n 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 .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 =\r\n `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 =\r\n `All Hz Calibration: waiting for the signal to stabilize...`.toString() +\r\n 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 =\r\n `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +\r\n 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] * 0.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 * 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\r\n //depends on goal\r\n if (this._calibrateSoundCheck != 'system'){\r\n const buffer = audioCtx.createBuffer(\r\n 1, // number of channels\r\n this.componentConvolution.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.componentConvolution.length; i += 1) {\r\n data[i] = this.componentConvolution[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 }else{\r\n const buffer = audioCtx.createBuffer(\r\n 1, // number of channels\r\n this.systemConvolution.length,\r\n audioCtx.sampleRate // sample rate\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.systemConvolution.length; i += 1) {\r\n data[i] = this.systemConvolution[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 \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 =\r\n `All Hz Calibration: playing the calibration tone...`.toString() +\r\n this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\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 =\r\n `All Hz Calibration: playing the convolved calibration tone...`.toString() +\r\n 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 calibration audio ' + this.stepNum);\r\n this.status =\r\n `All Hz Calibration: stopping the calibration tone...`.toString() +\r\n 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 =\r\n `All Hz Calibration: stopping the convolved calibration tone...`.toString() +\r\n this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\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.sendSystemImpulseResponsesToServerForProcessing();\r\n await this.sendComponentImpulseResponsesToServerForProcessing();\r\n\r\n this.numSuccessfulCaptured = 0;\r\n // debugging function, use to test the result of the IIR\r\n\r\n //if goal == loudspeaker etc, \r\n let iir_ir_and_plots;\r\n if (this._calibrateSoundCheck != 'none'){\r\n await this.playMLSwithIIR(stream, this.invertedImpulseResponse);\r\n this.#stopCalibrationAudioConvolved();\r\n let conv_recs = this.getAllFilteredRecordedSignals();\r\n let recs = this.getAllRecordedSignals();\r\n let unconv_rec = recs[0];\r\n let conv_rec = conv_recs[0];\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 =\r\n `All Hz Calibration: done computing the PSD graphs...`.toString() +\r\n 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 iir_ir_and_plots = {\r\n systemIIR: this.systemInvertedImpulseResponse,\r\n componentIIR: this.componentInvertedImpulseResponse,\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 componentIR: this.componentIR,\r\n systemIR: this.systemIR,\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.componentConvolution, 'python_component_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.systemConvolution,'python_system_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.componentInvertedImpulseResponse, 'componentIIR.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.systemInvertedImpulseResponse, 'systemIIR.csv');\r\n const computedIRagain = await Promise.all(this.impulseResponses).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 }else{\r\n iir_ir_and_plots = {\r\n systemIIR: this.systemInvertedImpulseResponse,\r\n componentIIR: this.componentInvertedImpulseResponse,\r\n x_unconv: [],\r\n y_unconv: [],\r\n x_conv: [],\r\n y_conv: [],\r\n componentIR: this.componentIR,\r\n systemIR: this.systemIR,\r\n };\r\n if (this.#download) {\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.#mls, 'MLS.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.componentConvolution, 'python_component_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.systemConvolution,'python_system_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.componentInvertedImpulseResponse, 'componentIIR.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.systemInvertedImpulseResponse, 'systemIIR.csv');\r\n const computedIRagain = await Promise.all(this.impulseResponses).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 \r\n\r\n \r\n this.percent_complete = 100;\r\n\r\n this.status =\r\n `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n\r\n \r\n\r\n //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR\r\n //that was used to calibrate\r\n\r\n\r\n return iir_ir_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 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(\r\n offsetCurve,\r\n totalDuration - this.TAPER_SECS,\r\n this.TAPER_SECS\r\n );\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 => {\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, componentGainDBSPL) => {\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 =\r\n `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() +\r\n 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 =\r\n `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() +\r\n 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 componentGainDBSPL,\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 return result;\r\n };\r\n\r\n // function to write frq and gain to firebase database given speakerID\r\n writeFrqGain = async (speakerID, frq, gain) => {\r\n // freq and gain are too large to take samples 1 in every 100 samples\r\n\r\n const sampledFrq = [];\r\n const sampledGain = [];\r\n for (let i = 0; i < frq.length; i += 100) {\r\n sampledFrq.push(frq[i]);\r\n sampledGain.push(gain[i]);\r\n }\r\n\r\n const data = {Freq: sampledFrq, Gain: sampledGain};\r\n\r\n await (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.set)((0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_3__[\"default\"], `Microphone/${speakerID}/linear`), data);\r\n };\r\n\r\n // Function to Read frq and gain from firebase database given speakerID\r\n // returns an array of frq and gain if speakerID exists, returns null otherwise\r\n\r\n readFrqGain = async speakerID => {\r\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_3__[\"default\"]);\r\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.child)(dbRef, `Microphone/${speakerID}/linear`));\r\n if (snapshot.exists()) {\r\n return snapshot.val();\r\n }\r\n return null;\r\n };\r\n\r\n readGainat1000Hz = async speakerID => {\r\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_3__[\"default\"]);\r\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.child)(dbRef, `Microphone/${speakerID}/Gain1000`));\r\n if (snapshot.exists()) {\r\n return snapshot.val();\r\n }\r\n return null;\r\n };\r\n\r\n writeGainat1000Hz = async (speakerID, gain) => {\r\n const data = {Gain1000: gain};\r\n await (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.set)((0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_3__[\"default\"], `Microphone/${speakerID}`), data);\r\n };\r\n\r\n convertToDB = gain => {\r\n return Math.log10(gain) * 20;\r\n };\r\n\r\n // Function to perform linear interpolation between two points\r\n interpolate(x, x0, y0, x1, y1) {\r\n return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);\r\n }\r\n\r\n findGainatFrequency = (frequencies, gains, targetFrequency) => {\r\n // Find the index of the first frequency in the array greater than the target frequency\r\n let index = 0;\r\n while (index < frequencies.length && frequencies[index] < targetFrequency) {\r\n index++;\r\n }\r\n\r\n // Handle cases when the target frequency is outside the range of the given data\r\n if (index === 0) {\r\n return gains[0];\r\n } else if (index === frequencies.length) {\r\n return gains[gains.length - 1];\r\n } else {\r\n // Interpolate the gain based on the surrounding frequencies\r\n const x0 = frequencies[index - 1];\r\n const y0 = gains[index - 1];\r\n const x1 = frequencies[index];\r\n const y1 = gains[index];\r\n return this.interpolate(targetFrequency, x0, y0, x1, y1);\r\n }\r\n };\r\n\r\n // Example of how to use the writeFrqGain and readFrqGain functions\r\n // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);\r\n // Speaker1 is the speakerID you want to write to in the database\r\n // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));\r\n // MiniDSPUMIK_1 is the speakerID with some Data in the database\r\n //adding gainDBSPL\r\n startCalibration = async (\r\n stream,\r\n gainValues,\r\n lCalib = 104.92978421490648,\r\n componentIR = null,\r\n microphoneName = 'MiniDSPUMIK_1',\r\n _calibrateSoundCheck = 'system'\r\n ) => {\r\n\r\n //feed calibration goal here\r\n this._calibrateSoundCheck = _calibrateSoundCheck;\r\n\r\n\r\n\r\n //check if a componentIR was given to the system, if it isn't check for the microphone. using dummy data here bc we need to\r\n //check the db based on the microphone currently connected\r\n\r\n //new lCalib found at top of calibration files *1000hz, make sure to correct\r\n //based on zeroing of 1000hz, search for \"*1000Hz\"\r\n if (componentIR == null) { //mode 'ir'\r\n //global variable this.componentIR must be set\r\n this.componentIR = await this.readFrqGain(microphoneName).then(data => {\r\n return data;\r\n });\r\n\r\n lCalib = await this.readGainat1000Hz(microphoneName);\r\n this.componentGainDBSPL = this.convertToDB(lCalib);\r\n //TODO: if this call to database is unknown, cannot perform experiment => return false\r\n if (this.componentIR == null) {\r\n this.status =\r\n `Microphone ${microphoneName} is not in the database. Please add it to the database.`.toString();\r\n this.emit('update', {message: this.status});\r\n return false;\r\n }\r\n } else {\r\n this.componentIR = componentIR;\r\n lCalib = this.findGainatFrequency(this.componentIR.Freq, this.componentIR.Gain, 1000);\r\n this.componentGainDBSPL = this.convertToDB(lCalib);\r\n await this.writeGainat1000Hz(microphoneName, lCalib);\r\n }\r\n\r\n //TODO:\r\n //if *1000 is in, lcalib is that value and componentGainDBSPL is that value converted to dB\r\n //this value (lcalib) is 1000 hz offset so it must be added to every gain\r\n //if *1000 is not in, interpolate to get gain at 1000 hz (lcalib) and obtain componentGainDBSPL by converting lCalib to dB\r\n\r\n //lCalib is gain at 1000 hz, componentGainDBSPL is gain at 1000 hz converted to db\r\n //TODO: get this parameter from DB\r\n // lCalib = -37.4;\r\n // this.componentGainDBSPL = -30;\r\n // componentGainDBSPL = -30;\r\n\r\n let volumeResults = await this.startCalibrationVolume(\r\n stream,\r\n gainValues,\r\n lCalib,\r\n this.componentGainDBSPL\r\n );\r\n\r\n let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);\r\n //TODO: if needed, insert componentIR into db\r\n\r\n if (componentIR != null) {\r\n //insert Freq and Gain from this.componentIR into db\r\n await this.writeFrqGain(\r\n microphoneName,\r\n impulseResponseResults.componentIR.Freq,\r\n impulseResponseResults.componentIR.Gain\r\n );\r\n }\r\n\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/* harmony default export */ __webpack_exports__[\"default\"] = (Combination);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/combination/combination.js?");
832
+ 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/* harmony import */ var _config_firebase__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../config/firebase */ \"./src/config/firebase.js\");\n/* harmony import */ var firebase_database__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! firebase/database */ \"./node_modules/firebase/database/dist/esm/index.esm.js\");\n\r\n\r\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({\r\n download = false,\r\n mlsOrder = 18,\r\n numCaptures = 3,\r\n numMLSPerCapture = 4,\r\n lowHz = 20,\r\n highHz = 10000,\r\n }) {\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 componentInvertedImpulseResponse = null;\r\n\r\n /** @private */\r\n systemInvertedImpulseResponse = null;\r\n\r\n //averaged and subtracted ir returned from calibration used to calculated iir\r\n /** @private */\r\n ir = 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 componentConvolution;\r\n\r\n /** @private */\r\n systemConvolution;\r\n\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.01; // 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 /**@private */\r\n componentIR = null;\r\n\r\n /**@private */\r\n systemIR = null;\r\n\r\n /**@private */\r\n _calibrateSoundCheck = '';\r\n\r\n deviceType = null;\r\n\r\n deviceName = null;\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 setDeviceType = deviceType => {\r\n this.deviceType = deviceType;\r\n };\r\n\r\n setDeviceName = deviceName => {\r\n this.deviceName = deviceName;\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 sendSystemImpulseResponsesToServerForProcessing = 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 componentIRGains = this.componentIR['Gain'];\r\n //const componentIRFreqs = this.componentIR['Freq'];\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 =\r\n `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 .getSystemInverseImpulseResponseWithRetry({\r\n payload: filteredComputedIRs.slice(0, this.numCaptures),\r\n mls,\r\n lowHz,\r\n highHz,\r\n sampleRate: this.sourceSamplingRate || 96000,\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 =\r\n `All Hz Calibration: done computing the IIR...`.toString() +\r\n this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n this.systemInvertedImpulseResponse = res['iir'];\r\n this.systemIR = res['ir'];\r\n //this.componentIR['Gain'] = res['ir'];\r\n //this.componentIR['Freq'] = res['frequencies'];\r\n this.systemConvolution = 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 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 sendComponentImpulseResponsesToServerForProcessing = 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 componentIRGains = this.componentIR['Gain'];\r\n const componentIRFreqs = this.componentIR['Freq'];\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 =\r\n `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 .getComponentInverseImpulseResponseWithRetry({\r\n payload: filteredComputedIRs.slice(0, this.numCaptures),\r\n mls,\r\n lowHz,\r\n highHz,\r\n componentIRGains,\r\n componentIRFreqs,\r\n sampleRate: this.sourceSamplingRate || 96000,\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 =\r\n `All Hz Calibration: done computing the IIR...`.toString() +\r\n this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n this.componentInvertedImpulseResponse = res['iir'];\r\n this.componentIR['Gain'] = res['ir'];\r\n this.componentIR['Freq'] = res['frequencies'];\r\n this.componentConvolution = 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 =\r\n `All Hz Calibration Step: computing the IR of the last recording...`.toString() +\r\n 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 =\r\n `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +\r\n 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 .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 =\r\n `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 =\r\n `All Hz Calibration: waiting for the signal to stabilize...`.toString() +\r\n 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 =\r\n `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +\r\n 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] * 0.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 * 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\r\n //depends on goal\r\n if (this._calibrateSoundCheck != 'system') {\r\n const buffer = audioCtx.createBuffer(\r\n 1, // number of channels\r\n this.componentConvolution.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.componentConvolution.length; i += 1) {\r\n data[i] = this.componentConvolution[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 } else {\r\n const buffer = audioCtx.createBuffer(\r\n 1, // number of channels\r\n this.systemConvolution.length,\r\n audioCtx.sampleRate // sample rate\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.systemConvolution.length; i += 1) {\r\n data[i] = this.systemConvolution[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 /**\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 =\r\n `All Hz Calibration: playing the calibration tone...`.toString() +\r\n this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\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 =\r\n `All Hz Calibration: playing the convolved calibration tone...`.toString() +\r\n 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 calibration audio ' + this.stepNum);\r\n this.status =\r\n `All Hz Calibration: stopping the calibration tone...`.toString() +\r\n 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 =\r\n `All Hz Calibration: stopping the convolved calibration tone...`.toString() +\r\n this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\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.sendSystemImpulseResponsesToServerForProcessing();\r\n await this.sendComponentImpulseResponsesToServerForProcessing();\r\n\r\n this.numSuccessfulCaptured = 0;\r\n // debugging function, use to test the result of the IIR\r\n\r\n //if goal == loudspeaker etc,\r\n let iir_ir_and_plots;\r\n if (this._calibrateSoundCheck != 'none') {\r\n await this.playMLSwithIIR(stream, this.invertedImpulseResponse);\r\n this.#stopCalibrationAudioConvolved();\r\n let conv_recs = this.getAllFilteredRecordedSignals();\r\n let recs = this.getAllRecordedSignals();\r\n let unconv_rec = recs[0];\r\n let conv_rec = conv_recs[0];\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 =\r\n `All Hz Calibration: done computing the PSD graphs...`.toString() +\r\n 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 iir_ir_and_plots = {\r\n systemIIR: this.systemInvertedImpulseResponse,\r\n componentIIR: this.componentInvertedImpulseResponse,\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 componentIR: this.componentIR,\r\n systemIR: this.systemIR,\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.componentConvolution, 'python_component_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.systemConvolution, 'python_system_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.componentInvertedImpulseResponse, 'componentIIR.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.systemInvertedImpulseResponse, 'systemIIR.csv');\r\n const computedIRagain = await Promise.all(this.impulseResponses).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 } else {\r\n iir_ir_and_plots = {\r\n systemIIR: this.systemInvertedImpulseResponse,\r\n componentIIR: this.componentInvertedImpulseResponse,\r\n x_unconv: [],\r\n y_unconv: [],\r\n x_conv: [],\r\n y_conv: [],\r\n componentIR: this.componentIR,\r\n systemIR: this.systemIR,\r\n };\r\n if (this.#download) {\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.#mls, 'MLS.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.componentConvolution, 'python_component_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.systemConvolution, 'python_system_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.componentInvertedImpulseResponse, 'componentIIR.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.systemInvertedImpulseResponse, 'systemIIR.csv');\r\n const computedIRagain = await Promise.all(this.impulseResponses).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\r\n this.percent_complete = 100;\r\n\r\n this.status = `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();\r\n this.emit('update', {message: this.status});\r\n\r\n //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR\r\n //that was used to calibrate\r\n\r\n return iir_ir_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 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(\r\n offsetCurve,\r\n totalDuration - this.TAPER_SECS,\r\n this.TAPER_SECS\r\n );\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 => {\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, componentGainDBSPL) => {\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 =\r\n `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() +\r\n 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 =\r\n `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() +\r\n 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 componentGainDBSPL,\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 return result;\r\n };\r\n\r\n // function to write frq and gain to firebase database given speakerID\r\n writeFrqGain = async (speakerID, frq, gain) => {\r\n // freq and gain are too large to take samples 1 in every 100 samples\r\n\r\n const sampledFrq = [];\r\n const sampledGain = [];\r\n for (let i = 0; i < frq.length; i += 100) {\r\n sampledFrq.push(frq[i]);\r\n sampledGain.push(gain[i]);\r\n }\r\n\r\n const data = {Freq: sampledFrq, Gain: sampledGain};\r\n\r\n await (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.set)((0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_3__[\"default\"], `Microphone/${speakerID}/linear`), data);\r\n };\r\n\r\n // Function to Read frq and gain from firebase database given speakerID\r\n // returns an array of frq and gain if speakerID exists, returns null otherwise\r\n\r\n readFrqGain = async speakerID => {\r\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_3__[\"default\"]);\r\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.child)(dbRef, `Microphone/${speakerID}/linear`));\r\n if (snapshot.exists()) {\r\n return snapshot.val();\r\n }\r\n return null;\r\n };\r\n\r\n readGainat1000Hz = async speakerID => {\r\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_3__[\"default\"]);\r\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.child)(dbRef, `Microphone/${speakerID}/Gain1000`));\r\n if (snapshot.exists()) {\r\n return snapshot.val();\r\n }\r\n return null;\r\n };\r\n\r\n writeGainat1000Hz = async (speakerID, gain) => {\r\n const data = {Gain1000: gain};\r\n await (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.set)((0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_3__[\"default\"], `Microphone/${speakerID}`), data);\r\n };\r\n\r\n convertToDB = gain => {\r\n return Math.log10(gain) * 20;\r\n };\r\n\r\n // Function to perform linear interpolation between two points\r\n interpolate(x, x0, y0, x1, y1) {\r\n return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);\r\n }\r\n\r\n findGainatFrequency = (frequencies, gains, targetFrequency) => {\r\n // Find the index of the first frequency in the array greater than the target frequency\r\n let index = 0;\r\n while (index < frequencies.length && frequencies[index] < targetFrequency) {\r\n index++;\r\n }\r\n\r\n // Handle cases when the target frequency is outside the range of the given data\r\n if (index === 0) {\r\n return gains[0];\r\n } else if (index === frequencies.length) {\r\n return gains[gains.length - 1];\r\n } else {\r\n // Interpolate the gain based on the surrounding frequencies\r\n const x0 = frequencies[index - 1];\r\n const y0 = gains[index - 1];\r\n const x1 = frequencies[index];\r\n const y1 = gains[index];\r\n return this.interpolate(targetFrequency, x0, y0, x1, y1);\r\n }\r\n };\r\n\r\n writeIsSmartPhone = async (speakerID, isSmartPhone) => {\r\n const data = {isSmartPhone: isSmartPhone};\r\n await (0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.set)((0,firebase_database__WEBPACK_IMPORTED_MODULE_4__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_3__[\"default\"], `Microphone/${speakerID}`), data);\r\n };\r\n\r\n // Example of how to use the writeFrqGain and readFrqGain functions\r\n // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);\r\n // Speaker1 is the speakerID you want to write to in the database\r\n // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));\r\n // MiniDSPUMIK_1 is the speakerID with some Data in the database\r\n //adding gainDBSPL\r\n startCalibration = async (\r\n stream,\r\n gainValues,\r\n lCalib = 104.92978421490648,\r\n componentIR = null,\r\n microphoneName = 'MiniDSPUMIK_1',\r\n _calibrateSoundCheck = 'system',\r\n isSmartPhone = false\r\n ) => {\r\n //feed calibration goal here\r\n this._calibrateSoundCheck = _calibrateSoundCheck;\r\n\r\n //check if a componentIR was given to the system, if it isn't check for the microphone. using dummy data here bc we need to\r\n //check the db based on the microphone currently connected\r\n\r\n //new lCalib found at top of calibration files *1000hz, make sure to correct\r\n //based on zeroing of 1000hz, search for \"*1000Hz\"\r\n if (componentIR == null) {\r\n //mode 'ir'\r\n //global variable this.componentIR must be set\r\n this.componentIR = await this.readFrqGain(microphoneName).then(data => {\r\n return data;\r\n });\r\n\r\n lCalib = await this.readGainat1000Hz(microphoneName);\r\n this.componentGainDBSPL = this.convertToDB(lCalib);\r\n //TODO: if this call to database is unknown, cannot perform experiment => return false\r\n if (this.componentIR == null) {\r\n this.status =\r\n `Microphone ${microphoneName} is not in the database. Please add it to the database.`.toString();\r\n this.emit('update', {message: this.status});\r\n return false;\r\n }\r\n } else {\r\n this.componentIR = componentIR;\r\n lCalib = this.findGainatFrequency(this.componentIR.Freq, this.componentIR.Gain, 1000);\r\n this.componentGainDBSPL = this.convertToDB(lCalib);\r\n await this.writeGainat1000Hz(microphoneName, lCalib);\r\n }\r\n\r\n //TODO:\r\n //if *1000 is in, lcalib is that value and componentGainDBSPL is that value converted to dB\r\n //this value (lcalib) is 1000 hz offset so it must be added to every gain\r\n //if *1000 is not in, interpolate to get gain at 1000 hz (lcalib) and obtain componentGainDBSPL by converting lCalib to dB\r\n\r\n //lCalib is gain at 1000 hz, componentGainDBSPL is gain at 1000 hz converted to db\r\n //TODO: get this parameter from DB\r\n // lCalib = -37.4;\r\n // this.componentGainDBSPL = -30;\r\n // componentGainDBSPL = -30;\r\n\r\n let volumeResults = await this.startCalibrationVolume(\r\n stream,\r\n gainValues,\r\n lCalib,\r\n this.componentGainDBSPL\r\n );\r\n\r\n let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);\r\n //TODO: if needed, insert componentIR into db\r\n\r\n if (componentIR != null) {\r\n //insert Freq and Gain from this.componentIR into db\r\n await this.writeFrqGain(\r\n microphoneName,\r\n impulseResponseResults.componentIR.Freq,\r\n impulseResponseResults.componentIR.Gain\r\n );\r\n }\r\n\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/* harmony default export */ __webpack_exports__[\"default\"] = (Combination);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/combination/combination.js?");
833
833
 
834
834
  /***/ }),
835
835
 
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "speaker-calibration",
3
- "version": "2.1.25",
3
+ "version": "2.1.26",
4
4
  "description": "Speaker calibration library for auditory testing",
5
5
  "main": "dist/main.js",
6
6
  "directories": {
7
7
  "bin": "dist"
8
8
  },
9
9
  "scripts": {
10
+ "phrases": "node dist/example/fetch-languages-sheets.js && prettier --write dist/example/i18n.js",
10
11
  "build:prod": "webpack --mode production --progress",
11
12
  "build:dev": "webpack --watch --mode development --progress",
12
13
  "build:dev:analyze": "webpack --watch --mode development --progress --analyze",
@@ -90,7 +90,8 @@ class Speaker extends AudioPeer {
90
90
  params.ICalib,
91
91
  params.knownIR,
92
92
  params.microphoneName,
93
- params.calibrateSoundCheck
93
+ params.calibrateSoundCheck,
94
+ params.isSmartPhone
94
95
  );
95
96
  speaker.#removeUIElems();
96
97
  resolve(speaker.result);
@@ -163,6 +164,7 @@ class Speaker extends AudioPeer {
163
164
  // Get query string, the URL parameters to specify a Listener
164
165
  const queryStringParameters = {
165
166
  speakerPeerId: this.peer.id,
167
+ isSmartPhone: this.isSmartPhone,
166
168
  };
167
169
  const queryString = this.queryStringFromObject(queryStringParameters);
168
170
  const uri = this.siteUrl + queryString;
@@ -203,7 +203,7 @@ class Combination extends AudioCalibrator {
203
203
  this.generateTemplate().toString();
204
204
  this.emit('update', {message: this.status});
205
205
  this.systemInvertedImpulseResponse = res['iir'];
206
- this.systemIR = res['ir']
206
+ this.systemIR = res['ir'];
207
207
  //this.componentIR['Gain'] = res['ir'];
208
208
  //this.componentIR['Freq'] = res['frequencies'];
209
209
  this.systemConvolution = res['convolution'];
@@ -214,7 +214,6 @@ class Combination extends AudioCalibrator {
214
214
  });
215
215
  };
216
216
 
217
-
218
217
  /** .
219
218
  * .
220
219
  * .
@@ -502,7 +501,7 @@ class Combination extends AudioCalibrator {
502
501
  const audioCtx = this.makeNewSourceAudioContextConvolved();
503
502
 
504
503
  //depends on goal
505
- if (this._calibrateSoundCheck != 'system'){
504
+ if (this._calibrateSoundCheck != 'system') {
506
505
  const buffer = audioCtx.createBuffer(
507
506
  1, // number of channels
508
507
  this.componentConvolution.length,
@@ -526,14 +525,14 @@ class Combination extends AudioCalibrator {
526
525
  source.connect(audioCtx.destination);
527
526
 
528
527
  this.addCalibrationNodeConvolved(source);
529
- }else{
528
+ } else {
530
529
  const buffer = audioCtx.createBuffer(
531
530
  1, // number of channels
532
531
  this.systemConvolution.length,
533
532
  audioCtx.sampleRate // sample rate
534
533
  );
535
534
  const data = buffer.getChannelData(0); // get data
536
- // fill the buffer with our data
535
+ // fill the buffer with our data
537
536
  try {
538
537
  for (let i = 0; i < this.systemConvolution.length; i += 1) {
539
538
  data[i] = this.systemConvolution[i];
@@ -550,9 +549,6 @@ class Combination extends AudioCalibrator {
550
549
 
551
550
  this.addCalibrationNodeConvolved(source);
552
551
  }
553
-
554
-
555
-
556
552
  };
557
553
 
558
554
  /**
@@ -703,9 +699,9 @@ class Combination extends AudioCalibrator {
703
699
  this.numSuccessfulCaptured = 0;
704
700
  // debugging function, use to test the result of the IIR
705
701
 
706
- //if goal == loudspeaker etc,
702
+ //if goal == loudspeaker etc,
707
703
  let iir_ir_and_plots;
708
- if (this._calibrateSoundCheck != 'none'){
704
+ if (this._calibrateSoundCheck != 'none') {
709
705
  await this.playMLSwithIIR(stream, this.invertedImpulseResponse);
710
706
  this.#stopCalibrationAudioConvolved();
711
707
  let conv_recs = this.getAllFilteredRecordedSignals();
@@ -713,21 +709,21 @@ class Combination extends AudioCalibrator {
713
709
  let unconv_rec = recs[0];
714
710
  let conv_rec = conv_recs[0];
715
711
  let results = await this.pyServerAPI
716
- .getPSDWithRetry({
717
- unconv_rec,
718
- conv_rec,
719
- })
720
- .then(res => {
721
- this.incrementStatusBar();
722
- this.status =
723
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
724
- this.generateTemplate().toString();
725
- this.emit('update', {message: this.status});
726
- return res;
727
- })
728
- .catch(err => {
729
- console.error(err);
730
- });
712
+ .getPSDWithRetry({
713
+ unconv_rec,
714
+ conv_rec,
715
+ })
716
+ .then(res => {
717
+ this.incrementStatusBar();
718
+ this.status =
719
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
720
+ this.generateTemplate().toString();
721
+ this.emit('update', {message: this.status});
722
+ return res;
723
+ })
724
+ .catch(err => {
725
+ console.error(err);
726
+ });
731
727
  iir_ir_and_plots = {
732
728
  systemIIR: this.systemInvertedImpulseResponse,
733
729
  componentIIR: this.componentInvertedImpulseResponse,
@@ -743,7 +739,7 @@ class Combination extends AudioCalibrator {
743
739
  this.downloadSingleFilteredRecording();
744
740
  saveToCSV(this.#mls, 'MLS.csv');
745
741
  saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
746
- saveToCSV(this.systemConvolution,'python_system_convolution_mls_iir.csv');
742
+ saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
747
743
  saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
748
744
  saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
749
745
  const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
@@ -754,7 +750,7 @@ class Combination extends AudioCalibrator {
754
750
  }
755
751
  });
756
752
  }
757
- }else{
753
+ } else {
758
754
  iir_ir_and_plots = {
759
755
  systemIIR: this.systemInvertedImpulseResponse,
760
756
  componentIIR: this.componentInvertedImpulseResponse,
@@ -768,7 +764,7 @@ class Combination extends AudioCalibrator {
768
764
  if (this.#download) {
769
765
  saveToCSV(this.#mls, 'MLS.csv');
770
766
  saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
771
- saveToCSV(this.systemConvolution,'python_system_convolution_mls_iir.csv');
767
+ saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
772
768
  saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
773
769
  saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
774
770
  const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
@@ -780,21 +776,15 @@ class Combination extends AudioCalibrator {
780
776
  });
781
777
  }
782
778
  }
783
-
784
779
 
785
-
786
780
  this.percent_complete = 100;
787
781
 
788
- this.status =
789
- `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();
782
+ this.status = `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();
790
783
  this.emit('update', {message: this.status});
791
784
 
792
-
793
-
794
785
  //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR
795
786
  //that was used to calibrate
796
787
 
797
-
798
788
  return iir_ir_and_plots;
799
789
  };
800
790
 
@@ -1099,6 +1089,11 @@ class Combination extends AudioCalibrator {
1099
1089
  }
1100
1090
  };
1101
1091
 
1092
+ writeIsSmartPhone = async (speakerID, isSmartPhone) => {
1093
+ const data = {isSmartPhone: isSmartPhone};
1094
+ await set(ref(database, `Microphone/${speakerID}`), data);
1095
+ };
1096
+
1102
1097
  // Example of how to use the writeFrqGain and readFrqGain functions
1103
1098
  // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);
1104
1099
  // Speaker1 is the speakerID you want to write to in the database
@@ -1111,20 +1106,19 @@ class Combination extends AudioCalibrator {
1111
1106
  lCalib = 104.92978421490648,
1112
1107
  componentIR = null,
1113
1108
  microphoneName = 'MiniDSPUMIK_1',
1114
- _calibrateSoundCheck = 'system'
1109
+ _calibrateSoundCheck = 'system',
1110
+ isSmartPhone = false
1115
1111
  ) => {
1116
-
1117
1112
  //feed calibration goal here
1118
1113
  this._calibrateSoundCheck = _calibrateSoundCheck;
1119
1114
 
1120
-
1121
-
1122
1115
  //check if a componentIR was given to the system, if it isn't check for the microphone. using dummy data here bc we need to
1123
1116
  //check the db based on the microphone currently connected
1124
1117
 
1125
1118
  //new lCalib found at top of calibration files *1000hz, make sure to correct
1126
1119
  //based on zeroing of 1000hz, search for "*1000Hz"
1127
- if (componentIR == null) { //mode 'ir'
1120
+ if (componentIR == null) {
1121
+ //mode 'ir'
1128
1122
  //global variable this.componentIR must be set
1129
1123
  this.componentIR = await this.readFrqGain(microphoneName).then(data => {
1130
1124
  return data;