mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-31 13:00:26 -04:00 
			
		
		
		
	Rx plugins: refactoring of classes (1)
This commit is contained in:
		
							parent
							
								
									acd0892536
								
							
						
					
					
						commit
						5b83b2a4a8
					
				| @ -3,6 +3,7 @@ project(demod) | ||||
| add_subdirectory(demodam) | ||||
| add_subdirectory(demodbfm) | ||||
| add_subdirectory(demodnfm) | ||||
| add_subdirectory(demodnfmtest) | ||||
| add_subdirectory(demodssb) | ||||
| add_subdirectory(udpsink) | ||||
| add_subdirectory(demodwfm) | ||||
|  | ||||
| @ -2,14 +2,18 @@ project(am) | ||||
| 
 | ||||
| set(am_SOURCES | ||||
| 	amdemod.cpp | ||||
| 	amdemodsettings.cpp | ||||
|     amdemodsettings.cpp | ||||
|     amdemodbaseband.cpp | ||||
|     amdemodsink.cpp | ||||
|     amdemodplugin.cpp | ||||
|     amdemodwebapiadapter.cpp | ||||
| ) | ||||
| 
 | ||||
| set(am_HEADERS | ||||
| 	amdemod.h | ||||
| 	amdemodsettings.h | ||||
|     amdemodsettings.h | ||||
|     amdemodbaseband.h | ||||
|     amdemodsink.h | ||||
|     amdemodplugin.h | ||||
|     amdemodwebapiadapter.h | ||||
| ) | ||||
| @ -24,13 +28,12 @@ if(NOT SERVER_MODE) | ||||
|         ${am_SOURCES} | ||||
|         amdemodgui.cpp | ||||
|         amdemodssbdialog.cpp | ||||
| 
 | ||||
|         amdemodgui.ui | ||||
|         amdemodssb.ui | ||||
|     ) | ||||
|     set(am_HEADERS | ||||
|         ${am_HEADERS} | ||||
| 	amdemodgui.h | ||||
| 	    amdemodgui.h | ||||
|         amdemodssbdialog.h | ||||
|     ) | ||||
| 
 | ||||
| @ -50,8 +53,8 @@ add_library(${TARGET_NAME} SHARED | ||||
| ) | ||||
| 
 | ||||
| target_link_libraries(${TARGET_NAME} | ||||
|         Qt5::Core | ||||
|         ${TARGET_LIB} | ||||
|     Qt5::Core | ||||
|     ${TARGET_LIB} | ||||
| 	sdrbase | ||||
| 	${TARGET_LIB_GUI} | ||||
| ) | ||||
|  | ||||
| @ -22,6 +22,7 @@ | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| #include <QThread> | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include <complex.h> | ||||
| @ -31,19 +32,12 @@ | ||||
| #include "SWGChannelReport.h" | ||||
| #include "SWGAMDemodReport.h" | ||||
| 
 | ||||
| #include "dsp/downchannelizer.h" | ||||
| #include "audio/audiooutput.h" | ||||
| #include "dsp/dspengine.h" | ||||
| #include "dsp/threadedbasebandsamplesink.h" | ||||
| #include "dsp/dspcommands.h" | ||||
| #include "dsp/devicesamplemimo.h" | ||||
| #include "dsp/fftfilt.h" | ||||
| #include "device/deviceapi.h" | ||||
| #include "util/db.h" | ||||
| #include "util/stepfunctions.h" | ||||
| 
 | ||||
| MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureAMDemod, Message) | ||||
| MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureChannelizer, Message) | ||||
| 
 | ||||
| const QString AMDemod::m_channelIdURI = "sdrangel.channel.amdemod"; | ||||
| const QString AMDemod::m_channelId = "AMDemod"; | ||||
| @ -52,45 +46,19 @@ const int AMDemod::m_udpBlockSize = 512; | ||||
| AMDemod::AMDemod(DeviceAPI *deviceAPI) : | ||||
|         ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), | ||||
|         m_deviceAPI(deviceAPI), | ||||
|         m_inputSampleRate(48000), | ||||
|         m_inputFrequencyOffset(0), | ||||
|         m_running(false), | ||||
|         m_squelchOpen(false), | ||||
|         m_squelchDelayLine(9600), | ||||
|         m_magsqSum(0.0f), | ||||
|         m_magsqPeak(0.0f), | ||||
|         m_magsqCount(0), | ||||
|         m_volumeAGC(0.003), | ||||
|         m_syncAMAGC(12000, 0.1, 1e-2), | ||||
|         m_audioFifo(48000), | ||||
|         m_settingsMutex(QMutex::Recursive) | ||||
|         m_basebandSampleRate(0) | ||||
| { | ||||
|     setObjectName(m_channelId); | ||||
| 
 | ||||
| 	m_audioBuffer.resize(1<<14); | ||||
| 	m_audioBufferFill = 0; | ||||
|     m_thread = new QThread(this); | ||||
|     m_basebandSink = new AMDemodBaseband(); | ||||
|     m_basebandSink->moveToThread(m_thread); | ||||
| 
 | ||||
| 	m_magsq = 0.0; | ||||
| 	applySettings(m_settings, true); | ||||
| 
 | ||||
| 	DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); | ||||
| 	m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); | ||||
|     DSBFilter = new fftfilt((2.0f * m_settings.m_rfBandwidth) / m_audioSampleRate, 2 * 1024); | ||||
|     SSBFilter = new fftfilt(0.0f, m_settings.m_rfBandwidth / m_audioSampleRate, 1024); | ||||
|     m_syncAMAGC.setThresholdEnable(false); | ||||
|     m_syncAMAGC.resize(12000, 6000, 0.1); | ||||
| 
 | ||||
|     applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); | ||||
|     applySettings(m_settings, true); | ||||
| 
 | ||||
|     m_channelizer = new DownChannelizer(this); | ||||
|     m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); | ||||
|     m_deviceAPI->addChannelSink(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelSink(this); | ||||
|     m_deviceAPI->addChannelSinkAPI(this); | ||||
| 
 | ||||
|     m_pllFilt.create(101, m_audioSampleRate, 200.0); | ||||
|     m_pll.computeCoefficients(0.05, 0.707, 1000); | ||||
|     m_syncAMBuffIndex = 0; | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| @ -99,13 +67,10 @@ AMDemod::~AMDemod() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
| 	DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); | ||||
|     m_deviceAPI->removeChannelSinkAPI(this); | ||||
|     m_deviceAPI->removeChannelSink(m_threadedChannelizer); | ||||
|     delete m_threadedChannelizer; | ||||
|     delete m_channelizer; | ||||
|     delete DSBFilter; | ||||
|     delete SSBFilter; | ||||
| 	m_deviceAPI->removeChannelSinkAPI(this); | ||||
|     m_deviceAPI->removeChannelSink(this); | ||||
|     delete m_basebandSink; | ||||
|     delete m_thread; | ||||
| } | ||||
| 
 | ||||
| uint32_t AMDemod::getNumberOfDeviceStreams() const | ||||
| @ -116,226 +81,31 @@ uint32_t AMDemod::getNumberOfDeviceStreams() const | ||||
| void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) | ||||
| { | ||||
|     (void) firstOfBurst; | ||||
| 	Complex ci; | ||||
| 
 | ||||
| 	if (!m_running) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
| 	m_settingsMutex.lock(); | ||||
| 
 | ||||
| 	for (SampleVector::const_iterator it = begin; it != end; ++it) | ||||
| 	{ | ||||
| 		Complex c(it->real(), it->imag()); | ||||
| 		c *= m_nco.nextIQ(); | ||||
| 
 | ||||
| 		if (m_interpolatorDistance < 1.0f) // interpolate
 | ||||
| 		{ | ||||
| 		    while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) | ||||
|             { | ||||
|                 processOneSample(ci); | ||||
|                 m_interpolatorDistanceRemain += m_interpolatorDistance; | ||||
|             } | ||||
| 		} | ||||
| 		else // decimate
 | ||||
| 		{ | ||||
| 	        if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) | ||||
| 	        { | ||||
| 	            processOneSample(ci); | ||||
| 	            m_interpolatorDistanceRemain += m_interpolatorDistance; | ||||
| 	        } | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (m_audioBufferFill > 0) | ||||
| 	{ | ||||
| 		uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); | ||||
| 
 | ||||
| 		if (res != m_audioBufferFill) | ||||
| 		{ | ||||
| 			qDebug("AMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill); | ||||
| 		} | ||||
| 
 | ||||
| 		m_audioBufferFill = 0; | ||||
| 	} | ||||
| 
 | ||||
| 	m_settingsMutex.unlock(); | ||||
| } | ||||
| 
 | ||||
| void AMDemod::processOneSample(Complex &ci) | ||||
| { | ||||
|     Real re = ci.real() / SDR_RX_SCALEF; | ||||
|     Real im = ci.imag() / SDR_RX_SCALEF; | ||||
|     Real magsq = re*re + im*im; | ||||
|     m_movingAverage(magsq); | ||||
|     m_magsq = m_movingAverage.asDouble(); | ||||
|     m_magsqSum += magsq; | ||||
| 
 | ||||
|     if (magsq > m_magsqPeak) | ||||
|     { | ||||
|         m_magsqPeak = magsq; | ||||
|     } | ||||
| 
 | ||||
|     m_magsqCount++; | ||||
| 
 | ||||
|     m_squelchDelayLine.write(magsq); | ||||
| 
 | ||||
|     if (m_magsq < m_squelchLevel) | ||||
|     { | ||||
|         if (m_squelchCount > 0) { | ||||
|             m_squelchCount--; | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         if (m_squelchCount < m_audioSampleRate / 10) { | ||||
|             m_squelchCount++; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     qint16 sample; | ||||
| 
 | ||||
|     m_squelchOpen = (m_squelchCount >= m_audioSampleRate / 20); | ||||
| 
 | ||||
|     if (m_squelchOpen && !m_settings.m_audioMute) | ||||
|     { | ||||
|         Real demod; | ||||
| 
 | ||||
|         if (m_settings.m_pll) | ||||
|         { | ||||
|             std::complex<float> s(re, im); | ||||
|             s = m_pllFilt.filter(s); | ||||
|             m_pll.feed(s.real(), s.imag()); | ||||
|             float yr = re * m_pll.getImag() - im * m_pll.getReal(); | ||||
|             float yi = re * m_pll.getReal() + im * m_pll.getImag(); | ||||
| 
 | ||||
|             fftfilt::cmplx *sideband; | ||||
|             std::complex<float> cs(yr, yi); | ||||
|             int n_out; | ||||
| 
 | ||||
|             if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { | ||||
|                 n_out = DSBFilter->runDSB(cs, &sideband, false); | ||||
|             } else { | ||||
|                 n_out = SSBFilter->runSSB(cs, &sideband, m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB, false); | ||||
|             } | ||||
| 
 | ||||
|             for (int i = 0; i < n_out; i++) | ||||
|             { | ||||
|                 float agcVal = m_syncAMAGC.feedAndGetValue(sideband[i]); | ||||
|                 fftfilt::cmplx z = sideband[i] * agcVal; // * m_syncAMAGC.getStepValue();
 | ||||
| 
 | ||||
|                 if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { | ||||
|                     m_syncAMBuff[i] = (z.real() + z.imag()); | ||||
|                 } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { | ||||
|                     m_syncAMBuff[i] = (z.real() + z.imag()); | ||||
|                 } else { | ||||
|                     m_syncAMBuff[i] = (z.real() + z.imag()); | ||||
|                 } | ||||
| 
 | ||||
| //                    if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) {
 | ||||
| //                        m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag())/2.0f;
 | ||||
| //                    } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) {
 | ||||
| //                        m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag());
 | ||||
| //                    } else {
 | ||||
| //                        m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag());
 | ||||
| //                    }
 | ||||
| 
 | ||||
|                 m_syncAMBuffIndex = 0; | ||||
|             } | ||||
| 
 | ||||
|             m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0; | ||||
|             demod = m_syncAMBuff[m_syncAMBuffIndex++]*4.0f; // mos pifometrico
 | ||||
| //                demod = m_syncAMBuff[m_syncAMBuffIndex++]*(SDR_RX_SCALEF/602.0f);
 | ||||
| //                m_volumeAGC.feed(demod);
 | ||||
| //                demod /= (10.0*m_volumeAGC.getValue());
 | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); | ||||
|             m_volumeAGC.feed(demod); | ||||
|             demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); | ||||
|         } | ||||
| 
 | ||||
|         if (m_settings.m_bandpassEnable) | ||||
|         { | ||||
|             demod = m_bandpass.filter(demod); | ||||
|             demod /= 301.0f; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             demod = m_lowpass.filter(demod); | ||||
|         } | ||||
| 
 | ||||
|         Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate); | ||||
|         sample = demod * StepFunctions::smootherstep(attack) * (m_audioSampleRate/24) * m_settings.m_volume; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         sample = 0; | ||||
|     } | ||||
| 
 | ||||
|     m_audioBuffer[m_audioBufferFill].l = sample; | ||||
|     m_audioBuffer[m_audioBufferFill].r = sample; | ||||
|     ++m_audioBufferFill; | ||||
| 
 | ||||
|     if (m_audioBufferFill >= m_audioBuffer.size()) | ||||
|     { | ||||
|         uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); | ||||
| 
 | ||||
|         if (res != m_audioBufferFill) | ||||
|         { | ||||
|             qDebug("AMDemod::processOneSample: %u/%u audio samples written", res, m_audioBufferFill); | ||||
|             m_audioFifo.clear(); | ||||
|         } | ||||
| 
 | ||||
|         m_audioBufferFill = 0; | ||||
|     } | ||||
|     m_basebandSink->feed(begin, end); | ||||
| } | ||||
| 
 | ||||
| void AMDemod::start() | ||||
| { | ||||
| 	qDebug("AMDemod::start"); | ||||
| 	m_squelchCount = 0; | ||||
| 	m_audioFifo.clear(); | ||||
|     applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); | ||||
|     m_running = true; | ||||
| 
 | ||||
|     if (m_basebandSampleRate != 0) { | ||||
|         m_basebandSink->setBasebandSampleRate(m_basebandSampleRate); | ||||
|     } | ||||
| 
 | ||||
|     m_basebandSink->reset(); | ||||
|     m_thread->start(); | ||||
| } | ||||
| 
 | ||||
| void AMDemod::stop() | ||||
| { | ||||
|     qDebug("AMDemod::stop"); | ||||
|     m_running = false; | ||||
| 	m_thread->exit(); | ||||
| 	m_thread->wait(); | ||||
| } | ||||
| 
 | ||||
| bool AMDemod::handleMessage(const Message& cmd) | ||||
| { | ||||
| 	if (DownChannelizer::MsgChannelizerNotification::match(cmd)) | ||||
| 	{ | ||||
| 		DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; | ||||
| 
 | ||||
|         qDebug() << "AMDemod::handleMessage: MsgChannelizerNotification:" | ||||
|                 << " inputSampleRate: " << notif.getSampleRate() | ||||
|                 << " inputFrequencyOffset: " << notif.getFrequencyOffset(); | ||||
| 
 | ||||
|         applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); | ||||
| 
 | ||||
| 		return true; | ||||
| 	} | ||||
| 	else if (MsgConfigureChannelizer::match(cmd)) | ||||
| 	{ | ||||
| 	    MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; | ||||
| 
 | ||||
| 	    qDebug() << "AMDemod::handleMessage: MsgConfigureChannelizer:" | ||||
|                 << " sampleRate: " << cfg.getSampleRate() | ||||
|                 << " inputFrequencyOffset: " << cfg.getCenterFrequency(); | ||||
| 
 | ||||
|         m_channelizer->configure(m_channelizer->getInputMessageQueue(), | ||||
|             cfg.getSampleRate(), | ||||
|             cfg.getCenterFrequency()); | ||||
| 
 | ||||
|         return true; | ||||
| 	} | ||||
| 	else if (MsgConfigureAMDemod::match(cmd)) | ||||
| 	if (MsgConfigureAMDemod::match(cmd)) | ||||
| 	{ | ||||
|         MsgConfigureAMDemod& cfg = (MsgConfigureAMDemod&) cmd; | ||||
|         qDebug() << "AMDemod::handleMessage: MsgConfigureAMDemod"; | ||||
| @ -343,30 +113,16 @@ bool AMDemod::handleMessage(const Message& cmd) | ||||
| 
 | ||||
| 		return true; | ||||
| 	} | ||||
|     else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) | ||||
|     { | ||||
|         BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; | ||||
|         const QThread *thread = cfg.getThread(); | ||||
|         qDebug("AMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); | ||||
|         return true; | ||||
|     } | ||||
|     else if (DSPSignalNotification::match(cmd)) | ||||
|     { | ||||
|         return true; | ||||
|     } | ||||
|     else if (DSPConfigureAudio::match(cmd)) | ||||
|     { | ||||
|         DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; | ||||
|         uint32_t sampleRate = cfg.getSampleRate(); | ||||
|         DSPSignalNotification& notif = (DSPSignalNotification&) cmd; | ||||
|         m_basebandSampleRate = notif.getSampleRate(); | ||||
|         // Forward to the sink
 | ||||
|         DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
 | ||||
|         qDebug() << "AMDemod::handleMessage: DSPSignalNotification"; | ||||
|         m_basebandSink->getInputMessageQueue()->push(rep); | ||||
| 
 | ||||
|         qDebug() << "AMDemod::handleMessage: DSPConfigureAudio:" | ||||
|                 << " sampleRate: " << sampleRate; | ||||
| 
 | ||||
|         if (sampleRate != m_audioSampleRate) { | ||||
|             applyAudioSampleRate(sampleRate); | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
| 	    return true; | ||||
|     } | ||||
| 	else | ||||
| 	{ | ||||
| @ -374,65 +130,6 @@ bool AMDemod::handleMessage(const Message& cmd) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void AMDemod::applyAudioSampleRate(int sampleRate) | ||||
| { | ||||
|     qDebug("AMDemod::applyAudioSampleRate: sampleRate: %d m_inputSampleRate: %d", sampleRate, m_inputSampleRate); | ||||
| 
 | ||||
|     MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( | ||||
|             sampleRate, m_settings.m_inputFrequencyOffset); | ||||
|     m_inputMessageQueue.push(channelConfigMsg); | ||||
| 
 | ||||
|     m_settingsMutex.lock(); | ||||
| 
 | ||||
|     m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f); | ||||
|     m_interpolatorDistanceRemain = 0; | ||||
|     m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; | ||||
|     m_bandpass.create(301, sampleRate, 300.0, m_settings.m_rfBandwidth / 2.0f); | ||||
|     m_lowpass.create(301, sampleRate,  m_settings.m_rfBandwidth / 2.0f); | ||||
|     m_audioFifo.setSize(sampleRate); | ||||
|     m_squelchDelayLine.resize(sampleRate/5); | ||||
|     DSBFilter->create_dsb_filter((2.0f * m_settings.m_rfBandwidth) / (float) sampleRate); | ||||
|     m_pllFilt.create(101, sampleRate, 200.0); | ||||
| 
 | ||||
|     if (m_settings.m_pll) { | ||||
|         m_volumeAGC.resizeNew(sampleRate, 0.003); | ||||
|     } else { | ||||
|         m_volumeAGC.resizeNew(sampleRate/10, 0.003); | ||||
|     } | ||||
| 
 | ||||
|     m_syncAMAGC.resize(sampleRate/4, sampleRate/8, 0.1); | ||||
|     m_pll.setSampleRate(sampleRate); | ||||
| 
 | ||||
|     m_settingsMutex.unlock(); | ||||
|     m_audioSampleRate = sampleRate; | ||||
| } | ||||
| 
 | ||||
| void AMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) | ||||
| { | ||||
|     qDebug() << "AMDemod::applyChannelSettings:" | ||||
|             << " inputSampleRate: " << inputSampleRate | ||||
|             << " inputFrequencyOffset: " << inputFrequencyOffset | ||||
|             << " m_audioSampleRate: " << m_audioSampleRate; | ||||
| 
 | ||||
|     if ((m_inputFrequencyOffset != inputFrequencyOffset) || | ||||
|         (m_inputSampleRate != inputSampleRate) || force) | ||||
|     { | ||||
|         m_nco.setFreq(-inputFrequencyOffset, inputSampleRate); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_inputSampleRate != inputSampleRate) || force) | ||||
|     { | ||||
|         m_settingsMutex.lock(); | ||||
|         m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.2f); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate; | ||||
|         m_settingsMutex.unlock(); | ||||
|     } | ||||
| 
 | ||||
|     m_inputSampleRate = inputSampleRate; | ||||
|     m_inputFrequencyOffset = inputFrequencyOffset; | ||||
| } | ||||
| 
 | ||||
| void AMDemod::applySettings(const AMDemodSettings& settings, bool force) | ||||
| { | ||||
|     qDebug() << "AMDemod::applySettings:" | ||||
| @ -455,66 +152,27 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) | ||||
| 
 | ||||
|     QList<QString> reverseAPIKeys; | ||||
| 
 | ||||
|     if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || | ||||
|         (m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) | ||||
|     { | ||||
|         m_settingsMutex.lock(); | ||||
|         m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.2f); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate; | ||||
|         m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_rfBandwidth / 2.0f); | ||||
|         m_lowpass.create(301, m_audioSampleRate,  settings.m_rfBandwidth / 2.0f); | ||||
|         DSBFilter->create_dsb_filter((2.0f * settings.m_rfBandwidth) / (float) m_audioSampleRate); | ||||
|         m_settingsMutex.unlock(); | ||||
| 
 | ||||
|         if ((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || force) { | ||||
|             reverseAPIKeys.append("rfBandwidth"); | ||||
|         } | ||||
|         if ((m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) { | ||||
|             reverseAPIKeys.append("bandpassEnable"); | ||||
|         } | ||||
|     if ((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || force) { | ||||
|         reverseAPIKeys.append("rfBandwidth"); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_squelch != settings.m_squelch) || force) | ||||
|     { | ||||
|         m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch); | ||||
|     if ((m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) { | ||||
|         reverseAPIKeys.append("bandpassEnable"); | ||||
|     } | ||||
|     if ((m_settings.m_squelch != settings.m_squelch) || force) { | ||||
|         reverseAPIKeys.append("squelch"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) | ||||
|     { | ||||
|         AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); | ||||
|         int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); | ||||
|         //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex);
 | ||||
|         audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); | ||||
|         uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); | ||||
| 
 | ||||
|         if (m_audioSampleRate != audioSampleRate) { | ||||
|             applyAudioSampleRate(audioSampleRate); | ||||
|         } | ||||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { | ||||
|         reverseAPIKeys.append("audioDeviceName"); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_pll != settings.m_pll) || force) | ||||
|     { | ||||
|         if (settings.m_pll) | ||||
|         { | ||||
|             m_volumeAGC.resizeNew(m_audioSampleRate/4, 0.003); | ||||
|             m_syncAMBuffIndex = 0; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             m_volumeAGC.resizeNew(m_audioSampleRate/10, 0.003); | ||||
|         } | ||||
| 
 | ||||
|         reverseAPIKeys.append("pll"); | ||||
|         reverseAPIKeys.append("syncAMOperation"); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_syncAMOperation != settings.m_syncAMOperation) || force) | ||||
|     { | ||||
|         m_syncAMBuffIndex = 0; | ||||
|         reverseAPIKeys.append("pll"); | ||||
|         reverseAPIKeys.append("syncAMOperation"); | ||||
|     } | ||||
| @ -536,16 +194,17 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) | ||||
|         if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
 | ||||
|         { | ||||
|             m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex); | ||||
|             m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex); | ||||
|             m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex); | ||||
|             m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex); | ||||
|             m_deviceAPI->addChannelSink(this, settings.m_streamIndex); | ||||
|             m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex); | ||||
|             // apply stream sample rate to itself
 | ||||
|             applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset); | ||||
|         } | ||||
| 
 | ||||
|         reverseAPIKeys.append("streamIndex"); | ||||
|     } | ||||
| 
 | ||||
|     AMDemodBaseband::MsgConfigureAMDemodBaseband *msg = AMDemodBaseband::MsgConfigureAMDemodBaseband::create(settings, force); | ||||
|     m_basebandSink->getInputMessageQueue()->push(msg); | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
| @ -602,13 +261,6 @@ int AMDemod::webapiSettingsPutPatch( | ||||
|     AMDemodSettings settings = m_settings; | ||||
|     webapiUpdateChannelSettings(settings, channelSettingsKeys, response); | ||||
| 
 | ||||
|     if (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) | ||||
|     { | ||||
|         MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( | ||||
|                 m_audioSampleRate, settings.m_inputFrequencyOffset); | ||||
|         m_inputMessageQueue.push(channelConfigMsg); | ||||
|     } | ||||
| 
 | ||||
|     MsgConfigureAMDemod *msg = MsgConfigureAMDemod::create(settings, force); | ||||
|     m_inputMessageQueue.push(msg); | ||||
| 
 | ||||
| @ -744,9 +396,9 @@ void AMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) | ||||
|     getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); | ||||
| 
 | ||||
|     response.getAmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); | ||||
|     response.getAmDemodReport()->setSquelch(m_squelchOpen ? 1 : 0); | ||||
|     response.getAmDemodReport()->setAudioSampleRate(m_audioSampleRate); | ||||
|     response.getAmDemodReport()->setChannelSampleRate(m_inputSampleRate); | ||||
|     response.getAmDemodReport()->setSquelch(m_basebandSink->getSquelchOpen() ? 1 : 0); | ||||
|     response.getAmDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate()); | ||||
|     response.getAmDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate()); | ||||
| } | ||||
| 
 | ||||
| void AMDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AMDemodSettings& settings, bool force) | ||||
|  | ||||
| @ -21,29 +21,18 @@ | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QNetworkRequest> | ||||
| #include <QMutex> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "channel/channelapi.h" | ||||
| #include "dsp/nco.h" | ||||
| #include "dsp/interpolator.h" | ||||
| #include "util/movingaverage.h" | ||||
| #include "dsp/agc.h" | ||||
| #include "dsp/bandpass.h" | ||||
| #include "dsp/lowpass.h" | ||||
| #include "dsp/phaselockcomplex.h" | ||||
| #include "audio/audiofifo.h" | ||||
| #include "util/message.h" | ||||
| #include "util/doublebufferfifo.h" | ||||
| 
 | ||||
| #include "amdemodbaseband.h" | ||||
| #include "amdemodsettings.h" | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class QThread; | ||||
| class DeviceAPI; | ||||
| class DownChannelizer; | ||||
| class ThreadedBasebandSampleSink; | ||||
| class fftfilt; | ||||
| 
 | ||||
| class AMDemod : public BasebandSampleSink, public ChannelAPI { | ||||
| 	Q_OBJECT | ||||
| @ -71,29 +60,6 @@ public: | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     class MsgConfigureChannelizer : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         int getSampleRate() const { return m_sampleRate; } | ||||
|         int getCenterFrequency() const { return m_centerFrequency; } | ||||
| 
 | ||||
|         static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) | ||||
|         { | ||||
|             return new MsgConfigureChannelizer(sampleRate, centerFrequency); | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
|         int m_sampleRate; | ||||
|         int  m_centerFrequency; | ||||
| 
 | ||||
|         MsgConfigureChannelizer(int sampleRate, int centerFrequency) : | ||||
|             Message(), | ||||
|             m_sampleRate(sampleRate), | ||||
|             m_centerFrequency(centerFrequency) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     AMDemod(DeviceAPI *deviceAPI); | ||||
| 	~AMDemod(); | ||||
| 	virtual void destroy() { delete this; } | ||||
| @ -143,28 +109,14 @@ public: | ||||
|             const QStringList& channelSettingsKeys, | ||||
|             SWGSDRangel::SWGChannelSettings& response); | ||||
| 
 | ||||
|     uint32_t getAudioSampleRate() const { return m_audioSampleRate; } | ||||
| 	double getMagSq() const { return m_magsq; } | ||||
| 	bool getSquelchOpen() const { return m_squelchOpen; } | ||||
| 	bool getPllLocked() const { return m_settings.m_pll && m_pll.locked(); } | ||||
| 	Real getPllFrequency() const { return m_pll.getFreq(); } | ||||
|     uint32_t getAudioSampleRate() const { return m_basebandSink->getAudioSampleRate(); } | ||||
| 	double getMagSq() const { return m_basebandSink->getMagSq(); } | ||||
| 	bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); } | ||||
| 	bool getPllLocked() const { return m_settings.m_pll && m_basebandSink->getPllLocked(); } | ||||
| 	Real getPllFrequency() const { return m_basebandSink->getPllFrequency(); } | ||||
| 
 | ||||
|     void getMagSqLevels(double& avg, double& peak, int& nbSamples) | ||||
|     { | ||||
|         if (m_magsqCount > 0) | ||||
|         { | ||||
|             m_magsq = m_magsqSum / m_magsqCount; | ||||
|             m_magSqLevelStore.m_magsq = m_magsq; | ||||
|             m_magSqLevelStore.m_magsqPeak = m_magsqPeak; | ||||
|         } | ||||
| 
 | ||||
|         avg = m_magSqLevelStore.m_magsq; | ||||
|         peak = m_magSqLevelStore.m_magsqPeak; | ||||
|         nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; | ||||
| 
 | ||||
|         m_magsqSum = 0.0f; | ||||
|         m_magsqPeak = 0.0f; | ||||
|         m_magsqCount = 0; | ||||
|     void getMagSqLevels(double& avg, double& peak, int& nbSamples) { | ||||
|         m_basebandSink->getMagSqLevels(avg, peak, nbSamples); | ||||
|     } | ||||
| 
 | ||||
|     uint32_t getNumberOfDeviceStreams() const; | ||||
| @ -173,77 +125,21 @@ public: | ||||
|     static const QString m_channelId; | ||||
| 
 | ||||
| private: | ||||
|     struct MagSqLevelsStore | ||||
|     { | ||||
|         MagSqLevelsStore() : | ||||
|             m_magsq(1e-12), | ||||
|             m_magsqPeak(1e-12) | ||||
|         {} | ||||
|         double m_magsq; | ||||
|         double m_magsqPeak; | ||||
|     }; | ||||
| 
 | ||||
| 	enum RateState { | ||||
| 		RSInitialFill, | ||||
| 		RSRunning | ||||
| 	}; | ||||
| 
 | ||||
| 	DeviceAPI *m_deviceAPI; | ||||
|     ThreadedBasebandSampleSink* m_threadedChannelizer; | ||||
|     DownChannelizer* m_channelizer; | ||||
| 
 | ||||
|     int m_inputSampleRate; | ||||
|     int m_inputFrequencyOffset; | ||||
|     QThread *m_thread; | ||||
|     AMDemodBaseband* m_basebandSink; | ||||
|     AMDemodSettings m_settings; | ||||
|     uint32_t m_audioSampleRate; | ||||
|     bool m_running; | ||||
| 
 | ||||
| 	NCO m_nco; | ||||
| 	Interpolator m_interpolator; | ||||
| 	Real m_interpolatorDistance; | ||||
| 	Real m_interpolatorDistanceRemain; | ||||
| 
 | ||||
| 	Real m_squelchLevel; | ||||
| 	uint32_t m_squelchCount; | ||||
| 	bool m_squelchOpen; | ||||
| 	DoubleBufferFIFO<Real> m_squelchDelayLine; | ||||
| 	double m_magsq; | ||||
| 	double m_magsqSum; | ||||
| 	double m_magsqPeak; | ||||
| 	int  m_magsqCount; | ||||
| 	MagSqLevelsStore m_magSqLevelStore; | ||||
| 
 | ||||
| 	MovingAverageUtil<Real, double, 16> m_movingAverage; | ||||
| 	SimpleAGC<4800> m_volumeAGC; | ||||
|     Bandpass<Real> m_bandpass; | ||||
|     Lowpass<Real> m_lowpass; | ||||
|     Lowpass<std::complex<float> > m_pllFilt; | ||||
|     PhaseLockComplex m_pll; | ||||
|     fftfilt* DSBFilter; | ||||
|     fftfilt* SSBFilter; | ||||
|     Real m_syncAMBuff[2*1024]; | ||||
|     uint32_t m_syncAMBuffIndex; | ||||
|     MagAGC m_syncAMAGC; | ||||
| 
 | ||||
| 	AudioVector m_audioBuffer; | ||||
| 	uint32_t m_audioBufferFill; | ||||
| 	AudioFifo m_audioFifo; | ||||
|     int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
 | ||||
| 
 | ||||
|     static const int m_udpBlockSize; | ||||
| 
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
| 	QMutex m_settingsMutex; | ||||
| 
 | ||||
| 	void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); | ||||
|     void applySettings(const AMDemodSettings& settings, bool force = false); | ||||
|     void applyAudioSampleRate(int sampleRate); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AMDemodSettings& settings, bool force); | ||||
| 
 | ||||
