/*  -*-  mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8  -*-  */
/************************************************************************************
 **                                                                                 **
 **                               mcHF QRP Transceiver                              **
 **                             K Atanassov - M0NKA 2014                            **
 **                                                                                 **
 **---------------------------------------------------------------------------------**
 **                                                                                 **
 **  File name:                                                                     **
 **  Description:                                                                   **
 **  Last Modified:                                                                 **
 **  Licence:      GNU GPLv3                                                      **
 ************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "ui_spectrum.h"
#include "ui_lcd_hy28.h"
// For spectrum display struct
#include "audio_driver.h"
#include "ui_driver.h"
#include "ui_menu.h"
#include "waterfall_colours.h"
#include "radio_management.h"
#include "rtty.h"
#include "cw_decoder.h"
#include "audio_nr.h"
#include "psk.h"
#include "uhsdr_math.h"
/*
#if defined(USE_DISP_480_320) || defined(USE_EXPERIMENTAL_MULTIRES)
#define USE_DISP_480_320_SPEC
#endif
*/

typedef struct
{
    const int16_t SCOPE_GRID_VERT_COUNT;
    const int16_t SCOPE_GRID_HORIZ;
} pos_spectrum_display_t;


SpectrumAreas_t slayout;

// full area =      x[const]=58,                y[const]= 128,                          w[const]= 262,                  h[const]=94
// draw area =      x[const]=full_area.x +2,    y[const]=full_area.y + 2,               w[const]=full_area.w - (2 + 2), h[const]=full_area.h - (2 + 2)
// title area =     x[const]=draw_area.x,       y[const]=draw_area.y,                   w[const]=draw_area.w,           h[var]=big?0:16
// scope area =     x[const]=draw_area.x,       y[var]=title_area.y+title_area.h,       w[const]=draw_area.w,           h[var]=scope_disabled?0:(draw_area.h - title_area.h - graticule_area.h)/(wfall_disabled?1:2)
// graticule area = x[const]=draw_area.x,       y[var]=scope_area.y+scope_area.h,       w[const]=draw_area.w,           h[const]=16
// wfall area =     x[const]=draw_area.x,       y[var]=graticule_area.y + graticule.h,  w[const]=draw_area.w,           h[var]=wfall_disabled?0:(draw_area.h - title_area.h - graticule_area.h)/(scope_disabled?1:2)

/*
 * @brief Implements the full calculation of coordinates for a variable sized spectrum display
 * This algorithm can also be used to calculate the layout statically offline (we don't do this yet).
 */
void UiSpectrum_CalculateLayout(const bool is_big, const UiArea_t* full_ptr, const uint16_t padding)
{
	sd.Slayout=&slayout;

    slayout.full.x = full_ptr->x;
    slayout.full.y = full_ptr->y;
    slayout.full.w = full_ptr->w;
    slayout.full.h = full_ptr->h;

    slayout.draw.x = slayout.full.x + padding;
    slayout.draw.y = slayout.full.y + padding;
    slayout.draw.w = slayout.full.w - 2*padding;
    slayout.draw.h = slayout.full.h - 2*padding;

    slayout.title.x = slayout.draw.x;
    slayout.title.y = slayout.draw.y;
    slayout.title.w = slayout.draw.w;
    slayout.title.h = is_big?0:16; // hide title if big

    slayout.graticule.x = slayout.draw.x;
    slayout.graticule.w = slayout.draw.w;
//    slayout.graticule.h = 16;
    slayout.graticule.h = 13;

    slayout.scope.x = slayout.draw.x;
    slayout.scope.y = slayout.title.y + slayout.title.h;
    slayout.scope.w = slayout.draw.w;

   // UiSpectrum_SetNewGraticulePosition(ts.graticulePowerupYpos);
    UiSpectrum_ResetSpectrum();

    slayout.scope.h = slayout.graticule.y - slayout.scope.y;

    slayout.wfall.x = slayout.draw.x;
    slayout.wfall.y = slayout.graticule.y + slayout.graticule.h;
    slayout.wfall.w = slayout.draw.w;
    slayout.wfall.h = slayout.draw.y+slayout.draw.h-slayout.wfall.y;
}

//sets graticule position according to control bits (to default for particular case)
void UiSpectrum_ResetSpectrum()
{
	switch(ts.flags1&(FLAGS1_SCOPE_ENABLED | FLAGS1_WFALL_ENABLED))
	{
	case FLAGS1_SCOPE_ENABLED:
		slayout.graticule.y=slayout.draw.y+slayout.draw.h-slayout.graticule.h;
		break;
	case FLAGS1_WFALL_ENABLED:
		slayout.graticule.y=slayout.draw.y+slayout.title.h;
		break;
	case (FLAGS1_SCOPE_ENABLED | FLAGS1_WFALL_ENABLED):
		slayout.graticule.y=UiSprectrum_CheckNewGraticulePos(ts.graticulePowerupYpos);
		break;
	default:
		break;
	}
}
uint16_t UiSprectrum_CheckNewGraticulePos(uint16_t new_y)
{
	if((new_y<sd.Slayout->draw.y))
	{
		new_y=sd.Slayout->draw.y+sd.Slayout->draw.h/2-sd.Slayout->graticule.h/2;		//the default setting for graticule location if invalid value detected (first powerup issue)
	}
	if((new_y<sd.Slayout->draw.y+MinimumScopeSize))
	{
		new_y=sd.Slayout->draw.y+MinimumScopeSize;
	}
	if(new_y>(sd.Slayout->draw.y+sd.Slayout->draw.h-MinimumWaterfallSize-sd.Slayout->graticule.h))
	{
		new_y=sd.Slayout->draw.y+sd.Slayout->draw.h-MinimumWaterfallSize-sd.Slayout->graticule.h;
	}
	return new_y;
}

const pos_spectrum_display_t pos_spectrum_set[] =
{
#ifdef USE_DISP_320_240
        {
#if 0
                .WIDTH = 256,
                .DRAW_Y_TOP = POS_SPECTRUM_DRAW_Y_TOP,
                .NORMAL_HEIGHT = SPECTRUM_HEIGHT,
                .BIG_HEIGHT= (SPECTRUM_HEIGHT + SPEC_LIGHT_MORE_POINTS),
                .DRAW_X_LEFT= (POS_SPECTRUM_IND_X - 2),
                .DRAW_HEIGHT= (94),
                .DRAW_WIDTH= (262),
                .NORMAL_START_Y= (SPECTRUM_START_Y),
                .BIG_START_Y= (SPECTRUM_START_Y - SPEC_LIGHT_MORE_POINTS),
                .GRATICULE_Y = (POS_SPECTRUM_IND_Y + 60),
#endif
                // .GRID_VERT_START = POS_SPECTRUM_GRID_VERT_START,
                // .GRID_HORIZ_START = POS_SPECTRUM_GRID_HORIZ_START,
                // .SCOPE_GRID_VERT = SPECTRUM_SCOPE_GRID_VERT,
                .SCOPE_GRID_VERT_COUNT = SPECTRUM_SCOPE_GRID_VERT_COUNT,
                .SCOPE_GRID_HORIZ = SPECTRUM_SCOPE_GRID_HORIZ,
#if 0
                .IND_Y = POS_SPECTRUM_IND_Y,
                .IND_X = POS_SPECTRUM_IND_X,
                .START_X = POS_SPECTRUM_IND_X, // (SPECTRUM_START_X) seems to be a duplicate; used for waterfall left x
                .IND_W = POS_SPECTRUM_IND_W,
                .FREQ_BAR_Y = POS_SPECTRUM_FREQ_BAR_Y,
                .FREQ_BAR_H = POS_SPECTRUM_FREQ_BAR_H,
                .NORMAL_WATERFALL_START_Y = (SPECTRUM_START_Y + SPECTRUM_SCOPE_TOP_LIMIT),
                .NORMAL_WATERFALL_HEIGHT = (SPECTRUM_HEIGHT - SPECTRUM_SCOPE_TOP_LIMIT),
                .BIG_WATERFALL_START_Y = SPECTRUM_START_Y - WFALL_MEDIUM_ADDITIONAL,
                .BIG_WATERFALL_HEIGHT = SPECTRUM_HEIGHT + WFALL_MEDIUM_ADDITIONAL,
#endif
        },
#endif
//#ifdef USE_DISP_480_320_SPEC
         {
                .SCOPE_GRID_HORIZ = SPECTRUM_SCOPE_GRID_HORIZ,
                .SCOPE_GRID_VERT_COUNT = SPECTRUM_SCOPE_GRID_VERT_COUNT,
        },
//#endif
};



// in single resolution case we can set both of the to const, then the compiler will optimize
// it all memory access to the data away and the code is as performant as with all constant coordinates.
// const pos_spectrum_display_t* pos_spectrum = &pos_spectrum_set[0];
// const disp_resolution_t disp_resolution;
const pos_spectrum_display_t* pos_spectrum = &pos_spectrum_set[0];




// ------------------------------------------------
// Spectrum display public
SpectrumDisplay  __MCHF_SPECIALMEM       sd;
// this data structure is now located in the Core Connected Memory of the STM32F4
// this is highly hardware specific code. This data structure nicely fills the 64k with roughly 60k.
// If this data structure is being changed,  be aware of the 64k limit. See linker script arm-gcc-link.ld
//
// scaling factors for the various dB/division settings
//
#define DB_SCALING_5                        63.2456     // 5dB/division scaling
#define DB_SCALING_7                        42.1637     // 7.5dB/division scaling
#define DB_SCALING_10                       31.6228     // 10dB/division scaling
#define DB_SCALING_15                       21.0819     // 15dB/division scaling
#define DB_SCALING_20                       15.8114     // 20dB/division scaling
#define DB_SCALING_S1                       52.7046     // 1 S unit (6 dB)/division scaling
#define DB_SCALING_S2                       26.3523     // 2 S unit (12 dB)/division scaling
#define DB_SCALING_S3                       17.5682     // 3 S unit (18 dB)/division scaling
//


typedef struct
{
    float32_t value;
    const char* label;
} scope_scaling_info_t;
/*
static const scope_scaling_info_t scope_scaling_factors[SCOPE_SCALE_NUM+1] =
{
        // scaling factors for the various dB/division settings
        { 0,                        "Waterfall      " }, // just a small trick, the scope will never use scaling index 0
        { DB_SCALING_5,             "SC(5dB/div)    " },
        { DB_SCALING_7,             "SC(7.5dB/div)  " },
        { DB_SCALING_10,            "SC(10dB/div)   " },
        { DB_SCALING_15,            "SC(15dB/div)   " },
        { DB_SCALING_20,            "SC(20dB/div)   " },
        { DB_SCALING_S1,            "SC(1S-Unit/div)" },
        { DB_SCALING_S2,            "SC(2S-Unit/div)" },
        { DB_SCALING_S3,            "SC(3S-Unit/div)" },
        { 0,                        "Dual (10dB/div)" }, // just a small trick, the scope will never use scaling index SCOPE_SCALE_NUM
};*/

static const scope_scaling_info_t scope_scaling_factors[SCOPE_SCALE_NUM+1] =
{
        // scaling factors for the various dB/division settings
        { 0,                        "Waterfall  " }, // just a small trick, the scope will never use scaling index 0
        { DB_SCALING_5,             "(5dB/div)  " },
        { DB_SCALING_7,             "(7.5dB/div)" },
        { DB_SCALING_10,            "(10dB/div) " },
        { DB_SCALING_15,            "(15dB/div) " },
        { DB_SCALING_20,            "(20dB/div) " },
        { DB_SCALING_S1,            "(1S/div)   " },
        { DB_SCALING_S2,            "(2S/div)   " },
        { DB_SCALING_S3,            "(3S/div)   " },
};

static void     UiSpectrum_DrawFrequencyBar(void);
static void		UiSpectrum_CalculateDBm(void);

// FIXME: This is partially application logic and should be moved to UI and/or radio management
// instead of monitoring change, changes should trigger update of spectrum configuration (from pull to push)
static void UiSpectrum_UpdateSpectrumPixelParameters(void)
{
    static uint16_t old_magnify = 0xFF;
    static bool old_lsb = false;
    static uint8_t old_dmod_mode = 0xFF;
    static uint8_t old_iq_freq_mode = 0xFF;
    static uint16_t old_cw_sidetone_freq = 0;
    static uint16_t old_rtty_shift = 0;
    static uint8_t old_digital_mode = 0xFF;
//    static int32_t old_iq_freq_delta = 0;
    static int32_t old_iq_freq_delta = -20000;

    static bool force_update = true;

    if (sd.magnify != old_magnify || force_update)
    {
        old_magnify = sd.magnify;
        sd.hz_per_pixel = IQ_SAMPLE_RATE_F/((1 << sd.magnify) * slayout.scope.w);     // magnify mode is on
        force_update = true;
    }

//    if (ts.iq_freq_mode != old_iq_freq_mode  || force_update)
//    if (ts.iq_freq_mode != old_iq_freq_mode  || force_update || (ts.iq_freq_mode == FREQ_IQ_CONV_SLIDE && ts.iq_freq_delta != old_iq_freq_delta))
    if ((ts.iq_freq_mode == FREQ_IQ_CONV_SLIDE && ts.iq_freq_delta != old_iq_freq_delta) || force_update)
    {
//        old_iq_freq_mode = ts.iq_freq_mode;
        old_iq_freq_delta = ts.iq_freq_delta;
        force_update = true;
    }

    if (ts.iq_freq_mode != old_iq_freq_mode  || force_update)
    {
        old_iq_freq_mode = ts.iq_freq_mode;
        force_update = true;
        if(!sd.magnify)     // is magnify mode on?
        {
            sd.rx_carrier_pos = slayout.scope.w/2 - 0.5 - (AudioDriver_GetTranslateFreq()/sd.hz_per_pixel);
        }
        else        // magnify mode is on
        {
//            sd.rx_carrier_pos = slayout.scope.w/2 -0.5;                                // line is always in center in "magnify" mode
            if (ts.iq_freq_mode == FREQ_IQ_CONV_SLIDE) {
                sd.rx_carrier_pos = slayout.scope.w/2 - 0.5 - (AudioDriver_GetTranslateFreq()/sd.hz_per_pixel);
            } else {
                sd.rx_carrier_pos = slayout.scope.w/2 -0.5;                                // line is always in center in "magnify" mode
            }
        }
    }
    bool cur_lsb = RadioManagement_LSBActive(ts.dmod_mode);

    if (cur_lsb != old_lsb || ts.cw_sidetone_freq != old_cw_sidetone_freq || old_rtty_shift != rtty_ctrl_config.shift_idx || ts.dmod_mode != old_dmod_mode || ts.digital_mode != old_digital_mode || force_update)
    {
        old_lsb = cur_lsb;
        old_cw_sidetone_freq = ts.cw_sidetone_freq;
        old_dmod_mode = ts.dmod_mode;
        old_digital_mode = ts.digital_mode;
        old_rtty_shift = rtty_ctrl_config.shift_idx;

//      float32_t tx_vfo_offset = ((float32_t)(((int32_t)RadioManagement_GetTXDialFrequency() - (int32_t)RadioManagement_GetRXDialFrequency())))/sd.hz_per_pixel;
        float32_t tx_vfo_offset = 0;
        if(!is_splitmode())
        {
            tx_vfo_offset = ((float32_t)(((int32_t)RadioManagement_GetTXDialFrequency() - (int32_t)RadioManagement_GetRXDialFrequency())))/sd.hz_per_pixel;
        }

        // FIXME: DOES NOT WORK PROPERLY IN SPLIT MODE
        sd.marker_num_prev = sd.marker_num;
        float32_t mode_marker_offset[SPECTRUM_MAX_MARKER];
        switch(ts.dmod_mode)
        {
        case DEMOD_CW:
            mode_marker_offset[0] =(ts.cw_lsb?-1.0:1.0)*((float32_t)ts.cw_sidetone_freq / sd.hz_per_pixel);
            sd.marker_num = 1;
            break;
        case DEMOD_DIGI:
        {
            float32_t mode_marker[SPECTRUM_MAX_MARKER];
            switch(ts.digital_mode)
            {
#ifdef USE_FREEDV
            case DigitalMode_FreeDV:
            	// 1500 +/- 625Hz
                mode_marker[0] = 875;
                mode_marker[1] = 2125;
//                sd.marker_num = 2;
                mode_marker[2] = 0;
                sd.marker_num = 3;
                break;
#endif
            case DigitalMode_RTTY:
                mode_marker[0] = 915; // Mark Frequency
                mode_marker[1] = mode_marker[0] + rtty_shifts[rtty_ctrl_config.shift_idx].value;
//                sd.marker_num = 2;
                mode_marker[2] = 0;
                sd.marker_num = 3;
                break;
            case DigitalMode_BPSK:
                mode_marker[0] = PSK_OFFSET - psk_speeds[psk_ctrl_config.speed_idx].value / 2;
                mode_marker[1] = PSK_OFFSET + psk_speeds[psk_ctrl_config.speed_idx].value / 2;
//                sd.marker_num = 2;
                mode_marker[2] = 0;
                sd.marker_num = 3;
            	break;
            default:
                mode_marker[0] = 0;
                sd.marker_num = 1;
            }

            for (uint16_t idx = 0; idx < sd.marker_num; idx++)
            {
                mode_marker_offset[idx] = (ts.digi_lsb?-1.0:1.0)*(mode_marker[idx] / sd.hz_per_pixel);
            }
        }
        break;
        default:
            mode_marker_offset[0] = 0;
            sd.marker_num = 1;
        }

        for (uint16_t idx = 0; idx < sd.marker_num; idx++)
        {
            sd.marker_offset[idx] = tx_vfo_offset + mode_marker_offset[idx];
            sd.marker_pos[idx] = sd.rx_carrier_pos + sd.marker_offset[idx];
        }
        for (uint16_t idx = sd.marker_num; idx < SPECTRUM_MAX_MARKER; idx++)
        {
        	sd.marker_offset[idx] = 0;
//        	sd.marker_pos[idx] = slayout.scope.w; // this is an invalid position out of screen
            sd.marker_pos[idx] = slayout.scope.w * 2; // this is an invalid position out of screen
        }

        force_update = false;
    }
}

