speaker-calibration 2.2.100 → 2.2.102

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