speaker-calibration 2.2.20 → 2.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -796,7 +796,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
796
796
  /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
797
797
 
798
798
  "use strict";
799
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var 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/* harmony import */ var _dist_example_i18n__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../dist/example/i18n */ \"./dist/example/i18n.js\");\n\n\n\n\n\n\n\n\n\n/**\n * @class Handles the speaker's side of the connection. Responsible for initiating the connection,\n * rendering the QRCode, and answering the call.\n * @augments AudioPeer\n */\nclass Speaker extends _audioPeer__WEBPACK_IMPORTED_MODULE_1__[\"default\"] {\n /**\n * Takes the url of the current site and a target element where html elements will be appended.\n *\n * @param params - See type definition for initParameters.\n * @param Calibrator - An instance of the AudioCalibrator class, should not use AudioCalibrator directly, instead use an extended class available in /tasks/.\n * @param CalibratorInstance\n * @example\n */\n constructor(params, CalibratorInstance) {\n super(params);\n this.siteUrl += '/listener?';\n this.ac = CalibratorInstance;\n this.result = null;\n this.debug = params?.debug ?? false;\n this.isSmartPhone = params?.isSmartPhone ?? false;\n this.calibrateSoundHz = params?.calibrateSoundHz ?? 48000;\n this.instructionDisplayId = params?.instructionDisplayId ?? '';\n this.titleDisplayId = params?.titleDisplayId ?? '';\n this.timeToCalibrate = params?.timeToCalibrate ?? 10;\n\n /* Set up callbacks that handle any events related to our peer object. */\n this.peer.on('open', this.#onPeerOpen);\n this.peer.on('connection', this.#onPeerConnection);\n this.peer.on('close', this.#onPeerClose);\n this.peer.on('disconnected', this.#onPeerDisconnected);\n this.peer.on('error', this.#onPeerError);\n }\n\n static getMicrophoneNamesFromDatabase = async () => {\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_5__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_4__[\"default\"]);\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_5__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_5__.child)(dbRef, 'Microphone'));\n if (snapshot.exists()) {\n return Object.keys(snapshot.val());\n }\n return null;\n };\n\n /**\n * Async factory method that creates the Speaker object, and returns a promise that resolves to the result of the calibration.\n *\n * @param params - The parameters to be passed to the peer object.\n * @param Calibrator - The class that defines the calibration process.\n * @param CalibratorInstance\n * @param timeOut - The amount of time to wait before timing out the connection (in milliseconds).\n * @public\n * @example\n */\n static startCalibration = async (params, CalibratorInstance, timeOut = 180000) => {\n window.speaker = new Speaker(params, CalibratorInstance);\n const {speaker} = window;\n\n // wrap the calibration process in a promise so we can await it\n return new Promise((resolve, reject) => {\n // when a call is received\n speaker.peer.on('call', async call => {\n // Answer the call (one way)\n call.answer();\n speaker.#removeUIElems();\n speaker.#showSpinner();\n speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));\n // when we start receiving audio\n call.on('stream', async stream => {\n window.localStream = stream;\n window.localAudio.srcObject = stream;\n window.localAudio.autoplay = false;\n\n // if the sinkSamplingRate is not set sleep\n while (!speaker.ac.sampleRatesSet()) {\n console.log('SinkSamplingRate is undefined, sleeping');\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1);\n }\n // resolve when we have a result\n speaker.result = await speaker.ac.startCalibration(\n stream,\n params.gainValues,\n params.ICalib,\n params.knownIR,\n params.microphoneName,\n params.calibrateSoundCheck,\n params.isSmartPhone,\n params.calibrateSoundBurstRepeats,\n params.calibrateSoundBurstSec,\n params.calibrateSoundBurstsWarmup,\n params.calibrateSoundHz,\n params.calibrateSoundIIRSec,\n params.micManufacturer,\n params.micSerialNumber,\n params.micModelNumber,\n params.micModelName\n );\n speaker.#removeUIElems();\n resolve(speaker.result);\n });\n // if we do not receive a result within the timeout, reject\n setTimeout(() => {\n reject(\n new _peerErrors__WEBPACK_IMPORTED_MODULE_3__.CalibrationTimedOutError(\n `Calibration failed to produce a result after ${\n timeOut / 1000\n } seconds. Please try again.`\n )\n );\n }, timeOut);\n });\n });\n };\n\n static testIIR = async (params, CalibratorInstance, IIR, timeOut = 180000) => {\n window.speaker = new Speaker(params, CalibratorInstance);\n const {speaker} = window;\n\n // wrap the calibration process in a promise so we can await it\n return new Promise((resolve, reject) => {\n // when a call is received\n speaker.peer.on('call', async call => {\n // Answer the call (one way)\n call.answer();\n speaker.#removeUIElems();\n speaker.#showSpinner();\n speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));\n // when we start receiving audio\n call.on('stream', async stream => {\n window.localStream = stream;\n window.localAudio.srcObject = stream;\n window.localAudio.autoplay = false;\n\n // if the sinkSamplingRate is not set sleep\n while (!speaker.ac.sampleRatesSet()) {\n console.log('SinkSamplingRate is undefined, sleeping');\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1);\n }\n // resolve when we have a result\n speaker.result = await speaker.ac.playMLSwithIIR(stream, IIR);\n speaker.#removeUIElems();\n resolve(speaker.result);\n });\n // if we do not receive a result within the timeout, reject\n setTimeout(() => {\n reject(\n new _peerErrors__WEBPACK_IMPORTED_MODULE_3__.CalibrationTimedOutError(\n `Calibration failed to produce a result after ${\n timeOut / 1000\n } seconds. Please try again.`\n )\n );\n }, timeOut);\n });\n });\n };\n\n /**\n * Called after the peer conncection has been opened.\n * Generates a QR code for the connection and displays it.\n *\n * @private\n * @example\n */\n #showQRCode = () => {\n // Get query string, the URL parameters to specify a Listener\n const queryStringParameters = {\n speakerPeerId: this.peer.id,\n isSmartPhone: this.isSmartPhone,\n calibrateSoundHz: this.calibrateSoundHz,\n };\n const queryString = this.queryStringFromObject(queryStringParameters);\n const uri = this.siteUrl + queryString;\n if (this.isSmartPhone) {\n // Display QR code for the participant to scan\n const qrCanvas = document.createElement('canvas');\n qrCanvas.setAttribute('id', 'qrCanvas');\n console.log(uri);\n qrcode__WEBPACK_IMPORTED_MODULE_0__.toCanvas(qrCanvas, uri, error => {\n if (error) console.error(error);\n });\n document.getElementById(this.targetElement).appendChild(qrCanvas);\n } else {\n // show the link to the user\n // If specified HTML Id is available, show QR code there\n if (document.getElementById(this.targetElement)) {\n // const linkTag = document.createElement('a');\n // linkTag.setAttribute('href', uri);\n // linkTag.innerHTML = 'Click here to start the calibration';\n // linkTag.target = '_blank';\n // document.getElementById(this.targetElement).appendChild(linkTag);\n // document.getElementById(this.targetElement).appendChild(qrCanvas);\n\n const proceedButton = document.createElement('button');\n proceedButton.setAttribute('id', 'calibrationProceedButton');\n proceedButton.setAttribute('class', 'btn btn-success');\n proceedButton.innerHTML = 'Proceed';\n proceedButton.onclick = () => {\n // open the link in a new tab\n window.open(uri, '_blank');\n // remove the button\n document.getElementById('calibrationProceedButton').remove();\n };\n document.getElementById(this.targetElement).appendChild(proceedButton);\n }\n }\n // or just print it to console\n console.log('TEST: Peer reachable at: ', uri);\n };\n\n #showSpinner = () => {\n const spinner = document.createElement('div');\n spinner.className = 'spinner-border ml-auto';\n spinner.role = 'status';\n spinner.ariaHidden = 'true';\n document.getElementById(this.targetElement).appendChild(spinner);\n\n // clear instructionDisplay\n const instructionDisplay = document.getElementById(this.instructionDisplayId);\n const background = document.getElementById('background'); // todo: get background id from params\n if (instructionDisplay) {\n instructionDisplay.innerHTML = '';\n instructionDisplay.style.whiteSpace = 'nowrap';\n instructionDisplay.style.fontWeight = 'bold';\n instructionDisplay.style.width = 'fit-content';\n instructionDisplay.innerHTML = _dist_example_i18n__WEBPACK_IMPORTED_MODULE_6__.phrases.RC_soundRecording[\"en-US\"];\n let fontSize = 100;\n instructionDisplay.style.fontSize = fontSize + 'px';\n while (instructionDisplay.scrollWidth > background.scrollWidth * 0.9 && fontSize > 10) {\n fontSize--;\n instructionDisplay.style.fontSize = fontSize + 'px';\n }\n const p = document.createElement('p');\n // font size\n p.style.fontSize = '1.1rem';\n p.style.fontWeight = 'normal';\n p.style.paddingTop = '20px';\n const timeToCalibrateText = _dist_example_i18n__WEBPACK_IMPORTED_MODULE_6__.phrases.RC_howLongToCalibrate[\"en-US\"];\n p.innerHTML = timeToCalibrateText.replace('111', this.timeToCalibrate);\n instructionDisplay.appendChild(p);\n }\n\n // Update title - titleDisplayId\n const titleDisplay = document.getElementById(this.titleDisplayId);\n if (titleDisplay) {\n // replace 2 with 3\n titleDisplay.innerHTML = titleDisplay.innerHTML.replace('2', '3');\n // replace 1 with 3\n titleDisplay.innerHTML = titleDisplay.innerHTML.replace('1', '3');\n }\n };\n\n #removeUIElems = () => {\n const parent = document.getElementById(this.targetElement);\n while (parent.firstChild) {\n parent.firstChild.remove();\n }\n };\n\n /**\n * Called when the peer connection is opened.\n * Saves the peer id and calls the QR code generator.\n *\n * @param peerId - The peer id of the peer connection.\n * @param id\n * @private\n * @example\n */\n #onPeerOpen = id => {\n // Workaround for peer.reconnect deleting previous id\n if (id === null) {\n console.error('Received null id from peer open');\n this.peer.id = this.lastPeerId;\n } else {\n this.lastPeerId = this.peer.id;\n }\n\n if (id !== this.peer.id) {\n console.warn('DEBUG Check you assumption that id === this.peer.id');\n }\n\n this.#showQRCode();\n };\n\n /**\n * Called when the peer connection is established.\n * Enforces a single connection.\n *\n * @param connection - The connection object.\n * @private\n * @example\n */\n #onPeerConnection = connection => {\n // Allow only a single connection\n if (this.conn && this.conn.open) {\n connection.on('open', () => {\n connection.send('Already connected to another client');\n setTimeout(() => {\n connection.close();\n }, 500);\n });\n return;\n }\n\n this.conn = connection;\n console.log('Connected to: ', this.conn.peer);\n this.#ready();\n };\n\n /**\n * Called when the peer connection is closed.\n *\n * @private\n * @example\n */\n #onPeerClose = () => {\n this.conn = null;\n console.log('Connection destroyed');\n };\n\n /**\n * Called when the peer connection is disconnected.\n * Attempts to reconnect.\n *\n * @private\n * @example\n */\n #onPeerDisconnected = () => {\n console.log('Connection lost. Please reconnect');\n\n // Workaround for peer.reconnect deleting previous id\n this.peer.id = this.lastPeerId;\n // eslint-disable-next-line no-underscore-dangle\n this.peer._lastServerId = this.lastPeerId;\n this.peer.reconnect();\n };\n\n /**\n * Called when the peer connection encounters an error.\n *\n * @param error\n * @private\n * @example\n */\n #onPeerError = error => {\n // TODO: check if this function is needed or not\n console.error(error);\n };\n\n /**\n * Called when data is received from the peer connection.\n *\n * @param data\n * @private\n * @example\n */\n #onIncomingData = data => {\n // enforce object type\n if (\n !Object.prototype.hasOwnProperty.call(data, 'name') ||\n !Object.prototype.hasOwnProperty.call(data, 'payload')\n ) {\n console.error('Received malformed data: ', data);\n return;\n }\n\n switch (data.name) {\n case 'samplingRate':\n this.ac.setSamplingRates(data.payload);\n break;\n case 'deviceType':\n this.ac.setDeviceType(data.payload);\n break;\n case 'deviceName':\n this.ac.setDeviceName(data.payload);\n break;\n case 'deviceInfo':\n this.ac.setDeviceInfo(data.payload);\n case _peerErrors__WEBPACK_IMPORTED_MODULE_3__.UnsupportedDeviceError.name:\n case _peerErrors__WEBPACK_IMPORTED_MODULE_3__.MissingSpeakerIdError.name:\n throw data.payload;\n break;\n default:\n break;\n }\n };\n\n /**\n * Called when the peer connection is #ready.\n *\n * @private\n * @example\n */\n #ready = () => {\n // Perform callback with data\n this.conn.on('data', this.#onIncomingData);\n this.conn.on('close', () => {\n console.log('Connection reset<br>Awaiting connection...');\n this.conn = null;\n });\n };\n\n /** .\n * .\n * .\n * Debug method for downloading the recorded audio\n *\n * @public\n * @example\n */\n downloadData = () => {\n this.ac.downloadData();\n };\n}\n\n/* \nReferenced links:\nhttps://stackoverflow.com/questions/28016664/when-you-pass-this-as-an-argument/28016676#28016676\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes\nhttps://stackoverflow.com/questions/879152/how-do-i-make-javascript-beep [3]\n*/\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (Speaker);\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/peer-connection/speaker.js?");
799
+ 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/* harmony import */ var _dist_example_i18n__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../dist/example/i18n */ \"./dist/example/i18n.js\");\n\n\n\n\n\n\n\n\n\n/**\n * @class Handles the speaker's side of the connection. Responsible for initiating the connection,\n * rendering the QRCode, and answering the call.\n * @augments AudioPeer\n */\nclass Speaker extends _audioPeer__WEBPACK_IMPORTED_MODULE_1__[\"default\"] {\n /**\n * Takes the url of the current site and a target element where html elements will be appended.\n *\n * @param params - See type definition for initParameters.\n * @param Calibrator - An instance of the AudioCalibrator class, should not use AudioCalibrator directly, instead use an extended class available in /tasks/.\n * @param CalibratorInstance\n * @example\n */\n constructor(params, CalibratorInstance) {\n super(params);\n this.siteUrl += '/listener?';\n this.ac = CalibratorInstance;\n this.result = null;\n this.debug = params?.debug ?? false;\n this.isSmartPhone = params?.isSmartPhone ?? false;\n this.calibrateSoundHz = params?.calibrateSoundHz ?? 48000;\n this.instructionDisplayId = params?.instructionDisplayId ?? '';\n this.titleDisplayId = params?.titleDisplayId ?? '';\n this.timeToCalibrate = params?.timeToCalibrate ?? 10;\n\n /* Set up callbacks that handle any events related to our peer object. */\n this.peer.on('open', this.#onPeerOpen);\n this.peer.on('connection', this.#onPeerConnection);\n this.peer.on('close', this.#onPeerClose);\n this.peer.on('disconnected', this.#onPeerDisconnected);\n this.peer.on('error', this.#onPeerError);\n }\n\n static getMicrophoneNamesFromDatabase = async () => {\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_5__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_4__[\"default\"]);\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_5__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_5__.child)(dbRef, 'Microphone'));\n if (snapshot.exists()) {\n return Object.keys(snapshot.val());\n }\n return null;\n };\n\n /**\n * Async factory method that creates the Speaker object, and returns a promise that resolves to the result of the calibration.\n *\n * @param params - The parameters to be passed to the peer object.\n * @param Calibrator - The class that defines the calibration process.\n * @param CalibratorInstance\n * @param timeOut - The amount of time to wait before timing out the connection (in milliseconds).\n * @public\n * @example\n */\n static startCalibration = async (params, CalibratorInstance, timeOut = 180000) => {\n window.speaker = new Speaker(params, CalibratorInstance);\n const {speaker} = window;\n\n // wrap the calibration process in a promise so we can await it\n return new Promise((resolve, reject) => {\n // when a call is received\n speaker.peer.on('call', async call => {\n // Answer the call (one way)\n call.answer();\n speaker.#removeUIElems();\n speaker.#showSpinner();\n speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));\n // when we start receiving audio\n call.on('stream', async stream => {\n window.localStream = stream;\n window.localAudio.srcObject = stream;\n window.localAudio.autoplay = false;\n\n // if the sinkSamplingRate is not set sleep\n while (!speaker.ac.sampleRatesSet()) {\n console.log('SinkSamplingRate is undefined, sleeping');\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1);\n }\n // resolve when we have a result\n speaker.result = await speaker.ac.startCalibration(\n stream,\n params.gainValues,\n params.ICalib,\n params.knownIR,\n params.microphoneName,\n params.calibrateSoundCheck,\n params.isSmartPhone,\n params.calibrateSoundBurstRepeats,\n params.calibrateSoundBurstSec,\n params.calibrateSoundBurstsWarmup,\n params.calibrateSoundHz,\n params.calibrateSoundIIRSec,\n params.calibrateSound1000HzSec,\n params.micManufacturer,\n params.micSerialNumber,\n params.micModelNumber,\n params.micModelName\n );\n speaker.#removeUIElems();\n resolve(speaker.result);\n });\n // if we do not receive a result within the timeout, reject\n setTimeout(() => {\n reject(\n new _peerErrors__WEBPACK_IMPORTED_MODULE_3__.CalibrationTimedOutError(\n `Calibration failed to produce a result after ${\n timeOut / 1000\n } seconds. Please try again.`\n )\n );\n }, timeOut);\n });\n });\n };\n\n static testIIR = async (params, CalibratorInstance, IIR, timeOut = 180000) => {\n window.speaker = new Speaker(params, CalibratorInstance);\n const {speaker} = window;\n\n // wrap the calibration process in a promise so we can await it\n return new Promise((resolve, reject) => {\n // when a call is received\n speaker.peer.on('call', async call => {\n // Answer the call (one way)\n call.answer();\n speaker.#removeUIElems();\n speaker.#showSpinner();\n speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));\n // when we start receiving audio\n call.on('stream', async stream => {\n window.localStream = stream;\n window.localAudio.srcObject = stream;\n window.localAudio.autoplay = false;\n\n // if the sinkSamplingRate is not set sleep\n while (!speaker.ac.sampleRatesSet()) {\n console.log('SinkSamplingRate is undefined, sleeping');\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1);\n }\n // resolve when we have a result\n speaker.result = await speaker.ac.playMLSwithIIR(stream, IIR);\n speaker.#removeUIElems();\n resolve(speaker.result);\n });\n // if we do not receive a result within the timeout, reject\n setTimeout(() => {\n reject(\n new _peerErrors__WEBPACK_IMPORTED_MODULE_3__.CalibrationTimedOutError(\n `Calibration failed to produce a result after ${\n timeOut / 1000\n } seconds. Please try again.`\n )\n );\n }, timeOut);\n });\n });\n };\n\n /**\n * Called after the peer conncection has been opened.\n * Generates a QR code for the connection and displays it.\n *\n * @private\n * @example\n */\n #showQRCode = () => {\n // Get query string, the URL parameters to specify a Listener\n const queryStringParameters = {\n speakerPeerId: this.peer.id,\n isSmartPhone: this.isSmartPhone,\n calibrateSoundHz: this.calibrateSoundHz,\n };\n const queryString = this.queryStringFromObject(queryStringParameters);\n const uri = this.siteUrl + queryString;\n if (this.isSmartPhone) {\n // Display QR code for the participant to scan\n const qrCanvas = document.createElement('canvas');\n qrCanvas.setAttribute('id', 'qrCanvas');\n console.log(uri);\n qrcode__WEBPACK_IMPORTED_MODULE_0__.toCanvas(qrCanvas, uri, error => {\n if (error) console.error(error);\n });\n document.getElementById(this.targetElement).appendChild(qrCanvas);\n } else {\n // show the link to the user\n // If specified HTML Id is available, show QR code there\n if (document.getElementById(this.targetElement)) {\n // const linkTag = document.createElement('a');\n // linkTag.setAttribute('href', uri);\n // linkTag.innerHTML = 'Click here to start the calibration';\n // linkTag.target = '_blank';\n // document.getElementById(this.targetElement).appendChild(linkTag);\n // document.getElementById(this.targetElement).appendChild(qrCanvas);\n\n const proceedButton = document.createElement('button');\n proceedButton.setAttribute('id', 'calibrationProceedButton');\n proceedButton.setAttribute('class', 'btn btn-success');\n proceedButton.innerHTML = 'Proceed';\n proceedButton.onclick = () => {\n // open the link in a new tab\n window.open(uri, '_blank');\n // remove the button\n document.getElementById('calibrationProceedButton').remove();\n };\n document.getElementById(this.targetElement).appendChild(proceedButton);\n }\n }\n // or just print it to console\n console.log('TEST: Peer reachable at: ', uri);\n };\n\n #showSpinner = () => {\n const spinner = document.createElement('div');\n spinner.className = 'spinner-border ml-auto';\n spinner.role = 'status';\n spinner.ariaHidden = 'true';\n document.getElementById(this.targetElement).appendChild(spinner);\n\n // clear instructionDisplay\n const instructionDisplay = document.getElementById(this.instructionDisplayId);\n const background = document.getElementById('background'); // todo: get background id from params\n if (instructionDisplay) {\n instructionDisplay.innerHTML = '';\n instructionDisplay.style.whiteSpace = 'nowrap';\n instructionDisplay.style.fontWeight = 'bold';\n instructionDisplay.style.width = 'fit-content';\n instructionDisplay.innerHTML = _dist_example_i18n__WEBPACK_IMPORTED_MODULE_6__.phrases.RC_soundRecording[\"en-US\"];\n let fontSize = 100;\n instructionDisplay.style.fontSize = fontSize + 'px';\n while (instructionDisplay.scrollWidth > background.scrollWidth * 0.9 && fontSize > 10) {\n fontSize--;\n instructionDisplay.style.fontSize = fontSize + 'px';\n }\n const p = document.createElement('p');\n // font size\n p.style.fontSize = '1.1rem';\n p.style.fontWeight = 'normal';\n p.style.paddingTop = '20px';\n const timeToCalibrateText = _dist_example_i18n__WEBPACK_IMPORTED_MODULE_6__.phrases.RC_howLongToCalibrate[\"en-US\"];\n p.innerHTML = timeToCalibrateText.replace('111', this.timeToCalibrate);\n instructionDisplay.appendChild(p);\n }\n\n // Update title - titleDisplayId\n const titleDisplay = document.getElementById(this.titleDisplayId);\n if (titleDisplay) {\n // replace 2 with 3\n titleDisplay.innerHTML = titleDisplay.innerHTML.replace('2', '3');\n // replace 1 with 3\n titleDisplay.innerHTML = titleDisplay.innerHTML.replace('1', '3');\n }\n };\n\n #removeUIElems = () => {\n const parent = document.getElementById(this.targetElement);\n while (parent.firstChild) {\n parent.firstChild.remove();\n }\n };\n\n /**\n * Called when the peer connection is opened.\n * Saves the peer id and calls the QR code generator.\n *\n * @param peerId - The peer id of the peer connection.\n * @param id\n * @private\n * @example\n */\n #onPeerOpen = id => {\n // Workaround for peer.reconnect deleting previous id\n if (id === null) {\n console.error('Received null id from peer open');\n this.peer.id = this.lastPeerId;\n } else {\n this.lastPeerId = this.peer.id;\n }\n\n if (id !== this.peer.id) {\n console.warn('DEBUG Check you assumption that id === this.peer.id');\n }\n\n this.#showQRCode();\n };\n\n /**\n * Called when the peer connection is established.\n * Enforces a single connection.\n *\n * @param connection - The connection object.\n * @private\n * @example\n */\n #onPeerConnection = connection => {\n // Allow only a single connection\n if (this.conn && this.conn.open) {\n connection.on('open', () => {\n connection.send('Already connected to another client');\n setTimeout(() => {\n connection.close();\n }, 500);\n });\n return;\n }\n\n this.conn = connection;\n console.log('Connected to: ', this.conn.peer);\n this.#ready();\n };\n\n /**\n * Called when the peer connection is closed.\n *\n * @private\n * @example\n */\n #onPeerClose = () => {\n this.conn = null;\n console.log('Connection destroyed');\n };\n\n /**\n * Called when the peer connection is disconnected.\n * Attempts to reconnect.\n *\n * @private\n * @example\n */\n #onPeerDisconnected = () => {\n console.log('Connection lost. Please reconnect');\n\n // Workaround for peer.reconnect deleting previous id\n this.peer.id = this.lastPeerId;\n // eslint-disable-next-line no-underscore-dangle\n this.peer._lastServerId = this.lastPeerId;\n this.peer.reconnect();\n };\n\n /**\n * Called when the peer connection encounters an error.\n *\n * @param error\n * @private\n * @example\n */\n #onPeerError = error => {\n // TODO: check if this function is needed or not\n console.error(error);\n };\n\n /**\n * Called when data is received from the peer connection.\n *\n * @param data\n * @private\n * @example\n */\n #onIncomingData = data => {\n // enforce object type\n if (\n !Object.prototype.hasOwnProperty.call(data, 'name') ||\n !Object.prototype.hasOwnProperty.call(data, 'payload')\n ) {\n console.error('Received malformed data: ', data);\n return;\n }\n\n switch (data.name) {\n case 'samplingRate':\n this.ac.setSamplingRates(data.payload);\n break;\n case 'deviceType':\n this.ac.setDeviceType(data.payload);\n break;\n case 'deviceName':\n this.ac.setDeviceName(data.payload);\n break;\n case 'deviceInfo':\n this.ac.setDeviceInfo(data.payload);\n case _peerErrors__WEBPACK_IMPORTED_MODULE_3__.UnsupportedDeviceError.name:\n case _peerErrors__WEBPACK_IMPORTED_MODULE_3__.MissingSpeakerIdError.name:\n throw data.payload;\n break;\n default:\n break;\n }\n };\n\n /**\n * Called when the peer connection is #ready.\n *\n * @private\n * @example\n */\n #ready = () => {\n // Perform callback with data\n this.conn.on('data', this.#onIncomingData);\n this.conn.on('close', () => {\n console.log('Connection reset<br>Awaiting connection...');\n this.conn = null;\n });\n };\n\n /** .\n * .\n * .\n * Debug method for downloading the recorded audio\n *\n * @public\n * @example\n */\n downloadData = () => {\n this.ac.downloadData();\n };\n}\n\n/* \nReferenced links:\nhttps://stackoverflow.com/questions/28016664/when-you-pass-this-as-an-argument/28016676#28016676\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes\nhttps://stackoverflow.com/questions/879152/how-do-i-make-javascript-beep [3]\n*/\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (Speaker);\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/peer-connection/speaker.js?");
800
800
 
