Import upstream version 0.99.2
[fmit.git] / src / modules / GLFT.cpp
index bbde0d6..42071f7 100644 (file)
@@ -16,6 +16,8 @@
 // along with this program; if not, write to the Free Software
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
+// TODO zoom with a rectangle
+
 #include "GLFT.h"
 
 static const unsigned char g_icon_FT[] = {
@@ -168,6 +170,9 @@ static const unsigned char g_icon_FT[] = {
 using namespace std;
 #include <qtooltip.h>
 #include <qimage.h>
+#include <qevent.h>
+#include <qboxlayout.h>
+#include <qwidgetaction.h>
 #include <GL/glut.h>
 #include <Music/Music.h>
 #include <Music/SPWindow.h>
@@ -176,80 +181,122 @@ using namespace Music;
 using namespace Math;
 
 GLFT::GLFT(QWidget* parent)
-: QGLWidget(parent, "GLFT")
-, View("Fourier Transform", this)
+: QGLWidget(parent)
+, View(tr("Fourier Transform"), this)
 , m_components_max(1.0)
 {
        m_start_move_mouse = true;
-       m_x = 0;
-       m_y = 0;
-       m_z = 0.0;
 
-       m_zp_factor = 4;                                        // actually, actuellement, not zero-padding but win-size-factor
+       m_os = 2;                                       // over sampling factor
 
        // settings
-       QImage img;
+       QPixmap img;
        img.loadFromData(g_icon_FT, sizeof(g_icon_FT), "PNG");
-       setting_show->setIconSet(QIconSet(QImage(img)));
-       setting_show->setOn(false);
+       setting_show->setIcon(QIcon(img));
+       setting_show->setChecked(false);
        hide();
 
-       setting_autoScale = new QAction(this);
-       setting_autoScale->setMenuText(tr("Auto scale"));
-       setting_autoScale->setToggleAction(true);
-       connect(setting_autoScale, SIGNAL(toggled(bool)), this, SLOT(update()));
-       setting_autoScale->setOn(true);
-       setting_autoScale->addTo(&m_popup_menu);
-
-       setting_db_scale = new QAction(this);
-       setting_db_scale->setMenuText(tr("dB scale"));
-       setting_db_scale->setToggleAction(true);
-       connect(setting_db_scale, SIGNAL(toggled(bool)), this, SLOT(update()));
-       setting_db_scale->setOn(true);
-       setting_db_scale->addTo(&m_popup_menu);
-
-       m_popup_menu.insertItem(new Title(tr("Size"), &m_popup_menu));
-       setting_spinNumComponents = new QSpinBox(1, 65536, 1, &m_popup_menu);
-       setting_spinNumComponents->setValue(8192);
-       m_components.resize(setting_spinNumComponents->value());
-       QToolTip::add(setting_spinNumComponents, tr("Size"));
-       connect(setting_spinNumComponents, SIGNAL(valueChanged(int)), this, SLOT(spinNumComponentsChanged(int)));
-       m_popup_menu.insertItem(setting_spinNumComponents);
-       m_popup_menu.insertItem(new Title(tr("- Press left mouse button and move mouse to zoom"), &m_popup_menu));
-       m_popup_menu.insertItem(new Title(tr("- Press SHIFT key, left mouse button and move mouse to move view"), &m_popup_menu));
-       m_popup_menu.insertItem(new Title(tr("- Double-click to reset view"), &m_popup_menu));
-
-       m_plan.resize(setting_spinNumComponents->value());
-       m_components.resize(m_plan.size()/2, 0.0);
-       win = hann(m_plan.size());
+       setting_db_scale = new QAction(tr("dB scale"), this);
+       setting_db_scale->setCheckable(true);
+       connect(setting_db_scale, SIGNAL(toggled(bool)), this, SLOT(dBScaleChanged(bool)));
+       setting_db_scale->setChecked(true);
+       m_popup_menu.addAction(setting_db_scale);
+       resetaxis();
+
+       QHBoxLayout* sizeActionLayout = new QHBoxLayout(&m_popup_menu);
+
+       QLabel* sizeActionTitle = new QLabel(tr("Size"), &m_popup_menu);
+       sizeActionLayout->addWidget(sizeActionTitle);
+
+       setting_winlen = new QSpinBox(&m_popup_menu);
+       setting_winlen->setObjectName("setting_winlen");
+       setting_winlen->setMinimum(1);
+       setting_winlen->setMaximum(1000);
+       setting_winlen->setSingleStep(1);
+       setting_winlen->setValue(20);
+       setting_winlen->setSuffix(" ms");
+       setting_winlen->setToolTip(tr("window length"));
+       connect(setting_winlen, SIGNAL(valueChanged(int)), this, SLOT(spinWinLengthChanged(int)));
+       sizeActionLayout->addWidget(setting_winlen);
+
+       QWidget* sizeActionWidget = new QWidget(&m_popup_menu);
+       sizeActionWidget->setLayout(sizeActionLayout);
+
+       QWidgetAction* sizeAction = new QWidgetAction(&m_popup_menu);
+       sizeAction->setDefaultWidget(sizeActionWidget);
+       m_popup_menu.addAction(sizeAction);
+
+       QWidgetAction* helpCaption01 = new QWidgetAction(&m_popup_menu);
+       helpCaption01->setDefaultWidget(new Title(tr("- Press left mouse button to move the view"), &m_popup_menu));
+       m_popup_menu.addAction(helpCaption01);
+       QWidgetAction* helpCaption02 = new QWidgetAction(&m_popup_menu);
+       helpCaption02->setDefaultWidget(new Title(tr("- Press SHIFT key and left mouse button to zoom in and out"), &m_popup_menu));
+       m_popup_menu.addAction(helpCaption02);
+       QWidgetAction* helpCaption03 = new QWidgetAction(&m_popup_menu);
+       helpCaption03->setDefaultWidget(new Title(tr("- Double-click to reset the view"), &m_popup_menu));
+       m_popup_menu.addAction(helpCaption03);
+
+       s_settings->add(setting_winlen);
+
+       spinWinLengthChanged(setting_winlen->value());
 }
 
-void GLFT::save()
+void GLFT::refreshGraph()
 {
-       s_settings->writeEntry("spinNumComponents", setting_spinNumComponents->value());
+       while(int(buff.size())>m_plan.size())
+               buff.pop_back();
 }
-void GLFT::load()
+
+void GLFT::setSamplingRate(int sr)
 {
-       setting_spinNumComponents->setValue(s_settings->readNumEntry("spinNumComponents", setting_spinNumComponents->value()));
+       m_maxf=sr/2;
 }
-void GLFT::clearSettings()
+
+void GLFT::resetaxis()
 {
-       s_settings->removeEntry("spinNumComponents");
-}
+       m_minf=0;
+       m_maxf=Music::GetSamplingRate()/2; // sr is surely -1 because not yet defined
 
-void GLFT::refreshGraph()
+       if(setting_db_scale->isChecked())
+       {
+               m_minA = -50; // [dB]
+               m_maxA = 100; // [dB]
+       }
+       else
+       {
+               m_maxA = 1; // [amplitude]
+       }
+}
+void GLFT::dBScaleChanged(bool db)
 {
-       while(int(buff.size())>m_plan.size())
-               buff.pop_back();
+       resetaxis();
+       update();
 }
 
-void GLFT::spinNumComponentsChanged(int num)
+void GLFT::spinWinLengthChanged(int num)
 {
-       m_plan.resize(int(m_zp_factor*num));
-       m_components.resize(m_plan.size()/2);
-       win = hann(num);
-
-       cerr << "GLFT: INFO: window size=" << win.size() << " FFT size=" << m_plan.size() << endl;
+       if(Music::GetSamplingRate()>0)
+       {
+               // Create window
+               int winlen = int(num/1000.0*Music::GetSamplingRate());
+               win = hann(winlen);
+               double win_sum = 0.0;
+               // normalize the window in energy
+               for(size_t i=0; i<win.size(); i++)
+                       win_sum += win[i];
+               for(size_t i=0; i<win.size(); i++)
+                       win[i] *= 2*win.size()/win_sum; // 2* because the positive freq are half of the energy
+
+               // Create FFTW3 plan
+               int fftlen=1;
+               while(fftlen<winlen) fftlen *= 2;
+               fftlen *= pow(2,m_os);
+               assert(fftlen<int(Music::GetSamplingRate()));
+               m_plan.resize(fftlen);
+               m_components.resize(m_plan.size()/2);
+
+        cerr << "GLFT: INFO: window length=" << win.size() << "ms FFT length=" << m_plan.size() << endl;
+       }
 }
 
 void GLFT::initializeGL()
@@ -268,15 +315,19 @@ void GLFT::mousePressEvent(QMouseEvent* e)
        m_start_move_mouse = true;
        m_press_x = e->x();
        m_press_y = e->y();
+       m_press_minf = m_minf;
+       m_press_maxf = m_maxf;
+
+       double f = (m_maxf-m_minf)*double(m_press_x)/width()+m_minf;
+       m_text = tr("Frequency %1 [Hz]").arg(f);
+       updateGL();
 }
 void GLFT::mouseDoubleClickEvent(QMouseEvent* e)
 {
        m_start_move_mouse = true;
-       m_x = 0;
-       m_y = 0;
-//     m_z = 0.0;
-       m_z = 1.0;
-
+       m_minf=0;
+       m_maxf=Music::GetSamplingRate()/2; // sr is surely -1 because not yet defined
+       resetaxis();
        updateGL();
 }
 void GLFT::mouseMoveEvent(QMouseEvent* e)
