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