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.

740 lines
26KB

  1. #!/usr/bin/env python3
  2. # SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
  3. # SPDX-License-Identifier: GPL-2.0-or-later
  4. # ------------------------------------------------------------------------------------------------------------
  5. # Imports (Global)
  6. from qt_compat import qt_config
  7. if qt_config == 5:
  8. from PyQt5.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer
  9. from PyQt5.QtGui import QColor, QPainter, QPen
  10. from PyQt5.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow, QApplication
  11. elif qt_config == 6:
  12. from PyQt6.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer
  13. from PyQt6.QtGui import QColor, QPainter, QPen
  14. from PyQt6.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow, QApplication
  15. import ctypes
  16. from time import sleep
  17. # -----------------------------------------------------------------------
  18. # Imports (Custom)
  19. from carla_shared import *
  20. from carla_utils import *
  21. from widgets.scalabledial import ScalableDial
  22. import ui_xycontroller
  23. # -----------------------------------------------------------------------
  24. # Imports (ExternalUI)
  25. from carla_app import CarlaApplication
  26. from externalui import ExternalUI
  27. from widgets.paramspinbox import ParamSpinBox
  28. # ------------------------------------------------------------------------------------------------------------
  29. XYCONTROLLER_PARAMETER_SMOOTH = 0
  30. XYCONTROLLER_PARAMETER_LINEAR = 1
  31. XYCONTROLLER_PARAMETER_SPEED = 2
  32. XYCONTROLLER_PARAMETER_REVERSEY = 3
  33. XYCONTROLLER_PARAMETER_X = 4
  34. XYCONTROLLER_PARAMETER_Y = 5
  35. XYCONTROLLER_PARAMETER_OUT_X = 6
  36. XYCONTROLLER_PARAMETER_OUT_Y = 7
  37. # ------------------------------------------------------------------------------------------------------------
  38. class XYGraphicsScene(QGraphicsScene):
  39. # signals
  40. cursorMoved = pyqtSignal(float,float)
  41. knobsUpdate = pyqtSignal(float,float)
  42. def __init__(self, parent):
  43. QGraphicsScene.__init__(self, parent)
  44. self.cc_x = 1
  45. self.cc_y = 2
  46. self.rparent = parent
  47. self.m_channels = []
  48. self.m_mouseLock = False
  49. self.m_smooth = False
  50. self.m_linear = False
  51. self.m_speed = 8.0 # 1.0 to 100.0
  52. self.m_rSmooth = False # Using right button
  53. self.m_smooth_x = 0.0
  54. self.m_smooth_y = 0.0
  55. self.reverseY = 1.0 # -1.0 when reversed
  56. self.xpPrev = 0.0
  57. self.ypPrev = 0.0
  58. self.time: ctypes.c_uint64 = 0 # I sure just int is quite enough here, but...
  59. self.prevTime: ctypes.c_uint64 = 0
  60. self.setBackgroundBrush(Qt.black)
  61. cursorPen = QPen(QColor(255, 255, 255), 2)
  62. cursorBrush = QColor(255, 255, 255, 50)
  63. self.m_cursor = self.addEllipse(QRectF(-10, -10, 20, 20), cursorPen, cursorBrush)
  64. linePen = QPen(QColor(200, 200, 200, 100), 1, Qt.DashLine)
  65. self.m_lineH = self.addLine(-9999, 0, 9999, 0, linePen)
  66. self.m_lineV = self.addLine(0, -9999, 0, 9999, linePen)
  67. self.p_size = QRectF(-100, -100, 100, 100)
  68. # -------------------------------------------------------------------
  69. def setControlX(self, x: int):
  70. self.cc_x = x
  71. def setControlY(self, y: int):
  72. self.cc_y = y
  73. def setChannels(self, channels):
  74. self.m_channels = channels
  75. def setPosX(self, x: float, forward: bool = True):
  76. if self.m_mouseLock:
  77. return
  78. posX = x * (self.p_size.x() + self.p_size.width())
  79. self.m_cursor.setPos(posX, self.m_cursor.y())
  80. self.m_lineV.setX(posX)
  81. if forward:
  82. value = posX / (self.p_size.x() + self.p_size.width())
  83. self.sendMIDI(value, None)
  84. else:
  85. self.m_smooth_x = posX
  86. def setPosY(self, y: float, forward: bool = True):
  87. if self.m_mouseLock:
  88. return
  89. posY = y * (self.p_size.y() + self.p_size.height()) * self.reverseY
  90. self.m_cursor.setPos(self.m_cursor.x(), posY)
  91. self.m_lineH.setY(posY)
  92. if forward:
  93. value = posY / (self.p_size.y() + self.p_size.height()) * self.reverseY
  94. self.sendMIDI(None, value)
  95. else:
  96. self.m_smooth_y = posY
  97. def setSmoothValues(self, x: float, y: float):
  98. self.m_smooth_x = x * (self.p_size.x() + self.p_size.width())
  99. self.m_smooth_y = y * (self.p_size.y() + self.p_size.height()) * self.reverseY
  100. def setReverseY(self, rev: bool):
  101. self.reverseY = 1 - (int(rev) * 2) # 1.0 or -1.0
  102. # -------------------------------------------------------------------
  103. def updateSize(self, size: QSize):
  104. self.p_size.setRect(-(float(size.width())/2),
  105. -(float(size.height())/2),
  106. size.width(),
  107. size.height())
  108. def updatePos(self, pos: QPointF, filterSame: bool = False, knobsOnly: bool = False):
  109. xp = pos.x() / (self.p_size.x() + self.p_size.width())
  110. yp = pos.y() / (self.p_size.y() + self.p_size.height()) * self.reverseY
  111. if knobsOnly:
  112. self.knobsUpdate.emit(xp * 100, yp * 100)
  113. return
  114. self.m_cursor.setPos(pos)
  115. self.m_lineH.setY(pos.y())
  116. self.m_lineV.setX(pos.x())
  117. # Set 0.05% precision, yet exact final value settling, esp. zero
  118. xp = round(xp * 1000) / 1000
  119. yp = round(yp * 1000) / 1000
  120. self.sendMIDI(xp, yp, filterSame)
  121. self.cursorMoved.emit(xp, yp)
  122. def updateSmooth(self, time):
  123. if not (self.m_smooth or self.m_rSmooth):
  124. return
  125. dx = self.m_smooth_x - self.m_cursor.x()
  126. dy = self.m_smooth_y - self.m_cursor.y()
  127. if dx == dy == 0:
  128. return
  129. same = 0
  130. if abs(dx) <= 0.0005:
  131. self.m_smooth_x = self.m_cursor.x()
  132. same += 1
  133. if abs(dy) <= 0.0005:
  134. self.m_smooth_y = self.m_cursor.y()
  135. same += 1
  136. if same == 2:
  137. return
  138. speed = self.m_speed
  139. mod = QApplication.keyboardModifiers()
  140. if (mod & Qt.ControlModifier):
  141. speed /= 2
  142. elif (mod & Qt.ShiftModifier):
  143. speed *= 2
  144. if self.m_linear:
  145. newX = self.m_cursor.x() + max(min(dx / speed, 1), -1) * speed
  146. newY = self.m_cursor.y() + max(min(dy / speed, 1), -1) * speed
  147. else:
  148. precision = 64 / speed
  149. newX = float(self.m_smooth_x + self.m_cursor.x()*(precision-1)) / precision
  150. newY = float(self.m_smooth_y + self.m_cursor.y()*(precision-1)) / precision
  151. pos = QPointF(newX, newY)
  152. self.updatePos(pos, ((time - self.prevTime) == 1)) # Continuous calls or Not
  153. self.prevTime = time
  154. # -------------------------------------------------------------------
  155. def handleMousePos(self, event):
  156. if (event.buttons() & Qt.MiddleButton):
  157. pos = QPointF(0, 0)
  158. else:
  159. pos = QPointF(event.scenePos())
  160. if not self.p_size.contains(pos):
  161. if pos.x() < self.p_size.x():
  162. pos.setX(self.p_size.x())
  163. elif pos.x() > (self.p_size.x() + self.p_size.width()):
  164. pos.setX(self.p_size.x() + self.p_size.width())
  165. if pos.y() < self.p_size.y():
  166. pos.setY(self.p_size.y())
  167. elif pos.y() > (self.p_size.y() + self.p_size.height()):
  168. pos.setY(self.p_size.y() + self.p_size.height())
  169. self.updatePos(pos, knobsOnly=True)
  170. self.m_smooth_x = pos.x()
  171. self.m_smooth_y = pos.y()
  172. self.m_rSmooth = event.buttons() & Qt.RightButton
  173. # When not smooth, update each time;
  174. # (commented-out part) When smooth, update (re-send same) if click position is same (when smoothing not sends anything). (To match non-smoothed behaviour)
  175. if (not (self.m_smooth or self.m_rSmooth)): # or (abs(self.m_cursor.x() - self.m_smooth_x) <= 0.0005 and abs(self.m_cursor.y() - self.m_smooth_y) <= 0.0005):
  176. self.updatePos(pos)
  177. def sendMIDI(self, xp, yp, filterSame = False):
  178. rate = float(0xff) / 4
  179. prefix = []
  180. msgd = []
  181. if xp is not None:
  182. value = int(xp * rate + rate)
  183. if not (filterSame and (value == self.xpPrev)):
  184. prefix = ["cc"]
  185. msgd.append(self.cc_x)
  186. msgd.append(value)
  187. self.xpPrev = value
  188. if yp is not None:
  189. value = int(yp * rate + rate)
  190. if not (filterSame and (value == self.ypPrev)):
  191. if prefix == []:
  192. prefix = ["cc"]
  193. else:
  194. prefix = ["cc2"]
  195. msgd.append(self.cc_y)
  196. msgd.append(value)
  197. self.ypPrev = value
  198. if not (prefix == []):
  199. self.rparent.send(prefix + msgd)
  200. # -------------------------------------------------------------------
  201. def keyPressEvent(self, event): # QKeyEvent
  202. event.accept()
  203. def wheelEvent(self, event): # QGraphicsSceneWheelEvent
  204. event.accept()
  205. def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
  206. self.m_mouseLock = True
  207. self.handleMousePos(event)
  208. self.rparent.setCursor(Qt.CrossCursor)
  209. QGraphicsScene.mousePressEvent(self, event)
  210. def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
  211. self.handleMousePos(event)
  212. QGraphicsScene.mouseMoveEvent(self, event)
  213. def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
  214. self.m_mouseLock = False
  215. self.rparent.setCursor(Qt.ArrowCursor)
  216. QGraphicsScene.mouseReleaseEvent(self, event)
  217. # -----------------------------------------------------------------------
  218. # External UI
  219. class XYControllerUI(ExternalUI, QMainWindow):
  220. def __init__(self):
  221. ExternalUI.__init__(self)
  222. QMainWindow.__init__(self)
  223. self.ui = ui_xycontroller.Ui_XYControllerW()
  224. self.ui.setupUi(self)
  225. self.fSaveSizeNowChecker = -1
  226. self.isXActual = False
  227. self.isYActual = False
  228. # ---------------------------------------------------------------
  229. # Set-up GUI stuff
  230. self.scene = XYGraphicsScene(self)
  231. # Now knobs are QWidgets, not QDials.
  232. self.ui.dial_x = ScalableDial(self.ui.dial_x, 0, 400, 0, -100, 100, "X", 64, -1, "%", "tube", 1, {})
  233. self.ui.dial_y = ScalableDial(self.ui.dial_y, 1, 400, 0, -100, 100, "Y", 64, -1, "%", "tube", 1, {})
  234. # These are outputs (7-seg displays).
  235. self.ui.dial_out_x = ScalableDial(self.ui.dial_out_x, 2, 400, 0, -100, 100, "Out X", 64, -1, "%", "tube", 1, {'Auto7segWidth':1, }, isOutput=True)
  236. self.ui.dial_out_y = ScalableDial(self.ui.dial_out_y, 3, 400, 0, -100, 100, "Out Y", 64, -1, "%", "tube", 1, {'Auto7segWidth':1, }, isOutput=True)
  237. self.ui.keyboard.setOctaves(10)
  238. self.ui.graphicsView.setScene(self.scene)
  239. self.ui.graphicsView.setRenderHints(QPainter.Antialiasing)
  240. for MIDI_CC in MIDI_CC_LIST:
  241. self.ui.cb_control_x.addItem(MIDI_CC)
  242. self.ui.cb_control_y.addItem(MIDI_CC)
  243. # ---------------------------------------------------------------
  244. # Initial state
  245. self.m_channels = [1]
  246. self.ui.act_ch_01.setChecked(True)
  247. self.ui.act_show_keyboard.setChecked(True)
  248. self.ui.cb_control_y.setCurrentIndex(1)
  249. # ---------------------------------------------------------------
  250. # Connect actions to functions
  251. self.scene.cursorMoved.connect(self.slot_sceneCursorMoved)
  252. self.scene.knobsUpdate.connect(self.slot_setKnobs)
  253. self.ui.keyboard.noteOn.connect(self.slot_noteOn)
  254. self.ui.keyboard.noteOff.connect(self.slot_noteOff)
  255. self.ui.cb_smooth.clicked.connect(self.slot_setSmooth)
  256. self.ui.dial_x.realValueChanged.connect(self.slot_knobValueChangedX)
  257. self.ui.dial_y.realValueChanged.connect(self.slot_knobValueChangedY)
  258. if QT_VERSION >= 0x60000:
  259. self.ui.cb_control_x.currentTextChanged.connect(self.slot_checkCC_X)
  260. self.ui.cb_control_y.currentTextChanged.connect(self.slot_checkCC_Y)
  261. else:
  262. self.ui.cb_control_x.currentIndexChanged[str].connect(self.slot_checkCC_X)
  263. self.ui.cb_control_y.currentIndexChanged[str].connect(self.slot_checkCC_Y)
  264. self.ui.act_ch_01.triggered.connect(self.slot_checkChannel)
  265. self.ui.act_ch_02.triggered.connect(self.slot_checkChannel)
  266. self.ui.act_ch_03.triggered.connect(self.slot_checkChannel)
  267. self.ui.act_ch_04.triggered.connect(self.slot_checkChannel)
  268. self.ui.act_ch_05.triggered.connect(self.slot_checkChannel)
  269. self.ui.act_ch_06.triggered.connect(self.slot_checkChannel)
  270. self.ui.act_ch_07.triggered.connect(self.slot_checkChannel)
  271. self.ui.act_ch_08.triggered.connect(self.slot_checkChannel)
  272. self.ui.act_ch_09.triggered.connect(self.slot_checkChannel)
  273. self.ui.act_ch_10.triggered.connect(self.slot_checkChannel)
  274. self.ui.act_ch_11.triggered.connect(self.slot_checkChannel)
  275. self.ui.act_ch_12.triggered.connect(self.slot_checkChannel)
  276. self.ui.act_ch_13.triggered.connect(self.slot_checkChannel)
  277. self.ui.act_ch_14.triggered.connect(self.slot_checkChannel)
  278. self.ui.act_ch_15.triggered.connect(self.slot_checkChannel)
  279. self.ui.act_ch_16.triggered.connect(self.slot_checkChannel)
  280. self.ui.act_ch_all.triggered.connect(self.slot_checkChannel_all)
  281. self.ui.act_ch_none.triggered.connect(self.slot_checkChannel_none)
  282. self.ui.act_show_keyboard.triggered.connect(self.slot_showKeyboard)
  283. # ---------------------------------------------------------------
  284. # Final stuff
  285. self.fIdleTimer = self.startTimer(60)
  286. self.setWindowTitle(self.fUiName)
  287. self.ready()
  288. # -------------------------------------------------------------------
  289. def setSmooth(self, smooth: bool):
  290. self.scene.m_smooth = smooth
  291. x = self.ui.dial_x.rvalue() / 100
  292. y = self.ui.dial_y.rvalue() / 100
  293. if smooth:
  294. self.scene.setSmoothValues(x, y)
  295. else:
  296. self.scene.setPosX(x, True)
  297. self.scene.setPosY(y, True)
  298. self.slot_sceneCursorMoved(x, y)
  299. @pyqtSlot()
  300. def slot_updateScreen(self):
  301. self.ui.graphicsView.centerOn(0, 0)
  302. self.scene.updateSize(self.ui.graphicsView.size())
  303. @pyqtSlot(int)
  304. def slot_noteOn(self, note):
  305. self.send(["note", True, note])
  306. @pyqtSlot(int)
  307. def slot_noteOff(self, note):
  308. self.send(["note", False, note])
  309. @pyqtSlot(float)
  310. def slot_knobValueChangedX(self, x:float, external:bool=False, firstRun:bool=False):
  311. if not external:
  312. self.sendControl(XYCONTROLLER_PARAMETER_X, x)
  313. else:
  314. self.ui.dial_x.setValue(x, False)
  315. if (not self.scene.m_smooth) or firstRun:
  316. self.sendControl(XYCONTROLLER_PARAMETER_OUT_X, x)
  317. self.ui.dial_out_x.setValue(x, False)
  318. self.scene.setPosX(x / 100, True)
  319. else:
  320. self.scene.setSmoothValues(x / 100, self.ui.dial_y.rvalue() / 100)
  321. @pyqtSlot(float)
  322. def slot_knobValueChangedY(self, y:float, external:bool=False, firstRun:bool=False):
  323. if not external:
  324. self.sendControl(XYCONTROLLER_PARAMETER_Y, y)
  325. else:
  326. self.ui.dial_y.setValue(y, False)
  327. if (not self.scene.m_smooth) or firstRun:
  328. self.sendControl(XYCONTROLLER_PARAMETER_OUT_Y, y)
  329. self.ui.dial_out_y.setValue(y, False)
  330. self.scene.setPosY(y / 100, True)
  331. else:
  332. self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, y / 100)
  333. @pyqtSlot(str)
  334. def slot_checkCC_X(self, text: str):
  335. if not text:
  336. return
  337. cc_x = int(text.split(" ",1)[0])
  338. self.scene.setControlX(cc_x)
  339. self.sendConfigure("cc_x", str(cc_x))
  340. @pyqtSlot(str)
  341. def slot_checkCC_Y(self, text: str):
  342. if not text:
  343. return
  344. cc_y = int(text.split(" ",1)[0])
  345. self.scene.setControlY(cc_y)
  346. self.sendConfigure("cc_y", str(cc_y))
  347. @pyqtSlot(bool)
  348. def slot_checkChannel(self, clicked):
  349. if not self.sender():
  350. return
  351. channel = int(self.sender().text())
  352. if clicked and channel not in self.m_channels:
  353. self.m_channels.append(channel)
  354. elif not clicked and channel in self.m_channels:
  355. self.m_channels.remove(channel)
  356. self.scene.setChannels(self.m_channels)
  357. self.sendConfigure("channels", ",".join(str(c) for c in self.m_channels))
  358. @pyqtSlot()
  359. def slot_checkChannel_all(self):
  360. self.ui.act_ch_01.setChecked(True)
  361. self.ui.act_ch_02.setChecked(True)
  362. self.ui.act_ch_03.setChecked(True)
  363. self.ui.act_ch_04.setChecked(True)
  364. self.ui.act_ch_05.setChecked(True)
  365. self.ui.act_ch_06.setChecked(True)
  366. self.ui.act_ch_07.setChecked(True)
  367. self.ui.act_ch_08.setChecked(True)
  368. self.ui.act_ch_09.setChecked(True)
  369. self.ui.act_ch_10.setChecked(True)
  370. self.ui.act_ch_11.setChecked(True)
  371. self.ui.act_ch_12.setChecked(True)
  372. self.ui.act_ch_13.setChecked(True)
  373. self.ui.act_ch_14.setChecked(True)
  374. self.ui.act_ch_15.setChecked(True)
  375. self.ui.act_ch_16.setChecked(True)
  376. self.m_channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
  377. self.scene.setChannels(self.m_channels)
  378. self.sendConfigure("channels", ",".join(str(c) for c in self.m_channels))
  379. @pyqtSlot()
  380. def slot_checkChannel_none(self):
  381. self.ui.act_ch_01.setChecked(False)
  382. self.ui.act_ch_02.setChecked(False)
  383. self.ui.act_ch_03.setChecked(False)
  384. self.ui.act_ch_04.setChecked(False)
  385. self.ui.act_ch_05.setChecked(False)
  386. self.ui.act_ch_06.setChecked(False)
  387. self.ui.act_ch_07.setChecked(False)
  388. self.ui.act_ch_08.setChecked(False)
  389. self.ui.act_ch_09.setChecked(False)
  390. self.ui.act_ch_10.setChecked(False)
  391. self.ui.act_ch_11.setChecked(False)
  392. self.ui.act_ch_12.setChecked(False)
  393. self.ui.act_ch_13.setChecked(False)
  394. self.ui.act_ch_14.setChecked(False)
  395. self.ui.act_ch_15.setChecked(False)
  396. self.ui.act_ch_16.setChecked(False)
  397. self.m_channels = []
  398. self.scene.setChannels(self.m_channels)
  399. self.sendConfigure("channels", "")
  400. @pyqtSlot(bool)
  401. def slot_setSmooth(self, smooth):
  402. self.setSmooth(smooth)
  403. self.sendConfigure("smooth", "yes" if smooth else "no")
  404. self.sendControl(XYCONTROLLER_PARAMETER_SMOOTH, int(smooth))
  405. @pyqtSlot(float, float)
  406. def slot_sceneCursorMoved(self, xp: float, yp: float):
  407. self.ui.dial_out_x.setValue(xp * 100, False)
  408. self.ui.dial_out_y.setValue(yp * 100, False)
  409. self.sendControl(XYCONTROLLER_PARAMETER_OUT_X, xp * 100)
  410. self.sendControl(XYCONTROLLER_PARAMETER_OUT_Y, yp * 100)
  411. @pyqtSlot(bool)
  412. def slot_showKeyboard(self, yesno):
  413. self.ui.scrollArea.setVisible(yesno)
  414. self.sendConfigure("show-midi-keyboard", "yes" if yesno else "no")
  415. QTimer.singleShot(0, self.slot_updateScreen)
  416. @pyqtSlot(float, float)
  417. def slot_setKnobs(self, x, y):
  418. self.ui.dial_x.setValue(x, False)
  419. self.ui.dial_y.setValue(y, False)
  420. self.sendControl(XYCONTROLLER_PARAMETER_X, x)
  421. self.sendControl(XYCONTROLLER_PARAMETER_Y, y)
  422. # -------------------------------------------------------------------
  423. # DSP Callbacks
  424. # NOTE It called continuously with params 6, 7 (outs, not used here), is this good?
  425. def dspParameterChanged(self, index: int, value: float):
  426. if index == XYCONTROLLER_PARAMETER_SMOOTH:
  427. self.ui.cb_smooth.blockSignals(True)
  428. self.ui.cb_smooth.setChecked(bool(value))
  429. self.ui.cb_smooth.blockSignals(False)
  430. self.setSmooth(bool(value))
  431. elif index == XYCONTROLLER_PARAMETER_LINEAR:
  432. self.scene.m_linear = bool(value)
  433. elif index == XYCONTROLLER_PARAMETER_SPEED:
  434. self.scene.m_speed = value
  435. elif index == XYCONTROLLER_PARAMETER_REVERSEY:
  436. self.scene.setReverseY(bool(value))
  437. elif index == XYCONTROLLER_PARAMETER_X:
  438. self.slot_knobValueChangedX(value, True, not self.isXActual)
  439. self.isXActual = True
  440. elif index == XYCONTROLLER_PARAMETER_Y:
  441. self.slot_knobValueChangedY(value, True, not self.isYActual)
  442. if self.isXActual and not self.isYActual:
  443. # Run it once, BUT when both X & Y are received (they can be shuffled).
  444. self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, self.ui.dial_y.rvalue() / 100)
  445. self.isYActual = True
  446. else:
  447. return
  448. def dspStateChanged(self, key: str, value: str):
  449. if key == "guiWidth":
  450. try:
  451. width = int(value)
  452. except:
  453. width = 0
  454. if width > 0:
  455. self.resize(width, self.height())
  456. elif key == "guiHeight":
  457. try:
  458. height = int(value)
  459. except:
  460. height = 0
  461. if height > 0:
  462. self.resize(self.width(), height)
  463. elif key == "smooth":
  464. smooth = (value == "yes")
  465. self.setSmooth(bool(smooth))
  466. elif key == "show-midi-keyboard":
  467. show = (value == "yes")
  468. self.ui.act_show_keyboard.blockSignals(True)
  469. self.ui.act_show_keyboard.setChecked(show)
  470. self.ui.act_show_keyboard.blockSignals(False)
  471. self.ui.scrollArea.setVisible(show)
  472. elif key == "channels":
  473. if value:
  474. self.m_channels = [int(c) for c in value.split(",")]
  475. else:
  476. self.m_channels = []
  477. self.scene.setChannels(self.m_channels)
  478. self.ui.act_ch_01.setChecked(bool(1 in self.m_channels))
  479. self.ui.act_ch_02.setChecked(bool(2 in self.m_channels))
  480. self.ui.act_ch_03.setChecked(bool(3 in self.m_channels))
  481. self.ui.act_ch_04.setChecked(bool(4 in self.m_channels))
  482. self.ui.act_ch_05.setChecked(bool(5 in self.m_channels))
  483. self.ui.act_ch_06.setChecked(bool(6 in self.m_channels))
  484. self.ui.act_ch_07.setChecked(bool(7 in self.m_channels))
  485. self.ui.act_ch_08.setChecked(bool(8 in self.m_channels))
  486. self.ui.act_ch_09.setChecked(bool(9 in self.m_channels))
  487. self.ui.act_ch_10.setChecked(bool(10 in self.m_channels))
  488. self.ui.act_ch_11.setChecked(bool(11 in self.m_channels))
  489. self.ui.act_ch_12.setChecked(bool(12 in self.m_channels))
  490. self.ui.act_ch_13.setChecked(bool(13 in self.m_channels))
  491. self.ui.act_ch_14.setChecked(bool(14 in self.m_channels))
  492. self.ui.act_ch_15.setChecked(bool(15 in self.m_channels))
  493. self.ui.act_ch_16.setChecked(bool(16 in self.m_channels))
  494. elif key == "cc_x":
  495. cc_x = int(value)
  496. self.scene.setControlX(cc_x)
  497. for cc_index in range(len(MIDI_CC_LIST)):
  498. if cc_x == int(MIDI_CC_LIST[cc_index].split(" ",1)[0]):
  499. self.ui.cb_control_x.blockSignals(True)
  500. self.ui.cb_control_x.setCurrentIndex(cc_index)
  501. self.ui.cb_control_x.blockSignals(False)
  502. break
  503. elif key == "cc_y":
  504. cc_y = int(value)
  505. self.scene.setControlY(cc_y)
  506. for cc_index in range(len(MIDI_CC_LIST)):
  507. if cc_y == int(MIDI_CC_LIST[cc_index].split(" ",1)[0]):
  508. self.ui.cb_control_y.blockSignals(True)
  509. self.ui.cb_control_y.setCurrentIndex(cc_index)
  510. self.ui.cb_control_y.blockSignals(False)
  511. break
  512. def dspNoteReceived(self, onOff, channel, note, velocity):
  513. if channel+1 not in self.m_channels:
  514. return
  515. if onOff:
  516. self.ui.keyboard.sendNoteOn(note, False)
  517. else:
  518. self.ui.keyboard.sendNoteOff(note, False)
  519. # -------------------------------------------------------------------
  520. # ExternalUI Callbacks
  521. def uiShow(self):
  522. self.show()
  523. def uiFocus(self):
  524. self.setWindowState((self.windowState() & ~Qt.WindowMinimized) | Qt.WindowActive)
  525. self.show()
  526. self.raise_()
  527. self.activateWindow()
  528. def uiHide(self):
  529. self.hide()
  530. def uiQuit(self):
  531. self.closeExternalUI()
  532. self.close()
  533. app.quit()
  534. def uiTitleChanged(self, uiTitle):
  535. self.setWindowTitle(uiTitle)
  536. # -------------------------------------------------------------------
  537. # Qt events
  538. def showEvent(self, event):
  539. self.slot_updateScreen()
  540. QMainWindow.showEvent(self, event)
  541. def resizeEvent(self, event):
  542. self.fSaveSizeNowChecker = 0
  543. self.slot_updateScreen()
  544. QMainWindow.resizeEvent(self, event)
  545. def timerEvent(self, event):
  546. self.scene.time += 1
  547. if event.timerId() == self.fIdleTimer:
  548. self.idleExternalUI()
  549. self.scene.updateSmooth(self.scene.time)
  550. if self.fSaveSizeNowChecker == 11:
  551. self.sendConfigure("guiWidth", str(self.width()))
  552. self.sendConfigure("guiHeight", str(self.height()))
  553. self.fSaveSizeNowChecker = -1
  554. elif self.fSaveSizeNowChecker >= 0:
  555. self.fSaveSizeNowChecker += 1
  556. QMainWindow.timerEvent(self, event)
  557. def closeEvent(self, event):
  558. self.closeExternalUI()
  559. QMainWindow.closeEvent(self, event)
  560. # there might be other qt windows open which will block the UI from quitting
  561. app.quit()
  562. #--------------- main ------------------
  563. if __name__ == '__main__':
  564. import resources_rc
  565. pathBinaries, _ = getPaths()
  566. gCarla.utils = CarlaUtils(os.path.join(pathBinaries, "libcarla_utils." + DLL_EXTENSION))
  567. gCarla.utils.set_process_name("XYController")
  568. app = CarlaApplication("XYController")
  569. gui = XYControllerUI()
  570. app.exit_exec()