diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index d89f30201..244e63bee 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -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 diff --git a/sdrbase/dsp/fftfilterrrc.cpp b/sdrbase/dsp/fftfilterrrc.cpp index 23229eda0..228d3ddff 100644 --- a/sdrbase/dsp/fftfilterrrc.cpp +++ b/sdrbase/dsp/fftfilterrrc.cpp @@ -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 diff --git a/sdrbase/dsp/fftfilterrrc.h b/sdrbase/dsp/fftfilterrrc.h index 78d48320e..d186b2926 100644 --- a/sdrbase/dsp/fftfilterrrc.h +++ b/sdrbase/dsp/fftfilterrrc.h @@ -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 * diff --git a/sdrbase/dsp/firfilterrrc.cpp b/sdrbase/dsp/firfilterrrc.cpp new file mode 100644 index 000000000..784f387a5 --- /dev/null +++ b/sdrbase/dsp/firfilterrrc.cpp @@ -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 // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsp/firfilterrrc.h" + +#include +#include + +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(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(i - center) / static_cast(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(m_samples.size()); + const int halfTaps = static_cast(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(numTaps) - 1) ? 0 : m_ptr + 1; + + return acc; +} diff --git a/sdrbase/dsp/firfilterrrc.h b/sdrbase/dsp/firfilterrrc.h new file mode 100644 index 000000000..5f18a0440 --- /dev/null +++ b/sdrbase/dsp/firfilterrrc.h @@ -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 // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DSP_FIRFILTERRRC_H +#define INCLUDE_DSP_FIRFILTERRRC_H + +#include +#include + +#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; + + /** + * @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(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& 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 m_taps; //!< Filter tap coefficients (symmetric, stored as half + center) + std::vector 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 diff --git a/sdrbench/CMakeLists.txt b/sdrbench/CMakeLists.txt index 4cfcd5c46..35d3ae141 100644 --- a/sdrbench/CMakeLists.txt +++ b/sdrbench/CMakeLists.txt @@ -12,6 +12,7 @@ set(sdrbench_SOURCES test_callsign.cpp test_ft8protocols.cpp test_fftrrc.cpp + test_firrrc.cpp ) set(sdrbench_HEADERS diff --git a/sdrbench/mainbench.cpp b/sdrbench/mainbench.cpp index 06e6cd200..e2cf5fbb1 100644 --- a/sdrbench/mainbench.cpp +++ b/sdrbench/mainbench.cpp @@ -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(); } diff --git a/sdrbench/mainbench.h b/sdrbench/mainbench.h index 41efa9a3b..5f91c88f8 100644 --- a/sdrbench/mainbench.h +++ b/sdrbench/mainbench.h @@ -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); diff --git a/sdrbench/parserbench.cpp b/sdrbench/parserbench.cpp index 1490f0a84..1942d40d9 100644 --- a/sdrbench/parserbench.cpp +++ b/sdrbench/parserbench.cpp @@ -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; } diff --git a/sdrbench/parserbench.h b/sdrbench/parserbench.h index 874c70dd5..dc0e092f2 100644 --- a/sdrbench/parserbench.h +++ b/sdrbench/parserbench.h @@ -41,7 +41,8 @@ public: TestFT8, TestCallsign, TestFT8Protocols, - TestFFTRRCFilter + TestFFTRRCFilter, + TestFIRRRCFilter } TestType; ParserBench(); diff --git a/sdrbench/test_fftrrc.cpp b/sdrbench/test_fftrrc.cpp index ecf29c3c8..efaa606b3 100644 --- a/sdrbench/test_fftrrc.cpp +++ b/sdrbench/test_fftrrc.cpp @@ -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++; diff --git a/sdrbench/test_firrrc.cpp b/sdrbench/test_firrrc.cpp new file mode 100644 index 000000000..5c28fe351 --- /dev/null +++ b/sdrbench/test_firrrc.cpp @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2026 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 "mainbench.h" +#include "dsp/firfilterrrc.h" +#include + +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& 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); +} diff --git a/sdrbench/test_fftrrc.py b/sdrbench/test_rrc.py similarity index 86% rename from sdrbench/test_fftrrc.py rename to sdrbench/test_rrc.py index f25790bc2..3d8fa9d42 100755 --- a/sdrbench/test_fftrrc.py +++ b/sdrbench/test_rrc.py @@ -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')