1 // qsynthMeter.cpp
2 //
3 /****************************************************************************
4    Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved.
5 
6    This program is free software; you can redistribute it and/or
7    modify it under the terms of the GNU General Public License
8    as published by the Free Software Foundation; either version 2
9    of the License, or (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License along
17    with this program; if not, write to the Free Software Foundation, Inc.,
18    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 
20 *****************************************************************************/
21 
22 #include "qsynthAbout.h"
23 #include "qsynthMeter.h"
24 
25 #include <QPainter>
26 #include <QToolTip>
27 #include <QLabel>
28 
29 #include <QHBoxLayout>
30 
31 #include <QtMath>
32 
33 
34 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
35 #define horizontalAdvance  width
36 #endif
37 
38 
39 // Meter level limits (in dB).
40 #define QSYNTH_METER_MAXDB		(+3.0f)
41 #define QSYNTH_METER_MINDB		(-70.0f)
42 
43 // The decay rates (magic goes here :).
44 // - value decay rate (faster)
45 #define QSYNTH_METER_DECAY_RATE1	(1.0f - 3E-2f)
46 // - peak decay rate (slower)
47 #define QSYNTH_METER_DECAY_RATE2	(1.0f - 3E-6f)
48 
49 // Number of cycles the peak stays on hold before fall-off.
50 #define QSYNTH_METER_PEAK_FALLOFF	16
51 
52 
53 //----------------------------------------------------------------------------
54 // qsynthMeterScale -- Meter bridge scale widget.
55 
56 // Constructor.
qsynthMeterScale(qsynthMeter * pMeter)57 qsynthMeterScale::qsynthMeterScale( qsynthMeter *pMeter )
58 	: QWidget(pMeter), m_pMeter(pMeter)
59 {
60 	m_iLastY = 0;
61 
62 	QWidget::setMinimumWidth(16);
63 //	QWidget::setBackgroundRole(QPalette::Mid);
64 
65 	const QFont& font = QWidget::font();
66 	QWidget::setFont(QFont(font.family(), font.pointSize() - 4));
67 }
68 
69 
70 // Draw IEC scale line and label; assumes labels drawed from top to bottom.
drawLineLabel(QPainter * p,int y,const QString & sLabel)71 void qsynthMeterScale::drawLineLabel (
72 	QPainter *p, int y, const QString& sLabel )
73 {
74 	const int iCurrY = QWidget::height() - y;
75 	const int iWidth = QWidget::width()  - 2;
76 
77 	const QFontMetrics& fm = p->fontMetrics();
78 	const int iMidHeight = (fm.height() >> 1);
79 
80 	if (iCurrY < iMidHeight || iCurrY > m_iLastY + iMidHeight) {
81 		if (fm.horizontalAdvance(sLabel) < iWidth - 5) {
82 			p->drawLine(0, iCurrY, 2, iCurrY);
83 			if (m_pMeter->portCount() > 1)
84 				p->drawLine(iWidth - 3, iCurrY, iWidth - 1, iCurrY);
85 		}
86 		p->drawText(0, iCurrY - iMidHeight, iWidth - 2, fm.height(),
87 			Qt::AlignHCenter | Qt::AlignVCenter, sLabel);
88 		m_iLastY = iCurrY + 1;
89 	}
90 }
91 
92 
93 // Paint event handler.
paintEvent(QPaintEvent *)94 void qsynthMeterScale::paintEvent ( QPaintEvent * )
95 {
96 	QPainter p(this);
97 
98 	m_iLastY = 0;
99 
100 	const QPalette& pal = QWidget::palette();
101 	const bool bDark = (pal.base().color().value() < 0x7f);
102 	const QColor& color = pal.midlight().color();
103 	p.setPen(bDark ? color.lighter() : color.darker());
104 
105 	drawLineLabel(&p, m_pMeter->iec_level(qsynthMeter::Color0dB),   "0");
106 	drawLineLabel(&p, m_pMeter->iec_level(qsynthMeter::Color3dB),   "3");
107 	drawLineLabel(&p, m_pMeter->iec_level(qsynthMeter::Color6dB),   "6");
108 	drawLineLabel(&p, m_pMeter->iec_level(qsynthMeter::Color10dB), "10");
109 
110 	for (float dB = -20.0f; dB > QSYNTH_METER_MINDB; dB -= 10.0f)
111 		drawLineLabel(&p, m_pMeter->iec_scale(dB), QString::number(-int(dB)));
112 }
113 
114 
115 //----------------------------------------------------------------------------
116 // qsynthMeterValue -- Meter bridge value widget.
117 
118 // Constructor.
qsynthMeterValue(qsynthMeter * pMeter)119 qsynthMeterValue::qsynthMeterValue ( qsynthMeter *pMeter )
120 	: QFrame(pMeter), m_pMeter(pMeter)
121 {
122 	m_fValue = 0.0f;
123 
124 	m_iValue = 0;
125 	m_fValueDecay = QSYNTH_METER_DECAY_RATE1;
126 
127 	m_iPeak = 0;
128 	m_fPeakDecay = QSYNTH_METER_DECAY_RATE2;
129 	m_iPeakHold  = 0;
130 	m_iPeakColor = qsynthMeter::Color6dB;
131 
132 	QWidget::setMinimumWidth(12);
133 	QFrame::setBackgroundRole(QPalette::NoRole);
134 
135 	QFrame::setFrameShape(QFrame::StyledPanel);
136 	QFrame::setFrameShadow(QFrame::Sunken);
137 }
138 
139 
140 // Frame value one-way accessors.
setValue(float fValue)141 void qsynthMeterValue::setValue ( float fValue )
142 {
143 	if (m_fValue < fValue)
144 		m_fValue = fValue;
145 
146 	refresh();
147 }
148 
149 
150 // Reset peak holder.
peakReset(void)151 void qsynthMeterValue::peakReset (void)
152 {
153 	m_iPeak = 0;
154 }
155 
156 
157 // Value refreshment.
refresh(void)158 void qsynthMeterValue::refresh (void)
159 {
160 	if (m_fValue < 0.001f && m_iPeak < 1)
161 		return;
162 
163 	float dB = QSYNTH_METER_MINDB;
164 	if (m_fValue > 0.0f) {
165 		dB = 20.0f * ::log10f(m_fValue);
166 		m_fValue = 0.0f;
167 	}
168 
169 	if (dB < QSYNTH_METER_MINDB)
170 		dB = QSYNTH_METER_MINDB;
171 	else if (dB > QSYNTH_METER_MAXDB)
172 		dB = QSYNTH_METER_MAXDB;
173 
174 	int iValue = m_pMeter->iec_scale(dB);
175 	if (iValue < m_iValue) {
176 		iValue = int(m_fValueDecay * float(m_iValue));
177 		m_fValueDecay *= m_fValueDecay;
178 	} else {
179 		m_fValueDecay = QSYNTH_METER_DECAY_RATE1;
180 	}
181 
182 	int iPeak = m_iPeak;
183 	if (iPeak < iValue) {
184 		iPeak = iValue;
185 		m_iPeakHold = 0;
186 		m_fPeakDecay = QSYNTH_METER_DECAY_RATE2;
187 		m_iPeakColor = qsynthMeter::Color10dB;
188 		for (; m_iPeakColor > qsynthMeter::ColorOver
189 			&& iPeak >= m_pMeter->iec_level(m_iPeakColor); --m_iPeakColor)
190 			/* empty body loop */;
191 	} else if (++m_iPeakHold > m_pMeter->peakFalloff()) {
192 		iPeak = int(m_fPeakDecay * float(iPeak));
193 		if (iPeak < iValue) {
194 			iPeak = iValue;
195 		} else {
196 			m_fPeakDecay *= m_fPeakDecay;
197 		}
198 	}
199 
200 	if (iValue == m_iValue && iPeak == m_iPeak)
201 		return;
202 
203 	m_iValue = iValue;
204 	m_iPeak  = iPeak;
205 
206 	update();
207 }
208 
209 
210 
211 // Paint event handler.
paintEvent(QPaintEvent *)212 void qsynthMeterValue::paintEvent ( QPaintEvent * )
213 {
214 	QPainter painter(this);
215 
216 	const int w = QWidget::width();
217 	const int h = QWidget::height();
218 
219 	int y;
220 
221 	if (isEnabled()) {
222 		painter.fillRect(0, 0, w, h,
223 			m_pMeter->color(qsynthMeter::ColorBack));
224 		y = m_pMeter->iec_level(qsynthMeter::Color0dB);
225 		painter.setPen(m_pMeter->color(qsynthMeter::ColorFore));
226 		painter.drawLine(0, h - y, w, h - y);
227 	} else {
228 		painter.fillRect(0, 0, w, h, QWidget::palette().dark().color());
229 	}
230 
231 #ifdef CONFIG_GRADIENT
232 	painter.drawPixmap(0, h - m_iValue,
233 		m_pMeter->pixmap(), 0, h - m_iValue, w, m_iValue);
234 #else
235 	y = m_iValue;
236 
237 	int y_over = 0;
238 	int y_curr = 0;
239 
240 	for (int i = qsynthMeter::Color10dB;
241 			i > qsynthMeter::ColorOver && y >= y_over; --i) {
242 		y_curr = m_pMeter->iec_level(i);
243 		if (y < y_curr) {
244 			painter.fillRect(0, h - y, w, y - y_over,
245 				m_pMeter->color(i));
246 		} else {
247 			painter.fillRect(0, h - y_curr, w, y_curr - y_over,
248 				m_pMeter->color(i));
249 		}
250 		y_over = y_curr;
251 	}
252 
253 	if (y > y_over) {
254 		painter.fillRect(0, h - y, w, y - y_over,
255 			m_pMeter->color(qsynthMeter::ColorOver));
256 	}
257 #endif
258 
259 	painter.setPen(m_pMeter->color(m_iPeakColor));
260 	painter.drawLine(0, h - m_iPeak, w, h - m_iPeak);
261 }
262 
263 
264 // Resize event handler.
resizeEvent(QResizeEvent * pResizeEvent)265 void qsynthMeterValue::resizeEvent ( QResizeEvent *pResizeEvent )
266 {
267 	m_iPeak = 0;
268 
269 	QWidget::resizeEvent(pResizeEvent);
270 //	QWidget::repaint(true);
271 }
272 
273 
274 //----------------------------------------------------------------------------
275 // qsynthMeter -- Meter bridge slot widget.
276 
277 // Constructor.
qsynthMeter(QWidget * pParent)278 qsynthMeter::qsynthMeter ( QWidget *pParent )
279 	: QWidget(pParent)
280 {
281 	m_iPortCount   = 2;	// FIXME: Default port count.
282 	m_iScaleCount  = m_iPortCount;
283 	m_ppValues     = nullptr;
284 	m_ppScales     = nullptr;
285 
286 	m_fScale = 0.0f;
287 
288 #ifdef CONFIG_GRADIENT
289 	m_pPixmap = new QPixmap();
290 #endif
291 
292 	m_iPeakFalloff = QSYNTH_METER_PEAK_FALLOFF;
293 
294 	for (int i = 0; i < LevelCount; i++)
295 		m_levels[i] = 0;
296 
297 	m_colors[ColorOver] = QColor(240,  0, 20);
298 	m_colors[Color0dB]  = QColor(240,160, 20);
299 	m_colors[Color3dB]  = QColor(220,220, 20);
300 	m_colors[Color6dB]  = QColor(160,220, 20);
301 	m_colors[Color10dB] = QColor( 40,160, 40);
302 	m_colors[ColorBack] = QColor( 20, 40, 20);
303 	m_colors[ColorFore] = QColor( 80, 80, 80);
304 
305 	m_pHBoxLayout = new QHBoxLayout();
306 	m_pHBoxLayout->setContentsMargins(0, 0, 0, 0);
307 	m_pHBoxLayout->setSpacing(0);
308 	QWidget::setLayout(m_pHBoxLayout);
309 
310 	QWidget::setBackgroundRole(QPalette::NoRole);
311 
312 	if (m_iPortCount > 0) {
313 		if (m_iPortCount > 1)
314 			m_iScaleCount--;
315 		m_ppValues = new qsynthMeterValue *[m_iPortCount];
316 		m_ppScales = new qsynthMeterScale *[m_iScaleCount];
317 		for (int iPort = 0; iPort < m_iPortCount; iPort++) {
318 			m_ppValues[iPort] = new qsynthMeterValue(this);
319 			m_pHBoxLayout->addWidget(m_ppValues[iPort]);
320 			if (iPort < m_iScaleCount) {
321 				m_ppScales[iPort] = new qsynthMeterScale(this);
322 				m_pHBoxLayout->addWidget(m_ppScales[iPort]);
323 			}
324 		}
325 		int iStripCount = 2 * m_iPortCount;
326 		if (m_iPortCount > 1)
327 			--iStripCount;
328 		QWidget::setMinimumSize(12 * iStripCount, 120);
329 		QWidget::setMaximumWidth(16 * iStripCount);
330 	} else {
331 		QWidget::setMinimumSize(2, 120);
332 		QWidget::setMaximumWidth(4);
333 	}
334 
335 	QWidget::setSizePolicy(
336 		QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding));
337 }
338 
339 
340 // Default destructor.
~qsynthMeter(void)341 qsynthMeter::~qsynthMeter (void)
342 {
343 #ifdef CONFIG_GRADIENT
344 	delete m_pPixmap;
345 #endif
346 	for (int iPort = 0; iPort < m_iPortCount; iPort++) {
347 		delete m_ppValues[iPort];
348 		if (iPort < m_iScaleCount)
349 			delete m_ppScales[iPort];
350 	}
351 
352 	delete [] m_ppScales;
353 	delete [] m_ppValues;
354 
355 	delete m_pHBoxLayout;
356 }
357 
358 
359 // Child widget accessors.
iec_scale(float dB) const360 int qsynthMeter::iec_scale ( float dB ) const
361 {
362 	float fDef = 1.0;
363 
364 	if (dB < -70.0)
365 		fDef = 0.0;
366 	else if (dB < -60.0)
367 		fDef = (dB + 70.0) * 0.0025;
368 	else if (dB < -50.0)
369 		fDef = (dB + 60.0) * 0.005 + 0.025;
370 	else if (dB < -40.0)
371 		fDef = (dB + 50.0) * 0.0075 + 0.075;
372 	else if (dB < -30.0)
373 		fDef = (dB + 40.0) * 0.015 + 0.15;
374 	else if (dB < -20.0)
375 		fDef = (dB + 30.0) * 0.02 + 0.3;
376 	else /* if (dB < 0.0) */
377 		fDef = (dB + 20.0) * 0.025 + 0.5;
378 
379 	return (int) (fDef * m_fScale);
380 }
381 
382 
iec_level(int iIndex) const383 int qsynthMeter::iec_level ( int iIndex ) const
384 {
385 	return m_levels[iIndex];
386 }
387 
388 
portCount(void) const389 int qsynthMeter::portCount (void) const
390 {
391 	return m_iPortCount;
392 }
393 
394 
395 
396 // Peak falloff mode setting.
setPeakFalloff(int iPeakFalloff)397 void qsynthMeter::setPeakFalloff ( int iPeakFalloff )
398 {
399 	m_iPeakFalloff = iPeakFalloff;
400 }
401 
peakFalloff(void) const402 int qsynthMeter::peakFalloff (void) const
403 {
404 	return m_iPeakFalloff;
405 }
406 
407 
408 // Reset peak holder.
peakReset(void)409 void qsynthMeter::peakReset (void)
410 {
411 	for (int iPort = 0; iPort < m_iPortCount; iPort++)
412 		m_ppValues[iPort]->peakReset();
413 }
414 
415 
416 #ifdef CONFIG_GRADIENT
417 // Gradient pixmap accessor.
pixmap(void) const418 const QPixmap& qsynthMeter::pixmap (void) const
419 {
420 	return *m_pPixmap;
421 }
422 
updatePixmap(void)423 void qsynthMeter::updatePixmap (void)
424 {
425 	const int w = QWidget::width();
426 	const int h = QWidget::height();
427 
428 	QLinearGradient grad(0, 0, 0, h);
429 	grad.setColorAt(0.2f, color(ColorOver));
430 	grad.setColorAt(0.3f, color(Color0dB));
431 	grad.setColorAt(0.4f, color(Color3dB));
432 	grad.setColorAt(0.6f, color(Color6dB));
433 	grad.setColorAt(0.8f, color(Color10dB));
434 
435 	*m_pPixmap = QPixmap(w, h);
436 
437 	QPainter(m_pPixmap).fillRect(0, 0, w, h, grad);
438 }
439 #endif
440 
441 
442 // Slot refreshment.
refresh(void)443 void qsynthMeter::refresh (void)
444 {
445 	for (int iPort = 0; iPort < m_iPortCount; iPort++)
446 		m_ppValues[iPort]->refresh();
447 }
448 
449 
450 // Resize event handler.
resizeEvent(QResizeEvent *)451 void qsynthMeter::resizeEvent ( QResizeEvent * )
452 {
453 	m_fScale = 0.85f * float(QWidget::height());
454 
455 	m_levels[Color0dB]  = iec_scale(  0.0f);
456 	m_levels[Color3dB]  = iec_scale( -3.0f);
457 	m_levels[Color6dB]  = iec_scale( -6.0f);
458 	m_levels[Color10dB] = iec_scale(-10.0f);
459 
460 #ifdef CONFIG_GRADIENT
461 	updatePixmap();
462 #endif
463 }
464 
465 
466 // Meter value proxy.
setValue(int iPort,float fValue)467 void qsynthMeter::setValue ( int iPort, float fValue )
468 {
469 	m_ppValues[iPort]->setValue(fValue);
470 }
471 
472 
473 // Common resource accessor.
color(int iIndex) const474 const QColor& qsynthMeter::color ( int iIndex ) const
475 {
476 	return m_colors[iIndex];
477 }
478 
479 
480 // end of qsynthMeter.cpp
481