#ifdef USE_PREDEFINED_WINDOW_DATA
// Information on these windowing functions may be found on the internet - check the Wikipedia article "Window Function"
// KA7OEI - 20150602
// we should hardcode the windows in order to save a considerable amount of processing time DD4WH, Feb 2018
// const von_Hann_256 [256] = {0,0.00015177,0.000607,0.00136541,0.00242654,0.00378975,0.0054542,0.00741888,0.00968261,0.01224402,0.01510153,0.01825343,0.02169779,0.02543253,0.02945537,0.03376389,0.03835545,0.04322727,0.0483764,0.05379971,0.0594939,0.06545553,0.07168096,0.07816643,0.08490798,0.09190154,0.09914286,0.10662753,0.11435102,0.12230863,0.13049554,0.13890677,0.14753723,0.15638166,0.1654347,0.17469085,0.1841445,0.1937899,0.2036212,0.21363243,0.22381751,0.23417027,0.2446844,0.25535354,0.2661712,0.27713082,0.28822574,0.29944923,0.31079447,0.32225458,0.3338226,0.3454915,0.35725421,0.36910357,0.3810324,0.39303346,0.40509945,0.41722306,0.42939692,0.44161365,0.45386582,0.466146,0.47844673,0.49076055,0.50307997,0.51539753,0.52770574,0.53999713,0.55226423,0.56449961,0.57669583,0.58884548,0.6009412,0.61297564,0.62494149,0.6368315,0.64863843,0.66035512,0.67197446,0.6834894,0.69489294,0.70617816,0.71733821,0.72836632,0.73925578,0.75,0.76059244,0.77102668,0.78129638,0.7913953,0.80131732,0.81105641,0.82060666,0.82996227,0.83911756,0.84806697,0.85680508,0.86532657,0.87362627,0.88169914,0.88954029,0.89714494,0.9045085,0.91162647,0.91849455,0.92510857,0.9314645,0.93755849,0.94338684,0.94894602,0.95423264,0.95924349,0.96397554,0.96842592,0.97259191,0.976471,0.98006082,0.9833592,0.98636414,0.9890738,0.99148655,0.99360092,0.99541563,0.99692957,0.99814183,0.99905166,0.99965853,0.99996206,0.99996206,0.99965853,0.99905166,0.99814183,0.99692957,0.99541563,0.99360092,0.99148655,0.9890738,0.98636414,0.9833592,0.98006082,0.976471,0.97259191,0.96842592,0.96397554,0.95924349,0.95423264,0.94894602,0.94338684,0.93755849,0.9314645,0.92510857,0.91849455,0.91162647,0.9045085,0.89714494,0.88954029,0.88169914,0.87362627,0.86532657,0.85680508,0.84806697,0.83911756,0.82996227,0.82060666,0.81105641,0.80131732,0.7913953,0.78129638,0.77102668,0.76059244,0.75,0.73925578,0.72836632,0.71733821,0.70617816,0.69489294,0.6834894,0.67197446,0.66035512,0.64863843,0.6368315,0.62494149,0.61297564,0.6009412,0.58884548,0.57669583,0.56449961,0.55226423,0.53999713,0.52770574,0.51539753,0.50307997,0.49076055,0.47844673,0.466146,0.45386582,0.44161365,0.42939692,0.41722306,0.40509945,0.39303346,0.3810324,0.36910357,0.35725421,0.3454915,0.3338226,0.32225458,0.31079447,0.29944923,0.28822574,0.27713082,0.2661712,0.25535354,0.2446844,0.23417027,0.22381751,0.21363243,0.2036212,0.1937899,0.1841445,0.17469085,0.1654347,0.15638166,0.14753723,0.13890677,0.13049554,0.12230863,0.11435102,0.10662753,0.09914286,0.09190154,0.08490798,0.07816643,0.07168096,0.06545553,0.0594939,0.05379971,0.0483764,0.04322727,0.03835545,0.03376389,0.02945537,0.02543253,0.02169779,0.01825343,0.01510153,0.01224402,0.00968261,0.00741888,0.0054542,0.00378975,0.00242654,0.00136541,0.000607,0.00015177,0};

	//we have to provide both (512 and 1024) of the window coefficients because of automatic screen resolution change, SP9BSL Feb 2018
	const float32_t von_Hann_512 [512] = {0,3.77966E-05,0.000151181,0.000340135,0.000604631,0.000944629,0.001360077,0.001850913,0.002417062,0.003058438,0.003774946,0.004566476,0.005432908,0.006374113,0.007389947,0.008480256,0.009644877,0.010883633,0.012196337,0.013582789,0.015042782,0.016576093,0.018182492,0.019861734,0.021613567,0.023437725,0.025333934,0.027301905,0.029341341,0.031451935,0.033633367,0.035885307,0.038207415,0.040599339,0.043060719,0.045591181,0.048190344,0.050857815,0.053593189,0.056396054,0.059265986,0.062202551,0.065205305,0.068273794,0.071407554,0.074606112,0.077868983,0.081195674,0.084585683,0.088038497,0.091553594,0.095130442,0.098768501,0.102467221,0.106226043,0.110044397,0.113921708,0.117857388,0.121850843,0.125901469,0.130008654,0.134171776,0.138390206,0.142663307,0.146990432,0.151370928,0.155804131,0.160289372,0.164825973,0.169413247,0.174050502,0.178737036,0.18347214,0.188255099,0.19308519,0.197961681,0.202883837,0.207850913,0.212862158,0.217916814,0.223014117,0.228153297,0.233333576,0.238554171,0.243814294,0.249113148,0.254449933,0.259823842,0.265234062,0.270679775,0.276160159,0.281674384,0.287221617,0.292801019,0.298411747,0.304052952,0.309723782,0.315423378,0.321150881,0.326905422,0.332686134,0.338492141,0.344322565,0.350176526,0.356053138,0.361951513,0.36787076,0.373809982,0.379768282,0.38574476,0.391738511,0.397748631,0.403774209,0.409814335,0.415868096,0.421934577,0.42801286,0.434102027,0.440201156,0.446309327,0.452425614,0.458549094,0.464678841,0.470813928,0.476953428,0.483096412,0.489241951,0.495389117,0.50153698,0.507684611,0.51383108,0.519975458,0.526116815,0.532254225,0.538386758,0.544513487,0.550633486,0.556745831,0.562849596,0.568943859,0.575027699,0.581100196,0.587160431,0.593207489,0.599240456,0.605258418,0.611260467,0.617245695,0.623213197,0.62916207,0.635091417,0.641000339,0.646887944,0.652753341,0.658595644,0.66441397,0.670207439,0.675975174,0.681716305,0.687429962,0.693115283,0.698771407,0.70439748,0.709992651,
			0.715556073,0.721086907,0.726584315,0.732047467,0.737475536,0.742867702,0.74822315,0.75354107,0.758820659,0.764061117,0.769261652,0.774421479,0.779539817,0.784615893,0.789648938,0.794638193,0.799582902,0.804482319,0.809335702,0.814142317,0.818901439,0.823612347,0.828274329,0.832886681,0.837448705,0.841959711,0.846419017,0.85082595,0.855179843,0.859480037,0.863725883,0.867916738,0.87205197,0.876130952,0.880153069,0.884117711,0.888024281,0.891872186,0.895660845,0.899389686,0.903058145,0.906665667,0.910211707,0.913695728,0.917117204,0.920475618,0.923770461,0.927001237,0.930167455,0.933268638,0.936304317,0.939274033,0.942177336,0.945013788,0.94778296,0.950484434,0.9531178,0.955682662,0.95817863,0.960605328,0.962962389,0.965249456,0.967466184,0.969612237,0.971687291,0.973691033,0.975623159,0.977483377,0.979271407,0.980986977,0.982629829,0.984199713,0.985696393,0.987119643,0.988469246,0.989745,0.990946711,0.992074198,0.99312729,0.994105827,0.995009663,0.99583866,0.996592693,0.997271648,0.997875422,0.998403924,0.998857075,0.999234805,0.999537058,0.999763787,0.999914959,0.999990551,0.999990551,0.999914959,0.999763787,0.999537058,0.999234805,0.998857075,0.998403924,0.997875422,0.997271648,0.996592693,0.99583866,0.995009663,0.994105827,0.99312729,0.992074198,0.990946711,0.989745,0.988469246,0.987119643,0.985696393,0.984199713,0.982629829,0.980986977,0.979271407,0.977483377,0.975623159,0.973691033,0.971687291,0.969612237,0.967466184,0.965249456,0.962962389,0.960605328,0.95817863,0.955682662,0.9531178,0.950484434,0.94778296,0.945013788,0.942177336,0.939274033,0.936304317,0.933268638,0.930167455,0.927001237,0.923770461,0.920475618,0.917117204,0.913695728,0.910211707,0.906665667,0.903058145,0.899389686,0.895660845,0.891872186,0.888024281,0.884117711,0.880153069,0.876130952,0.87205197,0.867916738,0.863725883,0.859480037,0.855179843,0.85082595,0.846419017,0.841959711,0.837448705,0.832886681,0.828274329,0.823612347,0.818901439,0.814142317,0.809335702,0.804482319,0.799582902,0.794638193,0.789648938,0.784615893,0.779539817,0.774421479,0.769261652,0.764061117,0.758820659,0.75354107,0.74822315,0.742867702,0.737475536,0.732047467,0.726584315,0.721086907,0.715556073,0.709992651,0.70439748,
			0.698771407,0.693115283,0.687429962,0.681716305,0.675975174,0.670207439,0.66441397,0.658595644,0.652753341,0.646887944,0.641000339,0.635091417,0.62916207,0.623213197,0.617245695,0.611260467,0.605258418,0.599240456,0.593207489,0.587160431,0.581100196,0.575027699,0.568943859,0.562849596,0.556745831,0.550633486,0.544513487,0.538386758,0.532254225,0.526116815,0.519975458,0.51383108,0.507684611,0.50153698,0.495389117,0.489241951,0.483096412,0.476953428,0.470813928,0.464678841,0.458549094,0.452425614,0.446309327,0.440201156,0.434102027,0.42801286,0.421934577,0.415868096,0.409814335,0.403774209,0.397748631,0.391738511,0.38574476,0.379768282,0.373809982,0.36787076,0.361951513,0.356053138,0.350176526,0.344322565,0.338492141,0.332686134,0.326905422,0.321150881,0.315423378,0.309723782,0.304052952,0.298411747,0.292801019,0.287221617,0.281674384,0.276160159,0.270679775,0.265234062,0.259823842,0.254449933,0.249113148,0.243814294,0.238554171,0.233333576,0.228153297,0.223014117,0.217916814,0.212862158,0.207850913,0.202883837,0.197961681,0.19308519,0.188255099,0.18347214,0.178737036,0.174050502,0.169413247,0.164825973,0.160289372,0.155804131,0.151370928,0.146990432,0.142663307,0.138390206,0.134171776,0.130008654,0.125901469,0.121850843,0.117857388,0.113921708,0.110044397,0.106226043,0.102467221,0.098768501,0.095130442,0.091553594,0.088038497,0.084585683,0.081195674,0.077868983,0.074606112,0.071407554,0.068273794,0.065205305,0.062202551,0.059265986,0.056396054,0.053593189,0.050857815,0.048190344,0.045591181,0.043060719,0.040599339,0.038207415,0.035885307,0.033633367,0.031451935,0.029341341,0.027301905,0.025333934,0.023437725,0.021613567,0.019861734,0.018182492,0.016576093,0.015042782,0.013582789,0.012196337,0.010883633,0.009644877,0.008480256,0.007389947,0.006374113,0.005432908,0.004566476,0.003774946,0.003058438,0.002417062,0.001850913,0.001360077,0.000944629,0.000604631,0.000340135,0.000151181,3.77966E-05,0};
	const float32_t von_Hann_1024 [1024] = {0,9.43077E-06,3.77227E-05,8.48748E-05,0.000150885,0.000235751,0.00033947,0.000462038,0.00060345,0.0007637,0.000942783,0.001140693,0.00135742,0.001592958,0.001847298,0.002120429,0.002412342,0.002723026,0.003052468,0.003400657,0.003767578,0.00415322,0.004557566,0.004980602,0.005422311,0.005882678,0.006361684,0.006859311,0.007375542,0.007910355,0.008463732,0.009035651,0.009626091,0.010235029,0.010862443,0.011508308,0.012172601,0.012855296,0.013556368,0.01427579,0.015013535,0.015769575,0.016543882,0.017336426,0.018147178,0.018976107,0.019823181,0.020688369,0.021571639,0.022472956,0.023392287,0.024329597,0.025284851,0.026258012,0.027249045,0.028257911,0.029284572,0.030328991,0.031391127,0.03247094,0.03356839,0.034683435,0.035816033,0.036966142,0.038133718,0.039318717,0.040521094,0.041740804,0.042977801,0.044232039,0.045503469,0.046792044,0.048097716,0.049420435,0.050760151,0.052116814,0.053490372,0.054880775,0.056287968,0.0577119,0.059152516,0.060609762,0.062083583,0.063573924,0.065080728,0.066603939,0.068143498,0.069699349,0.071271432,0.072859688,0.074464057,0.076084478,0.077720891,0.079373234,0.081041443,0.082725458,
			0.084425213,0.086140645,0.087871689,0.089618279,0.091380351,0.093157837,0.09495067,0.096758783,0.098582108,0.100420575,0.102274115,0.104142659,0.106026136,0.107924475,0.109837605,0.111765452,0.113707945,0.115665009,0.117636572,0.11962256,0.121622896,0.123637505,0.125666312,0.127709241,0.129766213,0.131837151,0.133921978,0.136020614,0.138132981,0.140258998,0.142398586,0.144551664,0.146718151,0.148897964,0.151091022,0.153297242,0.15551654,0.157748834,0.159994038,0.162252069,0.16452284,0.166806267,0.169102262,0.17141074,0.173731614,0.176064795,0.178410196,0.180767729,0.183137304,0.185518832,0.187912223,0.190317387,0.192734233,0.195162671,0.197602607,0.200053951,0.20251661,0.204990491,0.207475501,0.209971545,0.21247853,0.214996362,0.217524945,0.220064183,0.222613982,0.225174244,0.227744874,0.230325774,0.232916847,0.235517995,0.23812912,0.240750124,0.243380907,0.246021371,0.248671416,0.251330942,0.253999848,0.256678034,0.259365399,0.262061841,0.26476726,0.267481552,0.270204615,
			0.272936347,0.275676644,0.278425404,0.281182522,0.283947894,0.286721417,0.289502985,0.292292493,0.295089838,0.297894912,0.30070761,0.303527825,0.306355453,0.309190385,0.312032515,0.314881736,0.31773794,0.32060102,0.323470867,0.326347374,0.329230431,0.33211993,0.335015762,0.337917817,0.340825987,0.343740162,0.346660231,0.349586084,0.352517612,0.355454703,0.358397247,0.361345132,0.364298248,0.367256483,0.370219726,0.373187864,0.376160786,0.379138379,0.382120532,0.385107132,0.388098066,0.391093221,0.394092484,0.397095742,0.400102883,0.403113792,0.406128355,0.40914646,0.412167992,0.415192837,0.418220882,0.421252011,0.424286111,0.427323067,0.430362765,0.43340509,0.436449927,0.439497161,0.442546678,0.445598362,0.448652098,0.451707771,0.454765266,0.457824467,0.460885259,0.463947527,0.467011155,0.470076027,0.473142028,0.476209042,0.479276954,0.482345647,0.485415006,0.488484916,0.49155526,0.494625922,0.497696788,0.50076774,0.503838663,0.506909441,0.509979959,0.5130501,0.516119749,0.51918879,0.522257107,0.525324584,0.528391106,0.531456558,0.534520822,0.537583784,0.540645329,0.54370534,0.546763702,
			0.549820301,0.55287502,0.555927744,0.558978359,0.562026748,0.565072798,0.568116393,0.571157419,0.57419576,0.577231303,0.580263932,0.583293533,0.586319992,0.589343195,0.592363028,0.595379376,0.598392127,0.601401166,0.604406379,0.607407654,0.610404877,0.613397936,0.616386717,0.619371107,0.622350994,0.625326266,0.62829681,0.631262515,0.634223267,0.637178957,0.640129471,0.6430747,0.646014531,0.648948854,0.651877559,0.654800534,0.657717669,0.660628855,0.663533982,0.666432939,0.669325618,0.67221191,0.675091705,0.677964896,0.680831372,0.683691028,0.686543754,0.689389443,0.692227987,0.695059281,0.697883216,0.700699686,0.703508585,0.706309807,0.709103247,0.711888798,0.714666357,0.717435818,0.720197076,0.722950028,0.725694569,0.728430596,0.731158007,0.733876697,0.736586565,0.739287508,0.741979424,0.744662213,0.747335771,0.75,0.752654798,0.755300065,0.757935701,0.760561607,0.763177684,0.765783833,0.768379955,0.770965954,0.773541731,0.776107189,0.778662232,0.781206762,0.783740685,0.786263904,0.788776324,0.791277851,0.793768389,
			0.796247846,0.798716128,0.801173141,0.803618793,0.806052991,0.808475644,0.810886661,0.81328595,0.815673421,0.818048983,0.820412548,0.822764026,0.825103329,0.827430367,0.829745054,0.832047301,0.834337023,0.836614133,0.838878544,0.841130172,0.843368932,0.845594738,0.847807508,0.850007157,0.852193603,0.854366763,0.856526556,0.858672899,0.860805712,0.862924914,0.865030425,0.867122167,0.869200059,0.871264024,0.873313984,0.875349861,0.877371579,0.879379061,0.881372232,0.883351017,0.88531534,0.887265128,0.889200307,0.891120804,0.893026547,0.894917464,0.896793484,0.898654535,0.900500547,0.902331452,0.904147179,0.905947661,0.907732829,0.909502616,0.911256955,0.912995781,0.914719027,0.916426628,0.918118521,0.919794641,0.921454925,0.92309931,0.924727735,0.926340138,0.927936458,0.929516635,0.931080609,0.932628322,0.934159714,0.935674728,0.937173308,0.938655396,0.940120937,0.941569875,0.943002155,0.944417724,0.945816529,0.947198515,0.948563632,0.949911828,0.951243052,0.952557254,0.953854383,0.955134392,0.956397232,0.957642855,0.958871214,0.960082264,0.961275958,0.96245225,0.963611098,0.964752457,0.965876284,0.966982537,0.968071174,0.969142153,0.970195436,0.971230981,0.972248749,0.973248703,0.974230805,0.975195017,0.976141303,0.977069628,0.977979956,
			0.978872254,0.979746487,0.980602622,0.981440628,0.982260472,0.983062124,0.983845553,0.98461073,0.985357626,0.986086213,0.986796463,0.98748835,0.988161847,0.988816929,0.989453572,0.990071751,0.990671442,0.991252625,0.991815275,0.992359373,0.992884898,0.993391829,0.993880148,0.994349837,0.994800877,0.995233251,0.995646944,0.99604194,0.996418223,0.99677578,0.997114597,0.997434662,0.997735961,0.998018485,0.998282221,0.998527161,0.998753295,0.998960615,0.999149112,0.99931878,0.999469611,0.999601602,0.999714745,0.999809038,0.999884477,0.999941059,0.999978781,0.999997642,0.999997642,0.999978781,0.999941059,0.999884477,0.999809038,0.999714745,0.999601602,0.999469611,0.99931878,0.999149112,0.998960615,0.998753295,0.998527161,0.998282221,0.998018485,0.997735961,0.997434662,0.997114597,0.99677578,0.996418223,0.99604194,0.995646944,0.995233251,0.994800877,0.994349837,0.993880148,0.993391829,0.992884898,0.992359373,0.991815275,0.991252625,0.990671442,0.990071751,0.989453572,0.988816929,0.988161847,0.98748835,0.986796463,0.986086213,0.985357626,0.98461073,0.983845553,0.983062124,0.982260472,0.981440628,0.980602622,0.979746487,0.978872254,0.977979956,0.977069628,0.976141303,0.975195017,0.974230805,0.973248703,0.972248749,0.971230981,0.970195436,0.969142153,0.968071174,0.966982537,0.965876284,0.964752457,0.963611098,0.96245225,0.961275958,0.960082264,0.958871214,0.957642855,0.956397232,0.955134392,0.953854383,0.952557254,0.951243052,0.949911828,0.948563632,0.947198515,0.945816529,0.944417724,0.943002155,0.941569875,0.940120937,0.938655396,0.937173308,0.935674728,0.934159714,0.932628322,
			0.931080609,0.929516635,0.927936458,0.926340138,0.924727735,0.92309931,0.921454925,0.919794641,0.918118521,0.916426628,0.914719027,0.912995781,0.911256955,0.909502616,0.907732829,0.905947661,0.904147179,0.902331452,0.900500547,0.898654535,0.896793484,0.894917464,0.893026547,0.891120804,0.889200307,0.887265128,0.88531534,0.883351017,0.881372232,0.879379061,0.877371579,0.875349861,0.873313984,0.871264024,0.869200059,0.867122167,0.865030425,0.862924914,0.860805712,0.858672899,0.856526556,0.854366763,0.852193603,0.850007157,0.847807508,0.845594738,0.843368932,0.841130172,0.838878544,0.836614133,0.834337023,0.832047301,0.829745054,0.827430367,0.825103329,0.822764026,0.820412548,0.818048983,0.815673421,0.81328595,0.810886661,0.808475644,0.806052991,0.803618793,0.801173141,0.798716128,0.796247846,0.793768389,0.791277851,0.788776324,0.786263904,0.783740685,0.781206762,0.778662232,0.776107189,0.773541731,0.770965954,0.768379955,0.765783833,0.763177684,0.760561607,0.757935701,0.755300065,0.752654798,0.75,0.747335771,0.744662213,0.741979424,0.739287508,0.736586565,0.733876697,0.731158007,0.728430596,0.725694569,0.722950028,0.720197076,0.717435818,0.714666357,0.711888798,0.709103247,0.706309807,0.703508585,0.700699686,0.697883216,0.695059281,0.692227987,0.689389443,0.686543754,0.683691028,0.680831372,0.677964896,0.675091705,0.67221191,0.669325618,0.666432939,0.663533982,0.660628855,0.657717669,0.654800534,0.651877559,0.648948854,0.646014531,0.6430747,0.640129471,0.637178957,0.634223267,0.631262515,0.62829681,0.625326266,0.622350994,0.619371107,0.616386717,0.613397936,0.610404877,0.607407654,0.604406379,0.601401166,0.598392127,0.595379376,0.592363028,0.589343195,0.586319992,0.583293533,0.580263932,
			0.577231303,0.57419576,0.571157419,0.568116393,0.565072798,0.562026748,0.558978359,0.555927744,0.55287502,0.549820301,0.546763702,0.54370534,0.540645329,0.537583784,0.534520822,0.531456558,0.528391106,0.525324584,0.522257107,0.51918879,0.516119749,0.5130501,0.509979959,0.506909441,0.503838663,0.50076774,0.497696788,0.494625922,0.49155526,0.488484916,0.485415006,0.482345647,0.479276954,0.476209042,0.473142028,0.470076027,0.467011155,0.463947527,0.460885259,0.457824467,0.454765266,0.451707771,0.448652098,0.445598362,0.442546678,0.439497161,0.436449927,0.43340509,0.430362765,0.427323067,0.424286111,0.421252011,0.418220882,0.415192837,0.412167992,0.40914646,0.406128355,0.403113792,0.400102883,0.397095742,0.394092484,0.391093221,0.388098066,0.385107132,0.382120532,0.379138379,0.376160786,0.373187864,0.370219726,0.367256483,0.364298248,0.361345132,0.358397247,0.355454703,0.352517612,0.349586084,0.346660231,0.343740162,0.340825987,0.337917817,0.335015762,0.33211993,0.329230431,0.326347374,0.323470867,0.32060102,0.31773794,0.314881736,0.312032515,0.309190385,0.306355453,0.303527825,0.30070761,0.297894912,0.295089838,0.292292493,0.289502985,0.286721417,0.283947894,0.281182522,0.278425404,0.275676644,0.272936347,0.270204615,0.267481552,0.26476726,0.262061841,0.259365399,0.256678034,0.253999848,0.251330942,0.248671416,0.246021371,0.243380907,0.240750124,0.23812912,0.235517995,0.232916847,0.230325774,0.227744874,0.225174244,0.222613982,0.220064183,0.217524945,0.214996362,0.21247853,0.209971545,0.207475501,0.204990491,0.20251661,0.200053951,0.197602607,0.195162671,0.192734233,0.190317387,
			0.187912223,0.185518832,0.183137304,0.180767729,0.178410196,0.176064795,0.173731614,0.17141074,0.169102262,0.166806267,0.16452284,0.162252069,0.159994038,0.157748834,0.15551654,0.153297242,0.151091022,0.148897964,0.146718151,0.144551664,0.142398586,0.140258998,0.138132981,0.136020614,0.133921978,0.131837151,0.129766213,0.127709241,0.125666312,0.123637505,0.121622896,0.11962256,0.117636572,0.115665009,0.113707945,0.111765452,0.109837605,0.107924475,0.106026136,0.104142659,0.102274115,0.100420575,0.098582108,0.096758783,0.09495067,0.093157837,0.091380351,0.089618279,0.087871689,0.086140645,0.084425213,0.082725458,0.081041443,0.079373234,0.077720891,0.076084478,0.074464057,0.072859688,0.071271432,0.069699349,0.068143498,0.066603939,0.065080728,0.063573924,0.062083583,0.060609762,0.059152516,0.0577119,0.056287968,0.054880775,0.053490372,0.052116814,0.050760151,0.049420435,0.048097716,0.046792044,0.045503469,0.044232039,0.042977801,0.041740804,0.040521094,0.039318717,0.038133718,0.036966142,0.035816033,0.034683435,0.03356839,0.03247094,0.031391127,0.030328991,0.029284572,0.028257911,0.027249045,0.026258012,0.025284851,0.024329597,0.023392287,0.022472956,0.021571639,0.020688369,0.019823181,0.018976107,0.018147178,0.017336426,0.016543882,0.015769575,0.015013535,0.01427579,0.013556368,0.012855296,0.012172601,0.011508308,0.010862443,0.010235029,0.009626091,0.009035651,0.008463732,0.007910355,0.007375542,0.006859311,0.006361684,0.005882678,0.005422311,0.004980602,0.004557566,0.00415322,0.003767578,0.003400657,0.003052468,0.002723026,0.002412342,0.002120429,0.001847298,0.001592958,0.00135742,0.001140693,0.000942783,0.0007637,0.00060345,0.000462038,0.00033947,0.000235751,0.000150885,8.48748E-05,3.77227E-05,9.43077E-06,0};
