mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-30 12:30:20 -04:00 
			
		
		
		
	Websocket spectrum: first implementation
This commit is contained in:
		
							parent
							
								
									6a6b5f8d7e
								
							
						
					
					
						commit
						ac6c3b08f2
					
				| @ -399,6 +399,8 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban | ||||
| 	m_spectrumVis->configure( | ||||
| 	        m_spectrumVis->getInputMessageQueue(), | ||||
|             64, // FFT size
 | ||||
|             0, // Ref level (dB)
 | ||||
|             100, // Power range (dB)
 | ||||
|             10, // overlapping %
 | ||||
|             0,  // number of averaging samples
 | ||||
|             SpectrumVis::AvgModeNone,  // no averaging
 | ||||
|  | ||||
| @ -189,6 +189,8 @@ UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS | ||||
| 	ui->glSpectrum->setDisplayMaxHold(true); | ||||
| 	m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), | ||||
| 	        64, // FFT size
 | ||||
|             0, // Ref level (dB)
 | ||||
|             100, // Power range (dB)
 | ||||
| 	        10, // overlapping %
 | ||||
| 	        0,  // number of averaging samples
 | ||||
| 	        SpectrumVis::AvgModeNone,  // no averaging
 | ||||
|  | ||||
| @ -146,6 +146,8 @@ UDPSourceGUI::UDPSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb | ||||
|     ui->glSpectrum->setDisplayMaxHold(true); | ||||
|     m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), | ||||
|             64, // FFT size
 | ||||
|             0, // Ref level (dB)
 | ||||
|             100, // Power range (dB)
 | ||||
|             10, // overlapping %
 | ||||
|             0,  // number of averaging samples
 | ||||
|             SpectrumVis::AvgModeNone,  // no averaging
 | ||||
|  | ||||
| @ -163,6 +163,8 @@ set(sdrbase_SOURCES | ||||
|     webapi/webapirequestmapper.cpp | ||||
|     webapi/webapiserver.cpp | ||||
| 
 | ||||
|     websockets/wsspectrum.cpp | ||||
| 
 | ||||
|     mainparser.cpp | ||||
| 
 | ||||
|     resources/webapi.qrc | ||||
| @ -310,7 +312,9 @@ set(sdrbase_HEADERS | ||||
|     webapi/webapiadapterbase.h | ||||
|     webapi/webapiadapterinterface.h | ||||
|     webapi/webapirequestmapper.h | ||||
|     webapi/webapiserver | ||||
|     webapi/webapiserver.h | ||||
| 
 | ||||
|     websockets/wsspectrum.h | ||||
| 
 | ||||
|     mainparser.h | ||||
| ) | ||||
| @ -343,6 +347,7 @@ target_link_libraries(sdrbase | ||||
|     ${sdrbase_LIMERFE_LIB} | ||||
|     Qt5::Core | ||||
|     Qt5::Multimedia | ||||
|     Qt5::WebSockets | ||||
|     httpserver | ||||
|     qrtplib | ||||
|     swagger | ||||
|  | ||||
| @ -36,6 +36,7 @@ inline double log2f(double n) | ||||
| #endif | ||||
| 
 | ||||
| MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureSpectrumVis, Message) | ||||
| MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureDSP, Message) | ||||
| MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureScalingFactor, Message) | ||||
| 
 | ||||
| const Real SpectrumVis::m_mult = (10.0f / log2f(10.0f)); | ||||
| @ -53,12 +54,15 @@ SpectrumVis::SpectrumVis(Real scalef, GLSpectrumInterface* glSpectrum) : | ||||
| 	m_averageNb(0), | ||||
| 	m_avgMode(AvgModeNone), | ||||
| 	m_linear(false), | ||||
|     m_centerFrequency(0), | ||||
|     m_sampleRate(48000), | ||||
| 	m_ofs(0), | ||||
|     m_powFFTDiv(1.0), | ||||
| 	m_mutex(QMutex::Recursive) | ||||
| { | ||||
| 	setObjectName("SpectrumVis"); | ||||
| 	handleConfigure(1024, 0, 0, AvgModeNone, FFTWindow::BlackmanHarris, false); | ||||
| 	handleConfigure(1024, 0, 100, 0, 0, AvgModeNone, FFTWindow::BlackmanHarris, false); | ||||
|     m_wsSpectrum.openSocket(); // FIXME: conditional
 | ||||
| } | ||||
| 
 | ||||
| SpectrumVis::~SpectrumVis() | ||||
| @ -69,17 +73,34 @@ SpectrumVis::~SpectrumVis() | ||||
| 
 | ||||
| void SpectrumVis::configure(MessageQueue* msgQueue, | ||||
|         int fftSize, | ||||
|         float refLevel, | ||||
|         float powerRange, | ||||
|         int overlapPercent, | ||||
|         unsigned int averagingNb, | ||||
|         AvgMode averagingMode, | ||||
|         FFTWindow::Function window, | ||||
|         bool linear) | ||||
| { | ||||
| 	MsgConfigureSpectrumVis* cmd = new MsgConfigureSpectrumVis(fftSize, overlapPercent, averagingNb, averagingMode, window, linear); | ||||
| 	MsgConfigureSpectrumVis* cmd = new MsgConfigureSpectrumVis( | ||||
|         fftSize, | ||||
|         refLevel, | ||||
|         powerRange, | ||||
|         overlapPercent, | ||||
|         averagingNb, | ||||
|         averagingMode, | ||||
|         window, | ||||
|         linear | ||||
|     ); | ||||
| 	msgQueue->push(cmd); | ||||
| } | ||||
| 
 | ||||
