mirror of
https://github.com/cjcliffe/CubicSDR.git
synced 2024-11-23 12:18:37 -05:00
Merge branch 'vsonnier-vso_waterfall_fixes_and_improvements'
This commit is contained in:
commit
e181849a1d
@ -6,8 +6,8 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/")
|
|||||||
|
|
||||||
SET(CUBICSDR_VERSION_MAJOR "0")
|
SET(CUBICSDR_VERSION_MAJOR "0")
|
||||||
SET(CUBICSDR_VERSION_MINOR "2")
|
SET(CUBICSDR_VERSION_MINOR "2")
|
||||||
SET(CUBICSDR_VERSION_PATCH "1")
|
SET(CUBICSDR_VERSION_PATCH "2")
|
||||||
SET(CUBICSDR_VERSION_SUFFIX "-alpha-bookmark1")
|
SET(CUBICSDR_VERSION_SUFFIX "-alpha")
|
||||||
SET(CUBICSDR_VERSION "${CUBICSDR_VERSION_MAJOR}.${CUBICSDR_VERSION_MINOR}.${CUBICSDR_VERSION_PATCH}${CUBICSDR_VERSION_SUFFIX}")
|
SET(CUBICSDR_VERSION "${CUBICSDR_VERSION_MAJOR}.${CUBICSDR_VERSION_MINOR}.${CUBICSDR_VERSION_PATCH}${CUBICSDR_VERSION_SUFFIX}")
|
||||||
|
|
||||||
SET(CPACK_PACKAGE_VERSION "${CUBICSDR_VERSION_MAJOR}.${CUBICSDR_VERSION_MINOR}.${CUBICSDR_VERSION_PATCH}")
|
SET(CPACK_PACKAGE_VERSION "${CUBICSDR_VERSION_MAJOR}.${CUBICSDR_VERSION_MINOR}.${CUBICSDR_VERSION_PATCH}")
|
||||||
|
@ -164,7 +164,7 @@ AppFrame::AppFrame() :
|
|||||||
#if CUBICSDR_ENABLE_VIEW_DEMOD
|
#if CUBICSDR_ENABLE_VIEW_DEMOD
|
||||||
wxBoxSizer *demodVisuals = new wxBoxSizer(wxVERTICAL);
|
wxBoxSizer *demodVisuals = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
wxGetApp().getDemodSpectrumProcessor()->setup(1024);
|
wxGetApp().getDemodSpectrumProcessor()->setup(DEFAULT_DMOD_FFT_SIZE);
|
||||||
demodSpectrumCanvas = new SpectrumCanvas(demodPanel, attribList);
|
demodSpectrumCanvas = new SpectrumCanvas(demodPanel, attribList);
|
||||||
demodSpectrumCanvas->setView(wxGetApp().getConfig()->getCenterFreq(), 300000);
|
demodSpectrumCanvas->setView(wxGetApp().getConfig()->getCenterFreq(), 300000);
|
||||||
demodVisuals->Add(demodSpectrumCanvas, 3, wxEXPAND | wxALL, 0);
|
demodVisuals->Add(demodSpectrumCanvas, 3, wxEXPAND | wxALL, 0);
|
||||||
@ -173,7 +173,7 @@ AppFrame::AppFrame() :
|
|||||||
demodVisuals->AddSpacer(1);
|
demodVisuals->AddSpacer(1);
|
||||||
|
|
||||||
demodWaterfallCanvas = new WaterfallCanvas(demodPanel, attribList);
|
demodWaterfallCanvas = new WaterfallCanvas(demodPanel, attribList);
|
||||||
demodWaterfallCanvas->setup(1024, 128);
|
demodWaterfallCanvas->setup(DEFAULT_DMOD_FFT_SIZE, DEFAULT_DEMOD_WATERFALL_LINES_NB);
|
||||||
demodWaterfallCanvas->setView(wxGetApp().getConfig()->getCenterFreq(), 300000);
|
demodWaterfallCanvas->setView(wxGetApp().getConfig()->getCenterFreq(), 300000);
|
||||||
demodWaterfallCanvas->attachSpectrumCanvas(demodSpectrumCanvas);
|
demodWaterfallCanvas->attachSpectrumCanvas(demodSpectrumCanvas);
|
||||||
demodWaterfallCanvas->setMinBandwidth(8000);
|
demodWaterfallCanvas->setMinBandwidth(8000);
|
||||||
@ -181,6 +181,8 @@ AppFrame::AppFrame() :
|
|||||||
demodVisuals->Add(demodWaterfallCanvas, 6, wxEXPAND | wxALL, 0);
|
demodVisuals->Add(demodWaterfallCanvas, 6, wxEXPAND | wxALL, 0);
|
||||||
wxGetApp().getDemodSpectrumProcessor()->attachOutput(demodWaterfallCanvas->getVisualDataQueue());
|
wxGetApp().getDemodSpectrumProcessor()->attachOutput(demodWaterfallCanvas->getVisualDataQueue());
|
||||||
demodWaterfallCanvas->getVisualDataQueue()->set_max_num_items(3);
|
demodWaterfallCanvas->getVisualDataQueue()->set_max_num_items(3);
|
||||||
|
demodWaterfallCanvas->setLinesPerSecond((int)(DEFAULT_DEMOD_WATERFALL_LINES_NB / DEMOD_WATERFALL_DURATION_IN_SECONDS));
|
||||||
|
|
||||||
|
|
||||||
demodVisuals->SetMinSize(wxSize(128,-1));
|
demodVisuals->SetMinSize(wxSize(128,-1));
|
||||||
|
|
||||||
@ -209,7 +211,7 @@ AppFrame::AppFrame() :
|
|||||||
scopeCanvas->setHelpTip("Audio Visuals, drag left/right to toggle Scope or Spectrum, 'B' to toggle decibels display.");
|
scopeCanvas->setHelpTip("Audio Visuals, drag left/right to toggle Scope or Spectrum, 'B' to toggle decibels display.");
|
||||||
scopeCanvas->SetMinSize(wxSize(128,-1));
|
scopeCanvas->SetMinSize(wxSize(128,-1));
|
||||||
demodScopeTray->Add(scopeCanvas, 8, wxEXPAND | wxALL, 0);
|
demodScopeTray->Add(scopeCanvas, 8, wxEXPAND | wxALL, 0);
|
||||||
wxGetApp().getScopeProcessor()->setup(1024);
|
wxGetApp().getScopeProcessor()->setup(DEFAULT_SCOPE_FFT_SIZE);
|
||||||
wxGetApp().getScopeProcessor()->attachOutput(scopeCanvas->getInputQueue());
|
wxGetApp().getScopeProcessor()->attachOutput(scopeCanvas->getInputQueue());
|
||||||
|
|
||||||
demodScopeTray->AddSpacer(1);
|
demodScopeTray->AddSpacer(1);
|
||||||
@ -293,7 +295,7 @@ AppFrame::AppFrame() :
|
|||||||
wxPanel *spectrumPanel = new wxPanel(mainVisSplitter, wxID_ANY);
|
wxPanel *spectrumPanel = new wxPanel(mainVisSplitter, wxID_ANY);
|
||||||
wxBoxSizer *spectrumSizer = new wxBoxSizer(wxHORIZONTAL);
|
wxBoxSizer *spectrumSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
|
||||||
wxGetApp().getSpectrumProcessor()->setup(2048);
|
wxGetApp().getSpectrumProcessor()->setup(DEFAULT_FFT_SIZE);
|
||||||
spectrumCanvas = new SpectrumCanvas(spectrumPanel, attribList);
|
spectrumCanvas = new SpectrumCanvas(spectrumPanel, attribList);
|
||||||
spectrumCanvas->setShowDb(true);
|
spectrumCanvas->setShowDb(true);
|
||||||
spectrumCanvas->setUseDBOfs(true);
|
spectrumCanvas->setUseDBOfs(true);
|
||||||
@ -336,7 +338,7 @@ AppFrame::AppFrame() :
|
|||||||
wxBoxSizer *wfSizer = new wxBoxSizer(wxHORIZONTAL);
|
wxBoxSizer *wfSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
|
||||||
waterfallCanvas = new WaterfallCanvas(waterfallPanel, attribList);
|
waterfallCanvas = new WaterfallCanvas(waterfallPanel, attribList);
|
||||||
waterfallCanvas->setup(2048, 512);
|
waterfallCanvas->setup(DEFAULT_FFT_SIZE, DEFAULT_MAIN_WATERFALL_LINES_NB);
|
||||||
|
|
||||||
waterfallDataThread = new FFTVisualDataThread();
|
waterfallDataThread = new FFTVisualDataThread();
|
||||||
|
|
||||||
@ -1025,17 +1027,6 @@ void AppFrame::OnMenu(wxCommandEvent& event) {
|
|||||||
lowPerfMode = lowPerfMenuItem->IsChecked();
|
lowPerfMode = lowPerfMenuItem->IsChecked();
|
||||||
wxGetApp().getConfig()->setLowPerfMode(lowPerfMode);
|
wxGetApp().getConfig()->setLowPerfMode(lowPerfMode);
|
||||||
|
|
||||||
// long srate = wxGetApp().getSampleRate();
|
|
||||||
// if (srate > CHANNELIZER_RATE_MAX && lowPerfMode) {
|
|
||||||
// if (wxGetApp().getSpectrumProcessor()->getFFTSize() != 1024) {
|
|
||||||
// setMainWaterfallFFTSize(1024);
|
|
||||||
// }
|
|
||||||
// } else if (srate > CHANNELIZER_RATE_MAX) {
|
|
||||||
// if (wxGetApp().getSpectrumProcessor()->getFFTSize() != 2048) {
|
|
||||||
// setMainWaterfallFFTSize(2048);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
} else if (event.GetId() == wxID_SET_TIPS ) {
|
} else if (event.GetId() == wxID_SET_TIPS ) {
|
||||||
if (wxGetApp().getConfig()->getShowTips()) {
|
if (wxGetApp().getConfig()->getShowTips()) {
|
||||||
wxGetApp().getConfig()->setShowTips(false);
|
wxGetApp().getConfig()->setShowTips(false);
|
||||||
|
@ -635,15 +635,15 @@ void CubicSDR::setSampleRate(long long rate_in) {
|
|||||||
setFrequency(frequency);
|
setFrequency(frequency);
|
||||||
|
|
||||||
if (rate_in <= CHANNELIZER_RATE_MAX / 8) {
|
if (rate_in <= CHANNELIZER_RATE_MAX / 8) {
|
||||||
appframe->setMainWaterfallFFTSize(512);
|
appframe->setMainWaterfallFFTSize(DEFAULT_FFT_SIZE / 4);
|
||||||
appframe->getWaterfallDataThread()->getProcessor()->setHideDC(false);
|
appframe->getWaterfallDataThread()->getProcessor()->setHideDC(false);
|
||||||
spectrumVisualThread->getProcessor()->setHideDC(false);
|
spectrumVisualThread->getProcessor()->setHideDC(false);
|
||||||
} else if (rate_in <= CHANNELIZER_RATE_MAX) {
|
} else if (rate_in <= CHANNELIZER_RATE_MAX) {
|
||||||
appframe->setMainWaterfallFFTSize(1024);
|
appframe->setMainWaterfallFFTSize(DEFAULT_FFT_SIZE / 2);
|
||||||
appframe->getWaterfallDataThread()->getProcessor()->setHideDC(false);
|
appframe->getWaterfallDataThread()->getProcessor()->setHideDC(false);
|
||||||
spectrumVisualThread->getProcessor()->setHideDC(false);
|
spectrumVisualThread->getProcessor()->setHideDC(false);
|
||||||
} else if (rate_in > CHANNELIZER_RATE_MAX) {
|
} else if (rate_in > CHANNELIZER_RATE_MAX) {
|
||||||
appframe->setMainWaterfallFFTSize(2048);
|
appframe->setMainWaterfallFFTSize(DEFAULT_FFT_SIZE);
|
||||||
appframe->getWaterfallDataThread()->getProcessor()->setHideDC(true);
|
appframe->getWaterfallDataThread()->getProcessor()->setHideDC(true);
|
||||||
spectrumVisualThread->getProcessor()->setHideDC(true);
|
spectrumVisualThread->getProcessor()->setHideDC(true);
|
||||||
}
|
}
|
||||||
|
@ -31,15 +31,31 @@ const char filePathSeparator =
|
|||||||
#define BUF_SIZE (16384*6)
|
#define BUF_SIZE (16384*6)
|
||||||
|
|
||||||
#define DEFAULT_SAMPLE_RATE 2500000
|
#define DEFAULT_SAMPLE_RATE 2500000
|
||||||
|
|
||||||
|
//
|
||||||
#define DEFAULT_FFT_SIZE 2048
|
#define DEFAULT_FFT_SIZE 2048
|
||||||
|
#define DEFAULT_DMOD_FFT_SIZE (DEFAULT_FFT_SIZE / 2)
|
||||||
|
#define DEFAULT_SCOPE_FFT_SIZE (DEFAULT_FFT_SIZE / 2)
|
||||||
|
|
||||||
|
//Both must be a power of 2 to prevent terrible OpenGL performance.
|
||||||
|
//TODO: Make the waterfall resolutions an option.
|
||||||
|
#define DEFAULT_MAIN_WATERFALL_LINES_NB 512 // 1024
|
||||||
|
#define DEFAULT_DEMOD_WATERFALL_LINES_NB 256
|
||||||
|
|
||||||
#define DEFAULT_DEMOD_TYPE "FM"
|
#define DEFAULT_DEMOD_TYPE "FM"
|
||||||
#define DEFAULT_DEMOD_BW 200000
|
#define DEFAULT_DEMOD_BW 200000
|
||||||
|
|
||||||
#define DEFAULT_WATERFALL_LPS 30
|
#define DEFAULT_WATERFALL_LPS 30
|
||||||
|
|
||||||
|
//Dmod waterfall lines per second is adjusted
|
||||||
|
//so that the whole demod waterfall show DEMOD_WATERFALL_DURATION_IN_SECONDS
|
||||||
|
//seconds.
|
||||||
|
#define DEMOD_WATERFALL_DURATION_IN_SECONDS 4.0
|
||||||
|
|
||||||
#define CHANNELIZER_RATE_MAX 500000
|
#define CHANNELIZER_RATE_MAX 500000
|
||||||
|
|
||||||
#define MANUAL_SAMPLE_RATE_MIN 2000000 // 2MHz
|
#define MANUAL_SAMPLE_RATE_MIN 2000000 // 2MHz
|
||||||
#define MANUAL_SAMPLE_RATE_MAX 200000000 // 200MHz (We are 2017+ after all)
|
#define MANUAL_SAMPLE_RATE_MAX 200000000 // 200MHz (We are 2017+ after all)
|
||||||
|
|
||||||
|
//Represents the amount of time to process in the FFT distributor.
|
||||||
|
#define FFT_DISTRIBUTOR_BUFFER_IN_SECONDS 0.250
|
||||||
|
@ -59,6 +59,7 @@ class ModemKit;
|
|||||||
class DemodulatorThreadPostIQData: public ReferenceCounter {
|
class DemodulatorThreadPostIQData: public ReferenceCounter {
|
||||||
public:
|
public:
|
||||||
std::vector<liquid_float_complex> data;
|
std::vector<liquid_float_complex> data;
|
||||||
|
|
||||||
long long sampleRate;
|
long long sampleRate;
|
||||||
std::string modemName;
|
std::string modemName;
|
||||||
std::string modemType;
|
std::string modemType;
|
||||||
|
@ -39,6 +39,7 @@ void WaterfallPanel::refreshTheme() {
|
|||||||
void WaterfallPanel::setPoints(std::vector<float> &points) {
|
void WaterfallPanel::setPoints(std::vector<float> &points) {
|
||||||
size_t halfPts = points.size()/2;
|
size_t halfPts = points.size()/2;
|
||||||
if (halfPts == fft_size) {
|
if (halfPts == fft_size) {
|
||||||
|
|
||||||
for (unsigned int i = 0; i < fft_size; i++) {
|
for (unsigned int i = 0; i < fft_size; i++) {
|
||||||
this->points[i] = points[i*2+1];
|
this->points[i] = points[i*2+1];
|
||||||
}
|
}
|
||||||
@ -102,6 +103,10 @@ void WaterfallPanel::update() {
|
|||||||
|
|
||||||
unsigned char *waterfall_tex;
|
unsigned char *waterfall_tex;
|
||||||
|
|
||||||
|
//Creates 2x 2D textures into card memory.
|
||||||
|
//of size half_fft_size * waterfall_lines, which can be BIG.
|
||||||
|
//The limit of the size of Waterfall is the size of the maximum supported 2D texture
|
||||||
|
//by the graphic card. (half_fft_size * waterfall_lines, i.e DEFAULT_DEMOD_WATERFALL_LINES_NB * DEFAULT_FFT_SIZE/2)
|
||||||
waterfall_tex = new unsigned char[half_fft_size * waterfall_lines];
|
waterfall_tex = new unsigned char[half_fft_size * waterfall_lines];
|
||||||
memset(waterfall_tex, 0, half_fft_size * waterfall_lines);
|
memset(waterfall_tex, 0, half_fft_size * waterfall_lines);
|
||||||
|
|
||||||
|
@ -2,13 +2,15 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0+
|
// SPDX-License-Identifier: GPL-2.0+
|
||||||
|
|
||||||
#include "FFTDataDistributor.h"
|
#include "FFTDataDistributor.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
FFTDataDistributor::FFTDataDistributor() : outputBuffers("FFTDataDistributorBuffers"), fftSize(DEFAULT_FFT_SIZE), linesPerSecond(DEFAULT_WATERFALL_LPS), lineRateAccum(0.0) {
|
FFTDataDistributor::FFTDataDistributor() : outputBuffers("FFTDataDistributorBuffers"), fftSize(DEFAULT_FFT_SIZE), linesPerSecond(DEFAULT_WATERFALL_LPS), lineRateAccum(0.0) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFTDataDistributor::setFFTSize(unsigned int fftSize) {
|
void FFTDataDistributor::setFFTSize(unsigned int size) {
|
||||||
this->fftSize = fftSize;
|
|
||||||
|
fftSize.store(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFTDataDistributor::setLinesPerSecond(unsigned int lines) {
|
void FFTDataDistributor::setLinesPerSecond(unsigned int lines) {
|
||||||
@ -29,25 +31,50 @@ void FFTDataDistributor::process() {
|
|||||||
input->pop(inp);
|
input->pop(inp);
|
||||||
|
|
||||||
if (inp) {
|
if (inp) {
|
||||||
|
//Settings have changed, set new values and dump all previous samples stored in inputBuffer:
|
||||||
if (inputBuffer.sampleRate != inp->sampleRate || inputBuffer.frequency != inp->frequency) {
|
if (inputBuffer.sampleRate != inp->sampleRate || inputBuffer.frequency != inp->frequency) {
|
||||||
|
|
||||||
bufferMax = inp->sampleRate / 4;
|
//bufferMax must be at least fftSize (+ margin), else the waterfall get frozen, because no longer updated.
|
||||||
|
bufferMax = std::max((size_t)(inp->sampleRate * FFT_DISTRIBUTOR_BUFFER_IN_SECONDS), (size_t)(1.2 * fftSize.load()));
|
||||||
|
|
||||||
// std::cout << "Buffer Max: " << bufferMax << std::endl;
|
// std::cout << "Buffer Max: " << bufferMax << std::endl;
|
||||||
bufferOffset = 0;
|
bufferOffset = 0;
|
||||||
|
bufferedItems = 0;
|
||||||
inputBuffer.sampleRate = inp->sampleRate;
|
inputBuffer.sampleRate = inp->sampleRate;
|
||||||
inputBuffer.frequency = inp->frequency;
|
inputBuffer.frequency = inp->frequency;
|
||||||
inputBuffer.data.resize(bufferMax);
|
inputBuffer.data.resize(bufferMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//adjust (bufferMax ; inputBuffer.data) in case of FFT size change only.
|
||||||
|
if (bufferMax < (size_t)(1.2 * fftSize.load())) {
|
||||||
|
bufferMax = (size_t)(1.2 * fftSize.load());
|
||||||
|
inputBuffer.data.resize(bufferMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t nbSamplesToAdd = inp->data.size();
|
||||||
|
|
||||||
|
//No room left in inputBuffer.data to accept inp->data.size() more samples.
|
||||||
|
//so make room by sliding left of bufferOffset, which is fine because
|
||||||
|
//those samples has already been processed.
|
||||||
if ((bufferOffset + bufferedItems + inp->data.size()) > bufferMax) {
|
if ((bufferOffset + bufferedItems + inp->data.size()) > bufferMax) {
|
||||||
memmove(&inputBuffer.data[0], &inputBuffer.data[bufferOffset], bufferedItems*sizeof(liquid_float_complex));
|
memmove(&inputBuffer.data[0], &inputBuffer.data[bufferOffset], bufferedItems*sizeof(liquid_float_complex));
|
||||||
bufferOffset = 0;
|
bufferOffset = 0;
|
||||||
} else {
|
//if there are too much samples, we may even overflow !
|
||||||
memcpy(&inputBuffer.data[bufferOffset+bufferedItems],&inp->data[0],inp->data.size()*sizeof(liquid_float_complex));
|
//as a fallback strategy, drop the last incomming new samples not fitting in inputBuffer.data.
|
||||||
bufferedItems += inp->data.size();
|
if (bufferedItems + inp->data.size() > bufferMax) {
|
||||||
|
//clamp nbSamplesToAdd
|
||||||
|
nbSamplesToAdd = bufferMax - bufferedItems;
|
||||||
|
std::cout << "FFTDataDistributor::process() incoming samples overflow, dropping the last " << (inp->data.size() - nbSamplesToAdd) << " input samples..." << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//store nbSamplesToAdd incoming samples.
|
||||||
|
memcpy(&inputBuffer.data[bufferOffset+bufferedItems],&inp->data[0], nbSamplesToAdd *sizeof(liquid_float_complex));
|
||||||
|
bufferedItems += nbSamplesToAdd;
|
||||||
|
//
|
||||||
inp->decRefCount();
|
inp->decRefCount();
|
||||||
} else {
|
} else {
|
||||||
|
//empty inp, wait for another.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,12 +83,14 @@ void FFTDataDistributor::process() {
|
|||||||
// number of lines in input
|
// number of lines in input
|
||||||
double inputLines = (double)bufferedItems / (double)fftSize;
|
double inputLines = (double)bufferedItems / (double)fftSize;
|
||||||
|
|
||||||
// ratio required to achieve the desired rate
|
// ratio required to achieve the desired rate:
|
||||||
|
// it means we can achieive 'lineRateStep' times the target linesPerSecond.
|
||||||
|
// < 1 means we cannot reach it by lack of samples.
|
||||||
double lineRateStep = ((double)linesPerSecond * inputTime)/(double)inputLines;
|
double lineRateStep = ((double)linesPerSecond * inputTime)/(double)inputLines;
|
||||||
|
|
||||||
|
//we have enough samples to FFT at least one 'line' of 'fftSize' frequencies for display:
|
||||||
if (bufferedItems >= fftSize) {
|
if (bufferedItems >= fftSize) {
|
||||||
size_t numProcessed = 0;
|
size_t numProcessed = 0;
|
||||||
|
|
||||||
if (lineRateAccum + (lineRateStep * ((double)bufferedItems/(double)fftSize)) < 1.0) {
|
if (lineRateAccum + (lineRateStep * ((double)bufferedItems/(double)fftSize)) < 1.0) {
|
||||||
// move along, nothing to see here..
|
// move along, nothing to see here..
|
||||||
lineRateAccum += (lineRateStep * ((double)bufferedItems/(double)fftSize));
|
lineRateAccum += (lineRateStep * ((double)bufferedItems/(double)fftSize));
|
||||||
@ -74,10 +103,12 @@ void FFTDataDistributor::process() {
|
|||||||
lineRateAccum += lineRateStep;
|
lineRateAccum += lineRateStep;
|
||||||
|
|
||||||
if (lineRateAccum >= 1.0) {
|
if (lineRateAccum >= 1.0) {
|
||||||
|
//each i represents a FFT computation
|
||||||
DemodulatorThreadIQData *outp = outputBuffers.getBuffer();
|
DemodulatorThreadIQData *outp = outputBuffers.getBuffer();
|
||||||
outp->frequency = inputBuffer.frequency;
|
outp->frequency = inputBuffer.frequency;
|
||||||
outp->sampleRate = inputBuffer.sampleRate;
|
outp->sampleRate = inputBuffer.sampleRate;
|
||||||
outp->data.assign(inputBuffer.data.begin()+bufferOffset+i,inputBuffer.data.begin()+bufferOffset+i+fftSize);
|
outp->data.assign(inputBuffer.data.begin()+bufferOffset+i,
|
||||||
|
inputBuffer.data.begin()+bufferOffset+i+ fftSize);
|
||||||
distribute(outp);
|
distribute(outp);
|
||||||
|
|
||||||
while (lineRateAccum >= 1.0) {
|
while (lineRateAccum >= 1.0) {
|
||||||
@ -86,16 +117,19 @@ void FFTDataDistributor::process() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
numProcessed += fftSize;
|
numProcessed += fftSize;
|
||||||
}
|
} //end for
|
||||||
}
|
}
|
||||||
|
//advance bufferOffset read pointer,
|
||||||
|
//reduce size of bufferedItems.
|
||||||
if (numProcessed) {
|
if (numProcessed) {
|
||||||
bufferedItems -= numProcessed;
|
bufferedItems -= numProcessed;
|
||||||
bufferOffset += numProcessed;
|
bufferOffset += numProcessed;
|
||||||
}
|
}
|
||||||
|
//clamp to zero the number of remaining items.
|
||||||
if (bufferedItems <= 0) {
|
if (bufferedItems <= 0) {
|
||||||
bufferedItems = 0;
|
bufferedItems = 0;
|
||||||
bufferOffset = 0;
|
bufferOffset = 0;
|
||||||
}
|
}
|
||||||
}
|
} //end if bufferedItems >= fftSize
|
||||||
}
|
} //en while
|
||||||
}
|
}
|
||||||
|
@ -7,20 +7,22 @@
|
|||||||
#include "DemodDefs.h"
|
#include "DemodDefs.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
class FFTDataDistributor : public VisualProcessor<DemodulatorThreadIQData, DemodulatorThreadIQData> {
|
class FFTDataDistributor : public VisualProcessor<DemodulatorThreadIQData, DemodulatorThreadIQData> {
|
||||||
public:
|
public:
|
||||||
FFTDataDistributor();
|
FFTDataDistributor();
|
||||||
void setFFTSize(unsigned int fftSize);
|
void setFFTSize(unsigned int size);
|
||||||
void setLinesPerSecond(unsigned int lines);
|
void setLinesPerSecond(unsigned int lines);
|
||||||
unsigned int getLinesPerSecond();
|
unsigned int getLinesPerSecond();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void process();
|
virtual void process();
|
||||||
|
|
||||||
DemodulatorThreadIQData inputBuffer, tempBuffer;
|
DemodulatorThreadIQData inputBuffer, tempBuffer;
|
||||||
ReBuffer<DemodulatorThreadIQData> outputBuffers;
|
ReBuffer<DemodulatorThreadIQData> outputBuffers;
|
||||||
unsigned int fftSize;
|
std::atomic<unsigned int> fftSize;
|
||||||
|
|
||||||
unsigned int linesPerSecond;
|
unsigned int linesPerSecond;
|
||||||
double lineRateAccum;
|
double lineRateAccum;
|
||||||
size_t bufferMax = 0;
|
size_t bufferMax = 0;
|
||||||
|
@ -30,10 +30,18 @@ void FFTVisualDataThread::run() {
|
|||||||
DemodulatorThreadInputQueue *pipeIQDataIn = static_cast<DemodulatorThreadInputQueue *>(getInputQueue("IQDataInput"));
|
DemodulatorThreadInputQueue *pipeIQDataIn = static_cast<DemodulatorThreadInputQueue *>(getInputQueue("IQDataInput"));
|
||||||
SpectrumVisualDataQueue *pipeFFTDataOut = static_cast<SpectrumVisualDataQueue *>(getOutputQueue("FFTDataOutput"));
|
SpectrumVisualDataQueue *pipeFFTDataOut = static_cast<SpectrumVisualDataQueue *>(getOutputQueue("FFTDataOutput"));
|
||||||
|
|
||||||
|
|
||||||
fftQueue.set_max_num_items(100);
|
fftQueue.set_max_num_items(100);
|
||||||
pipeFFTDataOut->set_max_num_items(100);
|
pipeFFTDataOut->set_max_num_items(100);
|
||||||
|
|
||||||
|
//FFT distributor plumbing:
|
||||||
|
// IQDataInput push samples to process to FFT Data distributor.
|
||||||
fftDistrib.setInput(pipeIQDataIn);
|
fftDistrib.setInput(pipeIQDataIn);
|
||||||
|
|
||||||
|
//The FFT distributor has actually 1 output only, so it doesn't distribute at all :)
|
||||||
fftDistrib.attachOutput(&fftQueue);
|
fftDistrib.attachOutput(&fftQueue);
|
||||||
|
|
||||||
|
//FFT Distributor output is ==> SpectrumVisualProcessor input.
|
||||||
wproc.setInput(&fftQueue);
|
wproc.setInput(&fftQueue);
|
||||||
wproc.attachOutput(pipeFFTDataOut);
|
wproc.attachOutput(pipeFFTDataOut);
|
||||||
wproc.setup(DEFAULT_FFT_SIZE);
|
wproc.setup(DEFAULT_FFT_SIZE);
|
||||||
@ -42,7 +50,9 @@ void FFTVisualDataThread::run() {
|
|||||||
|
|
||||||
while(!stopping) {
|
while(!stopping) {
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
//this if fed by FFTDataDistributor which has a buffer of FFT_DISTRIBUTOR_BUFFER_IN_SECONDS
|
||||||
|
//so sleep for << FFT_DISTRIBUTOR_BUFFER_IN_SECONDS not to be overflown
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds((int)(FFT_DISTRIBUTOR_BUFFER_IN_SECONDS * 1000.0 / 25.0)));
|
||||||
// std::this_thread::yield();
|
// std::this_thread::yield();
|
||||||
|
|
||||||
int fftSize = wproc.getDesiredInputSize();
|
int fftSize = wproc.getDesiredInputSize();
|
||||||
@ -59,8 +69,11 @@ void FFTVisualDataThread::run() {
|
|||||||
lpsChanged.store(false);
|
lpsChanged.store(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Make FFT Distributor process IQ samples
|
||||||
|
//and package them into ready-to-FFT sample sets (representing 1 line) by wproc
|
||||||
fftDistrib.run();
|
fftDistrib.run();
|
||||||
|
|
||||||
|
// Make wproc do a FFT of each of the sample sets provided by fftDistrib:
|
||||||
while (!wproc.isInputEmpty()) {
|
while (!wproc.isInputEmpty()) {
|
||||||
wproc.run();
|
wproc.run();
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ ScopeVisualProcessor::ScopeVisualProcessor(): outputBuffers("ScopeVisualProcesso
|
|||||||
fft_average_rate = 0.65f;
|
fft_average_rate = 0.65f;
|
||||||
fft_ceil_ma = fft_ceil_maa = 0;
|
fft_ceil_ma = fft_ceil_maa = 0;
|
||||||
fft_floor_ma = fft_floor_maa = 0;
|
fft_floor_ma = fft_floor_maa = 0;
|
||||||
maxScopeSamples = 1024;
|
maxScopeSamples = DEFAULT_DMOD_FFT_SIZE;
|
||||||
fftPlan = nullptr;
|
fftPlan = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ public:
|
|||||||
void setScopeEnabled(bool scopeEnable);
|
void setScopeEnabled(bool scopeEnable);
|
||||||
void setSpectrumEnabled(bool spectrumEnable);
|
void setSpectrumEnabled(bool spectrumEnable);
|
||||||
protected:
|
protected:
|
||||||
void process();
|
virtual void process();
|
||||||
ReBuffer<ScopeRenderData> outputBuffers;
|
ReBuffer<ScopeRenderData> outputBuffers;
|
||||||
|
|
||||||
std::atomic_bool scopeEnabled;
|
std::atomic_bool scopeEnabled;
|
||||||
|
@ -16,11 +16,12 @@ SpectrumVisualProcessor *SpectrumVisualDataThread::getProcessor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SpectrumVisualDataThread::run() {
|
void SpectrumVisualDataThread::run() {
|
||||||
// std::cout << "Spectrum visual data thread started." << std::endl;
|
|
||||||
|
|
||||||
while(!stopping) {
|
while(!stopping) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
//this if fed by FFTDataDistributor which has a buffer of FFT_DISTRIBUTOR_BUFFER_IN_SECONDS
|
||||||
// std::this_thread::yield();
|
//so sleep for << FFT_DISTRIBUTOR_BUFFER_IN_SECONDS not to be overflown
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds((int)(FFT_DISTRIBUTOR_BUFFER_IN_SECONDS * 1000.0 / 25.0)));
|
||||||
|
|
||||||
sproc.run();
|
sproc.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ public:
|
|||||||
float getScaleFactor();
|
float getScaleFactor();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void process();
|
virtual void process();
|
||||||
|
|
||||||
ReBuffer<SpectrumVisualData> outputBuffers;
|
ReBuffer<SpectrumVisualData> outputBuffers;
|
||||||
std::atomic_bool is_view;
|
std::atomic_bool is_view;
|
||||||
|
@ -43,19 +43,22 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Set a (new) 'input' queue for incoming data.
|
||||||
void setInput(ThreadQueue<InputDataType *> *vis_in) {
|
void setInput(ThreadQueue<InputDataType *> *vis_in) {
|
||||||
std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
|
std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
|
||||||
input = vis_in;
|
input = vis_in;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Add a vis_out queue where to consumed 'input' data will be
|
||||||
|
//dispatched by distribute().
|
||||||
void attachOutput(ThreadQueue<OutputDataType *> *vis_out) {
|
void attachOutput(ThreadQueue<OutputDataType *> *vis_out) {
|
||||||
// attach an output queue
|
// attach an output queue
|
||||||
std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
|
std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
|
||||||
outputs.push_back(vis_out);
|
outputs.push_back(vis_out);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//reverse of attachOutput(), removed an existing attached vis_out.
|
||||||
void removeOutput(ThreadQueue<OutputDataType *> *vis_out) {
|
void removeOutput(ThreadQueue<OutputDataType *> *vis_out) {
|
||||||
// remove an output queue
|
// remove an output queue
|
||||||
std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
|
std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
|
||||||
@ -64,9 +67,9 @@ public:
|
|||||||
if (i != outputs.end()) {
|
if (i != outputs.end()) {
|
||||||
outputs.erase(i);
|
outputs.erase(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Call process() repeateadly until all available 'input' data is consumed.
|
||||||
void run() {
|
void run() {
|
||||||
|
|
||||||
std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
|
std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
|
||||||
@ -78,37 +81,51 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void process() {
|
// derived class must implement a process() interface
|
||||||
// process inputs to output
|
//where typically 'input' data is consummed, procerssed, and then dispatched
|
||||||
// distribute(output);
|
//with distribute() to all 'outputs'.
|
||||||
}
|
virtual void process() = 0;
|
||||||
|
|
||||||
|
//To be used by derived classes implementing
|
||||||
|
//process() : will dispatch 'item' into as many
|
||||||
|
//available outputs, previously set by attachOutput().
|
||||||
|
void distribute(OutputDataType *item) {
|
||||||
|
|
||||||
void distribute(OutputDataType *output) {
|
|
||||||
// distribute outputs
|
|
||||||
std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
|
std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
|
||||||
|
//We will try to distribute 'output' among all 'outputs',
|
||||||
output->setRefCount((int)outputs.size());
|
//so 'output' will a-priori be shared among all 'outputs' so set its ref count to this
|
||||||
|
//amount.
|
||||||
|
item->setRefCount((int)outputs.size());
|
||||||
for (outputs_i = outputs.begin(); outputs_i != outputs.end(); outputs_i++) {
|
for (outputs_i = outputs.begin(); outputs_i != outputs.end(); outputs_i++) {
|
||||||
|
//if 'output' failed to be given to an outputs_i, dec its ref count accordingly.
|
||||||
if (!(*outputs_i)->push(output)) {
|
if (!(*outputs_i)->push(item)) {
|
||||||
output->decRefCount();
|
item->decRefCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now 'item' refcount matches the times 'item' has been successfully distributed,
|
||||||
|
//i.e shared among the outputs.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//the incoming data queue
|
||||||
ThreadQueue<InputDataType *> *input = nullptr;
|
ThreadQueue<InputDataType *> *input = nullptr;
|
||||||
std::vector<ThreadQueue<OutputDataType *> *> outputs;
|
|
||||||
typename std::vector<ThreadQueue<OutputDataType *> *>::iterator outputs_i;
|
|
||||||
|
|
||||||
//protects input and outputs, must be recursive because ao reentrance
|
//the n-outputs where to process()-ed data is distribute()-ed.
|
||||||
|
std::vector<ThreadQueue<OutputDataType *> *> outputs;
|
||||||
|
|
||||||
|
typename std::vector<ThreadQueue<OutputDataType *> *>::iterator outputs_i;
|
||||||
|
|
||||||
|
//protects input and outputs, must be recursive because of re-entrance
|
||||||
std::recursive_mutex busy_update;
|
std::recursive_mutex busy_update;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Specialization much like VisualDataReDistributor, except
|
||||||
|
//the input (pointer) is directly re-dispatched
|
||||||
|
//to outputs, so that all output indeed SHARE the same instance.
|
||||||
template<class OutputDataType = ReferenceCounter>
|
template<class OutputDataType = ReferenceCounter>
|
||||||
class VisualDataDistributor : public VisualProcessor<OutputDataType, OutputDataType> {
|
class VisualDataDistributor : public VisualProcessor<OutputDataType, OutputDataType> {
|
||||||
protected:
|
protected:
|
||||||
void process() {
|
virtual void process() {
|
||||||
OutputDataType *inp;
|
OutputDataType *inp;
|
||||||
while (VisualProcessor<OutputDataType, OutputDataType>::input->try_pop(inp)) {
|
while (VisualProcessor<OutputDataType, OutputDataType>::input->try_pop(inp)) {
|
||||||
|
|
||||||
@ -126,11 +143,12 @@ protected:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//specialization class which process() take an input item and re-dispatch
|
||||||
|
//A COPY to every outputs, without further processing. This is a 1-to-n dispatcher.
|
||||||
template<class OutputDataType = ReferenceCounter>
|
template<class OutputDataType = ReferenceCounter>
|
||||||
class VisualDataReDistributor : public VisualProcessor<OutputDataType, OutputDataType> {
|
class VisualDataReDistributor : public VisualProcessor<OutputDataType, OutputDataType> {
|
||||||
protected:
|
protected:
|
||||||
void process() {
|
virtual void process() {
|
||||||
OutputDataType *inp;
|
OutputDataType *inp;
|
||||||
while (VisualProcessor<OutputDataType, OutputDataType>::input->try_pop(inp)) {
|
while (VisualProcessor<OutputDataType, OutputDataType>::input->try_pop(inp)) {
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ WaterfallCanvas::WaterfallCanvas(wxWindow *parent, std::vector<int> dispAttrs) :
|
|||||||
dragOfs(0), mouseZoom(1), zoom(1), freqMoving(false), freqMove(0.0), hoverAlpha(1.0) {
|
dragOfs(0), mouseZoom(1), zoom(1), freqMoving(false), freqMove(0.0), hoverAlpha(1.0) {
|
||||||
|
|
||||||
glContext = new PrimaryGLContext(this, &wxGetApp().GetContext(this));
|
glContext = new PrimaryGLContext(this, &wxGetApp().GetContext(this));
|
||||||
linesPerSecond = 30;
|
linesPerSecond = DEFAULT_WATERFALL_LPS;
|
||||||
lpsIndex = 0;
|
lpsIndex = 0;
|
||||||
preBuf = false;
|
preBuf = false;
|
||||||
SetCursor(wxCURSOR_CROSS);
|
SetCursor(wxCURSOR_CROSS);
|
||||||
|
Loading…
Reference in New Issue
Block a user