#endif

static void UiSpectrum_FFTWindowFunction(char mode)
{

	const uint16_t  fft_iq_m1_half = (sd.fft_iq_len-1)/2;

    switch(mode)
    {
    case FFT_WINDOW_RECTANGULAR:	// No processing at all
        break;
    case FFT_WINDOW_COSINE:			// Sine window function (a.k.a. "Cosine Window").  Kind of wide...
        for(int i = 0; i < sd.fft_iq_len; i++)
        {
            sd.FFT_Samples[i] = arm_sin_f32((PI * (float32_t)i)/sd.fft_iq_len - 1) * sd.FFT_Samples[i];
        }
        break;
    case FFT_WINDOW_BARTLETT:		// a.k.a. "Triangular" window - Bartlett (or Fej?r) window is special case where demonimator is "N-1". Somewhat better-behaved than Rectangular
        for(int i = 0; i < sd.fft_iq_len; i++)
        {
            sd.FFT_Samples[i] = (1 - fabs(i - ((float32_t)fft_iq_m1_half))/(float32_t)fft_iq_m1_half) * sd.FFT_Samples[i];
        }
        break;
    case FFT_WINDOW_WELCH:			// Parabolic window function, fairly wide, comparable to Bartlett
        for(int i = 0; i < sd.fft_iq_len; i++)
        {
            sd.FFT_Samples[i] = (1 - ((i - ((float32_t)fft_iq_m1_half))/(float32_t)fft_iq_m1_half)*((i - ((float32_t)fft_iq_m1_half))/(float32_t)fft_iq_m1_half)) * sd.FFT_Samples[i];
        }
        break;

    case FFT_WINDOW_HANN:			// Raised Cosine Window (non zero-phase version) - This has the best sidelobe rejection of what is here, but not as narrow as Hamming.
#ifndef USE_PREDEFINED_WINDOW_DATA
    	for(int i = 0; i < sd.fft_iq_len; i++)
    	{
    		sd.FFT_Samples[i] = 0.5 * (float32_t)((1 - (arm_cos_f32(PI*2 * (float32_t)i / (float32_t)(sd.fft_iq_len-1)))) * sd.FFT_Samples[i]);
    	}
#else
    	{
    		const float32_t* WindowCoefficients= sd.fft_iq_len==512?von_Hann_512:von_Hann_1024;
    		for(int i = 0; i < sd.fft_iq_len; i++)
    		{
    			sd.FFT_Samples[i] *= WindowCoefficients[i];
    		}
    	}
#endif
        break;

    case FFT_WINDOW_HAMMING:		// Another Raised Cosine window - This is the narrowest with reasonably good sidelobe rejection.
        for(int i = 0; i < sd.fft_iq_len; i++)
        {
            sd.FFT_Samples[i] = (0.53836 - (0.46164 * arm_cos_f32(PI*2 * (float32_t)i / (float32_t)(sd.fft_iq_len-1)))) * sd.FFT_Samples[i];
        }
        break;
    case FFT_WINDOW_BLACKMAN:		// Approx. same "narrowness" as Hamming but not as good sidelobe rejection - probably best for "default" use.
        for(int i = 0; i < sd.fft_iq_len; i++)
        {
            sd.FFT_Samples[i] = (0.42659 - (0.49656*arm_cos_f32((2*PI*(float32_t)i)/(float32_t)sd.fft_iq_len-1)) + (0.076849*arm_cos_f32((4*PI*(float32_t)i)/(float32_t)sd.fft_iq_len-1))) * sd.FFT_Samples[i];
        }
        break;
    case FFT_WINDOW_NUTTALL:		// Slightly wider than Blackman, comparable sidelobe rejection.
        for(int i = 0; i < sd.fft_iq_len; i++)
        {
            sd.FFT_Samples[i] = (0.355768 - (0.487396*arm_cos_f32((2*PI*(float32_t)i)/(float32_t)sd.fft_iq_len-1)) + (0.144232*arm_cos_f32((4*PI*(float32_t)i)/(float32_t)sd.fft_iq_len-1)) - (0.012604*arm_cos_f32((6*PI*(float32_t)i)/(float32_t)sd.fft_iq_len-1))) * sd.FFT_Samples[i];
        }
        break;
    }

    float32_t gcalc = 1.0/ads.codec_gain_calc;                // Get gain setting of codec and convert to multiplier factor
    arm_scale_f32(sd.FFT_Samples,gcalc, sd.FFT_Samples, sd.fft_iq_len);

}

