From b8ccca5ebc1294608fbe26c6939f4d573023fd1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Tue, 1 Dec 2020 22:28:32 +0100 Subject: [PATCH] consolidated TDR bandpass/lowpass mode, configuration dialog for TDR --- Software/PC_Application/Application.pro | 9 +- Software/PC_Application/Traces/Math/tdr.cpp | 216 ++++++++++++++++++ .../Traces/Math/{tdrlowpass.h => tdr.h} | 17 +- .../Traces/Math/tdrbandpass.cpp | 85 ------- .../PC_Application/Traces/Math/tdrbandpass.h | 25 -- .../PC_Application/Traces/Math/tdrdialog.ui | 150 ++++++++++++ .../PC_Application/Traces/Math/tdrlowpass.cpp | 98 -------- .../PC_Application/Traces/Math/tracemath.cpp | 42 ++-- .../PC_Application/Traces/Math/tracemath.h | 3 +- .../Traces/Math/tracematheditdialog.cpp | 1 + .../Traces/Math/windowfunction.cpp | 145 ++++++++++++ .../Traces/Math/windowfunction.h | 48 ++++ Software/PC_Application/Traces/fftcomplex.cpp | 13 ++ Software/PC_Application/Traces/fftcomplex.h | 4 +- 14 files changed, 613 insertions(+), 243 deletions(-) create mode 100644 Software/PC_Application/Traces/Math/tdr.cpp rename Software/PC_Application/Traces/Math/{tdrlowpass.h => tdr.h} (57%) delete mode 100644 Software/PC_Application/Traces/Math/tdrbandpass.cpp delete mode 100644 Software/PC_Application/Traces/Math/tdrbandpass.h create mode 100644 Software/PC_Application/Traces/Math/tdrdialog.ui delete mode 100644 Software/PC_Application/Traces/Math/tdrlowpass.cpp create mode 100644 Software/PC_Application/Traces/Math/windowfunction.cpp create mode 100644 Software/PC_Application/Traces/Math/windowfunction.h diff --git a/Software/PC_Application/Application.pro b/Software/PC_Application/Application.pro index 2326e5c..110195a 100644 --- a/Software/PC_Application/Application.pro +++ b/Software/PC_Application/Application.pro @@ -26,10 +26,10 @@ HEADERS += \ Tools/eseries.h \ Tools/impedancematchdialog.h \ Traces/Math/medianfilter.h \ - Traces/Math/tdrbandpass.h \ - Traces/Math/tdrlowpass.h \ + Traces/Math/tdr.h \ Traces/Math/tracemath.h \ Traces/Math/tracematheditdialog.h \ + Traces/Math/windowfunction.h \ Traces/fftcomplex.h \ Traces/markerwidget.h \ Traces/trace.h \ @@ -83,10 +83,10 @@ SOURCES += \ Tools/eseries.cpp \ Tools/impedancematchdialog.cpp \ Traces/Math/medianfilter.cpp \ - Traces/Math/tdrbandpass.cpp \ - Traces/Math/tdrlowpass.cpp \ + Traces/Math/tdr.cpp \ Traces/Math/tracemath.cpp \ Traces/Math/tracematheditdialog.cpp \ + Traces/Math/windowfunction.cpp \ Traces/fftcomplex.cpp \ Traces/markerwidget.cpp \ Traces/trace.cpp \ @@ -134,6 +134,7 @@ FORMS += \ Traces/Math/medianexplanationwidget.ui \ Traces/Math/medianfilterdialog.ui \ Traces/Math/newtracemathdialog.ui \ + Traces/Math/tdrdialog.ui \ Traces/Math/tracematheditdialog.ui \ Traces/markerwidget.ui \ Traces/smithchartdialog.ui \ diff --git a/Software/PC_Application/Traces/Math/tdr.cpp b/Software/PC_Application/Traces/Math/tdr.cpp new file mode 100644 index 0000000..006fb86 --- /dev/null +++ b/Software/PC_Application/Traces/Math/tdr.cpp @@ -0,0 +1,216 @@ +#include "tdr.h" + +#include "Traces/fftcomplex.h" +#include "ui_tdrdialog.h" +#include +#include +using namespace Math; +using namespace std; + +TDR::TDR() +{ + automaticDC = true; + manualDC = 1.0; + stepResponse = true; + mode = Mode::Lowpass; + + connect(&window, &WindowFunction::changed, this, &TDR::updateTDR); +} + +TraceMath::DataType TDR::outputType(TraceMath::DataType inputType) +{ + if(inputType == DataType::Frequency) { + return DataType::Time; + } else { + return DataType::Invalid; + } +} + +QString TDR::description() +{ + QString ret = "TDR ("; + if(mode == Mode::Lowpass) { + ret += "lowpass)"; + if(stepResponse) { + ret += ", with step response"; + } + } else { + ret += "bandpass)"; + } + ret += ", window: " + window.getDescription(); + + return ret; +} + +void TDR::edit() +{ + auto d = new QDialog(); + auto ui = new Ui::TDRDialog; + ui->setupUi(d); + ui->windowBox->setLayout(new QVBoxLayout); + ui->windowBox->layout()->addWidget(window.createEditor()); + + auto updateEnabledWidgets = [=]() { + bool enable = mode == Mode::Lowpass; + ui->computeStepResponse->setEnabled(enable); + enable &= stepResponse; + ui->DCmanual->setEnabled(enable); + ui->DCautomatic->setEnabled(enable); + enable &= !automaticDC; + ui->manualPhase->setEnabled(enable); + ui->manualMag->setEnabled(enable); + }; + + connect(ui->mode, qOverload(&QComboBox::currentIndexChanged), [=](int index){ + mode = (Mode) index; + updateEnabledWidgets(); + updateTDR(); + }); + + connect(ui->computeStepResponse, &QCheckBox::toggled, [=](bool computeStep) { + stepResponse = computeStep; + updateEnabledWidgets(); + updateTDR(); + }); + + connect(ui->DCmanual, &QRadioButton::toggled, [=](bool manual) { + automaticDC = !manual; + updateEnabledWidgets(); + updateTDR(); + }); + + if(automaticDC) { + ui->DCautomatic->setChecked(true); + } else { + ui->DCmanual->setChecked(true); + } + ui->computeStepResponse->setChecked(stepResponse); + + ui->manualMag->setUnit("dBm"); + ui->manualMag->setPrecision(3); + ui->manualMag->setValue(20*log10(abs(manualDC))); + ui->manualPhase->setUnit("°"); + ui->manualPhase->setPrecision(4); + ui->manualPhase->setValue(180.0/M_PI * arg(manualDC)); + + connect(ui->manualMag, &SIUnitEdit::valueChanged, [=](double newval){ + manualDC = polar(pow(10, newval / 20.0), arg(manualDC)); + updateTDR(); + }); + connect(ui->manualPhase, &SIUnitEdit::valueChanged, [=](double newval){ + manualDC = polar(abs(manualDC), newval * M_PI / 180.0); + updateTDR(); + }); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, d, &QDialog::accept); + d->show(); +} + +QWidget *TDR::createExplanationWidget() +{ + return new QLabel("Test"); +} + +void TDR::inputSamplesChanged(unsigned int begin, unsigned int end) +{ + Q_UNUSED(begin); + if(input->rData().size() >= 2) { + // TDR is computationally expensive, only update at the end of sweep + if(end != input->rData().size()) { + // not the end, do nothing + return; + } + vector> frequencyDomain; + auto stepSize = input->rData()[1].x - input->rData()[0].x; + if(mode == Mode::Lowpass) { + if(stepResponse) { + auto steps = input->rData().size(); + auto firstStep = input->rData().front().x; + // frequency points need to be evenly spaced all the way to DC + if(firstStep == 0) { + // zero as first step would result in infinite number of points, skip and start with second + firstStep = input->rData()[1].x; + steps--; + } + if(firstStep * steps != input->rData().back().x) { + // data is not available with correct frequency spacing, calculate required steps + steps = input->rData().back().x / firstStep; + } + frequencyDomain.resize(2 * steps + 1); + // copy frequencies, use the flipped conjugate for negative part + for(unsigned int i = 1;i<=steps;i++) { + auto S = input->getInterpolatedSample(stepSize * i).y; + frequencyDomain[steps - i] = conj(S); + frequencyDomain[steps + i] = S; + } + if(automaticDC) { + // use simple extrapolation from lowest two points to extract DC value + auto abs_DC = 2.0 * abs(frequencyDomain[steps + 1]) - abs(frequencyDomain[steps + 2]); + auto phase_DC = 2.0 * arg(frequencyDomain[steps + 1]) - arg(frequencyDomain[steps + 2]); + frequencyDomain[steps] = polar(abs_DC, phase_DC); + } else { + frequencyDomain[steps] = manualDC; + } + } else { + auto steps = input->rData().size(); + unsigned int offset = 0; + if(input->rData().front().x == 0) { + // DC measurement is inaccurate, skip + steps--; + offset++; + } + // no step response required, can use frequency values as they are. No extra extrapolated DC value here -> 2 values less than with step response + frequencyDomain.resize(2 * steps - 1); + frequencyDomain[steps - 1] = input->rData()[offset].y; + for(unsigned int i = 1;irData()[i + offset].y; + frequencyDomain[steps - i - 1] = conj(S); + frequencyDomain[steps + i - 1] = S; + } + } + } else { + // bandpass mode + // Can use input data directly, no need to extend with complex conjugate + frequencyDomain.resize(input->rData().size()); + for(unsigned int i=0;irData().size();i++) { + frequencyDomain[i] = input->rData()[i].y; + } + } + + window.apply(frequencyDomain); + Fft::shift(frequencyDomain, true); + + auto fft_bins = frequencyDomain.size(); + const double fs = 1.0 / (stepSize * fft_bins); + + Fft::transform(frequencyDomain, true); + + data.clear(); + data.resize(fft_bins); + + for(unsigned int i = 0;irData().size()); + } +} diff --git a/Software/PC_Application/Traces/Math/tdrlowpass.h b/Software/PC_Application/Traces/Math/tdr.h similarity index 57% rename from Software/PC_Application/Traces/Math/tdrlowpass.h rename to Software/PC_Application/Traces/Math/tdr.h index 12dca8f..5420c3f 100644 --- a/Software/PC_Application/Traces/Math/tdrlowpass.h +++ b/Software/PC_Application/Traces/Math/tdr.h @@ -2,13 +2,14 @@ #define TDRLOWPASS_H #include "tracemath.h" +#include "windowfunction.h" namespace Math { -class TDRLowpass : public TraceMath +class TDR : public TraceMath { public: - TDRLowpass(); + TDR(); DataType outputType(DataType inputType) override; QString description() override; @@ -18,6 +19,18 @@ public: public slots: void inputSamplesChanged(unsigned int begin, unsigned int end) override; + +private: + void updateTDR(); + enum class Mode { + Lowpass, + Bandpass, + }; + Mode mode; + WindowFunction window; + bool stepResponse; + bool automaticDC; + std::complex manualDC; }; } diff --git a/Software/PC_Application/Traces/Math/tdrbandpass.cpp b/Software/PC_Application/Traces/Math/tdrbandpass.cpp deleted file mode 100644 index afd8f14..0000000 --- a/Software/PC_Application/Traces/Math/tdrbandpass.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "tdrbandpass.h" - -#include "Traces/fftcomplex.h" - - -#include -using namespace Math; -using namespace std; - -TDRBandpass::TDRBandpass() -{ - -} - -TraceMath::DataType TDRBandpass::outputType(TraceMath::DataType inputType) -{ - if(inputType == DataType::Frequency) { - return DataType::Time; - } else { - return DataType::Invalid; - } -} - -QString TDRBandpass::description() -{ - return "TDR (bandpass mode)"; -} - -void TDRBandpass::edit() -{ - // nothing to do for now -} - -QWidget *TDRBandpass::createExplanationWidget() -{ - return new QLabel("Test"); -} - -void TDRBandpass::inputSamplesChanged(unsigned int begin, unsigned int end) -{ - Q_UNUSED(begin); - const double PI = 3.141592653589793238463; - if(input->rData().size() >= 2) { - // TDR is computationally expensive, only update at the end of sweep - if(end != input->rData().size()) { - // not the end, do nothing - return; - } - // create vector for frequency data - vector> dft(end); - // window data and perform FFTshift ("zero"-bin expected at index 0 but is in the middle of input data) - for(unsigned int i=0;irData()[i].y * hamming; - // swap upper and lower half - if(i < end/2) { - dft[i + (end+1)/2] = windowed; - } else { - dft[i - end/2] = windowed; - } - } - // perform IFT operation on data - Fft::transform(dft, true); - - // calculate sample distance - auto freqStep = input->rData()[1].x - input->rData()[0].x; - auto timeStep = 1.0 / (freqStep * end); - - // copy DFT data to output data - data.resize(dft.size()); - for(unsigned int i=0;i + + TDRDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 268 + 421 + + + + TDR + + + true + + + + + + + + Mode: + + + + + + + + Lowpass + + + + + Bandpass + + + + + + + + + + Compute Step Response + + + + + + + DC point + + + + + + false + + + Automatic (extrapolate) + + + buttonGroup + + + + + + + false + + + Specify manually: + + + buttonGroup + + + + + + + + + Magnitude: + + + + + + + false + + + + + + + Phase: + + + + + + + false + + + + + + + + + + + + Window + + + + + + + QDialogButtonBox::Ok + + + + + + + + SIUnitEdit + QLineEdit +
CustomWidgets/siunitedit.h
+
+
+ + + + + +
diff --git a/Software/PC_Application/Traces/Math/tdrlowpass.cpp b/Software/PC_Application/Traces/Math/tdrlowpass.cpp deleted file mode 100644 index aa0ddb0..0000000 --- a/Software/PC_Application/Traces/Math/tdrlowpass.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "tdrlowpass.h" - -#include "Traces/fftcomplex.h" - - -#include -using namespace Math; -using namespace std; - -TDRLowpass::TDRLowpass() -{ - -} - -TraceMath::DataType TDRLowpass::outputType(TraceMath::DataType inputType) -{ - if(inputType == DataType::Frequency) { - return DataType::Time; - } else { - return DataType::Invalid; - } -} - -QString TDRLowpass::description() -{ - return "TDR (lowpass mode)"; -} - -void TDRLowpass::edit() -{ - // nothing to do for now -} - -QWidget *TDRLowpass::createExplanationWidget() -{ - return new QLabel("Test"); -} - -void TDRLowpass::inputSamplesChanged(unsigned int begin, unsigned int end) -{ - Q_UNUSED(begin); - if(input->rData().size() >= 2) { - // TDR is computationally expensive, only update at the end of sweep - if(end != input->rData().size()) { - // not the end, do nothing - return; - } - auto steps = input->rData().size(); - auto firstStep = input->rData().front().x; - if(firstStep == 0) { - // zero as first step would result in infinite number of points, skip and start with second - firstStep = input->rData()[1].x; - steps--; - } - if(firstStep * steps != input->rData().back().x) { - // data is not available with correct frequency spacing, calculate required steps - steps = input->rData().back().x / firstStep; - } - const double PI = 3.141592653589793238463; - // reserve vector for negative frequenies and DC as well - vector> frequencyDomain(2*steps + 1); - // copy frequencies, use the flipped conjugate for negative part - for(unsigned int i = 1;i<=steps;i++) { - auto S = input->getInterpolatedSample(firstStep * i).y; - constexpr double alpha0 = 0.54; - auto hamming = alpha0 - (1.0 - alpha0) * -cos(PI * i / steps); - S *= hamming; - frequencyDomain[2 * steps - i + 1] = conj(S); - frequencyDomain[i] = S; - } - // use simple extrapolation from lowest two points to extract DC value - auto abs_DC = 2.0 * abs(frequencyDomain[1]) - abs(frequencyDomain[2]); - auto phase_DC = 2.0 * arg(frequencyDomain[1]) - arg(frequencyDomain[2]); - frequencyDomain[0] = polar(abs_DC, phase_DC); - - auto fft_bins = frequencyDomain.size(); - const double fs = 1.0 / (firstStep * fft_bins); - - Fft::transform(frequencyDomain, true); - - data.clear(); - data.resize(fft_bins); - - for(unsigned int i = 0;iinput) { removeInput(); this->input = input; - inputTypeChanged(input->dataType); - // do initial calculation - inputSamplesChanged(0, input->data.size()); - // connect to input - connect(input, &TraceMath::outputSamplesChanged, this, &TraceMath::inputSamplesChanged); connect(input, &TraceMath::outputTypeChanged, this, &TraceMath::inputTypeChanged); + inputTypeChanged(input->dataType); } } void TraceMath::inputTypeChanged(TraceMath::DataType type) { auto newType = outputType(type); - if(newType != dataType) { - dataType = newType; - data.clear(); + dataType = newType; + data.clear(); + if(dataType == DataType::Invalid) { + error("Invalid input data"); + disconnect(input, &TraceMath::outputSamplesChanged, this, &TraceMath::inputSamplesChanged); + updateStepResponse(false); + } else { + connect(input, &TraceMath::outputSamplesChanged, this, &TraceMath::inputSamplesChanged); inputSamplesChanged(0, input->data.size()); - emit outputTypeChanged(dataType); - if(dataType == DataType::Invalid) { - error("Invalid input data"); - updateStepResponse(false); - } } + emit outputTypeChanged(dataType); } void TraceMath::warning(QString warn) diff --git a/Software/PC_Application/Traces/Math/tracemath.h b/Software/PC_Application/Traces/Math/tracemath.h index 52a2616..7fa16c1 100644 --- a/Software/PC_Application/Traces/Math/tracemath.h +++ b/Software/PC_Application/Traces/Math/tracemath.h @@ -66,8 +66,7 @@ public: enum class Type { MedianFilter, - TDRlowpass, - TDRbandpass, + TDR, // Add new math operations here, do not explicitly assign values and keep the Last entry at the last position Last, }; diff --git a/Software/PC_Application/Traces/Math/tracematheditdialog.cpp b/Software/PC_Application/Traces/Math/tracematheditdialog.cpp index d592f83..809cd11 100644 --- a/Software/PC_Application/Traces/Math/tracematheditdialog.cpp +++ b/Software/PC_Application/Traces/Math/tracematheditdialog.cpp @@ -57,6 +57,7 @@ TraceMathEditDialog::TraceMathEditDialog(Trace &t, QWidget *parent) : // always show the widget for the selected function connect(ui->list, &QListWidget::currentRowChanged, ui->stack, &QStackedWidget::setCurrentIndex); + connect(ui->list, &QListWidget::doubleClicked, ui->buttonBox, &QDialogButtonBox::accepted); connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){ auto newMath = TraceMath::createMath(static_cast(ui->list->currentRow())); if(newMath) { diff --git a/Software/PC_Application/Traces/Math/windowfunction.cpp b/Software/PC_Application/Traces/Math/windowfunction.cpp new file mode 100644 index 0000000..2a82a1a --- /dev/null +++ b/Software/PC_Application/Traces/Math/windowfunction.cpp @@ -0,0 +1,145 @@ +#include "windowfunction.h" +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include "CustomWidgets/siunitedit.h" + +QString WindowFunction::typeToName(WindowFunction::Type type) +{ + switch(type) { + case Type::Rectangular: return "Rectangular"; break; +// case Type::Kaiser: return "Kaiser"; break; + case Type::Hamming: return "Hamming"; break; + case Type::Hann: return "Hann"; break; + case Type::Blackman: return "Blackman"; break; + case Type::Gaussian: return "Gaussian"; break; + case Type::Chebyshev: return "Chebyshev"; break; + default: return "Invalid"; break; + } +} + +WindowFunction::WindowFunction(WindowFunction::Type type) +{ + this->type = type; + // set default parameters + kaiser_alpha = 3.0; + gaussian_sigma = 0.4; + chebyshev_alpha = 5; +} + +void WindowFunction::apply(std::vector > &data) +{ + unsigned int N = data.size(); + for(unsigned int n = 0;nsetLayout(layout); + auto cbType = new QComboBox(); + for(unsigned int i=0;i<(unsigned int) Type::Last;i++) { + cbType->addItem(typeToName((Type) i)); + } + + layout->addRow(new QLabel("Type:"), cbType); + + QObject::connect(cbType, qOverload(&QComboBox::currentIndexChanged), [=](int newIndex){ + if(layout->rowCount() > 1) { + layout->removeRow(1); + } + type = (Type) newIndex; + QLabel *paramLabel = nullptr; + SIUnitEdit *paramEdit = nullptr; + // add GUI elements for window types that have a parameter + switch(type) { + case Type::Gaussian: + paramLabel = new QLabel("Parameter σ:"); + paramEdit = new SIUnitEdit("", " ", 3); + paramEdit->setValue(gaussian_sigma); + QObject::connect(paramEdit, &SIUnitEdit::valueChanged, [=](double newval) { + gaussian_sigma = newval; + }); + break; + case Type::Chebyshev: + paramLabel = new QLabel("Parameter α:"); + paramEdit = new SIUnitEdit("", " ", 3); + paramEdit->setValue(chebyshev_alpha); + QObject::connect(paramEdit, &SIUnitEdit::valueChanged, [=](double newval) { + chebyshev_alpha = newval; + }); + break; +// case Type::Kaiser: +// // TODO +// break; + default: + break; + } + if(paramLabel != nullptr && paramEdit != nullptr) { + layout->addRow(paramLabel, paramEdit); + QObject::connect(paramEdit, &SIUnitEdit::valueChanged, this, &WindowFunction::changed); + } + emit changed(); + }); + + cbType->setCurrentIndex((int) type); + + return top; +} + +WindowFunction::Type WindowFunction::getType() const +{ + return type; +} + +QString WindowFunction::getDescription() +{ + QString ret = typeToName(type); + if(type == Type::Gaussian) { + ret += ", σ=" + QString::number(gaussian_sigma); + } else if(type == Type::Chebyshev) { + ret += ", α=" + QString::number(chebyshev_alpha); + } + return ret; +} + +double WindowFunction::getFactor(unsigned int n, unsigned int N) +{ + // all formulas from https://en.wikipedia.org/wiki/Window_function + switch(type) { + case Type::Rectangular: + // nothing to do + return 1.0; +// case Type::Kaiser: +// // TODO +// break; + case Type::Hamming: + return 25.0/46.0 - (21.0/46.0) * cos(2*M_PI*n / N); + case Type::Hann: + return pow(sin(M_PI*n / N), 2.0); + case Type::Blackman: + return 0.42 - 0.5 * cos(2*M_PI*n / N) + 0.08 * cos(4*M_PI*n / N); + case Type::Gaussian: + return exp(-0.5 * pow((n - (double) N/2) / (gaussian_sigma * N / 2), 2)); + case Type::Chebyshev: { + double beta = cosh(1.0 / N * acosh(pow(10, chebyshev_alpha))); + double T_N_arg = beta * cos(M_PI*n/(N+1)); + double T_N; + if(T_N_arg >= 1.0) { + T_N = cosh(N * acosh(T_N_arg)); + } else if(T_N_arg <= -1.0) { + T_N = pow(-1.0, N) * cosh(N * acosh(T_N_arg)); + } else { + T_N = cos(N * acos(T_N_arg)); + } + return T_N / pow(10.0, chebyshev_alpha); + } + default: + return 1.0; + } +} diff --git a/Software/PC_Application/Traces/Math/windowfunction.h b/Software/PC_Application/Traces/Math/windowfunction.h new file mode 100644 index 0000000..8d106d2 --- /dev/null +++ b/Software/PC_Application/Traces/Math/windowfunction.h @@ -0,0 +1,48 @@ +#ifndef WINDOWFUNCTION_H +#define WINDOWFUNCTION_H + +#include +#include +#include + +class WindowFunction : public QObject +{ + Q_OBJECT; +public: + enum class Type { + Rectangular, +// Kaiser, + Gaussian, + Chebyshev, + Hann, + Hamming, + Blackman, + // always has to be the last entry + Last, + }; + static QString typeToName(Type type); + + WindowFunction(Type type = Type::Hamming); + + void apply(std::vector>& data); + + QWidget *createEditor(); + + Type getType() const; + QString getDescription(); + +signals: + void changed(); + +private: + double getFactor(unsigned int n, unsigned int N); + Type type; + // parameters for the different types. Not all windows use one and most only one. + // But keeping all parameters for all windows allows switching between window types + // while remembering the settings for each type + double kaiser_alpha; + double gaussian_sigma; + double chebyshev_alpha; +}; + +#endif // WINDOWFUNCTION_H diff --git a/Software/PC_Application/Traces/fftcomplex.cpp b/Software/PC_Application/Traces/fftcomplex.cpp index 86cad8f..cdfa177 100644 --- a/Software/PC_Application/Traces/fftcomplex.cpp +++ b/Software/PC_Application/Traces/fftcomplex.cpp @@ -26,6 +26,7 @@ #include #include #include "fftcomplex.h" +#include using std::complex; using std::size_t; @@ -150,3 +151,15 @@ static size_t reverseBits(size_t val, int width) { result = (result << 1) | (val & 1U); return result; } + +void Fft::shift(std::vector > &vec, bool inverse) +{ + int rotate_len = vec.size() / 2; + if(vec.size() % 0x01 != 0) { + // odd size, behavior depends on whether this is an inverse shift + if(!inverse) { + rotate_len++; + } + } + std::rotate(vec.begin(), vec.begin() + rotate_len, vec.end()); +} diff --git a/Software/PC_Application/Traces/fftcomplex.h b/Software/PC_Application/Traces/fftcomplex.h index ae5f0f5..fe9cdef 100644 --- a/Software/PC_Application/Traces/fftcomplex.h +++ b/Software/PC_Application/Traces/fftcomplex.h @@ -26,9 +26,11 @@ #include #include - namespace Fft { + // swap left/right halves of the vector, similar to matlabs fftshift/ifftshift + void shift(std::vector > &vec, bool inverse); + /* * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector. * The vector can have any length. This is a wrapper function. The inverse transform does not perform scaling, so it is not a true inverse.