mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-31 13:00:26 -04:00 
			
		
		
		
	AM demod: basic synchronous AM detection option
This commit is contained in:
		
							parent
							
								
									1549ecaa0f
								
							
						
					
					
						commit
						e9f64a05f2
					
				| @ -66,6 +66,7 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : | ||||
| 
 | ||||
| 	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); | ||||
| 
 | ||||
|     applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); | ||||
|     applySettings(m_settings, true); | ||||
| @ -74,6 +75,10 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : | ||||
|     m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); | ||||
|     m_deviceAPI->addThreadedSink(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelAPI(this); | ||||
| 
 | ||||
|     m_pllFilt.create(101, m_audioSampleRate, 500.0); | ||||
|     m_pll.computeCoefficients(0.05, 0.707, 1000); | ||||
|     m_syncAMBuffIndex = 0; | ||||
| } | ||||
| 
 | ||||
| AMDemod::~AMDemod() | ||||
| @ -83,6 +88,7 @@ AMDemod::~AMDemod() | ||||
|     m_deviceAPI->removeThreadedSink(m_threadedChannelizer); | ||||
|     delete m_threadedChannelizer; | ||||
|     delete m_channelizer; | ||||
|     delete DSBFilter; | ||||
| } | ||||
| 
 | ||||
| void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) | ||||
| @ -233,6 +239,8 @@ void AMDemod::applyAudioSampleRate(int sampleRate) | ||||
|     m_bandpass.create(301, sampleRate, 300.0, 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, 500.0); | ||||
|     m_settingsMutex.unlock(); | ||||
| 
 | ||||
|     m_audioSampleRate = sampleRate; | ||||
| @ -273,6 +281,7 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) | ||||
|             << " m_audioMute: " << settings.m_audioMute | ||||
|             << " m_bandpassEnable: " << settings.m_bandpassEnable | ||||
|             << " m_audioDeviceName: " << settings.m_audioDeviceName | ||||
|             << " m_pll: " << settings.m_pll | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || | ||||
| @ -283,6 +292,7 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate; | ||||
|         m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_rfBandwidth / 2.0f); | ||||
|         DSBFilter->create_dsb_filter((2.0f * settings.m_rfBandwidth) / (float) m_audioSampleRate); | ||||
|         m_settingsMutex.unlock(); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -27,6 +27,9 @@ | ||||
| #include "util/movingaverage.h" | ||||
| #include "dsp/agc.h" | ||||
| #include "dsp/bandpass.h" | ||||
| #include "dsp/lowpass.h" | ||||
| #include "dsp/phaselockcomplex.h" | ||||
| #include "dsp/fftfilt.h" | ||||
| #include "audio/audiofifo.h" | ||||
| #include "util/message.h" | ||||
| #include "util/doublebufferfifo.h" | ||||
| @ -118,6 +121,7 @@ public: | ||||
|     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(); } | ||||
| 
 | ||||
| 	void getMagSqLevels(double& avg, double& peak, int& nbSamples) | ||||
| 	{ | ||||
| @ -165,6 +169,11 @@ private: | ||||
| 	MovingAverageUtil<Real, double, 16> m_movingAverage; | ||||
| 	SimpleAGC<4096> m_volumeAGC; | ||||
|     Bandpass<Real> m_bandpass; | ||||
|     Lowpass<std::complex<float> > m_pllFilt; | ||||
|     PhaseLockComplex m_pll; | ||||
|     fftfilt* DSBFilter; | ||||
|     Real m_syncAMBuff[2*1024]; | ||||
|     uint32_t m_syncAMBuffIndex; | ||||
| 
 | ||||
| 	AudioVector m_audioBuffer; | ||||
| 	uint32_t m_audioBufferFill; | ||||
| @ -217,9 +226,37 @@ private: | ||||
| 
 | ||||
|         if (m_squelchOpen && !m_settings.m_audioMute) | ||||
|         { | ||||
|             Real demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); | ||||
|             m_volumeAGC.feed(demod); | ||||
|             demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); | ||||
|             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 = DSBFilter->runDSB(cs, &sideband, false); | ||||
| 
 | ||||
|                 for (int i = 0; i < n_out; i++) | ||||
|                 { | ||||
|                     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++]*0.7*(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) | ||||
|             { | ||||
|  | ||||
| @ -139,6 +139,16 @@ void AMDemodGUI::on_deltaFrequency_changed(qint64 value) | ||||
|     applySettings(); | ||||
| } | ||||
| 
 | ||||
| void AMDemodGUI::on_pll_toggled(bool checked) | ||||
| { | ||||
|     if (!checked) { | ||||
|         ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); | ||||
|     } | ||||
| 
 | ||||
|     m_settings.m_pll = checked; | ||||
|     applySettings(); | ||||
| } | ||||
| 
 | ||||
| void AMDemodGUI::on_bandpassEnable_toggled(bool checked) | ||||
| { | ||||
|     m_settings.m_bandpassEnable = checked; | ||||
| @ -302,6 +312,7 @@ void AMDemodGUI::displaySettings() | ||||
| 
 | ||||
|     ui->audioMute->setChecked(m_settings.m_audioMute); | ||||
|     ui->bandpassEnable->setChecked(m_settings.m_bandpassEnable); | ||||
|     ui->pll->setChecked(m_settings.m_pll); | ||||
| 
 | ||||
|     blockApplySettings(false); | ||||
| } | ||||
| @ -359,6 +370,15 @@ void AMDemodGUI::tick() | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (m_settings.m_pll) | ||||
| 	{ | ||||
| 	    if (m_amDemod->getPllLocked()) { | ||||
| 	        ui->pll->setStyleSheet("QToolButton { background-color : green; }"); | ||||
| 	    } else { | ||||
| 	        ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); | ||||
| 	    } | ||||
| 	} | ||||
| 
 | ||||
