speaker-calibration 2.1.7 → 2.1.9
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.
package/package.json
CHANGED
|
@@ -6,7 +6,6 @@ class PythonServerAPI {
|
|
|
6
6
|
static PYTHON_SERVER_URL = 'https://easyeyes-python-flask-server.herokuapp.com';
|
|
7
7
|
|
|
8
8
|
static TEST_SERVER_URL = 'http://127.0.0.1:5000';
|
|
9
|
-
|
|
10
9
|
/**
|
|
11
10
|
* @param data- -
|
|
12
11
|
* g = inverted impulse response, when convolved with the impulse
|
|
@@ -20,7 +19,7 @@ class PythonServerAPI {
|
|
|
20
19
|
* @returns
|
|
21
20
|
* @example
|
|
22
21
|
*/
|
|
23
|
-
getImpulseResponse = async ({payload, sampleRate, P}) => {
|
|
22
|
+
getImpulseResponse = async ({mls, payload, sampleRate, P}) => {
|
|
24
23
|
const task = 'impulse-response';
|
|
25
24
|
let res = null;
|
|
26
25
|
|
|
@@ -30,6 +29,7 @@ class PythonServerAPI {
|
|
|
30
29
|
task,
|
|
31
30
|
payload,
|
|
32
31
|
'sample-rate': sampleRate,
|
|
32
|
+
mls,
|
|
33
33
|
P,
|
|
34
34
|
});
|
|
35
35
|
|
|
@@ -48,11 +48,10 @@ class PythonServerAPI {
|
|
|
48
48
|
.catch(error => {
|
|
49
49
|
throw error;
|
|
50
50
|
});
|
|
51
|
-
|
|
52
51
|
return res.data[task];
|
|
53
52
|
};
|
|
54
53
|
|
|
55
|
-
getInverseImpulseResponse = async ({payload}) => {
|
|
54
|
+
getInverseImpulseResponse = async ({payload,mls,lowHz,highHz}) => {
|
|
56
55
|
const task = 'inverse-impulse-response';
|
|
57
56
|
let res = null;
|
|
58
57
|
|
|
@@ -61,6 +60,9 @@ class PythonServerAPI {
|
|
|
61
60
|
const data = JSON.stringify({
|
|
62
61
|
task,
|
|
63
62
|
payload,
|
|
63
|
+
mls,
|
|
64
|
+
lowHz,
|
|
65
|
+
highHz
|
|
64
66
|
});
|
|
65
67
|
|
|
66
68
|
await axios({
|
|
@@ -31,6 +31,9 @@ class AudioCalibrator extends AudioRecorder {
|
|
|
31
31
|
/** @private */
|
|
32
32
|
sourceAudioContext;
|
|
33
33
|
|
|
34
|
+
/** @private */
|
|
35
|
+
sourceAudioContextConvolved;
|
|
36
|
+
|
|
34
37
|
/** @protected */
|
|
35
38
|
numCalibratingRounds = 1;
|
|
36
39
|
|
|
@@ -43,6 +46,9 @@ class AudioCalibrator extends AudioRecorder {
|
|
|
43
46
|
/** @protected */
|
|
44
47
|
calibrationNodes = [];
|
|
45
48
|
|
|
49
|
+
/** @protected */
|
|
50
|
+
calibrationNodesConvolved = [];
|
|
51
|
+
|
|
46
52
|
/** @protected */
|
|
47
53
|
localAudio;
|
|
48
54
|
|
|
@@ -181,6 +187,7 @@ class AudioCalibrator extends AudioRecorder {
|
|
|
181
187
|
setSamplingRates = samplingRate => {
|
|
182
188
|
this.sinkSamplingRate = samplingRate;
|
|
183
189
|
this.sourceSamplingRate = samplingRate;
|
|
190
|
+
|
|
184
191
|
// this.emit('update', {message: `sampling at ${samplingRate}Hz...`});
|
|
185
192
|
};
|
|
186
193
|
|
|
@@ -190,6 +197,11 @@ class AudioCalibrator extends AudioRecorder {
|
|
|
190
197
|
this.calibrationNodes.push(node);
|
|
191
198
|
};
|
|
192
199
|
|
|
200
|
+
addCalibrationNodeConvolved = node => {
|
|
201
|
+
|
|
202
|
+
this.calibrationNodesConvolved.push(node);
|
|
203
|
+
}
|
|
204
|
+
|
|
193
205
|
makeNewSourceAudioContext = () => {
|
|
194
206
|
const options = {
|
|
195
207
|
sampleRate: this.sourceSamplingRate,
|
|
@@ -202,6 +214,20 @@ class AudioCalibrator extends AudioRecorder {
|
|
|
202
214
|
return this.sourceAudioContext;
|
|
203
215
|
};
|
|
204
216
|
|
|
217
|
+
makeNewSourceAudioContextConvolved = () => {
|
|
218
|
+
const options = {
|
|
219
|
+
sampleRate: this.sourceSamplingRate,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
this.sourceAudioContextConvolved = new (window.AudioContext ||
|
|
223
|
+
window.webkitAudioContext ||
|
|
224
|
+
window.audioContext)(options);
|
|
225
|
+
|
|
226
|
+
return this.sourceAudioContextConvolved;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
205
231
|
/** .
|
|
206
232
|
* .
|
|
207
233
|
* .
|
|
@@ -212,7 +238,12 @@ class AudioCalibrator extends AudioRecorder {
|
|
|
212
238
|
downloadData = () => {
|
|
213
239
|
const recordings = this.getAllRecordedSignals();
|
|
214
240
|
const i = recordings.length - 1;
|
|
215
|
-
saveToCSV(recordings[i], `recordedMLSignal_${i}.csv`);
|
|
241
|
+
saveToCSV(recordings[i], `recordedMLSignal_${i}_unconvolved.csv`);
|
|
242
|
+
};
|
|
243
|
+
downloadConvolvedData = () => {
|
|
244
|
+
const recordings = this.getAllRecordedSignals();
|
|
245
|
+
const i = recordings.length - 1;
|
|
246
|
+
saveToCSV(recordings[i], `recordedMLSignal_${i}_convolved.csv`);
|
|
216
247
|
};
|
|
217
248
|
}
|
|
218
249
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import AudioCalibrator from '../audioCalibrator';
|
|
2
2
|
import MlsGenInterface from './mlsGen/mlsGenInterface';
|
|
3
3
|
|
|
4
|
-
import {sleep, csvToArray} from '../../utils';
|
|
4
|
+
import {sleep, csvToArray, saveToCSV} from '../../utils';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
*
|
|
@@ -16,11 +16,14 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
16
16
|
* @param {number} [calibratorParams.numCaptures = 5] - number of captures to perform
|
|
17
17
|
* @param {number} [calibratorParams.numMLSPerCapture = 4] - number of bursts of MLS per capture
|
|
18
18
|
*/
|
|
19
|
-
constructor({download = false, mlsOrder = 18, numCaptures = 3, numMLSPerCapture =
|
|
19
|
+
constructor({download = false, mlsOrder = 18, numCaptures = 3, numMLSPerCapture = 4, lowHz = 20, highHz = 10000}) {
|
|
20
20
|
super(numCaptures, numMLSPerCapture);
|
|
21
21
|
this.#mlsOrder = parseInt(mlsOrder, 10);
|
|
22
22
|
this.#P = 2 ** mlsOrder - 1;
|
|
23
23
|
this.#download = download;
|
|
24
|
+
this.#mls = [];
|
|
25
|
+
this.#lowHz = lowHz;
|
|
26
|
+
this.#highHz = highHz;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
/** @private */
|
|
@@ -41,15 +44,30 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
41
44
|
/** @private */
|
|
42
45
|
#mlsOrder;
|
|
43
46
|
|
|
47
|
+
/** @private */
|
|
48
|
+
#lowHz;
|
|
49
|
+
|
|
50
|
+
/** @private */
|
|
51
|
+
#highHz;
|
|
52
|
+
|
|
53
|
+
/** @private */
|
|
54
|
+
#mls;
|
|
55
|
+
|
|
44
56
|
/** @private */
|
|
45
57
|
#P;
|
|
46
58
|
|
|
59
|
+
/** @private */
|
|
60
|
+
#audioContext;
|
|
61
|
+
|
|
47
62
|
/** @private */
|
|
48
63
|
TAPER_SECS = 5;
|
|
49
64
|
|
|
50
65
|
/** @private */
|
|
51
66
|
offsetGainNode;
|
|
52
67
|
|
|
68
|
+
/** @private */
|
|
69
|
+
convolution;
|
|
70
|
+
|
|
53
71
|
/** .
|
|
54
72
|
* .
|
|
55
73
|
* .
|
|
@@ -60,14 +78,22 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
60
78
|
*/
|
|
61
79
|
sendImpulseResponsesToServerForProcessing = async () => {
|
|
62
80
|
const computedIRs = await Promise.all(this.impulseResponses);
|
|
81
|
+
const mls = this.#mls;
|
|
82
|
+
const lowHz = this.#lowHz;
|
|
83
|
+
const highHz = this.#highHz;
|
|
63
84
|
this.emit('update', {message: `computing the IIR...`});
|
|
64
85
|
return this.pyServerAPI
|
|
65
86
|
.getInverseImpulseResponse({
|
|
66
87
|
payload: computedIRs.slice(0, this.numCaptures),
|
|
88
|
+
mls,
|
|
89
|
+
lowHz,
|
|
90
|
+
highHz
|
|
67
91
|
})
|
|
68
92
|
.then(res => {
|
|
93
|
+
console.log(res);
|
|
69
94
|
this.emit('update', {message: `done computing the IIR...`});
|
|
70
|
-
this.invertedImpulseResponse = res;
|
|
95
|
+
this.invertedImpulseResponse = res["iir"];
|
|
96
|
+
this.convolution = res["convolution"];
|
|
71
97
|
})
|
|
72
98
|
.catch(err => {
|
|
73
99
|
// this.emit('InvertedImpulseResponse', {res: false});
|
|
@@ -86,6 +112,7 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
86
112
|
sendRecordingToServerForProcessing = signalCsv => {
|
|
87
113
|
const allSignals = this.getAllRecordedSignals();
|
|
88
114
|
const numSignals = allSignals.length;
|
|
115
|
+
const mls = this.#mls;
|
|
89
116
|
const payload =
|
|
90
117
|
signalCsv && signalCsv.length > 0 ? csvToArray(signalCsv) : allSignals[numSignals - 1];
|
|
91
118
|
|
|
@@ -95,11 +122,13 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
95
122
|
.getImpulseResponse({
|
|
96
123
|
sampleRate: this.sourceSamplingRate || 96000,
|
|
97
124
|
payload,
|
|
125
|
+
mls,
|
|
98
126
|
P: this.#P,
|
|
99
127
|
})
|
|
100
128
|
.then(res => {
|
|
101
129
|
if (this.numSuccessfulCaptured < this.numCaptures) {
|
|
102
130
|
this.numSuccessfulCaptured += 1;
|
|
131
|
+
console.log("num succ capt: " + this.numSuccessfulCaptured);
|
|
103
132
|
this.emit('update', {
|
|
104
133
|
message: `${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`,
|
|
105
134
|
});
|
|
@@ -156,9 +185,14 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
156
185
|
|
|
157
186
|
#afterMLSwIIRRecord = () => {
|
|
158
187
|
if (this.#download) {
|
|
159
|
-
this.
|
|
188
|
+
this.downloadConvolvedData();
|
|
189
|
+
}
|
|
190
|
+
if (this.numSuccessfulCaptured < this.numCaptures) {
|
|
191
|
+
this.numSuccessfulCaptured += 1;
|
|
192
|
+
this.emit('update', {
|
|
193
|
+
message: `${this.numSuccessfulCaptured}/${this.numCaptures} IRs computed...`,
|
|
194
|
+
});
|
|
160
195
|
}
|
|
161
|
-
this.#stopCalibrationAudio();
|
|
162
196
|
};
|
|
163
197
|
|
|
164
198
|
/** .
|
|
@@ -234,12 +268,13 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
234
268
|
// fill the buffer with our data
|
|
235
269
|
try {
|
|
236
270
|
for (let i = 0; i < dataBuffer.length; i += 1) {
|
|
237
|
-
data[i] = dataBuffer[i];
|
|
271
|
+
data[i] = dataBuffer[i]*.1;
|
|
238
272
|
}
|
|
239
273
|
} catch (error) {
|
|
240
274
|
console.error(error);
|
|
241
275
|
}
|
|
242
|
-
|
|
276
|
+
console.log("mls second, same?");
|
|
277
|
+
console.log(data);
|
|
243
278
|
const onsetGainNode = audioContext.createGain();
|
|
244
279
|
this.offsetGainNode = audioContext.createGain();
|
|
245
280
|
const source = audioContext.createBufferSource();
|
|
@@ -252,7 +287,6 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
252
287
|
|
|
253
288
|
const onsetCurve = ImpulseResponse.createSCurveBuffer(this.sourceSamplingRate, Math.PI / 2);
|
|
254
289
|
onsetGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
|
|
255
|
-
|
|
256
290
|
this.addCalibrationNode(source);
|
|
257
291
|
};
|
|
258
292
|
|
|
@@ -264,70 +298,47 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
264
298
|
*/
|
|
265
299
|
#setCalibrationNodesFromBuffer = (dataBufferArray = [this.#mlsBufferView]) => {
|
|
266
300
|
if (dataBufferArray.length === 1) {
|
|
301
|
+
console.log('data buffer aray');
|
|
302
|
+
console.log(dataBufferArray);
|
|
267
303
|
this.#createCalibrationNodeFromBuffer(dataBufferArray[0]);
|
|
268
304
|
} else {
|
|
269
305
|
throw new Error('The length of the data buffer array must be 1');
|
|
270
306
|
}
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
#createImpulseResponseFilterGraph = (calibrationSignal, iir) => {
|
|
274
|
-
const audioCtx = this.makeNewSourceAudioContext();
|
|
275
|
-
|
|
276
|
-
// -------------------------------------------------------- IIR
|
|
277
|
-
const iirBuffer = audioCtx.createBuffer(
|
|
278
|
-
1,
|
|
279
|
-
// TODO: quality check this
|
|
280
|
-
iir.length - 1,
|
|
281
|
-
audioCtx.sampleRate
|
|
282
|
-
);
|
|
283
307
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
for (let i = 0; i < iirBuffer.length; i += 1) {
|
|
287
|
-
// audio needs to be in [-1.0; 1.0]
|
|
288
|
-
iirChannelZeroBuffer[i] = iir[i];
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const convolverNode = audioCtx.createConvolver();
|
|
292
|
-
|
|
293
|
-
convolverNode.normalize = false;
|
|
294
|
-
convolverNode.channelCount = 1;
|
|
295
|
-
convolverNode.buffer = iirBuffer;
|
|
308
|
+
|
|
309
|
+
};
|
|
296
310
|
|
|
297
|
-
|
|
298
|
-
|
|
311
|
+
/**
|
|
312
|
+
* function to put MLS filtered IIR data obtained from
|
|
313
|
+
* python server into our audio buffer to be played aloud
|
|
314
|
+
*/
|
|
315
|
+
#putInPythonConv = () => {
|
|
316
|
+
const audioCtx = this.makeNewSourceAudioContextConvolved();
|
|
317
|
+
const buffer = audioCtx.createBuffer(
|
|
299
318
|
1, // number of channels
|
|
300
|
-
|
|
319
|
+
this.convolution.length,
|
|
301
320
|
audioCtx.sampleRate // sample rate
|
|
302
321
|
);
|
|
303
322
|
|
|
304
|
-
const
|
|
323
|
+
const data = buffer.getChannelData(0); // get data
|
|
305
324
|
// fill the buffer with our data
|
|
306
325
|
try {
|
|
307
|
-
for (let i = 0; i <
|
|
308
|
-
|
|
326
|
+
for (let i = 0; i < this.convolution.length; i += 1) {
|
|
327
|
+
data[i] = this.convolution[i];
|
|
309
328
|
}
|
|
310
329
|
} catch (error) {
|
|
311
330
|
console.error(error);
|
|
312
331
|
}
|
|
313
332
|
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
sourceNode.buffer = calibrationSignalBuffer;
|
|
317
|
-
sourceNode.loop = true;
|
|
318
|
-
sourceNode.connect(convolverNode);
|
|
319
|
-
|
|
320
|
-
convolverNode.connect(audioCtx.destination);
|
|
321
|
-
|
|
322
|
-
console.log({convolverNode, sourceNode});
|
|
323
|
-
|
|
324
|
-
this.addCalibrationNode(sourceNode);
|
|
325
|
-
};
|
|
333
|
+
const source = audioCtx.createBufferSource();
|
|
326
334
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
335
|
+
source.buffer = buffer;
|
|
336
|
+
source.loop = true;
|
|
337
|
+
source.connect(audioCtx.destination);
|
|
330
338
|
|
|
339
|
+
this.addCalibrationNodeConvolved(source);
|
|
340
|
+
}
|
|
341
|
+
|
|
331
342
|
/**
|
|
332
343
|
* Creates an audio context and plays it for a few seconds.
|
|
333
344
|
*
|
|
@@ -337,8 +348,16 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
337
348
|
*/
|
|
338
349
|
#playCalibrationAudio = () => {
|
|
339
350
|
this.calibrationNodes[0].start(0);
|
|
351
|
+
this.#mls = this.calibrationNodes[0].buffer.getChannelData(0);
|
|
352
|
+
console.log(this.#mls);
|
|
340
353
|
this.emit('update', {message: 'playing the calibration tone...'});
|
|
341
|
-
};
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
#playCalibrationAudioConvolved = () => {
|
|
358
|
+
this.calibrationNodesConvolved[0].start(0);
|
|
359
|
+
this.emit('update',{message: 'playing the convolved calibration tone...'})
|
|
360
|
+
}
|
|
342
361
|
|
|
343
362
|
/** .
|
|
344
363
|
* .
|
|
@@ -354,34 +373,53 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
354
373
|
);
|
|
355
374
|
|
|
356
375
|
this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContext.currentTime, 0.5);
|
|
376
|
+
this.calibrationNodes[0].stop(0);
|
|
377
|
+
this.sourceAudioContext.close();
|
|
357
378
|
this.emit('update', {message: 'stopping the calibration tone...'});
|
|
358
379
|
};
|
|
359
380
|
|
|
381
|
+
#stopCalibrationAudioConvolved = () => {
|
|
382
|
+
this.offsetGainNode.gain.setValueAtTime(
|
|
383
|
+
this.offsetGainNode.gain.value,
|
|
384
|
+
this.sourceAudioContextConvolved.currentTime
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
this.offsetGainNode.gain.setTargetAtTime(0, this.sourceAudioContextConvolved.currentTime, 0.5);
|
|
388
|
+
//this.calibrationNodesConvolved[0].stop(0);
|
|
389
|
+
console.log("right before closing volved audio context");
|
|
390
|
+
this.sourceAudioContextConvolved.close();
|
|
391
|
+
this.emit('update', {message: 'stopping the convolved calibration tone...'});
|
|
392
|
+
|
|
393
|
+
}
|
|
394
|
+
|
|
360
395
|
playMLSwithIIR = async (stream, iir) => {
|
|
396
|
+
console.log('play mls with iir');
|
|
361
397
|
this.invertedImpulseResponse = iir;
|
|
362
398
|
// initialize the MLSGenInterface object with it's factory method
|
|
399
|
+
|
|
363
400
|
await MlsGenInterface.factory(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
401
|
+
this.#mlsOrder,
|
|
402
|
+
this.sinkSamplingRate,
|
|
403
|
+
this.sourceSamplingRate
|
|
367
404
|
).then(mlsGenInterface => {
|
|
368
|
-
|
|
369
|
-
|
|
405
|
+
this.#mlsGenInterface = mlsGenInterface;
|
|
406
|
+
this.#mlsBufferView = this.#mlsGenInterface.getMLS();
|
|
370
407
|
});
|
|
371
408
|
|
|
409
|
+
console.log('after mls factory'); //works up to here.
|
|
410
|
+
console.log(this.#mls);
|
|
372
411
|
// after intializating, start the calibration steps with garbage collection
|
|
373
412
|
await this.#mlsGenInterface.withGarbageCollection([
|
|
374
|
-
|
|
375
|
-
this.calibrationSteps
|
|
376
|
-
[
|
|
413
|
+
() =>
|
|
414
|
+
this.calibrationSteps(
|
|
377
415
|
stream,
|
|
378
|
-
this.#
|
|
379
|
-
this.#
|
|
380
|
-
|
|
416
|
+
this.#playCalibrationAudioConvolved, // play audio func (required)
|
|
417
|
+
this.#putInPythonConv, // before play func
|
|
418
|
+
this.#awaitSignalOnset, // before record
|
|
419
|
+
() => this.numSuccessfulCaptured < this.numCaptures,
|
|
381
420
|
this.#awaitDesiredMLSLength, // during record
|
|
382
421
|
this.#afterMLSwIIRRecord, // after record
|
|
383
|
-
|
|
384
|
-
],
|
|
422
|
+
),
|
|
385
423
|
]);
|
|
386
424
|
};
|
|
387
425
|
|
|
@@ -426,8 +464,22 @@ class ImpulseResponse extends AudioCalibrator {
|
|
|
426
464
|
// so let's send all the IRs to the server to be converted to a single IIR
|
|
427
465
|
await this.sendImpulseResponsesToServerForProcessing();
|
|
428
466
|
|
|
467
|
+
if (this.#download) {
|
|
468
|
+
saveToCSV(this.invertedImpulseResponse,'IIR.csv');
|
|
469
|
+
const computedIRagain = await Promise.all(this.impulseResponses)
|
|
470
|
+
.then(res => {
|
|
471
|
+
for (let i = 0; i < res.length; i++){
|
|
472
|
+
saveToCSV(res[i], `IR_${i}`);
|
|
473
|
+
}
|
|
474
|
+
})
|
|
475
|
+
saveToCSV(this.#mls,"MLS.csv");
|
|
476
|
+
saveToCSV(this.convolution,'python_convolution_mls_iir.csv');
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
this.numSuccessfulCaptured = 0;
|
|
429
480
|
// debugging function, use to test the result of the IIR
|
|
430
|
-
|
|
481
|
+
await this.playMLSwithIIR(stream, this.invertedImpulseResponse);
|
|
482
|
+
this.#stopCalibrationAudioConvolved();
|
|
431
483
|
|
|
432
484
|
return this.invertedImpulseResponse;
|
|
433
485
|
};
|