|     void processOneSample(Complex &ci); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										172
									
								
								plugins/channelrx/demodam/amdemodbaseband.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								plugins/channelrx/demodam/amdemodbaseband.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,172 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 <QDebug> | ||||
| 
 | ||||
| #include "dsp/dspengine.h" | ||||
| #include "dsp/dspcommands.h" | ||||
| #include "dsp/downsamplechannelizer.h" | ||||
| 
 | ||||
| #include "amdemodbaseband.h" | ||||
| 
 | ||||
| MESSAGE_CLASS_DEFINITION(AMDemodBaseband::MsgConfigureAMDemodBaseband, Message) | ||||
| 
 | ||||
| AMDemodBaseband::AMDemodBaseband() : | ||||
|     m_mutex(QMutex::Recursive) | ||||
| { | ||||
|     qDebug("AMDemodBaseband::AMDemodBaseband"); | ||||
| 
 | ||||
|     m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); | ||||
|     m_channelizer = new DownSampleChannelizer(&m_sink); | ||||
| 
 | ||||
|     QObject::connect( | ||||
|         &m_sampleFifo, | ||||
|         &SampleSinkFifo::dataReady, | ||||
|         this, | ||||
|         &AMDemodBaseband::handleData, | ||||
|         Qt::QueuedConnection | ||||
|     ); | ||||
| 
 | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue()); | ||||
|     m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate()); | ||||
| 
 | ||||
|     connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); | ||||
| } | ||||
| 
 | ||||
| AMDemodBaseband::~AMDemodBaseband() | ||||
| { | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo()); | ||||
|     delete m_channelizer; | ||||
| } | ||||
| 
 | ||||
| void AMDemodBaseband::reset() | ||||
| { | ||||
|     QMutexLocker mutexLocker(&m_mutex); | ||||
|     m_sampleFifo.reset(); | ||||
| } | ||||
| 
 | ||||
| void AMDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) | ||||
| { | ||||
|     QMutexLocker mutexLocker(&m_mutex); | ||||
|     m_sampleFifo.write(begin, end); | ||||
| } | ||||
| 
 | ||||
| void AMDemodBaseband::handleData() | ||||
| { | ||||
|     while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) | ||||
|     { | ||||
| 		SampleVector::iterator part1begin; | ||||
| 		SampleVector::iterator part1end; | ||||
| 		SampleVector::iterator part2begin; | ||||
| 		SampleVector::iterator part2end; | ||||
| 
 | ||||
|         std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); | ||||
| 
 | ||||
| 		// first part of FIFO data
 | ||||
|         if (part1begin != part1end) { | ||||
|             m_channelizer->feed(part1begin, part1end); | ||||
|         } | ||||
| 
 | ||||
| 		// second part of FIFO data (used when block wraps around)
 | ||||
| 		if(part2begin != part2end) { | ||||
|             m_channelizer->feed(part2begin, part2end); | ||||
|         } | ||||
| 
 | ||||
| 		m_sampleFifo.readCommit((unsigned int) count); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void AMDemodBaseband::handleInputMessages() | ||||
| { | ||||
| 	Message* message; | ||||
| 
 | ||||
| 	while ((message = m_inputMessageQueue.pop()) != nullptr) | ||||
| 	{ | ||||
| 		if (handleMessage(*message)) { | ||||
| 			delete message; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bool AMDemodBaseband::handleMessage(const Message& cmd) | ||||
| { | ||||
|     if (MsgConfigureAMDemodBaseband::match(cmd)) | ||||
|     { | ||||
|         QMutexLocker mutexLocker(&m_mutex); | ||||
|         MsgConfigureAMDemodBaseband& cfg = (MsgConfigureAMDemodBaseband&) cmd; | ||||
|         qDebug() << "AMDemodBaseband::handleMessage: MsgConfigureAMDemodBaseband"; | ||||
| 
 | ||||
|         applySettings(cfg.getSettings(), cfg.getForce()); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     else if (DSPSignalNotification::match(cmd)) | ||||
|     { | ||||
|         QMutexLocker mutexLocker(&m_mutex); | ||||
|         DSPSignalNotification& notif = (DSPSignalNotification&) cmd; | ||||
|         qDebug() << "AMDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); | ||||
|         m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); | ||||
|         m_channelizer->setBasebandSampleRate(notif.getSampleRate()); | ||||
|         m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); | ||||
| 
 | ||||
| 		return true; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void AMDemodBaseband::applySettings(const AMDemodSettings& settings, bool force) | ||||
| { | ||||
|     if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) | ||||
|     { | ||||
|         m_channelizer->setChannelization(m_sink.getAudioSampleRate(), settings.m_inputFrequencyOffset); | ||||
|         m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) | ||||
|     { | ||||
|         AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); | ||||
|         int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); | ||||
|         //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex);
 | ||||
|         audioDeviceManager->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue(), audioDeviceIndex); | ||||
|         uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); | ||||
| 
 | ||||
|         if (m_sink.getAudioSampleRate() != audioSampleRate) | ||||
|         { | ||||
|             m_sink.applyAudioSampleRate(audioSampleRate); | ||||
|             m_channelizer->setChannelization(audioSampleRate, settings.m_inputFrequencyOffset); | ||||
|             m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m_sink.applySettings(settings, force); | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
| int AMDemodBaseband::getChannelSampleRate() const | ||||
| { | ||||
|     return m_channelizer->getChannelSampleRate(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void AMDemodBaseband::setBasebandSampleRate(int sampleRate) | ||||
| { | ||||
|     m_channelizer->setBasebandSampleRate(sampleRate); | ||||
| } | ||||
							
								
								
									
										89
									
								
								plugins/channelrx/demodam/amdemodbaseband.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								plugins/channelrx/demodam/amdemodbaseband.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 INCLUDE_AMDEMODBASEBAND_H | ||||
| #define INCLUDE_AMDEMODBASEBAND_H | ||||
| 
 | ||||
| #include <QObject> | ||||
| #include <QMutex> | ||||
| 
 | ||||
| #include "dsp/samplesinkfifo.h" | ||||
| #include "util/message.h" | ||||
| #include "util/messagequeue.h" | ||||
| 
 | ||||
| #include "amdemodsink.h" | ||||
| 
 | ||||
| class DownSampleChannelizer; | ||||
| 
 | ||||
| class AMDemodBaseband : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     class MsgConfigureAMDemodBaseband : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         const AMDemodSettings& getSettings() const { return m_settings; } | ||||
|         bool getForce() const { return m_force; } | ||||
| 
 | ||||
|         static MsgConfigureAMDemodBaseband* create(const AMDemodSettings& settings, bool force) | ||||
|         { | ||||
|             return new MsgConfigureAMDemodBaseband(settings, force); | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
|         AMDemodSettings m_settings; | ||||
|         bool m_force; | ||||
| 
 | ||||
|         MsgConfigureAMDemodBaseband(const AMDemodSettings& settings, bool force) : | ||||
|             Message(), | ||||
|             m_settings(settings), | ||||
|             m_force(force) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     AMDemodBaseband(); | ||||
|     ~AMDemodBaseband(); | ||||
|     void reset(); | ||||
|     void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); | ||||
|     MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
 | ||||
|     int getChannelSampleRate() const; | ||||
|     void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); } | ||||
|     bool getSquelchOpen() const { return m_sink.getSquelchOpen(); } | ||||
|     unsigned int getAudioSampleRate() const { return m_sink.getAudioSampleRate(); } | ||||
|     void setBasebandSampleRate(int sampleRate); | ||||
|     double getMagSq() const { return m_sink.getMagSq(); } | ||||
|     bool getPllLocked() const { return m_sink.getPllLocked(); } | ||||
|     Real getPllFrequency() const { return m_sink.getPllFrequency(); } | ||||
| 
 | ||||
| private: | ||||
|     SampleSinkFifo m_sampleFifo; | ||||
|     DownSampleChannelizer *m_channelizer; | ||||
|     AMDemodSink m_sink; | ||||
| 	MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
 | ||||
|     AMDemodSettings m_settings; | ||||
|     QMutex m_mutex; | ||||
| 
 | ||||
|     bool handleMessage(const Message& cmd); | ||||
|     void applySettings(const AMDemodSettings& settings, bool force = false); | ||||
| 
 | ||||
| private slots: | ||||
|     void handleInputMessages(); | ||||
|     void handleData(); //!< Handle data when samples have to be processed
 | ||||
| }; | ||||
| 
 | ||||
| #endif // INCLUDE_AMDEMODBASEBAND_H
 | ||||
| @ -325,11 +325,6 @@ void AMDemodGUI::applySettings(bool force) | ||||
| { | ||||
| 	if (m_doApplySettings) | ||||
| 	{ | ||||
| 		AMDemod::MsgConfigureChannelizer* channelConfigMsg = AMDemod::MsgConfigureChannelizer::create( | ||||
| 		        m_amDemod->getAudioSampleRate(), m_channelMarker.getCenterFrequency()); | ||||
| 		m_amDemod->getInputMessageQueue()->push(channelConfigMsg); | ||||
| 
 | ||||
| 
 | ||||
| 	    AMDemod::MsgConfigureAMDemod* message = AMDemod::MsgConfigureAMDemod::create( m_settings, force); | ||||
| 	    m_amDemod->getInputMessageQueue()->push(message); | ||||
| 	} | ||||
|  | ||||
| @ -10,7 +10,7 @@ | ||||
| 
 | ||||
| const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = { | ||||
| 	QString("AM Demodulator"), | ||||
| 	QString("4.11.6"), | ||||
| 	QString("4.12.2"), | ||||
| 	QString("(c) Edouard Griffiths, F4EXB"), | ||||
| 	QString("https://github.com/f4exb/sdrangel"), | ||||
| 	true, | ||||
|  | ||||
							
								
								
									
										320
									
								
								plugins/channelrx/demodam/amdemodsink.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								plugins/channelrx/demodam/amdemodsink.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,320 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 <QDebug> | ||||
| 
 | ||||
| #include <complex.h> | ||||
| 
 | ||||
| #include "audio/audiooutput.h" | ||||
| #include "dsp/fftfilt.h" | ||||
| #include "util/db.h" | ||||
| #include "util/stepfunctions.h" | ||||
| 
 | ||||
| #include "amdemodsink.h" | ||||
| 
 | ||||
| AMDemodSink::AMDemodSink() : | ||||
|         m_channelSampleRate(48000), | ||||
|         m_channelFrequencyOffset(0), | ||||
|         m_squelchOpen(false), | ||||
|         m_squelchDelayLine(9600), | ||||
|         m_magsqSum(0.0f), | ||||
|         m_magsqPeak(0.0f), | ||||
|         m_magsqCount(0), | ||||
|         m_volumeAGC(0.003), | ||||
|         m_syncAMAGC(12000, 0.1, 1e-2), | ||||
|         m_audioFifo(48000) | ||||
| { | ||||
| 	m_audioBuffer.resize(1<<14); | ||||
| 	m_audioBufferFill = 0; | ||||
| 
 | ||||
| 	m_magsq = 0.0; | ||||
| 
 | ||||
|     DSBFilter = new fftfilt((2.0f * m_settings.m_rfBandwidth) / m_audioSampleRate, 2 * 1024); | ||||
|     SSBFilter = new fftfilt(0.0f, m_settings.m_rfBandwidth / m_audioSampleRate, 1024); | ||||
|     m_syncAMAGC.setThresholdEnable(false); | ||||
|     m_syncAMAGC.resize(12000, 6000, 0.1); | ||||
| 
 | ||||
|     m_pllFilt.create(101, m_audioSampleRate, 200.0); | ||||
|     m_pll.computeCoefficients(0.05, 0.707, 1000); | ||||
|     m_syncAMBuffIndex = 0; | ||||
| 
 | ||||
| 	applySettings(m_settings, true); | ||||
|     applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); | ||||
| } | ||||
| 
 | ||||
| AMDemodSink::~AMDemodSink() | ||||
| { | ||||
|     delete DSBFilter; | ||||
|     delete SSBFilter; | ||||
| } | ||||
| 
 | ||||
| void AMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) | ||||
| { | ||||
| 	Complex ci; | ||||
| 
 | ||||
| 	for (SampleVector::const_iterator it = begin; it != end; ++it) | ||||
| 	{ | ||||
| 		Complex c(it->real(), it->imag()); | ||||
| 		c *= m_nco.nextIQ(); | ||||
| 
 | ||||
| 		if (m_interpolatorDistance < 1.0f) // interpolate
 | ||||
| 		{ | ||||
| 		    while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) | ||||
|             { | ||||
|                 processOneSample(ci); | ||||
|                 m_interpolatorDistanceRemain += m_interpolatorDistance; | ||||
|             } | ||||
| 		} | ||||
| 		else // decimate
 | ||||
| 		{ | ||||
| 	        if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) | ||||
| 	        { | ||||
| 	            processOneSample(ci); | ||||
| 	            m_interpolatorDistanceRemain += m_interpolatorDistance; | ||||
| 	        } | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (m_audioBufferFill > 0) | ||||
| 	{ | ||||
| 		uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); | ||||
| 
 | ||||
| 		if (res != m_audioBufferFill) { | ||||
| 			qDebug("AMDemodSink::feed: %u/%u tail samples written", res, m_audioBufferFill); | ||||
| 		} | ||||
| 
 | ||||
| 		m_audioBufferFill = 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void AMDemodSink::processOneSample(Complex &ci) | ||||
| { | ||||
|     Real re = ci.real() / SDR_RX_SCALEF; | ||||
|     Real im = ci.imag() / SDR_RX_SCALEF; | ||||
|     Real magsq = re*re + im*im; | ||||
|     m_movingAverage(magsq); | ||||
|     m_magsq = m_movingAverage.asDouble(); | ||||
|     m_magsqSum += magsq; | ||||
| 
 | ||||
|     if (magsq > m_magsqPeak) | ||||
|     { | ||||
|         m_magsqPeak = magsq; | ||||
|     } | ||||
| 
 | ||||
|     m_magsqCount++; | ||||
| 
 | ||||
|     m_squelchDelayLine.write(magsq); | ||||
| 
 | ||||
|     if (m_magsq < m_squelchLevel) | ||||
|     { | ||||
|         if (m_squelchCount > 0) { | ||||
|             m_squelchCount--; | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         if (m_squelchCount < m_audioSampleRate / 10) { | ||||
|             m_squelchCount++; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     qint16 sample; | ||||
| 
 | ||||
|     m_squelchOpen = (m_squelchCount >= m_audioSampleRate / 20); | ||||
| 
 | ||||
|     if (m_squelchOpen && !m_settings.m_audioMute) | ||||
|     { | ||||
|         Real demod; | ||||
| 
 | ||||
|         if (m_settings.m_pll) | ||||
|         { | ||||
|             std::complex<float> s(re, im); | ||||
|             s = m_pllFilt.filter(s); | ||||
|             m_pll.feed(s.real(), s.imag()); | ||||
|             float yr = re * m_pll.getImag() - im * m_pll.getReal(); | ||||
|             float yi = re * m_pll.getReal() + im * m_pll.getImag(); | ||||
| 
 | ||||
|             fftfilt::cmplx *sideband; | ||||
|             std::complex<float> cs(yr, yi); | ||||
|             int n_out; | ||||
| 
 | ||||
|             if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { | ||||
|                 n_out = DSBFilter->runDSB(cs, &sideband, false); | ||||
|             } else { | ||||
|                 n_out = SSBFilter->runSSB(cs, &sideband, m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB, false); | ||||
|             } | ||||
| 
 | ||||
|             for (int i = 0; i < n_out; i++) | ||||
|             { | ||||
|                 float agcVal = m_syncAMAGC.feedAndGetValue(sideband[i]); | ||||
|                 fftfilt::cmplx z = sideband[i] * agcVal; // * m_syncAMAGC.getStepValue();
 | ||||
| 
 | ||||
|                 if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { | ||||
|                     m_syncAMBuff[i] = (z.real() + z.imag()); | ||||
|                 } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { | ||||
|                     m_syncAMBuff[i] = (z.real() + z.imag()); | ||||
|                 } else { | ||||
|                     m_syncAMBuff[i] = (z.real() + z.imag()); | ||||
|                 } | ||||
| 
 | ||||
|                 m_syncAMBuffIndex = 0; | ||||
|             } | ||||
| 
 | ||||
|             m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0; | ||||
|             demod = m_syncAMBuff[m_syncAMBuffIndex++]*4.0f; // mos pifometrico
 | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); | ||||
|             m_volumeAGC.feed(demod); | ||||
|             demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); | ||||
|         } | ||||
| 
 | ||||
|         if (m_settings.m_bandpassEnable) | ||||
|         { | ||||
|             demod = m_bandpass.filter(demod); | ||||
|             demod /= 301.0f; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             demod = m_lowpass.filter(demod); | ||||
|         } | ||||
| 
 | ||||
|         Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate); | ||||
|         sample = demod * StepFunctions::smootherstep(attack) * (m_audioSampleRate/24) * m_settings.m_volume; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         sample = 0; | ||||
|     } | ||||
| 
 | ||||
|     m_audioBuffer[m_audioBufferFill].l = sample; | ||||
|     m_audioBuffer[m_audioBufferFill].r = sample; | ||||
|     ++m_audioBufferFill; | ||||
| 
 | ||||
|     if (m_audioBufferFill >= m_audioBuffer.size()) | ||||
|     { | ||||
|         uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); | ||||
| 
 | ||||
|         if (res != m_audioBufferFill) | ||||
|         { | ||||
|             qDebug("AMDemodSink::processOneSample: %u/%u audio samples written", res, m_audioBufferFill); | ||||
|             m_audioFifo.clear(); | ||||
|         } | ||||
| 
 | ||||
|         m_audioBufferFill = 0; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void AMDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) | ||||
| { | ||||
|     qDebug() << "AMDemodSink::applyChannelSettings:" | ||||
|             << " channelSampleRate: " << channelSampleRate | ||||
|             << " channelFrequencyOffset: " << channelFrequencyOffset | ||||
|             << " m_audioSampleRate: " << m_audioSampleRate; | ||||
| 
 | ||||
|     if ((m_channelFrequencyOffset != channelFrequencyOffset) || | ||||
|         (m_channelSampleRate != channelSampleRate) || force) | ||||
|     { | ||||
|         m_nco.setFreq(-channelFrequencyOffset, channelSampleRate); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_channelSampleRate != channelSampleRate) || force) | ||||
|     { | ||||
|         m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2f); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance = (Real) channelSampleRate / (Real) m_audioSampleRate; | ||||
|     } | ||||
| 
 | ||||
|     m_channelSampleRate = channelSampleRate; | ||||
|     m_channelFrequencyOffset = channelFrequencyOffset; | ||||
| } | ||||
| 
 | ||||
| void AMDemodSink::applySettings(const AMDemodSettings& settings, bool force) | ||||
| { | ||||
|     qDebug() << "AMDemodSink::applySettings:" | ||||
|             << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset | ||||
|             << " m_rfBandwidth: " << settings.m_rfBandwidth | ||||
|             << " m_volume: " << settings.m_volume | ||||
|             << " m_squelch: " << settings.m_squelch | ||||
|             << " m_audioMute: " << settings.m_audioMute | ||||
|             << " m_bandpassEnable: " << settings.m_bandpassEnable | ||||
|             << " m_audioDeviceName: " << settings.m_audioDeviceName | ||||
|             << " m_pll: " << settings.m_pll | ||||
|             << " m_syncAMOperation: " << (int) settings.m_syncAMOperation | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || | ||||
|         (m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) | ||||
|     { | ||||
|         m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2f); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_audioSampleRate; | ||||
|         m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_rfBandwidth / 2.0f); | ||||
|         m_lowpass.create(301, m_audioSampleRate,  settings.m_rfBandwidth / 2.0f); | ||||
|         DSBFilter->create_dsb_filter((2.0f * settings.m_rfBandwidth) / (float) m_audioSampleRate); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_squelch != settings.m_squelch) || force) { | ||||
|         m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_pll != settings.m_pll) || force) | ||||
|     { | ||||
|         if (settings.m_pll) | ||||
|         { | ||||
|             m_volumeAGC.resizeNew(m_audioSampleRate/4, 0.003); | ||||
|             m_syncAMBuffIndex = 0; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             m_volumeAGC.resizeNew(m_audioSampleRate/10, 0.003); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_syncAMOperation != settings.m_syncAMOperation) || force) { | ||||
|         m_syncAMBuffIndex = 0; | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
| void AMDemodSink::applyAudioSampleRate(int sampleRate) | ||||
| { | ||||
|     qDebug("AMDemodSink::applyAudioSampleRate: sampleRate: %d m_channelSampleRate: %d", sampleRate, m_channelSampleRate); | ||||
| 
 | ||||
|     m_interpolator.create(16, m_channelSampleRate, m_settings.m_rfBandwidth / 2.2f); | ||||
|     m_interpolatorDistanceRemain = 0; | ||||
|     m_interpolatorDistance = (Real) m_channelSampleRate / (Real) sampleRate; | ||||
|     m_bandpass.create(301, sampleRate, 300.0, m_settings.m_rfBandwidth / 2.0f); | ||||
|     m_lowpass.create(301, sampleRate,  m_settings.m_rfBandwidth / 2.0f); | ||||
|     m_audioFifo.setSize(sampleRate); | ||||
|     m_squelchDelayLine.resize(sampleRate/5); | ||||
|     DSBFilter->create_dsb_filter((2.0f * m_settings.m_rfBandwidth) / (float) sampleRate); | ||||
|     m_pllFilt.create(101, sampleRate, 200.0); | ||||
| 
 | ||||
|     if (m_settings.m_pll) { | ||||
|         m_volumeAGC.resizeNew(sampleRate, 0.003); | ||||
|     } else { | ||||
|         m_volumeAGC.resizeNew(sampleRate/10, 0.003); | ||||
|     } | ||||
| 
 | ||||
|     m_syncAMAGC.resize(sampleRate/4, sampleRate/8, 0.1); | ||||
|     m_pll.setSampleRate(sampleRate); | ||||
| 
 | ||||
|     m_audioSampleRate = sampleRate; | ||||
| } | ||||
							
								
								
									
										127
									
								
								plugins/channelrx/demodam/amdemodsink.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								plugins/channelrx/demodam/amdemodsink.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,127 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 INCLUDE_AMDEMODSINK_H | ||||
| #define INCLUDE_AMDEMODSINK_H | ||||
| 
 | ||||
| #include "dsp/channelsamplesink.h" | ||||
| #include "dsp/nco.h" | ||||
| #include "dsp/interpolator.h" | ||||
| #include "dsp/agc.h" | ||||
| #include "dsp/bandpass.h" | ||||
| #include "dsp/lowpass.h" | ||||
| #include "dsp/phaselockcomplex.h" | ||||
| #include "audio/audiofifo.h" | ||||
| #include "util/movingaverage.h" | ||||
| #include "util/doublebufferfifo.h" | ||||
| 
 | ||||
| #include "amdemodsettings.h" | ||||
| 
 | ||||
| class fftfilt; | ||||
| 
 | ||||
| class AMDemodSink : public ChannelSampleSink { | ||||
| public: | ||||
|     AMDemodSink(); | ||||
| 	~AMDemodSink(); | ||||
| 
 | ||||
| 	virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); | ||||
| 
 | ||||
| 	void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false); | ||||
|     void applySettings(const AMDemodSettings& settings, bool force = false); | ||||
|     void applyAudioSampleRate(int sampleRate); | ||||
| 
 | ||||
|     uint32_t getAudioSampleRate() const { return m_audioSampleRate; } | ||||
| 	double getMagSq() const { return m_magsq; } | ||||
| 	bool getSquelchOpen() const { return m_squelchOpen; } | ||||
| 	bool getPllLocked() const { return m_settings.m_pll && m_pll.locked(); } | ||||
| 	Real getPllFrequency() const { return m_pll.getFreq(); } | ||||
|     AudioFifo *getAudioFifo() { return &m_audioFifo; } | ||||
| 
 | ||||
|     void getMagSqLevels(double& avg, double& peak, int& nbSamples) | ||||
|     { | ||||
|         if (m_magsqCount > 0) | ||||
|         { | ||||
|             m_magsq = m_magsqSum / m_magsqCount; | ||||
|             m_magSqLevelStore.m_magsq = m_magsq; | ||||
|             m_magSqLevelStore.m_magsqPeak = m_magsqPeak; | ||||
|         } | ||||
| 
 | ||||
|         avg = m_magSqLevelStore.m_magsq; | ||||
|         peak = m_magSqLevelStore.m_magsqPeak; | ||||
|         nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; | ||||
| 
 | ||||
|         m_magsqSum = 0.0f; | ||||
|         m_magsqPeak = 0.0f; | ||||
|         m_magsqCount = 0; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     struct MagSqLevelsStore | ||||
|     { | ||||
|         MagSqLevelsStore() : | ||||
|             m_magsq(1e-12), | ||||
|             m_magsqPeak(1e-12) | ||||
|         {} | ||||
|         double m_magsq; | ||||
|         double m_magsqPeak; | ||||
|     }; | ||||
| 
 | ||||
| 	enum RateState { | ||||
| 		RSInitialFill, | ||||
| 		RSRunning | ||||
| 	}; | ||||
| 
 | ||||
|     int m_channelSampleRate; | ||||
|     int m_channelFrequencyOffset; | ||||
|     AMDemodSettings m_settings; | ||||
|     uint32_t m_audioSampleRate; | ||||
| 
 | ||||
| 	NCO m_nco; | ||||
| 	Interpolator m_interpolator; | ||||
| 	Real m_interpolatorDistance; | ||||
| 	Real m_interpolatorDistanceRemain; | ||||
| 
 | ||||
| 	Real m_squelchLevel; | ||||
| 	uint32_t m_squelchCount; | ||||
| 	bool m_squelchOpen; | ||||
| 	DoubleBufferFIFO<Real> m_squelchDelayLine; | ||||
| 	double m_magsq; | ||||
| 	double m_magsqSum; | ||||
| 	double m_magsqPeak; | ||||
| 	int  m_magsqCount; | ||||
| 	MagSqLevelsStore m_magSqLevelStore; | ||||
| 
 | ||||
| 	MovingAverageUtil<Real, double, 16> m_movingAverage; | ||||
| 	SimpleAGC<4800> m_volumeAGC; | ||||
|     Bandpass<Real> m_bandpass; | ||||
|     Lowpass<Real> m_lowpass; | ||||
|     Lowpass<std::complex<float> > m_pllFilt; | ||||
|     PhaseLockComplex m_pll; | ||||
|     fftfilt* DSBFilter; | ||||
|     fftfilt* SSBFilter; | ||||
|     Real m_syncAMBuff[2*1024]; | ||||
|     uint32_t m_syncAMBuffIndex; | ||||
|     MagAGC m_syncAMAGC; | ||||
| 
 | ||||
| 	AudioVector m_audioBuffer; | ||||
| 	AudioFifo m_audioFifo; | ||||
| 	uint32_t m_audioBufferFill; | ||||
| 
 | ||||
|     void processOneSample(Complex &ci); | ||||
| }; | ||||
| 
 | ||||
| #endif // INCLUDE_AMDEMODSINK_H
 | ||||
| @ -5,6 +5,8 @@ set(dsddemod_SOURCES | ||||
|     dsddemodplugin.cpp | ||||
|     dsddemodbaudrates.cpp | ||||
|     dsddemodsettings.cpp | ||||
|     dsddemodsink.cpp | ||||
|     dsddemodbaseband.cpp | ||||
|     dsddemodwebapiadapter.cpp | ||||
|     dsddecoder.cpp | ||||
| ) | ||||
| @ -14,6 +16,8 @@ set(dsddemod_HEADERS | ||||
|     dsddemodplugin.h | ||||
|     dsddemodbaudrates.h | ||||
|     dsddemodsettings.h | ||||
|     dsddemodsink.h | ||||
|     dsddemodbaseband.h | ||||
|     dsddemodwebapiadapter.h | ||||
|     dsddecoder.h | ||||
| ) | ||||
| @ -29,7 +33,6 @@ if(NOT SERVER_MODE) | ||||
|         ${dsddemod_SOURCES} | ||||
|         dsddemodgui.cpp | ||||
|         dsdstatustextdialog.cpp | ||||
| 
 | ||||
|         dsddemodgui.ui | ||||
|         dsdstatustextdialog.ui | ||||
|     ) | ||||
| @ -59,12 +62,12 @@ if(ENABLE_EXTERNAL_LIBRARIES) | ||||
| endif() | ||||
| 
 | ||||
| target_link_libraries(${TARGET_NAME} | ||||
|         Qt5::Core | ||||
|         ${TARGET_LIB} | ||||
|     Qt5::Core | ||||
|     ${TARGET_LIB} | ||||
| 	sdrbase | ||||
| 	${TARGET_LIB_GUI} | ||||
|         ${LIBDSDCC_LIBRARIES} | ||||
|         ${LIBMBE_LIBRARY} | ||||
|     ${LIBDSDCC_LIBRARIES} | ||||
|     ${LIBMBE_LIBRARY} | ||||
| ) | ||||
| 
 | ||||
| install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) | ||||
|  | ||||
| @ -26,6 +26,7 @@ | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| #include <QThread> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| #include "SWGDSDDemodSettings.h" | ||||
| @ -33,20 +34,14 @@ | ||||
| #include "SWGDSDDemodReport.h" | ||||
| #include "SWGRDSReport.h" | ||||
| 
 | ||||
| #include "audio/audiooutput.h" | ||||
| #include "dsp/dspengine.h" | ||||
| #include "dsp/threadedbasebandsamplesink.h" | ||||
| #include "dsp/downchannelizer.h" | ||||
| #include "dsp/dspcommands.h" | ||||
| #include "dsp/devicesamplemimo.h" | ||||
| #include "device/deviceapi.h" | ||||
| #include "util/db.h" | ||||
| 
 | ||||
| #include "dsddemod.h" | ||||
| 
 | ||||
| MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureChannelizer, Message) | ||||
| MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureDSDDemod, Message) | ||||
| MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureMyPosition, Message) | ||||
| 
 | ||||
| const QString DSDDemod::m_channelIdURI = "sdrangel.channel.dsddemod"; | ||||
| const QString DSDDemod::m_channelId = "DSDDemod"; | ||||
| @ -55,50 +50,17 @@ const int DSDDemod::m_udpBlockSize = 512; | ||||
| DSDDemod::DSDDemod(DeviceAPI *deviceAPI) : | ||||
|         ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), | ||||
|         m_deviceAPI(deviceAPI), | ||||
|         m_inputSampleRate(48000), | ||||
|         m_inputFrequencyOffset(0), | ||||
|         m_interpolatorDistance(0.0f), | ||||
|         m_interpolatorDistanceRemain(0.0f), | ||||
|         m_sampleCount(0), | ||||
|         m_squelchCount(0), | ||||
|         m_squelchGate(0), | ||||
|         m_squelchLevel(1e-4), | ||||
|         m_squelchOpen(false), | ||||
|         m_squelchDelayLine(24000), | ||||
|         m_audioFifo1(48000), | ||||
|         m_audioFifo2(48000), | ||||
|         m_scopeXY(0), | ||||
|         m_scopeEnabled(true), | ||||
|         m_dsdDecoder(), | ||||
|         m_signalFormat(signalFormatNone), | ||||
|         m_settingsMutex(QMutex::Recursive) | ||||
|         m_basebandSampleRate(0) | ||||
| { | ||||
|     qDebug("DSDDemod::DSDDemod"); | ||||
| 	setObjectName(m_channelId); | ||||
| 
 | ||||
| 	m_audioBuffer.resize(1<<14); | ||||
| 	m_audioBufferFill = 0; | ||||
|     m_thread = new QThread(this); | ||||
|     m_basebandSink = new DSDDemodBaseband(); | ||||
|     m_basebandSink->moveToThread(m_thread); | ||||
| 
 | ||||
| 	m_sampleBuffer = new FixReal[1<<17]; // 128 kS
 | ||||
| 	m_sampleBufferIndex = 0; | ||||
| 	m_scaleFromShort = SDR_RX_SAMP_SZ < sizeof(short)*8 ? 1 : 1<<(SDR_RX_SAMP_SZ - sizeof(short)*8); | ||||
| 
 | ||||
| 	m_magsq = 0.0f; | ||||
|     m_magsqSum = 0.0f; | ||||
|     m_magsqPeak = 0.0f; | ||||
|     m_magsqCount = 0; | ||||
| 
 | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo1, getInputMessageQueue()); | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo2, getInputMessageQueue()); | ||||
|     m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); | ||||
| 
 | ||||
|     applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); | ||||
|     applySettings(m_settings, true); | ||||
| 
 | ||||
|     m_channelizer = new DownChannelizer(this); | ||||
|     m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); | ||||
|     m_deviceAPI->addChannelSink(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelSinkAPI(this); | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| @ -107,20 +69,10 @@ DSDDemod::~DSDDemod() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
|     delete[] m_sampleBuffer; | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo1); | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo2); | ||||
| 
 | ||||
|     m_deviceAPI->removeChannelSinkAPI(this); | ||||
|     m_deviceAPI->removeChannelSink(m_threadedChannelizer); | ||||
|     delete m_threadedChannelizer; | ||||
|     delete m_channelizer; | ||||
| } | ||||
| 
 | ||||
| void DSDDemod::configureMyPosition(MessageQueue* messageQueue, float myLatitude, float myLongitude) | ||||
| { | ||||
| 	Message* cmd = MsgConfigureMyPosition::create(myLatitude, myLongitude); | ||||
| 	messageQueue->push(cmd); | ||||
| 	m_deviceAPI->removeChannelSinkAPI(this); | ||||
|     m_deviceAPI->removeChannelSink(this); | ||||
|     delete m_basebandSink; | ||||
|     delete m_thread; | ||||
| } | ||||
| 
 | ||||
| uint32_t DSDDemod::getNumberOfDeviceStreams() const | ||||
| @ -131,269 +83,33 @@ uint32_t DSDDemod::getNumberOfDeviceStreams() const | ||||
| void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) | ||||
| { | ||||
|     (void) firstOfBurst; | ||||
| 	Complex ci; | ||||
| 	int samplesPerSymbol = m_dsdDecoder.getSamplesPerSymbol(); | ||||
| 
 | ||||
| 	m_settingsMutex.lock(); | ||||
| 	m_scopeSampleBuffer.clear(); | ||||
| 
 | ||||
| 	m_dsdDecoder.enableMbelib(!DSPEngine::instance()->hasDVSerialSupport()); // disable mbelib if DV serial support is present and activated else enable it
 | ||||
| 
 | ||||
| 	for (SampleVector::const_iterator it = begin; it != end; ++it) | ||||
| 	{ | ||||
| 		Complex c(it->real(), it->imag()); | ||||
| 		c *= m_nco.nextIQ(); | ||||
| 
 | ||||
|         if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) | ||||
|         { | ||||
|             FixReal sample, delayedSample; | ||||
|             qint16 sampleDSD; | ||||
| 
 | ||||
|             Real re = ci.real() / SDR_RX_SCALED; | ||||
|             Real im = ci.imag() / SDR_RX_SCALED; | ||||
|             Real magsq = re*re + im*im; | ||||
|             m_movingAverage(magsq); | ||||
| 
 | ||||
|             m_magsqSum += magsq; | ||||
| 
 | ||||
|             if (magsq > m_magsqPeak) | ||||
|             { | ||||
|                 m_magsqPeak = magsq; | ||||
|             } | ||||
| 
 | ||||
|             m_magsqCount++; | ||||
| 
 | ||||
|             Real demod = m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_demodGain; // [-1.0:1.0]
 | ||||
|             m_sampleCount++; | ||||
| 
 | ||||
|             // AF processing
 | ||||
| 
 | ||||
|             if (m_movingAverage.asDouble() > m_squelchLevel) | ||||
|             { | ||||
|                 if (m_squelchGate > 0) | ||||
|                 { | ||||
| 
 | ||||
|                     if (m_squelchCount < m_squelchGate*2) { | ||||
|                         m_squelchCount++; | ||||
|                     } | ||||
| 
 | ||||
|                     m_squelchDelayLine.write(demod); | ||||
|                     m_squelchOpen = m_squelchCount > m_squelchGate; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     m_squelchOpen = true; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (m_squelchGate > 0) | ||||
|                 { | ||||
|                     if (m_squelchCount > 0) { | ||||
|                         m_squelchCount--; | ||||
|                     } | ||||
| 
 | ||||
|                     m_squelchDelayLine.write(0); | ||||
|                     m_squelchOpen = m_squelchCount > m_squelchGate; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     m_squelchOpen = false; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (m_squelchOpen) | ||||
|             { | ||||
|                 if (m_squelchGate > 0) | ||||
|                 { | ||||
|                     sampleDSD = m_squelchDelayLine.readBack(m_squelchGate) * 32768.0f;   // DSD decoder takes int16 samples
 | ||||
|                     sample = m_squelchDelayLine.readBack(m_squelchGate) * SDR_RX_SCALEF; // scale to sample size
 | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     sampleDSD = demod * 32768.0f;   // DSD decoder takes int16 samples
 | ||||
|                     sample = demod * SDR_RX_SCALEF; // scale to sample size
 | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 sampleDSD = 0; | ||||
|                 sample = 0; | ||||
|             } | ||||
| 
 | ||||
|             m_dsdDecoder.pushSample(sampleDSD); | ||||
| 
 | ||||
|             if (m_settings.m_enableCosineFiltering) { // show actual input to FSK demod
 | ||||
|             	sample = m_dsdDecoder.getFilteredSample() * m_scaleFromShort; | ||||
|             } | ||||
| 
 | ||||
|             if (m_sampleBufferIndex < (1<<17)-1) { | ||||
|                 m_sampleBufferIndex++; | ||||
|             } else { | ||||
|                 m_sampleBufferIndex = 0; | ||||
|             } | ||||
| 
 | ||||
|             m_sampleBuffer[m_sampleBufferIndex] = sample; | ||||
| 
 | ||||
|             if (m_sampleBufferIndex < samplesPerSymbol) { | ||||
|                 delayedSample = m_sampleBuffer[(1<<17) - samplesPerSymbol + m_sampleBufferIndex]; // wrap
 | ||||
|             } else { | ||||
|                 delayedSample = m_sampleBuffer[m_sampleBufferIndex - samplesPerSymbol]; | ||||
|             } | ||||
| 
 | ||||
|             if (m_settings.m_syncOrConstellation) | ||||
|             { | ||||
|                 Sample s(sample, m_dsdDecoder.getSymbolSyncSample() * m_scaleFromShort * 0.84); | ||||
|                 m_scopeSampleBuffer.push_back(s); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 Sample s(sample, delayedSample); // I=signal, Q=signal delayed by 20 samples (2400 baud: lowest rate)
 | ||||
|                 m_scopeSampleBuffer.push_back(s); | ||||
|             } | ||||
| 
 | ||||
|             if (DSPEngine::instance()->hasDVSerialSupport()) | ||||
|             { | ||||
|                 if ((m_settings.m_slot1On) && m_dsdDecoder.mbeDVReady1()) | ||||
|                 { | ||||
|                     if (!m_settings.m_audioMute) | ||||
|                     { | ||||
|                         DSPEngine::instance()->pushMbeFrame( | ||||
|                                 m_dsdDecoder.getMbeDVFrame1(), | ||||
|                                 m_dsdDecoder.getMbeRateIndex(), | ||||
|                                 m_settings.m_volume * 10.0, | ||||
|                                 m_settings.m_tdmaStereo ? 1 : 3, // left or both channels
 | ||||
|                                 m_settings.m_highPassFilter, | ||||
|                                 m_audioSampleRate/8000, // upsample from native 8k
 | ||||
|                                 &m_audioFifo1); | ||||
|                     } | ||||
| 
 | ||||
|                     m_dsdDecoder.resetMbeDV1(); | ||||
|                 } | ||||
| 
 | ||||
|                 if ((m_settings.m_slot2On) && m_dsdDecoder.mbeDVReady2()) | ||||
|                 { | ||||
|                     if (!m_settings.m_audioMute) | ||||
|                     { | ||||
|                         DSPEngine::instance()->pushMbeFrame( | ||||
|                                 m_dsdDecoder.getMbeDVFrame2(), | ||||
|                                 m_dsdDecoder.getMbeRateIndex(), | ||||
|                                 m_settings.m_volume * 10.0, | ||||
|                                 m_settings.m_tdmaStereo ? 2 : 3, // right or both channels
 | ||||
|                                 m_settings.m_highPassFilter, | ||||
|                                 m_audioSampleRate/8000, // upsample from native 8k
 | ||||
|                                 &m_audioFifo2); | ||||
|                     } | ||||
| 
 | ||||
|                     m_dsdDecoder.resetMbeDV2(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
| //            if (DSPEngine::instance()->hasDVSerialSupport() && m_dsdDecoder.mbeDVReady1())
 | ||||
| //            {
 | ||||
| //                if (!m_settings.m_audioMute)
 | ||||
| //                {
 | ||||
| //                    DSPEngine::instance()->pushMbeFrame(m_dsdDecoder.getMbeDVFrame1(), m_dsdDecoder.getMbeRateIndex(), m_settings.m_volume, &m_audioFifo1);
 | ||||
| //                }
 | ||||
| //
 | ||||
| //                m_dsdDecoder.resetMbeDV1();
 | ||||
| //            }
 | ||||
| 
 | ||||
|             m_interpolatorDistanceRemain += m_interpolatorDistance; | ||||
|         } | ||||
| 	} | ||||
| 
 | ||||
| 	if (!DSPEngine::instance()->hasDVSerialSupport()) | ||||
| 	{ | ||||
| 	    if (m_settings.m_slot1On) | ||||
| 	    { | ||||
| 	        int nbAudioSamples; | ||||
| 	        short *dsdAudio = m_dsdDecoder.getAudio1(nbAudioSamples); | ||||
| 
 | ||||
| 	        if (nbAudioSamples > 0) | ||||
| 	        { | ||||
| 	            if (!m_settings.m_audioMute) { | ||||
| 	                m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples); | ||||
| 	            } | ||||
| 
 | ||||
| 	            m_dsdDecoder.resetAudio1(); | ||||
| 	        } | ||||
| 	    } | ||||
| 
 | ||||
|         if (m_settings.m_slot2On) | ||||
|         { | ||||
|             int nbAudioSamples; | ||||
|             short *dsdAudio = m_dsdDecoder.getAudio2(nbAudioSamples); | ||||
| 
 | ||||
|             if (nbAudioSamples > 0) | ||||
|             { | ||||
|                 if (!m_settings.m_audioMute) { | ||||
|                     m_audioFifo2.write((const quint8*) dsdAudio, nbAudioSamples); | ||||
|                 } | ||||
| 
 | ||||
|                 m_dsdDecoder.resetAudio2(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| //	    int nbAudioSamples;
 | ||||
| //	    short *dsdAudio = m_dsdDecoder.getAudio1(nbAudioSamples);
 | ||||
| //
 | ||||
| //	    if (nbAudioSamples > 0)
 | ||||
| //	    {
 | ||||
| //	        if (!m_settings.m_audioMute) {
 | ||||
| //	            uint res = m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples, 10);
 | ||||
| //	        }
 | ||||
| //
 | ||||
| //	        m_dsdDecoder.resetAudio1();
 | ||||
| //	    }
 | ||||
| 	} | ||||
| 
 | ||||
|     if ((m_scopeXY != 0) && (m_scopeEnabled)) | ||||
|     { | ||||
|         m_scopeXY->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth
 | ||||
|     } | ||||
| 
 | ||||
| 	m_settingsMutex.unlock(); | ||||
|     m_basebandSink->feed(begin, end); | ||||
| } | ||||
| 
 | ||||
| void DSDDemod::start() | ||||
| { | ||||
| 	m_audioFifo1.clear(); | ||||
|     m_audioFifo2.clear(); | ||||
| 	m_phaseDiscri.reset(); | ||||
| 	applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); | ||||
|     qDebug() << "DSDDemod::start"; | ||||
| 
 | ||||
|     if (m_basebandSampleRate != 0) { | ||||
|         m_basebandSink->setBasebandSampleRate(m_basebandSampleRate); | ||||
|     } | ||||
| 
 | ||||
|     m_basebandSink->reset(); | ||||
|     m_thread->start(); | ||||
| } | ||||
| 
 | ||||
| void DSDDemod::stop() | ||||
| { | ||||
|     qDebug() << "DSDDemod::stop"; | ||||
| 	m_thread->exit(); | ||||
| 	m_thread->wait(); | ||||
| } | ||||
| 
 | ||||
| bool DSDDemod::handleMessage(const Message& cmd) | ||||
| { | ||||
| 	qDebug() << "DSDDemod::handleMessage"; | ||||
| 
 | ||||
| 	if (DownChannelizer::MsgChannelizerNotification::match(cmd)) | ||||
| 	{ | ||||
| 		DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; | ||||
| 		qDebug() << "DSDDemod::handleMessage: MsgChannelizerNotification: inputSampleRate: " << notif.getSampleRate() | ||||
| 				<< " inputFrequencyOffset: " << notif.getFrequencyOffset(); | ||||
| 
 | ||||
| 		applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); | ||||
| 
 | ||||
| 		return true; | ||||
| 	} | ||||
|     else if (MsgConfigureChannelizer::match(cmd)) | ||||
|     { | ||||
|         MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; | ||||
|         qDebug("DSDDemod::handleMessage: MsgConfigureChannelizer"); | ||||
| 
 | ||||
|         m_channelizer->configure(m_channelizer->getInputMessageQueue(), | ||||
|             cfg.getSampleRate(), | ||||
|             cfg.getCenterFrequency()); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     else if (MsgConfigureDSDDemod::match(cmd)) | ||||
|     if (MsgConfigureDSDDemod::match(cmd)) | ||||
|     { | ||||
|         MsgConfigureDSDDemod& cfg = (MsgConfigureDSDDemod&) cmd; | ||||
|         qDebug("DSDDemod::handleMessage: MsgConfigureDSDDemod: m_rfBandwidth"); | ||||
| @ -402,33 +118,16 @@ bool DSDDemod::handleMessage(const Message& cmd) | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 	else if (MsgConfigureMyPosition::match(cmd)) | ||||
| 	{ | ||||
| 		MsgConfigureMyPosition& cfg = (MsgConfigureMyPosition&) cmd; | ||||
| 		m_dsdDecoder.setMyPoint(cfg.getMyLatitude(), cfg.getMyLongitude()); | ||||
| 		return true; | ||||
| 	} | ||||
|     else if (DSPConfigureAudio::match(cmd)) | ||||
|     { | ||||
|         DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; | ||||
|         uint32_t sampleRate = cfg.getSampleRate(); | ||||
| 
 | ||||
|         qDebug() << "DSDDemod::handleMessage: DSPConfigureAudio:" | ||||
|                 << " sampleRate: " << sampleRate; | ||||
| 
 | ||||
|         if (sampleRate != m_audioSampleRate) { | ||||
|             applyAudioSampleRate(sampleRate); | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) | ||||
|     { | ||||
|         return true; | ||||
|     } | ||||
|     else if (DSPSignalNotification::match(cmd)) | ||||
|     { | ||||
|         return true; | ||||
|         DSPSignalNotification& notif = (DSPSignalNotification&) cmd; | ||||
|         m_basebandSampleRate = notif.getSampleRate(); | ||||
|         // Forward to the sink
 | ||||
|         DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
 | ||||
|         qDebug() << "DSDDemod::handleMessage: DSPSignalNotification"; | ||||
|         m_basebandSink->getInputMessageQueue()->push(rep); | ||||
| 
 | ||||
| 	    return true; | ||||
|     } | ||||
| 	else | ||||
| 	{ | ||||
| @ -436,45 +135,6 @@ bool DSDDemod::handleMessage(const Message& cmd) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void DSDDemod::applyAudioSampleRate(int sampleRate) | ||||
| { | ||||
|     int upsampling = sampleRate / 8000; | ||||
| 
 | ||||
|     qDebug("DSDDemod::applyAudioSampleRate: audio rate: %d upsample by %d", sampleRate, upsampling); | ||||
| 
 | ||||
|     if (sampleRate % 8000 != 0) { | ||||
|         qDebug("DSDDemod::applyAudioSampleRate: audio will sound best with sample rates that are integer multiples of 8 kS/s"); | ||||
|     } | ||||
| 
 | ||||
|     m_dsdDecoder.setUpsampling(upsampling); | ||||
|     m_audioSampleRate = sampleRate; | ||||
| } | ||||
| 
 | ||||
| void DSDDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) | ||||
| { | ||||
|     qDebug() << "DSDDemod::applyChannelSettings:" | ||||
|             << " inputSampleRate: " << inputSampleRate | ||||
|             << " inputFrequencyOffset: " << inputFrequencyOffset; | ||||
| 
 | ||||
|     if ((inputFrequencyOffset != m_inputFrequencyOffset) || | ||||
|         (inputSampleRate != m_inputSampleRate) || force) | ||||
|     { | ||||
|         m_nco.setFreq(-inputFrequencyOffset, inputSampleRate); | ||||
|     } | ||||
| 
 | ||||
|     if ((inputSampleRate != m_inputSampleRate) || force) | ||||
|     { | ||||
|         m_settingsMutex.lock(); | ||||
|         m_interpolator.create(16, inputSampleRate, (m_settings.m_rfBandwidth) / 2.2); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance =  (Real) inputSampleRate / (Real) 48000; | ||||
|         m_settingsMutex.unlock(); | ||||
|     } | ||||
| 
 | ||||
|     m_inputSampleRate = inputSampleRate; | ||||
|     m_inputFrequencyOffset = inputFrequencyOffset; | ||||
| } | ||||
| 
 | ||||
| void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) | ||||
| { | ||||
|     qDebug() << "DSDDemod::applySettings: " | ||||
| @ -527,87 +187,38 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) | ||||
|     if ((settings.m_traceLengthMutliplier != m_settings.m_traceLengthMutliplier) || force) { | ||||
|         reverseAPIKeys.append("traceLengthMutliplier"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | ||||
|     { | ||||
|     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { | ||||
|         reverseAPIKeys.append("rfBandwidth"); | ||||
|         m_settingsMutex.lock(); | ||||
|         m_interpolator.create(16, m_inputSampleRate, (settings.m_rfBandwidth) / 2.2); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance =  (Real) m_inputSampleRate / (Real) 48000; | ||||
|         //m_phaseDiscri.setFMScaling((float) settings.m_rfBandwidth / (float) settings.m_fmDeviation);
 | ||||
|         m_settingsMutex.unlock(); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) | ||||
|     { | ||||
|     if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) { | ||||
|         reverseAPIKeys.append("fmDeviation"); | ||||
|         m_phaseDiscri.setFMScaling(48000.0f / (2.0f*settings.m_fmDeviation)); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) | ||||
|     { | ||||
|     if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) { | ||||
|         reverseAPIKeys.append("squelchGate"); | ||||
|         m_squelchGate = 480 * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
 | ||||
|         m_squelchCount = 0; // reset squelch open counter
 | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelch != m_settings.m_squelch) || force) | ||||
|     { | ||||
|     if ((settings.m_squelch != m_settings.m_squelch) || force) { | ||||
|         reverseAPIKeys.append("squelch"); | ||||
|         // input is a value in dB
 | ||||
|         m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_volume != m_settings.m_volume) || force) | ||||
|     { | ||||
|     if ((settings.m_volume != m_settings.m_volume) || force) { | ||||
|         reverseAPIKeys.append("volume"); | ||||
|         m_dsdDecoder.setAudioGain(settings.m_volume); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_baudRate != m_settings.m_baudRate) || force) | ||||
|     { | ||||
|     if ((settings.m_baudRate != m_settings.m_baudRate) || force) { | ||||
|         reverseAPIKeys.append("baudRate"); | ||||
|         m_dsdDecoder.setBaudRate(settings.m_baudRate); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_enableCosineFiltering != m_settings.m_enableCosineFiltering) || force) | ||||
|     { | ||||
|     if ((settings.m_enableCosineFiltering != m_settings.m_enableCosineFiltering) || force) { | ||||
|         reverseAPIKeys.append("enableCosineFiltering"); | ||||
|         m_dsdDecoder.enableCosineFiltering(settings.m_enableCosineFiltering); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_tdmaStereo != m_settings.m_tdmaStereo) || force) | ||||
|     { | ||||
|     if ((settings.m_tdmaStereo != m_settings.m_tdmaStereo) || force) { | ||||
|         reverseAPIKeys.append("tdmaStereo"); | ||||
|         m_dsdDecoder.setTDMAStereo(settings.m_tdmaStereo); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_pllLock != m_settings.m_pllLock) || force) | ||||
|     { | ||||
|     if ((settings.m_pllLock != m_settings.m_pllLock) || force) { | ||||
|         reverseAPIKeys.append("pllLock"); | ||||
|         m_dsdDecoder.setSymbolPLLLock(settings.m_pllLock); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_highPassFilter != m_settings.m_highPassFilter) || force) | ||||
|     { | ||||
|     if ((settings.m_highPassFilter != m_settings.m_highPassFilter) || force) { | ||||
|         reverseAPIKeys.append("highPassFilter"); | ||||
|         m_dsdDecoder.useHPMbelib(settings.m_highPassFilter); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) | ||||
|     { | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { | ||||
|         reverseAPIKeys.append("audioDeviceName"); | ||||
|         AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); | ||||
|         int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); | ||||
|         //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex);
 | ||||
|         audioDeviceManager->addAudioSink(&m_audioFifo1, getInputMessageQueue(), audioDeviceIndex); | ||||
|         audioDeviceManager->addAudioSink(&m_audioFifo2, getInputMessageQueue(), audioDeviceIndex); | ||||
|         uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); | ||||
| 
 | ||||
|         if (m_audioSampleRate != audioSampleRate) { | ||||
|             applyAudioSampleRate(audioSampleRate); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (m_settings.m_streamIndex != settings.m_streamIndex) | ||||
| @ -615,16 +226,17 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) | ||||
|         if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
 | ||||
|         { | ||||
|             m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex); | ||||
|             m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex); | ||||
|             m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex); | ||||
|             m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex); | ||||
|             m_deviceAPI->addChannelSink(this, settings.m_streamIndex); | ||||
|             m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex); | ||||
|             // apply stream sample rate to itself
 | ||||
|             applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset); | ||||
|         } | ||||
| 
 | ||||
|         reverseAPIKeys.append("streamIndex"); | ||||
|     } | ||||
| 
 | ||||
|     DSDDemodBaseband::MsgConfigureDSDDemodBaseband *msg = DSDDemodBaseband::MsgConfigureDSDDemodBaseband::create(settings, force); | ||||
|     m_basebandSink->getInputMessageQueue()->push(msg); | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
| @ -660,195 +272,6 @@ bool DSDDemod::deserialize(const QByteArray& data) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const char *DSDDemod::updateAndGetStatusText() | ||||
| { | ||||
|     formatStatusText(); | ||||
|     return m_formatStatusText; | ||||
| } | ||||
| 
 | ||||
| void DSDDemod::formatStatusText() | ||||
| { | ||||
|     switch (getDecoder().getSyncType()) | ||||
|     { | ||||
|     case DSDcc::DSDDecoder::DSDSyncDMRDataMS: | ||||
|     case DSDcc::DSDDecoder::DSDSyncDMRDataP: | ||||
|     case DSDcc::DSDDecoder::DSDSyncDMRVoiceMS: | ||||
|     case DSDcc::DSDDecoder::DSDSyncDMRVoiceP: | ||||
|         if (m_signalFormat != signalFormatDMR) | ||||
|         { | ||||
|             strcpy(m_formatStatusText, "Sta: __ S1: __________________________ S2: __________________________"); | ||||
|         } | ||||
| 
 | ||||
|         switch (getDecoder().getStationType()) | ||||
|         { | ||||
|         case DSDcc::DSDDecoder::DSDBaseStation: | ||||
|             memcpy(&m_formatStatusText[5], "BS ", 3); | ||||
|             break; | ||||
|         case DSDcc::DSDDecoder::DSDMobileStation: | ||||
|             memcpy(&m_formatStatusText[5], "MS ", 3); | ||||
|             break; | ||||
|         default: | ||||
|             memcpy(&m_formatStatusText[5], "NA ", 3); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         memcpy(&m_formatStatusText[12], getDecoder().getDMRDecoder().getSlot0Text(), 26); | ||||
|         memcpy(&m_formatStatusText[43], getDecoder().getDMRDecoder().getSlot1Text(), 26); | ||||
|         m_signalFormat = signalFormatDMR; | ||||
|         break; | ||||
|     case DSDcc::DSDDecoder::DSDSyncDStarHeaderN: | ||||
|     case DSDcc::DSDDecoder::DSDSyncDStarHeaderP: | ||||
|     case DSDcc::DSDDecoder::DSDSyncDStarN: | ||||
|     case DSDcc::DSDDecoder::DSDSyncDStarP: | ||||
|         if (m_signalFormat != signalFormatDStar) | ||||
|         { | ||||
|                                      //           1    1    2    2    3    3    4    4    5    5    6    6    7    7    8
 | ||||
|                                      // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
 | ||||
|             strcpy(m_formatStatusText, "________/____>________|________>________|____________________|______:___/_____._"); | ||||
|                                      // MY            UR       RPT1     RPT2     Info                 Loc    Target
 | ||||
|         } | ||||
| 
 | ||||
|         { | ||||
|             const std::string& rpt1 = getDecoder().getDStarDecoder().getRpt1(); | ||||
|             const std::string& rpt2 = getDecoder().getDStarDecoder().getRpt2(); | ||||
|             const std::string& mySign = getDecoder().getDStarDecoder().getMySign(); | ||||
|             const std::string& yrSign = getDecoder().getDStarDecoder().getYourSign(); | ||||
| 
 | ||||
|             if (rpt1.length() > 0) { // 0 or 8
 | ||||
|                 memcpy(&m_formatStatusText[23], rpt1.c_str(), 8); | ||||
|             } | ||||
|             if (rpt2.length() > 0) { // 0 or 8
 | ||||
|                 memcpy(&m_formatStatusText[32], rpt2.c_str(), 8); | ||||
|             } | ||||
|             if (yrSign.length() > 0) { // 0 or 8
 | ||||
|                 memcpy(&m_formatStatusText[14], yrSign.c_str(), 8); | ||||
|             } | ||||
|             if (mySign.length() > 0) { // 0 or 13
 | ||||
|                 memcpy(&m_formatStatusText[0], mySign.c_str(), 13); | ||||
|             } | ||||
|             memcpy(&m_formatStatusText[41], getDecoder().getDStarDecoder().getInfoText(), 20); | ||||
|             memcpy(&m_formatStatusText[62], getDecoder().getDStarDecoder().getLocator(), 6); | ||||
|             snprintf(&m_formatStatusText[69], 82-69, "%03d/%07.1f", | ||||
|                     getDecoder().getDStarDecoder().getBearing(), | ||||
|                     getDecoder().getDStarDecoder().getDistance()); | ||||
|         } | ||||
| 
 | ||||
|         m_formatStatusText[82] = '\0'; | ||||
|         m_signalFormat = signalFormatDStar; | ||||
|         break; | ||||
|     case DSDcc::DSDDecoder::DSDSyncDPMR: | ||||
|         snprintf(m_formatStatusText, 82, "%s CC: %04d OI: %08d CI: %08d", | ||||
|                 DSDcc::DSDdPMR::dpmrFrameTypes[(int) getDecoder().getDPMRDecoder().getFrameType()], | ||||
|                 getDecoder().getDPMRDecoder().getColorCode(), | ||||
|                 getDecoder().getDPMRDecoder().getOwnId(), | ||||
|                 getDecoder().getDPMRDecoder().getCalledId()); | ||||
|         m_signalFormat = signalFormatDPMR; | ||||
|         break; | ||||
|     case DSDcc::DSDDecoder::DSDSyncNXDNP: | ||||
|     case DSDcc::DSDDecoder::DSDSyncNXDNN: | ||||
|         if (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRCCH) | ||||
|         { | ||||
|             //           1    1    2    2    3    3    4    4    5    5    6    6    7    7    8
 | ||||
|             // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
 | ||||
|             // RC r cc mm llllll ssss
 | ||||
|             snprintf(m_formatStatusText, 82, "RC %s %02d %02X %06X %02X", | ||||
|                     getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H", | ||||
|                     getDecoder().getNXDNDecoder().getRAN(), | ||||
|                     getDecoder().getNXDNDecoder().getMessageType(), | ||||
|                     getDecoder().getNXDNDecoder().getLocationId(), | ||||
|                     getDecoder().getNXDNDecoder().getServicesFlag()); | ||||
|         } | ||||
|         else if ((getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRTCH) | ||||
|             || (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRDCH)) | ||||
|         { | ||||
|             if (getDecoder().getNXDNDecoder().isIdle()) { | ||||
|                 snprintf(m_formatStatusText, 82, "%s IDLE", getDecoder().getNXDNDecoder().getRFChannelStr()); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 //           1    1    2    2    3    3    4    4    5    5    6    6    7    7    8
 | ||||
|                 // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
 | ||||
|                 // Rx r cc mm sssss>gddddd
 | ||||
|                 snprintf(m_formatStatusText, 82, "%s %s %02d %02X %05d>%c%05d", | ||||
|                         getDecoder().getNXDNDecoder().getRFChannelStr(), | ||||
|                         getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H", | ||||
|                         getDecoder().getNXDNDecoder().getRAN(), | ||||
|                         getDecoder().getNXDNDecoder().getMessageType(), | ||||
|                         getDecoder().getNXDNDecoder().getSourceId(), | ||||
|                         getDecoder().getNXDNDecoder().isGroupCall() ? 'G' : 'I', | ||||
|                         getDecoder().getNXDNDecoder().getDestinationId()); | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             //           1    1    2    2    3    3    4    4    5    5    6    6    7    7    8
 | ||||
|             // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
 | ||||
|             // RU
 | ||||
|             snprintf(m_formatStatusText, 82, "RU"); | ||||
|         } | ||||
|         m_signalFormat = signalFormatNXDN; | ||||
|         break; | ||||
|     case DSDcc::DSDDecoder::DSDSyncYSF: | ||||
|         //           1    1    2    2    3    3    4    4    5    5    6    6    7    7    8
 | ||||
|         // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
 | ||||
|         // C V2 RI 0:7 WL000|ssssssssss>dddddddddd |UUUUUUUUUU>DDDDDDDDDD|44444
 | ||||
|         if (getDecoder().getYSFDecoder().getFICHError() == DSDcc::DSDYSF::FICHNoError) | ||||
|         { | ||||
|             snprintf(m_formatStatusText, 82, "%s ", DSDcc::DSDYSF::ysfChannelTypeText[(int) getDecoder().getYSFDecoder().getFICH().getFrameInformation()]); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             snprintf(m_formatStatusText, 82, "%d ", (int) getDecoder().getYSFDecoder().getFICHError()); | ||||
|         } | ||||
| 
 | ||||
|         snprintf(&m_formatStatusText[2], 80, "%s %s %d:%d %c%c", | ||||
|                 DSDcc::DSDYSF::ysfDataTypeText[(int) getDecoder().getYSFDecoder().getFICH().getDataType()], | ||||
|                 DSDcc::DSDYSF::ysfCallModeText[(int) getDecoder().getYSFDecoder().getFICH().getCallMode()], | ||||
|                 getDecoder().getYSFDecoder().getFICH().getBlockTotal(), | ||||
|                 getDecoder().getYSFDecoder().getFICH().getFrameTotal(), | ||||
|                 (getDecoder().getYSFDecoder().getFICH().isNarrowMode() ? 'N' : 'W'), | ||||
|                 (getDecoder().getYSFDecoder().getFICH().isInternetPath() ? 'I' : 'L')); | ||||
| 
 | ||||
|         if (getDecoder().getYSFDecoder().getFICH().isSquelchCodeEnabled()) | ||||
|         { | ||||
|             snprintf(&m_formatStatusText[14], 82-14, "%03d", getDecoder().getYSFDecoder().getFICH().getSquelchCode()); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             strncpy(&m_formatStatusText[14], "---", 82-14); | ||||
|         } | ||||
| 
 | ||||
|         char dest[13]; | ||||
| 
 | ||||
|         if (getDecoder().getYSFDecoder().radioIdMode()) | ||||
|         { | ||||
|             snprintf(dest, 12, "%-5s:%-5s", | ||||
|                     getDecoder().getYSFDecoder().getDestId(), | ||||
|                     getDecoder().getYSFDecoder().getSrcId()); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             snprintf(dest, 11, "%-10s", getDecoder().getYSFDecoder().getDest()); | ||||
|         } | ||||
| 
 | ||||
|         snprintf(&m_formatStatusText[17], 82-17, "|%-10s>%s|%-10s>%-10s|%-5s", | ||||
|                 getDecoder().getYSFDecoder().getSrc(), | ||||
|                 dest, | ||||
|                 getDecoder().getYSFDecoder().getUplink(), | ||||
|                 getDecoder().getYSFDecoder().getDownlink(), | ||||
|                 getDecoder().getYSFDecoder().getRem4()); | ||||
| 
 | ||||
|         m_signalFormat = signalFormatYSF; | ||||
|         break; | ||||
|     default: | ||||
|         m_signalFormat = signalFormatNone; | ||||
|         m_formatStatusText[0] = '\0'; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     m_formatStatusText[82] = '\0'; // guard
 | ||||
| } | ||||
| 
 | ||||
| int DSDDemod::webapiSettingsGet( | ||||
|         SWGSDRangel::SWGChannelSettings& response, | ||||
|         QString& errorMessage) | ||||
| @ -870,13 +293,6 @@ int DSDDemod::webapiSettingsPutPatch( | ||||
|     DSDDemodSettings settings = m_settings; | ||||
|     webapiUpdateChannelSettings(settings, channelSettingsKeys, response); | ||||
| 
 | ||||
|     if (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) | ||||
|     { | ||||
|         MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( | ||||
|                 m_audioSampleRate, settings.m_inputFrequencyOffset); | ||||
|         m_inputMessageQueue.push(channelConfigMsg); | ||||
|     } | ||||
| 
 | ||||
|     MsgConfigureDSDDemod *msg = MsgConfigureDSDDemod::create(settings, force); | ||||
|     m_inputMessageQueue.push(msg); | ||||
| 
 | ||||
| @ -1051,9 +467,9 @@ void DSDDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response | ||||
|     getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); | ||||
| 
 | ||||
|     response.getDsdDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); | ||||
|     response.getDsdDemodReport()->setAudioSampleRate(m_audioSampleRate); | ||||
|     response.getDsdDemodReport()->setChannelSampleRate(m_inputSampleRate); | ||||
|     response.getDsdDemodReport()->setSquelch(m_squelchOpen ? 1 : 0); | ||||
|     response.getDsdDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate()); | ||||
|     response.getDsdDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate()); | ||||
|     response.getDsdDemodReport()->setSquelch(m_basebandSink->getSquelchOpen() ? 1 : 0); | ||||
|     response.getDsdDemodReport()->setPllLocked(getDecoder().getSymbolPLLLocked() ? 1 : 0); | ||||
|     response.getDsdDemodReport()->setSlot1On(getDecoder().getVoice1On() ? 1 : 0); | ||||
|     response.getDsdDemodReport()->setSlot2On(getDecoder().getVoice2On() ? 1 : 0); | ||||
|  | ||||
| @ -21,31 +21,20 @@ | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <QNetworkRequest> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "channel/channelapi.h" | ||||
| #include "dsp/phasediscri.h" | ||||
| #include "dsp/nco.h" | ||||
| #include "dsp/interpolator.h" | ||||
| #include "dsp/lowpass.h" | ||||
| #include "dsp/bandpass.h" | ||||
| #include "dsp/afsquelch.h" | ||||
| #include "util/movingaverage.h" | ||||
| #include "dsp/afsquelch.h" | ||||
| #include "audio/audiofifo.h" | ||||
| #include "util/message.h" | ||||
| #include "util/doublebufferfifo.h" | ||||
| 
 | ||||
| #include "dsddemodsettings.h" | ||||
| #include "dsddecoder.h" | ||||
| #include "dsddemodbaseband.h" | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class DeviceAPI; | ||||
| class ThreadedBasebandSampleSink; | ||||
| class DownChannelizer; | ||||
| class QThread; | ||||
| class DownSampleChannelizer; | ||||
| 
 | ||||
| class DSDDemod : public BasebandSampleSink, public ChannelAPI { | ||||
|     Q_OBJECT | ||||
| @ -73,35 +62,9 @@ public: | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     class MsgConfigureChannelizer : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         int getSampleRate() const { return m_sampleRate; } | ||||
|         int getCenterFrequency() const { return m_centerFrequency; } | ||||
| 
 | ||||
|         static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) | ||||
|         { | ||||
|             return new MsgConfigureChannelizer(sampleRate, centerFrequency); | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
|         int m_sampleRate; | ||||
|         int  m_centerFrequency; | ||||
| 
 | ||||
|         MsgConfigureChannelizer(int sampleRate, int centerFrequency) : | ||||
|             Message(), | ||||
|             m_sampleRate(sampleRate), | ||||
|             m_centerFrequency(centerFrequency) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     DSDDemod(DeviceAPI *deviceAPI); | ||||
| 	~DSDDemod(); | ||||
| 	virtual void destroy() { delete this; } | ||||
| 	void setScopeXYSink(BasebandSampleSink* sampleSink) { m_scopeXY = sampleSink; } | ||||
| 
 | ||||
| 	void configureMyPosition(MessageQueue* messageQueue, float myLatitude, float myLongitude); | ||||
| 
 | ||||
| 	virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); | ||||
| 	virtual void start(); | ||||
| @ -125,31 +88,6 @@ public: | ||||
|         return m_settings.m_inputFrequencyOffset; | ||||
|     } | ||||
| 
 | ||||
| 	double getMagSq() { return m_magsq; } | ||||
| 	bool getSquelchOpen() const { return m_squelchOpen; } | ||||
| 
 | ||||
| 	const DSDDecoder& getDecoder() const { return m_dsdDecoder; } | ||||
| 
 | ||||
|     void getMagSqLevels(double& avg, double& peak, int& nbSamples) | ||||
|     { | ||||
|         if (m_magsqCount > 0) | ||||
|         { | ||||
|             m_magsq = m_magsqSum / m_magsqCount; | ||||
|             m_magSqLevelStore.m_magsq = m_magsq; | ||||
|             m_magSqLevelStore.m_magsqPeak = m_magsqPeak; | ||||
|         } | ||||
| 
 | ||||
|         avg = m_magSqLevelStore.m_magsq; | ||||
|         peak = m_magSqLevelStore.m_magsqPeak; | ||||
|         nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; | ||||
| 
 | ||||
|         m_magsqSum = 0.0f; | ||||
|         m_magsqPeak = 0.0f; | ||||
|         m_magsqCount = 0; | ||||
|     } | ||||
| 
 | ||||
|     const char *updateAndGetStatusText(); | ||||
| 
 | ||||
|     virtual int webapiSettingsGet( | ||||
|             SWGSDRangel::SWGChannelSettings& response, | ||||
|             QString& errorMessage); | ||||
| @ -174,115 +112,30 @@ public: | ||||
|             SWGSDRangel::SWGChannelSettings& response); | ||||
| 
 | ||||
|     uint32_t getNumberOfDeviceStreams() const; | ||||
| 	void setScopeXYSink(BasebandSampleSink* sampleSink) { m_basebandSink->setScopeXYSink(sampleSink); } | ||||
| 	void configureMyPosition(float myLatitude, float myLongitude) { m_basebandSink->configureMyPosition(myLatitude, myLongitude); } | ||||
| 	double getMagSq() { return m_basebandSink->getMagSq(); } | ||||
| 	bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); } | ||||
| 	const DSDDecoder& getDecoder() const { return m_basebandSink->getDecoder(); } | ||||
|     void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_basebandSink->getMagSqLevels(avg, peak, nbSamples); } | ||||
|     const char *updateAndGetStatusText() { return m_basebandSink->updateAndGetStatusText(); } | ||||
| 
 | ||||