| 	m_tickCount++; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -65,6 +65,7 @@ private: | ||||
| 
 | ||||
| private slots: | ||||
| 	void on_deltaFrequency_changed(qint64 value); | ||||
| 	void on_pll_toggled(bool checked); | ||||
| 	void on_bandpassEnable_toggled(bool checked); | ||||
| 	void on_rfBW_valueChanged(int value); | ||||
| 	void on_volume_valueChanged(int value); | ||||
|  | ||||
| @ -129,6 +129,31 @@ | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item> | ||||
|          <widget class="Line" name="line"> | ||||
|           <property name="orientation"> | ||||
|            <enum>Qt::Vertical</enum> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item> | ||||
|          <widget class="QToolButton" name="pll"> | ||||
|           <property name="toolTip"> | ||||
|            <string>PLL for synchronous AM</string> | ||||
|           </property> | ||||
|           <property name="text"> | ||||
|            <string/> | ||||
|           </property> | ||||
|           <property name="icon"> | ||||
|            <iconset resource="../../../sdrgui/resources/res.qrc"> | ||||
|             <normaloff>:/unlocked.png</normaloff> | ||||
|             <normalon>:/locked.png</normalon>:/unlocked.png</iconset> | ||||
|           </property> | ||||
|           <property name="checkable"> | ||||
|            <bool>true</bool> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item> | ||||
|          <spacer name="horizontalSpacer"> | ||||
|           <property name="orientation"> | ||||
|  | ||||
| @ -9,7 +9,7 @@ | ||||
| 
 | ||||
| const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = { | ||||
| 	QString("AM Demodulator"), | ||||
| 	QString("3.14.5"), | ||||
| 	QString("3.14.7"), | ||||
| 	QString("(c) Edouard Griffiths, F4EXB"), | ||||
| 	QString("https://github.com/f4exb/sdrangel"), | ||||
| 	true, | ||||
|  | ||||
| @ -38,6 +38,7 @@ void AMDemodSettings::resetToDefaults() | ||||
|     m_rgbColor = QColor(255, 255, 0).rgb(); | ||||
|     m_title = "AM Demodulator"; | ||||
|     m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; | ||||
|     m_pll = false; | ||||
| } | ||||
| 
 | ||||
| QByteArray AMDemodSettings::serialize() const | ||||
| @ -56,6 +57,7 @@ QByteArray AMDemodSettings::serialize() const | ||||
|     s.writeBool(8, m_bandpassEnable); | ||||
|     s.writeString(9, m_title); | ||||
|     s.writeString(11, m_audioDeviceName); | ||||
|     s.writeBool(12, m_pll); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
| @ -93,6 +95,7 @@ bool AMDemodSettings::deserialize(const QByteArray& data) | ||||
|         d.readBool(8, &m_bandpassEnable, false); | ||||
|         d.readString(9, &m_title, "AM Demodulator"); | ||||
|         d.readString(11, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); | ||||
|         d.readBool(12, &m_pll, false); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
| @ -33,6 +33,7 @@ struct AMDemodSettings | ||||
|     QString m_title; | ||||
|     Serializable *m_channelMarker; | ||||
|     QString m_audioDeviceName; | ||||
|     bool m_pll; | ||||
| 
 | ||||
|     AMDemodSettings(); | ||||
|     void resetToDefaults(); | ||||
|  | ||||
| @ -298,7 +298,7 @@ int fftfilt::runSSB(const cmplx & in, cmplx **out, bool usb, bool getDC) | ||||
| } | ||||
| 
 | ||||
| // Version for double sideband. You have to double the FFT size used for SSB.
 | ||||
| int fftfilt::runDSB(const cmplx & in, cmplx **out) | ||||
| int fftfilt::runDSB(const cmplx & in, cmplx **out, bool getDC) | ||||
| { | ||||
| 	data[inptr++] = in; | ||||
| 	if (inptr < flen2) | ||||
| @ -312,6 +312,9 @@ int fftfilt::runDSB(const cmplx & in, cmplx **out) | ||||
| 		data[flen2 + i] *= filter[flen2 + i]; | ||||
| 	} | ||||
| 
 | ||||
|     // get or reject DC component
 | ||||
|     data[0] = getDC ? data[0] : 0; | ||||
| 
 | ||||
| 	// in-place FFT: freqdata overwritten with filtered timedata
 | ||||
| 	fft->InverseComplexFFT(data); | ||||
| 
 | ||||
|  | ||||
| @ -32,7 +32,7 @@ public: | ||||
| 	int noFilt(const cmplx& in, cmplx **out); | ||||
| 	int runFilt(const cmplx& in, cmplx **out); | ||||
| 	int runSSB(const cmplx& in, cmplx **out, bool usb, bool getDC = true); | ||||
| 	int runDSB(const cmplx& in, cmplx **out); | ||||
| 	int runDSB(const cmplx& in, cmplx **out, bool getDC = true); | ||||
| 	int runAsym(const cmplx & in, cmplx **out, bool usb); //!< Asymmetrical fitering can be used for vestigial sideband
 | ||||
| 
 | ||||
| protected: | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user