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

10 years ago
11 years ago
11 years ago
10 years ago
11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla plugin host
  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 GPL.txt file
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Config)
  19. from carla_config import *
  20. # ------------------------------------------------------------------------------------------------------------
  21. # Imports (Global)
  22. if config_UseQt5:
  23. from PyQt5.QtWidgets import QLabel, QTabWidget
  24. else:
  25. from PyQt4.QtGui import QLabel, QTabWidget
  26. # ------------------------------------------------------------------------------------------------------------
  27. # Imports (Custom Stuff)
  28. from carla_host import *
  29. from carla_patchbay import CarlaPatchbayW
  30. from carla_rack import CarlaRackW
  31. # ------------------------------------------------------------------------------------------------------------
  32. # Tab widget (rack + patchbay)
  33. class CarlaMultiW(QTabWidget):
  34. def __init__(self, parent):
  35. QTabWidget.__init__(self, parent)
  36. self.fRack = CarlaRackW(parent, False)
  37. self.fPatchbay = CarlaPatchbayW(parent, False, False)
  38. self.fParent = parent
  39. self.fUseCustomPaint = False
  40. self.addTab(self.fRack, "Plugins")
  41. self.addTab(self.fPatchbay, "Patchbay")
  42. #self.fPatchbay.hide()
  43. #self.removeTab(1)
  44. #self.fPatchbay.setParent(None)
  45. #self.fPatchbay.show()
  46. self.scene = self.fPatchbay.scene
  47. parent.ui.act_plugins_enable.triggered.connect(self.fRack.slot_pluginsEnable)
  48. parent.ui.act_plugins_disable.triggered.connect(self.fRack.slot_pluginsDisable)
  49. parent.ui.act_plugins_volume100.triggered.connect(self.fRack.slot_pluginsVolume100)
  50. parent.ui.act_plugins_mute.triggered.connect(self.fRack.slot_pluginsMute)
  51. parent.ui.act_plugins_wet100.triggered.connect(self.fRack.slot_pluginsWet100)
  52. parent.ui.act_plugins_bypass.triggered.connect(self.fRack.slot_pluginsBypass)
  53. parent.ui.act_plugins_center.triggered.connect(self.fRack.slot_pluginsCenter)
  54. parent.ui.act_plugins_panic.triggered.connect(self.fRack.slot_pluginsDisable)
  55. parent.ui.act_canvas_arrange.setEnabled(False) # TODO, later
  56. parent.ui.act_canvas_arrange.triggered.connect(self.fPatchbay.slot_canvasArrange)
  57. parent.ui.act_canvas_refresh.triggered.connect(self.fPatchbay.slot_canvasRefresh)
  58. parent.ui.act_canvas_zoom_fit.triggered.connect(self.fPatchbay.slot_canvasZoomFit)
  59. parent.ui.act_canvas_zoom_in.triggered.connect(self.fPatchbay.slot_canvasZoomIn)
  60. parent.ui.act_canvas_zoom_out.triggered.connect(self.fPatchbay.slot_canvasZoomOut)
  61. parent.ui.act_canvas_zoom_100.triggered.connect(self.fPatchbay.slot_canvasZoomReset)
  62. parent.ui.act_canvas_print.triggered.connect(self.fPatchbay.slot_canvasPrint)
  63. parent.ui.act_canvas_save_image.triggered.connect(self.fPatchbay.slot_canvasSaveImage)
  64. parent.ui.act_settings_configure.triggered.connect(self.fPatchbay.slot_configureCarla)
  65. parent.ParameterValueChangedCallback.connect(self.fRack.slot_handleParameterValueChangedCallback)
  66. parent.ParameterValueChangedCallback.connect(self.fPatchbay.slot_handleParameterValueChangedCallback)
  67. parent.ParameterDefaultChangedCallback.connect(self.fRack.slot_handleParameterDefaultChangedCallback)
  68. parent.ParameterMidiChannelChangedCallback.connect(self.fRack.slot_handleParameterMidiChannelChangedCallback)
  69. parent.ParameterMidiCcChangedCallback.connect(self.fRack.slot_handleParameterMidiCcChangedCallback)
  70. parent.ProgramChangedCallback.connect(self.fRack.slot_handleProgramChangedCallback)
  71. parent.MidiProgramChangedCallback.connect(self.fRack.slot_handleMidiProgramChangedCallback)
  72. parent.UiStateChangedCallback.connect(self.fRack.slot_handleUiStateChangedCallback)
  73. parent.NoteOnCallback.connect(self.fRack.slot_handleNoteOnCallback)
  74. parent.NoteOnCallback.connect(self.fPatchbay.slot_handleNoteOnCallback)
  75. parent.NoteOffCallback.connect(self.fRack.slot_handleNoteOffCallback)
  76. parent.NoteOffCallback.connect(self.fPatchbay.slot_handleNoteOffCallback)
  77. parent.UpdateCallback.connect(self.fRack.slot_handleUpdateCallback)
  78. parent.ReloadInfoCallback.connect(self.fRack.slot_handleReloadInfoCallback)
  79. parent.ReloadParametersCallback.connect(self.fRack.slot_handleReloadParametersCallback)
  80. parent.ReloadParametersCallback.connect(self.fPatchbay.slot_handleReloadParametersCallback)
  81. parent.ReloadProgramsCallback.connect(self.fRack.slot_handleReloadProgramsCallback)
  82. parent.ReloadAllCallback.connect(self.fRack.slot_handleReloadAllCallback)
  83. parent.ReloadAllCallback.connect(self.fPatchbay.slot_handleReloadAllCallback)
  84. parent.PatchbayClientAddedCallback.connect(self.fPatchbay.slot_handlePatchbayClientAddedCallback)
  85. parent.PatchbayClientRemovedCallback.connect(self.fPatchbay.slot_handlePatchbayClientRemovedCallback)
  86. parent.PatchbayClientRenamedCallback.connect(self.fPatchbay.slot_handlePatchbayClientRenamedCallback)
  87. parent.PatchbayClientDataChangedCallback.connect(self.fPatchbay.slot_handlePatchbayClientDataChangedCallback)
  88. parent.PatchbayPortAddedCallback.connect(self.fPatchbay.slot_handlePatchbayPortAddedCallback)
  89. parent.PatchbayPortRemovedCallback.connect(self.fPatchbay.slot_handlePatchbayPortRemovedCallback)
  90. parent.PatchbayPortRenamedCallback.connect(self.fPatchbay.slot_handlePatchbayPortRenamedCallback)
  91. parent.PatchbayConnectionAddedCallback.connect(self.fPatchbay.slot_handlePatchbayConnectionAddedCallback)
  92. parent.PatchbayConnectionRemovedCallback.connect(self.fPatchbay.slot_handlePatchbayConnectionRemovedCallback)
  93. # -----------------------------------------------------------------
  94. def getPluginCount(self):
  95. return self.fRack.getPluginCount()
  96. # -----------------------------------------------------------------
  97. def addPlugin(self, pluginId, isProjectLoading):
  98. self.fRack.addPlugin(pluginId, isProjectLoading)
  99. self.fPatchbay.addPlugin(pluginId, isProjectLoading)
  100. def removePlugin(self, pluginId):
  101. self.fRack.removePlugin(pluginId)
  102. self.fPatchbay.removePlugin(pluginId)
  103. def renamePlugin(self, pluginId, newName):
  104. self.fRack.renamePlugin(pluginId, newName)
  105. def disablePlugin(self, pluginId, errorMsg):
  106. self.fRack.disablePlugin(pluginId, errorMsg)
  107. def removeAllPlugins(self):
  108. self.fRack.removeAllPlugins()
  109. self.fPatchbay.removeAllPlugins()
  110. # -----------------------------------------------------------------
  111. def engineStarted(self):
  112. #self.fRack.engineStarted()
  113. #self.fPatchbay.engineStarted()
  114. self.fParent.engineChanged()
  115. def engineStopped(self):
  116. #self.fRack.engineStopped()
  117. self.fPatchbay.engineStopped()
  118. self.fParent.engineStopped()
  119. def engineChanged(self):
  120. self.fParent.engineChanged()
  121. # -----------------------------------------------------------------
  122. def idleFast(self):
  123. self.fRack.idleFast()
  124. self.fPatchbay.idleFast()
  125. def idleSlow(self):
  126. self.fRack.idleSlow()
  127. self.fPatchbay.idleSlow()
  128. # -----------------------------------------------------------------
  129. def projectLoadingStarted(self):
  130. self.fRack.projectLoadingStarted()
  131. #self.fPatchbay.projectLoadingStarted()
  132. def projectLoadingFinished(self):
  133. self.fRack.projectLoadingFinished()
  134. self.fPatchbay.projectLoadingFinished()
  135. # -----------------------------------------------------------------
  136. def saveSettings(self, settings):
  137. self.fPatchbay.saveSettings(settings)
  138. def showEditDialog(self, pluginId):
  139. self.fRack.showEditDialog(pluginId)
  140. # -----------------------------------------------------------------
  141. def fixCanvasPreviewSize(self):
  142. self.fPatchbay.resize(self.fRack.size())
  143. self.fPatchbay.slot_miniCanvasCheckSize()
  144. def setUseCustomPaint(self, useCustomPaint):
  145. if self.fUseCustomPaint != useCustomPaint:
  146. self.fUseCustomPaint = useCustomPaint
  147. self.update()
  148. def paintEvent(self, event):
  149. QTabWidget.paintEvent(self, event)
  150. if MACOS or not self.fUseCustomPaint:
  151. return
  152. painter = QPainter(self)
  153. painter.setBrush(QColor(36, 36, 36))
  154. painter.setPen(QColor(62, 62, 62))
  155. painter.drawRect(1, self.height()/2, self.width()-3, self.height()-self.height()/2-1)
  156. def resizeEvent(self, event):
  157. QTabWidget.resizeEvent(self, event)
  158. if self.currentIndex() == 0:
  159. self.fixCanvasPreviewSize()
  160. # ------------------------------------------------------------------------------------------------------------
  161. # Main Window
  162. class CarlaHostW(HostWindow):
  163. def __init__(self, parent=None):
  164. HostWindow.__init__(self, parent)
  165. # -------------------------------------------------------------
  166. # Set-up container
  167. self.fContainer = CarlaMultiW(self)
  168. self.setupContainer(True, self.fContainer.fPatchbay.themeData)
  169. self.fContainer.setUseCustomPaint(self.fSavedSettings[CARLA_KEY_CUSTOM_PAINTING])
  170. # -------------------------------------------------------------
  171. # Set-up GUI stuff
  172. self.fInfoText = ""
  173. self.fInfoLabel = QLabel(self)
  174. self.fInfoLabel.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
  175. self.fInfoLabel.setText("Engine stopped")
  176. if MACOS and False: # TODO: check if NOT using pro theme
  177. self.fInfoLabel.hide()
  178. self.setUnifiedTitleAndToolBarOnMac(True)
  179. # -------------------------------------------------------------
  180. self.ui.act_settings_show_toolbar.triggered.connect(self.slot_toolbarShown)
  181. self.ui.splitter.splitterMoved.connect(self.slot_splitterMoved)
  182. QTimer.singleShot(0, self.slot_initWidgets)
  183. # -----------------------------------------------------------------
  184. def engineStopped(self):
  185. self.fInfoText = ""
  186. self.fInfoLabel.setText("Engine stopped")
  187. def engineChanged(self):
  188. self.fInfoText = "Engine running | SampleRate: %g | BufferSize: %i" % (gCarla.sampleRate, gCarla.bufferSize)
  189. self.fInfoLabel.setText("%s | %s" % (self.fInfoText, self.fTextTransport))
  190. # -----------------------------------------------------------------
  191. def updateInfoLabelXandSize(self):
  192. tabBar = self.fContainer.tabBar()
  193. x = tabBar.width() + self.ui.tabUtils.width() + 20
  194. self.fInfoLabel.move(x, self.fInfoLabel.y())
  195. self.fInfoLabel.resize(self.fContainer.width()-tabBar.width()-20, self.fInfoLabel.height())
  196. def updateInfoLabelY(self):
  197. tabBar = self.fContainer.tabBar()
  198. y = tabBar.mapFromParent(self.ui.centralwidget.pos()).y()
  199. if not self.ui.toolBar.isVisible():
  200. y -= self.ui.toolBar.height()
  201. self.fInfoLabel.move(self.fInfoLabel.x(), y)
  202. # -----------------------------------------------------------------
  203. @pyqtSlot()
  204. def slot_initWidgets(self):
  205. tabBar = self.fContainer.tabBar()
  206. x = tabBar.width() + self.ui.tabUtils.width() + 20
  207. y = tabBar.mapFromParent(self.ui.centralwidget.pos()).y()
  208. self.fInfoLabel.move(x, y)
  209. self.fInfoLabel.resize(self.fContainer.width()-tabBar.width()-20, tabBar.height())
  210. # FIXME: Qt4 needs this so it properly creates & resizes the canvas
  211. self.fContainer.setCurrentIndex(1)
  212. self.fContainer.setCurrentIndex(0)
  213. self.fContainer.fixCanvasPreviewSize()
  214. @pyqtSlot()
  215. def slot_splitterMoved(self):
  216. self.updateInfoLabelXandSize()
  217. @pyqtSlot()
  218. def slot_toolbarShown(self):
  219. self.updateInfoLabelY()
  220. # -----------------------------------------------------------------
  221. def resizeEvent(self, event):
  222. HostWindow.resizeEvent(self, event)
  223. self.updateInfoLabelXandSize()
  224. def timerEvent(self, event):
  225. HostWindow.timerEvent(self, event)
  226. if event.timerId() == self.fIdleTimerFast:
  227. self.fInfoLabel.setText("%s | %s" % (self.fInfoText, self.fTextTransport))
  228. # ------------------------------------------------------------------------------------------------------------
  229. # Main
  230. if __name__ == '__main__':
  231. # -------------------------------------------------------------
  232. # Read CLI args
  233. initName = os.path.basename(__file__) if ("__file__" in dir() and os.path.dirname(__file__) in PATH) else sys.argv[0]
  234. libPrefix = None
  235. for arg in sys.argv:
  236. if arg.startswith("--with-appname="):
  237. initName = os.path.basename(arg.replace("--with-initname=", ""))
  238. elif arg.startswith("--with-libprefix="):
  239. libPrefix = arg.replace("--with-libprefix=", "")
  240. # -------------------------------------------------------------
  241. # App initialization
  242. app = CarlaApplication("Carla2", libPrefix)
  243. # -------------------------------------------------------------
  244. # Set-up custom signal handling
  245. setUpSignals()
  246. # -------------------------------------------------------------
  247. # Init host backend
  248. gCarla.isControl = False
  249. gCarla.isLocal = True
  250. gCarla.isPlugin = False
  251. initHost(initName, libPrefix)
  252. # -------------------------------------------------------------
  253. # Create GUI
  254. gCarla.gui = CarlaHostW()
  255. # -------------------------------------------------------------
  256. # Load project file if set
  257. args = app.arguments()
  258. if len(args) > 1:
  259. arg = args[-1]
  260. if arg.startswith("--with-appname=") or arg.startswith("--with-libprefix="):
  261. pass
  262. elif os.path.exists(arg):
  263. gCarla.gui.loadProjectLater(arg)
  264. # -------------------------------------------------------------
  265. # Show GUI
  266. gCarla.gui.show()
  267. # -------------------------------------------------------------
  268. # App-Loop
  269. app.exit_exec()