1 // Copyright 2004 "Gilles Degottex"
2
3 // This file is part of "fmit"
4
5 // "fmit" is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // "fmit" is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19 // TODO zoom with a rectangle
20
21 #include "GLFT.h"
22
23 #include <iostream>
24 using namespace std;
25 #include <qtooltip.h>
26 #include <qimage.h>
27 #include <qevent.h>
28 #include <qboxlayout.h>
29 #include <qwidgetaction.h>
30 #include <QPainter>
31 #include <Music/Music.h>
32 #include <Music/SPWindow.h>
33 using namespace Music;
34 #include <CppAddons/CAMath.h>
35 using namespace Math;
36
GLFT(QWidget * parent)37 GLFT::GLFT(QWidget* parent)
38 : QOpenGLWidget(parent)
39 , View(tr("Fourier Transform"), this)
40 , m_font("Helvetica")
41 , m_components_max(1.0)
42 {
43 m_start_move_mouse = true;
44
45 m_os = 2; // over sampling factor
46
47 // settings
48 setting_show->setIcon(QIcon(":/fmit/ui/images/module_fourier.svg"));
49 setting_show->setChecked(false);
50 hide();
51
52 setting_db_scale = new QAction(tr("dB scale"), this);
53 setting_db_scale->setCheckable(true);
54 connect(setting_db_scale, SIGNAL(toggled(bool)), this, SLOT(dBScaleChanged(bool)));
55 setting_db_scale->setChecked(true);
56 m_popup_menu.addAction(setting_db_scale);
57 resetaxis();
58
59 QHBoxLayout* sizeActionLayout = new QHBoxLayout(&m_popup_menu);
60
61 QLabel* sizeActionTitle = new QLabel(tr("Size"), &m_popup_menu);
62 sizeActionLayout->addWidget(sizeActionTitle);
63
64 setting_winlen = new QSpinBox(&m_popup_menu);
65 setting_winlen->setObjectName("setting_winlen");
66 setting_winlen->setMinimum(1);
67 setting_winlen->setMaximum(1000);
68 setting_winlen->setSingleStep(1);
69 setting_winlen->setValue(20);
70 setting_winlen->setSuffix(" ms");
71 setting_winlen->setToolTip(tr("window length"));
72 connect(setting_winlen, SIGNAL(valueChanged(int)), this, SLOT(spinWinLengthChanged(int)));
73 sizeActionLayout->addWidget(setting_winlen);
74
75 QWidget* sizeActionWidget = new QWidget(&m_popup_menu);
76 sizeActionWidget->setLayout(sizeActionLayout);
77
78 QWidgetAction* sizeAction = new QWidgetAction(&m_popup_menu);
79 sizeAction->setDefaultWidget(sizeActionWidget);
80 m_popup_menu.addAction(sizeAction);
81
82 QWidgetAction* helpCaption01 = new QWidgetAction(&m_popup_menu);
83 helpCaption01->setDefaultWidget(new Title(tr("- Press left mouse button to move the view"), &m_popup_menu));
84 m_popup_menu.addAction(helpCaption01);
85 QWidgetAction* helpCaption02 = new QWidgetAction(&m_popup_menu);
86 helpCaption02->setDefaultWidget(new Title(tr("- Press SHIFT key and left mouse button to zoom in and out"), &m_popup_menu));
87 m_popup_menu.addAction(helpCaption02);
88 QWidgetAction* helpCaption03 = new QWidgetAction(&m_popup_menu);
89 helpCaption03->setDefaultWidget(new Title(tr("- Double-click to reset the view"), &m_popup_menu));
90 m_popup_menu.addAction(helpCaption03);
91
92 s_settings->add(setting_winlen);
93
94 spinWinLengthChanged(setting_winlen->value());
95 }
96
refreshGraph()97 void GLFT::refreshGraph()
98 {
99 while(int(buff.size())>m_plan.size())
100 buff.pop_back();
101 }
102
setSamplingRate(int sr)103 void GLFT::setSamplingRate(int sr)
104 {
105 m_maxf=sr/2;
106 }
107
resetaxis()108 void GLFT::resetaxis()
109 {
110 m_minf=0;
111 m_maxf=Music::GetSamplingRate()/2; // sr is surely -1 because not yet defined
112
113 if(setting_db_scale->isChecked())
114 {
115 m_minA = -50; // [dB]
116 m_maxA = 100; // [dB]
117 }
118 else
119 {
120 m_maxA = 1; // [amplitude]
121 }
122 }
dBScaleChanged(bool db)123 void GLFT::dBScaleChanged(bool db)
124 {
125 Q_UNUSED(db)
126
127 resetaxis();
128 update();
129 }
130
spinWinLengthChanged(int num)131 void GLFT::spinWinLengthChanged(int num)
132 {
133 if(Music::GetSamplingRate()>0)
134 {
135 // Create window
136 int winlen = int(num/1000.0*Music::GetSamplingRate());
137 win = hann(winlen);
138 double win_sum = 0.0;
139 // normalize the window in energy
140 for(size_t i=0; i<win.size(); i++)
141 win_sum += win[i];
142 for(size_t i=0; i<win.size(); i++)
143 win[i] *= 2*win.size()/win_sum; // 2* because the positive freq are half of the energy
144
145 // Create FFTW3 plan
146 int fftlen=1;
147 while(fftlen<winlen) fftlen *= 2;
148 fftlen *= pow(2,m_os);
149 assert(fftlen<int(Music::GetSamplingRate()));
150 m_plan.resize(fftlen);
151 m_components.resize(m_plan.size()/2);
152
153 cout << "GLFT: INFO: window length=" << win.size() << "ms FFT length=" << m_plan.size() << endl;
154 }
155 }
156
initializeGL()157 void GLFT::initializeGL()
158 {
159 // Set the clear color to black
160 glClearColor(1.0, 1.0, 1.0, 0.0);
161
162 glShadeModel( GL_FLAT );
163 // glShadeModel( GL_SMOOTH );
164
165 glLoadIdentity();
166 }
167
mousePressEvent(QMouseEvent * e)168 void GLFT::mousePressEvent(QMouseEvent* e)
169 {
170 m_start_move_mouse = true;
171 m_press_x = e->x();
172 m_press_y = e->y();
173 m_press_minf = m_minf;
174 m_press_maxf = m_maxf;
175
176 double f = (m_maxf-m_minf)*double(m_press_x)/width()+m_minf;
177 m_text = tr("Frequency %1 [Hz]").arg(f);
178 update();
179 }
mouseDoubleClickEvent(QMouseEvent * e)180 void GLFT::mouseDoubleClickEvent(QMouseEvent* e)
181 {
182 Q_UNUSED(e)
183
184 m_start_move_mouse = true;
185 m_minf=0;
186 m_maxf=Music::GetSamplingRate()/2; // sr is surely -1 because not yet defined
187 resetaxis();
188 update();
189 }
mouseMoveEvent(QMouseEvent * e)190 void GLFT::mouseMoveEvent(QMouseEvent* e)
191 {
192 static int old_x;
193 static int old_y;
194 if(m_start_move_mouse)
195 {
196 old_x = e->x();
197 old_y = e->y();
198 m_start_move_mouse = false;
199 }
200 int dx = e->x() - old_x;
201 int dy = e->y() - old_y;
202
203 if(Qt::LeftButton & e->buttons())
204 {
205 if(Qt::ShiftModifier & e->modifiers())
206 {
207 double f = (m_maxf-m_minf)*double(m_press_x)/width()+m_minf;
208 double zx = double(m_press_x-e->x())/width();
209 zx = pow(8, zx);
210 m_minf = f - zx*(f-m_press_minf);
211 m_maxf = f + zx*(m_press_maxf-f);
212 }
213 else
214 {
215 m_minf -= (m_maxf-m_minf)*dx/width();
216 m_maxf -= (m_maxf-m_minf)*dx/width();
217
218 if(setting_db_scale->isChecked())
219 {
220 m_minA += (m_maxA-m_minA)*dy/height();
221 m_maxA += (m_maxA-m_minA)*dy/height();
222 }
223 else
224 m_maxA -= m_maxA*double(dy)/height();
225 }
226
227 update();
228 }
229
230 old_x = e->x();
231 old_y = e->y();
232 }
233
paintGL()234 void GLFT::paintGL()
235 {
236 glClear(GL_COLOR_BUFFER_BIT);
237
238 if(win.size()>0 && int(buff.size())>=m_plan.size())
239 {
240 // Use last samples
241 while(int(buff.size())>m_plan.size())
242 buff.pop_back();
243
244 int sr = Music::GetSamplingRate();
245
246 // cout << m_plan.size() << endl;
247
248 // name
249 glColor3f(0.75,0.75,0.75);
250 QPainter painter(this);
251 m_font.setPixelSize(20);
252 painter.setFont(m_font);
253 painter.drawText(2, 20, tr("Fourier Transform"));
254 painter.end();
255
256 for(int i=0; i<int(win.size()); i++)
257 m_plan.in[i] = buff[i]*win[i];
258 for(int i=int(win.size()); i<int(m_plan.in.size()); i++)
259 m_plan.in[i] = 0.0;
260
261 m_plan.execute();
262
263 for(int i=0; i<int(m_components.size()); i++)
264 m_components[i] = mod(m_plan.out[i]);
265
266 // cout << "m_minA=" << m_minA << " m_maxA=" << m_maxA << endl;
267 double y;
268 glBegin(GL_LINE_STRIP);
269 glColor3f(0.4, 0.4, 0.5);
270 for(int x=0; x<width(); x++)
271 {
272 int index = int(0.5+(m_minf+(m_maxf-m_minf)*double(x)/width())*m_components.size()/(sr/2.0));
273 if(index<0) index=0;
274 else if(index>=int(m_components.size())) index=int(m_components.size());
275 y = m_components[index];
276 if(setting_db_scale->isChecked())
277 y = height()*(lp(y)-m_minA)/(m_maxA-m_minA);
278 else
279 y = height()*y*m_maxA;
280
281 glVertex2i(x, int(y));
282 }
283
284 glEnd();
285
286 // scale
287 /*glColor3f(0,0,0);
288 for(size_t i=0; i<m_components.size(); i++)
289 {
290 glRasterPos2i(int((i+0.5)*step)-3, 2);
291
292 // string str = StringAddons::toString(i+1);
293 string str = QString::number(i+1).toStdString();
294
295 for(size_t i = 0; i < str.length(); i++)
296 glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, (unsigned char)str[i]);
297 }*/
298 }
299
300 // Text
301 if(m_text.length()>0)
302 {
303 glColor3f(0,0,0);
304
305 QPainter painter(this);
306 m_font.setPixelSize(14);
307 painter.setFont(m_font);
308 painter.drawText(width()/2, 12, m_text);
309 painter.end();
310 }
311
312 glFlush();
313 }
314
resizeGL(int w,int h)315 void GLFT::resizeGL( int w, int h )
316 {
317 // Set the new viewport size
318 glViewport(0, 0, (GLint)w, (GLint)h);
319
320 // Choose the projection matrix to be the matrix
321 // manipulated by the following calls
322 glMatrixMode(GL_PROJECTION);
323
324 // Set the projection matrix to be the identity matrix
325 glLoadIdentity();
326
327 // Define the dimensions of the Orthographic Viewing Volume
328 glOrtho(0.0, w, 0.0, h, 0.0, 1.0);
329
330 // Choose the modelview matrix to be the matrix
331 // manipulated by further calls
332 glMatrixMode(GL_MODELVIEW);
333 }
334