diff --git a/src/dsp/filter.h b/src/dsp/filter.h index d8fb76d6..19ac22dd 100644 --- a/src/dsp/filter.h +++ b/src/dsp/filter.h @@ -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; diff --git a/src/dsp/legacy_stream.h b/src/dsp/legacy_stream.h index 69d100bb..92381cb8 100644 --- a/src/dsp/legacy_stream.h +++ b/src/dsp/legacy_stream.h @@ -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; } diff --git a/src/dsp/resampling.h b/src/dsp/resampling.h index eb53dffa..6b3f7e9f 100644 --- a/src/dsp/resampling.h +++ b/src/dsp/resampling.h @@ -346,8 +346,7 @@ namespace dsp { return; } interp.stop(); - //decim.stop(); - Sleep(200); + decim.stop(); running = false; } diff --git a/src/dsp/vfo.h b/src/dsp/vfo.h index 9b9c9ef5..81abfc5f 100644 --- a/src/dsp/vfo.h +++ b/src/dsp/vfo.h @@ -39,7 +39,6 @@ namespace dsp { lo.start(); mixer.start(); if (_interp != 1) { - printf("UH OH INTERPOLATOR STARTED :/\n"); interp.start(); } decim.start(); diff --git a/src/main_window.cpp b/src/main_window.cpp index 03082265..38486d64 100644 --- a/src/main_window.cpp +++ b/src/main_window.cpp @@ -25,7 +25,6 @@ bool dcbias = true; io::SoapyWrapper soapy; -//dsp::HackRFSource src; SignalPath sigPath; std::vector _data; std::vector 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(); } \ No newline at end of file diff --git a/src/signal_path.cpp b/src/signal_path.cpp index 5e8eed34..aa933d4e 100644 --- a/src/signal_path.cpp +++ b/src/signal_path.cpp @@ -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(); } diff --git a/src/waterfall.cpp b/src/waterfall.cpp index 8bc16766..638060b4 100644 --- a/src/waterfall.cpp +++ b/src/waterfall.cpp @@ -1,231 +1,397 @@ #include #include -#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 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 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(aPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10); + bPos = std::clamp(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(mapped, 0.0f); - mapped = std::min(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(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(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 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(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; + } }; diff --git a/src/waterfall.h b/src/waterfall.h index 619a310f..77890da6 100644 --- a/src/waterfall.h +++ b/src/waterfall.h @@ -5,28 +5,106 @@ #include #include +#define WATERFALL_RESOLUTION 1000000 + namespace ImGui { + class WaterFall { public: WaterFall(); - void draw(float* vfo); + void draw(); void pushFFT(std::vector 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> 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> rawFFTs; + float* latestFFT; + + uint32_t* waterfallFb; + }; }; \ No newline at end of file