801
801
  /***/ }),
802
802
 
@@ -818,7 +818,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var axio
818
818
  /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
819
819
 
820
820
  "use strict";
821
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _audioRecorder__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./audioRecorder */ \"./src/tasks/audioRecorder.js\");\n/* harmony import */ var _server_PythonServerAPI__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../server/PythonServerAPI */ \"./src/server/PythonServerAPI.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils */ \"./src/utils.js\");\n/* eslint-disable no-await-in-loop */\n\n\n\n\n/**\n * .\n * .\n * .\n * Provides methods for calibrating the user's speakers\n *\n * @extends AudioRecorder\n */\nclass AudioCalibrator extends _audioRecorder__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n /**\n *\n * @param numCaptures\n * @param numMLSPerCapture\n * @example\n */\n constructor(numCaptures = 1, numMLSPerCapture = 1) {\n super();\n this.numCaptures = numCaptures;\n this.numMLSPerCapture = numMLSPerCapture;\n this.pyServerAPI = new _server_PythonServerAPI__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\n }\n\n /** @private */\n #isCalibrating = false;\n\n /** @private */\n sourceAudioContext;\n\n /** @private */\n sourceAudioContextConvolved;\n\n /** @protected */\n numCalibratingRounds = 1;\n\n /** @protected */\n numSuccessfulCaptured = 0;\n\n /** @private */\n sourceSamplingRate;\n\n /** @protected */\n calibrationNodes = [];\n\n /** @protected */\n calibrationNodesConvolved = [];\n\n /** @protected */\n localAudio;\n\n /**\n * Called when a call is received.\n * Creates a local audio DOM element and attaches it to the page.\n *\n * @param targetElement\n * @example\n */\n createLocalAudio = targetElement => {\n this.localAudio = document.createElement('audio');\n this.localAudio.setAttribute('id', 'localAudio');\n targetElement.appendChild(this.localAudio);\n };\n\n /**\n *\n * @param {MediaStream} stream\n * @param {Function} playCalibrationAudio - (async) function that plays the calibration audio\n * @param {*} beforePlay - (async) function that is called before playing the audio\n * @param {*} beforeRecord - (async) function that is called before recording\n * @param {*} duringRecord - (async) function that is called while recording\n * @param {*} afterRecord - (async) function that is called after recording\n * @example\n */\n calibrationSteps = async (\n stream,\n playCalibrationAudio,\n beforePlay = async () => {},\n beforeRecord = async () => {},\n loopCondition = () => false,\n duringRecord = async () => {},\n afterRecord = async () => {},\n mode\n ) => {\n this.numSuccessfulCaptured = 0;\n\n // do something before playing such as using the MLS to fill the buffers\n console.warn('beforePlay');\n await beforePlay();\n\n // play calibration audio\n console.warn('playCalibrationAudio');\n playCalibrationAudio();\n\n // do something before recording such as awaiting a certain amount of time\n console.warn('beforeRecord');\n await beforeRecord();\n\n // calibration loop\n while (loopCondition()) {\n // start recording\n console.warn('startRecording');\n await this.startRecording(stream);\n\n // do something during the recording such as sleep n amount of time\n console.warn('duringRecord');\n await duringRecord();\n\n // when done, stop recording\n console.warn('stopRecording');\n await this.stopRecording(mode);\n\n // do something after recording such as start processing values\n console.warn('afterRecord');\n await afterRecord();\n\n // eslint-disable-next-line no-await-in-loop\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1);\n }\n };\n\n /**\n *\n * @param {MediaStream} stream\n * @param {Function} playCalibrationAudio - (async) function that plays the calibration audio\n * @param {*} beforeRecord - (async) function that is called before recording\n * @param {*} afterRecord - (async) function that is called after recording\n * @param {Number} gainValue - the gain value to set the gain node to\n */\n volumeCalibrationSteps = async (\n stream,\n playCalibrationAudio,\n beforeRecord = () => {},\n afterRecord = () => {},\n gainValue,\n lCalib = 104.92978421490648\n ) => {\n this.numCalibratingRoundsCompleted = 0;\n\n // calibration loop\n while (!this.#isCalibrating && this.numCalibratingRoundsCompleted < this.numCalibratingRounds) {\n // before recording\n await beforeRecord(gainValue);\n\n // start recording\n await this.startRecording(stream);\n\n // play calibration audio\n console.log(`Calibration Round ${this.numCalibratingRoundsCompleted}`);\n await playCalibrationAudio();\n\n // when done, stop recording\n console.log('Calibration Round Complete');\n await this.stopRecording();\n\n // after recording\n await afterRecord(lCalib);\n\n this.calibrationNodes = [];\n\n // eslint-disable-next-line no-await-in-loop\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(2);\n this.numCalibratingRoundsCompleted += 1;\n }\n };\n\n /**\n * Getter for the isCalibrating property.\n *\n * @public\n * @returns - True if the audio is being calibrated, false otherwise.\n * @example\n */\n getCalibrationStatus = () => this.#isCalibrating;\n\n /** .\n * .\n * .\n * Set the sampling rate to the value received from the listener\n *\n * @param {*} sinkSamplingRate\n * @param samplingRate\n * @example\n */\n setSamplingRates = samplingRate => {\n this.sinkSamplingRate = samplingRate;\n this.sourceSamplingRate = samplingRate;\n\n // this.emit('update', {message: `sampling at ${samplingRate}Hz...`});\n };\n\n sampleRatesSet = () => this.sourceSamplingRate && this.sinkSamplingRate;\n\n addCalibrationNode = node => {\n this.calibrationNodes.push(node);\n };\n\n addCalibrationNodeConvolved = node => {\n \n this.calibrationNodesConvolved.push(node);\n }\n\n makeNewSourceAudioContext = () => {\n const options = {\n sampleRate: this.sourceSamplingRate,\n };\n\n this.sourceAudioContext = new (window.AudioContext ||\n window.webkitAudioContext ||\n window.audioContext)(options);\n\n return this.sourceAudioContext;\n };\n\n makeNewSourceAudioContextConvolved = () => {\n const options = {\n sampleRate: this.sourceSamplingRate,\n };\n\n this.sourceAudioContextConvolved = new (window.AudioContext ||\n window.webkitAudioContext ||\n window.audioContext)(options);\n\n return this.sourceAudioContextConvolved;\n };\n\n\n\n /** .\n * .\n * .\n * Download the result of the calibration roudns\n *\n * @example\n */\n downloadData = () => {\n const recordings = this.getAllRecordedSignals();\n const i = recordings.length - 1;\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[i], `recordedMLSignal_${i}_unconvolved.csv`);\n };\n downloadSingleUnfilteredRecording = () => {\n const recordings = this.getAllRecordedSignals();\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[0], `recordedMLSignal_unconvolved.csv`);\n }\n downloadSingleFilteredRecording = () => {\n const recordings = this.getAllFilteredRecordedSignals();\n console.log(\"Single filtered recording should be of length: \" + recordings[0].length);\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[0], `recordedMLSignal_convolved.csv`);\n }\n downloadUnfilteredRecordings = () => {\n const recordings = this.getAllRecordedSignals();\n console.log(\"unfilterd download?\");\n for (let i = 0; i < recordings.length; i++){\n console.log(i);\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[i], `recordedMLSignal_${i}_unconvolved.csv`);\n }\n };\n downloadFilteredRecordings = () => {\n const recordings = this.getAllFilteredRecordedSignals();\n for (let i = 0; i < recordings.length; i++){\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[i], `recordedMLSignal_${i}_convolved.csv`);\n }\n };\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (AudioCalibrator);\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/audioCalibrator.js?");
821
+ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _audioRecorder__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./audioRecorder */ \"./src/tasks/audioRecorder.js\");\n/* harmony import */ var _server_PythonServerAPI__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../server/PythonServerAPI */ \"./src/server/PythonServerAPI.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils */ \"./src/utils.js\");\n/* eslint-disable no-await-in-loop */\n\n\n\n\n/**\n * .\n * .\n * .\n * Provides methods for calibrating the user's speakers\n *\n * @extends AudioRecorder\n */\nclass AudioCalibrator extends _audioRecorder__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n /**\n *\n * @param numCaptures\n * @param numMLSPerCapture\n * @example\n */\n constructor(numCaptures = 1, numMLSPerCapture = 1) {\n super();\n this.numCaptures = numCaptures;\n this.numMLSPerCapture = numMLSPerCapture;\n this.pyServerAPI = new _server_PythonServerAPI__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\n }\n\n /** @private */\n #isCalibrating = false;\n\n /** @private */\n sourceAudioContext;\n\n /** @private */\n sourceAudioContextConvolved;\n\n /** @protected */\n numCalibratingRounds = 1;\n\n /** @protected */\n numSuccessfulCaptured = 0;\n\n /** @private */\n sourceSamplingRate;\n\n /** @protected */\n calibrationNodes = [];\n\n /** @protected */\n calibrationNodesConvolved = [];\n\n /** @protected */\n localAudio;\n\n /**\n * Called when a call is received.\n * Creates a local audio DOM element and attaches it to the page.\n *\n * @param targetElement\n * @example\n */\n createLocalAudio = targetElement => {\n this.localAudio = document.createElement('audio');\n this.localAudio.setAttribute('id', 'localAudio');\n targetElement.appendChild(this.localAudio);\n };\n\n /**\n *\n * @param {MediaStream} stream\n * @param {Function} playCalibrationAudio - (async) function that plays the calibration audio\n * @param {*} beforePlay - (async) function that is called before playing the audio\n * @param {*} beforeRecord - (async) function that is called before recording\n * @param {*} duringRecord - (async) function that is called while recording\n * @param {*} afterRecord - (async) function that is called after recording\n * @example\n */\n calibrationSteps = async (\n stream,\n playCalibrationAudio,\n beforePlay = async () => {},\n beforeRecord = async () => {},\n loopCondition = () => false,\n duringRecord = async () => {},\n afterRecord = async () => {},\n mode,\n checkRec\n ) => {\n this.numSuccessfulCaptured = 0;\n\n // do something before playing such as using the MLS to fill the buffers\n console.warn('beforePlay');\n await beforePlay();\n\n // play calibration audio\n console.warn('playCalibrationAudio');\n playCalibrationAudio();\n\n // do something before recording such as awaiting a certain amount of time\n console.warn('beforeRecord');\n await beforeRecord();\n\n // calibration loop\n while (loopCondition()) {\n // start recording\n console.warn('startRecording');\n await this.startRecording(stream);\n\n // do something during the recording such as sleep n amount of time\n console.warn('duringRecord');\n await duringRecord();\n\n // when done, stop recording\n console.warn('stopRecording');\n await this.stopRecording(mode,checkRec);\n\n // do something after recording such as start processing values\n console.warn('afterRecord');\n await afterRecord();\n\n // eslint-disable-next-line no-await-in-loop\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1);\n }\n };\n\n /**\n *\n * @param {MediaStream} stream\n * @param {Function} playCalibrationAudio - (async) function that plays the calibration audio\n * @param {*} beforeRecord - (async) function that is called before recording\n * @param {*} afterRecord - (async) function that is called after recording\n * @param {Number} gainValue - the gain value to set the gain node to\n */\n volumeCalibrationSteps = async (\n stream,\n playCalibrationAudio,\n beforeRecord = () => {},\n afterRecord = () => {},\n gainValue,\n lCalib = 104.92978421490648,\n checkRec\n ) => {\n this.numCalibratingRoundsCompleted = 0;\n\n // calibration loop\n while (!this.#isCalibrating && this.numCalibratingRoundsCompleted < this.numCalibratingRounds) {\n // before recording\n await beforeRecord(gainValue);\n\n // start recording\n await this.startRecording(stream);\n\n // play calibration audio\n console.log(`Calibration Round ${this.numCalibratingRoundsCompleted}`);\n await playCalibrationAudio();\n\n // when done, stop recording\n console.log('Calibration Round Complete');\n await this.stopRecording('volume',checkRec);\n\n // after recording\n await afterRecord(lCalib);\n\n this.calibrationNodes = [];\n\n // eslint-disable-next-line no-await-in-loop\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(2);\n this.numCalibratingRoundsCompleted += 1;\n }\n };\n\n /**\n * Getter for the isCalibrating property.\n *\n * @public\n * @returns - True if the audio is being calibrated, false otherwise.\n * @example\n */\n getCalibrationStatus = () => this.#isCalibrating;\n\n /** .\n * .\n * .\n * Set the sampling rate to the value received from the listener\n *\n * @param {*} sinkSamplingRate\n * @param samplingRate\n * @example\n */\n setSamplingRates = samplingRate => {\n this.sinkSamplingRate = samplingRate;\n this.sourceSamplingRate = samplingRate;\n\n // this.emit('update', {message: `sampling at ${samplingRate}Hz...`});\n };\n\n sampleRatesSet = () => this.sourceSamplingRate && this.sinkSamplingRate;\n\n addCalibrationNode = node => {\n this.calibrationNodes.push(node);\n };\n\n addCalibrationNodeConvolved = node => {\n \n this.calibrationNodesConvolved.push(node);\n }\n\n makeNewSourceAudioContext = () => {\n const options = {\n sampleRate: this.sourceSamplingRate,\n };\n\n this.sourceAudioContext = new (window.AudioContext ||\n window.webkitAudioContext ||\n window.audioContext)(options);\n\n return this.sourceAudioContext;\n };\n\n makeNewSourceAudioContextConvolved = () => {\n const options = {\n sampleRate: this.sourceSamplingRate,\n };\n\n this.sourceAudioContextConvolved = new (window.AudioContext ||\n window.webkitAudioContext ||\n window.audioContext)(options);\n\n return this.sourceAudioContextConvolved;\n };\n\n\n\n /** .\n * .\n * .\n * Download the result of the calibration roudns\n *\n * @example\n */\n downloadData = () => {\n const recordings = this.getAllRecordedSignals();\n const i = recordings.length - 1;\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[i], `recordedMLSignal_${i}_unconvolved.csv`);\n };\n downloadSingleUnfilteredRecording = () => {\n const recordings = this.getAllRecordedSignals();\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[recordings.length-1], `recordedMLSignal_unconvolved.csv`);\n }\n downloadSingleFilteredRecording = () => {\n const recordings = this.getAllFilteredRecordedSignals();\n console.log(\"Single filtered recording should be of length: \" + recordings[0].length);\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[0], `recordedMLSignal_convolved.csv`);\n }\n downloadUnfilteredRecordings = () => {\n const recordings = this.getAllRecordedSignals();\n console.log(\"unfilterd download?\");\n for (let i = 0; i < recordings.length; i++){\n console.log(i);\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[i], `recordedMLSignal_${i}_unconvolved.csv`);\n }\n };\n downloadFilteredRecordings = () => {\n const recordings = this.getAllFilteredRecordedSignals();\n for (let i = 0; i < recordings.length; i++){\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[i], `recordedMLSignal_${i}_convolved.csv`);\n }\n };\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (AudioCalibrator);\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/audioCalibrator.js?");
822
822
 
