speaker-calibration 2.2.19 → 2.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/.eslintignore +71 -71
  2. package/.eslintrc.json +40 -40
  3. package/.prettierignore +69 -69
  4. package/.prettierrc +14 -14
  5. package/LICENSE +20 -20
  6. package/README.md +133 -133
  7. package/__mocks__/fileMock.js +1 -1
  8. package/__mocks__/styleMock.js +1 -1
  9. package/babel.config.js +3 -3
  10. package/coverage/clover.xml +71 -71
  11. package/coverage/coverage-final.json +224 -224
  12. package/coverage/lcov-report/PythonServerInterface.js.html +265 -265
  13. package/coverage/lcov-report/base.css +354 -354
  14. package/coverage/lcov-report/block-navigation.js +82 -82
  15. package/coverage/lcov-report/index.html +123 -123
  16. package/coverage/lcov-report/prettify.css +101 -101
  17. package/coverage/lcov-report/prettify.js +937 -937
  18. package/coverage/lcov-report/sorter.js +189 -189
  19. package/coverage/lcov-report/src/index.html +121 -121
  20. package/coverage/lcov-report/src/server/PythonServerInterface.js.html +268 -268
  21. package/coverage/lcov-report/src/server/index.html +123 -123
  22. package/coverage/lcov-report/src/tasks/audioCalibrator.js.html +499 -499
  23. package/coverage/lcov-report/src/tasks/audioRecorder.js.html +412 -412
  24. package/coverage/lcov-report/src/tasks/index.html +143 -143
  25. package/coverage/lcov-report/src/tasks/volume/index.html +123 -123
  26. package/coverage/lcov-report/src/tasks/volume/volume.js.html +409 -409
  27. package/coverage/lcov-report/src/utils.js.html +172 -172
  28. package/coverage/lcov.info +91 -91
  29. package/dist/example/fetch-languages-sheets.js +77 -77
  30. package/dist/example/i18n.js +6822 -6822
  31. package/dist/example/index.html +47 -47
  32. package/dist/example/listener.html +61 -61
  33. package/dist/example/listener.js +97 -97
  34. package/dist/example/server.js +51 -51
  35. package/dist/example/speaker.html +138 -138
  36. package/dist/example/speakerUI.js +269 -269
  37. package/dist/example/styles.css +63 -63
  38. package/dist/main.js +17 -17
  39. package/dist/mlsGen.js +6814 -6814
  40. package/dist/mlsGen.wasm +0 -0
  41. package/dist/package-lock.json +1018 -1018
  42. package/dist/package.json +18 -18
  43. package/doc/AudioCalibrator.html +417 -417
  44. package/doc/AudioPeer.html +251 -251
  45. package/doc/AudioRecorder.html +195 -195
  46. package/doc/ImpulseResponse.html +215 -215
  47. package/doc/Listener.html +308 -308
  48. package/doc/MlsGenInterface.html +226 -226
  49. package/doc/MyEventEmitter.html +274 -274
  50. package/doc/PythonServerAPI.html +109 -109
  51. package/doc/Speaker.html +276 -276
  52. package/doc/Takes%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +128 -128
  53. package/doc/Takes%20the%20url%20of%20the%20current%20site%0Aand%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +138 -138
  54. package/doc/Takes%20the%20url%20of%20the%20current%20site%20and%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +137 -137
  55. package/doc/Volume.html +88 -88
  56. package/doc/audioCalibrator.js.html +179 -179
  57. package/doc/audioPeer.js.html +175 -175
  58. package/doc/audioRecorder.js.html +163 -163
  59. package/doc/creates%20a%20new%20AudioRecorder%20instance.%20%0ASets%20up%20the%20audio%20context%20and%20file%20reader..html +114 -114
  60. package/doc/fonts/OpenSans-Bold-webfont.svg +1829 -1829
  61. package/doc/fonts/OpenSans-BoldItalic-webfont.svg +1829 -1829
  62. package/doc/fonts/OpenSans-Italic-webfont.svg +1829 -1829
  63. package/doc/fonts/OpenSans-Light-webfont.svg +1830 -1830
  64. package/doc/fonts/OpenSans-LightItalic-webfont.svg +1834 -1834
  65. package/doc/fonts/OpenSans-Regular-webfont.svg +1830 -1830
  66. package/doc/global.html +308 -308
  67. package/doc/index.html +58 -58
  68. package/doc/listener.js.html +170 -170
  69. package/doc/mlsGen_mlsGenInterface.js.html +117 -117
  70. package/doc/myEventEmitter.js.html +124 -124
  71. package/doc/peer-connection_audioPeer.js.html +188 -188
  72. package/doc/peer-connection_listener.js.html +311 -311
  73. package/doc/peer-connection_speaker.js.html +381 -381
  74. package/doc/scripts/linenumber.js +25 -25
  75. package/doc/scripts/prettify/Apache-License-2.0.txt +202 -202
  76. package/doc/scripts/prettify/lang-css.js +24 -24
  77. package/doc/scripts/prettify/prettify.js +640 -640
  78. package/doc/server_PythonServerAPI.js.html +160 -160
  79. package/doc/speaker.js.html +248 -248
  80. package/doc/styles/jsdoc-default.css +371 -371
  81. package/doc/styles/prettify-jsdoc.css +111 -111
  82. package/doc/styles/prettify-tomorrow.css +163 -163
  83. package/doc/tasks_audioCalibrator.js.html +207 -207
  84. package/doc/tasks_audioRecorder.js.html +190 -190
  85. package/doc/tasks_impulse-response_impulseResponse.js.html +442 -442
  86. package/doc/tasks_impulse-response_mlsGen_mlsGenInterface.js.html +175 -175
  87. package/doc/tasks_volume_volume.js.html +185 -185
  88. package/doc/utils.js.html +105 -105
  89. package/jest.config.js +173 -173
  90. package/makefile +74 -0
  91. package/netlify.toml +26 -26
  92. package/package.json +69 -69
  93. package/src/config/firebase.js +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 +436 -435
  101. package/src/server/PythonServerAPI.js +415 -415
  102. package/src/tasks/audioCalibrator.js +269 -269
  103. package/src/tasks/audioRecorder.js +177 -177
  104. package/src/tasks/combination/combination.js +1207 -1264
  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/.gitignore +0 -81
