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_host.py 40KB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla host code
  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 doc/GPL.txt file.
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. try:
  20. from PyQt5.QtCore import QTimer
  21. from PyQt5.QtGui import QPalette
  22. from PyQt5.QtWidgets import QApplication, QMainWindow
  23. except:
  24. from PyQt4.QtCore import QTimer
  25. from PyQt4.QtGui import QApplication, QMainWindow, QPalette
  26. # ------------------------------------------------------------------------------------------------------------
  27. # Imports (Custom)
  28. import ui_carla_host
  29. from carla_database import *
  30. from carla_settings import *
  31. from carla_style import *
  32. from carla_widgets import *
  33. # ------------------------------------------------------------------------------------------------------------
  34. # PatchCanvas defines
  35. CANVAS_ANTIALIASING_SMALL = 1
  36. CANVAS_EYECANDY_SMALL = 1
  37. CANVAS_DEFAULT_THEME_NAME = "Modern Dark"
  38. # ------------------------------------------------------------------------------------------------------------
  39. # Session Management support
  40. LADISH_APP_NAME = os.getenv("LADISH_APP_NAME")
  41. NSM_URL = os.getenv("NSM_URL")
  42. # ------------------------------------------------------------------------------------------------------------
  43. # Dummy widget
  44. class CarlaDummyW(object):
  45. def __init__(self, parent):
  46. object.__init__(self)
  47. # -----------------------------------------------------------------
  48. def getPluginCount(self):
  49. return 0
  50. # -----------------------------------------------------------------
  51. def addPlugin(self, pluginId, isProjectLoading):
  52. pass
  53. def removePlugin(self, pluginId):
  54. pass
  55. def renamePlugin(self, pluginId, newName):
  56. pass
  57. def removeAllPlugins(self):
  58. pass
  59. # -----------------------------------------------------------------
  60. def engineStarted(self):
  61. pass
  62. def engineStopped(self):
  63. pass
  64. # -----------------------------------------------------------------
  65. def idleFast(self):
  66. pass
  67. def idleSlow(self):
  68. pass
  69. # -----------------------------------------------------------------
  70. def saveSettings(self, settings):
  71. pass
  72. # ------------------------------------------------------------------------------------------------------------
  73. # Host Window
  74. class HostWindow(QMainWindow):
  75. # signals
  76. DebugCallback = pyqtSignal(int, int, int, float, str)
  77. PluginAddedCallback = pyqtSignal(int)
  78. PluginRemovedCallback = pyqtSignal(int)
  79. PluginRenamedCallback = pyqtSignal(int, str)
  80. ParameterValueChangedCallback = pyqtSignal(int, int, float)
  81. ParameterDefaultChangedCallback = pyqtSignal(int, int, float)
  82. ParameterMidiChannelChangedCallback = pyqtSignal(int, int, int)
  83. ParameterMidiCcChangedCallback = pyqtSignal(int, int, int)
  84. ProgramChangedCallback = pyqtSignal(int, int)
  85. MidiProgramChangedCallback = pyqtSignal(int, int)
  86. NoteOnCallback = pyqtSignal(int, int, int, int)
  87. NoteOffCallback = pyqtSignal(int, int, int)
  88. ShowGuiCallback = pyqtSignal(int, int)
  89. UpdateCallback = pyqtSignal(int)
  90. ReloadInfoCallback = pyqtSignal(int)
  91. ReloadParametersCallback = pyqtSignal(int)
  92. ReloadProgramsCallback = pyqtSignal(int)
  93. ReloadAllCallback = pyqtSignal(int)
  94. PatchbayClientAddedCallback = pyqtSignal(int, int, str)
  95. PatchbayClientRemovedCallback = pyqtSignal(int, str)
  96. PatchbayClientRenamedCallback = pyqtSignal(int, str)
  97. PatchbayPortAddedCallback = pyqtSignal(int, int, int, str)
  98. PatchbayPortRemovedCallback = pyqtSignal(int, int, str)
  99. PatchbayPortRenamedCallback = pyqtSignal(int, int, str)
  100. PatchbayConnectionAddedCallback = pyqtSignal(int, int, int)
  101. PatchbayConnectionRemovedCallback = pyqtSignal(int)
  102. PatchbayIconChangedCallback = pyqtSignal(int, int)
  103. BufferSizeChangedCallback = pyqtSignal(int)
  104. SampleRateChangedCallback = pyqtSignal(float)
  105. ProcessModeChangedCallback = pyqtSignal(int)
  106. EngineStartedCallback = pyqtSignal(str)
  107. EngineStoppedChangedCallback = pyqtSignal()
  108. NSM_AnnounceCallback = pyqtSignal(str)
  109. NSM_OpenCallback = pyqtSignal(str)
  110. NSM_SaveCallback = pyqtSignal()
  111. ErrorCallback = pyqtSignal(str)
  112. QuitCallback = pyqtSignal()
  113. SIGTERM = pyqtSignal()
  114. SIGUSR1 = pyqtSignal()
  115. def __init__(self, parent):
  116. QMainWindow.__init__(self, parent)
  117. self.ui = ui_carla_host.Ui_CarlaHostW()
  118. self.ui.setupUi(self)
  119. if False:
  120. Carla.gui = self
  121. self.fContainer = CarlaDummyW(self)
  122. # -------------------------------------------------------------
  123. # Set callback
  124. Carla.host.set_engine_callback(EngineCallback)
  125. # -------------------------------------------------------------
  126. # Internal stuff
  127. self.fBufferSize = 0
  128. self.fSampleRate = 0.0
  129. self.fIdleTimerFast = 0
  130. self.fIdleTimerSlow = 0
  131. self.fIsProjectLoading = False
  132. self.fProjectFilename = ""
  133. self.fLadspaRdfNeedsUpdate = True
  134. self.fLadspaRdfList = []
  135. self.fLastTransportFrame = 0
  136. self.fLastTransportState = False
  137. self.fSavedSettings = {}
  138. if LADISH_APP_NAME:
  139. self.fClientName = LADISH_APP_NAME
  140. self.fSessionManagerName = "LADISH"
  141. elif NSM_URL:
  142. self.fClientName = "Carla.tmp"
  143. self.fSessionManagerName = "Non Session Manager"
  144. else:
  145. self.fClientName = "Carla"
  146. self.fSessionManagerName = ""
  147. # -------------------------------------------------------------
  148. # Load Settings
  149. self.loadSettings(True)
  150. # -------------------------------------------------------------
  151. # Set up GUI (engine stopped)
  152. if Carla.isPlugin:
  153. self.ui.act_file_new.setEnabled(False)
  154. self.ui.act_file_open.setEnabled(False)
  155. self.ui.act_engine_start.setEnabled(False)
  156. self.ui.act_engine_stop.setEnabled(False)
  157. self.ui.menu_Engine.setEnabled(False)
  158. else:
  159. self.ui.act_engine_start.setEnabled(True)
  160. self.ui.act_file_save.setEnabled(False)
  161. self.ui.act_file_save_as.setEnabled(False)
  162. self.ui.act_engine_stop.setEnabled(False)
  163. self.ui.act_plugin_remove_all.setEnabled(False)
  164. self.ui.menu_PluginMacros.setEnabled(False)
  165. self.ui.menu_Canvas.setEnabled(False)
  166. self.setTransportMenuEnabled(False)
  167. self.setProperWindowTitle()
  168. # -------------------------------------------------------------
  169. # Connect actions to functions
  170. self.ui.act_file_new.triggered.connect(self.slot_fileNew)
  171. self.ui.act_file_open.triggered.connect(self.slot_fileOpen)
  172. self.ui.act_file_save.triggered.connect(self.slot_fileSave)
  173. self.ui.act_file_save_as.triggered.connect(self.slot_fileSaveAs)
  174. self.ui.act_engine_start.triggered.connect(self.slot_engineStart)
  175. self.ui.act_engine_stop.triggered.connect(self.slot_engineStop)
  176. self.ui.act_plugin_add.triggered.connect(self.slot_pluginAdd)
  177. self.ui.act_plugin_add2.triggered.connect(self.slot_pluginAdd)
  178. self.ui.act_plugin_remove_all.triggered.connect(self.slot_pluginRemoveAll)
  179. self.ui.act_transport_play.triggered.connect(self.slot_transportPlayPause)
  180. self.ui.act_transport_stop.triggered.connect(self.slot_transportStop)
  181. self.ui.act_transport_backwards.triggered.connect(self.slot_transportBackwards)
  182. self.ui.act_transport_forwards.triggered.connect(self.slot_transportForwards)
  183. self.ui.act_help_about.triggered.connect(self.slot_aboutCarla)
  184. self.ui.act_help_about_qt.triggered.connect(self.slot_aboutQt)
  185. #self.ui.splitter.splitterMoved.connect(self.slot_splitterMoved)
  186. #self.ui.cb_disk.currentIndexChanged.connect(self.slot_diskFolderChanged)
  187. #self.ui.b_disk_add.clicked.connect(self.slot_diskFolderAdd)
  188. #self.ui.b_disk_remove.clicked.connect(self.slot_diskFolderRemove)
  189. #self.ui.fileTreeView.doubleClicked.connect(self.slot_fileTreeDoubleClicked)
  190. self.DebugCallback.connect(self.slot_handleDebugCallback)
  191. self.PluginAddedCallback.connect(self.slot_handlePluginAddedCallback)
  192. self.PluginRemovedCallback.connect(self.slot_handlePluginRemovedCallback)
  193. self.PluginRenamedCallback.connect(self.slot_handlePluginRenamedCallback)
  194. self.BufferSizeChangedCallback.connect(self.slot_handleBufferSizeChangedCallback)
  195. self.SampleRateChangedCallback.connect(self.slot_handleSampleRateChangedCallback)
  196. self.EngineStartedCallback.connect(self.slot_handleEngineStartedCallback)
  197. self.EngineStoppedChangedCallback.connect(self.slot_handleEngineStoppedCallback)
  198. #self.NSM_AnnounceCallback.connect(self.slot_handleNSM_AnnounceCallback)
  199. #self.NSM_OpenCallback.connect(self.slot_handleNSM_OpenCallback)
  200. #self.NSM_SaveCallback.connect(self.slot_handleNSM_SaveCallback)
  201. #self.ErrorCallback.connect(self.slot_handleErrorCallback)
  202. #self.QuitCallback.connect(self.slot_handleQuitCallback)
  203. self.SIGUSR1.connect(self.slot_handleSIGUSR1)
  204. self.SIGTERM.connect(self.slot_handleSIGTERM)
  205. # -------------------------------------------------------------
  206. # Final setup
  207. if Carla.isPlugin:
  208. QTimer.singleShot(0, self.slot_engineStart)
  209. elif NSM_URL:
  210. Carla.host.nsm_ready()
  211. # -----------------------------------------------------------------
  212. # Called by containers
  213. def openSettingsWindow(self, hasCanvas, hasCanvasGL):
  214. dialog = CarlaSettingsW(self, hasCanvas, hasCanvasGL)
  215. return dialog.exec_()
  216. # -----------------------------------------------------------------
  217. # Internal stuff (files)
  218. def loadProjectNow(self):
  219. if not self.fProjectFilename:
  220. return qCritical("ERROR: loading project without filename set")
  221. # TESTING
  222. if not Carla.host.is_engine_running():
  223. self.slot_engineStart()
  224. self.fIsProjectLoading = True
  225. Carla.host.load_project(self.fProjectFilename)
  226. self.fIsProjectLoading = False
  227. @pyqtSlot()
  228. def slot_loadProjectNow(self):
  229. self.loadProjectNow()
  230. def loadProjectLater(self, filename):
  231. self.fProjectFilename = filename
  232. self.setProperWindowTitle()
  233. QTimer.singleShot(0, self.slot_loadProjectNow)
  234. def saveProjectNow(self):
  235. if not self.fProjectFilename:
  236. return qCritical("ERROR: saving project without filename set")
  237. Carla.host.save_project(self.fProjectFilename)
  238. # -----------------------------------------------------------------
  239. # Internal stuff (engine)
  240. def setEngineSettings(self, settings = None):
  241. if Carla.isPlugin:
  242. return "Plugin"
  243. if settings is None: settings = QSettings()
  244. forceStereo = settings.value("Engine/ForceStereo", CARLA_DEFAULT_FORCE_STEREO, type=bool)
  245. preferPluginBridges = settings.value("Engine/PreferPluginBridges", CARLA_DEFAULT_PREFER_PLUGIN_BRIDGES, type=bool)
  246. preferUiBridges = settings.value("Engine/PreferUiBridges", CARLA_DEFAULT_PREFER_UI_BRIDGES, type=bool)
  247. uisAlwaysOnTop = settings.value("Engine/OscUiTimeout", CARLA_DEFAULT_UIS_ALWAYS_ON_TOP, type=bool)
  248. uiBridgesTimeout = settings.value("Engine/OscUiTimeout", CARLA_DEFAULT_UI_BRIDGES_TIMEOUT, type=int)
  249. Carla.processMode = settings.value("Engine/ProcessMode", CARLA_DEFAULT_PROCESS_MODE, type=int)
  250. Carla.maxParameters = settings.value("Engine/MaxParameters", CARLA_DEFAULT_MAX_PARAMETERS, type=int)
  251. audioDriver = settings.value("Engine/AudioDriver", CARLA_DEFAULT_AUDIO_DRIVER, type=str)
  252. if audioDriver == "JACK":
  253. #transportMode = settings.value("Engine/TransportMode", TRANSPORT_MODE_JACK, type=int)
  254. transportMode = TRANSPORT_MODE_JACK
  255. else:
  256. transportMode = TRANSPORT_MODE_INTERNAL
  257. audioNumPeriods = settings.value("Engine/AudioBufferSize", CARLA_DEFAULT_AUDIO_NUM_PERIODS, type=int)
  258. audioBufferSize = settings.value("Engine/AudioBufferSize", CARLA_DEFAULT_AUDIO_BUFFER_SIZE, type=int)
  259. audioSampleRate = settings.value("Engine/AudioSampleRate", CARLA_DEFAULT_AUDIO_SAMPLE_RATE, type=int)
  260. audioDevice = settings.value("Engine/AudioDevice", "", type=str)
  261. Carla.host.set_engine_option(OPTION_AUDIO_NUM_PERIODS, audioNumPeriods, "")
  262. Carla.host.set_engine_option(OPTION_AUDIO_BUFFER_SIZE, audioBufferSize, "")
  263. Carla.host.set_engine_option(OPTION_AUDIO_SAMPLE_RATE, audioSampleRate, "")
  264. Carla.host.set_engine_option(OPTION_AUDIO_DEVICE, 0, audioDevice)
  265. if Carla.processMode == PROCESS_MODE_CONTINUOUS_RACK:
  266. forceStereo = True
  267. elif Carla.processMode == PROCESS_MODE_MULTIPLE_CLIENTS: # and LADISH_APP_NAME:
  268. print("LADISH detected but using multiple clients (not allowed), forcing single client now")
  269. Carla.processMode = PROCESS_MODE_SINGLE_CLIENT
  270. Carla.host.set_engine_option(OPTION_FORCE_STEREO, forceStereo, "")
  271. Carla.host.set_engine_option(OPTION_PREFER_PLUGIN_BRIDGES, preferPluginBridges, "")
  272. Carla.host.set_engine_option(OPTION_PREFER_UI_BRIDGES, preferUiBridges, "")
  273. Carla.host.set_engine_option(OPTION_UIS_ALWAYS_ON_TOP, uisAlwaysOnTop, "")
  274. Carla.host.set_engine_option(OPTION_UI_BRIDGES_TIMEOUT, uiBridgesTimeout, "")
  275. Carla.host.set_engine_option(OPTION_PROCESS_MODE, Carla.processMode, "")
  276. Carla.host.set_engine_option(OPTION_MAX_PARAMETERS, Carla.maxParameters, "")
  277. Carla.host.set_engine_option(OPTION_TRANSPORT_MODE, transportMode, "")
  278. return audioDriver
  279. def startEngine(self):
  280. audioDriver = self.setEngineSettings()
  281. if not Carla.host.engine_init(audioDriver, self.fClientName):
  282. #if self.fFirstEngineInit:
  283. #self.fFirstEngineInit = False
  284. #return
  285. audioError = cString(Carla.host.get_last_error())
  286. if audioError:
  287. QMessageBox.critical(self, self.tr("Error"), self.tr("Could not connect to Audio backend '%s', possible reasons:\n%s" % (audioDriver, audioError)))
  288. else:
  289. QMessageBox.critical(self, self.tr("Error"), self.tr("Could not connect to Audio backend '%s'" % audioDriver))
  290. return
  291. self.fBufferSize = Carla.host.get_buffer_size()
  292. self.fSampleRate = Carla.host.get_sample_rate()
  293. #self.fFirstEngineInit = False
  294. # Peaks and TimeInfo
  295. self.fIdleTimerFast = self.startTimer(self.fSavedSettings["Main/RefreshInterval"])
  296. # LEDs and edit dialog parameters
  297. self.fIdleTimerSlow = self.startTimer(self.fSavedSettings["Main/RefreshInterval"]*2)
  298. def stopEngine(self):
  299. if self.fContainer.getPluginCount() > 0:
  300. 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"
  301. "Do you want to do this now?"),
  302. QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
  303. if ask != QMessageBox.Yes:
  304. return
  305. self.ui.act_plugin_remove_all.setEnabled(False)
  306. self.fContainer.removeAllPlugins()
  307. if Carla.host.is_engine_running() and not Carla.host.engine_close():
  308. print(cString(Carla.host.get_last_error()))
  309. self.fBufferSize = 0
  310. self.fSampleRate = 0.0
  311. if self.fIdleTimerFast != 0:
  312. self.killTimer(self.fIdleTimerFast)
  313. self.fIdleTimerFast = 0
  314. if self.fIdleTimerSlow != 0:
  315. self.killTimer(self.fIdleTimerSlow)
  316. self.fIdleTimerSlow = 0
  317. # -----------------------------------------------------------------
  318. # Internal stuff (plugins)
  319. def getExtraStuff(self, plugin):
  320. ptype = plugin['type']
  321. if ptype == PLUGIN_LADSPA:
  322. uniqueId = plugin['uniqueId']
  323. self.maybeLoadRDFs()
  324. for rdfItem in self.fLadspaRdfList:
  325. if rdfItem.UniqueID == uniqueId:
  326. return pointer(rdfItem)
  327. elif ptype in (PLUGIN_GIG, PLUGIN_SF2, PLUGIN_SFZ):
  328. if plugin['name'].lower().endswith(" (16 outputs)"):
  329. # return a dummy non-null pointer
  330. INTPOINTER = POINTER(c_int)
  331. int1 = c_int(0x1)
  332. addr = addressof(int1)
  333. return cast(addr, INTPOINTER)
  334. return c_nullptr
  335. def maybeLoadRDFs(self):
  336. if not self.fLadspaRdfNeedsUpdate:
  337. return
  338. self.fLadspaRdfNeedsUpdate = False
  339. self.fLadspaRdfList = []
  340. if not haveLRDF:
  341. return
  342. settingsDir = os.path.join(HOME, ".config", "falkTX")
  343. frLadspaFile = os.path.join(settingsDir, "ladspa_rdf.db")
  344. if os.path.exists(frLadspaFile):
  345. frLadspa = open(frLadspaFile, 'r')
  346. try:
  347. self.fLadspaRdfList = ladspa_rdf.get_c_ladspa_rdfs(json.load(frLadspa))
  348. except:
  349. pass
  350. frLadspa.close()
  351. def setLoadRDFsNeeded(self):
  352. self.fLadspaRdfNeedsUpdate = True
  353. # -----------------------------------------------------------------
  354. # Internal stuff (transport)
  355. def refreshTransport(self, forced = False):
  356. if not Carla.host.is_engine_running():
  357. return
  358. if self.fSampleRate == 0.0:
  359. return
  360. timeInfo = Carla.host.get_transport_info()
  361. playing = bool(timeInfo['playing'])
  362. frame = int(timeInfo['frame'])
  363. if playing != self.fLastTransportState or forced:
  364. if playing:
  365. icon = getIcon("media-playback-pause")
  366. self.ui.act_transport_play.setChecked(True)
  367. self.ui.act_transport_play.setIcon(icon)
  368. self.ui.act_transport_play.setText(self.tr("&Pause"))
  369. else:
  370. icon = getIcon("media-playback-start")
  371. self.ui.act_transport_play.setChecked(False)
  372. self.ui.act_transport_play.setIcon(icon)
  373. self.ui.act_transport_play.setText(self.tr("&Play"))
  374. self.fLastTransportState = playing
  375. if frame != self.fLastTransportFrame or forced:
  376. time = frame / self.fSampleRate
  377. secs = time % 60
  378. mins = (time / 60) % 60
  379. hrs = (time / 3600) % 60
  380. #textTransport = "Transport %s, at %02i:%02i:%02i" % ("playing" if playing else "stopped", hrs, mins, secs)
  381. #self.fInfoLabel.setText("%s | %s" % (self.fInfoText, textTransport))
  382. self.fLastTransportFrame = frame
  383. def setTransportMenuEnabled(self, enabled):
  384. self.ui.act_transport_play.setEnabled(enabled)
  385. self.ui.act_transport_stop.setEnabled(enabled)
  386. self.ui.act_transport_backwards.setEnabled(enabled)
  387. self.ui.act_transport_forwards.setEnabled(enabled)
  388. self.ui.menu_Transport.setEnabled(enabled)
  389. # -----------------------------------------------------------------
  390. # Internal stuff (settings)
  391. def loadSettings(self, firstTime):
  392. settings = QSettings()
  393. if firstTime:
  394. self.restoreGeometry(settings.value("Geometry", ""))
  395. showToolbar = settings.value("ShowToolbar", True, type=bool)
  396. self.ui.act_settings_show_toolbar.setChecked(showToolbar)
  397. self.ui.toolBar.setVisible(showToolbar)
  398. #if settings.contains("SplitterState"):
  399. #self.ui.splitter.restoreState(settings.value("SplitterState", ""))
  400. #else:
  401. #self.ui.splitter.setSizes([99999, 210])
  402. #diskFolders = toList(settings.value("DiskFolders", [HOME]))
  403. #self.ui.cb_disk.setItemData(0, HOME)
  404. #for i in range(len(diskFolders)):
  405. #if i == 0: continue
  406. #folder = diskFolders[i]
  407. #self.ui.cb_disk.addItem(os.path.basename(folder), folder)
  408. if MACOS and not settings.value("Main/UseProTheme", True, type=bool):
  409. self.setUnifiedTitleAndToolBarOnMac(True)
  410. # ---------------------------------------------
  411. # plugin checks
  412. if settings.value("Engine/DisableChecks", False, type=bool):
  413. os.environ["CARLA_DISCOVERY_NO_PROCESSING_CHECKS"] = "true"
  414. elif os.getenv("CARLA_DISCOVERY_NO_PROCESSING_CHECKS"):
  415. os.environ.pop("CARLA_DISCOVERY_NO_PROCESSING_CHECKS")
  416. # ---------------------------------------------
  417. if not Carla.isPlugin:
  418. # engine
  419. self.setEngineSettings(settings)
  420. # plugin paths
  421. LADSPA_PATH = toList(settings.value("Paths/LADSPA", Carla.DEFAULT_LADSPA_PATH))
  422. DSSI_PATH = toList(settings.value("Paths/DSSI", Carla.DEFAULT_DSSI_PATH))
  423. LV2_PATH = toList(settings.value("Paths/LV2", Carla.DEFAULT_LV2_PATH))
  424. VST_PATH = toList(settings.value("Paths/VST", Carla.DEFAULT_VST_PATH))
  425. AU_PATH = toList(settings.value("Paths/AU", Carla.DEFAULT_AU_PATH))
  426. CSOUND_PATH = toList(settings.value("Paths/CSOUND", Carla.DEFAULT_CSOUND_PATH))
  427. GIG_PATH = toList(settings.value("Paths/GIG", Carla.DEFAULT_GIG_PATH))
  428. SF2_PATH = toList(settings.value("Paths/SF2", Carla.DEFAULT_SF2_PATH))
  429. SFZ_PATH = toList(settings.value("Paths/SFZ", Carla.DEFAULT_SFZ_PATH))
  430. os.environ["LADSPA_PATH"] = splitter.join(LADSPA_PATH)
  431. os.environ["DSSI_PATH"] = splitter.join(DSSI_PATH)
  432. os.environ["LV2_PATH"] = splitter.join(LV2_PATH)
  433. os.environ["VST_PATH"] = splitter.join(VST_PATH)
  434. os.environ["AU_PATH"] = splitter.join(AU_PATH)
  435. os.environ["CSOUND_PATH"] = splitter.join(CSOUND_PATH)
  436. os.environ["GIG_PATH"] = splitter.join(GIG_PATH)
  437. os.environ["SF2_PATH"] = splitter.join(SF2_PATH)
  438. os.environ["SFZ_PATH"] = splitter.join(SFZ_PATH)
  439. # ---------------------------------------------
  440. self.fSavedSettings = {
  441. "Main/DefaultProjectFolder": settings.value("Main/DefaultProjectFolder", HOME, type=str),
  442. "Main/RefreshInterval": settings.value("Main/RefreshInterval", 50, type=int),
  443. "Canvas/Theme": settings.value("Canvas/Theme", CANVAS_DEFAULT_THEME_NAME, type=str),
  444. "Canvas/AutoHideGroups": settings.value("Canvas/AutoHideGroups", False, type=bool),
  445. "Canvas/UseBezierLines": settings.value("Canvas/UseBezierLines", True, type=bool),
  446. "Canvas/EyeCandy": settings.value("Canvas/EyeCandy", CANVAS_EYECANDY_SMALL, type=int),
  447. "Canvas/UseOpenGL": settings.value("Canvas/UseOpenGL", False, type=bool),
  448. "Canvas/Antialiasing": settings.value("Canvas/Antialiasing", CANVAS_ANTIALIASING_SMALL, type=int),
  449. "Canvas/HighQualityAntialiasing": settings.value("Canvas/HighQualityAntialiasing", False, type=bool),
  450. "UseCustomMiniCanvasPaint": (settings.value("Main/UseProTheme", True, type=bool) and
  451. settings.value("Main/ProThemeColor", "Black", type=str).lower() == "black")
  452. }
  453. # ---------------------------------------------
  454. if self.fIdleTimerFast != 0:
  455. self.killTimer(self.fIdleTimerFast)
  456. self.fIdleTimerFast = self.startTimer(self.fSavedSettings["Main/RefreshInterval"])
  457. if self.fIdleTimerSlow != 0:
  458. self.killTimer(self.fIdleTimerSlow)
  459. self.fIdleTimerSlow = self.startTimer(self.fSavedSettings["Main/RefreshInterval"]*2)
  460. def saveSettings(self):
  461. settings = QSettings()
  462. settings.setValue("Geometry", self.saveGeometry())
  463. #settings.setValue("SplitterState", self.ui.splitter.saveState())
  464. settings.setValue("ShowToolbar", self.ui.toolBar.isVisible())
  465. #settings.setValue("HorizontalScrollBarValue", self.ui.graphicsView.horizontalScrollBar().value())
  466. #settings.setValue("VerticalScrollBarValue", self.ui.graphicsView.verticalScrollBar().value())
  467. #diskFolders = []
  468. #for i in range(self.ui.cb_disk.count()):
  469. #diskFolders.append(self.ui.cb_disk.itemData(i))
  470. #settings.setValue("DiskFolders", diskFolders)
  471. self.fContainer.saveSettings(settings)
  472. # -----------------------------------------------------------------
  473. # Internal stuff (gui)
  474. def setProperWindowTitle(self):
  475. title = self.fClientName
  476. if self.fProjectFilename:
  477. title += " - %s" % os.path.basename(self.fProjectFilename)
  478. if self.fSessionManagerName:
  479. title += " (%s)" % self.fSessionManagerName
  480. self.setWindowTitle(title)
  481. # -----------------------------------------------------------------
  482. @pyqtSlot()
  483. def slot_fileNew(self):
  484. self.fContainer.removeAllPlugins()
  485. self.fProjectFilename = ""
  486. self.setProperWindowTitle()
  487. @pyqtSlot()
  488. def slot_fileOpen(self):
  489. fileFilter = self.tr("Carla Project File (*.carxp)")
  490. filenameTry = QFileDialog.getOpenFileName(self, self.tr("Open Carla Project File"), self.fSavedSettings["Main/DefaultProjectFolder"], filter=fileFilter)[0]
  491. if not filenameTry:
  492. return
  493. newFile = True
  494. if self.fContainer.getPluginCount() > 0:
  495. ask = QMessageBox.question(self, self.tr("Question"), self.tr("There are some plugins loaded, do you want to remove them now?"),
  496. QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
  497. newFile = ask == QMessageBox.Yes
  498. if newFile:
  499. self.fContainer.removeAllPlugins()
  500. self.fProjectFilename = filenameTry
  501. self.setProperWindowTitle()
  502. self.loadProjectNow()
  503. else:
  504. filenameOld = self.fProjectFilename
  505. self.fProjectFilename = filenameTry
  506. self.loadProjectNow()
  507. self.fProjectFilename = filenameOld
  508. @pyqtSlot()
  509. def slot_fileSave(self, saveAs=False):
  510. if self.fProjectFilename and not saveAs:
  511. return self.saveProjectNow()
  512. fileFilter = self.tr("Carla Project File (*.carxp)")
  513. filenameTry = QFileDialog.getSaveFileName(self, self.tr("Save Carla Project File"), self.fSavedSettings["Main/DefaultProjectFolder"], filter=fileFilter)[0]
  514. if not filenameTry:
  515. return
  516. if not filenameTry.endswith(".carxp"):
  517. filenameTry += ".carxp"
  518. if self.fProjectFilename != filenameTry:
  519. self.fProjectFilename = filenameTry
  520. self.setProperWindowTitle()
  521. self.saveProjectNow()
  522. @pyqtSlot()
  523. def slot_fileSaveAs(self):
  524. self.slot_fileSave(True)
  525. # -----------------------------------------------------------------
  526. @pyqtSlot()
  527. def slot_engineStart(self, doStart = True):
  528. if doStart: self.startEngine()
  529. check = Carla.host.is_engine_running()
  530. self.ui.menu_PluginMacros.setEnabled(check)
  531. self.ui.menu_Canvas.setEnabled(check)
  532. if not Carla.isPlugin:
  533. self.ui.act_file_save.setEnabled(check)
  534. self.ui.act_engine_start.setEnabled(not check)
  535. self.ui.act_engine_stop.setEnabled(check)
  536. if self.fSessionManagerName != "Non Session Manager":
  537. self.ui.act_file_open.setEnabled(check)
  538. self.ui.act_file_save_as.setEnabled(check)
  539. self.setTransportMenuEnabled(check)
  540. if check:
  541. #self.fInfoText = "Engine running | SampleRate: %g | BufferSize: %i" % (self.fSampleRate, self.fBufferSize)
  542. self.fContainer.engineStarted()
  543. if not Carla.isPlugin:
  544. self.refreshTransport(True)
  545. @pyqtSlot()
  546. def slot_engineStop(self, doStop = True):
  547. if doStop: self.stopEngine()
  548. check = Carla.host.is_engine_running()
  549. self.ui.menu_PluginMacros.setEnabled(check)
  550. self.ui.menu_Canvas.setEnabled(check)
  551. if not Carla.isPlugin:
  552. self.ui.act_file_save.setEnabled(check)
  553. self.ui.act_engine_start.setEnabled(not check)
  554. self.ui.act_engine_stop.setEnabled(check)
  555. if self.fSessionManagerName != "Non Session Manager":
  556. self.ui.act_file_open.setEnabled(check)
  557. self.ui.act_file_save_as.setEnabled(check)
  558. self.setTransportMenuEnabled(check)
  559. if not check:
  560. self.fContainer.engineStopped()
  561. #self.fInfoText = ""
  562. #self.fInfoLabel.setText("Engine stopped")
  563. # -----------------------------------------------------------------
  564. @pyqtSlot()
  565. def slot_pluginAdd(self):
  566. dialog = PluginDatabaseW(self)
  567. if not dialog.exec_():
  568. return
  569. if not Carla.host.is_engine_running():
  570. QMessageBox.warning(self, self.tr("Warning"), self.tr("Cannot add new plugins while engine is stopped"))
  571. return
  572. btype = dialog.fRetPlugin['build']
  573. ptype = dialog.fRetPlugin['type']
  574. filename = dialog.fRetPlugin['binary']
  575. label = dialog.fRetPlugin['label']
  576. extraStuff = self.getExtraStuff(dialog.fRetPlugin)
  577. if not Carla.host.add_plugin(btype, ptype, filename, None, label, c_nullptr):
  578. CustomMessageBox(self, QMessageBox.Critical, self.tr("Error"), self.tr("Failed to load plugin"), cString(Carla.host.get_last_error()), QMessageBox.Ok, QMessageBox.Ok)
  579. return
  580. @pyqtSlot()
  581. def slot_pluginRemoveAll(self):
  582. self.ui.act_plugin_remove_all.setEnabled(False)
  583. self.fContainer.removeAllPlugins()
  584. Carla.host.remove_all_plugins()
  585. # -----------------------------------------------------------------
  586. @pyqtSlot(bool)
  587. def slot_transportPlayPause(self, toggled):
  588. if not Carla.host.is_engine_running():
  589. return
  590. if toggled:
  591. Carla.host.transport_play()
  592. else:
  593. Carla.host.transport_pause()
  594. self.refreshTransport()
  595. @pyqtSlot()
  596. def slot_transportStop(self):
  597. if not Carla.host.is_engine_running():
  598. return
  599. Carla.host.transport_pause()
  600. Carla.host.transport_relocate(0)
  601. self.refreshTransport()
  602. @pyqtSlot()
  603. def slot_transportBackwards(self):
  604. if not Carla.host.is_engine_running():
  605. return
  606. newFrame = Carla.host.get_current_transport_frame() - 100000
  607. if newFrame < 0:
  608. newFrame = 0
  609. Carla.host.transport_relocate(newFrame)
  610. @pyqtSlot()
  611. def slot_transportForwards(self):
  612. if not Carla.host.is_engine_running():
  613. return
  614. newFrame = Carla.host.get_current_transport_frame() + 100000
  615. Carla.host.transport_relocate(newFrame)
  616. # -----------------------------------------------------------------
  617. @pyqtSlot()
  618. def slot_aboutCarla(self):
  619. CarlaAboutW(self).exec_()
  620. @pyqtSlot()
  621. def slot_aboutQt(self):
  622. QApplication.instance().aboutQt()
  623. # -----------------------------------------------------------------
  624. @pyqtSlot(int, int, int, float, str)
  625. def slot_handleDebugCallback(self, pluginId, value1, value2, value3, valueStr):
  626. print("DEBUG:", pluginId, value1, value2, value3, valueStr)
  627. #self.ui.pte_log.appendPlainText(valueStr.replace("", "DEBUG: ").replace("", "ERROR: ").replace("", "").replace("\n", ""))
  628. @pyqtSlot(int)
  629. def slot_handlePluginAddedCallback(self, pluginId):
  630. self.fContainer.addPlugin(pluginId, self.fIsProjectLoading)
  631. if self.fContainer.getPluginCount() == 1:
  632. self.ui.act_plugin_remove_all.setEnabled(True)
  633. @pyqtSlot(int)
  634. def slot_handlePluginRemovedCallback(self, pluginId):
  635. self.fContainer.removePlugin(pluginId)
  636. if self.fContainer.getPluginCount() == 0:
  637. self.ui.act_plugin_remove_all.setEnabled(False)
  638. @pyqtSlot(int, str)
  639. def slot_handlePluginRenamedCallback(self, pluginId, newName):
  640. self.fContainer.renamePlugin(pluginId, newName)
  641. @pyqtSlot(int)
  642. def slot_handleBufferSizeChangedCallback(self, newBufferSize):
  643. self.fBufferSize = newBufferSize
  644. #self.fInfoText = "Engine running | SampleRate: %g | BufferSize: %i" % (self.fSampleRate, self.fBufferSize)
  645. @pyqtSlot(float)
  646. def slot_handleSampleRateChangedCallback(self, newSampleRate):
  647. self.fSampleRate = newSampleRate
  648. #self.fInfoText = "Engine running | SampleRate: %g | BufferSize: %i" % (self.fSampleRate, self.fBufferSize)
  649. # -----------------------------------------------------------------
  650. @pyqtSlot(str)
  651. def slot_handleEngineStartedCallback(self, driverName):
  652. self.fBufferSize = Carla.host.get_buffer_size()
  653. self.fSampleRate = Carla.host.get_sample_rate()
  654. if self.fIdleTimerFast == 0:
  655. self.fIdleTimerFast = self.startTimer(self.fSavedSettings["Main/RefreshInterval"])
  656. if self.fIdleTimerSlow == 0:
  657. self.fIdleTimerSlow = self.startTimer(self.fSavedSettings["Main/RefreshInterval"]*2)
  658. self.slot_engineStart(False)
  659. @pyqtSlot()
  660. def slot_handleEngineStoppedCallback(self):
  661. self.fBufferSize = 0
  662. self.fSampleRate = 0.0
  663. if self.fIdleTimerFast != 0:
  664. self.killTimer(self.fIdleTimerFast)
  665. self.fIdleTimerFast = 0
  666. if self.fIdleTimerSlow != 0:
  667. self.killTimer(self.fIdleTimerSlow)
  668. self.fIdleTimerSlow = 0
  669. if self.fContainer.getPluginCount() > 0:
  670. self.ui.act_plugin_remove_all.setEnabled(False)
  671. self.fContainer.removeAllPlugins() # FIXME?
  672. self.slot_engineStop(False)
  673. # -----------------------------------------------------------------
  674. @pyqtSlot()
  675. def slot_handleSIGUSR1(self):
  676. print("Got SIGUSR1 -> Saving project now")
  677. #QTimer.singleShot(0, self, SLOT("slot_fileSave)
  678. @pyqtSlot()
  679. def slot_handleSIGTERM(self):
  680. print("Got SIGTERM -> Closing now")
  681. self.close()
  682. # -----------------------------------------------------------------
  683. def timerEvent(self, event):
  684. if event.timerId() == self.fIdleTimerFast:
  685. if not Carla.isPlugin:
  686. Carla.host.engine_idle()
  687. self.refreshTransport()
  688. self.fContainer.idleFast()
  689. elif event.timerId() == self.fIdleTimerSlow:
  690. self.fContainer.idleSlow()
  691. QMainWindow.timerEvent(self, event)
  692. def closeEvent(self, event):
  693. if self.fIdleTimerFast != 0:
  694. self.killTimer(self.fIdleTimerFast)
  695. self.fIdleTimerFast = 0
  696. if self.fIdleTimerSlow != 0:
  697. self.killTimer(self.fIdleTimerSlow)
  698. self.fIdleTimerSlow = 0
  699. self.saveSettings()
  700. if Carla.host.is_engine_running():
  701. Carla.host.set_engine_about_to_close()
  702. self.ui.act_plugin_remove_all.setEnabled(False)
  703. self.fContainer.removeAllPlugins()
  704. self.stopEngine()
  705. QMainWindow.closeEvent(self, event)
  706. # ------------------------------------------------------------------------------------------------------------
  707. # Engine callback
  708. def EngineCallback(ptr, action, pluginId, value1, value2, value3, valueStr):
  709. if pluginId < 0 or not Carla.gui:
  710. return
  711. if action == CALLBACK_DEBUG:
  712. Carla.gui.DebugCallback.emit(pluginId, value1, value2, value3, cString(valueStr))
  713. elif action == CALLBACK_PLUGIN_ADDED:
  714. Carla.gui.PluginAddedCallback.emit(pluginId)
  715. elif action == CALLBACK_PLUGIN_REMOVED:
  716. Carla.gui.PluginRemovedCallback.emit(pluginId)
  717. elif action == CALLBACK_PLUGIN_RENAMED:
  718. Carla.gui.PluginRenamedCallback.emit(pluginId, valueStr)
  719. elif action == CALLBACK_PARAMETER_VALUE_CHANGED:
  720. Carla.gui.ParameterValueChangedCallback.emit(pluginId, value1, value3)
  721. elif action == CALLBACK_PARAMETER_DEFAULT_CHANGED:
  722. Carla.gui.ParameterDefaultChangedCallback.emit(pluginId, value1, value3)
  723. elif action == CALLBACK_PARAMETER_MIDI_CHANNEL_CHANGED:
  724. Carla.gui.ParameterMidiChannelChangedCallback.emit(pluginId, value1, value2)
  725. elif action == CALLBACK_PARAMETER_MIDI_CC_CHANGED:
  726. Carla.gui.ParameterMidiCcChangedCallback.emit(pluginId, value1, value2)
  727. elif action == CALLBACK_PROGRAM_CHANGED:
  728. Carla.gui.ProgramChangedCallback.emit(pluginId, value1)
  729. elif action == CALLBACK_MIDI_PROGRAM_CHANGED:
  730. Carla.gui.MidiProgramChangedCallback.emit(pluginId, value1)
  731. elif action == CALLBACK_NOTE_ON:
  732. Carla.gui.NoteOnCallback.emit(pluginId, value1, value2, value3)
  733. elif action == CALLBACK_NOTE_OFF:
  734. Carla.gui.NoteOffCallback.emit(pluginId, value1, value2)
  735. elif action == CALLBACK_SHOW_GUI:
  736. Carla.gui.ShowGuiCallback.emit(pluginId, value1)
  737. elif action == CALLBACK_UPDATE:
  738. Carla.gui.UpdateCallback.emit(pluginId)
  739. elif action == CALLBACK_RELOAD_INFO:
  740. Carla.gui.ReloadInfoCallback.emit(pluginId)
  741. elif action == CALLBACK_RELOAD_PARAMETERS:
  742. Carla.gui.ReloadParametersCallback.emit(pluginId)
  743. elif action == CALLBACK_RELOAD_PROGRAMS:
  744. Carla.gui.ReloadProgramsCallback.emit(pluginId)
  745. elif action == CALLBACK_RELOAD_ALL:
  746. Carla.gui.ReloadAllCallback.emit(pluginId)
  747. elif action == CALLBACK_PATCHBAY_CLIENT_ADDED:
  748. Carla.gui.PatchbayClientAddedCallback.emit(value1, value2, cString(valueStr))
  749. elif action == CALLBACK_PATCHBAY_CLIENT_REMOVED:
  750. Carla.gui.PatchbayClientRemovedCallback.emit(value1, cString(valueStr))
  751. elif action == CALLBACK_PATCHBAY_CLIENT_RENAMED:
  752. Carla.gui.PatchbayClientRenamedCallback.emit(value1, cString(valueStr))
  753. elif action == CALLBACK_PATCHBAY_PORT_ADDED:
  754. Carla.gui.PatchbayPortAddedCallback.emit(value1, value2, int(value3), cString(valueStr))
  755. elif action == CALLBACK_PATCHBAY_PORT_REMOVED:
  756. Carla.gui.PatchbayPortRemovedCallback.emit(value1, value2, cString(valueStr))
  757. elif action == CALLBACK_PATCHBAY_PORT_RENAMED:
  758. Carla.gui.PatchbayPortRenamedCallback.emit(value1, value2, cString(valueStr))
  759. elif action == CALLBACK_PATCHBAY_CONNECTION_ADDED:
  760. Carla.gui.PatchbayConnectionAddedCallback.emit(value1, value2, value3)
  761. elif action == CALLBACK_PATCHBAY_CONNECTION_REMOVED:
  762. Carla.gui.PatchbayConnectionRemovedCallback.emit(value1)
  763. elif action == CALLBACK_PATCHBAY_ICON_CHANGED:
  764. Carla.gui.PatchbayIconChangedCallback.emit(value1, value2)
  765. elif action == CALLBACK_BUFFER_SIZE_CHANGED:
  766. Carla.gui.BufferSizeChangedCallback.emit(value1)
  767. elif action == CALLBACK_SAMPLE_RATE_CHANGED:
  768. Carla.gui.SampleRateChangedCallback.emit(value3)
  769. elif action == CALLBACK_PROCESS_MODE_CHANGED:
  770. Carla.gui.ProcessModeChangedCallback.emit(value1)
  771. elif action == CALLBACK_ENGINE_STARTED:
  772. Carla.gui.EngineStartedCallback.emit(cString(valueStr))
  773. elif action == CALLBACK_ENGINE_STOPPED:
  774. Carla.gui.EngineStoppedChangedCallback.emit()
  775. elif action == CALLBACK_NSM_ANNOUNCE:
  776. Carla.gui.NSM_AnnounceCallback.emit(cString(valueStr))
  777. elif action == CALLBACK_NSM_OPEN:
  778. Carla.gui.NSM_OpenCallback.emit(cString(valueStr))
  779. elif action == CALLBACK_NSM_SAVE:
  780. Carla.gui.NSM_SaveCallback.emit()
  781. elif action == CALLBACK_ERROR:
  782. Carla.gui.ErrorCallback.emit(cString(valueStr))
  783. elif action == CALLBACK_QUIT:
  784. Carla.gui.QuitCallback.emit()