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.

822 lines
32KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla plugin host
  4. # Copyright (C) 2011-2013 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 (Global)
  19. from PyQt4.QtCore import QSize
  20. from PyQt4.QtGui import QApplication, QListWidgetItem, QMainWindow
  21. # ------------------------------------------------------------------------------------------------------------
  22. # Imports (Custom Stuff)
  23. import ui_carla
  24. from carla_backend import *
  25. from carla_shared import *
  26. # FIXME, remove later
  27. #from shared_settings import *
  28. # ------------------------------------------------------------------------------------------------------------
  29. # Main Window
  30. class CarlaMainW(QMainWindow):
  31. def __init__(self, parent=None):
  32. QMainWindow.__init__(self, parent)
  33. self.ui = ui_carla.Ui_CarlaMainW()
  34. self.ui.setupUi(self)
  35. # -------------------------------------------------------------
  36. # Load Settings
  37. self.loadSettings(True)
  38. self.loadRDFs()
  39. self.setStyleSheet("""
  40. QWidget#centralwidget {
  41. background-color: qlineargradient(spread:pad,
  42. x1:0.0, y1:0.0,
  43. x2:0.2, y2:1.0,
  44. stop:0 rgb( 7, 7, 7),
  45. stop:1 rgb(28, 28, 28)
  46. );
  47. }
  48. """)
  49. # -------------------------------------------------------------
  50. # Internal stuff
  51. self.fEngineStarted = False
  52. self.fFirstEngineInit = False
  53. self.fProjectFilename = None
  54. self.fProjectLoading = False
  55. self.fPluginCount = 0
  56. self.fPluginList = []
  57. self.fIdleTimerFast = 0
  58. self.fIdleTimerSlow = 0
  59. #self._nsmAnnounce2str = ""
  60. #self._nsmOpen1str = ""
  61. #self._nsmOpen2str = ""
  62. #self.nsm_server = None
  63. #self.nsm_url = None
  64. # -------------------------------------------------------------
  65. # Set-up GUI stuff
  66. self.ui.act_engine_start.setEnabled(False)
  67. self.ui.act_engine_stop.setEnabled(False)
  68. self.ui.act_plugin_remove_all.setEnabled(False)
  69. self.resize(self.width(), 0)
  70. #self.m_fakeEdit = PluginEdit(self, -1)
  71. #self.m_curEdit = self.m_fakeEdit
  72. #self.w_edit.layout().addWidget(self.m_curEdit)
  73. #self.w_edit.layout().addStretch()
  74. # -------------------------------------------------------------
  75. # Connect actions to functions
  76. self.connect(self.ui.act_file_new, SIGNAL("triggered()"), SLOT("slot_fileNew()"))
  77. self.connect(self.ui.act_file_open, SIGNAL("triggered()"), SLOT("slot_fileOpen()"))
  78. self.connect(self.ui.act_file_save, SIGNAL("triggered()"), SLOT("slot_fileSave()"))
  79. self.connect(self.ui.act_file_save_as, SIGNAL("triggered()"), SLOT("slot_fileSaveAs()"))
  80. self.connect(self.ui.act_engine_start, SIGNAL("triggered()"), SLOT("slot_engineStart()"))
  81. self.connect(self.ui.act_engine_stop, SIGNAL("triggered()"), SLOT("slot_engineStop()"))
  82. self.connect(self.ui.act_plugin_add, SIGNAL("triggered()"), SLOT("slot_pluginAdd()"))
  83. self.connect(self.ui.act_plugin_remove_all, SIGNAL("triggered()"), SLOT("slot_pluginRemoveAll()"))
  84. self.connect(self.ui.act_settings_configure, SIGNAL("triggered()"), SLOT("slot_configureCarla()"))
  85. self.connect(self.ui.act_help_about, SIGNAL("triggered()"), SLOT("slot_aboutCarla()"))
  86. self.connect(self.ui.act_help_about_qt, SIGNAL("triggered()"), app, SLOT("aboutQt()"))
  87. self.connect(self, SIGNAL("SIGUSR1()"), SLOT("slot_handleSIGUSR1()"))
  88. self.connect(self, SIGNAL("SIGTERM()"), SLOT("slot_handleSIGTERM()"))
  89. self.connect(self, SIGNAL("DebugCallback(int, int, int, double, QString)"), SLOT("slot_handleDebugCallback(int, int, int, double, QString)"))
  90. self.connect(self, SIGNAL("PluginAddedCallback(int)"), SLOT("slot_handlePluginAddedCallback(int)"))
  91. self.connect(self, SIGNAL("PluginRemovedCallback(int)"), SLOT("slot_handlePluginRemovedCallback(int)"))
  92. self.connect(self, SIGNAL("ParameterValueChangedCallback(int, int, double)"), SLOT("slot_handleParameterValueChangedCallback(int, int, double)"))
  93. self.connect(self, SIGNAL("ParameterDefaultChangedCallback(int, int, double)"), SLOT("slot_handleParameterDefaultChangedCallback(int, int, double)"))
  94. self.connect(self, SIGNAL("ParameterMidiChannelChangedCallback(int, int, int)"), SLOT("slot_handleParameterMidiChannelChangedCallback(int, int, int)"))
  95. self.connect(self, SIGNAL("ParameterMidiCcChangedCallback(int, int, int)"), SLOT("slot_handleParameterMidiCcChangedCallback(int, int, int)"))
  96. self.connect(self, SIGNAL("ProgramChangedCallback(int, int)"), SLOT("slot_handleProgramChangedCallback(int, int)"))
  97. self.connect(self, SIGNAL("MidiProgramChangedCallback(int, int)"), SLOT("slot_handleMidiProgramChangedCallback(int, int)"))
  98. self.connect(self, SIGNAL("NoteOnCallback(int, int, int, int)"), SLOT("slot_handleNoteOnCallback(int, int, int, int)"))
  99. self.connect(self, SIGNAL("NoteOffCallback(int, int, int)"), SLOT("slot_handleNoteOffCallback(int, int, int)"))
  100. self.connect(self, SIGNAL("ShowGuiCallback(int, int)"), SLOT("slot_handleShowGuiCallback(int, int)"))
  101. self.connect(self, SIGNAL("UpdateCallback(int)"), SLOT("slot_handleUpdateCallback(int)"))
  102. self.connect(self, SIGNAL("ReloadInfoCallback(int)"), SLOT("slot_handleReloadInfoCallback(int)"))
  103. self.connect(self, SIGNAL("ReloadParametersCallback(int)"), SLOT("slot_handleReloadParametersCallback(int)"))
  104. self.connect(self, SIGNAL("ReloadProgramsCallback(int)"), SLOT("slot_handleReloadProgramsCallback(int)"))
  105. self.connect(self, SIGNAL("ReloadAllCallback(int)"), SLOT("slot_handleReloadAllCallback(int)"))
  106. #self.connect(self, SIGNAL("NSM_AnnounceCallback()"), SLOT("slot_handleNSM_AnnounceCallback()"))
  107. #self.connect(self, SIGNAL("NSM_Open1Callback()"), SLOT("slot_handleNSM_Open1Callback()"))
  108. #self.connect(self, SIGNAL("NSM_Open2Callback()"), SLOT("slot_handleNSM_Open2Callback()"))
  109. #self.connect(self, SIGNAL("NSM_SaveCallback()"), SLOT("slot_handleNSM_SaveCallback()"))
  110. self.connect(self, SIGNAL("ErrorCallback(QString)"), SLOT("slot_handleErrorCallback(QString)"))
  111. self.connect(self, SIGNAL("QuitCallback()"), SLOT("slot_handleQuitCallback()"))
  112. #NSM_URL = os.getenv("NSM_URL")
  113. #if NSM_URL:
  114. #Carla.host.nsm_announce(NSM_URL, os.getpid())
  115. #else:
  116. QTimer.singleShot(0, self, SLOT("slot_engineStart()"))
  117. def startEngine(self, clientName = "Carla"):
  118. # ---------------------------------------------
  119. # Engine settings
  120. settings = QSettings()
  121. Carla.processMode = settings.value("Engine/ProcessMode", PROCESS_MODE_MULTIPLE_CLIENTS, type=int)
  122. Carla.maxParameters = settings.value("Engine/MaxParameters", MAX_DEFAULT_PARAMETERS, type=int)
  123. forceStereo = settings.value("Engine/ForceStereo", False, type=bool)
  124. preferPluginBridges = settings.value("Engine/PreferPluginBridges", False, type=bool)
  125. preferUiBridges = settings.value("Engine/PreferUiBridges", True, type=bool)
  126. useDssiVstChunks = settings.value("Engine/UseDssiVstChunks", False, type=bool)
  127. oscUiTimeout = settings.value("Engine/OscUiTimeout", 40, type=int)
  128. preferredBufferSize = settings.value("Engine/PreferredBufferSize", 512, type=int)
  129. preferredSampleRate = settings.value("Engine/PreferredSampleRate", 44100, type=int)
  130. if Carla.processMode == PROCESS_MODE_CONTINUOUS_RACK:
  131. forceStereo = True
  132. elif Carla.processMode == PROCESS_MODE_MULTIPLE_CLIENTS and os.getenv("LADISH_APP_NAME"):
  133. print("LADISH detected but using multiple clients (not allowed), forcing single client now")
  134. Carla.processMode = PROCESS_MODE_SINGLE_CLIENT
  135. Carla.host.set_option(OPTION_PROCESS_MODE, Carla.processMode, "")
  136. Carla.host.set_option(OPTION_MAX_PARAMETERS, Carla.maxParameters, "")
  137. Carla.host.set_option(OPTION_FORCE_STEREO, forceStereo, "")
  138. Carla.host.set_option(OPTION_PREFER_PLUGIN_BRIDGES, preferPluginBridges, "")
  139. Carla.host.set_option(OPTION_PREFER_UI_BRIDGES, preferUiBridges, "")
  140. Carla.host.set_option(OPTION_USE_DSSI_VST_CHUNKS, useDssiVstChunks, "")
  141. Carla.host.set_option(OPTION_OSC_UI_TIMEOUT, oscUiTimeout, "")
  142. Carla.host.set_option(OPTION_PREFERRED_BUFFER_SIZE, preferredBufferSize, "")
  143. Carla.host.set_option(OPTION_PREFERRED_SAMPLE_RATE, preferredSampleRate, "")
  144. # ---------------------------------------------
  145. # start
  146. audioDriver = settings.value("Engine/AudioDriver", "JACK", type=str)
  147. if not Carla.host.engine_init(audioDriver, clientName):
  148. if self.fFirstEngineInit:
  149. self.fFirstEngineInit = False
  150. return
  151. #self.ui.act_engine_start.setEnabled(True)
  152. #self.ui.act_engine_stop.setEnabled(False)
  153. audioError = cString(Carla.host.get_last_error())
  154. if audioError:
  155. QMessageBox.critical(self, self.tr("Error"), self.tr("Could not connect to Audio backend '%s', possible reasons: %s" % (audioDriver, audioError)))
  156. else:
  157. QMessageBox.critical(self, self.tr("Error"), self.tr("Could not connect to Audio backend '%s'" % audioDriver))
  158. return
  159. self.fEngineStarted = True
  160. self.fFirstEngineInit = False
  161. self.fPluginCount = 0
  162. self.fPluginList = []
  163. if Carla.processMode == PROCESS_MODE_CONTINUOUS_RACK:
  164. maxCount = MAX_RACK_PLUGINS
  165. elif Carla.processMode == PROCESS_MODE_PATCHBAY:
  166. maxCount = MAX_PATCHBAY_PLUGINS
  167. else:
  168. maxCount = MAX_DEFAULT_PLUGINS
  169. for x in range(maxCount):
  170. self.fPluginList.append(None)
  171. # Peaks
  172. self.fIdleTimerFast = self.startTimer(self.fSavedSettings["Main/RefreshInterval"])
  173. # LEDs and edit dialog parameters
  174. self.fIdleTimerSlow = self.startTimer(self.fSavedSettings["Main/RefreshInterval"]*2)
  175. def stopEngine(self):
  176. if self.fPluginCount > 0:
  177. ask = QMessageBox.question(self, self.tr("Warning"), self.tr("There are still some plugins loaded, you need to remove them to stop the engine.\n"
  178. "Do you want to do this now?"),
  179. QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
  180. if ask != QMessageBox.Yes:
  181. return
  182. self.removeAllPlugins()
  183. if Carla.host.is_engine_running() and not Carla.host.engine_close():
  184. print(cString(Carla.host.get_last_error()))
  185. self.fEngineStarted = False
  186. self.fPluginCount = 0
  187. self.fPluginList = []
  188. self.killTimer(self.fIdleTimerFast)
  189. self.killTimer(self.fIdleTimerSlow)
  190. def loadProject(self, filename):
  191. self.fProjectLoading = True
  192. self.fProjectFilename = filename
  193. Carla.host.load_project(filename)
  194. def loadProjectLater(self, filename):
  195. self.fProjectLoading = True
  196. self.fProjectFilename = filename
  197. self.setWindowTitle("Carla - %s" % os.path.basename(filename))
  198. QTimer.singleShot(0, self, SLOT("slot_loadProjectLater()"))
  199. def saveProject(self):
  200. Carla.host.save_project(self.fProjectFilename)
  201. def addPlugin(self, btype, ptype, filename, name, label, extraStuff):
  202. if not self.fEngineStarted:
  203. QMessageBox.warning(self, self.tr("Warning"), self.tr("Cannot add new plugins while engine is stopped"))
  204. return False
  205. if not Carla.host.add_plugin(btype, ptype, filename, name, label, extraStuff):
  206. CustomMessageBox(self, QMessageBox.Critical, self.tr("Error"), self.tr("Failed to load plugin"), cString(Carla.host.get_last_error()), QMessageBox.Ok, QMessageBox.Ok)
  207. return False
  208. return True
  209. def removeAllPlugins(self):
  210. while (self.ui.w_plugins.layout().takeAt(0)):
  211. pass
  212. for i in range(self.fPluginCount):
  213. pwidget = self.fPluginList[i]
  214. if pwidget is None:
  215. break
  216. self.fPluginList[i] = None
  217. pwidget.ui.edit_dialog.close()
  218. pwidget.close()
  219. pwidget.deleteLater()
  220. del pwidget
  221. self.fPluginCount = 0
  222. @pyqtSlot()
  223. def slot_fileNew(self):
  224. self.removeAllPlugins()
  225. self.fProjectFilename = None
  226. self.fProjectLoading = False
  227. self.setWindowTitle("Carla")
  228. @pyqtSlot()
  229. def slot_fileOpen(self):
  230. fileFilter = self.tr("Carla Project File (*.carxp)")
  231. filenameTry = QFileDialog.getOpenFileName(self, self.tr("Open Carla Project File"), self.fSavedSettings["Main/DefaultProjectFolder"], filter=fileFilter)
  232. if filenameTry:
  233. # FIXME - show dialog to user
  234. self.removeAllPlugins()
  235. self.loadProject(filenameTry)
  236. self.setWindowTitle("Carla - %s" % os.path.basename(filenameTry))
  237. @pyqtSlot()
  238. def slot_fileSave(self, saveAs=False):
  239. if self.fProjectFilename and not saveAs:
  240. return self.saveProject()
  241. fileFilter = self.tr("Carla Project File (*.carxp)")
  242. filenameTry = QFileDialog.getSaveFileName(self, self.tr("Save Carla Project File"), self.fSavedSettings["Main/DefaultProjectFolder"], filter=fileFilter)
  243. if filenameTry:
  244. if not filenameTry.endswith(".carxp"):
  245. filenameTry += ".carxp"
  246. self.fProjectFilename = filenameTry
  247. self.saveProject()
  248. self.setWindowTitle("Carla - %s" % os.path.basename(filenameTry))
  249. @pyqtSlot()
  250. def slot_fileSaveAs(self):
  251. self.slot_fileSave(True)
  252. @pyqtSlot()
  253. def slot_loadProjectLater(self):
  254. Carla.host.load_project(self.fProjectFilename)
  255. @pyqtSlot()
  256. def slot_engineStart(self):
  257. self.startEngine()
  258. check = Carla.host.is_engine_running()
  259. self.ui.act_file_open.setEnabled(check)
  260. self.ui.act_engine_start.setEnabled(not check)
  261. self.ui.act_engine_stop.setEnabled(check)
  262. @pyqtSlot()
  263. def slot_engineStop(self):
  264. self.stopEngine()
  265. check = Carla.host.is_engine_running()
  266. self.ui.act_file_open.setEnabled(check)
  267. self.ui.act_engine_start.setEnabled(not check)
  268. self.ui.act_engine_stop.setEnabled(check)
  269. @pyqtSlot()
  270. def slot_pluginAdd(self):
  271. dialog = PluginDatabaseW(self)
  272. if dialog.exec_():
  273. btype = dialog.fRetPlugin['build']
  274. ptype = dialog.fRetPlugin['type']
  275. filename = dialog.fRetPlugin['binary']
  276. label = dialog.fRetPlugin['label']
  277. extraStuff = self.getExtraStuff(dialog.fRetPlugin)
  278. self.addPlugin(btype, ptype, filename, None, label, extraStuff)
  279. @pyqtSlot()
  280. def slot_pluginRemoveAll(self):
  281. self.removeAllPlugins()
  282. @pyqtSlot()
  283. def slot_aboutCarla(self):
  284. CarlaAboutW(self).exec_()
  285. @pyqtSlot()
  286. def slot_configureCarla(self):
  287. CarlaAboutW(self).exec_()
  288. @pyqtSlot()
  289. def slot_handleSIGUSR1(self):
  290. print("Got SIGUSR1 -> Saving project now")
  291. QTimer.singleShot(0, self, SLOT("slot_fileSave()"))
  292. @pyqtSlot()
  293. def slot_handleSIGTERM(self):
  294. print("Got SIGTERM -> Closing now")
  295. self.close()
  296. @pyqtSlot(int, int, int, float, str)
  297. def slot_handleDebugCallback(self, pluginId, value1, value2, value3, valueStr):
  298. print("DEBUG :: %i, %i, %i, %f, \"%s\")" % (pluginId, value1, value2, value3, valueStr))
  299. @pyqtSlot(int)
  300. def slot_handlePluginAddedCallback(self, pluginId, pluginName="todo"):
  301. pwidget = PluginWidget(self, pluginId)
  302. pwidget.setRefreshRate(self.fSavedSettings["Main/RefreshInterval"])
  303. self.ui.w_plugins.layout().addWidget(pwidget)
  304. self.fPluginCount += 1
  305. self.fPluginList[pluginId] = pwidget
  306. if self.fPluginCount == 1:
  307. self.ui.act_plugin_remove_all.setEnabled(True)
  308. if not self.fProjectLoading:
  309. pwidget.setActive(True, True, True)
  310. @pyqtSlot(int)
  311. def slot_handlePluginRemovedCallback(self, pluginId):
  312. pwidget = self.fPluginList[pluginId]
  313. if pwidget is None:
  314. return
  315. self.fPluginList[pluginId] = None
  316. self.fPluginCount -= 1
  317. self.ui.w_plugins.layout().removeWidget(pwidget)
  318. pwidget.ui.edit_dialog.close()
  319. pwidget.close()
  320. pwidget.deleteLater()
  321. del pwidget
  322. # push all plugins 1 slot back
  323. for i in range(self.fPluginCount):
  324. if i < pluginId:
  325. continue
  326. self.fPluginList[i] = self.fPluginList[i+1]
  327. self.fPluginList[i].setId(i)
  328. if self.fPluginCount == 0:
  329. self.ui.act_plugin_remove_all.setEnabled(False)
  330. @pyqtSlot(int, int, float)
  331. def slot_handleParameterValueChangedCallback(self, pluginId, parameterId, value):
  332. pwidget = self.fPluginList[pluginId]
  333. if pwidget is None:
  334. return
  335. pwidget.setParameterValue(value, True, False)
  336. @pyqtSlot(int, int, float)
  337. def slot_handleParameterDefaultChangedCallback(self, pluginId, parameterId, value):
  338. pwidget = self.fPluginList[pluginId]
  339. if pwidget is None:
  340. return
  341. pwidget.setParameterDefault(value, True, False)
  342. @pyqtSlot(int, int, int)
  343. def slot_handleParameterMidiChannelChangedCallback(self, pluginId, parameterId, channel):
  344. pwidget = self.fPluginList[pluginId]
  345. if pwidget is None:
  346. return
  347. pwidget.setParameterMidiChannel(parameterId, channel, True)
  348. @pyqtSlot(int, int, int)
  349. def slot_handleParameterMidiCcChangedCallback(self, pluginId, parameterId, cc):
  350. pwidget = self.fPluginList[pluginId]
  351. if pwidget is None:
  352. return
  353. pwidget.setParameterMidiControl(parameterId, cc, True)
  354. @pyqtSlot(int, int)
  355. def slot_handleProgramChangedCallback(self, pluginId, programId):
  356. pwidget = self.fPluginList[pluginId]
  357. if pwidget is None:
  358. return
  359. pwidget.setProgram(programId)
  360. @pyqtSlot(int, int)
  361. def slot_handleMidiProgramChangedCallback(self, pluginId, midiProgramId):
  362. pwidget = self.fPluginList[pluginId]
  363. if pwidget is None:
  364. return
  365. pwidget.setMidiProgram(midiProgramId)
  366. @pyqtSlot(int, int, int, int)
  367. def slot_handleNoteOnCallback(self, pluginId, channel, note, velo):
  368. pwidget = self.fPluginList[pluginId]
  369. if pwidget is None:
  370. return
  371. pwidget.ui.edit_dialog.keyboard.sendNoteOn(note, False)
  372. @pyqtSlot(int, int, int)
  373. def slot_handleNoteOffCallback(self, pluginId, channel, note):
  374. pwidget = self.fPluginList[pluginId]
  375. if pwidget is None:
  376. return
  377. pwidget.ui.edit_dialog.keyboard.sendNoteOff(note, False)
  378. @pyqtSlot(int, int)
  379. def slot_handleShowGuiCallback(self, pluginId, show):
  380. pwidget = self.fPluginList[pluginId]
  381. if pwidget is None:
  382. return
  383. if show == 0:
  384. pwidget.ui.b_gui.setChecked(False)
  385. pwidget.ui.b_gui.setEnabled(True)
  386. elif show == 1:
  387. pwidget.ui.b_gui.setChecked(True)
  388. pwidget.ui.b_gui.setEnabled(True)
  389. elif show == -1:
  390. pwidget.ui.b_gui.setChecked(False)
  391. pwidget.ui.b_gui.setEnabled(False)
  392. @pyqtSlot(int)
  393. def slot_handleUpdateCallback(self, pluginId):
  394. pwidget = self.fPluginList[pluginId]
  395. if pwidget is None:
  396. return
  397. pwidget.ui.edit_dialog.do_update()
  398. @pyqtSlot(int)
  399. def slot_handleReloadInfoCallback(self, pluginId):
  400. pwidget = self.fPluginList[pluginId]
  401. if pwidget is None:
  402. return
  403. pwidget.ui.edit_dialog.reloadInfo()
  404. @pyqtSlot(int)
  405. def slot_handleReloadParametersCallback(self, pluginId):
  406. pwidget = self.fPluginList[pluginId]
  407. if pwidget is None:
  408. return
  409. pwidget.ui.edit_dialog.reloadParameters()
  410. @pyqtSlot(int)
  411. def slot_handleReloadProgramsCallback(self, pluginId):
  412. pwidget = self.fPluginList[pluginId]
  413. if pwidget is None:
  414. return
  415. pwidget.ui.edit_dialog.reloadPrograms()
  416. @pyqtSlot(int)
  417. def slot_handleReloadAllCallback(self, pluginId):
  418. pwidget = self.fPluginList[pluginId]
  419. if pwidget is None:
  420. return
  421. pwidget.ui.edit_dialog.reloadAll()
  422. @pyqtSlot(str)
  423. def slot_handleErrorCallback(self, error):
  424. QMessageBox.critical(self, self.tr("Error"), error)
  425. @pyqtSlot()
  426. def slot_handleQuitCallback(self):
  427. CustomMessageBox(self, QMessageBox.Warning, self.tr("Warning"),
  428. self.tr("Engine has been stopped or crashed.\nPlease restart Carla"),
  429. self.tr("You may want to save your session now..."), QMessageBox.Ok, QMessageBox.Ok)
  430. def getExtraStuff(self, plugin):
  431. ptype = plugin['type']
  432. if ptype == PLUGIN_LADSPA:
  433. uniqueId = plugin['uniqueId']
  434. for rdfItem in self.fLadspaRdfList:
  435. if rdfItem.UniqueID == uniqueId:
  436. return pointer(rdfItem)
  437. elif ptype == PLUGIN_DSSI:
  438. if (plugin['hints'] & PLUGIN_HAS_GUI):
  439. gui = findDSSIGUI(plugin['binary'], plugin['name'], plugin['label'])
  440. if gui:
  441. return gui.encode("utf-8")
  442. return c_nullptr
  443. def loadRDFs(self):
  444. # Save RDF info for later
  445. self.fLadspaRdfList = []
  446. if not haveLRDF:
  447. return
  448. settingsDir = os.path.join(HOME, ".config", "Cadence")
  449. frLadspaFile = os.path.join(settingsDir, "ladspa_rdf.db")
  450. if os.path.exists(frLadspaFile):
  451. frLadspa = open(frLadspaFile, 'r')
  452. try:
  453. self.fLadspaRdfList = ladspa_rdf.get_c_ladspa_rdfs(json.load(frLadspa))
  454. except:
  455. pass
  456. frLadspa.close()
  457. def saveSettings(self):
  458. settings = QSettings()
  459. settings.setValue("Geometry", self.saveGeometry())
  460. settings.setValue("ShowToolbar", self.ui.toolBar.isVisible())
  461. def loadSettings(self, geometry):
  462. settings = QSettings()
  463. if geometry:
  464. self.restoreGeometry(settings.value("Geometry", ""))
  465. showToolbar = settings.value("ShowToolbar", True, type=bool)
  466. self.ui.act_settings_show_toolbar.setChecked(showToolbar)
  467. self.ui.toolBar.setVisible(showToolbar)
  468. self.fSavedSettings = {
  469. "Main/DefaultProjectFolder": settings.value("Main/DefaultProjectFolder", HOME, type=str),
  470. "Main/RefreshInterval": settings.value("Main/RefreshInterval", 50, type=int)
  471. }
  472. # ---------------------------------------------
  473. # plugin checks
  474. if settings.value("Engine/DisableChecks", False, type=bool):
  475. os.environ["CARLA_DISCOVERY_NO_PROCESSING_CHECKS"] = "true"
  476. elif os.getenv("CARLA_DISCOVERY_NO_PROCESSING_CHECKS"):
  477. os.environ.pop("CARLA_DISCOVERY_NO_PROCESSING_CHECKS")
  478. # ---------------------------------------------
  479. # plugin paths
  480. global LADSPA_PATH, DSSI_PATH, LV2_PATH, VST_PATH, GIG_PATH, SF2_PATH, SFZ_PATH
  481. LADSPA_PATH = toList(settings.value("Paths/LADSPA", LADSPA_PATH))
  482. DSSI_PATH = toList(settings.value("Paths/DSSI", DSSI_PATH))
  483. LV2_PATH = toList(settings.value("Paths/LV2", LV2_PATH))
  484. VST_PATH = toList(settings.value("Paths/VST", VST_PATH))
  485. GIG_PATH = toList(settings.value("Paths/GIG", GIG_PATH))
  486. SF2_PATH = toList(settings.value("Paths/SF2", SF2_PATH))
  487. SFZ_PATH = toList(settings.value("Paths/SFZ", SFZ_PATH))
  488. os.environ["LADSPA_PATH"] = splitter.join(LADSPA_PATH)
  489. os.environ["DSSI_PATH"] = splitter.join(DSSI_PATH)
  490. os.environ["LV2_PATH"] = splitter.join(LV2_PATH)
  491. os.environ["VST_PATH"] = splitter.join(VST_PATH)
  492. os.environ["GIG_PATH"] = splitter.join(GIG_PATH)
  493. os.environ["SF2_PATH"] = splitter.join(SF2_PATH)
  494. os.environ["SFZ_PATH"] = splitter.join(SFZ_PATH)
  495. def timerEvent(self, event):
  496. if event.timerId() == self.fIdleTimerFast:
  497. Carla.host.engine_idle()
  498. for pwidget in self.fPluginList:
  499. if pwidget is None:
  500. break
  501. pwidget.idleFast()
  502. elif event.timerId() == self.fIdleTimerSlow:
  503. for pwidget in self.fPluginList:
  504. if pwidget is None:
  505. break
  506. pwidget.idleSlow()
  507. QMainWindow.timerEvent(self, event)
  508. def closeEvent(self, event):
  509. #if self.nsm_server:
  510. #self.nsm_server.stop()
  511. self.saveSettings()
  512. self.removeAllPlugins()
  513. self.stopEngine()
  514. QMainWindow.closeEvent(self, event)
  515. # ------------------------------------------------------------------------------------------------
  516. def callbackFunction(ptr, action, pluginId, value1, value2, value3, valueStr):
  517. if pluginId < 0 or not Carla.gui:
  518. return
  519. if action == CALLBACK_DEBUG:
  520. return Carla.gui.emit(SIGNAL("DebugCallback(int, int, int, double, QString)"), pluginId, value1, value2, value3, cString(valueStr))
  521. elif action == CALLBACK_PLUGIN_ADDED:
  522. return Carla.gui.emit(SIGNAL("PluginAddedCallback(int)"), pluginId)
  523. elif action == CALLBACK_PLUGIN_REMOVED:
  524. return Carla.gui.emit(SIGNAL("PluginRemovedCallback(int)"), pluginId)
  525. elif action == CALLBACK_PARAMETER_VALUE_CHANGED:
  526. return Carla.gui.emit(SIGNAL("ParameterValueChangedCallback(int, int, double)"), pluginId, value1, value3)
  527. elif action == CALLBACK_PARAMETER_DEFAULT_CHANGED:
  528. return Carla.gui.emit(SIGNAL("ParameterDefaultChangedCallback(int, int, double)"), pluginId, value1, value3)
  529. elif action == CALLBACK_PARAMETER_MIDI_CHANNEL_CHANGED:
  530. return Carla.gui.emit(SIGNAL("ParameterMidiChannelChangedCallback(int, int, int)"), pluginId, value1, value2)
  531. elif action == CALLBACK_PARAMETER_MIDI_CC_CHANGED:
  532. return Carla.gui.emit(SIGNAL("ParameterMidiCcChangedCallback(int, int, int)"), pluginId, value1, value2)
  533. elif action == CALLBACK_PROGRAM_CHANGED:
  534. return Carla.gui.emit(SIGNAL("ProgramChangedCallback(int, int)"), pluginId, value1)
  535. elif action == CALLBACK_MIDI_PROGRAM_CHANGED:
  536. return Carla.gui.emit(SIGNAL("MidiProgramChangedCallback(int, int)"), pluginId, value1)
  537. elif action == CALLBACK_NOTE_ON:
  538. return Carla.gui.emit(SIGNAL("NoteOnCallback(int, int, int, int)"), pluginId, value1, value2, value3)
  539. elif action == CALLBACK_NOTE_OFF:
  540. return Carla.gui.emit(SIGNAL("NoteOffCallback(int, int, int)"), pluginId, value1, value2)
  541. elif action == CALLBACK_SHOW_GUI:
  542. return Carla.gui.emit(SIGNAL("ShowGuiCallback(int, int)"), pluginId, value1)
  543. elif action == CALLBACK_UPDATE:
  544. return Carla.gui.emit(SIGNAL("UpdateCallback(int)"), pluginId)
  545. elif action == CALLBACK_RELOAD_INFO:
  546. return Carla.gui.emit(SIGNAL("ReloadInfoCallback(int)"), pluginId)
  547. elif action == CALLBACK_RELOAD_PARAMETERS:
  548. return Carla.gui.emit(SIGNAL("ReloadParametersCallback(int)"), pluginId)
  549. elif action == CALLBACK_RELOAD_PROGRAMS:
  550. return Carla.gui.emit(SIGNAL("ReloadProgramsCallback(int)"), pluginId)
  551. elif action == CALLBACK_RELOAD_ALL:
  552. return Carla.gui.emit(SIGNAL("ReloadAllCallback(int)"), pluginId)
  553. #elif action == CALLBACK_NSM_ANNOUNCE:
  554. #Carla.gui._nsmAnnounce2str = cString(Carla.host.get_last_error())
  555. #Carla.gui.emit(SIGNAL("NSM_AnnounceCallback()"))
  556. #return
  557. #elif action == CALLBACK_NSM_OPEN1:
  558. #Carla.gui._nsmOpen1str = cString(valueStr)
  559. #Carla.gui.emit(SIGNAL("NSM_Open1Callback()"))
  560. #return
  561. #elif action == CALLBACK_NSM_OPEN2:
  562. #Carla.gui._nsmOpen2str = cString(valueStr)
  563. #Carla.gui.emit(SIGNAL("NSM_Open2Callback()"))
  564. #return
  565. #elif action == CALLBACK_NSM_SAVE:
  566. #return Carla.gui.emit(SIGNAL("NSM_SaveCallback()"))
  567. elif action == CALLBACK_ERROR:
  568. return Carla.gui.emit(SIGNAL("ErrorCallback(QString)"), valueStr)
  569. elif action == CALLBACK_QUIT:
  570. return Carla.gui.emit(SIGNAL("QuitCallback()"))
  571. #--------------- main ------------------
  572. if __name__ == '__main__':
  573. # App initialization
  574. app = QApplication(sys.argv)
  575. app.setApplicationName("Carla")
  576. app.setApplicationVersion(VERSION)
  577. app.setOrganizationName("Cadence")
  578. app.setWindowIcon(QIcon(":/scalable/carla.svg"))
  579. libPrefix = None
  580. projectFilename = None
  581. for i in range(len(app.arguments())):
  582. if i == 0: continue
  583. argument = app.arguments()[i]
  584. if argument.startswith("--with-libprefix="):
  585. libPrefix = argument.replace("--with-libprefix=", "")
  586. elif os.path.exists(argument):
  587. projectFilename = argument
  588. # Init backend
  589. Carla.host = Host(libPrefix)
  590. Carla.host.set_callback_function(callbackFunction)
  591. Carla.host.set_option(OPTION_PROCESS_NAME, 0, "carla")
  592. # Set bridge paths
  593. if carla_bridge_native:
  594. Carla.host.set_option(OPTION_PATH_BRIDGE_NATIVE, 0, carla_bridge_native)
  595. if carla_bridge_posix32:
  596. Carla.host.set_option(OPTION_PATH_BRIDGE_POSIX32, 0, carla_bridge_posix32)
  597. if carla_bridge_posix64:
  598. Carla.host.set_option(OPTION_PATH_BRIDGE_POSIX64, 0, carla_bridge_posix64)
  599. if carla_bridge_win32:
  600. Carla.host.set_option(OPTION_PATH_BRIDGE_WIN32, 0, carla_bridge_win32)
  601. if carla_bridge_win64:
  602. Carla.host.set_option(OPTION_PATH_BRIDGE_WIN64, 0, carla_bridge_win64)
  603. if WINDOWS:
  604. if carla_bridge_lv2_windows:
  605. Carla.host.set_option(OPTION_PATH_BRIDGE_LV2_WINDOWS, 0, carla_bridge_lv2_windows)
  606. if carla_bridge_vst_hwnd:
  607. Carla.host.set_option(OPTION_PATH_BRIDGE_VST_HWND, 0, carla_bridge_vst_hwnd)
  608. elif MACOS:
  609. if carla_bridge_lv2_cocoa:
  610. Carla.host.set_option(OPTION_PATH_BRIDGE_LV2_COCOA, 0, carla_bridge_lv2_cocoa)
  611. if carla_bridge_vst_cocoa:
  612. Carla.host.set_option(OPTION_PATH_BRIDGE_VST_COCOA, 0, carla_bridge_vst_cocoa)
  613. else:
  614. if carla_bridge_lv2_gtk2:
  615. Carla.host.set_option(OPTION_PATH_BRIDGE_LV2_GTK2, 0, carla_bridge_lv2_gtk2)
  616. if carla_bridge_lv2_gtk3:
  617. Carla.host.set_option(OPTION_PATH_BRIDGE_LV2_GTK3, 0, carla_bridge_lv2_gtk3)
  618. if carla_bridge_lv2_qt4:
  619. Carla.host.set_option(OPTION_PATH_BRIDGE_LV2_QT4, 0, carla_bridge_lv2_qt4)
  620. if carla_bridge_lv2_qt5:
  621. Carla.host.set_option(OPTION_PATH_BRIDGE_LV2_QT5, 0, carla_bridge_lv2_qt5)
  622. if carla_bridge_lv2_x11:
  623. Carla.host.set_option(OPTION_PATH_BRIDGE_LV2_X11, 0, carla_bridge_lv2_x11)
  624. if carla_bridge_vst_x11:
  625. Carla.host.set_option(OPTION_PATH_BRIDGE_VST_X11, 0, carla_bridge_vst_x11)
  626. # Create GUI and start engine
  627. Carla.gui = CarlaMainW()
  628. # Set-up custom signal handling
  629. setUpSignals(Carla.gui)
  630. # Show GUI
  631. Carla.gui.show()
  632. # Load project file if set
  633. if projectFilename:
  634. Carla.gui.loadProjectLater(projectFilename)
  635. # App-Loop
  636. ret = app.exec_()
  637. # Exit properly
  638. sys.exit(ret)