static void UiSpectrum_SpectrumTopBar_GetText(char* wfbartext)
{

    if(is_waterfallmode() && is_scopemode())           // dual waterfall
    {
        sprintf(wfbartext,"Dual%s < Magnify %2ux >",scope_scaling_factors[ts.spectrum_db_scale].label, (1<<sd.magnify));
    }
    else if(is_waterfallmode())           //waterfall
    {
        sprintf(wfbartext,"  %s   < Magnify %2ux >",scope_scaling_factors[0].label, (1<<sd.magnify));	// a small trick, we use the empty location of index 0 in the table
    }
    else                                                // scope
    {
        sprintf(wfbartext," SC %s < Magnify %2ux >",scope_scaling_factors[ts.spectrum_db_scale].label, (1<<sd.magnify));	// a small trick, we use the empty location of index 0 in the table
    }


}

/**
 * tells us if x is on a vertical grid line. Called for all slayout.scope.w lines
 */
static bool UiSpectrum_Draw_XposIsOnVertGrid(const uint16_t x)
{
    bool repaint_v_grid = false;

#if 0
    int k;
    // Enumerate all saved x positions
    for(k = 0; k < 7; k++)
    {
        // Exit on match
        if(x == sd.vert_grid_id[k])
        {
            repaint_v_grid = true;
            break;
            // leave loop, found match
        }
        else if (sd.vert_grid_id[k]>x)
        {
            // leave loop, no need to look further, we passed the area where x could have hit a vertical grid line.
            // assume low to high order in sd.vert_grid_id
            break;
        }
    }
#else
    // this should be faster in case of power of 2 vert grid distance than the generic lookup code above
    // since the compiler should detect that module power 2 means just masking the higher bits
    // we have special check if we are on the rightmost pixel, this one is not a vertical line.
    return ((x + 1) % sd.vert_grid == 0) && (x != (slayout.scope.w - 1));
#endif
    return repaint_v_grid;
}

static void UiSpectrum_DrawLine(uint16_t x, uint16_t y_pos_prev, uint16_t y_pos, uint16_t clr)
{

    if(y_pos - y_pos_prev > 1) // && x !=(SPECTRUM_START_X + x_offset))
    { // plot line upwards
        UiLcdHy28_DrawStraightLine(x, y_pos_prev + 1, y_pos -  y_pos_prev,LCD_DIR_VERTICAL, clr);
    }
    else if (y_pos - y_pos_prev < -1) // && x !=(SPECTRUM_START_X + x_offset))
    { // plot line downwards
        UiLcdHy28_DrawStraightLine(x, y_pos, y_pos_prev - y_pos,LCD_DIR_VERTICAL, clr);
    }
    else
    {
        UiLcdHy28_DrawStraightLine(x, y_pos,1,LCD_DIR_VERTICAL, clr);
    }

}

static void UiSpectrum_ScopeStandard_UpdateVerticalDataLine(uint16_t x, uint16_t y_old_pos, uint16_t y_new_pos, uint16_t clr_scope, uint16_t clr_bgr, bool is_carrier_line)
{
    // normal scope
    if(y_old_pos > y_new_pos)
    {
        // is old line going to be overwritten by new line, anyway?
        UiLcdHy28_DrawStraightLine(x, y_new_pos,y_old_pos-y_new_pos, LCD_DIR_VERTICAL, clr_scope);
    }
    else if (y_old_pos < y_new_pos )
    {
        // is old line is longer than the new line?

        // repaint the vertical grid
        bool repaint_v_grid = UiSpectrum_Draw_XposIsOnVertGrid(x - slayout.scope.x);

        // we need to delete by overwriting with background color or grid color if we are on a vertical grid line
        uint16_t clr_bg =
                is_carrier_line?
                        sd.scope_centre_grid_colour_active
                        :
                        ( repaint_v_grid ? sd.scope_grid_colour_active : clr_bgr )
                        ;

        UiLcdHy28_DrawStraightLine(x,y_old_pos,y_new_pos-y_old_pos,LCD_DIR_VERTICAL,clr_bg);

        if (!repaint_v_grid && is_carrier_line == false)
        {
            // now we repaint the deleted points of the horizontal grid lines
            // but only if we are not on a vertical grid line, we already painted that in this case
            for(uint16_t k = 0; k < sd.upper_horiz_gridline && y_old_pos <= sd.horz_grid_id[k]; k++)
            { // we run if pixel positions are inside of deleted area (i.e. not lower than y_old_pos) or if there are no more lines

                if(y_old_pos <= sd.horz_grid_id[k] && sd.horz_grid_id[k] < y_new_pos )
                {
                    UiLcdHy28_DrawStraightLine(x,sd.horz_grid_id[k],1,LCD_DIR_HORIZONTAL, sd.scope_grid_colour_active);
                }
            }
        }
    }
}

static void UiSpectrum_CreateDrawArea(void)
{
	//Since we have now highlighted spectrum, the grid is to be drawn in UiSpectrum_DrawScope().
	//Here we only calculate positions of grid and write it to appropriate arrays
	//Also the vertical grid array is used for frequency labels in freq ruler
    UiSpectrum_UpdateSpectrumPixelParameters();
    //const bool is_scope_light = (ts.flags1 & FLAGS1_SCOPE_LIGHT_ENABLE) != 0;
    // get grid colour of all but center line
    UiMenu_MapColors(ts.scope_grid_colour,NULL, &sd.scope_grid_colour_active);
    // Get color of center vertical line of spectrum scope
    UiMenu_MapColors(ts.spectrum_centre_line_colour,NULL, &sd.scope_centre_grid_colour_active);

    // Clear screen where frequency information will be under graticule
    UiLcdHy28_DrawFullRect(slayout.graticule.x, slayout.graticule.y, slayout.graticule.h, slayout.graticule.w, Black);    // Clear screen under spectrum scope by drawing a single, black block (faster with SPI!)

    //sd.wfall_DrawDirection=1;

// was used on 320x240, we may reactivate that at some point for all resolutions
#if 0
    // Frequency bar separator
    UiLcdHy28_DrawHorizLineWithGrad(pos_spectrum->IND_X,(pos_spectrum->IND_Y + pos_spectrum->IND_H - 20),pos_spectrum->IND_W,COL_SPECTRUM_GRAD);

    // Draw control left and right border
    UiLcdHy28_DrawStraightLineDouble((pos_spectrum->DRAW_X_LEFT),
    		(pos_spectrum->IND_Y - 20),
			(pos_spectrum->IND_H + 12),
			LCD_DIR_VERTICAL,
			//									RGB(COL_SPECTRUM_GRAD,COL_SPECTRUM_GRAD,COL_SPECTRUM_GRAD));
			sd.scope_grid_colour_active);

    UiLcdHy28_DrawStraightLineDouble(	(pos_spectrum->IND_X + pos_spectrum->IND_W - 2),
    		(pos_spectrum->IND_Y - 20),
			(pos_spectrum->IND_H + 12),
			LCD_DIR_VERTICAL,
			//									RGB(COL_SPECTRUM_GRAD,COL_SPECTRUM_GRAD,COL_SPECTRUM_GRAD));
			sd.scope_grid_colour_active);
#endif


    if(slayout.title.h != 0)		//don't draw text bar if there is no space allocated for it
    {
//		UiLcdHy28_PrintText(292,118,"   ",Blue,Black,0);
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
        UiLcdHy28_PrintText(292,118 + (!ts.show_wide_spectrum?0:7),"   ",Blue,Black,0);
	#else
        UiLcdHy28_PrintText(452,107 + (!ts.show_wide_spectrum?0:7),"   ",Blue,Black,0);
	#endif
#else
        UiLcdHy28_PrintText(452,107 + (!ts.show_wide_spectrum?0:7),"   ",Blue,Black,0);
#endif

    	// Draw top band = grey box in which text is printed
    	for(int i = 0; i < 16; i++)
    	{
    		UiLcdHy28_DrawHorizLineWithGrad(slayout.title.x,slayout.title.y + i, slayout.title.w, COL_SPECTRUM_GRAD);
    	}

    	char bartext[34];

    	// Top band text - middle caption
    	UiSpectrum_SpectrumTopBar_GetText(bartext);

    	UiLcdHy28_PrintTextCentered(
    			slayout.title.x,
    			slayout.title.y + (slayout.title.h - UiLcdHy28_TextHeight(0))/2,
    			slayout.title.w,
				bartext,
				White,
//				RGB((COL_SPECTRUM_GRAD*2),(COL_SPECTRUM_GRAD*2),(COL_SPECTRUM_GRAD*2)),
				RGB(64,64,64),
				0);
    }
    else
    {
        char fwmltext[3];
        sprintf(fwmltext,"x%u ", (1<<sd.magnify));
//		UiLcdHy28_PrintText(292,118,fwmltext,Blue,Black,0);
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
        UiLcdHy28_PrintText(292,118 + (!ts.show_wide_spectrum?0:6),fwmltext,Blue,Black,0);
	#else
        UiLcdHy28_PrintText(452,107 + (!ts.show_wide_spectrum?0:6),fwmltext,Blue,Black,0);
	#endif
#else
        UiLcdHy28_PrintText(452,107 + (!ts.show_wide_spectrum?0:6),fwmltext,Blue,Black,0);
#endif
    }

    // Horizontal grid lines
    sd.upper_horiz_gridline = slayout.scope.h / pos_spectrum->SCOPE_GRID_HORIZ;

    // array must be filled from higher to the y coordinates
    // the lookup code for a match counts on this.
    for(int i = 0; i < sd.upper_horiz_gridline; i++)
    {
        // Save y position for grid draw and repaint
        sd.horz_grid_id[i] = (slayout.scope.y + slayout.scope.h  - ((i+1) * pos_spectrum->SCOPE_GRID_HORIZ));
    }

    // Vertical grid lines
    // array must be filled from low to higher x coordinates
    // the lookup code for a match counts on this.
    sd.vert_grid = slayout.scope.w / pos_spectrum->SCOPE_GRID_VERT_COUNT;
    for(int i = 1; i < pos_spectrum->SCOPE_GRID_VERT_COUNT; i++)
    {
        // Save x position for grid draw and repaint
        sd.vert_grid_id[i - 1] = (i*sd.vert_grid - 1);
    }


    if (is_waterfallmode() == true && ts.waterfall.speed == 0)
    {
        // print "disabled" in the middle of the screen if the waterfall or scope was disabled
        UiLcdHy28_PrintTextCentered(
                slayout.wfall.x,
                slayout.wfall.y + (slayout.scope.h - UiLcdHy28_TextHeight(0))/2 ,
                slayout.wfall.w,
                "DISABLED",
                Grey, Black,0);
    }

    if (is_scopemode() == true && ts.scope_speed == 0)
    {
        // print "disabled" in the middle of the screen if the waterfall or scope was disabled
        UiLcdHy28_PrintTextCentered(
                slayout.scope.x,
                slayout.scope.y + (slayout.scope.h - UiLcdHy28_TextHeight(0))/2 ,
                slayout.scope.w,
                "DISABLED",
                Grey, Black,0);
    }

    // Draw Frequency bar text after arrays with coordinates are set
    UiSpectrum_DrawFrequencyBar();
    //show highlighted filter bandwidth on the spectrum
    sd.old_left_filter_border_pos=slayout.scope.x;
    sd.old_right_filter_border_pos=slayout.scope.w+slayout.scope.x;
}

void UiSpectrum_Clear()
{
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
    UiLcdHy28_DrawFullRect(slayout.full.x, slayout.full.y + (ts.show_wide_spectrum?1:0), slayout.full.h - (ts.show_wide_spectrum?1:0), slayout.full.w + (ts.show_wide_spectrum?1:0), Black);	// Clear screen under spectrum scope by drawing a single, black block (faster with SPI!)
	#else
    UiLcdHy28_DrawFullRect(slayout.full.x, slayout.full.y + 1, slayout.full.h - 1, slayout.full.w, Black);
	#endif
#else
    UiLcdHy28_DrawFullRect(slayout.full.x, slayout.full.y + 1, slayout.full.h - 1, slayout.full.w, Black);
#endif
    ts.VirtualKeysShown_flag=false;	//if virtual keypad was shown, switch it off
}

