diff --git a/CMakeLists.txt b/CMakeLists.txt index cf437d55..11f020cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required (VERSION 2.8) SET(CUBICSDR_VERSION_MAJOR "0") SET(CUBICSDR_VERSION_MINOR "1") -SET(CUBICSDR_VERSION_PATCH "8") -SET(CUBICSDR_VERSION_REL "beta-issue64") +SET(CUBICSDR_VERSION_PATCH "10") +SET(CUBICSDR_VERSION_REL "alpha-pfbch-single-issue150") SET(CUBICSDR_VERSION "${CUBICSDR_VERSION_MAJOR}.${CUBICSDR_VERSION_MINOR}.${CUBICSDR_VERSION_PATCH}-${CUBICSDR_VERSION_REL}") SET(CPACK_PACKAGE_VERSION "${CUBICSDR_VERSION_MAJOR}.${CUBICSDR_VERSION_MINOR}.${CUBICSDR_VERSION_PATCH}") diff --git a/README.md b/README.md index 1c075474..5d5f2f90 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ Cross-Platform Software-Defined Radio Application Utilizes: -------- - - liquid-dsp (http://liquidsdr.org/ https://github.com/jgaeddert/liquid-dsp) - - FFTW (http://www.fftw.org/ https://github.com/FFTW/fftw3) - - RtAudio (http://www.music.mcgill.ca/~gary/rtaudio/ http://github.com/thestk/rtaudio/) - - Osmocom RTLSDR (http://sdr.osmocom.org/trac/wiki/rtl-sdr) + - liquid-dsp (http://liquidsdr.org/ -- https://github.com/jgaeddert/liquid-dsp) + - SoapySDR (http://www.pothosware.com/ -- https://github.com/pothosware/SoapySDR) + - FFTW (http://www.fftw.org/ -- https://github.com/FFTW/fftw3) + - RtAudio (http://www.music.mcgill.ca/~gary/rtaudio/ -- http://github.com/thestk/rtaudio/) - LodePNG (http://lodev.org/lodepng/) - - BMFont (http://www.angelcode.com/ http://www.angelcode.com/products/bmfont/) + - BMFont (http://www.angelcode.com/ -- http://www.angelcode.com/products/bmfont/) - Bitstream Vera font (http://en.wikipedia.org/wiki/Bitstream_Vera) - OpenGL (https://www.opengl.org/) - wxWidgets (https://www.wxwidgets.org/) @@ -20,9 +20,19 @@ Features and Status: -------------------- - Simple UI - Devices - - [x] RTL-SDR + - [x] SoapySDR Device support (known working checked) + - [x] SoapySDRPlay for SDRPlay (Maintained by C.J.) + - [x] SoapyRTLSDR for RTL-SDR (Maintained by C.J.) + - [x] SoapyHackRF for HackRF + - [x] SoapyBladeRF for BladeRF + - [ ] SoapyUHD for Ettus USRP + - [x] SoapyRemote, use any SoapySDR Device via network (works on Pi) + - [x] SoapyOsmo for GrOsmoSDR devices + - [ ] OsmoSDR + - [ ] MiriSDR + - [ ] RFSpace + - [x] AirSpy - [ ] rtl_tcp client - - [ ] gr-osmosdr - Basic Features - [x] Device Selection - [x] Bandwidth @@ -30,11 +40,14 @@ Features and Status: - [x] Load/Save session - [x] Audio sample rate - [x] Device PPM + - [x] Waterfall speed + - [x] Spectrum average speed + - [ ] Gain Controls + - [ ] Bookmarks + - [ ] History - [ ] Default preferences - [ ] Audio defaults - [x] Device defaults - - [ ] Bookmarks - - [ ] History - [ ] Run as rtl_tcp server and visualize control - Neat Visuals - [ ] 2D visuals @@ -68,8 +81,6 @@ Features and Status: - [x] Volume control - [x] Direct frequency input - [x] Mute - - [x] Waterfall speed - - [ ] RTL-SDR Gain - Basic Input Controls - [x] Drag spectrum to change center frequency - [x] Hold shift and click on waterfall to create a new demodulator diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index 662d882e..357f1d69 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -280,14 +280,14 @@ AppFrame::AppFrame() : sampleRateMenuItems[wxID_BANDWIDTH_2000M] = menu->AppendRadioItem(wxID_BANDWIDTH_2000M, "2.0M"); sampleRateMenuItems[wxID_BANDWIDTH_2048M] = menu->AppendRadioItem(wxID_BANDWIDTH_2048M, "2.048M"); sampleRateMenuItems[wxID_BANDWIDTH_2160M] = menu->AppendRadioItem(wxID_BANDWIDTH_2160M, "2.16M"); - sampleRateMenuItems[wxID_BANDWIDTH_2400M] = menu->AppendRadioItem(wxID_BANDWIDTH_2400M, "2.4M"); - sampleRateMenuItems[wxID_BANDWIDTH_2560M] = menu->AppendRadioItem(wxID_BANDWIDTH_2560M, "2.56M"); +// sampleRateMenuItems[wxID_BANDWIDTH_2400M] = menu->AppendRadioItem(wxID_BANDWIDTH_2400M, "2.4M"); + sampleRateMenuItems[wxID_BANDWIDTH_2500M] = menu->AppendRadioItem(wxID_BANDWIDTH_2500M, "2.5M"); sampleRateMenuItems[wxID_BANDWIDTH_2880M] = menu->AppendRadioItem(wxID_BANDWIDTH_2880M, "2.88M"); // sampleRateMenuItems[wxID_BANDWIDTH_3000M] = menu->AppendRadioItem(wxID_BANDWIDTH_3000M, "3.0M"); sampleRateMenuItems[wxID_BANDWIDTH_3200M] = menu->AppendRadioItem(wxID_BANDWIDTH_3200M, "3.2M"); sampleRateMenuItems[wxID_BANDWIDTH_MANUAL] = menu->AppendRadioItem(wxID_BANDWIDTH_MANUAL, "Manual Entry"); - sampleRateMenuItems[wxID_BANDWIDTH_2400M]->Check(true); + sampleRateMenuItems[wxID_BANDWIDTH_2500M]->Check(true); menuBar->Append(menu, wxT("&Input Bandwidth")); @@ -574,11 +574,8 @@ void AppFrame::OnMenu(wxCommandEvent& event) { case wxID_BANDWIDTH_2160M: wxGetApp().setSampleRate(2160000); break; - case wxID_BANDWIDTH_2400M: - wxGetApp().setSampleRate(2400000); - break; - case wxID_BANDWIDTH_2560M: - wxGetApp().setSampleRate(2560000); + case wxID_BANDWIDTH_2500M: + wxGetApp().setSampleRate(2500000); break; case wxID_BANDWIDTH_2880M: wxGetApp().setSampleRate(2880000); @@ -671,8 +668,6 @@ void AppFrame::OnIdle(wxIdleEvent& event) { DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); if (demod) { - DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); - if (demod->isTracking()) { if (spectrumCanvas->getViewState()) { long long diff = abs(demod->getFrequency() - spectrumCanvas->getCenterFrequency()) + (demod->getBandwidth()/2) + (demod->getBandwidth()/4); @@ -816,7 +811,7 @@ void AppFrame::OnIdle(wxIdleEvent& event) { wxGetApp().getAudioVisualQueue()->set_max_num_items((scopeCanvas->scopeVisible()?1:0) + (scopeCanvas->spectrumVisible()?1:0)); wxGetApp().getScopeProcessor()->run(); - wxGetApp().getSpectrumDistributor()->run(); +// wxGetApp().getSpectrumDistributor()->run(); SpectrumVisualProcessor *proc = wxGetApp().getSpectrumProcessor(); @@ -857,6 +852,7 @@ void AppFrame::OnIdle(wxIdleEvent& event) { wproc->setView(waterfallCanvas->getViewState()); wproc->setBandwidth(waterfallCanvas->getBandwidth()); wproc->setCenterFrequency(waterfallCanvas->getCenterFrequency()); + wxGetApp().getSDRPostThread()->setIQVisualRange(waterfallCanvas->getCenterFrequency(), waterfallCanvas->getBandwidth()); // waterfallCanvas->processInputQueue(); // waterfallCanvas->Refresh(); diff --git a/src/AppFrame.h b/src/AppFrame.h index 26c0c61e..3359d97b 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -41,8 +41,8 @@ #define wxID_BANDWIDTH_2000M 2156 #define wxID_BANDWIDTH_2048M 2157 #define wxID_BANDWIDTH_2160M 2158 -#define wxID_BANDWIDTH_2400M 2159 -#define wxID_BANDWIDTH_2560M 2160 +//#define wxID_BANDWIDTH_2400M 2159 +#define wxID_BANDWIDTH_2500M 2160 #define wxID_BANDWIDTH_2880M 2161 //#define wxID_BANDWIDTH_3000M 2162 #define wxID_BANDWIDTH_3200M 2163 diff --git a/src/CubicSDR.cpp b/src/CubicSDR.cpp index 15e411c4..791b1ba5 100644 --- a/src/CubicSDR.cpp +++ b/src/CubicSDR.cpp @@ -59,22 +59,14 @@ bool CubicSDR::OnInit() { pipeIQVisualData = new DemodulatorThreadInputQueue(); pipeIQVisualData->set_max_num_items(1); - spectrumDistributor.setInput(pipeIQVisualData); - pipeDemodIQVisualData = new DemodulatorThreadInputQueue(); pipeDemodIQVisualData->set_max_num_items(1); - pipeSpectrumIQVisualData = new DemodulatorThreadInputQueue(); - pipeSpectrumIQVisualData->set_max_num_items(1); - pipeWaterfallIQVisualData = new DemodulatorThreadInputQueue(); pipeWaterfallIQVisualData->set_max_num_items(128); - spectrumDistributor.attachOutput(pipeDemodIQVisualData); - spectrumDistributor.attachOutput(pipeSpectrumIQVisualData); - getDemodSpectrumProcessor()->setInput(pipeDemodIQVisualData); - getSpectrumProcessor()->setInput(pipeSpectrumIQVisualData); + getSpectrumProcessor()->setInput(pipeIQVisualData); pipeAudioVisualData = new DemodulatorThreadOutputQueue(); pipeAudioVisualData->set_max_num_items(1); @@ -89,19 +81,17 @@ bool CubicSDR::OnInit() { sdrThread->setOutputQueue("IQDataOutput",pipeSDRIQData); sdrPostThread = new SDRPostThread(); -// sdrPostThread->setNumVisSamples(BUF_SIZE); sdrPostThread->setInputQueue("IQDataInput", pipeSDRIQData); sdrPostThread->setOutputQueue("IQVisualDataOutput", pipeIQVisualData); sdrPostThread->setOutputQueue("IQDataOutput", pipeWaterfallIQVisualData); + sdrPostThread->setOutputQueue("IQActiveDemodVisualDataOutput", pipeDemodIQVisualData); t_PostSDR = new std::thread(&SDRPostThread::threadMain, sdrPostThread); t_SpectrumVisual = new std::thread(&SpectrumVisualDataThread::threadMain, spectrumVisualThread); t_DemodVisual = new std::thread(&SpectrumVisualDataThread::threadMain, demodVisualThread); -// t_SDR = new std::thread(&SDRThread::threadMain, sdrThread); sdrEnum = new SDREnumerator(); - appframe = new AppFrame(); t_SDREnum = new std::thread(&SDREnumerator::threadMain, sdrEnum); @@ -371,11 +361,6 @@ SpectrumVisualProcessor *CubicSDR::getDemodSpectrumProcessor() { return demodVisualThread->getProcessor(); } -VisualDataDistributor *CubicSDR::getSpectrumDistributor() { - return &spectrumDistributor; -} - - DemodulatorThreadOutputQueue* CubicSDR::getAudioVisualQueue() { return pipeAudioVisualData; } @@ -392,6 +377,10 @@ DemodulatorMgr &CubicSDR::getDemodMgr() { return demodMgr; } +SDRPostThread *CubicSDR::getSDRPostThread() { + return sdrPostThread; +} + void CubicSDR::bindDemodulator(DemodulatorInstance *demod) { if (!demod) { return; diff --git a/src/CubicSDR.h b/src/CubicSDR.h index f892fdd0..acfb800c 100644 --- a/src/CubicSDR.h +++ b/src/CubicSDR.h @@ -70,13 +70,15 @@ class CubicSDR: public wxApp { ScopeVisualProcessor *getScopeProcessor(); SpectrumVisualProcessor *getSpectrumProcessor(); SpectrumVisualProcessor *getDemodSpectrumProcessor(); - VisualDataDistributor *getSpectrumDistributor(); DemodulatorThreadOutputQueue* getAudioVisualQueue(); DemodulatorThreadInputQueue* getIQVisualQueue(); DemodulatorThreadInputQueue* getWaterfallVisualQueue(); + DemodulatorThreadInputQueue* getActiveDemodVisualQueue(); DemodulatorMgr &getDemodMgr(); + SDRPostThread *getSDRPostThread(); + void bindDemodulator(DemodulatorInstance *demod); void removeDemodulator(DemodulatorInstance *demod); @@ -122,18 +124,15 @@ class CubicSDR: public wxApp { SpectrumVisualDataThread *spectrumVisualThread; SpectrumVisualDataThread *demodVisualThread; -// SDRThreadCommandQueue* pipeSDRCommand; SDRThreadIQDataQueue* pipeSDRIQData; DemodulatorThreadInputQueue* pipeIQVisualData; DemodulatorThreadOutputQueue* pipeAudioVisualData; DemodulatorThreadInputQueue* pipeDemodIQVisualData; - DemodulatorThreadInputQueue* pipeSpectrumIQVisualData; DemodulatorThreadInputQueue* pipeWaterfallIQVisualData; + DemodulatorThreadInputQueue* pipeActiveDemodIQVisualData; ScopeVisualProcessor scopeProcessor; - VisualDataDistributor spectrumDistributor; - SDRDevicesDialog *deviceSelectorDialog; std::thread *t_SDR, *t_SDREnum, *t_PostSDR, *t_SpectrumVisual, *t_DemodVisual; diff --git a/src/CubicSDRDefs.h b/src/CubicSDRDefs.h index 49df3126..11a98386 100644 --- a/src/CubicSDRDefs.h +++ b/src/CubicSDRDefs.h @@ -27,10 +27,12 @@ const char filePathSeparator = #define BUF_SIZE (16384*6) -#define DEFAULT_SAMPLE_RATE 2400000 +#define DEFAULT_SAMPLE_RATE 2500000 #define DEFAULT_FFT_SIZE 2048 #define DEFAULT_DEMOD_TYPE 1 #define DEFAULT_DEMOD_BW 200000 #define DEFAULT_WATERFALL_LPS 30 + +#define CHANNELIZER_RATE_MAX 400000 \ No newline at end of file diff --git a/src/demod/DemodulatorPreThread.cpp b/src/demod/DemodulatorPreThread.cpp index 5c3820f6..f96dc2c8 100644 --- a/src/demod/DemodulatorPreThread.cpp +++ b/src/demod/DemodulatorPreThread.cpp @@ -155,18 +155,20 @@ void DemodulatorPreThread::run() { } if (!initialized) { + inp->decRefCount(); continue; } // Requested frequency is not center, shift it into the center! if ((params.frequency - inp->frequency) != shiftFrequency || rateChanged) { shiftFrequency = params.frequency - inp->frequency; - if (abs(shiftFrequency) <= (int) ((double) (wxGetApp().getSampleRate() / 2) * 1.5)) { - nco_crcf_set_frequency(freqShifter, (2.0 * M_PI) * (((double) abs(shiftFrequency)) / ((double) wxGetApp().getSampleRate()))); + if (abs(shiftFrequency) <= (int) ((double) (inp->sampleRate / 2) * 1.5)) { + nco_crcf_set_frequency(freqShifter, (2.0 * M_PI) * (((double) abs(shiftFrequency)) / ((double) inp->sampleRate))); } } - if (abs(shiftFrequency) > (int) ((double) (wxGetApp().getSampleRate() / 2) * 1.5)) { + if (abs(shiftFrequency) > (int) ((double) (inp->sampleRate / 2) * 1.5)) { + inp->decRefCount(); continue; } diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index e1274e29..133beb4b 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -59,7 +59,7 @@ void DemodulatorThread::run() { nco_crcf_pll_set_bandwidth(stereoPilot, 0.25f); // half band filter used for side-band elimination - resamp2_cccf ssbFilt = resamp2_cccf_create(12,-0.25f,60.0f); + resamp2_crcf ssbFilt = resamp2_crcf_create(12,-0.25f,60.0f); // Automatic IQ gain iqAutoGain = agc_crcf_create(); @@ -192,13 +192,13 @@ void DemodulatorThread::run() { switch (demodulatorType.load()) { case DEMOD_TYPE_LSB: for (int i = 0; i < bufSize; i++) { // Reject upper band - resamp2_cccf_filter_execute(ssbFilt,(*inputData)[i],&x,&y); + resamp2_crcf_filter_execute(ssbFilt,(*inputData)[i],&x,&y); ampmodem_demodulate(demodAM, x, &demodOutputData[i]); } break; case DEMOD_TYPE_USB: for (int i = 0; i < bufSize; i++) { // Reject lower band - resamp2_cccf_filter_execute(ssbFilt,(*inputData)[i],&x,&y); + resamp2_crcf_filter_execute(ssbFilt,(*inputData)[i],&x,&y); ampmodem_demodulate(demodAM, y, &demodOutputData[i]); } break; @@ -487,7 +487,7 @@ void DemodulatorThread::run() { firhilbf_destroy(firStereoR2C); firhilbf_destroy(firStereoC2R); nco_crcf_destroy(stereoPilot); - resamp2_cccf_destroy(ssbFilt); + resamp2_crcf_destroy(ssbFilt); outputBuffers.purge(); diff --git a/src/panel/WaterfallPanel.cpp b/src/panel/WaterfallPanel.cpp index 5de721a7..4720b4f7 100644 --- a/src/panel/WaterfallPanel.cpp +++ b/src/panel/WaterfallPanel.cpp @@ -91,15 +91,46 @@ void WaterfallPanel::step() { waterfall_slice[i] = (unsigned char) floor(wv * 255.0); } + int newBufSize = (half_fft_size*lines_buffered+half_fft_size); + if (lineBuffer[j].size() < newBufSize) { + lineBuffer[j].resize(newBufSize); + rLineBuffer[j].resize(newBufSize); + } + memcpy(&(lineBuffer[j][half_fft_size*lines_buffered]), waterfall_slice, sizeof(unsigned char) * half_fft_size); + } + lines_buffered++; + } +} + +void WaterfallPanel::update() { + int half_fft_size = fft_size / 2; + + for (int i = 0; i < lines_buffered; i++) { + for (int j = 0; j < 2; j++) { + memcpy(&(rLineBuffer[j][i*half_fft_size]), + &(lineBuffer[j][((lines_buffered-1)*half_fft_size)-(i*half_fft_size)]), sizeof(unsigned char) * half_fft_size); + } + } + + int run_ofs = 0; + while (lines_buffered) { + int run_lines = lines_buffered; + if (run_lines > waterfall_ofs[0]) { + run_lines = waterfall_ofs[0]; + } + for (int j = 0; j < 2; j++) { glBindTexture(GL_TEXTURE_2D, waterfall[j]); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, waterfall_ofs[j], half_fft_size, 1, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, (GLvoid *) waterfall_slice); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, waterfall_ofs[j]-run_lines, half_fft_size, run_lines, + GL_COLOR_INDEX, GL_UNSIGNED_BYTE, (GLvoid *) &(rLineBuffer[j][run_ofs])); + + waterfall_ofs[j]-=run_lines; if (waterfall_ofs[j] == 0) { waterfall_ofs[j] = waterfall_lines; } - - waterfall_ofs[j]--; } + run_ofs += run_lines*half_fft_size; + lines_buffered-=run_lines; } } diff --git a/src/panel/WaterfallPanel.h b/src/panel/WaterfallPanel.h index 65c664a0..faa566aa 100644 --- a/src/panel/WaterfallPanel.h +++ b/src/panel/WaterfallPanel.h @@ -9,7 +9,8 @@ class WaterfallPanel : public GLPanel { void refreshTheme(); void setPoints(std::vector &points); void step(); - + void update(); + protected: void drawPanelContents(); @@ -21,6 +22,9 @@ class WaterfallPanel : public GLPanel { int fft_size; int waterfall_lines; unsigned char *waterfall_slice; + std::vector lineBuffer[2]; + std::vector rLineBuffer[2]; + int lines_buffered; ColorTheme *activeTheme; }; diff --git a/src/sdr/SDREnumerator.cpp b/src/sdr/SDREnumerator.cpp index 9c369dbe..94dbe9d7 100644 --- a/src/sdr/SDREnumerator.cpp +++ b/src/sdr/SDREnumerator.cpp @@ -124,7 +124,7 @@ std::vector *SDREnumerator::enumerate_devices(std::string remot if (isRemote) { wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Querying remote " + remoteAddr + " device #" + std::to_string(i)); - deviceArgs["remote"] = remoteAddr; +// deviceArgs["remote"] = remoteAddr; if (deviceArgs.count("rtl") != 0) { streamArgs["remote:mtu"] = "8192"; streamArgs["remote:format"] = "CS8"; diff --git a/src/sdr/SDRPostThread.cpp b/src/sdr/SDRPostThread.cpp index 7836c2b3..31ec0b7c 100644 --- a/src/sdr/SDRPostThread.cpp +++ b/src/sdr/SDRPostThread.cpp @@ -5,26 +5,23 @@ #include #include -SDRPostThread::SDRPostThread() : IOThread(), - iqDataInQueue(NULL), iqDataOutQueue(NULL), iqVisualQueue(NULL), dcFilter(NULL){ - - swapIQ.store(false); - - // create a lookup table - for (unsigned int i = 0; i <= 0xffff; i++) { - liquid_float_complex tmp,tmp_swap; -# if (__BYTE_ORDER == __LITTLE_ENDIAN) - tmp_swap.imag = tmp.real = (float(i & 0xff) - 127.4f) * (1.0f/128.0f); - tmp_swap.real = tmp.imag = (float(i >> 8) - 127.4f) * (1.0f/128.0f); - _lut.push_back(tmp); - _lut_swap.push_back(tmp_swap); -#else // BIG_ENDIAN - tmp_swap.imag = tmp.real = (float(i >> 8) - 127.4f) * (1.0f/128.0f); - tmp_swap.real = tmp.imag = (float(i & 0xff) - 127.4f) * (1.0f/128.0f); - _lut.push_back(tmp); - _lut_swap.push_back(tmp_swap); -#endif - } +SDRPostThread::SDRPostThread() : IOThread() { + iqDataInQueue = NULL; + iqDataOutQueue = NULL; + iqVisualQueue = NULL; + + swapIQ.store(false); + numChannels = 0; + channelizer = NULL; + + sampleRate = 0; + nRunDemods = 0; + + visFrequency.store(0); + visBandwidth.store(0); + + doRefresh.store(false); + dcFilter = iirfilt_crcf_create_dc_blocker(0.0005); } SDRPostThread::~SDRPostThread() { @@ -33,6 +30,7 @@ SDRPostThread::~SDRPostThread() { void SDRPostThread::bindDemodulator(DemodulatorInstance *demod) { busy_demod.lock(); demodulators.push_back(demod); + doRefresh.store(true); busy_demod.unlock(); } @@ -46,6 +44,7 @@ void SDRPostThread::removeDemodulator(DemodulatorInstance *demod) { if (i != demodulators.end()) { demodulators.erase(i); + doRefresh.store(true); } busy_demod.unlock(); } @@ -58,27 +57,116 @@ bool SDRPostThread::getSwapIQ() { return this->swapIQ.load(); } +void SDRPostThread::initPFBChannelizer() { +// std::cout << "Initializing post-process FIR polyphase filterbank channelizer with " << numChannels << " channels." << std::endl; + if (channelizer) { + firpfbch_crcf_destroy(channelizer); + } + channelizer = firpfbch_crcf_create_kaiser(LIQUID_ANALYZER, numChannels, 4, 60); + + chanBw = (sampleRate / numChannels); + + chanCenters.resize(numChannels+1); + demodChannelActive.resize(numChannels+1); + +// std::cout << "Channel bandwidth spacing: " << (chanBw) << std::endl; +} + +void SDRPostThread::updateActiveDemodulators() { + // In range? + std::vector::iterator demod_i; + + nRunDemods = 0; + + for (demod_i = demodulators.begin(); demod_i != demodulators.end(); demod_i++) { + DemodulatorInstance *demod = *demod_i; + DemodulatorThreadInputQueue *demodQueue = demod->getIQInputDataPipe(); + + // not in range? + if (abs(frequency - demod->getFrequency()) > (sampleRate / 2)) { + // deactivate if active + if (demod->isActive() && !demod->isFollow() && !demod->isTracking()) { + demod->setActive(false); + DemodulatorThreadIQData *dummyDataOut = new DemodulatorThreadIQData; + dummyDataOut->frequency = frequency; + dummyDataOut->sampleRate = sampleRate; + demodQueue->push(dummyDataOut); + } + + // follow if follow mode + if (demod->isFollow() && wxGetApp().getFrequency() != demod->getFrequency()) { + wxGetApp().setFrequency(demod->getFrequency()); + demod->setFollow(false); + } + } else if (!demod->isActive()) { // in range, activate if not activated + demod->setActive(true); + if (wxGetApp().getDemodMgr().getLastActiveDemodulator() == NULL) { + wxGetApp().getDemodMgr().setActiveDemodulator(demod); + } + } + + if (!demod->isActive()) { + continue; + } + + // Add to the current run + if (nRunDemods == runDemods.size()) { + runDemods.push_back(demod); + demodChannel.push_back(-1); + } else { + runDemods[nRunDemods] = demod; + demodChannel[nRunDemods] = -1; + } + nRunDemods++; + } +} + +void SDRPostThread::updateChannels() { + // calculate channel center frequencies, todo: cache + for (int i = 0; i < numChannels/2; i++) { + int ofs = ((chanBw) * i); + chanCenters[i] = frequency + ofs; + chanCenters[i+(numChannels/2)] = frequency - (sampleRate/2) + ofs; + } + chanCenters[numChannels] = frequency + (sampleRate/2); +} + +int SDRPostThread::getChannelAt(long long frequency) { + int chan = -1; + long long minDelta = sampleRate; + for (int i = 0; i < numChannels+1; i++) { + long long fdelta = abs(frequency - chanCenters[i]); + if (fdelta < minDelta) { + minDelta = fdelta; + chan = i; + } + } + return chan; +} + +void SDRPostThread::setIQVisualRange(long long frequency, int bandwidth) { + visFrequency.store(frequency); + visBandwidth.store(bandwidth); +} + void SDRPostThread::run() { #ifdef __APPLE__ pthread_t tID = pthread_self(); // ID of this thread - int priority = sched_get_priority_max( SCHED_FIFO) - 1; + int priority = sched_get_priority_max( SCHED_FIFO); sched_param prio = {priority}; // scheduling priority of thread pthread_setschedparam(tID, SCHED_FIFO, &prio); #endif - dcFilter = iirfilt_crcf_create_dc_blocker(0.0005); - std::cout << "SDR post-processing thread started.." << std::endl; iqDataInQueue = (SDRThreadIQDataQueue*)getInputQueue("IQDataInput"); iqDataOutQueue = (DemodulatorThreadInputQueue*)getOutputQueue("IQDataOutput"); iqVisualQueue = (DemodulatorThreadInputQueue*)getOutputQueue("IQVisualDataOutput"); - - ReBuffer buffers; - std::vector fpData; - std::vector dataOut; - + iqActiveDemodVisualQueue = (DemodulatorThreadInputQueue*)getOutputQueue("IQActiveDemodVisualDataOutput"); + iqDataInQueue->set_max_num_items(0); + + std::vector dcBuf; while (!terminated) { SDRThreadIQData *data_in; @@ -86,17 +174,24 @@ void SDRPostThread::run() { iqDataInQueue->pop(data_in); // std::lock_guard < std::mutex > lock(data_in->m_mutex); - if (data_in && data_in->data.size()) { - int dataSize = data_in->data.size()/2; + if (data_in && data_in->data.size() && data_in->numChannels) { + if (numChannels != data_in->numChannels || sampleRate != data_in->sampleRate) { + numChannels = data_in->numChannels; + sampleRate = data_in->sampleRate; + initPFBChannelizer(); + doRefresh.store(true); + } + + int dataSize = data_in->data.size(); + int outSize = data_in->data.size(); - if (dataSize > dataOut.capacity()) { - dataOut.reserve(dataSize); + if (outSize > dataOut.capacity()) { + dataOut.reserve(outSize); } - if (dataSize != dataOut.size()) { - dataOut.resize(dataSize); + if (outSize != dataOut.size()) { + dataOut.resize(outSize); } - // if (swapIQ) { // for (int i = 0; i < dataSize; i++) { // fpData[i] = _lut_swap[*((uint16_t*)&data_in->data[2*i])]; @@ -106,121 +201,154 @@ void SDRPostThread::run() { // fpData[i] = _lut[*((uint16_t*)&data_in->data[2*i])]; // } // } - - if (data_in->dcCorrected) { - for (int i = 0; i < dataSize; i++) { - dataOut[i].real = data_in->data[i*2]; - dataOut[i].imag = data_in->data[i*2+1]; - } - } else { - if (dataSize > fpData.capacity()) { - fpData.reserve(dataSize); - } - if (dataSize != fpData.size()) { - fpData.resize(dataSize); + + int activeVisChannel = -1; + +// if (visBandwidth.load() && visBandwidth.load() < (chanBw/2)) { +// activeVisChannel = getChannelAt(visFrequency); +// } + + if (iqDataOutQueue != NULL && !iqDataOutQueue->full() && activeVisChannel < 0) { + DemodulatorThreadIQData *iqDataOut = visualDataBuffers.getBuffer(); + + bool doVis = false; + + if (iqVisualQueue != NULL && !iqVisualQueue->full()) { + doVis = true; } + + iqDataOut->setRefCount(1 + (doVis?1:0)); + + iqDataOut->frequency = data_in->frequency; + iqDataOut->sampleRate = data_in->sampleRate; + iqDataOut->data.assign(data_in->data.begin(), data_in->data.begin() + dataSize); - for (int i = 0; i < dataSize; i++) { - fpData[i].real = data_in->data[i*2]; - fpData[i].imag = data_in->data[i*2+1]; + iqDataOutQueue->push(iqDataOut); + if (doVis) { + iqVisualQueue->push(iqDataOut); } + } + + busy_demod.lock(); - iirfilt_crcf_execute_block(dcFilter, &fpData[0], dataSize, &dataOut[0]); + if (frequency != data_in->frequency) { + frequency = data_in->frequency; + doRefresh.store(true); } + + if (doRefresh.load()) { + updateActiveDemodulators(); + updateChannels(); + doRefresh.store(false); + } + + DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); + int activeDemodChannel = -1; - if (iqVisualQueue != NULL && !iqVisualQueue->full()) { - DemodulatorThreadIQData *visualDataOut = visualDataBuffers.getBuffer(); - visualDataOut->setRefCount(1); + // Find active demodulators + if (nRunDemods || (activeVisChannel >= 0)) { - int num_vis_samples = dataOut.size(); - -// if (visualDataOut->data.size() < num_vis_samples) { -// if (visualDataOut->data.capacity() < num_vis_samples) { -// visualDataOut->data.reserve(num_vis_samples); -// } -// visualDataOut->data.resize(num_vis_samples); +// for (int i = 0; i < numChannels; i++) { +// firpfbch_crcf_set_channel_state(channelizer, i, (demodChannelActive[i]>0)?1:0); // } -// - visualDataOut->frequency = data_in->frequency; - visualDataOut->sampleRate = data_in->sampleRate; - visualDataOut->data.assign(dataOut.begin(), dataOut.begin() + num_vis_samples); - iqVisualQueue->push(visualDataOut); - } - - busy_demod.lock(); - - int activeDemods = 0; - bool pushedData = false; - - if (demodulators.size() || iqDataOutQueue != NULL) { - std::vector::iterator demod_i; - for (demod_i = demodulators.begin(); demod_i != demodulators.end(); demod_i++) { - DemodulatorInstance *demod = *demod_i; - if (demod->getFrequency() != data_in->frequency - && abs(data_in->frequency - demod->getFrequency()) > (wxGetApp().getSampleRate() / 2)) { - continue; - } - activeDemods++; + // channelize data + // firpfbch output rate is (input rate / channels) + for (int i = 0, iMax = dataSize; i < iMax; i+=numChannels) { + firpfbch_crcf_analyzer_execute(channelizer, &data_in->data[i], &dataOut[i]); } - - if (iqDataOutQueue != NULL) { - activeDemods++; + + for (int i = 0, iMax = numChannels; i < iMax; i++) { + demodChannelActive[i] = 0; } - DemodulatorThreadIQData *demodDataOut = buffers.getBuffer(); + // Find nearest channel for each demodulator + for (int i = 0; i < nRunDemods; i++) { + DemodulatorInstance *demod = runDemods[i]; + demodChannel[i] = getChannelAt(demod->getFrequency()); + if (demod == activeDemod) { + activeDemodChannel = demodChannel[i]; + } + } - // std::lock_guard < std::mutex > lock(demodDataOut->m_mutex); - demodDataOut->frequency = data_in->frequency; - demodDataOut->sampleRate = data_in->sampleRate; - demodDataOut->setRefCount(activeDemods); - demodDataOut->data.assign(dataOut.begin(), dataOut.end()); + for (int i = 0; i < nRunDemods; i++) { + // cache channel usage refcounts + if (demodChannel[i] >= 0) { + demodChannelActive[demodChannel[i]]++; + } + } - for (demod_i = demodulators.begin(); demod_i != demodulators.end(); demod_i++) { - DemodulatorInstance *demod = *demod_i; - DemodulatorThreadInputQueue *demodQueue = demod->getIQInputDataPipe(); + // Run channels + for (int i = 0; i < numChannels+1; i++) { + int doDemodVis = ((activeDemodChannel == i) && (iqActiveDemodVisualQueue != NULL) && !iqActiveDemodVisualQueue->full())?1:0; + int doVis = 0; - if (abs(data_in->frequency - demod->getFrequency()) > (wxGetApp().getSampleRate() / 2)) { - if (demod->isActive() && !demod->isFollow() && !demod->isTracking()) { - demod->setActive(false); - DemodulatorThreadIQData *dummyDataOut = new DemodulatorThreadIQData; - dummyDataOut->frequency = data_in->frequency; - dummyDataOut->sampleRate = data_in->sampleRate; - demodQueue->push(dummyDataOut); - } - - if (demod->isFollow() && wxGetApp().getFrequency() != demod->getFrequency()) { - wxGetApp().setFrequency(demod->getFrequency()); - } - } else if (!demod->isActive()) { - demod->setActive(true); - if (wxGetApp().getDemodMgr().getLastActiveDemodulator() == NULL) { - wxGetApp().getDemodMgr().setActiveDemodulator(demod); - } - } +// if (activeVisChannel == i) { +// doVis = (((iqDataOutQueue != NULL))?1:0) + ((iqVisualQueue != NULL && !iqVisualQueue->full())?1:0); +// } - if (!demod->isActive()) { + if (!doVis && !doDemodVis && demodChannelActive[i] == 0) { continue; } - if (demod->isFollow()) { - demod->setFollow(false); + + DemodulatorThreadIQData *demodDataOut = buffers.getBuffer(); + demodDataOut->setRefCount(demodChannelActive[i] + doVis + doDemodVis); + demodDataOut->frequency = chanCenters[i]; + demodDataOut->sampleRate = chanBw; + + // Calculate channel buffer size + int chanDataSize = (outSize/numChannels); + + if (demodDataOut->data.size() != chanDataSize) { + if (demodDataOut->data.capacity() < chanDataSize) { + demodDataOut->data.reserve(chanDataSize); + } + demodDataOut->data.resize(chanDataSize); } + + int idx = i; - demodQueue->push(demodDataOut); - pushedData = true; - } - - if (iqDataOutQueue != NULL) { - if (!iqDataOutQueue->full()) { - iqDataOutQueue->push(demodDataOut); - pushedData = true; + // Extra channel wraps lower side band of lowest channel + // to fix frequency gap on upper side of spectrum + if (i == numChannels) { + idx = (numChannels/2); + } + + // prepare channel data buffer + if (i == 0) { // Channel 0 requires DC correction + if (dcBuf.size() != chanDataSize) { + dcBuf.resize(chanDataSize); + } + for (int j = 0; j < chanDataSize; j++) { + idx += numChannels; + dcBuf[j] = dataOut[idx]; + } + iirfilt_crcf_execute_block(dcFilter, &dcBuf[0], chanDataSize, &demodDataOut->data[0]); } else { - demodDataOut->decRefCount(); + for (int j = 0; j < chanDataSize; j++) { + idx += numChannels; + demodDataOut->data[j] = dataOut[idx]; + } + } + +// if (doVis) { +// iqDataOutQueue->push(demodDataOut); +// if (doVis>1) { +// iqVisualQueue->push(demodDataOut); +// } +// } + + if (doDemodVis) { + iqActiveDemodVisualQueue->push(demodDataOut); + } + + for (int j = 0; j < nRunDemods; j++) { + if (demodChannel[j] == i) { + DemodulatorInstance *demod = runDemods[j]; + demod->getIQInputDataPipe()->push(demodDataOut); +// std::cout << "Demodulator " << j << " in channel #" << i << " ctr: " << chanCenters[i] << " dataSize: " << chanDataSize << std::endl; + } } - } - - if (!pushedData && iqDataOutQueue == NULL) { - demodDataOut->setRefCount(0); } } diff --git a/src/sdr/SDRPostThread.h b/src/sdr/SDRPostThread.h index 6137df36..7a9b6349 100644 --- a/src/sdr/SDRPostThread.h +++ b/src/sdr/SDRPostThread.h @@ -21,19 +21,41 @@ class SDRPostThread : public IOThread { void run(); void terminate(); + void setIQVisualRange(long long frequency, int bandwidth); + protected: SDRThreadIQDataQueue *iqDataInQueue; DemodulatorThreadInputQueue *iqDataOutQueue; DemodulatorThreadInputQueue *iqVisualQueue; - + DemodulatorThreadInputQueue *iqActiveDemodVisualQueue; + std::mutex busy_demod; std::vector demodulators; - iirfilt_crcf dcFilter; std::atomic_bool swapIQ; - ReBuffer visualDataBuffers; - private: - std::vector _lut; - std::vector _lut_swap; + void initPFBChannelizer(); + void updateActiveDemodulators(); + void updateChannels(); + int getChannelAt(long long frequency); + + ReBuffer buffers; + std::vector fpData; + std::vector dataOut; + std::vector chanCenters; + long long chanBw; + + int nRunDemods; + std::vector runDemods; + std::vector demodChannel; + std::vector demodChannelActive; + + ReBuffer visualDataBuffers; + atomic_bool doRefresh; + atomic_llong visFrequency; + atomic_int visBandwidth; + int numChannels, sampleRate; + long long frequency; + firpfbch_crcf channelizer; + iirfilt_crcf dcFilter; }; diff --git a/src/sdr/SoapySDRThread.cpp b/src/sdr/SoapySDRThread.cpp index 61da248f..8fe79956 100644 --- a/src/sdr/SoapySDRThread.cpp +++ b/src/sdr/SoapySDRThread.cpp @@ -28,6 +28,9 @@ SDRThread::SDRThread() : IOThread() { hasPPM.store(false); hasHardwareDC.store(false); + numChannels.store(8); + +// dcFilter = iirfilt_crcf_create_dc_blocker(0.0005); } SDRThread::~SDRThread() { @@ -77,7 +80,7 @@ void SDRThread::init() { } if (chan->hasHardwareDC()) { hasHardwareDC.store(true); - wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Found hardware DC offset correction support, internal disabled.")); +// wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Found hardware DC offset correction support, internal disabled.")); device->setDCOffsetMode(SOAPY_SDR_RX, chan->getChannel(), true); } else { hasHardwareDC.store(false); @@ -85,7 +88,9 @@ void SDRThread::init() { device->setGainMode(SOAPY_SDR_RX,0,true); - numElems = getOptimalElementCount(sampleRate.load(), 60); + numChannels.store(getOptimalChannelCount(sampleRate.load())); + numElems.store(getOptimalElementCount(sampleRate.load(), 30)); + inpBuffer.data.resize(numElems.load()); buffs[0] = malloc(numElems * 2 * sizeof(float)); } @@ -101,30 +106,36 @@ void SDRThread::readStream(SDRThreadIQDataQueue* iqDataOutQueue) { int flags; long long timeNs; - SDRThreadIQData *dataOut = buffers.getBuffer(); - if (dataOut->data.size() != numElems * 2) { - dataOut->data.resize(numElems * 2); - } int n_read = 0; - while (n_read != numElems) { + while (n_read != numElems && !terminated) { int n_stream_read = device->readStream(stream, buffs, numElems-n_read, flags, timeNs); if (n_stream_read > 0) { - memcpy(&dataOut->data[n_read * 2], buffs[0], n_stream_read * sizeof(float) * 2); + memcpy(&inpBuffer.data[n_read], buffs[0], n_stream_read * sizeof(float) * 2); n_read += n_stream_read; } else { - dataOut->data.resize(n_read); break; } } - // std::cout << n_read << std::endl; - - if (n_read > 0) { + if (n_read > 0 && !terminated) { + SDRThreadIQData *dataOut = buffers.getBuffer(); + +// if (hasHardwareDC) { + dataOut->data.assign(inpBuffer.data.begin(), inpBuffer.data.begin()+n_read); +// } else { +// if (dataOut->data.size() != n_read) { +// dataOut->data.resize(n_read); +// } +// iirfilt_crcf_execute_block(dcFilter, &inpBuffer.data[0], n_read, &dataOut->data[0]); +// } + + dataOut->setRefCount(1); - dataOut->frequency = frequency; + dataOut->frequency = frequency.load(); dataOut->sampleRate = sampleRate.load(); - dataOut->dcCorrected = hasHardwareDC; + dataOut->dcCorrected = hasHardwareDC.load(); + dataOut->numChannels = numChannels.load(); iqDataOutQueue->push(dataOut); } @@ -148,7 +159,9 @@ void SDRThread::readLoop() { if (rate_changed.load()) { device->setSampleRate(SOAPY_SDR_RX,0,sampleRate.load()); sampleRate.store(device->getSampleRate(SOAPY_SDR_RX,0)); + numChannels.store(getOptimalChannelCount(sampleRate.load())); numElems.store(getOptimalElementCount(sampleRate.load(), 60)); + inpBuffer.data.resize(numElems.load()); free(buffs[0]); buffs[0] = malloc(numElems.load() * 2 * sizeof(float)); rate_changed.store(false); @@ -174,7 +187,7 @@ void SDRThread::readLoop() { void SDRThread::run() { //#ifdef __APPLE__ // pthread_t tID = pthread_self(); // ID of this thread -// int priority = sched_get_priority_max( SCHED_FIFO) - 1; +// int priority = sched_get_priority_max( SCHED_FIFO); // sched_param prio = { priority }; // scheduling priority of thread // pthread_setschedparam(tID, SCHED_FIFO, &prio); //#endif @@ -214,11 +227,31 @@ void SDRThread::setDevice(SDRDeviceInfo *dev) { int SDRThread::getOptimalElementCount(long long sampleRate, int fps) { int elemCount = (int)floor((double)sampleRate/(double)fps); - elemCount = int(ceil((double)elemCount/512.0)*512.0); - std::cout << "Calculated optimal element count of " << elemCount << std::endl; + int nch = numChannels.load(); + elemCount = int(ceil((double)elemCount/(double)nch))*nch; + std::cout << "Calculated optimal " << numChannels.load() << " channel element count of " << elemCount << std::endl; return elemCount; } +int SDRThread::getOptimalChannelCount(long long sampleRate) { + int optimal_rate = CHANNELIZER_RATE_MAX; + int optimal_count = int(ceil(double(sampleRate)/double(optimal_rate))); + + if (optimal_count % 2 == 1) { + optimal_count--; + } + + if (optimal_count < 4) { + optimal_count = 4; + } + +// if (optimal_count > 16) { +// optimal_count = 16; +// } + return optimal_count; +} + + void SDRThread::setFrequency(long long freq) { if (freq < sampleRate.load() / 2) { freq = sampleRate.load() / 2; diff --git a/src/sdr/SoapySDRThread.h b/src/sdr/SoapySDRThread.h index 542ce35e..d86044f8 100644 --- a/src/sdr/SoapySDRThread.h +++ b/src/sdr/SoapySDRThread.h @@ -18,10 +18,11 @@ class SDRThreadIQData: public ReferenceCounter { long long frequency; long long sampleRate; bool dcCorrected; - std::vector data; + int numChannels; + std::vector data; SDRThreadIQData() : - frequency(0), sampleRate(DEFAULT_SAMPLE_RATE), dcCorrected(true) { + frequency(0), sampleRate(DEFAULT_SAMPLE_RATE), dcCorrected(true), numChannels(0) { } @@ -54,6 +55,7 @@ class SDRThread : public IOThread { SDRDeviceInfo *getDevice(); void setDevice(SDRDeviceInfo *dev); int getOptimalElementCount(long long sampleRate, int fps); + int getOptimalChannelCount(long long sampleRate); void setFrequency(long long freq); long long getFrequency(); @@ -75,13 +77,13 @@ class SDRThread : public IOThread { SoapySDR::Device *device; void *buffs[1]; ReBuffer buffers; - + SDRThreadIQData inpBuffer; std::atomic deviceConfig; std::atomic deviceInfo; std::atomic sampleRate; std::atomic_llong frequency, offset; - std::atomic_int ppm, direct_sampling_mode, numElems; + std::atomic_int ppm, direct_sampling_mode, numElems, numChannels; std::atomic_bool hasPPM, hasHardwareDC; std::atomic_bool rate_changed, freq_changed, offset_changed, diff --git a/src/visual/TuningCanvas.cpp b/src/visual/TuningCanvas.cpp index a9fe015e..ffe2a361 100644 --- a/src/visual/TuningCanvas.cpp +++ b/src/visual/TuningCanvas.cpp @@ -190,8 +190,8 @@ void TuningCanvas::StepTuner(ActiveState state, int exponent, bool up) { bw += amount; } - if (bw > wxGetApp().getSampleRate()) { - bw = wxGetApp().getSampleRate(); + if (bw > CHANNELIZER_RATE_MAX) { + bw = CHANNELIZER_RATE_MAX; } wxGetApp().getDemodMgr().setLastBandwidth(bw); diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index 91e18d6a..5b5cbe87 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -100,6 +100,7 @@ void WaterfallCanvas::processInputQueue() { break; } } + waterfallPanel.update(); tex_update.unlock(); } }} @@ -435,8 +436,8 @@ void WaterfallCanvas::OnMouseMoved(wxMouseEvent& event) { int currentBW = demod->getBandwidth(); currentBW = currentBW + bwDiff; - if (currentBW > wxGetApp().getSampleRate()) { - currentBW = wxGetApp().getSampleRate(); + if (currentBW > CHANNELIZER_RATE_MAX) { + currentBW = CHANNELIZER_RATE_MAX; } if (currentBW < MIN_BANDWIDTH) { currentBW = MIN_BANDWIDTH;