/////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2019-2020 Edouard Griffiths, F4EXB // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU General Public License as published by // // the Free Software Foundation as version 3 of the License, or // // (at your option) any later version. // // // // This program is distributed in the hope that it will be useful, // // but WITHOUT ANY WARRANTY; without even the implied warranty of // // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // // GNU General Public License V3 for more details. // // // // You should have received a copy of the GNU General Public License // // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// #include #include #include "dsp/dspengine.h" #include "dsp/fftfactory.h" #include "dsp/fftengine.h" #include "util/db.h" #include "interferometercorr.h" std::complex s2c(const Sample& s) { return std::complex{s.real() / SDR_RX_SCALEF, s.imag() / SDR_RX_SCALEF}; } std::complex s2cNorm(const Sample& s) { float x = s.real() / SDR_RX_SCALEF; float y = s.imag() / SDR_RX_SCALEF; float m = sqrt(x*x + y*y); return std::complex{x/m, y/m}; } Sample sFirst(const Sample& a, const Sample& b) { (void) b; return a; } Sample sSecond(const Sample& a, const Sample& b) { (void) a; return b; } Sample sSecondInv(const Sample& a, const Sample& b) { (void) a; return Sample{-b.real(), -b.imag()}; } Sample sAdd(const Sample& a, const Sample& b) { //!< Sample addition return Sample{(a.real()+b.real())/2, (a.imag()+b.imag())/2}; } Sample sAddInv(const Sample& a, const Sample& b) { //!< Sample addition return Sample{(a.real()-b.real())/2, (a.imag()+b.imag())/2}; } Sample sMulConj(const Sample& a, const Sample& b) { //!< Sample multiply with conjugate Sample s; // Integer processing int64_t ax = a.real(); int64_t ay = a.imag(); int64_t bx = b.real(); int64_t by = b.imag(); int64_t x = ax*bx + ay*by; int64_t y = ay*bx - ax*by; s.setReal(x>>(SDR_RX_SAMP_SZ-1)); s.setImag(y>>(SDR_RX_SAMP_SZ-1)); // Floating point processing (in practice there is no significant performance difference) // float ax = a.real() / SDR_RX_SCALEF; // float ay = a.imag() / SDR_RX_SCALEF; // float bx = b.real() / SDR_RX_SCALEF; // float by = b.imag() / SDR_RX_SCALEF; // float x = ax*bx + ay*by; // float y = ay*bx - ax*by; // s.setReal(x*SDR_RX_SCALEF); // s.setImag(y*SDR_RX_SCALEF); return s; } Sample sMulConjInv(const Sample& a, const Sample& b) { //!< Sample multiply with conjugate Sample s; // Integer processing int64_t ax = a.real(); int64_t ay = a.imag(); int64_t bx = -b.real(); int64_t by = -b.imag(); int64_t x = ax*bx + ay*by; int64_t y = ay*bx - ax*by; s.setReal(x>>(SDR_RX_SAMP_SZ-1)); s.setImag(y>>(SDR_RX_SAMP_SZ-1)); return s; } Sample invfft2s(const std::complex& a) { //!< Complex float to Sample for 1 side time correlation Sample s; s.setReal(a.real()/2.0f); s.setImag(a.imag()/2.0f); return s; } Sample invfft2s2(const std::complex& a) { //!< Complex float to Sample for 2 sides time correlation Sample s; s.setReal(a.real()); s.setImag(a.imag()); return s; } Sample invfft2star(const std::complex& a) { //!< Complex float to Sample for 1 side time correlation Sample s; s.setReal(a.real()/2.82842712475f); // 2*sqrt(2) s.setImag(a.imag()/2.82842712475f); return s; } InterferometerCorrelator::InterferometerCorrelator(int fftSize) : m_corrType(InterferometerSettings::CorrelationAdd), m_fftSize(fftSize) { setPhase(0); setGain(0); FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory(); m_window.create(FFTWindow::Function::Hanning, fftSize); m_data0w.resize(m_fftSize); m_data1w.resize(m_fftSize); for (int i = 0; i < 2; i++) { m_fftSequences[i] = fftFactory->getEngine(2*fftSize, false, &m_fft[i]); // internally twice the data FFT size m_fft2Sequences[i] = fftFactory->getEngine(fftSize, false, &m_fft2[i]); } m_invFFTSequence = fftFactory->getEngine(2*fftSize, true, &m_invFFT); m_invFFT2Sequence = fftFactory->getEngine(fftSize, true, &m_invFFT2); m_dataj = new std::complex[2*fftSize]; // receives actual FFT result hence twice the data FFT size m_scorr.resize(fftSize); m_tcorr.resize(fftSize); m_scorrSize = fftSize; m_tcorrSize = fftSize; } InterferometerCorrelator::~InterferometerCorrelator() { FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory(); fftFactory->releaseEngine(2*m_fftSize, true, m_invFFTSequence); fftFactory->releaseEngine(m_fftSize, true, m_invFFT2Sequence); delete[] m_dataj; for (int i = 0; i < 2; i++) { fftFactory->releaseEngine(2*m_fftSize, false, m_fftSequences[i]); fftFactory->releaseEngine(m_fftSize, false, m_fft2Sequences[i]); } } bool InterferometerCorrelator::performCorr( const SampleVector& data0, unsigned int size0, const SampleVector& data1, unsigned int size1 ) { bool results = false; const SampleVector *pdata1 = &data1; if ((m_gain != 0) || (m_phase != 0)) { if (size1 > m_data1p.size()) { m_data1p.resize(size1); } pdata1 = &m_data1p; if (m_phase == 0) { std::transform( data1.begin(), data1.begin() + size1, m_data1p.begin(), [this](const Sample& s) -> Sample { FixReal sx = s.real()*m_gain; FixReal sy = s.imag()*m_gain; return Sample{sx, sy}; } ); } else { std::transform( data1.begin(), data1.begin() + size1, m_data1p.begin(), [this](const Sample& s) -> Sample { Sample t; int64_t sx = s.real()*m_gain; int64_t sy = s.imag()*m_gain; int64_t x = sx*m_cos + sy*m_sin; int64_t y = sy*m_cos - sx*m_sin; t.setReal(x>>(SDR_RX_SAMP_SZ-1)); t.setImag(y>>(SDR_RX_SAMP_SZ-1)); return t; } ); } } switch (m_corrType) { case InterferometerSettings::Correlation0: results = performOpCorr(data0, size0, pdata1, size1, sFirst); break; case InterferometerSettings::Correlation1: results = performOpCorr(data0, size0, pdata1, size1, sSecond); break; case InterferometerSettings::CorrelationAdd: results = performOpCorr(data0, size0, pdata1, size1, sAdd); break; case InterferometerSettings::CorrelationMultiply: results = performOpCorr(data0, size0, pdata1, size1, sMulConj); break; case InterferometerSettings::CorrelationIFFT: results = performIFFTCorr(data0, size0, pdata1, size1); break; case InterferometerSettings::CorrelationIFFTStar: results = performIFFTCorr(data0, size0, pdata1, size1, true); break; case InterferometerSettings::CorrelationFFT: results = performFFTProd(data0, size0, pdata1, size1); break; case InterferometerSettings::CorrelationIFFT2: results = performIFFT2Corr(data0, size0, pdata1, size1); break; default: break; } return results; } bool InterferometerCorrelator::performOpCorr( const SampleVector& data0, unsigned int size0, const SampleVector* data1, unsigned int size1, Sample sampleOp(const Sample& a, const Sample& b) ) { unsigned int size = std::min(size0, size1); adjustTCorrSize(size); std::transform( data0.begin(), data0.begin() + size, data1->begin(), m_tcorr.begin(), sampleOp ); m_processed = size; m_remaining[0] = size0 - size; m_remaining[1] = size1 - size; return true; } bool InterferometerCorrelator::performIFFTCorr( const SampleVector& data0, unsigned int size0, const SampleVector* data1, unsigned int size1, bool star ) { unsigned int size = std::min(size0, size1); int nfft = 0; SampleVector::const_iterator begin0 = data0.begin(); SampleVector::const_iterator begin1 = data1->begin(); adjustSCorrSize(size); adjustTCorrSize(size); while (size >= m_fftSize) { // FFT[0] std::transform( begin0, begin0 + m_fftSize, m_fft[0]->in(), s2c ); m_window.apply(m_fft[0]->in()); std::fill(m_fft[0]->in() + m_fftSize, m_fft[0]->in() + 2*m_fftSize, std::complex{0, 0}); m_fft[0]->transform(); // FFT[1] std::transform( begin1, begin1 + m_fftSize, m_fft[1]->in(), s2c ); m_window.apply(m_fft[1]->in()); std::fill(m_fft[1]->in() + m_fftSize, m_fft[1]->in() + 2*m_fftSize, std::complex{0, 0}); m_fft[1]->transform(); // conjugate FFT[1] std::transform( m_fft[1]->out(), m_fft[1]->out() + 2*m_fftSize, m_dataj, [](const std::complex& c) -> std::complex { return std::conj(c); } ); // product of FFT[1]* with FFT[0] and store in inverse FFT input std::transform( m_fft[0]->out(), m_fft[0]->out() + 2*m_fftSize, m_dataj, m_invFFT->in(), [](std::complex& a, const std::complex& b) -> std::complex { return (a*b); } ); // copy product to correlation spectrum - convert and scale to FFT size and Hanning window std::transform( m_invFFT->in(), m_invFFT->in() + m_fftSize, m_scorr.begin() + nfft*m_fftSize, [this](const std::complex& a) -> Sample { Sample s; s.setReal(a.real()*(SDR_RX_SCALEF/m_fftSize)); s.setImag(a.imag()*(SDR_RX_SCALEF/m_fftSize)); return s; } ); // do the inverse FFT to get time correlation m_invFFT->transform(); if (star) { // sum first half with the reversed second half as one is the conjugate of the other this should yield constant phase *m_tcorr.begin() = invfft2star(m_invFFT->out()[0]); // t = 0 std::reverse(m_invFFT->out() + m_fftSize, m_invFFT->out() + 2*m_fftSize); std::transform( m_invFFT->out() + 1, m_invFFT->out() + m_fftSize, m_invFFT->out() + m_fftSize, m_tcorr.begin() + nfft*m_fftSize, [](const std::complex& a, const std::complex& b) -> Sample { Sample s; std::complex sum = a + b; s.setReal(sum.real()/12.0f); s.setImag(sum.imag()/12.0f); return s; } ); } else { std::transform( m_invFFT->out(), m_invFFT->out() + m_fftSize, m_tcorr.begin() + nfft*m_fftSize, [](const std::complex& a) -> Sample { Sample s; s.setReal(a.real()/6.0f); s.setImag(a.imag()/6.0f); return s; } ); } size -= m_fftSize; begin0 += m_fftSize; begin1 += m_fftSize; nfft++; } // update the samples counters m_processed = nfft*m_fftSize; m_remaining[0] = size0 - nfft*m_fftSize; m_remaining[1] = size1 - nfft*m_fftSize; return nfft > 0; } bool InterferometerCorrelator::performIFFT2Corr( const SampleVector& data0, unsigned int size0, const SampleVector* data1, unsigned int size1 ) { unsigned int size = std::min(size0, size1); int nfft = 0; SampleVector::const_iterator begin0 = data0.begin(); SampleVector::const_iterator begin1 = data1->begin(); adjustSCorrSize(size); adjustTCorrSize(size); while (size >= m_fftSize) { // FFT[0] std::transform( begin0, begin0 + m_fftSize, m_fft2[0]->in(), s2c ); m_window.apply(m_fft2[0]->in()); m_fft2[0]->transform(); // FFT[1] std::transform( begin1, begin1 + m_fftSize, m_fft2[1]->in(), s2c ); m_window.apply(m_fft2[1]->in()); m_fft2[1]->transform(); // conjugate FFT[1] std::transform( m_fft2[1]->out(), m_fft2[1]->out() + m_fftSize, m_dataj, [](const std::complex& c) -> std::complex { return std::conj(c); } ); // product of FFT[1]* with FFT[0] and store in inverse FFT input std::transform( m_fft2[0]->out(), m_fft2[0]->out() + m_fftSize, m_dataj, m_invFFT2->in(), [](std::complex& a, const std::complex& b) -> std::complex { return (a*b); } ); // copy product to correlation spectrum - convert and scale to FFT size std::transform( m_invFFT2->in(), m_invFFT2->in() + m_fftSize, m_scorr.begin() + nfft*m_fftSize, [this](const std::complex& a) -> Sample { Sample s; s.setReal(a.real()*(SDR_RX_SCALEF/m_fftSize)); s.setImag(a.imag()*(SDR_RX_SCALEF/m_fftSize)); return s; } ); // do the inverse FFT to get time correlation m_invFFT2->transform(); std::transform( m_invFFT2->out() + m_fftSize/2, m_invFFT2->out() + m_fftSize, m_tcorr.begin() + nfft*m_fftSize, [](const std::complex& a) -> Sample { Sample s; s.setReal(a.real()/3.0f); s.setImag(a.imag()/3.0f); return s; } ); std::transform( m_invFFT2->out(), m_invFFT2->out() + m_fftSize/2, m_tcorr.begin() + nfft*m_fftSize + m_fftSize/2, [](const std::complex& a) -> Sample { Sample s; s.setReal(a.real()/3.0f); s.setImag(a.imag()/3.0f); return s; } ); size -= m_fftSize; begin0 += m_fftSize; begin1 += m_fftSize; nfft++; } // update the samples counters m_processed = nfft*m_fftSize; m_remaining[0] = size0 - nfft*m_fftSize; m_remaining[1] = size1 - nfft*m_fftSize; return nfft > 0; } bool InterferometerCorrelator::performFFTProd( const SampleVector& data0, unsigned int size0, const SampleVector* data1, unsigned int size1 ) { unsigned int size = std::min(size0, size1); int nfft = 0; SampleVector::const_iterator begin0 = data0.begin(); SampleVector::const_iterator begin1 = data1->begin(); adjustSCorrSize(size); adjustTCorrSize(size); while (size >= m_fftSize) { // FFT[0] std::transform( begin0, begin0 + m_fftSize, m_fft2[0]->in(), s2cNorm ); m_window.apply(m_fft2[0]->in()); m_fft2[0]->transform(); // FFT[1] std::transform( begin1, begin1 + m_fftSize, m_fft2[1]->in(), s2cNorm ); m_window.apply(m_fft2[1]->in()); m_fft2[1]->transform(); // conjugate FFT[1] std::transform( m_fft2[1]->out(), m_fft2[1]->out() + m_fftSize, m_dataj, [](const std::complex& c) -> std::complex { return std::conj(c); } ); // product of FFT[1]* with FFT[0] and store in both results std::transform( m_fft2[0]->out(), m_fft2[0]->out() + m_fftSize, m_dataj, m_invFFT2->in(), [](std::complex& a, const std::complex& b) -> std::complex { return (a*b); } ); // copy product to time domain - re-order, convert and scale to FFT size std::transform( m_invFFT2->in(), m_invFFT2->in() + m_fftSize/2, m_tcorr.begin() + nfft*m_fftSize + m_fftSize/2, [](const std::complex& a) -> Sample { Sample s; s.setReal(a.real()/2.0f); s.setImag(a.imag()/2.0f); return s; } ); std::transform( m_invFFT2->in() + m_fftSize/2, m_invFFT2->in() + m_fftSize, m_tcorr.begin() + nfft*m_fftSize, [](const std::complex& a) -> Sample { Sample s; s.setReal(a.real()/2.0f); s.setImag(a.imag()/2.0f); return s; } ); // feed spectrum with the sum std::transform( begin0, begin0 + m_fftSize, begin1, m_scorr.begin() + nfft*m_fftSize, sAdd ); size -= m_fftSize; begin0 += m_fftSize; begin1 += m_fftSize; nfft++; } // update the samples counters m_processed = nfft*m_fftSize; m_remaining[0] = size0 - nfft*m_fftSize; m_remaining[1] = size1 - nfft*m_fftSize; return nfft > 0; } void InterferometerCorrelator::adjustSCorrSize(int size) { int nFFTSize = (size/m_fftSize)*m_fftSize; if (nFFTSize > m_scorrSize) { m_scorr.resize(nFFTSize); m_scorrSize = nFFTSize; } } void InterferometerCorrelator::adjustTCorrSize(int size) { int nFFTSize = (size/m_fftSize)*m_fftSize; if (nFFTSize > m_tcorrSize) { m_tcorr.resize(nFFTSize); m_tcorrSize = nFFTSize; } } void InterferometerCorrelator::setPhase(int phase) { m_phase = phase; if (phase == 0) { m_sin = 0; m_cos = 1<<(SDR_RX_SAMP_SZ-1); } else if (phase == 90) { m_sin = 1<<(SDR_RX_SAMP_SZ-1); m_cos = 0; } else if (phase == -90) { m_sin = -(1<<(SDR_RX_SAMP_SZ-1)); m_cos = 0; } else if ((phase == -180) || (phase == 180)) { m_sin = 0; m_cos = -(1<<(SDR_RX_SAMP_SZ-1)); } else { m_phase = phase % 180; double d_sin = sin(M_PI*(m_phase/180.0)) * (1<<(SDR_RX_SAMP_SZ-1)); double d_cos = cos(M_PI*(m_phase/180.0)) * (1<<(SDR_RX_SAMP_SZ-1)); m_sin = d_sin; m_cos = d_cos; } } void InterferometerCorrelator::setGain(int gainCB) { double db = gainCB / 10.0; m_gainCB = gainCB; m_gain = CalcDb::powerFromdB(db); }