|     static const QString m_channelIdURI; | ||||
|     static const QString m_channelId; | ||||
| 
 | ||||
| private: | ||||
|     struct MagSqLevelsStore | ||||
|     { | ||||
|         MagSqLevelsStore() : | ||||
|             m_magsq(1e-12), | ||||
|             m_magsqPeak(1e-12) | ||||
|         {} | ||||
|         double m_magsq; | ||||
|         double m_magsqPeak; | ||||
|     }; | ||||
| 
 | ||||
|     typedef enum | ||||
|     { | ||||
|         signalFormatNone, | ||||
|         signalFormatDMR, | ||||
|         signalFormatDStar, | ||||
|         signalFormatDPMR, | ||||
|         signalFormatYSF, | ||||
|         signalFormatNXDN | ||||
|     } SignalFormat; //!< Used for status text formatting
 | ||||
| 
 | ||||
| 	class MsgConfigureMyPosition : public Message { | ||||
| 		MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
| 	public: | ||||
| 		float getMyLatitude() const { return m_myLatitude; } | ||||
| 		float getMyLongitude() const { return m_myLongitude; } | ||||
| 
 | ||||
| 		static MsgConfigureMyPosition* create(float myLatitude, float myLongitude) | ||||
| 		{ | ||||
| 			return new MsgConfigureMyPosition(myLatitude, myLongitude); | ||||
| 		} | ||||
| 
 | ||||
| 	private: | ||||
| 		float m_myLatitude; | ||||
| 		float m_myLongitude; | ||||
| 
 | ||||
| 		MsgConfigureMyPosition(float myLatitude, float myLongitude) : | ||||
| 			m_myLatitude(myLatitude), | ||||
| 			m_myLongitude(myLongitude) | ||||
| 		{} | ||||
| 	}; | ||||
| 
 | ||||
| 	enum RateState { | ||||
| 		RSInitialFill, | ||||
| 		RSRunning | ||||
| 	}; | ||||
| 
 | ||||
| 	DeviceAPI *m_deviceAPI; | ||||
|     ThreadedBasebandSampleSink* m_threadedChannelizer; | ||||
|     DownChannelizer* m_channelizer; | ||||
| 
 | ||||
|     int m_inputSampleRate; | ||||
| 	int m_inputFrequencyOffset; | ||||
|     QThread *m_thread; | ||||
|     DSDDemodBaseband *m_basebandSink; | ||||
| 	DSDDemodSettings m_settings; | ||||
|     quint32 m_audioSampleRate; | ||||
| 
 | ||||
| 	NCO m_nco; | ||||
| 	Interpolator m_interpolator; | ||||
| 	Real m_interpolatorDistance; | ||||
| 	Real m_interpolatorDistanceRemain; | ||||
| 	int m_sampleCount; | ||||
| 	int m_squelchCount; | ||||
| 	int m_squelchGate; | ||||
| 	double m_squelchLevel; | ||||
| 	bool m_squelchOpen; | ||||
|     DoubleBufferFIFO<Real> m_squelchDelayLine; | ||||
| 
 | ||||
|     MovingAverageUtil<Real, double, 16> m_movingAverage; | ||||
|     double m_magsq; | ||||
|     double m_magsqSum; | ||||
|     double m_magsqPeak; | ||||
|     int  m_magsqCount; | ||||
|     MagSqLevelsStore m_magSqLevelStore; | ||||
| 
 | ||||
| 	SampleVector m_scopeSampleBuffer; | ||||
| 	AudioVector m_audioBuffer; | ||||
| 	uint m_audioBufferFill; | ||||
| 	FixReal *m_sampleBuffer; //!< samples ring buffer
 | ||||
| 	int m_sampleBufferIndex; | ||||
| 	int m_scaleFromShort; | ||||
| 
 | ||||
| 	AudioFifo m_audioFifo1; | ||||
|     AudioFifo m_audioFifo2; | ||||
| 	BasebandSampleSink* m_scopeXY; | ||||
| 	bool m_scopeEnabled; | ||||
| 
 | ||||
| 	DSDDecoder m_dsdDecoder; | ||||
| 
 | ||||
| 	char m_formatStatusText[82+1]; //!< Fixed signal format dependent status text
 | ||||
|     SignalFormat m_signalFormat;   //!< Used to keep formatting during successive calls for the same standard type
 | ||||
|     PhaseDiscriminators m_phaseDiscri; | ||||
|     int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
 | ||||
| 
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
|     QMutex m_settingsMutex; | ||||
| 
 | ||||
|     static const int m_udpBlockSize; | ||||
| 
 | ||||
|     void applyAudioSampleRate(int sampleRate); | ||||
|     void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); | ||||
| 	void applySettings(const DSDDemodSettings& settings, bool force = false); | ||||
| 	void formatStatusText(); | ||||
| 
 | ||||
|     void applySettings(const DSDDemodSettings& settings, bool force = false); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DSDDemodSettings& settings, bool force); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										174
									
								
								plugins/channelrx/demoddsd/dsddemodbaseband.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								plugins/channelrx/demoddsd/dsddemodbaseband.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,174 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 <QDebug> | ||||
| 
 | ||||
| #include "dsp/dspengine.h" | ||||
| #include "dsp/dspcommands.h" | ||||
| #include "dsp/downsamplechannelizer.h" | ||||
| 
 | ||||
| #include "dsddemodbaseband.h" | ||||
| 
 | ||||
| MESSAGE_CLASS_DEFINITION(DSDDemodBaseband::MsgConfigureDSDDemodBaseband, Message) | ||||
| 
 | ||||
| DSDDemodBaseband::DSDDemodBaseband() : | ||||
|     m_mutex(QMutex::Recursive) | ||||
| { | ||||
|     qDebug("DSDDemodBaseband::DSDDemodBaseband"); | ||||
|     m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); | ||||
|     m_channelizer = new DownSampleChannelizer(&m_sink); | ||||
| 
 | ||||
|     QObject::connect( | ||||
|         &m_sampleFifo, | ||||
|         &SampleSinkFifo::dataReady, | ||||
|         this, | ||||
|         &DSDDemodBaseband::handleData, | ||||
|         Qt::QueuedConnection | ||||
|     ); | ||||
| 
 | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo1(), getInputMessageQueue()); | ||||
|     m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate()); | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo2(), getInputMessageQueue()); | ||||
|     m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate()); | ||||
| 
 | ||||
|     connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); | ||||
| } | ||||
| 
 | ||||
| DSDDemodBaseband::~DSDDemodBaseband() | ||||
| { | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo1()); | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo2()); | ||||
|     delete m_channelizer; | ||||
| } | ||||
| 
 | ||||
| void DSDDemodBaseband::reset() | ||||
| { | ||||
|     QMutexLocker mutexLocker(&m_mutex); | ||||
|     m_sampleFifo.reset(); | ||||
| } | ||||
| 
 | ||||
| void DSDDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) | ||||
| { | ||||
|     m_sampleFifo.write(begin, end); | ||||
| } | ||||
| 
 | ||||
| void DSDDemodBaseband::handleData() | ||||
| { | ||||
|     while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) | ||||
|     { | ||||
| 		SampleVector::iterator part1begin; | ||||
| 		SampleVector::iterator part1end; | ||||
| 		SampleVector::iterator part2begin; | ||||
| 		SampleVector::iterator part2end; | ||||
| 
 | ||||
|         std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); | ||||
| 
 | ||||
| 		// first part of FIFO data
 | ||||
|         if (part1begin != part1end) { | ||||
|             m_channelizer->feed(part1begin, part1end); | ||||
|         } | ||||
| 
 | ||||
| 		// second part of FIFO data (used when block wraps around)
 | ||||
| 		if(part2begin != part2end) { | ||||
|             m_channelizer->feed(part2begin, part2end); | ||||
|         } | ||||
| 
 | ||||
