consolidated TDR bandpass/lowpass mode, configuration dialog for TDR

This commit is contained in:
Jan Käberich 2020-12-01 22:28:32 +01:00
parent 2039c8f74d
commit b8ccca5ebc
14 changed files with 613 additions and 243 deletions

View File

@ -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 \

View File

@ -0,0 +1,216 @@
#include "tdr.h"
#include "Traces/fftcomplex.h"
#include "ui_tdrdialog.h"
#include <QVBoxLayout>
#include <QLabel>
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<int>(&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<complex<double>> 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;i<steps;i++) {
auto S = input->rData()[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;i<input->rData().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;i<fft_bins;i++) {
data[i].x = fs * i;
data[i].y = frequencyDomain[i] / (double) fft_bins;
}
if(stepResponse && mode == Mode::Lowpass) {
updateStepResponse(true);
} else {
updateStepResponse(false);
}
emit outputSamplesChanged(0, data.size());
success();
} else {
// not enough input data
data.clear();
updateStepResponse(false);
emit outputSamplesChanged(0, 0);
warning("Not enough input samples");
}
}
void TDR::updateTDR()
{
if(dataType != DataType::Invalid) {
inputSamplesChanged(0, input->rData().size());
}
}

View File

@ -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<double> manualDC;
};
}

View File

@ -1,85 +0,0 @@
#include "tdrbandpass.h"
#include "Traces/fftcomplex.h"
#include <QLabel>
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<complex<double>> 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;i<end;i++) {
// TODO implement other windows as well
constexpr double alpha0 = 0.54;
auto hamming = alpha0 - (1.0 - alpha0) * cos(2 * PI * i / end);
auto windowed = input->rData()[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<dft.size();i++) {
data[i].y = dft[i];
data[i].x = i * timeStep;
}
emit outputSamplesChanged(0, data.size());
success();
} else {
// not enough input data
data.clear();
emit outputSamplesChanged(0, 0);
warning("Not enough input samples");
}
}

View File

@ -1,25 +0,0 @@
#ifndef TDRBANDPASS_H
#define TDRBANDPASS_H
#include "tracemath.h"
namespace Math {
class TDRBandpass : public TraceMath
{
public:
TDRBandpass();
DataType outputType(DataType inputType) override;
QString description() override;
void edit() override;
static QWidget* createExplanationWidget();
public slots:
void inputSamplesChanged(unsigned int begin, unsigned int end) override;
};
}
#endif // TDRBANDPASS_H

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TDRDialog</class>
<widget class="QDialog" name="TDRDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>268</width>
<height>421</height>
</rect>
</property>
<property name="windowTitle">
<string>TDR</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,1,0">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Mode:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="mode">
<item>
<property name="text">
<string>Lowpass</string>
</property>
</item>
<item>
<property name="text">
<string>Bandpass</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="computeStepResponse">
<property name="text">
<string>Compute Step Response</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>DC point</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="DCautomatic">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Automatic (extrapolate)</string>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="DCmanual">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Specify manually:</string>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Magnitude:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="SIUnitEdit" name="manualMag">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Phase:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="manualPhase">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="windowBox">
<property name="title">
<string>Window</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SIUnitEdit</class>
<extends>QLineEdit</extends>
<header>CustomWidgets/siunitedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="buttonGroup"/>
</buttongroups>
</ui>

View File

@ -1,98 +0,0 @@
#include "tdrlowpass.h"
#include "Traces/fftcomplex.h"
#include <QLabel>
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<complex<double>> 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;i<fft_bins;i++) {
data[i].x = fs * i;
data[i].y = frequencyDomain[i] / (double) fft_bins;
}
updateStepResponse(true);
emit outputSamplesChanged(0, data.size());
success();
} else {
// not enough input data
data.clear();
updateStepResponse(false);
emit outputSamplesChanged(0, 0);
warning("Not enough input samples");
}
}

View File

@ -1,8 +1,7 @@
#include "tracemath.h"
#include "medianfilter.h"
#include "tdrbandpass.h"
#include "tdrlowpass.h"
#include "tdr.h"
TraceMath::TraceMath()
{
@ -16,10 +15,8 @@ TraceMath *TraceMath::createMath(TraceMath::Type type)
switch(type) {
case Type::MedianFilter:
return new Math::MedianFilter();
case Type::TDRlowpass:
return new Math::TDRLowpass();
case Type::TDRbandpass:
return new Math::TDRBandpass();
case Type::TDR:
return new Math::TDR();
default:
return nullptr;
}
@ -33,13 +30,9 @@ TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type)
ret.name = "Median filter";
ret.explanationWidget = Math::MedianFilter::createExplanationWidget();
break;
case Type::TDRlowpass:
ret.name = "TDR (lowpass)";
ret.explanationWidget = Math::TDRLowpass::createExplanationWidget();
break;
case Type::TDRbandpass:
ret.name = "TDR (bandpass)";
ret.explanationWidget = Math::TDRBandpass::createExplanationWidget();
case Type::TDR:
ret.name = "TDR";
ret.explanationWidget = Math::TDR::createExplanationWidget();
break;
default:
break;
@ -110,28 +103,25 @@ void TraceMath::assignInput(TraceMath *input)
if(input != this->input) {
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)

View File

@ -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,
};

View File

@ -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<TraceMath::Type>(ui->list->currentRow()));
if(newMath) {

View File

@ -0,0 +1,145 @@
#include "windowfunction.h"
#define _USE_MATH_DEFINES
#include <math.h>
#include <QComboBox>
#include <QLabel>
#include <QFormLayout>
#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<std::complex<double> > &data)
{
unsigned int N = data.size();
for(unsigned int n = 0;n<N;n++) {
data[n] *= getFactor(n, N);
}
}
QWidget *WindowFunction::createEditor()
{
auto top = new QWidget();
auto layout = new QFormLayout();
top->setLayout(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<int>(&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;
}
}

View File

@ -0,0 +1,48 @@
#ifndef WINDOWFUNCTION_H
#define WINDOWFUNCTION_H
#include <QWidget>
#include <complex>
#include <vector>
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<std::complex<double>>& 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

View File

@ -26,6 +26,7 @@
#include <stdexcept>
#include <utility>
#include "fftcomplex.h"
#include <algorithm>
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<std::complex<double> > &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());
}

View File

@ -26,9 +26,11 @@
#include <complex>
#include <vector>
namespace Fft {
// swap left/right halves of the vector, similar to matlabs fftshift/ifftshift
void shift(std::vector<std::complex<double> > &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.