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.

608 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. if not doSetup: return
  238. parent.ui.menu_Canvas.hide()
  239. parent.ui.act_plugins_enable.triggered.connect(self.slot_pluginsEnable)
  240. parent.ui.act_plugins_disable.triggered.connect(self.slot_pluginsDisable)
  241. parent.ui.act_plugins_volume100.triggered.connect(self.slot_pluginsVolume100)
  242. parent.ui.act_plugins_mute.triggered.connect(self.slot_pluginsMute)
  243. parent.ui.act_plugins_wet100.triggered.connect(self.slot_pluginsWet100)
  244. parent.ui.act_plugins_bypass.triggered.connect(self.slot_pluginsBypass)
  245. parent.ui.act_plugins_center.triggered.connect(self.slot_pluginsCenter)
  246. parent.ui.act_plugins_panic.triggered.connect(self.slot_pluginsDisable)
  247. parent.ui.act_settings_configure.triggered.connect(self.slot_configureCarla)
  248. host.PluginAddedCallback.connect(self.slot_handlePluginAddedCallback)
  249. host.PluginRemovedCallback.connect(self.slot_handlePluginRemovedCallback)
  250. host.PluginRenamedCallback.connect(self.slot_handlePluginRenamedCallback)
  251. host.PluginUnavailableCallback.connect(self.slot_handlePluginUnavailableCallback)
  252. host.ReloadAllCallback.connect(self.slot_handleReloadAllCallback)
  253. # -----------------------------------------------------------------
  254. @pyqtSlot(int, str)
  255. def slot_handlePluginAddedCallback(self, pluginId, pluginName):
  256. pitem = CarlaRackItem(self.fRack, pluginId, self.fParent.getSavedSettings()[CARLA_KEY_MAIN_USE_CUSTOM_SKINS])
  257. self.fPluginList.append(pitem)
  258. self.fPluginCount += 1
  259. if not self.fParent.isProjectLoading():
  260. pitem.getWidget().setActive(True, True, True)
  261. @pyqtSlot(int)
  262. def slot_handlePluginRemovedCallback(self, pluginId):
  263. pitem = self.getPluginItem(pluginId)
  264. self.fPluginCount -= 1
  265. self.fPluginList.pop(pluginId)
  266. self.fRack.takeItem(pluginId)
  267. if pitem is not None:
  268. pitem.closeEditDialog()
  269. del pitem
  270. # push all plugins 1 slot back
  271. for i in range(pluginId, self.fPluginCount):
  272. pitem = self.fPluginList[i]
  273. pitem.setPluginId(i)
  274. @pyqtSlot(int, str)
  275. def slot_handlePluginRenamedCallback(self, pluginId, newName):
  276. widget = self.getPluginSlotWidget(pluginId)
  277. if widget is None:
  278. return
  279. widget.setName(newName)
  280. @pyqtSlot(int, str)
  281. def slot_handlePluginUnavailableCallback(self, pluginId, errorMsg):
  282. widget = self.getPluginSlotWidget(pluginId)
  283. if widget is None:
  284. return
  285. # -----------------------------------------------------------------
  286. def removeAllPlugins(self):
  287. while self.fRack.takeItem(0):
  288. pass
  289. for pitem in self.fPluginList:
  290. if pitem is None:
  291. break
  292. pitem.closeEditDialog()
  293. del pitem
  294. self.fPluginCount = 0
  295. self.fPluginList = []
  296. # -----------------------------------------------------------------
  297. def engineStarted(self):
  298. pass
  299. def engineStopped(self):
  300. pass
  301. # -----------------------------------------------------------------
  302. def idleFast(self):
  303. for pitem in self.fPluginList:
  304. if pitem is None:
  305. break
  306. pitem.getWidget().idleFast()
  307. def idleSlow(self):
  308. for pitem in self.fPluginList:
  309. if pitem is None:
  310. break
  311. pitem.getWidget().idleSlow()
  312. # -----------------------------------------------------------------
  313. def projectLoadingStarted(self):
  314. self.fRack.setEnabled(False)
  315. def projectLoadingFinished(self):
  316. self.fRack.setEnabled(True)
  317. # -----------------------------------------------------------------
  318. def saveSettings(self, settings):
  319. pass
  320. def showEditDialog(self, pluginId):
  321. dialog = self.getPluginEditDialog(pluginId)
  322. if dialog is None:
  323. return
  324. dialog.show()
  325. # -----------------------------------------------------------------
  326. @pyqtSlot()
  327. def slot_pluginsEnable(self):
  328. if not self.host.is_engine_running():
  329. return
  330. for pitem in self.fPluginList:
  331. if pitem is None:
  332. break
  333. pitem.getWidget().setActive(True, True, True)
  334. @pyqtSlot()
  335. def slot_pluginsDisable(self):
  336. if not self.host.is_engine_running():
  337. return
  338. for pitem in self.fPluginList:
  339. if pitem is None:
  340. break
  341. pitem.getWidget().setActive(False, True, True)
  342. @pyqtSlot()
  343. def slot_pluginsVolume100(self):
  344. if not self.host.is_engine_running():
  345. return
  346. for pitem in self.fPluginList:
  347. if pitem is None:
  348. break
  349. pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 1.0)
  350. @pyqtSlot()
  351. def slot_pluginsMute(self):
  352. if not self.host.is_engine_running():
  353. return
  354. for pitem in self.fPluginList:
  355. if pitem is None:
  356. break
  357. pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 0.0)
  358. @pyqtSlot()
  359. def slot_pluginsWet100(self):
  360. if not self.host.is_engine_running():
  361. return
  362. for pitem in self.fPluginList:
  363. if pitem is None:
  364. break
  365. pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 1.0)
  366. @pyqtSlot()
  367. def slot_pluginsBypass(self):
  368. if not self.host.is_engine_running():
  369. return
  370. for pitem in self.fPluginList:
  371. if pitem is None:
  372. break
  373. pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 0.0)
  374. @pyqtSlot()
  375. def slot_pluginsCenter(self):
  376. if not self.host.is_engine_running():
  377. return
  378. for pitem in self.fPluginList:
  379. if pitem is None:
  380. break
  381. pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_LEFT, -1.0)
  382. pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_RIGHT, 1.0)
  383. pitem.getWidget().setInternalParameter(PARAMETER_PANNING, 0.0)
  384. # -----------------------------------------------------------------
  385. @pyqtSlot()
  386. def slot_configureCarla(self):
  387. if self.fParent is None or not self.fParent.openSettingsWindow(False, False):
  388. return
  389. self.fParent.loadSettings(False)
  390. # -----------------------------------------------------------------
  391. @pyqtSlot(int)
  392. def slot_handleReloadAllCallback(self, pluginId):
  393. if pluginId >= self.fPluginCount:
  394. return
  395. pitem = self.fPluginList[pluginId]
  396. if pitem is None:
  397. return
  398. self.fRack.setCurrentRow(-1)
  399. self.fCurrentRow = -1
  400. self.fLastSelectedItem = None
  401. pitem.recreateWidget()
  402. # -----------------------------------------------------------------
  403. @pyqtSlot(int)
  404. def slot_currentRowChanged(self, row):
  405. self.fCurrentRow = row
  406. if self.fLastSelectedItem is not None:
  407. self.fLastSelectedItem.setSelected(False)
  408. if row < 0 or row >= self.fPluginCount or self.fPluginList[row] is None:
  409. self.fLastSelectedItem = None
  410. return
  411. pitem = self.fPluginList[row]
  412. pitem.getWidget().setSelected(True)
  413. self.fLastSelectedItem = pitem.getWidget()
  414. # -----------------------------------------------------------------
  415. def getPluginItem(self, pluginId):
  416. if pluginId >= self.fPluginCount:
  417. return None
  418. pitem = self.fPluginList[pluginId]
  419. if pitem is None:
  420. return None
  421. if False:
  422. pitem = CarlaRackItem(self, 0, False)
  423. return pitem
  424. def getPluginEditDialog(self, pluginId):
  425. if pluginId >= self.fPluginCount:
  426. return None
  427. pitem = self.fPluginList[pluginId]
  428. if pitem is None:
  429. return None
  430. if False:
  431. pitem = CarlaRackItem(self, 0, False)
  432. return pitem.getEditDialog()
  433. def getPluginSlotWidget(self, pluginId):
  434. if pluginId >= self.fPluginCount:
  435. return None
  436. pitem = self.fPluginList[pluginId]
  437. if pitem is None:
  438. return None
  439. if False:
  440. pitem = CarlaRackItem(self, 0, False)
  441. return pitem.getWidget()
  442. # -----------------------------------------------------------------