| 		m_sampleFifo.readCommit((unsigned int) count); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DSDDemodBaseband::handleInputMessages() | ||||
| { | ||||
| 	Message* message; | ||||
| 
 | ||||
| 	while ((message = m_inputMessageQueue.pop()) != nullptr) | ||||
| 	{ | ||||
| 		if (handleMessage(*message)) { | ||||
| 			delete message; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bool DSDDemodBaseband::handleMessage(const Message& cmd) | ||||
| { | ||||
|     if (MsgConfigureDSDDemodBaseband::match(cmd)) | ||||
|     { | ||||
|         QMutexLocker mutexLocker(&m_mutex); | ||||
|         MsgConfigureDSDDemodBaseband& cfg = (MsgConfigureDSDDemodBaseband&) cmd; | ||||
|         qDebug() << "DSDDemodBaseband::handleMessage: MsgConfigureDSDDemodBaseband"; | ||||
| 
 | ||||
|         applySettings(cfg.getSettings(), cfg.getForce()); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     else if (DSPSignalNotification::match(cmd)) | ||||
|     { | ||||
|         QMutexLocker mutexLocker(&m_mutex); | ||||
|         DSPSignalNotification& notif = (DSPSignalNotification&) cmd; | ||||
|         qDebug() << "DSDDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); | ||||
|         m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); | ||||
|         m_channelizer->setBasebandSampleRate(notif.getSampleRate()); | ||||
|         m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); | ||||
| 
 | ||||
| 		return true; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DSDDemodBaseband::applySettings(const DSDDemodSettings& settings, bool force) | ||||
| { | ||||
|     if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) | ||||
|     { | ||||
|         m_channelizer->setChannelization(m_sink.getAudioSampleRate(), settings.m_inputFrequencyOffset); | ||||
|         m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) | ||||
|     { | ||||
|         AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); | ||||
|         int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); | ||||
|         //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex);
 | ||||
|         audioDeviceManager->addAudioSink(m_sink.getAudioFifo1(), getInputMessageQueue(), audioDeviceIndex); | ||||
|         audioDeviceManager->addAudioSink(m_sink.getAudioFifo2(), getInputMessageQueue(), audioDeviceIndex); | ||||
|         uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); | ||||
| 
 | ||||
|         if (m_sink.getAudioSampleRate() != audioSampleRate) | ||||
|         { | ||||
|             m_sink.applyAudioSampleRate(audioSampleRate); | ||||
|             m_channelizer->setChannelization(audioSampleRate, settings.m_inputFrequencyOffset); | ||||
|             m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m_sink.applySettings(settings, force); | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
| int DSDDemodBaseband::getChannelSampleRate() const | ||||
| { | ||||
|     return m_channelizer->getChannelSampleRate(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void DSDDemodBaseband::setBasebandSampleRate(int sampleRate) | ||||
| { | ||||
|     m_channelizer->setBasebandSampleRate(sampleRate); | ||||
| } | ||||
							
								
								
									
										91
									
								
								plugins/channelrx/demoddsd/dsddemodbaseband.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								plugins/channelrx/demoddsd/dsddemodbaseband.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 INCLUDE_DSDDEMODBASEBAND_H | ||||
| #define INCLUDE_DSDDEMODBASEBAND_H | ||||
| 
 | ||||
| #include <QObject> | ||||
| #include <QMutex> | ||||
| 
 | ||||
| #include "dsp/samplesinkfifo.h" | ||||
| #include "util/message.h" | ||||
| #include "util/messagequeue.h" | ||||
| 
 | ||||
| #include "dsddemodsink.h" | ||||
| 
 | ||||
| class DownSampleChannelizer; | ||||
| 
 | ||||
| class DSDDemodBaseband : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     class MsgConfigureDSDDemodBaseband : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         const DSDDemodSettings& getSettings() const { return m_settings; } | ||||
|         bool getForce() const { return m_force; } | ||||
| 
 | ||||
|         static MsgConfigureDSDDemodBaseband* create(const DSDDemodSettings& settings, bool force) | ||||
|         { | ||||
|             return new MsgConfigureDSDDemodBaseband(settings, force); | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
|         DSDDemodSettings m_settings; | ||||
|         bool m_force; | ||||
| 
 | ||||
|         MsgConfigureDSDDemodBaseband(const DSDDemodSettings& settings, bool force) : | ||||
|             Message(), | ||||
|             m_settings(settings), | ||||
|             m_force(force) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     DSDDemodBaseband(); | ||||
|     ~DSDDemodBaseband(); | ||||
|     void reset(); | ||||
|     void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); | ||||
|     MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
 | ||||
|     int getChannelSampleRate() const; | ||||
|     unsigned int getAudioSampleRate() const { return m_sink.getAudioSampleRate(); } | ||||
|     double getMagSq() { return m_sink.getMagSq(); } | ||||
|     void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); } | ||||
|     bool getSquelchOpen() const { return m_sink.getSquelchOpen(); } | ||||
|     void setBasebandSampleRate(int sampleRate); | ||||
| 	void setScopeXYSink(BasebandSampleSink* scopeSink) { m_sink.setScopeXYSink(scopeSink); } | ||||
| 	void configureMyPosition(float myLatitude, float myLongitude) { m_sink.configureMyPosition(myLatitude, myLongitude); } | ||||
|    	const DSDDecoder& getDecoder() const { return m_sink.getDecoder(); } | ||||
|     const char *updateAndGetStatusText() { return m_sink.updateAndGetStatusText(); } | ||||
| 
 | ||||
| private: | ||||
|     SampleSinkFifo m_sampleFifo; | ||||
|     DownSampleChannelizer *m_channelizer; | ||||
|     DSDDemodSink m_sink; | ||||
| 	MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
 | ||||
|     DSDDemodSettings m_settings; | ||||
|     QMutex m_mutex; | ||||
| 
 | ||||
|     bool handleMessage(const Message& cmd); | ||||
|     void applySettings(const DSDDemodSettings& settings, bool force = false); | ||||
| 
 | ||||
| private slots: | ||||
|     void handleInputMessages(); | ||||
|     void handleData(); //!< Handle data when samples have to be processed
 | ||||
| }; | ||||
| 
 | ||||
| #endif // INCLUDE_DSDDEMODBASEBAND_H
 | ||||
| @ -417,7 +417,7 @@ void DSDDemodGUI::updateMyPosition() | ||||
| 
 | ||||
|     if ((m_myLatitude != latitude) || (m_myLongitude != longitude)) | ||||
|     { | ||||
|         m_dsdDemod->configureMyPosition(m_dsdDemod->getInputMessageQueue(), latitude, longitude); | ||||
|         m_dsdDemod->configureMyPosition(latitude, longitude); | ||||
|         m_myLatitude = latitude; | ||||
|         m_myLongitude = longitude; | ||||
|     } | ||||
| @ -500,10 +500,6 @@ void DSDDemodGUI::applySettings(bool force) | ||||
| 	{ | ||||
| 		qDebug() << "DSDDemodGUI::applySettings"; | ||||
| 
 | ||||
|         DSDDemod::MsgConfigureChannelizer* channelConfigMsg = DSDDemod::MsgConfigureChannelizer::create( | ||||
|                 48000, m_channelMarker.getCenterFrequency()); | ||||
|         m_dsdDemod->getInputMessageQueue()->push(channelConfigMsg); | ||||
| 
 | ||||
|         DSDDemod::MsgConfigureDSDDemod* message = DSDDemod::MsgConfigureDSDDemod::create( m_settings, force); | ||||
|         m_dsdDemod->getInputMessageQueue()->push(message); | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										595
									
								
								plugins/channelrx/demoddsd/dsddemodsink.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										595
									
								
								plugins/channelrx/demoddsd/dsddemodsink.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,595 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 <string.h> | ||||
| #include <stdio.h> | ||||
| #include <complex.h> | ||||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| #include "SWGDSDDemodSettings.h" | ||||
| #include "SWGChannelReport.h" | ||||
| #include "SWGDSDDemodReport.h" | ||||
| #include "SWGRDSReport.h" | ||||
| 
 | ||||
| #include "dsp/dspengine.h" | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "audio/audiooutput.h" | ||||
| #include "util/db.h" | ||||
| 
 | ||||
| #include "dsddemodsink.h" | ||||
| 
 | ||||
| DSDDemodSink::DSDDemodSink() : | ||||
|     m_channelSampleRate(48000), | ||||
|     m_channelFrequencyOffset(0), | ||||
|     m_interpolatorDistance(0.0f), | ||||
|     m_interpolatorDistanceRemain(0.0f), | ||||
|     m_sampleCount(0), | ||||
|     m_squelchCount(0), | ||||
|     m_squelchGate(0), | ||||
|     m_squelchLevel(1e-4), | ||||
|     m_squelchOpen(false), | ||||
|     m_squelchDelayLine(24000), | ||||
|     m_audioFifo1(48000), | ||||
|     m_audioFifo2(48000), | ||||
|     m_scopeXY(0), | ||||
|     m_scopeEnabled(true), | ||||
|     m_dsdDecoder(), | ||||
|     m_signalFormat(signalFormatNone) | ||||
| { | ||||
| 	m_audioBuffer.resize(1<<14); | ||||
| 	m_audioBufferFill = 0; | ||||
| 
 | ||||
| 	m_sampleBuffer = new FixReal[1<<17]; // 128 kS
 | ||||
| 	m_sampleBufferIndex = 0; | ||||
| 	m_scaleFromShort = SDR_RX_SAMP_SZ < sizeof(short)*8 ? 1 : 1<<(SDR_RX_SAMP_SZ - sizeof(short)*8); | ||||
| 
 | ||||
| 	m_magsq = 0.0f; | ||||
|     m_magsqSum = 0.0f; | ||||
|     m_magsqPeak = 0.0f; | ||||
|     m_magsqCount = 0; | ||||
| 
 | ||||
| 	applySettings(m_settings, true); | ||||
|     applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); | ||||
| } | ||||
| 
 | ||||
| DSDDemodSink::~DSDDemodSink() | ||||
| { | ||||
|     delete[] m_sampleBuffer; | ||||
| } | ||||
| 
 | ||||
| void DSDDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) | ||||
| {	Complex ci; | ||||
| 	int samplesPerSymbol = m_dsdDecoder.getSamplesPerSymbol(); | ||||
| 
 | ||||
| 	m_scopeSampleBuffer.clear(); | ||||
| 
 | ||||
| 	m_dsdDecoder.enableMbelib(!DSPEngine::instance()->hasDVSerialSupport()); // disable mbelib if DV serial support is present and activated else enable it
 | ||||
| 
 | ||||
| 	for (SampleVector::const_iterator it = begin; it != end; ++it) | ||||
| 	{ | ||||
| 		Complex c(it->real(), it->imag()); | ||||
| 		c *= m_nco.nextIQ(); | ||||
| 
 | ||||
|         if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) | ||||
|         { | ||||
|             FixReal sample, delayedSample; | ||||
|             qint16 sampleDSD; | ||||
| 
 | ||||
|             Real re = ci.real() / SDR_RX_SCALED; | ||||
|             Real im = ci.imag() / SDR_RX_SCALED; | ||||
|             Real magsq = re*re + im*im; | ||||
|             m_movingAverage(magsq); | ||||
| 
 | ||||
|             m_magsqSum += magsq; | ||||
| 
 | ||||
|             if (magsq > m_magsqPeak) | ||||
|             { | ||||
|                 m_magsqPeak = magsq; | ||||
|             } | ||||
| 
 | ||||
|             m_magsqCount++; | ||||
| 
 | ||||
|             Real demod = m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_demodGain; // [-1.0:1.0]
 | ||||
|             m_sampleCount++; | ||||
| 
 | ||||
|             // AF processing
 | ||||
| 
 | ||||
|             if (m_movingAverage.asDouble() > m_squelchLevel) | ||||
|             { | ||||
|                 if (m_squelchGate > 0) | ||||
|                 { | ||||
| 
 | ||||
|                     if (m_squelchCount < m_squelchGate*2) { | ||||
|                         m_squelchCount++; | ||||
|                     } | ||||
| 
 | ||||
|                     m_squelchDelayLine.write(demod); | ||||
|                     m_squelchOpen = m_squelchCount > m_squelchGate; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     m_squelchOpen = true; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (m_squelchGate > 0) | ||||
|                 { | ||||
|                     if (m_squelchCount > 0) { | ||||
|                         m_squelchCount--; | ||||
|                     } | ||||
| 
 | ||||
|                     m_squelchDelayLine.write(0); | ||||
|                     m_squelchOpen = m_squelchCount > m_squelchGate; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     m_squelchOpen = false; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (m_squelchOpen) | ||||
|             { | ||||
|                 if (m_squelchGate > 0) | ||||
|                 { | ||||
|                     sampleDSD = m_squelchDelayLine.readBack(m_squelchGate) * 32768.0f;   // DSD decoder takes int16 samples
 | ||||
|                     sample = m_squelchDelayLine.readBack(m_squelchGate) * SDR_RX_SCALEF; // scale to sample size
 | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     sampleDSD = demod * 32768.0f;   // DSD decoder takes int16 samples
 | ||||
|                     sample = demod * SDR_RX_SCALEF; // scale to sample size
 | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 sampleDSD = 0; | ||||
|                 sample = 0; | ||||
|             } | ||||
| 
 | ||||
|             m_dsdDecoder.pushSample(sampleDSD); | ||||
| 
 | ||||
|             if (m_settings.m_enableCosineFiltering) { // show actual input to FSK demod
 | ||||
|             	sample = m_dsdDecoder.getFilteredSample() * m_scaleFromShort; | ||||
|             } | ||||
| 
 | ||||
|             if (m_sampleBufferIndex < (1<<17)-1) { | ||||
|                 m_sampleBufferIndex++; | ||||
|             } else { | ||||
|                 m_sampleBufferIndex = 0; | ||||
|             } | ||||
| 
 | ||||
|             m_sampleBuffer[m_sampleBufferIndex] = sample; | ||||
| 
 | ||||
|             if (m_sampleBufferIndex < samplesPerSymbol) { | ||||
|                 delayedSample = m_sampleBuffer[(1<<17) - samplesPerSymbol + m_sampleBufferIndex]; // wrap
 | ||||
|             } else { | ||||
|                 delayedSample = m_sampleBuffer[m_sampleBufferIndex - samplesPerSymbol]; | ||||
|             } | ||||
| 
 | ||||
|             if (m_settings.m_syncOrConstellation) | ||||
|             { | ||||
|                 Sample s(sample, m_dsdDecoder.getSymbolSyncSample() * m_scaleFromShort * 0.84); | ||||
|                 m_scopeSampleBuffer.push_back(s); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 Sample s(sample, delayedSample); // I=signal, Q=signal delayed by 20 samples (2400 baud: lowest rate)
 | ||||
|                 m_scopeSampleBuffer.push_back(s); | ||||
|             } | ||||
| 
 | ||||
|             if (DSPEngine::instance()->hasDVSerialSupport()) | ||||
|             { | ||||
|                 if ((m_settings.m_slot1On) && m_dsdDecoder.mbeDVReady1()) | ||||
|                 { | ||||
|                     if (!m_settings.m_audioMute) | ||||
|                     { | ||||
|                         DSPEngine::instance()->pushMbeFrame( | ||||
|                                 m_dsdDecoder.getMbeDVFrame1(), | ||||
|                                 m_dsdDecoder.getMbeRateIndex(), | ||||
|                                 m_settings.m_volume * 10.0, | ||||
|                                 m_settings.m_tdmaStereo ? 1 : 3, // left or both channels
 | ||||
|                                 m_settings.m_highPassFilter, | ||||
|                                 m_audioSampleRate/8000, // upsample from native 8k
 | ||||
|                                 &m_audioFifo1); | ||||
|                     } | ||||
| 
 | ||||
|                     m_dsdDecoder.resetMbeDV1(); | ||||
|                 } | ||||
| 
 | ||||
|                 if ((m_settings.m_slot2On) && m_dsdDecoder.mbeDVReady2()) | ||||
|                 { | ||||
|                     if (!m_settings.m_audioMute) | ||||
|                     { | ||||
|                         DSPEngine::instance()->pushMbeFrame( | ||||
|                                 m_dsdDecoder.getMbeDVFrame2(), | ||||
|                                 m_dsdDecoder.getMbeRateIndex(), | ||||
|                                 m_settings.m_volume * 10.0, | ||||
|                                 m_settings.m_tdmaStereo ? 2 : 3, // right or both channels
 | ||||
|                                 m_settings.m_highPassFilter, | ||||
|                                 m_audioSampleRate/8000, // upsample from native 8k
 | ||||
|                                 &m_audioFifo2); | ||||
|                     } | ||||
| 
 | ||||
|                     m_dsdDecoder.resetMbeDV2(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             m_interpolatorDistanceRemain += m_interpolatorDistance; | ||||
|         } | ||||
| 	} | ||||
| 
 | ||||
| 	if (!DSPEngine::instance()->hasDVSerialSupport()) | ||||
| 	{ | ||||
| 	    if (m_settings.m_slot1On) | ||||
| 	    { | ||||
| 	        int nbAudioSamples; | ||||
| 	        short *dsdAudio = m_dsdDecoder.getAudio1(nbAudioSamples); | ||||
| 
 | ||||
| 	        if (nbAudioSamples > 0) | ||||
| 	        { | ||||
| 	            if (!m_settings.m_audioMute) { | ||||
| 	                m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples); | ||||
| 	            } | ||||
| 
 | ||||
| 	            m_dsdDecoder.resetAudio1(); | ||||
| 	        } | ||||
| 	    } | ||||
| 
 | ||||
|         if (m_settings.m_slot2On) | ||||
|         { | ||||
|             int nbAudioSamples; | ||||
|             short *dsdAudio = m_dsdDecoder.getAudio2(nbAudioSamples); | ||||
| 
 | ||||
|             if (nbAudioSamples > 0) | ||||
|             { | ||||
|                 if (!m_settings.m_audioMute) { | ||||
|                     m_audioFifo2.write((const quint8*) dsdAudio, nbAudioSamples); | ||||
|                 } | ||||
| 
 | ||||
|                 m_dsdDecoder.resetAudio2(); | ||||
|             } | ||||
|         } | ||||
| 	} | ||||
| 
 | ||||
|     if ((m_scopeXY != 0) && (m_scopeEnabled)) | ||||
|     { | ||||
|         m_scopeXY->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DSDDemodSink::applyAudioSampleRate(int sampleRate) | ||||
| { | ||||
|     int upsampling = sampleRate / 8000; | ||||
| 
 | ||||
|     qDebug("DSDDemodSink::applyAudioSampleRate: audio rate: %d upsample by %d", sampleRate, upsampling); | ||||
| 
 | ||||
|     if (sampleRate % 8000 != 0) { | ||||
|         qDebug("DSDDemodSink::applyAudioSampleRate: audio will sound best with sample rates that are integer multiples of 8 kS/s"); | ||||
|     } | ||||
| 
 | ||||
|     m_dsdDecoder.setUpsampling(upsampling); | ||||
|     m_audioSampleRate = sampleRate; | ||||
| } | ||||
| 
 | ||||
| void DSDDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) | ||||
| { | ||||
|     qDebug() << "DSDDemodSink::applyChannelSettings:" | ||||
|             << " channelSampleRate: " << channelSampleRate | ||||
|             << " inputFrequencyOffset: " << channelFrequencyOffset; | ||||
| 
 | ||||
|     if ((channelFrequencyOffset != m_channelFrequencyOffset) || | ||||
|         (channelSampleRate != m_channelSampleRate) || force) | ||||
|     { | ||||
|         m_nco.setFreq(-channelFrequencyOffset, channelSampleRate); | ||||
|     } | ||||
| 
 | ||||
|     if ((channelSampleRate != m_channelSampleRate) || force) | ||||
|     { | ||||
|         m_interpolator.create(16, channelSampleRate, (m_settings.m_rfBandwidth) / 2.2); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance =  (Real) channelSampleRate / (Real) 48000; | ||||
|     } | ||||
| 
 | ||||
|     m_channelSampleRate = channelSampleRate; | ||||
|     m_channelFrequencyOffset = channelFrequencyOffset; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void DSDDemodSink::applySettings(const DSDDemodSettings& settings, bool force) | ||||
| { | ||||
|     qDebug() << "DSDDemodSink::applySettings: " | ||||
|             << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset | ||||
|             << " m_rfBandwidth: " << settings.m_rfBandwidth | ||||
|             << " m_fmDeviation: " << settings.m_fmDeviation | ||||
|             << " m_demodGain: " << settings.m_demodGain | ||||
|             << " m_volume: " << settings.m_volume | ||||
|             << " m_baudRate: " << settings.m_baudRate | ||||
|             << " m_squelchGate" << settings.m_squelchGate | ||||
|             << " m_squelch: " << settings.m_squelch | ||||
|             << " m_audioMute: " << settings.m_audioMute | ||||
|             << " m_enableCosineFiltering: " << settings.m_enableCosineFiltering | ||||
|             << " m_syncOrConstellation: " << settings.m_syncOrConstellation | ||||
|             << " m_slot1On: " << settings.m_slot1On | ||||
|             << " m_slot2On: " << settings.m_slot2On | ||||
|             << " m_tdmaStereo: " << settings.m_tdmaStereo | ||||
|             << " m_pllLock: " << settings.m_pllLock | ||||
|             << " m_highPassFilter: "<< settings.m_highPassFilter | ||||
|             << " m_audioDeviceName: " << settings.m_audioDeviceName | ||||
|             << " m_traceLengthMutliplier: " << settings.m_traceLengthMutliplier | ||||
|             << " m_traceStroke: " << settings.m_traceStroke | ||||
|             << " m_traceDecay: " << settings.m_traceDecay | ||||
|             << " m_streamIndex: " << settings.m_streamIndex | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | ||||
|     { | ||||
|         m_interpolator.create(16, m_channelSampleRate, (settings.m_rfBandwidth) / 2.2); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance =  (Real) m_channelSampleRate / (Real) 48000; | ||||
|         //m_phaseDiscri.setFMScaling((float) settings.m_rfBandwidth / (float) settings.m_fmDeviation);
 | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) | ||||
|     { | ||||
|         m_phaseDiscri.setFMScaling(48000.0f / (2.0f*settings.m_fmDeviation)); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) | ||||
|     { | ||||
|         m_squelchGate = 480 * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
 | ||||
|         m_squelchCount = 0; // reset squelch open counter
 | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelch != m_settings.m_squelch) || force) | ||||
|     { | ||||
|         // input is a value in dB
 | ||||
|         m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_volume != m_settings.m_volume) || force) | ||||
|     { | ||||
|         m_dsdDecoder.setAudioGain(settings.m_volume); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_baudRate != m_settings.m_baudRate) || force) | ||||
|     { | ||||
|         m_dsdDecoder.setBaudRate(settings.m_baudRate); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_enableCosineFiltering != m_settings.m_enableCosineFiltering) || force) | ||||
|     { | ||||
|         m_dsdDecoder.enableCosineFiltering(settings.m_enableCosineFiltering); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_tdmaStereo != m_settings.m_tdmaStereo) || force) | ||||
|     { | ||||
|         m_dsdDecoder.setTDMAStereo(settings.m_tdmaStereo); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_pllLock != m_settings.m_pllLock) || force) | ||||
|     { | ||||
|         m_dsdDecoder.setSymbolPLLLock(settings.m_pllLock); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_highPassFilter != m_settings.m_highPassFilter) || force) | ||||
|     { | ||||
|         m_dsdDecoder.useHPMbelib(settings.m_highPassFilter); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
| void DSDDemodSink::configureMyPosition(float myLatitude, float myLongitude) | ||||
| { | ||||
|     m_dsdDecoder.setMyPoint(myLatitude, myLongitude); | ||||
| } | ||||
| 
 | ||||
| const char *DSDDemodSink::updateAndGetStatusText() | ||||
| { | ||||
|     formatStatusText(); | ||||
|     return m_formatStatusText; | ||||
| } | ||||
| 
 | ||||
| void DSDDemodSink::formatStatusText() | ||||
| { | ||||
|     switch (getDecoder().getSyncType()) | ||||
|     { | ||||
|     case DSDcc::DSDDecoder::DSDSyncDMRDataMS: | ||||
|     case DSDcc::DSDDecoder::DSDSyncDMRDataP: | ||||
|     case DSDcc::DSDDecoder::DSDSyncDMRVoiceMS: | ||||
|     case DSDcc::DSDDecoder::DSDSyncDMRVoiceP: | ||||
|         if (m_signalFormat != signalFormatDMR) | ||||
|         { | ||||
|             strcpy(m_formatStatusText, "Sta: __ S1: __________________________ S2: __________________________"); | ||||
|         } | ||||
| 
 | ||||
|         switch (getDecoder().getStationType()) | ||||
|         { | ||||
|         case DSDcc::DSDDecoder::DSDBaseStation: | ||||
|             memcpy(&m_formatStatusText[5], "BS ", 3); | ||||
|             break; | ||||
|         case DSDcc::DSDDecoder::DSDMobileStation: | ||||
|             memcpy(&m_formatStatusText[5], "MS ", 3); | ||||
|             break; | ||||
|         default: | ||||
|             memcpy(&m_formatStatusText[5], "NA ", 3); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         memcpy(&m_formatStatusText[12], getDecoder().getDMRDecoder().getSlot0Text(), 26); | ||||
|         memcpy(&m_formatStatusText[43], getDecoder().getDMRDecoder().getSlot1Text(), 26); | ||||
|         m_signalFormat = signalFormatDMR; | ||||
|         break; | ||||
|     case DSDcc::DSDDecoder::DSDSyncDStarHeaderN: | ||||
|     case DSDcc::DSDDecoder::DSDSyncDStarHeaderP: | ||||
|     case DSDcc::DSDDecoder::DSDSyncDStarN: | ||||
|     case DSDcc::DSDDecoder::DSDSyncDStarP: | ||||
|         if (m_signalFormat != signalFormatDStar) | ||||
|         { | ||||
|                                      //           1    1    2    2    3    3    4    4    5    5    6    6    7    7    8
 | ||||
|                                      // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
 | ||||
|             strcpy(m_formatStatusText, "________/____>________|________>________|____________________|______:___/_____._"); | ||||
|                                      // MY            UR       RPT1     RPT2     Info                 Loc    Target
 | ||||
|         } | ||||
| 
 | ||||
|         { | ||||
|             const std::string& rpt1 = getDecoder().getDStarDecoder().getRpt1(); | ||||
|             const std::string& rpt2 = getDecoder().getDStarDecoder().getRpt2(); | ||||
|             const std::string& mySign = getDecoder().getDStarDecoder().getMySign(); | ||||
|             const std::string& yrSign = getDecoder().getDStarDecoder().getYourSign(); | ||||
| 
 | ||||
|             if (rpt1.length() > 0) { // 0 or 8
 | ||||
|                 memcpy(&m_formatStatusText[23], rpt1.c_str(), 8); | ||||
|             } | ||||
|             if (rpt2.length() > 0) { // 0 or 8
 | ||||
|                 memcpy(&m_formatStatusText[32], rpt2.c_str(), 8); | ||||
|             } | ||||
|             if (yrSign.length() > 0) { // 0 or 8
 | ||||
|                 memcpy(&m_formatStatusText[14], yrSign.c_str(), 8); | ||||
|             } | ||||
|             if (mySign.length() > 0) { // 0 or 13
 | ||||
|                 memcpy(&m_formatStatusText[0], mySign.c_str(), 13); | ||||
|             } | ||||
|             memcpy(&m_formatStatusText[41], getDecoder().getDStarDecoder().getInfoText(), 20); | ||||
|             memcpy(&m_formatStatusText[62], getDecoder().getDStarDecoder().getLocator(), 6); | ||||
|             snprintf(&m_formatStatusText[69], 82-69, "%03d/%07.1f", | ||||
|                     getDecoder().getDStarDecoder().getBearing(), | ||||
|                     getDecoder().getDStarDecoder().getDistance()); | ||||
|         } | ||||
| 
 | ||||
|         m_formatStatusText[82] = '\0'; | ||||
|         m_signalFormat = signalFormatDStar; | ||||
|         break; | ||||
|     case DSDcc::DSDDecoder::DSDSyncDPMR: | ||||
|         snprintf(m_formatStatusText, 82, "%s CC: %04d OI: %08d CI: %08d", | ||||
|                 DSDcc::DSDdPMR::dpmrFrameTypes[(int) getDecoder().getDPMRDecoder().getFrameType()], | ||||
|                 getDecoder().getDPMRDecoder().getColorCode(), | ||||
|                 getDecoder().getDPMRDecoder().getOwnId(), | ||||
|                 getDecoder().getDPMRDecoder().getCalledId()); | ||||
|         m_signalFormat = signalFormatDPMR; | ||||
|         break; | ||||
|     case DSDcc::DSDDecoder::DSDSyncNXDNP: | ||||
|     case DSDcc::DSDDecoder::DSDSyncNXDNN: | ||||
|         if (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRCCH) | ||||
|         { | ||||
|             //           1    1    2    2    3    3    4    4    5    5    6    6    7    7    8
 | ||||
|             // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
 | ||||
|             // RC r cc mm llllll ssss
 | ||||
|             snprintf(m_formatStatusText, 82, "RC %s %02d %02X %06X %02X", | ||||
|                     getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H", | ||||
|                     getDecoder().getNXDNDecoder().getRAN(), | ||||
|                     getDecoder().getNXDNDecoder().getMessageType(), | ||||
|                     getDecoder().getNXDNDecoder().getLocationId(), | ||||
|                     getDecoder().getNXDNDecoder().getServicesFlag()); | ||||
|         } | ||||
|         else if ((getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRTCH) | ||||
|             || (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRDCH)) | ||||
|         { | ||||
|             if (getDecoder().getNXDNDecoder().isIdle()) { | ||||
|                 snprintf(m_formatStatusText, 82, "%s IDLE", getDecoder().getNXDNDecoder().getRFChannelStr()); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 //           1    1    2    2    3    3    4    4    5    5    6    6    7    7    8
 | ||||
|                 // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
 | ||||
|                 // Rx r cc mm sssss>gddddd
 | ||||
|                 snprintf(m_formatStatusText, 82, "%s %s %02d %02X %05d>%c%05d", | ||||
|                         getDecoder().getNXDNDecoder().getRFChannelStr(), | ||||
|                         getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H", | ||||
|                         getDecoder().getNXDNDecoder().getRAN(), | ||||
|                         getDecoder().getNXDNDecoder().getMessageType(), | ||||
|                         getDecoder().getNXDNDecoder().getSourceId(), | ||||
|                         getDecoder().getNXDNDecoder().isGroupCall() ? 'G' : 'I', | ||||
|                         getDecoder().getNXDNDecoder().getDestinationId()); | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             //           1    1    2    2    3    3    4    4    5    5    6    6    7    7    8
 | ||||
|             // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
 | ||||
|             // RU
 | ||||
|             snprintf(m_formatStatusText, 82, "RU"); | ||||
|         } | ||||
|         m_signalFormat = signalFormatNXDN; | ||||
|         break; | ||||
|     case DSDcc::DSDDecoder::DSDSyncYSF: | ||||
|         //           1    1    2    2    3    3    4    4    5    5    6    6    7    7    8
 | ||||
|         // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
 | ||||
|         // C V2 RI 0:7 WL000|ssssssssss>dddddddddd |UUUUUUUUUU>DDDDDDDDDD|44444
 | ||||
|         if (getDecoder().getYSFDecoder().getFICHError() == DSDcc::DSDYSF::FICHNoError) | ||||
|         { | ||||
|             snprintf(m_formatStatusText, 82, "%s ", DSDcc::DSDYSF::ysfChannelTypeText[(int) getDecoder().getYSFDecoder().getFICH().getFrameInformation()]); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             snprintf(m_formatStatusText, 82, "%d ", (int) getDecoder().getYSFDecoder().getFICHError()); | ||||
|         } | ||||
| 
 | ||||
|         snprintf(&m_formatStatusText[2], 80, "%s %s %d:%d %c%c", | ||||
|                 DSDcc::DSDYSF::ysfDataTypeText[(int) getDecoder().getYSFDecoder().getFICH().getDataType()], | ||||
|                 DSDcc::DSDYSF::ysfCallModeText[(int) getDecoder().getYSFDecoder().getFICH().getCallMode()], | ||||
|                 getDecoder().getYSFDecoder().getFICH().getBlockTotal(), | ||||
|                 getDecoder().getYSFDecoder().getFICH().getFrameTotal(), | ||||
|                 (getDecoder().getYSFDecoder().getFICH().isNarrowMode() ? 'N' : 'W'), | ||||
|                 (getDecoder().getYSFDecoder().getFICH().isInternetPath() ? 'I' : 'L')); | ||||
| 
 | ||||
|         if (getDecoder().getYSFDecoder().getFICH().isSquelchCodeEnabled()) | ||||
|         { | ||||
|             snprintf(&m_formatStatusText[14], 82-14, "%03d", getDecoder().getYSFDecoder().getFICH().getSquelchCode()); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             strncpy(&m_formatStatusText[14], "---", 82-14); | ||||
|         } | ||||
| 
 | ||||
|         char dest[13]; | ||||
| 
 | ||||
|         if (getDecoder().getYSFDecoder().radioIdMode()) | ||||
|         { | ||||
|             snprintf(dest, 12, "%-5s:%-5s", | ||||
|                     getDecoder().getYSFDecoder().getDestId(), | ||||
|                     getDecoder().getYSFDecoder().getSrcId()); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             snprintf(dest, 11, "%-10s", getDecoder().getYSFDecoder().getDest()); | ||||
|         } | ||||
| 
 | ||||
|         snprintf(&m_formatStatusText[17], 82-17, "|%-10s>%s|%-10s>%-10s|%-5s", | ||||
|                 getDecoder().getYSFDecoder().getSrc(), | ||||
|                 dest, | ||||
|                 getDecoder().getYSFDecoder().getUplink(), | ||||
|                 getDecoder().getYSFDecoder().getDownlink(), | ||||
|                 getDecoder().getYSFDecoder().getRem4()); | ||||
| 
 | ||||
|         m_signalFormat = signalFormatYSF; | ||||
|         break; | ||||
|     default: | ||||
|         m_signalFormat = signalFormatNone; | ||||
|         m_formatStatusText[0] = '\0'; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     m_formatStatusText[82] = '\0'; // guard
 | ||||
| } | ||||
							
								
								
									
										150
									
								
								plugins/channelrx/demoddsd/dsddemodsink.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								plugins/channelrx/demoddsd/dsddemodsink.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,150 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 INCLUDE_DSDDEMODSINK_H | ||||
| #define INCLUDE_DSDDEMODSINK_H | ||||
| 
 | ||||
| #include "dsp/channelsamplesink.h" | ||||
| #include "dsp/phasediscri.h" | ||||
| #include "dsp/nco.h" | ||||
| #include "dsp/interpolator.h" | ||||
| #include "dsp/lowpass.h" | ||||
| #include "dsp/bandpass.h" | ||||
| #include "dsp/afsquelch.h" | ||||
| #include "dsp/afsquelch.h" | ||||
| #include "audio/audiofifo.h" | ||||
| #include "util/movingaverage.h" | ||||
| #include "util/doublebufferfifo.h" | ||||
| 
 | ||||
| #include "dsddemodsettings.h" | ||||
| #include "dsddecoder.h" | ||||
| 
 | ||||
| class BasebandSampleSink; | ||||
| 
 | ||||
| class DSDDemodSink : public ChannelSampleSink { | ||||
| public: | ||||
|     DSDDemodSink(); | ||||
| 	~DSDDemodSink(); | ||||
| 
 | ||||
| 	virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); | ||||
| 
 | ||||
|     void applyAudioSampleRate(int sampleRate); | ||||
|     void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false); | ||||
| 	void applySettings(const DSDDemodSettings& settings, bool force = false); | ||||
|     AudioFifo *getAudioFifo1() { return &m_audioFifo1; } | ||||
|     AudioFifo *getAudioFifo2() { return &m_audioFifo2; } | ||||
|     unsigned int getAudioSampleRate() const { return m_audioSampleRate; } | ||||
| 
 | ||||
| 	void setScopeXYSink(BasebandSampleSink* scopeSink) { m_scopeXY = scopeSink; } | ||||
| 	void configureMyPosition(float myLatitude, float myLongitude); | ||||
| 
 | ||||
| 	double getMagSq() { return m_magsq; } | ||||
| 	bool getSquelchOpen() const { return m_squelchOpen; } | ||||
| 
 | ||||
| 	const DSDDecoder& getDecoder() const { return m_dsdDecoder; } | ||||
| 
 | ||||
|     void getMagSqLevels(double& avg, double& peak, int& nbSamples) | ||||
|     { | ||||
|         if (m_magsqCount > 0) | ||||
|         { | ||||
|             m_magsq = m_magsqSum / m_magsqCount; | ||||
|             m_magSqLevelStore.m_magsq = m_magsq; | ||||
|             m_magSqLevelStore.m_magsqPeak = m_magsqPeak; | ||||
|         } | ||||
| 
 | ||||
|         avg = m_magSqLevelStore.m_magsq; | ||||
|         peak = m_magSqLevelStore.m_magsqPeak; | ||||
|         nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; | ||||
| 
 | ||||
|         m_magsqSum = 0.0f; | ||||
|         m_magsqPeak = 0.0f; | ||||
|         m_magsqCount = 0; | ||||
|     } | ||||
| 
 | ||||
|     const char *updateAndGetStatusText(); | ||||
| 
 | ||||
| private: | ||||
|     struct MagSqLevelsStore | ||||
|     { | ||||
|         MagSqLevelsStore() : | ||||
|             m_magsq(1e-12), | ||||
|             m_magsqPeak(1e-12) | ||||
|         {} | ||||
|         double m_magsq; | ||||
|         double m_magsqPeak; | ||||
|     }; | ||||
| 
 | ||||
|     typedef enum | ||||
|     { | ||||
|         signalFormatNone, | ||||
|         signalFormatDMR, | ||||
|         signalFormatDStar, | ||||
|         signalFormatDPMR, | ||||
|         signalFormatYSF, | ||||
|         signalFormatNXDN | ||||
|     } SignalFormat; //!< Used for status text formatting
 | ||||
| 
 | ||||
| 	enum RateState { | ||||
| 		RSInitialFill, | ||||
| 		RSRunning | ||||
| 	}; | ||||
| 
 | ||||
|     int m_channelSampleRate; | ||||
| 	int m_channelFrequencyOffset; | ||||
| 	DSDDemodSettings m_settings; | ||||
|     quint32 m_audioSampleRate; | ||||
| 
 | ||||
| 	NCO m_nco; | ||||
| 	Interpolator m_interpolator; | ||||
| 	Real m_interpolatorDistance; | ||||
| 	Real m_interpolatorDistanceRemain; | ||||
| 	int m_sampleCount; | ||||
| 	int m_squelchCount; | ||||
| 	int m_squelchGate; | ||||
| 	double m_squelchLevel; | ||||
| 	bool m_squelchOpen; | ||||
|     DoubleBufferFIFO<Real> m_squelchDelayLine; | ||||
| 
 | ||||
|     MovingAverageUtil<Real, double, 16> m_movingAverage; | ||||
|     double m_magsq; | ||||
|     double m_magsqSum; | ||||
|     double m_magsqPeak; | ||||
|     int  m_magsqCount; | ||||
|     MagSqLevelsStore m_magSqLevelStore; | ||||
| 
 | ||||
| 	SampleVector m_scopeSampleBuffer; | ||||
| 	AudioVector m_audioBuffer; | ||||
| 	uint m_audioBufferFill; | ||||
| 	FixReal *m_sampleBuffer; //!< samples ring buffer
 | ||||
| 	int m_sampleBufferIndex; | ||||
| 	int m_scaleFromShort; | ||||
| 
 | ||||
| 	AudioFifo m_audioFifo1; | ||||
|     AudioFifo m_audioFifo2; | ||||
| 	BasebandSampleSink* m_scopeXY; | ||||
| 	bool m_scopeEnabled; | ||||
| 
 | ||||
| 	DSDDecoder m_dsdDecoder; | ||||
| 
 | ||||
| 	char m_formatStatusText[82+1]; //!< Fixed signal format dependent status text
 | ||||
|     SignalFormat m_signalFormat;   //!< Used to keep formatting during successive calls for the same standard type
 | ||||
|     PhaseDiscriminators m_phaseDiscri; | ||||
| 
 | ||||
|     void formatStatusText(); | ||||
| }; | ||||
| 
 | ||||
| #endif // INCLUDE_DSDDEMODSINK_H
 | ||||
| @ -4,14 +4,20 @@ set(nfm_SOURCES | ||||
| 	nfmdemod.cpp | ||||
|     nfmdemodsettings.cpp | ||||
|     nfmdemodwebapiadapter.cpp | ||||
| 	nfmplugin.cpp | ||||
|     nfmplugin.cpp | ||||
|     nfmdemodreport.cpp | ||||
|     nfmdemodsink.cpp | ||||
|     nfmdemodbaseband.cpp | ||||
| ) | ||||
| 
 | ||||
| set(nfm_HEADERS | ||||
| 	nfmdemod.h | ||||
|     nfmdemodsettings.h | ||||
|     nfmdemodwebapiadapter.h | ||||
| 	nfmplugin.h | ||||
|     nfmplugin.h | ||||
|     nfmdemodreport.h | ||||
|     nfmdemodsink.h | ||||
|     nfmdemodbaseband.h | ||||
| ) | ||||
| 
 | ||||
| include_directories( | ||||
|  | ||||
| @ -24,81 +24,43 @@ | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| #include <QThread> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| #include "SWGNFMDemodSettings.h" | ||||
| #include "SWGChannelReport.h" | ||||
| #include "SWGNFMDemodReport.h" | ||||
| 
 | ||||
| #include "dsp/downchannelizer.h" | ||||
| #include "util/stepfunctions.h" | ||||
| #include "util/db.h" | ||||
| #include "audio/audiooutput.h" | ||||
| #include "dsp/dspengine.h" | ||||
| #include "dsp/threadedbasebandsamplesink.h" | ||||
| #include "dsp/dspcommands.h" | ||||
| #include "dsp/devicesamplemimo.h" | ||||
| #include "device/deviceapi.h" | ||||
| #include "util/db.h" | ||||
| 
 | ||||
| #include "nfmdemod.h" | ||||
| 
 | ||||
| MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureNFMDemod, Message) | ||||
| MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureChannelizer, Message) | ||||
| MESSAGE_CLASS_DEFINITION(NFMDemod::MsgReportCTCSSFreq, Message) | ||||
| 
 | ||||
| const QString NFMDemod::m_channelIdURI = "sdrangel.channel.nfmdemod"; | ||||
| const QString NFMDemod::m_channelId = "NFMDemod"; | ||||
| 
 | ||||
| static const double afSqTones[2] = {1000.0, 6000.0}; // {1200.0, 8000.0};
 | ||||
| static const double afSqTones_lowrate[2] = {1000.0, 3500.0}; | ||||
| const int NFMDemod::m_udpBlockSize = 512; | ||||
| 
 | ||||
| NFMDemod::NFMDemod(DeviceAPI *devieAPI) : | ||||
|         ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), | ||||
|         m_deviceAPI(devieAPI), | ||||
|         m_inputSampleRate(48000), | ||||
|         m_inputFrequencyOffset(0), | ||||
|         m_running(false), | ||||
|         m_ctcssIndex(0), | ||||
|         m_sampleCount(0), | ||||
|         m_squelchCount(0), | ||||
|         m_squelchGate(4800), | ||||
|         m_squelchLevel(-990), | ||||
|         m_squelchOpen(false), | ||||
|         m_afSquelchOpen(false), | ||||
|         m_magsq(0.0f), | ||||
|         m_magsqSum(0.0f), | ||||
|         m_magsqPeak(0.0f), | ||||
|         m_magsqCount(0), | ||||
|         m_afSquelch(), | ||||
|         m_squelchDelayLine(24000), | ||||
|         m_audioFifo(48000), | ||||
|         m_settingsMutex(QMutex::Recursive) | ||||
|         m_basebandSampleRate(0) | ||||
| { | ||||
|     qDebug("NFMDemod::NFMDemod"); | ||||
| 	setObjectName(m_channelId); | ||||
| 
 | ||||
| 	m_audioBuffer.resize(1<<14); | ||||
| 	m_audioBufferFill = 0; | ||||
|     m_thread = new QThread(this); | ||||
|     m_basebandSink = new NFMDemodBaseband(); | ||||
|     m_basebandSink->moveToThread(m_thread); | ||||
| 
 | ||||
| 	m_agcLevel = 1.0; | ||||
| 
 | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); | ||||
|     m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); | ||||
|     m_discriCompensation = (m_audioSampleRate/48000.0f); | ||||
|     m_discriCompensation *= sqrt(m_discriCompensation); | ||||
| 
 | ||||
| 	m_ctcssDetector.setCoefficients(m_audioSampleRate/16, m_audioSampleRate/8.0f); // 0.5s / 2 Hz resolution
 | ||||
| 	m_afSquelch.setCoefficients(m_audioSampleRate/2000, 600, m_audioSampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
 | ||||
| 
 | ||||
|     m_ctcssLowpass.create(301, m_audioSampleRate, 250.0); | ||||
| 
 | ||||
|     applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); | ||||
| 	applySettings(m_settings, true); | ||||
| 
 | ||||
|     m_channelizer = new DownChannelizer(this); | ||||
|     m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); | ||||
|     m_deviceAPI->addChannelSink(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelSink(this); | ||||
|     m_deviceAPI->addChannelSinkAPI(this); | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
| @ -109,43 +71,10 @@ NFMDemod::~NFMDemod() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
| 	DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); | ||||
| 	m_deviceAPI->removeChannelSinkAPI(this); | ||||
|     m_deviceAPI->removeChannelSink(m_threadedChannelizer); | ||||
|     delete m_threadedChannelizer; | ||||
|     delete m_channelizer; | ||||
| } | ||||
| 
 | ||||
