@@ -94,39 +94,79 @@ GraphSpectrumCalc.dataLoadFrequency = function() {
94
94
const flightSamples = this . _getFlightSamplesFreq ( ) ;
95
95
96
96
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
98
98
}
99
99
100
- //calculate fft
100
+ //calculate fft for the all samples
101
101
const fftOutput = this . _fft ( flightSamples . samples ) ;
102
102
103
103
// Normalize the result
104
- const fftData = this . _normalizeFft ( fftOutput , flightSamples . samples . length ) ;
104
+ const fftData = this . _normalizeFft ( fftOutput ) ;
105
105
106
106
return fftData ;
107
107
} ;
108
108
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
+ } ;
109
141
110
142
GraphSpectrumCalc . _dataLoadFrequencyVsX = function ( vsFieldNames , minValue = Infinity , maxValue = - Infinity ) {
111
143
112
144
const flightSamples = this . _getFlightSamplesFreqVsX ( vsFieldNames , minValue , maxValue ) ;
113
145
114
146
// We divide it into FREQ_VS_THR_CHUNK_TIME_MS FFT chunks, we calculate the average throttle
115
147
// 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 ) ;
117
149
const fftChunkWindow = Math . round ( fftChunkLength / FREQ_VS_THR_WINDOW_DIVISOR ) ;
150
+ const fftBufferSize = Math . pow ( 2 , Math . ceil ( Math . log2 ( fftChunkLength ) ) ) ;
118
151
119
152
let maxNoise = 0 ; // Stores the maximum amplitude of the fft over all chunks
120
153
// 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 ) ) ;
122
155
123
156
const numberSamples = new Uint32Array ( NUM_VS_BINS ) ; // Number of samples in each vs value, used to average them later.
124
157
125
- const fft = new FFT . complex ( fftChunkLength , false ) ;
158
+
159
+ const fft = new FFT . complex ( fftBufferSize , false ) ;
126
160
for ( let fftChunkIndex = 0 ; fftChunkIndex + fftChunkLength < flightSamples . samples . length ; fftChunkIndex += fftChunkWindow ) {
127
161
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
+ }
130
170
131
171
// Hanning window applied to input data
132
172
if ( userSettings . analyserHanning ) {
@@ -135,10 +175,12 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi
135
175
136
176
fft . simple ( fftOutput , fftInput , 'real' ) ;
137
177
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
139
179
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
140
182
// Use only abs values
141
- for ( let i = 0 ; i < fftChunkLength ; i ++ ) {
183
+ for ( let i = 0 ; i < fftBufferSize ; i ++ ) {
142
184
fftOutput [ i ] = Math . abs ( fftOutput [ i ] ) ;
143
185
maxNoise = Math . max ( fftOutput [ i ] , maxNoise ) ;
144
186
}
@@ -152,7 +194,7 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi
152
194
}
153
195
// Translate the average vs value to a bin index
154
196
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 ) ) ;
156
198
// ensure that avgVsValue == flightSamples.maxValue does not result in an out of bounds access
157
199
if ( vsBinIndex === NUM_VS_BINS ) { vsBinIndex = NUM_VS_BINS - 1 ; }
158
200
numberSamples [ vsBinIndex ] ++ ;
@@ -178,13 +220,13 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi
178
220
// blur algorithm to the heat map image
179
221
180
222
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 } ,
188
230
} ;
189
231
190
232
return fftData ;
@@ -298,8 +340,10 @@ GraphSpectrumCalc._getFlightSamplesFreq = function() {
298
340
}
299
341
}
300
342
343
+ // The FFT input size is power 2 to get maximal performance
344
+ const fftBufferSize = Math . pow ( 2 , Math . ceil ( Math . log2 ( samplesCount ) ) ) ;
301
345
return {
302
- samples : samples ,
346
+ samples : samples . slice ( 0 , fftBufferSize ) ,
303
347
count : samplesCount ,
304
348
} ;
305
349
} ;
@@ -375,7 +419,7 @@ GraphSpectrumCalc._getFlightSamplesFreqVsX = function(vsFieldNames, minValue = I
375
419
}
376
420
}
377
421
378
- let slicedVsValues = [ ] ;
422
+ const slicedVsValues = [ ] ;
379
423
for ( const vsValueArray of vsValues ) {
380
424
slicedVsValues . push ( vsValueArray . slice ( 0 , samplesCount ) ) ;
381
425
}
@@ -451,18 +495,16 @@ GraphSpectrumCalc._fft = function(samples, type) {
451
495
/**
452
496
* Makes all the values absolute and returns the index of maxFrequency found
453
497
*/
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 ;
460
501
// Make all the values absolute, and calculate some useful values (max noise, etc.)
461
502
const maxFrequency = ( this . _blackBoxRate / 2.0 ) ;
462
503
const noiseLowEndIdx = 100 / maxFrequency * fftLength ;
463
504
let maxNoiseIdx = 0 ;
464
505
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
466
508
for ( let i = 0 ; i < fftLength ; i ++ ) {
467
509
fftOutput [ i ] = Math . abs ( fftOutput [ i ] ) ;
468
510
if ( i > noiseLowEndIdx && fftOutput [ i ] > maxNoise ) {
0 commit comments