823
823
  /***/ }),
824
824
 
@@ -829,7 +829,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _aud
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 _myEventEmitter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../myEventEmitter */ \"./src/myEventEmitter.js\");\n\n\n/**\n * @class provides a simple interface for recording audio from a microphone\n * using the Media Recorder API.\n */\nclass AudioRecorder extends _myEventEmitter__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n /** @private */\n #mediaRecorder;\n\n /** @private */\n #recordedChunks = [];\n\n /** @private */\n #audioBlob;\n\n /** @private */\n #audioContext;\n\n /** @private */\n #recordedSignals = [];\n\n /** @private */\n #filteredRecordings = [];\n\n /** @private */\n sinkSamplingRate;\n\n /**\n * Decode the audio data from the recorded audio blob.\n *\n * @private\n * @example\n */\n #saveRecording = async () => {\n const arrayBuffer = await this.#audioBlob.arrayBuffer();\n const audioBuffer = await this.#audioContext.decodeAudioData(arrayBuffer);\n const data = audioBuffer.getChannelData(0);\n\n console.log(`Decoded audio buffer with ${data.length} samples`);\n console.log(`Unfiltered recording should be of length: ${data.length}`);\n this.#recordedSignals.push(Array.from(data));\n };\n\n #saveFilteredRecording = async () => {\n const arrayBuffer = await this.#audioBlob.arrayBuffer();\n const audioBuffer = await this.#audioContext.decodeAudioData(arrayBuffer);\n const data = audioBuffer.getChannelData(0);\n\n console.log(`Decoded audio buffer with ${data.length} samples`);\n console.log(`Filtered recording should be of length: ${data.length}`);\n this.#filteredRecordings.push(Array.from(data));\n };\n\n /**\n * Event listener triggered when data is available in the media recorder.\n *\n * @private\n * @param e - The event object.\n * @example\n */\n #onRecorderDataAvailable = e => {\n if (e.data && e.data.size > 0) this.#recordedChunks.push(e.data);\n };\n\n /**\n * Method to create a media recorder object and set up event listeners.\n *\n * @private\n * @param stream - The stream of audio from the Listener.\n * @example\n */\n #setMediaRecorder = stream => {\n // Create a new MediaRecorder object\n this.#mediaRecorder = new MediaRecorder(stream);\n\n // Add event listeners\n this.#mediaRecorder.ondataavailable = e => this.#onRecorderDataAvailable(e);\n };\n\n #setAudioContext = () => {\n this.#audioContext = new (window.AudioContext ||\n window.webkitAudioContext ||\n window.audioContext)({\n sampleRate: this.sinkSamplingRate,\n //sampleRate: 96000\n });\n };\n\n /**\n * Public method to start the recording process.\n *\n * @param stream - The stream of audio from the Listener.\n * @example\n */\n startRecording = async stream => {\n // Create a fresh audio context\n this.#setAudioContext();\n // Set up media recorder if needed\n if (!this.#mediaRecorder) this.#setMediaRecorder(stream);\n // clear recorded chunks\n this.#recordedChunks = [];\n // start recording\n this.#mediaRecorder.start();\n };\n\n /**\n * Method to stop the recording process.\n *\n * @public\n * @example\n */\n stopRecording = async (mode) => {\n // Stop the media recorder, and wait for the data to be available\n await new Promise(resolve => {\n this.#mediaRecorder.onstop = () => {\n // when the stop event is triggered, resolve the promise\n this.#audioBlob = new Blob(this.#recordedChunks, {\n type: 'audio/wav; codecs=opus',\n });\n resolve(this.#audioBlob);\n };\n // call stop\n this.#mediaRecorder.stop();\n });\n // Now that we have data, save it\n if (mode === 'filtered'){\n await this.#saveFilteredRecording();\n }else{\n await this.#saveRecording();\n }\n };\n\n /** .\n * .\n * .\n * Public method to get the last recorded audio signal\n *\n * @returns\n * @example\n */\n getLastRecordedSignal = () => this.#recordedSignals[this.#recordedSignals.length - 1];\n\n /** .\n * .\n * .\n * Public method to get all the recorded audio signals\n *\n * @returns\n * @example\n */\n getAllRecordedSignals = () => this.#recordedSignals;\n\n /** .\n * .\n * .\n * Public method to get all the recorded audio signals\n *\n * @returns\n * @example\n */\n getAllFilteredRecordedSignals = () => this.#filteredRecordings;\n\n /** .\n * .\n * .\n * Public method to set the sampling rate used by the capture device\n *\n * @param {Number} sinkSamplingRate - The sampling rate of the capture device\n * @example\n */\n setSinkSamplingRate = sinkSamplingRate => {\n this.sinkSamplingRate = sinkSamplingRate;\n };\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (AudioRecorder);\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/audioRecorder.js?");
832
+ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _myEventEmitter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../myEventEmitter */ \"./src/myEventEmitter.js\");\n\n\n/**\n * @class provides a simple interface for recording audio from a microphone\n * using the Media Recorder API.\n */\nclass AudioRecorder extends _myEventEmitter__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n /** @private */\n #mediaRecorder;\n\n /** @private */\n #recordedChunks = [];\n\n /** @private */\n #audioBlob;\n\n /** @private */\n #audioContext;\n\n /** @private */\n #recordedSignals = [];\n\n /** @private */\n #filteredRecordings = [];\n\n /** @private */\n sinkSamplingRate;\n\n /**\n * Decode the audio data from the recorded audio blob.\n *\n * @private\n * @example\n */\n #saveRecording = async (checkRec) => {\n const arrayBuffer = await this.#audioBlob.arrayBuffer();\n const audioBuffer = await this.#audioContext.decodeAudioData(arrayBuffer);\n const data = audioBuffer.getChannelData(0);\n const dataArray = Array.from(data);\n\n console.log(`Decoded audio buffer with ${data.length} samples`);\n console.log(`Unfiltered recording should be of length: ${data.length}`);\n if (checkRec == 'loudest'){\n const uniqueSet = new Set(dataArray);\n const numberOfUniqueValues = uniqueSet.size;\n const squaredValues = dataArray.map(value => value * value);\n const sum_of_squares = squaredValues.reduce((total, value) => total + value, 0);\n const squared_mean = sum_of_squares / dataArray.length;\n const dbLevel = 20 * Math.log10(Math.sqrt(squared_mean));\n console.log(\"Loudest 1000-Hz recording: \" + dbLevel + \" with \" + numberOfUniqueValues + \" unique values.\")\n }else if (checkRec == 'allhz'){\n const uniqueSet = new Set(dataArray);\n const numberOfUniqueValues = uniqueSet.size;\n const squaredValues = dataArray.map(value => value * value);\n const sum_of_squares = squaredValues.reduce((total, value) => total + value, 0);\n const squared_mean = sum_of_squares / dataArray.length;\n const dbLevel = 20 * Math.log10(Math.sqrt(squared_mean));\n console.log(\"All Hz Recording: \" + dbLevel + \" with \" + numberOfUniqueValues + \" unique values.\")\n }\n this.#recordedSignals.push(dataArray);\n };\n\n #saveFilteredRecording = async () => {\n const arrayBuffer = await this.#audioBlob.arrayBuffer();\n const audioBuffer = await this.#audioContext.decodeAudioData(arrayBuffer);\n const data = audioBuffer.getChannelData(0);\n\n console.log(`Decoded audio buffer with ${data.length} samples`);\n console.log(`Filtered recording should be of length: ${data.length}`);\n this.#filteredRecordings.push(Array.from(data));\n };\n\n /**\n * Event listener triggered when data is available in the media recorder.\n *\n * @private\n * @param e - The event object.\n * @example\n */\n #onRecorderDataAvailable = e => {\n if (e.data && e.data.size > 0) this.#recordedChunks.push(e.data);\n };\n\n /**\n * Method to create a media recorder object and set up event listeners.\n *\n * @private\n * @param stream - The stream of audio from the Listener.\n * @example\n */\n #setMediaRecorder = stream => {\n // Create a new MediaRecorder object\n this.#mediaRecorder = new MediaRecorder(stream);\n\n // Add event listeners\n this.#mediaRecorder.ondataavailable = e => this.#onRecorderDataAvailable(e);\n };\n\n #setAudioContext = () => {\n this.#audioContext = new (window.AudioContext ||\n window.webkitAudioContext ||\n window.audioContext)({\n sampleRate: this.sinkSamplingRate,\n //sampleRate: 96000\n });\n };\n\n /**\n * Public method to start the recording process.\n *\n * @param stream - The stream of audio from the Listener.\n * @example\n */\n startRecording = async stream => {\n // Create a fresh audio context\n this.#setAudioContext();\n // Set up media recorder if needed\n if (!this.#mediaRecorder) this.#setMediaRecorder(stream);\n // clear recorded chunks\n this.#recordedChunks = [];\n // start recording\n this.#mediaRecorder.start();\n };\n\n /**\n * Method to stop the recording process.\n *\n * @public\n * @example\n */\n stopRecording = async (mode,checkRec) => {\n // Stop the media recorder, and wait for the data to be available\n await new Promise(resolve => {\n this.#mediaRecorder.onstop = () => {\n // when the stop event is triggered, resolve the promise\n this.#audioBlob = new Blob(this.#recordedChunks, {\n type: 'audio/wav; codecs=opus',\n });\n resolve(this.#audioBlob);\n };\n // call stop\n this.#mediaRecorder.stop();\n });\n // Now that we have data, save it\n if (mode === 'filtered'){\n await this.#saveFilteredRecording();\n }else{\n await this.#saveRecording(checkRec);\n }\n };\n\n /** .\n * .\n * .\n * Public method to get the last recorded audio signal\n *\n * @returns\n * @example\n */\n getLastRecordedSignal = () => this.#recordedSignals[this.#recordedSignals.length - 1];\n\n /** .\n * .\n * .\n * Public method to get all the recorded audio signals\n *\n * @returns\n * @example\n */\n getAllRecordedSignals = () => this.#recordedSignals;\n\n /** .\n * .\n * .\n * Public method to get all the recorded audio signals\n *\n * @returns\n * @example\n */\n getAllFilteredRecordedSignals = () => this.#filteredRecordings;\n\n /** .\n * .\n * .\n * Public method to set the sampling rate used by the capture device\n *\n * @param {Number} sinkSamplingRate - The sampling rate of the capture device\n * @example\n */\n setSinkSamplingRate = sinkSamplingRate => {\n this.sinkSamplingRate = sinkSamplingRate;\n };\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (AudioRecorder);\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/audioRecorder.js?");
833
833
 