@@ -292,17 +343,28 @@ void GLFT::mouseMoveEvent(QMouseEvent* e)
        int dx = e->x() - old_x;
        int dy = e->y() - old_y;
 
-       if(Qt::LeftButton & e->state())
+       if(Qt::LeftButton & e->buttons())
        {
-               if(Qt::ShiftButton & e->state())
+               if(Qt::ShiftModifier & e->modifiers())
                {
-                       m_x += dx;
-                       m_y -= dy;
+                       double f = (m_maxf-m_minf)*double(m_press_x)/width()+m_minf;
+                       double zx = double(m_press_x-e->x())/width();
+                       zx = pow(8, zx);
+                       m_minf = f - zx*(f-m_press_minf);
+                       m_maxf = f + zx*(m_press_maxf-f);
                }
                else
                {
-                       m_z += dx/100.0;
-                       if(m_z<0.0) m_z = 0.0;
+                       m_minf -= (m_maxf-m_minf)*dx/width();
+                       m_maxf -= (m_maxf-m_minf)*dx/width();
+
+                       if(setting_db_scale->isChecked())
+                       {
+                               m_minA += (m_maxA-m_minA)*dy/height();
+                               m_maxA += (m_maxA-m_minA)*dy/height();
+                       }
+                       else
+                               m_maxA -= m_maxA*double(dy)/height();
                }
 
                updateGL();
@@ -316,19 +378,22 @@ void GLFT::paintGL()
 {
        glClear(GL_COLOR_BUFFER_BIT);
 
-       if(int(buff.size())>=m_plan.size())
+       if(win.size()>0 && int(buff.size())>=m_plan.size())
        {
+               // Use last samples
                while(int(buff.size())>m_plan.size())
                        buff.pop_back();
 
+               int sr = Music::GetSamplingRate();
+
+//             cout << m_plan.size() << endl;
+
                // name
-               string str = tr("Fourier Transform");
+               string str = tr("Fourier Transform").toStdString();
                glColor3f(0.75,0.75,0.75);
                glRasterPos2i(2, height()-20);
                for(size_t i = 0; i < str.size(); i++)
-                       glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, str[i]);
-
-               int scale_height = 0;
+                       glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, (unsigned char)str[i]);
 
                for(int i=0; i<int(win.size()); i++)
                        m_plan.in[i] = buff[i]*win[i];
@@ -337,56 +402,55 @@ void GLFT::paintGL()
 
                m_plan.execute();
 
-               m_components_max = -1e100;
                for(int i=0; i<int(m_components.size()); i++)
-               {
                        m_components[i] = mod(m_plan.out[i]);
-                       if(setting_db_scale->isOn())
-                               m_components[i] = 100+20*log10(m_components[i]);
-                       m_components_max = max(m_components_max, m_components[i]);
-               }
-
-//             cerr << m_components_max << endl;
-
-               double scale_factor = 1.0;
-               if(setting_autoScale->isOn() && m_components_max>0.0)
-                       scale_factor = 1.0/m_components_max;
 
-//             cerr << scale_factor << endl;
-
-               // bars
-               glBegin(GL_QUADS);
-               float step = float(width())/m_components.size() + m_z;
-               int space = (step>2)?1:0;
-               for(size_t i=0; i<m_components.size(); i++)
+//             cerr << "m_minA=" << m_minA << " m_maxA=" << m_maxA << endl;
+               double y;
+               glBegin(GL_LINE_STRIP);
+               glColor3f(0.4, 0.4, 0.5);
+               for(size_t x=0; x<width(); x++)
                {
-                       glColor3f(0.4, 0.4, 0.5);
-                       int x = int(i*step);
-                       int y = int( (scale_factor*m_components[i]) * (height()-scale_height)) + scale_height;
-//                     if(y>=0)
-                       {
-                               glVertex2i(m_x+x, m_y+scale_height);
-                               glVertex2i(m_x+x, m_y+y);
-                               glVertex2i(m_x+int((i+1)*step)-space, m_y+y);
-                               glVertex2i(m_x+int((i+1)*step)-space, m_y+scale_height);
-                       }
+                       int index = int(0.5+(m_minf+(m_maxf-m_minf)*double(x)/width())*m_components.size()/(sr/2.0));
+                       if(index<0) index=0;
+                       else if(index>=m_components.size()) index=m_components.size();
+                       y = m_components[index];
+                       if(setting_db_scale->isChecked())
+                               y = height()*(lp(y)-m_minA)/(m_maxA-m_minA);
+                       else
+                               y = height()*y*m_maxA;
+
+                       glVertex2i(x, int(y));
                }
+
                glEnd();
 
                // scale
-/*             glColor3f(0,0,0);
+               /*glColor3f(0,0,0);
                for(size_t i=0; i<m_components.size(); i++)
                {
                        glRasterPos2i(int((i+0.5)*step)-3, 2);
 
-       //              string str = StringAddons::toString(i+1);
-                       string str = QString::number(i+1);
+//                     string str = StringAddons::toString(i+1);
+                       string str = QString::number(i+1).toStdString();
 
-                       for(size_t i = 0; i < str.size(); i++)
-                               glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, str[i]);
+                       for(size_t i = 0; i < str.length(); i++)
+                               glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, (unsigned char)str[i]);
                }*/
        }
 
+       // Text
+       if(m_text.length()>0)
+       {
+               glColor3f(0,0,0);
+               glRasterPos2i(width()/2, 12);
+
+               string str = m_text.toStdString();
+
+               for(size_t i = 0; i < str.length(); i++)
+                       glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, (unsigned char)str[i]);
+       }
+
        glFlush();
 }