speaker-calibration 2.2.256 → 2.2.257
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/.eslintignore +71 -71
- package/.eslintrc.json +40 -40
- package/.gitignore +81 -0
- package/.prettierignore +69 -69
- package/.prettierrc +14 -14
- package/LICENSE +20 -20
- package/README.md +133 -133
- package/__mocks__/fileMock.js +1 -1
- package/__mocks__/styleMock.js +1 -1
- package/babel.config.js +3 -3
- package/coverage/clover.xml +71 -71
- package/coverage/coverage-final.json +224 -224
- package/coverage/lcov-report/PythonServerInterface.js.html +265 -265
- package/coverage/lcov-report/base.css +354 -354
- package/coverage/lcov-report/block-navigation.js +82 -82
- package/coverage/lcov-report/index.html +123 -123
- package/coverage/lcov-report/prettify.css +101 -101
- package/coverage/lcov-report/prettify.js +937 -937
- package/coverage/lcov-report/sorter.js +189 -189
- package/coverage/lcov-report/src/index.html +121 -121
- package/coverage/lcov-report/src/server/PythonServerInterface.js.html +268 -268
- package/coverage/lcov-report/src/server/index.html +123 -123
- package/coverage/lcov-report/src/tasks/audioCalibrator.js.html +499 -499
- package/coverage/lcov-report/src/tasks/audioRecorder.js.html +412 -412
- package/coverage/lcov-report/src/tasks/index.html +143 -143
- package/coverage/lcov-report/src/tasks/volume/index.html +123 -123
- package/coverage/lcov-report/src/tasks/volume/volume.js.html +409 -409
- package/coverage/lcov-report/src/utils.js.html +172 -172
- package/coverage/lcov.info +91 -91
- package/dist/example/NoSleep.min.js +1 -1
- package/dist/example/fetch-languages-sheets.js +77 -77
- package/dist/example/i18n.js +29038 -29654
- package/dist/example/index.html +47 -47
- package/dist/example/listener.html +81 -81
- package/dist/example/server.js +51 -51
- package/dist/example/speaker.html +145 -145
- package/dist/example/speakerUI.js +273 -273
- package/dist/example/styles.css +152 -152
- package/dist/listener.js +3 -3
- package/dist/main.js +9 -9
- package/dist/mlsGen.js +6814 -6814
- package/dist/mlsGen.wasm +0 -0
- package/dist/package-lock.json +1018 -1018
- package/dist/package.json +18 -18
- package/dist/phonePeer.js +3 -3
- package/doc/AudioCalibrator.html +417 -417
- package/doc/AudioPeer.html +251 -251
- package/doc/AudioRecorder.html +195 -195
- package/doc/ImpulseResponse.html +215 -215
- package/doc/Listener.html +308 -308
- package/doc/MlsGenInterface.html +226 -226
- package/doc/MyEventEmitter.html +274 -274
- package/doc/PythonServerAPI.html +109 -109
- package/doc/Speaker.html +276 -276
- package/doc/Takes%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +128 -128
- package/doc/Takes%20the%20url%20of%20the%20current%20site%0Aand%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +138 -138
- package/doc/Takes%20the%20url%20of%20the%20current%20site%20and%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +137 -137
- package/doc/Volume.html +88 -88
- package/doc/audioCalibrator.js.html +179 -179
- package/doc/audioPeer.js.html +175 -175
- package/doc/audioRecorder.js.html +163 -163
- package/doc/creates%20a%20new%20AudioRecorder%20instance.%20%0ASets%20up%20the%20audio%20context%20and%20file%20reader..html +114 -114
- package/doc/fonts/OpenSans-Bold-webfont.svg +1829 -1829
- package/doc/fonts/OpenSans-BoldItalic-webfont.svg +1829 -1829
- package/doc/fonts/OpenSans-Italic-webfont.svg +1829 -1829
- package/doc/fonts/OpenSans-Light-webfont.svg +1830 -1830
- package/doc/fonts/OpenSans-LightItalic-webfont.svg +1834 -1834
- package/doc/fonts/OpenSans-Regular-webfont.svg +1830 -1830
- package/doc/global.html +308 -308
- package/doc/index.html +58 -58
- package/doc/listener.js.html +170 -170
- package/doc/mlsGen_mlsGenInterface.js.html +117 -117
- package/doc/myEventEmitter.js.html +124 -124
- package/doc/peer-connection_audioPeer.js.html +188 -188
- package/doc/peer-connection_listener.js.html +311 -311
- package/doc/peer-connection_speaker.js.html +381 -381
- package/doc/scripts/linenumber.js +25 -25
- package/doc/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/doc/scripts/prettify/lang-css.js +24 -24
- package/doc/scripts/prettify/prettify.js +640 -640
- package/doc/server_PythonServerAPI.js.html +160 -160
- package/doc/speaker.js.html +248 -248
- package/doc/styles/jsdoc-default.css +371 -371
- package/doc/styles/prettify-jsdoc.css +111 -111
- package/doc/styles/prettify-tomorrow.css +163 -163
- package/doc/tasks_audioCalibrator.js.html +207 -207
- package/doc/tasks_audioRecorder.js.html +190 -190
- package/doc/tasks_impulse-response_impulseResponse.js.html +442 -442
- package/doc/tasks_impulse-response_mlsGen_mlsGenInterface.js.html +175 -175
- package/doc/tasks_volume_volume.js.html +185 -185
- package/doc/utils.js.html +105 -105
- package/jest.config.js +173 -173
- package/netlify.toml +26 -26
- package/package.json +78 -78
- package/src/config/firebase.js +26 -26
- package/src/index.html +21 -21
- package/src/listener-app/PhonePeer.js +499 -499
- package/src/listener-app/listener.js +380 -380
- package/src/main.js +22 -22
- package/src/myEventEmitter.js +83 -83
- package/src/peer-connection/audioPeer.js +100 -100
- package/src/peer-connection/listener.js +298 -298
- package/src/peer-connection/peerErrors.js +25 -25
- package/src/peer-connection/speaker.js +983 -983
- package/src/powerCheck.js +110 -110
- package/src/server/PythonServerAPI.js +1001 -1001
- package/src/tasks/combination/combination.js +3927 -3918
- package/src/tasks/combination/mlsGen/mlsGen.cpp +98 -98
- package/src/tasks/combination/mlsGen/mlsGen.hpp +303 -303
- package/src/tasks/combination/mlsGen/mlsGenInterface.js +131 -131
- package/src/tasks/combination/mlsGen/mlsGenTest.cpp +180 -180
- package/src/tasks/impulse-response/impulseResponse.js +610 -610
- package/src/tasks/impulse-response/mlsGen/mlsGen.cpp +98 -98
- package/src/tasks/impulse-response/mlsGen/mlsGen.hpp +303 -303
- package/src/tasks/impulse-response/mlsGen/mlsGenInterface.js +131 -131
- package/src/tasks/impulse-response/mlsGen/mlsGenTest.cpp +180 -180
- package/src/tasks/volume/volume.cpp +2 -2
- package/src/tasks/volume/volume.hpp +22 -22
- package/src/tasks/volume/volume.js +279 -279
- package/src/utils.js +215 -205
- package/webpack.config.js +65 -65
- package/.github/workflows/update-phrases.yml +0 -37
- package/makefile +0 -74
|
@@ -1,279 +1,279 @@
|
|
|
1
|
-
import AudioCalibrator from '../audioCalibrator';
|
|
2
|
-
import axios from 'axios';
|
|
3
|
-
import {sleep} from '../../utils';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
7
|
-
*/
|
|
8
|
-
class Volume extends AudioCalibrator {
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
* @param root0
|
|
12
|
-
* @param root0.download
|
|
13
|
-
* @param root0.numCalibrationRounds
|
|
14
|
-
* @param root0.numCalibrationNodes
|
|
15
|
-
* @example
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/** @private */
|
|
19
|
-
#CALIBRATION_TONE_FREQUENCY = 1000; // Hz
|
|
20
|
-
|
|
21
|
-
/** @private */
|
|
22
|
-
#CALIBRATION_TONE_TYPE = 'sine';
|
|
23
|
-
|
|
24
|
-
/** @private */
|
|
25
|
-
#CALIBRATION_TONE_DURATION = 5; // seconds
|
|
26
|
-
|
|
27
|
-
/** @private */
|
|
28
|
-
outDBSPL = null;
|
|
29
|
-
THD = null;
|
|
30
|
-
outDBSPL1000 = null;
|
|
31
|
-
|
|
32
|
-
/** @private */
|
|
33
|
-
TAPER_SECS = 0.010; // seconds
|
|
34
|
-
|
|
35
|
-
/** @private */
|
|
36
|
-
status_denominator = 2;
|
|
37
|
-
|
|
38
|
-
/** @private */
|
|
39
|
-
status_numerator = 0;
|
|
40
|
-
|
|
41
|
-
/** @private */
|
|
42
|
-
percent_complete = 0;
|
|
43
|
-
|
|
44
|
-
/** @private */
|
|
45
|
-
status = ``;
|
|
46
|
-
|
|
47
|
-
/**generate string template that gets reevaluated as variable increases */
|
|
48
|
-
generateTemplate = () => {
|
|
49
|
-
if (this.percent_complete > 100){
|
|
50
|
-
this.percent_complete = 100;
|
|
51
|
-
}
|
|
52
|
-
const template = `<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>`;
|
|
53
|
-
return template;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** increment numerator and percent for status bar */
|
|
57
|
-
incrementStatusBar = () => {
|
|
58
|
-
this.status_numerator += 1;
|
|
59
|
-
this.percent_complete = (this.status_numerator/this.status_denominator)*100;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
handleIncomingData = data => {
|
|
63
|
-
console.log('Received data: ', data);
|
|
64
|
-
if (data.type === 'soundGainDBSPL') {
|
|
65
|
-
this.soundGainDBSPL = data.value;
|
|
66
|
-
} else {
|
|
67
|
-
throw new Error(`Unknown data type: ${data.type}`);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
createSCurveBuffer = (onSetBool=true) => {
|
|
72
|
-
|
|
73
|
-
const curve = new Float32Array(this.TAPER_SECS*this.sourceSamplingRate+1);
|
|
74
|
-
const frequency = 1 / (4 * this.TAPER_SECS);
|
|
75
|
-
let j = 0;
|
|
76
|
-
for (let i = 0; i < this.TAPER_SECS*this.sourceSamplingRate+1; i += 1) {
|
|
77
|
-
const phase = 2 * Math.PI * frequency * j;
|
|
78
|
-
const onsetTaper = Math.pow(Math.sin(phase) , 2);
|
|
79
|
-
const offsetTaper = Math.pow(Math.cos(phase) , 2);
|
|
80
|
-
curve[i] = onSetBool? onsetTaper : offsetTaper;
|
|
81
|
-
j += (1 / this.sourceSamplingRate);
|
|
82
|
-
}
|
|
83
|
-
return curve;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
#getTruncatedSignal = (left = 3.5, right = 4.5) => {
|
|
87
|
-
const start = Math.floor(left * this.sourceSamplingRate);
|
|
88
|
-
const end = Math.floor(right * this.sourceSamplingRate);
|
|
89
|
-
const result = Array.from(this.getLastRecordedSignal().slice(start, end));
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* function to check that capture was properly made
|
|
93
|
-
* @param {*} list
|
|
94
|
-
*/
|
|
95
|
-
const checkResult = list => {
|
|
96
|
-
const setItem = new Set(list);
|
|
97
|
-
if (setItem.size === 1 && setItem.has(0)) {
|
|
98
|
-
console.warn(
|
|
99
|
-
'The last capture failed, all recorded signal is zero',
|
|
100
|
-
this.getAllRecordedSignals()
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
if (setItem.size === 0) {
|
|
104
|
-
console.warn('The last capture failed, no recorded signal');
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
checkResult(result);
|
|
108
|
-
return result;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
Construct a calibration Node with the calibration parameters and given gain value
|
|
115
|
-
* @param {*} gainValue
|
|
116
|
-
* */
|
|
117
|
-
#createCalibrationToneWithGainValue = gainValue => {
|
|
118
|
-
const audioContext = this.makeNewSourceAudioContext();
|
|
119
|
-
const oscilator = audioContext.createOscillator();
|
|
120
|
-
const gainNode = audioContext.createGain();
|
|
121
|
-
const taperGainNode = audioContext.createGain();
|
|
122
|
-
const offsetGainNode = audioContext.createGain();
|
|
123
|
-
const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;
|
|
124
|
-
|
|
125
|
-
oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
|
|
126
|
-
oscilator.type = this.#CALIBRATION_TONE_TYPE;
|
|
127
|
-
gainNode.gain.value = gainValue;
|
|
128
|
-
|
|
129
|
-
oscilator.connect(gainNode);
|
|
130
|
-
gainNode.connect(taperGainNode);
|
|
131
|
-
const onsetCurve = this.createSCurveBuffer();
|
|
132
|
-
taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
|
|
133
|
-
taperGainNode.connect(offsetGainNode);
|
|
134
|
-
const offsetCurve = this.createSCurveBuffer(false);
|
|
135
|
-
offsetGainNode.gain.setValueCurveAtTime(offsetCurve, (totalDuration-this.TAPER_SECS), this.TAPER_SECS);
|
|
136
|
-
offsetGainNode.connect(audioContext.destination);
|
|
137
|
-
|
|
138
|
-
this.addCalibrationNode(oscilator);
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Construct a Calibration Node with the calibration parameters.
|
|
143
|
-
*
|
|
144
|
-
* @private
|
|
145
|
-
* @example
|
|
146
|
-
*/
|
|
147
|
-
#createCalibrationNode = () => {
|
|
148
|
-
const audioContext = this.makeNewSourceAudioContext();
|
|
149
|
-
const oscilator = audioContext.createOscillator();
|
|
150
|
-
const gainNode = audioContext.createGain();
|
|
151
|
-
|
|
152
|
-
oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
|
|
153
|
-
oscilator.type = this.#CALIBRATION_TONE_TYPE;
|
|
154
|
-
gainNode.gain.value = 0.04;
|
|
155
|
-
|
|
156
|
-
oscilator.connect(gainNode);
|
|
157
|
-
gainNode.connect(audioContext.destination);
|
|
158
|
-
|
|
159
|
-
this.addCalibrationNode(oscilator);
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
#playCalibrationAudio = async () => {
|
|
163
|
-
const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;
|
|
164
|
-
|
|
165
|
-
this.calibrationNodes[0].start(0);
|
|
166
|
-
this.calibrationNodes[0].stop(totalDuration);
|
|
167
|
-
console.log(`Playing a buffer of ${this.#CALIBRATION_TONE_DURATION} seconds of audio`);
|
|
168
|
-
console.log(`Waiting a total of ${totalDuration} seconds`);
|
|
169
|
-
await sleep(totalDuration);
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
#sendToServerForProcessing = (lCalib = 104.92978421490648) => {
|
|
173
|
-
console.log('Sending data to server');
|
|
174
|
-
this.pyServerAPI
|
|
175
|
-
.getVolumeCalibration({
|
|
176
|
-
sampleRate: this.sourceSamplingRate,
|
|
177
|
-
payload: this.#getTruncatedSignal(),
|
|
178
|
-
lCalib: lCalib,
|
|
179
|
-
})
|
|
180
|
-
.then(res => {
|
|
181
|
-
if (this.outDBSPL === null) {
|
|
182
|
-
this.incrementStatusBar();
|
|
183
|
-
this.outDBSPL = res['outDbSPL'];
|
|
184
|
-
this.outDBSPL1000 = res['outDbSPL1000'];
|
|
185
|
-
this.THD = res['thd'];
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
.catch(err => {
|
|
189
|
-
console.warn(err);
|
|
190
|
-
});
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
startCalibration = async (stream, gainValues, lCalib = 104.92978421490648) => {
|
|
194
|
-
const trialIterations = gainValues.length;
|
|
195
|
-
this.status_denominator += trialIterations;
|
|
196
|
-
const thdValues = [];
|
|
197
|
-
const inDBValues = [];
|
|
198
|
-
let inDB = 0;
|
|
199
|
-
const outDBSPLValues = [];
|
|
200
|
-
const outDBSPL1000Values = [];
|
|
201
|
-
|
|
202
|
-
// do one calibration that will be discarded
|
|
203
|
-
const soundLevelToDiscard = -60;
|
|
204
|
-
const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);
|
|
205
|
-
this.status = `Sound Level: ${soundLevelToDiscard} dB`.toString() + this.generateTemplate().toString();
|
|
206
|
-
this.emit('update', {message: this.status});
|
|
207
|
-
do {
|
|
208
|
-
// eslint-disable-next-line no-await-in-loop
|
|
209
|
-
await this.volumeCalibrationSteps(
|
|
210
|
-
stream,
|
|
211
|
-
this.#playCalibrationAudio,
|
|
212
|
-
this.#createCalibrationToneWithGainValue,
|
|
213
|
-
this.#sendToServerForProcessing,
|
|
214
|
-
gainToDiscard,
|
|
215
|
-
lCalib //todo make this a class parameter
|
|
216
|
-
);
|
|
217
|
-
} while (this.outDBSPL === null);
|
|
218
|
-
//reset the values
|
|
219
|
-
this.outDBSPL = null;
|
|
220
|
-
this.outDBSPL = null;
|
|
221
|
-
this.outDBSPL1000 = null;
|
|
222
|
-
this.THD = null;
|
|
223
|
-
|
|
224
|
-
// run the calibration at different gain values provided by the user
|
|
225
|
-
for (let i = 0; i < trialIterations; i++) {
|
|
226
|
-
//convert gain to DB and add to inDB
|
|
227
|
-
inDB = Math.log10(gainValues[i]) * 20;
|
|
228
|
-
// precision to 1 decimal place
|
|
229
|
-
inDB = Math.round(inDB * 10) / 10;
|
|
230
|
-
inDBValues.push(inDB);
|
|
231
|
-
this.status = `Sound Level: ${inDB} dB`.toString() + this.generateTemplate().toString();
|
|
232
|
-
this.emit('update', {message: this.status});
|
|
233
|
-
do {
|
|
234
|
-
// eslint-disable-next-line no-await-in-loop
|
|
235
|
-
await this.volumeCalibrationSteps(
|
|
236
|
-
stream,
|
|
237
|
-
this.#playCalibrationAudio,
|
|
238
|
-
this.#createCalibrationToneWithGainValue,
|
|
239
|
-
this.#sendToServerForProcessing,
|
|
240
|
-
gainValues[i],
|
|
241
|
-
lCalib //todo make this a class parameter
|
|
242
|
-
);
|
|
243
|
-
} while (this.outDBSPL === null);
|
|
244
|
-
outDBSPL1000Values.push(this.outDBSPL1000);
|
|
245
|
-
thdValues.push(this.THD);
|
|
246
|
-
outDBSPLValues.push(this.outDBSPL);
|
|
247
|
-
|
|
248
|
-
this.outDBSPL = null;
|
|
249
|
-
this.outDBSPL1000 = null;
|
|
250
|
-
this.THD = null;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// get the volume calibration parameters from the server
|
|
254
|
-
|
|
255
|
-
const parameters = await this.pyServerAPI
|
|
256
|
-
.getVolumeCalibrationParameters({
|
|
257
|
-
inDBValues: inDBValues,
|
|
258
|
-
outDBSPLValues: outDBSPL1000Values,
|
|
259
|
-
lCalib: lCalib,
|
|
260
|
-
})
|
|
261
|
-
.then(res => {
|
|
262
|
-
this.incrementStatusBar();
|
|
263
|
-
this.status = `done with 1000 Hz calibration`.toString() + this.generateTemplate().toString();
|
|
264
|
-
this.emit('update', {message: this.status});
|
|
265
|
-
return res;
|
|
266
|
-
});
|
|
267
|
-
const result = {
|
|
268
|
-
parameters: parameters,
|
|
269
|
-
inDBValues: inDBValues,
|
|
270
|
-
outDBSPLValues: outDBSPLValues,
|
|
271
|
-
outDBSPL1000Values: outDBSPL1000Values,
|
|
272
|
-
thdValues: thdValues,
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
return result;
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export default Volume;
|
|
1
|
+
import AudioCalibrator from '../audioCalibrator';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import {sleep} from '../../utils';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
class Volume extends AudioCalibrator {
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* @param root0
|
|
12
|
+
* @param root0.download
|
|
13
|
+
* @param root0.numCalibrationRounds
|
|
14
|
+
* @param root0.numCalibrationNodes
|
|
15
|
+
* @example
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** @private */
|
|
19
|
+
#CALIBRATION_TONE_FREQUENCY = 1000; // Hz
|
|
20
|
+
|
|
21
|
+
/** @private */
|
|
22
|
+
#CALIBRATION_TONE_TYPE = 'sine';
|
|
23
|
+
|
|
24
|
+
/** @private */
|
|
25
|
+
#CALIBRATION_TONE_DURATION = 5; // seconds
|
|
26
|
+
|
|
27
|
+
/** @private */
|
|
28
|
+
outDBSPL = null;
|
|
29
|
+
THD = null;
|
|
30
|
+
outDBSPL1000 = null;
|
|
31
|
+
|
|
32
|
+
/** @private */
|
|
33
|
+
TAPER_SECS = 0.010; // seconds
|
|
34
|
+
|
|
35
|
+
/** @private */
|
|
36
|
+
status_denominator = 2;
|
|
37
|
+
|
|
38
|
+
/** @private */
|
|
39
|
+
status_numerator = 0;
|
|
40
|
+
|
|
41
|
+
/** @private */
|
|
42
|
+
percent_complete = 0;
|
|
43
|
+
|
|
44
|
+
/** @private */
|
|
45
|
+
status = ``;
|
|
46
|
+
|
|
47
|
+
/**generate string template that gets reevaluated as variable increases */
|
|
48
|
+
generateTemplate = () => {
|
|
49
|
+
if (this.percent_complete > 100){
|
|
50
|
+
this.percent_complete = 100;
|
|
51
|
+
}
|
|
52
|
+
const template = `<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>`;
|
|
53
|
+
return template;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** increment numerator and percent for status bar */
|
|
57
|
+
incrementStatusBar = () => {
|
|
58
|
+
this.status_numerator += 1;
|
|
59
|
+
this.percent_complete = (this.status_numerator/this.status_denominator)*100;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
handleIncomingData = data => {
|
|
63
|
+
console.log('Received data: ', data);
|
|
64
|
+
if (data.type === 'soundGainDBSPL') {
|
|
65
|
+
this.soundGainDBSPL = data.value;
|
|
66
|
+
} else {
|
|
67
|
+
throw new Error(`Unknown data type: ${data.type}`);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
createSCurveBuffer = (onSetBool=true) => {
|
|
72
|
+
|
|
73
|
+
const curve = new Float32Array(this.TAPER_SECS*this.sourceSamplingRate+1);
|
|
74
|
+
const frequency = 1 / (4 * this.TAPER_SECS);
|
|
75
|
+
let j = 0;
|
|
76
|
+
for (let i = 0; i < this.TAPER_SECS*this.sourceSamplingRate+1; i += 1) {
|
|
77
|
+
const phase = 2 * Math.PI * frequency * j;
|
|
78
|
+
const onsetTaper = Math.pow(Math.sin(phase) , 2);
|
|
79
|
+
const offsetTaper = Math.pow(Math.cos(phase) , 2);
|
|
80
|
+
curve[i] = onSetBool? onsetTaper : offsetTaper;
|
|
81
|
+
j += (1 / this.sourceSamplingRate);
|
|
82
|
+
}
|
|
83
|
+
return curve;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
#getTruncatedSignal = (left = 3.5, right = 4.5) => {
|
|
87
|
+
const start = Math.floor(left * this.sourceSamplingRate);
|
|
88
|
+
const end = Math.floor(right * this.sourceSamplingRate);
|
|
89
|
+
const result = Array.from(this.getLastRecordedSignal().slice(start, end));
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* function to check that capture was properly made
|
|
93
|
+
* @param {*} list
|
|
94
|
+
*/
|
|
95
|
+
const checkResult = list => {
|
|
96
|
+
const setItem = new Set(list);
|
|
97
|
+
if (setItem.size === 1 && setItem.has(0)) {
|
|
98
|
+
console.warn(
|
|
99
|
+
'The last capture failed, all recorded signal is zero',
|
|
100
|
+
this.getAllRecordedSignals()
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (setItem.size === 0) {
|
|
104
|
+
console.warn('The last capture failed, no recorded signal');
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
checkResult(result);
|
|
108
|
+
return result;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
*
|
|
113
|
+
*
|
|
114
|
+
Construct a calibration Node with the calibration parameters and given gain value
|
|
115
|
+
* @param {*} gainValue
|
|
116
|
+
* */
|
|
117
|
+
#createCalibrationToneWithGainValue = gainValue => {
|
|
118
|
+
const audioContext = this.makeNewSourceAudioContext();
|
|
119
|
+
const oscilator = audioContext.createOscillator();
|
|
120
|
+
const gainNode = audioContext.createGain();
|
|
121
|
+
const taperGainNode = audioContext.createGain();
|
|
122
|
+
const offsetGainNode = audioContext.createGain();
|
|
123
|
+
const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;
|
|
124
|
+
|
|
125
|
+
oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
|
|
126
|
+
oscilator.type = this.#CALIBRATION_TONE_TYPE;
|
|
127
|
+
gainNode.gain.value = gainValue;
|
|
128
|
+
|
|
129
|
+
oscilator.connect(gainNode);
|
|
130
|
+
gainNode.connect(taperGainNode);
|
|
131
|
+
const onsetCurve = this.createSCurveBuffer();
|
|
132
|
+
taperGainNode.gain.setValueCurveAtTime(onsetCurve, 0, this.TAPER_SECS);
|
|
133
|
+
taperGainNode.connect(offsetGainNode);
|
|
134
|
+
const offsetCurve = this.createSCurveBuffer(false);
|
|
135
|
+
offsetGainNode.gain.setValueCurveAtTime(offsetCurve, (totalDuration-this.TAPER_SECS), this.TAPER_SECS);
|
|
136
|
+
offsetGainNode.connect(audioContext.destination);
|
|
137
|
+
|
|
138
|
+
this.addCalibrationNode(oscilator);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Construct a Calibration Node with the calibration parameters.
|
|
143
|
+
*
|
|
144
|
+
* @private
|
|
145
|
+
* @example
|
|
146
|
+
*/
|
|
147
|
+
#createCalibrationNode = () => {
|
|
148
|
+
const audioContext = this.makeNewSourceAudioContext();
|
|
149
|
+
const oscilator = audioContext.createOscillator();
|
|
150
|
+
const gainNode = audioContext.createGain();
|
|
151
|
+
|
|
152
|
+
oscilator.frequency.value = this.#CALIBRATION_TONE_FREQUENCY;
|
|
153
|
+
oscilator.type = this.#CALIBRATION_TONE_TYPE;
|
|
154
|
+
gainNode.gain.value = 0.04;
|
|
155
|
+
|
|
156
|
+
oscilator.connect(gainNode);
|
|
157
|
+
gainNode.connect(audioContext.destination);
|
|
158
|
+
|
|
159
|
+
this.addCalibrationNode(oscilator);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
#playCalibrationAudio = async () => {
|
|
163
|
+
const totalDuration = this.#CALIBRATION_TONE_DURATION * 1.2;
|
|
164
|
+
|
|
165
|
+
this.calibrationNodes[0].start(0);
|
|
166
|
+
this.calibrationNodes[0].stop(totalDuration);
|
|
167
|
+
console.log(`Playing a buffer of ${this.#CALIBRATION_TONE_DURATION} seconds of audio`);
|
|
168
|
+
console.log(`Waiting a total of ${totalDuration} seconds`);
|
|
169
|
+
await sleep(totalDuration);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
#sendToServerForProcessing = (lCalib = 104.92978421490648) => {
|
|
173
|
+
console.log('Sending data to server');
|
|
174
|
+
this.pyServerAPI
|
|
175
|
+
.getVolumeCalibration({
|
|
176
|
+
sampleRate: this.sourceSamplingRate,
|
|
177
|
+
payload: this.#getTruncatedSignal(),
|
|
178
|
+
lCalib: lCalib,
|
|
179
|
+
})
|
|
180
|
+
.then(res => {
|
|
181
|
+
if (this.outDBSPL === null) {
|
|
182
|
+
this.incrementStatusBar();
|
|
183
|
+
this.outDBSPL = res['outDbSPL'];
|
|
184
|
+
this.outDBSPL1000 = res['outDbSPL1000'];
|
|
185
|
+
this.THD = res['thd'];
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
.catch(err => {
|
|
189
|
+
console.warn(err);
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
startCalibration = async (stream, gainValues, lCalib = 104.92978421490648) => {
|
|
194
|
+
const trialIterations = gainValues.length;
|
|
195
|
+
this.status_denominator += trialIterations;
|
|
196
|
+
const thdValues = [];
|
|
197
|
+
const inDBValues = [];
|
|
198
|
+
let inDB = 0;
|
|
199
|
+
const outDBSPLValues = [];
|
|
200
|
+
const outDBSPL1000Values = [];
|
|
201
|
+
|
|
202
|
+
// do one calibration that will be discarded
|
|
203
|
+
const soundLevelToDiscard = -60;
|
|
204
|
+
const gainToDiscard = Math.pow(10, soundLevelToDiscard / 20);
|
|
205
|
+
this.status = `Sound Level: ${soundLevelToDiscard} dB`.toString() + this.generateTemplate().toString();
|
|
206
|
+
this.emit('update', {message: this.status});
|
|
207
|
+
do {
|
|
208
|
+
// eslint-disable-next-line no-await-in-loop
|
|
209
|
+
await this.volumeCalibrationSteps(
|
|
210
|
+
stream,
|
|
211
|
+
this.#playCalibrationAudio,
|
|
212
|
+
this.#createCalibrationToneWithGainValue,
|
|
213
|
+
this.#sendToServerForProcessing,
|
|
214
|
+
gainToDiscard,
|
|
215
|
+
lCalib //todo make this a class parameter
|
|
216
|
+
);
|
|
217
|
+
} while (this.outDBSPL === null);
|
|
218
|
+
//reset the values
|
|
219
|
+
this.outDBSPL = null;
|
|
220
|
+
this.outDBSPL = null;
|
|
221
|
+
this.outDBSPL1000 = null;
|
|
222
|
+
this.THD = null;
|
|
223
|
+
|
|
224
|
+
// run the calibration at different gain values provided by the user
|
|
225
|
+
for (let i = 0; i < trialIterations; i++) {
|
|
226
|
+
//convert gain to DB and add to inDB
|
|
227
|
+
inDB = Math.log10(gainValues[i]) * 20;
|
|
228
|
+
// precision to 1 decimal place
|
|
229
|
+
inDB = Math.round(inDB * 10) / 10;
|
|
230
|
+
inDBValues.push(inDB);
|
|
231
|
+
this.status = `Sound Level: ${inDB} dB`.toString() + this.generateTemplate().toString();
|
|
232
|
+
this.emit('update', {message: this.status});
|
|
233
|
+
do {
|
|
234
|
+
// eslint-disable-next-line no-await-in-loop
|
|
235
|
+
await this.volumeCalibrationSteps(
|
|
236
|
+
stream,
|
|
237
|
+
this.#playCalibrationAudio,
|
|
238
|
+
this.#createCalibrationToneWithGainValue,
|
|
239
|
+
this.#sendToServerForProcessing,
|
|
240
|
+
gainValues[i],
|
|
241
|
+
lCalib //todo make this a class parameter
|
|
242
|
+
);
|
|
243
|
+
} while (this.outDBSPL === null);
|
|
244
|
+
outDBSPL1000Values.push(this.outDBSPL1000);
|
|
245
|
+
thdValues.push(this.THD);
|
|
246
|
+
outDBSPLValues.push(this.outDBSPL);
|
|
247
|
+
|
|
248
|
+
this.outDBSPL = null;
|
|
249
|
+
this.outDBSPL1000 = null;
|
|
250
|
+
this.THD = null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// get the volume calibration parameters from the server
|
|
254
|
+
|
|
255
|
+
const parameters = await this.pyServerAPI
|
|
256
|
+
.getVolumeCalibrationParameters({
|
|
257
|
+
inDBValues: inDBValues,
|
|
258
|
+
outDBSPLValues: outDBSPL1000Values,
|
|
259
|
+
lCalib: lCalib,
|
|
260
|
+
})
|
|
261
|
+
.then(res => {
|
|
262
|
+
this.incrementStatusBar();
|
|
263
|
+
this.status = `done with 1000 Hz calibration`.toString() + this.generateTemplate().toString();
|
|
264
|
+
this.emit('update', {message: this.status});
|
|
265
|
+
return res;
|
|
266
|
+
});
|
|
267
|
+
const result = {
|
|
268
|
+
parameters: parameters,
|
|
269
|
+
inDBValues: inDBValues,
|
|
270
|
+
outDBSPLValues: outDBSPLValues,
|
|
271
|
+
outDBSPL1000Values: outDBSPL1000Values,
|
|
272
|
+
thdValues: thdValues,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
return result;
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export default Volume;
|