mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-11-10 04:37:37 +01:00
Rewrote the waterfall
This commit is contained in:
parent
30f1b423a6
commit
370324bc68
@ -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);
|
||||
// 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->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() {
|
||||
if (waterfallUpdate) {
|
||||
waterfallUpdate = false;
|
||||
updateWaterfallTexture();
|
||||
}
|
||||
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::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);
|
||||
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))];
|
||||
}
|
||||
printf("Allocating new buffer");
|
||||
img = (uint32_t*)malloc(w * h * sizeof(uint32_t));
|
||||
newSamples = true;
|
||||
}
|
||||
delete[] tempData;
|
||||
waterfallUpdate = 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)]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dataWidth, waterfallHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)waterfallFb);
|
||||
}
|
||||
|
||||
|
||||
|
||||
window->DrawList->AddImage((void*)(intptr_t)textureId, ImVec2(pos.x + 40, pos.y), ImVec2(pos.x + w, pos.y + h));
|
||||
void WaterFall::onPositionChange() {
|
||||
printf("Pos changed\n");
|
||||
}
|
||||
|
||||
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);
|
||||
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();
|
||||
if (fftBuffer.size() > height) {
|
||||
fftBuffer.resize(height - 302);
|
||||
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();
|
||||
}
|
||||
drawFFT(window, width, 300, vMin, vfo);
|
||||
drawWaterfall(window, width - 2, height - 302, ImVec2(vMin.x + 1, vMin.y + 301));
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
100
src/waterfall.h
100
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();
|
||||
|
||||
bool waterfallUpdate = false;
|
||||
|
||||
uint32_t waterfallPallet[WATERFALL_RESOLUTION];
|
||||
|
||||
ImVec2 widgetPos;
|
||||
ImVec2 widgetEndPos;
|
||||
ImVec2 widgetSize;
|
||||
|
||||
ImVec2 lastWidgetPos;
|
||||
ImVec2 lastWidgetSize;
|
||||
|
||||
ImGuiWindow* window;
|
||||
|
||||
std::vector<std::vector<float>> fftBuffer;
|
||||
bool newSamples;
|
||||
std::mutex buf_mtx;
|
||||
GLuint textureId;
|
||||
uint8_t* pixelBuffer;
|
||||
float* fftDrawBuffer;
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user