Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

digitalpeakmeter.py 18KB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
11 years ago
9 years ago
11 years ago
9 years ago
9 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
11 years ago
11 years ago
9 years ago
9 years ago
9 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
9 years ago
9 years ago
9 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
11 years ago
9 years ago
9 years ago
9 years ago
11 years ago
9 years ago
9 years ago
11 years ago
9 years ago
11 years ago
9 years ago
11 years ago
9 years ago
11 years ago
11 years ago
11 years ago
9 years ago
11 years ago
9 years ago
11 years ago
9 years ago
9 years ago
11 years ago
11 years ago
11 years ago
9 years ago
9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Digital Peak Meter, a custom Qt widget
  4. # Copyright (C) 2011-2019 Filipe Coelho <falktx@falktx.com>
  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 as
  8. # published by the Free Software Foundation; either version 2 of
  9. # the License, or 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. # For a full copy of the GNU General Public License see the doc/GPL.txt file.
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. from math import sqrt
  20. from PyQt5.QtCore import qCritical, Qt, QTimer, QSize, QLineF, QRectF
  21. from PyQt5.QtGui import QColor, QLinearGradient, QPainter, QPen, QPixmap
  22. from PyQt5.QtWidgets import QWidget
  23. # ------------------------------------------------------------------------------------------------------------
  24. # Widget Class
  25. class DigitalPeakMeter(QWidget):
  26. # enum Color
  27. COLOR_GREEN = 1
  28. COLOR_BLUE = 2
  29. # enum Orientation
  30. HORIZONTAL = 1
  31. VERTICAL = 2
  32. # enum Style
  33. STYLE_DEFAULT = 1
  34. STYLE_OPENAV = 2
  35. STYLE_RNCBC = 3
  36. STYLE_CALF = 4
  37. # --------------------------------------------------------------------------------------------------------
  38. def __init__(self, parent):
  39. QWidget.__init__(self, parent)
  40. self.setAttribute(Qt.WA_OpaquePaintEvent)
  41. # defaults are VERTICAL, COLOR_GREEN, STYLE_DEFAULT
  42. self.fChannelCount = 0
  43. self.fChannelData = []
  44. self.fLastChannelData = []
  45. self.fMeterColor = self.COLOR_GREEN
  46. self.fMeterColorBase = QColor(93, 231, 61)
  47. self.fMeterColorBaseAlt = QColor(15, 110, 15, 100)
  48. self.fMeterLinesEnabled = True
  49. self.fMeterOrientation = self.VERTICAL
  50. self.fMeterStyle = self.STYLE_DEFAULT
  51. self.fMeterBackground = QColor("#070707")
  52. self.fMeterGradient = QLinearGradient(0, 0, 0, 0)
  53. self.fMeterPixmaps = ()
  54. self.fSmoothMultiplier = 2
  55. self.updateGrandient()
  56. # --------------------------------------------------------------------------------------------------------
  57. def channelCount(self):
  58. return self.fChannelCount
  59. def setChannelCount(self, count):
  60. if self.fChannelCount == count:
  61. return
  62. if count < 0:
  63. return qCritical("DigitalPeakMeter::setChannelCount(%i) - channel count must be a positive integer or zero" % count)
  64. self.fChannelCount = count
  65. self.fChannelData = []
  66. self.fLastChannelData = []
  67. for x in range(count):
  68. self.fChannelData.append(0.0)
  69. self.fLastChannelData.append(0.0)
  70. if self.fMeterStyle == self.STYLE_CALF:
  71. if self.fChannelCount > 0:
  72. self.setFixedSize(100, 12*self.fChannelCount)
  73. else:
  74. self.setMinimumSize(0, 0)
  75. self.setMaximumSize(9999, 9999)
  76. # --------------------------------------------------------------------------------------------------------
  77. def meterColor(self):
  78. return self.fMeterColor
  79. def setMeterColor(self, color):
  80. if self.fMeterColor == color:
  81. return
  82. if color not in (self.COLOR_GREEN, self.COLOR_BLUE):
  83. return qCritical("DigitalPeakMeter::setMeterColor(%i) - invalid color" % color)
  84. if color == self.COLOR_GREEN:
  85. self.fMeterColorBase = QColor(93, 231, 61)
  86. self.fMeterColorBaseAlt = QColor(15, 110, 15, 100)
  87. elif color == self.COLOR_BLUE:
  88. self.fMeterColorBase = QColor(82, 238, 248)
  89. self.fMeterColorBaseAlt = QColor(15, 15, 110, 100)
  90. self.fMeterColor = color
  91. self.updateGrandient()
  92. # --------------------------------------------------------------------------------------------------------
  93. def meterLinesEnabled(self):
  94. return self.fMeterLinesEnabled
  95. def setMeterLinesEnabled(self, yesNo):
  96. if self.fMeterLinesEnabled == yesNo:
  97. return
  98. self.fMeterLinesEnabled = yesNo
  99. # --------------------------------------------------------------------------------------------------------
  100. def meterOrientation(self):
  101. return self.fMeterOrientation
  102. def setMeterOrientation(self, orientation):
  103. if self.fMeterOrientation == orientation:
  104. return
  105. if orientation not in (self.HORIZONTAL, self.VERTICAL):
  106. return qCritical("DigitalPeakMeter::setMeterOrientation(%i) - invalid orientation" % orientation)
  107. self.fMeterOrientation = orientation
  108. self.updateGrandient()
  109. # --------------------------------------------------------------------------------------------------------
  110. def meterStyle(self):
  111. return self.fMeterStyle
  112. def setMeterStyle(self, style):
  113. if self.fMeterStyle == style:
  114. return
  115. if style not in (self.STYLE_DEFAULT, self.STYLE_OPENAV, self.STYLE_RNCBC, self.STYLE_CALF):
  116. return qCritical("DigitalPeakMeter::setMeterStyle(%i) - invalid style" % style)
  117. if style == self.STYLE_DEFAULT:
  118. self.fMeterBackground = QColor("#070707")
  119. elif style == self.STYLE_OPENAV:
  120. self.fMeterBackground = QColor("#1A1A1A")
  121. elif style == self.STYLE_RNCBC:
  122. self.fMeterBackground = QColor("#070707")
  123. elif style == self.STYLE_CALF:
  124. self.fMeterBackground = QColor("#000")
  125. if style == self.STYLE_CALF:
  126. self.fMeterPixmaps = (QPixmap(":/bitmaps/meter_calf_off.png"), QPixmap(":/bitmaps/meter_calf_on.png"))
  127. if self.fChannelCount > 0:
  128. self.setFixedSize(100, 12*self.fChannelCount)
  129. else:
  130. self.fMeterPixmaps = ()
  131. self.setMinimumSize(0, 0)
  132. self.setMaximumSize(9999, 9999)
  133. self.fMeterStyle = style
  134. self.updateGrandient()
  135. # --------------------------------------------------------------------------------------------------------
  136. def smoothMultiplier(self):
  137. return self.fSmoothMultiplier
  138. def setSmoothMultiplier(self, value):
  139. if self.fSmoothMultiplier == value:
  140. return
  141. if not isinstance(value, int):
  142. return qCritical("DigitalPeakMeter::setSmoothMultiplier() - value must be an integer")
  143. if value < 0:
  144. return qCritical("DigitalPeakMeter::setSmoothMultiplier(%i) - value must be >= 0" % value)
  145. if value > 5:
  146. return qCritical("DigitalPeakMeter::setSmoothMultiplier(%i) - value must be < 5" % value)
  147. self.fSmoothMultiplier = value
  148. # --------------------------------------------------------------------------------------------------------
  149. def displayMeter(self, meter, level, forced = False):
  150. if not isinstance(meter, int):
  151. return qCritical("DigitalPeakMeter::displayMeter(,) - meter value must be an integer")
  152. if not isinstance(level, float):
  153. return qCritical("DigitalPeakMeter::displayMeter(%i,) - level value must be a float" % (meter,))
  154. if meter <= 0 or meter > self.fChannelCount:
  155. return qCritical("DigitalPeakMeter::displayMeter(%i, %f) - invalid meter number" % (meter, level))
  156. i = meter - 1
  157. if self.fSmoothMultiplier > 0 and not forced:
  158. level = (self.fLastChannelData[i] * float(self.fSmoothMultiplier) + level) / float(self.fSmoothMultiplier + 1)
  159. if level < 0.001:
  160. level = 0.0
  161. elif level > 0.999:
  162. level = 1.0
  163. if self.fChannelData[i] != level:
  164. self.fChannelData[i] = level
  165. self.update()
  166. self.fLastChannelData[i] = level
  167. # --------------------------------------------------------------------------------------------------------
  168. def updateGrandient(self):
  169. self.fMeterGradient = QLinearGradient(0, 0, 1, 1)
  170. if self.fMeterStyle == self.STYLE_DEFAULT:
  171. if self.fMeterOrientation == self.HORIZONTAL:
  172. self.fMeterGradient.setColorAt(0.0, self.fMeterColorBase)
  173. self.fMeterGradient.setColorAt(0.2, self.fMeterColorBase)
  174. self.fMeterGradient.setColorAt(0.4, self.fMeterColorBase)
  175. self.fMeterGradient.setColorAt(0.6, self.fMeterColorBase)
  176. self.fMeterGradient.setColorAt(0.8, Qt.yellow)
  177. self.fMeterGradient.setColorAt(1.0, Qt.red)
  178. elif self.fMeterOrientation == self.VERTICAL:
  179. self.fMeterGradient.setColorAt(0.0, Qt.red)
  180. self.fMeterGradient.setColorAt(0.2, Qt.yellow)
  181. self.fMeterGradient.setColorAt(0.4, self.fMeterColorBase)
  182. self.fMeterGradient.setColorAt(0.6, self.fMeterColorBase)
  183. self.fMeterGradient.setColorAt(0.8, self.fMeterColorBase)
  184. self.fMeterGradient.setColorAt(1.0, self.fMeterColorBase)
  185. elif self.fMeterStyle == self.STYLE_RNCBC:
  186. if self.fMeterColor == self.COLOR_BLUE:
  187. c1 = QColor(40,160,160)
  188. c2 = QColor(60,220,160)
  189. elif self.fMeterColor == self.COLOR_GREEN:
  190. c1 = QColor( 40,160,40)
  191. c2 = QColor(160,220,20)
  192. if self.fMeterOrientation == self.HORIZONTAL:
  193. self.fMeterGradient.setColorAt(0.0, c1)
  194. self.fMeterGradient.setColorAt(0.2, c1)
  195. self.fMeterGradient.setColorAt(0.6, c2)
  196. self.fMeterGradient.setColorAt(0.7, QColor(220,220, 20))
  197. self.fMeterGradient.setColorAt(0.8, QColor(240,160, 20))
  198. self.fMeterGradient.setColorAt(0.9, QColor(240, 0, 20))
  199. self.fMeterGradient.setColorAt(1.0, QColor(240, 0, 20))
  200. elif self.fMeterOrientation == self.VERTICAL:
  201. self.fMeterGradient.setColorAt(0.0, QColor(240, 0, 20))
  202. self.fMeterGradient.setColorAt(0.1, QColor(240, 0, 20))
  203. self.fMeterGradient.setColorAt(0.2, QColor(240,160, 20))
  204. self.fMeterGradient.setColorAt(0.3, QColor(220,220, 20))
  205. self.fMeterGradient.setColorAt(0.4, c2)
  206. self.fMeterGradient.setColorAt(0.8, c1)
  207. self.fMeterGradient.setColorAt(1.0, c1)
  208. elif self.fMeterStyle in (self.STYLE_OPENAV, self.STYLE_CALF):
  209. self.fMeterGradient.setColorAt(0.0, self.fMeterColorBase)
  210. self.fMeterGradient.setColorAt(1.0, self.fMeterColorBase)
  211. self.updateGrandientFinalStop()
  212. def updateGrandientFinalStop(self):
  213. if self.fMeterOrientation == self.HORIZONTAL:
  214. self.fMeterGradient.setFinalStop(self.width(), 0)
  215. elif self.fMeterOrientation == self.VERTICAL:
  216. self.fMeterGradient.setFinalStop(0, self.height())
  217. # --------------------------------------------------------------------------------------------------------
  218. def minimumSizeHint(self):
  219. return QSize(10, 10)
  220. def sizeHint(self):
  221. return QSize(self.width(), self.height())
  222. # --------------------------------------------------------------------------------------------------------
  223. def drawCalf(self, event):
  224. painter = QPainter(self)
  225. event.accept()
  226. # no channels, draw black
  227. if self.fChannelCount == 0:
  228. painter.setPen(QPen(Qt.black, 2))
  229. painter.setBrush(Qt.black)
  230. painter.drawRect(0, 0, self.width(), self.height())
  231. return
  232. for i in range(self.fChannelCount):
  233. painter.drawPixmap(0, 12*i, self.fMeterPixmaps[0])
  234. meterPos = 4
  235. meterSize = 12
  236. # draw levels
  237. for level in self.fChannelData:
  238. if level != 0.0:
  239. blevel = int(sqrt(level)*26.0)*3
  240. painter.drawPixmap(5, meterPos, blevel, 4, self.fMeterPixmaps[1], 0, 0, blevel, 4)
  241. meterPos += meterSize
  242. def paintEvent(self, event):
  243. if self.fMeterStyle == self.STYLE_CALF:
  244. return self.drawCalf(event)
  245. painter = QPainter(self)
  246. event.accept()
  247. width = self.width()
  248. height = self.height()
  249. # draw background
  250. painter.setPen(QPen(self.fMeterBackground, 2))
  251. painter.setBrush(self.fMeterBackground)
  252. painter.drawRect(0, 0, width, height)
  253. if self.fChannelCount == 0:
  254. return
  255. meterPad = 0
  256. meterPos = 0
  257. meterSize = (height if self.fMeterOrientation == self.HORIZONTAL else width)/self.fChannelCount
  258. # set pen/brush for levels
  259. if self.fMeterStyle == self.STYLE_OPENAV:
  260. colorTrans = QColor(self.fMeterColorBase)
  261. colorTrans.setAlphaF(0.5)
  262. painter.setBrush(colorTrans)
  263. painter.setPen(QPen(self.fMeterColorBase, 1))
  264. del colorTrans
  265. meterPad += 2
  266. meterSize -= 2
  267. else:
  268. painter.setPen(QPen(self.fMeterBackground, 0))
  269. painter.setBrush(self.fMeterGradient)
  270. # draw levels
  271. for level in self.fChannelData:
  272. if level == 0.0:
  273. pass
  274. elif self.fMeterOrientation == self.HORIZONTAL:
  275. painter.drawRect(QRectF(0, meterPos, sqrt(level) * float(width), meterSize))
  276. elif self.fMeterOrientation == self.VERTICAL:
  277. painter.drawRect(QRectF(meterPos, height - sqrt(level) * float(height), meterSize, height))
  278. meterPos += meterSize+meterPad
  279. if not self.fMeterLinesEnabled:
  280. return
  281. # draw lines
  282. if self.fMeterOrientation == self.HORIZONTAL:
  283. # Variables
  284. lsmall = float(width)
  285. lfull = float(height - 1)
  286. if self.fMeterStyle == self.STYLE_OPENAV:
  287. painter.setPen(QColor(37, 37, 37, 100))
  288. painter.drawLine(QLineF(lsmall * 0.25, 2, lsmall * 0.25, lfull-2.0))
  289. painter.drawLine(QLineF(lsmall * 0.50, 2, lsmall * 0.50, lfull-2.0))
  290. painter.drawLine(QLineF(lsmall * 0.75, 2, lsmall * 0.75, lfull-2.0))
  291. if self.fChannelCount > 1:
  292. painter.drawLine(QLineF(1, lfull/2-1, lsmall-1, lfull/2-1))
  293. else:
  294. # Base
  295. painter.setBrush(Qt.black)
  296. painter.setPen(QPen(self.fMeterColorBaseAlt, 1))
  297. painter.drawLine(QLineF(lsmall * 0.25, 2, lsmall * 0.25, lfull-2.0))
  298. painter.drawLine(QLineF(lsmall * 0.50, 2, lsmall * 0.50, lfull-2.0))
  299. # Yellow
  300. painter.setPen(QColor(110, 110, 15, 100))
  301. painter.drawLine(QLineF(lsmall * 0.70, 2, lsmall * 0.70, lfull-2.0))
  302. painter.drawLine(QLineF(lsmall * 0.83, 2, lsmall * 0.83, lfull-2.0))
  303. # Orange
  304. painter.setPen(QColor(180, 110, 15, 100))
  305. painter.drawLine(QLineF(lsmall * 0.90, 2, lsmall * 0.90, lfull-2.0))
  306. # Red
  307. painter.setPen(QColor(110, 15, 15, 100))
  308. painter.drawLine(QLineF(lsmall * 0.96, 2, lsmall * 0.96, lfull-2.0))
  309. elif self.fMeterOrientation == self.VERTICAL:
  310. # Variables
  311. lsmall = float(height)
  312. lfull = float(width - 1)
  313. if self.fMeterStyle == self.STYLE_OPENAV:
  314. painter.setPen(QColor(37, 37, 37, 100))
  315. painter.drawLine(QLineF(2, lsmall - (lsmall * 0.25), lfull-2.0, lsmall - (lsmall * 0.25)))
  316. painter.drawLine(QLineF(2, lsmall - (lsmall * 0.50), lfull-2.0, lsmall - (lsmall * 0.50)))
  317. painter.drawLine(QLineF(2, lsmall - (lsmall * 0.75), lfull-2.0, lsmall - (lsmall * 0.75)))
  318. if self.fChannelCount > 1:
  319. painter.drawLine(QLineF(lfull/2-1, 1, lfull/2-1, lsmall-1))
  320. else:
  321. # Base
  322. painter.setBrush(Qt.black)
  323. painter.setPen(QPen(self.fMeterColorBaseAlt, 1))
  324. painter.drawLine(QLineF(2, lsmall - (lsmall * 0.25), lfull-2.0, lsmall - (lsmall * 0.25)))
  325. painter.drawLine(QLineF(2, lsmall - (lsmall * 0.50), lfull-2.0, lsmall - (lsmall * 0.50)))
  326. # Yellow
  327. painter.setPen(QColor(110, 110, 15, 100))
  328. painter.drawLine(QLineF(2, lsmall - (lsmall * 0.70), lfull-2.0, lsmall - (lsmall * 0.70)))
  329. painter.drawLine(QLineF(2, lsmall - (lsmall * 0.82), lfull-2.0, lsmall - (lsmall * 0.82)))
  330. # Orange
  331. painter.setPen(QColor(180, 110, 15, 100))
  332. painter.drawLine(QLineF(2, lsmall - (lsmall * 0.90), lfull-2.0, lsmall - (lsmall * 0.90)))
  333. # Red
  334. painter.setPen(QColor(110, 15, 15, 100))
  335. painter.drawLine(QLineF(2, lsmall - (lsmall * 0.96), lfull-2.0, lsmall - (lsmall * 0.96)))
  336. # --------------------------------------------------------------------------------------------------------
  337. def resizeEvent(self, event):
  338. QWidget.resizeEvent(self, event)
  339. self.updateGrandientFinalStop()
  340. # ------------------------------------------------------------------------------------------------------------
  341. # Main Testing
  342. if __name__ == '__main__':
  343. import sys
  344. import resources_rc
  345. from PyQt5.QtWidgets import QApplication
  346. app = QApplication(sys.argv)
  347. gui = DigitalPeakMeter(None)
  348. gui.setChannelCount(2)
  349. #gui.setMeterOrientation(DigitalPeakMeter.HORIZONTAL)
  350. gui.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC)
  351. gui.displayMeter(1, 0.5)
  352. gui.displayMeter(2, 0.8)
  353. gui.show()
  354. sys.exit(app.exec_())
  355. # ------------------------------------------------------------------------------------------------------------