speaker-calibration 2.2.212 → 2.2.213

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.
Files changed (140) hide show
  1. package/.eslintignore +71 -71
  2. package/.eslintrc.json +40 -40
  3. package/.gitignore +81 -81
  4. package/.prettierignore +69 -69
  5. package/.prettierrc +14 -14
  6. package/LICENSE +20 -20
  7. package/README.md +133 -133
  8. package/__mocks__/fileMock.js +1 -1
  9. package/__mocks__/styleMock.js +1 -1
  10. package/babel.config.js +3 -3
  11. package/coverage/clover.xml +71 -71
  12. package/coverage/coverage-final.json +224 -224
  13. package/coverage/lcov-report/PythonServerInterface.js.html +265 -265
  14. package/coverage/lcov-report/base.css +354 -354
  15. package/coverage/lcov-report/block-navigation.js +82 -82
  16. package/coverage/lcov-report/favicon.png +0 -0
  17. package/coverage/lcov-report/index.html +123 -123
  18. package/coverage/lcov-report/prettify.css +101 -101
  19. package/coverage/lcov-report/prettify.js +937 -937
  20. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  21. package/coverage/lcov-report/sorter.js +189 -189
  22. package/coverage/lcov-report/src/index.html +121 -121
  23. package/coverage/lcov-report/src/server/PythonServerInterface.js.html +268 -268
  24. package/coverage/lcov-report/src/server/index.html +123 -123
  25. package/coverage/lcov-report/src/tasks/audioCalibrator.js.html +499 -499
  26. package/coverage/lcov-report/src/tasks/audioRecorder.js.html +412 -412
  27. package/coverage/lcov-report/src/tasks/index.html +143 -143
  28. package/coverage/lcov-report/src/tasks/volume/index.html +123 -123
  29. package/coverage/lcov-report/src/tasks/volume/volume.js.html +409 -409
  30. package/coverage/lcov-report/src/utils.js.html +172 -172
  31. package/coverage/lcov.info +91 -91
  32. package/dist/Procfile +0 -0
  33. package/dist/example/NoSleep.min.js +1 -1
  34. package/dist/example/credentials.json.gpg +0 -0
  35. package/dist/example/fetch-languages-sheets.js +77 -77
  36. package/dist/example/i18n.js +27366 -27366
  37. package/dist/example/index.html +47 -47
  38. package/dist/example/listener.html +79 -79
  39. package/dist/example/server.js +51 -51
  40. package/dist/example/speaker.html +145 -145
  41. package/dist/example/speakerUI.js +273 -273
  42. package/dist/example/styles.css +99 -99
  43. package/dist/listener.js +3459 -0
  44. package/dist/main.js +2420 -207
  45. package/dist/main.js.LICENSE.txt +118 -118
  46. package/dist/mlsGen.js +6814 -6814
  47. package/dist/mlsGen.wasm +0 -0
  48. package/dist/package-lock.json +1018 -1018
  49. package/dist/package.json +18 -18
  50. package/doc/AudioCalibrator.html +417 -417
  51. package/doc/AudioPeer.html +251 -251
  52. package/doc/AudioRecorder.html +195 -195
  53. package/doc/ImpulseResponse.html +215 -215
  54. package/doc/Listener.html +308 -308
  55. package/doc/MlsGenInterface.html +226 -226
  56. package/doc/MyEventEmitter.html +274 -274
  57. package/doc/PythonServerAPI.html +109 -109
  58. package/doc/Speaker-Calibration-UML-Diagram.png +0 -0
  59. package/doc/Speaker.html +276 -276
  60. package/doc/Takes%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +128 -128
  61. package/doc/Takes%20the%20url%20of%20the%20current%20site%0Aand%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +138 -138
  62. package/doc/Takes%20the%20url%20of%20the%20current%20site%20and%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +137 -137
  63. package/doc/Volume.html +88 -88
  64. package/doc/audioCalibrator.js.html +179 -179
  65. package/doc/audioPeer.js.html +175 -175
  66. package/doc/audioRecorder.js.html +163 -163
  67. package/doc/creates%20a%20new%20AudioRecorder%20instance.%20%0ASets%20up%20the%20audio%20context%20and%20file%20reader..html +114 -114
  68. package/doc/fonts/OpenSans-Bold-webfont.eot +0 -0
  69. package/doc/fonts/OpenSans-Bold-webfont.svg +1829 -1829
  70. package/doc/fonts/OpenSans-Bold-webfont.woff +0 -0
  71. package/doc/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  72. package/doc/fonts/OpenSans-BoldItalic-webfont.svg +1829 -1829
  73. package/doc/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  74. package/doc/fonts/OpenSans-Italic-webfont.eot +0 -0
  75. package/doc/fonts/OpenSans-Italic-webfont.svg +1829 -1829
  76. package/doc/fonts/OpenSans-Italic-webfont.woff +0 -0
  77. package/doc/fonts/OpenSans-Light-webfont.eot +0 -0
  78. package/doc/fonts/OpenSans-Light-webfont.svg +1830 -1830
  79. package/doc/fonts/OpenSans-Light-webfont.woff +0 -0
  80. package/doc/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  81. package/doc/fonts/OpenSans-LightItalic-webfont.svg +1834 -1834
  82. package/doc/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  83. package/doc/fonts/OpenSans-Regular-webfont.eot +0 -0
  84. package/doc/fonts/OpenSans-Regular-webfont.svg +1830 -1830
  85. package/doc/fonts/OpenSans-Regular-webfont.woff +0 -0
  86. package/doc/global.html +308 -308
  87. package/doc/index.html +58 -58
  88. package/doc/listener.js.html +170 -170
  89. package/doc/mlsGen_mlsGenInterface.js.html +117 -117
  90. package/doc/myEventEmitter.js.html +124 -124
  91. package/doc/peer-connection_audioPeer.js.html +188 -188
  92. package/doc/peer-connection_listener.js.html +311 -311
  93. package/doc/peer-connection_speaker.js.html +381 -381
  94. package/doc/sc-activity-diagram.png +0 -0
  95. package/doc/scripts/linenumber.js +25 -25
  96. package/doc/scripts/prettify/Apache-License-2.0.txt +202 -202
  97. package/doc/scripts/prettify/lang-css.js +24 -24
  98. package/doc/scripts/prettify/prettify.js +640 -640
  99. package/doc/server_PythonServerAPI.js.html +160 -160
  100. package/doc/speaker.js.html +248 -248
  101. package/doc/styles/jsdoc-default.css +371 -371
  102. package/doc/styles/prettify-jsdoc.css +111 -111
  103. package/doc/styles/prettify-tomorrow.css +163 -163
  104. package/doc/tasks_audioCalibrator.js.html +207 -207
  105. package/doc/tasks_audioRecorder.js.html +190 -190
  106. package/doc/tasks_impulse-response_impulseResponse.js.html +442 -442
  107. package/doc/tasks_impulse-response_mlsGen_mlsGenInterface.js.html +175 -175
  108. package/doc/tasks_volume_volume.js.html +185 -185
  109. package/doc/utils.js.html +105 -105
  110. package/jest.config.js +173 -173
  111. package/netlify.toml +26 -26
  112. package/package.json +78 -73
  113. package/src/config/firebase.js +26 -26
  114. package/src/index.html +21 -21
  115. package/{dist/example → src/listener-app}/listener.js +153 -152
  116. package/src/main.js +23 -23
  117. package/src/myEventEmitter.js +83 -83
  118. package/src/peer-connection/audioPeer.js +183 -183
  119. package/src/peer-connection/listener.js +366 -367
  120. package/src/peer-connection/peerErrors.js +25 -25
  121. package/src/peer-connection/speaker.js +739 -738
  122. package/src/powerCheck.js +98 -98
  123. package/src/server/PythonServerAPI.js +869 -869
  124. package/src/tasks/audioCalibrator.js +351 -351
  125. package/src/tasks/audioRecorder.js +315 -315
  126. package/src/tasks/combination/combination.js +3160 -3031
  127. package/src/tasks/combination/mlsGen/mlsGen.cpp +98 -98
  128. package/src/tasks/combination/mlsGen/mlsGen.hpp +303 -303
  129. package/src/tasks/combination/mlsGen/mlsGenInterface.js +131 -131
  130. package/src/tasks/combination/mlsGen/mlsGenTest.cpp +180 -180
  131. package/src/tasks/impulse-response/impulseResponse.js +610 -610
  132. package/src/tasks/impulse-response/mlsGen/mlsGen.cpp +98 -98
  133. package/src/tasks/impulse-response/mlsGen/mlsGen.hpp +303 -303
  134. package/src/tasks/impulse-response/mlsGen/mlsGenInterface.js +131 -131
  135. package/src/tasks/impulse-response/mlsGen/mlsGenTest.cpp +180 -180
  136. package/src/tasks/volume/volume.cpp +2 -2
  137. package/src/tasks/volume/volume.hpp +22 -22
  138. package/src/tasks/volume/volume.js +279 -279
  139. package/src/utils.js +205 -205
  140. package/webpack.config.js +64 -37
