#include #include #include "gui_class.h" #include #include using namespace std; #define STRETCH /* nothing */ extern int verbosity; // should be in a .h file somewhere ///////////////// // some kludgey global variables, // mostly for inter-thread communication int pcmRate(0); double cpkp(0); // cycles per krunch period double fpkp(0); // frames per krunch period: double actOutFreq(0); // actual refout frequency double refOutAmp_dB(-15.); // default should not be not too loud double VoCal_dB(0.); double ViCal_dB(0.); double timeShift(0.); // in seconds double phaseShift(0.); // in degrees double lockerPhase(0.); // in degrees int repaintFrame(0); // // Note: more globals can be found in lockin.cxx class colorer { public: Qt::GlobalColor code; const char* name; colorer(const Qt::GlobalColor _code, const char* const _name) : code(_code), name(_name) {} }; #define X(foo) colorer(Qt::foo, #foo) static colorer color_list[] = {X(red), X(blue), X(black)}; #undef X static int color_list_size = sizeof(color_list) / sizeof(color_list[0]); #if 0 /* code not needed at present */ double now(){ timespec xx; #ifdef _POSIX_MONOTONIC_CLOCK clock_gettime(CLOCK_MONOTONIC, &xx); #else clock_gettime(CLOCK_MONOTONIC, &xx); #endif return xx.tv_sec + xx.tv_nsec*1e-9; } #endif myWindow::myWindow() { // The top myWindow and its layout : horizontal: // topWindow == this topLayout = new QHBoxLayout; setLayout(topLayout); ctrlCol = new ctrl_column(this); topLayout->addWidget(ctrlCol->group); for (int ii = 0; ii < myWindow::numRsltPanels; ii++){ rsltPanel[ii] = new rslt_panel(this); topLayout->addWidget(rsltPanel[ii]->group); } // setting the indication to its current value won't // change the indicator, but will cause it to update // the associated plot for (unsigned int ii = 0; ii < ndc8r.size(); ii++){ indicator* foo = ndc8r[ii]; foo->setIndication(0,0, foo->rpBox->value(), foo->ipBox->value()); } frameTimer = new timer; } void myWindow::flush() { if (!repaintFrame) return; repaintFrame = 0; for (int ii = 0; ii < numRsltPanels; ii++){ rsltPanel[ii]->plot->flush(); } } ctrl_column::ctrl_column(myWindow* topwin){ group = new QGroupBox; group->setFlat(0); layout = new QVBoxLayout; group->setLayout(layout); refOutGroup = new refOut_grouper(topwin); layout->addWidget(refOutGroup->group); layout->insertStretch(-1); entrailsGroup = new entrails_grouper(topwin); layout->addWidget(entrailsGroup->group); } // the refOut group and its layout: vertical: refOut_grouper::refOut_grouper(myWindow* _topwin) : topwin(_topwin), VLabel(0) { // the freq box group = new QGroupBox("Ref Out"); layout = new QVBoxLayout; group->setLayout(layout); //group->setStyleSheet("background-color: lavenderblush;"); //group->setStyleSheet("background-color: rgba(100, 0, 0, 150);"); //group->setStyleSheet("background-color: lightcyan;"); group->setStyleSheet(".QGroupBox{background-color: #E8ffFF}"); freqLabel = new QLabel; freqLabel->setText("Frequency"); freqBox = new QDoubleSpinBox; freqBox->setRange(0.0, 10000.0); freqBox->setSingleStep(1); freqBox->setValue(440); freqBox->setKeyboardTracking(0); freqBox->setSuffix(" Hz"); connect(freqBox, SIGNAL(valueChanged(double)), this, SLOT(freqChanged(double))); actFreqLabel = new QLabel; actFreqLabel->setText("Actual"); actFreqBox = new QDoubleSpinBox; actFreqBox->setRange(0.0, 10000.0); actFreqBox->setValue(440); actFreqBox->setReadOnly(1); actFreqBox->setButtonSymbols(QAbstractSpinBox::NoButtons); actFreqBox->setSuffix(" Hz"); dBLabel = new QLabel; dBLabel->setText("Amplitude"); dBBox = new QDoubleSpinBox; dBBox->setRange(-1000.0, 1000.0); dBBox->setDecimals(3); dBBox->setSingleStep(1); //later: dBBox->setValue(refOutAmp_dB); dBBox->setKeyboardTracking(0); dBBox->setSuffix(" dBV"); connect(dBBox, SIGNAL(valueChanged(double)), this, SLOT(ampChanged(double))); // express amplitude in V, not just in dB: //?? VLabel = new QLabel; //?? VLabel->setText("???"); VBox = new QDoubleSpinBox; VBox->setRange(-1e300, +1e300); VBox->setReadOnly(1); VBox->setDecimals(6); VBox->setButtonSymbols(QAbstractSpinBox::NoButtons); VBox->setSuffix(" V"); dBBox->setValue(refOutAmp_dB); // will throw ampChanged signal layout->addWidget(freqLabel STRETCH); layout->addWidget(freqBox STRETCH); layout->addWidget(actFreqLabel STRETCH); layout->addWidget(actFreqBox STRETCH); layout->addWidget(dBLabel STRETCH); layout->addWidget(dBBox STRETCH); if (VLabel) layout->addWidget(VLabel STRETCH); layout->addWidget(VBox STRETCH); } // the entrails group and its layout: vertical: entrails_grouper::entrails_grouper(myWindow* _topwin) : topwin(_topwin), tweakFreq(tweakSize), tweakPhase(tweakSize), tweakPtr(0), howmany(0) { group = new QGroupBox("Entrails"); layout = new QVBoxLayout; group->setLayout(layout); group->setStyleSheet("QGroupBox{background-color: #ffffe0}"); VoCalLabel = new QLabel; VoCalLabel->setText("Vo Calibration"); VoCalBox = new QDoubleSpinBox; VoCalBox->setRange(-1e300, +1e300); VoCalBox->setSingleStep(1); VoCalBox->setKeyboardTracking(0); VoCalBox->setSuffix(" dBV FS"); connect(VoCalBox, SIGNAL(valueChanged(double)), this, SLOT(VoChanged(double))); VoCalBox->setValue(VoCal_dB); ViCalLabel = new QLabel; ViCalLabel->setText("Vi Calibration"); ViCalBox = new QDoubleSpinBox; ViCalBox->setRange(-1e300, +1e300); ViCalBox->setSingleStep(1); ViCalBox->setKeyboardTracking(0); ViCalBox->setSuffix(" dBV FS"); connect(ViCalBox, SIGNAL(valueChanged(double)), this, SLOT(ViChanged(double))); ViCalBox->setValue(ViCal_dB); rateLabel = new QLabel; rateLabel->setText("Krunch Rate"); rateBox = new QDoubleSpinBox; rateBox->setRange(0.0, 1e300); rateBox->setSingleStep(1); rateBox->setValue(6.); rateBox->setKeyboardTracking(0); rateBox->setSuffix(" Hz"); connect(rateBox, SIGNAL(valueChanged(double)), this, SLOT(rateChanged(double))); actRateLabel = new QLabel; actRateLabel->setText("Actual"); actRateBox = new QDoubleSpinBox; actRateBox->setRange(0.0, 1e300); actRateBox->setValue(6.); actRateBox->setReadOnly(1); actRateBox->setSuffix(" Hz"); actRateBox->setButtonSymbols(QAbstractSpinBox::NoButtons); pcmLabel = new QLabel; pcmLabel->setText("PCM Rate"); pcmBox = new QDoubleSpinBox; pcmBox->setRange(-1e300, +1e300); pcmBox->setValue(440); pcmBox->setReadOnly(1); pcmBox->setSuffix(" Hz"); pcmBox->setButtonSymbols(QAbstractSpinBox::NoButtons); timeShiftLabel = new QLabel; timeShiftLabel->setText("Time Shift"); timeShiftBox = new QDoubleSpinBox; timeShiftBox->setRange(-1000.0, 1000.0); timeShiftBox->setDecimals(6); timeShiftBox->setSingleStep(1e-6); timeShiftBox->setValue(timeShift); timeShiftBox->setKeyboardTracking(0); timeShiftBox->setSuffix(" s"); connect(timeShiftBox, SIGNAL(valueChanged(double)), this, SLOT(timeShiftChanged(double))); phaseShiftLabel = new QLabel; phaseShiftLabel->setText("Phase Shift"); phaseShiftBox = new QDoubleSpinBox; phaseShiftBox->setRange(-1e300, +1e300); phaseShiftBox->setDecimals(2); phaseShiftBox->setSingleStep(1); phaseShiftBox->setValue(phaseShift); phaseShiftBox->setKeyboardTracking(0); phaseShiftBox->setSuffix(QString::fromUtf8(" °")); connect(phaseShiftBox, SIGNAL(valueChanged(double)), this, SLOT(phaseShiftChanged(double))); tweakButton = new QPushButton("&Tweak"); connect(tweakButton, SIGNAL(clicked()), this, SLOT(tweakButtonClicked())); layout->addWidget(VoCalLabel STRETCH); layout->addWidget(VoCalBox STRETCH); layout->addWidget(ViCalLabel STRETCH); layout->addWidget(ViCalBox STRETCH); layout->addWidget(rateLabel STRETCH); layout->addWidget(rateBox STRETCH); layout->addWidget(actRateLabel STRETCH); layout->addWidget(actRateBox STRETCH); layout->addWidget(pcmLabel STRETCH); layout->addWidget(pcmBox STRETCH); layout->addWidget(timeShiftLabel STRETCH); layout->addWidget(timeShiftBox STRETCH); layout->addWidget(phaseShiftLabel STRETCH); layout->addWidget(phaseShiftBox STRETCH); layout->addWidget(tweakButton STRETCH); } rslt_panel::rslt_panel(myWindow* _topWin) : topWin(_topWin) { group = new QGroupBox; group->setFlat(0); layout = new QHBoxLayout; group->setLayout(layout); plot = new myPlot("plotname"); block = new blockOfIndicators(topWin, plot); // layout->addWidget(plot, 0, Qt::AlignHCenter); layout->addWidget(plot); layout->addWidget(block->group); layout->insertStretch(-1); } double quantize125(double arg){ if (arg < sqrt(2.)) return 1.; if (arg < sqrt(10.)) return 2.; if (arg < sqrt(50.)) return 5.; return 10.; } double logstep(double arg){ double ctc = pow(10., floor(log10(arg))); double mant = arg / ctc; return quantize125(mant) * ctc; } scaleBoxer::scaleBoxer(indicator* _parent) : parent(_parent) {} void scaleBoxer::stepBy(int steps){ double newval(1); if (steps == 1) newval = logstep(2.0 * logstep(value())); if (steps == -1) newval = logstep(0.5 * logstep(value())); if (steps == 10) newval = logstep(10.0 * value()); if (steps == -10) newval = logstep( 0.1 * value()); parent->setDecim(newval); setValue(newval); } void indicator::setDecim(const double newval){ int decim = -int(floor(log10(newval))); scaleBox->setDecimals(std::max(0, decim)); rpBox->setDecimals(std::max(0, 2+decim)); ipBox->setDecimals(std::max(0, 2+decim)); magBox->setDecimals(std::max(0, 2+decim)); } // An indicator and its internal layout: grid: indicator::indicator(myPlot* _plot, int _locker) : plot(_plot), plotcur(0), locker(_locker) { if (plot) { plotcur = plot->assign_curve(); } group = new QGroupBox; group->setFlat(0); QString style = ".QGroupBox{"; if (plot) { style += "border-top: 4px solid "; style += color_list[plotcur % color_list_size].name; style += ";"; } style += "background-color: #D0ffD0;"; style += "}"; group->setStyleSheet(style); layout = new QGridLayout; group->setLayout(layout); scaleLabel = new QLabel; scaleLabel->setText("Scale"); scaleBox = new scaleBoxer(this); scaleBox->setKeyboardTracking(0); scaleBox->setRange(-1e300, +1e300); double sc(pow(10., ViCal_dB/20.)); sc /= 2.5; // half scale, where full scale is 5 divisions sc = logstep(sc); scaleBox->setDecimals(10); scaleBox->setValue(sc); scaleBox->setSuffix(" V/div"); rpLabel = new QLabel; rpLabel->setText("Rp"); rpBox = new QDoubleSpinBox; rpBox->setReadOnly(1); rpBox->setButtonSymbols(QAbstractSpinBox::NoButtons); rpBox->setRange(-1e300, +1e300); rpBox->setValue(0); rpBox->setSuffix(" V"); ipLabel = new QLabel; ipLabel->setText("Ip"); ipBox = new QDoubleSpinBox; ipBox->setReadOnly(1); ipBox->setButtonSymbols(QAbstractSpinBox::NoButtons); ipBox->setRange(-1e300, +1e300); ipBox->setValue(0); ipBox->setSuffix(" V"); magLabel = new QLabel; magLabel->setText("Mag"); magBox = new QDoubleSpinBox; magBox->setReadOnly(1); magBox->setButtonSymbols(QAbstractSpinBox::NoButtons); magBox->setRange(-1e300, +1e300); magBox->setValue(0.1234); magBox->setSuffix(" V"); phaseLabel = new QLabel; phaseLabel->setText("Phase"); phaseBox = new QDoubleSpinBox; phaseBox->setReadOnly(1); phaseBox->setButtonSymbols(QAbstractSpinBox::NoButtons); phaseBox->setRange(-1e300, +1e300); phaseBox->setSuffix(QString::fromUtf8(" °")); setDecim(sc); int row(0); layout->addWidget(scaleLabel, row, 0); row++; layout->addWidget(scaleBox, row, 0); row++; layout->addWidget(rpLabel, row, 0); layout->addWidget(ipLabel, row, 1); row++; layout->addWidget(rpLabel, row, 0); layout->addWidget(ipLabel, row, 1); row++; layout->addWidget(rpBox, row, 0); layout->addWidget(ipBox, row, 1); row++; layout->addWidget(magLabel, row, 0); layout->addWidget(phaseLabel, row, 1); row++; layout->addWidget(magBox, row, 0); layout->addWidget(phaseBox, row, 1); } blockOfIndicators::blockOfIndicators(myWindow* topwin, myPlot* plot) { group = new QGroupBox; group->setFlat(0); group->setStyleSheet("border:0;"); layout = new QVBoxLayout; indicator* temp; group->setLayout(layout); temp = new indicator(plot, 1/* phaselock */); topwin->ndc8r.push_back(temp); layout->addWidget(temp->group); temp = new indicator(plot); topwin->ndc8r.push_back(temp); layout->addWidget(temp->group); temp = new indicator(plot); topwin->ndc8r.push_back(temp); layout->addWidget(temp->group); layout->insertStretch(-1); } void indicator::setIndication(const double rp0, const double ip0, const double rp1, const double ip1){ double rp(rp1-rp0); double ip(ip1-ip0); double mag(sqrt(rp*rp + ip*ip)); double phase(0); if (mag) phase = atan2(ip, rp); rpBox->setValue(rp); ipBox->setValue(ip); magBox->setValue(mag); double phDeg = phase * 180 / M_PI; phaseBox->setValue(phDeg); if (locker) lockerPhase = phDeg; if (plot) { double denom = scaleBox->value(); // volts per division denom *= plot->divsPerUnit; // volts full scale plot->setReading(plotcur, rp0/denom, ip0/denom, rp1/denom, ip1/denom); } } void myPlot::flush() { if (need_replot) replot(); } void myPlot::setReading(const int ndx, const double rp0, const double ip0, const double rp1, const double ip1){ int npts(2); double xxx[npts]; double yyy[npts]; xxx[0] = rp0; yyy[0] = ip0; xxx[1] = rp1; yyy[1] = ip1; ////////////////// curve[ndx]->setData(xxx, yyy, npts); curve[ndx]->setSamples(xxx, yyy, npts); need_replot = 1; } // There is no such thing as a scale change on the plot. // Rescale the data instead. #ifdef OLD_SCALE_IDEA void myPlot::scaleChange(double newScale){ setAxisScale(QwtPlot::xBottom, -newScale, newScale); setAxisScale(QwtPlot::yLeft, -newScale, newScale); replot(); } #endif // Unless you set keyboardTracking to false, // this is useless when typing in digits : // signals too early and too often. void refOut_grouper::freqChanged(double /* newfreq not used */) { topwin->actualFreqs(); } void entrails_grouper::VoChanged(double newVo) { VoCal_dB = newVo; //wrong topwin->ctrlCol->refOutGroup->VBox-> //wrong setValue(pow(10., (refOutAmp_dB + VoCal_dB)/20.)); } void entrails_grouper::ViChanged(double newVi) { ViCal_dB = newVi; } void refOut_grouper::ampChanged(double newAmp) { refOutAmp_dB = newAmp; //wrong: VBox->setValue(pow(10., (refOutAmp_dB + VoCal_dB)/20.)); VBox->setValue(pow(10., (refOutAmp_dB)/20.)); } void entrails_grouper::rateChanged(double /* newRate not used */) { topwin->actualFreqs(); } // beware the fmod of a negative number is negative double pval(const double angle){ double rslt = fmod(angle, 360.); if (rslt > 180.) rslt -= 360.; if (rslt < -180.) rslt += 360.; return rslt; } void entrails_grouper::timeShiftChanged(double newTimeShift) { //-- std::cout << "timeShiftChange: " << newTimeShift << std::endl; // Calculate new phase such that changing the timeShift // doesn't change the phase; it is only supposed to // change d(phase)/d(frequency). double newPhase = phaseShift - (newTimeShift - timeShift) * actOutFreq * 360.; newPhase = pval(newPhase); timeShift = newTimeShift; phaseShiftBox->setValue(newPhase); } void entrails_grouper::phaseShiftChanged(double newPhaseShift) { //-- std::cout << "phaseShiftChange: " << newPhaseShift << std::endl; while (newPhaseShift > 360.) newPhaseShift -= 360.; while (newPhaseShift < -360.) newPhaseShift += 360.; phaseShift = newPhaseShift; } class bad_thing: public std::exception{ const char* msg; virtual const char* what() const throw() { return msg; } public: bad_thing(const char* _msg) : msg(_msg) {} }; double max(const std::valarray& foo){ unsigned int howmany(foo.size()); if (howmany == 0) throw bad_thing("max of empty list"); double rslt = foo[0]; for (unsigned int ii = 1; ii < howmany; ii++) { rslt = std::max(rslt, foo[ii]); } return rslt; } double min(const std::valarray& foo){ unsigned int howmany(foo.size()); if (howmany == 0) throw bad_thing("min of empty list"); double rslt = foo[0]; for (unsigned int ii = 1; ii < howmany; ii++) { rslt = std::min(rslt, foo[ii]); } return rslt; } void entrails_grouper::tweakButtonClicked(){ using namespace std; double rawPhase = (lockerPhase + phaseShift) + actOutFreq*timeShift * 360.; if (tweakPtr >= 10) tweakPtr = 0; tweakFreq[tweakPtr] = actOutFreq; tweakPhase[tweakPtr] = rawPhase; tweakPtr++; if (howmany < tweakPtr) howmany = tweakPtr; // otherwise howmany stays at its maximum, i.e. tweakSize. valarray myFreq(&tweakFreq[0], howmany); valarray myPhase(&tweakPhase[0], howmany); double big = max(myFreq); double little = min(myFreq); if (big == little) { double newPhase = myPhase.sum() / howmany - timeShift*myFreq[0]*360.; // This will change the value in the box, and raise // the valueChanged signal: phaseShiftBox->setValue(pval(newPhase)); } else do { valarray cookedPhase(myPhase); valarray cookedFreq(myFreq); for (unsigned int ii = 0; ii < howmany; ii++) { cookedFreq[ii] -= actOutFreq; cookedPhase[ii] -= phaseShift + timeShift*myFreq[ii]*360.; cookedPhase[ii] = pval(cookedPhase[ii]); } if (max(cookedPhase) > 90. || min(cookedPhase) < -90.) { cout << "Phase range too big; can't tweak." << endl; break; } double intercept, slope; double cv00, cv01, cv11; double sumsq; gsl_fit_linear(&cookedFreq[0], 1, &cookedPhase[0], 1, howmany, &intercept, &slope, &cv00, &cv01, &cv11, &sumsq); double newTime = timeShift + slope / 360.; double newPhase = phaseShift + intercept - (newTime - timeShift) * actOutFreq * 360.; // must do time shift first: timeShiftBox->setValue(newTime); phaseShiftBox->setValue(pval(newPhase)); } while (0); } void myWindow::actualFreqs() { using namespace std; double d_out_freq = ctrlCol->refOutGroup->freqBox->value(); { // calculatte cycles per krunch period double d_krunch_rate = ctrlCol->entrailsGroup->rateBox->value(); if (d_krunch_rate == 0.) cpkp = 1; else cpkp = round(d_out_freq/d_krunch_rate); if (cpkp < 1.) cpkp = 1.; } // frames per krunch period: fpkp = round(cpkp * pcmRate / d_out_freq); double actUp = pcmRate / fpkp; ctrlCol->entrailsGroup->actRateBox->setValue(actUp); ctrlCol->entrailsGroup->pcmBox->setValue(pcmRate); actOutFreq = cpkp * actUp; ctrlCol->refOutGroup->actFreqBox->setValue(actOutFreq); if (verbosity > 0) { cout.precision(4); cout << fixed; cout << " d_out_freq: " << d_out_freq << " cpkp: " << cpkp << " fpkp: " << fpkp << " actUp: " << setw(10) << actUp << " actOutFreq: " << actOutFreq << endl; } } myPlot::myPlot(const QString name) : QwtPlot(QwtText(name)), grid(), divsPerUnit(5), need_replot(0) { // FIXME : should calculate these sizes: setMinimumWidth(500); setMaximumWidth(500); setMinimumHeight(500); setMaximumHeight(500); // Set up axis, permanently -1 to 1 in both directions. // We are ASSUMING the library function will give us // five minor divisions per unit (ten total). setAxisScale(QwtPlot::xBottom,-1.0, 1.0, 1.); setAxisScale(QwtPlot::yLeft, -1.0, 1.0, 1.); enableAxis(QwtPlot::xBottom, 0); enableAxis(QwtPlot::yLeft, 0); setStyleSheet("border:0;"); grid.enableXMin(true); grid.enableYMin(true); grid.setMajorPen(QPen(Qt::white, 2)); grid.setMinorPen(QPen(Qt::white, 1)); // was: (QPen(Qt::white, 1, Qt::DotLine)); grid.attach(this); } int myPlot::assign_curve(){ int ndx = curve.size(); curve.push_back(new QwtPlotCurve); int npts(2); double xxx[npts]; double yyy[npts]; xxx[0] = 0; yyy[0] = 0; xxx[1] = sin(.1 * ndx); yyy[1] = cos(.2 * ndx); ///////////// curve[ndx]->setData(xxx, yyy, npts); curve[ndx]->setSamples(xxx, yyy, npts); curve[ndx]->setPen(QPen(color_list[ndx % color_list_size].code, 4)); curve[ndx]->attach(this); return ndx; } //////////////////////////////////// timer::timer(QWidget *parent) : QWidget(parent) { startTimer(33); // 30.303 repaints per second } void timer::timerEvent(QTimerEvent* /* event not used */){ using namespace std; repaintFrame = 1; }