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 #include "GLErrorHistory.h"
20
21 #include <iostream>
22 #include <limits>
23 using namespace std;
24 #include <qtimer.h>
25 #include <qtooltip.h>
26 #include <qimage.h>
27 #include <qwidgetaction.h>
28 #include <qboxlayout.h>
29 #include <QPainter>
30 #include <Music/Music.h>
31
init()32 void GLErrorHistory::Note::init()
33 {
34 min_err = numeric_limits<float>::max();
35 max_err = -numeric_limits<float>::max();
36 avg_err = 0.0f;
37 }
Note(int h)38 GLErrorHistory::Note::Note(int h)
39 : ht(h)
40 {
41 init();
42 }
Note(int h,int num,int den)43 GLErrorHistory::Note::Note(int h, int num, int den)
44 : ht(h)
45 {
46 init();
47 factor = QString::number(num)+"/"+QString::number(den);
48 }
Note(int h,float cents)49 GLErrorHistory::Note::Note(int h, float cents)
50 : ht(h)
51 {
52 init();
53 factor = QString::number(cents);
54 }
getName() const55 QString GLErrorHistory::Note::getName() const
56 {
57 return QString::fromStdString(Music::h2n(ht))+factor;
58 }
addError(float err)59 void GLErrorHistory::Note::addError(float err)
60 {
61 min_err = (err<min_err)?err:min_err;
62 max_err = (err>max_err)?err:max_err;
63
64 if(!errors.empty())
65 avg_err *= errors.size();
66
67 errors.push_back(err);
68
69 avg_err += err;
70 avg_err /= errors.size();
71 }
72
GLErrorHistory(QWidget * parent)73 GLErrorHistory::GLErrorHistory(QWidget* parent)
74 : QOpenGLWidget(parent)
75 , View(tr("Error history"), this)
76 , m_font("Helvetica")
77 {
78 // settings
79 setting_show->setIcon(QIcon(":/fmit/ui/images/module_errorhistory.svg"));
80 setting_show->setChecked(true);
81
82 setting_keep = new QAction(tr("Keep previous notes"), this);
83 setting_keep->setCheckable(true);
84 setting_keep->setChecked(false);
85 connect(setting_keep, SIGNAL(toggled(bool)), this, SLOT(keepPreviousNotes(bool)));
86 m_popup_menu.addAction(setting_keep);
87
88 setting_useCents = new QAction(tr("Use cents"), this);
89 setting_useCents->setCheckable(true);
90 setting_useCents->setChecked(true);
91 connect(setting_useCents, SIGNAL(toggled(bool)), this, SLOT(update()));
92 m_popup_menu.addAction(setting_useCents);
93
94 QHBoxLayout* scaleActionLayout = new QHBoxLayout(&m_popup_menu);
95
96 QLabel* scaleActionTitle = new QLabel(tr("Scale range"), &m_popup_menu);
97 scaleActionLayout->addWidget(scaleActionTitle);
98
99 setting_spinScale = new QSpinBox(&m_popup_menu);
100 setting_spinScale->setMinimum(5);
101 setting_spinScale->setMaximum(50);
102 setting_spinScale->setSingleStep(1);
103 setting_spinScale->setValue(50);
104 setting_spinScale->setToolTip(tr("Scale range (in cents)"));
105 connect(setting_spinScale, SIGNAL(valueChanged(int)), this, SLOT(update()));
106 scaleActionLayout->addWidget(setting_spinScale);
107
108 QWidget* scaleActionWidget = new QWidget(&m_popup_menu);
109 scaleActionWidget->setLayout(scaleActionLayout);
110
111 QWidgetAction* scaleAction = new QWidgetAction(&m_popup_menu);
112 scaleAction->setDefaultWidget(scaleActionWidget);
113 m_popup_menu.addAction(scaleAction);
114 }
115
save()116 void GLErrorHistory::save()
117 {
118 s_settings->setValue("keep", setting_keep->isChecked());
119 s_settings->setValue("useCents", setting_useCents->isChecked());
120 s_settings->setValue("spinScale", setting_spinScale->value());
121 }
load()122 void GLErrorHistory::load()
123 {
124 setting_keep->setChecked(s_settings->value("keep", setting_keep->isChecked()).toBool());
125 setting_useCents->setChecked(s_settings->value("useCents", setting_useCents->isChecked()).toBool());
126 setting_spinScale->setValue(s_settings->value("spinScale", setting_spinScale->value()).toInt());
127 }
clearSettings()128 void GLErrorHistory::clearSettings()
129 {
130 s_settings->remove("keep");
131 s_settings->remove("useCents");
132 s_settings->remove("spinScale");
133 }
134
addNote(GLErrorHistory::Note note)135 void GLErrorHistory::addNote(GLErrorHistory::Note note)
136 {
137 m_notes.push_back(note);
138
139 if(!setting_keep->isChecked())
140 while(m_notes.size()>1)
141 m_notes.pop_front();
142 }
addError(float err)143 void GLErrorHistory::addError(float err)
144 {
145 m_notes.back().addError(err);
146 }
147
keepPreviousNotes(bool keep)148 void GLErrorHistory::keepPreviousNotes(bool keep)
149 {
150 if(!keep)
151 while(m_notes.size()>1)
152 m_notes.pop_front();
153 }
154
initializeGL()155 void GLErrorHistory::initializeGL()
156 {
157 // Set the clear color to black
158 glClearColor(1.0, 1.0, 1.0, 0.0);
159
160 // glShadeModel( GL_FLAT );
161 glShadeModel( GL_SMOOTH );
162
163 glLoadIdentity();
164 }
165
drawTicksCent(int r,int ticks_size)166 void GLErrorHistory::drawTicksCent(int r, int ticks_size)
167 {
168 // only work within range that is a pure multiple of r
169 float range = int(setting_spinScale->value()/r)*r;
170
171 float scale = 50.0f/setting_spinScale->value();
172 if((height()-ticks_size)*r/100.0f*scale>2)
173 {
174 for(float i=-range; i<=range; i+=r)
175 {
176 int y = height()/2 + int(height()*i/100.0f*scale);
177 glVertex2i(ticks_size, y);
178 glVertex2i(width(), y);
179 }
180 }
181 }
drawTextTickCent(int r,int dy)182 void GLErrorHistory::drawTextTickCent(int r, int dy)
183 {
184 Q_UNUSED(dy)
185
186 // only work within range that is a pure multiple of r
187 int range = int(setting_spinScale->value()/r)*r;
188
189 QPainter painter(this);
190 m_font.setPixelSize(14);
191 painter.setFont(m_font);
192 QFontMetrics fm(m_font);
193
194 float scale = 50.0f/setting_spinScale->value();
195 QString txt;
196 for(int i=-range; i<=range; i+=r)
197 {
198 txt = QString::number(i);
199 if(i>=0) txt = QString(" ")+txt;
200 if(i==0) txt = QString(" ")+txt;
201 int y = height()/2 - int(height()*i/100.0f*scale);
202 if(y>fm.xHeight() && y<height()-fm.xHeight())
203 painter.drawText(2, y+ fm.xHeight()/2, txt);
204 }
205 painter.end();
206 }
207
208 //void GLErrorHistory::paintEvent(QPaintEvent * event){
209 // QPainter painter;
210 // painter.begin(this);
211 // QBrush background = QBrush(Qt::white);
212 // painter.fillRect(rect(), background);
213 // painter.end();
214 //}
215
paintGL()216 void GLErrorHistory::paintGL()
217 {
218 // cout << "GLErrorHistory::paintGL " << m_notes.size() << endl;
219
220 glClear(GL_COLOR_BUFFER_BIT);
221
222 glLineWidth(1.0f);
223
224 // name
225 glColor3f(0.75,0.75,0.75);
226 QPainter painter(this);
227 m_font.setPixelSize(20);
228 painter.setFont(m_font);
229 painter.drawText(2, 20, tr("Error"));
230 painter.end();
231
232 int char_size = 9;
233 int ticks_size = 2+3*char_size;
234 int dy = char_size/2;
235
236 // horiz lines
237 if(setting_useCents->isChecked())
238 {
239 glBegin(GL_LINES);
240 float gray = 0.87;
241 // glColor3f(gray, gray, gray);
242 // drawTicksCent(1, ticks_size);
243 gray = 0.875;
244 glColor3f(gray, gray, gray);
245 drawTicksCent(2, ticks_size);
246 gray = 0.75;
247 glColor3f(gray, gray, gray);
248 drawTicksCent(10, ticks_size);
249 glEnd();
250 }
251 else
252 {
253 glBegin(GL_LINES);
254 float gray = 0.5;
255 glColor3f(gray, gray, gray);
256 glVertex2i(ticks_size, height()/2);
257 glVertex2i(width(), height()/2);
258 gray = 0.75;
259 glColor3f(gray, gray, gray);
260 glVertex2i(ticks_size, height()/4);
261 glVertex2i(width(), height()/4);
262 glVertex2i(ticks_size, 3*height()/4);
263 glVertex2i(width(), 3*height()/4);
264 gray = 0.87;
265 glColor3f(gray, gray, gray);
266 glVertex2i(ticks_size, height()/8);
267 glVertex2i(width(), height()/8);
268 glVertex2i(ticks_size, 7*height()/8);
269 glVertex2i(width(), 7*height()/8);
270 glVertex2i(ticks_size, 3*height()/8);
271 glVertex2i(width(), 3*height()/8);
272 glVertex2i(ticks_size, 5*height()/8);
273 glVertex2i(width(), 5*height()/8);
274 glEnd();
275 }
276
277 // text marks
278 float gray = 0.5;
279 glColor3f(gray, gray, gray);
280 if(setting_useCents->isChecked())
281 {
282 int grad = 10;
283 if(setting_spinScale->value() <= 25) grad=5;
284 if(setting_spinScale->value() <= 10) grad=1;
285 drawTextTickCent(grad, dy);
286 }
287 else
288 {
289 painter.begin(this);
290 m_font.setPixelSize(14);
291 painter.setFont(m_font);
292 QFontMetrics fm(m_font);
293 string sfraq, sufraq;
294 sufraq = string("1/")+QString::number(int(50/setting_spinScale->value())*2).toStdString();
295 sfraq = string("-")+sufraq;
296 painter.drawText(2, 3*height()/4-dy+fm.xHeight(), QString(sfraq.c_str()));
297 sfraq = string("+")+sufraq;
298 painter.drawText(2, height()/4-dy+fm.xHeight(), QString(sfraq.c_str()));
299
300 sufraq = string("1/")+QString::number(int(50/setting_spinScale->value())*4).toStdString();
301 sfraq = string("-")+sufraq;
302 painter.drawText(2, 5*height()/8-dy+fm.xHeight(), QString(sfraq.c_str()));
303 sfraq = string("+")+sufraq;
304 painter.drawText(2, 3*height()/8-dy+fm.xHeight(), QString(sfraq.c_str()));
305 painter.end();
306 }
307
308 // errors
309 if(!m_notes.empty())
310 {
311 int total_size = 0;
312 for(size_t i=0; i<m_notes.size(); i++)
313 total_size += int(m_notes[i].errors.size())-1;
314
315 float step = float(width()-ticks_size)/total_size;
316
317 // cout << "total_size=" << total_size << " step=" << step << endl;
318
319 int curr_total = 0;
320 for(size_t i=0; i<m_notes.size(); i++)
321 {
322 float x = ticks_size+step*curr_total;
323
324 // if it's not the first, add a separation
325 if(i>0)
326 {
327 glColor3f(0.75,0.75,0.75);
328 glLineWidth(1.0f);
329 glBegin(GL_LINES);
330 glVertex2f(x, 0); glVertex2f(x, height());
331 glEnd();
332 }
333
334 // the note name
335 string str = m_notes[i].getName().toStdString();
336 glColor3f(0.0,0.0,1.0);
337 painter.begin(this);
338 m_font.setPixelSize(14);
339 painter.setFont(m_font);
340 painter.drawText(x+2, height()-2, QString(str.c_str()));
341 painter.end();
342
343 // draw the error graph
344 glColor3f(0.0f,0.0f,0.0f);
345 glLineWidth(2.0f);
346 glBegin(GL_LINE_STRIP);
347
348 if(setting_useCents->isChecked())
349 {
350 float scale = 50.0f/setting_spinScale->value();
351 glVertex2f(x, int(scale*m_notes[i].errors[0]*height()) + height()/2);
352 for(int j=1; j<int(m_notes[i].errors.size()); j++)
353 glVertex2f(x+j*step, scale*m_notes[i].errors[j]*height() + height()/2);
354 }
355 else
356 {
357 float scale = int(50/setting_spinScale->value());
358 glVertex2f(x, int((scale*m_notes[i].errors[0])*height()) + height()/2);
359 for(int j=1; j<int(m_notes[i].errors.size()); j++)
360 glVertex2f(x+j*step, (scale*m_notes[i].errors[j])*height() + height()/2);
361 }
362 glEnd();
363
364 curr_total += int(m_notes[i].errors.size())-1;
365 }
366 }
367
368 glFlush();
369 }
370
resizeGL(int w,int h)371 void GLErrorHistory::resizeGL( int w, int h )
372 {
373 // Set the new viewport size
374 glViewport(0, 0, (GLint)w, (GLint)h);
375
376 // Choose the projection matrix to be the matrix
377 // manipulated by the following calls
378 glMatrixMode(GL_PROJECTION);
379
380 // Set the projection matrix to be the identity matrix
381 glLoadIdentity();
382
383 // Define the dimensions of the Orthographic Viewing Volume
384 glOrtho(0.0, w, 0.0, h, 0.0, 1.0);
385
386 // Choose the modelview matrix to be the matrix
387 // manipulated by further calls
388 glMatrixMode(GL_MODELVIEW);
389 }
390
391