mirror of
https://github.com/f4exb/sdrangel.git
synced 2026-02-13 21:23:41 -05:00
FIR RRC filter
This commit is contained in:
parent
26455261ee
commit
649312d7ba
@ -153,6 +153,7 @@ set(sdrbase_SOURCES
|
||||
dsp/filerecord.cpp
|
||||
dsp/filerecordinterface.cpp
|
||||
dsp/firfilter.cpp
|
||||
dsp/firfilterrrc.cpp
|
||||
dsp/fmpreemphasis.cpp
|
||||
dsp/freqlockcomplex.cpp
|
||||
dsp/interpolator.cpp
|
||||
@ -383,6 +384,7 @@ set(sdrbase_HEADERS
|
||||
dsp/filerecord.h
|
||||
dsp/filerecordinterface.h
|
||||
dsp/firfilter.h
|
||||
dsp/firfilterrrc.h
|
||||
dsp/fmpreemphasis.h
|
||||
dsp/freqlockcomplex.h
|
||||
dsp/gfft.h
|
||||
|
||||
@ -78,10 +78,9 @@ void FFTFilterRRC::create(float symbolRate, float rolloff)
|
||||
m_rolloff = 1.0f;
|
||||
}
|
||||
|
||||
// Initialize filter to zero
|
||||
// Create filter directly in frequency domain
|
||||
std::fill(m_filter, m_filter + m_fftLen, Complex(0.0f, 0.0f));
|
||||
|
||||
// Compute frequency-domain RRC response for each FFT bin
|
||||
for (int i = 0; i < m_fftLen; i++) {
|
||||
m_filter[i] = computeRRCResponse(m_symbolRate, m_rolloff, i, m_fftLen);
|
||||
}
|
||||
@ -121,23 +120,23 @@ FFTFilterRRC::Complex FFTFilterRRC::computeRRCResponse(
|
||||
// Absolute frequency
|
||||
float absFreq = std::abs(freq);
|
||||
|
||||
// Symbol time (inverse of symbol rate)
|
||||
float T = 1.0f / symbolRate;
|
||||
|
||||
// Compute frequency boundaries
|
||||
float f1 = (1.0f - rolloff) / (2.0f * T); // Start of transition band
|
||||
float f2 = (1.0f + rolloff) / (2.0f * T); // End of transition band
|
||||
// For RRC: passband is Rs/2, where Rs is symbol rate
|
||||
// Transition band: Rs/2 * (1-beta) to Rs/2 * (1+beta)
|
||||
float f1 = symbolRate * (1.0f - rolloff) / 2.0f; // Start of transition band
|
||||
float f2 = symbolRate * (1.0f + rolloff) / 2.0f; // End of transition band
|
||||
|
||||
Complex response;
|
||||
|
||||
if (absFreq <= f1) {
|
||||
// Passband: constant response
|
||||
response = Complex(std::sqrt(T), 0.0f);
|
||||
response = Complex(1.0f, 0.0f);
|
||||
} else if (absFreq > f1 && absFreq <= f2) {
|
||||
// Transition band: raised cosine roll-off
|
||||
// H(f) = sqrt(T) * 0.5 * [1 + cos(pi*T/beta * (|f| - (1-beta)/(2T)))]
|
||||
float arg = (M_PI * T / rolloff) * (absFreq - f1);
|
||||
float amplitude = std::sqrt(T) * 0.5f * (1.0f + std::cos(arg));
|
||||
// H(f) = 0.5 * [1 + cos(pi/beta * (|f|/Rs - (1-beta)/2))]
|
||||
float normalizedFreq = absFreq / symbolRate; // Normalize by symbol rate
|
||||
float arg = (M_PI / rolloff) * (normalizedFreq - (1.0f - rolloff) / 2.0f);
|
||||
float amplitude = 0.5f * (1.0f + std::cos(arg));
|
||||
response = Complex(amplitude, 0.0f);
|
||||
} else {
|
||||
// Stopband: zero response
|
||||
|
||||
@ -68,9 +68,11 @@ public:
|
||||
*
|
||||
* The filter bandwidth extends from -symbolRate*(1+rolloff)/2 to
|
||||
* +symbolRate*(1+rolloff)/2 in normalized frequency (0 to 0.5 = Nyquist).
|
||||
*
|
||||
* Creates mathematically correct RRC frequency response for use in
|
||||
* digital communications applications.
|
||||
*/
|
||||
void create(float symbolRate, float rolloff);
|
||||
|
||||
/**
|
||||
* @brief Process one complex input sample through the filter
|
||||
*
|
||||
|
||||
192
sdrbase/dsp/firfilterrrc.cpp
Normal file
192
sdrbase/dsp/firfilterrrc.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dsp/firfilterrrc.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
FIRFilterRRC::FIRFilterRRC() :
|
||||
m_ptr(0),
|
||||
m_symbolRate(0.0f),
|
||||
m_rolloff(0.0f),
|
||||
m_samplesPerSymbol(0)
|
||||
{
|
||||
}
|
||||
|
||||
void FIRFilterRRC::create(float symbolRate, float rolloff, int symbolSpan, Normalization normalization)
|
||||
{
|
||||
m_symbolRate = symbolRate;
|
||||
m_rolloff = rolloff;
|
||||
|
||||
// Clamp rolloff to valid range [0, 1]
|
||||
if (m_rolloff < 0.0f) {
|
||||
m_rolloff = 0.0f;
|
||||
} else if (m_rolloff > 1.0f) {
|
||||
m_rolloff = 1.0f;
|
||||
}
|
||||
|
||||
// Calculate samples per symbol
|
||||
m_samplesPerSymbol = static_cast<int>(std::round(1.0f / symbolRate));
|
||||
|
||||
// Calculate number of taps (always odd for symmetry)
|
||||
int numTaps = symbolSpan * m_samplesPerSymbol + 1;
|
||||
if ((numTaps & 1) == 0) {
|
||||
numTaps++; // Ensure odd number of taps
|
||||
}
|
||||
|
||||
// Allocate tap storage (only half + center due to symmetry)
|
||||
int halfTaps = numTaps / 2 + 1;
|
||||
m_taps.resize(halfTaps);
|
||||
|
||||
// Calculate filter taps
|
||||
int center = numTaps / 2;
|
||||
for (int i = 0; i < halfTaps; i++)
|
||||
{
|
||||
// Time offset from center in symbol periods
|
||||
float t = static_cast<float>(i - center) / static_cast<float>(m_samplesPerSymbol);
|
||||
m_taps[i] = computeRRCTap(t, m_rolloff);
|
||||
}
|
||||
|
||||
// Apply normalization
|
||||
if (normalization == Normalization::Energy)
|
||||
{
|
||||
// Normalize energy: sqrt(sum of squares) = 1
|
||||
float sum = 0.0f;
|
||||
for (int i = 0; i < halfTaps - 1; i++) {
|
||||
sum += m_taps[i] * m_taps[i] * 2.0f; // Two symmetric taps
|
||||
}
|
||||
sum += m_taps[halfTaps - 1] * m_taps[halfTaps - 1]; // Center tap
|
||||
float scale = std::sqrt(sum);
|
||||
|
||||
if (scale > 0.0f) {
|
||||
for (int i = 0; i < halfTaps; i++) {
|
||||
m_taps[i] /= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (normalization == Normalization::Amplitude)
|
||||
{
|
||||
// Normalize amplitude: max output for bipolar sequence ≈ 1
|
||||
float maxGain = 0.0f;
|
||||
for (int offset = 0; offset < m_samplesPerSymbol; offset++)
|
||||
{
|
||||
float gain = 0.0f;
|
||||
for (int i = offset; i < halfTaps - 1; i += m_samplesPerSymbol) {
|
||||
gain += std::abs(m_taps[i]) * 2.0f; // Both sides
|
||||
}
|
||||
// Add center tap if aligned
|
||||
if ((center - offset) % m_samplesPerSymbol == 0) {
|
||||
gain += std::abs(m_taps[halfTaps - 1]);
|
||||
}
|
||||
if (gain > maxGain) {
|
||||
maxGain = gain;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxGain > 0.0f) {
|
||||
for (int i = 0; i < halfTaps; i++) {
|
||||
m_taps[i] /= maxGain;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (normalization == Normalization::Gain)
|
||||
{
|
||||
// Normalize for unity gain: sum of taps = 1
|
||||
float sum = 0.0f;
|
||||
for (int i = 0; i < halfTaps - 1; i++) {
|
||||
sum += m_taps[i] * 2.0f; // Two symmetric taps
|
||||
}
|
||||
sum += m_taps[halfTaps - 1]; // Center tap
|
||||
|
||||
if (sum > 0.0f) {
|
||||
for (int i = 0; i < halfTaps; i++) {
|
||||
m_taps[i] /= sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize sample buffer
|
||||
m_samples.resize(numTaps);
|
||||
std::fill(m_samples.begin(), m_samples.end(), Complex(0.0f, 0.0f));
|
||||
m_ptr = 0;
|
||||
}
|
||||
|
||||
float FIRFilterRRC::computeRRCTap(float t, float rolloff) const
|
||||
{
|
||||
constexpr float Ts = 1.0f; // Symbol period (normalized)
|
||||
const float beta = rolloff;
|
||||
|
||||
// Handle special case: t = 0
|
||||
if (t == 0.0f) {
|
||||
// h(0) = (1/T) * (1 + beta*(4/π - 1))
|
||||
return (1.0f / Ts) * (1.0f + beta * (4.0f / M_PI - 1.0f));
|
||||
}
|
||||
|
||||
// Check for zeros of denominator: 1 - (4*β*t/T)² = 0
|
||||
// This occurs at t = ±T/(4*β)
|
||||
const float denomCheck = 4.0f * beta * t / Ts;
|
||||
if (std::abs(std::abs(denomCheck) - 1.0f) < 1e-6f && beta > 0.0f)
|
||||
{
|
||||
// h(±T/4β) = (β/(T*√2)) * [(1+2/π)*sin(π/4β) + (1-2/π)*cos(π/4β)]
|
||||
const float arg = M_PI / (4.0f * beta);
|
||||
return (beta / (Ts * std::sqrt(2.0f))) *
|
||||
((1.0f + 2.0f / M_PI) * std::sin(arg) +
|
||||
(1.0f - 2.0f / M_PI) * std::cos(arg));
|
||||
}
|
||||
|
||||
// General case
|
||||
const float numerator = std::sin(M_PI * t / Ts * (1.0f - beta)) +
|
||||
4.0f * beta * t / Ts * std::cos(M_PI * t / Ts * (1.0f + beta));
|
||||
const float denominator = M_PI * t / Ts * (1.0f - denomCheck * denomCheck);
|
||||
|
||||
return numerator / (denominator * Ts);
|
||||
}
|
||||
|
||||
FIRFilterRRC::Complex FIRFilterRRC::filter(const Complex& input)
|
||||
{
|
||||
const int numTaps = static_cast<int>(m_samples.size());
|
||||
const int halfTaps = static_cast<int>(m_taps.size()) - 1;
|
||||
|
||||
// Store input sample in circular buffer
|
||||
m_samples[m_ptr] = input;
|
||||
|
||||
// Perform convolution using symmetry
|
||||
Complex acc(0.0f, 0.0f);
|
||||
int a = m_ptr;
|
||||
int b = (a == numTaps - 1) ? 0 : a + 1;
|
||||
|
||||
// Process symmetric pairs
|
||||
for (int i = 0; i < halfTaps; i++)
|
||||
{
|
||||
acc += (m_samples[a] + m_samples[b]) * m_taps[i];
|
||||
|
||||
a = (a == 0) ? numTaps - 1 : a - 1;
|
||||
b = (b == numTaps - 1) ? 0 : b + 1;
|
||||
}
|
||||
|
||||
// Add center tap
|
||||
acc += m_samples[a] * m_taps[halfTaps];
|
||||
|
||||
// Advance circular buffer pointer
|
||||
m_ptr = (m_ptr == static_cast<size_t>(numTaps) - 1) ? 0 : m_ptr + 1;
|
||||
|
||||
return acc;
|
||||
}
|
||||
153
sdrbase/dsp/firfilterrrc.h
Normal file
153
sdrbase/dsp/firfilterrrc.h
Normal file
@ -0,0 +1,153 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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
|
||||
@ -12,6 +12,7 @@ set(sdrbench_SOURCES
|
||||
test_callsign.cpp
|
||||
test_ft8protocols.cpp
|
||||
test_fftrrc.cpp
|
||||
test_firrrc.cpp
|
||||
)
|
||||
|
||||
set(sdrbench_HEADERS
|
||||
|
||||
@ -75,6 +75,8 @@ void MainBench::run()
|
||||
testFT8Protocols(m_parser.getArgsStr());
|
||||
} else if (m_parser.getTestType() == ParserBench::TestFFTRRCFilter) {
|
||||
testFFTRRCFilter();
|
||||
} else if (m_parser.getTestType() == ParserBench::TestFIRRRCFilter) {
|
||||
testFIRRRCFilter();
|
||||
} else {
|
||||
qDebug() << "MainBench::run: unknown test type: " << m_parser.getTestType();
|
||||
}
|
||||
|
||||
@ -58,6 +58,7 @@ private:
|
||||
void testDecimateFF();
|
||||
void testGolay2312();
|
||||
void testFFTRRCFilter();
|
||||
void testFIRRRCFilter();
|
||||
void testFT8(const QString& wavFile, const QString& argsStr); //!< use with sdrbench/samples/ft8/230105_091630.wav in -f option
|
||||
void testFT8Protocols(const QString& argsStr);
|
||||
void testCallsign(const QString& argsStr);
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
ParserBench::ParserBench() :
|
||||
m_testOption(QStringList() << "t" << "test",
|
||||
"Test type: decimateii, decimatefi, decimateff, decimateif, decimateinfii, decimatesupii, ambe, golay2312, ft8, ft8protocols, callsign, fftrrcfilter.",
|
||||
"Test type: decimateii, decimatefi, decimateff, decimateif, decimateinfii, decimatesupii, ambe, golay2312, ft8, ft8protocols, callsign, fftrrcfilter, firrrcfilter.",
|
||||
"test",
|
||||
"decimateii"),
|
||||
m_nbSamplesOption(QStringList() << "n" << "nb-samples",
|
||||
@ -153,6 +153,8 @@ ParserBench::TestType ParserBench::getTestType() const
|
||||
return TestFT8Protocols;
|
||||
} else if (m_testStr == "fftrrcfilter") {
|
||||
return TestFFTRRCFilter;
|
||||
} else if (m_testStr == "firrrcfilter") {
|
||||
return TestFIRRRCFilter;
|
||||
} else {
|
||||
return TestDecimatorsII;
|
||||
}
|
||||
|
||||
@ -41,7 +41,8 @@ public:
|
||||
TestFT8,
|
||||
TestCallsign,
|
||||
TestFT8Protocols,
|
||||
TestFFTRRCFilter
|
||||
TestFFTRRCFilter,
|
||||
TestFIRRRCFilter
|
||||
} TestType;
|
||||
|
||||
ParserBench();
|
||||
|
||||
@ -28,22 +28,26 @@ void MainBench::testFFTRRCFilter()
|
||||
filter.create(0.05f, 0.35f); // 2400 baud, 0.35 rolloff
|
||||
|
||||
qDebug() << "MainBench::testFFTRRCFilter: filter created";
|
||||
FILE *fd_filter = fopen("test_fftrrc_filter.txt", "w");
|
||||
FILE *fd_filter = fopen("test_rrc_filter.txt", "w");
|
||||
|
||||
for (int i = 0; i < RRC_FFT_SIZE; i++) {
|
||||
fprintf(fd_filter, "%f\n", std::abs(filter.getFilter()[i]));
|
||||
}
|
||||
qDebug() << "MainBench::testFFTRRCFilter: filter coefficients written to test_fftrrc_filter.txt";
|
||||
qDebug() << "MainBench::testFFTRRCFilter: filter coefficients written to test_rrc_filter.txt";
|
||||
fclose(fd_filter);
|
||||
|
||||
qDebug() << "MainBench::testFFTRRCFilter: running filter";
|
||||
FILE *fd = fopen("test_fftrrc.txt", "w");
|
||||
FILE *fd = fopen("test_rrc.txt", "w");
|
||||
int outLen = 0;
|
||||
|
||||
for (int i = 0; i < 1024; i++)
|
||||
for (int i = 0; i < 5000; i++)
|
||||
{
|
||||
Real phi = i * (48000.0 / 1200.0) * (2*3.141);
|
||||
Real x = sin(phi);
|
||||
int ss = 48000 / 2400; // Samples per symbol at 2400 baud
|
||||
int d = i / ss; // Symbol index at 2400 baud
|
||||
Real s = (d % 2 == 0) ? 1.0f : -1.0f; // BPSK symbol sequence
|
||||
Real x = (i % ss == 0 ? s : 0.0f); // Pulsed signal at 2400 Hz
|
||||
// Real phi = i * (1200.0 / 48000.0) * (2*3.141);
|
||||
// Real x = sin(phi) > 0.0 ? 1.0f : -1.0f; // Simulate a BPSK signal at 1200 baud
|
||||
filter.process(Complex(x, 0.0f), &rrcFilterOut);
|
||||
outLen++;
|
||||
|
||||
|
||||
50
sdrbench/test_firrrc.cpp
Normal file
50
sdrbench/test_firrrc.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2026 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "mainbench.h"
|
||||
#include "dsp/firfilterrrc.h"
|
||||
#include <QDebug>
|
||||
|
||||
void MainBench::testFIRRRCFilter()
|
||||
{
|
||||
qDebug() << "MainBench::testFIRRRCFilter";
|
||||
FIRFilterRRC filter;
|
||||
filter.create(0.05f, 0.35f, 8, FIRFilterRRC::Normalization::Gain); // 2400 baud, 0.35 rolloff
|
||||
|
||||
qDebug() << "MainBench::testFIRRRCFilter: filter created";
|
||||
FILE *fd_filter = fopen("test_rrc_filter.txt", "w");
|
||||
const std::vector<float>& taps = filter.getTaps();
|
||||
for (auto tap : taps) {
|
||||
fprintf(fd_filter, "%f\n", std::abs(tap));
|
||||
}
|
||||
qDebug() << "MainBench::testFIRRRCFilter: filter coefficients written to test_rrc_filter.txt";
|
||||
fclose(fd_filter);
|
||||
|
||||
qDebug() << "MainBench::testFIRRRCFilter: running filter";
|
||||
FILE *fd = fopen("test_rrc.txt", "w");
|
||||
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
Real phi = i * (1200.0 / 48000.0) * (2*3.141);
|
||||
Real x = sin(phi) > 0.0 ? 1.0f : -1.0f; // Simulate a BPSK signal at 1200 baud
|
||||
Complex rrc = filter.filter(Complex(x, 0.0f));
|
||||
fprintf(fd, "%f\n", rrc.real());
|
||||
}
|
||||
|
||||
qDebug() << "MainBench::testFIRRRCFilter: output samples written to test_firrrc.txt";
|
||||
fclose(fd);
|
||||
}
|
||||
@ -2,13 +2,13 @@
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
with open('../build/test_fftrrc_filter.txt', 'r') as f:
|
||||
with open('../build/test_rrc_filter.txt', 'r') as f:
|
||||
filter_data = f.read()
|
||||
|
||||
filter_out = [float(x) for x in filter_data.splitlines()]
|
||||
xf = np.array(filter_out)
|
||||
|
||||
with open('../build/test_fftrrc.txt', 'r') as f:
|
||||
with open('../build/test_rrc.txt', 'r') as f:
|
||||
data = f.read()
|
||||
|
||||
out = [float(x) for x in data.splitlines()]
|
||||
@ -24,7 +24,7 @@ ax2.set_xlabel('Index')
|
||||
ax2.set_ylabel('Amplitude')
|
||||
ax2.grid(True)
|
||||
|
||||
ax1.set_title('FFTRRC Filter Output (Real Part)')
|
||||
ax1.set_title('RRC Filter Output (Real Part)')
|
||||
ax1.plot(x)
|
||||
# ax1.scatter(marks, x[marks],
|
||||
# color='red', marker='.', s=100, zorder=5, edgecolors='black')
|
||||
Loading…
x
Reference in New Issue
Block a user