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.

1697 lines
67KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla widgets code
  4. # Copyright (C) 2011-2013 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 PyQt4.QtCore import pyqtSignal, pyqtSlot, QByteArray, QSettings
  20. from PyQt4.QtGui import QColor, QCursor, QFontMetrics, QPainter, QPainterPath
  21. from PyQt4.QtGui import QDialog, QFrame, QInputDialog, QLineEdit, QMenu, QVBoxLayout, QWidget
  22. # ------------------------------------------------------------------------------------------------------------
  23. # Imports (Custom)
  24. import ui_carla_about
  25. import ui_carla_edit
  26. import ui_carla_parameter
  27. import ui_carla_plugin
  28. from carla_shared import *
  29. # ------------------------------------------------------------------------------------------------------------
  30. # Carla GUI defines
  31. ICON_STATE_NULL = 0
  32. ICON_STATE_OFF = 1
  33. ICON_STATE_WAIT = 2
  34. ICON_STATE_ON = 3
  35. # ------------------------------------------------------------------------------------------------------------
  36. # Fake plugin info for easy testing
  37. gFakePluginInfo = {
  38. "type": PLUGIN_NONE,
  39. "category": PLUGIN_CATEGORY_SYNTH,
  40. "hints": PLUGIN_IS_SYNTH|PLUGIN_CAN_DRYWET|PLUGIN_CAN_VOLUME|PLUGIN_CAN_PANNING,
  41. "optionsAvailable": 0x1FF, # all
  42. "optionsEnabled": 0x1FF, # all
  43. "filename": "AwesoomeFilename.what",
  44. "name": "Awesoome Name",
  45. "label": "awesoomeLabel",
  46. "maker": "Awesoome Maker",
  47. "copyright": "Awesoome Copyright",
  48. "iconName": "plugin",
  49. "uniqueId": 0
  50. }
  51. gFakeParamInfo = {
  52. "hints": PARAMETER_IS_ENABLED|PARAMETER_IS_AUTOMABLE,
  53. "name": "Parameter Name",
  54. "unit": "",
  55. "scalePoints": [],
  56. "index": 0,
  57. "default": 0.0,
  58. "minimum": 0.0,
  59. "maximum": 1.0,
  60. "step": 0.01,
  61. "stepSmall": 0.01,
  62. "stepLarge": 0.01,
  63. "midiCC": -1,
  64. "midiChannel": 1,
  65. "current": 0.0
  66. }
  67. gFakePortCountInfo = {
  68. "ins": 0,
  69. "outs": 0
  70. }
  71. # ------------------------------------------------------------------------------------------------------------
  72. # Carla About dialog
  73. class CarlaAboutW(QDialog):
  74. def __init__(self, parent):
  75. QDialog.__init__(self, parent)
  76. self.ui = ui_carla_about.Ui_CarlaAboutW()
  77. self.ui.setupUi(self)
  78. if Carla.isControl:
  79. extraInfo = " - <b>%s</b>" % self.tr("OSC Bridge Version")
  80. elif Carla.isPlugin:
  81. extraInfo = " - <b>%s</b>" % self.tr("Plugin Version")
  82. else:
  83. extraInfo = ""
  84. self.ui.l_about.setText(self.tr(""
  85. "<br>Version %s"
  86. "<br>Carla is a Multi-Plugin Host for JACK%s.<br>"
  87. "<br>Copyright (C) 2011-2013 falkTX<br>"
  88. "" % (VERSION, extraInfo)))
  89. if Carla.isControl or Carla.isPlugin or Carla.host is None:
  90. self.ui.l_extended.hide()
  91. self.ui.tabWidget.removeTab(1)
  92. self.ui.tabWidget.removeTab(1)
  93. self.adjustSize()
  94. else:
  95. self.ui.l_extended.setText(Carla.host.get_complete_license_text())
  96. if Carla.host.is_engine_running():
  97. self.ui.le_osc_url_tcp.setText(Carla.host.get_host_osc_url_tcp())
  98. self.ui.le_osc_url_udp.setText(Carla.host.get_host_osc_url_udp())
  99. else:
  100. self.ui.le_osc_url_tcp.setText(self.tr("(Engine not running)"))
  101. self.ui.le_osc_url_udp.setText(self.tr("(Engine not running)"))
  102. self.ui.l_osc_cmds.setText(""
  103. " /set_active <i-value>\n"
  104. " /set_drywet <f-value>\n"
  105. " /set_volume <f-value>\n"
  106. " /set_balance_left <f-value>\n"
  107. " /set_balance_right <f-value>\n"
  108. " /set_panning <f-value>\n"
  109. " /set_parameter_value <i-index> <f-value>\n"
  110. " /set_parameter_midi_cc <i-index> <i-cc>\n"
  111. " /set_parameter_midi_channel <i-index> <i-channel>\n"
  112. " /set_program <i-index>\n"
  113. " /set_midi_program <i-index>\n"
  114. " /note_on <i-note> <i-velo>\n"
  115. " /note_off <i-note>\n"
  116. )
  117. self.ui.l_example.setText("/Carla/2/set_parameter_value 5 1.0")
  118. self.ui.l_example_help.setText("<i>(as in this example, \"2\" is the plugin number and \"5\" the parameter)</i>")
  119. self.ui.l_ladspa.setText(self.tr("Everything! (Including LRDF)"))
  120. self.ui.l_dssi.setText(self.tr("Everything! (Including CustomData/Chunks)"))
  121. self.ui.l_lv2.setText(self.tr("About 80&#37; complete (using custom extensions)<br/>"
  122. "Implemented Feature/Extensions:"
  123. "<ul>"
  124. "<li>http://lv2plug.in/ns/ext/atom</li>"
  125. "<li>http://lv2plug.in/ns/ext/buf-size</li>"
  126. "<li>http://lv2plug.in/ns/ext/data-access</li>"
  127. #"<li>http://lv2plug.in/ns/ext/dynmanifest</li>"
  128. "<li>http://lv2plug.in/ns/ext/event</li>"
  129. "<li>http://lv2plug.in/ns/ext/instance-access</li>"
  130. "<li>http://lv2plug.in/ns/ext/log</li>"
  131. "<li>http://lv2plug.in/ns/ext/midi</li>"
  132. #"<li>http://lv2plug.in/ns/ext/morph</li>"
  133. "<li>http://lv2plug.in/ns/ext/options</li>"
  134. "<li>http://lv2plug.in/ns/ext/parameters</li>"
  135. #"<li>http://lv2plug.in/ns/ext/patch</li>"
  136. #"<li>http://lv2plug.in/ns/ext/port-groups</li>"
  137. #"<li>http://lv2plug.in/ns/ext/port-props</li>"
  138. "<li>http://lv2plug.in/ns/ext/presets</li>"
  139. #"<li>http://lv2plug.in/ns/ext/resize-port</li>"
  140. "<li>http://lv2plug.in/ns/ext/state</li>"
  141. "<li>http://lv2plug.in/ns/ext/time</li>"
  142. "<li>http://lv2plug.in/ns/ext/uri-map</li>"
  143. "<li>http://lv2plug.in/ns/ext/urid</li>"
  144. #"<li>http://lv2plug.in/ns/ext/worker</li>"
  145. "<li>http://lv2plug.in/ns/extensions/ui</li>"
  146. "<li>http://lv2plug.in/ns/extensions/units</li>"
  147. "<li>http://kxstudio.sf.net/ns/lv2ext/external-ui</li>"
  148. "<li>http://kxstudio.sf.net/ns/lv2ext/programs</li>"
  149. "<li>http://kxstudio.sf.net/ns/lv2ext/rtmempool</li>"
  150. "<li>http://ll-plugins.nongnu.org/lv2/ext/midimap</li>"
  151. "<li>http://ll-plugins.nongnu.org/lv2/ext/miditype</li>"
  152. "</ul>"))
  153. self.ui.l_vst.setText(self.tr("<p>About 85&#37; complete (missing vst bank/presets and some minor stuff)</p>"))
  154. def done(self, r):
  155. QDialog.done(self, r)
  156. self.close()
  157. # ------------------------------------------------------------------------------------------------------------
  158. # Plugin Parameter
  159. class PluginParameter(QWidget):
  160. midiControlChanged = pyqtSignal(int, int)
  161. midiChannelChanged = pyqtSignal(int, int)
  162. valueChanged = pyqtSignal(int, float)
  163. def __init__(self, parent, pInfo, pluginId, tabIndex):
  164. QWidget.__init__(self, parent)
  165. self.ui = ui_carla_parameter.Ui_PluginParameter()
  166. self.ui.setupUi(self)
  167. # -------------------------------------------------------------
  168. # Internal stuff
  169. self.fMidiControl = -1
  170. self.fMidiChannel = 1
  171. self.fParameterId = pInfo['index']
  172. self.fPluginId = pluginId
  173. self.fTabIndex = tabIndex
  174. # -------------------------------------------------------------
  175. # Set-up GUI
  176. pHints = pInfo['hints']
  177. self.ui.label.setText(pInfo['name'])
  178. self.ui.widget.setName(pInfo['name'])
  179. self.ui.widget.setMinimum(pInfo['minimum'])
  180. self.ui.widget.setMaximum(pInfo['maximum'])
  181. self.ui.widget.setDefault(pInfo['default'])
  182. self.ui.widget.setValue(pInfo['current'], False)
  183. self.ui.widget.setLabel(pInfo['unit'])
  184. self.ui.widget.setStep(pInfo['step'])
  185. self.ui.widget.setStepSmall(pInfo['stepSmall'])
  186. self.ui.widget.setStepLarge(pInfo['stepLarge'])
  187. self.ui.widget.setScalePoints(pInfo['scalePoints'], bool(pHints & PARAMETER_USES_SCALEPOINTS))
  188. if not pHints & PARAMETER_IS_ENABLED:
  189. self.ui.label.setEnabled(False)
  190. self.ui.widget.setEnabled(False)
  191. self.ui.widget.setReadOnly(True)
  192. self.ui.sb_control.setEnabled(False)
  193. self.ui.sb_channel.setEnabled(False)
  194. elif not pHints & PARAMETER_IS_AUTOMABLE:
  195. self.ui.sb_control.setEnabled(False)
  196. self.ui.sb_channel.setEnabled(False)
  197. if pHints & PARAMETER_IS_READ_ONLY:
  198. self.ui.widget.setReadOnly(True)
  199. if pHints & PARAMETER_USES_CUSTOM_TEXT:
  200. self.ui.widget.setTextCallback(self._textCallBack)
  201. self.ui.widget.updateAll()
  202. self.setMidiControl(pInfo['midiCC'])
  203. self.setMidiChannel(pInfo['midiChannel'])
  204. # -------------------------------------------------------------
  205. # Set-up connections
  206. self.ui.sb_control.customContextMenuRequested.connect(self.slot_controlSpinboxCustomMenu)
  207. self.ui.sb_channel.customContextMenuRequested.connect(self.slot_channelSpinboxCustomMenu)
  208. self.ui.sb_control.valueChanged.connect(self.slot_controlSpinboxChanged)
  209. self.ui.sb_channel.valueChanged.connect(self.slot_channelSpinboxChanged)
  210. self.ui.widget.valueChanged.connect(self.slot_widgetValueChanged)
  211. # -------------------------------------------------------------
  212. def getPluginId(self):
  213. return self.fPluginId
  214. def getTabIndex(self):
  215. return self.fTabIndex
  216. def setDefault(self, value):
  217. self.ui.widget.setDefault(value)
  218. def setValue(self, value, send=True):
  219. self.ui.widget.setValue(value, send)
  220. def setMidiControl(self, control):
  221. self.fMidiControl = control
  222. self.ui.sb_control.blockSignals(True)
  223. self.ui.sb_control.setValue(control)
  224. self.ui.sb_control.blockSignals(False)
  225. def setMidiChannel(self, channel):
  226. self.fMidiChannel = channel
  227. self.ui.sb_channel.blockSignals(True)
  228. self.ui.sb_channel.setValue(channel)
  229. self.ui.sb_channel.blockSignals(False)
  230. def setLabelWidth(self, width):
  231. self.ui.label.setMinimumWidth(width)
  232. self.ui.label.setMaximumWidth(width)
  233. @pyqtSlot()
  234. def slot_controlSpinboxCustomMenu(self):
  235. menu = QMenu(self)
  236. actNone = menu.addAction(self.tr("None"))
  237. if self.fMidiControl == -1:
  238. actNone.setCheckable(True)
  239. actNone.setChecked(True)
  240. for cc in MIDI_CC_LIST:
  241. action = menu.addAction(cc)
  242. if self.fMidiControl != -1 and int(cc.split(" ")[0], 16) == self.fMidiControl:
  243. action.setCheckable(True)
  244. action.setChecked(True)
  245. actSel = menu.exec_(QCursor.pos())
  246. if not actSel:
  247. pass
  248. elif actSel == actNone:
  249. self.ui.sb_control.setValue(-1)
  250. else:
  251. selControlStr = actSel.text()
  252. selControl = int(selControlStr.split(" ")[0], 16)
  253. self.ui.sb_control.setValue(selControl)
  254. @pyqtSlot()
  255. def slot_channelSpinboxCustomMenu(self):
  256. menu = QMenu(self)
  257. for i in range(1, 16+1):
  258. action = menu.addAction("%i" % i)
  259. if self.fMidiChannel == i:
  260. action.setCheckable(True)
  261. action.setChecked(True)
  262. actSel = menu.exec_(QCursor.pos())
  263. if actSel:
  264. selChannel = int(actSel.text())
  265. self.ui.sb_channel.setValue(selChannel)
  266. @pyqtSlot(int)
  267. def slot_controlSpinboxChanged(self, control):
  268. if self.fMidiControl != control:
  269. self.midiControlChanged.emit(self.fParameterId, control)
  270. self.fMidiControl = control
  271. @pyqtSlot(int)
  272. def slot_channelSpinboxChanged(self, channel):
  273. if self.fMidiChannel != channel:
  274. self.midiChannelChanged.emit(self.fParameterId, channel)
  275. self.fMidiChannel = channel
  276. @pyqtSlot(float)
  277. def slot_widgetValueChanged(self, value):
  278. self.valueChanged.emit(self.fParameterId, value)
  279. def _textCallBack(self):
  280. return Carla.host.get_parameter_text(self.fPluginId, self.fParameterId)
  281. # ------------------------------------------------------------------------------------------------------------
  282. # Plugin Editor (Built-in)
  283. class PluginEdit(QDialog):
  284. kParamsPerPage = 8
  285. def __init__(self, parent, pluginId):
  286. QDialog.__init__(self, Carla.gui)
  287. self.ui = ui_carla_edit.Ui_PluginEdit()
  288. self.ui.setupUi(self)
  289. # -------------------------------------------------------------
  290. # Internal stuff
  291. self.fGeometry = QByteArray()
  292. self.fPluginId = pluginId
  293. self.fPuginInfo = None
  294. self.fRealParent = parent
  295. self.fCurrentProgram = -1
  296. self.fCurrentMidiProgram = -1
  297. self.fCurrentStateFilename = None
  298. self.fControlChannel = 0
  299. self.fScrollAreaSetup = False
  300. self.fParameterCount = 0
  301. self.fParameterList = [] # (isInput, id, widget)
  302. self.fParametersToUpdate = [] # (id, value)
  303. self.fPlayingNotes = [] # (channel, note)
  304. self.fTabIconOff = QIcon(":/bitmaps/led_off.png")
  305. self.fTabIconOn = QIcon(":/bitmaps/led_yellow.png")
  306. self.fTabIconCount = 0
  307. self.fTabIconTimers = []
  308. # -------------------------------------------------------------
  309. # Set-up GUI
  310. self.ui.dial_drywet.setCustomPaint(self.ui.dial_drywet.CUSTOM_PAINT_CARLA_WET)
  311. self.ui.dial_drywet.setPixmap(3)
  312. self.ui.dial_drywet.setLabel("Dry/Wet")
  313. self.ui.dial_vol.setCustomPaint(self.ui.dial_vol.CUSTOM_PAINT_CARLA_VOL)
  314. self.ui.dial_vol.setPixmap(3)
  315. self.ui.dial_vol.setLabel("Volume")
  316. self.ui.dial_b_left.setCustomPaint(self.ui.dial_b_left.CUSTOM_PAINT_CARLA_L)
  317. self.ui.dial_b_left.setPixmap(4)
  318. self.ui.dial_b_left.setLabel("L")
  319. self.ui.dial_b_right.setCustomPaint(self.ui.dial_b_right.CUSTOM_PAINT_CARLA_R)
  320. self.ui.dial_b_right.setPixmap(4)
  321. self.ui.dial_b_right.setLabel("R")
  322. self.ui.dial_pan.setCustomPaint(self.ui.dial_b_right.CUSTOM_PAINT_CARLA_R) # FIXME
  323. self.ui.dial_pan.setPixmap(4)
  324. self.ui.dial_pan.setLabel("Pan")
  325. self.ui.keyboard.setMode(self.ui.keyboard.HORIZONTAL)
  326. self.ui.keyboard.setOctaves(10)
  327. self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1)
  328. self.ui.scrollArea.ensureVisible(self.ui.keyboard.width() / 3, 0)
  329. self.ui.scrollArea.setEnabled(False)
  330. self.ui.scrollArea.setVisible(False)
  331. self.reloadAll()
  332. # -------------------------------------------------------------
  333. # Set-up connections
  334. self.finished.connect(self.slot_finished)
  335. self.ui.ch_fixed_buffer.clicked.connect(self.slot_optionChanged)
  336. self.ui.ch_force_stereo.clicked.connect(self.slot_optionChanged)
  337. self.ui.ch_map_program_changes.clicked.connect(self.slot_optionChanged)
  338. self.ui.ch_use_chunks.clicked.connect(self.slot_optionChanged)
  339. self.ui.ch_send_control_changes.clicked.connect(self.slot_optionChanged)
  340. self.ui.ch_send_channel_pressure.clicked.connect(self.slot_optionChanged)
  341. self.ui.ch_send_note_aftertouch.clicked.connect(self.slot_optionChanged)
  342. self.ui.ch_send_pitchbend.clicked.connect(self.slot_optionChanged)
  343. self.ui.ch_send_all_sound_off.clicked.connect(self.slot_optionChanged)
  344. self.ui.dial_drywet.valueChanged.connect(self.slot_dryWetChanged)
  345. self.ui.dial_vol.valueChanged.connect(self.slot_volumeChanged)
  346. self.ui.dial_b_left.valueChanged.connect(self.slot_balanceLeftChanged)
  347. self.ui.dial_b_right.valueChanged.connect(self.slot_balanceRightChanged)
  348. self.ui.sb_ctrl_channel.valueChanged.connect(self.slot_ctrlChannelChanged)
  349. self.ui.dial_drywet.customContextMenuRequested.connect(self.slot_knobCustomMenu)
  350. self.ui.dial_vol.customContextMenuRequested.connect(self.slot_knobCustomMenu)
  351. self.ui.dial_b_left.customContextMenuRequested.connect(self.slot_knobCustomMenu)
  352. self.ui.dial_b_right.customContextMenuRequested.connect(self.slot_knobCustomMenu)
  353. self.ui.sb_ctrl_channel.customContextMenuRequested.connect(self.slot_channelCustomMenu)
  354. self.ui.keyboard.noteOn.connect(self.slot_noteOn)
  355. self.ui.keyboard.noteOff.connect(self.slot_noteOff)
  356. self.ui.cb_programs.currentIndexChanged.connect(self.slot_programIndexChanged)
  357. self.ui.cb_midi_programs.currentIndexChanged.connect(self.slot_midiProgramIndexChanged)
  358. if Carla.isLocal:
  359. self.ui.b_save_state.clicked.connect(self.slot_stateSave)
  360. self.ui.b_load_state.clicked.connect(self.slot_stateLoad)
  361. else:
  362. self.ui.b_load_state.setEnabled(False)
  363. self.ui.b_save_state.setEnabled(False)
  364. # -------------------------------------------------------------
  365. def reloadAll(self):
  366. if Carla.host is not None:
  367. self.fPluginInfo = Carla.host.get_plugin_info(self.fPluginId)
  368. self.fPluginInfo['binary'] = charPtrToString(self.fPluginInfo['binary'])
  369. self.fPluginInfo['name'] = charPtrToString(self.fPluginInfo['name'])
  370. self.fPluginInfo['label'] = charPtrToString(self.fPluginInfo['label'])
  371. self.fPluginInfo['maker'] = charPtrToString(self.fPluginInfo['maker'])
  372. self.fPluginInfo['copyright'] = charPtrToString(self.fPluginInfo['copyright'])
  373. self.fPluginInfo['iconName'] = charPtrToString(self.fPluginInfo['iconName'])
  374. if not Carla.isLocal:
  375. self.fPluginInfo['hints'] &= ~PLUGIN_HAS_CUSTOM_UI
  376. else:
  377. self.fPluginInfo = gFakePluginInfo
  378. self.reloadInfo()
  379. self.reloadParameters()
  380. self.reloadPrograms()
  381. if self.fPluginInfo['type'] == PLUGIN_LV2:
  382. self.ui.b_save_state.setEnabled(False)
  383. if not self.ui.scrollArea.isEnabled():
  384. self.resize(self.width(), self.height()-self.ui.scrollArea.height())
  385. def reloadInfo(self):
  386. if Carla.host is not None:
  387. pluginName = Carla.host.get_real_plugin_name(self.fPluginId)
  388. audioCountInfo = Carla.host.get_audio_port_count_info(self.fPluginId)
  389. midiCountInfo = Carla.host.get_midi_port_count_info(self.fPluginId)
  390. paramCountInfo = Carla.host.get_parameter_count_info(self.fPluginId)
  391. else:
  392. pluginName = ""
  393. audioCountInfo = gFakePortCountInfo
  394. midiCountInfo = gFakePortCountInfo
  395. paramCountInfo = gFakePortCountInfo
  396. pluginType = self.fPluginInfo['type']
  397. pluginHints = self.fPluginInfo['hints']
  398. if pluginType == PLUGIN_INTERNAL:
  399. self.ui.le_type.setText(self.tr("Internal"))
  400. elif pluginType == PLUGIN_LADSPA:
  401. self.ui.le_type.setText("LADSPA")
  402. elif pluginType == PLUGIN_DSSI:
  403. self.ui.le_type.setText("DSSI")
  404. elif pluginType == PLUGIN_LV2:
  405. self.ui.le_type.setText("LV2")
  406. elif pluginType == PLUGIN_VST:
  407. self.ui.le_type.setText("VST")
  408. elif pluginType == PLUGIN_AU:
  409. self.ui.le_type.setText("AU")
  410. elif pluginType == PLUGIN_CSOUND:
  411. self.ui.le_type.setText("CSOUND")
  412. elif pluginType == PLUGIN_GIG:
  413. self.ui.le_type.setText("GIG")
  414. elif pluginType == PLUGIN_SF2:
  415. self.ui.le_type.setText("SF2")
  416. elif pluginType == PLUGIN_SFZ:
  417. self.ui.le_type.setText("SFZ")
  418. else:
  419. self.ui.le_type.setText(self.tr("Unknown"))
  420. if pluginName:
  421. self.ui.label_name.setEnabled(True)
  422. self.ui.le_name.setEnabled(True)
  423. self.ui.le_name.setText(pluginName)
  424. self.ui.le_name.setToolTip(pluginName)
  425. else:
  426. self.ui.label_name.setEnabled(False)
  427. self.ui.le_name.setEnabled(False)
  428. self.ui.le_name.setText("")
  429. self.ui.le_name.setToolTip("")
  430. if self.fPluginInfo['label']:
  431. self.ui.label_label.setEnabled(True)
  432. self.ui.le_label.setEnabled(True)
  433. self.ui.le_label.setText(self.fPluginInfo['label'])
  434. self.ui.le_label.setToolTip(self.fPluginInfo['label'])
  435. else:
  436. self.ui.label_label.setEnabled(False)
  437. self.ui.le_label.setEnabled(False)
  438. self.ui.le_label.setText("")
  439. self.ui.le_label.setToolTip("")
  440. if self.fPluginInfo['maker']:
  441. self.ui.label_maker.setEnabled(True)
  442. self.ui.le_maker.setEnabled(True)
  443. self.ui.le_maker.setText(self.fPluginInfo['maker'])
  444. self.ui.le_maker.setToolTip(self.fPluginInfo['maker'])
  445. else:
  446. self.ui.label_maker.setEnabled(False)
  447. self.ui.le_maker.setEnabled(False)
  448. self.ui.le_maker.setText("")
  449. self.ui.le_maker.setToolTip("")
  450. if self.fPluginInfo['copyright']:
  451. self.ui.label_copyright.setEnabled(True)
  452. self.ui.le_copyright.setEnabled(True)
  453. self.ui.le_copyright.setText(self.fPluginInfo['copyright'])
  454. self.ui.le_copyright.setToolTip(self.fPluginInfo['copyright'])
  455. else:
  456. self.ui.label_copyright.setEnabled(False)
  457. self.ui.le_copyright.setEnabled(False)
  458. self.ui.le_copyright.setText("")
  459. self.ui.le_copyright.setToolTip("")
  460. if self.fPluginInfo['uniqueId'] != 0:
  461. self.ui.label_unique_id.setEnabled(True)
  462. self.ui.le_unique_id.setEnabled(True)
  463. self.ui.le_unique_id.setText(str(self.fPluginInfo['uniqueId']))
  464. self.ui.le_unique_id.setToolTip(str(self.fPluginInfo['uniqueId']))
  465. else:
  466. self.ui.label_unique_id.setEnabled(False)
  467. self.ui.le_unique_id.setEnabled(False)
  468. self.ui.le_unique_id.setText("")
  469. self.ui.le_unique_id.setToolTip("")
  470. self.ui.label_plugin.setText("\n%s\n" % self.fPluginInfo['name'])
  471. self.setWindowTitle(self.fPluginInfo['name'])
  472. self.ui.dial_drywet.setEnabled(pluginHints & PLUGIN_CAN_DRYWET)
  473. self.ui.dial_vol.setEnabled(pluginHints & PLUGIN_CAN_VOLUME)
  474. self.ui.dial_b_left.setEnabled(pluginHints & PLUGIN_CAN_BALANCE)
  475. self.ui.dial_b_right.setEnabled(pluginHints & PLUGIN_CAN_BALANCE)
  476. self.ui.dial_pan.setEnabled(pluginHints & PLUGIN_CAN_PANNING)
  477. self.ui.ch_fixed_buffer.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_FIXED_BUFFERS)
  478. self.ui.ch_fixed_buffer.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_FIXED_BUFFERS)
  479. self.ui.ch_force_stereo.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_FORCE_STEREO)
  480. self.ui.ch_force_stereo.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_FORCE_STEREO)
  481. self.ui.ch_map_program_changes.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_MAP_PROGRAM_CHANGES)
  482. self.ui.ch_map_program_changes.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_MAP_PROGRAM_CHANGES)
  483. self.ui.ch_use_chunks.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_USE_CHUNKS)
  484. self.ui.ch_use_chunks.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_USE_CHUNKS)
  485. self.ui.ch_send_control_changes.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_CONTROL_CHANGES)
  486. self.ui.ch_send_control_changes.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_SEND_CONTROL_CHANGES)
  487. self.ui.ch_send_channel_pressure.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE)
  488. self.ui.ch_send_channel_pressure.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE)
  489. self.ui.ch_send_note_aftertouch.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH)
  490. self.ui.ch_send_note_aftertouch.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH)
  491. self.ui.ch_send_pitchbend.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_PITCHBEND)
  492. self.ui.ch_send_pitchbend.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_SEND_PITCHBEND)
  493. self.ui.ch_send_all_sound_off.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
  494. self.ui.ch_send_all_sound_off.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
  495. if self.fPluginInfo['type'] != PLUGIN_VST:
  496. self.ui.sw_programs.setCurrentIndex(1)
  497. # Show/hide keyboard
  498. showKeyboard = (self.fPluginInfo['category'] == PLUGIN_CATEGORY_SYNTH or midiCountInfo['ins'] > 0 < midiCountInfo['outs'])
  499. self.ui.scrollArea.setEnabled(showKeyboard)
  500. self.ui.scrollArea.setVisible(showKeyboard)
  501. # Force-Update parent for new hints
  502. if self.fRealParent:
  503. self.fRealParent.recheckPluginHints(pluginHints)
  504. def reloadParameters(self):
  505. # Reset
  506. self.fParameterCount = 0
  507. self.fParameterList = []
  508. self.fParametersToUpdate = []
  509. self.fTabIconCount = 0
  510. self.fTabIconTimers = []
  511. # Remove all previous parameters
  512. for x in range(self.ui.tabWidget.count()-1):
  513. self.ui.tabWidget.widget(1).deleteLater()
  514. self.ui.tabWidget.removeTab(1)
  515. if Carla.host is None:
  516. paramFakeListFull = []
  517. paramFakeList = []
  518. paramFakeWidth = QFontMetrics(self.font()).width(gFakeParamInfo['name'])
  519. paramFakeList.append(gFakeParamInfo)
  520. paramFakeListFull.append((paramFakeList, paramFakeWidth))
  521. self._createParameterWidgets(True, paramFakeListFull, self.tr("Parameters"))
  522. return
  523. parameterCount = Carla.host.get_parameter_count(self.fPluginId)
  524. if parameterCount <= 0:
  525. pass
  526. elif parameterCount <= Carla.maxParameters:
  527. paramInputListFull = []
  528. paramOutputListFull = []
  529. paramInputList = [] # ([params], width)
  530. paramInputWidth = 0
  531. paramOutputList = [] # ([params], width)
  532. paramOutputWidth = 0
  533. for i in range(parameterCount):
  534. paramInfo = Carla.host.get_parameter_info(self.fPluginId, i)
  535. paramData = Carla.host.get_parameter_data(self.fPluginId, i)
  536. paramRanges = Carla.host.get_parameter_ranges(self.fPluginId, i)
  537. paramValue = Carla.host.get_current_parameter_value(self.fPluginId, i)
  538. parameter = {
  539. 'type': paramData['type'],
  540. 'hints': paramData['hints'],
  541. 'name': charPtrToString(paramInfo['name']),
  542. 'unit': charPtrToString(paramInfo['unit']),
  543. 'scalePoints': [],
  544. 'index': paramData['index'],
  545. 'default': paramRanges['def'],
  546. 'minimum': paramRanges['min'],
  547. 'maximum': paramRanges['max'],
  548. 'step': paramRanges['step'],
  549. 'stepSmall': paramRanges['stepSmall'],
  550. 'stepLarge': paramRanges['stepLarge'],
  551. 'midiCC': paramData['midiCC'],
  552. 'midiChannel': paramData['midiChannel']+1,
  553. 'current': paramValue
  554. }
  555. for j in range(paramInfo['scalePointCount']):
  556. scalePointInfo = Carla.host.get_parameter_scalepoint_info(self.fPluginId, i, j)
  557. parameter['scalePoints'].append({
  558. 'value': scalePointInfo['value'],
  559. 'label': charPtrToString(scalePointInfo['label'])
  560. })
  561. #parameter['name'] = parameter['name'][:30] + (parameter['name'][30:] and "...")
  562. # -----------------------------------------------------------------
  563. # Get width values, in packs of 10
  564. if parameter['hints'] == PARAMETER_IS_INPUT:
  565. paramInputWidthTMP = QFontMetrics(self.font()).width(parameter['name'])
  566. if paramInputWidthTMP > paramInputWidth:
  567. paramInputWidth = paramInputWidthTMP
  568. paramInputList.append(parameter)
  569. if len(paramInputList) == self.kParamsPerPage:
  570. paramInputListFull.append((paramInputList, paramInputWidth))
  571. paramInputList = []
  572. paramInputWidth = 0
  573. else:
  574. paramOutputWidthTMP = QFontMetrics(self.font()).width(parameter['name'])
  575. if paramOutputWidthTMP > paramOutputWidth:
  576. paramOutputWidth = paramOutputWidthTMP
  577. paramOutputList.append(parameter)
  578. if len(paramOutputList) == self.kParamsPerPage:
  579. paramOutputListFull.append((paramOutputList, paramOutputWidth))
  580. paramOutputList = []
  581. paramOutputWidth = 0
  582. # for i in range(parameterCount)
  583. else:
  584. # Final page width values
  585. if 0 < len(paramInputList) < 10:
  586. paramInputListFull.append((paramInputList, paramInputWidth))
  587. if 0 < len(paramOutputList) < 10:
  588. paramOutputListFull.append((paramOutputList, paramOutputWidth))
  589. # -----------------------------------------------------------------
  590. # Create parameter tabs + widgets
  591. self._createParameterWidgets(True, paramInputListFull, self.tr("Parameters"))
  592. self._createParameterWidgets(False, paramOutputListFull, self.tr("Outputs"))
  593. else: # > Carla.maxParameters
  594. fakeName = self.tr("This plugin has too many parameters to display here!")
  595. paramFakeListFull = []
  596. paramFakeList = []
  597. paramFakeWidth = QFontMetrics(self.font()).width(fakeName)
  598. parameter = {
  599. 'hints': PARAMETER_IS_INPUT|PARAMETER_IS_ENABLED|PARAMETER_IS_READ_ONLY,
  600. 'name': fakeName,
  601. 'unit': "",
  602. 'scalePoints': [],
  603. 'index': 0,
  604. 'default': 0.0,
  605. 'minimum': 0.0,
  606. 'maximum': 0.0,
  607. 'step': 0.0,
  608. 'stepSmall': 0.0,
  609. 'stepLarge': 0.0,
  610. 'midiCC': -1,
  611. 'midiChannel': 1,
  612. 'current': 0.0
  613. }
  614. paramFakeList.append(parameter)
  615. paramFakeListFull.append((paramFakeList, paramFakeWidth))
  616. self._createParameterWidgets(True, paramFakeListFull, self.tr("Information"))
  617. def reloadPrograms(self):
  618. # Programs
  619. self.ui.cb_programs.blockSignals(True)
  620. self.ui.cb_programs.clear()
  621. programCount = Carla.host.get_program_count(self.fPluginId) if Carla.host is not None else 0
  622. if programCount > 0:
  623. self.ui.cb_programs.setEnabled(True)
  624. self.ui.label_programs.setEnabled(True)
  625. for i in range(programCount):
  626. pName = charPtrToString(Carla.host.get_program_name(self.fPluginId, i))
  627. #pName = pName[:40] + (pName[40:] and "...")
  628. self.ui.cb_programs.addItem(pName)
  629. self.fCurrentProgram = Carla.host.get_current_program_index(self.fPluginId)
  630. self.ui.cb_programs.setCurrentIndex(self.fCurrentProgram)
  631. else:
  632. self.fCurrentProgram = -1
  633. self.ui.cb_programs.setEnabled(False)
  634. self.ui.label_programs.setEnabled(False)
  635. self.ui.cb_programs.blockSignals(False)
  636. # MIDI Programs
  637. self.ui.cb_midi_programs.blockSignals(True)
  638. self.ui.cb_midi_programs.clear()
  639. midiProgramCount = Carla.host.get_midi_program_count(self.fPluginId) if Carla.host is not None else 0
  640. if midiProgramCount > 0:
  641. self.ui.cb_midi_programs.setEnabled(True)
  642. self.ui.label_midi_programs.setEnabled(True)
  643. for i in range(midiProgramCount):
  644. mpData = Carla.host.get_midi_program_data(self.fPluginId, i)
  645. mpBank = int(mpData['bank'])
  646. mpProg = int(mpData['program'])
  647. mpName = charPtrToString(mpData['name'])
  648. #mpName = mpName[:40] + (mpName[40:] and "...")
  649. self.ui.cb_midi_programs.addItem("%03i:%03i - %s" % (mpBank+1, mpProg+1, mpName))
  650. self.fCurrentMidiProgram = Carla.host.get_current_midi_program_index(self.fPluginId)
  651. self.ui.cb_midi_programs.setCurrentIndex(self.fCurrentMidiProgram)
  652. else:
  653. self.fCurrentMidiProgram = -1
  654. self.ui.cb_midi_programs.setEnabled(False)
  655. self.ui.label_midi_programs.setEnabled(False)
  656. self.ui.cb_midi_programs.blockSignals(False)
  657. self.ui.sw_programs.setEnabled(programCount > 0 or midiProgramCount > 0)
  658. if self.fPluginInfo['type'] == PLUGIN_LV2:
  659. self.ui.b_load_state.setEnabled(programCount > 0)
  660. def updateInfo(self):
  661. # Update current program text
  662. if self.ui.cb_programs.count() > 0:
  663. pIndex = self.ui.cb_programs.currentIndex()
  664. pName = charPtrToString(Carla.host.get_program_name(self.fPluginId, pIndex))
  665. #pName = pName[:40] + (pName[40:] and "...")
  666. self.ui.cb_programs.setItemText(pIndex, pName)
  667. # Update current midi program text
  668. if self.ui.cb_midi_programs.count() > 0:
  669. mpIndex = self.ui.cb_midi_programs.currentIndex()
  670. mpData = Carla.host.get_midi_program_data(self.fPluginId, mpIndex)
  671. mpBank = int(mpData['bank'])
  672. mpProg = int(mpData['program'])
  673. mpName = charPtrToString(mpData['name'])
  674. #mpName = mpName[:40] + (mpName[40:] and "...")
  675. self.ui.cb_midi_programs.setItemText(mpIndex, "%03i:%03i - %s" % (mpBank+1, mpProg+1, mpName))
  676. # Update all parameter values
  677. for isInput, paramId, paramWidget in self.fParameterList:
  678. paramWidget.setValue(Carla.host.get_current_parameter_value(self.fPluginId, paramId), False)
  679. paramWidget.update()
  680. self.fParametersToUpdate = []
  681. def clearNotes(self):
  682. self.fPlayingNotes = []
  683. self.ui.keyboard.allNotesOff()
  684. def setName(self, name):
  685. self.ui.label_plugin.setText("\n%s\n" % name)
  686. self.setWindowTitle(name)
  687. def setParameterValue(self, parameterId, value):
  688. for paramItem in self.fParametersToUpdate:
  689. if paramItem[0] == parameterId:
  690. paramItem[1] = value
  691. break
  692. else:
  693. self.fParametersToUpdate.append([parameterId, value])
  694. def setParameterDefault(self, parameterId, value):
  695. for isInput, paramId, paramWidget in self.fParameterList:
  696. if paramId == parameterId:
  697. paramWidget.setDefault(value)
  698. break
  699. def setParameterMidiControl(self, parameterId, control):
  700. for isInput, paramId, paramWidget in self.fParameterList:
  701. if paramId == parameterId:
  702. paramWidget.setMidiControl(control)
  703. break
  704. def setParameterMidiChannel(self, parameterId, channel):
  705. for isInput, paramId, paramWidget in self.fParameterList:
  706. if paramId == parameterId:
  707. paramWidget.setMidiChannel(channel+1)
  708. break
  709. def setProgram(self, index):
  710. self.ui.cb_programs.blockSignals(True)
  711. self.ui.cb_programs.setCurrentIndex(index)
  712. self.ui.cb_programs.blockSignals(False)
  713. def setMidiProgram(self, index):
  714. self.ui.cb_midi_programs.blockSignals(True)
  715. self.ui.cb_midi_programs.setCurrentIndex(index)
  716. self.ui.cb_midi_programs.blockSignals(False)
  717. def sendNoteOn(self, channel, note):
  718. if self.fControlChannel == channel:
  719. self.ui.keyboard.sendNoteOn(note, False)
  720. playItem = (channel, note)
  721. if playItem not in self.fPlayingNotes:
  722. self.fPlayingNotes.append(playItem)
  723. return bool(len(self.fPlayingNotes) == 1)
  724. def sendNoteOff(self, channel, note):
  725. if self.fControlChannel == channel:
  726. self.ui.keyboard.sendNoteOff(note, False)
  727. playItem = (channel, note)
  728. if playItem in self.fPlayingNotes:
  729. self.fPlayingNotes.remove(playItem)
  730. return bool(len(self.fPlayingNotes) == 0)
  731. def setVisible(self, yesNo):
  732. if yesNo:
  733. if not self.fGeometry.isNull():
  734. self.restoreGeometry(self.fGeometry)
  735. else:
  736. self.fGeometry = self.saveGeometry()
  737. QDialog.setVisible(self, yesNo)
  738. def idleSlow(self):
  739. # Check Tab icons
  740. for i in range(len(self.fTabIconTimers)):
  741. if self.fTabIconTimers[i] == ICON_STATE_ON:
  742. self.fTabIconTimers[i] = ICON_STATE_WAIT
  743. elif self.fTabIconTimers[i] == ICON_STATE_WAIT:
  744. self.fTabIconTimers[i] = ICON_STATE_OFF
  745. elif self.fTabIconTimers[i] == ICON_STATE_OFF:
  746. self.fTabIconTimers[i] = ICON_STATE_NULL
  747. self.ui.tabWidget.setTabIcon(i+1, self.fTabIconOff)
  748. # Check parameters needing update
  749. for index, value in self.fParametersToUpdate:
  750. if index == PARAMETER_DRYWET:
  751. self.ui.dial_drywet.blockSignals(True)
  752. self.ui.dial_drywet.setValue(value * 1000)
  753. self.ui.dial_drywet.blockSignals(False)
  754. elif index == PARAMETER_VOLUME:
  755. self.ui.dial_vol.blockSignals(True)
  756. self.ui.dial_vol.setValue(value * 1000)
  757. self.ui.dial_vol.blockSignals(False)
  758. elif index == PARAMETER_BALANCE_LEFT:
  759. self.ui.dial_b_left.blockSignals(True)
  760. self.ui.dial_b_left.setValue(value * 1000)
  761. self.ui.dial_b_left.blockSignals(False)
  762. elif index == PARAMETER_BALANCE_RIGHT:
  763. self.ui.dial_b_right.blockSignals(True)
  764. self.ui.dial_b_right.setValue(value * 1000)
  765. self.ui.dial_b_right.blockSignals(False)
  766. #elif index == PARAMETER_PANNING:
  767. #self.ui.dial_pan.blockSignals(True)
  768. #self.ui.dial_pan.setValue(value * 1000, True, False)
  769. #self.ui.dial_pan.blockSignals(False)
  770. elif index == PARAMETER_CTRL_CHANNEL:
  771. self.fControlChannel = int(value)
  772. self.ui.sb_ctrl_channel.blockSignals(True)
  773. self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1)
  774. self.ui.sb_ctrl_channel.blockSignals(False)
  775. self.ui.keyboard.allNotesOff()
  776. self._updateCtrlMidiProgram()
  777. elif index >= 0:
  778. for isInput, paramId, paramWidget in self.fParameterList:
  779. if paramId != index:
  780. continue
  781. paramWidget.setValue(value, False)
  782. if isInput:
  783. tabIndex = paramWidget.tabIndex()
  784. if self.fTabIconTimers[tabIndex-1] == ICON_STATE_NULL:
  785. self.ui.tabWidget.setTabIcon(tabIndex, self.fTabIconOn)
  786. self.fTabIconTimers[tabIndex-1] = ICON_STATE_ON
  787. break
  788. # Clear all parameters
  789. self.fParametersToUpdate = []
  790. # Update parameter outputs
  791. for isInput, paramId, paramWidget in self.fParameterList:
  792. if not isInput:
  793. value = Carla.host.get_current_parameter_value(self.fPluginId, paramId)
  794. paramWidget.setValue(value, False)
  795. @pyqtSlot()
  796. def slot_stateSave(self):
  797. if self.fPluginInfo['type'] == PLUGIN_LV2:
  798. # TODO
  799. return
  800. if self.fCurrentStateFilename:
  801. askTry = QMessageBox.question(self, self.tr("Overwrite?"), self.tr("Overwrite previously created file?"), QMessageBox.Ok|QMessageBox.Cancel)
  802. if askTry == QMessageBox.Ok:
  803. Carla.host.save_plugin_state(self.fPluginId, self.fCurrentStateFilename)
  804. return
  805. self.fCurrentStateFilename = None
  806. fileFilter = self.tr("Carla State File (*.carxs)")
  807. filenameTry = QFileDialog.getSaveFileName(self, self.tr("Save Plugin State File"), filter=fileFilter)
  808. if filenameTry:
  809. if not filenameTry.lower().endswith(".carxs"):
  810. filenameTry += ".carxs"
  811. self.fCurrentStateFilename = filenameTry
  812. Carla.host.save_plugin_state(self.fPluginId, self.fCurrentStateFilename)
  813. @pyqtSlot()
  814. def slot_stateLoad(self):
  815. if self.fPluginInfo['type'] == PLUGIN_LV2:
  816. presetList = []
  817. for i in range(Carla.host.get_program_count(self.fPluginId)):
  818. presetList.append("%03i - %s" % (i+1, charPtrToString(Carla.host.get_program_name(self.fPluginId, i))))
  819. ret = QInputDialog.getItem(self, self.tr("Open LV2 Preset"), self.tr("Select an LV2 Preset:"), presetList, 0, False)
  820. if ret[1]:
  821. index = int(ret[0].split(" - ", 1)[0])-1
  822. Carla.host.set_midi_program(self.fPluginId, -1)
  823. Carla.host.set_program(self.fPluginId, index)
  824. self.setMidiProgram(-1)
  825. return
  826. fileFilter = self.tr("Carla State File (*.carxs)")
  827. filenameTry = QFileDialog.getOpenFileName(self, self.tr("Open Plugin State File"), filter=fileFilter)
  828. if filenameTry:
  829. self.fCurrentStateFilename = filenameTry
  830. Carla.host.load_plugin_state(self.fPluginId, self.fCurrentStateFilename)
  831. @pyqtSlot(bool)
  832. def slot_optionChanged(self, clicked):
  833. if Carla.host is None:
  834. return
  835. sender = self.sender()
  836. if sender == self.ui.ch_fixed_buffer:
  837. option = PLUGIN_OPTION_FIXED_BUFFERS
  838. elif sender == self.ui.ch_force_stereo:
  839. option = PLUGIN_OPTION_FORCE_STEREO
  840. elif sender == self.ui.ch_map_program_changes:
  841. option = PLUGIN_OPTION_MAP_PROGRAM_CHANGES
  842. elif sender == self.ui.ch_use_chunks:
  843. option = PLUGIN_OPTION_USE_CHUNKS
  844. elif sender == self.ui.ch_send_control_changes:
  845. option = PLUGIN_OPTION_SEND_CONTROL_CHANGES
  846. elif sender == self.ui.ch_send_channel_pressure:
  847. option = PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
  848. elif sender == self.ui.ch_send_note_aftertouch:
  849. option = PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
  850. elif sender == self.ui.ch_send_pitchbend:
  851. option = PLUGIN_OPTION_SEND_PITCHBEND
  852. elif sender == self.ui.ch_send_all_sound_off:
  853. option = PLUGIN_OPTION_SEND_ALL_SOUND_OFF
  854. else:
  855. return
  856. Carla.host.set_option(self.fPluginId, option, clicked)
  857. @pyqtSlot(int)
  858. def slot_dryWetChanged(self, value):
  859. if Carla.host is not None:
  860. Carla.host.set_drywet(self.fPluginId, float(value)/1000)
  861. @pyqtSlot(int)
  862. def slot_volumeChanged(self, value):
  863. if Carla.host is not None:
  864. Carla.host.set_volume(self.fPluginId, float(value)/1000)
  865. @pyqtSlot(int)
  866. def slot_balanceLeftChanged(self, value):
  867. if Carla.host is not None:
  868. Carla.host.set_balance_left(self.fPluginId, float(value)/1000)
  869. @pyqtSlot(int)
  870. def slot_balanceRightChanged(self, value):
  871. if Carla.host is not None:
  872. Carla.host.set_balance_right(self.fPluginId, float(value)/1000)
  873. @pyqtSlot(int)
  874. def slot_panningChanged(self, value):
  875. if Carla.host is not None:
  876. Carla.host.set_panning(self.fPluginId, float(value)/1000)
  877. @pyqtSlot(int)
  878. def slot_ctrlChannelChanged(self, value):
  879. self.fControlChannel = value-1
  880. if Carla.host is not None:
  881. Carla.host.set_ctrl_channel(self.fPluginId, self.fControlChannel)
  882. self.ui.keyboard.allNotesOff()
  883. self._updateCtrlMidiProgram()
  884. @pyqtSlot(int, float)
  885. def slot_parameterValueChanged(self, parameterId, value):
  886. if Carla.host is not None:
  887. Carla.host.set_parameter_value(self.fPluginId, parameterId, value)
  888. @pyqtSlot(int, int)
  889. def slot_parameterMidiControlChanged(self, parameterId, control):
  890. if Carla.host is not None:
  891. Carla.host.set_parameter_midi_cc(self.fPluginId, parameterId, control)
  892. @pyqtSlot(int, int)
  893. def slot_parameterMidiChannelChanged(self, parameterId, channel):
  894. if Carla.host is not None:
  895. Carla.host.set_parameter_midi_channel(self.fPluginId, parameterId, channel-1)
  896. @pyqtSlot(int)
  897. def slot_programIndexChanged(self, index):
  898. self.fCurrentProgram = index
  899. if Carla.host is not None:
  900. Carla.host.set_program(self.fPluginId, index)
  901. @pyqtSlot(int)
  902. def slot_midiProgramIndexChanged(self, index):
  903. self.fCurrentMidiProgram = index
  904. if Carla.host is not None:
  905. Carla.host.set_midi_program(self.fPluginId, index)
  906. @pyqtSlot(int)
  907. def slot_noteOn(self, note):
  908. if self.fControlChannel >= 0 and Carla.host is not None:
  909. Carla.host.send_midi_note(self.fPluginId, self.fControlChannel, note, 100)
  910. @pyqtSlot(int)
  911. def slot_noteOff(self, note):
  912. if self.fControlChannel >= 0 and Carla.host is not None:
  913. Carla.host.send_midi_note(self.fPluginId, self.fControlChannel, note, 0)
  914. @pyqtSlot()
  915. def slot_finished(self):
  916. if self.fRealParent:
  917. self.fRealParent.editClosed()
  918. @pyqtSlot()
  919. def slot_knobCustomMenu(self):
  920. dialName = self.sender().objectName()
  921. if dialName == "dial_drywet":
  922. minimum = 0
  923. maximum = 100
  924. default = 100
  925. label = "Dry/Wet"
  926. elif dialName == "dial_vol":
  927. minimum = 0
  928. maximum = 127
  929. default = 100
  930. label = "Volume"
  931. elif dialName == "dial_b_left":
  932. minimum = -100
  933. maximum = 100
  934. default = -100
  935. label = "Balance-Left"
  936. elif dialName == "dial_b_right":
  937. minimum = -100
  938. maximum = 100
  939. default = 100
  940. label = "Balance-Right"
  941. elif dialName == "dial_panning":
  942. minimum = -100
  943. maximum = 100
  944. default = 0
  945. label = "Panning"
  946. else:
  947. minimum = 0
  948. maximum = 100
  949. default = 100
  950. label = "Unknown"
  951. current = self.sender().value() / 10
  952. menu = QMenu(self)
  953. actReset = menu.addAction(self.tr("Reset (%i%%)" % default))
  954. menu.addSeparator()
  955. actMinimum = menu.addAction(self.tr("Set to Minimum (%i%%)" % minimum))
  956. actCenter = menu.addAction(self.tr("Set to Center"))
  957. actMaximum = menu.addAction(self.tr("Set to Maximum (%i%%)" % maximum))
  958. menu.addSeparator()
  959. actSet = menu.addAction(self.tr("Set value..."))
  960. if label not in ("Balance-Left", "Balance-Right"):
  961. menu.removeAction(actCenter)
  962. actSelected = menu.exec_(QCursor.pos())
  963. if actSelected == actSet:
  964. valueTry = QInputDialog.getInteger(self, self.tr("Set value"), label, current, minimum, maximum, 1)
  965. if valueTry[1]:
  966. value = valueTry[0] * 10
  967. else:
  968. return
  969. elif actSelected == actMinimum:
  970. value = minimum * 10
  971. elif actSelected == actMaximum:
  972. value = maximum * 10
  973. elif actSelected == actReset:
  974. value = default * 10
  975. elif actSelected == actCenter:
  976. value = 0
  977. else:
  978. return
  979. if label == "Dry/Wet":
  980. self.ui.dial_drywet.setValue(value)
  981. elif label == "Volume":
  982. self.ui.dial_vol.setValue(value)
  983. elif label == "Balance-Left":
  984. self.ui.dial_b_left.setValue(value)
  985. elif label == "Balance-Right":
  986. self.ui.dial_b_right.setValue(value)
  987. #elif label == "Panning":
  988. #self.ui.dial_panning.setValue(value)
  989. @pyqtSlot()
  990. def slot_channelCustomMenu(self):
  991. menu = QMenu(self)
  992. actNone = menu.addAction(self.tr("None"))
  993. if self.fControlChannel+1 == 0:
  994. actNone.setCheckable(True)
  995. actNone.setChecked(True)
  996. for i in range(1, 16+1):
  997. action = menu.addAction("%i" % i)
  998. if self.fControlChannel+1 == i:
  999. action.setCheckable(True)
  1000. action.setChecked(True)
  1001. actSel = menu.exec_(QCursor.pos())
  1002. if not actSel:
  1003. pass
  1004. elif actSel == actNone:
  1005. self.ui.sb_ctrl_channel.setValue(0)
  1006. elif actSel:
  1007. selChannel = int(actSel.text())
  1008. self.ui.sb_ctrl_channel.setValue(selChannel)
  1009. def _createParameterWidgets(self, isInput, paramListFull, tabPageName):
  1010. i = 1
  1011. for paramList, width in paramListFull:
  1012. if len(paramList) == 0:
  1013. break
  1014. tabIndex = self.ui.tabWidget.count()
  1015. tabPageContainer = QWidget(self.ui.tabWidget)
  1016. tabPageLayout = QVBoxLayout(tabPageContainer)
  1017. tabPageContainer.setLayout(tabPageLayout)
  1018. for paramInfo in paramList:
  1019. paramWidget = PluginParameter(tabPageContainer, paramInfo, self.fPluginId, tabIndex)
  1020. paramWidget.setLabelWidth(width)
  1021. tabPageLayout.addWidget(paramWidget)
  1022. self.fParameterList.append((isInput, paramInfo['index'], paramWidget))
  1023. if isInput:
  1024. paramWidget.valueChanged.connect(self.slot_parameterValueChanged)
  1025. paramWidget.midiControlChanged.connect(self.slot_parameterMidiControlChanged)
  1026. paramWidget.midiChannelChanged.connect(self.slot_parameterMidiChannelChanged)
  1027. tabPageLayout.addStretch()
  1028. self.ui.tabWidget.addTab(tabPageContainer, "%s (%i)" % (tabPageName, i))
  1029. i += 1
  1030. if isInput:
  1031. self.ui.tabWidget.setTabIcon(tabIndex, self.fTabIconOff)
  1032. self.fTabIconTimers.append(ICON_STATE_NULL)
  1033. def _updateCtrlMidiProgram(self):
  1034. if self.fPluginInfo['type'] not in (PLUGIN_INTERNAL, PLUGIN_SF2):
  1035. return
  1036. elif self.fPluginInfo['category'] != PLUGIN_CATEGORY_SYNTH:
  1037. return
  1038. if self.fControlChannel < 0:
  1039. self.ui.cb_midi_programs.setEnabled(False)
  1040. return
  1041. self.ui.cb_midi_programs.setEnabled(True)
  1042. mpIndex = Carla.host.get_current_midi_program_index(self.fPluginId)
  1043. if self.ui.cb_midi_programs.currentIndex() != mpIndex:
  1044. self.setMidiProgram(mpIndex)
  1045. def showEvent(self, event):
  1046. if not self.fScrollAreaSetup:
  1047. self.fScrollAreaSetup = True
  1048. minHeight = self.ui.scrollArea.height()+2
  1049. self.ui.scrollArea.setMinimumHeight(minHeight)
  1050. self.ui.scrollArea.setMaximumHeight(minHeight)
  1051. QDialog.showEvent(self, event)
  1052. def done(self, r):
  1053. QDialog.done(self, r)
  1054. self.close()
  1055. # ------------------------------------------------------------------------------------------------------------
  1056. # Plugin Widget
  1057. class PluginWidget(QFrame):
  1058. def __init__(self, parent, pluginId):
  1059. QFrame.__init__(self, parent)
  1060. self.ui = ui_carla_plugin.Ui_PluginWidget()
  1061. self.ui.setupUi(self)
  1062. # -------------------------------------------------------------
  1063. # Internal stuff
  1064. self.fPluginId = pluginId
  1065. self.fPluginInfo = Carla.host.get_plugin_info(self.fPluginId) if Carla.host is not None else gFakePluginInfo
  1066. self.fPluginInfo['binary'] = charPtrToString(self.fPluginInfo['binary'])
  1067. self.fPluginInfo['name'] = charPtrToString(self.fPluginInfo['name'])
  1068. self.fPluginInfo['label'] = charPtrToString(self.fPluginInfo['label'])
  1069. self.fPluginInfo['maker'] = charPtrToString(self.fPluginInfo['maker'])
  1070. self.fPluginInfo['copyright'] = charPtrToString(self.fPluginInfo['copyright'])
  1071. self.fPluginInfo['iconName'] = charPtrToString(self.fPluginInfo['iconName'])
  1072. if not Carla.isLocal:
  1073. self.fPluginInfo['hints'] &= ~PLUGIN_HAS_CUSTOM_UI
  1074. self.fLastGreenLedState = False
  1075. self.fLastBlueLedState = False
  1076. self.fParameterIconTimer = ICON_STATE_NULL
  1077. if Carla.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK or Carla.host is None:
  1078. self.fPeaksInputCount = 2
  1079. self.fPeaksOutputCount = 2
  1080. else:
  1081. audioCountInfo = Carla.host.get_audio_port_count_info(self.fPluginId)
  1082. self.fPeaksInputCount = int(audioCountInfo['ins'])
  1083. self.fPeaksOutputCount = int(audioCountInfo['outs'])
  1084. if self.fPeaksInputCount > 2:
  1085. self.fPeaksInputCount = 2
  1086. if self.fPeaksOutputCount > 2:
  1087. self.fPeaksOutputCount = 2
  1088. if self.palette().window().color().lightness() > 100:
  1089. # Light background
  1090. labelColor = "333"
  1091. isLight = True
  1092. self.fColorTop = QColor(60, 60, 60)
  1093. self.fColorBottom = QColor(47, 47, 47)
  1094. self.fColorSeprtr = QColor(70, 70, 70)
  1095. else:
  1096. # Dark background
  1097. labelColor = "BBB"
  1098. isLight = False
  1099. self.fColorTop = QColor(60, 60, 60)
  1100. self.fColorBottom = QColor(47, 47, 47)
  1101. self.fColorSeprtr = QColor(70, 70, 70)
  1102. # -------------------------------------------------------------
  1103. # Set-up GUI
  1104. self.setStyleSheet("""
  1105. QLabel#label_name {
  1106. color: #%s;
  1107. }""" % labelColor)
  1108. if isLight:
  1109. self.ui.b_enable.setPixmaps(":/bitmaps/button_off2.png", ":/bitmaps/button_on2.png", ":/bitmaps/button_off2.png")
  1110. self.ui.b_edit.setPixmaps(":/bitmaps/button_edit2.png", ":/bitmaps/button_edit_down2.png", ":/bitmaps/button_edit_hover2.png")
  1111. if self.fPluginInfo['iconName'] == "distrho":
  1112. self.ui.b_gui.setPixmaps(":/bitmaps/button_distrho2.png", ":/bitmaps/button_distrho_down2.png", ":/bitmaps/button_distrho_hover2.png")
  1113. elif self.fPluginInfo['iconName'] == "file":
  1114. self.ui.b_gui.setPixmaps(":/bitmaps/button_file2.png", ":/bitmaps/button_file_down2.png", ":/bitmaps/button_file_hover2.png")
  1115. else:
  1116. self.ui.b_gui.setPixmaps(":/bitmaps/button_gui2.png", ":/bitmaps/button_gui_down2.png", ":/bitmaps/button_gui_hover2.png")
  1117. else:
  1118. self.ui.b_enable.setPixmaps(":/bitmaps/button_off.png", ":/bitmaps/button_on.png", ":/bitmaps/button_off.png")
  1119. self.ui.b_edit.setPixmaps(":/bitmaps/button_edit.png", ":/bitmaps/button_edit_down.png", ":/bitmaps/button_edit_hover.png")
  1120. if self.fPluginInfo['iconName'] == "distrho":
  1121. self.ui.b_gui.setPixmaps(":/bitmaps/button_distrho.png", ":/bitmaps/button_distrho_down.png", ":/bitmaps/button_distrho_hover.png")
  1122. elif self.fPluginInfo['iconName'] == "file":
  1123. self.ui.b_gui.setPixmaps(":/bitmaps/button_file.png", ":/bitmaps/button_file_down.png", ":/bitmaps/button_file_hover.png")
  1124. else:
  1125. self.ui.b_gui.setPixmaps(":/bitmaps/button_gui.png", ":/bitmaps/button_gui_down.png", ":/bitmaps/button_gui_hover.png")
  1126. self.ui.led_control.setColor(self.ui.led_control.YELLOW)
  1127. self.ui.led_control.setEnabled(False)
  1128. self.ui.led_midi.setColor(self.ui.led_midi.RED)
  1129. self.ui.led_midi.setEnabled(False)
  1130. self.ui.led_audio_in.setColor(self.ui.led_audio_in.GREEN)
  1131. self.ui.led_audio_in.setEnabled(False)
  1132. self.ui.led_audio_out.setColor(self.ui.led_audio_out.BLUE)
  1133. self.ui.led_audio_out.setEnabled(False)
  1134. self.ui.peak_in.setColor(self.ui.peak_in.GREEN)
  1135. self.ui.peak_in.setChannels(self.fPeaksInputCount)
  1136. self.ui.peak_in.setOrientation(self.ui.peak_in.HORIZONTAL)
  1137. self.ui.peak_out.setColor(self.ui.peak_in.BLUE)
  1138. self.ui.peak_out.setChannels(self.fPeaksOutputCount)
  1139. self.ui.peak_out.setOrientation(self.ui.peak_out.HORIZONTAL)
  1140. self.ui.label_name.setText(self.fPluginInfo['name'])
  1141. self.ui.edit_dialog = PluginEdit(self, self.fPluginId)
  1142. self.ui.edit_dialog.hide()
  1143. self.setFixedHeight(32)
  1144. # -------------------------------------------------------------
  1145. # Set-up connections
  1146. self.customContextMenuRequested.connect(self.slot_showCustomMenu)
  1147. self.ui.b_enable.clicked.connect(self.slot_enableClicked)
  1148. self.ui.b_gui.clicked.connect(self.slot_guiClicked)
  1149. self.ui.b_edit.clicked.connect(self.slot_editClicked)
  1150. # -------------------------------------------------------------
  1151. def idleFast(self):
  1152. # Input peaks
  1153. if self.fPeaksInputCount > 0:
  1154. if self.fPeaksInputCount > 1:
  1155. peak1 = Carla.host.get_input_peak_value(self.fPluginId, 1)
  1156. peak2 = Carla.host.get_input_peak_value(self.fPluginId, 2)
  1157. ledState = bool(peak1 != 0.0 or peak2 != 0.0)
  1158. self.ui.peak_in.displayMeter(1, peak1)
  1159. self.ui.peak_in.displayMeter(2, peak2)
  1160. else:
  1161. peak = Carla.host.get_input_peak_value(self.fPluginId, 1)
  1162. ledState = bool(peak != 0.0)
  1163. self.ui.peak_in.displayMeter(1, peak)
  1164. if self.fLastGreenLedState != ledState:
  1165. self.fLastGreenLedState = ledState
  1166. self.ui.led_audio_in.setChecked(ledState)
  1167. # Output peaks
  1168. if self.fPeaksOutputCount > 0:
  1169. if self.fPeaksOutputCount > 1:
  1170. peak1 = Carla.host.get_output_peak_value(self.fPluginId, 1)
  1171. peak2 = Carla.host.get_output_peak_value(self.fPluginId, 2)
  1172. ledState = bool(peak1 != 0.0 or peak2 != 0.0)
  1173. self.ui.peak_out.displayMeter(1, peak1)
  1174. self.ui.peak_out.displayMeter(2, peak2)
  1175. else:
  1176. peak = Carla.host.get_output_peak_value(self.fPluginId, 1)
  1177. ledState = bool(peak != 0.0)
  1178. self.ui.peak_out.displayMeter(1, peak)
  1179. if self.fLastBlueLedState != ledState:
  1180. self.fLastBlueLedState = ledState
  1181. self.ui.led_audio_out.setChecked(ledState)
  1182. def idleSlow(self):
  1183. # Parameter Activity LED
  1184. if self.fParameterIconTimer == ICON_STATE_ON:
  1185. self.fParameterIconTimer = ICON_STATE_WAIT
  1186. self.ui.led_control.setChecked(True)
  1187. elif self.fParameterIconTimer == ICON_STATE_WAIT:
  1188. self.fParameterIconTimer = ICON_STATE_OFF
  1189. elif self.fParameterIconTimer == ICON_STATE_OFF:
  1190. self.fParameterIconTimer = ICON_STATE_NULL
  1191. self.ui.led_control.setChecked(False)
  1192. # Update edit dialog
  1193. self.ui.edit_dialog.idleSlow()
  1194. def editClosed(self):
  1195. self.ui.b_edit.setChecked(False)
  1196. def recheckPluginHints(self, hints):
  1197. self.fPluginInfo['hints'] = hints
  1198. self.ui.b_gui.setEnabled(hints & PLUGIN_HAS_CUSTOM_UI)
  1199. def setActive(self, active, sendGui=False, sendCallback=True):
  1200. if sendGui: self.ui.b_enable.setChecked(active)
  1201. if sendCallback: Carla.host.set_active(self.fPluginId, active)
  1202. if active:
  1203. self.ui.edit_dialog.clearNotes()
  1204. self.ui.led_midi.setChecked(False)
  1205. def setParameterValue(self, parameterId, value):
  1206. self.fParameterIconTimer = ICON_STATE_ON
  1207. if parameterId == PARAMETER_ACTIVE:
  1208. return self.setActive(bool(value), True, False)
  1209. self.ui.edit_dialog.setParameterValue(parameterId, value)
  1210. def setParameterDefault(self, parameterId, value):
  1211. self.ui.edit_dialog.setParameterDefault(parameterId, value)
  1212. def setParameterMidiControl(self, parameterId, control):
  1213. self.ui.edit_dialog.setParameterMidiControl(parameterId, control)
  1214. def setParameterMidiChannel(self, parameterId, channel):
  1215. self.ui.edit_dialog.setParameterMidiChannel(parameterId, channel)
  1216. def setProgram(self, index):
  1217. self.fParameterIconTimer = ICON_STATE_ON
  1218. self.ui.edit_dialog.setProgram(index)
  1219. def setMidiProgram(self, index):
  1220. self.fParameterIconTimer = ICON_STATE_ON
  1221. self.ui.edit_dialog.setMidiProgram(index)
  1222. def sendNoteOn(self, channel, note):
  1223. if self.ui.edit_dialog.sendNoteOn(channel, note):
  1224. self.ui.led_midi.setChecked(True)
  1225. def sendNoteOff(self, channel, note):
  1226. if self.ui.edit_dialog.sendNoteOff(channel, note):
  1227. self.ui.led_midi.setChecked(False)
  1228. def setId(self, idx):
  1229. self.fPluginId = idx
  1230. self.ui.edit_dialog.fPluginId = idx
  1231. @pyqtSlot()
  1232. def slot_showCustomMenu(self):
  1233. menu = QMenu(self)
  1234. actActive = menu.addAction(self.tr("Disable") if self.ui.b_enable.isChecked() else self.tr("Enable"))
  1235. menu.addSeparator()
  1236. actGui = menu.addAction(self.tr("Show GUI"))
  1237. actGui.setCheckable(True)
  1238. actGui.setChecked(self.ui.b_gui.isChecked())
  1239. actGui.setEnabled(self.ui.b_gui.isEnabled())
  1240. actEdit = menu.addAction(self.tr("Edit"))
  1241. actEdit.setCheckable(True)
  1242. actEdit.setChecked(self.ui.b_edit.isChecked())
  1243. menu.addSeparator()
  1244. actClone = menu.addAction(self.tr("Clone"))
  1245. actRename = menu.addAction(self.tr("Rename..."))
  1246. actRemove = menu.addAction(self.tr("Remove"))
  1247. actSel = menu.exec_(QCursor.pos())
  1248. if not actSel:
  1249. return
  1250. if actSel == actActive:
  1251. self.setActive(not self.ui.b_enable.isChecked(), True, True)
  1252. elif actSel == actGui:
  1253. self.ui.b_gui.click()
  1254. elif actSel == actEdit:
  1255. self.ui.b_edit.click()
  1256. elif actSel == actClone:
  1257. if not Carla.host.clone_plugin(self.fPluginId):
  1258. CustomMessageBox(self, QMessageBox.Warning, self.tr("Error"), self.tr("Operation failed"),
  1259. Carla.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok)
  1260. elif actSel == actRename:
  1261. oldName = self.fPluginInfo['name']
  1262. newNameTry = QInputDialog.getText(self, self.tr("Rename Plugin"), self.tr("New plugin name:"), QLineEdit.Normal, oldName)
  1263. if not (newNameTry[1] and newNameTry[0] and oldName != newNameTry[0]):
  1264. return
  1265. newName = newNameTry[0]
  1266. if Carla.host is None or Carla.host.rename_plugin(self.fPluginId, newName):
  1267. self.fPluginInfo['name'] = newName
  1268. self.ui.edit_dialog.fPluginInfo['name'] = newName
  1269. self.ui.edit_dialog.reloadInfo()
  1270. self.ui.label_name.setText(newName)
  1271. else:
  1272. CustomMessageBox(self, QMessageBox.Warning, self.tr("Error"), self.tr("Operation failed"),
  1273. Carla.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok)
  1274. elif actSel == actRemove:
  1275. if not Carla.host.remove_plugin(self.fPluginId):
  1276. CustomMessageBox(self, QMessageBox.Warning, self.tr("Error"), self.tr("Operation failed"),
  1277. Carla.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok)
  1278. @pyqtSlot(bool)
  1279. def slot_enableClicked(self, yesNo):
  1280. self.setActive(yesNo, False, True)
  1281. @pyqtSlot(bool)
  1282. def slot_guiClicked(self, show):
  1283. Carla.host.show_gui(self.fPluginId, show)
  1284. @pyqtSlot(bool)
  1285. def slot_editClicked(self, show):
  1286. self.ui.edit_dialog.setVisible(show)
  1287. def paintEvent(self, event):
  1288. painter = QPainter(self)
  1289. painter.save()
  1290. areaX = self.ui.area_right.x()+7
  1291. painter.setPen(self.fColorSeprtr.lighter(110))
  1292. painter.setBrush(self.fColorBottom)
  1293. painter.setRenderHint(QPainter.Antialiasing, True)
  1294. # name -> leds arc
  1295. path = QPainterPath()
  1296. path.moveTo(areaX-20, self.height()-4)
  1297. path.cubicTo(areaX, self.height()-5, areaX-20, 4.75, areaX, 4.75)
  1298. path.lineTo(areaX, self.height()-5)
  1299. painter.drawPath(path)
  1300. painter.setPen(self.fColorSeprtr)
  1301. painter.setRenderHint(QPainter.Antialiasing, False)
  1302. # separator lines
  1303. painter.drawLine(0, self.height()-5, areaX-20, self.height()-5)
  1304. painter.drawLine(areaX, 4, self.width(), 4)
  1305. painter.setPen(self.fColorBottom)
  1306. painter.setBrush(self.fColorBottom)
  1307. # top, bottom and left lines
  1308. painter.drawLine(0, 0, self.width(), 0)
  1309. painter.drawRect(0, self.height()-4, areaX, 4)
  1310. painter.drawRoundedRect(areaX-20, self.height()-5, areaX, 5, 22, 22)
  1311. painter.drawLine(0, 0, 0, self.height())
  1312. # fill the rest
  1313. painter.drawRect(areaX-1, 5, self.width(), self.height())
  1314. # bottom 1px line
  1315. painter.setPen(self.fColorSeprtr)
  1316. painter.drawLine(0, self.height()-1, self.width(), self.height()-1)
  1317. painter.restore()
  1318. QFrame.paintEvent(self, event)
  1319. # ------------------------------------------------------------------------------------------------------------
  1320. # Main
  1321. if __name__ == '__main__':
  1322. from carla_style import *
  1323. app = CarlaApplication()
  1324. #gui = CarlaAboutW(None)
  1325. #gui = PluginParameter(None, gFakeParamInfo, 0, 0)
  1326. gui = PluginEdit(None, 0)
  1327. #gui = PluginWidget(None, 0)
  1328. gui.show()
  1329. sys.exit(app.exec_())