mirror of https://github.com/f4exb/sdrangel.git
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());
|
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue());
|
||||||
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
|
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);
|
applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
|
||||||
applySettings(m_settings, true);
|
applySettings(m_settings, true);
|
||||||
|
@ -74,6 +75,10 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) :
|
||||||
m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this);
|
m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this);
|
||||||
m_deviceAPI->addThreadedSink(m_threadedChannelizer);
|
m_deviceAPI->addThreadedSink(m_threadedChannelizer);
|
||||||
m_deviceAPI->addChannelAPI(this);
|
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()
|
AMDemod::~AMDemod()
|
||||||
|
@ -83,6 +88,7 @@ AMDemod::~AMDemod()
|
||||||
m_deviceAPI->removeThreadedSink(m_threadedChannelizer);
|
m_deviceAPI->removeThreadedSink(m_threadedChannelizer);
|
||||||
delete m_threadedChannelizer;
|
delete m_threadedChannelizer;
|
||||||
delete m_channelizer;
|
delete m_channelizer;
|
||||||
|
delete DSBFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused)))
|
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_bandpass.create(301, sampleRate, 300.0, m_settings.m_rfBandwidth / 2.0f);
|
||||||
m_audioFifo.setSize(sampleRate);
|
m_audioFifo.setSize(sampleRate);
|
||||||
m_squelchDelayLine.resize(sampleRate/5);
|
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_settingsMutex.unlock();
|
||||||
|
|
||||||
m_audioSampleRate = sampleRate;
|
m_audioSampleRate = sampleRate;
|
||||||
|
@ -273,6 +281,7 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force)
|
||||||
<< " m_audioMute: " << settings.m_audioMute
|
<< " m_audioMute: " << settings.m_audioMute
|
||||||
<< " m_bandpassEnable: " << settings.m_bandpassEnable
|
<< " m_bandpassEnable: " << settings.m_bandpassEnable
|
||||||
<< " m_audioDeviceName: " << settings.m_audioDeviceName
|
<< " m_audioDeviceName: " << settings.m_audioDeviceName
|
||||||
|
<< " m_pll: " << settings.m_pll
|
||||||
<< " force: " << force;
|
<< " force: " << force;
|
||||||
|
|
||||||
if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) ||
|
if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) ||
|
||||||
|
@ -283,6 +292,7 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force)
|
||||||
m_interpolatorDistanceRemain = 0;
|
m_interpolatorDistanceRemain = 0;
|
||||||
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate;
|
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate;
|
||||||
m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_rfBandwidth / 2.0f);
|
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();
|
m_settingsMutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,9 @@
|
||||||
#include "util/movingaverage.h"
|
#include "util/movingaverage.h"
|
||||||
#include "dsp/agc.h"
|
#include "dsp/agc.h"
|
||||||
#include "dsp/bandpass.h"
|
#include "dsp/bandpass.h"
|
||||||
|
#include "dsp/lowpass.h"
|
||||||
|
#include "dsp/phaselockcomplex.h"
|
||||||
|
#include "dsp/fftfilt.h"
|
||||||
#include "audio/audiofifo.h"
|
#include "audio/audiofifo.h"
|
||||||
#include "util/message.h"
|
#include "util/message.h"
|
||||||
#include "util/doublebufferfifo.h"
|
#include "util/doublebufferfifo.h"
|
||||||
|
@ -118,6 +121,7 @@ public:
|
||||||
uint32_t getAudioSampleRate() const { return m_audioSampleRate; }
|
uint32_t getAudioSampleRate() const { return m_audioSampleRate; }
|
||||||
double getMagSq() const { return m_magsq; }
|
double getMagSq() const { return m_magsq; }
|
||||||
bool getSquelchOpen() const { return m_squelchOpen; }
|
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)
|
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
|
||||||
{
|
{
|
||||||
|
@ -165,6 +169,11 @@ private:
|
||||||
MovingAverageUtil<Real, double, 16> m_movingAverage;
|
MovingAverageUtil<Real, double, 16> m_movingAverage;
|
||||||
SimpleAGC<4096> m_volumeAGC;
|
SimpleAGC<4096> m_volumeAGC;
|
||||||
Bandpass<Real> m_bandpass;
|
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;
|
AudioVector m_audioBuffer;
|
||||||
uint32_t m_audioBufferFill;
|
uint32_t m_audioBufferFill;
|
||||||
|
@ -217,9 +226,37 @@ private:
|
||||||
|
|
||||||
if (m_squelchOpen && !m_settings.m_audioMute)
|
if (m_squelchOpen && !m_settings.m_audioMute)
|
||||||
{
|
{
|
||||||
Real demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20));
|
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);
|
m_volumeAGC.feed(demod);
|
||||||
demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue();
|
demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
if (m_settings.m_bandpassEnable)
|
if (m_settings.m_bandpassEnable)
|
||||||
{
|
{
|
||||||
|
|
|
@ -139,6 +139,16 @@ void AMDemodGUI::on_deltaFrequency_changed(qint64 value)
|
||||||
applySettings();
|
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)
|
void AMDemodGUI::on_bandpassEnable_toggled(bool checked)
|
||||||
{
|
{
|
||||||
m_settings.m_bandpassEnable = checked;
|
m_settings.m_bandpassEnable = checked;
|
||||||
|
@ -302,6 +312,7 @@ void AMDemodGUI::displaySettings()
|
||||||
|
|
||||||
ui->audioMute->setChecked(m_settings.m_audioMute);
|
ui->audioMute->setChecked(m_settings.m_audioMute);
|
||||||
ui->bandpassEnable->setChecked(m_settings.m_bandpassEnable);
|
ui->bandpassEnable->setChecked(m_settings.m_bandpassEnable);
|
||||||
|
ui->pll->setChecked(m_settings.m_pll);
|
||||||
|
|
||||||
blockApplySettings(false);
|
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++;
|
m_tickCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,7 @@ private:
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_deltaFrequency_changed(qint64 value);
|
void on_deltaFrequency_changed(qint64 value);
|
||||||
|
void on_pll_toggled(bool checked);
|
||||||
void on_bandpassEnable_toggled(bool checked);
|
void on_bandpassEnable_toggled(bool checked);
|
||||||
void on_rfBW_valueChanged(int value);
|
void on_rfBW_valueChanged(int value);
|
||||||
void on_volume_valueChanged(int value);
|
void on_volume_valueChanged(int value);
|
||||||
|
|
|
@ -129,6 +129,31 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
<item>
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = {
|
const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = {
|
||||||
QString("AM Demodulator"),
|
QString("AM Demodulator"),
|
||||||
QString("3.14.5"),
|
QString("3.14.7"),
|
||||||
QString("(c) Edouard Griffiths, F4EXB"),
|
QString("(c) Edouard Griffiths, F4EXB"),
|
||||||
QString("https://github.com/f4exb/sdrangel"),
|
QString("https://github.com/f4exb/sdrangel"),
|
||||||
true,
|
true,
|
||||||
|
|
|
@ -38,6 +38,7 @@ void AMDemodSettings::resetToDefaults()
|
||||||
m_rgbColor = QColor(255, 255, 0).rgb();
|
m_rgbColor = QColor(255, 255, 0).rgb();
|
||||||
m_title = "AM Demodulator";
|
m_title = "AM Demodulator";
|
||||||
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
|
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
|
||||||
|
m_pll = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray AMDemodSettings::serialize() const
|
QByteArray AMDemodSettings::serialize() const
|
||||||
|
@ -56,6 +57,7 @@ QByteArray AMDemodSettings::serialize() const
|
||||||
s.writeBool(8, m_bandpassEnable);
|
s.writeBool(8, m_bandpassEnable);
|
||||||
s.writeString(9, m_title);
|
s.writeString(9, m_title);
|
||||||
s.writeString(11, m_audioDeviceName);
|
s.writeString(11, m_audioDeviceName);
|
||||||
|
s.writeBool(12, m_pll);
|
||||||
|
|
||||||
return s.final();
|
return s.final();
|
||||||
}
|
}
|
||||||
|
@ -93,6 +95,7 @@ bool AMDemodSettings::deserialize(const QByteArray& data)
|
||||||
d.readBool(8, &m_bandpassEnable, false);
|
d.readBool(8, &m_bandpassEnable, false);
|
||||||
d.readString(9, &m_title, "AM Demodulator");
|
d.readString(9, &m_title, "AM Demodulator");
|
||||||
d.readString(11, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName);
|
d.readString(11, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName);
|
||||||
|
d.readBool(12, &m_pll, false);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ struct AMDemodSettings
|
||||||
QString m_title;
|
QString m_title;
|
||||||
Serializable *m_channelMarker;
|
Serializable *m_channelMarker;
|
||||||
QString m_audioDeviceName;
|
QString m_audioDeviceName;
|
||||||
|
bool m_pll;
|
||||||
|
|
||||||
AMDemodSettings();
|
AMDemodSettings();
|
||||||
void resetToDefaults();
|
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.
|
// 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;
|
data[inptr++] = in;
|
||||||
if (inptr < flen2)
|
if (inptr < flen2)
|
||||||
|
@ -312,6 +312,9 @@ int fftfilt::runDSB(const cmplx & in, cmplx **out)
|
||||||
data[flen2 + i] *= filter[flen2 + i];
|
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
|
// in-place FFT: freqdata overwritten with filtered timedata
|
||||||
fft->InverseComplexFFT(data);
|
fft->InverseComplexFFT(data);
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ public:
|
||||||
int noFilt(const cmplx& in, cmplx **out);
|
int noFilt(const cmplx& in, cmplx **out);
|
||||||
int runFilt(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 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
|
int runAsym(const cmplx & in, cmplx **out, bool usb); //!< Asymmetrical fitering can be used for vestigial sideband
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
Loading…
Reference in New Issue