| float arctan2(Real y, Real x) | ||||
| { | ||||
| 	Real coeff_1 = M_PI / 4; | ||||
| 	Real coeff_2 = 3 * coeff_1; | ||||
| 	Real abs_y = fabs(y) + 1e-10;      // kludge to prevent 0/0 condition
 | ||||
| 	Real angle; | ||||
| 	if( x>= 0) { | ||||
| 		Real r = (x - abs_y) / (x + abs_y); | ||||
| 		angle = coeff_1 - coeff_1 * r; | ||||
| 	} else { | ||||
| 		Real r = (x + abs_y) / (abs_y - x); | ||||
| 		angle = coeff_2 - coeff_1 * r; | ||||
| 	} | ||||
| 	if(y < 0) { | ||||
| 		return(-angle); | ||||
| 	} else { | ||||
| 	    return(angle); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| Real angleDist(Real a, Real b) | ||||
| { | ||||
| 	Real dist = b - a; | ||||
| 
 | ||||
| 	while(dist <= M_PI) | ||||
| 		dist += 2 * M_PI; | ||||
| 	while(dist >= M_PI) | ||||
| 		dist -= 2 * M_PI; | ||||
| 
 | ||||
| 	return dist; | ||||
|     m_deviceAPI->removeChannelSink(this); | ||||
|     delete m_basebandSink; | ||||
|     delete m_thread; | ||||
| } | ||||
| 
 | ||||
| uint32_t NFMDemod::getNumberOfDeviceStreams() const | ||||
| @ -156,245 +85,31 @@ uint32_t NFMDemod::getNumberOfDeviceStreams() const | ||||
| void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) | ||||
| { | ||||
|     (void) firstOfBurst; | ||||
| 	Complex ci; | ||||
| 
 | ||||
| 	if (!m_running) { | ||||
| 	    return; | ||||
| 	} | ||||
| 
 | ||||
| 	m_settingsMutex.lock(); | ||||
| 
 | ||||
| 	for (SampleVector::const_iterator it = begin; it != end; ++it) | ||||
| 	{ | ||||
| 		Complex c(it->real(), it->imag()); | ||||
| 		c *= m_nco.nextIQ(); | ||||
| 
 | ||||
|         if (m_interpolatorDistance < 1.0f) // interpolate
 | ||||
|         { | ||||
|             while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) | ||||
|             { | ||||
|                 processOneSample(ci); | ||||
|                 m_interpolatorDistanceRemain += m_interpolatorDistance; | ||||
|             } | ||||
|         } | ||||
|         else // decimate
 | ||||
|         { | ||||
|             if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) | ||||
|             { | ||||
|                 processOneSample(ci); | ||||
|                 m_interpolatorDistanceRemain += m_interpolatorDistance; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 	m_settingsMutex.unlock(); | ||||
| } | ||||
| 
 | ||||
| void NFMDemod::processOneSample(Complex &ci) | ||||
| { | ||||
|     qint16 sample; | ||||
| 
 | ||||
|     double magsqRaw; // = ci.real()*ci.real() + c.imag()*c.imag();
 | ||||
|     Real deviation; | ||||
| 
 | ||||
|     Real demod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation); | ||||
| 
 | ||||
|     Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED); | ||||
|     m_movingAverage(magsq); | ||||
|     m_magsqSum += magsq; | ||||
| 
 | ||||
|     if (magsq > m_magsqPeak) | ||||
|     { | ||||
|         m_magsqPeak = magsq; | ||||
|     } | ||||
| 
 | ||||
|     m_magsqCount++; | ||||
|     m_sampleCount++; | ||||
| 
 | ||||
|     // AF processing
 | ||||
| 
 | ||||
|     if (m_settings.m_deltaSquelch) | ||||
|     { | ||||
|         if (m_afSquelch.analyze(demod * m_discriCompensation)) | ||||
|         { | ||||
|             m_afSquelchOpen = m_afSquelch.evaluate(); // ? m_squelchGate + m_squelchDecay : 0;
 | ||||
| 
 | ||||
|             if (!m_afSquelchOpen) { | ||||
|                 m_squelchDelayLine.zeroBack(m_audioSampleRate/10); // zero out evaluation period
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (m_afSquelchOpen) | ||||
|         { | ||||
|             m_squelchDelayLine.write(demod * m_discriCompensation); | ||||
| 
 | ||||
|             if (m_squelchCount < 2*m_squelchGate) { | ||||
|                 m_squelchCount++; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             m_squelchDelayLine.write(0); | ||||
| 
 | ||||
|             if (m_squelchCount > 0) { | ||||
|                 m_squelchCount--; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         if ((Real) m_movingAverage < m_squelchLevel) | ||||
|         { | ||||
|             m_squelchDelayLine.write(0); | ||||
| 
 | ||||
|             if (m_squelchCount > 0) { | ||||
|                 m_squelchCount--; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             m_squelchDelayLine.write(demod * m_discriCompensation); | ||||
| 
 | ||||
|             if (m_squelchCount < 2*m_squelchGate) { | ||||
|                 m_squelchCount++; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m_squelchOpen = (m_squelchCount > m_squelchGate); | ||||
| 
 | ||||
|     if (m_settings.m_audioMute) | ||||
|     { | ||||
|         sample = 0; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         if (m_squelchOpen) | ||||
|         { | ||||
|             if (m_settings.m_ctcssOn) | ||||
|             { | ||||
|                 Real ctcss_sample = m_ctcssLowpass.filter(demod * m_discriCompensation); | ||||
| 
 | ||||
|                 if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k
 | ||||
|                 { | ||||
|                     if (m_ctcssDetector.analyze(&ctcss_sample)) | ||||
|                     { | ||||
|                         int maxToneIndex; | ||||
| 
 | ||||
|                         if (m_ctcssDetector.getDetectedTone(maxToneIndex)) | ||||
|                         { | ||||
|                             if (maxToneIndex+1 != m_ctcssIndex) | ||||
|                             { | ||||
|                                 if (getMessageQueueToGUI()) { | ||||
|                                     MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(m_ctcssDetector.getToneSet()[maxToneIndex]); | ||||
|                                     getMessageQueueToGUI()->push(msg); | ||||
|                                 } | ||||
|                                 m_ctcssIndex = maxToneIndex+1; | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             if (m_ctcssIndex != 0) | ||||
|                             { | ||||
|                                 if (getMessageQueueToGUI()) { | ||||
|                                     MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0); | ||||
|                                     getMessageQueueToGUI()->push(msg); | ||||
|                                 } | ||||
|                                 m_ctcssIndex = 0; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (m_settings.m_ctcssOn && m_ctcssIndexSelected && (m_ctcssIndexSelected != m_ctcssIndex)) | ||||
|             { | ||||
|                 sample = 0; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (m_settings.m_highPass) { | ||||
|                     sample = m_bandpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume; | ||||
|                 } else { | ||||
|                     sample = m_lowpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume * 301.0f; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if (m_ctcssIndex != 0) | ||||
|             { | ||||
|                 if (getMessageQueueToGUI()) { | ||||
|                     MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0); | ||||
|                     getMessageQueueToGUI()->push(msg); | ||||
|                 } | ||||
| 
 | ||||
|                 m_ctcssIndex = 0; | ||||
|             } | ||||
| 
 | ||||
|             sample = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     m_audioBuffer[m_audioBufferFill].l = sample; | ||||
|     m_audioBuffer[m_audioBufferFill].r = sample; | ||||
|     ++m_audioBufferFill; | ||||
| 
 | ||||
|     if (m_audioBufferFill >= m_audioBuffer.size()) | ||||
|     { | ||||
|         uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); | ||||
| 
 | ||||
|         if (res != m_audioBufferFill) { | ||||
|             qDebug("NFMDemod::feed: %u/%u audio samples written", res, m_audioBufferFill); | ||||
|         } | ||||
| 
 | ||||
|         m_audioBufferFill = 0; | ||||
|     } | ||||
|     m_basebandSink->feed(begin, end); | ||||
| } | ||||
| 
 | ||||
| void NFMDemod::start() | ||||
| { | ||||
|     qDebug() << "NFMDemod::start"; | ||||
|     m_squelchCount = 0; | ||||
| 	m_audioFifo.clear(); | ||||
| 	m_phaseDiscri.reset(); | ||||
| 	applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); | ||||
| 	m_running = true; | ||||
| 
 | ||||
|     if (m_basebandSampleRate != 0) { | ||||
|         m_basebandSink->setBasebandSampleRate(m_basebandSampleRate); | ||||
|     } | ||||
| 
 | ||||
|     m_basebandSink->reset(); | ||||
|     m_thread->start(); | ||||
| } | ||||
| 
 | ||||
| void NFMDemod::stop() | ||||
| { | ||||
|     qDebug() << "NFMDemod::stop"; | ||||
|     m_running = false; | ||||
| 	m_thread->exit(); | ||||
| 	m_thread->wait(); | ||||
| } | ||||
| 
 | ||||
| bool NFMDemod::handleMessage(const Message& cmd) | ||||
| { | ||||
| 	if (DownChannelizer::MsgChannelizerNotification::match(cmd)) | ||||
| 	{ | ||||
| 		DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; | ||||
| 		qDebug() << "NFMDemod::handleMessage: DownChannelizer::MsgChannelizerNotification"; | ||||
| 
 | ||||
| 		applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); | ||||
| 
 | ||||
| 		return true; | ||||
| 	} | ||||
|     else if (MsgConfigureChannelizer::match(cmd)) | ||||
|     { | ||||
|         MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; | ||||
| 
 | ||||
|         qDebug() << "NFMDemod::handleMessage: MsgConfigureChannelizer:" | ||||
|                  << " sampleRate: " << cfg.getSampleRate() | ||||
|                  << " centerFrequency: " << cfg.getCenterFrequency(); | ||||
| 
 | ||||
|         m_channelizer->configure(m_channelizer->getInputMessageQueue(), | ||||
|             cfg.getSampleRate(), | ||||
|             cfg.getCenterFrequency()); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 	else if (MsgConfigureNFMDemod::match(cmd)) | ||||
| 	if (MsgConfigureNFMDemod::match(cmd)) | ||||
| 	{ | ||||
| 	    MsgConfigureNFMDemod& cfg = (MsgConfigureNFMDemod&) cmd; | ||||
| 		qDebug() << "NFMDemod::handleMessage: MsgConfigureNFMDemod"; | ||||
| @ -403,29 +118,15 @@ bool NFMDemod::handleMessage(const Message& cmd) | ||||
| 
 | ||||
|         return true; | ||||
| 	} | ||||
| 	else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) | ||||
| 	{ | ||||
| 	    BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; | ||||
| 	    const QThread *thread = cfg.getThread(); | ||||
| 	    qDebug("NFMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); | ||||
| 	    return true; | ||||
| 	} | ||||
|     else if (DSPConfigureAudio::match(cmd)) | ||||
|     { | ||||
|         DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; | ||||
|         uint32_t sampleRate = cfg.getSampleRate(); | ||||
| 
 | ||||
|         qDebug() << "NFMDemod::handleMessage: DSPConfigureAudio:" | ||||
|                 << " sampleRate: " << sampleRate; | ||||
| 
 | ||||
|         if (sampleRate != m_audioSampleRate) { | ||||
|             applyAudioSampleRate(sampleRate); | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 	else if (DSPSignalNotification::match(cmd)) | ||||
| 	{ | ||||
|         DSPSignalNotification& notif = (DSPSignalNotification&) cmd; | ||||
|         m_basebandSampleRate = notif.getSampleRate(); | ||||
|         // Forward to the sink
 | ||||
|         DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
 | ||||
|         qDebug() << "NFMDemod::handleMessage: DSPSignalNotification"; | ||||
|         m_basebandSink->getInputMessageQueue()->push(rep); | ||||
| 
 | ||||
| 	    return true; | ||||
| 	} | ||||
| 	else | ||||
| @ -434,70 +135,6 @@ bool NFMDemod::handleMessage(const Message& cmd) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void NFMDemod::applyAudioSampleRate(int sampleRate) | ||||
| { | ||||
|     qDebug("NFMDemod::applyAudioSampleRate: %d", sampleRate); | ||||
| 
 | ||||
|     MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( | ||||
|             sampleRate, m_settings.m_inputFrequencyOffset); | ||||
|     m_inputMessageQueue.push(channelConfigMsg); | ||||
| 
 | ||||
|     m_settingsMutex.lock(); | ||||
| 
 | ||||
|     m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f); | ||||
|     m_interpolatorDistanceRemain = 0; | ||||
|     m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; | ||||
|     m_ctcssLowpass.create(301, sampleRate, 250.0); | ||||
|     m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth); | ||||
|     m_lowpass.create(301, sampleRate, m_settings.m_afBandwidth); | ||||
|     m_squelchGate = (sampleRate / 100) * m_settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
 | ||||
|     m_squelchCount = 0; // reset squelch open counter
 | ||||
|     m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution
 | ||||
| 
 | ||||
|     if (sampleRate < 16000) { | ||||
|         m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones_lowrate); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
 | ||||
| 
 | ||||
|     } else { | ||||
|         m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
 | ||||
|     } | ||||
| 
 | ||||
|     m_discriCompensation = (sampleRate/48000.0f); | ||||
|     m_discriCompensation *= sqrt(m_discriCompensation); | ||||
| 
 | ||||
|     m_phaseDiscri.setFMScaling(sampleRate / static_cast<float>(m_settings.m_fmDeviation)); | ||||
|     m_audioFifo.setSize(sampleRate); | ||||
|     m_squelchDelayLine.resize(sampleRate/2); | ||||
| 
 | ||||
|     m_settingsMutex.unlock(); | ||||
| 
 | ||||
|     m_audioSampleRate = sampleRate; | ||||
| } | ||||
| 
 | ||||
| void NFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) | ||||
| { | ||||
|     qDebug() << "NFMDemod::applyChannelSettings:" | ||||
|             << " inputSampleRate: " << inputSampleRate | ||||
|             << " inputFrequencyOffset: " << inputFrequencyOffset; | ||||
| 
 | ||||
|     if ((inputFrequencyOffset != m_inputFrequencyOffset) || | ||||
|         (inputSampleRate != m_inputSampleRate) || force) | ||||
|     { | ||||
|         m_nco.setFreq(-inputFrequencyOffset, inputSampleRate); | ||||
|     } | ||||
| 
 | ||||
|     if ((inputSampleRate != m_inputSampleRate) || force) | ||||
|     { | ||||
|         m_settingsMutex.lock(); | ||||
|         m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.2f); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance =  (Real) inputSampleRate / (Real) m_audioSampleRate; | ||||
|         m_settingsMutex.unlock(); | ||||
|     } | ||||
| 
 | ||||
|     m_inputSampleRate = inputSampleRate; | ||||
|     m_inputFrequencyOffset = inputFrequencyOffset; | ||||
| } | ||||
| 
 | ||||
| void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) | ||||
| { | ||||
|     qDebug() << "NFMDemod::applySettings:" | ||||
| @ -542,86 +179,32 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) | ||||
|     if ((settings.m_title != m_settings.m_title) || force) { | ||||
|         reverseAPIKeys.append("title"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | ||||
|     { | ||||
|     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { | ||||
|         reverseAPIKeys.append("rfBandwidth"); | ||||
|         m_settingsMutex.lock(); | ||||
|         m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.2); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance =  (Real) m_inputSampleRate / (Real) m_audioSampleRate; | ||||
|         m_settingsMutex.unlock(); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) | ||||
|     { | ||||
|     if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) { | ||||
|         reverseAPIKeys.append("fmDeviation"); | ||||
|         m_phaseDiscri.setFMScaling((8.0f*m_audioSampleRate) / static_cast<float>(settings.m_fmDeviation)); // integrate 4x factor
 | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) | ||||
|     { | ||||
|     if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) { | ||||
|         reverseAPIKeys.append("afBandwidth"); | ||||
|         m_settingsMutex.lock(); | ||||
|         m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth); | ||||
|         m_lowpass.create(301, m_audioSampleRate, settings.m_afBandwidth); | ||||
|         m_settingsMutex.unlock(); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) | ||||
|     { | ||||
|     if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) { | ||||
|         reverseAPIKeys.append("squelchGate"); | ||||
|         m_squelchGate = (m_audioSampleRate / 100) * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
 | ||||
|         m_squelchCount = 0; // reset squelch open counter
 | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelch != m_settings.m_squelch) || force) { | ||||
|         reverseAPIKeys.append("squelch"); | ||||
|     } | ||||
|     if ((settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force) { | ||||
|         reverseAPIKeys.append("deltaSquelch"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelch != m_settings.m_squelch) || | ||||
|         (settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force) | ||||
|     { | ||||
|         if (settings.m_deltaSquelch) | ||||
|         { // input is a value in negative centis
 | ||||
|             m_squelchLevel = (- settings.m_squelch) / 100.0; | ||||
|             m_afSquelch.setThreshold(m_squelchLevel); | ||||
|             m_afSquelch.reset(); | ||||
|         } | ||||
|         else | ||||
|         { // input is a value in deci-Bels
 | ||||
|             m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); | ||||
|             m_movingAverage.reset(); | ||||
|         } | ||||
| 
 | ||||
|         m_squelchCount = 0; // reset squelch open counter
 | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) | ||||
|     { | ||||
|     if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) { | ||||
|         reverseAPIKeys.append("ctcssIndex"); | ||||
|         setSelectedCtcssIndex(settings.m_ctcssIndex); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_highPass != m_settings.m_highPass) || force) { | ||||
|         reverseAPIKeys.append("highPass"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) | ||||
|     { | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { | ||||
|         reverseAPIKeys.append("audioDeviceName"); | ||||
|         AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); | ||||
|         int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); | ||||
|         //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex);
 | ||||
|         audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); | ||||
|         uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); | ||||
| 
 | ||||
|         if (m_audioSampleRate != audioSampleRate) { | ||||
|             applyAudioSampleRate(audioSampleRate); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (m_settings.m_streamIndex != settings.m_streamIndex) | ||||
| @ -629,16 +212,18 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) | ||||
|         if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
 | ||||
|         { | ||||
|             m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex); | ||||
|             m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex); | ||||
|             m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex); | ||||
|             m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex); | ||||
|             m_deviceAPI->addChannelSink(this, settings.m_streamIndex); | ||||
|             m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex); | ||||
|             // apply stream sample rate to itself
 | ||||
|             applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset); | ||||
|         } | ||||
| 
 | ||||
|         reverseAPIKeys.append("streamIndex"); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     NFMDemodBaseband::MsgConfigureNFMDemodBaseband *msg = NFMDemodBaseband::MsgConfigureNFMDemodBaseband::create(settings, force); | ||||
|     m_basebandSink->getInputMessageQueue()->push(msg); | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
| @ -667,10 +252,6 @@ bool NFMDemod::deserialize(const QByteArray& data) | ||||
|         success = false; | ||||
|     } | ||||
| 
 | ||||
|     NFMDemod::MsgConfigureChannelizer* channelConfigMsg = NFMDemod::MsgConfigureChannelizer::create( | ||||
|             m_audioSampleRate, m_settings.m_inputFrequencyOffset); | ||||
|     m_inputMessageQueue.push(channelConfigMsg); | ||||
| 
 | ||||
|     MsgConfigureNFMDemod *msg = MsgConfigureNFMDemod::create(m_settings, true); | ||||
|     m_inputMessageQueue.push(msg); | ||||
| 
 | ||||
| @ -698,13 +279,6 @@ int NFMDemod::webapiSettingsPutPatch( | ||||
|     NFMDemodSettings settings = m_settings; | ||||
|     webapiUpdateChannelSettings(settings, channelSettingsKeys, response); | ||||
| 
 | ||||
|     if (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) | ||||
|     { | ||||
|         MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( | ||||
|                 m_audioSampleRate, settings.m_inputFrequencyOffset); | ||||
|         m_inputMessageQueue.push(channelConfigMsg); | ||||
|     } | ||||
| 
 | ||||
|     MsgConfigureNFMDemod *msg = MsgConfigureNFMDemod::create(settings, force); | ||||
|     m_inputMessageQueue.push(msg); | ||||
| 
 | ||||
| @ -849,10 +423,20 @@ void NFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response | ||||
|     getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); | ||||
| 
 | ||||
|     response.getNfmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); | ||||
|     response.getNfmDemodReport()->setCtcssTone(m_settings.m_ctcssOn ? (m_ctcssIndex ? 0 : m_ctcssDetector.getToneSet()[m_ctcssIndex-1]) : 0); | ||||
|     response.getNfmDemodReport()->setSquelch(m_squelchOpen ? 1 : 0); | ||||
|     response.getNfmDemodReport()->setAudioSampleRate(m_audioSampleRate); | ||||
|     response.getNfmDemodReport()->setChannelSampleRate(m_inputSampleRate); | ||||
|     int nbCtcssToneFrequencies; | ||||
|     const Real *ctcssToneFrequencies = m_basebandSink->getCtcssToneSet(nbCtcssToneFrequencies); | ||||
|     response.getNfmDemodReport()->setCtcssTone( | ||||
|         m_settings.m_ctcssOn ? | ||||
|             m_settings.m_ctcssIndex < 0 ? | ||||
|                 0 | ||||
|                 : m_settings.m_ctcssIndex < nbCtcssToneFrequencies ? | ||||
|                     ctcssToneFrequencies[m_settings.m_ctcssIndex-1] | ||||
|                     : 0 | ||||
|             : 0 | ||||
|     ); | ||||
|     response.getNfmDemodReport()->setSquelch(m_basebandSink->getSquelchOpen() ? 1 : 0); | ||||
|     response.getNfmDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate()); | ||||
|     response.getNfmDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate()); | ||||
| } | ||||
| 
 | ||||
| void NFMDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const NFMDemodSettings& settings, bool force) | ||||
|  | ||||
| @ -16,36 +16,24 @@ | ||||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #ifndef INCLUDE_NFMDEMOD_H | ||||
| #define INCLUDE_NFMDEMOD_H | ||||
| #ifndef INCLUDE_NFMTESTDEMOD_H | ||||
| #define INCLUDE_NFMTESTDEMOD_H | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <QNetworkRequest> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "channel/channelapi.h" | ||||
| #include "dsp/phasediscri.h" | ||||
| #include "dsp/nco.h" | ||||
| #include "dsp/interpolator.h" | ||||
| #include "dsp/lowpass.h" | ||||
| #include "dsp/bandpass.h" | ||||
| #include "dsp/afsquelch.h" | ||||
| #include "dsp/agc.h" | ||||
| #include "dsp/ctcssdetector.h" | ||||
| #include "audio/audiofifo.h" | ||||
| #include "util/message.h" | ||||
| #include "util/movingaverage.h" | ||||
| #include "util/doublebufferfifo.h" | ||||
| 
 | ||||
| #include "nfmdemodbaseband.h" | ||||
| #include "nfmdemodsettings.h" | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class QThread; | ||||
| class DeviceAPI; | ||||
| class ThreadedBasebandSampleSink; | ||||
| class DownChannelizer; | ||||
| 
 | ||||
| class NFMDemod : public BasebandSampleSink, public ChannelAPI { | ||||
|     Q_OBJECT | ||||
| @ -73,54 +61,11 @@ public: | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     class MsgConfigureChannelizer : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         int getSampleRate() const { return m_sampleRate; } | ||||
|         int getCenterFrequency() const { return m_centerFrequency; } | ||||
| 
 | ||||
|         static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) | ||||
|         { | ||||
|             return new MsgConfigureChannelizer(sampleRate, centerFrequency); | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
|         int m_sampleRate; | ||||
|         int  m_centerFrequency; | ||||
| 
 | ||||
|         MsgConfigureChannelizer(int sampleRate, int centerFrequency) : | ||||
|             Message(), | ||||
|             m_sampleRate(sampleRate), | ||||
|             m_centerFrequency(centerFrequency) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     class MsgReportCTCSSFreq : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         Real getFrequency() const { return m_freq; } | ||||
| 
 | ||||
|         static MsgReportCTCSSFreq* create(Real freq) | ||||
|         { | ||||
|             return new MsgReportCTCSSFreq(freq); | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
|         Real m_freq; | ||||
| 
 | ||||
|         MsgReportCTCSSFreq(Real freq) : | ||||
|             Message(), | ||||
|             m_freq(freq) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     NFMDemod(DeviceAPI *deviceAPI); | ||||
| 	~NFMDemod(); | ||||
| 	virtual void destroy() { delete this; } | ||||
| 
 | ||||
| 	virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); | ||||
| 	virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positive); | ||||
| 	virtual void start(); | ||||
| 	virtual void stop(); | ||||
| 	virtual bool handleMessage(const Message& cmd); | ||||
| @ -165,35 +110,11 @@ public: | ||||
|             const QStringList& channelSettingsKeys, | ||||
|             SWGSDRangel::SWGChannelSettings& response); | ||||
| 
 | ||||
| 	const Real *getCtcssToneSet(int& nbTones) const { | ||||
| 		nbTones = m_ctcssDetector.getNTones(); | ||||
| 		return m_ctcssDetector.getToneSet(); | ||||
| 	} | ||||
| 
 | ||||
| 	void setSelectedCtcssIndex(int selectedCtcssIndex) { | ||||
| 		m_ctcssIndexSelected = selectedCtcssIndex; | ||||
| 	} | ||||
| 
 | ||||
| 	Real getMag() { return m_magsq; } | ||||
| 	bool getSquelchOpen() const { return m_squelchOpen; } | ||||
| 
 | ||||
|     void getMagSqLevels(double& avg, double& peak, int& nbSamples) | ||||
|     { | ||||
|         if (m_magsqCount > 0) | ||||
|         { | ||||
|             m_magsq = m_magsqSum / m_magsqCount; | ||||
|             m_magSqLevelStore.m_magsq = m_magsq; | ||||
|             m_magSqLevelStore.m_magsqPeak = m_magsqPeak; | ||||
|         } | ||||
| 
 | ||||
|         avg = m_magSqLevelStore.m_magsq; | ||||
|         peak = m_magSqLevelStore.m_magsqPeak; | ||||
|         nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; | ||||
| 
 | ||||
|         m_magsqSum = 0.0f; | ||||
|         m_magsqPeak = 0.0f; | ||||
|         m_magsqCount = 0; | ||||
|     } | ||||
| 	const Real *getCtcssToneSet(int& nbTones) const { m_basebandSink->getCtcssToneSet(nbTones); } | ||||
| 	void setSelectedCtcssIndex(int selectedCtcssIndex) { m_basebandSink->setSelectedCtcssIndex(selectedCtcssIndex); } | ||||
| 	bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); } | ||||
|     void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_basebandSink->getMagSqLevels(avg, peak, nbSamples); } | ||||
|     void propagateMessageQueueToGUI() {  m_basebandSink->setMessageQueueToGUI(getMessageQueueToGUI()); } | ||||
| 
 | ||||
|     uint32_t getNumberOfDeviceStreams() const; | ||||
| 
 | ||||
| @ -201,82 +122,26 @@ public: | ||||
|     static const QString m_channelId; | ||||
| 
 | ||||
| private: | ||||
|     struct MagSqLevelsStore | ||||
|     { | ||||
|         MagSqLevelsStore() : | ||||
|             m_magsq(1e-12), | ||||
|             m_magsqPeak(1e-12) | ||||
|         {} | ||||
|         double m_magsq; | ||||
|         double m_magsqPeak; | ||||
|     enum RateState { | ||||
|         RSInitialFill, | ||||
|         RSRunning | ||||
|     }; | ||||
| 
 | ||||
| 	enum RateState { | ||||
| 		RSInitialFill, | ||||
| 		RSRunning | ||||
| 	}; | ||||
| 
 | ||||
|     DeviceAPI* m_deviceAPI; | ||||
|     ThreadedBasebandSampleSink* m_threadedChannelizer; | ||||
|     DownChannelizer* m_channelizer; | ||||
| 
 | ||||
|     int m_inputSampleRate; | ||||
|     int m_inputFrequencyOffset; | ||||
|     QThread *m_thread; | ||||
|     NFMDemodBaseband* m_basebandSink; | ||||
| 	NFMDemodSettings m_settings; | ||||
| 	uint32_t m_audioSampleRate; | ||||
| 	float m_discriCompensation; //!< compensation factor that depends on audio rate (1 for 48 kS/s)
 | ||||
| 	bool m_running; | ||||
| 
 | ||||
| 	NCO m_nco; | ||||
| 	Interpolator m_interpolator; | ||||
| 	Real m_interpolatorDistance; | ||||
| 	Real m_interpolatorDistanceRemain; | ||||
| 	Lowpass<Real> m_ctcssLowpass; | ||||
| 	Bandpass<Real> m_bandpass; | ||||
|     Lowpass<Real> m_lowpass; | ||||
| 	CTCSSDetector m_ctcssDetector; | ||||
| 	int m_ctcssIndex; // 0 for nothing detected
 | ||||
| 	int m_ctcssIndexSelected; | ||||
| 	int m_sampleCount; | ||||
| 	int m_squelchCount; | ||||
| 	int m_squelchGate; | ||||
| 
 | ||||
| 	Real m_squelchLevel; | ||||
| 	bool m_squelchOpen; | ||||
| 	bool m_afSquelchOpen; | ||||
| 	double m_magsq; //!< displayed averaged value
 | ||||
| 	double m_magsqSum; | ||||
| 	double m_magsqPeak; | ||||
|     int  m_magsqCount; | ||||
|     MagSqLevelsStore m_magSqLevelStore; | ||||
| 
 | ||||
| 	MovingAverageUtil<Real, double, 32> m_movingAverage; | ||||
| 	AFSquelch m_afSquelch; | ||||
| 	Real m_agcLevel; // AGC will aim to  this level
 | ||||
| 	DoubleBufferFIFO<Real> m_squelchDelayLine; | ||||
| 
 | ||||
| 	AudioVector m_audioBuffer; | ||||
| 	uint m_audioBufferFill; | ||||
| 	AudioFifo m_audioFifo; | ||||
| 
 | ||||
| 	QMutex m_settingsMutex; | ||||
| 
 | ||||
|     PhaseDiscriminators m_phaseDiscri; | ||||
|     int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
 | ||||
| 
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
|     static const int m_udpBlockSize; | ||||
| 
 | ||||
| //    void apply(bool force = false);
 | ||||
|     void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); | ||||
|     void applySettings(const NFMDemodSettings& settings, bool force = false); | ||||
|     void applyAudioSampleRate(int sampleRate); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const NFMDemodSettings& settings, bool force); | ||||
| 
 | ||||
