speaker-calibration 2.2.115 → 2.2.117

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/.gitignore +81 -0
  4. package/.prettierignore +69 -69
  5. package/.prettierrc +14 -14
  6. package/LICENSE +20 -20
  7. package/README.md +133 -133
  8. package/__mocks__/fileMock.js +1 -1
  9. package/__mocks__/styleMock.js +1 -1
  10. package/babel.config.js +3 -3
  11. package/coverage/clover.xml +71 -71
  12. package/coverage/coverage-final.json +224 -224
  13. package/coverage/lcov-report/PythonServerInterface.js.html +265 -265
  14. package/coverage/lcov-report/base.css +354 -354
  15. package/coverage/lcov-report/block-navigation.js +82 -82
  16. package/coverage/lcov-report/index.html +123 -123
  17. package/coverage/lcov-report/prettify.css +101 -101
  18. package/coverage/lcov-report/prettify.js +937 -937
  19. package/coverage/lcov-report/sorter.js +189 -189
  20. package/coverage/lcov-report/src/index.html +121 -121
  21. package/coverage/lcov-report/src/server/PythonServerInterface.js.html +268 -268
  22. package/coverage/lcov-report/src/server/index.html +123 -123
  23. package/coverage/lcov-report/src/tasks/audioCalibrator.js.html +499 -499
  24. package/coverage/lcov-report/src/tasks/audioRecorder.js.html +412 -412
  25. package/coverage/lcov-report/src/tasks/index.html +143 -143
  26. package/coverage/lcov-report/src/tasks/volume/index.html +123 -123
  27. package/coverage/lcov-report/src/tasks/volume/volume.js.html +409 -409
  28. package/coverage/lcov-report/src/utils.js.html +172 -172
  29. package/coverage/lcov.info +91 -91
  30. package/dist/example/fetch-languages-sheets.js +77 -77
  31. package/dist/example/i18n.js +35846 -35846
  32. package/dist/example/index.html +47 -47
  33. package/dist/example/listener.html +62 -62
  34. package/dist/example/listener.js +129 -129
  35. package/dist/example/server.js +51 -51
  36. package/dist/example/speaker.html +145 -145
  37. package/dist/example/speakerUI.js +273 -273
  38. package/dist/example/styles.css +92 -92
  39. package/dist/main.js +17 -17
  40. package/dist/mlsGen.js +6814 -6814
  41. package/dist/mlsGen.wasm +0 -0
  42. package/dist/package-lock.json +1018 -1018
  43. package/dist/package.json +18 -18
  44. package/doc/AudioCalibrator.html +417 -417
  45. package/doc/AudioPeer.html +251 -251
  46. package/doc/AudioRecorder.html +195 -195
  47. package/doc/ImpulseResponse.html +215 -215
  48. package/doc/Listener.html +308 -308
  49. package/doc/MlsGenInterface.html +226 -226
  50. package/doc/MyEventEmitter.html +274 -274
  51. package/doc/PythonServerAPI.html +109 -109
  52. package/doc/Speaker.html +276 -276
  53. package/doc/Takes%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +128 -128
  54. package/doc/Takes%20the%20url%20of%20the%20current%20site%0Aand%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +138 -138
  55. package/doc/Takes%20the%20url%20of%20the%20current%20site%20and%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +137 -137
  56. package/doc/Volume.html +88 -88
  57. package/doc/audioCalibrator.js.html +179 -179
  58. package/doc/audioPeer.js.html +175 -175
  59. package/doc/audioRecorder.js.html +163 -163
  60. package/doc/creates%20a%20new%20AudioRecorder%20instance.%20%0ASets%20up%20the%20audio%20context%20and%20file%20reader..html +114 -114
  61. package/doc/fonts/OpenSans-Bold-webfont.svg +1829 -1829
  62. package/doc/fonts/OpenSans-BoldItalic-webfont.svg +1829 -1829
  63. package/doc/fonts/OpenSans-Italic-webfont.svg +1829 -1829
  64. package/doc/fonts/OpenSans-Light-webfont.svg +1830 -1830
  65. package/doc/fonts/OpenSans-LightItalic-webfont.svg +1834 -1834
  66. package/doc/fonts/OpenSans-Regular-webfont.svg +1830 -1830
  67. package/doc/global.html +308 -308
  68. package/doc/index.html +58 -58
  69. package/doc/listener.js.html +170 -170
  70. package/doc/mlsGen_mlsGenInterface.js.html +117 -117
  71. package/doc/myEventEmitter.js.html +124 -124
  72. package/doc/peer-connection_audioPeer.js.html +188 -188
  73. package/doc/peer-connection_listener.js.html +311 -311
  74. package/doc/peer-connection_speaker.js.html +381 -381
  75. package/doc/scripts/linenumber.js +25 -25
  76. package/doc/scripts/prettify/Apache-License-2.0.txt +202 -202
  77. package/doc/scripts/prettify/lang-css.js +24 -24
  78. package/doc/scripts/prettify/prettify.js +640 -640
  79. package/doc/server_PythonServerAPI.js.html +160 -160
  80. package/doc/speaker.js.html +248 -248
  81. package/doc/styles/jsdoc-default.css +371 -371
  82. package/doc/styles/prettify-jsdoc.css +111 -111
  83. package/doc/styles/prettify-tomorrow.css +163 -163
  84. package/doc/tasks_audioCalibrator.js.html +207 -207
  85. package/doc/tasks_audioRecorder.js.html +190 -190
  86. package/doc/tasks_impulse-response_impulseResponse.js.html +442 -442
  87. package/doc/tasks_impulse-response_mlsGen_mlsGenInterface.js.html +175 -175
  88. package/doc/tasks_volume_volume.js.html +185 -185
  89. package/doc/utils.js.html +105 -105
  90. package/jest.config.js +173 -173
  91. package/netlify.toml +26 -26
  92. package/package.json +69 -69
  93. package/src/config/firebase.js +26 -26
  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 +327 -327
  99. package/src/peer-connection/peerErrors.js +25 -25
  100. package/src/peer-connection/speaker.js +485 -485
  101. package/src/server/PythonServerAPI.js +673 -673
  102. package/src/tasks/audioCalibrator.js +310 -310
  103. package/src/tasks/audioRecorder.js +301 -301
  104. package/src/tasks/combination/combination.js +2508 -2504
  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 +135 -135
  118. package/webpack.config.js +37 -37
  119. package/makefile +0 -74
