2 # SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # ------------------------------------------------------------------------------------------------------------
8 from abc
import abstractmethod
10 # ------------------------------------------------------------------------------------------------------------
13 from qt_compat
import qt_config
16 from PyQt5
.QtCore
import pyqtSignal
, pyqtSlot
, Qt
, QByteArray
17 from PyQt5
.QtGui
import QCursor
, QIcon
, QPalette
, QPixmap
18 from PyQt5
.QtWidgets
import (
29 from PyQt6
.QtCore
import pyqtSignal
, pyqtSlot
, Qt
, QByteArray
30 from PyQt6
.QtGui
import QCursor
, QIcon
, QPalette
, QPixmap
31 from PyQt6
.QtWidgets
import (
42 # ------------------------------------------------------------------------------------------------------------
47 import ui_carla_parameter
49 from carla_backend
import (
62 PLUGIN_CATEGORY_SYNTH
,
63 PLUGIN_OPTION_FIXED_BUFFERS
,
64 PLUGIN_OPTION_FORCE_STEREO
,
65 PLUGIN_OPTION_MAP_PROGRAM_CHANGES
,
66 PLUGIN_OPTION_USE_CHUNKS
,
67 PLUGIN_OPTION_SEND_CONTROL_CHANGES
,
68 PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
,
69 PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
,
70 PLUGIN_OPTION_SEND_PITCHBEND
,
71 PLUGIN_OPTION_SEND_ALL_SOUND_OFF
,
72 PLUGIN_OPTION_SEND_PROGRAM_CHANGES
,
73 PLUGIN_OPTION_SKIP_SENDING_NOTES
,
76 PARAMETER_BALANCE_LEFT
,
77 PARAMETER_BALANCE_RIGHT
,
79 PARAMETER_CTRL_CHANNEL
,
81 PARAMETER_IS_AUTOMATABLE
,
82 PARAMETER_IS_READ_ONLY
,
83 PARAMETER_USES_SCALEPOINTS
,
84 PARAMETER_USES_CUSTOM_TEXT
,
85 PARAMETER_CAN_BE_CV_CONTROLLED
,
86 PARAMETER_INPUT
, PARAMETER_OUTPUT
,
88 CONTROL_INDEX_MIDI_PITCHBEND
,
89 CONTROL_INDEX_MIDI_LEARN
,
93 from carla_shared
import (
94 MIDI_CC_LIST
, MAX_MIDI_CC_LIST_ITEM
,
97 fontMetricsHorizontalAdvance
,
102 from carla_utils
import getPluginTypeAsString
105 from widgets
.collapsablewidget
import CollapsibleBox
106 from widgets
.pixmapkeyboard
import PixmapKeyboardHArea
108 # ------------------------------------------------------------------------------------------------------------
111 ICON_STATE_ON
= 3 # turns on, sets as wait
112 ICON_STATE_WAIT
= 2 # nothing, sets as off
113 ICON_STATE_OFF
= 1 # turns off, sets as null
114 ICON_STATE_NULL
= 0 # nothing
116 # ------------------------------------------------------------------------------------------------------------
119 class CarlaAboutW(QDialog
):
120 def __init__(self
, parent
, host
):
121 QDialog
.__init
__(self
, parent
)
122 self
.ui
= ui_carla_about
.Ui_CarlaAboutW()
123 self
.ui
.setupUi(self
)
126 extraInfo
= " - <b>%s</b>" % self
.tr("OSC Bridge Version")
128 extraInfo
= " - <b>%s</b>" % self
.tr("Plugin Version")
132 self
.ui
.l_about
.setText(self
.tr(""
134 "<br>Carla is a fully-featured audio plugin host%s.<br>"
135 "<br>Copyright (C) 2011-2022 falkTX<br>"
136 "" % (VERSION
, extraInfo
)))
138 if self
.ui
.about
.palette().color(QPalette
.Background
).blackF() < 0.5:
139 self
.ui
.l_icons
.setPixmap(QPixmap(":/bitmaps/carla_about_black.png"))
140 self
.ui
.ico_example_edit
.setPixmap(QPixmap(":/bitmaps/button_file-black.png"))
141 self
.ui
.ico_example_file
.setPixmap(QPixmap(":/scalable/button_edit-black.svg"))
142 self
.ui
.ico_example_gui
.setPixmap(QPixmap(":/bitmaps/button_gui-black.png"))
145 self
.ui
.l_extended
.hide()
146 self
.ui
.tabWidget
.removeTab(3)
147 self
.ui
.tabWidget
.removeTab(2)
149 self
.ui
.l_extended
.setText(gCarla
.utils
.get_complete_license_text())
151 if host
.is_engine_running() and not host
.isControl
:
152 self
.ui
.le_osc_url_tcp
.setText(host
.get_host_osc_url_tcp())
153 self
.ui
.le_osc_url_udp
.setText(host
.get_host_osc_url_udp())
155 self
.ui
.le_osc_url_tcp
.setText(self
.tr("(Engine not running)"))
156 self
.ui
.le_osc_url_udp
.setText(self
.tr("(Engine not running)"))
158 # pylint: disable=line-too-long
159 self
.ui
.l_osc_cmds
.setText("<table>"
160 "<tr><td>" "/set_active" " </td><td><i-value></td></tr>"
161 "<tr><td>" "/set_drywet" " </td><td><f-value></td></tr>"
162 "<tr><td>" "/set_volume" " </td><td><f-value></td></tr>"
163 "<tr><td>" "/set_balance_left" " </td><td><f-value></td></tr>"
164 "<tr><td>" "/set_balance_right" " </td><td><f-value></td></tr>"
165 "<tr><td>" "/set_panning" " </td><td><f-value></td></tr>"
166 "<tr><td>" "/set_parameter_value" " </td><td><i-index> <f-value></td></tr>"
167 "<tr><td>" "/set_parameter_midi_cc" " </td><td><i-index> <i-cc></td></tr>"
168 "<tr><td>" "/set_parameter_midi_channel" " </td><td><i-index> <i-channel></td></tr>"
169 "<tr><td>" "/set_program" " </td><td><i-index></td></tr>"
170 "<tr><td>" "/set_midi_program" " </td><td><i-index></td></tr>"
171 "<tr><td>" "/note_on" " </td><td><i-channel> <i-note> <i-velo></td></tr>"
172 "<tr><td>" "/note_off" " </td><td><i-channel> <i-note</td></tr>"
176 self
.ui
.l_example
.setText("/Carla/2/set_parameter_value 5 1.0")
177 self
.ui
.l_example_help
.setText("<i>(as in this example, \"2\" is the plugin number and \"5\" the parameter)</i>")
178 # pylint: enable=line-too-long
180 self
.ui
.l_ladspa
.setText(self
.tr("Everything! (Including LRDF)"))
181 self
.ui
.l_dssi
.setText(self
.tr("Everything! (Including CustomData/Chunks)"))
182 self
.ui
.l_lv2
.setText(self
.tr("About 110% complete (using custom extensions)<br/>"
183 "Implemented Feature/Extensions:"
185 "<li>http://lv2plug.in/ns/ext/atom</li>"
186 "<li>http://lv2plug.in/ns/ext/buf-size</li>"
187 "<li>http://lv2plug.in/ns/ext/data-access</li>"
188 #"<li>http://lv2plug.in/ns/ext/dynmanifest</li>"
189 "<li>http://lv2plug.in/ns/ext/event</li>"
190 "<li>http://lv2plug.in/ns/ext/instance-access</li>"
191 "<li>http://lv2plug.in/ns/ext/log</li>"
192 "<li>http://lv2plug.in/ns/ext/midi</li>"
193 #"<li>http://lv2plug.in/ns/ext/morph</li>"
194 "<li>http://lv2plug.in/ns/ext/options</li>"
195 "<li>http://lv2plug.in/ns/ext/parameters</li>"
196 #"<li>http://lv2plug.in/ns/ext/patch</li>"
197 "<li>http://lv2plug.in/ns/ext/port-props</li>"
198 "<li>http://lv2plug.in/ns/ext/presets</li>"
199 "<li>http://lv2plug.in/ns/ext/resize-port</li>"
200 "<li>http://lv2plug.in/ns/ext/state</li>"
201 "<li>http://lv2plug.in/ns/ext/time</li>"
202 "<li>http://lv2plug.in/ns/ext/uri-map</li>"
203 "<li>http://lv2plug.in/ns/ext/urid</li>"
204 "<li>http://lv2plug.in/ns/ext/worker</li>"
205 "<li>http://lv2plug.in/ns/extensions/ui</li>"
206 "<li>http://lv2plug.in/ns/extensions/units</li>"
207 "<li>http://home.gna.org/lv2dynparam/rtmempool/v1</li>"
208 "<li>http://kxstudio.sf.net/ns/lv2ext/external-ui</li>"
209 "<li>http://kxstudio.sf.net/ns/lv2ext/programs</li>"
210 "<li>http://kxstudio.sf.net/ns/lv2ext/props</li>"
211 "<li>http://kxstudio.sf.net/ns/lv2ext/rtmempool</li>"
212 "<li>http://ll-plugins.nongnu.org/lv2/ext/midimap</li>"
213 "<li>http://ll-plugins.nongnu.org/lv2/ext/miditype</li>"
216 self
.ui
.l_vst2
.setText(self
.tr("About 85% complete (missing vst bank/presets and some minor stuff)"))
217 self
.ui
.l_vst3
.setText(self
.tr("About 66% complete"))
220 self
.ui
.l_au
.setText(self
.tr("About 20% complete"))
222 self
.ui
.line_vst3
.hide()
224 self
.ui
.lid_au
.hide()
226 # 3rd tab is usually longer than the 1st
227 # adjust appropriately
228 self
.ui
.tabWidget
.setCurrentIndex(2)
230 self
.ui
.tabWidget
.setCurrentIndex(0)
232 self
.setFixedSize(self
.size())
234 flags
= self
.windowFlags()
235 flags
&= ~Qt
.WindowContextHelpButtonHint
238 flags |
= Qt
.MSWindowsFixedSizeDialogHint
240 self
.setWindowFlags(flags
)
243 self
.setWindowModality(Qt
.WindowModal
)
245 # ------------------------------------------------------------------------------------------------------------
248 class PluginParameter(QWidget
):
249 mappedControlChanged
= pyqtSignal(int, int)
250 mappedRangeChanged
= pyqtSignal(int, float, float)
251 midiChannelChanged
= pyqtSignal(int, int)
252 valueChanged
= pyqtSignal(int, float)
254 def __init__(self
, parent
, host
, pInfo
, pluginId
, tabIndex
):
255 QWidget
.__init
__(self
, parent
)
257 self
.ui
= ui_carla_parameter
.Ui_PluginParameter()
258 self
.ui
.setupUi(self
)
260 # -------------------------------------------------------------
263 self
.fDecimalPoints
= max(2, countDecimalPoints(pInfo
['step'], pInfo
['stepSmall']))
264 self
.fCanBeInCV
= pInfo
['hints'] & PARAMETER_CAN_BE_CV_CONTROLLED
265 self
.fMappedCtrl
= pInfo
['mappedControlIndex']
266 self
.fMappedMinimum
= pInfo
['mappedMinimum']
267 self
.fMappedMaximum
= pInfo
['mappedMaximum']
268 self
.fMinimum
= pInfo
['minimum']
269 self
.fMaximum
= pInfo
['maximum']
270 self
.fMidiChannel
= pInfo
['midiChannel']
271 self
.fParameterId
= pInfo
['index']
272 self
.fPluginId
= pluginId
273 self
.fTabIndex
= tabIndex
275 # -------------------------------------------------------------
278 pType
= pInfo
['type']
279 pHints
= pInfo
['hints']
281 self
.ui
.l_name
.setText(pInfo
['name'])
282 self
.ui
.widget
.setName(pInfo
['name'])
283 self
.ui
.widget
.setMinimum(pInfo
['minimum'])
284 self
.ui
.widget
.setMaximum(pInfo
['maximum'])
285 self
.ui
.widget
.setDefault(pInfo
['default'])
286 self
.ui
.widget
.setLabel(pInfo
['unit'])
287 self
.ui
.widget
.setStep(pInfo
['step'])
288 self
.ui
.widget
.setStepSmall(pInfo
['stepSmall'])
289 self
.ui
.widget
.setStepLarge(pInfo
['stepLarge'])
290 self
.ui
.widget
.setScalePoints(pInfo
['scalePoints'], bool(pHints
& PARAMETER_USES_SCALEPOINTS
))
293 self
.ui
.l_name
.setToolTip(pInfo
['comment'])
294 self
.ui
.widget
.setToolTip(pInfo
['comment'])
296 if pType
== PARAMETER_INPUT
:
297 if not pHints
& PARAMETER_IS_ENABLED
:
298 self
.ui
.l_name
.setEnabled(False)
299 self
.ui
.l_status
.setEnabled(False)
300 self
.ui
.widget
.setEnabled(False)
301 self
.ui
.widget
.setReadOnly(True)
302 self
.ui
.tb_options
.setEnabled(False)
304 elif not pHints
& PARAMETER_IS_AUTOMATABLE
:
305 self
.ui
.l_status
.setEnabled(False)
306 self
.ui
.tb_options
.setEnabled(False)
308 if pHints
& PARAMETER_IS_READ_ONLY
:
309 self
.ui
.l_status
.setEnabled(False)
310 self
.ui
.widget
.setReadOnly(True)
311 self
.ui
.tb_options
.setEnabled(False)
313 elif pType
== PARAMETER_OUTPUT
:
314 self
.ui
.widget
.setReadOnly(True)
317 self
.ui
.l_status
.setVisible(False)
318 self
.ui
.widget
.setVisible(False)
319 self
.ui
.tb_options
.setVisible(False)
321 # Only set value after all hints are handled
322 self
.ui
.widget
.setValue(pInfo
['current'])
324 if pHints
& PARAMETER_USES_CUSTOM_TEXT
and not host
.isPlugin
:
325 self
.ui
.widget
.setTextCallback(self
._textCallBack
)
327 self
.ui
.l_status
.setFixedWidth(fontMetricsHorizontalAdvance(self
.ui
.l_status
.fontMetrics(),
328 self
.tr("CC%i Ch%i" % (119,16))))
330 self
.ui
.widget
.setValueCallback(self
._valueCallBack
)
331 self
.ui
.widget
.updateAll()
333 self
.setMappedControlIndex(pInfo
['mappedControlIndex'])
334 self
.setMidiChannel(pInfo
['midiChannel'])
335 self
.updateStatusLabel()
337 # -------------------------------------------------------------
340 self
.ui
.tb_options
.clicked
.connect(self
.slot_optionsCustomMenu
)
341 self
.ui
.widget
.dragStateChanged
.connect(self
.slot_parameterDragStateChanged
)
343 # -------------------------------------------------------------
345 def getPluginId(self
):
346 return self
.fPluginId
348 def getTabIndex(self
):
349 return self
.fTabIndex
351 def setPluginId(self
, pluginId
):
352 self
.fPluginId
= pluginId
354 def setDefault(self
, value
):
355 self
.ui
.widget
.setDefault(value
)
357 def setValue(self
, value
):
358 self
.ui
.widget
.blockSignals(True)
359 self
.ui
.widget
.setValue(value
)
360 self
.ui
.widget
.blockSignals(False)
362 def setMappedControlIndex(self
, control
):
363 self
.fMappedCtrl
= control
364 self
.updateStatusLabel()
366 def setMappedRange(self
, minimum
, maximum
):
367 self
.fMappedMinimum
= minimum
368 self
.fMappedMaximum
= maximum
370 def setMidiChannel(self
, channel
):
371 self
.fMidiChannel
= channel
372 self
.updateStatusLabel()
374 def setLabelWidth(self
, width
):
375 self
.ui
.l_name
.setFixedWidth(width
)
377 def updateStatusLabel(self
):
378 if self
.fMappedCtrl
== CONTROL_INDEX_NONE
:
379 text
= self
.tr("Unmapped")
380 elif self
.fMappedCtrl
== CONTROL_INDEX_CV
:
381 text
= self
.tr("CV export")
382 elif self
.fMappedCtrl
== CONTROL_INDEX_MIDI_PITCHBEND
:
383 text
= self
.tr("PBend Ch%i" % (self
.fMidiChannel
,))
384 elif self
.fMappedCtrl
== CONTROL_INDEX_MIDI_LEARN
:
385 text
= self
.tr("MIDI Learn")
387 text
= self
.tr("CC%i Ch%i" % (self
.fMappedCtrl
, self
.fMidiChannel
))
389 self
.ui
.l_status
.setText(text
)
392 def slot_optionsCustomMenu(self
):
395 if self
.fMappedCtrl
== CONTROL_INDEX_NONE
:
396 title
= self
.tr("Unmapped")
397 elif self
.fMappedCtrl
== CONTROL_INDEX_CV
:
398 title
= self
.tr("Exposed as CV port")
399 elif self
.fMappedCtrl
== CONTROL_INDEX_MIDI_PITCHBEND
:
400 title
= self
.tr("Mapped to MIDI Pitchbend, channel %i" % (self
.fMidiChannel
,))
401 elif self
.fMappedCtrl
== CONTROL_INDEX_MIDI_LEARN
:
402 title
= self
.tr("MIDI Learn active")
404 title
= self
.tr("Mapped to MIDI control %i, channel %i" % (self
.fMappedCtrl
, self
.fMidiChannel
))
406 if self
.fMappedCtrl
!= CONTROL_INDEX_NONE
:
407 title
+= " (range: %g-%g)" % (self
.fMappedMinimum
, self
.fMappedMaximum
)
409 actTitle
= menu
.addAction(title
)
410 actTitle
.setEnabled(False)
414 actUnmap
= menu
.addAction(self
.tr("Unmap"))
416 if self
.fMappedCtrl
== CONTROL_INDEX_NONE
:
417 actUnmap
.setCheckable(True)
418 actUnmap
.setChecked(True)
421 menu
.addSection("CV")
422 actCV
= menu
.addAction(self
.tr("Expose as CV port"))
423 if self
.fMappedCtrl
== CONTROL_INDEX_CV
:
424 actCV
.setCheckable(True)
425 actCV
.setChecked(True)
429 menu
.addSection("MIDI")
431 if not self
.ui
.widget
.isReadOnly():
432 actLearn
= menu
.addAction(self
.tr("MIDI Learn"))
434 if self
.fMappedCtrl
== CONTROL_INDEX_MIDI_LEARN
:
435 actLearn
.setCheckable(True)
436 actLearn
.setChecked(True)
440 menuMIDI
= menu
.addMenu(self
.tr("MIDI Control"))
442 if self
.fMappedCtrl
not in (CONTROL_INDEX_NONE
,
444 CONTROL_INDEX_MIDI_PITCHBEND
,
445 CONTROL_INDEX_MIDI_LEARN
):
446 action
= menuMIDI
.menuAction()
447 action
.setCheckable(True)
448 action
.setChecked(True)
452 for cc
in MIDI_CC_LIST
:
453 action
= menuMIDI
.addAction(cc
)
454 actCCs
.append(action
)
456 if self
.fMappedCtrl
>= 0 and self
.fMappedCtrl
<= MAX_MIDI_CC_LIST_ITEM
:
457 ccx
= int(cc
.split(" [", 1)[0], 10)
459 if ccx
> self
.fMappedCtrl
and not inlist
:
461 action
= menuMIDI
.addAction(self
.tr("%02i [0x%02X] (Custom)" % (self
.fMappedCtrl
,
463 action
.setCheckable(True)
464 action
.setChecked(True)
465 actCCs
.append(action
)
467 elif ccx
== self
.fMappedCtrl
:
469 action
.setCheckable(True)
470 action
.setChecked(True)
472 if self
.fMappedCtrl
> MAX_MIDI_CC_LIST_ITEM
and self
.fMappedCtrl
<= 0x77:
473 action
= menuMIDI
.addAction(self
.tr("%02i [0x%02X] (Custom)" % (self
.fMappedCtrl
, self
.fMappedCtrl
)))
474 action
.setCheckable(True)
475 action
.setChecked(True)
476 actCCs
.append(action
)
478 actCustomCC
= menuMIDI
.addAction(self
.tr("Custom..."))
481 #actPitchbend = menu.addAction(self.tr("MIDI Pitchbend"))
483 #if self.fMappedCtrl == CONTROL_INDEX_MIDI_PITCHBEND:
484 #actPitchbend.setCheckable(True)
485 #actPitchbend.setChecked(True)
487 menuChannel
= menu
.addMenu(self
.tr("MIDI Channel"))
490 for i
in range(1, 16+1):
491 action
= menuChannel
.addAction("%i" % i
)
492 actChannels
.append(action
)
494 if self
.fMidiChannel
== i
:
495 action
.setCheckable(True)
496 action
.setChecked(True)
498 if self
.fMappedCtrl
!= CONTROL_INDEX_NONE
:
499 if self
.fMappedCtrl
== CONTROL_INDEX_CV
:
500 menu
.addSection("Range (Scaled CV input)")
502 menu
.addSection("Range (MIDI bounds)")
503 actRangeMinimum
= menu
.addAction(self
.tr("Set minimum... (%g)" % self
.fMappedMinimum
))
504 actRangeMaximum
= menu
.addAction(self
.tr("Set maximum... (%g)" % self
.fMappedMaximum
))
506 actRangeMinimum
= actRangeMaximum
= None
508 actSel
= menu
.exec_(QCursor
.pos())
513 if actSel
in actChannels
:
514 channel
= int(actSel
.text())
515 self
.fMidiChannel
= channel
516 self
.updateStatusLabel()
517 self
.midiChannelChanged
.emit(self
.fParameterId
, channel
)
520 if actSel
== actRangeMinimum
:
521 value
, ok
= QInputDialog
.getDouble(self
,
522 self
.tr("Custom Minimum"),
523 "Custom minimum value to use:",
525 self
.fMinimum
if self
.fMappedCtrl
!= CONTROL_INDEX_CV
else -9e6
,
526 self
.fMaximum
if self
.fMappedCtrl
!= CONTROL_INDEX_CV
else 9e6
,
531 self
.fMappedMinimum
= value
532 self
.mappedRangeChanged
.emit(self
.fParameterId
, self
.fMappedMinimum
, self
.fMappedMaximum
)
535 if actSel
== actRangeMaximum
:
536 value
, ok
= QInputDialog
.getDouble(self
,
537 self
.tr("Custom Maximum"),
538 "Custom maximum value to use:",
540 self
.fMinimum
if self
.fMappedCtrl
!= CONTROL_INDEX_CV
else -9e6
,
541 self
.fMaximum
if self
.fMappedCtrl
!= CONTROL_INDEX_CV
else 9e6
,
546 self
.fMappedMaximum
= value
547 self
.mappedRangeChanged
.emit(self
.fParameterId
, self
.fMappedMinimum
, self
.fMappedMaximum
)
550 if actSel
== actUnmap
:
551 ctrl
= CONTROL_INDEX_NONE
552 elif actSel
== actCV
:
553 ctrl
= CONTROL_INDEX_CV
554 elif actSel
== actCustomCC
:
555 value
= self
.fMappedCtrl
if self
.fMappedCtrl
>= 0x01 and self
.fMappedCtrl
<= 0x77 else 1
556 ctrl
, ok
= QInputDialog
.getInt(self
,
557 self
.tr("Custom CC"),
558 "Custom MIDI CC to use:",
563 #elif actSel == actPitchbend:
564 #ctrl = CONTROL_INDEX_MIDI_PITCHBEND
565 elif actSel
== actLearn
:
566 ctrl
= CONTROL_INDEX_MIDI_LEARN
567 elif actSel
in actCCs
:
568 ctrl
= int(actSel
.text().split(" ", 1)[0].replace("&",""), 10)
572 self
.fMappedCtrl
= ctrl
573 self
.updateStatusLabel()
574 self
.mappedControlChanged
.emit(self
.fParameterId
, ctrl
)
577 def slot_parameterDragStateChanged(self
, touch
):
578 self
.host
.set_parameter_touch(self
.fPluginId
, self
.fParameterId
, touch
)
580 def _textCallBack(self
):
581 return self
.host
.get_parameter_text(self
.fPluginId
, self
.fParameterId
)
583 def _valueCallBack(self
, value
):
584 self
.valueChanged
.emit(self
.fParameterId
, value
)
586 # ------------------------------------------------------------------------------------------------------------
587 # Plugin Editor Parent (Meta class)
589 class PluginEditParentMeta():
590 #class PluginEditParentMeta(metaclass=ABCMeta):
592 def editDialogVisibilityChanged(self
, pluginId
, visible
):
593 raise NotImplementedError
596 def editDialogPluginHintsChanged(self
, pluginId
, hints
):
597 raise NotImplementedError
600 def editDialogParameterValueChanged(self
, pluginId
, parameterId
, value
):
601 raise NotImplementedError
604 def editDialogProgramChanged(self
, pluginId
, index
):
605 raise NotImplementedError
608 def editDialogMidiProgramChanged(self
, pluginId
, index
):
609 raise NotImplementedError
612 def editDialogNotePressed(self
, pluginId
, note
):
613 raise NotImplementedError
616 def editDialogNoteReleased(self
, pluginId
, note
):
617 raise NotImplementedError
620 def editDialogMidiActivityChanged(self
, pluginId
, onOff
):
621 raise NotImplementedError
623 # ------------------------------------------------------------------------------------------------------------
624 # Plugin Editor (Built-in)
626 class PluginEdit(QDialog
):
628 SIGTERM
= pyqtSignal()
629 SIGUSR1
= pyqtSignal()
631 def __init__(self
, parent
, host
, pluginId
):
632 QDialog
.__init
__(self
, parent
.window() if parent
is not None else None)
634 self
.ui
= ui_carla_edit
.Ui_PluginEdit()
635 self
.ui
.setupUi(self
)
637 # -------------------------------------------------------------
640 self
.fGeometry
= QByteArray()
641 self
.fParent
= parent
642 self
.fPluginId
= pluginId
643 self
.fPluginInfo
= None
645 self
.fCurrentStateFilename
= None
646 self
.fControlChannel
= round(host
.get_internal_parameter_value(pluginId
, PARAMETER_CTRL_CHANNEL
))
647 self
.fFirstInit
= True
649 self
.fParameterList
= [] # (type, id, widget)
650 self
.fParametersToUpdate
= [] # (id, value)
652 self
.fPlayingNotes
= [] # (channel, note)
654 self
.fTabIconOff
= QIcon(":/scalable/led_off.svg")
655 self
.fTabIconOn
= QIcon(":/scalable/led_yellow.svg")
656 self
.fTabIconTimers
= []
658 # used during testing
659 self
.fIdleTimerId
= 0
661 # -------------------------------------------------------------
664 labelPluginFont
= self
.ui
.label_plugin
.font()
665 labelPluginFont
.setPixelSize(15)
666 labelPluginFont
.setWeight(75)
667 self
.ui
.label_plugin
.setFont(labelPluginFont
)
669 self
.ui
.dial_drywet
.setCustomPaintMode(self
.ui
.dial_drywet
.CUSTOM_PAINT_MODE_CARLA_WET
)
670 self
.ui
.dial_drywet
.setImage(3)
671 self
.ui
.dial_drywet
.setLabel("Dry/Wet")
672 self
.ui
.dial_drywet
.setMinimum(0.0)
673 self
.ui
.dial_drywet
.setMaximum(1.0)
674 self
.ui
.dial_drywet
.setValue(host
.get_internal_parameter_value(pluginId
, PARAMETER_DRYWET
))
676 self
.ui
.dial_vol
.setCustomPaintMode(self
.ui
.dial_vol
.CUSTOM_PAINT_MODE_CARLA_VOL
)
677 self
.ui
.dial_vol
.setImage(3)
678 self
.ui
.dial_vol
.setLabel("Volume")
679 self
.ui
.dial_vol
.setMinimum(0.0)
680 self
.ui
.dial_vol
.setMaximum(1.27)
681 self
.ui
.dial_vol
.setValue(host
.get_internal_parameter_value(pluginId
, PARAMETER_VOLUME
))
683 self
.ui
.dial_b_left
.setCustomPaintMode(self
.ui
.dial_b_left
.CUSTOM_PAINT_MODE_CARLA_L
)
684 self
.ui
.dial_b_left
.setImage(4)
685 self
.ui
.dial_b_left
.setLabel("L")
686 self
.ui
.dial_b_left
.setMinimum(-1.0)
687 self
.ui
.dial_b_left
.setMaximum(1.0)
688 self
.ui
.dial_b_left
.setValue(host
.get_internal_parameter_value(pluginId
, PARAMETER_BALANCE_LEFT
))
690 self
.ui
.dial_b_right
.setCustomPaintMode(self
.ui
.dial_b_right
.CUSTOM_PAINT_MODE_CARLA_R
)
691 self
.ui
.dial_b_right
.setImage(4)
692 self
.ui
.dial_b_right
.setLabel("R")
693 self
.ui
.dial_b_right
.setMinimum(-1.0)
694 self
.ui
.dial_b_right
.setMaximum(1.0)
695 self
.ui
.dial_b_right
.setValue(host
.get_internal_parameter_value(pluginId
, PARAMETER_BALANCE_RIGHT
))
697 self
.ui
.dial_pan
.setCustomPaintMode(self
.ui
.dial_b_right
.CUSTOM_PAINT_MODE_CARLA_PAN
)
698 self
.ui
.dial_pan
.setImage(4)
699 self
.ui
.dial_pan
.setLabel("Pan")
700 self
.ui
.dial_pan
.setMinimum(-1.0)
701 self
.ui
.dial_pan
.setMaximum(1.0)
702 self
.ui
.dial_pan
.setValue(host
.get_internal_parameter_value(pluginId
, PARAMETER_PANNING
))
704 self
.ui
.sb_ctrl_channel
.setValue(self
.fControlChannel
+1)
706 self
.ui
.scrollArea
= PixmapKeyboardHArea(self
)
707 self
.ui
.keyboard
= self
.ui
.scrollArea
.keyboard
708 self
.ui
.keyboard
.setEnabled(self
.fControlChannel
>= 0)
709 self
.layout().addWidget(self
.ui
.scrollArea
)
711 self
.ui
.scrollArea
.setEnabled(False)
712 self
.ui
.scrollArea
.setVisible(False)
715 self
.ui
.rb_balance
.setEnabled(False)
716 self
.ui
.rb_balance
.setVisible(False)
717 self
.ui
.rb_pan
.setEnabled(False)
718 self
.ui
.rb_pan
.setVisible(False)
720 flags
= self
.windowFlags()
721 flags
&= ~Qt
.WindowContextHelpButtonHint
722 self
.setWindowFlags(flags
)
726 self
.fFirstInit
= False
728 # -------------------------------------------------------------
731 self
.finished
.connect(self
.slot_finished
)
733 self
.ui
.ch_fixed_buffer
.clicked
.connect(self
.slot_optionChanged
)
734 self
.ui
.ch_force_stereo
.clicked
.connect(self
.slot_optionChanged
)
735 self
.ui
.ch_map_program_changes
.clicked
.connect(self
.slot_optionChanged
)
736 self
.ui
.ch_use_chunks
.clicked
.connect(self
.slot_optionChanged
)
737 self
.ui
.ch_send_notes
.clicked
.connect(self
.slot_optionChanged
)
738 self
.ui
.ch_send_program_changes
.clicked
.connect(self
.slot_optionChanged
)
739 self
.ui
.ch_send_control_changes
.clicked
.connect(self
.slot_optionChanged
)
740 self
.ui
.ch_send_channel_pressure
.clicked
.connect(self
.slot_optionChanged
)
741 self
.ui
.ch_send_note_aftertouch
.clicked
.connect(self
.slot_optionChanged
)
742 self
.ui
.ch_send_pitchbend
.clicked
.connect(self
.slot_optionChanged
)
743 self
.ui
.ch_send_all_sound_off
.clicked
.connect(self
.slot_optionChanged
)
745 self
.ui
.dial_drywet
.realValueChanged
.connect(self
.slot_dryWetChanged
)
746 self
.ui
.dial_vol
.realValueChanged
.connect(self
.slot_volumeChanged
)
747 self
.ui
.dial_b_left
.realValueChanged
.connect(self
.slot_balanceLeftChanged
)
748 self
.ui
.dial_b_right
.realValueChanged
.connect(self
.slot_balanceRightChanged
)
749 self
.ui
.dial_pan
.realValueChanged
.connect(self
.slot_panChanged
)
750 self
.ui
.sb_ctrl_channel
.valueChanged
.connect(self
.slot_ctrlChannelChanged
)
752 self
.ui
.dial_drywet
.customContextMenuRequested
.connect(self
.slot_knobCustomMenu
)
753 self
.ui
.dial_vol
.customContextMenuRequested
.connect(self
.slot_knobCustomMenu
)
754 self
.ui
.dial_b_left
.customContextMenuRequested
.connect(self
.slot_knobCustomMenu
)
755 self
.ui
.dial_b_right
.customContextMenuRequested
.connect(self
.slot_knobCustomMenu
)
756 self
.ui
.dial_pan
.customContextMenuRequested
.connect(self
.slot_knobCustomMenu
)
757 self
.ui
.sb_ctrl_channel
.customContextMenuRequested
.connect(self
.slot_channelCustomMenu
)
759 self
.ui
.keyboard
.noteOn
.connect(self
.slot_noteOn
)
760 self
.ui
.keyboard
.noteOff
.connect(self
.slot_noteOff
)
762 self
.ui
.cb_programs
.currentIndexChanged
.connect(self
.slot_programIndexChanged
)
763 self
.ui
.cb_midi_programs
.currentIndexChanged
.connect(self
.slot_midiProgramIndexChanged
)
765 self
.ui
.b_save_state
.clicked
.connect(self
.slot_stateSave
)
766 self
.ui
.b_load_state
.clicked
.connect(self
.slot_stateLoad
)
768 host
.NoteOnCallback
.connect(self
.slot_handleNoteOnCallback
)
769 host
.NoteOffCallback
.connect(self
.slot_handleNoteOffCallback
)
770 host
.UpdateCallback
.connect(self
.slot_handleUpdateCallback
)
771 host
.ReloadInfoCallback
.connect(self
.slot_handleReloadInfoCallback
)
772 host
.ReloadParametersCallback
.connect(self
.slot_handleReloadParametersCallback
)
773 host
.ReloadProgramsCallback
.connect(self
.slot_handleReloadProgramsCallback
)
774 host
.ReloadAllCallback
.connect(self
.slot_handleReloadAllCallback
)
776 #------------------------------------------------------------------
778 @pyqtSlot(int, int, int, int)
779 def slot_handleNoteOnCallback(self
, pluginId
, channel
, note
, velocity
):
780 if self
.fPluginId
!= pluginId
:
783 if self
.fControlChannel
== channel
:
784 self
.ui
.keyboard
.sendNoteOn(note
, False)
786 playItem
= (channel
, note
)
788 if playItem
not in self
.fPlayingNotes
:
789 self
.fPlayingNotes
.append(playItem
)
791 if len(self
.fPlayingNotes
) == 1 and self
.fParent
is not None:
792 self
.fParent
.editDialogMidiActivityChanged(self
.fPluginId
, True)
794 @pyqtSlot(int, int, int)
795 def slot_handleNoteOffCallback(self
, pluginId
, channel
, note
):
796 if self
.fPluginId
!= pluginId
:
799 if self
.fControlChannel
== channel
:
800 self
.ui
.keyboard
.sendNoteOff(note
, False)
802 playItem
= (channel
, note
)
804 if playItem
in self
.fPlayingNotes
:
805 self
.fPlayingNotes
.remove(playItem
)
807 if self
.fPlayingNotes
and self
.fParent
is not None:
808 self
.fParent
.editDialogMidiActivityChanged(self
.fPluginId
, False)
811 def slot_handleUpdateCallback(self
, pluginId
):
812 if self
.fPluginId
== pluginId
:
816 def slot_handleReloadInfoCallback(self
, pluginId
):
817 if self
.fPluginId
== pluginId
:
821 def slot_handleReloadParametersCallback(self
, pluginId
):
822 if self
.fPluginId
== pluginId
:
823 self
.reloadParameters()
826 def slot_handleReloadProgramsCallback(self
, pluginId
):
827 if self
.fPluginId
== pluginId
:
828 self
.reloadPrograms()
831 def slot_handleReloadAllCallback(self
, pluginId
):
832 if self
.fPluginId
== pluginId
:
835 #------------------------------------------------------------------
837 def updateInfo(self
):
838 # Update current program text
839 if self
.ui
.cb_programs
.count() > 0:
840 pIndex
= self
.ui
.cb_programs
.currentIndex()
842 pName
= self
.host
.get_program_name(self
.fPluginId
, pIndex
)
843 #pName = pName[:40] + (pName[40:] and "...")
844 self
.ui
.cb_programs
.setItemText(pIndex
, pName
)
846 # Update current midi program text
847 if self
.ui
.cb_midi_programs
.count() > 0:
848 mpIndex
= self
.ui
.cb_midi_programs
.currentIndex()
850 mpData
= self
.host
.get_midi_program_data(self
.fPluginId
, mpIndex
)
851 mpBank
= mpData
['bank']
852 mpProg
= mpData
['program']
853 mpName
= mpData
['name']
854 #mpName = mpName[:40] + (mpName[40:] and "...")
855 self
.ui
.cb_midi_programs
.setItemText(mpIndex
, "%03i:%03i - %s" % (mpBank
+1, mpProg
+1, mpName
))
857 # Update all parameter values
858 for _
, paramId
, paramWidget
in self
.fParameterList
:
859 paramWidget
.blockSignals(True)
860 paramWidget
.setValue(self
.host
.get_current_parameter_value(self
.fPluginId
, paramId
))
861 paramWidget
.blockSignals(False)
863 # and the internal ones too
864 self
.ui
.dial_drywet
.blockSignals(True)
865 self
.ui
.dial_drywet
.setValue(self
.host
.get_internal_parameter_value(self
.fPluginId
, PARAMETER_DRYWET
))
866 self
.ui
.dial_drywet
.blockSignals(False)
868 self
.ui
.dial_vol
.blockSignals(True)
869 self
.ui
.dial_vol
.setValue(self
.host
.get_internal_parameter_value(self
.fPluginId
, PARAMETER_VOLUME
))
870 self
.ui
.dial_vol
.blockSignals(False)
872 self
.ui
.dial_b_left
.blockSignals(True)
873 self
.ui
.dial_b_left
.setValue(self
.host
.get_internal_parameter_value(self
.fPluginId
, PARAMETER_BALANCE_LEFT
))
874 self
.ui
.dial_b_left
.blockSignals(False)
876 self
.ui
.dial_b_right
.blockSignals(True)
877 self
.ui
.dial_b_right
.setValue(self
.host
.get_internal_parameter_value(self
.fPluginId
, PARAMETER_BALANCE_RIGHT
))
878 self
.ui
.dial_b_right
.blockSignals(False)
880 self
.ui
.dial_pan
.blockSignals(True)
881 self
.ui
.dial_pan
.setValue(self
.host
.get_internal_parameter_value(self
.fPluginId
, PARAMETER_PANNING
))
882 self
.ui
.dial_pan
.blockSignals(False)
884 self
.fControlChannel
= round(self
.host
.get_internal_parameter_value(self
.fPluginId
, PARAMETER_CTRL_CHANNEL
))
885 self
.ui
.sb_ctrl_channel
.blockSignals(True)
886 self
.ui
.sb_ctrl_channel
.setValue(self
.fControlChannel
+1)
887 self
.ui
.sb_ctrl_channel
.blockSignals(False)
888 self
.ui
.keyboard
.allNotesOff()
889 self
._updateCtrlPrograms
()
891 self
.fParametersToUpdate
= []
893 #------------------------------------------------------------------
896 self
.fPluginInfo
= self
.host
.get_plugin_info(self
.fPluginId
)
899 self
.reloadParameters()
900 self
.reloadPrograms()
902 if self
.fPluginInfo
['type'] == PLUGIN_LV2
:
903 self
.ui
.b_save_state
.setEnabled(False)
905 if not self
.ui
.scrollArea
.isEnabled():
906 self
.resize(self
.width(), self
.height()-self
.ui
.scrollArea
.height())
908 #------------------------------------------------------------------
910 def reloadInfo(self
):
911 realPluginName
= self
.host
.get_real_plugin_name(self
.fPluginId
)
912 #audioCountInfo = self.host.get_audio_port_count_info(self.fPluginId)
913 midiCountInfo
= self
.host
.get_midi_port_count_info(self
.fPluginId
)
914 #paramCountInfo = self.host.get_parameter_count_info(self.fPluginId)
916 pluginHints
= self
.fPluginInfo
['hints']
918 self
.ui
.le_type
.setText(getPluginTypeAsString(self
.fPluginInfo
['type']))
920 self
.ui
.label_name
.setEnabled(bool(realPluginName
))
921 self
.ui
.le_name
.setEnabled(bool(realPluginName
))
922 self
.ui
.le_name
.setText(realPluginName
)
923 self
.ui
.le_name
.setToolTip(realPluginName
)
925 self
.ui
.label_label
.setEnabled(bool(self
.fPluginInfo
['label']))
926 self
.ui
.le_label
.setEnabled(bool(self
.fPluginInfo
['label']))
927 self
.ui
.le_label
.setText(self
.fPluginInfo
['label'])
928 self
.ui
.le_label
.setToolTip(self
.fPluginInfo
['label'])
930 self
.ui
.label_maker
.setEnabled(bool(self
.fPluginInfo
['maker']))
931 self
.ui
.le_maker
.setEnabled(bool(self
.fPluginInfo
['maker']))
932 self
.ui
.le_maker
.setText(self
.fPluginInfo
['maker'])
933 self
.ui
.le_maker
.setToolTip(self
.fPluginInfo
['maker'])
935 self
.ui
.label_copyright
.setEnabled(bool(self
.fPluginInfo
['copyright']))
936 self
.ui
.le_copyright
.setEnabled(bool(self
.fPluginInfo
['copyright']))
937 self
.ui
.le_copyright
.setText(self
.fPluginInfo
['copyright'])
938 self
.ui
.le_copyright
.setToolTip(self
.fPluginInfo
['copyright'])
940 self
.ui
.label_unique_id
.setEnabled(bool(self
.fPluginInfo
['uniqueId']))
941 self
.ui
.le_unique_id
.setEnabled(bool(self
.fPluginInfo
['uniqueId']))
942 self
.ui
.le_unique_id
.setText(str(self
.fPluginInfo
['uniqueId']))
943 self
.ui
.le_unique_id
.setToolTip(str(self
.fPluginInfo
['uniqueId']))
945 self
.ui
.label_plugin
.setText("\n%s\n" % (self
.fPluginInfo
['name'] or "(none)"))
946 self
.setWindowTitle(self
.fPluginInfo
['name'] or "(none)")
948 self
.ui
.dial_drywet
.setEnabled(pluginHints
& PLUGIN_CAN_DRYWET
)
949 self
.ui
.dial_vol
.setEnabled(pluginHints
& PLUGIN_CAN_VOLUME
)
950 self
.ui
.dial_b_left
.setEnabled(pluginHints
& PLUGIN_CAN_BALANCE
)
951 self
.ui
.dial_b_right
.setEnabled(pluginHints
& PLUGIN_CAN_BALANCE
)
952 self
.ui
.dial_pan
.setEnabled(pluginHints
& PLUGIN_CAN_PANNING
)
954 optsAvailable
= self
.fPluginInfo
['optionsAvailable']
955 optsEnabled
= self
.fPluginInfo
['optionsEnabled']
956 self
.ui
.ch_use_chunks
.setEnabled(optsAvailable
& PLUGIN_OPTION_USE_CHUNKS
)
957 self
.ui
.ch_use_chunks
.setChecked(optsEnabled
& PLUGIN_OPTION_USE_CHUNKS
)
958 self
.ui
.ch_fixed_buffer
.setEnabled(optsAvailable
& PLUGIN_OPTION_FIXED_BUFFERS
)
959 self
.ui
.ch_fixed_buffer
.setChecked(optsEnabled
& PLUGIN_OPTION_FIXED_BUFFERS
)
960 self
.ui
.ch_force_stereo
.setEnabled(optsAvailable
& PLUGIN_OPTION_FORCE_STEREO
)
961 self
.ui
.ch_force_stereo
.setChecked(optsEnabled
& PLUGIN_OPTION_FORCE_STEREO
)
962 self
.ui
.ch_map_program_changes
.setEnabled(optsAvailable
& PLUGIN_OPTION_MAP_PROGRAM_CHANGES
)
963 self
.ui
.ch_map_program_changes
.setChecked(optsEnabled
& PLUGIN_OPTION_MAP_PROGRAM_CHANGES
)
964 self
.ui
.ch_send_notes
.setEnabled(optsAvailable
& PLUGIN_OPTION_SKIP_SENDING_NOTES
)
965 self
.ui
.ch_send_notes
.setChecked((self
.ui
.ch_send_notes
.isEnabled() and
966 (optsEnabled
& PLUGIN_OPTION_SKIP_SENDING_NOTES
) == 0x0))
967 self
.ui
.ch_send_control_changes
.setEnabled(optsAvailable
& PLUGIN_OPTION_SEND_CONTROL_CHANGES
)
968 self
.ui
.ch_send_control_changes
.setChecked(optsEnabled
& PLUGIN_OPTION_SEND_CONTROL_CHANGES
)
969 self
.ui
.ch_send_channel_pressure
.setEnabled(optsAvailable
& PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
)
970 self
.ui
.ch_send_channel_pressure
.setChecked(optsEnabled
& PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
)
971 self
.ui
.ch_send_note_aftertouch
.setEnabled(optsAvailable
& PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
)
972 self
.ui
.ch_send_note_aftertouch
.setChecked(optsEnabled
& PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
)
973 self
.ui
.ch_send_pitchbend
.setEnabled(optsAvailable
& PLUGIN_OPTION_SEND_PITCHBEND
)
974 self
.ui
.ch_send_pitchbend
.setChecked(optsEnabled
& PLUGIN_OPTION_SEND_PITCHBEND
)
975 self
.ui
.ch_send_all_sound_off
.setEnabled(optsAvailable
& PLUGIN_OPTION_SEND_ALL_SOUND_OFF
)
976 self
.ui
.ch_send_all_sound_off
.setChecked(optsEnabled
& PLUGIN_OPTION_SEND_ALL_SOUND_OFF
)
978 canSendPrograms
= bool((optsAvailable
& PLUGIN_OPTION_SEND_PROGRAM_CHANGES
) != 0 and
979 (optsEnabled
& PLUGIN_OPTION_MAP_PROGRAM_CHANGES
) == 0)
980 self
.ui
.ch_send_program_changes
.setEnabled(canSendPrograms
)
981 self
.ui
.ch_send_program_changes
.setChecked(optsEnabled
& PLUGIN_OPTION_SEND_PROGRAM_CHANGES
)
983 self
.ui
.sw_programs
.setCurrentIndex(0 if self
.fPluginInfo
['type'] in (PLUGIN_VST2
, PLUGIN_SFZ
) else 1)
986 showKeyboard
= (self
.fPluginInfo
['category'] == PLUGIN_CATEGORY_SYNTH
or midiCountInfo
['ins'] > 0)
987 self
.ui
.scrollArea
.setEnabled(showKeyboard
)
988 self
.ui
.scrollArea
.setVisible(showKeyboard
)
990 # Force-update parent for new hints
991 if self
.fParent
is not None and not self
.fFirstInit
:
992 self
.fParent
.editDialogPluginHintsChanged(self
.fPluginId
, pluginHints
)
994 def reloadParameters(self
):
996 self
.fParameterList
= []
997 self
.fParametersToUpdate
= []
998 self
.fTabIconTimers
= []
1000 # Save current tab state
1001 tabIndex
= self
.ui
.tabWidget
.currentIndex()
1002 tabWidget
= self
.ui
.tabWidget
.currentWidget()
1003 scrollVal
= tabWidget
.verticalScrollBar().value() if isinstance(tabWidget
, QScrollArea
) else None
1006 # Remove all previous parameters
1007 for _
in range(self
.ui
.tabWidget
.count()-1):
1008 self
.ui
.tabWidget
.widget(1).deleteLater()
1009 self
.ui
.tabWidget
.removeTab(1)
1011 parameterCount
= self
.host
.get_parameter_count(self
.fPluginId
)
1013 # -----------------------------------------------------------------
1015 if parameterCount
<= 0:
1018 # -----------------------------------------------------------------
1021 paramOutputList
= []
1023 paramOutputWidth
= 0
1024 unusedParameters
= 0
1026 paramInputListFull
= [] # ([params], width)
1027 paramOutputListFull
= [] # ([params], width)
1029 for i
in range(parameterCount
):
1030 if i
- unusedParameters
== self
.host
.maxParameters
:
1033 paramData
= self
.host
.get_parameter_data(self
.fPluginId
, i
)
1035 if paramData
['type'] not in (PARAMETER_INPUT
, PARAMETER_OUTPUT
):
1036 unusedParameters
+= 1
1038 if (paramData
['hints'] & PARAMETER_IS_ENABLED
) == 0:
1039 unusedParameters
+= 1
1042 paramInfo
= self
.host
.get_parameter_info(self
.fPluginId
, i
)
1043 paramRanges
= self
.host
.get_parameter_ranges(self
.fPluginId
, i
)
1044 paramValue
= self
.host
.get_current_parameter_value(self
.fPluginId
, i
)
1047 'type': paramData
['type'],
1048 'hints': paramData
['hints'],
1049 'name': paramInfo
['name'],
1050 'unit': paramInfo
['unit'],
1053 'index': paramData
['index'],
1054 'default': paramRanges
['def'],
1055 'minimum': paramRanges
['min'],
1056 'maximum': paramRanges
['max'],
1057 'step': paramRanges
['step'],
1058 'stepSmall': paramRanges
['stepSmall'],
1059 'stepLarge': paramRanges
['stepLarge'],
1060 'mappedControlIndex': paramData
['mappedControlIndex'],
1061 'mappedMinimum': paramData
['mappedMinimum'],
1062 'mappedMaximum': paramData
['mappedMaximum'],
1063 'midiChannel': paramData
['midiChannel']+1,
1065 'comment': paramInfo
['comment'],
1066 'groupName': paramInfo
['groupName'],
1068 'current': paramValue
1071 for j
in range(paramInfo
['scalePointCount']):
1072 scalePointInfo
= self
.host
.get_parameter_scalepoint_info(self
.fPluginId
, i
, j
)
1074 parameter
['scalePoints'].append({
1075 'value': scalePointInfo
['value'],
1076 'label': scalePointInfo
['label']
1079 #parameter['name'] = parameter['name'][:30] + (parameter['name'][30:] and "...")
1081 # -----------------------------------------------------------------
1082 # Get width values, in packs of 20
1084 if parameter
['type'] == PARAMETER_INPUT
:
1085 paramInputWidthTMP
= fontMetricsHorizontalAdvance(self
.fontMetrics(), parameter
['name'])
1087 if paramInputWidthTMP
> paramInputWidth
:
1088 paramInputWidth
= paramInputWidthTMP
1090 paramInputList
.append(parameter
)
1093 paramOutputWidthTMP
= fontMetricsHorizontalAdvance(self
.fontMetrics(), parameter
['name'])
1095 if paramOutputWidthTMP
> paramOutputWidth
:
1096 paramOutputWidth
= paramOutputWidthTMP
1098 paramOutputList
.append(parameter
)
1100 paramInputListFull
.append((paramInputList
, paramInputWidth
))
1101 paramOutputListFull
.append((paramOutputList
, paramOutputWidth
))
1103 # Create parameter tabs + widgets
1104 self
._createParameterWidgets
(PARAMETER_INPUT
, paramInputListFull
, self
.tr("Parameters"))
1105 self
._createParameterWidgets
(PARAMETER_OUTPUT
, paramOutputListFull
, self
.tr("Outputs"))
1108 if tabIndex
< self
.ui
.tabWidget
.count():
1109 self
.ui
.tabWidget
.setCurrentIndex(tabIndex
)
1110 if scrollVal
is not None:
1111 self
.ui
.tabWidget
.currentWidget().verticalScrollBar().setValue(scrollVal
)
1113 def reloadPrograms(self
):
1115 self
.ui
.cb_programs
.blockSignals(True)
1116 self
.ui
.cb_programs
.clear()
1118 programCount
= self
.host
.get_program_count(self
.fPluginId
)
1120 if programCount
> 0:
1121 self
.ui
.cb_programs
.setEnabled(True)
1122 self
.ui
.label_programs
.setEnabled(True)
1124 for i
in range(programCount
):
1125 pName
= self
.host
.get_program_name(self
.fPluginId
, i
)
1126 #pName = pName[:40] + (pName[40:] and "...")
1127 self
.ui
.cb_programs
.addItem(pName
)
1129 self
.ui
.cb_programs
.setCurrentIndex(self
.host
.get_current_program_index(self
.fPluginId
))
1132 self
.ui
.cb_programs
.setEnabled(False)
1133 self
.ui
.label_programs
.setEnabled(False)
1135 self
.ui
.cb_programs
.blockSignals(False)
1138 self
.ui
.cb_midi_programs
.blockSignals(True)
1139 self
.ui
.cb_midi_programs
.clear()
1141 midiProgramCount
= self
.host
.get_midi_program_count(self
.fPluginId
)
1143 if midiProgramCount
> 0:
1144 self
.ui
.cb_midi_programs
.setEnabled(True)
1145 self
.ui
.label_midi_programs
.setEnabled(True)
1147 for i
in range(midiProgramCount
):
1148 mpData
= self
.host
.get_midi_program_data(self
.fPluginId
, i
)
1149 mpBank
= mpData
['bank']
1150 mpProg
= mpData
['program']
1151 mpName
= mpData
['name']
1152 #mpName = mpName[:40] + (mpName[40:] and "...")
1154 self
.ui
.cb_midi_programs
.addItem("%03i:%03i - %s" % (mpBank
+1, mpProg
+1, mpName
))
1156 self
.ui
.cb_midi_programs
.setCurrentIndex(self
.host
.get_current_midi_program_index(self
.fPluginId
))
1159 self
.ui
.cb_midi_programs
.setEnabled(False)
1160 self
.ui
.label_midi_programs
.setEnabled(False)
1162 self
.ui
.cb_midi_programs
.blockSignals(False)
1164 self
.ui
.sw_programs
.setEnabled(programCount
> 0 or midiProgramCount
> 0)
1166 if self
.fPluginInfo
['type'] == PLUGIN_LV2
:
1167 self
.ui
.b_load_state
.setEnabled(programCount
> 0)
1169 #------------------------------------------------------------------
1171 def clearNotes(self
):
1172 self
.fPlayingNotes
= []
1173 self
.ui
.keyboard
.allNotesOff()
1175 def noteOn(self
, channel
, note
, velocity
):
1176 if self
.fControlChannel
== channel
:
1177 self
.ui
.keyboard
.sendNoteOn(note
, False)
1179 def noteOff(self
, channel
, note
):
1180 if self
.fControlChannel
== channel
:
1181 self
.ui
.keyboard
.sendNoteOff(note
, False)
1183 #------------------------------------------------------------------
1186 return self
.fPluginInfo
['hints']
1188 def setPluginId(self
, idx
):
1189 self
.fPluginId
= idx
1191 def setName(self
, name
):
1192 self
.fPluginInfo
['name'] = name
1193 self
.ui
.label_plugin
.setText("\n%s\n" % name
)
1194 self
.setWindowTitle(name
)
1196 #------------------------------------------------------------------
1198 def setParameterValue(self
, parameterId
, value
):
1199 for paramItem
in self
.fParametersToUpdate
:
1200 if paramItem
[0] == parameterId
:
1201 paramItem
[1] = value
1204 self
.fParametersToUpdate
.append([parameterId
, value
])
1206 def setParameterDefault(self
, parameterId
, value
):
1207 for _
, paramId
, paramWidget
in self
.fParameterList
:
1208 if paramId
== parameterId
:
1209 paramWidget
.setDefault(value
)
1212 def setParameterMappedControlIndex(self
, parameterId
, control
):
1213 for _
, paramId
, paramWidget
in self
.fParameterList
:
1214 if paramId
== parameterId
:
1215 paramWidget
.setMappedControlIndex(control
)
1218 def setParameterMappedRange(self
, parameterId
, minimum
, maximum
):
1219 for _
, paramId
, paramWidget
in self
.fParameterList
:
1220 if paramId
== parameterId
:
1221 paramWidget
.setMappedRange(minimum
, maximum
)
1224 def setParameterMidiChannel(self
, parameterId
, channel
):
1225 for _
, paramId
, paramWidget
in self
.fParameterList
:
1226 if paramId
== parameterId
:
1227 paramWidget
.setMidiChannel(channel
+1)
1230 def setProgram(self
, index
):
1231 self
.ui
.cb_programs
.blockSignals(True)
1232 self
.ui
.cb_programs
.setCurrentIndex(index
)
1233 self
.ui
.cb_programs
.blockSignals(False)
1234 self
._updateParameterValues
()
1236 def setMidiProgram(self
, index
):
1237 self
.ui
.cb_midi_programs
.blockSignals(True)
1238 self
.ui
.cb_midi_programs
.setCurrentIndex(index
)
1239 self
.ui
.cb_midi_programs
.blockSignals(False)
1240 self
._updateParameterValues
()
1242 def setOption(self
, option
, yesNo
):
1243 if option
== PLUGIN_OPTION_USE_CHUNKS
:
1244 widget
= self
.ui
.ch_use_chunks
1245 elif option
== PLUGIN_OPTION_FIXED_BUFFERS
:
1246 widget
= self
.ui
.ch_fixed_buffer
1247 elif option
== PLUGIN_OPTION_FORCE_STEREO
:
1248 widget
= self
.ui
.ch_force_stereo
1249 elif option
== PLUGIN_OPTION_MAP_PROGRAM_CHANGES
:
1250 widget
= self
.ui
.ch_map_program_changes
1251 elif option
== PLUGIN_OPTION_SKIP_SENDING_NOTES
:
1252 widget
= self
.ui
.ch_send_notes
1254 elif option
== PLUGIN_OPTION_SEND_PROGRAM_CHANGES
:
1255 widget
= self
.ui
.ch_send_program_changes
1256 elif option
== PLUGIN_OPTION_SEND_CONTROL_CHANGES
:
1257 widget
= self
.ui
.ch_send_control_changes
1258 elif option
== PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
:
1259 widget
= self
.ui
.ch_send_channel_pressure
1260 elif option
== PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
:
1261 widget
= self
.ui
.ch_send_note_aftertouch
1262 elif option
== PLUGIN_OPTION_SEND_PITCHBEND
:
1263 widget
= self
.ui
.ch_send_pitchbend
1264 elif option
== PLUGIN_OPTION_SEND_ALL_SOUND_OFF
:
1265 widget
= self
.ui
.ch_send_all_sound_off
1269 widget
.blockSignals(True)
1270 widget
.setChecked(yesNo
)
1271 widget
.blockSignals(False)
1273 #------------------------------------------------------------------
1275 def setVisible(self
, yesNo
):
1277 if not self
.fGeometry
.isNull():
1278 self
.restoreGeometry(self
.fGeometry
)
1280 self
.fGeometry
= self
.saveGeometry()
1282 QDialog
.setVisible(self
, yesNo
)
1285 parent
= self
.parent()
1288 gCarla
.utils
.cocoa_set_transient_window_for(self
.windowHandle().winId(), parent
.windowHandle().winId())
1290 #------------------------------------------------------------------
1294 for i
in range(len(self
.fTabIconTimers
)):
1295 if self
.fTabIconTimers
[i
] == ICON_STATE_ON
:
1296 self
.fTabIconTimers
[i
] = ICON_STATE_WAIT
1297 elif self
.fTabIconTimers
[i
] == ICON_STATE_WAIT
:
1298 self
.fTabIconTimers
[i
] = ICON_STATE_OFF
1299 elif self
.fTabIconTimers
[i
] == ICON_STATE_OFF
:
1300 self
.fTabIconTimers
[i
] = ICON_STATE_NULL
1301 self
.ui
.tabWidget
.setTabIcon(i
+1, self
.fTabIconOff
)
1303 # Check parameters needing update
1304 for index
, value
in self
.fParametersToUpdate
:
1305 if index
== PARAMETER_DRYWET
:
1306 self
.ui
.dial_drywet
.blockSignals(True)
1307 self
.ui
.dial_drywet
.setValue(value
)
1308 self
.ui
.dial_drywet
.blockSignals(False)
1310 elif index
== PARAMETER_VOLUME
:
1311 self
.ui
.dial_vol
.blockSignals(True)
1312 self
.ui
.dial_vol
.setValue(value
)
1313 self
.ui
.dial_vol
.blockSignals(False)
1315 elif index
== PARAMETER_BALANCE_LEFT
:
1316 self
.ui
.dial_b_left
.blockSignals(True)
1317 self
.ui
.dial_b_left
.setValue(value
)
1318 self
.ui
.dial_b_left
.blockSignals(False)
1320 elif index
== PARAMETER_BALANCE_RIGHT
:
1321 self
.ui
.dial_b_right
.blockSignals(True)
1322 self
.ui
.dial_b_right
.setValue(value
)
1323 self
.ui
.dial_b_right
.blockSignals(False)
1325 elif index
== PARAMETER_PANNING
:
1326 self
.ui
.dial_pan
.blockSignals(True)
1327 self
.ui
.dial_pan
.setValue(value
)
1328 self
.ui
.dial_pan
.blockSignals(False)
1330 elif index
== PARAMETER_CTRL_CHANNEL
:
1331 self
.fControlChannel
= round(value
)
1332 self
.ui
.sb_ctrl_channel
.blockSignals(True)
1333 self
.ui
.sb_ctrl_channel
.setValue(self
.fControlChannel
+1)
1334 self
.ui
.sb_ctrl_channel
.blockSignals(False)
1335 self
.ui
.keyboard
.allNotesOff()
1336 self
._updateCtrlPrograms
()
1339 for paramType
, paramId
, paramWidget
in self
.fParameterList
:
1340 if paramId
!= index
:
1343 if paramType
!= PARAMETER_INPUT
:
1346 paramWidget
.blockSignals(True)
1347 paramWidget
.setValue(value
)
1348 paramWidget
.blockSignals(False)
1350 #if paramType == PARAMETER_INPUT:
1351 tabIndex
= paramWidget
.getTabIndex()
1353 if self
.fTabIconTimers
[tabIndex
-1] == ICON_STATE_NULL
:
1354 self
.ui
.tabWidget
.setTabIcon(tabIndex
, self
.fTabIconOn
)
1356 self
.fTabIconTimers
[tabIndex
-1] = ICON_STATE_ON
1359 # Clear all parameters
1360 self
.fParametersToUpdate
= []
1362 # Update parameter outputs | FIXME needed?
1363 for paramType
, paramId
, paramWidget
in self
.fParameterList
:
1364 if paramType
!= PARAMETER_OUTPUT
:
1367 paramWidget
.blockSignals(True)
1368 paramWidget
.setValue(self
.host
.get_current_parameter_value(self
.fPluginId
, paramId
))
1369 paramWidget
.blockSignals(False)
1371 #------------------------------------------------------------------
1374 def slot_stateSave(self
):
1375 if self
.fPluginInfo
['type'] == PLUGIN_LV2
:
1379 if self
.fCurrentStateFilename
:
1380 askTry
= QMessageBox
.question(self
,
1381 self
.tr("Overwrite?"),
1382 self
.tr("Overwrite previously created file?"),
1383 QMessageBox
.Ok|QMessageBox
.Cancel
)
1385 if askTry
== QMessageBox
.Ok
:
1386 self
.host
.save_plugin_state(self
.fPluginId
, self
.fCurrentStateFilename
)
1389 self
.fCurrentStateFilename
= None
1391 fileFilter
= self
.tr("Carla State File (*.carxs)")
1392 filename
, _
= QFileDialog
.getSaveFileName(self
, self
.tr("Save Plugin State File"), filter=fileFilter
)
1394 # FIXME use ok value, test if it works as expected
1398 if not filename
.lower().endswith(".carxs"):
1399 filename
+= ".carxs"
1401 self
.fCurrentStateFilename
= filename
1402 self
.host
.save_plugin_state(self
.fPluginId
, self
.fCurrentStateFilename
)
1405 def slot_stateLoad(self
):
1406 if self
.fPluginInfo
['type'] == PLUGIN_LV2
:
1409 for i
in range(self
.host
.get_program_count(self
.fPluginId
)):
1410 presetList
.append("%03i - %s" % (i
+1, self
.host
.get_program_name(self
.fPluginId
, i
)))
1412 ret
= QInputDialog
.getItem(self
,
1413 self
.tr("Open LV2 Preset"),
1414 self
.tr("Select an LV2 Preset:"),
1415 presetList
, 0, False)
1418 index
= int(ret
[0].split(" - ", 1)[0])-1
1419 self
.host
.set_program(self
.fPluginId
, index
)
1420 self
.setMidiProgram(-1)
1424 fileFilter
= self
.tr("Carla State File (*.carxs)")
1425 filename
, _
= QFileDialog
.getOpenFileName(self
, self
.tr("Open Plugin State File"), filter=fileFilter
)
1427 # FIXME use ok value, test if it works as expected
1431 self
.fCurrentStateFilename
= filename
1432 self
.host
.load_plugin_state(self
.fPluginId
, self
.fCurrentStateFilename
)
1434 #------------------------------------------------------------------
1437 def slot_optionChanged(self
, clicked
):
1438 sender
= self
.sender()
1440 if sender
== self
.ui
.ch_use_chunks
:
1441 option
= PLUGIN_OPTION_USE_CHUNKS
1442 elif sender
== self
.ui
.ch_fixed_buffer
:
1443 option
= PLUGIN_OPTION_FIXED_BUFFERS
1444 elif sender
== self
.ui
.ch_force_stereo
:
1445 option
= PLUGIN_OPTION_FORCE_STEREO
1446 elif sender
== self
.ui
.ch_map_program_changes
:
1447 option
= PLUGIN_OPTION_MAP_PROGRAM_CHANGES
1448 elif sender
== self
.ui
.ch_send_notes
:
1449 option
= PLUGIN_OPTION_SKIP_SENDING_NOTES
1450 clicked
= not clicked
1451 elif sender
== self
.ui
.ch_send_program_changes
:
1452 option
= PLUGIN_OPTION_SEND_PROGRAM_CHANGES
1453 elif sender
== self
.ui
.ch_send_control_changes
:
1454 option
= PLUGIN_OPTION_SEND_CONTROL_CHANGES
1455 elif sender
== self
.ui
.ch_send_channel_pressure
:
1456 option
= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
1457 elif sender
== self
.ui
.ch_send_note_aftertouch
:
1458 option
= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
1459 elif sender
== self
.ui
.ch_send_pitchbend
:
1460 option
= PLUGIN_OPTION_SEND_PITCHBEND
1461 elif sender
== self
.ui
.ch_send_all_sound_off
:
1462 option
= PLUGIN_OPTION_SEND_ALL_SOUND_OFF
1466 #--------------------------------------------------------------
1467 # handle map-program-changes and send-program-changes conflict
1469 if option
== PLUGIN_OPTION_MAP_PROGRAM_CHANGES
and clicked
:
1470 self
.ui
.ch_send_program_changes
.setEnabled(False)
1472 # disable send-program-changes if needed
1473 if self
.ui
.ch_send_program_changes
.isChecked():
1474 self
.host
.set_option(self
.fPluginId
, PLUGIN_OPTION_SEND_PROGRAM_CHANGES
, False)
1476 #--------------------------------------------------------------
1479 self
.host
.set_option(self
.fPluginId
, option
, clicked
)
1481 #--------------------------------------------------------------
1482 # handle map-program-changes and send-program-changes conflict
1484 if option
== PLUGIN_OPTION_MAP_PROGRAM_CHANGES
and not clicked
:
1485 self
.ui
.ch_send_program_changes
.setEnabled(
1486 self
.fPluginInfo
['optionsAvailable'] & PLUGIN_OPTION_SEND_PROGRAM_CHANGES
)
1488 # restore send-program-changes if needed
1489 if self
.ui
.ch_send_program_changes
.isChecked():
1490 self
.host
.set_option(self
.fPluginId
, PLUGIN_OPTION_SEND_PROGRAM_CHANGES
, True)
1492 #------------------------------------------------------------------
1495 def slot_dryWetChanged(self
, value
):
1496 self
.host
.set_drywet(self
.fPluginId
, value
)
1498 if self
.fParent
is not None:
1499 self
.fParent
.editDialogParameterValueChanged(self
.fPluginId
, PARAMETER_DRYWET
, value
)
1502 def slot_volumeChanged(self
, value
):
1503 self
.host
.set_volume(self
.fPluginId
, value
)
1505 if self
.fParent
is not None:
1506 self
.fParent
.editDialogParameterValueChanged(self
.fPluginId
, PARAMETER_VOLUME
, value
)
1509 def slot_balanceLeftChanged(self
, value
):
1510 self
.host
.set_balance_left(self
.fPluginId
, value
)
1512 if self
.fParent
is not None:
1513 self
.fParent
.editDialogParameterValueChanged(self
.fPluginId
, PARAMETER_BALANCE_LEFT
, value
)
1516 def slot_balanceRightChanged(self
, value
):
1517 self
.host
.set_balance_right(self
.fPluginId
, value
)
1519 if self
.fParent
is not None:
1520 self
.fParent
.editDialogParameterValueChanged(self
.fPluginId
, PARAMETER_BALANCE_RIGHT
, value
)
1523 def slot_panChanged(self
, value
):
1524 self
.host
.set_panning(self
.fPluginId
, value
)
1526 if self
.fParent
is not None:
1527 self
.fParent
.editDialogParameterValueChanged(self
.fPluginId
, PARAMETER_PANNING
, value
)
1530 def slot_ctrlChannelChanged(self
, value
):
1531 self
.fControlChannel
= value
-1
1532 self
.host
.set_ctrl_channel(self
.fPluginId
, self
.fControlChannel
)
1534 self
.ui
.keyboard
.allNotesOff()
1535 self
._updateCtrlPrograms
()
1537 #------------------------------------------------------------------
1539 @pyqtSlot(int, float)
1540 def slot_parameterValueChanged(self
, parameterId
, value
):
1541 self
.host
.set_parameter_value(self
.fPluginId
, parameterId
, value
)
1543 if self
.fParent
is not None:
1544 self
.fParent
.editDialogParameterValueChanged(self
.fPluginId
, parameterId
, value
)
1547 def slot_parameterMappedControlChanged(self
, parameterId
, control
):
1548 self
.host
.set_parameter_mapped_control_index(self
.fPluginId
, parameterId
, control
)
1550 @pyqtSlot(int, float, float)
1551 def slot_parameterMappedRangeChanged(self
, parameterId
, minimum
, maximum
):
1552 self
.host
.set_parameter_mapped_range(self
.fPluginId
, parameterId
, minimum
, maximum
)
1555 def slot_parameterMidiChannelChanged(self
, parameterId
, channel
):
1556 self
.host
.set_parameter_midi_channel(self
.fPluginId
, parameterId
, channel
-1)
1558 #------------------------------------------------------------------
1561 def slot_programIndexChanged(self
, index
):
1562 self
.host
.set_program(self
.fPluginId
, index
)
1564 if self
.fParent
is not None:
1565 self
.fParent
.editDialogProgramChanged(self
.fPluginId
, index
)
1567 self
._updateParameterValues
()
1570 def slot_midiProgramIndexChanged(self
, index
):
1571 self
.host
.set_midi_program(self
.fPluginId
, index
)
1573 if self
.fParent
is not None:
1574 self
.fParent
.editDialogMidiProgramChanged(self
.fPluginId
, index
)
1576 self
._updateParameterValues
()
1578 #------------------------------------------------------------------
1581 def slot_noteOn(self
, note
):
1582 if self
.fControlChannel
>= 0:
1583 self
.host
.send_midi_note(self
.fPluginId
, self
.fControlChannel
, note
, 100)
1585 if self
.fParent
is not None:
1586 self
.fParent
.editDialogNotePressed(self
.fPluginId
, note
)
1589 def slot_noteOff(self
, note
):
1590 if self
.fControlChannel
>= 0:
1591 self
.host
.send_midi_note(self
.fPluginId
, self
.fControlChannel
, note
, 0)
1593 if self
.fParent
is not None:
1594 self
.fParent
.editDialogNoteReleased(self
.fPluginId
, note
)
1596 #------------------------------------------------------------------
1599 def slot_finished(self
):
1600 if self
.fParent
is not None:
1601 self
.fParent
.editDialogVisibilityChanged(self
.fPluginId
, False)
1603 #------------------------------------------------------------------
1606 def slot_knobCustomMenu(self
):
1607 sender
= self
.sender()
1608 knobName
= sender
.objectName()
1610 if knobName
== "dial_drywet":
1615 elif knobName
== "dial_vol":
1620 elif knobName
== "dial_b_left":
1624 label
= "Balance-Left"
1625 elif knobName
== "dial_b_right":
1629 label
= "Balance-Right"
1630 elif knobName
== "dial_pan":
1642 actReset
= menu
.addAction(self
.tr("Reset (%i%%)" % (default
*100)))
1644 actMinimum
= menu
.addAction(self
.tr("Set to Minimum (%i%%)" % (minimum
*100)))
1645 actCenter
= menu
.addAction(self
.tr("Set to Center"))
1646 actMaximum
= menu
.addAction(self
.tr("Set to Maximum (%i%%)" % (maximum
*100)))
1648 actSet
= menu
.addAction(self
.tr("Set value..."))
1650 if label
not in ("Balance-Left", "Balance-Right", "Panning"):
1651 menu
.removeAction(actCenter
)
1653 actSelected
= menu
.exec_(QCursor
.pos())
1655 if actSelected
== actSet
:
1656 current
= minimum
+ (maximum
-minimum
)*(float(sender
.value())/10000)
1657 value
, ok
= QInputDialog
.getInt(self
,
1658 self
.tr("Set value"),
1660 round(current
*100.0),
1661 round(minimum
*100.0),
1662 round(maximum
*100.0),
1665 value
= float(value
)/100.0
1670 elif actSelected
== actMinimum
:
1672 elif actSelected
== actMaximum
:
1674 elif actSelected
== actReset
:
1676 elif actSelected
== actCenter
:
1681 sender
.setValue(value
, True)
1683 #------------------------------------------------------------------
1686 def slot_channelCustomMenu(self
):
1689 actNone
= menu
.addAction(self
.tr("None"))
1691 if self
.fControlChannel
+1 == 0:
1692 actNone
.setCheckable(True)
1693 actNone
.setChecked(True)
1695 for i
in range(1, 16+1):
1696 action
= menu
.addAction("%i" % i
)
1698 if self
.fControlChannel
+1 == i
:
1699 action
.setCheckable(True)
1700 action
.setChecked(True)
1702 actSel
= menu
.exec_(QCursor
.pos())
1706 elif actSel
== actNone
:
1707 self
.ui
.sb_ctrl_channel
.setValue(0)
1709 selChannel
= int(actSel
.text())
1710 self
.ui
.sb_ctrl_channel
.setValue(selChannel
)
1712 #------------------------------------------------------------------
1714 def _createParameterWidgets(self
, paramType
, paramListFull
, tabPageName
):
1717 for paramList
, width
in paramListFull
:
1721 tabIndex
= self
.ui
.tabWidget
.count()
1723 scrollArea
= QScrollArea(self
.ui
.tabWidget
)
1724 scrollArea
.setWidgetResizable(True)
1725 scrollArea
.setFrameStyle(0)
1727 palette1
= scrollArea
.palette()
1728 palette1
.setColor(QPalette
.Background
, Qt
.transparent
)
1729 scrollArea
.setPalette(palette1
)
1731 palette2
= scrollArea
.palette()
1732 palette2
.setColor(QPalette
.Background
, palette2
.color(QPalette
.Button
))
1734 scrollAreaWidget
= QWidget(scrollArea
)
1735 scrollAreaLayout
= QVBoxLayout(scrollAreaWidget
)
1736 scrollAreaLayout
.setSpacing(3)
1738 for paramInfo
in paramList
:
1739 groupName
= paramInfo
['groupName']
1741 groupSymbol
, groupName
= groupName
.split(":",1)
1742 groupLayout
, groupWidget
= groupWidgets
.get(groupSymbol
, (None, None))
1743 if groupLayout
is None:
1744 groupWidget
= CollapsibleBox(groupName
, scrollAreaWidget
)
1745 groupLayout
= groupWidget
.getContentLayout()
1746 groupWidget
.setPalette(palette2
)
1747 scrollAreaLayout
.addWidget(groupWidget
)
1748 groupWidgets
[groupSymbol
] = (groupLayout
, groupWidget
)
1750 groupLayout
= scrollAreaLayout
1751 groupWidget
= scrollAreaWidget
1753 paramWidget
= PluginParameter(groupWidget
, self
.host
, paramInfo
, self
.fPluginId
, tabIndex
)
1754 paramWidget
.setLabelWidth(width
)
1755 groupLayout
.addWidget(paramWidget
)
1757 self
.fParameterList
.append((paramType
, paramInfo
['index'], paramWidget
))
1759 if paramType
== PARAMETER_INPUT
:
1760 paramWidget
.valueChanged
.connect(self
.slot_parameterValueChanged
)
1762 paramWidget
.mappedControlChanged
.connect(self
.slot_parameterMappedControlChanged
)
1763 paramWidget
.mappedRangeChanged
.connect(self
.slot_parameterMappedRangeChanged
)
1764 paramWidget
.midiChannelChanged
.connect(self
.slot_parameterMidiChannelChanged
)
1766 scrollAreaLayout
.addStretch()
1768 scrollArea
.setWidget(scrollAreaWidget
)
1770 self
.ui
.tabWidget
.addTab(scrollArea
, tabPageName
)
1772 if paramType
== PARAMETER_INPUT
:
1773 self
.ui
.tabWidget
.setTabIcon(tabIndex
, self
.fTabIconOff
)
1775 self
.fTabIconTimers
.append(ICON_STATE_NULL
)
1777 def _updateCtrlPrograms(self
):
1778 self
.ui
.keyboard
.setEnabled(self
.fControlChannel
>= 0)
1780 if self
.fPluginInfo
['category'] != PLUGIN_CATEGORY_SYNTH
:
1782 if self
.fPluginInfo
['type'] not in (PLUGIN_INTERNAL
, PLUGIN_SF2
):
1785 if self
.fControlChannel
< 0:
1786 self
.ui
.cb_programs
.setEnabled(False)
1787 self
.ui
.cb_midi_programs
.setEnabled(False)
1790 self
.ui
.cb_programs
.setEnabled(True)
1791 self
.ui
.cb_midi_programs
.setEnabled(True)
1793 pIndex
= self
.host
.get_current_program_index(self
.fPluginId
)
1795 if self
.ui
.cb_programs
.currentIndex() != pIndex
:
1796 self
.setProgram(pIndex
)
1798 mpIndex
= self
.host
.get_current_midi_program_index(self
.fPluginId
)
1800 if self
.ui
.cb_midi_programs
.currentIndex() != mpIndex
:
1801 self
.setMidiProgram(mpIndex
)
1803 def _updateParameterValues(self
):
1804 for _
, paramId
, paramWidget
in self
.fParameterList
:
1805 paramWidget
.blockSignals(True)
1806 paramWidget
.setValue(self
.host
.get_current_parameter_value(self
.fPluginId
, paramId
))
1807 paramWidget
.blockSignals(False)
1809 #------------------------------------------------------------------
1811 def testTimer(self
):
1812 self
.fIdleTimerId
= self
.startTimer(50)
1814 self
.SIGTERM
.connect(self
.testTimerClose
)
1818 def testTimerClose(self
):
1822 #------------------------------------------------------------------
1824 def closeEvent(self
, event
):
1825 if self
.fIdleTimerId
!= 0:
1826 self
.killTimer(self
.fIdleTimerId
)
1827 self
.fIdleTimerId
= 0
1829 self
.host
.engine_close()
1831 QDialog
.closeEvent(self
, event
)
1833 def timerEvent(self
, event
):
1834 if event
.timerId() == self
.fIdleTimerId
:
1835 self
.host
.engine_idle()
1838 QDialog
.timerEvent(self
, event
)
1840 # ------------------------------------------------------------------------------------------------------------
1843 if __name__
== '__main__':
1844 from carla_app
import CarlaApplication
1845 from carla_host
import initHost
as _initHost
, loadHostSettings
as _loadHostSettings
1847 _app
= CarlaApplication()
1848 _host
= _initHost("Widgets", None, False, False, False)
1849 _loadHostSettings(_host
)
1851 _host
.engine_init("JACK", "Carla-Widgets")
1852 _host
.add_plugin(BINARY_NATIVE
, PLUGIN_DSSI
, "/usr/lib/dssi/karplong.so", "karplong", "karplong", 0, None, 0x0)
1853 _host
.set_active(0, True)
1855 gui1
= CarlaAboutW(None, _host
)
1858 gui2
= PluginEdit(None, _host
, 0)