diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index 8858ac2..d1a26bf 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -104,6 +104,7 @@ AppFrame::AppFrame() : waterfallCanvas = new WaterfallCanvas(this, NULL); waterfallCanvas->setup(2048, 512); waterfallCanvas->attachSpectrumCanvas(spectrumCanvas); + waterfallCanvas->attachWaterfallCanvas(demodWaterfallCanvas); spectrumCanvas->attachWaterfallCanvas(waterfallCanvas); vbox->Add(waterfallCanvas, 20, wxEXPAND | wxALL, 0); @@ -390,50 +391,11 @@ void AppFrame::OnIdle(wxIdleEvent& event) { activeDemodulator = demod; } - if (!wxGetApp().getIQVisualQueue()->empty()) { - DemodulatorThreadIQData *iqData; - wxGetApp().getIQVisualQueue()->pop(iqData); - - if (iqData && iqData->data.size()) { -// spectrumCanvas->setData(iqData); - waterfallCanvas->setData(iqData); - demodWaterfallCanvas->setData(iqData); - delete iqData; - } else { - std::cout << "Incoming IQ data empty?" << std::endl; - } - work_done = true; - } - - if (!wxGetApp().getAudioVisualQueue()->empty()) { - AudioThreadInput *demodAudioData; - wxGetApp().getAudioVisualQueue()->pop(demodAudioData); - if (demodAudioData && demodAudioData->data.size()) { - if (scopeCanvas->waveform_points.size() != demodAudioData->data.size() * 2) { - scopeCanvas->waveform_points.resize(demodAudioData->data.size() * 2); - } - - for (int i = 0, iMax = demodAudioData->data.size(); i < iMax; i++) { - scopeCanvas->waveform_points[i * 2 + 1] = demodAudioData->data[i] * 0.5f; - scopeCanvas->waveform_points[i * 2] = ((double) i / (double) iMax); - } - - scopeCanvas->setStereo(demodAudioData->channels == 2); - - delete demodAudioData; - } else { - std::cout << "Incoming Demodulator data empty?" << std::endl; - } - work_done = true; - } - if (!waterfallCanvas->HasFocus()) { waterfallCanvas->SetFocus(); } - if (!work_done) { - event.Skip(); - } + event.Skip(); } void AppFrame::saveSession(std::string fileName) { diff --git a/src/CubicSDR.cpp b/src/CubicSDR.cpp index cbd6cc6..4f428ca 100644 --- a/src/CubicSDR.cpp +++ b/src/CubicSDR.cpp @@ -185,6 +185,7 @@ void CubicSDR::removeDemodulator(DemodulatorInstance *demod) { if (!demod) { return; } + demod->setActive(false); sdrPostThread->removeDemodulator(demod); } diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index 896b10c..ccbb3d1 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -126,14 +126,14 @@ bool DemodulatorInstance::isTerminated() { case DemodulatorThreadCommand::DEMOD_THREAD_CMD_AUDIO_TERMINATED: t_Audio->join(); audioTerminated = true; -// delete t_Audio; + delete t_Audio; break; case DemodulatorThreadCommand::DEMOD_THREAD_CMD_DEMOD_TERMINATED: #ifdef __APPLE__ pthread_join(t_Demod, NULL); #else t_Demod->join(); -// delete t_Demod; + delete t_Demod; #endif demodTerminated = true; break; @@ -142,7 +142,7 @@ bool DemodulatorInstance::isTerminated() { pthread_join(t_PreDemod, NULL); #else t_PreDemod->join(); -// delete t_PreDemod; + delete t_PreDemod; #endif preDemodTerminated = true; break; diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index 7194109..9f853f6 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -90,6 +90,9 @@ void DemodulatorThread::threadMain() { iqAutoGain = agc_crcf_create(); agc_crcf_set_bandwidth(iqAutoGain, 0.9); + AudioThreadInput *ati_vis = new AudioThreadInput; + ati_vis->data.reserve(DEMOD_VIS_SIZE); + std::cout << "Demodulator thread started.." << std::endl; terminated = false; @@ -294,7 +297,6 @@ void DemodulatorThread::threadMain() { } if (ati && audioVisOutputQueue != NULL && audioVisOutputQueue->empty()) { - AudioThreadInput *ati_vis = new AudioThreadInput; int num_vis = DEMOD_VIS_SIZE; if (stereo) { @@ -303,6 +305,7 @@ void DemodulatorThread::threadMain() { if (stereoSize > DEMOD_VIS_SIZE) { stereoSize = DEMOD_VIS_SIZE; } + ati_vis->data.resize(stereoSize); for (int i = 0; i < stereoSize / 2; i++) { @@ -406,6 +409,12 @@ void DemodulatorThread::threadMain() { delete audioDataDel; } + if (audioVisOutputQueue && !audioVisOutputQueue->empty()) { + AudioThreadInput *dummy_vis; + audioVisOutputQueue->pop(dummy_vis); + } + delete ati_vis; + DemodulatorThreadCommand tCmd(DemodulatorThreadCommand::DEMOD_THREAD_CMD_DEMOD_TERMINATED); tCmd.context = this; threadQueueNotify->push(tCmd); diff --git a/src/demod/DemodulatorWorkerThread.cpp b/src/demod/DemodulatorWorkerThread.cpp index 58bc1de..9fd228c 100644 --- a/src/demod/DemodulatorWorkerThread.cpp +++ b/src/demod/DemodulatorWorkerThread.cpp @@ -24,6 +24,9 @@ void DemodulatorWorkerThread::threadMain() { commandQueue->pop(command); switch (command.cmd) { case DemodulatorWorkerThreadCommand::DEMOD_WORKER_THREAD_CMD_BUILD_FILTERS: + if (!filterCommand.bandwidth || !filterCommand.audioSampleRate) { + break; + } filterChanged = true; filterCommand = command; break; @@ -32,13 +35,14 @@ void DemodulatorWorkerThread::threadMain() { } done = commandQueue->empty(); } + if (filterChanged && !terminated) { DemodulatorWorkerThreadResult result(DemodulatorWorkerThreadResult::DEMOD_WORKER_THREAD_RESULT_FILTERS); result.iqResampleRatio = (double) (filterCommand.bandwidth) / (double) filterCommand.sampleRate; result.audioResamplerRatio = (double) (filterCommand.audioSampleRate) / (double) filterCommand.bandwidth; - + float As = 60.0f; // stop-band attenuation [dB] result.iqResampler = msresamp_crcf_create(result.iqResampleRatio, As); diff --git a/src/sdr/SDRPostThread.cpp b/src/sdr/SDRPostThread.cpp index 168d623..c8b1037 100644 --- a/src/sdr/SDRPostThread.cpp +++ b/src/sdr/SDRPostThread.cpp @@ -55,6 +55,8 @@ void SDRPostThread::threadMain() { dcFilter = iirfilt_crcf_create_dc_blocker(0.0005); + DemodulatorThreadIQData *visualDataOut = new DemodulatorThreadIQData; + std::cout << "SDR post-processing thread started.." << std::endl; std::deque buffers; @@ -96,7 +98,13 @@ void SDRPostThread::threadMain() { } if (iqVisualQueue != NULL && iqVisualQueue.load()->empty()) { - DemodulatorThreadIQData *visualDataOut = new DemodulatorThreadIQData; + 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); + } + visualDataOut->frequency = data_in->frequency; visualDataOut->sampleRate = data_in->sampleRate; visualDataOut->data.assign(dataOut.begin(), dataOut.begin() + num_vis_samples); @@ -200,6 +208,12 @@ void SDRPostThread::threadMain() { // std::lock_guard < std::mutex > lock(demodDataDel->m_mutex); // delete demodDataDel; } + if (iqVisualQueue.load() && !iqVisualQueue.load()->empty()) { + DemodulatorThreadIQData *visualDataDummy; + iqVisualQueue.load()->pop(visualDataDummy); + } + + delete visualDataOut; std::cout << "SDR post-processing thread done." << std::endl; } diff --git a/src/visual/ScopeCanvas.cpp b/src/visual/ScopeCanvas.cpp index ef04240..03db708 100644 --- a/src/visual/ScopeCanvas.cpp +++ b/src/visual/ScopeCanvas.cpp @@ -47,6 +47,25 @@ void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { wxPaintDC dc(this); const wxSize ClientSize = GetClientSize(); + if (!wxGetApp().getAudioVisualQueue()->empty()) { + AudioThreadInput *demodAudioData; + wxGetApp().getAudioVisualQueue()->pop(demodAudioData); + if (demodAudioData && demodAudioData->data.size()) { + if (waveform_points.size() != demodAudioData->data.size() * 2) { + waveform_points.resize(demodAudioData->data.size() * 2); + } + + for (int i = 0, iMax = demodAudioData->data.size(); i < iMax; i++) { + waveform_points[i * 2 + 1] = demodAudioData->data[i] * 0.5f; + waveform_points[i * 2] = ((double) i / (double) iMax); + } + + setStereo(demodAudioData->channels == 2); + } else { + std::cout << "Incoming Demodulator data empty?" << std::endl; + } + } + glContext->SetCurrent(*this); glViewport(0, 0, ClientSize.x, ClientSize.y); diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index 88879c7..3fed1be 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -36,7 +36,7 @@ wxEND_EVENT_TABLE() WaterfallCanvas::WaterfallCanvas(wxWindow *parent, int *attribList) : InteractiveCanvas(parent, attribList), spectrumCanvas(NULL), dragState(WF_DRAG_NONE), nextDragState(WF_DRAG_NONE), fft_size(0), waterfall_lines( 0), plan( - NULL), in(NULL), out(NULL), resampler(NULL), resamplerRatio(0), lastInputBandwidth(0), zoom(1), mouseZoom(1) { + NULL), in(NULL), out(NULL), resampler(NULL), resamplerRatio(0), lastInputBandwidth(0), zoom(1), mouseZoom(1), otherWaterfallCanvas(NULL), polling(true) { glContext = new WaterfallContext(this, &wxGetApp().GetContext(this)); @@ -92,6 +92,21 @@ void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { wxPaintDC dc(this); const wxSize ClientSize = GetClientSize(); + + if (polling && !wxGetApp().getIQVisualQueue()->empty()) { + DemodulatorThreadIQData *iqData; + wxGetApp().getIQVisualQueue()->pop(iqData); + + if (iqData && iqData->data.size()) { + setData(iqData); + if (otherWaterfallCanvas) { + otherWaterfallCanvas->setData(iqData); + } + } else { + std::cout << "Incoming IQ data empty?" << std::endl; + } + } + glContext->SetCurrent(*this); glViewport(0, 0, ClientSize.x, ClientSize.y); @@ -824,3 +839,17 @@ void WaterfallCanvas::OnMouseRightReleased(wxMouseEvent& event) { mouseTracker.setHorizDragLock(false); mouseZoom = 1.0; } + +void WaterfallCanvas::attachWaterfallCanvas(WaterfallCanvas* canvas_in) { + otherWaterfallCanvas = canvas_in; + otherWaterfallCanvas->setPolling(false); +} + + +bool WaterfallCanvas::isPolling() { + return polling; +} + +void WaterfallCanvas::setPolling(bool polling) { + this->polling = polling; +} diff --git a/src/visual/WaterfallCanvas.h b/src/visual/WaterfallCanvas.h index 7ab97e9..07ed755 100644 --- a/src/visual/WaterfallCanvas.h +++ b/src/visual/WaterfallCanvas.h @@ -29,6 +29,10 @@ public: DragState getNextDragState(); void attachSpectrumCanvas(SpectrumCanvas *canvas_in); + void attachWaterfallCanvas(WaterfallCanvas *canvas_in); + + bool isPolling(); + void setPolling(bool polling); private: void OnPaint(wxPaintEvent& event); @@ -46,9 +50,12 @@ private: void OnMouseEnterWindow(wxMouseEvent& event); void OnMouseLeftWindow(wxMouseEvent& event); - SpectrumCanvas *spectrumCanvas; std::vector spectrum_points; + SpectrumCanvas *spectrumCanvas; + WaterfallCanvas *otherWaterfallCanvas; + bool polling; + fftwf_complex *in, *out; fftwf_plan plan; diff --git a/src/visual/WaterfallContext.cpp b/src/visual/WaterfallContext.cpp index 94fcbc8..e22ee03 100644 --- a/src/visual/WaterfallContext.cpp +++ b/src/visual/WaterfallContext.cpp @@ -25,9 +25,12 @@ void WaterfallContext::Setup(int fft_size_in, int num_waterfall_lines_in) { delete waterfall_tex[i]; } - waterfall_tex[i] = new unsigned char[half_fft_size * waterfall_lines]; - memset(waterfall_tex[i], 0, half_fft_size * waterfall_lines); + waterfall_tex[i] = new unsigned char[half_fft_size * waterfall_lines * 2]; + memset(waterfall_tex[i], 0, half_fft_size * waterfall_lines * 2); } + // Stagger memory updates at half intervals for tiles + waterfall_ofs[0] = waterfall_lines; + waterfall_ofs[1] = waterfall_lines - waterfall_lines / 8; } void WaterfallContext::refreshTheme() { @@ -71,51 +74,70 @@ void WaterfallContext::Draw(std::vector &points) { if (points.size()) { for (int j = 0; j < 2; j++) { - memmove(waterfall_tex[j] + half_fft_size, waterfall_tex[j], (waterfall_lines - 1) * half_fft_size); + int ofs = waterfall_ofs[j]; for (int i = 0, iMax = half_fft_size; i < iMax; i++) { float v = points[(j * half_fft_size + i) * 2 + 1]; - float wv = v; - if (wv < 0.0) - wv = 0.0; - if (wv > 0.99) - wv = 0.99; - waterfall_tex[j][i] = (unsigned char) floor(wv * 255.0); + float wv = v < 0 ? 0 : (v > 0.99 ? 0.99 : v); + + waterfall_tex[j][i + ofs * half_fft_size] = (unsigned char) floor(wv * 255.0); } + + int quarter_lines = (waterfall_lines / 4); + int k = 4; + while (k--) { + if (waterfall_ofs[j] == quarter_lines * k) { + memcpy(waterfall_tex[j] + (waterfall_lines * half_fft_size) + (quarter_lines * k * half_fft_size), + waterfall_tex[j] + (quarter_lines * k * half_fft_size), quarter_lines * half_fft_size); + } + } + + if (waterfall_ofs[j] == 0) { + waterfall_ofs[j] = waterfall_lines; + } + + glBindTexture(GL_TEXTURE_2D, waterfall[j]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, half_fft_size, waterfall_lines, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, + (GLvoid *) (waterfall_tex[j] + (half_fft_size * (waterfall_ofs[j])))); + + waterfall_ofs[j]--; } - - } - - for (int i = 0; i < 2; i++) { - glBindTexture(GL_TEXTURE_2D, waterfall[i]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, half_fft_size, waterfall_lines, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, (GLvoid *) waterfall_tex[i]); } glColor3f(1.0, 1.0, 1.0); + GLint vp[4]; + glGetIntegerv( GL_VIEWPORT, vp); + + float viewWidth = (float) vp[2]; + + // some bias to prevent seams at odd scales + float half_pixel = 1.0 / (float) viewWidth; + float half_texel = 1.0 / (float) half_fft_size; + glBindTexture(GL_TEXTURE_2D, waterfall[0]); glBegin(GL_QUADS); - glTexCoord2f(0.0, 1.0); + glTexCoord2f(0.0 + half_texel, 1.0 - half_texel); glVertex3f(-1.0, -1.0, 0.0); - glTexCoord2f(1.0, 1.0); - glVertex3f(0.0, -1.0, 0.0); - glTexCoord2f(1.0, 0.0); - glVertex3f(0.0, 1.0, 0.0); - glTexCoord2f(0.0, 0.0); + glTexCoord2f(1.0 - half_texel, 1.0 - half_texel); + glVertex3f(0.0 + half_pixel, -1.0, 0.0); + glTexCoord2f(1.0 - half_texel, 0.0 + half_texel); + glVertex3f(0.0 + half_pixel, 1.0, 0.0); + glTexCoord2f(0.0 + half_texel, 0.0 + half_texel); glVertex3f(-1.0, 1.0, 0.0); glEnd(); glBindTexture(GL_TEXTURE_2D, waterfall[1]); glBegin(GL_QUADS); - glTexCoord2f(0.0, 1.0); - glVertex3f(0.0, -1.0, 0.0); - glTexCoord2f(1.0, 1.0); + glTexCoord2f(0.0 + half_texel, 1.0 - half_texel); + glVertex3f(0.0 - half_pixel, -1.0, 0.0); + glTexCoord2f(1.0 - half_texel, 1.0 - half_texel); glVertex3f(1.0, -1.0, 0.0); - glTexCoord2f(1.0, 0.0); + glTexCoord2f(1.0 - half_texel, 0.0 + half_texel); glVertex3f(1.0, 1.0, 0.0); - glTexCoord2f(0.0, 0.0); - glVertex3f(0.0, 1.0, 0.0); + glTexCoord2f(0.0 + half_texel, 0.0 + half_texel); + glVertex3f(0.0 - half_pixel, 1.0, 0.0); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); diff --git a/src/visual/WaterfallContext.h b/src/visual/WaterfallContext.h index 18dfaca..d22ba88 100644 --- a/src/visual/WaterfallContext.h +++ b/src/visual/WaterfallContext.h @@ -17,6 +17,7 @@ public: private: GLuint waterfall[2]; unsigned char *waterfall_tex[2]; + int waterfall_ofs[2]; int fft_size; int waterfall_lines;