2020-08-31 04:03:41 +08:00
|
|
|
#include "tracemarker.h"
|
|
|
|
#include <QPainter>
|
2020-10-20 23:03:49 +08:00
|
|
|
#include "CustomWidgets/siunitedit.h"
|
|
|
|
#include <QHBoxLayout>
|
|
|
|
#include <QLabel>
|
|
|
|
#include <QSpinBox>
|
|
|
|
#include <QDebug>
|
|
|
|
#include "tracemarkermodel.h"
|
|
|
|
#include "unit.h"
|
2020-08-31 04:03:41 +08:00
|
|
|
|
2020-10-23 20:12:45 +08:00
|
|
|
using namespace std;
|
|
|
|
|
2020-11-03 07:37:06 +08:00
|
|
|
TraceMarker::TraceMarker(TraceMarkerModel *model, int number, TraceMarker *parent, QString descr)
|
2020-12-10 11:07:52 +08:00
|
|
|
: editingFrequency(false),
|
2020-10-20 23:03:49 +08:00
|
|
|
model(model),
|
2020-09-21 20:06:20 +08:00
|
|
|
parentTrace(nullptr),
|
2020-11-23 04:25:41 +08:00
|
|
|
position(1000000000),
|
2020-10-20 03:21:04 +08:00
|
|
|
number(number),
|
2020-10-20 23:03:49 +08:00
|
|
|
data(0),
|
|
|
|
type(Type::Manual),
|
2020-11-03 07:37:06 +08:00
|
|
|
description(descr),
|
2020-10-21 01:15:06 +08:00
|
|
|
delta(nullptr),
|
2020-11-03 07:37:06 +08:00
|
|
|
parent(parent),
|
2020-12-03 05:15:34 +08:00
|
|
|
cutoffAmplitude(-3.0),
|
|
|
|
peakThreshold(-40.0),
|
|
|
|
offset(10000)
|
2020-08-31 04:03:41 +08:00
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
TraceMarker::~TraceMarker()
|
|
|
|
{
|
|
|
|
if(parentTrace) {
|
|
|
|
parentTrace->removeMarker(this);
|
|
|
|
}
|
2020-10-21 01:15:06 +08:00
|
|
|
deleteHelperMarkers();
|
2020-08-31 04:03:41 +08:00
|
|
|
emit deleted(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TraceMarker::assignTrace(Trace *t)
|
|
|
|
{
|
|
|
|
if(parentTrace) {
|
|
|
|
// remove connection from previous parent trace
|
|
|
|
parentTrace->removeMarker(this);
|
2020-12-03 05:15:34 +08:00
|
|
|
disconnect(parentTrace, &Trace::deleted, this, nullptr);
|
2020-08-31 04:03:41 +08:00
|
|
|
}
|
|
|
|
parentTrace = t;
|
2020-10-23 20:12:45 +08:00
|
|
|
if(!getSupportedTypes().count(type)) {
|
|
|
|
// new trace does not support the current type
|
|
|
|
setType(Type::Manual);
|
|
|
|
}
|
|
|
|
|
2020-08-31 04:03:41 +08:00
|
|
|
connect(parentTrace, &Trace::deleted, this, &TraceMarker::parentTraceDeleted);
|
|
|
|
connect(parentTrace, &Trace::dataChanged, this, &TraceMarker::traceDataChanged);
|
|
|
|
connect(parentTrace, &Trace::colorChanged, this, &TraceMarker::updateSymbol);
|
2020-12-03 05:15:34 +08:00
|
|
|
connect(parentTrace, &Trace::typeChanged, [=](){
|
|
|
|
emit domainChanged();
|
|
|
|
checkDeltaMarker();
|
|
|
|
});
|
2020-11-23 04:25:41 +08:00
|
|
|
constrainPosition();
|
2020-08-31 04:03:41 +08:00
|
|
|
updateSymbol();
|
|
|
|
parentTrace->addMarker(this);
|
2020-10-21 01:15:06 +08:00
|
|
|
for(auto m : helperMarkers) {
|
|
|
|
m->assignTrace(t);
|
|
|
|
}
|
2020-10-20 23:03:49 +08:00
|
|
|
update();
|
2020-11-03 07:37:06 +08:00
|
|
|
emit traceChanged(this);
|
2020-08-31 04:03:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
Trace *TraceMarker::trace()
|
|
|
|
{
|
|
|
|
return parentTrace;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString TraceMarker::readableData()
|
|
|
|
{
|
2020-12-05 20:33:42 +08:00
|
|
|
if(!parentTrace) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
if(position < parentTrace->minX() || position > parentTrace->maxX()) {
|
|
|
|
return "";
|
|
|
|
}
|
2020-12-03 05:15:34 +08:00
|
|
|
if(isTimeDomain()) {
|
|
|
|
switch(type) {
|
|
|
|
case Type::Manual: {
|
|
|
|
QString ret;
|
|
|
|
auto impulse = data.real();
|
|
|
|
auto step = parentTrace->sample(parentTrace->index(position), Trace::SampleType::TimeStep).y.real();
|
|
|
|
ret += "Impulse:"+Unit::ToString(impulse, "", "m ", 3);
|
|
|
|
if(!isnan(step)) {
|
|
|
|
ret += " Step:"+Unit::ToString(step, "", "m ", 3);
|
|
|
|
if(abs(step) < 1.0) {
|
|
|
|
auto impedance = 50.0 * (1.0 + step) / (1.0 - step);
|
|
|
|
ret += " Impedance:"+Unit::ToString(impedance, "Ω", "m kM", 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
2020-11-23 04:25:41 +08:00
|
|
|
}
|
2020-12-03 05:15:34 +08:00
|
|
|
case Type::Delta: {
|
|
|
|
if(!delta || !delta->isTimeDomain()) {
|
|
|
|
return "Invalid delta marker";
|
|
|
|
}
|
|
|
|
// calculate difference between markers
|
|
|
|
auto impulse = data.real() - delta->data.real();
|
|
|
|
QString ret;
|
|
|
|
auto timeDiff = position - delta->position;
|
|
|
|
auto distanceDiff = parentTrace->timeToDistance(position) - delta->parentTrace->timeToDistance(delta->position);
|
|
|
|
ret += "Δ:"+Unit::ToString(timeDiff, "s", "fpnum ", 4) + "/" + Unit::ToString(distanceDiff, "m", "m k", 4);
|
|
|
|
ret += " ΔImpulse:"+Unit::ToString(impulse, "", "m ", 3);
|
|
|
|
auto step = parentTrace->sample(parentTrace->index(position), Trace::SampleType::TimeStep).y.real();
|
|
|
|
auto stepDelta = delta->parentTrace->sample(delta->parentTrace->index(delta->position), Trace::SampleType::TimeStep).y.real();
|
|
|
|
if(!isnan(step) && !isnan(stepDelta)) {
|
|
|
|
auto stepDiff = step - stepDelta;
|
|
|
|
ret += " ΔStep:"+Unit::ToString(stepDiff, "", "m ", 3);
|
|
|
|
if(abs(step) < 1.0 && abs(stepDelta) < 1.0) {
|
|
|
|
auto impedance = 50.0 * (1.0 + step) / (1.0 - step);
|
|
|
|
auto impedanceDelta = 50.0 * (1.0 + stepDelta) / (1.0 - stepDelta);
|
|
|
|
auto impedanceDiff = impedance - impedanceDelta;
|
|
|
|
ret += " ΔImpedance:"+Unit::ToString(impedanceDiff, "Ω", "m kM", 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return "Invalid type";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch(type) {
|
|
|
|
case Type::Manual:
|
|
|
|
case Type::Maximum:
|
|
|
|
case Type::Minimum: {
|
|
|
|
auto phase = arg(data);
|
|
|
|
return QString::number(toDecibel(), 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4);
|
|
|
|
}
|
|
|
|
case Type::Delta:
|
2020-12-05 19:59:23 +08:00
|
|
|
if(!delta || delta->isTimeDomain()) {
|
2020-12-03 05:15:34 +08:00
|
|
|
return "Invalid delta marker";
|
|
|
|
} else {
|
2020-11-23 04:25:41 +08:00
|
|
|
// calculate difference between markers
|
|
|
|
auto freqDiff = position - delta->position;
|
|
|
|
auto valueDiff = data / delta->data;
|
|
|
|
auto phase = arg(valueDiff);
|
|
|
|
auto db = 20*log10(abs(valueDiff));
|
|
|
|
return Unit::ToString(freqDiff, "Hz", " kMG") + " / " + QString::number(db, 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4);
|
2020-10-21 01:15:06 +08:00
|
|
|
}
|
2020-12-03 05:15:34 +08:00
|
|
|
break;
|
|
|
|
case Type::Noise:
|
|
|
|
return Unit::ToString(parentTrace->getNoise(position), "dbm/Hz", " ", 3);
|
|
|
|
case Type::PeakTable:
|
|
|
|
return "Found " + QString::number(helperMarkers.size()) + " peaks";
|
|
|
|
case Type::Lowpass:
|
|
|
|
case Type::Highpass:
|
|
|
|
if(parentTrace->isReflection()) {
|
|
|
|
return "Calculation not possible with reflection measurement";
|
2020-10-21 01:15:06 +08:00
|
|
|
} else {
|
2020-12-03 05:15:34 +08:00
|
|
|
auto insertionLoss = toDecibel();
|
|
|
|
auto cutoff = helperMarkers[0]->toDecibel();
|
|
|
|
QString ret = "fc: ";
|
|
|
|
if(cutoff > insertionLoss + cutoffAmplitude) {
|
|
|
|
// the trace never dipped below the specified cutoffAmplitude, exact cutoff frequency unknown
|
|
|
|
ret += type == Type::Lowpass ? ">" : "<";
|
|
|
|
}
|
|
|
|
ret += Unit::ToString(helperMarkers[0]->position, "Hz", " kMG", 4);
|
|
|
|
ret += ", Ins.Loss: >=" + QString::number(-insertionLoss, 'g', 4) + "db";
|
|
|
|
return ret;
|
2020-10-21 01:15:06 +08:00
|
|
|
}
|
2020-12-03 05:15:34 +08:00
|
|
|
break;
|
|
|
|
case Type::Bandpass:
|
|
|
|
if(parentTrace->isReflection()) {
|
|
|
|
return "Calculation not possible with reflection measurement";
|
|
|
|
} else {
|
|
|
|
auto insertionLoss = toDecibel();
|
|
|
|
auto cutoffL = helperMarkers[0]->toDecibel();
|
|
|
|
auto cutoffH = helperMarkers[1]->toDecibel();
|
|
|
|
auto bandwidth = helperMarkers[1]->position - helperMarkers[0]->position;
|
|
|
|
auto center = helperMarkers[2]->position;
|
|
|
|
QString ret = "fc: ";
|
|
|
|
if(cutoffL > insertionLoss + cutoffAmplitude || cutoffH > insertionLoss + cutoffAmplitude) {
|
|
|
|
// the trace never dipped below the specified cutoffAmplitude, center and exact bandwidth unknown
|
|
|
|
ret += "?, BW: >";
|
|
|
|
} else {
|
|
|
|
ret += Unit::ToString(center, "Hz", " kMG", 5)+ ", BW: ";
|
|
|
|
}
|
|
|
|
ret += Unit::ToString(bandwidth, "Hz", " kMG", 4);
|
|
|
|
ret += ", Ins.Loss: >=" + QString::number(-insertionLoss, 'g', 4) + "db";
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Type::TOI: {
|
|
|
|
auto avgFundamental = (helperMarkers[0]->toDecibel() + helperMarkers[1]->toDecibel()) / 2;
|
|
|
|
auto avgDistortion = (helperMarkers[2]->toDecibel() + helperMarkers[3]->toDecibel()) / 2;
|
|
|
|
auto TOI = (3 * avgFundamental - avgDistortion) / 2;
|
|
|
|
return "Fundamental: " + Unit::ToString(avgFundamental, "dbm", " ", 3) + ", distortion: " + Unit::ToString(avgDistortion, "dbm", " ", 3) + ", TOI: "+Unit::ToString(TOI, "dbm", " ", 3);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Type::PhaseNoise: {
|
|
|
|
auto carrier = toDecibel();
|
|
|
|
auto phasenoise = parentTrace->getNoise(helperMarkers[0]->position) - carrier;
|
|
|
|
return Unit::ToString(phasenoise, "dbc/Hz", " ", 3) +"@" + Unit::ToString(offset, "Hz", " kM", 4) + " offset (" + Unit::ToString(position, "Hz", " kMG", 6) + " carrier)";
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return "Unknown marker type";
|
2020-10-21 01:15:06 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString TraceMarker::readableSettings()
|
|
|
|
{
|
2020-12-03 05:15:34 +08:00
|
|
|
if(isTimeDomain()) {
|
|
|
|
switch(type) {
|
|
|
|
case Type::Manual:
|
|
|
|
case Type::Delta:
|
|
|
|
return Unit::ToString(position, "s", "fpnum ", 4) + "/" + Unit::ToString(parentTrace->timeToDistance(position), "m", "um k", 4);
|
|
|
|
default:
|
|
|
|
return "Unhandled case";
|
|
|
|
}
|
|
|
|
} else {
|
2020-11-23 04:25:41 +08:00
|
|
|
switch(type) {
|
|
|
|
case Type::Manual:
|
|
|
|
case Type::Maximum:
|
|
|
|
case Type::Minimum:
|
|
|
|
case Type::Delta:
|
|
|
|
case Type::Noise:
|
|
|
|
return Unit::ToString(position, "Hz", " kMG", 6);
|
|
|
|
case Type::Lowpass:
|
|
|
|
case Type::Highpass:
|
|
|
|
case Type::Bandpass:
|
|
|
|
return Unit::ToString(cutoffAmplitude, "db", " ", 3);
|
|
|
|
case Type::PeakTable:
|
|
|
|
return Unit::ToString(peakThreshold, "db", " ", 3);
|
|
|
|
case Type::TOI:
|
|
|
|
return "none";
|
|
|
|
case Type::PhaseNoise:
|
|
|
|
return Unit::ToString(offset, "Hz", " kM", 4);
|
|
|
|
default:
|
|
|
|
return "Unhandled case";
|
|
|
|
}
|
2020-12-03 05:15:34 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString TraceMarker::tooltipSettings()
|
|
|
|
{
|
|
|
|
if(isTimeDomain()) {
|
|
|
|
switch(type) {
|
|
|
|
case Type::Manual:
|
|
|
|
case Type::Delta:
|
|
|
|
return "Time/Distance";
|
|
|
|
default:
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch(type) {
|
|
|
|
case Type::Manual:
|
|
|
|
case Type::Maximum:
|
|
|
|
case Type::Minimum:
|
|
|
|
case Type::Delta:
|
|
|
|
case Type::Noise:
|
|
|
|
return "Marker frequency";
|
|
|
|
case Type::Lowpass:
|
|
|
|
case Type::Highpass:
|
|
|
|
case Type::Bandpass:
|
|
|
|
return "Cutoff amplitude (relativ to peak)";
|
|
|
|
case Type::PeakTable:
|
|
|
|
return "Peak threshold";
|
|
|
|
case Type::PhaseNoise:
|
|
|
|
return "Frequency offset";
|
|
|
|
default:
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
}
|
2020-08-31 04:03:41 +08:00
|
|
|
}
|
|
|
|
|
2020-11-03 07:37:06 +08:00
|
|
|
QString TraceMarker::readableType()
|
|
|
|
{
|
|
|
|
if(parent) {
|
|
|
|
return description;
|
|
|
|
} else {
|
|
|
|
return typeToString(type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 04:25:41 +08:00
|
|
|
void TraceMarker::setPosition(double pos)
|
2020-08-31 04:03:41 +08:00
|
|
|
{
|
2020-11-23 04:25:41 +08:00
|
|
|
position = pos;
|
|
|
|
constrainPosition();
|
2020-08-31 04:03:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void TraceMarker::parentTraceDeleted(Trace *t)
|
|
|
|
{
|
|
|
|
if(t == parentTrace) {
|
|
|
|
delete this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TraceMarker::traceDataChanged()
|
|
|
|
{
|
2020-11-23 04:25:41 +08:00
|
|
|
complex<double> newdata;
|
2020-12-05 19:59:23 +08:00
|
|
|
if(!parentTrace || parentTrace->numSamples() == 0) {
|
|
|
|
// no data, invalidate
|
|
|
|
newdata = numeric_limits<complex<double>>::quiet_NaN();
|
|
|
|
} else {
|
|
|
|
if(position < parentTrace->minX() || position > parentTrace->maxX()) {
|
|
|
|
// this normally should not happen because the position is constrained to the trace X range.
|
|
|
|
// However, when loading a setup, the trace might have been just created and essentially empty
|
|
|
|
newdata = numeric_limits<complex<double>>::quiet_NaN();
|
|
|
|
} else {
|
|
|
|
// some data of the parent trace changed, check if marker data also changed
|
|
|
|
auto sampleType = isTimeDomain() ? Trace::SampleType::TimeImpulse : Trace::SampleType::Frequency;
|
|
|
|
newdata = parentTrace->sample(parentTrace->index(position), sampleType).y;
|
|
|
|
}
|
|
|
|
}
|
2020-11-23 04:25:41 +08:00
|
|
|
if (newdata != data) {
|
|
|
|
data = newdata;
|
2020-10-20 23:03:49 +08:00
|
|
|
update();
|
|
|
|
emit rawDataChanged();
|
2020-08-31 04:03:41 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TraceMarker::updateSymbol()
|
|
|
|
{
|
2020-11-03 07:37:06 +08:00
|
|
|
if(isVisible()) {
|
|
|
|
constexpr int width = 15, height = 15;
|
|
|
|
symbol = QPixmap(width, height);
|
|
|
|
symbol.fill(Qt::transparent);
|
|
|
|
QPainter p(&symbol);
|
|
|
|
p.setRenderHint(QPainter::Antialiasing);
|
|
|
|
QPointF points[] = {QPointF(0,0),QPointF(width,0),QPointF(width/2,height)};
|
|
|
|
auto traceColor = parentTrace->color();
|
|
|
|
p.setPen(traceColor);
|
|
|
|
p.setBrush(traceColor);
|
|
|
|
p.drawConvexPolygon(points, 3);
|
|
|
|
auto brightness = traceColor.redF() * 0.299 + traceColor.greenF() * 0.587 + traceColor.blueF() * 0.114;
|
|
|
|
p.setPen((brightness > 0.6) ? Qt::black : Qt::white);
|
|
|
|
p.drawText(QRectF(0,0,width, height*2.0/3.0), Qt::AlignCenter, QString::number(number) + suffix);
|
|
|
|
} else {
|
|
|
|
symbol = QPixmap(1,1);
|
|
|
|
}
|
2020-10-20 23:03:49 +08:00
|
|
|
emit symbolChanged(this);
|
2020-08-31 04:03:41 +08:00
|
|
|
}
|
|
|
|
|
2020-12-03 05:15:34 +08:00
|
|
|
void TraceMarker::checkDeltaMarker()
|
|
|
|
{
|
|
|
|
if(type != Type::Delta) {
|
|
|
|
// not a delta marker, nothing to do
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Check if type of delta marker is still okay
|
2020-12-05 19:59:23 +08:00
|
|
|
if(!delta || delta->isTimeDomain() != isTimeDomain()) {
|
2020-12-03 05:15:34 +08:00
|
|
|
// not the same domain anymore, adjust delta
|
|
|
|
assignDeltaMarker(bestDeltaCandidate());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-10 20:23:25 +08:00
|
|
|
void TraceMarker::deltaDeleted()
|
|
|
|
{
|
|
|
|
// the delta marker of this marker has been deleted, find new match
|
|
|
|
delta = nullptr;
|
|
|
|
qDebug() << "assigned delta deleted";
|
|
|
|
assignDeltaMarker(bestDeltaCandidate());
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
2020-10-23 20:12:45 +08:00
|
|
|
std::set<TraceMarker::Type> TraceMarker::getSupportedTypes()
|
|
|
|
{
|
|
|
|
set<TraceMarker::Type> supported;
|
|
|
|
if(parentTrace) {
|
2020-12-03 05:15:34 +08:00
|
|
|
if(isTimeDomain()) {
|
|
|
|
// only basic markers in time domain
|
|
|
|
supported.insert(Type::Manual);
|
|
|
|
supported.insert(Type::Delta);
|
|
|
|
} else {
|
2020-11-23 04:25:41 +08:00
|
|
|
// all traces support some basic markers
|
|
|
|
supported.insert(Type::Manual);
|
|
|
|
supported.insert(Type::Maximum);
|
|
|
|
supported.insert(Type::Minimum);
|
|
|
|
supported.insert(Type::Delta);
|
|
|
|
supported.insert(Type::PeakTable);
|
|
|
|
if(parentTrace->isLive()) {
|
|
|
|
switch(parentTrace->liveParameter()) {
|
|
|
|
case Trace::LiveParameter::S11:
|
|
|
|
case Trace::LiveParameter::S12:
|
|
|
|
case Trace::LiveParameter::S21:
|
|
|
|
case Trace::LiveParameter::S22:
|
|
|
|
// special VNA marker types
|
|
|
|
supported.insert(Type::Lowpass);
|
|
|
|
supported.insert(Type::Highpass);
|
|
|
|
supported.insert(Type::Bandpass);
|
|
|
|
break;
|
|
|
|
case Trace::LiveParameter::Port1:
|
|
|
|
case Trace::LiveParameter::Port2:
|
|
|
|
// special SA marker types
|
|
|
|
supported.insert(Type::Noise);
|
|
|
|
supported.insert(Type::TOI);
|
|
|
|
supported.insert(Type::PhaseNoise);
|
|
|
|
break;
|
|
|
|
}
|
2020-10-23 20:12:45 +08:00
|
|
|
}
|
2020-12-03 05:15:34 +08:00
|
|
|
}
|
2020-10-23 20:12:45 +08:00
|
|
|
}
|
|
|
|
return supported;
|
|
|
|
}
|
|
|
|
|
2020-11-23 04:25:41 +08:00
|
|
|
void TraceMarker::constrainPosition()
|
2020-08-31 04:03:41 +08:00
|
|
|
{
|
2020-11-23 04:25:41 +08:00
|
|
|
if(parentTrace) {
|
2020-12-03 05:15:34 +08:00
|
|
|
if(parentTrace->size() > 0) {
|
|
|
|
if(position > parentTrace->maxX()) {
|
|
|
|
position = parentTrace->maxX();
|
|
|
|
} else if(position < parentTrace->minX()) {
|
|
|
|
position = parentTrace->minX();
|
2020-11-23 04:25:41 +08:00
|
|
|
}
|
2020-12-03 05:15:34 +08:00
|
|
|
}
|
2020-08-31 04:03:41 +08:00
|
|
|
traceDataChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-03 05:15:34 +08:00
|
|
|
TraceMarker *TraceMarker::bestDeltaCandidate()
|
|
|
|
{
|
|
|
|
TraceMarker *match = nullptr;
|
|
|
|
// invalid delta marker assigned, attempt to find a matching marker
|
|
|
|
for(int pass = 0;pass < 3;pass++) {
|
|
|
|
for(auto m : model->getMarkers()) {
|
|
|
|
if(m->isTimeDomain() != isTimeDomain()) {
|
|
|
|
// markers are not on the same domain
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if(pass == 0 && m->parentTrace != parentTrace) {
|
|
|
|
// ignore markers on different traces in first pass
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if(pass <= 1 && m == this) {
|
|
|
|
// ignore itself on second pass
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
match = m;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if(match) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return match;
|
|
|
|
}
|
|
|
|
|
2020-10-20 23:03:49 +08:00
|
|
|
void TraceMarker::assignDeltaMarker(TraceMarker *m)
|
|
|
|
{
|
2020-12-05 19:59:23 +08:00
|
|
|
if(type != Type::Delta) {
|
|
|
|
// ignore
|
|
|
|
return;
|
|
|
|
}
|
2020-10-20 23:03:49 +08:00
|
|
|
if(delta) {
|
|
|
|
disconnect(delta, &TraceMarker::dataChanged, this, &TraceMarker::update);
|
|
|
|
}
|
|
|
|
delta = m;
|
|
|
|
if(delta && delta != this) {
|
|
|
|
// this marker has to be updated when the delta marker changes
|
|
|
|
connect(delta, &TraceMarker::rawDataChanged, this, &TraceMarker::update);
|
2020-12-03 05:15:34 +08:00
|
|
|
connect(delta, &TraceMarker::domainChanged, this, &TraceMarker::checkDeltaMarker);
|
2020-12-10 20:23:25 +08:00
|
|
|
connect(delta, &TraceMarker::deleted, this, &TraceMarker::deltaDeleted);
|
2020-10-20 23:03:49 +08:00
|
|
|
}
|
2020-12-05 19:59:23 +08:00
|
|
|
emit assignedDeltaChanged(this);
|
2020-10-20 23:03:49 +08:00
|
|
|
}
|
|
|
|
|
2020-10-21 01:15:06 +08:00
|
|
|
void TraceMarker::deleteHelperMarkers()
|
|
|
|
{
|
2020-11-03 07:37:06 +08:00
|
|
|
emit beginRemoveHelperMarkers(this);
|
2020-10-21 01:15:06 +08:00
|
|
|
for(auto m : helperMarkers) {
|
|
|
|
delete m;
|
|
|
|
}
|
|
|
|
helperMarkers.clear();
|
2020-11-03 07:37:06 +08:00
|
|
|
emit endRemoveHelperMarkers(this);
|
2020-10-21 01:15:06 +08:00
|
|
|
}
|
2020-10-20 23:03:49 +08:00
|
|
|
|
2020-10-23 20:12:45 +08:00
|
|
|
void TraceMarker::setType(TraceMarker::Type t)
|
|
|
|
{
|
|
|
|
// remove any potential helper markers
|
|
|
|
deleteHelperMarkers();
|
|
|
|
type = t;
|
2020-11-03 07:37:06 +08:00
|
|
|
using helper_descr = struct {
|
|
|
|
QString suffix;
|
|
|
|
QString description;
|
2020-11-09 04:28:47 +08:00
|
|
|
Type type;
|
2020-11-03 07:37:06 +08:00
|
|
|
};
|
|
|
|
vector<helper_descr> required_helpers;
|
2020-10-23 20:12:45 +08:00
|
|
|
switch(type) {
|
|
|
|
case Type::Delta:
|
2020-12-03 05:15:34 +08:00
|
|
|
assignDeltaMarker(bestDeltaCandidate());
|
2020-10-23 20:12:45 +08:00
|
|
|
break;
|
|
|
|
case Type::Lowpass:
|
|
|
|
case Type::Highpass:
|
2020-11-09 04:28:47 +08:00
|
|
|
required_helpers = {{"c", "cutoff", Type::Manual}};
|
2020-10-23 20:12:45 +08:00
|
|
|
break;
|
|
|
|
case Type::Bandpass:
|
2020-11-09 04:28:47 +08:00
|
|
|
required_helpers = {{"l", "lower cutoff", Type::Manual}, {"h", "higher cutoff", Type::Manual} ,{"c", "center", Type::Manual}};
|
2020-10-23 20:12:45 +08:00
|
|
|
break;
|
|
|
|
case Type::TOI:
|
2020-11-09 04:28:47 +08:00
|
|
|
required_helpers = {{"p", "first peak", Type::Manual}, {"p", "second peak", Type::Manual}, {"l", "left intermodulation", Type::Manual}, {"r", "right intermodulation", Type::Manual}};
|
|
|
|
break;
|
|
|
|
case Type::PhaseNoise:
|
|
|
|
required_helpers = {{"o", "Offset", Type::Noise}};
|
|
|
|
break;
|
2020-10-23 20:12:45 +08:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// create helper markers
|
2020-11-03 07:37:06 +08:00
|
|
|
for(auto h : required_helpers) {
|
|
|
|
auto helper = new TraceMarker(model, number, this, h.description);
|
|
|
|
helper->suffix = h.suffix;
|
2020-10-23 20:12:45 +08:00
|
|
|
helper->assignTrace(parentTrace);
|
2020-11-09 04:28:47 +08:00
|
|
|
helper->setType(h.type);
|
2020-10-23 20:12:45 +08:00
|
|
|
helperMarkers.push_back(helper);
|
|
|
|
}
|
2020-11-03 07:37:06 +08:00
|
|
|
updateSymbol();
|
2020-10-23 20:12:45 +08:00
|
|
|
emit typeChanged(this);
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
|
|
|
double TraceMarker::toDecibel()
|
|
|
|
{
|
|
|
|
return 20*log10(abs(data));
|
|
|
|
}
|
|
|
|
|
2020-11-03 07:37:06 +08:00
|
|
|
bool TraceMarker::isVisible()
|
|
|
|
{
|
|
|
|
switch(type) {
|
|
|
|
case Type::Manual:
|
|
|
|
case Type::Delta:
|
|
|
|
case Type::Maximum:
|
|
|
|
case Type::Minimum:
|
2020-11-09 04:28:47 +08:00
|
|
|
case Type::Noise:
|
|
|
|
case Type::PhaseNoise:
|
2020-11-03 07:37:06 +08:00
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-05 19:59:23 +08:00
|
|
|
TraceMarker::Type TraceMarker::getType() const
|
|
|
|
{
|
|
|
|
return type;
|
|
|
|
}
|
|
|
|
|
2020-11-03 07:37:06 +08:00
|
|
|
QString TraceMarker::getSuffix() const
|
|
|
|
{
|
|
|
|
return suffix;
|
|
|
|
}
|
|
|
|
|
2020-12-05 19:59:23 +08:00
|
|
|
nlohmann::json TraceMarker::toJSON()
|
|
|
|
{
|
|
|
|
nlohmann::json j;
|
|
|
|
j["trace"] = parentTrace->toHash();
|
|
|
|
j["type"] = typeToString(type).toStdString();
|
|
|
|
j["number"] = number;
|
|
|
|
j["position"] = position;
|
|
|
|
switch(type) {
|
|
|
|
case Type::Delta:
|
|
|
|
j["delta_marker"] = delta->toHash();
|
|
|
|
break;
|
|
|
|
case Type::PeakTable:
|
|
|
|
j["peak_threshold"] = peakThreshold;
|
|
|
|
break;
|
|
|
|
case Type::Lowpass:
|
|
|
|
case Type::Highpass:
|
|
|
|
case Type::Bandpass:
|
|
|
|
j["cutoff"] = cutoffAmplitude;
|
|
|
|
break;
|
|
|
|
case Type::PhaseNoise:
|
|
|
|
j["offset"] = offset;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// other types have no settings
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return j;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TraceMarker::fromJSON(nlohmann::json j)
|
|
|
|
{
|
|
|
|
if(!j.contains("trace")) {
|
|
|
|
throw runtime_error("Marker has no trace assigned");
|
|
|
|
}
|
|
|
|
number = j.value("number", 1);
|
|
|
|
position = j.value("position", 0.0);
|
|
|
|
|
|
|
|
unsigned int hash = j["trace"];
|
|
|
|
// find correct trace
|
|
|
|
bool found = false;
|
|
|
|
for(auto t : model->getModel().getTraces()) {
|
|
|
|
if(t->toHash() == hash) {
|
|
|
|
found = true;
|
|
|
|
assignTrace(t);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!found) {
|
|
|
|
throw runtime_error("Unable to find trace with hash " + to_string(hash));
|
|
|
|
}
|
|
|
|
auto typeString = QString::fromStdString(j.value("type", "Manual"));
|
|
|
|
for(unsigned int i=0;i<(int) Type::Last;i++) {
|
|
|
|
if(typeToString((Type) i) == typeString) {
|
|
|
|
setType((Type) i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch(type) {
|
|
|
|
case Type::Delta:
|
|
|
|
// can't assign delta marker here, because it might not have been created (if it was below this marker in the table).
|
|
|
|
// Instead it will be correctly assigned in TraceMarkerModel::fromJSON()
|
|
|
|
break;
|
|
|
|
case Type::PeakTable:
|
|
|
|
peakThreshold = j.value("peak_threshold", -40);
|
|
|
|
break;
|
|
|
|
case Type::Lowpass:
|
|
|
|
case Type::Highpass:
|
|
|
|
case Type::Bandpass:
|
|
|
|
cutoffAmplitude = j.value("cutoff", -3.0);
|
|
|
|
break;
|
|
|
|
case Type::PhaseNoise:
|
|
|
|
j.value("offset", 10000);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// other types have no settings
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int TraceMarker::toHash()
|
|
|
|
{
|
|
|
|
// taking the easy way: create the json string and hash it (already contains all necessary information)
|
|
|
|
// This is slower than it could be, but this function is only used when loading setups, so this isn't a big problem
|
|
|
|
std::string json_string = toJSON().dump();
|
|
|
|
return hash<std::string>{}(json_string);
|
|
|
|
}
|
|
|
|
|
2020-11-03 07:37:06 +08:00
|
|
|
const std::vector<TraceMarker *> &TraceMarker::getHelperMarkers() const
|
|
|
|
{
|
|
|
|
return helperMarkers;
|
|
|
|
}
|
|
|
|
|
|
|
|
TraceMarker *TraceMarker::helperMarker(unsigned int i)
|
|
|
|
{
|
|
|
|
if(i < helperMarkers.size()) {
|
|
|
|
return helperMarkers[i];
|
|
|
|
} else {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TraceMarker *TraceMarker::getParent() const
|
|
|
|
{
|
|
|
|
return parent;
|
|
|
|
}
|
|
|
|
|
2020-10-20 23:03:49 +08:00
|
|
|
void TraceMarker::setNumber(int value)
|
|
|
|
{
|
|
|
|
number = value;
|
|
|
|
updateSymbol();
|
2020-11-03 07:37:06 +08:00
|
|
|
for(auto h : helperMarkers) {
|
|
|
|
h->setNumber(number);
|
|
|
|
}
|
2020-10-20 23:03:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
QWidget *TraceMarker::getTypeEditor(QAbstractItemDelegate *delegate)
|
|
|
|
{
|
|
|
|
auto c = new QComboBox;
|
2020-10-23 20:12:45 +08:00
|
|
|
for(auto t : getSupportedTypes()) {
|
2020-10-20 23:03:49 +08:00
|
|
|
c->addItem(typeToString(t));
|
|
|
|
if(type == t) {
|
|
|
|
// select this item
|
|
|
|
c->setCurrentIndex(c->count() - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(type == Type::Delta) {
|
|
|
|
// add additional spinbox to choose corresponding delta marker
|
|
|
|
auto w = new QWidget;
|
|
|
|
auto layout = new QHBoxLayout;
|
|
|
|
layout->addWidget(c);
|
|
|
|
c->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
|
|
layout->setContentsMargins(0,0,0,0);
|
|
|
|
layout->setMargin(0);
|
|
|
|
layout->setSpacing(0);
|
|
|
|
layout->addWidget(new QLabel("to"));
|
|
|
|
auto spinbox = new QSpinBox;
|
|
|
|
if(delta) {
|
|
|
|
spinbox->setValue(delta->number);
|
|
|
|
}
|
|
|
|
connect(spinbox, qOverload<int>(&QSpinBox::valueChanged), [=](int newval){
|
|
|
|
bool found = false;
|
|
|
|
for(auto m : model->getMarkers()) {
|
|
|
|
if(m->number == newval) {
|
|
|
|
assignDeltaMarker(m);
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!found) {
|
|
|
|
assignDeltaMarker(nullptr);
|
|
|
|
}
|
|
|
|
update();
|
|
|
|
});
|
|
|
|
spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
|
|
layout->addWidget(spinbox);
|
|
|
|
w->setLayout(layout);
|
|
|
|
c->setObjectName("Type");
|
|
|
|
if(delegate){
|
|
|
|
connect(c, qOverload<int>(&QComboBox::currentIndexChanged), [=](int) {
|
|
|
|
emit delegate->commitData(w);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return w;
|
|
|
|
} else {
|
|
|
|
// no delta marker, simply return the combobox
|
|
|
|
connect(c, qOverload<int>(&QComboBox::currentIndexChanged), [=](int) {
|
|
|
|
emit delegate->commitData(c);
|
|
|
|
});
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TraceMarker::updateTypeFromEditor(QWidget *w)
|
|
|
|
{
|
|
|
|
QComboBox *c;
|
|
|
|
if(type == Type::Delta) {
|
|
|
|
c = w->findChild<QComboBox*>("Type");
|
|
|
|
} else {
|
|
|
|
c = (QComboBox*) w;
|
|
|
|
}
|
2020-10-23 20:12:45 +08:00
|
|
|
for(auto t : getSupportedTypes()) {
|
2020-10-20 23:03:49 +08:00
|
|
|
if(c->currentText() == typeToString(t)) {
|
|
|
|
if(type != t) {
|
2020-10-23 20:12:45 +08:00
|
|
|
setType(t);
|
2020-10-20 23:03:49 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-03 07:37:06 +08:00
|
|
|
update();
|
2020-10-20 23:03:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
SIUnitEdit *TraceMarker::getSettingsEditor()
|
|
|
|
{
|
2020-12-03 05:15:34 +08:00
|
|
|
if(isTimeDomain()) {
|
|
|
|
switch(type) {
|
|
|
|
case Type::Manual:
|
|
|
|
case Type::Delta:
|
|
|
|
return new SIUnitEdit("", "fpnum k", 6);
|
|
|
|
default:
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
} else {
|
2020-11-23 04:25:41 +08:00
|
|
|
switch(type) {
|
|
|
|
case Type::Manual:
|
|
|
|
case Type::Maximum:
|
|
|
|
case Type::Minimum:
|
|
|
|
case Type::Delta:
|
|
|
|
case Type::Noise:
|
|
|
|
case Type::PhaseNoise:
|
|
|
|
default:
|
|
|
|
return new SIUnitEdit("Hz", " kMG", 6);
|
|
|
|
case Type::Lowpass:
|
|
|
|
case Type::Highpass:
|
|
|
|
case Type::PeakTable:
|
|
|
|
return new SIUnitEdit("db", " ", 3);
|
|
|
|
case Type::TOI:
|
|
|
|
return nullptr;
|
|
|
|
}
|
2020-12-03 05:15:34 +08:00
|
|
|
}
|
2020-10-21 01:15:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void TraceMarker::adjustSettings(double value)
|
|
|
|
{
|
2020-12-03 05:15:34 +08:00
|
|
|
if(isTimeDomain()) {
|
|
|
|
switch(type) {
|
|
|
|
case Type::Manual:
|
|
|
|
case Type::Delta: {
|
|
|
|
// check if entered position is time or distance
|
|
|
|
if(value > parentTrace->sample(parentTrace->size() - 1).x) {
|
|
|
|
// entered a distance, convert to time
|
|
|
|
setPosition(parentTrace->distanceToTime(value));
|
|
|
|
} else {
|
|
|
|
// entered a time, can set directly
|
|
|
|
setPosition(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch(type) {
|
|
|
|
case Type::Manual:
|
|
|
|
case Type::Maximum:
|
|
|
|
case Type::Minimum:
|
|
|
|
case Type::Delta:
|
|
|
|
case Type::Noise:
|
|
|
|
setPosition(value);
|
|
|
|
break;
|
|
|
|
case Type::Lowpass:
|
|
|
|
case Type::Highpass:
|
|
|
|
case Type::Bandpass:
|
|
|
|
if(value > 0.0) {
|
|
|
|
value = -value;
|
|
|
|
}
|
|
|
|
cutoffAmplitude = value;
|
|
|
|
break;
|
|
|
|
case Type::PeakTable:
|
|
|
|
peakThreshold = value;
|
|
|
|
break;
|
|
|
|
case Type::PhaseNoise:
|
|
|
|
offset = value;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2020-10-21 01:15:06 +08:00
|
|
|
}
|
|
|
|
}
|
2020-11-03 07:37:06 +08:00
|
|
|
update();
|
2020-10-20 23:03:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void TraceMarker::update()
|
|
|
|
{
|
2020-10-21 01:15:06 +08:00
|
|
|
if(!parentTrace->size()) {
|
|
|
|
// empty trace, nothing to do
|
|
|
|
return;
|
|
|
|
}
|
2020-10-20 23:03:49 +08:00
|
|
|
switch(type) {
|
|
|
|
case Type::Manual:
|
2020-10-23 03:12:33 +08:00
|
|
|
case Type::Delta:
|
2020-11-09 04:28:47 +08:00
|
|
|
case Type::Noise:
|
2020-10-20 23:03:49 +08:00
|
|
|
// nothing to do
|
|
|
|
break;
|
|
|
|
case Type::Maximum:
|
2020-11-23 04:25:41 +08:00
|
|
|
setPosition(parentTrace->findExtremumFreq(true));
|
2020-10-20 23:03:49 +08:00
|
|
|
break;
|
|
|
|
case Type::Minimum:
|
2020-11-23 04:25:41 +08:00
|
|
|
setPosition(parentTrace->findExtremumFreq(false));
|
2020-10-20 23:03:49 +08:00
|
|
|
break;
|
2020-11-03 07:37:06 +08:00
|
|
|
case Type::PeakTable: {
|
|
|
|
deleteHelperMarkers();
|
2020-11-09 04:28:47 +08:00
|
|
|
auto peaks = parentTrace->findPeakFrequencies(100, peakThreshold);
|
2020-11-03 07:37:06 +08:00
|
|
|
char suffix = 'a';
|
|
|
|
for(auto p : peaks) {
|
|
|
|
auto helper = new TraceMarker(model, number, this);
|
|
|
|
helper->suffix = suffix;
|
|
|
|
helper->assignTrace(parentTrace);
|
2020-11-23 04:25:41 +08:00
|
|
|
helper->setPosition(p);
|
2020-11-03 07:37:06 +08:00
|
|
|
suffix++;
|
|
|
|
helperMarkers.push_back(helper);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2020-10-21 01:15:06 +08:00
|
|
|
case Type::Lowpass:
|
|
|
|
case Type::Highpass:
|
|
|
|
if(parentTrace->isReflection()) {
|
|
|
|
// lowpass/highpass calculation only works with transmission measurement
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
// find the maximum
|
|
|
|
auto peakFreq = parentTrace->findExtremumFreq(true);
|
|
|
|
// this marker shows the insertion loss
|
2020-11-23 04:25:41 +08:00
|
|
|
setPosition(peakFreq);
|
2020-10-21 01:15:06 +08:00
|
|
|
// find the cutoff frequency
|
|
|
|
auto index = parentTrace->index(peakFreq);
|
2020-11-25 23:47:29 +08:00
|
|
|
auto peakAmplitude = 20*log10(abs(parentTrace->sample(index).y));
|
2020-10-21 01:15:06 +08:00
|
|
|
auto cutoff = peakAmplitude + cutoffAmplitude;
|
|
|
|
int inc = type == Type::Lowpass ? 1 : -1;
|
|
|
|
while(index >= 0 && index < (int) parentTrace->size()) {
|
2020-11-25 23:47:29 +08:00
|
|
|
auto amplitude = 20*log10(abs(parentTrace->sample(index).y));
|
2020-10-21 01:15:06 +08:00
|
|
|
if(amplitude <= cutoff) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
index += inc;
|
|
|
|
}
|
|
|
|
if(index < 0) {
|
|
|
|
index = 0;
|
|
|
|
} else if(index >= (int) parentTrace->size()) {
|
|
|
|
index = parentTrace->size() - 1;
|
|
|
|
}
|
|
|
|
// set position of cutoff marker
|
2020-11-25 23:47:29 +08:00
|
|
|
helperMarkers[0]->setPosition(parentTrace->sample(index).x);
|
2020-10-21 01:15:06 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Type::Bandpass:
|
|
|
|
if(parentTrace->isReflection()) {
|
|
|
|
// lowpass/highpass calculation only works with transmission measurement
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
// find the maximum
|
|
|
|
auto peakFreq = parentTrace->findExtremumFreq(true);
|
|
|
|
// this marker shows the insertion loss
|
2020-11-23 04:25:41 +08:00
|
|
|
setPosition(peakFreq);
|
2020-10-21 01:15:06 +08:00
|
|
|
// find the cutoff frequencies
|
|
|
|
auto index = parentTrace->index(peakFreq);
|
2020-11-25 23:47:29 +08:00
|
|
|
auto peakAmplitude = 20*log10(abs(parentTrace->sample(index).y));
|
2020-10-21 01:15:06 +08:00
|
|
|
auto cutoff = peakAmplitude + cutoffAmplitude;
|
|
|
|
|
|
|
|
auto low_index = index;
|
|
|
|
while(low_index >= 0) {
|
2020-11-25 23:47:29 +08:00
|
|
|
auto amplitude = 20*log10(abs(parentTrace->sample(low_index).y));
|
2020-10-21 01:15:06 +08:00
|
|
|
if(amplitude <= cutoff) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
low_index--;
|
|
|
|
}
|
|
|
|
if(low_index < 0) {
|
|
|
|
low_index = 0;
|
|
|
|
}
|
|
|
|
// set position of cutoff marker
|
2020-11-25 23:47:29 +08:00
|
|
|
helperMarkers[0]->setPosition(parentTrace->sample(low_index).x);
|
2020-10-21 01:15:06 +08:00
|
|
|
|
|
|
|
auto high_index = index;
|
|
|
|
while(high_index < (int) parentTrace->size()) {
|
2020-11-25 23:47:29 +08:00
|
|
|
auto amplitude = 20*log10(abs(parentTrace->sample(high_index).y));
|
2020-10-21 01:15:06 +08:00
|
|
|
if(amplitude <= cutoff) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
high_index++;
|
|
|
|
}
|
|
|
|
if(high_index >= (int) parentTrace->size()) {
|
|
|
|
high_index = parentTrace->size() - 1;
|
|
|
|
}
|
|
|
|
// set position of cutoff marker
|
2020-11-25 23:47:29 +08:00
|
|
|
helperMarkers[1]->setPosition(parentTrace->sample(high_index).x);
|
2020-10-21 01:15:06 +08:00
|
|
|
// set center marker inbetween cutoff markers
|
2020-11-23 04:25:41 +08:00
|
|
|
helperMarkers[2]->setPosition((helperMarkers[0]->position + helperMarkers[1]->position) / 2);
|
2020-10-21 01:15:06 +08:00
|
|
|
}
|
|
|
|
break;
|
2020-10-23 20:12:45 +08:00
|
|
|
case Type::TOI: {
|
|
|
|
auto peaks = parentTrace->findPeakFrequencies(2);
|
|
|
|
if(peaks.size() != 2) {
|
|
|
|
// error finding peaks, do nothing
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// assign marker frequenies:
|
|
|
|
// this marker is the left peak, first helper the right peak.
|
|
|
|
// 2nd and 3rd helpers are left and right TOI peaks
|
2020-11-23 04:25:41 +08:00
|
|
|
helperMarkers[0]->setPosition(peaks[0]);
|
|
|
|
helperMarkers[1]->setPosition(peaks[1]);
|
2020-10-23 20:12:45 +08:00
|
|
|
auto freqDiff = peaks[1] - peaks[0];
|
2020-11-23 04:25:41 +08:00
|
|
|
helperMarkers[2]->setPosition(peaks[0] - freqDiff);
|
|
|
|
helperMarkers[3]->setPosition(peaks[1] + freqDiff);
|
2020-10-23 20:12:45 +08:00
|
|
|
}
|
|
|
|
break;
|
2020-11-09 04:28:47 +08:00
|
|
|
case Type::PhaseNoise:
|
2020-11-23 04:25:41 +08:00
|
|
|
setPosition(parentTrace->findExtremumFreq(true));
|
|
|
|
helperMarkers[0]->setPosition(position + offset);
|
2020-11-09 04:28:47 +08:00
|
|
|
break;
|
2020-12-13 05:51:38 +08:00
|
|
|
case Type::Last:
|
|
|
|
break;
|
2020-10-20 23:03:49 +08:00
|
|
|
}
|
|
|
|
emit dataChanged(this);
|
|
|
|
}
|
|
|
|
|
2020-10-20 03:21:04 +08:00
|
|
|
Trace *TraceMarker::getTrace() const
|
|
|
|
{
|
|
|
|
return parentTrace;
|
|
|
|
}
|
|
|
|
|
2020-08-31 04:03:41 +08:00
|
|
|
int TraceMarker::getNumber() const
|
|
|
|
{
|
|
|
|
return number;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::complex<double> TraceMarker::getData() const
|
|
|
|
{
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2020-11-03 07:37:06 +08:00
|
|
|
bool TraceMarker::isMovable()
|
|
|
|
{
|
|
|
|
if(parent) {
|
|
|
|
// helper traces are never movable by the user
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
switch(type) {
|
|
|
|
case Type::Manual:
|
|
|
|
case Type::Delta:
|
2020-11-09 04:28:47 +08:00
|
|
|
case Type::Noise:
|
2020-11-03 07:37:06 +08:00
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 04:03:41 +08:00
|
|
|
QPixmap &TraceMarker::getSymbol()
|
|
|
|
{
|
|
|
|
return symbol;
|
|
|
|
}
|
|
|
|
|
2020-11-23 04:25:41 +08:00
|
|
|
double TraceMarker::getPosition() const
|
2020-08-31 04:03:41 +08:00
|
|
|
{
|
2020-11-23 04:25:41 +08:00
|
|
|
return position;
|
2020-08-31 04:03:41 +08:00
|
|
|
}
|
|
|
|
|
2020-12-03 05:15:34 +08:00
|
|
|
bool TraceMarker::isTimeDomain()
|
|
|
|
{
|
|
|
|
if(parentTrace) {
|
|
|
|
if(parentTrace->outputType() == Trace::DataType::Time) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|