// This version of "Draw Scope" is revised from the original in that it interleaves the erasure with the drawing
// of the spectrum to minimize visible flickering  (KA7OEI, 20140916, adapted from original)
//
// 20141004 NOTE:  This has been somewhat optimized to prevent drawing vertical line segments that would need to be re-drawn:
//  - New lines that were shorter than old ones are NOT erased
//  - Line segments that are to be erased are only erased starting at the position of the new line segment.
//
//  This should reduce the amount of CGRAM access - especially via SPI mode - to a minimum.

static void    UiSpectrum_DrawScope(uint16_t *old_pos, float32_t *fft_new)
{

    // before accessing pixel parameters, request update according to configuration
    UiSpectrum_UpdateSpectrumPixelParameters();

    const bool is_scope_light = (ts.flags1 & FLAGS1_SCOPE_LIGHT_ENABLE) != 0;
    const uint16_t spec_height_limit = sd.scope_size - 1;
    const uint16_t spec_top_y = sd.scope_ystart + sd.scope_size;

    uint32_t clr_scope_normal, clr_scope_fltr, clr_scope_fltrbg;
    uint16_t clr_bg;

    //calculations of bandwidth highlight parameters and colours
    float32_t filter_width_;                          // calculate width of BW highlight in pixels
    float32_t left_filter_border_pos_;				// first pixel of filter

    UiSpectrum_CalculateDisplayFilterBW(&filter_width_,&left_filter_border_pos_);
    uint16_t left_filter_border_pos=left_filter_border_pos_;
    uint16_t right_filter_border_pos=left_filter_border_pos_+filter_width_;

    if (right_filter_border_pos >= slayout.scope.w)
    {
        right_filter_border_pos = slayout.scope.w - 1;
    }

    //mapping the colours of highlighted bandwidth
    //foreground of highlighted bandwidth is one of predefined colours selected fromm array, so simply map it
    //background is the percentage of foreground, so we must disassemly the rgb data(16 bit) into seperate RGB channels,
    //then scale it and assembly to 16 bit
    UiMenu_MapColors(ts.scope_trace_colour, NULL, &clr_scope_normal); //foreground colour
    UiMenu_MapColors(ts.scope_trace_BW_colour, NULL, &clr_scope_fltr);//background colour of highlight
    uint16_t BWHbgr=ts.scope_backgr_BW_colour;
    BWHbgr<<=8;
    BWHbgr/=100;
    uint16_t colR=(clr_scope_fltr>>8)&0xf8;
    uint16_t colG=(clr_scope_fltr>>3)&0xfc;
    uint16_t colB=(clr_scope_fltr<<3)&0xf8;
    colR=(colR*BWHbgr)>>8;
    colG=(colG*BWHbgr)>>8;
    colB=(colB*BWHbgr)>>8;
    clr_scope_fltrbg=RGB(colR,colG,colB);	//background color of the active demodulation filter highlight

    left_filter_border_pos+=slayout.scope.x;		//left boundary of highlighted spectrum in absolute pixels
    right_filter_border_pos+=slayout.scope.x;		//right boundary of highlighted spectrum in absolute pixels

    if((sd.old_left_filter_border_pos!=left_filter_border_pos ) || (sd.old_right_filter_border_pos!=right_filter_border_pos ))
    {
    	//BW changed so we must refresh the highlighted spectrum
    	uint16_t x_start=sd.old_left_filter_border_pos;
    	uint16_t x_end=sd.old_right_filter_border_pos;
    	if(sd.old_left_filter_border_pos>left_filter_border_pos)
    	{
    		x_start=left_filter_border_pos;
    	}
    	if(sd.old_right_filter_border_pos<right_filter_border_pos)
    	{
    		x_end=right_filter_border_pos;
    	}

    	for(uint16_t xh=x_start;xh<=x_end;xh++)
    	{
    	    uint16_t clr_scope;
            if((xh>=left_filter_border_pos)&&(xh<=right_filter_border_pos)) //BW highlight control
            {
            	clr_scope=clr_scope_fltr;
            	clr_bg=clr_scope_fltrbg;
            }
            else
            {
                clr_scope=clr_scope_normal;
                clr_bg=Black;
            }

        	if (is_scope_light)
        	{
        		UiSpectrum_DrawLine(xh, spec_top_y, spec_top_y-spec_height_limit, clr_bg);
        	}
        	else
        	{
        		UiSpectrum_ScopeStandard_UpdateVerticalDataLine(xh, spec_top_y-spec_height_limit, spec_top_y, clr_scope, clr_bg, false);
        	}
        	old_pos[xh-slayout.scope.x]=spec_top_y;
    	}

    	//causing the redraw of all marker lines within the bandwidth area
    	for (uint16_t idx = 0; idx < SPECTRUM_MAX_MARKER; idx++)
    	{
    		if((sd.marker_line_pos_prev[idx]>=x_start) && (sd.marker_line_pos_prev[idx]<=x_end))
    		{
    			sd.marker_line_pos_prev[idx]=65535;
    		}
    	}
    }

    //Correction of the SSB carrier upper marker disappearing bug when changing modulation
    sd.marker_line_pos_prev[2]=65535;

    sd.old_left_filter_border_pos=left_filter_border_pos;
    sd.old_right_filter_border_pos=right_filter_border_pos;

    uint16_t marker_line_pos[SPECTRUM_MAX_MARKER];


    for (uint16_t idx = 0; idx < SPECTRUM_MAX_MARKER; idx++)
    {
        marker_line_pos[idx] = slayout.scope.x + sd.marker_pos[idx];

        // this is the tx carrier line, we redraw only if line changes place around,
        // init code must take care to reset prev position to 0xffff in order to get initialization done after clean start

        if (marker_line_pos[idx] != sd.marker_line_pos_prev[idx])
        {
            if ((sd.marker_line_pos_prev[idx] < slayout.scope.x + slayout.scope.w)&&
            		(sd.marker_line_pos_prev[idx] > slayout.scope.x))
            {
            	if((sd.marker_line_pos_prev[idx]>=left_filter_border_pos)&&(sd.marker_line_pos_prev[idx]<=right_filter_border_pos)) //BW highlight control
            	{
            		clr_bg=clr_scope_fltrbg;
            	}
            	else
            	{
            		clr_bg=Black;
            	}


                // delete old line if previously inside screen limits

                if(is_scope_light)
                {
                    UiLcdHy28_DrawStraightLine( sd.marker_line_pos_prev[idx],
                            spec_top_y - spec_height_limit,
                            spec_height_limit,
                            LCD_DIR_VERTICAL,
							clr_bg);
                }
                else
                {
                    UiSpectrum_ScopeStandard_UpdateVerticalDataLine(
                            sd.marker_line_pos_prev[idx],
                            spec_top_y - spec_height_limit /* old = max pos */ ,
                            spec_top_y /* new = min pos */,
                            clr_scope_normal, clr_bg,	//TODO: add highlight color here
                            false);

                    // we erase the memory for this location, so that it is fully redrawn
                    if (sd.marker_line_pos_prev[idx] < slayout.scope.x + slayout.scope.w)
                    {
                        old_pos[sd.marker_line_pos_prev[idx] - slayout.scope.x] = spec_top_y;
                    }
                }
            }

            if ((marker_line_pos[idx] < slayout.scope.x + slayout.scope.w)&&
            		(marker_line_pos[idx] > slayout.scope.x))
            {

                // draw new line if inside screen limits

                UiLcdHy28_DrawStraightLine( marker_line_pos[idx],
                        spec_top_y - spec_height_limit,
                        spec_height_limit,
                        LCD_DIR_VERTICAL,
                        sd.scope_centre_grid_colour_active);

                if (is_scope_light == false)
                {
                    // we erase the memory for this location, so that it is fully redrawn
                    if (marker_line_pos[idx] < slayout.scope.x + slayout.scope.w)
                    {
                        old_pos[marker_line_pos[idx] - slayout.scope.x] = spec_top_y;
                    }
                }
            }

            // done, remember where line has been drawn
            sd.marker_line_pos_prev[idx] = marker_line_pos[idx];
        }
    }

    uint16_t marker_lines_togo = sd.marker_num;

    // we stop if there is a ptt_request and go straight out of the display update
    for(uint16_t x = slayout.scope.x, idx = 0; ts.ptt_req == false && idx < slayout.scope.w; x++, idx++)
    {
        uint16_t clr_scope;

        if((x>=left_filter_border_pos)&&(x<=right_filter_border_pos)) //BW highlight control
        {
        	clr_scope=clr_scope_fltr;
        	clr_bg=clr_scope_fltrbg;
        }
        else
        {
            clr_scope=clr_scope_normal;
            clr_bg=Black;
        }

        // averaged FFT data scaled to height and (if necessary) limited here to current max height
        uint16_t y_height = (fft_new[idx] < spec_height_limit ? fft_new[idx] : spec_height_limit);

        // Data to y position by subtraction from the lowest spectrum y,
        // since scope y goes from high to low coordinates if y goes from low to high
        // due to screen 0,0 reference being the top left corner.
        uint16_t y_new_pos  = spec_top_y - y_height;

        // we get the old y position value of last scope draw run
        // and remember the new position value for next round in same place
        uint16_t y_old_pos  = old_pos[idx];
        old_pos[idx] = y_new_pos;


        if (is_scope_light)
        {
            static uint16_t      y_new_pos_prev = 0, y_old_pos_prev = 0;
            // ATTENTION: CODE ONLY UPDATES THESE IF IN LIGHT SCOPE MODE !!!
            // pixel values from the previous column left to the current x position
            // these are static so that when we do the right half of the spectrum, we get the previous value from the left side of the screen

            // special case of first x position of spectrum, we don't have a left side neighbor
            if (idx == 0)
            {
                y_old_pos_prev = y_old_pos;
                y_new_pos_prev = y_new_pos;
            }

            // x position is not on vertical center line (the one that indicates the tx carrier frequency)
            // we draw a line if y_new_pos and the last drawn pixel (y_old_pos) are more than 1 pixel apart in the vertical axis
            // makes the spectrum display look more complete . . .
           // uint16_t clr_bg = Black;

            // TODO: we  could find out the lowest marker_line and do not process search before that line
            for (uint16_t marker_idx = 0; marker_lines_togo > 0 && marker_idx < sd.marker_num; marker_idx++)
            {
                if (x == marker_line_pos[marker_idx])
                {
                    clr_bg = sd.scope_centre_grid_colour_active;
                    marker_lines_togo--; // once we marked all, skip further tests;
                    break;
                }
            }
            UiSpectrum_DrawLine(x, y_old_pos_prev, y_old_pos, clr_bg);
            UiSpectrum_DrawLine(x, y_new_pos_prev, y_new_pos, clr_scope);

            // we are done, lets remember this information for next round
            y_new_pos_prev = y_new_pos;
            y_old_pos_prev = y_old_pos;

        }
        else
        {

            bool is_marker_line = false;
            for (uint16_t idx = 0; !(is_marker_line) && idx < sd.marker_num; idx++)
            {
                is_marker_line =  x == marker_line_pos[idx];
            }

            // we just draw our vertical line in a optimized fashion here
            // handles also the grid (re)drawing if necessary
            UiSpectrum_ScopeStandard_UpdateVerticalDataLine(x, y_old_pos, y_new_pos, clr_scope, clr_bg, is_marker_line);
        }
    }
}






/**
 * @brief init data strctures for both "Scope Display" and "Waterfall" Display
 */
static void UiSpectrum_InitSpectrumDisplayData(void)
{
	//init colour of markers
	UiMenu_MapColors(ts.spectrum_centre_line_colour,NULL, &sd.scope_centre_grid_colour_active);

    // Init publics
    sd.state 		= 0;
    sd.samp_ptr 	= 0;
    sd.enabled		= 0;
    ts.dial_moved	= 0;
    sd.RedrawType   = 0;

    switch(disp_resolution)
    {

    case RESOLUTION_320_240:
//    	sd.spec_len = 256;
//    	sd.fft_iq_len = 512;
//    	sd.cfft_instance = &arm_cfft_sR_f32_len256;
    	sd.spec_len = 512;
    	sd.fft_iq_len = 1024;
    	sd.cfft_instance = &arm_cfft_sR_f32_len512;
    	break;
    case RESOLUTION_480_320:
    	sd.spec_len = 512;
    	sd.fft_iq_len = 1024;
    	sd.cfft_instance = &arm_cfft_sR_f32_len512;
    	break;
    case RESOLUTION_800_480:	//FIXME: fill with correct values (move to layouts.c ??)
    	sd.spec_len = 512;
    	sd.fft_iq_len = 1024;
    	sd.cfft_instance = &arm_cfft_sR_f32_len512;
    	break;
    }


    sd.agc_rate = ((float32_t)ts.spectrum_agc_rate) / SPECTRUM_AGC_SCALING;	// calculate agc rate
    //
    sd.wfall_line_update = 0;		// init count used for incrementing number of lines for each vertical waterfall screen update
    //
    // load buffer containing waterfall colours
    //

    const uint16_t* wfall_scheme = NULL;

    switch(ts.waterfall.color_scheme)
    {
    case WFALL_HOT_COLD:
        wfall_scheme = &waterfall_cold_hot[0];
        break;
    case WFALL_FLAME:
        wfall_scheme = &waterfall_flame[0];
        break;
    case WFALL_SUNSET:
        wfall_scheme = &waterfall_sunset[0];
        break;
    case WFALL_MATRIX:
        wfall_scheme = &waterfall_matrix[0];
        break;
    case WFALL_RAINBOW:
        wfall_scheme = &waterfall_rainbow[0];
        break;
    case WFALL_BLUE:
        wfall_scheme = &waterfall_blue[0];
        break;
    case WFALL_GRAY_INVERSE:
        wfall_scheme = &waterfall_grey_inverse[0];
        break;
    case WFALL_GRAY:
    default:
        wfall_scheme =  &waterfall_grey[0];
        break;
    }

    memcpy(sd.waterfall_colours,wfall_scheme,sizeof(*wfall_scheme)* NUMBER_WATERFALL_COLOURS);

    // erase previous position memory
    // Load "top" color of palette (the 65th) with that to be used for the center grid color
    sd.waterfall_colours[NUMBER_WATERFALL_COLOURS] = sd.scope_centre_grid_colour_active;

    if (ts.spectrum_db_scale >= SCOPE_SCALE_NUM)
    {
    	ts.spectrum_db_scale = DB_DIV_ADJUST_DEFAULT;
    }
    sd.db_scale = scope_scaling_factors[ts.spectrum_db_scale].value;

    // if we later scale the spectrum width from the fft width, we incorporate
    // the required negative gain for the downsampling here.
    // makes spectrum_width float32_t multiplications less per waterfall update

    if (sd.spec_len != slayout.scope.w)
    {
        sd.db_scale *= (float32_t)slayout.scope.w/(float32_t)sd.spec_len;
    }

    sd.scope_ystart = slayout.scope.y;
    sd.scope_size = slayout.scope.h;
    sd.wfall_ystart = slayout.wfall.y;
    sd.wfall_size = slayout.wfall.h;

    // now make sure we fit in
    // please note, this works only if we have enough memory for have the lines
    // otherwise we will reduce size of displayed waterfall
    if((sd.wfall_size * slayout.scope.w) > sizeof(sd.waterfall))
    {
        //sd.doubleWaterfallLine = 1;


        if (sd.wfall_size/2 * slayout.scope.w > sizeof(sd.waterfall))
        {
            // we caculate how many lines we can do with the amount of memory
            // and adjust displayed line count accordingly.
            sd.wfall_size = sizeof(sd.waterfall)/(slayout.scope.w);
            // FIXME: Notify user of issue
            // if memory is too small even with doubled
            // lines
        }
        else
        {
            sd.wfall_size /= 2;
        }
    }
 /*   else
    {
        sd.repeatWaterfallLine = 0;
    }*/

    sd.repeatWaterfallLine = slayout.wfall.h/(sd.wfall_size);

    //no need for this variable because we have slayout.wfall.h
    //sd.wfall_disp_lines = sd.wfall_size * (sd.doubleWaterfallLine==true? 2:1);


    for (uint16_t idx = 0; idx < slayout.scope.w; idx++)
    {
        sd.Old_PosData[idx] = sd.scope_ystart + sd.scope_size;
    }

    sd.wfall_contrast = (float)ts.waterfall.contrast / 100.0;		// calculate scaling for contrast

    for (uint16_t marker_idx = 0; marker_idx < SPECTRUM_MAX_MARKER; marker_idx++)
    {
        sd.marker_line_pos_prev[marker_idx] = 0xffff; // off screen
    }

    sd.enabled		= 1;
}