| void SpectrumVis::setScalef(MessageQueue* msgQueue, Real scalef) | ||||
| void SpectrumVis::configureDSP(uint64_t centerFrequency, int sampleRate) | ||||
| { | ||||
|     MsgConfigureDSP* cmd = new MsgConfigureDSP(centerFrequency, sampleRate); | ||||
|     getInputMessageQueue()->push(cmd); | ||||
| } | ||||
| 
 | ||||
| void SpectrumVis::setScalef(Real scalef) | ||||
| { | ||||
|     MsgConfigureScalingFactor* cmd = new MsgConfigureScalingFactor(scalef); | ||||
|     getInputMessageQueue()->push(cmd); | ||||
| @ -106,11 +127,189 @@ void SpectrumVis::feedTriggered(const SampleVector::const_iterator& triggerPoint | ||||
| 	}*/ | ||||
| } | ||||
| 
 | ||||
| void SpectrumVis::feed(const Complex *begin, unsigned int length) | ||||
| { | ||||
| 	if (!m_glSpectrum && !m_wsSpectrum.socketOpened()) { | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
|     if (!m_mutex.tryLock(0)) { // prevent conflicts with configuration process
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Complex c; | ||||
|     Real v; | ||||
| 
 | ||||
|     if (m_avgMode == AvgModeNone) | ||||
|     { | ||||
|         for (unsigned int i = 0; i < m_fftSize; i++) | ||||
|         { | ||||
|             if (i < length) { | ||||
|                 c = begin[i]; | ||||
|             } else { | ||||
|                 c = Complex{0,0}; | ||||
|             } | ||||
| 
 | ||||
|             v = c.real() * c.real() + c.imag() * c.imag(); | ||||
|             v = m_linear ? v/m_powFFTDiv : m_mult * log2f(v) + m_ofs; | ||||
|             m_powerSpectrum[i] = v; | ||||
|         } | ||||
| 
 | ||||
|         // send new data to visualisation
 | ||||
|         if (m_glSpectrum) { | ||||
|             m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); | ||||
|         } | ||||
| 
 | ||||
|         // web socket spectrum connections
 | ||||
|         if (m_wsSpectrum.socketOpened()) | ||||
|         { | ||||
|             m_wsSpectrum.newSpectrum( | ||||
|                 m_powerSpectrum, | ||||
|                 m_fftSize, | ||||
|                 m_refLevel, | ||||
|                 m_powerRange, | ||||
|                 m_centerFrequency, | ||||
|                 m_sampleRate, | ||||
|                 m_linear | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|     else if (m_avgMode == AvgModeMovingAvg) | ||||
|     { | ||||
|         for (unsigned int i = 0; i < m_fftSize; i++) | ||||
|         { | ||||
|             if (i < length) { | ||||
|                 c = begin[i]; | ||||
|             } else { | ||||
|                 c = Complex{0,0}; | ||||
|             } | ||||
| 
 | ||||
|             v = c.real() * c.real() + c.imag() * c.imag(); | ||||
|             v = m_movingAverage.storeAndGetAvg(v, i); | ||||
|             v = m_linear ? v/m_powFFTDiv : m_mult * log2f(v) + m_ofs; | ||||
|             m_powerSpectrum[i] = v; | ||||
|         } | ||||
| 
 | ||||
|         // send new data to visualisation
 | ||||
|         if (m_glSpectrum) { | ||||
|             m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); | ||||
|         } | ||||
| 
 | ||||
|         // web socket spectrum connections
 | ||||
|         if (m_wsSpectrum.socketOpened()) | ||||
|         { | ||||
|             m_wsSpectrum.newSpectrum( | ||||
|                 m_powerSpectrum, | ||||
|                 m_fftSize, | ||||
|                 m_refLevel, | ||||
|                 m_powerRange, | ||||
|                 m_centerFrequency, | ||||
|                 m_sampleRate, | ||||
|                 m_linear | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         m_movingAverage.nextAverage(); | ||||
|     } | ||||
|     else if (m_avgMode == AvgModeFixedAvg) | ||||
|     { | ||||
|         double avg; | ||||
| 
 | ||||
|         for (unsigned int i = 0; i < m_fftSize; i++) | ||||
|         { | ||||
|             if (i < length) { | ||||
|                 c = begin[i]; | ||||
|             } else { | ||||
|                 c = Complex{0,0}; | ||||
|             } | ||||
| 
 | ||||
|             v = c.real() * c.real() + c.imag() * c.imag(); | ||||
| 
 | ||||
|             // result available
 | ||||
|             if (m_fixedAverage.storeAndGetAvg(avg, v, i)) | ||||
|             { | ||||
|                 avg = m_linear ? avg/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; | ||||
|                 m_powerSpectrum[i] = avg; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // result available
 | ||||
|         if (m_fixedAverage.nextAverage()) | ||||
|         { | ||||
|             // send new data to visualisation
 | ||||
|             if (m_glSpectrum) { | ||||
|                 m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); | ||||
|             } | ||||
| 
 | ||||
|             // web socket spectrum connections
 | ||||
|             if (m_wsSpectrum.socketOpened()) | ||||
|             { | ||||
|                 m_wsSpectrum.newSpectrum( | ||||
|                     m_powerSpectrum, | ||||
|                     m_fftSize, | ||||
|                     m_refLevel, | ||||
|                     m_powerRange, | ||||
|                     m_centerFrequency, | ||||
|                     m_sampleRate, | ||||
|                     m_linear | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     else if (m_avgMode == AvgModeMax) | ||||
|     { | ||||
|         double max; | ||||
| 
 | ||||
|         for (unsigned int i = 0; i < m_fftSize; i++) | ||||
|         { | ||||
|             if (i < length) { | ||||
|                 c = begin[i]; | ||||
|             } else { | ||||
|                 c = Complex{0,0}; | ||||
|             } | ||||
| 
 | ||||
|             v = c.real() * c.real() + c.imag() * c.imag(); | ||||
| 
 | ||||
|             // result available
 | ||||
|             if (m_max.storeAndGetMax(max, v, i)) | ||||
|             { | ||||
|                 max = m_linear ? max/m_powFFTDiv : m_mult * log2f(max) + m_ofs; | ||||
|                 m_powerSpectrum[i] = max; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // result available
 | ||||
|         if (m_max.nextMax()) | ||||
|         { | ||||
|             // send new data to visualisation
 | ||||
|             if (m_glSpectrum) { | ||||
|                 m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); | ||||
|             } | ||||
| 
 | ||||
|             // web socket spectrum connections
 | ||||
|             if (m_wsSpectrum.socketOpened()) | ||||
|             { | ||||
|                 m_wsSpectrum.newSpectrum( | ||||
|                     m_powerSpectrum, | ||||
|                     m_fftSize, | ||||
|                     m_refLevel, | ||||
|                     m_powerRange, | ||||
|                     m_centerFrequency, | ||||
|                     m_sampleRate, | ||||
|                     m_linear | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m_mutex.unlock(); | ||||
| } | ||||
| 
 | ||||
| void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, bool positiveOnly) | ||||
| { | ||||
| 	// if no visualisation is set, send the samples to /dev/null
 | ||||
| 
 | ||||
| 	if (m_glSpectrum == 0) { | ||||
| 	if (!m_glSpectrum && !m_wsSpectrum.socketOpened()) { | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| @ -177,7 +376,23 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV | ||||
|                 } | ||||
| 
 | ||||
|                 // send new data to visualisation
 | ||||
|                 m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); | ||||
|                 if (m_glSpectrum) { | ||||
|                     m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); | ||||
|                 } | ||||
| 
 | ||||
|                 // web socket spectrum connections
 | ||||
|                 if (m_wsSpectrum.socketOpened()) | ||||
|                 { | ||||
|                     m_wsSpectrum.newSpectrum( | ||||
|                         m_powerSpectrum, | ||||
|                         m_fftSize, | ||||
|                         m_refLevel, | ||||
|                         m_powerRange, | ||||
|                         m_centerFrequency, | ||||
|                         m_sampleRate, | ||||
|                         m_linear | ||||
|                     ); | ||||
|                 } | ||||
| 			} | ||||
| 			else if (m_avgMode == AvgModeMovingAvg) | ||||
| 			{ | ||||
| @ -212,7 +427,24 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV | ||||
| 	            } | ||||
| 
 | ||||
| 	            // send new data to visualisation
 | ||||
| 	            m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); | ||||
|                 if (m_glSpectrum) { | ||||
|     	            m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); | ||||
|                 } | ||||
| 
 | ||||
|                 // web socket spectrum connections
 | ||||
|                 if (m_wsSpectrum.socketOpened()) | ||||
|                 { | ||||
|                     m_wsSpectrum.newSpectrum( | ||||
|                         m_powerSpectrum, | ||||
|                         m_fftSize, | ||||
|                         m_refLevel, | ||||
|                         m_powerRange, | ||||
|                         m_centerFrequency, | ||||
|                         m_sampleRate, | ||||
|                         m_linear | ||||
|                     ); | ||||
|                 } | ||||
| 
 | ||||
| 	            m_movingAverage.nextAverage(); | ||||
| 			} | ||||
| 			else if (m_avgMode == AvgModeFixedAvg) | ||||
| @ -241,8 +473,9 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV | ||||
|                         c = fftOut[i + halfSize]; | ||||
|                         v = c.real() * c.real() + c.imag() * c.imag(); | ||||
| 
 | ||||
|                         // result available
 | ||||
|                         if (m_fixedAverage.storeAndGetAvg(avg, v, i+halfSize)) | ||||
|                         { // result available
 | ||||
|                         { | ||||
|                             avg = m_linear ? avg/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; | ||||
|                             m_powerSpectrum[i] = avg; | ||||
|                         } | ||||
| @ -250,16 +483,36 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV | ||||
|                         c = fftOut[i]; | ||||
|                         v = c.real() * c.real() + c.imag() * c.imag(); | ||||
| 
 | ||||
|                         // result available
 | ||||
|                         if (m_fixedAverage.storeAndGetAvg(avg, v, i)) | ||||
|                         { // result available
 | ||||
|                         { | ||||
|                             avg = m_linear ? avg/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; | ||||
|                             m_powerSpectrum[i + halfSize] = avg; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (m_fixedAverage.nextAverage()) { // result available
 | ||||
|                     m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); // send new data to visualisation
 | ||||
|                 // result available
 | ||||
|                 if (m_fixedAverage.nextAverage()) | ||||
|                 { | ||||
|                     // send new data to visualisation
 | ||||
|                     if (m_glSpectrum) { | ||||
|                         m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); | ||||
|                     } | ||||
| 
 | ||||
|                     // web socket spectrum connections
 | ||||
|                     if (m_wsSpectrum.socketOpened()) | ||||
|                     { | ||||
|                         m_wsSpectrum.newSpectrum( | ||||
|                             m_powerSpectrum, | ||||
|                             m_fftSize, | ||||
|                             m_refLevel, | ||||
|                             m_powerRange, | ||||
|                             m_centerFrequency, | ||||
|                             m_sampleRate, | ||||
|                             m_linear | ||||
|                         ); | ||||
|                     } | ||||
|                 } | ||||
| 			} | ||||
| 			else if (m_avgMode == AvgModeMax) | ||||
| @ -288,8 +541,9 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV | ||||
|                         c = fftOut[i + halfSize]; | ||||
|                         v = c.real() * c.real() + c.imag() * c.imag(); | ||||
| 
 | ||||
|                         // result available
 | ||||
|                         if (m_max.storeAndGetMax(max, v, i+halfSize)) | ||||
|                         { // result available
 | ||||
|                         { | ||||
|                             max = m_linear ? max/m_powFFTDiv : m_mult * log2f(max) + m_ofs; | ||||
|                             m_powerSpectrum[i] = max; | ||||
|                         } | ||||
| @ -297,16 +551,36 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV | ||||
|                         c = fftOut[i]; | ||||
|                         v = c.real() * c.real() + c.imag() * c.imag(); | ||||
| 
 | ||||
|                         // result available
 | ||||
|                         if (m_max.storeAndGetMax(max, v, i)) | ||||
|                         { // result available
 | ||||
|                         { | ||||
|                             max = m_linear ? max/m_powFFTDiv : m_mult * log2f(max) + m_ofs; | ||||
|                             m_powerSpectrum[i + halfSize] = max; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (m_max.nextMax()) { // result available
 | ||||
|                     m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); // send new data to visualisation
 | ||||
|                 // result available
 | ||||
|                 if (m_max.nextMax()) | ||||
|                 { | ||||
|                     // send new data to visualisation
 | ||||
|                     if (m_glSpectrum) { | ||||
|                         m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); | ||||
|                     } | ||||
| 
 | ||||
|                     // web socket spectrum connections
 | ||||
|                     if (m_wsSpectrum.socketOpened()) | ||||
|                     { | ||||
|                         m_wsSpectrum.newSpectrum( | ||||
|                             m_powerSpectrum, | ||||
|                             m_fftSize, | ||||
|                             m_refLevel, | ||||
|                             m_powerRange, | ||||
|                             m_centerFrequency, | ||||
|                             m_sampleRate, | ||||
|                             m_linear | ||||
|                         ); | ||||
|                     } | ||||
|                 } | ||||
| 			} | ||||
| 
 | ||||
| @ -347,6 +621,8 @@ bool SpectrumVis::handleMessage(const Message& message) | ||||
| 	{ | ||||
| 		MsgConfigureSpectrumVis& conf = (MsgConfigureSpectrumVis&) message; | ||||
| 		handleConfigure(conf.getFFTSize(), | ||||
|                 conf.getRefLevel(), | ||||
|                 conf.getPowerRange(), | ||||
| 		        conf.getOverlapPercent(), | ||||
| 		        conf.getAverageNb(), | ||||
| 		        conf.getAvgMode(), | ||||
| @ -354,6 +630,12 @@ bool SpectrumVis::handleMessage(const Message& message) | ||||
| 		        conf.getLinear()); | ||||
| 		return true; | ||||
| 	} | ||||
|     else if (MsgConfigureDSP::match(message)) | ||||
|     { | ||||
|         MsgConfigureDSP& conf = (MsgConfigureDSP&) message; | ||||
|         handleConfigureDSP(conf.getCenterFrequency(), conf.getSampleRate()); | ||||
|         return true; | ||||
|     } | ||||
|     else if (MsgConfigureScalingFactor::match(message)) | ||||
|     { | ||||
|         MsgConfigureScalingFactor& conf = (MsgConfigureScalingFactor&) message; | ||||
| @ -367,6 +649,8 @@ bool SpectrumVis::handleMessage(const Message& message) | ||||
| } | ||||
| 
 | ||||
| void SpectrumVis::handleConfigure(int fftSize, | ||||
|         float refLevel, | ||||
|         float powerRange, | ||||
|         int overlapPercent, | ||||
|         unsigned int averageNb, | ||||
|         AvgMode averagingMode, | ||||
| @ -377,25 +661,20 @@ void SpectrumVis::handleConfigure(int fftSize, | ||||
| //            fftSize, overlapPercent, averageNb, (int) averagingMode, (int) window, linear ? "true" : "false");
 | ||||
| 	QMutexLocker mutexLocker(&m_mutex); | ||||
| 
 | ||||
| 	if (fftSize > MAX_FFT_SIZE) | ||||
| 	{ | ||||
| 	if (fftSize > MAX_FFT_SIZE) { | ||||
| 		fftSize = MAX_FFT_SIZE; | ||||
| 	} | ||||
| 	else if (fftSize < 64) | ||||
| 	{ | ||||
| 	} else if (fftSize < 64) { | ||||
| 		fftSize = 64; | ||||
| 	} | ||||
| 
 | ||||
| 	if (overlapPercent > 100) | ||||
| 	{ | ||||
|     m_refLevel = refLevel; | ||||
|     m_powerRange = powerRange; | ||||
| 
 | ||||
| 	if (overlapPercent > 100) { | ||||
| 		m_overlapPercent = 100; | ||||
| 	} | ||||
| 	else if (overlapPercent < 0) | ||||
| 	{ | ||||
| 	} else if (overlapPercent < 0) { | ||||
| 		m_overlapPercent = 0; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
|         m_overlapPercent = overlapPercent; | ||||
| 	} | ||||
| 
 | ||||
| @ -417,6 +696,13 @@ void SpectrumVis::handleConfigure(int fftSize, | ||||
| 	m_powFFTDiv = m_fftSize*m_fftSize; | ||||
| } | ||||
| 
 | ||||
| void SpectrumVis::handleConfigureDSP(uint64_t centerFrequency, int sampleRate) | ||||
| { | ||||
|     QMutexLocker mutexLocker(&m_mutex); | ||||
|     m_centerFrequency = centerFrequency; | ||||
|     m_sampleRate = sampleRate; | ||||
| } | ||||
| 
 | ||||
| void SpectrumVis::handleScalef(Real scalef) | ||||
| { | ||||
|     QMutexLocker mutexLocker(&m_mutex); | ||||
|  | ||||
| @ -21,8 +21,9 @@ | ||||
| #ifndef INCLUDE_SPECTRUMVIS_H | ||||
| #define INCLUDE_SPECTRUMVIS_H | ||||
| 
 | ||||
| #include <dsp/basebandsamplesink.h> | ||||
| #include <QMutex> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "dsp/fftengine.h" | ||||
| #include "dsp/fftwindow.h" | ||||
| #include "export.h" | ||||
| @ -30,6 +31,7 @@ | ||||
| #include "util/movingaverage2d.h" | ||||
| #include "util/fixedaverage2d.h" | ||||
| #include "util/max2d.h" | ||||
| #include "websockets/wsspectrum.h" | ||||
| 
 | ||||
| class GLSpectrumInterface; | ||||
| class MessageQueue; | ||||
| @ -51,6 +53,8 @@ public: | ||||
| 	public: | ||||
| 		MsgConfigureSpectrumVis( | ||||
| 		        int fftSize, | ||||
|                 float refLevel, | ||||
|                 float powerRange, | ||||
| 		        int overlapPercent, | ||||
| 		        unsigned int averageNb, | ||||
| 		        AvgMode avgMode, | ||||
| @ -59,6 +63,8 @@ public: | ||||
| 			Message(), | ||||
| 			m_fftSize(fftSize), | ||||
| 			m_overlapPercent(overlapPercent), | ||||
|             m_refLevel(refLevel), | ||||
|             m_powerRange(powerRange), | ||||
| 			m_averageNb(averageNb), | ||||
|             m_avgMode(avgMode), | ||||
| 			m_window(window), | ||||
| @ -66,6 +72,8 @@ public: | ||||
| 		{} | ||||
| 
 | ||||
| 		int getFFTSize() const { return m_fftSize; } | ||||
|         float getRefLevel() const { return m_refLevel; } | ||||
|         float getPowerRange() const { return m_powerRange; } | ||||
| 		int getOverlapPercent() const { return m_overlapPercent; } | ||||
| 		unsigned int getAverageNb() const { return m_averageNb; } | ||||
| 		SpectrumVis::AvgMode getAvgMode() const { return m_avgMode; } | ||||
| @ -75,12 +83,33 @@ public: | ||||
| 	private: | ||||
| 		int m_fftSize; | ||||
| 		int m_overlapPercent; | ||||
|         float m_refLevel; | ||||
|         float m_powerRange; | ||||
| 		unsigned int m_averageNb; | ||||
| 		SpectrumVis::AvgMode m_avgMode; | ||||
| 		FFTWindow::Function m_window; | ||||
| 		bool m_linear; | ||||
| 	}; | ||||
| 
 | ||||
|     class MsgConfigureDSP : public Message | ||||
|     { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         MsgConfigureDSP(uint64_t centerFrequency, int sampleRate) : | ||||
|             Message(), | ||||
|             m_centerFrequency(centerFrequency), | ||||
|             m_sampleRate(sampleRate) | ||||
|         {} | ||||
| 
 | ||||
|         uint64_t getCenterFrequency() const { return m_centerFrequency; } | ||||
|         int getSampleRate() const { return m_sampleRate; } | ||||
| 
 | ||||
|     private: | ||||
|         uint64_t m_centerFrequency; | ||||
|         int m_sampleRate; | ||||
|     }; | ||||
| 
 | ||||
|     class MsgConfigureScalingFactor : public Message | ||||
|     { | ||||
| 		MESSAGE_CLASS_DECLARATION | ||||
| @ -102,12 +131,15 @@ public: | ||||
| 
 | ||||
| 	void configure(MessageQueue* msgQueue, | ||||
| 	        int fftSize, | ||||
|             float refLevel, | ||||
|             float powerRange, | ||||
| 	        int overlapPercent, | ||||
| 	        unsigned int averagingNb, | ||||
| 	        AvgMode averagingMode, | ||||
| 	        FFTWindow::Function window, | ||||
| 	        bool m_linear); | ||||
|     void setScalef(MessageQueue* msgQueue, Real scalef); | ||||
|     void configureDSP(uint64_t centerFrequency, int sampleRate); | ||||
|     void setScalef(Real scalef); | ||||
| 
 | ||||
| 	virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); | ||||
| 	void feedTriggered(const SampleVector::const_iterator& triggerPoint, const SampleVector::const_iterator& end, bool positiveOnly); | ||||
| @ -124,6 +156,8 @@ private: | ||||
| 	std::vector<Real> m_powerSpectrum; | ||||
| 
 | ||||
| 	std::size_t m_fftSize; | ||||
|     float m_refLevel; | ||||
|     float m_powerRange; | ||||
| 	std::size_t m_overlapPercent; | ||||
| 	std::size_t m_overlapSize; | ||||
| 	std::size_t m_refillSize; | ||||
| @ -132,6 +166,7 @@ private: | ||||
| 
 | ||||
| 	Real m_scalef; | ||||
| 	GLSpectrumInterface* m_glSpectrum; | ||||
|     WSSpectrum m_wsSpectrum; | ||||
| 	MovingAverage2D<double> m_movingAverage; | ||||
| 	FixedAverage2D<double> m_fixedAverage; | ||||
| 	Max2D<double> m_max; | ||||
| @ -139,6 +174,9 @@ private: | ||||
| 	AvgMode m_avgMode; | ||||
| 	bool m_linear; | ||||
| 
 | ||||
|     uint64_t m_centerFrequency; | ||||
|     int m_sampleRate; | ||||
| 
 | ||||
| 	Real m_ofs; | ||||
| 	Real m_powFFTDiv; | ||||
| 	static const Real m_mult; | ||||
| @ -146,11 +184,15 @@ private: | ||||
| 	QMutex m_mutex; | ||||
| 
 | ||||
| 	void handleConfigure(int fftSize, | ||||
|             float refLevel, | ||||
|             float powerRange, | ||||
| 	        int overlapPercent, | ||||
| 	        unsigned int averageNb, | ||||
| 	        AvgMode averagingMode, | ||||
| 	        FFTWindow::Function window, | ||||
| 	        bool linear); | ||||
|     void handleConfigureDSP(uint64_t centerFrequency, | ||||
|             int sampleRate); | ||||
|     void handleScalef(Real scalef); | ||||
| }; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										164
									
								
								sdrbase/websockets/wsspectrum.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								sdrbase/websockets/wsspectrum.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,164 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2020 Edouard Griffiths, F4EXB.                                  //
 | ||||
| //                                                                               //
 | ||||
| // This program is free software; you can redistribute it and/or modify          //
 | ||||
| // it under the terms of the GNU General Public License as published by          //
 | ||||
| // the Free Software Foundation as version 3 of the License, or                  //
 | ||||
| // (at your option) any later version.                                           //
 | ||||
| //                                                                               //
 | ||||
| // This program is distributed in the hope that it will be useful,               //
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of                //
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                  //
 | ||||
| // GNU General Public License V3 for more details.                               //
 | ||||
| //                                                                               //
 | ||||
| // You should have received a copy of the GNU General Public License             //
 | ||||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <QtWebSockets> | ||||
| #include <QHostAddress> | ||||
| #include <QDebug> | ||||
| 
 | ||||
| #include "wsspectrum.h" | ||||
| 
 | ||||
| WSSpectrum::WSSpectrum(QObject *parent) : | ||||
|     QObject(parent), | ||||
|     m_listeningAddress(QHostAddress::LocalHost), | ||||
|     m_port(8887), | ||||
|     m_webSocketServer(nullptr) | ||||
| { | ||||
|     m_timer.start(); | ||||
| } | ||||
| 
 | ||||
| WSSpectrum::~WSSpectrum() | ||||
| { | ||||
|     closeSocket(); | ||||
| } | ||||
| 
 | ||||
| void WSSpectrum::openSocket() | ||||
| { | ||||
|     m_webSocketServer = new QWebSocketServer( | ||||
|         QStringLiteral("Spectrum Server"), | ||||
|         QWebSocketServer::NonSecureMode, | ||||
|         this); | ||||
| 
 | ||||
|     if (m_webSocketServer->listen(m_listeningAddress, m_port)) | ||||
|     { | ||||
|         qDebug() << "WSSpectrum::openSocket: spectrum server listening at " << m_listeningAddress.toString() << " on port " << m_port; | ||||
|         connect(m_webSocketServer, &QWebSocketServer::newConnection, this, &WSSpectrum::onNewConnection); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         qInfo("WSSpectrum::openSocket: cannot start spectrum server at %s on port %u", qPrintable(m_listeningAddress.toString()), m_port); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void WSSpectrum::closeSocket() | ||||
| { | ||||
|     if (m_webSocketServer) | ||||
|     { | ||||
|         delete m_webSocketServer; | ||||
|         m_webSocketServer = nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool WSSpectrum::socketOpened() | ||||
| { | ||||
|     return m_webSocketServer && m_webSocketServer->isListening(); | ||||
| } | ||||
| 
 | ||||
| QString WSSpectrum::getWebSocketIdentifier(QWebSocket *peer) | ||||
| { | ||||
|     return QStringLiteral("%1:%2").arg(peer->peerAddress().toString(), QString::number(peer->peerPort())); | ||||
| } | ||||
| 
 | ||||
| void WSSpectrum::onNewConnection() | ||||
| { | ||||
|     auto pSocket = m_webSocketServer->nextPendingConnection(); | ||||
|     qDebug() << " WSSpectrum::onNewConnection: " << getWebSocketIdentifier(pSocket) << " connected"; | ||||
|     pSocket->setParent(this); | ||||
| 
 | ||||
|     connect(pSocket, &QWebSocket::textMessageReceived, this, &WSSpectrum::processClientMessage); | ||||
|     connect(pSocket, &QWebSocket::disconnected, this, &WSSpectrum::socketDisconnected); | ||||
| 
 | ||||
|     m_clients << pSocket; | ||||
| } | ||||
| 
 | ||||
| void WSSpectrum::processClientMessage(const QString &message) | ||||
| { | ||||
|      qDebug() << "WSSpectrum::processClientMessage: " << message; | ||||
| } | ||||
| 
 | ||||
| void WSSpectrum::socketDisconnected() | ||||
| { | ||||
|     QWebSocket *pClient = qobject_cast<QWebSocket *>(sender()); | ||||
|     qDebug() << getWebSocketIdentifier(pClient) << " disconnected"; | ||||
| 
 | ||||
|     if (pClient) | ||||
|     { | ||||
|         m_clients.removeAll(pClient); | ||||
|         pClient->deleteLater(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void WSSpectrum::newSpectrum( | ||||
|     const std::vector<Real>& spectrum, | ||||
|     int fftSize, | ||||
|     float refLevel, | ||||
|     float powerRange, | ||||
|     uint64_t centerFrequency, | ||||
|     int bandwidth, | ||||
|     bool linear | ||||
| ) | ||||
| { | ||||
|     if (m_timer.elapsed() < 200) { // Max 5 frames per second
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     qint64 elapsed = m_timer.restart(); | ||||
|     QWebSocket *pSender = qobject_cast<QWebSocket *>(sender()); | ||||
|     QByteArray payload; | ||||
| 
 | ||||
|     buildPayload( | ||||
|         payload, | ||||
|         spectrum, | ||||
|         fftSize, | ||||
|         elapsed, | ||||
|         refLevel, | ||||
|         powerRange, | ||||
|         centerFrequency, | ||||
|         bandwidth, | ||||
|         linear | ||||
|     ); | ||||
|     //qDebug() << "WSSpectrum::newSpectrum: " << payload.size() << " bytes in " << elapsed << " ms";
 | ||||
| 
 | ||||
|     for (QWebSocket *pClient : qAsConst(m_clients)) { | ||||
|         pClient->sendBinaryMessage(payload); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void WSSpectrum::buildPayload( | ||||
|     QByteArray& bytes, | ||||
|     const std::vector<Real>& spectrum, | ||||
|     int fftSize, | ||||
|     int64_t fftTimeMs, | ||||
|     float refLevel, | ||||
|     float powerRange, | ||||
|     uint64_t centerFrequency, | ||||
|     int bandwidth, | ||||
|     bool linear | ||||
| ) | ||||
| { | ||||
|     QBuffer buffer(&bytes); | ||||
|     buffer.open(QIODevice::WriteOnly); | ||||
|     buffer.write((char*) &fftSize, sizeof(int)); | ||||
|     buffer.write((char*) &fftTimeMs, sizeof(int64_t)); | ||||
|     buffer.write((char*) &refLevel, sizeof(float)); | ||||
|     buffer.write((char*) &powerRange, sizeof(float)); | ||||
|     buffer.write((char*) ¢erFrequency, sizeof(uint64_t)); | ||||
|     buffer.write((char*) &bandwidth, sizeof(int)); | ||||
|     int linearInt = linear ? 1 : 0; | ||||
|     buffer.write((char*) &linearInt, sizeof(int)); | ||||
|     buffer.write((char*) spectrum.data(), fftSize*sizeof(Real)); | ||||
|     buffer.close(); | ||||
| } | ||||
							
								
								
									
										83
									
								
								sdrbase/websockets/wsspectrum.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								sdrbase/websockets/wsspectrum.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2020 Edouard Griffiths, F4EXB.                                  //
 | ||||
| //                                                                               //
 | ||||
| // This program is free software; you can redistribute it and/or modify          //
 | ||||
| // it under the terms of the GNU General Public License as published by          //
 | ||||
| // the Free Software Foundation as version 3 of the License, or                  //
 | ||||
| // (at your option) any later version.                                           //
 | ||||
| //                                                                               //
 | ||||
| // This program is distributed in the hope that it will be useful,               //
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of                //
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                  //
 | ||||
| // GNU General Public License V3 for more details.                               //
 | ||||
| //                                                                               //
 | ||||
| // You should have received a copy of the GNU General Public License             //
 | ||||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #ifndef SDRBASE_WEBSOCKETS_WSSPECTRUM_H_ | ||||
| #define SDRBASE_WEBSOCKETS_WSSPECTRUM_H_ | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QObject> | ||||
| #include <QList> | ||||
| #include <QElapsedTimer> | ||||
| #include <QHostAddress> | ||||
| 
 | ||||
| #include "dsp/dsptypes.h" | ||||
| 
 | ||||
| #include "export.h" | ||||
| 
 | ||||
| class QWebSocketServer; | ||||
| class QWebSocket; | ||||
| 
 | ||||
| class SDRBASE_API WSSpectrum : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     explicit WSSpectrum(QObject *parent = nullptr); | ||||
|     ~WSSpectrum() override; | ||||
| 
 | ||||
|     void openSocket(); | ||||
|     void closeSocket(); | ||||
|     bool socketOpened(); | ||||
|     void setListeningAddress(const QString& address) { m_listeningAddress.setAddress(address); } | ||||
|     void setPort(quint16 port) { m_port = port; } | ||||
|     void newSpectrum( | ||||
|         const std::vector<Real>& spectrum, | ||||
|         int fftSize, | ||||
|         float refLevel, | ||||
|         float powerRange, | ||||
|         uint64_t centerFrequency, | ||||
|         int bandwidth, | ||||
|         bool linear | ||||
|     ); | ||||
| 
 | ||||
| private slots: | ||||
|     void onNewConnection(); | ||||
|     void processClientMessage(const QString &message); | ||||
|     void socketDisconnected(); | ||||
| 
 | ||||
| private: | ||||
|     QHostAddress m_listeningAddress; | ||||
|     quint16 m_port; | ||||
|     QWebSocketServer* m_webSocketServer; | ||||
|     QList<QWebSocket*> m_clients; | ||||
|     QElapsedTimer m_timer; | ||||
| 
 | ||||
|     static QString getWebSocketIdentifier(QWebSocket *peer); | ||||
|     void buildPayload( | ||||
|         QByteArray& bytes, | ||||
|         const std::vector<Real>& spectrum, | ||||
|         int fftSize, | ||||
|         int64_t fftTimeMs, | ||||
|         float refLevel, | ||||
|         float powerRange, | ||||
|         uint64_t centerFrequency, | ||||
|         int bandwidth, | ||||
|         bool linear | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| #endif // SDRBASE_WEBSOCKETS_WSSPECTRUM_H_
 | ||||
| @ -74,7 +74,7 @@ DeviceUISet::~DeviceUISet() | ||||
| 
 | ||||
| void DeviceUISet::setSpectrumScalingFactor(float scalef) | ||||
| { | ||||
|     m_spectrumVis->setScalef(m_spectrumVis->getInputMessageQueue(), scalef); | ||||
|     m_spectrumVis->setScalef(scalef); | ||||
| } | ||||
| 
 | ||||
| void DeviceUISet::addChannelMarker(ChannelMarker* channelMarker) | ||||
|  | ||||
| @ -231,6 +231,8 @@ void GLSpectrumGUI::applySettingsVis() | ||||
| 	{ | ||||
| 	    m_spectrumVis->configure(m_messageQueueToVis, | ||||
| 	            m_fftSize, | ||||
|                 m_refLevel, | ||||
|                 m_powerRange, | ||||
| 	            m_fftOverlap, | ||||
| 	            m_averagingNb, | ||||
| 	            (SpectrumVis::AvgMode) m_averagingMode, | ||||
| @ -338,6 +340,8 @@ void GLSpectrumGUI::on_refLevel_currentIndexChanged(int index) | ||||
| 	    m_glSpectrum->setReferenceLevel(refLevel); | ||||
|         m_glSpectrum->setPowerRange(powerRange); | ||||
| 	} | ||||
| 
 | ||||
|     applySettingsVis(); | ||||
| } | ||||
| 
 | ||||
| void GLSpectrumGUI::on_levelRange_currentIndexChanged(int index) | ||||
| @ -352,6 +356,8 @@ void GLSpectrumGUI::on_levelRange_currentIndexChanged(int index) | ||||
|         m_glSpectrum->setReferenceLevel(refLevel); | ||||
| 	    m_glSpectrum->setPowerRange(powerRange); | ||||
| 	} | ||||
| 
 | ||||
|     applySettingsVis(); | ||||
| } | ||||
| 
 | ||||
| void GLSpectrumGUI::on_decay_valueChanged(int index) | ||||
|  | ||||
| @ -37,8 +37,6 @@ class DSPEngine; | ||||
| class DSPDeviceSourceEngine; | ||||
| class DSPDeviceSinkEngine; | ||||
| class Indicator; | ||||
| class SpectrumVis; | ||||
| class GLSpectrum; | ||||
| class GLSpectrumGUI; | ||||
| class ChannelWindow; | ||||
| class PluginAPI; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user