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