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.

1744 lines
70KB

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