speaker-calibration 2.0.0 → 2.1.0

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 (126) hide show
  1. package/.eslintignore +72 -0
  2. package/.eslintrc.json +40 -0
  3. package/.gitignore +78 -0
  4. package/.prettierignore +70 -0
  5. package/.prettierrc +15 -0
  6. package/LICENSE +20 -20
  7. package/README.md +133 -133
  8. package/__mocks__/fileMock.js +1 -0
  9. package/__mocks__/styleMock.js +1 -0
  10. package/babel.config.js +3 -0
  11. package/coverage/clover.xml +71 -0
  12. package/coverage/coverage-final.json +224 -0
  13. package/coverage/lcov-report/PythonServerInterface.js.html +265 -0
  14. package/coverage/lcov-report/base.css +354 -0
  15. package/coverage/lcov-report/block-navigation.js +82 -0
  16. package/coverage/lcov-report/favicon.png +0 -0
  17. package/coverage/lcov-report/index.html +123 -0
  18. package/coverage/lcov-report/prettify.css +101 -0
  19. package/coverage/lcov-report/prettify.js +937 -0
  20. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  21. package/coverage/lcov-report/sorter.js +189 -0
  22. package/coverage/lcov-report/src/index.html +121 -0
  23. package/coverage/lcov-report/src/server/PythonServerInterface.js.html +268 -0
  24. package/coverage/lcov-report/src/server/index.html +123 -0
  25. package/coverage/lcov-report/src/tasks/audioCalibrator.js.html +499 -0
  26. package/coverage/lcov-report/src/tasks/audioRecorder.js.html +412 -0
  27. package/coverage/lcov-report/src/tasks/index.html +143 -0
  28. package/coverage/lcov-report/src/tasks/volume/index.html +123 -0
  29. package/coverage/lcov-report/src/tasks/volume/volume.js.html +409 -0
  30. package/coverage/lcov-report/src/utils.js.html +172 -0
  31. package/coverage/lcov.info +91 -0
  32. package/dist/example/Queen-Bohemian_Rhapsody.wav +0 -0
  33. package/dist/example/Queen-Bohemian_Rhapsody_g_filtered.wav +0 -0
  34. package/dist/example/index.html +47 -0
  35. package/dist/example/listener.html +89 -0
  36. package/dist/example/server.js +49 -0
  37. package/dist/example/speaker.html +126 -0
  38. package/dist/example/speakerUI.js +217 -0
  39. package/dist/example/styles.css +40 -0
  40. package/dist/main.js +1 -1
  41. package/dist/mlsGen.js +6814 -6814
  42. package/dist/mlsGen.wasm +0 -0
  43. package/doc/AudioCalibrator.html +417 -0
  44. package/doc/AudioPeer.html +251 -0
  45. package/doc/AudioRecorder.html +195 -0
  46. package/doc/ImpulseResponse.html +215 -0
  47. package/doc/Listener.html +308 -0
  48. package/doc/MlsGenInterface.html +226 -0
  49. package/doc/MyEventEmitter.html +274 -0
  50. package/doc/PythonServerAPI.html +109 -0
  51. package/doc/Speaker-Calibration-UML-Diagram.png +0 -0
  52. package/doc/Speaker.html +276 -0
  53. package/doc/Takes%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +128 -0
  54. package/doc/Takes%20the%20url%20of%20the%20current%20site%0Aand%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +138 -0
  55. package/doc/Takes%20the%20url%20of%20the%20current%20site%20and%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +137 -0
  56. package/doc/Volume.html +88 -0
  57. package/doc/audioCalibrator.js.html +179 -0
  58. package/doc/audioPeer.js.html +175 -0
  59. package/doc/audioRecorder.js.html +163 -0
  60. package/doc/creates%20a%20new%20AudioRecorder%20instance.%20%0ASets%20up%20the%20audio%20context%20and%20file%20reader..html +114 -0
  61. package/doc/fonts/OpenSans-Bold-webfont.eot +0 -0
  62. package/doc/fonts/OpenSans-Bold-webfont.svg +1830 -0
  63. package/doc/fonts/OpenSans-Bold-webfont.woff +0 -0
  64. package/doc/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  65. package/doc/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
  66. package/doc/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  67. package/doc/fonts/OpenSans-Italic-webfont.eot +0 -0
  68. package/doc/fonts/OpenSans-Italic-webfont.svg +1830 -0
  69. package/doc/fonts/OpenSans-Italic-webfont.woff +0 -0
  70. package/doc/fonts/OpenSans-Light-webfont.eot +0 -0
  71. package/doc/fonts/OpenSans-Light-webfont.svg +1831 -0
  72. package/doc/fonts/OpenSans-Light-webfont.woff +0 -0
  73. package/doc/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  74. package/doc/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
  75. package/doc/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  76. package/doc/fonts/OpenSans-Regular-webfont.eot +0 -0
  77. package/doc/fonts/OpenSans-Regular-webfont.svg +1831 -0
  78. package/doc/fonts/OpenSans-Regular-webfont.woff +0 -0
  79. package/doc/global.html +308 -0
  80. package/doc/index.html +58 -0
  81. package/doc/listener.js.html +170 -0
  82. package/doc/mlsGen_mlsGenInterface.js.html +117 -0
  83. package/doc/myEventEmitter.js.html +124 -0
  84. package/doc/peer-connection_audioPeer.js.html +188 -0
  85. package/doc/peer-connection_listener.js.html +311 -0
  86. package/doc/peer-connection_speaker.js.html +381 -0
  87. package/doc/sc-activity-diagram.png +0 -0
  88. package/doc/scripts/linenumber.js +25 -0
  89. package/doc/scripts/prettify/Apache-License-2.0.txt +202 -0
  90. package/doc/scripts/prettify/lang-css.js +24 -0
  91. package/doc/scripts/prettify/prettify.js +640 -0
  92. package/doc/server_PythonServerAPI.js.html +160 -0
  93. package/doc/speaker.js.html +248 -0
  94. package/doc/styles/jsdoc-default.css +371 -0
  95. package/doc/styles/prettify-jsdoc.css +111 -0
  96. package/doc/styles/prettify-tomorrow.css +163 -0
  97. package/doc/tasks_audioCalibrator.js.html +207 -0
  98. package/doc/tasks_audioRecorder.js.html +190 -0
  99. package/doc/tasks_impulse-response_impulseResponse.js.html +442 -0
  100. package/doc/tasks_impulse-response_mlsGen_mlsGenInterface.js.html +175 -0
  101. package/doc/tasks_volume_volume.js.html +185 -0
  102. package/doc/utils.js.html +105 -0
  103. package/jest.config.js +173 -0
  104. package/netlify.toml +27 -0
  105. package/package.json +67 -66
  106. package/src/index.html +21 -0
  107. package/src/main.js +21 -0
  108. package/src/myEventEmitter.js +83 -0
  109. package/src/peer-connection/audioPeer.js +151 -0
  110. package/src/peer-connection/listener.js +251 -0
  111. package/src/peer-connection/peerErrors.js +25 -0
  112. package/src/peer-connection/speaker.js +346 -0
  113. package/src/server/PythonServerAPI.js +117 -0
  114. package/src/tasks/audioCalibrator.js +218 -0
  115. package/src/tasks/audioRecorder.js +148 -0
  116. package/src/tasks/impulse-response/impulseResponse.js +436 -0
  117. package/src/tasks/impulse-response/mlsGen/mlsGen.cpp +99 -0
  118. package/src/tasks/impulse-response/mlsGen/mlsGen.hpp +304 -0
  119. package/src/tasks/impulse-response/mlsGen/mlsGenInterface.js +131 -0
  120. package/src/tasks/impulse-response/mlsGen/mlsGenTest.cpp +181 -0
  121. package/src/tasks/volume/volume.cpp +3 -0
  122. package/src/tasks/volume/volume.hpp +23 -0
  123. package/src/tasks/volume/volume.js +157 -0
  124. package/src/utils.js +55 -0
  125. package/webpack.config.js +37 -0
  126. package/README +0 -3
