2 # -*- coding: utf-8 -*-
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 # ------------------------------------------------------------------------------------------------------------
22 from abc
import abstractmethod
24 # ------------------------------------------------------------------------------------------------------------
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 # ------------------------------------------------------------------------------------------------------------
36 import ui_carla_parameter
38 from carla_backend
import (
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
,
65 PARAMETER_BALANCE_LEFT
,
66 PARAMETER_BALANCE_RIGHT
,
68 PARAMETER_CTRL_CHANNEL
,
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
,
77 CONTROL_INDEX_MIDI_PITCHBEND
,
78 CONTROL_INDEX_MIDI_LEARN
,
82 from carla_shared
import (
83 MIDI_CC_LIST
, MAX_MIDI_CC_LIST_ITEM
,
86 fontMetricsHorizontalAdvance
,
91 from carla_utils
import getPluginTypeAsString
94 from widgets
.collapsablewidget
import CollapsibleBox
95 from widgets
.pixmapkeyboard
import PixmapKeyboardHArea
97 # ------------------------------------------------------------------------------------------------------------
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 # ------------------------------------------------------------------------------------------------------------
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
)
115 extraInfo
= " - <b>%s</b>" % self
.tr("OSC Bridge Version")
117 extraInfo
= " - <b>%s</b>" % self
.tr("Plugin Version")
121 self
.ui
.l_about
.setText(self
.tr(""
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"))
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())
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" " </td><td><i-value></td></tr>"
150 "<tr><td>" "/set_drywet" " </td><td><f-value></td></tr>"
151 "<tr><td>" "/set_volume" " </td><td><f-value></td></tr>"
152 "<tr><td>" "/set_balance_left" " </td><td><f-value></td></tr>"
153 "<tr><td>" "/set_balance_right" " </td><td><f-value></td></tr>"
154 "<tr><td>" "/set_panning" " </td><td><f-value></td></tr>"
155 "<tr><td>" "/set_parameter_value" " </td><td><i-index> <f-value></td></tr>"
156 "<tr><td>" "/set_parameter_midi_cc" " </td><td><i-index> <i-cc></td></tr>"
157 "<tr><td>" "/set_parameter_midi_channel" " </td><td><i-index> <i-channel></td></tr>"
158 "<tr><td>" "/set_program" " </td><td><i-index></td></tr>"
159 "<tr><td>" "/set_midi_program" " </td><td><i-index></td></tr>"
160 "<tr><td>" "/note_on" " </td><td><i-channel> <i-note> <i-velo></td></tr>"
161 "<tr><td>" "/note_off" " </td><td><i-channel> <i-note</td></tr>"
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% complete (using custom extensions)<br/>"
172 "Implemented Feature/Extensions:"
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>"
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"))
210 self
.ui
.l_vst2
.setText(self
.tr("About 85% complete (missing vst bank/presets and some minor stuff)"))
213 self
.ui
.l_vst3
.setText(self
.tr("Using JUCE host"))
216 self
.ui
.l_au
.setText(self
.tr("Using JUCE host"))
218 self
.ui
.line_vst3
.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)
226 self
.ui
.tabWidget
.setCurrentIndex(0)
228 self
.setFixedSize(self
.size())
230 flags
= self
.windowFlags()
231 flags
&= ~Qt
.WindowContextHelpButtonHint
234 flags |
= Qt
.MSWindowsFixedSizeDialogHint
236 self
.setWindowFlags(flags
)
239 self
.setWindowModality(Qt
.WindowModal
)
241 # ------------------------------------------------------------------------------------------------------------
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
)
253 self
.ui
= ui_carla_parameter
.Ui_PluginParameter()
254 self
.ui
.setupUi(self
)
256 # -------------------------------------------------------------
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 # -------------------------------------------------------------
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
))
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)
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 # -------------------------------------------------------------
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")
383 text
= self
.tr("CC%i Ch%i" % (self
.fMappedCtrl
, self
.fMidiChannel
))
385 self
.ui
.l_status
.setText(text
)
388 def slot_optionsCustomMenu(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")
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)
410 actUnmap
= menu
.addAction(self
.tr("Unmap"))
412 if self
.fMappedCtrl
== CONTROL_INDEX_NONE
:
413 actUnmap
.setCheckable(True)
414 actUnmap
.setChecked(True)
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)
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)
436 menuMIDI
= menu
.addMenu(self
.tr("MIDI Control"))
438 if self
.fMappedCtrl
not in (CONTROL_INDEX_NONE
,
440 CONTROL_INDEX_MIDI_PITCHBEND
,
441 CONTROL_INDEX_MIDI_LEARN
):
442 action
= menuMIDI
.menuAction()
443 action
.setCheckable(True)
444 action
.setChecked(True)
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
:
457 action
= menuMIDI
.addAction(self
.tr("%02i [0x%02X] (Custom)" % (self
.fMappedCtrl
,
459 action
.setCheckable(True)
460 action
.setChecked(True)
461 actCCs
.append(action
)
463 elif ccx
== self
.fMappedCtrl
:
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..."))
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"))
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)")
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
))
502 actRangeMinimum
= actRangeMaximum
= None
504 actSel
= menu
.exec_(QCursor
.pos())
509 if actSel
in actChannels
:
510 channel
= int(actSel
.text())
511 self
.fMidiChannel
= channel
512 self
.updateStatusLabel()
513 self
.midiChannelChanged
.emit(self
.fParameterId
, channel
)
516 if actSel
== actRangeMinimum
:
517 value
, ok
= QInputDialog
.getDouble(self
,
518 self
.tr("Custom Minimum"),
519 "Custom minimum value to use:",
521 self
.fMinimum
if self
.fMappedCtrl
!= CONTROL_INDEX_CV
else -9e6
,
522 self
.fMaximum
if self
.fMappedCtrl
!= CONTROL_INDEX_CV
else 9e6
,
527 self
.fMappedMinimum
= value
528 self
.mappedRangeChanged
.emit(self
.fParameterId
, self
.fMappedMinimum
, self
.fMappedMaximum
)
531 if actSel
== actRangeMaximum
:
532 value
, ok
= QInputDialog
.getDouble(self
,
533 self
.tr("Custom Maximum"),
534 "Custom maximum value to use:",
536 self
.fMinimum
if self
.fMappedCtrl
!= CONTROL_INDEX_CV
else -9e6
,
537 self
.fMaximum
if self
.fMappedCtrl
!= CONTROL_INDEX_CV
else 9e6
,
542 self
.fMappedMaximum
= value
543 self
.mappedRangeChanged
.emit(self
.fParameterId
, self
.fMappedMinimum
, self
.fMappedMaximum
)
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:",
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)
568 self
.fMappedCtrl
= ctrl
569 self
.updateStatusLabel()
570 self
.mappedControlChanged
.emit(self
.fParameterId
, ctrl
)
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):
588 def editDialogVisibilityChanged(self
, pluginId
, visible
):
589 raise NotImplementedError
592 def editDialogPluginHintsChanged(self
, pluginId
, hints
):
593 raise NotImplementedError
596 def editDialogParameterValueChanged(self
, pluginId
, parameterId
, value
):
597 raise NotImplementedError
600 def editDialogProgramChanged(self
, pluginId
, index
):
601 raise NotImplementedError
604 def editDialogMidiProgramChanged(self
, pluginId
, index
):
605 raise NotImplementedError
608 def editDialogNotePressed(self
, pluginId
, note
):
609 raise NotImplementedError
612 def editDialogNoteReleased(self
, pluginId
, note
):
613 raise NotImplementedError
616 def editDialogMidiActivityChanged(self
, pluginId
, onOff
):
617 raise NotImplementedError
619 # ------------------------------------------------------------------------------------------------------------
620 # Plugin Editor (Built-in)
622 class PluginEdit(QDialog
):
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)
630 self
.ui
= ui_carla_edit
.Ui_PluginEdit()
631 self
.ui
.setupUi(self
)
633 # -------------------------------------------------------------
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 # -------------------------------------------------------------
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)
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
)
722 self
.fFirstInit
= False
724 # -------------------------------------------------------------
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
:
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
:
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)
807 def slot_handleUpdateCallback(self
, pluginId
):
808 if self
.fPluginId
== pluginId
:
812 def slot_handleReloadInfoCallback(self
, pluginId
):
813 if self
.fPluginId
== pluginId
:
817 def slot_handleReloadParametersCallback(self
, pluginId
):
818 if self
.fPluginId
== pluginId
:
819 self
.reloadParameters()
822 def slot_handleReloadProgramsCallback(self
, pluginId
):
823 if self
.fPluginId
== pluginId
:
824 self
.reloadPrograms()
827 def slot_handleReloadAllCallback(self
, pluginId
):
828 if self
.fPluginId
== pluginId
:
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()
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()
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 #------------------------------------------------------------------
892 self
.fPluginInfo
= self
.host
.get_plugin_info(self
.fPluginId
)
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)
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
):
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
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:
1014 # -----------------------------------------------------------------
1017 paramOutputList
= []
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
:
1029 paramData
= self
.host
.get_parameter_data(self
.fPluginId
, i
)
1031 if paramData
['type'] not in (PARAMETER_INPUT
, PARAMETER_OUTPUT
):
1032 unusedParameters
+= 1
1034 if (paramData
['hints'] & PARAMETER_IS_ENABLED
) == 0:
1035 unusedParameters
+= 1
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
)
1043 'type': paramData
['type'],
1044 'hints': paramData
['hints'],
1045 'name': paramInfo
['name'],
1046 'unit': paramInfo
['unit'],
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
)
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"))
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
):
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
))
1128 self
.ui
.cb_programs
.setEnabled(False)
1129 self
.ui
.label_programs
.setEnabled(False)
1131 self
.ui
.cb_programs
.blockSignals(False)
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
))
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 #------------------------------------------------------------------
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
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
)
1208 def setParameterMappedControlIndex(self
, parameterId
, control
):
1209 for _
, paramId
, paramWidget
in self
.fParameterList
:
1210 if paramId
== parameterId
:
1211 paramWidget
.setMappedControlIndex(control
)
1214 def setParameterMappedRange(self
, parameterId
, minimum
, maximum
):
1215 for _
, paramId
, paramWidget
in self
.fParameterList
:
1216 if paramId
== parameterId
:
1217 paramWidget
.setMappedRange(minimum
, maximum
)
1220 def setParameterMidiChannel(self
, parameterId
, channel
):
1221 for _
, paramId
, paramWidget
in self
.fParameterList
:
1222 if paramId
== parameterId
:
1223 paramWidget
.setMidiChannel(channel
+1)
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
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
1265 widget
.blockSignals(True)
1266 widget
.setChecked(yesNo
)
1267 widget
.blockSignals(False)
1269 #------------------------------------------------------------------
1271 def setVisible(self
, yesNo
):
1273 if not self
.fGeometry
.isNull():
1274 self
.restoreGeometry(self
.fGeometry
)
1276 self
.fGeometry
= self
.saveGeometry()
1278 QDialog
.setVisible(self
, yesNo
)
1281 parent
= self
.parent()
1284 gCarla
.utils
.cocoa_set_transient_window_for(self
.windowHandle().winId(), parent
.windowHandle().winId())
1286 #------------------------------------------------------------------
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
()
1335 for paramType
, paramId
, paramWidget
in self
.fParameterList
:
1336 if paramId
!= index
:
1339 if paramType
!= PARAMETER_INPUT
:
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
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
:
1363 paramWidget
.blockSignals(True)
1364 paramWidget
.setValue(self
.host
.get_current_parameter_value(self
.fPluginId
, paramId
))
1365 paramWidget
.blockSignals(False)
1367 #------------------------------------------------------------------
1370 def slot_stateSave(self
):
1371 if self
.fPluginInfo
['type'] == PLUGIN_LV2
:
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
)
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
1394 if not filename
.lower().endswith(".carxs"):
1395 filename
+= ".carxs"
1397 self
.fCurrentStateFilename
= filename
1398 self
.host
.save_plugin_state(self
.fPluginId
, self
.fCurrentStateFilename
)
1401 def slot_stateLoad(self
):
1402 if self
.fPluginInfo
['type'] == PLUGIN_LV2
:
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)
1414 index
= int(ret
[0].split(" - ", 1)[0])-1
1415 self
.host
.set_program(self
.fPluginId
, index
)
1416 self
.setMidiProgram(-1)
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
1427 self
.fCurrentStateFilename
= filename
1428 self
.host
.load_plugin_state(self
.fPluginId
, self
.fCurrentStateFilename
)
1430 #------------------------------------------------------------------
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
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 #--------------------------------------------------------------
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 #------------------------------------------------------------------
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
)
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
)
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
)
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
)
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
)
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
)
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
)
1551 def slot_parameterMidiChannelChanged(self
, parameterId
, channel
):
1552 self
.host
.set_parameter_midi_channel(self
.fPluginId
, parameterId
, channel
-1)
1554 #------------------------------------------------------------------
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
()
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 #------------------------------------------------------------------
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
)
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 #------------------------------------------------------------------
1595 def slot_finished(self
):
1596 if self
.fParent
is not None:
1597 self
.fParent
.editDialogVisibilityChanged(self
.fPluginId
, False)
1599 #------------------------------------------------------------------
1602 def slot_knobCustomMenu(self
):
1603 sender
= self
.sender()
1604 knobName
= sender
.objectName()
1606 if knobName
== "dial_drywet":
1611 elif knobName
== "dial_vol":
1616 elif knobName
== "dial_b_left":
1620 label
= "Balance-Left"
1621 elif knobName
== "dial_b_right":
1625 label
= "Balance-Right"
1626 elif knobName
== "dial_pan":
1638 actReset
= menu
.addAction(self
.tr("Reset (%i%%)" % (default
*100)))
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)))
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"),
1656 round(current
*100.0),
1657 round(minimum
*100.0),
1658 round(maximum
*100.0),
1661 value
= float(value
)/100.0
1666 elif actSelected
== actMinimum
:
1668 elif actSelected
== actMaximum
:
1670 elif actSelected
== actReset
:
1672 elif actSelected
== actCenter
:
1677 sender
.setValue(value
, True)
1679 #------------------------------------------------------------------
1682 def slot_channelCustomMenu(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())
1702 elif actSel
== actNone
:
1703 self
.ui
.sb_ctrl_channel
.setValue(0)
1705 selChannel
= int(actSel
.text())
1706 self
.ui
.sb_ctrl_channel
.setValue(selChannel
)
1708 #------------------------------------------------------------------
1710 def _createParameterWidgets(self
, paramType
, paramListFull
, tabPageName
):
1713 for paramList
, width
in paramListFull
:
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']
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
)
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
:
1778 if self
.fPluginInfo
['type'] not in (PLUGIN_INTERNAL
, PLUGIN_SF2
):
1781 if self
.fControlChannel
< 0:
1782 self
.ui
.cb_programs
.setEnabled(False)
1783 self
.ui
.cb_midi_programs
.setEnabled(False)
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
)
1814 def testTimerClose(self
):
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()
1834 QDialog
.timerEvent(self
, event
)
1836 # ------------------------------------------------------------------------------------------------------------
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
)
1854 gui2
= PluginEdit(None, _host
, 0)