void UiSpectrum_WaterfallClearData()
{
    // this assume sd.watefall being an array, not a pointer to one!
    memset(sd.waterfall,0, sizeof(sd.waterfall));
    memset(sd.waterfall_frequencies,0,sizeof(sd.waterfall_frequencies));
}


static void UiSpectrum_DrawWaterfall(void)
{
    sd.wfall_line %= sd.wfall_size; // make sure that the circular buffer is clipped to the size of the display area

    // Contrast:  100 = 1.00 multiply factor:  125 = multiply by 1.25 - "sd.wfall_contrast" already converted to 100=1.00
    arm_scale_f32(sd.FFT_Samples, sd.wfall_contrast, sd.FFT_Samples, sd.spec_len);


    UiSpectrum_UpdateSpectrumPixelParameters(); // before accessing pixel parameters, request update according to configuration


    uint16_t marker_line_pixel_pos[SPECTRUM_MAX_MARKER];
    for (uint16_t idx = 0; idx < sd.marker_num; idx++)
    {
        marker_line_pixel_pos[idx] = sd.marker_pos[idx];
    }

    // After the above manipulation, clip the result to make sure that it is within the range of the palette table
    //for(uint16_t i = 0; i < sd.spec_len; i++)
    uint8_t  * const waterfallline_ptr = &sd.waterfall[sd.wfall_line*slayout.wfall.w];

    for(uint16_t i = 0; i < slayout.wfall.w; i++)
    {
        if(sd.FFT_Samples[i] >= NUMBER_WATERFALL_COLOURS)   // is there an illegal color value?
        {
            sd.FFT_Samples[i] = NUMBER_WATERFALL_COLOURS - 1;   // yes - clip it
        }

        waterfallline_ptr[i] = sd.FFT_Samples[i]; // save the manipulated value in the circular waterfall buffer
    }

    sd.waterfall_frequencies[sd.wfall_line] = sd.FFT_frequency;
    static uint8_t doubleLineStart=0;

	// Draw lines from buffer
    doubleLineStart--;			//changing the size of repeat of first line (for low RAM devices) to make it look more smooth
    if(doubleLineStart==0xff)
    {
    	doubleLineStart=sd.repeatWaterfallLine;
    	sd.wfall_line++;        // bump to the next line in the circular buffer for next go-around
    }

    uint32_t lptr = sd.wfall_line;      // get current line of "bottom" of waterfall in circular buffer

    sd.wfall_line_update++;                                 // update waterfall line count
    sd.wfall_line_update %= ts.waterfall.vert_step_size;    // clip it to number of lines per iteration

    if(!sd.wfall_line_update)                               // if it's count is zero, it's time to move the waterfall up
    {
    	    // can't use modulo here, doesn't work if we use uint16_t,
    	    // since it 0-1 == 65536 and not -1 (it is an unsigned integer after all)
    	    lptr = lptr?lptr-1 : sd.wfall_size-1;

        lptr %= sd.wfall_size;      // do modulus limit of spectrum high

        // set up LCD for bulk write, limited only to area of screen with waterfall display.  This allow data to start from the
        // bottom-left corner and advance to the right and up to the next line automatically without ever needing to address
        // the location of any of the display data - as long as we "blindly" write precisely the correct number of pixels per
        // line and the number of lines.

        UiLcdHy28_BulkPixel_OpenWrite(slayout.wfall.x, slayout.wfall.w, slayout.wfall.y, slayout.wfall.h);

        uint16_t spectrum_pixel_buf[slayout.wfall.w];

        const int32_t cur_center_hz = sd.FFT_frequency;

        uint8_t doubleLine=doubleLineStart;

        // we update the display unless there is a ptt request, in this case we skip to the end.
        for(uint16_t lcnt = 0; ts.ptt_req == false && lcnt < slayout.wfall.h;)                 // set up counter for number of lines defining height of waterfall
        {
            uint8_t  * const waterfallline_ptr = &sd.waterfall[lptr*slayout.wfall.w];


            const int32_t line_center_hz = sd.waterfall_frequencies[lptr];

            // if our old_center is lower than cur_center_hz -> find start idx in waterfall_line, end_idx is line end, and pad with black pixels;
            // if our old_center is higher than cur_center_hz -> find start pixel x in end_idx is line end, first pad with black pixels until this point and then use pixel buffer;
            // if identical -> well, no padding.
            const int32_t diff_centers = (line_center_hz - cur_center_hz);
            int32_t offset_pixel = diff_centers/sd.hz_per_pixel;
            uint16_t pixel_start, pixel_count, left_padding_count, right_padding_count;


            // here we actually create a single line pixel by pixel.

            if (offset_pixel >= slayout.wfall.w || offset_pixel <= -slayout.wfall.w)
            {
                offset_pixel = slayout.wfall.w-1;
            }
            if (offset_pixel <= 0)
            {
                                // we have to start -offset_pixel later and then pad with black
                left_padding_count = 0;
                pixel_start = -offset_pixel;
                right_padding_count = -offset_pixel;
                pixel_count = slayout.wfall.w + offset_pixel;
            }
            else
            {
                // we to start with offset_pixel black padding  and then draw the pixels until we reach spectrum width
                left_padding_count = offset_pixel;
                pixel_start = 0;
                right_padding_count = 0;
                pixel_count = slayout.wfall.w - offset_pixel;
            }


            uint16_t* pixel_buf_ptr = &spectrum_pixel_buf[0];

            // fill from the left border with black pixels
            for(uint16_t i = 0; i < left_padding_count; i++)
            {
                *pixel_buf_ptr++ = Black;
            }

            for(uint16_t idx = pixel_start, i = 0; i < pixel_count; i++,idx++)
            {
                *pixel_buf_ptr++ = sd.waterfall_colours[waterfallline_ptr[idx]];    // write to memory using waterfall color from palette
            }

            // fill to the right border with black pixels
            for(uint16_t i = 0; i < right_padding_count; i++)
            {
                *pixel_buf_ptr++ = Black;
            }

            for (uint16_t idx = 0; idx < sd.marker_num; idx ++)
            {
                // Place center line marker on screen:  Location [64] (the 65th) of the palette is reserved is a special color reserved for this
                if (marker_line_pixel_pos[idx] < slayout.wfall.w)
                {
                    spectrum_pixel_buf[marker_line_pixel_pos[idx]] = sd.waterfall_colours[NUMBER_WATERFALL_COLOURS];
                }
            }



            for(;doubleLine<sd.repeatWaterfallLine+1;doubleLine++)
            {
                UiLcdHy28_BulkPixel_PutBuffer(spectrum_pixel_buf, slayout.wfall.w);
                lcnt++;
                if(lcnt==slayout.wfall.h)	//preventing the window overlap if doubling oversize the display window
                {
                	break;
                }
            }
            doubleLine=0;
            lptr = lptr?lptr-1 : sd.wfall_size-1;
            lptr %= sd.wfall_size;              // clip to display height

        }


        UiLcdHy28_BulkPixel_CloseWrite();                   // we are done updating the display - return to normal full-screen mode
    }

}

static float32_t  UiSpectrum_ScaleFFTValue(const float32_t value, float32_t* min_p)
{
    float32_t sig = sd.display_offset + Math_log10f_fast(value) * sd.db_scale;     // take FFT data, do a log10 and multiply it to scale 10dB (fixed)
    // apply "AGC", vertical "sliding" offset (or brightness for waterfall)

    if (sig < *min_p)
    {
        *min_p = sig;
    }

    return  (sig < 1)? 1 : sig;
}

static void UiSpectrum_ScaleFFT(float32_t dest[], float32_t source[], float32_t* min_p )
{
    // not an in-place algorithm
    assert(dest != source);

    for(uint16_t i = 0; i < (sd.spec_len/2); i++)
    {
        dest[sd.spec_len - i - 1] = UiSpectrum_ScaleFFTValue(source[i + sd.spec_len/2], min_p);
        // take FFT data, do a log10 and multiply it to scale 10dB (fixed)
        // apply "AGC", vertical "sliding" offset (or brightness for waterfall)
    }


    for(uint16_t i = (sd.spec_len/2); i < (sd.spec_len); i++)
    {
        // build right half of spectrum data
        dest[sd.spec_len - i - 1] = UiSpectrum_ScaleFFTValue(source[i - sd.spec_len/2], min_p);
        // take FFT data, do a log10 and multiply it to scale 10dB (fixed)
        // apply "AGC", vertical "sliding" offset (or brightness for waterfall)
    }

}

/**
 * @brief simple algorithm to scale down in place to a fractional scale
 * Please note, that there is no gain correction in this algorithm,
 * We rely on the gain correction taking place elsewhere!
 * required gain correction is (float32_t)to_len/(float32_t)from_len
 */
static void UiSpectrum_ScaleFFT2SpectrumWidth(float32_t samples[], uint16_t from_len, uint16_t to_len)
{

    assert(from_len >= to_len);

    const float32_t full_amount = (float32_t)from_len/(float32_t)to_len;


    // init loop values
    float32_t amount = full_amount;
    uint16_t idx_new = 0;
    uint16_t idx_old = 0;
    float32_t value = 0;


    // iterate over both arrays from index 0 towards (to_len-1) or (from_len-1)
    do
    {
        while (amount >=1)
        {
            value += samples[idx_old];
            idx_old++;
            amount-= 1.0;
        }

        float32_t for_next = (1-amount) * samples[idx_old];

        value += samples[idx_old] - for_next;
        samples[idx_new] = value;
        idx_new++;

        if (idx_new < to_len)
        {
            value  = for_next;
            idx_old++;
            amount = full_amount - (1-amount);
        }
    }
    while(idx_new < to_len);
}

// Spectrum Display code rewritten by C. Turner, KA7OEI, September 2014, May 2015
// Waterfall Display code written by C. Turner, KA7OEI, May 2015 entirely from "scratch"
// - which is to say that I did not borrow any of it
// from anywhere else, aside from keeping some of the general functions found in "Case 1".
/**
 * @briefs implement a staged calculation and drawing of the spectrum scope / waterfall
 * should not be called directly, go through UiSpectrum_Redraw which implements a rate limiter
 * it relies on the audio driver implementing the first stage of data collection.
 */
static void UiSpectrum_RedrawSpectrum(void)
{
	bool is_RedrawActive=(ts.menu_mode == false)					//if this flag is false we do only dBm calculation (for S-meter and tune helper)
						&& (sd.enabled == true)
						&& (ts.mem_disp == false)
						&& (ts.xvtr_disp == false)
						&& (ts.SpectrumResize_flag == false)
						&& (ts.VirtualKeysShown_flag ==false);


    // Process implemented as state machine
    switch(sd.state)
    {
    case 0:
        sd.reading_ringbuffer = true;
        __DSB();
        // we make sure the interrupt sees this variable value by ensure all memory operations have been done
        // after this point
        arm_copy_f32(&sd.FFT_RingBuffer[sd.samp_ptr],&sd.FFT_Samples[0],sd.fft_iq_len-sd.samp_ptr);
        arm_copy_f32(&sd.FFT_RingBuffer[0],&sd.FFT_Samples[sd.fft_iq_len-sd.samp_ptr],sd.samp_ptr);
        sd.reading_ringbuffer = false;
        // Apply gain to collected IQ samples and then do FFT
        // Scale input according to A/D gain and apply Window function

    	//        UiSpectrum_FFTWindowFunction(ts.fft_window_type);		// do windowing function on input data to get less "Bin Leakage" on FFT data
        // fixed window type to Hann Window, because it provides excellent bin leakage behaviour AND
    	// it corresponds very well with the coefficients in the quadratic interpolation algorithm that is used
    	// for the SNAP carrier function
    	UiSpectrum_FFTWindowFunction(FFT_WINDOW_HANN);

        sd.state++;
        break;
    case 1:		// Do FFT and calculate complex magnitude
    {
        arm_cfft_f32(sd.cfft_instance, sd.FFT_Samples,0,1);	// Do FFT
        sd.state++;
        break;
    }
    case 2:
    {
        // Calculate magnitude
        arm_cmplx_mag_f32( sd.FFT_Samples, sd.FFT_MagData ,sd.spec_len);
        // FIXME:

        // just for debugging purposes
        // display the spectral noise reduction bin gain values in the second 64 pixels of the spectrum display
        if((is_dsp_nr()) && ts.nr_gain_display != 0)
        {
        	if(ts.nr_gain_display == 1)
        	{
        	for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
        	{
        		sd.FFT_MagData[(nr_params.NR_FFT_L / 2 - 1) - bindx] = NR2.Hk[bindx] * 150.0;
        	}
        	}
        	/*        	else
        	if(ts.nr_gain_display == 2)
        	{
            	for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
            	{
            		sd.FFT_MagData[(nr_params.NR_FFT_L / 2 - 1) - bindx] = NR2.long_tone_gain[bindx] * 150.0;
            	}
        	}
        	else
        	if(ts.nr_gain_display == 3)
        	{
            	for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
            	{
            		sd.FFT_MagData[(nr_params.NR_FFT_L / 2 - 1) - bindx] = NR.Hk[bindx] * NR2.long_tone_gain[bindx] * 150.0;
            	}
        	} */
        	// set all other pixels to a low value
        	for(int bindx = nr_params.NR_FFT_L / 2; bindx < sd.spec_len; bindx++)
        	{
        		sd.FFT_MagData[bindx] = 10.0;
        	}
        }

        sd.state++;
        break;
    }

    //  Low-pass filter amplitude magnitude data
    case 3:
    {
    	float32_t filt_factor = 1/(float)ts.spectrum_filter;		// use stored filter setting inverted to allow multiplication
        arm_scale_f32(sd.FFT_AVGData, filt_factor, sd.FFT_Samples, sd.spec_len);	// get scaled version of previous data
        arm_sub_f32(sd.FFT_AVGData, sd.FFT_Samples, sd.FFT_AVGData, sd.spec_len);	// subtract scaled information from old, average data
        arm_scale_f32(sd.FFT_MagData, filt_factor, sd.FFT_Samples, sd.spec_len);	// get scaled version of new, input data
        arm_add_f32(sd.FFT_Samples, sd.FFT_AVGData, sd.FFT_AVGData, sd.spec_len);	// add portion new, input data into average

        for(uint32_t i = 0; i < sd.spec_len; i++)	 		// guarantee that the result will always be >= 0
        {
            if(sd.FFT_AVGData[i] < 1)
            {
                sd.FFT_AVGData[i] = 1;
            }
        }

        UiSpectrum_CalculateDBm();

        if (is_RedrawActive)
        {   //continue if there is no objection to display spectrum or waterfall
            if(ts.dial_moved)
            {
                ts.dial_moved = 0;	// Dial moved - reset indicator
                UiSpectrum_DrawFrequencyBar();	// redraw frequency bar on the bottom of the display
            }
        	sd.state++;
        }
        else
        {
        	sd.RedrawType=0;
        	sd.state=0;
        }
        break;
    }

    // De-linearize and normalize display data and do AGC processing
    case 4:
    	if (is_RedrawActive)		//this is needed for overwrite prevention if menu was drawn when sd.state>4
    	{
    		float32_t	min1=100000;
    		// De-linearize data with dB/division
    		// Transfer data to the waterfall display circular buffer, putting the bins in frequency-sequential order!
    		// TODO: if we would use a different data structure here (e.g. q15), we could speed up collection of enough samples in driver
    		// we could let it run as soon as last FFT_Samples read has been done here
    		UiSpectrum_ScaleFFT(sd.FFT_Samples,sd.FFT_AVGData,&min1);

    		if (sd.spec_len != slayout.scope.w)
    		{
    			// in place downscaling (!)
    			UiSpectrum_ScaleFFT2SpectrumWidth(sd.FFT_Samples,sd.spec_len, slayout.scope.w);
    		}

    		// Adjust the sliding window so that the lowest signal is always black
    		sd.display_offset -= sd.agc_rate*min1/5;
    	}
    	sd.state++;
    	break;

    case 5:	// rescale waterfall horizontally, apply brightness/contrast, process pallate and put vertical line on screen, if enabled.
    	if (is_RedrawActive)		//this is needed for overwrite prevention if menu was drawn when sd.state>4
    	{
    		if(sd.RedrawType&Redraw_SCOPE)
    		{
    			UiSpectrum_DrawScope(sd.Old_PosData, sd.FFT_Samples);
    		}

    		if(sd.RedrawType&Redraw_WATERFALL)
    		{
    			UiSpectrum_DrawWaterfall();
    		}


    		if(((ts.waterfall.speed == 0)
    				&&(ts.scope_speed == 0))
					||(sd.RedrawType != 0)
    		)
    		{
    			sd.state = 0;
    		}
    	}
    	else
    	{
    		sd.state = 0;
    	}
		sd.RedrawType=0;
    	break;
    default:
    	sd.state = 0;
    	break;
    }
}

