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.

1730 lines
66KB

  1. #!/usr/bin/env python3
  2. # SPDX-FileCopyrightText: 2011-2025 Filipe Coelho <falktx@falktx.com>
  3. # SPDX-License-Identifier: GPL-2.0-or-later
  4. # ------------------------------------------------------------------------------------------------------------
  5. # Imports (Global)
  6. from abc import abstractmethod
  7. # ------------------------------------------------------------------------------------------------------------
  8. # Imports (PyQt)
  9. from qt_compat import qt_config
  10. if qt_config == 5:
  11. from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray
  12. from PyQt5.QtGui import QCursor, QIcon, QPalette, QPixmap
  13. from PyQt5.QtWidgets import (
  14. QDialog,
  15. QFileDialog,
  16. QInputDialog,
  17. QMenu,
  18. QMessageBox,
  19. QScrollArea,
  20. QVBoxLayout,
  21. QWidget,
  22. )
  23. elif qt_config == 6:
  24. from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray
  25. from PyQt6.QtGui import QCursor, QIcon, QPalette, QPixmap
  26. from PyQt6.QtWidgets import (
  27. QDialog,
  28. QFileDialog,
  29. QInputDialog,
  30. QMenu,
  31. QMessageBox,
  32. QScrollArea,
  33. QVBoxLayout,
  34. QWidget,
  35. )
  36. # ------------------------------------------------------------------------------------------------------------
  37. # Imports (Custom)
  38. import ui_carla_edit
  39. import ui_carla_parameter
  40. from carla_backend import (
  41. BINARY_NATIVE,
  42. CARLA_VERSION_STRING,
  43. CARLA_OS_MAC,
  44. CARLA_OS_WIN,
  45. PLUGIN_INTERNAL,
  46. PLUGIN_DSSI,
  47. PLUGIN_LV2,
  48. PLUGIN_VST2,
  49. PLUGIN_SF2,
  50. PLUGIN_SFZ,
  51. PLUGIN_CAN_DRYWET,
  52. PLUGIN_CAN_VOLUME,
  53. PLUGIN_CAN_BALANCE,
  54. PLUGIN_CAN_PANNING,
  55. PLUGIN_CATEGORY_SYNTH,
  56. PLUGIN_OPTION_FIXED_BUFFERS,
  57. PLUGIN_OPTION_FORCE_STEREO,
  58. PLUGIN_OPTION_MAP_PROGRAM_CHANGES,
  59. PLUGIN_OPTION_USE_CHUNKS,
  60. PLUGIN_OPTION_SEND_CONTROL_CHANGES,
  61. PLUGIN_OPTION_SEND_CHANNEL_PRESSURE,
  62. PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH,
  63. PLUGIN_OPTION_SEND_PITCHBEND,
  64. PLUGIN_OPTION_SEND_ALL_SOUND_OFF,
  65. PLUGIN_OPTION_SEND_PROGRAM_CHANGES,
  66. PLUGIN_OPTION_SKIP_SENDING_NOTES,
  67. PARAMETER_DRYWET,
  68. PARAMETER_VOLUME,
  69. PARAMETER_BALANCE_LEFT,
  70. PARAMETER_BALANCE_RIGHT,
  71. PARAMETER_PANNING,
  72. PARAMETER_CTRL_CHANNEL,
  73. PARAMETER_IS_ENABLED,
  74. PARAMETER_IS_AUTOMATABLE,
  75. PARAMETER_IS_READ_ONLY,
  76. PARAMETER_USES_SCALEPOINTS,
  77. PARAMETER_USES_CUSTOM_TEXT,
  78. PARAMETER_CAN_BE_CV_CONTROLLED,
  79. PARAMETER_INPUT, PARAMETER_OUTPUT,
  80. CONTROL_INDEX_NONE,
  81. CONTROL_INDEX_MIDI_PITCHBEND,
  82. CONTROL_INDEX_MIDI_LEARN,
  83. CONTROL_INDEX_CV
  84. )
  85. from carla_shared import (
  86. MIDI_CC_LIST, MAX_MIDI_CC_LIST_ITEM,
  87. countDecimalPoints,
  88. fontMetricsHorizontalAdvance,
  89. setUpSignals,
  90. gCarla
  91. )
  92. from carla_utils import getPluginTypeAsString
  93. from widgets.collapsablewidget import CollapsibleBox
  94. from widgets.pixmapkeyboard import PixmapKeyboardHArea
  95. # ------------------------------------------------------------------------------------------------------------
  96. # Carla GUI defines
  97. ICON_STATE_ON = 3 # turns on, sets as wait
  98. ICON_STATE_WAIT = 2 # nothing, sets as off
  99. ICON_STATE_OFF = 1 # turns off, sets as null
  100. ICON_STATE_NULL = 0 # nothing
  101. # ------------------------------------------------------------------------------------------------------------
  102. # Plugin Parameter
  103. class PluginParameter(QWidget):
  104. mappedControlChanged = pyqtSignal(int, int)
  105. mappedRangeChanged = pyqtSignal(int, float, float)
  106. midiChannelChanged = pyqtSignal(int, int)
  107. valueChanged = pyqtSignal(int, float)
  108. def __init__(self, parent, host, pInfo, pluginId, tabIndex):
  109. QWidget.__init__(self, parent)
  110. self.host = host
  111. self.ui = ui_carla_parameter.Ui_PluginParameter()
  112. self.ui.setupUi(self)
  113. # -------------------------------------------------------------
  114. # Internal stuff
  115. self.fDecimalPoints = max(2, countDecimalPoints(pInfo['step'], pInfo['stepSmall']))
  116. self.fCanBeInCV = pInfo['hints'] & PARAMETER_CAN_BE_CV_CONTROLLED
  117. self.fMappedCtrl = pInfo['mappedControlIndex']
  118. self.fMappedMinimum = pInfo['mappedMinimum']
  119. self.fMappedMaximum = pInfo['mappedMaximum']
  120. self.fMinimum = pInfo['minimum']
  121. self.fMaximum = pInfo['maximum']
  122. self.fMidiChannel = pInfo['midiChannel']
  123. self.fParameterId = pInfo['index']
  124. self.fPluginId = pluginId
  125. self.fTabIndex = tabIndex
  126. # -------------------------------------------------------------
  127. # Set-up GUI
  128. pType = pInfo['type']
  129. pHints = pInfo['hints']
  130. self.ui.l_name.setText(pInfo['name'])
  131. self.ui.widget.setName(pInfo['name'])
  132. self.ui.widget.setMinimum(pInfo['minimum'])
  133. self.ui.widget.setMaximum(pInfo['maximum'])
  134. self.ui.widget.setDefault(pInfo['default'])
  135. self.ui.widget.setLabel(pInfo['unit'])
  136. self.ui.widget.setStep(pInfo['step'])
  137. self.ui.widget.setStepSmall(pInfo['stepSmall'])
  138. self.ui.widget.setStepLarge(pInfo['stepLarge'])
  139. self.ui.widget.setScalePoints(pInfo['scalePoints'], bool(pHints & PARAMETER_USES_SCALEPOINTS))
  140. if pInfo['comment']:
  141. self.ui.l_name.setToolTip(pInfo['comment'])
  142. self.ui.widget.setToolTip(pInfo['comment'])
  143. if pType == PARAMETER_INPUT:
  144. if not pHints & PARAMETER_IS_ENABLED:
  145. self.ui.l_name.setEnabled(False)
  146. self.ui.l_status.setEnabled(False)
  147. self.ui.widget.setEnabled(False)
  148. self.ui.widget.setReadOnly(True)
  149. self.ui.tb_options.setEnabled(False)
  150. elif not pHints & PARAMETER_IS_AUTOMATABLE:
  151. self.ui.l_status.setEnabled(False)
  152. self.ui.tb_options.setEnabled(False)
  153. if pHints & PARAMETER_IS_READ_ONLY:
  154. self.ui.l_status.setEnabled(False)
  155. self.ui.widget.setReadOnly(True)
  156. self.ui.tb_options.setEnabled(False)
  157. elif pType == PARAMETER_OUTPUT:
  158. self.ui.widget.setReadOnly(True)
  159. else:
  160. self.ui.l_status.setVisible(False)
  161. self.ui.widget.setVisible(False)
  162. self.ui.tb_options.setVisible(False)
  163. # Only set value after all hints are handled
  164. self.ui.widget.setValue(pInfo['current'])
  165. if pHints & PARAMETER_USES_CUSTOM_TEXT and not host.isPlugin:
  166. self.ui.widget.setTextCallback(self._textCallBack)
  167. self.ui.l_status.setFixedWidth(fontMetricsHorizontalAdvance(self.ui.l_status.fontMetrics(),
  168. self.tr("CC%i Ch%i" % (119,16))))
  169. self.ui.widget.setValueCallback(self._valueCallBack)
  170. self.ui.widget.updateAll()
  171. self.setMappedControlIndex(pInfo['mappedControlIndex'])
  172. self.setMidiChannel(pInfo['midiChannel'])
  173. self.updateStatusLabel()
  174. # -------------------------------------------------------------
  175. # Set-up connections
  176. self.ui.tb_options.clicked.connect(self.slot_optionsCustomMenu)
  177. self.ui.widget.dragStateChanged.connect(self.slot_parameterDragStateChanged)
  178. # -------------------------------------------------------------
  179. def getPluginId(self):
  180. return self.fPluginId
  181. def getTabIndex(self):
  182. return self.fTabIndex
  183. def setPluginId(self, pluginId):
  184. self.fPluginId = pluginId
  185. def setDefault(self, value):
  186. self.ui.widget.setDefault(value)
  187. def setValue(self, value):
  188. self.ui.widget.blockSignals(True)
  189. self.ui.widget.setValue(value)
  190. self.ui.widget.blockSignals(False)
  191. def setMappedControlIndex(self, control):
  192. self.fMappedCtrl = control
  193. self.updateStatusLabel()
  194. def setMappedRange(self, minimum, maximum):
  195. self.fMappedMinimum = minimum
  196. self.fMappedMaximum = maximum
  197. def setMidiChannel(self, channel):
  198. self.fMidiChannel = channel
  199. self.updateStatusLabel()
  200. def setLabelWidth(self, width):
  201. self.ui.l_name.setFixedWidth(width)
  202. def updateStatusLabel(self):
  203. if self.fMappedCtrl == CONTROL_INDEX_NONE:
  204. text = self.tr("Unmapped")
  205. elif self.fMappedCtrl == CONTROL_INDEX_CV:
  206. text = self.tr("CV export")
  207. elif self.fMappedCtrl == CONTROL_INDEX_MIDI_PITCHBEND:
  208. text = self.tr("PBend Ch%i" % (self.fMidiChannel,))
  209. elif self.fMappedCtrl == CONTROL_INDEX_MIDI_LEARN:
  210. text = self.tr("MIDI Learn")
  211. else:
  212. text = self.tr("CC%i Ch%i" % (self.fMappedCtrl, self.fMidiChannel))
  213. self.ui.l_status.setText(text)
  214. @pyqtSlot()
  215. def slot_optionsCustomMenu(self):
  216. menu = QMenu(self)
  217. if self.fMappedCtrl == CONTROL_INDEX_NONE:
  218. title = self.tr("Unmapped")
  219. elif self.fMappedCtrl == CONTROL_INDEX_CV:
  220. title = self.tr("Exposed as CV port")
  221. elif self.fMappedCtrl == CONTROL_INDEX_MIDI_PITCHBEND:
  222. title = self.tr("Mapped to MIDI Pitchbend, channel %i" % (self.fMidiChannel,))
  223. elif self.fMappedCtrl == CONTROL_INDEX_MIDI_LEARN:
  224. title = self.tr("MIDI Learn active")
  225. else:
  226. title = self.tr("Mapped to MIDI control %i, channel %i" % (self.fMappedCtrl, self.fMidiChannel))
  227. if self.fMappedCtrl != CONTROL_INDEX_NONE:
  228. title += " (range: %g-%g)" % (self.fMappedMinimum, self.fMappedMaximum)
  229. actTitle = menu.addAction(title)
  230. actTitle.setEnabled(False)
  231. menu.addSeparator()
  232. actUnmap = menu.addAction(self.tr("Unmap"))
  233. if self.fMappedCtrl == CONTROL_INDEX_NONE:
  234. actUnmap.setCheckable(True)
  235. actUnmap.setChecked(True)
  236. if self.fCanBeInCV:
  237. menu.addSection("CV")
  238. actCV = menu.addAction(self.tr("Expose as CV port"))
  239. if self.fMappedCtrl == CONTROL_INDEX_CV:
  240. actCV.setCheckable(True)
  241. actCV.setChecked(True)
  242. else:
  243. actCV = None
  244. menu.addSection("MIDI")
  245. if not self.ui.widget.isReadOnly():
  246. actLearn = menu.addAction(self.tr("MIDI Learn"))
  247. if self.fMappedCtrl == CONTROL_INDEX_MIDI_LEARN:
  248. actLearn.setCheckable(True)
  249. actLearn.setChecked(True)
  250. else:
  251. actLearn = None
  252. menuMIDI = menu.addMenu(self.tr("MIDI Control"))
  253. if self.fMappedCtrl not in (CONTROL_INDEX_NONE,
  254. CONTROL_INDEX_CV,
  255. CONTROL_INDEX_MIDI_PITCHBEND,
  256. CONTROL_INDEX_MIDI_LEARN):
  257. action = menuMIDI.menuAction()
  258. action.setCheckable(True)
  259. action.setChecked(True)
  260. inlist = False
  261. actCCs = []
  262. for cc in MIDI_CC_LIST:
  263. action = menuMIDI.addAction(cc)
  264. actCCs.append(action)
  265. if self.fMappedCtrl >= 0 and self.fMappedCtrl <= MAX_MIDI_CC_LIST_ITEM:
  266. ccx = int(cc.split(" [", 1)[0], 10)
  267. if ccx > self.fMappedCtrl and not inlist:
  268. inlist = True
  269. action = menuMIDI.addAction(self.tr("%02i [0x%02X] (Custom)" % (self.fMappedCtrl,
  270. self.fMappedCtrl)))
  271. action.setCheckable(True)
  272. action.setChecked(True)
  273. actCCs.append(action)
  274. elif ccx == self.fMappedCtrl:
  275. inlist = True
  276. action.setCheckable(True)
  277. action.setChecked(True)
  278. if self.fMappedCtrl > MAX_MIDI_CC_LIST_ITEM and self.fMappedCtrl <= 0x77:
  279. action = menuMIDI.addAction(self.tr("%02i [0x%02X] (Custom)" % (self.fMappedCtrl, self.fMappedCtrl)))
  280. action.setCheckable(True)
  281. action.setChecked(True)
  282. actCCs.append(action)
  283. actCustomCC = menuMIDI.addAction(self.tr("Custom..."))
  284. # TODO
  285. #actPitchbend = menu.addAction(self.tr("MIDI Pitchbend"))
  286. #if self.fMappedCtrl == CONTROL_INDEX_MIDI_PITCHBEND:
  287. #actPitchbend.setCheckable(True)
  288. #actPitchbend.setChecked(True)
  289. menuChannel = menu.addMenu(self.tr("MIDI Channel"))
  290. actChannels = []
  291. for i in range(1, 16+1):
  292. action = menuChannel.addAction("%i" % i)
  293. actChannels.append(action)
  294. if self.fMidiChannel == i:
  295. action.setCheckable(True)
  296. action.setChecked(True)
  297. if self.fMappedCtrl != CONTROL_INDEX_NONE:
  298. if self.fMappedCtrl == CONTROL_INDEX_CV:
  299. menu.addSection("Range (Scaled CV input)")
  300. else:
  301. menu.addSection("Range (MIDI bounds)")
  302. actRangeMinimum = menu.addAction(self.tr("Set minimum... (%g)" % self.fMappedMinimum))
  303. actRangeMaximum = menu.addAction(self.tr("Set maximum... (%g)" % self.fMappedMaximum))
  304. else:
  305. actRangeMinimum = actRangeMaximum = None
  306. actSel = menu.exec_(QCursor.pos())
  307. if not actSel:
  308. return
  309. if actSel in actChannels:
  310. channel = int(actSel.text())
  311. self.fMidiChannel = channel
  312. self.updateStatusLabel()
  313. self.midiChannelChanged.emit(self.fParameterId, channel)
  314. return
  315. if actSel == actRangeMinimum:
  316. value, ok = QInputDialog.getDouble(self,
  317. self.tr("Custom Minimum"),
  318. "Custom minimum value to use:",
  319. self.fMappedMinimum,
  320. self.fMinimum if self.fMappedCtrl != CONTROL_INDEX_CV else -9e6,
  321. self.fMaximum if self.fMappedCtrl != CONTROL_INDEX_CV else 9e6,
  322. self.fDecimalPoints)
  323. if not ok:
  324. return
  325. self.fMappedMinimum = value
  326. self.mappedRangeChanged.emit(self.fParameterId, self.fMappedMinimum, self.fMappedMaximum)
  327. return
  328. if actSel == actRangeMaximum:
  329. value, ok = QInputDialog.getDouble(self,
  330. self.tr("Custom Maximum"),
  331. "Custom maximum value to use:",
  332. self.fMappedMaximum,
  333. self.fMinimum if self.fMappedCtrl != CONTROL_INDEX_CV else -9e6,
  334. self.fMaximum if self.fMappedCtrl != CONTROL_INDEX_CV else 9e6,
  335. self.fDecimalPoints)
  336. if not ok:
  337. return
  338. self.fMappedMaximum = value
  339. self.mappedRangeChanged.emit(self.fParameterId, self.fMappedMinimum, self.fMappedMaximum)
  340. return
  341. if actSel == actUnmap:
  342. ctrl = CONTROL_INDEX_NONE
  343. elif actSel == actCV:
  344. ctrl = CONTROL_INDEX_CV
  345. elif actSel == actCustomCC:
  346. value = self.fMappedCtrl if self.fMappedCtrl >= 0x01 and self.fMappedCtrl <= 0x77 else 1
  347. ctrl, ok = QInputDialog.getInt(self,
  348. self.tr("Custom CC"),
  349. "Custom MIDI CC to use:",
  350. value,
  351. 0x01, 0x77, 1)
  352. if not ok:
  353. return
  354. #elif actSel == actPitchbend:
  355. #ctrl = CONTROL_INDEX_MIDI_PITCHBEND
  356. elif actSel == actLearn:
  357. ctrl = CONTROL_INDEX_MIDI_LEARN
  358. elif actSel in actCCs:
  359. ctrl = int(actSel.text().split(" ", 1)[0].replace("&",""), 10)
  360. else:
  361. return
  362. self.fMappedCtrl = ctrl
  363. self.updateStatusLabel()
  364. self.mappedControlChanged.emit(self.fParameterId, ctrl)
  365. @pyqtSlot(bool)
  366. def slot_parameterDragStateChanged(self, touch):
  367. self.host.set_parameter_touch(self.fPluginId, self.fParameterId, touch)
  368. def _textCallBack(self):
  369. return self.host.get_parameter_text(self.fPluginId, self.fParameterId)
  370. def _valueCallBack(self, value):
  371. self.valueChanged.emit(self.fParameterId, value)
  372. # ------------------------------------------------------------------------------------------------------------
  373. # Plugin Editor Parent (Meta class)
  374. class PluginEditParentMeta():
  375. #class PluginEditParentMeta(metaclass=ABCMeta):
  376. @abstractmethod
  377. def editDialogVisibilityChanged(self, pluginId, visible):
  378. raise NotImplementedError
  379. @abstractmethod
  380. def editDialogPluginHintsChanged(self, pluginId, hints):
  381. raise NotImplementedError
  382. @abstractmethod
  383. def editDialogParameterValueChanged(self, pluginId, parameterId, value):
  384. raise NotImplementedError
  385. @abstractmethod
  386. def editDialogProgramChanged(self, pluginId, index):
  387. raise NotImplementedError
  388. @abstractmethod
  389. def editDialogMidiProgramChanged(self, pluginId, index):
  390. raise NotImplementedError
  391. @abstractmethod
  392. def editDialogNotePressed(self, pluginId, note):
  393. raise NotImplementedError
  394. @abstractmethod
  395. def editDialogNoteReleased(self, pluginId, note):
  396. raise NotImplementedError
  397. @abstractmethod
  398. def editDialogMidiActivityChanged(self, pluginId, onOff):
  399. raise NotImplementedError
  400. # ------------------------------------------------------------------------------------------------------------
  401. # Plugin Editor (Built-in)
  402. class PluginEdit(QDialog):
  403. # signals
  404. SIGTERM = pyqtSignal()
  405. SIGUSR1 = pyqtSignal()
  406. def __init__(self, parent, host, pluginId):
  407. QDialog.__init__(self, parent.window() if parent is not None else None)
  408. self.host = host
  409. self.ui = ui_carla_edit.Ui_PluginEdit()
  410. self.ui.setupUi(self)
  411. # -------------------------------------------------------------
  412. # Internal stuff
  413. self.fGeometry = QByteArray()
  414. self.fParent = parent
  415. self.fPluginId = pluginId
  416. self.fPluginInfo = None
  417. self.fCurrentStateFilename = None
  418. self.fControlChannel = round(host.get_internal_parameter_value(pluginId, PARAMETER_CTRL_CHANNEL))
  419. self.fFirstInit = True
  420. self.fParameterList = [] # (type, id, widget)
  421. self.fParametersToUpdate = [] # (id, value)
  422. self.fPlayingNotes = [] # (channel, note)
  423. self.fTabIconOff = QIcon(":/scalable/led_off.svg")
  424. self.fTabIconOn = QIcon(":/scalable/led_yellow.svg")
  425. self.fTabIconTimers = []
  426. # used during testing
  427. self.fIdleTimerId = 0
  428. # -------------------------------------------------------------
  429. # Set-up GUI
  430. labelPluginFont = self.ui.label_plugin.font()
  431. labelPluginFont.setPixelSize(15)
  432. labelPluginFont.setWeight(75)
  433. self.ui.label_plugin.setFont(labelPluginFont)
  434. self.ui.dial_drywet.setCustomPaintMode(self.ui.dial_drywet.CUSTOM_PAINT_MODE_CARLA_WET)
  435. self.ui.dial_drywet.setImage(3)
  436. self.ui.dial_drywet.setLabel("Dry/Wet")
  437. self.ui.dial_drywet.setMinimum(0.0)
  438. self.ui.dial_drywet.setMaximum(1.0)
  439. self.ui.dial_drywet.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_DRYWET))
  440. self.ui.dial_vol.setCustomPaintMode(self.ui.dial_vol.CUSTOM_PAINT_MODE_CARLA_VOL)
  441. self.ui.dial_vol.setImage(3)
  442. self.ui.dial_vol.setLabel("Volume")
  443. self.ui.dial_vol.setMinimum(0.0)
  444. self.ui.dial_vol.setMaximum(1.27)
  445. self.ui.dial_vol.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_VOLUME))
  446. self.ui.dial_b_left.setCustomPaintMode(self.ui.dial_b_left.CUSTOM_PAINT_MODE_CARLA_L)
  447. self.ui.dial_b_left.setImage(4)
  448. self.ui.dial_b_left.setLabel("L")
  449. self.ui.dial_b_left.setMinimum(-1.0)
  450. self.ui.dial_b_left.setMaximum(1.0)
  451. self.ui.dial_b_left.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_LEFT))
  452. self.ui.dial_b_right.setCustomPaintMode(self.ui.dial_b_right.CUSTOM_PAINT_MODE_CARLA_R)
  453. self.ui.dial_b_right.setImage(4)
  454. self.ui.dial_b_right.setLabel("R")
  455. self.ui.dial_b_right.setMinimum(-1.0)
  456. self.ui.dial_b_right.setMaximum(1.0)
  457. self.ui.dial_b_right.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_RIGHT))
  458. self.ui.dial_pan.setCustomPaintMode(self.ui.dial_b_right.CUSTOM_PAINT_MODE_CARLA_PAN)
  459. self.ui.dial_pan.setImage(4)
  460. self.ui.dial_pan.setLabel("Pan")
  461. self.ui.dial_pan.setMinimum(-1.0)
  462. self.ui.dial_pan.setMaximum(1.0)
  463. self.ui.dial_pan.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_PANNING))
  464. self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1)
  465. self.ui.scrollArea = PixmapKeyboardHArea(self)
  466. self.ui.keyboard = self.ui.scrollArea.keyboard
  467. self.ui.keyboard.setEnabled(self.fControlChannel >= 0)
  468. self.layout().addWidget(self.ui.scrollArea)
  469. self.ui.scrollArea.setEnabled(False)
  470. self.ui.scrollArea.setVisible(False)
  471. # todo
  472. self.ui.rb_balance.setEnabled(False)
  473. self.ui.rb_balance.setVisible(False)
  474. self.ui.rb_pan.setEnabled(False)
  475. self.ui.rb_pan.setVisible(False)
  476. flags = self.windowFlags()
  477. flags &= ~Qt.WindowContextHelpButtonHint
  478. self.setWindowFlags(flags)
  479. self.reloadAll()
  480. self.fFirstInit = False
  481. # -------------------------------------------------------------
  482. # Set-up connections
  483. self.finished.connect(self.slot_finished)
  484. self.ui.ch_fixed_buffer.clicked.connect(self.slot_optionChanged)
  485. self.ui.ch_force_stereo.clicked.connect(self.slot_optionChanged)
  486. self.ui.ch_map_program_changes.clicked.connect(self.slot_optionChanged)
  487. self.ui.ch_use_chunks.clicked.connect(self.slot_optionChanged)
  488. self.ui.ch_send_notes.clicked.connect(self.slot_optionChanged)
  489. self.ui.ch_send_program_changes.clicked.connect(self.slot_optionChanged)
  490. self.ui.ch_send_control_changes.clicked.connect(self.slot_optionChanged)
  491. self.ui.ch_send_channel_pressure.clicked.connect(self.slot_optionChanged)
  492. self.ui.ch_send_note_aftertouch.clicked.connect(self.slot_optionChanged)
  493. self.ui.ch_send_pitchbend.clicked.connect(self.slot_optionChanged)
  494. self.ui.ch_send_all_sound_off.clicked.connect(self.slot_optionChanged)
  495. self.ui.dial_drywet.realValueChanged.connect(self.slot_dryWetChanged)
  496. self.ui.dial_vol.realValueChanged.connect(self.slot_volumeChanged)
  497. self.ui.dial_b_left.realValueChanged.connect(self.slot_balanceLeftChanged)
  498. self.ui.dial_b_right.realValueChanged.connect(self.slot_balanceRightChanged)
  499. self.ui.dial_pan.realValueChanged.connect(self.slot_panChanged)
  500. self.ui.sb_ctrl_channel.valueChanged.connect(self.slot_ctrlChannelChanged)
  501. self.ui.dial_drywet.customContextMenuRequested.connect(self.slot_knobCustomMenu)
  502. self.ui.dial_vol.customContextMenuRequested.connect(self.slot_knobCustomMenu)
  503. self.ui.dial_b_left.customContextMenuRequested.connect(self.slot_knobCustomMenu)
  504. self.ui.dial_b_right.customContextMenuRequested.connect(self.slot_knobCustomMenu)
  505. self.ui.dial_pan.customContextMenuRequested.connect(self.slot_knobCustomMenu)
  506. self.ui.sb_ctrl_channel.customContextMenuRequested.connect(self.slot_channelCustomMenu)
  507. self.ui.keyboard.noteOn.connect(self.slot_noteOn)
  508. self.ui.keyboard.noteOff.connect(self.slot_noteOff)
  509. self.ui.cb_programs.currentIndexChanged.connect(self.slot_programIndexChanged)
  510. self.ui.cb_midi_programs.currentIndexChanged.connect(self.slot_midiProgramIndexChanged)
  511. self.ui.b_save_state.clicked.connect(self.slot_stateSave)
  512. self.ui.b_load_state.clicked.connect(self.slot_stateLoad)
  513. host.NoteOnCallback.connect(self.slot_handleNoteOnCallback)
  514. host.NoteOffCallback.connect(self.slot_handleNoteOffCallback)
  515. host.UpdateCallback.connect(self.slot_handleUpdateCallback)
  516. host.ReloadInfoCallback.connect(self.slot_handleReloadInfoCallback)
  517. host.ReloadParametersCallback.connect(self.slot_handleReloadParametersCallback)
  518. host.ReloadProgramsCallback.connect(self.slot_handleReloadProgramsCallback)
  519. host.ReloadAllCallback.connect(self.slot_handleReloadAllCallback)
  520. #------------------------------------------------------------------
  521. @pyqtSlot(int, int, int, int)
  522. def slot_handleNoteOnCallback(self, pluginId, channel, note, velocity):
  523. if self.fPluginId != pluginId:
  524. return
  525. if self.fControlChannel == channel:
  526. self.ui.keyboard.sendNoteOn(note, False)
  527. playItem = (channel, note)
  528. if playItem not in self.fPlayingNotes:
  529. self.fPlayingNotes.append(playItem)
  530. if len(self.fPlayingNotes) == 1 and self.fParent is not None:
  531. self.fParent.editDialogMidiActivityChanged(self.fPluginId, True)
  532. @pyqtSlot(int, int, int)
  533. def slot_handleNoteOffCallback(self, pluginId, channel, note):
  534. if self.fPluginId != pluginId:
  535. return
  536. if self.fControlChannel == channel:
  537. self.ui.keyboard.sendNoteOff(note, False)
  538. playItem = (channel, note)
  539. if playItem in self.fPlayingNotes:
  540. self.fPlayingNotes.remove(playItem)
  541. if self.fPlayingNotes and self.fParent is not None:
  542. self.fParent.editDialogMidiActivityChanged(self.fPluginId, False)
  543. @pyqtSlot(int)
  544. def slot_handleUpdateCallback(self, pluginId):
  545. if self.fPluginId == pluginId:
  546. self.updateInfo()
  547. @pyqtSlot(int)
  548. def slot_handleReloadInfoCallback(self, pluginId):
  549. if self.fPluginId == pluginId:
  550. self.reloadInfo()
  551. @pyqtSlot(int)
  552. def slot_handleReloadParametersCallback(self, pluginId):
  553. if self.fPluginId == pluginId:
  554. self.reloadParameters()
  555. @pyqtSlot(int)
  556. def slot_handleReloadProgramsCallback(self, pluginId):
  557. if self.fPluginId == pluginId:
  558. self.reloadPrograms()
  559. @pyqtSlot(int)
  560. def slot_handleReloadAllCallback(self, pluginId):
  561. if self.fPluginId == pluginId:
  562. self.reloadAll()
  563. #------------------------------------------------------------------
  564. def updateInfo(self):
  565. # Update current program text
  566. if self.ui.cb_programs.count() > 0:
  567. pIndex = self.ui.cb_programs.currentIndex()
  568. if pIndex >= 0:
  569. pName = self.host.get_program_name(self.fPluginId, pIndex)
  570. #pName = pName[:40] + (pName[40:] and "...")
  571. self.ui.cb_programs.setItemText(pIndex, pName)
  572. # Update current midi program text
  573. if self.ui.cb_midi_programs.count() > 0:
  574. mpIndex = self.ui.cb_midi_programs.currentIndex()
  575. if mpIndex >= 0:
  576. mpData = self.host.get_midi_program_data(self.fPluginId, mpIndex)
  577. mpBank = mpData['bank']
  578. mpProg = mpData['program']
  579. mpName = mpData['name']
  580. #mpName = mpName[:40] + (mpName[40:] and "...")
  581. self.ui.cb_midi_programs.setItemText(mpIndex, "%03i:%03i - %s" % (mpBank+1, mpProg+1, mpName))
  582. # Update all parameter values
  583. for _, paramId, paramWidget in self.fParameterList:
  584. paramWidget.blockSignals(True)
  585. paramWidget.setValue(self.host.get_current_parameter_value(self.fPluginId, paramId))
  586. paramWidget.blockSignals(False)
  587. # and the internal ones too
  588. self.ui.dial_drywet.blockSignals(True)
  589. self.ui.dial_drywet.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_DRYWET))
  590. self.ui.dial_drywet.blockSignals(False)
  591. self.ui.dial_vol.blockSignals(True)
  592. self.ui.dial_vol.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_VOLUME))
  593. self.ui.dial_vol.blockSignals(False)
  594. self.ui.dial_b_left.blockSignals(True)
  595. self.ui.dial_b_left.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_BALANCE_LEFT))
  596. self.ui.dial_b_left.blockSignals(False)
  597. self.ui.dial_b_right.blockSignals(True)
  598. self.ui.dial_b_right.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_BALANCE_RIGHT))
  599. self.ui.dial_b_right.blockSignals(False)
  600. self.ui.dial_pan.blockSignals(True)
  601. self.ui.dial_pan.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_PANNING))
  602. self.ui.dial_pan.blockSignals(False)
  603. self.fControlChannel = round(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_CTRL_CHANNEL))
  604. self.ui.sb_ctrl_channel.blockSignals(True)
  605. self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1)
  606. self.ui.sb_ctrl_channel.blockSignals(False)
  607. self.ui.keyboard.allNotesOff()
  608. self._updateCtrlPrograms()
  609. self.fParametersToUpdate = []
  610. #------------------------------------------------------------------
  611. def reloadAll(self):
  612. self.fPluginInfo = self.host.get_plugin_info(self.fPluginId)
  613. self.reloadInfo()
  614. self.reloadParameters()
  615. self.reloadPrograms()
  616. if self.fPluginInfo['type'] == PLUGIN_LV2:
  617. self.ui.b_save_state.setEnabled(False)
  618. if not self.ui.scrollArea.isEnabled():
  619. self.resize(self.width(), self.height()-self.ui.scrollArea.height())
  620. #------------------------------------------------------------------
  621. def reloadInfo(self):
  622. realPluginName = self.host.get_real_plugin_name(self.fPluginId)
  623. #audioCountInfo = self.host.get_audio_port_count_info(self.fPluginId)
  624. midiCountInfo = self.host.get_midi_port_count_info(self.fPluginId)
  625. #paramCountInfo = self.host.get_parameter_count_info(self.fPluginId)
  626. pluginHints = self.fPluginInfo['hints']
  627. self.ui.le_type.setText(getPluginTypeAsString(self.fPluginInfo['type']))
  628. self.ui.label_name.setEnabled(bool(realPluginName))
  629. self.ui.le_name.setEnabled(bool(realPluginName))
  630. self.ui.le_name.setText(realPluginName)
  631. self.ui.le_name.setToolTip(realPluginName)
  632. self.ui.label_label.setEnabled(bool(self.fPluginInfo['label']))
  633. self.ui.le_label.setEnabled(bool(self.fPluginInfo['label']))
  634. self.ui.le_label.setText(self.fPluginInfo['label'])
  635. self.ui.le_label.setToolTip(self.fPluginInfo['label'])
  636. self.ui.label_maker.setEnabled(bool(self.fPluginInfo['maker']))
  637. self.ui.le_maker.setEnabled(bool(self.fPluginInfo['maker']))
  638. self.ui.le_maker.setText(self.fPluginInfo['maker'])
  639. self.ui.le_maker.setToolTip(self.fPluginInfo['maker'])
  640. self.ui.label_copyright.setEnabled(bool(self.fPluginInfo['copyright']))
  641. self.ui.le_copyright.setEnabled(bool(self.fPluginInfo['copyright']))
  642. self.ui.le_copyright.setText(self.fPluginInfo['copyright'])
  643. self.ui.le_copyright.setToolTip(self.fPluginInfo['copyright'])
  644. self.ui.label_unique_id.setEnabled(bool(self.fPluginInfo['uniqueId']))
  645. self.ui.le_unique_id.setEnabled(bool(self.fPluginInfo['uniqueId']))
  646. self.ui.le_unique_id.setText(str(self.fPluginInfo['uniqueId']))
  647. self.ui.le_unique_id.setToolTip(str(self.fPluginInfo['uniqueId']))
  648. self.ui.label_plugin.setText("\n%s\n" % (self.fPluginInfo['name'] or "(none)"))
  649. self.setWindowTitle(self.fPluginInfo['name'] or "(none)")
  650. self.ui.dial_drywet.setEnabled(pluginHints & PLUGIN_CAN_DRYWET)
  651. self.ui.dial_vol.setEnabled(pluginHints & PLUGIN_CAN_VOLUME)
  652. self.ui.dial_b_left.setEnabled(pluginHints & PLUGIN_CAN_BALANCE)
  653. self.ui.dial_b_right.setEnabled(pluginHints & PLUGIN_CAN_BALANCE)
  654. self.ui.dial_pan.setEnabled(pluginHints & PLUGIN_CAN_PANNING)
  655. optsAvailable = self.fPluginInfo['optionsAvailable']
  656. optsEnabled = self.fPluginInfo['optionsEnabled']
  657. self.ui.ch_use_chunks.setEnabled(optsAvailable & PLUGIN_OPTION_USE_CHUNKS)
  658. self.ui.ch_use_chunks.setChecked(optsEnabled & PLUGIN_OPTION_USE_CHUNKS)
  659. self.ui.ch_fixed_buffer.setEnabled(optsAvailable & PLUGIN_OPTION_FIXED_BUFFERS)
  660. self.ui.ch_fixed_buffer.setChecked(optsEnabled & PLUGIN_OPTION_FIXED_BUFFERS)
  661. self.ui.ch_force_stereo.setEnabled(optsAvailable & PLUGIN_OPTION_FORCE_STEREO)
  662. self.ui.ch_force_stereo.setChecked(optsEnabled & PLUGIN_OPTION_FORCE_STEREO)
  663. self.ui.ch_map_program_changes.setEnabled(optsAvailable & PLUGIN_OPTION_MAP_PROGRAM_CHANGES)
  664. self.ui.ch_map_program_changes.setChecked(optsEnabled & PLUGIN_OPTION_MAP_PROGRAM_CHANGES)
  665. self.ui.ch_send_notes.setEnabled(optsAvailable & PLUGIN_OPTION_SKIP_SENDING_NOTES)
  666. self.ui.ch_send_notes.setChecked((self.ui.ch_send_notes.isEnabled() and
  667. (optsEnabled & PLUGIN_OPTION_SKIP_SENDING_NOTES) == 0x0))
  668. self.ui.ch_send_control_changes.setEnabled(optsAvailable & PLUGIN_OPTION_SEND_CONTROL_CHANGES)
  669. self.ui.ch_send_control_changes.setChecked(optsEnabled & PLUGIN_OPTION_SEND_CONTROL_CHANGES)
  670. self.ui.ch_send_channel_pressure.setEnabled(optsAvailable & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE)
  671. self.ui.ch_send_channel_pressure.setChecked(optsEnabled & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE)
  672. self.ui.ch_send_note_aftertouch.setEnabled(optsAvailable & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH)
  673. self.ui.ch_send_note_aftertouch.setChecked(optsEnabled & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH)
  674. self.ui.ch_send_pitchbend.setEnabled(optsAvailable & PLUGIN_OPTION_SEND_PITCHBEND)
  675. self.ui.ch_send_pitchbend.setChecked(optsEnabled & PLUGIN_OPTION_SEND_PITCHBEND)
  676. self.ui.ch_send_all_sound_off.setEnabled(optsAvailable & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
  677. self.ui.ch_send_all_sound_off.setChecked(optsEnabled & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
  678. canSendPrograms = bool((optsAvailable & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) != 0 and
  679. (optsEnabled & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) == 0)
  680. self.ui.ch_send_program_changes.setEnabled(canSendPrograms)
  681. self.ui.ch_send_program_changes.setChecked(optsEnabled & PLUGIN_OPTION_SEND_PROGRAM_CHANGES)
  682. self.ui.sw_programs.setCurrentIndex(0 if self.fPluginInfo['type'] in (PLUGIN_VST2, PLUGIN_SFZ) else 1)
  683. # Show/hide keyboard
  684. showKeyboard = (self.fPluginInfo['category'] == PLUGIN_CATEGORY_SYNTH or midiCountInfo['ins'] > 0)
  685. self.ui.scrollArea.setEnabled(showKeyboard)
  686. self.ui.scrollArea.setVisible(showKeyboard)
  687. # Force-update parent for new hints
  688. if self.fParent is not None and not self.fFirstInit:
  689. self.fParent.editDialogPluginHintsChanged(self.fPluginId, pluginHints)
  690. def reloadParameters(self):
  691. # Reset
  692. self.fParameterList = []
  693. self.fParametersToUpdate = []
  694. self.fTabIconTimers = []
  695. # Save current tab state
  696. tabIndex = self.ui.tabWidget.currentIndex()
  697. tabWidget = self.ui.tabWidget.currentWidget()
  698. scrollVal = tabWidget.verticalScrollBar().value() if isinstance(tabWidget, QScrollArea) else None
  699. del tabWidget
  700. # Remove all previous parameters
  701. for _ in range(self.ui.tabWidget.count()-1):
  702. self.ui.tabWidget.widget(1).deleteLater()
  703. self.ui.tabWidget.removeTab(1)
  704. parameterCount = self.host.get_parameter_count(self.fPluginId)
  705. # -----------------------------------------------------------------
  706. if parameterCount <= 0:
  707. return
  708. # -----------------------------------------------------------------
  709. paramInputList = []
  710. paramOutputList = []
  711. paramInputWidth = 0
  712. paramOutputWidth = 0
  713. unusedParameters = 0
  714. paramInputListFull = [] # ([params], width)
  715. paramOutputListFull = [] # ([params], width)
  716. for i in range(parameterCount):
  717. if i - unusedParameters == self.host.maxParameters:
  718. break
  719. paramData = self.host.get_parameter_data(self.fPluginId, i)
  720. if paramData['type'] not in (PARAMETER_INPUT, PARAMETER_OUTPUT):
  721. unusedParameters += 1
  722. continue
  723. if (paramData['hints'] & PARAMETER_IS_ENABLED) == 0:
  724. unusedParameters += 1
  725. continue
  726. paramInfo = self.host.get_parameter_info(self.fPluginId, i)
  727. paramRanges = self.host.get_parameter_ranges(self.fPluginId, i)
  728. paramValue = self.host.get_current_parameter_value(self.fPluginId, i)
  729. parameter = {
  730. 'type': paramData['type'],
  731. 'hints': paramData['hints'],
  732. 'name': paramInfo['name'],
  733. 'unit': paramInfo['unit'],
  734. 'scalePoints': [],
  735. 'index': paramData['index'],
  736. 'default': paramRanges['def'],
  737. 'minimum': paramRanges['min'],
  738. 'maximum': paramRanges['max'],
  739. 'step': paramRanges['step'],
  740. 'stepSmall': paramRanges['stepSmall'],
  741. 'stepLarge': paramRanges['stepLarge'],
  742. 'mappedControlIndex': paramData['mappedControlIndex'],
  743. 'mappedMinimum': paramData['mappedMinimum'],
  744. 'mappedMaximum': paramData['mappedMaximum'],
  745. 'midiChannel': paramData['midiChannel']+1,
  746. 'comment': paramInfo['comment'],
  747. 'groupName': paramInfo['groupName'],
  748. 'current': paramValue
  749. }
  750. for j in range(paramInfo['scalePointCount']):
  751. scalePointInfo = self.host.get_parameter_scalepoint_info(self.fPluginId, i, j)
  752. parameter['scalePoints'].append({
  753. 'value': scalePointInfo['value'],
  754. 'label': scalePointInfo['label']
  755. })
  756. #parameter['name'] = parameter['name'][:30] + (parameter['name'][30:] and "...")
  757. # -----------------------------------------------------------------
  758. # Get width values, in packs of 20
  759. if parameter['type'] == PARAMETER_INPUT:
  760. paramInputWidthTMP = fontMetricsHorizontalAdvance(self.fontMetrics(), parameter['name'])
  761. if paramInputWidthTMP > paramInputWidth:
  762. paramInputWidth = paramInputWidthTMP
  763. paramInputList.append(parameter)
  764. else:
  765. paramOutputWidthTMP = fontMetricsHorizontalAdvance(self.fontMetrics(), parameter['name'])
  766. if paramOutputWidthTMP > paramOutputWidth:
  767. paramOutputWidth = paramOutputWidthTMP
  768. paramOutputList.append(parameter)
  769. paramInputListFull.append((paramInputList, paramInputWidth))
  770. paramOutputListFull.append((paramOutputList, paramOutputWidth))
  771. # Create parameter tabs + widgets
  772. self._createParameterWidgets(PARAMETER_INPUT, paramInputListFull, self.tr("Parameters"))
  773. self._createParameterWidgets(PARAMETER_OUTPUT, paramOutputListFull, self.tr("Outputs"))
  774. # Restore tab state
  775. if tabIndex < self.ui.tabWidget.count():
  776. self.ui.tabWidget.setCurrentIndex(tabIndex)
  777. if scrollVal is not None:
  778. self.ui.tabWidget.currentWidget().verticalScrollBar().setValue(scrollVal)
  779. def reloadPrograms(self):
  780. # Programs
  781. self.ui.cb_programs.blockSignals(True)
  782. self.ui.cb_programs.clear()
  783. programCount = self.host.get_program_count(self.fPluginId)
  784. if programCount > 0:
  785. self.ui.cb_programs.setEnabled(True)
  786. self.ui.label_programs.setEnabled(True)
  787. for i in range(programCount):
  788. pName = self.host.get_program_name(self.fPluginId, i)
  789. #pName = pName[:40] + (pName[40:] and "...")
  790. self.ui.cb_programs.addItem(pName)
  791. self.ui.cb_programs.setCurrentIndex(self.host.get_current_program_index(self.fPluginId))
  792. else:
  793. self.ui.cb_programs.setEnabled(False)
  794. self.ui.label_programs.setEnabled(False)
  795. self.ui.cb_programs.blockSignals(False)
  796. # MIDI Programs
  797. self.ui.cb_midi_programs.blockSignals(True)
  798. self.ui.cb_midi_programs.clear()
  799. midiProgramCount = self.host.get_midi_program_count(self.fPluginId)
  800. if midiProgramCount > 0:
  801. self.ui.cb_midi_programs.setEnabled(True)
  802. self.ui.label_midi_programs.setEnabled(True)
  803. for i in range(midiProgramCount):
  804. mpData = self.host.get_midi_program_data(self.fPluginId, i)
  805. mpBank = mpData['bank']
  806. mpProg = mpData['program']
  807. mpName = mpData['name']
  808. #mpName = mpName[:40] + (mpName[40:] and "...")
  809. self.ui.cb_midi_programs.addItem("%03i:%03i - %s" % (mpBank+1, mpProg+1, mpName))
  810. self.ui.cb_midi_programs.setCurrentIndex(self.host.get_current_midi_program_index(self.fPluginId))
  811. else:
  812. self.ui.cb_midi_programs.setEnabled(False)
  813. self.ui.label_midi_programs.setEnabled(False)
  814. self.ui.cb_midi_programs.blockSignals(False)
  815. self.ui.sw_programs.setEnabled(programCount > 0 or midiProgramCount > 0)
  816. if self.fPluginInfo['type'] == PLUGIN_LV2:
  817. self.ui.b_load_state.setEnabled(programCount > 0)
  818. #------------------------------------------------------------------
  819. def clearNotes(self):
  820. self.fPlayingNotes = []
  821. self.ui.keyboard.allNotesOff()
  822. def noteOn(self, channel, note, velocity):
  823. if self.fControlChannel == channel:
  824. self.ui.keyboard.sendNoteOn(note, False)
  825. def noteOff(self, channel, note):
  826. if self.fControlChannel == channel:
  827. self.ui.keyboard.sendNoteOff(note, False)
  828. #------------------------------------------------------------------
  829. def getHints(self):
  830. return self.fPluginInfo['hints']
  831. def setPluginId(self, idx):
  832. self.fPluginId = idx
  833. def setName(self, name):
  834. self.fPluginInfo['name'] = name
  835. self.ui.label_plugin.setText("\n%s\n" % name)
  836. self.setWindowTitle(name)
  837. #------------------------------------------------------------------
  838. def setParameterValue(self, parameterId, value):
  839. for paramItem in self.fParametersToUpdate:
  840. if paramItem[0] == parameterId:
  841. paramItem[1] = value
  842. break
  843. else:
  844. self.fParametersToUpdate.append([parameterId, value])
  845. def setParameterDefault(self, parameterId, value):
  846. for _, paramId, paramWidget in self.fParameterList:
  847. if paramId == parameterId:
  848. paramWidget.setDefault(value)
  849. break
  850. def setParameterMappedControlIndex(self, parameterId, control):
  851. for _, paramId, paramWidget in self.fParameterList:
  852. if paramId == parameterId:
  853. paramWidget.setMappedControlIndex(control)
  854. break
  855. def setParameterMappedRange(self, parameterId, minimum, maximum):
  856. for _, paramId, paramWidget in self.fParameterList:
  857. if paramId == parameterId:
  858. paramWidget.setMappedRange(minimum, maximum)
  859. break
  860. def setParameterMidiChannel(self, parameterId, channel):
  861. for _, paramId, paramWidget in self.fParameterList:
  862. if paramId == parameterId:
  863. paramWidget.setMidiChannel(channel+1)
  864. break
  865. def setProgram(self, index):
  866. self.ui.cb_programs.blockSignals(True)
  867. self.ui.cb_programs.setCurrentIndex(index)
  868. self.ui.cb_programs.blockSignals(False)
  869. self._updateParameterValues()
  870. def setMidiProgram(self, index):
  871. self.ui.cb_midi_programs.blockSignals(True)
  872. self.ui.cb_midi_programs.setCurrentIndex(index)
  873. self.ui.cb_midi_programs.blockSignals(False)
  874. self._updateParameterValues()
  875. def setOption(self, option, yesNo):
  876. if option == PLUGIN_OPTION_USE_CHUNKS:
  877. widget = self.ui.ch_use_chunks
  878. elif option == PLUGIN_OPTION_FIXED_BUFFERS:
  879. widget = self.ui.ch_fixed_buffer
  880. elif option == PLUGIN_OPTION_FORCE_STEREO:
  881. widget = self.ui.ch_force_stereo
  882. elif option == PLUGIN_OPTION_MAP_PROGRAM_CHANGES:
  883. widget = self.ui.ch_map_program_changes
  884. elif option == PLUGIN_OPTION_SKIP_SENDING_NOTES:
  885. widget = self.ui.ch_send_notes
  886. yesNo = not yesNo
  887. elif option == PLUGIN_OPTION_SEND_PROGRAM_CHANGES:
  888. widget = self.ui.ch_send_program_changes
  889. elif option == PLUGIN_OPTION_SEND_CONTROL_CHANGES:
  890. widget = self.ui.ch_send_control_changes
  891. elif option == PLUGIN_OPTION_SEND_CHANNEL_PRESSURE:
  892. widget = self.ui.ch_send_channel_pressure
  893. elif option == PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH:
  894. widget = self.ui.ch_send_note_aftertouch
  895. elif option == PLUGIN_OPTION_SEND_PITCHBEND:
  896. widget = self.ui.ch_send_pitchbend
  897. elif option == PLUGIN_OPTION_SEND_ALL_SOUND_OFF:
  898. widget = self.ui.ch_send_all_sound_off
  899. else:
  900. return
  901. widget.blockSignals(True)
  902. widget.setChecked(yesNo)
  903. widget.blockSignals(False)
  904. #------------------------------------------------------------------
  905. def setVisible(self, yesNo):
  906. if yesNo:
  907. if not self.fGeometry.isNull():
  908. self.restoreGeometry(self.fGeometry)
  909. else:
  910. self.fGeometry = self.saveGeometry()
  911. QDialog.setVisible(self, yesNo)
  912. if CARLA_OS_MAC and yesNo:
  913. parent = self.parent()
  914. if parent is None:
  915. return
  916. gCarla.utils.cocoa_set_transient_window_for(self.windowHandle().winId(), parent.windowHandle().winId())
  917. #------------------------------------------------------------------
  918. def idleSlow(self):
  919. # Check Tab icons
  920. for i in range(len(self.fTabIconTimers)):
  921. if self.fTabIconTimers[i] == ICON_STATE_ON:
  922. self.fTabIconTimers[i] = ICON_STATE_WAIT
  923. elif self.fTabIconTimers[i] == ICON_STATE_WAIT:
  924. self.fTabIconTimers[i] = ICON_STATE_OFF
  925. elif self.fTabIconTimers[i] == ICON_STATE_OFF:
  926. self.fTabIconTimers[i] = ICON_STATE_NULL
  927. self.ui.tabWidget.setTabIcon(i+1, self.fTabIconOff)
  928. # Check parameters needing update
  929. for index, value in self.fParametersToUpdate:
  930. if index == PARAMETER_DRYWET:
  931. self.ui.dial_drywet.blockSignals(True)
  932. self.ui.dial_drywet.setValue(value)
  933. self.ui.dial_drywet.blockSignals(False)
  934. elif index == PARAMETER_VOLUME:
  935. self.ui.dial_vol.blockSignals(True)
  936. self.ui.dial_vol.setValue(value)
  937. self.ui.dial_vol.blockSignals(False)
  938. elif index == PARAMETER_BALANCE_LEFT:
  939. self.ui.dial_b_left.blockSignals(True)
  940. self.ui.dial_b_left.setValue(value)
  941. self.ui.dial_b_left.blockSignals(False)
  942. elif index == PARAMETER_BALANCE_RIGHT:
  943. self.ui.dial_b_right.blockSignals(True)
  944. self.ui.dial_b_right.setValue(value)
  945. self.ui.dial_b_right.blockSignals(False)
  946. elif index == PARAMETER_PANNING:
  947. self.ui.dial_pan.blockSignals(True)
  948. self.ui.dial_pan.setValue(value)
  949. self.ui.dial_pan.blockSignals(False)
  950. elif index == PARAMETER_CTRL_CHANNEL:
  951. self.fControlChannel = round(value)
  952. self.ui.sb_ctrl_channel.blockSignals(True)
  953. self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1)
  954. self.ui.sb_ctrl_channel.blockSignals(False)
  955. self.ui.keyboard.allNotesOff()
  956. self._updateCtrlPrograms()
  957. elif index >= 0:
  958. for paramType, paramId, paramWidget in self.fParameterList:
  959. if paramId != index:
  960. continue
  961. # FIXME see below
  962. if paramType != PARAMETER_INPUT:
  963. continue
  964. paramWidget.blockSignals(True)
  965. paramWidget.setValue(value)
  966. paramWidget.blockSignals(False)
  967. #if paramType == PARAMETER_INPUT:
  968. tabIndex = paramWidget.getTabIndex()
  969. if self.fTabIconTimers[tabIndex-1] == ICON_STATE_NULL:
  970. self.ui.tabWidget.setTabIcon(tabIndex, self.fTabIconOn)
  971. self.fTabIconTimers[tabIndex-1] = ICON_STATE_ON
  972. break
  973. # Clear all parameters
  974. self.fParametersToUpdate = []
  975. # Update parameter outputs | FIXME needed?
  976. for paramType, paramId, paramWidget in self.fParameterList:
  977. if paramType != PARAMETER_OUTPUT:
  978. continue
  979. paramWidget.blockSignals(True)
  980. paramWidget.setValue(self.host.get_current_parameter_value(self.fPluginId, paramId))
  981. paramWidget.blockSignals(False)
  982. #------------------------------------------------------------------
  983. @pyqtSlot()
  984. def slot_stateSave(self):
  985. if self.fPluginInfo['type'] == PLUGIN_LV2:
  986. # TODO
  987. return
  988. if self.fCurrentStateFilename:
  989. askTry = QMessageBox.question(self,
  990. self.tr("Overwrite?"),
  991. self.tr("Overwrite previously created file?"),
  992. QMessageBox.Ok|QMessageBox.Cancel)
  993. if askTry == QMessageBox.Ok:
  994. self.host.save_plugin_state(self.fPluginId, self.fCurrentStateFilename)
  995. return
  996. self.fCurrentStateFilename = None
  997. fileFilter = self.tr("Carla State File (*.carxs)")
  998. filename, _ = QFileDialog.getSaveFileName(self, self.tr("Save Plugin State File"), filter=fileFilter)
  999. # FIXME use ok value, test if it works as expected
  1000. if not filename:
  1001. return
  1002. if not filename.lower().endswith(".carxs"):
  1003. filename += ".carxs"
  1004. self.fCurrentStateFilename = filename
  1005. self.host.save_plugin_state(self.fPluginId, self.fCurrentStateFilename)
  1006. @pyqtSlot()
  1007. def slot_stateLoad(self):
  1008. if self.fPluginInfo['type'] == PLUGIN_LV2:
  1009. presetList = []
  1010. for i in range(self.host.get_program_count(self.fPluginId)):
  1011. presetList.append("%03i - %s" % (i+1, self.host.get_program_name(self.fPluginId, i)))
  1012. ret = QInputDialog.getItem(self,
  1013. self.tr("Open LV2 Preset"),
  1014. self.tr("Select an LV2 Preset:"),
  1015. presetList, 0, False)
  1016. if ret[1]:
  1017. index = int(ret[0].split(" - ", 1)[0])-1
  1018. self.host.set_program(self.fPluginId, index)
  1019. self.setMidiProgram(-1)
  1020. return
  1021. fileFilter = self.tr("Carla State File (*.carxs)")
  1022. filename, _ = QFileDialog.getOpenFileName(self, self.tr("Open Plugin State File"), filter=fileFilter)
  1023. # FIXME use ok value, test if it works as expected
  1024. if not filename:
  1025. return
  1026. self.fCurrentStateFilename = filename
  1027. self.host.load_plugin_state(self.fPluginId, self.fCurrentStateFilename)
  1028. #------------------------------------------------------------------
  1029. @pyqtSlot(bool)
  1030. def slot_optionChanged(self, clicked):
  1031. sender = self.sender()
  1032. if sender == self.ui.ch_use_chunks:
  1033. option = PLUGIN_OPTION_USE_CHUNKS
  1034. elif sender == self.ui.ch_fixed_buffer:
  1035. option = PLUGIN_OPTION_FIXED_BUFFERS
  1036. elif sender == self.ui.ch_force_stereo:
  1037. option = PLUGIN_OPTION_FORCE_STEREO
  1038. elif sender == self.ui.ch_map_program_changes:
  1039. option = PLUGIN_OPTION_MAP_PROGRAM_CHANGES
  1040. elif sender == self.ui.ch_send_notes:
  1041. option = PLUGIN_OPTION_SKIP_SENDING_NOTES
  1042. clicked = not clicked
  1043. elif sender == self.ui.ch_send_program_changes:
  1044. option = PLUGIN_OPTION_SEND_PROGRAM_CHANGES
  1045. elif sender == self.ui.ch_send_control_changes:
  1046. option = PLUGIN_OPTION_SEND_CONTROL_CHANGES
  1047. elif sender == self.ui.ch_send_channel_pressure:
  1048. option = PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
  1049. elif sender == self.ui.ch_send_note_aftertouch:
  1050. option = PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
  1051. elif sender == self.ui.ch_send_pitchbend:
  1052. option = PLUGIN_OPTION_SEND_PITCHBEND
  1053. elif sender == self.ui.ch_send_all_sound_off:
  1054. option = PLUGIN_OPTION_SEND_ALL_SOUND_OFF
  1055. else:
  1056. return
  1057. #--------------------------------------------------------------
  1058. # handle map-program-changes and send-program-changes conflict
  1059. if option == PLUGIN_OPTION_MAP_PROGRAM_CHANGES and clicked:
  1060. self.ui.ch_send_program_changes.setEnabled(False)
  1061. # disable send-program-changes if needed
  1062. if self.ui.ch_send_program_changes.isChecked():
  1063. self.host.set_option(self.fPluginId, PLUGIN_OPTION_SEND_PROGRAM_CHANGES, False)
  1064. #--------------------------------------------------------------
  1065. # set option
  1066. self.host.set_option(self.fPluginId, option, clicked)
  1067. #--------------------------------------------------------------
  1068. # handle map-program-changes and send-program-changes conflict
  1069. if option == PLUGIN_OPTION_MAP_PROGRAM_CHANGES and not clicked:
  1070. self.ui.ch_send_program_changes.setEnabled(
  1071. self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_PROGRAM_CHANGES)
  1072. # restore send-program-changes if needed
  1073. if self.ui.ch_send_program_changes.isChecked():
  1074. self.host.set_option(self.fPluginId, PLUGIN_OPTION_SEND_PROGRAM_CHANGES, True)
  1075. #------------------------------------------------------------------
  1076. @pyqtSlot(float)
  1077. def slot_dryWetChanged(self, value):
  1078. self.host.set_drywet(self.fPluginId, value)
  1079. if self.fParent is not None:
  1080. self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_DRYWET, value)
  1081. @pyqtSlot(float)
  1082. def slot_volumeChanged(self, value):
  1083. self.host.set_volume(self.fPluginId, value)
  1084. if self.fParent is not None:
  1085. self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_VOLUME, value)
  1086. @pyqtSlot(float)
  1087. def slot_balanceLeftChanged(self, value):
  1088. self.host.set_balance_left(self.fPluginId, value)
  1089. if self.fParent is not None:
  1090. self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_BALANCE_LEFT, value)
  1091. @pyqtSlot(float)
  1092. def slot_balanceRightChanged(self, value):
  1093. self.host.set_balance_right(self.fPluginId, value)
  1094. if self.fParent is not None:
  1095. self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_BALANCE_RIGHT, value)
  1096. @pyqtSlot(float)
  1097. def slot_panChanged(self, value):
  1098. self.host.set_panning(self.fPluginId, value)
  1099. if self.fParent is not None:
  1100. self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_PANNING, value)
  1101. @pyqtSlot(int)
  1102. def slot_ctrlChannelChanged(self, value):
  1103. self.fControlChannel = value-1
  1104. self.host.set_ctrl_channel(self.fPluginId, self.fControlChannel)
  1105. self.ui.keyboard.allNotesOff()
  1106. self._updateCtrlPrograms()
  1107. #------------------------------------------------------------------
  1108. @pyqtSlot(int, float)
  1109. def slot_parameterValueChanged(self, parameterId, value):
  1110. self.host.set_parameter_value(self.fPluginId, parameterId, value)
  1111. if self.fParent is not None:
  1112. self.fParent.editDialogParameterValueChanged(self.fPluginId, parameterId, value)
  1113. @pyqtSlot(int, int)
  1114. def slot_parameterMappedControlChanged(self, parameterId, control):
  1115. self.host.set_parameter_mapped_control_index(self.fPluginId, parameterId, control)
  1116. @pyqtSlot(int, float, float)
  1117. def slot_parameterMappedRangeChanged(self, parameterId, minimum, maximum):
  1118. self.host.set_parameter_mapped_range(self.fPluginId, parameterId, minimum, maximum)
  1119. @pyqtSlot(int, int)
  1120. def slot_parameterMidiChannelChanged(self, parameterId, channel):
  1121. self.host.set_parameter_midi_channel(self.fPluginId, parameterId, channel-1)
  1122. #------------------------------------------------------------------
  1123. @pyqtSlot(int)
  1124. def slot_programIndexChanged(self, index):
  1125. self.host.set_program(self.fPluginId, index)
  1126. if self.fParent is not None:
  1127. self.fParent.editDialogProgramChanged(self.fPluginId, index)
  1128. self._updateParameterValues()
  1129. @pyqtSlot(int)
  1130. def slot_midiProgramIndexChanged(self, index):
  1131. self.host.set_midi_program(self.fPluginId, index)
  1132. if self.fParent is not None:
  1133. self.fParent.editDialogMidiProgramChanged(self.fPluginId, index)
  1134. self._updateParameterValues()
  1135. #------------------------------------------------------------------
  1136. @pyqtSlot(int)
  1137. def slot_noteOn(self, note):
  1138. if self.fControlChannel >= 0:
  1139. self.host.send_midi_note(self.fPluginId, self.fControlChannel, note, 100)
  1140. if self.fParent is not None:
  1141. self.fParent.editDialogNotePressed(self.fPluginId, note)
  1142. @pyqtSlot(int)
  1143. def slot_noteOff(self, note):
  1144. if self.fControlChannel >= 0:
  1145. self.host.send_midi_note(self.fPluginId, self.fControlChannel, note, 0)
  1146. if self.fParent is not None:
  1147. self.fParent.editDialogNoteReleased(self.fPluginId, note)
  1148. #------------------------------------------------------------------
  1149. @pyqtSlot()
  1150. def slot_finished(self):
  1151. if self.fParent is not None:
  1152. self.fParent.editDialogVisibilityChanged(self.fPluginId, False)
  1153. #------------------------------------------------------------------
  1154. @pyqtSlot()
  1155. def slot_knobCustomMenu(self):
  1156. sender = self.sender()
  1157. knobName = sender.objectName()
  1158. if knobName == "dial_drywet":
  1159. minimum = 0.0
  1160. maximum = 1.0
  1161. default = 1.0
  1162. label = "Dry/Wet"
  1163. elif knobName == "dial_vol":
  1164. minimum = 0.0
  1165. maximum = 1.27
  1166. default = 1.0
  1167. label = "Volume"
  1168. elif knobName == "dial_b_left":
  1169. minimum = -1.0
  1170. maximum = 1.0
  1171. default = -1.0
  1172. label = "Balance-Left"
  1173. elif knobName == "dial_b_right":
  1174. minimum = -1.0
  1175. maximum = 1.0
  1176. default = 1.0
  1177. label = "Balance-Right"
  1178. elif knobName == "dial_pan":
  1179. minimum = -1.0
  1180. maximum = 1.0
  1181. default = 0.0
  1182. label = "Panning"
  1183. else:
  1184. minimum = 0.0
  1185. maximum = 1.0
  1186. default = 0.5
  1187. label = "Unknown"
  1188. menu = QMenu(self)
  1189. actReset = menu.addAction(self.tr("Reset (%i%%)" % (default*100)))
  1190. menu.addSeparator()
  1191. actMinimum = menu.addAction(self.tr("Set to Minimum (%i%%)" % (minimum*100)))
  1192. actCenter = menu.addAction(self.tr("Set to Center"))
  1193. actMaximum = menu.addAction(self.tr("Set to Maximum (%i%%)" % (maximum*100)))
  1194. menu.addSeparator()
  1195. actSet = menu.addAction(self.tr("Set value..."))
  1196. if label not in ("Balance-Left", "Balance-Right", "Panning"):
  1197. menu.removeAction(actCenter)
  1198. actSelected = menu.exec_(QCursor.pos())
  1199. if actSelected == actSet:
  1200. current = minimum + (maximum-minimum)*(float(sender.value())/10000)
  1201. value, ok = QInputDialog.getInt(self,
  1202. self.tr("Set value"),
  1203. label,
  1204. round(current*100.0),
  1205. round(minimum*100.0),
  1206. round(maximum*100.0),
  1207. 1)
  1208. if ok:
  1209. value = float(value)/100.0
  1210. if not ok:
  1211. return
  1212. elif actSelected == actMinimum:
  1213. value = minimum
  1214. elif actSelected == actMaximum:
  1215. value = maximum
  1216. elif actSelected == actReset:
  1217. value = default
  1218. elif actSelected == actCenter:
  1219. value = 0.0
  1220. else:
  1221. return
  1222. sender.setValue(value, True)
  1223. #------------------------------------------------------------------
  1224. @pyqtSlot()
  1225. def slot_channelCustomMenu(self):
  1226. menu = QMenu(self)
  1227. actNone = menu.addAction(self.tr("None"))
  1228. if self.fControlChannel+1 == 0:
  1229. actNone.setCheckable(True)
  1230. actNone.setChecked(True)
  1231. for i in range(1, 16+1):
  1232. action = menu.addAction("%i" % i)
  1233. if self.fControlChannel+1 == i:
  1234. action.setCheckable(True)
  1235. action.setChecked(True)
  1236. actSel = menu.exec_(QCursor.pos())
  1237. if not actSel:
  1238. pass
  1239. elif actSel == actNone:
  1240. self.ui.sb_ctrl_channel.setValue(0)
  1241. elif actSel:
  1242. selChannel = int(actSel.text())
  1243. self.ui.sb_ctrl_channel.setValue(selChannel)
  1244. #------------------------------------------------------------------
  1245. def _createParameterWidgets(self, paramType, paramListFull, tabPageName):
  1246. groupWidgets = {}
  1247. for paramList, width in paramListFull:
  1248. if not paramList:
  1249. break
  1250. tabIndex = self.ui.tabWidget.count()
  1251. scrollArea = QScrollArea(self.ui.tabWidget)
  1252. scrollArea.setWidgetResizable(True)
  1253. scrollArea.setFrameStyle(0)
  1254. palette1 = scrollArea.palette()
  1255. palette1.setColor(QPalette.Background, Qt.transparent)
  1256. scrollArea.setPalette(palette1)
  1257. palette2 = scrollArea.palette()
  1258. palette2.setColor(QPalette.Background, palette2.color(QPalette.Button))
  1259. scrollAreaWidget = QWidget(scrollArea)
  1260. scrollAreaLayout = QVBoxLayout(scrollAreaWidget)
  1261. scrollAreaLayout.setSpacing(3)
  1262. for paramInfo in paramList:
  1263. groupName = paramInfo['groupName']
  1264. if groupName:
  1265. groupSymbol, groupName = groupName.split(":",1)
  1266. groupLayout, groupWidget = groupWidgets.get(groupSymbol, (None, None))
  1267. if groupLayout is None:
  1268. groupWidget = CollapsibleBox(groupName, scrollAreaWidget)
  1269. groupLayout = groupWidget.getContentLayout()
  1270. groupWidget.setPalette(palette2)
  1271. scrollAreaLayout.addWidget(groupWidget)
  1272. groupWidgets[groupSymbol] = (groupLayout, groupWidget)
  1273. else:
  1274. groupLayout = scrollAreaLayout
  1275. groupWidget = scrollAreaWidget
  1276. paramWidget = PluginParameter(groupWidget, self.host, paramInfo, self.fPluginId, tabIndex)
  1277. paramWidget.setLabelWidth(width)
  1278. groupLayout.addWidget(paramWidget)
  1279. self.fParameterList.append((paramType, paramInfo['index'], paramWidget))
  1280. if paramType == PARAMETER_INPUT:
  1281. paramWidget.valueChanged.connect(self.slot_parameterValueChanged)
  1282. paramWidget.mappedControlChanged.connect(self.slot_parameterMappedControlChanged)
  1283. paramWidget.mappedRangeChanged.connect(self.slot_parameterMappedRangeChanged)
  1284. paramWidget.midiChannelChanged.connect(self.slot_parameterMidiChannelChanged)
  1285. scrollAreaLayout.addStretch()
  1286. scrollArea.setWidget(scrollAreaWidget)
  1287. self.ui.tabWidget.addTab(scrollArea, tabPageName)
  1288. if paramType == PARAMETER_INPUT:
  1289. self.ui.tabWidget.setTabIcon(tabIndex, self.fTabIconOff)
  1290. self.fTabIconTimers.append(ICON_STATE_NULL)
  1291. def _updateCtrlPrograms(self):
  1292. self.ui.keyboard.setEnabled(self.fControlChannel >= 0)
  1293. if self.fPluginInfo['category'] != PLUGIN_CATEGORY_SYNTH:
  1294. return
  1295. if self.fPluginInfo['type'] not in (PLUGIN_INTERNAL, PLUGIN_SF2):
  1296. return
  1297. if self.fControlChannel < 0:
  1298. self.ui.cb_programs.setEnabled(False)
  1299. self.ui.cb_midi_programs.setEnabled(False)
  1300. return
  1301. self.ui.cb_programs.setEnabled(True)
  1302. self.ui.cb_midi_programs.setEnabled(True)
  1303. pIndex = self.host.get_current_program_index(self.fPluginId)
  1304. if self.ui.cb_programs.currentIndex() != pIndex:
  1305. self.setProgram(pIndex)
  1306. mpIndex = self.host.get_current_midi_program_index(self.fPluginId)
  1307. if self.ui.cb_midi_programs.currentIndex() != mpIndex:
  1308. self.setMidiProgram(mpIndex)
  1309. def _updateParameterValues(self):
  1310. for _, paramId, paramWidget in self.fParameterList:
  1311. paramWidget.blockSignals(True)
  1312. paramWidget.setValue(self.host.get_current_parameter_value(self.fPluginId, paramId))
  1313. paramWidget.blockSignals(False)
  1314. #------------------------------------------------------------------
  1315. def testTimer(self):
  1316. self.fIdleTimerId = self.startTimer(50)
  1317. self.SIGTERM.connect(self.testTimerClose)
  1318. gCarla.gui = self
  1319. setUpSignals()
  1320. def testTimerClose(self):
  1321. self.close()
  1322. _app.quit()
  1323. #------------------------------------------------------------------
  1324. def closeEvent(self, event):
  1325. if self.fIdleTimerId != 0:
  1326. self.killTimer(self.fIdleTimerId)
  1327. self.fIdleTimerId = 0
  1328. self.host.engine_close()
  1329. QDialog.closeEvent(self, event)
  1330. def timerEvent(self, event):
  1331. if event.timerId() == self.fIdleTimerId:
  1332. self.host.engine_idle()
  1333. self.idleSlow()
  1334. QDialog.timerEvent(self, event)
  1335. # ------------------------------------------------------------------------------------------------------------
  1336. # Main
  1337. if __name__ == '__main__':
  1338. from carla_app import CarlaApplication
  1339. from carla_host import initHost as _initHost, loadHostSettings as _loadHostSettings
  1340. _app = CarlaApplication()
  1341. _host = _initHost("Widgets", None, False, False, False)
  1342. _loadHostSettings(_host)
  1343. _host.engine_init("JACK", "Carla-Widgets")
  1344. _host.add_plugin(BINARY_NATIVE, PLUGIN_DSSI, "/usr/lib/dssi/karplong.so", "karplong", "karplong", 0, None, 0x0)
  1345. _host.set_active(0, True)
  1346. gui2 = PluginEdit(None, _host, 0)
  1347. gui2.testTimer()
  1348. gui2.show()
  1349. _app.exit_exec()