From 2aa0cea1ca8f67a6eff2ab62c1ec51f87c638e2c Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 12 Dec 2015 02:17:41 +0100 Subject: [PATCH] BFM demod: use atan2 phase discriminator with scaling depending on sample rate and excursion for better fidelity --- plugins/channel/bfm/bfmdemod.cpp | 30 ++++++++++-------------- plugins/channel/bfm/bfmdemod.h | 40 +++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/plugins/channel/bfm/bfmdemod.cpp b/plugins/channel/bfm/bfmdemod.cpp index 80a9ac6cc..e2e4092d7 100644 --- a/plugins/channel/bfm/bfmdemod.cpp +++ b/plugins/channel/bfm/bfmdemod.cpp @@ -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; } diff --git a/plugins/channel/bfm/bfmdemod.h b/plugins/channel/bfm/bfmdemod.h index d63be9c27..c3ddf34cb 100644 --- a/plugins/channel/bfm/bfmdemod.h +++ b/plugins/channel/bfm/bfmdemod.h @@ -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 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(); };