Skip to content

Commit ec62340

Browse files
committed
The FFT is used power 2 input length to get maximal fft performance
1 parent cd62b52 commit ec62340

File tree

1 file changed

+37
-22
lines changed

1 file changed

+37
-22
lines changed

src/graph_spectrum_calc.js

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,14 @@ GraphSpectrumCalc.dataLoadFrequency = function() {
9494
const flightSamples = this._getFlightSamplesFreq();
9595

9696
if (userSettings.analyserHanning) {
97-
this._hanningWindow(flightSamples.samples, flightSamples.count);
97+
this._hanningWindow(flightSamples.samples, flightSamples.count); // Apply Hann function to actual flightSamples.count values only
9898
}
9999

100-
//calculate fft
100+
//calculate fft for the all samples
101101
const fftOutput = this._fft(flightSamples.samples);
102102

103103
// Normalize the result
104-
const fftData = this._normalizeFft(fftOutput, flightSamples.samples.length);
104+
const fftData = this._normalizeFft(fftOutput);
105105

106106
return fftData;
107107
};
@@ -116,7 +116,12 @@ GraphSpectrumCalc.dataLoadPSD = function(analyserZoomY) {
116116
} else if (multipiler > 1) {
117117
pointsPerSegment *= 2 ** Math.floor(multipiler / 2);
118118
}
119-
pointsPerSegment = Math.min(pointsPerSegment, flightSamples.samples.length);
119+
120+
// Use power 2 fft size what is not bigger flightSamples.samples.length
121+
if (pointsPerSegment > flightSamples.samples.length) {
122+
pointsPerSegment = Math.pow(2, Math.floor(Math.log2(flightSamples.samples.length)));
123+
}
124+
120125
const overlapCount = Math.floor(pointsPerSegment / 2);
121126

122127
const psd = this._psd(flightSamples.samples, pointsPerSegment, overlapCount);
@@ -140,20 +145,28 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi
140145

141146
// We divide it into FREQ_VS_THR_CHUNK_TIME_MS FFT chunks, we calculate the average throttle
142147
// for each chunk. We use a moving window to get more chunks available.
143-
const fftChunkLength = this._blackBoxRate * FREQ_VS_THR_CHUNK_TIME_MS / 1000;
148+
const fftChunkLength = Math.round(this._blackBoxRate * FREQ_VS_THR_CHUNK_TIME_MS / 1000);
144149
const fftChunkWindow = Math.round(fftChunkLength / FREQ_VS_THR_WINDOW_DIVISOR);
150+
const fftBufferSize = Math.pow(2, Math.ceil(Math.log2(fftChunkLength)));
145151

146152
let maxNoise = 0; // Stores the maximum amplitude of the fft over all chunks
147153
// Matrix where each row represents a bin of vs values, and the columns are amplitudes at frequencies
148-
const matrixFftOutput = new Array(NUM_VS_BINS).fill(null).map(() => new Float64Array(fftChunkLength * 2));
154+
const matrixFftOutput = new Array(NUM_VS_BINS).fill(null).map(() => new Float64Array(fftBufferSize * 2));
149155

150156
const numberSamples = new Uint32Array(NUM_VS_BINS); // Number of samples in each vs value, used to average them later.
151157

