<NME> toolbox_FFT.ui
<BEF> ADDFILE
<MSG> big work with FFT and PlotWidgetBase refactoring

<DFF> @@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>toolbox_fft</class>
+ <widget class="QWidget" name="toolbox_fft">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>1079</width>
+    <height>756</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,0,1,0,1,0,0">
+   <item>
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Drag and Drop a timeseries here (from the left panel):</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <property name="topMargin">
+      <number>6</number>
+     </property>
+     <property name="bottomMargin">
+      <number>6</number>
+     </property>
+     <item>
+      <widget class="QLineEdit" name="lineEdit"/>
+     </item>
+     <item>
+      <widget class="QLabel" name="label_4">
+       <property name="text">
+        <string>Sampling period:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDoubleSpinBox" name="spinBoxPeriod">
+       <property name="readOnly">
+        <bool>true</bool>
+       </property>
+       <property name="buttonSymbols">
+        <enum>QAbstractSpinBox::NoButtons</enum>
+       </property>
+       <property name="decimals">
+        <number>4</number>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="label_5">
+       <property name="text">
+        <string>Standard deviation:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDoubleSpinBox" name="spinBoxStdDev">
+       <property name="readOnly">
+        <bool>true</bool>
+       </property>
+       <property name="buttonSymbols">
+        <enum>QAbstractSpinBox::NoButtons</enum>
+       </property>
+       <property name="decimals">
+        <number>4</number>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="Line" name="line">
+     <property name="frameShadow">
+      <enum>QFrame::Plain</enum>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Original Curve</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QFrame" name="frameA">
+     <property name="frameShape">
+      <enum>QFrame::Box</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Plain</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_3">
+     <property name="topMargin">
+      <number>10</number>
+     </property>
+     <item>
+      <widget class="QLabel" name="label_3">
+       <property name="text">
+        <string>FFT: Frequencies</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer_3">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QRadioButton" name="radioAll">
+       <property name="text">
+        <string>All Data</string>
+       </property>
+       <property name="checked">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QRadioButton" name="radioZoomed">
+       <property name="text">
+        <string>Zoom Area</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QCheckBox" name="checkBox">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="text">
+        <string>Resample</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QFrame" name="frameB">
+     <property name="frameShape">
+      <enum>QFrame::Box</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Plain</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <property name="bottomMargin">
+      <number>11</number>
+     </property>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="pushButton">
+       <property name="text">
+        <string>Save Fequency Curve</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer_2">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

