speaker-calibration 2.2.51 → 2.2.53

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 (119) hide show
  1. package/.eslintignore +71 -71
  2. package/.eslintrc.json +40 -40
  3. package/.prettierignore +69 -69
  4. package/.prettierrc +14 -14
  5. package/LICENSE +20 -20
  6. package/README.md +133 -133
  7. package/__mocks__/fileMock.js +1 -1
  8. package/__mocks__/styleMock.js +1 -1
  9. package/babel.config.js +3 -3
  10. package/coverage/clover.xml +71 -71
  11. package/coverage/coverage-final.json +224 -224
  12. package/coverage/lcov-report/PythonServerInterface.js.html +265 -265
  13. package/coverage/lcov-report/base.css +354 -354
  14. package/coverage/lcov-report/block-navigation.js +82 -82
  15. package/coverage/lcov-report/index.html +123 -123
  16. package/coverage/lcov-report/prettify.css +101 -101
  17. package/coverage/lcov-report/prettify.js +937 -937
  18. package/coverage/lcov-report/sorter.js +189 -189
  19. package/coverage/lcov-report/src/index.html +121 -121
  20. package/coverage/lcov-report/src/server/PythonServerInterface.js.html +268 -268
  21. package/coverage/lcov-report/src/server/index.html +123 -123
  22. package/coverage/lcov-report/src/tasks/audioCalibrator.js.html +499 -499
  23. package/coverage/lcov-report/src/tasks/audioRecorder.js.html +412 -412
  24. package/coverage/lcov-report/src/tasks/index.html +143 -143
  25. package/coverage/lcov-report/src/tasks/volume/index.html +123 -123
  26. package/coverage/lcov-report/src/tasks/volume/volume.js.html +409 -409
  27. package/coverage/lcov-report/src/utils.js.html +172 -172
  28. package/coverage/lcov.info +91 -91
  29. package/dist/example/fetch-languages-sheets.js +77 -77
  30. package/dist/example/i18n.js +6822 -6822
  31. package/dist/example/index.html +47 -47
  32. package/dist/example/listener.html +61 -61
  33. package/dist/example/listener.js +97 -97
  34. package/dist/example/server.js +51 -51
  35. package/dist/example/speaker.html +139 -139
  36. package/dist/example/speakerUI.js +270 -270
  37. package/dist/example/styles.css +63 -63
  38. package/dist/main.js +17 -17
  39. package/dist/mlsGen.js +6814 -6814
  40. package/dist/mlsGen.wasm +0 -0
  41. package/dist/package-lock.json +1018 -1018
  42. package/dist/package.json +18 -18
  43. package/doc/AudioCalibrator.html +417 -417
  44. package/doc/AudioPeer.html +251 -251
  45. package/doc/AudioRecorder.html +195 -195
  46. package/doc/ImpulseResponse.html +215 -215
  47. package/doc/Listener.html +308 -308
  48. package/doc/MlsGenInterface.html +226 -226
  49. package/doc/MyEventEmitter.html +274 -274
  50. package/doc/PythonServerAPI.html +109 -109
  51. package/doc/Speaker.html +276 -276
  52. package/doc/Takes%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +128 -128
  53. package/doc/Takes%20the%20url%20of%20the%20current%20site%0Aand%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +138 -138
  54. package/doc/Takes%20the%20url%20of%20the%20current%20site%20and%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +137 -137
  55. package/doc/Volume.html +88 -88
  56. package/doc/audioCalibrator.js.html +179 -179
  57. package/doc/audioPeer.js.html +175 -175
  58. package/doc/audioRecorder.js.html +163 -163
  59. package/doc/creates%20a%20new%20AudioRecorder%20instance.%20%0ASets%20up%20the%20audio%20context%20and%20file%20reader..html +114 -114
  60. package/doc/fonts/OpenSans-Bold-webfont.svg +1829 -1829
  61. package/doc/fonts/OpenSans-BoldItalic-webfont.svg +1829 -1829
  62. package/doc/fonts/OpenSans-Italic-webfont.svg +1829 -1829
  63. package/doc/fonts/OpenSans-Light-webfont.svg +1830 -1830
  64. package/doc/fonts/OpenSans-LightItalic-webfont.svg +1834 -1834
  65. package/doc/fonts/OpenSans-Regular-webfont.svg +1830 -1830
  66. package/doc/global.html +308 -308
  67. package/doc/index.html +58 -58
  68. package/doc/listener.js.html +170 -170
  69. package/doc/mlsGen_mlsGenInterface.js.html +117 -117
  70. package/doc/myEventEmitter.js.html +124 -124
  71. package/doc/peer-connection_audioPeer.js.html +188 -188
  72. package/doc/peer-connection_listener.js.html +311 -311
  73. package/doc/peer-connection_speaker.js.html +381 -381
  74. package/doc/scripts/linenumber.js +25 -25
  75. package/doc/scripts/prettify/Apache-License-2.0.txt +202 -202
  76. package/doc/scripts/prettify/lang-css.js +24 -24
  77. package/doc/scripts/prettify/prettify.js +640 -640
  78. package/doc/server_PythonServerAPI.js.html +160 -160
  79. package/doc/speaker.js.html +248 -248
  80. package/doc/styles/jsdoc-default.css +371 -371
  81. package/doc/styles/prettify-jsdoc.css +111 -111
  82. package/doc/styles/prettify-tomorrow.css +163 -163
  83. package/doc/tasks_audioCalibrator.js.html +207 -207
  84. package/doc/tasks_audioRecorder.js.html +190 -190
  85. package/doc/tasks_impulse-response_impulseResponse.js.html +442 -442
  86. package/doc/tasks_impulse-response_mlsGen_mlsGenInterface.js.html +175 -175
  87. package/doc/tasks_volume_volume.js.html +185 -185
  88. package/doc/utils.js.html +105 -105
  89. package/jest.config.js +173 -173
  90. package/makefile +74 -0
  91. package/netlify.toml +26 -26
  92. package/package.json +69 -69
  93. package/src/config/firebase.js +25 -25
  94. package/src/index.html +21 -21
  95. package/src/main.js +23 -23
  96. package/src/myEventEmitter.js +83 -83
  97. package/src/peer-connection/audioPeer.js +151 -151
  98. package/src/peer-connection/listener.js +288 -288
  99. package/src/peer-connection/peerErrors.js +25 -25
  100. package/src/peer-connection/speaker.js +454 -454
  101. package/src/server/PythonServerAPI.js +587 -587
  102. package/src/tasks/audioCalibrator.js +304 -304
  103. package/src/tasks/audioRecorder.js +277 -277
  104. package/src/tasks/combination/combination.js +1958 -1940
  105. package/src/tasks/combination/mlsGen/mlsGen.cpp +98 -98
  106. package/src/tasks/combination/mlsGen/mlsGen.hpp +303 -303
  107. package/src/tasks/combination/mlsGen/mlsGenInterface.js +131 -131
  108. package/src/tasks/combination/mlsGen/mlsGenTest.cpp +180 -180
  109. package/src/tasks/impulse-response/impulseResponse.js +610 -610
  110. package/src/tasks/impulse-response/mlsGen/mlsGen.cpp +98 -98
  111. package/src/tasks/impulse-response/mlsGen/mlsGen.hpp +303 -303
  112. package/src/tasks/impulse-response/mlsGen/mlsGenInterface.js +131 -131
  113. package/src/tasks/impulse-response/mlsGen/mlsGenTest.cpp +180 -180
  114. package/src/tasks/volume/volume.cpp +2 -2
  115. package/src/tasks/volume/volume.hpp +22 -22
  116. package/src/tasks/volume/volume.js +279 -279
  117. package/src/utils.js +55 -55
  118. package/webpack.config.js +37 -37
  119. package/.gitignore +0 -81
