speaker-calibration 2.2.107 → 2.2.108

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