Wolf-LITE/STM32/Core/Src/fft2.c

1160 wiersze
41 KiB
C
Czysty Zwykły widok Historia

2022-04-18 16:53:10 +00:00
#include "fft.h"
#include "main.h"
#include "arm_const_structs.h"
#include "audio_filters.h"
#include "screen_layout.h"
#include "lcd.h"
//Public variables
bool FFT_need_fft = true; // need to prepare data for display on the screen
bool FFT_new_buffer_ready = false; // buffer is full, can be processed
uint32_t FFT_buff_index = 0; // current buffer index when it is filled with FPGA
bool FFT_buff_current = 0; // current FFT Input buffer A - false, B - true
float32_t FFTInput_I_A[FFT_SIZE] = {0}; // incoming buffer FFT I
float32_t FFTInput_Q_A[FFT_SIZE] = {0}; // incoming buffer FFT Q
float32_t FFTInput_I_B[FFT_SIZE] = {0}; // incoming buffer FFT I
float32_t FFTInput_Q_B[FFT_SIZE] = {0}; // incoming buffer FFT Q
uint16_t FFT_FPS = 0;
uint16_t FFT_FPS_Last = 0;
//Private variables
#if FFT_SIZE == 1024
const static arm_cfft_instance_f32 *FFT_Inst = &arm_cfft_sR_f32_len1024;
#endif
#if FFT_SIZE == 512
const static arm_cfft_instance_f32 *FFT_Inst = &arm_cfft_sR_f32_len512;
#endif
#if FFT_SIZE == 256
const static arm_cfft_instance_f32 *FFT_Inst = &arm_cfft_sR_f32_len256;
#endif
#if FFT_SIZE == 128
const static arm_cfft_instance_f32 *FFT_Inst = &arm_cfft_sR_f32_len128;
#endif
static float32_t FFTInputCharge[FFT_DOUBLE_SIZE_BUFFER] = {0}; // charge FFT I and Q buffer
static float32_t FFTInput[FFT_DOUBLE_SIZE_BUFFER] = {0}; // combined FFT I and Q buffer
static float32_t FFTInput_tmp[LAY_FFT_PRINT_SIZE] = {0}; // temporary buffer for sorting, moving and fft compressing
static float32_t FFTOutput_mean[LAY_FFT_PRINT_SIZE] = {0}; // averaged FFT buffer (for output)
static float32_t maxValueFFT_rx = 0; // maximum value of the amplitude in the resulting frequency response
static float32_t maxValueFFT_tx = 0; // maximum value of the amplitude in the resulting frequency response
static uint32_t currentFFTFreq = 0;
static uint32_t lastWTFFreq = 0; //last WTF printed freq
static uint16_t color_scale[LAY_FFT_HEIGHT] = {0}; // color gradient in height FFT
static uint16_t palette_fft[LAY_FFT_HEIGHT + 1] = {0}; // color palette with FFT colors
static uint16_t palette_bg_gradient[LAY_FFT_HEIGHT + 1] = {0}; // color palette with gradient background of FFT
static uint16_t palette_bw_fft_colors[LAY_FFT_HEIGHT + 1] = {0}; // color palette with bw highlighted FFT colors
static uint16_t palette_bw_bg_colors[LAY_FFT_HEIGHT + 1] = {0}; // color palette with bw highlighted background colors
IRAM1 static uint16_t fft_output_buffer[LAY_FFT_HEIGHT][LAY_FFT_PRINT_SIZE] = {{0}}; //buffer with fft print data
IRAM1 __attribute__((aligned(32))) static uint8_t indexed_wtf_buffer[LAY_WTF_HEIGHT][LAY_FFT_PRINT_SIZE] = {{0}}; //indexed color buffer with wtf
static uint32_t wtf_buffer_freqs[LAY_WTF_HEIGHT] = {0}; // frequencies for each row of the waterfall
IRAM1 static uint16_t wtf_output_line[LAY_FFT_PRINT_SIZE] = {0}; // temporary buffer to draw the waterfall
static uint16_t fft_header[LAY_FFT_PRINT_SIZE] = {0}; //buffer with fft colors output
static int32_t grid_lines_pos[20] = {-1}; //grid lines positions
static int16_t bw_line_start = 0; //BW bar params
static int16_t bw_line_width = 0; //BW bar params
static int16_t bw_line_end = 0; //BW bar params
static int16_t bw_line_center = 0; //BW bar params
static uint16_t print_wtf_yindex = 0; // the current coordinate of the waterfall output via DMA
static float32_t window_multipliers[FFT_SIZE] = {0}; // coefficients of the selected window function
static uint16_t bandmap_line_tmp[LAY_FFT_PRINT_SIZE] = {0}; // temporary buffer to move the waterfall
static arm_sort_instance_f32 FFT_sortInstance = {0}; // sorting instance (to find the median)
static uint32_t print_fft_dma_estimated_size = 0; //block size for dma
static uint32_t print_fft_dma_position = 0; //positior for dma fft print
// Decimator for Zoom FFT
static arm_fir_decimate_instance_f32 DECIMATE_ZOOM_FFT_I;
static arm_fir_decimate_instance_f32 DECIMATE_ZOOM_FFT_Q;
static float32_t decimZoomFFTIState[FFT_SIZE + 4 - 1];
static float32_t decimZoomFFTQState[FFT_SIZE + 4 - 1];
static uint_fast16_t zoomed_width = 0;
static uint16_t fft_peaks[LAY_FFT_PRINT_SIZE] = {0}; //buffer with fft peaks
//Коэффициенты для ZoomFFT lowpass filtering / дециматора
static arm_biquad_cascade_df2T_instance_f32 IIR_biquad_Zoom_FFT_I =
{
.numStages = ZOOMFFT_DECIM_STAGES,
.pCoeffs = (float32_t *)(float32_t[ZOOMFFT_DECIM_STAGES * 5]){0},
.pState = (float32_t *)(float32_t[ZOOMFFT_DECIM_STAGES * 2]){0}};
static arm_biquad_cascade_df2T_instance_f32 IIR_biquad_Zoom_FFT_Q =
{
.numStages = ZOOMFFT_DECIM_STAGES,
.pCoeffs = (float32_t *)(float32_t[ZOOMFFT_DECIM_STAGES * 5]){0},
.pState = (float32_t *)(float32_t[ZOOMFFT_DECIM_STAGES * 2]){0}};
static const float32_t *mag_coeffs[17] =
{
NULL, // 0
NULL, // 1
// 2x magnify, 24kHz, sample rate 96k, 60dB stopband
(float32_t *)(const float32_t[ZOOMFFT_DECIM_STAGES * 5]){2.484242790213,0,0,0,0,1,1.898288195851,1,0.6974136319001,-0.3065583381015,0.02825209609289,0,0,0,0,1,1.500253906972,1,0.008112054577326,-0.7721086004364,1,0,0,0,0},
NULL, // 3
// 4x magnify, 12kHz, sample rate 96k, 60dB stopband
(float32_t *)(const float32_t[ZOOMFFT_DECIM_STAGES * 5]){0.7134827863049,0,0,0,0,1,1.472005720002,1,1.415948015621,-0.5717655848516,0.01368406787431,0,0,0,0,1,0.1832282425444,1,1.300866126796,-0.8337859400983,1,0,0,0,0},
NULL, // 5
NULL, // 6
NULL, // 7
// 8x magnify, 6kHz, sample rate 96k, 60dB stopband
(float32_t *)(const float32_t[ZOOMFFT_DECIM_STAGES * 5]){0.2759821831997,0,0,0,0,1,0.4104549042334,1,1.718681040914,-0.7605249789395,0.009291375667003,0,0,0,0,1,-1.132037961389,1,1.762711686353,-0.9065677781814,1,0,0,0,0},
NULL, // 9
NULL, // 10
NULL, // 11
NULL, // 12
NULL, // 13
NULL, // 14
NULL, // 15
// 16x magnify, 3kHz, sample rate 96k, 60dB stopband
(float32_t *)(const float32_t[ZOOMFFT_DECIM_STAGES * 5]){0.1614831396677,0,0,0,0,1,-0.9158954187733,1,1.861713716539,-0.8727253274572,0.008184762202728,0,0,0,0,1,-1.745517115598,1,1.914110094101,-0.9512646565581,1,0,0,0,0},
};
static const arm_fir_decimate_instance_f32 FirZoomFFTDecimate[17] =
{
{0}, // 0
{0}, // 1
// 48ksps, 12kHz lowpass
{
.numTaps = 4,
.pCoeffs = (float32_t *)(const float32_t[]){475.1179397144384210E-6f, 0.503905202786044337f, 0.503905202786044337f, 475.1179397144384210E-6f},
.pState = NULL},
{0}, // 3
// 48ksps, 6kHz lowpass
{
.numTaps = 4,
.pCoeffs = (float32_t *)(const float32_t[]){0.198273254218889416f, 0.298085149879260325f, 0.298085149879260325f, 0.198273254218889416f},
.pState = NULL},
{0}, // 5
{0}, // 6
{0}, // 7
// 48ksps, 3kHz lowpass
{
.numTaps = 4,
.pCoeffs = (float32_t *)(const float32_t[]){0.199820836596682871f, 0.272777397353925699f, 0.272777397353925699f, 0.199820836596682871f},
.pState = NULL},
{0}, // 9
{0}, // 10
{0}, // 11
{0}, // 12
{0}, // 13
{0}, // 14
{0}, // 15
// 48ksps, 1.5kHz lowpass
{
.numTaps = 4,
.pCoeffs = (float32_t *)(const float32_t[]){0.199820836596682871f, 0.272777397353925699f, 0.272777397353925699f, 0.199820836596682871f},
.pState = NULL},
};
//Prototypes
static uint16_t getFFTColor(uint_fast8_t height); // get color from signal strength
static void FFT_fill_color_palette(void); // prepare the color palette
static void FFT_move(int32_t _freq_diff); // shift the waterfall
static int32_t getFreqPositionOnFFT(uint32_t freq); // get the position on the FFT for a given frequency
static uint32_t FFT_getLensCorrection(uint32_t normal_distance_from_center);
// FFT initialization
void FFT_PreInit(void)
{
//Windowing
//Dolph–Chebyshev
if (TRX.FFT_Window == 1)
{
const float64_t atten = 100.0;
float64_t max = 0.0;
float64_t tg = pow(10.0, atten / 20.0);
float64_t x0 = cosh((1.0 / ((float64_t)FFT_SIZE - 1.0)) * acosh(tg));
float64_t M = (FFT_SIZE - 1) / 2;
if((FFT_SIZE % 2) == 0)
M = M + 0.5; /* handle even length windows */
for(uint32_t nn=0; nn < ((FFT_SIZE / 2) + 1); nn++)
{
float64_t n = nn - M;
float64_t sum = 0.0;
for(uint32_t i = 1; i <= M; i++)
{
float64_t cheby_poly = 0.0;
float64_t cp_x = x0 * cos(F_PI * i / (float64_t)FFT_SIZE);
float64_t cp_n = FFT_SIZE - 1;
if (fabs(cp_x) <= 1)
cheby_poly = cos(cp_n * acos(cp_x));
else
cheby_poly = cosh(cp_n * acosh(cp_x));
sum += cheby_poly * cos(2.0 * n * F_PI * (float64_t)i / (float64_t)FFT_SIZE);
}
window_multipliers[nn] = tg + 2 * sum;
window_multipliers[FFT_SIZE - nn - 1] = window_multipliers[nn];
if(window_multipliers[nn] > max)
max = window_multipliers[nn];
}
for(uint32_t nn=0; nn < FFT_SIZE; nn++)
window_multipliers[nn] /= max; /* normalise everything */
}
for (uint_fast16_t i = 0; i < FFT_SIZE; i++)
{
//Blackman-Harris
if (TRX.FFT_Window == 2)
window_multipliers[i] = 0.35875f - 0.48829f * arm_cos_f32(2.0f * F_PI * (float32_t)i / ((float32_t)FFT_SIZE - 1.0f)) + 0.14128f * arm_cos_f32(4.0f * F_PI * (float32_t)i / ((float32_t)FFT_SIZE - 1.0f)) - 0.01168f * arm_cos_f32(6.0f * F_PI * (float32_t)i / ((float32_t)FFT_SIZE - 1.0f));
//Nutall
else if (TRX.FFT_Window == 3)
window_multipliers[i] = 0.355768f - 0.487396f * arm_cos_f32(2.0f * F_PI * (float32_t)i / ((float32_t)FFT_SIZE - 1.0f)) + 0.144232f * arm_cos_f32(4.0f * F_PI * (float32_t)i / ((float32_t)FFT_SIZE - 1.0f)) - 0.012604 * arm_cos_f32(6.0f * F_PI * (float32_t)i / ((float32_t)FFT_SIZE - 1.0f));
//Blackman-Nutall
else if (TRX.FFT_Window == 4)
window_multipliers[i] = 0.3635819f - 0.4891775f * arm_cos_f32(2.0f * F_PI * (float32_t)i / ((float32_t)FFT_SIZE - 1.0f)) + 0.1365995f * arm_cos_f32(4.0f * F_PI * (float32_t)i / ((float32_t)FFT_SIZE - 1.0f)) - 0.0106411f * arm_cos_f32(6.0f * F_PI * (float32_t)i / ((float32_t)FFT_SIZE - 1.0f));
//Hann
else if (TRX.FFT_Window == 5)
window_multipliers[i] = 0.5f * (1.0f - arm_cos_f32(2.0f * F_PI * (float32_t)i / (float32_t)FFT_SIZE));
//Hamming
else if (TRX.FFT_Window == 6)
window_multipliers[i] = 0.54f - 0.46f * arm_cos_f32((2.0f * F_PI * (float32_t)i) / ((float32_t)FFT_SIZE - 1.0f));
//No window
else if (TRX.FFT_Window == 7)
window_multipliers[i] = 1.0f;
}
// initialize sort
arm_sort_init_f32(&FFT_sortInstance, ARM_SORT_HEAP, ARM_SORT_ASCENDING);
}
void FFT_Init(void)
{
FFT_fill_color_palette();
//ZoomFFT
if (TRX.FFT_Zoom > 1)
{
IIR_biquad_Zoom_FFT_I.pCoeffs = mag_coeffs[TRX.FFT_Zoom];
IIR_biquad_Zoom_FFT_Q.pCoeffs = mag_coeffs[TRX.FFT_Zoom];
memset(IIR_biquad_Zoom_FFT_I.pState, 0x00, sizeof(float32_t) * ZOOMFFT_DECIM_STAGES * 2);
memset(IIR_biquad_Zoom_FFT_Q.pState, 0x00, sizeof(float32_t) * ZOOMFFT_DECIM_STAGES * 2);
arm_fir_decimate_init_f32(&DECIMATE_ZOOM_FFT_I,
FirZoomFFTDecimate[TRX.FFT_Zoom].numTaps,
TRX.FFT_Zoom, // Decimation factor
FirZoomFFTDecimate[TRX.FFT_Zoom].pCoeffs,
decimZoomFFTIState, // Filter state variables
FFT_SIZE);
arm_fir_decimate_init_f32(&DECIMATE_ZOOM_FFT_Q,
FirZoomFFTDecimate[TRX.FFT_Zoom].numTaps,
TRX.FFT_Zoom, // Decimation factor
FirZoomFFTDecimate[TRX.FFT_Zoom].pCoeffs,
decimZoomFFTQState, // Filter state variables
FFT_SIZE);
zoomed_width = FFT_SIZE / TRX.FFT_Zoom;
}
else
zoomed_width = FFT_SIZE;
// clear the buffers
memset(&fft_output_buffer, 0x00, sizeof(fft_output_buffer));
memset(&indexed_wtf_buffer, LAY_FFT_HEIGHT, sizeof(indexed_wtf_buffer));
memset(&FFTInputCharge, 0x00, sizeof(FFTInputCharge));
}
// FFT calculation
void FFT_bufferPrepare(void)
{
if (!TRX.FFT_Enabled)
return;
if (!FFT_new_buffer_ready)
return;
/*if (CPU_LOAD.Load > 90)
return;*/
float32_t* FFTInput_I_current = !FFT_buff_current ? (float32_t*)&FFTInput_I_A : (float32_t*)&FFTInput_I_B; //inverted
float32_t* FFTInput_Q_current = !FFT_buff_current ? (float32_t*)&FFTInput_Q_A : (float32_t*)&FFTInput_Q_B;
//Process DC corrector filter
if (!TRX_on_TX())
{
dc_filter(FFTInput_I_current, FFT_SIZE, DC_FILTER_FFT_I);
dc_filter(FFTInput_Q_current, FFT_SIZE, DC_FILTER_FFT_Q);
}
//-----------------------------------------------------------------------------------------------------------------------------------------
//FFT Peaks
if(TRX.FFT_HoldPeaks)
{
uint32_t fft_y_prev = 0;
for (uint32_t fft_x = 0; fft_x < LAY_FFT_PRINT_SIZE; fft_x++)
{
uint32_t fft_y = LAY_WTF_HEIGHT - fft_peaks[fft_x];
int32_t y_diff = (int32_t)fft_y - (int32_t)fft_y_prev;
if (fft_x == 0 || (y_diff <= 1 && y_diff >= -1))
{
fft_output_buffer[fft_y][fft_x] = palette_fft[LAY_WTF_HEIGHT / 2];
}
else
{
for (uint32_t l = 0; l < (abs(y_diff / 2) + 1); l++) //draw line
{
fft_output_buffer[fft_y_prev + ((y_diff > 0) ? l : -l)][fft_x - 1] = palette_fft[LAY_WTF_HEIGHT / 2];
fft_output_buffer[fft_y + ((y_diff > 0) ? -l : l)][fft_x] = palette_fft[LAY_WTF_HEIGHT / 2];
}
}
fft_y_prev = fft_y;
}
}
//-----------------------------------------------------------------------------------------------------------------------------------------
//FFT Peaks
if(TRX.FFT_HoldPeaks)
{
if(lastWTFFreq == currentFFTFreq)
{
for (uint32_t fft_x = 0; fft_x < LAY_FFT_PRINT_SIZE; fft_x++)
if(fft_peaks[fft_x] <= fft_header[fft_x])
fft_peaks[fft_x] = fft_header[fft_x];
else if(fft_peaks[fft_x] > 0)
fft_peaks[fft_x]--;
}
else
{
for (uint32_t fft_x = 0; fft_x < LAY_FFT_PRINT_SIZE; fft_x++)
fft_peaks[fft_x] = fft_header[fft_x];
}
}
//-----------------------------------------------------------------------------------------------------------------------------------------
//ZoomFFT
if (TRX.FFT_Zoom > 1)
{
//Biquad LPF filter
arm_biquad_cascade_df2T_f32(&IIR_biquad_Zoom_FFT_I, FFTInput_I_current, FFTInput_I_current, FFT_SIZE);
arm_biquad_cascade_df2T_f32(&IIR_biquad_Zoom_FFT_Q, FFTInput_Q_current, FFTInput_Q_current, FFT_SIZE);
// Decimator
arm_fir_decimate_f32(&DECIMATE_ZOOM_FFT_I, FFTInput_I_current, FFTInput_I_current, FFT_SIZE);
arm_fir_decimate_f32(&DECIMATE_ZOOM_FFT_Q, FFTInput_Q_current, FFTInput_Q_current, FFT_SIZE);
// Shift old data
memcpy(&FFTInputCharge[0], &FFTInputCharge[zoomed_width], sizeof(float32_t) * (FFT_SIZE - zoomed_width));
// Add new data
for (uint_fast16_t i = 0; i < zoomed_width; i++)
{
uint16_t wind_pos = i * (FFT_SIZE / zoomed_width);
FFTInputCharge[(FFT_SIZE - zoomed_width + i) * 2] = FFTInput_I_current[i] * window_multipliers[wind_pos];
FFTInputCharge[(FFT_SIZE - zoomed_width + i) * 2 + 1] = FFTInput_Q_current[i] * window_multipliers[wind_pos];
}
}
else
{
// make a combined buffer for calculation
for (uint_fast16_t i = 0; i < FFT_SIZE; i++)
{
FFTInputCharge[i * 2] = FFTInput_I_current[i] * window_multipliers[i];
FFTInputCharge[i * 2 + 1] = FFTInput_Q_current[i] * window_multipliers[i];
}
}
FFT_new_buffer_ready = false;
}
void FFT_doFFT(void)
{
if (!TRX.FFT_Enabled)
return;
if (!FFT_need_fft)
return;
if (!TRX_Inited)
return;
/*if (CPU_LOAD.Load > 90)
return;*/
// Get charge buffer
memcpy(&FFTInput, &FFTInputCharge, sizeof(FFTInput));
memset(&FFTInputCharge, 0x00, sizeof(FFTInputCharge));
arm_cfft_f32(FFT_Inst, FFTInput, 0, 1);
arm_cmplx_mag_f32(FFTInput, FFTInput, FFT_SIZE);
// Swap fft parts
memcpy(&FFTInput[FFT_SIZE], &FFTInput[0], sizeof(float32_t) * (FFT_SIZE / 2)); //left - > tmp
memcpy(&FFTInput[0], &FFTInput[FFT_SIZE / 2], sizeof(float32_t) * (FFT_SIZE / 2)); //right - > left
memcpy(&FFTInput[FFT_SIZE / 2], &FFTInput[FFT_SIZE], sizeof(float32_t) * (FFT_SIZE / 2)); //tmp - > right
// Compress the calculated FFT to visible
memcpy(&FFTInput[0], &FFTInput[FFT_SIZE / 2 - FFT_USEFUL_SIZE / 2], sizeof(float32_t) * FFT_USEFUL_SIZE); //useful fft part
float32_t fft_compress_rate = (float32_t)FFT_USEFUL_SIZE / (float32_t)LAY_FFT_PRINT_SIZE;
float32_t fft_compress_rate_half = floorf(fft_compress_rate / 2.0f); //full points
float32_t fft_compress_rate_parts = fmodf(fft_compress_rate / 2.0f, 1.0f); //partial points
//Compress FFT
for (uint32_t i = 0; i < LAY_FFT_PRINT_SIZE; i++)
{
int32_t left_index = (uint32_t)((float32_t)i * fft_compress_rate - fft_compress_rate_half);
if(left_index < 0)
left_index = 0;
int32_t right_index = (uint32_t)((float32_t)i * fft_compress_rate + fft_compress_rate_half);
if(right_index >= FFT_USEFUL_SIZE)
right_index = FFT_USEFUL_SIZE - 1;
float32_t points = 0;
float32_t accum = 0.0f;
//full points
for(uint32_t index = left_index; index <= right_index ; index++)
{
accum += FFTInput[index];
points += 1.0f;
}
//partial points
if(fft_compress_rate_parts > 0.0f)
{
if(left_index > 0)
{
accum += FFTInput[left_index - 1] * fft_compress_rate_parts;
points += fft_compress_rate_parts;
}
if(right_index < (FFT_USEFUL_SIZE - 1))
{
accum += FFTInput[right_index + 1] * fft_compress_rate_parts;
points += fft_compress_rate_parts;
}
}
FFTInput_tmp[i] = accum / points;
}
memcpy(&FFTInput, FFTInput_tmp, sizeof(FFTInput_tmp));
//Delete noise
float32_t minAmplValue = 0;
uint32_t minAmplIndex = 0;
arm_min_f32(FFTInput, LAY_FFT_PRINT_SIZE, &minAmplValue, &minAmplIndex);
if (!TRX_on_TX())
arm_offset_f32(FFTInput, -minAmplValue * 0.8f, FFTInput, LAY_FFT_PRINT_SIZE);
// Looking for the median in frequency response
arm_sort_f32(&FFT_sortInstance, FFTInput, FFTInput_tmp, LAY_FFT_PRINT_SIZE);
float32_t medianValue = FFTInput_tmp[LAY_FFT_PRINT_SIZE / 2];
// Maximum amplitude
float32_t maxValueFFT = maxValueFFT_rx;
if (TRX_on_TX())
maxValueFFT = maxValueFFT_tx;
float32_t maxValue = (medianValue * FFT_MAX);
float32_t targetValue = (medianValue * FFT_TARGET);
float32_t minValue = (medianValue * FFT_MIN);
// Looking for the maximum in frequency response
float32_t maxAmplValue = 0;
arm_max_no_idx_f32(FFTInput, LAY_FFT_PRINT_SIZE, &maxAmplValue);
// Auto-calibrate FFT levels
maxValueFFT += (targetValue - maxValueFFT) / FFT_STEP_COEFF;
// minimum-maximum threshold for median
if (maxValueFFT < minValue)
maxValueFFT = minValue;
if (maxValueFFT > maxValue)
maxValueFFT = maxValue;
// Compress peaks
float32_t compressTargetValue = (maxValueFFT * FFT_COMPRESS_INTERVAL);
float32_t compressSourceInterval = maxAmplValue - compressTargetValue;
float32_t compressTargetInterval = maxValueFFT - compressTargetValue;
float32_t compressRate = compressTargetInterval / compressSourceInterval;
if (!TRX_on_TX() && TRX.FFT_Compressor)
{
for (uint_fast16_t i = 0; i < LAY_FFT_PRINT_SIZE; i++)
if (FFTInput[i] > compressTargetValue)
FFTInput[i] = compressTargetValue + ((FFTInput[i] - compressTargetValue) * compressRate);
}
// Auto-calibrate FFT levels
/*if (TRX_on_TX() || (TRX.FFT_Automatic && TRX.FFT_Sensitivity == FFT_MAX_TOP_SCALE)) //Fit FFT to MAX
{
maxValueFFT = maxValueFFT * 0.95f + maxAmplValue * 0.05f;
if (maxValueFFT < maxAmplValue)
maxValueFFT = maxAmplValue;
minValue = (medianValue * 6.0f);
if (maxValueFFT < minValue)
maxValueFFT = minValue;
}
else if (TRX.FFT_Automatic) //Fit by median (automatic)
{
maxValueFFT += (targetValue - maxValueFFT) / FFT_STEP_COEFF;
// minimum-maximum threshold for median
if (maxValueFFT < minValue)
maxValueFFT = minValue;
if (maxValueFFT > maxValue)
maxValueFFT = maxValue;
// Compress peaks
float32_t compressTargetValue = (maxValueFFT * FFT_COMPRESS_INTERVAL);
float32_t compressSourceInterval = maxAmplValue - compressTargetValue;
float32_t compressTargetInterval = maxValueFFT - compressTargetValue;
float32_t compressRate = compressTargetInterval / compressSourceInterval;
if (TRX.FFT_Compressor && TRX.FFT_Sensitivity < 50)
{
for (uint_fast16_t i = 0; i < LAYOUT->FFT_PRINT_SIZE; i++)
if (FFTOutput_mean[i] > compressTargetValue)
FFTOutput_mean[i] = compressTargetValue + ((FFTOutput_mean[i] - compressTargetValue) * compressRate);
}
}
else //Manual Scale
{
float32_t minManualAmplitude = sqrtf(db2rateP((float32_t)TRX.FFT_ManualBottom)) * (float32_t)FFT_SIZE * (float32_t)TRX.FFT_Averaging;
float32_t maxManualAmplitude = sqrtf(db2rateP((float32_t)TRX.FFT_ManualTop)) * (float32_t)FFT_SIZE * (float32_t)TRX.FFT_Averaging;
arm_offset_f32(FFTOutput_mean, -minManualAmplitude, FFTOutput_mean, LAYOUT->FFT_PRINT_SIZE);
for (uint_fast16_t i = 0; i < LAYOUT->FFT_PRINT_SIZE; i++)
if (FFTOutput_mean[i] < 0)
FFTOutput_mean[i] = 0;
maxValueFFT = maxManualAmplitude - minManualAmplitude;
}*/
//limits
if (TRX_on_TX())
maxValueFFT = maxAmplValue;
if (maxValueFFT < 0.0000001f)
maxValueFFT = 0.0000001f;
// save values for switching RX / TX
if (TRX_on_TX())
maxValueFFT_tx = maxValueFFT;
else
maxValueFFT_rx = maxValueFFT;
// Normalize the frequency response to one
if (maxValueFFT > 0)
arm_scale_f32(FFTInput, 1.0f / maxValueFFT, FFTInput, LAY_FFT_PRINT_SIZE);
// Averaging values for subsequent output
float32_t averaging = (float32_t)TRX.FFT_Averaging;
if (averaging < 1.0f)
averaging = 1.0f;
for (uint_fast16_t i = 0; i < LAY_FFT_PRINT_SIZE; i++)
if (FFTOutput_mean[i] < FFTInput[i])
FFTOutput_mean[i] += (FFTInput[i] - FFTOutput_mean[i]) / averaging;
else
FFTOutput_mean[i] -= (FFTOutput_mean[i] - FFTInput[i]) / averaging;
FFT_need_fft = false;
}
// FFT output
bool FFT_printFFT(void)
{
if (LCD_busy)
return false;
if (!TRX.FFT_Enabled)
return false;
if (!TRX_Inited)
return false;
if (FFT_need_fft)
return false;
if (LCD_systemMenuOpened)
return false;
/*if (CPU_LOAD.Load > 90)
return;*/
LCD_busy = true;
uint16_t height = 0; // column height in FFT output
uint16_t tmp = 0;
if (CurrentVFO()->Freq != currentFFTFreq)
{
//calculate scale lines
memset(grid_lines_pos, 0x00, sizeof(grid_lines_pos));
uint8_t index = 0;
for (int8_t i = 0; i < FFT_MAX_GRID_NUMBER; i++)
{
int32_t pos = -1;
if (TRX.FFT_Grid > 0)
{
pos = getFreqPositionOnFFT((CurrentVFO()->Freq / 5000 * 5000) + ((i - 6) * 5000));
}
if (pos >= 0)
{
grid_lines_pos[index] = pos;
index++;
}
}
// offset the fft if needed
FFT_move((int32_t)CurrentVFO()->Freq - (int32_t)currentFFTFreq);
currentFFTFreq = CurrentVFO()->Freq;
}
// move the waterfall down using DMA
for (tmp = LAY_WTF_HEIGHT - 1; tmp > 0; tmp--)
{
HAL_DMA_Start(&hdma_memtomem_dma2_stream7, (uint32_t)&indexed_wtf_buffer[tmp - 1], (uint32_t)&indexed_wtf_buffer[tmp], LAY_FFT_PRINT_SIZE / 4); //32bit dma, 8bit index data
HAL_DMA_PollForTransfer(&hdma_memtomem_dma2_stream7, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY);
wtf_buffer_freqs[tmp] = wtf_buffer_freqs[tmp - 1];
}
// calculate the colors for the waterfall
for (uint32_t fft_x = 0; fft_x < LAY_FFT_PRINT_SIZE; fft_x++)
{
height = (uint16_t)((float32_t)FFTOutput_mean[(uint_fast16_t)fft_x] * LAY_FFT_HEIGHT);
if (height > LAY_FFT_HEIGHT)
height = LAY_FFT_HEIGHT;
wtf_buffer_freqs[0] = currentFFTFreq;
fft_header[fft_x] = height;
indexed_wtf_buffer[0][fft_x] = LAY_FFT_HEIGHT - height;
if (fft_x == (LAY_FFT_PRINT_SIZE / 2))
continue;
}
// calculate bw bar size
switch (CurrentVFO()->Mode)
{
case TRX_MODE_LSB:
case TRX_MODE_CW_L:
case TRX_MODE_DIGI_L:
bw_line_width = (int16_t)(CurrentVFO()->RX_LPF_Filter_Width / FFT_HZ_IN_PIXEL * TRX.FFT_Zoom);
if (bw_line_width > (LAY_FFT_PRINT_SIZE / 2))
bw_line_width = LAY_FFT_PRINT_SIZE / 2;
bw_line_start = LAY_FFT_PRINT_SIZE / 2 - bw_line_width;
break;
case TRX_MODE_USB:
case TRX_MODE_CW_U:
case TRX_MODE_DIGI_U:
bw_line_width = (int16_t)(CurrentVFO()->RX_LPF_Filter_Width / FFT_HZ_IN_PIXEL * TRX.FFT_Zoom);
if (bw_line_width > (LAY_FFT_PRINT_SIZE / 2))
bw_line_width = LAY_FFT_PRINT_SIZE / 2;
bw_line_start = LAY_FFT_PRINT_SIZE / 2;
break;
case TRX_MODE_NFM:
case TRX_MODE_AM:
bw_line_width = (int16_t)(CurrentVFO()->RX_LPF_Filter_Width / FFT_HZ_IN_PIXEL * TRX.FFT_Zoom * 2);
if (bw_line_width > LAY_FFT_PRINT_SIZE)
bw_line_width = LAY_FFT_PRINT_SIZE;
bw_line_start = LAY_FFT_PRINT_SIZE / 2 - (bw_line_width / 2);
break;
case TRX_MODE_WFM:
bw_line_width = LAY_FFT_PRINT_SIZE;
bw_line_start = LAY_FFT_PRINT_SIZE / 2 - (bw_line_width / 2);
break;
default:
break;
}
bw_line_center = bw_line_start + bw_line_width / 2;
bw_line_end = bw_line_start + bw_line_width;
// prepare FFT print over the waterfall
uint16_t background = BG_COLOR;
for (uint32_t fft_y = 0; fft_y < LAY_FFT_HEIGHT; fft_y++)
{
if (TRX.FFT_Background)
background = palette_bg_gradient[fft_y];
//gradient
for (uint32_t fft_x = 0; fft_x < LAY_FFT_PRINT_SIZE; fft_x++)
{
if (fft_x >= bw_line_start && fft_x <= bw_line_end) //bw bar
{
if (fft_y >= (LAY_FFT_HEIGHT - fft_header[fft_x]))
fft_output_buffer[fft_y][fft_x] = palette_bw_fft_colors[fft_y];
else
fft_output_buffer[fft_y][fft_x] = palette_bw_bg_colors[fft_y];
}
else //other fft data
{
if (fft_y >= (LAY_FFT_HEIGHT - fft_header[fft_x]))
fft_output_buffer[fft_y][fft_x] = palette_fft[fft_y];
else
fft_output_buffer[fft_y][fft_x] = background;
}
}
}
//draw grids
if (TRX.FFT_Grid == 1 || TRX.FFT_Grid == 2)
{
for (int32_t grid_line_index = 0; grid_line_index < FFT_MAX_GRID_NUMBER; grid_line_index++)
if (grid_lines_pos[grid_line_index] > 0 && grid_lines_pos[grid_line_index] < LAY_FFT_PRINT_SIZE && grid_lines_pos[grid_line_index] != (LAY_FFT_PRINT_SIZE / 2))
for (uint32_t fft_y = 0; fft_y < LAY_FFT_HEIGHT; fft_y++)
fft_output_buffer[fft_y][grid_lines_pos[grid_line_index]] = palette_fft[LAY_FFT_HEIGHT * 3 / 4]; //mixColors(fft_output_buffer[fft_y][grid_lines_pos[grid_line_index]], palette_fft[fftHeight / 2], FFT_SCALE_LINES_BRIGHTNESS);
}
//Gauss filter center
if (TRX.CW_GaussFilter && (CurrentVFO()->Mode == TRX_MODE_CW_L || CurrentVFO()->Mode == TRX_MODE_CW_U))
{
for (uint32_t fft_y = 0; fft_y < LAY_FFT_HEIGHT; fft_y++)
fft_output_buffer[fft_y][bw_line_center] = palette_fft[LAY_FFT_HEIGHT / 2]; //mixColors(fft_output_buffer[fft_y][bw_line_center], palette_fft[fftHeight / 2], FFT_SCALE_LINES_BRIGHTNESS);
}
//draw center line
for (uint32_t fft_y = 0; fft_y < LAY_FFT_HEIGHT; fft_y++)
fft_output_buffer[fft_y][(LAY_FFT_PRINT_SIZE / 2)] = palette_fft[LAY_FFT_HEIGHT / 2]; //mixColors(fft_output_buffer[fft_y][(LAY_FFT_PRINT_SIZE / 2)], palette_fft[fftHeight / 2], FFT_SCALE_LINES_BRIGHTNESS);
//Print FFT
LCDDriver_SetCursorAreaPosition(0, LAY_FFT_FFTWTF_POS_Y, LAY_FFT_PRINT_SIZE - 1, (LAY_FFT_FFTWTF_POS_Y + LAY_FFT_HEIGHT));
print_fft_dma_estimated_size = LAY_FFT_PRINT_SIZE * LAY_FFT_HEIGHT;
print_fft_dma_position = 0;
FFT_afterPrintFFT();
return true;
}
//actions after FFT_printFFT
void FFT_afterPrintFFT(void)
{
//continue DMA draw?
if(print_fft_dma_estimated_size > 0)
{
if(print_fft_dma_estimated_size <= FFT_DMA_MAX_BLOCK)
{
HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream5, (uint32_t)&fft_output_buffer[0] + print_fft_dma_position * 2, LCD_FSMC_DATA_ADDR, print_fft_dma_estimated_size);
print_fft_dma_estimated_size = 0;
print_fft_dma_position = 0;
}
else
{
print_fft_dma_estimated_size -= FFT_DMA_MAX_BLOCK;
HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream5, (uint32_t)&fft_output_buffer[0] + print_fft_dma_position * 2, LCD_FSMC_DATA_ADDR, FFT_DMA_MAX_BLOCK);
print_fft_dma_position += FFT_DMA_MAX_BLOCK;
}
return;
}
// clear and display part of the vertical bar
memset(bandmap_line_tmp, 0x00, sizeof(bandmap_line_tmp));
// output bandmaps
int8_t band_curr = getBandFromFreq(CurrentVFO()->Freq, true);
int8_t band_left = band_curr;
if (band_curr > 0)
band_left = band_curr - 1;
int8_t band_right = band_curr;
if (band_curr < (BANDS_COUNT - 1))
band_right = band_curr + 1;
int32_t fft_freq_position_start = 0;
int32_t fft_freq_position_stop = 0;
for (uint16_t band = band_left; band <= band_right; band++)
{
//regions
for (uint16_t region = 0; region < BANDS[band].regionsCount; region++)
{
uint16_t region_color = COLOR->BANDMAP_SSB;
if (BANDS[band].regions[region].mode == TRX_MODE_CW_L || BANDS[band].regions[region].mode == TRX_MODE_CW_U)
region_color = COLOR->BANDMAP_CW;
else if (BANDS[band].regions[region].mode == TRX_MODE_DIGI_L || BANDS[band].regions[region].mode == TRX_MODE_DIGI_U)
region_color = COLOR->BANDMAP_DIGI;
else if (BANDS[band].regions[region].mode == TRX_MODE_NFM || BANDS[band].regions[region].mode == TRX_MODE_WFM)
region_color = COLOR->BANDMAP_FM;
else if (BANDS[band].regions[region].mode == TRX_MODE_AM)
region_color = COLOR->BANDMAP_AM;
fft_freq_position_start = getFreqPositionOnFFT(BANDS[band].regions[region].startFreq);
fft_freq_position_stop = getFreqPositionOnFFT(BANDS[band].regions[region].endFreq);
if (fft_freq_position_start != -1 && fft_freq_position_stop == -1)
fft_freq_position_stop = LAY_FFT_PRINT_SIZE;
if (fft_freq_position_start == -1 && fft_freq_position_stop != -1)
fft_freq_position_start = 0;
if (fft_freq_position_start == -1 && fft_freq_position_stop == -1 && BANDS[band].regions[region].startFreq < CurrentVFO()->Freq && BANDS[band].regions[region].endFreq > CurrentVFO()->Freq)
{
fft_freq_position_start = 0;
fft_freq_position_stop = LAY_FFT_PRINT_SIZE;
}
if (fft_freq_position_start != -1 && fft_freq_position_stop != -1)
for (int32_t pixel_counter = fft_freq_position_start; pixel_counter < fft_freq_position_stop; pixel_counter++)
bandmap_line_tmp[(uint16_t)pixel_counter] = region_color;
}
}
LCDDriver_SetCursorAreaPosition(0, LAY_FFT_FFTWTF_POS_Y - 4, LAY_FFT_PRINT_SIZE - 1, LAY_FFT_FFTWTF_POS_Y - 3);
for (uint8_t r = 0; r < 2; r++)
for (uint32_t pixel_counter = 0; pixel_counter < LAY_FFT_PRINT_SIZE; pixel_counter++)
LCDDriver_SendData(bandmap_line_tmp[pixel_counter]);
// display the waterfall using DMA
print_wtf_yindex = 0;
FFT_printWaterfallDMA();
}
// waterfall output
void FFT_printWaterfallDMA(void)
{
//wtf area
if (print_wtf_yindex == 0)
LCDDriver_SetCursorAreaPosition(0, LAY_FFT_FFTWTF_POS_Y + LAY_FFT_HEIGHT, LAY_FFT_PRINT_SIZE - 1, LAY_FFT_FFTWTF_POS_Y + LAY_FFT_HEIGHT + (uint16_t)LAY_WTF_HEIGHT - 1);
//print waterfall line
if (print_wtf_yindex < LAY_WTF_HEIGHT)
{
// calculate offset
float32_t freq_diff = (((float32_t)currentFFTFreq - (float32_t)wtf_buffer_freqs[print_wtf_yindex]) / FFT_HZ_IN_PIXEL) * (float32_t)TRX.FFT_Zoom;
float32_t freq_diff_part = fmodf(freq_diff, 1.0f);
int32_t margin_left = 0;
if (freq_diff < 0)
margin_left = -floorf(freq_diff);
if (margin_left > LAY_FFT_PRINT_SIZE)
margin_left = LAY_FFT_PRINT_SIZE;
int32_t margin_right = 0;
if (freq_diff > 0)
margin_right = ceilf(freq_diff);
if (margin_right > LAY_FFT_PRINT_SIZE)
margin_right = LAY_FFT_PRINT_SIZE;
if ((margin_left + margin_right) > LAY_FFT_PRINT_SIZE)
margin_right = 0;
//rounding
int32_t body_width = LAY_FFT_PRINT_SIZE - margin_left - margin_right;
if (body_width <= 0)
{
memset(&wtf_output_line, BG_COLOR, sizeof(wtf_output_line));
}
else
{
if (margin_left == 0 && margin_right == 0)
{
for (uint32_t wtf_x = 0; wtf_x < LAY_FFT_PRINT_SIZE; wtf_x++)
if (wtf_x >= bw_line_start && wtf_x <= bw_line_end) //print bw bar
wtf_output_line[wtf_x] = palette_bw_fft_colors[indexed_wtf_buffer[print_wtf_yindex][wtf_x]];
else
wtf_output_line[wtf_x] = palette_fft[indexed_wtf_buffer[print_wtf_yindex][wtf_x]];
}
else if (margin_left > 0)
{
memset(&wtf_output_line, BG_COLOR, (uint32_t)(margin_left * 2)); // fill the space to the left
for (uint32_t wtf_x = 0; wtf_x < (LAY_FFT_PRINT_SIZE - margin_left); wtf_x++)
if ((margin_left + wtf_x) >= bw_line_start && (margin_left + wtf_x) <= bw_line_end) //print bw bar
wtf_output_line[margin_left + wtf_x] = palette_bw_fft_colors[indexed_wtf_buffer[print_wtf_yindex][wtf_x]];
else
wtf_output_line[margin_left + wtf_x] = palette_fft[indexed_wtf_buffer[print_wtf_yindex][wtf_x]];
}
if (margin_right > 0)
{
memset(&wtf_output_line[(LAY_FFT_PRINT_SIZE - margin_right)], BG_COLOR, (uint32_t)(margin_right * 2)); // fill the space to the right
for (uint32_t wtf_x = 0; wtf_x < (LAY_FFT_PRINT_SIZE - margin_right); wtf_x++)
if (wtf_x >= bw_line_start && wtf_x <= bw_line_end) //print bw bar
wtf_output_line[wtf_x] = palette_bw_fft_colors[indexed_wtf_buffer[print_wtf_yindex][wtf_x + margin_right]];
else
wtf_output_line[wtf_x] = palette_fft[indexed_wtf_buffer[print_wtf_yindex][wtf_x + margin_right]];
}
}
//print scale lines
if (TRX.FFT_Grid >= 2)
for (int8_t i = 0; i < FFT_MAX_GRID_NUMBER; i++)
if (grid_lines_pos[i] > 0)
wtf_output_line[grid_lines_pos[i]] = palette_fft[LAY_FFT_HEIGHT * 3 / 4]; //mixColors(wtf_output_line[grid_lines_pos[i]], palette_fft[fftHeight / 2], FFT_SCALE_LINES_BRIGHTNESS);
//Gauss filter center
if (TRX.CW_GaussFilter && (CurrentVFO()->Mode == TRX_MODE_CW_L || CurrentVFO()->Mode == TRX_MODE_CW_U))
wtf_output_line[bw_line_center] = palette_fft[LAY_FFT_HEIGHT / 2]; //mixColors(fft_output_buffer[fft_y][bw_line_center], palette_fft[fftHeight / 2], FFT_SCALE_LINES_BRIGHTNESS);
//center line
wtf_output_line[LAY_FFT_PRINT_SIZE / 2] = palette_fft[LAY_FFT_HEIGHT / 2]; //mixColors(wtf_output_line[LAY_FFT_PRINT_SIZE / 2], palette_fft[fftHeight / 2], FFT_SCALE_LINES_BRIGHTNESS);
//draw the line
HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream6, (uint32_t)&wtf_output_line[0], LCD_FSMC_DATA_ADDR, LAY_FFT_PRINT_SIZE);
print_wtf_yindex++;
}
else
{
FFT_FPS++;
lastWTFFreq = currentFFTFreq;
FFT_need_fft = true;
LCD_busy = false;
}
}
// shift the waterfall
static void FFT_move(int32_t _freq_diff)
{
if (_freq_diff == 0)
return;
float32_t old_x_true = 0.0f;
int32_t old_x_l = 0;
int32_t old_x_r = 0;
float32_t freq_diff = ((float32_t)_freq_diff / FFT_HZ_IN_PIXEL) * (float32_t)TRX.FFT_Zoom;
float32_t old_x_part_r = fmodf(freq_diff, 1.0f);
float32_t old_x_part_l = 1.0f - old_x_part_r;
if (freq_diff < 0.0f)
{
old_x_part_l = fmodf(-freq_diff, 1.0f);
old_x_part_r = 1.0f - old_x_part_l;
}
//Move mean Buffer
for (int32_t x = 0; x < LAY_FFT_PRINT_SIZE; x++)
{
old_x_true = (float32_t)x + freq_diff;
old_x_l = (int32_t)(floorf(old_x_true));
old_x_r = (int32_t)(ceilf(old_x_true));
FFTInput_tmp[x] = 0;
if ((old_x_true >= LAY_FFT_PRINT_SIZE) || (old_x_true < 0.0f))
continue;
if ((old_x_l < LAY_FFT_PRINT_SIZE) && (old_x_l >= 0))
FFTInput_tmp[x] += (FFTOutput_mean[old_x_l] * old_x_part_l);
if ((old_x_r < LAY_FFT_PRINT_SIZE) && (old_x_r >= 0))
FFTInput_tmp[x] += (FFTOutput_mean[old_x_r] * old_x_part_r);
//sides
if (old_x_r >= LAY_FFT_PRINT_SIZE)
FFTInput_tmp[x] = FFTOutput_mean[old_x_l];
}
//save results
memcpy(&FFTOutput_mean, &FFTInput_tmp, sizeof FFTOutput_mean);
//clean charge buffer
if(TRX.FFT_Zoom > 1)
memset(&FFTInputCharge[0], 0x00, sizeof(float32_t) * (FFT_SIZE - zoomed_width) * 2);
}
// get color from signal strength
static uint16_t getFFTColor(uint_fast8_t height) // Get FFT color warmth (blue to red)
{
uint_fast8_t red = 0;
uint_fast8_t green = 0;
uint_fast8_t blue = 0;
if (COLOR->WTF_BG_WHITE)
{
red = 255;
green = 255;
blue = 255;
}
//blue -> yellow -> red
if (TRX.FFT_Color == 1)
{
// r g b
// 0 0 0
// 0 0 255
// 255 255 0
// 255 0 0
// contrast of each of the 3 zones, the total should be 1.0f
const float32_t contrast1 = 0.02f;
const float32_t contrast2 = 0.45f;
const float32_t contrast3 = 0.53f;
if (height < LAY_FFT_HEIGHT * contrast1)
{
blue = (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT * contrast1));
if (COLOR->WTF_BG_WHITE)
{
red -= blue;
green -= blue;
}
}
else if (height < LAY_FFT_HEIGHT * (contrast1 + contrast2))
{
green = (uint_fast8_t)((height - LAY_FFT_HEIGHT * contrast1) * 255 / ((LAY_FFT_HEIGHT - LAY_FFT_HEIGHT * contrast1) * (contrast1 + contrast2)));
red = green;
blue = 255 - green;
}
else
{
red = 255;
blue = 0;
green = (uint_fast8_t)(255 - (height - (LAY_FFT_HEIGHT * (contrast1 + contrast2))) * 255 / ((LAY_FFT_HEIGHT - (LAY_FFT_HEIGHT * (contrast1 + contrast2))) * (contrast1 + contrast2 + contrast3)));
}
return rgb888torgb565(red, green, blue);
}
//black -> yellow -> red
if (TRX.FFT_Color == 2)
{
// r g b
// 0 0 0
// 255 255 0
// 255 0 0
// contrast of each of the 2 zones, the total should be 1.0f
float32_t contrast1 = 0.5f;
float32_t contrast2 = 0.5f;
if (COLOR->WTF_BG_WHITE)
{
contrast1 = 0.2f;
contrast2 = 0.8f;
}
if (height < LAY_FFT_HEIGHT * contrast1)
{
if (!COLOR->WTF_BG_WHITE)
{
red = (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT * contrast1));
green = (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT * contrast1));
blue = 0;
}
else
{
red = 255;
green = 255;
blue = 255 - (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT * contrast1));
}
}
else
{
red = 255;
blue = 0;
green = (uint_fast8_t)(255 - (height - (LAY_FFT_HEIGHT * (contrast1))) * 255 / ((LAY_FFT_HEIGHT - (LAY_FFT_HEIGHT * (contrast1))) * (contrast1 + contrast2)));
if (COLOR->WTF_BG_WHITE)
{
blue = green;
}
}
return rgb888torgb565(red, green, blue);
}
//black -> yellow -> green
if (TRX.FFT_Color == 3)
{
// r g b
// 0 0 0
// 255 255 0
// 0 255 0
// contrast of each of the 2 zones, the total should be 1.0f
float32_t contrast1 = 0.5f;
float32_t contrast2 = 0.5f;
if (COLOR->WTF_BG_WHITE)
{
contrast1 = 0.2f;
contrast2 = 0.8f;
}
if (height < LAY_FFT_HEIGHT * contrast1)
{
if (!COLOR->WTF_BG_WHITE)
{
red = (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT * contrast1));
green = (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT * contrast1));
blue = 0;
}
else
{
red = 255;
green = 255;
blue = 255 - (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT * contrast1));
}
}
else
{
green = 255;
blue = 0;
red = (uint_fast8_t)(255 - (height - (LAY_FFT_HEIGHT * (contrast1))) * 255 / ((LAY_FFT_HEIGHT - (LAY_FFT_HEIGHT * (contrast1))) * (contrast1 + contrast2)));
if (COLOR->WTF_BG_WHITE)
{
green = red;
}
}
return rgb888torgb565(red, green, blue);
}
//black -> red
if (TRX.FFT_Color == 4)
{
// r g b
// 0 0 0
// 255 0 0
if (height <= LAY_FFT_HEIGHT)
{
red = (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT));
if (COLOR->WTF_BG_WHITE)
{
green -= (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT));
blue -= (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT));
red = 255;
}
}
return rgb888torgb565(red, green, blue);
}
//black -> green
if (TRX.FFT_Color == 5)
{
// r g b
// 0 0 0
// 0 255 0
if (height <= LAY_FFT_HEIGHT)
{
green = (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT));
if (COLOR->WTF_BG_WHITE)
{
green = 255;
blue -= (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT));
red -= (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT));
}
}
return rgb888torgb565(red, green, blue);
}
//black -> blue
if (TRX.FFT_Color == 6)
{
// r g b
// 0 0 0
// 0 0 255
if (height <= LAY_FFT_HEIGHT)
{
blue = (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT));
if (COLOR->WTF_BG_WHITE)
{
green -= (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT));
blue = 255;
red -= (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT));
}
}
return rgb888torgb565(red, green, blue);
}
//black -> white
if (TRX.FFT_Color == 7)
{
// r g b
// 0 0 0
// 255 255 255
if (height <= LAY_FFT_HEIGHT)
{
red = (uint_fast8_t)(height * 255 / (LAY_FFT_HEIGHT));
green = red;
blue = red;
if (COLOR->WTF_BG_WHITE)
{
red = 255 - red;
green = 255 - green;
blue = 255 - blue;
}
}
return rgb888torgb565(red, green, blue);
}
//unknown
return COLOR_WHITE;
}
static uint16_t getBGColor(uint_fast8_t height) // Get FFT background gradient
{
float32_t fftheight = LAY_FFT_HEIGHT;
float32_t step_red = (float32_t)(COLOR->FFT_GRADIENT_END_R - COLOR->FFT_GRADIENT_START_R) / fftheight;
float32_t step_green = (float32_t)(COLOR->FFT_GRADIENT_END_G - COLOR->FFT_GRADIENT_START_G) / fftheight;
float32_t step_blue = (float32_t)(COLOR->FFT_GRADIENT_END_B - COLOR->FFT_GRADIENT_START_B) / fftheight;
uint_fast8_t red = (uint_fast8_t)(COLOR->FFT_GRADIENT_START_R + (float32_t)height * step_red);
uint_fast8_t green = (uint_fast8_t)(COLOR->FFT_GRADIENT_START_G + (float32_t)height * step_green);
uint_fast8_t blue = (uint_fast8_t)(COLOR->FFT_GRADIENT_START_B + (float32_t)height * step_blue);
return rgb888torgb565(red, green, blue);
}
// prepare the color palette
static void FFT_fill_color_palette(void) // Fill FFT Color Gradient On Initialization
{
for (uint_fast8_t i = 0; i <= LAY_FFT_HEIGHT; i++)
{
palette_fft[i] = getFFTColor(LAY_FFT_HEIGHT - i);
palette_bg_gradient[i] = getBGColor(LAY_FFT_HEIGHT - i);
palette_bw_fft_colors[i] = addColor(palette_fft[i], FFT_BW_BRIGHTNESS, FFT_BW_BRIGHTNESS, FFT_BW_BRIGHTNESS);
palette_bw_bg_colors[i] = addColor(palette_bg_gradient[i], FFT_BW_BRIGHTNESS, FFT_BW_BRIGHTNESS, FFT_BW_BRIGHTNESS);
}
}
// reset FFT
void FFT_Reset(void) // clear the FFT
{
FFT_new_buffer_ready = false;
memset(FFTInput_I_A, 0x00, sizeof FFTInput_I_A);
memset(FFTInput_Q_A, 0x00, sizeof FFTInput_Q_A);
memset(FFTInput_I_B, 0x00, sizeof FFTInput_I_B);
memset(FFTInput_Q_B, 0x00, sizeof FFTInput_Q_B);
memset(FFTInputCharge, 0x00, sizeof FFTInputCharge);
memset(FFTInput, 0x00, sizeof FFTInput);
memset(FFTOutput_mean, 0x00, sizeof FFTOutput_mean);
FFT_buff_index = 0;
}
static inline int32_t getFreqPositionOnFFT(uint32_t freq)
{
int32_t pos = (int32_t)((float32_t)LAY_FFT_PRINT_SIZE / 2 + (float32_t)((float32_t)freq - (float32_t)CurrentVFO()->Freq) / FFT_HZ_IN_PIXEL * (float32_t)TRX.FFT_Zoom);
if (pos < 0 || pos >= LAY_FFT_PRINT_SIZE)
return -1;
return pos;
}
uint32_t getFreqOnFFTPosition(uint16_t position)
{
return (uint32_t)((int32_t)CurrentVFO()->Freq + (int32_t)(-((float32_t)LAY_FFT_PRINT_SIZE * (FFT_HZ_IN_PIXEL / (float32_t)TRX.FFT_Zoom) / 2.0f) + (float32_t)position * (FFT_HZ_IN_PIXEL / (float32_t)TRX.FFT_Zoom)));
}