1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-04-28 10:24:00 -04:00
sdrangel/sdrbase/dsp/firfilterrrc.h
2026-02-12 23:23:27 +01:00

154 lines
6.1 KiB
C++

///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2026 SDRangel contributors //
// //
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DSP_FIRFILTERRRC_H
#define INCLUDE_DSP_FIRFILTERRRC_H
#include <complex>
#include <vector>
#include "export.h"
/**
* @brief FIR root raised cosine filter for complex signals
*
* Implements a time-domain FIR root raised cosine (RRC) filter using direct
* convolution. The RRC filter is commonly used for pulse shaping in digital
* communications to minimize intersymbol interference (ISI) while satisfying
* the Nyquist criterion.
*
* This filter offers sample-by-sample processing with low latency, making it
* suitable for real-time applications. For longer filters or higher throughput,
* consider using FFTFilterRRC which uses frequency-domain processing.
*/
class SDRBASE_API FIRFilterRRC
{
public:
using Complex = std::complex<float>;
/**
* @brief Normalization modes for filter taps
*/
enum class Normalization {
Energy, //!< Impulse response energy normalized (TX+RX = raised cosine peak = 1) - for matched filter pairs
Amplitude, //!< Bipolar symbol sequence output amplitude normalized to ±1 - for pulse amplitude preservation
Gain //!< Unity gain (0 dB) - tap coefficients sum to 1 - for continuous wave input
};
/**
* @brief Construct FIR root raised cosine filter
*/
FIRFilterRRC();
/**
* @brief Destructor
*/
~FIRFilterRRC() = default;
/**
* @brief Create root raised cosine filter taps
*
* @param symbolRate Symbol rate (normalized to sample rate, 0 to 0.5)
* @param rolloff Roll-off factor (beta) - typically 0.2 to 0.5
* 0 = rectangular, 1 = full cosine roll-off
* @param symbolSpan Number of symbols over which filter is spread
* Typical values: 6-12 (more = better filtering, more delay)
* @param normalization How to normalize the filter taps
*
* Total number of taps = symbolSpan * samplesPerSymbol + 1 (always odd).
* For symbolRate=0.05 (20 samples/symbol), symbolSpan=8 gives 161 taps.
*/
void create(float symbolRate, float rolloff, int symbolSpan = 8,
Normalization normalization = Normalization::Energy);
/**
* @brief Process one complex input sample through the filter
*
* Direct convolution with stored tap coefficients. Processes samples
* one at a time with minimal latency.
*
* @param input Complex input sample
* @return Filtered complex output sample
*/
Complex filter(const Complex& input);
/**
* @brief Get current symbol rate
* @return Symbol rate (normalized)
*/
float getSymbolRate() const { return m_symbolRate; }
/**
* @brief Get current rolloff factor
* @return Rolloff factor (beta)
*/
float getRolloff() const { return m_rolloff; }
/**
* @brief Get number of taps in filter
* @return Number of filter taps
*/
int getNumTaps() const { return static_cast<int>(m_taps.size()); }
/**
* @brief Get samples per symbol
* @return Samples per symbol (1/symbolRate)
*/
int getSamplesPerSymbol() const { return m_samplesPerSymbol; }
/**
* @brief Get filter delay in samples
* @return Group delay (approximately numTaps/2)
*/
int getDelay() const { return getNumTaps() / 2; }
/**
* @brief Get reference to filter tap coefficients
*
* The taps are stored as half + center due to symmetry. The full impulse
* response can be reconstructed by mirroring the taps around the center.
*
* @return Reference to vector of filter tap coefficients (half + center)
*/
const std::vector<float>& getTaps() const { return m_taps; }
private:
/**
* @brief Compute time-domain RRC tap value
*
* Implements the mathematical RRC impulse response:
* h(t) = [sin(π*t/T*(1-β)) + 4*β*t/T*cos(π*t/T*(1+β))] / [π*t/T*(1-(4*β*t/T)²)]
*
* @param t Time index (in symbol periods)
* @param rolloff Roll-off factor (beta)
* @return Tap value at time t
*/
float computeRRCTap(float t, float rolloff) const;
std::vector<float> m_taps; //!< Filter tap coefficients (symmetric, stored as half + center)
std::vector<Complex> m_samples; //!< Circular buffer for input samples
size_t m_ptr; //!< Current position in circular buffer
float m_symbolRate; //!< Symbol rate (normalized)
float m_rolloff; //!< Rolloff factor (beta)
int m_samplesPerSymbol; //!< Samples per symbol (1/symbolRate)
};
#endif // INCLUDE_DSP_FIRFILTERRRC_H