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.

590 lines
19KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla rack widget code
  4. # Copyright (C) 2011-2014 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 (Config)
  19. from carla_config import *
  20. # ------------------------------------------------------------------------------------------------------------
  21. # Imports (Global)
  22. if config_UseQt5:
  23. from PyQt5.QtCore import Qt, QSize, QTimer
  24. from PyQt5.QtGui import QPixmap
  25. from PyQt5.QtWidgets import QAbstractItemView, QApplication, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QScrollBar
  26. else:
  27. from PyQt4.QtCore import Qt, QSize, QTimer
  28. from PyQt4.QtGui import QAbstractItemView, QApplication, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPixmap, QScrollBar
  29. # ------------------------------------------------------------------------------------------------------------
  30. # Imports (Custom Stuff)
  31. from carla_skin import *
  32. # ------------------------------------------------------------------------------------------------------------
  33. # Rack widget item
  34. class CarlaRackItem(QListWidgetItem):
  35. kRackItemType = QListWidgetItem.UserType + 1
  36. def __init__(self, parent, pluginId, useSkins):
  37. QListWidgetItem.__init__(self, parent, self.kRackItemType)
  38. # ----------------------------------------------------------------------------------------------------
  39. # Internal stuff
  40. self.fParent = parent
  41. self.fPluginId = pluginId
  42. self.fUseSkins = useSkins
  43. self.fWidget = None
  44. self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
  45. #self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled|Qt.ItemIsDragEnabled|Qt.ItemIsDropEnabled)
  46. # ----------------------------------------------------------------------------------------------------
  47. # Set-up GUI
  48. self.recreateWidget()
  49. if False:
  50. self.fWidget = AbstractPluginSlot(parent, parent.host, pluginId)
  51. # --------------------------------------------------------------------------------------------------------
  52. def setPluginId(self, pluginId):
  53. self.fPluginId = pluginId
  54. self.fWidget.setPluginId(pluginId)
  55. # --------------------------------------------------------------------------------------------------------
  56. def getEditDialog(self):
  57. return self.fWidget.fEditDialog
  58. def closeEditDialog(self):
  59. self.fWidget.fEditDialog.close()
  60. # --------------------------------------------------------------------------------------------------------
  61. def getWidget(self):
  62. return self.fWidget
  63. def recreateWidget(self):
  64. if self.fWidget is not None:
  65. #self.fWidget.fEditDialog.close()
  66. del self.fWidget
  67. self.fWidget = createPluginSlot(self.fParent, self.fParent.host, self.fPluginId, self.fUseSkins)
  68. self.fWidget.setFixedHeight(self.fWidget.getFixedHeight())
  69. self.setSizeHint(QSize(640, self.fWidget.getFixedHeight()))
  70. self.fParent.setItemWidget(self, self.fWidget)
  71. # ------------------------------------------------------------------------------------------------------------
  72. # Rack widget list
  73. class CarlaRackList(QListWidget):
  74. def __init__(self, parent, host):
  75. QListWidget.__init__(self, parent)
  76. self.host = host
  77. if False:
  78. # kdevelop likes this :)
  79. host = CarlaHostMeta()
  80. self.host = host
  81. # -------------------------------------------------------------
  82. exts = host.get_supported_file_extensions().split(";")
  83. # plugin files
  84. exts.append("dll")
  85. if MACOS:
  86. exts.append("dylib")
  87. if not WINDOWS:
  88. exts.append("so")
  89. self.fSupportedExtensions = tuple(i.replace("*.","") for i in exts)
  90. self.fWasLastDragValid = False
  91. self.setMinimumWidth(640+20) # required by zita, 591 was old value
  92. self.setSelectionMode(QAbstractItemView.SingleSelection)
  93. self.setSortingEnabled(False)
  94. #self.setSortingEnabled(True)
  95. self.setDragEnabled(True)
  96. self.setDragDropMode(QAbstractItemView.DropOnly)
  97. self.setDropIndicatorShown(True)
  98. self.viewport().setAcceptDrops(True)
  99. self.setFrameShape(QFrame.NoFrame)
  100. self.setFrameShadow(QFrame.Plain)
  101. self.fPixmapL = QPixmap(":/bitmaps/rack_interior_left.png")
  102. self.fPixmapR = QPixmap(":/bitmaps/rack_interior_right.png")
  103. self.fPixmapWidth = self.fPixmapL.width()
  104. def isDragEventValid(self, urls):
  105. for url in urls:
  106. filename = url.toLocalFile()
  107. if os.path.isdir(filename):
  108. if os.path.exists(os.path.join(filename, "manifest.ttl")):
  109. return True
  110. elif os.path.isfile(filename):
  111. if filename.lower().endswith(self.fSupportedExtensions):
  112. return True
  113. return False
  114. def dragEnterEvent(self, event):
  115. if self.isDragEventValid(event.mimeData().urls()):
  116. self.fWasLastDragValid = True
  117. event.acceptProposedAction()
  118. return
  119. self.fWasLastDragValid = False
  120. QListWidget.dragEnterEvent(self, event)
  121. def dragMoveEvent(self, event):
  122. if self.fWasLastDragValid:
  123. event.acceptProposedAction()
  124. tryItem = self.itemAt(event.pos())
  125. if tryItem is not None:
  126. self.setCurrentRow(tryItem.widget.getPluginId())
  127. else:
  128. self.setCurrentRow(-1)
  129. return
  130. QListWidget.dragMoveEvent(self, event)
  131. #def dragLeaveEvent(self, event):
  132. #self.fWasLastDragValid = False
  133. #QListWidget.dragLeaveEvent(self, event)
  134. def dropEvent(self, event):
  135. event.acceptProposedAction()
  136. urls = event.mimeData().urls()
  137. if len(urls) == 0:
  138. return
  139. tryItem = self.itemAt(event.pos())
  140. if tryItem is not None:
  141. pluginId = tryItem.widget.getPluginId()
  142. self.host.replace_plugin(pluginId)
  143. for url in urls:
  144. filename = url.toLocalFile()
  145. if not self.host.load_file(filename):
  146. CustomMessageBox(self, QMessageBox.Critical, self.tr("Error"),
  147. self.tr("Failed to load file"),
  148. self.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok)
  149. if tryItem is not None:
  150. self.host.replace_plugin(self.parent().fPluginCount)
  151. #tryItem.widget.setActive(True, True, True)
  152. def mousePressEvent(self, event):
  153. if self.itemAt(event.pos()) is None:
  154. event.accept()
  155. self.setCurrentRow(-1)
  156. return
  157. QListWidget.mousePressEvent(self, event)
  158. def paintEvent(self, event):
  159. painter = QPainter(self.viewport())
  160. painter.drawTiledPixmap(0, 0, self.fPixmapWidth, self.height(), self.fPixmapL)
  161. painter.drawTiledPixmap(self.width()-self.fPixmapWidth-2, 0, self.fPixmapWidth, self.height(), self.fPixmapR)
  162. QListWidget.paintEvent(self, event)
  163. # ------------------------------------------------------------------------------------------------------------
  164. # Rack widget
  165. class CarlaRackW(QFrame):
  166. def __init__(self, parent, host, doSetup = True):
  167. QFrame.__init__(self, parent)
  168. self.host = host
  169. if False:
  170. # kdevelop likes this :)
  171. host = CarlaHostMeta()
  172. self.host = host
  173. # -------------------------------------------------------------
  174. self.fLayout = QHBoxLayout(self)
  175. self.fLayout.setContentsMargins(0, 0, 0, 0)
  176. self.fLayout.setSpacing(0)
  177. self.setLayout(self.fLayout)
  178. self.fPadLeft = QLabel(self)
  179. self.fPadLeft.setFixedWidth(25)
  180. self.fPadLeft.setObjectName("PadLeft")
  181. self.fPadLeft.setText("")
  182. self.fPadRight = QLabel(self)
  183. self.fPadRight.setFixedWidth(25)
  184. self.fPadRight.setObjectName("PadRight")
  185. self.fPadRight.setText("")
  186. self.fRack = CarlaRackList(self, host)
  187. self.fRack.setObjectName("CarlaRackList")
  188. self.fRack.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
  189. self.fRack.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
  190. self.fRack.currentRowChanged.connect(self.slot_currentRowChanged)
  191. sb = self.fRack.verticalScrollBar()
  192. self.fScrollBar = QScrollBar(Qt.Vertical, self)
  193. self.fScrollBar.setMinimum(sb.minimum())
  194. self.fScrollBar.setMaximum(sb.maximum())
  195. self.fScrollBar.setValue(sb.value())
  196. #sb.actionTriggered.connect(self.fScrollBar.triggerAction)
  197. #sb.sliderMoved.connect(self.fScrollBar.)
  198. #sb.sliderPressed.connect(self.fScrollBar.)
  199. #sb.sliderReleased.connect(self.fScrollBar.)
  200. sb.rangeChanged.connect(self.fScrollBar.setRange)
  201. sb.valueChanged.connect(self.fScrollBar.setValue)
  202. self.fScrollBar.rangeChanged.connect(sb.setRange)
  203. self.fScrollBar.valueChanged.connect(sb.setValue)
  204. self.fLayout.addWidget(self.fPadLeft)
  205. self.fLayout.addWidget(self.fRack)
  206. self.fLayout.addWidget(self.fPadRight)
  207. self.fLayout.addWidget(self.fScrollBar)
  208. # -------------------------------------------------------------
  209. # Internal stuff
  210. self.fParent = parent
  211. self.fPluginCount = 0
  212. self.fPluginList = []
  213. self.fCurrentRow = -1
  214. self.fLastSelectedItem = None
  215. # -------------------------------------------------------------
  216. # Set-up GUI stuff
  217. #app = QApplication.instance()
  218. #pal1 = app.palette().base().color()
  219. #pal2 = app.palette().button().color()
  220. #col1 = "stop:0 rgb(%i, %i, %i)" % (pal1.red(), pal1.green(), pal1.blue())
  221. #col2 = "stop:1 rgb(%i, %i, %i)" % (pal2.red(), pal2.green(), pal2.blue())
  222. self.setStyleSheet("""
  223. QLabel#PadLeft {
  224. background-image: url(:/bitmaps/rack_padding_left.png);
  225. background-repeat: repeat-y;
  226. }
  227. QLabel#PadRight {
  228. background-image: url(:/bitmaps/rack_padding_right.png);
  229. background-repeat: repeat-y;
  230. }
  231. CarlaRackList#CarlaRackList {
  232. background-color: black;
  233. }
  234. """)
  235. # -------------------------------------------------------------
  236. # Connect actions to functions
  237. host.PluginAddedCallback.connect(self.slot_handlePluginAddedCallback)
  238. host.PluginRemovedCallback.connect(self.slot_handlePluginRemovedCallback)
  239. host.ReloadAllCallback.connect(self.slot_handleReloadAllCallback)
  240. if not doSetup: return
  241. parent.ui.menu_Canvas.hide()
  242. parent.ui.act_plugins_enable.triggered.connect(self.slot_pluginsEnable)
  243. parent.ui.act_plugins_disable.triggered.connect(self.slot_pluginsDisable)
  244. parent.ui.act_plugins_volume100.triggered.connect(self.slot_pluginsVolume100)
  245. parent.ui.act_plugins_mute.triggered.connect(self.slot_pluginsMute)
  246. parent.ui.act_plugins_wet100.triggered.connect(self.slot_pluginsWet100)
  247. parent.ui.act_plugins_bypass.triggered.connect(self.slot_pluginsBypass)
  248. parent.ui.act_plugins_center.triggered.connect(self.slot_pluginsCenter)
  249. parent.ui.act_plugins_panic.triggered.connect(self.slot_pluginsDisable)
  250. parent.ui.act_settings_configure.triggered.connect(self.slot_configureCarla)
  251. # -----------------------------------------------------------------
  252. @pyqtSlot(int, str)
  253. def slot_handlePluginAddedCallback(self, pluginId, pluginName):
  254. pitem = CarlaRackItem(self.fRack, pluginId, self.fParent.getSavedSettings()[CARLA_KEY_MAIN_USE_CUSTOM_SKINS])
  255. self.fPluginList.append(pitem)
  256. self.fPluginCount += 1
  257. if not self.fParent.isProjectLoading():
  258. pitem.getWidget().setActive(True, True, True)
  259. @pyqtSlot(int)
  260. def slot_handlePluginRemovedCallback(self, pluginId):
  261. pitem = self.getPluginItem(pluginId)
  262. self.fPluginCount -= 1
  263. self.fPluginList.pop(pluginId)
  264. self.fRack.takeItem(pluginId)
  265. if pitem is not None:
  266. pitem.closeEditDialog()
  267. del pitem
  268. # push all plugins 1 slot back
  269. for i in range(pluginId, self.fPluginCount):
  270. pitem = self.fPluginList[i]
  271. pitem.setPluginId(i)
  272. # -----------------------------------------------------------------
  273. def removeAllPlugins(self):
  274. while self.fRack.takeItem(0):
  275. pass
  276. for pitem in self.fPluginList:
  277. if pitem is None:
  278. break
  279. pitem.closeEditDialog()
  280. del pitem
  281. self.fPluginCount = 0
  282. self.fPluginList = []
  283. # -----------------------------------------------------------------
  284. def engineStarted(self):
  285. pass
  286. def engineStopped(self):
  287. pass
  288. # -----------------------------------------------------------------
  289. def idleFast(self):
  290. for pitem in self.fPluginList:
  291. if pitem is None:
  292. break
  293. pitem.getWidget().idleFast()
  294. def idleSlow(self):
  295. for pitem in self.fPluginList:
  296. if pitem is None:
  297. break
  298. pitem.getWidget().idleSlow()
  299. # -----------------------------------------------------------------
  300. def projectLoadingStarted(self):
  301. self.fRack.setEnabled(False)
  302. def projectLoadingFinished(self):
  303. self.fRack.setEnabled(True)
  304. # -----------------------------------------------------------------
  305. def saveSettings(self, settings):
  306. pass
  307. def showEditDialog(self, pluginId):
  308. dialog = self.getPluginEditDialog(pluginId)
  309. if dialog is None:
  310. return
  311. dialog.show()
  312. # -----------------------------------------------------------------
  313. @pyqtSlot()
  314. def slot_pluginsEnable(self):
  315. if not self.host.is_engine_running():
  316. return
  317. for pitem in self.fPluginList:
  318. if pitem is None:
  319. break
  320. pitem.getWidget().setActive(True, True, True)
  321. @pyqtSlot()
  322. def slot_pluginsDisable(self):
  323. if not self.host.is_engine_running():
  324. return
  325. for pitem in self.fPluginList:
  326. if pitem is None:
  327. break
  328. pitem.getWidget().setActive(False, True, True)
  329. @pyqtSlot()
  330. def slot_pluginsVolume100(self):
  331. if not self.host.is_engine_running():
  332. return
  333. for pitem in self.fPluginList:
  334. if pitem is None:
  335. break
  336. pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 1.0)
  337. @pyqtSlot()
  338. def slot_pluginsMute(self):
  339. if not self.host.is_engine_running():
  340. return
  341. for pitem in self.fPluginList:
  342. if pitem is None:
  343. break
  344. pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 0.0)
  345. @pyqtSlot()
  346. def slot_pluginsWet100(self):
  347. if not self.host.is_engine_running():
  348. return
  349. for pitem in self.fPluginList:
  350. if pitem is None:
  351. break
  352. pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 1.0)
  353. @pyqtSlot()
  354. def slot_pluginsBypass(self):
  355. if not self.host.is_engine_running():
  356. return
  357. for pitem in self.fPluginList:
  358. if pitem is None:
  359. break
  360. pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 0.0)
  361. @pyqtSlot()
  362. def slot_pluginsCenter(self):
  363. if not self.host.is_engine_running():
  364. return
  365. for pitem in self.fPluginList:
  366. if pitem is None:
  367. break
  368. pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_LEFT, -1.0)
  369. pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_RIGHT, 1.0)
  370. pitem.getWidget().setInternalParameter(PARAMETER_PANNING, 0.0)
  371. # -----------------------------------------------------------------
  372. @pyqtSlot()
  373. def slot_configureCarla(self):
  374. if self.fParent is None or not self.fParent.openSettingsWindow(False, False):
  375. return
  376. self.fParent.loadSettings(False)
  377. # -----------------------------------------------------------------
  378. @pyqtSlot(int)
  379. def slot_handleReloadAllCallback(self, pluginId):
  380. if pluginId >= self.fPluginCount:
  381. return
  382. pitem = self.fPluginList[pluginId]
  383. if pitem is None:
  384. return
  385. self.fRack.setCurrentRow(-1)
  386. self.fCurrentRow = -1
  387. self.fLastSelectedItem = None
  388. pitem.recreateWidget()
  389. # -----------------------------------------------------------------
  390. @pyqtSlot(int)
  391. def slot_currentRowChanged(self, row):
  392. self.fCurrentRow = row
  393. if self.fLastSelectedItem is not None:
  394. self.fLastSelectedItem.setSelected(False)
  395. if row < 0 or row >= self.fPluginCount or self.fPluginList[row] is None:
  396. self.fLastSelectedItem = None
  397. return
  398. pitem = self.fPluginList[row]
  399. pitem.getWidget().setSelected(True)
  400. self.fLastSelectedItem = pitem.getWidget()
  401. # -----------------------------------------------------------------
  402. def getPluginItem(self, pluginId):
  403. if pluginId >= self.fPluginCount:
  404. return None
  405. pitem = self.fPluginList[pluginId]
  406. if pitem is None:
  407. return None
  408. if False:
  409. pitem = CarlaRackItem(self, 0, False)
  410. return pitem
  411. def getPluginEditDialog(self, pluginId):
  412. if pluginId >= self.fPluginCount:
  413. return None
  414. pitem = self.fPluginList[pluginId]
  415. if pitem is None:
  416. return None
  417. if False:
  418. pitem = CarlaRackItem(self, 0, False)
  419. return pitem.getEditDialog()
  420. def getPluginSlotWidget(self, pluginId):
  421. if pluginId >= self.fPluginCount:
  422. return None
  423. pitem = self.fPluginList[pluginId]
  424. if pitem is None:
  425. return None
  426. if False:
  427. pitem = CarlaRackItem(self, 0, False)
  428. return pitem.getWidget()
  429. # -----------------------------------------------------------------