#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Carla widgets code # Copyright (C) 2011-2020 Filipe Coelho # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of # the License, or any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # For a full copy of the GNU General Public License see the doc/GPL.txt file. # ------------------------------------------------------------------------------------------------------------ # Imports (Global) from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray, QTimer from PyQt5.QtGui import QColor, QCursor, QFontMetrics, QPainter, QPainterPath, QPalette, QPixmap from PyQt5.QtWidgets import QDialog, QGroupBox, QInputDialog, QLineEdit, QMenu, QScrollArea, QVBoxLayout, QWidget # ------------------------------------------------------------------------------------------------------------ # Imports (Custom) import ui_carla_about import ui_carla_about_juce import ui_carla_edit import ui_carla_parameter from carla_shared import * from carla_utils import * from widgets.collapsablewidget import CollapsibleBox from widgets.paramspinbox import CustomInputDialog from widgets.pixmapkeyboard import PixmapKeyboardHArea # ------------------------------------------------------------------------------------------------------------ # Carla GUI defines ICON_STATE_ON = 3 # turns on, sets as wait ICON_STATE_WAIT = 2 # nothing, sets as off ICON_STATE_OFF = 1 # turns off, sets as null ICON_STATE_NULL = 0 # nothing # ------------------------------------------------------------------------------------------------------------ # Carla About dialog class CarlaAboutW(QDialog): def __init__(self, parent, host): QDialog.__init__(self, parent) self.ui = ui_carla_about.Ui_CarlaAboutW() self.ui.setupUi(self) if False: # kdevelop likes this :) host = CarlaHostNull() if host.isControl: extraInfo = " - %s" % self.tr("OSC Bridge Version") elif host.isPlugin: extraInfo = " - %s" % self.tr("Plugin Version") else: extraInfo = "" self.ui.l_about.setText(self.tr("" "
Version %s" "
Carla is a fully-featured audio plugin host%s.
" "
Copyright (C) 2011-2020 falkTX
" "" % (VERSION, extraInfo))) if self.ui.about.palette().color(QPalette.Background).blackF() < 0.5: self.ui.l_icons.setPixmap(QPixmap(":/bitmaps/carla_about_black.png")) self.ui.ico_example_edit.setPixmap(QPixmap(":/bitmaps/button_file-black.png")) self.ui.ico_example_file.setPixmap(QPixmap(":/bitmaps/button_edit-black.png")) self.ui.ico_example_gui.setPixmap(QPixmap(":/bitmaps/button_gui-black.png")) if host.isControl: self.ui.l_extended.hide() self.ui.tabWidget.removeTab(3) self.ui.tabWidget.removeTab(2) self.ui.l_extended.setText(gCarla.utils.get_complete_license_text()) if host.is_engine_running() and not host.isControl: self.ui.le_osc_url_tcp.setText(host.get_host_osc_url_tcp()) self.ui.le_osc_url_udp.setText(host.get_host_osc_url_udp()) else: self.ui.le_osc_url_tcp.setText(self.tr("(Engine not running)")) self.ui.le_osc_url_udp.setText(self.tr("(Engine not running)")) self.ui.l_osc_cmds.setText("" "" "" "" "" "" "" "" "" "" "" "" "" "" "
" "/set_active" " <i-value>
" "/set_drywet" " <f-value>
" "/set_volume" " <f-value>
" "/set_balance_left" " <f-value>
" "/set_balance_right" " <f-value>
" "/set_panning" " <f-value>
" "/set_parameter_value" " <i-index> <f-value>
" "/set_parameter_midi_cc" " <i-index> <i-cc>
" "/set_parameter_midi_channel" " <i-index> <i-channel>
" "/set_program" " <i-index>
" "/set_midi_program" " <i-index>
" "/note_on" " <i-channel> <i-note> <i-velo>
" "/note_off" " <i-channel> <i-note
" ) self.ui.l_example.setText("/Carla/2/set_parameter_value 5 1.0") self.ui.l_example_help.setText("(as in this example, \"2\" is the plugin number and \"5\" the parameter)") self.ui.l_ladspa.setText(self.tr("Everything! (Including LRDF)")) self.ui.l_dssi.setText(self.tr("Everything! (Including CustomData/Chunks)")) self.ui.l_lv2.setText(self.tr("About 110% complete (using custom extensions)
" "Implemented Feature/Extensions:" "")) usingJuce = "juce" in gCarla.utils.get_supported_features() if usingJuce and (MACOS or WINDOWS): self.ui.l_vst2.setText(self.tr("Using JUCE host")) self.ui.l_vst3.setText(self.tr("Using JUCE host")) else: self.ui.l_vst2.setText(self.tr("About 85% complete (missing vst bank/presets and some minor stuff)")) self.ui.line_vst2.hide() self.ui.l_vst3.hide() self.ui.lid_vst3.hide() if MACOS: self.ui.l_au.setText(self.tr("Using JUCE host")) else: self.ui.line_vst3.hide() self.ui.l_au.hide() self.ui.lid_au.hide() # 3rd tab is usually longer than the 1st # adjust appropriately self.ui.tabWidget.setCurrentIndex(2) self.adjustSize() self.ui.tabWidget.setCurrentIndex(0) self.setFixedSize(self.size()) flags = self.windowFlags() flags &= ~Qt.WindowContextHelpButtonHint if WINDOWS: flags |= Qt.MSWindowsFixedSizeDialogHint self.setWindowFlags(flags) def done(self, r): QDialog.done(self, r) self.close() # ------------------------------------------------------------------------------------------------------------ # JUCE About dialog class JuceAboutW(QDialog): def __init__(self, parent): QDialog.__init__(self, parent) self.ui = ui_carla_about_juce.Ui_JuceAboutW() self.ui.setupUi(self) self.ui.l_text2.setText(self.tr("This program uses JUCE version %s." % gCarla.utils.get_juce_version())) self.adjustSize() self.setFixedSize(self.size()) flags = self.windowFlags() flags &= ~Qt.WindowContextHelpButtonHint if WINDOWS: flags |= Qt.MSWindowsFixedSizeDialogHint self.setWindowFlags(flags) def done(self, r): QDialog.done(self, r) self.close() # ------------------------------------------------------------------------------------------------------------ # Plugin Parameter class PluginParameter(QWidget): mappedControlChanged = pyqtSignal(int, int) mappedRangeChanged = pyqtSignal(int, float, float) midiChannelChanged = pyqtSignal(int, int) valueChanged = pyqtSignal(int, float) def __init__(self, parent, host, pInfo, pluginId, tabIndex): QWidget.__init__(self, parent) self.host = host self.ui = ui_carla_parameter.Ui_PluginParameter() self.ui.setupUi(self) if False: # kdevelop likes this :) host = CarlaHostNull() self.host = host # ------------------------------------------------------------- # Internal stuff self.fDecimalPoints = max(2, countDecimalPoints(pInfo['step'], pInfo['stepSmall'])) self.fCanBeInCV = pInfo['hints'] & PARAMETER_CAN_BE_CV_CONTROLLED self.fMappedCtrl = pInfo['mappedControlIndex'] self.fMappedMinimum = pInfo['mappedMinimum'] self.fMappedMaximum = pInfo['mappedMaximum'] self.fMinimum = pInfo['minimum'] self.fMaximum = pInfo['maximum'] self.fMidiChannel = pInfo['midiChannel'] self.fParameterId = pInfo['index'] self.fPluginId = pluginId self.fTabIndex = tabIndex # ------------------------------------------------------------- # Set-up GUI pType = pInfo['type'] pHints = pInfo['hints'] self.ui.label.setText(pInfo['name']) self.ui.widget.setName(pInfo['name']) self.ui.widget.setMinimum(pInfo['minimum']) self.ui.widget.setMaximum(pInfo['maximum']) self.ui.widget.setDefault(pInfo['default']) self.ui.widget.setLabel(pInfo['unit']) self.ui.widget.setStep(pInfo['step']) self.ui.widget.setStepSmall(pInfo['stepSmall']) self.ui.widget.setStepLarge(pInfo['stepLarge']) self.ui.widget.setScalePoints(pInfo['scalePoints'], bool(pHints & PARAMETER_USES_SCALEPOINTS)) if pInfo['comment']: self.ui.label.setToolTip(pInfo['comment']) self.ui.widget.setToolTip(pInfo['comment']) if pType == PARAMETER_INPUT: if not pHints & PARAMETER_IS_ENABLED: self.ui.label.setEnabled(False) self.ui.widget.setEnabled(False) self.ui.widget.setReadOnly(True) self.ui.tb_options.setEnabled(False) elif not pHints & PARAMETER_IS_AUTOMABLE: self.ui.tb_options.setEnabled(False) if pHints & PARAMETER_IS_READ_ONLY: self.ui.widget.setReadOnly(True) self.ui.tb_options.setEnabled(False) elif pType == PARAMETER_OUTPUT: self.ui.widget.setReadOnly(True) else: self.ui.widget.setVisible(False) self.ui.tb_options.setVisible(False) # Only set value after all hints are handled self.ui.widget.setValue(pInfo['current']) if pHints & PARAMETER_USES_CUSTOM_TEXT and not host.isPlugin: self.ui.widget.setTextCallback(self._textCallBack) self.ui.widget.setValueCallback(self._valueCallBack) self.ui.widget.updateAll() self.setMappedControlIndex(pInfo['mappedControlIndex']) self.setMidiChannel(pInfo['midiChannel']) # ------------------------------------------------------------- # Set-up connections self.ui.tb_options.clicked.connect(self.slot_optionsCustomMenu) self.ui.widget.dragStateChanged.connect(self.slot_parameterDragStateChanged) # ------------------------------------------------------------- def getPluginId(self): return self.fPluginId def getTabIndex(self): return self.fTabIndex def setPluginId(self, pluginId): self.fPluginId = pluginId def setDefault(self, value): self.ui.widget.setDefault(value) def setValue(self, value): self.ui.widget.blockSignals(True) self.ui.widget.setValue(value) self.ui.widget.blockSignals(False) def setMappedControlIndex(self, control): self.fMappedCtrl = control def setMappedRange(self, minimum, maximum): self.fMappedMinimum = minimum self.fMappedMaximum = maximum def setMidiChannel(self, channel): self.fMidiChannel = channel def setLabelWidth(self, width): self.ui.label.setFixedWidth(width) @pyqtSlot() def slot_optionsCustomMenu(self): menu = QMenu(self) if self.fMappedCtrl == CONTROL_VALUE_NONE: title = self.tr("Unmapped") elif self.fMappedCtrl == CONTROL_VALUE_CV: title = self.tr("Exposed as CV port") else: title = self.tr("Mapped to MIDI control %i, channel %i" % (self.fMappedCtrl, self.fMidiChannel)) if self.fMappedCtrl != CONTROL_VALUE_NONE: title += " (range: %g-%g)" % (self.fMappedMinimum, self.fMappedMaximum) actTitle = menu.addAction(title) actTitle.setEnabled(False) menu.addSeparator() actUnmap = menu.addAction(self.tr("Unmap")) if self.fMappedCtrl == CONTROL_VALUE_NONE: actUnmap.setCheckable(True) actUnmap.setChecked(True) if self.fCanBeInCV: menu.addSection("CV") actCV = menu.addAction(self.tr("Expose as CV port")) if self.fMappedCtrl == CONTROL_VALUE_CV: actCV.setCheckable(True) actCV.setChecked(True) else: actCV = None menu.addSection("MIDI") menuMIDI = menu.addMenu(self.tr("MIDI Control")) if self.fMappedCtrl not in (CONTROL_VALUE_NONE, CONTROL_VALUE_CV, CONTROL_VALUE_MIDI_PITCHBEND): action = menuMIDI.menuAction() action.setCheckable(True) action.setChecked(True) inlist = False actCCs = [] for cc in MIDI_CC_LIST: action = menuMIDI.addAction(cc) actCCs.append(action) if self.fMappedCtrl >= 0 and self.fMappedCtrl <= MAX_MIDI_CC_LIST_ITEM: ccx = int(cc.split(" [", 1)[0], 16) if ccx > self.fMappedCtrl and not inlist: inlist = True action = menuMIDI.addAction(self.tr("%02i [0x%02X] (Custom)" % (self.fMappedCtrl, self.fMappedCtrl))) action.setCheckable(True) action.setChecked(True) actCCs.append(action) elif ccx == self.fMappedCtrl: inlist = True action.setCheckable(True) action.setChecked(True) if self.fMappedCtrl > MAX_MIDI_CC_LIST_ITEM and self.fMappedCtrl <= 0x77: action = menuMIDI.addAction(self.tr("%02i [0x%02X] (Custom)" % (self.fMappedCtrl, self.fMappedCtrl))) action.setCheckable(True) action.setChecked(True) actCCs.append(action) actCustomCC = menuMIDI.addAction(self.tr("Custom...")) # TODO #actPitchbend = menu.addAction(self.tr("MIDI Pitchbend")) #if self.fMappedCtrl == CONTROL_VALUE_MIDI_PITCHBEND: #actPitchbend.setCheckable(True) #actPitchbend.setChecked(True) menuChannel = menu.addMenu(self.tr("MIDI Channel")) actChannels = [] for i in range(1, 16+1): action = menuChannel.addAction("%i" % i) actChannels.append(action) if self.fMidiChannel == i: action.setCheckable(True) action.setChecked(True) if self.fMappedCtrl != CONTROL_VALUE_NONE: if self.fMappedCtrl == CONTROL_VALUE_CV: menu.addSection("Range (Scaled CV input)") else: menu.addSection("Range (MIDI bounds)") actRangeMinimum = menu.addAction(self.tr("Set minimum... (%g)" % self.fMappedMinimum)) actRangeMaximum = menu.addAction(self.tr("Set maximum... (%g)" % self.fMappedMaximum)) else: actRangeMinimum = actRangeMaximum = None actSel = menu.exec_(QCursor.pos()) if not actSel: return if actSel in actChannels: channel = int(actSel.text()) self.fMidiChannel = channel self.midiChannelChanged.emit(self.fParameterId, channel) return if actSel == actRangeMinimum: value, ok = QInputDialog.getDouble(self, self.tr("Custom Minimum"), "Custom minimum value to use:", self.fMappedMinimum, self.fMinimum if self.fMappedCtrl != CONTROL_VALUE_CV else -9e6, self.fMaximum if self.fMappedCtrl != CONTROL_VALUE_CV else 9e6, self.fDecimalPoints) if not ok: return self.fMappedMinimum = value self.mappedRangeChanged.emit(self.fParameterId, self.fMappedMinimum, self.fMappedMaximum) return if actSel == actRangeMaximum: value, ok = QInputDialog.getDouble(self, self.tr("Custom Maximum"), "Custom maximum value to use:", self.fMappedMaximum, self.fMinimum if self.fMappedCtrl != CONTROL_VALUE_CV else -9e6, self.fMaximum if self.fMappedCtrl != CONTROL_VALUE_CV else 9e6, self.fDecimalPoints) if not ok: return self.fMappedMaximum = value self.mappedRangeChanged.emit(self.fParameterId, self.fMappedMinimum, self.fMappedMaximum) return if actSel == actUnmap: ctrl = CONTROL_VALUE_NONE elif actSel == actCV: ctrl = CONTROL_VALUE_CV elif actSel in actCCs: ctrl = int(actSel.text().split(" ", 1)[0].replace("&",""), 16) elif actSel == actCustomCC: ctrl, ok = QInputDialog.getInt(self, self.tr("Custom CC"), "Custom MIDI CC to use:", self.fMappedCtrl if self.fMappedCtrl >= 0x01 and self.fMappedCtrl <= 0x77 else 1, 0x01, 0x77, 1) if not ok: return #elif actSel in actPitchbend: #ctrl = CONTROL_VALUE_MIDI_PITCHBEND else: return self.fMappedCtrl = ctrl self.mappedControlChanged.emit(self.fParameterId, ctrl) @pyqtSlot(bool) def slot_parameterDragStateChanged(self, touch): self.host.set_parameter_touch(self.fPluginId, self.fParameterId, touch) def _textCallBack(self): return self.host.get_parameter_text(self.fPluginId, self.fParameterId) def _valueCallBack(self, value): self.valueChanged.emit(self.fParameterId, value) # ------------------------------------------------------------------------------------------------------------ # Plugin Editor Parent (Meta class) class PluginEditParentMeta(): #class PluginEditParentMeta(metaclass=ABCMeta): @abstractmethod def editDialogVisibilityChanged(self, pluginId, visible): raise NotImplementedError @abstractmethod def editDialogPluginHintsChanged(self, pluginId, hints): raise NotImplementedError @abstractmethod def editDialogParameterValueChanged(self, pluginId, parameterId, value): raise NotImplementedError @abstractmethod def editDialogProgramChanged(self, pluginId, index): raise NotImplementedError @abstractmethod def editDialogMidiProgramChanged(self, pluginId, index): raise NotImplementedError @abstractmethod def editDialogNotePressed(self, pluginId, note): raise NotImplementedError @abstractmethod def editDialogNoteReleased(self, pluginId, note): raise NotImplementedError @abstractmethod def editDialogMidiActivityChanged(self, pluginId, onOff): raise NotImplementedError # ------------------------------------------------------------------------------------------------------------ # Plugin Editor (Built-in) class PluginEdit(QDialog): # signals SIGTERM = pyqtSignal() SIGUSR1 = pyqtSignal() def __init__(self, parent, host, pluginId): QDialog.__init__(self, parent.window() if parent is not None else None) self.host = host self.ui = ui_carla_edit.Ui_PluginEdit() self.ui.setupUi(self) if False: # kdevelop likes this :) parent = PluginEditParentMeta() host = CarlaHostNull() self.host = host # ------------------------------------------------------------- # Internal stuff self.fGeometry = QByteArray() self.fParent = parent self.fPluginId = pluginId self.fPluginInfo = None self.fCurrentStateFilename = None self.fControlChannel = round(host.get_internal_parameter_value(pluginId, PARAMETER_CTRL_CHANNEL)) self.fFirstInit = True self.fParameterList = [] # (type, id, widget) self.fParametersToUpdate = [] # (id, value) self.fPlayingNotes = [] # (channel, note) self.fTabIconOff = QIcon(":/bitmaps/led_off.png") self.fTabIconOn = QIcon(":/bitmaps/led_yellow.png") self.fTabIconTimers = [] # used during testing self.fIdleTimerId = 0 # ------------------------------------------------------------- # Set-up GUI labelPluginFont = self.ui.label_plugin.font() labelPluginFont.setPixelSize(15) labelPluginFont.setWeight(75) self.ui.label_plugin.setFont(labelPluginFont) self.ui.dial_drywet.setCustomPaintMode(self.ui.dial_drywet.CUSTOM_PAINT_MODE_CARLA_WET) self.ui.dial_drywet.setPixmap(3) self.ui.dial_drywet.setLabel("Dry/Wet") self.ui.dial_drywet.setMinimum(0.0) self.ui.dial_drywet.setMaximum(1.0) self.ui.dial_drywet.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_DRYWET)) self.ui.dial_vol.setCustomPaintMode(self.ui.dial_vol.CUSTOM_PAINT_MODE_CARLA_VOL) self.ui.dial_vol.setPixmap(3) self.ui.dial_vol.setLabel("Volume") self.ui.dial_vol.setMinimum(0.0) self.ui.dial_vol.setMaximum(1.27) self.ui.dial_vol.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_VOLUME)) self.ui.dial_b_left.setCustomPaintMode(self.ui.dial_b_left.CUSTOM_PAINT_MODE_CARLA_L) self.ui.dial_b_left.setPixmap(4) self.ui.dial_b_left.setLabel("L") self.ui.dial_b_left.setMinimum(-1.0) self.ui.dial_b_left.setMaximum(1.0) self.ui.dial_b_left.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_LEFT)) self.ui.dial_b_right.setCustomPaintMode(self.ui.dial_b_right.CUSTOM_PAINT_MODE_CARLA_R) self.ui.dial_b_right.setPixmap(4) self.ui.dial_b_right.setLabel("R") self.ui.dial_b_right.setMinimum(-1.0) self.ui.dial_b_right.setMaximum(1.0) self.ui.dial_b_right.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_RIGHT)) self.ui.dial_pan.setCustomPaintMode(self.ui.dial_b_right.CUSTOM_PAINT_MODE_CARLA_PAN) self.ui.dial_pan.setPixmap(4) self.ui.dial_pan.setLabel("Pan") self.ui.dial_pan.setMinimum(-1.0) self.ui.dial_pan.setMaximum(1.0) self.ui.dial_pan.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_PANNING)) self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1) self.ui.scrollArea = PixmapKeyboardHArea(self) self.ui.keyboard = self.ui.scrollArea.keyboard self.ui.keyboard.setEnabled(self.fControlChannel >= 0) self.layout().addWidget(self.ui.scrollArea) self.ui.scrollArea.setEnabled(False) self.ui.scrollArea.setVisible(False) # todo self.ui.rb_balance.setEnabled(False) self.ui.rb_balance.setVisible(False) self.ui.rb_pan.setEnabled(False) self.ui.rb_pan.setVisible(False) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.reloadAll() self.fFirstInit = False # ------------------------------------------------------------- # Set-up connections self.finished.connect(self.slot_finished) self.ui.ch_fixed_buffer.clicked.connect(self.slot_optionChanged) self.ui.ch_force_stereo.clicked.connect(self.slot_optionChanged) self.ui.ch_map_program_changes.clicked.connect(self.slot_optionChanged) self.ui.ch_use_chunks.clicked.connect(self.slot_optionChanged) self.ui.ch_send_program_changes.clicked.connect(self.slot_optionChanged) self.ui.ch_send_control_changes.clicked.connect(self.slot_optionChanged) self.ui.ch_send_channel_pressure.clicked.connect(self.slot_optionChanged) self.ui.ch_send_note_aftertouch.clicked.connect(self.slot_optionChanged) self.ui.ch_send_pitchbend.clicked.connect(self.slot_optionChanged) self.ui.ch_send_all_sound_off.clicked.connect(self.slot_optionChanged) self.ui.dial_drywet.realValueChanged.connect(self.slot_dryWetChanged) self.ui.dial_vol.realValueChanged.connect(self.slot_volumeChanged) self.ui.dial_b_left.realValueChanged.connect(self.slot_balanceLeftChanged) self.ui.dial_b_right.realValueChanged.connect(self.slot_balanceRightChanged) self.ui.dial_pan.realValueChanged.connect(self.slot_panChanged) self.ui.sb_ctrl_channel.valueChanged.connect(self.slot_ctrlChannelChanged) self.ui.dial_drywet.customContextMenuRequested.connect(self.slot_knobCustomMenu) self.ui.dial_vol.customContextMenuRequested.connect(self.slot_knobCustomMenu) self.ui.dial_b_left.customContextMenuRequested.connect(self.slot_knobCustomMenu) self.ui.dial_b_right.customContextMenuRequested.connect(self.slot_knobCustomMenu) self.ui.dial_pan.customContextMenuRequested.connect(self.slot_knobCustomMenu) self.ui.sb_ctrl_channel.customContextMenuRequested.connect(self.slot_channelCustomMenu) self.ui.keyboard.noteOn.connect(self.slot_noteOn) self.ui.keyboard.noteOff.connect(self.slot_noteOff) self.ui.cb_programs.currentIndexChanged.connect(self.slot_programIndexChanged) self.ui.cb_midi_programs.currentIndexChanged.connect(self.slot_midiProgramIndexChanged) self.ui.b_save_state.clicked.connect(self.slot_stateSave) self.ui.b_load_state.clicked.connect(self.slot_stateLoad) host.NoteOnCallback.connect(self.slot_handleNoteOnCallback) host.NoteOffCallback.connect(self.slot_handleNoteOffCallback) host.UpdateCallback.connect(self.slot_handleUpdateCallback) host.ReloadInfoCallback.connect(self.slot_handleReloadInfoCallback) host.ReloadParametersCallback.connect(self.slot_handleReloadParametersCallback) host.ReloadProgramsCallback.connect(self.slot_handleReloadProgramsCallback) host.ReloadAllCallback.connect(self.slot_handleReloadAllCallback) #------------------------------------------------------------------ @pyqtSlot(int, int, int, int) def slot_handleNoteOnCallback(self, pluginId, channel, note, velocity): if self.fPluginId != pluginId: return if self.fControlChannel == channel: self.ui.keyboard.sendNoteOn(note, False) playItem = (channel, note) if playItem not in self.fPlayingNotes: self.fPlayingNotes.append(playItem) if len(self.fPlayingNotes) == 1 and self.fParent is not None: self.fParent.editDialogMidiActivityChanged(self.fPluginId, True) @pyqtSlot(int, int, int) def slot_handleNoteOffCallback(self, pluginId, channel, note): if self.fPluginId != pluginId: return if self.fControlChannel == channel: self.ui.keyboard.sendNoteOff(note, False) playItem = (channel, note) if playItem in self.fPlayingNotes: self.fPlayingNotes.remove(playItem) if len(self.fPlayingNotes) == 0 and self.fParent is not None: self.fParent.editDialogMidiActivityChanged(self.fPluginId, False) @pyqtSlot(int) def slot_handleUpdateCallback(self, pluginId): if self.fPluginId == pluginId: self.updateInfo() @pyqtSlot(int) def slot_handleReloadInfoCallback(self, pluginId): if self.fPluginId == pluginId: self.reloadInfo() @pyqtSlot(int) def slot_handleReloadParametersCallback(self, pluginId): if self.fPluginId == pluginId: self.reloadParameters() @pyqtSlot(int) def slot_handleReloadProgramsCallback(self, pluginId): if self.fPluginId == pluginId: self.reloadPrograms() @pyqtSlot(int) def slot_handleReloadAllCallback(self, pluginId): if self.fPluginId == pluginId: self.reloadAll() #------------------------------------------------------------------ def updateInfo(self): # Update current program text if self.ui.cb_programs.count() > 0: pIndex = self.ui.cb_programs.currentIndex() if pIndex >= 0: pName = self.host.get_program_name(self.fPluginId, pIndex) #pName = pName[:40] + (pName[40:] and "...") self.ui.cb_programs.setItemText(pIndex, pName) # Update current midi program text if self.ui.cb_midi_programs.count() > 0: mpIndex = self.ui.cb_midi_programs.currentIndex() if mpIndex >= 0: mpData = self.host.get_midi_program_data(self.fPluginId, mpIndex) mpBank = mpData['bank'] mpProg = mpData['program'] mpName = mpData['name'] #mpName = mpName[:40] + (mpName[40:] and "...") self.ui.cb_midi_programs.setItemText(mpIndex, "%03i:%03i - %s" % (mpBank+1, mpProg+1, mpName)) # Update all parameter values for paramType, paramId, paramWidget in self.fParameterList: paramWidget.blockSignals(True) paramWidget.setValue(self.host.get_current_parameter_value(self.fPluginId, paramId)) paramWidget.blockSignals(False) # and the internal ones too self.ui.dial_drywet.blockSignals(True) self.ui.dial_drywet.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_DRYWET)) self.ui.dial_drywet.blockSignals(False) self.ui.dial_vol.blockSignals(True) self.ui.dial_vol.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_VOLUME)) self.ui.dial_vol.blockSignals(False) self.ui.dial_b_left.blockSignals(True) self.ui.dial_b_left.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_BALANCE_LEFT)) self.ui.dial_b_left.blockSignals(False) self.ui.dial_b_right.blockSignals(True) self.ui.dial_b_right.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_BALANCE_RIGHT)) self.ui.dial_b_right.blockSignals(False) self.ui.dial_pan.blockSignals(True) self.ui.dial_pan.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_PANNING)) self.ui.dial_pan.blockSignals(False) self.fControlChannel = round(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_CTRL_CHANNEL)) self.ui.sb_ctrl_channel.blockSignals(True) self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1) self.ui.sb_ctrl_channel.blockSignals(False) self.ui.keyboard.allNotesOff() self._updateCtrlPrograms() self.fParametersToUpdate = [] #------------------------------------------------------------------ def reloadAll(self): self.fPluginInfo = self.host.get_plugin_info(self.fPluginId) self.reloadInfo() self.reloadParameters() self.reloadPrograms() if self.fPluginInfo['type'] == PLUGIN_LV2: self.ui.b_save_state.setEnabled(False) if not self.ui.scrollArea.isEnabled(): self.resize(self.width(), self.height()-self.ui.scrollArea.height()) #------------------------------------------------------------------ def reloadInfo(self): realPluginName = self.host.get_real_plugin_name(self.fPluginId) #audioCountInfo = self.host.get_audio_port_count_info(self.fPluginId) midiCountInfo = self.host.get_midi_port_count_info(self.fPluginId) #paramCountInfo = self.host.get_parameter_count_info(self.fPluginId) pluginHints = self.fPluginInfo['hints'] self.ui.le_type.setText(getPluginTypeAsString(self.fPluginInfo['type'])) self.ui.label_name.setEnabled(bool(realPluginName)) self.ui.le_name.setEnabled(bool(realPluginName)) self.ui.le_name.setText(realPluginName) self.ui.le_name.setToolTip(realPluginName) self.ui.label_label.setEnabled(bool(self.fPluginInfo['label'])) self.ui.le_label.setEnabled(bool(self.fPluginInfo['label'])) self.ui.le_label.setText(self.fPluginInfo['label']) self.ui.le_label.setToolTip(self.fPluginInfo['label']) self.ui.label_maker.setEnabled(bool(self.fPluginInfo['maker'])) self.ui.le_maker.setEnabled(bool(self.fPluginInfo['maker'])) self.ui.le_maker.setText(self.fPluginInfo['maker']) self.ui.le_maker.setToolTip(self.fPluginInfo['maker']) self.ui.label_copyright.setEnabled(bool(self.fPluginInfo['copyright'])) self.ui.le_copyright.setEnabled(bool(self.fPluginInfo['copyright'])) self.ui.le_copyright.setText(self.fPluginInfo['copyright']) self.ui.le_copyright.setToolTip(self.fPluginInfo['copyright']) self.ui.label_unique_id.setEnabled(bool(self.fPluginInfo['uniqueId'])) self.ui.le_unique_id.setEnabled(bool(self.fPluginInfo['uniqueId'])) self.ui.le_unique_id.setText(str(self.fPluginInfo['uniqueId'])) self.ui.le_unique_id.setToolTip(str(self.fPluginInfo['uniqueId'])) self.ui.label_plugin.setText("\n%s\n" % (self.fPluginInfo['name'] or "(none)")) self.setWindowTitle(self.fPluginInfo['name'] or "(none)") self.ui.dial_drywet.setEnabled(pluginHints & PLUGIN_CAN_DRYWET) self.ui.dial_vol.setEnabled(pluginHints & PLUGIN_CAN_VOLUME) self.ui.dial_b_left.setEnabled(pluginHints & PLUGIN_CAN_BALANCE) self.ui.dial_b_right.setEnabled(pluginHints & PLUGIN_CAN_BALANCE) self.ui.dial_pan.setEnabled(pluginHints & PLUGIN_CAN_PANNING) self.ui.ch_use_chunks.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_USE_CHUNKS) self.ui.ch_use_chunks.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_USE_CHUNKS) self.ui.ch_fixed_buffer.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_FIXED_BUFFERS) self.ui.ch_fixed_buffer.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_FIXED_BUFFERS) self.ui.ch_force_stereo.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_FORCE_STEREO) self.ui.ch_force_stereo.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_FORCE_STEREO) self.ui.ch_map_program_changes.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) self.ui.ch_map_program_changes.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) self.ui.ch_send_control_changes.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_CONTROL_CHANGES) self.ui.ch_send_control_changes.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_SEND_CONTROL_CHANGES) self.ui.ch_send_channel_pressure.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) self.ui.ch_send_channel_pressure.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) self.ui.ch_send_note_aftertouch.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) self.ui.ch_send_note_aftertouch.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) self.ui.ch_send_pitchbend.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_PITCHBEND) self.ui.ch_send_pitchbend.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_SEND_PITCHBEND) self.ui.ch_send_all_sound_off.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) self.ui.ch_send_all_sound_off.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) canSendPrograms = bool((self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) != 0 and (self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) == 0) self.ui.ch_send_program_changes.setEnabled(canSendPrograms) self.ui.ch_send_program_changes.setChecked(self.fPluginInfo['optionsEnabled'] & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) self.ui.sw_programs.setCurrentIndex(0 if self.fPluginInfo['type'] in (PLUGIN_VST2, PLUGIN_SFZ) else 1) # Show/hide keyboard showKeyboard = (self.fPluginInfo['category'] == PLUGIN_CATEGORY_SYNTH or midiCountInfo['ins'] > 0) self.ui.scrollArea.setEnabled(showKeyboard) self.ui.scrollArea.setVisible(showKeyboard) # Force-update parent for new hints if self.fParent is not None and not self.fFirstInit: self.fParent.editDialogPluginHintsChanged(self.fPluginId, pluginHints) def reloadParameters(self): # Reset self.fParameterList = [] self.fParametersToUpdate = [] self.fTabIconTimers = [] # Remove all previous parameters for x in range(self.ui.tabWidget.count()-1): self.ui.tabWidget.widget(1).deleteLater() self.ui.tabWidget.removeTab(1) parameterCount = self.host.get_parameter_count(self.fPluginId) # ----------------------------------------------------------------- if parameterCount <= 0: return # ----------------------------------------------------------------- paramInputList = [] paramOutputList = [] paramInputWidth = 0 paramOutputWidth = 0 paramInputListFull = [] # ([params], width) paramOutputListFull = [] # ([params], width) for i in range(min(parameterCount, self.host.maxParameters)): paramInfo = self.host.get_parameter_info(self.fPluginId, i) paramData = self.host.get_parameter_data(self.fPluginId, i) paramRanges = self.host.get_parameter_ranges(self.fPluginId, i) paramValue = self.host.get_current_parameter_value(self.fPluginId, i) if paramData['type'] not in (PARAMETER_INPUT, PARAMETER_OUTPUT): continue if (paramData['hints'] & PARAMETER_IS_ENABLED) == 0: continue parameter = { 'type': paramData['type'], 'hints': paramData['hints'], 'name': paramInfo['name'], 'unit': paramInfo['unit'], 'scalePoints': [], 'index': paramData['index'], 'default': paramRanges['def'], 'minimum': paramRanges['min'], 'maximum': paramRanges['max'], 'step': paramRanges['step'], 'stepSmall': paramRanges['stepSmall'], 'stepLarge': paramRanges['stepLarge'], 'mappedControlIndex': paramData['mappedControlIndex'], 'mappedMinimum': paramData['mappedMinimum'], 'mappedMaximum': paramData['mappedMaximum'], 'midiChannel': paramData['midiChannel']+1, 'comment': paramInfo['comment'], 'groupName': paramInfo['groupName'], 'current': paramValue } for j in range(paramInfo['scalePointCount']): scalePointInfo = self.host.get_parameter_scalepoint_info(self.fPluginId, i, j) parameter['scalePoints'].append({ 'value': scalePointInfo['value'], 'label': scalePointInfo['label'] }) #parameter['name'] = parameter['name'][:30] + (parameter['name'][30:] and "...") # ----------------------------------------------------------------- # Get width values, in packs of 20 if parameter['type'] == PARAMETER_INPUT: paramInputWidthTMP = fontMetricsHorizontalAdvance(self.fontMetrics(), parameter['name']) if paramInputWidthTMP > paramInputWidth: paramInputWidth = paramInputWidthTMP paramInputList.append(parameter) else: paramOutputWidthTMP = fontMetricsHorizontalAdvance(self.fontMetrics(), parameter['name']) if paramOutputWidthTMP > paramOutputWidth: paramOutputWidth = paramOutputWidthTMP paramOutputList.append(parameter) paramInputListFull.append((paramInputList, paramInputWidth)) paramOutputListFull.append((paramOutputList, paramOutputWidth)) # Create parameter tabs + widgets self._createParameterWidgets(PARAMETER_INPUT, paramInputListFull, self.tr("Parameters")) self._createParameterWidgets(PARAMETER_OUTPUT, paramOutputListFull, self.tr("Outputs")) def reloadPrograms(self): # Programs self.ui.cb_programs.blockSignals(True) self.ui.cb_programs.clear() programCount = self.host.get_program_count(self.fPluginId) if programCount > 0: self.ui.cb_programs.setEnabled(True) self.ui.label_programs.setEnabled(True) for i in range(programCount): pName = self.host.get_program_name(self.fPluginId, i) #pName = pName[:40] + (pName[40:] and "...") self.ui.cb_programs.addItem(pName) self.ui.cb_programs.setCurrentIndex(self.host.get_current_program_index(self.fPluginId)) else: self.ui.cb_programs.setEnabled(False) self.ui.label_programs.setEnabled(False) self.ui.cb_programs.blockSignals(False) # MIDI Programs self.ui.cb_midi_programs.blockSignals(True) self.ui.cb_midi_programs.clear() midiProgramCount = self.host.get_midi_program_count(self.fPluginId) if midiProgramCount > 0: self.ui.cb_midi_programs.setEnabled(True) self.ui.label_midi_programs.setEnabled(True) for i in range(midiProgramCount): mpData = self.host.get_midi_program_data(self.fPluginId, i) mpBank = mpData['bank'] mpProg = mpData['program'] mpName = mpData['name'] #mpName = mpName[:40] + (mpName[40:] and "...") self.ui.cb_midi_programs.addItem("%03i:%03i - %s" % (mpBank+1, mpProg+1, mpName)) self.ui.cb_midi_programs.setCurrentIndex(self.host.get_current_midi_program_index(self.fPluginId)) else: self.ui.cb_midi_programs.setEnabled(False) self.ui.label_midi_programs.setEnabled(False) self.ui.cb_midi_programs.blockSignals(False) self.ui.sw_programs.setEnabled(programCount > 0 or midiProgramCount > 0) if self.fPluginInfo['type'] == PLUGIN_LV2: self.ui.b_load_state.setEnabled(programCount > 0) #------------------------------------------------------------------ def clearNotes(self): self.fPlayingNotes = [] self.ui.keyboard.allNotesOff() def noteOn(self, channel, note, velocity): if self.fControlChannel == channel: self.ui.keyboard.sendNoteOn(note, False) def noteOff(self, channel, note): if self.fControlChannel == channel: self.ui.keyboard.sendNoteOff(note, False) #------------------------------------------------------------------ def getHints(self): return self.fPluginInfo['hints'] def setPluginId(self, idx): self.fPluginId = idx def setName(self, name): self.fPluginInfo['name'] = name self.ui.label_plugin.setText("\n%s\n" % name) self.setWindowTitle(name) #------------------------------------------------------------------ def setParameterValue(self, parameterId, value): for paramItem in self.fParametersToUpdate: if paramItem[0] == parameterId: paramItem[1] = value break else: self.fParametersToUpdate.append([parameterId, value]) def setParameterDefault(self, parameterId, value): for paramType, paramId, paramWidget in self.fParameterList: if paramId == parameterId: paramWidget.setDefault(value) break def setParameterMappedControlIndex(self, parameterId, control): for paramType, paramId, paramWidget in self.fParameterList: if paramId == parameterId: paramWidget.setMappedControlIndex(control) break def setParameterMappedRange(self, parameterId, minimum, maximum): for paramType, paramId, paramWidget in self.fParameterList: if paramId == parameterId: paramWidget.setMappedRange(minimum, maximum) break def setParameterMidiChannel(self, parameterId, channel): for paramType, paramId, paramWidget in self.fParameterList: if paramId == parameterId: paramWidget.setMidiChannel(channel+1) break def setProgram(self, index): self.ui.cb_programs.blockSignals(True) self.ui.cb_programs.setCurrentIndex(index) self.ui.cb_programs.blockSignals(False) self._updateParameterValues() def setMidiProgram(self, index): self.ui.cb_midi_programs.blockSignals(True) self.ui.cb_midi_programs.setCurrentIndex(index) self.ui.cb_midi_programs.blockSignals(False) self._updateParameterValues() def setOption(self, option, yesNo): if option == PLUGIN_OPTION_USE_CHUNKS: widget = self.ui.ch_use_chunks elif option == PLUGIN_OPTION_FIXED_BUFFERS: widget = self.ui.ch_fixed_buffer elif option == PLUGIN_OPTION_FORCE_STEREO: widget = self.ui.ch_force_stereo elif option == PLUGIN_OPTION_MAP_PROGRAM_CHANGES: widget = self.ui.ch_map_program_changes elif option == PLUGIN_OPTION_SEND_PROGRAM_CHANGES: widget = self.ui.ch_send_program_changes elif option == PLUGIN_OPTION_SEND_CONTROL_CHANGES: widget = self.ui.ch_send_control_changes elif option == PLUGIN_OPTION_SEND_CHANNEL_PRESSURE: widget = self.ui.ch_send_channel_pressure elif option == PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH: widget = self.ui.ch_send_note_aftertouch elif option == PLUGIN_OPTION_SEND_PITCHBEND: widget = self.ui.ch_send_pitchbend elif option == PLUGIN_OPTION_SEND_ALL_SOUND_OFF: widget = self.ui.ch_send_all_sound_off else: return widget.blockSignals(True) widget.setChecked(yesNo) widget.blockSignals(False) #------------------------------------------------------------------ def setVisible(self, yesNo): if yesNo: if not self.fGeometry.isNull(): self.restoreGeometry(self.fGeometry) else: self.fGeometry = self.saveGeometry() QDialog.setVisible(self, yesNo) #------------------------------------------------------------------ def idleSlow(self): # Check Tab icons for i in range(len(self.fTabIconTimers)): if self.fTabIconTimers[i] == ICON_STATE_ON: self.fTabIconTimers[i] = ICON_STATE_WAIT elif self.fTabIconTimers[i] == ICON_STATE_WAIT: self.fTabIconTimers[i] = ICON_STATE_OFF elif self.fTabIconTimers[i] == ICON_STATE_OFF: self.fTabIconTimers[i] = ICON_STATE_NULL self.ui.tabWidget.setTabIcon(i+1, self.fTabIconOff) # Check parameters needing update for index, value in self.fParametersToUpdate: if index == PARAMETER_DRYWET: self.ui.dial_drywet.blockSignals(True) self.ui.dial_drywet.setValue(value) self.ui.dial_drywet.blockSignals(False) elif index == PARAMETER_VOLUME: self.ui.dial_vol.blockSignals(True) self.ui.dial_vol.setValue(value) self.ui.dial_vol.blockSignals(False) elif index == PARAMETER_BALANCE_LEFT: self.ui.dial_b_left.blockSignals(True) self.ui.dial_b_left.setValue(value) self.ui.dial_b_left.blockSignals(False) elif index == PARAMETER_BALANCE_RIGHT: self.ui.dial_b_right.blockSignals(True) self.ui.dial_b_right.setValue(value) self.ui.dial_b_right.blockSignals(False) elif index == PARAMETER_PANNING: self.ui.dial_pan.blockSignals(True) self.ui.dial_pan.setValue(value) self.ui.dial_pan.blockSignals(False) elif index == PARAMETER_CTRL_CHANNEL: self.fControlChannel = round(value) self.ui.sb_ctrl_channel.blockSignals(True) self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1) self.ui.sb_ctrl_channel.blockSignals(False) self.ui.keyboard.allNotesOff() self._updateCtrlPrograms() elif index >= 0: for paramType, paramId, paramWidget in self.fParameterList: if paramId != index: continue # FIXME see below if paramType != PARAMETER_INPUT: continue paramWidget.blockSignals(True) paramWidget.setValue(value) paramWidget.blockSignals(False) #if paramType == PARAMETER_INPUT: tabIndex = paramWidget.getTabIndex() if self.fTabIconTimers[tabIndex-1] == ICON_STATE_NULL: self.ui.tabWidget.setTabIcon(tabIndex, self.fTabIconOn) self.fTabIconTimers[tabIndex-1] = ICON_STATE_ON break # Clear all parameters self.fParametersToUpdate = [] # Update parameter outputs | FIXME needed? for paramType, paramId, paramWidget in self.fParameterList: if paramType != PARAMETER_OUTPUT: continue paramWidget.blockSignals(True) paramWidget.setValue(self.host.get_current_parameter_value(self.fPluginId, paramId)) paramWidget.blockSignals(False) #------------------------------------------------------------------ @pyqtSlot() def slot_stateSave(self): if self.fPluginInfo['type'] == PLUGIN_LV2: # TODO return if self.fCurrentStateFilename: askTry = QMessageBox.question(self, self.tr("Overwrite?"), self.tr("Overwrite previously created file?"), QMessageBox.Ok|QMessageBox.Cancel) if askTry == QMessageBox.Ok: self.host.save_plugin_state(self.fPluginId, self.fCurrentStateFilename) return self.fCurrentStateFilename = None fileFilter = self.tr("Carla State File (*.carxs)") filename, ok = QFileDialog.getSaveFileName(self, self.tr("Save Plugin State File"), filter=fileFilter) # FIXME use ok value, test if it works as expected if not filename: return if not filename.lower().endswith(".carxs"): filename += ".carxs" self.fCurrentStateFilename = filename self.host.save_plugin_state(self.fPluginId, self.fCurrentStateFilename) @pyqtSlot() def slot_stateLoad(self): if self.fPluginInfo['type'] == PLUGIN_LV2: presetList = [] for i in range(self.host.get_program_count(self.fPluginId)): presetList.append("%03i - %s" % (i+1, self.host.get_program_name(self.fPluginId, i))) ret = QInputDialog.getItem(self, self.tr("Open LV2 Preset"), self.tr("Select an LV2 Preset:"), presetList, 0, False) if ret[1]: index = int(ret[0].split(" - ", 1)[0])-1 self.host.set_program(self.fPluginId, index) self.setMidiProgram(-1) return fileFilter = self.tr("Carla State File (*.carxs)") filename, ok = QFileDialog.getOpenFileName(self, self.tr("Open Plugin State File"), filter=fileFilter) # FIXME use ok value, test if it works as expected if not filename: return self.fCurrentStateFilename = filename self.host.load_plugin_state(self.fPluginId, self.fCurrentStateFilename) #------------------------------------------------------------------ @pyqtSlot(bool) def slot_optionChanged(self, clicked): sender = self.sender() if sender == self.ui.ch_use_chunks: option = PLUGIN_OPTION_USE_CHUNKS elif sender == self.ui.ch_fixed_buffer: option = PLUGIN_OPTION_FIXED_BUFFERS elif sender == self.ui.ch_force_stereo: option = PLUGIN_OPTION_FORCE_STEREO elif sender == self.ui.ch_map_program_changes: option = PLUGIN_OPTION_MAP_PROGRAM_CHANGES elif sender == self.ui.ch_send_program_changes: option = PLUGIN_OPTION_SEND_PROGRAM_CHANGES elif sender == self.ui.ch_send_control_changes: option = PLUGIN_OPTION_SEND_CONTROL_CHANGES elif sender == self.ui.ch_send_channel_pressure: option = PLUGIN_OPTION_SEND_CHANNEL_PRESSURE elif sender == self.ui.ch_send_note_aftertouch: option = PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH elif sender == self.ui.ch_send_pitchbend: option = PLUGIN_OPTION_SEND_PITCHBEND elif sender == self.ui.ch_send_all_sound_off: option = PLUGIN_OPTION_SEND_ALL_SOUND_OFF else: return #-------------------------------------------------------------- # handle map-program-changes and send-program-changes conflict if option == PLUGIN_OPTION_MAP_PROGRAM_CHANGES and clicked: self.ui.ch_send_program_changes.setEnabled(False) # disable send-program-changes if needed if self.ui.ch_send_program_changes.isChecked(): self.host.set_option(self.fPluginId, PLUGIN_OPTION_SEND_PROGRAM_CHANGES, False) #-------------------------------------------------------------- # set option self.host.set_option(self.fPluginId, option, clicked) #-------------------------------------------------------------- # handle map-program-changes and send-program-changes conflict if option == PLUGIN_OPTION_MAP_PROGRAM_CHANGES and not clicked: self.ui.ch_send_program_changes.setEnabled(self.fPluginInfo['optionsAvailable'] & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) # restore send-program-changes if needed if self.ui.ch_send_program_changes.isChecked(): self.host.set_option(self.fPluginId, PLUGIN_OPTION_SEND_PROGRAM_CHANGES, True) #------------------------------------------------------------------ @pyqtSlot(float) def slot_dryWetChanged(self, value): self.host.set_drywet(self.fPluginId, value) if self.fParent is not None: self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_DRYWET, value) @pyqtSlot(float) def slot_volumeChanged(self, value): self.host.set_volume(self.fPluginId, value) if self.fParent is not None: self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_VOLUME, value) @pyqtSlot(float) def slot_balanceLeftChanged(self, value): self.host.set_balance_left(self.fPluginId, value) if self.fParent is not None: self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_BALANCE_LEFT, value) @pyqtSlot(float) def slot_balanceRightChanged(self, value): self.host.set_balance_right(self.fPluginId, value) if self.fParent is not None: self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_BALANCE_RIGHT, value) @pyqtSlot(float) def slot_panChanged(self, value): self.host.set_panning(self.fPluginId, value) if self.fParent is not None: self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_PANNING, value) @pyqtSlot(int) def slot_ctrlChannelChanged(self, value): self.fControlChannel = value-1 self.host.set_ctrl_channel(self.fPluginId, self.fControlChannel) self.ui.keyboard.allNotesOff() self._updateCtrlPrograms() #------------------------------------------------------------------ @pyqtSlot(int, float) def slot_parameterValueChanged(self, parameterId, value): self.host.set_parameter_value(self.fPluginId, parameterId, value) if self.fParent is not None: self.fParent.editDialogParameterValueChanged(self.fPluginId, parameterId, value) @pyqtSlot(int, int) def slot_parameterMappedControlChanged(self, parameterId, control): self.host.set_parameter_mapped_control_index(self.fPluginId, parameterId, control) @pyqtSlot(int, float, float) def slot_parameterMappedRangeChanged(self, parameterId, minimum, maximum): self.host.set_parameter_mapped_range(self.fPluginId, parameterId, minimum, maximum) @pyqtSlot(int, int) def slot_parameterMidiChannelChanged(self, parameterId, channel): self.host.set_parameter_midi_channel(self.fPluginId, parameterId, channel-1) #------------------------------------------------------------------ @pyqtSlot(int) def slot_programIndexChanged(self, index): self.host.set_program(self.fPluginId, index) if self.fParent is not None: self.fParent.editDialogProgramChanged(self.fPluginId, index) self._updateParameterValues() @pyqtSlot(int) def slot_midiProgramIndexChanged(self, index): self.host.set_midi_program(self.fPluginId, index) if self.fParent is not None: self.fParent.editDialogMidiProgramChanged(self.fPluginId, index) self._updateParameterValues() #------------------------------------------------------------------ @pyqtSlot(int) def slot_noteOn(self, note): if self.fControlChannel >= 0: self.host.send_midi_note(self.fPluginId, self.fControlChannel, note, 100) if self.fParent is not None: self.fParent.editDialogNotePressed(self.fPluginId, note) @pyqtSlot(int) def slot_noteOff(self, note): if self.fControlChannel >= 0: self.host.send_midi_note(self.fPluginId, self.fControlChannel, note, 0) if self.fParent is not None: self.fParent.editDialogNoteReleased(self.fPluginId, note) #------------------------------------------------------------------ @pyqtSlot() def slot_finished(self): if self.fParent is not None: self.fParent.editDialogVisibilityChanged(self.fPluginId, False) #------------------------------------------------------------------ @pyqtSlot() def slot_knobCustomMenu(self): sender = self.sender() knobName = sender.objectName() if knobName == "dial_drywet": minimum = 0.0 maximum = 1.0 default = 1.0 label = "Dry/Wet" elif knobName == "dial_vol": minimum = 0.0 maximum = 1.27 default = 1.0 label = "Volume" elif knobName == "dial_b_left": minimum = -1.0 maximum = 1.0 default = -1.0 label = "Balance-Left" elif knobName == "dial_b_right": minimum = -1.0 maximum = 1.0 default = 1.0 label = "Balance-Right" elif knobName == "dial_pan": minimum = -1.0 maximum = 1.0 default = 0.0 label = "Panning" else: minimum = 0.0 maximum = 1.0 default = 0.5 label = "Unknown" menu = QMenu(self) actReset = menu.addAction(self.tr("Reset (%i%%)" % (default*100))) menu.addSeparator() actMinimum = menu.addAction(self.tr("Set to Minimum (%i%%)" % (minimum*100))) actCenter = menu.addAction(self.tr("Set to Center")) actMaximum = menu.addAction(self.tr("Set to Maximum (%i%%)" % (maximum*100))) menu.addSeparator() actSet = menu.addAction(self.tr("Set value...")) if label not in ("Balance-Left", "Balance-Right", "Panning"): menu.removeAction(actCenter) actSelected = menu.exec_(QCursor.pos()) if actSelected == actSet: current = minimum + (maximum-minimum)*(float(sender.value())/10000) value, ok = QInputDialog.getInt(self, self.tr("Set value"), label, round(current*100.0), round(minimum*100.0), round(maximum*100.0), 1) if ok: value = float(value)/100.0 if not ok: return elif actSelected == actMinimum: value = minimum elif actSelected == actMaximum: value = maximum elif actSelected == actReset: value = default elif actSelected == actCenter: value = 0.0 else: return sender.setValue(value, True) #------------------------------------------------------------------ @pyqtSlot() def slot_channelCustomMenu(self): menu = QMenu(self) actNone = menu.addAction(self.tr("None")) if self.fControlChannel+1 == 0: actNone.setCheckable(True) actNone.setChecked(True) for i in range(1, 16+1): action = menu.addAction("%i" % i) if self.fControlChannel+1 == i: action.setCheckable(True) action.setChecked(True) actSel = menu.exec_(QCursor.pos()) if not actSel: pass elif actSel == actNone: self.ui.sb_ctrl_channel.setValue(0) elif actSel: selChannel = int(actSel.text()) self.ui.sb_ctrl_channel.setValue(selChannel) #------------------------------------------------------------------ def _createParameterWidgets(self, paramType, paramListFull, tabPageName): groupWidgets = {} for paramList, width in paramListFull: if len(paramList) == 0: break tabIndex = self.ui.tabWidget.count() scrollArea = QScrollArea(self.ui.tabWidget) scrollArea.setWidgetResizable(True) scrollArea.setFrameStyle(0) palette1 = scrollArea.palette() palette1.setColor(QPalette.Background, Qt.transparent) scrollArea.setPalette(palette1) palette2 = scrollArea.palette() palette2.setColor(QPalette.Background, palette2.color(QPalette.Button)) scrollAreaWidget = QWidget(scrollArea) scrollAreaLayout = QVBoxLayout(scrollAreaWidget) scrollAreaLayout.setSpacing(3) for paramInfo in paramList: groupName = paramInfo['groupName'] if groupName: groupSymbol, groupName = groupName.split(":",1) groupLayout, groupWidget = groupWidgets.get(groupSymbol, (None, None)) if groupLayout is None: groupWidget = CollapsibleBox(groupName, scrollAreaWidget) groupLayout = groupWidget.getContentLayout() groupWidget.setPalette(palette2) scrollAreaLayout.addWidget(groupWidget) groupWidgets[groupSymbol] = (groupLayout, groupWidget) else: groupLayout = scrollAreaLayout groupWidget = scrollAreaWidget paramWidget = PluginParameter(groupWidget, self.host, paramInfo, self.fPluginId, tabIndex) paramWidget.setLabelWidth(width) groupLayout.addWidget(paramWidget) self.fParameterList.append((paramType, paramInfo['index'], paramWidget)) if paramType == PARAMETER_INPUT: paramWidget.valueChanged.connect(self.slot_parameterValueChanged) paramWidget.mappedControlChanged.connect(self.slot_parameterMappedControlChanged) paramWidget.mappedRangeChanged.connect(self.slot_parameterMappedRangeChanged) paramWidget.midiChannelChanged.connect(self.slot_parameterMidiChannelChanged) scrollAreaLayout.addStretch() scrollArea.setWidget(scrollAreaWidget) self.ui.tabWidget.addTab(scrollArea, tabPageName) if paramType == PARAMETER_INPUT: self.ui.tabWidget.setTabIcon(tabIndex, self.fTabIconOff) self.fTabIconTimers.append(ICON_STATE_NULL) def _updateCtrlPrograms(self): self.ui.keyboard.setEnabled(self.fControlChannel >= 0) if self.fPluginInfo['category'] != PLUGIN_CATEGORY_SYNTH or self.fPluginInfo['type'] not in (PLUGIN_INTERNAL, PLUGIN_SF2): return if self.fControlChannel < 0: self.ui.cb_programs.setEnabled(False) self.ui.cb_midi_programs.setEnabled(False) return self.ui.cb_programs.setEnabled(True) self.ui.cb_midi_programs.setEnabled(True) pIndex = self.host.get_current_program_index(self.fPluginId) if self.ui.cb_programs.currentIndex() != pIndex: self.setProgram(pIndex) mpIndex = self.host.get_current_midi_program_index(self.fPluginId) if self.ui.cb_midi_programs.currentIndex() != mpIndex: self.setMidiProgram(mpIndex) def _updateParameterValues(self): for paramType, paramId, paramWidget in self.fParameterList: paramWidget.blockSignals(True) paramWidget.setValue(self.host.get_current_parameter_value(self.fPluginId, paramId)) paramWidget.blockSignals(False) #------------------------------------------------------------------ def testTimer(self): self.fIdleTimerId = self.startTimer(50) self.SIGTERM.connect(self.testTimerClose) gCarla.gui = self setUpSignals() def testTimerClose(self): self.close() app.quit() #------------------------------------------------------------------ def closeEvent(self, event): if self.fIdleTimerId != 0: self.killTimer(self.fIdleTimerId) self.fIdleTimerId = 0 self.host.engine_close() QDialog.closeEvent(self, event) def timerEvent(self, event): if event.timerId() == self.fIdleTimerId: self.host.engine_idle() self.idleSlow() QDialog.timerEvent(self, event) def done(self, r): QDialog.done(self, r) self.close() # ------------------------------------------------------------------------------------------------------------ # Main if __name__ == '__main__': from carla_app import CarlaApplication from carla_host import initHost, loadHostSettings app = CarlaApplication() host = initHost("Widgets", None, False, False, False) loadHostSettings(host) host.engine_init("JACK", "Carla-Widgets") host.add_plugin(BINARY_NATIVE, PLUGIN_DSSI, "/usr/lib/dssi/karplong.so", "karplong", "karplong", 0, None, 0x0) host.set_active(0, True) gui1 = CarlaAboutW(None, host) gui1.show() gui2 = PluginEdit(None, host, 0) gui2.testTimer() gui2.show() app.exit_exec()