speaker-calibration 2.1.13 → 2.1.15
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
|
@@ -774,7 +774,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var axio
|
|
|
774
774
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
775
775
|
|
|
776
776
|
"use strict";
|
|
777
|
-
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 */\r\n\r\n\r\n\r\n\r\n/**\r\n * .\r\n * .\r\n * .\r\n * Provides methods for calibrating the user's speakers\r\n *\r\n * @extends AudioRecorder\r\n */\r\nclass AudioCalibrator extends _audioRecorder__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n /**\r\n *\r\n * @param numCaptures\r\n * @param numMLSPerCapture\r\n * @example\r\n */\r\n constructor(numCaptures = 1, numMLSPerCapture = 1) {\r\n super();\r\n this.numCaptures = numCaptures;\r\n this.numMLSPerCapture = numMLSPerCapture;\r\n this.pyServerAPI = new _server_PythonServerAPI__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\r\n }\r\n\r\n /** @private */\r\n #isCalibrating = false;\r\n\r\n /** @private */\r\n sourceAudioContext;\r\n\r\n /** @private */\r\n sourceAudioContextConvolved;\r\n\r\n /** @protected */\r\n numCalibratingRounds = 1;\r\n\r\n /** @protected */\r\n numSuccessfulCaptured = 0;\r\n\r\n /** @private */\r\n sourceSamplingRate;\r\n\r\n /** @protected */\r\n calibrationNodes = [];\r\n\r\n /** @protected */\r\n calibrationNodesConvolved = [];\r\n\r\n /** @protected */\r\n localAudio;\r\n\r\n /**\r\n * Called when a call is received.\r\n * Creates a local audio DOM element and attaches it to the page.\r\n *\r\n * @param targetElement\r\n * @example\r\n */\r\n createLocalAudio = targetElement => {\r\n this.localAudio = document.createElement('audio');\r\n this.localAudio.setAttribute('id', 'localAudio');\r\n targetElement.appendChild(this.localAudio);\r\n };\r\n\r\n /**\r\n *\r\n * @param {MediaStream} stream\r\n * @param {Function} playCalibrationAudio - (async) function that plays the calibration audio\r\n * @param {*} beforePlay - (async) function that is called before playing the audio\r\n * @param {*} beforeRecord - (async) function that is called before recording\r\n * @param {*} duringRecord - (async) function that is called while recording\r\n * @param {*} afterRecord - (async) function that is called after recording\r\n * @example\r\n */\r\n calibrationSteps = async (\r\n stream,\r\n playCalibrationAudio,\r\n beforePlay = async () => {},\r\n beforeRecord = async () => {},\r\n loopCondition = () => false,\r\n duringRecord = async () => {},\r\n afterRecord = async () => {}\r\n ) => {\r\n this.numSuccessfulCaptured = 0;\r\n\r\n // do something before playing such as using the MLS to fill the buffers\r\n console.warn('beforePlay');\r\n await beforePlay();\r\n\r\n // play calibration audio\r\n console.warn('playCalibrationAudio');\r\n playCalibrationAudio();\r\n\r\n // do something before recording such as awaiting a certain amount of time\r\n console.warn('beforeRecord');\r\n await beforeRecord();\r\n\r\n // calibration loop\r\n while (loopCondition()) {\r\n // start recording\r\n console.warn('startRecording');\r\n await this.startRecording(stream);\r\n\r\n // do something during the recording such as sleep n amount of time\r\n console.warn('duringRecord');\r\n await duringRecord();\r\n\r\n // when done, stop recording\r\n console.warn('stopRecording');\r\n await this.stopRecording();\r\n\r\n // do something after recording such as start processing values\r\n console.warn('afterRecord');\r\n await afterRecord();\r\n\r\n // eslint-disable-next-line no-await-in-loop\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1);\r\n }\r\n };\r\n\r\n /**\r\n *\r\n * @param {MediaStream} stream\r\n * @param {Function} playCalibrationAudio - (async) function that plays the calibration audio\r\n * @param {*} beforeRecord - (async) function that is called before recording\r\n * @param {*} afterRecord - (async) function that is called after recording\r\n * @param {Number} gainValue - the gain value to set the gain node to\r\n */\r\n volumeCalibrationSteps = async (\r\n stream,\r\n playCalibrationAudio,\r\n beforeRecord = () => {},\r\n afterRecord = () => {},\r\n gainValue,\r\n lCalib = 104.92978421490648\r\n ) => {\r\n this.numCalibratingRoundsCompleted = 0;\r\n\r\n // calibration loop\r\n while (!this.#isCalibrating && this.numCalibratingRoundsCompleted < this.numCalibratingRounds) {\r\n // before recording\r\n await beforeRecord(gainValue);\r\n\r\n // start recording\r\n await this.startRecording(stream);\r\n\r\n // play calibration audio\r\n console.log(`Calibration Round ${this.numCalibratingRoundsCompleted}`);\r\n await playCalibrationAudio();\r\n\r\n // when done, stop recording\r\n console.log('Calibration Round Complete');\r\n await this.stopRecording();\r\n\r\n // after recording\r\n await afterRecord(lCalib);\r\n\r\n this.calibrationNodes = [];\r\n\r\n // eslint-disable-next-line no-await-in-loop\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(2);\r\n this.numCalibratingRoundsCompleted += 1;\r\n }\r\n };\r\n\r\n /**\r\n * Getter for the isCalibrating property.\r\n *\r\n * @public\r\n * @returns - True if the audio is being calibrated, false otherwise.\r\n * @example\r\n */\r\n getCalibrationStatus = () => this.#isCalibrating;\r\n\r\n /** .\r\n * .\r\n * .\r\n * Set the sampling rate to the value received from the listener\r\n *\r\n * @param {*} sinkSamplingRate\r\n * @param samplingRate\r\n * @example\r\n */\r\n setSamplingRates = samplingRate => {\r\n this.sinkSamplingRate = samplingRate;\r\n this.sourceSamplingRate = samplingRate;\r\n\r\n // this.emit('update', {message: `sampling at ${samplingRate}Hz...`});\r\n };\r\n\r\n sampleRatesSet = () => this.sourceSamplingRate && this.sinkSamplingRate;\r\n\r\n addCalibrationNode = node => {\r\n this.calibrationNodes.push(node);\r\n };\r\n\r\n addCalibrationNodeConvolved = node => {\r\n \r\n this.calibrationNodesConvolved.push(node);\r\n }\r\n\r\n makeNewSourceAudioContext = () => {\r\n const options = {\r\n sampleRate: this.sourceSamplingRate,\r\n };\r\n\r\n this.sourceAudioContext = new (window.AudioContext ||\r\n window.webkitAudioContext ||\r\n window.audioContext)(options);\r\n\r\n return this.sourceAudioContext;\r\n };\r\n\r\n makeNewSourceAudioContextConvolved = () => {\r\n const options = {\r\n sampleRate: this.sourceSamplingRate,\r\n };\r\n\r\n this.sourceAudioContextConvolved = new (window.AudioContext ||\r\n window.webkitAudioContext ||\r\n window.audioContext)(options);\r\n\r\n return this.sourceAudioContextConvolved;\r\n };\r\n\r\n\r\n\r\n /** .\r\n * .\r\n * .\r\n * Download the result of the calibration roudns\r\n *\r\n * @example\r\n */\r\n downloadData = () => {\r\n const recordings = this.getAllRecordedSignals();\r\n const i = recordings.length - 1;\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[i], `recordedMLSignal_${i}_unconvolved.csv`);\r\n };\r\n
|
|
777
|
+
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 */\r\n\r\n\r\n\r\n\r\n/**\r\n * .\r\n * .\r\n * .\r\n * Provides methods for calibrating the user's speakers\r\n *\r\n * @extends AudioRecorder\r\n */\r\nclass AudioCalibrator extends _audioRecorder__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n /**\r\n *\r\n * @param numCaptures\r\n * @param numMLSPerCapture\r\n * @example\r\n */\r\n constructor(numCaptures = 1, numMLSPerCapture = 1) {\r\n super();\r\n this.numCaptures = numCaptures;\r\n this.numMLSPerCapture = numMLSPerCapture;\r\n this.pyServerAPI = new _server_PythonServerAPI__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\r\n }\r\n\r\n /** @private */\r\n #isCalibrating = false;\r\n\r\n /** @private */\r\n sourceAudioContext;\r\n\r\n /** @private */\r\n sourceAudioContextConvolved;\r\n\r\n /** @protected */\r\n numCalibratingRounds = 1;\r\n\r\n /** @protected */\r\n numSuccessfulCaptured = 0;\r\n\r\n /** @private */\r\n sourceSamplingRate;\r\n\r\n /** @protected */\r\n calibrationNodes = [];\r\n\r\n /** @protected */\r\n calibrationNodesConvolved = [];\r\n\r\n /** @protected */\r\n localAudio;\r\n\r\n /**\r\n * Called when a call is received.\r\n * Creates a local audio DOM element and attaches it to the page.\r\n *\r\n * @param targetElement\r\n * @example\r\n */\r\n createLocalAudio = targetElement => {\r\n this.localAudio = document.createElement('audio');\r\n this.localAudio.setAttribute('id', 'localAudio');\r\n targetElement.appendChild(this.localAudio);\r\n };\r\n\r\n /**\r\n *\r\n * @param {MediaStream} stream\r\n * @param {Function} playCalibrationAudio - (async) function that plays the calibration audio\r\n * @param {*} beforePlay - (async) function that is called before playing the audio\r\n * @param {*} beforeRecord - (async) function that is called before recording\r\n * @param {*} duringRecord - (async) function that is called while recording\r\n * @param {*} afterRecord - (async) function that is called after recording\r\n * @example\r\n */\r\n calibrationSteps = async (\r\n stream,\r\n playCalibrationAudio,\r\n beforePlay = async () => {},\r\n beforeRecord = async () => {},\r\n loopCondition = () => false,\r\n duringRecord = async () => {},\r\n afterRecord = async () => {},\r\n mode\r\n ) => {\r\n this.numSuccessfulCaptured = 0;\r\n\r\n // do something before playing such as using the MLS to fill the buffers\r\n console.warn('beforePlay');\r\n await beforePlay();\r\n\r\n // play calibration audio\r\n console.warn('playCalibrationAudio');\r\n playCalibrationAudio();\r\n\r\n // do something before recording such as awaiting a certain amount of time\r\n console.warn('beforeRecord');\r\n await beforeRecord();\r\n\r\n // calibration loop\r\n while (loopCondition()) {\r\n // start recording\r\n console.warn('startRecording');\r\n await this.startRecording(stream);\r\n\r\n // do something during the recording such as sleep n amount of time\r\n console.warn('duringRecord');\r\n await duringRecord();\r\n\r\n // when done, stop recording\r\n console.warn('stopRecording');\r\n await this.stopRecording(mode);\r\n\r\n // do something after recording such as start processing values\r\n console.warn('afterRecord');\r\n await afterRecord();\r\n\r\n // eslint-disable-next-line no-await-in-loop\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1);\r\n }\r\n };\r\n\r\n /**\r\n *\r\n * @param {MediaStream} stream\r\n * @param {Function} playCalibrationAudio - (async) function that plays the calibration audio\r\n * @param {*} beforeRecord - (async) function that is called before recording\r\n * @param {*} afterRecord - (async) function that is called after recording\r\n * @param {Number} gainValue - the gain value to set the gain node to\r\n */\r\n volumeCalibrationSteps = async (\r\n stream,\r\n playCalibrationAudio,\r\n beforeRecord = () => {},\r\n afterRecord = () => {},\r\n gainValue,\r\n lCalib = 104.92978421490648\r\n ) => {\r\n this.numCalibratingRoundsCompleted = 0;\r\n\r\n // calibration loop\r\n while (!this.#isCalibrating && this.numCalibratingRoundsCompleted < this.numCalibratingRounds) {\r\n // before recording\r\n await beforeRecord(gainValue);\r\n\r\n // start recording\r\n await this.startRecording(stream);\r\n\r\n // play calibration audio\r\n console.log(`Calibration Round ${this.numCalibratingRoundsCompleted}`);\r\n await playCalibrationAudio();\r\n\r\n // when done, stop recording\r\n console.log('Calibration Round Complete');\r\n await this.stopRecording();\r\n\r\n // after recording\r\n await afterRecord(lCalib);\r\n\r\n this.calibrationNodes = [];\r\n\r\n // eslint-disable-next-line no-await-in-loop\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(2);\r\n this.numCalibratingRoundsCompleted += 1;\r\n }\r\n };\r\n\r\n /**\r\n * Getter for the isCalibrating property.\r\n *\r\n * @public\r\n * @returns - True if the audio is being calibrated, false otherwise.\r\n * @example\r\n */\r\n getCalibrationStatus = () => this.#isCalibrating;\r\n\r\n /** .\r\n * .\r\n * .\r\n * Set the sampling rate to the value received from the listener\r\n *\r\n * @param {*} sinkSamplingRate\r\n * @param samplingRate\r\n * @example\r\n */\r\n setSamplingRates = samplingRate => {\r\n this.sinkSamplingRate = samplingRate;\r\n this.sourceSamplingRate = samplingRate;\r\n\r\n // this.emit('update', {message: `sampling at ${samplingRate}Hz...`});\r\n };\r\n\r\n sampleRatesSet = () => this.sourceSamplingRate && this.sinkSamplingRate;\r\n\r\n addCalibrationNode = node => {\r\n this.calibrationNodes.push(node);\r\n };\r\n\r\n addCalibrationNodeConvolved = node => {\r\n \r\n this.calibrationNodesConvolved.push(node);\r\n }\r\n\r\n makeNewSourceAudioContext = () => {\r\n const options = {\r\n sampleRate: this.sourceSamplingRate,\r\n };\r\n\r\n this.sourceAudioContext = new (window.AudioContext ||\r\n window.webkitAudioContext ||\r\n window.audioContext)(options);\r\n\r\n return this.sourceAudioContext;\r\n };\r\n\r\n makeNewSourceAudioContextConvolved = () => {\r\n const options = {\r\n sampleRate: this.sourceSamplingRate,\r\n };\r\n\r\n this.sourceAudioContextConvolved = new (window.AudioContext ||\r\n window.webkitAudioContext ||\r\n window.audioContext)(options);\r\n\r\n return this.sourceAudioContextConvolved;\r\n };\r\n\r\n\r\n\r\n /** .\r\n * .\r\n * .\r\n * Download the result of the calibration roudns\r\n *\r\n * @example\r\n */\r\n downloadData = () => {\r\n const recordings = this.getAllRecordedSignals();\r\n const i = recordings.length - 1;\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[i], `recordedMLSignal_${i}_unconvolved.csv`);\r\n };\r\n downloadSingleUnfilteredRecording = () => {\r\n const recordings = this.getAllRecordedSignals();\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[0], `recordedMLSignal_unconvolved.csv`);\r\n }\r\n downloadSingleFilteredRecording = () => {\r\n const recordings = this.getAllFilteredRecordedSignals();\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[0], `recordedMLSignal_convolved.csv`);\r\n }\r\n downloadUnfilteredRecordings = () => {\r\n const recordings = this.getAllRecordedSignals();\r\n console.log(\"unfilterd download?\");\r\n for (let i = 0; i < recordings.length; i++){\r\n console.log(i);\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[i], `recordedMLSignal_${i}_unconvolved.csv`);\r\n }\r\n };\r\n downloadFilteredRecordings = () => {\r\n const recordings = this.getAllFilteredRecordedSignals();\r\n for (let i = 0; i < recordings.length; i++){\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(recordings[i], `recordedMLSignal_${i}_convolved.csv`);\r\n }\r\n };\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (AudioCalibrator);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/audioCalibrator.js?");
|
|
778
778
|
|
|
779
779
|
/***/ }),
|
|
780
780
|
|
|
@@ -785,7 +785,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _aud
|
|
|
785
785
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
786
786
|
|
|
787
787
|
"use strict";
|
|
788
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _myEventEmitter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../myEventEmitter */ \"./src/myEventEmitter.js\");\n\r\n\r\n/**\r\n * @class provides a simple interface for recording audio from a microphone\r\n * using the Media Recorder API.\r\n */\r\nclass AudioRecorder extends _myEventEmitter__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n /** @private */\r\n #mediaRecorder;\r\n\r\n /** @private */\r\n #recordedChunks = [];\r\n\r\n /** @private */\r\n #audioBlob;\r\n\r\n /** @private */\r\n #audioContext;\r\n\r\n /** @private */\r\n #recordedSignals = [];\r\n\r\n /** @private */\r\n sinkSamplingRate;\r\n\r\n /**\r\n * Decode the audio data from the recorded audio blob.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #saveRecording = async () => {\r\n const arrayBuffer = await this.#audioBlob.arrayBuffer();\r\n const audioBuffer = await this.#audioContext.decodeAudioData(arrayBuffer);\r\n const data = audioBuffer.getChannelData(0);\r\n\r\n console.log(`Decoded audio buffer with ${data.length} samples`);\r\n this.#recordedSignals.push(Array.from(data));\r\n };\r\n\r\n /**\r\n * Event listener triggered when data is available in the media recorder.\r\n *\r\n * @private\r\n * @param e - The event object.\r\n * @example\r\n */\r\n #onRecorderDataAvailable = e => {\r\n if (e.data && e.data.size > 0) this.#recordedChunks.push(e.data);\r\n };\r\n\r\n /**\r\n * Method to create a media recorder object and set up event listeners.\r\n *\r\n * @private\r\n * @param stream - The stream of audio from the Listener.\r\n * @example\r\n */\r\n #setMediaRecorder = stream => {\r\n // Create a new MediaRecorder object\r\n this.#mediaRecorder = new MediaRecorder(stream);\r\n\r\n // Add event listeners\r\n this.#mediaRecorder.ondataavailable = e => this.#onRecorderDataAvailable(e);\r\n };\r\n\r\n #setAudioContext = () => {\r\n this.#audioContext = new (window.AudioContext ||\r\n window.webkitAudioContext ||\r\n window.audioContext)({\r\n sampleRate: this.sinkSamplingRate,\r\n //sampleRate: 96000\r\n });\r\n };\r\n\r\n /**\r\n * Public method to start the recording process.\r\n *\r\n * @param stream - The stream of audio from the Listener.\r\n * @example\r\n */\r\n startRecording = async stream => {\r\n // Create a fresh audio context\r\n this.#setAudioContext();\r\n // Set up media recorder if needed\r\n if (!this.#mediaRecorder) this.#setMediaRecorder(stream);\r\n // clear recorded chunks\r\n this.#recordedChunks = [];\r\n // start recording\r\n this.#mediaRecorder.start();\r\n };\r\n\r\n /**\r\n * Method to stop the recording process.\r\n *\r\n * @public\r\n * @example\r\n */\r\n stopRecording = async () => {\r\n // Stop the media recorder, and wait for the data to be available\r\n await new Promise(resolve => {\r\n this.#mediaRecorder.onstop = () => {\r\n // when the stop event is triggered, resolve the promise\r\n this.#audioBlob = new Blob(this.#recordedChunks, {\r\n type: 'audio/wav; codecs=opus',\r\n });\r\n resolve(this.#audioBlob);\r\n };\r\n // call stop\r\n this.#mediaRecorder.stop();\r\n });\r\n // Now that we have data, save it\r\n await this.#saveRecording();\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Public method to get the last recorded audio signal\r\n *\r\n * @returns\r\n * @example\r\n */\r\n getLastRecordedSignal = () => this.#recordedSignals[this.#recordedSignals.length - 1];\r\n\r\n /** .\r\n * .\r\n * .\r\n * Public method to get all the recorded audio signals\r\n *\r\n * @returns\r\n * @example\r\n */\r\n getAllRecordedSignals = () => this.#recordedSignals;\r\n\r\n /** .\r\n * .\r\n * .\r\n * Public method to set the sampling rate used by the capture device\r\n *\r\n * @param {Number} sinkSamplingRate - The sampling rate of the capture device\r\n * @example\r\n */\r\n setSinkSamplingRate = sinkSamplingRate => {\r\n this.sinkSamplingRate = sinkSamplingRate;\r\n };\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (AudioRecorder);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/audioRecorder.js?");
|
|
788
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _myEventEmitter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../myEventEmitter */ \"./src/myEventEmitter.js\");\n\r\n\r\n/**\r\n * @class provides a simple interface for recording audio from a microphone\r\n * using the Media Recorder API.\r\n */\r\nclass AudioRecorder extends _myEventEmitter__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n /** @private */\r\n #mediaRecorder;\r\n\r\n /** @private */\r\n #recordedChunks = [];\r\n\r\n /** @private */\r\n #audioBlob;\r\n\r\n /** @private */\r\n #audioContext;\r\n\r\n /** @private */\r\n #recordedSignals = [];\r\n\r\n /** @private */\r\n #filteredRecordings = [];\r\n\r\n /** @private */\r\n sinkSamplingRate;\r\n\r\n /**\r\n * Decode the audio data from the recorded audio blob.\r\n *\r\n * @private\r\n * @example\r\n */\r\n #saveRecording = async () => {\r\n const arrayBuffer = await this.#audioBlob.arrayBuffer();\r\n const audioBuffer = await this.#audioContext.decodeAudioData(arrayBuffer);\r\n const data = audioBuffer.getChannelData(0);\r\n\r\n console.log(`Decoded audio buffer with ${data.length} samples`);\r\n this.#recordedSignals.push(Array.from(data));\r\n };\r\n\r\n #saveFilteredRecording = async () => {\r\n const arrayBuffer = await this.#audioBlob.arrayBuffer();\r\n const audioBuffer = await this.#audioContext.decodeAudioData(arrayBuffer);\r\n const data = audioBuffer.getChannelData(0);\r\n\r\n console.log(`Decoded audio buffer with ${data.length} samples`);\r\n this.#filteredRecordings.push(Array.from(data));\r\n };\r\n\r\n /**\r\n * Event listener triggered when data is available in the media recorder.\r\n *\r\n * @private\r\n * @param e - The event object.\r\n * @example\r\n */\r\n #onRecorderDataAvailable = e => {\r\n if (e.data && e.data.size > 0) this.#recordedChunks.push(e.data);\r\n };\r\n\r\n /**\r\n * Method to create a media recorder object and set up event listeners.\r\n *\r\n * @private\r\n * @param stream - The stream of audio from the Listener.\r\n * @example\r\n */\r\n #setMediaRecorder = stream => {\r\n // Create a new MediaRecorder object\r\n this.#mediaRecorder = new MediaRecorder(stream);\r\n\r\n // Add event listeners\r\n this.#mediaRecorder.ondataavailable = e => this.#onRecorderDataAvailable(e);\r\n };\r\n\r\n #setAudioContext = () => {\r\n this.#audioContext = new (window.AudioContext ||\r\n window.webkitAudioContext ||\r\n window.audioContext)({\r\n sampleRate: this.sinkSamplingRate,\r\n //sampleRate: 96000\r\n });\r\n };\r\n\r\n /**\r\n * Public method to start the recording process.\r\n *\r\n * @param stream - The stream of audio from the Listener.\r\n * @example\r\n */\r\n startRecording = async stream => {\r\n // Create a fresh audio context\r\n this.#setAudioContext();\r\n // Set up media recorder if needed\r\n if (!this.#mediaRecorder) this.#setMediaRecorder(stream);\r\n // clear recorded chunks\r\n this.#recordedChunks = [];\r\n // start recording\r\n this.#mediaRecorder.start();\r\n };\r\n\r\n /**\r\n * Method to stop the recording process.\r\n *\r\n * @public\r\n * @example\r\n */\r\n stopRecording = async (mode) => {\r\n // Stop the media recorder, and wait for the data to be available\r\n await new Promise(resolve => {\r\n this.#mediaRecorder.onstop = () => {\r\n // when the stop event is triggered, resolve the promise\r\n this.#audioBlob = new Blob(this.#recordedChunks, {\r\n type: 'audio/wav; codecs=opus',\r\n });\r\n resolve(this.#audioBlob);\r\n };\r\n // call stop\r\n this.#mediaRecorder.stop();\r\n });\r\n // Now that we have data, save it\r\n if (mode === 'filtered'){\r\n await this.#saveFilteredRecording();\r\n }else{\r\n await this.#saveRecording();\r\n }\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Public method to get the last recorded audio signal\r\n *\r\n * @returns\r\n * @example\r\n */\r\n getLastRecordedSignal = () => this.#recordedSignals[this.#recordedSignals.length - 1];\r\n\r\n /** .\r\n * .\r\n * .\r\n * Public method to get all the recorded audio signals\r\n *\r\n * @returns\r\n * @example\r\n */\r\n getAllRecordedSignals = () => this.#recordedSignals;\r\n\r\n /** .\r\n * .\r\n * .\r\n * Public method to get all the recorded audio signals\r\n *\r\n * @returns\r\n * @example\r\n */\r\n getAllFilteredRecordedSignals = () => this.#filteredRecordings;\r\n\r\n /** .\r\n * .\r\n * .\r\n * Public method to set the sampling rate used by the capture device\r\n *\r\n * @param {Number} sinkSamplingRate - The sampling rate of the capture device\r\n * @example\r\n */\r\n setSinkSamplingRate = sinkSamplingRate => {\r\n this.sinkSamplingRate = sinkSamplingRate;\r\n };\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (AudioRecorder);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/audioRecorder.js?");
|
|
789
789
|
|
|
790
790
|
/***/ }),
|
|
791
791
|
|
|
@@ -796,7 +796,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _myE
|
|
|
796
796
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
797
797
|
|
|
798
798
|
"use strict";
|
|
799
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../audioCalibrator */ \"./src/tasks/audioCalibrator.js\");\n/* harmony import */ var _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mlsGen/mlsGenInterface */ \"./src/tasks/impulse-response/mlsGen/mlsGenInterface.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../utils */ \"./src/utils.js\");\n\r\n\r\n\r\n\r\n\r\n/**\r\n *\r\n */\r\nclass ImpulseResponse extends _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n /**\r\n * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.\r\n *\r\n * @param {Object<boolean, number, number, number>} calibratorParams - paramter object\r\n * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures\r\n * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated\r\n * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform\r\n * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture\r\n */\r\n constructor({download = false, mlsOrder = 18, numCaptures = 3, numMLSPerCapture = 4, lowHz = 20, highHz = 10000}) {\r\n super(numCaptures, numMLSPerCapture);\r\n this.#mlsOrder = parseInt(mlsOrder, 10);\r\n this.#P = 2 ** mlsOrder - 1;\r\n this.#download = download;\r\n this.#mls = [];\r\n this.#lowHz = lowHz;\r\n this.#highHz = highHz;\r\n }\r\n\r\n /** @private */\r\n #download;\r\n\r\n /** @private */\r\n #mlsGenInterface;\r\n\r\n /** @private */\r\n #mlsBufferView;\r\n\r\n /** @private */\r\n invertedImpulseResponse = null;\r\n\r\n /** @private */\r\n impulseResponses = [];\r\n\r\n /** @private */\r\n #mlsOrder;\r\n\r\n /** @private */\r\n #lowHz;\r\n\r\n /** @private */\r\n #highHz;\r\n\r\n /** @private */\r\n #mls;\r\n\r\n /** @private */\r\n #P;\r\n\r\n /** @private */\r\n #audioContext;\r\n\r\n /** @private */\r\n TAPER_SECS = 5;\r\n\r\n /** @private */\r\n offsetGainNode;\r\n\r\n /** @private */\r\n convolution;\r\n\r\n /** .\r\n * .\r\n * .\r\n * Sends all the computed impulse responses to the backend server for processing\r\n *\r\n * @returns sets the resulting inverted impulse response to the class property\r\n * @example\r\n */\r\n sendImpulseResponsesToServerForProcessing = async () => {\r\n const computedIRs = await Promise.all(this.impulseResponses);\r\n const mls = this.#mls;\r\n const lowHz = this.#lowHz;\r\n const highHz = this.#highHz;\r\n this.emit('update', {message: `computing the IIR...`});\r\n return this.pyServerAPI\r\n .getInverseImpulseResponse({\r\n payload: computedIRs.slice(0, this.numCaptures),\r\n mls,\r\n lowHz,\r\n highHz\r\n })\r\n .then(res => {\r\n console.log(res);\r\n this.emit('update', {message: `done computing the IIR...`});\r\n this.invertedImpulseResponse = res[\"iir\"];\r\n this.convolution = res[\"convolution\"];\r\n })\r\n .catch(err => {\r\n // this.emit('InvertedImpulseResponse', {res: false});\r\n console.error(err);\r\n });\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing\r\n *\r\n * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed\r\n * @example\r\n */\r\n sendRecordingToServerForProcessing = signalCsv => {\r\n const allSignals = this.getAllRecordedSignals();\r\n const numSignals = allSignals.length;\r\n const mls = this.#mls;\r\n const payload =\r\n signalCsv && signalCsv.length > 0 ? (0,_utils__WEBPACK_IMPORTED_MODULE_2__.csvToArray)(signalCsv) : allSignals[numSignals - 1];\r\n\r\n this.emit('update', {message: `computing the IR of the last recording...`});\r\n this.impulseResponses.push(\r\n this.pyServerAPI\r\n .getImpulseResponse({\r\n sampleRate: this.sourceSamplingRate || 96000,\r\n payload,\r\n mls,\r\n P: this.#P,\r\n })\r\n .then(res => {\r\n if (this.numSuccessfulCaptured < this.numCaptures) {\r\n this.numSuccessfulCaptured += 1;\r\n console.log(\"num succ capt: \" + this.numSuccessfulCaptured);\r\n this.emit('update', {\r\n message: `${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`,\r\n });\r\n }\r\n return res;\r\n })\r\n .catch(err => {\r\n console.error(err);\r\n })\r\n );\r\n };\r\n\r\n /**\r\n * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number\r\n * of MLS periods defined in the constructor.\r\n *\r\n * @example\r\n */\r\n #awaitDesiredMLSLength = async () => {\r\n // seconds per MLS = P / SR\r\n // await N * P / SR\r\n this.emit('update', {\r\n message: `sampling the calibration signal...`,\r\n });\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)((this.#P / this.sourceSamplingRate) * this.numMLSPerCapture);\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state\r\n *\r\n * @example\r\n */\r\n #awaitSignalOnset = async () => {\r\n this.emit('update', {\r\n message: `waiting for the signal to stabalize...`,\r\n });\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(this.TAPER_SECS);\r\n };\r\n\r\n /**\r\n * Called immediately after a recording is captured. Used to process the resulting signal\r\n * whether by sending the result to a server or by computing a result locally.\r\n *\r\n * @example\r\n */\r\n #afterMLSRecord = () => {\r\n if (this.#download) {\r\n this.downloadData();\r\n }\r\n this.sendRecordingToServerForProcessing();\r\n };\r\n\r\n #afterMLSwIIRRecord = () => {\r\n if (this.#download) {\r\n this.downloadConvolvedData();\r\n }\r\n if (this.numSuccessfulCaptured < this.numCaptures) {\r\n this.numSuccessfulCaptured += 1;\r\n this.emit('update', {\r\n message: `${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`,\r\n });\r\n }\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Created an S Curver Buffer to taper the signal onset\r\n *\r\n * @param {*} length\r\n * @param {*} phase\r\n * @returns\r\n * @example\r\n */\r\n static createSCurveBuffer = (length, phase) => {\r\n const curve = new Float32Array(length);\r\n let i;\r\n for (i = 0; i < length; i += 1) {\r\n // scale the curve to be between 0-1\r\n curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\r\n }\r\n return curve;\r\n };\r\n\r\n static createInverseSCurveBuffer = (length, phase) => {\r\n const curve = new Float32Array(length);\r\n let i;\r\n let j = length - 1;\r\n for (i = 0; i < length; i += 1) {\r\n // scale the curve to be between 0-1\r\n curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;\r\n j -= 1;\r\n }\r\n return curve;\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @param CALIBRATION_TONE_FREQUENCY\r\n * @private\r\n * @example\r\n */\r\n #createPureTonenNode = CALIBRATION_TONE_FREQUENCY => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n\r\n oscilator.frequency.value = CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = 'sine';\r\n gainNode.gain.value = 0.04;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @param dataBuffer\r\n * @private\r\n * @example\r\n */\r\n #createCalibrationNodeFromBuffer = dataBuffer => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const buffer = audioContext.createBuffer(\r\n 1, // number of channels\r\n dataBuffer.length,\r\n audioContext.sampleRate // sample rate\r\n );\r\n\r\n const data = buffer.getChannelData(0); // get data\r\n // fill the buffer with our data\r\n try {\r\n for (let i = 0; i < dataBuffer.length; i += 1) {\r\n data[i] = dataBuffer[i]*.1;\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n console.log(\"mls second, same?\");\r\n console.log(data);\r\n const onsetGainNode = audioContext.createGain();\r\n this.offsetGainNode = audioContext.createGain();\r\n const source = audioContext.createBufferSource();\r\n\r\n source.buffer = buffer;\r\n source.loop = true;\r\n source.connect(onsetGainNode);\r\n onsetGainNode.connect(this.offsetGainNode);\r\n this.offsetGainNode.connect(audioContext.destination);\r\n\r\n const onsetCurve = ImpulseResponse.createSCurveBuffer(this.sourceSamplingRate, Math.PI / 2);\r\n onsetGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);\r\n this.addCalibrationNode(source);\r\n };\r\n\r\n /**\r\n * Given a data buffer, creates the required calibration node\r\n *\r\n * @param {*} dataBufferArray\r\n * @example\r\n */\r\n #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {\r\n if (dataBufferArray.length === 1) {\r\n console.log('data buffer aray');\r\n console.log(dataBufferArray);\r\n this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);\r\n } else {\r\n throw new Error('The length of the data buffer array must be 1');\r\n }\r\n\r\n \r\n };\r\n\r\n /**\r\n * function to put MLS filtered IIR data obtained from\r\n * python server into our audio buffer to be played aloud\r\n */\r\n #putInPythonConv = () => {\r\n const audioCtx = this.makeNewSourceAudioContextConvolved();\r\n const buffer = audioCtx.createBuffer(\r\n 1, // number of channels\r\n this.convolution.length,\r\n audioCtx.sampleRate // sample rate\r\n );\r\n\r\n const data = buffer.getChannelData(0); // get data\r\n // fill the buffer with our data\r\n try {\r\n for (let i = 0; i < this.convolution.length; i += 1) {\r\n data[i] = this.convolution[i];\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n const source = audioCtx.createBufferSource();\r\n\r\n source.buffer = buffer;\r\n source.loop = true;\r\n source.connect(audioCtx.destination);\r\n\r\n this.addCalibrationNodeConvolved(source);\r\n }\r\n \r\n /**\r\n * Creates an audio context and plays it for a few seconds.\r\n *\r\n * @private\r\n * @returns - Resolves when the audio is done playing.\r\n * @example\r\n */\r\n #playCalibrationAudio = () => {\r\n this.calibrationNodes[0].start(0);\r\n this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);\r\n console.log(this.#mls);\r\n this.emit('update', {message: 'playing the calibration tone...'});\r\n }; \r\n\r\n\r\n #playCalibrationAudioConvolved = () => {\r\n this.calibrationNodesConvolved[0].start(0);\r\n this.emit('update',{message: 'playing the convolved calibration tone...'})\r\n }\r\n\r\n /** .\r\n * .\r\n * .\r\n * Stops the audio with tapered offset\r\n *\r\n * @example\r\n */\r\n #stopCalibrationAudio = () => {\r\n this.offsetGainNode.gain.setValueAtTime(\r\n this.offsetGainNode.gain.value,\r\n this.sourceAudioContext.currentTime\r\n );\r\n\r\n this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContext.currentTime, 0.5);\r\n this.calibrationNodes[0].stop(0);\r\n this.sourceAudioContext.close();\r\n this.emit('update', {message: 'stopping the calibration tone...'});\r\n };\r\n\r\n #stopCalibrationAudioConvolved = () => {\r\n this.offsetGainNode.gain.setValueAtTime(\r\n this.offsetGainNode.gain.value,\r\n this.sourceAudioContextConvolved.currentTime\r\n );\r\n\r\n this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContextConvolved.currentTime, 0.5);\r\n //this.calibrationNodesConvolved[0].stop(0);\r\n console.log(\"right before closing volved audio context\");\r\n this.sourceAudioContextConvolved.close();\r\n this.emit('update', {message: 'stopping the convolved calibration tone...'});\r\n\r\n }\r\n\r\n playMLSwithIIR = async (stream, iir) => {\r\n console.log('play mls with iir');\r\n this.invertedImpulseResponse = iir;\r\n // initialize the MLSGenInterface object with it's factory method\r\n \r\n await _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__[\"default\"].factory(\r\n this.#mlsOrder,\r\n this.sinkSamplingRate,\r\n this.sourceSamplingRate\r\n ).then(mlsGenInterface => {\r\n this.#mlsGenInterface = mlsGenInterface;\r\n this.#mlsBufferView = this.#mlsGenInterface.getMLS();\r\n });\r\n\r\n console.log('after mls factory'); //works up to here.\r\n console.log(this.#mls);\r\n // after intializating, start the calibration steps with garbage collection\r\n await this.#mlsGenInterface.withGarbageCollection([\r\n () =>\r\n this.calibrationSteps(\r\n stream,\r\n this.#playCalibrationAudioConvolved, // play audio func (required)\r\n this.#putInPythonConv, // before play func\r\n this.#awaitSignalOnset, // before record\r\n () => this.numSuccessfulCaptured < this.numCaptures,\r\n this.#awaitDesiredMLSLength, // during record\r\n this.#afterMLSwIIRRecord, // after record\r\n ),\r\n ]);\r\n };\r\n\r\n /**\r\n * Public method to start the calibration process. Objects intialized from webassembly allocate new memory\r\n * and must be manually freed. This function is responsible for intializing the MlsGenInterface,\r\n * and wrapping the calibration steps with a garbage collection safe gaurd.\r\n *\r\n * @public\r\n * @param stream - The stream of audio from the Listener.\r\n * @example\r\n */\r\n startCalibration = async stream => {\r\n // initialize the MLSGenInterface object with it's factory method\r\n await _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__[\"default\"].factory(\r\n this.#mlsOrder,\r\n this.sinkSamplingRate,\r\n this.sourceSamplingRate\r\n ).then(mlsGenInterface => {\r\n this.#mlsGenInterface = mlsGenInterface;\r\n this.#mlsBufferView = this.#mlsGenInterface.getMLS();\r\n });\r\n\r\n // after intializating, start the calibration steps with garbage collection\r\n await this.#mlsGenInterface.withGarbageCollection([\r\n () =>\r\n this.calibrationSteps(\r\n stream,\r\n this.#playCalibrationAudio, // play audio func (required)\r\n this.#setCalibrationNodesFromBuffer, // before play func\r\n this.#awaitSignalOnset, // before record\r\n () => this.numSuccessfulCaptured < this.numCaptures, // loop while true\r\n this.#awaitDesiredMLSLength, // during record\r\n this.#afterMLSRecord // after record\r\n ),\r\n ]);\r\n\r\n this.#stopCalibrationAudio();\r\n\r\n // at this stage we've captured all the required signals,\r\n // and have received IRs for each one\r\n // so let's send all the IRs to the server to be converted to a single IIR\r\n await this.sendImpulseResponsesToServerForProcessing();\r\n\r\n if (this.#download) {\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.invertedImpulseResponse,'IIR.csv');\r\n const computedIRagain = await Promise.all(this.impulseResponses)\r\n .then(res => {\r\n for (let i = 0; i < res.length; i++){\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(res[i], `IR_${i}`);\r\n }\r\n })\r\n ;(0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.#mls,\"MLS.csv\");\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.convolution,'python_convolution_mls_iir.csv');\r\n }\r\n\r\n this.numSuccessfulCaptured = 0;\r\n // debugging function, use to test the result of the IIR\r\n await this.playMLSwithIIR(stream, this.invertedImpulseResponse);\r\n this.#stopCalibrationAudioConvolved();\r\n\r\n let recs = this.getAllRecordedSignals();\r\n let unconv_rec = recs[0];\r\n let conv_rec = recs[4];\r\n\r\n let results = await this.pyServerAPI\r\n .getPSD({\r\n unconv_rec,\r\n conv_rec,\r\n })\r\n .then(res => {\r\n return res;\r\n })\r\n .catch(err => {\r\n console.error(err);\r\n })\r\n\r\n let iir_and_plots = {\r\n \"iir\": this.invertedImpulseResponse,\r\n \"x_unconv\": results[\"x_unconv\"],\r\n \"y_unconv\": results[\"y_unconv\"],\r\n \"x_conv\": results[\"x_conv\"],\r\n \"y_conv\": results[\"y_conv\"]\r\n }\r\n\r\n return iir_and_plots;\r\n };\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (ImpulseResponse);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/impulse-response/impulseResponse.js?");
|
|
799
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../audioCalibrator */ \"./src/tasks/audioCalibrator.js\");\n/* harmony import */ var _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mlsGen/mlsGenInterface */ \"./src/tasks/impulse-response/mlsGen/mlsGenInterface.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../utils */ \"./src/utils.js\");\n\r\n\r\n\r\n\r\n\r\n/**\r\n *\r\n */\r\nclass ImpulseResponse extends _audioCalibrator__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n /**\r\n * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.\r\n *\r\n * @param {Object<boolean, number, number, number>} calibratorParams - paramter object\r\n * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures\r\n * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated\r\n * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform\r\n * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture\r\n */\r\n constructor({download = false, mlsOrder = 18, numCaptures = 3, numMLSPerCapture = 4, lowHz = 20, highHz = 10000}) {\r\n super(numCaptures, numMLSPerCapture);\r\n this.#mlsOrder = parseInt(mlsOrder, 10);\r\n this.#P = 2 ** mlsOrder - 1;\r\n this.#download = download;\r\n this.#mls = [];\r\n this.#lowHz = lowHz;\r\n this.#highHz = highHz;\r\n }\r\n\r\n /** @private */\r\n stepNum = 0;\r\n\r\n /** @private */\r\n totalSteps = 25;\r\n\r\n /** @private */\r\n #download;\r\n\r\n /** @private */\r\n #mlsGenInterface;\r\n\r\n /** @private */\r\n #mlsBufferView;\r\n\r\n /** @private */\r\n invertedImpulseResponse = null;\r\n\r\n /** @private */\r\n impulseResponses = [];\r\n\r\n /** @private */\r\n #mlsOrder;\r\n\r\n /** @private */\r\n #lowHz;\r\n\r\n /** @private */\r\n #highHz;\r\n\r\n /** @private */\r\n #mls;\r\n\r\n /** @private */\r\n #P;\r\n\r\n /** @private */\r\n #audioContext;\r\n\r\n /** @private */\r\n TAPER_SECS = 5;\r\n\r\n /** @private */\r\n offsetGainNode;\r\n\r\n /** @private */\r\n convolution;\r\n\r\n /** .\r\n * .\r\n * .\r\n * Sends all the computed impulse responses to the backend server for processing\r\n *\r\n * @returns sets the resulting inverted impulse response to the class property\r\n * @example\r\n */\r\n sendImpulseResponsesToServerForProcessing = async () => {\r\n const computedIRs = await Promise.all(this.impulseResponses);\r\n const mls = this.#mls;\r\n const lowHz = this.#lowHz;\r\n const highHz = this.#highHz;\r\n this.stepNum += 1;\r\n this.emit('update', {message: `Step ${this.stepNum}/${this.totalSteps}: computing the IIR...`});\r\n return this.pyServerAPI\r\n .getInverseImpulseResponse({\r\n payload: computedIRs.slice(0, this.numCaptures),\r\n mls,\r\n lowHz,\r\n highHz\r\n })\r\n .then(res => {\r\n console.log(res);\r\n this.stepNum += 1;\r\n this.emit('update', {message: `Step ${this.stepNum}/${this.totalSteps}: done computing the IIR...`});\r\n this.invertedImpulseResponse = res[\"iir\"];\r\n this.convolution = res[\"convolution\"];\r\n })\r\n .catch(err => {\r\n // this.emit('InvertedImpulseResponse', {res: false});\r\n console.error(err);\r\n });\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing\r\n *\r\n * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed\r\n * @example\r\n */\r\n sendRecordingToServerForProcessing = signalCsv => {\r\n const allSignals = this.getAllRecordedSignals();\r\n const numSignals = allSignals.length;\r\n const mls = this.#mls;\r\n const payload =\r\n signalCsv && signalCsv.length > 0 ? (0,_utils__WEBPACK_IMPORTED_MODULE_2__.csvToArray)(signalCsv) : allSignals[numSignals - 1];\r\n console.log('sending rec');\r\n this.stepNum += 1;\r\n this.emit('update', {message: `Step: ${this.stepNum}/${this.totalSteps}: computing the IR of the last recording...`});\r\n this.impulseResponses.push(\r\n this.pyServerAPI\r\n .getImpulseResponse({\r\n sampleRate: this.sourceSamplingRate || 96000,\r\n payload,\r\n mls,\r\n P: this.#P,\r\n })\r\n .then(res => {\r\n if (this.numSuccessfulCaptured < this.numCaptures) {\r\n this.numSuccessfulCaptured += 1;\r\n console.log(\"num succ capt: \" + this.numSuccessfulCaptured);\r\n this.stepNum += 1;\r\n this.emit('update', {\r\n message: `Step: ${this.stepNum}/${this.totalSteps}: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`,\r\n });\r\n }\r\n return res;\r\n })\r\n .catch(err => {\r\n console.error(err);\r\n })\r\n );\r\n };\r\n\r\n /**\r\n * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number\r\n * of MLS periods defined in the constructor.\r\n *\r\n * @example\r\n */\r\n #awaitDesiredMLSLength = async () => {\r\n // seconds per MLS = P / SR\r\n // await N * P / SR\r\n this.stepNum += 1;\r\n this.emit('update', {\r\n message: `Step ${this.stepNum}/${this.totalSteps}: sampling the calibration signal...`,\r\n });\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)((this.#P / this.sourceSamplingRate) * this.numMLSPerCapture);\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state\r\n *\r\n * @example\r\n */\r\n #awaitSignalOnset = async () => {\r\n this.stepNum += 1;\r\n this.emit('update', {\r\n message: `Step ${this.stepNum}/${this.totalSteps}: waiting for the signal to stabilize...`,\r\n });\r\n await (0,_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(this.TAPER_SECS);\r\n };\r\n\r\n /**\r\n * Called immediately after a recording is captured. Used to process the resulting signal\r\n * whether by sending the result to a server or by computing a result locally.\r\n *\r\n * @example\r\n */\r\n #afterMLSRecord = () => {\r\n console.log('after record');\r\n this.sendRecordingToServerForProcessing();\r\n };\r\n\r\n #afterMLSwIIRRecord = () => {\r\n if (this.numSuccessfulCaptured < this.numCaptures) {\r\n this.numSuccessfulCaptured += 1;\r\n this.stepNum += 1;\r\n this.emit('update', {\r\n message: `Step ${this.stepNum}/${this.totalSteps}: ${this.numSuccessfulCaptured} recordings of convolved MLS captured`,\r\n });\r\n }\r\n };\r\n\r\n /** .\r\n * .\r\n * .\r\n * Created an S Curver Buffer to taper the signal onset\r\n *\r\n * @param {*} length\r\n * @param {*} phase\r\n * @returns\r\n * @example\r\n */\r\n static createSCurveBuffer = (length, phase) => {\r\n const curve = new Float32Array(length);\r\n let i;\r\n for (i = 0; i < length; i += 1) {\r\n // scale the curve to be between 0-1\r\n curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\r\n }\r\n return curve;\r\n };\r\n\r\n static createInverseSCurveBuffer = (length, phase) => {\r\n const curve = new Float32Array(length);\r\n let i;\r\n let j = length - 1;\r\n for (i = 0; i < length; i += 1) {\r\n // scale the curve to be between 0-1\r\n curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;\r\n j -= 1;\r\n }\r\n return curve;\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @param CALIBRATION_TONE_FREQUENCY\r\n * @private\r\n * @example\r\n */\r\n #createPureTonenNode = CALIBRATION_TONE_FREQUENCY => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const oscilator = audioContext.createOscillator();\r\n const gainNode = audioContext.createGain();\r\n\r\n oscilator.frequency.value = CALIBRATION_TONE_FREQUENCY;\r\n oscilator.type = 'sine';\r\n gainNode.gain.value = 0.04;\r\n\r\n oscilator.connect(gainNode);\r\n gainNode.connect(audioContext.destination);\r\n\r\n this.addCalibrationNode(oscilator);\r\n };\r\n\r\n /**\r\n * Construct a Calibration Node with the calibration parameters.\r\n *\r\n * @param dataBuffer\r\n * @private\r\n * @example\r\n */\r\n #createCalibrationNodeFromBuffer = dataBuffer => {\r\n const audioContext = this.makeNewSourceAudioContext();\r\n const buffer = audioContext.createBuffer(\r\n 1, // number of channels\r\n dataBuffer.length,\r\n audioContext.sampleRate // sample rate\r\n );\r\n\r\n const data = buffer.getChannelData(0); // get data\r\n // fill the buffer with our data\r\n try {\r\n for (let i = 0; i < dataBuffer.length; i += 1) {\r\n data[i] = dataBuffer[i]*.1;\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n console.log(\"mls second, same?\");\r\n console.log(data);\r\n const onsetGainNode = audioContext.createGain();\r\n this.offsetGainNode = audioContext.createGain();\r\n const source = audioContext.createBufferSource();\r\n\r\n source.buffer = buffer;\r\n source.loop = true;\r\n source.connect(onsetGainNode);\r\n onsetGainNode.connect(this.offsetGainNode);\r\n this.offsetGainNode.connect(audioContext.destination);\r\n\r\n const onsetCurve = ImpulseResponse.createSCurveBuffer(this.sourceSamplingRate, Math.PI / 2);\r\n onsetGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);\r\n this.addCalibrationNode(source);\r\n };\r\n\r\n /**\r\n * Given a data buffer, creates the required calibration node\r\n *\r\n * @param {*} dataBufferArray\r\n * @example\r\n */\r\n #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {\r\n if (dataBufferArray.length === 1) {\r\n console.log('data buffer aray');\r\n console.log(dataBufferArray);\r\n this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);\r\n } else {\r\n throw new Error('The length of the data buffer array must be 1');\r\n }\r\n\r\n \r\n };\r\n\r\n /**\r\n * function to put MLS filtered IIR data obtained from\r\n * python server into our audio buffer to be played aloud\r\n */\r\n #putInPythonConv = () => {\r\n const audioCtx = this.makeNewSourceAudioContextConvolved();\r\n const buffer = audioCtx.createBuffer(\r\n 1, // number of channels\r\n this.convolution.length,\r\n audioCtx.sampleRate // sample rate\r\n );\r\n\r\n const data = buffer.getChannelData(0); // get data\r\n // fill the buffer with our data\r\n try {\r\n for (let i = 0; i < this.convolution.length; i += 1) {\r\n data[i] = this.convolution[i];\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n const source = audioCtx.createBufferSource();\r\n\r\n source.buffer = buffer;\r\n source.loop = true;\r\n source.connect(audioCtx.destination);\r\n\r\n this.addCalibrationNodeConvolved(source);\r\n }\r\n \r\n /**\r\n * Creates an audio context and plays it for a few seconds.\r\n *\r\n * @private\r\n * @returns - Resolves when the audio is done playing.\r\n * @example\r\n */\r\n #playCalibrationAudio = () => {\r\n this.calibrationNodes[0].start(0);\r\n this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);\r\n this.stepNum += 1;\r\n this.emit('update', {message: `Step: ${this.stepNum}/${this.totalSteps}: playing the calibration tone...`});\r\n }; \r\n\r\n\r\n #playCalibrationAudioConvolved = () => {\r\n this.calibrationNodesConvolved[0].start(0);\r\n this.stepNum += 1;\r\n this.emit('update',{message: `Step: ${this.stepNum}/${this.totalSteps}: playing the convolved calibration tone...`})\r\n }\r\n\r\n /** .\r\n * .\r\n * .\r\n * Stops the audio with tapered offset\r\n *\r\n * @example\r\n */\r\n #stopCalibrationAudio = () => {\r\n this.offsetGainNode.gain.setValueAtTime(\r\n this.offsetGainNode.gain.value,\r\n this.sourceAudioContext.currentTime\r\n );\r\n\r\n this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContext.currentTime, 0.5);\r\n this.calibrationNodes[0].stop(0);\r\n this.sourceAudioContext.close();\r\n this.stepNum += 1;\r\n this.emit('update', {message: `Step ${this.stepNum}/${this.totalSteps}: stopping the calibration tone...`});\r\n };\r\n\r\n #stopCalibrationAudioConvolved = () => {\r\n this.offsetGainNode.gain.setValueAtTime(\r\n this.offsetGainNode.gain.value,\r\n this.sourceAudioContextConvolved.currentTime\r\n );\r\n\r\n this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContextConvolved.currentTime, 0.5);\r\n //this.calibrationNodesConvolved[0].stop(0);\r\n console.log(\"right before closing volved audio context\");\r\n this.sourceAudioContextConvolved.close();\r\n this.stepNum += 1;\r\n this.emit('update', {message: `Step ${this.stepNum}/${this.totalSteps}: stopping the convolved calibration tone...`});\r\n\r\n }\r\n\r\n playMLSwithIIR = async (stream, iir) => {\r\n console.log('play mls with iir');\r\n this.invertedImpulseResponse = iir;\r\n // initialize the MLSGenInterface object with it's factory method\r\n \r\n await _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__[\"default\"].factory(\r\n this.#mlsOrder,\r\n this.sinkSamplingRate,\r\n this.sourceSamplingRate\r\n ).then(mlsGenInterface => {\r\n this.#mlsGenInterface = mlsGenInterface;\r\n this.#mlsBufferView = this.#mlsGenInterface.getMLS();\r\n });\r\n\r\n console.log('after mls factory'); //works up to here.\r\n console.log(this.#mls);\r\n // after intializating, start the calibration steps with garbage collection\r\n await this.#mlsGenInterface.withGarbageCollection([\r\n () =>\r\n this.calibrationSteps(\r\n stream,\r\n this.#playCalibrationAudioConvolved, // play audio func (required)\r\n this.#putInPythonConv, // before play func\r\n this.#awaitSignalOnset, // before record\r\n () => this.numSuccessfulCaptured < this.numCaptures,\r\n this.#awaitDesiredMLSLength, // during record\r\n this.#afterMLSwIIRRecord, // after record\r\n 'filtered'\r\n ),\r\n ]);\r\n };\r\n\r\n /**\r\n * Public method to start the calibration process. Objects intialized from webassembly allocate new memory\r\n * and must be manually freed. This function is responsible for intializing the MlsGenInterface,\r\n * and wrapping the calibration steps with a garbage collection safe gaurd.\r\n *\r\n * @public\r\n * @param stream - The stream of audio from the Listener.\r\n * @example\r\n */\r\n startCalibration = async stream => {\r\n // initialize the MLSGenInterface object with it's factory method\r\n await _mlsGen_mlsGenInterface__WEBPACK_IMPORTED_MODULE_1__[\"default\"].factory(\r\n this.#mlsOrder,\r\n this.sinkSamplingRate,\r\n this.sourceSamplingRate\r\n ).then(mlsGenInterface => {\r\n this.#mlsGenInterface = mlsGenInterface;\r\n this.#mlsBufferView = this.#mlsGenInterface.getMLS();\r\n });\r\n\r\n // after intializating, start the calibration steps with garbage collection\r\n await this.#mlsGenInterface.withGarbageCollection([\r\n () =>\r\n this.calibrationSteps(\r\n stream,\r\n this.#playCalibrationAudio, // play audio func (required)\r\n this.#setCalibrationNodesFromBuffer, // before play func\r\n this.#awaitSignalOnset, // before record\r\n () => this.numSuccessfulCaptured < this.numCaptures, // loop while true\r\n this.#awaitDesiredMLSLength, // during record\r\n this.#afterMLSRecord, // after record\r\n 'unfiltered'\r\n ),\r\n ]);\r\n\r\n this.#stopCalibrationAudio();\r\n\r\n // at this stage we've captured all the required signals,\r\n // and have received IRs for each one\r\n // so let's send all the IRs to the server to be converted to a single IIR\r\n await this.sendImpulseResponsesToServerForProcessing();\r\n\r\n this.numSuccessfulCaptured = 0;\r\n // debugging function, use to test the result of the IIR\r\n await this.playMLSwithIIR(stream, this.invertedImpulseResponse);\r\n this.#stopCalibrationAudioConvolved();\r\n\r\n let recs = this.getAllRecordedSignals();\r\n let conv_recs = this.getAllFilteredRecordedSignals();\r\n let unconv_rec = recs[0];\r\n let conv_rec = conv_recs[0];\r\n\r\n let results = await this.pyServerAPI\r\n .getPSD({\r\n unconv_rec,\r\n conv_rec,\r\n })\r\n .then(res => {\r\n return res;\r\n })\r\n .catch(err => {\r\n console.error(err);\r\n })\r\n\r\n let iir_and_plots = {\r\n \"iir\": this.invertedImpulseResponse,\r\n \"x_unconv\": results[\"x_unconv\"],\r\n \"y_unconv\": results[\"y_unconv\"],\r\n \"x_conv\": results[\"x_conv\"],\r\n \"y_conv\": results[\"y_conv\"]\r\n }\r\n if (this.#download) {\r\n this.downloadSingleUnfilteredRecording();\r\n this.downloadSingleFilteredRecording();\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.#mls,\"MLS.csv\");\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.convolution,'python_convolution_mls_iir.csv');\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(this.invertedImpulseResponse,'IIR.csv');\r\n const computedIRagain = await Promise.all(this.impulseResponses)\r\n .then(res => {\r\n for (let i = 0; i < res.length; i++){\r\n (0,_utils__WEBPACK_IMPORTED_MODULE_2__.saveToCSV)(res[i], `IR_${i}`);\r\n }\r\n })\r\n }\r\n\r\n return iir_and_plots;\r\n };\r\n}\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (ImpulseResponse);\r\n\n\n//# sourceURL=webpack://speakerCalibrator/./src/tasks/impulse-response/impulseResponse.js?");
|
|
800
800
|
|
|
801
801
|
/***/ }),
|
|
802
802
|
|
package/package.json
CHANGED
|
@@ -82,7 +82,8 @@ class AudioCalibrator extends AudioRecorder {
|
|
|
82
82
|
beforeRecord = async () => {},
|
|
83
83
|
loopCondition = () => false,
|
|
84
84
|
duringRecord = async () => {},
|
|
85
|
-
afterRecord = async () => {}
|
|
85
|
+
afterRecord = async () => {},
|
|
86
|
+
mode
|
|
86
87
|
) => {
|
|
87
88
|
this.numSuccessfulCaptured = 0;
|
|
88
89
|
|
|
@@ -110,7 +111,7 @@ class AudioCalibrator extends AudioRecorder {
|
|
|
110
111
|
|
|
111
112
|
// when done, stop recording
|
|
112
113
|
console.warn('stopRecording');
|
|
113
|
-
await this.stopRecording();
|
|
114
|
+
await this.stopRecording(mode);
|
|
114
115
|
|
|
115
116
|
// do something after recording such as start processing values
|
|
116
117
|
console.warn('afterRecord');
|
|
@@ -240,10 +241,27 @@ class AudioCalibrator extends AudioRecorder {
|
|
|
240
241
|
const i = recordings.length - 1;
|
|
241
242
|
saveToCSV(recordings[i], `recordedMLSignal_${i}_unconvolved.csv`);
|
|
242
243
|
};
|
|
243
|
-
|
|
244
|
+
downloadSingleUnfilteredRecording = () => {
|
|
244
245
|
const recordings = this.getAllRecordedSignals();
|
|
245
|
-
|
|
246
|
-
|
|
246
|
+
saveToCSV(recordings[0], `recordedMLSignal_unconvolved.csv`);
|
|
247
|
+
}
|
|
248
|
+
downloadSingleFilteredRecording = () => {
|
|
249
|
+
const recordings = this.getAllFilteredRecordedSignals();
|
|
250
|
+
saveToCSV(recordings[0], `recordedMLSignal_convolved.csv`);
|
|
251
|
+
}
|
|
252
|
+
downloadUnfilteredRecordings = () => {
|
|
253
|
+
const recordings = this.getAllRecordedSignals();
|
|
254
|
+
console.log("unfilterd download?");
|
|
255
|
+
for (let i = 0; i < recordings.length; i++){
|
|
256
|
+
console.log(i);
|
|
257
|
+
saveToCSV(recordings[i], `recordedMLSignal_${i}_unconvolved.csv`);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
downloadFilteredRecordings = () => {
|
|
261
|
+
const recordings = this.getAllFilteredRecordedSignals();
|
|
262
|
+
for (let i = 0; i < recordings.length; i++){
|
|
263
|
+
saveToCSV(recordings[i], `recordedMLSignal_${i}_convolved.csv`);
|
|
264
|
+
}
|
|
247
265
|
};
|
|
248
266
|
}
|
|
249
267
|
|
|
@@ -20,6 +20,9 @@ class AudioRecorder extends MyEventEmitter {
|
|
|
20
20
|
/** @private */
|
|
21
21
|
#recordedSignals = [];
|
|
22
22
|
|
|
23
|
+
/** @private */
|
|
24
|
+
#filteredRecordings = [];
|
|
25
|
+
|
|
23
26
|
/** @private */
|
|
24
27
|
sinkSamplingRate;
|
|
25
28
|
|
|
@@ -38,6 +41,15 @@ class AudioRecorder extends MyEventEmitter {
|
|
|
38
41
|
this.#recordedSignals.push(Array.from(data));
|
|
39
42
|
};
|
|
40
43
|
|
|
44
|
+
#saveFilteredRecording = async () => {
|
|
45
|
+
const arrayBuffer = await this.#audioBlob.arrayBuffer();
|
|
46
|
+
const audioBuffer = await this.#audioContext.decodeAudioData(arrayBuffer);
|
|
47
|
+
const data = audioBuffer.getChannelData(0);
|
|
48
|
+
|
|
49
|
+
console.log(`Decoded audio buffer with ${data.length} samples`);
|
|
50
|
+
this.#filteredRecordings.push(Array.from(data));
|
|
51
|
+
};
|
|
52
|
+
|
|
41
53
|
/**
|
|
42
54
|
* Event listener triggered when data is available in the media recorder.
|
|
43
55
|
*
|
|
@@ -96,7 +108,7 @@ class AudioRecorder extends MyEventEmitter {
|
|
|
96
108
|
* @public
|
|
97
109
|
* @example
|
|
98
110
|
*/
|
|
99
|
-
stopRecording = async () => {
|
|
111
|
+
stopRecording = async (mode) => {
|
|
100
112
|
// Stop the media recorder, and wait for the data to be available
|
|
101
113
|
await new Promise(resolve => {
|
|
102
114
|
this.#mediaRecorder.onstop = () => {
|
|
@@ -110,7 +122,11 @@ class AudioRecorder extends MyEventEmitter {
|
|
|
110
122
|
this.#mediaRecorder.stop();
|
|
111
123
|
});
|
|
112
124
|
// Now that we have data, save it
|
|
113
|
-
|
|
125
|
+
if (mode === 'filtered'){
|
|
126
|
+
await this.#saveFilteredRecording();
|
|
127
|
+
}else{
|
|
128
|
+
await this.#saveRecording();
|
|
129
|
+
}
|
|
114
130
|
};
|
|
115
131
|
|
|
116
132
|
/** .
|
|
@@ -133,6 +149,16 @@ class AudioRecorder extends MyEventEmitter {
|
|
|
133
149
|
*/
|
|
134
150
|
getAllRecordedSignals = () => this.#recordedSignals;
|
|
135
151
|
|
|
152
|
+
/** .
|
|
153
|
+
* .
|
|
154
|
+
* .
|
|
155
|
+
* Public method to get all the recorded audio signals
|
|
156
|
+
*
|
|
157
|
+
* @returns
|
|
158
|
+
* @example
|
|
159
|
+
*/
|
|
160
|
+
getAllFilteredRecordedSignals = () => this.#filteredRecordings;
|
|
161
|
+
|
|
136
162
|
/** .
|
|
137
163
|
* .
|
|
138
164
|
* .
|
|
@@ -189,17 +189,11 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
189
189
|
* @example
|
|
190
190
|
*/
|
|
191
191
|
#afterMLSRecord = () => {
|
|
192
|
-
if (this.#download) {
|
|
193
|
-
this.downloadData();
|
|
194
|
-
}
|
|
195
192
|
console.log('after record');
|
|
196
193
|
this.sendRecordingToServerForProcessing();
|
|
197
194
|
};
|
|
198
195
|
|
|
199
196
|
#afterMLSwIIRRecord = () => {
|
|
200
|
-
if (this.#download) {
|
|
201
|
-
this.downloadConvolvedData();
|
|
202
|
-
}
|
|
203
197
|
if (this.numSuccessfulCaptured < this.numCaptures) {
|
|
204
198
|
this.numSuccessfulCaptured += 1;
|
|
205
199
|
this.stepNum += 1;
|
|
@@ -436,6 +430,7 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
436
430
|
() => this.numSuccessfulCaptured < this.numCaptures,
|
|
437
431
|
this.#awaitDesiredMLSLength, // during record
|
|
438
432
|
this.#afterMLSwIIRRecord, // after record
|
|
433
|
+
'filtered'
|
|
439
434
|
),
|
|
440
435
|
]);
|
|
441
436
|
};
|
|
@@ -470,7 +465,8 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
470
465
|
this.#awaitSignalOnset, // before record
|
|
471
466
|
() => this.numSuccessfulCaptured < this.numCaptures, // loop while true
|
|
472
467
|
this.#awaitDesiredMLSLength, // during record
|
|
473
|
-
this.#afterMLSRecord // after record
|
|
468
|
+
this.#afterMLSRecord, // after record
|
|
469
|
+
'unfiltered'
|
|
474
470
|
),
|
|
475
471
|
]);
|
|
476
472
|
|
|
@@ -481,26 +477,15 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
481
477
|
// so let's send all the IRs to the server to be converted to a single IIR
|
|
482
478
|
await this.sendImpulseResponsesToServerForProcessing();
|
|
483
479
|
|
|
484
|
-
if (this.#download) {
|
|
485
|
-
saveToCSV(this.invertedImpulseResponse,'IIR.csv');
|
|
486
|
-
const computedIRagain = await Promise.all(this.impulseResponses)
|
|
487
|
-
.then(res => {
|
|
488
|
-
for (let i = 0; i < res.length; i++){
|
|
489
|
-
saveToCSV(res[i], `IR_${i}`);
|
|
490
|
-
}
|
|
491
|
-
})
|
|
492
|
-
saveToCSV(this.#mls,"MLS.csv");
|
|
493
|
-
saveToCSV(this.convolution,'python_convolution_mls_iir.csv');
|
|
494
|
-
}
|
|
495
|
-
|
|
496
480
|
this.numSuccessfulCaptured = 0;
|
|
497
481
|
// debugging function, use to test the result of the IIR
|
|
498
482
|
await this.playMLSwithIIR(stream, this.invertedImpulseResponse);
|
|
499
483
|
this.#stopCalibrationAudioConvolved();
|
|
500
484
|
|
|
501
485
|
let recs = this.getAllRecordedSignals();
|
|
486
|
+
let conv_recs = this.getAllFilteredRecordedSignals();
|
|
502
487
|
let unconv_rec = recs[0];
|
|
503
|
-
let conv_rec =
|
|
488
|
+
let conv_rec = conv_recs[0];
|
|
504
489
|
|
|
505
490
|
let results = await this.pyServerAPI
|
|
506
491
|
.getPSD({
|
|
@@ -521,6 +506,19 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
521
506
|
"x_conv": results["x_conv"],
|
|
522
507
|
"y_conv": results["y_conv"]
|
|
523
508
|
}
|
|
509
|
+
if (this.#download) {
|
|
510
|
+
this.downloadSingleUnfilteredRecording();
|
|
511
|
+
this.downloadSingleFilteredRecording();
|
|
512
|
+
saveToCSV(this.#mls,"MLS.csv");
|
|
513
|
+
saveToCSV(this.convolution,'python_convolution_mls_iir.csv');
|
|
514
|
+
saveToCSV(this.invertedImpulseResponse,'IIR.csv');
|
|
515
|
+
const computedIRagain = await Promise.all(this.impulseResponses)
|
|
516
|
+
.then(res => {
|
|
517
|
+
for (let i = 0; i < res.length; i++){
|
|
518
|
+
saveToCSV(res[i], `IR_${i}`);
|
|
519
|
+
}
|
|
520
|
+
})
|
|
521
|
+
}
|
|
524
522
|
|
|
525
523
|
return iir_and_plots;
|
|
526
524
|
};
|