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 35KB

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