diff --git a/external/cubicvr2/math/mat4.h b/external/cubicvr2/math/mat4.h index 0059cd8..b847d26 100644 --- a/external/cubicvr2/math/mat4.h +++ b/external/cubicvr2/math/mat4.h @@ -74,13 +74,24 @@ namespace CubicVR { return mOut; } - - static mat4 perspective(__float fovy, __float aspect, __float znear, __float zfar) { + static mat4 frustum(__float left, __float right, __float bottom, __float top, __float zNear, __float zFar) { + __float A = (right + left) / (right - left); + __float B = (top + bottom) / (top - bottom); + __float C = - (zFar + zNear) / (zFar - zNear); + __float D = - (-2.0 * zFar * zNear) / (zFar - zNear); + + + return mat4((2.0 * zNear) / (right - left), 0, A, 0, + 0, (2.0 * zNear) / (top - bottom), B, 0, + 0, 0, C, D, + 0, 0, -1, 0); + }; + static mat4 perspective(__float fovy, __float aspect, __float zNear, __float zFar) { __float yFac = tan(fovy * (float)M_PI / 360.0f); __float xFac = yFac * aspect; - return mat4( - 1.0f / xFac, 0, 0, 0, 0, 1.0f / yFac, 0, 0, 0, 0, -(zfar + znear) / (zfar - znear), -1, 0, 0, -(2.0f * zfar * znear) / (zfar - znear), 0); + return mat4::frustum(-xFac, xFac, -yFac, yFac, zNear, zFar); + }; static mat4 ortho(__float left,__float right,__float bottom,__float top,__float znear,__float zfar) { return mat4(2.0f / (right - left), 0, 0, 0, 0, 2.0f / (top - bottom), 0, 0, 0, 0, -2.0f / (zfar - znear), 0, -(left + right) / (right - left), -(top + bottom) / (top - bottom), -(zfar + znear) / (zfar - znear), 1); @@ -300,8 +311,18 @@ namespace CubicVR { return mat4::translate(-eyex,-eyey,-eyez) * mat4( side[0], up[0], -forward[0], 0, side[1], up[1], -forward[1], 0, side[2], up[2], -forward[2], 0, 0, 0, 0, 1); }; + + static vec3 unProject(mat4 pMatrix, mat4 mvMatrix, float width, float height, float winx, float winy, float winz) { + vec4 p(((winx / width) * 2.0) - 1.0, -(((winy / height) * 2.0) - 1.0), 1.0, 1.0); + + vec4 invp = mat4::vec4_multiply(mat4::vec4_multiply(p, mat4::inverse(pMatrix)), mat4::inverse(mvMatrix)); + + vec3 result(invp[0] / invp[3], invp[1] / invp[3], invp[2] / invp[3]); + + return result; + }; }; - -} + } + #endif /* defined(__CubicVR2__mat4__) */ diff --git a/external/fftw-3.3.4/64/libfftw3f-3.lib b/external/fftw-3.3.4/64/libfftw3f-3.lib deleted file mode 100644 index 4f2f89d..0000000 Binary files a/external/fftw-3.3.4/64/libfftw3f-3.lib and /dev/null differ diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index e843109..0a20f77 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -90,7 +90,9 @@ AppFrame::AppFrame() : demodTray->AddSpacer(1); scopeCanvas = new ScopeCanvas(this, attribList); + scopeCanvas->setHelpTip("Audio Visuals, drag left/right to toggle Scope or Spectrum."); demodScopeTray->Add(scopeCanvas, 8, wxEXPAND | wxALL, 0); + wxGetApp().getScopeProcessor()->setup(2048); wxGetApp().getScopeProcessor()->attachOutput(scopeCanvas->getInputQueue()); demodScopeTray->AddSpacer(1); @@ -153,11 +155,12 @@ AppFrame::AppFrame() : waterfallCanvas->setup(2048, 512); waterfallDataThread = new FFTVisualDataThread(); - t_FFTData = new std::thread(&FFTVisualDataThread::threadMain, waterfallDataThread); waterfallDataThread->setInputQueue("IQDataInput", wxGetApp().getWaterfallVisualQueue()); waterfallDataThread->setOutputQueue("FFTDataOutput", waterfallCanvas->getVisualDataQueue()); - + + t_FFTData = new std::thread(&FFTVisualDataThread::threadMain, waterfallDataThread); + waterfallSpeedMeter = new MeterCanvas(this, attribList); waterfallSpeedMeter->setHelpTip("Waterfall speed, click or drag to adjust (max 1024 lines per second)"); waterfallSpeedMeter->setMax(sqrt(1024)); @@ -789,6 +792,11 @@ void AppFrame::OnIdle(wxIdleEvent& event) { scopeCanvas->setPPMMode(demodTuner->isAltDown()); + scopeCanvas->setShowDb(spectrumCanvas->getShowDb()); + wxGetApp().getScopeProcessor()->setScopeEnabled(scopeCanvas->scopeVisible()); + wxGetApp().getScopeProcessor()->setSpectrumEnabled(scopeCanvas->spectrumVisible()); + wxGetApp().getAudioVisualQueue()->set_max_num_items((scopeCanvas->scopeVisible()?1:0) + (scopeCanvas->spectrumVisible()?1:0)); + wxGetApp().getScopeProcessor()->run(); wxGetApp().getSpectrumDistributor()->run(); diff --git a/src/audio/AudioThread.h b/src/audio/AudioThread.h index d5dcb61..01ebda5 100644 --- a/src/audio/AudioThread.h +++ b/src/audio/AudioThread.h @@ -14,6 +14,7 @@ class AudioThreadInput: public ReferenceCounter { public: long long frequency; + int inputRate; int sampleRate; int channels; float peak; diff --git a/src/demod/DemodulatorPreThread.cpp b/src/demod/DemodulatorPreThread.cpp index c93df6f..5c3820f 100644 --- a/src/demod/DemodulatorPreThread.cpp +++ b/src/demod/DemodulatorPreThread.cpp @@ -72,9 +72,6 @@ void DemodulatorPreThread::initialize() { } DemodulatorPreThread::~DemodulatorPreThread() { - delete workerThread; - delete workerQueue; - delete workerResults; } void DemodulatorPreThread::run() { @@ -260,7 +257,7 @@ void DemodulatorPreThread::run() { inp->decRefCount(); - if (!workerResults->empty()) { + if (!terminated && !workerResults->empty()) { while (!workerResults->empty()) { DemodulatorWorkerThreadResult result; workerResults->pop(result); @@ -323,7 +320,12 @@ void DemodulatorPreThread::terminate() { terminated = true; DemodulatorThreadIQData *inp = new DemodulatorThreadIQData; // push dummy to nudge queue iqInputQueue->push(inp); + DemodulatorWorkerThreadCommand command(DemodulatorWorkerThreadCommand::DEMOD_WORKER_THREAD_CMD_NULL); + workerQueue->push(command); workerThread->terminate(); - t_Worker->detach(); + t_Worker->join(); delete t_Worker; + delete workerThread; + delete workerResults; + delete workerQueue; } diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index 485fd54..2ed31b0 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -310,6 +310,7 @@ void DemodulatorThread::run() { ati = outputBuffers.getBuffer(); ati->sampleRate = audioSampleRate; + ati->inputRate = inp->sampleRate; ati->setRefCount(1); if (demodulatorType == DEMOD_TYPE_RAW) { @@ -359,7 +360,9 @@ void DemodulatorThread::run() { if (ati && audioVisOutputQueue != NULL && audioVisOutputQueue->empty()) { ati_vis->busy_update.lock(); - + ati_vis->sampleRate = inp->sampleRate; + ati_vis->inputRate = inp->sampleRate; + int num_vis = DEMOD_VIS_SIZE; if (demodulatorType == DEMOD_TYPE_RAW || (stereo && inp->sampleRate >= 100000)) { ati_vis->channels = 2; @@ -377,6 +380,8 @@ void DemodulatorThread::run() { } } else { for (int i = 0; i < stereoSize / 2; i++) { + ati_vis->inputRate = audioSampleRate; + ati_vis->sampleRate = 36000; ati_vis->data[i] = ati->data[i * 2]; ati_vis->data[i + stereoSize / 2] = ati->data[i * 2 + 1]; } @@ -384,7 +389,7 @@ void DemodulatorThread::run() { } else { ati_vis->channels = 1; if (numAudioWritten > bufSize) { - + ati_vis->inputRate = audioSampleRate; if (num_vis > numAudioWritten) { num_vis = numAudioWritten; } @@ -399,9 +404,8 @@ void DemodulatorThread::run() { // std::cout << "Signal: " << agc_crcf_get_signal_level(agc) << " -- " << agc_crcf_get_rssi(agc) << "dB " << std::endl; } - audioVisOutputQueue->push(ati_vis); - ati_vis->busy_update.unlock(); + audioVisOutputQueue->push(ati_vis); } if (ati != NULL) { diff --git a/src/demod/DemodulatorThread.h b/src/demod/DemodulatorThread.h index 7f7e417..91d6b0c 100644 --- a/src/demod/DemodulatorThread.h +++ b/src/demod/DemodulatorThread.h @@ -8,7 +8,7 @@ typedef ThreadQueue DemodulatorThreadOutputQueue; -#define DEMOD_VIS_SIZE 1024 +#define DEMOD_VIS_SIZE 2048 class DemodulatorThread : public IOThread { public: diff --git a/src/panel/ScopePanel.cpp b/src/panel/ScopePanel.cpp index c50fa08..f72a14c 100644 --- a/src/panel/ScopePanel.cpp +++ b/src/panel/ScopePanel.cpp @@ -2,6 +2,7 @@ #include "ColorTheme.h" ScopePanel::ScopePanel() : GLPanel(), scopeMode(SCOPE_MODE_Y) { + setFill(GLPanelFillType::GLPANEL_FILL_NONE); bgPanel.setFill(GLPanelFillType::GLPANEL_FILL_GRAD_BAR_Y); bgPanelStereo[0].setFill(GLPanelFillType::GLPANEL_FILL_GRAD_BAR_Y); bgPanelStereo[0].setPosition(0, 0.5); @@ -21,14 +22,13 @@ void ScopePanel::setPoints(std::vector &points) { } void ScopePanel::drawPanelContents() { - - glLineWidth(1.0); if (scopeMode == SCOPE_MODE_Y) { bgPanel.setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground, ThemeMgr::mgr.currentTheme->scopeBackground * 2.0); bgPanel.calcTransform(transform); bgPanel.draw(); - + glLineWidth(1.0); + glEnable(GL_LINE_SMOOTH); glLoadMatrixf(transform); glColor3f(ThemeMgr::mgr.currentTheme->scopeLine.r * 0.35, ThemeMgr::mgr.currentTheme->scopeLine.g * 0.35, ThemeMgr::mgr.currentTheme->scopeLine.b * 0.35); @@ -45,8 +45,10 @@ void ScopePanel::drawPanelContents() { bgPanelStereo[1].calcTransform(transform); bgPanelStereo[1].draw(); + glLineWidth(1.0); glLoadMatrixf(transform); glColor3f(ThemeMgr::mgr.currentTheme->scopeLine.r, ThemeMgr::mgr.currentTheme->scopeLine.g, ThemeMgr::mgr.currentTheme->scopeLine.b); + glEnable(GL_LINE_SMOOTH); glBegin (GL_LINES); glVertex2f(-1.0, 0.0); glVertex2f(1.0, 0.0); diff --git a/src/panel/SpectrumPanel.cpp b/src/panel/SpectrumPanel.cpp index c615490..034d3c5 100644 --- a/src/panel/SpectrumPanel.cpp +++ b/src/panel/SpectrumPanel.cpp @@ -5,7 +5,7 @@ #include #include "ColorTheme.h" -SpectrumPanel::SpectrumPanel() : floorValue(0), ceilValue(1), showDb(false) { +SpectrumPanel::SpectrumPanel() : floorValue(0), ceilValue(1), showDb(false), fftSize(2048) { setFill(GLPANEL_FILL_GRAD_Y); setFillColor(ThemeMgr::mgr.currentTheme->fftBackground * 2.0, ThemeMgr::mgr.currentTheme->fftBackground); @@ -51,6 +51,14 @@ long long SpectrumPanel::getBandwidth() { return bandwidth; } +void SpectrumPanel::setFFTSize(int fftSize_in) { + this->fftSize = fftSize_in; +} + +int SpectrumPanel::getFFTSize() { + return fftSize; +} + void SpectrumPanel::setShowDb(bool showDb) { this->showDb = showDb; if (showDb) { @@ -132,27 +140,58 @@ void SpectrumPanel::drawPanelContents() { long long leftFreq = (double) freq - ((double) bandwidth / 2.0); long long rightFreq = leftFreq + (double) bandwidth; - - long long firstMhz = (leftFreq / 1000000) * 1000000; - long double mhzStart = ((long double) (firstMhz - leftFreq) / (long double) (rightFreq - leftFreq)) * 2.0; + + long long hzStep = 1000000; long double mhzStep = (100000.0 / (long double) (rightFreq - leftFreq)) * 2.0; - double mhzVisualStep = 0.1f; - + double mhzVisualStep = 0.1; + + std::stringstream label; + label.precision(1); + if (mhzStep * 0.5 * viewWidth < 40) { mhzStep = (250000.0 / (long double) (rightFreq - leftFreq)) * 2.0; - mhzVisualStep = 0.25f; - } + mhzVisualStep = 0.25; - if (mhzStep * 0.5 * viewWidth > 400) { + if (mhzStep * 0.5 * viewWidth < 40) { + mhzStep = (500000.0 / (long double) (rightFreq - leftFreq)) * 2.0; + mhzVisualStep = 0.5; + } + + if (mhzStep * 0.5 * viewWidth < 40) { + mhzStep = (1000000.0 / (long double) (rightFreq - leftFreq)) * 2.0; + mhzVisualStep = 1.0; + } + + if (mhzStep * 0.5 * viewWidth < 40) { + mhzStep = (2500000.0 / (long double) (rightFreq - leftFreq)) * 2.0; + mhzVisualStep = 2.5; + } + + if (mhzStep * 0.5 * viewWidth < 40) { + mhzStep = (5000000.0 / (long double) (rightFreq - leftFreq)) * 2.0; + mhzVisualStep = 5.0; + } + + if (mhzStep * 0.5 * viewWidth < 40) { + mhzStep = (10000000.0 / (long double) (rightFreq - leftFreq)) * 2.0; + mhzVisualStep = 10.0; + } + + if (mhzStep * 0.5 * viewWidth < 40) { + mhzStep = (50000000.0 / (long double) (rightFreq - leftFreq)) * 2.0; + mhzVisualStep = 50.0; + } + } else if (mhzStep * 0.5 * viewWidth > 350) { mhzStep = (10000.0 / (long double) (rightFreq - leftFreq)) * 2.0; - mhzVisualStep = 0.01f; + mhzVisualStep = 0.01; + label.precision(2); } - - long double currentMhz = trunc(floor(firstMhz / 1000000.0)); - std::stringstream label; - label.precision(2); + long long firstMhz = (leftFreq / hzStep) * hzStep; + long double mhzStart = ((long double) (firstMhz - leftFreq) / (long double) (rightFreq - leftFreq)) * 2.0; + long double currentMhz = trunc(floor(firstMhz / (long double)1000000.0)); + double hPos = 1.0 - (16.0 / viewHeight); double lMhzPos = 1.0 - (5.0 / viewHeight); @@ -189,21 +228,20 @@ void SpectrumPanel::drawPanelContents() { glLineWidth(1.0); - if (showDb) { float dbPanelWidth = (1.0/viewWidth)*75.0; float dbPanelHeight = (1.0/viewHeight)*14.0; std::stringstream ssLabel; - ssLabel << std::fixed << std::setprecision(1) << (20.0 * log10(2.0*(getCeilValue())/2048.0)) << "dB"; + ssLabel << std::fixed << std::setprecision(1) << (20.0 * log10(2.0*(getCeilValue())/(double)fftSize)) << "dB"; dbPanelCeil.setText(ssLabel.str(), GLFont::GLFONT_ALIGN_RIGHT); dbPanelCeil.setSize(dbPanelWidth, dbPanelHeight); dbPanelCeil.setPosition(-1.0 + dbPanelWidth, 1.0 - dbPanelHeight); ssLabel.str(""); - ssLabel << (20.0 * log10(2.0*(getFloorValue())/2048.0)) << "dB"; + ssLabel << (20.0 * log10(2.0*(getFloorValue())/(double)fftSize)) << "dB"; dbPanelFloor.setText(ssLabel.str(), GLFont::GLFONT_ALIGN_RIGHT); dbPanelFloor.setSize(dbPanelWidth, dbPanelHeight); diff --git a/src/panel/SpectrumPanel.h b/src/panel/SpectrumPanel.h index 6ffa3f8..3ccd747 100644 --- a/src/panel/SpectrumPanel.h +++ b/src/panel/SpectrumPanel.h @@ -19,7 +19,10 @@ public: void setBandwidth(long long bandwidth); long long getBandwidth(); - + + void setFFTSize(int fftSize_in); + int getFFTSize(); + void setShowDb(bool showDb); bool getShowDb(); @@ -28,6 +31,7 @@ protected: private: float floorValue, ceilValue; + int fftSize; long long freq; long long bandwidth; std::vector points; diff --git a/src/process/ScopeVisualProcessor.cpp b/src/process/ScopeVisualProcessor.cpp index 15669da..1cc4d24 100644 --- a/src/process/ScopeVisualProcessor.cpp +++ b/src/process/ScopeVisualProcessor.cpp @@ -1,4 +1,54 @@ #include "ScopeVisualProcessor.h" +#include +#include + +ScopeVisualProcessor::ScopeVisualProcessor(): fftInData(NULL), fftwOutput(NULL), fftw_plan(NULL), maxScopeSamples(1024) { + scopeEnabled.store(true); + spectrumEnabled.store(true); + fft_average_rate = 0.65; +} + +ScopeVisualProcessor::~ScopeVisualProcessor() { + if (fftInData) { + free(fftInData); + } + if (fftwOutput) { + free(fftwOutput); + } + if (fftw_plan) { + fftwf_destroy_plan(fftw_plan); + } +} + + +void ScopeVisualProcessor::setup(int fftSize_in) { + fftSize = fftSize_in; + desiredInputSize = fftSize; + + if (fftInData) { + free(fftInData); + } + fftInData = (float*) fftwf_malloc(sizeof(float) * fftSize); + if (fftwOutput) { + free(fftwOutput); + } + fftwOutput = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize); + if (fftw_plan) { + fftwf_destroy_plan(fftw_plan); + } + fftw_plan = fftwf_plan_dft_r2c_1d(fftSize, fftInData, fftwOutput, FFTW_ESTIMATE); + //(fftSize, fftInData, fftwOutput, 0); + //(fftSize, fftwInput, fftwOutput, FFTW_R2HC, FFTW_ESTIMATE); + +} + +void ScopeVisualProcessor::setScopeEnabled(bool scopeEnable) { + scopeEnabled.store(scopeEnable); +} + +void ScopeVisualProcessor::setSpectrumEnabled(bool spectrumEnable) { + spectrumEnabled.store(spectrumEnable); +} void ScopeVisualProcessor::process() { if (!isOutputEmpty()) { @@ -11,42 +61,144 @@ void ScopeVisualProcessor::process() { if (!audioInputData) { return; } - int iMax = audioInputData->data.size(); + int i, iMax = audioInputData->data.size(); if (!iMax) { audioInputData->decRefCount(); return; } audioInputData->busy_update.lock(); - ScopeRenderData *renderData = outputBuffers.getBuffer(); - renderData->channels = audioInputData->channels; - if (renderData->waveform_points.size() != iMax * 2) { - renderData->waveform_points.resize(iMax * 2); - } + ScopeRenderData *renderData = NULL; + + if (scopeEnabled) { + iMax = audioInputData->data.size(); + if (iMax > maxScopeSamples) { + iMax = maxScopeSamples; + } - float peak = 1.0f; - - for (int i = 0; i < iMax; i++) { - float p = fabs(audioInputData->data[i]); - if (p > peak) { - peak = p; + renderData = outputBuffers.getBuffer(); + renderData->channels = audioInputData->channels; + renderData->inputRate = audioInputData->inputRate; + renderData->sampleRate = audioInputData->sampleRate; + + if (renderData->waveform_points.size() != iMax * 2) { + renderData->waveform_points.resize(iMax * 2); } - } - - if (audioInputData->channels == 2) { - for (int i = 0; i < iMax; i++) { - renderData->waveform_points[i * 2] = (((double) (i % (iMax/2)) / (double) iMax) * 2.0 - 0.5) * 2.0; - renderData->waveform_points[i * 2 + 1] = audioInputData->data[i] / peak; - } - } else { - for (int i = 0; i < iMax; i++) { - renderData->waveform_points[i * 2] = (((double) i / (double) iMax) - 0.5) * 2.0; - renderData->waveform_points[i * 2 + 1] = audioInputData->data[i] / peak; - } - } - distribute(renderData); + float peak = 1.0f; + + for (i = 0; i < iMax; i++) { + float p = fabs(audioInputData->data[i]); + if (p > peak) { + peak = p; + } + } + + if (audioInputData->channels == 2) { + iMax = audioInputData->data.size(); + if (renderData->waveform_points.size() != iMax * 2) { + renderData->waveform_points.resize(iMax * 2); + } + for (i = 0; i < iMax; i++) { + renderData->waveform_points[i * 2] = (((double) (i % (iMax/2)) / (double) iMax) * 2.0 - 0.5) * 2.0; + renderData->waveform_points[i * 2 + 1] = audioInputData->data[i] / peak; + } + } else { + for (i = 0; i < iMax; i++) { + renderData->waveform_points[i * 2] = (((double) i / (double) iMax) - 0.5) * 2.0; + renderData->waveform_points[i * 2 + 1] = audioInputData->data[i] / peak; + } + } + + renderData->spectrum = false; + + distribute(renderData); + } + + if (spectrumEnabled) { + renderData = outputBuffers.getBuffer(); + iMax = audioInputData->data.size(); + + if (audioInputData->channels==1) { + for (i = 0; i < fftSize; i++) { + if (i < iMax) { + fftInData[i] = audioInputData->data[i]; + } else { + fftInData[i] = 0; + } + } + } else if (audioInputData->channels==2) { + iMax = iMax/2; + for (i = 0; i < fftSize; i++) { + if (i < iMax) { + fftInData[i] = audioInputData->data[i] + audioInputData->data[iMax+i]; + } else { + fftInData[i] = 0; + } + } + } + + + fftwf_execute(fftw_plan); + + float fft_ceil = 0, fft_floor = 1; + + if (fft_result.size() < (fftSize/2)) { + fft_result.resize((fftSize/2)); + fft_result_ma.resize((fftSize/2)); + fft_result_maa.resize((fftSize/2)); + } + + for (i = 0; i < (fftSize/2); i++) { + float a = fftwOutput[i][0]; + float b = fftwOutput[i][1]; + fft_result[i] = sqrt( a * a + b * b); + } + + for (i = 0; i < (fftSize/2); i++) { + fft_result_maa[i] += (fft_result_ma[i] - fft_result_maa[i]) * fft_average_rate; + fft_result_ma[i] += (fft_result[i] - fft_result_ma[i]) * fft_average_rate; + + if (fft_result_maa[i] > fft_ceil) { + fft_ceil = fft_result_maa[i]; + } + if (fft_result_maa[i] < fft_floor) { + fft_floor = fft_result_maa[i]; + } + } + + fft_ceil_ma = fft_ceil_ma + (fft_ceil - fft_ceil_ma) * 0.05; + fft_ceil_maa = fft_ceil_maa + (fft_ceil_ma - fft_ceil_maa) * 0.05; + + fft_floor_ma = fft_floor_ma + (fft_floor - fft_floor_ma) * 0.05; + fft_floor_maa = fft_floor_maa + (fft_floor_ma - fft_floor_maa) * 0.05; + + int outSize = fftSize/2; + + if (audioInputData->sampleRate != audioInputData->inputRate) { + outSize = (int)floor((float)outSize * ((float)audioInputData->sampleRate/(float)audioInputData->inputRate)); + } + + if (renderData->waveform_points.size() != outSize*2) { + renderData->waveform_points.resize(outSize*2); + } + + for (i = 0; i < outSize; i++) { + float v = (log10(fft_result_maa[i]+0.25 - (fft_floor_maa-0.75)) / log10((fft_ceil_maa+0.25) - (fft_floor_maa-0.75))); + renderData->waveform_points[i * 2] = ((double) i / (double) (outSize)); + renderData->waveform_points[i * 2 + 1] = v; + } + + renderData->fft_floor = fft_floor_maa; + renderData->fft_ceil = fft_ceil_maa; + renderData->fft_size = fftSize/2; + renderData->inputRate = audioInputData->inputRate; + renderData->sampleRate = audioInputData->sampleRate; + renderData->spectrum = true; + distribute(renderData); + } + audioInputData->busy_update.unlock(); } } diff --git a/src/process/ScopeVisualProcessor.h b/src/process/ScopeVisualProcessor.h index 08ce3bb..98a2398 100644 --- a/src/process/ScopeVisualProcessor.h +++ b/src/process/ScopeVisualProcessor.h @@ -2,17 +2,47 @@ #include "VisualProcessor.h" #include "AudioThread.h" +#include "fftw3.h" class ScopeRenderData: public ReferenceCounter { public: std::vector waveform_points; + int inputRate; + int sampleRate; int channels; + bool spectrum; + int fft_size; + double fft_floor, fft_ceil; }; typedef ThreadQueue ScopeRenderDataQueue; class ScopeVisualProcessor : public VisualProcessor { +public: + ScopeVisualProcessor(); + ~ScopeVisualProcessor(); + void setup(int fftSize_in); + void setScopeEnabled(bool scopeEnable); + void setSpectrumEnabled(bool spectrumEnable); protected: void process(); ReBuffer outputBuffers; + + std::atomic_bool scopeEnabled; + std::atomic_bool spectrumEnabled; + + float *fftInData; + fftwf_complex *fftwOutput; + fftwf_plan fftw_plan; + int fftSize; + int desiredInputSize; + int maxScopeSamples; + + double fft_ceil_ma, fft_ceil_maa; + double fft_floor_ma, fft_floor_maa; + float fft_average_rate; + + std::vector fft_result; + std::vector fft_result_ma; + std::vector fft_result_maa; }; diff --git a/src/process/SpectrumVisualProcessor.cpp b/src/process/SpectrumVisualProcessor.cpp index 9322462..8dc890d 100644 --- a/src/process/SpectrumVisualProcessor.cpp +++ b/src/process/SpectrumVisualProcessor.cpp @@ -14,7 +14,7 @@ SpectrumVisualProcessor::SpectrumVisualProcessor() : lastInputBandwidth(0), last fft_ceil_ma = fft_ceil_maa = 100.0; fft_floor_ma = fft_floor_maa = 0.0; - desiredInputSize = 0; + desiredInputSize.store(0); fft_average_rate = 0.65; } @@ -27,19 +27,25 @@ bool SpectrumVisualProcessor::isView() { } void SpectrumVisualProcessor::setView(bool bView) { + busy_run.lock(); is_view.store(bView); + busy_run.unlock(); } void SpectrumVisualProcessor::setFFTAverageRate(float fftAverageRate) { - this->fft_average_rate = fftAverageRate; + busy_run.lock(); + this->fft_average_rate.store(fftAverageRate); + busy_run.unlock(); } float SpectrumVisualProcessor::getFFTAverageRate() { - return this->fft_average_rate; + return this->fft_average_rate.load(); } void SpectrumVisualProcessor::setCenterFrequency(long long centerFreq_in) { + busy_run.lock(); centerFreq.store(centerFreq_in); + busy_run.unlock(); } long long SpectrumVisualProcessor::getCenterFrequency() { @@ -47,7 +53,9 @@ long long SpectrumVisualProcessor::getCenterFrequency() { } void SpectrumVisualProcessor::setBandwidth(long bandwidth_in) { + busy_run.lock(); bandwidth.store(bandwidth_in); + busy_run.unlock(); } long SpectrumVisualProcessor::getBandwidth() { @@ -55,12 +63,14 @@ long SpectrumVisualProcessor::getBandwidth() { } int SpectrumVisualProcessor::getDesiredInputSize() { - return desiredInputSize; + return desiredInputSize.load(); } void SpectrumVisualProcessor::setup(int fftSize_in) { + busy_run.lock(); + fftSize = fftSize_in; - desiredInputSize = fftSize; + desiredInputSize.store(fftSize); if (fftwInput) { free(fftwInput); @@ -82,7 +92,7 @@ void SpectrumVisualProcessor::setup(int fftSize_in) { fftwf_destroy_plan(fftw_plan); } fftw_plan = fftwf_plan_dft_1d(fftSize, fftwInput, fftwOutput, FFTW_FORWARD, FFTW_ESTIMATE); - + busy_run.unlock(); } void SpectrumVisualProcessor::process() { @@ -102,6 +112,7 @@ void SpectrumVisualProcessor::process() { } iqData->busy_rw.lock(); + busy_run.lock(); std::vector *data = &iqData->data; @@ -118,6 +129,7 @@ void SpectrumVisualProcessor::process() { if (!iqData->frequency || !iqData->sampleRate) { iqData->decRefCount(); iqData->busy_rw.unlock(); + busy_run.unlock(); return; } @@ -125,7 +137,7 @@ void SpectrumVisualProcessor::process() { int desired_input_size = fftSize / resamplerRatio; - this->desiredInputSize = desired_input_size; + this->desiredInputSize.store(desired_input_size); if (iqData->data.size() < desired_input_size) { // std::cout << "fft underflow, desired: " << desired_input_size << " actual:" << input->data.size() << std::endl; @@ -304,5 +316,6 @@ void SpectrumVisualProcessor::process() { iqData->decRefCount(); iqData->busy_rw.unlock(); + busy_run.unlock(); } diff --git a/src/process/SpectrumVisualProcessor.h b/src/process/SpectrumVisualProcessor.h index 8e1e694..149c170 100644 --- a/src/process/SpectrumVisualProcessor.h +++ b/src/process/SpectrumVisualProcessor.h @@ -53,7 +53,7 @@ private: double fft_ceil_ma, fft_ceil_maa; double fft_floor_ma, fft_floor_maa; - float fft_average_rate; + std::atomic fft_average_rate; std::vector fft_result; std::vector fft_result_ma; @@ -66,7 +66,8 @@ private: std::vector shiftBuffer; std::vector resampleBuffer; - int desiredInputSize; + std::atomic_int desiredInputSize; + std::mutex busy_run; }; diff --git a/src/ui/GLPanel.cpp b/src/ui/GLPanel.cpp index 00fa2be..7d06c81 100644 --- a/src/ui/GLPanel.cpp +++ b/src/ui/GLPanel.cpp @@ -8,6 +8,9 @@ using namespace CubicVR; GLPanel::GLPanel() : fillType(GLPANEL_FILL_SOLID), contentsVisible(true), transform(mat4::identity()) { pos[0] = 0.0f; pos[1] = 0.0f; + rot[0] = 0.0f; + rot[1] = 0.0f; + rot[2] = 0.0f; size[0] = 1.0f; size[1] = 1.0f; fill[0] = RGBA4f(0.5,0.5,0.5); @@ -241,6 +244,11 @@ void GLPanel::drawPanelContents() { void GLPanel::calcTransform(mat4 transform_in) { // compute local transform localTransform = mat4::translate(pos[0], pos[1], 0) * mat4::scale(size[0], size[1], 1); + + if (rot[0] || rot[1] || rot[2]) { + localTransform *= mat4::rotate(rot[0], rot[1], rot[2]); + } + // compute global transform transform = transform_in * localTransform; diff --git a/src/ui/GLPanel.h b/src/ui/GLPanel.h index b87bac2..0f3e3b6 100644 --- a/src/ui/GLPanel.h +++ b/src/ui/GLPanel.h @@ -36,6 +36,7 @@ public: typedef enum GLPanelFillType { GLPANEL_FILL_NONE, GLPANEL_FILL_SOLID, GLPANEL_FILL_GRAD_X, GLPANEL_FILL_GRAD_Y, GLPANEL_FILL_GRAD_BAR_X, GLPANEL_FILL_GRAD_BAR_Y } GLPanelFillType; typedef enum GLPanelCoordinateSystem { GLPANEL_Y_DOWN_ZERO_ONE, GLPANEL_Y_UP_ZERO_ONE, GLPANEL_Y_UP, GLPANEL_Y_DOWN } GLPanelCoordinateSystem; float pos[2]; + float rot[3]; float size[2]; float view[2]; GLPanelFillType fillType; diff --git a/src/util/ThreadQueue.h b/src/util/ThreadQueue.h index 8ba5e0a..e69265c 100644 --- a/src/util/ThreadQueue.h +++ b/src/util/ThreadQueue.h @@ -14,6 +14,7 @@ #include #include #include +#include class ThreadQueueBase { @@ -30,13 +31,17 @@ class ThreadQueue : public ThreadQueueBase { public: /*! Create safe queue. */ - ThreadQueue() = default; + ThreadQueue() { + m_max_num_items.store(0); + }; ThreadQueue(ThreadQueue&& sq) { m_queue = std::move(sq.m_queue); + m_max_num_items.store(0); } ThreadQueue(const ThreadQueue& sq) { std::lock_guard < std::mutex > lock(sq.m_mutex); m_queue = sq.m_queue; + m_max_num_items.store(0); } /*! Destroy safe queue. */ @@ -49,7 +54,10 @@ public: * \param[in] item An item. */ void set_max_num_items(unsigned int max_num_items) { - m_max_num_items = max_num_items; + std::lock_guard < std::mutex > lock(m_mutex); + if (m_max_num_items.load() != max_num_items) { + m_max_num_items.store(max_num_items); + } } /** @@ -60,7 +68,7 @@ public: bool push(const value_type& item) { std::lock_guard < std::mutex > lock(m_mutex); - if (m_max_num_items > 0 && m_queue.size() > m_max_num_items) + if (m_max_num_items.load() > 0 && m_queue.size() > m_max_num_items.load()) return false; m_queue.push(item); @@ -76,7 +84,7 @@ public: bool push(const value_type&& item) { std::lock_guard < std::mutex > lock(m_mutex); - if (m_max_num_items > 0 && m_queue.size() > m_max_num_items) + if (m_max_num_items.load() > 0 && m_queue.size() > m_max_num_items.load()) return false; m_queue.push(item); @@ -217,7 +225,7 @@ public: */ bool full() const { std::lock_guard < std::mutex > lock(m_mutex); - return (m_max_num_items != 0) && (m_queue.size() >= m_max_num_items); + return (m_max_num_items.load() != 0) && (m_queue.size() >= m_max_num_items.load()); } /** @@ -278,7 +286,7 @@ private: std::queue m_queue; mutable std::mutex m_mutex; std::condition_variable m_condition; - unsigned int m_max_num_items = 0; + std::atomic_uint m_max_num_items; }; /*! Swaps the contents of two ThreadQueue objects. */ diff --git a/src/visual/ScopeCanvas.cpp b/src/visual/ScopeCanvas.cpp index a4cd4be..9dfaced 100644 --- a/src/visual/ScopeCanvas.cpp +++ b/src/visual/ScopeCanvas.cpp @@ -14,24 +14,65 @@ #include "CubicSDRDefs.h" #include "AppFrame.h" #include +#include wxBEGIN_EVENT_TABLE(ScopeCanvas, wxGLCanvas) EVT_PAINT(ScopeCanvas::OnPaint) EVT_IDLE(ScopeCanvas::OnIdle) +EVT_MOTION(ScopeCanvas::OnMouseMoved) +EVT_LEFT_DOWN(ScopeCanvas::OnMouseDown) +EVT_LEFT_UP(ScopeCanvas::OnMouseReleased) +EVT_RIGHT_DOWN(ScopeCanvas::OnMouseRightDown) +EVT_RIGHT_UP(ScopeCanvas::OnMouseRightReleased) +EVT_LEAVE_WINDOW(ScopeCanvas::OnMouseLeftWindow) +EVT_ENTER_WINDOW(ScopeCanvas::OnMouseEnterWindow) wxEND_EVENT_TABLE() -ScopeCanvas::ScopeCanvas(wxWindow *parent, int *attribList) : - wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, - wxFULL_REPAINT_ON_RESIZE), stereo(false), ppmMode(false) { +ScopeCanvas::ScopeCanvas(wxWindow *parent, int *attribList) : InteractiveCanvas(parent, attribList), stereo(false), ppmMode(false), ctr(0), ctrTarget(0), dragAccel(0), helpTip("") { glContext = new ScopeContext(this, &wxGetApp().GetContext(this)); - inputData.set_max_num_items(1); + inputData.set_max_num_items(2); + bgPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_Y); + bgPanel.setSize(1.0, 0.5); + bgPanel.setPosition(0.0, -0.5); + panelSpacing = 0.4; + + parentPanel.addChild(&scopePanel); + parentPanel.addChild(&spectrumPanel); + parentPanel.setFill(GLPanel::GLPANEL_FILL_NONE); + scopePanel.setSize(1.0,-1.0); + spectrumPanel.setSize(1.0,-1.0); + spectrumPanel.setShowDb(true); } ScopeCanvas::~ScopeCanvas() { } +bool ScopeCanvas::scopeVisible() { + float panelInterval = (2.0 + panelSpacing); + + ctrTarget = abs(round(ctr / panelInterval)); + + if (ctrTarget == 0 || dragAccel || (ctr != ctrTarget)) { + return true; + } + + return false; +} + +bool ScopeCanvas::spectrumVisible() { + float panelInterval = (2.0 + panelSpacing); + + ctrTarget = abs(round(ctr / panelInterval)); + + if (ctrTarget == 1 || dragAccel || (ctr != ctrTarget)) { + return true; + } + + return false; +} + void ScopeCanvas::setStereo(bool state) { stereo = state; } @@ -49,20 +90,40 @@ bool ScopeCanvas::getPPMMode() { return ppmMode; } +void ScopeCanvas::setShowDb(bool showDb) { + this->showDb = showDb; +} + +bool ScopeCanvas::getShowDb() { + return showDb; +} + void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { wxPaintDC dc(this); const wxSize ClientSize = GetClientSize(); - if (!inputData.empty()) { + while (!inputData.empty()) { ScopeRenderData *avData; inputData.pop(avData); - if (avData) { + if (!avData->spectrum) { if (avData->waveform_points.size()) { scopePanel.setPoints(avData->waveform_points); setStereo(avData->channels == 2); } + avData->decRefCount(); + } else { + if (avData->waveform_points.size()) { + spectrumPanel.setPoints(avData->waveform_points); + spectrumPanel.setFloorValue(avData->fft_floor); + spectrumPanel.setCeilValue(avData->fft_ceil); + spectrumPanel.setBandwidth((avData->sampleRate/2)*1000); + spectrumPanel.setFreq((avData->sampleRate/4)*1000); + spectrumPanel.setFFTSize(avData->fft_size); + spectrumPanel.setShowDb(showDb); + } + avData->decRefCount(); } } @@ -71,21 +132,95 @@ void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { initGLExtensions(); glViewport(0, 0, ClientSize.x, ClientSize.y); - + glContext->DrawBegin(); + + bgPanel.setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground * 3.0, RGBA4f(0,0,0,0)); + bgPanel.calcTransform(CubicVR::mat4::identity()); + bgPanel.draw(); + scopePanel.setMode(stereo?ScopePanel::SCOPE_MODE_2Y:ScopePanel::SCOPE_MODE_Y); - scopePanel.calcTransform(CubicVR::mat4::identity()); - scopePanel.draw(); - glContext->DrawTunerTitles(ppmMode); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glLoadMatrixf(CubicVR::mat4::perspective(45.0, 1.0, 1.0, 1000.0)); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + CubicVR::mat4 modelView = CubicVR::mat4::lookat(0, 0, -1.205, 0, 0, 0, 0, -1, 0); + + float panelWidth = 1.0; + float panelInterval = (panelWidth * 2.0 + panelSpacing); + + if (!mouseTracker.mouseDown()) { + ctrTarget = round(ctr / panelInterval); + if (ctrTarget < -1.0) { + ctrTarget = -1.0; + } else if (ctrTarget > 0.0) { + ctrTarget = 0.0; + } + ctrTarget *= panelInterval; + if (!dragAccel) { + if (ctr != ctrTarget) { + ctr += (ctrTarget-ctr)*0.2; + } + if (abs(ctr - ctrTarget) < 0.001) { + ctr=ctrTarget; + } + } else { + dragAccel -= dragAccel * 0.1; + if ((abs(dragAccel) < 0.2) || (ctr < (ctrTarget-panelInterval/2.0)) || (ctr > (ctrTarget+panelInterval/2.0)) ) { + dragAccel = 0; + } else { + ctr += dragAccel; + } + } + } + + float roty = 0; + + scopePanel.setPosition(ctr, 0); + if (scopeVisible()) { + scopePanel.contentsVisible = true; + roty = atan2(scopePanel.pos[0],1.2); + scopePanel.rot[1] = -(roty * (180.0 / M_PI)); + } else { + scopePanel.contentsVisible = false; + } + + spectrumPanel.setPosition(panelInterval+ctr, 0); + if (spectrumVisible()) { + spectrumPanel.setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground * 2.0, RGBA4f(0,0,0,0)); + spectrumPanel.contentsVisible = true; + roty = atan2(spectrumPanel.pos[0],1.2); + spectrumPanel.rot[1] = -(roty * (180.0 / M_PI)); + } else { + spectrumPanel.contentsVisible = false; + } + + parentPanel.calcTransform(modelView); + parentPanel.draw(); + + if (spectrumVisible()) { + spectrumPanel.drawChildren(); + } + + glLoadMatrixf(scopePanel.transform); if (!deviceName.empty()) { glContext->DrawDeviceName(deviceName); } - glContext->DrawEnd(); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glContext->DrawTunerTitles(ppmMode); + glContext->DrawEnd(); SwapBuffers(); } + void ScopeCanvas::OnIdle(wxIdleEvent &event) { Refresh(); event.RequestMore(); @@ -94,3 +229,44 @@ void ScopeCanvas::OnIdle(wxIdleEvent &event) { ScopeRenderDataQueue *ScopeCanvas::getInputQueue() { return &inputData; } + +void ScopeCanvas::OnMouseMoved(wxMouseEvent& event) { + InteractiveCanvas::OnMouseMoved(event); + if (mouseTracker.mouseDown()) { + dragAccel = 4.0*mouseTracker.getDeltaMouseX(); + ctr += dragAccel; + } +} + +void ScopeCanvas::OnMouseWheelMoved(wxMouseEvent& event) { + +} + +void ScopeCanvas::OnMouseDown(wxMouseEvent& event) { + InteractiveCanvas::OnMouseDown(event); + +} + +void ScopeCanvas::OnMouseReleased(wxMouseEvent& event) { + InteractiveCanvas::OnMouseReleased(event); + +} + +void ScopeCanvas::OnMouseEnterWindow(wxMouseEvent& event) { + InteractiveCanvas::OnMouseEnterWindow(event); + if (!helpTip.empty()) { + setStatusText(helpTip); + } + SetCursor(wxCURSOR_SIZEWE); +} + +void ScopeCanvas::OnMouseLeftWindow(wxMouseEvent& event) { + InteractiveCanvas::OnMouseLeftWindow(event); + +} + + +void ScopeCanvas::setHelpTip(std::string tip) { + helpTip = tip; +} + diff --git a/src/visual/ScopeCanvas.h b/src/visual/ScopeCanvas.h index 6cdff03..d868b2e 100644 --- a/src/visual/ScopeCanvas.h +++ b/src/visual/ScopeCanvas.h @@ -9,9 +9,11 @@ #include "ScopeContext.h" #include "ScopeVisualProcessor.h" #include "ScopePanel.h" +#include "SpectrumPanel.h" #include "fftw3.h" +#include "InteractiveCanvas.h" -class ScopeCanvas: public wxGLCanvas { +class ScopeCanvas: public InteractiveCanvas { public: ScopeCanvas(wxWindow *parent, int *attribList = NULL); ~ScopeCanvas(); @@ -21,18 +23,41 @@ public: void setPPMMode(bool ppmMode); bool getPPMMode(); + void setShowDb(bool showDb); + bool getShowDb(); + + bool scopeVisible(); + bool spectrumVisible(); + + void setHelpTip(std::string tip); + ScopeRenderDataQueue *getInputQueue(); private: void OnPaint(wxPaintEvent& event); void OnIdle(wxIdleEvent &event); + void OnMouseMoved(wxMouseEvent& event); + void OnMouseWheelMoved(wxMouseEvent& event); + void OnMouseDown(wxMouseEvent& event); + void OnMouseReleased(wxMouseEvent& event); + void OnMouseEnterWindow(wxMouseEvent& event); + void OnMouseLeftWindow(wxMouseEvent& event); ScopeRenderDataQueue inputData; ScopePanel scopePanel; + GLPanel parentPanel; + SpectrumPanel spectrumPanel; + GLPanel bgPanel; ScopeContext *glContext; std::string deviceName; bool stereo; bool ppmMode; + bool showDb; + float panelSpacing; + float ctr; + float ctrTarget; + float dragAccel; + std::string helpTip; // event table wxDECLARE_EVENT_TABLE(); };