/**
 * @brief Initialize data and display for spectrum display
 */
void UiSpectrum_Init()
{

#ifdef USE_EXPERIMENTAL_MULTIRES
    if (disp_resolution == RESOLUTION_480_320)
    {
        disp_resolution = RESOLUTION_320_240;
        pos_spectrum = &pos_spectrum_set[0];
    }
    else
    {
        disp_resolution = RESOLUTION_480_320;
        pos_spectrum = &pos_spectrum_set[1];
    }
    UiSpectrum_WaterfallClearData();
#endif

/*
    switch(disp_resolution)
    {
#ifdef USE_DISP_480_320
    case RESOLUTION_480_320:
    {
        UiSpectrum_CalculateLayout(ts.spectrum_size == SPECTRUM_BIG, is_scopemode(), is_waterfallmode(), &ts.Layout->SpectrumWindow, 0);
        break;
    }
#endif
#ifdef USE_DISP_320_240
    case RESOLUTION_320_240:
        {
            const UiArea_t area_320_240 = { .x = 58, .y = 128, .w = 260, .h = 94 };
            UiSpectrum_CalculateLayout(ts.spectrum_size == SPECTRUM_BIG, is_scopemode(), is_waterfallmode(), &area_320_240, 2);
            break;
        }
#endif
    }
  */
//    UiSpectrum_CalculateLayout(ts.spectrum_size == SPECTRUM_BIG, &ts.Layout->SpectrumWindow, ts.Layout->SpectrumWindowPadding);
//    if(!ts.show_wide_spectrum)
//    {
        UiSpectrum_CalculateLayout(ts.spectrum_size == SPECTRUM_BIG, &ts.Layout->SpectrumWindow, ts.Layout->SpectrumWindowPadding);
//    }
//    else
//    {
//        const UiArea_t widearea_320_240 = { .x = 2, .y = 128, .w = 316, .h = 94 };
//        UiSpectrum_CalculateLayout(ts.spectrum_size == SPECTRUM_BIG, &widearea_320_240, ts.Layout->SpectrumWindowPadding);
//    }
    UiSpectrum_InitSpectrumDisplayData();
    UiSpectrum_Clear();         // clear display under spectrum scope
    UiSpectrum_CreateDrawArea();
    UiSpectrum_DisplayFilterBW();	// Update on-screen indicator of filter bandwidth
    if(ts.show_wide_spectrum && ts.spectrum_size == SPECTRUM_BIG)
    {
        UiLcdHy28_DrawStraightLine(0, 136, 320, LCD_DIR_HORIZONTAL, sd.scope_grid_colour_active);
    }
    //Test
#ifdef SDR_AMBER_480_320
    if(ts.spectrum_size == SPECTRUM_BIG)
    {
        UiLcdHy28_DrawStraightLine(0, 123, 480, LCD_DIR_HORIZONTAL, sd.scope_grid_colour_active);
    }
#endif
}
/**
 * @brief Calculate parameters for display filter bar. This function is used also for spectrum BW highlight.
 */
void UiSpectrum_CalculateDisplayFilterBW(float32_t* width_pixel_, float32_t* left_filter_border_pos_)
{

    const FilterPathDescriptor* path_p = &FilterPathInfo[ts.filter_path];
    const FilterDescriptor* filter_p = &FilterInfo[path_p->id];
    const float32_t width = filter_p->width;
    const float32_t offset = path_p->offset!=0 ? path_p->offset : width/2;

    UiSpectrum_UpdateSpectrumPixelParameters(); // before accessing pixel parameters, request update according to configuration

    float32_t width_pixel = width/sd.hz_per_pixel;                          // calculate width of line in pixels

    float32_t offset_pixel = offset/sd.hz_per_pixel;                            // calculate filter center frequency offset in pixels

    float32_t left_filter_border_pos;

    if(RadioManagement_UsesBothSidebands(ts.dmod_mode))     // special cases - AM, SAM and FM, which are double-sidebanded
    {
        left_filter_border_pos = sd.rx_carrier_pos - width_pixel;                   // line starts "width" below center
        width_pixel *= 2;                       // the width is double in AM & SAM, above and below center
    }
    else if(RadioManagement_LSBActive(ts.dmod_mode))    // not AM, but LSB:  calculate position of line, compensating for both width and the fact that SSB/CW filters are not centered
    {
        left_filter_border_pos = sd.rx_carrier_pos - (offset_pixel + (width_pixel/2));  // if LSB it will be below zero Hz
    }
    else                // USB mode
    {
        left_filter_border_pos = sd.rx_carrier_pos + (offset_pixel - (width_pixel/2));          // if USB it will be above zero Hz
    }

    if(left_filter_border_pos < 0) // prevents line to leave left border
     {
         width_pixel = width_pixel + left_filter_border_pos;
         left_filter_border_pos = 0;
     }

     if(left_filter_border_pos + width_pixel > slayout.scope.w) // prevents line to leave right border
     {
         width_pixel = (float32_t)slayout.scope.w - left_filter_border_pos;
     }

    *width_pixel_=width_pixel;
    *left_filter_border_pos_=left_filter_border_pos;
}

/**
 * @brief Show a small horizontal line below the spectrum to indicate the rx passband of the currently active filter
 */
void UiSpectrum_DisplayFilterBW()
{

    if((ts.menu_mode == 0) && (ts.VirtualKeysShown_flag == 0))
    {// bail out if in menu mode
        // Update screen indicator - first get the width and center-frequency offset of the currently-selected filter

        float32_t width_pixel;                          // calculate width of line in pixels
        float32_t left_filter_border_pos;
        UiSpectrum_CalculateDisplayFilterBW(&width_pixel,&left_filter_border_pos);

        uint16_t pos_bw_y = slayout.graticule.y + slayout.graticule.h - 2;

        UiLcdHy28_DrawStraightLineDouble(slayout.graticule.x, pos_bw_y, slayout.graticule.w, LCD_DIR_HORIZONTAL, Black);
        uint32_t clr;
        // get color for line
        UiMenu_MapColors(ts.filter_disp_colour,NULL, &clr);
        // draw line
        UiLcdHy28_DrawStraightLineDouble(((float32_t)slayout.graticule.x + roundf(left_filter_border_pos)), pos_bw_y, roundf(width_pixel), LCD_DIR_HORIZONTAL, clr);
    }
}

/**
 * @brief Draw the frequency information on the frequency bar at the bottom of the spectrum scope based on the current frequency
 */
static void UiSpectrum_DrawFrequencyBar()
{

    char    txt[16];

    UiSpectrum_UpdateSpectrumPixelParameters();

    if (ts.spectrum_freqscale_colour != SPEC_BLACK)     // don't bother updating frequency scale if it is black (invisible)!
    {
        float32_t grat = 6.0f / (float32_t)(1 << sd.magnify);

        // This function draws the frequency bar at the bottom of the spectrum scope, putting markers every at every graticule and the full frequency
        // (rounded to the nearest kHz) in the "center".  (by KA7OEI, 20140913)

        // get color for frequency scale
        uint32_t  clr;
        UiMenu_MapColors(ts.spectrum_freqscale_colour,NULL, &clr);

        float32_t freq_calc = RadioManagement_GetRXDialFrequency() + (ts.dmod_mode == DEMOD_CW ? RadioManagement_GetCWDialOffset() : 0 );      // get current tune frequency in Hz

//        if (sd.magnify == 0)
        if (sd.magnify == 0 || ts.iq_freq_mode == FREQ_IQ_CONV_SLIDE)
        {
            freq_calc += AudioDriver_GetTranslateFreq();
            // correct for display center not being RX center frequency location
        }
        if(sd.magnify < 3)
        {
            freq_calc = roundf(freq_calc/1000); // round graticule frequency to the nearest kHz
        }
        else if (sd.magnify < 5)
        {
            freq_calc = roundf(freq_calc/100) / 10; // round graticule frequency to the nearest 100Hz
        }
        else if(sd.magnify == 5)
        {
            freq_calc = roundf(freq_calc/50) / 20; // round graticule frequency to the nearest 50Hz
        }


        int16_t centerIdx = -100; // UiSpectrum_GetGridCenterLine(0);

        uint16_t idx2pos[pos_spectrum->SCOPE_GRID_VERT_COUNT+1];

        // remainder of frequency/graticule markings
        for(int i=1;i<pos_spectrum->SCOPE_GRID_VERT_COUNT;i++)
        {
            idx2pos[i]=sd.vert_grid_id[i-1];
        }

        idx2pos[0]=0;
        idx2pos[pos_spectrum->SCOPE_GRID_VERT_COUNT]=slayout.scope.w-1;

        if(sd.magnify > 2)
        {
            idx2pos[pos_spectrum->SCOPE_GRID_VERT_COUNT-1]-=9;
        }

       // FIXME: This code expect 8 vertical lines)
        for (int idx = -4; idx < 5; idx += (sd.magnify < 2) ? 1 : 2 )
        {
            int pos = idx2pos[idx+4];
            const uint8_t graticule_font = 4;
            const uint16_t number_width = UiLcdHy28_TextWidth("    ",graticule_font);
            const uint16_t pos_number_y = (slayout.graticule.y +  (slayout.graticule.h - UiLcdHy28_TextHeight(graticule_font))/2);
            if (idx != centerIdx)
            {
                char *c;
                if(sd.magnify < 3)
                {
                    snprintf(txt,16, "%02lu", ((uint32_t)(freq_calc+(idx*grat)))%100);   // build string for middle-left frequency (1khz precision)
                    c = txt;  // point at 2nd character from the end
                }
                else
                {
                    float32_t disp_freq = freq_calc+(idx*grat);
                    int bignum = disp_freq;
                    int smallnum = roundf((disp_freq-bignum)*100);
                    snprintf(txt,16, " %u.%02u", bignum, smallnum);   // build string for middle-left frequency (10Hz precision)
                    c = &txt[strlen(txt)-4];  // point at 5th character from the end
                }
                if (idx == -4) // left border
                {
                    UiLcdHy28_PrintText( slayout.graticule.x + pos, pos_number_y,c,clr,Black,graticule_font);
                }
                else if (idx == 4) // right border
                {
                    UiLcdHy28_PrintTextRight( slayout.graticule.x + pos, pos_number_y,c,clr,Black,graticule_font);
                }

                else
                {
                    UiLcdHy28_PrintTextCentered(slayout.graticule.x +  pos - number_width/2,pos_number_y, number_width,c,clr,Black,graticule_font);
                }
            }
        }
    }
}



void UiSpectrum_Redraw()
{
    // Only in RX mode and NOT while powering down or in menu mode or if displaying memory information
    if (
            (ts.txrx_mode == TRX_MODE_RX)
          //&& (ts.menu_mode == false)
            && (ts.powering_down == false)
          //&& (ts.mem_disp == false)
          //&& (sd.enabled == true)
            && (ts.lcd_blanking_flag == false)
		  //&& (ts.SpectrumResize_flag == false)
    )
    {
        if(ts.waterfall.scheduler == 0 && is_waterfallmode())   // is waterfall mode enabled?
        {
            if(ts.waterfall.speed > 0)  // is it time to update the scan, or is this scope to be disabled?
            {
                //ts.waterfall.scheduler = (ts.waterfall.speed)*(sd.doubleWaterfallLine?50:25); // we need to use half the speed if in double line drawing mode
            	ts.waterfall.scheduler = (ts.waterfall.speed)*(10*(sd.repeatWaterfallLine)); // we need to use half the speed if in double line drawing mode
                sd.RedrawType|=Redraw_WATERFALL;
            }
        }

        if(ts.scope_scheduler == 0 && is_scopemode())   // is waterfall mode enabled?
        {
            if(ts.scope_speed > 0)  // is it time to update the scan, or is this scope to be disabled?
            {
                ts.scope_scheduler = (ts.scope_speed)*50;
                sd.RedrawType|=Redraw_SCOPE;
            }
        }
        UiSpectrum_RedrawSpectrum();
    }
}




