2 # SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # ---------------------------------------------------------------------------------------------------------------------
10 from qt_compat
import qt_config
13 from PyQt5
.QtCore
import qCritical
, Qt
, QSize
, QLineF
, QRectF
14 from PyQt5
.QtGui
import QColor
, QLinearGradient
, QPainter
, QPen
, QPixmap
15 from PyQt5
.QtWidgets
import QWidget
17 from PyQt6
.QtCore
import qCritical
, Qt
, QSize
, QLineF
, QRectF
18 from PyQt6
.QtGui
import QColor
, QLinearGradient
, QPainter
, QPen
, QPixmap
19 from PyQt6
.QtWidgets
import QWidget
21 # ---------------------------------------------------------------------------------------------------------------------
24 class DigitalPeakMeter(QWidget
):
39 # -----------------------------------------------------------------------------------------------------------------
41 def __init__(self
, parent
):
42 QWidget
.__init
__(self
, parent
)
43 self
.setAttribute(Qt
.WA_OpaquePaintEvent
)
45 # defaults are VERTICAL, COLOR_GREEN, STYLE_DEFAULT
47 self
.fChannelCount
= 0
48 self
.fChannelData
= []
49 self
.fLastChannelData
= []
51 self
.fMeterColor
= self
.COLOR_GREEN
52 self
.fMeterColorBase
= QColor(93, 231, 61)
53 self
.fMeterColorBaseAlt
= QColor(15, 110, 15, 100)
55 self
.fMeterLinesEnabled
= True
56 self
.fMeterOrientation
= self
.VERTICAL
57 self
.fMeterStyle
= self
.STYLE_DEFAULT
59 self
.fMeterBackground
= QColor("#070707")
60 self
.fMeterGradient
= QLinearGradient(0, 0, 0, 0)
61 self
.fMeterPixmaps
= ()
63 self
.fSmoothMultiplier
= 2
65 self
.updateGrandient()
67 # -----------------------------------------------------------------------------------------------------------------
69 def channelCount(self
):
70 return self
.fChannelCount
72 def setChannelCount(self
, count
):
73 if self
.fChannelCount
== count
:
77 qCritical(f
"DigitalPeakMeter::setChannelCount({count}) - channel count must be a positive integer or zero")
80 self
.fChannelCount
= count
81 self
.fChannelData
= []
82 self
.fLastChannelData
= []
84 for _
in range(count
):
85 self
.fChannelData
.append(0.0)
86 self
.fLastChannelData
.append(0.0)
88 if self
.fMeterStyle
== self
.STYLE_CALF
:
89 if self
.fChannelCount
> 0:
90 self
.setFixedSize(100, 12*self
.fChannelCount
)
92 self
.setMinimumSize(0, 0)
93 self
.setMaximumSize(9999, 9999)
95 # -----------------------------------------------------------------------------------------------------------------
98 return self
.fMeterColor
100 def setMeterColor(self
, color
):
101 if self
.fMeterColor
== color
:
104 if color
not in (self
.COLOR_GREEN
, self
.COLOR_BLUE
):
105 qCritical(f
"DigitalPeakMeter::setMeterColor({color}) - invalid color")
108 if color
== self
.COLOR_GREEN
:
109 self
.fMeterColorBase
= QColor(93, 231, 61)
110 self
.fMeterColorBaseAlt
= QColor(15, 110, 15, 100)
111 elif color
== self
.COLOR_BLUE
:
112 self
.fMeterColorBase
= QColor(82, 238, 248)
113 self
.fMeterColorBaseAlt
= QColor(15, 15, 110, 100)
115 self
.fMeterColor
= color
117 self
.updateGrandient()
119 # -----------------------------------------------------------------------------------------------------------------
121 def meterLinesEnabled(self
):
122 return self
.fMeterLinesEnabled
124 def setMeterLinesEnabled(self
, yesNo
):
125 if self
.fMeterLinesEnabled
== yesNo
:
128 self
.fMeterLinesEnabled
= yesNo
130 # -----------------------------------------------------------------------------------------------------------------
132 def meterOrientation(self
):
133 return self
.fMeterOrientation
135 def setMeterOrientation(self
, orientation
):
136 if self
.fMeterOrientation
== orientation
:
139 if orientation
not in (self
.HORIZONTAL
, self
.VERTICAL
):
140 qCritical(f
"DigitalPeakMeter::setMeterOrientation({orientation}) - invalid orientation")
143 self
.fMeterOrientation
= orientation
145 self
.updateGrandient()
147 # -----------------------------------------------------------------------------------------------------------------
149 def meterStyle(self
):
150 return self
.fMeterStyle
152 def setMeterStyle(self
, style
):
153 if self
.fMeterStyle
== style
:
156 if style
not in (self
.STYLE_DEFAULT
, self
.STYLE_OPENAV
, self
.STYLE_RNCBC
, self
.STYLE_CALF
):
157 qCritical(f
"DigitalPeakMeter::setMeterStyle({style}) - invalid style")
160 if style
== self
.STYLE_DEFAULT
:
161 self
.fMeterBackground
= QColor("#070707")
162 elif style
== self
.STYLE_OPENAV
:
163 self
.fMeterBackground
= QColor("#1A1A1A")
164 elif style
== self
.STYLE_RNCBC
:
165 self
.fMeterBackground
= QColor("#070707")
166 elif style
== self
.STYLE_CALF
:
167 self
.fMeterBackground
= QColor("#000")
169 if style
== self
.STYLE_CALF
:
170 self
.fMeterPixmaps
= (QPixmap(":/bitmaps/meter_calf_off.png"), QPixmap(":/bitmaps/meter_calf_on.png"))
171 if self
.fChannelCount
> 0:
172 self
.setFixedSize(100, 12*self
.fChannelCount
)
174 self
.fMeterPixmaps
= ()
175 self
.setMinimumSize(0, 0)
176 self
.setMaximumSize(9999, 9999)
178 self
.fMeterStyle
= style
180 self
.updateGrandient()
182 # -----------------------------------------------------------------------------------------------------------------
184 def smoothMultiplier(self
):
185 return self
.fSmoothMultiplier
187 def setSmoothMultiplier(self
, value
):
188 if self
.fSmoothMultiplier
== value
:
191 if not isinstance(value
, int):
192 qCritical("DigitalPeakMeter::setSmoothMultiplier() - value must be an integer")
195 qCritical(f
"DigitalPeakMeter::setSmoothMultiplier({value}) - value must be >= 0")
198 qCritical(f
"DigitalPeakMeter::setSmoothMultiplier({value}) - value must be < 5")
201 self
.fSmoothMultiplier
= value
203 # -----------------------------------------------------------------------------------------------------------------
205 def displayMeter(self
, meter
, level
, forced
= False):
206 if not isinstance(meter
, int):
207 qCritical("DigitalPeakMeter::displayMeter(,) - meter value must be an integer")
209 if not isinstance(level
, float):
210 qCritical(f
"DigitalPeakMeter::displayMeter({meter},) - level value must be a float")
212 if meter
<= 0 or meter
> self
.fChannelCount
:
213 qCritical(f
"DigitalPeakMeter::displayMeter({meter}, {level}) - invalid meter number")
218 if self
.fSmoothMultiplier
> 0 and not forced
:
220 (self
.fLastChannelData
[i
] * float(self
.fSmoothMultiplier
) + level
)
221 / float(self
.fSmoothMultiplier
+ 1)
229 if self
.fChannelData
[i
] != level
:
230 self
.fChannelData
[i
] = level
233 self
.fLastChannelData
[i
] = level
235 # -----------------------------------------------------------------------------------------------------------------
237 def updateGrandient(self
):
238 self
.fMeterGradient
= QLinearGradient(0, 0, 1, 1)
240 if self
.fMeterStyle
== self
.STYLE_DEFAULT
:
241 if self
.fMeterOrientation
== self
.HORIZONTAL
:
242 self
.fMeterGradient
.setColorAt(0.0, self
.fMeterColorBase
)
243 self
.fMeterGradient
.setColorAt(0.2, self
.fMeterColorBase
)
244 self
.fMeterGradient
.setColorAt(0.4, self
.fMeterColorBase
)
245 self
.fMeterGradient
.setColorAt(0.6, self
.fMeterColorBase
)
246 self
.fMeterGradient
.setColorAt(0.8, Qt
.yellow
)
247 self
.fMeterGradient
.setColorAt(1.0, Qt
.red
)
249 elif self
.fMeterOrientation
== self
.VERTICAL
:
250 self
.fMeterGradient
.setColorAt(0.0, Qt
.red
)
251 self
.fMeterGradient
.setColorAt(0.2, Qt
.yellow
)
252 self
.fMeterGradient
.setColorAt(0.4, self
.fMeterColorBase
)
253 self
.fMeterGradient
.setColorAt(0.6, self
.fMeterColorBase
)
254 self
.fMeterGradient
.setColorAt(0.8, self
.fMeterColorBase
)
255 self
.fMeterGradient
.setColorAt(1.0, self
.fMeterColorBase
)
257 elif self
.fMeterStyle
== self
.STYLE_RNCBC
:
258 if self
.fMeterColor
== self
.COLOR_BLUE
:
259 c1
= QColor(40,160,160)
260 c2
= QColor(60,220,160)
261 elif self
.fMeterColor
== self
.COLOR_GREEN
:
262 c1
= QColor( 40,160,40)
263 c2
= QColor(160,220,20)
265 if self
.fMeterOrientation
== self
.HORIZONTAL
:
266 self
.fMeterGradient
.setColorAt(0.0, c1
)
267 self
.fMeterGradient
.setColorAt(0.2, c1
)
268 self
.fMeterGradient
.setColorAt(0.6, c2
)
269 self
.fMeterGradient
.setColorAt(0.7, QColor(220,220, 20))
270 self
.fMeterGradient
.setColorAt(0.8, QColor(240,160, 20))
271 self
.fMeterGradient
.setColorAt(0.9, QColor(240, 0, 20))
272 self
.fMeterGradient
.setColorAt(1.0, QColor(240, 0, 20))
274 elif self
.fMeterOrientation
== self
.VERTICAL
:
275 self
.fMeterGradient
.setColorAt(0.0, QColor(240, 0, 20))
276 self
.fMeterGradient
.setColorAt(0.1, QColor(240, 0, 20))
277 self
.fMeterGradient
.setColorAt(0.2, QColor(240,160, 20))
278 self
.fMeterGradient
.setColorAt(0.3, QColor(220,220, 20))
279 self
.fMeterGradient
.setColorAt(0.4, c2
)
280 self
.fMeterGradient
.setColorAt(0.8, c1
)
281 self
.fMeterGradient
.setColorAt(1.0, c1
)
283 elif self
.fMeterStyle
in (self
.STYLE_OPENAV
, self
.STYLE_CALF
):
284 self
.fMeterGradient
.setColorAt(0.0, self
.fMeterColorBase
)
285 self
.fMeterGradient
.setColorAt(1.0, self
.fMeterColorBase
)
287 self
.updateGrandientFinalStop()
289 def updateGrandientFinalStop(self
):
290 if self
.fMeterOrientation
== self
.HORIZONTAL
:
291 self
.fMeterGradient
.setFinalStop(self
.width(), 0)
293 elif self
.fMeterOrientation
== self
.VERTICAL
:
294 self
.fMeterGradient
.setFinalStop(0, self
.height())
296 # -----------------------------------------------------------------------------------------------------------------
298 def minimumSizeHint(self
):
299 return QSize(20, 10) if self
.fMeterOrientation
== self
.HORIZONTAL
else QSize(10, 20)
302 return QSize(self
.width(), self
.height())
304 # -----------------------------------------------------------------------------------------------------------------
306 def drawCalf(self
, event
):
307 painter
= QPainter(self
)
310 # no channels, draw black
311 if self
.fChannelCount
== 0:
312 painter
.setPen(QPen(Qt
.black
, 2))
313 painter
.setBrush(Qt
.black
)
314 painter
.drawRect(0, 0, self
.width(), self
.height())
317 for i
in range(self
.fChannelCount
):
318 painter
.drawPixmap(0, 12*i
, self
.fMeterPixmaps
[0])
324 for level
in self
.fChannelData
:
326 blevel
= int(sqrt(level
)*26.0)*3
327 painter
.drawPixmap(5, meterPos
, blevel
, 4, self
.fMeterPixmaps
[1], 0, 0, blevel
, 4)
328 meterPos
+= meterSize
330 def paintEvent(self
, event
):
331 if self
.fMeterStyle
== self
.STYLE_CALF
:
335 painter
= QPainter(self
)
339 height
= self
.height()
342 painter
.setPen(QPen(self
.fMeterBackground
, 2))
343 painter
.setBrush(self
.fMeterBackground
)
344 painter
.drawRect(0, 0, width
, height
)
346 if self
.fChannelCount
== 0:
351 meterSize
= (height
if self
.fMeterOrientation
== self
.HORIZONTAL
else width
)/self
.fChannelCount
353 # set pen/brush for levels
354 if self
.fMeterStyle
== self
.STYLE_OPENAV
:
355 colorTrans
= QColor(self
.fMeterColorBase
)
356 colorTrans
.setAlphaF(0.5)
357 painter
.setBrush(colorTrans
)
358 painter
.setPen(QPen(self
.fMeterColorBase
, 1))
364 painter
.setPen(QPen(self
.fMeterBackground
, 0))
365 painter
.setBrush(self
.fMeterGradient
)
368 for level
in self
.fChannelData
:
371 elif self
.fMeterOrientation
== self
.HORIZONTAL
:
372 painter
.drawRect(QRectF(0, meterPos
, sqrt(level
) * float(width
), meterSize
))
373 elif self
.fMeterOrientation
== self
.VERTICAL
:
374 painter
.drawRect(QRectF(meterPos
, height
- sqrt(level
) * float(height
), meterSize
, height
))
376 meterPos
+= meterSize
+meterPad
378 if not self
.fMeterLinesEnabled
:
382 if self
.fMeterOrientation
== self
.HORIZONTAL
:
384 lsmall
= float(width
)
385 lfull
= float(height
- 1)
387 if self
.fMeterStyle
== self
.STYLE_OPENAV
:
388 painter
.setPen(QColor(37, 37, 37, 100))
389 painter
.drawLine(QLineF(lsmall
* 0.25, 2, lsmall
* 0.25, lfull
-2.0))
390 painter
.drawLine(QLineF(lsmall
* 0.50, 2, lsmall
* 0.50, lfull
-2.0))
391 painter
.drawLine(QLineF(lsmall
* 0.75, 2, lsmall
* 0.75, lfull
-2.0))
393 if self
.fChannelCount
> 1:
394 painter
.drawLine(QLineF(1, lfull
/2-1, lsmall
-1, lfull
/2-1))
398 painter
.setBrush(Qt
.black
)
399 painter
.setPen(QPen(self
.fMeterColorBaseAlt
, 1))
400 painter
.drawLine(QLineF(lsmall
* 0.25, 2, lsmall
* 0.25, lfull
-2.0))
401 painter
.drawLine(QLineF(lsmall
* 0.50, 2, lsmall
* 0.50, lfull
-2.0))
404 painter
.setPen(QColor(110, 110, 15, 100))
405 painter
.drawLine(QLineF(lsmall
* 0.70, 2, lsmall
* 0.70, lfull
-2.0))
406 painter
.drawLine(QLineF(lsmall
* 0.83, 2, lsmall
* 0.83, lfull
-2.0))
409 painter
.setPen(QColor(180, 110, 15, 100))
410 painter
.drawLine(QLineF(lsmall
* 0.90, 2, lsmall
* 0.90, lfull
-2.0))
413 painter
.setPen(QColor(110, 15, 15, 100))
414 painter
.drawLine(QLineF(lsmall
* 0.96, 2, lsmall
* 0.96, lfull
-2.0))
416 elif self
.fMeterOrientation
== self
.VERTICAL
:
418 lsmall
= float(height
)
419 lfull
= float(width
- 1)
421 if self
.fMeterStyle
== self
.STYLE_OPENAV
:
422 painter
.setPen(QColor(37, 37, 37, 100))
423 painter
.drawLine(QLineF(2, lsmall
- (lsmall
* 0.25), lfull
-2.0, lsmall
- (lsmall
* 0.25)))
424 painter
.drawLine(QLineF(2, lsmall
- (lsmall
* 0.50), lfull
-2.0, lsmall
- (lsmall
* 0.50)))
425 painter
.drawLine(QLineF(2, lsmall
- (lsmall
* 0.75), lfull
-2.0, lsmall
- (lsmall
* 0.75)))
427 if self
.fChannelCount
> 1:
428 painter
.drawLine(QLineF(lfull
/2-1, 1, lfull
/2-1, lsmall
-1))
432 painter
.setBrush(Qt
.black
)
433 painter
.setPen(QPen(self
.fMeterColorBaseAlt
, 1))
434 painter
.drawLine(QLineF(2, lsmall
- (lsmall
* 0.25), lfull
-2.0, lsmall
- (lsmall
* 0.25)))
435 painter
.drawLine(QLineF(2, lsmall
- (lsmall
* 0.50), lfull
-2.0, lsmall
- (lsmall
* 0.50)))
438 painter
.setPen(QColor(110, 110, 15, 100))
439 painter
.drawLine(QLineF(2, lsmall
- (lsmall
* 0.70), lfull
-2.0, lsmall
- (lsmall
* 0.70)))
440 painter
.drawLine(QLineF(2, lsmall
- (lsmall
* 0.82), lfull
-2.0, lsmall
- (lsmall
* 0.82)))
443 painter
.setPen(QColor(180, 110, 15, 100))
444 painter
.drawLine(QLineF(2, lsmall
- (lsmall
* 0.90), lfull
-2.0, lsmall
- (lsmall
* 0.90)))
447 painter
.setPen(QColor(110, 15, 15, 100))
448 painter
.drawLine(QLineF(2, lsmall
- (lsmall
* 0.96), lfull
-2.0, lsmall
- (lsmall
* 0.96)))
450 # -----------------------------------------------------------------------------------------------------------------
452 def resizeEvent(self
, event
):
453 QWidget
.resizeEvent(self
, event
)
454 self
.updateGrandientFinalStop()
456 # ---------------------------------------------------------------------------------------------------------------------