speaker-calibration 2.2.82 → 2.2.83

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