152-
const fft = new FFT.complex(fftChunkLength, false);
158+
159+
const fft = new FFT.complex(fftBufferSize, false);
153160
for (let fftChunkIndex = 0; fftChunkIndex + fftChunkLength < flightSamples.samples.length; fftChunkIndex += fftChunkWindow) {
154161

155-
const fftInput = flightSamples.samples.slice(fftChunkIndex, fftChunkIndex + fftChunkLength);
156-
let fftOutput = new Float64Array(fftChunkLength * 2);
162+
const fftInput = new Float64Array(fftBufferSize);
163+
let fftOutput = new Float64Array(fftBufferSize * 2);
164+
165+
//TODO: to find method to just resize samples array to fftBufferSize
166+
const samples = flightSamples.samples.slice(fftChunkIndex, fftChunkIndex + fftChunkLength);
167+
for (let i = 0; i < fftChunkLength; i++) {
168+
fftInput[i] = samples[i];
169+
}
157170

158171
// Hanning window applied to input data
159172
if (userSettings.analyserHanning) {
@@ -162,10 +175,12 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi
162175

163176
fft.simple(fftOutput, fftInput, 'real');
164177

165-
fftOutput = fftOutput.slice(0, fftChunkLength);
178+
fftOutput = fftOutput.slice(0, fftBufferSize); // The fft output contains two side spectrum, we use the first part only to get one side
166179

180+
// TODO: This is wrong spectrum magnitude calculation as abs of separate complex Re, Im values.
181+
// We should use hypot(Re, Im) instead of and return divide by 2 (fftOutput.length / 4) arrays size
167182
// Use only abs values
168-
for (let i = 0; i < fftChunkLength; i++) {
183+
for (let i = 0; i < fftBufferSize; i++) {
169184
fftOutput[i] = Math.abs(fftOutput[i]);
170185
maxNoise = Math.max(fftOutput[i], maxNoise);
171186
}
@@ -179,7 +194,7 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi
179194
}
180195
// Translate the average vs value to a bin index
181196
const avgVsValue = sumVsValues / fftChunkLength;
182-
let vsBinIndex = Math.floor(NUM_VS_BINS * (avgVsValue - flightSamples.minValue) / (flightSamples.maxValue - flightSamples.minValue));
197+
let vsBinIndex = Math.round(NUM_VS_BINS * (avgVsValue - flightSamples.minValue) / (flightSamples.maxValue - flightSamples.minValue));
183198
// ensure that avgVsValue == flightSamples.maxValue does not result in an out of bounds access
184199
if (vsBinIndex === NUM_VS_BINS) { vsBinIndex = NUM_VS_BINS - 1; }
185200
numberSamples[vsBinIndex]++;
@@ -207,7 +222,7 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi
207222
const fftData = {
208223
fieldIndex : this._dataBuffer.fieldIndex,
209224
fieldName : this._dataBuffer.fieldName,
210-
fftLength : fftChunkLength,
225+
fftLength : fftBufferSize,
211226
fftOutput : matrixFftOutput,
212227
maxNoise : maxNoise,
213228
blackBoxRate : this._blackBoxRate,
@@ -329,8 +344,10 @@ GraphSpectrumCalc._getFlightSamplesFreq = function(scaled = true) {
329344
}
330345
}
331346

347+
// The FFT input size is power 2 to get maximal performance
348+
const fftBufferSize = Math.pow(2, Math.ceil(Math.log2(samplesCount)));
332349
return {
333-
samples : samples.slice(0, samplesCount),
350+
samples : samples.slice(0, fftBufferSize),
334351
count : samplesCount,
335352
};
336353
};
@@ -406,7 +423,7 @@ GraphSpectrumCalc._getFlightSamplesFreqVsX = function(vsFieldNames, minValue = I
406423
}
407424
}
408425

409-
let slicedVsValues = [];
426+
const slicedVsValues = [];
410427
for (const vsValueArray of vsValues) {
411428
slicedVsValues.push(vsValueArray.slice(0, samplesCount));
412429
}
@@ -483,18 +500,16 @@ GraphSpectrumCalc._fft = function(samples, type) {
483500
/**
484501
* Makes all the values absolute and returns the index of maxFrequency found
485502
*/
486-
GraphSpectrumCalc._normalizeFft = function(fftOutput, fftLength) {
487-
488-
if (!fftLength) {
489-
fftLength = fftOutput.length;
490-
}
491-
503+
GraphSpectrumCalc._normalizeFft = function(fftOutput) {
504+
// The fft output contains two side spectrum, we use the first part only to get one side
505+
const fftLength = fftOutput.length / 2;
492506
// Make all the values absolute, and calculate some useful values (max noise, etc.)
493507
const maxFrequency = (this._blackBoxRate / 2.0);
494508
const noiseLowEndIdx = 100 / maxFrequency * fftLength;
495509
let maxNoiseIdx = 0;
496510
let maxNoise = 0;
497-
511+
// TODO: This is wrong spectrum magnitude calculation as abs of separate complex Re, Im values.
512+
// We should use hypot(Re, Im) instead of and return divide by 2 (fftOutput.length / 4) arrays size
498513
for (let i = 0; i < fftLength; i++) {
499514
fftOutput[i] = Math.abs(fftOutput[i]);
500515
if (i > noiseLowEndIdx && fftOutput[i] > maxNoise) {

0 commit comments

Comments
 (0)