Skip to content

Commit 7905a91

Browse files
committed
The FFT is used power 2 input length to get maximal fft performance
1 parent 6eb3711 commit 7905a91

File tree

1 file changed

+69
-27
lines changed

1 file changed

+69
-27
lines changed

src/graph_spectrum_calc.js

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -94,39 +94,79 @@ 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
};
108108

109+
GraphSpectrumCalc.dataLoadPSD = function(analyserZoomY) {
110+
const flightSamples = this._getFlightSamplesFreq(false);
111+
112+
let pointsPerSegment = 512;
113+
const multipiler = Math.floor(1 / analyserZoomY); // 0. ... 10
114+
if (multipiler == 0) {
115+
pointsPerSegment = 256;
116+
} else if (multipiler > 1) {
117+
pointsPerSegment *= 2 ** Math.floor(multipiler / 2);
118+
}
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+
125+
const overlapCount = Math.floor(pointsPerSegment / 2);
126+
127+
const psd = this._psd(flightSamples.samples, pointsPerSegment, overlapCount);
128+
129+
const psdData = {
130+
fieldIndex : this._dataBuffer.fieldIndex,
131+
fieldName : this._dataBuffer.fieldName,
132+
psdLength : psd.psdOutput.length,
133+
psdOutput : psd.psdOutput,
134+
blackBoxRate : this._blackBoxRate,
135+
minimum: psd.min,
136+
maximum: psd.max,
137+
maxNoiseIdx: psd.maxNoiseIdx,
138+
};
139+
return psdData;
140+
};
109141

110142
GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infinity, maxValue = -Infinity) {
111143

112144
const flightSamples = this._getFlightSamplesFreqVsX(vsFieldNames, minValue, maxValue);
113145

114146
// We divide it into FREQ_VS_THR_CHUNK_TIME_MS FFT chunks, we calculate the average throttle
115147
// for each chunk. We use a moving window to get more chunks available.
116-
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);
117149
const fftChunkWindow = Math.round(fftChunkLength / FREQ_VS_THR_WINDOW_DIVISOR);
150+
const fftBufferSize = Math.pow(2, Math.ceil(Math.log2(fftChunkLength)));
118151

119152
let maxNoise = 0; // Stores the maximum amplitude of the fft over all chunks
120153
// Matrix where each row represents a bin of vs values, and the columns are amplitudes at frequencies
121-
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));
122155

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

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

128-
const fftInput = flightSamples.samples.slice(fftChunkIndex, fftChunkIndex + fftChunkLength);
129-
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+
}
130170

131171
// Hanning window applied to input data
132172
if (userSettings.analyserHanning) {
@@ -135,10 +175,12 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi
135175

136176
fft.simple(fftOutput, fftInput, 'real');
137177

138-
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
139179

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
140182
// Use only abs values
141-
for (let i = 0; i < fftChunkLength; i++) {
183+
for (let i = 0; i < fftBufferSize; i++) {
142184
fftOutput[i] = Math.abs(fftOutput[i]);
143185
maxNoise = Math.max(fftOutput[i], maxNoise);
144186
}
@@ -152,7 +194,7 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi
152194
}
153195
// Translate the average vs value to a bin index
154196
const avgVsValue = sumVsValues / fftChunkLength;
155-
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));
156198
// ensure that avgVsValue == flightSamples.maxValue does not result in an out of bounds access
157199
if (vsBinIndex === NUM_VS_BINS) { vsBinIndex = NUM_VS_BINS - 1; }
158200
numberSamples[vsBinIndex]++;
@@ -178,13 +220,13 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi
178220
// blur algorithm to the heat map image
179221

180222
const fftData = {
181-
fieldIndex : this._dataBuffer.fieldIndex,
182-
fieldName : this._dataBuffer.fieldName,
183-
fftLength : fftChunkLength,
184-
fftOutput : matrixFftOutput,
185-
maxNoise : maxNoise,
186-
blackBoxRate : this._blackBoxRate,
187-
vsRange : { min: flightSamples.minValue, max: flightSamples.maxValue},
223+
fieldIndex : this._dataBuffer.fieldIndex,
224+
fieldName : this._dataBuffer.fieldName,
225+
fftLength : fftBufferSize,
226+
fftOutput : matrixFftOutput,
227+
maxNoise : maxNoise,
228+
blackBoxRate : this._blackBoxRate,
229+
vsRange : { min: flightSamples.minValue, max: flightSamples.maxValue},
188230
};
189231

190232
return fftData;
@@ -298,8 +340,10 @@ GraphSpectrumCalc._getFlightSamplesFreq = function() {
298340
}
299341
}
300342

343+
// The FFT input size is power 2 to get maximal performance
344+
const fftBufferSize = Math.pow(2, Math.ceil(Math.log2(samplesCount)));
301345
return {
302-
samples : samples,
346+
samples : samples.slice(0, fftBufferSize),
303347
count : samplesCount,
304348
};
305349
};
@@ -375,7 +419,7 @@ GraphSpectrumCalc._getFlightSamplesFreqVsX = function(vsFieldNames, minValue = I
375419
}
376420
}
377421

378-
let slicedVsValues = [];
422+
const slicedVsValues = [];
379423
for (const vsValueArray of vsValues) {
380424
slicedVsValues.push(vsValueArray.slice(0, samplesCount));
381425
}
@@ -451,18 +495,16 @@ GraphSpectrumCalc._fft = function(samples, type) {
451495
/**
452496
* Makes all the values absolute and returns the index of maxFrequency found
453497
*/
454-
GraphSpectrumCalc._normalizeFft = function(fftOutput, fftLength) {
455-
456-
if (!fftLength) {
457-
fftLength = fftOutput.length;
458-
}
459-
498+
GraphSpectrumCalc._normalizeFft = function(fftOutput) {
499+
// The fft output contains two side spectrum, we use the first part only to get one side
500+
const fftLength = fftOutput.length / 2;
460501
// Make all the values absolute, and calculate some useful values (max noise, etc.)
461502
const maxFrequency = (this._blackBoxRate / 2.0);
462503
const noiseLowEndIdx = 100 / maxFrequency * fftLength;
463504
let maxNoiseIdx = 0;
464505
let maxNoise = 0;
465-
506+
// TODO: This is wrong spectrum magnitude calculation as abs of separate complex Re, Im values.
507+
// We should use hypot(Re, Im) instead of and return divide by 2 (fftOutput.length / 4) arrays size
466508
for (let i = 0; i < fftLength; i++) {
467509
fftOutput[i] = Math.abs(fftOutput[i]);
468510
if (i > noiseLowEndIdx && fftOutput[i] > maxNoise) {

0 commit comments

Comments
 (0)