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