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.

604 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 (Global)
  19. from PyQt4.QtCore import Qt, QSize, QTimer
  20. from PyQt4.QtGui import QApplication, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPixmap, QScrollBar
  21. # ------------------------------------------------------------------------------------------------------------
  22. # Imports (Custom Stuff)
  23. from carla_skin import *
  24. # ------------------------------------------------------------------------------------------------------------
  25. # Rack widget item
  26. class CarlaRackItem(QListWidgetItem):
  27. kRackItemType = QListWidgetItem.UserType + 1
  28. def __init__(self, parent, pluginId):
  29. QListWidgetItem.__init__(self, parent, self.kRackItemType)
  30. self.widget = createPluginSlot(parent, pluginId)
  31. self.widget.setFixedHeight(self.widget.getFixedHeight())
  32. self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) # Qt.ItemIsDragEnabled|Qt.ItemIsDropEnabled
  33. self.setSizeHint(QSize(300, self.widget.getFixedHeight()))
  34. parent.setItemWidget(self, self.widget)
  35. # -----------------------------------------------------------------
  36. def close(self):
  37. self.widget.fEditDialog.close()
  38. #def setId(self, idx):
  39. #self.widget.setId(idx)
  40. #def setName(self, newName):
  41. #self.widget.ui.label_name.setText(newName)
  42. #self.widget.ui.edit_dialog.setName(newName)
  43. # -----------------------------------------------------------------
  44. #def paintEvent(self, event):
  45. #painter = QPainter(self)
  46. #painter.save()
  47. #painter.setPen(QPen(Qt.black, 3))
  48. #painter.setBrush(Qt.black)
  49. #painter.drawRect(0, 0, self.width(), self.height())
  50. #painter.drawLine(0, self.height()-4, self.width(), self.height()-4)
  51. #painter.restore()
  52. #QListWidgetItem.paintEvent(self, event)
  53. # ------------------------------------------------------------------------------------------------------------
  54. # Rack widget list
  55. class CarlaRackList(QListWidget):
  56. def __init__(self, parent):
  57. QListWidget.__init__(self, parent)
  58. self.fPixmapL = QPixmap(":/bitmaps/rack_interior_left.png")
  59. self.fPixmapR = QPixmap(":/bitmaps/rack_interior_right.png")
  60. self.fPixmapWidth = self.fPixmapL.width()
  61. def paintEvent(self, event):
  62. painter = QPainter(self.viewport())
  63. painter.drawTiledPixmap(0, 0, self.fPixmapWidth, self.height(), self.fPixmapL)
  64. painter.drawTiledPixmap(self.width()-self.fPixmapWidth-2, 0, self.fPixmapWidth, self.height(), self.fPixmapR)
  65. QListWidget.paintEvent(self, event)
  66. # ------------------------------------------------------------------------------------------------------------
  67. # Rack widget
  68. class CarlaRackW(QFrame):
  69. def __init__(self, parent, doSetup = True):
  70. QFrame.__init__(self, parent)
  71. self.fLayout = QHBoxLayout(self)
  72. self.fLayout.setContentsMargins(0, 0, 0, 0)
  73. self.fLayout.setSpacing(0)
  74. self.setLayout(self.fLayout)
  75. self.fPadLeft = QLabel(self)
  76. self.fPadLeft.setFixedWidth(25)
  77. self.fPadLeft.setObjectName("PadLeft")
  78. self.fPadLeft.setText("")
  79. self.fPadRight = QLabel(self)
  80. self.fPadRight.setFixedWidth(25)
  81. self.fPadRight.setObjectName("PadRight")
  82. self.fPadRight.setText("")
  83. self.fRack = CarlaRackList(self)
  84. self.fRack.setMinimumWidth(640+20) # required by zita, 591 was old value
  85. self.fRack.setSortingEnabled(False)
  86. self.fRack.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
  87. self.fRack.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
  88. self.fRack.currentRowChanged.connect(self.slot_currentRowChanged)
  89. sb = self.fRack.verticalScrollBar()
  90. self.fScrollBar = QScrollBar(Qt.Vertical, self)
  91. self.fScrollBar.setMinimum(sb.minimum())
  92. self.fScrollBar.setMaximum(sb.maximum())
  93. self.fScrollBar.setValue(sb.value())
  94. #sb.actionTriggered.connect(self.fScrollBar.triggerAction)
  95. #sb.sliderMoved.connect(self.fScrollBar.)
  96. #sb.sliderPressed.connect(self.fScrollBar.)
  97. #sb.sliderReleased.connect(self.fScrollBar.)
  98. sb.rangeChanged.connect(self.fScrollBar.setRange)
  99. sb.valueChanged.connect(self.fScrollBar.setValue)
  100. self.fScrollBar.rangeChanged.connect(sb.setRange)
  101. self.fScrollBar.valueChanged.connect(sb.setValue)
  102. self.fLayout.addWidget(self.fPadLeft)
  103. self.fLayout.addWidget(self.fRack)
  104. self.fLayout.addWidget(self.fPadRight)
  105. self.fLayout.addWidget(self.fScrollBar)
  106. # -------------------------------------------------------------
  107. # Internal stuff
  108. self.fParent = parent
  109. self.fPluginCount = 0
  110. self.fPluginList = []
  111. self.fCurrentRow = -1
  112. self.fLastSelectedItem = None
  113. # -------------------------------------------------------------
  114. # Set-up GUI stuff
  115. #app = QApplication.instance()
  116. #pal1 = app.palette().base().color()
  117. #pal2 = app.palette().button().color()
  118. #col1 = "stop:0 rgb(%i, %i, %i)" % (pal1.red(), pal1.green(), pal1.blue())
  119. #col2 = "stop:1 rgb(%i, %i, %i)" % (pal2.red(), pal2.green(), pal2.blue())
  120. self.setStyleSheet("""
  121. QLabel#PadLeft {
  122. background-image: url(:/bitmaps/rack_padding_left.png);
  123. background-repeat: repeat-y;
  124. }
  125. QLabel#PadRight {
  126. background-image: url(:/bitmaps/rack_padding_right.png);
  127. background-repeat: repeat-y;
  128. }
  129. QListWidget {
  130. background-color: black;
  131. }
  132. """)
  133. # -------------------------------------------------------------
  134. # Connect actions to functions
  135. if not doSetup: return
  136. parent.ui.menu_Canvas.hide()
  137. parent.ui.act_plugins_enable.triggered.connect(self.slot_pluginsEnable)
  138. parent.ui.act_plugins_disable.triggered.connect(self.slot_pluginsDisable)
  139. parent.ui.act_plugins_volume100.triggered.connect(self.slot_pluginsVolume100)
  140. parent.ui.act_plugins_mute.triggered.connect(self.slot_pluginsMute)
  141. parent.ui.act_plugins_wet100.triggered.connect(self.slot_pluginsWet100)
  142. parent.ui.act_plugins_bypass.triggered.connect(self.slot_pluginsBypass)
  143. parent.ui.act_plugins_center.triggered.connect(self.slot_pluginsCenter)
  144. parent.ui.act_plugins_panic.triggered.connect(self.slot_pluginsDisable)
  145. parent.ui.act_settings_configure.triggered.connect(self.slot_configureCarla)
  146. parent.ParameterValueChangedCallback.connect(self.slot_handleParameterValueChangedCallback)
  147. parent.ParameterDefaultChangedCallback.connect(self.slot_handleParameterDefaultChangedCallback)
  148. parent.ParameterMidiChannelChangedCallback.connect(self.slot_handleParameterMidiChannelChangedCallback)
  149. parent.ParameterMidiCcChangedCallback.connect(self.slot_handleParameterMidiCcChangedCallback)
  150. parent.ProgramChangedCallback.connect(self.slot_handleProgramChangedCallback)
  151. parent.MidiProgramChangedCallback.connect(self.slot_handleMidiProgramChangedCallback)
  152. parent.UiStateChangedCallback.connect(self.slot_handleUiStateChangedCallback)
  153. parent.NoteOnCallback.connect(self.slot_handleNoteOnCallback)
  154. parent.NoteOffCallback.connect(self.slot_handleNoteOffCallback)
  155. parent.UpdateCallback.connect(self.slot_handleUpdateCallback)
  156. parent.ReloadInfoCallback.connect(self.slot_handleReloadInfoCallback)
  157. parent.ReloadParametersCallback.connect(self.slot_handleReloadParametersCallback)
  158. parent.ReloadProgramsCallback.connect(self.slot_handleReloadProgramsCallback)
  159. parent.ReloadAllCallback.connect(self.slot_handleReloadAllCallback)
  160. # -----------------------------------------------------------------
  161. def getPluginCount(self):
  162. return self.fPluginCount
  163. # -----------------------------------------------------------------
  164. def addPlugin(self, pluginId, isProjectLoading):
  165. pitem = CarlaRackItem(self.fRack, pluginId)
  166. self.fPluginList.append(pitem)
  167. self.fPluginCount += 1
  168. if not isProjectLoading:
  169. pitem.widget.setActive(True, True, True)
  170. def removePlugin(self, pluginId):
  171. if pluginId >= self.fPluginCount:
  172. return
  173. pitem = self.fPluginList[pluginId]
  174. if pitem is None:
  175. return
  176. self.fPluginCount -= 1
  177. self.fPluginList.pop(pluginId)
  178. self.fRack.takeItem(pluginId)
  179. pitem.close()
  180. del pitem
  181. # push all plugins 1 slot back
  182. for i in range(pluginId, self.fPluginCount):
  183. pitem = self.fPluginList[i]
  184. pitem.widget.setId(i)
  185. def renamePlugin(self, pluginId, newName):
  186. if pluginId >= self.fPluginCount:
  187. return
  188. pitem = self.fPluginList[pluginId]
  189. if pitem is None:
  190. return
  191. pitem.widget.setName(newName)
  192. def disablePlugin(self, pluginId, errorMsg):
  193. if pluginId >= self.fPluginCount:
  194. return
  195. pitem = self.fPluginList[pluginId]
  196. if pitem is None:
  197. return
  198. def removeAllPlugins(self):
  199. while self.fRack.takeItem(0):
  200. pass
  201. for i in range(self.fPluginCount):
  202. pitem = self.fPluginList[i]
  203. if pitem is None:
  204. break
  205. pitem.close()
  206. del pitem
  207. self.fPluginCount = 0
  208. self.fPluginList = []
  209. # -----------------------------------------------------------------
  210. def engineStarted(self):
  211. pass
  212. def engineStopped(self):
  213. pass
  214. def engineChanged(self):
  215. pass
  216. # -----------------------------------------------------------------
  217. def idleFast(self):
  218. for i in range(self.fPluginCount):
  219. pitem = self.fPluginList[i]
  220. if pitem is None:
  221. break
  222. pitem.widget.idleFast()
  223. def idleSlow(self):
  224. for i in range(self.fPluginCount):
  225. pitem = self.fPluginList[i]
  226. if pitem is None:
  227. break
  228. pitem.widget.idleSlow()
  229. # -----------------------------------------------------------------
  230. def projectLoaded(self):
  231. pass
  232. def saveSettings(self, settings):
  233. pass
  234. def showEditDialog(self, pluginId):
  235. if pluginId >= self.fPluginCount:
  236. return
  237. pitem = self.fPluginList[pluginId]
  238. if pitem is None:
  239. return
  240. pitem.widget.slot_showEditDialog(True)
  241. # -----------------------------------------------------------------
  242. @pyqtSlot()
  243. def slot_pluginsEnable(self):
  244. if not gCarla.host.is_engine_running():
  245. return
  246. for i in range(self.fPluginCount):
  247. pitem = self.fPluginList[i]
  248. if pitem is None:
  249. break
  250. pitem.widget.setActive(True, True, True)
  251. @pyqtSlot()
  252. def slot_pluginsDisable(self):
  253. if not gCarla.host.is_engine_running():
  254. return
  255. for i in range(self.fPluginCount):
  256. pitem = self.fPluginList[i]
  257. if pitem is None:
  258. break
  259. pitem.widget.setActive(False, True, True)
  260. @pyqtSlot()
  261. def slot_pluginsVolume100(self):
  262. if not gCarla.host.is_engine_running():
  263. return
  264. for i in range(self.fPluginCount):
  265. pitem = self.fPluginList[i]
  266. if pitem is None:
  267. break
  268. pitem.widget.setInternalParameter(PLUGIN_CAN_VOLUME, 1.0)
  269. @pyqtSlot()
  270. def slot_pluginsMute(self):
  271. if not gCarla.host.is_engine_running():
  272. return
  273. for i in range(self.fPluginCount):
  274. pitem = self.fPluginList[i]
  275. if pitem is None:
  276. break
  277. pitem.widget.setInternalParameter(PLUGIN_CAN_VOLUME, 0.0)
  278. @pyqtSlot()
  279. def slot_pluginsWet100(self):
  280. if not gCarla.host.is_engine_running():
  281. return
  282. for i in range(self.fPluginCount):
  283. pitem = self.fPluginList[i]
  284. if pitem is None:
  285. break
  286. pitem.widget.setInternalParameter(PLUGIN_CAN_DRYWET, 1.0)
  287. @pyqtSlot()
  288. def slot_pluginsBypass(self):
  289. if not gCarla.host.is_engine_running():
  290. return
  291. for i in range(self.fPluginCount):
  292. pitem = self.fPluginList[i]
  293. if pitem is None:
  294. break
  295. pitem.widget.setInternalParameter(PLUGIN_CAN_DRYWET, 0.0)
  296. @pyqtSlot()
  297. def slot_pluginsCenter(self):
  298. if not gCarla.host.is_engine_running():
  299. return
  300. for i in range(self.fPluginCount):
  301. pitem = self.fPluginList[i]
  302. if pitem is None:
  303. break
  304. pitem.widget.setInternalParameter(PARAMETER_BALANCE_LEFT, -1.0)
  305. pitem.widget.setInternalParameter(PARAMETER_BALANCE_RIGHT, 1.0)
  306. pitem.widget.setInternalParameter(PARAMETER_PANNING, 0.0)
  307. # -----------------------------------------------------------------
  308. @pyqtSlot()
  309. def slot_configureCarla(self):
  310. if self.fParent is None or not self.fParent.openSettingsWindow(False, False):
  311. return
  312. self.fParent.loadSettings(False)
  313. # -----------------------------------------------------------------
  314. @pyqtSlot(int, int, float)
  315. def slot_handleParameterValueChangedCallback(self, pluginId, index, value):
  316. if pluginId >= self.fPluginCount:
  317. return
  318. pitem = self.fPluginList[pluginId]
  319. if pitem is None:
  320. return
  321. pitem.widget.setParameterValue(index, value, True)
  322. @pyqtSlot(int, int, float)
  323. def slot_handleParameterDefaultChangedCallback(self, pluginId, index, value):
  324. if pluginId >= self.fPluginCount:
  325. return
  326. pitem = self.fPluginList[pluginId]
  327. if pitem is None:
  328. return
  329. pitem.widget.setParameterDefault(index, value)
  330. @pyqtSlot(int, int, int)
  331. def slot_handleParameterMidiCcChangedCallback(self, pluginId, index, cc):
  332. if pluginId >= self.fPluginCount:
  333. return
  334. pitem = self.fPluginList[pluginId]
  335. if pitem is None:
  336. return
  337. pitem.widget.setParameterMidiControl(index, cc)
  338. @pyqtSlot(int, int, int)
  339. def slot_handleParameterMidiChannelChangedCallback(self, pluginId, index, channel):
  340. if pluginId >= self.fPluginCount:
  341. return
  342. pitem = self.fPluginList[pluginId]
  343. if pitem is None:
  344. return
  345. pitem.widget.setParameterMidiChannel(index, channel)
  346. # -----------------------------------------------------------------
  347. @pyqtSlot(int, int)
  348. def slot_handleProgramChangedCallback(self, pluginId, index):
  349. if pluginId >= self.fPluginCount:
  350. return
  351. pitem = self.fPluginList[pluginId]
  352. if pitem is None:
  353. return
  354. pitem.widget.setProgram(index, True)
  355. @pyqtSlot(int, int)
  356. def slot_handleMidiProgramChangedCallback(self, pluginId, index):
  357. if pluginId >= self.fPluginCount:
  358. return
  359. pitem = self.fPluginList[pluginId]
  360. if pitem is None:
  361. return
  362. pitem.widget.setMidiProgram(index, True)
  363. # -----------------------------------------------------------------
  364. @pyqtSlot(int, int)
  365. def slot_handleUiStateChangedCallback(self, pluginId, state):
  366. if pluginId >= self.fPluginCount:
  367. return
  368. pitem = self.fPluginList[pluginId]
  369. if pitem is None:
  370. return
  371. pitem.widget.customUiStateChanged(state)
  372. # -----------------------------------------------------------------
  373. @pyqtSlot(int, int, int, int)
  374. def slot_handleNoteOnCallback(self, pluginId, channel, note, velo):
  375. if pluginId >= self.fPluginCount:
  376. return
  377. pitem = self.fPluginList[pluginId]
  378. if pitem is None:
  379. return
  380. pitem.widget.sendNoteOn(channel, note)
  381. @pyqtSlot(int, int, int)
  382. def slot_handleNoteOffCallback(self, pluginId, channel, note):
  383. if pluginId >= self.fPluginCount:
  384. return
  385. pitem = self.fPluginList[pluginId]
  386. if pitem is None:
  387. return
  388. pitem.widget.sendNoteOff(channel, note)
  389. # -----------------------------------------------------------------
  390. @pyqtSlot(int)
  391. def slot_handleUpdateCallback(self, pluginId):
  392. if pluginId >= self.fPluginCount:
  393. return
  394. pitem = self.fPluginList[pluginId]
  395. if pitem is None:
  396. return
  397. pitem.widget.fEditDialog.updateInfo()
  398. @pyqtSlot(int)
  399. def slot_handleReloadInfoCallback(self, pluginId):
  400. if pluginId >= self.fPluginCount:
  401. return
  402. pitem = self.fPluginList[pluginId]
  403. if pitem is None:
  404. return
  405. pitem.widget.fEditDialog.reloadInfo()
  406. @pyqtSlot(int)
  407. def slot_handleReloadParametersCallback(self, pluginId):
  408. if pluginId >= self.fPluginCount:
  409. return
  410. pitem = self.fPluginList[pluginId]
  411. if pitem is None:
  412. return
  413. pitem.widget.fEditDialog.reloadParameters()
  414. @pyqtSlot(int)
  415. def slot_handleReloadProgramsCallback(self, pluginId):
  416. if pluginId >= self.fPluginCount:
  417. return
  418. pitem = self.fPluginList[pluginId]
  419. if pitem is None:
  420. return
  421. pitem.widget.fEditDialog.reloadPrograms()
  422. @pyqtSlot(int)
  423. def slot_handleReloadAllCallback(self, pluginId):
  424. if pluginId >= self.fPluginCount:
  425. return
  426. pitem = self.fPluginList[pluginId]
  427. if pitem is None:
  428. return
  429. pitem.widget.fEditDialog.reloadAll()
  430. # -----------------------------------------------------------------
  431. def slot_currentRowChanged(self, row):
  432. self.fCurrentRow = row
  433. if self.fLastSelectedItem is not None:
  434. self.fLastSelectedItem.setSelected(False)
  435. if row < 0 or row >= self.fPluginCount or self.fPluginList[row] is None:
  436. self.fLastSelectedItem = None
  437. return
  438. pitem = self.fPluginList[row]
  439. pitem.widget.setSelected(True)
  440. self.fLastSelectedItem = pitem.widget
  441. # -----------------------------------------------------------------