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