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