@@ -1,2504 +1,2508 @@
1
- import AudioCalibrator from '../audioCalibrator';
2
-
3
- import {
4
- sleep,
5
- csvToArray,
6
- saveToCSV,
7
- saveToJSON,
8
- findMinValue,
9
- findMaxValue,
10
- getCurrentTimeString,
11
- standardDeviation,
12
- interpolate,
13
- } from '../../utils';
14
- import database from '../../config/firebase';
15
- import {ref, set, get, child} from 'firebase/database';
16
- import {
17
- doc,
18
- getDoc,
19
- collection,
20
- addDoc,
21
- updateDoc,
22
- setDoc,
23
- arrayUnion,
24
- getDocs,
25
- query,
26
- where,
27
- } from 'firebase/firestore';
28
-
29
- /**
30
- *
31
- */
32
- class Combination extends AudioCalibrator {
33
- /**
34
- * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.
35
- *
36
- * @param {Object<boolean, number, number, number>} calibratorParams - paramter object
37
- * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures
38
- * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated
39
- * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform
40
- * @param {number} [calibratorParams.numMLSPerCapture = 2] - number of bursts of MLS per capture
41
- */
42
- constructor({
43
- download = false,
44
- mlsOrder = 18,
45
- numCaptures = 3,
46
- numMLSPerCapture = 2,
47
- lowHz = 20,
48
- highHz = 10000,
49
- }) {
50
- super(numCaptures, numMLSPerCapture);
51
- this.#mlsOrder = parseInt(mlsOrder, 10);
52
- this.#P = 2 ** mlsOrder - 1;
53
- this.#download = download;
54
- this.#mls = [];
55
- this.#lowHz = lowHz;
56
- this.#highHz = highHz;
57
- }
58
-
59
- /** @private */
60
- stepNum = 0;
61
-
62
- /** @private */
63
- totalSteps = 25;
64
-
65
- /** @private */
66
- #download;
67
-
68
- /** @private */
69
- #mlsGenInterface;
70
-
71
- /** @private */
72
- #mlsBufferView;
73
-
74
- /** @private */
75
- componentInvertedImpulseResponse = null;
76
-
77
- /** @private */
78
- systemInvertedImpulseResponse = null;
79
-
80
- //averaged and subtracted ir returned from calibration used to calculated iir
81
- /** @private */
82
- ir = null;
83
-
84
- /** @private */
85
- impulseResponses = [];
86
-
87
- /** @private */
88
- #mlsOrder;
89
-
90
- /** @private */
91
- #lowHz;
92
-
93
- /** @private */
94
- #highHz;
95
-
96
- /** @private */
97
- #mls;
98
-
99
- /** @private */
100
- #P;
101
-
102
- /** @private */
103
- #audioContext;
104
-
105
- /** @private */
106
- TAPER_SECS = 5;
107
-
108
- /** @private */
109
- offsetGainNode;
110
-
111
- /** @private */
112
- componentConvolution;
113
-
114
- /** @private */
115
- componentIROrigin = {
116
- Freq: [],
117
- Gain: [],
118
- };
119
-
120
- /** @private */
121
- systemConvolution;
122
-
123
- ////////////////////////volume
124
- /** @private */
125
- #CALIBRATION_TONE_FREQUENCY = 1000; // Hz
126
-
127
- /** @private */
128
- #CALIBRATION_TONE_TYPE = 'sine';
129
-
130
- CALIBRATION_TONE_DURATION = 5; // seconds
131
- calibrateSound1000HzPreSec = 3.5;
132
- calibrateSound1000HzSec = 1.0;
133
- calibrateSound1000HzPostSec = 0.5;
134
-
135
- /** @private */
136
- outDBSPL = null;
137
- THD = null;
138
- outDBSPL1000 = null;
139
-
140
- /** @private */
141
- TAPER_SECS = 0.01; // seconds
142
-
143
- /** @private */
144
- status_denominator = 8;
145
-
146
- /** @private */
147
- status_numerator = 0;
148
-
149
- /** @private */
150
- percent_complete = 0;
151
-
152
- /** @private */
153
- status = ``;
154
-
155
- /**@private */
156
- 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>`;
157
-
158
- /**@private */
159
- componentIR = null;
160
-
161
- /**@private */
162
- oldComponentIR = null;
163
-
164
- /**@private */
165
- systemIR = null;
166
-
167
- /**@private */
168
- _calibrateSoundCheck = '';
169
-
170
- deviceType = null;
171
-
172
- deviceName = null;
173
-
174
- deviceInfo = null;
175
-
176
- desired_time_per_mls = 0;
177
-
178
- num_mls_to_skip = 0;
179
-
180
- desired_sampling_rate = 0;
181
-
182
- #currentConvolution = [];
183
-
184
- mode = 'unfiltered';
185
-
186
- sourceNode;
187
-
188
- autocorrelations = [];
189
-
190
- iirLength = 0;
191
-
192
- irLength = 0;
193
-
194
- componentInvertedImpulseResponseNoBandpass = [];
195
-
196
- componentIRInTimeDomain = [];
197
-
198
- systemInvertedImpulseResponseNoBandpass = [];
199
-
200
- _calibrateSoundBackgroundSecs;
201
-
202
- _calibrateSoundSmoothOctaves;
203
-
204
- background_noise = {};
205
-
206
- numSuccessfulBackgroundCaptured;
207
-
208
- _calibrateSoundBurstDb;
209
-
210
- SDofFilteredRange = {
211
- component: undefined,
212
- system: undefined,
213
- };
214
-
215
- transducerType = 'Loudspeaker';
216
-
217
- componentIRPhase = [];
218
-
219
- systemIRPhase = [];
220
-
221
- webAudioDeviceNames = {loudspeaker: '', microphone: '', loudspeakerText: '', microphoneText: ''};
222
-
223
- recordingChecks = {
224
- volume: {},
225
- unfiltered: [],
226
- system: [],
227
- component: [],
228
- };
229
-
230
- inDB;
231
-
232
- soundCheck = '';
233
-
234
- filteredMLSRange = {
235
- component: {
236
- Min: null,
237
- Max: null,
238
- },
239
- system: {
240
- Min: null,
241
- Max: null,
242
- },
243
- };
244
-
245
- /** @private */
246
- timeStamp = [];
247
-
248
- /** @private */
249
- startTime;
250
-
251
- restartCalibration = false;
252
-
253
- /**generate string template that gets reevaluated as variable increases */
254
- generateTemplate = () => {
255
- if (this.percent_complete > 100) {
256
- this.percent_complete = 100;
257
- }
258
- let MLSsd = '';
259
- let componentSD = '';
260
- let systemSD = '';
261
- const reportWebAudioNames = `<br>${this.webAudioDeviceNames.loudspeakerText} <br> ${this.webAudioDeviceNames.microphoneText}`;
262
- const reportParameters = `<br> Sampling: Loudspeaker ${this.sourceSamplingRate} Hz, Microphone ${this.sinkSamplingRate} Hz, ${this.sampleSize} bits`;
263
- if (this.recordingChecks['unfiltered'].length > 0) {
264
- MLSsd = `<br> Recorded MLS power SD: ${this.recordingChecks['unfiltered'][0].sd} dB`;
265
- }
266
- if (this.SDofFilteredRange['system']) {
267
- systemSD = `<br> Loudspeaker+Microphone correction SD: ${this.SDofFilteredRange['system']} dB`;
268
- }
269
- if (this.SDofFilteredRange['component']) {
270
- componentSD = `<br> ${this.transducerType} correction SD: ${this.SDofFilteredRange['component']} dB`;
271
- }
272
- 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>`;
273
- return reportWebAudioNames + reportParameters + MLSsd + systemSD + componentSD + template;
274
- };
275
-
276
- /** increment numerator and percent for status bar */
277
- incrementStatusBar = () => {
278
- this.status_numerator += 1;
279
- this.percent_complete = (this.status_numerator / this.status_denominator) * 100;
280
- };
281
-
282
- setDeviceType = deviceType => {
283
- this.deviceType = deviceType;
284
- };
285
-
286
- setDeviceName = deviceName => {
287
- this.deviceName = deviceName;
288
- };
289
-
290
- setDeviceInfo = deviceInfo => {
291
- this.deviceInfo = deviceInfo;
292
- };
293
-
294
- /** .
295
- * .
296
- * .
297
- * Sends all the computed impulse responses to the backend server for processing
298
- *
299
- * @returns sets the resulting inverted impulse response to the class property
300
- * @example
301
- */
302
- sendSystemImpulseResponsesToServerForProcessing = async () => {
303
- this.addTimeStamp('Get system iir');
304
- const computedIRs = await Promise.all(this.impulseResponses);
305
- const filteredComputedIRs = computedIRs.filter(element => {
306
- return element != undefined;
307
- }); //log any errors that are found in this step
308
- const mls = this.#mls;
309
- const lowHz = this.#lowHz; //gain of 1 below cutoff, need gain of 0
310
- const highHz = this.#highHz; //check error for anything other than 10 kHz
311
- const iirLength = this.iirLength;
312
- const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
313
- this.stepNum += 1;
314
- console.log('send impulse responses to server: ' + this.stepNum);
315
- this.status =
316
- `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
317
- this.emit('update', {message: this.status});
318
- return this.pyServerAPI
319
- .getSystemInverseImpulseResponseWithRetry({
320
- payload: filteredComputedIRs.slice(0, this.numCaptures),
321
- mls,
322
- lowHz,
323
- highHz,
324
- iirLength,
325
- num_periods,
326
- sampleRate: this.sourceSamplingRate || 96000,
327
- calibrateSoundBurstDb: this._calibrateSoundBurstDb,
328
- })
329
- .then(res => {
330
- console.log(res);
331
- this.stepNum += 1;
332
- console.log('got impulse response ' + this.stepNum);
333
- this.incrementStatusBar();
334
- this.status =
335
- `All Hz Calibration: done computing the IIR...`.toString() +
336
- this.generateTemplate().toString();
337
- this.emit('update', {message: this.status});
338
- this.systemInvertedImpulseResponse = res['iir'];
339
- this.systemIR = res['ir'];
340
- this.systemConvolution = res['convolution'];
341
- this.systemInvertedImpulseResponseNoBandpass = res['iirNoBandpass'];
342
- })
343
- .catch(err => {
344
- console.error(err);
345
- });
346
- };
347
-
348
- /** .
349
- * .
350
- * .
351
- * Sends all the computed impulse responses to the backend server for processing
352
- *
353
- * @returns sets the resulting inverted impulse response to the class property
354
- * @example
355
- */
356
- sendComponentImpulseResponsesToServerForProcessing = async () => {
357
- this.addTimeStamp('Get component iir');
358
- const computedIRs = await Promise.all(this.impulseResponses);
359
- const filteredComputedIRs = computedIRs.filter(element => {
360
- return element != undefined;
361
- });
362
- const componentIRGains = this.componentIR['Gain'];
363
- const componentIRFreqs = this.componentIR['Freq'];
364
- const mls = this.#mls;
365
- const lowHz = this.#lowHz;
366
- const iirLength = this.iirLength;
367
- const irLength = this.irLength;
368
- const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
369
- const highHz = this.#highHz;
370
- this.stepNum += 1;
371
- console.log('send impulse responses to server: ' + this.stepNum);
372
- this.status =
373
- `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
374
- this.emit('update', {message: this.status});
375
- return this.pyServerAPI
376
- .getComponentInverseImpulseResponseWithRetry({
377
- payload: filteredComputedIRs.slice(0, this.numCaptures),
378
- mls,
379
- lowHz,
380
- highHz,
381
- iirLength,
382
- componentIRGains,
383
- componentIRFreqs,
384
- num_periods,
385
- sampleRate: this.sourceSamplingRate || 96000,
386
- calibrateSoundBurstDb: this._calibrateSoundBurstDb,
387
- irLength,
388
- calibrateSoundSmoothOctaves: this._calibrateSoundSmoothOctaves,
389
- })
390
- .then(res => {
391
- console.log(res);
392
- this.stepNum += 1;
393
- console.log('got impulse response ' + this.stepNum);
394
- this.incrementStatusBar();
395
- this.status =
396
- `All Hz Calibration: done computing the IIR...`.toString() +
397
- this.generateTemplate().toString();
398
- this.emit('update', {message: this.status});
399
- this.componentInvertedImpulseResponse = res['iir'];
400
- this.componentIR['Gain'] = res['ir'];
401
- this.componentIR['Freq'] = res['frequencies'];
402
- this.componentIRPhase = res['component_angle'];
403
- this.systemIRPhase = res['system_angle'];
404
- this.componentIROrigin['Freq'] = res['frequencies'];
405
- this.componentIROrigin['Gain'] = res['irOrigin'];
406
- this.componentConvolution = res['convolution'];
407
- this.componentInvertedImpulseResponseNoBandpass = res['iirNoBandpass'];
408
- this.componentIRInTimeDomain = res['irTime'];
409
- })
410
- .catch(err => {
411
- // this.emit('InvertedImpulseResponse', {res: false});
412
- console.error(err);
413
- });
414
- };
415
-
416
- sendBackgroundRecording = () => {
417
- const allSignals = this.getAllBackgroundRecordings();
418
- const numSignals = allSignals.length;
419
- const background_rec_whole = allSignals[numSignals - 1];
420
- const fraction = 0.5 / (this._calibrateSoundBackgroundSecs + 0.5);
421
- // Calculate the starting index for slicing the array
422
- const startIndex = Math.round(fraction * background_rec_whole.length);
423
- // Slice the array from the calculated start index to the end of the array
424
- const background_rec = background_rec_whole.slice(startIndex);
425
- console.log('Sending background recording to server for processing');
426
- this.addTimeStamp('Get background PSD');
427
- this.pyServerAPI
428
- .getBackgroundNoisePSDWithRetry({
429
- background_rec,
430
- sampleRate: this.sourceSamplingRate || 96000,
431
- })
432
- .then(res => {
433
- if (this.numSuccessfulBackgroundCaptured < 1) {
434
- this.numSuccessfulBackgroundCaptured += 1;
435
- //storing all background data in background_psd object
436
- this.background_noise['x_background'] = res['x_background'];
437
- this.background_noise['y_background'] = res['y_background'];
438
- this.background_noise['recording'] = background_rec;
439
- }
440
- })
441
- .catch(err => {
442
- console.error(err);
443
- });
444
- };
445
-
446
- /** .
447
- * .
448
- * .
449
- * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing
450
- *
451
- * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed
452
- * @example
453
- */
454
- sendRecordingToServerForProcessing = async signalCsv => {
455
- const allSignals = this.getAllUnfilteredRecordedSignals();
456
- console.log(
457
- 'Obtaining last all hz unfiltered recording from #allHzUnfilteredRecordings to send to server for processing'
458
- );
459
- const numSignals = allSignals.length;
460
- const mls = this.#mlsBufferView;
461
- const payload =
462
- signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
463
- console.log('sending rec');
464
- this.stepNum += 1;
465
- console.log('send rec ' + this.stepNum);
466
- this.status =
467
- `All Hz Calibration Step: computing the IR of the last recording...`.toString() +
468
- this.generateTemplate().toString();
469
- this.emit('update', {message: this.status});
470
- if (this.isCalibrating) return null;
471
- await this.pyServerAPI
472
- .allHzPowerCheck({
473
- payload,
474
- sampleRate: this.sourceSamplingRate || 96000,
475
- binDesiredSec: this._calibrateSoundPowerBinDesiredSec,
476
- burstSec: this.desired_time_per_mls,
477
- })
478
- .then(result => {
479
- if (result) {
480
- if (result['sd'] < this._calibrateSoundPowerDbSDToleratedDb) {
481
- this.recordingChecks['unfiltered'].push(result);
482
- this.impulseResponses.push(
483
- this.pyServerAPI
484
- .getImpulseResponse({
485
- sampleRate: this.sourceSamplingRate || 96000,
486
- payload,
487
- mls,
488
- P: this.#P, //get rid of this
489
- numPeriods: this.numMLSPerCapture,
490
- })
491
- .then(res => {
492
- if (this.numSuccessfulCaptured < this.numCaptures) {
493
- this.numSuccessfulCaptured += 1;
494
- console.log('num succ capt: ' + this.numSuccessfulCaptured);
495
- this.stepNum += 1;
496
- console.log('got impulse response ' + this.stepNum);
497
- this.incrementStatusBar();
498
- this.status =
499
- `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +
500
- this.generateTemplate().toString();
501
- this.emit('update', {
502
- message: this.status,
503
- });
504
- this.autocorrelations.push(res['autocorrelation']);
505
- return res['ir'];
506
- }
507
- })
508
- .catch(err => {
509
- console.error(err);
510
- })
511
- );
512
- } else if (result['sd'] > this._calibrateSoundPowerDbSDToleratedDb) {
513
- this.clearLastUnfilteredRecordedSignals();
514
- console.log('unfiltered rec', this.getAllUnfilteredRecordedSignals.length);
515
- }
516
- }
517
- })
518
- .catch(err => {
519
- console.error(err);
520
- });
521
- };
522
-
523
- /**
524
- * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number
525
- * of MLS periods defined in the constructor.
526
- *
527
- * @example
528
- */
529
- #awaitDesiredMLSLength = async () => {
530
- // seconds per MLS = P / SR
531
- // await N * P / SR
532
- this.stepNum += 1;
533
- console.log('await desired length ' + this.stepNum);
534
- this.status =
535
- `All Hz Calibration: sampling the calibration signal...`.toString() +
536
- `\niteration ${this.stepNum}` +
537
- this.generateTemplate();
538
- this.emit('update', {
539
- message: this.status,
540
- });
541
- let time_to_wait = 0;
542
- if (this.mode === 'unfiltered') {
543
- //unfiltered
544
- time_to_wait = (this.#mls.length / this.sourceSamplingRate) * this.numMLSPerCapture;
545
- time_to_wait = time_to_wait * 1.1;
546
- } else if (this.mode === 'filtered') {
547
- //filtered
548
- // time_to_wait =
549
- // (this.#currentConvolution.length / this.sourceSamplingRate) *
550
- // (this.numMLSPerCapture / (this.num_mls_to_skip + this.numMLSPerCapture));
551
- time_to_wait =
552
- (this.#currentConvolution.length / this.sourceSamplingRate) * this.numMLSPerCapture;
553
- time_to_wait = time_to_wait * 1.1;
554
- } else {
555
- throw new Error('Mode broke in awaitDesiredMLSLength');
556
- }
557
-
558
- await sleep(time_to_wait);
559
- };
560
-
561
- /**
562
- * Passed to the background noise recording function, awaits the desired amount of seconds to capture the desired number
563
- * of seconds of background noise
564
- *
565
- * @example
566
- */
567
- #awaitBackgroundNoiseRecording = async () => {
568
- console.log(
569
- 'Waiting ' + this._calibrateSoundBackgroundSecs + ' second(s) to record background noise'
570
- );
571
- let time_to_wait = this._calibrateSoundBackgroundSecs + 0.5;
572
- await sleep(time_to_wait);
573
- };
574
-
575
- /** .
576
- * .
577
- * .
578
- * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state
579
- *
580
- * @example
581
- */
582
- #awaitSignalOnset = async () => {
583
- this.stepNum += 1;
584
- console.log('await signal onset ' + this.stepNum);
585
- this.status =
586
- `All Hz Calibration: waiting for the signal to stabilize...`.toString() +
587
- this.generateTemplate();
588
- this.emit('update', {
589
- message: this.status,
590
- });
591
- let number_of_bursts_to_skip = this.num_mls_to_skip;
592
- let time_to_sleep = 0;
593
- if (this.mode === 'unfiltered') {
594
- time_to_sleep = (this.#mls.length / this.sourceSamplingRate) * number_of_bursts_to_skip;
595
- } else if (this.mode === 'filtered') {
596
- console.log(this.#currentConvolution.length);
597
- // time_to_sleep =
598
- // (this.#currentConvolution.length / this.sourceSamplingRate) *
599
- // (number_of_bursts_to_skip / (number_of_bursts_to_skip + this.numMLSPerCapture));
600
- time_to_sleep =
601
- (this.#currentConvolution.length / this.sourceSamplingRate) * number_of_bursts_to_skip;
602
- } else {
603
- throw new Error('Mode broke in awaitSignalOnset');
604
- }
605
- await sleep(time_to_sleep);
606
- };
607
-
608
- /**
609
- * Called immediately after a recording is captured. Used to process the resulting signal
610
- * whether by sending the result to a server or by computing a result locally.
611
- *
612
- * @example
613
- */
614
- #afterMLSRecord = () => {
615
- console.log('after record');
616
- this.sendRecordingToServerForProcessing();
617
- };
618
-
619
- #afterMLSwIIRRecord = async () => {
620
- await this.checkPowerVariation();
621
- };
622
-
623
- /** .
624
- * .
625
- * .
626
- * Created an S Curver Buffer to taper the signal onset
627
- *
628
- * @param {*} length
629
- * @param {*} phase
630
- * @returns
631
- * @example
632
- */
633
- static createSCurveBuffer = (length, phase) => {
634
- const curve = new Float32Array(length);
635
- let i;
636
- for (i = 0; i < length; i += 1) {
637
- // scale the curve to be between 0-1
638
- curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;
639
- }
640
- return curve;
641
- };
642
-
643
- static createInverseSCurveBuffer = (length, phase) => {
644
- const curve = new Float32Array(length);
645
- let i;
646
- let j = length - 1;
647
- for (i = 0; i < length; i += 1) {
648
- // scale the curve to be between 0-1
649
- curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;
650
- j -= 1;
651
- }
652
- return curve;
653
- };
654
-
655
- /**
656
- * Construct a Calibration Node with the calibration parameters.
657
- *
658
- * @param dataBuffer
659
- * @private
660
- * @example
661
- */
662
- #createCalibrationNodeFromBuffer = dataBuffer => {
663
- console.log('length databuffer');
664
- console.log(dataBuffer.length);
665
- if (!this.sourceAudioContext) {
666
- this.makeNewSourceAudioContext();
667
- }
668
-
669
- const buffer = this.sourceAudioContext.createBuffer(
670
- 1, // number of channels
671
- dataBuffer.length,
672
- this.sourceAudioContext.sampleRate // sample rate
673
- );
674
-
675
- const data = buffer.getChannelData(0); // get data
676
-
677
- // fill the buffer with our data
678
- try {
679
- for (let i = 0; i < dataBuffer.length; i += 1) {
680
- data[i] = dataBuffer[i];
681
- }
682
- } catch (error) {
683
- console.error(error);
684
- }
685
-
686
- this.sourceNode = this.sourceAudioContext.createBufferSource();
687
-
688
- this.sourceNode.buffer = buffer;
689
-
690
- if (this.mode === 'filtered') {
691
- //used to not loop filtered
692
- this.sourceNode.loop = true;
693
- } else {
694
- this.sourceNode.loop = true;
695
- }
696
-
697
- this.sourceNode.connect(this.sourceAudioContext.destination);
698
-
699
- this.addCalibrationNode(this.sourceNode);
700
- };
701
-
702
- /**
703
- * Given a data buffer, creates the required calibration node
704
- *
705
- * @param {*} dataBufferArray
706
- * @example
707
- */
708
- #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {
709
- if (dataBufferArray.length === 1) {
710
- this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);
711
- } else {
712
- throw new Error('The length of the data buffer array must be 1');
713
- }
714
- };
715
-
716
- /**
717
- * Creates an audio context and plays it for a few seconds.
718
- *
719
- * @private
720
- * @returns - Resolves when the audio is done playing.
721
- * @example
722
- */
723
- #playCalibrationAudio = () => {
724
- this.addTimeStamp('Play unfiltered mls');
725
- this.calibrationNodes[0].start(0);
726
- this.status = ``;
727
- if (this.mode === 'unfiltered') {
728
- console.log('mls', this.#mls); // before multiplied by calibrateSoundBurstDb
729
- console.log('mls buffer view', this.#mlsBufferView); // after multiplied by calibrateSoundBurstDb
730
- console.log('play calibration audio ' + this.stepNum);
731
- this.status =
732
- `All Hz Calibration: playing the calibration tone...`.toString() +
733
- this.generateTemplate().toString();
734
- } else if (this.mode === 'filtered') {
735
- console.log('play convolved audio ' + this.stepNum);
736
- this.status =
737
- `All Hz Calibration: playing the convolved calibration tone...`.toString() +
738
- this.generateTemplate().toString();
739
- } else {
740
- throw new Error('Mode is incorrect');
741
- }
742
- this.emit('update', {message: this.status});
743
- this.stepNum += 1;
744
- console.log('sink sampling rate');
745
- console.log(this.sinkSamplingRate);
746
- console.log('source sampling rate');
747
- console.log(this.sourceSamplingRate);
748
- console.log('sample size');
749
- console.log(this.sampleSize);
750
- };
751
-
752
- /** .
753
- * .
754
- * .
755
- * Stops the audio with tapered offset
756
- *
757
- * @example
758
- */
759
- stopCalibrationAudio = () => {
760
- if (this.calibrationNodes.length === 0) {
761
- return;
762
- }
763
- this.calibrationNodes[0].stop(0);
764
- this.calibrationNodes = [];
765
- if (this.sourceNode) this.sourceNode.disconnect();
766
- this.stepNum += 1;
767
- console.log('stop calibration audio ' + this.stepNum);
768
- this.status =
769
- `All Hz Calibration: stopping the calibration tone...`.toString() +
770
- this.generateTemplate().toString();
771
- this.emit('update', {message: this.status});
772
- };
773
-
774
- playMLSwithIIR = async (stream, convolution) => {
775
- let checkRec = false;
776
- this.mode = 'filtered';
777
- console.log('play mls with iir');
778
- //this.invertedImpulseResponse = iir
779
-
780
- await this.calibrationSteps(
781
- stream,
782
- this.#playCalibrationAudio, // play audio func (required)
783
- this.#createCalibrationNodeFromBuffer(convolution), // before play func
784
- this.#awaitSignalOnset, // before record
785
- () => this.numSuccessfulCaptured < 1,
786
- this.#awaitDesiredMLSLength, // during record
787
- this.#afterMLSwIIRRecord, // after record
788
- this.mode,
789
- checkRec
790
- );
791
- };
792
-
793
- bothSoundCheck = async stream => {
794
- let iir_ir_and_plots;
795
- this.#currentConvolution = this.componentConvolution;
796
- this.filteredMLSRange.component.Min = findMinValue(this.#currentConvolution);
797
- this.filteredMLSRange.component.Max = findMaxValue(this.#currentConvolution);
798
- this.addTimeStamp('Play MLS with component IIR');
799
- this.soundCheck = 'component';
800
- if (this.isCalibrating) return null;
801
- await this.playMLSwithIIR(stream, this.#currentConvolution);
802
- this.stopCalibrationAudio();
803
- let component_conv_recs = this.getAllFilteredRecordedSignals();
804
- let return_component_conv_rec = component_conv_recs[component_conv_recs.length - 1];
805
- this.clearAllFilteredRecordedSignals();
806
- // await this.checkPowerVariation(return_component_conv_rec);
807
- this.numSuccessfulCaptured = 0;
808
- this.#currentConvolution = this.systemConvolution;
809
- this.filteredMLSRange.system.Min = findMinValue(this.#currentConvolution);
810
- this.filteredMLSRange.system.Max = findMaxValue(this.#currentConvolution);
811
- this.soundCheck = 'system';
812
- this.addTimeStamp('Play MLS with system IIR');
813
- if (this.isCalibrating) return null;
814
- await this.playMLSwithIIR(stream, this.#currentConvolution);
815
-
816
- this.stopCalibrationAudio();
817
-
818
- let system_conv_recs = this.getAllFilteredRecordedSignals();
819
- let return_system_conv_rec = system_conv_recs[system_conv_recs.length - 1];
820
- // await this.checkPowerVariation(return_system_conv_rec);
821
-
822
- this.clearAllFilteredRecordedSignals();
823
-
824
- this.sourceAudioContext.close();
825
- let recs = this.getAllUnfilteredRecordedSignals();
826
- let unconv_rec = recs[0];
827
- let return_unconv_rec = unconv_rec;
828
- let conv_rec = component_conv_recs[component_conv_recs.length - 1];
829
-
830
- //psd of component
831
- let knownGain = this.oldComponentIR.Gain;
832
- let knownFreq = this.oldComponentIR.Freq;
833
- let sampleRate = this.sourceSamplingRate || 96000;
834
- this.addTimeStamp('Get PSD of mls recording');
835
- if (this.isCalibrating) return null;
836
- let component_unconv_rec_psd = await this.pyServerAPI
837
- .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)
838
- .then(res => {
839
- this.incrementStatusBar();
840
- this.status =
841
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
842
- this.generateTemplate().toString();
843
- this.emit('update', {message: this.status});
844
- return res;
845
- })
846
- .catch(err => {
847
- console.error(err);
848
- });
849
-
850
- this.addTimeStamp('Get PSD of filtered recording (component)');
851
- if (this.isCalibrating) return null;
852
- let component_conv_rec_psd = await this.pyServerAPI
853
- .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)
854
- .then(res => {
855
- let interpolatedGain = res.x.map((freq, index) => {
856
- let i = 0;
857
- while (i < knownFreq.length && knownFreq[i] < freq) {
858
- i++;
859
- }
860
- if (i === 0 || i === knownFreq.length) {
861
- return knownGain[i];
862
- }
863
- return interpolate(freq, knownFreq[i - 1], knownFreq[i], knownGain[i - 1], knownGain[i]);
864
- });
865
- console.log(interpolatedGain);
866
- let correctedGain = res.y.map(
867
- (gain, index) => 10 * Math.log10(gain) - interpolatedGain[index]
868
- );
869
-
870
- let filtered_psd = correctedGain.filter(
871
- (value, index) => res.x[index] >= this.#lowHz && res.x[index] <= this.#highHz
872
- );
873
-
874
- this.SDofFilteredRange['component'] = standardDeviation(filtered_psd);
875
- this.incrementStatusBar();
876
- this.status =
877
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
878
- this.generateTemplate().toString();
879
- this.emit('update', {message: this.status});
880
- return res;
881
- })
882
- .catch(err => {
883
- console.error(err);
884
- });
885
-
886
- conv_rec = system_conv_recs[system_conv_recs.length - 1];
887
- //psd of system
888
- this.addTimeStamp('Get PSD of filtered recording (system) and unfiltered recording');
889
- if (this.isCalibrating) return null;
890
- let system_recs_psd = await this.pyServerAPI
891
- .getPSDWithRetry({
892
- unconv_rec,
893
- conv_rec,
894
- sampleRate: this.sourceSamplingRate || 96000,
895
- })
896
- .then(res => {
897
- console.log(res);
898
- let filtered_psd = res.y_conv
899
- .filter(
900
- (value, index) => res.x_conv[index] >= this.#lowHz && res.x_conv[index] <= this.#highHz
901
- )
902
- .map(value => 10 * Math.log10(value));
903
-
904
- this.SDofFilteredRange['system'] = standardDeviation(filtered_psd);
905
- this.incrementStatusBar();
906
- this.status =
907
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
908
- this.generateTemplate().toString();
909
- this.emit('update', {message: this.status});
910
- return res;
911
- })
912
- .catch(err => {
913
- console.error(err);
914
- });
915
-
916
- //iir w/ and without bandpass psd. done
917
- unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
918
- conv_rec = this.componentInvertedImpulseResponse;
919
- this.addTimeStamp('Get PSD of component iir and component iir no band pass');
920
- if (this.isCalibrating) return null;
921
- let component_iir_psd = await this.pyServerAPI
922
- .getPSDWithRetry({
923
- unconv_rec,
924
- conv_rec,
925
- sampleRate: this.sourceSamplingRate || 96000,
926
- })
927
- .then(res => {
928
- this.incrementStatusBar();
929
- this.status =
930
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
931
- this.generateTemplate().toString();
932
- this.emit('update', {message: this.status});
933
- return res;
934
- })
935
- .catch(err => {
936
- console.error(err);
937
- });
938
- unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
939
- conv_rec = this.systemInvertedImpulseResponse;
940
- this.addTimeStamp('Get PSD of system iir and system iir no band pass');
941
- if (this.isCalibrating) return null;
942
- let system_iir_psd = await this.pyServerAPI
943
- .getPSDWithRetry({
944
- unconv_rec,
945
- conv_rec,
946
- sampleRate: this.sourceSamplingRate || 96000,
947
- })
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
-
960
- this.addTimeStamp('Get PSD of mls sequence');
961
- if (this.isCalibrating) return null;
962
- let mls_psd = await this.pyServerAPI
963
- .getMLSPSDWithRetry({mls: this.#mlsBufferView, sampleRate: this.sourceSamplingRate || 96000})
964
- .then(res => {
965
- this.incrementStatusBar();
966
- this.status =
967
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
968
- this.generateTemplate().toString();
969
- this.emit('update', {message: this.status});
970
- return res;
971
- })
972
- .catch(err => {
973
- console.error(err);
974
- });
975
-
976
- this.addTimeStamp('Get PSD of filered mls (system)');
977
- if (this.isCalibrating) return null;
978
- let system_filtered_mls_psd = await this.pyServerAPI
979
- .getMLSPSDWithRetry({
980
- mls: this.systemConvolution,
981
- sampleRate: this.sourceSamplingRate || 96000,
982
- })
983
- .then(res => {
984
- this.incrementStatusBar();
985
- this.status =
986
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
987
- this.generateTemplate().toString();
988
- this.emit('update', {message: this.status});
989
- return res;
990
- })
991
- .catch(err => {
992
- console.error(err);
993
- });
994
-
995
- this.addTimeStamp('Get PSD of filered mls (component)');
996
- if (this.isCalibrating) return null;
997
- let component_filtered_mls_psd = await this.pyServerAPI
998
- .getMLSPSDWithRetry({
999
- mls: this.componentConvolution,
1000
- sampleRate: this.sourceSamplingRate || 96000,
1001
- })
1002
- .then(res => {
1003
- this.incrementStatusBar();
1004
- this.status =
1005
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1006
- this.generateTemplate().toString();
1007
- this.emit('update', {message: this.status});
1008
- return res;
1009
- })
1010
- .catch(err => {
1011
- console.error(err);
1012
- });
1013
-
1014
- let gainValue = this.getGainDBSPL();
1015
-
1016
- iir_ir_and_plots = {
1017
- filtered_recording: {
1018
- component: return_component_conv_rec,
1019
- system: return_system_conv_rec,
1020
- },
1021
- unfiltered_recording: this.getAllUnfilteredRecordedSignals()[0],
1022
- system: {
1023
- iir: this.systemInvertedImpulseResponse,
1024
- ir: this.systemIR,
1025
- iir_psd: {
1026
- y: system_iir_psd['y_conv'],
1027
- x: system_iir_psd['x_conv'],
1028
- y_no_bandpass: system_iir_psd['y_unconv'],
1029
- x_no_bandpass: system_iir_psd['x_unconv'],
1030
- },
1031
- filtered_mls_psd: {
1032
- x: system_filtered_mls_psd['x_mls'],
1033
- y: system_filtered_mls_psd['y_mls'],
1034
- },
1035
- convolution: this.systemConvolution,
1036
- psd: {
1037
- unconv: {
1038
- x: system_recs_psd['x_unconv'],
1039
- y: system_recs_psd['y_unconv'],
1040
- },
1041
- conv: {
1042
- x: system_recs_psd['x_conv'],
1043
- y: system_recs_psd['y_conv'],
1044
- },
1045
- },
1046
- },
1047
- component: {
1048
- iir: this.componentInvertedImpulseResponse,
1049
- ir: this.componentIR,
1050
- ir_origin: this.componentIROrigin,
1051
- ir_in_time_domain: this.componentIRInTimeDomain,
1052
- iir_psd: {
1053
- y: component_iir_psd['y_conv'],
1054
- x: component_iir_psd['x_conv'],
1055
- y_no_bandpass: component_iir_psd['y_unconv'],
1056
- x_no_bandpass: component_iir_psd['x_unconv'],
1057
- },
1058
- filtered_mls_psd: {
1059
- x: component_filtered_mls_psd['x_mls'],
1060
- y: component_filtered_mls_psd['y_mls'],
1061
- },
1062
- convolution: this.componentConvolution,
1063
- psd: {
1064
- unconv: {
1065
- x: component_unconv_rec_psd['x'],
1066
- y: component_unconv_rec_psd['y'],
1067
- },
1068
- conv: {
1069
- x: component_conv_rec_psd['x'],
1070
- y: component_conv_rec_psd['y'],
1071
- },
1072
- },
1073
- gainDBSPL: gainValue,
1074
- },
1075
- mls: this.#mlsBufferView,
1076
- mls_psd: {
1077
- x: mls_psd['x_mls'],
1078
- y: mls_psd['y_mls'],
1079
- },
1080
- autocorrelations: this.autocorrelations,
1081
- impulseResponses: [],
1082
- };
1083
-
1084
- return iir_ir_and_plots;
1085
- };
1086
-
1087
- singleSoundCheck = async stream => {
1088
- let iir_ir_and_plots;
1089
- if (this._calibrateSoundCheck != 'system') {
1090
- this.#currentConvolution = this.componentConvolution;
1091
- this.filteredMLSRange.component.Min = findMinValue(this.#currentConvolution);
1092
- this.filteredMLSRange.component.Max = findMaxValue(this.#currentConvolution);
1093
- this.addTimeStamp('Play MLS with component IIR');
1094
- this.soundCheck = 'component';
1095
- if (this.isCalibrating) return null;
1096
- await this.playMLSwithIIR(stream, this.#currentConvolution);
1097
- this.stopCalibrationAudio();
1098
- } else {
1099
- this.#currentConvolution = this.systemConvolution;
1100
- this.filteredMLSRange.system.Min = findMinValue(this.#currentConvolution);
1101
- this.filteredMLSRange.system.Max = findMaxValue(this.#currentConvolution);
1102
- this.addTimeStamp('Play MLS with system IIR');
1103
- this.soundCheck = 'systen';
1104
- if (this.isCalibrating) return null;
1105
- await this.playMLSwithIIR(stream, this.#currentConvolution);
1106
- this.stopCalibrationAudio();
1107
- }
1108
- let conv_recs = this.getAllFilteredRecordedSignals();
1109
- let recs = this.getAllUnfilteredRecordedSignals();
1110
- this.clearAllFilteredRecordedSignals();
1111
- console.log('Obtaining unfiltered recording from #allHzUnfilteredRecordings to calculate PSD');
1112
- console.log('Obtaining filtered recording from #allHzFilteredRecordings to calculate PSD');
1113
- let unconv_rec = recs[0];
1114
- let return_unconv_rec = unconv_rec;
1115
- let conv_rec = conv_recs[conv_recs.length - 1];
1116
- let return_conv_rec = conv_rec;
1117
- this.sourceAudioContext.close();
1118
- if (this._calibrateSoundCheck != 'system') {
1119
- let knownGain = this.oldComponentIR.Gain;
1120
- let knownFreq = this.oldComponentIR.Freq;
1121
- let sampleRate = this.sourceSamplingRate || 96000;
1122
- this.addTimeStamp('Get PSD of mls recording');
1123
- if (this.isCalibrating) return null;
1124
- let unconv_results = await this.pyServerAPI
1125
- .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)
1126
- .then(res => {
1127
- this.incrementStatusBar();
1128
- this.status =
1129
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1130
- this.generateTemplate().toString();
1131
- this.emit('update', {message: this.status});
1132
- return res;
1133
- })
1134
- .catch(err => {
1135
- console.error(err);
1136
- });
1137
-
1138
- this.addTimeStamp('Get PSD recording of filtered recording (component)');
1139
- if (this.isCalibrating) return null;
1140
- let conv_results = await this.pyServerAPI
1141
- .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)
1142
- .then(res => {
1143
- let interpolatedGain = res.x.map((freq, index) => {
1144
- let i = 0;
1145
- while (i < knownFreq.length && knownFreq[i] < freq) {
1146
- i++;
1147
- }
1148
- if (i === 0 || i === knownFreq.length) {
1149
- return knownGain[i];
1150
- }
1151
- return interpolate(
1152
- freq,
1153
- knownFreq[i - 1],
1154
- knownFreq[i],
1155
- knownGain[i - 1],
1156
- knownGain[i]
1157
- );
1158
- });
1159
-
1160
- console.log(interpolatedGain);
1161
- let correctedGain = res.y.map(
1162
- (gain, index) => 10 * Math.log10(gain) - interpolatedGain[index]
1163
- );
1164
- let filtered_psd = correctedGain.filter(
1165
- (value, index) => res.x[index] >= this.#lowHz && res.x[index] <= this.#highHz
1166
- );
1167
-
1168
- this.SDofFilteredRange['component'] = standardDeviation(filtered_psd);
1169
- this.incrementStatusBar();
1170
- this.status =
1171
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1172
- this.generateTemplate().toString();
1173
- this.emit('update', {message: this.status});
1174
- return res;
1175
- })
1176
- .catch(err => {
1177
- console.error(err);
1178
- });
1179
-
1180
- unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
1181
- conv_rec = this.componentInvertedImpulseResponse;
1182
- this.addTimeStamp('Get PSD of component iir and component iir no bandpass');
1183
- if (this.isCalibrating) return null;
1184
- let component_iir_psd = await this.pyServerAPI
1185
- .getPSDWithRetry({
1186
- unconv_rec,
1187
- conv_rec,
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
- unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
1202
- conv_rec = this.systemInvertedImpulseResponse;
1203
- this.addTimeStamp('Get PSD of system iir and system iir no bandpass');
1204
- if (this.isCalibrating) return null;
1205
- let system_iir_psd = await this.pyServerAPI
1206
- .getPSDWithRetry({
1207
- unconv_rec,
1208
- conv_rec,
1209
- sampleRate: this.sourceSamplingRate || 96000,
1210
- })
1211
- .then(res => {
1212
- this.incrementStatusBar();
1213
- this.status =
1214
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1215
- this.generateTemplate().toString();
1216
- this.emit('update', {message: this.status});
1217
- return res;
1218
- })
1219
- .catch(err => {
1220
- console.error(err);
1221
- });
1222
-
1223
- this.addTimeStamp('Get PSD of mls sequence');
1224
- if (this.isCalibrating) return null;
1225
- let mls_psd = await this.pyServerAPI
1226
- .getMLSPSDWithRetry({
1227
- mls: this.#mlsBufferView,
1228
- sampleRate: this.sourceSamplingRate || 96000,
1229
- })
1230
- .then(res => {
1231
- this.incrementStatusBar();
1232
- this.status =
1233
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1234
- this.generateTemplate().toString();
1235
- this.emit('update', {message: this.status});
1236
- return res;
1237
- })
1238
- .catch(err => {
1239
- console.error(err);
1240
- });
1241
-
1242
- this.addTimeStamp('Get PSD of filtered mls (component)');
1243
- if (this.isCalibrating) return null;
1244
- let filtered_mls_psd = await this.pyServerAPI
1245
- .getMLSPSDWithRetry({
1246
- mls: this.componentConvolution,
1247
- sampleRate: this.sourceSamplingRate || 96000,
1248
- })
1249
- .then(res => {
1250
- this.incrementStatusBar();
1251
- this.status =
1252
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1253
- this.generateTemplate().toString();
1254
- this.emit('update', {message: this.status});
1255
- return res;
1256
- })
1257
- .catch(err => {
1258
- console.error(err);
1259
- });
1260
-
1261
- let gainValue = this.getGainDBSPL();
1262
- iir_ir_and_plots = {
1263
- unfiltered_recording: return_unconv_rec,
1264
- filtered_recording: return_conv_rec,
1265
- system: {
1266
- iir: this.systemInvertedImpulseResponse,
1267
- ir: this.systemIR,
1268
- iir_psd: {
1269
- y: system_iir_psd['y_conv'],
1270
- x: system_iir_psd['y_conv'],
1271
- y_no_bandpass: system_iir_psd['y_unconv'],
1272
- x_no_bandpass: system_iir_psd['x_unconv'],
1273
- },
1274
- filtered_recording: [],
1275
- filtered_mls_psd: {},
1276
- convolution: this.systemConvolution,
1277
- psd: {
1278
- unconv: {
1279
- x: [],
1280
- y: [],
1281
- },
1282
- conv: {
1283
- x: [],
1284
- y: [],
1285
- },
1286
- },
1287
- },
1288
- component: {
1289
- iir: this.componentInvertedImpulseResponse,
1290
- ir: this.componentIR,
1291
- ir_origin: this.componentIROrigin,
1292
- ir_in_time_domain: this.componentIRInTimeDomain,
1293
- iir_psd: {
1294
- y: component_iir_psd['y_conv'],
1295
- x: component_iir_psd['x_conv'],
1296
- y_no_bandpass: component_iir_psd['y_unconv'],
1297
- x_no_bandpass: component_iir_psd['x_unconv'],
1298
- },
1299
- filtered_mls_psd: {
1300
- x: filtered_mls_psd['x_mls'],
1301
- y: filtered_mls_psd['y_mls'],
1302
- },
1303
- convolution: this.componentConvolution,
1304
- psd: {
1305
- unconv: {
1306
- x: unconv_results['x'],
1307
- y: unconv_results['y'],
1308
- },
1309
- conv: {
1310
- x: conv_results['x'],
1311
- y: conv_results['y'],
1312
- },
1313
- },
1314
- gainDBSPL: gainValue,
1315
- },
1316
- mls: this.#mlsBufferView,
1317
- mls_psd: {
1318
- x: mls_psd['x_mls'],
1319
- y: mls_psd['y_mls'],
1320
- },
1321
- autocorrelations: this.autocorrelations,
1322
- impulseResponses: [],
1323
- };
1324
- } else {
1325
- this.addTimeStamp('Get PSD of filtered recording (system) and unfiltered recording');
1326
- if (this.isCalibrating) return null;
1327
- let results = await this.pyServerAPI
1328
- .getPSDWithRetry({
1329
- unconv_rec,
1330
- conv_rec,
1331
- sampleRate: this.sourceSamplingRate || 96000,
1332
- })
1333
- .then(res => {
1334
- console.log(res);
1335
- let filtered_psd = res.y_conv
1336
- .filter(
1337
- (value, index) =>
1338
- res.x_conv[index] >= this.#lowHz && res.x_conv[index] <= this.#highHz
1339
- )
1340
- .map(value => 10 * Math.log10(value));
1341
-
1342
- this.SDofFilteredRange['system'] = standardDeviation(filtered_psd);
1343
- this.incrementStatusBar();
1344
- this.status =
1345
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1346
- this.generateTemplate().toString();
1347
- this.emit('update', {message: this.status});
1348
- return res;
1349
- })
1350
- .catch(err => {
1351
- console.error(err);
1352
- });
1353
-
1354
- //iir w/ and without bandpass psd
1355
- unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
1356
- conv_rec = this.componentInvertedImpulseResponse;
1357
- this.addTimeStamp('Get PSD of component iir and component iir no band pass');
1358
- if (this.isCalibrating) return null;
1359
- let component_iir_psd = await this.pyServerAPI
1360
- .getPSDWithRetry({
1361
- unconv_rec,
1362
- conv_rec,
1363
- sampleRate: this.sourceSamplingRate || 96000,
1364
- })
1365
- .then(res => {
1366
- this.incrementStatusBar();
1367
- this.status =
1368
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1369
- this.generateTemplate().toString();
1370
- this.emit('update', {message: this.status});
1371
- return res;
1372
- })
1373
- .catch(err => {
1374
- console.error(err);
1375
- });
1376
- unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
1377
- conv_rec = this.systemInvertedImpulseResponse;
1378
- this.addTimeStamp('Get PSD of system iir and system iir no band pass');
1379
- if (this.isCalibrating) return null;
1380
- let system_iir_psd = await this.pyServerAPI
1381
- .getPSDWithRetry({
1382
- unconv_rec,
1383
- conv_rec,
1384
- sampleRate: this.sourceSamplingRate || 96000,
1385
- })
1386
- .then(res => {
1387
- this.incrementStatusBar();
1388
- this.status =
1389
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1390
- this.generateTemplate().toString();
1391
- this.emit('update', {message: this.status});
1392
- return res;
1393
- })
1394
- .catch(err => {
1395
- console.error(err);
1396
- });
1397
-
1398
- this.addTimeStamp('Get PSD of mls sequence');
1399
- if (this.isCalibrating) return null;
1400
- let mls_psd = await this.pyServerAPI
1401
- .getMLSPSDWithRetry({
1402
- mls: this.#mlsBufferView,
1403
- sampleRate: this.sourceSamplingRate || 96000,
1404
- })
1405
- .then(res => {
1406
- this.incrementStatusBar();
1407
- this.status =
1408
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1409
- this.generateTemplate().toString();
1410
- this.emit('update', {message: this.status});
1411
- return res;
1412
- })
1413
- .catch(err => {
1414
- console.error(err);
1415
- });
1416
-
1417
- this.addTimeStamp('Get PSD of filtered mls (system)');
1418
- if (this.isCalibrating) return null;
1419
- let filtered_mls_psd = await this.pyServerAPI
1420
- .getMLSPSDWithRetry({
1421
- mls: this.systemConvolution,
1422
- sampleRate: this.sourceSamplingRate || 96000,
1423
- })
1424
- .then(res => {
1425
- this.incrementStatusBar();
1426
- this.status =
1427
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1428
- this.generateTemplate().toString();
1429
- this.emit('update', {message: this.status});
1430
- return res;
1431
- })
1432
- .catch(err => {
1433
- console.error(err);
1434
- });
1435
-
1436
- let gainValue = this.getGainDBSPL();
1437
- iir_ir_and_plots = {
1438
- unfiltered_recording: return_unconv_rec,
1439
- filtered_recording: return_conv_rec,
1440
- system: {
1441
- iir: this.systemInvertedImpulseResponse,
1442
- ir: this.systemIR,
1443
- iir_psd: {
1444
- y: system_iir_psd['y_conv'],
1445
- x: system_iir_psd['y_conv'],
1446
- y_no_bandpass: system_iir_psd['y_unconv'],
1447
- x_no_bandpass: system_iir_psd['x_unconv'],
1448
- },
1449
- filtered_recording: [],
1450
- filtered_mls_psd: {
1451
- x: filtered_mls_psd['x_mls'],
1452
- y: filtered_mls_psd['y_mls'],
1453
- },
1454
- convolution: this.systemConvolution,
1455
- psd: {
1456
- unconv: {
1457
- x: results['x_unconv'],
1458
- y: results['y_unconv'],
1459
- },
1460
- conv: {
1461
- x: results['x_conv'],
1462
- y: results['y_conv'],
1463
- },
1464
- },
1465
- },
1466
- component: {
1467
- iir: this.componentInvertedImpulseResponse,
1468
- ir: this.componentIR,
1469
- ir_origin: this.componentIROrigin,
1470
- ir_in_time_domain: this.componentIRInTimeDomain,
1471
- iir_psd: {
1472
- y: component_iir_psd['y_conv'],
1473
- x: component_iir_psd['x_conv'],
1474
- y_no_bandpass: component_iir_psd['y_unconv'],
1475
- x_no_bandpass: component_iir_psd['x_unconv'],
1476
- },
1477
- filtered_mls_psd: {},
1478
- convolution: this.componentConvolution,
1479
- psd: {
1480
- unconv: {
1481
- x: [],
1482
- y: [],
1483
- },
1484
- conv: {
1485
- x: [],
1486
- y: [],
1487
- },
1488
- },
1489
- gainDBSPL: gainValue,
1490
- },
1491
- mls: this.#mlsBufferView,
1492
- mls_psd: {
1493
- x: mls_psd['x_mls'],
1494
- y: mls_psd['y_mls'],
1495
- },
1496
- autocorrelations: this.autocorrelations,
1497
- impulseResponses: [],
1498
- };
1499
- }
1500
- if (this.isCalibrating) return null;
1501
- await Promise.all(this.impulseResponses).then(res => {
1502
- for (let i = 0; i < res.length; i++) {
1503
- if (res[i] != undefined) {
1504
- iir_ir_and_plots['impulseResponses'].push(res[i]);
1505
- }
1506
- }
1507
- });
1508
-
1509
- if (this.#download) {
1510
- this.downloadSingleUnfilteredRecording();
1511
- this.downloadSingleFilteredRecording();
1512
- saveToCSV(this.#mls, 'MLS.csv');
1513
- saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
1514
- saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
1515
- saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
1516
- saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
1517
- for (let i = 0; i < this.autocorrelations.length; i++) {
1518
- saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
1519
- }
1520
- const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
1521
- for (let i = 0; i < res.length; i++) {
1522
- if (res[i] != undefined) {
1523
- saveToCSV(res[i], `IR_${i}`);
1524
- }
1525
- }
1526
- });
1527
- }
1528
-
1529
- return iir_ir_and_plots;
1530
- };
1531
-
1532
- /**
1533
- * Public method to start the calibration process. Objects intialized from webassembly allocate new memory
1534
- * and must be manually freed. This function is responsible for intializing the MlsGenInterface,
1535
- * and wrapping the calibration steps with a garbage collection safe gaurd.
1536
- *
1537
- * @public
1538
- * @param stream - The stream of audio from the Listener.
1539
- * @example
1540
- */
1541
- startCalibrationImpulseResponse = async stream => {
1542
- let desired_time = this.desired_time_per_mls;
1543
- let checkRec = 'allhz';
1544
-
1545
- console.log('MLS sequence should be of length: ' + this.sourceSamplingRate * desired_time);
1546
-
1547
- length = this.sourceSamplingRate * desired_time;
1548
- //get mls here
1549
- const calibrateSoundBurstDb = this._calibrateSoundBurstDb;
1550
- this.addTimeStamp('Get MLS sequence');
1551
- if (this.isCalibrating) return null;
1552
- await this.pyServerAPI
1553
- .getMLSWithRetry({length, calibrateSoundBurstDb})
1554
- .then(res => {
1555
- console.log(res);
1556
- this.#mlsBufferView = res['mls'];
1557
- this.#mls = res['unscaledMLS'];
1558
- })
1559
- .catch(err => {
1560
- // this.emit('InvertedImpulseResponse', {res: false});
1561
- console.error(err);
1562
- });
1563
- this.numSuccessfulBackgroundCaptured = 0;
1564
- if (this._calibrateSoundBackgroundSecs > 0) {
1565
- this.mode = 'background';
1566
- this.status =
1567
- `All Hz Calibration: sampling the background noise...`.toString() +
1568
- this.generateTemplate().toString();
1569
- this.emit('update', {message: this.status});
1570
- if (this.isCalibrating) return null;
1571
- await this.recordBackground(
1572
- stream, //stream
1573
- () => this.numSuccessfulBackgroundCaptured < 1, //loop condition
1574
- this.#awaitBackgroundNoiseRecording, //sleep to record
1575
- this.sendBackgroundRecording, //send to get PSD
1576
- this.mode,
1577
- checkRec
1578
- );
1579
- this.incrementStatusBar();
1580
- }
1581
- this.mode = 'unfiltered';
1582
- this.numSuccessfulCaptured = 0;
1583
-
1584
- if (this.isCalibrating) return null;
1585
- await this.calibrationSteps(
1586
- stream,
1587
- this.#playCalibrationAudio, // play audio func (required)
1588
- this.#createCalibrationNodeFromBuffer(this.#mlsBufferView), // before play func
1589
- this.#awaitSignalOnset, // before record
1590
- () => this.numSuccessfulCaptured < this.numCaptures, // loop while true
1591
- this.#awaitDesiredMLSLength, // during record
1592
- this.#afterMLSRecord, // after record
1593
- this.mode,
1594
- checkRec
1595
- );
1596
- this.stopCalibrationAudio();
1597
- checkRec = false;
1598
-
1599
- // at this stage we've captured all the required signals,
1600
- // and have received IRs for each one
1601
- // so let's send all the IRs to the server to be converted to a single IIR
1602
- if (this.isCalibrating) return null;
1603
- await this.sendSystemImpulseResponsesToServerForProcessing();
1604
- if (this.isCalibrating) return null;
1605
- await this.sendComponentImpulseResponsesToServerForProcessing();
1606
-
1607
- this.numSuccessfulCaptured = 0;
1608
-
1609
- let iir_ir_and_plots;
1610
- if (this._calibrateSoundCheck != 'none') {
1611
- //do single check
1612
- if (this._calibrateSoundCheck == 'goal' || this._calibrateSoundCheck == 'system') {
1613
- if (this.isCalibrating) return null;
1614
- iir_ir_and_plots = await this.singleSoundCheck(stream);
1615
- if (this.isCalibrating) return null;
1616
- } else {
1617
- //both
1618
- if (this.isCalibrating) return null;
1619
- iir_ir_and_plots = await this.bothSoundCheck(stream);
1620
- if (this.isCalibrating) return null;
1621
- }
1622
- } else {
1623
- let unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
1624
- let conv_rec = this.componentInvertedImpulseResponse;
1625
- if (this.isCalibrating) return null;
1626
- let component_iir_psd = await this.pyServerAPI
1627
- .getPSDWithRetry({
1628
- unconv_rec,
1629
- conv_rec,
1630
- sampleRate: this.sourceSamplingRate || 96000,
1631
- })
1632
- .then(res => {
1633
- this.incrementStatusBar();
1634
- this.status =
1635
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1636
- this.generateTemplate().toString();
1637
- this.emit('update', {message: this.status});
1638
- return res;
1639
- })
1640
- .catch(err => {
1641
- console.error(err);
1642
- });
1643
- unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
1644
- conv_rec = this.systemInvertedImpulseResponse;
1645
- if (this.isCalibrating) return null;
1646
- let system_iir_psd = await this.pyServerAPI
1647
- .getPSDWithRetry({
1648
- unconv_rec,
1649
- conv_rec,
1650
- sampleRate: this.sourceSamplingRate || 96000,
1651
- })
1652
- .then(res => {
1653
- this.incrementStatusBar();
1654
- this.status =
1655
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
1656
- this.generateTemplate().toString();
1657
- this.emit('update', {message: this.status});
1658
- return res;
1659
- })
1660
- .catch(err => {
1661
- console.error(err);
1662
- });
1663
-
1664
- let gainValue = this.getGainDBSPL();
1665
- iir_ir_and_plots = {
1666
- unfiltered_recording: return_unconv_rec,
1667
- filtered_recording: return_conv_rec,
1668
- system: {
1669
- iir: this.systemInvertedImpulseResponse,
1670
- ir: this.systemIR,
1671
- iir_psd: {
1672
- y: system_iir_psd['y_conv'],
1673
- x: system_iir_psd['y_conv'],
1674
- y_no_bandpass: system_iir_psd['y_unconv'],
1675
- x_no_bandpass: system_iir_psd['x_unconv'],
1676
- },
1677
- filtered_recording: [],
1678
- convolution: this.systemConvolution,
1679
- psd: {
1680
- unconv: {
1681
- x: [],
1682
- y: [],
1683
- },
1684
- conv: {
1685
- x: [],
1686
- y: [],
1687
- },
1688
- },
1689
- },
1690
- component: {
1691
- iir: this.componentInvertedImpulseResponse,
1692
- ir: this.componentIR,
1693
- ir_in_time_domain: this.componentIRInTimeDomain,
1694
- iir_psd: {
1695
- y: component_iir_psd['y_conv'],
1696
- x: component_iir_psd['x_conv'],
1697
- y_no_bandpass: component_iir_psd['y_unconv'],
1698
- x_no_bandpass: component_iir_psd['x_unconv'],
1699
- },
1700
- convolution: this.componentConvolution,
1701
- psd: {
1702
- unconv: {
1703
- x: [],
1704
- y: [],
1705
- },
1706
- conv: {
1707
- x: [],
1708
- y: [],
1709
- },
1710
- },
1711
- gainDBSPL: gainValue,
1712
- },
1713
- mls: this.#mlsBufferView,
1714
- autocorrelations: this.autocorrelations,
1715
- impulseResponses: [],
1716
- };
1717
- if (this.isCalibrating) return null;
1718
- await Promise.all(this.impulseResponses).then(res => {
1719
- for (let i = 0; i < res.length; i++) {
1720
- if (res[i] != undefined) {
1721
- iir_ir_and_plots['impulseResponses'].push(res[i]);
1722
- }
1723
- }
1724
- });
1725
-
1726
- if (this.#download) {
1727
- saveToCSV(this.#mls, 'MLS.csv');
1728
- saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
1729
- saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
1730
- saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
1731
- saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
1732
- for (let i = 0; i < this.autocorrelations.length; i++) {
1733
- saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
1734
- }
1735
- const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
1736
- for (let i = 0; i < res.length; i++) {
1737
- if (res[i] != undefined) {
1738
- saveToCSV(res[i], `IR_${i}`);
1739
- }
1740
- }
1741
- });
1742
- }
1743
- }
1744
- if (this.isCalibrating) return null;
1745
- this.percent_complete = 100;
1746
- this.status = `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();
1747
- this.emit('update', {message: this.status});
1748
-
1749
- //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR
1750
- //that was used to calibrate
1751
- // saveToJSON(iir_ir_and_plots);
1752
- return iir_ir_and_plots;
1753
- };
1754
-
1755
- //////////////////////volume
1756
-
1757
- handleIncomingData = data => {
1758
- console.log('Received data: ', data);
1759
- if (data.type === 'soundGainDBSPL') {
1760
- this.soundGainDBSPL = data.value;
1761
- } else {
1762
- throw new Error(`Unknown data type: ${data.type}`);
1763
- }
1764
- };
1765
- createSCurveBuffer = (onSetBool = true) => {
1766
- const curve = new Float32Array(this.TAPER_SECS * this.sourceSamplingRate + 1);
1767
- const frequency = 1 / (4 * this.TAPER_SECS);
1768
- let j = 0;
1769
- for (let i = 0; i < this.TAPER_SECS * this.sourceSamplingRate + 1; i += 1) {
1770
- const phase = 2 * Math.PI * frequency * j;
1771
- const onsetTaper = Math.pow(Math.sin(phase), 2);
1772
- const offsetTaper = Math.pow(Math.cos(phase), 2);
1773
- curve[i] = onSetBool ? onsetTaper : offsetTaper;
1774
- j += 1 / this.sourceSamplingRate;
1775
- }
1776
- return curve;
1777
- };
1778
-
1779
- #getTruncatedSignal = (left = 3.5, right = 4.5) => {
1780
- const start = Math.floor(left * this.sourceSamplingRate);
1781
- const end = Math.floor(right * this.sourceSamplingRate);
1782
- const result = Array.from(this.getLastVolumeRecordedSignal().slice(start, end));
1783
- console.log(
1784
- 'Obtaining last 1000 hz recording from #allVolumeRecordings to send for processing'
1785
- );
1786
- /**
1787
- * function to check that capture was properly made
1788
- * @param {*} list
1789
- */
1790
- const checkResult = list => {
1791
- const setItem = new Set(list);
1792
- if (setItem.size === 1 && setItem.has(0)) {
1793
- console.warn(
1794
- 'The last capture failed, all recorded signal is zero',
1795
- this.getAllVolumeRecordedSignals()
1796
- );
1797
- }
1798
- if (setItem.size === 0) {
1799
- console.warn('The last capture failed, no recorded signal');
1800
- }
1801
- };
1802
- checkResult(result);
1803
- return result;
1804
- };
1805
-
1806
- /**
1807
- *
1808
- *
1809
- Construct a calibration Node with the calibration parameters and given gain value
1810
- * @param {*} gainValue
1811
- * */
1812
- #createCalibrationToneWithGainValue = gainValue => {
1813
- const audioContext = this.makeNewSourceAudioContext();
1814
- const oscilator = audioContext.createOscillator();
1815
- const gainNode = audioContext.createGain();
1816
- const taperGainNode = audioContext.createGain();
1817
- const offsetGainNode = audioContext.createGain();
1818
- const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
1819
-
1820
- oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
1821
- oscilator.type = this.#CALIBRATION_TONE_TYPE;
1822
- gainNode.gain.value = gainValue;
1823
-
1824
- oscilator.connect(gainNode);
1825
- gainNode.connect(taperGainNode);
1826
- const onsetCurve = this.createSCurveBuffer();
1827
- taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
1828
- taperGainNode.connect(offsetGainNode);
1829
- const offsetCurve = this.createSCurveBuffer(false);
1830
- offsetGainNode.gain.setValueCurveAtTime(
1831
- offsetCurve,
1832
- totalDuration - this.TAPER_SECS,
1833
- this.TAPER_SECS
1834
- );
1835
- offsetGainNode.connect(audioContext.destination);
1836
-
1837
- this.addCalibrationNode(oscilator);
1838
- };
1839
-
1840
- /**
1841
- * Construct a Calibration Node with the calibration parameters.
1842
- *
1843
- * @private
1844
- * @example
1845
- */
1846
- #createCalibrationNode = () => {
1847
- const audioContext = this.makeNewSourceAudioContext();
1848
- const oscilator = audioContext.createOscillator();
1849
- const gainNode = audioContext.createGain();
1850
-
1851
- oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
1852
- oscilator.type = this.#CALIBRATION_TONE_TYPE;
1853
- gainNode.gain.value = 0.04;
1854
-
1855
- oscilator.connect(gainNode);
1856
- gainNode.connect(audioContext.destination);
1857
-
1858
- this.addCalibrationNode(oscilator);
1859
- };
1860
-
1861
- #playCalibrationAudioVolume = async () => {
1862
- const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
1863
-
1864
- this.calibrationNodes[0].start(0);
1865
- this.calibrationNodes[0].stop(totalDuration);
1866
- console.log(`Playing a buffer of ${this.CALIBRATION_TONE_DURATION} seconds of audio`);
1867
- console.log(`Waiting a total of ${totalDuration} seconds`);
1868
- await sleep(totalDuration);
1869
- };
1870
-
1871
- stopCalibrationAudioVolume = () => {
1872
- if (this.calibrationNodes.length > 0) {
1873
- this.calibrationNodes[0].stop();
1874
- }
1875
- };
1876
-
1877
- #sendToServerForProcessing = lCalib => {
1878
- console.log('Sending data to server');
1879
- this.addTimeStamp('Send volume data to server');
1880
- let left = this.calibrateSound1000HzPreSec;
1881
- let right = this.calibrateSound1000HzPreSec + this.calibrateSound1000HzSec;
1882
- if (this.isCalibrating) return null;
1883
- this.pyServerAPI
1884
- .getVolumeCalibration({
1885
- sampleRate: this.sourceSamplingRate,
1886
- payload: this.#getTruncatedSignal(left, right),
1887
- lCalib: lCalib,
1888
- })
1889
- .then(res => {
1890
- if (this.outDBSPL === null) {
1891
- this.incrementStatusBar();
1892
- this.outDBSPL = res['outDbSPL'];
1893
- this.outDBSPL1000 = res['outDbSPL1000'];
1894
- this.THD = res['thd'];
1895
- }
1896
- })
1897
- .catch(err => {
1898
- console.warn(err);
1899
- });
1900
-
1901
- this.pyServerAPI
1902
- .volumePowerCheck({
1903
- payload: this.getLastVolumeRecordedSignal(),
1904
- sampleRate: this.sourceSamplingRate || 96000,
1905
- binDesiredSec: this._calibrateSoundPowerBinDesiredSec,
1906
- preSec: this.calibrateSound1000HzPreSec,
1907
- Sec: this.calibrateSound1000HzSec,
1908
- })
1909
- .then(res => {
1910
- if (res && res['sd'] < this._calibrateSoundPowerDbSDToleratedDb) {
1911
- this.recordingChecks['volume'][this.inDB] = res;
1912
- }
1913
- });
1914
- };
1915
-
1916
- startCalibrationVolume = async (stream, gainValues, lCalib, componentGainDBSPL) => {
1917
- if (this.isCalibrating) return null;
1918
- const trialIterations = gainValues.length;
1919
- this.status_denominator += trialIterations;
1920
- const thdValues = [];
1921
- const inDBValues = [];
1922
- let inDB = 0;
1923
- const outDBSPLValues = [];
1924
- const outDBSPL1000Values = [];
1925
- let checkRec = false;
1926
-
1927
- // do one calibration that will be discarded
1928
- const soundLevelToDiscard = -60;
1929
- const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);
1930
- this.inDB = soundLevelToDiscard;
1931
- this.status =
1932
- `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() +
1933
- this.generateTemplate().toString();
1934
- //this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});
1935
- this.emit('update', {message: this.status});
1936
- this.startTime = new Date().getTime();
1937
-
1938
- do {
1939
- console.log('while loop');
1940
- if (this.isCalibrating) {
1941
- console.log('restart calibration');
1942
- return null;
1943
- }
1944
- // eslint-disable-next-line no-await-in-loop
1945
- await this.volumeCalibrationSteps(
1946
- stream,
1947
- this.#playCalibrationAudioVolume,
1948
- this.#createCalibrationToneWithGainValue,
1949
- this.#sendToServerForProcessing,
1950
- gainToDiscard,
1951
- lCalib, //todo make this a class parameter
1952
- checkRec
1953
- );
1954
- } while (this.outDBSPL === null);
1955
- //reset the values
1956
- //this.incrementStatusBar();
1957
-
1958
- this.outDBSPL = null;
1959
- this.outDBSPL = null;
1960
- this.outDBSPL1000 = null;
1961
- this.THD = null;
1962
-
1963
- // run the calibration at different gain values provided by the user
1964
- for (let i = 0; i < trialIterations; i++) {
1965
- //convert gain to DB and add to inDB
1966
- if (i == trialIterations - 1) {
1967
- checkRec = 'loudest';
1968
- }
1969
- inDB = Math.log10(gainValues[i]) * 20;
1970
- // precision to 1 decimal place
1971
- inDB = Math.round(inDB * 10) / 10;
1972
- this.inDB = inDB;
1973
- inDBValues.push(inDB);
1974
- console.log('next update');
1975
- this.status =
1976
- `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() +
1977
- this.generateTemplate().toString();
1978
- this.emit('update', {message: this.status});
1979
- do {
1980
- if (this.isCalibrating) {
1981
- console.log('restart calibration');
1982
- return null;
1983
- }
1984
- // eslint-disable-next-line no-await-in-loop
1985
- await this.volumeCalibrationSteps(
1986
- stream,
1987
- this.#playCalibrationAudioVolume,
1988
- this.#createCalibrationToneWithGainValue,
1989
- this.#sendToServerForProcessing,
1990
- gainValues[i],
1991
- lCalib, //todo make this a class parameter
1992
- checkRec
1993
- );
1994
- } while (this.outDBSPL === null);
1995
- outDBSPL1000Values.push(this.outDBSPL1000);
1996
- thdValues.push(this.THD);
1997
- outDBSPLValues.push(this.outDBSPL);
1998
-
1999
- this.outDBSPL = null;
2000
- this.outDBSPL1000 = null;
2001
- this.THD = null;
2002
- }
2003
- if (this.isCalibrating) return null;
2004
- // get the volume calibration parameters from the server
2005
- this.addTimeStamp('Get Volume Calibration Parameters');
2006
-
2007
- const parameters = await this.pyServerAPI
2008
- .getVolumeCalibrationParameters({
2009
- inDBValues: inDBValues,
2010
- outDBSPLValues: outDBSPL1000Values,
2011
- lCalib: lCalib,
2012
- componentGainDBSPL,
2013
- })
2014
- .then(res => {
2015
- this.incrementStatusBar();
2016
- return res;
2017
- });
2018
- if (this.isCalibrating) return null;
2019
- const result = {
2020
- parameters: parameters,
2021
- inDBValues: inDBValues,
2022
- outDBSPLValues: outDBSPLValues,
2023
- outDBSPL1000Values: outDBSPL1000Values,
2024
- thdValues: thdValues,
2025
- };
2026
-
2027
- return result;
2028
- };
2029
-
2030
- writeFrqGainToFirestore = async (speakerID, frq, gain, OEM, documentID) => {
2031
- // freq and gain are too large to take samples 1 in every 100 samples
2032
- // const sampledFrq = [];
2033
- // const sampledGain = [];
2034
- // for (let i = 0; i < frq.length; i += 100) {
2035
- // sampledFrq.push(frq[i]);
2036
- // sampledGain.push(gain[i]);
2037
- // }
2038
-
2039
- const data = {Freq: frq, Gain: gain};
2040
-
2041
- const docRef = doc(database, 'Microphones', documentID);
2042
- await updateDoc(docRef, {
2043
- linear: data,
2044
- });
2045
-
2046
- // divide frq and gain into smaller chunks and write to firestore one chunk at a time
2047
- // use arrayUnion to append to the array
2048
- // const chunkSize = 600;
2049
- // const chunkedFrq = [];
2050
- // const chunkedGain = [];
2051
- // for (let i = 0; i < frq.length; i += chunkSize) {
2052
- // chunkedFrq.push(frq.slice(i, i + chunkSize));
2053
- // chunkedGain.push(gain.slice(i, i + chunkSize));
2054
- // }
2055
- // const docRef = doc(database, 'Microphones', documentID);
2056
- // for (let i = 0; i < chunkedFrq.length; i++) {
2057
- // await updateDoc(docRef, {
2058
- // linear: {
2059
- // Freq: arrayUnion(...chunkedFrq[i]),
2060
- // Gain: arrayUnion(...chunkedGain[i]),
2061
- // },
2062
- // });
2063
- // }
2064
- };
2065
- // function to write frq and gain to firebase database given speakerID
2066
- writeFrqGain = async (speakerID, frq, gain, OEM) => {
2067
- // freq and gain are too large to take samples 1 in every 100 samples
2068
-
2069
- const sampledFrq = [];
2070
- const sampledGain = [];
2071
- for (let i = 0; i < frq.length; i += 100) {
2072
- sampledFrq.push(frq[i]);
2073
- sampledGain.push(gain[i]);
2074
- }
2075
-
2076
- const data = {Freq: sampledFrq, Gain: sampledGain};
2077
-
2078
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/linear`), data);
2079
- };
2080
-
2081
- // Function to Read frq and gain from firebase database given speakerID
2082
- // returns an array of frq and gain if speakerID exists, returns null otherwise
2083
- readFrqGainFromFirestore = async (speakerID, OEM, isDefault) => {
2084
- const collectionRef = collection(database, 'Microphones');
2085
- const q = query(
2086
- collectionRef,
2087
- where('ID', '==', speakerID),
2088
- where('lowercaseOEM', '==', OEM),
2089
- where('isDefault', '==', isDefault)
2090
- );
2091
- const querySnapshot = await getDocs(q);
2092
- // if exists return the linear field of the first document
2093
- if (querySnapshot.size > 0) {
2094
- return querySnapshot.docs[0].data().linear;
2095
- }
2096
- return null;
2097
- };
2098
- readFrqGain = async (speakerID, OEM) => {
2099
- const dbRef = ref(database);
2100
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/linear`));
2101
- if (snapshot.exists()) {
2102
- return snapshot.val();
2103
- }
2104
- return null;
2105
- };
2106
- readGainat1000HzFromFirestore = async (speakerID, OEM, isDefault) => {
2107
- const collectionRef = collection(database, 'Microphones');
2108
- const q = query(
2109
- collectionRef,
2110
- where('ID', '==', speakerID),
2111
- where('lowercaseOEM', '==', OEM),
2112
- where('isDefault', '==', isDefault)
2113
- );
2114
- const querySnapshot = await getDocs(q);
2115
- // if exists return the Gain1000 field of the first document
2116
- if (querySnapshot.size > 0) {
2117
- return querySnapshot.docs[0].data().Gain1000;
2118
- }
2119
- return null;
2120
- };
2121
-
2122
- readGainat1000Hz = async (speakerID, OEM) => {
2123
- const dbRef = ref(database);
2124
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/Gain1000`));
2125
- if (snapshot.exists()) {
2126
- return snapshot.val();
2127
- }
2128
- return null;
2129
- };
2130
-
2131
- writeGainat1000HzToFirestore = async (speakerID, gain, OEM, documentID) => {
2132
- const docRef = doc(database, 'Microphones', documentID);
2133
- await updateDoc(docRef, {
2134
- Gain1000: gain,
2135
- });
2136
- };
2137
-
2138
- writeGainat1000Hz = async (speakerID, gain, OEM) => {
2139
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/Gain1000`), gain);
2140
- };
2141
-
2142
- writeIsSmartPhoneToFirestore = async (speakerID, isSmartPhone, OEM) => {
2143
- const collectionRef = collection(database, 'Microphones');
2144
- const q = query(
2145
- collectionRef,
2146
- where('ID', '==', speakerID),
2147
- where('lowercaseOEM', '==', OEM),
2148
- where('isDefault', '==', true)
2149
- );
2150
- const querySnapshot = await getDocs(q);
2151
- if (querySnapshot.size > 0) {
2152
- const docRef = await addDoc(collectionRef, {isSmartPhone: isSmartPhone, isDefault: false});
2153
- return docRef.id;
2154
- } else {
2155
- const docRef = await addDoc(collectionRef, {isSmartPhone: isSmartPhone, isDefault: true});
2156
- return docRef.id;
2157
- }
2158
- };
2159
-
2160
- writeIsSmartPhone = async (speakerID, isSmartPhone, OEM) => {
2161
- const data = {isSmartPhone: isSmartPhone};
2162
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/isSmartPhone`), isSmartPhone);
2163
- };
2164
-
2165
- writeMicrophoneInfoToFirestore = async (speakerID, micInfo, OEM, documentID) => {
2166
- const docRef = doc(database, 'Microphones', documentID);
2167
- await setDoc(docRef, micInfo, {merge: true});
2168
- };
2169
-
2170
- doesMicrophoneExistInFirestore = async (speakerID, OEM, documentID) => {
2171
- const docRef = doc(database, 'Microphone', OEM, speakerID, documentID);
2172
- const docSnap = await getDoc(docRef);
2173
- if (docSnap.exists()) {
2174
- return true;
2175
- }
2176
- return false;
2177
- };
2178
-
2179
- doesMicrophoneExist = async (speakerID, OEM) => {
2180
- const dbRef = ref(database);
2181
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}`));
2182
- if (snapshot.exists()) {
2183
- return true;
2184
- }
2185
- return false;
2186
- };
2187
-
2188
- addMicrophoneInfo = async (speakerID, OEM, micInfo) => {
2189
- // add to database if /info does not exist
2190
- const dbRef = ref(database);
2191
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/info`));
2192
- if (!snapshot.exists()) {
2193
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/info`), micInfo);
2194
- }
2195
- };
2196
-
2197
- convertToDB = gain => {
2198
- return Math.log10(gain) * 20;
2199
- };
2200
-
2201
- // Function to perform linear interpolation between two points
2202
- interpolate(x, x0, y0, x1, y1) {
2203
- return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);
2204
- }
2205
-
2206
- findGainatFrequency = (frequencies, gains, targetFrequency) => {
2207
- // Find the index of the first frequency in the array greater than the target frequency
2208
- let index = 0;
2209
- while (index < frequencies.length && frequencies[index] < targetFrequency) {
2210
- index++;
2211
- }
2212
-
2213
- // Handle cases when the target frequency is outside the range of the given data
2214
- if (index === 0) {
2215
- return gains[0];
2216
- } else if (index === frequencies.length) {
2217
- return gains[gains.length - 1];
2218
- } else {
2219
- // Interpolate the gain based on the surrounding frequencies
2220
- const x0 = frequencies[index - 1];
2221
- const y0 = gains[index - 1];
2222
- const x1 = frequencies[index];
2223
- const y1 = gains[index];
2224
- return this.interpolate(targetFrequency, x0, y0, x1, y1);
2225
- }
2226
- };
2227
-
2228
- // add time stamp
2229
- addTimeStamp = taskName => {
2230
- let startTaskTime = (new Date().getTime() - this.startTime) / 1000;
2231
- this.timeStamp.push(`SOUND ${Number(startTaskTime.toFixed(1))} s. ${taskName}`);
2232
- };
2233
-
2234
- checkPowerVariation = async () => {
2235
- const recordings = this.getAllFilteredRecordedSignals();
2236
- const rec = recordings[recordings.length - 1];
2237
- console.log(rec);
2238
- await this.pyServerAPI
2239
- .allHzPowerCheck({
2240
- payload: rec,
2241
- sampleRate: this.sourceSamplingRate || 96000,
2242
- binDesiredSec: this._calibrateSoundPowerBinDesiredSec,
2243
- burstSec: this.desired_time_per_mls,
2244
- })
2245
- .then(result => {
2246
- if (result) {
2247
- if (result['sd'] > this._calibrateSoundPowerDbSDToleratedDb) {
2248
- console.log('filtered recording sd too high');
2249
- } else {
2250
- this.recordingChecks[this.soundCheck].push(result);
2251
- if (this.numSuccessfulCaptured < 1) {
2252
- this.numSuccessfulCaptured += 1;
2253
- this.stepNum += 1;
2254
- this.incrementStatusBar();
2255
- console.log(
2256
- 'after mls w iir record for some reason add numSucc capt ' + this.stepNum
2257
- );
2258
- this.status =
2259
- `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +
2260
- this.generateTemplate().toString();
2261
- this.emit('update', {
2262
- message: this.status,
2263
- });
2264
- }
2265
- }
2266
- }
2267
- });
2268
- };
2269
-
2270
- getGainDBSPL = () => {
2271
- var freqIndex = this.componentIR.Freq.indexOf(1000);
2272
-
2273
- // If freqIndex is not -1 (meaning 1000 is found in the freq array)
2274
- if (freqIndex !== -1) {
2275
- // Get the corresponding gain value using the index
2276
- var gainValue = this.componentIR.Gain[freqIndex];
2277
- return gainValue;
2278
- } else {
2279
- console.log('Freq 1000 not found in the array.');
2280
- return null;
2281
- }
2282
- };
2283
- // Example of how to use the writeFrqGain and readFrqGain functions
2284
- // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);
2285
- // Speaker1 is the speakerID you want to write to in the database
2286
- // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));
2287
- // MiniDSPUMIK_1 is the speakerID with some Data in the database
2288
- //adding gainDBSPL
2289
- startCalibration = async (
2290
- stream,
2291
- gainValues,
2292
- lCalib = 104.92978421490648,
2293
- componentIR = null,
2294
- microphoneName = 'MiniDSP-UMIK1-711-4754-vertical',
2295
- _calibrateSoundCheck = 'goal', //GOAL PASSed in by default
2296
- isSmartPhone = false,
2297
- _calibrateSoundBurstDb = 0.1,
2298
- _calibrateSoundBurstRepeats = 3,
2299
- _calibrateSoundBurstSec = 1,
2300
- _calibrateSoundBurstsWarmup = 0,
2301
- _calibrateSoundHz = 48000,
2302
- _calibrateSoundIIRSec = 0.2,
2303
- _calibrateSoundIRSec = 0.2,
2304
- calibrateSound1000HzPreSec = 3.5,
2305
- calibrateSound1000HzSec = 1.0,
2306
- calibrateSound1000HzPostSec = 0.5,
2307
- _calibrateSoundBackgroundSecs = 0,
2308
- _calibrateSoundSmoothOctaves = 0.33,
2309
- _calibrateSoundPowerBinDesiredSec = 0.2,
2310
- _calibrateSoundPowerDbSDToleratedDb = 1,
2311
- micManufacturer = '',
2312
- micSerialNumber = '',
2313
- micModelNumber = '',
2314
- micModelName = '',
2315
- calibrateMicrophonesBool,
2316
- authorEmails,
2317
- webAudioDeviceNames = {
2318
- loudspeaker: 'loudspeaker',
2319
- microphone: 'microphone',
2320
- microphoneText: 'xxx XXX',
2321
- },
2322
- userIDs,
2323
- restartButton
2324
- ) => {
2325
- this._calibrateSoundBurstDb = _calibrateSoundBurstDb;
2326
- this.CALIBRATION_TONE_DURATION =
2327
- calibrateSound1000HzPreSec + calibrateSound1000HzSec + calibrateSound1000HzPostSec;
2328
- this.calibrateSound1000HzPreSec = calibrateSound1000HzPreSec;
2329
- this.calibrateSound1000HzSec = calibrateSound1000HzSec;
2330
- this.calibrateSound1000HzPostSec = calibrateSound1000HzPostSec;
2331
- this.iirLength = Math.floor(_calibrateSoundIIRSec * this.sourceSamplingRate);
2332
- this.irLength = Math.floor(_calibrateSoundIRSec * this.sourceSamplingRate);
2333
- console.log('device info:', this.deviceInfo);
2334
- this.numMLSPerCapture = _calibrateSoundBurstRepeats + 1;
2335
- this.desired_time_per_mls = _calibrateSoundBurstSec;
2336
- this.num_mls_to_skip = _calibrateSoundBurstsWarmup;
2337
- this.desired_sampling_rate = _calibrateSoundHz;
2338
- this._calibrateSoundBackgroundSecs = _calibrateSoundBackgroundSecs;
2339
- this._calibrateSoundSmoothOctaves = _calibrateSoundSmoothOctaves;
2340
- this._calibrateSoundPowerBinDesiredSec = _calibrateSoundPowerBinDesiredSec;
2341
- this._calibrateSoundPowerDbSDToleratedDb = _calibrateSoundPowerDbSDToleratedDb;
2342
- this.webAudioDeviceNames = webAudioDeviceNames;
2343
- if (isSmartPhone) this.webAudioDeviceNames.microphone = this.deviceInfo.microphoneFromAPI;
2344
- this.webAudioDeviceNames.microphoneText = this.webAudioDeviceNames.microphoneText
2345
- .replace('xxx', this.webAudioDeviceNames.microphone)
2346
- .replace('XXX', this.webAudioDeviceNames.microphone);
2347
- //feed calibration goal here
2348
- this._calibrateSoundCheck = _calibrateSoundCheck;
2349
- //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
2350
- //check the db based on the microphone currently connected
2351
-
2352
- //new lCalib found at top of calibration files *1000hz, make sure to correct
2353
- //based on zeroing of 1000hz, search for "*1000Hz"
2354
- const ID = isSmartPhone ? micModelNumber : micSerialNumber;
2355
- const OEM = isSmartPhone
2356
- ? micModelName === 'UMIK-1' || micModelName === 'UMIK-2'
2357
- ? 'minidsp'
2358
- : this.deviceInfo.OEM.toLowerCase().split(' ').join('')
2359
- : micManufacturer.toLowerCase().split(' ').join('');
2360
- // const ID = "712-5669";
2361
- // const OEM = "minidsp";
2362
- const micInfo = {
2363
- micModelName: isSmartPhone ? micModelName : microphoneName,
2364
- OEM: isSmartPhone
2365
- ? micModelName === 'UMIK-1' || micModelName === 'UMIK-2'
2366
- ? 'miniDSP'
2367
- : this.deviceInfo.OEM
2368
- : micManufacturer,
2369
- ID: ID,
2370
- createDate: new Date(),
2371
- DateText: getCurrentTimeString(),
2372
- HardwareName: isSmartPhone ? this.deviceInfo.hardwarename : microphoneName,
2373
- hardwareFamily: isSmartPhone ? this.deviceInfo.hardwarefamily : microphoneName,
2374
- HardwareModel: isSmartPhone ? this.deviceInfo.hardwaremodel : microphoneName,
2375
- PlatformName: isSmartPhone ? this.deviceInfo.platformname : 'N/A',
2376
- PlatformVersion: isSmartPhone ? this.deviceInfo.platformversion : 'N/A',
2377
- DeviceType: isSmartPhone ? this.deviceInfo.devicetype : 'N/A',
2378
- ID_from_51Degrees: isSmartPhone ? this.deviceInfo.DeviceId : 'N/A',
2379
- calibrateMicrophonesBool: calibrateMicrophonesBool,
2380
- webAudioDeviceNames: {
2381
- loudspeaker: this.webAudioDeviceNames.loudspeaker,
2382
- microphone: this.webAudioDeviceNames.microphone,
2383
- },
2384
- userIDs: userIDs,
2385
- lowercaseOEM: OEM.toLowerCase().split(' ').join(''),
2386
- };
2387
- if (calibrateMicrophonesBool) {
2388
- micInfo['authorEmails'] = authorEmails;
2389
- }
2390
- // if undefined in micInfo, set to empty string
2391
- for (const [key, value] of Object.entries(micInfo)) {
2392
- if (value === undefined) {
2393
- micInfo[key] = '';
2394
- }
2395
- }
2396
-
2397
- // this.writeMicrophoneInfoToFirestore(ID, micInfo, OEM, 'default');
2398
- // this.addMicrophoneInfo(ID, OEM, micInfo);
2399
- if (componentIR == null) {
2400
- //mode 'ir'
2401
- //global variable this.componentIR must be set
2402
- this.componentIR = await this.readFrqGainFromFirestore(ID, OEM, true).then(data => {
2403
- return data;
2404
- });
2405
- // await this.readFrqGain(ID, OEM).then(data => {
2406
- // return data;
2407
- // });
2408
-
2409
- // lCalib = await this.readGainat1000Hz(ID, OEM);
2410
- lCalib = await this.readGainat1000HzFromFirestore(ID, OEM, true);
2411
- micInfo['gainDBSPL'] = lCalib;
2412
- // this.componentGainDBSPL = this.convertToDB(lCalib);
2413
- this.componentGainDBSPL = lCalib;
2414
- //TODO: if this call to database is unknown, cannot perform experiment => return false
2415
- if (this.componentIR == null) {
2416
- this.status =
2417
- `Microphone (${OEM},${ID}) is not found in the database. Please add it to the database.`.toString();
2418
- this.emit('update', {message: this.status});
2419
- return false;
2420
- }
2421
- } else {
2422
- this.transducerType = 'Microphone';
2423
- this.componentIR = componentIR;
2424
- lCalib = this.findGainatFrequency(this.componentIR.Freq, this.componentIR.Gain, 1000);
2425
- // this.componentGainDBSPL = this.convertToDB(lCalib);
2426
- this.componentGainDBSPL = lCalib;
2427
- // await this.writeIsSmartPhone(ID, isSmartPhone, OEM);
2428
- }
2429
-
2430
- this.oldComponentIR = JSON.parse(JSON.stringify(this.componentIR));
2431
-
2432
- return await new Promise(async (resolve, reject) => {
2433
- // add event listner to params.restartButton to resolve the promise with a string: 'restart'
2434
-
2435
- if (restartButton) {
2436
- restartButton.style.display = '';
2437
- restartButton.addEventListener('click', () => {
2438
- this.stopCalibrationAudio();
2439
- this.isCalibrating = true;
2440
- restartButton.style.display = 'none';
2441
- resolve('restart');
2442
- });
2443
- }
2444
- let volumeResults = await this.startCalibrationVolume(
2445
- stream,
2446
- gainValues,
2447
- lCalib,
2448
- this.componentGainDBSPL
2449
- );
2450
- if (!volumeResults) return;
2451
- let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);
2452
- if (!impulseResponseResults) return;
2453
- impulseResponseResults['background_noise'] = this.background_noise;
2454
- if (componentIR != null) {
2455
- // I corrected microphone/loudpeaker IR scale in easyeyes,
2456
- // but since we write microphone IR to firestore here
2457
- // we need to correct microphone IR here
2458
- let correctGain =
2459
- Math.round((volumeResults.parameters.gainDBSPL - this.componentGainDBSPL) * 10) / 10;
2460
-
2461
- let IrFreq = impulseResponseResults?.component.ir.Freq.map(freq => Math.round(freq));
2462
- let IrGain = impulseResponseResults?.component?.ir.Gain;
2463
- const IrGainAt1000Hz = IrGain[IrFreq.findIndex(freq => freq === 1000)];
2464
- const difference = Math.round(10 * (IrGainAt1000Hz - correctGain)) / 10;
2465
- IrGain = IrGain.map(gain => gain - difference);
2466
- micInfo['mlsSD'] = this.recordingChecks.unfiltered[0].sd;
2467
- console.log(typeof micInfo['componentCorrectionSD']);
2468
- micInfo['systemCorrectionSD'] = Number(this.SDofFilteredRange['system']);
2469
- micInfo['componentCorrectionSD'] = Number(this.SDofFilteredRange['component']);
2470
- console.log(typeof micInfo['componentCorrectionSD']);
2471
- const id = await this.writeIsSmartPhoneToFirestore(ID, isSmartPhone, OEM);
2472
- await this.writeMicrophoneInfoToFirestore(ID, micInfo, OEM, id);
2473
- await this.writeFrqGainToFirestore(ID, IrFreq, IrGain, OEM, id);
2474
- micInfo['gainDBSPL'] = impulseResponseResults.component.gainDBSPL;
2475
- await this.writeGainat1000HzToFirestore(ID, micInfo['gainDBSPL'], OEM, id);
2476
- // await this.writeGainat1000Hz(ID, micInfo['gainDBSPL'], OEM);
2477
- }
2478
- const total_results = {...volumeResults, ...impulseResponseResults};
2479
- total_results['filteredMLSRange'] = this.filteredMLSRange;
2480
- total_results['micInfo'] = micInfo;
2481
- total_results['audioInfo'] = {};
2482
- total_results['audioInfo']['sinkSampleRate'] = this.sinkSamplingRate;
2483
- total_results['audioInfo']['sourceSampleRate'] = this.sourceSamplingRate;
2484
- total_results['audioInfo']['bitsPerSample'] = this.sampleSize;
2485
- const timeStampresult = [...this.timeStamp].join('\n');
2486
- total_results['timeStamps'] = timeStampresult;
2487
- total_results['recordingChecks'] = this.recordingChecks;
2488
- total_results['component']['phase'] = this.componentIRPhase;
2489
- total_results['system']['phase'] = this.systemIRPhase;
2490
- total_results['qualityMetrics'] = {
2491
- mlsSD: this.recordingChecks.unfiltered[0].sd,
2492
- correctionSD: this.SDofFilteredRange,
2493
- };
2494
- console.log('total results');
2495
- console.log(total_results);
2496
- console.log('Time Stamps');
2497
- console.log(timeStampresult);
2498
-
2499
- resolve(total_results);
2500
- });
2501
- };
2502
- }
2503
-
2504
- export default Combination;
1
+ import AudioCalibrator from '../audioCalibrator';
2
+
3
+ import {
4
+ sleep,
5
+ csvToArray,
6
+ saveToCSV,
7
+ saveToJSON,
8
+ findMinValue,
9
+ findMaxValue,
10
+ getCurrentTimeString,
11
+ standardDeviation,
12
+ interpolate,
13
+ } from '../../utils';
14
+ import database from '../../config/firebase';
15
+ import {ref, set, get, child} from 'firebase/database';
16
+ import {
17
+ doc,
18
+ getDoc,
19
+ collection,
20
+ addDoc,
21
+ updateDoc,
22
+ setDoc,
23
+ arrayUnion,
24
+ getDocs,
25
+ query,
26
+ where,
27
+ } from 'firebase/firestore';
28
+
29
+ /**
30
+ *
31
+ */
32
+ class Combination extends AudioCalibrator {
33
+ /**
34
+ * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.
35
+ *
36
+ * @param {Object<boolean, number, number, number>} calibratorParams - paramter object
37
+ * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures
38
+ * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated
39
+ * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform
40
+ * @param {number} [calibratorParams.numMLSPerCapture = 2] - number of bursts of MLS per capture
41
+ */
42
+ constructor({
43
+ download = false,
44
+ mlsOrder = 18,
45
+ numCaptures = 3,
46
+ numMLSPerCapture = 2,
47
+ lowHz = 20,
48
+ highHz = 10000,
49
+ }) {
50
+ super(numCaptures, numMLSPerCapture);
51
+ this.#mlsOrder = parseInt(mlsOrder, 10);
52
+ this.#P = 2 ** mlsOrder - 1;
53
+ this.#download = download;
54
+ this.#mls = [];
55
+ this.#lowHz = lowHz;
56
+ this.#highHz = highHz;
57
+ }
58
+
59
+ /** @private */
60
+ stepNum = 0;
61
+
62
+ /** @private */
63
+ totalSteps = 25;
64
+
65
+ /** @private */
66
+ #download;
67
+
68
+ /** @private */
69
+ #mlsGenInterface;
70
+
71
+ /** @private */
72
+ #mlsBufferView;
73
+
74
+ /** @private */
75
+ componentInvertedImpulseResponse = null;
76
+
77
+ /** @private */
78
+ systemInvertedImpulseResponse = null;
79
+
80
+ //averaged and subtracted ir returned from calibration used to calculated iir
81
+ /** @private */
82
+ ir = null;
83
+
84
+ /** @private */
85
+ impulseResponses = [];
86
+
87
+ /** @private */
88
+ #mlsOrder;
89
+
90
+ /** @private */
91
+ #lowHz;
92
+
93
+ /** @private */
94
+ #highHz;
95
+
96
+ /** @private */
97
+ #mls;
98
+
99
+ /** @private */
100
+ #P;
101
+
102
+ /** @private */
103
+ #audioContext;
104
+
105
+ /** @private */
106
+ TAPER_SECS = 5;
107
+
108
+ /** @private */
109
+ offsetGainNode;
110
+
111
+ /** @private */
112
+ componentConvolution;
113
+
114
+ /** @private */
115
+ componentIROrigin = {
116
+ Freq: [],
117
+ Gain: [],
118
+ };
119
+
120
+ /** @private */
121
+ systemConvolution;
122
+
123
+ ////////////////////////volume
124
+ /** @private */
125
+ #CALIBRATION_TONE_FREQUENCY = 1000; // Hz
126
+
127
+ /** @private */
128
+ #CALIBRATION_TONE_TYPE = 'sine';
129
+
130
+ CALIBRATION_TONE_DURATION = 5; // seconds
131
+ calibrateSound1000HzPreSec = 3.5;
132
+ calibrateSound1000HzSec = 1.0;
133
+ calibrateSound1000HzPostSec = 0.5;
134
+
135
+ /** @private */
136
+ outDBSPL = null;
137
+ THD = null;
138
+ outDBSPL1000 = null;
139
+
140
+ /** @private */
141
+ TAPER_SECS = 0.01; // seconds
142
+
143
+ /** @private */
144
+ status_denominator = 8;
145
+
146
+ /** @private */
147
+ status_numerator = 0;
148
+
149
+ /** @private */
150
+ percent_complete = 0;
151
+
152
+ /** @private */
153
+ status = ``;
154
+
155
+ /**@private */
156
+ 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>`;
157
+
158
+ /**@private */
159
+ componentIR = null;
160
+
161
+ /**@private */
162
+ oldComponentIR = null;
163
+
164
+ /**@private */
165
+ systemIR = null;
166
+
167
+ /**@private */
168
+ _calibrateSoundCheck = '';
169
+
170
+ deviceType = null;
171
+
172
+ deviceName = null;
173
+
174
+ deviceInfo = null;
175
+
176
+ desired_time_per_mls = 0;
177
+
178
+ num_mls_to_skip = 0;
179
+
180
+ desired_sampling_rate = 0;
181
+
182
+ #currentConvolution = [];
183
+
184
+ mode = 'unfiltered';
185
+
186
+ sourceNode;
187
+
188
+ autocorrelations = [];
189
+
190
+ iirLength = 0;
191
+
192
+ irLength = 0;
193
+
194
+ componentInvertedImpulseResponseNoBandpass = [];
195
+
196
+ componentIRInTimeDomain = [];
197
+
198
+ systemInvertedImpulseResponseNoBandpass = [];
199
+
200
+ _calibrateSoundBackgroundSecs;
201
+
202
+ _calibrateSoundSmoothOctaves;
203
+
204
+ background_noise = {};
205
+
206
+ numSuccessfulBackgroundCaptured;
207
+
208
+ _calibrateSoundBurstDb;
209
+
210
+ SDofFilteredRange = {
211
+ component: undefined,
212
+ system: undefined,
213
+ };
214
+
215
+ transducerType = 'Loudspeaker';
216
+
217
+ componentIRPhase = [];
218
+
219
+ systemIRPhase = [];
220
+
221
+ webAudioDeviceNames = {loudspeaker: '', microphone: '', loudspeakerText: '', microphoneText: ''};
222
+
223
+ recordingChecks = {
224
+ volume: {},
225
+ unfiltered: [],
226
+ system: [],
227
+ component: [],
228
+ };
229
+
230
+ inDB;
231
+
232
+ soundCheck = '';
233
+
234
+ filteredMLSRange = {
235
+ component: {
236
+ Min: null,
237
+ Max: null,
238
+ },
239
+ system: {
240
+ Min: null,
241
+ Max: null,
242
+ },
243
+ };
244
+
245
+ /** @private */
246
+ timeStamp = [];
247
+
248
+ /** @private */
249
+ startTime;
250
+
251
+ restartCalibration = false;
252
+
253
+ /**generate string template that gets reevaluated as variable increases */
254
+ generateTemplate = () => {
255
+ if (this.isCalibrating) {
256
+ return '';
257
+ }
258
+ if (this.percent_complete > 100) {
259
+ this.percent_complete = 100;
260
+ }
261
+ let MLSsd = '';
262
+ let componentSD = '';
263
+ let systemSD = '';
264
+ const reportWebAudioNames = `<br>${this.webAudioDeviceNames.loudspeakerText} <br> ${this.webAudioDeviceNames.microphoneText}`;
265
+ const reportParameters = `<br> Sampling: Loudspeaker ${this.sourceSamplingRate} Hz, Microphone ${this.sinkSamplingRate} Hz, ${this.sampleSize} bits`;
266
+ if (this.recordingChecks['unfiltered'].length > 0) {
267
+ MLSsd = `<br> Recorded MLS power SD: ${this.recordingChecks['unfiltered'][0].sd} dB`;
268
+ }
269
+ if (this.SDofFilteredRange['system']) {
270
+ systemSD = `<br> Loudspeaker+Microphone correction SD: ${this.SDofFilteredRange['system']} dB`;
271
+ }
272
+ if (this.SDofFilteredRange['component']) {
273
+ componentSD = `<br> ${this.transducerType} correction SD: ${this.SDofFilteredRange['component']} dB`;
274
+ }
275
+ 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>`;
276
+ return reportWebAudioNames + reportParameters + MLSsd + systemSD + componentSD + template;
277
+ };
278
+
279
+ /** increment numerator and percent for status bar */
280
+ incrementStatusBar = () => {
281
+ this.status_numerator += 1;
282
+ this.percent_complete = (this.status_numerator / this.status_denominator) * 100;
283
+ };
284
+
285
+ setDeviceType = deviceType => {
286
+ this.deviceType = deviceType;
287
+ };
288
+
289
+ setDeviceName = deviceName => {
290
+ this.deviceName = deviceName;
291
+ };
292
+
293
+ setDeviceInfo = deviceInfo => {
294
+ this.deviceInfo = deviceInfo;
295
+ };
296
+
297
+ /** .
298
+ * .
299
+ * .
300
+ * Sends all the computed impulse responses to the backend server for processing
301
+ *
302
+ * @returns sets the resulting inverted impulse response to the class property
303
+ * @example
304
+ */
305
+ sendSystemImpulseResponsesToServerForProcessing = async () => {
306
+ this.addTimeStamp('Get system iir');
307
+ const computedIRs = await Promise.all(this.impulseResponses);
308
+ const filteredComputedIRs = computedIRs.filter(element => {
309
+ return element != undefined;
310
+ }); //log any errors that are found in this step
311
+ const mls = this.#mls;
312
+ const lowHz = this.#lowHz; //gain of 1 below cutoff, need gain of 0
313
+ const highHz = this.#highHz; //check error for anything other than 10 kHz
314
+ const iirLength = this.iirLength;
315
+ const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
316
+ this.stepNum += 1;
317
+ console.log('send impulse responses to server: ' + this.stepNum);
318
+ this.status =
319
+ `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
320
+ this.emit('update', {message: this.status});
321
+ return this.pyServerAPI
322
+ .getSystemInverseImpulseResponseWithRetry({
323
+ payload: filteredComputedIRs.slice(0, this.numCaptures),
324
+ mls,
325
+ lowHz,
326
+ highHz,
327
+ iirLength,
328
+ num_periods,
329
+ sampleRate: this.sourceSamplingRate || 96000,
330
+ calibrateSoundBurstDb: this._calibrateSoundBurstDb,
331
+ })
332
+ .then(res => {
333
+ console.log(res);
334
+ this.stepNum += 1;
335
+ console.log('got impulse response ' + this.stepNum);
336
+ this.incrementStatusBar();
337
+ this.status =
338
+ `All Hz Calibration: done computing the IIR...`.toString() +
339
+ this.generateTemplate().toString();
340
+ this.emit('update', {message: this.status});
341
+ this.systemInvertedImpulseResponse = res['iir'];
342
+ this.systemIR = res['ir'];
343
+ this.systemConvolution = res['convolution'];
344
+ this.systemInvertedImpulseResponseNoBandpass = res['iirNoBandpass'];
345
+ })
346
+ .catch(err => {
347
+ console.error(err);
348
+ });
349
+ };
350
+
351
+ /** .
352
+ * .
353
+ * .
354
+ * Sends all the computed impulse responses to the backend server for processing
355
+ *
356
+ * @returns sets the resulting inverted impulse response to the class property
357
+ * @example
358
+ */
359
+ sendComponentImpulseResponsesToServerForProcessing = async () => {
360
+ this.addTimeStamp('Get component iir');
361
+ const computedIRs = await Promise.all(this.impulseResponses);
362
+ const filteredComputedIRs = computedIRs.filter(element => {
363
+ return element != undefined;
364
+ });
365
+ const componentIRGains = this.componentIR['Gain'];
366
+ const componentIRFreqs = this.componentIR['Freq'];
367
+ const mls = this.#mls;
368
+ const lowHz = this.#lowHz;
369
+ const iirLength = this.iirLength;
370
+ const irLength = this.irLength;
371
+ const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
372
+ const highHz = this.#highHz;
373
+ this.stepNum += 1;
374
+ console.log('send impulse responses to server: ' + this.stepNum);
375
+ this.status =
376
+ `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
377
+ this.emit('update', {message: this.status});
378
+ return this.pyServerAPI
379
+ .getComponentInverseImpulseResponseWithRetry({
380
+ payload: filteredComputedIRs.slice(0, this.numCaptures),
381
+ mls,
382
+ lowHz,
383
+ highHz,
384
+ iirLength,
385
+ componentIRGains,
386
+ componentIRFreqs,
387
+ num_periods,
388
+ sampleRate: this.sourceSamplingRate || 96000,
389
+ calibrateSoundBurstDb: this._calibrateSoundBurstDb,
390
+ irLength,
391
+ calibrateSoundSmoothOctaves: this._calibrateSoundSmoothOctaves,
392
+ })
393
+ .then(res => {
394
+ console.log(res);
395
+ this.stepNum += 1;
396
+ console.log('got impulse response ' + this.stepNum);
397
+ this.incrementStatusBar();
398
+ this.status =
399
+ `All Hz Calibration: done computing the IIR...`.toString() +
400
+ this.generateTemplate().toString();
401
+ this.emit('update', {message: this.status});
402
+ this.componentInvertedImpulseResponse = res['iir'];
403
+ this.componentIR['Gain'] = res['ir'];
404
+ this.componentIR['Freq'] = res['frequencies'];
405
+ this.componentIRPhase = res['component_angle'];
406
+ this.systemIRPhase = res['system_angle'];
407
+ this.componentIROrigin['Freq'] = res['frequencies'];
408
+ this.componentIROrigin['Gain'] = res['irOrigin'];
409
+ this.componentConvolution = res['convolution'];
410
+ this.componentInvertedImpulseResponseNoBandpass = res['iirNoBandpass'];
411
+ this.componentIRInTimeDomain = res['irTime'];
412
+ })
413
+ .catch(err => {
414
+ // this.emit('InvertedImpulseResponse', {res: false});
415
+ console.error(err);
416
+ });
417
+ };
418
+
419
+ sendBackgroundRecording = () => {
420
+ const allSignals = this.getAllBackgroundRecordings();
421
+ const numSignals = allSignals.length;
422
+ const background_rec_whole = allSignals[numSignals - 1];
423
+ const fraction = 0.5 / (this._calibrateSoundBackgroundSecs + 0.5);
424
+ // Calculate the starting index for slicing the array
425
+ const startIndex = Math.round(fraction * background_rec_whole.length);
426
+ // Slice the array from the calculated start index to the end of the array
427
+ const background_rec = background_rec_whole.slice(startIndex);
428
+ console.log('Sending background recording to server for processing');
429
+ this.addTimeStamp('Get background PSD');
430
+ this.pyServerAPI
431
+ .getBackgroundNoisePSDWithRetry({
432
+ background_rec,
433
+ sampleRate: this.sourceSamplingRate || 96000,
434
+ })
435
+ .then(res => {
436
+ if (this.numSuccessfulBackgroundCaptured < 1) {
437
+ this.numSuccessfulBackgroundCaptured += 1;
438
+ //storing all background data in background_psd object
439
+ this.background_noise['x_background'] = res['x_background'];
440
+ this.background_noise['y_background'] = res['y_background'];
441
+ this.background_noise['recording'] = background_rec;
442
+ }
443
+ })
444
+ .catch(err => {
445
+ console.error(err);
446
+ });
447
+ };
448
+
449
+ /** .
450
+ * .
451
+ * .
452
+ * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing
453
+ *
454
+ * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed
455
+ * @example
456
+ */
457
+ sendRecordingToServerForProcessing = async signalCsv => {
458
+ const allSignals = this.getAllUnfilteredRecordedSignals();
459
+ console.log(
460
+ 'Obtaining last all hz unfiltered recording from #allHzUnfilteredRecordings to send to server for processing'
461
+ );
462
+ const numSignals = allSignals.length;
463
+ const mls = this.#mlsBufferView;
464
+ const payload =
465
+ signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
466
+ console.log('sending rec');
467
+ this.stepNum += 1;
468
+ console.log('send rec ' + this.stepNum);
469
+ this.status =
470
+ `All Hz Calibration Step: computing the IR of the last recording...`.toString() +
471
+ this.generateTemplate().toString();
472
+ this.emit('update', {message: this.status});
473
+ if (this.isCalibrating) return null;
474
+ await this.pyServerAPI
475
+ .allHzPowerCheck({
476
+ payload,
477
+ sampleRate: this.sourceSamplingRate || 96000,
478
+ binDesiredSec: this._calibrateSoundPowerBinDesiredSec,
479
+ burstSec: this.desired_time_per_mls,
480
+ })
481
+ .then(result => {
482
+ if (result) {
483
+ if (result['sd'] < this._calibrateSoundPowerDbSDToleratedDb) {
484
+ this.recordingChecks['unfiltered'].push(result);
485
+ this.impulseResponses.push(
486
+ this.pyServerAPI
487
+ .getImpulseResponse({
488
+ sampleRate: this.sourceSamplingRate || 96000,
489
+ payload,
490
+ mls,
491
+ P: this.#P, //get rid of this
492
+ numPeriods: this.numMLSPerCapture,
493
+ })
494
+ .then(res => {
495
+ if (this.numSuccessfulCaptured < this.numCaptures) {
496
+ this.numSuccessfulCaptured += 1;
497
+ console.log('num succ capt: ' + this.numSuccessfulCaptured);
498
+ this.stepNum += 1;
499
+ console.log('got impulse response ' + this.stepNum);
500
+ this.incrementStatusBar();
501
+ this.status =
502
+ `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +
503
+ this.generateTemplate().toString();
504
+ this.emit('update', {
505
+ message: this.status,
506
+ });
507
+ this.autocorrelations.push(res['autocorrelation']);
508
+ return res['ir'];
509
+ }
510
+ })
511
+ .catch(err => {
512
+ console.error(err);
513
+ })
514
+ );
515
+ } else if (result['sd'] > this._calibrateSoundPowerDbSDToleratedDb) {
516
+ this.clearLastUnfilteredRecordedSignals();
517
+ console.log('unfiltered rec', this.getAllUnfilteredRecordedSignals.length);
518
+ }
519
+ }
520
+ })
521
+ .catch(err => {
522
+ console.error(err);
523
+ });
524
+ };
525
+
526
+ /**
527
+ * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number
528
+ * of MLS periods defined in the constructor.
529
+ *
530
+ * @example
531
+ */
532
+ #awaitDesiredMLSLength = async () => {
533
+ // seconds per MLS = P / SR
534
+ // await N * P / SR
535
+ this.stepNum += 1;
536
+ console.log('await desired length ' + this.stepNum);
537
+ this.status =
538
+ `All Hz Calibration: sampling the calibration signal...`.toString() +
539
+ `\niteration ${this.stepNum}` +
540
+ this.generateTemplate();
541
+ this.emit('update', {
542
+ message: this.status,
543
+ });
544
+ let time_to_wait = 0;
545
+ if (this.mode === 'unfiltered') {
546
+ //unfiltered
547
+ time_to_wait = (this.#mls.length / this.sourceSamplingRate) * this.numMLSPerCapture;
548
+ time_to_wait = time_to_wait * 1.1;
549
+ } else if (this.mode === 'filtered') {
550
+ //filtered
551
+ // time_to_wait =
552
+ // (this.#currentConvolution.length / this.sourceSamplingRate) *
553
+ // (this.numMLSPerCapture / (this.num_mls_to_skip + this.numMLSPerCapture));
554
+ time_to_wait =
555
+ (this.#currentConvolution.length / this.sourceSamplingRate) * this.numMLSPerCapture;
556
+ time_to_wait = time_to_wait * 1.1;
557
+ } else {
558
+ throw new Error('Mode broke in awaitDesiredMLSLength');
559
+ }
560
+
561
+ await sleep(time_to_wait);
562
+ };
563
+
564
+ /**
565
+ * Passed to the background noise recording function, awaits the desired amount of seconds to capture the desired number
566
+ * of seconds of background noise
567
+ *
568
+ * @example
569
+ */
570
+ #awaitBackgroundNoiseRecording = async () => {
571
+ console.log(
572
+ 'Waiting ' + this._calibrateSoundBackgroundSecs + ' second(s) to record background noise'
573
+ );
574
+ let time_to_wait = this._calibrateSoundBackgroundSecs + 0.5;
575
+ await sleep(time_to_wait);
576
+ };
577
+
578
+ /** .
579
+ * .
580
+ * .
581
+ * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state
582
+ *
583
+ * @example
584
+ */
585
+ #awaitSignalOnset = async () => {
586
+ this.stepNum += 1;
587
+ console.log('await signal onset ' + this.stepNum);
588
+ this.status =
589
+ `All Hz Calibration: waiting for the signal to stabilize...`.toString() +
590
+ this.generateTemplate();
591
+ this.emit('update', {
592
+ message: this.status,
593
+ });
594
+ let number_of_bursts_to_skip = this.num_mls_to_skip;
595
+ let time_to_sleep = 0;
596
+ if (this.mode === 'unfiltered') {
597
+ time_to_sleep = (this.#mls.length / this.sourceSamplingRate) * number_of_bursts_to_skip;
598
+ } else if (this.mode === 'filtered') {
599
+ console.log(this.#currentConvolution.length);
600
+ // time_to_sleep =
601
+ // (this.#currentConvolution.length / this.sourceSamplingRate) *
602
+ // (number_of_bursts_to_skip / (number_of_bursts_to_skip + this.numMLSPerCapture));
603
+ time_to_sleep =
604
+ (this.#currentConvolution.length / this.sourceSamplingRate) * number_of_bursts_to_skip;
605
+ } else {
606
+ throw new Error('Mode broke in awaitSignalOnset');
607
+ }
608
+ await sleep(time_to_sleep);
609
+ };
610
+
611
+ /**
612
+ * Called immediately after a recording is captured. Used to process the resulting signal
613
+ * whether by sending the result to a server or by computing a result locally.
614
+ *
615
+ * @example
616
+ */
617
+ #afterMLSRecord = () => {
618
+ console.log('after record');
619
+ this.sendRecordingToServerForProcessing();
620
+ };
621
+
622
+ #afterMLSwIIRRecord = async () => {
623
+ await this.checkPowerVariation();
624
+ };
625
+
626
+ /** .
627
+ * .
628
+ * .
629
+ * Created an S Curver Buffer to taper the signal onset
630
+ *
631
+ * @param {*} length
632
+ * @param {*} phase
633
+ * @returns
634
+ * @example
635
+ */
636
+ static createSCurveBuffer = (length, phase) => {
637
+ const curve = new Float32Array(length);
638
+ let i;
639
+ for (i = 0; i < length; i += 1) {
640
+ // scale the curve to be between 0-1
641
+ curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;
642
+ }
643
+ return curve;
644
+ };
645
+
646
+ static createInverseSCurveBuffer = (length, phase) => {
647
+ const curve = new Float32Array(length);
648
+ let i;
649
+ let j = length - 1;
650
+ for (i = 0; i < length; i += 1) {
651
+ // scale the curve to be between 0-1
652
+ curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;
653
+ j -= 1;
654
+ }
655
+ return curve;
656
+ };
657
+
658
+ /**
659
+ * Construct a Calibration Node with the calibration parameters.
660
+ *
661
+ * @param dataBuffer
662
+ * @private
663
+ * @example
664
+ */
665
+ #createCalibrationNodeFromBuffer = dataBuffer => {
666
+ console.log('length databuffer');
667
+ console.log(dataBuffer.length);
668
+ if (!this.sourceAudioContext) {
669
+ this.makeNewSourceAudioContext();
670
+ }
671
+
672
+ const buffer = this.sourceAudioContext.createBuffer(
673
+ 1, // number of channels
674
+ dataBuffer.length,
675
+ this.sourceAudioContext.sampleRate // sample rate
676
+ );
677
+
678
+ const data = buffer.getChannelData(0); // get data
679
+
680
+ // fill the buffer with our data
681
+ try {
682
+ for (let i = 0; i < dataBuffer.length; i += 1) {
683
+ data[i] = dataBuffer[i];
684
+ }
685
+ } catch (error) {
686
+ console.error(error);
687
+ }
688
+
689
+ this.sourceNode = this.sourceAudioContext.createBufferSource();
690
+
691
+ this.sourceNode.buffer = buffer;
692
+
693
+ if (this.mode === 'filtered') {
694
+ //used to not loop filtered
695
+ this.sourceNode.loop = true;
696
+ } else {
697
+ this.sourceNode.loop = true;
698
+ }
699
+
700
+ this.sourceNode.connect(this.sourceAudioContext.destination);
701
+
702
+ this.addCalibrationNode(this.sourceNode);
703
+ };
704
+
705
+ /**
706
+ * Given a data buffer, creates the required calibration node
707
+ *
708
+ * @param {*} dataBufferArray
709
+ * @example
710
+ */
711
+ #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {
712
+ if (dataBufferArray.length === 1) {
713
+ this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);
714
+ } else {
715
+ throw new Error('The length of the data buffer array must be 1');
716
+ }
717
+ };
718
+
719
+ /**
720
+ * Creates an audio context and plays it for a few seconds.
721
+ *
722
+ * @private
723
+ * @returns - Resolves when the audio is done playing.
724
+ * @example
725
+ */
726
+ #playCalibrationAudio = () => {
727
+ this.addTimeStamp('Play unfiltered mls');
728
+ this.calibrationNodes[0].start(0);
729
+ this.status = ``;
730
+ if (this.mode === 'unfiltered') {
731
+ console.log('mls', this.#mls); // before multiplied by calibrateSoundBurstDb
732
+ console.log('mls buffer view', this.#mlsBufferView); // after multiplied by calibrateSoundBurstDb
733
+ console.log('play calibration audio ' + this.stepNum);
734
+ this.status =
735
+ `All Hz Calibration: playing the calibration tone...`.toString() +
736
+ this.generateTemplate().toString();
737
+ } else if (this.mode === 'filtered') {
738
+ console.log('play convolved audio ' + this.stepNum);
739
+ this.status =
740
+ `All Hz Calibration: playing the convolved calibration tone...`.toString() +
741
+ this.generateTemplate().toString();
742
+ } else {
743
+ throw new Error('Mode is incorrect');
744
+ }
745
+ this.emit('update', {message: this.status});
746
+ this.stepNum += 1;
747
+ console.log('sink sampling rate');
748
+ console.log(this.sinkSamplingRate);
749
+ console.log('source sampling rate');
750
+ console.log(this.sourceSamplingRate);
751
+ console.log('sample size');
752
+ console.log(this.sampleSize);
753
+ };
754
+
755
+ /** .
756
+ * .
757
+ * .
758
+ * Stops the audio with tapered offset
759
+ *
760
+ * @example
761
+ */
762
+ stopCalibrationAudio = () => {
763
+ if (this.calibrationNodes.length === 0) {
764
+ return;
765
+ }
766
+ this.calibrationNodes[0].stop(0);
767
+ this.calibrationNodes = [];
768
+ if (this.sourceNode) this.sourceNode.disconnect();
769
+ this.stepNum += 1;
770
+ console.log('stop calibration audio ' + this.stepNum);
771
+ this.status =
772
+ `All Hz Calibration: stopping the calibration tone...`.toString() +
773
+ this.generateTemplate().toString();
774
+ this.emit('update', {message: this.status});
775
+ };
776
+
777
+ playMLSwithIIR = async (stream, convolution) => {
778
+ let checkRec = false;
779
+ this.mode = 'filtered';
780
+ console.log('play mls with iir');
781
+ //this.invertedImpulseResponse = iir
782
+
783
+ await this.calibrationSteps(
784
+ stream,
785
+ this.#playCalibrationAudio, // play audio func (required)
786
+ this.#createCalibrationNodeFromBuffer(convolution), // before play func
787
+ this.#awaitSignalOnset, // before record
788
+ () => this.numSuccessfulCaptured < 1,
789
+ this.#awaitDesiredMLSLength, // during record
790
+ this.#afterMLSwIIRRecord, // after record
791
+ this.mode,
792
+ checkRec
793
+ );
794
+ };
795
+
796
+ bothSoundCheck = async stream => {
797
+ let iir_ir_and_plots;
798
+ this.#currentConvolution = this.componentConvolution;
799
+ this.filteredMLSRange.component.Min = findMinValue(this.#currentConvolution);
800
+ this.filteredMLSRange.component.Max = findMaxValue(this.#currentConvolution);
801
+ this.addTimeStamp('Play MLS with component IIR');
802
+ this.soundCheck = 'component';
803
+ if (this.isCalibrating) return null;
804
+ await this.playMLSwithIIR(stream, this.#currentConvolution);
805
+ this.stopCalibrationAudio();
806
+ let component_conv_recs = this.getAllFilteredRecordedSignals();
807
+ let return_component_conv_rec = component_conv_recs[component_conv_recs.length - 1];
808
+ this.clearAllFilteredRecordedSignals();
809
+ // await this.checkPowerVariation(return_component_conv_rec);
810
+ this.numSuccessfulCaptured = 0;
811
+ this.#currentConvolution = this.systemConvolution;
812
+ this.filteredMLSRange.system.Min = findMinValue(this.#currentConvolution);
813
+ this.filteredMLSRange.system.Max = findMaxValue(this.#currentConvolution);
814
+ this.soundCheck = 'system';
815
+ this.addTimeStamp('Play MLS with system IIR');
816
+ if (this.isCalibrating) return null;
817
+ await this.playMLSwithIIR(stream, this.#currentConvolution);
818
+
819
+ this.stopCalibrationAudio();
820
+
821
+ let system_conv_recs = this.getAllFilteredRecordedSignals();
822
+ let return_system_conv_rec = system_conv_recs[system_conv_recs.length - 1];
823
+ // await this.checkPowerVariation(return_system_conv_rec);
824
+
825
+ this.clearAllFilteredRecordedSignals();
826
+
827
+ this.sourceAudioContext.close();
828
+ let recs = this.getAllUnfilteredRecordedSignals();
829
+ let unconv_rec = recs[0];
830
+ let return_unconv_rec = unconv_rec;
831
+ let conv_rec = component_conv_recs[component_conv_recs.length - 1];
832
+
833
+ //psd of component
834
+ let knownGain = this.oldComponentIR.Gain;
835
+ let knownFreq = this.oldComponentIR.Freq;
836
+ let sampleRate = this.sourceSamplingRate || 96000;
837
+ this.addTimeStamp('Get PSD of mls recording');
838
+ if (this.isCalibrating) return null;
839
+ let component_unconv_rec_psd = await this.pyServerAPI
840
+ .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)
841
+ .then(res => {
842
+ this.incrementStatusBar();
843
+ this.status =
844
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
845
+ this.generateTemplate().toString();
846
+ this.emit('update', {message: this.status});
847
+ return res;
848
+ })
849
+ .catch(err => {
850
+ console.error(err);
851
+ });
852
+
853
+ this.addTimeStamp('Get PSD of filtered recording (component)');
854
+ if (this.isCalibrating) return null;
855
+ let component_conv_rec_psd = await this.pyServerAPI
856
+ .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)
857
+ .then(res => {
858
+ let interpolatedGain = res.x.map((freq, index) => {
859
+ let i = 0;
860
+ while (i < knownFreq.length && knownFreq[i] < freq) {
861
+ i++;
862
+ }
863
+ if (i === 0 || i === knownFreq.length) {
864
+ return knownGain[i];
865
+ }
866
+ return interpolate(freq, knownFreq[i - 1], knownFreq[i], knownGain[i - 1], knownGain[i]);
867
+ });
868
+ console.log(interpolatedGain);
869
+ let correctedGain = res.y.map(
870
+ (gain, index) => 10 * Math.log10(gain) - interpolatedGain[index]
871
+ );
872
+
873
+ let filtered_psd = correctedGain.filter(
874
+ (value, index) => res.x[index] >= this.#lowHz && res.x[index] <= this.#highHz
875
+ );
876
+
877
+ this.SDofFilteredRange['component'] = standardDeviation(filtered_psd);
878
+ this.incrementStatusBar();
879
+ this.status =
880
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
881
+ this.generateTemplate().toString();
882
+ this.emit('update', {message: this.status});
883
+ return res;
884
+ })
885
+ .catch(err => {
886
+ console.error(err);
887
+ });
888
+
889
+ conv_rec = system_conv_recs[system_conv_recs.length - 1];
890
+ //psd of system
891
+ this.addTimeStamp('Get PSD of filtered recording (system) and unfiltered recording');
892
+ if (this.isCalibrating) return null;
893
+ let system_recs_psd = await this.pyServerAPI
894
+ .getPSDWithRetry({
895
+ unconv_rec,
896
+ conv_rec,
897
+ sampleRate: this.sourceSamplingRate || 96000,
898
+ })
899
+ .then(res => {
900
+ console.log(res);
901
+ let filtered_psd = res.y_conv
902
+ .filter(
903
+ (value, index) => res.x_conv[index] >= this.#lowHz && res.x_conv[index] <= this.#highHz
904
+ )
905
+ .map(value => 10 * Math.log10(value));
906
+
907
+ this.SDofFilteredRange['system'] = standardDeviation(filtered_psd);
908
+ this.incrementStatusBar();
909
+ this.status =
910
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
911
+ this.generateTemplate().toString();
912
+ this.emit('update', {message: this.status});
913
+ return res;
914
+ })
915
+ .catch(err => {
916
+ console.error(err);
917
+ });
918
+
919
+ //iir w/ and without bandpass psd. done
920
+ unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
921
+ conv_rec = this.componentInvertedImpulseResponse;
922
+ this.addTimeStamp('Get PSD of component iir and component iir no band pass');
923
+ if (this.isCalibrating) return null;
924
+ let component_iir_psd = await this.pyServerAPI
925
+ .getPSDWithRetry({
926
+ unconv_rec,
927
+ conv_rec,
928
+ sampleRate: this.sourceSamplingRate || 96000,
929
+ })
930
+ .then(res => {
931
+ this.incrementStatusBar();
932
+ this.status =
933
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
934
+ this.generateTemplate().toString();
935
+ this.emit('update', {message: this.status});
936
+ return res;
937
+ })
938
+ .catch(err => {
939
+ console.error(err);
940
+ });
941
+ unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
942
+ conv_rec = this.systemInvertedImpulseResponse;
943
+ this.addTimeStamp('Get PSD of system iir and system iir no band pass');
944
+ if (this.isCalibrating) return null;
945
+ let system_iir_psd = await this.pyServerAPI
946
+ .getPSDWithRetry({
947
+ unconv_rec,
948
+ conv_rec,
949
+ sampleRate: this.sourceSamplingRate || 96000,
950
+ })
951
+ .then(res => {
952
+ this.incrementStatusBar();
953
+ this.status =
954
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
955
+ this.generateTemplate().toString();
956
+ this.emit('update', {message: this.status});
957
+ return res;
958
+ })
959
+ .catch(err => {
960
+ console.error(err);
961
+ });
962
+
963
+ this.addTimeStamp('Get PSD of mls sequence');
964
+ if (this.isCalibrating) return null;
965
+ let mls_psd = await this.pyServerAPI
966
+ .getMLSPSDWithRetry({mls: this.#mlsBufferView, sampleRate: this.sourceSamplingRate || 96000})
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
+
979
+ this.addTimeStamp('Get PSD of filered mls (system)');
980
+ if (this.isCalibrating) return null;
981
+ let system_filtered_mls_psd = await this.pyServerAPI
982
+ .getMLSPSDWithRetry({
983
+ mls: this.systemConvolution,
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
+ this.addTimeStamp('Get PSD of filered mls (component)');
999
+ if (this.isCalibrating) return null;
1000
+ let component_filtered_mls_psd = await this.pyServerAPI
1001
+ .getMLSPSDWithRetry({
1002
+ mls: this.componentConvolution,
1003
+ sampleRate: this.sourceSamplingRate || 96000,
1004
+ })
1005
+ .then(res => {
1006
+ this.incrementStatusBar();
1007
+ this.status =
1008
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1009
+ this.generateTemplate().toString();
1010
+ this.emit('update', {message: this.status});
1011
+ return res;
1012
+ })
1013
+ .catch(err => {
1014
+ console.error(err);
1015
+ });
1016
+
1017
+ let gainValue = this.getGainDBSPL();
1018
+
1019
+ iir_ir_and_plots = {
1020
+ filtered_recording: {
1021
+ component: return_component_conv_rec,
1022
+ system: return_system_conv_rec,
1023
+ },
1024
+ unfiltered_recording: this.getAllUnfilteredRecordedSignals()[0],
1025
+ system: {
1026
+ iir: this.systemInvertedImpulseResponse,
1027
+ ir: this.systemIR,
1028
+ iir_psd: {
1029
+ y: system_iir_psd['y_conv'],
1030
+ x: system_iir_psd['x_conv'],
1031
+ y_no_bandpass: system_iir_psd['y_unconv'],
1032
+ x_no_bandpass: system_iir_psd['x_unconv'],
1033
+ },
1034
+ filtered_mls_psd: {
1035
+ x: system_filtered_mls_psd['x_mls'],
1036
+ y: system_filtered_mls_psd['y_mls'],
1037
+ },
1038
+ convolution: this.systemConvolution,
1039
+ psd: {
1040
+ unconv: {
1041
+ x: system_recs_psd['x_unconv'],
1042
+ y: system_recs_psd['y_unconv'],
1043
+ },
1044
+ conv: {
1045
+ x: system_recs_psd['x_conv'],
1046
+ y: system_recs_psd['y_conv'],
1047
+ },
1048
+ },
1049
+ },
1050
+ component: {
1051
+ iir: this.componentInvertedImpulseResponse,
1052
+ ir: this.componentIR,
1053
+ ir_origin: this.componentIROrigin,
1054
+ ir_in_time_domain: this.componentIRInTimeDomain,
1055
+ iir_psd: {
1056
+ y: component_iir_psd['y_conv'],
1057
+ x: component_iir_psd['x_conv'],
1058
+ y_no_bandpass: component_iir_psd['y_unconv'],
1059
+ x_no_bandpass: component_iir_psd['x_unconv'],
1060
+ },
1061
+ filtered_mls_psd: {
1062
+ x: component_filtered_mls_psd['x_mls'],
1063
+ y: component_filtered_mls_psd['y_mls'],
1064
+ },
1065
+ convolution: this.componentConvolution,
1066
+ psd: {
1067
+ unconv: {
1068
+ x: component_unconv_rec_psd['x'],
1069
+ y: component_unconv_rec_psd['y'],
1070
+ },
1071
+ conv: {
1072
+ x: component_conv_rec_psd['x'],
1073
+ y: component_conv_rec_psd['y'],
1074
+ },
1075
+ },
1076
+ gainDBSPL: gainValue,
1077
+ },
1078
+ mls: this.#mlsBufferView,
1079
+ mls_psd: {
1080
+ x: mls_psd['x_mls'],
1081
+ y: mls_psd['y_mls'],
1082
+ },
1083
+ autocorrelations: this.autocorrelations,
1084
+ impulseResponses: [],
1085
+ };
1086
+
1087
+ return iir_ir_and_plots;
1088
+ };
1089
+
1090
+ singleSoundCheck = async stream => {
1091
+ let iir_ir_and_plots;
1092
+ if (this._calibrateSoundCheck != 'system') {
1093
+ this.#currentConvolution = this.componentConvolution;
1094
+ this.filteredMLSRange.component.Min = findMinValue(this.#currentConvolution);
1095
+ this.filteredMLSRange.component.Max = findMaxValue(this.#currentConvolution);
1096
+ this.addTimeStamp('Play MLS with component IIR');
1097
+ this.soundCheck = 'component';
1098
+ if (this.isCalibrating) return null;
1099
+ await this.playMLSwithIIR(stream, this.#currentConvolution);
1100
+ this.stopCalibrationAudio();
1101
+ } else {
1102
+ this.#currentConvolution = this.systemConvolution;
1103
+ this.filteredMLSRange.system.Min = findMinValue(this.#currentConvolution);
1104
+ this.filteredMLSRange.system.Max = findMaxValue(this.#currentConvolution);
1105
+ this.addTimeStamp('Play MLS with system IIR');
1106
+ this.soundCheck = 'systen';
1107
+ if (this.isCalibrating) return null;
1108
+ await this.playMLSwithIIR(stream, this.#currentConvolution);
1109
+ this.stopCalibrationAudio();
1110
+ }
1111
+ let conv_recs = this.getAllFilteredRecordedSignals();
1112
+ let recs = this.getAllUnfilteredRecordedSignals();
1113
+ this.clearAllFilteredRecordedSignals();
1114
+ console.log('Obtaining unfiltered recording from #allHzUnfilteredRecordings to calculate PSD');
1115
+ console.log('Obtaining filtered recording from #allHzFilteredRecordings to calculate PSD');
1116
+ let unconv_rec = recs[0];
1117
+ let return_unconv_rec = unconv_rec;
1118
+ let conv_rec = conv_recs[conv_recs.length - 1];
1119
+ let return_conv_rec = conv_rec;
1120
+ this.sourceAudioContext.close();
1121
+ if (this._calibrateSoundCheck != 'system') {
1122
+ let knownGain = this.oldComponentIR.Gain;
1123
+ let knownFreq = this.oldComponentIR.Freq;
1124
+ let sampleRate = this.sourceSamplingRate || 96000;
1125
+ this.addTimeStamp('Get PSD of mls recording');
1126
+ if (this.isCalibrating) return null;
1127
+ let unconv_results = await this.pyServerAPI
1128
+ .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)
1129
+ .then(res => {
1130
+ this.incrementStatusBar();
1131
+ this.status =
1132
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1133
+ this.generateTemplate().toString();
1134
+ this.emit('update', {message: this.status});
1135
+ return res;
1136
+ })
1137
+ .catch(err => {
1138
+ console.error(err);
1139
+ });
1140
+
1141
+ this.addTimeStamp('Get PSD recording of filtered recording (component)');
1142
+ if (this.isCalibrating) return null;
1143
+ let conv_results = await this.pyServerAPI
1144
+ .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)
1145
+ .then(res => {
1146
+ let interpolatedGain = res.x.map((freq, index) => {
1147
+ let i = 0;
1148
+ while (i < knownFreq.length && knownFreq[i] < freq) {
1149
+ i++;
1150
+ }
1151
+ if (i === 0 || i === knownFreq.length) {
1152
+ return knownGain[i];
1153
+ }
1154
+ return interpolate(
1155
+ freq,
1156
+ knownFreq[i - 1],
1157
+ knownFreq[i],
1158
+ knownGain[i - 1],
1159
+ knownGain[i]
1160
+ );
1161
+ });
1162
+
1163
+ console.log(interpolatedGain);
1164
+ let correctedGain = res.y.map(
1165
+ (gain, index) => 10 * Math.log10(gain) - interpolatedGain[index]
1166
+ );
1167
+ let filtered_psd = correctedGain.filter(
1168
+ (value, index) => res.x[index] >= this.#lowHz && res.x[index] <= this.#highHz
1169
+ );
1170
+
1171
+ this.SDofFilteredRange['component'] = standardDeviation(filtered_psd);
1172
+ this.incrementStatusBar();
1173
+ this.status =
1174
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1175
+ this.generateTemplate().toString();
1176
+ this.emit('update', {message: this.status});
1177
+ return res;
1178
+ })
1179
+ .catch(err => {
1180
+ console.error(err);
1181
+ });
1182
+
1183
+ unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
1184
+ conv_rec = this.componentInvertedImpulseResponse;
1185
+ this.addTimeStamp('Get PSD of component iir and component iir no bandpass');
1186
+ if (this.isCalibrating) return null;
1187
+ let component_iir_psd = await this.pyServerAPI
1188
+ .getPSDWithRetry({
1189
+ unconv_rec,
1190
+ conv_rec,
1191
+ sampleRate: this.sourceSamplingRate || 96000,
1192
+ })
1193
+ .then(res => {
1194
+ this.incrementStatusBar();
1195
+ this.status =
1196
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1197
+ this.generateTemplate().toString();
1198
+ this.emit('update', {message: this.status});
1199
+ return res;
1200
+ })
1201
+ .catch(err => {
1202
+ console.error(err);
1203
+ });
1204
+ unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
1205
+ conv_rec = this.systemInvertedImpulseResponse;
1206
+ this.addTimeStamp('Get PSD of system iir and system iir no bandpass');
1207
+ if (this.isCalibrating) return null;
1208
+ let system_iir_psd = await this.pyServerAPI
1209
+ .getPSDWithRetry({
1210
+ unconv_rec,
1211
+ conv_rec,
1212
+ sampleRate: this.sourceSamplingRate || 96000,
1213
+ })
1214
+ .then(res => {
1215
+ this.incrementStatusBar();
1216
+ this.status =
1217
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1218
+ this.generateTemplate().toString();
1219
+ this.emit('update', {message: this.status});
1220
+ return res;
1221
+ })
1222
+ .catch(err => {
1223
+ console.error(err);
1224
+ });
1225
+
1226
+ this.addTimeStamp('Get PSD of mls sequence');
1227
+ if (this.isCalibrating) return null;
1228
+ let mls_psd = await this.pyServerAPI
1229
+ .getMLSPSDWithRetry({
1230
+ mls: this.#mlsBufferView,
1231
+ sampleRate: this.sourceSamplingRate || 96000,
1232
+ })
1233
+ .then(res => {
1234
+ this.incrementStatusBar();
1235
+ this.status =
1236
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1237
+ this.generateTemplate().toString();
1238
+ this.emit('update', {message: this.status});
1239
+ return res;
1240
+ })
1241
+ .catch(err => {
1242
+ console.error(err);
1243
+ });
1244
+
1245
+ this.addTimeStamp('Get PSD of filtered mls (component)');
1246
+ if (this.isCalibrating) return null;
1247
+ let filtered_mls_psd = await this.pyServerAPI
1248
+ .getMLSPSDWithRetry({
1249
+ mls: this.componentConvolution,
1250
+ sampleRate: this.sourceSamplingRate || 96000,
1251
+ })
1252
+ .then(res => {
1253
+ this.incrementStatusBar();
1254
+ this.status =
1255
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1256
+ this.generateTemplate().toString();
1257
+ this.emit('update', {message: this.status});
1258
+ return res;
1259
+ })
1260
+ .catch(err => {
1261
+ console.error(err);
1262
+ });
1263
+
1264
+ let gainValue = this.getGainDBSPL();
1265
+ iir_ir_and_plots = {
1266
+ unfiltered_recording: return_unconv_rec,
1267
+ filtered_recording: return_conv_rec,
1268
+ system: {
1269
+ iir: this.systemInvertedImpulseResponse,
1270
+ ir: this.systemIR,
1271
+ iir_psd: {
1272
+ y: system_iir_psd['y_conv'],
1273
+ x: system_iir_psd['y_conv'],
1274
+ y_no_bandpass: system_iir_psd['y_unconv'],
1275
+ x_no_bandpass: system_iir_psd['x_unconv'],
1276
+ },
1277
+ filtered_recording: [],
1278
+ filtered_mls_psd: {},
1279
+ convolution: this.systemConvolution,
1280
+ psd: {
1281
+ unconv: {
1282
+ x: [],
1283
+ y: [],
1284
+ },
1285
+ conv: {
1286
+ x: [],
1287
+ y: [],
1288
+ },
1289
+ },
1290
+ },
1291
+ component: {
1292
+ iir: this.componentInvertedImpulseResponse,
1293
+ ir: this.componentIR,
1294
+ ir_origin: this.componentIROrigin,
1295
+ ir_in_time_domain: this.componentIRInTimeDomain,
1296
+ iir_psd: {
1297
+ y: component_iir_psd['y_conv'],
1298
+ x: component_iir_psd['x_conv'],
1299
+ y_no_bandpass: component_iir_psd['y_unconv'],
1300
+ x_no_bandpass: component_iir_psd['x_unconv'],
1301
+ },
1302
+ filtered_mls_psd: {
1303
+ x: filtered_mls_psd['x_mls'],
1304
+ y: filtered_mls_psd['y_mls'],
1305
+ },
1306
+ convolution: this.componentConvolution,
1307
+ psd: {
1308
+ unconv: {
1309
+ x: unconv_results['x'],
1310
+ y: unconv_results['y'],
1311
+ },
1312
+ conv: {
1313
+ x: conv_results['x'],
1314
+ y: conv_results['y'],
1315
+ },
1316
+ },
1317
+ gainDBSPL: gainValue,
1318
+ },
1319
+ mls: this.#mlsBufferView,
1320
+ mls_psd: {
1321
+ x: mls_psd['x_mls'],
1322
+ y: mls_psd['y_mls'],
1323
+ },
1324
+ autocorrelations: this.autocorrelations,
1325
+ impulseResponses: [],
1326
+ };
1327
+ } else {
1328
+ this.addTimeStamp('Get PSD of filtered recording (system) and unfiltered recording');
1329
+ if (this.isCalibrating) return null;
1330
+ let results = await this.pyServerAPI
1331
+ .getPSDWithRetry({
1332
+ unconv_rec,
1333
+ conv_rec,
1334
+ sampleRate: this.sourceSamplingRate || 96000,
1335
+ })
1336
+ .then(res => {
1337
+ console.log(res);
1338
+ let filtered_psd = res.y_conv
1339
+ .filter(
1340
+ (value, index) =>
1341
+ res.x_conv[index] >= this.#lowHz && res.x_conv[index] <= this.#highHz
1342
+ )
1343
+ .map(value => 10 * Math.log10(value));
1344
+
1345
+ this.SDofFilteredRange['system'] = standardDeviation(filtered_psd);
1346
+ this.incrementStatusBar();
1347
+ this.status =
1348
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1349
+ this.generateTemplate().toString();
1350
+ this.emit('update', {message: this.status});
1351
+ return res;
1352
+ })
1353
+ .catch(err => {
1354
+ console.error(err);
1355
+ });
1356
+
1357
+ //iir w/ and without bandpass psd
1358
+ unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
1359
+ conv_rec = this.componentInvertedImpulseResponse;
1360
+ this.addTimeStamp('Get PSD of component iir and component iir no band pass');
1361
+ if (this.isCalibrating) return null;
1362
+ let component_iir_psd = await this.pyServerAPI
1363
+ .getPSDWithRetry({
1364
+ unconv_rec,
1365
+ conv_rec,
1366
+ sampleRate: this.sourceSamplingRate || 96000,
1367
+ })
1368
+ .then(res => {
1369
+ this.incrementStatusBar();
1370
+ this.status =
1371
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1372
+ this.generateTemplate().toString();
1373
+ this.emit('update', {message: this.status});
1374
+ return res;
1375
+ })
1376
+ .catch(err => {
1377
+ console.error(err);
1378
+ });
1379
+ unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
1380
+ conv_rec = this.systemInvertedImpulseResponse;
1381
+ this.addTimeStamp('Get PSD of system iir and system iir no band pass');
1382
+ if (this.isCalibrating) return null;
1383
+ let system_iir_psd = await this.pyServerAPI
1384
+ .getPSDWithRetry({
1385
+ unconv_rec,
1386
+ conv_rec,
1387
+ sampleRate: this.sourceSamplingRate || 96000,
1388
+ })
1389
+ .then(res => {
1390
+ this.incrementStatusBar();
1391
+ this.status =
1392
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1393
+ this.generateTemplate().toString();
1394
+ this.emit('update', {message: this.status});
1395
+ return res;
1396
+ })
1397
+ .catch(err => {
1398
+ console.error(err);
1399
+ });
1400
+
1401
+ this.addTimeStamp('Get PSD of mls sequence');
1402
+ if (this.isCalibrating) return null;
1403
+ let mls_psd = await this.pyServerAPI
1404
+ .getMLSPSDWithRetry({
1405
+ mls: this.#mlsBufferView,
1406
+ sampleRate: this.sourceSamplingRate || 96000,
1407
+ })
1408
+ .then(res => {
1409
+ this.incrementStatusBar();
1410
+ this.status =
1411
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1412
+ this.generateTemplate().toString();
1413
+ this.emit('update', {message: this.status});
1414
+ return res;
1415
+ })
1416
+ .catch(err => {
1417
+ console.error(err);
1418
+ });
1419
+
1420
+ this.addTimeStamp('Get PSD of filtered mls (system)');
1421
+ if (this.isCalibrating) return null;
1422
+ let filtered_mls_psd = await this.pyServerAPI
1423
+ .getMLSPSDWithRetry({
1424
+ mls: this.systemConvolution,
1425
+ sampleRate: this.sourceSamplingRate || 96000,
1426
+ })
1427
+ .then(res => {
1428
+ this.incrementStatusBar();
1429
+ this.status =
1430
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1431
+ this.generateTemplate().toString();
1432
+ this.emit('update', {message: this.status});
1433
+ return res;
1434
+ })
1435
+ .catch(err => {
1436
+ console.error(err);
1437
+ });
1438
+
1439
+ let gainValue = this.getGainDBSPL();
1440
+ iir_ir_and_plots = {
1441
+ unfiltered_recording: return_unconv_rec,
1442
+ filtered_recording: return_conv_rec,
1443
+ system: {
1444
+ iir: this.systemInvertedImpulseResponse,
1445
+ ir: this.systemIR,
1446
+ iir_psd: {
1447
+ y: system_iir_psd['y_conv'],
1448
+ x: system_iir_psd['y_conv'],
1449
+ y_no_bandpass: system_iir_psd['y_unconv'],
1450
+ x_no_bandpass: system_iir_psd['x_unconv'],
1451
+ },
1452
+ filtered_recording: [],
1453
+ filtered_mls_psd: {
1454
+ x: filtered_mls_psd['x_mls'],
1455
+ y: filtered_mls_psd['y_mls'],
1456
+ },
1457
+ convolution: this.systemConvolution,
1458
+ psd: {
1459
+ unconv: {
1460
+ x: results['x_unconv'],
1461
+ y: results['y_unconv'],
1462
+ },
1463
+ conv: {
1464
+ x: results['x_conv'],
1465
+ y: results['y_conv'],
1466
+ },
1467
+ },
1468
+ },
1469
+ component: {
1470
+ iir: this.componentInvertedImpulseResponse,
1471
+ ir: this.componentIR,
1472
+ ir_origin: this.componentIROrigin,
1473
+ ir_in_time_domain: this.componentIRInTimeDomain,
1474
+ iir_psd: {
1475
+ y: component_iir_psd['y_conv'],
1476
+ x: component_iir_psd['x_conv'],
1477
+ y_no_bandpass: component_iir_psd['y_unconv'],
1478
+ x_no_bandpass: component_iir_psd['x_unconv'],
1479
+ },
1480
+ filtered_mls_psd: {},
1481
+ convolution: this.componentConvolution,
1482
+ psd: {
1483
+ unconv: {
1484
+ x: [],
1485
+ y: [],
1486
+ },
1487
+ conv: {
1488
+ x: [],
1489
+ y: [],
1490
+ },
1491
+ },
1492
+ gainDBSPL: gainValue,
1493
+ },
1494
+ mls: this.#mlsBufferView,
1495
+ mls_psd: {
1496
+ x: mls_psd['x_mls'],
1497
+ y: mls_psd['y_mls'],
1498
+ },
1499
+ autocorrelations: this.autocorrelations,
1500
+ impulseResponses: [],
1501
+ };
1502
+ }
1503
+ if (this.isCalibrating) return null;
1504
+ await Promise.all(this.impulseResponses).then(res => {
1505
+ for (let i = 0; i < res.length; i++) {
1506
+ if (res[i] != undefined) {
1507
+ iir_ir_and_plots['impulseResponses'].push(res[i]);
1508
+ }
1509
+ }
1510
+ });
1511
+
1512
+ if (this.#download) {
1513
+ this.downloadSingleUnfilteredRecording();
1514
+ this.downloadSingleFilteredRecording();
1515
+ saveToCSV(this.#mls, 'MLS.csv');
1516
+ saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
1517
+ saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
1518
+ saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
1519
+ saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
1520
+ for (let i = 0; i < this.autocorrelations.length; i++) {
1521
+ saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
1522
+ }
1523
+ const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
1524
+ for (let i = 0; i < res.length; i++) {
1525
+ if (res[i] != undefined) {
1526
+ saveToCSV(res[i], `IR_${i}`);
1527
+ }
1528
+ }
1529
+ });
1530
+ }
1531
+
1532
+ return iir_ir_and_plots;
1533
+ };
1534
+
1535
+ /**
1536
+ * Public method to start the calibration process. Objects intialized from webassembly allocate new memory
1537
+ * and must be manually freed. This function is responsible for intializing the MlsGenInterface,
1538
+ * and wrapping the calibration steps with a garbage collection safe gaurd.
1539
+ *
1540
+ * @public
1541
+ * @param stream - The stream of audio from the Listener.
1542
+ * @example
1543
+ */
1544
+ startCalibrationImpulseResponse = async stream => {
1545
+ let desired_time = this.desired_time_per_mls;
1546
+ let checkRec = 'allhz';
1547
+
1548
+ console.log('MLS sequence should be of length: ' + this.sourceSamplingRate * desired_time);
1549
+
1550
+ length = this.sourceSamplingRate * desired_time;
1551
+ //get mls here
1552
+ const calibrateSoundBurstDb = this._calibrateSoundBurstDb;
1553
+ this.addTimeStamp('Get MLS sequence');
1554
+ if (this.isCalibrating) return null;
1555
+ await this.pyServerAPI
1556
+ .getMLSWithRetry({length, calibrateSoundBurstDb})
1557
+ .then(res => {
1558
+ console.log(res);
1559
+ this.#mlsBufferView = res['mls'];
1560
+ this.#mls = res['unscaledMLS'];
1561
+ })
1562
+ .catch(err => {
1563
+ // this.emit('InvertedImpulseResponse', {res: false});
1564
+ console.error(err);
1565
+ });
1566
+ this.numSuccessfulBackgroundCaptured = 0;
1567
+ if (this._calibrateSoundBackgroundSecs > 0) {
1568
+ this.mode = 'background';
1569
+ this.status =
1570
+ `All Hz Calibration: sampling the background noise...`.toString() +
1571
+ this.generateTemplate().toString();
1572
+ this.emit('update', {message: this.status});
1573
+ if (this.isCalibrating) return null;
1574
+ await this.recordBackground(
1575
+ stream, //stream
1576
+ () => this.numSuccessfulBackgroundCaptured < 1, //loop condition
1577
+ this.#awaitBackgroundNoiseRecording, //sleep to record
1578
+ this.sendBackgroundRecording, //send to get PSD
1579
+ this.mode,
1580
+ checkRec
1581
+ );
1582
+ this.incrementStatusBar();
1583
+ }
1584
+ this.mode = 'unfiltered';
1585
+ this.numSuccessfulCaptured = 0;
1586
+
1587
+ if (this.isCalibrating) return null;
1588
+ await this.calibrationSteps(
1589
+ stream,
1590
+ this.#playCalibrationAudio, // play audio func (required)
1591
+ this.#createCalibrationNodeFromBuffer(this.#mlsBufferView), // before play func
1592
+ this.#awaitSignalOnset, // before record
1593
+ () => this.numSuccessfulCaptured < this.numCaptures, // loop while true
1594
+ this.#awaitDesiredMLSLength, // during record
1595
+ this.#afterMLSRecord, // after record
1596
+ this.mode,
1597
+ checkRec
1598
+ );
1599
+ this.stopCalibrationAudio();
1600
+ checkRec = false;
1601
+
1602
+ // at this stage we've captured all the required signals,
1603
+ // and have received IRs for each one
1604
+ // so let's send all the IRs to the server to be converted to a single IIR
1605
+ if (this.isCalibrating) return null;
1606
+ await this.sendSystemImpulseResponsesToServerForProcessing();
1607
+ if (this.isCalibrating) return null;
1608
+ await this.sendComponentImpulseResponsesToServerForProcessing();
1609
+
1610
+ this.numSuccessfulCaptured = 0;
1611
+
1612
+ let iir_ir_and_plots;
1613
+ if (this._calibrateSoundCheck != 'none') {
1614
+ //do single check
1615
+ if (this._calibrateSoundCheck == 'goal' || this._calibrateSoundCheck == 'system') {
1616
+ if (this.isCalibrating) return null;
1617
+ iir_ir_and_plots = await this.singleSoundCheck(stream);
1618
+ if (this.isCalibrating) return null;
1619
+ } else {
1620
+ //both
1621
+ if (this.isCalibrating) return null;
1622
+ iir_ir_and_plots = await this.bothSoundCheck(stream);
1623
+ if (this.isCalibrating) return null;
1624
+ }
1625
+ } else {
1626
+ let unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
1627
+ let conv_rec = this.componentInvertedImpulseResponse;
1628
+ if (this.isCalibrating) return null;
1629
+ let component_iir_psd = await this.pyServerAPI
1630
+ .getPSDWithRetry({
1631
+ unconv_rec,
1632
+ conv_rec,
1633
+ sampleRate: this.sourceSamplingRate || 96000,
1634
+ })
1635
+ .then(res => {
1636
+ this.incrementStatusBar();
1637
+ this.status =
1638
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1639
+ this.generateTemplate().toString();
1640
+ this.emit('update', {message: this.status});
1641
+ return res;
1642
+ })
1643
+ .catch(err => {
1644
+ console.error(err);
1645
+ });
1646
+ unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
1647
+ conv_rec = this.systemInvertedImpulseResponse;
1648
+ if (this.isCalibrating) return null;
1649
+ let system_iir_psd = await this.pyServerAPI
1650
+ .getPSDWithRetry({
1651
+ unconv_rec,
1652
+ conv_rec,
1653
+ sampleRate: this.sourceSamplingRate || 96000,
1654
+ })
1655
+ .then(res => {
1656
+ this.incrementStatusBar();
1657
+ this.status =
1658
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
1659
+ this.generateTemplate().toString();
1660
+ this.emit('update', {message: this.status});
1661
+ return res;
1662
+ })
1663
+ .catch(err => {
1664
+ console.error(err);
1665
+ });
1666
+
1667
+ let gainValue = this.getGainDBSPL();
1668
+ iir_ir_and_plots = {
1669
+ unfiltered_recording: return_unconv_rec,
1670
+ filtered_recording: return_conv_rec,
1671
+ system: {
1672
+ iir: this.systemInvertedImpulseResponse,
1673
+ ir: this.systemIR,
1674
+ iir_psd: {
1675
+ y: system_iir_psd['y_conv'],
1676
+ x: system_iir_psd['y_conv'],
1677
+ y_no_bandpass: system_iir_psd['y_unconv'],
1678
+ x_no_bandpass: system_iir_psd['x_unconv'],
1679
+ },
1680
+ filtered_recording: [],
1681
+ convolution: this.systemConvolution,
1682
+ psd: {
1683
+ unconv: {
1684
+ x: [],
1685
+ y: [],
1686
+ },
1687
+ conv: {
1688
+ x: [],
1689
+ y: [],
1690
+ },
1691
+ },
1692
+ },
1693
+ component: {
1694
+ iir: this.componentInvertedImpulseResponse,
1695
+ ir: this.componentIR,
1696
+ ir_in_time_domain: this.componentIRInTimeDomain,
1697
+ iir_psd: {
1698
+ y: component_iir_psd['y_conv'],
1699
+ x: component_iir_psd['x_conv'],
1700
+ y_no_bandpass: component_iir_psd['y_unconv'],
1701
+ x_no_bandpass: component_iir_psd['x_unconv'],
1702
+ },
1703
+ convolution: this.componentConvolution,
1704
+ psd: {
1705
+ unconv: {
1706
+ x: [],
1707
+ y: [],
1708
+ },
1709
+ conv: {
1710
+ x: [],
1711
+ y: [],
1712
+ },
1713
+ },
1714
+ gainDBSPL: gainValue,
1715
+ },
1716
+ mls: this.#mlsBufferView,
1717
+ autocorrelations: this.autocorrelations,
1718
+ impulseResponses: [],
1719
+ };
1720
+ if (this.isCalibrating) return null;
1721
+ await Promise.all(this.impulseResponses).then(res => {
1722
+ for (let i = 0; i < res.length; i++) {
1723
+ if (res[i] != undefined) {
1724
+ iir_ir_and_plots['impulseResponses'].push(res[i]);
1725
+ }
1726
+ }
1727
+ });
1728
+
1729
+ if (this.#download) {
1730
+ saveToCSV(this.#mls, 'MLS.csv');
1731
+ saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
1732
+ saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
1733
+ saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
1734
+ saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
1735
+ for (let i = 0; i < this.autocorrelations.length; i++) {
1736
+ saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
1737
+ }
1738
+ const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
1739
+ for (let i = 0; i < res.length; i++) {
1740
+ if (res[i] != undefined) {
1741
+ saveToCSV(res[i], `IR_${i}`);
1742
+ }
1743
+ }
1744
+ });
1745
+ }
1746
+ }
1747
+ if (this.isCalibrating) return null;
1748
+ this.percent_complete = 100;
1749
+ this.status = `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();
1750
+ this.emit('update', {message: this.status});
1751
+
1752
+ //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR
1753
+ //that was used to calibrate
1754
+ // saveToJSON(iir_ir_and_plots);
1755
+ return iir_ir_and_plots;
1756
+ };
1757
+
1758
+ //////////////////////volume
1759
+
1760
+ handleIncomingData = data => {
1761
+ console.log('Received data: ', data);
1762
+ if (data.type === 'soundGainDBSPL') {
1763
+ this.soundGainDBSPL = data.value;
1764
+ } else {
1765
+ throw new Error(`Unknown data type: ${data.type}`);
1766
+ }
1767
+ };
1768
+ createSCurveBuffer = (onSetBool = true) => {
1769
+ const curve = new Float32Array(this.TAPER_SECS * this.sourceSamplingRate + 1);
1770
+ const frequency = 1 / (4 * this.TAPER_SECS);
1771
+ let j = 0;
1772
+ for (let i = 0; i < this.TAPER_SECS * this.sourceSamplingRate + 1; i += 1) {
1773
+ const phase = 2 * Math.PI * frequency * j;
1774
+ const onsetTaper = Math.pow(Math.sin(phase), 2);
1775
+ const offsetTaper = Math.pow(Math.cos(phase), 2);
1776
+ curve[i] = onSetBool ? onsetTaper : offsetTaper;
1777
+ j += 1 / this.sourceSamplingRate;
1778
+ }
1779
+ return curve;
1780
+ };
1781
+
1782
+ #getTruncatedSignal = (left = 3.5, right = 4.5) => {
1783
+ const start = Math.floor(left * this.sourceSamplingRate);
1784
+ const end = Math.floor(right * this.sourceSamplingRate);
1785
+ const result = Array.from(this.getLastVolumeRecordedSignal().slice(start, end));
1786
+ console.log(
1787
+ 'Obtaining last 1000 hz recording from #allVolumeRecordings to send for processing'
1788
+ );
1789
+ /**
1790
+ * function to check that capture was properly made
1791
+ * @param {*} list
1792
+ */
1793
+ const checkResult = list => {
1794
+ const setItem = new Set(list);
1795
+ if (setItem.size === 1 && setItem.has(0)) {
1796
+ console.warn(
1797
+ 'The last capture failed, all recorded signal is zero',
1798
+ this.getAllVolumeRecordedSignals()
1799
+ );
1800
+ }
1801
+ if (setItem.size === 0) {
1802
+ console.warn('The last capture failed, no recorded signal');
1803
+ }
1804
+ };
1805
+ checkResult(result);
1806
+ return result;
1807
+ };
1808
+
1809
+ /**
1810
+ *
1811
+ *
1812
+ Construct a calibration Node with the calibration parameters and given gain value
1813
+ * @param {*} gainValue
1814
+ * */
1815
+ #createCalibrationToneWithGainValue = gainValue => {
1816
+ const audioContext = this.makeNewSourceAudioContext();
1817
+ const oscilator = audioContext.createOscillator();
1818
+ const gainNode = audioContext.createGain();
1819
+ const taperGainNode = audioContext.createGain();
1820
+ const offsetGainNode = audioContext.createGain();
1821
+ const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
1822
+
1823
+ oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
1824
+ oscilator.type = this.#CALIBRATION_TONE_TYPE;
1825
+ gainNode.gain.value = gainValue;
1826
+
1827
+ oscilator.connect(gainNode);
1828
+ gainNode.connect(taperGainNode);
1829
+ const onsetCurve = this.createSCurveBuffer();
1830
+ taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
1831
+ taperGainNode.connect(offsetGainNode);
1832
+ const offsetCurve = this.createSCurveBuffer(false);
1833
+ offsetGainNode.gain.setValueCurveAtTime(
1834
+ offsetCurve,
1835
+ totalDuration - this.TAPER_SECS,
1836
+ this.TAPER_SECS
1837
+ );
1838
+ offsetGainNode.connect(audioContext.destination);
1839
+
1840
+ this.addCalibrationNode(oscilator);
1841
+ };
1842
+
1843
+ /**
1844
+ * Construct a Calibration Node with the calibration parameters.
1845
+ *
1846
+ * @private
1847
+ * @example
1848
+ */
1849
+ #createCalibrationNode = () => {
1850
+ const audioContext = this.makeNewSourceAudioContext();
1851
+ const oscilator = audioContext.createOscillator();
1852
+ const gainNode = audioContext.createGain();
1853
+
1854
+ oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
1855
+ oscilator.type = this.#CALIBRATION_TONE_TYPE;
1856
+ gainNode.gain.value = 0.04;
1857
+
1858
+ oscilator.connect(gainNode);
1859
+ gainNode.connect(audioContext.destination);
1860
+
1861
+ this.addCalibrationNode(oscilator);
1862
+ };
1863
+
1864
+ #playCalibrationAudioVolume = async () => {
1865
+ const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
1866
+
1867
+ this.calibrationNodes[0].start(0);
1868
+ this.calibrationNodes[0].stop(totalDuration);
1869
+ console.log(`Playing a buffer of ${this.CALIBRATION_TONE_DURATION} seconds of audio`);
1870
+ console.log(`Waiting a total of ${totalDuration} seconds`);
1871
+ await sleep(totalDuration);
1872
+ };
1873
+
1874
+ stopCalibrationAudioVolume = () => {
1875
+ if (this.calibrationNodes.length > 0) {
1876
+ this.calibrationNodes[0].stop();
1877
+ }
1878
+ };
1879
+
1880
+ #sendToServerForProcessing = lCalib => {
1881
+ console.log('Sending data to server');
1882
+ this.addTimeStamp('Send volume data to server');
1883
+ let left = this.calibrateSound1000HzPreSec;
1884
+ let right = this.calibrateSound1000HzPreSec + this.calibrateSound1000HzSec;
1885
+ if (this.isCalibrating) return null;
1886
+ this.pyServerAPI
1887
+ .getVolumeCalibration({
1888
+ sampleRate: this.sourceSamplingRate,
1889
+ payload: this.#getTruncatedSignal(left, right),
1890
+ lCalib: lCalib,
1891
+ })
1892
+ .then(res => {
1893
+ if (this.outDBSPL === null) {
1894
+ this.incrementStatusBar();
1895
+ this.outDBSPL = res['outDbSPL'];
1896
+ this.outDBSPL1000 = res['outDbSPL1000'];
1897
+ this.THD = res['thd'];
1898
+ }
1899
+ })
1900
+ .catch(err => {
1901
+ console.warn(err);
1902
+ });
1903
+
1904
+ this.pyServerAPI
1905
+ .volumePowerCheck({
1906
+ payload: this.getLastVolumeRecordedSignal(),
1907
+ sampleRate: this.sourceSamplingRate || 96000,
1908
+ binDesiredSec: this._calibrateSoundPowerBinDesiredSec,
1909
+ preSec: this.calibrateSound1000HzPreSec,
1910
+ Sec: this.calibrateSound1000HzSec,
1911
+ })
1912
+ .then(res => {
1913
+ if (res && res['sd'] < this._calibrateSoundPowerDbSDToleratedDb) {
1914
+ this.recordingChecks['volume'][this.inDB] = res;
1915
+ }
1916
+ });
1917
+ };
1918
+
1919
+ startCalibrationVolume = async (stream, gainValues, lCalib, componentGainDBSPL) => {
1920
+ if (this.isCalibrating) return null;
1921
+ const trialIterations = gainValues.length;
1922
+ this.status_denominator += trialIterations;
1923
+ const thdValues = [];
1924
+ const inDBValues = [];
1925
+ let inDB = 0;
1926
+ const outDBSPLValues = [];
1927
+ const outDBSPL1000Values = [];
1928
+ let checkRec = false;
1929
+
1930
+ // do one calibration that will be discarded
1931
+ const soundLevelToDiscard = -60;
1932
+ const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);
1933
+ this.inDB = soundLevelToDiscard;
1934
+ this.status =
1935
+ `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() +
1936
+ this.generateTemplate().toString();
1937
+ //this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});
1938
+ this.emit('update', {message: this.status});
1939
+ this.startTime = new Date().getTime();
1940
+
1941
+ do {
1942
+ console.log('while loop');
1943
+ if (this.isCalibrating) {
1944
+ console.log('restart calibration');
1945
+ return null;
1946
+ }
1947
+ // eslint-disable-next-line no-await-in-loop
1948
+ await this.volumeCalibrationSteps(
1949
+ stream,
1950
+ this.#playCalibrationAudioVolume,
1951
+ this.#createCalibrationToneWithGainValue,
1952
+ this.#sendToServerForProcessing,
1953
+ gainToDiscard,
1954
+ lCalib, //todo make this a class parameter
1955
+ checkRec
1956
+ );
1957
+ } while (this.outDBSPL === null);
1958
+ //reset the values
1959
+ //this.incrementStatusBar();
1960
+
1961
+ this.outDBSPL = null;
1962
+ this.outDBSPL = null;
1963
+ this.outDBSPL1000 = null;
1964
+ this.THD = null;
1965
+
1966
+ // run the calibration at different gain values provided by the user
1967
+ for (let i = 0; i < trialIterations; i++) {
1968
+ //convert gain to DB and add to inDB
1969
+ if (i == trialIterations - 1) {
1970
+ checkRec = 'loudest';
1971
+ }
1972
+ inDB = Math.log10(gainValues[i]) * 20;
1973
+ // precision to 1 decimal place
1974
+ inDB = Math.round(inDB * 10) / 10;
1975
+ this.inDB = inDB;
1976
+ inDBValues.push(inDB);
1977
+ console.log('next update');
1978
+ this.status =
1979
+ `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() +
1980
+ this.generateTemplate().toString();
1981
+ this.emit('update', {message: this.status});
1982
+ do {
1983
+ if (this.isCalibrating) {
1984
+ console.log('restart calibration');
1985
+ return null;
1986
+ }
1987
+ // eslint-disable-next-line no-await-in-loop
1988
+ await this.volumeCalibrationSteps(
1989
+ stream,
1990
+ this.#playCalibrationAudioVolume,
1991
+ this.#createCalibrationToneWithGainValue,
1992
+ this.#sendToServerForProcessing,
1993
+ gainValues[i],
1994
+ lCalib, //todo make this a class parameter
1995
+ checkRec
1996
+ );
1997
+ } while (this.outDBSPL === null);
1998
+ outDBSPL1000Values.push(this.outDBSPL1000);
1999
+ thdValues.push(this.THD);
2000
+ outDBSPLValues.push(this.outDBSPL);
2001
+
2002
+ this.outDBSPL = null;
2003
+ this.outDBSPL1000 = null;
2004
+ this.THD = null;
2005
+ }
2006
+ if (this.isCalibrating) return null;
2007
+ // get the volume calibration parameters from the server
2008
+ this.addTimeStamp('Get Volume Calibration Parameters');
2009
+
2010
+ const parameters = await this.pyServerAPI
2011
+ .getVolumeCalibrationParameters({
2012
+ inDBValues: inDBValues,
2013
+ outDBSPLValues: outDBSPL1000Values,
2014
+ lCalib: lCalib,
2015
+ componentGainDBSPL,
2016
+ })
2017
+ .then(res => {
2018
+ this.incrementStatusBar();
2019
+ return res;
2020
+ });
2021
+ if (this.isCalibrating) return null;
2022
+ const result = {
2023
+ parameters: parameters,
2024
+ inDBValues: inDBValues,
2025
+ outDBSPLValues: outDBSPLValues,
2026
+ outDBSPL1000Values: outDBSPL1000Values,
2027
+ thdValues: thdValues,
2028
+ };
2029
+
2030
+ return result;
2031
+ };
2032
+
2033
+ writeFrqGainToFirestore = async (speakerID, frq, gain, OEM, documentID) => {
2034
+ // freq and gain are too large to take samples 1 in every 100 samples
2035
+ // const sampledFrq = [];
2036
+ // const sampledGain = [];
2037
+ // for (let i = 0; i < frq.length; i += 100) {
2038
+ // sampledFrq.push(frq[i]);
2039
+ // sampledGain.push(gain[i]);
2040
+ // }
2041
+
2042
+ const data = {Freq: frq, Gain: gain};
2043
+
2044
+ const docRef = doc(database, 'Microphones', documentID);
2045
+ await updateDoc(docRef, {
2046
+ linear: data,
2047
+ });
2048
+
2049
+ // divide frq and gain into smaller chunks and write to firestore one chunk at a time
2050
+ // use arrayUnion to append to the array
2051
+ // const chunkSize = 600;
2052
+ // const chunkedFrq = [];
2053
+ // const chunkedGain = [];
2054
+ // for (let i = 0; i < frq.length; i += chunkSize) {
2055
+ // chunkedFrq.push(frq.slice(i, i + chunkSize));
2056
+ // chunkedGain.push(gain.slice(i, i + chunkSize));
2057
+ // }
2058
+ // const docRef = doc(database, 'Microphones', documentID);
2059
+ // for (let i = 0; i < chunkedFrq.length; i++) {
2060
+ // await updateDoc(docRef, {
2061
+ // linear: {
2062
+ // Freq: arrayUnion(...chunkedFrq[i]),
2063
+ // Gain: arrayUnion(...chunkedGain[i]),
2064
+ // },
2065
+ // });
2066
+ // }
2067
+ };
2068
+ // function to write frq and gain to firebase database given speakerID
2069
+ writeFrqGain = async (speakerID, frq, gain, OEM) => {
2070
+ // freq and gain are too large to take samples 1 in every 100 samples
2071
+
2072
+ const sampledFrq = [];
2073
+ const sampledGain = [];
2074
+ for (let i = 0; i < frq.length; i += 100) {
2075
+ sampledFrq.push(frq[i]);
2076
+ sampledGain.push(gain[i]);
2077
+ }
2078
+
2079
+ const data = {Freq: sampledFrq, Gain: sampledGain};
2080
+
2081
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/linear`), data);
2082
+ };
2083
+
2084
+ // Function to Read frq and gain from firebase database given speakerID
2085
+ // returns an array of frq and gain if speakerID exists, returns null otherwise
2086
+ readFrqGainFromFirestore = async (speakerID, OEM, isDefault) => {
2087
+ const collectionRef = collection(database, 'Microphones');
2088
+ const q = query(
2089
+ collectionRef,
2090
+ where('ID', '==', speakerID),
2091
+ where('lowercaseOEM', '==', OEM),
2092
+ where('isDefault', '==', isDefault)
2093
+ );
2094
+ const querySnapshot = await getDocs(q);
2095
+ // if exists return the linear field of the first document
2096
+ if (querySnapshot.size > 0) {
2097
+ return querySnapshot.docs[0].data().linear;
2098
+ }
2099
+ return null;
2100
+ };
2101
+ readFrqGain = async (speakerID, OEM) => {
2102
+ const dbRef = ref(database);
2103
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/linear`));
2104
+ if (snapshot.exists()) {
2105
+ return snapshot.val();
2106
+ }
2107
+ return null;
2108
+ };
2109
+ readGainat1000HzFromFirestore = async (speakerID, OEM, isDefault) => {
2110
+ const collectionRef = collection(database, 'Microphones');
2111
+ const q = query(
2112
+ collectionRef,
2113
+ where('ID', '==', speakerID),
2114
+ where('lowercaseOEM', '==', OEM),
2115
+ where('isDefault', '==', isDefault)
2116
+ );
2117
+ const querySnapshot = await getDocs(q);
2118
+ // if exists return the Gain1000 field of the first document
2119
+ if (querySnapshot.size > 0) {
2120
+ return querySnapshot.docs[0].data().Gain1000;
2121
+ }
2122
+ return null;
2123
+ };
2124
+
2125
+ readGainat1000Hz = async (speakerID, OEM) => {
2126
+ const dbRef = ref(database);
2127
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/Gain1000`));
2128
+ if (snapshot.exists()) {
2129
+ return snapshot.val();
2130
+ }
2131
+ return null;
2132
+ };
2133
+
2134
+ writeGainat1000HzToFirestore = async (speakerID, gain, OEM, documentID) => {
2135
+ const docRef = doc(database, 'Microphones', documentID);
2136
+ await updateDoc(docRef, {
2137
+ Gain1000: gain,
2138
+ });
2139
+ };
2140
+
2141
+ writeGainat1000Hz = async (speakerID, gain, OEM) => {
2142
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/Gain1000`), gain);
2143
+ };
2144
+
2145
+ writeIsSmartPhoneToFirestore = async (speakerID, isSmartPhone, OEM) => {
2146
+ const collectionRef = collection(database, 'Microphones');
2147
+ const q = query(
2148
+ collectionRef,
2149
+ where('ID', '==', speakerID),
2150
+ where('lowercaseOEM', '==', OEM),
2151
+ where('isDefault', '==', true)
2152
+ );
2153
+ const querySnapshot = await getDocs(q);
2154
+ if (querySnapshot.size > 0) {
2155
+ const docRef = await addDoc(collectionRef, {isSmartPhone: isSmartPhone, isDefault: false});
2156
+ return docRef.id;
2157
+ } else {
2158
+ const docRef = await addDoc(collectionRef, {isSmartPhone: isSmartPhone, isDefault: true});
2159
+ return docRef.id;
2160
+ }
2161
+ };
2162
+
2163
+ writeIsSmartPhone = async (speakerID, isSmartPhone, OEM) => {
2164
+ const data = {isSmartPhone: isSmartPhone};
2165
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/isSmartPhone`), isSmartPhone);
2166
+ };
2167
+
2168
+ writeMicrophoneInfoToFirestore = async (speakerID, micInfo, OEM, documentID) => {
2169
+ const docRef = doc(database, 'Microphones', documentID);
2170
+ await setDoc(docRef, micInfo, {merge: true});
2171
+ };
2172
+
2173
+ doesMicrophoneExistInFirestore = async (speakerID, OEM, documentID) => {
2174
+ const docRef = doc(database, 'Microphone', OEM, speakerID, documentID);
2175
+ const docSnap = await getDoc(docRef);
2176
+ if (docSnap.exists()) {
2177
+ return true;
2178
+ }
2179
+ return false;
2180
+ };
2181
+
2182
+ doesMicrophoneExist = async (speakerID, OEM) => {
2183
+ const dbRef = ref(database);
2184
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}`));
2185
+ if (snapshot.exists()) {
2186
+ return true;
2187
+ }
2188
+ return false;
2189
+ };
2190
+
2191
+ addMicrophoneInfo = async (speakerID, OEM, micInfo) => {
2192
+ // add to database if /info does not exist
2193
+ const dbRef = ref(database);
2194
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/info`));
2195
+ if (!snapshot.exists()) {
2196
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/info`), micInfo);
2197
+ }
2198
+ };
2199
+
2200
+ convertToDB = gain => {
2201
+ return Math.log10(gain) * 20;
2202
+ };
2203
+
2204
+ // Function to perform linear interpolation between two points
2205
+ interpolate(x, x0, y0, x1, y1) {
2206
+ return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);
2207
+ }
2208
+
2209
+ findGainatFrequency = (frequencies, gains, targetFrequency) => {
2210
+ // Find the index of the first frequency in the array greater than the target frequency
2211
+ let index = 0;
2212
+ while (index < frequencies.length && frequencies[index] < targetFrequency) {
2213
+ index++;
2214
+ }
2215
+
2216
+ // Handle cases when the target frequency is outside the range of the given data
2217
+ if (index === 0) {
2218
+ return gains[0];
2219
+ } else if (index === frequencies.length) {
2220
+ return gains[gains.length - 1];
2221
+ } else {
2222
+ // Interpolate the gain based on the surrounding frequencies
2223
+ const x0 = frequencies[index - 1];
2224
+ const y0 = gains[index - 1];
2225
+ const x1 = frequencies[index];
2226
+ const y1 = gains[index];
2227
+ return this.interpolate(targetFrequency, x0, y0, x1, y1);
2228
+ }
2229
+ };
2230
+
2231
+ // add time stamp
2232
+ addTimeStamp = taskName => {
2233
+ let startTaskTime = (new Date().getTime() - this.startTime) / 1000;
2234
+ this.timeStamp.push(`SOUND ${Number(startTaskTime.toFixed(1))} s. ${taskName}`);
2235
+ };
2236
+
2237
+ checkPowerVariation = async () => {
2238
+ const recordings = this.getAllFilteredRecordedSignals();
2239
+ const rec = recordings[recordings.length - 1];
2240
+ console.log(rec);
2241
+ await this.pyServerAPI
2242
+ .allHzPowerCheck({
2243
+ payload: rec,
2244
+ sampleRate: this.sourceSamplingRate || 96000,
2245
+ binDesiredSec: this._calibrateSoundPowerBinDesiredSec,
2246
+ burstSec: this.desired_time_per_mls,
2247
+ })
2248
+ .then(result => {
2249
+ if (result) {
2250
+ if (result['sd'] > this._calibrateSoundPowerDbSDToleratedDb) {
2251
+ console.log('filtered recording sd too high');
2252
+ } else {
2253
+ this.recordingChecks[this.soundCheck].push(result);
2254
+ if (this.numSuccessfulCaptured < 1) {
2255
+ this.numSuccessfulCaptured += 1;
2256
+ this.stepNum += 1;
2257
+ this.incrementStatusBar();
2258
+ console.log(
2259
+ 'after mls w iir record for some reason add numSucc capt ' + this.stepNum
2260
+ );
2261
+ this.status =
2262
+ `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +
2263
+ this.generateTemplate().toString();
2264
+ this.emit('update', {
2265
+ message: this.status,
2266
+ });
2267
+ }
2268
+ }
2269
+ }
2270
+ });
2271
+ };
2272
+
2273
+ getGainDBSPL = () => {
2274
+ var freqIndex = this.componentIR.Freq.indexOf(1000);
2275
+
2276
+ // If freqIndex is not -1 (meaning 1000 is found in the freq array)
2277
+ if (freqIndex !== -1) {
2278
+ // Get the corresponding gain value using the index
2279
+ var gainValue = this.componentIR.Gain[freqIndex];
2280
+ return gainValue;
2281
+ } else {
2282
+ console.log('Freq 1000 not found in the array.');
2283
+ return null;
2284
+ }
2285
+ };
2286
+ // Example of how to use the writeFrqGain and readFrqGain functions
2287
+ // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);
2288
+ // Speaker1 is the speakerID you want to write to in the database
2289
+ // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));
2290
+ // MiniDSPUMIK_1 is the speakerID with some Data in the database
2291
+ //adding gainDBSPL
2292
+ startCalibration = async (
2293
+ stream,
2294
+ gainValues,
2295
+ lCalib = 104.92978421490648,
2296
+ componentIR = null,
2297
+ microphoneName = 'MiniDSP-UMIK1-711-4754-vertical',
2298
+ _calibrateSoundCheck = 'goal', //GOAL PASSed in by default
2299
+ isSmartPhone = false,
2300
+ _calibrateSoundBurstDb = 0.1,
2301
+ _calibrateSoundBurstRepeats = 3,
2302
+ _calibrateSoundBurstSec = 1,
2303
+ _calibrateSoundBurstsWarmup = 0,
2304
+ _calibrateSoundHz = 48000,
2305
+ _calibrateSoundIIRSec = 0.2,
2306
+ _calibrateSoundIRSec = 0.2,
2307
+ calibrateSound1000HzPreSec = 3.5,
2308
+ calibrateSound1000HzSec = 1.0,
2309
+ calibrateSound1000HzPostSec = 0.5,
2310
+ _calibrateSoundBackgroundSecs = 0,
2311
+ _calibrateSoundSmoothOctaves = 0.33,
2312
+ _calibrateSoundPowerBinDesiredSec = 0.2,
2313
+ _calibrateSoundPowerDbSDToleratedDb = 1,
2314
+ micManufacturer = '',
2315
+ micSerialNumber = '',
2316
+ micModelNumber = '',
2317
+ micModelName = '',
2318
+ calibrateMicrophonesBool,
2319
+ authorEmails,
2320
+ webAudioDeviceNames = {
2321
+ loudspeaker: 'loudspeaker',
2322
+ microphone: 'microphone',
2323
+ microphoneText: 'xxx XXX',
2324
+ },
2325
+ userIDs,
2326
+ restartButton
2327
+ ) => {
2328
+ this._calibrateSoundBurstDb = _calibrateSoundBurstDb;
2329
+ this.CALIBRATION_TONE_DURATION =
2330
+ calibrateSound1000HzPreSec + calibrateSound1000HzSec + calibrateSound1000HzPostSec;
2331
+ this.calibrateSound1000HzPreSec = calibrateSound1000HzPreSec;
2332
+ this.calibrateSound1000HzSec = calibrateSound1000HzSec;
2333
+ this.calibrateSound1000HzPostSec = calibrateSound1000HzPostSec;
2334
+ this.iirLength = Math.floor(_calibrateSoundIIRSec * this.sourceSamplingRate);
2335
+ this.irLength = Math.floor(_calibrateSoundIRSec * this.sourceSamplingRate);
2336
+ console.log('device info:', this.deviceInfo);
2337
+ this.numMLSPerCapture = _calibrateSoundBurstRepeats + 1;
2338
+ this.desired_time_per_mls = _calibrateSoundBurstSec;
2339
+ this.num_mls_to_skip = _calibrateSoundBurstsWarmup;
2340
+ this.desired_sampling_rate = _calibrateSoundHz;
2341
+ this._calibrateSoundBackgroundSecs = _calibrateSoundBackgroundSecs;
2342
+ this._calibrateSoundSmoothOctaves = _calibrateSoundSmoothOctaves;
2343
+ this._calibrateSoundPowerBinDesiredSec = _calibrateSoundPowerBinDesiredSec;
2344
+ this._calibrateSoundPowerDbSDToleratedDb = _calibrateSoundPowerDbSDToleratedDb;
2345
+ this.webAudioDeviceNames = webAudioDeviceNames;
2346
+ if (isSmartPhone) this.webAudioDeviceNames.microphone = this.deviceInfo.microphoneFromAPI;
2347
+ this.webAudioDeviceNames.microphoneText = this.webAudioDeviceNames.microphoneText
2348
+ .replace('xxx', this.webAudioDeviceNames.microphone)
2349
+ .replace('XXX', this.webAudioDeviceNames.microphone);
2350
+ //feed calibration goal here
2351
+ this._calibrateSoundCheck = _calibrateSoundCheck;
2352
+ //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
2353
+ //check the db based on the microphone currently connected
2354
+
2355
+ //new lCalib found at top of calibration files *1000hz, make sure to correct
2356
+ //based on zeroing of 1000hz, search for "*1000Hz"
2357
+ const ID = isSmartPhone ? micModelNumber : micSerialNumber;
2358
+ const OEM = isSmartPhone
2359
+ ? micModelName === 'UMIK-1' || micModelName === 'UMIK-2'
2360
+ ? 'minidsp'
2361
+ : this.deviceInfo.OEM.toLowerCase().split(' ').join('')
2362
+ : micManufacturer.toLowerCase().split(' ').join('');
2363
+ // const ID = "712-5669";
2364
+ // const OEM = "minidsp";
2365
+ const micInfo = {
2366
+ micModelName: isSmartPhone ? micModelName : microphoneName,
2367
+ OEM: isSmartPhone
2368
+ ? micModelName === 'UMIK-1' || micModelName === 'UMIK-2'
2369
+ ? 'miniDSP'
2370
+ : this.deviceInfo.OEM
2371
+ : micManufacturer,
2372
+ ID: ID,
2373
+ createDate: new Date(),
2374
+ DateText: getCurrentTimeString(),
2375
+ HardwareName: isSmartPhone ? this.deviceInfo.hardwarename : microphoneName,
2376
+ hardwareFamily: isSmartPhone ? this.deviceInfo.hardwarefamily : microphoneName,
2377
+ HardwareModel: isSmartPhone ? this.deviceInfo.hardwaremodel : microphoneName,
2378
+ PlatformName: isSmartPhone ? this.deviceInfo.platformname : 'N/A',
2379
+ PlatformVersion: isSmartPhone ? this.deviceInfo.platformversion : 'N/A',
2380
+ DeviceType: isSmartPhone ? this.deviceInfo.devicetype : 'N/A',
2381
+ ID_from_51Degrees: isSmartPhone ? this.deviceInfo.DeviceId : 'N/A',
2382
+ calibrateMicrophonesBool: calibrateMicrophonesBool,
2383
+ webAudioDeviceNames: {
2384
+ loudspeaker: this.webAudioDeviceNames.loudspeaker,
2385
+ microphone: this.webAudioDeviceNames.microphone,
2386
+ },
2387
+ userIDs: userIDs,
2388
+ lowercaseOEM: OEM.toLowerCase().split(' ').join(''),
2389
+ };
2390
+ if (calibrateMicrophonesBool) {
2391
+ micInfo['authorEmails'] = authorEmails;
2392
+ }
2393
+ // if undefined in micInfo, set to empty string
2394
+ for (const [key, value] of Object.entries(micInfo)) {
2395
+ if (value === undefined) {
2396
+ micInfo[key] = '';
2397
+ }
2398
+ }
2399
+
2400
+ // this.writeMicrophoneInfoToFirestore(ID, micInfo, OEM, 'default');
2401
+ // this.addMicrophoneInfo(ID, OEM, micInfo);
2402
+ if (componentIR == null) {
2403
+ //mode 'ir'
2404
+ //global variable this.componentIR must be set
2405
+ this.componentIR = await this.readFrqGainFromFirestore(ID, OEM, true).then(data => {
2406
+ return data;
2407
+ });
2408
+ // await this.readFrqGain(ID, OEM).then(data => {
2409
+ // return data;
2410
+ // });
2411
+
2412
+ // lCalib = await this.readGainat1000Hz(ID, OEM);
2413
+ lCalib = await this.readGainat1000HzFromFirestore(ID, OEM, true);
2414
+ micInfo['gainDBSPL'] = lCalib;
2415
+ // this.componentGainDBSPL = this.convertToDB(lCalib);
2416
+ this.componentGainDBSPL = lCalib;
2417
+ //TODO: if this call to database is unknown, cannot perform experiment => return false
2418
+ if (this.componentIR == null) {
2419
+ this.status =
2420
+ `Microphone (${OEM},${ID}) is not found in the database. Please add it to the database.`.toString();
2421
+ this.emit('update', {message: this.status});
2422
+ return false;
2423
+ }
2424
+ } else {
2425
+ this.transducerType = 'Microphone';
2426
+ this.componentIR = componentIR;
2427
+ lCalib = this.findGainatFrequency(this.componentIR.Freq, this.componentIR.Gain, 1000);
2428
+ // this.componentGainDBSPL = this.convertToDB(lCalib);
2429
+ this.componentGainDBSPL = lCalib;
2430
+ // await this.writeIsSmartPhone(ID, isSmartPhone, OEM);
2431
+ }
2432
+
2433
+ this.oldComponentIR = JSON.parse(JSON.stringify(this.componentIR));
2434
+
2435
+ return await new Promise(async (resolve, reject) => {
2436
+ // add event listner to params.restartButton to resolve the promise with a string: 'restart'
2437
+
2438
+ if (restartButton) {
2439
+ restartButton.style.display = '';
2440
+ restartButton.addEventListener('click', () => {
2441
+ this.stopCalibrationAudio();
2442
+ this.isCalibrating = true;
2443
+ restartButton.style.display = 'none';
2444
+ this.emit('update', {message: 'Restarting calibration...'});
2445
+ resolve('restart');
2446
+ });
2447
+ }
2448
+ let volumeResults = await this.startCalibrationVolume(
2449
+ stream,
2450
+ gainValues,
2451
+ lCalib,
2452
+ this.componentGainDBSPL
2453
+ );
2454
+ if (!volumeResults) return;
2455
+ let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);
2456
+ if (!impulseResponseResults) return;
2457
+ impulseResponseResults['background_noise'] = this.background_noise;
2458
+ if (componentIR != null) {
2459
+ // I corrected microphone/loudpeaker IR scale in easyeyes,
2460
+ // but since we write microphone IR to firestore here
2461
+ // we need to correct microphone IR here
2462
+ let correctGain =
2463
+ Math.round((volumeResults.parameters.gainDBSPL - this.componentGainDBSPL) * 10) / 10;
2464
+
2465
+ let IrFreq = impulseResponseResults?.component.ir.Freq.map(freq => Math.round(freq));
2466
+ let IrGain = impulseResponseResults?.component?.ir.Gain;
2467
+ const IrGainAt1000Hz = IrGain[IrFreq.findIndex(freq => freq === 1000)];
2468
+ const difference = Math.round(10 * (IrGainAt1000Hz - correctGain)) / 10;
2469
+ IrGain = IrGain.map(gain => gain - difference);
2470
+ micInfo['mlsSD'] = this.recordingChecks.unfiltered[0].sd;
2471
+ console.log(typeof micInfo['componentCorrectionSD']);
2472
+ micInfo['systemCorrectionSD'] = Number(this.SDofFilteredRange['system']);
2473
+ micInfo['componentCorrectionSD'] = Number(this.SDofFilteredRange['component']);
2474
+ console.log(typeof micInfo['componentCorrectionSD']);
2475
+ // const id = await this.writeIsSmartPhoneToFirestore(ID, isSmartPhone, OEM);
2476
+ // await this.writeMicrophoneInfoToFirestore(ID, micInfo, OEM, id);
2477
+ // await this.writeFrqGainToFirestore(ID, IrFreq, IrGain, OEM, id);
2478
+ // micInfo['gainDBSPL'] = impulseResponseResults.component.gainDBSPL;
2479
+ // await this.writeGainat1000HzToFirestore(ID, micInfo['gainDBSPL'], OEM, id);
2480
+ // await this.writeGainat1000Hz(ID, micInfo['gainDBSPL'], OEM);
2481
+ }
2482
+ const total_results = {...volumeResults, ...impulseResponseResults};
2483
+ total_results['filteredMLSRange'] = this.filteredMLSRange;
2484
+ total_results['micInfo'] = micInfo;
2485
+ total_results['audioInfo'] = {};
2486
+ total_results['audioInfo']['sinkSampleRate'] = this.sinkSamplingRate;
2487
+ total_results['audioInfo']['sourceSampleRate'] = this.sourceSamplingRate;
2488
+ total_results['audioInfo']['bitsPerSample'] = this.sampleSize;
2489
+ const timeStampresult = [...this.timeStamp].join('\n');
2490
+ total_results['timeStamps'] = timeStampresult;
2491
+ total_results['recordingChecks'] = this.recordingChecks;
2492
+ total_results['component']['phase'] = this.componentIRPhase;
2493
+ total_results['system']['phase'] = this.systemIRPhase;
2494
+ total_results['qualityMetrics'] = {
2495
+ mlsSD: this.recordingChecks.unfiltered[0].sd,
2496
+ correctionSD: this.SDofFilteredRange,
2497
+ };
2498
+ console.log('total results');
2499
+ console.log(total_results);
2500
+ console.log('Time Stamps');
2501
+ console.log(timeStampresult);
2502
+
2503
+ resolve(total_results);
2504
+ });
2505
+ };
2506
+ }
2507
+
2508
+ export default Combination;