// 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[] = {
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>
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()
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)
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();
{
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];
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();
}