| OLD | NEW |
| 1 function createGraph(options) { | 1 function createGraph(options) { |
| 2 var context = new OfflineAudioContext(1, renderFrames, sampleRate); | 2 let context = new OfflineAudioContext(1, renderFrames, sampleRate); |
| 3 | 3 |
| 4 // Use a default sawtooth wave for the test signal. We want something is a | 4 // Use a default sawtooth wave for the test signal. We want something is a |
| 5 // bit more harmonic content than a sine wave, but otherwise it doesn't | 5 // bit more harmonic content than a sine wave, but otherwise it doesn't |
| 6 // really matter. | 6 // really matter. |
| 7 var src = context.createOscillator(); | 7 let src = context.createOscillator(); |
| 8 src.type = "sawtooth"; | 8 src.type = 'sawtooth'; |
| 9 | 9 |
| 10 var analyser = context.createAnalyser(); | 10 let analyser = context.createAnalyser(); |
| 11 analyser.fftSize = Math.pow(2, options.order); | 11 analyser.fftSize = Math.pow(2, options.order); |
| 12 analyser.smoothingTimeConstant = options.smoothing || 0; | 12 analyser.smoothingTimeConstant = options.smoothing || 0; |
| 13 analyser.minDecibels = options.minDecibels || analyser.minDecibels; | 13 analyser.minDecibels = options.minDecibels || analyser.minDecibels; |
| 14 | 14 |
| 15 // Connect the nodes together and start the source. | 15 // Connect the nodes together and start the source. |
| 16 src.connect(analyser); | 16 src.connect(analyser); |
| 17 analyser.connect(context.destination); | 17 analyser.connect(context.destination); |
| 18 | 18 |
| 19 src.start(); | 19 src.start(); |
| 20 | 20 |
| 21 return { | 21 return { |
| 22 context: context, | 22 context: context, |
| 23 analyser: analyser, | 23 analyser: analyser, |
| 24 }; | 24 }; |
| 25 } | 25 } |
| 26 | 26 |
| 27 // Apply the windowing function, in place. | 27 // Apply the windowing function, in place. |
| 28 function applyWindow(timeData) { | 28 function applyWindow(timeData) { |
| 29 var length = timeData.length; | 29 let length = timeData.length; |
| 30 var alpha = 0.16; | 30 let alpha = 0.16; |
| 31 var a0 = (1 - alpha) / 2; | 31 let a0 = (1 - alpha) / 2; |
| 32 var a1 = 0.5; | 32 let a1 = 0.5; |
| 33 var a2 = alpha / 2; | 33 let a2 = alpha / 2; |
| 34 var omega = 2 * Math.PI / length; | 34 let omega = 2 * Math.PI / length; |
| 35 | 35 |
| 36 for (var k = 0; k < length; ++k) { | 36 for (let k = 0; k < length; ++k) { |
| 37 var w = a0 - a1 * Math.cos(omega * k) + a2 * Math.cos(2 * omega * | 37 let w = a0 - a1 * Math.cos(omega * k) + a2 * Math.cos(2 * omega * k); |
| 38 k); | 38 timeData[k] *= w; |
| 39 timeData[k] *= w; | 39 } |
| 40 } | |
| 41 } | 40 } |
| 42 | 41 |
| 43 // Compute the FFT magnitude of |timeData|. | 42 // Compute the FFT magnitude of |timeData|. |
| 44 function computeFFTMagnitude(timeData, order) { | 43 function computeFFTMagnitude(timeData, order) { |
| 45 // Compute the expected frequency response. First, apply the window. | 44 // Compute the expected frequency response. First, apply the window. |
| 46 // Compute the forward FFT. | 45 // Compute the forward FFT. |
| 47 applyWindow(timeData); | 46 applyWindow(timeData); |
| 48 | 47 |
| 49 var fft = new FFT(order); | 48 let fft = new FFT(order); |
| 50 var fftSize = Math.pow(2, order); | 49 let fftSize = Math.pow(2, order); |
| 51 var fftr = new Float32Array(fftSize); | 50 let fftr = new Float32Array(fftSize); |
| 52 var ffti = new Float32Array(fftSize); | 51 let ffti = new Float32Array(fftSize); |
| 53 fft.rfft(timeData, fftr, ffti); | 52 fft.rfft(timeData, fftr, ffti); |
| 54 | 53 |
| 55 // Compute the magnitude of the expected result. | 54 // Compute the magnitude of the expected result. |
| 56 var expected = new Float32Array(fftSize / 2); | 55 let expected = new Float32Array(fftSize / 2); |
| 57 for (var k = 0; k < expected.length; ++k) | 56 for (let k = 0; k < expected.length; ++k) |
| 58 expected[k] = Math.hypot(fftr[k], ffti[k]) / fftSize; | 57 expected[k] = Math.hypot(fftr[k], ffti[k]) / fftSize; |
| 59 | 58 |
| 60 return expected; | 59 return expected; |
| 61 } | 60 } |
| 62 | 61 |
| 63 // Convert dB value to linear value. | 62 // Convert dB value to linear value. |
| 64 function dbToLinear(x) { | 63 function dbToLinear(x) { |
| 65 return Math.pow(10, x / 20); | 64 return Math.pow(10, x / 20); |
| 66 } | 65 } |
| 67 | 66 |
| 68 // Convert linear value to dB. | 67 // Convert linear value to dB. |
| 69 function linearToDb(x) { | 68 function linearToDb(x) { |
| 70 return 20 * Math.log10(x); | 69 return 20 * Math.log10(x); |
| 71 } | 70 } |
| 72 | 71 |
| 73 // Clip the FFT magnitude so that values below |limit| are set to |limit|. The | 72 // Clip the FFT magnitude so that values below |limit| are set to |limit|. The |
| 74 // FFT must be in dB. The input array is clipped in place. | 73 // FFT must be in dB. The input array is clipped in place. |
| 75 function clipMagnitude(limit, x) { | 74 function clipMagnitude(limit, x) { |
| 76 for (var k = 0; k < x.length; ++k) | 75 for (let k = 0; k < x.length; ++k) |
| 77 x[k] = Math.max(limit, x[k]) | 76 x[k] = Math.max(limit, x[k]) |
| 78 } | 77 } |
| 79 | 78 |
| 80 // Compare the float frequency data in dB, |freqData|, against the expected valu
e, | 79 // Compare the float frequency data in dB, |freqData|, against the expected |
| 81 // |expectedFreq|. |options| is a dictionary with the property |floatRelError| f
or setting the | 80 // value, |expectedFreq|. |options| is a dictionary with the property |
| 82 // comparison threshold and |precision| for setting the printed precision. Sett
ing |precision| to | 81 // |floatRelError| for setting the comparison threshold and |precision| for |
| 83 // |undefined| means printing all digits. If |options.precision} doesn't exist,
use a default | 82 // setting the printed precision. Setting |precision| to |undefined| means |
| 83 // printing all digits. If |options.precision} doesn't exist, use a default |
| 84 // precision. | 84 // precision. |
| 85 function compareFloatFreq(message, freqData, expectedFreq, should, options) { | 85 function compareFloatFreq(message, freqData, expectedFreq, should, options) { |
| 86 // Any dB values below -100 is pretty much in the noise due to round-off in | 86 // Any dB values below -100 is pretty much in the noise due to round-off in |
| 87 // the (single-precisiion) FFT, so just clip those values to -100. | 87 // the (single-precisiion) FFT, so just clip those values to -100. |
| 88 var lowerLimit = -100; | 88 let lowerLimit = -100; |
| 89 clipMagnitude(lowerLimit, expectedFreq); | 89 clipMagnitude(lowerLimit, expectedFreq); |
| 90 | 90 |
| 91 var actual = freqData; | 91 let actual = freqData; |
| 92 clipMagnitude(lowerLimit, actual); | 92 clipMagnitude(lowerLimit, actual); |
| 93 | 93 |
| 94 var success = should(actual, message) | 94 let success = should(actual, message).beCloseToArray(expectedFreq, { |
| 95 .beCloseToArray(expectedFreq, { | 95 relativeThreshold: options.floatRelError || 0, |
| 96 relativeThreshold: options.floatRelError || 0, | 96 }); |
| 97 }); | |
| 98 | 97 |
| 99 return { | 98 return {success: success, expected: expectedFreq}; |
| 100 success: success, | |
| 101 expected: expectedFreq | |
| 102 }; | |
| 103 } | 99 } |
| 104 | 100 |
| 105 // Apply FFT smoothing, accumulating the result in |oldFreqData| with the new | 101 // Apply FFT smoothing, accumulating the result in |oldFreqData| with the new |
| 106 // data in |newFreqData|. The smoothing time constant is |smoothingTime| | 102 // data in |newFreqData|. The smoothing time constant is |smoothingTime| |
| 107 function smoothFFT(oldFreqData, newFreqData, smoothingTime) { | 103 function smoothFFT(oldFreqData, newFreqData, smoothingTime) { |
| 108 for (var k = 0; k < oldFreqData.length; ++k) { | 104 for (let k = 0; k < oldFreqData.length; ++k) { |
| 109 var value = smoothingTime * oldFreqData[k] + (1 - smoothingTime) * | 105 let value = |
| 110 newFreqData[k]; | 106 smoothingTime * oldFreqData[k] + (1 - smoothingTime) * newFreqData[k]; |
| 111 oldFreqData[k] = value; | 107 oldFreqData[k] = value; |
| 112 } | 108 } |
| 113 } | 109 } |
| 114 | 110 |
| 115 // Convert the float frequency data, |floatFreqData|, to byte values using the | 111 // Convert the float frequency data, |floatFreqData|, to byte values using the |
| 116 // dB limits |minDecibels| and |maxDecibels|. The new byte array is returned. | 112 // dB limits |minDecibels| and |maxDecibels|. The new byte array is returned. |
| 117 function convertFloatToByte(floatFreqData, minDecibels, maxDecibels) { | 113 function convertFloatToByte(floatFreqData, minDecibels, maxDecibels) { |
| 118 var scale = 255 / (maxDecibels - minDecibels); | 114 let scale = 255 / (maxDecibels - minDecibels); |
| 119 | 115 |
| 120 return floatFreqData.map(function (x) { | 116 return floatFreqData.map(function(x) { |
| 121 var value = Math.floor(scale * (x - minDecibels)); | 117 let value = Math.floor(scale * (x - minDecibels)); |
| 122 return Math.min(255, Math.max(0, value)); | 118 return Math.min(255, Math.max(0, value)); |
| 123 }); | 119 }); |
| 124 } | 120 } |
| OLD | NEW |