{'commit': 'diff --git a/CMakeLists.txt b/CMakeLists.txt\nindex fbae371e..5b2fc925 100644\n--- a/CMakeLists.txt\n+++ b/CMakeLists.txt\n@@ -231,6 +231,7 @@ add_subdirectory( plotjuggler_plugins/DataStreamWebsocket )\n add_subdirectory( plotjuggler_plugins/DataStreamUDP )\n add_subdirectory( plotjuggler_plugins/StatePublisherCSV )\n add_subdirectory( plotjuggler_plugins/ToolboxQuaternion )\n+add_subdirectory( plotjuggler_plugins/ToolboxFFT )\n \n if( ZMQ_FOUND )\n   add_subdirectory( plotjuggler_plugins/DataStreamZMQ )\ndiff --git a/plotjuggler_app/mainwindow.cpp b/plotjuggler_app/mainwindow.cpp\nindex d7bb86da..055d54d7 100644\n--- a/plotjuggler_app/mainwindow.cpp\n+++ b/plotjuggler_app/mainwindow.cpp\n@@ -566,6 +566,9 @@ QStringList MainWindow::initializePlugins(QString directory_name)\n       else if (message_parser){\n         plugin_name = message_parser->name();\n       }\n+      else if (toolbox){\n+        plugin_name = toolbox->name();\n+      }\n \n       if (loaded_plugins.find(plugin_name) == loaded_plugins.end())\n       {\n@@ -699,6 +702,7 @@ QStringList MainWindow::initializePlugins(QString directory_name)\n       }\n       else if(toolbox)\n       {\n+        qDebug() << filename << ": is a Toolbox plugin";\n         toolbox->init( _mapped_plot_data, _transform_functions );\n \n         auto action = ui->menuTools->addAction( toolbox->name() );\n@@ -852,6 +856,7 @@ void MainWindow::onPlotAdded(PlotWidget* plot)\n   plot->on_changeDateTimeScale(ui->pushButtonUseDateTime->isChecked());\n   plot->activateGrid(ui->pushButtonActivateGrid->isChecked());\n   plot->enableTracker(!isStreamingActive());\n+  plot->setKeepRatioXY(ui->pushButtonRatio->isChecked());\n   plot->configureTracker(_tracker_param);\n }\n \n@@ -2167,7 +2172,7 @@ void MainWindow::on_pushButtonActivateGrid_toggled(bool checked)\n void MainWindow::on_pushButtonRatio_toggled(bool checked)\n {\n   forEachWidget([checked](PlotWidget* plot) {\n-    plot->setConstantRatioXY(checked);\n+    plot->setKeepRatioXY(checked);\n     plot->replot();\n   });\n }\ndiff --git a/plotjuggler_app/plotwidget.cpp b/plotjuggler_app/plotwidget.cpp\nindex fd4ee520..14c8b80f 100644\n--- a/plotjuggler_app/plotwidget.cpp\n+++ b/plotjuggler_app/plotwidget.cpp\n@@ -71,9 +71,7 @@ PlotWidget::PlotWidget(PlotDataMapRef& datamap, QWidget* parent)\n   , _use_date_time_scale(false)\n   , _dragging({ DragInfo::NONE, {}, nullptr })\n   , _time_offset(0.0)\n-  , _xy_mode(false)\n   , _transform_select_dialog(nullptr)\n-  , _keep_aspect_ratio(true)\n   ,_context_menu_enabled(true)\n {\n   connect(this, &PlotWidget::curveListChanged,\n@@ -486,7 +484,7 @@ void PlotWidget::onDropEvent(QDropEvent*)\n   }\n   else if (_dragging.mode == DragInfo::NEW_XY && _dragging.curves.size() == 2)\n   {\n-    if (!curveList().empty() && !_xy_mode)\n+    if (!curveList().empty() && !isXYPlot())\n     {\n       _dragging.mode = DragInfo::NONE;\n       _dragging.curves.clear();\n@@ -524,7 +522,7 @@ QDomElement PlotWidget::xmlSaveState(QDomDocument& doc) const\n   QDomElement plot_el = doc.createElement("plot");\n \n   QDomElement range_el = doc.createElement("range");\n-  QRectF rect = this->canvasBoundingRect();\n+  QRectF rect = canvasBoundingRect();\n   range_el.setAttribute("bottom", QString::number(rect.bottom(), \'f\', 6));\n   range_el.setAttribute("top", QString::number(rect.top(), \'f\', 6));\n   range_el.setAttribute("left", QString::number(rect.left(), \'f\', 6));\n@@ -732,61 +730,6 @@ bool PlotWidget::xmlLoadState(QDomElement& plot_widget)\n   return true;\n }\n \n-QRectF PlotWidget::canvasBoundingRect() const\n-{\n-  QRectF rect;\n-  rect.setBottom(qwtPlot()->canvasMap(QwtPlot::yLeft).s1());\n-  rect.setTop(qwtPlot()->canvasMap(QwtPlot::yLeft).s2());\n-  rect.setLeft(qwtPlot()->canvasMap(QwtPlot::xBottom).s1());\n-  rect.setRight(qwtPlot()->canvasMap(QwtPlot::xBottom).s2());\n-  return rect;\n-}\n-\n-void PlotWidget::updateMaximumZoomArea()\n-{\n-  QRectF max_rect;\n-  auto rangeX = getMaximumRangeX();\n-  max_rect.setLeft(rangeX.min);\n-  max_rect.setRight(rangeX.max);\n-\n-  auto rangeY = getMaximumRangeY(rangeX);\n-  max_rect.setBottom(rangeY.min);\n-  max_rect.setTop(rangeY.max);\n-\n-  if (isXYPlot() && _keep_aspect_ratio)\n-  {\n-    const QRectF canvas_rect = qwtPlot()->canvas()->contentsRect();\n-    const double canvas_ratio = fabs(canvas_rect.width() / canvas_rect.height());\n-    const double data_ratio = fabs(max_rect.width() / max_rect.height());\n-    if (data_ratio < canvas_ratio)\n-    {\n-      // height is negative!!!!\n-      double new_width = fabs(max_rect.height() * canvas_ratio);\n-      double increment = new_width - max_rect.width();\n-      max_rect.setWidth(new_width);\n-      max_rect.moveLeft(max_rect.left() - 0.5 * increment);\n-    }\n-    else\n-    {\n-      // height must be negative!!!!\n-      double new_height = -(max_rect.width() / canvas_ratio);\n-      double increment = fabs(new_height - max_rect.height());\n-      max_rect.setHeight(new_height);\n-      max_rect.moveTop(max_rect.top() + 0.5 * increment);\n-    }\n-    magnifier()->setAxisLimits(QwtPlot::xBottom, max_rect.left(), max_rect.right());\n-    magnifier()->setAxisLimits(QwtPlot::yLeft, max_rect.bottom(), max_rect.top());\n-    zoomer()->keepAspectRatio(true);\n-  }\n-  else\n-  {\n-    magnifier()->setAxisLimits(QwtPlot::xBottom, max_rect.left(), max_rect.right());\n-    magnifier()->setAxisLimits(QwtPlot::yLeft, max_rect.bottom(), max_rect.top());\n-    zoomer()->keepAspectRatio(false);\n-  }\n-  _max_zoom_rect = max_rect;\n-}\n-\n void PlotWidget::rescaleEqualAxisScaling()\n {\n   const QwtScaleMap xMap = qwtPlot()->canvasMap(QwtPlot::xBottom);\n@@ -818,9 +761,9 @@ void PlotWidget::rescaleEqualAxisScaling()\n     rect.setHeight(new_height);\n     rect.moveTop(rect.top() + 0.5 * increment);\n   }\n-  if (rect.contains(_max_zoom_rect))\n+  if (rect.contains(maxZoomRect()))\n   {\n-    rect = _max_zoom_rect;\n+    rect = maxZoomRect();\n   }\n \n   qwtPlot()->setAxisScale(QwtPlot::yLeft,\n@@ -832,20 +775,6 @@ void PlotWidget::rescaleEqualAxisScaling()\n   qwtPlot()->updateAxes();\n }\n \n-void PlotWidget::setConstantRatioXY(bool active)\n-{\n-  _keep_aspect_ratio = active;\n-  if (isXYPlot() && active)\n-  {\n-    zoomer()->keepAspectRatio(true);\n-  }\n-  else\n-  {\n-    zoomer()->keepAspectRatio(false);\n-  }\n-  zoomOut(false);\n-}\n-\n void PlotWidget::setZoomRectangle(QRectF rect, bool emit_signal)\n {\n   QRectF current_rect = canvasBoundingRect();\n@@ -861,7 +790,7 @@ void PlotWidget::setZoomRectangle(QRectF rect, bool emit_signal)\n                      std::max(rect.left(), rect.right()));\n   qwtPlot()->updateAxes();\n \n-  if (isXYPlot() && _keep_aspect_ratio)\n+  if (isXYPlot() && keepRatioXY())\n   {\n     rescaleEqualAxisScaling();\n   }\n@@ -1005,73 +934,12 @@ void PlotWidget::on_changeDateTimeScale(bool enable)\n   }\n }\n \n-Range PlotWidget::getMaximumRangeX() const\n-{\n-  auto [left, right] = PlotWidgetBase::getMaximumRangeX();\n-\n-  double margin = 0.0;\n-  if (fabs(right - left) > std::numeric_limits<double>::epsilon())\n-  {\n-    margin = isXYPlot() ? ((right - left) * 0.025) : 0.0;\n-  }\n-  right = right + margin;\n-  left = left - margin;\n-\n-  return Range({ left, right });\n-}\n-\n // TODO report failure for empty dataset\n-Range PlotWidget::getMaximumRangeY(Range range_X) const\n+Range PlotWidget::getVisualizationRangeY(Range range_X) const\n {\n-  double top = -std::numeric_limits<double>::max();\n-  double bottom = std::numeric_limits<double>::max();\n-\n-  for (auto& it : curveList())\n-  {\n-    if (!it.curve->isVisible())\n-      continue;\n-\n-    auto series = dynamic_cast<QwtSeriesWrapper*>(it.curve->data());\n-\n-    const auto max_range_X = series->getVisualizationRangeX();\n-    if (!max_range_X){\n-      continue;\n-    }\n-\n-    double left = std::max(max_range_X->min, range_X.min);\n-    double right = std::min(max_range_X->max, range_X.max);\n-\n-    left += _time_offset;\n-    right += _time_offset;\n-    left = std::nextafter(left, right);\n-    right = std::nextafter(right, left);\n-\n-    auto range_Y = series->getVisualizationRangeY({ left, right });\n-    if (!range_Y)\n-    {\n-      qDebug() << " invalid range_Y in PlotWidget::maximumRangeY";\n-      continue;\n-    }\n-    if (top < range_Y->max){\n-      top = range_Y->max;\n-    }\n-    if (bottom > range_Y->min){\n-      bottom = range_Y->min;\n-    }\n-  }\n-\n-  double margin = 0.1;\n-\n-  if (bottom > top)\n-  {\n-    bottom = 0;\n-    top = 0;\n-  }\n-\n-  if (top - bottom > std::numeric_limits<double>::epsilon())\n-  {\n-    margin = (top - bottom) * 0.025;\n-  }\n+  range_X.min += _time_offset;\n+  range_X.max += _time_offset;\n+  auto [bottom, top] = PlotWidgetBase::getVisualizationRangeY(range_X);\n \n   const bool lower_limit = _custom_Y_limits.min > -MAX_DOUBLE;\n   const bool upper_limit = _custom_Y_limits.max < MAX_DOUBLE;\n@@ -1080,7 +948,7 @@ Range PlotWidget::getMaximumRangeY(Range range_X) const\n   {\n     bottom = _custom_Y_limits.min;\n     if (top < bottom){\n-      top = bottom + margin;\n+      top = bottom;\n     }\n   }\n \n@@ -1088,16 +956,10 @@ Range PlotWidget::getMaximumRangeY(Range range_X) const\n   {\n     top = _custom_Y_limits.max;\n     if (top < bottom){\n-      bottom = top - margin;\n+      bottom = top;\n     }\n   }\n \n-  if (!lower_limit && !upper_limit)\n-  {\n-    top += margin;\n-    bottom -= margin;\n-  }\n-\n   return Range({ bottom, top });\n }\n \n@@ -1133,8 +995,6 @@ void PlotWidget::on_changeCurveColor(const QString& curve_name, QColor new_color\n \n void PlotWidget::on_externallyResized(const QRectF& rect)\n {\n-  //updateMaximumZoomArea();\n-\n   QRectF current_rect = canvasBoundingRect();\n   if (current_rect == rect)\n   {\n@@ -1143,7 +1003,7 @@ void PlotWidget::on_externallyResized(const QRectF& rect)\n \n   if (isXYPlot())\n   {\n-    if( _keep_aspect_ratio ) {\n+    if( keepRatioXY() ) {\n       rescaleEqualAxisScaling();\n     }\n     emit undoableChange();\n@@ -1163,7 +1023,7 @@ void PlotWidget::zoomOut(bool emit_signal)\n     return;\n   }\n   updateMaximumZoomArea();\n-  setZoomRectangle(_max_zoom_rect, emit_signal);\n+  setZoomRectangle( maxZoomRect(), emit_signal);\n   replot();\n }\n \n@@ -1171,7 +1031,7 @@ void PlotWidget::on_zoomOutHorizontal_triggered(bool emit_signal)\n {\n   updateMaximumZoomArea();\n   QRectF act = canvasBoundingRect();\n-  auto rangeX = getMaximumRangeX();\n+  auto rangeX = getVisualizationRangeX();\n \n   act.setLeft(rangeX.min);\n   act.setRight(rangeX.max);\n@@ -1182,25 +1042,20 @@ void PlotWidget::on_zoomOutVertical_triggered(bool emit_signal)\n {\n   updateMaximumZoomArea();\n   QRectF rect = canvasBoundingRect();\n-  auto rangeY = getMaximumRangeY({ rect.left(), rect.right() });\n+  auto rangeY = getVisualizationRangeY({ rect.left(), rect.right() });\n \n   rect.setBottom(rangeY.min);\n   rect.setTop(rangeY.max);\n   this->setZoomRectangle(rect, emit_signal);\n }\n \n-bool PlotWidget::isXYPlot() const\n-{\n-  return _xy_mode;\n-}\n-\n void PlotWidget::setModeXY(bool enable)\n {\n-  if( enable == _xy_mode)\n+  if( enable == isXYPlot() )\n   {\n     return;\n   }\n-  _xy_mode = enable;\n+  PlotWidgetBase::setModeXY( enable );\n \n   enableTracker(!enable);\n \n@@ -1397,7 +1252,7 @@ bool PlotWidget::eventFilter(QObject* obj, QEvent* event)\n   if (magnifier() &&\n       (obj == bottomAxis || obj == leftAxis)\n       && !(isXYPlot()\n-      && _keep_aspect_ratio))\n+      && keepRatioXY()))\n   {\n     if (event->type() == QEvent::Wheel)\n     {\ndiff --git a/plotjuggler_app/plotwidget.h b/plotjuggler_app/plotwidget.h\nindex 6f123774..ad16df8a 100644\n--- a/plotjuggler_app/plotwidget.h\n+++ b/plotjuggler_app/plotwidget.h\n@@ -40,22 +40,14 @@ public:\n \n   bool xmlLoadState(QDomElement& element);\n \n-  Range getMaximumRangeX() const override;\n-\n-  Range getMaximumRangeY(Range range_X) const override;\n+  Range getVisualizationRangeY(Range range_X) const override;\n \n   void setZoomRectangle(QRectF rect, bool emit_signal);\n \n   void reloadPlotData();\n \n-  bool isXYPlot() const;\n-\n   void changeBackgroundColor(QColor color);\n \n-  QRectF canvasBoundingRect() const;\n-\n-  void setConstantRatioXY(bool active);\n-\n   double timeOffset() const\n   {\n     return _time_offset;\n@@ -137,7 +129,7 @@ private slots:\n \n   //void on_changeToBuiltinTransforms(QString new_transform);\n \n-  void setModeXY(bool enable);\n+  void setModeXY(bool enable) override;\n \n   void on_savePlotToFile();\n \n@@ -202,21 +194,15 @@ private:\n \n   double _time_offset;\n \n-  bool _xy_mode;\n-\n   Range _custom_Y_limits;\n \n   TransformSelector* _transform_select_dialog;\n \n   SnippetsMap _snippets;\n \n-  bool _keep_aspect_ratio;\n-\n-  QRectF _max_zoom_rect;\n-\n   bool _context_menu_enabled;\n \n-  void updateMaximumZoomArea();\n+  //void updateMaximumZoomArea();\n   void rescaleEqualAxisScaling();\n   void overrideCursonMove();\n };\ndiff --git a/plotjuggler_app/plotwidget_editor.cpp b/plotjuggler_app/plotwidget_editor.cpp\nindex 76e96894..e1c270fe 100644\n--- a/plotjuggler_app/plotwidget_editor.cpp\n+++ b/plotjuggler_app/plotwidget_editor.cpp\n@@ -64,8 +64,8 @@ PlotwidgetEditor::PlotwidgetEditor(PlotWidget *plotwidget, QWidget *parent) :\n   ui->lineLimitMin->setValidator(new QDoubleValidator(this));\n \n   auto ylimits = _plotwidget->customAxisLimit();\n-  auto range_x = _plotwidget->getMaximumRangeX();\n-  Range suggested_limits = _plotwidget->getMaximumRangeY(range_x);\n+  auto range_x = _plotwidget->getVisualizationRangeX();\n+  Range suggested_limits = _plotwidget->getVisualizationRangeY(range_x);\n \n   if( ylimits.min != -MAX_DOUBLE)\n   {\n@@ -302,8 +302,8 @@ void PlotwidgetEditor::on_pushButtonReset_clicked()\n \n   _plotwidget->setCustomAxisLimits(no_limits);\n \n-  auto range_x = _plotwidget->getMaximumRangeX();\n-  Range limits = _plotwidget->getMaximumRangeY(range_x);\n+  auto range_x = _plotwidget->getVisualizationRangeX();\n+  Range limits = _plotwidget->getVisualizationRangeY(range_x);\n \n   ui->lineLimitMin->setText(QString::number(limits.min));\n   ui->lineLimitMax->setText(QString::number(limits.max));\ndiff --git a/plotjuggler_base/include/PlotJuggler/plotwidget_base.h b/plotjuggler_base/include/PlotJuggler/plotwidget_base.h\nindex 721774f0..1db52395 100644\n--- a/plotjuggler_base/include/PlotJuggler/plotwidget_base.h\n+++ b/plotjuggler_base/include/PlotJuggler/plotwidget_base.h\n@@ -44,7 +44,6 @@ public:\n                               PlotData &src_data,\n                               QColor color = Qt::transparent);\n \n-\n   virtual void removeCurve(const QString& title);\n \n   const std::list<CurveInfo> &curveList() const;\n@@ -61,6 +60,14 @@ public:\n       const QString& transform_ID,\n       const PlotData* data);\n \n+  virtual void resetZoom();\n+\n+  virtual PJ::Range getVisualizationRangeX() const;\n+\n+  virtual PJ::Range getVisualizationRangeY(PJ::Range range_X) const;\n+\n+  virtual void setModeXY(bool enable);\n+\n   void setLegendSize(int size);\n \n   void setLegendAlignment(Qt::Alignment alignment);\n@@ -71,16 +78,21 @@ public:\n \n   void changeCurvesStyle(CurveStyle style);\n \n-  CurveStyle curveStyle() const;\n+  bool isXYPlot() const;\n+\n+  QRectF canvasBoundingRect() const;\n+\n+  QRectF maxZoomRect() const;\n \n   QWidget* widget();\n+\n   const QWidget* widget() const;\n \n-  void resetZoom();\n+  CurveStyle curveStyle() const;\n \n-  virtual PJ::Range getMaximumRangeX() const;\n+  bool keepRatioXY() const;\n \n-  virtual PJ::Range getMaximumRangeY(PJ::Range range_X) const;\n+  void setKeepRatioXY(bool active);\n \n public slots:\n \n@@ -114,6 +126,14 @@ protected:\n   PlotZoomer* zoomer();\n   PlotMagnifier* magnifier();\n \n+  void updateMaximumZoomArea();\n+\n+private:\n+  bool _xy_mode;\n+\n+  QRectF _max_zoom_rect;\n+\n+  bool _keep_aspect_ratio;\n };\n \n }\ndiff --git a/plotjuggler_base/src/plotwidget_base.cpp b/plotjuggler_base/src/plotwidget_base.cpp\nindex fd9d757e..833ab0ea 100644\n--- a/plotjuggler_base/src/plotwidget_base.cpp\n+++ b/plotjuggler_base/src/plotwidget_base.cpp\n@@ -134,6 +134,8 @@ public:\n     resized_callback(canvasBoundingRect());\n   }\n \n+\n+\n   std::list<CurveInfo> curve_list;\n \n   CurveStyle curve_style = LINES;\n@@ -177,38 +179,21 @@ const QWidget* PlotWidgetBase::widget() const\n \n void PlotWidgetBase::resetZoom()\n {\n-  QRectF rect(0, 1, 1, -1);\n-  if (curveList().size() > 0)\n-  {\n-    auto rangeX = getMaximumRangeX();\n-    rect.setLeft(rangeX.min);\n-    rect.setRight(rangeX.max);\n+  updateMaximumZoomArea();\n+  QRectF rect = maxZoomRect();\n \n-    auto rangeY = getMaximumRangeY(rangeX);\n-    rect.setBottom(rangeY.min);\n-    rect.setTop(rangeY.max);\n-  }\n-\n-  magnifier()->setAxisLimits(QwtPlot::xBottom, rect.left(), rect.right());\n-  magnifier()->setAxisLimits(QwtPlot::yLeft, rect.bottom(), rect.top());\n-\n-  QRectF current_rect = p->canvasBoundingRect();\n-  if (current_rect == rect)\n-  {\n-    return;\n-  }\n   qwtPlot()->setAxisScale(QwtPlot::yLeft,\n-                     std::min(rect.bottom(), rect.top()),\n-                     std::max(rect.bottom(), rect.top()));\n+                          std::min(rect.bottom(), rect.top()),\n+                          std::max(rect.bottom(), rect.top()));\n   qwtPlot()->setAxisScale(QwtPlot::xBottom,\n-                     std::min(rect.left(), rect.right()),\n-                     std::max(rect.left(), rect.right()));\n+                          std::min(rect.left(), rect.right()),\n+                          std::max(rect.left(), rect.right()));\n   qwtPlot()->updateAxes();\n \n   replot();\n }\n \n-Range PlotWidgetBase::getMaximumRangeX() const\n+Range PlotWidgetBase::getVisualizationRangeX() const\n {\n   double left = std::numeric_limits<double>::max();\n   double right = -std::numeric_limits<double>::max();\n@@ -220,8 +205,9 @@ Range PlotWidgetBase::getMaximumRangeX() const\n \n     auto series = dynamic_cast<QwtSeriesWrapper*>(it.curve->data());\n     const auto max_range_X = series->getVisualizationRangeX();\n-    if (!max_range_X)\n+    if (!max_range_X){\n       continue;\n+    }\n \n     left = std::min(max_range_X->min, left);\n     right = std::max(max_range_X->max, right);\n@@ -232,10 +218,19 @@ Range PlotWidgetBase::getMaximumRangeX() const\n     left = 0;\n     right = 0;\n   }\n+\n+  double margin = 0.0;\n+  if (fabs(right - left) > std::numeric_limits<double>::epsilon())\n+  {\n+    margin = isXYPlot() ? ((right - left) * 0.025) : 0.0;\n+  }\n+  right = right + margin;\n+  left = left - margin;\n+\n   return Range({ left, right });\n }\n \n-Range PlotWidgetBase::getMaximumRangeY(Range range_X) const\n+Range PlotWidgetBase::getVisualizationRangeY(Range range_X) const\n {\n   double top = -std::numeric_limits<double>::max();\n   double bottom = std::numeric_limits<double>::max();\n@@ -247,7 +242,7 @@ Range PlotWidgetBase::getMaximumRangeY(Range range_X) const\n \n     auto series = dynamic_cast<QwtSeriesWrapper*>(it.curve->data());\n \n-    const auto max_range_X = series->getVisualizationRangeX();\n+    const auto max_range_X = series->plotData()->rangeX();\n     if (!max_range_X){\n       continue;\n     }\n@@ -290,10 +285,34 @@ Range PlotWidgetBase::getMaximumRangeY(Range range_X) const\n   return Range({ bottom, top });\n }\n \n+bool PlotWidgetBase::isXYPlot() const\n+{\n+  return _xy_mode;\n+}\n+\n+QRectF PlotWidgetBase::canvasBoundingRect() const\n+{\n+  return p->canvasBoundingRect();\n+}\n+\n+QRectF PlotWidgetBase::maxZoomRect() const\n+{\n+  return _max_zoom_rect;\n+}\n+\n+void PlotWidgetBase::setModeXY(bool enable)\n+{\n+  _xy_mode = enable;\n+}\n+\n \n-PlotWidgetBase::PlotWidgetBase(QWidget *parent)\n+PlotWidgetBase::PlotWidgetBase(QWidget *parent):\n+  _xy_mode(false),\n+  _keep_aspect_ratio(false)\n {\n-  auto onViewResized = [this](const QRectF& r) { emit viewResized(r); };\n+  auto onViewResized = [this](const QRectF& r) {\n+    emit viewResized(r);\n+  };\n \n   auto onEvent = [this](QEvent* event)\n   {\n@@ -467,6 +486,25 @@ PlotWidgetBase::CurveStyle PlotWidgetBase::curveStyle() const\n   return p->curve_style;\n }\n \n+bool PlotWidgetBase::keepRatioXY() const\n+{\n+  return _keep_aspect_ratio;\n+}\n+\n+void PlotWidgetBase::setKeepRatioXY(bool active)\n+{\n+  _keep_aspect_ratio = active;\n+  if (isXYPlot() && active)\n+  {\n+    zoomer()->keepAspectRatio(true);\n+  }\n+  else\n+  {\n+    zoomer()->keepAspectRatio(false);\n+  }\n+}\n+\n+\n QColor PlotWidgetBase::getColorHint(PlotData* data)\n {\n   QSettings settings;\n@@ -644,4 +682,51 @@ PlotMagnifier* PlotWidgetBase::magnifier()\n   return p->magnifier;\n }\n \n+void PlotWidgetBase::updateMaximumZoomArea()\n+{\n+  QRectF max_rect;\n+  auto rangeX = getVisualizationRangeX();\n+  max_rect.setLeft(rangeX.min);\n+  max_rect.setRight(rangeX.max);\n+\n+  rangeX.min = -std::numeric_limits<double>::max();\n+  rangeX.max = std::numeric_limits<double>::max();\n+  auto rangeY = getVisualizationRangeY(rangeX);\n+  max_rect.setBottom(rangeY.min);\n+  max_rect.setTop(rangeY.max);\n+\n+  if (isXYPlot() && _keep_aspect_ratio)\n+  {\n+    const QRectF canvas_rect = p->canvas()->contentsRect();\n+    const double canvas_ratio = fabs(canvas_rect.width() / canvas_rect.height());\n+    const double data_ratio = fabs(max_rect.width() / max_rect.height());\n+    if (data_ratio < canvas_ratio)\n+    {\n+      // height is negative!!!!\n+      double new_width = fabs(max_rect.height() * canvas_ratio);\n+      double increment = new_width - max_rect.width();\n+      max_rect.setWidth(new_width);\n+      max_rect.moveLeft(max_rect.left() - 0.5 * increment);\n+    }\n+    else\n+    {\n+      // height must be negative!!!!\n+      double new_height = -(max_rect.width() / canvas_ratio);\n+      double increment = fabs(new_height - max_rect.height());\n+      max_rect.setHeight(new_height);\n+      max_rect.moveTop(max_rect.top() + 0.5 * increment);\n+    }\n+    magnifier()->setAxisLimits(QwtPlot::xBottom, max_rect.left(), max_rect.right());\n+    magnifier()->setAxisLimits(QwtPlot::yLeft, max_rect.bottom(), max_rect.top());\n+    zoomer()->keepAspectRatio(true);\n+  }\n+  else\n+  {\n+    magnifier()->setAxisLimits(QwtPlot::xBottom, max_rect.left(), max_rect.right());\n+    magnifier()->setAxisLimits(QwtPlot::yLeft, max_rect.bottom(), max_rect.top());\n+    zoomer()->keepAspectRatio(false);\n+  }\n+  _max_zoom_rect = max_rect;\n+}\n+\n }\ndiff --git a/plotjuggler_base/src/plotzoomer.cpp b/plotjuggler_base/src/plotzoomer.cpp\nindex 094067b8..315c457f 100644\n--- a/plotjuggler_base/src/plotzoomer.cpp\n+++ b/plotjuggler_base/src/plotzoomer.cpp\n@@ -12,7 +12,7 @@ PlotZoomer::PlotZoomer(QWidget* canvas)\n   : QwtPlotZoomer(canvas, false)\n   , _mouse_pressed(false)\n   , _zoom_enabled(false)\n-  , _keep_aspect_ratio(true)\n+  , _keep_aspect_ratio(false)\n {\n   this->setTrackerMode(AlwaysOff);\n }\ndiff --git a/plotjuggler_plugins/ToolboxFFT/CMakeLists.txt b/plotjuggler_plugins/ToolboxFFT/CMakeLists.txt\nnew file mode 100644\nindex 00000000..6e0a0feb\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/CMakeLists.txt\n@@ -0,0 +1,24 @@\n+\n+include_directories( ../ )\n+\n+add_definitions(${QT_DEFINITIONS})\n+add_definitions(-DQT_PLUGIN)\n+\n+add_subdirectory(KissFFT)\n+\n+QT5_WRAP_UI ( UI_SRC\n+    toolbox_FFT.ui\n+    )\n+\n+add_library(ToolboxFFT SHARED\n+    toolbox_FFT.cpp\n+    toolbox_FFT.h\n+    ${UI_SRC}  )\n+\n+target_link_libraries(ToolboxFFT\n+    ${Qt5Widgets_LIBRARIES}\n+    ${Qt5Xml_LIBRARIES}\n+    kissfft\n+    plotjuggler_base)\n+\n+install(TARGETS ToolboxFFT DESTINATION ${PJ_PLUGIN_INSTALL_DIRECTORY}  )\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/CMakeLists.txt b/plotjuggler_plugins/ToolboxFFT/KissFFT/CMakeLists.txt\nnew file mode 100644\nindex 00000000..25b86877\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/CMakeLists.txt\n@@ -0,0 +1,123 @@\n+\n+cmake_minimum_required(VERSION 3.6)\n+project(kissfft)\n+\n+#\n+# CMake configuration options\n+# Principal datatype: double, float (default), int16_t, int32_t, simd\n+\n+set(KISSFFT_DATATYPE "float" CACHE STRING "Principal datatype of kissfft: double, float (default), int16_t, int32_t, simd")\n+\n+if (NOT KISSFFT_DATATYPE MATCHES "^double$" AND\n+    NOT KISSFFT_DATATYPE MATCHES "^float$" AND\n+    NOT KISSFFT_DATATYPE MATCHES "^int16_t$" AND\n+    NOT KISSFFT_DATATYPE MATCHES "^int32_t$" AND\n+    NOT KISSFFT_DATATYPE MATCHES "^simd$")\n+    message(FATAL_ERROR "Incorrect value of KISSFFT_DATATYPE! It can be one of: double, float, int16_t, int32_t, simd")\n+endif()\n+\n+#\n+# Print principal datatype\n+message(STATUS "Building KissFFT with datatype: ${KISSFFT_DATATYPE}")\n+\n+#\n+# Detect C compiler and pass appropriate flags\n+#\n+\n+if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang|AppleClang")\n+    add_compile_options(-fomit-frame-pointer -fPIC\n+        -W -Wall -Wcast-align -Wcast-qual -Wshadow -Wwrite-strings\n+        "$<$<COMPILE_LANGUAGE:C>:-Wstrict-prototypes;-Wmissing-prototypes;-Wnested-externs;-Wbad-function-cast>")\n+endif()\n+\n+#\n+# Add GNUInstallDirs for GNU infrastructure before target)include_directories\n+#\n+\n+if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU)$" AND NOT CMAKE_CROSSCOMPILING)\n+    include(GNUInstallDirs)\n+endif()\n+\n+\n+#\n+# Declare kissfft library ( libkissfft.a / libkissfft-${KISSFFT_DATATYPE}.so.${MAKEFILE_EXTRACTED_VERSION} )\n+#\n+\n+add_library(kissfft\n+  kiss_fft.c\n+  kfc.c\n+  kiss_fftnd.c\n+  kiss_fftndr.c\n+  kiss_fftr.c)\n+\n+target_include_directories(kissfft PUBLIC\n+    $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>\n+    $<INSTALL_INTERFACE:${PKGINCLUDEDIR}>)\n+\n+#\n+# Set compile definitions based on datatype and additional support flags\n+#\n+\n+set(KISSFFT_COMPILE_DEFINITIONS)\n+\n+#\n+# double / float\n+#\n+\n+if(KISSFFT_DATATYPE MATCHES "^float$" OR KISSFFT_DATATYPE MATCHES "^double$")\n+    list(APPEND KISSFFT_COMPILE_DEFINITIONS kiss_fft_scalar=${KISSFFT_DATATYPE})\n+else()\n+\n+    #\n+    # int16_t\n+    #\n+\n+    if(KISSFFT_DATATYPE MATCHES "^int16_t$")\n+        list(APPEND KISSFFT_COMPILE_DEFINITIONS FIXED_POINT=16)\n+    else()\n+\n+        #\n+        # int32_t\n+        #\n+\n+        if(KISSFFT_DATATYPE MATCHES "^int32_t$")\n+            list(APPEND KISSFFT_COMPILE_DEFINITIONS FIXED_POINT=32)\n+        else()\n+\n+            #\n+            # simd\n+            #\n+\n+            if(KISSFFT_DATATYPE MATCHES "^simd$")\n+                list(APPEND KISSFFT_COMPILE_DEFINITIONS USE_SIMD)\n+                if (NOT MSVC)\n+                    target_compile_options(kissfft PRIVATE -msse)\n+                else()\n+                    target_compile_options(kissfft PRIVATE "/arch:SSE")\n+                endif()\n+            endif()\n+        endif()\n+    endif()\n+endif()\n+\n+\n+#\n+# Static library\n+#\n+set(KISSFFT_EXPORT_SUFFIX "${KISSFFT_EXPORT_SUFFIX}-static")\n+\n+\n+# Set library name, version, soversion and aliases\n+\n+target_compile_definitions(kissfft PUBLIC ${KISSFFT_COMPILE_DEFINITIONS})\n+add_library(kissfft::kissfft ALIAS kissfft)\n+\n+#\n+# Build with libm (-lm) on Linux and kFreeBSD\n+#\n+\n+if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU)$" AND NOT CMAKE_CROSSCOMPILING)\n+    target_link_libraries(kissfft PRIVATE m)\n+endif()\n+\n+\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/COPYING b/plotjuggler_plugins/ToolboxFFT/KissFFT/COPYING\nnew file mode 100644\nindex 00000000..6b4b622e\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/COPYING\n@@ -0,0 +1,11 @@\n+Copyright (c) 2003-2010 Mark Borgerding . All rights reserved.\n+\n+KISS FFT is provided under:\n+\n+  SPDX-License-Identifier: BSD-3-Clause\n+\n+Being under the terms of the BSD 3-clause "New" or "Revised" License,\n+according with:\n+\n+  LICENSES/BSD-3-Clause\n+\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/_kiss_fft_guts.h b/plotjuggler_plugins/ToolboxFFT/KissFFT/_kiss_fft_guts.h\nnew file mode 100644\nindex 00000000..4bd8d1c1\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/_kiss_fft_guts.h\n@@ -0,0 +1,167 @@\n+/*\n+ *  Copyright (c) 2003-2010, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+/* kiss_fft.h\n+   defines kiss_fft_scalar as either short or a float type\n+   and defines\n+   typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */\n+\n+#ifndef _kiss_fft_guts_h\n+#define _kiss_fft_guts_h\n+\n+#include "kiss_fft.h"\n+#include "kiss_fft_log.h"\n+#include <limits.h>\n+\n+#define MAXFACTORS 32\n+/* e.g. an fft of length 128 has 4 factors\n+ as far as kissfft is concerned\n+ 4*4*4*2\n+ */\n+\n+struct kiss_fft_state{\n+    int nfft;\n+    int inverse;\n+    int factors[2*MAXFACTORS];\n+    kiss_fft_cpx twiddles[1];\n+};\n+\n+/*\n+  Explanation of macros dealing with complex math:\n+\n+   C_MUL(m,a,b)         : m = a*b\n+   C_FIXDIV( c , div )  : if a fixed point impl., c /= div. noop otherwise\n+   C_SUB( res, a,b)     : res = a - b\n+   C_SUBFROM( res , a)  : res -= a\n+   C_ADDTO( res , a)    : res += a\n+ * */\n+#ifdef FIXED_POINT\n+#include <stdint.h>\n+#if (FIXED_POINT==32)\n+# define FRACBITS 31\n+# define SAMPPROD int64_t\n+#define SAMP_MAX INT32_MAX\n+#define SAMP_MIN INT32_MIN\n+#else\n+# define FRACBITS 15\n+# define SAMPPROD int32_t\n+#define SAMP_MAX INT16_MAX\n+#define SAMP_MIN INT16_MIN\n+#endif\n+\n+#if defined(CHECK_OVERFLOW)\n+#  define CHECK_OVERFLOW_OP(a,op,b)  \\\n+    if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \\\n+        KISS_FFT_WARNING("overflow (%d " #op" %d) = %ld", (a),(b),(SAMPPROD)(a) op (SAMPPROD)(b)); }\n+#endif\n+\n+\n+#   define smul(a,b) ( (SAMPPROD)(a)*(b) )\n+#   define sround( x )  (kiss_fft_scalar)( ( (x) + (1<<(FRACBITS-1)) ) >> FRACBITS )\n+\n+#   define S_MUL(a,b) sround( smul(a,b) )\n+\n+#   define C_MUL(m,a,b) \\\n+      do{ (m).r = sround( smul((a).r,(b).r) - smul((a).i,(b).i) ); \\\n+          (m).i = sround( smul((a).r,(b).i) + smul((a).i,(b).r) ); }while(0)\n+\n+#   define DIVSCALAR(x,k) \\\n+    (x) = sround( smul(  x, SAMP_MAX/k ) )\n+\n+#   define C_FIXDIV(c,div) \\\n+    do {    DIVSCALAR( (c).r , div);  \\\n+        DIVSCALAR( (c).i  , div); }while (0)\n+\n+#   define C_MULBYSCALAR( c, s ) \\\n+    do{ (c).r =  sround( smul( (c).r , s ) ) ;\\\n+        (c).i =  sround( smul( (c).i , s ) ) ; }while(0)\n+\n+#else  /* not FIXED_POINT*/\n+\n+#   define S_MUL(a,b) ( (a)*(b) )\n+#define C_MUL(m,a,b) \\\n+    do{ (m).r = (a).r*(b).r - (a).i*(b).i;\\\n+        (m).i = (a).r*(b).i + (a).i*(b).r; }while(0)\n+#   define C_FIXDIV(c,div) /* NOOP */\n+#   define C_MULBYSCALAR( c, s ) \\\n+    do{ (c).r *= (s);\\\n+        (c).i *= (s); }while(0)\n+#endif\n+\n+#ifndef CHECK_OVERFLOW_OP\n+#  define CHECK_OVERFLOW_OP(a,op,b) /* noop */\n+#endif\n+\n+#define  C_ADD( res, a,b)\\\n+    do { \\\n+        CHECK_OVERFLOW_OP((a).r,+,(b).r)\\\n+        CHECK_OVERFLOW_OP((a).i,+,(b).i)\\\n+        (res).r=(a).r+(b).r;  (res).i=(a).i+(b).i; \\\n+    }while(0)\n+#define  C_SUB( res, a,b)\\\n+    do { \\\n+        CHECK_OVERFLOW_OP((a).r,-,(b).r)\\\n+        CHECK_OVERFLOW_OP((a).i,-,(b).i)\\\n+        (res).r=(a).r-(b).r;  (res).i=(a).i-(b).i; \\\n+    }while(0)\n+#define C_ADDTO( res , a)\\\n+    do { \\\n+        CHECK_OVERFLOW_OP((res).r,+,(a).r)\\\n+        CHECK_OVERFLOW_OP((res).i,+,(a).i)\\\n+        (res).r += (a).r;  (res).i += (a).i;\\\n+    }while(0)\n+\n+#define C_SUBFROM( res , a)\\\n+    do {\\\n+        CHECK_OVERFLOW_OP((res).r,-,(a).r)\\\n+        CHECK_OVERFLOW_OP((res).i,-,(a).i)\\\n+        (res).r -= (a).r;  (res).i -= (a).i; \\\n+    }while(0)\n+\n+\n+#ifdef FIXED_POINT\n+#  define KISS_FFT_COS(phase)  floor(.5+SAMP_MAX * cos (phase))\n+#  define KISS_FFT_SIN(phase)  floor(.5+SAMP_MAX * sin (phase))\n+#  define HALF_OF(x) ((x)>>1)\n+#elif defined(USE_SIMD)\n+#  define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) )\n+#  define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) )\n+#  define HALF_OF(x) ((x)*_mm_set1_ps(.5))\n+#else\n+#  define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase)\n+#  define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase)\n+#  define HALF_OF(x) ((x)*((kiss_fft_scalar).5))\n+#endif\n+\n+#define  kf_cexp(x,phase) \\\n+    do{ \\\n+        (x)->r = KISS_FFT_COS(phase);\\\n+        (x)->i = KISS_FFT_SIN(phase);\\\n+    }while(0)\n+\n+\n+/* a debugging function */\n+#define pcpx(c)\\\n+    KISS_FFT_DEBUG("%g + %gi\\n",(double)((c)->r),(double)((c)->i))\n+\n+\n+#ifdef KISS_FFT_USE_ALLOCA\n+// define this to allow use of alloca instead of malloc for temporary buffers\n+// Temporary buffers are used in two case:\n+// 1. FFT sizes that have "bad" factors. i.e. not 2,3 and 5\n+// 2. "in-place" FFTs.  Notice the quotes, since kissfft does not really do an in-place transform.\n+#include <alloca.h>\n+#define  KISS_FFT_TMP_ALLOC(nbytes) alloca(nbytes)\n+#define  KISS_FFT_TMP_FREE(ptr)\n+#else\n+#define  KISS_FFT_TMP_ALLOC(nbytes) KISS_FFT_MALLOC(nbytes)\n+#define  KISS_FFT_TMP_FREE(ptr) KISS_FFT_FREE(ptr)\n+#endif\n+\n+#endif /* _kiss_fft_guts_h */\n+\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kfc.c b/plotjuggler_plugins/ToolboxFFT/KissFFT/kfc.c\nnew file mode 100644\nindex 00000000..a405d9b5\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kfc.c\n@@ -0,0 +1,109 @@\n+/*\n+ *  Copyright (c) 2003-2004, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+#include "kfc.h"\n+\n+typedef struct cached_fft *kfc_cfg;\n+\n+struct cached_fft\n+{\n+    int nfft;\n+    int inverse;\n+    kiss_fft_cfg cfg;\n+    kfc_cfg next;\n+};\n+\n+static kfc_cfg cache_root=NULL;\n+static int ncached=0;\n+\n+static kiss_fft_cfg find_cached_fft(int nfft,int inverse)\n+{\n+    size_t len;\n+    kfc_cfg  cur=cache_root;\n+    kfc_cfg  prev=NULL;\n+    while ( cur ) {\n+        if ( cur->nfft == nfft && inverse == cur->inverse )\n+            break;/*found the right node*/\n+        prev = cur;\n+        cur = prev->next;\n+    }\n+    if (cur== NULL) {\n+        /* no cached node found, need to create a new one*/\n+        kiss_fft_alloc(nfft,inverse,0,&len);\n+#ifdef USE_SIMD\n+        int padding = (16-sizeof(struct cached_fft)) & 15;\n+        // make sure the cfg aligns on a 16 byte boundary\n+        len += padding;\n+#endif\n+        cur = (kfc_cfg)KISS_FFT_MALLOC((sizeof(struct cached_fft) + len ));\n+        if (cur == NULL)\n+            return NULL;\n+        cur->cfg = (kiss_fft_cfg)(cur+1);\n+#ifdef USE_SIMD\n+        cur->cfg = (kiss_fft_cfg) ((char*)(cur+1)+padding);\n+#endif\n+        kiss_fft_alloc(nfft,inverse,cur->cfg,&len);\n+        cur->nfft=nfft;\n+        cur->inverse=inverse;\n+        cur->next = NULL;\n+        if ( prev )\n+            prev->next = cur;\n+        else\n+            cache_root = cur;\n+        ++ncached;\n+    }\n+    return cur->cfg;\n+}\n+\n+void kfc_cleanup(void)\n+{\n+    kfc_cfg  cur=cache_root;\n+    kfc_cfg  next=NULL;\n+    while (cur){\n+        next = cur->next;\n+        free(cur);\n+        cur=next;\n+    }\n+    ncached=0;\n+    cache_root = NULL;\n+}\n+void kfc_fft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout)\n+{\n+    kiss_fft( find_cached_fft(nfft,0),fin,fout );\n+}\n+\n+void kfc_ifft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout)\n+{\n+    kiss_fft( find_cached_fft(nfft,1),fin,fout );\n+}\n+\n+#ifdef KFC_TEST\n+static void check(int nc)\n+{\n+    if (ncached != nc) {\n+        fprintf(stderr,"ncached should be %d,but it is %d\\n",nc,ncached);\n+        exit(1);\n+    }\n+}\n+\n+int main(void)\n+{\n+    kiss_fft_cpx buf1[1024],buf2[1024];\n+    memset(buf1,0,sizeof(buf1));\n+    check(0);\n+    kfc_fft(512,buf1,buf2);\n+    check(1);\n+    kfc_fft(512,buf1,buf2);\n+    check(1);\n+    kfc_ifft(512,buf1,buf2);\n+    check(2);\n+    kfc_cleanup();\n+    check(0);\n+    return 0;\n+}\n+#endif\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kfc.h b/plotjuggler_plugins/ToolboxFFT/KissFFT/kfc.h\nnew file mode 100644\nindex 00000000..d7d8c1b4\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kfc.h\n@@ -0,0 +1,54 @@\n+/*\n+ *  Copyright (c) 2003-2004, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+#ifndef KFC_H\n+#define KFC_H\n+#include "kiss_fft.h"\n+\n+#ifdef __cplusplus\n+extern "C" {\n+#endif\n+\n+/*\n+KFC -- Kiss FFT Cache\n+\n+Not needing to deal with kiss_fft_alloc and a config \n+object may be handy for a lot of programs.\n+\n+KFC uses the underlying KISS FFT functions, but caches the config object. \n+The first time kfc_fft or kfc_ifft for a given FFT size, the cfg \n+object is created for it.  All subsequent calls use the cached \n+configuration object.\n+\n+NOTE:\n+You should probably not use this if your program will be using a lot \n+of various sizes of FFTs.  There is a linear search through the\n+cached objects.  If you are only using one or two FFT sizes, this\n+will be negligible. Otherwise, you may want to use another method \n+of managing the cfg objects.\n+ \n+ There is no automated cleanup of the cached objects.  This could lead \n+to large memory usage in a program that uses a lot of *DIFFERENT* \n+sized FFTs.  If you want to force all cached cfg objects to be freed,\n+call kfc_cleanup.\n+ \n+ */\n+\n+/*forward complex FFT */\n+void KISS_FFT_API kfc_fft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout);\n+/*reverse complex FFT */\n+void KISS_FFT_API kfc_ifft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout);\n+\n+/*free all cached objects*/\n+void KISS_FFT_API kfc_cleanup(void);\n+\n+#ifdef __cplusplus\n+}\n+#endif\n+\n+#endif\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fft.c b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fft.c\nnew file mode 100644\nindex 00000000..58c24a06\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fft.c\n@@ -0,0 +1,420 @@\n+/*\n+ *  Copyright (c) 2003-2010, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+\n+#include "_kiss_fft_guts.h"\n+/* The guts header contains all the multiplication and addition macros that are defined for\n+ fixed or floating point complex numbers.  It also delares the kf_ internal functions.\n+ */\n+\n+static void kf_bfly2(\n+        kiss_fft_cpx * Fout,\n+        const size_t fstride,\n+        const kiss_fft_cfg st,\n+        int m\n+        )\n+{\n+    kiss_fft_cpx * Fout2;\n+    kiss_fft_cpx * tw1 = st->twiddles;\n+    kiss_fft_cpx t;\n+    Fout2 = Fout + m;\n+    do{\n+        C_FIXDIV(*Fout,2); C_FIXDIV(*Fout2,2);\n+\n+        C_MUL (t,  *Fout2 , *tw1);\n+        tw1 += fstride;\n+        C_SUB( *Fout2 ,  *Fout , t );\n+        C_ADDTO( *Fout ,  t );\n+        ++Fout2;\n+        ++Fout;\n+    }while (--m);\n+}\n+\n+static void kf_bfly4(\n+        kiss_fft_cpx * Fout,\n+        const size_t fstride,\n+        const kiss_fft_cfg st,\n+        const size_t m\n+        )\n+{\n+    kiss_fft_cpx *tw1,*tw2,*tw3;\n+    kiss_fft_cpx scratch[6];\n+    size_t k=m;\n+    const size_t m2=2*m;\n+    const size_t m3=3*m;\n+\n+\n+    tw3 = tw2 = tw1 = st->twiddles;\n+\n+    do {\n+        C_FIXDIV(*Fout,4); C_FIXDIV(Fout[m],4); C_FIXDIV(Fout[m2],4); C_FIXDIV(Fout[m3],4);\n+\n+        C_MUL(scratch[0],Fout[m] , *tw1 );\n+        C_MUL(scratch[1],Fout[m2] , *tw2 );\n+        C_MUL(scratch[2],Fout[m3] , *tw3 );\n+\n+        C_SUB( scratch[5] , *Fout, scratch[1] );\n+        C_ADDTO(*Fout, scratch[1]);\n+        C_ADD( scratch[3] , scratch[0] , scratch[2] );\n+        C_SUB( scratch[4] , scratch[0] , scratch[2] );\n+        C_SUB( Fout[m2], *Fout, scratch[3] );\n+        tw1 += fstride;\n+        tw2 += fstride*2;\n+        tw3 += fstride*3;\n+        C_ADDTO( *Fout , scratch[3] );\n+\n+        if(st->inverse) {\n+            Fout[m].r = scratch[5].r - scratch[4].i;\n+            Fout[m].i = scratch[5].i + scratch[4].r;\n+            Fout[m3].r = scratch[5].r + scratch[4].i;\n+            Fout[m3].i = scratch[5].i - scratch[4].r;\n+        }else{\n+            Fout[m].r = scratch[5].r + scratch[4].i;\n+            Fout[m].i = scratch[5].i - scratch[4].r;\n+            Fout[m3].r = scratch[5].r - scratch[4].i;\n+            Fout[m3].i = scratch[5].i + scratch[4].r;\n+        }\n+        ++Fout;\n+    }while(--k);\n+}\n+\n+static void kf_bfly3(\n+         kiss_fft_cpx * Fout,\n+         const size_t fstride,\n+         const kiss_fft_cfg st,\n+         size_t m\n+         )\n+{\n+     size_t k=m;\n+     const size_t m2 = 2*m;\n+     kiss_fft_cpx *tw1,*tw2;\n+     kiss_fft_cpx scratch[5];\n+     kiss_fft_cpx epi3;\n+     epi3 = st->twiddles[fstride*m];\n+\n+     tw1=tw2=st->twiddles;\n+\n+     do{\n+         C_FIXDIV(*Fout,3); C_FIXDIV(Fout[m],3); C_FIXDIV(Fout[m2],3);\n+\n+         C_MUL(scratch[1],Fout[m] , *tw1);\n+         C_MUL(scratch[2],Fout[m2] , *tw2);\n+\n+         C_ADD(scratch[3],scratch[1],scratch[2]);\n+         C_SUB(scratch[0],scratch[1],scratch[2]);\n+         tw1 += fstride;\n+         tw2 += fstride*2;\n+\n+         Fout[m].r = Fout->r - HALF_OF(scratch[3].r);\n+         Fout[m].i = Fout->i - HALF_OF(scratch[3].i);\n+\n+         C_MULBYSCALAR( scratch[0] , epi3.i );\n+\n+         C_ADDTO(*Fout,scratch[3]);\n+\n+         Fout[m2].r = Fout[m].r + scratch[0].i;\n+         Fout[m2].i = Fout[m].i - scratch[0].r;\n+\n+         Fout[m].r -= scratch[0].i;\n+         Fout[m].i += scratch[0].r;\n+\n+         ++Fout;\n+     }while(--k);\n+}\n+\n+static void kf_bfly5(\n+        kiss_fft_cpx * Fout,\n+        const size_t fstride,\n+        const kiss_fft_cfg st,\n+        int m\n+        )\n+{\n+    kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4;\n+    int u;\n+    kiss_fft_cpx scratch[13];\n+    kiss_fft_cpx * twiddles = st->twiddles;\n+    kiss_fft_cpx *tw;\n+    kiss_fft_cpx ya,yb;\n+    ya = twiddles[fstride*m];\n+    yb = twiddles[fstride*2*m];\n+\n+    Fout0=Fout;\n+    Fout1=Fout0+m;\n+    Fout2=Fout0+2*m;\n+    Fout3=Fout0+3*m;\n+    Fout4=Fout0+4*m;\n+\n+    tw=st->twiddles;\n+    for ( u=0; u<m; ++u ) {\n+        C_FIXDIV( *Fout0,5); C_FIXDIV( *Fout1,5); C_FIXDIV( *Fout2,5); C_FIXDIV( *Fout3,5); C_FIXDIV( *Fout4,5);\n+        scratch[0] = *Fout0;\n+\n+        C_MUL(scratch[1] ,*Fout1, tw[u*fstride]);\n+        C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]);\n+        C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]);\n+        C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]);\n+\n+        C_ADD( scratch[7],scratch[1],scratch[4]);\n+        C_SUB( scratch[10],scratch[1],scratch[4]);\n+        C_ADD( scratch[8],scratch[2],scratch[3]);\n+        C_SUB( scratch[9],scratch[2],scratch[3]);\n+\n+        Fout0->r += scratch[7].r + scratch[8].r;\n+        Fout0->i += scratch[7].i + scratch[8].i;\n+\n+        scratch[5].r = scratch[0].r + S_MUL(scratch[7].r,ya.r) + S_MUL(scratch[8].r,yb.r);\n+        scratch[5].i = scratch[0].i + S_MUL(scratch[7].i,ya.r) + S_MUL(scratch[8].i,yb.r);\n+\n+        scratch[6].r =  S_MUL(scratch[10].i,ya.i) + S_MUL(scratch[9].i,yb.i);\n+        scratch[6].i = -S_MUL(scratch[10].r,ya.i) - S_MUL(scratch[9].r,yb.i);\n+\n+        C_SUB(*Fout1,scratch[5],scratch[6]);\n+        C_ADD(*Fout4,scratch[5],scratch[6]);\n+\n+        scratch[11].r = scratch[0].r + S_MUL(scratch[7].r,yb.r) + S_MUL(scratch[8].r,ya.r);\n+        scratch[11].i = scratch[0].i + S_MUL(scratch[7].i,yb.r) + S_MUL(scratch[8].i,ya.r);\n+        scratch[12].r = - S_MUL(scratch[10].i,yb.i) + S_MUL(scratch[9].i,ya.i);\n+        scratch[12].i = S_MUL(scratch[10].r,yb.i) - S_MUL(scratch[9].r,ya.i);\n+\n+        C_ADD(*Fout2,scratch[11],scratch[12]);\n+        C_SUB(*Fout3,scratch[11],scratch[12]);\n+\n+        ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4;\n+    }\n+}\n+\n+/* perform the butterfly for one stage of a mixed radix FFT */\n+static void kf_bfly_generic(\n+        kiss_fft_cpx * Fout,\n+        const size_t fstride,\n+        const kiss_fft_cfg st,\n+        int m,\n+        int p\n+        )\n+{\n+    int u,k,q1,q;\n+    kiss_fft_cpx * twiddles = st->twiddles;\n+    kiss_fft_cpx t;\n+    int Norig = st->nfft;\n+\n+    kiss_fft_cpx * scratch = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC(sizeof(kiss_fft_cpx)*p);\n+    if (scratch == NULL){\n+        KISS_FFT_ERROR("Memory allocation failed.");\n+        return;\n+    }\n+\n+    for ( u=0; u<m; ++u ) {\n+        k=u;\n+        for ( q1=0 ; q1<p ; ++q1 ) {\n+            scratch[q1] = Fout[ k  ];\n+            C_FIXDIV(scratch[q1],p);\n+            k += m;\n+        }\n+\n+        k=u;\n+        for ( q1=0 ; q1<p ; ++q1 ) {\n+            int twidx=0;\n+            Fout[ k ] = scratch[0];\n+            for (q=1;q<p;++q ) {\n+                twidx += fstride * k;\n+                if (twidx>=Norig) twidx-=Norig;\n+                C_MUL(t,scratch[q] , twiddles[twidx] );\n+                C_ADDTO( Fout[ k ] ,t);\n+            }\n+            k += m;\n+        }\n+    }\n+    KISS_FFT_TMP_FREE(scratch);\n+}\n+\n+static\n+void kf_work(\n+        kiss_fft_cpx * Fout,\n+        const kiss_fft_cpx * f,\n+        const size_t fstride,\n+        int in_stride,\n+        int * factors,\n+        const kiss_fft_cfg st\n+        )\n+{\n+    kiss_fft_cpx * Fout_beg=Fout;\n+    const int p=*factors++; /* the radix  */\n+    const int m=*factors++; /* stage\'s fft length/p */\n+    const kiss_fft_cpx * Fout_end = Fout + p*m;\n+\n+#ifdef _OPENMP\n+    // use openmp extensions at the\n+    // top-level (not recursive)\n+    if (fstride==1 && p<=5 && m!=1)\n+    {\n+        int k;\n+\n+        // execute the p different work units in different threads\n+#       pragma omp parallel for\n+        for (k=0;k<p;++k)\n+            kf_work( Fout +k*m, f+ fstride*in_stride*k,fstride*p,in_stride,factors,st);\n+        // all threads have joined by this point\n+\n+        switch (p) {\n+            case 2: kf_bfly2(Fout,fstride,st,m); break;\n+            case 3: kf_bfly3(Fout,fstride,st,m); break;\n+            case 4: kf_bfly4(Fout,fstride,st,m); break;\n+            case 5: kf_bfly5(Fout,fstride,st,m); break;\n+            default: kf_bfly_generic(Fout,fstride,st,m,p); break;\n+        }\n+        return;\n+    }\n+#endif\n+\n+    if (m==1) {\n+        do{\n+            *Fout = *f;\n+            f += fstride*in_stride;\n+        }while(++Fout != Fout_end );\n+    }else{\n+        do{\n+            // recursive call:\n+            // DFT of size m*p performed by doing\n+            // p instances of smaller DFTs of size m,\n+            // each one takes a decimated version of the input\n+            kf_work( Fout , f, fstride*p, in_stride, factors,st);\n+            f += fstride*in_stride;\n+        }while( (Fout += m) != Fout_end );\n+    }\n+\n+    Fout=Fout_beg;\n+\n+    // recombine the p smaller DFTs\n+    switch (p) {\n+        case 2: kf_bfly2(Fout,fstride,st,m); break;\n+        case 3: kf_bfly3(Fout,fstride,st,m); break;\n+        case 4: kf_bfly4(Fout,fstride,st,m); break;\n+        case 5: kf_bfly5(Fout,fstride,st,m); break;\n+        default: kf_bfly_generic(Fout,fstride,st,m,p); break;\n+    }\n+}\n+\n+/*  facbuf is populated by p1,m1,p2,m2, ...\n+    where\n+    p[i] * m[i] = m[i-1]\n+    m0 = n                  */\n+static\n+void kf_factor(int n,int * facbuf)\n+{\n+    int p=4;\n+    double floor_sqrt;\n+    floor_sqrt = floor( sqrt((double)n) );\n+\n+    /*factor out powers of 4, powers of 2, then any remaining primes */\n+    do {\n+        while (n % p) {\n+            switch (p) {\n+                case 4: p = 2; break;\n+                case 2: p = 3; break;\n+                default: p += 2; break;\n+            }\n+            if (p > floor_sqrt)\n+                p = n;          /* no more factors, skip to end */\n+        }\n+        n /= p;\n+        *facbuf++ = p;\n+        *facbuf++ = n;\n+    } while (n > 1);\n+}\n+\n+/*\n+ *\n+ * User-callable function to allocate all necessary storage space for the fft.\n+ *\n+ * The return value is a contiguous block of memory, allocated with malloc.  As such,\n+ * It can be freed with free(), rather than a kiss_fft-specific function.\n+ * */\n+kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem )\n+{\n+    KISS_FFT_ALIGN_CHECK(mem)\n+\n+    kiss_fft_cfg st=NULL;\n+    size_t memneeded = KISS_FFT_ALIGN_SIZE_UP(sizeof(struct kiss_fft_state)\n+        + sizeof(kiss_fft_cpx)*(nfft-1)); /* twiddle factors*/\n+\n+    if ( lenmem==NULL ) {\n+        st = ( kiss_fft_cfg)KISS_FFT_MALLOC( memneeded );\n+    }else{\n+        if (mem != NULL && *lenmem >= memneeded)\n+            st = (kiss_fft_cfg)mem;\n+        *lenmem = memneeded;\n+    }\n+    if (st) {\n+        int i;\n+        st->nfft=nfft;\n+        st->inverse = inverse_fft;\n+\n+        for (i=0;i<nfft;++i) {\n+            const double pi=3.141592653589793238462643383279502884197169399375105820974944;\n+            double phase = -2*pi*i / nfft;\n+            if (st->inverse)\n+                phase *= -1;\n+            kf_cexp(st->twiddles+i, phase );\n+        }\n+\n+        kf_factor(nfft,st->factors);\n+    }\n+    return st;\n+}\n+\n+\n+void kiss_fft_stride(kiss_fft_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int in_stride)\n+{\n+    if (fin == fout) {\n+        //NOTE: this is not really an in-place FFT algorithm.\n+        //It just performs an out-of-place FFT into a temp buffer\n+        if (fout == NULL){\n+            KISS_FFT_ERROR("fout buffer NULL.");\n+        return;\n+        }\n+\n+        kiss_fft_cpx * tmpbuf = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC( sizeof(kiss_fft_cpx)*st->nfft);\n+        if (tmpbuf == NULL){\n+            KISS_FFT_ERROR("Memory allocation error.");\n+        return;\n+        }\n+\n+\n+\n+        kf_work(tmpbuf,fin,1,in_stride, st->factors,st);\n+        memcpy(fout,tmpbuf,sizeof(kiss_fft_cpx)*st->nfft);\n+        KISS_FFT_TMP_FREE(tmpbuf);\n+    }else{\n+        kf_work( fout, fin, 1,in_stride, st->factors,st );\n+    }\n+}\n+\n+void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout)\n+{\n+    kiss_fft_stride(cfg,fin,fout,1);\n+}\n+\n+\n+void kiss_fft_cleanup(void)\n+{\n+    // nothing needed any more\n+}\n+\n+int kiss_fft_next_fast_size(int n)\n+{\n+    while(1) {\n+        int m=n;\n+        while ( (m%2) == 0 ) m/=2;\n+        while ( (m%3) == 0 ) m/=3;\n+        while ( (m%5) == 0 ) m/=5;\n+        if (m<=1)\n+            break; /* n is completely factorable by twos, threes, and fives */\n+        n++;\n+    }\n+    return n;\n+}\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fft.h b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fft.h\nnew file mode 100644\nindex 00000000..dce1034a\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fft.h\n@@ -0,0 +1,160 @@\n+/*\n+ *  Copyright (c) 2003-2010, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+#ifndef KISS_FFT_H\n+#define KISS_FFT_H\n+\n+#include <stdlib.h>\n+#include <stdio.h>\n+#include <math.h>\n+#include <string.h>\n+\n+// Define KISS_FFT_SHARED macro to properly export symbols\n+#ifdef KISS_FFT_SHARED\n+# ifdef _WIN32\n+#  ifdef KISS_FFT_BUILD\n+#   define KISS_FFT_API __declspec(dllexport)\n+#  else\n+#   define KISS_FFT_API __declspec(dllimport)\n+#  endif\n+# else\n+#  define KISS_FFT_API __attribute__ ((visibility ("default")))\n+# endif\n+#else\n+# define KISS_FFT_API\n+#endif\n+\n+#ifdef __cplusplus\n+extern "C" {\n+#endif\n+\n+/*\n+ ATTENTION!\n+ If you would like a :\n+ -- a utility that will handle the caching of fft objects\n+ -- real-only (no imaginary time component ) FFT\n+ -- a multi-dimensional FFT\n+ -- a command-line utility to perform ffts\n+ -- a command-line utility to perform fast-convolution filtering\n+\n+ Then see kfc.h kiss_fftr.h kiss_fftnd.h fftutil.c kiss_fastfir.c\n+  in the tools/ directory.\n+*/\n+\n+/* User may override KISS_FFT_MALLOC and/or KISS_FFT_FREE. */\n+#ifdef USE_SIMD\n+# include <xmmintrin.h>\n+# define kiss_fft_scalar __m128\n+# ifndef KISS_FFT_MALLOC\n+#  define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16)\n+#  define KISS_FFT_ALIGN_CHECK(ptr) \n+#  define KISS_FFT_ALIGN_SIZE_UP(size) ((size + 15UL) & ~0xFUL)\n+# endif\n+# ifndef KISS_FFT_FREE\n+#  define KISS_FFT_FREE _mm_free\n+# endif\n+#else\n+# define KISS_FFT_ALIGN_CHECK(ptr)\n+# define KISS_FFT_ALIGN_SIZE_UP(size) (size)\n+# ifndef KISS_FFT_MALLOC\n+#  define KISS_FFT_MALLOC malloc\n+# endif\n+# ifndef KISS_FFT_FREE\n+#  define KISS_FFT_FREE free\n+# endif\n+#endif\n+\n+\n+#ifdef FIXED_POINT\n+#include <stdint.h>\n+# if (FIXED_POINT == 32)\n+#  define kiss_fft_scalar int32_t\n+# else\t\n+#  define kiss_fft_scalar int16_t\n+# endif\n+#else\n+# ifndef kiss_fft_scalar\n+/*  default is float */\n+#   define kiss_fft_scalar float\n+# endif\n+#endif\n+\n+typedef struct {\n+    kiss_fft_scalar r;\n+    kiss_fft_scalar i;\n+}kiss_fft_cpx;\n+\n+typedef struct kiss_fft_state* kiss_fft_cfg;\n+\n+/* \n+ *  kiss_fft_alloc\n+ *  \n+ *  Initialize a FFT (or IFFT) algorithm\'s cfg/state buffer.\n+ *\n+ *  typical usage:      kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL);\n+ *\n+ *  The return value from fft_alloc is a cfg buffer used internally\n+ *  by the fft routine or NULL.\n+ *\n+ *  If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc.\n+ *  The returned value should be free()d when done to avoid memory leaks.\n+ *  \n+ *  The state can be placed in a user supplied buffer \'mem\':\n+ *  If lenmem is not NULL and mem is not NULL and *lenmem is large enough,\n+ *      then the function places the cfg in mem and the size used in *lenmem\n+ *      and returns mem.\n+ *  \n+ *  If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough),\n+ *      then the function returns NULL and places the minimum cfg \n+ *      buffer size in *lenmem.\n+ * */\n+\n+kiss_fft_cfg KISS_FFT_API kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem);\n+\n+/*\n+ * kiss_fft(cfg,in_out_buf)\n+ *\n+ * Perform an FFT on a complex input buffer.\n+ * for a forward FFT,\n+ * fin should be  f[0] , f[1] , ... ,f[nfft-1]\n+ * fout will be   F[0] , F[1] , ... ,F[nfft-1]\n+ * Note that each element is complex and can be accessed like\n+    f[k].r and f[k].i\n+ * */\n+void KISS_FFT_API kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);\n+\n+/*\n+ A more generic version of the above function. It reads its input from every Nth sample.\n+ * */\n+void KISS_FFT_API kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride);\n+\n+/* If kiss_fft_alloc allocated a buffer, it is one contiguous \n+   buffer and can be simply free()d when no longer needed*/\n+#define kiss_fft_free KISS_FFT_FREE\n+\n+/*\n+ Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up \n+ your compiler output to call this before you exit.\n+*/\n+void KISS_FFT_API kiss_fft_cleanup(void);\n+\t\n+\n+/*\n+ * Returns the smallest integer k, such that k>=n and k has only "fast" factors (2,3,5)\n+ */\n+int KISS_FFT_API kiss_fft_next_fast_size(int n);\n+\n+/* for real ffts, we need an even size */\n+#define kiss_fftr_next_fast_size_real(n) \\\n+        (kiss_fft_next_fast_size( ((n)+1)>>1)<<1)\n+\n+#ifdef __cplusplus\n+} \n+#endif\n+\n+#endif\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fft_log.h b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fft_log.h\nnew file mode 100644\nindex 00000000..b5b631a0\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fft_log.h\n@@ -0,0 +1,36 @@\n+/*\n+ *  Copyright (c) 2003-2010, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+#ifndef kiss_fft_log_h\n+#define kiss_fft_log_h\n+\n+#define ERROR 1\n+#define WARNING 2\n+#define INFO 3\n+#define DEBUG 4\n+\n+#define STRINGIFY(x) #x\n+#define TOSTRING(x) STRINGIFY(x)\n+\n+#if defined(NDEBUG)\n+# define KISS_FFT_LOG_MSG(severity, ...) ((void)0)\n+#else\n+# define KISS_FFT_LOG_MSG(severity, ...) \\\n+    fprintf(stderr, "[" #severity "] " __FILE__ ":" TOSTRING(__LINE__) " "); \\\n+    fprintf(stderr, __VA_ARGS__); \\\n+    fprintf(stderr, "\\n")\n+#endif\n+\n+#define KISS_FFT_ERROR(...) KISS_FFT_LOG_MSG(ERROR, __VA_ARGS__)\n+#define KISS_FFT_WARNING(...) KISS_FFT_LOG_MSG(WARNING, __VA_ARGS__)\n+#define KISS_FFT_INFO(...) KISS_FFT_LOG_MSG(INFO, __VA_ARGS__)\n+#define KISS_FFT_DEBUG(...) KISS_FFT_LOG_MSG(DEBUG, __VA_ARGS__)\n+\n+\n+\n+#endif /* kiss_fft_log_h */\n\\ No newline at end of file\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftnd.c b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftnd.c\nnew file mode 100644\nindex 00000000..5d5b089a\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftnd.c\n@@ -0,0 +1,188 @@\n+/*\n+ *  Copyright (c) 2003-2004, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+#include "kiss_fftnd.h"\n+#include "_kiss_fft_guts.h"\n+\n+struct kiss_fftnd_state{\n+    int dimprod; /* dimsum would be mighty tasty right now */\n+    int ndims; \n+    int *dims;\n+    kiss_fft_cfg *states; /* cfg states for each dimension */\n+    kiss_fft_cpx * tmpbuf; /*buffer capable of hold the entire input */\n+};\n+\n+kiss_fftnd_cfg kiss_fftnd_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem)\n+{\n+    KISS_FFT_ALIGN_CHECK(mem)\n+\n+    kiss_fftnd_cfg st = NULL;\n+    int i;\n+    int dimprod=1;\n+    size_t memneeded = KISS_FFT_ALIGN_SIZE_UP(sizeof(struct kiss_fftnd_state));\n+    char * ptr = NULL;\n+\n+    for (i=0;i<ndims;++i) {\n+        size_t sublen=0;\n+        kiss_fft_alloc (dims[i], inverse_fft, NULL, &sublen);\n+        memneeded += sublen;   /* st->states[i] */\n+        dimprod *= dims[i];\n+    }\n+    memneeded += KISS_FFT_ALIGN_SIZE_UP(sizeof(int) * ndims);/*  st->dims */\n+    memneeded += KISS_FFT_ALIGN_SIZE_UP(sizeof(void*) * ndims);/* st->states  */\n+    memneeded += KISS_FFT_ALIGN_SIZE_UP(sizeof(kiss_fft_cpx) * dimprod); /* st->tmpbuf */\n+\n+    if (lenmem == NULL) {/* allocate for the caller*/\n+        ptr = (char *) malloc (memneeded);\n+    } else { /* initialize supplied buffer if big enough */\n+        if (*lenmem >= memneeded)\n+            ptr = (char *) mem;\n+        *lenmem = memneeded; /*tell caller how big struct is (or would be) */\n+    }\n+    if (!ptr)\n+        return NULL; /*malloc failed or buffer too small */\n+\n+    st = (kiss_fftnd_cfg) ptr;\n+    st->dimprod = dimprod;\n+    st->ndims = ndims;\n+    ptr += KISS_FFT_ALIGN_SIZE_UP(sizeof(struct kiss_fftnd_state));\n+\n+    st->states = (kiss_fft_cfg *)ptr;\n+    ptr += KISS_FFT_ALIGN_SIZE_UP(sizeof(void*) * ndims);\n+\n+    st->dims = (int*)ptr;\n+    ptr += KISS_FFT_ALIGN_SIZE_UP(sizeof(int) * ndims);\n+\n+    st->tmpbuf = (kiss_fft_cpx*)ptr;\n+    ptr += KISS_FFT_ALIGN_SIZE_UP(sizeof(kiss_fft_cpx) * dimprod);\n+\n+    for (i=0;i<ndims;++i) {\n+        size_t len;\n+        st->dims[i] = dims[i];\n+        kiss_fft_alloc (st->dims[i], inverse_fft, NULL, &len);\n+        st->states[i] = kiss_fft_alloc (st->dims[i], inverse_fft, ptr,&len);\n+        ptr += len;\n+    }\n+    /*\n+Hi there!\n+\n+If you\'re looking at this particular code, it probably means you\'ve got a brain-dead bounds checker \n+that thinks the above code overwrites the end of the array.\n+\n+It doesn\'t.\n+\n+-- Mark \n+\n+P.S.\n+The below code might give you some warm fuzzies and help convince you.\n+       */\n+    if ( ptr - (char*)st != (int)memneeded ) {\n+        fprintf(stderr,\n+                "################################################################################\\n"\n+                "Internal error! Memory allocation miscalculation\\n"\n+                "################################################################################\\n"\n+               );\n+    }\n+    return st;\n+}\n+\n+/*\n+ This works by tackling one dimension at a time.\n+\n+ In effect,\n+ Each stage starts out by reshaping the matrix into a DixSi 2d matrix.\n+ A Di-sized fft is taken of each column, transposing the matrix as it goes.\n+\n+Here\'s a 3-d example:\n+Take a 2x3x4 matrix, laid out in memory as a contiguous buffer\n+ [ [ [ a b c d ] [ e f g h ] [ i j k l ] ]\n+   [ [ m n o p ] [ q r s t ] [ u v w x ] ] ]\n+\n+Stage 0 ( D=2): treat the buffer as a 2x12 matrix\n+   [ [a b ... k l]\n+     [m n ... w x] ]\n+\n+   FFT each column with size 2.\n+   Transpose the matrix at the same time using kiss_fft_stride.\n+\n+   [ [ a+m a-m ]\n+     [ b+n b-n]\n+     ...\n+     [ k+w k-w ]\n+     [ l+x l-x ] ]\n+\n+   Note fft([x y]) == [x+y x-y]\n+\n+Stage 1 ( D=3) treats the buffer (the output of stage D=2) as an 3x8 matrix,\n+   [ [ a+m a-m b+n b-n c+o c-o d+p d-p ] \n+     [ e+q e-q f+r f-r g+s g-s h+t h-t ]\n+     [ i+u i-u j+v j-v k+w k-w l+x l-x ] ]\n+\n+   And perform FFTs (size=3) on each of the columns as above, transposing \n+   the matrix as it goes.  The output of stage 1 is \n+       (Legend: ap = [ a+m e+q i+u ]\n+                am = [ a-m e-q i-u ] )\n+   \n+   [ [ sum(ap) fft(ap)[0] fft(ap)[1] ]\n+     [ sum(am) fft(am)[0] fft(am)[1] ]\n+     [ sum(bp) fft(bp)[0] fft(bp)[1] ]\n+     [ sum(bm) fft(bm)[0] fft(bm)[1] ]\n+     [ sum(cp) fft(cp)[0] fft(cp)[1] ]\n+     [ sum(cm) fft(cm)[0] fft(cm)[1] ]\n+     [ sum(dp) fft(dp)[0] fft(dp)[1] ]\n+     [ sum(dm) fft(dm)[0] fft(dm)[1] ]  ]\n+\n+Stage 2 ( D=4) treats this buffer as a 4*6 matrix,\n+   [ [ sum(ap) fft(ap)[0] fft(ap)[1] sum(am) fft(am)[0] fft(am)[1] ]\n+     [ sum(bp) fft(bp)[0] fft(bp)[1] sum(bm) fft(bm)[0] fft(bm)[1] ]\n+     [ sum(cp) fft(cp)[0] fft(cp)[1] sum(cm) fft(cm)[0] fft(cm)[1] ]\n+     [ sum(dp) fft(dp)[0] fft(dp)[1] sum(dm) fft(dm)[0] fft(dm)[1] ]  ]\n+\n+   Then FFTs each column, transposing as it goes.\n+\n+   The resulting matrix is the 3d FFT of the 2x3x4 input matrix.\n+\n+   Note as a sanity check that the first element of the final \n+   stage\'s output (DC term) is \n+   sum( [ sum(ap) sum(bp) sum(cp) sum(dp) ] )\n+   , i.e. the summation of all 24 input elements. \n+\n+*/\n+void kiss_fftnd(kiss_fftnd_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout)\n+{\n+    int i,k;\n+    const kiss_fft_cpx * bufin=fin;\n+    kiss_fft_cpx * bufout;\n+\n+    /*arrange it so the last bufout == fout*/\n+    if ( st->ndims & 1 ) {\n+        bufout = fout;\n+        if (fin==fout) {\n+            memcpy( st->tmpbuf, fin, sizeof(kiss_fft_cpx) * st->dimprod );\n+            bufin = st->tmpbuf;\n+        }\n+    }else\n+        bufout = st->tmpbuf;\n+\n+    for ( k=0; k < st->ndims; ++k) {\n+        int curdim = st->dims[k];\n+        int stride = st->dimprod / curdim;\n+\n+        for ( i=0 ; i<stride ; ++i ) \n+            kiss_fft_stride( st->states[k], bufin+i , bufout+i*curdim, stride );\n+\n+        /*toggle back and forth between the two buffers*/\n+        if (bufout == st->tmpbuf){\n+            bufout = fout;\n+            bufin = st->tmpbuf;\n+        }else{\n+            bufout = st->tmpbuf;\n+            bufin = fout;\n+        }\n+    }\n+}\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftnd.h b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftnd.h\nnew file mode 100644\nindex 00000000..956ba94d\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftnd.h\n@@ -0,0 +1,26 @@\n+/*\n+ *  Copyright (c) 2003-2004, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+#ifndef KISS_FFTND_H\n+#define KISS_FFTND_H\n+\n+#include "kiss_fft.h"\n+\n+#ifdef __cplusplus\n+extern "C" {\n+#endif\n+\n+typedef struct kiss_fftnd_state * kiss_fftnd_cfg;\n+    \n+kiss_fftnd_cfg KISS_FFT_API kiss_fftnd_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem);\n+void KISS_FFT_API kiss_fftnd(kiss_fftnd_cfg  cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);\n+\n+#ifdef __cplusplus\n+}\n+#endif\n+#endif\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftndr.c b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftndr.c\nnew file mode 100644\nindex 00000000..e979d03e\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftndr.c\n@@ -0,0 +1,120 @@\n+/*\n+ *  Copyright (c) 2003-2004, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+#include "kiss_fftndr.h"\n+#include "_kiss_fft_guts.h"\n+#define MAX(x,y) ( ( (x)<(y) )?(y):(x) )\n+\n+struct kiss_fftndr_state\n+{\n+    int dimReal;\n+    int dimOther;\n+    kiss_fftr_cfg cfg_r;\n+    kiss_fftnd_cfg cfg_nd;\n+    void * tmpbuf;\n+};\n+\n+static int prod(const int *dims, int ndims)\n+{\n+    int x=1;\n+    while (ndims--) \n+        x *= *dims++;\n+    return x;\n+}\n+\n+kiss_fftndr_cfg kiss_fftndr_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem)\n+{\n+    KISS_FFT_ALIGN_CHECK(mem)\n+\n+    kiss_fftndr_cfg st = NULL;\n+    size_t nr=0 , nd=0,ntmp=0;\n+    int dimReal = dims[ndims-1];\n+    int dimOther = prod(dims,ndims-1);\n+    size_t memneeded;\n+    char * ptr = NULL;\n+\n+    (void)kiss_fftr_alloc(dimReal,inverse_fft,NULL,&nr);\n+    (void)kiss_fftnd_alloc(dims,ndims-1,inverse_fft,NULL,&nd);\n+    ntmp =\n+        MAX( 2*dimOther , dimReal+2) * sizeof(kiss_fft_scalar)  // freq buffer for one pass\n+        + dimOther*(dimReal+2) * sizeof(kiss_fft_scalar);  // large enough to hold entire input in case of in-place\n+\n+    memneeded = KISS_FFT_ALIGN_SIZE_UP(sizeof( struct kiss_fftndr_state )) + KISS_FFT_ALIGN_SIZE_UP(nr) + KISS_FFT_ALIGN_SIZE_UP(nd) + KISS_FFT_ALIGN_SIZE_UP(ntmp);\n+\n+    if (lenmem==NULL) {\n+        ptr = (char*) malloc(memneeded);\n+    }else{\n+        if (*lenmem >= memneeded)\n+            ptr = (char *)mem;\n+        *lenmem = memneeded; \n+    }\n+    if (ptr==NULL)\n+        return NULL;\n+    \n+    st = (kiss_fftndr_cfg) ptr;\n+    memset( st , 0 , memneeded);\n+    ptr += KISS_FFT_ALIGN_SIZE_UP(sizeof(struct kiss_fftndr_state));\n+    \n+    st->dimReal = dimReal;\n+    st->dimOther = dimOther;\n+    st->cfg_r = kiss_fftr_alloc( dimReal,inverse_fft,ptr,&nr);\n+    ptr += KISS_FFT_ALIGN_SIZE_UP(nr);\n+    st->cfg_nd = kiss_fftnd_alloc(dims,ndims-1,inverse_fft, ptr,&nd);\n+    ptr += KISS_FFT_ALIGN_SIZE_UP(nd);\n+    st->tmpbuf = ptr;\n+\n+    return st;\n+}\n+\n+void kiss_fftndr(kiss_fftndr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata)\n+{\n+    int k1,k2;\n+    int dimReal = st->dimReal;\n+    int dimOther = st->dimOther;\n+    int nrbins = dimReal/2+1;\n+\n+    kiss_fft_cpx * tmp1 = (kiss_fft_cpx*)st->tmpbuf; \n+    kiss_fft_cpx * tmp2 = tmp1 + MAX(nrbins,dimOther);\n+\n+    // timedata is N0 x N1 x ... x Nk real\n+\n+    // take a real chunk of data, fft it and place the output at correct intervals\n+    for (k1=0;k1<dimOther;++k1) {\n+        kiss_fftr( st->cfg_r, timedata + k1*dimReal , tmp1 ); // tmp1 now holds nrbins complex points\n+        for (k2=0;k2<nrbins;++k2)\n+           tmp2[ k2*dimOther+k1 ] = tmp1[k2];\n+    }\n+\n+    for (k2=0;k2<nrbins;++k2) {\n+        kiss_fftnd(st->cfg_nd, tmp2+k2*dimOther, tmp1);  // tmp1 now holds dimOther complex points\n+        for (k1=0;k1<dimOther;++k1) \n+            freqdata[ k1*(nrbins) + k2] = tmp1[k1];\n+    }\n+}\n+\n+void kiss_fftndri(kiss_fftndr_cfg st,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata)\n+{\n+    int k1,k2;\n+    int dimReal = st->dimReal;\n+    int dimOther = st->dimOther;\n+    int nrbins = dimReal/2+1;\n+    kiss_fft_cpx * tmp1 = (kiss_fft_cpx*)st->tmpbuf; \n+    kiss_fft_cpx * tmp2 = tmp1 + MAX(nrbins,dimOther);\n+\n+    for (k2=0;k2<nrbins;++k2) {\n+        for (k1=0;k1<dimOther;++k1) \n+            tmp1[k1] = freqdata[ k1*(nrbins) + k2 ];\n+        kiss_fftnd(st->cfg_nd, tmp1, tmp2+k2*dimOther);\n+    }\n+\n+    for (k1=0;k1<dimOther;++k1) {\n+        for (k2=0;k2<nrbins;++k2)\n+            tmp1[k2] = tmp2[ k2*dimOther+k1 ];\n+        kiss_fftri( st->cfg_r,tmp1,timedata + k1*dimReal);\n+    }\n+}\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftndr.h b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftndr.h\nnew file mode 100644\nindex 00000000..0d56a1fa\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftndr.h\n@@ -0,0 +1,55 @@\n+/*\n+ *  Copyright (c) 2003-2004, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+#ifndef KISS_NDR_H\n+#define KISS_NDR_H\n+\n+#include "kiss_fft.h"\n+#include "kiss_fftr.h"\n+#include "kiss_fftnd.h"\n+\n+#ifdef __cplusplus\n+extern "C" {\n+#endif\n+    \n+typedef struct kiss_fftndr_state *kiss_fftndr_cfg;\n+\n+\n+kiss_fftndr_cfg KISS_FFT_API kiss_fftndr_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem);\n+/*\n+ dims[0] must be even\n+\n+ If you don\'t care to allocate space, use mem = lenmem = NULL \n+*/\n+\n+\n+void KISS_FFT_API kiss_fftndr(\n+        kiss_fftndr_cfg cfg,\n+        const kiss_fft_scalar *timedata,\n+        kiss_fft_cpx *freqdata);\n+/*\n+ input timedata has dims[0] X dims[1] X ... X  dims[ndims-1] scalar points\n+ output freqdata has dims[0] X dims[1] X ... X  dims[ndims-1]/2+1 complex points\n+*/\n+\n+void KISS_FFT_API kiss_fftndri(\n+        kiss_fftndr_cfg cfg,\n+        const kiss_fft_cpx *freqdata,\n+        kiss_fft_scalar *timedata);\n+/*\n+ input and output dimensions are the exact opposite of kiss_fftndr\n+*/\n+\n+\n+#define kiss_fftndr_free free\n+\n+#ifdef __cplusplus\n+}\n+#endif\n+\n+#endif\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftr.c b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftr.c\nnew file mode 100644\nindex 00000000..778a9a62\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftr.c\n@@ -0,0 +1,155 @@\n+/*\n+ *  Copyright (c) 2003-2004, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+#include "kiss_fftr.h"\n+#include "_kiss_fft_guts.h"\n+\n+struct kiss_fftr_state{\n+    kiss_fft_cfg substate;\n+    kiss_fft_cpx * tmpbuf;\n+    kiss_fft_cpx * super_twiddles;\n+#ifdef USE_SIMD\n+    void * pad;\n+#endif\n+};\n+\n+kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem)\n+{\n+\tKISS_FFT_ALIGN_CHECK(mem)\n+\n+    int i;\n+    kiss_fftr_cfg st = NULL;\n+    size_t subsize = 0, memneeded;\n+\n+    if (nfft & 1) {\n+        KISS_FFT_ERROR("Real FFT optimization must be even.");\n+        return NULL;\n+    }\n+    nfft >>= 1;\n+\n+    kiss_fft_alloc (nfft, inverse_fft, NULL, &subsize);\n+    memneeded = sizeof(struct kiss_fftr_state) + subsize + sizeof(kiss_fft_cpx) * ( nfft * 3 / 2);\n+\n+    if (lenmem == NULL) {\n+        st = (kiss_fftr_cfg) KISS_FFT_MALLOC (memneeded);\n+    } else {\n+        if (*lenmem >= memneeded)\n+            st = (kiss_fftr_cfg) mem;\n+        *lenmem = memneeded;\n+    }\n+    if (!st)\n+        return NULL;\n+\n+    st->substate = (kiss_fft_cfg) (st + 1); /*just beyond kiss_fftr_state struct */\n+    st->tmpbuf = (kiss_fft_cpx *) (((char *) st->substate) + subsize);\n+    st->super_twiddles = st->tmpbuf + nfft;\n+    kiss_fft_alloc(nfft, inverse_fft, st->substate, &subsize);\n+\n+    for (i = 0; i < nfft/2; ++i) {\n+        double phase =\n+            -3.14159265358979323846264338327 * ((double) (i+1) / nfft + .5);\n+        if (inverse_fft)\n+            phase *= -1;\n+        kf_cexp (st->super_twiddles+i,phase);\n+    }\n+    return st;\n+}\n+\n+void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata)\n+{\n+    /* input buffer timedata is stored row-wise */\n+    int k,ncfft;\n+    kiss_fft_cpx fpnk,fpk,f1k,f2k,tw,tdc;\n+\n+    if ( st->substate->inverse) {\n+        KISS_FFT_ERROR("kiss fft usage error: improper alloc");\n+        return;/* The caller did not call the correct function */\n+    }\n+\n+    ncfft = st->substate->nfft;\n+\n+    /*perform the parallel fft of two real signals packed in real,imag*/\n+    kiss_fft( st->substate , (const kiss_fft_cpx*)timedata, st->tmpbuf );\n+    /* The real part of the DC element of the frequency spectrum in st->tmpbuf\n+     * contains the sum of the even-numbered elements of the input time sequence\n+     * The imag part is the sum of the odd-numbered elements\n+     *\n+     * The sum of tdc.r and tdc.i is the sum of the input time sequence.\n+     *      yielding DC of input time sequence\n+     * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1...\n+     *      yielding Nyquist bin of input time sequence\n+     */\n+\n+    tdc.r = st->tmpbuf[0].r;\n+    tdc.i = st->tmpbuf[0].i;\n+    C_FIXDIV(tdc,2);\n+    CHECK_OVERFLOW_OP(tdc.r ,+, tdc.i);\n+    CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i);\n+    freqdata[0].r = tdc.r + tdc.i;\n+    freqdata[ncfft].r = tdc.r - tdc.i;\n+#ifdef USE_SIMD\n+    freqdata[ncfft].i = freqdata[0].i = _mm_set1_ps(0);\n+#else\n+    freqdata[ncfft].i = freqdata[0].i = 0;\n+#endif\n+\n+    for ( k=1;k <= ncfft/2 ; ++k ) {\n+        fpk    = st->tmpbuf[k];\n+        fpnk.r =   st->tmpbuf[ncfft-k].r;\n+        fpnk.i = - st->tmpbuf[ncfft-k].i;\n+        C_FIXDIV(fpk,2);\n+        C_FIXDIV(fpnk,2);\n+\n+        C_ADD( f1k, fpk , fpnk );\n+        C_SUB( f2k, fpk , fpnk );\n+        C_MUL( tw , f2k , st->super_twiddles[k-1]);\n+\n+        freqdata[k].r = HALF_OF(f1k.r + tw.r);\n+        freqdata[k].i = HALF_OF(f1k.i + tw.i);\n+        freqdata[ncfft-k].r = HALF_OF(f1k.r - tw.r);\n+        freqdata[ncfft-k].i = HALF_OF(tw.i - f1k.i);\n+    }\n+}\n+\n+void kiss_fftri(kiss_fftr_cfg st,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata)\n+{\n+    /* input buffer timedata is stored row-wise */\n+    int k, ncfft;\n+\n+    if (st->substate->inverse == 0) {\n+        KISS_FFT_ERROR("kiss fft usage error: improper alloc");\n+        return;/* The caller did not call the correct function */\n+    }\n+\n+    ncfft = st->substate->nfft;\n+\n+    st->tmpbuf[0].r = freqdata[0].r + freqdata[ncfft].r;\n+    st->tmpbuf[0].i = freqdata[0].r - freqdata[ncfft].r;\n+    C_FIXDIV(st->tmpbuf[0],2);\n+\n+    for (k = 1; k <= ncfft / 2; ++k) {\n+        kiss_fft_cpx fk, fnkc, fek, fok, tmp;\n+        fk = freqdata[k];\n+        fnkc.r = freqdata[ncfft - k].r;\n+        fnkc.i = -freqdata[ncfft - k].i;\n+        C_FIXDIV( fk , 2 );\n+        C_FIXDIV( fnkc , 2 );\n+\n+        C_ADD (fek, fk, fnkc);\n+        C_SUB (tmp, fk, fnkc);\n+        C_MUL (fok, tmp, st->super_twiddles[k-1]);\n+        C_ADD (st->tmpbuf[k],     fek, fok);\n+        C_SUB (st->tmpbuf[ncfft - k], fek, fok);\n+#ifdef USE_SIMD\n+        st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0);\n+#else\n+        st->tmpbuf[ncfft - k].i *= -1;\n+#endif\n+    }\n+    kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata);\n+}\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftr.h b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftr.h\nnew file mode 100644\nindex 00000000..7fd73d2d\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kiss_fftr.h\n@@ -0,0 +1,54 @@\n+/*\n+ *  Copyright (c) 2003-2004, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+#ifndef KISS_FTR_H\n+#define KISS_FTR_H\n+\n+#include "kiss_fft.h"\n+#ifdef __cplusplus\n+extern "C" {\n+#endif\n+\n+    \n+/* \n+ \n+ Real optimized version can save about 45% cpu time vs. complex fft of a real seq.\n+\n+ \n+ \n+ */\n+\n+typedef struct kiss_fftr_state *kiss_fftr_cfg;\n+\n+\n+kiss_fftr_cfg KISS_FFT_API kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem);\n+/*\n+ nfft must be even\n+\n+ If you don\'t care to allocate space, use mem = lenmem = NULL \n+*/\n+\n+\n+void KISS_FFT_API kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata);\n+/*\n+ input timedata has nfft scalar points\n+ output freqdata has nfft/2+1 complex points\n+*/\n+\n+void KISS_FFT_API kiss_fftri(kiss_fftr_cfg cfg,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata);\n+/*\n+ input freqdata has  nfft/2+1 complex points\n+ output timedata has nfft scalar points\n+*/\n+\n+#define kiss_fftr_free KISS_FFT_FREE\n+\n+#ifdef __cplusplus\n+}\n+#endif\n+#endif\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kissfft.hh b/plotjuggler_plugins/ToolboxFFT/KissFFT/kissfft.hh\nnew file mode 100644\nindex 00000000..8112c11c\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kissfft.hh\n@@ -0,0 +1,361 @@\n+/*\n+ *  Copyright (c) 2003-2010, Mark Borgerding. All rights reserved.\n+ *  This file is part of KISS FFT - https://github.com/mborgerding/kissfft\n+ *\n+ *  SPDX-License-Identifier: BSD-3-Clause\n+ *  See COPYING file for more information.\n+ */\n+\n+#ifndef KISSFFT_CLASS_HH\n+#define KISSFFT_CLASS_HH\n+#include <complex>\n+#include <utility>\n+#include <vector>\n+\n+\n+template <typename scalar_t>\n+class kissfft\n+{\n+    public:\n+\n+        typedef std::complex<scalar_t> cpx_t;\n+\n+        kissfft( const std::size_t nfft,\n+                 const bool inverse )\n+            :_nfft(nfft)\n+            ,_inverse(inverse)\n+        {\n+            // fill twiddle factors\n+            _twiddles.resize(_nfft);\n+            const scalar_t phinc =  (_inverse?2:-2)* std::acos( (scalar_t) -1)  / _nfft;\n+            for (std::size_t i=0;i<_nfft;++i)\n+                _twiddles[i] = std::exp( cpx_t(0,i*phinc) );\n+\n+            //factorize\n+            //start factoring out 4\'s, then 2\'s, then 3,5,7,9,...\n+            std::size_t n= _nfft;\n+            std::size_t p=4;\n+            do {\n+                while (n % p) {\n+                    switch (p) {\n+                        case 4: p = 2; break;\n+                        case 2: p = 3; break;\n+                        default: p += 2; break;\n+                    }\n+                    if (p*p>n)\n+                        p = n;// no more factors\n+                }\n+                n /= p;\n+                _stageRadix.push_back(p);\n+                _stageRemainder.push_back(n);\n+            }while(n>1);\n+        }\n+\n+\n+        /// Changes the FFT-length and/or the transform direction.\n+        ///\n+        /// @post The @c kissfft object will be in the same state as if it\n+        /// had been newly constructed with the passed arguments.\n+        /// However, the implementation may be faster than constructing a\n+        /// new fft object.\n+        void assign( const std::size_t nfft,\n+                     const bool inverse )\n+        {\n+            if ( nfft != _nfft )\n+            {\n+                kissfft tmp( nfft, inverse ); // O(n) time.\n+                std::swap( tmp, *this ); // this is O(1) in C++11, O(n) otherwise.\n+            }\n+            else if ( inverse != _inverse )\n+            {\n+                // conjugate the twiddle factors.\n+                for ( typename std::vector<cpx_t>::iterator it = _twiddles.begin();\n+                      it != _twiddles.end(); ++it )\n+                    it->imag( -it->imag() );\n+            }\n+        }\n+\n+        /// Calculates the complex Discrete Fourier Transform.\n+        ///\n+        /// The size of the passed arrays must be passed in the constructor.\n+        /// The sum of the squares of the absolute values in the @c dst\n+        /// array will be @c N times the sum of the squares of the absolute\n+        /// values in the @c src array, where @c N is the size of the array.\n+        /// In other words, the l_2 norm of the resulting array will be\n+        /// @c sqrt(N) times as big as the l_2 norm of the input array.\n+        /// This is also the case when the inverse flag is set in the\n+        /// constructor. Hence when applying the same transform twice, but with\n+        /// the inverse flag changed the second time, then the result will\n+        /// be equal to the original input times @c N.\n+        void transform(const cpx_t * fft_in, cpx_t * fft_out, const std::size_t stage = 0, const std::size_t fstride = 1, const std::size_t in_stride = 1) const\n+        {\n+            const std::size_t p = _stageRadix[stage];\n+            const std::size_t m = _stageRemainder[stage];\n+            cpx_t * const Fout_beg = fft_out;\n+            cpx_t * const Fout_end = fft_out + p*m;\n+\n+            if (m==1) {\n+                do{\n+                    *fft_out = *fft_in;\n+                    fft_in += fstride*in_stride;\n+                }while(++fft_out != Fout_end );\n+            }else{\n+                do{\n+                    // recursive call:\n+                    // DFT of size m*p performed by doing\n+                    // p instances of smaller DFTs of size m,\n+                    // each one takes a decimated version of the input\n+                    transform(fft_in, fft_out, stage+1, fstride*p,in_stride);\n+                    fft_in += fstride*in_stride;\n+                }while( (fft_out += m) != Fout_end );\n+            }\n+\n+            fft_out=Fout_beg;\n+\n+            // recombine the p smaller DFTs\n+            switch (p) {\n+                case 2: kf_bfly2(fft_out,fstride,m); break;\n+                case 3: kf_bfly3(fft_out,fstride,m); break;\n+                case 4: kf_bfly4(fft_out,fstride,m); break;\n+                case 5: kf_bfly5(fft_out,fstride,m); break;\n+                default: kf_bfly_generic(fft_out,fstride,m,p); break;\n+            }\n+        }\n+\n+        /// Calculates the Discrete Fourier Transform (DFT) of a real input\n+        /// of size @c 2*N.\n+        ///\n+        /// The 0-th and N-th value of the DFT are real numbers. These are\n+        /// stored in @c dst[0].real() and @c dst[0].imag() respectively.\n+        /// The remaining DFT values up to the index N-1 are stored in\n+        /// @c dst[1] to @c dst[N-1].\n+        /// The other half of the DFT values can be calculated from the\n+        /// symmetry relation\n+        ///     @code\n+        ///         DFT(src)[2*N-k] == conj( DFT(src)[k] );\n+        ///     @endcode\n+        /// The same scaling factors as in @c transform() apply.\n+        ///\n+        /// @note For this to work, the types @c scalar_t and @c cpx_t\n+        /// must fulfill the following requirements:\n+        ///\n+        /// For any object @c z of type @c cpx_t,\n+        /// @c reinterpret_cast<scalar_t(&)[2]>(z)[0] is the real part of @c z and\n+        /// @c reinterpret_cast<scalar_t(&)[2]>(z)[1] is the imaginary part of @c z.\n+        /// For any pointer to an element of an array of @c cpx_t named @c p\n+        /// and any valid array index @c i, @c reinterpret_cast<T*>(p)[2*i]\n+        /// is the real part of the complex number @c p[i], and\n+        /// @c reinterpret_cast<T*>(p)[2*i+1] is the imaginary part of the\n+        /// complex number @c p[i].\n+        ///\n+        /// Since C++11, these requirements are guaranteed to be satisfied for\n+        /// @c scalar_ts being @c float, @c double or @c long @c double\n+        /// together with @c cpx_t being @c std::complex<scalar_t>.\n+        void transform_real( const scalar_t * const src,\n+                             cpx_t * const dst ) const\n+        {\n+            const std::size_t N = _nfft;\n+            if ( N == 0 )\n+                return;\n+\n+            // perform complex FFT\n+            transform( reinterpret_cast<const cpx_t*>(src), dst );\n+\n+            // post processing for k = 0 and k = N\n+            dst[0] = cpx_t( dst[0].real() + dst[0].imag(),\n+                               dst[0].real() - dst[0].imag() );\n+\n+            // post processing for all the other k = 1, 2, ..., N-1\n+            const scalar_t pi = std::acos( (scalar_t) -1);\n+            const scalar_t half_phi_inc = ( _inverse ? pi : -pi ) / N;\n+            const cpx_t twiddle_mul = std::exp( cpx_t(0, half_phi_inc) );\n+            for ( std::size_t k = 1; 2*k < N; ++k )\n+            {\n+                const cpx_t w = (scalar_t)0.5 * cpx_t(\n+                     dst[k].real() + dst[N-k].real(),\n+                     dst[k].imag() - dst[N-k].imag() );\n+                const cpx_t z = (scalar_t)0.5 * cpx_t(\n+                     dst[k].imag() + dst[N-k].imag(),\n+                    -dst[k].real() + dst[N-k].real() );\n+                const cpx_t twiddle =\n+                    k % 2 == 0 ?\n+                    _twiddles[k/2] :\n+                    _twiddles[k/2] * twiddle_mul;\n+                dst[  k] =       w + twiddle * z;\n+                dst[N-k] = std::conj( w - twiddle * z );\n+            }\n+            if ( N % 2 == 0 )\n+                dst[N/2] = std::conj( dst[N/2] );\n+        }\n+\n+    private:\n+\n+        void kf_bfly2( cpx_t * Fout, const size_t fstride, const std::size_t m) const\n+        {\n+            for (std::size_t k=0;k<m;++k) {\n+                const cpx_t t = Fout[m+k] * _twiddles[k*fstride];\n+                Fout[m+k] = Fout[k] - t;\n+                Fout[k] += t;\n+            }\n+        }\n+\n+        void kf_bfly3( cpx_t * Fout, const std::size_t fstride, const std::size_t m) const\n+        {\n+            std::size_t k=m;\n+            const std::size_t m2 = 2*m;\n+            const cpx_t *tw1,*tw2;\n+            cpx_t scratch[5];\n+            const cpx_t epi3 = _twiddles[fstride*m];\n+\n+            tw1=tw2=&_twiddles[0];\n+\n+            do{\n+                scratch[1] = Fout[m]  * *tw1;\n+                scratch[2] = Fout[m2] * *tw2;\n+\n+                scratch[3] = scratch[1] + scratch[2];\n+                scratch[0] = scratch[1] - scratch[2];\n+                tw1 += fstride;\n+                tw2 += fstride*2;\n+\n+                Fout[m] = Fout[0] - scratch[3]*scalar_t(0.5);\n+                scratch[0] *= epi3.imag();\n+\n+                Fout[0] += scratch[3];\n+\n+                Fout[m2] = cpx_t(  Fout[m].real() + scratch[0].imag() , Fout[m].imag() - scratch[0].real() );\n+\n+                Fout[m] += cpx_t( -scratch[0].imag(),scratch[0].real() );\n+                ++Fout;\n+            }while(--k);\n+        }\n+\n+        void kf_bfly4( cpx_t * const Fout, const std::size_t fstride, const std::size_t m) const\n+        {\n+            cpx_t scratch[7];\n+            const scalar_t negative_if_inverse = _inverse ? -1 : +1;\n+            for (std::size_t k=0;k<m;++k) {\n+                scratch[0] = Fout[k+  m] * _twiddles[k*fstride  ];\n+                scratch[1] = Fout[k+2*m] * _twiddles[k*fstride*2];\n+                scratch[2] = Fout[k+3*m] * _twiddles[k*fstride*3];\n+                scratch[5] = Fout[k] - scratch[1];\n+\n+                Fout[k] += scratch[1];\n+                scratch[3] = scratch[0] + scratch[2];\n+                scratch[4] = scratch[0] - scratch[2];\n+                scratch[4] = cpx_t( scratch[4].imag()*negative_if_inverse ,\n+                                      -scratch[4].real()*negative_if_inverse );\n+\n+                Fout[k+2*m]  = Fout[k] - scratch[3];\n+                Fout[k    ]+= scratch[3];\n+                Fout[k+  m] = scratch[5] + scratch[4];\n+                Fout[k+3*m] = scratch[5] - scratch[4];\n+            }\n+        }\n+\n+        void kf_bfly5( cpx_t * const Fout, const std::size_t fstride, const std::size_t m) const\n+        {\n+            cpx_t *Fout0,*Fout1,*Fout2,*Fout3,*Fout4;\n+            cpx_t scratch[13];\n+            const cpx_t ya = _twiddles[fstride*m];\n+            const cpx_t yb = _twiddles[fstride*2*m];\n+\n+            Fout0=Fout;\n+            Fout1=Fout0+m;\n+            Fout2=Fout0+2*m;\n+            Fout3=Fout0+3*m;\n+            Fout4=Fout0+4*m;\n+\n+            for ( std::size_t u=0; u<m; ++u ) {\n+                scratch[0] = *Fout0;\n+\n+                scratch[1] = *Fout1 * _twiddles[  u*fstride];\n+                scratch[2] = *Fout2 * _twiddles[2*u*fstride];\n+                scratch[3] = *Fout3 * _twiddles[3*u*fstride];\n+                scratch[4] = *Fout4 * _twiddles[4*u*fstride];\n+\n+                scratch[7] = scratch[1] + scratch[4];\n+                scratch[10]= scratch[1] - scratch[4];\n+                scratch[8] = scratch[2] + scratch[3];\n+                scratch[9] = scratch[2] - scratch[3];\n+\n+                *Fout0 += scratch[7];\n+                *Fout0 += scratch[8];\n+\n+                scratch[5] = scratch[0] + cpx_t(\n+                        scratch[7].real()*ya.real() + scratch[8].real()*yb.real(),\n+                        scratch[7].imag()*ya.real() + scratch[8].imag()*yb.real()\n+                        );\n+\n+                scratch[6] =  cpx_t(\n+                         scratch[10].imag()*ya.imag() + scratch[9].imag()*yb.imag(),\n+                        -scratch[10].real()*ya.imag() - scratch[9].real()*yb.imag()\n+                        );\n+\n+                *Fout1 = scratch[5] - scratch[6];\n+                *Fout4 = scratch[5] + scratch[6];\n+\n+                scratch[11] = scratch[0] +\n+                    cpx_t(\n+                            scratch[7].real()*yb.real() + scratch[8].real()*ya.real(),\n+                            scratch[7].imag()*yb.real() + scratch[8].imag()*ya.real()\n+                            );\n+\n+                scratch[12] = cpx_t(\n+                        -scratch[10].imag()*yb.imag() + scratch[9].imag()*ya.imag(),\n+                         scratch[10].real()*yb.imag() - scratch[9].real()*ya.imag()\n+                        );\n+\n+                *Fout2 = scratch[11] + scratch[12];\n+                *Fout3 = scratch[11] - scratch[12];\n+\n+                ++Fout0;\n+                ++Fout1;\n+                ++Fout2;\n+                ++Fout3;\n+                ++Fout4;\n+            }\n+        }\n+\n+        /* perform the butterfly for one stage of a mixed radix FFT */\n+        void kf_bfly_generic(\n+                cpx_t * const Fout,\n+                const size_t fstride,\n+                const std::size_t m,\n+                const std::size_t p\n+                ) const\n+        {\n+            const cpx_t * twiddles = &_twiddles[0];\n+\n+            if(p > _scratchbuf.size()) _scratchbuf.resize(p);\n+\n+            for ( std::size_t u=0; u<m; ++u ) {\n+                std::size_t k = u;\n+                for ( std::size_t q1=0 ; q1<p ; ++q1 ) {\n+                    _scratchbuf[q1] = Fout[ k  ];\n+                    k += m;\n+                }\n+\n+                k=u;\n+                for ( std::size_t q1=0 ; q1<p ; ++q1 ) {\n+                    std::size_t twidx=0;\n+                    Fout[ k ] = _scratchbuf[0];\n+                    for ( std::size_t q=1;q<p;++q ) {\n+                        twidx += fstride * k;\n+                        if (twidx>=_nfft)\n+                          twidx-=_nfft;\n+                        Fout[ k ] += _scratchbuf[q] * twiddles[twidx];\n+                    }\n+                    k += m;\n+                }\n+            }\n+        }\n+\n+        std::size_t _nfft;\n+        bool _inverse;\n+        std::vector<cpx_t> _twiddles;\n+        std::vector<std::size_t> _stageRadix;\n+        std::vector<std::size_t> _stageRemainder;\n+        mutable std::vector<cpx_t> _scratchbuf;\n+};\n+#endif\ndiff --git a/plotjuggler_plugins/ToolboxFFT/KissFFT/kissfft_i32.hh b/plotjuggler_plugins/ToolboxFFT/KissFFT/kissfft_i32.hh\nnew file mode 100644\nindex 00000000..5871e008\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/KissFFT/kissfft_i32.hh\n@@ -0,0 +1,304 @@\n+#ifndef KISSFFT_I32_CLASS_HH\n+#define KISSFFT_I32_CLASS_HH\n+\n+#include <complex>\n+#include <utility>\n+#include <vector>\n+\n+// TODO1: substitute complex<type> (behaviour not defined for nonfloats), should be faster\n+// TODO2: use std:: namespace\n+// TODO3: make unittests for all ffts (c, cpp, i32)\n+\n+template <typename DType>\n+struct complex_s\n+{\n+    DType real;\n+    DType imag;\n+};\n+\n+class kissfft_i32\n+{\n+private:\n+\n+    using scalar_type = int32_t;\n+    using cpx_type    = complex<int32_t>;\n+\n+    scalar_type _scale_factor;\n+    std::size_t _nfft;\n+    bool _inverse;\n+    std::vector<cpx_type> _twiddles;\n+    std::vector<std::size_t> _stageRadix;\n+    std::vector<std::size_t> _stageRemainder;\n+\n+public:\n+\n+    // scale_factor: upscale twiddle-factors otherwise they lie between 0..1 (out of range for integer) --> fixed point math\n+    kissfft_i32(const std::size_t nfft, const bool inverse, const double scale_factor = 1024.0)\n+            : _scale_factor(scalar_type(scale_factor)), _nfft(nfft), _inverse(inverse)\n+    {\n+        // fill twiddle factors\n+        _twiddles.resize(_nfft);\n+        const double phinc = (_inverse ? 2 : -2) * acos(-1.0) / _nfft;\n+        for (std::size_t i = 0; i < _nfft; ++i)\n+        {\n+            _twiddles[i] = scale_factor * exp(complex<double>(0, i * phinc));\n+        }\n+        //factorize\n+        //start factoring out 4\'s, then 2\'s, then 3,5,7,9,...\n+        std::size_t n = _nfft;\n+        std::size_t p = 4;\n+        do\n+        {\n+            while (n % p)\n+            {\n+                switch (p)\n+                {\n+                    case 4:\n+                        p = 2;\n+                        break;\n+                    case 2:\n+                        p = 3;\n+                        break;\n+                    default:\n+                        p += 2;\n+                        break;\n+                }\n+                if (p * p > n)  p = n;// no more factors\n+            }\n+            n /= p;\n+            _stageRadix.push_back(p);\n+            _stageRemainder.push_back(n);\n+        } while (n > 1);\n+    }\n+\n+    /// Calculates the complex Discrete Fourier Transform.\n+    ///\n+    /// The size of the passed arrays must be passed in the constructor.\n+    /// The sum of the squares of the absolute values in the @c dst\n+    /// array will be @c N times the sum of the squares of the absolute\n+    /// values in the @c src array, where @c N is the size of the array.\n+    /// In other words, the l_2 norm of the resulting array will be\n+    /// @c sqrt(N) times as big as the l_2 norm of the input array.\n+    /// This is also the case when the inverse flag is set in the\n+    /// constructor. Hence when applying the same transform twice, but with\n+    /// the inverse flag changed the second time, then the result will\n+    /// be equal to the original input times @c N.\n+    void transform(const cpx_type * FSrc,\n+                   cpx_type * FDst,\n+                   const std::size_t stage = 0,\n+                   const std::size_t fstride = 1,\n+                   const std::size_t in_stride = 1) const\n+    {\n+        const std::size_t p = _stageRadix[stage];\n+        const std::size_t m = _stageRemainder[stage];\n+        cpx_type *const Fout_beg = FDst;\n+        cpx_type *const Fout_end = FDst + p * m;\n+\n+        if (m == 1)\n+        {\n+            do\n+            {\n+                *FDst = *FSrc;\n+                FSrc += fstride * in_stride;\n+            } while (++FDst != Fout_end);\n+        }\n+        else\n+        {\n+            do\n+            {\n+                // recursive call:\n+                // DFT of size m*p performed by doing\n+                // p instances of smaller DFTs of size m,\n+                // each one takes a decimated version of the input\n+                transform(FSrc, FDst, stage + 1, fstride * p, in_stride);\n+                FSrc += fstride * in_stride;\n+            } while ((FDst += m) != Fout_end);\n+        }\n+\n+        FDst = Fout_beg;\n+\n+        // recombine the p smaller DFTs\n+        switch (p)\n+        {\n+            case 2:\n+                kf_bfly2(FDst, fstride, m);\n+                break;\n+            case 3:\n+                kf_bfly3(FDst, fstride, m);\n+                break;\n+            case 4:\n+                kf_bfly4(FDst, fstride, m);\n+                break;\n+            case 5:\n+                kf_bfly5(FDst, fstride, m);\n+                break;\n+            default:\n+                kf_bfly_generic(FDst, fstride, m, p);\n+                break;\n+        }\n+    }\n+\n+private:\n+\n+    void kf_bfly2(cpx_type *const Fout, const size_t fstride, const std::size_t m) const\n+    {\n+        for (std::size_t k = 0; k < m; ++k)\n+        {\n+            const cpx_type t = (Fout[m + k] * _twiddles[k * fstride]) / _scale_factor;\n+            Fout[m + k] = Fout[k] - t;\n+            Fout[k] += t;\n+        }\n+    }\n+\n+    void kf_bfly3(cpx_type *Fout, const std::size_t fstride, const std::size_t m) const\n+    {\n+        std::size_t k = m;\n+        const std::size_t m2 = 2 * m;\n+        const cpx_type *tw1, *tw2;\n+        cpx_type scratch[5];\n+        const cpx_type epi3 = _twiddles[fstride * m];\n+\n+        tw1 = tw2 = &_twiddles[0];\n+\n+        do\n+        {\n+            scratch[1] = (Fout[m] * *tw1) / _scale_factor;\n+            scratch[2] = (Fout[m2] * *tw2) / _scale_factor;\n+\n+            scratch[3] = scratch[1] + scratch[2];\n+            scratch[0] = scratch[1] - scratch[2];\n+            tw1 += fstride;\n+            tw2 += fstride * 2;\n+\n+            Fout[m] = Fout[0] - (scratch[3] / 2);\n+            scratch[0] *= epi3.imag();\n+            scratch[0] /= _scale_factor;\n+\n+            Fout[0] += scratch[3];\n+\n+            Fout[m2] = cpx_type(Fout[m].real() + scratch[0].imag(), Fout[m].imag() - scratch[0].real());\n+\n+            Fout[m] += cpx_type(-scratch[0].imag(), scratch[0].real());\n+            ++Fout;\n+        } while (--k);\n+    }\n+\n+    void kf_bfly4(cpx_type *const Fout, const std::size_t fstride, const std::size_t m) const\n+    {\n+        cpx_type scratch[7];\n+        const scalar_type negative_if_inverse = _inverse ? -1 : +1;\n+\n+        for (std::size_t k = 0; k < m; ++k)\n+        {\n+            scratch[0] = (Fout[k + m] * _twiddles[k * fstride]) / _scale_factor;\n+            scratch[1] = (Fout[k + 2 * m] * _twiddles[k * fstride * 2]) / _scale_factor;\n+            scratch[2] = (Fout[k + 3 * m] * _twiddles[k * fstride * 3]) / _scale_factor;\n+            scratch[5] = Fout[k] - scratch[1];\n+\n+            Fout[k] += scratch[1];\n+            scratch[3] = scratch[0] + scratch[2];\n+            scratch[4] = scratch[0] - scratch[2];\n+            scratch[4] = cpx_type(scratch[4].imag() * negative_if_inverse,\n+                                  -scratch[4].real() * negative_if_inverse);\n+\n+            Fout[k + 2 * m] = Fout[k] - scratch[3];\n+            Fout[k] += scratch[3];\n+            Fout[k + m] = scratch[5] + scratch[4];\n+            Fout[k + 3 * m] = scratch[5] - scratch[4];\n+        }\n+    }\n+\n+    void kf_bfly5(cpx_type *const Fout, const std::size_t fstride, const std::size_t m) const\n+    {\n+        cpx_type *Fout0, *Fout1, *Fout2, *Fout3, *Fout4;\n+        cpx_type scratch[13];\n+        const cpx_type ya = _twiddles[fstride * m];\n+        const cpx_type yb = _twiddles[fstride * 2 * m];\n+\n+        Fout0 = Fout;\n+        Fout1 = Fout0 + m;\n+        Fout2 = Fout0 + 2 * m;\n+        Fout3 = Fout0 + 3 * m;\n+        Fout4 = Fout0 + 4 * m;\n+\n+        for (std::size_t u = 0; u < m; ++u)\n+        {\n+            scratch[0] = *Fout0;\n+\n+            scratch[1] = (*Fout1 * _twiddles[u * fstride]) / _scale_factor;\n+            scratch[2] = (*Fout2 * _twiddles[2 * u * fstride]) / _scale_factor;\n+            scratch[3] = (*Fout3 * _twiddles[3 * u * fstride]) / _scale_factor;\n+            scratch[4] = (*Fout4 * _twiddles[4 * u * fstride]) / _scale_factor;\n+\n+            scratch[7] = scratch[1] + scratch[4];\n+            scratch[10] = scratch[1] - scratch[4];\n+            scratch[8] = scratch[2] + scratch[3];\n+            scratch[9] = scratch[2] - scratch[3];\n+\n+            *Fout0 += scratch[7];\n+            *Fout0 += scratch[8];\n+\n+            scratch[5] = scratch[0] + (cpx_type(\n+                    scratch[7].real() * ya.real() + scratch[8].real() * yb.real(),\n+                    scratch[7].imag() * ya.real() + scratch[8].imag() * yb.real() ) / _scale_factor);\n+\n+            scratch[6] = cpx_type(\n+                    scratch[10].imag() * ya.imag() + scratch[9].imag() * yb.imag(),\n+                    -scratch[10].real() * ya.imag() - scratch[9].real() * yb.imag() ) / _scale_factor;\n+\n+            *Fout1 = scratch[5] - scratch[6];\n+            *Fout4 = scratch[5] + scratch[6];\n+\n+            scratch[11] = scratch[0] + (cpx_type(\n+                    scratch[7].real() * yb.real() + scratch[8].real() * ya.real(),\n+                    scratch[7].imag() * yb.real() + scratch[8].imag() * ya.real() ) / _scale_factor);\n+\n+            scratch[12] = cpx_type(\n+                    -scratch[10].imag() * yb.imag() + scratch[9].imag() * ya.imag(),\n+                    scratch[10].real() * yb.imag() - scratch[9].real() * ya.imag() ) / _scale_factor;\n+\n+            *Fout2 = scratch[11] + scratch[12];\n+            *Fout3 = scratch[11] - scratch[12];\n+\n+            ++Fout0;\n+            ++Fout1;\n+            ++Fout2;\n+            ++Fout3;\n+            ++Fout4;\n+        }\n+    }\n+\n+    /* perform the butterfly for one stage of a mixed radix FFT */\n+    void kf_bfly_generic(cpx_type * const Fout, const size_t fstride, const std::size_t m, const std::size_t p) const\n+    {\n+        const cpx_type *twiddles = &_twiddles[0];\n+        cpx_type scratchbuf[p];\n+\n+        for (std::size_t u = 0; u < m; ++u)\n+        {\n+            std::size_t k = u;\n+            for (std::size_t q1 = 0; q1 < p; ++q1)\n+            {\n+                scratchbuf[q1] = Fout[k];\n+                k += m;\n+            }\n+\n+            k = u;\n+            for (std::size_t q1 = 0; q1 < p; ++q1)\n+            {\n+                std::size_t twidx = 0;\n+                Fout[k] = scratchbuf[0];\n+                for (std::size_t q = 1; q < p; ++q)\n+                {\n+                    twidx += fstride * k;\n+                    if (twidx >= _nfft)\n+                        twidx -= _nfft;\n+                    Fout[k] += (scratchbuf[q] * twiddles[twidx]) / _scale_factor;\n+                }\n+                k += m;\n+            }\n+        }\n+    }\n+};\n+\n+#endif\ndiff --git a/plotjuggler_plugins/ToolboxFFT/toolbox_FFT.cpp b/plotjuggler_plugins/ToolboxFFT/toolbox_FFT.cpp\nnew file mode 100644\nindex 00000000..8af0c25b\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/toolbox_FFT.cpp\n@@ -0,0 +1,175 @@\n+#include "toolbox_FFT.h"\n+#include "ui_toolbox_FFT.h"\n+\n+#include <QDialogButtonBox>\n+#include <QEvent>\n+#include <QMimeData>\n+#include <QDragEnterEvent>\n+\n+#include "PlotJuggler/transform_function.h"\n+#include "KissFFT/kiss_fftr.h"\n+\n+ToolboxFFT::ToolboxFFT()\n+{\n+  _widget = new QWidget(nullptr);\n+  ui = new Ui::toolbox_fft;\n+\n+  ui->setupUi(_widget);\n+\n+  ui->lineEdit->installEventFilter(this);\n+\n+  connect( ui->buttonBox, &QDialogButtonBox::rejected,\n+           this, &ToolboxPlugin::closed );\n+}\n+\n+ToolboxFFT::~ToolboxFFT()\n+{\n+\n+}\n+\n+void ToolboxFFT::init(PJ::PlotDataMapRef &src_data,\n+                      PJ::TransformsMap &transform_map)\n+{\n+  _plot_data = &src_data;\n+  _transforms = &transform_map;\n+\n+  _plot_widget_A = new PJ::PlotWidgetBase(ui->frameA);\n+  _plot_widget_B = new PJ::PlotWidgetBase(ui->frameB);\n+\n+  auto preview_layout_A = new QHBoxLayout( ui->frameA );\n+  preview_layout_A->setMargin(6);\n+  preview_layout_A->addWidget( _plot_widget_A->widget() );\n+\n+  auto preview_layout_B = new QHBoxLayout( ui->frameB );\n+  preview_layout_B->setMargin(6);\n+  preview_layout_B->addWidget( _plot_widget_B->widget() );\n+}\n+\n+std::pair<QWidget*, PJ::ToolboxPlugin::WidgetType>\n+ToolboxFFT::providedWidget() const\n+{\n+  return { _widget, PJ::ToolboxPlugin::FIXED };\n+}\n+\n+bool ToolboxFFT::onShowWidget()\n+{\n+  return true;\n+}\n+\n+void ToolboxFFT::addCurve(std::string curve_id)\n+{\n+  PlotData& curve_data = _plot_data->getOrCreateNumeric(curve_id);\n+  const int N = curve_data.size();\n+  if( N < 2 ) {\n+    return;\n+  }\n+  _plot_widget_A->removeAllCurves();\n+  _plot_widget_A->addCurve( curve_id, curve_data );\n+  _plot_widget_A->resetZoom();\n+\n+  float dT = float(curve_data.back().x - curve_data.front().x ) /\n+             float(N-1);\n+\n+  std::vector<kiss_fft_scalar> input;\n+  input.reserve( curve_data.size() );\n+\n+  double std_dev = 0.0;\n+\n+  for(size_t i=0; i<N; i++ ) {\n+    const auto& p = curve_data[i];\n+    input.push_back( static_cast<kiss_fft_scalar>(p.y) );\n+\n+    if( i != 0) {\n+      double dt = ( p.x - curve_data[i-1].x );\n+      std_dev += dt*dt;\n+    }\n+  }\n+  std_dev = sqrt(std_dev) / double(N-1);\n+\n+  ui->spinBoxPeriod->setValue( dT );\n+  ui->spinBoxStdDev->setValue( std_dev );\n+\n+  std::vector<kiss_fft_cpx> out( N/2+1 );\n+\n+  auto config = kiss_fftr_alloc(N, false, nullptr, nullptr);\n+  kiss_fftr( config, input.data(), out.data() );\n+\n+\n+  auto& curver_fft = _local_data.getOrCreateNumeric( curve_id + "/fft_freq" );\n+\n+  for ( int i=0; i<N/2; i++)\n+  {\n+    kiss_fft_scalar Hz = i * ( 1.0 / dT) / double(N);\n+    kiss_fft_scalar amplitude = std::hypot( out[i].r, out[i].i ) / N ;\n+    curver_fft.pushBack( {Hz, amplitude} );\n+  }\n+\n+  _plot_widget_B->removeAllCurves();\n+  _plot_widget_B->addCurve( curve_id + "/fft_freq", curver_fft );\n+  _plot_widget_B->resetZoom();\n+}\n+\n+bool ToolboxFFT::eventFilter(QObject *obj, QEvent *ev)\n+{\n+  if( ev->type() == QEvent::DragEnter )\n+  {\n+    auto event = static_cast<QDragEnterEvent*>(ev);\n+    const QMimeData* mimeData = event->mimeData();\n+    QStringList mimeFormats = mimeData->formats();\n+\n+    for(const QString& format : mimeFormats)\n+    {\n+      QByteArray encoded = mimeData->data(format);\n+      QDataStream stream(&encoded, QIODevice::ReadOnly);\n+\n+      if (format != "curveslist/add_curve") {\n+        return false;\n+      }\n+\n+      QStringList curves;\n+      while (!stream.atEnd())\n+      {\n+        QString curve_name;\n+        stream >> curve_name;\n+        if (!curve_name.isEmpty())\n+        {\n+          curves.push_back(curve_name);\n+        }\n+      }\n+      if( curves.size() != 1 )\n+      {\n+        return false;\n+      }\n+\n+      _dragging_curve = curves.front();\n+\n+      if( obj == ui->lineEdit )\n+      {\n+        event->acceptProposedAction();\n+        return true;\n+      }\n+    }\n+  }\n+  else if ( ev->type() == QEvent::Drop ) {\n+    auto lineEdit = qobject_cast<QLineEdit*>( obj );\n+\n+    if ( !lineEdit )\n+    {\n+      return false;\n+    }\n+    lineEdit->setText( _dragging_curve );\n+    std::string curve_id = _dragging_curve.toStdString();\n+    addCurve(curve_id);\n+  }\n+\n+  return false;\n+}\n+\n+\n+void ToolboxFFT::onParametersChanged()\n+{\n+    if( ui->lineEdit->text().isEmpty() )\n+    {\n+      return;\n+    }\n+}\ndiff --git a/plotjuggler_plugins/ToolboxFFT/toolbox_FFT.h b/plotjuggler_plugins/ToolboxFFT/toolbox_FFT.h\nnew file mode 100644\nindex 00000000..e350ac5a\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/toolbox_FFT.h\n@@ -0,0 +1,60 @@\n+#pragma once\n+\n+#include <QtPlugin>\n+#include <thread>\n+#include "PlotJuggler/toolbox_base.h"\n+#include "PlotJuggler/plotwidget_base.h"\n+\n+namespace Ui {\n+class toolbox_fft;\n+}\n+\n+class ToolboxFFT : public PJ::ToolboxPlugin\n+{\n+  Q_OBJECT\n+  Q_PLUGIN_METADATA(IID "facontidavide.PlotJuggler3.Toolbox")\n+  Q_INTERFACES(PJ::ToolboxPlugin)\n+\n+public:\n+  ToolboxFFT();\n+\n+  ~ToolboxFFT() override;\n+\n+  const char* name() const override\n+  {\n+    return "Fast Fourier Transform";\n+  }\n+\n+  void init( PJ::PlotDataMapRef& src_data,\n+            PJ::TransformsMap& transform_map) override;\n+\n+  std::pair<QWidget*, WidgetType> providedWidget() const override;\n+\n+  void addCurve(std::string curve_id);\n+  \n+public slots:\n+\n+  bool onShowWidget() override;\n+\n+private slots:\n+\n+  void onParametersChanged();\n+\n+private:\n+  QWidget* _widget;\n+  Ui::toolbox_fft *ui;\n+\n+  bool eventFilter(QObject *obj, QEvent *event) override;\n+\n+  QString _dragging_curve;\n+\n+  PJ::PlotWidgetBase* _plot_widget_A = nullptr;\n+  PJ::PlotWidgetBase* _plot_widget_B = nullptr;\n+\n+  PJ::PlotDataMapRef* _plot_data = nullptr;\n+  PJ::TransformsMap* _transforms = nullptr;\n+\n+  PJ::PlotDataMapRef _local_data;\n+\n+};\n+\ndiff --git a/plotjuggler_plugins/ToolboxFFT/toolbox_FFT.ui b/plotjuggler_plugins/ToolboxFFT/toolbox_FFT.ui\nnew file mode 100644\nindex 00000000..4ec5b135\n--- /dev/null\n+++ b/plotjuggler_plugins/ToolboxFFT/toolbox_FFT.ui\n@@ -0,0 +1,219 @@\n+<?xml version="1.0" encoding="UTF-8"?>\n+<ui version="4.0">\n+ <class>toolbox_fft</class>\n+ <widget class="QWidget" name="toolbox_fft">\n+  <property name="geometry">\n+   <rect>\n+    <x>0</x>\n+    <y>0</y>\n+    <width>1079</width>\n+    <height>756</height>\n+   </rect>\n+  </property>\n+  <property name="windowTitle">\n+   <string>Form</string>\n+  </property>\n+  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,0,1,0,1,0,0">\n+   <item>\n+    <widget class="QLabel" name="label">\n+     <property name="text">\n+      <string>Drag and Drop a timeseries here (from the left panel):</string>\n+     </property>\n+    </widget>\n+   </item>\n+   <item>\n+    <layout class="QHBoxLayout" name="horizontalLayout_2">\n+     <property name="topMargin">\n+      <number>6</number>\n+     </property>\n+     <property name="bottomMargin">\n+      <number>6</number>\n+     </property>\n+     <item>\n+      <widget class="QLineEdit" name="lineEdit"/>\n+     </item>\n+     <item>\n+      <widget class="QLabel" name="label_4">\n+       <property name="text">\n+        <string>Sampling period:</string>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <widget class="QDoubleSpinBox" name="spinBoxPeriod">\n+       <property name="readOnly">\n+        <bool>true</bool>\n+       </property>\n+       <property name="buttonSymbols">\n+        <enum>QAbstractSpinBox::NoButtons</enum>\n+       </property>\n+       <property name="decimals">\n+        <number>4</number>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <widget class="QLabel" name="label_5">\n+       <property name="text">\n+        <string>Standard deviation:</string>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <widget class="QDoubleSpinBox" name="spinBoxStdDev">\n+       <property name="readOnly">\n+        <bool>true</bool>\n+       </property>\n+       <property name="buttonSymbols">\n+        <enum>QAbstractSpinBox::NoButtons</enum>\n+       </property>\n+       <property name="decimals">\n+        <number>4</number>\n+       </property>\n+      </widget>\n+     </item>\n+    </layout>\n+   </item>\n+   <item>\n+    <widget class="Line" name="line">\n+     <property name="frameShadow">\n+      <enum>QFrame::Plain</enum>\n+     </property>\n+     <property name="orientation">\n+      <enum>Qt::Horizontal</enum>\n+     </property>\n+    </widget>\n+   </item>\n+   <item>\n+    <widget class="QLabel" name="label_2">\n+     <property name="text">\n+      <string>Original Curve</string>\n+     </property>\n+    </widget>\n+   </item>\n+   <item>\n+    <widget class="QFrame" name="frameA">\n+     <property name="frameShape">\n+      <enum>QFrame::Box</enum>\n+     </property>\n+     <property name="frameShadow">\n+      <enum>QFrame::Plain</enum>\n+     </property>\n+    </widget>\n+   </item>\n+   <item>\n+    <layout class="QHBoxLayout" name="horizontalLayout_3">\n+     <property name="topMargin">\n+      <number>10</number>\n+     </property>\n+     <item>\n+      <widget class="QLabel" name="label_3">\n+       <property name="text">\n+        <string>FFT: Frequencies</string>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <spacer name="horizontalSpacer_3">\n+       <property name="orientation">\n+        <enum>Qt::Horizontal</enum>\n+       </property>\n+       <property name="sizeHint" stdset="0">\n+        <size>\n+         <width>40</width>\n+         <height>20</height>\n+        </size>\n+       </property>\n+      </spacer>\n+     </item>\n+     <item>\n+      <widget class="QRadioButton" name="radioAll">\n+       <property name="text">\n+        <string>All Data</string>\n+       </property>\n+       <property name="checked">\n+        <bool>true</bool>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <widget class="QRadioButton" name="radioZoomed">\n+       <property name="text">\n+        <string>Zoom Area</string>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <widget class="QCheckBox" name="checkBox">\n+       <property name="enabled">\n+        <bool>false</bool>\n+       </property>\n+       <property name="text">\n+        <string>Resample</string>\n+       </property>\n+      </widget>\n+     </item>\n+    </layout>\n+   </item>\n+   <item>\n+    <widget class="QFrame" name="frameB">\n+     <property name="frameShape">\n+      <enum>QFrame::Box</enum>\n+     </property>\n+     <property name="frameShadow">\n+      <enum>QFrame::Plain</enum>\n+     </property>\n+    </widget>\n+   </item>\n+   <item>\n+    <layout class="QHBoxLayout" name="horizontalLayout">\n+     <property name="bottomMargin">\n+      <number>11</number>\n+     </property>\n+     <item>\n+      <spacer name="horizontalSpacer">\n+       <property name="orientation">\n+        <enum>Qt::Horizontal</enum>\n+       </property>\n+       <property name="sizeHint" stdset="0">\n+        <size>\n+         <width>40</width>\n+         <height>20</height>\n+        </size>\n+       </property>\n+      </spacer>\n+     </item>\n+     <item>\n+      <widget class="QPushButton" name="pushButton">\n+       <property name="text">\n+        <string>Save Fequency Curve</string>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <spacer name="horizontalSpacer_2">\n+       <property name="orientation">\n+        <enum>Qt::Horizontal</enum>\n+       </property>\n+       <property name="sizeHint" stdset="0">\n+        <size>\n+         <width>40</width>\n+         <height>20</height>\n+        </size>\n+       </property>\n+      </spacer>\n+     </item>\n+    </layout>\n+   </item>\n+   <item>\n+    <widget class="QDialogButtonBox" name="buttonBox">\n+     <property name="standardButtons">\n+      <set>QDialogButtonBox::Close</set>\n+     </property>\n+    </widget>\n+   </item>\n+  </layout>\n+ </widget>\n+ <resources/>\n+ <connections/>\n+</ui>\n', 'commit_subject': 'big work with FFT and PlotWidgetBase refactoring', 'repo_name': 'facontidavide/PlotJuggler', 'committer': {'name': 'Davide Faconti', 'email': 'b1b8e267afd05b72de68d2304114f1bf2e89b661@gmail.com', 'time_sec': '1628244380', 'tz_offset': '120', 'date': {'seconds': '1628244380'}}, 'commit_tree': '07e35e11408f15af7a31b8d80f8a70dd26612613', 'commit_message': 'big work with FFT and PlotWidgetBase refactoring\n', 'language_name': 'C', 'license': 'mpl-2.0', 'hunks': '@@ -0,0 +1,219 @@\n+<?xml version="1.0" encoding="UTF-8"?>\n+<ui version="4.0">\n+ <class>toolbox_fft</class>\n+ <widget class="QWidget" name="toolbox_fft">\n+  <property name="geometry">\n+   <rect>\n+    <x>0</x>\n+    <y>0</y>\n+    <width>1079</width>\n+    <height>756</height>\n+   </rect>\n+  </property>\n+  <property name="windowTitle">\n+   <string>Form</string>\n+  </property>\n+  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,0,1,0,1,0,0">\n+   <item>\n+    <widget class="QLabel" name="label">\n+     <property name="text">\n+      <string>Drag and Drop a timeseries here (from the left panel):</string>\n+     </property>\n+    </widget>\n+   </item>\n+   <item>\n+    <layout class="QHBoxLayout" name="horizontalLayout_2">\n+     <property name="topMargin">\n+      <number>6</number>\n+     </property>\n+     <property name="bottomMargin">\n+      <number>6</number>\n+     </property>\n+     <item>\n+      <widget class="QLineEdit" name="lineEdit"/>\n+     </item>\n+     <item>\n+      <widget class="QLabel" name="label_4">\n+       <property name="text">\n+        <string>Sampling period:</string>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <widget class="QDoubleSpinBox" name="spinBoxPeriod">\n+       <property name="readOnly">\n+        <bool>true</bool>\n+       </property>\n+       <property name="buttonSymbols">\n+        <enum>QAbstractSpinBox::NoButtons</enum>\n+       </property>\n+       <property name="decimals">\n+        <number>4</number>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <widget class="QLabel" name="label_5">\n+       <property name="text">\n+        <string>Standard deviation:</string>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <widget class="QDoubleSpinBox" name="spinBoxStdDev">\n+       <property name="readOnly">\n+        <bool>true</bool>\n+       </property>\n+       <property name="buttonSymbols">\n+        <enum>QAbstractSpinBox::NoButtons</enum>\n+       </property>\n+       <property name="decimals">\n+        <number>4</number>\n+       </property>\n+      </widget>\n+     </item>\n+    </layout>\n+   </item>\n+   <item>\n+    <widget class="Line" name="line">\n+     <property name="frameShadow">\n+      <enum>QFrame::Plain</enum>\n+     </property>\n+     <property name="orientation">\n+      <enum>Qt::Horizontal</enum>\n+     </property>\n+    </widget>\n+   </item>\n+   <item>\n+    <widget class="QLabel" name="label_2">\n+     <property name="text">\n+      <string>Original Curve</string>\n+     </property>\n+    </widget>\n+   </item>\n+   <item>\n+    <widget class="QFrame" name="frameA">\n+     <property name="frameShape">\n+      <enum>QFrame::Box</enum>\n+     </property>\n+     <property name="frameShadow">\n+      <enum>QFrame::Plain</enum>\n+     </property>\n+    </widget>\n+   </item>\n+   <item>\n+    <layout class="QHBoxLayout" name="horizontalLayout_3">\n+     <property name="topMargin">\n+      <number>10</number>\n+     </property>\n+     <item>\n+      <widget class="QLabel" name="label_3">\n+       <property name="text">\n+        <string>FFT: Frequencies</string>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <spacer name="horizontalSpacer_3">\n+       <property name="orientation">\n+        <enum>Qt::Horizontal</enum>\n+       </property>\n+       <property name="sizeHint" stdset="0">\n+        <size>\n+         <width>40</width>\n+         <height>20</height>\n+        </size>\n+       </property>\n+      </spacer>\n+     </item>\n+     <item>\n+      <widget class="QRadioButton" name="radioAll">\n+       <property name="text">\n+        <string>All Data</string>\n+       </property>\n+       <property name="checked">\n+        <bool>true</bool>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <widget class="QRadioButton" name="radioZoomed">\n+       <property name="text">\n+        <string>Zoom Area</string>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <widget class="QCheckBox" name="checkBox">\n+       <property name="enabled">\n+        <bool>false</bool>\n+       </property>\n+       <property name="text">\n+        <string>Resample</string>\n+       </property>\n+      </widget>\n+     </item>\n+    </layout>\n+   </item>\n+   <item>\n+    <widget class="QFrame" name="frameB">\n+     <property name="frameShape">\n+      <enum>QFrame::Box</enum>\n+     </property>\n+     <property name="frameShadow">\n+      <enum>QFrame::Plain</enum>\n+     </property>\n+    </widget>\n+   </item>\n+   <item>\n+    <layout class="QHBoxLayout" name="horizontalLayout">\n+     <property name="bottomMargin">\n+      <number>11</number>\n+     </property>\n+     <item>\n+      <spacer name="horizontalSpacer">\n+       <property name="orientation">\n+        <enum>Qt::Horizontal</enum>\n+       </property>\n+       <property name="sizeHint" stdset="0">\n+        <size>\n+         <width>40</width>\n+         <height>20</height>\n+        </size>\n+       </property>\n+      </spacer>\n+     </item>\n+     <item>\n+      <widget class="QPushButton" name="pushButton">\n+       <property name="text">\n+        <string>Save Fequency Curve</string>\n+       </property>\n+      </widget>\n+     </item>\n+     <item>\n+      <spacer name="horizontalSpacer_2">\n+       <property name="orientation">\n+        <enum>Qt::Horizontal</enum>\n+       </property>\n+       <property name="sizeHint" stdset="0">\n+        <size>\n+         <width>40</width>\n+         <height>20</height>\n+        </size>\n+       </property>\n+      </spacer>\n+     </item>\n+    </layout>\n+   </item>\n+   <item>\n+    <widget class="QDialogButtonBox" name="buttonBox">\n+     <property name="standardButtons">\n+      <set>QDialogButtonBox::Close</set>\n+     </property>\n+    </widget>\n+   </item>\n+  </layout>\n+ </widget>\n+ <resources/>\n+ <connections/>\n+</ui>\n', 'addition_count': 219, 'deletion_count': 0, 'src_file': '/dev/null', 'tgt_file': 'b/plotjuggler_plugins/ToolboxFFT/toolbox_FFT.ui', 'file_extension': '.ui', 'before_file': 'ADDFILE', 'message': 'big work with FFT and PlotWidgetBase refactoring\n', 'author_name': 'Davide Faconti', 'lang': 'ui'}