1 | const log = require('./log');
|
2 |
|
3 | class Loudness {
|
4 | /**
|
5 | * Instrument and detect a loudness value from a local microphone.
|
6 | * @param {AudioContext} audioContext - context to create nodes from for
|
7 | * detecting loudness
|
8 | * @constructor
|
9 | */
|
10 | constructor (audioContext) {
|
11 | /**
|
12 | * AudioContext the mic will connect to and provide analysis of
|
13 | * @type {AudioContext}
|
14 | */
|
15 | this.audioContext = audioContext;
|
16 |
|
17 | /**
|
18 | * Are we connecting to the mic yet?
|
19 | * @type {Boolean}
|
20 | */
|
21 | this.connectingToMic = false;
|
22 |
|
23 | /**
|
24 | * microphone, for measuring loudness, with a level meter analyzer
|
25 | * @type {MediaStreamSourceNode}
|
26 | */
|
27 | this.mic = null;
|
28 | }
|
29 |
|
30 | /**
|
31 | * Get the current loudness of sound received by the microphone.
|
32 | * Sound is measured in RMS and smoothed.
|
33 | * Some code adapted from Tone.js: https://github.com/Tonejs/Tone.js
|
34 | * @return {number} loudness scaled 0 to 100
|
35 | */
|
36 | getLoudness () {
|
37 | // The microphone has not been set up, so try to connect to it
|
38 | if (!this.mic && !this.connectingToMic) {
|
39 | this.connectingToMic = true; // prevent multiple connection attempts
|
40 | navigator.mediaDevices.getUserMedia({audio: true}).then(stream => {
|
41 | this.audioStream = stream;
|
42 | this.mic = this.audioContext.createMediaStreamSource(stream);
|
43 | this.analyser = this.audioContext.createAnalyser();
|
44 | this.mic.connect(this.analyser);
|
45 | this.micDataArray = new Float32Array(this.analyser.fftSize);
|
46 | })
|
47 | .catch(err => {
|
48 | log.warn(err);
|
49 | });
|
50 | }
|
51 |
|
52 | // If the microphone is set up and active, measure the loudness
|
53 | if (this.mic && this.audioStream.active) {
|
54 | this.analyser.getFloatTimeDomainData(this.micDataArray);
|
55 | let sum = 0;
|
56 | // compute the RMS of the sound
|
57 | for (let i = 0; i < this.micDataArray.length; i++){
|
58 | sum += Math.pow(this.micDataArray[i], 2);
|
59 | }
|
60 | let rms = Math.sqrt(sum / this.micDataArray.length);
|
61 | // smooth the value, if it is descending
|
62 | if (this._lastValue) {
|
63 | rms = Math.max(rms, this._lastValue * 0.6);
|
64 | }
|
65 | this._lastValue = rms;
|
66 |
|
67 | // Scale the measurement so it's more sensitive to quieter sounds
|
68 | rms *= 1.63;
|
69 | rms = Math.sqrt(rms);
|
70 | // Scale it up to 0-100 and round
|
71 | rms = Math.round(rms * 100);
|
72 | // Prevent it from going above 100
|
73 | rms = Math.min(rms, 100);
|
74 | return rms;
|
75 | }
|
76 |
|
77 | // if there is no microphone input, return -1
|
78 | return -1;
|
79 | }
|
80 | }
|
81 |
|
82 | module.exports = Loudness;
|