|     void processOneSample(Complex &ci); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										170
									
								
								plugins/channelrx/demodnfm/nfmdemodbaseband.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								plugins/channelrx/demodnfm/nfmdemodbaseband.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,170 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 <QDebug> | ||||
| 
 | ||||
| #include "dsp/dspengine.h" | ||||
| #include "dsp/dspcommands.h" | ||||
| #include "dsp/downsamplechannelizer.h" | ||||
| 
 | ||||
| #include "nfmdemodbaseband.h" | ||||
| 
 | ||||
| MESSAGE_CLASS_DEFINITION(NFMDemodBaseband::MsgConfigureNFMDemodBaseband, Message) | ||||
| 
 | ||||
| NFMDemodBaseband::NFMDemodBaseband() : | ||||
|     m_mutex(QMutex::Recursive) | ||||
| { | ||||
|     m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); | ||||
|     m_channelizer = new DownSampleChannelizer(&m_sink); | ||||
| 
 | ||||
|     qDebug("NFMDemodBaseband::NFMDemodBaseband"); | ||||
|     QObject::connect( | ||||
|         &m_sampleFifo, | ||||
|         &SampleSinkFifo::dataReady, | ||||
|         this, | ||||
|         &NFMDemodBaseband::handleData, | ||||
|         Qt::QueuedConnection | ||||
|     ); | ||||
| 
 | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue()); | ||||
|     m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate()); | ||||
| 
 | ||||
|     connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); | ||||
| } | ||||
| 
 | ||||
| NFMDemodBaseband::~NFMDemodBaseband() | ||||
| { | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo()); | ||||
|     delete m_channelizer; | ||||
| } | ||||
| 
 | ||||
| void NFMDemodBaseband::reset() | ||||
| { | ||||
|     QMutexLocker mutexLocker(&m_mutex); | ||||
|     m_sampleFifo.reset(); | ||||
| } | ||||
| 
 | ||||
| void NFMDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) | ||||
| { | ||||
|     m_sampleFifo.write(begin, end); | ||||
| } | ||||
| 
 | ||||
| void NFMDemodBaseband::handleData() | ||||
| { | ||||
|     while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) | ||||
|     { | ||||
| 		SampleVector::iterator part1begin; | ||||
| 		SampleVector::iterator part1end; | ||||
| 		SampleVector::iterator part2begin; | ||||
| 		SampleVector::iterator part2end; | ||||
| 
 | ||||
|         std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); | ||||
| 
 | ||||
| 		// first part of FIFO data
 | ||||
|         if (part1begin != part1end) { | ||||
|             m_channelizer->feed(part1begin, part1end); | ||||
|         } | ||||
| 
 | ||||
| 		// second part of FIFO data (used when block wraps around)
 | ||||
| 		if(part2begin != part2end) { | ||||
|             m_channelizer->feed(part2begin, part2end); | ||||
|         } | ||||
| 
 | ||||
