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

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