1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Digital Peak Meter, a custom Qt widget 5# Copyright (C) 2011-2019 Filipe Coelho <falktx@falktx.com> 6# 7# This program is free software; you can redistribute it and/or 8# modify it under the terms of the GNU General Public License as 9# published by the Free Software Foundation; either version 2 of 10# the License, or any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# For a full copy of the GNU General Public License see the doc/GPL.txt file. 18 19# ------------------------------------------------------------------------------------------------------------ 20# Imports (Global) 21 22from math import sqrt 23 24from PyQt5.QtCore import qCritical, Qt, QTimer, QSize 25from PyQt5.QtGui import QColor, QLinearGradient, QPainter, QPen, QPixmap 26from PyQt5.QtWidgets import QWidget 27 28# ------------------------------------------------------------------------------------------------------------ 29# Widget Class 30 31class DigitalPeakMeter(QWidget): 32 # enum Color 33 COLOR_GREEN = 1 34 COLOR_BLUE = 2 35 36 # enum Orientation 37 HORIZONTAL = 1 38 VERTICAL = 2 39 40 # enum Style 41 STYLE_DEFAULT = 1 42 STYLE_OPENAV = 2 43 STYLE_RNCBC = 3 44 STYLE_CALF = 4 45 46 # -------------------------------------------------------------------------------------------------------- 47 48 def __init__(self, parent): 49 QWidget.__init__(self, parent) 50 self.setAttribute(Qt.WA_OpaquePaintEvent) 51 52 # defaults are VERTICAL, COLOR_GREEN, STYLE_DEFAULT 53 54 self.fChannelCount = 0 55 self.fChannelData = [] 56 self.fLastChannelData = [] 57 58 self.fMeterColor = self.COLOR_GREEN 59 self.fMeterColorBase = QColor(93, 231, 61) 60 self.fMeterColorBaseAlt = QColor(15, 110, 15, 100) 61 62 self.fMeterLinesEnabled = True 63 self.fMeterOrientation = self.VERTICAL 64 self.fMeterStyle = self.STYLE_DEFAULT 65 66 self.fMeterBackground = QColor("#070707") 67 self.fMeterGradient = QLinearGradient(0, 0, 0, 0) 68 self.fMeterPixmaps = () 69 70 self.fSmoothMultiplier = 2 71 72 self.updateGrandient() 73 74 # -------------------------------------------------------------------------------------------------------- 75 76 def channelCount(self): 77 return self.fChannelCount 78 79 def setChannelCount(self, count): 80 if self.fChannelCount == count: 81 return 82 83 if count < 0: 84 return qCritical("DigitalPeakMeter::setChannelCount(%i) - channel count must be a positive integer or zero" % count) 85 86 self.fChannelCount = count 87 self.fChannelData = [] 88 self.fLastChannelData = [] 89 90 for x in range(count): 91 self.fChannelData.append(0.0) 92 self.fLastChannelData.append(0.0) 93 94 if self.fMeterStyle == self.STYLE_CALF: 95 if self.fChannelCount > 0: 96 self.setFixedSize(100, 12*self.fChannelCount) 97 else: 98 self.setMinimumSize(0, 0) 99 self.setMaximumSize(9999, 9999) 100 101 # -------------------------------------------------------------------------------------------------------- 102 103 def meterColor(self): 104 return self.fMeterColor 105 106 def setMeterColor(self, color): 107 if self.fMeterColor == color: 108 return 109 110 if color not in (self.COLOR_GREEN, self.COLOR_BLUE): 111 return qCritical("DigitalPeakMeter::setMeterColor(%i) - invalid color" % color) 112 113 if color == self.COLOR_GREEN: 114 self.fMeterColorBase = QColor(93, 231, 61) 115 self.fMeterColorBaseAlt = QColor(15, 110, 15, 100) 116 elif color == self.COLOR_BLUE: 117 self.fMeterColorBase = QColor(82, 238, 248) 118 self.fMeterColorBaseAlt = QColor(15, 15, 110, 100) 119 120 self.fMeterColor = color 121 122 self.updateGrandient() 123 124 # -------------------------------------------------------------------------------------------------------- 125 126 def meterLinesEnabled(self): 127 return self.fMeterLinesEnabled 128 129 def setMeterLinesEnabled(self, yesNo): 130 if self.fMeterLinesEnabled == yesNo: 131 return 132 133 self.fMeterLinesEnabled = yesNo 134 135 # -------------------------------------------------------------------------------------------------------- 136 137 def meterOrientation(self): 138 return self.fMeterOrientation 139 140 def setMeterOrientation(self, orientation): 141 if self.fMeterOrientation == orientation: 142 return 143 144 if orientation not in (self.HORIZONTAL, self.VERTICAL): 145 return qCritical("DigitalPeakMeter::setMeterOrientation(%i) - invalid orientation" % orientation) 146 147 self.fMeterOrientation = orientation 148 149 self.updateGrandient() 150 151 # -------------------------------------------------------------------------------------------------------- 152 153 def meterStyle(self): 154 return self.fMeterStyle 155 156 def setMeterStyle(self, style): 157 if self.fMeterStyle == style: 158 return 159 160 if style not in (self.STYLE_DEFAULT, self.STYLE_OPENAV, self.STYLE_RNCBC, self.STYLE_CALF): 161 return qCritical("DigitalPeakMeter::setMeterStyle(%i) - invalid style" % style) 162 163 if style == self.STYLE_DEFAULT: 164 self.fMeterBackground = QColor("#070707") 165 elif style == self.STYLE_OPENAV: 166 self.fMeterBackground = QColor("#1A1A1A") 167 elif style == self.STYLE_RNCBC: 168 self.fMeterBackground = QColor("#070707") 169 elif style == self.STYLE_CALF: 170 self.fMeterBackground = QColor("#000") 171 172 if style == self.STYLE_CALF: 173 self.fMeterPixmaps = (QPixmap(":/bitmaps/meter_calf_off.png"), QPixmap(":/bitmaps/meter_calf_on.png")) 174 if self.fChannelCount > 0: 175 self.setFixedSize(100, 12*self.fChannelCount) 176 else: 177 self.fMeterPixmaps = () 178 self.setMinimumSize(0, 0) 179 self.setMaximumSize(9999, 9999) 180 181 self.fMeterStyle = style 182 183 self.updateGrandient() 184 185 # -------------------------------------------------------------------------------------------------------- 186 187 def smoothMultiplier(self): 188 return self.fSmoothMultiplier 189 190 def setSmoothMultiplier(self, value): 191 if self.fSmoothMultiplier == value: 192 return 193 194 if not isinstance(value, int): 195 return qCritical("DigitalPeakMeter::setSmoothMultiplier() - value must be an integer") 196 if value < 0: 197 return qCritical("DigitalPeakMeter::setSmoothMultiplier(%i) - value must be >= 0" % value) 198 if value > 5: 199 return qCritical("DigitalPeakMeter::setSmoothMultiplier(%i) - value must be < 5" % value) 200 201 self.fSmoothMultiplier = value 202 203 # -------------------------------------------------------------------------------------------------------- 204 205 def displayMeter(self, meter, level, forced = False): 206 if not isinstance(meter, int): 207 return qCritical("DigitalPeakMeter::displayMeter(,) - meter value must be an integer") 208 if not isinstance(level, float): 209 return qCritical("DigitalPeakMeter::displayMeter(%i,) - level value must be a float" % (meter,)) 210 if meter <= 0 or meter > self.fChannelCount: 211 return qCritical("DigitalPeakMeter::displayMeter(%i, %f) - invalid meter number" % (meter, level)) 212 213 i = meter - 1 214 215 if self.fSmoothMultiplier > 0 and not forced: 216 level = (self.fLastChannelData[i] * float(self.fSmoothMultiplier) + level) / float(self.fSmoothMultiplier + 1) 217 218 if level < 0.001: 219 level = 0.0 220 elif level > 0.999: 221 level = 1.0 222 223 if self.fChannelData[i] != level: 224 self.fChannelData[i] = level 225 self.update() 226 227 self.fLastChannelData[i] = level 228 229 # -------------------------------------------------------------------------------------------------------- 230 231 def updateGrandient(self): 232 self.fMeterGradient = QLinearGradient(0, 0, 1, 1) 233 234 if self.fMeterStyle == self.STYLE_DEFAULT: 235 if self.fMeterOrientation == self.HORIZONTAL: 236 self.fMeterGradient.setColorAt(0.0, self.fMeterColorBase) 237 self.fMeterGradient.setColorAt(0.2, self.fMeterColorBase) 238 self.fMeterGradient.setColorAt(0.4, self.fMeterColorBase) 239 self.fMeterGradient.setColorAt(0.6, self.fMeterColorBase) 240 self.fMeterGradient.setColorAt(0.8, Qt.yellow) 241 self.fMeterGradient.setColorAt(1.0, Qt.red) 242 243 elif self.fMeterOrientation == self.VERTICAL: 244 self.fMeterGradient.setColorAt(0.0, Qt.red) 245 self.fMeterGradient.setColorAt(0.2, Qt.yellow) 246 self.fMeterGradient.setColorAt(0.4, self.fMeterColorBase) 247 self.fMeterGradient.setColorAt(0.6, self.fMeterColorBase) 248 self.fMeterGradient.setColorAt(0.8, self.fMeterColorBase) 249 self.fMeterGradient.setColorAt(1.0, self.fMeterColorBase) 250 251 elif self.fMeterStyle == self.STYLE_RNCBC: 252 if self.fMeterColor == self.COLOR_BLUE: 253 c1 = QColor(40,160,160) 254 c2 = QColor(60,220,160) 255 elif self.fMeterColor == self.COLOR_GREEN: 256 c1 = QColor( 40,160,40) 257 c2 = QColor(160,220,20) 258 259 if self.fMeterOrientation == self.HORIZONTAL: 260 self.fMeterGradient.setColorAt(0.0, c1) 261 self.fMeterGradient.setColorAt(0.2, c1) 262 self.fMeterGradient.setColorAt(0.6, c2) 263 self.fMeterGradient.setColorAt(0.7, QColor(220,220, 20)) 264 self.fMeterGradient.setColorAt(0.8, QColor(240,160, 20)) 265 self.fMeterGradient.setColorAt(0.9, QColor(240, 0, 20)) 266 self.fMeterGradient.setColorAt(1.0, QColor(240, 0, 20)) 267 268 elif self.fMeterOrientation == self.VERTICAL: 269 self.fMeterGradient.setColorAt(0.0, QColor(240, 0, 20)) 270 self.fMeterGradient.setColorAt(0.1, QColor(240, 0, 20)) 271 self.fMeterGradient.setColorAt(0.2, QColor(240,160, 20)) 272 self.fMeterGradient.setColorAt(0.3, QColor(220,220, 20)) 273 self.fMeterGradient.setColorAt(0.4, c2) 274 self.fMeterGradient.setColorAt(0.8, c1) 275 self.fMeterGradient.setColorAt(1.0, c1) 276 277 elif self.fMeterStyle in (self.STYLE_OPENAV, self.STYLE_CALF): 278 self.fMeterGradient.setColorAt(0.0, self.fMeterColorBase) 279 self.fMeterGradient.setColorAt(1.0, self.fMeterColorBase) 280 281 self.updateGrandientFinalStop() 282 283 def updateGrandientFinalStop(self): 284 if self.fMeterOrientation == self.HORIZONTAL: 285 self.fMeterGradient.setFinalStop(self.width(), 0) 286 287 elif self.fMeterOrientation == self.VERTICAL: 288 self.fMeterGradient.setFinalStop(0, self.height()) 289 290 # -------------------------------------------------------------------------------------------------------- 291 292 def minimumSizeHint(self): 293 return QSize(10, 10) 294 295 def sizeHint(self): 296 return QSize(self.width(), self.height()) 297 298 # -------------------------------------------------------------------------------------------------------- 299 300 def drawCalf(self, event): 301 painter = QPainter(self) 302 event.accept() 303 304 # no channels, draw black 305 if self.fChannelCount == 0: 306 painter.setPen(QPen(Qt.black, 2)) 307 painter.setBrush(Qt.black) 308 painter.drawRect(0, 0, self.width(), self.height()) 309 return 310 311 for i in range(self.fChannelCount): 312 painter.drawPixmap(0, 12*i, self.fMeterPixmaps[0]) 313 314 meterPos = 4 315 meterSize = 12 316 317 # draw levels 318 for level in self.fChannelData: 319 if level != 0.0: 320 blevel = int(sqrt(level)*26.0)*3 321 painter.drawPixmap(5, meterPos, blevel, 4, self.fMeterPixmaps[1], 0, 0, blevel, 4) 322 meterPos += meterSize 323 324 def paintEvent(self, event): 325 if self.fMeterStyle == self.STYLE_CALF: 326 return self.drawCalf(event) 327 328 painter = QPainter(self) 329 event.accept() 330 331 width = self.width() 332 height = self.height() 333 334 # draw background 335 painter.setPen(QPen(self.fMeterBackground, 2)) 336 painter.setBrush(self.fMeterBackground) 337 painter.drawRect(0, 0, width, height) 338 339 if self.fChannelCount == 0: 340 return 341 342 meterPad = 0 343 meterPos = 0 344 meterSize = (height if self.fMeterOrientation == self.HORIZONTAL else width)/self.fChannelCount 345 346 # set pen/brush for levels 347 if self.fMeterStyle == self.STYLE_OPENAV: 348 colorTrans = QColor(self.fMeterColorBase) 349 colorTrans.setAlphaF(0.5) 350 painter.setBrush(colorTrans) 351 painter.setPen(QPen(self.fMeterColorBase, 1)) 352 del colorTrans 353 meterPad += 2 354 meterSize -= 2 355 356 else: 357 painter.setPen(QPen(self.fMeterBackground, 0)) 358 painter.setBrush(self.fMeterGradient) 359 360 # draw levels 361 for level in self.fChannelData: 362 if level == 0.0: 363 pass 364 elif self.fMeterOrientation == self.HORIZONTAL: 365 painter.drawRect(0, meterPos, int(sqrt(level) * float(width)), meterSize) 366 elif self.fMeterOrientation == self.VERTICAL: 367 painter.drawRect(meterPos, height - int(sqrt(level) * float(height)), meterSize, height) 368 369 meterPos += meterSize+meterPad 370 371 if not self.fMeterLinesEnabled: 372 return 373 374 # draw lines 375 if self.fMeterOrientation == self.HORIZONTAL: 376 # Variables 377 lsmall = float(width) 378 lfull = float(height - 1) 379 380 if self.fMeterStyle == self.STYLE_OPENAV: 381 painter.setPen(QColor(37, 37, 37, 100)) 382 painter.drawLine(lsmall * 0.25, 2, lsmall * 0.25, lfull-2.0) 383 painter.drawLine(lsmall * 0.50, 2, lsmall * 0.50, lfull-2.0) 384 painter.drawLine(lsmall * 0.75, 2, lsmall * 0.75, lfull-2.0) 385 386 if self.fChannelCount > 1: 387 painter.drawLine(1, lfull/2-1, lsmall-1, lfull/2-1) 388 389 else: 390 # Base 391 painter.setBrush(Qt.black) 392 painter.setPen(QPen(self.fMeterColorBaseAlt, 1)) 393 painter.drawLine(lsmall * 0.25, 2, lsmall * 0.25, lfull-2.0) 394 painter.drawLine(lsmall * 0.50, 2, lsmall * 0.50, lfull-2.0) 395 396 # Yellow 397 painter.setPen(QColor(110, 110, 15, 100)) 398 painter.drawLine(lsmall * 0.70, 2, lsmall * 0.70, lfull-2.0) 399 painter.drawLine(lsmall * 0.83, 2, lsmall * 0.83, lfull-2.0) 400 401 # Orange 402 painter.setPen(QColor(180, 110, 15, 100)) 403 painter.drawLine(lsmall * 0.90, 2, lsmall * 0.90, lfull-2.0) 404 405 # Red 406 painter.setPen(QColor(110, 15, 15, 100)) 407 painter.drawLine(lsmall * 0.96, 2, lsmall * 0.96, lfull-2.0) 408 409 elif self.fMeterOrientation == self.VERTICAL: 410 # Variables 411 lsmall = float(height) 412 lfull = float(width - 1) 413 414 if self.fMeterStyle == self.STYLE_OPENAV: 415 painter.setPen(QColor(37, 37, 37, 100)) 416 painter.drawLine(2, lsmall - (lsmall * 0.25), lfull-2.0, lsmall - (lsmall * 0.25)) 417 painter.drawLine(2, lsmall - (lsmall * 0.50), lfull-2.0, lsmall - (lsmall * 0.50)) 418 painter.drawLine(2, lsmall - (lsmall * 0.75), lfull-2.0, lsmall - (lsmall * 0.75)) 419 420 if self.fChannelCount > 1: 421 painter.drawLine(lfull/2-1, 1, lfull/2-1, lsmall-1) 422 423 else: 424 # Base 425 painter.setBrush(Qt.black) 426 painter.setPen(QPen(self.fMeterColorBaseAlt, 1)) 427 painter.drawLine(2, lsmall - (lsmall * 0.25), lfull-2.0, lsmall - (lsmall * 0.25)) 428 painter.drawLine(2, lsmall - (lsmall * 0.50), lfull-2.0, lsmall - (lsmall * 0.50)) 429 430 # Yellow 431 painter.setPen(QColor(110, 110, 15, 100)) 432 painter.drawLine(2, lsmall - (lsmall * 0.70), lfull-2.0, lsmall - (lsmall * 0.70)) 433 painter.drawLine(2, lsmall - (lsmall * 0.82), lfull-2.0, lsmall - (lsmall * 0.82)) 434 435 # Orange 436 painter.setPen(QColor(180, 110, 15, 100)) 437 painter.drawLine(2, lsmall - (lsmall * 0.90), lfull-2.0, lsmall - (lsmall * 0.90)) 438 439 # Red 440 painter.setPen(QColor(110, 15, 15, 100)) 441 painter.drawLine(2, lsmall - (lsmall * 0.96), lfull-2.0, lsmall - (lsmall * 0.96)) 442 443 # -------------------------------------------------------------------------------------------------------- 444 445 def resizeEvent(self, event): 446 QWidget.resizeEvent(self, event) 447 self.updateGrandientFinalStop() 448 449# ------------------------------------------------------------------------------------------------------------ 450# Main Testing 451 452if __name__ == '__main__': 453 import sys 454 import resources_rc 455 from PyQt5.QtWidgets import QApplication 456 457 app = QApplication(sys.argv) 458 459 gui = DigitalPeakMeter(None) 460 gui.setChannelCount(2) 461 #gui.setMeterOrientation(DigitalPeakMeter.HORIZONTAL) 462 gui.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC) 463 gui.displayMeter(1, 0.5) 464 gui.displayMeter(2, 0.8) 465 gui.show() 466 467 sys.exit(app.exec_()) 468 469# ------------------------------------------------------------------------------------------------------------ 470