@@ -1,1940 +1,1958 @@
1
- import AudioCalibrator from '../audioCalibrator';
2
-
3
- import {sleep, csvToArray, saveToCSV} from '../../utils';
4
- import database from '../../config/firebase';
5
- import {ref, set, get, child} from 'firebase/database';
6
-
7
- /**
8
- *
9
- */
10
- class Combination extends AudioCalibrator {
11
- /**
12
- * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.
13
- *
14
- * @param {Object<boolean, number, number, number>} calibratorParams - paramter object
15
- * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures
16
- * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated
17
- * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform
18
- * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture
19
- */
20
- constructor({
21
- download = false,
22
- mlsOrder = 18,
23
- numCaptures = 3,
24
- numMLSPerCapture = 4,
25
- lowHz = 20,
26
- highHz = 10000,
27
- }) {
28
- super(numCaptures, numMLSPerCapture);
29
- this.#mlsOrder = parseInt(mlsOrder, 10);
30
- this.#P = 2 ** mlsOrder - 1;
31
- this.#download = download;
32
- this.#mls = [];
33
- this.#lowHz = lowHz;
34
- this.#highHz = highHz;
35
- }
36
-
37
- /** @private */
38
- stepNum = 0;
39
-
40
- /** @private */
41
- totalSteps = 25;
42
-
43
- /** @private */
44
- #download;
45
-
46
- /** @private */
47
- #mlsGenInterface;
48
-
49
- /** @private */
50
- #mlsBufferView;
51
-
52
- /** @private */
53
- componentInvertedImpulseResponse = null;
54
-
55
- /** @private */
56
- systemInvertedImpulseResponse = null;
57
-
58
- //averaged and subtracted ir returned from calibration used to calculated iir
59
- /** @private */
60
- ir = null;
61
-
62
- /** @private */
63
- impulseResponses = [];
64
-
65
- /** @private */
66
- #mlsOrder;
67
-
68
- /** @private */
69
- #lowHz;
70
-
71
- /** @private */
72
- #highHz;
73
-
74
- /** @private */
75
- #mls;
76
-
77
- /** @private */
78
- #P;
79
-
80
- /** @private */
81
- #audioContext;
82
-
83
- /** @private */
84
- TAPER_SECS = 5;
85
-
86
- /** @private */
87
- offsetGainNode;
88
-
89
- /** @private */
90
- componentConvolution;
91
-
92
- /** @private */
93
- systemConvolution;
94
-
95
- ////////////////////////volume
96
- /** @private */
97
- #CALIBRATION_TONE_FREQUENCY = 1000; // Hz
98
-
99
- /** @private */
100
- #CALIBRATION_TONE_TYPE = 'sine';
101
-
102
- CALIBRATION_TONE_DURATION = 5; // seconds
103
- calibrateSound1000HzPreSec = 3.5;
104
- calibrateSound1000HzSec = 1.0;
105
- calibrateSound1000HzPostSec = 0.5;
106
-
107
- /** @private */
108
- outDBSPL = null;
109
- THD = null;
110
- outDBSPL1000 = null;
111
-
112
- /** @private */
113
- TAPER_SECS = 0.01; // seconds
114
-
115
- /** @private */
116
- status_denominator = 8;
117
-
118
- /** @private */
119
- status_numerator = 0;
120
-
121
- /** @private */
122
- percent_complete = 0;
123
-
124
- /** @private */
125
- status = ``;
126
-
127
- /**@private */
128
- status_literal = `<div style="display: flex; justify-content: center;"><div style="width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;"><div style="width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;"></div></div></div>`;
129
-
130
- /**@private */
131
- componentIR = null;
132
-
133
- /**@private */
134
- oldComponentIR = null;
135
-
136
- /**@private */
137
- systemIR = null;
138
-
139
- /**@private */
140
- _calibrateSoundCheck = '';
141
-
142
- deviceType = null;
143
-
144
- deviceName = null;
145
-
146
- deviceInfo = null;
147
-
148
- desired_time_per_mls = 0;
149
-
150
- num_mls_to_skip = 0;
151
-
152
- desired_sampling_rate = 0;
153
-
154
- #currentConvolution = [];
155
-
156
- mode = 'unfiltered';
157
-
158
- sourceNode;
159
-
160
- autocorrelations = [];
161
-
162
- iirLength = 0;
163
-
164
- componentInvertedImpulseResponseNoBandpass = [];
165
-
166
- systemInvertedImpulseResponseNoBandpass = [];
167
-
168
- _calibrateSoundBackgroundSecs;
169
-
170
- background_noise = {};
171
-
172
- numSuccessfulBackgroundCaptured;
173
-
174
- _calibrateSoundBurstDb;
175
-
176
- /**generate string template that gets reevaluated as variable increases */
177
- generateTemplate = () => {
178
- if (this.percent_complete > 100) {
179
- this.percent_complete = 100;
180
- }
181
- const template = `<div style="display: flex; justify-content: center; margin-top:12px;"><div style="width: 800px; height: 20px; border: 2px solid #000; border-radius: 10px;"><div style="width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;"></div></div></div>`;
182
- return template;
183
- };
184
-
185
- /** increment numerator and percent for status bar */
186
- incrementStatusBar = () => {
187
- this.status_numerator += 1;
188
- this.percent_complete = (this.status_numerator / this.status_denominator) * 100;
189
- };
190
-
191
- setDeviceType = deviceType => {
192
- this.deviceType = deviceType;
193
- };
194
-
195
- setDeviceName = deviceName => {
196
- this.deviceName = deviceName;
197
- };
198
-
199
- setDeviceInfo = deviceInfo => {
200
- this.deviceInfo = deviceInfo;
201
- };
202
-
203
- /** .
204
- * .
205
- * .
206
- * Sends all the computed impulse responses to the backend server for processing
207
- *
208
- * @returns sets the resulting inverted impulse response to the class property
209
- * @example
210
- */
211
- sendSystemImpulseResponsesToServerForProcessing = async () => {
212
- const computedIRs = await Promise.all(this.impulseResponses);
213
- const filteredComputedIRs = computedIRs.filter(element => {
214
- return element != undefined;
215
- }); //log any errors that are found in this step
216
- const mls = this.#mls;
217
- const lowHz = this.#lowHz; //gain of 1 below cutoff, need gain of 0
218
- const highHz = this.#highHz; //check error for anything other than 10 kHz
219
- const iirLength = this.iirLength;
220
- const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
221
- this.stepNum += 1;
222
- console.log('send impulse responses to server: ' + this.stepNum);
223
- this.status =
224
- `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
225
- this.emit('update', {message: this.status});
226
- return this.pyServerAPI
227
- .getSystemInverseImpulseResponseWithRetry({
228
- payload: filteredComputedIRs.slice(0, this.numCaptures),
229
- mls,
230
- lowHz,
231
- highHz,
232
- iirLength,
233
- num_periods,
234
- sampleRate: this.sourceSamplingRate || 96000,
235
- calibrateSoundBurstDb: this._calibrateSoundBurstDb
236
- })
237
- .then(res => {
238
- console.log(res);
239
- this.stepNum += 1;
240
- console.log('got impulse response ' + this.stepNum);
241
- this.incrementStatusBar();
242
- this.status =
243
- `All Hz Calibration: done computing the IIR...`.toString() +
244
- this.generateTemplate().toString();
245
- this.emit('update', {message: this.status});
246
- this.systemInvertedImpulseResponse = res['iir'];
247
- this.systemIR = res['ir'];
248
- this.systemConvolution = res['convolution'];
249
- this.systemInvertedImpulseResponseNoBandpass = res['iirNoBandpass'];
250
- })
251
- .catch(err => {
252
- console.error(err);
253
- });
254
- };
255
-
256
- /** .
257
- * .
258
- * .
259
- * Sends all the computed impulse responses to the backend server for processing
260
- *
261
- * @returns sets the resulting inverted impulse response to the class property
262
- * @example
263
- */
264
- sendComponentImpulseResponsesToServerForProcessing = async () => {
265
- const computedIRs = await Promise.all(this.impulseResponses);
266
- const filteredComputedIRs = computedIRs.filter(element => {
267
- return element != undefined;
268
- });
269
- const componentIRGains = this.componentIR['Gain'];
270
- const componentIRFreqs = this.componentIR['Freq'];
271
- const mls = this.#mls;
272
- const lowHz = this.#lowHz;
273
- const iirLength = this.iirLength;
274
- const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
275
- const highHz = this.#highHz;
276
- this.stepNum += 1;
277
- console.log('send impulse responses to server: ' + this.stepNum);
278
- this.status =
279
- `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
280
- this.emit('update', {message: this.status});
281
- return this.pyServerAPI
282
- .getComponentInverseImpulseResponseWithRetry({
283
- payload: filteredComputedIRs.slice(0, this.numCaptures),
284
- mls,
285
- lowHz,
286
- highHz,
287
- iirLength,
288
- componentIRGains,
289
- componentIRFreqs,
290
- num_periods,
291
- sampleRate: this.sourceSamplingRate || 96000,
292
- calibrateSoundBurstDb: this._calibrateSoundBurstDb
293
- })
294
- .then(res => {
295
- console.log(res);
296
- this.stepNum += 1;
297
- console.log('got impulse response ' + this.stepNum);
298
- this.incrementStatusBar();
299
- this.status =
300
- `All Hz Calibration: done computing the IIR...`.toString() +
301
- this.generateTemplate().toString();
302
- this.emit('update', {message: this.status});
303
- this.componentInvertedImpulseResponse = res['iir'];
304
- this.componentIR['Gain'] = res['ir'];
305
- this.componentIR['Freq'] = res['frequencies'];
306
- this.componentConvolution = res['convolution'];
307
- this.componentInvertedImpulseResponseNoBandpass = res['iirNoBandpass'];
308
- })
309
- .catch(err => {
310
- // this.emit('InvertedImpulseResponse', {res: false});
311
- console.error(err);
312
- });
313
- };
314
-
315
- sendBackgroundRecording = () => {
316
- const allSignals = this.getAllBackgroundRecordings();
317
- const numSignals = allSignals.length;
318
- const background_rec_whole = allSignals[numSignals - 1];
319
- const fraction = 0.5 / (this._calibrateSoundBackgroundSecs + 0.5);
320
- // Calculate the starting index for slicing the array
321
- const startIndex = Math.round(fraction * background_rec_whole.length);
322
- // Slice the array from the calculated start index to the end of the array
323
- const background_rec = background_rec_whole.slice(startIndex);
324
- console.log('Sending background recording to server for processing');
325
- this.pyServerAPI
326
- .getBackgroundNoisePSDWithRetry({
327
- background_rec,
328
- sampleRate: this.sourceSamplingRate || 96000,
329
- })
330
- .then(res => {
331
- if (this.numSuccessfulBackgroundCaptured < 1) {
332
- this.numSuccessfulBackgroundCaptured += 1;
333
- //storing all background data in background_psd object
334
- this.background_noise['x_background'] = res['x_background'];
335
- this.background_noise['y_background'] = res['y_background'];
336
- this.background_noise['recording'] = background_rec;
337
- }
338
- })
339
- .catch(err => {
340
- console.error(err);
341
- });
342
- };
343
-
344
- /** .
345
- * .
346
- * .
347
- * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing
348
- *
349
- * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed
350
- * @example
351
- */
352
- sendRecordingToServerForProcessing = signalCsv => {
353
- const allSignals = this.getAllUnfilteredRecordedSignals();
354
- console.log(
355
- 'Obtaining last all hz unfiltered recording from #allHzUnfilteredRecordings to send to server for processing'
356
- );
357
- const numSignals = allSignals.length;
358
- const mls = this.#mlsBufferView;
359
- const payload =
360
- signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
361
- console.log('sending rec');
362
- this.stepNum += 1;
363
- console.log('send rec ' + this.stepNum);
364
- this.status =
365
- `All Hz Calibration Step: computing the IR of the last recording...`.toString() +
366
- this.generateTemplate().toString();
367
- this.emit('update', {message: this.status});
368
- this.impulseResponses.push(
369
- this.pyServerAPI
370
- .getImpulseResponse({
371
- sampleRate: this.sourceSamplingRate || 96000,
372
- payload,
373
- mls,
374
- P: this.#P, //get rid of this
375
- numPeriods: this.numMLSPerCapture,
376
- })
377
- .then(res => {
378
- if (this.numSuccessfulCaptured < this.numCaptures) {
379
- this.numSuccessfulCaptured += 1;
380
- console.log('num succ capt: ' + this.numSuccessfulCaptured);
381
- this.stepNum += 1;
382
- console.log('got impulse response ' + this.stepNum);
383
- this.incrementStatusBar();
384
- this.status =
385
- `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +
386
- this.generateTemplate().toString();
387
- this.emit('update', {
388
- message: this.status,
389
- });
390
- this.autocorrelations.push(res['autocorrelation']);
391
- return res['ir'];
392
- }
393
- })
394
- .catch(err => {
395
- console.error(err);
396
- })
397
- );
398
- };
399
-
400
- /**
401
- * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number
402
- * of MLS periods defined in the constructor.
403
- *
404
- * @example
405
- */
406
- #awaitDesiredMLSLength = async () => {
407
- // seconds per MLS = P / SR
408
- // await N * P / SR
409
- this.stepNum += 1;
410
- console.log('await desired length ' + this.stepNum);
411
- this.status =
412
- `All Hz Calibration: sampling the calibration signal...`.toString() +
413
- `\niteration ${this.stepNum}` +
414
- this.generateTemplate();
415
- this.emit('update', {
416
- message: this.status,
417
- });
418
- let time_to_wait = 0;
419
- if (this.mode === 'unfiltered') {
420
- //unfiltered
421
- time_to_wait = (this.#mls.length / this.sourceSamplingRate) * this.numMLSPerCapture;
422
- time_to_wait = time_to_wait * 1.1;
423
- } else if (this.mode === 'filtered') {
424
- //filtered
425
- // time_to_wait =
426
- // (this.#currentConvolution.length / this.sourceSamplingRate) *
427
- // (this.numMLSPerCapture / (this.num_mls_to_skip + this.numMLSPerCapture));
428
- time_to_wait =
429
- (this.#currentConvolution.length / this.sourceSamplingRate) * this.numMLSPerCapture;
430
- time_to_wait = time_to_wait * 1.1;
431
- } else {
432
- throw new Error('Mode broke in awaitDesiredMLSLength');
433
- }
434
-
435
- await sleep(time_to_wait);
436
- };
437
-
438
- /**
439
- * Passed to the background noise recording function, awaits the desired amount of seconds to capture the desired number
440
- * of seconds of background noise
441
- *
442
- * @example
443
- */
444
- #awaitBackgroundNoiseRecording = async () => {
445
- console.log(
446
- 'Waiting ' + this._calibrateSoundBackgroundSecs + ' second(s) to record background noise'
447
- );
448
- let time_to_wait = this._calibrateSoundBackgroundSecs + 0.5;
449
- await sleep(time_to_wait);
450
- };
451
-
452
- /** .
453
- * .
454
- * .
455
- * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state
456
- *
457
- * @example
458
- */
459
- #awaitSignalOnset = async () => {
460
- this.stepNum += 1;
461
- console.log('await signal onset ' + this.stepNum);
462
- this.status =
463
- `All Hz Calibration: waiting for the signal to stabilize...`.toString() +
464
- this.generateTemplate();
465
- this.emit('update', {
466
- message: this.status,
467
- });
468
- let number_of_bursts_to_skip = this.num_mls_to_skip;
469
- let time_to_sleep = 0;
470
- if (this.mode === 'unfiltered') {
471
- time_to_sleep = (this.#mls.length / this.sourceSamplingRate) * number_of_bursts_to_skip;
472
- } else if (this.mode === 'filtered') {
473
- console.log(this.#currentConvolution.length);
474
- // time_to_sleep =
475
- // (this.#currentConvolution.length / this.sourceSamplingRate) *
476
- // (number_of_bursts_to_skip / (number_of_bursts_to_skip + this.numMLSPerCapture));
477
- time_to_sleep =
478
- (this.#currentConvolution.length / this.sourceSamplingRate) * number_of_bursts_to_skip;
479
- } else {
480
- throw new Error('Mode broke in awaitSignalOnset');
481
- }
482
- await sleep(time_to_sleep);
483
- };
484
-
485
- /**
486
- * Called immediately after a recording is captured. Used to process the resulting signal
487
- * whether by sending the result to a server or by computing a result locally.
488
- *
489
- * @example
490
- */
491
- #afterMLSRecord = () => {
492
- console.log('after record');
493
- this.sendRecordingToServerForProcessing();
494
- };
495
-
496
- #afterMLSwIIRRecord = () => {
497
- if (this.numSuccessfulCaptured < 1) {
498
- this.numSuccessfulCaptured += 1;
499
- this.stepNum += 1;
500
- this.incrementStatusBar();
501
- console.log('after mls w iir record for some reason add numSucc capt ' + this.stepNum);
502
- this.status =
503
- `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +
504
- this.generateTemplate().toString();
505
- this.emit('update', {
506
- message: this.status,
507
- });
508
- }
509
- };
510
-
511
- /** .
512
- * .
513
- * .
514
- * Created an S Curver Buffer to taper the signal onset
515
- *
516
- * @param {*} length
517
- * @param {*} phase
518
- * @returns
519
- * @example
520
- */
521
- static createSCurveBuffer = (length, phase) => {
522
- const curve = new Float32Array(length);
523
- let i;
524
- for (i = 0; i < length; i += 1) {
525
- // scale the curve to be between 0-1
526
- curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;
527
- }
528
- return curve;
529
- };
530
-
531
- static createInverseSCurveBuffer = (length, phase) => {
532
- const curve = new Float32Array(length);
533
- let i;
534
- let j = length - 1;
535
- for (i = 0; i < length; i += 1) {
536
- // scale the curve to be between 0-1
537
- curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;
538
- j -= 1;
539
- }
540
- return curve;
541
- };
542
-
543
- /**
544
- * Construct a Calibration Node with the calibration parameters.
545
- *
546
- * @param dataBuffer
547
- * @private
548
- * @example
549
- */
550
- #createCalibrationNodeFromBuffer = dataBuffer => {
551
- console.log('length databuffer');
552
- console.log(dataBuffer.length);
553
- if (!this.sourceAudioContext) {
554
- this.makeNewSourceAudioContext();
555
- }
556
-
557
- const buffer = this.sourceAudioContext.createBuffer(
558
- 1, // number of channels
559
- dataBuffer.length,
560
- this.sourceAudioContext.sampleRate // sample rate
561
- );
562
-
563
- const data = buffer.getChannelData(0); // get data
564
- if (this.mode === 'filtered') {
565
- console.log("check max and min of convolution");
566
- } else {
567
- console.log("check max and min of mls");
568
- }
569
-
570
- console.log("Max:", Math.min(...dataBuffer));
571
- console.log("Min:",Math.max(...dataBuffer));
572
- // fill the buffer with our data
573
- try {
574
- for (let i = 0; i < dataBuffer.length; i += 1) {
575
- data[i] = dataBuffer[i];
576
- }
577
- } catch (error) {
578
- console.error(error);
579
- }
580
-
581
- this.sourceNode = this.sourceAudioContext.createBufferSource();
582
-
583
- this.sourceNode.buffer = buffer;
584
-
585
- if (this.mode === 'filtered') {
586
-
587
- //used to not loop filtered
588
- this.sourceNode.loop = true;
589
- } else {
590
- this.sourceNode.loop = true;
591
- }
592
-
593
- this.sourceNode.connect(this.sourceAudioContext.destination);
594
-
595
- this.addCalibrationNode(this.sourceNode);
596
- };
597
-
598
- /**
599
- * Given a data buffer, creates the required calibration node
600
- *
601
- * @param {*} dataBufferArray
602
- * @example
603
- */
604
- #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {
605
- if (dataBufferArray.length === 1) {
606
- this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);
607
- } else {
608
- throw new Error('The length of the data buffer array must be 1');
609
- }
610
- };
611
-
612
- /**
613
- * Creates an audio context and plays it for a few seconds.
614
- *
615
- * @private
616
- * @returns - Resolves when the audio is done playing.
617
- * @example
618
- */
619
- #playCalibrationAudio = () => {
620
- this.calibrationNodes[0].start(0);
621
- this.status = ``;
622
- if (this.mode === 'unfiltered') {
623
- console.log('mls', this.#mls); // before multiplied by calibrateSoundBurstDb
624
- console.log('mls buffer view', this.#mlsBufferView); // after multiplied by calibrateSoundBurstDb
625
- console.log('play calibration audio ' + this.stepNum);
626
- this.status =
627
- `All Hz Calibration: playing the calibration tone...`.toString() +
628
- this.generateTemplate().toString();
629
- } else if (this.mode === 'filtered') {
630
- console.log('play convolved audio ' + this.stepNum);
631
- this.status =
632
- `All Hz Calibration: playing the convolved calibration tone...`.toString() +
633
- this.generateTemplate().toString();
634
- } else {
635
- throw new Error('Mode is incorrect');
636
- }
637
- this.emit('update', {message: this.status});
638
- this.stepNum += 1;
639
- console.log('sink sampling rate');
640
- console.log(this.sinkSamplingRate);
641
- console.log('source sampling rate');
642
- console.log(this.sourceSamplingRate);
643
- };
644
-
645
- /** .
646
- * .
647
- * .
648
- * Stops the audio with tapered offset
649
- *
650
- * @example
651
- */
652
- #stopCalibrationAudio = () => {
653
- this.calibrationNodes[0].stop(0);
654
- this.calibrationNodes = [];
655
- this.sourceNode.disconnect();
656
- this.stepNum += 1;
657
- console.log('stop calibration audio ' + this.stepNum);
658
- this.status =
659
- `All Hz Calibration: stopping the calibration tone...`.toString() +
660
- this.generateTemplate().toString();
661
- this.emit('update', {message: this.status});
662
- };
663
-
664
- playMLSwithIIR = async (stream, convolution) => {
665
- let checkRec = false;
666
- this.mode = 'filtered';
667
- console.log('play mls with iir');
668
- //this.invertedImpulseResponse = iir;
669
-
670
- await this.calibrationSteps(
671
- stream,
672
- this.#playCalibrationAudio, // play audio func (required)
673
- this.#createCalibrationNodeFromBuffer(convolution), // before play func
674
- this.#awaitSignalOnset, // before record
675
- () => this.numSuccessfulCaptured < 1,
676
- this.#awaitDesiredMLSLength, // during record
677
- this.#afterMLSwIIRRecord, // after record
678
- this.mode,
679
- checkRec
680
- );
681
- };
682
-
683
- bothSoundCheck = async stream => {
684
- let iir_ir_and_plots;
685
- this.#currentConvolution = this.componentConvolution;
686
- await this.playMLSwithIIR(stream, this.#currentConvolution);
687
- this.#stopCalibrationAudio();
688
- let component_conv_recs = this.getAllFilteredRecordedSignals();
689
- let return_component_conv_rec = component_conv_recs[0];
690
- this.clearAllFilteredRecordedSignals();
691
- this.#currentConvolution = this.systemConvolution;
692
- await this.playMLSwithIIR(stream, this.#currentConvolution);
693
- this.#stopCalibrationAudio();
694
- let system_conv_recs = this.getAllFilteredRecordedSignals();
695
- let return_system_conv_rec = system_conv_recs[0];
696
- this.sourceAudioContext.close();
697
- let recs = this.getAllUnfilteredRecordedSignals();
698
- let unconv_rec = recs[0];
699
- let return_unconv_rec = unconv_rec;
700
- let conv_rec = component_conv_recs[0];
701
-
702
- //psd of component
703
- let knownGain = this.oldComponentIR.Gain;
704
- let knownFreq = this.oldComponentIR.Freq;
705
- let sampleRate = this.sourceSamplingRate || 96000;
706
- let component_unconv_rec_psd = await this.pyServerAPI
707
- .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)
708
- .then(res => {
709
- this.incrementStatusBar();
710
- this.status =
711
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
712
- this.generateTemplate().toString();
713
- this.emit('update', {message: this.status});
714
- return res;
715
- })
716
- .catch(err => {
717
- console.error(err);
718
- });
719
-
720
- let component_conv_rec_psd = await this.pyServerAPI
721
- .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)
722
- .then(res => {
723
- this.incrementStatusBar();
724
- this.status =
725
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
726
- this.generateTemplate().toString();
727
- this.emit('update', {message: this.status});
728
- return res;
729
- })
730
- .catch(err => {
731
- console.error(err);
732
- });
733
-
734
- conv_rec = system_conv_recs[0];
735
- //psd of system
736
- let system_recs_psd = await this.pyServerAPI
737
- .getPSDWithRetry({
738
- unconv_rec,
739
- conv_rec,
740
- sampleRate: this.sourceSamplingRate || 96000,
741
- })
742
- .then(res => {
743
- this.incrementStatusBar();
744
- this.status =
745
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
746
- this.generateTemplate().toString();
747
- this.emit('update', {message: this.status});
748
- return res;
749
- })
750
- .catch(err => {
751
- console.error(err);
752
- });
753
-
754
- //iir w/ and without bandpass psd. done
755
- unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
756
- conv_rec = this.componentInvertedImpulseResponse;
757
- let component_iir_psd = await this.pyServerAPI
758
- .getPSDWithRetry({
759
- unconv_rec,
760
- conv_rec,
761
- sampleRate: this.sourceSamplingRate || 96000,
762
- })
763
- .then(res => {
764
- this.incrementStatusBar();
765
- this.status =
766
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
767
- this.generateTemplate().toString();
768
- this.emit('update', {message: this.status});
769
- return res;
770
- })
771
- .catch(err => {
772
- console.error(err);
773
- });
774
- unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
775
- conv_rec = this.systemInvertedImpulseResponse;
776
- let system_iir_psd = await this.pyServerAPI
777
- .getPSDWithRetry({
778
- unconv_rec,
779
- conv_rec,
780
- sampleRate: this.sourceSamplingRate || 96000,
781
- })
782
- .then(res => {
783
- this.incrementStatusBar();
784
- this.status =
785
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
786
- this.generateTemplate().toString();
787
- this.emit('update', {message: this.status});
788
- return res;
789
- })
790
- .catch(err => {
791
- console.error(err);
792
- });
793
-
794
- let mls_psd = await this.pyServerAPI
795
- .getMLSPSDWithRetry({mls: this.#mlsBufferView, sampleRate: this.sourceSamplingRate || 96000})
796
- .then(res => {
797
- this.incrementStatusBar();
798
- this.status =
799
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
800
- this.generateTemplate().toString();
801
- this.emit('update', {message: this.status});
802
- return res;
803
- })
804
- .catch(err => {
805
- console.error(err);
806
- });
807
-
808
- let system_filtered_mls_psd = await this.pyServerAPI
809
- .getMLSPSDWithRetry({
810
- mls: this.systemConvolution,
811
- sampleRate: this.sourceSamplingRate || 96000,
812
- })
813
- .then(res => {
814
- this.incrementStatusBar();
815
- this.status =
816
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
817
- this.generateTemplate().toString();
818
- this.emit('update', {message: this.status});
819
- return res;
820
- })
821
- .catch(err => {
822
- console.error(err);
823
- });
824
-
825
- let component_filtered_mls_psd = await this.pyServerAPI
826
- .getMLSPSDWithRetry({
827
- mls: this.componentConvolution,
828
- sampleRate: this.sourceSamplingRate || 96000,
829
- })
830
- .then(res => {
831
- this.incrementStatusBar();
832
- this.status =
833
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
834
- this.generateTemplate().toString();
835
- this.emit('update', {message: this.status});
836
- return res;
837
- })
838
- .catch(err => {
839
- console.error(err);
840
- });
841
-
842
- iir_ir_and_plots = {
843
- filtered_recording: {
844
- component: return_component_conv_rec,
845
- system: return_system_conv_rec,
846
- },
847
- unfiltered_recording: this.getAllUnfilteredRecordedSignals()[0],
848
- system: {
849
- iir: this.systemInvertedImpulseResponse,
850
- ir: this.systemIR,
851
- iir_psd: {
852
- y: system_iir_psd['y_conv'],
853
- x: system_iir_psd['x_conv'],
854
- y_no_bandpass: system_iir_psd['y_unconv'],
855
- x_no_bandpass: system_iir_psd['x_unconv'],
856
- },
857
- filtered_mls_psd: {
858
- x: system_filtered_mls_psd['x_mls'],
859
- y: system_filtered_mls_psd['y_mls'],
860
- },
861
- convolution: this.systemConvolution,
862
- psd: {
863
- unconv: {
864
- x: system_recs_psd['x_unconv'],
865
- y: system_recs_psd['y_unconv'],
866
- },
867
- conv: {
868
- x: system_recs_psd['x_conv'],
869
- y: system_recs_psd['y_conv'],
870
- },
871
- },
872
- },
873
- component: {
874
- iir: this.componentInvertedImpulseResponse,
875
- ir: this.componentIR,
876
- iir_psd: {
877
- y: component_iir_psd['y_conv'],
878
- x: component_iir_psd['x_conv'],
879
- y_no_bandpass: component_iir_psd['y_unconv'],
880
- x_no_bandpass: component_iir_psd['x_unconv'],
881
- },
882
- filtered_mls_psd: {
883
- x: component_filtered_mls_psd['x_mls'],
884
- y: component_filtered_mls_psd['y_mls'],
885
- },
886
- convolution: this.componentConvolution,
887
- psd: {
888
- unconv: {
889
- x: component_unconv_rec_psd['x'],
890
- y: component_unconv_rec_psd['y'],
891
- },
892
- conv: {
893
- x: component_conv_rec_psd['x'],
894
- y: component_conv_rec_psd['y'],
895
- },
896
- },
897
- },
898
- mls: this.#mlsBufferView,
899
- mls_psd: {
900
- x: mls_psd['x_mls'],
901
- y: mls_psd['y_mls'],
902
- },
903
- autocorrelations: this.autocorrelations,
904
- impulseResponses: [],
905
- };
906
-
907
- return iir_ir_and_plots;
908
- };
909
-
910
- singleSoundCheck = async stream => {
911
- let iir_ir_and_plots;
912
- if (this._calibrateSoundCheck != 'system') {
913
- this.#currentConvolution = this.componentConvolution;
914
- } else {
915
- this.#currentConvolution = this.systemConvolution;
916
- }
917
- await this.playMLSwithIIR(stream, this.#currentConvolution);
918
- this.#stopCalibrationAudio();
919
- this.sourceAudioContext.close();
920
- let conv_recs = this.getAllFilteredRecordedSignals();
921
- let recs = this.getAllUnfilteredRecordedSignals();
922
- console.log('Obtaining unfiltered recording from #allHzUnfilteredRecordings to calculate PSD');
923
- console.log('Obtaining filtered recording from #allHzFilteredRecordings to calculate PSD');
924
- let unconv_rec = recs[0];
925
- let return_unconv_rec = unconv_rec;
926
- let conv_rec = conv_recs[0];
927
- let return_conv_rec = conv_rec;
928
- if (this._calibrateSoundCheck != 'system') {
929
- let knownGain = this.oldComponentIR.Gain;
930
- let knownFreq = this.oldComponentIR.Freq;
931
- let sampleRate = this.sourceSamplingRate || 96000;
932
- let unconv_results = await this.pyServerAPI
933
- .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)
934
- .then(res => {
935
- this.incrementStatusBar();
936
- this.status =
937
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
938
- this.generateTemplate().toString();
939
- this.emit('update', {message: this.status});
940
- return res;
941
- })
942
- .catch(err => {
943
- console.error(err);
944
- });
945
-
946
- let conv_results = await this.pyServerAPI
947
- .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)
948
- .then(res => {
949
- this.incrementStatusBar();
950
- this.status =
951
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
952
- this.generateTemplate().toString();
953
- this.emit('update', {message: this.status});
954
- return res;
955
- })
956
- .catch(err => {
957
- console.error(err);
958
- });
959
- unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
960
- conv_rec = this.componentInvertedImpulseResponse;
961
- let component_iir_psd = await this.pyServerAPI
962
- .getPSDWithRetry({
963
- unconv_rec,
964
- conv_rec,
965
- sampleRate: this.sourceSamplingRate || 96000,
966
- })
967
- .then(res => {
968
- this.incrementStatusBar();
969
- this.status =
970
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
971
- this.generateTemplate().toString();
972
- this.emit('update', {message: this.status});
973
- return res;
974
- })
975
- .catch(err => {
976
- console.error(err);
977
- });
978
- unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
979
- conv_rec = this.systemInvertedImpulseResponse;
980
- let system_iir_psd = await this.pyServerAPI
981
- .getPSDWithRetry({
982
- unconv_rec,
983
- conv_rec,
984
- sampleRate: this.sourceSamplingRate || 96000,
985
- })
986
- .then(res => {
987
- this.incrementStatusBar();
988
- this.status =
989
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
990
- this.generateTemplate().toString();
991
- this.emit('update', {message: this.status});
992
- return res;
993
- })
994
- .catch(err => {
995
- console.error(err);
996
- });
997
-
998
- let mls_psd = await this.pyServerAPI
999
- .getMLSPSDWithRetry({
1000
- mls: this.#mlsBufferView,
1001
- sampleRate: this.sourceSamplingRate || 96000,
1002
- })
1003
- .then(res => {
1004
- this.incrementStatusBar();
1005
- this.status =
1006
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1007
- this.generateTemplate().toString();
1008
- this.emit('update', {message: this.status});
1009
- return res;
1010
- })
1011
- .catch(err => {
1012
- console.error(err);
1013
- });
1014
-
1015
- let filtered_mls_psd = await this.pyServerAPI
1016
- .getMLSPSDWithRetry({
1017
- mls: this.componentConvolution,
1018
- sampleRate: this.sourceSamplingRate || 96000,
1019
- })
1020
- .then(res => {
1021
- this.incrementStatusBar();
1022
- this.status =
1023
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1024
- this.generateTemplate().toString();
1025
- this.emit('update', {message: this.status});
1026
- return res;
1027
- })
1028
- .catch(err => {
1029
- console.error(err);
1030
- });
1031
-
1032
- iir_ir_and_plots = {
1033
- unfiltered_recording: return_unconv_rec,
1034
- filtered_recording: return_conv_rec,
1035
- system: {
1036
- iir: this.systemInvertedImpulseResponse,
1037
- ir: this.systemIR,
1038
- iir_psd: {
1039
- y: system_iir_psd['y_conv'],
1040
- x: system_iir_psd['y_conv'],
1041
- y_no_bandpass: system_iir_psd['y_unconv'],
1042
- x_no_bandpass: system_iir_psd['x_unconv'],
1043
- },
1044
- filtered_recording: [],
1045
- filtered_mls_psd: {},
1046
- convolution: this.systemConvolution,
1047
- psd: {
1048
- unconv: {
1049
- x: [],
1050
- y: [],
1051
- },
1052
- conv: {
1053
- x: [],
1054
- y: [],
1055
- },
1056
- },
1057
- },
1058
- component: {
1059
- iir: this.componentInvertedImpulseResponse,
1060
- ir: this.componentIR,
1061
- iir_psd: {
1062
- y: component_iir_psd['y_conv'],
1063
- x: component_iir_psd['x_conv'],
1064
- y_no_bandpass: component_iir_psd['y_unconv'],
1065
- x_no_bandpass: component_iir_psd['x_unconv'],
1066
- },
1067
- filtered_mls_psd: {
1068
- x: filtered_mls_psd['x_mls'],
1069
- y: filtered_mls_psd['y_mls'],
1070
- },
1071
- convolution: this.componentConvolution,
1072
- psd: {
1073
- unconv: {
1074
- x: unconv_results['x'],
1075
- y: unconv_results['y'],
1076
- },
1077
- conv: {
1078
- x: conv_results['x'],
1079
- y: conv_results['y'],
1080
- },
1081
- },
1082
- },
1083
- mls: this.#mlsBufferView,
1084
- mls_psd: {
1085
- x: mls_psd['x_mls'],
1086
- y: mls_psd['y_mls'],
1087
- },
1088
- autocorrelations: this.autocorrelations,
1089
- impulseResponses: [],
1090
- };
1091
- } else {
1092
- let results = await this.pyServerAPI
1093
- .getPSDWithRetry({
1094
- unconv_rec,
1095
- conv_rec,
1096
- sampleRate: this.sourceSamplingRate || 96000,
1097
- })
1098
- .then(res => {
1099
- this.incrementStatusBar();
1100
- this.status =
1101
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1102
- this.generateTemplate().toString();
1103
- this.emit('update', {message: this.status});
1104
- return res;
1105
- })
1106
- .catch(err => {
1107
- console.error(err);
1108
- });
1109
-
1110
- //iir w/ and without bandpass psd
1111
- unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
1112
- conv_rec = this.componentInvertedImpulseResponse;
1113
- let component_iir_psd = await this.pyServerAPI
1114
- .getPSDWithRetry({
1115
- unconv_rec,
1116
- conv_rec,
1117
- sampleRate: this.sourceSamplingRate || 96000,
1118
- })
1119
- .then(res => {
1120
- this.incrementStatusBar();
1121
- this.status =
1122
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1123
- this.generateTemplate().toString();
1124
- this.emit('update', {message: this.status});
1125
- return res;
1126
- })
1127
- .catch(err => {
1128
- console.error(err);
1129
- });
1130
- unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
1131
- conv_rec = this.systemInvertedImpulseResponse;
1132
- let system_iir_psd = await this.pyServerAPI
1133
- .getPSDWithRetry({
1134
- unconv_rec,
1135
- conv_rec,
1136
- sampleRate: this.sourceSamplingRate || 96000,
1137
- })
1138
- .then(res => {
1139
- this.incrementStatusBar();
1140
- this.status =
1141
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1142
- this.generateTemplate().toString();
1143
- this.emit('update', {message: this.status});
1144
- return res;
1145
- })
1146
- .catch(err => {
1147
- console.error(err);
1148
- });
1149
-
1150
- let mls_psd = await this.pyServerAPI
1151
- .getMLSPSDWithRetry({
1152
- mls: this.#mlsBufferView,
1153
- sampleRate: this.sourceSamplingRate || 96000,
1154
- })
1155
- .then(res => {
1156
- this.incrementStatusBar();
1157
- this.status =
1158
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1159
- this.generateTemplate().toString();
1160
- this.emit('update', {message: this.status});
1161
- return res;
1162
- })
1163
- .catch(err => {
1164
- console.error(err);
1165
- });
1166
-
1167
- let filtered_mls_psd = await this.pyServerAPI
1168
- .getMLSPSDWithRetry({
1169
- mls: this.systemConvolution,
1170
- sampleRate: this.sourceSamplingRate || 96000,
1171
- })
1172
- .then(res => {
1173
- this.incrementStatusBar();
1174
- this.status =
1175
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1176
- this.generateTemplate().toString();
1177
- this.emit('update', {message: this.status});
1178
- return res;
1179
- })
1180
- .catch(err => {
1181
- console.error(err);
1182
- });
1183
-
1184
- iir_ir_and_plots = {
1185
- unfiltered_recording: return_unconv_rec,
1186
- filtered_recording: return_conv_rec,
1187
- system: {
1188
- iir: this.systemInvertedImpulseResponse,
1189
- ir: this.systemIR,
1190
- iir_psd: {
1191
- y: system_iir_psd['y_conv'],
1192
- x: system_iir_psd['y_conv'],
1193
- y_no_bandpass: system_iir_psd['y_unconv'],
1194
- x_no_bandpass: system_iir_psd['x_unconv'],
1195
- },
1196
- filtered_recording: [],
1197
- filtered_mls_psd: {
1198
- x: filtered_mls_psd['x_mls'],
1199
- y: filtered_mls_psd['y_mls'],
1200
- },
1201
- convolution: this.systemConvolution,
1202
- psd: {
1203
- unconv: {
1204
- x: results['x_unconv'],
1205
- y: results['y_unconv'],
1206
- },
1207
- conv: {
1208
- x: results['x_conv'],
1209
- y: results['y_conv'],
1210
- },
1211
- },
1212
- },
1213
- component: {
1214
- iir: this.componentInvertedImpulseResponse,
1215
- ir: this.componentIR,
1216
- iir_psd: {
1217
- y: component_iir_psd['y_conv'],
1218
- x: component_iir_psd['x_conv'],
1219
- y_no_bandpass: component_iir_psd['y_unconv'],
1220
- x_no_bandpass: component_iir_psd['x_unconv'],
1221
- },
1222
- filtered_mls_psd: {},
1223
- convolution: this.componentConvolution,
1224
- psd: {
1225
- unconv: {
1226
- x: [],
1227
- y: [],
1228
- },
1229
- conv: {
1230
- x: [],
1231
- y: [],
1232
- },
1233
- },
1234
- },
1235
- mls: this.#mlsBufferView,
1236
- mls_psd: {
1237
- x: mls_psd['x_mls'],
1238
- y: mls_psd['y_mls'],
1239
- },
1240
- autocorrelations: this.autocorrelations,
1241
- impulseResponses: [],
1242
- };
1243
- }
1244
- await Promise.all(this.impulseResponses).then(res => {
1245
- for (let i = 0; i < res.length; i++) {
1246
- if (res[i] != undefined) {
1247
- iir_ir_and_plots['impulseResponses'].push(res[i]);
1248
- }
1249
- }
1250
- });
1251
-
1252
- if (this.#download) {
1253
- this.downloadSingleUnfilteredRecording();
1254
- this.downloadSingleFilteredRecording();
1255
- saveToCSV(this.#mls, 'MLS.csv');
1256
- saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
1257
- saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
1258
- saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
1259
- saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
1260
- for (let i = 0; i < this.autocorrelations.length; i++) {
1261
- saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
1262
- }
1263
- const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
1264
- for (let i = 0; i < res.length; i++) {
1265
- if (res[i] != undefined) {
1266
- saveToCSV(res[i], `IR_${i}`);
1267
- }
1268
- }
1269
- });
1270
- }
1271
-
1272
- return iir_ir_and_plots;
1273
- };
1274
-
1275
- /**
1276
- * Public method to start the calibration process. Objects intialized from webassembly allocate new memory
1277
- * and must be manually freed. This function is responsible for intializing the MlsGenInterface,
1278
- * and wrapping the calibration steps with a garbage collection safe gaurd.
1279
- *
1280
- * @public
1281
- * @param stream - The stream of audio from the Listener.
1282
- * @example
1283
- */
1284
- startCalibrationImpulseResponse = async stream => {
1285
- let desired_time = this.desired_time_per_mls;
1286
- let checkRec = 'allhz';
1287
-
1288
- console.log('MLS sequence should be of length: ' + this.sourceSamplingRate * desired_time);
1289
-
1290
- length = this.sourceSamplingRate * desired_time;
1291
- //get mls here
1292
- const calibrateSoundBurstDb = this._calibrateSoundBurstDb;
1293
- await this.pyServerAPI
1294
- .getMLSWithRetry({length, calibrateSoundBurstDb})
1295
- .then(res => {
1296
- console.log(res);
1297
- this.#mlsBufferView = res['mls'];
1298
- this.#mls = res['unscaledMLS']
1299
- })
1300
- .catch(err => {
1301
- // this.emit('InvertedImpulseResponse', {res: false});
1302
- console.error(err);
1303
- });
1304
- this.numSuccessfulBackgroundCaptured = 0;
1305
- if (this._calibrateSoundBackgroundSecs > 0) {
1306
- this.mode = 'background';
1307
- this.status =
1308
- `All Hz Calibration: sampling the background noise...`.toString() +
1309
- this.generateTemplate().toString();
1310
- this.emit('update', {message: this.status});
1311
- await this.recordBackground(
1312
- stream, //stream
1313
- () => this.numSuccessfulBackgroundCaptured < 1, //loop condition
1314
- this.#awaitBackgroundNoiseRecording, //sleep to record
1315
- this.sendBackgroundRecording, //send to get PSD
1316
- this.mode,
1317
- checkRec
1318
- );
1319
- this.incrementStatusBar();
1320
- }
1321
- this.mode = 'unfiltered';
1322
- this.numSuccessfulCaptured = 0;
1323
-
1324
- await this.calibrationSteps(
1325
- stream,
1326
- this.#playCalibrationAudio, // play audio func (required)
1327
- this.#createCalibrationNodeFromBuffer(this.#mlsBufferView), // before play func
1328
- this.#awaitSignalOnset, // before record
1329
- () => this.numSuccessfulCaptured < this.numCaptures, // loop while true
1330
- this.#awaitDesiredMLSLength, // during record
1331
- this.#afterMLSRecord, // after record
1332
- this.mode,
1333
- checkRec
1334
- ),
1335
- this.#stopCalibrationAudio();
1336
- checkRec = false;
1337
-
1338
- // at this stage we've captured all the required signals,
1339
- // and have received IRs for each one
1340
- // so let's send all the IRs to the server to be converted to a single IIR
1341
- await this.sendSystemImpulseResponsesToServerForProcessing();
1342
- await this.sendComponentImpulseResponsesToServerForProcessing();
1343
-
1344
- this.numSuccessfulCaptured = 0;
1345
-
1346
- let iir_ir_and_plots;
1347
- if (this._calibrateSoundCheck != 'none') {
1348
- //do single check
1349
- if (this._calibrateSoundCheck == 'goal' || this._calibrateSoundCheck == 'system') {
1350
- iir_ir_and_plots = await this.singleSoundCheck(stream);
1351
- } else {
1352
- //both
1353
- iir_ir_and_plots = await this.bothSoundCheck(stream);
1354
- }
1355
- } else {
1356
- let unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
1357
- let conv_rec = this.componentInvertedImpulseResponse;
1358
- let component_iir_psd = await this.pyServerAPI
1359
- .getPSDWithRetry({
1360
- unconv_rec,
1361
- conv_rec,
1362
- sampleRate: this.sourceSamplingRate || 96000,
1363
- })
1364
- .then(res => {
1365
- this.incrementStatusBar();
1366
- this.status =
1367
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1368
- this.generateTemplate().toString();
1369
- this.emit('update', {message: this.status});
1370
- return res;
1371
- })
1372
- .catch(err => {
1373
- console.error(err);
1374
- });
1375
- unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
1376
- conv_rec = this.systemInvertedImpulseResponse;
1377
- let system_iir_psd = await this.pyServerAPI
1378
- .getPSDWithRetry({
1379
- unconv_rec,
1380
- conv_rec,
1381
- sampleRate: this.sourceSamplingRate || 96000,
1382
- })
1383
- .then(res => {
1384
- this.incrementStatusBar();
1385
- this.status =
1386
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1387
- this.generateTemplate().toString();
1388
- this.emit('update', {message: this.status});
1389
- return res;
1390
- })
1391
- .catch(err => {
1392
- console.error(err);
1393
- });
1394
-
1395
- iir_ir_and_plots = {
1396
- unfiltered_recording: return_unconv_rec,
1397
- filtered_recording: return_conv_rec,
1398
- system: {
1399
- iir: this.systemInvertedImpulseResponse,
1400
- ir: this.systemIR,
1401
- iir_psd: {
1402
- y: system_iir_psd['y_conv'],
1403
- x: system_iir_psd['y_conv'],
1404
- y_no_bandpass: system_iir_psd['y_unconv'],
1405
- x_no_bandpass: system_iir_psd['x_unconv'],
1406
- },
1407
- filtered_recording: [],
1408
- convolution: this.systemConvolution,
1409
- psd: {
1410
- unconv: {
1411
- x: [],
1412
- y: [],
1413
- },
1414
- conv: {
1415
- x: [],
1416
- y: [],
1417
- },
1418
- },
1419
- },
1420
- component: {
1421
- iir: this.componentInvertedImpulseResponse,
1422
- ir: this.componentIR,
1423
- iir_psd: {
1424
- y: component_iir_psd['y_conv'],
1425
- x: component_iir_psd['x_conv'],
1426
- y_no_bandpass: component_iir_psd['y_unconv'],
1427
- x_no_bandpass: component_iir_psd['x_unconv'],
1428
- },
1429
- convolution: this.componentConvolution,
1430
- psd: {
1431
- unconv: {
1432
- x: [],
1433
- y: [],
1434
- },
1435
- conv: {
1436
- x: [],
1437
- y: [],
1438
- },
1439
- },
1440
- },
1441
- mls: this.#mlsBufferView,
1442
- autocorrelations: this.autocorrelations,
1443
- impulseResponses: [],
1444
- };
1445
- await Promise.all(this.impulseResponses).then(res => {
1446
- for (let i = 0; i < res.length; i++) {
1447
- if (res[i] != undefined) {
1448
- iir_ir_and_plots['impulseResponses'].push(res[i]);
1449
- }
1450
- }
1451
- });
1452
-
1453
- if (this.#download) {
1454
- saveToCSV(this.#mls, 'MLS.csv');
1455
- saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
1456
- saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
1457
- saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
1458
- saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
1459
- for (let i = 0; i < this.autocorrelations.length; i++) {
1460
- saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
1461
- }
1462
- const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
1463
- for (let i = 0; i < res.length; i++) {
1464
- if (res[i] != undefined) {
1465
- saveToCSV(res[i], `IR_${i}`);
1466
- }
1467
- }
1468
- });
1469
- }
1470
- }
1471
-
1472
- this.percent_complete = 100;
1473
-
1474
- this.status = `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();
1475
- this.emit('update', {message: this.status});
1476
-
1477
- //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR
1478
- //that was used to calibrate
1479
-
1480
- return iir_ir_and_plots;
1481
- };
1482
-
1483
- //////////////////////volume
1484
-
1485
- handleIncomingData = data => {
1486
- console.log('Received data: ', data);
1487
- if (data.type === 'soundGainDBSPL') {
1488
- this.soundGainDBSPL = data.value;
1489
- } else {
1490
- throw new Error(`Unknown data type: ${data.type}`);
1491
- }
1492
- };
1493
- createSCurveBuffer = (onSetBool = true) => {
1494
- const curve = new Float32Array(this.TAPER_SECS * this.sourceSamplingRate + 1);
1495
- const frequency = 1 / (4 * this.TAPER_SECS);
1496
- let j = 0;
1497
- for (let i = 0; i < this.TAPER_SECS * this.sourceSamplingRate + 1; i += 1) {
1498
- const phase = 2 * Math.PI * frequency * j;
1499
- const onsetTaper = Math.pow(Math.sin(phase), 2);
1500
- const offsetTaper = Math.pow(Math.cos(phase), 2);
1501
- curve[i] = onSetBool ? onsetTaper : offsetTaper;
1502
- j += 1 / this.sourceSamplingRate;
1503
- }
1504
- return curve;
1505
- };
1506
-
1507
- #getTruncatedSignal = (left = 3.5, right = 4.5) => {
1508
- const start = Math.floor(left * this.sourceSamplingRate);
1509
- const end = Math.floor(right * this.sourceSamplingRate);
1510
- const result = Array.from(this.getLastVolumeRecordedSignal().slice(start, end));
1511
- console.log(
1512
- 'Obtaining last 1000 hz recording from #allVolumeRecordings to send for processing'
1513
- );
1514
- /**
1515
- * function to check that capture was properly made
1516
- * @param {*} list
1517
- */
1518
- const checkResult = list => {
1519
- const setItem = new Set(list);
1520
- if (setItem.size === 1 && setItem.has(0)) {
1521
- console.warn(
1522
- 'The last capture failed, all recorded signal is zero',
1523
- this.getAllVolumeRecordedSignals()
1524
- );
1525
- }
1526
- if (setItem.size === 0) {
1527
- console.warn('The last capture failed, no recorded signal');
1528
- }
1529
- };
1530
- checkResult(result);
1531
- return result;
1532
- };
1533
-
1534
- /**
1535
- *
1536
- *
1537
- Construct a calibration Node with the calibration parameters and given gain value
1538
- * @param {*} gainValue
1539
- * */
1540
- #createCalibrationToneWithGainValue = gainValue => {
1541
- const audioContext = this.makeNewSourceAudioContext();
1542
- const oscilator = audioContext.createOscillator();
1543
- const gainNode = audioContext.createGain();
1544
- const taperGainNode = audioContext.createGain();
1545
- const offsetGainNode = audioContext.createGain();
1546
- const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
1547
-
1548
- oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
1549
- oscilator.type = this.#CALIBRATION_TONE_TYPE;
1550
- gainNode.gain.value = gainValue;
1551
-
1552
- oscilator.connect(gainNode);
1553
- gainNode.connect(taperGainNode);
1554
- const onsetCurve = this.createSCurveBuffer();
1555
- taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
1556
- taperGainNode.connect(offsetGainNode);
1557
- const offsetCurve = this.createSCurveBuffer(false);
1558
- offsetGainNode.gain.setValueCurveAtTime(
1559
- offsetCurve,
1560
- totalDuration - this.TAPER_SECS,
1561
- this.TAPER_SECS
1562
- );
1563
- offsetGainNode.connect(audioContext.destination);
1564
-
1565
- this.addCalibrationNode(oscilator);
1566
- };
1567
-
1568
- /**
1569
- * Construct a Calibration Node with the calibration parameters.
1570
- *
1571
- * @private
1572
- * @example
1573
- */
1574
- #createCalibrationNode = () => {
1575
- const audioContext = this.makeNewSourceAudioContext();
1576
- const oscilator = audioContext.createOscillator();
1577
- const gainNode = audioContext.createGain();
1578
-
1579
- oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
1580
- oscilator.type = this.#CALIBRATION_TONE_TYPE;
1581
- gainNode.gain.value = 0.04;
1582
-
1583
- oscilator.connect(gainNode);
1584
- gainNode.connect(audioContext.destination);
1585
-
1586
- this.addCalibrationNode(oscilator);
1587
- };
1588
-
1589
- #playCalibrationAudioVolume = async () => {
1590
- const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
1591
-
1592
- this.calibrationNodes[0].start(0);
1593
- this.calibrationNodes[0].stop(totalDuration);
1594
- console.log(`Playing a buffer of ${this.CALIBRATION_TONE_DURATION} seconds of audio`);
1595
- console.log(`Waiting a total of ${totalDuration} seconds`);
1596
- await sleep(totalDuration);
1597
- };
1598
-
1599
- #sendToServerForProcessing = lCalib => {
1600
- console.log('Sending data to server');
1601
- let left = this.calibrateSound1000HzPreSec;
1602
- let right = this.calibrateSound1000HzPreSec + this.calibrateSound1000HzSec;
1603
- this.pyServerAPI
1604
- .getVolumeCalibration({
1605
- sampleRate: this.sourceSamplingRate,
1606
- payload: this.#getTruncatedSignal(left, right),
1607
- lCalib: lCalib,
1608
- })
1609
- .then(res => {
1610
- if (this.outDBSPL === null) {
1611
- this.incrementStatusBar();
1612
- this.outDBSPL = res['outDbSPL'];
1613
- this.outDBSPL1000 = res['outDbSPL1000'];
1614
- this.THD = res['thd'];
1615
- }
1616
- })
1617
- .catch(err => {
1618
- console.warn(err);
1619
- });
1620
- };
1621
-
1622
- startCalibrationVolume = async (stream, gainValues, lCalib, componentGainDBSPL) => {
1623
- const trialIterations = gainValues.length;
1624
- this.status_denominator += trialIterations;
1625
- const thdValues = [];
1626
- const inDBValues = [];
1627
- let inDB = 0;
1628
- const outDBSPLValues = [];
1629
- const outDBSPL1000Values = [];
1630
- let checkRec = false;
1631
-
1632
- // do one calibration that will be discarded
1633
- const soundLevelToDiscard = -60;
1634
- const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);
1635
- this.status =
1636
- `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() +
1637
- this.generateTemplate().toString();
1638
- //this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});
1639
- this.emit('update', {message: this.status});
1640
-
1641
- do {
1642
- // eslint-disable-next-line no-await-in-loop
1643
- await this.volumeCalibrationSteps(
1644
- stream,
1645
- this.#playCalibrationAudioVolume,
1646
- this.#createCalibrationToneWithGainValue,
1647
- this.#sendToServerForProcessing,
1648
- gainToDiscard,
1649
- lCalib, //todo make this a class parameter
1650
- checkRec
1651
- );
1652
- } while (this.outDBSPL === null);
1653
- //reset the values
1654
- //this.incrementStatusBar();
1655
-
1656
- this.outDBSPL = null;
1657
- this.outDBSPL = null;
1658
- this.outDBSPL1000 = null;
1659
- this.THD = null;
1660
-
1661
- // run the calibration at different gain values provided by the user
1662
- for (let i = 0; i < trialIterations; i++) {
1663
- //convert gain to DB and add to inDB
1664
- if (i == trialIterations - 1) {
1665
- checkRec = 'loudest';
1666
- }
1667
- inDB = Math.log10(gainValues[i]) * 20;
1668
- // precision to 1 decimal place
1669
- inDB = Math.round(inDB * 10) / 10;
1670
- inDBValues.push(inDB);
1671
- console.log('next update');
1672
- this.status =
1673
- `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() +
1674
- this.generateTemplate().toString();
1675
- this.emit('update', {message: this.status});
1676
- do {
1677
- // eslint-disable-next-line no-await-in-loop
1678
- await this.volumeCalibrationSteps(
1679
- stream,
1680
- this.#playCalibrationAudioVolume,
1681
- this.#createCalibrationToneWithGainValue,
1682
- this.#sendToServerForProcessing,
1683
- gainValues[i],
1684
- lCalib, //todo make this a class parameter
1685
- checkRec
1686
- );
1687
- } while (this.outDBSPL === null);
1688
- outDBSPL1000Values.push(this.outDBSPL1000);
1689
- thdValues.push(this.THD);
1690
- outDBSPLValues.push(this.outDBSPL);
1691
-
1692
- this.outDBSPL = null;
1693
- this.outDBSPL1000 = null;
1694
- this.THD = null;
1695
- }
1696
-
1697
- // get the volume calibration parameters from the server
1698
- const parameters = await this.pyServerAPI
1699
- .getVolumeCalibrationParameters({
1700
- inDBValues: inDBValues,
1701
- outDBSPLValues: outDBSPL1000Values,
1702
- lCalib: lCalib,
1703
- componentGainDBSPL,
1704
- })
1705
- .then(res => {
1706
- this.incrementStatusBar();
1707
- return res;
1708
- });
1709
- const result = {
1710
- parameters: parameters,
1711
- inDBValues: inDBValues,
1712
- outDBSPLValues: outDBSPLValues,
1713
- outDBSPL1000Values: outDBSPL1000Values,
1714
- thdValues: thdValues,
1715
- };
1716
-
1717
- return result;
1718
- };
1719
-
1720
- // function to write frq and gain to firebase database given speakerID
1721
- writeFrqGain = async (speakerID, frq, gain, OEM) => {
1722
- // freq and gain are too large to take samples 1 in every 100 samples
1723
-
1724
- const sampledFrq = [];
1725
- const sampledGain = [];
1726
- for (let i = 0; i < frq.length; i += 100) {
1727
- sampledFrq.push(frq[i]);
1728
- sampledGain.push(gain[i]);
1729
- }
1730
-
1731
- const data = {Freq: sampledFrq, Gain: sampledGain};
1732
-
1733
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/linear`), data);
1734
- };
1735
-
1736
- // Function to Read frq and gain from firebase database given speakerID
1737
- // returns an array of frq and gain if speakerID exists, returns null otherwise
1738
-
1739
- readFrqGain = async (speakerID, OEM) => {
1740
- const dbRef = ref(database);
1741
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/linear`));
1742
- if (snapshot.exists()) {
1743
- return snapshot.val();
1744
- }
1745
- return null;
1746
- };
1747
-
1748
- readGainat1000Hz = async (speakerID, OEM) => {
1749
- const dbRef = ref(database);
1750
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/Gain1000`));
1751
- if (snapshot.exists()) {
1752
- return snapshot.val();
1753
- }
1754
- return null;
1755
- };
1756
-
1757
- writeGainat1000Hz = async (speakerID, gain, OEM) => {
1758
- const data = {Gain: gain};
1759
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/Gain1000`), gain);
1760
- };
1761
-
1762
- writeIsSmartPhone = async (speakerID, isSmartPhone, OEM) => {
1763
- const data = {isSmartPhone: isSmartPhone};
1764
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/isSmartPhone`), isSmartPhone);
1765
- };
1766
-
1767
- doesMicrophoneExist = async (speakerID, OEM) => {
1768
- const dbRef = ref(database);
1769
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}`));
1770
- if (snapshot.exists()) {
1771
- return true;
1772
- }
1773
- return false;
1774
- };
1775
-
1776
- addMicrophoneInfo = async (speakerID, OEM, micInfo) => {
1777
- // add to database if /info does not exist
1778
- const dbRef = ref(database);
1779
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/info`));
1780
- if (!snapshot.exists()) {
1781
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/info`), micInfo);
1782
- }
1783
- };
1784
-
1785
- convertToDB = gain => {
1786
- return Math.log10(gain) * 20;
1787
- };
1788
-
1789
- // Function to perform linear interpolation between two points
1790
- interpolate(x, x0, y0, x1, y1) {
1791
- return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);
1792
- }
1793
-
1794
- findGainatFrequency = (frequencies, gains, targetFrequency) => {
1795
- // Find the index of the first frequency in the array greater than the target frequency
1796
- let index = 0;
1797
- while (index < frequencies.length && frequencies[index] < targetFrequency) {
1798
- index++;
1799
- }
1800
-
1801
- // Handle cases when the target frequency is outside the range of the given data
1802
- if (index === 0) {
1803
- return gains[0];
1804
- } else if (index === frequencies.length) {
1805
- return gains[gains.length - 1];
1806
- } else {
1807
- // Interpolate the gain based on the surrounding frequencies
1808
- const x0 = frequencies[index - 1];
1809
- const y0 = gains[index - 1];
1810
- const x1 = frequencies[index];
1811
- const y1 = gains[index];
1812
- return this.interpolate(targetFrequency, x0, y0, x1, y1);
1813
- }
1814
- };
1815
-
1816
- // Example of how to use the writeFrqGain and readFrqGain functions
1817
- // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);
1818
- // Speaker1 is the speakerID you want to write to in the database
1819
- // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));
1820
- // MiniDSPUMIK_1 is the speakerID with some Data in the database
1821
- //adding gainDBSPL
1822
- startCalibration = async (
1823
- stream,
1824
- gainValues,
1825
- lCalib = 104.92978421490648,
1826
- componentIR = null,
1827
- microphoneName = 'MiniDSP-UMIK1-711-4754-vertical',
1828
- _calibrateSoundCheck = 'goal', //GOAL PASSed in by default
1829
- isSmartPhone = false,
1830
- _calibrateSoundBurstDb = 0.33,
1831
- _calibrateSoundBurstRepeats = 4,
1832
- _calibrateSoundBurstSec = 1,
1833
- _calibrateSoundBurstsWarmup = 1,
1834
- _calibrateSoundHz = 48000,
1835
- _calibrateSoundIIRSec = 0.2,
1836
- calibrateSound1000HzPreSec = 3.5,
1837
- calibrateSound1000HzSec = 1.0,
1838
- calibrateSound1000HzPostSec = 0.5,
1839
- _calibrateSoundBackgroundSecs = 0,
1840
- micManufacturer = '',
1841
- micSerialNumber = '',
1842
- micModelNumber = '',
1843
- micModelName = ''
1844
- ) => {
1845
- this._calibrateSoundBurstDb = _calibrateSoundBurstDb;
1846
- this.CALIBRATION_TONE_DURATION =
1847
- calibrateSound1000HzPreSec + calibrateSound1000HzSec + calibrateSound1000HzPostSec;
1848
- this.calibrateSound1000HzPreSec = calibrateSound1000HzPreSec;
1849
- this.calibrateSound1000HzSec = calibrateSound1000HzSec;
1850
- this.calibrateSound1000HzPostSec = calibrateSound1000HzPostSec;
1851
- this.iirLength = Math.floor(_calibrateSoundIIRSec * this.sourceSamplingRate);
1852
- console.log('device info:', this.deviceInfo);
1853
- this.numMLSPerCapture = _calibrateSoundBurstRepeats;
1854
- this.desired_time_per_mls = _calibrateSoundBurstSec;
1855
- this.num_mls_to_skip = _calibrateSoundBurstsWarmup;
1856
- this.desired_sampling_rate = _calibrateSoundHz;
1857
- this._calibrateSoundBackgroundSecs = _calibrateSoundBackgroundSecs;
1858
-
1859
- //feed calibration goal here
1860
- this._calibrateSoundCheck = _calibrateSoundCheck;
1861
- //check if a componentIR was given to the system, if it isn't check for the microphone. using dummy data here bc we need to
1862
- //check the db based on the microphone currently connected
1863
-
1864
- //new lCalib found at top of calibration files *1000hz, make sure to correct
1865
- //based on zeroing of 1000hz, search for "*1000Hz"
1866
- const ID = isSmartPhone ? micModelNumber : micSerialNumber;
1867
- const OEM = isSmartPhone ? this.deviceInfo.OEM : micManufacturer;
1868
- // const ID = "711-4754";
1869
- // const OEM = "MiniDSP";
1870
- const micInfo = {
1871
- micModelName: isSmartPhone ? micModelName : microphoneName,
1872
- OEM: OEM,
1873
- ID: ID,
1874
- HardwareName: isSmartPhone ? this.deviceInfo.hardwarename : microphoneName,
1875
- hardwareFamily: isSmartPhone ? this.deviceInfo.hardwarefamily : microphoneName,
1876
- HardwareModel: isSmartPhone ? this.deviceInfo.hardwaremodel : microphoneName,
1877
- PlatformName: isSmartPhone ? this.deviceInfo.platformname : 'N/A',
1878
- PlatformVersion: isSmartPhone ? this.deviceInfo.platformversion : 'N/A',
1879
- DeviceType: isSmartPhone ? this.deviceInfo.devicetype : 'N/A',
1880
- };
1881
- this.addMicrophoneInfo(ID, OEM, micInfo);
1882
- if (componentIR == null) {
1883
- //mode 'ir'
1884
- //global variable this.componentIR must be set
1885
- this.componentIR = await this.readFrqGain(ID, OEM).then(data => {
1886
- return data;
1887
- });
1888
-
1889
- lCalib = await this.readGainat1000Hz(ID, OEM);
1890
- micInfo['gainDBSPL'] = lCalib;
1891
- // this.componentGainDBSPL = this.convertToDB(lCalib);
1892
- this.componentGainDBSPL = lCalib;
1893
- //TODO: if this call to database is unknown, cannot perform experiment => return false
1894
- if (this.componentIR == null) {
1895
- this.status =
1896
- `Microphone (${OEM},${ID}) is not found in the database. Please add it to the database.`.toString();
1897
- this.emit('update', {message: this.status});
1898
- return false;
1899
- }
1900
- } else {
1901
- this.componentIR = componentIR;
1902
- lCalib = this.findGainatFrequency(this.componentIR.Freq, this.componentIR.Gain, 1000);
1903
- // this.componentGainDBSPL = this.convertToDB(lCalib);
1904
- this.componentGainDBSPL = lCalib;
1905
- await this.writeIsSmartPhone(ID, isSmartPhone, OEM);
1906
- }
1907
-
1908
- this.oldComponentIR = this.componentIR;
1909
-
1910
- let volumeResults = await this.startCalibrationVolume(
1911
- stream,
1912
- gainValues,
1913
- lCalib,
1914
- this.componentGainDBSPL
1915
- );
1916
-
1917
- let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);
1918
- impulseResponseResults['background_noise'] = this.background_noise;
1919
- if (componentIR != null) {
1920
- //insert Freq and Gain from this.componentIR into db
1921
- await this.writeFrqGain(
1922
- ID,
1923
- impulseResponseResults.componentIR.Freq,
1924
- impulseResponseResults.componentIR.Gain,
1925
- OEM
1926
- );
1927
- micInfo['gainDBSPL'] = impulseResponseResults.parameters.gainDBSPL;
1928
- await this.writeGainat1000Hz(ID, micInfo['gainDBSPL'], OEM);
1929
- }
1930
-
1931
- const total_results = {...volumeResults, ...impulseResponseResults};
1932
-
1933
- total_results['micInfo'] = micInfo;
1934
- console.log('total results');
1935
- console.log(total_results);
1936
- return total_results;
1937
- };
1938
- }
1939
-
1940
- export default Combination;
1
+ import AudioCalibrator from '../audioCalibrator';
2
+
3
+ import {sleep, csvToArray, saveToCSV} from '../../utils';
4
+ import database from '../../config/firebase';
5
+ import {ref, set, get, child} from 'firebase/database';
6
+
7
+ /**
8
+ *
9
+ */
10
+ class Combination extends AudioCalibrator {
11
+ /**
12
+ * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.
13
+ *
14
+ * @param {Object<boolean, number, number, number>} calibratorParams - paramter object
15
+ * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures
16
+ * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated
17
+ * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform
18
+ * @param {number} [calibratorParams.numMLSPerCapture = 2] - number of bursts of MLS per capture
19
+ */
20
+ constructor({
21
+ download = false,
22
+ mlsOrder = 18,
23
+ numCaptures = 3,
24
+ numMLSPerCapture = 2,
25
+ lowHz = 20,
26
+ highHz = 10000,
27
+ }) {
28
+ super(numCaptures, numMLSPerCapture);
29
+ this.#mlsOrder = parseInt(mlsOrder, 10);
30
+ this.#P = 2 ** mlsOrder - 1;
31
+ this.#download = download;
32
+ this.#mls = [];
33
+ this.#lowHz = lowHz;
34
+ this.#highHz = highHz;
35
+ }
36
+
37
+ /** @private */
38
+ stepNum = 0;
39
+
40
+ /** @private */
41
+ totalSteps = 25;
42
+
43
+ /** @private */
44
+ #download;
45
+
46
+ /** @private */
47
+ #mlsGenInterface;
48
+
49
+ /** @private */
50
+ #mlsBufferView;
51
+
52
+ /** @private */
53
+ componentInvertedImpulseResponse = null;
54
+
55
+ /** @private */
56
+ systemInvertedImpulseResponse = null;
57
+
58
+ //averaged and subtracted ir returned from calibration used to calculated iir
59
+ /** @private */
60
+ ir = null;
61
+
62
+ /** @private */
63
+ impulseResponses = [];
64
+
65
+ /** @private */
66
+ #mlsOrder;
67
+
68
+ /** @private */
69
+ #lowHz;
70
+
71
+ /** @private */
72
+ #highHz;
73
+
74
+ /** @private */
75
+ #mls;
76
+
77
+ /** @private */
78
+ #P;
79
+
80
+ /** @private */
81
+ #audioContext;
82
+
83
+ /** @private */
84
+ TAPER_SECS = 5;
85
+
86
+ /** @private */
87
+ offsetGainNode;
88
+
89
+ /** @private */
90
+ componentConvolution;
91
+
92
+ /** @private */
93
+ systemConvolution;
94
+
95
+ ////////////////////////volume
96
+ /** @private */
97
+ #CALIBRATION_TONE_FREQUENCY = 1000; // Hz
98
+
99
+ /** @private */
100
+ #CALIBRATION_TONE_TYPE = 'sine';
101
+
102
+ CALIBRATION_TONE_DURATION = 5; // seconds
103
+ calibrateSound1000HzPreSec = 3.5;
104
+ calibrateSound1000HzSec = 1.0;
105
+ calibrateSound1000HzPostSec = 0.5;
106
+
107
+ /** @private */
108
+ outDBSPL = null;
109
+ THD = null;
110
+ outDBSPL1000 = null;
111
+
112
+ /** @private */
113
+ TAPER_SECS = 0.01; // seconds
114
+
115
+ /** @private */
116
+ status_denominator = 8;
117
+
118
+ /** @private */
119
+ status_numerator = 0;
120
+
121
+ /** @private */
122
+ percent_complete = 0;
123
+
124
+ /** @private */
125
+ status = ``;
126
+
127
+ /**@private */
128
+ status_literal = `<div style="display: flex; justify-content: center;"><div style="width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;"><div style="width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;"></div></div></div>`;
129
+
130
+ /**@private */
131
+ componentIR = null;
132
+
133
+ /**@private */
134
+ oldComponentIR = null;
135
+
136
+ /**@private */
137
+ systemIR = null;
138
+
139
+ /**@private */
140
+ _calibrateSoundCheck = '';
141
+
142
+ deviceType = null;
143
+
144
+ deviceName = null;
145
+
146
+ deviceInfo = null;
147
+
148
+ desired_time_per_mls = 0;
149
+
150
+ num_mls_to_skip = 0;
151
+
152
+ desired_sampling_rate = 0;
153
+
154
+ #currentConvolution = [];
155
+
156
+ mode = 'unfiltered';
157
+
158
+ sourceNode;
159
+
160
+ autocorrelations = [];
161
+
162
+ iirLength = 0;
163
+
164
+ componentInvertedImpulseResponseNoBandpass = [];
165
+
166
+ systemInvertedImpulseResponseNoBandpass = [];
167
+
168
+ _calibrateSoundBackgroundSecs;
169
+
170
+ background_noise = {};
171
+
172
+ numSuccessfulBackgroundCaptured;
173
+
174
+ _calibrateSoundBurstDb;
175
+
176
+ filteredMLSRange = {
177
+ component: {
178
+ Min: null,
179
+ Max: null,
180
+ },
181
+ system: {
182
+ Min: null,
183
+ Max: null,
184
+ },
185
+ };
186
+
187
+ /**generate string template that gets reevaluated as variable increases */
188
+ generateTemplate = () => {
189
+ if (this.percent_complete > 100) {
190
+ this.percent_complete = 100;
191
+ }
192
+ const template = `<div style="display: flex; justify-content: center; margin-top:12px;"><div style="width: 800px; height: 20px; border: 2px solid #000; border-radius: 10px;"><div style="width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;"></div></div></div>`;
193
+ return template;
194
+ };
195
+
196
+ /** increment numerator and percent for status bar */
197
+ incrementStatusBar = () => {
198
+ this.status_numerator += 1;
199
+ this.percent_complete = (this.status_numerator / this.status_denominator) * 100;
200
+ };
201
+
202
+ setDeviceType = deviceType => {
203
+ this.deviceType = deviceType;
204
+ };
205
+
206
+ setDeviceName = deviceName => {
207
+ this.deviceName = deviceName;
208
+ };
209
+
210
+ setDeviceInfo = deviceInfo => {
211
+ this.deviceInfo = deviceInfo;
212
+ };
213
+
214
+ /** .
215
+ * .
216
+ * .
217
+ * Sends all the computed impulse responses to the backend server for processing
218
+ *
219
+ * @returns sets the resulting inverted impulse response to the class property
220
+ * @example
221
+ */
222
+ sendSystemImpulseResponsesToServerForProcessing = async () => {
223
+ const computedIRs = await Promise.all(this.impulseResponses);
224
+ const filteredComputedIRs = computedIRs.filter(element => {
225
+ return element != undefined;
226
+ }); //log any errors that are found in this step
227
+ const mls = this.#mls;
228
+ const lowHz = this.#lowHz; //gain of 1 below cutoff, need gain of 0
229
+ const highHz = this.#highHz; //check error for anything other than 10 kHz
230
+ const iirLength = this.iirLength;
231
+ const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
232
+ this.stepNum += 1;
233
+ console.log('send impulse responses to server: ' + this.stepNum);
234
+ this.status =
235
+ `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
236
+ this.emit('update', {message: this.status});
237
+ return this.pyServerAPI
238
+ .getSystemInverseImpulseResponseWithRetry({
239
+ payload: filteredComputedIRs.slice(0, this.numCaptures),
240
+ mls,
241
+ lowHz,
242
+ highHz,
243
+ iirLength,
244
+ num_periods,
245
+ sampleRate: this.sourceSamplingRate || 96000,
246
+ calibrateSoundBurstDb: this._calibrateSoundBurstDb,
247
+ })
248
+ .then(res => {
249
+ console.log(res);
250
+ this.stepNum += 1;
251
+ console.log('got impulse response ' + this.stepNum);
252
+ this.incrementStatusBar();
253
+ this.status =
254
+ `All Hz Calibration: done computing the IIR...`.toString() +
255
+ this.generateTemplate().toString();
256
+ this.emit('update', {message: this.status});
257
+ this.systemInvertedImpulseResponse = res['iir'];
258
+ this.systemIR = res['ir'];
259
+ this.systemConvolution = res['convolution'];
260
+ this.systemInvertedImpulseResponseNoBandpass = res['iirNoBandpass'];
261
+ })
262
+ .catch(err => {
263
+ console.error(err);
264
+ });
265
+ };
266
+
267
+ /** .
268
+ * .
269
+ * .
270
+ * Sends all the computed impulse responses to the backend server for processing
271
+ *
272
+ * @returns sets the resulting inverted impulse response to the class property
273
+ * @example
274
+ */
275
+ sendComponentImpulseResponsesToServerForProcessing = async () => {
276
+ const computedIRs = await Promise.all(this.impulseResponses);
277
+ const filteredComputedIRs = computedIRs.filter(element => {
278
+ return element != undefined;
279
+ });
280
+ const componentIRGains = this.componentIR['Gain'];
281
+ const componentIRFreqs = this.componentIR['Freq'];
282
+ const mls = this.#mls;
283
+ const lowHz = this.#lowHz;
284
+ const iirLength = this.iirLength;
285
+ const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
286
+ const highHz = this.#highHz;
287
+ this.stepNum += 1;
288
+ console.log('send impulse responses to server: ' + this.stepNum);
289
+ this.status =
290
+ `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
291
+ this.emit('update', {message: this.status});
292
+ return this.pyServerAPI
293
+ .getComponentInverseImpulseResponseWithRetry({
294
+ payload: filteredComputedIRs.slice(0, this.numCaptures),
295
+ mls,
296
+ lowHz,
297
+ highHz,
298
+ iirLength,
299
+ componentIRGains,
300
+ componentIRFreqs,
301
+ num_periods,
302
+ sampleRate: this.sourceSamplingRate || 96000,
303
+ calibrateSoundBurstDb: this._calibrateSoundBurstDb,
304
+ })
305
+ .then(res => {
306
+ console.log(res);
307
+ this.stepNum += 1;
308
+ console.log('got impulse response ' + this.stepNum);
309
+ this.incrementStatusBar();
310
+ this.status =
311
+ `All Hz Calibration: done computing the IIR...`.toString() +
312
+ this.generateTemplate().toString();
313
+ this.emit('update', {message: this.status});
314
+ this.componentInvertedImpulseResponse = res['iir'];
315
+ this.componentIR['Gain'] = res['ir'];
316
+ this.componentIR['Freq'] = res['frequencies'];
317
+ this.componentConvolution = res['convolution'];
318
+ this.componentInvertedImpulseResponseNoBandpass = res['iirNoBandpass'];
319
+ })
320
+ .catch(err => {
321
+ // this.emit('InvertedImpulseResponse', {res: false});
322
+ console.error(err);
323
+ });
324
+ };
325
+
326
+ sendBackgroundRecording = () => {
327
+ const allSignals = this.getAllBackgroundRecordings();
328
+ const numSignals = allSignals.length;
329
+ const background_rec_whole = allSignals[numSignals - 1];
330
+ const fraction = 0.5 / (this._calibrateSoundBackgroundSecs + 0.5);
331
+ // Calculate the starting index for slicing the array
332
+ const startIndex = Math.round(fraction * background_rec_whole.length);
333
+ // Slice the array from the calculated start index to the end of the array
334
+ const background_rec = background_rec_whole.slice(startIndex);
335
+ console.log('Sending background recording to server for processing');
336
+ this.pyServerAPI
337
+ .getBackgroundNoisePSDWithRetry({
338
+ background_rec,
339
+ sampleRate: this.sourceSamplingRate || 96000,
340
+ })
341
+ .then(res => {
342
+ if (this.numSuccessfulBackgroundCaptured < 1) {
343
+ this.numSuccessfulBackgroundCaptured += 1;
344
+ //storing all background data in background_psd object
345
+ this.background_noise['x_background'] = res['x_background'];
346
+ this.background_noise['y_background'] = res['y_background'];
347
+ this.background_noise['recording'] = background_rec;
348
+ }
349
+ })
350
+ .catch(err => {
351
+ console.error(err);
352
+ });
353
+ };
354
+
355
+ /** .
356
+ * .
357
+ * .
358
+ * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing
359
+ *
360
+ * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed
361
+ * @example
362
+ */
363
+ sendRecordingToServerForProcessing = signalCsv => {
364
+ const allSignals = this.getAllUnfilteredRecordedSignals();
365
+ console.log(
366
+ 'Obtaining last all hz unfiltered recording from #allHzUnfilteredRecordings to send to server for processing'
367
+ );
368
+ const numSignals = allSignals.length;
369
+ const mls = this.#mlsBufferView;
370
+ const payload =
371
+ signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
372
+ console.log('sending rec');
373
+ this.stepNum += 1;
374
+ console.log('send rec ' + this.stepNum);
375
+ this.status =
376
+ `All Hz Calibration Step: computing the IR of the last recording...`.toString() +
377
+ this.generateTemplate().toString();
378
+ this.emit('update', {message: this.status});
379
+ this.impulseResponses.push(
380
+ this.pyServerAPI
381
+ .getImpulseResponse({
382
+ sampleRate: this.sourceSamplingRate || 96000,
383
+ payload,
384
+ mls,
385
+ P: this.#P, //get rid of this
386
+ numPeriods: this.numMLSPerCapture,
387
+ })
388
+ .then(res => {
389
+ if (this.numSuccessfulCaptured < this.numCaptures) {
390
+ this.numSuccessfulCaptured += 1;
391
+ console.log('num succ capt: ' + this.numSuccessfulCaptured);
392
+ this.stepNum += 1;
393
+ console.log('got impulse response ' + this.stepNum);
394
+ this.incrementStatusBar();
395
+ this.status =
396
+ `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +
397
+ this.generateTemplate().toString();
398
+ this.emit('update', {
399
+ message: this.status,
400
+ });
401
+ this.autocorrelations.push(res['autocorrelation']);
402
+ return res['ir'];
403
+ }
404
+ })
405
+ .catch(err => {
406
+ console.error(err);
407
+ })
408
+ );
409
+ };
410
+
411
+ /**
412
+ * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number
413
+ * of MLS periods defined in the constructor.
414
+ *
415
+ * @example
416
+ */
417
+ #awaitDesiredMLSLength = async () => {
418
+ // seconds per MLS = P / SR
419
+ // await N * P / SR
420
+ this.stepNum += 1;
421
+ console.log('await desired length ' + this.stepNum);
422
+ this.status =
423
+ `All Hz Calibration: sampling the calibration signal...`.toString() +
424
+ `\niteration ${this.stepNum}` +
425
+ this.generateTemplate();
426
+ this.emit('update', {
427
+ message: this.status,
428
+ });
429
+ let time_to_wait = 0;
430
+ if (this.mode === 'unfiltered') {
431
+ //unfiltered
432
+ time_to_wait = (this.#mls.length / this.sourceSamplingRate) * this.numMLSPerCapture;
433
+ time_to_wait = time_to_wait * 1.1;
434
+ } else if (this.mode === 'filtered') {
435
+ //filtered
436
+ // time_to_wait =
437
+ // (this.#currentConvolution.length / this.sourceSamplingRate) *
438
+ // (this.numMLSPerCapture / (this.num_mls_to_skip + this.numMLSPerCapture));
439
+ time_to_wait =
440
+ (this.#currentConvolution.length / this.sourceSamplingRate) * this.numMLSPerCapture;
441
+ time_to_wait = time_to_wait * 1.1;
442
+ } else {
443
+ throw new Error('Mode broke in awaitDesiredMLSLength');
444
+ }
445
+
446
+ await sleep(time_to_wait);
447
+ };
448
+
449
+ /**
450
+ * Passed to the background noise recording function, awaits the desired amount of seconds to capture the desired number
451
+ * of seconds of background noise
452
+ *
453
+ * @example
454
+ */
455
+ #awaitBackgroundNoiseRecording = async () => {
456
+ console.log(
457
+ 'Waiting ' + this._calibrateSoundBackgroundSecs + ' second(s) to record background noise'
458
+ );
459
+ let time_to_wait = this._calibrateSoundBackgroundSecs + 0.5;
460
+ await sleep(time_to_wait);
461
+ };
462
+
463
+ /** .
464
+ * .
465
+ * .
466
+ * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state
467
+ *
468
+ * @example
469
+ */
470
+ #awaitSignalOnset = async () => {
471
+ this.stepNum += 1;
472
+ console.log('await signal onset ' + this.stepNum);
473
+ this.status =
474
+ `All Hz Calibration: waiting for the signal to stabilize...`.toString() +
475
+ this.generateTemplate();
476
+ this.emit('update', {
477
+ message: this.status,
478
+ });
479
+ let number_of_bursts_to_skip = this.num_mls_to_skip;
480
+ let time_to_sleep = 0;
481
+ if (this.mode === 'unfiltered') {
482
+ time_to_sleep = (this.#mls.length / this.sourceSamplingRate) * number_of_bursts_to_skip;
483
+ } else if (this.mode === 'filtered') {
484
+ console.log(this.#currentConvolution.length);
485
+ // time_to_sleep =
486
+ // (this.#currentConvolution.length / this.sourceSamplingRate) *
487
+ // (number_of_bursts_to_skip / (number_of_bursts_to_skip + this.numMLSPerCapture));
488
+ time_to_sleep =
489
+ (this.#currentConvolution.length / this.sourceSamplingRate) * number_of_bursts_to_skip;
490
+ } else {
491
+ throw new Error('Mode broke in awaitSignalOnset');
492
+ }
493
+ await sleep(time_to_sleep);
494
+ };
495
+
496
+ /**
497
+ * Called immediately after a recording is captured. Used to process the resulting signal
498
+ * whether by sending the result to a server or by computing a result locally.
499
+ *
500
+ * @example
501
+ */
502
+ #afterMLSRecord = () => {
503
+ console.log('after record');
504
+ this.sendRecordingToServerForProcessing();
505
+ };
506
+
507
+ #afterMLSwIIRRecord = () => {
508
+ if (this.numSuccessfulCaptured < 1) {
509
+ this.numSuccessfulCaptured += 1;
510
+ this.stepNum += 1;
511
+ this.incrementStatusBar();
512
+ console.log('after mls w iir record for some reason add numSucc capt ' + this.stepNum);
513
+ this.status =
514
+ `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +
515
+ this.generateTemplate().toString();
516
+ this.emit('update', {
517
+ message: this.status,
518
+ });
519
+ }
520
+ };
521
+
522
+ /** .
523
+ * .
524
+ * .
525
+ * Created an S Curver Buffer to taper the signal onset
526
+ *
527
+ * @param {*} length
528
+ * @param {*} phase
529
+ * @returns
530
+ * @example
531
+ */
532
+ static createSCurveBuffer = (length, phase) => {
533
+ const curve = new Float32Array(length);
534
+ let i;
535
+ for (i = 0; i < length; i += 1) {
536
+ // scale the curve to be between 0-1
537
+ curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;
538
+ }
539
+ return curve;
540
+ };
541
+
542
+ static createInverseSCurveBuffer = (length, phase) => {
543
+ const curve = new Float32Array(length);
544
+ let i;
545
+ let j = length - 1;
546
+ for (i = 0; i < length; i += 1) {
547
+ // scale the curve to be between 0-1
548
+ curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;
549
+ j -= 1;
550
+ }
551
+ return curve;
552
+ };
553
+
554
+ /**
555
+ * Construct a Calibration Node with the calibration parameters.
556
+ *
557
+ * @param dataBuffer
558
+ * @private
559
+ * @example
560
+ */
561
+ #createCalibrationNodeFromBuffer = dataBuffer => {
562
+ console.log('length databuffer');
563
+ console.log(dataBuffer.length);
564
+ if (!this.sourceAudioContext) {
565
+ this.makeNewSourceAudioContext();
566
+ }
567
+
568
+ const buffer = this.sourceAudioContext.createBuffer(
569
+ 1, // number of channels
570
+ dataBuffer.length,
571
+ this.sourceAudioContext.sampleRate // sample rate
572
+ );
573
+
574
+ const data = buffer.getChannelData(0); // get data
575
+ if (this.mode === 'filtered') {
576
+ console.log('check max and min of convolution');
577
+ } else {
578
+ console.log('check max and min of mls');
579
+ }
580
+
581
+ console.log('Max:', Math.min(...dataBuffer));
582
+ console.log('Min:', Math.max(...dataBuffer));
583
+ // fill the buffer with our data
584
+ try {
585
+ for (let i = 0; i < dataBuffer.length; i += 1) {
586
+ data[i] = dataBuffer[i];
587
+ }
588
+ } catch (error) {
589
+ console.error(error);
590
+ }
591
+
592
+ this.sourceNode = this.sourceAudioContext.createBufferSource();
593
+
594
+ this.sourceNode.buffer = buffer;
595
+
596
+ if (this.mode === 'filtered') {
597
+ //used to not loop filtered
598
+ this.sourceNode.loop = true;
599
+ } else {
600
+ this.sourceNode.loop = true;
601
+ }
602
+
603
+ this.sourceNode.connect(this.sourceAudioContext.destination);
604
+
605
+ this.addCalibrationNode(this.sourceNode);
606
+ };
607
+
608
+ /**
609
+ * Given a data buffer, creates the required calibration node
610
+ *
611
+ * @param {*} dataBufferArray
612
+ * @example
613
+ */
614
+ #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {
615
+ if (dataBufferArray.length === 1) {
616
+ this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);
617
+ } else {
618
+ throw new Error('The length of the data buffer array must be 1');
619
+ }
620
+ };
621
+
622
+ /**
623
+ * Creates an audio context and plays it for a few seconds.
624
+ *
625
+ * @private
626
+ * @returns - Resolves when the audio is done playing.
627
+ * @example
628
+ */
629
+ #playCalibrationAudio = () => {
630
+ this.calibrationNodes[0].start(0);
631
+ this.status = ``;
632
+ if (this.mode === 'unfiltered') {
633
+ console.log('mls', this.#mls); // before multiplied by calibrateSoundBurstDb
634
+ console.log('mls buffer view', this.#mlsBufferView); // after multiplied by calibrateSoundBurstDb
635
+ console.log('play calibration audio ' + this.stepNum);
636
+ this.status =
637
+ `All Hz Calibration: playing the calibration tone...`.toString() +
638
+ this.generateTemplate().toString();
639
+ } else if (this.mode === 'filtered') {
640
+ console.log('play convolved audio ' + this.stepNum);
641
+ this.status =
642
+ `All Hz Calibration: playing the convolved calibration tone...`.toString() +
643
+ this.generateTemplate().toString();
644
+ } else {
645
+ throw new Error('Mode is incorrect');
646
+ }
647
+ this.emit('update', {message: this.status});
648
+ this.stepNum += 1;
649
+ console.log('sink sampling rate');
650
+ console.log(this.sinkSamplingRate);
651
+ console.log('source sampling rate');
652
+ console.log(this.sourceSamplingRate);
653
+ };
654
+
655
+ /** .
656
+ * .
657
+ * .
658
+ * Stops the audio with tapered offset
659
+ *
660
+ * @example
661
+ */
662
+ #stopCalibrationAudio = () => {
663
+ this.calibrationNodes[0].stop(0);
664
+ this.calibrationNodes = [];
665
+ this.sourceNode.disconnect();
666
+ this.stepNum += 1;
667
+ console.log('stop calibration audio ' + this.stepNum);
668
+ this.status =
669
+ `All Hz Calibration: stopping the calibration tone...`.toString() +
670
+ this.generateTemplate().toString();
671
+ this.emit('update', {message: this.status});
672
+ };
673
+
674
+ playMLSwithIIR = async (stream, convolution) => {
675
+ let checkRec = false;
676
+ this.mode = 'filtered';
677
+ console.log('play mls with iir');
678
+ //this.invertedImpulseResponse = iir
679
+
680
+ await this.calibrationSteps(
681
+ stream,
682
+ this.#playCalibrationAudio, // play audio func (required)
683
+ this.#createCalibrationNodeFromBuffer(convolution), // before play func
684
+ this.#awaitSignalOnset, // before record
685
+ () => this.numSuccessfulCaptured < 1,
686
+ this.#awaitDesiredMLSLength, // during record
687
+ this.#afterMLSwIIRRecord, // after record
688
+ this.mode,
689
+ checkRec
690
+ );
691
+ };
692
+
693
+ bothSoundCheck = async stream => {
694
+ let iir_ir_and_plots;
695
+ this.#currentConvolution = this.componentConvolution;
696
+ this.filteredMLSRange.component.Min = Math.min(...this.#currentConvolution);
697
+ this.filteredMLSRange.component.Max = Math.max(...this.#currentConvolution);
698
+ await this.playMLSwithIIR(stream, this.#currentConvolution);
699
+ this.#stopCalibrationAudio();
700
+ let component_conv_recs = this.getAllFilteredRecordedSignals();
701
+ let return_component_conv_rec = component_conv_recs[0];
702
+ this.clearAllFilteredRecordedSignals();
703
+ this.#currentConvolution = this.systemConvolution;
704
+ this.filteredMLSRange.system.Min = Math.min(...this.#currentConvolution);
705
+ this.filteredMLSRange.system.Max = Math.max(...this.#currentConvolution);
706
+ await this.playMLSwithIIR(stream, this.#currentConvolution);
707
+ this.#stopCalibrationAudio();
708
+ let system_conv_recs = this.getAllFilteredRecordedSignals();
709
+ let return_system_conv_rec = system_conv_recs[0];
710
+ this.sourceAudioContext.close();
711
+ let recs = this.getAllUnfilteredRecordedSignals();
712
+ let unconv_rec = recs[0];
713
+ let return_unconv_rec = unconv_rec;
714
+ let conv_rec = component_conv_recs[0];
715
+
716
+ //psd of component
717
+ let knownGain = this.oldComponentIR.Gain;
718
+ let knownFreq = this.oldComponentIR.Freq;
719
+ let sampleRate = this.sourceSamplingRate || 96000;
720
+ let component_unconv_rec_psd = await this.pyServerAPI
721
+ .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)
722
+ .then(res => {
723
+ this.incrementStatusBar();
724
+ this.status =
725
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
726
+ this.generateTemplate().toString();
727
+ this.emit('update', {message: this.status});
728
+ return res;
729
+ })
730
+ .catch(err => {
731
+ console.error(err);
732
+ });
733
+
734
+ let component_conv_rec_psd = await this.pyServerAPI
735
+ .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)
736
+ .then(res => {
737
+ this.incrementStatusBar();
738
+ this.status =
739
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
740
+ this.generateTemplate().toString();
741
+ this.emit('update', {message: this.status});
742
+ return res;
743
+ })
744
+ .catch(err => {
745
+ console.error(err);
746
+ });
747
+
748
+ conv_rec = system_conv_recs[0];
749
+ //psd of system
750
+ let system_recs_psd = await this.pyServerAPI
751
+ .getPSDWithRetry({
752
+ unconv_rec,
753
+ conv_rec,
754
+ sampleRate: this.sourceSamplingRate || 96000,
755
+ })
756
+ .then(res => {
757
+ this.incrementStatusBar();
758
+ this.status =
759
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
760
+ this.generateTemplate().toString();
761
+ this.emit('update', {message: this.status});
762
+ return res;
763
+ })
764
+ .catch(err => {
765
+ console.error(err);
766
+ });
767
+
768
+ //iir w/ and without bandpass psd. done
769
+ unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
770
+ conv_rec = this.componentInvertedImpulseResponse;
771
+ let component_iir_psd = await this.pyServerAPI
772
+ .getPSDWithRetry({
773
+ unconv_rec,
774
+ conv_rec,
775
+ sampleRate: this.sourceSamplingRate || 96000,
776
+ })
777
+ .then(res => {
778
+ this.incrementStatusBar();
779
+ this.status =
780
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
781
+ this.generateTemplate().toString();
782
+ this.emit('update', {message: this.status});
783
+ return res;
784
+ })
785
+ .catch(err => {
786
+ console.error(err);
787
+ });
788
+ unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
789
+ conv_rec = this.systemInvertedImpulseResponse;
790
+ let system_iir_psd = await this.pyServerAPI
791
+ .getPSDWithRetry({
792
+ unconv_rec,
793
+ conv_rec,
794
+ sampleRate: this.sourceSamplingRate || 96000,
795
+ })
796
+ .then(res => {
797
+ this.incrementStatusBar();
798
+ this.status =
799
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
800
+ this.generateTemplate().toString();
801
+ this.emit('update', {message: this.status});
802
+ return res;
803
+ })
804
+ .catch(err => {
805
+ console.error(err);
806
+ });
807
+
808
+ let mls_psd = await this.pyServerAPI
809
+ .getMLSPSDWithRetry({mls: this.#mlsBufferView, sampleRate: this.sourceSamplingRate || 96000})
810
+ .then(res => {
811
+ this.incrementStatusBar();
812
+ this.status =
813
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
814
+ this.generateTemplate().toString();
815
+ this.emit('update', {message: this.status});
816
+ return res;
817
+ })
818
+ .catch(err => {
819
+ console.error(err);
820
+ });
821
+
822
+ let system_filtered_mls_psd = await this.pyServerAPI
823
+ .getMLSPSDWithRetry({
824
+ mls: this.systemConvolution,
825
+ sampleRate: this.sourceSamplingRate || 96000,
826
+ })
827
+ .then(res => {
828
+ this.incrementStatusBar();
829
+ this.status =
830
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
831
+ this.generateTemplate().toString();
832
+ this.emit('update', {message: this.status});
833
+ return res;
834
+ })
835
+ .catch(err => {
836
+ console.error(err);
837
+ });
838
+
839
+ let component_filtered_mls_psd = await this.pyServerAPI
840
+ .getMLSPSDWithRetry({
841
+ mls: this.componentConvolution,
842
+ sampleRate: this.sourceSamplingRate || 96000,
843
+ })
844
+ .then(res => {
845
+ this.incrementStatusBar();
846
+ this.status =
847
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
848
+ this.generateTemplate().toString();
849
+ this.emit('update', {message: this.status});
850
+ return res;
851
+ })
852
+ .catch(err => {
853
+ console.error(err);
854
+ });
855
+
856
+ iir_ir_and_plots = {
857
+ filtered_recording: {
858
+ component: return_component_conv_rec,
859
+ system: return_system_conv_rec,
860
+ },
861
+ unfiltered_recording: this.getAllUnfilteredRecordedSignals()[0],
862
+ system: {
863
+ iir: this.systemInvertedImpulseResponse,
864
+ ir: this.systemIR,
865
+ iir_psd: {
866
+ y: system_iir_psd['y_conv'],
867
+ x: system_iir_psd['x_conv'],
868
+ y_no_bandpass: system_iir_psd['y_unconv'],
869
+ x_no_bandpass: system_iir_psd['x_unconv'],
870
+ },
871
+ filtered_mls_psd: {
872
+ x: system_filtered_mls_psd['x_mls'],
873
+ y: system_filtered_mls_psd['y_mls'],
874
+ },
875
+ convolution: this.systemConvolution,
876
+ psd: {
877
+ unconv: {
878
+ x: system_recs_psd['x_unconv'],
879
+ y: system_recs_psd['y_unconv'],
880
+ },
881
+ conv: {
882
+ x: system_recs_psd['x_conv'],
883
+ y: system_recs_psd['y_conv'],
884
+ },
885
+ },
886
+ },
887
+ component: {
888
+ iir: this.componentInvertedImpulseResponse,
889
+ ir: this.componentIR,
890
+ iir_psd: {
891
+ y: component_iir_psd['y_conv'],
892
+ x: component_iir_psd['x_conv'],
893
+ y_no_bandpass: component_iir_psd['y_unconv'],
894
+ x_no_bandpass: component_iir_psd['x_unconv'],
895
+ },
896
+ filtered_mls_psd: {
897
+ x: component_filtered_mls_psd['x_mls'],
898
+ y: component_filtered_mls_psd['y_mls'],
899
+ },
900
+ convolution: this.componentConvolution,
901
+ psd: {
902
+ unconv: {
903
+ x: component_unconv_rec_psd['x'],
904
+ y: component_unconv_rec_psd['y'],
905
+ },
906
+ conv: {
907
+ x: component_conv_rec_psd['x'],
908
+ y: component_conv_rec_psd['y'],
909
+ },
910
+ },
911
+ },
912
+ mls: this.#mlsBufferView,
913
+ mls_psd: {
914
+ x: mls_psd['x_mls'],
915
+ y: mls_psd['y_mls'],
916
+ },
917
+ autocorrelations: this.autocorrelations,
918
+ impulseResponses: [],
919
+ };
920
+
921
+ return iir_ir_and_plots;
922
+ };
923
+
924
+ singleSoundCheck = async stream => {
925
+ let iir_ir_and_plots;
926
+ if (this._calibrateSoundCheck != 'system') {
927
+ this.#currentConvolution = this.componentConvolution;
928
+ this.filteredMLSRange.component.Min = Math.min(...this.#currentConvolution);
929
+ this.filteredMLSRange.component.Max = Math.max(...this.#currentConvolution);
930
+ } else {
931
+ this.#currentConvolution = this.systemConvolution;
932
+ this.filteredMLSRange.system.Min = Math.min(...this.#currentConvolution);
933
+ this.filteredMLSRange.system.Max = Math.max(...this.#currentConvolution);
934
+ }
935
+ await this.playMLSwithIIR(stream, this.#currentConvolution);
936
+ this.#stopCalibrationAudio();
937
+ this.sourceAudioContext.close();
938
+ let conv_recs = this.getAllFilteredRecordedSignals();
939
+ let recs = this.getAllUnfilteredRecordedSignals();
940
+ console.log('Obtaining unfiltered recording from #allHzUnfilteredRecordings to calculate PSD');
941
+ console.log('Obtaining filtered recording from #allHzFilteredRecordings to calculate PSD');
942
+ let unconv_rec = recs[0];
943
+ let return_unconv_rec = unconv_rec;
944
+ let conv_rec = conv_recs[0];
945
+ let return_conv_rec = conv_rec;
946
+ if (this._calibrateSoundCheck != 'system') {
947
+ let knownGain = this.oldComponentIR.Gain;
948
+ let knownFreq = this.oldComponentIR.Freq;
949
+ let sampleRate = this.sourceSamplingRate || 96000;
950
+ let unconv_results = await this.pyServerAPI
951
+ .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)
952
+ .then(res => {
953
+ this.incrementStatusBar();
954
+ this.status =
955
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
956
+ this.generateTemplate().toString();
957
+ this.emit('update', {message: this.status});
958
+ return res;
959
+ })
960
+ .catch(err => {
961
+ console.error(err);
962
+ });
963
+
964
+ let conv_results = await this.pyServerAPI
965
+ .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)
966
+ .then(res => {
967
+ this.incrementStatusBar();
968
+ this.status =
969
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
970
+ this.generateTemplate().toString();
971
+ this.emit('update', {message: this.status});
972
+ return res;
973
+ })
974
+ .catch(err => {
975
+ console.error(err);
976
+ });
977
+ unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
978
+ conv_rec = this.componentInvertedImpulseResponse;
979
+ let component_iir_psd = await this.pyServerAPI
980
+ .getPSDWithRetry({
981
+ unconv_rec,
982
+ conv_rec,
983
+ sampleRate: this.sourceSamplingRate || 96000,
984
+ })
985
+ .then(res => {
986
+ this.incrementStatusBar();
987
+ this.status =
988
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
989
+ this.generateTemplate().toString();
990
+ this.emit('update', {message: this.status});
991
+ return res;
992
+ })
993
+ .catch(err => {
994
+ console.error(err);
995
+ });
996
+ unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
997
+ conv_rec = this.systemInvertedImpulseResponse;
998
+ let system_iir_psd = await this.pyServerAPI
999
+ .getPSDWithRetry({
1000
+ unconv_rec,
1001
+ conv_rec,
1002
+ sampleRate: this.sourceSamplingRate || 96000,
1003
+ })
1004
+ .then(res => {
1005
+ this.incrementStatusBar();
1006
+ this.status =
1007
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1008
+ this.generateTemplate().toString();
1009
+ this.emit('update', {message: this.status});
1010
+ return res;
1011
+ })
1012
+ .catch(err => {
1013
+ console.error(err);
1014
+ });
1015
+
1016
+ let mls_psd = await this.pyServerAPI
1017
+ .getMLSPSDWithRetry({
1018
+ mls: this.#mlsBufferView,
1019
+ sampleRate: this.sourceSamplingRate || 96000,
1020
+ })
1021
+ .then(res => {
1022
+ this.incrementStatusBar();
1023
+ this.status =
1024
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1025
+ this.generateTemplate().toString();
1026
+ this.emit('update', {message: this.status});
1027
+ return res;
1028
+ })
1029
+ .catch(err => {
1030
+ console.error(err);
1031
+ });
1032
+
1033
+ let filtered_mls_psd = await this.pyServerAPI
1034
+ .getMLSPSDWithRetry({
1035
+ mls: this.componentConvolution,
1036
+ sampleRate: this.sourceSamplingRate || 96000,
1037
+ })
1038
+ .then(res => {
1039
+ this.incrementStatusBar();
1040
+ this.status =
1041
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1042
+ this.generateTemplate().toString();
1043
+ this.emit('update', {message: this.status});
1044
+ return res;
1045
+ })
1046
+ .catch(err => {
1047
+ console.error(err);
1048
+ });
1049
+
1050
+ iir_ir_and_plots = {
1051
+ unfiltered_recording: return_unconv_rec,
1052
+ filtered_recording: return_conv_rec,
1053
+ system: {
1054
+ iir: this.systemInvertedImpulseResponse,
1055
+ ir: this.systemIR,
1056
+ iir_psd: {
1057
+ y: system_iir_psd['y_conv'],
1058
+ x: system_iir_psd['y_conv'],
1059
+ y_no_bandpass: system_iir_psd['y_unconv'],
1060
+ x_no_bandpass: system_iir_psd['x_unconv'],
1061
+ },
1062
+ filtered_recording: [],
1063
+ filtered_mls_psd: {},
1064
+ convolution: this.systemConvolution,
1065
+ psd: {
1066
+ unconv: {
1067
+ x: [],
1068
+ y: [],
1069
+ },
1070
+ conv: {
1071
+ x: [],
1072
+ y: [],
1073
+ },
1074
+ },
1075
+ },
1076
+ component: {
1077
+ iir: this.componentInvertedImpulseResponse,
1078
+ ir: this.componentIR,
1079
+ iir_psd: {
1080
+ y: component_iir_psd['y_conv'],
1081
+ x: component_iir_psd['x_conv'],
1082
+ y_no_bandpass: component_iir_psd['y_unconv'],
1083
+ x_no_bandpass: component_iir_psd['x_unconv'],
1084
+ },
1085
+ filtered_mls_psd: {
1086
+ x: filtered_mls_psd['x_mls'],
1087
+ y: filtered_mls_psd['y_mls'],
1088
+ },
1089
+ convolution: this.componentConvolution,
1090
+ psd: {
1091
+ unconv: {
1092
+ x: unconv_results['x'],
1093
+ y: unconv_results['y'],
1094
+ },
1095
+ conv: {
1096
+ x: conv_results['x'],
1097
+ y: conv_results['y'],
1098
+ },
1099
+ },
1100
+ },
1101
+ mls: this.#mlsBufferView,
1102
+ mls_psd: {
1103
+ x: mls_psd['x_mls'],
1104
+ y: mls_psd['y_mls'],
1105
+ },
1106
+ autocorrelations: this.autocorrelations,
1107
+ impulseResponses: [],
1108
+ };
1109
+ } else {
1110
+ let results = await this.pyServerAPI
1111
+ .getPSDWithRetry({
1112
+ unconv_rec,
1113
+ conv_rec,
1114
+ sampleRate: this.sourceSamplingRate || 96000,
1115
+ })
1116
+ .then(res => {
1117
+ this.incrementStatusBar();
1118
+ this.status =
1119
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1120
+ this.generateTemplate().toString();
1121
+ this.emit('update', {message: this.status});
1122
+ return res;
1123
+ })
1124
+ .catch(err => {
1125
+ console.error(err);
1126
+ });
1127
+
1128
+ //iir w/ and without bandpass psd
1129
+ unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
1130
+ conv_rec = this.componentInvertedImpulseResponse;
1131
+ let component_iir_psd = await this.pyServerAPI
1132
+ .getPSDWithRetry({
1133
+ unconv_rec,
1134
+ conv_rec,
1135
+ sampleRate: this.sourceSamplingRate || 96000,
1136
+ })
1137
+ .then(res => {
1138
+ this.incrementStatusBar();
1139
+ this.status =
1140
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1141
+ this.generateTemplate().toString();
1142
+ this.emit('update', {message: this.status});
1143
+ return res;
1144
+ })
1145
+ .catch(err => {
1146
+ console.error(err);
1147
+ });
1148
+ unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
1149
+ conv_rec = this.systemInvertedImpulseResponse;
1150
+ let system_iir_psd = await this.pyServerAPI
1151
+ .getPSDWithRetry({
1152
+ unconv_rec,
1153
+ conv_rec,
1154
+ sampleRate: this.sourceSamplingRate || 96000,
1155
+ })
1156
+ .then(res => {
1157
+ this.incrementStatusBar();
1158
+ this.status =
1159
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1160
+ this.generateTemplate().toString();
1161
+ this.emit('update', {message: this.status});
1162
+ return res;
1163
+ })
1164
+ .catch(err => {
1165
+ console.error(err);
1166
+ });
1167
+
1168
+ let mls_psd = await this.pyServerAPI
1169
+ .getMLSPSDWithRetry({
1170
+ mls: this.#mlsBufferView,
1171
+ sampleRate: this.sourceSamplingRate || 96000,
1172
+ })
1173
+ .then(res => {
1174
+ this.incrementStatusBar();
1175
+ this.status =
1176
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1177
+ this.generateTemplate().toString();
1178
+ this.emit('update', {message: this.status});
1179
+ return res;
1180
+ })
1181
+ .catch(err => {
1182
+ console.error(err);
1183
+ });
1184
+
1185
+ let filtered_mls_psd = await this.pyServerAPI
1186
+ .getMLSPSDWithRetry({
1187
+ mls: this.systemConvolution,
1188
+ sampleRate: this.sourceSamplingRate || 96000,
1189
+ })
1190
+ .then(res => {
1191
+ this.incrementStatusBar();
1192
+ this.status =
1193
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1194
+ this.generateTemplate().toString();
1195
+ this.emit('update', {message: this.status});
1196
+ return res;
1197
+ })
1198
+ .catch(err => {
1199
+ console.error(err);
1200
+ });
1201
+
1202
+ iir_ir_and_plots = {
1203
+ unfiltered_recording: return_unconv_rec,
1204
+ filtered_recording: return_conv_rec,
1205
+ system: {
1206
+ iir: this.systemInvertedImpulseResponse,
1207
+ ir: this.systemIR,
1208
+ iir_psd: {
1209
+ y: system_iir_psd['y_conv'],
1210
+ x: system_iir_psd['y_conv'],
1211
+ y_no_bandpass: system_iir_psd['y_unconv'],
1212
+ x_no_bandpass: system_iir_psd['x_unconv'],
1213
+ },
1214
+ filtered_recording: [],
1215
+ filtered_mls_psd: {
1216
+ x: filtered_mls_psd['x_mls'],
1217
+ y: filtered_mls_psd['y_mls'],
1218
+ },
1219
+ convolution: this.systemConvolution,
1220
+ psd: {
1221
+ unconv: {
1222
+ x: results['x_unconv'],
1223
+ y: results['y_unconv'],
1224
+ },
1225
+ conv: {
1226
+ x: results['x_conv'],
1227
+ y: results['y_conv'],
1228
+ },
1229
+ },
1230
+ },
1231
+ component: {
1232
+ iir: this.componentInvertedImpulseResponse,
1233
+ ir: this.componentIR,
1234
+ iir_psd: {
1235
+ y: component_iir_psd['y_conv'],
1236
+ x: component_iir_psd['x_conv'],
1237
+ y_no_bandpass: component_iir_psd['y_unconv'],
1238
+ x_no_bandpass: component_iir_psd['x_unconv'],
1239
+ },
1240
+ filtered_mls_psd: {},
1241
+ convolution: this.componentConvolution,
1242
+ psd: {
1243
+ unconv: {
1244
+ x: [],
1245
+ y: [],
1246
+ },
1247
+ conv: {
1248
+ x: [],
1249
+ y: [],
1250
+ },
1251
+ },
1252
+ },
1253
+ mls: this.#mlsBufferView,
1254
+ mls_psd: {
1255
+ x: mls_psd['x_mls'],
1256
+ y: mls_psd['y_mls'],
1257
+ },
1258
+ autocorrelations: this.autocorrelations,
1259
+ impulseResponses: [],
1260
+ };
1261
+ }
1262
+ await Promise.all(this.impulseResponses).then(res => {
1263
+ for (let i = 0; i < res.length; i++) {
1264
+ if (res[i] != undefined) {
1265
+ iir_ir_and_plots['impulseResponses'].push(res[i]);
1266
+ }
1267
+ }
1268
+ });
1269
+
1270
+ if (this.#download) {
1271
+ this.downloadSingleUnfilteredRecording();
1272
+ this.downloadSingleFilteredRecording();
1273
+ saveToCSV(this.#mls, 'MLS.csv');
1274
+ saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
1275
+ saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
1276
+ saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
1277
+ saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
1278
+ for (let i = 0; i < this.autocorrelations.length; i++) {
1279
+ saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
1280
+ }
1281
+ const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
1282
+ for (let i = 0; i < res.length; i++) {
1283
+ if (res[i] != undefined) {
1284
+ saveToCSV(res[i], `IR_${i}`);
1285
+ }
1286
+ }
1287
+ });
1288
+ }
1289
+
1290
+ return iir_ir_and_plots;
1291
+ };
1292
+
1293
+ /**
1294
+ * Public method to start the calibration process. Objects intialized from webassembly allocate new memory
1295
+ * and must be manually freed. This function is responsible for intializing the MlsGenInterface,
1296
+ * and wrapping the calibration steps with a garbage collection safe gaurd.
1297
+ *
1298
+ * @public
1299
+ * @param stream - The stream of audio from the Listener.
1300
+ * @example
1301
+ */
1302
+ startCalibrationImpulseResponse = async stream => {
1303
+ let desired_time = this.desired_time_per_mls;
1304
+ let checkRec = 'allhz';
1305
+
1306
+ console.log('MLS sequence should be of length: ' + this.sourceSamplingRate * desired_time);
1307
+
1308
+ length = this.sourceSamplingRate * desired_time;
1309
+ //get mls here
1310
+ const calibrateSoundBurstDb = this._calibrateSoundBurstDb;
1311
+ await this.pyServerAPI
1312
+ .getMLSWithRetry({length, calibrateSoundBurstDb})
1313
+ .then(res => {
1314
+ console.log(res);
1315
+ this.#mlsBufferView = res['mls'];
1316
+ this.#mls = res['unscaledMLS'];
1317
+ })
1318
+ .catch(err => {
1319
+ // this.emit('InvertedImpulseResponse', {res: false});
1320
+ console.error(err);
1321
+ });
1322
+ this.numSuccessfulBackgroundCaptured = 0;
1323
+ if (this._calibrateSoundBackgroundSecs > 0) {
1324
+ this.mode = 'background';
1325
+ this.status =
1326
+ `All Hz Calibration: sampling the background noise...`.toString() +
1327
+ this.generateTemplate().toString();
1328
+ this.emit('update', {message: this.status});
1329
+ await this.recordBackground(
1330
+ stream, //stream
1331
+ () => this.numSuccessfulBackgroundCaptured < 1, //loop condition
1332
+ this.#awaitBackgroundNoiseRecording, //sleep to record
1333
+ this.sendBackgroundRecording, //send to get PSD
1334
+ this.mode,
1335
+ checkRec
1336
+ );
1337
+ this.incrementStatusBar();
1338
+ }
1339
+ this.mode = 'unfiltered';
1340
+ this.numSuccessfulCaptured = 0;
1341
+
1342
+ await this.calibrationSteps(
1343
+ stream,
1344
+ this.#playCalibrationAudio, // play audio func (required)
1345
+ this.#createCalibrationNodeFromBuffer(this.#mlsBufferView), // before play func
1346
+ this.#awaitSignalOnset, // before record
1347
+ () => this.numSuccessfulCaptured < this.numCaptures, // loop while true
1348
+ this.#awaitDesiredMLSLength, // during record
1349
+ this.#afterMLSRecord, // after record
1350
+ this.mode,
1351
+ checkRec
1352
+ ),
1353
+ this.#stopCalibrationAudio();
1354
+ checkRec = false;
1355
+
1356
+ // at this stage we've captured all the required signals,
1357
+ // and have received IRs for each one
1358
+ // so let's send all the IRs to the server to be converted to a single IIR
1359
+ await this.sendSystemImpulseResponsesToServerForProcessing();
1360
+ await this.sendComponentImpulseResponsesToServerForProcessing();
1361
+
1362
+ this.numSuccessfulCaptured = 0;
1363
+
1364
+ let iir_ir_and_plots;
1365
+ if (this._calibrateSoundCheck != 'none') {
1366
+ //do single check
1367
+ if (this._calibrateSoundCheck == 'goal' || this._calibrateSoundCheck == 'system') {
1368
+ iir_ir_and_plots = await this.singleSoundCheck(stream);
1369
+ } else {
1370
+ //both
1371
+ iir_ir_and_plots = await this.bothSoundCheck(stream);
1372
+ }
1373
+ } else {
1374
+ let unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
1375
+ let conv_rec = this.componentInvertedImpulseResponse;
1376
+ let component_iir_psd = await this.pyServerAPI
1377
+ .getPSDWithRetry({
1378
+ unconv_rec,
1379
+ conv_rec,
1380
+ sampleRate: this.sourceSamplingRate || 96000,
1381
+ })
1382
+ .then(res => {
1383
+ this.incrementStatusBar();
1384
+ this.status =
1385
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1386
+ this.generateTemplate().toString();
1387
+ this.emit('update', {message: this.status});
1388
+ return res;
1389
+ })
1390
+ .catch(err => {
1391
+ console.error(err);
1392
+ });
1393
+ unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
1394
+ conv_rec = this.systemInvertedImpulseResponse;
1395
+ let system_iir_psd = await this.pyServerAPI
1396
+ .getPSDWithRetry({
1397
+ unconv_rec,
1398
+ conv_rec,
1399
+ sampleRate: this.sourceSamplingRate || 96000,
1400
+ })
1401
+ .then(res => {
1402
+ this.incrementStatusBar();
1403
+ this.status =
1404
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1405
+ this.generateTemplate().toString();
1406
+ this.emit('update', {message: this.status});
1407
+ return res;
1408
+ })
1409
+ .catch(err => {
1410
+ console.error(err);
1411
+ });
1412
+
1413
+ iir_ir_and_plots = {
1414
+ unfiltered_recording: return_unconv_rec,
1415
+ filtered_recording: return_conv_rec,
1416
+ system: {
1417
+ iir: this.systemInvertedImpulseResponse,
1418
+ ir: this.systemIR,
1419
+ iir_psd: {
1420
+ y: system_iir_psd['y_conv'],
1421
+ x: system_iir_psd['y_conv'],
1422
+ y_no_bandpass: system_iir_psd['y_unconv'],
1423
+ x_no_bandpass: system_iir_psd['x_unconv'],
1424
+ },
1425
+ filtered_recording: [],
1426
+ convolution: this.systemConvolution,
1427
+ psd: {
1428
+ unconv: {
1429
+ x: [],
1430
+ y: [],
1431
+ },
1432
+ conv: {
1433
+ x: [],
1434
+ y: [],
1435
+ },
1436
+ },
1437
+ },
1438
+ component: {
1439
+ iir: this.componentInvertedImpulseResponse,
1440
+ ir: this.componentIR,
1441
+ iir_psd: {
1442
+ y: component_iir_psd['y_conv'],
1443
+ x: component_iir_psd['x_conv'],
1444
+ y_no_bandpass: component_iir_psd['y_unconv'],
1445
+ x_no_bandpass: component_iir_psd['x_unconv'],
1446
+ },
1447
+ convolution: this.componentConvolution,
1448
+ psd: {
1449
+ unconv: {
1450
+ x: [],
1451
+ y: [],
1452
+ },
1453
+ conv: {
1454
+ x: [],
1455
+ y: [],
1456
+ },
1457
+ },
1458
+ },
1459
+ mls: this.#mlsBufferView,
1460
+ autocorrelations: this.autocorrelations,
1461
+ impulseResponses: [],
1462
+ };
1463
+ await Promise.all(this.impulseResponses).then(res => {
1464
+ for (let i = 0; i < res.length; i++) {
1465
+ if (res[i] != undefined) {
1466
+ iir_ir_and_plots['impulseResponses'].push(res[i]);
1467
+ }
1468
+ }
1469
+ });
1470
+
1471
+ if (this.#download) {
1472
+ saveToCSV(this.#mls, 'MLS.csv');
1473
+ saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
1474
+ saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
1475
+ saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
1476
+ saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
1477
+ for (let i = 0; i < this.autocorrelations.length; i++) {
1478
+ saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
1479
+ }
1480
+ const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
1481
+ for (let i = 0; i < res.length; i++) {
1482
+ if (res[i] != undefined) {
1483
+ saveToCSV(res[i], `IR_${i}`);
1484
+ }
1485
+ }
1486
+ });
1487
+ }
1488
+ }
1489
+
1490
+ this.percent_complete = 100;
1491
+
1492
+ this.status = `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();
1493
+ this.emit('update', {message: this.status});
1494
+
1495
+ //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR
1496
+ //that was used to calibrate
1497
+
1498
+ return iir_ir_and_plots;
1499
+ };
1500
+
1501
+ //////////////////////volume
1502
+
1503
+ handleIncomingData = data => {
1504
+ console.log('Received data: ', data);
1505
+ if (data.type === 'soundGainDBSPL') {
1506
+ this.soundGainDBSPL = data.value;
1507
+ } else {
1508
+ throw new Error(`Unknown data type: ${data.type}`);
1509
+ }
1510
+ };
1511
+ createSCurveBuffer = (onSetBool = true) => {
1512
+ const curve = new Float32Array(this.TAPER_SECS * this.sourceSamplingRate + 1);
1513
+ const frequency = 1 / (4 * this.TAPER_SECS);
1514
+ let j = 0;
1515
+ for (let i = 0; i < this.TAPER_SECS * this.sourceSamplingRate + 1; i += 1) {
1516
+ const phase = 2 * Math.PI * frequency * j;
1517
+ const onsetTaper = Math.pow(Math.sin(phase), 2);
1518
+ const offsetTaper = Math.pow(Math.cos(phase), 2);
1519
+ curve[i] = onSetBool ? onsetTaper : offsetTaper;
1520
+ j += 1 / this.sourceSamplingRate;
1521
+ }
1522
+ return curve;
1523
+ };
1524
+
1525
+ #getTruncatedSignal = (left = 3.5, right = 4.5) => {
1526
+ const start = Math.floor(left * this.sourceSamplingRate);
1527
+ const end = Math.floor(right * this.sourceSamplingRate);
1528
+ const result = Array.from(this.getLastVolumeRecordedSignal().slice(start, end));
1529
+ console.log(
1530
+ 'Obtaining last 1000 hz recording from #allVolumeRecordings to send for processing'
1531
+ );
1532
+ /**
1533
+ * function to check that capture was properly made
1534
+ * @param {*} list
1535
+ */
1536
+ const checkResult = list => {
1537
+ const setItem = new Set(list);
1538
+ if (setItem.size === 1 && setItem.has(0)) {
1539
+ console.warn(
1540
+ 'The last capture failed, all recorded signal is zero',
1541
+ this.getAllVolumeRecordedSignals()
1542
+ );
1543
+ }
1544
+ if (setItem.size === 0) {
1545
+ console.warn('The last capture failed, no recorded signal');
1546
+ }
1547
+ };
1548
+ checkResult(result);
1549
+ return result;
1550
+ };
1551
+
1552
+ /**
1553
+ *
1554
+ *
1555
+ Construct a calibration Node with the calibration parameters and given gain value
1556
+ * @param {*} gainValue
1557
+ * */
1558
+ #createCalibrationToneWithGainValue = gainValue => {
1559
+ const audioContext = this.makeNewSourceAudioContext();
1560
+ const oscilator = audioContext.createOscillator();
1561
+ const gainNode = audioContext.createGain();
1562
+ const taperGainNode = audioContext.createGain();
1563
+ const offsetGainNode = audioContext.createGain();
1564
+ const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
1565
+
1566
+ oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
1567
+ oscilator.type = this.#CALIBRATION_TONE_TYPE;
1568
+ gainNode.gain.value = gainValue;
1569
+
1570
+ oscilator.connect(gainNode);
1571
+ gainNode.connect(taperGainNode);
1572
+ const onsetCurve = this.createSCurveBuffer();
1573
+ taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
1574
+ taperGainNode.connect(offsetGainNode);
1575
+ const offsetCurve = this.createSCurveBuffer(false);
1576
+ offsetGainNode.gain.setValueCurveAtTime(
1577
+ offsetCurve,
1578
+ totalDuration - this.TAPER_SECS,
1579
+ this.TAPER_SECS
1580
+ );
1581
+ offsetGainNode.connect(audioContext.destination);
1582
+
1583
+ this.addCalibrationNode(oscilator);
1584
+ };
1585
+
1586
+ /**
1587
+ * Construct a Calibration Node with the calibration parameters.
1588
+ *
1589
+ * @private
1590
+ * @example
1591
+ */
1592
+ #createCalibrationNode = () => {
1593
+ const audioContext = this.makeNewSourceAudioContext();
1594
+ const oscilator = audioContext.createOscillator();
1595
+ const gainNode = audioContext.createGain();
1596
+
1597
+ oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
1598
+ oscilator.type = this.#CALIBRATION_TONE_TYPE;
1599
+ gainNode.gain.value = 0.04;
1600
+
1601
+ oscilator.connect(gainNode);
1602
+ gainNode.connect(audioContext.destination);
1603
+
1604
+ this.addCalibrationNode(oscilator);
1605
+ };
1606
+
1607
+ #playCalibrationAudioVolume = async () => {
1608
+ const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
1609
+
1610
+ this.calibrationNodes[0].start(0);
1611
+ this.calibrationNodes[0].stop(totalDuration);
1612
+ console.log(`Playing a buffer of ${this.CALIBRATION_TONE_DURATION} seconds of audio`);
1613
+ console.log(`Waiting a total of ${totalDuration} seconds`);
1614
+ await sleep(totalDuration);
1615
+ };
1616
+
1617
+ #sendToServerForProcessing = lCalib => {
1618
+ console.log('Sending data to server');
1619
+ let left = this.calibrateSound1000HzPreSec;
1620
+ let right = this.calibrateSound1000HzPreSec + this.calibrateSound1000HzSec;
1621
+ this.pyServerAPI
1622
+ .getVolumeCalibration({
1623
+ sampleRate: this.sourceSamplingRate,
1624
+ payload: this.#getTruncatedSignal(left, right),
1625
+ lCalib: lCalib,
1626
+ })
1627
+ .then(res => {
1628
+ if (this.outDBSPL === null) {
1629
+ this.incrementStatusBar();
1630
+ this.outDBSPL = res['outDbSPL'];
1631
+ this.outDBSPL1000 = res['outDbSPL1000'];
1632
+ this.THD = res['thd'];
1633
+ }
1634
+ })
1635
+ .catch(err => {
1636
+ console.warn(err);
1637
+ });
1638
+ };
1639
+
1640
+ startCalibrationVolume = async (stream, gainValues, lCalib, componentGainDBSPL) => {
1641
+ const trialIterations = gainValues.length;
1642
+ this.status_denominator += trialIterations;
1643
+ const thdValues = [];
1644
+ const inDBValues = [];
1645
+ let inDB = 0;
1646
+ const outDBSPLValues = [];
1647
+ const outDBSPL1000Values = [];
1648
+ let checkRec = false;
1649
+
1650
+ // do one calibration that will be discarded
1651
+ const soundLevelToDiscard = -60;
1652
+ const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);
1653
+ this.status =
1654
+ `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() +
1655
+ this.generateTemplate().toString();
1656
+ //this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});
1657
+ this.emit('update', {message: this.status});
1658
+
1659
+ do {
1660
+ // eslint-disable-next-line no-await-in-loop
1661
+ await this.volumeCalibrationSteps(
1662
+ stream,
1663
+ this.#playCalibrationAudioVolume,
1664
+ this.#createCalibrationToneWithGainValue,
1665
+ this.#sendToServerForProcessing,
1666
+ gainToDiscard,
1667
+ lCalib, //todo make this a class parameter
1668
+ checkRec
1669
+ );
1670
+ } while (this.outDBSPL === null);
1671
+ //reset the values
1672
+ //this.incrementStatusBar();
1673
+
1674
+ this.outDBSPL = null;
1675
+ this.outDBSPL = null;
1676
+ this.outDBSPL1000 = null;
1677
+ this.THD = null;
1678
+
1679
+ // run the calibration at different gain values provided by the user
1680
+ for (let i = 0; i < trialIterations; i++) {
1681
+ //convert gain to DB and add to inDB
1682
+ if (i == trialIterations - 1) {
1683
+ checkRec = 'loudest';
1684
+ }
1685
+ inDB = Math.log10(gainValues[i]) * 20;
1686
+ // precision to 1 decimal place
1687
+ inDB = Math.round(inDB * 10) / 10;
1688
+ inDBValues.push(inDB);
1689
+ console.log('next update');
1690
+ this.status =
1691
+ `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() +
1692
+ this.generateTemplate().toString();
1693
+ this.emit('update', {message: this.status});
1694
+ do {
1695
+ // eslint-disable-next-line no-await-in-loop
1696
+ await this.volumeCalibrationSteps(
1697
+ stream,
1698
+ this.#playCalibrationAudioVolume,
1699
+ this.#createCalibrationToneWithGainValue,
1700
+ this.#sendToServerForProcessing,
1701
+ gainValues[i],
1702
+ lCalib, //todo make this a class parameter
1703
+ checkRec
1704
+ );
1705
+ } while (this.outDBSPL === null);
1706
+ outDBSPL1000Values.push(this.outDBSPL1000);
1707
+ thdValues.push(this.THD);
1708
+ outDBSPLValues.push(this.outDBSPL);
1709
+
1710
+ this.outDBSPL = null;
1711
+ this.outDBSPL1000 = null;
1712
+ this.THD = null;
1713
+ }
1714
+
1715
+ // get the volume calibration parameters from the server
1716
+ const parameters = await this.pyServerAPI
1717
+ .getVolumeCalibrationParameters({
1718
+ inDBValues: inDBValues,
1719
+ outDBSPLValues: outDBSPL1000Values,
1720
+ lCalib: lCalib,
1721
+ componentGainDBSPL,
1722
+ })
1723
+ .then(res => {
1724
+ this.incrementStatusBar();
1725
+ return res;
1726
+ });
1727
+ const result = {
1728
+ parameters: parameters,
1729
+ inDBValues: inDBValues,
1730
+ outDBSPLValues: outDBSPLValues,
1731
+ outDBSPL1000Values: outDBSPL1000Values,
1732
+ thdValues: thdValues,
1733
+ };
1734
+
1735
+ return result;
1736
+ };
1737
+
1738
+ // function to write frq and gain to firebase database given speakerID
1739
+ writeFrqGain = async (speakerID, frq, gain, OEM) => {
1740
+ // freq and gain are too large to take samples 1 in every 100 samples
1741
+
1742
+ const sampledFrq = [];
1743
+ const sampledGain = [];
1744
+ for (let i = 0; i < frq.length; i += 100) {
1745
+ sampledFrq.push(frq[i]);
1746
+ sampledGain.push(gain[i]);
1747
+ }
1748
+
1749
+ const data = {Freq: sampledFrq, Gain: sampledGain};
1750
+
1751
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/linear`), data);
1752
+ };
1753
+
1754
+ // Function to Read frq and gain from firebase database given speakerID
1755
+ // returns an array of frq and gain if speakerID exists, returns null otherwise
1756
+
1757
+ readFrqGain = async (speakerID, OEM) => {
1758
+ const dbRef = ref(database);
1759
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/linear`));
1760
+ if (snapshot.exists()) {
1761
+ return snapshot.val();
1762
+ }
1763
+ return null;
1764
+ };
1765
+
1766
+ readGainat1000Hz = async (speakerID, OEM) => {
1767
+ const dbRef = ref(database);
1768
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/Gain1000`));
1769
+ if (snapshot.exists()) {
1770
+ return snapshot.val();
1771
+ }
1772
+ return null;
1773
+ };
1774
+
1775
+ writeGainat1000Hz = async (speakerID, gain, OEM) => {
1776
+ const data = {Gain: gain};
1777
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/Gain1000`), gain);
1778
+ };
1779
+
1780
+ writeIsSmartPhone = async (speakerID, isSmartPhone, OEM) => {
1781
+ const data = {isSmartPhone: isSmartPhone};
1782
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/isSmartPhone`), isSmartPhone);
1783
+ };
1784
+
1785
+ doesMicrophoneExist = async (speakerID, OEM) => {
1786
+ const dbRef = ref(database);
1787
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}`));
1788
+ if (snapshot.exists()) {
1789
+ return true;
1790
+ }
1791
+ return false;
1792
+ };
1793
+
1794
+ addMicrophoneInfo = async (speakerID, OEM, micInfo) => {
1795
+ // add to database if /info does not exist
1796
+ const dbRef = ref(database);
1797
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/info`));
1798
+ if (!snapshot.exists()) {
1799
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/info`), micInfo);
1800
+ }
1801
+ };
1802
+
1803
+ convertToDB = gain => {
1804
+ return Math.log10(gain) * 20;
1805
+ };
1806
+
1807
+ // Function to perform linear interpolation between two points
1808
+ interpolate(x, x0, y0, x1, y1) {
1809
+ return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);
1810
+ }
1811
+
1812
+ findGainatFrequency = (frequencies, gains, targetFrequency) => {
1813
+ // Find the index of the first frequency in the array greater than the target frequency
1814
+ let index = 0;
1815
+ while (index < frequencies.length && frequencies[index] < targetFrequency) {
1816
+ index++;
1817
+ }
1818
+
1819
+ // Handle cases when the target frequency is outside the range of the given data
1820
+ if (index === 0) {
1821
+ return gains[0];
1822
+ } else if (index === frequencies.length) {
1823
+ return gains[gains.length - 1];
1824
+ } else {
1825
+ // Interpolate the gain based on the surrounding frequencies
1826
+ const x0 = frequencies[index - 1];
1827
+ const y0 = gains[index - 1];
1828
+ const x1 = frequencies[index];
1829
+ const y1 = gains[index];
1830
+ return this.interpolate(targetFrequency, x0, y0, x1, y1);
1831
+ }
1832
+ };
1833
+
1834
+ // Example of how to use the writeFrqGain and readFrqGain functions
1835
+ // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);
1836
+ // Speaker1 is the speakerID you want to write to in the database
1837
+ // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));
1838
+ // MiniDSPUMIK_1 is the speakerID with some Data in the database
1839
+ //adding gainDBSPL
1840
+ startCalibration = async (
1841
+ stream,
1842
+ gainValues,
1843
+ lCalib = 104.92978421490648,
1844
+ componentIR = null,
1845
+ microphoneName = 'MiniDSP-UMIK1-711-4754-vertical',
1846
+ _calibrateSoundCheck = 'goal', //GOAL PASSed in by default
1847
+ isSmartPhone = false,
1848
+ _calibrateSoundBurstDb = 0.33,
1849
+ _calibrateSoundBurstRepeats = 2,
1850
+ _calibrateSoundBurstSec = 1,
1851
+ _calibrateSoundBurstsWarmup = 1,
1852
+ _calibrateSoundHz = 48000,
1853
+ _calibrateSoundIIRSec = 0.2,
1854
+ calibrateSound1000HzPreSec = 3.5,
1855
+ calibrateSound1000HzSec = 1.0,
1856
+ calibrateSound1000HzPostSec = 0.5,
1857
+ _calibrateSoundBackgroundSecs = 0,
1858
+ micManufacturer = '',
1859
+ micSerialNumber = '',
1860
+ micModelNumber = '',
1861
+ micModelName = ''
1862
+ ) => {
1863
+ this._calibrateSoundBurstDb = _calibrateSoundBurstDb;
1864
+ this.CALIBRATION_TONE_DURATION =
1865
+ calibrateSound1000HzPreSec + calibrateSound1000HzSec + calibrateSound1000HzPostSec;
1866
+ this.calibrateSound1000HzPreSec = calibrateSound1000HzPreSec;
1867
+ this.calibrateSound1000HzSec = calibrateSound1000HzSec;
1868
+ this.calibrateSound1000HzPostSec = calibrateSound1000HzPostSec;
1869
+ this.iirLength = Math.floor(_calibrateSoundIIRSec * this.sourceSamplingRate);
1870
+ console.log('device info:', this.deviceInfo);
1871
+ this.numMLSPerCapture = _calibrateSoundBurstRepeats;
1872
+ this.desired_time_per_mls = _calibrateSoundBurstSec;
1873
+ this.num_mls_to_skip = _calibrateSoundBurstsWarmup;
1874
+ this.desired_sampling_rate = _calibrateSoundHz;
1875
+ this._calibrateSoundBackgroundSecs = _calibrateSoundBackgroundSecs;
1876
+
1877
+ //feed calibration goal here
1878
+ this._calibrateSoundCheck = _calibrateSoundCheck;
1879
+ //check if a componentIR was given to the system, if it isn't check for the microphone. using dummy data here bc we need to
1880
+ //check the db based on the microphone currently connected
1881
+
1882
+ //new lCalib found at top of calibration files *1000hz, make sure to correct
1883
+ //based on zeroing of 1000hz, search for "*1000Hz"
1884
+ const ID = isSmartPhone ? micModelNumber : micSerialNumber;
1885
+ const OEM = isSmartPhone ? this.deviceInfo.OEM : micManufacturer;
1886
+ // const ID = "711-4754";
1887
+ // const OEM = "MiniDSP";
1888
+ const micInfo = {
1889
+ micModelName: isSmartPhone ? micModelName : microphoneName,
1890
+ OEM: OEM,
1891
+ ID: ID,
1892
+ HardwareName: isSmartPhone ? this.deviceInfo.hardwarename : microphoneName,
1893
+ hardwareFamily: isSmartPhone ? this.deviceInfo.hardwarefamily : microphoneName,
1894
+ HardwareModel: isSmartPhone ? this.deviceInfo.hardwaremodel : microphoneName,
1895
+ PlatformName: isSmartPhone ? this.deviceInfo.platformname : 'N/A',
1896
+ PlatformVersion: isSmartPhone ? this.deviceInfo.platformversion : 'N/A',
1897
+ DeviceType: isSmartPhone ? this.deviceInfo.devicetype : 'N/A',
1898
+ };
1899
+ this.addMicrophoneInfo(ID, OEM, micInfo);
1900
+ if (componentIR == null) {
1901
+ //mode 'ir'
1902
+ //global variable this.componentIR must be set
1903
+ this.componentIR = await this.readFrqGain(ID, OEM).then(data => {
1904
+ return data;
1905
+ });
1906
+
1907
+ lCalib = await this.readGainat1000Hz(ID, OEM);
1908
+ micInfo['gainDBSPL'] = lCalib;
1909
+ // this.componentGainDBSPL = this.convertToDB(lCalib);
1910
+ this.componentGainDBSPL = lCalib;
1911
+ //TODO: if this call to database is unknown, cannot perform experiment => return false
1912
+ if (this.componentIR == null) {
1913
+ this.status =
1914
+ `Microphone (${OEM},${ID}) is not found in the database. Please add it to the database.`.toString();
1915
+ this.emit('update', {message: this.status});
1916
+ return false;
1917
+ }
1918
+ } else {
1919
+ this.componentIR = componentIR;
1920
+ lCalib = this.findGainatFrequency(this.componentIR.Freq, this.componentIR.Gain, 1000);
1921
+ // this.componentGainDBSPL = this.convertToDB(lCalib);
1922
+ this.componentGainDBSPL = lCalib;
1923
+ await this.writeIsSmartPhone(ID, isSmartPhone, OEM);
1924
+ }
1925
+
1926
+ this.oldComponentIR = this.componentIR;
1927
+
1928
+ let volumeResults = await this.startCalibrationVolume(
1929
+ stream,
1930
+ gainValues,
1931
+ lCalib,
1932
+ this.componentGainDBSPL
1933
+ );
1934
+
1935
+ let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);
1936
+ impulseResponseResults['background_noise'] = this.background_noise;
1937
+ if (componentIR != null) {
1938
+ //insert Freq and Gain from this.componentIR into db
1939
+ await this.writeFrqGain(
1940
+ ID,
1941
+ impulseResponseResults.componentIR.Freq,
1942
+ impulseResponseResults.componentIR.Gain,
1943
+ OEM
1944
+ );
1945
+ micInfo['gainDBSPL'] = impulseResponseResults.parameters.gainDBSPL;
1946
+ await this.writeGainat1000Hz(ID, micInfo['gainDBSPL'], OEM);
1947
+ }
1948
+
1949
+ const total_results = {...volumeResults, ...impulseResponseResults};
1950
+ total_results['filteredMLSRange'] = this.filteredMLSRange;
1951
+ total_results['micInfo'] = micInfo;
1952
+ console.log('total results');
1953
+ console.log(total_results);
1954
+ return total_results;
1955
+ };
1956
+ }
1957
+
1958
+ export default Combination;