| 		m_sampleFifo.readCommit((unsigned int) count); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void NFMDemodBaseband::handleInputMessages() | ||||
| { | ||||
| 	Message* message; | ||||
| 
 | ||||
| 	while ((message = m_inputMessageQueue.pop()) != nullptr) | ||||
| 	{ | ||||
| 		if (handleMessage(*message)) { | ||||
| 			delete message; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bool NFMDemodBaseband::handleMessage(const Message& cmd) | ||||
| { | ||||
|     if (MsgConfigureNFMDemodBaseband::match(cmd)) | ||||
|     { | ||||
|         QMutexLocker mutexLocker(&m_mutex); | ||||
|         MsgConfigureNFMDemodBaseband& cfg = (MsgConfigureNFMDemodBaseband&) cmd; | ||||
|         qDebug() << "NFMDemodBaseband::handleMessage: MsgConfigureNFMDemodBaseband"; | ||||
| 
 | ||||
|         applySettings(cfg.getSettings(), cfg.getForce()); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     else if (DSPSignalNotification::match(cmd)) | ||||
|     { | ||||
|         QMutexLocker mutexLocker(&m_mutex); | ||||
|         DSPSignalNotification& notif = (DSPSignalNotification&) cmd; | ||||
|         qDebug() << "NFMDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); | ||||
|         m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); | ||||
|         m_channelizer->setBasebandSampleRate(notif.getSampleRate()); | ||||
|         m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); | ||||
| 
 | ||||
| 		return true; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void NFMDemodBaseband::applySettings(const NFMDemodSettings& settings, bool force) | ||||
| { | ||||
|     if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) | ||||
|     { | ||||
|         m_channelizer->setChannelization(m_sink.getAudioSampleRate(), settings.m_inputFrequencyOffset); | ||||
|         m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) | ||||
|     { | ||||
|         AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); | ||||
|         int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); | ||||
|         //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex);
 | ||||
|         audioDeviceManager->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue(), audioDeviceIndex); | ||||
|         uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); | ||||
| 
 | ||||
|         if (m_sink.getAudioSampleRate() != audioSampleRate) | ||||
|         { | ||||
|             m_sink.applyAudioSampleRate(audioSampleRate); | ||||
|             m_channelizer->setChannelization(audioSampleRate, settings.m_inputFrequencyOffset); | ||||
|             m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m_sink.applySettings(settings, force); | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
| int NFMDemodBaseband::getChannelSampleRate() const | ||||
| { | ||||
|     return m_channelizer->getChannelSampleRate(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NFMDemodBaseband::setBasebandSampleRate(int sampleRate) | ||||
| { | ||||
|     m_channelizer->setBasebandSampleRate(sampleRate); | ||||
| } | ||||
							
								
								
									
										89
									
								
								plugins/channelrx/demodnfm/nfmdemodbaseband.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								plugins/channelrx/demodnfm/nfmdemodbaseband.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 INCLUDE_NFMDEMODBASEBAND_H | ||||
| #define INCLUDE_NFMDEMODBASEBAND_H | ||||
| 
 | ||||
| #include <QObject> | ||||
| #include <QMutex> | ||||
| 
 | ||||
| #include "dsp/samplesinkfifo.h" | ||||
| #include "util/message.h" | ||||
| #include "util/messagequeue.h" | ||||
| 
 | ||||
| #include "nfmdemodsink.h" | ||||
| 
 | ||||
| class DownSampleChannelizer; | ||||
| 
 | ||||
| class NFMDemodBaseband : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     class MsgConfigureNFMDemodBaseband : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         const NFMDemodSettings& getSettings() const { return m_settings; } | ||||
|         bool getForce() const { return m_force; } | ||||
| 
 | ||||
|         static MsgConfigureNFMDemodBaseband* create(const NFMDemodSettings& settings, bool force) | ||||
|         { | ||||
|             return new MsgConfigureNFMDemodBaseband(settings, force); | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
|         NFMDemodSettings m_settings; | ||||
|         bool m_force; | ||||
| 
 | ||||
|         MsgConfigureNFMDemodBaseband(const NFMDemodSettings& settings, bool force) : | ||||
|             Message(), | ||||
|             m_settings(settings), | ||||
|             m_force(force) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     NFMDemodBaseband(); | ||||
|     ~NFMDemodBaseband(); | ||||
|     void reset(); | ||||
|     void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); | ||||
|     MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
 | ||||
|     int getChannelSampleRate() const; | ||||
|     void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); } | ||||
|     void setSelectedCtcssIndex(int selectedCtcssIndex) { m_sink.setSelectedCtcssIndex(selectedCtcssIndex); } | ||||
|     bool getSquelchOpen() const { return m_sink.getSquelchOpen(); } | ||||
|     const Real *getCtcssToneSet(int& nbTones) const { return m_sink.getCtcssToneSet(nbTones); } | ||||
|     void setMessageQueueToGUI(MessageQueue *messageQueue) { m_sink.setMessageQueueToGUI(messageQueue); } | ||||
|     unsigned int getAudioSampleRate() const { return m_sink.getAudioSampleRate(); } | ||||
|     void setBasebandSampleRate(int sampleRate); | ||||
| 
 | ||||
| private: | ||||
|     SampleSinkFifo m_sampleFifo; | ||||
|     DownSampleChannelizer *m_channelizer; | ||||
|     NFMDemodSink m_sink; | ||||
| 	MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
 | ||||
|     NFMDemodSettings m_settings; | ||||
|     QMutex m_mutex; | ||||
| 
 | ||||
|     bool handleMessage(const Message& cmd); | ||||
|     void applySettings(const NFMDemodSettings& settings, bool force = false); | ||||
| 
 | ||||
| private slots: | ||||
|     void handleInputMessages(); | ||||
|     void handleData(); //!< Handle data when samples have to be processed
 | ||||
| }; | ||||
| 
 | ||||
| #endif // INCLUDE_NFMDEMODBASEBAND_H
 | ||||
| @ -1,5 +1,3 @@ | ||||
| #include "nfmdemodgui.h" | ||||
| 
 | ||||
| #include "device/deviceuiset.h" | ||||
| #include <QDockWidget> | ||||
| #include <QMainWindow> | ||||
| @ -15,7 +13,10 @@ | ||||
| #include "gui/audioselectdialog.h" | ||||
| #include "dsp/dspengine.h" | ||||
| #include "mainwindow.h" | ||||
| 
 | ||||
| #include "nfmdemodreport.h" | ||||
| #include "nfmdemod.h" | ||||
| #include "nfmdemodgui.h" | ||||
| 
 | ||||
| NFMDemodGUI* NFMDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) | ||||
| { | ||||
| @ -75,12 +76,10 @@ bool NFMDemodGUI::deserialize(const QByteArray& data) | ||||
| 
 | ||||
| bool NFMDemodGUI::handleMessage(const Message& message) | ||||
| { | ||||
|     if (NFMDemod::MsgReportCTCSSFreq::match(message)) | ||||
|     if (NFMDemodReport::MsgReportCTCSSFreq::match(message)) | ||||
|     { | ||||
|         //qDebug("NFMDemodGUI::handleMessage: NFMDemod::MsgReportCTCSSFreq");
 | ||||
|         NFMDemod::MsgReportCTCSSFreq& report = (NFMDemod::MsgReportCTCSSFreq&) message; | ||||
|         NFMDemodReport::MsgReportCTCSSFreq& report = (NFMDemodReport::MsgReportCTCSSFreq&) message; | ||||
|         setCtcssFreq(report.getFrequency()); | ||||
|         //qDebug("NFMDemodGUI::handleMessage: MsgReportCTCSSFreq: %f", report.getFrequency());
 | ||||
|         return true; | ||||
|     } | ||||
|     else if (NFMDemod::MsgConfigureNFMDemod::match(message)) | ||||
| @ -366,10 +365,6 @@ void NFMDemodGUI::applySettings(bool force) | ||||
| 	{ | ||||
| 		qDebug() << "NFMDemodGUI::applySettings"; | ||||
| 
 | ||||
|         NFMDemod::MsgConfigureChannelizer* channelConfigMsg = NFMDemod::MsgConfigureChannelizer::create( | ||||
|                 48000, m_channelMarker.getCenterFrequency()); | ||||
|         m_nfmDemod->getInputMessageQueue()->push(channelConfigMsg); | ||||
| 
 | ||||
|         NFMDemod::MsgConfigureNFMDemod* message = NFMDemod::MsgConfigureNFMDemod::create( m_settings, force); | ||||
|         m_nfmDemod->getInputMessageQueue()->push(message); | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										26
									
								
								plugins/channelrx/demodnfm/nfmdemodreport.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								plugins/channelrx/demodnfm/nfmdemodreport.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 "nfmdemodreport.h" | ||||
| 
 | ||||
| MESSAGE_CLASS_DEFINITION(NFMDemodReport::MsgReportCTCSSFreq, Message) | ||||
| 
 | ||||
| NFMDemodReport::NFMDemodReport() | ||||
| { } | ||||
| 
 | ||||
| NFMDemodReport::~NFMDemodReport() | ||||
| { } | ||||
							
								
								
									
										55
									
								
								plugins/channelrx/demodnfm/nfmdemodreport.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								plugins/channelrx/demodnfm/nfmdemodreport.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 PLUGINS_CHANNELRX_DEMONFM_NFMDEMODREPORT_H_ | ||||
| #define PLUGINS_CHANNELRX_DEMONFM_NFMDEMODREPORT_H_ | ||||
| 
 | ||||
| #include <QObject> | ||||
| 
 | ||||
| #include "dsp/dsptypes.h" | ||||
| #include "util/message.h" | ||||
| 
 | ||||
| class NFMDemodReport : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     class MsgReportCTCSSFreq : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         Real getFrequency() const { return m_freq; } | ||||
| 
 | ||||
|         static MsgReportCTCSSFreq* create(Real freq) | ||||
|         { | ||||
|             return new MsgReportCTCSSFreq(freq); | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
|         Real m_freq; | ||||
| 
 | ||||
|         MsgReportCTCSSFreq(Real freq) : | ||||
|             Message(), | ||||
|             m_freq(freq) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
| public: | ||||
|     NFMDemodReport(); | ||||
|     ~NFMDemodReport(); | ||||
| }; | ||||
| 
 | ||||
| #endif // PLUGINS_CHANNELRX_DEMONFM_NFMDEMODREPORT_H_
 | ||||
							
								
								
									
										385
									
								
								plugins/channelrx/demodnfm/nfmdemodsink.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										385
									
								
								plugins/channelrx/demodnfm/nfmdemodsink.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,385 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 <stdio.h> | ||||
| #include <complex.h> | ||||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| 
 | ||||
| #include "util/stepfunctions.h" | ||||
| #include "util/db.h" | ||||
| #include "audio/audiooutput.h" | ||||
| #include "dsp/dspengine.h" | ||||
| #include "dsp/dspcommands.h" | ||||
| #include "dsp/devicesamplemimo.h" | ||||
| #include "device/deviceapi.h" | ||||
| 
 | ||||
| #include "nfmdemodreport.h" | ||||
| #include "nfmdemodsink.h" | ||||
| 
 | ||||
| const double NFMDemodSink::afSqTones[] = {1000.0, 6000.0}; // {1200.0, 8000.0};
 | ||||
| const double NFMDemodSink::afSqTones_lowrate[] = {1000.0, 3500.0}; | ||||
| 
 | ||||
| NFMDemodSink::NFMDemodSink() : | ||||
|         m_channelSampleRate(48000), | ||||
|         m_channelFrequencyOffset(0), | ||||
|         m_audioSampleRate(48000), | ||||
|         m_audioBufferFill(0), | ||||
|         m_audioFifo(48000), | ||||
|         m_ctcssIndex(0), | ||||
|         m_sampleCount(0), | ||||
|         m_squelchCount(0), | ||||
|         m_squelchGate(4800), | ||||
|         m_squelchLevel(-990), | ||||
|         m_squelchOpen(false), | ||||
|         m_afSquelchOpen(false), | ||||
|         m_magsq(0.0f), | ||||
|         m_magsqSum(0.0f), | ||||
|         m_magsqPeak(0.0f), | ||||
|         m_magsqCount(0), | ||||
|         m_afSquelch(), | ||||
|         m_squelchDelayLine(24000), | ||||
|         m_messageQueueToGUI(nullptr) | ||||
| { | ||||
| 	m_agcLevel = 1.0; | ||||
|     m_audioBuffer.resize(1<<14); | ||||
| 
 | ||||
| 	applySettings(m_settings, true); | ||||
|     applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); | ||||
| } | ||||
| 
 | ||||
| NFMDemodSink::~NFMDemodSink() | ||||
| { | ||||
| } | ||||
| 
 | ||||
| void NFMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) | ||||
| { | ||||
| 	Complex ci; | ||||
| 
 | ||||
| 	for (SampleVector::const_iterator it = begin; it != end; ++it) | ||||
| 	{ | ||||
| 		Complex c(it->real(), it->imag()); | ||||
| 		c *= m_nco.nextIQ(); | ||||
| 
 | ||||
|         if (m_interpolatorDistance < 1.0f) // interpolate
 | ||||
|         { | ||||
|             while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) | ||||
|             { | ||||
|                 processOneSample(ci); | ||||
|                 m_interpolatorDistanceRemain += m_interpolatorDistance; | ||||
|             } | ||||
|         } | ||||
|         else // decimate
 | ||||
|         { | ||||
|             if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) | ||||
|             { | ||||
|                 processOneSample(ci); | ||||
|                 m_interpolatorDistanceRemain += m_interpolatorDistance; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void NFMDemodSink::processOneSample(Complex &ci) | ||||
| { | ||||
|     qint16 sample; | ||||
| 
 | ||||
|     double magsqRaw; // = ci.real()*ci.real() + c.imag()*c.imag();
 | ||||
|     Real deviation; | ||||
| 
 | ||||
|     Real demod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation); | ||||
| 
 | ||||
|     Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED); | ||||
|     m_movingAverage(magsq); | ||||
|     m_magsqSum += magsq; | ||||
| 
 | ||||
|     if (magsq > m_magsqPeak) | ||||
|     { | ||||
|         m_magsqPeak = magsq; | ||||
|     } | ||||
| 
 | ||||
|     m_magsqCount++; | ||||
|     m_sampleCount++; | ||||
| 
 | ||||
|     // AF processing
 | ||||
| 
 | ||||
|     if (m_settings.m_deltaSquelch) | ||||
|     { | ||||
|         if (m_afSquelch.analyze(demod * m_discriCompensation)) | ||||
|         { | ||||
|             m_afSquelchOpen = m_afSquelch.evaluate(); // ? m_squelchGate + m_squelchDecay : 0;
 | ||||
| 
 | ||||
|             if (!m_afSquelchOpen) { | ||||
|                 m_squelchDelayLine.zeroBack(m_audioSampleRate/10); // zero out evaluation period
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (m_afSquelchOpen) | ||||
|         { | ||||
|             m_squelchDelayLine.write(demod * m_discriCompensation); | ||||
| 
 | ||||
|             if (m_squelchCount < 2*m_squelchGate) { | ||||
|                 m_squelchCount++; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             m_squelchDelayLine.write(0); | ||||
| 
 | ||||
|             if (m_squelchCount > 0) { | ||||
|                 m_squelchCount--; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         if ((Real) m_movingAverage < m_squelchLevel) | ||||
|         { | ||||
|             m_squelchDelayLine.write(0); | ||||
| 
 | ||||
|             if (m_squelchCount > 0) { | ||||
|                 m_squelchCount--; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             m_squelchDelayLine.write(demod * m_discriCompensation); | ||||
| 
 | ||||
|             if (m_squelchCount < 2*m_squelchGate) { | ||||
|                 m_squelchCount++; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m_squelchOpen = (m_squelchCount > m_squelchGate); | ||||
| 
 | ||||
|     if (m_settings.m_audioMute) | ||||
|     { | ||||
|         sample = 0; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         if (m_squelchOpen) | ||||
|         { | ||||
|             if (m_settings.m_ctcssOn) | ||||
|             { | ||||
|                 Real ctcss_sample = m_ctcssLowpass.filter(demod * m_discriCompensation); | ||||
| 
 | ||||
|                 if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k
 | ||||
|                 { | ||||
|                     if (m_ctcssDetector.analyze(&ctcss_sample)) | ||||
|                     { | ||||
|                         int maxToneIndex; | ||||
| 
 | ||||
|                         if (m_ctcssDetector.getDetectedTone(maxToneIndex)) | ||||
|                         { | ||||
|                             if (maxToneIndex+1 != m_ctcssIndex) | ||||
|                             { | ||||
|                                 if (getMessageQueueToGUI()) | ||||
|                                 { | ||||
|                                     NFMDemodReport::MsgReportCTCSSFreq *msg = NFMDemodReport::MsgReportCTCSSFreq::create(m_ctcssDetector.getToneSet()[maxToneIndex]); | ||||
|                                     getMessageQueueToGUI()->push(msg); | ||||
|                                 } | ||||
| 
 | ||||
|                                 m_ctcssIndex = maxToneIndex+1; | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             if (m_ctcssIndex != 0) | ||||
|                             { | ||||
|                                 if (getMessageQueueToGUI()) | ||||
|                                 { | ||||
|                                     NFMDemodReport::MsgReportCTCSSFreq *msg = NFMDemodReport::MsgReportCTCSSFreq::create(0); | ||||
|                                     getMessageQueueToGUI()->push(msg); | ||||
|                                 } | ||||
| 
 | ||||
|                                 m_ctcssIndex = 0; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (m_settings.m_ctcssOn && m_ctcssIndexSelected && (m_ctcssIndexSelected != m_ctcssIndex)) | ||||
|             { | ||||
|                 sample = 0; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (m_settings.m_highPass) { | ||||
|                     sample = m_bandpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume; | ||||
|                 } else { | ||||
|                     sample = m_lowpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume * 301.0f; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if (m_ctcssIndex != 0) | ||||
|             { | ||||
|                 if (getMessageQueueToGUI()) | ||||
|                 { | ||||
|                     NFMDemodReport::MsgReportCTCSSFreq *msg = NFMDemodReport::MsgReportCTCSSFreq::create(0); | ||||
|                     getMessageQueueToGUI()->push(msg); | ||||
|                 } | ||||
| 
 | ||||
|                 m_ctcssIndex = 0; | ||||
|             } | ||||
| 
 | ||||
|             sample = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m_audioBuffer[m_audioBufferFill].l = sample; | ||||
|     m_audioBuffer[m_audioBufferFill].r = sample; | ||||
|     ++m_audioBufferFill; | ||||
| 
 | ||||
|     if (m_audioBufferFill >= m_audioBuffer.size()) | ||||
|     { | ||||
|         uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); | ||||
| 
 | ||||
|         if (res != m_audioBufferFill) | ||||
|         { | ||||
|             qDebug("NFMDemodSink::feed: %u/%u audio samples written", res, m_audioBufferFill); | ||||
|             qDebug("NFMDemodSink::feed: m_audioSampleRate: %u m_channelSampleRate: %d", m_audioSampleRate, m_channelSampleRate); | ||||
|         } | ||||
| 
 | ||||
|         m_audioBufferFill = 0; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NFMDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) | ||||
| { | ||||
|     qDebug() << "NFMDemodSink::applyChannelSettings:" | ||||
|             << " channelSampleRate: " << channelSampleRate | ||||
|             << " channelFrequencyOffset: " << channelFrequencyOffset; | ||||
| 
 | ||||
|     if ((channelFrequencyOffset != m_channelFrequencyOffset) || | ||||
|         (channelSampleRate != m_channelSampleRate) || force) | ||||
|     { | ||||
|         m_nco.setFreq(-channelFrequencyOffset, channelSampleRate); | ||||
|     } | ||||
| 
 | ||||
|     if ((channelSampleRate != m_channelSampleRate) || force) | ||||
|     { | ||||
|         m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance =  (Real) channelSampleRate / (Real) m_audioSampleRate; | ||||
|     } | ||||
| 
 | ||||
|     m_channelSampleRate = channelSampleRate; | ||||
|     m_channelFrequencyOffset = channelFrequencyOffset; | ||||
| } | ||||
| 
 | ||||
| void NFMDemodSink::applySettings(const NFMDemodSettings& settings, bool force) | ||||
| { | ||||
|     qDebug() << "NFMDemodSink::applySettings:" | ||||
|             << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset | ||||
|             << " m_rfBandwidth: " << settings.m_rfBandwidth | ||||
|             << " m_afBandwidth: " << settings.m_afBandwidth | ||||
|             << " m_fmDeviation: " << settings.m_fmDeviation | ||||
|             << " m_volume: " << settings.m_volume | ||||
|             << " m_squelchGate: " << settings.m_squelchGate | ||||
|             << " m_deltaSquelch: " << settings.m_deltaSquelch | ||||
|             << " m_squelch: " << settings.m_squelch | ||||
|             << " m_ctcssIndex: " << settings.m_ctcssIndex | ||||
|             << " m_ctcssOn: " << settings.m_ctcssOn | ||||
|             << " m_highPass: " << settings.m_highPass | ||||
|             << " m_audioMute: " << settings.m_audioMute | ||||
|             << " m_audioDeviceName: " << settings.m_audioDeviceName | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | ||||
|     { | ||||
|         m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance =  (Real) m_channelSampleRate / (Real) m_audioSampleRate; | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) | ||||
|     { | ||||
|         m_phaseDiscri.setFMScaling((8.0f*m_audioSampleRate) / static_cast<float>(settings.m_fmDeviation)); // integrate 4x factor
 | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) | ||||
|     { | ||||
|         m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth); | ||||
|         m_lowpass.create(301, m_audioSampleRate, settings.m_afBandwidth); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) | ||||
|     { | ||||
|         m_squelchGate = (m_audioSampleRate / 100) * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
 | ||||
|         m_squelchCount = 0; // reset squelch open counter
 | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelch != m_settings.m_squelch) || | ||||
|         (settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force) | ||||
|     { | ||||
|         if (settings.m_deltaSquelch) | ||||
|         { // input is a value in negative centis
 | ||||
|             m_squelchLevel = (- settings.m_squelch) / 100.0; | ||||
|             m_afSquelch.setThreshold(m_squelchLevel); | ||||
|             m_afSquelch.reset(); | ||||
|         } | ||||
|         else | ||||
|         { // input is a value in deci-Bels
 | ||||
|             m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); | ||||
|             m_movingAverage.reset(); | ||||
|         } | ||||
| 
 | ||||
|         m_squelchCount = 0; // reset squelch open counter
 | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) { | ||||
|         setSelectedCtcssIndex(settings.m_ctcssIndex); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
| void NFMDemodSink::applyAudioSampleRate(unsigned int sampleRate) | ||||
| { | ||||
|     qDebug("NFMDemodSink::applyAudioSampleRate: %u m_channelSampleRate: %d", sampleRate, m_channelSampleRate); | ||||
| 
 | ||||
|     m_ctcssLowpass.create(301, sampleRate, 250.0); | ||||
|     m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth); | ||||
|     m_lowpass.create(301, sampleRate, m_settings.m_afBandwidth); | ||||
|     m_squelchGate = (sampleRate / 100) * m_settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
 | ||||
|     m_squelchCount = 0; // reset squelch open counter
 | ||||
|     m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution
 | ||||
| 
 | ||||
|     if (sampleRate < 16000) { | ||||
|         m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones_lowrate); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
 | ||||
| 
 | ||||
|     } else { | ||||
|         m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
 | ||||
|     } | ||||
| 
 | ||||
|     m_discriCompensation = (sampleRate/48000.0f); | ||||
|     m_discriCompensation *= sqrt(m_discriCompensation); | ||||
| 
 | ||||
|     m_phaseDiscri.setFMScaling(sampleRate / static_cast<float>(m_settings.m_fmDeviation)); | ||||
|     m_audioFifo.setSize(sampleRate); | ||||
|     m_squelchDelayLine.resize(sampleRate/2); | ||||
| 
 | ||||
|     m_audioSampleRate = sampleRate; | ||||
| } | ||||
							
								
								
									
										180
									
								
								plugins/channelrx/demodnfm/nfmdemodsink.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								plugins/channelrx/demodnfm/nfmdemodsink.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,180 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 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 INCLUDE_NFMDEMODSINK_H | ||||
| #define INCLUDE_NFMDEMODSINK_H | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "dsp/channelsamplesink.h" | ||||
| #include "dsp/phasediscri.h" | ||||
| #include "dsp/nco.h" | ||||
| #include "dsp/interpolator.h" | ||||
| #include "dsp/lowpass.h" | ||||
| #include "dsp/bandpass.h" | ||||
| #include "dsp/afsquelch.h" | ||||
| #include "dsp/agc.h" | ||||
| #include "dsp/ctcssdetector.h" | ||||
| #include "util/movingaverage.h" | ||||
| #include "util/doublebufferfifo.h" | ||||
| #include "audio/audiofifo.h" | ||||
| 
 | ||||
| #include "nfmdemodsettings.h" | ||||
| 
 | ||||
| class NFMDemodSink : public ChannelSampleSink { | ||||
| public: | ||||
|     NFMDemodSink(); | ||||
| 	~NFMDemodSink(); | ||||
| 
 | ||||
| 	virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); | ||||
| 
 | ||||
| 	const Real *getCtcssToneSet(int& nbTones) const { | ||||
| 		nbTones = m_ctcssDetector.getNTones(); | ||||
| 		return m_ctcssDetector.getToneSet(); | ||||
| 	} | ||||
| 
 | ||||
| 	void setSelectedCtcssIndex(int selectedCtcssIndex) { | ||||
| 		m_ctcssIndexSelected = selectedCtcssIndex; | ||||
| 	} | ||||
| 
 | ||||
| 	bool getSquelchOpen() const { return m_squelchOpen; } | ||||
| 
 | ||||
|     void getMagSqLevels(double& avg, double& peak, int& nbSamples) | ||||
|     { | ||||
|         if (m_magsqCount > 0) | ||||
|         { | ||||
|             m_magsq = m_magsqSum / m_magsqCount; | ||||
|             m_magSqLevelStore.m_magsq = m_magsq; | ||||
|             m_magSqLevelStore.m_magsqPeak = m_magsqPeak; | ||||
|         } | ||||
| 
 | ||||
|         avg = m_magSqLevelStore.m_magsq; | ||||
|         peak = m_magSqLevelStore.m_magsqPeak; | ||||
|         nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; | ||||
| 
 | ||||
|         m_magsqSum = 0.0f; | ||||
|         m_magsqPeak = 0.0f; | ||||
|         m_magsqCount = 0; | ||||
|     } | ||||
| 
 | ||||
|     void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false); | ||||
|     void applySettings(const NFMDemodSettings& settings, bool force = false); | ||||
|     void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; } | ||||
| 
 | ||||
|     AudioFifo *getAudioFifo() { return &m_audioFifo; } | ||||
|     void applyAudioSampleRate(unsigned int sampleRate); | ||||
|     unsigned int getAudioSampleRate() const { return m_audioSampleRate; } | ||||
| 
 | ||||
| private: | ||||
|     struct MagSqLevelsStore | ||||
|     { | ||||
|         MagSqLevelsStore() : | ||||
|             m_magsq(1e-12), | ||||
|             m_magsqPeak(1e-12) | ||||
|         {} | ||||
|         double m_magsq; | ||||
|         double m_magsqPeak; | ||||
|     }; | ||||
| 
 | ||||
| 	enum RateState { | ||||
| 		RSInitialFill, | ||||
| 		RSRunning | ||||
| 	}; | ||||
| 
 | ||||
|     int m_channelSampleRate; | ||||
|     int m_channelFrequencyOffset; | ||||
| 	NFMDemodSettings m_settings; | ||||
| 
 | ||||
|     quint32 m_audioSampleRate; | ||||
|     AudioVector m_audioBuffer; | ||||
|     uint m_audioBufferFill; | ||||
|     AudioFifo m_audioFifo; | ||||
| 
 | ||||
| 	float m_discriCompensation; //!< compensation factor that depends on audio rate (1 for 48 kS/s)
 | ||||
| 
 | ||||
| 	NCO m_nco; | ||||
| 	Interpolator m_interpolator; | ||||
| 	Real m_interpolatorDistance; | ||||
| 	Real m_interpolatorDistanceRemain; | ||||
| 	Lowpass<Real> m_ctcssLowpass; | ||||
| 	Bandpass<Real> m_bandpass; | ||||
|     Lowpass<Real> m_lowpass; | ||||
| 	CTCSSDetector m_ctcssDetector; | ||||
| 	int m_ctcssIndex; // 0 for nothing detected
 | ||||
| 	int m_ctcssIndexSelected; | ||||
| 	int m_sampleCount; | ||||
| 	int m_squelchCount; | ||||
| 	int m_squelchGate; | ||||
| 
 | ||||
| 	Real m_squelchLevel; | ||||
| 	bool m_squelchOpen; | ||||
| 	bool m_afSquelchOpen; | ||||
| 	double m_magsq; //!< displayed averaged value
 | ||||
| 	double m_magsqSum; | ||||
| 	double m_magsqPeak; | ||||
|     int  m_magsqCount; | ||||
|     MagSqLevelsStore m_magSqLevelStore; | ||||
| 
 | ||||
| 	MovingAverageUtil<Real, double, 32> m_movingAverage; | ||||
| 	AFSquelch m_afSquelch; | ||||
| 	Real m_agcLevel; // AGC will aim to  this level
 | ||||
| 	DoubleBufferFIFO<Real> m_squelchDelayLine; | ||||
| 
 | ||||
|     PhaseDiscriminators m_phaseDiscri; | ||||
|     MessageQueue *m_messageQueueToGUI; | ||||
| 
 | ||||
|     static const double afSqTones[]; | ||||
|     static const double afSqTones_lowrate[]; | ||||
| 
 | ||||
|     void processOneSample(Complex &ci); | ||||
|     MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; } | ||||
| 
 | ||||
|     inline float arctan2(Real y, Real x) | ||||
|     { | ||||
|         Real coeff_1 = M_PI / 4; | ||||
|         Real coeff_2 = 3 * coeff_1; | ||||
|         Real abs_y = fabs(y) + 1e-10;      // kludge to prevent 0/0 condition
 | ||||
|         Real angle; | ||||
|         if( x>= 0) { | ||||
|             Real r = (x - abs_y) / (x + abs_y); | ||||
|             angle = coeff_1 - coeff_1 * r; | ||||
|         } else { | ||||
|             Real r = (x + abs_y) / (abs_y - x); | ||||
|             angle = coeff_2 - coeff_1 * r; | ||||
|         } | ||||
|         if(y < 0) { | ||||
|             return(-angle); | ||||
|         } else { | ||||
|             return(angle); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     inline Real angleDist(Real a, Real b) | ||||
|     { | ||||
|         Real dist = b - a; | ||||
| 
 | ||||
|         while(dist <= M_PI) | ||||
|             dist += 2 * M_PI; | ||||
|         while(dist >= M_PI) | ||||
|             dist -= 2 * M_PI; | ||||
| 
 | ||||
|         return dist; | ||||
|     } | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| #endif // INCLUDE_NFMDEMODSINK_H
 | ||||
| @ -11,7 +11,7 @@ | ||||
| 
 | ||||
| const PluginDescriptor NFMPlugin::m_pluginDescriptor = { | ||||
| 	QString("NFM Demodulator"), | ||||
| 	QString("4.11.6"), | ||||
| 	QString("4.12.2"), | ||||
| 	QString("(c) Edouard Griffiths, F4EXB"), | ||||
| 	QString("https://github.com/f4exb/sdrangel"), | ||||
| 	true, | ||||
|  | ||||
| @ -63,9 +63,11 @@ set(sdrbase_SOURCES | ||||
|     dsp/afsquelch.cpp | ||||
|     dsp/agc.cpp | ||||
|     dsp/downchannelizer.cpp | ||||
|     dsp/downsamplechannelizer.cpp | ||||
|     dsp/upchannelizer.cpp | ||||
|     dsp/channelmarker.cpp | ||||
|     dsp/ctcssdetector.cpp | ||||
|     dsp/channelsamplesink.cpp | ||||
|     dsp/channelsamplesource.cpp | ||||
|     dsp/cwkeyer.cpp | ||||
|     dsp/cwkeyersettings.cpp | ||||
| @ -178,8 +180,10 @@ set(sdrbase_HEADERS | ||||
|     dsp/afsquelch.h | ||||
|     dsp/autocorrector.h | ||||
|     dsp/downchannelizer.h | ||||
|     dsp/downsamplechannelizer.h | ||||
|     dsp/upchannelizer.h | ||||
|     dsp/channelmarker.h | ||||
|     dsp/channelsamplesink.h | ||||
|     dsp/channelsamplesource.h | ||||
|     dsp/complex.h | ||||
|     dsp/cwkeyer.h | ||||
|  | ||||
| @ -107,6 +107,24 @@ void DeviceAPI::removeChannelSink(ThreadedBasebandSampleSink* sink, int streamIn | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DeviceAPI::addChannelSink(BasebandSampleSink* sink, int streamIndex) | ||||
| { | ||||
|     if (m_deviceSourceEngine) { | ||||
|         m_deviceSourceEngine->addSink(sink); | ||||
|     } else if (m_deviceMIMOEngine) { | ||||
|         m_deviceMIMOEngine->addChannelSink(sink); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DeviceAPI::removeChannelSink(BasebandSampleSink* sink, int streamIndex) | ||||
| { | ||||
|     (void) streamIndex; | ||||
| 
 | ||||
|     if (m_deviceSourceEngine) { | ||||
|         m_deviceSourceEngine->removeSink(sink); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DeviceAPI::addChannelSource(BasebandSampleSource* source, int streamIndex) | ||||
| { | ||||
|     if (m_deviceSinkEngine) { | ||||
|  | ||||
| @ -73,6 +73,8 @@ public: | ||||
| 
 | ||||
|     void addChannelSink(ThreadedBasebandSampleSink* sink, int streamIndex = 0);        //!< Add a channel sink (Rx)
 | ||||
|     void removeChannelSink(ThreadedBasebandSampleSink* sink, int streamIndex = 0);     //!< Remove a channel sink (Rx)
 | ||||
|     void addChannelSink(BasebandSampleSink* sink, int streamIndex = 0);                //!< Add a channel sink (Rx)
 | ||||
|     void removeChannelSink(BasebandSampleSink* sink, int streamIndex = 0);             //!< Remove a channel sink (Rx)
 | ||||
|     void addChannelSource(BasebandSampleSource* sink, int streamIndex = 0);            //!< Add a channel source (Tx)
 | ||||
|     void removeChannelSource(BasebandSampleSource* sink, int streamIndex = 0);         //!< Remove a channel source (Tx)
 | ||||
|     void addMIMOChannel(MIMOChannel* channel);   //!< Add a MIMO channel (n Rx and m Tx combination)
 | ||||
|  | ||||
							
								
								
									
										25
									
								
								sdrbase/dsp/channelsamplesink.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								sdrbase/dsp/channelsamplesink.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 F4EXB                                                      //
 | ||||
| // written by Edouard Griffiths                                                  //
 | ||||
| //                                                                               //
 | ||||
| // 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 "channelsamplesink.h" | ||||
| 
 | ||||
| ChannelSampleSink::ChannelSampleSink() | ||||
| {} | ||||
| 
 | ||||
| ChannelSampleSink::~ChannelSampleSink() | ||||
| {} | ||||
							
								
								
									
										37
									
								
								sdrbase/dsp/channelsamplesink.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								sdrbase/dsp/channelsamplesink.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2019 F4EXB                                                      //
 | ||||
| // written by Edouard Griffiths                                                  //
 | ||||
| //                                                                               //
 | ||||
| // 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/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| // This is a lightweight channel sample source interface
 | ||||
| 
 | ||||
| #ifndef SDRBASE_DSP_CHANNELSAMPLESINK_H_ | ||||
| #define SDRBASE_DSP_CHANNELSAMPLESINK_H_ | ||||
| 
 | ||||
| #include "export.h" | ||||
| #include "dsptypes.h" | ||||
| 
 | ||||
| class Message; | ||||
| 
 | ||||
| class SDRBASE_API ChannelSampleSink { | ||||
| public: | ||||
| 	ChannelSampleSink(); | ||||
| 	virtual ~ChannelSampleSink(); | ||||
| 
 | ||||
|     virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) = 0; | ||||
| }; | ||||
| 
 | ||||
| #endif // SDRBASE_DSP_CHANNELSAMPLESINK_H_
 | ||||
							
								
								
									
										322
									
								
								sdrbase/dsp/downsamplechannelizer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										322
									
								
								sdrbase/dsp/downsamplechannelizer.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,322 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2016-2019 F4EXB                                                 //
 | ||||
| // written by Edouard Griffiths                                                  //
 | ||||
| //                                                                               //
 | ||||
| // 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 <QString> | ||||
| #include <QDebug> | ||||
| 
 | ||||
| #include "dsp/inthalfbandfilter.h" | ||||
| #include "dsp/dspcommands.h" | ||||
| #include "dsp/hbfilterchainconverter.h" | ||||
| #include "downsamplechannelizer.h" | ||||
| 
 | ||||
| DownSampleChannelizer::DownSampleChannelizer(ChannelSampleSink* sampleSink) : | ||||
|     m_filterChainSetMode(false), | ||||
| 	m_sampleSink(sampleSink), | ||||
| 	m_basebandSampleRate(0), | ||||
| 	m_requestedOutputSampleRate(0), | ||||
| 	m_requestedCenterFrequency(0), | ||||
|     m_channelSampleRate(0), | ||||
| 	m_channelFrequencyOffset(0), | ||||
|     m_log2Decim(0), | ||||
|     m_filterChainHash(0) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| DownSampleChannelizer::~DownSampleChannelizer() | ||||
| { | ||||
| 	freeFilterChain(); | ||||
| } | ||||
| 
 | ||||
| void DownSampleChannelizer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) | ||||
| { | ||||
| 	if (m_sampleSink == 0) | ||||
|     { | ||||
| 		m_sampleBuffer.clear(); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (m_filterStages.size() == 0) // optimization when no downsampling is done anyway
 | ||||
| 	{ | ||||
| 		m_sampleSink->feed(begin, end); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		for (SampleVector::const_iterator sample = begin; sample != end; ++sample) | ||||
| 		{ | ||||
| 			Sample s(*sample); | ||||
| 			FilterStages::iterator stage = m_filterStages.begin(); | ||||
| 
 | ||||
| 			for (; stage != m_filterStages.end(); ++stage) | ||||
| 			{ | ||||
| #ifndef SDR_RX_SAMPLE_24BIT | ||||
|                 s.m_real /= 2; // avoid saturation on 16 bit samples
 | ||||
|                 s.m_imag /= 2; | ||||
| #endif | ||||
| 				if (!(*stage)->work(&s)) { | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if(stage == m_filterStages.end()) | ||||
| 			{ | ||||
| #ifdef SDR_RX_SAMPLE_24BIT | ||||
| 			    s.m_real /= (1<<(m_filterStages.size())); // on 32 bit samples there is enough headroom to just divide the final result
 | ||||
| 			    s.m_imag /= (1<<(m_filterStages.size())); | ||||
| #endif | ||||
| 				m_sampleBuffer.push_back(s); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end()); | ||||
| 		m_sampleBuffer.clear(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void DownSampleChannelizer::setChannelization(int requestedSampleRate, qint64 requestedCenterFrequency) | ||||
| { | ||||
|     m_requestedOutputSampleRate = requestedSampleRate; | ||||
|     m_requestedCenterFrequency = requestedCenterFrequency; | ||||
|     applyChannelization(); | ||||
| } | ||||
| 
 | ||||
| void DownSampleChannelizer::setBasebandSampleRate(int basebandSampleRate, bool decim) | ||||
| { | ||||
|     m_basebandSampleRate = basebandSampleRate; | ||||
| 
 | ||||
|     if (decim) { | ||||
|         applyDecimation(); | ||||
|     } else { | ||||
|         applyChannelization(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DownSampleChannelizer::applyChannelization() | ||||
| { | ||||
|     m_filterChainSetMode = false; | ||||
| 
 | ||||
| 	if (m_basebandSampleRate == 0) | ||||
| 	{ | ||||
| 		qDebug() << "DownSampleChannelizer::applyChannelization: aborting (in=0)" | ||||
|             << " in (baseband):" << m_basebandSampleRate | ||||
|             << " req:" << m_requestedOutputSampleRate | ||||
|             << " out (channel):" << m_channelSampleRate | ||||
|             << " fc:" << m_channelFrequencyOffset; | ||||
|         return; | ||||
| 	} | ||||
| 
 | ||||
| 	freeFilterChain(); | ||||
| 
 | ||||
| 	m_channelFrequencyOffset = createFilterChain( | ||||
| 		m_basebandSampleRate / -2, m_basebandSampleRate / 2, | ||||
| 		m_requestedCenterFrequency - m_requestedOutputSampleRate / 2, m_requestedCenterFrequency + m_requestedOutputSampleRate / 2); | ||||
| 
 | ||||
| 	m_channelSampleRate = m_basebandSampleRate / (1 << m_filterStages.size()); | ||||
| 
 | ||||
| 	qDebug() << "DownChannelizer::applyChannelization done:" | ||||
|         << " in (baseband):" << m_basebandSampleRate | ||||
| 		<< " req:" << m_requestedOutputSampleRate | ||||
| 		<< " out (channel):" << m_channelSampleRate | ||||
| 		<< " fc:" << m_channelFrequencyOffset; | ||||
| } | ||||
| 
 | ||||
| void DownSampleChannelizer::setDecimation(unsigned int log2Decim, unsigned int filterChainHash) | ||||
| { | ||||
|     m_log2Decim = log2Decim; | ||||
|     m_filterChainHash = filterChainHash; | ||||
|     applyDecimation(); | ||||
| } | ||||
| 
 | ||||
| void DownSampleChannelizer::applyDecimation() | ||||
| { | ||||
|     m_filterChainSetMode = true; | ||||
|     std::vector<unsigned int> stageIndexes; | ||||
|     m_channelFrequencyOffset = m_basebandSampleRate * HBFilterChainConverter::convertToIndexes(m_log2Decim, m_filterChainHash, stageIndexes); | ||||
|     m_requestedCenterFrequency = m_channelFrequencyOffset; | ||||
| 
 | ||||
|     freeFilterChain(); | ||||
| 
 | ||||
|     m_channelFrequencyOffset = m_basebandSampleRate * setFilterChain(stageIndexes); | ||||
|     m_channelSampleRate = m_basebandSampleRate / (1 << m_filterStages.size()); | ||||
|     m_requestedOutputSampleRate = m_channelSampleRate; | ||||
| 
 | ||||
| 	qDebug() << "UpChannelizer::applyInterpolation:" | ||||
|             << " m_log2Interp:" << m_log2Decim | ||||
|             << " m_filterChainHash:" << m_filterChainHash | ||||
|             << " out:" << m_basebandSampleRate | ||||
| 			<< " in:" << m_channelSampleRate | ||||
| 			<< " fc:" << m_channelFrequencyOffset; | ||||
| } | ||||
| 
 | ||||
| #ifdef SDR_RX_SAMPLE_24BIT | ||||
| DownSampleChannelizer::FilterStage::FilterStage(Mode mode) : | ||||
|     m_filter(new IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER>), | ||||
|     m_workFunction(0), | ||||
|     m_mode(mode), | ||||
|     m_sse(true) | ||||
| { | ||||
|     switch(mode) { | ||||
|         case ModeCenter: | ||||
|             m_workFunction = &IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER>::workDecimateCenter; | ||||
|             break; | ||||
| 
 | ||||
|         case ModeLowerHalf: | ||||
|             m_workFunction = &IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER>::workDecimateLowerHalf; | ||||
|             break; | ||||
| 
 | ||||
|         case ModeUpperHalf: | ||||
|             m_workFunction = &IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER>::workDecimateUpperHalf; | ||||
|             break; | ||||
|     } | ||||
| } | ||||
| #else | ||||
| DownSampleChannelizer::FilterStage::FilterStage(Mode mode) : | ||||
|     m_filter(new IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER>), | ||||
|     m_workFunction(0), | ||||
|     m_mode(mode), | ||||
|     m_sse(true) | ||||
| { | ||||
|     switch(mode) { | ||||
|         case ModeCenter: | ||||
|             m_workFunction = &IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER>::workDecimateCenter; | ||||
|             break; | ||||
| 
 | ||||
|         case ModeLowerHalf: | ||||
|             m_workFunction = &IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER>::workDecimateLowerHalf; | ||||
|             break; | ||||
| 
 | ||||
|         case ModeUpperHalf: | ||||
|             m_workFunction = &IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER>::workDecimateUpperHalf; | ||||
|             break; | ||||
|     } | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| DownSampleChannelizer::FilterStage::~FilterStage() | ||||
| { | ||||
| 	delete m_filter; | ||||
| } | ||||
| 
 | ||||
| bool DownSampleChannelizer::signalContainsChannel(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) const | ||||
| { | ||||
| 	//qDebug("   testing signal [%f, %f], channel [%f, %f]", sigStart, sigEnd, chanStart, chanEnd);
 | ||||
| 	if(sigEnd <= sigStart) | ||||
| 		return false; | ||||
| 	if(chanEnd <= chanStart) | ||||
| 		return false; | ||||
| 	return (sigStart <= chanStart) && (sigEnd >= chanEnd); | ||||
| } | ||||
| 
 | ||||
| Real DownSampleChannelizer::createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) | ||||
| { | ||||
| 	Real sigBw = sigEnd - sigStart; | ||||
| 	Real rot = sigBw / 4; | ||||
| 
 | ||||
| 	//qDebug("DownChannelizer::createFilterChain: Signal [%.1f, %.1f] (BW %.1f), Channel [%.1f, %.1f], Rot %.1f", sigStart, sigEnd, sigBw, chanStart, chanEnd, rot);
 | ||||
| 
 | ||||
| 	// check if it fits into the left half
 | ||||
| 	if(signalContainsChannel(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd)) | ||||
|     { | ||||
| 		//qDebug("DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)");
 | ||||
| 		m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf)); | ||||
| 		return createFilterChain(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd); | ||||
| 	} | ||||
| 
 | ||||
| 	// check if it fits into the right half
 | ||||
| 	if(signalContainsChannel(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd)) | ||||
|     { | ||||
| 		//qDebug("DownChannelizer::createFilterChain: -> take right half (rotate by -1/4 and decimate by 2)");
 | ||||
| 		m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf)); | ||||
| 		return createFilterChain(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd); | ||||
| 	} | ||||
| 
 | ||||
| 	// check if it fits into the center
 | ||||
| 	if(signalContainsChannel(sigStart + rot, sigEnd - rot, chanStart, chanEnd)) | ||||
|     { | ||||
| 		//qDebug("DownChannelizer::createFilterChain: -> take center half (decimate by 2)");
 | ||||
| 		m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter)); | ||||
| 		return createFilterChain(sigStart + rot, sigEnd - rot, chanStart, chanEnd); | ||||
| 	} | ||||
| 
 | ||||
| 	Real ofs = ((chanEnd - chanStart) / 2.0 + chanStart) - ((sigEnd - sigStart) / 2.0 + sigStart); | ||||
| 	//qDebug("DownChannelizer::createFilterChain: -> complete (final BW %.1f, frequency offset %.1f)", sigBw, ofs);
 | ||||
| 	return ofs; | ||||
| } | ||||
| 
 | ||||
| double DownSampleChannelizer::setFilterChain(const std::vector<unsigned int>& stageIndexes) | ||||
| { | ||||
|     // filters are described from lower to upper level but the chain is constructed the other way round
 | ||||
|     std::vector<unsigned int>::const_reverse_iterator rit = stageIndexes.rbegin(); | ||||
|     double ofs = 0.0, ofs_stage = 0.25; | ||||
| 
 | ||||
|     // Each index is a base 3 number with 0 = low, 1 = center, 2 = high
 | ||||
|     // Functions at upper level will convert a number to base 3 to describe the filter chain. Common converting
 | ||||
|     // algorithms will go from LSD to MSD. This explains the reverse order.
 | ||||
|     for (; rit != stageIndexes.rend(); ++rit) | ||||
|     { | ||||
|         if (*rit == 0) | ||||
|         { | ||||
|             m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf)); | ||||
|             ofs -= ofs_stage; | ||||
|             qDebug("DownSampleChannelizer::setFilterChain: lower half: ofs: %f", ofs); | ||||
|         } | ||||
|         else if (*rit == 1) | ||||
|         { | ||||
|             m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter)); | ||||
|             qDebug("DownSampleChannelizer::setFilterChain: center: ofs: %f", ofs); | ||||
|         } | ||||
|         else if (*rit == 2) | ||||
|         { | ||||
|             m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf)); | ||||
|             ofs += ofs_stage; | ||||
|             qDebug("DownSampleChannelizer::setFilterChain: upper half: ofs: %f", ofs); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ofs; | ||||
| } | ||||
| 
 | ||||
| void DownSampleChannelizer::freeFilterChain() | ||||
| { | ||||
| 	for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it) | ||||
| 		delete *it; | ||||
| 	m_filterStages.clear(); | ||||
| } | ||||
| 
 | ||||
| void DownSampleChannelizer::debugFilterChain() | ||||
| { | ||||
|     qDebug("DownChannelizer::debugFilterChain: %lu stages", m_filterStages.size()); | ||||
| 
 | ||||
|     for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it) | ||||
|     { | ||||
|         switch ((*it)->m_mode) | ||||
|         { | ||||
|         case FilterStage::ModeCenter: | ||||
|             qDebug("DownChannelizer::debugFilterChain: center %s", (*it)->m_sse ? "sse" : "no_sse"); | ||||
|             break; | ||||
|         case FilterStage::ModeLowerHalf: | ||||
|             qDebug("DownChannelizer::debugFilterChain: lower %s", (*it)->m_sse ? "sse" : "no_sse"); | ||||
|             break; | ||||
|         case FilterStage::ModeUpperHalf: | ||||
|             qDebug("DownChannelizer::debugFilterChain: upper %s", (*it)->m_sse ? "sse" : "no_sse"); | ||||
|             break; | ||||
|         default: | ||||
|             qDebug("DownChannelizer::debugFilterChain: none %s", (*it)->m_sse ? "sse" : "no_sse"); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										96
									
								
								sdrbase/dsp/downsamplechannelizer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								sdrbase/dsp/downsamplechannelizer.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2016-2019 F4EXB                                                 //
 | ||||
| // written by Edouard Griffiths                                                  //
 | ||||
| //                                                                               //
 | ||||
| // 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_DSP_DOWNSAMPLECHANNELIZER_H | ||||
| #define SDRBASE_DSP_DOWNSAMPLECHANNELIZER_H | ||||
| 
 | ||||
| #include <list> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "export.h" | ||||
| #include "util/message.h" | ||||
| #include "dsp/inthalfbandfiltereo.h" | ||||
| 
 | ||||
| #include "channelsamplesink.h" | ||||
| 
 | ||||
| #define DOWNCHANNELIZER_HB_FILTER_ORDER 48 | ||||
| 
 | ||||
| class SDRBASE_API DownSampleChannelizer : public ChannelSampleSink { | ||||
| public: | ||||
| 	DownSampleChannelizer(ChannelSampleSink* sampleSink); | ||||
| 	virtual ~DownSampleChannelizer(); | ||||
| 
 | ||||
| 	virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); | ||||
| 
 | ||||
|     void setDecimation(unsigned int log2Decim, unsigned int filterChainHash);         //!< Define channelizer with decimation factor and filter chain definition
 | ||||
|     void setChannelization(int requestedSampleRate, qint64 requestedCenterFrequency); //!< Define channelizer with requested sample rate and center frequency (shift in the baseband)
 | ||||
|     void setBasebandSampleRate(int basebandSampleRate, bool decim = false);           //!< decim: true => use direct decimation false => use channel configuration
 | ||||
| 	int getChannelSampleRate() const { return m_channelSampleRate; } | ||||
| 	int getChannelFrequencyOffset() const { return m_channelFrequencyOffset; } | ||||
| 
 | ||||
| protected: | ||||
| 	struct FilterStage { | ||||
| 		enum Mode { | ||||
| 			ModeCenter, | ||||
| 			ModeLowerHalf, | ||||
| 			ModeUpperHalf | ||||
| 		}; | ||||
| 
 | ||||
| #ifdef SDR_RX_SAMPLE_24BIT | ||||
|         typedef bool (IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER>::*WorkFunction)(Sample* s); | ||||
|         IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER>* m_filter; | ||||
| #else | ||||
|         typedef bool (IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER>::*WorkFunction)(Sample* s); | ||||
|         IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER>* m_filter; | ||||
| #endif | ||||
| 
 | ||||
| 		WorkFunction m_workFunction; | ||||
| 		Mode m_mode; | ||||
| 		bool m_sse; | ||||
| 
 | ||||
| 		FilterStage(Mode mode); | ||||
| 		~FilterStage(); | ||||
| 
 | ||||
| 		bool work(Sample* sample) | ||||
| 		{ | ||||
| 			return (m_filter->*m_workFunction)(sample); | ||||
| 		} | ||||
| 	}; | ||||
| 	typedef std::list<FilterStage*> FilterStages; | ||||
| 	FilterStages m_filterStages; | ||||
|     bool m_filterChainSetMode; | ||||
| 	ChannelSampleSink* m_sampleSink; //!< Demodulator
 | ||||
|     int m_basebandSampleRate; | ||||
| 	int m_requestedOutputSampleRate; | ||||
| 	int m_requestedCenterFrequency; | ||||
| 	int m_channelSampleRate; | ||||
|     int m_channelFrequencyOffset; | ||||
|     unsigned int m_log2Decim; | ||||
|     unsigned int m_filterChainHash; | ||||
| 	SampleVector m_sampleBuffer; | ||||
| 
 | ||||
| 	void applyChannelization(); | ||||
|     void applyDecimation(); | ||||
| 	bool signalContainsChannel(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) const; | ||||
| 	Real createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd); | ||||
|     double setFilterChain(const std::vector<unsigned int>& stageIndexes); | ||||
| 	void freeFilterChain(); | ||||
| 	void debugFilterChain(); | ||||
| }; | ||||
| 
 | ||||
| #endif // SDRBASE_DSP_DOWNCHANNELIZER_H
 | ||||
| @ -191,6 +191,26 @@ void DSPDeviceMIMOEngine::removeChannelSink(ThreadedBasebandSampleSink* sink, in | ||||
| 	m_syncMessenger.sendWait(cmd); | ||||
| } | ||||
| 
 | ||||
| void DSPDeviceMIMOEngine::addChannelSink(BasebandSampleSink* sink, int index) | ||||
| { | ||||
| 	qDebug() << "DSPDeviceMIMOEngine::addChannelSink: " | ||||
|         << sink->objectName().toStdString().c_str() | ||||
|         << " at: " | ||||
|         << index; | ||||
| 	AddBasebandSampleSink cmd(sink, index); | ||||
| 	m_syncMessenger.sendWait(cmd); | ||||
| } | ||||
| 
 | ||||
| void DSPDeviceMIMOEngine::removeChannelSink(BasebandSampleSink* sink, int index) | ||||
| { | ||||
| 	qDebug() << "DSPDeviceMIMOEngine::removeChannelSink: " | ||||
|         << sink->objectName().toStdString().c_str() | ||||
|         << " at: " | ||||
|         << index; | ||||
| 	RemoveBasebandSampleSink cmd(sink, index); | ||||
| 	m_syncMessenger.sendWait(cmd); | ||||
| } | ||||
| 
 | ||||
| void DSPDeviceMIMOEngine::addMIMOChannel(MIMOChannel *channel) | ||||
| { | ||||
| 	qDebug() << "DSPDeviceMIMOEngine::addMIMOChannel: " | ||||
|  | ||||
| @ -262,6 +262,8 @@ public: | ||||
| 
 | ||||
| 	void addChannelSource(BasebandSampleSource* source, int index = 0);            //!< Add a channel source
 | ||||
| 	void removeChannelSource(BasebandSampleSource* source, int index = 0);         //!< Remove a channel source
 | ||||
| 	void addChannelSink(BasebandSampleSink* sink, int index = 0);                  //!< Add a channel sink
 | ||||
| 	void removeChannelSink(BasebandSampleSink* sink, int index = 0);               //!< Remove a channel sink
 | ||||
| 	void addChannelSink(ThreadedBasebandSampleSink* sink, int index = 0);          //!< Add a channel sink that will run on its own thread
 | ||||
| 	void removeChannelSink(ThreadedBasebandSampleSink* sink, int index = 0);       //!< Remove a channel sink that runs on its own thread
 | ||||
|     void addMIMOChannel(MIMOChannel *channel);                                     //!< Add a MIMO channel
 | ||||
|  | ||||
| @ -31,6 +31,14 @@ void SampleSinkFifo::create(unsigned int s) | ||||
| 	m_size = m_data.size(); | ||||
| } | ||||
| 
 | ||||
| void SampleSinkFifo::reset() | ||||
| { | ||||
| 	m_suppressed = -1; | ||||
| 	m_fill = 0; | ||||
| 	m_head = 0; | ||||
| 	m_tail = 0; | ||||
| } | ||||
| 
 | ||||
| SampleSinkFifo::SampleSinkFifo(QObject* parent) : | ||||
| 	QObject(parent), | ||||
| 	m_data() | ||||
| @ -274,3 +282,8 @@ unsigned int SampleSinkFifo::readCommit(unsigned int count) | ||||
| 
 | ||||
| 	return count; | ||||
| } | ||||
| 
 | ||||
| unsigned int SampleSinkFifo::getSizePolicy(unsigned int sampleRate) | ||||
| { | ||||
|     return (sampleRate/100)*64; // .64s
 | ||||
| } | ||||
| @ -49,6 +49,7 @@ public: | ||||
| 	~SampleSinkFifo(); | ||||
| 
 | ||||
| 	bool setSize(int size); | ||||
|     void reset(); | ||||
| 	inline unsigned int size() const { return m_size; } | ||||
| 	inline unsigned int fill() { QMutexLocker mutexLocker(&m_mutex); unsigned int fill = m_fill; return fill; } | ||||
| 
 | ||||
| @ -61,6 +62,7 @@ public: | ||||
| 		SampleVector::iterator* part1Begin, SampleVector::iterator* part1End, | ||||
| 		SampleVector::iterator* part2Begin, SampleVector::iterator* part2End); | ||||
| 	unsigned int readCommit(unsigned int count); | ||||
|     static unsigned int getSizePolicy(unsigned int sampleRate); | ||||
| 
 | ||||
| signals: | ||||
| 	void dataReady(); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user