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_control.py 29KB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
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
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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla Backend code (OSC stuff)
  4. # Copyright (C) 2011-2019 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. from PyQt5.QtCore import QEventLoop
  20. # ------------------------------------------------------------------------------------------------------------
  21. # Imports (Custom)
  22. import ui_carla_osc_connect
  23. from carla_backend_qt import CarlaHostQtPlugin
  24. from carla_host import *
  25. # ------------------------------------------------------------------------------------------------------------
  26. # Imports (liblo)
  27. from liblo import (
  28. Address,
  29. AddressError,
  30. ServerError,
  31. Server,
  32. make_method,
  33. send as lo_send,
  34. TCP as LO_TCP,
  35. UDP as LO_UDP,
  36. )
  37. from random import random
  38. # ------------------------------------------------------------------------------------------------------------
  39. DEBUG = False
  40. # ----------------------------------------------------------------------------------------------------------------------
  41. # OSC connect Dialog
  42. class ConnectDialog(QDialog):
  43. def __init__(self, parent):
  44. QDialog.__init__(self, parent)
  45. self.ui = ui_carla_osc_connect.Ui_Dialog()
  46. self.ui.setupUi(self)
  47. self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
  48. # Make PlainTextEdit background the same color as the window background
  49. palette = self.ui.te_reported_hint.palette()
  50. palette.setColor(QPalette.Base, palette.color(QPalette.Background))
  51. self.ui.te_reported_hint.setPalette(palette)
  52. # -------------------------------------------------------------------------------------------------------------
  53. # Load settings
  54. self.loadSettings()
  55. # -------------------------------------------------------------------------------------------------------------
  56. # Set-up connections
  57. self.finished.connect(self.slot_saveSettings)
  58. self.ui.rb_reported_auto.clicked.connect(self.slot_reportedAutoClicked)
  59. self.ui.rb_reported_custom.clicked.connect(self.slot_reportedCustomClicked)
  60. self.ui.le_host.textChanged.connect(self.slot_hostChanged)
  61. self.ui.le_reported_host.textChanged.connect(self.slot_reportedHostChanged)
  62. # -----------------------------------------------------------------------------------------------------------------
  63. def getResult(self):
  64. return (self.ui.le_host.text(),
  65. self.ui.le_reported_host.text() if self.ui.rb_reported_custom.isChecked() else "",
  66. self.ui.sb_tcp_port.value(),
  67. self.ui.sb_udp_port.value())
  68. def checkIfButtonBoxShouldBeEnabled(self, host, reportedHostAutomatic, reportedHost):
  69. enabled = len(host) > 0
  70. if enabled and not reportedHostAutomatic:
  71. enabled = len(reportedHost) > 0
  72. self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enabled)
  73. def loadSettings(self):
  74. settings = QSafeSettings("falkTX", "CarlaOSCConnect")
  75. if settings.value("ReportedHostAutomatic", True, bool):
  76. self.ui.rb_reported_custom.setChecked(False)
  77. self.ui.rb_reported_auto.setChecked(True)
  78. else:
  79. self.ui.rb_reported_auto.setChecked(False)
  80. self.ui.rb_reported_custom.setChecked(True)
  81. self.ui.le_host.setText(settings.value("Host", "127.0.0.1", str))
  82. self.ui.le_reported_host.setText(settings.value("ReportedHost", "", str))
  83. self.ui.sb_tcp_port.setValue(settings.value("TCPPort", CARLA_DEFAULT_OSC_TCP_PORT_NUMBER, int))
  84. self.ui.sb_udp_port.setValue(settings.value("UDPPort", CARLA_DEFAULT_OSC_UDP_PORT_NUMBER, int))
  85. self.checkIfButtonBoxShouldBeEnabled(self.ui.le_host.text(),
  86. self.ui.rb_reported_auto.isChecked(),
  87. self.ui.le_reported_host.text())
  88. # ------------------------------------------------------------------------------------------------------------------
  89. @pyqtSlot(bool)
  90. def slot_reportedAutoClicked(self, clicked):
  91. self.checkIfButtonBoxShouldBeEnabled(self.ui.le_host.text(),
  92. clicked,
  93. self.ui.le_reported_host.text())
  94. @pyqtSlot(bool)
  95. def slot_reportedCustomClicked(self, clicked):
  96. self.checkIfButtonBoxShouldBeEnabled(self.ui.le_host.text(),
  97. not clicked,
  98. self.ui.le_reported_host.text())
  99. @pyqtSlot(str)
  100. def slot_hostChanged(self, text):
  101. self.checkIfButtonBoxShouldBeEnabled(text,
  102. self.ui.rb_reported_auto.isChecked(),
  103. self.ui.le_reported_host.text())
  104. @pyqtSlot(str)
  105. def slot_reportedHostChanged(self, text):
  106. self.checkIfButtonBoxShouldBeEnabled(self.ui.le_host.text(),
  107. self.ui.rb_reported_auto.isChecked(),
  108. text)
  109. @pyqtSlot()
  110. def slot_saveSettings(self):
  111. settings = QSafeSettings("falkTX", "CarlaOSCConnect")
  112. settings.setValue("Host", self.ui.le_host.text())
  113. settings.setValue("ReportedHost", self.ui.le_reported_host.text())
  114. settings.setValue("TCPPort", self.ui.sb_tcp_port.value())
  115. settings.setValue("UDPPort", self.ui.sb_udp_port.value())
  116. settings.setValue("ReportedHostAutomatic", self.ui.rb_reported_auto.isChecked())
  117. # ------------------------------------------------------------------------------------------------------------------
  118. def done(self, r):
  119. QDialog.done(self, r)
  120. self.close()
  121. # ------------------------------------------------------------------------------------------------------------
  122. # Host OSC object
  123. class CarlaHostOSC(CarlaHostQtPlugin):
  124. def __init__(self):
  125. CarlaHostQtPlugin.__init__(self)
  126. self.lo_server_tcp = None
  127. self.lo_server_udp = None
  128. self.lo_target_tcp = None
  129. self.lo_target_udp = None
  130. self.lo_target_tcp_name = ""
  131. self.lo_target_udp_name = ""
  132. self.resetPendingMessages()
  133. # -------------------------------------------------------------------
  134. def resetPendingMessages(self):
  135. self.lastMessageId = 1
  136. self.pendingMessages = []
  137. self.responses = {}
  138. def printAndReturnError(self, error):
  139. print(error)
  140. self.fLastError = error
  141. return False
  142. def sendMsg(self, lines):
  143. if len(lines) < 1:
  144. return self.printAndReturnError("not enough arguments")
  145. method = lines.pop(0)
  146. if method == "set_engine_option":
  147. return True
  148. if self.lo_target_tcp is None:
  149. return self.printAndReturnError("lo_target_tcp is None")
  150. if self.lo_target_tcp_name is None:
  151. return self.printAndReturnError("lo_target_tcp_name is None")
  152. if method in ("clear_engine_xruns",
  153. "cancel_engine_action",
  154. #"load_file",
  155. #"load_project",
  156. #"save_project",
  157. #"clear_project_filename",
  158. "patchbay_connect",
  159. "patchbay_disconnect",
  160. "patchbay_set_group_pos",
  161. "patchbay_refresh",
  162. "transport_play",
  163. "transport_pause",
  164. "transport_bpm",
  165. "transport_relocate",
  166. "add_plugin",
  167. "remove_plugin",
  168. "remove_all_plugins",
  169. "rename_plugin",
  170. "clone_plugin",
  171. "replace_plugin",
  172. "switch_plugins",
  173. #"load_plugin_state",
  174. #"save_plugin_state",
  175. ):
  176. path = "/ctrl/" + method
  177. needResp = True
  178. elif method in (#"set_option",
  179. "set_active",
  180. "set_drywet",
  181. "set_volume",
  182. "set_balance_left",
  183. "set_balance_right",
  184. "set_panning",
  185. #"set_ctrl_channel",
  186. "set_parameter_value",
  187. "set_parameter_midi_channel",
  188. "set_parameter_midi_cc",
  189. "set_program",
  190. "set_midi_program",
  191. #"set_custom_data",
  192. #"set_chunk_data",
  193. #"prepare_for_save",
  194. #"reset_parameters",
  195. #"randomize_parameters",
  196. ):
  197. pluginId = lines.pop(0)
  198. needResp = False
  199. path = "/%s/%i/%s" % (self.lo_target_tcp_name, pluginId, method)
  200. elif method == "send_midi_note":
  201. pluginId = lines.pop(0)
  202. needResp = False
  203. channel, note, velocity = lines
  204. if velocity:
  205. path = "/%s/%i/note_on" % (self.lo_target_tcp_name, pluginId)
  206. else:
  207. path = "/%s/%i/note_off" % (self.lo_target_tcp_name, pluginId)
  208. lines.pop(2)
  209. else:
  210. return self.printAndReturnError("invalid method '%s'" % method)
  211. if len(self.pendingMessages) != 0:
  212. return self.printAndReturnError("A previous operation is still pending, please wait")
  213. args = [int(line) if isinstance(line, bool) else line for line in lines]
  214. #print(path, args)
  215. if not needResp:
  216. lo_send(self.lo_target_tcp, path, *args)
  217. return True
  218. messageId = self.lastMessageId
  219. self.lastMessageId += 1
  220. self.pendingMessages.append(messageId)
  221. lo_send(self.lo_target_tcp, path, messageId, *args)
  222. while messageId in self.pendingMessages:
  223. QApplication.processEvents(QEventLoop.AllEvents, 100)
  224. error = self.responses.pop(messageId)
  225. if not error:
  226. return True
  227. self.fLastError = error
  228. return False
  229. def sendMsgAndSetError(self, lines):
  230. return self.sendMsg(lines)
  231. # -------------------------------------------------------------------
  232. def engine_init(self, driverName, clientName):
  233. return self.lo_target_tcp is not None
  234. def engine_close(self):
  235. return True
  236. def engine_idle(self):
  237. return
  238. def is_engine_running(self):
  239. return self.lo_target_tcp is not None
  240. def set_engine_about_to_close(self):
  241. return
  242. # ---------------------------------------------------------------------------------------------------------------------
  243. # OSC Control server
  244. class CarlaControlServerTCP(Server):
  245. def __init__(self, host, rhost):
  246. Server.__init__(self, proto=LO_TCP)
  247. if False:
  248. host = CarlaHostOSC()
  249. self.host = host
  250. self.rhost = rhost
  251. def idle(self):
  252. self.fReceivedMsgs = False
  253. while self.recv(0) and self.fReceivedMsgs:
  254. pass
  255. def getFullURL(self):
  256. if self.rhost:
  257. return "osc.tcp://%s:%i/ctrl" % (self.rhost, self.get_port())
  258. return "%sctrl" % self.get_url()
  259. @make_method('/ctrl/cb', 'iiiiifs')
  260. def carla_cb(self, path, args):
  261. if DEBUG: print(path, args)
  262. self.fReceivedMsgs = True
  263. action, pluginId, value1, value2, value3, valuef, valueStr = args
  264. self.host._setViaCallback(action, pluginId, value1, value2, value3, valuef, valueStr)
  265. engineCallback(self.host, action, pluginId, value1, value2, value3, valuef, valueStr)
  266. @make_method('/ctrl/info', 'iiiihiisssssss')
  267. def carla_info(self, path, args):
  268. if DEBUG: print(path, args)
  269. self.fReceivedMsgs = True
  270. (
  271. pluginId, type_, category, hints, uniqueId, optsAvail, optsEnabled,
  272. name, filename, iconName, realName, label, maker, copyright,
  273. ) = args
  274. hints &= ~PLUGIN_HAS_CUSTOM_UI
  275. pinfo = {
  276. 'type': type_,
  277. 'category': category,
  278. 'hints': hints,
  279. 'optionsAvailable': optsAvail,
  280. 'optionsEnabled': optsEnabled,
  281. 'uniqueId': uniqueId,
  282. 'filename': filename,
  283. 'name': name,
  284. 'label': label,
  285. 'maker': maker,
  286. 'copyright': copyright,
  287. 'iconName': iconName
  288. }
  289. self.host._set_pluginInfoUpdate(pluginId, pinfo)
  290. self.host._set_pluginRealName(pluginId, realName)
  291. @make_method('/ctrl/ports', 'iiiiiiii')
  292. def carla_ports(self, path, args):
  293. if DEBUG: print(path, args)
  294. self.fReceivedMsgs = True
  295. pluginId, audioIns, audioOuts, midiIns, midiOuts, paramIns, paramOuts, paramTotal = args
  296. self.host._set_audioCountInfo(pluginId, {'ins': audioIns, 'outs': audioOuts})
  297. self.host._set_midiCountInfo(pluginId, {'ins': midiOuts, 'outs': midiOuts})
  298. self.host._set_parameterCountInfo(pluginId, paramTotal, {'ins': paramIns, 'outs': paramOuts})
  299. @make_method('/ctrl/paramInfo', 'iissss')
  300. def carla_paramInfo(self, path, args):
  301. if DEBUG: print(path, args)
  302. self.fReceivedMsgs = True
  303. pluginId, paramId, name, unit, comment, groupName = args
  304. paramInfo = {
  305. 'name': name,
  306. 'symbol': "",
  307. 'unit': unit,
  308. 'comment': comment,
  309. 'groupName': groupName,
  310. 'scalePointCount': 0,
  311. 'scalePoints': [],
  312. }
  313. self.host._set_parameterInfo(pluginId, paramId, paramInfo)
  314. @make_method('/ctrl/paramData', 'iiiiiifff')
  315. def carla_paramData(self, path, args):
  316. if DEBUG: print(path, args)
  317. self.fReceivedMsgs = True
  318. pluginId, paramId, type_, hints, midiChan, mappedCtrl, mappedMin, mappedMax, value = args
  319. hints &= ~(PARAMETER_USES_SCALEPOINTS | PARAMETER_USES_CUSTOM_TEXT)
  320. paramData = {
  321. 'type': type_,
  322. 'hints': hints,
  323. 'index': paramId,
  324. 'rindex': -1,
  325. 'midiChannel': midiChan,
  326. 'mappedControlIndex': mappedCtrl,
  327. 'mappedMinimum': mappedMin,
  328. 'mappedMaximum': mappedMax,
  329. }
  330. self.host._set_parameterData(pluginId, paramId, paramData)
  331. self.host._set_parameterValue(pluginId, paramId, value)
  332. @make_method('/ctrl/paramRanges', 'iiffffff')
  333. def carla_paramRanges(self, path, args):
  334. if DEBUG: print(path, args)
  335. self.fReceivedMsgs = True
  336. pluginId, paramId, def_, min_, max_, step, stepSmall, stepLarge = args
  337. paramRanges = {
  338. 'def': def_,
  339. 'min': min_,
  340. 'max': max_,
  341. 'step': step,
  342. 'stepSmall': stepSmall,
  343. 'stepLarge': stepLarge,
  344. }
  345. self.host._set_parameterRanges(pluginId, paramId, paramRanges)
  346. @make_method('/ctrl/count', 'iiiiii')
  347. def carla_count(self, path, args):
  348. if DEBUG: print(path, args)
  349. self.fReceivedMsgs = True
  350. pluginId, pcount, mpcount, cdcount, cp, cmp = args
  351. self.host._set_programCount(pluginId, pcount)
  352. self.host._set_midiProgramCount(pluginId, mpcount)
  353. self.host._set_customDataCount(pluginId, cdcount)
  354. self.host._set_pluginInfoUpdate(pluginId, { 'programCurrent': cp, 'midiProgramCurrent': cmp })
  355. @make_method('/ctrl/pcount', 'iii')
  356. def carla_pcount(self, path, args):
  357. if DEBUG: print(path, args)
  358. self.fReceivedMsgs = True
  359. pluginId, pcount, mpcount = args
  360. self.host._set_programCount(pluginId, pcount)
  361. self.host._set_midiProgramCount(pluginId, mpcount)
  362. @make_method('/ctrl/prog', 'iis')
  363. def carla_prog(self, path, args):
  364. if DEBUG: print(path, args)
  365. self.fReceivedMsgs = True
  366. pluginId, progId, progName = args
  367. self.host._set_programName(pluginId, progId, progName)
  368. @make_method('/ctrl/mprog', 'iiiis')
  369. def carla_mprog(self, path, args):
  370. if DEBUG: print(path, args)
  371. self.fReceivedMsgs = True
  372. pluginId, midiProgId, bank, program, name = args
  373. self.host._set_midiProgramData(pluginId, midiProgId, {'bank': bank, 'program': program, 'name': name})
  374. @make_method('/ctrl/cdata', 'iisss')
  375. def carla_cdata(self, path, args):
  376. if DEBUG: print(path, args)
  377. self.fReceivedMsgs = True
  378. pluginId, index, type_, key, value = args
  379. self.host._set_customData(pluginId, index, { 'type': type_, 'key': key, 'value': value })
  380. @make_method('/ctrl/iparams', 'ifffffff')
  381. def carla_iparams(self, path, args):
  382. if DEBUG: print(path, args)
  383. self.fReceivedMsgs = True
  384. pluginId, active, drywet, volume, balLeft, balRight, pan, ctrlChan = args
  385. self.host._set_internalValue(pluginId, PARAMETER_ACTIVE, active)
  386. self.host._set_internalValue(pluginId, PARAMETER_DRYWET, drywet)
  387. self.host._set_internalValue(pluginId, PARAMETER_VOLUME, volume)
  388. self.host._set_internalValue(pluginId, PARAMETER_BALANCE_LEFT, balLeft)
  389. self.host._set_internalValue(pluginId, PARAMETER_BALANCE_RIGHT, balRight)
  390. self.host._set_internalValue(pluginId, PARAMETER_PANNING, pan)
  391. self.host._set_internalValue(pluginId, PARAMETER_CTRL_CHANNEL, ctrlChan)
  392. @make_method('/ctrl/resp', 'is')
  393. def carla_resp(self, path, args):
  394. if DEBUG: print(path, args)
  395. self.fReceivedMsgs = True
  396. messageId, error = args
  397. self.host.responses[messageId] = error
  398. self.host.pendingMessages.remove(messageId)
  399. @make_method('/ctrl/exit', '')
  400. def carla_exit(self, path, args):
  401. if DEBUG: print(path, args)
  402. self.fReceivedMsgs = True
  403. #self.host.lo_target_tcp = None
  404. self.host.QuitCallback.emit()
  405. @make_method('/ctrl/exit-error', 's')
  406. def carla_exit_error(self, path, args):
  407. if DEBUG: print(path, args)
  408. self.fReceivedMsgs = True
  409. error, = args
  410. self.host.lo_target_tcp = None
  411. self.host.QuitCallback.emit()
  412. self.host.ErrorCallback.emit(error)
  413. @make_method(None, None)
  414. def fallback(self, path, args):
  415. print("ControlServerTCP::fallback(\"%s\") - unknown message, args =" % path, args)
  416. self.fReceivedMsgs = True
  417. # ---------------------------------------------------------------------------------------------------------------------
  418. class CarlaControlServerUDP(Server):
  419. def __init__(self, host, rhost):
  420. Server.__init__(self, proto=LO_UDP)
  421. if False:
  422. host = CarlaHostOSC()
  423. self.host = host
  424. self.rhost = rhost
  425. def idle(self):
  426. self.fReceivedMsgs = False
  427. while self.recv(0) and self.fReceivedMsgs:
  428. pass
  429. def getFullURL(self):
  430. if self.rhost:
  431. return "osc.udp://%s:%i/ctrl" % (self.rhost, self.get_port())
  432. return "%sctrl" % self.get_url()
  433. @make_method('/ctrl/runtime', 'fiihiiif')
  434. def carla_runtime(self, path, args):
  435. self.fReceivedMsgs = True
  436. load, xruns, playing, frame, bar, beat, tick, bpm = args
  437. self.host._set_runtime_info(load, xruns)
  438. self.host._set_transport(bool(playing), frame, bar, beat, tick, bpm)
  439. @make_method('/ctrl/param', 'iif')
  440. def carla_param_fixme(self, path, args):
  441. self.fReceivedMsgs = True
  442. pluginId, paramId, paramValue = args
  443. self.host._set_parameterValue(pluginId, paramId, paramValue)
  444. @make_method('/ctrl/peaks', 'iffff')
  445. def carla_peaks(self, path, args):
  446. self.fReceivedMsgs = True
  447. pluginId, in1, in2, out1, out2 = args
  448. self.host._set_peaks(pluginId, in1, in2, out1, out2)
  449. @make_method(None, None)
  450. def fallback(self, path, args):
  451. print("ControlServerUDP::fallback(\"%s\") - unknown message, args =" % path, args)
  452. self.fReceivedMsgs = True
  453. # ---------------------------------------------------------------------------------------------------------------------
  454. # Main Window
  455. class HostWindowOSC(HostWindow):
  456. def __init__(self, host, oscAddr = None):
  457. self.fCustomOscAddress = oscAddr
  458. HostWindow.__init__(self, host, True)
  459. self.host = host
  460. if False:
  461. # kdevelop likes this :)
  462. host = CarlaHostOSC()
  463. self.host = host
  464. # ----------------------------------------------------------------------------------------------------
  465. # Connect actions to functions
  466. self.ui.act_file_connect.triggered.connect(self.slot_fileConnect)
  467. self.ui.act_file_refresh.triggered.connect(self.slot_fileRefresh)
  468. # ----------------------------------------------------------------------------------------------------
  469. # Final setup
  470. if oscAddr:
  471. QTimer.singleShot(0, self.connectOsc)
  472. def connectOsc(self, addrTCP = None, addrUDP = None, rhost = None):
  473. if self.fCustomOscAddress is not None:
  474. addrTCP = self.fCustomOscAddress.replace("osc.udp://","osc.tcp://")
  475. addrUDP = self.fCustomOscAddress.replace("osc.tcp://","osc.udp://")
  476. else:
  477. if addrTCP is not None:
  478. self.fOscAddressTCP = addrTCP
  479. if addrUDP is not None:
  480. self.fOscAddressUDP = addrUDP
  481. if rhost is not None:
  482. self.fOscReportedHost = rhost
  483. lo_target_tcp_name = addrTCP.rsplit("/", 1)[-1]
  484. lo_target_udp_name = addrUDP.rsplit("/", 1)[-1]
  485. err = None
  486. try:
  487. lo_target_tcp = Address(addrTCP)
  488. lo_server_tcp = CarlaControlServerTCP(self.host, rhost)
  489. lo_send(lo_target_tcp, "/register", lo_server_tcp.getFullURL())
  490. lo_target_udp = Address(addrUDP)
  491. lo_server_udp = CarlaControlServerUDP(self.host, rhost)
  492. lo_send(lo_target_udp, "/register", lo_server_udp.getFullURL())
  493. except AddressError as e:
  494. err = e
  495. except OSError as e:
  496. err = e
  497. except:
  498. err = Exception()
  499. if err is not None:
  500. fullError = self.tr("Failed to connect to the Carla instance.")
  501. if len(err.args) > 0:
  502. fullError += " %s\n%s\n" % (self.tr("Error was:"), err.args[0])
  503. fullError += "\n"
  504. fullError += self.tr("Make sure the remote Carla is running and the URL and Port are correct.") + "\n"
  505. fullError += self.tr("If it still does not work, check your current device and the remote's firewall.")
  506. CustomMessageBox(self,
  507. QMessageBox.Warning,
  508. self.tr("Error"),
  509. self.tr("Connection failed"),
  510. fullError,
  511. QMessageBox.Ok,
  512. QMessageBox.Ok)
  513. return
  514. self.host.lo_server_tcp = lo_server_tcp
  515. self.host.lo_target_tcp = lo_target_tcp
  516. self.host.lo_target_tcp_name = lo_target_tcp_name
  517. self.host.lo_server_udp = lo_server_udp
  518. self.host.lo_target_udp = lo_target_udp
  519. self.host.lo_target_udp_name = lo_target_udp_name
  520. self.ui.act_file_refresh.setEnabled(True)
  521. self.startTimers()
  522. def disconnectOsc(self):
  523. self.killTimers()
  524. self.unregister()
  525. self.removeAllPlugins()
  526. patchcanvas.clear()
  527. self.ui.act_file_refresh.setEnabled(False)
  528. # --------------------------------------------------------------------------------------------------------
  529. def unregister(self):
  530. if self.host.lo_server_tcp is not None:
  531. if self.host.lo_target_tcp is not None:
  532. try:
  533. lo_send(self.host.lo_target_tcp, "/unregister", self.host.lo_server_tcp.getFullURL())
  534. except:
  535. pass
  536. self.host.lo_target_tcp = None
  537. while self.host.lo_server_tcp.recv(0):
  538. pass
  539. #self.host.lo_server_tcp.free()
  540. self.host.lo_server_tcp = None
  541. if self.host.lo_server_udp is not None:
  542. if self.host.lo_target_udp is not None:
  543. try:
  544. lo_send(self.host.lo_target_udp, "/unregister", self.host.lo_server_udp.getFullURL())
  545. except:
  546. pass
  547. self.host.lo_target_udp = None
  548. while self.host.lo_server_udp.recv(0):
  549. pass
  550. #self.host.lo_server_udp.free()
  551. self.host.lo_server_udp = None
  552. self.host.lo_target_tcp_name = ""
  553. self.host.lo_target_udp_name = ""
  554. # --------------------------------------------------------------------------------------------------------
  555. # Timers
  556. def idleFast(self):
  557. HostWindow.idleFast(self)
  558. if self.host.lo_server_tcp is not None:
  559. self.host.lo_server_tcp.idle()
  560. else:
  561. self.disconnectOsc()
  562. if self.host.lo_server_udp is not None:
  563. self.host.lo_server_udp.idle()
  564. else:
  565. self.disconnectOsc()
  566. # --------------------------------------------------------------------------------------------------------
  567. def removeAllPlugins(self):
  568. self.host.fPluginsInfo = {}
  569. HostWindow.removeAllPlugins(self)
  570. # --------------------------------------------------------------------------------------------------------
  571. def loadSettings(self, firstTime):
  572. settings = HostWindow.loadSettings(self, firstTime)
  573. if self.fCustomOscAddress is not None:
  574. self.fOscAddressTCP = settings.value("RemoteAddressTCP", "osc.tcp://127.0.0.1:22752/Carla", str)
  575. self.fOscAddressUDP = settings.value("RemoteAddressUDP", "osc.udp://127.0.0.1:22752/Carla", str)
  576. self.fOscReportedHost = settings.value("RemoteReportedHost", "", str)
  577. def saveSettings(self):
  578. settings = HostWindow.saveSettings(self)
  579. if self.fOscAddressTCP:
  580. settings.setValue("RemoteAddressTCP", self.fOscAddressTCP)
  581. if self.fOscAddressUDP:
  582. settings.setValue("RemoteAddressUDP", self.fOscAddressUDP)
  583. if self.fOscReportedHost:
  584. settings.setValue("RemoteReportedHost", self.fOscReportedHost)
  585. # --------------------------------------------------------------------------------------------------------
  586. @pyqtSlot()
  587. def slot_fileConnect(self):
  588. dialog = ConnectDialog(self)
  589. if not dialog.exec_():
  590. return
  591. host, rhost, tcpPort, udpPort = dialog.getResult()
  592. self.disconnectOsc()
  593. self.connectOsc("osc.tcp://%s:%i/Carla" % (host, tcpPort),
  594. "osc.udp://%s:%i/Carla" % (host, udpPort),
  595. rhost)
  596. @pyqtSlot()
  597. def slot_fileRefresh(self):
  598. if None in (self.host.lo_server_tcp, self.host.lo_server_udp, self.host.lo_target_tcp, self.host.lo_target_udp):
  599. return
  600. lo_send(self.host.lo_target_udp, "/unregister", self.host.lo_server_udp.getFullURL())
  601. while self.host.lo_server_udp.recv(0):
  602. pass
  603. #self.host.lo_server_udp.free()
  604. lo_send(self.host.lo_target_tcp, "/unregister", self.host.lo_server_tcp.getFullURL())
  605. while self.host.lo_server_tcp.recv(0):
  606. pass
  607. #self.host.lo_server_tcp.free()
  608. self.removeAllPlugins()
  609. patchcanvas.clear()
  610. self.host.lo_server_tcp = CarlaControlServerTCP(self.host, self.fOscReportedHost)
  611. self.host.lo_server_udp = CarlaControlServerUDP(self.host, self.fOscReportedHost)
  612. try:
  613. lo_send(self.host.lo_target_tcp, "/register", self.host.lo_server_tcp.getFullURL())
  614. except:
  615. self.disconnectOsc()
  616. return
  617. try:
  618. lo_send(self.host.lo_target_udp, "/register", self.host.lo_server_udp.getFullURL())
  619. except:
  620. self.disconnectOsc()
  621. return
  622. # --------------------------------------------------------------------------------------------------------
  623. @pyqtSlot()
  624. def slot_handleSIGTERM(self):
  625. print("Got SIGTERM -> Closing now")
  626. self.host.pendingMessages = []
  627. self.close()
  628. @pyqtSlot()
  629. def slot_handleQuitCallback(self):
  630. self.disconnectOsc()
  631. HostWindow.slot_handleQuitCallback(self)
  632. # --------------------------------------------------------------------------------------------------------
  633. def closeEvent(self, event):
  634. self.killTimers()
  635. self.unregister()
  636. HostWindow.closeEvent(self, event)
  637. # ------------------------------------------------------------------------------------------------------------