@@ -1,610 +1,610 @@
1
- import AudioCalibrator from '../audioCalibrator';
2
- import MlsGenInterface from './mlsGen/mlsGenInterface';
3
-
4
- import {sleep, csvToArray, saveToCSV} from '../../utils';
5
- import database from '../../config/firebase';
6
- import {ref, set, get, child} from 'firebase/database';
7
-
8
- /**
9
- *
10
- */
11
- class ImpulseResponse extends AudioCalibrator {
12
- /**
13
- * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.
14
- *
15
- * @param {Object<boolean, number, number, number>} calibratorParams - paramter object
16
- * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures
17
- * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated
18
- * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform
19
- * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture
20
- */
21
- constructor({
22
- download = false,
23
- mlsOrder = 18,
24
- numCaptures = 3,
25
- numMLSPerCapture = 4,
26
- lowHz = 20,
27
- highHz = 10000,
28
- loudSpeakerIr = null,
29
- }) {
30
- super(numCaptures, numMLSPerCapture);
31
- this.#mlsOrder = parseInt(mlsOrder, 10);
32
- this.#P = 2 ** mlsOrder - 1;
33
- this.#download = download;
34
- this.#mls = [];
35
- this.#lowHz = lowHz;
36
- this.#highHz = highHz;
37
- }
38
-
39
- /** @private */
40
- stepNum = 0;
41
-
42
- /** @private */
43
- totalSteps = 25;
44
-
45
- /** @private */
46
- #download;
47
-
48
- /** @private */
49
- #mlsGenInterface;
50
-
51
- /** @private */
52
- #mlsBufferView;
53
-
54
- /** @private */
55
- invertedImpulseResponse = null;
56
-
57
- /** @private */
58
- impulseResponses = [];
59
-
60
- /** @private */
61
- #mlsOrder;
62
-
63
- /** @private */
64
- #lowHz;
65
-
66
- /** @private */
67
- #highHz;
68
-
69
- /** @private */
70
- #mls;
71
-
72
- /** @private */
73
- #P;
74
-
75
- /** @private */
76
- #audioContext;
77
-
78
- /** @private */
79
- TAPER_SECS = 5;
80
-
81
- /** @private */
82
- offsetGainNode;
83
-
84
- /** @private */
85
- convolution;
86
-
87
- /** @private */
88
- status_denominator = 6;
89
-
90
- /** @private */
91
- status_numerator = 0;
92
-
93
- /** @private */
94
- percent_complete = 0;
95
-
96
- /** @private */
97
- status = ``;
98
-
99
- /**generate string template that gets reevaluated as variable increases */
100
- generateTemplate = () => {
101
- if (this.percent_complete > 100) {
102
- this.percent_complete = 100;
103
- }
104
- const template = `<div style="display: flex; justify-content: center;"><div style="width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;"><div style="width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;"></div></div></div>`;
105
- return template;
106
- };
107
-
108
- /** increment numerator and percent for status bar */
109
- incrementStatusBar = () => {
110
- this.status_numerator += 1;
111
- this.percent_complete = (this.status_numerator / this.status_denominator) * 100;
112
- };
113
-
114
- /** .
115
- * .
116
- * .
117
- * Sends all the computed impulse responses to the backend server for processing
118
- *
119
- * @returns sets the resulting inverted impulse response to the class property
120
- * @example
121
- */
122
- sendImpulseResponsesToServerForProcessing = async () => {
123
- const computedIRs = await Promise.all(this.impulseResponses);
124
- const filteredComputedIRs = computedIRs.filter(element => {
125
- return element != undefined;
126
- });
127
- const mls = this.#mls;
128
- const lowHz = this.#lowHz;
129
- const highHz = this.#highHz;
130
- this.stepNum += 1;
131
- this.status = `computing the IIR...`.toString() + this.generateTemplate().toString();
132
- this.emit('update', {message: this.status});
133
- return this.pyServerAPI
134
- .getInverseImpulseResponseWithRetry({
135
- payload: filteredComputedIRs.slice(0, this.numCaptures),
136
- mls,
137
- lowHz,
138
- highHz,
139
- })
140
- .then(res => {
141
- console.log(res);
142
- this.stepNum += 1;
143
- this.incrementStatusBar();
144
- this.status = `done computing the IIR...`.toString() + this.generateTemplate().toString();
145
- this.emit('update', {message: this.status});
146
- this.invertedImpulseResponse = res['iir'];
147
- this.convolution = res['convolution'];
148
- })
149
- .catch(err => {
150
- // this.emit('InvertedImpulseResponse', {res: false});
151
- console.error(err);
152
- });
153
- };
154
-
155
- /** .
156
- * .
157
- * .
158
- * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing
159
- *
160
- * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed
161
- * @example
162
- */
163
- sendRecordingToServerForProcessing = signalCsv => {
164
- const allSignals = this.getAllRecordedSignals();
165
- const numSignals = allSignals.length;
166
- const mls = this.#mls;
167
- const payload =
168
- signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
169
- console.log('sending rec');
170
- this.stepNum += 1;
171
- this.status =
172
- `computing the IR of the last recording...`.toString() + this.generateTemplate().toString();
173
- this.emit('update', {message: this.status});
174
- this.impulseResponses.push(
175
- this.pyServerAPI
176
- .getImpulseResponse({
177
- sampleRate: this.sourceSamplingRate || 96000,
178
- payload,
179
- mls,
180
- P: this.#P,
181
- })
182
- .then(res => {
183
- if (this.numSuccessfulCaptured < this.numCaptures) {
184
- this.numSuccessfulCaptured += 1;
185
- console.log('num succ capt: ' + this.numSuccessfulCaptured);
186
- this.stepNum += 1;
187
- this.incrementStatusBar();
188
- this.status =
189
- `${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +
190
- this.generateTemplate().toString();
191
- this.emit('update', {message: this.status});
192
- return res;
193
- }
194
- })
195
- .catch(err => {
196
- console.error(err);
197
- })
198
- );
199
- };
200
-
201
- /**
202
- * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number
203
- * of MLS periods defined in the constructor.
204
- *
205
- * @example
206
- */
207
- #awaitDesiredMLSLength = async () => {
208
- // seconds per MLS = P / SR
209
- // await N * P / SR
210
- this.stepNum += 1;
211
- this.status =
212
- `sampling the calibration signal...`.toString() + this.generateTemplate().toString();
213
- this.emit('update', {message: this.status});
214
- await sleep((this.#P / this.sourceSamplingRate) * this.numMLSPerCapture);
215
- };
216
-
217
- /** .
218
- * .
219
- * .
220
- * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state
221
- *
222
- * @example
223
- */
224
- #awaitSignalOnset = async () => {
225
- this.stepNum += 1;
226
- this.status =
227
- `waiting for the signal to stabilize...`.toString() + this.generateTemplate().toString();
228
- this.emit('update', {message: this.status});
229
- await sleep(this.TAPER_SECS);
230
- };
231
-
232
- /**
233
- * Called immediately after a recording is captured. Used to process the resulting signal
234
- * whether by sending the result to a server or by computing a result locally.
235
- *
236
- * @example
237
- */
238
- #afterMLSRecord = () => {
239
- console.log('after record');
240
- this.sendRecordingToServerForProcessing();
241
- };
242
-
243
- #afterMLSwIIRRecord = () => {
244
- if (this.numSuccessfulCaptured < 1) {
245
- this.numSuccessfulCaptured += 1;
246
- this.stepNum += 1;
247
- this.incrementStatusBar();
248
- this.status =
249
- `${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +
250
- this.generateTemplate().toString();
251
- this.emit('update', {message: this.status});
252
- }
253
- };
254
-
255
- /** .
256
- * .
257
- * .
258
- * Created an S Curver Buffer to taper the signal onset
259
- *
260
- * @param {*} length
261
- * @param {*} phase
262
- * @returns
263
- * @example
264
- */
265
- static createSCurveBuffer = (length, phase) => {
266
- const curve = new Float32Array(length);
267
- let i;
268
- for (i = 0; i < length; i += 1) {
269
- // scale the curve to be between 0-1
270
- curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;
271
- }
272
- return curve;
273
- };
274
-
275
- static createInverseSCurveBuffer = (length, phase) => {
276
- const curve = new Float32Array(length);
277
- let i;
278
- let j = length - 1;
279
- for (i = 0; i < length; i += 1) {
280
- // scale the curve to be between 0-1
281
- curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;
282
- j -= 1;
283
- }
284
- return curve;
285
- };
286
-
287
- /**
288
- * Construct a Calibration Node with the calibration parameters.
289
- *
290
- * @param CALIBRATION_TONE_FREQUENCY
291
- * @private
292
- * @example
293
- */
294
- #createPureTonenNode = CALIBRATION_TONE_FREQUENCY => {
295
- const audioContext = this.makeNewSourceAudioContext();
296
- const oscilator = audioContext.createOscillator();
297
- const gainNode = audioContext.createGain();
298
-
299
- oscilator.frequency.value = CALIBRATION_TONE_FREQUENCY;
300
- oscilator.type = 'sine';
301
- gainNode.gain.value = 0.04;
302
-
303
- oscilator.connect(gainNode);
304
- gainNode.connect(audioContext.destination);
305
-
306
- this.addCalibrationNode(oscilator);
307
- };
308
-
309
- /**
310
- * Construct a Calibration Node with the calibration parameters.
311
- *
312
- * @param dataBuffer
313
- * @private
314
- * @example
315
- */
316
- #createCalibrationNodeFromBuffer = dataBuffer => {
317
- const audioContext = this.makeNewSourceAudioContext();
318
- const buffer = audioContext.createBuffer(
319
- 1, // number of channels
320
- dataBuffer.length,
321
- audioContext.sampleRate // sample rate
322
- );
323
-
324
- const data = buffer.getChannelData(0); // get data
325
- // fill the buffer with our data
326
- try {
327
- for (let i = 0; i < dataBuffer.length; i += 1) {
328
- data[i] = dataBuffer[i] * 0.1;
329
- }
330
- } catch (error) {
331
- console.error(error);
332
- }
333
- console.log('mls second, same?');
334
- console.log(data);
335
- const onsetGainNode = audioContext.createGain();
336
- this.offsetGainNode = audioContext.createGain();
337
- const source = audioContext.createBufferSource();
338
-
339
- source.buffer = buffer;
340
- source.loop = true;
341
- source.connect(onsetGainNode);
342
- onsetGainNode.connect(this.offsetGainNode);
343
- this.offsetGainNode.connect(audioContext.destination);
344
-
345
- const onsetCurve = ImpulseResponse.createSCurveBuffer(this.sourceSamplingRate, Math.PI / 2);
346
- onsetGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
347
- this.addCalibrationNode(source);
348
- };
349
-
350
- /**
351
- * Given a data buffer, creates the required calibration node
352
- *
353
- * @param {*} dataBufferArray
354
- * @example
355
- */
356
- #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {
357
- if (dataBufferArray.length === 1) {
358
- this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);
359
- } else {
360
- throw new Error('The length of the data buffer array must be 1');
361
- }
362
- };
363
-
364
- /**
365
- * function to put MLS filtered IIR data obtained from
366
- * python server into our audio buffer to be played aloud
367
- */
368
- #putInPythonConv = () => {
369
- const audioCtx = this.makeNewSourceAudioContextConvolved();
370
- const buffer = audioCtx.createBuffer(
371
- 1, // number of channels
372
- this.convolution.length,
373
- audioCtx.sampleRate // sample rate
374
- );
375
-
376
- const data = buffer.getChannelData(0); // get data
377
- // fill the buffer with our data
378
- try {
379
- for (let i = 0; i < this.convolution.length; i += 1) {
380
- data[i] = this.convolution[i];
381
- }
382
- } catch (error) {
383
- console.error(error);
384
- }
385
-
386
- const source = audioCtx.createBufferSource();
387
-
388
- source.buffer = buffer;
389
- source.loop = true;
390
- source.connect(audioCtx.destination);
391
-
392
- this.addCalibrationNodeConvolved(source);
393
- };
394
-
395
- /**
396
- * Creates an audio context and plays it for a few seconds.
397
- *
398
- * @private
399
- * @returns - Resolves when the audio is done playing.
400
- * @example
401
- */
402
- #playCalibrationAudio = () => {
403
- this.calibrationNodes[0].start(0);
404
- this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);
405
- this.stepNum += 1;
406
- this.status = `playing the calibration tone...`.toString() + this.generateTemplate().toString();
407
- this.emit('update', {message: this.status});
408
- };
409
-
410
- #playCalibrationAudioConvolved = () => {
411
- this.calibrationNodesConvolved[0].start(0);
412
- this.stepNum += 1;
413
- this.status =
414
- `playing the convolved calibration tone...`.toString() + this.generateTemplate().toString();
415
- this.emit('update', {message: this.status});
416
- };
417
-
418
- /** .
419
- * .
420
- * .
421
- * Stops the audio with tapered offset
422
- *
423
- * @example
424
- */
425
- #stopCalibrationAudio = () => {
426
- this.offsetGainNode.gain.setValueAtTime(
427
- this.offsetGainNode.gain.value,
428
- this.sourceAudioContext.currentTime
429
- );
430
-
431
- this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContext.currentTime, 0.5);
432
- this.calibrationNodes[0].stop(0);
433
- this.sourceAudioContext.close();
434
- this.stepNum += 1;
435
- this.status =
436
- `stopping the calibration tone...`.toString() + this.generateTemplate().toString();
437
- this.emit('update', {message: this.status});
438
- };
439
-
440
- #stopCalibrationAudioConvolved = () => {
441
- this.offsetGainNode.gain.setValueAtTime(
442
- this.offsetGainNode.gain.value,
443
- this.sourceAudioContextConvolved.currentTime
444
- );
445
-
446
- this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContextConvolved.currentTime, 0.5);
447
- this.sourceAudioContextConvolved.close();
448
- this.stepNum += 1;
449
- this.status =
450
- `stopping the convolved calibration tone...`.toString() + this.generateTemplate().toString();
451
- this.emit('update', {message: this.status});
452
- };
453
-
454
- playMLSwithIIR = async (stream, iir) => {
455
- this.invertedImpulseResponse = iir;
456
- // initialize the MLSGenInterface object with it's factory method
457
-
458
- await MlsGenInterface.factory(
459
- this.#mlsOrder,
460
- this.sinkSamplingRate,
461
- this.sourceSamplingRate
462
- ).then(mlsGenInterface => {
463
- this.#mlsGenInterface = mlsGenInterface;
464
- this.#mlsBufferView = this.#mlsGenInterface.getMLS();
465
- });
466
-
467
- // after intializating, start the calibration steps with garbage collection
468
- await this.#mlsGenInterface.withGarbageCollection([
469
- () =>
470
- this.calibrationSteps(
471
- stream,
472
- this.#playCalibrationAudioConvolved, // play audio func (required)
473
- this.#putInPythonConv, // before play func
474
- this.#awaitSignalOnset, // before record
475
- () => this.numSuccessfulCaptured < 1, // < this.numCaptures
476
- this.#awaitDesiredMLSLength, // during record
477
- this.#afterMLSwIIRRecord, // after record
478
- 'filtered'
479
- ),
480
- ]);
481
- };
482
-
483
- // function to write frq and gain to firebase database given speakerID
484
- writeFrqGain = async (speakerID, frq, gain) => {
485
- const data = {
486
- frq,
487
- gain,
488
- };
489
- await set(ref(database, `${speakerID}/linear`), data);
490
- };
491
-
492
- // Function to Read frq and gain from firebase database given speakerID
493
- // returns an array of frq and gain if speakerID exists, returns null otherwise
494
-
495
- readFrqGain = async speakerID => {
496
- const dbRef = ref(database);
497
- const snapshot = await get(child(dbRef, `${speakerID}/linear`));
498
- if (snapshot.exists()) {
499
- return snapshot.val();
500
- }
501
- return null;
502
- };
503
-
504
- // Example of how to use the writeFrqGain and readFrqGain functions
505
- // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);
506
- // Speaker1 is the speakerID you want to write to in the database
507
- // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));
508
- // MiniDSPUMIK_1 is the speakerID with some Data in the database
509
-
510
- /**
511
- * Public method to start the calibration process. Objects intialized from webassembly allocate new memory
512
- * and must be manually freed. This function is responsible for intializing the MlsGenInterface,
513
- * and wrapping the calibration steps with a garbage collection safe gaurd.
514
- *
515
- * @public
516
- * @param stream - The stream of audio from the Listener.
517
- * @example
518
- */
519
-
520
- startCalibration = async stream => {
521
- // initialize the MLSGenInterface object with it's factory method
522
- await MlsGenInterface.factory(
523
- this.#mlsOrder,
524
- this.sinkSamplingRate,
525
- this.sourceSamplingRate
526
- ).then(mlsGenInterface => {
527
- this.#mlsGenInterface = mlsGenInterface;
528
- this.#mlsBufferView = this.#mlsGenInterface.getMLS();
529
- });
530
-
531
- // after intializating, start the calibration steps with garbage collection
532
- await this.#mlsGenInterface.withGarbageCollection([
533
- () =>
534
- this.calibrationSteps(
535
- stream,
536
- this.#playCalibrationAudio, // play audio func (required)
537
- this.#setCalibrationNodesFromBuffer, // before play func
538
- this.#awaitSignalOnset, // before record
539
- () => this.numSuccessfulCaptured < this.numCaptures, // loop while true
540
- this.#awaitDesiredMLSLength, // during record
541
- this.#afterMLSRecord, // after record
542
- 'unfiltered'
543
- ),
544
- ]);
545
-
546
- this.#stopCalibrationAudio();
547
-
548
- // at this stage we've captured all the required signals,
549
- // and have received IRs for each one
550
- // so let's send all the IRs to the server to be converted to a single IIR
551
-
552
- await this.sendImpulseResponsesToServerForProcessing();
553
-
554
- this.numSuccessfulCaptured = 0;
555
- // debugging function, use to test the result of the IIR
556
- await this.playMLSwithIIR(stream, this.invertedImpulseResponse);
557
- this.#stopCalibrationAudioConvolved();
558
-
559
- let recs = this.getAllRecordedSignals();
560
- let conv_recs = this.getAllFilteredRecordedSignals();
561
- let unconv_rec = recs[0];
562
- let conv_rec = conv_recs[0];
563
-
564
- this.status = `computing the PSD graphs...`.toString() + this.generateTemplate().toString();
565
- this.emit('update', {message: this.status});
566
-
567
- let results = await this.pyServerAPI
568
- .getPSDWithRetry({
569
- unconv_rec,
570
- conv_rec,
571
- })
572
- .then(res => {
573
- this.incrementStatusBar();
574
- this.status =
575
- `done computing the PSD graphs`.toString() + this.generateTemplate().toString();
576
- this.emit('update', {message: this.status});
577
- return res;
578
- })
579
- .catch(err => {
580
- console.error(err);
581
- });
582
-
583
- let iir_and_plots = {
584
- iir: this.invertedImpulseResponse,
585
- x_unconv: results['x_unconv'],
586
- y_unconv: results['y_unconv'],
587
- x_conv: results['x_conv'],
588
- y_conv: results['y_conv'],
589
- };
590
-
591
- if (this.#download) {
592
- this.downloadSingleUnfilteredRecording();
593
- this.downloadSingleFilteredRecording();
594
- saveToCSV(this.#mls, 'MLS.csv');
595
- saveToCSV(this.convolution, 'python_convolution_mls_iir.csv');
596
- saveToCSV(this.invertedImpulseResponse, 'IIR.csv');
597
- const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
598
- for (let i = 0; i < res.length; i++) {
599
- if (res[i] != undefined) {
600
- saveToCSV(res[i], `IR_${i}`);
601
- }
602
- }
603
- });
604
- }
605
-
606
- return iir_and_plots;
607
- };
608
- }
609
-
610
- export default ImpulseResponse;
1
+ import AudioCalibrator from '../audioCalibrator';
2
+ import MlsGenInterface from './mlsGen/mlsGenInterface';
3
+
4
+ import {sleep, csvToArray, saveToCSV} from '../../utils';
5
+ import database from '../../config/firebase';
6
+ import {ref, set, get, child} from 'firebase/database';
7
+
8
+ /**
9
+ *
10
+ */
11
+ class ImpulseResponse extends AudioCalibrator {
12
+ /**
13
+ * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.
14
+ *
15
+ * @param {Object<boolean, number, number, number>} calibratorParams - paramter object
16
+ * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures
17
+ * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated
18
+ * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform
19
+ * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture
20
+ */
21
+ constructor({
22
+ download = false,
23
+ mlsOrder = 18,
24
+ numCaptures = 3,
25
+ numMLSPerCapture = 4,
26
+ lowHz = 20,
27
+ highHz = 10000,
28
+ loudSpeakerIr = null,
29
+ }) {
30
+ super(numCaptures, numMLSPerCapture);
31
+ this.#mlsOrder = parseInt(mlsOrder, 10);
32
+ this.#P = 2 ** mlsOrder - 1;
33
+ this.#download = download;
34
+ this.#mls = [];
35
+ this.#lowHz = lowHz;
36
+ this.#highHz = highHz;
37
+ }
38
+
39
+ /** @private */
40
+ stepNum = 0;
41
+
42
+ /** @private */
43
+ totalSteps = 25;
44
+
45
+ /** @private */
46
+ #download;
47
+
48
+ /** @private */
49
+ #mlsGenInterface;
50
+
51
+ /** @private */
52
+ #mlsBufferView;
53
+
54
+ /** @private */
55
+ invertedImpulseResponse = null;
56
+
57
+ /** @private */
58
+ impulseResponses = [];
59
+
60
+ /** @private */
61
+ #mlsOrder;
62
+
63
+ /** @private */
64
+ #lowHz;
65
+
66
+ /** @private */
67
+ #highHz;
68
+
69
+ /** @private */
70
+ #mls;
71
+
72
+ /** @private */
73
+ #P;
74
+
75
+ /** @private */
76
+ #audioContext;
77
+
78
+ /** @private */
79
+ TAPER_SECS = 5;
80
+
81
+ /** @private */
82
+ offsetGainNode;
83
+
84
+ /** @private */
85
+ convolution;
86
+
87
+ /** @private */
88
+ status_denominator = 6;
89
+
90
+ /** @private */
91
+ status_numerator = 0;
92
+
93
+ /** @private */
94
+ percent_complete = 0;
95
+
96
+ /** @private */
97
+ status = ``;
98
+
99
+ /**generate string template that gets reevaluated as variable increases */
100
+ generateTemplate = () => {
101
+ if (this.percent_complete > 100) {
102
+ this.percent_complete = 100;
103
+ }
104
+ const template = `<div style="display: flex; justify-content: center;"><div style="width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;"><div style="width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;"></div></div></div>`;
105
+ return template;
106
+ };
107
+
108
+ /** increment numerator and percent for status bar */
109
+ incrementStatusBar = () => {
110
+ this.status_numerator += 1;
111
+ this.percent_complete = (this.status_numerator / this.status_denominator) * 100;
112
+ };
113
+
114
+ /** .
115
+ * .
116
+ * .
117
+ * Sends all the computed impulse responses to the backend server for processing
118
+ *
119
+ * @returns sets the resulting inverted impulse response to the class property
120
+ * @example
121
+ */
122
+ sendImpulseResponsesToServerForProcessing = async () => {
123
+ const computedIRs = await Promise.all(this.impulseResponses);
124
+ const filteredComputedIRs = computedIRs.filter(element => {
125
+ return element != undefined;
126
+ });
127
+ const mls = this.#mls;
128
+ const lowHz = this.#lowHz;
129
+ const highHz = this.#highHz;
130
+ this.stepNum += 1;
131
+ this.status = `computing the IIR...`.toString() + this.generateTemplate().toString();
132
+ this.emit('update', {message: this.status});
133
+ return this.pyServerAPI
134
+ .getInverseImpulseResponseWithRetry({
135
+ payload: filteredComputedIRs.slice(0, this.numCaptures),
136
+ mls,
137
+ lowHz,
138
+ highHz,
139
+ })
140
+ .then(res => {
141
+ console.log(res);
142
+ this.stepNum += 1;
143
+ this.incrementStatusBar();
144
+ this.status = `done computing the IIR...`.toString() + this.generateTemplate().toString();
145
+ this.emit('update', {message: this.status});
146
+ this.invertedImpulseResponse = res['iir'];
147
+ this.convolution = res['convolution'];
148
+ })
149
+ .catch(err => {
150
+ // this.emit('InvertedImpulseResponse', {res: false});
151
+ console.error(err);
152
+ });
153
+ };
154
+
155
+ /** .
156
+ * .
157
+ * .
158
+ * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing
159
+ *
160
+ * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed
161
+ * @example
162
+ */
163
+ sendRecordingToServerForProcessing = signalCsv => {
164
+ const allSignals = this.getAllRecordedSignals();
165
+ const numSignals = allSignals.length;
166
+ const mls = this.#mls;
167
+ const payload =
168
+ signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
169
+ console.log('sending rec');
170
+ this.stepNum += 1;
171
+ this.status =
172
+ `computing the IR of the last recording...`.toString() + this.generateTemplate().toString();
173
+ this.emit('update', {message: this.status});
174
+ this.impulseResponses.push(
175
+ this.pyServerAPI
176
+ .getImpulseResponse({
177
+ sampleRate: this.sourceSamplingRate || 96000,
178
+ payload,
179
+ mls,
180
+ P: this.#P,
181
+ })
182
+ .then(res => {
183
+ if (this.numSuccessfulCaptured < this.numCaptures) {
184
+ this.numSuccessfulCaptured += 1;
185
+ console.log('num succ capt: ' + this.numSuccessfulCaptured);
186
+ this.stepNum += 1;
187
+ this.incrementStatusBar();
188
+ this.status =
189
+ `${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +
190
+ this.generateTemplate().toString();
191
+ this.emit('update', {message: this.status});
192
+ return res;
193
+ }
194
+ })
195
+ .catch(err => {
196
+ console.error(err);
197
+ })
198
+ );
199
+ };
200
+
201
+ /**
202
+ * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number
203
+ * of MLS periods defined in the constructor.
204
+ *
205
+ * @example
206
+ */
207
+ #awaitDesiredMLSLength = async () => {
208
+ // seconds per MLS = P / SR
209
+ // await N * P / SR
210
+ this.stepNum += 1;
211
+ this.status =
212
+ `sampling the calibration signal...`.toString() + this.generateTemplate().toString();
213
+ this.emit('update', {message: this.status});
214
+ await sleep((this.#P / this.sourceSamplingRate) * this.numMLSPerCapture);
215
+ };
216
+
217
+ /** .
218
+ * .
219
+ * .
220
+ * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state
221
+ *
222
+ * @example
223
+ */
224
+ #awaitSignalOnset = async () => {
225
+ this.stepNum += 1;
226
+ this.status =
227
+ `waiting for the signal to stabilize...`.toString() + this.generateTemplate().toString();
228
+ this.emit('update', {message: this.status});
229
+ await sleep(this.TAPER_SECS);
230
+ };
231
+
232
+ /**
233
+ * Called immediately after a recording is captured. Used to process the resulting signal
234
+ * whether by sending the result to a server or by computing a result locally.
235
+ *
236
+ * @example
237
+ */
238
+ #afterMLSRecord = () => {
239
+ console.log('after record');
240
+ this.sendRecordingToServerForProcessing();
241
+ };
242
+
243
+ #afterMLSwIIRRecord = () => {
244
+ if (this.numSuccessfulCaptured < 1) {
245
+ this.numSuccessfulCaptured += 1;
246
+ this.stepNum += 1;
247
+ this.incrementStatusBar();
248
+ this.status =
249
+ `${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +
250
+ this.generateTemplate().toString();
251
+ this.emit('update', {message: this.status});
252
+ }
253
+ };
254
+
255
+ /** .
256
+ * .
257
+ * .
258
+ * Created an S Curver Buffer to taper the signal onset
259
+ *
260
+ * @param {*} length
261
+ * @param {*} phase
262
+ * @returns
263
+ * @example
264
+ */
265
+ static createSCurveBuffer = (length, phase) => {
266
+ const curve = new Float32Array(length);
267
+ let i;
268
+ for (i = 0; i < length; i += 1) {
269
+ // scale the curve to be between 0-1
270
+ curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;
271
+ }
272
+ return curve;
273
+ };
274
+
275
+ static createInverseSCurveBuffer = (length, phase) => {
276
+ const curve = new Float32Array(length);
277
+ let i;
278
+ let j = length - 1;
279
+ for (i = 0; i < length; i += 1) {
280
+ // scale the curve to be between 0-1
281
+ curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;
282
+ j -= 1;
283
+ }
284
+ return curve;
285
+ };
286
+
287
+ /**
288
+ * Construct a Calibration Node with the calibration parameters.
289
+ *
290
+ * @param CALIBRATION_TONE_FREQUENCY
291
+ * @private
292
+ * @example
293
+ */
294
+ #createPureTonenNode = CALIBRATION_TONE_FREQUENCY => {
295
+ const audioContext = this.makeNewSourceAudioContext();
296
+ const oscilator = audioContext.createOscillator();
297
+ const gainNode = audioContext.createGain();
298
+
299
+ oscilator.frequency.value = CALIBRATION_TONE_FREQUENCY;
300
+ oscilator.type = 'sine';
301
+ gainNode.gain.value = 0.04;
302
+
303
+ oscilator.connect(gainNode);
304
+ gainNode.connect(audioContext.destination);
305
+
306
+ this.addCalibrationNode(oscilator);
307
+ };
308
+
309
+ /**
310
+ * Construct a Calibration Node with the calibration parameters.
311
+ *
312
+ * @param dataBuffer
313
+ * @private
314
+ * @example
315
+ */
316
+ #createCalibrationNodeFromBuffer = dataBuffer => {
317
+ const audioContext = this.makeNewSourceAudioContext();
318
+ const buffer = audioContext.createBuffer(
319
+ 1, // number of channels
320
+ dataBuffer.length,
321
+ audioContext.sampleRate // sample rate
322
+ );
323
+
324
+ const data = buffer.getChannelData(0); // get data
325
+ // fill the buffer with our data
326
+ try {
327
+ for (let i = 0; i < dataBuffer.length; i += 1) {
328
+ data[i] = dataBuffer[i] * 0.1;
329
+ }
330
+ } catch (error) {
331
+ console.error(error);
332
+ }
333
+ console.log('mls second, same?');
334
+ console.log(data);
335
+ const onsetGainNode = audioContext.createGain();
336
+ this.offsetGainNode = audioContext.createGain();
337
+ const source = audioContext.createBufferSource();
338
+
339
+ source.buffer = buffer;
340
+ source.loop = true;
341
+ source.connect(onsetGainNode);
342
+ onsetGainNode.connect(this.offsetGainNode);
343
+ this.offsetGainNode.connect(audioContext.destination);
344
+
345
+ const onsetCurve = ImpulseResponse.createSCurveBuffer(this.sourceSamplingRate, Math.PI / 2);
346
+ onsetGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
347
+ this.addCalibrationNode(source);
348
+ };
349
+
350
+ /**
351
+ * Given a data buffer, creates the required calibration node
352
+ *
353
+ * @param {*} dataBufferArray
354
+ * @example
355
+ */
356
+ #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {
357
+ if (dataBufferArray.length === 1) {
358
+ this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);
359
+ } else {
360
+ throw new Error('The length of the data buffer array must be 1');
361
+ }
362
+ };
363
+
364
+ /**
365
+ * function to put MLS filtered IIR data obtained from
366
+ * python server into our audio buffer to be played aloud
367
+ */
368
+ #putInPythonConv = () => {
369
+ const audioCtx = this.makeNewSourceAudioContextConvolved();
370
+ const buffer = audioCtx.createBuffer(
371
+ 1, // number of channels
372
+ this.convolution.length,
373
+ audioCtx.sampleRate // sample rate
374
+ );
375
+
376
+ const data = buffer.getChannelData(0); // get data
377
+ // fill the buffer with our data
378
+ try {
379
+ for (let i = 0; i < this.convolution.length; i += 1) {
380
+ data[i] = this.convolution[i];
381
+ }
382
+ } catch (error) {
383
+ console.error(error);
384
+ }
385
+
386
+ const source = audioCtx.createBufferSource();
387
+
388
+ source.buffer = buffer;
389
+ source.loop = true;
390
+ source.connect(audioCtx.destination);
391
+
392
+ this.addCalibrationNodeConvolved(source);
393
+ };
394
+
395
+ /**
396
+ * Creates an audio context and plays it for a few seconds.
397
+ *
398
+ * @private
399
+ * @returns - Resolves when the audio is done playing.
400
+ * @example
401
+ */
402
+ #playCalibrationAudio = () => {
403
+ this.calibrationNodes[0].start(0);
404
+ this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);
405
+ this.stepNum += 1;
406
+ this.status = `playing the calibration tone...`.toString() + this.generateTemplate().toString();
407
+ this.emit('update', {message: this.status});
408
+ };
409
+
410
+ #playCalibrationAudioConvolved = () => {
411
+ this.calibrationNodesConvolved[0].start(0);
412
+ this.stepNum += 1;
413
+ this.status =
414
+ `playing the convolved calibration tone...`.toString() + this.generateTemplate().toString();
415
+ this.emit('update', {message: this.status});
416
+ };
417
+
418
+ /** .
419
+ * .
420
+ * .
421
+ * Stops the audio with tapered offset
422
+ *
423
+ * @example
424
+ */
425
+ #stopCalibrationAudio = () => {
426
+ this.offsetGainNode.gain.setValueAtTime(
427
+ this.offsetGainNode.gain.value,
428
+ this.sourceAudioContext.currentTime
429
+ );
430
+
431
+ this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContext.currentTime, 0.5);
432
+ this.calibrationNodes[0].stop(0);
433
+ this.sourceAudioContext.close();
434
+ this.stepNum += 1;
435
+ this.status =
436
+ `stopping the calibration tone...`.toString() + this.generateTemplate().toString();
437
+ this.emit('update', {message: this.status});
438
+ };
439
+
440
+ #stopCalibrationAudioConvolved = () => {
441
+ this.offsetGainNode.gain.setValueAtTime(
442
+ this.offsetGainNode.gain.value,
443
+ this.sourceAudioContextConvolved.currentTime
444
+ );
445
+
446
+ this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContextConvolved.currentTime, 0.5);
447
+ this.sourceAudioContextConvolved.close();
448
+ this.stepNum += 1;
449
+ this.status =
450
+ `stopping the convolved calibration tone...`.toString() + this.generateTemplate().toString();
451
+ this.emit('update', {message: this.status});
452
+ };
453
+
454
+ playMLSwithIIR = async (stream, iir) => {
455
+ this.invertedImpulseResponse = iir;
456
+ // initialize the MLSGenInterface object with it's factory method
457
+
458
+ await MlsGenInterface.factory(
459
+ this.#mlsOrder,
460
+ this.sinkSamplingRate,
461
+ this.sourceSamplingRate
462
+ ).then(mlsGenInterface => {
463
+ this.#mlsGenInterface = mlsGenInterface;
464
+ this.#mlsBufferView = this.#mlsGenInterface.getMLS();
465
+ });
466
+
467
+ // after intializating, start the calibration steps with garbage collection
468
+ await this.#mlsGenInterface.withGarbageCollection([
469
+ () =>
470
+ this.calibrationSteps(
471
+ stream,
472
+ this.#playCalibrationAudioConvolved, // play audio func (required)
473
+ this.#putInPythonConv, // before play func
474
+ this.#awaitSignalOnset, // before record
475
+ () => this.numSuccessfulCaptured < 1, // < this.numCaptures
476
+ this.#awaitDesiredMLSLength, // during record
477
+ this.#afterMLSwIIRRecord, // after record
478
+ 'filtered'
479
+ ),
480
+ ]);
481
+ };
482
+
483
+ // function to write frq and gain to firebase database given speakerID
484
+ writeFrqGain = async (speakerID, frq, gain) => {
485
+ const data = {
486
+ frq,
487
+ gain,
488
+ };
489
+ await set(ref(database, `${speakerID}/linear`), data);
490
+ };
491
+
492
+ // Function to Read frq and gain from firebase database given speakerID
493
+ // returns an array of frq and gain if speakerID exists, returns null otherwise
494
+
495
+ readFrqGain = async speakerID => {
496
+ const dbRef = ref(database);
497
+ const snapshot = await get(child(dbRef, `${speakerID}/linear`));
498
+ if (snapshot.exists()) {
499
+ return snapshot.val();
500
+ }
501
+ return null;
502
+ };
503
+
504
+ // Example of how to use the writeFrqGain and readFrqGain functions
505
+ // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);
506
+ // Speaker1 is the speakerID you want to write to in the database
507
+ // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));
508
+ // MiniDSPUMIK_1 is the speakerID with some Data in the database
509
+
510
+ /**
511
+ * Public method to start the calibration process. Objects intialized from webassembly allocate new memory
512
+ * and must be manually freed. This function is responsible for intializing the MlsGenInterface,
513
+ * and wrapping the calibration steps with a garbage collection safe gaurd.
514
+ *
515
+ * @public
516
+ * @param stream - The stream of audio from the Listener.
517
+ * @example
518
+ */
519
+
520
+ startCalibration = async stream => {
521
+ // initialize the MLSGenInterface object with it's factory method
522
+ await MlsGenInterface.factory(
523
+ this.#mlsOrder,
524
+ this.sinkSamplingRate,
525
+ this.sourceSamplingRate
526
+ ).then(mlsGenInterface => {
527
+ this.#mlsGenInterface = mlsGenInterface;
528
+ this.#mlsBufferView = this.#mlsGenInterface.getMLS();
529
+ });
530
+
531
+ // after intializating, start the calibration steps with garbage collection
532
+ await this.#mlsGenInterface.withGarbageCollection([
533
+ () =>
534
+ this.calibrationSteps(
535
+ stream,
536
+ this.#playCalibrationAudio, // play audio func (required)
537
+ this.#setCalibrationNodesFromBuffer, // before play func
538
+ this.#awaitSignalOnset, // before record
539
+ () => this.numSuccessfulCaptured < this.numCaptures, // loop while true
540
+ this.#awaitDesiredMLSLength, // during record
541
+ this.#afterMLSRecord, // after record
542
+ 'unfiltered'
543
+ ),
544
+ ]);
545
+
546
+ this.#stopCalibrationAudio();
547
+
548
+ // at this stage we've captured all the required signals,
549
+ // and have received IRs for each one
550
+ // so let's send all the IRs to the server to be converted to a single IIR
551
+
552
+ await this.sendImpulseResponsesToServerForProcessing();
553
+
554
+ this.numSuccessfulCaptured = 0;
555
+ // debugging function, use to test the result of the IIR
556
+ await this.playMLSwithIIR(stream, this.invertedImpulseResponse);
557
+ this.#stopCalibrationAudioConvolved();
558
+
559
+ let recs = this.getAllRecordedSignals();
560
+ let conv_recs = this.getAllFilteredRecordedSignals();
561
+ let unconv_rec = recs[0];
562
+ let conv_rec = conv_recs[0];
563
+
564
+ this.status = `computing the PSD graphs...`.toString() + this.generateTemplate().toString();
565
+ this.emit('update', {message: this.status});
566
+
567
+ let results = await this.pyServerAPI
568
+ .getPSDWithRetry({
569
+ unconv_rec,
570
+ conv_rec,
571
+ })
572
+ .then(res => {
573
+ this.incrementStatusBar();
574
+ this.status =
575
+ `done computing the PSD graphs`.toString() + this.generateTemplate().toString();
576
+ this.emit('update', {message: this.status});
577
+ return res;
578
+ })
579
+ .catch(err => {
580
+ console.error(err);
581
+ });
582
+
583
+ let iir_and_plots = {
584
+ iir: this.invertedImpulseResponse,
585
+ x_unconv: results['x_unconv'],
586
+ y_unconv: results['y_unconv'],
587
+ x_conv: results['x_conv'],
588
+ y_conv: results['y_conv'],
589
+ };
590
+
591
+ if (this.#download) {
592
+ this.downloadSingleUnfilteredRecording();
593
+ this.downloadSingleFilteredRecording();
594
+ saveToCSV(this.#mls, 'MLS.csv');
595
+ saveToCSV(this.convolution, 'python_convolution_mls_iir.csv');
596
+ saveToCSV(this.invertedImpulseResponse, 'IIR.csv');
597
+ const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
598
+ for (let i = 0; i < res.length; i++) {
599
+ if (res[i] != undefined) {
600
+ saveToCSV(res[i], `IR_${i}`);
601
+ }
602
+ }
603
+ });
604
+ }
605
+
606
+ return iir_and_plots;
607
+ };
608
+ }
609
+
610
+ export default ImpulseResponse;