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

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