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.

624 lines
22KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # XY Controller UI, taken from Cadence
  4. # Copyright (C) 2011-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 doc/GPL.txt file.
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPointF, QRectF, QSize, QTimer
  20. from PyQt5.QtGui import QColor, QPainter, QPen
  21. from PyQt5.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow
  22. # -----------------------------------------------------------------------
  23. # Imports (Custom)
  24. from carla_shared import *
  25. from carla_utils import *
  26. import ui_xycontroller
  27. # -----------------------------------------------------------------------
  28. # Imports (ExternalUI)
  29. from carla_app import CarlaApplication
  30. from externalui import ExternalUI
  31. from widgets.paramspinbox import ParamSpinBox
  32. # ------------------------------------------------------------------------------------------------------------
  33. XYCONTROLLER_PARAMETER_X = 0
  34. XYCONTROLLER_PARAMETER_Y = 1
  35. # ------------------------------------------------------------------------------------------------------------
  36. class XYGraphicsScene(QGraphicsScene):
  37. # signals
  38. cursorMoved = pyqtSignal(float,float)
  39. def __init__(self, parent):
  40. QGraphicsScene.__init__(self, parent)
  41. self.cc_x = 1
  42. self.cc_y = 2
  43. self.rparent = parent
  44. self.m_channels = []
  45. self.m_mouseLock = False
  46. self.m_smooth = False
  47. self.m_smooth_x = 0.0
  48. self.m_smooth_y = 0.0
  49. self.setBackgroundBrush(Qt.black)
  50. cursorPen = QPen(QColor(255, 255, 255), 2)
  51. cursorBrush = QColor(255, 255, 255, 50)
  52. self.m_cursor = self.addEllipse(QRectF(-10, -10, 20, 20), cursorPen, cursorBrush)
  53. linePen = QPen(QColor(200, 200, 200, 100), 1, Qt.DashLine)
  54. self.m_lineH = self.addLine(-9999, 0, 9999, 0, linePen)
  55. self.m_lineV = self.addLine(0, -9999, 0, 9999, linePen)
  56. self.p_size = QRectF(-100, -100, 100, 100)
  57. # -------------------------------------------------------------------
  58. def setControlX(self, x: int):
  59. self.cc_x = x
  60. def setControlY(self, y: int):
  61. self.cc_y = y
  62. def setChannels(self, channels):
  63. self.m_channels = channels
  64. def setPosX(self, x: float, forward: bool = True):
  65. if self.m_mouseLock:
  66. return
  67. posX = x * (self.p_size.x() + self.p_size.width())
  68. self.m_cursor.setPos(posX, self.m_cursor.y())
  69. self.m_lineV.setX(posX)
  70. if forward:
  71. value = posX / (self.p_size.x() + self.p_size.width());
  72. self.sendMIDI(value, None)
  73. else:
  74. self.m_smooth_x = posX;
  75. def setPosY(self, y: float, forward: bool = True):
  76. if self.m_mouseLock:
  77. return;
  78. posY = y * (self.p_size.y() + self.p_size.height())
  79. self.m_cursor.setPos(self.m_cursor.x(), posY)
  80. self.m_lineH.setY(posY)
  81. if forward:
  82. value = posY / (self.p_size.y() + self.p_size.height())
  83. self.sendMIDI(None, value)
  84. else:
  85. self.m_smooth_y = posY
  86. def setSmooth(self, smooth: bool):
  87. self.m_smooth = smooth
  88. def setSmoothValues(self, x: float, y: float):
  89. self.m_smooth_x = x * (self.p_size.x() + self.p_size.width());
  90. self.m_smooth_y = y * (self.p_size.y() + self.p_size.height());
  91. # -------------------------------------------------------------------
  92. def updateSize(self, size: QSize):
  93. self.p_size.setRect(-(float(size.width())/2),
  94. -(float(size.height())/2),
  95. size.width(),
  96. size.height());
  97. def updateSmooth(self):
  98. if not self.m_smooth:
  99. return
  100. if self.m_cursor.x() == self.m_smooth_x and self.m_cursor.y() == self.m_smooth_y:
  101. return
  102. same = 0
  103. if abs(self.m_cursor.x() - self.m_smooth_x) <= 0.0005:
  104. self.m_smooth_x = self.m_cursor.x()
  105. same += 1
  106. if abs(self.m_cursor.y() - self.m_smooth_y) <= 0.0005:
  107. self.m_smooth_y = self.m_cursor.y()
  108. same += 1
  109. if same == 2:
  110. return
  111. newX = float(self.m_smooth_x + self.m_cursor.x()*7) / 8
  112. newY = float(self.m_smooth_y + self.m_cursor.y()*7) / 8
  113. pos = QPointF(newX, newY)
  114. self.m_cursor.setPos(pos)
  115. self.m_lineH.setY(pos.y())
  116. self.m_lineV.setX(pos.x())
  117. xp = pos.x() / (self.p_size.x() + self.p_size.width())
  118. yp = pos.y() / (self.p_size.y() + self.p_size.height())
  119. self.sendMIDI(xp, yp)
  120. self.cursorMoved.emit(xp, yp)
  121. # -------------------------------------------------------------------
  122. def handleMousePos(self, pos: QPointF):
  123. if not self.p_size.contains(pos):
  124. if pos.x() < self.p_size.x():
  125. pos.setX(self.p_size.x())
  126. elif pos.x() > (self.p_size.x() + self.p_size.width()):
  127. pos.setX(self.p_size.x() + self.p_size.width());
  128. if pos.y() < self.p_size.y():
  129. pos.setY(self.p_size.y())
  130. elif pos.y() > (self.p_size.y() + self.p_size.height()):
  131. pos.setY(self.p_size.y() + self.p_size.height())
  132. self.m_smooth_x = pos.x()
  133. self.m_smooth_y = pos.y()
  134. if not self.m_smooth:
  135. self.m_cursor.setPos(pos)
  136. self.m_lineH.setY(pos.y())
  137. self.m_lineV.setX(pos.x())
  138. xp = pos.x() / (self.p_size.x() + self.p_size.width());
  139. yp = pos.y() / (self.p_size.y() + self.p_size.height());
  140. self.sendMIDI(xp, yp)
  141. self.cursorMoved.emit(xp, yp)
  142. def sendMIDI(self, xp, yp):
  143. rate = float(0xff) / 4
  144. msgd = ["cc2" if xp is not None and yp is not None else "cc"]
  145. if xp is not None:
  146. msgd.append(self.cc_x)
  147. msgd.append(int(xp * rate + rate))
  148. if yp is not None:
  149. msgd.append(self.cc_y)
  150. msgd.append(int(yp * rate + rate))
  151. self.rparent.send(msgd)
  152. # -------------------------------------------------------------------
  153. def keyPressEvent(self, event): # QKeyEvent
  154. event.accept()
  155. def wheelEvent(self, event): # QGraphicsSceneWheelEvent
  156. event.accept()
  157. def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
  158. self.m_mouseLock = True
  159. self.handleMousePos(event.scenePos())
  160. self.rparent.setCursor(Qt.CrossCursor)
  161. QGraphicsScene.mousePressEvent(self, event);
  162. def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
  163. self.handleMousePos(event.scenePos())
  164. QGraphicsScene.mouseMoveEvent(self, event);
  165. def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
  166. self.m_mouseLock = False
  167. self.rparent.setCursor(Qt.ArrowCursor)
  168. QGraphicsScene.mouseReleaseEvent(self, event)
  169. # -----------------------------------------------------------------------
  170. # External UI
  171. class XYControllerUI(ExternalUI, QMainWindow):
  172. def __init__(self):
  173. ExternalUI.__init__(self)
  174. QMainWindow.__init__(self)
  175. self.ui = ui_xycontroller.Ui_XYControllerW()
  176. self.ui.setupUi(self)
  177. self.fSaveSizeNowChecker = -1
  178. # ---------------------------------------------------------------
  179. # Set-up GUI stuff
  180. self.scene = XYGraphicsScene(self)
  181. self.ui.dial_x.setImage(2)
  182. self.ui.dial_y.setImage(2)
  183. self.ui.dial_x.setLabel("X")
  184. self.ui.dial_y.setLabel("Y")
  185. self.ui.keyboard.setOctaves(10)
  186. self.ui.graphicsView.setScene(self.scene)
  187. self.ui.graphicsView.setRenderHints(QPainter.Antialiasing)
  188. for MIDI_CC in MIDI_CC_LIST:
  189. self.ui.cb_control_x.addItem(MIDI_CC)
  190. self.ui.cb_control_y.addItem(MIDI_CC)
  191. # ---------------------------------------------------------------
  192. # Initial state
  193. self.m_channels = [1]
  194. self.ui.act_ch_01.setChecked(True)
  195. self.ui.act_show_keyboard.setChecked(True)
  196. self.ui.cb_control_y.setCurrentIndex(1)
  197. # ---------------------------------------------------------------
  198. # Connect actions to functions
  199. self.scene.cursorMoved.connect(self.slot_sceneCursorMoved)
  200. self.ui.keyboard.noteOn.connect(self.slot_noteOn)
  201. self.ui.keyboard.noteOff.connect(self.slot_noteOff)
  202. self.ui.cb_smooth.clicked.connect(self.slot_setSmooth)
  203. self.ui.dial_x.realValueChanged.connect(self.slot_knobValueChangedX)
  204. self.ui.dial_y.realValueChanged.connect(self.slot_knobValueChangedY)
  205. self.ui.cb_control_x.currentIndexChanged[str].connect(self.slot_checkCC_X)
  206. self.ui.cb_control_y.currentIndexChanged[str].connect(self.slot_checkCC_Y)
  207. self.ui.act_ch_01.triggered.connect(self.slot_checkChannel)
  208. self.ui.act_ch_02.triggered.connect(self.slot_checkChannel)
  209. self.ui.act_ch_03.triggered.connect(self.slot_checkChannel)
  210. self.ui.act_ch_04.triggered.connect(self.slot_checkChannel)
  211. self.ui.act_ch_05.triggered.connect(self.slot_checkChannel)
  212. self.ui.act_ch_06.triggered.connect(self.slot_checkChannel)
  213. self.ui.act_ch_07.triggered.connect(self.slot_checkChannel)
  214. self.ui.act_ch_08.triggered.connect(self.slot_checkChannel)
  215. self.ui.act_ch_09.triggered.connect(self.slot_checkChannel)
  216. self.ui.act_ch_10.triggered.connect(self.slot_checkChannel)
  217. self.ui.act_ch_11.triggered.connect(self.slot_checkChannel)
  218. self.ui.act_ch_12.triggered.connect(self.slot_checkChannel)
  219. self.ui.act_ch_13.triggered.connect(self.slot_checkChannel)
  220. self.ui.act_ch_14.triggered.connect(self.slot_checkChannel)
  221. self.ui.act_ch_15.triggered.connect(self.slot_checkChannel)
  222. self.ui.act_ch_16.triggered.connect(self.slot_checkChannel)
  223. self.ui.act_ch_all.triggered.connect(self.slot_checkChannel_all)
  224. self.ui.act_ch_none.triggered.connect(self.slot_checkChannel_none)
  225. self.ui.act_show_keyboard.triggered.connect(self.slot_showKeyboard)
  226. # ---------------------------------------------------------------
  227. # Final stuff
  228. self.fIdleTimer = self.startTimer(60)
  229. self.setWindowTitle(self.fUiName)
  230. self.ready()
  231. # -------------------------------------------------------------------
  232. @pyqtSlot()
  233. def slot_updateScreen(self):
  234. self.ui.graphicsView.centerOn(0, 0)
  235. self.scene.updateSize(self.ui.graphicsView.size())
  236. dial_x = self.ui.dial_x.rvalue()
  237. dial_y = self.ui.dial_y.rvalue()
  238. self.scene.setPosX(dial_x / 100, False)
  239. self.scene.setPosY(dial_y / 100, False)
  240. self.scene.setSmoothValues(dial_x / 100, dial_y / 100)
  241. @pyqtSlot(int)
  242. def slot_noteOn(self, note):
  243. self.send(["note", True, note])
  244. @pyqtSlot(int)
  245. def slot_noteOff(self, note):
  246. self.send(["note", False, note])
  247. @pyqtSlot(float)
  248. def slot_knobValueChangedX(self, x: float):
  249. self.sendControl(XYCONTROLLER_PARAMETER_X, x)
  250. self.scene.setPosX(x / 100, True)
  251. self.scene.setSmoothValues(x / 100, self.ui.dial_y.rvalue() / 100)
  252. @pyqtSlot(float)
  253. def slot_knobValueChangedY(self, y: float):
  254. self.sendControl(XYCONTROLLER_PARAMETER_Y, y)
  255. self.scene.setPosY(y / 100, True)
  256. self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, y / 100)
  257. @pyqtSlot(str)
  258. def slot_checkCC_X(self, text: str):
  259. if not text:
  260. return
  261. cc_x = int(text.split(" ",1)[0])
  262. self.scene.setControlX(cc_x)
  263. self.sendConfigure("cc_x", str(cc_x))
  264. @pyqtSlot(str)
  265. def slot_checkCC_Y(self, text: str):
  266. if not text:
  267. return
  268. cc_y = int(text.split(" ",1)[0])
  269. self.scene.setControlY(cc_y)
  270. self.sendConfigure("cc_y", str(cc_y))
  271. @pyqtSlot(bool)
  272. def slot_checkChannel(self, clicked):
  273. if not self.sender():
  274. return
  275. channel = int(self.sender().text())
  276. if clicked and channel not in self.m_channels:
  277. self.m_channels.append(channel)
  278. elif not clicked and channel in self.m_channels:
  279. self.m_channels.remove(channel)
  280. self.scene.setChannels(self.m_channels)
  281. self.sendConfigure("channels", ",".join(str(c) for c in self.m_channels))
  282. @pyqtSlot()
  283. def slot_checkChannel_all(self):
  284. self.ui.act_ch_01.setChecked(True)
  285. self.ui.act_ch_02.setChecked(True)
  286. self.ui.act_ch_03.setChecked(True)
  287. self.ui.act_ch_04.setChecked(True)
  288. self.ui.act_ch_05.setChecked(True)
  289. self.ui.act_ch_06.setChecked(True)
  290. self.ui.act_ch_07.setChecked(True)
  291. self.ui.act_ch_08.setChecked(True)
  292. self.ui.act_ch_09.setChecked(True)
  293. self.ui.act_ch_10.setChecked(True)
  294. self.ui.act_ch_11.setChecked(True)
  295. self.ui.act_ch_12.setChecked(True)
  296. self.ui.act_ch_13.setChecked(True)
  297. self.ui.act_ch_14.setChecked(True)
  298. self.ui.act_ch_15.setChecked(True)
  299. self.ui.act_ch_16.setChecked(True)
  300. self.m_channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
  301. self.scene.setChannels(self.m_channels)
  302. self.sendConfigure("channels", ",".join(str(c) for c in self.m_channels))
  303. @pyqtSlot()
  304. def slot_checkChannel_none(self):
  305. self.ui.act_ch_01.setChecked(False)
  306. self.ui.act_ch_02.setChecked(False)
  307. self.ui.act_ch_03.setChecked(False)
  308. self.ui.act_ch_04.setChecked(False)
  309. self.ui.act_ch_05.setChecked(False)
  310. self.ui.act_ch_06.setChecked(False)
  311. self.ui.act_ch_07.setChecked(False)
  312. self.ui.act_ch_08.setChecked(False)
  313. self.ui.act_ch_09.setChecked(False)
  314. self.ui.act_ch_10.setChecked(False)
  315. self.ui.act_ch_11.setChecked(False)
  316. self.ui.act_ch_12.setChecked(False)
  317. self.ui.act_ch_13.setChecked(False)
  318. self.ui.act_ch_14.setChecked(False)
  319. self.ui.act_ch_15.setChecked(False)
  320. self.ui.act_ch_16.setChecked(False)
  321. self.m_channels = []
  322. self.scene.setChannels(self.m_channels)
  323. self.sendConfigure("channels", "")
  324. @pyqtSlot(bool)
  325. def slot_setSmooth(self, smooth):
  326. self.scene.setSmooth(smooth)
  327. self.sendConfigure("smooth", "yes" if smooth else "no")
  328. if smooth:
  329. dial_x = self.ui.dial_x.rvalue()
  330. dial_y = self.ui.dial_y.rvalue()
  331. self.scene.setSmoothValues(dial_x / 100, dial_y / 100)
  332. @pyqtSlot(float, float)
  333. def slot_sceneCursorMoved(self, xp: float, yp: float):
  334. self.ui.dial_x.setValue(xp * 100, False)
  335. self.ui.dial_y.setValue(yp * 100, False)
  336. self.sendControl(XYCONTROLLER_PARAMETER_X, xp * 100)
  337. self.sendControl(XYCONTROLLER_PARAMETER_Y, yp * 100)
  338. @pyqtSlot(bool)
  339. def slot_showKeyboard(self, yesno):
  340. self.ui.scrollArea.setVisible(yesno)
  341. self.sendConfigure("show-midi-keyboard", "yes" if yesno else "no")
  342. QTimer.singleShot(0, self.slot_updateScreen)
  343. # -------------------------------------------------------------------
  344. # DSP Callbacks
  345. def dspParameterChanged(self, index: int, value: float):
  346. if index == XYCONTROLLER_PARAMETER_X:
  347. self.ui.dial_x.setValue(value, False)
  348. self.scene.setPosX(value / 100, False)
  349. elif index == XYCONTROLLER_PARAMETER_Y:
  350. self.ui.dial_y.setValue(value, False)
  351. self.scene.setPosY(value / 100, False)
  352. else:
  353. return
  354. self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100,
  355. self.ui.dial_y.rvalue() / 100)
  356. def dspStateChanged(self, key: str, value: str):
  357. if key == "guiWidth":
  358. try:
  359. width = int(value)
  360. except:
  361. width = 0
  362. if width > 0:
  363. self.resize(width, self.height())
  364. elif key == "guiHeight":
  365. try:
  366. height = int(value)
  367. except:
  368. height = 0
  369. if height > 0:
  370. self.resize(self.width(), height)
  371. elif key == "smooth":
  372. smooth = (value == "yes")
  373. self.ui.cb_smooth.blockSignals(True)
  374. self.ui.cb_smooth.setChecked(smooth)
  375. self.ui.cb_smooth.blockSignals(False)
  376. self.scene.setSmooth(smooth)
  377. if smooth:
  378. dial_x = self.ui.dial_x.rvalue()
  379. dial_y = self.ui.dial_y.rvalue()
  380. self.scene.setSmoothValues(dial_x / 100, dial_y / 100)
  381. elif key == "show-midi-keyboard":
  382. show = (value == "yes")
  383. self.ui.act_show_keyboard.blockSignals(True)
  384. self.ui.act_show_keyboard.setChecked(show)
  385. self.ui.act_show_keyboard.blockSignals(False)
  386. self.ui.scrollArea.setVisible(show)
  387. elif key == "channels":
  388. if value:
  389. self.m_channels = [int(c) for c in value.split(",")]
  390. else:
  391. self.m_channels = []
  392. self.scene.setChannels(self.m_channels)
  393. self.ui.act_ch_01.setChecked(bool(1 in self.m_channels))
  394. self.ui.act_ch_02.setChecked(bool(2 in self.m_channels))
  395. self.ui.act_ch_03.setChecked(bool(3 in self.m_channels))
  396. self.ui.act_ch_04.setChecked(bool(4 in self.m_channels))
  397. self.ui.act_ch_05.setChecked(bool(5 in self.m_channels))
  398. self.ui.act_ch_06.setChecked(bool(6 in self.m_channels))
  399. self.ui.act_ch_07.setChecked(bool(7 in self.m_channels))
  400. self.ui.act_ch_08.setChecked(bool(8 in self.m_channels))
  401. self.ui.act_ch_09.setChecked(bool(9 in self.m_channels))
  402. self.ui.act_ch_10.setChecked(bool(10 in self.m_channels))
  403. self.ui.act_ch_11.setChecked(bool(11 in self.m_channels))
  404. self.ui.act_ch_12.setChecked(bool(12 in self.m_channels))
  405. self.ui.act_ch_13.setChecked(bool(13 in self.m_channels))
  406. self.ui.act_ch_14.setChecked(bool(14 in self.m_channels))
  407. self.ui.act_ch_15.setChecked(bool(15 in self.m_channels))
  408. self.ui.act_ch_16.setChecked(bool(16 in self.m_channels))
  409. elif key == "cc_x":
  410. cc_x = int(value)
  411. self.scene.setControlX(cc_x)
  412. for cc_index in range(len(MIDI_CC_LIST)):
  413. if cc_x == int(MIDI_CC_LIST[cc_index].split(" ",1)[0]):
  414. self.ui.cb_control_x.blockSignals(True)
  415. self.ui.cb_control_x.setCurrentIndex(cc_index)
  416. self.ui.cb_control_x.blockSignals(False)
  417. break
  418. elif key == "cc_y":
  419. cc_y = int(value)
  420. self.scene.setControlY(cc_y)
  421. for cc_index in range(len(MIDI_CC_LIST)):
  422. if cc_y == int(MIDI_CC_LIST[cc_index].split(" ",1)[0]):
  423. self.ui.cb_control_y.blockSignals(True)
  424. self.ui.cb_control_y.setCurrentIndex(cc_index)
  425. self.ui.cb_control_y.blockSignals(False)
  426. break
  427. def dspNoteReceived(self, onOff, channel, note, velocity):
  428. if channel+1 not in self.m_channels:
  429. return
  430. if onOff:
  431. self.ui.keyboard.sendNoteOn(note, False)
  432. else:
  433. self.ui.keyboard.sendNoteOff(note, False)
  434. # -------------------------------------------------------------------
  435. # ExternalUI Callbacks
  436. def uiShow(self):
  437. self.show()
  438. def uiFocus(self):
  439. self.setWindowState((self.windowState() & ~Qt.WindowMinimized) | Qt.WindowActive)
  440. self.show()
  441. self.raise_()
  442. self.activateWindow()
  443. def uiHide(self):
  444. self.hide()
  445. def uiQuit(self):
  446. self.closeExternalUI()
  447. self.close()
  448. app.quit()
  449. def uiTitleChanged(self, uiTitle):
  450. self.setWindowTitle(uiTitle)
  451. # -------------------------------------------------------------------
  452. # Qt events
  453. def showEvent(self, event):
  454. self.slot_updateScreen()
  455. QMainWindow.showEvent(self, event)
  456. def resizeEvent(self, event):
  457. self.fSaveSizeNowChecker = 0
  458. self.slot_updateScreen()
  459. QMainWindow.resizeEvent(self, event)
  460. def timerEvent(self, event):
  461. if event.timerId() == self.fIdleTimer:
  462. self.idleExternalUI()
  463. self.scene.updateSmooth()
  464. if self.fSaveSizeNowChecker == 11:
  465. self.sendConfigure("guiWidth", str(self.width()))
  466. self.sendConfigure("guiHeight", str(self.height()))
  467. self.fSaveSizeNowChecker = -1
  468. elif self.fSaveSizeNowChecker >= 0:
  469. self.fSaveSizeNowChecker += 1
  470. QMainWindow.timerEvent(self, event)
  471. def closeEvent(self, event):
  472. self.closeExternalUI()
  473. QMainWindow.closeEvent(self, event)
  474. # there might be other qt windows open which will block the UI from quitting
  475. app.quit()
  476. #--------------- main ------------------
  477. if __name__ == '__main__':
  478. import resources_rc
  479. pathBinaries, _ = getPaths()
  480. gCarla.utils = CarlaUtils(os.path.join(pathBinaries, "libcarla_utils." + DLL_EXTENSION))
  481. gCarla.utils.set_process_name("XYController")
  482. app = CarlaApplication("XYController")
  483. gui = XYControllerUI()
  484. app.exit_exec()