Update CI version
[carla.git] / source / frontend / carla_widgets.py
blob3766e8696b0ee79c4b86608cd5b8813ce493a703
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
4 # Carla widgets code
5 # Copyright (C) 2011-2022 Filipe Coelho <falktx@falktx.com>
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License as
9 # published by the Free Software Foundation; either version 2 of
10 # the License, or any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # For a full copy of the GNU General Public License see the doc/GPL.txt file.
19 # ------------------------------------------------------------------------------------------------------------
20 # Imports (Global)
22 from abc import abstractmethod
24 # ------------------------------------------------------------------------------------------------------------
25 # Imports (PyQt5)
27 from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray
28 from PyQt5.QtGui import QCursor, QIcon, QPalette, QPixmap
29 from PyQt5.QtWidgets import QDialog, QFileDialog, QInputDialog, QMenu, QMessageBox, QScrollArea, QVBoxLayout, QWidget
31 # ------------------------------------------------------------------------------------------------------------
32 # Imports (Custom)
34 import ui_carla_about
35 import ui_carla_edit
36 import ui_carla_parameter
38 from carla_backend import (
39 MACOS, WINDOWS,
40 BINARY_NATIVE,
41 PLUGIN_INTERNAL,
42 PLUGIN_DSSI,
43 PLUGIN_LV2,
44 PLUGIN_VST2,
45 PLUGIN_SF2,
46 PLUGIN_SFZ,
47 PLUGIN_CAN_DRYWET,
48 PLUGIN_CAN_VOLUME,
49 PLUGIN_CAN_BALANCE,
50 PLUGIN_CAN_PANNING,
51 PLUGIN_CATEGORY_SYNTH,
52 PLUGIN_OPTION_FIXED_BUFFERS,
53 PLUGIN_OPTION_FORCE_STEREO,
54 PLUGIN_OPTION_MAP_PROGRAM_CHANGES,
55 PLUGIN_OPTION_USE_CHUNKS,
56 PLUGIN_OPTION_SEND_CONTROL_CHANGES,
57 PLUGIN_OPTION_SEND_CHANNEL_PRESSURE,
58 PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH,
59 PLUGIN_OPTION_SEND_PITCHBEND,
60 PLUGIN_OPTION_SEND_ALL_SOUND_OFF,
61 PLUGIN_OPTION_SEND_PROGRAM_CHANGES,
62 PLUGIN_OPTION_SKIP_SENDING_NOTES,
63 PARAMETER_DRYWET,
64 PARAMETER_VOLUME,
65 PARAMETER_BALANCE_LEFT,
66 PARAMETER_BALANCE_RIGHT,
67 PARAMETER_PANNING,
68 PARAMETER_CTRL_CHANNEL,
69 PARAMETER_IS_ENABLED,
70 PARAMETER_IS_AUTOMATABLE,
71 PARAMETER_IS_READ_ONLY,
72 PARAMETER_USES_SCALEPOINTS,
73 PARAMETER_USES_CUSTOM_TEXT,
74 PARAMETER_CAN_BE_CV_CONTROLLED,
75 PARAMETER_INPUT, PARAMETER_OUTPUT,
76 CONTROL_INDEX_NONE,
77 CONTROL_INDEX_MIDI_PITCHBEND,
78 CONTROL_INDEX_MIDI_LEARN,
79 CONTROL_INDEX_CV
82 from carla_shared import (
83 MIDI_CC_LIST, MAX_MIDI_CC_LIST_ITEM,
84 VERSION,
85 countDecimalPoints,
86 fontMetricsHorizontalAdvance,
87 setUpSignals,
88 gCarla
91 from carla_utils import getPluginTypeAsString
94 from widgets.collapsablewidget import CollapsibleBox
95 from widgets.pixmapkeyboard import PixmapKeyboardHArea
97 # ------------------------------------------------------------------------------------------------------------
98 # Carla GUI defines
100 ICON_STATE_ON = 3 # turns on, sets as wait
101 ICON_STATE_WAIT = 2 # nothing, sets as off
102 ICON_STATE_OFF = 1 # turns off, sets as null
103 ICON_STATE_NULL = 0 # nothing
105 # ------------------------------------------------------------------------------------------------------------
106 # Carla About dialog
108 class CarlaAboutW(QDialog):
109 def __init__(self, parent, host):
110 QDialog.__init__(self, parent)
111 self.ui = ui_carla_about.Ui_CarlaAboutW()
112 self.ui.setupUi(self)
114 if host.isControl:
115 extraInfo = " - <b>%s</b>" % self.tr("OSC Bridge Version")
116 elif host.isPlugin:
117 extraInfo = " - <b>%s</b>" % self.tr("Plugin Version")
118 else:
119 extraInfo = ""
121 self.ui.l_about.setText(self.tr(""
122 "<br>Version %s"
123 "<br>Carla is a fully-featured audio plugin host%s.<br>"
124 "<br>Copyright (C) 2011-2022 falkTX<br>"
125 "" % (VERSION, extraInfo)))
127 if self.ui.about.palette().color(QPalette.Background).blackF() < 0.5:
128 self.ui.l_icons.setPixmap(QPixmap(":/bitmaps/carla_about_black.png"))
129 self.ui.ico_example_edit.setPixmap(QPixmap(":/bitmaps/button_file-black.png"))
130 self.ui.ico_example_file.setPixmap(QPixmap(":/scalable/button_edit-black.svg"))
131 self.ui.ico_example_gui.setPixmap(QPixmap(":/bitmaps/button_gui-black.png"))
133 if host.isControl:
134 self.ui.l_extended.hide()
135 self.ui.tabWidget.removeTab(3)
136 self.ui.tabWidget.removeTab(2)
138 self.ui.l_extended.setText(gCarla.utils.get_complete_license_text())
140 if host.is_engine_running() and not host.isControl:
141 self.ui.le_osc_url_tcp.setText(host.get_host_osc_url_tcp())
142 self.ui.le_osc_url_udp.setText(host.get_host_osc_url_udp())
143 else:
144 self.ui.le_osc_url_tcp.setText(self.tr("(Engine not running)"))
145 self.ui.le_osc_url_udp.setText(self.tr("(Engine not running)"))
147 # pylint: disable=line-too-long
148 self.ui.l_osc_cmds.setText("<table>"
149 "<tr><td>" "/set_active" "&nbsp;</td><td>&lt;i-value&gt;</td></tr>"
150 "<tr><td>" "/set_drywet" "&nbsp;</td><td>&lt;f-value&gt;</td></tr>"
151 "<tr><td>" "/set_volume" "&nbsp;</td><td>&lt;f-value&gt;</td></tr>"
152 "<tr><td>" "/set_balance_left" "&nbsp;</td><td>&lt;f-value&gt;</td></tr>"
153 "<tr><td>" "/set_balance_right" "&nbsp;</td><td>&lt;f-value&gt;</td></tr>"
154 "<tr><td>" "/set_panning" "&nbsp;</td><td>&lt;f-value&gt;</td></tr>"
155 "<tr><td>" "/set_parameter_value" "&nbsp;</td><td>&lt;i-index&gt; &lt;f-value&gt;</td></tr>"
156 "<tr><td>" "/set_parameter_midi_cc" "&nbsp;</td><td>&lt;i-index&gt; &lt;i-cc&gt;</td></tr>"
157 "<tr><td>" "/set_parameter_midi_channel" "&nbsp;</td><td>&lt;i-index&gt; &lt;i-channel&gt;</td></tr>"
158 "<tr><td>" "/set_program" "&nbsp;</td><td>&lt;i-index&gt;</td></tr>"
159 "<tr><td>" "/set_midi_program" "&nbsp;</td><td>&lt;i-index&gt;</td></tr>"
160 "<tr><td>" "/note_on" "&nbsp;</td><td>&lt;i-channel&gt; &lt;i-note&gt; &lt;i-velo&gt;</td></tr>"
161 "<tr><td>" "/note_off" "&nbsp;</td><td>&lt;i-channel&gt; &lt;i-note</td></tr>"
162 "</table>"
165 self.ui.l_example.setText("/Carla/2/set_parameter_value 5 1.0")
166 self.ui.l_example_help.setText("<i>(as in this example, \"2\" is the plugin number and \"5\" the parameter)</i>")
167 # pylint: enable=line-too-long
169 self.ui.l_ladspa.setText(self.tr("Everything! (Including LRDF)"))
170 self.ui.l_dssi.setText(self.tr("Everything! (Including CustomData/Chunks)"))
171 self.ui.l_lv2.setText(self.tr("About 110&#37; complete (using custom extensions)<br/>"
172 "Implemented Feature/Extensions:"
173 "<ul>"
174 "<li>http://lv2plug.in/ns/ext/atom</li>"
175 "<li>http://lv2plug.in/ns/ext/buf-size</li>"
176 "<li>http://lv2plug.in/ns/ext/data-access</li>"
177 #"<li>http://lv2plug.in/ns/ext/dynmanifest</li>"
178 "<li>http://lv2plug.in/ns/ext/event</li>"
179 "<li>http://lv2plug.in/ns/ext/instance-access</li>"
180 "<li>http://lv2plug.in/ns/ext/log</li>"
181 "<li>http://lv2plug.in/ns/ext/midi</li>"
182 #"<li>http://lv2plug.in/ns/ext/morph</li>"
183 "<li>http://lv2plug.in/ns/ext/options</li>"
184 "<li>http://lv2plug.in/ns/ext/parameters</li>"
185 #"<li>http://lv2plug.in/ns/ext/patch</li>"
186 "<li>http://lv2plug.in/ns/ext/port-props</li>"
187 "<li>http://lv2plug.in/ns/ext/presets</li>"
188 "<li>http://lv2plug.in/ns/ext/resize-port</li>"
189 "<li>http://lv2plug.in/ns/ext/state</li>"
190 "<li>http://lv2plug.in/ns/ext/time</li>"
191 "<li>http://lv2plug.in/ns/ext/uri-map</li>"
192 "<li>http://lv2plug.in/ns/ext/urid</li>"
193 "<li>http://lv2plug.in/ns/ext/worker</li>"
194 "<li>http://lv2plug.in/ns/extensions/ui</li>"
195 "<li>http://lv2plug.in/ns/extensions/units</li>"
196 "<li>http://home.gna.org/lv2dynparam/rtmempool/v1</li>"
197 "<li>http://kxstudio.sf.net/ns/lv2ext/external-ui</li>"
198 "<li>http://kxstudio.sf.net/ns/lv2ext/programs</li>"
199 "<li>http://kxstudio.sf.net/ns/lv2ext/props</li>"
200 "<li>http://kxstudio.sf.net/ns/lv2ext/rtmempool</li>"
201 "<li>http://ll-plugins.nongnu.org/lv2/ext/midimap</li>"
202 "<li>http://ll-plugins.nongnu.org/lv2/ext/miditype</li>"
203 "</ul>"))
205 usingJuce = "juce" in gCarla.utils.get_supported_features()
207 if usingJuce and (MACOS or WINDOWS):
208 self.ui.l_vst2.setText(self.tr("Using JUCE host"))
209 else:
210 self.ui.l_vst2.setText(self.tr("About 85&#37; complete (missing vst bank/presets and some minor stuff)"))
212 if usingJuce:
213 self.ui.l_vst3.setText(self.tr("Using JUCE host"))
215 if MACOS:
216 self.ui.l_au.setText(self.tr("Using JUCE host"))
217 else:
218 self.ui.line_vst3.hide()
219 self.ui.l_au.hide()
220 self.ui.lid_au.hide()
222 # 3rd tab is usually longer than the 1st
223 # adjust appropriately
224 self.ui.tabWidget.setCurrentIndex(2)
225 self.adjustSize()
226 self.ui.tabWidget.setCurrentIndex(0)
228 self.setFixedSize(self.size())
230 flags = self.windowFlags()
231 flags &= ~Qt.WindowContextHelpButtonHint
233 if WINDOWS:
234 flags |= Qt.MSWindowsFixedSizeDialogHint
236 self.setWindowFlags(flags)
238 if MACOS:
239 self.setWindowModality(Qt.WindowModal)
241 # ------------------------------------------------------------------------------------------------------------
242 # Plugin Parameter
244 class PluginParameter(QWidget):
245 mappedControlChanged = pyqtSignal(int, int)
246 mappedRangeChanged = pyqtSignal(int, float, float)
247 midiChannelChanged = pyqtSignal(int, int)
248 valueChanged = pyqtSignal(int, float)
250 def __init__(self, parent, host, pInfo, pluginId, tabIndex):
251 QWidget.__init__(self, parent)
252 self.host = host
253 self.ui = ui_carla_parameter.Ui_PluginParameter()
254 self.ui.setupUi(self)
256 # -------------------------------------------------------------
257 # Internal stuff
259 self.fDecimalPoints = max(2, countDecimalPoints(pInfo['step'], pInfo['stepSmall']))
260 self.fCanBeInCV = pInfo['hints'] & PARAMETER_CAN_BE_CV_CONTROLLED
261 self.fMappedCtrl = pInfo['mappedControlIndex']
262 self.fMappedMinimum = pInfo['mappedMinimum']
263 self.fMappedMaximum = pInfo['mappedMaximum']
264 self.fMinimum = pInfo['minimum']
265 self.fMaximum = pInfo['maximum']
266 self.fMidiChannel = pInfo['midiChannel']
267 self.fParameterId = pInfo['index']
268 self.fPluginId = pluginId
269 self.fTabIndex = tabIndex
271 # -------------------------------------------------------------
272 # Set-up GUI
274 pType = pInfo['type']
275 pHints = pInfo['hints']
277 self.ui.l_name.setText(pInfo['name'])
278 self.ui.widget.setName(pInfo['name'])
279 self.ui.widget.setMinimum(pInfo['minimum'])
280 self.ui.widget.setMaximum(pInfo['maximum'])
281 self.ui.widget.setDefault(pInfo['default'])
282 self.ui.widget.setLabel(pInfo['unit'])
283 self.ui.widget.setStep(pInfo['step'])
284 self.ui.widget.setStepSmall(pInfo['stepSmall'])
285 self.ui.widget.setStepLarge(pInfo['stepLarge'])
286 self.ui.widget.setScalePoints(pInfo['scalePoints'], bool(pHints & PARAMETER_USES_SCALEPOINTS))
288 if pInfo['comment']:
289 self.ui.l_name.setToolTip(pInfo['comment'])
290 self.ui.widget.setToolTip(pInfo['comment'])
292 if pType == PARAMETER_INPUT:
293 if not pHints & PARAMETER_IS_ENABLED:
294 self.ui.l_name.setEnabled(False)
295 self.ui.l_status.setEnabled(False)
296 self.ui.widget.setEnabled(False)
297 self.ui.widget.setReadOnly(True)
298 self.ui.tb_options.setEnabled(False)
300 elif not pHints & PARAMETER_IS_AUTOMATABLE:
301 self.ui.l_status.setEnabled(False)
302 self.ui.tb_options.setEnabled(False)
304 if pHints & PARAMETER_IS_READ_ONLY:
305 self.ui.l_status.setEnabled(False)
306 self.ui.widget.setReadOnly(True)
307 self.ui.tb_options.setEnabled(False)
309 elif pType == PARAMETER_OUTPUT:
310 self.ui.widget.setReadOnly(True)
312 else:
313 self.ui.l_status.setVisible(False)
314 self.ui.widget.setVisible(False)
315 self.ui.tb_options.setVisible(False)
317 # Only set value after all hints are handled
318 self.ui.widget.setValue(pInfo['current'])
320 if pHints & PARAMETER_USES_CUSTOM_TEXT and not host.isPlugin:
321 self.ui.widget.setTextCallback(self._textCallBack)
323 self.ui.l_status.setFixedWidth(fontMetricsHorizontalAdvance(self.ui.l_status.fontMetrics(),
324 self.tr("CC%i Ch%i" % (119,16))))
326 self.ui.widget.setValueCallback(self._valueCallBack)
327 self.ui.widget.updateAll()
329 self.setMappedControlIndex(pInfo['mappedControlIndex'])
330 self.setMidiChannel(pInfo['midiChannel'])
331 self.updateStatusLabel()
333 # -------------------------------------------------------------
334 # Set-up connections
336 self.ui.tb_options.clicked.connect(self.slot_optionsCustomMenu)
337 self.ui.widget.dragStateChanged.connect(self.slot_parameterDragStateChanged)
339 # -------------------------------------------------------------
341 def getPluginId(self):
342 return self.fPluginId
344 def getTabIndex(self):
345 return self.fTabIndex
347 def setPluginId(self, pluginId):
348 self.fPluginId = pluginId
350 def setDefault(self, value):
351 self.ui.widget.setDefault(value)
353 def setValue(self, value):
354 self.ui.widget.blockSignals(True)
355 self.ui.widget.setValue(value)
356 self.ui.widget.blockSignals(False)
358 def setMappedControlIndex(self, control):
359 self.fMappedCtrl = control
360 self.updateStatusLabel()
362 def setMappedRange(self, minimum, maximum):
363 self.fMappedMinimum = minimum
364 self.fMappedMaximum = maximum
366 def setMidiChannel(self, channel):
367 self.fMidiChannel = channel
368 self.updateStatusLabel()
370 def setLabelWidth(self, width):
371 self.ui.l_name.setFixedWidth(width)
373 def updateStatusLabel(self):
374 if self.fMappedCtrl == CONTROL_INDEX_NONE:
375 text = self.tr("Unmapped")
376 elif self.fMappedCtrl == CONTROL_INDEX_CV:
377 text = self.tr("CV export")
378 elif self.fMappedCtrl == CONTROL_INDEX_MIDI_PITCHBEND:
379 text = self.tr("PBend Ch%i" % (self.fMidiChannel,))
380 elif self.fMappedCtrl == CONTROL_INDEX_MIDI_LEARN:
381 text = self.tr("MIDI Learn")
382 else:
383 text = self.tr("CC%i Ch%i" % (self.fMappedCtrl, self.fMidiChannel))
385 self.ui.l_status.setText(text)
387 @pyqtSlot()
388 def slot_optionsCustomMenu(self):
389 menu = QMenu(self)
391 if self.fMappedCtrl == CONTROL_INDEX_NONE:
392 title = self.tr("Unmapped")
393 elif self.fMappedCtrl == CONTROL_INDEX_CV:
394 title = self.tr("Exposed as CV port")
395 elif self.fMappedCtrl == CONTROL_INDEX_MIDI_PITCHBEND:
396 title = self.tr("Mapped to MIDI Pitchbend, channel %i" % (self.fMidiChannel,))
397 elif self.fMappedCtrl == CONTROL_INDEX_MIDI_LEARN:
398 title = self.tr("MIDI Learn active")
399 else:
400 title = self.tr("Mapped to MIDI control %i, channel %i" % (self.fMappedCtrl, self.fMidiChannel))
402 if self.fMappedCtrl != CONTROL_INDEX_NONE:
403 title += " (range: %g-%g)" % (self.fMappedMinimum, self.fMappedMaximum)
405 actTitle = menu.addAction(title)
406 actTitle.setEnabled(False)
408 menu.addSeparator()
410 actUnmap = menu.addAction(self.tr("Unmap"))
412 if self.fMappedCtrl == CONTROL_INDEX_NONE:
413 actUnmap.setCheckable(True)
414 actUnmap.setChecked(True)
416 if self.fCanBeInCV:
417 menu.addSection("CV")
418 actCV = menu.addAction(self.tr("Expose as CV port"))
419 if self.fMappedCtrl == CONTROL_INDEX_CV:
420 actCV.setCheckable(True)
421 actCV.setChecked(True)
422 else:
423 actCV = None
425 menu.addSection("MIDI")
427 if not self.ui.widget.isReadOnly():
428 actLearn = menu.addAction(self.tr("MIDI Learn"))
430 if self.fMappedCtrl == CONTROL_INDEX_MIDI_LEARN:
431 actLearn.setCheckable(True)
432 actLearn.setChecked(True)
433 else:
434 actLearn = None
436 menuMIDI = menu.addMenu(self.tr("MIDI Control"))
438 if self.fMappedCtrl not in (CONTROL_INDEX_NONE,
439 CONTROL_INDEX_CV,
440 CONTROL_INDEX_MIDI_PITCHBEND,
441 CONTROL_INDEX_MIDI_LEARN):
442 action = menuMIDI.menuAction()
443 action.setCheckable(True)
444 action.setChecked(True)
446 inlist = False
447 actCCs = []
448 for cc in MIDI_CC_LIST:
449 action = menuMIDI.addAction(cc)
450 actCCs.append(action)
452 if self.fMappedCtrl >= 0 and self.fMappedCtrl <= MAX_MIDI_CC_LIST_ITEM:
453 ccx = int(cc.split(" [", 1)[0], 10)
455 if ccx > self.fMappedCtrl and not inlist:
456 inlist = True
457 action = menuMIDI.addAction(self.tr("%02i [0x%02X] (Custom)" % (self.fMappedCtrl,
458 self.fMappedCtrl)))
459 action.setCheckable(True)
460 action.setChecked(True)
461 actCCs.append(action)
463 elif ccx == self.fMappedCtrl:
464 inlist = True
465 action.setCheckable(True)
466 action.setChecked(True)
468 if self.fMappedCtrl > MAX_MIDI_CC_LIST_ITEM and self.fMappedCtrl <= 0x77:
469 action = menuMIDI.addAction(self.tr("%02i [0x%02X] (Custom)" % (self.fMappedCtrl, self.fMappedCtrl)))
470 action.setCheckable(True)
471 action.setChecked(True)
472 actCCs.append(action)
474 actCustomCC = menuMIDI.addAction(self.tr("Custom..."))
476 # TODO
477 #actPitchbend = menu.addAction(self.tr("MIDI Pitchbend"))
479 #if self.fMappedCtrl == CONTROL_INDEX_MIDI_PITCHBEND:
480 #actPitchbend.setCheckable(True)
481 #actPitchbend.setChecked(True)
483 menuChannel = menu.addMenu(self.tr("MIDI Channel"))
485 actChannels = []
486 for i in range(1, 16+1):
487 action = menuChannel.addAction("%i" % i)
488 actChannels.append(action)
490 if self.fMidiChannel == i:
491 action.setCheckable(True)
492 action.setChecked(True)
494 if self.fMappedCtrl != CONTROL_INDEX_NONE:
495 if self.fMappedCtrl == CONTROL_INDEX_CV:
496 menu.addSection("Range (Scaled CV input)")
497 else:
498 menu.addSection("Range (MIDI bounds)")
499 actRangeMinimum = menu.addAction(self.tr("Set minimum... (%g)" % self.fMappedMinimum))
500 actRangeMaximum = menu.addAction(self.tr("Set maximum... (%g)" % self.fMappedMaximum))
501 else:
502 actRangeMinimum = actRangeMaximum = None
504 actSel = menu.exec_(QCursor.pos())
506 if not actSel:
507 return
509 if actSel in actChannels:
510 channel = int(actSel.text())
511 self.fMidiChannel = channel
512 self.updateStatusLabel()
513 self.midiChannelChanged.emit(self.fParameterId, channel)
514 return
516 if actSel == actRangeMinimum:
517 value, ok = QInputDialog.getDouble(self,
518 self.tr("Custom Minimum"),
519 "Custom minimum value to use:",
520 self.fMappedMinimum,
521 self.fMinimum if self.fMappedCtrl != CONTROL_INDEX_CV else -9e6,
522 self.fMaximum if self.fMappedCtrl != CONTROL_INDEX_CV else 9e6,
523 self.fDecimalPoints)
524 if not ok:
525 return
527 self.fMappedMinimum = value
528 self.mappedRangeChanged.emit(self.fParameterId, self.fMappedMinimum, self.fMappedMaximum)
529 return
531 if actSel == actRangeMaximum:
532 value, ok = QInputDialog.getDouble(self,
533 self.tr("Custom Maximum"),
534 "Custom maximum value to use:",
535 self.fMappedMaximum,
536 self.fMinimum if self.fMappedCtrl != CONTROL_INDEX_CV else -9e6,
537 self.fMaximum if self.fMappedCtrl != CONTROL_INDEX_CV else 9e6,
538 self.fDecimalPoints)
539 if not ok:
540 return
542 self.fMappedMaximum = value
543 self.mappedRangeChanged.emit(self.fParameterId, self.fMappedMinimum, self.fMappedMaximum)
544 return
546 if actSel == actUnmap:
547 ctrl = CONTROL_INDEX_NONE
548 elif actSel == actCV:
549 ctrl = CONTROL_INDEX_CV
550 elif actSel == actCustomCC:
551 value = self.fMappedCtrl if self.fMappedCtrl >= 0x01 and self.fMappedCtrl <= 0x77 else 1
552 ctrl, ok = QInputDialog.getInt(self,
553 self.tr("Custom CC"),
554 "Custom MIDI CC to use:",
555 value,
556 0x01, 0x77, 1)
557 if not ok:
558 return
559 #elif actSel == actPitchbend:
560 #ctrl = CONTROL_INDEX_MIDI_PITCHBEND
561 elif actSel == actLearn:
562 ctrl = CONTROL_INDEX_MIDI_LEARN
563 elif actSel in actCCs:
564 ctrl = int(actSel.text().split(" ", 1)[0].replace("&",""), 10)
565 else:
566 return
568 self.fMappedCtrl = ctrl
569 self.updateStatusLabel()
570 self.mappedControlChanged.emit(self.fParameterId, ctrl)
572 @pyqtSlot(bool)
573 def slot_parameterDragStateChanged(self, touch):
574 self.host.set_parameter_touch(self.fPluginId, self.fParameterId, touch)
576 def _textCallBack(self):
577 return self.host.get_parameter_text(self.fPluginId, self.fParameterId)
579 def _valueCallBack(self, value):
580 self.valueChanged.emit(self.fParameterId, value)
582 # ------------------------------------------------------------------------------------------------------------
583 # Plugin Editor Parent (Meta class)
585 class PluginEditParentMeta():
586 #class PluginEditParentMeta(metaclass=ABCMeta):
587 @abstractmethod
588 def editDialogVisibilityChanged(self, pluginId, visible):
589 raise NotImplementedError
591 @abstractmethod
592 def editDialogPluginHintsChanged(self, pluginId, hints):
593 raise NotImplementedError
595 @abstractmethod
596 def editDialogParameterValueChanged(self, pluginId, parameterId, value):
597 raise NotImplementedError
599 @abstractmethod
600 def editDialogProgramChanged(self, pluginId, index):
601 raise NotImplementedError
603 @abstractmethod
604 def editDialogMidiProgramChanged(self, pluginId, index):
605 raise NotImplementedError
607 @abstractmethod
608 def editDialogNotePressed(self, pluginId, note):
609 raise NotImplementedError
611 @abstractmethod
612 def editDialogNoteReleased(self, pluginId, note):
613 raise NotImplementedError
615 @abstractmethod
616 def editDialogMidiActivityChanged(self, pluginId, onOff):
617 raise NotImplementedError
619 # ------------------------------------------------------------------------------------------------------------
620 # Plugin Editor (Built-in)
622 class PluginEdit(QDialog):
623 # signals
624 SIGTERM = pyqtSignal()
625 SIGUSR1 = pyqtSignal()
627 def __init__(self, parent, host, pluginId):
628 QDialog.__init__(self, parent.window() if parent is not None else None)
629 self.host = host
630 self.ui = ui_carla_edit.Ui_PluginEdit()
631 self.ui.setupUi(self)
633 # -------------------------------------------------------------
634 # Internal stuff
636 self.fGeometry = QByteArray()
637 self.fParent = parent
638 self.fPluginId = pluginId
639 self.fPluginInfo = None
641 self.fCurrentStateFilename = None
642 self.fControlChannel = round(host.get_internal_parameter_value(pluginId, PARAMETER_CTRL_CHANNEL))
643 self.fFirstInit = True
645 self.fParameterList = [] # (type, id, widget)
646 self.fParametersToUpdate = [] # (id, value)
648 self.fPlayingNotes = [] # (channel, note)
650 self.fTabIconOff = QIcon(":/scalable/led_off.svg")
651 self.fTabIconOn = QIcon(":/scalable/led_yellow.svg")
652 self.fTabIconTimers = []
654 # used during testing
655 self.fIdleTimerId = 0
657 # -------------------------------------------------------------
658 # Set-up GUI
660 labelPluginFont = self.ui.label_plugin.font()
661 labelPluginFont.setPixelSize(15)
662 labelPluginFont.setWeight(75)
663 self.ui.label_plugin.setFont(labelPluginFont)
665 self.ui.dial_drywet.setCustomPaintMode(self.ui.dial_drywet.CUSTOM_PAINT_MODE_CARLA_WET)
666 self.ui.dial_drywet.setImage(3)
667 self.ui.dial_drywet.setLabel("Dry/Wet")
668 self.ui.dial_drywet.setMinimum(0.0)
669 self.ui.dial_drywet.setMaximum(1.0)
670 self.ui.dial_drywet.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_DRYWET))
672 self.ui.dial_vol.setCustomPaintMode(self.ui.dial_vol.CUSTOM_PAINT_MODE_CARLA_VOL)
673 self.ui.dial_vol.setImage(3)
674 self.ui.dial_vol.setLabel("Volume")
675 self.ui.dial_vol.setMinimum(0.0)
676 self.ui.dial_vol.setMaximum(1.27)
677 self.ui.dial_vol.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_VOLUME))
679 self.ui.dial_b_left.setCustomPaintMode(self.ui.dial_b_left.CUSTOM_PAINT_MODE_CARLA_L)
680 self.ui.dial_b_left.setImage(4)
681 self.ui.dial_b_left.setLabel("L")
682 self.ui.dial_b_left.setMinimum(-1.0)
683 self.ui.dial_b_left.setMaximum(1.0)
684 self.ui.dial_b_left.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_LEFT))
686 self.ui.dial_b_right.setCustomPaintMode(self.ui.dial_b_right.CUSTOM_PAINT_MODE_CARLA_R)
687 self.ui.dial_b_right.setImage(4)
688 self.ui.dial_b_right.setLabel("R")
689 self.ui.dial_b_right.setMinimum(-1.0)
690 self.ui.dial_b_right.setMaximum(1.0)
691 self.ui.dial_b_right.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_RIGHT))
693 self.ui.dial_pan.setCustomPaintMode(self.ui.dial_b_right.CUSTOM_PAINT_MODE_CARLA_PAN)
694 self.ui.dial_pan.setImage(4)
695 self.ui.dial_pan.setLabel("Pan")
696 self.ui.dial_pan.setMinimum(-1.0)
697 self.ui.dial_pan.setMaximum(1.0)
698 self.ui.dial_pan.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_PANNING))
700 self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1)
702 self.ui.scrollArea = PixmapKeyboardHArea(self)
703 self.ui.keyboard = self.ui.scrollArea.keyboard
704 self.ui.keyboard.setEnabled(self.fControlChannel >= 0)
705 self.layout().addWidget(self.ui.scrollArea)
707 self.ui.scrollArea.setEnabled(False)
708 self.ui.scrollArea.setVisible(False)
710 # todo
711 self.ui.rb_balance.setEnabled(False)
712 self.ui.rb_balance.setVisible(False)
713 self.ui.rb_pan.setEnabled(False)
714 self.ui.rb_pan.setVisible(False)
716 flags = self.windowFlags()
717 flags &= ~Qt.WindowContextHelpButtonHint
718 self.setWindowFlags(flags)
720 self.reloadAll()
722 self.fFirstInit = False
724 # -------------------------------------------------------------
725 # Set-up connections
727 self.finished.connect(self.slot_finished)
729 self.ui.ch_fixed_buffer.clicked.connect(self.slot_optionChanged)
730 self.ui.ch_force_stereo.clicked.connect(self.slot_optionChanged)
731 self.ui.ch_map_program_changes.clicked.connect(self.slot_optionChanged)
732 self.ui.ch_use_chunks.clicked.connect(self.slot_optionChanged)
733 self.ui.ch_send_notes.clicked.connect(self.slot_optionChanged)
734 self.ui.ch_send_program_changes.clicked.connect(self.slot_optionChanged)
735 self.ui.ch_send_control_changes.clicked.connect(self.slot_optionChanged)
736 self.ui.ch_send_channel_pressure.clicked.connect(self.slot_optionChanged)
737 self.ui.ch_send_note_aftertouch.clicked.connect(self.slot_optionChanged)
738 self.ui.ch_send_pitchbend.clicked.connect(self.slot_optionChanged)
739 self.ui.ch_send_all_sound_off.clicked.connect(self.slot_optionChanged)
741 self.ui.dial_drywet.realValueChanged.connect(self.slot_dryWetChanged)
742 self.ui.dial_vol.realValueChanged.connect(self.slot_volumeChanged)
743 self.ui.dial_b_left.realValueChanged.connect(self.slot_balanceLeftChanged)
744 self.ui.dial_b_right.realValueChanged.connect(self.slot_balanceRightChanged)
745 self.ui.dial_pan.realValueChanged.connect(self.slot_panChanged)
746 self.ui.sb_ctrl_channel.valueChanged.connect(self.slot_ctrlChannelChanged)
748 self.ui.dial_drywet.customContextMenuRequested.connect(self.slot_knobCustomMenu)
749 self.ui.dial_vol.customContextMenuRequested.connect(self.slot_knobCustomMenu)
750 self.ui.dial_b_left.customContextMenuRequested.connect(self.slot_knobCustomMenu)
751 self.ui.dial_b_right.customContextMenuRequested.connect(self.slot_knobCustomMenu)
752 self.ui.dial_pan.customContextMenuRequested.connect(self.slot_knobCustomMenu)
753 self.ui.sb_ctrl_channel.customContextMenuRequested.connect(self.slot_channelCustomMenu)
755 self.ui.keyboard.noteOn.connect(self.slot_noteOn)
756 self.ui.keyboard.noteOff.connect(self.slot_noteOff)
758 self.ui.cb_programs.currentIndexChanged.connect(self.slot_programIndexChanged)
759 self.ui.cb_midi_programs.currentIndexChanged.connect(self.slot_midiProgramIndexChanged)
761 self.ui.b_save_state.clicked.connect(self.slot_stateSave)
762 self.ui.b_load_state.clicked.connect(self.slot_stateLoad)
764 host.NoteOnCallback.connect(self.slot_handleNoteOnCallback)
765 host.NoteOffCallback.connect(self.slot_handleNoteOffCallback)
766 host.UpdateCallback.connect(self.slot_handleUpdateCallback)
767 host.ReloadInfoCallback.connect(self.slot_handleReloadInfoCallback)
768 host.ReloadParametersCallback.connect(self.slot_handleReloadParametersCallback)
769 host.ReloadProgramsCallback.connect(self.slot_handleReloadProgramsCallback)
770 host.ReloadAllCallback.connect(self.slot_handleReloadAllCallback)
772 #------------------------------------------------------------------
774 @pyqtSlot(int, int, int, int)
775 def slot_handleNoteOnCallback(self, pluginId, channel, note, velocity):
776 if self.fPluginId != pluginId:
777 return
779 if self.fControlChannel == channel:
780 self.ui.keyboard.sendNoteOn(note, False)
782 playItem = (channel, note)
784 if playItem not in self.fPlayingNotes:
785 self.fPlayingNotes.append(playItem)
787 if len(self.fPlayingNotes) == 1 and self.fParent is not None:
788 self.fParent.editDialogMidiActivityChanged(self.fPluginId, True)
790 @pyqtSlot(int, int, int)
791 def slot_handleNoteOffCallback(self, pluginId, channel, note):
792 if self.fPluginId != pluginId:
793 return
795 if self.fControlChannel == channel:
796 self.ui.keyboard.sendNoteOff(note, False)
798 playItem = (channel, note)
800 if playItem in self.fPlayingNotes:
801 self.fPlayingNotes.remove(playItem)
803 if self.fPlayingNotes and self.fParent is not None:
804 self.fParent.editDialogMidiActivityChanged(self.fPluginId, False)
806 @pyqtSlot(int)
807 def slot_handleUpdateCallback(self, pluginId):
808 if self.fPluginId == pluginId:
809 self.updateInfo()
811 @pyqtSlot(int)
812 def slot_handleReloadInfoCallback(self, pluginId):
813 if self.fPluginId == pluginId:
814 self.reloadInfo()
816 @pyqtSlot(int)
817 def slot_handleReloadParametersCallback(self, pluginId):
818 if self.fPluginId == pluginId:
819 self.reloadParameters()
821 @pyqtSlot(int)
822 def slot_handleReloadProgramsCallback(self, pluginId):
823 if self.fPluginId == pluginId:
824 self.reloadPrograms()
826 @pyqtSlot(int)
827 def slot_handleReloadAllCallback(self, pluginId):
828 if self.fPluginId == pluginId:
829 self.reloadAll()
831 #------------------------------------------------------------------
833 def updateInfo(self):
834 # Update current program text
835 if self.ui.cb_programs.count() > 0:
836 pIndex = self.ui.cb_programs.currentIndex()
837 if pIndex >= 0:
838 pName = self.host.get_program_name(self.fPluginId, pIndex)
839 #pName = pName[:40] + (pName[40:] and "...")
840 self.ui.cb_programs.setItemText(pIndex, pName)
842 # Update current midi program text
843 if self.ui.cb_midi_programs.count() > 0:
844 mpIndex = self.ui.cb_midi_programs.currentIndex()
845 if mpIndex >= 0:
846 mpData = self.host.get_midi_program_data(self.fPluginId, mpIndex)
847 mpBank = mpData['bank']
848 mpProg = mpData['program']
849 mpName = mpData['name']
850 #mpName = mpName[:40] + (mpName[40:] and "...")
851 self.ui.cb_midi_programs.setItemText(mpIndex, "%03i:%03i - %s" % (mpBank+1, mpProg+1, mpName))
853 # Update all parameter values
854 for _, paramId, paramWidget in self.fParameterList:
855 paramWidget.blockSignals(True)
856 paramWidget.setValue(self.host.get_current_parameter_value(self.fPluginId, paramId))
857 paramWidget.blockSignals(False)
859 # and the internal ones too
860 self.ui.dial_drywet.blockSignals(True)
861 self.ui.dial_drywet.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_DRYWET))
862 self.ui.dial_drywet.blockSignals(False)
864 self.ui.dial_vol.blockSignals(True)
865 self.ui.dial_vol.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_VOLUME))
866 self.ui.dial_vol.blockSignals(False)
868 self.ui.dial_b_left.blockSignals(True)
869 self.ui.dial_b_left.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_BALANCE_LEFT))
870 self.ui.dial_b_left.blockSignals(False)
872 self.ui.dial_b_right.blockSignals(True)
873 self.ui.dial_b_right.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_BALANCE_RIGHT))
874 self.ui.dial_b_right.blockSignals(False)
876 self.ui.dial_pan.blockSignals(True)
877 self.ui.dial_pan.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_PANNING))
878 self.ui.dial_pan.blockSignals(False)
880 self.fControlChannel = round(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_CTRL_CHANNEL))
881 self.ui.sb_ctrl_channel.blockSignals(True)
882 self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1)
883 self.ui.sb_ctrl_channel.blockSignals(False)
884 self.ui.keyboard.allNotesOff()
885 self._updateCtrlPrograms()
887 self.fParametersToUpdate = []
889 #------------------------------------------------------------------
891 def reloadAll(self):
892 self.fPluginInfo = self.host.get_plugin_info(self.fPluginId)
894 self.reloadInfo()
895 self.reloadParameters()
896 self.reloadPrograms()
898 if self.fPluginInfo['type'] == PLUGIN_LV2:
899 self.ui.b_save_state.setEnabled(False)
901 if not self.ui.scrollArea.isEnabled():
902 self.resize(self.width(), self.height()-self.ui.scrollArea.height())
904 #------------------------------------------------------------------
906 def reloadInfo(self):
907 realPluginName = self.host.get_real_plugin_name(self.fPluginId)
908 #audioCountInfo = self.host.get_audio_port_count_info(self.fPluginId)
909 midiCountInfo = self.host.get_midi_port_count_info(self.fPluginId)
910 #paramCountInfo = self.host.get_parameter_count_info(self.fPluginId)
912 pluginHints = self.fPluginInfo['hints']
914 self.ui.le_type.setText(getPluginTypeAsString(self.fPluginInfo['type']))
916 self.ui.label_name.setEnabled(bool(realPluginName))
917 self.ui.le_name.setEnabled(bool(realPluginName))
918 self.ui.le_name.setText(realPluginName)
919 self.ui.le_name.setToolTip(realPluginName)
921 self.ui.label_label.setEnabled(bool(self.fPluginInfo['label']))
922 self.ui.le_label.setEnabled(bool(self.fPluginInfo['label']))
923 self.ui.le_label.setText(self.fPluginInfo['label'])
924 self.ui.le_label.setToolTip(self.fPluginInfo['label'])
926 self.ui.label_maker.setEnabled(bool(self.fPluginInfo['maker']))
927 self.ui.le_maker.setEnabled(bool(self.fPluginInfo['maker']))
928 self.ui.le_maker.setText(self.fPluginInfo['maker'])
929 self.ui.le_maker.setToolTip(self.fPluginInfo['maker'])
931 self.ui.label_copyright.setEnabled(bool(self.fPluginInfo['copyright']))
932 self.ui.le_copyright.setEnabled(bool(self.fPluginInfo['copyright']))
933 self.ui.le_copyright.setText(self.fPluginInfo['copyright'])
934 self.ui.le_copyright.setToolTip(self.fPluginInfo['copyright'])
936 self.ui.label_unique_id.setEnabled(bool(self.fPluginInfo['uniqueId']))
937 self.ui.le_unique_id.setEnabled(bool(self.fPluginInfo['uniqueId']))
938 self.ui.le_unique_id.setText(str(self.fPluginInfo['uniqueId']))
939 self.ui.le_unique_id.setToolTip(str(self.fPluginInfo['uniqueId']))
941 self.ui.label_plugin.setText("\n%s\n" % (self.fPluginInfo['name'] or "(none)"))
942 self.setWindowTitle(self.fPluginInfo['name'] or "(none)")
944 self.ui.dial_drywet.setEnabled(pluginHints & PLUGIN_CAN_DRYWET)
945 self.ui.dial_vol.setEnabled(pluginHints & PLUGIN_CAN_VOLUME)
946 self.ui.dial_b_left.setEnabled(pluginHints & PLUGIN_CAN_BALANCE)
947 self.ui.dial_b_right.setEnabled(pluginHints & PLUGIN_CAN_BALANCE)
948 self.ui.dial_pan.setEnabled(pluginHints & PLUGIN_CAN_PANNING)
950 optsAvailable = self.fPluginInfo['optionsAvailable']
951 optsEnabled = self.fPluginInfo['optionsEnabled']
952 self.ui.ch_use_chunks.setEnabled(optsAvailable & PLUGIN_OPTION_USE_CHUNKS)
953 self.ui.ch_use_chunks.setChecked(optsEnabled & PLUGIN_OPTION_USE_CHUNKS)
954 self.ui.ch_fixed_buffer.setEnabled(optsAvailable & PLUGIN_OPTION_FIXED_BUFFERS)
955 self.ui.ch_fixed_buffer.setChecked(optsEnabled & PLUGIN_OPTION_FIXED_BUFFERS)
956 self.ui.ch_force_stereo.setEnabled(optsAvailable & PLUGIN_OPTION_FORCE_STEREO)
957 self.ui.ch_force_stereo.setChecked(optsEnabled & PLUGIN_OPTION_FORCE_STEREO)
958 self.ui.ch_map_program_changes.setEnabled(optsAvailable & PLUGIN_OPTION_MAP_PROGRAM_CHANGES)
959 self.ui.ch_map_program_changes.setChecked(optsEnabled & PLUGIN_OPTION_MAP_PROGRAM_CHANGES)
960 self.ui.ch_send_notes.setEnabled(optsAvailable & PLUGIN_OPTION_SKIP_SENDING_NOTES)
961 self.ui.ch_send_notes.setChecked((self.ui.ch_send_notes.isEnabled() and
962 (optsEnabled & PLUGIN_OPTION_SKIP_SENDING_NOTES) == 0x0))
963 self.ui.ch_send_control_changes.setEnabled(optsAvailable & PLUGIN_OPTION_SEND_CONTROL_CHANGES)
964 self.ui.ch_send_control_changes.setChecked(optsEnabled & PLUGIN_OPTION_SEND_CONTROL_CHANGES)
965 self.ui.ch_send_channel_pressure.setEnabled(optsAvailable & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE)
966 self.ui.ch_send_channel_pressure.setChecked(optsEnabled & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE)
967 self.ui.ch_send_note_aftertouch.setEnabled(optsAvailable & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH)
968 self.ui.ch_send_note_aftertouch.setChecked(optsEnabled & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH)
969 self.ui.ch_send_pitchbend.setEnabled(optsAvailable & PLUGIN_OPTION_SEND_PITCHBEND)
970 self.ui.ch_send_pitchbend.setChecked(optsEnabled & PLUGIN_OPTION_SEND_PITCHBEND)
971 self.ui.ch_send_all_sound_off.setEnabled(optsAvailable & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
972 self.ui.ch_send_all_sound_off.setChecked(optsEnabled & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
974 canSendPrograms = bool((optsAvailable & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) != 0 and
975 (optsEnabled & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) == 0)
976 self.ui.ch_send_program_changes.setEnabled(canSendPrograms)
977 self.ui.ch_send_program_changes.setChecked(optsEnabled & PLUGIN_OPTION_SEND_PROGRAM_CHANGES)
979 self.ui.sw_programs.setCurrentIndex(0 if self.fPluginInfo['type'] in (PLUGIN_VST2, PLUGIN_SFZ) else 1)
981 # Show/hide keyboard
982 showKeyboard = (self.fPluginInfo['category'] == PLUGIN_CATEGORY_SYNTH or midiCountInfo['ins'] > 0)
983 self.ui.scrollArea.setEnabled(showKeyboard)
984 self.ui.scrollArea.setVisible(showKeyboard)
986 # Force-update parent for new hints
987 if self.fParent is not None and not self.fFirstInit:
988 self.fParent.editDialogPluginHintsChanged(self.fPluginId, pluginHints)
990 def reloadParameters(self):
991 # Reset
992 self.fParameterList = []
993 self.fParametersToUpdate = []
994 self.fTabIconTimers = []
996 # Save current tab state
997 tabIndex = self.ui.tabWidget.currentIndex()
998 tabWidget = self.ui.tabWidget.currentWidget()
999 scrollVal = tabWidget.verticalScrollBar().value() if isinstance(tabWidget, QScrollArea) else None
1000 del tabWidget
1002 # Remove all previous parameters
1003 for _ in range(self.ui.tabWidget.count()-1):
1004 self.ui.tabWidget.widget(1).deleteLater()
1005 self.ui.tabWidget.removeTab(1)
1007 parameterCount = self.host.get_parameter_count(self.fPluginId)
1009 # -----------------------------------------------------------------
1011 if parameterCount <= 0:
1012 return
1014 # -----------------------------------------------------------------
1016 paramInputList = []
1017 paramOutputList = []
1018 paramInputWidth = 0
1019 paramOutputWidth = 0
1020 unusedParameters = 0
1022 paramInputListFull = [] # ([params], width)
1023 paramOutputListFull = [] # ([params], width)
1025 for i in range(parameterCount):
1026 if i - unusedParameters == self.host.maxParameters:
1027 break
1029 paramData = self.host.get_parameter_data(self.fPluginId, i)
1031 if paramData['type'] not in (PARAMETER_INPUT, PARAMETER_OUTPUT):
1032 unusedParameters += 1
1033 continue
1034 if (paramData['hints'] & PARAMETER_IS_ENABLED) == 0:
1035 unusedParameters += 1
1036 continue
1038 paramInfo = self.host.get_parameter_info(self.fPluginId, i)
1039 paramRanges = self.host.get_parameter_ranges(self.fPluginId, i)
1040 paramValue = self.host.get_current_parameter_value(self.fPluginId, i)
1042 parameter = {
1043 'type': paramData['type'],
1044 'hints': paramData['hints'],
1045 'name': paramInfo['name'],
1046 'unit': paramInfo['unit'],
1047 'scalePoints': [],
1049 'index': paramData['index'],
1050 'default': paramRanges['def'],
1051 'minimum': paramRanges['min'],
1052 'maximum': paramRanges['max'],
1053 'step': paramRanges['step'],
1054 'stepSmall': paramRanges['stepSmall'],
1055 'stepLarge': paramRanges['stepLarge'],
1056 'mappedControlIndex': paramData['mappedControlIndex'],
1057 'mappedMinimum': paramData['mappedMinimum'],
1058 'mappedMaximum': paramData['mappedMaximum'],
1059 'midiChannel': paramData['midiChannel']+1,
1061 'comment': paramInfo['comment'],
1062 'groupName': paramInfo['groupName'],
1064 'current': paramValue
1067 for j in range(paramInfo['scalePointCount']):
1068 scalePointInfo = self.host.get_parameter_scalepoint_info(self.fPluginId, i, j)
1070 parameter['scalePoints'].append({
1071 'value': scalePointInfo['value'],
1072 'label': scalePointInfo['label']
1075 #parameter['name'] = parameter['name'][:30] + (parameter['name'][30:] and "...")
1077 # -----------------------------------------------------------------
1078 # Get width values, in packs of 20
1080 if parameter['type'] == PARAMETER_INPUT:
1081 paramInputWidthTMP = fontMetricsHorizontalAdvance(self.fontMetrics(), parameter['name'])
1083 if paramInputWidthTMP > paramInputWidth:
1084 paramInputWidth = paramInputWidthTMP
1086 paramInputList.append(parameter)
1088 else:
1089 paramOutputWidthTMP = fontMetricsHorizontalAdvance(self.fontMetrics(), parameter['name'])
1091 if paramOutputWidthTMP > paramOutputWidth:
1092 paramOutputWidth = paramOutputWidthTMP
1094 paramOutputList.append(parameter)
1096 paramInputListFull.append((paramInputList, paramInputWidth))
1097 paramOutputListFull.append((paramOutputList, paramOutputWidth))
1099 # Create parameter tabs + widgets
1100 self._createParameterWidgets(PARAMETER_INPUT, paramInputListFull, self.tr("Parameters"))
1101 self._createParameterWidgets(PARAMETER_OUTPUT, paramOutputListFull, self.tr("Outputs"))
1103 # Restore tab state
1104 if tabIndex < self.ui.tabWidget.count():
1105 self.ui.tabWidget.setCurrentIndex(tabIndex)
1106 if scrollVal is not None:
1107 self.ui.tabWidget.currentWidget().verticalScrollBar().setValue(scrollVal)
1109 def reloadPrograms(self):
1110 # Programs
1111 self.ui.cb_programs.blockSignals(True)
1112 self.ui.cb_programs.clear()
1114 programCount = self.host.get_program_count(self.fPluginId)
1116 if programCount > 0:
1117 self.ui.cb_programs.setEnabled(True)
1118 self.ui.label_programs.setEnabled(True)
1120 for i in range(programCount):
1121 pName = self.host.get_program_name(self.fPluginId, i)
1122 #pName = pName[:40] + (pName[40:] and "...")
1123 self.ui.cb_programs.addItem(pName)
1125 self.ui.cb_programs.setCurrentIndex(self.host.get_current_program_index(self.fPluginId))
1127 else:
1128 self.ui.cb_programs.setEnabled(False)
1129 self.ui.label_programs.setEnabled(False)
1131 self.ui.cb_programs.blockSignals(False)
1133 # MIDI Programs
1134 self.ui.cb_midi_programs.blockSignals(True)
1135 self.ui.cb_midi_programs.clear()
1137 midiProgramCount = self.host.get_midi_program_count(self.fPluginId)
1139 if midiProgramCount > 0:
1140 self.ui.cb_midi_programs.setEnabled(True)
1141 self.ui.label_midi_programs.setEnabled(True)
1143 for i in range(midiProgramCount):
1144 mpData = self.host.get_midi_program_data(self.fPluginId, i)
1145 mpBank = mpData['bank']
1146 mpProg = mpData['program']
1147 mpName = mpData['name']
1148 #mpName = mpName[:40] + (mpName[40:] and "...")
1150 self.ui.cb_midi_programs.addItem("%03i:%03i - %s" % (mpBank+1, mpProg+1, mpName))
1152 self.ui.cb_midi_programs.setCurrentIndex(self.host.get_current_midi_program_index(self.fPluginId))
1154 else:
1155 self.ui.cb_midi_programs.setEnabled(False)
1156 self.ui.label_midi_programs.setEnabled(False)
1158 self.ui.cb_midi_programs.blockSignals(False)
1160 self.ui.sw_programs.setEnabled(programCount > 0 or midiProgramCount > 0)
1162 if self.fPluginInfo['type'] == PLUGIN_LV2:
1163 self.ui.b_load_state.setEnabled(programCount > 0)
1165 #------------------------------------------------------------------
1167 def clearNotes(self):
1168 self.fPlayingNotes = []
1169 self.ui.keyboard.allNotesOff()
1171 def noteOn(self, channel, note, velocity):
1172 if self.fControlChannel == channel:
1173 self.ui.keyboard.sendNoteOn(note, False)
1175 def noteOff(self, channel, note):
1176 if self.fControlChannel == channel:
1177 self.ui.keyboard.sendNoteOff(note, False)
1179 #------------------------------------------------------------------
1181 def getHints(self):
1182 return self.fPluginInfo['hints']
1184 def setPluginId(self, idx):
1185 self.fPluginId = idx
1187 def setName(self, name):
1188 self.fPluginInfo['name'] = name
1189 self.ui.label_plugin.setText("\n%s\n" % name)
1190 self.setWindowTitle(name)
1192 #------------------------------------------------------------------
1194 def setParameterValue(self, parameterId, value):
1195 for paramItem in self.fParametersToUpdate:
1196 if paramItem[0] == parameterId:
1197 paramItem[1] = value
1198 break
1199 else:
1200 self.fParametersToUpdate.append([parameterId, value])
1202 def setParameterDefault(self, parameterId, value):
1203 for _, paramId, paramWidget in self.fParameterList:
1204 if paramId == parameterId:
1205 paramWidget.setDefault(value)
1206 break
1208 def setParameterMappedControlIndex(self, parameterId, control):
1209 for _, paramId, paramWidget in self.fParameterList:
1210 if paramId == parameterId:
1211 paramWidget.setMappedControlIndex(control)
1212 break
1214 def setParameterMappedRange(self, parameterId, minimum, maximum):
1215 for _, paramId, paramWidget in self.fParameterList:
1216 if paramId == parameterId:
1217 paramWidget.setMappedRange(minimum, maximum)
1218 break
1220 def setParameterMidiChannel(self, parameterId, channel):
1221 for _, paramId, paramWidget in self.fParameterList:
1222 if paramId == parameterId:
1223 paramWidget.setMidiChannel(channel+1)
1224 break
1226 def setProgram(self, index):
1227 self.ui.cb_programs.blockSignals(True)
1228 self.ui.cb_programs.setCurrentIndex(index)
1229 self.ui.cb_programs.blockSignals(False)
1230 self._updateParameterValues()
1232 def setMidiProgram(self, index):
1233 self.ui.cb_midi_programs.blockSignals(True)
1234 self.ui.cb_midi_programs.setCurrentIndex(index)
1235 self.ui.cb_midi_programs.blockSignals(False)
1236 self._updateParameterValues()
1238 def setOption(self, option, yesNo):
1239 if option == PLUGIN_OPTION_USE_CHUNKS:
1240 widget = self.ui.ch_use_chunks
1241 elif option == PLUGIN_OPTION_FIXED_BUFFERS:
1242 widget = self.ui.ch_fixed_buffer
1243 elif option == PLUGIN_OPTION_FORCE_STEREO:
1244 widget = self.ui.ch_force_stereo
1245 elif option == PLUGIN_OPTION_MAP_PROGRAM_CHANGES:
1246 widget = self.ui.ch_map_program_changes
1247 elif option == PLUGIN_OPTION_SKIP_SENDING_NOTES:
1248 widget = self.ui.ch_send_notes
1249 yesNo = not yesNo
1250 elif option == PLUGIN_OPTION_SEND_PROGRAM_CHANGES:
1251 widget = self.ui.ch_send_program_changes
1252 elif option == PLUGIN_OPTION_SEND_CONTROL_CHANGES:
1253 widget = self.ui.ch_send_control_changes
1254 elif option == PLUGIN_OPTION_SEND_CHANNEL_PRESSURE:
1255 widget = self.ui.ch_send_channel_pressure
1256 elif option == PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH:
1257 widget = self.ui.ch_send_note_aftertouch
1258 elif option == PLUGIN_OPTION_SEND_PITCHBEND:
1259 widget = self.ui.ch_send_pitchbend
1260 elif option == PLUGIN_OPTION_SEND_ALL_SOUND_OFF:
1261 widget = self.ui.ch_send_all_sound_off
1262 else:
1263 return
1265 widget.blockSignals(True)
1266 widget.setChecked(yesNo)
1267 widget.blockSignals(False)
1269 #------------------------------------------------------------------
1271 def setVisible(self, yesNo):
1272 if yesNo:
1273 if not self.fGeometry.isNull():
1274 self.restoreGeometry(self.fGeometry)
1275 else:
1276 self.fGeometry = self.saveGeometry()
1278 QDialog.setVisible(self, yesNo)
1280 if MACOS and yesNo:
1281 parent = self.parent()
1282 if parent is None:
1283 return
1284 gCarla.utils.cocoa_set_transient_window_for(self.windowHandle().winId(), parent.windowHandle().winId())
1286 #------------------------------------------------------------------
1288 def idleSlow(self):
1289 # Check Tab icons
1290 for i in range(len(self.fTabIconTimers)):
1291 if self.fTabIconTimers[i] == ICON_STATE_ON:
1292 self.fTabIconTimers[i] = ICON_STATE_WAIT
1293 elif self.fTabIconTimers[i] == ICON_STATE_WAIT:
1294 self.fTabIconTimers[i] = ICON_STATE_OFF
1295 elif self.fTabIconTimers[i] == ICON_STATE_OFF:
1296 self.fTabIconTimers[i] = ICON_STATE_NULL
1297 self.ui.tabWidget.setTabIcon(i+1, self.fTabIconOff)
1299 # Check parameters needing update
1300 for index, value in self.fParametersToUpdate:
1301 if index == PARAMETER_DRYWET:
1302 self.ui.dial_drywet.blockSignals(True)
1303 self.ui.dial_drywet.setValue(value)
1304 self.ui.dial_drywet.blockSignals(False)
1306 elif index == PARAMETER_VOLUME:
1307 self.ui.dial_vol.blockSignals(True)
1308 self.ui.dial_vol.setValue(value)
1309 self.ui.dial_vol.blockSignals(False)
1311 elif index == PARAMETER_BALANCE_LEFT:
1312 self.ui.dial_b_left.blockSignals(True)
1313 self.ui.dial_b_left.setValue(value)
1314 self.ui.dial_b_left.blockSignals(False)
1316 elif index == PARAMETER_BALANCE_RIGHT:
1317 self.ui.dial_b_right.blockSignals(True)
1318 self.ui.dial_b_right.setValue(value)
1319 self.ui.dial_b_right.blockSignals(False)
1321 elif index == PARAMETER_PANNING:
1322 self.ui.dial_pan.blockSignals(True)
1323 self.ui.dial_pan.setValue(value)
1324 self.ui.dial_pan.blockSignals(False)
1326 elif index == PARAMETER_CTRL_CHANNEL:
1327 self.fControlChannel = round(value)
1328 self.ui.sb_ctrl_channel.blockSignals(True)
1329 self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1)
1330 self.ui.sb_ctrl_channel.blockSignals(False)
1331 self.ui.keyboard.allNotesOff()
1332 self._updateCtrlPrograms()
1334 elif index >= 0:
1335 for paramType, paramId, paramWidget in self.fParameterList:
1336 if paramId != index:
1337 continue
1338 # FIXME see below
1339 if paramType != PARAMETER_INPUT:
1340 continue
1342 paramWidget.blockSignals(True)
1343 paramWidget.setValue(value)
1344 paramWidget.blockSignals(False)
1346 #if paramType == PARAMETER_INPUT:
1347 tabIndex = paramWidget.getTabIndex()
1349 if self.fTabIconTimers[tabIndex-1] == ICON_STATE_NULL:
1350 self.ui.tabWidget.setTabIcon(tabIndex, self.fTabIconOn)
1352 self.fTabIconTimers[tabIndex-1] = ICON_STATE_ON
1353 break
1355 # Clear all parameters
1356 self.fParametersToUpdate = []
1358 # Update parameter outputs | FIXME needed?
1359 for paramType, paramId, paramWidget in self.fParameterList:
1360 if paramType != PARAMETER_OUTPUT:
1361 continue
1363 paramWidget.blockSignals(True)
1364 paramWidget.setValue(self.host.get_current_parameter_value(self.fPluginId, paramId))
1365 paramWidget.blockSignals(False)
1367 #------------------------------------------------------------------
1369 @pyqtSlot()
1370 def slot_stateSave(self):
1371 if self.fPluginInfo['type'] == PLUGIN_LV2:
1372 # TODO
1373 return
1375 if self.fCurrentStateFilename:
1376 askTry = QMessageBox.question(self,
1377 self.tr("Overwrite?"),
1378 self.tr("Overwrite previously created file?"),
1379 QMessageBox.Ok|QMessageBox.Cancel)
1381 if askTry == QMessageBox.Ok:
1382 self.host.save_plugin_state(self.fPluginId, self.fCurrentStateFilename)
1383 return
1385 self.fCurrentStateFilename = None
1387 fileFilter = self.tr("Carla State File (*.carxs)")
1388 filename, _ = QFileDialog.getSaveFileName(self, self.tr("Save Plugin State File"), filter=fileFilter)
1390 # FIXME use ok value, test if it works as expected
1391 if not filename:
1392 return
1394 if not filename.lower().endswith(".carxs"):
1395 filename += ".carxs"
1397 self.fCurrentStateFilename = filename
1398 self.host.save_plugin_state(self.fPluginId, self.fCurrentStateFilename)
1400 @pyqtSlot()
1401 def slot_stateLoad(self):
1402 if self.fPluginInfo['type'] == PLUGIN_LV2:
1403 presetList = []
1405 for i in range(self.host.get_program_count(self.fPluginId)):
1406 presetList.append("%03i - %s" % (i+1, self.host.get_program_name(self.fPluginId, i)))
1408 ret = QInputDialog.getItem(self,
1409 self.tr("Open LV2 Preset"),
1410 self.tr("Select an LV2 Preset:"),
1411 presetList, 0, False)
1413 if ret[1]:
1414 index = int(ret[0].split(" - ", 1)[0])-1
1415 self.host.set_program(self.fPluginId, index)
1416 self.setMidiProgram(-1)
1418 return
1420 fileFilter = self.tr("Carla State File (*.carxs)")
1421 filename, _ = QFileDialog.getOpenFileName(self, self.tr("Open Plugin State File"), filter=fileFilter)
1423 # FIXME use ok value, test if it works as expected
1424 if not filename:
1425 return
1427 self.fCurrentStateFilename = filename
1428 self.host.load_plugin_state(self.fPluginId, self.fCurrentStateFilename)
1430 #------------------------------------------------------------------
1432 @pyqtSlot(bool)
1433 def slot_optionChanged(self, clicked):
1434 sender = self.sender()
1436 if sender == self.ui.ch_use_chunks:
1437 option = PLUGIN_OPTION_USE_CHUNKS
1438 elif sender == self.ui.ch_fixed_buffer:
1439 option = PLUGIN_OPTION_FIXED_BUFFERS
1440 elif sender == self.ui.ch_force_stereo:
1441 option = PLUGIN_OPTION_FORCE_STEREO
1442 elif sender == self.ui.ch_map_program_changes:
1443 option = PLUGIN_OPTION_MAP_PROGRAM_CHANGES
1444 elif sender == self.ui.ch_send_notes:
1445 option = PLUGIN_OPTION_SKIP_SENDING_NOTES
1446 clicked = not clicked
1447 elif sender == self.ui.ch_send_program_changes:
1448 option = PLUGIN_OPTION_SEND_PROGRAM_CHANGES
1449 elif sender == self.ui.ch_send_control_changes:
1450 option = PLUGIN_OPTION_SEND_CONTROL_CHANGES
1451 elif sender == self.ui.ch_send_channel_pressure:
1452 option = PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
1453 elif sender == self.ui.ch_send_note_aftertouch:
1454 option = PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
1455 elif sender == self.ui.ch_send_pitchbend:
1456 option = PLUGIN_OPTION_SEND_PITCHBEND
1457 elif sender == self.ui.ch_send_all_sound_off:
1458 option = PLUGIN_OPTION_SEND_ALL_SOUND_OFF
1459 else:
1460 return
1462 #--------------------------------------------------------------
1463 # handle map-program-changes and send-program-changes conflict
1465 if option == PLUGIN_OPTION_MAP_PROGRAM_CHANGES and clicked:
1466 self.ui.ch_send_program_changes.setEnabled(False)
1468 # disable send-program-changes if needed
1469 if self.ui.ch_send_program_changes.isChecked():
1470 self.host.set_option(self.fPluginId, PLUGIN_OPTION_SEND_PROGRAM_CHANGES, False)
1472 #--------------------------------------------------------------
1473 # set option
1475 self.host.set_option(self.fPluginId, option, clicked)
1477 #--------------------------------------------------------------
1478 # handle map-program-changes and send-program-changes conflict
1480 if option == PLUGIN_OPTION_MAP_PROGRAM_CHANGES and not clicked:
1481 self.ui.ch_send_program_changes.setEnabled(
1482 self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_PROGRAM_CHANGES)
1484 # restore send-program-changes if needed
1485 if self.ui.ch_send_program_changes.isChecked():
1486 self.host.set_option(self.fPluginId, PLUGIN_OPTION_SEND_PROGRAM_CHANGES, True)
1488 #------------------------------------------------------------------
1490 @pyqtSlot(float)
1491 def slot_dryWetChanged(self, value):
1492 self.host.set_drywet(self.fPluginId, value)
1494 if self.fParent is not None:
1495 self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_DRYWET, value)
1497 @pyqtSlot(float)
1498 def slot_volumeChanged(self, value):
1499 self.host.set_volume(self.fPluginId, value)
1501 if self.fParent is not None:
1502 self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_VOLUME, value)
1504 @pyqtSlot(float)
1505 def slot_balanceLeftChanged(self, value):
1506 self.host.set_balance_left(self.fPluginId, value)
1508 if self.fParent is not None:
1509 self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_BALANCE_LEFT, value)
1511 @pyqtSlot(float)
1512 def slot_balanceRightChanged(self, value):
1513 self.host.set_balance_right(self.fPluginId, value)
1515 if self.fParent is not None:
1516 self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_BALANCE_RIGHT, value)
1518 @pyqtSlot(float)
1519 def slot_panChanged(self, value):
1520 self.host.set_panning(self.fPluginId, value)
1522 if self.fParent is not None:
1523 self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_PANNING, value)
1525 @pyqtSlot(int)
1526 def slot_ctrlChannelChanged(self, value):
1527 self.fControlChannel = value-1
1528 self.host.set_ctrl_channel(self.fPluginId, self.fControlChannel)
1530 self.ui.keyboard.allNotesOff()
1531 self._updateCtrlPrograms()
1533 #------------------------------------------------------------------
1535 @pyqtSlot(int, float)
1536 def slot_parameterValueChanged(self, parameterId, value):
1537 self.host.set_parameter_value(self.fPluginId, parameterId, value)
1539 if self.fParent is not None:
1540 self.fParent.editDialogParameterValueChanged(self.fPluginId, parameterId, value)
1542 @pyqtSlot(int, int)
1543 def slot_parameterMappedControlChanged(self, parameterId, control):
1544 self.host.set_parameter_mapped_control_index(self.fPluginId, parameterId, control)
1546 @pyqtSlot(int, float, float)
1547 def slot_parameterMappedRangeChanged(self, parameterId, minimum, maximum):
1548 self.host.set_parameter_mapped_range(self.fPluginId, parameterId, minimum, maximum)
1550 @pyqtSlot(int, int)
1551 def slot_parameterMidiChannelChanged(self, parameterId, channel):
1552 self.host.set_parameter_midi_channel(self.fPluginId, parameterId, channel-1)
1554 #------------------------------------------------------------------
1556 @pyqtSlot(int)
1557 def slot_programIndexChanged(self, index):
1558 self.host.set_program(self.fPluginId, index)
1560 if self.fParent is not None:
1561 self.fParent.editDialogProgramChanged(self.fPluginId, index)
1563 self._updateParameterValues()
1565 @pyqtSlot(int)
1566 def slot_midiProgramIndexChanged(self, index):
1567 self.host.set_midi_program(self.fPluginId, index)
1569 if self.fParent is not None:
1570 self.fParent.editDialogMidiProgramChanged(self.fPluginId, index)
1572 self._updateParameterValues()
1574 #------------------------------------------------------------------
1576 @pyqtSlot(int)
1577 def slot_noteOn(self, note):
1578 if self.fControlChannel >= 0:
1579 self.host.send_midi_note(self.fPluginId, self.fControlChannel, note, 100)
1581 if self.fParent is not None:
1582 self.fParent.editDialogNotePressed(self.fPluginId, note)
1584 @pyqtSlot(int)
1585 def slot_noteOff(self, note):
1586 if self.fControlChannel >= 0:
1587 self.host.send_midi_note(self.fPluginId, self.fControlChannel, note, 0)
1589 if self.fParent is not None:
1590 self.fParent.editDialogNoteReleased(self.fPluginId, note)
1592 #------------------------------------------------------------------
1594 @pyqtSlot()
1595 def slot_finished(self):
1596 if self.fParent is not None:
1597 self.fParent.editDialogVisibilityChanged(self.fPluginId, False)
1599 #------------------------------------------------------------------
1601 @pyqtSlot()
1602 def slot_knobCustomMenu(self):
1603 sender = self.sender()
1604 knobName = sender.objectName()
1606 if knobName == "dial_drywet":
1607 minimum = 0.0
1608 maximum = 1.0
1609 default = 1.0
1610 label = "Dry/Wet"
1611 elif knobName == "dial_vol":
1612 minimum = 0.0
1613 maximum = 1.27
1614 default = 1.0
1615 label = "Volume"
1616 elif knobName == "dial_b_left":
1617 minimum = -1.0
1618 maximum = 1.0
1619 default = -1.0
1620 label = "Balance-Left"
1621 elif knobName == "dial_b_right":
1622 minimum = -1.0
1623 maximum = 1.0
1624 default = 1.0
1625 label = "Balance-Right"
1626 elif knobName == "dial_pan":
1627 minimum = -1.0
1628 maximum = 1.0
1629 default = 0.0
1630 label = "Panning"
1631 else:
1632 minimum = 0.0
1633 maximum = 1.0
1634 default = 0.5
1635 label = "Unknown"
1637 menu = QMenu(self)
1638 actReset = menu.addAction(self.tr("Reset (%i%%)" % (default*100)))
1639 menu.addSeparator()
1640 actMinimum = menu.addAction(self.tr("Set to Minimum (%i%%)" % (minimum*100)))
1641 actCenter = menu.addAction(self.tr("Set to Center"))
1642 actMaximum = menu.addAction(self.tr("Set to Maximum (%i%%)" % (maximum*100)))
1643 menu.addSeparator()
1644 actSet = menu.addAction(self.tr("Set value..."))
1646 if label not in ("Balance-Left", "Balance-Right", "Panning"):
1647 menu.removeAction(actCenter)
1649 actSelected = menu.exec_(QCursor.pos())
1651 if actSelected == actSet:
1652 current = minimum + (maximum-minimum)*(float(sender.value())/10000)
1653 value, ok = QInputDialog.getInt(self,
1654 self.tr("Set value"),
1655 label,
1656 round(current*100.0),
1657 round(minimum*100.0),
1658 round(maximum*100.0),
1660 if ok:
1661 value = float(value)/100.0
1663 if not ok:
1664 return
1666 elif actSelected == actMinimum:
1667 value = minimum
1668 elif actSelected == actMaximum:
1669 value = maximum
1670 elif actSelected == actReset:
1671 value = default
1672 elif actSelected == actCenter:
1673 value = 0.0
1674 else:
1675 return
1677 sender.setValue(value, True)
1679 #------------------------------------------------------------------
1681 @pyqtSlot()
1682 def slot_channelCustomMenu(self):
1683 menu = QMenu(self)
1685 actNone = menu.addAction(self.tr("None"))
1687 if self.fControlChannel+1 == 0:
1688 actNone.setCheckable(True)
1689 actNone.setChecked(True)
1691 for i in range(1, 16+1):
1692 action = menu.addAction("%i" % i)
1694 if self.fControlChannel+1 == i:
1695 action.setCheckable(True)
1696 action.setChecked(True)
1698 actSel = menu.exec_(QCursor.pos())
1700 if not actSel:
1701 pass
1702 elif actSel == actNone:
1703 self.ui.sb_ctrl_channel.setValue(0)
1704 elif actSel:
1705 selChannel = int(actSel.text())
1706 self.ui.sb_ctrl_channel.setValue(selChannel)
1708 #------------------------------------------------------------------
1710 def _createParameterWidgets(self, paramType, paramListFull, tabPageName):
1711 groupWidgets = {}
1713 for paramList, width in paramListFull:
1714 if not paramList:
1715 break
1717 tabIndex = self.ui.tabWidget.count()
1719 scrollArea = QScrollArea(self.ui.tabWidget)
1720 scrollArea.setWidgetResizable(True)
1721 scrollArea.setFrameStyle(0)
1723 palette1 = scrollArea.palette()
1724 palette1.setColor(QPalette.Background, Qt.transparent)
1725 scrollArea.setPalette(palette1)
1727 palette2 = scrollArea.palette()
1728 palette2.setColor(QPalette.Background, palette2.color(QPalette.Button))
1730 scrollAreaWidget = QWidget(scrollArea)
1731 scrollAreaLayout = QVBoxLayout(scrollAreaWidget)
1732 scrollAreaLayout.setSpacing(3)
1734 for paramInfo in paramList:
1735 groupName = paramInfo['groupName']
1736 if groupName:
1737 groupSymbol, groupName = groupName.split(":",1)
1738 groupLayout, groupWidget = groupWidgets.get(groupSymbol, (None, None))
1739 if groupLayout is None:
1740 groupWidget = CollapsibleBox(groupName, scrollAreaWidget)
1741 groupLayout = groupWidget.getContentLayout()
1742 groupWidget.setPalette(palette2)
1743 scrollAreaLayout.addWidget(groupWidget)
1744 groupWidgets[groupSymbol] = (groupLayout, groupWidget)
1745 else:
1746 groupLayout = scrollAreaLayout
1747 groupWidget = scrollAreaWidget
1749 paramWidget = PluginParameter(groupWidget, self.host, paramInfo, self.fPluginId, tabIndex)
1750 paramWidget.setLabelWidth(width)
1751 groupLayout.addWidget(paramWidget)
1753 self.fParameterList.append((paramType, paramInfo['index'], paramWidget))
1755 if paramType == PARAMETER_INPUT:
1756 paramWidget.valueChanged.connect(self.slot_parameterValueChanged)
1758 paramWidget.mappedControlChanged.connect(self.slot_parameterMappedControlChanged)
1759 paramWidget.mappedRangeChanged.connect(self.slot_parameterMappedRangeChanged)
1760 paramWidget.midiChannelChanged.connect(self.slot_parameterMidiChannelChanged)
1762 scrollAreaLayout.addStretch()
1764 scrollArea.setWidget(scrollAreaWidget)
1766 self.ui.tabWidget.addTab(scrollArea, tabPageName)
1768 if paramType == PARAMETER_INPUT:
1769 self.ui.tabWidget.setTabIcon(tabIndex, self.fTabIconOff)
1771 self.fTabIconTimers.append(ICON_STATE_NULL)
1773 def _updateCtrlPrograms(self):
1774 self.ui.keyboard.setEnabled(self.fControlChannel >= 0)
1776 if self.fPluginInfo['category'] != PLUGIN_CATEGORY_SYNTH:
1777 return
1778 if self.fPluginInfo['type'] not in (PLUGIN_INTERNAL, PLUGIN_SF2):
1779 return
1781 if self.fControlChannel < 0:
1782 self.ui.cb_programs.setEnabled(False)
1783 self.ui.cb_midi_programs.setEnabled(False)
1784 return
1786 self.ui.cb_programs.setEnabled(True)
1787 self.ui.cb_midi_programs.setEnabled(True)
1789 pIndex = self.host.get_current_program_index(self.fPluginId)
1791 if self.ui.cb_programs.currentIndex() != pIndex:
1792 self.setProgram(pIndex)
1794 mpIndex = self.host.get_current_midi_program_index(self.fPluginId)
1796 if self.ui.cb_midi_programs.currentIndex() != mpIndex:
1797 self.setMidiProgram(mpIndex)
1799 def _updateParameterValues(self):
1800 for _, paramId, paramWidget in self.fParameterList:
1801 paramWidget.blockSignals(True)
1802 paramWidget.setValue(self.host.get_current_parameter_value(self.fPluginId, paramId))
1803 paramWidget.blockSignals(False)
1805 #------------------------------------------------------------------
1807 def testTimer(self):
1808 self.fIdleTimerId = self.startTimer(50)
1810 self.SIGTERM.connect(self.testTimerClose)
1811 gCarla.gui = self
1812 setUpSignals()
1814 def testTimerClose(self):
1815 self.close()
1816 _app.quit()
1818 #------------------------------------------------------------------
1820 def closeEvent(self, event):
1821 if self.fIdleTimerId != 0:
1822 self.killTimer(self.fIdleTimerId)
1823 self.fIdleTimerId = 0
1825 self.host.engine_close()
1827 QDialog.closeEvent(self, event)
1829 def timerEvent(self, event):
1830 if event.timerId() == self.fIdleTimerId:
1831 self.host.engine_idle()
1832 self.idleSlow()
1834 QDialog.timerEvent(self, event)
1836 # ------------------------------------------------------------------------------------------------------------
1837 # Main
1839 if __name__ == '__main__':
1840 from carla_app import CarlaApplication
1841 from carla_host import initHost as _initHost, loadHostSettings as _loadHostSettings
1843 _app = CarlaApplication()
1844 _host = _initHost("Widgets", None, False, False, False)
1845 _loadHostSettings(_host)
1847 _host.engine_init("JACK", "Carla-Widgets")
1848 _host.add_plugin(BINARY_NATIVE, PLUGIN_DSSI, "/usr/lib/dssi/karplong.so", "karplong", "karplong", 0, None, 0x0)
1849 _host.set_active(0, True)
1851 gui1 = CarlaAboutW(None, _host)
1852 gui1.show()
1854 gui2 = PluginEdit(None, _host, 0)
1855 gui2.testTimer()
1856 gui2.show()
1858 _app.exit_exec()