1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-17 13:51:47 -05:00

BFM demod: use atan2 phase discriminator with scaling depending on sample rate and excursion for better fidelity

This commit is contained in:
f4exb 2015-12-12 02:17:41 +01:00
parent 85f4963438
commit 2aa0cea1ca
2 changed files with 49 additions and 21 deletions

View File

@ -34,7 +34,9 @@ BFMDemod::BFMDemod(SampleSink* sampleSink) :
m_settingsMutex(QMutex::Recursive),
m_pilotPLL(19000/384000, 50/384000, 0.01),
m_deemphasisFilterX(default_deemphasis * 48000 * 1.0e-6),
m_deemphasisFilterY(default_deemphasis * 48000 * 1.0e-6)
m_deemphasisFilterY(default_deemphasis * 48000 * 1.0e-6),
m_fmExcursion(default_excursion),
m_fmScaling(384000/m_fmExcursion)
{
setObjectName("BFMDemod");
@ -112,23 +114,14 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto
{
m_squelchState--;
// Alternative without atan
// http://www.embedded.com/design/configurable-systems/4212086/DSP-Tricks--Frequency-demodulation-algorithms-
// in addition it needs scaling by instantaneous magnitude squared and volume (0..10) adjustment factor
Real ip = rf[i].real() - m_m2Sample.real();
Real qp = rf[i].imag() - m_m2Sample.imag();
Real h1 = m_m1Sample.real() * qp;
Real h2 = m_m1Sample.imag() * ip;
demod = (h1 - h2) / (msq * 10.0);
//demod = phaseDiscriminator2(rf[i], msq);
demod = phaseDiscriminator(rf[i]);
}
else
{
demod = 0;
}
m_m2Sample = m_m1Sample;
m_m1Sample = rf[i];
if (!m_running.m_showPilot)
{
m_sampleBuffer.push_back(Sample(demod * (1<<15), 0.0));
@ -164,14 +157,14 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto
Real deemph_l, deemph_r; // Pre-emphasis is applied on each channel before multiplexing
m_deemphasisFilterX.process(ci.real() + sampleStereo, deemph_l);
m_deemphasisFilterY.process(ci.real() - sampleStereo, deemph_r);
m_audioBuffer[m_audioBufferFill].l = (qint16)(deemph_l * 3000 * m_running.m_volume);
m_audioBuffer[m_audioBufferFill].r = (qint16)(deemph_r * 3000 * m_running.m_volume);
m_audioBuffer[m_audioBufferFill].l = (qint16)(deemph_l * (1<<12) * m_running.m_volume);
m_audioBuffer[m_audioBufferFill].r = (qint16)(deemph_r * (1<<12) * m_running.m_volume);
}
else
{
Real deemph;
m_deemphasisFilterX.process(ci.real(), deemph);
quint16 sample = (qint16)(deemph * 3000 * m_running.m_volume);
quint16 sample = (qint16)(deemph * (1<<12) * m_running.m_volume);
m_audioBuffer[m_audioBufferFill].l = sample;
m_audioBuffer[m_audioBufferFill].r = sample;
}
@ -230,8 +223,6 @@ void BFMDemod::stop()
bool BFMDemod::handleMessage(const Message& cmd)
{
qDebug() << "BFMDemod::handleMessage";
if (Channelizer::MsgChannelizerNotification::match(cmd))
{
Channelizer::MsgChannelizerNotification& notif = (Channelizer::MsgChannelizerNotification&) cmd;
@ -270,6 +261,8 @@ bool BFMDemod::handleMessage(const Message& cmd)
}
else
{
qDebug() << "BFMDemod::handleMessage: none";
if (m_sampleSink != 0)
{
return m_sampleSink->handleMessage(cmd);
@ -318,6 +311,7 @@ void BFMDemod::apply()
Real lowCut = -(m_config.m_rfBandwidth / 2.0) / m_config.m_inputSampleRate;
Real hiCut = (m_config.m_rfBandwidth / 2.0) / m_config.m_inputSampleRate;
m_rfFilter->create_filter(lowCut, hiCut);
m_fmScaling = m_config.m_inputSampleRate / m_fmExcursion;
m_settingsMutex.unlock();
qDebug() << "BFMDemod::handleMessage: m_rfFilter->create_filter: sampleRate: "
@ -337,7 +331,7 @@ void BFMDemod::apply()
if(m_config.m_squelch != m_running.m_squelch) {
qDebug() << "BFMDemod::handleMessage: set m_squelchLevel";
m_squelchLevel = pow(10.0, m_config.m_squelch / 20.0);
m_squelchLevel = std::pow(10.0, m_config.m_squelch / 20.0);
m_squelchLevel *= m_squelchLevel;
}

View File

@ -153,9 +153,10 @@ private:
Real m_squelchLevel;
int m_squelchState;
Real m_lastArgument;
Complex m_m1Sample; //!< x^-1 sample
Complex m_m2Sample; //!< x^-1 sample
Complex m_m1Sample; //!< x^-1 complex sample
Complex m_m2Sample; //!< x^-2 complex sample
Real m_m1Arg; //!> x^-1 real sample
MovingAverage<Real> m_movingAverage;
AudioVector m_audioBuffer;
@ -173,6 +174,39 @@ private:
LowPassFilterRC m_deemphasisFilterY;
static const Real default_deemphasis = 50.0; // 50 us
Real m_fmExcursion;
Real m_fmScaling;
static const int default_excursion = 750000; // +/- 75 kHz
/**
* Standard discriminator using atan2. On modern processors this is as efficient as the non atan2 one.
* This is better for high fidelity.
*/
Real phaseDiscriminator(const Complex& sample)
{
Complex d(std::conj(m_m1Sample) * sample);
m_m1Sample = sample;
return (std::atan2(d.imag(), d.real()) / M_PI_2) * m_fmScaling;
}
/**
* Alternative without atan at the expense of a slight distorsion on very wideband signals
* http://www.embedded.com/design/configurable-systems/4212086/DSP-Tricks--Frequency-demodulation-algorithms-
* in addition it needs scaling by instantaneous magnitude squared and volume (0..10) adjustment factor
*/
Real phaseDiscriminator2(const Complex& sample, Real msq)
{
Real ip = sample.real() - m_m2Sample.real();
Real qp = sample.imag() - m_m2Sample.imag();
Real h1 = m_m1Sample.real() * qp;
Real h2 = m_m1Sample.imag() * ip;
m_m2Sample = m_m1Sample;
m_m1Sample = sample;
return ((h1 - h2) / (msq * M_PI)) * m_fmScaling;
}
void apply();
};