AM demod: basic synchronous AM detection option

This commit is contained in:
f4exb 2018-05-13 17:27:24 +02:00
parent 1549ecaa0f
commit e9f64a05f2
10 changed files with 106 additions and 6 deletions

View File

@ -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();
}

View File

@ -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)
{

View File

@ -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++;
}

View File

@ -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);

View File

@ -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">

View File

@ -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,

View File

@ -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;
}

View File

@ -33,6 +33,7 @@ struct AMDemodSettings
QString m_title;
Serializable *m_channelMarker;
QString m_audioDeviceName;
bool m_pll;
AMDemodSettings();
void resetToDefaults();

View File

@ -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);

View File

@ -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: