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.

carla_rack.py 18KB

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