diff --git a/PhaseEqualizationDialog.cpp b/PhaseEqualizationDialog.cpp index 264d545e8..2d4e07122 100644 --- a/PhaseEqualizationDialog.cpp +++ b/PhaseEqualizationDialog.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -19,88 +21,211 @@ namespace { + float constexpr PI = 3.1415927f; char const * const title = "Phase Equalization"; size_t constexpr intervals = 144; // plot data loaders - wraps a plot providing value_type and // push_back so that a std::back_inserter output iterator can be // used to load plot data - template + template struct plot_data_loader { public: typedef T value_type; - plot_data_loader (QCustomPlot * plot, int graph_index) + // the adjust argument is a function that is passed the plot + // pointer, the graph index and a data point, it returns a + // possibly adjusted data point and can modify the graph including + // adding extra points or gaps (quiet_NaN) + plot_data_loader (QCustomPlot * plot, int graph_index, A adjust) : plot_ {plot} - , index_ {graph_index } + , index_ {graph_index} + , adjust_ (adjust) { } // load point into graph void push_back (value_type const& d) { - plot_->graph (index_)->data ()->add (d); + plot_->graph (index_)->data ()->add (adjust_ (plot_, index_, d)); } private: QCustomPlot * plot_; int index_; + A adjust_; + }; + // helper function template to make a plot_data_loader instance + template + auto make_plot_data_loader (QCustomPlot * plot, int index, A adjust) + -> plot_data_loader + { + return plot_data_loader {plot, index, adjust}; + } + // identity adjust function when none is needed in the above + // instantiation helper + QCPGraphData adjust_identity (QCustomPlot *, int, QCPGraphData const& v) {return v;} + + // a plot_data_loader adjustment function that wraps Y values of + // (-1..+1) plotting discontinuities as gaps in the graph data + auto wrap_pi = [] (QCustomPlot * plot, int index, QCPGraphData d) + { + double constexpr limit {1}; + static unsigned wrap_count {0}; + static double last_x {std::numeric_limits::lowest ()}; + + d.value += 2 * limit * wrap_count; + if (d.value > limit) + { + // insert a gap in the graph + plot->graph (index)->data ()->add ({last_x + (d.key - last_x) / 2 + , std::numeric_limits::quiet_NaN ()}); + while (d.value > limit) + { + --wrap_count; + d.value -= 2 * limit; + } + } + else if (d.value < -limit) + { + // insert a gap into the graph + plot->graph (index)->data ()->add ({last_x + (d.key - last_x) / 2 + , std::numeric_limits::quiet_NaN ()}); + while (d.value < -limit) + { + ++wrap_count; + d.value += 2 * limit; + } + } + last_x = d.key; + return d; }; - typedef plot_data_loader graph_loader_type; - - // generate points of type T for a 5 term polynomial for x in - // (-1..+1) with N intervals and function S to scale X + // generate points of type R from a function of type F for X in + // (-1..+1) with N intervals and function of type SX to scale X and + // of type SY to scale Y // // it is up to the user to call the generator sufficient times which // is interval+1 times to reach +1 - template - struct poly_generator + template + struct graph_generator { public: - poly_generator (QVector const& coeffs, size_t intervals, S scaling = [] (T x) {return x;}) + graph_generator (F f, size_t intervals, SX x_scaling, SY y_scaling) : x_ {0} + , f_ (f) , intervals_ {intervals} - , scaling_ (scaling) - , coeffs_ {coeffs} + , x_scaling_ (x_scaling) + , y_scaling_ (y_scaling) { } R operator () () { - T x {x_++ * 2.f / intervals_ - 1.f}; - return {scaling_ (x), coeffs_[0] + x * (coeffs_[1] + x * (coeffs_[2] + x * (coeffs_[3] + x * coeffs_[4])))}; + typename F::value_type x {x_++ * 2.f / intervals_ - 1.f}; + return {x_scaling_ (x), y_scaling_ (f_ (x))}; } - size_t intervals () const {return intervals_;} - private: int x_; + F f_; size_t intervals_; - S scaling_; - QVector coeffs_; + SX x_scaling_; + SY y_scaling_; }; - - // make our n=5 polynomial single precision FP generator of + // helper function template to make a graph_generator instance for // QCPGraphData type points with intervals intervals - template - auto make_poly_generator (QVector const& coeffs, S x_scaling) - -> poly_generator + template + auto make_graph_generator (F function, SX x_scaling, SY y_scaling) + -> graph_generator { - return poly_generator {coeffs, intervals, x_scaling}; + return graph_generator + {function, intervals, x_scaling, y_scaling}; } - // a lambda that scales the X axis appropriately - auto x_scaling = [] (float x) -> float {return 1500. + 1000 * x;}; + // template function object for a polynomial with coefficients + template + class polynomial + { + public: + typedef typename C::value_type value_type; + + explicit polynomial (C const& coefficients) + : c_ {coefficients} + { + } + + value_type operator () (value_type const& x) + { + value_type y {}; + for (typename C::size_type i = c_.size (); i > 0; --i) + { + y = c_[i - 1] + x * y; + } + return y; + } + + private: + C c_; + }; + // helper function template to instantiate a polynomial instance + template + auto make_polynomial (C const& coefficients) -> polynomial + { + return polynomial (coefficients); + } + + // template function object for a group delay with coefficients + template + class group_delay + { + public: + typedef typename C::value_type value_type; + + explicit group_delay (C const& coefficients) + : c_ {coefficients} + { + } + + value_type operator () (value_type const& x) + { + value_type tau {}; + for (typename C::size_type i = 2; i < c_.size (); ++i) + { + tau += i * c_[i] * std::pow (x, i - 1); + } + return -1 / (2 * PI) * tau; + } + + private: + C c_; + }; + // helper function template to instantiate a group_delay function + // object + template + auto make_group_delay (C const& coefficients) -> group_delay + { + return group_delay (coefficients); + } + + // handy identity function + template T identity (T const& v) {return v;} + + // a lambda that scales the X axis from normalized to (500..2500)Hz + auto freq_scaling = [] (float v) -> float {return 1500.f + 1000.f * v;}; + + // a lambda that scales the phase Y axis from radians to units of Pi + auto pi_scaling = [] (float v) -> float {return v / PI;}; } -// read a phase point line from a stream -std::istream& operator >> (std::istream& is, graph_loader_type::value_type& v) +// read a phase point line from a stream (pcoeff file) +std::istream& operator >> (std::istream& is, QCPGraphData& v) { float pp, sigmay; // discard these is >> v.key >> pp >> v.value >> sigmay; v.key = 1500. + 1000. * v.key; // scale frequency to Hz + v.value /= PI; // scale to units of Pi return is; } @@ -179,11 +304,18 @@ PhaseEqualizationDialog::impl::impl (PhaseEqualizationDialog * self layout_.addWidget (&plot_); plot_.xAxis->setLabel (tr ("Freq (Hz)")); - plot_.yAxis->setLabel (tr ("Phase (Radians)")); - plot_.legend->setVisible (true); - plot_.setInteractions (QCP::iRangeDrag | QCP::iRangeZoom); + plot_.xAxis->setRange (500, 2500); + plot_.yAxis->setLabel (tr ("Phase (Π)")); + plot_.yAxis->setRange (-1, +1); + plot_.yAxis2->setLabel (tr ("Delay (ms)")); plot_.axisRect ()->setRangeDrag (Qt::Vertical); plot_.axisRect ()->setRangeZoom (Qt::Vertical); + plot_.yAxis2->setVisible (true); + plot_.axisRect ()->setRangeDragAxes (0, plot_.yAxis2); + plot_.axisRect ()->setRangeZoomAxes (0, plot_.yAxis2); + plot_.axisRect ()->insetLayout ()->setInsetAlignment (0, Qt::AlignBottom|Qt::AlignRight); + plot_.legend->setVisible (true); + plot_.setInteractions (QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables); plot_.addGraph (); plot_.graph ()->setName (tr ("Measured")); @@ -201,8 +333,8 @@ PhaseEqualizationDialog::impl::impl (PhaseEqualizationDialog * self plot_.graph ()->setName (tr ("Current")); plot_.graph ()->setPen (QPen {Qt::green}); - plot_.addGraph (); - plot_.graph ()->setName (tr ("Current as Used")); + plot_.addGraph (plot_.xAxis, plot_.yAxis2); + plot_.graph ()->setName (tr ("Group Delay")); plot_.graph ()->setPen (QPen {Qt::darkGreen}); auto load_button = button_box_.addButton (tr ("Load ..."), QDialogButtonBox::ActionRole); @@ -242,9 +374,6 @@ PhaseEqualizationDialog::impl::impl (PhaseEqualizationDialog * self plot_.graph (1)->setVisible (false); plot_.graph (1)->removeFromLegend (); - plot_.graph (2)->rescaleAxes (); - plot_.graph (3)->rescaleValueAxis (true); - plot_.replot (); } }); @@ -257,24 +386,18 @@ void PhaseEqualizationDialog::impl::plot_current () plot_.graph (2)->data ()->clear (); plot_.graph (3)->data ()->clear (); { - graph_loader_type graph {&plot_, 2}; - std::generate_n (std::back_inserter (graph), intervals + 1, make_poly_generator (current_coefficients_, x_scaling)); + // plot the current polynomial + auto graph = make_plot_data_loader (&plot_, 2, wrap_pi); + std::generate_n (std::back_inserter (graph), intervals + 1 + , make_graph_generator (make_polynomial (current_coefficients_), freq_scaling, pi_scaling)); } { - // plot the adjusted polynomial using only the three high order terms - graph_loader_type graph {&plot_, 3}; - QVector reduced {current_coefficients_}; - reduced[0] = 0.f; - reduced[1] = 0.f; - std::generate_n (std::back_inserter (graph), intervals + 1, make_poly_generator (reduced, x_scaling)); + // plot the group delay for the current polynomial + auto graph = make_plot_data_loader (&plot_, 3, adjust_identity); + std::generate_n (std::back_inserter (graph), intervals + 1 + , make_graph_generator (make_group_delay (current_coefficients_), freq_scaling, identity)); + plot_.graph (3)->rescaleValueAxis (); } - plot_.graph (2)->rescaleAxes (); - plot_.graph (3)->rescaleValueAxis (true); - if (plot_.graph (0)->dataCount ()) - { - plot_.graph (0)->rescaleValueAxis (true); - plot_.graph (1)->rescaleValueAxis (true); - } plot_.replot (); } @@ -302,28 +425,22 @@ void PhaseEqualizationDialog::impl::plot () plot_.graph (1)->data ()->clear (); { // read the phase data to plot into graph 0 - graph_loader_type graph {&plot_, 0}; - std::istream_iterator start {coeffs_source}; + auto graph = make_plot_data_loader (&plot_, 0, adjust_identity); + std::istream_iterator start {coeffs_source}; std::copy_n (start, intervals + 1, std::back_inserter (graph)); } { // generate the proposed polynomial plot in graph 1 - graph_loader_type graph {&plot_, 1}; - std::generate_n (std::back_inserter (graph), intervals + 1, make_poly_generator (new_coefficients_, x_scaling)); + auto graph = make_plot_data_loader (&plot_, 1, wrap_pi); + std::generate_n (std::back_inserter (graph), intervals + 1 + , make_graph_generator (make_polynomial (new_coefficients_), freq_scaling, pi_scaling)); } plot_.graph (0)->setVisible (true); plot_.graph (0)->addToLegend (); - plot_.graph (0)->rescaleAxes (); plot_.graph (1)->setVisible (true); plot_.graph (1)->addToLegend (); - plot_.graph (1)->rescaleValueAxis (true); - if (plot_.graph (2)->dataCount ()) - { - plot_.graph (2)->rescaleValueAxis (true); - plot_.graph (3)->rescaleValueAxis (true); - } plot_.replot (); } }