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_patchbay.py 36KB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla patchbay 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 QPointF, QTimer
  24. from PyQt5.QtGui import QImage
  25. from PyQt5.QtPrintSupport import QPrinter, QPrintDialog
  26. from PyQt5.QtWidgets import QFrame, QGraphicsView, QGridLayout
  27. else:
  28. from PyQt4.QtCore import QPointF, QTimer
  29. from PyQt4.QtGui import QFrame, QGraphicsView, QGridLayout, QImage, QPrinter, QPrintDialog
  30. # ------------------------------------------------------------------------------------------------------------
  31. # Imports (Custom Stuff)
  32. import patchcanvas
  33. from carla_widgets import *
  34. from digitalpeakmeter import DigitalPeakMeter
  35. from pixmapkeyboard import PixmapKeyboardHArea
  36. # ------------------------------------------------------------------------------------------------------------
  37. # Try Import OpenGL
  38. try:
  39. if config_UseQt5:
  40. from PyQt5.QtOpenGL import QGLWidget
  41. else:
  42. from PyQt4.QtOpenGL import QGLWidget
  43. hasGL = True
  44. except:
  45. hasGL = False
  46. # ------------------------------------------------------------------------------------------------------------
  47. # Carla Canvas defaults
  48. CARLA_DEFAULT_CANVAS_SIZE_WIDTH = 3100
  49. CARLA_DEFAULT_CANVAS_SIZE_HEIGHT = 2400
  50. # ------------------------------------------------------------------------------------------------
  51. # Patchbay widget
  52. class CarlaPatchbayW(QFrame):
  53. def __init__(self, parent, doSetup = True, onlyPatchbay = True, is3D = False):
  54. QFrame.__init__(self, parent)
  55. self.fLayout = QGridLayout(self)
  56. self.fLayout.setContentsMargins(0, 0, 0, 0)
  57. self.fLayout.setSpacing(1)
  58. self.setLayout(self.fLayout)
  59. self.fView = QGraphicsView(self)
  60. self.fKeys = PixmapKeyboardHArea(self)
  61. self.fPeaksIn = DigitalPeakMeter(self)
  62. self.fPeaksOut = DigitalPeakMeter(self)
  63. self.fPeaksCleared = True
  64. self.fPeaksIn.setColor(DigitalPeakMeter.BLUE)
  65. self.fPeaksIn.setChannels(2)
  66. self.fPeaksIn.setOrientation(DigitalPeakMeter.VERTICAL)
  67. self.fPeaksIn.setFixedWidth(25)
  68. self.fPeaksOut.setColor(DigitalPeakMeter.GREEN)
  69. self.fPeaksOut.setChannels(2)
  70. self.fPeaksOut.setOrientation(DigitalPeakMeter.VERTICAL)
  71. self.fPeaksOut.setFixedWidth(25)
  72. self.fLayout.addWidget(self.fPeaksIn, 0, 0)
  73. self.fLayout.addWidget(self.fView, 0, 1) # self.fViewWidget if is3D else
  74. self.fLayout.addWidget(self.fPeaksOut, 0, 2)
  75. self.fLayout.addWidget(self.fKeys, 1, 0, 1, 0)
  76. # -------------------------------------------------------------
  77. # Internal stuff
  78. self.fParent = parent
  79. self.fPluginCount = 0
  80. self.fPluginList = []
  81. self.fIsOnlyPatchbay = onlyPatchbay
  82. self.fSelectedPlugins = []
  83. self.fCanvasWidth = 0
  84. self.fCanvasHeight = 0
  85. # -------------------------------------------------------------
  86. # Set-up Canvas Preview
  87. self.fMiniCanvasPreview = self.fParent.ui.miniCanvasPreview
  88. self.fMiniCanvasPreview.setRealParent(self)
  89. self.fMovingViaMiniCanvas = False
  90. # -------------------------------------------------------------
  91. # Set-up Canvas
  92. self.scene = patchcanvas.PatchScene(self, self.fView)
  93. self.fView.setScene(self.scene)
  94. self.fView.setRenderHint(QPainter.Antialiasing, bool(parent.fSavedSettings[CARLA_KEY_CANVAS_ANTIALIASING] == patchcanvas.ANTIALIASING_FULL))
  95. if parent.fSavedSettings[CARLA_KEY_CANVAS_USE_OPENGL] and hasGL: # and not is3D:
  96. self.fViewWidget = QGLWidget(self)
  97. self.fView.setViewport(self.fViewWidget)
  98. self.fView.setRenderHint(QPainter.HighQualityAntialiasing, parent.fSavedSettings[CARLA_KEY_CANVAS_HQ_ANTIALIASING])
  99. self.setupCanvas()
  100. QTimer.singleShot(100, self.slot_restoreScrollbarValues)
  101. # -------------------------------------------------------------
  102. # Connect actions to functions
  103. parent.ui.act_settings_show_meters.toggled.connect(self.slot_showCanvasMeters)
  104. parent.ui.act_settings_show_keyboard.toggled.connect(self.slot_showCanvasKeyboard)
  105. self.fView.horizontalScrollBar().valueChanged.connect(self.slot_horizontalScrollBarChanged)
  106. self.fView.verticalScrollBar().valueChanged.connect(self.slot_verticalScrollBarChanged)
  107. self.scene.scaleChanged.connect(self.slot_canvasScaleChanged)
  108. self.scene.sceneGroupMoved.connect(self.slot_canvasItemMoved)
  109. self.scene.pluginSelected.connect(self.slot_canvasPluginSelected)
  110. self.fMiniCanvasPreview.miniCanvasMoved.connect(self.slot_miniCanvasMoved)
  111. self.fKeys.keyboard.noteOn.connect(self.slot_noteOn)
  112. self.fKeys.keyboard.noteOff.connect(self.slot_noteOff)
  113. # -------------------------------------------------------------
  114. # Load Settings
  115. settings = QSettings()
  116. showMeters = settings.value("ShowMeters", False, type=bool)
  117. self.fParent.ui.act_settings_show_meters.setChecked(showMeters)
  118. self.fPeaksIn.setVisible(showMeters)
  119. self.fPeaksOut.setVisible(showMeters)
  120. showKeyboard = settings.value("ShowKeyboard", True, type=bool)
  121. self.fParent.ui.act_settings_show_keyboard.setChecked(showKeyboard)
  122. self.fKeys.setVisible(showKeyboard)
  123. # -------------------------------------------------------------
  124. # Connect actions to functions (part 2)
  125. if not doSetup: return
  126. parent.ui.act_plugins_enable.triggered.connect(self.slot_pluginsEnable)
  127. parent.ui.act_plugins_disable.triggered.connect(self.slot_pluginsDisable)
  128. parent.ui.act_plugins_volume100.triggered.connect(self.slot_pluginsVolume100)
  129. parent.ui.act_plugins_mute.triggered.connect(self.slot_pluginsMute)
  130. parent.ui.act_plugins_wet100.triggered.connect(self.slot_pluginsWet100)
  131. parent.ui.act_plugins_bypass.triggered.connect(self.slot_pluginsBypass)
  132. parent.ui.act_plugins_center.triggered.connect(self.slot_pluginsCenter)
  133. parent.ui.act_plugins_panic.triggered.connect(self.slot_pluginsDisable)
  134. parent.ui.act_canvas_arrange.setEnabled(False) # TODO, later
  135. parent.ui.act_canvas_arrange.triggered.connect(self.slot_canvasArrange)
  136. parent.ui.act_canvas_refresh.triggered.connect(self.slot_canvasRefresh)
  137. parent.ui.act_canvas_zoom_fit.triggered.connect(self.slot_canvasZoomFit)
  138. parent.ui.act_canvas_zoom_in.triggered.connect(self.slot_canvasZoomIn)
  139. parent.ui.act_canvas_zoom_out.triggered.connect(self.slot_canvasZoomOut)
  140. parent.ui.act_canvas_zoom_100.triggered.connect(self.slot_canvasZoomReset)
  141. parent.ui.act_canvas_print.triggered.connect(self.slot_canvasPrint)
  142. parent.ui.act_canvas_save_image.triggered.connect(self.slot_canvasSaveImage)
  143. parent.ui.act_settings_configure.triggered.connect(self.slot_configureCarla)
  144. parent.ParameterValueChangedCallback.connect(self.slot_handleParameterValueChangedCallback)
  145. parent.ParameterDefaultChangedCallback.connect(self.slot_handleParameterDefaultChangedCallback)
  146. parent.ParameterMidiChannelChangedCallback.connect(self.slot_handleParameterMidiChannelChangedCallback)
  147. parent.ParameterMidiCcChangedCallback.connect(self.slot_handleParameterMidiCcChangedCallback)
  148. parent.ProgramChangedCallback.connect(self.slot_handleProgramChangedCallback)
  149. parent.MidiProgramChangedCallback.connect(self.slot_handleMidiProgramChangedCallback)
  150. parent.NoteOnCallback.connect(self.slot_handleNoteOnCallback)
  151. parent.NoteOffCallback.connect(self.slot_handleNoteOffCallback)
  152. parent.UpdateCallback.connect(self.slot_handleUpdateCallback)
  153. parent.ReloadInfoCallback.connect(self.slot_handleReloadInfoCallback)
  154. parent.ReloadParametersCallback.connect(self.slot_handleReloadParametersCallback)
  155. parent.ReloadProgramsCallback.connect(self.slot_handleReloadProgramsCallback)
  156. parent.ReloadAllCallback.connect(self.slot_handleReloadAllCallback)
  157. parent.PatchbayClientAddedCallback.connect(self.slot_handlePatchbayClientAddedCallback)
  158. parent.PatchbayClientRemovedCallback.connect(self.slot_handlePatchbayClientRemovedCallback)
  159. parent.PatchbayClientRenamedCallback.connect(self.slot_handlePatchbayClientRenamedCallback)
  160. parent.PatchbayClientDataChangedCallback.connect(self.slot_handlePatchbayClientDataChangedCallback)
  161. parent.PatchbayPortAddedCallback.connect(self.slot_handlePatchbayPortAddedCallback)
  162. parent.PatchbayPortRemovedCallback.connect(self.slot_handlePatchbayPortRemovedCallback)
  163. parent.PatchbayPortRenamedCallback.connect(self.slot_handlePatchbayPortRenamedCallback)
  164. parent.PatchbayConnectionAddedCallback.connect(self.slot_handlePatchbayConnectionAddedCallback)
  165. parent.PatchbayConnectionRemovedCallback.connect(self.slot_handlePatchbayConnectionRemovedCallback)
  166. # -----------------------------------------------------------------
  167. def getPluginCount(self):
  168. return self.fPluginCount
  169. # -----------------------------------------------------------------
  170. def addPlugin(self, pluginId, isProjectLoading):
  171. if not self.fIsOnlyPatchbay:
  172. self.fPluginCount += 1
  173. return
  174. pitem = PluginEdit(self, pluginId)
  175. self.fPluginList.append(pitem)
  176. self.fPluginCount += 1
  177. if not isProjectLoading:
  178. gCarla.host.set_active(pluginId, True)
  179. def removePlugin(self, pluginId):
  180. patchcanvas.handlePluginRemoved(pluginId)
  181. if pluginId in self.fSelectedPlugins:
  182. self.clearSideStuff()
  183. if not self.fIsOnlyPatchbay:
  184. self.fPluginCount -= 1
  185. return
  186. if pluginId >= self.fPluginCount:
  187. return
  188. pitem = self.fPluginList[pluginId]
  189. if pitem is None:
  190. return
  191. self.fPluginCount -= 1
  192. self.fPluginList.pop(pluginId)
  193. pitem.close()
  194. del pitem
  195. # push all plugins 1 slot back
  196. for i in range(pluginId, self.fPluginCount):
  197. pitem = self.fPluginList[i]
  198. pitem.setId(i)
  199. def renamePlugin(self, pluginId, newName):
  200. if pluginId >= self.fPluginCount:
  201. return
  202. pitem = self.fPluginList[pluginId]
  203. if pitem is None:
  204. return
  205. pitem.setName(newName)
  206. def disablePlugin(self, pluginId, errorMsg):
  207. if pluginId >= self.fPluginCount:
  208. return
  209. pitem = self.fPluginList[pluginId]
  210. if pitem is None:
  211. return
  212. def removeAllPlugins(self):
  213. for pitem in self.fPluginList:
  214. if pitem is None:
  215. break
  216. pitem.close()
  217. del pitem
  218. self.fPluginCount = 0
  219. self.fPluginList = []
  220. self.clearSideStuff()
  221. patchcanvas.handlePluginRemoved(0)
  222. # -----------------------------------------------------------------
  223. def engineStarted(self):
  224. pass
  225. def engineStopped(self):
  226. patchcanvas.clear()
  227. def engineChanged(self):
  228. pass
  229. # -----------------------------------------------------------------
  230. def idleFast(self):
  231. if self.fPluginCount == 0:
  232. return
  233. for pluginId in self.fSelectedPlugins:
  234. self.fPeaksCleared = False
  235. if self.fPeaksIn.isVisible():
  236. self.fPeaksIn.displayMeter(1, gCarla.host.get_input_peak_value(pluginId, True))
  237. self.fPeaksIn.displayMeter(2, gCarla.host.get_input_peak_value(pluginId, False))
  238. if self.fPeaksOut.isVisible():
  239. self.fPeaksOut.displayMeter(1, gCarla.host.get_output_peak_value(pluginId, True))
  240. self.fPeaksOut.displayMeter(2, gCarla.host.get_output_peak_value(pluginId, False))
  241. return
  242. if self.fPeaksCleared:
  243. return
  244. self.fPeaksCleared = True
  245. self.fPeaksIn.displayMeter(1, 0.0, True)
  246. self.fPeaksIn.displayMeter(2, 0.0, True)
  247. self.fPeaksOut.displayMeter(1, 0.0, True)
  248. self.fPeaksOut.displayMeter(2, 0.0, True)
  249. def idleSlow(self):
  250. for pitem in self.fPluginList:
  251. if pitem is None:
  252. break
  253. pitem.idleSlow()
  254. # -----------------------------------------------------------------
  255. def projectLoadingStarted(self):
  256. pass
  257. def projectLoadingFinished(self):
  258. QTimer.singleShot(1000, self.slot_canvasRefresh)
  259. # -----------------------------------------------------------------
  260. def saveSettings(self, settings):
  261. settings.setValue("ShowMeters", self.fParent.ui.act_settings_show_meters.isChecked())
  262. settings.setValue("ShowKeyboard", self.fParent.ui.act_settings_show_keyboard.isChecked())
  263. settings.setValue("HorizontalScrollBarValue", self.fView.horizontalScrollBar().value())
  264. settings.setValue("VerticalScrollBarValue", self.fView.verticalScrollBar().value())
  265. def showEditDialog(self, pluginId):
  266. if pluginId >= self.fPluginCount:
  267. return
  268. pitem = self.fPluginList[pluginId]
  269. if pitem is None:
  270. return
  271. pitem.show()
  272. # -----------------------------------------------------------------
  273. # called by PluginEdit to plugin skin parent, ignored here
  274. def editDialogChanged(self, visible):
  275. pass
  276. def recheckPluginHints(self, hints):
  277. pass
  278. # -----------------------------------------------------------------
  279. def clearSideStuff(self):
  280. self.scene.clearSelection()
  281. self.fSelectedPlugins = []
  282. self.fKeys.keyboard.allNotesOff(False)
  283. self.fKeys.setEnabled(False)
  284. self.fPeaksCleared = True
  285. self.fPeaksIn.displayMeter(1, 0.0, True)
  286. self.fPeaksIn.displayMeter(2, 0.0, True)
  287. self.fPeaksOut.displayMeter(1, 0.0, True)
  288. self.fPeaksOut.displayMeter(2, 0.0, True)
  289. def setupCanvas(self):
  290. pOptions = patchcanvas.options_t()
  291. pOptions.theme_name = self.fParent.fSavedSettings[CARLA_KEY_CANVAS_THEME]
  292. pOptions.auto_hide_groups = self.fParent.fSavedSettings[CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS]
  293. pOptions.use_bezier_lines = self.fParent.fSavedSettings[CARLA_KEY_CANVAS_USE_BEZIER_LINES]
  294. pOptions.antialiasing = self.fParent.fSavedSettings[CARLA_KEY_CANVAS_ANTIALIASING]
  295. pOptions.eyecandy = self.fParent.fSavedSettings[CARLA_KEY_CANVAS_EYE_CANDY]
  296. pFeatures = patchcanvas.features_t()
  297. pFeatures.group_info = False
  298. pFeatures.group_rename = False
  299. pFeatures.port_info = False
  300. pFeatures.port_rename = False
  301. pFeatures.handle_group_pos = True
  302. patchcanvas.setOptions(pOptions)
  303. patchcanvas.setFeatures(pFeatures)
  304. patchcanvas.init("Carla2", self.scene, canvasCallback, False)
  305. tryCanvasSize = self.fParent.fSavedSettings[CARLA_KEY_CANVAS_SIZE].split("x")
  306. if len(tryCanvasSize) == 2 and tryCanvasSize[0].isdigit() and tryCanvasSize[1].isdigit():
  307. self.fCanvasWidth = int(tryCanvasSize[0])
  308. self.fCanvasHeight = int(tryCanvasSize[1])
  309. else:
  310. self.fCanvasWidth = CARLA_DEFAULT_CANVAS_SIZE_WIDTH
  311. self.fCanvasHeight = CARLA_DEFAULT_CANVAS_SIZE_HEIGHT
  312. patchcanvas.setCanvasSize(0, 0, self.fCanvasWidth, self.fCanvasHeight)
  313. patchcanvas.setInitialPos(self.fCanvasWidth / 2, self.fCanvasHeight / 2)
  314. self.fView.setSceneRect(0, 0, self.fCanvasWidth, self.fCanvasHeight)
  315. self.themeData = [self.fCanvasWidth, self.fCanvasHeight, patchcanvas.canvas.theme.canvas_bg, patchcanvas.canvas.theme.rubberband_brush, patchcanvas.canvas.theme.rubberband_pen.color()]
  316. def updateCanvasInitialPos(self):
  317. x = self.fView.horizontalScrollBar().value() + self.width()/4
  318. y = self.fView.verticalScrollBar().value() + self.height()/4
  319. patchcanvas.setInitialPos(x, y)
  320. # -----------------------------------------------------------------
  321. @pyqtSlot(bool)
  322. def slot_showCanvasMeters(self, yesNo):
  323. self.fPeaksIn.setVisible(yesNo)
  324. self.fPeaksOut.setVisible(yesNo)
  325. @pyqtSlot(bool)
  326. def slot_showCanvasKeyboard(self, yesNo):
  327. self.fKeys.setVisible(yesNo)
  328. # -----------------------------------------------------------------
  329. @pyqtSlot()
  330. def slot_miniCanvasCheckAll(self):
  331. self.slot_miniCanvasCheckSize()
  332. self.slot_horizontalScrollBarChanged(self.fView.horizontalScrollBar().value())
  333. self.slot_verticalScrollBarChanged(self.fView.verticalScrollBar().value())
  334. @pyqtSlot()
  335. def slot_miniCanvasCheckSize(self):
  336. self.fMiniCanvasPreview.setViewSize(float(self.width()) / self.fCanvasWidth, float(self.height()) / self.fCanvasHeight)
  337. @pyqtSlot(int)
  338. def slot_horizontalScrollBarChanged(self, value):
  339. if self.fMovingViaMiniCanvas: return
  340. maximum = self.fView.horizontalScrollBar().maximum()
  341. if maximum == 0:
  342. xp = 0
  343. else:
  344. xp = float(value) / maximum
  345. self.fMiniCanvasPreview.setViewPosX(xp)
  346. self.updateCanvasInitialPos()
  347. @pyqtSlot(int)
  348. def slot_verticalScrollBarChanged(self, value):
  349. if self.fMovingViaMiniCanvas: return
  350. maximum = self.fView.verticalScrollBar().maximum()
  351. if maximum == 0:
  352. yp = 0
  353. else:
  354. yp = float(value) / maximum
  355. self.fMiniCanvasPreview.setViewPosY(yp)
  356. self.updateCanvasInitialPos()
  357. @pyqtSlot()
  358. def slot_restoreScrollbarValues(self):
  359. settings = QSettings()
  360. self.fView.horizontalScrollBar().setValue(settings.value("HorizontalScrollBarValue", self.fView.horizontalScrollBar().maximum()/2, type=int))
  361. self.fView.verticalScrollBar().setValue(settings.value("VerticalScrollBarValue", self.fView.verticalScrollBar().maximum()/2, type=int))
  362. # -----------------------------------------------------------------
  363. @pyqtSlot(float)
  364. def slot_canvasScaleChanged(self, scale):
  365. self.fMiniCanvasPreview.setViewScale(scale)
  366. @pyqtSlot(int, int, QPointF)
  367. def slot_canvasItemMoved(self, group_id, split_mode, pos):
  368. self.fMiniCanvasPreview.update()
  369. @pyqtSlot(list)
  370. def slot_canvasPluginSelected(self, pluginList):
  371. self.fKeys.keyboard.allNotesOff(False)
  372. self.fKeys.setEnabled(len(pluginList) != 0) # and self.fPluginCount > 0
  373. self.fSelectedPlugins = pluginList
  374. @pyqtSlot(float, float)
  375. def slot_miniCanvasMoved(self, xp, yp):
  376. self.fMovingViaMiniCanvas = True
  377. self.fView.horizontalScrollBar().setValue(xp * self.fView.horizontalScrollBar().maximum())
  378. self.fView.verticalScrollBar().setValue(yp * self.fView.verticalScrollBar().maximum())
  379. self.fMovingViaMiniCanvas = False
  380. self.updateCanvasInitialPos()
  381. # -----------------------------------------------------------------
  382. @pyqtSlot(int)
  383. def slot_noteOn(self, note):
  384. for pluginId in self.fSelectedPlugins:
  385. gCarla.host.send_midi_note(pluginId, 0, note, 100)
  386. @pyqtSlot(int)
  387. def slot_noteOff(self, note):
  388. for pluginId in self.fSelectedPlugins:
  389. gCarla.host.send_midi_note(pluginId, 0, note, 0)
  390. # -----------------------------------------------------------------
  391. @pyqtSlot()
  392. def slot_pluginsEnable(self):
  393. if not gCarla.host.is_engine_running():
  394. return
  395. for i in range(self.fPluginCount):
  396. gCarla.host.set_active(i, True)
  397. @pyqtSlot()
  398. def slot_pluginsDisable(self):
  399. if not gCarla.host.is_engine_running():
  400. return
  401. for i in range(self.fPluginCount):
  402. gCarla.host.set_active(i, False)
  403. @pyqtSlot()
  404. def slot_pluginsVolume100(self):
  405. if not gCarla.host.is_engine_running():
  406. return
  407. for i in range(self.fPluginCount):
  408. pitem = self.fPluginList[i]
  409. if pitem is None:
  410. break
  411. if pitem.getHints() & PLUGIN_CAN_VOLUME:
  412. pitem.setParameterValue(PARAMETER_VOLUME, 1.0)
  413. gCarla.host.set_volume(i, 1.0)
  414. @pyqtSlot()
  415. def slot_pluginsMute(self):
  416. if not gCarla.host.is_engine_running():
  417. return
  418. for i in range(self.fPluginCount):
  419. pitem = self.fPluginList[i]
  420. if pitem is None:
  421. break
  422. if pitem.getHints() & PLUGIN_CAN_VOLUME:
  423. pitem.setParameterValue(PARAMETER_VOLUME, 0.0)
  424. gCarla.host.set_volume(i, 0.0)
  425. @pyqtSlot()
  426. def slot_pluginsWet100(self):
  427. if not gCarla.host.is_engine_running():
  428. return
  429. for i in range(self.fPluginCount):
  430. pitem = self.fPluginList[i]
  431. if pitem is None:
  432. break
  433. if pitem.getHints() & PLUGIN_CAN_DRYWET:
  434. pitem.setParameterValue(PARAMETER_DRYWET, 1.0)
  435. gCarla.host.set_drywet(i, 1.0)
  436. @pyqtSlot()
  437. def slot_pluginsBypass(self):
  438. if not gCarla.host.is_engine_running():
  439. return
  440. for i in range(self.fPluginCount):
  441. pitem = self.fPluginList[i]
  442. if pitem is None:
  443. break
  444. if pitem.getHints() & PLUGIN_CAN_DRYWET:
  445. pitem.setParameterValue(PARAMETER_DRYWET, 0.0)
  446. gCarla.host.set_drywet(i, 0.0)
  447. @pyqtSlot()
  448. def slot_pluginsCenter(self):
  449. if not gCarla.host.is_engine_running():
  450. return
  451. for i in range(self.fPluginCount):
  452. pitem = self.fPluginList[i]
  453. if pitem is None:
  454. break
  455. if pitem.getHints() & PLUGIN_CAN_BALANCE:
  456. pitem.setParameterValue(PARAMETER_BALANCE_LEFT, -1.0)
  457. pitem.setParameterValue(PARAMETER_BALANCE_RIGHT, 1.0)
  458. gCarla.host.set_balance_left(i, -1.0)
  459. gCarla.host.set_balance_right(i, 1.0)
  460. if pitem.getHints() & PLUGIN_CAN_PANNING:
  461. pitem.setParameterValue(PARAMETER_PANNING, 0.0)
  462. gCarla.host.set_panning(i, 0.0)
  463. # -----------------------------------------------------------------
  464. @pyqtSlot()
  465. def slot_configureCarla(self):
  466. if self.fParent is None or not self.fParent.openSettingsWindow(True, hasGL):
  467. return
  468. self.fParent.loadSettings(False)
  469. patchcanvas.clear()
  470. self.setupCanvas()
  471. self.fParent.updateContainer(self.themeData)
  472. self.slot_miniCanvasCheckAll()
  473. if gCarla.host.is_engine_running():
  474. gCarla.host.patchbay_refresh()
  475. # -----------------------------------------------------------------
  476. @pyqtSlot(int, int, float)
  477. def slot_handleParameterValueChangedCallback(self, pluginId, index, value):
  478. if pluginId >= self.fPluginCount:
  479. return
  480. pitem = self.fPluginList[pluginId]
  481. if pitem is None:
  482. return
  483. pitem.setParameterValue(index, value)
  484. @pyqtSlot(int, int, float)
  485. def slot_handleParameterDefaultChangedCallback(self, pluginId, index, value):
  486. if pluginId >= self.fPluginCount:
  487. return
  488. pitem = self.fPluginList[pluginId]
  489. if pitem is None:
  490. return
  491. pitem.setParameterDefault(index, value)
  492. @pyqtSlot(int, int, int)
  493. def slot_handleParameterMidiCcChangedCallback(self, pluginId, index, cc):
  494. if pluginId >= self.fPluginCount:
  495. return
  496. pitem = self.fPluginList[pluginId]
  497. if pitem is None:
  498. return
  499. pitem.setParameterMidiControl(index, cc)
  500. @pyqtSlot(int, int, int)
  501. def slot_handleParameterMidiChannelChangedCallback(self, pluginId, index, channel):
  502. if pluginId >= self.fPluginCount:
  503. return
  504. pitem = self.fPluginList[pluginId]
  505. if pitem is None:
  506. return
  507. pitem.setParameterMidiChannel(index, channel)
  508. # -----------------------------------------------------------------
  509. @pyqtSlot(int, int)
  510. def slot_handleProgramChangedCallback(self, pluginId, index):
  511. if pluginId >= self.fPluginCount:
  512. return
  513. pitem = self.fPluginList[pluginId]
  514. if pitem is None:
  515. return
  516. pitem.setProgram(index)
  517. @pyqtSlot(int, int)
  518. def slot_handleMidiProgramChangedCallback(self, pluginId, index):
  519. if pluginId >= self.fPluginCount:
  520. return
  521. pitem = self.fPluginList[pluginId]
  522. if pitem is None:
  523. return
  524. pitem.setMidiProgram(index)
  525. # -----------------------------------------------------------------
  526. @pyqtSlot(int, int, int, int)
  527. def slot_handleNoteOnCallback(self, pluginId, channel, note, velo):
  528. if pluginId in self.fSelectedPlugins:
  529. self.fKeys.keyboard.sendNoteOn(note, False)
  530. if not self.fIsOnlyPatchbay:
  531. return
  532. if pluginId >= self.fPluginCount:
  533. return
  534. pitem = self.fPluginList[pluginId]
  535. if pitem is None:
  536. return
  537. pitem.sendNoteOn(channel, note)
  538. @pyqtSlot(int, int, int)
  539. def slot_handleNoteOffCallback(self, pluginId, channel, note):
  540. if pluginId in self.fSelectedPlugins:
  541. self.fKeys.keyboard.sendNoteOff(note, False)
  542. if not self.fIsOnlyPatchbay:
  543. return
  544. if pluginId >= self.fPluginCount:
  545. return
  546. pitem = self.fPluginList[pluginId]
  547. if pitem is None:
  548. return
  549. pitem.sendNoteOff(channel, note)
  550. # -----------------------------------------------------------------
  551. @pyqtSlot(int)
  552. def slot_handleUpdateCallback(self, pluginId):
  553. if pluginId >= self.fPluginCount:
  554. return
  555. pitem = self.fPluginList[pluginId]
  556. if pitem is None:
  557. return
  558. pitem.updateInfo()
  559. @pyqtSlot(int)
  560. def slot_handleReloadInfoCallback(self, pluginId):
  561. if pluginId >= self.fPluginCount:
  562. return
  563. pitem = self.fPluginList[pluginId]
  564. if pitem is None:
  565. return
  566. pitem.reloadInfo()
  567. @pyqtSlot(int)
  568. def slot_handleReloadParametersCallback(self, pluginId):
  569. if pluginId >= self.fPluginCount:
  570. return
  571. pitem = self.fPluginList[pluginId]
  572. if pitem is None:
  573. return
  574. pitem.reloadParameters()
  575. @pyqtSlot(int)
  576. def slot_handleReloadProgramsCallback(self, pluginId):
  577. if pluginId >= self.fPluginCount:
  578. return
  579. pitem = self.fPluginList[pluginId]
  580. if pitem is None:
  581. return
  582. pitem.reloadPrograms()
  583. @pyqtSlot(int)
  584. def slot_handleReloadAllCallback(self, pluginId):
  585. if pluginId >= self.fPluginCount:
  586. return
  587. pitem = self.fPluginList[pluginId]
  588. if pitem is None:
  589. return
  590. pitem.reloadAll()
  591. # -----------------------------------------------------------------
  592. @pyqtSlot(int, int, int, str)
  593. def slot_handlePatchbayClientAddedCallback(self, clientId, clientIcon, pluginId, clientName):
  594. pcSplit = patchcanvas.SPLIT_UNDEF
  595. pcIcon = patchcanvas.ICON_APPLICATION
  596. if clientIcon == PATCHBAY_ICON_PLUGIN:
  597. pcIcon = patchcanvas.ICON_PLUGIN
  598. if clientIcon == PATCHBAY_ICON_HARDWARE:
  599. pcIcon = patchcanvas.ICON_HARDWARE
  600. elif clientIcon == PATCHBAY_ICON_CARLA:
  601. pass
  602. elif clientIcon == PATCHBAY_ICON_DISTRHO:
  603. pcIcon = patchcanvas.ICON_DISTRHO
  604. elif clientIcon == PATCHBAY_ICON_FILE:
  605. pcIcon = patchcanvas.ICON_FILE
  606. patchcanvas.addGroup(clientId, clientName, pcSplit, pcIcon)
  607. QTimer.singleShot(0, self.fMiniCanvasPreview.update)
  608. if pluginId < 0:
  609. return
  610. if pluginId >= self.fPluginCount:
  611. print("sorry, can't map this plugin to canvas client", pluginId, self.fPluginCount)
  612. return
  613. patchcanvas.setGroupAsPlugin(clientId, pluginId, bool(gCarla.host.get_plugin_info(pluginId)['hints'] & PLUGIN_HAS_CUSTOM_UI))
  614. @pyqtSlot(int)
  615. def slot_handlePatchbayClientRemovedCallback(self, clientId):
  616. #if not self.fEngineStarted: return
  617. patchcanvas.removeGroup(clientId)
  618. QTimer.singleShot(0, self.fMiniCanvasPreview.update)
  619. @pyqtSlot(int, str)
  620. def slot_handlePatchbayClientRenamedCallback(self, clientId, newClientName):
  621. patchcanvas.renameGroup(clientId, newClientName)
  622. QTimer.singleShot(0, self.fMiniCanvasPreview.update)
  623. @pyqtSlot(int, int, int)
  624. def slot_handlePatchbayClientDataChangedCallback(self, clientId, clientIcon, pluginId):
  625. pcIcon = patchcanvas.ICON_APPLICATION
  626. if clientIcon == PATCHBAY_ICON_PLUGIN:
  627. pcIcon = patchcanvas.ICON_PLUGIN
  628. if clientIcon == PATCHBAY_ICON_HARDWARE:
  629. pcIcon = patchcanvas.ICON_HARDWARE
  630. elif clientIcon == PATCHBAY_ICON_CARLA:
  631. pass
  632. elif clientIcon == PATCHBAY_ICON_DISTRHO:
  633. pcIcon = patchcanvas.ICON_DISTRHO
  634. elif clientIcon == PATCHBAY_ICON_FILE:
  635. pcIcon = patchcanvas.ICON_FILE
  636. patchcanvas.setGroupIcon(clientId, pcIcon)
  637. QTimer.singleShot(0, self.fMiniCanvasPreview.update)
  638. if pluginId < 0:
  639. return
  640. if pluginId >= self.fPluginCount:
  641. print("sorry, can't map this plugin to canvas client", pluginId, self.getPluginCount())
  642. return
  643. patchcanvas.setGroupAsPlugin(clientId, pluginId, bool(gCarla.host.get_plugin_info(pluginId)['hints'] & PLUGIN_HAS_CUSTOM_UI))
  644. @pyqtSlot(int, int, int, str)
  645. def slot_handlePatchbayPortAddedCallback(self, clientId, portId, portFlags, portName):
  646. if (portFlags & PATCHBAY_PORT_IS_INPUT):
  647. portMode = patchcanvas.PORT_MODE_INPUT
  648. else:
  649. portMode = patchcanvas.PORT_MODE_OUTPUT
  650. if (portFlags & PATCHBAY_PORT_TYPE_AUDIO):
  651. portType = patchcanvas.PORT_TYPE_AUDIO_JACK
  652. elif (portFlags & PATCHBAY_PORT_TYPE_CV):
  653. portType = patchcanvas.PORT_TYPE_AUDIO_JACK # TODO
  654. elif (portFlags & PATCHBAY_PORT_TYPE_MIDI):
  655. portType = patchcanvas.PORT_TYPE_MIDI_JACK
  656. else:
  657. portType = patchcanvas.PORT_TYPE_NULL
  658. patchcanvas.addPort(clientId, portId, portName, portMode, portType)
  659. QTimer.singleShot(0, self.fMiniCanvasPreview.update)
  660. @pyqtSlot(int, int)
  661. def slot_handlePatchbayPortRemovedCallback(self, groupId, portId):
  662. #if not self.fEngineStarted: return
  663. patchcanvas.removePort(portId)
  664. QTimer.singleShot(0, self.fMiniCanvasPreview.update)
  665. @pyqtSlot(int, int, str)
  666. def slot_handlePatchbayPortRenamedCallback(self, groupId, portId, newPortName):
  667. patchcanvas.renamePort(portId, newPortName)
  668. QTimer.singleShot(0, self.fMiniCanvasPreview.update)
  669. @pyqtSlot(int, int, int)
  670. def slot_handlePatchbayConnectionAddedCallback(self, connectionId, portOutId, portInId):
  671. patchcanvas.connectPorts(connectionId, portOutId, portInId)
  672. QTimer.singleShot(0, self.fMiniCanvasPreview.update)
  673. @pyqtSlot(int, int, int)
  674. def slot_handlePatchbayConnectionRemovedCallback(self, connectionId, portOutId, portInId):
  675. #if not self.fEngineStarted: return
  676. patchcanvas.disconnectPorts(connectionId)
  677. QTimer.singleShot(0, self.fMiniCanvasPreview.update)
  678. # -----------------------------------------------------------------
  679. @pyqtSlot()
  680. def slot_canvasArrange(self):
  681. patchcanvas.arrange()
  682. @pyqtSlot()
  683. def slot_canvasRefresh(self):
  684. patchcanvas.clear()
  685. if gCarla.host.is_engine_running():
  686. gCarla.host.patchbay_refresh()
  687. QTimer.singleShot(1000 if self.fParent.fSavedSettings[CARLA_KEY_CANVAS_EYE_CANDY] else 0, self.fMiniCanvasPreview.update)
  688. @pyqtSlot()
  689. def slot_canvasZoomFit(self):
  690. self.scene.zoom_fit()
  691. @pyqtSlot()
  692. def slot_canvasZoomIn(self):
  693. self.scene.zoom_in()
  694. @pyqtSlot()
  695. def slot_canvasZoomOut(self):
  696. self.scene.zoom_out()
  697. @pyqtSlot()
  698. def slot_canvasZoomReset(self):
  699. self.scene.zoom_reset()
  700. @pyqtSlot()
  701. def slot_canvasPrint(self):
  702. self.scene.clearSelection()
  703. self.fExportPrinter = QPrinter()
  704. dialog = QPrintDialog(self.fExportPrinter, self)
  705. if dialog.exec_():
  706. painter = QPainter(self.fExportPrinter)
  707. painter.save()
  708. painter.setRenderHint(QPainter.Antialiasing)
  709. painter.setRenderHint(QPainter.TextAntialiasing)
  710. self.scene.render(painter)
  711. painter.restore()
  712. @pyqtSlot()
  713. def slot_canvasSaveImage(self):
  714. newPath = QFileDialog.getSaveFileName(self, self.tr("Save Image"), filter=self.tr("PNG Image (*.png);;JPEG Image (*.jpg)"))
  715. if newPath:
  716. self.scene.clearSelection()
  717. if newPath.lower().endswith((".jpg",)):
  718. imgFormat = "JPG"
  719. elif newPath.lower().endswith((".png",)):
  720. imgFormat = "PNG"
  721. else:
  722. # File-dialog may not auto-add the extension
  723. imgFormat = "PNG"
  724. newPath += ".png"
  725. self.fExportImage = QImage(self.scene.sceneRect().width(), self.scene.sceneRect().height(), QImage.Format_RGB32)
  726. painter = QPainter(self.fExportImage)
  727. painter.save()
  728. painter.setRenderHint(QPainter.Antialiasing) # TODO - set true, cleanup this
  729. painter.setRenderHint(QPainter.TextAntialiasing)
  730. self.scene.render(painter)
  731. self.fExportImage.save(newPath, imgFormat, 100)
  732. painter.restore()
  733. # -----------------------------------------------------------------
  734. def resizeEvent(self, event):
  735. QFrame.resizeEvent(self, event)
  736. self.slot_miniCanvasCheckSize()
  737. # ------------------------------------------------------------------------------------------------
  738. # Canvas callback
  739. def canvasCallback(action, value1, value2, valueStr):
  740. if action == patchcanvas.ACTION_GROUP_INFO:
  741. pass
  742. elif action == patchcanvas.ACTION_GROUP_RENAME:
  743. pass
  744. elif action == patchcanvas.ACTION_GROUP_SPLIT:
  745. groupId = value1
  746. patchcanvas.splitGroup(groupId)
  747. gCarla.gui.ui.miniCanvasPreview.update()
  748. elif action == patchcanvas.ACTION_GROUP_JOIN:
  749. groupId = value1
  750. patchcanvas.joinGroup(groupId)
  751. gCarla.gui.ui.miniCanvasPreview.update()
  752. elif action == patchcanvas.ACTION_PORT_INFO:
  753. pass
  754. elif action == patchcanvas.ACTION_PORT_RENAME:
  755. pass
  756. elif action == patchcanvas.ACTION_PORTS_CONNECT:
  757. portIdA = value1
  758. portIdB = value2
  759. if not gCarla.host.patchbay_connect(portIdA, portIdB):
  760. print("Connection failed:", gCarla.host.get_last_error())
  761. elif action == patchcanvas.ACTION_PORTS_DISCONNECT:
  762. connectionId = value1
  763. if not gCarla.host.patchbay_disconnect(connectionId):
  764. print("Disconnect failed:", gCarla.host.get_last_error())
  765. elif action == patchcanvas.ACTION_PLUGIN_CLONE:
  766. pluginId = value1
  767. gCarla.host.clone_plugin(pluginId)
  768. elif action == patchcanvas.ACTION_PLUGIN_EDIT:
  769. pluginId = value1
  770. gCarla.gui.fContainer.showEditDialog(pluginId)
  771. elif action == patchcanvas.ACTION_PLUGIN_RENAME:
  772. pluginId = value1
  773. newName = valueStr
  774. gCarla.host.rename_plugin(pluginId, newName)
  775. elif action == patchcanvas.ACTION_PLUGIN_REMOVE:
  776. pluginId = value1
  777. gCarla.host.remove_plugin(pluginId)
  778. elif action == patchcanvas.ACTION_PLUGIN_SHOW_UI:
  779. pluginId = value1
  780. gCarla.host.show_custom_ui(pluginId, True)