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.

1863 lines
74KB

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