834
834
  /***/ }),
835
835
 
@@ -840,7 +840,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _myE
840
840
  /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
841
841
 
842
842
  "use strict";
843
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../audioCalibrator */ \"./src/tasks/audioCalibrator.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils */ \"./src/utils.js\");\n/* harmony import */ var _config_firebase__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../config/firebase */ \"./src/config/firebase.js\");\n/* harmony import */ var firebase_database__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! firebase/database */ \"./node_modules/firebase/database/dist/esm/index.esm.js\");\n\n\n\n\n\n\n/**\n *\n */\nclass Combination extends _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n /**\n * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.\n *\n * @param {Object<boolean, number, number, number>} calibratorParams - paramter object\n * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures\n * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated\n * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform\n * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture\n */\n constructor({\n download = false,\n mlsOrder = 18,\n numCaptures = 3,\n numMLSPerCapture = 4,\n lowHz = 20,\n highHz = 10000,\n }) {\n super(numCaptures, numMLSPerCapture);\n this.#mlsOrder = parseInt(mlsOrder, 10);\n this.#P = 2 ** mlsOrder - 1;\n this.#download = download;\n this.#mls = [];\n this.#lowHz = lowHz;\n this.#highHz = highHz;\n }\n\n /** @private */\n stepNum = 0;\n\n /** @private */\n totalSteps = 25;\n\n /** @private */\n #download;\n\n /** @private */\n #mlsGenInterface;\n\n /** @private */\n #mlsBufferView;\n\n /** @private */\n componentInvertedImpulseResponse = null;\n\n /** @private */\n systemInvertedImpulseResponse = null;\n\n //averaged and subtracted ir returned from calibration used to calculated iir\n /** @private */\n ir = null;\n\n /** @private */\n impulseResponses = [];\n\n /** @private */\n #mlsOrder;\n\n /** @private */\n #lowHz;\n\n /** @private */\n #highHz;\n\n /** @private */\n #mls;\n\n /** @private */\n #P;\n\n /** @private */\n #audioContext;\n\n /** @private */\n TAPER_SECS = 5;\n\n /** @private */\n offsetGainNode;\n\n /** @private */\n componentConvolution;\n\n /** @private */\n systemConvolution;\n\n ////////////////////////volume\n /** @private */\n #CALIBRATION_TONE_FREQUENCY = 1000; // Hz\n\n /** @private */\n #CALIBRATION_TONE_TYPE = 'sine';\n\n /** @private */\n #CALIBRATION_TONE_DURATION = 5; // seconds\n\n /** @private */\n outDBSPL = null;\n THD = null;\n outDBSPL1000 = null;\n\n /** @private */\n TAPER_SECS = 0.01; // seconds\n\n /** @private */\n status_denominator = 8;\n\n /** @private */\n status_numerator = 0;\n\n /** @private */\n percent_complete = 0;\n\n /** @private */\n status = ``;\n\n /**@private */\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>`;\n\n /**@private */\n componentIR = null;\n\n /**@private */\n oldComponentIR = null;\n\n /**@private */\n systemIR = null;\n\n /**@private */\n _calibrateSoundCheck = '';\n\n deviceType = null;\n\n deviceName = null;\n\n deviceInfo = null;\n\n desired_time_per_mls = 0;\n\n num_mls_to_skip = 0;\n\n desired_sampling_rate = 0;\n\n #currentConvolution = [];\n\n mode = 'unfiltered';\n\n sourceNode;\n\n autocorrelations = [];\n\n iirLength = 0;\n\n /**generate string template that gets reevaluated as variable increases */\n generateTemplate = () => {\n if (this.percent_complete > 100) {\n this.percent_complete = 100;\n }\n const template = `<div style=\"display: flex; justify-content: center;\"><div style=\"width: 800px; 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>`;\n return template;\n };\n\n /** increment numerator and percent for status bar */\n incrementStatusBar = () => {\n this.status_numerator += 1;\n this.percent_complete = (this.status_numerator / this.status_denominator) * 100;\n };\n\n setDeviceType = deviceType => {\n this.deviceType = deviceType;\n };\n\n setDeviceName = deviceName => {\n this.deviceName = deviceName;\n };\n\n setDeviceInfo = deviceInfo => {\n this.deviceInfo = deviceInfo;\n };\n\n /** .\n * .\n * .\n * Sends all the computed impulse responses to the backend server for processing\n *\n * @returns sets the resulting inverted impulse response to the class property\n * @example\n */\n sendSystemImpulseResponsesToServerForProcessing = async () => {\n const computedIRs = await Promise.all(this.impulseResponses);\n const filteredComputedIRs = computedIRs.filter(element => {\n return element != undefined;\n });\n const mls = this.#mls;\n const lowHz = this.#lowHz;\n const highHz = this.#highHz;\n const iirLength = this.iirLength;\n const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;\n this.stepNum += 1;\n console.log('send impulse responses to server: ' + this.stepNum);\n this.status =\n `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n return this.pyServerAPI\n .getSystemInverseImpulseResponseWithRetry({\n payload: filteredComputedIRs.slice(0, this.numCaptures),\n mls,\n lowHz,\n highHz,\n iirLength,\n num_periods,\n sampleRate: this.sourceSamplingRate || 96000,\n })\n .then(res => {\n console.log(res);\n this.stepNum += 1;\n console.log('got impulse response ' + this.stepNum);\n this.incrementStatusBar();\n this.status =\n `All Hz Calibration: done computing the IIR...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n this.systemInvertedImpulseResponse = res['iir'];\n this.systemIR = res['ir'];\n this.systemConvolution = res['convolution'];\n })\n .catch(err => {\n console.error(err);\n });\n };\n\n /** .\n * .\n * .\n * Sends all the computed impulse responses to the backend server for processing\n *\n * @returns sets the resulting inverted impulse response to the class property\n * @example\n */\n sendComponentImpulseResponsesToServerForProcessing = async () => {\n const computedIRs = await Promise.all(this.impulseResponses);\n const filteredComputedIRs = computedIRs.filter(element => {\n return element != undefined;\n });\n const componentIRGains = this.componentIR['Gain'];\n const componentIRFreqs = this.componentIR['Freq'];\n const mls = this.#mls;\n const lowHz = this.#lowHz;\n const iirLength = this.iirLength;\n const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;\n const highHz = this.#highHz;\n this.stepNum += 1;\n console.log('send impulse responses to server: ' + this.stepNum);\n this.status =\n `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n return this.pyServerAPI\n .getComponentInverseImpulseResponseWithRetry({\n payload: filteredComputedIRs.slice(0, this.numCaptures),\n mls,\n lowHz,\n highHz,\n iirLength,\n componentIRGains,\n componentIRFreqs,\n num_periods,\n sampleRate: this.sourceSamplingRate || 96000,\n })\n .then(res => {\n console.log(res);\n this.stepNum += 1;\n console.log('got impulse response ' + this.stepNum);\n this.incrementStatusBar();\n this.status =\n `All Hz Calibration: done computing the IIR...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n this.componentInvertedImpulseResponse = res['iir'];\n this.componentIR['Gain'] = res['ir'];\n this.componentIR['Freq'] = res['frequencies'];\n this.componentConvolution = res['convolution'];\n })\n .catch(err => {\n // this.emit('InvertedImpulseResponse', {res: false});\n console.error(err);\n });\n };\n\n /** .\n * .\n * .\n * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing\n *\n * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed\n * @example\n */\n sendRecordingToServerForProcessing = signalCsv => {\n const allSignals = this.getAllRecordedSignals();\n const numSignals = allSignals.length;\n const mls = this.#mls;\n const payload =\n signalCsv && signalCsv.length > 0 ? (0,_utils__WEBPACK_IMPORTED_MODULE_1__.csvToArray)(signalCsv) : allSignals[numSignals - 1];\n console.log('sending rec');\n this.stepNum += 1;\n console.log('send rec ' + this.stepNum);\n this.status =\n `All Hz Calibration Step: computing the IR of the last recording...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n this.impulseResponses.push(\n this.pyServerAPI\n .getImpulseResponse({\n sampleRate: this.sourceSamplingRate || 96000,\n payload,\n mls,\n P: this.#P,\n numPeriods: this.numMLSPerCapture,\n })\n .then(res => {\n if (this.numSuccessfulCaptured < this.numCaptures) {\n this.numSuccessfulCaptured += 1;\n console.log('num succ capt: ' + this.numSuccessfulCaptured);\n this.stepNum += 1;\n console.log('got impulse response ' + this.stepNum);\n this.incrementStatusBar();\n this.status =\n `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {\n message: this.status,\n });\n this.autocorrelations.push(res['autocorrelation']);\n return res['ir'];\n }\n })\n .catch(err => {\n console.error(err);\n })\n );\n };\n\n /**\n * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number\n * of MLS periods defined in the constructor.\n *\n * @example\n */\n #awaitDesiredMLSLength = async () => {\n // seconds per MLS = P / SR\n // await N * P / SR\n this.stepNum += 1;\n console.log('await desired length ' + this.stepNum);\n this.status =\n `All Hz Calibration: sampling the calibration signal...`.toString() +\n `\\niteration ${this.stepNum}` +\n this.generateTemplate();\n this.emit('update', {\n message: this.status,\n });\n let time_to_wait = 0;\n if (this.mode === 'unfiltered') {\n time_to_wait = (this.#mls.length / this.sourceSamplingRate) * this.numMLSPerCapture;\n time_to_wait= time_to_wait*1.1;\n } else if (this.mode === 'filtered') {\n time_to_wait =\n (this.#currentConvolution.length / this.sourceSamplingRate) * (this.numMLSPerCapture/(this.num_mls_to_skip+this.numMLSPerCapture));\n } else {\n throw new Error('Mode broke in awaitDesiredMLSLength');\n }\n\n await (0,_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(time_to_wait);\n };\n\n /** .\n * .\n * .\n * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state\n *\n * @example\n */\n #awaitSignalOnset = async () => {\n this.stepNum += 1;\n console.log('await signal onset ' + this.stepNum);\n this.status =\n `All Hz Calibration: waiting for the signal to stabilize...`.toString() +\n this.generateTemplate();\n this.emit('update', {\n message: this.status,\n });\n let number_of_bursts_to_skip = this.num_mls_to_skip;\n let time_to_sleep = 0;\n if (this.mode === 'unfiltered') {\n time_to_sleep = (this.#mls.length / this.sourceSamplingRate)*number_of_bursts_to_skip;\n } else if (this.mode === 'filtered') {\n console.log(this.#currentConvolution.length);\n time_to_sleep = (this.#currentConvolution.length / this.sourceSamplingRate)*(number_of_bursts_to_skip/(number_of_bursts_to_skip+this.numMLSPerCapture));\n } else {\n throw new Error('Mode broke in awaitSignalOnset');\n }\n await (0,_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(time_to_sleep);\n };\n\n /**\n * Called immediately after a recording is captured. Used to process the resulting signal\n * whether by sending the result to a server or by computing a result locally.\n *\n * @example\n */\n #afterMLSRecord = () => {\n console.log('after record');\n this.sendRecordingToServerForProcessing();\n };\n\n #afterMLSwIIRRecord = () => {\n if (this.numSuccessfulCaptured < 1) {\n this.numSuccessfulCaptured += 1;\n this.stepNum += 1;\n this.incrementStatusBar();\n console.log('after mls w iir record for some reason add numSucc capt ' + this.stepNum);\n this.status =\n `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {\n message: this.status,\n });\n }\n };\n\n /** .\n * .\n * .\n * Created an S Curver Buffer to taper the signal onset\n *\n * @param {*} length\n * @param {*} phase\n * @returns\n * @example\n */\n static createSCurveBuffer = (length, phase) => {\n const curve = new Float32Array(length);\n let i;\n for (i = 0; i < length; i += 1) {\n // scale the curve to be between 0-1\n curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\n }\n return curve;\n };\n\n static createInverseSCurveBuffer = (length, phase) => {\n const curve = new Float32Array(length);\n let i;\n let j = length - 1;\n for (i = 0; i < length; i += 1) {\n // scale the curve to be between 0-1\n curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;\n j -= 1;\n }\n return curve;\n };\n\n /**\n * Construct a Calibration Node with the calibration parameters.\n *\n * @param dataBuffer\n * @private\n * @example\n */\n #createCalibrationNodeFromBuffer = dataBuffer => {\n console.log('databuffer');\n console.log(dataBuffer);\n if (!this.sourceAudioContext) {\n this.makeNewSourceAudioContext();\n }\n\n const buffer = this.sourceAudioContext.createBuffer(\n 1, // number of channels\n dataBuffer.length,\n this.sourceAudioContext.sampleRate // sample rate\n );\n\n const data = buffer.getChannelData(0); // get data\n // fill the buffer with our data\n try {\n for (let i = 0; i < dataBuffer.length; i += 1) {\n data[i] = dataBuffer[i] * 0.1;\n }\n } catch (error) {\n console.error(error);\n }\n\n this.sourceNode = this.sourceAudioContext.createBufferSource();\n\n this.sourceNode.buffer = buffer;\n if (this.mode === 'filtered'){\n this.sourceNode.loop = false;\n }else{\n this.sourceNode.loop = true;\n }\n \n this.sourceNode.connect(this.sourceAudioContext.destination);\n\n this.addCalibrationNode(this.sourceNode);\n };\n\n /**\n * Given a data buffer, creates the required calibration node\n *\n * @param {*} dataBufferArray\n * @example\n */\n #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {\n if (dataBufferArray.length === 1) {\n this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);\n } else {\n throw new Error('The length of the data buffer array must be 1');\n }\n };\n\n /**\n * Creates an audio context and plays it for a few seconds.\n *\n * @private\n * @returns - Resolves when the audio is done playing.\n * @example\n */\n #playCalibrationAudio = () => {\n\n this.calibrationNodes[0].start(0);\n this.status = ``;\n if (this.mode === 'unfiltered') {\n this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);\n console.log('play calibration audio ' + this.stepNum);\n this.status =\n `All Hz Calibration: playing the calibration tone...`.toString() +\n this.generateTemplate().toString();\n } else if (this.mode === 'filtered') {\n console.log('play convolved audio ' + this.stepNum);\n this.status =\n `All Hz Calibration: playing the convolved calibration tone...`.toString() +\n this.generateTemplate().toString();\n } else {\n throw new Error('Mode is incorrect');\n }\n this.emit('update', {message: this.status});\n this.stepNum += 1;\n console.log('sink sampling rate');\n console.log(this.sinkSamplingRate);\n console.log('source sampling rate');\n console.log(this.sourceSamplingRate);\n };\n\n /** .\n * .\n * .\n * Stops the audio with tapered offset\n *\n * @example\n */\n #stopCalibrationAudio = () => {\n this.calibrationNodes[0].stop(0);\n this.calibrationNodes = [];\n this.sourceNode.disconnect();\n this.stepNum += 1;\n console.log('stop calibration audio ' + this.stepNum);\n this.status =\n `All Hz Calibration: stopping the calibration tone...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n };\n\n playMLSwithIIR = async (stream, iir) => {\n this.mode = 'filtered';\n console.log('play mls with iir');\n this.invertedImpulseResponse = iir;\n\n await this.calibrationSteps(\n stream,\n this.#playCalibrationAudio, // play audio func (required)\n this.#createCalibrationNodeFromBuffer(this.#currentConvolution), // before play func\n this.#awaitSignalOnset, // before record\n () => this.numSuccessfulCaptured < 1,\n this.#awaitDesiredMLSLength, // during record\n this.#afterMLSwIIRRecord, // after record\n this.mode\n );\n };\n\n /**\n * Public method to start the calibration process. Objects intialized from webassembly allocate new memory\n * and must be manually freed. This function is responsible for intializing the MlsGenInterface,\n * and wrapping the calibration steps with a garbage collection safe gaurd.\n *\n * @public\n * @param stream - The stream of audio from the Listener.\n * @example\n */\n startCalibrationImpulseResponse = async stream => {\n let desired_time = this.desired_time_per_mls;\n\n console.log('MLS sequence should be of length: ' + this.sourceSamplingRate * desired_time);\n\n length = this.sourceSamplingRate * desired_time;\n //get mls here\n await this.pyServerAPI\n .getMLSWithRetry(length)\n .then(res => {\n console.log(res);\n this.#mlsBufferView = res['mls'];\n })\n .catch(err => {\n // this.emit('InvertedImpulseResponse', {res: false});\n console.error(err);\n });\n await this.calibrationSteps(\n stream,\n this.#playCalibrationAudio, // play audio func (required)\n this.#createCalibrationNodeFromBuffer(this.#mlsBufferView), // before play func\n this.#awaitSignalOnset, // before record\n () => this.numSuccessfulCaptured < this.numCaptures, // loop while true\n this.#awaitDesiredMLSLength, // during record\n this.#afterMLSRecord, // after record\n this.mode\n ),\n this.#stopCalibrationAudio();\n\n // at this stage we've captured all the required signals,\n // and have received IRs for each one\n // so let's send all the IRs to the server to be converted to a single IIR\n await this.sendSystemImpulseResponsesToServerForProcessing();\n await this.sendComponentImpulseResponsesToServerForProcessing();\n\n this.numSuccessfulCaptured = 0;\n\n let iir_ir_and_plots;\n if (this._calibrateSoundCheck != 'none') {\n if (this._calibrateSoundCheck != 'system') {\n this.#currentConvolution = this.componentConvolution;\n } else {\n this.#currentConvolution = this.systemConvolution;\n }\n await this.playMLSwithIIR(stream, this.invertedImpulseResponse);\n this.#stopCalibrationAudio();\n this.sourceAudioContext.close();\n let conv_recs = this.getAllFilteredRecordedSignals();\n let recs = this.getAllRecordedSignals();\n let unconv_rec = recs[0];\n let conv_rec = conv_recs[0];\n if (this._calibrateSoundCheck != 'system') {\n let knownGain = this.oldComponentIR.Gain;\n let knownFreq = this.oldComponentIR.Freq;\n let sampleRate = this.sourceSamplingRate || 96000;\n let unconv_results = await this.pyServerAPI\n .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)\n .then(res => {\n this.incrementStatusBar();\n this.status =\n `All Hz Calibration: done computing the PSD graphs...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n return res;\n })\n .catch(err => {\n console.error(err);\n });\n\n let conv_results = await this.pyServerAPI\n .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)\n .then(res => {\n this.incrementStatusBar();\n this.status =\n `All Hz Calibration: done computing the PSD graphs...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n return res;\n })\n .catch(err => {\n console.error(err);\n });\n iir_ir_and_plots = {\n systemIIR: this.systemInvertedImpulseResponse,\n componentIIR: this.componentInvertedImpulseResponse,\n x_unconv: unconv_results['x'],\n y_unconv: unconv_results['y'],\n x_conv: conv_results['x'],\n y_conv: conv_results['y'],\n componentIR: this.componentIR,\n systemIR: this.systemIR,\n };\n } else {\n let results = await this.pyServerAPI\n .getPSDWithRetry({\n unconv_rec,\n conv_rec,\n sampleRate: this.sourceSamplingRate || 96000,\n })\n .then(res => {\n this.incrementStatusBar();\n this.status =\n `All Hz Calibration: done computing the PSD graphs...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n return res;\n })\n .catch(err => {\n console.error(err);\n });\n iir_ir_and_plots = {\n systemIIR: this.systemInvertedImpulseResponse,\n componentIIR: this.componentInvertedImpulseResponse,\n x_unconv: results['x_unconv'],\n y_unconv: results['y_unconv'],\n x_conv: results['x_conv'],\n y_conv: results['y_conv'],\n componentIR: this.componentIR,\n systemIR: this.systemIR,\n };\n }\n\n if (this.#download) {\n this.downloadSingleUnfilteredRecording();\n this.downloadSingleFilteredRecording();\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.#mls, 'MLS.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.componentConvolution, 'python_component_convolution_mls_iir.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.systemConvolution, 'python_system_convolution_mls_iir.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.componentInvertedImpulseResponse, 'componentIIR.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.systemInvertedImpulseResponse, 'systemIIR.csv');\n for (let i = 0; i < this.autocorrelations.length; i++) {\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.autocorrelations[i], `autocorrelation_${i}`);\n }\n const computedIRagain = await Promise.all(this.impulseResponses).then(res => {\n for (let i = 0; i < res.length; i++) {\n if (res[i] != undefined) {\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(res[i], `IR_${i}`);\n }\n }\n });\n }\n } else {\n iir_ir_and_plots = {\n systemIIR: this.systemInvertedImpulseResponse,\n componentIIR: this.componentInvertedImpulseResponse,\n x_unconv: [],\n y_unconv: [],\n x_conv: [],\n y_conv: [],\n componentIR: this.componentIR,\n systemIR: this.systemIR,\n };\n if (this.#download) {\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.#mls, 'MLS.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.componentConvolution, 'python_component_convolution_mls_iir.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.systemConvolution, 'python_system_convolution_mls_iir.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.componentInvertedImpulseResponse, 'componentIIR.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.systemInvertedImpulseResponse, 'systemIIR.csv');\n for (let i = 0; i < this.autocorrelations.length; i++) {\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.autocorrelations[i], `autocorrelation_${i}`);\n }\n const computedIRagain = await Promise.all(this.impulseResponses).then(res => {\n for (let i = 0; i < res.length; i++) {\n if (res[i] != undefined) {\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(res[i], `IR_${i}`);\n }\n }\n });\n }\n }\n\n this.percent_complete = 100;\n\n this.status = `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n\n //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR\n //that was used to calibrate\n\n return iir_ir_and_plots;\n };\n\n //////////////////////volume\n\n handleIncomingData = data => {\n console.log('Received data: ', data);\n if (data.type === 'soundGainDBSPL') {\n this.soundGainDBSPL = data.value;\n } else {\n throw new Error(`Unknown data type: ${data.type}`);\n }\n };\n createSCurveBuffer = (onSetBool = true) => {\n const curve = new Float32Array(this.TAPER_SECS * this.sourceSamplingRate + 1);\n const frequency = 1 / (4 * this.TAPER_SECS);\n let j = 0;\n for (let i = 0; i < this.TAPER_SECS * this.sourceSamplingRate + 1; i += 1) {\n const phase = 2 * Math.PI * frequency * j;\n const onsetTaper = Math.pow(Math.sin(phase), 2);\n const offsetTaper = Math.pow(Math.cos(phase), 2);\n curve[i] = onSetBool ? onsetTaper : offsetTaper;\n j += 1 / this.sourceSamplingRate;\n }\n return curve;\n };\n\n #getTruncatedSignal = (left = 3.5, right = 4.5) => {\n const start = Math.floor(left * this.sourceSamplingRate);\n const end = Math.floor(right * this.sourceSamplingRate);\n const result = Array.from(this.getLastRecordedSignal().slice(start, end));\n\n /**\n * function to check that capture was properly made\n * @param {*} list\n */\n const checkResult = list => {\n const setItem = new Set(list);\n if (setItem.size === 1 && setItem.has(0)) {\n console.warn(\n 'The last capture failed, all recorded signal is zero',\n this.getAllRecordedSignals()\n );\n }\n if (setItem.size === 0) {\n console.warn('The last capture failed, no recorded signal');\n }\n };\n checkResult(result);\n return result;\n };\n\n /** \n * \n * \n Construct a calibration Node with the calibration parameters and given gain value\n * @param {*} gainValue\n * */\n #createCalibrationToneWithGainValue = gainValue => {\n const audioContext = this.makeNewSourceAudioContext();\n const oscilator = audioContext.createOscillator();\n const gainNode = audioContext.createGain();\n const taperGainNode = audioContext.createGain();\n const offsetGainNode = audioContext.createGain();\n const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;\n\n oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;\n oscilator.type = this.#CALIBRATION_TONE_TYPE;\n gainNode.gain.value = gainValue;\n\n oscilator.connect(gainNode);\n gainNode.connect(taperGainNode);\n const onsetCurve = this.createSCurveBuffer();\n taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);\n taperGainNode.connect(offsetGainNode);\n const offsetCurve = this.createSCurveBuffer(false);\n offsetGainNode.gain.setValueCurveAtTime(\n offsetCurve,\n totalDuration - this.TAPER_SECS,\n this.TAPER_SECS\n );\n offsetGainNode.connect(audioContext.destination);\n\n this.addCalibrationNode(oscilator);\n };\n\n /**\n * Construct a Calibration Node with the calibration parameters.\n *\n * @private\n * @example\n */\n #createCalibrationNode = () => {\n const audioContext = this.makeNewSourceAudioContext();\n const oscilator = audioContext.createOscillator();\n const gainNode = audioContext.createGain();\n\n oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;\n oscilator.type = this.#CALIBRATION_TONE_TYPE;\n gainNode.gain.value = 0.04;\n\n oscilator.connect(gainNode);\n gainNode.connect(audioContext.destination);\n\n this.addCalibrationNode(oscilator);\n };\n\n #playCalibrationAudioVolume = async () => {\n const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;\n\n this.calibrationNodes[0].start(0);\n this.calibrationNodes[0].stop(totalDuration);\n console.log(`Playing a buffer of ${this.#CALIBRATION_TONE_DURATION} seconds of audio`);\n console.log(`Waiting a total of ${totalDuration} seconds`);\n await (0,_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(totalDuration);\n };\n\n #sendToServerForProcessing = lCalib => {\n console.log('Sending data to server');\n this.pyServerAPI\n .getVolumeCalibration({\n sampleRate: this.sourceSamplingRate,\n payload: this.#getTruncatedSignal(),\n lCalib: lCalib,\n })\n .then(res => {\n if (this.outDBSPL === null) {\n this.incrementStatusBar();\n this.outDBSPL = res['outDbSPL'];\n this.outDBSPL1000 = res['outDbSPL1000'];\n this.THD = res['thd'];\n }\n })\n .catch(err => {\n console.warn(err);\n });\n };\n\n startCalibrationVolume = async (stream, gainValues, lCalib, componentGainDBSPL) => {\n const trialIterations = gainValues.length;\n this.status_denominator += trialIterations;\n const thdValues = [];\n const inDBValues = [];\n let inDB = 0;\n const outDBSPLValues = [];\n const outDBSPL1000Values = [];\n\n // do one calibration that will be discarded\n const soundLevelToDiscard = -60;\n const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);\n this.status =\n `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() +\n this.generateTemplate().toString();\n //this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});\n this.emit('update', {message: this.status});\n\n do {\n // eslint-disable-next-line no-await-in-loop\n await this.volumeCalibrationSteps(\n stream,\n this.#playCalibrationAudioVolume,\n this.#createCalibrationToneWithGainValue,\n this.#sendToServerForProcessing,\n gainToDiscard,\n lCalib //todo make this a class parameter\n );\n } while (this.outDBSPL === null);\n //reset the values\n //this.incrementStatusBar();\n\n this.outDBSPL = null;\n this.outDBSPL = null;\n this.outDBSPL1000 = null;\n this.THD = null;\n\n // run the calibration at different gain values provided by the user\n for (let i = 0; i < trialIterations; i++) {\n //convert gain to DB and add to inDB\n inDB = Math.log10(gainValues[i]) * 20;\n // precision to 1 decimal place\n inDB = Math.round(inDB * 10) / 10;\n inDBValues.push(inDB);\n console.log('next update');\n this.status =\n `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n do {\n // eslint-disable-next-line no-await-in-loop\n await this.volumeCalibrationSteps(\n stream,\n this.#playCalibrationAudioVolume,\n this.#createCalibrationToneWithGainValue,\n this.#sendToServerForProcessing,\n gainValues[i],\n lCalib //todo make this a class parameter\n );\n } while (this.outDBSPL === null);\n outDBSPL1000Values.push(this.outDBSPL1000);\n thdValues.push(this.THD);\n outDBSPLValues.push(this.outDBSPL);\n\n this.outDBSPL = null;\n this.outDBSPL1000 = null;\n this.THD = null;\n }\n\n // get the volume calibration parameters from the server\n const parameters = await this.pyServerAPI\n .getVolumeCalibrationParameters({\n inDBValues: inDBValues,\n outDBSPLValues: outDBSPL1000Values,\n lCalib: lCalib,\n componentGainDBSPL,\n })\n .then(res => {\n this.incrementStatusBar();\n return res;\n });\n const result = {\n parameters: parameters,\n inDBValues: inDBValues,\n outDBSPLValues: outDBSPLValues,\n outDBSPL1000Values: outDBSPL1000Values,\n thdValues: thdValues,\n };\n\n return result;\n };\n\n // function to write frq and gain to firebase database given speakerID\n writeFrqGain = async (speakerID, frq, gain, OEM) => {\n // freq and gain are too large to take samples 1 in every 100 samples\n\n const sampledFrq = [];\n const sampledGain = [];\n for (let i = 0; i < frq.length; i += 100) {\n sampledFrq.push(frq[i]);\n sampledGain.push(gain[i]);\n }\n\n const data = {Freq: sampledFrq, Gain: sampledGain};\n\n await (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.set)((0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_2__[\"default\"], `Microphone2/${OEM}/${speakerID}/linear`), data);\n };\n\n // Function to Read frq and gain from firebase database given speakerID\n // returns an array of frq and gain if speakerID exists, returns null otherwise\n\n readFrqGain = async (speakerID, OEM) => {\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_2__[\"default\"]);\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.child)(dbRef, `Microphone2/${OEM}/${speakerID}/linear`));\n if (snapshot.exists()) {\n return snapshot.val();\n }\n return null;\n };\n\n readGainat1000Hz = async (speakerID, OEM) => {\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_2__[\"default\"]);\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.child)(dbRef, `Microphone2/${OEM}/${speakerID}/Gain1000`));\n if (snapshot.exists()) {\n return snapshot.val();\n }\n return null;\n };\n\n writeGainat1000Hz = async (speakerID, gain, OEM) => {\n const data = {Gain: gain};\n await (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.set)((0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_2__[\"default\"], `Microphone2/${OEM}/${speakerID}/Gain1000`), gain);\n };\n\n writeIsSmartPhone = async (speakerID, isSmartPhone, OEM) => {\n const data = {isSmartPhone: isSmartPhone};\n await (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.set)((0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_2__[\"default\"], `Microphone2/${OEM}/${speakerID}/isSmartPhone`), isSmartPhone);\n };\n\n doesMicrophoneExist = async (speakerID, OEM) => {\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_2__[\"default\"]);\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.child)(dbRef, `Microphone2/${OEM}/${speakerID}`));\n if (snapshot.exists()) {\n return true;\n }\n return false;\n };\n\n convertToDB = gain => {\n return Math.log10(gain) * 20;\n };\n\n // Function to perform linear interpolation between two points\n interpolate(x, x0, y0, x1, y1) {\n return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);\n }\n\n findGainatFrequency = (frequencies, gains, targetFrequency) => {\n // Find the index of the first frequency in the array greater than the target frequency\n let index = 0;\n while (index < frequencies.length && frequencies[index] < targetFrequency) {\n index++;\n }\n\n // Handle cases when the target frequency is outside the range of the given data\n if (index === 0) {\n return gains[0];\n } else if (index === frequencies.length) {\n return gains[gains.length - 1];\n } else {\n // Interpolate the gain based on the surrounding frequencies\n const x0 = frequencies[index - 1];\n const y0 = gains[index - 1];\n const x1 = frequencies[index];\n const y1 = gains[index];\n return this.interpolate(targetFrequency, x0, y0, x1, y1);\n }\n };\n\n // Example of how to use the writeFrqGain and readFrqGain functions\n // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);\n // Speaker1 is the speakerID you want to write to in the database\n // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));\n // MiniDSPUMIK_1 is the speakerID with some Data in the database\n //adding gainDBSPL\n startCalibration = async (\n stream,\n gainValues,\n lCalib = 104.92978421490648,\n componentIR = null,\n microphoneName = 'MiniDSP-UMIK1-711-4754-vertical',\n _calibrateSoundCheck = 'goal', //GOAL PASSed in by default\n isSmartPhone = false,\n _calibrateSoundBurstRepeats = 4,\n _calibrateSoundBurstSec = 1,\n _calibrateSoundBurstsWarmup = 1,\n _calibrateSoundHz = 48000,\n _calibrateSoundIIRSec = 0.2,\n micManufacturer = '',\n micSerialNumber = '',\n micModelNumber = '',\n micModelName = ''\n ) => {\n this.iirLength = Math.floor(_calibrateSoundIIRSec*this.sourceSamplingRate);\n console.log('device info:', this.deviceInfo);\n this.numMLSPerCapture = _calibrateSoundBurstRepeats;\n this.desired_time_per_mls = _calibrateSoundBurstSec;\n this.num_mls_to_skip = _calibrateSoundBurstsWarmup;\n this.desired_sampling_rate = _calibrateSoundHz;\n\n //feed calibration goal here\n this._calibrateSoundCheck = _calibrateSoundCheck;\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\n //check the db based on the microphone currently connected\n\n //new lCalib found at top of calibration files *1000hz, make sure to correct\n //based on zeroing of 1000hz, search for \"*1000Hz\"\n const ID = isSmartPhone ? micModelNumber : micSerialNumber;\n const OEM = isSmartPhone ? this.deviceInfo.OEM : micManufacturer;\n if (componentIR == null) {\n //mode 'ir'\n //global variable this.componentIR must be set\n this.componentIR = await this.readFrqGain(ID, OEM).then(data => {\n return data;\n });\n\n lCalib = await this.readGainat1000Hz(ID, OEM);\n // this.componentGainDBSPL = this.convertToDB(lCalib);\n this.componentGainDBSPL = lCalib;\n //TODO: if this call to database is unknown, cannot perform experiment => return false\n if (this.componentIR == null) {\n this.status =\n `Microphone (${OEM},${ID}) is not found in the database. Please add it to the database.`.toString();\n this.emit('update', {message: this.status});\n return false;\n }\n } else {\n this.componentIR = componentIR;\n lCalib = this.findGainatFrequency(this.componentIR.Freq, this.componentIR.Gain, 1000);\n // this.componentGainDBSPL = this.convertToDB(lCalib);\n this.componentGainDBSPL = lCalib;\n await this.writeGainat1000Hz(ID, lCalib, OEM);\n await this.writeIsSmartPhone(ID, isSmartPhone, OEM);\n }\n\n this.oldComponentIR = this.componentIR;\n\n let volumeResults = await this.startCalibrationVolume(\n stream,\n gainValues,\n lCalib,\n this.componentGainDBSPL\n );\n\n let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);\n\n if (componentIR != null) {\n //insert Freq and Gain from this.componentIR into db\n await this.writeFrqGain(\n ID,\n impulseResponseResults.componentIR.Freq,\n impulseResponseResults.componentIR.Gain,\n OEM\n );\n }\n\n const total_results = {...volumeResults, ...impulseResponseResults};\n\n total_results['micInfo'] = {\n micManufacturer: micManufacturer,\n micSerialNumber: micSerialNumber,\n micModelNumber: micModelNumber,\n micModelName: micModelName,\n ID: ID,\n OEM: OEM,\n };\n console.log('total results');\n console.log(total_results);\n return total_results;\n };\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (Combination);\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/combination/combination.js?");
843
+ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../audioCalibrator */ \"./src/tasks/audioCalibrator.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils */ \"./src/utils.js\");\n/* harmony import */ var _config_firebase__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../config/firebase */ \"./src/config/firebase.js\");\n/* harmony import */ var firebase_database__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! firebase/database */ \"./node_modules/firebase/database/dist/esm/index.esm.js\");\n\n\n\n\n\n\n/**\n *\n */\nclass Combination extends _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n /**\n * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.\n *\n * @param {Object<boolean, number, number, number>} calibratorParams - paramter object\n * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures\n * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated\n * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform\n * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture\n */\n constructor({\n download = false,\n mlsOrder = 18,\n numCaptures = 3,\n numMLSPerCapture = 4,\n lowHz = 20,\n highHz = 10000,\n }) {\n super(numCaptures, numMLSPerCapture);\n this.#mlsOrder = parseInt(mlsOrder, 10);\n this.#P = 2 ** mlsOrder - 1;\n this.#download = download;\n this.#mls = [];\n this.#lowHz = lowHz;\n this.#highHz = highHz;\n }\n\n /** @private */\n stepNum = 0;\n\n /** @private */\n totalSteps = 25;\n\n /** @private */\n #download;\n\n /** @private */\n #mlsGenInterface;\n\n /** @private */\n #mlsBufferView;\n\n /** @private */\n componentInvertedImpulseResponse = null;\n\n /** @private */\n systemInvertedImpulseResponse = null;\n\n //averaged and subtracted ir returned from calibration used to calculated iir\n /** @private */\n ir = null;\n\n /** @private */\n impulseResponses = [];\n\n /** @private */\n #mlsOrder;\n\n /** @private */\n #lowHz;\n\n /** @private */\n #highHz;\n\n /** @private */\n #mls;\n\n /** @private */\n #P;\n\n /** @private */\n #audioContext;\n\n /** @private */\n TAPER_SECS = 5;\n\n /** @private */\n offsetGainNode;\n\n /** @private */\n componentConvolution;\n\n /** @private */\n systemConvolution;\n\n ////////////////////////volume\n /** @private */\n #CALIBRATION_TONE_FREQUENCY = 1000; // Hz\n\n /** @private */\n #CALIBRATION_TONE_TYPE = 'sine';\n\n CALIBRATION_TONE_DURATION = 5; // seconds\n\n /** @private */\n outDBSPL = null;\n THD = null;\n outDBSPL1000 = null;\n\n /** @private */\n TAPER_SECS = 0.01; // seconds\n\n /** @private */\n status_denominator = 8;\n\n /** @private */\n status_numerator = 0;\n\n /** @private */\n percent_complete = 0;\n\n /** @private */\n status = ``;\n\n /**@private */\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>`;\n\n /**@private */\n componentIR = null;\n\n /**@private */\n oldComponentIR = null;\n\n /**@private */\n systemIR = null;\n\n /**@private */\n _calibrateSoundCheck = '';\n\n deviceType = null;\n\n deviceName = null;\n\n deviceInfo = null;\n\n desired_time_per_mls = 0;\n\n num_mls_to_skip = 0;\n\n desired_sampling_rate = 0;\n\n #currentConvolution = [];\n\n mode = 'unfiltered';\n\n sourceNode;\n\n autocorrelations = [];\n\n iirLength = 0;\n\n /**generate string template that gets reevaluated as variable increases */\n generateTemplate = () => {\n if (this.percent_complete > 100) {\n this.percent_complete = 100;\n }\n const template = `<div style=\"display: flex; justify-content: center;\"><div style=\"width: 800px; 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>`;\n return template;\n };\n\n /** increment numerator and percent for status bar */\n incrementStatusBar = () => {\n this.status_numerator += 1;\n this.percent_complete = (this.status_numerator / this.status_denominator) * 100;\n };\n\n setDeviceType = deviceType => {\n this.deviceType = deviceType;\n };\n\n setDeviceName = deviceName => {\n this.deviceName = deviceName;\n };\n\n setDeviceInfo = deviceInfo => {\n this.deviceInfo = deviceInfo;\n };\n\n /** .\n * .\n * .\n * Sends all the computed impulse responses to the backend server for processing\n *\n * @returns sets the resulting inverted impulse response to the class property\n * @example\n */\n sendSystemImpulseResponsesToServerForProcessing = async () => {\n const computedIRs = await Promise.all(this.impulseResponses);\n const filteredComputedIRs = computedIRs.filter(element => {\n return element != undefined;\n });\n const mls = this.#mls;\n const lowHz = this.#lowHz;\n const highHz = this.#highHz;\n const iirLength = this.iirLength;\n const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;\n this.stepNum += 1;\n console.log('send impulse responses to server: ' + this.stepNum);\n this.status =\n `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n return this.pyServerAPI\n .getSystemInverseImpulseResponseWithRetry({\n payload: filteredComputedIRs.slice(0, this.numCaptures),\n mls,\n lowHz,\n highHz,\n iirLength,\n num_periods,\n sampleRate: this.sourceSamplingRate || 96000,\n })\n .then(res => {\n console.log(res);\n this.stepNum += 1;\n console.log('got impulse response ' + this.stepNum);\n this.incrementStatusBar();\n this.status =\n `All Hz Calibration: done computing the IIR...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n this.systemInvertedImpulseResponse = res['iir'];\n this.systemIR = res['ir'];\n this.systemConvolution = res['convolution'];\n })\n .catch(err => {\n console.error(err);\n });\n };\n\n /** .\n * .\n * .\n * Sends all the computed impulse responses to the backend server for processing\n *\n * @returns sets the resulting inverted impulse response to the class property\n * @example\n */\n sendComponentImpulseResponsesToServerForProcessing = async () => {\n const computedIRs = await Promise.all(this.impulseResponses);\n const filteredComputedIRs = computedIRs.filter(element => {\n return element != undefined;\n });\n const componentIRGains = this.componentIR['Gain'];\n const componentIRFreqs = this.componentIR['Freq'];\n const mls = this.#mls;\n const lowHz = this.#lowHz;\n const iirLength = this.iirLength;\n const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;\n const highHz = this.#highHz;\n this.stepNum += 1;\n console.log('send impulse responses to server: ' + this.stepNum);\n this.status =\n `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n return this.pyServerAPI\n .getComponentInverseImpulseResponseWithRetry({\n payload: filteredComputedIRs.slice(0, this.numCaptures),\n mls,\n lowHz,\n highHz,\n iirLength,\n componentIRGains,\n componentIRFreqs,\n num_periods,\n sampleRate: this.sourceSamplingRate || 96000,\n })\n .then(res => {\n console.log(res);\n this.stepNum += 1;\n console.log('got impulse response ' + this.stepNum);\n this.incrementStatusBar();\n this.status =\n `All Hz Calibration: done computing the IIR...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n this.componentInvertedImpulseResponse = res['iir'];\n this.componentIR['Gain'] = res['ir'];\n this.componentIR['Freq'] = res['frequencies'];\n this.componentConvolution = res['convolution'];\n })\n .catch(err => {\n // this.emit('InvertedImpulseResponse', {res: false});\n console.error(err);\n });\n };\n\n /** .\n * .\n * .\n * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing\n *\n * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed\n * @example\n */\n sendRecordingToServerForProcessing = signalCsv => {\n const allSignals = this.getAllRecordedSignals();\n const numSignals = allSignals.length;\n const mls = this.#mls;\n const payload =\n signalCsv && signalCsv.length > 0 ? (0,_utils__WEBPACK_IMPORTED_MODULE_1__.csvToArray)(signalCsv) : allSignals[numSignals - 1];\n console.log('sending rec');\n this.stepNum += 1;\n console.log('send rec ' + this.stepNum);\n this.status =\n `All Hz Calibration Step: computing the IR of the last recording...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n this.impulseResponses.push(\n this.pyServerAPI\n .getImpulseResponse({\n sampleRate: this.sourceSamplingRate || 96000,\n payload,\n mls,\n P: this.#P,\n numPeriods: this.numMLSPerCapture,\n })\n .then(res => {\n if (this.numSuccessfulCaptured < this.numCaptures) {\n this.numSuccessfulCaptured += 1;\n console.log('num succ capt: ' + this.numSuccessfulCaptured);\n this.stepNum += 1;\n console.log('got impulse response ' + this.stepNum);\n this.incrementStatusBar();\n this.status =\n `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {\n message: this.status,\n });\n this.autocorrelations.push(res['autocorrelation']);\n return res['ir'];\n }\n })\n .catch(err => {\n console.error(err);\n })\n );\n };\n\n /**\n * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number\n * of MLS periods defined in the constructor.\n *\n * @example\n */\n #awaitDesiredMLSLength = async () => {\n // seconds per MLS = P / SR\n // await N * P / SR\n this.stepNum += 1;\n console.log('await desired length ' + this.stepNum);\n this.status =\n `All Hz Calibration: sampling the calibration signal...`.toString() +\n `\\niteration ${this.stepNum}` +\n this.generateTemplate();\n this.emit('update', {\n message: this.status,\n });\n let time_to_wait = 0;\n if (this.mode === 'unfiltered') {\n time_to_wait = (this.#mls.length / this.sourceSamplingRate) * this.numMLSPerCapture;\n time_to_wait = time_to_wait * 1.1;\n } else if (this.mode === 'filtered') {\n time_to_wait =\n (this.#currentConvolution.length / this.sourceSamplingRate) *\n (this.numMLSPerCapture / (this.num_mls_to_skip + this.numMLSPerCapture));\n } else {\n throw new Error('Mode broke in awaitDesiredMLSLength');\n }\n\n await (0,_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(time_to_wait);\n };\n\n /** .\n * .\n * .\n * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state\n *\n * @example\n */\n #awaitSignalOnset = async () => {\n this.stepNum += 1;\n console.log('await signal onset ' + this.stepNum);\n this.status =\n `All Hz Calibration: waiting for the signal to stabilize...`.toString() +\n this.generateTemplate();\n this.emit('update', {\n message: this.status,\n });\n let number_of_bursts_to_skip = this.num_mls_to_skip;\n let time_to_sleep = 0;\n if (this.mode === 'unfiltered') {\n time_to_sleep = (this.#mls.length / this.sourceSamplingRate) * number_of_bursts_to_skip;\n } else if (this.mode === 'filtered') {\n console.log(this.#currentConvolution.length);\n time_to_sleep =\n (this.#currentConvolution.length / this.sourceSamplingRate) *\n (number_of_bursts_to_skip / (number_of_bursts_to_skip + this.numMLSPerCapture));\n } else {\n throw new Error('Mode broke in awaitSignalOnset');\n }\n await (0,_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(time_to_sleep);\n };\n\n /**\n * Called immediately after a recording is captured. Used to process the resulting signal\n * whether by sending the result to a server or by computing a result locally.\n *\n * @example\n */\n #afterMLSRecord = () => {\n console.log('after record');\n this.sendRecordingToServerForProcessing();\n };\n\n #afterMLSwIIRRecord = () => {\n if (this.numSuccessfulCaptured < 1) {\n this.numSuccessfulCaptured += 1;\n this.stepNum += 1;\n this.incrementStatusBar();\n console.log('after mls w iir record for some reason add numSucc capt ' + this.stepNum);\n this.status =\n `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {\n message: this.status,\n });\n }\n };\n\n /** .\n * .\n * .\n * Created an S Curver Buffer to taper the signal onset\n *\n * @param {*} length\n * @param {*} phase\n * @returns\n * @example\n */\n static createSCurveBuffer = (length, phase) => {\n const curve = new Float32Array(length);\n let i;\n for (i = 0; i < length; i += 1) {\n // scale the curve to be between 0-1\n curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\n }\n return curve;\n };\n\n static createInverseSCurveBuffer = (length, phase) => {\n const curve = new Float32Array(length);\n let i;\n let j = length - 1;\n for (i = 0; i < length; i += 1) {\n // scale the curve to be between 0-1\n curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;\n j -= 1;\n }\n return curve;\n };\n\n /**\n * Construct a Calibration Node with the calibration parameters.\n *\n * @param dataBuffer\n * @private\n * @example\n */\n #createCalibrationNodeFromBuffer = dataBuffer => {\n console.log('databuffer');\n console.log(dataBuffer);\n if (!this.sourceAudioContext) {\n this.makeNewSourceAudioContext();\n }\n\n const buffer = this.sourceAudioContext.createBuffer(\n 1, // number of channels\n dataBuffer.length,\n this.sourceAudioContext.sampleRate // sample rate\n );\n\n const data = buffer.getChannelData(0); // get data\n // fill the buffer with our data\n try {\n for (let i = 0; i < dataBuffer.length; i += 1) {\n data[i] = dataBuffer[i] * 0.1;\n }\n } catch (error) {\n console.error(error);\n }\n\n this.sourceNode = this.sourceAudioContext.createBufferSource();\n\n this.sourceNode.buffer = buffer;\n if (this.mode === 'filtered') {\n this.sourceNode.loop = false;\n } else {\n this.sourceNode.loop = true;\n }\n\n this.sourceNode.connect(this.sourceAudioContext.destination);\n\n this.addCalibrationNode(this.sourceNode);\n };\n\n /**\n * Given a data buffer, creates the required calibration node\n *\n * @param {*} dataBufferArray\n * @example\n */\n #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {\n if (dataBufferArray.length === 1) {\n this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);\n } else {\n throw new Error('The length of the data buffer array must be 1');\n }\n };\n\n /**\n * Creates an audio context and plays it for a few seconds.\n *\n * @private\n * @returns - Resolves when the audio is done playing.\n * @example\n */\n #playCalibrationAudio = () => {\n this.calibrationNodes[0].start(0);\n this.status = ``;\n if (this.mode === 'unfiltered') {\n this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);\n console.log('play calibration audio ' + this.stepNum);\n this.status =\n `All Hz Calibration: playing the calibration tone...`.toString() +\n this.generateTemplate().toString();\n } else if (this.mode === 'filtered') {\n console.log('play convolved audio ' + this.stepNum);\n this.status =\n `All Hz Calibration: playing the convolved calibration tone...`.toString() +\n this.generateTemplate().toString();\n } else {\n throw new Error('Mode is incorrect');\n }\n this.emit('update', {message: this.status});\n this.stepNum += 1;\n console.log('sink sampling rate');\n console.log(this.sinkSamplingRate);\n console.log('source sampling rate');\n console.log(this.sourceSamplingRate);\n };\n\n /** .\n * .\n * .\n * Stops the audio with tapered offset\n *\n * @example\n */\n #stopCalibrationAudio = () => {\n this.calibrationNodes[0].stop(0);\n this.calibrationNodes = [];\n this.sourceNode.disconnect();\n this.stepNum += 1;\n console.log('stop calibration audio ' + this.stepNum);\n this.status =\n `All Hz Calibration: stopping the calibration tone...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n };\n\n playMLSwithIIR = async (stream, iir) => {\n let checkRec = false;\n this.mode = 'filtered';\n console.log('play mls with iir');\n this.invertedImpulseResponse = iir;\n\n await this.calibrationSteps(\n stream,\n this.#playCalibrationAudio, // play audio func (required)\n this.#createCalibrationNodeFromBuffer(this.#currentConvolution), // before play func\n this.#awaitSignalOnset, // before record\n () => this.numSuccessfulCaptured < 1,\n this.#awaitDesiredMLSLength, // during record\n this.#afterMLSwIIRRecord, // after record\n this.mode,\n checkRec\n );\n };\n\n /**\n * Public method to start the calibration process. Objects intialized from webassembly allocate new memory\n * and must be manually freed. This function is responsible for intializing the MlsGenInterface,\n * and wrapping the calibration steps with a garbage collection safe gaurd.\n *\n * @public\n * @param stream - The stream of audio from the Listener.\n * @example\n */\n startCalibrationImpulseResponse = async stream => {\n let desired_time = this.desired_time_per_mls;\n let checkRec = 'allhz';\n\n console.log('MLS sequence should be of length: ' + this.sourceSamplingRate * desired_time);\n\n length = this.sourceSamplingRate * desired_time;\n //get mls here\n await this.pyServerAPI\n .getMLSWithRetry(length)\n .then(res => {\n console.log(res);\n this.#mlsBufferView = res['mls'];\n })\n .catch(err => {\n // this.emit('InvertedImpulseResponse', {res: false});\n console.error(err);\n });\n await this.calibrationSteps(\n stream,\n this.#playCalibrationAudio, // play audio func (required)\n this.#createCalibrationNodeFromBuffer(this.#mlsBufferView), // before play func\n this.#awaitSignalOnset, // before record\n () => this.numSuccessfulCaptured < this.numCaptures, // loop while true\n this.#awaitDesiredMLSLength, // during record\n this.#afterMLSRecord, // after record\n this.mode,\n checkRec\n ),\n this.#stopCalibrationAudio();\n checkRec = false;\n\n // at this stage we've captured all the required signals,\n // and have received IRs for each one\n // so let's send all the IRs to the server to be converted to a single IIR\n await this.sendSystemImpulseResponsesToServerForProcessing();\n await this.sendComponentImpulseResponsesToServerForProcessing();\n\n this.numSuccessfulCaptured = 0;\n\n let iir_ir_and_plots;\n if (this._calibrateSoundCheck != 'none') {\n if (this._calibrateSoundCheck != 'system') {\n this.#currentConvolution = this.componentConvolution;\n } else {\n this.#currentConvolution = this.systemConvolution;\n }\n await this.playMLSwithIIR(stream, this.invertedImpulseResponse);\n this.#stopCalibrationAudio();\n this.sourceAudioContext.close();\n let conv_recs = this.getAllFilteredRecordedSignals();\n let recs = this.getAllRecordedSignals();\n let unconv_rec = recs[0];\n let conv_rec = conv_recs[0];\n if (this._calibrateSoundCheck != 'system') {\n let knownGain = this.oldComponentIR.Gain;\n let knownFreq = this.oldComponentIR.Freq;\n let sampleRate = this.sourceSamplingRate || 96000;\n let unconv_results = await this.pyServerAPI\n .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)\n .then(res => {\n this.incrementStatusBar();\n this.status =\n `All Hz Calibration: done computing the PSD graphs...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n return res;\n })\n .catch(err => {\n console.error(err);\n });\n\n let conv_results = await this.pyServerAPI\n .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)\n .then(res => {\n this.incrementStatusBar();\n this.status =\n `All Hz Calibration: done computing the PSD graphs...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n return res;\n })\n .catch(err => {\n console.error(err);\n });\n iir_ir_and_plots = {\n systemIIR: this.systemInvertedImpulseResponse,\n componentIIR: this.componentInvertedImpulseResponse,\n x_unconv: unconv_results['x'],\n y_unconv: unconv_results['y'],\n x_conv: conv_results['x'],\n y_conv: conv_results['y'],\n componentIR: this.componentIR,\n systemIR: this.systemIR,\n };\n } else {\n let results = await this.pyServerAPI\n .getPSDWithRetry({\n unconv_rec,\n conv_rec,\n sampleRate: this.sourceSamplingRate || 96000,\n })\n .then(res => {\n this.incrementStatusBar();\n this.status =\n `All Hz Calibration: done computing the PSD graphs...`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n return res;\n })\n .catch(err => {\n console.error(err);\n });\n iir_ir_and_plots = {\n systemIIR: this.systemInvertedImpulseResponse,\n componentIIR: this.componentInvertedImpulseResponse,\n x_unconv: results['x_unconv'],\n y_unconv: results['y_unconv'],\n x_conv: results['x_conv'],\n y_conv: results['y_conv'],\n componentIR: this.componentIR,\n systemIR: this.systemIR,\n };\n }\n\n if (this.#download) {\n this.downloadSingleUnfilteredRecording();\n this.downloadSingleFilteredRecording();\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.#mls, 'MLS.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.componentConvolution, 'python_component_convolution_mls_iir.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.systemConvolution, 'python_system_convolution_mls_iir.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.componentInvertedImpulseResponse, 'componentIIR.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.systemInvertedImpulseResponse, 'systemIIR.csv');\n for (let i = 0; i < this.autocorrelations.length; i++) {\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.autocorrelations[i], `autocorrelation_${i}`);\n }\n const computedIRagain = await Promise.all(this.impulseResponses).then(res => {\n for (let i = 0; i < res.length; i++) {\n if (res[i] != undefined) {\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(res[i], `IR_${i}`);\n }\n }\n });\n }\n } else {\n iir_ir_and_plots = {\n systemIIR: this.systemInvertedImpulseResponse,\n componentIIR: this.componentInvertedImpulseResponse,\n x_unconv: [],\n y_unconv: [],\n x_conv: [],\n y_conv: [],\n componentIR: this.componentIR,\n systemIR: this.systemIR,\n };\n if (this.#download) {\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.#mls, 'MLS.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.componentConvolution, 'python_component_convolution_mls_iir.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.systemConvolution, 'python_system_convolution_mls_iir.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.componentInvertedImpulseResponse, 'componentIIR.csv');\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.systemInvertedImpulseResponse, 'systemIIR.csv');\n for (let i = 0; i < this.autocorrelations.length; i++) {\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(this.autocorrelations[i], `autocorrelation_${i}`);\n }\n const computedIRagain = await Promise.all(this.impulseResponses).then(res => {\n for (let i = 0; i < res.length; i++) {\n if (res[i] != undefined) {\n (0,_utils__WEBPACK_IMPORTED_MODULE_1__.saveToCSV)(res[i], `IR_${i}`);\n }\n }\n });\n }\n }\n\n this.percent_complete = 100;\n\n this.status = `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n\n //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR\n //that was used to calibrate\n\n return iir_ir_and_plots;\n };\n\n //////////////////////volume\n\n handleIncomingData = data => {\n console.log('Received data: ', data);\n if (data.type === 'soundGainDBSPL') {\n this.soundGainDBSPL = data.value;\n } else {\n throw new Error(`Unknown data type: ${data.type}`);\n }\n };\n createSCurveBuffer = (onSetBool = true) => {\n const curve = new Float32Array(this.TAPER_SECS * this.sourceSamplingRate + 1);\n const frequency = 1 / (4 * this.TAPER_SECS);\n let j = 0;\n for (let i = 0; i < this.TAPER_SECS * this.sourceSamplingRate + 1; i += 1) {\n const phase = 2 * Math.PI * frequency * j;\n const onsetTaper = Math.pow(Math.sin(phase), 2);\n const offsetTaper = Math.pow(Math.cos(phase), 2);\n curve[i] = onSetBool ? onsetTaper : offsetTaper;\n j += 1 / this.sourceSamplingRate;\n }\n return curve;\n };\n\n #getTruncatedSignal = (left = 3.5, right = 4.5) => {\n const start = Math.floor(left * this.sourceSamplingRate);\n const end = Math.floor(right * this.sourceSamplingRate);\n const result = Array.from(this.getLastRecordedSignal().slice(start, end));\n\n /**\n * function to check that capture was properly made\n * @param {*} list\n */\n const checkResult = list => {\n const setItem = new Set(list);\n if (setItem.size === 1 && setItem.has(0)) {\n console.warn(\n 'The last capture failed, all recorded signal is zero',\n this.getAllRecordedSignals()\n );\n }\n if (setItem.size === 0) {\n console.warn('The last capture failed, no recorded signal');\n }\n };\n checkResult(result);\n return result;\n };\n\n /** \n * \n * \n Construct a calibration Node with the calibration parameters and given gain value\n * @param {*} gainValue\n * */\n #createCalibrationToneWithGainValue = gainValue => {\n const audioContext = this.makeNewSourceAudioContext();\n const oscilator = audioContext.createOscillator();\n const gainNode = audioContext.createGain();\n const taperGainNode = audioContext.createGain();\n const offsetGainNode = audioContext.createGain();\n const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;\n\n oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;\n oscilator.type = this.#CALIBRATION_TONE_TYPE;\n gainNode.gain.value = gainValue;\n\n oscilator.connect(gainNode);\n gainNode.connect(taperGainNode);\n const onsetCurve = this.createSCurveBuffer();\n taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);\n taperGainNode.connect(offsetGainNode);\n const offsetCurve = this.createSCurveBuffer(false);\n offsetGainNode.gain.setValueCurveAtTime(\n offsetCurve,\n totalDuration - this.TAPER_SECS,\n this.TAPER_SECS\n );\n offsetGainNode.connect(audioContext.destination);\n\n this.addCalibrationNode(oscilator);\n };\n\n /**\n * Construct a Calibration Node with the calibration parameters.\n *\n * @private\n * @example\n */\n #createCalibrationNode = () => {\n const audioContext = this.makeNewSourceAudioContext();\n const oscilator = audioContext.createOscillator();\n const gainNode = audioContext.createGain();\n\n oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;\n oscilator.type = this.#CALIBRATION_TONE_TYPE;\n gainNode.gain.value = 0.04;\n\n oscilator.connect(gainNode);\n gainNode.connect(audioContext.destination);\n\n this.addCalibrationNode(oscilator);\n };\n\n #playCalibrationAudioVolume = async () => {\n const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;\n\n this.calibrationNodes[0].start(0);\n this.calibrationNodes[0].stop(totalDuration);\n console.log(`Playing a buffer of ${this.CALIBRATION_TONE_DURATION} seconds of audio`);\n console.log(`Waiting a total of ${totalDuration} seconds`);\n await (0,_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(totalDuration);\n };\n\n #sendToServerForProcessing = lCalib => {\n console.log('Sending data to server');\n this.pyServerAPI\n .getVolumeCalibration({\n sampleRate: this.sourceSamplingRate,\n payload: this.#getTruncatedSignal(),\n lCalib: lCalib,\n })\n .then(res => {\n if (this.outDBSPL === null) {\n this.incrementStatusBar();\n this.outDBSPL = res['outDbSPL'];\n this.outDBSPL1000 = res['outDbSPL1000'];\n this.THD = res['thd'];\n }\n })\n .catch(err => {\n console.warn(err);\n });\n };\n\n startCalibrationVolume = async (stream, gainValues, lCalib, componentGainDBSPL) => {\n const trialIterations = gainValues.length;\n this.status_denominator += trialIterations;\n const thdValues = [];\n const inDBValues = [];\n let inDB = 0;\n const outDBSPLValues = [];\n const outDBSPL1000Values = [];\n let checkRec = false;\n\n // do one calibration that will be discarded\n const soundLevelToDiscard = -60;\n const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);\n this.status =\n `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() +\n this.generateTemplate().toString();\n //this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});\n this.emit('update', {message: this.status});\n\n do {\n // eslint-disable-next-line no-await-in-loop\n await this.volumeCalibrationSteps(\n stream,\n this.#playCalibrationAudioVolume,\n this.#createCalibrationToneWithGainValue,\n this.#sendToServerForProcessing,\n gainToDiscard,\n lCalib, //todo make this a class parameter\n checkRec\n );\n } while (this.outDBSPL === null);\n //reset the values\n //this.incrementStatusBar();\n\n this.outDBSPL = null;\n this.outDBSPL = null;\n this.outDBSPL1000 = null;\n this.THD = null;\n\n // run the calibration at different gain values provided by the user\n for (let i = 0; i < trialIterations; i++) {\n //convert gain to DB and add to inDB\n if (i == trialIterations-1){\n checkRec = 'loudest';\n }\n inDB = Math.log10(gainValues[i]) * 20;\n // precision to 1 decimal place\n inDB = Math.round(inDB * 10) / 10;\n inDBValues.push(inDB);\n console.log('next update');\n this.status =\n `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() +\n this.generateTemplate().toString();\n this.emit('update', {message: this.status});\n do {\n // eslint-disable-next-line no-await-in-loop\n await this.volumeCalibrationSteps(\n stream,\n this.#playCalibrationAudioVolume,\n this.#createCalibrationToneWithGainValue,\n this.#sendToServerForProcessing,\n gainValues[i],\n lCalib, //todo make this a class parameter\n checkRec\n );\n } while (this.outDBSPL === null);\n outDBSPL1000Values.push(this.outDBSPL1000);\n thdValues.push(this.THD);\n outDBSPLValues.push(this.outDBSPL);\n\n this.outDBSPL = null;\n this.outDBSPL1000 = null;\n this.THD = null;\n }\n\n // get the volume calibration parameters from the server\n const parameters = await this.pyServerAPI\n .getVolumeCalibrationParameters({\n inDBValues: inDBValues,\n outDBSPLValues: outDBSPL1000Values,\n lCalib: lCalib,\n componentGainDBSPL,\n })\n .then(res => {\n this.incrementStatusBar();\n return res;\n });\n const result = {\n parameters: parameters,\n inDBValues: inDBValues,\n outDBSPLValues: outDBSPLValues,\n outDBSPL1000Values: outDBSPL1000Values,\n thdValues: thdValues,\n };\n\n return result;\n };\n\n // function to write frq and gain to firebase database given speakerID\n writeFrqGain = async (speakerID, frq, gain, OEM) => {\n // freq and gain are too large to take samples 1 in every 100 samples\n\n const sampledFrq = [];\n const sampledGain = [];\n for (let i = 0; i < frq.length; i += 100) {\n sampledFrq.push(frq[i]);\n sampledGain.push(gain[i]);\n }\n\n const data = {Freq: sampledFrq, Gain: sampledGain};\n\n await (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.set)((0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_2__[\"default\"], `Microphone2/${OEM}/${speakerID}/linear`), data);\n };\n\n // Function to Read frq and gain from firebase database given speakerID\n // returns an array of frq and gain if speakerID exists, returns null otherwise\n\n readFrqGain = async (speakerID, OEM) => {\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_2__[\"default\"]);\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.child)(dbRef, `Microphone2/${OEM}/${speakerID}/linear`));\n if (snapshot.exists()) {\n return snapshot.val();\n }\n return null;\n };\n\n readGainat1000Hz = async (speakerID, OEM) => {\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_2__[\"default\"]);\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.child)(dbRef, `Microphone2/${OEM}/${speakerID}/Gain1000`));\n if (snapshot.exists()) {\n return snapshot.val();\n }\n return null;\n };\n\n writeGainat1000Hz = async (speakerID, gain, OEM) => {\n const data = {Gain: gain};\n await (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.set)((0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_2__[\"default\"], `Microphone2/${OEM}/${speakerID}/Gain1000`), gain);\n };\n\n writeIsSmartPhone = async (speakerID, isSmartPhone, OEM) => {\n const data = {isSmartPhone: isSmartPhone};\n await (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.set)((0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_2__[\"default\"], `Microphone2/${OEM}/${speakerID}/isSmartPhone`), isSmartPhone);\n };\n\n doesMicrophoneExist = async (speakerID, OEM) => {\n const dbRef = (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.ref)(_config_firebase__WEBPACK_IMPORTED_MODULE_2__[\"default\"]);\n const snapshot = await (0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.get)((0,firebase_database__WEBPACK_IMPORTED_MODULE_3__.child)(dbRef, `Microphone2/${OEM}/${speakerID}`));\n if (snapshot.exists()) {\n return true;\n }\n return false;\n };\n\n convertToDB = gain => {\n return Math.log10(gain) * 20;\n };\n\n // Function to perform linear interpolation between two points\n interpolate(x, x0, y0, x1, y1) {\n return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);\n }\n\n findGainatFrequency = (frequencies, gains, targetFrequency) => {\n // Find the index of the first frequency in the array greater than the target frequency\n let index = 0;\n while (index < frequencies.length && frequencies[index] < targetFrequency) {\n index++;\n }\n\n // Handle cases when the target frequency is outside the range of the given data\n if (index === 0) {\n return gains[0];\n } else if (index === frequencies.length) {\n return gains[gains.length - 1];\n } else {\n // Interpolate the gain based on the surrounding frequencies\n const x0 = frequencies[index - 1];\n const y0 = gains[index - 1];\n const x1 = frequencies[index];\n const y1 = gains[index];\n return this.interpolate(targetFrequency, x0, y0, x1, y1);\n }\n };\n\n // Example of how to use the writeFrqGain and readFrqGain functions\n // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);\n // Speaker1 is the speakerID you want to write to in the database\n // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));\n // MiniDSPUMIK_1 is the speakerID with some Data in the database\n //adding gainDBSPL\n startCalibration = async (\n stream,\n gainValues,\n lCalib = 104.92978421490648,\n componentIR = null,\n microphoneName = 'MiniDSP-UMIK1-711-4754-vertical',\n _calibrateSoundCheck = 'goal', //GOAL PASSed in by default\n isSmartPhone = false,\n _calibrateSoundBurstRepeats = 4,\n _calibrateSoundBurstSec = 1,\n _calibrateSoundBurstsWarmup = 1,\n _calibrateSoundHz = 48000,\n _calibrateSoundIIRSec = 0.2,\n calibrateSound1000HzSec = 5,\n micManufacturer = '',\n micSerialNumber = '',\n micModelNumber = '',\n micModelName = ''\n ) => {\n this.CALIBRATION_TONE_DURATION = calibrateSound1000HzSec;\n this.iirLength = Math.floor(_calibrateSoundIIRSec * this.sourceSamplingRate);\n console.log('device info:', this.deviceInfo);\n this.numMLSPerCapture = _calibrateSoundBurstRepeats;\n this.desired_time_per_mls = _calibrateSoundBurstSec;\n this.num_mls_to_skip = _calibrateSoundBurstsWarmup;\n this.desired_sampling_rate = _calibrateSoundHz;\n\n //feed calibration goal here\n this._calibrateSoundCheck = _calibrateSoundCheck;\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\n //check the db based on the microphone currently connected\n\n //new lCalib found at top of calibration files *1000hz, make sure to correct\n //based on zeroing of 1000hz, search for \"*1000Hz\"\n const ID = isSmartPhone ? micModelNumber : micSerialNumber;\n const OEM = isSmartPhone ? this.deviceInfo.OEM : micManufacturer;\n if (componentIR == null) {\n //mode 'ir'\n //global variable this.componentIR must be set\n this.componentIR = await this.readFrqGain(ID, OEM).then(data => {\n return data;\n });\n\n lCalib = await this.readGainat1000Hz(ID, OEM);\n // this.componentGainDBSPL = this.convertToDB(lCalib);\n this.componentGainDBSPL = lCalib;\n //TODO: if this call to database is unknown, cannot perform experiment => return false\n if (this.componentIR == null) {\n this.status =\n `Microphone (${OEM},${ID}) is not found in the database. Please add it to the database.`.toString();\n this.emit('update', {message: this.status});\n return false;\n }\n } else {\n this.componentIR = componentIR;\n lCalib = this.findGainatFrequency(this.componentIR.Freq, this.componentIR.Gain, 1000);\n // this.componentGainDBSPL = this.convertToDB(lCalib);\n this.componentGainDBSPL = lCalib;\n await this.writeGainat1000Hz(ID, lCalib, OEM);\n await this.writeIsSmartPhone(ID, isSmartPhone, OEM);\n }\n\n this.oldComponentIR = this.componentIR;\n\n let volumeResults = await this.startCalibrationVolume(\n stream,\n gainValues,\n lCalib,\n this.componentGainDBSPL\n );\n\n let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);\n\n if (componentIR != null) {\n //insert Freq and Gain from this.componentIR into db\n await this.writeFrqGain(\n ID,\n impulseResponseResults.componentIR.Freq,\n impulseResponseResults.componentIR.Gain,\n OEM\n );\n }\n\n const total_results = {...volumeResults, ...impulseResponseResults};\n\n total_results['micInfo'] = {\n micManufacturer: micManufacturer,\n micSerialNumber: micSerialNumber,\n micModelNumber: micModelNumber,\n micModelName: micModelName,\n ID: ID,\n OEM: OEM,\n };\n console.log('total results');\n console.log(total_results);\n return total_results;\n };\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (Combination);\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/combination/combination.js?");
844
844
 
