mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-11-04 10:49:11 +01:00 
			
		
		
		
	Rewrote the waterfall
This commit is contained in:
		@@ -100,7 +100,6 @@ namespace dsp {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            _tapCount = taps.size();
 | 
			
		||||
            printf("[%d]\n", _tapCount);
 | 
			
		||||
            delete[] _taps;
 | 
			
		||||
            delete[] delayBuf;
 | 
			
		||||
            _taps = new float[_tapCount];
 | 
			
		||||
@@ -316,7 +315,7 @@ namespace dsp {
 | 
			
		||||
            int delaySize = (_this->_tapCount - 1) * sizeof(float);
 | 
			
		||||
 | 
			
		||||
            int blockSize = _this->_blockSize;
 | 
			
		||||
            int outBufferLength = outputSize * sizeof(complex_t);
 | 
			
		||||
            int outBufferLength = outputSize * sizeof(float);
 | 
			
		||||
            int tapCount = _this->_tapCount;
 | 
			
		||||
            int decim = _this->_decim;
 | 
			
		||||
            float* delayBuf = _this->delayBuf;
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,6 @@ namespace dsp {
 | 
			
		||||
            while (dataRead < len) {
 | 
			
		||||
                int canRead = waitUntilReadable();
 | 
			
		||||
                if (canRead < 0) {
 | 
			
		||||
                    printf("Reader stopped\n");
 | 
			
		||||
                    clearReadStop();
 | 
			
		||||
                    return -1;
 | 
			
		||||
                }
 | 
			
		||||
@@ -65,7 +64,6 @@ namespace dsp {
 | 
			
		||||
            while (dataRead < len) {
 | 
			
		||||
                int canRead = waitUntilReadable();
 | 
			
		||||
                if (canRead < 0) {
 | 
			
		||||
                    printf("reader stopped (read and skip)\n");
 | 
			
		||||
                    clearReadStop();
 | 
			
		||||
                    return -1;
 | 
			
		||||
                }
 | 
			
		||||
@@ -129,7 +127,6 @@ namespace dsp {
 | 
			
		||||
            while (dataWrite < len) {
 | 
			
		||||
                int canWrite = waitUntilWriteable();
 | 
			
		||||
                if (canWrite < 0) {
 | 
			
		||||
                    printf("Writer stopped\n");
 | 
			
		||||
                    clearWriteStop();
 | 
			
		||||
                    return -1;
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -346,8 +346,7 @@ namespace dsp {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            interp.stop();
 | 
			
		||||
            //decim.stop();
 | 
			
		||||
            Sleep(200);
 | 
			
		||||
            decim.stop();
 | 
			
		||||
            running = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,6 @@ namespace dsp {
 | 
			
		||||
            lo.start();
 | 
			
		||||
            mixer.start();
 | 
			
		||||
            if (_interp != 1) {
 | 
			
		||||
                printf("UH OH INTERPOLATOR STARTED :/\n");
 | 
			
		||||
                interp.start();
 | 
			
		||||
            }
 | 
			
		||||
            decim.start();
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ bool dcbias = true;
 | 
			
		||||
 | 
			
		||||
io::SoapyWrapper soapy;
 | 
			
		||||
 | 
			
		||||
//dsp::HackRFSource src;
 | 
			
		||||
SignalPath sigPath;
 | 
			
		||||
std::vector<float> _data;
 | 
			
		||||
std::vector<float> fftTaps;
 | 
			
		||||
@@ -50,9 +49,9 @@ void fftHandler(dsp::complex_t* samples) {
 | 
			
		||||
 | 
			
		||||
void windowInit() {
 | 
			
		||||
    int sampleRate = 8000000;
 | 
			
		||||
    wtf.bandWidth = sampleRate;
 | 
			
		||||
    wtf.range = 500000;
 | 
			
		||||
    wtf.centerFrequency = 90500000;
 | 
			
		||||
    wtf.setBandwidth(sampleRate);
 | 
			
		||||
    //wtf.range = 500000;
 | 
			
		||||
    wtf.setCenterFrequency(90500000);
 | 
			
		||||
    printf("fft taps: %d\n", fftTaps.size());
 | 
			
		||||
    
 | 
			
		||||
    fft_in = (fftwf_complex*) fftw_malloc(sizeof(fftwf_complex) * fftSize);
 | 
			
		||||
@@ -61,24 +60,6 @@ void windowInit() {
 | 
			
		||||
 | 
			
		||||
    printf("Starting DSP Thread!\n");
 | 
			
		||||
 | 
			
		||||
    // hackrf_init();
 | 
			
		||||
    // hackrf_device_list_t* list = hackrf_device_list();
 | 
			
		||||
    
 | 
			
		||||
    // int err = hackrf_device_list_open(list, 0, &dev);
 | 
			
		||||
    // if (err != 0) {
 | 
			
		||||
    //     printf("Error while opening HackRF: %d\n", err);
 | 
			
		||||
    //     return;
 | 
			
		||||
    // }
 | 
			
		||||
    // hackrf_set_freq(dev, 90500000);
 | 
			
		||||
    // //hackrf_set_txvga_gain(dev, 10);
 | 
			
		||||
    // hackrf_set_amp_enable(dev, 1);
 | 
			
		||||
    // hackrf_set_lna_gain(dev, 24);
 | 
			
		||||
    // hackrf_set_vga_gain(dev, 20);
 | 
			
		||||
    // hackrf_set_baseband_filter_bandwidth(dev, sampleRate);
 | 
			
		||||
    // hackrf_set_sample_rate(dev, sampleRate);
 | 
			
		||||
 | 
			
		||||
    //src.init(dev, 64000);
 | 
			
		||||
 | 
			
		||||
    sigPath.init(sampleRate, 20, fftSize, &soapy.output, (dsp::complex_t*)fft_in, fftHandler);
 | 
			
		||||
    sigPath.start();
 | 
			
		||||
}
 | 
			
		||||
@@ -94,6 +75,8 @@ bool showExample = false;
 | 
			
		||||
int freq = 90500;
 | 
			
		||||
int _freq = 90500;
 | 
			
		||||
 | 
			
		||||
int demod = 0;
 | 
			
		||||
 | 
			
		||||
bool state = false;
 | 
			
		||||
bool mulstate = true;
 | 
			
		||||
 | 
			
		||||
@@ -103,12 +86,17 @@ float lastVfoFreq = 92000000.0f;
 | 
			
		||||
float volume = 1.0f;
 | 
			
		||||
float lastVolume = 1.0f;
 | 
			
		||||
 | 
			
		||||
float fftMin = -70.0f;
 | 
			
		||||
float fftMax = 0.0f;
 | 
			
		||||
 | 
			
		||||
float offset = 0.0f;
 | 
			
		||||
float bw = 8000000.0f;
 | 
			
		||||
 | 
			
		||||
void drawWindow() {
 | 
			
		||||
    if (freq != _freq) {
 | 
			
		||||
        _freq = freq;
 | 
			
		||||
        wtf.centerFrequency = freq * 1000;
 | 
			
		||||
        wtf.setCenterFrequency(freq * 1000);
 | 
			
		||||
        soapy.setFrequency(freq * 1000);
 | 
			
		||||
        //hackrf_set_freq(dev, freq * 1000);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (vfoFreq != lastVfoFreq) {
 | 
			
		||||
@@ -186,17 +174,17 @@ void drawWindow() {
 | 
			
		||||
        ImGui::BeginGroup();
 | 
			
		||||
 | 
			
		||||
        ImGui::Columns(4, "RadioModeColumns", false);
 | 
			
		||||
        ImGui::RadioButton("NFM", false);
 | 
			
		||||
        ImGui::RadioButton("WFM", true);
 | 
			
		||||
        if (ImGui::RadioButton("NFM", demod == 0) && demod != 0) { demod = 0; };
 | 
			
		||||
        if (ImGui::RadioButton("WFM", demod == 1) && demod != 1) { sigPath.setDemodulator(SignalPath::DEMOD_FM); demod = 1; };
 | 
			
		||||
        ImGui::NextColumn();
 | 
			
		||||
        ImGui::RadioButton("AM", false);
 | 
			
		||||
        ImGui::RadioButton("DSB", false);
 | 
			
		||||
        if (ImGui::RadioButton("AM", demod == 2) && demod != 2) { sigPath.setDemodulator(SignalPath::DEMOD_AM); demod = 2; };
 | 
			
		||||
        if (ImGui::RadioButton("DSB", demod == 3) && demod != 3) { demod = 3; };
 | 
			
		||||
        ImGui::NextColumn();
 | 
			
		||||
        ImGui::RadioButton("USB", false);
 | 
			
		||||
        ImGui::RadioButton("CW", false);
 | 
			
		||||
        if (ImGui::RadioButton("USB", demod == 4) && demod != 4) { demod = 4; };
 | 
			
		||||
        if (ImGui::RadioButton("CW", demod == 5) && demod != 5) { demod = 5; };
 | 
			
		||||
        ImGui::NextColumn();
 | 
			
		||||
        ImGui::RadioButton("LSB", false);
 | 
			
		||||
        ImGui::RadioButton("RAW", false);
 | 
			
		||||
        if (ImGui::RadioButton("LSB", demod == 6) && demod != 6) { demod = 6; };
 | 
			
		||||
        if (ImGui::RadioButton("RAW", demod == 7) && demod != 7) { demod = 7; };
 | 
			
		||||
        ImGui::Columns(1, "EndRadioModeColumns", false);
 | 
			
		||||
 | 
			
		||||
        ImGui::InputInt("Frequency (kHz)", &freq);
 | 
			
		||||
@@ -215,20 +203,36 @@ void drawWindow() {
 | 
			
		||||
        ImGui::Text("Frame time: %.3f ms/frame", 1000.0f / ImGui::GetIO().Framerate);
 | 
			
		||||
        ImGui::Text("Framerate: %.1f FPS", ImGui::GetIO().Framerate);
 | 
			
		||||
 | 
			
		||||
        if (ImGui::Button("FM demod")) {
 | 
			
		||||
            sigPath.setDemodulator(SignalPath::DEMOD_FM);
 | 
			
		||||
        }
 | 
			
		||||
        if (ImGui::Button("AM demod")) {
 | 
			
		||||
            sigPath.setDemodulator(SignalPath::DEMOD_AM);
 | 
			
		||||
        ImGui::SliderFloat("##_3_", &fftMax, 0.0f, -100.0f, "");
 | 
			
		||||
        ImGui::SliderFloat("##_4_", &fftMin, 0.0f, -100.0f, "");
 | 
			
		||||
 | 
			
		||||
        if (ImGui::Button("Auto Range")) {
 | 
			
		||||
            printf("Auto ranging...\n");
 | 
			
		||||
            wtf.autoRange();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ImGui::SliderFloat("##_5_", &offset, -4000000.0f, 4000000.0f, "");
 | 
			
		||||
        ImGui::SliderFloat("##_6_", &bw, 1.0f, 8000000.0f, "");
 | 
			
		||||
 | 
			
		||||
        wtf.setViewOffset(offset);
 | 
			
		||||
        wtf.setViewBandwidth(bw);
 | 
			
		||||
 | 
			
		||||
        wtf.setFFTMin(fftMin);
 | 
			
		||||
        wtf.setFFTMax(fftMax);
 | 
			
		||||
        wtf.setWaterfallMin(fftMin);
 | 
			
		||||
        wtf.setWaterfallMax(fftMax);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ImVec2 delta = ImGui::GetMouseDragDelta();
 | 
			
		||||
    ImGui::ResetMouseDragDelta();
 | 
			
		||||
    //printf("%f %f\n", delta.x, delta.y);
 | 
			
		||||
 | 
			
		||||
    ImGui::EndChild();
 | 
			
		||||
 | 
			
		||||
    // Right Column
 | 
			
		||||
    ImGui::NextColumn();
 | 
			
		||||
 | 
			
		||||
    ImGui::BeginChild("Waterfall");
 | 
			
		||||
    wtf.draw(&vfoFreq);    
 | 
			
		||||
    wtf.draw();    
 | 
			
		||||
    ImGui::EndChild();
 | 
			
		||||
}
 | 
			
		||||
@@ -49,31 +49,31 @@ void SignalPath::setDemodulator(int demId) {
 | 
			
		||||
    audioResamp.stop();
 | 
			
		||||
 | 
			
		||||
    // Stop current demodulator
 | 
			
		||||
    // if (_demod == DEMOD_FM) {
 | 
			
		||||
    //     printf("Stopping FM demodulator\n");
 | 
			
		||||
    //     demod.stop();
 | 
			
		||||
    // }
 | 
			
		||||
    // else if (_demod == DEMOD_AM) {
 | 
			
		||||
    //     printf("Stopping AM demodulator\n");
 | 
			
		||||
    //     amDemod.stop();
 | 
			
		||||
    // }
 | 
			
		||||
    // _demod = demId;
 | 
			
		||||
    if (_demod == DEMOD_FM) {
 | 
			
		||||
        printf("Stopping FM demodulator\n");
 | 
			
		||||
        demod.stop();
 | 
			
		||||
    }
 | 
			
		||||
    else if (_demod == DEMOD_AM) {
 | 
			
		||||
        printf("Stopping AM demodulator\n");
 | 
			
		||||
        amDemod.stop();
 | 
			
		||||
    }
 | 
			
		||||
    _demod = demId;
 | 
			
		||||
 | 
			
		||||
    // // Set input of the audio resampler
 | 
			
		||||
    // if (demId == DEMOD_FM) {
 | 
			
		||||
    //     printf("Starting FM demodulator\n");
 | 
			
		||||
    //     // mainVFO.setOutputSampleRate(200000, 200000);
 | 
			
		||||
    //     // audioResamp.setInput(&demod.output);
 | 
			
		||||
    //     // audioResamp.setInputSampleRate(200000, 800);
 | 
			
		||||
    //     demod.start();
 | 
			
		||||
    // }
 | 
			
		||||
    // else if (demId == DEMOD_AM) {
 | 
			
		||||
    //     printf("Starting AM demodulator\n");
 | 
			
		||||
    //     mainVFO.setOutputSampleRate(12500, 12500);
 | 
			
		||||
    //     audioResamp.setInput(&amDemod.output);
 | 
			
		||||
    //     audioResamp.setInputSampleRate(12500, 50);
 | 
			
		||||
    //     amDemod.start();
 | 
			
		||||
    // }
 | 
			
		||||
    // Set input of the audio resampler
 | 
			
		||||
    if (demId == DEMOD_FM) {
 | 
			
		||||
        printf("Starting FM demodulator\n");
 | 
			
		||||
        mainVFO.setOutputSampleRate(200000, 200000);
 | 
			
		||||
        audioResamp.setInput(&demod.output);
 | 
			
		||||
        audioResamp.setInputSampleRate(200000, 800);
 | 
			
		||||
        demod.start();
 | 
			
		||||
    }
 | 
			
		||||
    else if (demId == DEMOD_AM) {
 | 
			
		||||
        printf("Starting AM demodulator\n");
 | 
			
		||||
        mainVFO.setOutputSampleRate(12500, 12500);
 | 
			
		||||
        audioResamp.setInput(&amDemod.output);
 | 
			
		||||
        audioResamp.setInputSampleRate(12500, 50);
 | 
			
		||||
        amDemod.start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    audioResamp.start();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,231 +1,397 @@
 | 
			
		||||
#include <waterfall.h>
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
#define MAP_VAL(aMin, aMax, bMin, bMax, val) ( ( (   ((val) - (aMin)) / ((aMax) - (aMin))  )  * ((bMax) - (bMin)) )  + bMin)
 | 
			
		||||
 | 
			
		||||
const float COLOR_MAP[][3] = {
 | 
			
		||||
    {0x4A, 0x00, 0x00},
 | 
			
		||||
    {0x75, 0x00, 0x00},
 | 
			
		||||
    {0x9F, 0x00, 0x00},
 | 
			
		||||
    {0xC6, 0x00, 0x00},
 | 
			
		||||
    {0xFF, 0x00, 0x00},
 | 
			
		||||
    {0xFE, 0x6D, 0x16},
 | 
			
		||||
    {0xFF, 0xFF, 0x00},
 | 
			
		||||
    {0xFF, 0xFF, 0xFF},
 | 
			
		||||
    {0x1E, 0x90, 0xFF},
 | 
			
		||||
    {0x00, 0x00, 0x91},
 | 
			
		||||
    {0x00, 0x00, 0x50},
 | 
			
		||||
float COLOR_MAP[][3] = {
 | 
			
		||||
    {0x00, 0x00, 0x20},
 | 
			
		||||
    {0x00, 0x00, 0x30},
 | 
			
		||||
    {0x00, 0x00, 0x20}
 | 
			
		||||
    {0x00, 0x00, 0x50},
 | 
			
		||||
    {0x00, 0x00, 0x91},
 | 
			
		||||
    {0x1E, 0x90, 0xFF},
 | 
			
		||||
    {0xFF, 0xFF, 0xFF},
 | 
			
		||||
    {0xFF, 0xFF, 0x00},
 | 
			
		||||
    {0xFE, 0x6D, 0x16},
 | 
			
		||||
    {0xFF, 0x00, 0x00},
 | 
			
		||||
    {0xC6, 0x00, 0x00},
 | 
			
		||||
    {0x9F, 0x00, 0x00},
 | 
			
		||||
    {0x75, 0x00, 0x00},
 | 
			
		||||
    {0x4A, 0x00, 0x00}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool isInArea(ImVec2 pos, ImVec2 min, ImVec2 max) {
 | 
			
		||||
    return (pos.x >= min.x && pos.y >= min.y && pos.x < max.x && pos.y < max.y);
 | 
			
		||||
void doZoom(int offset, int width, int outWidth, std::vector<float> data, float* out) {
 | 
			
		||||
    float factor = (float)width / (float)outWidth;
 | 
			
		||||
    for (int i = 0; i < outWidth; i++) {
 | 
			
		||||
        out[i] = data[offset + ((float)i * factor)];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float freq_ranges[] = {
 | 
			
		||||
        1000.0f, 2000.0f, 2500.0f, 5000.0f,
 | 
			
		||||
        10000.0f, 20000.0f, 25000.0f, 50000.0f,
 | 
			
		||||
        100000.0f, 200000.0f, 250000.0f, 500000.0f,
 | 
			
		||||
        1000000.0f, 2000000.0f, 2500000.0f, 5000000.0f,
 | 
			
		||||
        10000000.0f, 20000000.0f, 25000000.0f, 50000000.0f
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
float findBestFreqRange(float bandwidth) {
 | 
			
		||||
    for (int i = 0; i < 15; i++) {
 | 
			
		||||
        if (bandwidth / freq_ranges[i] < 15.0f) {
 | 
			
		||||
            return freq_ranges[i];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void printAndScale(float freq, char* buf) {
 | 
			
		||||
    if (freq < 1000) {
 | 
			
		||||
        sprintf(buf, "%.3f", freq);
 | 
			
		||||
    }
 | 
			
		||||
    else if (freq < 1000000) {
 | 
			
		||||
        sprintf(buf, "%.3fK", freq / 1000.0f);
 | 
			
		||||
    }
 | 
			
		||||
    else if (freq < 1000000000) {
 | 
			
		||||
        sprintf(buf, "%.3fM", freq / 1000000.0f);
 | 
			
		||||
    }
 | 
			
		||||
    else if (freq < 1000000000000) {
 | 
			
		||||
        sprintf(buf, "%.3fG", freq / 1000000000.0f);
 | 
			
		||||
    }
 | 
			
		||||
    for (int i = strlen(buf) - 2; i >= 0; i--) {
 | 
			
		||||
        if (buf[i] != '0') {
 | 
			
		||||
            if (buf[i] == '.') {
 | 
			
		||||
                i--;
 | 
			
		||||
            }
 | 
			
		||||
            char scale = buf[strlen(buf) - 1];
 | 
			
		||||
            buf[i + 1] = scale;
 | 
			
		||||
            buf[i + 2] = 0;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace ImGui {
 | 
			
		||||
 | 
			
		||||
    uint32_t* img = NULL;
 | 
			
		||||
    int lastW = 0;
 | 
			
		||||
    int lastH = 0;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    WaterFall::WaterFall() {
 | 
			
		||||
        std::vector<float> base;
 | 
			
		||||
        for (int i = 0; i < 1024; i++) {
 | 
			
		||||
            base.push_back(-100.0f);
 | 
			
		||||
        }
 | 
			
		||||
        fftBuffer.push_back(base);
 | 
			
		||||
        newSamples = false;
 | 
			
		||||
        fftMin = -70.0f;
 | 
			
		||||
        fftMax = 0.0f;
 | 
			
		||||
        waterfallMin = -70.0f;
 | 
			
		||||
        waterfallMax = 0.0f;
 | 
			
		||||
        fftHeight = 250;
 | 
			
		||||
        dataWidth = 600;
 | 
			
		||||
        lastWidgetPos.x = 0;
 | 
			
		||||
        lastWidgetPos.y = 0;
 | 
			
		||||
        lastWidgetSize.x = 0;
 | 
			
		||||
        lastWidgetSize.y = 0;
 | 
			
		||||
        latestFFT = new float[1];
 | 
			
		||||
        waterfallFb = new uint32_t[1];
 | 
			
		||||
 | 
			
		||||
        viewBandwidth = 1.0f;
 | 
			
		||||
        wholeBandwidth = 1.0f;
 | 
			
		||||
 | 
			
		||||
        glGenTextures(1, &textureId);
 | 
			
		||||
 | 
			
		||||
        updatePallette(COLOR_MAP, 13);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::drawFFT(ImGuiWindow* window, int width, int height, ImVec2 pos, float* vfo) {
 | 
			
		||||
        float lines = 6.0f;
 | 
			
		||||
        int w = width - 10;
 | 
			
		||||
        float lineHeight = (float)(height - 20 - 30) / lines;
 | 
			
		||||
    void WaterFall::drawFFT() {
 | 
			
		||||
        // Calculate scaling factor
 | 
			
		||||
        float startLine = floorf(fftMax / 10.0f) * 10.0f;
 | 
			
		||||
        float vertRange = fftMax - fftMin;
 | 
			
		||||
        float scaleFactor = fftHeight / vertRange;
 | 
			
		||||
        char buf[100];
 | 
			
		||||
        int fftWidth = width - 50;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Vertical scale
 | 
			
		||||
        for (int i = 0; i < (lines + 1); i++) {
 | 
			
		||||
            sprintf(buf, "%d", -i * 10);
 | 
			
		||||
        for (float line = startLine; line > fftMin; line -= 10.0f) {
 | 
			
		||||
            float yPos = widgetPos.y + fftHeight + 10 - ((line - fftMin) * scaleFactor);
 | 
			
		||||
            window->DrawList->AddLine(ImVec2(widgetPos.x + 50, roundf(yPos)), 
 | 
			
		||||
                                    ImVec2(widgetPos.x + dataWidth + 50, roundf(yPos)),
 | 
			
		||||
                                    IM_COL32(50, 50, 50, 255), 1.0f);
 | 
			
		||||
            sprintf(buf, "%d", (int)line);
 | 
			
		||||
            ImVec2 txtSz = ImGui::CalcTextSize(buf);
 | 
			
		||||
            window->DrawList->AddText(ImVec2(pos.x + 30 - txtSz.x, pos.y + (i * lineHeight) + 2), IM_COL32( 255, 255, 255, 255 ), buf);
 | 
			
		||||
            if (i == lines) { // Last line
 | 
			
		||||
                window->DrawList->AddLine(ImVec2(pos.x + 40, pos.y + (i * lineHeight) + 10),
 | 
			
		||||
                                        ImVec2(pos.x + width - 10, pos.y + (i * lineHeight) + 10),
 | 
			
		||||
                                        IM_COL32( 255, 255, 255, 255 ), 1.0f);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            window->DrawList->AddLine(ImVec2(pos.x + 40, pos.y + (i * lineHeight) + 10), 
 | 
			
		||||
                                    ImVec2(pos.x + width - 10, pos.y + (i * lineHeight) + 10), 
 | 
			
		||||
                                    IM_COL32( 70, 70, 70, 255 ), 1.0f);
 | 
			
		||||
            window->DrawList->AddText(ImVec2(widgetPos.x + 40 - txtSz.x, roundf(yPos - (txtSz.y / 2))), IM_COL32( 255, 255, 255, 255 ), buf);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Horizontal scale
 | 
			
		||||
        float start = ceilf((centerFrequency - (bandWidth / 2)) / range) * range;
 | 
			
		||||
        float end = centerFrequency + (bandWidth / 2);
 | 
			
		||||
        float offsetStart = start - (centerFrequency - (bandWidth / 2));
 | 
			
		||||
        float pixelOffset = (offsetStart * fftWidth) / bandWidth;
 | 
			
		||||
        float pixelWidth = (range * fftWidth) / bandWidth;
 | 
			
		||||
        int count = 0;
 | 
			
		||||
        for (; start < end; start += range) {
 | 
			
		||||
            window->DrawList->AddLine(ImVec2(pos.x + pixelOffset + (pixelWidth * count) + 40, pos.y + 10),
 | 
			
		||||
                                    ImVec2(pos.x + pixelOffset + (pixelWidth * count) + 40, pos.y + (lines * lineHeight) + 10), 
 | 
			
		||||
                                    IM_COL32( 70, 70, 70, 255 ), 1.0f);
 | 
			
		||||
 | 
			
		||||
            window->DrawList->AddLine(ImVec2(pos.x + pixelOffset + (pixelWidth * count) + 40, pos.y + (lines * lineHeight) + 10),
 | 
			
		||||
                                    ImVec2(pos.x + pixelOffset + (pixelWidth * count) + 40, pos.y + (lines * lineHeight) + 20), 
 | 
			
		||||
                                    IM_COL32( 255, 255, 255, 255 ), 1.0f);
 | 
			
		||||
 | 
			
		||||
            sprintf(buf, "%.1fM", start / 1000000.0f);
 | 
			
		||||
 | 
			
		||||
        float startFreq = ceilf(lowerFreq / range) * range;
 | 
			
		||||
        float horizScale = (float)dataWidth / viewBandwidth;
 | 
			
		||||
        for (float freq = startFreq; freq < upperFreq; freq += range) {
 | 
			
		||||
            float xPos = widgetPos.x + 50 + ((freq - lowerFreq) * horizScale);
 | 
			
		||||
            window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + 10), 
 | 
			
		||||
                                    ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10),
 | 
			
		||||
                                    IM_COL32(50, 50, 50, 255), 1.0f);
 | 
			
		||||
            window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10), 
 | 
			
		||||
                                    ImVec2(roundf(xPos), widgetPos.y + fftHeight + 17),
 | 
			
		||||
                                    IM_COL32(255, 255, 255, 255), 1.0f);
 | 
			
		||||
            printAndScale(freq, buf);
 | 
			
		||||
            ImVec2 txtSz = ImGui::CalcTextSize(buf);
 | 
			
		||||
 | 
			
		||||
            window->DrawList->AddText(ImVec2(pos.x + pixelOffset + (pixelWidth * count) + 40 - (txtSz.x / 2.0f), pos.y + (lines * lineHeight) + 25), IM_COL32( 255, 255, 255, 255 ), buf);
 | 
			
		||||
 | 
			
		||||
            count++;
 | 
			
		||||
            window->DrawList->AddText(ImVec2(roundf(xPos - (txtSz.x / 2.0f)), widgetPos.y + fftHeight + 10 + txtSz.y), IM_COL32( 255, 255, 255, 255 ), buf);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int dataCount = fftBuffer[0].size();
 | 
			
		||||
        float multiplier = (float)dataCount / (float)fftWidth;
 | 
			
		||||
        if (lastW != w) {
 | 
			
		||||
            if (fftDrawBuffer != NULL) {
 | 
			
		||||
                delete[] fftDrawBuffer;
 | 
			
		||||
        // Data
 | 
			
		||||
        for (int i = 1; i < dataWidth; i++) {
 | 
			
		||||
            float aPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i - 1] - fftMin) * scaleFactor);
 | 
			
		||||
            float bPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i] - fftMin) * scaleFactor);
 | 
			
		||||
            if (aPos < fftMin && bPos < fftMin) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            fftDrawBuffer = new float[fftWidth];
 | 
			
		||||
        }
 | 
			
		||||
        for (int i = 1; i < fftWidth; i++) {
 | 
			
		||||
            float a = (fftBuffer[0][(int)((float)(i - 1) * multiplier)] / 10.0f) * lineHeight;
 | 
			
		||||
            float b = (fftBuffer[0][(int)((float)i * multiplier)] / 10.0f) * lineHeight;
 | 
			
		||||
            window->DrawList->AddLine(ImVec2(pos.x + i + 39, pos.y - a), 
 | 
			
		||||
                                    ImVec2(pos.x + i + 40, pos.y - b), 
 | 
			
		||||
                                    IM_COL32( 0, 255, 255, 255 ), 1.0f);
 | 
			
		||||
 | 
			
		||||
            window->DrawList->AddLine(ImVec2(pos.x + i + 39, pos.y - a), 
 | 
			
		||||
                                    ImVec2(pos.x + i + 39, pos.y + (lines * lineHeight) + 9), 
 | 
			
		||||
                                    IM_COL32( 0, 255, 255, 50 ), 1.0f);
 | 
			
		||||
            aPos = std::clamp<float>(aPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10);
 | 
			
		||||
            bPos = std::clamp<float>(bPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10);
 | 
			
		||||
            window->DrawList->AddLine(ImVec2(widgetPos.x + 49 + i, roundf(aPos)), 
 | 
			
		||||
                                    ImVec2(widgetPos.x + 50 + i, roundf(bPos)),
 | 
			
		||||
                                    IM_COL32(0, 255, 255, 255), 1.0f);
 | 
			
		||||
            window->DrawList->AddLine(ImVec2(widgetPos.x + 50 + i, roundf(bPos)), 
 | 
			
		||||
                                    ImVec2(widgetPos.x + 50 + i, widgetPos.y + fftHeight + 10),
 | 
			
		||||
                                    IM_COL32(0, 255, 255, 50), 1.0f);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // window->DrawList->AddLine(ImVec2(pos.x + ((i - 1) * spacing) + 40, pos.y - a), 
 | 
			
		||||
        //                          ImVec2(pos.x + (i * spacing) + 40, pos.y - b), 
 | 
			
		||||
        //                          IM_COL32( 0, 255, 255, 255 ), 1.0f);
 | 
			
		||||
 | 
			
		||||
        window->DrawList->AddLine(ImVec2(pos.x + 40, pos.y + 10), ImVec2(pos.x + 40, pos.y + (lines * lineHeight) + 10), IM_COL32( 255, 255, 255, 255 ), 1.0f);
 | 
			
		||||
 | 
			
		||||
        ImVec2 mPos = ImGui::GetMousePos();
 | 
			
		||||
        // window->DrawList->AddRectFilled(ImVec2(mPos.x - 20, pos.y + 11), ImVec2(mPos.x + 20, pos.y + (lines * lineHeight) + 10), IM_COL32( 255, 255, 255, 50 ));
 | 
			
		||||
        // window->DrawList->AddLine(ImVec2(mPos.x, pos.y + 11), ImVec2(mPos.x, pos.y + (lines * lineHeight) + 10), IM_COL32( 255, 0, 0, 255 ), 1.0f);
 | 
			
		||||
 | 
			
		||||
        float vfoPos = (((*vfo - centerFrequency) + (bandWidth / 2.0f)) / bandWidth) * (float)fftWidth + 40;
 | 
			
		||||
 | 
			
		||||
        if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && isInArea(mPos, ImVec2(pos.x + 40, pos.y + 10), ImVec2(pos.x + fftWidth + 40, pos.y + (lines * lineHeight) + 10))) {
 | 
			
		||||
            *vfo = (((((mPos.x - pos.x) - 40) / (float)fftWidth) * bandWidth) - (bandWidth / 2.0f)) + centerFrequency;
 | 
			
		||||
            //*vfo = roundf(*vfo / 100000.0f) * 100000.0f;
 | 
			
		||||
        }
 | 
			
		||||
        window->DrawList->AddRectFilled(ImVec2(pos.x + vfoPos - 20, pos.y + 11), ImVec2(pos.x + vfoPos + 20, pos.y + (lines * lineHeight) + 10), IM_COL32( 255, 255, 255, 50 ));
 | 
			
		||||
        window->DrawList->AddLine(ImVec2(pos.x + vfoPos, pos.y + 11), ImVec2(pos.x + vfoPos, pos.y + (lines * lineHeight) + 10), IM_COL32( 255, 0, 0, 255 ), 1.0f);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uint32_t mapColor(float val) {
 | 
			
		||||
        float mapped = MAP_VAL(-50.0f, -15.0f, 0, 12, val);
 | 
			
		||||
        mapped = std::max<float>(mapped, 0.0f);
 | 
			
		||||
        mapped = std::min<float>(mapped, 12.0f);
 | 
			
		||||
        int floored = floorf(mapped);
 | 
			
		||||
        float ratio = mapped - (float)floored;
 | 
			
		||||
 | 
			
		||||
        float r = ((COLOR_MAP[floored][2] * (1.0f - ratio)) + (COLOR_MAP[floored + 1][2] * ratio));
 | 
			
		||||
        float g = ((COLOR_MAP[floored][1] * (1.0f - ratio)) + (COLOR_MAP[floored + 1][1] * ratio));
 | 
			
		||||
        float b = ((COLOR_MAP[floored][0] * (1.0f - ratio)) + (COLOR_MAP[floored + 1][0] * ratio));
 | 
			
		||||
 | 
			
		||||
        //printf("%f %f %f\n", r, g, b);
 | 
			
		||||
 | 
			
		||||
        return ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::drawWaterfall(ImGuiWindow* window, int width, int height, ImVec2 pos) {
 | 
			
		||||
        int w = width - 10;
 | 
			
		||||
        int h = height;
 | 
			
		||||
        int count = fftBuffer.size();
 | 
			
		||||
        float factor = (float)count / (float)w;
 | 
			
		||||
        bool newSize = false;
 | 
			
		||||
 | 
			
		||||
        if (lastW != w || lastH != h) {
 | 
			
		||||
            
 | 
			
		||||
            newSize = true;
 | 
			
		||||
            lastW = w;
 | 
			
		||||
            lastH = h;
 | 
			
		||||
            if (img != NULL) {
 | 
			
		||||
                free(img);
 | 
			
		||||
            }
 | 
			
		||||
            printf("Allocating new buffer");
 | 
			
		||||
            img = (uint32_t*)malloc(w * h * sizeof(uint32_t));
 | 
			
		||||
            newSamples = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newSamples || newSize) {
 | 
			
		||||
            newSamples = false;
 | 
			
		||||
            float factor;
 | 
			
		||||
 | 
			
		||||
            if (newSize) {
 | 
			
		||||
                for (int y = 0; y < count; y++) {
 | 
			
		||||
                    factor = (float)fftBuffer[y].size() / (float)w;
 | 
			
		||||
                    for (int x = 0; x < w; x++) {
 | 
			
		||||
                        img[(y * w) + x] = mapColor(fftBuffer[y][(int)((float)x * factor)]);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                factor = (float)fftBuffer[0].size() / (float)w;
 | 
			
		||||
                memcpy(&img[w], img, (h - 1) * w * sizeof(uint32_t));
 | 
			
		||||
                for (int x = 0; x < w; x++) {
 | 
			
		||||
                    img[x] = mapColor(fftBuffer[0][(int)((float)x * factor)]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            glBindTexture(GL_TEXTURE_2D, textureId);
 | 
			
		||||
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 | 
			
		||||
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 | 
			
		||||
            glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
 | 
			
		||||
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
 | 
			
		||||
        }
 | 
			
		||||
        // X Axis
 | 
			
		||||
        window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 10), 
 | 
			
		||||
                                    ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10),
 | 
			
		||||
                                    IM_COL32(255, 255, 255, 255), 1.0f);
 | 
			
		||||
        // Y Axis
 | 
			
		||||
        window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + 10), 
 | 
			
		||||
                                    ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 10),
 | 
			
		||||
                                    IM_COL32(255, 255, 255, 255), 1.0f);
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        window->DrawList->AddImage((void*)(intptr_t)textureId, ImVec2(pos.x + 40, pos.y), ImVec2(pos.x + w, pos.y + h));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::draw(float* vfo) {
 | 
			
		||||
        ImGuiWindow* window = GetCurrentWindow();
 | 
			
		||||
        ImVec2 vMin = ImGui::GetWindowContentRegionMin();
 | 
			
		||||
        ImVec2 vMax = ImGui::GetWindowContentRegionMax();
 | 
			
		||||
        vMin.x += ImGui::GetWindowPos().x;
 | 
			
		||||
        vMin.y += ImGui::GetWindowPos().y;
 | 
			
		||||
        vMax.x += ImGui::GetWindowPos().x;
 | 
			
		||||
        vMax.y += ImGui::GetWindowPos().y;
 | 
			
		||||
        int width = vMax.x - vMin.x;
 | 
			
		||||
        int height = vMax.y - vMin.y;
 | 
			
		||||
        window->DrawList->AddRect( vMin, vMax, IM_COL32( 50, 50, 50, 255 ) );
 | 
			
		||||
 | 
			
		||||
        window->DrawList->AddLine(ImVec2(vMin.x, vMin.y + 300), ImVec2(vMin.x + width, vMin.y + 300), IM_COL32( 50, 50, 50, 255 ), 1.0f);
 | 
			
		||||
 | 
			
		||||
        buf_mtx.lock();
 | 
			
		||||
        if (fftBuffer.size() > height) {
 | 
			
		||||
            fftBuffer.resize(height - 302);
 | 
			
		||||
    void WaterFall::drawWaterfall() {
 | 
			
		||||
        if (waterfallUpdate) {
 | 
			
		||||
            waterfallUpdate = false;
 | 
			
		||||
            updateWaterfallTexture();
 | 
			
		||||
        }
 | 
			
		||||
        drawFFT(window, width, 300, vMin, vfo);
 | 
			
		||||
        drawWaterfall(window, width - 2, height - 302, ImVec2(vMin.x + 1, vMin.y + 301));
 | 
			
		||||
        window->DrawList->AddImage((void*)(intptr_t)textureId, ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 51),
 | 
			
		||||
                                ImVec2(widgetPos.x + 50 + dataWidth, widgetPos.y + fftHeight + 51 + waterfallHeight));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::updateWaterfallFb() {
 | 
			
		||||
        float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
 | 
			
		||||
        int drawDataSize;
 | 
			
		||||
        int drawDataStart;
 | 
			
		||||
        int count = std::min<int>(waterfallHeight, rawFFTs.size());
 | 
			
		||||
        float* tempData = new float[dataWidth];
 | 
			
		||||
        float pixel;
 | 
			
		||||
        float dataRange = waterfallMax - waterfallMin;
 | 
			
		||||
        for (int i = 0; i < count; i++) {
 | 
			
		||||
            drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTs[i].size();
 | 
			
		||||
            drawDataStart = (((float)rawFFTs[i].size() / 2.0f) * (offsetRatio + 1)) - (drawDataSize / 2);
 | 
			
		||||
            doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs[i], tempData);
 | 
			
		||||
            for (int j = 0; j < dataWidth; j++) {
 | 
			
		||||
                pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
 | 
			
		||||
                waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        delete[] tempData;
 | 
			
		||||
        waterfallUpdate = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::updateWaterfallTexture() {
 | 
			
		||||
        glBindTexture(GL_TEXTURE_2D, textureId);
 | 
			
		||||
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 | 
			
		||||
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 | 
			
		||||
        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
 | 
			
		||||
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dataWidth, waterfallHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)waterfallFb);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::onPositionChange() {
 | 
			
		||||
        printf("Pos changed\n");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::onResize() {
 | 
			
		||||
        printf("Resized\n");
 | 
			
		||||
        dataWidth = widgetSize.x - 60.0f;
 | 
			
		||||
        waterfallHeight = widgetSize.y - fftHeight - 52;
 | 
			
		||||
        delete[] latestFFT;
 | 
			
		||||
        delete[] waterfallFb;
 | 
			
		||||
        latestFFT = new float[dataWidth];
 | 
			
		||||
        waterfallFb = new uint32_t[dataWidth * waterfallHeight];
 | 
			
		||||
        for (int i = 0; i < dataWidth; i++) {
 | 
			
		||||
            latestFFT[i] = -1000.0f; // Hide everything
 | 
			
		||||
        }
 | 
			
		||||
        updateWaterfallFb();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::draw() {
 | 
			
		||||
        buf_mtx.lock();
 | 
			
		||||
        window = GetCurrentWindow();
 | 
			
		||||
        widgetPos = ImGui::GetWindowContentRegionMin();
 | 
			
		||||
        widgetEndPos = ImGui::GetWindowContentRegionMax();
 | 
			
		||||
        widgetPos.x += window->Pos.x;
 | 
			
		||||
        widgetPos.y += window->Pos.y;
 | 
			
		||||
        widgetEndPos.x += window->Pos.x;
 | 
			
		||||
        widgetEndPos.y += window->Pos.y;
 | 
			
		||||
        widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
 | 
			
		||||
 | 
			
		||||
        if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
 | 
			
		||||
            lastWidgetPos = widgetPos;
 | 
			
		||||
            onPositionChange();
 | 
			
		||||
        }
 | 
			
		||||
        if (widgetSize.x != lastWidgetSize.x || widgetSize.y != lastWidgetSize.y) {
 | 
			
		||||
            lastWidgetSize = widgetSize;
 | 
			
		||||
            onResize();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        window->DrawList->AddRect(widgetPos, widgetEndPos, IM_COL32( 50, 50, 50, 255 ));
 | 
			
		||||
        window->DrawList->AddLine(ImVec2(widgetPos.x, widgetPos.y + fftHeight + 50), ImVec2(widgetPos.x + widgetSize.x, widgetPos.y + fftHeight + 50), IM_COL32(50, 50, 50, 255), 1.0f);
 | 
			
		||||
 | 
			
		||||
        drawFFT();
 | 
			
		||||
        drawWaterfall();
 | 
			
		||||
        buf_mtx.unlock();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::pushFFT(std::vector<float> data, int n) {
 | 
			
		||||
        buf_mtx.lock();
 | 
			
		||||
        fftBuffer.insert(fftBuffer.begin(), data);
 | 
			
		||||
        newSamples = true;
 | 
			
		||||
        fftDrawBuffer = NULL;
 | 
			
		||||
        float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
 | 
			
		||||
        int drawDataSize = (viewBandwidth / wholeBandwidth) * data.size();
 | 
			
		||||
        int drawDataStart = (((float)data.size() / 2.0f) * (offsetRatio + 1)) - (drawDataSize / 2);
 | 
			
		||||
        doZoom(drawDataStart, drawDataSize, dataWidth, data, latestFFT);
 | 
			
		||||
        rawFFTs.insert(rawFFTs.begin(), data);
 | 
			
		||||
        if (rawFFTs.size() > waterfallHeight + 300) {
 | 
			
		||||
            rawFFTs.resize(waterfallHeight);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        memcpy(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
 | 
			
		||||
        float pixel;
 | 
			
		||||
        float dataRange = waterfallMax - waterfallMin;
 | 
			
		||||
        for (int j = 0; j < dataWidth; j++) {
 | 
			
		||||
            pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
 | 
			
		||||
            int id = (int)(pixel * (WATERFALL_RESOLUTION - 1));
 | 
			
		||||
            waterfallFb[j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
 | 
			
		||||
        }
 | 
			
		||||
        waterfallUpdate = true;
 | 
			
		||||
        buf_mtx.unlock();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::updatePallette(float colors[][3], int colorCount) {
 | 
			
		||||
        for (int i = 0; i < WATERFALL_RESOLUTION; i++) {
 | 
			
		||||
            int lowerId = floorf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
 | 
			
		||||
            int upperId = ceilf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
 | 
			
		||||
            float ratio = (((float)i / (float)WATERFALL_RESOLUTION) * colorCount) - lowerId;
 | 
			
		||||
            float r = (colors[lowerId][0] * (1.0f - ratio)) + (colors[upperId][0] * (ratio));
 | 
			
		||||
            float g = (colors[lowerId][1] * (1.0f - ratio)) + (colors[upperId][1] * (ratio));
 | 
			
		||||
            float b = (colors[lowerId][2] * (1.0f - ratio)) + (colors[upperId][2] * (ratio));
 | 
			
		||||
            waterfallPallet[i] = ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::autoRange() {
 | 
			
		||||
        float min = INFINITY;
 | 
			
		||||
        float max = -INFINITY;
 | 
			
		||||
        for (int i = 0; i < dataWidth; i++) {
 | 
			
		||||
            if (latestFFT[i] < min) {
 | 
			
		||||
                min = latestFFT[i];
 | 
			
		||||
            }
 | 
			
		||||
            if (latestFFT[i] > max) {
 | 
			
		||||
                max = latestFFT[i];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        fftMin = min - 5;
 | 
			
		||||
        fftMax = max + 5;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::setCenterFrequency(float freq) {
 | 
			
		||||
        centerFreq = freq;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    float WaterFall::getCenterFrequency() {
 | 
			
		||||
        return centerFreq;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::setBandwidth(float bandWidth) {
 | 
			
		||||
        float currentRatio = viewBandwidth / wholeBandwidth;
 | 
			
		||||
        wholeBandwidth = bandWidth;
 | 
			
		||||
        setViewBandwidth(bandWidth * currentRatio);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    float WaterFall::getBandwidth() {
 | 
			
		||||
        return wholeBandwidth;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::setVFOOffset(float offset) {
 | 
			
		||||
        vfoOffset = offset;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    float WaterFall::getVFOOfset() {
 | 
			
		||||
        return vfoOffset;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::setVFOBandwidth(float bandwidth) {
 | 
			
		||||
        vfoBandwidth = bandwidth;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    float WaterFall::getVFOBandwidth() {
 | 
			
		||||
        return vfoBandwidth;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::setViewBandwidth(float bandWidth) {
 | 
			
		||||
        if (bandWidth == viewBandwidth) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (abs(viewOffset) + (bandWidth / 2.0f) > wholeBandwidth / 2.0f) {
 | 
			
		||||
            if (viewOffset < 0) {
 | 
			
		||||
                viewOffset = (bandWidth / 2.0f) - (wholeBandwidth / 2.0f);
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                viewOffset = (wholeBandwidth / 2.0f) - (bandWidth / 2.0f);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        viewBandwidth = bandWidth;
 | 
			
		||||
        lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f);
 | 
			
		||||
        upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0f);
 | 
			
		||||
        range = findBestFreqRange(bandWidth);
 | 
			
		||||
        updateWaterfallFb();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::setViewOffset(float offset) {
 | 
			
		||||
        if (offset == viewOffset) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (abs(offset) + (viewBandwidth / 2.0f) > (wholeBandwidth / 2.0f)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        viewOffset = offset;
 | 
			
		||||
        lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f);
 | 
			
		||||
        upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0f);
 | 
			
		||||
        updateWaterfallFb();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    void WaterFall::setFFTMin(float min) {
 | 
			
		||||
        fftMin = min;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    float WaterFall::getFFTMin() {
 | 
			
		||||
        return fftMin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::setFFTMax(float max) {
 | 
			
		||||
        fftMax = max;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    float WaterFall::getFFTMax() {
 | 
			
		||||
        return fftMax;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::setWaterfallMin(float min) {
 | 
			
		||||
        if (min == waterfallMin) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        waterfallMin = min;
 | 
			
		||||
        updateWaterfallFb();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    float WaterFall::getWaterfallMin() {
 | 
			
		||||
        return waterfallMin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WaterFall::setWaterfallMax(float max) {
 | 
			
		||||
        if (max == waterfallMax) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        waterfallMax = max;
 | 
			
		||||
        updateWaterfallFb();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    float WaterFall::getWaterfallMax() {
 | 
			
		||||
        return waterfallMax;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										102
									
								
								src/waterfall.h
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								src/waterfall.h
									
									
									
									
									
								
							@@ -5,28 +5,106 @@
 | 
			
		||||
#include <mutex>
 | 
			
		||||
#include <GL/glew.h>
 | 
			
		||||
 | 
			
		||||
#define WATERFALL_RESOLUTION    1000000
 | 
			
		||||
 | 
			
		||||
namespace ImGui {
 | 
			
		||||
 | 
			
		||||
    class WaterFall {
 | 
			
		||||
    public:
 | 
			
		||||
        WaterFall();
 | 
			
		||||
 | 
			
		||||
        void draw(float* vfo);
 | 
			
		||||
        void draw();
 | 
			
		||||
        void pushFFT(std::vector<float> data, int n);
 | 
			
		||||
 | 
			
		||||
        float centerFrequency;
 | 
			
		||||
        float bandWidth;
 | 
			
		||||
        float range;
 | 
			
		||||
        void updatePallette(float colors[][3], int colorCount);
 | 
			
		||||
 | 
			
		||||
        void setCenterFrequency(float freq);
 | 
			
		||||
        float getCenterFrequency();
 | 
			
		||||
 | 
			
		||||
        void setBandwidth(float bandWidth);
 | 
			
		||||
        float getBandwidth();
 | 
			
		||||
 | 
			
		||||
        void setVFOOffset(float offset);
 | 
			
		||||
        float getVFOOfset();
 | 
			
		||||
 | 
			
		||||
        void setVFOBandwidth(float bandwidth);
 | 
			
		||||
        float getVFOBandwidth();
 | 
			
		||||
 | 
			
		||||
        void setViewBandwidth(float bandWidth);
 | 
			
		||||
        void setViewOffset(float offset);
 | 
			
		||||
 | 
			
		||||
        void setFFTMin(float min);
 | 
			
		||||
        float getFFTMin();
 | 
			
		||||
 | 
			
		||||
        void setFFTMax(float max);
 | 
			
		||||
        float getFFTMax();
 | 
			
		||||
 | 
			
		||||
        void setWaterfallMin(float min);
 | 
			
		||||
        float getWaterfallMin();
 | 
			
		||||
 | 
			
		||||
        void setWaterfallMax(float max);
 | 
			
		||||
        float getWaterfallMax();
 | 
			
		||||
 | 
			
		||||
        void setZoom(float zoomLevel);
 | 
			
		||||
        void setOffset(float zoomOffset);
 | 
			
		||||
 | 
			
		||||
        void autoRange();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        void drawWaterfall(ImGuiWindow* window, int width, int height, ImVec2 pos);
 | 
			
		||||
        void drawFFT(ImGuiWindow* window, int width, int height, ImVec2 pos, float* vfo);
 | 
			
		||||
        void drawWaterfall();
 | 
			
		||||
        void drawFFT();
 | 
			
		||||
        void onPositionChange();
 | 
			
		||||
        void onResize();
 | 
			
		||||
        void updateWaterfallFb();
 | 
			
		||||
        void updateWaterfallTexture();
 | 
			
		||||
 | 
			
		||||
        std::vector<std::vector<float>> fftBuffer;
 | 
			
		||||
        bool newSamples;
 | 
			
		||||
        std::mutex buf_mtx;
 | 
			
		||||
        GLuint textureId;
 | 
			
		||||
        uint8_t* pixelBuffer;
 | 
			
		||||
        float* fftDrawBuffer;
 | 
			
		||||
        bool waterfallUpdate = false;
 | 
			
		||||
 | 
			
		||||
        uint32_t waterfallPallet[WATERFALL_RESOLUTION];
 | 
			
		||||
        
 | 
			
		||||
        ImVec2 widgetPos;
 | 
			
		||||
        ImVec2 widgetEndPos;
 | 
			
		||||
        ImVec2 widgetSize;
 | 
			
		||||
 | 
			
		||||
        ImVec2 lastWidgetPos;
 | 
			
		||||
        ImVec2 lastWidgetSize;
 | 
			
		||||
 | 
			
		||||
        ImGuiWindow* window;
 | 
			
		||||
 | 
			
		||||
        GLuint textureId;
 | 
			
		||||
 | 
			
		||||
        std::mutex buf_mtx;
 | 
			
		||||
 | 
			
		||||
        int dataWidth;          // Width of the FFT and waterfall
 | 
			
		||||
        int fftHeight;          // Height of the fft graph
 | 
			
		||||
        int waterfallHeight;    // Height of the waterfall
 | 
			
		||||
 | 
			
		||||
        float viewBandwidth;
 | 
			
		||||
        float viewOffset;
 | 
			
		||||
 | 
			
		||||
        float lowerFreq;
 | 
			
		||||
        float upperFreq;
 | 
			
		||||
        float range;
 | 
			
		||||
 | 
			
		||||
        // Absolute values
 | 
			
		||||
        float centerFreq;
 | 
			
		||||
        float wholeBandwidth;
 | 
			
		||||
 | 
			
		||||
        // VFO
 | 
			
		||||
        float vfoOffset;
 | 
			
		||||
        float vfoBandwidth;
 | 
			
		||||
 | 
			
		||||
        // Ranges
 | 
			
		||||
        float fftMin;
 | 
			
		||||
        float fftMax;
 | 
			
		||||
        float waterfallMin;
 | 
			
		||||
        float waterfallMax;
 | 
			
		||||
 | 
			
		||||
        std::vector<std::vector<float>> rawFFTs;
 | 
			
		||||
        float* latestFFT;
 | 
			
		||||
 | 
			
		||||
        uint32_t* waterfallFb;
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user