speaker-calibration 2.2.118 → 2.2.119

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