1 /* -*- c++ -*- */
2 /* + + +   This Software is released under the "Simplified BSD License"  + + +
3  *
4  * Copyright 2010 Moe Wheatley. All rights reserved.
5  * Copyright 2011-2013 Alexandru Csete OZ9AEC.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  *    1. Redistributions of source code must retain the above copyright notice,
11  *       this list of conditions and the following disclaimer.
12  *
13  *    2. Redistributions in binary form must reproduce the above copyright
14  *       notice, this list of conditions and the following disclaimer in the
15  *       documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
20  * EVENT SHALL Moe Wheatley OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
23  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  * The views and conclusions contained in the software and documentation are
29  * those of the authors and should not be interpreted as representing official
30  * policies, either expressed or implied, of Moe Wheatley.
31  */
32 
33 #include <cmath>
34 #include <QDebug>
35 #include "meter.h"
36 
37 // ratio to total control width or height
38 #define CTRL_MARGIN 0.07		// left/right margin
39 #define CTRL_MAJOR_START 0.3	// top of major tic line
40 #define CTRL_MINOR_START 0.3	// top of minor tic line
41 #define CTRL_XAXIS_HEGHT 0.4	// vertical position of horizontal axis
42 #define CTRL_NEEDLE_TOP 0.4		// vertical position of top of needle triangle
43 
44 #define MIN_DB -100.0f
45 #define MAX_DB +0.0f
46 
47 #define ALPHA_DECAY     0.25f
48 #define ALPHA_RISE      0.70f
49 
CMeter(QWidget * parent)50 CMeter::CMeter(QWidget *parent) : QFrame(parent)
51 {
52     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
53     setFocusPolicy(Qt::StrongFocus);
54     setAttribute(Qt::WA_PaintOnScreen,false);
55     setAutoFillBackground(false);
56     setAttribute(Qt::WA_OpaquePaintEvent, false);
57     setAttribute(Qt::WA_NoSystemBackground, true);
58     setMouseTracking(true);
59 
60     m_Font = QFont("Arial");
61     m_Font.setWeight(QFont::Normal);
62     m_2DPixmap = QPixmap(0,0);
63     m_OverlayPixmap = QPixmap(0,0);
64     m_Size = QSize(0,0);
65     m_pixperdb = 0.0f;
66     m_Siglevel = 0;
67     m_dBFS = MIN_DB;
68     m_Sql = -150.0f;
69     m_SqlLevel = 0.0f;
70 }
71 
~CMeter()72 CMeter::~CMeter()
73 {
74 }
75 
minimumSizeHint() const76 QSize CMeter::minimumSizeHint() const
77 {
78     return QSize(20, 10);
79 }
80 
sizeHint() const81 QSize CMeter::sizeHint() const
82 {
83     return QSize(100, 30);
84 }
85 
resizeEvent(QResizeEvent *)86 void CMeter::resizeEvent(QResizeEvent *)
87 {
88     if (!size().isValid())
89         return;
90 
91     if (m_Size != size())
92     {
93         // if size changed, resize pixmaps to new screensize
94         m_Size = size();
95         int dpr = devicePixelRatio();
96         m_OverlayPixmap = QPixmap(m_Size.width() * dpr, m_Size.height() * dpr);
97         m_OverlayPixmap.setDevicePixelRatio(dpr);
98         m_OverlayPixmap.fill(Qt::black);
99         m_2DPixmap = QPixmap(m_Size.width() * dpr, m_Size.height() * dpr);
100         m_2DPixmap.setDevicePixelRatio(dpr);
101         m_2DPixmap.fill(Qt::black);
102 
103         qreal w = (m_2DPixmap.width() / dpr) - 2 * CTRL_MARGIN * (m_2DPixmap.width() / dpr);
104         m_pixperdb = w / fabs(MAX_DB - MIN_DB);
105         setSqlLevel(m_Sql);
106     }
107 
108     DrawOverlay();
109     draw();
110 }
111 
setLevel(float dbfs)112 void CMeter::setLevel(float dbfs)
113 {
114     if (dbfs < MIN_DB)
115         dbfs = MIN_DB;
116     else if (dbfs > MAX_DB)
117         dbfs = MAX_DB;
118 
119     float level = m_dBFS;
120     float alpha  = dbfs < level ? ALPHA_DECAY : ALPHA_RISE;
121     m_dBFS -= alpha * (level - dbfs);
122     m_Siglevel = (int)((level - MIN_DB) * m_pixperdb);
123 
124     draw();
125 }
126 
setSqlLevel(float dbfs)127 void CMeter::setSqlLevel(float dbfs)
128 {
129     if (dbfs >= 0.f)
130         m_SqlLevel = 0.0f;
131     else
132         m_SqlLevel = (dbfs - MIN_DB) * m_pixperdb;
133 
134     if (m_SqlLevel < 0.0f)
135         m_SqlLevel = 0.0f;
136 
137     m_Sql = dbfs;
138 }
139 
140 // Called by QT when screen needs to be redrawn
paintEvent(QPaintEvent *)141 void CMeter::paintEvent(QPaintEvent *)
142 {
143     QPainter painter(this);
144 
145     painter.drawPixmap(0, 0, m_2DPixmap);
146     return;
147 }
148 
149 // Called to update s-meter data for displaying on the screen
draw()150 void CMeter::draw()
151 {
152     int w;
153     int h;
154 
155     if (m_2DPixmap.isNull())
156         return;
157 
158     // get/draw the 2D spectrum
159     w = m_2DPixmap.width() / m_2DPixmap.devicePixelRatio();
160     h = m_2DPixmap.height() / m_2DPixmap.devicePixelRatio();
161 
162     // first copy into 2Dbitmap the overlay bitmap.
163     m_2DPixmap = m_OverlayPixmap.copy(0, 0, m_OverlayPixmap.width(), m_OverlayPixmap.height());
164     QPainter painter(&m_2DPixmap);
165 
166     // DrawCurrent position indicator
167     qreal hline = (qreal) h * CTRL_XAXIS_HEGHT;
168     qreal marg = (qreal) w * CTRL_MARGIN;
169     qreal ht = (qreal) h * CTRL_NEEDLE_TOP;
170     qreal x = marg + m_Siglevel;
171     QPoint pts[3];
172     pts[0].setX(x);
173     pts[0].setY(ht + 2);
174     pts[1].setX(x - 6);
175     pts[1].setY(hline + 8);
176     pts[2].setX(x + 6);
177     pts[2].setY(hline + 8);
178 
179     painter.setBrush(QBrush(QColor(0, 190, 0, 255)));
180     painter.setOpacity(1.0);
181 
182     // Qt 4.8+ has a 1-pixel error (or they fixed line drawing)
183     // see http://stackoverflow.com/questions/16990326
184     painter.drawRect(marg - 1, ht + 1, x - marg, 6);
185 
186     if (m_SqlLevel > 0.0f)
187     {
188         x = marg + m_SqlLevel;
189         painter.setPen(QPen(Qt::yellow, 1, Qt::SolidLine));
190         painter.drawLine(QLineF(x, hline, x, hline + 8));
191     }
192 
193     int y = (h) / 4;
194     m_Font.setPixelSize(y);
195     painter.setFont(m_Font);
196 
197     painter.setPen(QColor(0xDA, 0xDA, 0xDA, 0xFF));
198     painter.setOpacity(1.0);
199     m_Str.setNum(m_dBFS);
200     painter.drawText(marg, h - 2, m_Str + " dBFS" );
201 
202     update();
203 }
204 
205 // Called to draw an overlay bitmap containing items that
206 // does not need to be recreated every fft data update.
DrawOverlay()207 void CMeter::DrawOverlay()
208 {
209     if (m_OverlayPixmap.isNull())
210         return;
211 
212     int w = m_OverlayPixmap.width() / m_OverlayPixmap.devicePixelRatio();
213     int h = m_OverlayPixmap.height() / m_OverlayPixmap.devicePixelRatio();
214     int x,y;
215     QRect rect;
216     QPainter painter(&m_OverlayPixmap);
217 
218     m_OverlayPixmap.fill(QColor(0x1F, 0x1D, 0x1D, 0xFF));
219 
220     // Draw scale lines
221     qreal marg = (qreal) w * CTRL_MARGIN;
222     qreal hline = (qreal)h * CTRL_XAXIS_HEGHT;
223     qreal magstart = (qreal) h * CTRL_MAJOR_START;
224     qreal minstart = (qreal) h * CTRL_MINOR_START;
225     qreal hstop = (qreal) w - marg;
226     painter.setPen(QPen(Qt::white, 1, Qt::SolidLine));
227     painter.drawLine(QLineF(marg, hline, hstop, hline));        // top line
228     painter.drawLine(QLineF(marg, hline+8, hstop, hline+8));    // bottom line
229     qreal xpos = marg;
230     for (x = 0; x < 11; x++) {
231         if (x & 1)
232             //minor tics
233             painter.drawLine(QLineF(xpos, minstart, xpos, hline));
234         else
235             painter.drawLine(QLineF(xpos, magstart, xpos, hline));
236         xpos += (hstop-marg) / 10.0;
237     }
238 
239     // draw scale text
240     y = h / 4;
241     m_Font.setPixelSize(y);
242     painter.setFont(m_Font);
243     int rwidth = (int)((hstop - marg) / 5.0);
244     m_Str = "-100";
245     rect.setRect(marg / 2 - 5, 0, rwidth, magstart);
246 
247     for (x = MIN_DB; x <= MAX_DB; x += 20)
248     {
249         m_Str.setNum(x);
250         painter.drawText(rect, Qt::AlignHCenter|Qt::AlignVCenter, m_Str);
251         rect.translate(rwidth, 0);
252     }
253 }
254