speaker-calibration 2.2.35 → 2.2.37

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 +6822 -6822
  32. package/dist/example/index.html +47 -47
  33. package/dist/example/listener.html +61 -61
  34. package/dist/example/listener.js +97 -97
  35. package/dist/example/server.js +51 -51
  36. package/dist/example/speaker.html +138 -138
  37. package/dist/example/speakerUI.js +269 -269
  38. package/dist/example/styles.css +63 -63
  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 +25 -25
  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 +288 -288
  99. package/src/peer-connection/peerErrors.js +25 -25
  100. package/src/peer-connection/speaker.js +439 -437
  101. package/src/server/PythonServerAPI.js +468 -468
  102. package/src/tasks/audioCalibrator.js +304 -304
  103. package/src/tasks/audioRecorder.js +267 -267
  104. package/src/tasks/combination/combination.js +1509 -1495
  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 +55 -55
  118. package/webpack.config.js +37 -37
  119. package/makefile +0 -74
@@ -1,1495 +1,1509 @@
1
- import AudioCalibrator from '../audioCalibrator';
2
-
3
- import {sleep, csvToArray, saveToCSV} from '../../utils';
4
- import database from '../../config/firebase';
5
- import {ref, set, get, child} from 'firebase/database';
6
-
7
- /**
8
- *
9
- */
10
- class Combination extends AudioCalibrator {
11
- /**
12
- * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.
13
- *
14
- * @param {Object<boolean, number, number, number>} calibratorParams - paramter object
15
- * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures
16
- * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated
17
- * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform
18
- * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture
19
- */
20
- constructor({
21
- download = false,
22
- mlsOrder = 18,
23
- numCaptures = 3,
24
- numMLSPerCapture = 4,
25
- lowHz = 20,
26
- highHz = 10000,
27
- }) {
28
- super(numCaptures, numMLSPerCapture);
29
- this.#mlsOrder = parseInt(mlsOrder, 10);
30
- this.#P = 2 ** mlsOrder - 1;
31
- this.#download = download;
32
- this.#mls = [];
33
- this.#lowHz = lowHz;
34
- this.#highHz = highHz;
35
- }
36
-
37
- /** @private */
38
- stepNum = 0;
39
-
40
- /** @private */
41
- totalSteps = 25;
42
-
43
- /** @private */
44
- #download;
45
-
46
- /** @private */
47
- #mlsGenInterface;
48
-
49
- /** @private */
50
- #mlsBufferView;
51
-
52
- /** @private */
53
- componentInvertedImpulseResponse = null;
54
-
55
- /** @private */
56
- systemInvertedImpulseResponse = null;
57
-
58
- //averaged and subtracted ir returned from calibration used to calculated iir
59
- /** @private */
60
- ir = null;
61
-
62
- /** @private */
63
- impulseResponses = [];
64
-
65
- /** @private */
66
- #mlsOrder;
67
-
68
- /** @private */
69
- #lowHz;
70
-
71
- /** @private */
72
- #highHz;
73
-
74
- /** @private */
75
- #mls;
76
-
77
- /** @private */
78
- #P;
79
-
80
- /** @private */
81
- #audioContext;
82
-
83
- /** @private */
84
- TAPER_SECS = 5;
85
-
86
- /** @private */
87
- offsetGainNode;
88
-
89
- /** @private */
90
- componentConvolution;
91
-
92
- /** @private */
93
- systemConvolution;
94
-
95
- ////////////////////////volume
96
- /** @private */
97
- #CALIBRATION_TONE_FREQUENCY = 1000; // Hz
98
-
99
- /** @private */
100
- #CALIBRATION_TONE_TYPE = 'sine';
101
-
102
- CALIBRATION_TONE_DURATION = 5; // seconds
103
-
104
- /** @private */
105
- outDBSPL = null;
106
- THD = null;
107
- outDBSPL1000 = null;
108
-
109
- /** @private */
110
- TAPER_SECS = 0.01; // seconds
111
-
112
- /** @private */
113
- status_denominator = 8;
114
-
115
- /** @private */
116
- status_numerator = 0;
117
-
118
- /** @private */
119
- percent_complete = 0;
120
-
121
- /** @private */
122
- status = ``;
123
-
124
- /**@private */
125
- 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>`;
126
-
127
- /**@private */
128
- componentIR = null;
129
-
130
- /**@private */
131
- oldComponentIR = null;
132
-
133
- /**@private */
134
- systemIR = null;
135
-
136
- /**@private */
137
- _calibrateSoundCheck = '';
138
-
139
- deviceType = null;
140
-
141
- deviceName = null;
142
-
143
- deviceInfo = null;
144
-
145
- desired_time_per_mls = 0;
146
-
147
- num_mls_to_skip = 0;
148
-
149
- desired_sampling_rate = 0;
150
-
151
- #currentConvolution = [];
152
-
153
- mode = 'unfiltered';
154
-
155
- sourceNode;
156
-
157
- autocorrelations = [];
158
-
159
- iirLength = 0;
160
-
161
- componentInvertedImpulseResponseNoBandpass = [];
162
-
163
- systemInvertedImpulseResponseNoBandpass = [];
164
-
165
- _calibrateSoundBackgroundSecs;
166
-
167
- background_noise = {};
168
-
169
- numSuccessfulBackgroundCaptured;
170
-
171
- /**generate string template that gets reevaluated as variable increases */
172
- generateTemplate = () => {
173
- if (this.percent_complete > 100) {
174
- this.percent_complete = 100;
175
- }
176
- const template = `<div style="display: flex; justify-content: center;"><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>`;
177
- return template;
178
- };
179
-
180
- /** increment numerator and percent for status bar */
181
- incrementStatusBar = () => {
182
- this.status_numerator += 1;
183
- this.percent_complete = (this.status_numerator / this.status_denominator) * 100;
184
- };
185
-
186
- setDeviceType = deviceType => {
187
- this.deviceType = deviceType;
188
- };
189
-
190
- setDeviceName = deviceName => {
191
- this.deviceName = deviceName;
192
- };
193
-
194
- setDeviceInfo = deviceInfo => {
195
- this.deviceInfo = deviceInfo;
196
- };
197
-
198
- /** .
199
- * .
200
- * .
201
- * Sends all the computed impulse responses to the backend server for processing
202
- *
203
- * @returns sets the resulting inverted impulse response to the class property
204
- * @example
205
- */
206
- sendSystemImpulseResponsesToServerForProcessing = async () => {
207
- const computedIRs = await Promise.all(this.impulseResponses);
208
- const filteredComputedIRs = computedIRs.filter(element => {
209
- return element != undefined;
210
- }); //log any errors that are found in this step
211
- const mls = this.#mls;
212
- const lowHz = this.#lowHz; //gain of 1 below cutoff, need gain of 0
213
- const highHz = this.#highHz; //check error for anything other than 10 kHz
214
- const iirLength = this.iirLength;
215
- const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
216
- this.stepNum += 1;
217
- console.log('send impulse responses to server: ' + this.stepNum);
218
- this.status =
219
- `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
220
- this.emit('update', {message: this.status});
221
- return this.pyServerAPI
222
- .getSystemInverseImpulseResponseWithRetry({
223
- payload: filteredComputedIRs.slice(0, this.numCaptures),
224
- mls,
225
- lowHz,
226
- highHz,
227
- iirLength,
228
- num_periods,
229
- sampleRate: this.sourceSamplingRate || 96000,
230
- })
231
- .then(res => {
232
- console.log(res);
233
- this.stepNum += 1;
234
- console.log('got impulse response ' + this.stepNum);
235
- this.incrementStatusBar();
236
- this.status =
237
- `All Hz Calibration: done computing the IIR...`.toString() +
238
- this.generateTemplate().toString();
239
- this.emit('update', {message: this.status});
240
- this.systemInvertedImpulseResponse = res['iir'];
241
- this.systemIR = res['ir'];
242
- this.systemConvolution = res['convolution'];
243
- this.systemInvertedImpulseResponseNoBandpass = res['iirNoBandpass'];
244
- })
245
- .catch(err => {
246
- console.error(err);
247
- });
248
- };
249
-
250
- /** .
251
- * .
252
- * .
253
- * Sends all the computed impulse responses to the backend server for processing
254
- *
255
- * @returns sets the resulting inverted impulse response to the class property
256
- * @example
257
- */
258
- sendComponentImpulseResponsesToServerForProcessing = async () => {
259
- const computedIRs = await Promise.all(this.impulseResponses);
260
- const filteredComputedIRs = computedIRs.filter(element => {
261
- return element != undefined;
262
- });
263
- const componentIRGains = this.componentIR['Gain'];
264
- const componentIRFreqs = this.componentIR['Freq'];
265
- const mls = this.#mls;
266
- const lowHz = this.#lowHz;
267
- const iirLength = this.iirLength;
268
- const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
269
- const highHz = this.#highHz;
270
- this.stepNum += 1;
271
- console.log('send impulse responses to server: ' + this.stepNum);
272
- this.status =
273
- `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
274
- this.emit('update', {message: this.status});
275
- return this.pyServerAPI
276
- .getComponentInverseImpulseResponseWithRetry({
277
- payload: filteredComputedIRs.slice(0, this.numCaptures),
278
- mls,
279
- lowHz,
280
- highHz,
281
- iirLength,
282
- componentIRGains,
283
- componentIRFreqs,
284
- num_periods,
285
- sampleRate: this.sourceSamplingRate || 96000,
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.componentInvertedImpulseResponse = res['iir'];
297
- this.componentIR['Gain'] = res['ir'];
298
- this.componentIR['Freq'] = res['frequencies'];
299
- this.componentConvolution = res['convolution'];
300
- this.componentInvertedImpulseResponseNoBandpass = res['iirNoBandpass'];
301
- })
302
- .catch(err => {
303
- // this.emit('InvertedImpulseResponse', {res: false});
304
- console.error(err);
305
- });
306
- };
307
-
308
- sendBackgroundRecording = () => {
309
- const allSignals = this.getAllBackgroundRecordings();
310
- const numSignals = allSignals.length;
311
- const background_rec_whole = allSignals[numSignals-1];
312
- const fraction = .5/(this._calibrateSoundBackgroundSecs + .5);
313
- // Calculate the starting index for slicing the array
314
- const startIndex = Math.round(fraction * background_rec_whole.length);
315
- // Slice the array from the calculated start index to the end of the array
316
- const background_rec = background_rec_whole.slice(startIndex);
317
- console.log('Sending background recording to server for processing');
318
- this.pyServerAPI
319
- .getBackgroundNoisePSDWithRetry({
320
- background_rec,
321
- sampleRate: this.sourceSamplingRate || 96000,
322
- })
323
- .then(res => {
324
- if (this.numSuccessfulBackgroundCaptured < 1) {
325
- this.numSuccessfulBackgroundCaptured += 1;
326
- //storing all background data in background_psd object
327
- this.background_noise['x_background'] = res['x_background'];
328
- this.background_noise['y_background'] = res['y_background'];
329
- this.background_noise['recording'] = background_rec;
330
- }
331
- })
332
- .catch(err => {
333
- console.error(err);
334
- });
335
- }
336
-
337
- /** .
338
- * .
339
- * .
340
- * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing
341
- *
342
- * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed
343
- * @example
344
- */
345
- sendRecordingToServerForProcessing = signalCsv => {
346
- const allSignals = this.getAllUnfilteredRecordedSignals();
347
- console.log(
348
- 'Obtaining last all hz unfiltered recording from #allHzUnfilteredRecordings to send to server for processing'
349
- );
350
- const numSignals = allSignals.length;
351
- const mls = this.#mls;
352
- const payload =
353
- signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
354
- console.log('sending rec');
355
- this.stepNum += 1;
356
- console.log('send rec ' + this.stepNum);
357
- this.status =
358
- `All Hz Calibration Step: computing the IR of the last recording...`.toString() +
359
- this.generateTemplate().toString();
360
- this.emit('update', {message: this.status});
361
- this.impulseResponses.push(
362
- this.pyServerAPI
363
- .getImpulseResponse({
364
- sampleRate: this.sourceSamplingRate || 96000,
365
- payload,
366
- mls,
367
- P: this.#P, //get rid of this
368
- numPeriods: this.numMLSPerCapture,
369
- })
370
- .then(res => {
371
- if (this.numSuccessfulCaptured < this.numCaptures) {
372
- this.numSuccessfulCaptured += 1;
373
- console.log('num succ capt: ' + this.numSuccessfulCaptured);
374
- this.stepNum += 1;
375
- console.log('got impulse response ' + this.stepNum);
376
- this.incrementStatusBar();
377
- this.status =
378
- `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +
379
- this.generateTemplate().toString();
380
- this.emit('update', {
381
- message: this.status,
382
- });
383
- this.autocorrelations.push(res['autocorrelation']);
384
- return res['ir'];
385
- }
386
- })
387
- .catch(err => {
388
- console.error(err);
389
- })
390
- );
391
- };
392
-
393
- /**
394
- * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number
395
- * of MLS periods defined in the constructor.
396
- *
397
- * @example
398
- */
399
- #awaitDesiredMLSLength = async () => {
400
- // seconds per MLS = P / SR
401
- // await N * P / SR
402
- this.stepNum += 1;
403
- console.log('await desired length ' + this.stepNum);
404
- this.status =
405
- `All Hz Calibration: sampling the calibration signal...`.toString() +
406
- `\niteration ${this.stepNum}` +
407
- this.generateTemplate();
408
- this.emit('update', {
409
- message: this.status,
410
- });
411
- let time_to_wait = 0;
412
- if (this.mode === 'unfiltered') {
413
- time_to_wait = (this.#mls.length / this.sourceSamplingRate) * this.numMLSPerCapture;
414
- time_to_wait = time_to_wait * 1.1;
415
- } else if (this.mode === 'filtered') {
416
- time_to_wait =
417
- (this.#currentConvolution.length / this.sourceSamplingRate) *
418
- (this.numMLSPerCapture / (this.num_mls_to_skip + this.numMLSPerCapture));
419
- } else {
420
- throw new Error('Mode broke in awaitDesiredMLSLength');
421
- }
422
-
423
- await sleep(time_to_wait);
424
- };
425
-
426
- /**
427
- * Passed to the background noise recording function, awaits the desired amount of seconds to capture the desired number
428
- * of seconds of background noise
429
- *
430
- * @example
431
- */
432
- #awaitBackgroundNoiseRecording = async () => {
433
- console.log('Waiting ' + this._calibrateSoundBackgroundSecs + " second(s) to record background noise");
434
- let time_to_wait = this._calibrateSoundBackgroundSecs + .5;
435
- await sleep(time_to_wait);
436
- };
437
-
438
-
439
- /** .
440
- * .
441
- * .
442
- * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state
443
- *
444
- * @example
445
- */
446
- #awaitSignalOnset = async () => {
447
- this.stepNum += 1;
448
- console.log('await signal onset ' + this.stepNum);
449
- this.status =
450
- `All Hz Calibration: waiting for the signal to stabilize...`.toString() +
451
- this.generateTemplate();
452
- this.emit('update', {
453
- message: this.status,
454
- });
455
- let number_of_bursts_to_skip = this.num_mls_to_skip;
456
- let time_to_sleep = 0;
457
- if (this.mode === 'unfiltered') {
458
- time_to_sleep = (this.#mls.length / this.sourceSamplingRate) * number_of_bursts_to_skip;
459
- } else if (this.mode === 'filtered') {
460
- console.log(this.#currentConvolution.length);
461
- time_to_sleep =
462
- (this.#currentConvolution.length / this.sourceSamplingRate) *
463
- (number_of_bursts_to_skip / (number_of_bursts_to_skip + this.numMLSPerCapture));
464
- } else {
465
- throw new Error('Mode broke in awaitSignalOnset');
466
- }
467
- await sleep(time_to_sleep);
468
- };
469
-
470
- /**
471
- * Called immediately after a recording is captured. Used to process the resulting signal
472
- * whether by sending the result to a server or by computing a result locally.
473
- *
474
- * @example
475
- */
476
- #afterMLSRecord = () => {
477
- console.log('after record');
478
- this.sendRecordingToServerForProcessing();
479
- };
480
-
481
- #afterMLSwIIRRecord = () => {
482
- if (this.numSuccessfulCaptured < 1) {
483
- this.numSuccessfulCaptured += 1;
484
- this.stepNum += 1;
485
- this.incrementStatusBar();
486
- console.log('after mls w iir record for some reason add numSucc capt ' + this.stepNum);
487
- this.status =
488
- `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +
489
- this.generateTemplate().toString();
490
- this.emit('update', {
491
- message: this.status,
492
- });
493
- }
494
- };
495
-
496
- /** .
497
- * .
498
- * .
499
- * Created an S Curver Buffer to taper the signal onset
500
- *
501
- * @param {*} length
502
- * @param {*} phase
503
- * @returns
504
- * @example
505
- */
506
- static createSCurveBuffer = (length, phase) => {
507
- const curve = new Float32Array(length);
508
- let i;
509
- for (i = 0; i < length; i += 1) {
510
- // scale the curve to be between 0-1
511
- curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;
512
- }
513
- return curve;
514
- };
515
-
516
- static createInverseSCurveBuffer = (length, phase) => {
517
- const curve = new Float32Array(length);
518
- let i;
519
- let j = length - 1;
520
- for (i = 0; i < length; i += 1) {
521
- // scale the curve to be between 0-1
522
- curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;
523
- j -= 1;
524
- }
525
- return curve;
526
- };
527
-
528
- /**
529
- * Construct a Calibration Node with the calibration parameters.
530
- *
531
- * @param dataBuffer
532
- * @private
533
- * @example
534
- */
535
- #createCalibrationNodeFromBuffer = dataBuffer => {
536
- console.log('databuffer');
537
- console.log(dataBuffer);
538
- if (!this.sourceAudioContext) {
539
- this.makeNewSourceAudioContext();
540
- }
541
-
542
- const buffer = this.sourceAudioContext.createBuffer(
543
- 1, // number of channels
544
- dataBuffer.length,
545
- this.sourceAudioContext.sampleRate // sample rate
546
- );
547
-
548
- const data = buffer.getChannelData(0); // get data
549
- // fill the buffer with our data
550
- try {
551
- for (let i = 0; i < dataBuffer.length; i += 1) {
552
- data[i] = dataBuffer[i] * 0.33;
553
- }
554
- } catch (error) {
555
- console.error(error);
556
- }
557
-
558
- this.sourceNode = this.sourceAudioContext.createBufferSource();
559
-
560
- this.sourceNode.buffer = buffer;
561
- if (this.mode === 'filtered') {
562
- this.sourceNode.loop = false;
563
- } else {
564
- this.sourceNode.loop = true;
565
- }
566
-
567
- this.sourceNode.connect(this.sourceAudioContext.destination);
568
-
569
- this.addCalibrationNode(this.sourceNode);
570
- };
571
-
572
- /**
573
- * Given a data buffer, creates the required calibration node
574
- *
575
- * @param {*} dataBufferArray
576
- * @example
577
- */
578
- #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {
579
- if (dataBufferArray.length === 1) {
580
- this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);
581
- } else {
582
- throw new Error('The length of the data buffer array must be 1');
583
- }
584
- };
585
-
586
- /**
587
- * Creates an audio context and plays it for a few seconds.
588
- *
589
- * @private
590
- * @returns - Resolves when the audio is done playing.
591
- * @example
592
- */
593
- #playCalibrationAudio = () => {
594
- this.calibrationNodes[0].start(0);
595
- this.status = ``;
596
- if (this.mode === 'unfiltered') {
597
- this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);
598
- console.log('play calibration audio ' + this.stepNum);
599
- this.status =
600
- `All Hz Calibration: playing the calibration tone...`.toString() +
601
- this.generateTemplate().toString();
602
- } else if (this.mode === 'filtered') {
603
- console.log('play convolved audio ' + this.stepNum);
604
- this.status =
605
- `All Hz Calibration: playing the convolved calibration tone...`.toString() +
606
- this.generateTemplate().toString();
607
- } else {
608
- throw new Error('Mode is incorrect');
609
- }
610
- this.emit('update', {message: this.status});
611
- this.stepNum += 1;
612
- console.log('sink sampling rate');
613
- console.log(this.sinkSamplingRate);
614
- console.log('source sampling rate');
615
- console.log(this.sourceSamplingRate);
616
- };
617
-
618
- /** .
619
- * .
620
- * .
621
- * Stops the audio with tapered offset
622
- *
623
- * @example
624
- */
625
- #stopCalibrationAudio = () => {
626
- this.calibrationNodes[0].stop(0);
627
- this.calibrationNodes = [];
628
- this.sourceNode.disconnect();
629
- this.stepNum += 1;
630
- console.log('stop calibration audio ' + this.stepNum);
631
- this.status =
632
- `All Hz Calibration: stopping the calibration tone...`.toString() +
633
- this.generateTemplate().toString();
634
- this.emit('update', {message: this.status});
635
- };
636
-
637
- playMLSwithIIR = async (stream, iir) => {
638
- let checkRec = false;
639
- this.mode = 'filtered';
640
- console.log('play mls with iir');
641
- this.invertedImpulseResponse = iir;
642
-
643
- await this.calibrationSteps(
644
- stream,
645
- this.#playCalibrationAudio, // play audio func (required)
646
- this.#createCalibrationNodeFromBuffer(this.#currentConvolution), // before play func
647
- this.#awaitSignalOnset, // before record
648
- () => this.numSuccessfulCaptured < 1,
649
- this.#awaitDesiredMLSLength, // during record
650
- this.#afterMLSwIIRRecord, // after record
651
- this.mode,
652
- checkRec
653
- );
654
- };
655
-
656
- /**
657
- * Public method to start the calibration process. Objects intialized from webassembly allocate new memory
658
- * and must be manually freed. This function is responsible for intializing the MlsGenInterface,
659
- * and wrapping the calibration steps with a garbage collection safe gaurd.
660
- *
661
- * @public
662
- * @param stream - The stream of audio from the Listener.
663
- * @example
664
- */
665
- startCalibrationImpulseResponse = async stream => {
666
- let desired_time = this.desired_time_per_mls;
667
- let checkRec = 'allhz';
668
-
669
- console.log('MLS sequence should be of length: ' + this.sourceSamplingRate * desired_time);
670
-
671
- length = this.sourceSamplingRate * desired_time;
672
- //get mls here
673
- await this.pyServerAPI
674
- .getMLSWithRetry(length)
675
- .then(res => {
676
- console.log(res);
677
- this.#mlsBufferView = res['mls'];
678
- })
679
- .catch(err => {
680
- // this.emit('InvertedImpulseResponse', {res: false});
681
- console.error(err);
682
- });
683
- this.numSuccessfulBackgroundCaptured = 0;
684
- if (this._calibrateSoundBackgroundSecs > 0){
685
- this.mode='background';
686
- this.status =
687
- `All Hz Calibration: sampling the background noise...`.toString() + this.generateTemplate().toString();
688
- this.emit('update', {message: this.status});
689
- await this.recordBackground(
690
- stream, //stream
691
- () => this.numSuccessfulBackgroundCaptured < 1, //loop condition
692
- this.#awaitBackgroundNoiseRecording, //sleep to record
693
- this.sendBackgroundRecording, //send to get PSD
694
- this.mode,
695
- checkRec
696
- )
697
- this.incrementStatusBar();
698
- }
699
- this.mode='unfiltered';
700
- this.numSuccessfulCaptured = 0;
701
-
702
- await this.calibrationSteps(
703
- stream,
704
- this.#playCalibrationAudio, // play audio func (required)
705
- this.#createCalibrationNodeFromBuffer(this.#mlsBufferView), // before play func
706
- this.#awaitSignalOnset, // before record
707
- () => this.numSuccessfulCaptured < this.numCaptures, // loop while true
708
- this.#awaitDesiredMLSLength, // during record
709
- this.#afterMLSRecord, // after record
710
- this.mode,
711
- checkRec
712
- ),
713
- this.#stopCalibrationAudio();
714
- checkRec = false;
715
-
716
- // at this stage we've captured all the required signals,
717
- // and have received IRs for each one
718
- // so let's send all the IRs to the server to be converted to a single IIR
719
- await this.sendSystemImpulseResponsesToServerForProcessing();
720
- await this.sendComponentImpulseResponsesToServerForProcessing();
721
-
722
- this.numSuccessfulCaptured = 0;
723
-
724
- let iir_ir_and_plots;
725
- if (this._calibrateSoundCheck != 'none') {
726
- if (this._calibrateSoundCheck != 'system') {
727
- this.#currentConvolution = this.componentConvolution;
728
- } else {
729
- this.#currentConvolution = this.systemConvolution;
730
- }
731
- await this.playMLSwithIIR(stream, this.invertedImpulseResponse);
732
- this.#stopCalibrationAudio();
733
- this.sourceAudioContext.close();
734
- let conv_recs = this.getAllFilteredRecordedSignals();
735
- let recs = this.getAllUnfilteredRecordedSignals();
736
- console.log(
737
- 'Obtaining unfiltered recording from #allHzUnfilteredRecordings to calculate PSD'
738
- );
739
- console.log('Obtaining filtered recording from #allHzFilteredRecordings to calculate PSD');
740
- let unconv_rec = recs[0];
741
- let conv_rec = conv_recs[0];
742
- if (this._calibrateSoundCheck != 'system') {
743
- let knownGain = this.oldComponentIR.Gain;
744
- let knownFreq = this.oldComponentIR.Freq;
745
- let sampleRate = this.sourceSamplingRate || 96000;
746
- let unconv_results = await this.pyServerAPI
747
- .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)
748
- .then(res => {
749
- this.incrementStatusBar();
750
- this.status =
751
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
752
- this.generateTemplate().toString();
753
- this.emit('update', {message: this.status});
754
- return res;
755
- })
756
- .catch(err => {
757
- console.error(err);
758
- });
759
-
760
- let conv_results = await this.pyServerAPI
761
- .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)
762
- .then(res => {
763
- this.incrementStatusBar();
764
- this.status =
765
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
766
- this.generateTemplate().toString();
767
- this.emit('update', {message: this.status});
768
- return res;
769
- })
770
- .catch(err => {
771
- console.error(err);
772
- });
773
- unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
774
- conv_rec = this.componentInvertedImpulseResponse;
775
- let component_iir_psd = await this.pyServerAPI
776
- .getPSDWithRetry({
777
- unconv_rec,
778
- conv_rec,
779
- sampleRate: this.sourceSamplingRate || 96000,
780
- })
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
- unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
793
- conv_rec = this.systemInvertedImpulseResponse;
794
- let system_iir_psd = await this.pyServerAPI
795
- .getPSDWithRetry({
796
- unconv_rec,
797
- conv_rec,
798
- sampleRate: this.sourceSamplingRate || 96000,
799
- })
800
- .then(res => {
801
- this.incrementStatusBar();
802
- this.status =
803
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
804
- this.generateTemplate().toString();
805
- this.emit('update', {message: this.status});
806
- return res;
807
- })
808
- .catch(err => {
809
- console.error(err);
810
- });
811
- iir_ir_and_plots = {
812
- systemIIR: this.systemInvertedImpulseResponse,
813
- componentIIR: this.componentInvertedImpulseResponse,
814
- x_unconv: unconv_results['x'],
815
- y_unconv: unconv_results['y'],
816
- x_conv: conv_results['x'],
817
- y_conv: conv_results['y'],
818
- componentIR: this.componentIR,
819
- systemIR: this.systemIR,
820
- x_system_iir: system_iir_psd['x_conv'],
821
- y_system_iir: system_iir_psd['y_conv'],
822
- x_system_iir_no_bandpass: system_iir_psd['x_unconv'],
823
- y_system_iir_no_bandpass: system_iir_psd['y_unconv'],
824
- x_component_iir: component_iir_psd['x_conv'],
825
- y_component_iir: component_iir_psd['y_conv'],
826
- x_component_iir_no_bandpass: component_iir_psd['x_unconv'],
827
- y_component_iir_no_bandpass: component_iir_psd['y_unconv'],
828
- unconv_rec: recs[0],
829
- conv_rec: conv_recs[0],
830
- mls: this.#mls,
831
- componentConvolution: this.componentConvolution,
832
- systemConvolution: this.systemConvolution,
833
- autocorrelations:this.autocorrelations,
834
- impulseResponses: []
835
- };
836
- } else {
837
- let results = await this.pyServerAPI
838
- .getPSDWithRetry({
839
- unconv_rec,
840
- conv_rec,
841
- sampleRate: this.sourceSamplingRate || 96000,
842
- })
843
- .then(res => {
844
- this.incrementStatusBar();
845
- this.status =
846
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
847
- this.generateTemplate().toString();
848
- this.emit('update', {message: this.status});
849
- return res;
850
- })
851
- .catch(err => {
852
- console.error(err);
853
- });
854
-
855
- //iir w/ and without bandpass psd
856
- unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
857
- conv_rec = this.componentInvertedImpulseResponse;
858
- let component_iir_psd = await this.pyServerAPI
859
- .getPSDWithRetry({
860
- unconv_rec,
861
- conv_rec,
862
- sampleRate: this.sourceSamplingRate || 96000,
863
- })
864
- .then(res => {
865
- this.incrementStatusBar();
866
- this.status =
867
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
868
- this.generateTemplate().toString();
869
- this.emit('update', {message: this.status});
870
- return res;
871
- })
872
- .catch(err => {
873
- console.error(err);
874
- });
875
- unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
876
- conv_rec = this.systemInvertedImpulseResponse;
877
- let system_iir_psd = await this.pyServerAPI
878
- .getPSDWithRetry({
879
- unconv_rec,
880
- conv_rec,
881
- sampleRate: this.sourceSamplingRate || 96000,
882
- })
883
- .then(res => {
884
- this.incrementStatusBar();
885
- this.status =
886
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
887
- this.generateTemplate().toString();
888
- this.emit('update', {message: this.status});
889
- return res;
890
- })
891
- .catch(err => {
892
- console.error(err);
893
- });
894
-
895
- iir_ir_and_plots = {
896
- systemIIR: this.systemInvertedImpulseResponse,
897
- componentIIR: this.componentInvertedImpulseResponse,
898
- x_unconv: results['x_unconv'],
899
- y_unconv: results['y_unconv'],
900
- x_conv: results['x_conv'],
901
- y_conv: results['y_conv'],
902
- componentIR: this.componentIR,
903
- systemIR: this.systemIR,
904
- x_system_iir: system_iir_psd['x_conv'],
905
- y_system_iir: system_iir_psd['y_conv'],
906
- x_system_iir_no_bandpass: system_iir_psd['x_unconv'],
907
- y_system_iir_no_bandpass: system_iir_psd['y_unconv'],
908
- x_component_iir: component_iir_psd['x_conv'],
909
- y_component_iir: component_iir_psd['y_conv'],
910
- x_component_iir_no_bandpass: component_iir_psd['x_unconv'],
911
- y_component_iir_no_bandpass: component_iir_psd['y_unconv'],
912
- unconv_rec: recs[0],
913
- conv_rec: conv_recs[0],
914
- mls: this.#mls,
915
- componentConvolution: this.componentConvolution,
916
- systemConvolution: this.systemConvolution,
917
- autocorrelations:this.autocorrelations,
918
- impulseResponses: []
919
- };
920
- }
921
- await Promise.all(this.impulseResponses).then(res => {
922
- for (let i = 0; i < res.length; i++) {
923
- if (res[i] != undefined) {
924
- iir_ir_and_plots['impulseResponses'].push(res[i])
925
- }
926
- }
927
- });
928
-
929
- if (this.#download) {
930
- this.downloadSingleUnfilteredRecording();
931
- this.downloadSingleFilteredRecording();
932
- saveToCSV(this.#mls, 'MLS.csv');
933
- saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
934
- saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
935
- saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
936
- saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
937
- for (let i = 0; i < this.autocorrelations.length; i++) {
938
- saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
939
- }
940
- const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
941
- for (let i = 0; i < res.length; i++) {
942
- if (res[i] != undefined) {
943
- saveToCSV(res[i], `IR_${i}`);
944
- }
945
- }
946
- });
947
- }
948
- } else {
949
- let unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
950
- let conv_rec = this.componentInvertedImpulseResponse;
951
- let component_iir_psd = await this.pyServerAPI
952
- .getPSDWithRetry({
953
- unconv_rec,
954
- conv_rec,
955
- sampleRate: this.sourceSamplingRate || 96000,
956
- })
957
- .then(res => {
958
- this.incrementStatusBar();
959
- this.status =
960
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
961
- this.generateTemplate().toString();
962
- this.emit('update', {message: this.status});
963
- return res;
964
- })
965
- .catch(err => {
966
- console.error(err);
967
- });
968
- unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
969
- conv_rec = this.systemInvertedImpulseResponse;
970
- let system_iir_psd = await this.pyServerAPI
971
- .getPSDWithRetry({
972
- unconv_rec,
973
- conv_rec,
974
- sampleRate: this.sourceSamplingRate || 96000,
975
- })
976
- .then(res => {
977
- this.incrementStatusBar();
978
- this.status =
979
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
980
- this.generateTemplate().toString();
981
- this.emit('update', {message: this.status});
982
- return res;
983
- })
984
- .catch(err => {
985
- console.error(err);
986
- });
987
- iir_ir_and_plots = {
988
- systemIIR: this.systemInvertedImpulseResponse,
989
- componentIIR: this.componentInvertedImpulseResponse,
990
- x_unconv: [],
991
- y_unconv: [],
992
- x_conv: [],
993
- y_conv: [],
994
- componentIR: this.componentIR,
995
- systemIR: this.systemIR,
996
- x_system_iir: system_iir_psd['x_conv'],
997
- y_system_iir: system_iir_psd['y_conv'],
998
- x_system_iir_no_bandpass: system_iir_psd['x_unconv'],
999
- y_system_iir_no_bandpass: system_iir_psd['y_unconv'],
1000
- x_component_iir: component_iir_psd['x_conv'],
1001
- y_component_iir: component_iir_psd['y_conv'],
1002
- x_component_iir_no_bandpass: component_iir_psd['x_unconv'],
1003
- y_component_iir_no_bandpass: component_iir_psd['y_unconv'],
1004
- unconv_rec: recs[0],
1005
- conv_rec: conv_recs[0],
1006
- mls: this.#mls,
1007
- componentConvolution: this.componentConvolution,
1008
- systemConvolution: this.systemConvolution,
1009
- autocorrelations:this.autocorrelations,
1010
- impulseResponses: []
1011
- };
1012
- await Promise.all(this.impulseResponses).then(res => {
1013
- for (let i = 0; i < res.length; i++) {
1014
- if (res[i] != undefined) {
1015
- iir_ir_and_plots['impulseResponses'].push(res[i])
1016
- }
1017
- }
1018
- });
1019
-
1020
- if (this.#download) {
1021
- saveToCSV(this.#mls, 'MLS.csv');
1022
- saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
1023
- saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
1024
- saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
1025
- saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
1026
- for (let i = 0; i < this.autocorrelations.length; i++) {
1027
- saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
1028
- }
1029
- const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
1030
- for (let i = 0; i < res.length; i++) {
1031
- if (res[i] != undefined) {
1032
- saveToCSV(res[i], `IR_${i}`);
1033
- }
1034
- }
1035
- });
1036
- }
1037
- }
1038
-
1039
- this.percent_complete = 100;
1040
-
1041
- this.status = `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();
1042
- this.emit('update', {message: this.status});
1043
-
1044
- //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR
1045
- //that was used to calibrate
1046
-
1047
- return iir_ir_and_plots;
1048
- };
1049
-
1050
- //////////////////////volume
1051
-
1052
- handleIncomingData = data => {
1053
- console.log('Received data: ', data);
1054
- if (data.type === 'soundGainDBSPL') {
1055
- this.soundGainDBSPL = data.value;
1056
- } else {
1057
- throw new Error(`Unknown data type: ${data.type}`);
1058
- }
1059
- };
1060
- createSCurveBuffer = (onSetBool = true) => {
1061
- const curve = new Float32Array(this.TAPER_SECS * this.sourceSamplingRate + 1);
1062
- const frequency = 1 / (4 * this.TAPER_SECS);
1063
- let j = 0;
1064
- for (let i = 0; i < this.TAPER_SECS * this.sourceSamplingRate + 1; i += 1) {
1065
- const phase = 2 * Math.PI * frequency * j;
1066
- const onsetTaper = Math.pow(Math.sin(phase), 2);
1067
- const offsetTaper = Math.pow(Math.cos(phase), 2);
1068
- curve[i] = onSetBool ? onsetTaper : offsetTaper;
1069
- j += 1 / this.sourceSamplingRate;
1070
- }
1071
- return curve;
1072
- };
1073
-
1074
- #getTruncatedSignal = (left = 3.5, right = 4.5) => {
1075
- const start = Math.floor(left * this.sourceSamplingRate);
1076
- const end = Math.floor(right * this.sourceSamplingRate);
1077
- const result = Array.from(this.getLastVolumeRecordedSignal().slice(start, end));
1078
- console.log(
1079
- 'Obtaining last 1000 hz recording from #allVolumeRecordings to send for processing'
1080
- );
1081
- /**
1082
- * function to check that capture was properly made
1083
- * @param {*} list
1084
- */
1085
- const checkResult = list => {
1086
- const setItem = new Set(list);
1087
- if (setItem.size === 1 && setItem.has(0)) {
1088
- console.warn(
1089
- 'The last capture failed, all recorded signal is zero',
1090
- this.getAllVolumeRecordedSignals()
1091
- );
1092
- }
1093
- if (setItem.size === 0) {
1094
- console.warn('The last capture failed, no recorded signal');
1095
- }
1096
- };
1097
- checkResult(result);
1098
- return result;
1099
- };
1100
-
1101
- /**
1102
- *
1103
- *
1104
- Construct a calibration Node with the calibration parameters and given gain value
1105
- * @param {*} gainValue
1106
- * */
1107
- #createCalibrationToneWithGainValue = gainValue => {
1108
- const audioContext = this.makeNewSourceAudioContext();
1109
- const oscilator = audioContext.createOscillator();
1110
- const gainNode = audioContext.createGain();
1111
- const taperGainNode = audioContext.createGain();
1112
- const offsetGainNode = audioContext.createGain();
1113
- const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
1114
-
1115
- oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
1116
- oscilator.type = this.#CALIBRATION_TONE_TYPE;
1117
- gainNode.gain.value = gainValue;
1118
-
1119
- oscilator.connect(gainNode);
1120
- gainNode.connect(taperGainNode);
1121
- const onsetCurve = this.createSCurveBuffer();
1122
- taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
1123
- taperGainNode.connect(offsetGainNode);
1124
- const offsetCurve = this.createSCurveBuffer(false);
1125
- offsetGainNode.gain.setValueCurveAtTime(
1126
- offsetCurve,
1127
- totalDuration - this.TAPER_SECS,
1128
- this.TAPER_SECS
1129
- );
1130
- offsetGainNode.connect(audioContext.destination);
1131
-
1132
- this.addCalibrationNode(oscilator);
1133
- };
1134
-
1135
- /**
1136
- * Construct a Calibration Node with the calibration parameters.
1137
- *
1138
- * @private
1139
- * @example
1140
- */
1141
- #createCalibrationNode = () => {
1142
- const audioContext = this.makeNewSourceAudioContext();
1143
- const oscilator = audioContext.createOscillator();
1144
- const gainNode = audioContext.createGain();
1145
-
1146
- oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
1147
- oscilator.type = this.#CALIBRATION_TONE_TYPE;
1148
- gainNode.gain.value = 0.04;
1149
-
1150
- oscilator.connect(gainNode);
1151
- gainNode.connect(audioContext.destination);
1152
-
1153
- this.addCalibrationNode(oscilator);
1154
- };
1155
-
1156
- #playCalibrationAudioVolume = async () => {
1157
- const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
1158
-
1159
- this.calibrationNodes[0].start(0);
1160
- this.calibrationNodes[0].stop(totalDuration);
1161
- console.log(`Playing a buffer of ${this.CALIBRATION_TONE_DURATION} seconds of audio`);
1162
- console.log(`Waiting a total of ${totalDuration} seconds`);
1163
- await sleep(totalDuration);
1164
- };
1165
-
1166
- #sendToServerForProcessing = lCalib => {
1167
- console.log('Sending data to server');
1168
- this.pyServerAPI
1169
- .getVolumeCalibration({
1170
- sampleRate: this.sourceSamplingRate,
1171
- payload: this.#getTruncatedSignal(),
1172
- lCalib: lCalib,
1173
- })
1174
- .then(res => {
1175
- if (this.outDBSPL === null) {
1176
- this.incrementStatusBar();
1177
- this.outDBSPL = res['outDbSPL'];
1178
- this.outDBSPL1000 = res['outDbSPL1000'];
1179
- this.THD = res['thd'];
1180
- }
1181
- })
1182
- .catch(err => {
1183
- console.warn(err);
1184
- });
1185
- };
1186
-
1187
- startCalibrationVolume = async (stream, gainValues, lCalib, componentGainDBSPL) => {
1188
- const trialIterations = gainValues.length;
1189
- this.status_denominator += trialIterations;
1190
- const thdValues = [];
1191
- const inDBValues = [];
1192
- let inDB = 0;
1193
- const outDBSPLValues = [];
1194
- const outDBSPL1000Values = [];
1195
- let checkRec = false;
1196
-
1197
- // do one calibration that will be discarded
1198
- const soundLevelToDiscard = -60;
1199
- const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);
1200
- this.status =
1201
- `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() +
1202
- this.generateTemplate().toString();
1203
- //this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});
1204
- this.emit('update', {message: this.status});
1205
-
1206
- do {
1207
- // eslint-disable-next-line no-await-in-loop
1208
- await this.volumeCalibrationSteps(
1209
- stream,
1210
- this.#playCalibrationAudioVolume,
1211
- this.#createCalibrationToneWithGainValue,
1212
- this.#sendToServerForProcessing,
1213
- gainToDiscard,
1214
- lCalib, //todo make this a class parameter
1215
- checkRec
1216
- );
1217
- } while (this.outDBSPL === null);
1218
- //reset the values
1219
- //this.incrementStatusBar();
1220
-
1221
- this.outDBSPL = null;
1222
- this.outDBSPL = null;
1223
- this.outDBSPL1000 = null;
1224
- this.THD = null;
1225
-
1226
- // run the calibration at different gain values provided by the user
1227
- for (let i = 0; i < trialIterations; i++) {
1228
- //convert gain to DB and add to inDB
1229
- if (i == trialIterations - 1) {
1230
- checkRec = 'loudest';
1231
- }
1232
- inDB = Math.log10(gainValues[i]) * 20;
1233
- // precision to 1 decimal place
1234
- inDB = Math.round(inDB * 10) / 10;
1235
- inDBValues.push(inDB);
1236
- console.log('next update');
1237
- this.status =
1238
- `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() +
1239
- this.generateTemplate().toString();
1240
- this.emit('update', {message: this.status});
1241
- do {
1242
- // eslint-disable-next-line no-await-in-loop
1243
- await this.volumeCalibrationSteps(
1244
- stream,
1245
- this.#playCalibrationAudioVolume,
1246
- this.#createCalibrationToneWithGainValue,
1247
- this.#sendToServerForProcessing,
1248
- gainValues[i],
1249
- lCalib, //todo make this a class parameter
1250
- checkRec
1251
- );
1252
- } while (this.outDBSPL === null);
1253
- outDBSPL1000Values.push(this.outDBSPL1000);
1254
- thdValues.push(this.THD);
1255
- outDBSPLValues.push(this.outDBSPL);
1256
-
1257
- this.outDBSPL = null;
1258
- this.outDBSPL1000 = null;
1259
- this.THD = null;
1260
- }
1261
-
1262
- // get the volume calibration parameters from the server
1263
- const parameters = await this.pyServerAPI
1264
- .getVolumeCalibrationParameters({
1265
- inDBValues: inDBValues,
1266
- outDBSPLValues: outDBSPL1000Values,
1267
- lCalib: lCalib,
1268
- componentGainDBSPL,
1269
- })
1270
- .then(res => {
1271
- this.incrementStatusBar();
1272
- return res;
1273
- });
1274
- const result = {
1275
- parameters: parameters,
1276
- inDBValues: inDBValues,
1277
- outDBSPLValues: outDBSPLValues,
1278
- outDBSPL1000Values: outDBSPL1000Values,
1279
- thdValues: thdValues,
1280
- };
1281
-
1282
- return result;
1283
- };
1284
-
1285
- // function to write frq and gain to firebase database given speakerID
1286
- writeFrqGain = async (speakerID, frq, gain, OEM) => {
1287
- // freq and gain are too large to take samples 1 in every 100 samples
1288
-
1289
- const sampledFrq = [];
1290
- const sampledGain = [];
1291
- for (let i = 0; i < frq.length; i += 100) {
1292
- sampledFrq.push(frq[i]);
1293
- sampledGain.push(gain[i]);
1294
- }
1295
-
1296
- const data = {Freq: sampledFrq, Gain: sampledGain};
1297
-
1298
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/linear`), data);
1299
- };
1300
-
1301
- // Function to Read frq and gain from firebase database given speakerID
1302
- // returns an array of frq and gain if speakerID exists, returns null otherwise
1303
-
1304
- readFrqGain = async (speakerID, OEM) => {
1305
- const dbRef = ref(database);
1306
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/linear`));
1307
- if (snapshot.exists()) {
1308
- return snapshot.val();
1309
- }
1310
- return null;
1311
- };
1312
-
1313
- readGainat1000Hz = async (speakerID, OEM) => {
1314
- const dbRef = ref(database);
1315
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/Gain1000`));
1316
- if (snapshot.exists()) {
1317
- return snapshot.val();
1318
- }
1319
- return null;
1320
- };
1321
-
1322
- writeGainat1000Hz = async (speakerID, gain, OEM) => {
1323
- const data = {Gain: gain};
1324
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/Gain1000`), gain);
1325
- };
1326
-
1327
- writeIsSmartPhone = async (speakerID, isSmartPhone, OEM) => {
1328
- const data = {isSmartPhone: isSmartPhone};
1329
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/isSmartPhone`), isSmartPhone);
1330
- };
1331
-
1332
- doesMicrophoneExist = async (speakerID, OEM) => {
1333
- const dbRef = ref(database);
1334
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}`));
1335
- if (snapshot.exists()) {
1336
- return true;
1337
- }
1338
- return false;
1339
- };
1340
-
1341
- addMicrophoneInfo = async (speakerID, OEM, micInfo) => {
1342
- // add to database if /info does not exist
1343
- const dbRef = ref(database);
1344
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/info`));
1345
- if (!snapshot.exists()) {
1346
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/info`), micInfo);
1347
- }
1348
- };
1349
-
1350
- convertToDB = gain => {
1351
- return Math.log10(gain) * 20;
1352
- };
1353
-
1354
- // Function to perform linear interpolation between two points
1355
- interpolate(x, x0, y0, x1, y1) {
1356
- return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);
1357
- }
1358
-
1359
- findGainatFrequency = (frequencies, gains, targetFrequency) => {
1360
- // Find the index of the first frequency in the array greater than the target frequency
1361
- let index = 0;
1362
- while (index < frequencies.length && frequencies[index] < targetFrequency) {
1363
- index++;
1364
- }
1365
-
1366
- // Handle cases when the target frequency is outside the range of the given data
1367
- if (index === 0) {
1368
- return gains[0];
1369
- } else if (index === frequencies.length) {
1370
- return gains[gains.length - 1];
1371
- } else {
1372
- // Interpolate the gain based on the surrounding frequencies
1373
- const x0 = frequencies[index - 1];
1374
- const y0 = gains[index - 1];
1375
- const x1 = frequencies[index];
1376
- const y1 = gains[index];
1377
- return this.interpolate(targetFrequency, x0, y0, x1, y1);
1378
- }
1379
- };
1380
-
1381
- // Example of how to use the writeFrqGain and readFrqGain functions
1382
- // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);
1383
- // Speaker1 is the speakerID you want to write to in the database
1384
- // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));
1385
- // MiniDSPUMIK_1 is the speakerID with some Data in the database
1386
- //adding gainDBSPL
1387
- startCalibration = async (
1388
- stream,
1389
- gainValues,
1390
- lCalib = 104.92978421490648,
1391
- componentIR = null,
1392
- microphoneName = 'MiniDSP-UMIK1-711-4754-vertical',
1393
- _calibrateSoundCheck = 'goal', //GOAL PASSed in by default
1394
- isSmartPhone = false,
1395
- _calibrateSoundBurstRepeats = 4,
1396
- _calibrateSoundBurstSec = 1,
1397
- _calibrateSoundBurstsWarmup = 1,
1398
- _calibrateSoundHz = 48000,
1399
- _calibrateSoundIIRSec = 0.2,
1400
- calibrateSound1000HzSec = 5,
1401
- _calibrateSoundBackgroundSecs = 0,
1402
- micManufacturer = '',
1403
- micSerialNumber = '',
1404
- micModelNumber = '',
1405
- micModelName = ''
1406
- ) => {
1407
- this.CALIBRATION_TONE_DURATION = calibrateSound1000HzSec;
1408
- this.iirLength = Math.floor(_calibrateSoundIIRSec * this.sourceSamplingRate);
1409
- console.log('device info:', this.deviceInfo);
1410
- this.numMLSPerCapture = _calibrateSoundBurstRepeats;
1411
- this.desired_time_per_mls = _calibrateSoundBurstSec;
1412
- this.num_mls_to_skip = _calibrateSoundBurstsWarmup;
1413
- this.desired_sampling_rate = _calibrateSoundHz;
1414
- this._calibrateSoundBackgroundSecs = _calibrateSoundBackgroundSecs;
1415
-
1416
- //feed calibration goal here
1417
- this._calibrateSoundCheck = _calibrateSoundCheck;
1418
- //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
1419
- //check the db based on the microphone currently connected
1420
-
1421
- //new lCalib found at top of calibration files *1000hz, make sure to correct
1422
- //based on zeroing of 1000hz, search for "*1000Hz"
1423
- const ID = isSmartPhone ? micModelNumber : micSerialNumber;
1424
- const OEM = isSmartPhone ? this.deviceInfo.OEM : micManufacturer;
1425
- const micInfo = {
1426
- micModelName: isSmartPhone ? micModelName : microphoneName,
1427
- OEM: OEM,
1428
- ID: ID,
1429
- HardwareName: isSmartPhone ? this.deviceInfo.hardwarename : microphoneName,
1430
- hardwareFamily: isSmartPhone ? this.deviceInfo.hardwarefamily : microphoneName,
1431
- HardwareModel: isSmartPhone ? this.deviceInfo.hardwaremodel : microphoneName,
1432
- PlatformName: isSmartPhone ? this.deviceInfo.platformname : 'N/A',
1433
- PlatformVersion: isSmartPhone ? this.deviceInfo.platformversion : 'N/A',
1434
- DeviceType: isSmartPhone ? this.deviceInfo.devicetype : 'N/A',
1435
- };
1436
- this.addMicrophoneInfo(ID, OEM, micInfo);
1437
- if (componentIR == null) {
1438
- //mode 'ir'
1439
- //global variable this.componentIR must be set
1440
- this.componentIR = await this.readFrqGain(ID, OEM).then(data => {
1441
- return data;
1442
- });
1443
-
1444
- lCalib = await this.readGainat1000Hz(ID, OEM);
1445
- micInfo['gainDBSPL'] = lCalib;
1446
- // this.componentGainDBSPL = this.convertToDB(lCalib);
1447
- this.componentGainDBSPL = lCalib;
1448
- //TODO: if this call to database is unknown, cannot perform experiment => return false
1449
- if (this.componentIR == null) {
1450
- this.status =
1451
- `Microphone (${OEM},${ID}) is not found in the database. Please add it to the database.`.toString();
1452
- this.emit('update', {message: this.status});
1453
- return false;
1454
- }
1455
- } else {
1456
- this.componentIR = componentIR;
1457
- lCalib = this.findGainatFrequency(this.componentIR.Freq, this.componentIR.Gain, 1000);
1458
- // this.componentGainDBSPL = this.convertToDB(lCalib);
1459
- this.componentGainDBSPL = lCalib;
1460
- await this.writeIsSmartPhone(ID, isSmartPhone, OEM);
1461
- }
1462
-
1463
- this.oldComponentIR = this.componentIR;
1464
-
1465
- let volumeResults = await this.startCalibrationVolume(
1466
- stream,
1467
- gainValues,
1468
- lCalib,
1469
- this.componentGainDBSPL
1470
- );
1471
-
1472
- let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);
1473
- impulseResponseResults['background_noise'] = this.background_noise;
1474
- if (componentIR != null) {
1475
- //insert Freq and Gain from this.componentIR into db
1476
- await this.writeFrqGain(
1477
- ID,
1478
- impulseResponseResults.componentIR.Freq,
1479
- impulseResponseResults.componentIR.Gain,
1480
- OEM
1481
- );
1482
- micInfo['gainDBSPL'] = impulseResponseResults.parameters.gainDBSPL;
1483
- await this.writeGainat1000Hz(ID, micInfo['gainDBSPL'], OEM);
1484
- }
1485
-
1486
- const total_results = {...volumeResults, ...impulseResponseResults};
1487
-
1488
- total_results['micInfo'] = micInfo;
1489
- console.log('total results');
1490
- console.log(total_results);
1491
- return total_results;
1492
- };
1493
- }
1494
-
1495
- export default Combination;
1
+ import AudioCalibrator from '../audioCalibrator';
2
+
3
+ import {sleep, csvToArray, saveToCSV} from '../../utils';
4
+ import database from '../../config/firebase';
5
+ import {ref, set, get, child} from 'firebase/database';
6
+
7
+ /**
8
+ *
9
+ */
10
+ class Combination extends AudioCalibrator {
11
+ /**
12
+ * Default constructor. Creates an instance with any number of paramters passed or the default parameters defined here.
13
+ *
14
+ * @param {Object<boolean, number, number, number>} calibratorParams - paramter object
15
+ * @param {boolean} [calibratorParams.download = false] - boolean flag to download captures
16
+ * @param {number} [calibratorParams.mlsOrder = 18] - order of the MLS to be generated
17
+ * @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform
18
+ * @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture
19
+ */
20
+ constructor({
21
+ download = false,
22
+ mlsOrder = 18,
23
+ numCaptures = 3,
24
+ numMLSPerCapture = 4,
25
+ lowHz = 20,
26
+ highHz = 10000,
27
+ }) {
28
+ super(numCaptures, numMLSPerCapture);
29
+ this.#mlsOrder = parseInt(mlsOrder, 10);
30
+ this.#P = 2 ** mlsOrder - 1;
31
+ this.#download = download;
32
+ this.#mls = [];
33
+ this.#lowHz = lowHz;
34
+ this.#highHz = highHz;
35
+ }
36
+
37
+ /** @private */
38
+ stepNum = 0;
39
+
40
+ /** @private */
41
+ totalSteps = 25;
42
+
43
+ /** @private */
44
+ #download;
45
+
46
+ /** @private */
47
+ #mlsGenInterface;
48
+
49
+ /** @private */
50
+ #mlsBufferView;
51
+
52
+ /** @private */
53
+ componentInvertedImpulseResponse = null;
54
+
55
+ /** @private */
56
+ systemInvertedImpulseResponse = null;
57
+
58
+ //averaged and subtracted ir returned from calibration used to calculated iir
59
+ /** @private */
60
+ ir = null;
61
+
62
+ /** @private */
63
+ impulseResponses = [];
64
+
65
+ /** @private */
66
+ #mlsOrder;
67
+
68
+ /** @private */
69
+ #lowHz;
70
+
71
+ /** @private */
72
+ #highHz;
73
+
74
+ /** @private */
75
+ #mls;
76
+
77
+ /** @private */
78
+ #P;
79
+
80
+ /** @private */
81
+ #audioContext;
82
+
83
+ /** @private */
84
+ TAPER_SECS = 5;
85
+
86
+ /** @private */
87
+ offsetGainNode;
88
+
89
+ /** @private */
90
+ componentConvolution;
91
+
92
+ /** @private */
93
+ systemConvolution;
94
+
95
+ ////////////////////////volume
96
+ /** @private */
97
+ #CALIBRATION_TONE_FREQUENCY = 1000; // Hz
98
+
99
+ /** @private */
100
+ #CALIBRATION_TONE_TYPE = 'sine';
101
+
102
+ CALIBRATION_TONE_DURATION = 5; // seconds
103
+ calibrateSound1000HzPreSec = 3.5;
104
+ calibrateSound1000HzSec = 1.0;
105
+ calibrateSound1000HzPostSec = 0.5;
106
+
107
+
108
+ /** @private */
109
+ outDBSPL = null;
110
+ THD = null;
111
+ outDBSPL1000 = null;
112
+
113
+ /** @private */
114
+ TAPER_SECS = 0.01; // seconds
115
+
116
+ /** @private */
117
+ status_denominator = 8;
118
+
119
+ /** @private */
120
+ status_numerator = 0;
121
+
122
+ /** @private */
123
+ percent_complete = 0;
124
+
125
+ /** @private */
126
+ status = ``;
127
+
128
+ /**@private */
129
+ status_literal = `<div style="display: flex; justify-content: center;"><div style="width: 200px; height: 20px; border: 2px solid #000; border-radius: 10px;"><div style="width: ${this.percent_complete}%; height: 100%; background-color: #00aaff; border-radius: 8px;"></div></div></div>`;
130
+
131
+ /**@private */
132
+ componentIR = null;
133
+
134
+ /**@private */
135
+ oldComponentIR = null;
136
+
137
+ /**@private */
138
+ systemIR = null;
139
+
140
+ /**@private */
141
+ _calibrateSoundCheck = '';
142
+
143
+ deviceType = null;
144
+
145
+ deviceName = null;
146
+
147
+ deviceInfo = null;
148
+
149
+ desired_time_per_mls = 0;
150
+
151
+ num_mls_to_skip = 0;
152
+
153
+ desired_sampling_rate = 0;
154
+
155
+ #currentConvolution = [];
156
+
157
+ mode = 'unfiltered';
158
+
159
+ sourceNode;
160
+
161
+ autocorrelations = [];
162
+
163
+ iirLength = 0;
164
+
165
+ componentInvertedImpulseResponseNoBandpass = [];
166
+
167
+ systemInvertedImpulseResponseNoBandpass = [];
168
+
169
+ _calibrateSoundBackgroundSecs;
170
+
171
+ background_noise = {};
172
+
173
+ numSuccessfulBackgroundCaptured;
174
+
175
+ /**generate string template that gets reevaluated as variable increases */
176
+ generateTemplate = () => {
177
+ if (this.percent_complete > 100) {
178
+ this.percent_complete = 100;
179
+ }
180
+ const template = `<div style="display: flex; justify-content: center;"><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>`;
181
+ return template;
182
+ };
183
+
184
+ /** increment numerator and percent for status bar */
185
+ incrementStatusBar = () => {
186
+ this.status_numerator += 1;
187
+ this.percent_complete = (this.status_numerator / this.status_denominator) * 100;
188
+ };
189
+
190
+ setDeviceType = deviceType => {
191
+ this.deviceType = deviceType;
192
+ };
193
+
194
+ setDeviceName = deviceName => {
195
+ this.deviceName = deviceName;
196
+ };
197
+
198
+ setDeviceInfo = deviceInfo => {
199
+ this.deviceInfo = deviceInfo;
200
+ };
201
+
202
+ /** .
203
+ * .
204
+ * .
205
+ * Sends all the computed impulse responses to the backend server for processing
206
+ *
207
+ * @returns sets the resulting inverted impulse response to the class property
208
+ * @example
209
+ */
210
+ sendSystemImpulseResponsesToServerForProcessing = async () => {
211
+ const computedIRs = await Promise.all(this.impulseResponses);
212
+ const filteredComputedIRs = computedIRs.filter(element => {
213
+ return element != undefined;
214
+ }); //log any errors that are found in this step
215
+ const mls = this.#mls;
216
+ const lowHz = this.#lowHz; //gain of 1 below cutoff, need gain of 0
217
+ const highHz = this.#highHz; //check error for anything other than 10 kHz
218
+ const iirLength = this.iirLength;
219
+ const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
220
+ this.stepNum += 1;
221
+ console.log('send impulse responses to server: ' + this.stepNum);
222
+ this.status =
223
+ `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
224
+ this.emit('update', {message: this.status});
225
+ return this.pyServerAPI
226
+ .getSystemInverseImpulseResponseWithRetry({
227
+ payload: filteredComputedIRs.slice(0, this.numCaptures),
228
+ mls,
229
+ lowHz,
230
+ highHz,
231
+ iirLength,
232
+ num_periods,
233
+ sampleRate: this.sourceSamplingRate || 96000,
234
+ })
235
+ .then(res => {
236
+ console.log(res);
237
+ this.stepNum += 1;
238
+ console.log('got impulse response ' + this.stepNum);
239
+ this.incrementStatusBar();
240
+ this.status =
241
+ `All Hz Calibration: done computing the IIR...`.toString() +
242
+ this.generateTemplate().toString();
243
+ this.emit('update', {message: this.status});
244
+ this.systemInvertedImpulseResponse = res['iir'];
245
+ this.systemIR = res['ir'];
246
+ this.systemConvolution = res['convolution'];
247
+ this.systemInvertedImpulseResponseNoBandpass = res['iirNoBandpass'];
248
+ })
249
+ .catch(err => {
250
+ console.error(err);
251
+ });
252
+ };
253
+
254
+ /** .
255
+ * .
256
+ * .
257
+ * Sends all the computed impulse responses to the backend server for processing
258
+ *
259
+ * @returns sets the resulting inverted impulse response to the class property
260
+ * @example
261
+ */
262
+ sendComponentImpulseResponsesToServerForProcessing = async () => {
263
+ const computedIRs = await Promise.all(this.impulseResponses);
264
+ const filteredComputedIRs = computedIRs.filter(element => {
265
+ return element != undefined;
266
+ });
267
+ const componentIRGains = this.componentIR['Gain'];
268
+ const componentIRFreqs = this.componentIR['Freq'];
269
+ const mls = this.#mls;
270
+ const lowHz = this.#lowHz;
271
+ const iirLength = this.iirLength;
272
+ const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
273
+ const highHz = this.#highHz;
274
+ this.stepNum += 1;
275
+ console.log('send impulse responses to server: ' + this.stepNum);
276
+ this.status =
277
+ `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
278
+ this.emit('update', {message: this.status});
279
+ return this.pyServerAPI
280
+ .getComponentInverseImpulseResponseWithRetry({
281
+ payload: filteredComputedIRs.slice(0, this.numCaptures),
282
+ mls,
283
+ lowHz,
284
+ highHz,
285
+ iirLength,
286
+ componentIRGains,
287
+ componentIRFreqs,
288
+ num_periods,
289
+ sampleRate: this.sourceSamplingRate || 96000,
290
+ })
291
+ .then(res => {
292
+ console.log(res);
293
+ this.stepNum += 1;
294
+ console.log('got impulse response ' + this.stepNum);
295
+ this.incrementStatusBar();
296
+ this.status =
297
+ `All Hz Calibration: done computing the IIR...`.toString() +
298
+ this.generateTemplate().toString();
299
+ this.emit('update', {message: this.status});
300
+ this.componentInvertedImpulseResponse = res['iir'];
301
+ this.componentIR['Gain'] = res['ir'];
302
+ this.componentIR['Freq'] = res['frequencies'];
303
+ this.componentConvolution = res['convolution'];
304
+ this.componentInvertedImpulseResponseNoBandpass = res['iirNoBandpass'];
305
+ })
306
+ .catch(err => {
307
+ // this.emit('InvertedImpulseResponse', {res: false});
308
+ console.error(err);
309
+ });
310
+ };
311
+
312
+ sendBackgroundRecording = () => {
313
+ const allSignals = this.getAllBackgroundRecordings();
314
+ const numSignals = allSignals.length;
315
+ const background_rec_whole = allSignals[numSignals-1];
316
+ const fraction = .5/(this._calibrateSoundBackgroundSecs + .5);
317
+ // Calculate the starting index for slicing the array
318
+ const startIndex = Math.round(fraction * background_rec_whole.length);
319
+ // Slice the array from the calculated start index to the end of the array
320
+ const background_rec = background_rec_whole.slice(startIndex);
321
+ console.log('Sending background recording to server for processing');
322
+ this.pyServerAPI
323
+ .getBackgroundNoisePSDWithRetry({
324
+ background_rec,
325
+ sampleRate: this.sourceSamplingRate || 96000,
326
+ })
327
+ .then(res => {
328
+ if (this.numSuccessfulBackgroundCaptured < 1) {
329
+ this.numSuccessfulBackgroundCaptured += 1;
330
+ //storing all background data in background_psd object
331
+ this.background_noise['x_background'] = res['x_background'];
332
+ this.background_noise['y_background'] = res['y_background'];
333
+ this.background_noise['recording'] = background_rec;
334
+ }
335
+ })
336
+ .catch(err => {
337
+ console.error(err);
338
+ });
339
+ }
340
+
341
+ /** .
342
+ * .
343
+ * .
344
+ * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing
345
+ *
346
+ * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed
347
+ * @example
348
+ */
349
+ sendRecordingToServerForProcessing = signalCsv => {
350
+ const allSignals = this.getAllUnfilteredRecordedSignals();
351
+ console.log(
352
+ 'Obtaining last all hz unfiltered recording from #allHzUnfilteredRecordings to send to server for processing'
353
+ );
354
+ const numSignals = allSignals.length;
355
+ const mls = this.#mls;
356
+ const payload =
357
+ signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
358
+ console.log('sending rec');
359
+ this.stepNum += 1;
360
+ console.log('send rec ' + this.stepNum);
361
+ this.status =
362
+ `All Hz Calibration Step: computing the IR of the last recording...`.toString() +
363
+ this.generateTemplate().toString();
364
+ this.emit('update', {message: this.status});
365
+ this.impulseResponses.push(
366
+ this.pyServerAPI
367
+ .getImpulseResponse({
368
+ sampleRate: this.sourceSamplingRate || 96000,
369
+ payload,
370
+ mls,
371
+ P: this.#P, //get rid of this
372
+ numPeriods: this.numMLSPerCapture,
373
+ })
374
+ .then(res => {
375
+ if (this.numSuccessfulCaptured < this.numCaptures) {
376
+ this.numSuccessfulCaptured += 1;
377
+ console.log('num succ capt: ' + this.numSuccessfulCaptured);
378
+ this.stepNum += 1;
379
+ console.log('got impulse response ' + this.stepNum);
380
+ this.incrementStatusBar();
381
+ this.status =
382
+ `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +
383
+ this.generateTemplate().toString();
384
+ this.emit('update', {
385
+ message: this.status,
386
+ });
387
+ this.autocorrelations.push(res['autocorrelation']);
388
+ return res['ir'];
389
+ }
390
+ })
391
+ .catch(err => {
392
+ console.error(err);
393
+ })
394
+ );
395
+ };
396
+
397
+ /**
398
+ * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number
399
+ * of MLS periods defined in the constructor.
400
+ *
401
+ * @example
402
+ */
403
+ #awaitDesiredMLSLength = async () => {
404
+ // seconds per MLS = P / SR
405
+ // await N * P / SR
406
+ this.stepNum += 1;
407
+ console.log('await desired length ' + this.stepNum);
408
+ this.status =
409
+ `All Hz Calibration: sampling the calibration signal...`.toString() +
410
+ `\niteration ${this.stepNum}` +
411
+ this.generateTemplate();
412
+ this.emit('update', {
413
+ message: this.status,
414
+ });
415
+ let time_to_wait = 0;
416
+ if (this.mode === 'unfiltered') {
417
+ time_to_wait = (this.#mls.length / this.sourceSamplingRate) * this.numMLSPerCapture;
418
+ time_to_wait = time_to_wait * 1.1;
419
+ } else if (this.mode === 'filtered') {
420
+ time_to_wait =
421
+ (this.#currentConvolution.length / this.sourceSamplingRate) *
422
+ (this.numMLSPerCapture / (this.num_mls_to_skip + this.numMLSPerCapture));
423
+ } else {
424
+ throw new Error('Mode broke in awaitDesiredMLSLength');
425
+ }
426
+
427
+ await sleep(time_to_wait);
428
+ };
429
+
430
+ /**
431
+ * Passed to the background noise recording function, awaits the desired amount of seconds to capture the desired number
432
+ * of seconds of background noise
433
+ *
434
+ * @example
435
+ */
436
+ #awaitBackgroundNoiseRecording = async () => {
437
+ console.log('Waiting ' + this._calibrateSoundBackgroundSecs + " second(s) to record background noise");
438
+ let time_to_wait = this._calibrateSoundBackgroundSecs + .5;
439
+ await sleep(time_to_wait);
440
+ };
441
+
442
+
443
+ /** .
444
+ * .
445
+ * .
446
+ * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state
447
+ *
448
+ * @example
449
+ */
450
+ #awaitSignalOnset = async () => {
451
+ this.stepNum += 1;
452
+ console.log('await signal onset ' + this.stepNum);
453
+ this.status =
454
+ `All Hz Calibration: waiting for the signal to stabilize...`.toString() +
455
+ this.generateTemplate();
456
+ this.emit('update', {
457
+ message: this.status,
458
+ });
459
+ let number_of_bursts_to_skip = this.num_mls_to_skip;
460
+ let time_to_sleep = 0;
461
+ if (this.mode === 'unfiltered') {
462
+ time_to_sleep = (this.#mls.length / this.sourceSamplingRate) * number_of_bursts_to_skip;
463
+ } else if (this.mode === 'filtered') {
464
+ console.log(this.#currentConvolution.length);
465
+ time_to_sleep =
466
+ (this.#currentConvolution.length / this.sourceSamplingRate) *
467
+ (number_of_bursts_to_skip / (number_of_bursts_to_skip + this.numMLSPerCapture));
468
+ } else {
469
+ throw new Error('Mode broke in awaitSignalOnset');
470
+ }
471
+ await sleep(time_to_sleep);
472
+ };
473
+
474
+ /**
475
+ * Called immediately after a recording is captured. Used to process the resulting signal
476
+ * whether by sending the result to a server or by computing a result locally.
477
+ *
478
+ * @example
479
+ */
480
+ #afterMLSRecord = () => {
481
+ console.log('after record');
482
+ this.sendRecordingToServerForProcessing();
483
+ };
484
+
485
+ #afterMLSwIIRRecord = () => {
486
+ if (this.numSuccessfulCaptured < 1) {
487
+ this.numSuccessfulCaptured += 1;
488
+ this.stepNum += 1;
489
+ this.incrementStatusBar();
490
+ console.log('after mls w iir record for some reason add numSucc capt ' + this.stepNum);
491
+ this.status =
492
+ `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +
493
+ this.generateTemplate().toString();
494
+ this.emit('update', {
495
+ message: this.status,
496
+ });
497
+ }
498
+ };
499
+
500
+ /** .
501
+ * .
502
+ * .
503
+ * Created an S Curver Buffer to taper the signal onset
504
+ *
505
+ * @param {*} length
506
+ * @param {*} phase
507
+ * @returns
508
+ * @example
509
+ */
510
+ static createSCurveBuffer = (length, phase) => {
511
+ const curve = new Float32Array(length);
512
+ let i;
513
+ for (i = 0; i < length; i += 1) {
514
+ // scale the curve to be between 0-1
515
+ curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;
516
+ }
517
+ return curve;
518
+ };
519
+
520
+ static createInverseSCurveBuffer = (length, phase) => {
521
+ const curve = new Float32Array(length);
522
+ let i;
523
+ let j = length - 1;
524
+ for (i = 0; i < length; i += 1) {
525
+ // scale the curve to be between 0-1
526
+ curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;
527
+ j -= 1;
528
+ }
529
+ return curve;
530
+ };
531
+
532
+ /**
533
+ * Construct a Calibration Node with the calibration parameters.
534
+ *
535
+ * @param dataBuffer
536
+ * @private
537
+ * @example
538
+ */
539
+ #createCalibrationNodeFromBuffer = dataBuffer => {
540
+ console.log('databuffer');
541
+ console.log(dataBuffer);
542
+ if (!this.sourceAudioContext) {
543
+ this.makeNewSourceAudioContext();
544
+ }
545
+
546
+ const buffer = this.sourceAudioContext.createBuffer(
547
+ 1, // number of channels
548
+ dataBuffer.length,
549
+ this.sourceAudioContext.sampleRate // sample rate
550
+ );
551
+
552
+ const data = buffer.getChannelData(0); // get data
553
+ // fill the buffer with our data
554
+ try {
555
+ for (let i = 0; i < dataBuffer.length; i += 1) {
556
+ data[i] = dataBuffer[i] * 0.33;
557
+ }
558
+ } catch (error) {
559
+ console.error(error);
560
+ }
561
+
562
+ this.sourceNode = this.sourceAudioContext.createBufferSource();
563
+
564
+ this.sourceNode.buffer = buffer;
565
+ if (this.mode === 'filtered') {
566
+ this.sourceNode.loop = false;
567
+ } else {
568
+ this.sourceNode.loop = true;
569
+ }
570
+
571
+ this.sourceNode.connect(this.sourceAudioContext.destination);
572
+
573
+ this.addCalibrationNode(this.sourceNode);
574
+ };
575
+
576
+ /**
577
+ * Given a data buffer, creates the required calibration node
578
+ *
579
+ * @param {*} dataBufferArray
580
+ * @example
581
+ */
582
+ #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {
583
+ if (dataBufferArray.length === 1) {
584
+ this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);
585
+ } else {
586
+ throw new Error('The length of the data buffer array must be 1');
587
+ }
588
+ };
589
+
590
+ /**
591
+ * Creates an audio context and plays it for a few seconds.
592
+ *
593
+ * @private
594
+ * @returns - Resolves when the audio is done playing.
595
+ * @example
596
+ */
597
+ #playCalibrationAudio = () => {
598
+ this.calibrationNodes[0].start(0);
599
+ this.status = ``;
600
+ if (this.mode === 'unfiltered') {
601
+ this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);
602
+ console.log('play calibration audio ' + this.stepNum);
603
+ this.status =
604
+ `All Hz Calibration: playing the calibration tone...`.toString() +
605
+ this.generateTemplate().toString();
606
+ } else if (this.mode === 'filtered') {
607
+ console.log('play convolved audio ' + this.stepNum);
608
+ this.status =
609
+ `All Hz Calibration: playing the convolved calibration tone...`.toString() +
610
+ this.generateTemplate().toString();
611
+ } else {
612
+ throw new Error('Mode is incorrect');
613
+ }
614
+ this.emit('update', {message: this.status});
615
+ this.stepNum += 1;
616
+ console.log('sink sampling rate');
617
+ console.log(this.sinkSamplingRate);
618
+ console.log('source sampling rate');
619
+ console.log(this.sourceSamplingRate);
620
+ };
621
+
622
+ /** .
623
+ * .
624
+ * .
625
+ * Stops the audio with tapered offset
626
+ *
627
+ * @example
628
+ */
629
+ #stopCalibrationAudio = () => {
630
+ this.calibrationNodes[0].stop(0);
631
+ this.calibrationNodes = [];
632
+ this.sourceNode.disconnect();
633
+ this.stepNum += 1;
634
+ console.log('stop calibration audio ' + this.stepNum);
635
+ this.status =
636
+ `All Hz Calibration: stopping the calibration tone...`.toString() +
637
+ this.generateTemplate().toString();
638
+ this.emit('update', {message: this.status});
639
+ };
640
+
641
+ playMLSwithIIR = async (stream, iir) => {
642
+ let checkRec = false;
643
+ this.mode = 'filtered';
644
+ console.log('play mls with iir');
645
+ this.invertedImpulseResponse = iir;
646
+
647
+ await this.calibrationSteps(
648
+ stream,
649
+ this.#playCalibrationAudio, // play audio func (required)
650
+ this.#createCalibrationNodeFromBuffer(this.#currentConvolution), // before play func
651
+ this.#awaitSignalOnset, // before record
652
+ () => this.numSuccessfulCaptured < 1,
653
+ this.#awaitDesiredMLSLength, // during record
654
+ this.#afterMLSwIIRRecord, // after record
655
+ this.mode,
656
+ checkRec
657
+ );
658
+ };
659
+
660
+ /**
661
+ * Public method to start the calibration process. Objects intialized from webassembly allocate new memory
662
+ * and must be manually freed. This function is responsible for intializing the MlsGenInterface,
663
+ * and wrapping the calibration steps with a garbage collection safe gaurd.
664
+ *
665
+ * @public
666
+ * @param stream - The stream of audio from the Listener.
667
+ * @example
668
+ */
669
+ startCalibrationImpulseResponse = async stream => {
670
+ let desired_time = this.desired_time_per_mls;
671
+ let checkRec = 'allhz';
672
+
673
+ console.log('MLS sequence should be of length: ' + this.sourceSamplingRate * desired_time);
674
+
675
+ length = this.sourceSamplingRate * desired_time;
676
+ //get mls here
677
+ await this.pyServerAPI
678
+ .getMLSWithRetry(length)
679
+ .then(res => {
680
+ console.log(res);
681
+ this.#mlsBufferView = res['mls'];
682
+ })
683
+ .catch(err => {
684
+ // this.emit('InvertedImpulseResponse', {res: false});
685
+ console.error(err);
686
+ });
687
+ this.numSuccessfulBackgroundCaptured = 0;
688
+ if (this._calibrateSoundBackgroundSecs > 0){
689
+ this.mode='background';
690
+ this.status =
691
+ `All Hz Calibration: sampling the background noise...`.toString() + this.generateTemplate().toString();
692
+ this.emit('update', {message: this.status});
693
+ await this.recordBackground(
694
+ stream, //stream
695
+ () => this.numSuccessfulBackgroundCaptured < 1, //loop condition
696
+ this.#awaitBackgroundNoiseRecording, //sleep to record
697
+ this.sendBackgroundRecording, //send to get PSD
698
+ this.mode,
699
+ checkRec
700
+ )
701
+ this.incrementStatusBar();
702
+ }
703
+ this.mode='unfiltered';
704
+ this.numSuccessfulCaptured = 0;
705
+
706
+ await this.calibrationSteps(
707
+ stream,
708
+ this.#playCalibrationAudio, // play audio func (required)
709
+ this.#createCalibrationNodeFromBuffer(this.#mlsBufferView), // before play func
710
+ this.#awaitSignalOnset, // before record
711
+ () => this.numSuccessfulCaptured < this.numCaptures, // loop while true
712
+ this.#awaitDesiredMLSLength, // during record
713
+ this.#afterMLSRecord, // after record
714
+ this.mode,
715
+ checkRec
716
+ ),
717
+ this.#stopCalibrationAudio();
718
+ checkRec = false;
719
+
720
+ // at this stage we've captured all the required signals,
721
+ // and have received IRs for each one
722
+ // so let's send all the IRs to the server to be converted to a single IIR
723
+ await this.sendSystemImpulseResponsesToServerForProcessing();
724
+ await this.sendComponentImpulseResponsesToServerForProcessing();
725
+
726
+ this.numSuccessfulCaptured = 0;
727
+
728
+ let iir_ir_and_plots;
729
+ if (this._calibrateSoundCheck != 'none') {
730
+ if (this._calibrateSoundCheck != 'system') {
731
+ this.#currentConvolution = this.componentConvolution;
732
+ } else {
733
+ this.#currentConvolution = this.systemConvolution;
734
+ }
735
+ await this.playMLSwithIIR(stream, this.invertedImpulseResponse);
736
+ this.#stopCalibrationAudio();
737
+ this.sourceAudioContext.close();
738
+ let conv_recs = this.getAllFilteredRecordedSignals();
739
+ let recs = this.getAllUnfilteredRecordedSignals();
740
+ console.log(
741
+ 'Obtaining unfiltered recording from #allHzUnfilteredRecordings to calculate PSD'
742
+ );
743
+ console.log('Obtaining filtered recording from #allHzFilteredRecordings to calculate PSD');
744
+ let unconv_rec = recs[0];
745
+ let conv_rec = conv_recs[0];
746
+ if (this._calibrateSoundCheck != 'system') {
747
+ let knownGain = this.oldComponentIR.Gain;
748
+ let knownFreq = this.oldComponentIR.Freq;
749
+ let sampleRate = this.sourceSamplingRate || 96000;
750
+ let unconv_results = await this.pyServerAPI
751
+ .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)
752
+ .then(res => {
753
+ this.incrementStatusBar();
754
+ this.status =
755
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
756
+ this.generateTemplate().toString();
757
+ this.emit('update', {message: this.status});
758
+ return res;
759
+ })
760
+ .catch(err => {
761
+ console.error(err);
762
+ });
763
+
764
+ let conv_results = await this.pyServerAPI
765
+ .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)
766
+ .then(res => {
767
+ this.incrementStatusBar();
768
+ this.status =
769
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
770
+ this.generateTemplate().toString();
771
+ this.emit('update', {message: this.status});
772
+ return res;
773
+ })
774
+ .catch(err => {
775
+ console.error(err);
776
+ });
777
+ unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
778
+ conv_rec = this.componentInvertedImpulseResponse;
779
+ let component_iir_psd = await this.pyServerAPI
780
+ .getPSDWithRetry({
781
+ unconv_rec,
782
+ conv_rec,
783
+ sampleRate: this.sourceSamplingRate || 96000,
784
+ })
785
+ .then(res => {
786
+ this.incrementStatusBar();
787
+ this.status =
788
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
789
+ this.generateTemplate().toString();
790
+ this.emit('update', {message: this.status});
791
+ return res;
792
+ })
793
+ .catch(err => {
794
+ console.error(err);
795
+ });
796
+ unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
797
+ conv_rec = this.systemInvertedImpulseResponse;
798
+ let system_iir_psd = await this.pyServerAPI
799
+ .getPSDWithRetry({
800
+ unconv_rec,
801
+ conv_rec,
802
+ sampleRate: this.sourceSamplingRate || 96000,
803
+ })
804
+ .then(res => {
805
+ this.incrementStatusBar();
806
+ this.status =
807
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
808
+ this.generateTemplate().toString();
809
+ this.emit('update', {message: this.status});
810
+ return res;
811
+ })
812
+ .catch(err => {
813
+ console.error(err);
814
+ });
815
+ iir_ir_and_plots = {
816
+ systemIIR: this.systemInvertedImpulseResponse,
817
+ componentIIR: this.componentInvertedImpulseResponse,
818
+ x_unconv: unconv_results['x'],
819
+ y_unconv: unconv_results['y'],
820
+ x_conv: conv_results['x'],
821
+ y_conv: conv_results['y'],
822
+ componentIR: this.componentIR,
823
+ systemIR: this.systemIR,
824
+ x_system_iir: system_iir_psd['x_conv'],
825
+ y_system_iir: system_iir_psd['y_conv'],
826
+ x_system_iir_no_bandpass: system_iir_psd['x_unconv'],
827
+ y_system_iir_no_bandpass: system_iir_psd['y_unconv'],
828
+ x_component_iir: component_iir_psd['x_conv'],
829
+ y_component_iir: component_iir_psd['y_conv'],
830
+ x_component_iir_no_bandpass: component_iir_psd['x_unconv'],
831
+ y_component_iir_no_bandpass: component_iir_psd['y_unconv'],
832
+ unconv_rec: recs[0],
833
+ conv_rec: conv_recs[0],
834
+ mls: this.#mls,
835
+ componentConvolution: this.componentConvolution,
836
+ systemConvolution: this.systemConvolution,
837
+ autocorrelations:this.autocorrelations,
838
+ impulseResponses: []
839
+ };
840
+ } else {
841
+ let results = await this.pyServerAPI
842
+ .getPSDWithRetry({
843
+ unconv_rec,
844
+ conv_rec,
845
+ sampleRate: this.sourceSamplingRate || 96000,
846
+ })
847
+ .then(res => {
848
+ this.incrementStatusBar();
849
+ this.status =
850
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
851
+ this.generateTemplate().toString();
852
+ this.emit('update', {message: this.status});
853
+ return res;
854
+ })
855
+ .catch(err => {
856
+ console.error(err);
857
+ });
858
+
859
+ //iir w/ and without bandpass psd
860
+ unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
861
+ conv_rec = this.componentInvertedImpulseResponse;
862
+ let component_iir_psd = await this.pyServerAPI
863
+ .getPSDWithRetry({
864
+ unconv_rec,
865
+ conv_rec,
866
+ sampleRate: this.sourceSamplingRate || 96000,
867
+ })
868
+ .then(res => {
869
+ this.incrementStatusBar();
870
+ this.status =
871
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
872
+ this.generateTemplate().toString();
873
+ this.emit('update', {message: this.status});
874
+ return res;
875
+ })
876
+ .catch(err => {
877
+ console.error(err);
878
+ });
879
+ unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
880
+ conv_rec = this.systemInvertedImpulseResponse;
881
+ let system_iir_psd = await this.pyServerAPI
882
+ .getPSDWithRetry({
883
+ unconv_rec,
884
+ conv_rec,
885
+ sampleRate: this.sourceSamplingRate || 96000,
886
+ })
887
+ .then(res => {
888
+ this.incrementStatusBar();
889
+ this.status =
890
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
891
+ this.generateTemplate().toString();
892
+ this.emit('update', {message: this.status});
893
+ return res;
894
+ })
895
+ .catch(err => {
896
+ console.error(err);
897
+ });
898
+
899
+ iir_ir_and_plots = {
900
+ systemIIR: this.systemInvertedImpulseResponse,
901
+ componentIIR: this.componentInvertedImpulseResponse,
902
+ x_unconv: results['x_unconv'],
903
+ y_unconv: results['y_unconv'],
904
+ x_conv: results['x_conv'],
905
+ y_conv: results['y_conv'],
906
+ componentIR: this.componentIR,
907
+ systemIR: this.systemIR,
908
+ x_system_iir: system_iir_psd['x_conv'],
909
+ y_system_iir: system_iir_psd['y_conv'],
910
+ x_system_iir_no_bandpass: system_iir_psd['x_unconv'],
911
+ y_system_iir_no_bandpass: system_iir_psd['y_unconv'],
912
+ x_component_iir: component_iir_psd['x_conv'],
913
+ y_component_iir: component_iir_psd['y_conv'],
914
+ x_component_iir_no_bandpass: component_iir_psd['x_unconv'],
915
+ y_component_iir_no_bandpass: component_iir_psd['y_unconv'],
916
+ unconv_rec: recs[0],
917
+ conv_rec: conv_recs[0],
918
+ mls: this.#mls,
919
+ componentConvolution: this.componentConvolution,
920
+ systemConvolution: this.systemConvolution,
921
+ autocorrelations:this.autocorrelations,
922
+ impulseResponses: []
923
+ };
924
+ }
925
+ await Promise.all(this.impulseResponses).then(res => {
926
+ for (let i = 0; i < res.length; i++) {
927
+ if (res[i] != undefined) {
928
+ iir_ir_and_plots['impulseResponses'].push(res[i])
929
+ }
930
+ }
931
+ });
932
+
933
+ if (this.#download) {
934
+ this.downloadSingleUnfilteredRecording();
935
+ this.downloadSingleFilteredRecording();
936
+ saveToCSV(this.#mls, 'MLS.csv');
937
+ saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
938
+ saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
939
+ saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
940
+ saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
941
+ for (let i = 0; i < this.autocorrelations.length; i++) {
942
+ saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
943
+ }
944
+ const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
945
+ for (let i = 0; i < res.length; i++) {
946
+ if (res[i] != undefined) {
947
+ saveToCSV(res[i], `IR_${i}`);
948
+ }
949
+ }
950
+ });
951
+ }
952
+ } else {
953
+ let unconv_rec = this.componentInvertedImpulseResponseNoBandpass;
954
+ let conv_rec = this.componentInvertedImpulseResponse;
955
+ let component_iir_psd = await this.pyServerAPI
956
+ .getPSDWithRetry({
957
+ unconv_rec,
958
+ conv_rec,
959
+ sampleRate: this.sourceSamplingRate || 96000,
960
+ })
961
+ .then(res => {
962
+ this.incrementStatusBar();
963
+ this.status =
964
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
965
+ this.generateTemplate().toString();
966
+ this.emit('update', {message: this.status});
967
+ return res;
968
+ })
969
+ .catch(err => {
970
+ console.error(err);
971
+ });
972
+ unconv_rec = this.systemInvertedImpulseResponseNoBandpass;
973
+ conv_rec = this.systemInvertedImpulseResponse;
974
+ let system_iir_psd = await this.pyServerAPI
975
+ .getPSDWithRetry({
976
+ unconv_rec,
977
+ conv_rec,
978
+ sampleRate: this.sourceSamplingRate || 96000,
979
+ })
980
+ .then(res => {
981
+ this.incrementStatusBar();
982
+ this.status =
983
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
984
+ this.generateTemplate().toString();
985
+ this.emit('update', {message: this.status});
986
+ return res;
987
+ })
988
+ .catch(err => {
989
+ console.error(err);
990
+ });
991
+ iir_ir_and_plots = {
992
+ systemIIR: this.systemInvertedImpulseResponse,
993
+ componentIIR: this.componentInvertedImpulseResponse,
994
+ x_unconv: [],
995
+ y_unconv: [],
996
+ x_conv: [],
997
+ y_conv: [],
998
+ componentIR: this.componentIR,
999
+ systemIR: this.systemIR,
1000
+ x_system_iir: system_iir_psd['x_conv'],
1001
+ y_system_iir: system_iir_psd['y_conv'],
1002
+ x_system_iir_no_bandpass: system_iir_psd['x_unconv'],
1003
+ y_system_iir_no_bandpass: system_iir_psd['y_unconv'],
1004
+ x_component_iir: component_iir_psd['x_conv'],
1005
+ y_component_iir: component_iir_psd['y_conv'],
1006
+ x_component_iir_no_bandpass: component_iir_psd['x_unconv'],
1007
+ y_component_iir_no_bandpass: component_iir_psd['y_unconv'],
1008
+ unconv_rec: recs[0],
1009
+ conv_rec: conv_recs[0],
1010
+ mls: this.#mls,
1011
+ componentConvolution: this.componentConvolution,
1012
+ systemConvolution: this.systemConvolution,
1013
+ autocorrelations:this.autocorrelations,
1014
+ impulseResponses: []
1015
+ };
1016
+ await Promise.all(this.impulseResponses).then(res => {
1017
+ for (let i = 0; i < res.length; i++) {
1018
+ if (res[i] != undefined) {
1019
+ iir_ir_and_plots['impulseResponses'].push(res[i])
1020
+ }
1021
+ }
1022
+ });
1023
+
1024
+ if (this.#download) {
1025
+ saveToCSV(this.#mls, 'MLS.csv');
1026
+ saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
1027
+ saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
1028
+ saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
1029
+ saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
1030
+ for (let i = 0; i < this.autocorrelations.length; i++) {
1031
+ saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
1032
+ }
1033
+ const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
1034
+ for (let i = 0; i < res.length; i++) {
1035
+ if (res[i] != undefined) {
1036
+ saveToCSV(res[i], `IR_${i}`);
1037
+ }
1038
+ }
1039
+ });
1040
+ }
1041
+ }
1042
+
1043
+ this.percent_complete = 100;
1044
+
1045
+ this.status = `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();
1046
+ this.emit('update', {message: this.status});
1047
+
1048
+ //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR
1049
+ //that was used to calibrate
1050
+
1051
+ return iir_ir_and_plots;
1052
+ };
1053
+
1054
+ //////////////////////volume
1055
+
1056
+ handleIncomingData = data => {
1057
+ console.log('Received data: ', data);
1058
+ if (data.type === 'soundGainDBSPL') {
1059
+ this.soundGainDBSPL = data.value;
1060
+ } else {
1061
+ throw new Error(`Unknown data type: ${data.type}`);
1062
+ }
1063
+ };
1064
+ createSCurveBuffer = (onSetBool = true) => {
1065
+ const curve = new Float32Array(this.TAPER_SECS * this.sourceSamplingRate + 1);
1066
+ const frequency = 1 / (4 * this.TAPER_SECS);
1067
+ let j = 0;
1068
+ for (let i = 0; i < this.TAPER_SECS * this.sourceSamplingRate + 1; i += 1) {
1069
+ const phase = 2 * Math.PI * frequency * j;
1070
+ const onsetTaper = Math.pow(Math.sin(phase), 2);
1071
+ const offsetTaper = Math.pow(Math.cos(phase), 2);
1072
+ curve[i] = onSetBool ? onsetTaper : offsetTaper;
1073
+ j += 1 / this.sourceSamplingRate;
1074
+ }
1075
+ return curve;
1076
+ };
1077
+
1078
+ #getTruncatedSignal = (left = 3.5, right = 4.5) => {
1079
+ const start = Math.floor(left * this.sourceSamplingRate);
1080
+ const end = Math.floor(right * this.sourceSamplingRate);
1081
+ const result = Array.from(this.getLastVolumeRecordedSignal().slice(start, end));
1082
+ console.log(
1083
+ 'Obtaining last 1000 hz recording from #allVolumeRecordings to send for processing'
1084
+ );
1085
+ /**
1086
+ * function to check that capture was properly made
1087
+ * @param {*} list
1088
+ */
1089
+ const checkResult = list => {
1090
+ const setItem = new Set(list);
1091
+ if (setItem.size === 1 && setItem.has(0)) {
1092
+ console.warn(
1093
+ 'The last capture failed, all recorded signal is zero',
1094
+ this.getAllVolumeRecordedSignals()
1095
+ );
1096
+ }
1097
+ if (setItem.size === 0) {
1098
+ console.warn('The last capture failed, no recorded signal');
1099
+ }
1100
+ };
1101
+ checkResult(result);
1102
+ return result;
1103
+ };
1104
+
1105
+ /**
1106
+ *
1107
+ *
1108
+ Construct a calibration Node with the calibration parameters and given gain value
1109
+ * @param {*} gainValue
1110
+ * */
1111
+ #createCalibrationToneWithGainValue = gainValue => {
1112
+ const audioContext = this.makeNewSourceAudioContext();
1113
+ const oscilator = audioContext.createOscillator();
1114
+ const gainNode = audioContext.createGain();
1115
+ const taperGainNode = audioContext.createGain();
1116
+ const offsetGainNode = audioContext.createGain();
1117
+ const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
1118
+
1119
+ oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
1120
+ oscilator.type = this.#CALIBRATION_TONE_TYPE;
1121
+ gainNode.gain.value = gainValue;
1122
+
1123
+ oscilator.connect(gainNode);
1124
+ gainNode.connect(taperGainNode);
1125
+ const onsetCurve = this.createSCurveBuffer();
1126
+ taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
1127
+ taperGainNode.connect(offsetGainNode);
1128
+ const offsetCurve = this.createSCurveBuffer(false);
1129
+ offsetGainNode.gain.setValueCurveAtTime(
1130
+ offsetCurve,
1131
+ totalDuration - this.TAPER_SECS,
1132
+ this.TAPER_SECS
1133
+ );
1134
+ offsetGainNode.connect(audioContext.destination);
1135
+
1136
+ this.addCalibrationNode(oscilator);
1137
+ };
1138
+
1139
+ /**
1140
+ * Construct a Calibration Node with the calibration parameters.
1141
+ *
1142
+ * @private
1143
+ * @example
1144
+ */
1145
+ #createCalibrationNode = () => {
1146
+ const audioContext = this.makeNewSourceAudioContext();
1147
+ const oscilator = audioContext.createOscillator();
1148
+ const gainNode = audioContext.createGain();
1149
+
1150
+ oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
1151
+ oscilator.type = this.#CALIBRATION_TONE_TYPE;
1152
+ gainNode.gain.value = 0.04;
1153
+
1154
+ oscilator.connect(gainNode);
1155
+ gainNode.connect(audioContext.destination);
1156
+
1157
+ this.addCalibrationNode(oscilator);
1158
+ };
1159
+
1160
+ #playCalibrationAudioVolume = async () => {
1161
+ const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
1162
+
1163
+ this.calibrationNodes[0].start(0);
1164
+ this.calibrationNodes[0].stop(totalDuration);
1165
+ console.log(`Playing a buffer of ${this.CALIBRATION_TONE_DURATION} seconds of audio`);
1166
+ console.log(`Waiting a total of ${totalDuration} seconds`);
1167
+ await sleep(totalDuration);
1168
+ };
1169
+
1170
+ #sendToServerForProcessing = (lCalib) => {
1171
+ console.log('Sending data to server');
1172
+ let left = this.calibrateSound1000HzPreSec
1173
+ let right = this.calibrateSound1000HzPreSec + this.calibrateSound1000HzSec
1174
+ this.pyServerAPI
1175
+ .getVolumeCalibration({
1176
+ sampleRate: this.sourceSamplingRate,
1177
+ payload: this.#getTruncatedSignal(left,right),
1178
+ lCalib: lCalib,
1179
+ })
1180
+ .then(res => {
1181
+ if (this.outDBSPL === null) {
1182
+ this.incrementStatusBar();
1183
+ this.outDBSPL = res['outDbSPL'];
1184
+ this.outDBSPL1000 = res['outDbSPL1000'];
1185
+ this.THD = res['thd'];
1186
+ }
1187
+ })
1188
+ .catch(err => {
1189
+ console.warn(err);
1190
+ });
1191
+ };
1192
+
1193
+ startCalibrationVolume = async (stream, gainValues, lCalib, componentGainDBSPL) => {
1194
+ const trialIterations = gainValues.length;
1195
+ this.status_denominator += trialIterations;
1196
+ const thdValues = [];
1197
+ const inDBValues = [];
1198
+ let inDB = 0;
1199
+ const outDBSPLValues = [];
1200
+ const outDBSPL1000Values = [];
1201
+ let checkRec = false;
1202
+
1203
+ // do one calibration that will be discarded
1204
+ const soundLevelToDiscard = -60;
1205
+ const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);
1206
+ this.status =
1207
+ `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() +
1208
+ this.generateTemplate().toString();
1209
+ //this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});
1210
+ this.emit('update', {message: this.status});
1211
+
1212
+ do {
1213
+ // eslint-disable-next-line no-await-in-loop
1214
+ await this.volumeCalibrationSteps(
1215
+ stream,
1216
+ this.#playCalibrationAudioVolume,
1217
+ this.#createCalibrationToneWithGainValue,
1218
+ this.#sendToServerForProcessing,
1219
+ gainToDiscard,
1220
+ lCalib, //todo make this a class parameter
1221
+ checkRec
1222
+ );
1223
+ } while (this.outDBSPL === null);
1224
+ //reset the values
1225
+ //this.incrementStatusBar();
1226
+
1227
+ this.outDBSPL = null;
1228
+ this.outDBSPL = null;
1229
+ this.outDBSPL1000 = null;
1230
+ this.THD = null;
1231
+
1232
+ // run the calibration at different gain values provided by the user
1233
+ for (let i = 0; i < trialIterations; i++) {
1234
+ //convert gain to DB and add to inDB
1235
+ if (i == trialIterations - 1) {
1236
+ checkRec = 'loudest';
1237
+ }
1238
+ inDB = Math.log10(gainValues[i]) * 20;
1239
+ // precision to 1 decimal place
1240
+ inDB = Math.round(inDB * 10) / 10;
1241
+ inDBValues.push(inDB);
1242
+ console.log('next update');
1243
+ this.status =
1244
+ `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() +
1245
+ this.generateTemplate().toString();
1246
+ this.emit('update', {message: this.status});
1247
+ do {
1248
+ // eslint-disable-next-line no-await-in-loop
1249
+ await this.volumeCalibrationSteps(
1250
+ stream,
1251
+ this.#playCalibrationAudioVolume,
1252
+ this.#createCalibrationToneWithGainValue,
1253
+ this.#sendToServerForProcessing,
1254
+ gainValues[i],
1255
+ lCalib, //todo make this a class parameter
1256
+ checkRec
1257
+ );
1258
+ } while (this.outDBSPL === null);
1259
+ outDBSPL1000Values.push(this.outDBSPL1000);
1260
+ thdValues.push(this.THD);
1261
+ outDBSPLValues.push(this.outDBSPL);
1262
+
1263
+ this.outDBSPL = null;
1264
+ this.outDBSPL1000 = null;
1265
+ this.THD = null;
1266
+ }
1267
+
1268
+ // get the volume calibration parameters from the server
1269
+ const parameters = await this.pyServerAPI
1270
+ .getVolumeCalibrationParameters({
1271
+ inDBValues: inDBValues,
1272
+ outDBSPLValues: outDBSPL1000Values,
1273
+ lCalib: lCalib,
1274
+ componentGainDBSPL,
1275
+ })
1276
+ .then(res => {
1277
+ this.incrementStatusBar();
1278
+ return res;
1279
+ });
1280
+ const result = {
1281
+ parameters: parameters,
1282
+ inDBValues: inDBValues,
1283
+ outDBSPLValues: outDBSPLValues,
1284
+ outDBSPL1000Values: outDBSPL1000Values,
1285
+ thdValues: thdValues,
1286
+ };
1287
+
1288
+ return result;
1289
+ };
1290
+
1291
+ // function to write frq and gain to firebase database given speakerID
1292
+ writeFrqGain = async (speakerID, frq, gain, OEM) => {
1293
+ // freq and gain are too large to take samples 1 in every 100 samples
1294
+
1295
+ const sampledFrq = [];
1296
+ const sampledGain = [];
1297
+ for (let i = 0; i < frq.length; i += 100) {
1298
+ sampledFrq.push(frq[i]);
1299
+ sampledGain.push(gain[i]);
1300
+ }
1301
+
1302
+ const data = {Freq: sampledFrq, Gain: sampledGain};
1303
+
1304
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/linear`), data);
1305
+ };
1306
+
1307
+ // Function to Read frq and gain from firebase database given speakerID
1308
+ // returns an array of frq and gain if speakerID exists, returns null otherwise
1309
+
1310
+ readFrqGain = async (speakerID, OEM) => {
1311
+ const dbRef = ref(database);
1312
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/linear`));
1313
+ if (snapshot.exists()) {
1314
+ return snapshot.val();
1315
+ }
1316
+ return null;
1317
+ };
1318
+
1319
+ readGainat1000Hz = async (speakerID, OEM) => {
1320
+ const dbRef = ref(database);
1321
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/Gain1000`));
1322
+ if (snapshot.exists()) {
1323
+ return snapshot.val();
1324
+ }
1325
+ return null;
1326
+ };
1327
+
1328
+ writeGainat1000Hz = async (speakerID, gain, OEM) => {
1329
+ const data = {Gain: gain};
1330
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/Gain1000`), gain);
1331
+ };
1332
+
1333
+ writeIsSmartPhone = async (speakerID, isSmartPhone, OEM) => {
1334
+ const data = {isSmartPhone: isSmartPhone};
1335
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/isSmartPhone`), isSmartPhone);
1336
+ };
1337
+
1338
+ doesMicrophoneExist = async (speakerID, OEM) => {
1339
+ const dbRef = ref(database);
1340
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}`));
1341
+ if (snapshot.exists()) {
1342
+ return true;
1343
+ }
1344
+ return false;
1345
+ };
1346
+
1347
+ addMicrophoneInfo = async (speakerID, OEM, micInfo) => {
1348
+ // add to database if /info does not exist
1349
+ const dbRef = ref(database);
1350
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/info`));
1351
+ if (!snapshot.exists()) {
1352
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/info`), micInfo);
1353
+ }
1354
+ };
1355
+
1356
+ convertToDB = gain => {
1357
+ return Math.log10(gain) * 20;
1358
+ };
1359
+
1360
+ // Function to perform linear interpolation between two points
1361
+ interpolate(x, x0, y0, x1, y1) {
1362
+ return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);
1363
+ }
1364
+
1365
+ findGainatFrequency = (frequencies, gains, targetFrequency) => {
1366
+ // Find the index of the first frequency in the array greater than the target frequency
1367
+ let index = 0;
1368
+ while (index < frequencies.length && frequencies[index] < targetFrequency) {
1369
+ index++;
1370
+ }
1371
+
1372
+ // Handle cases when the target frequency is outside the range of the given data
1373
+ if (index === 0) {
1374
+ return gains[0];
1375
+ } else if (index === frequencies.length) {
1376
+ return gains[gains.length - 1];
1377
+ } else {
1378
+ // Interpolate the gain based on the surrounding frequencies
1379
+ const x0 = frequencies[index - 1];
1380
+ const y0 = gains[index - 1];
1381
+ const x1 = frequencies[index];
1382
+ const y1 = gains[index];
1383
+ return this.interpolate(targetFrequency, x0, y0, x1, y1);
1384
+ }
1385
+ };
1386
+
1387
+ // Example of how to use the writeFrqGain and readFrqGain functions
1388
+ // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);
1389
+ // Speaker1 is the speakerID you want to write to in the database
1390
+ // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));
1391
+ // MiniDSPUMIK_1 is the speakerID with some Data in the database
1392
+ //adding gainDBSPL
1393
+ startCalibration = async (
1394
+ stream,
1395
+ gainValues,
1396
+ lCalib = 104.92978421490648,
1397
+ componentIR = null,
1398
+ microphoneName = 'MiniDSP-UMIK1-711-4754-vertical',
1399
+ _calibrateSoundCheck = 'goal', //GOAL PASSed in by default
1400
+ isSmartPhone = false,
1401
+ _calibrateSoundBurstRepeats = 4,
1402
+ _calibrateSoundBurstSec = 1,
1403
+ _calibrateSoundBurstsWarmup = 1,
1404
+ _calibrateSoundHz = 48000,
1405
+ _calibrateSoundIIRSec = 0.2,
1406
+ calibrateSound1000HzPreSec = 3.5,
1407
+ calibrateSound1000HzSec = 1.0,
1408
+ calibrateSound1000HzPostSec = 0.5,
1409
+ _calibrateSoundBackgroundSecs = 0,
1410
+ micManufacturer = '',
1411
+ micSerialNumber = '',
1412
+ micModelNumber = '',
1413
+ micModelName = ''
1414
+ ) => {
1415
+ this.CALIBRATION_TONE_DURATION =
1416
+ calibrateSound1000HzPreSec + calibrateSound1000HzSec + calibrateSound1000HzPostSec;
1417
+ this.calibrateSound1000HzPreSec = calibrateSound1000HzPreSec;
1418
+ this.calibrateSound1000HzSec = calibrateSound1000HzSec;
1419
+ this.calibrateSound1000HzPostSec = calibrateSound1000HzPostSec;
1420
+ this.iirLength = Math.floor(_calibrateSoundIIRSec * this.sourceSamplingRate);
1421
+ console.log('device info:', this.deviceInfo);
1422
+ this.numMLSPerCapture = _calibrateSoundBurstRepeats;
1423
+ this.desired_time_per_mls = _calibrateSoundBurstSec;
1424
+ this.num_mls_to_skip = _calibrateSoundBurstsWarmup;
1425
+ this.desired_sampling_rate = _calibrateSoundHz;
1426
+ this._calibrateSoundBackgroundSecs = _calibrateSoundBackgroundSecs;
1427
+
1428
+ //feed calibration goal here
1429
+ this._calibrateSoundCheck = _calibrateSoundCheck;
1430
+ //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
1431
+ //check the db based on the microphone currently connected
1432
+
1433
+ //new lCalib found at top of calibration files *1000hz, make sure to correct
1434
+ //based on zeroing of 1000hz, search for "*1000Hz"
1435
+ const ID = isSmartPhone ? micModelNumber : micSerialNumber;
1436
+ const OEM = isSmartPhone ? this.deviceInfo.OEM : micManufacturer;
1437
+ // const ID = "711-4754";
1438
+ // const OEM = "MiniDSP";
1439
+ const micInfo = {
1440
+ micModelName: isSmartPhone ? micModelName : microphoneName,
1441
+ OEM: OEM,
1442
+ ID: ID,
1443
+ HardwareName: isSmartPhone ? this.deviceInfo.hardwarename : microphoneName,
1444
+ hardwareFamily: isSmartPhone ? this.deviceInfo.hardwarefamily : microphoneName,
1445
+ HardwareModel: isSmartPhone ? this.deviceInfo.hardwaremodel : microphoneName,
1446
+ PlatformName: isSmartPhone ? this.deviceInfo.platformname : 'N/A',
1447
+ PlatformVersion: isSmartPhone ? this.deviceInfo.platformversion : 'N/A',
1448
+ DeviceType: isSmartPhone ? this.deviceInfo.devicetype : 'N/A',
1449
+ };
1450
+ this.addMicrophoneInfo(ID, OEM, micInfo);
1451
+ if (componentIR == null) {
1452
+ //mode 'ir'
1453
+ //global variable this.componentIR must be set
1454
+ this.componentIR = await this.readFrqGain(ID, OEM).then(data => {
1455
+ return data;
1456
+ });
1457
+
1458
+ lCalib = await this.readGainat1000Hz(ID, OEM);
1459
+ micInfo['gainDBSPL'] = lCalib;
1460
+ // this.componentGainDBSPL = this.convertToDB(lCalib);
1461
+ this.componentGainDBSPL = lCalib;
1462
+ //TODO: if this call to database is unknown, cannot perform experiment => return false
1463
+ if (this.componentIR == null) {
1464
+ this.status =
1465
+ `Microphone (${OEM},${ID}) is not found in the database. Please add it to the database.`.toString();
1466
+ this.emit('update', {message: this.status});
1467
+ return false;
1468
+ }
1469
+ } else {
1470
+ this.componentIR = componentIR;
1471
+ lCalib = this.findGainatFrequency(this.componentIR.Freq, this.componentIR.Gain, 1000);
1472
+ // this.componentGainDBSPL = this.convertToDB(lCalib);
1473
+ this.componentGainDBSPL = lCalib;
1474
+ await this.writeIsSmartPhone(ID, isSmartPhone, OEM);
1475
+ }
1476
+
1477
+ this.oldComponentIR = this.componentIR;
1478
+
1479
+ let volumeResults = await this.startCalibrationVolume(
1480
+ stream,
1481
+ gainValues,
1482
+ lCalib,
1483
+ this.componentGainDBSPL
1484
+ );
1485
+
1486
+ let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);
1487
+ impulseResponseResults['background_noise'] = this.background_noise;
1488
+ if (componentIR != null) {
1489
+ //insert Freq and Gain from this.componentIR into db
1490
+ await this.writeFrqGain(
1491
+ ID,
1492
+ impulseResponseResults.componentIR.Freq,
1493
+ impulseResponseResults.componentIR.Gain,
1494
+ OEM
1495
+ );
1496
+ micInfo['gainDBSPL'] = impulseResponseResults.parameters.gainDBSPL;
1497
+ await this.writeGainat1000Hz(ID, micInfo['gainDBSPL'], OEM);
1498
+ }
1499
+
1500
+ const total_results = {...volumeResults, ...impulseResponseResults};
1501
+
1502
+ total_results['micInfo'] = micInfo;
1503
+ console.log('total results');
1504
+ console.log(total_results);
1505
+ return total_results;
1506
+ };
1507
+ }
1508
+
1509
+ export default Combination;