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.

464 lines
17KB

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