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-plugin 22KB

10 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla plugin host (plugin UI)
  4. # Copyright (C) 2013-2020 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 PyQt5.QtGui import QKeySequence, QMouseEvent
  20. from PyQt5.QtWidgets import QFrame, QSplitter
  21. # ------------------------------------------------------------------------------------------------------------
  22. # Imports (Custom Stuff)
  23. from carla_backend_qt import CarlaHostQtPlugin
  24. from carla_host import *
  25. from externalui import ExternalUI
  26. # ------------------------------------------------------------------------------------------------------------
  27. # Host Plugin object
  28. class PluginHost(CarlaHostQtPlugin):
  29. def __init__(self):
  30. CarlaHostQtPlugin.__init__(self)
  31. if False:
  32. # kdevelop likes this :)
  33. self.fExternalUI = ExternalUI()
  34. # ---------------------------------------------------------------
  35. self.fExternalUI = None
  36. # -------------------------------------------------------------------
  37. def setExternalUI(self, extUI):
  38. self.fExternalUI = extUI
  39. def sendMsg(self, lines):
  40. if self.fExternalUI is None:
  41. return False
  42. return self.fExternalUI.send(lines)
  43. # -------------------------------------------------------------------
  44. def engine_init(self, driverName, clientName):
  45. return True
  46. def engine_close(self):
  47. return True
  48. def engine_idle(self):
  49. self.fExternalUI.idleExternalUI()
  50. def is_engine_running(self):
  51. if self.fExternalUI is None:
  52. return False
  53. return self.fExternalUI.isRunning()
  54. def set_engine_about_to_close(self):
  55. return True
  56. def get_host_osc_url_tcp(self):
  57. return self.tr("(OSC TCP port not provided in Plugin version)")
  58. # ------------------------------------------------------------------------------------------------------------
  59. # Main Window
  60. class CarlaMiniW(ExternalUI, HostWindow):
  61. def __init__(self, host, isPatchbay, parent=None):
  62. ExternalUI.__init__(self)
  63. HostWindow.__init__(self, host, isPatchbay, parent)
  64. if False:
  65. # kdevelop likes this :)
  66. host = PluginHost()
  67. self.host = host
  68. host.setExternalUI(self)
  69. self.fFirstInit = True
  70. self.setWindowTitle(self.fUiName)
  71. self.ready()
  72. # Override this as it can be called from several places.
  73. # We really need to close all UIs as events are driven by host idle which is only available when UI is visible
  74. def closeExternalUI(self):
  75. for i in reversed(range(self.fPluginCount)):
  76. self.host.show_custom_ui(i, False)
  77. ExternalUI.closeExternalUI(self)
  78. # -------------------------------------------------------------------
  79. # ExternalUI Callbacks
  80. def uiShow(self):
  81. if self.parent() is not None:
  82. return
  83. self.show()
  84. def uiFocus(self):
  85. if self.parent() is not None:
  86. return
  87. self.setWindowState((self.windowState() & ~Qt.WindowMinimized) | Qt.WindowActive)
  88. self.show()
  89. self.raise_()
  90. self.activateWindow()
  91. def uiHide(self):
  92. if self.parent() is not None:
  93. return
  94. self.hide()
  95. def uiQuit(self):
  96. self.closeExternalUI()
  97. self.close()
  98. if self != gui:
  99. gui.close()
  100. # there might be other qt windows open which will block carla-plugin from quitting
  101. app.quit()
  102. def uiTitleChanged(self, uiTitle):
  103. self.setWindowTitle(uiTitle)
  104. # -------------------------------------------------------------------
  105. # Qt events
  106. def closeEvent(self, event):
  107. self.closeExternalUI()
  108. HostWindow.closeEvent(self, event)
  109. # there might be other qt windows open which will block carla-plugin from quitting
  110. app.quit()
  111. # -------------------------------------------------------------------
  112. # Custom callback
  113. def msgCallback(self, msg):
  114. try:
  115. self.msgCallback2(msg)
  116. except Exception as e:
  117. print("msgCallback error, skipped for", msg, "error was:\n", e)
  118. def msgCallback2(self, msg):
  119. msg = charPtrToString(msg)
  120. #if not msg:
  121. #return
  122. if msg == "runtime-info":
  123. values = self.readlineblock().split(":")
  124. load = float(values[0])
  125. xruns = int(values[1])
  126. self.host._set_runtime_info(load, xruns)
  127. elif msg == "project-folder":
  128. self.fProjectFilename = self.readlineblock()
  129. elif msg == "transport":
  130. playing = self.readlineblock_bool()
  131. frame, bar, beat, tick = [int(i) for i in self.readlineblock().split(":")]
  132. bpm = self.readlineblock_float()
  133. self.host._set_transport(playing, frame, bar, beat, tick, bpm)
  134. elif msg.startswith("PEAKS_"):
  135. pluginId = int(msg.replace("PEAKS_", ""))
  136. in1, in2, out1, out2 = [float(i) for i in self.readlineblock().split(":")]
  137. self.host._set_peaks(pluginId, in1, in2, out1, out2)
  138. elif msg.startswith("PARAMVAL_"):
  139. pluginId, paramId = [int(i) for i in msg.replace("PARAMVAL_", "").split(":")]
  140. paramValue = self.readlineblock_float()
  141. if paramId < 0:
  142. self.host._set_internalValue(pluginId, paramId, paramValue)
  143. else:
  144. self.host._set_parameterValue(pluginId, paramId, paramValue)
  145. elif msg.startswith("ENGINE_CALLBACK_"):
  146. action = int(msg.replace("ENGINE_CALLBACK_", ""))
  147. pluginId = self.readlineblock_int()
  148. value1 = self.readlineblock_int()
  149. value2 = self.readlineblock_int()
  150. value3 = self.readlineblock_int()
  151. valuef = self.readlineblock_float()
  152. valueStr = self.readlineblock()
  153. self.host._setViaCallback(action, pluginId, value1, value2, value3, valuef, valueStr)
  154. engineCallback(self.host, action, pluginId, value1, value2, value3, valuef, valueStr)
  155. elif msg.startswith("ENGINE_OPTION_"):
  156. option = int(msg.replace("ENGINE_OPTION_", ""))
  157. forced = self.readlineblock_bool()
  158. value = self.readlineblock()
  159. if self.fFirstInit and not forced:
  160. return
  161. if option == ENGINE_OPTION_PROCESS_MODE:
  162. self.host.processMode = int(value)
  163. elif option == ENGINE_OPTION_TRANSPORT_MODE:
  164. self.host.transportMode = int(value)
  165. elif option == ENGINE_OPTION_FORCE_STEREO:
  166. self.host.forceStereo = bool(value == "true")
  167. elif option == ENGINE_OPTION_PREFER_PLUGIN_BRIDGES:
  168. self.host.preferPluginBridges = bool(value == "true")
  169. elif option == ENGINE_OPTION_PREFER_UI_BRIDGES:
  170. self.host.preferUIBridges = bool(value == "true")
  171. elif option == ENGINE_OPTION_UIS_ALWAYS_ON_TOP:
  172. self.host.uisAlwaysOnTop = bool(value == "true")
  173. elif option == ENGINE_OPTION_MAX_PARAMETERS:
  174. self.host.maxParameters = int(value)
  175. elif option == ENGINE_OPTION_UI_BRIDGES_TIMEOUT:
  176. self.host.uiBridgesTimeout = int(value)
  177. elif option == ENGINE_OPTION_PATH_BINARIES:
  178. self.host.pathBinaries = value
  179. elif option == ENGINE_OPTION_PATH_RESOURCES:
  180. self.host.pathResources = value
  181. elif msg.startswith("PLUGIN_INFO_"):
  182. pluginId = int(msg.replace("PLUGIN_INFO_", ""))
  183. self.host._add(pluginId)
  184. type_, category, hints, uniqueId, optsAvail, optsEnabled = [int(i) for i in self.readlineblock().split(":")]
  185. filename = self.readlineblock()
  186. name = self.readlineblock()
  187. iconName = self.readlineblock()
  188. realName = self.readlineblock()
  189. label = self.readlineblock()
  190. maker = self.readlineblock()
  191. copyright = self.readlineblock()
  192. pinfo = {
  193. 'type': type_,
  194. 'category': category,
  195. 'hints': hints,
  196. 'optionsAvailable': optsAvail,
  197. 'optionsEnabled': optsEnabled,
  198. 'filename': filename,
  199. 'name': name,
  200. 'label': label,
  201. 'maker': maker,
  202. 'copyright': copyright,
  203. 'iconName': iconName,
  204. 'patchbayClientId': 0,
  205. 'uniqueId': uniqueId
  206. }
  207. self.host._set_pluginInfo(pluginId, pinfo)
  208. self.host._set_pluginRealName(pluginId, realName)
  209. elif msg.startswith("AUDIO_COUNT_"):
  210. pluginId, ins, outs = [int(i) for i in msg.replace("AUDIO_COUNT_", "").split(":")]
  211. self.host._set_audioCountInfo(pluginId, {'ins': ins, 'outs': outs})
  212. elif msg.startswith("MIDI_COUNT_"):
  213. pluginId, ins, outs = [int(i) for i in msg.replace("MIDI_COUNT_", "").split(":")]
  214. self.host._set_midiCountInfo(pluginId, {'ins': ins, 'outs': outs})
  215. elif msg.startswith("PARAMETER_COUNT_"):
  216. pluginId, ins, outs, count = [int(i) for i in msg.replace("PARAMETER_COUNT_", "").split(":")]
  217. self.host._set_parameterCountInfo(pluginId, count, {'ins': ins, 'outs': outs})
  218. elif msg.startswith("PARAMETER_DATA_"):
  219. pluginId, paramId = [int(i) for i in msg.replace("PARAMETER_DATA_", "").split(":")]
  220. paramType, paramHints, mappedControlIndex, midiChannel = [int(i) for i in self.readlineblock().split(":")]
  221. mappedMinimum, mappedMaximum = [float(i) for i in self.readlineblock().split(":")]
  222. paramName = self.readlineblock()
  223. paramUnit = self.readlineblock()
  224. paramComment = self.readlineblock()
  225. paramGroupName = self.readlineblock()
  226. paramInfo = {
  227. 'name': paramName,
  228. 'symbol': "",
  229. 'unit': paramUnit,
  230. 'comment': paramComment,
  231. 'groupName': paramGroupName,
  232. 'scalePointCount': 0,
  233. }
  234. self.host._set_parameterInfo(pluginId, paramId, paramInfo)
  235. paramData = {
  236. 'type': paramType,
  237. 'hints': paramHints,
  238. 'index': paramId,
  239. 'rindex': -1,
  240. 'midiChannel': midiChannel,
  241. 'mappedControlIndex': mappedControlIndex,
  242. 'mappedMinimum': mappedMinimum,
  243. 'mappedMaximum': mappedMaximum,
  244. }
  245. self.host._set_parameterData(pluginId, paramId, paramData)
  246. elif msg.startswith("PARAMETER_RANGES_"):
  247. pluginId, paramId = [int(i) for i in msg.replace("PARAMETER_RANGES_", "").split(":")]
  248. def_, min_, max_, step, stepSmall, stepLarge = [float(i) for i in self.readlineblock().split(":")]
  249. paramRanges = {
  250. 'def': def_,
  251. 'min': min_,
  252. 'max': max_,
  253. 'step': step,
  254. 'stepSmall': stepSmall,
  255. 'stepLarge': stepLarge
  256. }
  257. self.host._set_parameterRanges(pluginId, paramId, paramRanges)
  258. elif msg.startswith("PROGRAM_COUNT_"):
  259. pluginId, count, current = [int(i) for i in msg.replace("PROGRAM_COUNT_", "").split(":")]
  260. self.host._set_programCount(pluginId, count)
  261. self.host._set_currentProgram(pluginId, current)
  262. elif msg.startswith("PROGRAM_NAME_"):
  263. pluginId, progId = [int(i) for i in msg.replace("PROGRAM_NAME_", "").split(":")]
  264. progName = self.readlineblock()
  265. self.host._set_programName(pluginId, progId, progName)
  266. elif msg.startswith("MIDI_PROGRAM_COUNT_"):
  267. pluginId, count, current = [int(i) for i in msg.replace("MIDI_PROGRAM_COUNT_", "").split(":")]
  268. self.host._set_midiProgramCount(pluginId, count)
  269. self.host._set_currentMidiProgram(pluginId, current)
  270. elif msg.startswith("MIDI_PROGRAM_DATA_"):
  271. pluginId, midiProgId = [int(i) for i in msg.replace("MIDI_PROGRAM_DATA_", "").split(":")]
  272. bank, program = [int(i) for i in self.readlineblock().split(":")]
  273. name = self.readlineblock()
  274. self.host._set_midiProgramData(pluginId, midiProgId, {'bank': bank, 'program': program, 'name': name})
  275. elif msg.startswith("CUSTOM_DATA_COUNT_"):
  276. pluginId, count = [int(i) for i in msg.replace("CUSTOM_DATA_COUNT_", "").split(":")]
  277. self.host._set_customDataCount(pluginId, count)
  278. elif msg.startswith("CUSTOM_DATA_"):
  279. pluginId, customDataId = [int(i) for i in msg.replace("CUSTOM_DATA_", "").split(":")]
  280. type_ = self.readlineblock()
  281. key = self.readlineblock()
  282. value = self.readlineblock()
  283. self.host._set_customData(pluginId, customDataId, {'type': type_, 'key': key, 'value': value})
  284. elif msg == "osc-urls":
  285. tcp = self.readlineblock()
  286. udp = self.readlineblock()
  287. self.host.fOscTCP = tcp
  288. self.host.fOscUDP = udp
  289. elif msg == "max-plugin-number":
  290. maxnum = self.readlineblock_int()
  291. self.host.fMaxPluginNumber = maxnum
  292. elif msg == "buffer-size":
  293. bufsize = self.readlineblock_int()
  294. self.host.fBufferSize = bufsize
  295. elif msg == "sample-rate":
  296. srate = self.readlineblock_float()
  297. self.host.fSampleRate = srate
  298. elif msg == "error":
  299. error = self.readlineblock()
  300. engineCallback(self.host, ENGINE_CALLBACK_ERROR, 0, 0, 0, 0, 0.0, error)
  301. elif msg == "show":
  302. self.fFirstInit = False
  303. self.uiShow()
  304. elif msg == "focus":
  305. self.uiFocus()
  306. elif msg == "hide":
  307. self.uiHide()
  308. elif msg == "quit":
  309. self.fQuitReceived = True
  310. self.uiQuit()
  311. elif msg == "uiTitle":
  312. uiTitle = self.readlineblock()
  313. self.uiTitleChanged(uiTitle)
  314. else:
  315. print("unknown message: \"" + msg + "\"")
  316. # ------------------------------------------------------------------------------------------------------------
  317. # Embed Widget
  318. class QEmbedWidget(QWidget):
  319. def __init__(self, winId):
  320. QWidget.__init__(self)
  321. self.setAttribute(Qt.WA_LayoutUsesWidgetRect)
  322. self.move(0, 0)
  323. self.fPos = (0, 0)
  324. self.fWinId = 0
  325. def finalSetup(self, gui, winId):
  326. self.fWinId = int(self.winId())
  327. gui.ui.centralwidget.installEventFilter(self)
  328. gui.ui.menubar.installEventFilter(self)
  329. gCarla.utils.x11_reparent_window(self.fWinId, winId)
  330. self.show()
  331. def fixPosition(self):
  332. pos = gCarla.utils.x11_get_window_pos(self.fWinId)
  333. if self.fPos == pos:
  334. return
  335. self.fPos = pos
  336. self.move(pos[0], pos[1])
  337. gCarla.utils.x11_move_window(self.fWinId, pos[2], pos[3])
  338. def eventFilter(self, obj, ev):
  339. if isinstance(ev, QMouseEvent):
  340. self.fixPosition()
  341. return False
  342. def enterEvent(self, ev):
  343. self.fixPosition()
  344. QWidget.enterEvent(self, ev)
  345. # ------------------------------------------------------------------------------------------------------------
  346. # Embed plugin UI
  347. class CarlaEmbedW(QEmbedWidget):
  348. def __init__(self, host, winId, isPatchbay):
  349. QEmbedWidget.__init__(self, winId)
  350. if False:
  351. host = CarlaHostPlugin()
  352. self.host = host
  353. self.fWinId = winId
  354. self.setFixedSize(1024, 712)
  355. self.fLayout = QVBoxLayout(self)
  356. self.fLayout.setContentsMargins(0, 0, 0, 0)
  357. self.fLayout.setSpacing(0)
  358. self.setLayout(self.fLayout)
  359. self.gui = CarlaMiniW(host, isPatchbay, self)
  360. self.gui.hide()
  361. self.gui.ui.act_file_quit.setEnabled(False)
  362. self.gui.ui.act_file_quit.setVisible(False)
  363. self.fShortcutActions = []
  364. self.addShortcutActions(self.gui.ui.menu_File.actions())
  365. self.addShortcutActions(self.gui.ui.menu_Plugin.actions())
  366. self.addShortcutActions(self.gui.ui.menu_PluginMacros.actions())
  367. self.addShortcutActions(self.gui.ui.menu_Settings.actions())
  368. self.addShortcutActions(self.gui.ui.menu_Help.actions())
  369. if self.host.processMode == ENGINE_PROCESS_MODE_PATCHBAY:
  370. self.addShortcutActions(self.gui.ui.menu_Canvas.actions())
  371. self.addShortcutActions(self.gui.ui.menu_Canvas_Zoom.actions())
  372. self.addWidget(self.gui.ui.menubar)
  373. self.addLine()
  374. self.addWidget(self.gui.ui.toolBar)
  375. if self.host.processMode == ENGINE_PROCESS_MODE_PATCHBAY:
  376. self.addLine()
  377. self.fCentralSplitter = QSplitter(self)
  378. policy = self.fCentralSplitter.sizePolicy()
  379. policy.setVerticalStretch(1)
  380. self.fCentralSplitter.setSizePolicy(policy)
  381. self.addCentralWidget(self.gui.ui.dockWidget)
  382. self.addCentralWidget(self.gui.centralWidget())
  383. self.fLayout.addWidget(self.fCentralSplitter)
  384. self.finalSetup(self.gui, winId)
  385. def addShortcutActions(self, actions):
  386. for action in actions:
  387. if not action.shortcut().isEmpty():
  388. self.fShortcutActions.append(action)
  389. def addWidget(self, widget):
  390. widget.setParent(self)
  391. self.fLayout.addWidget(widget)
  392. def addCentralWidget(self, widget):
  393. widget.setParent(self)
  394. self.fCentralSplitter.addWidget(widget)
  395. def addLine(self):
  396. line = QFrame(self)
  397. line.setFrameShadow(QFrame.Sunken)
  398. line.setFrameShape(QFrame.HLine)
  399. line.setLineWidth(0)
  400. line.setMidLineWidth(1)
  401. self.fLayout.addWidget(line)
  402. def keyPressEvent(self, event):
  403. modifiers = event.modifiers()
  404. modifiersStr = ""
  405. if modifiers & Qt.ShiftModifier:
  406. modifiersStr += "Shift+"
  407. if modifiers & Qt.ControlModifier:
  408. modifiersStr += "Ctrl+"
  409. if modifiers & Qt.AltModifier:
  410. modifiersStr += "Alt+"
  411. if modifiers & Qt.MetaModifier:
  412. modifiersStr += "Meta+"
  413. keyStr = QKeySequence(event.key()).toString()
  414. keySeq = QKeySequence(modifiersStr + keyStr)
  415. for action in self.fShortcutActions:
  416. if not action.isEnabled():
  417. continue
  418. if keySeq.matches(action.shortcut()) != QKeySequence.ExactMatch:
  419. continue
  420. event.accept()
  421. action.trigger()
  422. return
  423. QEmbedWidget.keyPressEvent(self, event)
  424. def showEvent(self, event):
  425. QEmbedWidget.showEvent(self, event)
  426. if QT_VERSION >= 0x50600:
  427. self.host.set_engine_option(ENGINE_OPTION_FRONTEND_UI_SCALE, int(self.devicePixelRatioF() * 1000), "")
  428. print("Plugin UI pixel ratio is", self.devicePixelRatioF(),
  429. "with %ix%i" % (self.width(), self.height()), "in size")
  430. # set our gui as parent for all plugins UIs
  431. if self.host.manageUIs:
  432. if MACOS:
  433. nsViewPtr = int(self.fWinId)
  434. winIdStr = "%x" % gCarla.utils.cocoa_get_window(nsViewPtr)
  435. else:
  436. winIdStr = "%x" % int(self.fWinId)
  437. self.host.set_engine_option(ENGINE_OPTION_FRONTEND_WIN_ID, 0, winIdStr)
  438. def hideEvent(self, event):
  439. # disable parent
  440. self.host.set_engine_option(ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0")
  441. QEmbedWidget.hideEvent(self, event)
  442. def closeEvent(self, event):
  443. self.gui.close()
  444. self.gui.closeExternalUI()
  445. QEmbedWidget.closeEvent(self, event)
  446. # there might be other qt windows open which will block carla-plugin from quitting
  447. app.quit()
  448. def setLoadRDFsNeeded(self):
  449. self.gui.setLoadRDFsNeeded()
  450. # ------------------------------------------------------------------------------------------------------------
  451. # Main
  452. if __name__ == '__main__':
  453. # -------------------------------------------------------------
  454. # App initialization
  455. app = CarlaApplication("Carla2-Plugin")
  456. # -------------------------------------------------------------
  457. # Set-up custom signal handling
  458. setUpSignals()
  459. # -------------------------------------------------------------
  460. # Init host backend
  461. isPatchbay = sys.argv[0].rsplit(os.path.sep)[-1].lower().replace(".exe","") == "carla-plugin-patchbay"
  462. host = initHost("Carla-Plugin", None, False, True, True, PluginHost)
  463. host.processMode = ENGINE_PROCESS_MODE_PATCHBAY if isPatchbay else ENGINE_PROCESS_MODE_CONTINUOUS_RACK
  464. host.processModeForced = True
  465. host.nextProcessMode = host.processMode
  466. loadHostSettings(host)
  467. # -------------------------------------------------------------
  468. # Create GUI
  469. try:
  470. winId = int(os.getenv("CARLA_PLUGIN_EMBED_WINID"))
  471. except:
  472. winId = 0
  473. gCarla.utils.setenv("CARLA_PLUGIN_EMBED_WINID", "0")
  474. if LINUX and winId != 0:
  475. gui = CarlaEmbedW(host, winId, isPatchbay)
  476. else:
  477. gui = CarlaMiniW(host, isPatchbay)
  478. # -------------------------------------------------------------
  479. # App-Loop
  480. app.exit_exec()