@@ -1,1264 +1,1207 @@
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
- /** @private */
103
- #CALIBRATION_TONE_DURATION = 5; // seconds
104
-
105
- /** @private */
106
- outDBSPL = null;
107
- THD = null;
108
- outDBSPL1000 = null;
109
-
110
- /** @private */
111
- TAPER_SECS = 0.01; // seconds
112
-
113
- /** @private */
114
- status_denominator = 8;
115
-
116
- /** @private */
117
- status_numerator = 0;
118
-
119
- /** @private */
120
- percent_complete = 0;
121
-
122
- /** @private */
123
- status = ``;
124
-
125
- /**@private */
126
- 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>`;
127
-
128
- /**@private */
129
- componentIR = null;
130
-
131
- /**@private */
132
- oldComponentIR = null;
133
-
134
- /**@private */
135
- systemIR = null;
136
-
137
- /**@private */
138
- _calibrateSoundCheck = '';
139
-
140
- deviceType = null;
141
-
142
- deviceName = null;
143
-
144
- deviceInfo = null;
145
-
146
- desired_time_per_mls = 0;
147
-
148
- num_mls_to_skip = 0;
149
-
150
- desired_sampling_rate = 0;
151
-
152
- #currentConvolution = [];
153
-
154
- mode = 'unfiltered';
155
-
156
- sourceNode;
157
-
158
- autocorrelations = [];
159
-
160
- iirLength = 0;
161
-
162
- /**generate string template that gets reevaluated as variable increases */
163
- generateTemplate = () => {
164
- if (this.percent_complete > 100) {
165
- this.percent_complete = 100;
166
- }
167
- 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>`;
168
- return template;
169
- };
170
-
171
- /** increment numerator and percent for status bar */
172
- incrementStatusBar = () => {
173
- this.status_numerator += 1;
174
- this.percent_complete = (this.status_numerator / this.status_denominator) * 100;
175
- };
176
-
177
- setDeviceType = deviceType => {
178
- this.deviceType = deviceType;
179
- };
180
-
181
- setDeviceName = deviceName => {
182
- this.deviceName = deviceName;
183
- };
184
-
185
- setDeviceInfo = deviceInfo => {
186
- this.deviceInfo = deviceInfo;
187
- };
188
-
189
- /** .
190
- * .
191
- * .
192
- * Sends all the computed impulse responses to the backend server for processing
193
- *
194
- * @returns sets the resulting inverted impulse response to the class property
195
- * @example
196
- */
197
- sendSystemImpulseResponsesToServerForProcessing = async () => {
198
- const computedIRs = await Promise.all(this.impulseResponses);
199
- const filteredComputedIRs = computedIRs.filter(element => {
200
- return element != undefined;
201
- });
202
- const mls = this.#mls;
203
- const lowHz = this.#lowHz;
204
- const highHz = this.#highHz;
205
- const iirLength = this.iirLength;
206
- const num_periods = this.numMLSPerCapture;
207
- this.stepNum += 1;
208
- console.log('send impulse responses to server: ' + this.stepNum);
209
- this.status =
210
- `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
211
- this.emit('update', {message: this.status});
212
- return this.pyServerAPI
213
- .getSystemInverseImpulseResponseWithRetry({
214
- payload: filteredComputedIRs.slice(0, this.numCaptures),
215
- mls,
216
- lowHz,
217
- highHz,
218
- iirLength,
219
- num_periods,
220
- sampleRate: this.sourceSamplingRate || 96000,
221
- })
222
- .then(res => {
223
- console.log(res);
224
- this.stepNum += 1;
225
- console.log('got impulse response ' + this.stepNum);
226
- this.incrementStatusBar();
227
- this.status =
228
- `All Hz Calibration: done computing the IIR...`.toString() +
229
- this.generateTemplate().toString();
230
- this.emit('update', {message: this.status});
231
- this.systemInvertedImpulseResponse = res['iir'];
232
- this.systemIR = res['ir'];
233
- this.systemConvolution = res['convolution'];
234
- })
235
- .catch(err => {
236
- console.error(err);
237
- });
238
- };
239
-
240
- /** .
241
- * .
242
- * .
243
- * Sends all the computed impulse responses to the backend server for processing
244
- *
245
- * @returns sets the resulting inverted impulse response to the class property
246
- * @example
247
- */
248
- sendComponentImpulseResponsesToServerForProcessing = async () => {
249
- const computedIRs = await Promise.all(this.impulseResponses);
250
- const filteredComputedIRs = computedIRs.filter(element => {
251
- return element != undefined;
252
- });
253
- const componentIRGains = this.componentIR['Gain'];
254
- const componentIRFreqs = this.componentIR['Freq'];
255
- const mls = this.#mls;
256
- const lowHz = this.#lowHz;
257
- const iirLength = this.iirLength;
258
- const num_periods = this.numMLSPerCapture;
259
- const highHz = this.#highHz;
260
- this.stepNum += 1;
261
- console.log('send impulse responses to server: ' + this.stepNum);
262
- this.status =
263
- `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
264
- this.emit('update', {message: this.status});
265
- return this.pyServerAPI
266
- .getComponentInverseImpulseResponseWithRetry({
267
- payload: filteredComputedIRs.slice(0, this.numCaptures),
268
- mls,
269
- lowHz,
270
- highHz,
271
- iirLength,
272
- componentIRGains,
273
- componentIRFreqs,
274
- num_periods,
275
- sampleRate: this.sourceSamplingRate || 96000,
276
- })
277
- .then(res => {
278
- console.log(res);
279
- this.stepNum += 1;
280
- console.log('got impulse response ' + this.stepNum);
281
- this.incrementStatusBar();
282
- this.status =
283
- `All Hz Calibration: done computing the IIR...`.toString() +
284
- this.generateTemplate().toString();
285
- this.emit('update', {message: this.status});
286
- this.componentInvertedImpulseResponse = res['iir'];
287
- this.componentIR['Gain'] = res['ir'];
288
- this.componentIR['Freq'] = res['frequencies'];
289
- this.componentConvolution = res['convolution'];
290
- })
291
- .catch(err => {
292
- // this.emit('InvertedImpulseResponse', {res: false});
293
- console.error(err);
294
- });
295
- };
296
-
297
- /** .
298
- * .
299
- * .
300
- * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing
301
- *
302
- * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed
303
- * @example
304
- */
305
- sendRecordingToServerForProcessing = signalCsv => {
306
- const allSignals = this.getAllRecordedSignals();
307
- const numSignals = allSignals.length;
308
- const mls = this.#mls;
309
- const payload =
310
- signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
311
- console.log('sending rec');
312
- this.stepNum += 1;
313
- console.log('send rec ' + this.stepNum);
314
- this.status =
315
- `All Hz Calibration Step: computing the IR of the last recording...`.toString() +
316
- this.generateTemplate().toString();
317
- this.emit('update', {message: this.status});
318
- this.impulseResponses.push(
319
- this.pyServerAPI
320
- .getImpulseResponse({
321
- sampleRate: this.sourceSamplingRate || 96000,
322
- payload,
323
- mls,
324
- P: this.#P,
325
- numPeriods: this.numMLSPerCapture,
326
- })
327
- .then(res => {
328
- if (this.numSuccessfulCaptured < this.numCaptures) {
329
- this.numSuccessfulCaptured += 1;
330
- console.log('num succ capt: ' + this.numSuccessfulCaptured);
331
- this.stepNum += 1;
332
- console.log('got impulse response ' + this.stepNum);
333
- this.incrementStatusBar();
334
- this.status =
335
- `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +
336
- this.generateTemplate().toString();
337
- this.emit('update', {
338
- message: this.status,
339
- });
340
- this.autocorrelations.push(res['autocorrelation']);
341
- return res['ir'];
342
- }
343
- })
344
- .catch(err => {
345
- console.error(err);
346
- })
347
- );
348
- };
349
-
350
- /**
351
- * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number
352
- * of MLS periods defined in the constructor.
353
- *
354
- * @example
355
- */
356
- #awaitDesiredMLSLength = async () => {
357
- // seconds per MLS = P / SR
358
- // await N * P / SR
359
- this.stepNum += 1;
360
- console.log('await desired length ' + this.stepNum);
361
- this.status =
362
- `All Hz Calibration: sampling the calibration signal...`.toString() +
363
- `\niteration ${this.stepNum}` +
364
- this.generateTemplate();
365
- this.emit('update', {
366
- message: this.status,
367
- });
368
- let time_to_wait = 0;
369
- if (this.mode === 'unfiltered') {
370
- time_to_wait = (this.#mls.length / this.sourceSamplingRate) * this.numMLSPerCapture;
371
- } else if (this.mode === 'filtered') {
372
- time_to_wait =
373
- (this.#currentConvolution.length / this.sourceSamplingRate) * this.numMLSPerCapture;
374
- } else {
375
- throw new Error('Mode broke in awaitDesiredMLSLength');
376
- }
377
-
378
- await sleep(time_to_wait * 1.1);
379
- };
380
-
381
- /** .
382
- * .
383
- * .
384
- * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state
385
- *
386
- * @example
387
- */
388
- #awaitSignalOnset = async () => {
389
- this.stepNum += 1;
390
- console.log('await signal onset ' + this.stepNum);
391
- this.status =
392
- `All Hz Calibration: waiting for the signal to stabilize...`.toString() +
393
- this.generateTemplate();
394
- this.emit('update', {
395
- message: this.status,
396
- });
397
- let number_of_bursts_to_skip = this.num_mls_to_skip;
398
- let time_to_sleep = 0;
399
- if (this.mode === 'unfiltered') {
400
- time_to_sleep = (this.#mls.length / this.sourceSamplingRate)*this.number_of_bursts_to_skip;
401
- } else if (this.mode === 'filtered') {
402
- console.log(this.#currentConvolution.length);
403
- time_to_sleep = (this.#currentConvolution.length / this.sourceSamplingRate)*this.number_of_bursts_to_skip;
404
- } else {
405
- throw new Error('Mode broke in awaitSignalOnset');
406
- }
407
- await sleep(time_to_sleep);
408
- };
409
-
410
- /**
411
- * Called immediately after a recording is captured. Used to process the resulting signal
412
- * whether by sending the result to a server or by computing a result locally.
413
- *
414
- * @example
415
- */
416
- #afterMLSRecord = () => {
417
- console.log('after record');
418
- this.sendRecordingToServerForProcessing();
419
- };
420
-
421
- #afterMLSwIIRRecord = () => {
422
- if (this.numSuccessfulCaptured < 1) {
423
- this.numSuccessfulCaptured += 1;
424
- this.stepNum += 1;
425
- this.incrementStatusBar();
426
- console.log('after mls w iir record for some reason add numSucc capt ' + this.stepNum);
427
- this.status =
428
- `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +
429
- this.generateTemplate().toString();
430
- this.emit('update', {
431
- message: this.status,
432
- });
433
- }
434
- };
435
-
436
- /** .
437
- * .
438
- * .
439
- * Created an S Curver Buffer to taper the signal onset
440
- *
441
- * @param {*} length
442
- * @param {*} phase
443
- * @returns
444
- * @example
445
- */
446
- static createSCurveBuffer = (length, phase) => {
447
- const curve = new Float32Array(length);
448
- let i;
449
- for (i = 0; i < length; i += 1) {
450
- // scale the curve to be between 0-1
451
- curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;
452
- }
453
- return curve;
454
- };
455
-
456
- static createInverseSCurveBuffer = (length, phase) => {
457
- const curve = new Float32Array(length);
458
- let i;
459
- let j = length - 1;
460
- for (i = 0; i < length; i += 1) {
461
- // scale the curve to be between 0-1
462
- curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;
463
- j -= 1;
464
- }
465
- return curve;
466
- };
467
-
468
- /**
469
- * Construct a Calibration Node with the calibration parameters.
470
- *
471
- * @param dataBuffer
472
- * @private
473
- * @example
474
- */
475
- #createCalibrationNodeFromBuffer = dataBuffer => {
476
- console.log('databuffer');
477
- console.log(dataBuffer);
478
- if (!this.sourceAudioContext) {
479
- this.makeNewSourceAudioContext();
480
- }
481
-
482
- const buffer = this.sourceAudioContext.createBuffer(
483
- 1, // number of channels
484
- dataBuffer.length,
485
- this.sourceAudioContext.sampleRate // sample rate
486
- );
487
-
488
- const data = buffer.getChannelData(0); // get data
489
- // fill the buffer with our data
490
- try {
491
- for (let i = 0; i < dataBuffer.length; i += 1) {
492
- data[i] = dataBuffer[i] * 0.1;
493
- }
494
- } catch (error) {
495
- console.error(error);
496
- }
497
-
498
- this.sourceNode = this.sourceAudioContext.createBufferSource();
499
-
500
- this.sourceNode.buffer = buffer;
501
- if (this.mode === 'filtered'){
502
- this.sourceNode.loop = false;
503
- }else{
504
- this.sourceNode.loop = true;
505
- }
506
-
507
- this.sourceNode.connect(this.sourceAudioContext.destination);
508
-
509
- this.addCalibrationNode(this.sourceNode);
510
- };
511
-
512
- /**
513
- * Given a data buffer, creates the required calibration node
514
- *
515
- * @param {*} dataBufferArray
516
- * @example
517
- */
518
- #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {
519
- if (dataBufferArray.length === 1) {
520
- this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);
521
- } else {
522
- throw new Error('The length of the data buffer array must be 1');
523
- }
524
- };
525
-
526
- /**
527
- * function to put MLS filtered IIR data obtained from
528
- * python server into our audio buffer to be played aloud
529
- */
530
- #putInPythonConv = () => {
531
- const audioCtx = this.makeNewSourceAudioContextConvolved();
532
-
533
- //depends on goal
534
- if (this._calibrateSoundCheck != 'system') {
535
- this.#currentConvolution = this.componentConvolution;
536
- const buffer = audioCtx.createBuffer(
537
- 1, // number of channels
538
- this.componentConvolution.length,
539
- audioCtx.sampleRate // sample rate
540
- );
541
-
542
- const data = buffer.getChannelData(0); // get data
543
- // fill the buffer with our data
544
- try {
545
- for (let i = 0; i < this.componentConvolution.length; i += 1) {
546
- data[i] = this.componentConvolution[i];
547
- }
548
- } catch (error) {
549
- console.error(error);
550
- }
551
-
552
- const source = audioCtx.createBufferSource();
553
-
554
- source.buffer = buffer;
555
- source.loop = true;
556
- source.connect(audioCtx.destination);
557
-
558
- this.addCalibrationNodeConvolved(source);
559
- } else {
560
- this.#currentConvolution = this.systemConvolution;
561
- const buffer = audioCtx.createBuffer(
562
- 1, // number of channels
563
- this.systemConvolution.length,
564
- audioCtx.sampleRate // sample rate
565
- );
566
- const data = buffer.getChannelData(0); // get data
567
- // fill the buffer with our data
568
- try {
569
- for (let i = 0; i < this.systemConvolution.length; i += 1) {
570
- data[i] = this.systemConvolution[i];
571
- }
572
- } catch (error) {
573
- console.error(error);
574
- }
575
-
576
- const source = audioCtx.createBufferSource();
577
-
578
- source.buffer = buffer;
579
- source.loop = true;
580
- source.connect(audioCtx.destination);
581
-
582
- this.addCalibrationNodeConvolved(source);
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
- if (this.mode === 'unfiltered') {
595
- this.calibrationNodes[0].start(0);
596
- this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);
597
- console.log('play calibration audio ' + this.stepNum);
598
- this.status =
599
- `All Hz Calibration: playing the calibration tone...`.toString() +
600
- this.generateTemplate().toString();
601
- this.emit('update', {message: this.status});
602
- } else if (this.mode === 'filtered') {
603
- this.calibrationNodes[0].start(0);
604
- console.log('play convolved audio ' + this.stepNum);
605
- this.status =
606
- `All Hz Calibration: playing the convolved calibration tone...`.toString() +
607
- this.generateTemplate().toString();
608
- this.emit('update', {message: this.status});
609
- } else {
610
- throw new Error('Mode is incorrect');
611
- }
612
- this.stepNum += 1;
613
- console.log('sink sampling rate');
614
- console.log(this.sinkSamplingRate);
615
- console.log('source sampling rate');
616
- console.log(this.sourceSamplingRate);
617
- };
618
-
619
- /** .
620
- * .
621
- * .
622
- * Stops the audio with tapered offset
623
- *
624
- * @example
625
- */
626
- #stopCalibrationAudio = () => {
627
- this.calibrationNodes[0].stop(0);
628
- this.calibrationNodes = [];
629
- this.sourceNode.disconnect();
630
- this.stepNum += 1;
631
- console.log('stop calibration audio ' + this.stepNum);
632
- this.status =
633
- `All Hz Calibration: stopping the calibration tone...`.toString() +
634
- this.generateTemplate().toString();
635
- this.emit('update', {message: this.status});
636
- };
637
-
638
- playMLSwithIIR = async (stream, iir) => {
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
- );
653
- };
654
-
655
- /**
656
- * Public method to start the calibration process. Objects intialized from webassembly allocate new memory
657
- * and must be manually freed. This function is responsible for intializing the MlsGenInterface,
658
- * and wrapping the calibration steps with a garbage collection safe gaurd.
659
- *
660
- * @public
661
- * @param stream - The stream of audio from the Listener.
662
- * @example
663
- */
664
- startCalibrationImpulseResponse = async stream => {
665
- let desired_time = this.desired_time_per_mls;
666
-
667
- console.log('MLS sequence should be of length: ' + this.sourceSamplingRate * desired_time);
668
-
669
- length = this.sourceSamplingRate * desired_time;
670
- //get mls here
671
- await this.pyServerAPI
672
- .getMLSWithRetry(length)
673
- .then(res => {
674
- console.log(res);
675
- this.#mlsBufferView = res['mls'];
676
- })
677
- .catch(err => {
678
- // this.emit('InvertedImpulseResponse', {res: false});
679
- console.error(err);
680
- });
681
- await this.calibrationSteps(
682
- stream,
683
- this.#playCalibrationAudio, // play audio func (required)
684
- this.#createCalibrationNodeFromBuffer(this.#mlsBufferView), // before play func
685
- this.#awaitSignalOnset, // before record
686
- () => this.numSuccessfulCaptured < this.numCaptures, // loop while true
687
- this.#awaitDesiredMLSLength, // during record
688
- this.#afterMLSRecord, // after record
689
- this.mode
690
- ),
691
- this.#stopCalibrationAudio();
692
-
693
- // at this stage we've captured all the required signals,
694
- // and have received IRs for each one
695
- // so let's send all the IRs to the server to be converted to a single IIR
696
- await this.sendSystemImpulseResponsesToServerForProcessing();
697
- await this.sendComponentImpulseResponsesToServerForProcessing();
698
-
699
- this.numSuccessfulCaptured = 0;
700
-
701
- let iir_ir_and_plots;
702
- if (this._calibrateSoundCheck != 'none') {
703
- if (this._calibrateSoundCheck != 'system') {
704
- this.#currentConvolution = this.componentConvolution;
705
- } else {
706
- this.#currentConvolution = this.systemConvolution;
707
- }
708
- this.numMLSPerCapture = 1;
709
- await this.playMLSwithIIR(stream, this.invertedImpulseResponse);
710
- this.#stopCalibrationAudio();
711
- this.sourceAudioContext.close();
712
- let conv_recs = this.getAllFilteredRecordedSignals();
713
- let recs = this.getAllRecordedSignals();
714
- let unconv_rec = recs[0];
715
- let conv_rec = conv_recs[0];
716
- if (this._calibrateSoundCheck != 'system') {
717
- let knownGain = this.oldComponentIR.Gain;
718
- let knownFreq = this.oldComponentIR.Freq;
719
- let sampleRate = this.sourceSamplingRate || 96000;
720
- let unconv_results = await this.pyServerAPI
721
- .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)
722
- .then(res => {
723
- this.incrementStatusBar();
724
- this.status =
725
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
726
- this.generateTemplate().toString();
727
- this.emit('update', {message: this.status});
728
- return res;
729
- })
730
- .catch(err => {
731
- console.error(err);
732
- });
733
-
734
- let conv_results = await this.pyServerAPI
735
- .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)
736
- .then(res => {
737
- this.incrementStatusBar();
738
- this.status =
739
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
740
- this.generateTemplate().toString();
741
- this.emit('update', {message: this.status});
742
- return res;
743
- })
744
- .catch(err => {
745
- console.error(err);
746
- });
747
- iir_ir_and_plots = {
748
- systemIIR: this.systemInvertedImpulseResponse,
749
- componentIIR: this.componentInvertedImpulseResponse,
750
- x_unconv: unconv_results['x'],
751
- y_unconv: unconv_results['y'],
752
- x_conv: conv_results['x'],
753
- y_conv: conv_results['y'],
754
- componentIR: this.componentIR,
755
- systemIR: this.systemIR,
756
- };
757
- } else {
758
- let results = await this.pyServerAPI
759
- .getPSDWithRetry({
760
- unconv_rec,
761
- conv_rec,
762
- sampleRate: this.sourceSamplingRate || 96000,
763
- })
764
- .then(res => {
765
- this.incrementStatusBar();
766
- this.status =
767
- `All Hz Calibration: done computing the PSD graphs...`.toString() +
768
- this.generateTemplate().toString();
769
- this.emit('update', {message: this.status});
770
- return res;
771
- })
772
- .catch(err => {
773
- console.error(err);
774
- });
775
- iir_ir_and_plots = {
776
- systemIIR: this.systemInvertedImpulseResponse,
777
- componentIIR: this.componentInvertedImpulseResponse,
778
- x_unconv: results['x_unconv'],
779
- y_unconv: results['y_unconv'],
780
- x_conv: results['x_conv'],
781
- y_conv: results['y_conv'],
782
- componentIR: this.componentIR,
783
- systemIR: this.systemIR,
784
- };
785
- }
786
-
787
- if (this.#download) {
788
- this.downloadSingleUnfilteredRecording();
789
- this.downloadSingleFilteredRecording();
790
- saveToCSV(this.#mls, 'MLS.csv');
791
- saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
792
- saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
793
- saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
794
- saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
795
- for (let i = 0; i < this.autocorrelations.length; i++) {
796
- saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
797
- }
798
- const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
799
- for (let i = 0; i < res.length; i++) {
800
- if (res[i] != undefined) {
801
- saveToCSV(res[i], `IR_${i}`);
802
- }
803
- }
804
- });
805
- }
806
- } else {
807
- iir_ir_and_plots = {
808
- systemIIR: this.systemInvertedImpulseResponse,
809
- componentIIR: this.componentInvertedImpulseResponse,
810
- x_unconv: [],
811
- y_unconv: [],
812
- x_conv: [],
813
- y_conv: [],
814
- componentIR: this.componentIR,
815
- systemIR: this.systemIR,
816
- };
817
- if (this.#download) {
818
- saveToCSV(this.#mls, 'MLS.csv');
819
- saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
820
- saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
821
- saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
822
- saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
823
- for (let i = 0; i < this.autocorrelations.length; i++) {
824
- saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
825
- }
826
- const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
827
- for (let i = 0; i < res.length; i++) {
828
- if (res[i] != undefined) {
829
- saveToCSV(res[i], `IR_${i}`);
830
- }
831
- }
832
- });
833
- }
834
- }
835
-
836
- this.percent_complete = 100;
837
-
838
- this.status = `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();
839
- this.emit('update', {message: this.status});
840
-
841
- //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR
842
- //that was used to calibrate
843
-
844
- return iir_ir_and_plots;
845
- };
846
-
847
- //////////////////////volume
848
-
849
- handleIncomingData = data => {
850
- console.log('Received data: ', data);
851
- if (data.type === 'soundGainDBSPL') {
852
- this.soundGainDBSPL = data.value;
853
- } else {
854
- throw new Error(`Unknown data type: ${data.type}`);
855
- }
856
- };
857
- createSCurveBuffer = (onSetBool = true) => {
858
- const curve = new Float32Array(this.TAPER_SECS * this.sourceSamplingRate + 1);
859
- const frequency = 1 / (4 * this.TAPER_SECS);
860
- let j = 0;
861
- for (let i = 0; i < this.TAPER_SECS * this.sourceSamplingRate + 1; i += 1) {
862
- const phase = 2 * Math.PI * frequency * j;
863
- const onsetTaper = Math.pow(Math.sin(phase), 2);
864
- const offsetTaper = Math.pow(Math.cos(phase), 2);
865
- curve[i] = onSetBool ? onsetTaper : offsetTaper;
866
- j += 1 / this.sourceSamplingRate;
867
- }
868
- return curve;
869
- };
870
-
871
- #getTruncatedSignal = (left = 3.5, right = 4.5) => {
872
- const start = Math.floor(left * this.sourceSamplingRate);
873
- const end = Math.floor(right * this.sourceSamplingRate);
874
- const result = Array.from(this.getLastRecordedSignal().slice(start, end));
875
-
876
- /**
877
- * function to check that capture was properly made
878
- * @param {*} list
879
- */
880
- const checkResult = list => {
881
- const setItem = new Set(list);
882
- if (setItem.size === 1 && setItem.has(0)) {
883
- console.warn(
884
- 'The last capture failed, all recorded signal is zero',
885
- this.getAllRecordedSignals()
886
- );
887
- }
888
- if (setItem.size === 0) {
889
- console.warn('The last capture failed, no recorded signal');
890
- }
891
- };
892
- checkResult(result);
893
- return result;
894
- };
895
-
896
- /**
897
- *
898
- *
899
- Construct a calibration Node with the calibration parameters and given gain value
900
- * @param {*} gainValue
901
- * */
902
- #createCalibrationToneWithGainValue = gainValue => {
903
- const audioContext = this.makeNewSourceAudioContext();
904
- const oscilator = audioContext.createOscillator();
905
- const gainNode = audioContext.createGain();
906
- const taperGainNode = audioContext.createGain();
907
- const offsetGainNode = audioContext.createGain();
908
- const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;
909
-
910
- oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
911
- oscilator.type = this.#CALIBRATION_TONE_TYPE;
912
- gainNode.gain.value = gainValue;
913
-
914
- oscilator.connect(gainNode);
915
- gainNode.connect(taperGainNode);
916
- const onsetCurve = this.createSCurveBuffer();
917
- taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
918
- taperGainNode.connect(offsetGainNode);
919
- const offsetCurve = this.createSCurveBuffer(false);
920
- offsetGainNode.gain.setValueCurveAtTime(
921
- offsetCurve,
922
- totalDuration - this.TAPER_SECS,
923
- this.TAPER_SECS
924
- );
925
- offsetGainNode.connect(audioContext.destination);
926
-
927
- this.addCalibrationNode(oscilator);
928
- };
929
-
930
- /**
931
- * Construct a Calibration Node with the calibration parameters.
932
- *
933
- * @private
934
- * @example
935
- */
936
- #createCalibrationNode = () => {
937
- const audioContext = this.makeNewSourceAudioContext();
938
- const oscilator = audioContext.createOscillator();
939
- const gainNode = audioContext.createGain();
940
-
941
- oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
942
- oscilator.type = this.#CALIBRATION_TONE_TYPE;
943
- gainNode.gain.value = 0.04;
944
-
945
- oscilator.connect(gainNode);
946
- gainNode.connect(audioContext.destination);
947
-
948
- this.addCalibrationNode(oscilator);
949
- };
950
-
951
- #playCalibrationAudioVolume = async () => {
952
- const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;
953
-
954
- this.calibrationNodes[0].start(0);
955
- this.calibrationNodes[0].stop(totalDuration);
956
- console.log(`Playing a buffer of ${this.#CALIBRATION_TONE_DURATION} seconds of audio`);
957
- console.log(`Waiting a total of ${totalDuration} seconds`);
958
- await sleep(totalDuration);
959
- };
960
-
961
- #sendToServerForProcessing = lCalib => {
962
- console.log('Sending data to server');
963
- this.pyServerAPI
964
- .getVolumeCalibration({
965
- sampleRate: this.sourceSamplingRate,
966
- payload: this.#getTruncatedSignal(),
967
- lCalib: lCalib,
968
- })
969
- .then(res => {
970
- if (this.outDBSPL === null) {
971
- this.incrementStatusBar();
972
- this.outDBSPL = res['outDbSPL'];
973
- this.outDBSPL1000 = res['outDbSPL1000'];
974
- this.THD = res['thd'];
975
- }
976
- })
977
- .catch(err => {
978
- console.warn(err);
979
- });
980
- };
981
-
982
- startCalibrationVolume = async (stream, gainValues, lCalib, componentGainDBSPL) => {
983
- const trialIterations = gainValues.length;
984
- this.status_denominator += trialIterations;
985
- const thdValues = [];
986
- const inDBValues = [];
987
- let inDB = 0;
988
- const outDBSPLValues = [];
989
- const outDBSPL1000Values = [];
990
-
991
- // do one calibration that will be discarded
992
- const soundLevelToDiscard = -60;
993
- const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);
994
- this.status =
995
- `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() +
996
- this.generateTemplate().toString();
997
- //this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});
998
- this.emit('update', {message: this.status});
999
-
1000
- do {
1001
- // eslint-disable-next-line no-await-in-loop
1002
- await this.volumeCalibrationSteps(
1003
- stream,
1004
- this.#playCalibrationAudioVolume,
1005
- this.#createCalibrationToneWithGainValue,
1006
- this.#sendToServerForProcessing,
1007
- gainToDiscard,
1008
- lCalib //todo make this a class parameter
1009
- );
1010
- } while (this.outDBSPL === null);
1011
- //reset the values
1012
- //this.incrementStatusBar();
1013
-
1014
- this.outDBSPL = null;
1015
- this.outDBSPL = null;
1016
- this.outDBSPL1000 = null;
1017
- this.THD = null;
1018
-
1019
- // run the calibration at different gain values provided by the user
1020
- for (let i = 0; i < trialIterations; i++) {
1021
- //convert gain to DB and add to inDB
1022
- inDB = Math.log10(gainValues[i]) * 20;
1023
- // precision to 1 decimal place
1024
- inDB = Math.round(inDB * 10) / 10;
1025
- inDBValues.push(inDB);
1026
- console.log('next update');
1027
- this.status =
1028
- `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() +
1029
- this.generateTemplate().toString();
1030
- this.emit('update', {message: this.status});
1031
- do {
1032
- // eslint-disable-next-line no-await-in-loop
1033
- await this.volumeCalibrationSteps(
1034
- stream,
1035
- this.#playCalibrationAudioVolume,
1036
- this.#createCalibrationToneWithGainValue,
1037
- this.#sendToServerForProcessing,
1038
- gainValues[i],
1039
- lCalib //todo make this a class parameter
1040
- );
1041
- } while (this.outDBSPL === null);
1042
- outDBSPL1000Values.push(this.outDBSPL1000);
1043
- thdValues.push(this.THD);
1044
- outDBSPLValues.push(this.outDBSPL);
1045
-
1046
- this.outDBSPL = null;
1047
- this.outDBSPL1000 = null;
1048
- this.THD = null;
1049
- }
1050
-
1051
- // get the volume calibration parameters from the server
1052
- const parameters = await this.pyServerAPI
1053
- .getVolumeCalibrationParameters({
1054
- inDBValues: inDBValues,
1055
- outDBSPLValues: outDBSPL1000Values,
1056
- lCalib: lCalib,
1057
- componentGainDBSPL,
1058
- })
1059
- .then(res => {
1060
- this.incrementStatusBar();
1061
- return res;
1062
- });
1063
- const result = {
1064
- parameters: parameters,
1065
- inDBValues: inDBValues,
1066
- outDBSPLValues: outDBSPLValues,
1067
- outDBSPL1000Values: outDBSPL1000Values,
1068
- thdValues: thdValues,
1069
- };
1070
-
1071
- return result;
1072
- };
1073
-
1074
- // function to write frq and gain to firebase database given speakerID
1075
- writeFrqGain = async (speakerID, frq, gain, OEM) => {
1076
- // freq and gain are too large to take samples 1 in every 100 samples
1077
-
1078
- const sampledFrq = [];
1079
- const sampledGain = [];
1080
- for (let i = 0; i < frq.length; i += 100) {
1081
- sampledFrq.push(frq[i]);
1082
- sampledGain.push(gain[i]);
1083
- }
1084
-
1085
- const data = {Freq: sampledFrq, Gain: sampledGain};
1086
-
1087
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/linear`), data);
1088
- };
1089
-
1090
- // Function to Read frq and gain from firebase database given speakerID
1091
- // returns an array of frq and gain if speakerID exists, returns null otherwise
1092
-
1093
- readFrqGain = async (speakerID, OEM) => {
1094
- const dbRef = ref(database);
1095
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/linear`));
1096
- if (snapshot.exists()) {
1097
- return snapshot.val();
1098
- }
1099
- return null;
1100
- };
1101
-
1102
- readGainat1000Hz = async (speakerID, OEM) => {
1103
- const dbRef = ref(database);
1104
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/Gain1000`));
1105
- if (snapshot.exists()) {
1106
- return snapshot.val();
1107
- }
1108
- return null;
1109
- };
1110
-
1111
- writeGainat1000Hz = async (speakerID, gain, OEM) => {
1112
- const data = {Gain: gain};
1113
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/Gain1000`), gain);
1114
- };
1115
-
1116
- writeIsSmartPhone = async (speakerID, isSmartPhone, OEM) => {
1117
- const data = {isSmartPhone: isSmartPhone};
1118
- await set(ref(database, `Microphone2/${OEM}/${speakerID}/isSmartPhone`), isSmartPhone);
1119
- };
1120
-
1121
- doesMicrophoneExist = async (speakerID, OEM) => {
1122
- const dbRef = ref(database);
1123
- const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}`));
1124
- if (snapshot.exists()) {
1125
- return true;
1126
- }
1127
- return false;
1128
- };
1129
-
1130
- convertToDB = gain => {
1131
- return Math.log10(gain) * 20;
1132
- };
1133
-
1134
- // Function to perform linear interpolation between two points
1135
- interpolate(x, x0, y0, x1, y1) {
1136
- return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);
1137
- }
1138
-
1139
- findGainatFrequency = (frequencies, gains, targetFrequency) => {
1140
- // Find the index of the first frequency in the array greater than the target frequency
1141
- let index = 0;
1142
- while (index < frequencies.length && frequencies[index] < targetFrequency) {
1143
- index++;
1144
- }
1145
-
1146
- // Handle cases when the target frequency is outside the range of the given data
1147
- if (index === 0) {
1148
- return gains[0];
1149
- } else if (index === frequencies.length) {
1150
- return gains[gains.length - 1];
1151
- } else {
1152
- // Interpolate the gain based on the surrounding frequencies
1153
- const x0 = frequencies[index - 1];
1154
- const y0 = gains[index - 1];
1155
- const x1 = frequencies[index];
1156
- const y1 = gains[index];
1157
- return this.interpolate(targetFrequency, x0, y0, x1, y1);
1158
- }
1159
- };
1160
-
1161
- // Example of how to use the writeFrqGain and readFrqGain functions
1162
- // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);
1163
- // Speaker1 is the speakerID you want to write to in the database
1164
- // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));
1165
- // MiniDSPUMIK_1 is the speakerID with some Data in the database
1166
- //adding gainDBSPL
1167
- startCalibration = async (
1168
- stream,
1169
- gainValues,
1170
- lCalib = 104.92978421490648,
1171
- componentIR = null,
1172
- microphoneName = 'MiniDSP-UMIK1-711-4754-vertical',
1173
- _calibrateSoundCheck = 'goal', //GOAL PASSed in by default
1174
- isSmartPhone = false,
1175
- _calibrateSoundBurstRepeats = 4,
1176
- _calibrateSoundBurstSec = 1,
1177
- _calibrateSoundBurstsWarmup = 0,
1178
- _calibrateSoundHz = 48000,
1179
- _calibrateSoundIIRSec = 0.2,
1180
- micManufacturer = '',
1181
- micSerialNumber = '',
1182
- micModelNumber = '',
1183
- micModelName = ''
1184
- ) => {
1185
- this.iirLength = Math.floor(_calibrateSoundIIRSec*this.sourceSamplingRate);
1186
- console.log('device info:', this.deviceInfo);
1187
- this.numMLSPerCapture = _calibrateSoundBurstRepeats;
1188
- this.desired_time_per_mls = _calibrateSoundBurstSec;
1189
- this.num_mls_to_skip = _calibrateSoundBurstsWarmup;
1190
- this.desired_sampling_rate = _calibrateSoundHz;
1191
-
1192
- //feed calibration goal here
1193
- this._calibrateSoundCheck = _calibrateSoundCheck;
1194
- //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
1195
- //check the db based on the microphone currently connected
1196
-
1197
- //new lCalib found at top of calibration files *1000hz, make sure to correct
1198
- //based on zeroing of 1000hz, search for "*1000Hz"
1199
- const ID = isSmartPhone ? micModelNumber : micSerialNumber;
1200
- const OEM = isSmartPhone ? this.deviceInfo.OEM : micManufacturer;
1201
- if (componentIR == null) {
1202
- //mode 'ir'
1203
- //global variable this.componentIR must be set
1204
- this.componentIR = await this.readFrqGain(ID, OEM).then(data => {
1205
- return data;
1206
- });
1207
-
1208
- lCalib = await this.readGainat1000Hz(ID, OEM);
1209
- // this.componentGainDBSPL = this.convertToDB(lCalib);
1210
- this.componentGainDBSPL = lCalib;
1211
- //TODO: if this call to database is unknown, cannot perform experiment => return false
1212
- if (this.componentIR == null) {
1213
- this.status =
1214
- `Microphone (${OEM},${ID}) is not found in the database. Please add it to the database.`.toString();
1215
- this.emit('update', {message: this.status});
1216
- return false;
1217
- }
1218
- } else {
1219
- this.componentIR = componentIR;
1220
- lCalib = this.findGainatFrequency(this.componentIR.Freq, this.componentIR.Gain, 1000);
1221
- // this.componentGainDBSPL = this.convertToDB(lCalib);
1222
- this.componentGainDBSPL = lCalib;
1223
- await this.writeGainat1000Hz(ID, lCalib, OEM);
1224
- await this.writeIsSmartPhone(ID, isSmartPhone, OEM);
1225
- }
1226
-
1227
- this.oldComponentIR = this.componentIR;
1228
-
1229
- let volumeResults = await this.startCalibrationVolume(
1230
- stream,
1231
- gainValues,
1232
- lCalib,
1233
- this.componentGainDBSPL
1234
- );
1235
-
1236
- let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);
1237
-
1238
- if (componentIR != null) {
1239
- //insert Freq and Gain from this.componentIR into db
1240
- await this.writeFrqGain(
1241
- ID,
1242
- impulseResponseResults.componentIR.Freq,
1243
- impulseResponseResults.componentIR.Gain,
1244
- OEM
1245
- );
1246
- }
1247
-
1248
- const total_results = {...volumeResults, ...impulseResponseResults};
1249
-
1250
- total_results['micInfo'] = {
1251
- micManufacturer: micManufacturer,
1252
- micSerialNumber: micSerialNumber,
1253
- micModelNumber: micModelNumber,
1254
- micModelName: micModelName,
1255
- ID: ID,
1256
- OEM: OEM,
1257
- };
1258
- console.log('total results');
1259
- console.log(total_results);
1260
- return total_results;
1261
- };
1262
- }
1263
-
1264
- 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
+
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
+ /**generate string template that gets reevaluated as variable increases */
162
+ generateTemplate = () => {
163
+ if (this.percent_complete > 100) {
164
+ this.percent_complete = 100;
165
+ }
166
+ 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>`;
167
+ return template;
168
+ };
169
+
170
+ /** increment numerator and percent for status bar */
171
+ incrementStatusBar = () => {
172
+ this.status_numerator += 1;
173
+ this.percent_complete = (this.status_numerator / this.status_denominator) * 100;
174
+ };
175
+
176
+ setDeviceType = deviceType => {
177
+ this.deviceType = deviceType;
178
+ };
179
+
180
+ setDeviceName = deviceName => {
181
+ this.deviceName = deviceName;
182
+ };
183
+
184
+ setDeviceInfo = deviceInfo => {
185
+ this.deviceInfo = deviceInfo;
186
+ };
187
+
188
+ /** .
189
+ * .
190
+ * .
191
+ * Sends all the computed impulse responses to the backend server for processing
192
+ *
193
+ * @returns sets the resulting inverted impulse response to the class property
194
+ * @example
195
+ */
196
+ sendSystemImpulseResponsesToServerForProcessing = async () => {
197
+ const computedIRs = await Promise.all(this.impulseResponses);
198
+ const filteredComputedIRs = computedIRs.filter(element => {
199
+ return element != undefined;
200
+ });
201
+ const mls = this.#mls;
202
+ const lowHz = this.#lowHz;
203
+ const highHz = this.#highHz;
204
+ const iirLength = this.iirLength;
205
+ const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
206
+ this.stepNum += 1;
207
+ console.log('send impulse responses to server: ' + this.stepNum);
208
+ this.status =
209
+ `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
210
+ this.emit('update', {message: this.status});
211
+ return this.pyServerAPI
212
+ .getSystemInverseImpulseResponseWithRetry({
213
+ payload: filteredComputedIRs.slice(0, this.numCaptures),
214
+ mls,
215
+ lowHz,
216
+ highHz,
217
+ iirLength,
218
+ num_periods,
219
+ sampleRate: this.sourceSamplingRate || 96000,
220
+ })
221
+ .then(res => {
222
+ console.log(res);
223
+ this.stepNum += 1;
224
+ console.log('got impulse response ' + this.stepNum);
225
+ this.incrementStatusBar();
226
+ this.status =
227
+ `All Hz Calibration: done computing the IIR...`.toString() +
228
+ this.generateTemplate().toString();
229
+ this.emit('update', {message: this.status});
230
+ this.systemInvertedImpulseResponse = res['iir'];
231
+ this.systemIR = res['ir'];
232
+ this.systemConvolution = res['convolution'];
233
+ })
234
+ .catch(err => {
235
+ console.error(err);
236
+ });
237
+ };
238
+
239
+ /** .
240
+ * .
241
+ * .
242
+ * Sends all the computed impulse responses to the backend server for processing
243
+ *
244
+ * @returns sets the resulting inverted impulse response to the class property
245
+ * @example
246
+ */
247
+ sendComponentImpulseResponsesToServerForProcessing = async () => {
248
+ const computedIRs = await Promise.all(this.impulseResponses);
249
+ const filteredComputedIRs = computedIRs.filter(element => {
250
+ return element != undefined;
251
+ });
252
+ const componentIRGains = this.componentIR['Gain'];
253
+ const componentIRFreqs = this.componentIR['Freq'];
254
+ const mls = this.#mls;
255
+ const lowHz = this.#lowHz;
256
+ const iirLength = this.iirLength;
257
+ const num_periods = this.numMLSPerCapture + this.num_mls_to_skip;
258
+ const highHz = this.#highHz;
259
+ this.stepNum += 1;
260
+ console.log('send impulse responses to server: ' + this.stepNum);
261
+ this.status =
262
+ `All Hz Calibration: computing the IIR...`.toString() + this.generateTemplate().toString();
263
+ this.emit('update', {message: this.status});
264
+ return this.pyServerAPI
265
+ .getComponentInverseImpulseResponseWithRetry({
266
+ payload: filteredComputedIRs.slice(0, this.numCaptures),
267
+ mls,
268
+ lowHz,
269
+ highHz,
270
+ iirLength,
271
+ componentIRGains,
272
+ componentIRFreqs,
273
+ num_periods,
274
+ sampleRate: this.sourceSamplingRate || 96000,
275
+ })
276
+ .then(res => {
277
+ console.log(res);
278
+ this.stepNum += 1;
279
+ console.log('got impulse response ' + this.stepNum);
280
+ this.incrementStatusBar();
281
+ this.status =
282
+ `All Hz Calibration: done computing the IIR...`.toString() +
283
+ this.generateTemplate().toString();
284
+ this.emit('update', {message: this.status});
285
+ this.componentInvertedImpulseResponse = res['iir'];
286
+ this.componentIR['Gain'] = res['ir'];
287
+ this.componentIR['Freq'] = res['frequencies'];
288
+ this.componentConvolution = res['convolution'];
289
+ })
290
+ .catch(err => {
291
+ // this.emit('InvertedImpulseResponse', {res: false});
292
+ console.error(err);
293
+ });
294
+ };
295
+
296
+ /** .
297
+ * .
298
+ * .
299
+ * Sends the recorded signal, or a given csv string of a signal, to the back end server for processing
300
+ *
301
+ * @param {<array>String} signalCsv - Optional csv string of a previously recorded signal, if given, this signal will be processed
302
+ * @example
303
+ */
304
+ sendRecordingToServerForProcessing = signalCsv => {
305
+ const allSignals = this.getAllRecordedSignals();
306
+ const numSignals = allSignals.length;
307
+ const mls = this.#mls;
308
+ const payload =
309
+ signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
310
+ console.log('sending rec');
311
+ this.stepNum += 1;
312
+ console.log('send rec ' + this.stepNum);
313
+ this.status =
314
+ `All Hz Calibration Step: computing the IR of the last recording...`.toString() +
315
+ this.generateTemplate().toString();
316
+ this.emit('update', {message: this.status});
317
+ this.impulseResponses.push(
318
+ this.pyServerAPI
319
+ .getImpulseResponse({
320
+ sampleRate: this.sourceSamplingRate || 96000,
321
+ payload,
322
+ mls,
323
+ P: this.#P,
324
+ numPeriods: this.numMLSPerCapture,
325
+ })
326
+ .then(res => {
327
+ if (this.numSuccessfulCaptured < this.numCaptures) {
328
+ this.numSuccessfulCaptured += 1;
329
+ console.log('num succ capt: ' + this.numSuccessfulCaptured);
330
+ this.stepNum += 1;
331
+ console.log('got impulse response ' + this.stepNum);
332
+ this.incrementStatusBar();
333
+ this.status =
334
+ `All Hz Calibration: ${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`.toString() +
335
+ this.generateTemplate().toString();
336
+ this.emit('update', {
337
+ message: this.status,
338
+ });
339
+ this.autocorrelations.push(res['autocorrelation']);
340
+ return res['ir'];
341
+ }
342
+ })
343
+ .catch(err => {
344
+ console.error(err);
345
+ })
346
+ );
347
+ };
348
+
349
+ /**
350
+ * Passed to the calibration steps function, awaits the desired amount of seconds to capture the desired number
351
+ * of MLS periods defined in the constructor.
352
+ *
353
+ * @example
354
+ */
355
+ #awaitDesiredMLSLength = async () => {
356
+ // seconds per MLS = P / SR
357
+ // await N * P / SR
358
+ this.stepNum += 1;
359
+ console.log('await desired length ' + this.stepNum);
360
+ this.status =
361
+ `All Hz Calibration: sampling the calibration signal...`.toString() +
362
+ `\niteration ${this.stepNum}` +
363
+ this.generateTemplate();
364
+ this.emit('update', {
365
+ message: this.status,
366
+ });
367
+ let time_to_wait = 0;
368
+ if (this.mode === 'unfiltered') {
369
+ time_to_wait = (this.#mls.length / this.sourceSamplingRate) * this.numMLSPerCapture;
370
+ time_to_wait = time_to_wait * 1.1;
371
+ } else if (this.mode === 'filtered') {
372
+ time_to_wait =
373
+ (this.#currentConvolution.length / this.sourceSamplingRate) *
374
+ (this.numMLSPerCapture / (this.num_mls_to_skip + this.numMLSPerCapture));
375
+ } else {
376
+ throw new Error('Mode broke in awaitDesiredMLSLength');
377
+ }
378
+
379
+ await sleep(time_to_wait);
380
+ };
381
+
382
+ /** .
383
+ * .
384
+ * .
385
+ * Passed to the calibration steps function, awaits the onset of the signal to ensure a steady state
386
+ *
387
+ * @example
388
+ */
389
+ #awaitSignalOnset = async () => {
390
+ this.stepNum += 1;
391
+ console.log('await signal onset ' + this.stepNum);
392
+ this.status =
393
+ `All Hz Calibration: waiting for the signal to stabilize...`.toString() +
394
+ this.generateTemplate();
395
+ this.emit('update', {
396
+ message: this.status,
397
+ });
398
+ let number_of_bursts_to_skip = this.num_mls_to_skip;
399
+ let time_to_sleep = 0;
400
+ if (this.mode === 'unfiltered') {
401
+ time_to_sleep = (this.#mls.length / this.sourceSamplingRate) * number_of_bursts_to_skip;
402
+ } else if (this.mode === 'filtered') {
403
+ console.log(this.#currentConvolution.length);
404
+ time_to_sleep =
405
+ (this.#currentConvolution.length / this.sourceSamplingRate) *
406
+ (number_of_bursts_to_skip / (number_of_bursts_to_skip + this.numMLSPerCapture));
407
+ } else {
408
+ throw new Error('Mode broke in awaitSignalOnset');
409
+ }
410
+ await sleep(time_to_sleep);
411
+ };
412
+
413
+ /**
414
+ * Called immediately after a recording is captured. Used to process the resulting signal
415
+ * whether by sending the result to a server or by computing a result locally.
416
+ *
417
+ * @example
418
+ */
419
+ #afterMLSRecord = () => {
420
+ console.log('after record');
421
+ this.sendRecordingToServerForProcessing();
422
+ };
423
+
424
+ #afterMLSwIIRRecord = () => {
425
+ if (this.numSuccessfulCaptured < 1) {
426
+ this.numSuccessfulCaptured += 1;
427
+ this.stepNum += 1;
428
+ this.incrementStatusBar();
429
+ console.log('after mls w iir record for some reason add numSucc capt ' + this.stepNum);
430
+ this.status =
431
+ `All Hz Calibration: ${this.numSuccessfulCaptured} recording of convolved MLS captured`.toString() +
432
+ this.generateTemplate().toString();
433
+ this.emit('update', {
434
+ message: this.status,
435
+ });
436
+ }
437
+ };
438
+
439
+ /** .
440
+ * .
441
+ * .
442
+ * Created an S Curver Buffer to taper the signal onset
443
+ *
444
+ * @param {*} length
445
+ * @param {*} phase
446
+ * @returns
447
+ * @example
448
+ */
449
+ static createSCurveBuffer = (length, phase) => {
450
+ const curve = new Float32Array(length);
451
+ let i;
452
+ for (i = 0; i < length; i += 1) {
453
+ // scale the curve to be between 0-1
454
+ curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;
455
+ }
456
+ return curve;
457
+ };
458
+
459
+ static createInverseSCurveBuffer = (length, phase) => {
460
+ const curve = new Float32Array(length);
461
+ let i;
462
+ let j = length - 1;
463
+ for (i = 0; i < length; i += 1) {
464
+ // scale the curve to be between 0-1
465
+ curve[i] = Math.sin((Math.PI * j) / length - phase) / 2 + 0.5;
466
+ j -= 1;
467
+ }
468
+ return curve;
469
+ };
470
+
471
+ /**
472
+ * Construct a Calibration Node with the calibration parameters.
473
+ *
474
+ * @param dataBuffer
475
+ * @private
476
+ * @example
477
+ */
478
+ #createCalibrationNodeFromBuffer = dataBuffer => {
479
+ console.log('databuffer');
480
+ console.log(dataBuffer);
481
+ if (!this.sourceAudioContext) {
482
+ this.makeNewSourceAudioContext();
483
+ }
484
+
485
+ const buffer = this.sourceAudioContext.createBuffer(
486
+ 1, // number of channels
487
+ dataBuffer.length,
488
+ this.sourceAudioContext.sampleRate // sample rate
489
+ );
490
+
491
+ const data = buffer.getChannelData(0); // get data
492
+ // fill the buffer with our data
493
+ try {
494
+ for (let i = 0; i < dataBuffer.length; i += 1) {
495
+ data[i] = dataBuffer[i] * 0.1;
496
+ }
497
+ } catch (error) {
498
+ console.error(error);
499
+ }
500
+
501
+ this.sourceNode = this.sourceAudioContext.createBufferSource();
502
+
503
+ this.sourceNode.buffer = buffer;
504
+ if (this.mode === 'filtered') {
505
+ this.sourceNode.loop = false;
506
+ } else {
507
+ this.sourceNode.loop = true;
508
+ }
509
+
510
+ this.sourceNode.connect(this.sourceAudioContext.destination);
511
+
512
+ this.addCalibrationNode(this.sourceNode);
513
+ };
514
+
515
+ /**
516
+ * Given a data buffer, creates the required calibration node
517
+ *
518
+ * @param {*} dataBufferArray
519
+ * @example
520
+ */
521
+ #setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {
522
+ if (dataBufferArray.length === 1) {
523
+ this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);
524
+ } else {
525
+ throw new Error('The length of the data buffer array must be 1');
526
+ }
527
+ };
528
+
529
+ /**
530
+ * Creates an audio context and plays it for a few seconds.
531
+ *
532
+ * @private
533
+ * @returns - Resolves when the audio is done playing.
534
+ * @example
535
+ */
536
+ #playCalibrationAudio = () => {
537
+ this.calibrationNodes[0].start(0);
538
+ this.status = ``;
539
+ if (this.mode === 'unfiltered') {
540
+ this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);
541
+ console.log('play calibration audio ' + this.stepNum);
542
+ this.status =
543
+ `All Hz Calibration: playing the calibration tone...`.toString() +
544
+ this.generateTemplate().toString();
545
+ } else if (this.mode === 'filtered') {
546
+ console.log('play convolved audio ' + this.stepNum);
547
+ this.status =
548
+ `All Hz Calibration: playing the convolved calibration tone...`.toString() +
549
+ this.generateTemplate().toString();
550
+ } else {
551
+ throw new Error('Mode is incorrect');
552
+ }
553
+ this.emit('update', {message: this.status});
554
+ this.stepNum += 1;
555
+ console.log('sink sampling rate');
556
+ console.log(this.sinkSamplingRate);
557
+ console.log('source sampling rate');
558
+ console.log(this.sourceSamplingRate);
559
+ };
560
+
561
+ /** .
562
+ * .
563
+ * .
564
+ * Stops the audio with tapered offset
565
+ *
566
+ * @example
567
+ */
568
+ #stopCalibrationAudio = () => {
569
+ this.calibrationNodes[0].stop(0);
570
+ this.calibrationNodes = [];
571
+ this.sourceNode.disconnect();
572
+ this.stepNum += 1;
573
+ console.log('stop calibration audio ' + this.stepNum);
574
+ this.status =
575
+ `All Hz Calibration: stopping the calibration tone...`.toString() +
576
+ this.generateTemplate().toString();
577
+ this.emit('update', {message: this.status});
578
+ };
579
+
580
+ playMLSwithIIR = async (stream, iir) => {
581
+ this.mode = 'filtered';
582
+ console.log('play mls with iir');
583
+ this.invertedImpulseResponse = iir;
584
+
585
+ await this.calibrationSteps(
586
+ stream,
587
+ this.#playCalibrationAudio, // play audio func (required)
588
+ this.#createCalibrationNodeFromBuffer(this.#currentConvolution), // before play func
589
+ this.#awaitSignalOnset, // before record
590
+ () => this.numSuccessfulCaptured < 1,
591
+ this.#awaitDesiredMLSLength, // during record
592
+ this.#afterMLSwIIRRecord, // after record
593
+ this.mode
594
+ );
595
+ };
596
+
597
+ /**
598
+ * Public method to start the calibration process. Objects intialized from webassembly allocate new memory
599
+ * and must be manually freed. This function is responsible for intializing the MlsGenInterface,
600
+ * and wrapping the calibration steps with a garbage collection safe gaurd.
601
+ *
602
+ * @public
603
+ * @param stream - The stream of audio from the Listener.
604
+ * @example
605
+ */
606
+ startCalibrationImpulseResponse = async stream => {
607
+ let desired_time = this.desired_time_per_mls;
608
+
609
+ console.log('MLS sequence should be of length: ' + this.sourceSamplingRate * desired_time);
610
+
611
+ length = this.sourceSamplingRate * desired_time;
612
+ //get mls here
613
+ await this.pyServerAPI
614
+ .getMLSWithRetry(length)
615
+ .then(res => {
616
+ console.log(res);
617
+ this.#mlsBufferView = res['mls'];
618
+ })
619
+ .catch(err => {
620
+ // this.emit('InvertedImpulseResponse', {res: false});
621
+ console.error(err);
622
+ });
623
+ await this.calibrationSteps(
624
+ stream,
625
+ this.#playCalibrationAudio, // play audio func (required)
626
+ this.#createCalibrationNodeFromBuffer(this.#mlsBufferView), // before play func
627
+ this.#awaitSignalOnset, // before record
628
+ () => this.numSuccessfulCaptured < this.numCaptures, // loop while true
629
+ this.#awaitDesiredMLSLength, // during record
630
+ this.#afterMLSRecord, // after record
631
+ this.mode
632
+ ),
633
+ this.#stopCalibrationAudio();
634
+
635
+ // at this stage we've captured all the required signals,
636
+ // and have received IRs for each one
637
+ // so let's send all the IRs to the server to be converted to a single IIR
638
+ await this.sendSystemImpulseResponsesToServerForProcessing();
639
+ await this.sendComponentImpulseResponsesToServerForProcessing();
640
+
641
+ this.numSuccessfulCaptured = 0;
642
+
643
+ let iir_ir_and_plots;
644
+ if (this._calibrateSoundCheck != 'none') {
645
+ if (this._calibrateSoundCheck != 'system') {
646
+ this.#currentConvolution = this.componentConvolution;
647
+ } else {
648
+ this.#currentConvolution = this.systemConvolution;
649
+ }
650
+ await this.playMLSwithIIR(stream, this.invertedImpulseResponse);
651
+ this.#stopCalibrationAudio();
652
+ this.sourceAudioContext.close();
653
+ let conv_recs = this.getAllFilteredRecordedSignals();
654
+ let recs = this.getAllRecordedSignals();
655
+ let unconv_rec = recs[0];
656
+ let conv_rec = conv_recs[0];
657
+ if (this._calibrateSoundCheck != 'system') {
658
+ let knownGain = this.oldComponentIR.Gain;
659
+ let knownFreq = this.oldComponentIR.Freq;
660
+ let sampleRate = this.sourceSamplingRate || 96000;
661
+ let unconv_results = await this.pyServerAPI
662
+ .getSubtractedPSDWithRetry(unconv_rec, knownGain, knownFreq, sampleRate)
663
+ .then(res => {
664
+ this.incrementStatusBar();
665
+ this.status =
666
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
667
+ this.generateTemplate().toString();
668
+ this.emit('update', {message: this.status});
669
+ return res;
670
+ })
671
+ .catch(err => {
672
+ console.error(err);
673
+ });
674
+
675
+ let conv_results = await this.pyServerAPI
676
+ .getSubtractedPSDWithRetry(conv_rec, knownGain, knownFreq, sampleRate)
677
+ .then(res => {
678
+ this.incrementStatusBar();
679
+ this.status =
680
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
681
+ this.generateTemplate().toString();
682
+ this.emit('update', {message: this.status});
683
+ return res;
684
+ })
685
+ .catch(err => {
686
+ console.error(err);
687
+ });
688
+ iir_ir_and_plots = {
689
+ systemIIR: this.systemInvertedImpulseResponse,
690
+ componentIIR: this.componentInvertedImpulseResponse,
691
+ x_unconv: unconv_results['x'],
692
+ y_unconv: unconv_results['y'],
693
+ x_conv: conv_results['x'],
694
+ y_conv: conv_results['y'],
695
+ componentIR: this.componentIR,
696
+ systemIR: this.systemIR,
697
+ };
698
+ } else {
699
+ let results = await this.pyServerAPI
700
+ .getPSDWithRetry({
701
+ unconv_rec,
702
+ conv_rec,
703
+ sampleRate: this.sourceSamplingRate || 96000,
704
+ })
705
+ .then(res => {
706
+ this.incrementStatusBar();
707
+ this.status =
708
+ `All Hz Calibration: done computing the PSD graphs...`.toString() +
709
+ this.generateTemplate().toString();
710
+ this.emit('update', {message: this.status});
711
+ return res;
712
+ })
713
+ .catch(err => {
714
+ console.error(err);
715
+ });
716
+ iir_ir_and_plots = {
717
+ systemIIR: this.systemInvertedImpulseResponse,
718
+ componentIIR: this.componentInvertedImpulseResponse,
719
+ x_unconv: results['x_unconv'],
720
+ y_unconv: results['y_unconv'],
721
+ x_conv: results['x_conv'],
722
+ y_conv: results['y_conv'],
723
+ componentIR: this.componentIR,
724
+ systemIR: this.systemIR,
725
+ };
726
+ }
727
+
728
+ if (this.#download) {
729
+ this.downloadSingleUnfilteredRecording();
730
+ this.downloadSingleFilteredRecording();
731
+ saveToCSV(this.#mls, 'MLS.csv');
732
+ saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
733
+ saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
734
+ saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
735
+ saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
736
+ for (let i = 0; i < this.autocorrelations.length; i++) {
737
+ saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
738
+ }
739
+ const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
740
+ for (let i = 0; i < res.length; i++) {
741
+ if (res[i] != undefined) {
742
+ saveToCSV(res[i], `IR_${i}`);
743
+ }
744
+ }
745
+ });
746
+ }
747
+ } else {
748
+ iir_ir_and_plots = {
749
+ systemIIR: this.systemInvertedImpulseResponse,
750
+ componentIIR: this.componentInvertedImpulseResponse,
751
+ x_unconv: [],
752
+ y_unconv: [],
753
+ x_conv: [],
754
+ y_conv: [],
755
+ componentIR: this.componentIR,
756
+ systemIR: this.systemIR,
757
+ };
758
+ if (this.#download) {
759
+ saveToCSV(this.#mls, 'MLS.csv');
760
+ saveToCSV(this.componentConvolution, 'python_component_convolution_mls_iir.csv');
761
+ saveToCSV(this.systemConvolution, 'python_system_convolution_mls_iir.csv');
762
+ saveToCSV(this.componentInvertedImpulseResponse, 'componentIIR.csv');
763
+ saveToCSV(this.systemInvertedImpulseResponse, 'systemIIR.csv');
764
+ for (let i = 0; i < this.autocorrelations.length; i++) {
765
+ saveToCSV(this.autocorrelations[i], `autocorrelation_${i}`);
766
+ }
767
+ const computedIRagain = await Promise.all(this.impulseResponses).then(res => {
768
+ for (let i = 0; i < res.length; i++) {
769
+ if (res[i] != undefined) {
770
+ saveToCSV(res[i], `IR_${i}`);
771
+ }
772
+ }
773
+ });
774
+ }
775
+ }
776
+
777
+ this.percent_complete = 100;
778
+
779
+ this.status = `All Hz Calibration: Finished`.toString() + this.generateTemplate().toString();
780
+ this.emit('update', {message: this.status});
781
+
782
+ //here after calibration we have the component calibration (either loudspeaker or microphone) in the same form as the componentIR
783
+ //that was used to calibrate
784
+
785
+ return iir_ir_and_plots;
786
+ };
787
+
788
+ //////////////////////volume
789
+
790
+ handleIncomingData = data => {
791
+ console.log('Received data: ', data);
792
+ if (data.type === 'soundGainDBSPL') {
793
+ this.soundGainDBSPL = data.value;
794
+ } else {
795
+ throw new Error(`Unknown data type: ${data.type}`);
796
+ }
797
+ };
798
+ createSCurveBuffer = (onSetBool = true) => {
799
+ const curve = new Float32Array(this.TAPER_SECS * this.sourceSamplingRate + 1);
800
+ const frequency = 1 / (4 * this.TAPER_SECS);
801
+ let j = 0;
802
+ for (let i = 0; i < this.TAPER_SECS * this.sourceSamplingRate + 1; i += 1) {
803
+ const phase = 2 * Math.PI * frequency * j;
804
+ const onsetTaper = Math.pow(Math.sin(phase), 2);
805
+ const offsetTaper = Math.pow(Math.cos(phase), 2);
806
+ curve[i] = onSetBool ? onsetTaper : offsetTaper;
807
+ j += 1 / this.sourceSamplingRate;
808
+ }
809
+ return curve;
810
+ };
811
+
812
+ #getTruncatedSignal = (left = 3.5, right = 4.5) => {
813
+ const start = Math.floor(left * this.sourceSamplingRate);
814
+ const end = Math.floor(right * this.sourceSamplingRate);
815
+ const result = Array.from(this.getLastRecordedSignal().slice(start, end));
816
+
817
+ /**
818
+ * function to check that capture was properly made
819
+ * @param {*} list
820
+ */
821
+ const checkResult = list => {
822
+ const setItem = new Set(list);
823
+ if (setItem.size === 1 && setItem.has(0)) {
824
+ console.warn(
825
+ 'The last capture failed, all recorded signal is zero',
826
+ this.getAllRecordedSignals()
827
+ );
828
+ }
829
+ if (setItem.size === 0) {
830
+ console.warn('The last capture failed, no recorded signal');
831
+ }
832
+ };
833
+ checkResult(result);
834
+ return result;
835
+ };
836
+
837
+ /**
838
+ *
839
+ *
840
+ Construct a calibration Node with the calibration parameters and given gain value
841
+ * @param {*} gainValue
842
+ * */
843
+ #createCalibrationToneWithGainValue = gainValue => {
844
+ const audioContext = this.makeNewSourceAudioContext();
845
+ const oscilator = audioContext.createOscillator();
846
+ const gainNode = audioContext.createGain();
847
+ const taperGainNode = audioContext.createGain();
848
+ const offsetGainNode = audioContext.createGain();
849
+ const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
850
+
851
+ oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
852
+ oscilator.type = this.#CALIBRATION_TONE_TYPE;
853
+ gainNode.gain.value = gainValue;
854
+
855
+ oscilator.connect(gainNode);
856
+ gainNode.connect(taperGainNode);
857
+ const onsetCurve = this.createSCurveBuffer();
858
+ taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
859
+ taperGainNode.connect(offsetGainNode);
860
+ const offsetCurve = this.createSCurveBuffer(false);
861
+ offsetGainNode.gain.setValueCurveAtTime(
862
+ offsetCurve,
863
+ totalDuration - this.TAPER_SECS,
864
+ this.TAPER_SECS
865
+ );
866
+ offsetGainNode.connect(audioContext.destination);
867
+
868
+ this.addCalibrationNode(oscilator);
869
+ };
870
+
871
+ /**
872
+ * Construct a Calibration Node with the calibration parameters.
873
+ *
874
+ * @private
875
+ * @example
876
+ */
877
+ #createCalibrationNode = () => {
878
+ const audioContext = this.makeNewSourceAudioContext();
879
+ const oscilator = audioContext.createOscillator();
880
+ const gainNode = audioContext.createGain();
881
+
882
+ oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
883
+ oscilator.type = this.#CALIBRATION_TONE_TYPE;
884
+ gainNode.gain.value = 0.04;
885
+
886
+ oscilator.connect(gainNode);
887
+ gainNode.connect(audioContext.destination);
888
+
889
+ this.addCalibrationNode(oscilator);
890
+ };
891
+
892
+ #playCalibrationAudioVolume = async () => {
893
+ const totalDuration = this.CALIBRATION_TONE_DURATION * 1.2;
894
+
895
+ this.calibrationNodes[0].start(0);
896
+ this.calibrationNodes[0].stop(totalDuration);
897
+ console.log(`Playing a buffer of ${this.CALIBRATION_TONE_DURATION} seconds of audio`);
898
+ console.log(`Waiting a total of ${totalDuration} seconds`);
899
+ await sleep(totalDuration);
900
+ };
901
+
902
+ #sendToServerForProcessing = lCalib => {
903
+ console.log('Sending data to server');
904
+ this.pyServerAPI
905
+ .getVolumeCalibration({
906
+ sampleRate: this.sourceSamplingRate,
907
+ payload: this.#getTruncatedSignal(),
908
+ lCalib: lCalib,
909
+ })
910
+ .then(res => {
911
+ if (this.outDBSPL === null) {
912
+ this.incrementStatusBar();
913
+ this.outDBSPL = res['outDbSPL'];
914
+ this.outDBSPL1000 = res['outDbSPL1000'];
915
+ this.THD = res['thd'];
916
+ }
917
+ })
918
+ .catch(err => {
919
+ console.warn(err);
920
+ });
921
+ };
922
+
923
+ startCalibrationVolume = async (stream, gainValues, lCalib, componentGainDBSPL) => {
924
+ const trialIterations = gainValues.length;
925
+ this.status_denominator += trialIterations;
926
+ const thdValues = [];
927
+ const inDBValues = [];
928
+ let inDB = 0;
929
+ const outDBSPLValues = [];
930
+ const outDBSPL1000Values = [];
931
+
932
+ // do one calibration that will be discarded
933
+ const soundLevelToDiscard = -60;
934
+ const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);
935
+ this.status =
936
+ `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`.toString() +
937
+ this.generateTemplate().toString();
938
+ //this.emit('update', {message: `1000 Hz Calibration: Sound Level ${soundLevelToDiscard} dB`});
939
+ this.emit('update', {message: this.status});
940
+
941
+ do {
942
+ // eslint-disable-next-line no-await-in-loop
943
+ await this.volumeCalibrationSteps(
944
+ stream,
945
+ this.#playCalibrationAudioVolume,
946
+ this.#createCalibrationToneWithGainValue,
947
+ this.#sendToServerForProcessing,
948
+ gainToDiscard,
949
+ lCalib //todo make this a class parameter
950
+ );
951
+ } while (this.outDBSPL === null);
952
+ //reset the values
953
+ //this.incrementStatusBar();
954
+
955
+ this.outDBSPL = null;
956
+ this.outDBSPL = null;
957
+ this.outDBSPL1000 = null;
958
+ this.THD = null;
959
+
960
+ // run the calibration at different gain values provided by the user
961
+ for (let i = 0; i < trialIterations; i++) {
962
+ //convert gain to DB and add to inDB
963
+ inDB = Math.log10(gainValues[i]) * 20;
964
+ // precision to 1 decimal place
965
+ inDB = Math.round(inDB * 10) / 10;
966
+ inDBValues.push(inDB);
967
+ console.log('next update');
968
+ this.status =
969
+ `1000 Hz Calibration: Sound Level ${inDB} dB`.toString() +
970
+ this.generateTemplate().toString();
971
+ this.emit('update', {message: this.status});
972
+ do {
973
+ // eslint-disable-next-line no-await-in-loop
974
+ await this.volumeCalibrationSteps(
975
+ stream,
976
+ this.#playCalibrationAudioVolume,
977
+ this.#createCalibrationToneWithGainValue,
978
+ this.#sendToServerForProcessing,
979
+ gainValues[i],
980
+ lCalib //todo make this a class parameter
981
+ );
982
+ } while (this.outDBSPL === null);
983
+ outDBSPL1000Values.push(this.outDBSPL1000);
984
+ thdValues.push(this.THD);
985
+ outDBSPLValues.push(this.outDBSPL);
986
+
987
+ this.outDBSPL = null;
988
+ this.outDBSPL1000 = null;
989
+ this.THD = null;
990
+ }
991
+
992
+ // get the volume calibration parameters from the server
993
+ const parameters = await this.pyServerAPI
994
+ .getVolumeCalibrationParameters({
995
+ inDBValues: inDBValues,
996
+ outDBSPLValues: outDBSPL1000Values,
997
+ lCalib: lCalib,
998
+ componentGainDBSPL,
999
+ })
1000
+ .then(res => {
1001
+ this.incrementStatusBar();
1002
+ return res;
1003
+ });
1004
+ const result = {
1005
+ parameters: parameters,
1006
+ inDBValues: inDBValues,
1007
+ outDBSPLValues: outDBSPLValues,
1008
+ outDBSPL1000Values: outDBSPL1000Values,
1009
+ thdValues: thdValues,
1010
+ };
1011
+
1012
+ return result;
1013
+ };
1014
+
1015
+ // function to write frq and gain to firebase database given speakerID
1016
+ writeFrqGain = async (speakerID, frq, gain, OEM) => {
1017
+ // freq and gain are too large to take samples 1 in every 100 samples
1018
+
1019
+ const sampledFrq = [];
1020
+ const sampledGain = [];
1021
+ for (let i = 0; i < frq.length; i += 100) {
1022
+ sampledFrq.push(frq[i]);
1023
+ sampledGain.push(gain[i]);
1024
+ }
1025
+
1026
+ const data = {Freq: sampledFrq, Gain: sampledGain};
1027
+
1028
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/linear`), data);
1029
+ };
1030
+
1031
+ // Function to Read frq and gain from firebase database given speakerID
1032
+ // returns an array of frq and gain if speakerID exists, returns null otherwise
1033
+
1034
+ readFrqGain = async (speakerID, OEM) => {
1035
+ const dbRef = ref(database);
1036
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/linear`));
1037
+ if (snapshot.exists()) {
1038
+ return snapshot.val();
1039
+ }
1040
+ return null;
1041
+ };
1042
+
1043
+ readGainat1000Hz = async (speakerID, OEM) => {
1044
+ const dbRef = ref(database);
1045
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}/Gain1000`));
1046
+ if (snapshot.exists()) {
1047
+ return snapshot.val();
1048
+ }
1049
+ return null;
1050
+ };
1051
+
1052
+ writeGainat1000Hz = async (speakerID, gain, OEM) => {
1053
+ const data = {Gain: gain};
1054
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/Gain1000`), gain);
1055
+ };
1056
+
1057
+ writeIsSmartPhone = async (speakerID, isSmartPhone, OEM) => {
1058
+ const data = {isSmartPhone: isSmartPhone};
1059
+ await set(ref(database, `Microphone2/${OEM}/${speakerID}/isSmartPhone`), isSmartPhone);
1060
+ };
1061
+
1062
+ doesMicrophoneExist = async (speakerID, OEM) => {
1063
+ const dbRef = ref(database);
1064
+ const snapshot = await get(child(dbRef, `Microphone2/${OEM}/${speakerID}`));
1065
+ if (snapshot.exists()) {
1066
+ return true;
1067
+ }
1068
+ return false;
1069
+ };
1070
+
1071
+ convertToDB = gain => {
1072
+ return Math.log10(gain) * 20;
1073
+ };
1074
+
1075
+ // Function to perform linear interpolation between two points
1076
+ interpolate(x, x0, y0, x1, y1) {
1077
+ return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);
1078
+ }
1079
+
1080
+ findGainatFrequency = (frequencies, gains, targetFrequency) => {
1081
+ // Find the index of the first frequency in the array greater than the target frequency
1082
+ let index = 0;
1083
+ while (index < frequencies.length && frequencies[index] < targetFrequency) {
1084
+ index++;
1085
+ }
1086
+
1087
+ // Handle cases when the target frequency is outside the range of the given data
1088
+ if (index === 0) {
1089
+ return gains[0];
1090
+ } else if (index === frequencies.length) {
1091
+ return gains[gains.length - 1];
1092
+ } else {
1093
+ // Interpolate the gain based on the surrounding frequencies
1094
+ const x0 = frequencies[index - 1];
1095
+ const y0 = gains[index - 1];
1096
+ const x1 = frequencies[index];
1097
+ const y1 = gains[index];
1098
+ return this.interpolate(targetFrequency, x0, y0, x1, y1);
1099
+ }
1100
+ };
1101
+
1102
+ // Example of how to use the writeFrqGain and readFrqGain functions
1103
+ // writeFrqGain('speaker1', [1, 2, 3], [4, 5, 6]);
1104
+ // Speaker1 is the speakerID you want to write to in the database
1105
+ // readFrqGain('MiniDSPUMIK_1').then(data => console.log(data));
1106
+ // MiniDSPUMIK_1 is the speakerID with some Data in the database
1107
+ //adding gainDBSPL
1108
+ startCalibration = async (
1109
+ stream,
1110
+ gainValues,
1111
+ lCalib = 104.92978421490648,
1112
+ componentIR = null,
1113
+ microphoneName = 'MiniDSP-UMIK1-711-4754-vertical',
1114
+ _calibrateSoundCheck = 'goal', //GOAL PASSed in by default
1115
+ isSmartPhone = false,
1116
+ _calibrateSoundBurstRepeats = 4,
1117
+ _calibrateSoundBurstSec = 1,
1118
+ _calibrateSoundBurstsWarmup = 1,
1119
+ _calibrateSoundHz = 48000,
1120
+ _calibrateSoundIIRSec = 0.2,
1121
+ calibrateSound1000HzSec = 5,
1122
+ micManufacturer = '',
1123
+ micSerialNumber = '',
1124
+ micModelNumber = '',
1125
+ micModelName = ''
1126
+ ) => {
1127
+ this.CALIBRATION_TONE_DURATION = calibrateSound1000HzSec;
1128
+ this.iirLength = Math.floor(_calibrateSoundIIRSec * this.sourceSamplingRate);
1129
+ console.log('device info:', this.deviceInfo);
1130
+ this.numMLSPerCapture = _calibrateSoundBurstRepeats;
1131
+ this.desired_time_per_mls = _calibrateSoundBurstSec;
1132
+ this.num_mls_to_skip = _calibrateSoundBurstsWarmup;
1133
+ this.desired_sampling_rate = _calibrateSoundHz;
1134
+
1135
+ //feed calibration goal here
1136
+ this._calibrateSoundCheck = _calibrateSoundCheck;
1137
+ //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
1138
+ //check the db based on the microphone currently connected
1139
+
1140
+ //new lCalib found at top of calibration files *1000hz, make sure to correct
1141
+ //based on zeroing of 1000hz, search for "*1000Hz"
1142
+ const ID = isSmartPhone ? micModelNumber : micSerialNumber;
1143
+ const OEM = isSmartPhone ? this.deviceInfo.OEM : micManufacturer;
1144
+ if (componentIR == null) {
1145
+ //mode 'ir'
1146
+ //global variable this.componentIR must be set
1147
+ this.componentIR = await this.readFrqGain(ID, OEM).then(data => {
1148
+ return data;
1149
+ });
1150
+
1151
+ lCalib = await this.readGainat1000Hz(ID, OEM);
1152
+ // this.componentGainDBSPL = this.convertToDB(lCalib);
1153
+ this.componentGainDBSPL = lCalib;
1154
+ //TODO: if this call to database is unknown, cannot perform experiment => return false
1155
+ if (this.componentIR == null) {
1156
+ this.status =
1157
+ `Microphone (${OEM},${ID}) is not found in the database. Please add it to the database.`.toString();
1158
+ this.emit('update', {message: this.status});
1159
+ return false;
1160
+ }
1161
+ } else {
1162
+ this.componentIR = componentIR;
1163
+ lCalib = this.findGainatFrequency(this.componentIR.Freq, this.componentIR.Gain, 1000);
1164
+ // this.componentGainDBSPL = this.convertToDB(lCalib);
1165
+ this.componentGainDBSPL = lCalib;
1166
+ await this.writeGainat1000Hz(ID, lCalib, OEM);
1167
+ await this.writeIsSmartPhone(ID, isSmartPhone, OEM);
1168
+ }
1169
+
1170
+ this.oldComponentIR = this.componentIR;
1171
+
1172
+ let volumeResults = await this.startCalibrationVolume(
1173
+ stream,
1174
+ gainValues,
1175
+ lCalib,
1176
+ this.componentGainDBSPL
1177
+ );
1178
+
1179
+ let impulseResponseResults = await this.startCalibrationImpulseResponse(stream);
1180
+
1181
+ if (componentIR != null) {
1182
+ //insert Freq and Gain from this.componentIR into db
1183
+ await this.writeFrqGain(
1184
+ ID,
1185
+ impulseResponseResults.componentIR.Freq,
1186
+ impulseResponseResults.componentIR.Gain,
1187
+ OEM
1188
+ );
1189
+ }
1190
+
1191
+ const total_results = {...volumeResults, ...impulseResponseResults};
1192
+
1193
+ total_results['micInfo'] = {
1194
+ micManufacturer: micManufacturer,
1195
+ micSerialNumber: micSerialNumber,
1196
+ micModelNumber: micModelNumber,
1197
+ micModelName: micModelName,
1198
+ ID: ID,
1199
+ OEM: OEM,
1200
+ };
1201
+ console.log('total results');
1202
+ console.log(total_results);
1203
+ return total_results;
1204
+ };
1205
+ }
1206
+
1207
+ export default Combination;