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

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