speaker-calibration 2.2.92 → 2.2.94

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