//void ui_spectrum_init_cw_snap_display (bool visible)
void UiSpectrum_InitCwSnapDisplay (bool visible)
{
	int color = Green;
	if(!visible)
	{
		color = Black;
		// also erase yellow indicator
        UiLcdHy28_DrawFullRect(ts.Layout->SNAP_CARRIER.x-27, ts.Layout->SNAP_CARRIER.y, 6, 58, Black);
	}
	//Horizontal lines of box
	UiLcdHy28_DrawStraightLine(ts.Layout->SNAP_CARRIER.x-27,
			ts.Layout->SNAP_CARRIER.y + 6,
            27,
            LCD_DIR_HORIZONTAL,
            color);
	UiLcdHy28_DrawStraightLine(ts.Layout->SNAP_CARRIER.x+5,
			ts.Layout->SNAP_CARRIER.y + 6,
            27,
            LCD_DIR_HORIZONTAL,
            color);
	UiLcdHy28_DrawStraightLine(ts.Layout->SNAP_CARRIER.x-27,
			ts.Layout->SNAP_CARRIER.y - 1,
            27,
            LCD_DIR_HORIZONTAL,
            color);
	UiLcdHy28_DrawStraightLine(ts.Layout->SNAP_CARRIER.x+5,
			ts.Layout->SNAP_CARRIER.y - 1,
            27,
            LCD_DIR_HORIZONTAL,
            color);
	// vertical lines of box
	UiLcdHy28_DrawStraightLine(ts.Layout->SNAP_CARRIER.x-27,
			ts.Layout->SNAP_CARRIER.y - 1,
            8,
            LCD_DIR_VERTICAL,
            color);
	UiLcdHy28_DrawStraightLine(ts.Layout->SNAP_CARRIER.x+31,
			ts.Layout->SNAP_CARRIER.y - 1,
            8,
            LCD_DIR_VERTICAL,
            color);
}

void UiSpectrum_CwSnapDisplay (float32_t delta)
{
#define max_delta 140.0
#define divider 5.0
#define alpha 0.7
#if	defined(STM32F7) || defined(STM32H7)
	static float32_t old_delta = 0.0;
#endif

	static int old_delta_p = 0;
	if(delta > max_delta)
	{
		delta = max_delta;
	}
	else if(delta < -max_delta)
	{
		delta = -max_delta;
	}

	// lowpass filtering only for fast processors
#if	defined(STM32F7) || defined(STM32H7)
    delta = (1.0 - alpha) * delta + alpha * old_delta;
#endif


	int delta_p = (int)(0.5 + (delta / divider));

	if(delta_p != old_delta_p)
	{
	    UiLcdHy28_DrawStraightLineDouble( ts.Layout->SNAP_CARRIER.x + old_delta_p + 1,
	    		ts.Layout->SNAP_CARRIER.y,
	            6,
	            LCD_DIR_VERTICAL,
	            Black);

	    UiLcdHy28_DrawStraightLineDouble( ts.Layout->SNAP_CARRIER.x + delta_p + 1,
	    		ts.Layout->SNAP_CARRIER.y,
	            6,
	            LCD_DIR_VERTICAL,
	            Yellow);
#if	defined(STM32F7) || defined(STM32H7)
	    old_delta = delta;
#endif
		old_delta_p = delta_p;
	}
}


void UiSpectrum_CalculateSnap(float32_t Lbin, float32_t Ubin, int posbin, float32_t bin_BW)
{
	// SNAP is used to estimate the frequency of a carrier and subsequently tune the Rx frequency to that carrier frequency
	// At the moment (January 2018), it is usable in the following demodulation modes:
	// AM & SAM
	// CW -> a morse activity detector (built-in in the CW decoding algorithm) detects whenever a CW signal is present and allows
	//       frequency estimation update ONLY when a carrier is present
	// DIGIMODE -> BPSK
	// DD4WH, Jan 2018
	//
	if(ads.CW_signal || (ts.dmod_mode == DEMOD_AM || ts.dmod_mode == DEMOD_SAM || (ts.dmod_mode == DEMOD_DIGI && ts.digital_mode == DigitalMode_BPSK)))
		// this is only done, if there has been a pulse from the CW station that exceeds the threshold
		// in the CW decoder section
		// OR if we are in AM/SAM/Digi BPSK mode
	{
	    static float32_t freq_old = 10000000.0;
	    float32_t help_freq = df.tune_old;
	    // 1. lowpass filter all the relevant bins over 2 to 20 FFTs (?)
	    // lowpass filtering already exists in the spectrum/waterfall display driver

	    // 2. determine bin with maximum value inside these samples

	    // look for maximum value and save the bin # for frequency delta calculation
	    float32_t maximum = 0.0;
	    float32_t maxbin = 1.0;
	    float32_t delta1 = 0.0;
	    float32_t delta2 = 0.0;
	    float32_t delta = 0.0;

	    for (int c = (int)Lbin; c <= (int)Ubin; c++)   // search for FFT bin with highest value = carrier and save the no. of the bin in maxbin
	    {
	        if (maximum < sd.FFT_Samples[c])
	        {
	            maximum = sd.FFT_Samples[c];
	            maxbin = c;
	        }
	    }

// 3. first frequency carrier offset calculation
    // ok, we have found the maximum, now save first delta frequency
//    delta1 = (maxbin - (float32_t)posbin) * bin_BW;
    delta1 = ((maxbin + 1.0) - (float32_t)posbin) * bin_BW;

// 4. second frequency carrier offset calculation

    if(maxbin < 1.0)
    {
    	maxbin = 1.0;
    }
    float32_t bin1 = sd.FFT_Samples[(int)maxbin-1];
    float32_t bin2 = sd.FFT_Samples[(int)maxbin];
    float32_t bin3 = sd.FFT_Samples[(int)maxbin+1];

    if (bin1+bin2+bin3 == 0.0) bin1= 0.00000001; // prevent divide by 0

    // estimate frequency of carrier by three-point-interpolation of bins around maxbin
    // formula by (Jacobsen & Kootsookos 2007) equation (4) P=1.36 for Hanning window FFT function

//    delta2 = (bin_BW * (1.75 * (bin3 - bin1)) / (bin1 + bin2 + bin3));
    delta2 = (bin_BW * (1.36 * (bin3 - bin1)) / (bin1 + bin2 + bin3));
    if(delta2 > bin_BW) delta2 = 0.0;
    delta = delta1 + delta2;

    if(ts.dmod_mode == DEMOD_CW)
    { // only add offset, if in CW mode, not in AM/SAM etc.
        const float32_t cw_offset = (ts.cw_lsb?1.0:-1.0)*(float32_t)ts.cw_sidetone_freq;
        delta = delta + cw_offset;
    }

    if(ts.dmod_mode == DEMOD_DIGI && ts.digital_mode == DigitalMode_BPSK)
    {
    	if(ts.digi_lsb)
    	{
    		delta = delta + PSK_OFFSET; //
    	}
    	else
    	{
    		delta = delta - PSK_OFFSET; //
    	}
    }

    // make 10 frequency measurements and after that take the lowpass filtered frequency to tune to

    help_freq = help_freq + delta;
    // do we need a lowpass filter?
    help_freq = 0.2 * help_freq + 0.8 * freq_old;
    ads.snap_carrier_freq = (ulong) (help_freq);
    freq_old = help_freq;

	static uint8_t snap_counter = 0;
#if	defined(STM32F7) || defined(STM32H7)
	const int SNAP_COUNT_MAX = 10;
#else
	const int SNAP_COUNT_MAX = 6;
#endif
    if(sc.snap == true)
    {
    	snap_counter++;
    	if(snap_counter >= SNAP_COUNT_MAX) // take low pass filtered 6 freq measurements
    	{
    		// tune to frequency
            // set frequency of Si570 with 4 * dialfrequency
            df.tune_new = help_freq;
    		// reset counter
    		snap_counter = 0;
    		sc.snap = false;
    	}
    }
    // graphical TUNE HELPER display
    UiSpectrum_CwSnapDisplay (delta);

	}
}

static void UiSpectrum_CalculateDBm()
{

    //###########################################################################################################################################
    //###########################################################################################################################################
    // dBm/Hz-display DD4WH June, 9th 2016
    // the dBm/Hz display gives an absolute measure of the signal strength of the sum of all signals inside the passband of the filter
    // we take the FFT-magnitude values of the spectrum display FFT for this purpose (which are already calculated for the spectrum display),
    // so the additional processor load and additional RAM usage should be close to zero
    // this code also calculates the basis for the S-Meter (in sm.dbm and sm.dbmhz)
    //
    if( ts.txrx_mode == TRX_MODE_RX)
    {
        const float32_t slope = 19.8; // 19.6; --> empirical values derived from measurements by DL8MBY, 2016/06/30, Thanks!
        const float32_t cons = ts.dbm_constant - 225 - (sd.fft_iq_len == 1024?3:0);
        // the last term is for correcting the dbm value when a twice as large fft is being used (512 vs. 256)
        const int buff_len_int = sd.fft_iq_len;
        const float32_t buff_len = buff_len_int;

        // width of a 256 tap FFT bin = 187.5Hz
        // we have to take into account the magnify mode
        // --> recalculation of bin_BW
        // correct bin bandwidth is determined by the Zoom FFT display setting
        const float32_t bin_BW = IQ_SAMPLE_RATE_F * 2.0 / (buff_len * (1 << sd.magnify)) ;

        float32_t width = FilterInfo[ts.filters_p->id].width;
        float32_t offset = ts.filters_p->offset;

        if (offset == 0)
        {
            offset = width/2;
        }

        const float32_t lf_freq = offset - width/2;
        const float32_t uf_freq = offset + width/2;

        //	determine Lbin and Ubin from ts.dmod_mode and FilterInfo.width
        //	= determine bandwith separately for lower and upper sideband

        float32_t bw_LOWER = 0.0;
        float32_t bw_UPPER = 0.0;

        if (RadioManagement_UsesBothSidebands(ts.dmod_mode) == true)
        {
            if((ts.expflags1 & EXPFLAGS1_SMETER_AM_NOT_LO) && (ts.dmod_mode == DEMOD_AM))
            {
                bw_UPPER = uf_freq; // Calculate USB
                bw_LOWER = 300; // 300 Hz
            }
            else
            {
                bw_UPPER = uf_freq;
                bw_LOWER = -uf_freq;
            }
        }
        else if (RadioManagement_LSBActive(ts.dmod_mode) == true)
        {
            bw_UPPER = -lf_freq;
            bw_LOWER = -uf_freq;
        }
        else if (is_demod_psk())
        { // this is for experimental SNAP of BPSK carriers
            bw_LOWER = PSK_OFFSET - PSK_SNAP_RANGE;
            bw_UPPER = PSK_OFFSET + PSK_SNAP_RANGE;
        }
        else // USB
        {
            bw_UPPER = uf_freq;
            bw_LOWER = lf_freq;
        }


        //  determine posbin (where we receive at the moment) from ts.iq_freq_mode

        // frequency translation off, IF = 0 Hz OR
        // in all magnify cases (2x up to 32x) the posbin is in the centre of the spectrum display

//        const int32_t bin_offset = sd.magnify != 0 ? 0 : (- (buff_len_int * AudioDriver_GetTranslateFreq( )) / (2 * IQ_SAMPLE_RATE));
//        const int32_t posbin = buff_len_int / 4 + bin_offset;  // right in the middle!        const int32_t bin_offset = (sd.magnify != 0 && ts.iq_freq_mode != FREQ_IQ_CONV_SLIDE) ? 0 : (- (buff_len_int * AudioDriver_GetTranslateFreq( )) / (2 * IQ_SAMPLE_RATE));
        const int32_t bin_offset = (sd.magnify != 0 && ts.iq_freq_mode != FREQ_IQ_CONV_SLIDE) ? 0 : (- (buff_len_int * AudioDriver_GetTranslateFreq( )) / (2 * IQ_SAMPLE_RATE));
        const int32_t posbin = buff_len_int / 4 + bin_offset * (1 << sd.magnify);  // right in the middle!

        // calculate upper and lower limit for determination of signal strength
        // = filter passband is between the lower bin Lbin and the upper bin Ubin
        float32_t Lbin = (float32_t)posbin + roundf(bw_LOWER / bin_BW);
        float32_t Ubin = (float32_t)posbin + roundf(bw_UPPER / bin_BW); // the bin on the upper sideband side

        if(ts.dmod_mode == DEMOD_SAM && ads.sam_sideband == SAM_SIDEBAND_USB) // workaround to make SNAP and carrier offset display work with sideband-selected SAM
        {
            Lbin = Lbin - 1.0;
        }

        // take care of filter bandwidths that are larger than the displayed FFT bins
        if(Lbin < 0)
        {
            Lbin = 0;
        }

        if (Ubin > (sd.spec_len-1))
        {
            Ubin = sd.spec_len-1;
        }

        for(int32_t i = 0; i < (buff_len_int/4); i++)
        {
            sd.FFT_Samples[sd.spec_len - i - 1] = sd.FFT_MagData[i + buff_len_int/4] * SCOPE_PREAMP_GAIN;	// get data
        }
        for(int32_t i = buff_len_int/4; i < (buff_len_int/2); i++)
        {
            sd.FFT_Samples[sd.spec_len - i - 1] = sd.FFT_MagData[i - buff_len_int/4] * SCOPE_PREAMP_GAIN;	// get data
        }

        // here would be the right place to start with the SNAP mode!
        if(cw_decoder_config.snap_enable && (ts.dmod_mode == DEMOD_CW || ts.dmod_mode == DEMOD_AM || ts.dmod_mode == DEMOD_SAM || (ts.dmod_mode == DEMOD_DIGI && ts.digital_mode == DigitalMode_BPSK)))
        {
            UiSpectrum_CalculateSnap(Lbin, Ubin, posbin, bin_BW);
        }

        float32_t sum_db = 0.0;
        // determine the sum of all the bin values in the passband
        for (int c = Lbin; c <= (int)Ubin; c++)   // sum up all the values of all the bins in the passband
        {
            sum_db = sum_db + sd.FFT_Samples[c]; // / (float32_t)(1<<sd.magnify);
        }
        // we have to account for the larger number of bins that are summed up when using higher
        // magnifications
        // for example: if we have 34 bins to sum up for sd.magnify == 1, we sum up 68 bins for sd.magnify == 2

        //            sum_db /= (float32_t)sd.magnify + 1;
        //            cons = cons - 3.0 * (sd.magnify);

        if (sum_db > 0)
        {
            sm.dbm_cur = slope * Math_log10f_fast (sum_db) + cons;
            sm.dbmhz_cur = sm.dbm_cur -  10 * Math_log10f_fast ((float32_t)(((int)Ubin-(int)Lbin) * bin_BW)) ;
        }
        else
        {
            sm.dbm_cur = -145.0;
            sm.dbmhz_cur = -145.0;
        }
    }
}
/*
#ifdef X_USE_DISP_480_320_SPEC
//Waterfall memory pointer allocation.
//It sets memory pointer to Height/2 array located in CCM for f4 devices with low ram amount. For all rest allocates memory by calling malloc.
void UiSpectrum_SetWaterfallMemoryPointer(uint16_t ramsize)
{
	if(ramsize<256)
	{
		sd.waterfall=sd.waterfall_mem;	//CCM memory for devices with low ram amount. Used with each line doubled.
	}
	else
	{
		sd.waterfall=(uint8_t (*)[SPECTRUM_WIDTH]) malloc(WATERFALL_HEIGHT*SPECTRUM_WIDTH);	//malloc returns pointer in normal ram (for F4 devices CCM memory is always 64kB)
	}
}
#endif
*/