@@ -0,0 +1,346 @@
1
+ import QRCode from 'qrcode';
2
+ import AudioPeer from './audioPeer';
3
+ import {sleep} from '../utils';
4
+ import {
5
+ UnsupportedDeviceError,
6
+ MissingSpeakerIdError,
7
+ CalibrationTimedOutError,
8
+ } from './peerErrors';
9
+
10
+ /**
11
+ * @class Handles the speaker's side of the connection. Responsible for initiating the connection,
12
+ * rendering the QRCode, and answering the call.
13
+ * @augments AudioPeer
14
+ */
15
+ class Speaker extends AudioPeer {
16
+ /**
17
+ * Takes the url of the current site and a target element where html elements will be appended.
18
+ *
19
+ * @param params - See type definition for initParameters.
20
+ * @param Calibrator - An instance of the AudioCalibrator class, should not use AudioCalibrator directly, instead use an extended class available in /tasks/.
21
+ * @param CalibratorInstance
22
+ * @example
23
+ */
24
+ constructor(params, CalibratorInstance) {
25
+ super(params);
26
+
27
+ this.siteUrl += '/listener?';
28
+ this.ac = CalibratorInstance;
29
+ this.result = null;
30
+
31
+ /* Set up callbacks that handle any events related to our peer object. */
32
+ this.peer.on('open', this.#onPeerOpen);
33
+ this.peer.on('connection', this.#onPeerConnection);
34
+ this.peer.on('close', this.#onPeerClose);
35
+ this.peer.on('disconnected', this.#onPeerDisconnected);
36
+ this.peer.on('error', this.#onPeerError);
37
+ }
38
+
39
+ /**
40
+ * Async factory method that creates the Speaker object, and returns a promise that resolves to the result of the calibration.
41
+ *
42
+ * @param params - The parameters to be passed to the peer object.
43
+ * @param Calibrator - The class that defines the calibration process.
44
+ * @param CalibratorInstance
45
+ * @param timeOut - The amount of time to wait before timing out the connection (in milliseconds).
46
+ * @public
47
+ * @example
48
+ */
49
+ static startCalibration = async (params, CalibratorInstance, timeOut = 180000) => {
50
+ window.speaker = new Speaker(params, CalibratorInstance);
51
+ const {speaker} = window;
52
+
53
+ // wrap the calibration process in a promise so we can await it
54
+ return new Promise((resolve, reject) => {
55
+ // when a call is received
56
+ speaker.peer.on('call', async call => {
57
+ // Answer the call (one way)
58
+ call.answer();
59
+ speaker.#removeUIElems();
60
+ speaker.#showSpinner();
61
+ speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));
62
+ // when we start receiving audio
63
+ call.on('stream', async stream => {
64
+ window.localStream = stream;
65
+ window.localAudio.srcObject = stream;
66
+ window.localAudio.autoplay = false;
67
+
68
+ // if the sinkSamplingRate is not set sleep
69
+ while (!speaker.ac.sampleRatesSet()) {
70
+ console.log('SinkSamplingRate is undefined, sleeping');
71
+ await sleep(1);
72
+ }
73
+ // resolve when we have a result
74
+ speaker.result = await speaker.ac.startCalibration(stream, params.gainValues);
75
+ speaker.#removeUIElems();
76
+ resolve(speaker.result);
77
+ });
78
+ // if we do not receive a result within the timeout, reject
79
+ setTimeout(() => {
80
+ reject(
81
+ new CalibrationTimedOutError(
82
+ `Calibration failed to produce a result after ${
83
+ timeOut / 1000
84
+ } seconds. Please try again.`
85
+ )
86
+ );
87
+ }, timeOut);
88
+ });
89
+ });
90
+ };
91
+
92
+ static testIIR = async (params, CalibratorInstance, IIR, timeOut = 180000) => {
93
+ window.speaker = new Speaker(params, CalibratorInstance);
94
+ const {speaker} = window;
95
+
96
+ // wrap the calibration process in a promise so we can await it
97
+ return new Promise((resolve, reject) => {
98
+ // when a call is received
99
+ speaker.peer.on('call', async call => {
100
+ // Answer the call (one way)
101
+ call.answer();
102
+ speaker.#removeUIElems();
103
+ speaker.#showSpinner();
104
+ speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));
105
+ // when we start receiving audio
106
+ call.on('stream', async stream => {
107
+ window.localStream = stream;
108
+ window.localAudio.srcObject = stream;
109
+ window.localAudio.autoplay = false;
110
+
111
+ // if the sinkSamplingRate is not set sleep
112
+ while (!speaker.ac.sampleRatesSet()) {
113
+ console.log('SinkSamplingRate is undefined, sleeping');
114
+ await sleep(1);
115
+ }
116
+ // resolve when we have a result
117
+ speaker.result = await speaker.ac.playMLSwithIIR(stream, IIR);
118
+ speaker.#removeUIElems();
119
+ resolve(speaker.result);
120
+ });
121
+ // if we do not receive a result within the timeout, reject
122
+ setTimeout(() => {
123
+ reject(
124
+ new CalibrationTimedOutError(
125
+ `Calibration failed to produce a result after ${
126
+ timeOut / 1000
127
+ } seconds. Please try again.`
128
+ )
129
+ );
130
+ }, timeOut);
131
+ });
132
+ });
133
+ };
134
+
135
+ /**
136
+ * Called after the peer conncection has been opened.
137
+ * Generates a QR code for the connection and displays it.
138
+ *
139
+ * @private
140
+ * @example
141
+ */
142
+ #showQRCode = () => {
143
+ // Get query string, the URL parameters to specify a Listener
144
+ const queryStringParameters = {
145
+ speakerPeerId: this.peer.id,
146
+ };
147
+ const queryString = this.queryStringFromObject(queryStringParameters);
148
+ const uri = this.siteUrl + queryString;
149
+
150
+ // Display QR code for the participant to scan
151
+ const qrCanvas = document.createElement('canvas');
152
+ qrCanvas.setAttribute('id', 'qrCanvas');
153
+ console.log(uri);
154
+ QRCode.toCanvas(qrCanvas, uri, error => {
155
+ if (error) console.error(error);
156
+ });
157
+
158
+ // If specified HTML Id is available, show QR code there
159
+ if (document.getElementById(this.targetElement)) {
160
+ if (document.getElementById(this.targetElement)) {
161
+ if (process.env.NODE_ENV === 'development') {
162
+ const linkTag = document.createElement('a');
163
+ linkTag.setAttribute('href', uri);
164
+ linkTag.innerHTML = "Click here to connect to the speaker's microphone";
165
+ linkTag.target = '_blank';
166
+ document.getElementById(this.targetElement).appendChild(linkTag);
167
+ }
168
+ }
169
+ document.getElementById(this.targetElement).appendChild(qrCanvas);
170
+ } else {
171
+ // or just print it to console
172
+ console.log('TEST: Peer reachable at: ', uri);
173
+ }
174
+ };
175
+
176
+ #showSpinner = () => {
177
+ const spinner = document.createElement('div');
178
+ spinner.className = 'spinner-border ml-auto';
179
+ spinner.role = 'status';
180
+ spinner.ariaHidden = 'true';
181
+ document.getElementById(this.targetElement).appendChild(spinner);
182
+ };
183
+
184
+ #removeUIElems = () => {
185
+ const parent = document.getElementById(this.targetElement);
186
+ while (parent.firstChild) {
187
+ parent.firstChild.remove();
188
+ }
189
+ };
190
+
191
+ /**
192
+ * Called when the peer connection is opened.
193
+ * Saves the peer id and calls the QR code generator.
194
+ *
195
+ * @param peerId - The peer id of the peer connection.
196
+ * @param id
197
+ * @private
198
+ * @example
199
+ */
200
+ #onPeerOpen = id => {
201
+ // Workaround for peer.reconnect deleting previous id
202
+ if (id === null) {
203
+ console.error('Received null id from peer open');
204
+ this.peer.id = this.lastPeerId;
205
+ } else {
206
+ this.lastPeerId = this.peer.id;
207
+ }
208
+
209
+ if (id !== this.peer.id) {
210
+ console.warn('DEBUG Check you assumption that id === this.peer.id');
211
+ }
212
+
213
+ this.#showQRCode();
214
+ };
215
+
216
+ /**
217
+ * Called when the peer connection is established.
218
+ * Enforces a single connection.
219
+ *
220
+ * @param connection - The connection object.
221
+ * @private
222
+ * @example
223
+ */
224
+ #onPeerConnection = connection => {
225
+ // Allow only a single connection
226
+ if (this.conn && this.conn.open) {
227
+ connection.on('open', () => {
228
+ connection.send('Already connected to another client');
229
+ setTimeout(() => {
230
+ connection.close();
231
+ }, 500);
232
+ });
233
+ return;
234
+ }
235
+
236
+ this.conn = connection;
237
+ console.log('Connected to: ', this.conn.peer);
238
+ this.#ready();
239
+ };
240
+
241
+ /**
242
+ * Called when the peer connection is closed.
243
+ *
244
+ * @private
245
+ * @example
246
+ */
247
+ #onPeerClose = () => {
248
+ this.conn = null;
249
+ console.log('Connection destroyed');
250
+ };
251
+
252
+ /**
253
+ * Called when the peer connection is disconnected.
254
+ * Attempts to reconnect.
255
+ *
256
+ * @private
257
+ * @example
258
+ */
259
+ #onPeerDisconnected = () => {
260
+ console.log('Connection lost. Please reconnect');
261
+
262
+ // Workaround for peer.reconnect deleting previous id
263
+ this.peer.id = this.lastPeerId;
264
+ // eslint-disable-next-line no-underscore-dangle
265
+ this.peer._lastServerId = this.lastPeerId;
266
+ this.peer.reconnect();
267
+ };
268
+
269
+ /**
270
+ * Called when the peer connection encounters an error.
271
+ *
272
+ * @param error
273
+ * @private
274
+ * @example
275
+ */
276
+ #onPeerError = error => {
277
+ // TODO: check if this function is needed or not
278
+ console.error(error);
279
+ };
280
+
281
+ /**
282
+ * Called when data is received from the peer connection.
283
+ *
284
+ * @param data
285
+ * @private
286
+ * @example
287
+ */
288
+ #onIncomingData = data => {
289
+ // enforce object type
290
+ if (
291
+ !Object.prototype.hasOwnProperty.call(data, 'name') ||
292
+ !Object.prototype.hasOwnProperty.call(data, 'payload')
293
+ ) {
294
+ console.error('Received malformed data: ', data);
295
+ return;
296
+ }
297
+
298
+ switch (data.name) {
299
+ case 'samplingRate':
300
+ this.ac.setSamplingRates(data.payload);
301
+ break;
302
+ case UnsupportedDeviceError.name:
303
+ case MissingSpeakerIdError.name:
304
+ throw data.payload;
305
+ break;
306
+ default:
307
+ break;
308
+ }
309
+ };
310
+
311
+ /**
312
+ * Called when the peer connection is #ready.
313
+ *
314
+ * @private
315
+ * @example
316
+ */
317
+ #ready = () => {
318
+ // Perform callback with data
319
+ this.conn.on('data', this.#onIncomingData);
320
+ this.conn.on('close', () => {
321
+ console.log('Connection reset<br>Awaiting connection...');
322
+ this.conn = null;
323
+ });
324
+ };
325
+
326
+ /** .
327
+ * .
328
+ * .
329
+ * Debug method for downloading the recorded audio
330
+ *
331
+ * @public
332
+ * @example
333
+ */
334
+ downloadData = () => {
335
+ this.ac.downloadData();
336
+ };
337
+ }
338
+
339
+ /*
340
+ Referenced links:
341
+ https://stackoverflow.com/questions/28016664/when-you-pass-this-as-an-argument/28016676#28016676
342
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
343
+ https://stackoverflow.com/questions/879152/how-do-i-make-javascript-beep [3]
344
+ */
345
+
346
+ export default Speaker;
@@ -0,0 +1,117 @@
1
+ import axios from 'axios';
2
+ /**
3
+ *
4
+ */
5
+ class PythonServerAPI {
6
+ static PYTHON_SERVER_URL = 'https://easyeyes-python-flask-server.herokuapp.com';
7
+
8
+ static TEST_SERVER_URL = 'http://127.0.0.1:5000';
9
+
10
+ /**
11
+ * @param data- -
12
+ * g = inverted impulse response, when convolved with the impulse
13
+ * reponse, they cancel out.
14
+ * @param data.payload
15
+ * @param data.sampleRate
16
+ * @param data.P
17
+ * @param data-.payload
18
+ * @param data-.sampleRate
19
+ * @param data-.P
20
+ * @returns
21
+ * @example
22
+ */
23
+ getImpulseResponse = async ({payload, sampleRate, P}) => {
24
+ const task = 'impulse-response';
25
+ let res = null;
26
+
27
+ console.log({payload});
28
+
29
+ const data = JSON.stringify({
30
+ task,
31
+ payload,
32
+ 'sample-rate': sampleRate,
33
+ P,
34
+ });
35
+
36
+ await axios({
37
+ method: 'post',
38
+ baseURL: PythonServerAPI.PYTHON_SERVER_URL,
39
+ url: `/task/${task}`,
40
+ headers: {
41
+ 'Content-Type': 'application/json',
42
+ },
43
+ data,
44
+ })
45
+ .then(response => {
46
+ res = response;
47
+ })
48
+ .catch(error => {
49
+ throw error;
50
+ });
51
+
52
+ return res.data[task];
53
+ };
54
+
55
+ getInverseImpulseResponse = async ({payload}) => {
56
+ const task = 'inverse-impulse-response';
57
+ let res = null;
58
+
59
+ console.log({payload});
60
+
61
+ const data = JSON.stringify({
62
+ task,
63
+ payload,
64
+ });
65
+
66
+ await axios({
67
+ method: 'post',
68
+ baseURL: PythonServerAPI.PYTHON_SERVER_URL,
69
+ url: `/task/${task}`,
70
+ headers: {
71
+ 'Content-Type': 'application/json',
72
+ },
73
+ data,
74
+ })
75
+ .then(response => {
76
+ res = response;
77
+ })
78
+ .catch(error => {
79
+ throw error;
80
+ });
81
+
82
+ return res.data[task];
83
+ };
84
+
85
+ getVolumeCalibration = async ({payload, sampleRate}) => {
86
+ const task = 'volume';
87
+ let res = null;
88
+
89
+ console.log({payload});
90
+
91
+ const data = JSON.stringify({
92
+ task,
93
+ payload,
94
+ 'sample-rate': sampleRate,
95
+ });
96
+
97
+ await axios({
98
+ method: 'post',
99
+ baseURL: PythonServerAPI.PYTHON_SERVER_URL,
100
+ url: `/task/${task}`,
101
+ headers: {
102
+ 'Content-Type': 'application/json',
103
+ },
104
+ data,
105
+ })
106
+ .then(response => {
107
+ res = response;
108
+ })
109
+ .catch(error => {
110
+ throw error;
111
+ });
112
+
113
+ return res.data[task];
114
+ };
115
+ }
116
+
117
+ export default PythonServerAPI;
@@ -0,0 +1,218 @@
1
+ /* eslint-disable no-await-in-loop */
2
+ import AudioRecorder from './audioRecorder';
3
+ import PythonServerAPI from '../server/PythonServerAPI';
4
+ import {sleep, saveToCSV} from '../utils';
5
+
6
+ /**
7
+ * .
8
+ * .
9
+ * .
10
+ * Provides methods for calibrating the user's speakers
11
+ *
12
+ * @extends AudioRecorder
13
+ */
14
+ class AudioCalibrator extends AudioRecorder {
15
+ /**
16
+ *
17
+ * @param numCaptures
18
+ * @param numMLSPerCapture
19
+ * @example
20
+ */
21
+ constructor(numCaptures = 1, numMLSPerCapture = 1) {
22
+ super();
23
+ this.numCaptures = numCaptures;
24
+ this.numMLSPerCapture = numMLSPerCapture;
25
+ this.pyServerAPI = new PythonServerAPI();
26
+ }
27
+
28
+ /** @private */
29
+ #isCalibrating = false;
30
+
31
+ /** @private */
32
+ sourceAudioContext;
33
+
34
+ /** @protected */
35
+ numCalibratingRounds = 1;
36
+
37
+ /** @protected */
38
+ numSuccessfulCaptured = 0;
39
+
40
+ /** @private */
41
+ sourceSamplingRate;
42
+
43
+ /** @protected */
44
+ calibrationNodes = [];
45
+
46
+ /** @protected */
47
+ localAudio;
48
+
49
+ /**
50
+ * Called when a call is received.
51
+ * Creates a local audio DOM element and attaches it to the page.
52
+ *
53
+ * @param targetElement
54
+ * @example
55
+ */
56
+ createLocalAudio = targetElement => {
57
+ this.localAudio = document.createElement('audio');
58
+ this.localAudio.setAttribute('id', 'localAudio');
59
+ targetElement.appendChild(this.localAudio);
60
+ };
61
+
62
+ /**
63
+ *
64
+ * @param {MediaStream} stream
65
+ * @param {Function} playCalibrationAudio - (async) function that plays the calibration audio
66
+ * @param {*} beforePlay - (async) function that is called before playing the audio
67
+ * @param {*} beforeRecord - (async) function that is called before recording
68
+ * @param {*} duringRecord - (async) function that is called while recording
69
+ * @param {*} afterRecord - (async) function that is called after recording
70
+ * @example
71
+ */
72
+ calibrationSteps = async (
73
+ stream,
74
+ playCalibrationAudio,
75
+ beforePlay = async () => {},
76
+ beforeRecord = async () => {},
77
+ loopCondition = () => false,
78
+ duringRecord = async () => {},
79
+ afterRecord = async () => {}
80
+ ) => {
81
+ this.numSuccessfulCaptured = 0;
82
+
83
+ // do something before playing such as using the MLS to fill the buffers
84
+ console.warn('beforePlay');
85
+ await beforePlay();
86
+
87
+ // play calibration audio
88
+ console.warn('playCalibrationAudio');
89
+ playCalibrationAudio();
90
+
91
+ // do something before recording such as awaiting a certain amount of time
92
+ console.warn('beforeRecord');
93
+ await beforeRecord();
94
+
95
+ // calibration loop
96
+ while (loopCondition()) {
97
+ // start recording
98
+ console.warn('startRecording');
99
+ await this.startRecording(stream);
100
+
101
+ // do something during the recording such as sleep n amount of time
102
+ console.warn('duringRecord');
103
+ await duringRecord();
104
+
105
+ // when done, stop recording
106
+ console.warn('stopRecording');
107
+ await this.stopRecording();
108
+
109
+ // do something after recording such as start processing values
110
+ console.warn('afterRecord');
111
+ await afterRecord();
112
+
113
+ // eslint-disable-next-line no-await-in-loop
114
+ await sleep(1);
115
+ }
116
+ };
117
+
118
+ /**
119
+ *
120
+ * @param {MediaStream} stream
121
+ * @param {Function} playCalibrationAudio - (async) function that plays the calibration audio
122
+ * @param {*} beforeRecord - (async) function that is called before recording
123
+ * @param {*} afterRecord - (async) function that is called after recording
124
+ * @param {Number} gainValue - the gain value to set the gain node to
125
+ */
126
+ volumeCalibrationSteps = async (
127
+ stream,
128
+ playCalibrationAudio,
129
+ beforeRecord = () => {},
130
+ afterRecord = () => {},
131
+ gainValue
132
+ ) => {
133
+ this.numCalibratingRoundsCompleted = 0;
134
+
135
+ // calibration loop
136
+ while (!this.#isCalibrating && this.numCalibratingRoundsCompleted < this.numCalibratingRounds) {
137
+ // before recording
138
+ await beforeRecord(gainValue);
139
+
140
+ // start recording
141
+ await this.startRecording(stream);
142
+
143
+ // play calibration audio
144
+ console.log(`Calibration Round ${this.numCalibratingRoundsCompleted}`);
145
+ await playCalibrationAudio();
146
+
147
+ // when done, stop recording
148
+ console.log('Calibration Round Complete');
149
+ await this.stopRecording();
150
+
151
+ // after recording
152
+ await afterRecord();
153
+
154
+ this.calibrationNodes = [];
155
+
156
+ // eslint-disable-next-line no-await-in-loop
157
+ await sleep(2);
158
+ this.numCalibratingRoundsCompleted += 1;
159
+ }
160
+ };
161
+
162
+ /**
163
+ * Getter for the isCalibrating property.
164
+ *
165
+ * @public
166
+ * @returns - True if the audio is being calibrated, false otherwise.
167
+ * @example
168
+ */
169
+ getCalibrationStatus = () => this.#isCalibrating;
170
+
171
+ /** .
172
+ * .
173
+ * .
174
+ * Set the sampling rate to the value received from the listener
175
+ *
176
+ * @param {*} sinkSamplingRate
177
+ * @param samplingRate
178
+ * @example
179
+ */
180
+ setSamplingRates = samplingRate => {
181
+ this.sinkSamplingRate = samplingRate;
182
+ this.sourceSamplingRate = samplingRate;
183
+ this.emit('update', {message: `sampling at ${samplingRate}Hz...`});
184
+ };
185
+
186
+ sampleRatesSet = () => this.sourceSamplingRate && this.sinkSamplingRate;
187
+
188
+ addCalibrationNode = node => {
189
+ this.calibrationNodes.push(node);
190
+ };
191
+
192
+ makeNewSourceAudioContext = () => {
193
+ const options = {
194
+ sampleRate: this.sourceSamplingRate,
195
+ };
196
+
197
+ this.sourceAudioContext = new (window.AudioContext ||
198
+ window.webkitAudioContext ||
199
+ window.audioContext)(options);
200
+
201
+ return this.sourceAudioContext;
202
+ };
203
+
204
+ /** .
205
+ * .
206
+ * .
207
+ * Download the result of the calibration roudns
208
+ *
209
+ * @example
210
+ */
211
+ downloadData = () => {
212
+ const recordings = this.getAllRecordedSignals();
213
+ const i = recordings.length - 1;
214
+ saveToCSV(recordings[i], `recordedMLSignal_${i}.csv`);
215
+ };
216
+ }
217
+
218
+ export default AudioCalibrator;