845
845
  /***/ }),
846
846
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speaker-calibration",
3
- "version": "2.2.20",
3
+ "version": "2.2.22",
4
4
  "description": "Speaker calibration library for auditory testing",
5
5
  "main": "dist/main.js",
6
6
  "directories": {
@@ -102,6 +102,7 @@ class Speaker extends AudioPeer {
102
102
  params.calibrateSoundBurstsWarmup,
103
103
  params.calibrateSoundHz,
104
104
  params.calibrateSoundIIRSec,
105
+ params.calibrateSound1000HzSec,
105
106
  params.micManufacturer,
106
107
  params.micSerialNumber,
107
108
  params.micModelNumber,
@@ -83,7 +83,8 @@ class AudioCalibrator extends AudioRecorder {
83
83
  loopCondition = () => false,
84
84
  duringRecord = async () => {},
85
85
  afterRecord = async () => {},
86
- mode
86
+ mode,
87
+ checkRec
87
88
  ) => {
88
89
  this.numSuccessfulCaptured = 0;
89
90
 
@@ -111,7 +112,7 @@ class AudioCalibrator extends AudioRecorder {
111
112
 
112
113
  // when done, stop recording
113
114
  console.warn('stopRecording');
114
- await this.stopRecording(mode);
115
+ await this.stopRecording(mode,checkRec);
115
116
 
116
117
  // do something after recording such as start processing values
117
118
  console.warn('afterRecord');
@@ -136,7 +137,8 @@ class AudioCalibrator extends AudioRecorder {
136
137
  beforeRecord = () => {},
137
138
  afterRecord = () => {},
138
139
  gainValue,
139
- lCalib = 104.92978421490648
140
+ lCalib = 104.92978421490648,
141
+ checkRec
140
142
  ) => {
141
143
  this.numCalibratingRoundsCompleted = 0;
142
144
 
@@ -154,7 +156,7 @@ class AudioCalibrator extends AudioRecorder {
154
156
 
155
157
  // when done, stop recording
156
158
  console.log('Calibration Round Complete');
157
- await this.stopRecording();
159
+ await this.stopRecording('volume',checkRec);
158
160
 
159
161
  // after recording
160
162
  await afterRecord(lCalib);
@@ -243,7 +245,7 @@ class AudioCalibrator extends AudioRecorder {
243
245
  };
244
246
  downloadSingleUnfilteredRecording = () => {
245
247
  const recordings = this.getAllRecordedSignals();
246
- saveToCSV(recordings[0], `recordedMLSignal_unconvolved.csv`);
248
+ saveToCSV(recordings[recordings.length-1], `recordedMLSignal_unconvolved.csv`);
247
249
  }
248
250
  downloadSingleFilteredRecording = () => {
249
251
  const recordings = this.getAllFilteredRecordedSignals();
@@ -32,14 +32,32 @@ class AudioRecorder extends MyEventEmitter {
32
32
  * @private
33
33
  * @example
34
34
  */
35
- #saveRecording = async () => {
35
+ #saveRecording = async (checkRec) => {
36
36
  const arrayBuffer = await this.#audioBlob.arrayBuffer();
37
37
  const audioBuffer = await this.#audioContext.decodeAudioData(arrayBuffer);
38
38
  const data = audioBuffer.getChannelData(0);
39
+ const dataArray = Array.from(data);
39
40
 
40
41
  console.log(`Decoded audio buffer with ${data.length} samples`);
41
42
  console.log(`Unfiltered recording should be of length: ${data.length}`);
42
- this.#recordedSignals.push(Array.from(data));
43
+ if (checkRec == 'loudest'){
44
+ const uniqueSet = new Set(dataArray);
45
+ const numberOfUniqueValues = uniqueSet.size;
46
+ const squaredValues = dataArray.map(value => value * value);
47
+ const sum_of_squares = squaredValues.reduce((total, value) => total + value, 0);
48
+ const squared_mean = sum_of_squares / dataArray.length;
49
+ const dbLevel = 20 * Math.log10(Math.sqrt(squared_mean));
50
+ console.log("Loudest 1000-Hz recording: " + dbLevel + " with " + numberOfUniqueValues + " unique values.")
51
+ }else if (checkRec == 'allhz'){
52
+ const uniqueSet = new Set(dataArray);
53
+ const numberOfUniqueValues = uniqueSet.size;
54
+ const squaredValues = dataArray.map(value => value * value);
55
+ const sum_of_squares = squaredValues.reduce((total, value) => total + value, 0);
56
+ const squared_mean = sum_of_squares / dataArray.length;
57
+ const dbLevel = 20 * Math.log10(Math.sqrt(squared_mean));
58
+ console.log("All Hz Recording: " + dbLevel + " with " + numberOfUniqueValues + " unique values.")
59
+ }
60
+ this.#recordedSignals.push(dataArray);
43
61
  };
44
62
 
45
63
  #saveFilteredRecording = async () => {
@@ -110,7 +128,7 @@ class AudioRecorder extends MyEventEmitter {
110
128
  * @public
111
129
  * @example
112
130
  */
113
- stopRecording = async (mode) => {
131
+ stopRecording = async (mode,checkRec) => {
114
132
  // Stop the media recorder, and wait for the data to be available
115
133
  await new Promise(resolve => {
116
134
  this.#mediaRecorder.onstop = () => {
@@ -127,7 +145,7 @@ class AudioRecorder extends MyEventEmitter {
127
145
  if (mode === 'filtered'){
128
146
  await this.#saveFilteredRecording();
129
147
  }else{
130
- await this.#saveRecording();
148
+ await this.#saveRecording(checkRec);
131
149
  }
132
150
  };
133
151
 
@@ -99,8 +99,7 @@ class Combination extends AudioCalibrator {
99
99
  /** @private */
100
100
  #CALIBRATION_TONE_TYPE = 'sine';
101
101
 
102
- /** @private */
103
- #CALIBRATION_TONE_DURATION = 5; // seconds
102
+ CALIBRATION_TONE_DURATION = 5; // seconds
104
103
 
105
104
  /** @private */
106
105
  outDBSPL = null;
@@ -368,10 +367,11 @@ class Combination extends AudioCalibrator {
368
367
  let time_to_wait = 0;
369
368
  if (this.mode === 'unfiltered') {
370
369
  time_to_wait = (this.#mls.length / this.sourceSamplingRate) * this.numMLSPerCapture;
371
- time_to_wait= time_to_wait*1.1;
370
+ time_to_wait = time_to_wait * 1.1;
372
371
  } else if (this.mode === 'filtered') {
373
372
  time_to_wait =
374
- (this.#currentConvolution.length / this.sourceSamplingRate) * (this.numMLSPerCapture/(this.num_mls_to_skip+this.numMLSPerCapture));
373
+ (this.#currentConvolution.length / this.sourceSamplingRate) *
374
+ (this.numMLSPerCapture / (this.num_mls_to_skip + this.numMLSPerCapture));
375
375
  } else {
376
376
  throw new Error('Mode broke in awaitDesiredMLSLength');
377
377
  }
@@ -398,10 +398,12 @@ class Combination extends AudioCalibrator {
398
398
  let number_of_bursts_to_skip = this.num_mls_to_skip;
399
399
  let time_to_sleep = 0;
400
400
  if (this.mode === 'unfiltered') {
401
- time_to_sleep = (this.#mls.length / this.sourceSamplingRate)*number_of_bursts_to_skip;
401
+ time_to_sleep = (this.#mls.length / this.sourceSamplingRate) * number_of_bursts_to_skip;
402
402
  } else if (this.mode === 'filtered') {
403
403
  console.log(this.#currentConvolution.length);
404
- time_to_sleep = (this.#currentConvolution.length / this.sourceSamplingRate)*(number_of_bursts_to_skip/(number_of_bursts_to_skip+this.numMLSPerCapture));
404
+ time_to_sleep =
405
+ (this.#currentConvolution.length / this.sourceSamplingRate) *
406
+ (number_of_bursts_to_skip / (number_of_bursts_to_skip + this.numMLSPerCapture));
405
407
  } else {
406
408
  throw new Error('Mode broke in awaitSignalOnset');
407
409
  }
@@ -499,12 +501,12 @@ class Combination extends AudioCalibrator {
499
501
  this.sourceNode = this.sourceAudioContext.createBufferSource();
500
502
 
501
503
  this.sourceNode.buffer = buffer;
502
- if (this.mode === 'filtered'){
504
+ if (this.mode === 'filtered') {
503
505
  this.sourceNode.loop = false;
504
- }else{
506
+ } else {
505
507
  this.sourceNode.loop = true;
506
508
  }
507
-
509
+
508
510
  this.sourceNode.connect(this.sourceAudioContext.destination);
509
511
 
510
512
  this.addCalibrationNode(this.sourceNode);
@@ -532,7 +534,6 @@ class Combination extends AudioCalibrator {
532
534
  * @example
533
535
  */
534
536
  #playCalibrationAudio = () => {
535
-
536
537
  this.calibrationNodes[0].start(0);
537
538
  this.status = ``;
538
539
  if (this.mode === 'unfiltered') {
@@ -540,7 +541,7 @@ class Combination extends AudioCalibrator {
540
541
  console.log('play calibration audio ' + this.stepNum);
541
542
  this.status =
542
543
  `All Hz Calibration: playing the calibration tone...`.toString() +
543
- this.generateTemplate().toString();
544
+ this.generateTemplate().toString();
544
545
  } else if (this.mode === 'filtered') {
545
546
  console.log('play convolved audio ' + this.stepNum);
546
547
  this.status =
@@ -577,6 +578,7 @@ class Combination extends AudioCalibrator {
577
578
  };
578
579
 
579
580
  playMLSwithIIR = async (stream, iir) => {
581
+ let checkRec = false;
580
582
  this.mode = 'filtered';
581
583
  console.log('play mls with iir');
582
584
  this.invertedImpulseResponse = iir;
@@ -589,7 +591,8 @@ class Combination extends AudioCalibrator {
589
591
  () => this.numSuccessfulCaptured < 1,
590
592
  this.#awaitDesiredMLSLength, // during record
591
593
  this.#afterMLSwIIRRecord, // after record
592
- this.mode
594
+ this.mode,
595
+ checkRec
593
596
  );
594
597
  };
595
598
 
@@ -604,6 +607,7 @@ class Combination extends AudioCalibrator {
604
607
  */
605
608
  startCalibrationImpulseResponse = async stream => {
606
609
  let desired_time = this.desired_time_per_mls;
610
+ let checkRec = 'allhz';
607
611
 
608
612
  console.log('MLS sequence should be of length: ' + this.sourceSamplingRate * desired_time);
609
613
 
@@ -627,9 +631,11 @@ class Combination extends AudioCalibrator {
627
631
  () => this.numSuccessfulCaptured < this.numCaptures, // loop while true
628
632
  this.#awaitDesiredMLSLength, // during record
629
633
  this.#afterMLSRecord, // after record
630
- this.mode
634
+ this.mode,
635
+ checkRec
631
636
  ),
632
637
  this.#stopCalibrationAudio();
638
+ checkRec = false;
633
639
 
634
640
  // at this stage we've captured all the required signals,
635
641
  // and have received IRs for each one
@@ -845,7 +851,7 @@ class Combination extends AudioCalibrator {
845
851
  const gainNode = audioContext.createGain();
846
852
  const taperGainNode = audioContext.createGain();
847
853
  const offsetGainNode = audioContext.createGain();
848
- const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;
854
+ const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
849
855
 
850
856
  oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
851
857
  oscilator.type = this.#CALIBRATION_TONE_TYPE;
@@ -889,11 +895,11 @@ class Combination extends AudioCalibrator {
889
895
  };
890
896
 
891
897
  #playCalibrationAudioVolume = async () => {
892
- const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;
898
+ const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
893
899
 
894
900
  this.calibrationNodes[0].start(0);
895
901
  this.calibrationNodes[0].stop(totalDuration);
896
- console.log(`Playing a buffer of ${this.#CALIBRATION_TONE_DURATION} seconds of audio`);
902
+ console.log(`Playing a buffer of ${this.CALIBRATION_TONE_DURATION} seconds of audio`);
897
903
  console.log(`Waiting a total of ${totalDuration} seconds`);
898
904
  await sleep(totalDuration);
899
905
  };
@@ -927,6 +933,7 @@ class Combination extends AudioCalibrator {
927
933
  let inDB = 0;
928
934
  const outDBSPLValues = [];
929
935
  const outDBSPL1000Values = [];
936
+ let checkRec = false;
930
937
 
931
938
  // do one calibration that will be discarded
932
939
  const soundLevelToDiscard = -60;
@@ -945,7 +952,8 @@ class Combination extends AudioCalibrator {
945
952
  this.#createCalibrationToneWithGainValue,
946
953
  this.#sendToServerForProcessing,
947
954
  gainToDiscard,
948
- lCalib //todo make this a class parameter
955
+ lCalib, //todo make this a class parameter
956
+ checkRec
949
957
  );
950
958
  } while (this.outDBSPL === null);
951
959
  //reset the values
@@ -959,6 +967,9 @@ class Combination extends AudioCalibrator {
959
967
  // run the calibration at different gain values provided by the user
960
968
  for (let i = 0; i < trialIterations; i++) {
961
969
  //convert gain to DB and add to inDB
970
+ if (i == trialIterations-1){
971
+ checkRec = 'loudest';
972
+ }
962
973
  inDB = Math.log10(gainValues[i]) * 20;
963
974
  // precision to 1 decimal place
964
975
  inDB = Math.round(inDB * 10) / 10;
@@ -976,7 +987,8 @@ class Combination extends AudioCalibrator {
976
987
  this.#createCalibrationToneWithGainValue,
977
988
  this.#sendToServerForProcessing,
978
989
  gainValues[i],
979
- lCalib //todo make this a class parameter
990
+ lCalib, //todo make this a class parameter
991
+ checkRec
980
992
  );
981
993
  } while (this.outDBSPL === null);
982
994
  outDBSPL1000Values.push(this.outDBSPL1000);
@@ -1117,12 +1129,14 @@ class Combination extends AudioCalibrator {
1117
1129
  _calibrateSoundBurstsWarmup = 1,
1118
1130
  _calibrateSoundHz = 48000,
1119
1131
  _calibrateSoundIIRSec = 0.2,
1132
+ calibrateSound1000HzSec = 5,
1120
1133
  micManufacturer = '',
1121
1134
  micSerialNumber = '',
1122
1135
  micModelNumber = '',
1123
1136
  micModelName = ''
1124
1137
  ) => {
1125
- this.iirLength = Math.floor(_calibrateSoundIIRSec*this.sourceSamplingRate);
1138
+ this.CALIBRATION_TONE_DURATION = calibrateSound1000HzSec;
1139
+ this.iirLength = Math.floor(_calibrateSoundIIRSec * this.sourceSamplingRate);
1126
1140
  console.log('device info:', this.deviceInfo);
1127
1141
  this.numMLSPerCapture = _calibrateSoundBurstRepeats;
1128
1142
  this.desired_time_per_mls = _calibrateSoundBurstSec;