#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Common Carla code # Copyright (C) 2011-2012 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 COPYING file # Imports (Global) import platform, sys from copy import deepcopy from decimal import Decimal from sip import unwrapinstance from PyQt4.QtCore import pyqtSlot, Qt, QSettings, QTimer from PyQt4.QtGui import QColor, QCursor, QDialog, QFontMetrics, QFrame, QInputDialog, QMenu, QPainter, QVBoxLayout, QWidget from PyQt4.QtXml import QDomDocument try: from PyQt4.QtGui import QX11EmbedContainer GuiContainer = QX11EmbedContainer except: GuiContainer = QWidget # Imports (Custom) import ui_carla_edit, ui_carla_parameter, ui_carla_plugin from shared import * # Carla Host object class CarlaHostObject(object): __slots__ = [ 'Host', 'gui', 'isControl', 'processMode', 'maxParameters' ] Carla = CarlaHostObject() Carla.Host = None Carla.gui = None Carla.isControl = False is64bit = bool(platform.architecture()[0] == "64bit" and sys.maxsize > 2**32) # ------------------------------------------------------------------------------------------------ # Backend defines # static max values MAX_PLUGINS = 99 MAX_PARAMETERS = 200 # plugin hints PLUGIN_IS_BRIDGE = 0x001 PLUGIN_IS_SYNTH = 0x002 PLUGIN_HAS_GUI = 0x004 PLUGIN_USES_CHUNKS = 0x008 PLUGIN_USES_SINGLE_THREAD = 0x010 PLUGIN_CAN_DRYWET = 0x020 PLUGIN_CAN_VOLUME = 0x040 PLUGIN_CAN_BALANCE = 0x080 PLUGIN_CAN_FORCE_STEREO = 0x100 # parameter hints PARAMETER_IS_BOOLEAN = 0x01 PARAMETER_IS_INTEGER = 0x02 PARAMETER_IS_LOGARITHMIC = 0x04 PARAMETER_IS_ENABLED = 0x08 PARAMETER_IS_AUTOMABLE = 0x10 PARAMETER_USES_SAMPLERATE = 0x20 PARAMETER_USES_SCALEPOINTS = 0x40 PARAMETER_USES_CUSTOM_TEXT = 0x80 # enum BinaryType BINARY_NONE = 0 BINARY_POSIX32 = 1 BINARY_POSIX64 = 2 BINARY_WIN32 = 3 BINARY_WIN64 = 4 BINARY_OTHER = 5 # enum PluginType PLUGIN_NONE = 0 PLUGIN_INTERNAL = 1 PLUGIN_LADSPA = 2 PLUGIN_DSSI = 3 PLUGIN_LV2 = 4 PLUGIN_VST = 5 PLUGIN_GIG = 6 PLUGIN_SF2 = 7 PLUGIN_SFZ = 8 # enum PluginCategory PLUGIN_CATEGORY_NONE = 0 PLUGIN_CATEGORY_SYNTH = 1 PLUGIN_CATEGORY_DELAY = 2 # also Reverb PLUGIN_CATEGORY_EQ = 3 PLUGIN_CATEGORY_FILTER = 4 PLUGIN_CATEGORY_DYNAMICS = 5 # Amplifier, Compressor, Gate PLUGIN_CATEGORY_MODULATOR = 6 # Chorus, Flanger, Phaser PLUGIN_CATEGORY_UTILITY = 7 # Analyzer, Converter, Mixer PLUGIN_CATEGORY_OTHER = 8 # used to check if a plugin has a category # enum ParameterType PARAMETER_UNKNOWN = 0 PARAMETER_INPUT = 1 PARAMETER_OUTPUT = 2 PARAMETER_LATENCY = 3 PARAMETER_SAMPLE_RATE = 4 PARAMETER_LV2_FREEWHEEL = 5 PARAMETER_LV2_TIME = 6 # enum InternalParametersIndex PARAMETER_NULL = -1 PARAMETER_ACTIVE = -2 PARAMETER_DRYWET = -3 PARAMETER_VOLUME = -4 PARAMETER_BALANCE_LEFT = -5 PARAMETER_BALANCE_RIGHT = -6 # enum CustomDataType CUSTOM_DATA_INVALID = 0 CUSTOM_DATA_STRING = 1 CUSTOM_DATA_PATH = 2 CUSTOM_DATA_CHUNK = 3 CUSTOM_DATA_BINARY = 4 # enum GuiType GUI_NONE = 0 GUI_INTERNAL_QT4 = 1 GUI_INTERNAL_COCOA = 2 GUI_INTERNAL_HWND = 3 GUI_INTERNAL_X11 = 4 GUI_EXTERNAL_LV2 = 5 GUI_EXTERNAL_SUIL = 6 GUI_EXTERNAL_OSC = 7 # enum OptionsType OPTION_PROCESS_NAME = 0 OPTION_PROCESS_MODE = 1 OPTION_PROCESS_HIGH_PRECISION = 2 OPTION_MAX_PARAMETERS = 3 OPTION_PREFERRED_BUFFER_SIZE = 4 OPTION_PREFERRED_SAMPLE_RATE = 5 OPTION_FORCE_STEREO = 6 OPTION_USE_DSSI_VST_CHUNKS = 7 OPTION_PREFER_PLUGIN_BRIDGES = 8 OPTION_PREFER_UI_BRIDGES = 9 OPTION_OSC_UI_TIMEOUT = 10 OPTION_PATH_LADSPA = 11 OPTION_PATH_DSSI = 12 OPTION_PATH_LV2 = 13 OPTION_PATH_VST = 14 OPTION_PATH_GIG = 15 OPTION_PATH_SF2 = 16 OPTION_PATH_SFZ = 17 OPTION_PATH_BRIDGE_POSIX32 = 18 OPTION_PATH_BRIDGE_POSIX64 = 19 OPTION_PATH_BRIDGE_WIN32 = 20 OPTION_PATH_BRIDGE_WIN64 = 21 OPTION_PATH_BRIDGE_LV2_GTK2 = 22 OPTION_PATH_BRIDGE_LV2_GTK3 = 23 OPTION_PATH_BRIDGE_LV2_QT4 = 24 OPTION_PATH_BRIDGE_LV2_X11 = 25 OPTION_PATH_BRIDGE_VST_HWND = 26 OPTION_PATH_BRIDGE_VST_X11 = 27 # enum CallbackType CALLBACK_DEBUG = 0 CALLBACK_PARAMETER_VALUE_CHANGED = 1 CALLBACK_PARAMETER_MIDI_CHANNEL_CHANGED = 2 CALLBACK_PARAMETER_MIDI_CC_CHANGED = 3 CALLBACK_PROGRAM_CHANGED = 4 CALLBACK_MIDI_PROGRAM_CHANGED = 5 CALLBACK_NOTE_ON = 6 CALLBACK_NOTE_OFF = 7 CALLBACK_SHOW_GUI = 8 CALLBACK_RESIZE_GUI = 9 CALLBACK_UPDATE = 10 CALLBACK_RELOAD_INFO = 11 CALLBACK_RELOAD_PARAMETERS = 12 CALLBACK_RELOAD_PROGRAMS = 13 CALLBACK_RELOAD_ALL = 14 CALLBACK_NSM_ANNOUNCE = 15 CALLBACK_NSM_OPEN1 = 16 CALLBACK_NSM_OPEN2 = 17 CALLBACK_NSM_SAVE = 18 CALLBACK_ERROR = 19 CALLBACK_QUIT = 20 # enum ProcessModeType PROCESS_MODE_SINGLE_CLIENT = 0 PROCESS_MODE_MULTIPLE_CLIENTS = 1 PROCESS_MODE_CONTINUOUS_RACK = 2 PROCESS_MODE_PATCHBAY = 3 # ------------------------------------------------------------------------------------------------ # Carla GUI stuff Carla.processMode = PROCESS_MODE_CONTINUOUS_RACK Carla.maxParameters = MAX_PARAMETERS # set native binary type if HAIKU or LINUX or MACOS: BINARY_NATIVE = BINARY_POSIX64 if is64bit else BINARY_POSIX32 elif WINDOWS: BINARY_NATIVE = BINARY_WIN64 if is64bit else BINARY_WIN32 else: BINARY_NATIVE = BINARY_OTHER ICON_STATE_NULL = 0 ICON_STATE_WAIT = 1 ICON_STATE_OFF = 2 ICON_STATE_ON = 3 PALETTE_COLOR_NONE = 0 PALETTE_COLOR_WHITE = 1 PALETTE_COLOR_RED = 2 PALETTE_COLOR_GREEN = 3 PALETTE_COLOR_BLUE = 4 PALETTE_COLOR_YELLOW = 5 PALETTE_COLOR_ORANGE = 6 PALETTE_COLOR_BROWN = 7 PALETTE_COLOR_PINK = 8 save_state_dict = { 'Type': "", 'Name': "", 'Label': "", 'Binary': "", 'UniqueID': 0, 'Active': False, 'DryWet': 1.0, 'Volume': 1.0, 'Balance-Left': -1.0, 'Balance-Right': 1.0, 'Parameters': [], 'CurrentProgramIndex': -1, 'CurrentProgramName': "", 'CurrentMidiBank': -1, 'CurrentMidiProgram': -1, 'CustomData': [], 'Chunk': None } save_state_parameter = { 'index': 0, 'name': "", 'symbol': "", 'value': 0.0, 'midiChannel': 1, 'midiCC': -1 } save_state_custom_data = { 'type': CUSTOM_DATA_INVALID, 'key': "", 'value': "" } def getCustomDataTypeString(dtype): if dtype == CUSTOM_DATA_STRING: return "string" if dtype == CUSTOM_DATA_PATH: return "path" if dtype == CUSTOM_DATA_CHUNK: return "chunk" if dtype == CUSTOM_DATA_BINARY: return "binary" return "null" def getCustomDataStringType(stype): if stype == "string": return CUSTOM_DATA_STRING if stype == "path": return CUSTOM_DATA_PATH if stype == "chunk": return CUSTOM_DATA_CHUNK if stype == "binary": return CUSTOM_DATA_BINARY return CUSTOM_DATA_INVALID def getSaveStateDictFromXML(xmlNode): x_save_state_dict = deepcopy(save_state_dict) node = xmlNode.firstChild() while not node.isNull(): # ------------------------------------------------------ # Info if node.toElement().tagName() == "Info": xmlInfo = node.toElement().firstChild() while not xmlInfo.isNull(): tag = xmlInfo.toElement().tagName() text = xmlInfo.toElement().text().strip() if tag == "Type": x_save_state_dict['Type'] = text elif tag == "Name": x_save_state_dict['Name'] = xmlSafeString(text, False) elif tag in ("Label", "URI"): x_save_state_dict['Label'] = xmlSafeString(text, False) elif tag == "Binary": x_save_state_dict['Binary'] = xmlSafeString(text, False) elif tag == "UniqueID": if text.isdigit(): x_save_state_dict['UniqueID'] = int(text) xmlInfo = xmlInfo.nextSibling() # ------------------------------------------------------ # Data elif node.toElement().tagName() == "Data": xmlData = node.toElement().firstChild() while not xmlData.isNull(): tag = xmlData.toElement().tagName() text = xmlData.toElement().text().strip() # ---------------------------------------------- # Internal Data if tag == "Active": x_save_state_dict['Active'] = bool(text == "Yes") elif tag == "DryWet": if isNumber(text): x_save_state_dict['DryWet'] = float(text) elif tag == "Volume": if isNumber(text): x_save_state_dict['Volume'] = float(text) elif tag == "Balance-Left": if isNumber(text): x_save_state_dict['Balance-Left'] = float(text) elif tag == "Balance-Right": if isNumber(text): x_save_state_dict['Balance-Right'] = float(text) # ---------------------------------------------- # Program (current) elif tag == "CurrentProgramIndex": if text.isdigit(): x_save_state_dict['CurrentProgramIndex'] = int(text) elif tag == "CurrentProgramName": x_save_state_dict['CurrentProgramName'] = xmlSafeString(text, False) # ---------------------------------------------- # Midi Program (current) elif tag == "CurrentMidiBank": if text.isdigit(): x_save_state_dict['CurrentMidiBank'] = int(text) elif tag == "CurrentMidiProgram": if text.isdigit(): x_save_state_dict['CurrentMidiProgram'] = int(text) # ---------------------------------------------- # Parameters elif tag == "Parameter": x_save_state_parameter = deepcopy(save_state_parameter) xmlSubData = xmlData.toElement().firstChild() while not xmlSubData.isNull(): pTag = xmlSubData.toElement().tagName() pText = xmlSubData.toElement().text().strip() if pTag == "index": if pText.isdigit(): x_save_state_parameter['index'] = int(pText) elif pTag == "name": x_save_state_parameter['name'] = xmlSafeString(pText, False) elif pTag == "symbol": x_save_state_parameter['symbol'] = xmlSafeString(pText, False) elif pTag == "value": if isNumber(pText): x_save_state_parameter['value'] = float(pText) elif pTag == "midiChannel": if pText.isdigit(): x_save_state_parameter['midiChannel'] = int(pText) elif pTag == "midiCC": if pText.isdigit(): x_save_state_parameter['midiCC'] = int(pText) xmlSubData = xmlSubData.nextSibling() x_save_state_dict['Parameters'].append(x_save_state_parameter) # ---------------------------------------------- # Custom Data elif tag == "CustomData": x_save_state_custom_data = deepcopy(save_state_custom_data) xmlSubData = xmlData.toElement().firstChild() while not xmlSubData.isNull(): cTag = xmlSubData.toElement().tagName() cText = xmlSubData.toElement().text().strip() if cTag == "type": x_save_state_custom_data['type'] = getCustomDataStringType(cText) elif cTag == "key": x_save_state_custom_data['key'] = xmlSafeString(cText, False) elif cTag == "value": x_save_state_custom_data['value'] = xmlSafeString(cText, False) xmlSubData = xmlSubData.nextSibling() x_save_state_dict['CustomData'].append(x_save_state_custom_data) # ---------------------------------------------- # Chunk elif tag == "Chunk": x_save_state_dict['Chunk'] = xmlSafeString(text, False) # ---------------------------------------------- xmlData = xmlData.nextSibling() # ------------------------------------------------------ node = node.nextSibling() return x_save_state_dict def xmlSafeString(string, toXml): if toXml: return string.replace("&", "&").replace("<","<").replace(">",">").replace("'","'").replace("\"",""") else: return string.replace("&", "&").replace("<","<").replace(">",">").replace("'","'").replace(""","\"") # ------------------------------------------------------------------------------------------------ # Common widgets # Plugin Parameter class PluginParameter(QWidget, ui_carla_parameter.Ui_PluginParameter): def __init__(self, parent, pInfo, pluginId, tabIndex): QWidget.__init__(self, parent) self.setupUi(self) pType = pInfo['type'] pHints = pInfo['hints'] self.m_midiCC = -1 self.m_midiChannel = 1 self.m_pluginId = pluginId self.m_parameterId = pInfo['index'] self.m_tabIndex = tabIndex self.add_MIDI_CCs_to_ComboBox() self.label.setText(pInfo['name']) if pType == PARAMETER_INPUT: self.widget.set_minimum(pInfo['minimum']) self.widget.set_maximum(pInfo['maximum']) self.widget.set_default(pInfo['default']) self.widget.set_value(pInfo['current'], False) self.widget.set_label(pInfo['unit']) self.widget.set_step(pInfo['step']) self.widget.set_step_small(pInfo['stepSmall']) self.widget.set_step_large(pInfo['stepLarge']) self.widget.set_scalepoints(pInfo['scalepoints'], bool(pHints & PARAMETER_USES_SCALEPOINTS)) if not pHints & PARAMETER_IS_ENABLED: self.widget.set_read_only(True) self.combo.setEnabled(False) self.sb_channel.setEnabled(False) elif not pHints & PARAMETER_IS_AUTOMABLE: self.combo.setEnabled(False) self.sb_channel.setEnabled(False) elif pType == PARAMETER_OUTPUT: self.widget.set_minimum(pInfo['minimum']) self.widget.set_maximum(pInfo['maximum']) self.widget.set_value(pInfo['current'], False) self.widget.set_label(pInfo['unit']) self.widget.set_read_only(True) if not pHints & PARAMETER_IS_AUTOMABLE: self.combo.setEnabled(False) self.sb_channel.setEnabled(False) else: self.widget.setVisible(False) self.combo.setVisible(False) self.sb_channel.setVisible(False) self.set_parameter_midi_cc(pInfo['midiCC']) self.set_parameter_midi_channel(pInfo['midiChannel']) self.connect(self.widget, SIGNAL("valueChanged(double)"), SLOT("slot_valueChanged(double)")) self.connect(self.sb_channel, SIGNAL("valueChanged(int)"), SLOT("slot_midiChannelChanged(int)")) self.connect(self.combo, SIGNAL("currentIndexChanged(int)"), SLOT("slot_midiCcChanged(int)")) #if force_parameters_style: #self.widget.force_plastique_style() if pHints & PARAMETER_USES_CUSTOM_TEXT: self.widget.set_text_call(self.textCallFunction) self.widget.updateAll() def set_default_value(self, value): self.widget.set_default(value) def set_parameter_value(self, value, send=True): self.widget.set_value(value, send) def set_parameter_midi_cc(self, cc): self.m_midiCC = cc self.set_MIDI_CC_in_ComboBox(cc) def set_parameter_midi_channel(self, channel): self.m_midiChannel = channel+1 self.sb_channel.setValue(channel+1) def add_MIDI_CCs_to_ComboBox(self): for MIDI_CC in MIDI_CC_LIST: self.combo.addItem(MIDI_CC) def set_MIDI_CC_in_ComboBox(self, cc): for i in range(len(MIDI_CC_LIST)): ccText = MIDI_CC_LIST[i].split(" ")[0] if int(ccText, 16) == cc: ccIndex = i break else: ccIndex = -1 self.combo.setCurrentIndex(ccIndex+1) def tabIndex(self): return self.m_tabIndex def textCallFunction(self): return cString(Carla.Host.get_parameter_text(self.m_pluginId, self.m_parameterId)) @pyqtSlot(float) def slot_valueChanged(self, value): self.emit(SIGNAL("valueChanged(int, double)"), self.m_parameterId, value) @pyqtSlot(int) def slot_midiCcChanged(self, ccIndex): if ccIndex <= 0: cc = -1 else: ccText = MIDI_CC_LIST[ccIndex - 1].split(" ")[0] cc = int(ccText, 16) if self.m_midiCC != cc: self.emit(SIGNAL("midiCcChanged(int, int)"), self.m_parameterId, cc) self.m_midiCC = cc @pyqtSlot(int) def slot_midiChannelChanged(self, channel): if self.m_midiChannel != channel: self.emit(SIGNAL("midiChannelChanged(int, int)"), self.m_parameterId, channel) self.m_midiChannel = channel # Plugin Editor (Built-in) class PluginEdit(QDialog, ui_carla_edit.Ui_PluginEdit): def __init__(self, parent, pluginId): QDialog.__init__(self, parent) self.setupUi(self) self.m_firstShow = True self.m_geometry = None self.m_pluginId = pluginId self.m_pluginInfo = None self.m_parameterCount = 0 self.m_parameterList = [] # (type, id, widget) self.m_parameterIdsToUpdate = [] # id self.m_currentProgram = -1 self.m_currentMidiProgram = -1 self.m_currentStateFilename = None self.m_tabIconOff = QIcon(":/bitmaps/led_off.png") self.m_tabIconOn = QIcon(":/bitmaps/led_yellow.png") self.m_tabIconCount = 0 self.m_tabIconTimers = [] self.keyboard.setMode(self.keyboard.HORIZONTAL) self.keyboard.setOctaves(6) self.scrollArea.ensureVisible(self.keyboard.width() * 1 / 5, 0) self.scrollArea.setVisible(False) # TODO - not implemented yet self.b_reload_program.setEnabled(False) self.b_reload_midi_program.setEnabled(False) # Not available for carla-control if Carla.isControl: self.b_load_state.setEnabled(False) self.b_save_state.setEnabled(False) self.connect(self.b_save_state, SIGNAL("clicked()"), SLOT("slot_saveState()")) self.connect(self.b_load_state, SIGNAL("clicked()"), SLOT("slot_loadState()")) self.connect(self.keyboard, SIGNAL("noteOn(int)"), SLOT("slot_noteOn(int)")) self.connect(self.keyboard, SIGNAL("noteOff(int)"), SLOT("slot_noteOff(int)")) self.connect(self.keyboard, SIGNAL("notesOn()"), SLOT("slot_notesOn()")) self.connect(self.keyboard, SIGNAL("notesOff()"), SLOT("slot_notesOff()")) self.connect(self.cb_programs, SIGNAL("currentIndexChanged(int)"), SLOT("slot_programIndexChanged(int)")) self.connect(self.cb_midi_programs, SIGNAL("currentIndexChanged(int)"), SLOT("slot_midiProgramIndexChanged(int)")) self.connect(self, SIGNAL("finished(int)"), SLOT("slot_finished()")) self.do_reload_all() def do_reload_all(self): self.m_pluginInfo = Carla.Host.get_plugin_info(self.m_pluginId) self.m_pluginInfo["binary"] = cString(self.m_pluginInfo["binary"]) self.m_pluginInfo["name"] = cString(self.m_pluginInfo["name"]) self.m_pluginInfo["label"] = cString(self.m_pluginInfo["label"]) self.m_pluginInfo["maker"] = cString(self.m_pluginInfo["maker"]) self.m_pluginInfo["copyright"] = cString(self.m_pluginInfo["copyright"]) self.do_reload_info() self.do_reload_parameters() self.do_reload_programs() def do_reload_info(self): pluginName = cString(Carla.Host.get_real_plugin_name(self.m_pluginId)) pluginType = self.m_pluginInfo['type'] pluginHints = self.m_pluginInfo['hints'] # Automatically change to MidiProgram tab if pluginType in (PLUGIN_DSSI, PLUGIN_LV2, PLUGIN_GIG, PLUGIN_SF2, PLUGIN_SFZ) and not self.le_name.text(): self.tab_programs.setCurrentIndex(1) # Set Meta-Data if pluginType == PLUGIN_INTERNAL: self.le_type.setText(self.tr("Internal")) elif pluginType == PLUGIN_LADSPA: self.le_type.setText("LADSPA") elif pluginType == PLUGIN_DSSI: self.le_type.setText("DSSI") elif pluginType == PLUGIN_LV2: self.le_type.setText("LV2") elif pluginType == PLUGIN_VST: self.le_type.setText("VST") elif pluginType == PLUGIN_GIG: self.le_type.setText("GIG") elif pluginType == PLUGIN_SF2: self.le_type.setText("SF2") elif pluginType == PLUGIN_SFZ: self.le_type.setText("SFZ") else: self.le_type.setText(self.tr("Unknown")) self.le_name.setText(pluginName) self.le_name.setToolTip(pluginName) self.le_label.setText(self.m_pluginInfo['label']) self.le_label.setToolTip(self.m_pluginInfo['label']) self.le_maker.setText(self.m_pluginInfo['maker']) self.le_maker.setToolTip(self.m_pluginInfo['maker']) self.le_copyright.setText(self.m_pluginInfo['copyright']) self.le_copyright.setToolTip(self.m_pluginInfo['copyright']) self.le_unique_id.setText(str(self.m_pluginInfo['uniqueId'])) self.le_unique_id.setToolTip(str(self.m_pluginInfo['uniqueId'])) self.label_plugin.setText("\n%s\n" % self.m_pluginInfo['name']) self.setWindowTitle(self.m_pluginInfo['name']) # Set Processing Data audioCountInfo = Carla.Host.get_audio_port_count_info(self.m_pluginId) midiCountInfo = Carla.Host.get_midi_port_count_info(self.m_pluginId) paramCountInfo = Carla.Host.get_parameter_count_info(self.m_pluginId) self.le_ains.setText(str(audioCountInfo['ins'])) self.le_aouts.setText(str(audioCountInfo['outs'])) self.le_params.setText(str(paramCountInfo['ins'])) self.le_couts.setText(str(paramCountInfo['outs'])) self.le_is_synth.setText(self.tr("Yes") if bool(pluginHints & PLUGIN_IS_SYNTH) else self.tr("No")) self.le_has_gui.setText(self.tr("Yes") if bool(pluginHints & PLUGIN_HAS_GUI) else self.tr("No")) # Show/hide keyboard self.scrollArea.setVisible((pluginHints & PLUGIN_IS_SYNTH) > 0 or (midiCountInfo['ins'] > 0 < midiCountInfo['outs'])) # Force-Update parent for new hints (knobs) self.parent().recheck_hints(pluginHints) def do_reload_parameters(self): parameterCount = Carla.Host.get_parameter_count(self.m_pluginId) self.m_parameterList = [] self.m_parameterIdsToUpdate = [] self.m_tabIconCount = 0 self.m_tabIconTimers = [] # remove all parameter tabs for i in range(self.tabWidget.count()): if i == 0: continue self.tabWidget.widget(1).deleteLater() self.tabWidget.removeTab(1) if parameterCount <= 0: pass elif parameterCount <= Carla.maxParameters: paramInputListFull = [] paramOutputListFull = [] paramInputList = [] paramInputWidth = 0 paramOutputList = [] paramOutputWidth = 0 for i in range(parameterCount): paramInfo = Carla.Host.get_parameter_info(self.m_pluginId, i) paramData = Carla.Host.get_parameter_data(self.m_pluginId, i) paramRanges = Carla.Host.get_parameter_ranges(self.m_pluginId, i) if paramData['type'] not in (PARAMETER_INPUT, PARAMETER_OUTPUT): continue parameter = { 'type': paramData['type'], 'hints': paramData['hints'], 'name': cString(paramInfo['name']), 'unit': cString(paramInfo['unit']), 'scalepoints': [], 'index': paramData['index'], 'default': paramRanges['def'], 'minimum': paramRanges['min'], 'maximum': paramRanges['max'], 'step': paramRanges['step'], 'stepSmall': paramRanges['stepSmall'], 'stepLarge': paramRanges['stepLarge'], 'midiCC': paramData['midiCC'], 'midiChannel': paramData['midiChannel'], 'current': Carla.Host.get_current_parameter_value(self.m_pluginId, i) } for j in range(paramInfo['scalePointCount']): scalePointInfo = Carla.Host.get_parameter_scalepoint_info(self.m_pluginId, i, j) parameter['scalepoints'].append( { 'value': scalePointInfo['value'], 'label': cString(scalePointInfo['label']) }) # ----------------------------------------------------------------- # Get width values, in packs of 10 if parameter['type'] == PARAMETER_INPUT: paramInputList.append(parameter) paramInputWidthTMP = QFontMetrics(self.font()).width(parameter['name']) if paramInputWidthTMP > paramInputWidth: paramInputWidth = paramInputWidthTMP if len(paramInputList) == 10: paramInputListFull.append((paramInputList, paramInputWidth)) paramInputList = [] paramInputWidth = 0 elif parameter['type'] == PARAMETER_OUTPUT: paramOutputList.append(parameter) paramOutputWidthTMP = QFontMetrics(self.font()).width(parameter['name']) if paramOutputWidthTMP > paramOutputWidth: paramOutputWidth = paramOutputWidthTMP if len(paramOutputList) == 10: paramOutputListFull.append((paramOutputList, paramOutputWidth)) paramOutputList = [] paramOutputWidth = 0 else: # Final page width values if 0 < len(paramInputList) < 10: paramInputListFull.append((paramInputList, paramInputWidth)) if 0 < len(paramOutputList) < 10: paramOutputListFull.append((paramOutputList, paramOutputWidth)) # ----------------------------------------------------------------- # Create parameter widgets self.createParameterWidgets(PARAMETER_INPUT, paramInputListFull, self.tr("Parameters")) self.createParameterWidgets(PARAMETER_OUTPUT, paramOutputListFull, self.tr("Outputs")) else: # > Carla.maxParameters fakeName = self.tr("This plugin has too many parameters to display here!") paramFakeListFull = [] paramFakeList = [] paramFakeWidth = QFontMetrics(self.font()).width(fakeName) parameter = { 'type': PARAMETER_UNKNOWN, 'hints': 0, 'name': fakeName, 'unit': "", 'scalepoints': [], 'index': 0, 'default': 0, 'minimum': 0, 'maximum': 0, 'step': 0, 'stepSmall': 0, 'stepLarge': 0, 'midiCC': -1, 'midiChannel': 0, 'current': 0.0 } paramFakeList.append(parameter) paramFakeListFull.append((paramFakeList, paramFakeWidth)) self.createParameterWidgets(PARAMETER_UNKNOWN, paramFakeListFull, self.tr("Information")) def createParameterWidgets(self, paramType, paramListFull, tabPageName): i = 1 for paramList, width in paramListFull: if len(paramList) == 0: break tabPageContainer = QWidget(self.tabWidget) tabPageLayout = QVBoxLayout(tabPageContainer) tabPageContainer.setLayout(tabPageLayout) for paramInfo in paramList: paramWidget = PluginParameter(tabPageContainer, paramInfo, self.m_pluginId, self.tabWidget.count()) paramWidget.label.setMinimumWidth(width) paramWidget.label.setMaximumWidth(width) tabPageLayout.addWidget(paramWidget) self.m_parameterList.append((paramType, paramInfo['index'], paramWidget)) if paramType == PARAMETER_INPUT: self.connect(paramWidget, SIGNAL("valueChanged(int, double)"), SLOT("slot_parameterValueChanged(int, double)")) self.connect(paramWidget, SIGNAL("midiChannelChanged(int, int)"), SLOT("slot_parameterMidiChannelChanged(int, int)")) self.connect(paramWidget, SIGNAL("midiCcChanged(int, int)"), SLOT("slot_parameterMidiCcChanged(int, int)")) # FIXME tabPageLayout.addStretch() self.tabWidget.addTab(tabPageContainer, "%s (%i)" % (tabPageName, i)) i += 1 if paramType == PARAMETER_INPUT: self.tabWidget.setTabIcon(paramWidget.tabIndex(), self.m_tabIconOff) self.m_tabIconTimers.append(ICON_STATE_NULL) def do_reload_programs(self): # Programs self.cb_programs.blockSignals(True) self.cb_programs.clear() programCount = Carla.Host.get_program_count(self.m_pluginId) if programCount > 0: self.cb_programs.setEnabled(True) for i in range(programCount): pName = cString(Carla.Host.get_program_name(self.m_pluginId, i)) self.cb_programs.addItem(pName) self.m_currentProgram = Carla.Host.get_current_program_index(self.m_pluginId) self.cb_programs.setCurrentIndex(self.m_currentProgram) else: self.m_currentProgram = -1 self.cb_programs.setEnabled(False) self.cb_programs.blockSignals(False) # MIDI Programs self.cb_midi_programs.blockSignals(True) self.cb_midi_programs.clear() midiProgramCount = Carla.Host.get_midi_program_count(self.m_pluginId) if midiProgramCount > 0: self.cb_midi_programs.setEnabled(True) for i in range(midiProgramCount): mpData = Carla.Host.get_midi_program_data(self.m_pluginId, i) mpBank = int(mpData['bank']) mpProg = int(mpData['program']) mpLabel = cString(mpData['label']) self.cb_midi_programs.addItem("%03i:%03i - %s" % (mpBank, mpProg, mpLabel)) self.m_currentMidiProgram = Carla.Host.get_current_midi_program_index(self.m_pluginId) self.cb_midi_programs.setCurrentIndex(self.m_currentMidiProgram) else: self.m_currentMidiProgram = -1 self.cb_midi_programs.setEnabled(False) self.cb_midi_programs.blockSignals(False) def do_update(self): # Update current program text if self.cb_programs.count() > 0: pIndex = self.cb_programs.currentIndex() pName = cString(Carla.Host.get_program_name(self.m_pluginId, pIndex)) self.cb_programs.setItemText(pIndex, pName) # Update current midi program text if self.cb_midi_programs.count() > 0: mpIndex = self.cb_midi_programs.currentIndex() mpData = Carla.Host.get_midi_program_data(self.m_pluginId, mpIndex) mpBank = int(mpData['bank']) mpProg = int(mpData['program']) mpLabel = cString(mpData['label']) self.cb_midi_programs.setItemText(mpIndex, "%03i:%03i - %s" % (mpBank, mpProg, mpLabel)) for paramType, paramId, paramWidget in self.m_parameterList: paramWidget.set_parameter_value(Carla.Host.get_current_parameter_value(self.m_pluginId, paramId), False) paramWidget.update() def set_parameter_to_update(self, index): if index not in self.m_parameterIdsToUpdate: self.m_parameterIdsToUpdate.append(index) #def set_parameter_default_value(self, index, value): #self.m_parameterList[index].set_default_value(value) def set_parameter_midi_channel(self, index, midiChannel, blockSignals = False): for paramType, paramId, paramWidget in self.m_parameterList: if paramId == index: if (blockSignals): paramWidget.blockSignals(True) paramWidget.set_parameter_midi_channel(midiChannel) if (blockSignals): paramWidget.blockSignals(False) break def set_parameter_midi_cc(self, index, midiCC, blockSignals = False): for paramType, paramId, paramWidget in self.m_parameterList: if paramId == index: if (blockSignals): paramWidget.blockSignals(True) paramWidget.set_parameter_midi_cc(midiCC) if (blockSignals): paramWidget.blockSignals(False) break def set_program(self, index): self.m_currentProgram = index self.cb_programs.setCurrentIndex(index) QTimer.singleShot(0, self, SLOT("slot_checkInputControlParameters()")) def set_midi_program(self, index): self.m_currentMidiProgram = index self.cb_midi_programs.setCurrentIndex(index) QTimer.singleShot(0, self, SLOT("slot_checkInputControlParameters()")) def saveState(self): content = "" content += "\n" content += "\n" content += "\n" % VERSION content += self.parent().getSaveXMLContent() content += "\n" try: fd = uopen(self.m_currentStateFilename, "w") fd.write(content) fd.close() except: QMessageBox.critical(self, self.tr("Error"), self.tr("Failed to save state file")) def saveStateLV2(self): pass def saveStateAsVstPreset(self): pass def loadState(self): try: fd = uopen(self.m_currentStateFilename, "r") stateRead = fd.read() fd.close() except: QMessageBox.critical(self, self.tr("Error"), self.tr("Failed to load state file")) return xml = QDomDocument() xml.setContent(stateRead.encode("utf-8")) xmlNode = xml.documentElement() if xmlNode.tagName() != "CARLA-PRESET": QMessageBox.critical(self, self.tr("Error"), self.tr("Not a valid Carla state file")) return x_save_state_dict = getSaveStateDictFromXML(xmlNode) self.parent().loadStateDict(x_save_state_dict) def loadStateLV2(self): pass def loadStateFromVstPreset(self): pass def animateTab(self, index): if self.m_tabIconTimers[index-1] == ICON_STATE_NULL: self.tabWidget.setTabIcon(index, self.m_tabIconOn) self.m_tabIconTimers[index-1] = ICON_STATE_ON def setVisible(self, yesNo): if yesNo: if self.m_firstShow: self.m_firstShow = False self.restoreGeometry("") elif self.m_geometry and not self.m_geometry.isNull(): self.restoreGeometry(self.m_geometry) else: self.m_geometry = self.saveGeometry() QDialog.setVisible(self, yesNo) def updateParametersDefaultValues(self): for paramType, paramId, paramWidget in self.m_parameterList: if self.m_pluginInfo["type"] not in (PLUGIN_GIG, PLUGIN_SF2, PLUGIN_SFZ): paramWidget.set_default_value(Carla.Host.get_default_parameter_value(self.m_pluginId, paramId)) def updateParametersInputs(self): for paramType, paramId, paramWidget in self.m_parameterList: if paramType == PARAMETER_INPUT: paramWidget.set_parameter_value(Carla.Host.get_current_parameter_value(self.m_pluginId, paramId), False) def updateParametersOutputs(self): for paramType, paramId, paramWidget in self.m_parameterList: if paramType == PARAMETER_OUTPUT: paramWidget.set_parameter_value(Carla.Host.get_current_parameter_value(self.m_pluginId, paramId), False) def updatePlugin(self): # Check Tab icons for i in range(len(self.m_tabIconTimers)): if self.m_tabIconTimers[i] == ICON_STATE_ON: self.m_tabIconTimers[i] = ICON_STATE_WAIT elif self.m_tabIconTimers[i] == ICON_STATE_WAIT: self.m_tabIconTimers[i] = ICON_STATE_OFF elif self.m_tabIconTimers[i] == ICON_STATE_OFF: self.m_tabIconTimers[i] = ICON_STATE_NULL self.tabWidget.setTabIcon(i+1, self.m_tabIconOff) # Check parameters needing update for index in self.m_parameterIdsToUpdate: value = Carla.Host.get_current_parameter_value(self.m_pluginId, index) for paramType, paramId, paramWidget in self.m_parameterList: if paramId == index: paramWidget.set_parameter_value(value, False) if paramType == PARAMETER_INPUT: self.animateTab(paramWidget.tabIndex()) break # Clear all parameters self.m_parameterIdsToUpdate = [] # Update output parameters self.updateParametersOutputs() @pyqtSlot() def slot_saveState(self): if self.m_pluginInfo['type'] == PLUGIN_LV2: # TODO QMessageBox.warning(self, self.tr("Warning"), self.tr("LV2 Presets is not implemented yet")) return self.saveStateLV2() # TODO - VST preset support if self.m_currentStateFilename: askTry = QMessageBox.question(self, self.tr("Overwrite?"), self.tr("Overwrite previously created file?"), QMessageBox.Ok|QMessageBox.Cancel) if askTry == QMessageBox.Ok: return self.saveState() self.m_currentStateFilename = None self.slot_saveState() else: fileFilter = self.tr("Carla State File (*.carxs)") filenameTry = QFileDialog.getSaveFileName(self, self.tr("Save Plugin State File"), filter=fileFilter) if filenameTry: if not filenameTry.endswith(".carxs"): filenameTry += ".carxs" self.m_currentStateFilename = filenameTry self.saveState() @pyqtSlot() def slot_loadState(self): if self.m_pluginInfo['type'] == PLUGIN_LV2: # TODO QMessageBox.warning(self, self.tr("Warning"), self.tr("LV2 Presets is not implemented yet")) return self.loadStateLV2() # TODO - VST preset support fileFilter = self.tr("Carla State File (*.carxs)") filenameTry = QFileDialog.getOpenFileName(self, self.tr("Open Plugin State File"), filter=fileFilter) if filenameTry: self.m_currentStateFilename = filenameTry self.loadState() @pyqtSlot(int, float) def slot_parameterValueChanged(self, parameter_id, value): Carla.Host.set_parameter_value(self.m_pluginId, parameter_id, value) @pyqtSlot(int, int) def slot_parameterMidiChannelChanged(self, parameter_id, channel): Carla.Host.set_parameter_midi_channel(self.m_pluginId, parameter_id, channel-1) @pyqtSlot(int, int) def slot_parameterMidiCcChanged(self, parameter_id, cc_index): Carla.Host.set_parameter_midi_cc(self.m_pluginId, parameter_id, cc_index) @pyqtSlot(int) def slot_programIndexChanged(self, index): if self.m_currentProgram != index: Carla.Host.set_program(self.m_pluginId, index) self.updateParametersDefaultValues() self.updateParametersInputs() self.m_currentProgram = index @pyqtSlot(int) def slot_midiProgramIndexChanged(self, index): if self.m_currentMidiProgram != index: Carla.Host.set_midi_program(self.m_pluginId, index) self.updateParametersDefaultValues() self.updateParametersInputs() self.m_currentMidiProgram = index @pyqtSlot(int) def slot_noteOn(self, note): Carla.Host.send_midi_note(self.m_pluginId, 0, note, 100) @pyqtSlot(int) def slot_noteOff(self, note): Carla.Host.send_midi_note(self.m_pluginId, 0, note, 0) @pyqtSlot() def slot_notesOn(self): self.parent().led_midi.setChecked(True) @pyqtSlot() def slot_notesOff(self): self.parent().led_midi.setChecked(False) @pyqtSlot() def slot_checkInputControlParameters(self): self.updateParametersInputs() @pyqtSlot() def slot_finished(self): self.parent().b_edit.setChecked(False) def done(self, r): QDialog.done(self, r) self.close() # (New) Plugin Widget class PluginWidget(QFrame, ui_carla_plugin.Ui_PluginWidget): def __init__(self, parent, pluginId): QFrame.__init__(self, parent) self.setupUi(self) self.m_pluginId = pluginId self.m_pluginInfo = Carla.Host.get_plugin_info(pluginId) self.m_pluginInfo["binary"] = cString(self.m_pluginInfo["binary"]) self.m_pluginInfo["name"] = cString(self.m_pluginInfo["name"]) self.m_pluginInfo["label"] = cString(self.m_pluginInfo["label"]) self.m_pluginInfo["maker"] = cString(self.m_pluginInfo["maker"]) self.m_pluginInfo["copyright"] = cString(self.m_pluginInfo["copyright"]) self.m_parameterIconTimer = ICON_STATE_NULL self.m_lastGreenLedState = False self.m_lastBlueLedState = False if Carla.processMode == PROCESS_MODE_CONTINUOUS_RACK: self.m_peaksInputCount = 2 self.m_peaksOutputCount = 2 self.stackedWidget.setCurrentIndex(0) else: audioCountInfo = Carla.Host.get_audio_port_count_info(self.m_pluginId) self.m_peaksInputCount = int(audioCountInfo['ins']) self.m_peaksOutputCount = int(audioCountInfo['outs']) if self.m_peaksInputCount > 2: self.m_peaksInputCount = 2 if self.m_peaksOutputCount > 2: self.m_peaksOutputCount = 2 if audioCountInfo['total'] == 0: self.stackedWidget.setCurrentIndex(1) else: self.stackedWidget.setCurrentIndex(0) midiCountInfo = Carla.Host.get_midi_port_count_info(self.m_pluginId) if (self.m_pluginInfo['hints'] & PLUGIN_IS_SYNTH) > 0 or (midiCountInfo['ins'] > 0 < midiCountInfo['outs']): self.led_audio_in.setVisible(False) else: self.led_midi.setVisible(False) # Colorify if self.m_pluginInfo['category'] == PLUGIN_CATEGORY_SYNTH: self.setWidgetColor(PALETTE_COLOR_WHITE) elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_DELAY: self.setWidgetColor(PALETTE_COLOR_ORANGE) elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_EQ: self.setWidgetColor(PALETTE_COLOR_GREEN) elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_FILTER: self.setWidgetColor(PALETTE_COLOR_BLUE) elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_DYNAMICS: self.setWidgetColor(PALETTE_COLOR_PINK) elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_MODULATOR: self.setWidgetColor(PALETTE_COLOR_RED) elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_UTILITY: self.setWidgetColor(PALETTE_COLOR_YELLOW) elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_OTHER: self.setWidgetColor(PALETTE_COLOR_BROWN) else: self.setWidgetColor(PALETTE_COLOR_NONE) # Fake effect self.m_color1 = QColor(0, 0, 0, 220) self.m_color2 = QColor(0, 0, 0, 170) self.m_color3 = QColor(7, 7, 7, 250) self.m_color4 = QColor(14, 14, 14, 255) self.led_enable.setColor(self.led_enable.BIG_RED) self.led_enable.setChecked(False) self.led_control.setColor(self.led_control.YELLOW) self.led_control.setEnabled(False) self.led_midi.setColor(self.led_midi.RED) self.led_midi.setEnabled(False) self.led_audio_in.setColor(self.led_audio_in.GREEN) self.led_audio_in.setEnabled(False) self.led_audio_out.setColor(self.led_audio_out.BLUE) self.led_audio_out.setEnabled(False) self.dial_drywet.setPixmap(3) self.dial_vol.setPixmap(3) self.dial_b_left.setPixmap(4) self.dial_b_right.setPixmap(4) self.dial_drywet.setCustomPaint(self.dial_drywet.CUSTOM_PAINT_CARLA_WET) self.dial_vol.setCustomPaint(self.dial_vol.CUSTOM_PAINT_CARLA_VOL) self.dial_b_left.setCustomPaint(self.dial_b_left.CUSTOM_PAINT_CARLA_L) self.dial_b_right.setCustomPaint(self.dial_b_right.CUSTOM_PAINT_CARLA_R) self.peak_in.setColor(self.peak_in.GREEN) self.peak_in.setOrientation(self.peak_in.HORIZONTAL) self.peak_out.setColor(self.peak_in.BLUE) self.peak_out.setOrientation(self.peak_out.HORIZONTAL) self.peak_in.setChannels(self.m_peaksInputCount) self.peak_out.setChannels(self.m_peaksOutputCount) self.label_name.setText(self.m_pluginInfo['name']) self.edit_dialog = PluginEdit(self, self.m_pluginId) self.edit_dialog.hide() self.gui_dialog = None if self.m_pluginInfo['hints'] & PLUGIN_HAS_GUI: guiInfo = Carla.Host.get_gui_info(self.m_pluginId) guiType = guiInfo['type'] if guiType in (GUI_INTERNAL_QT4, GUI_INTERNAL_COCOA, GUI_INTERNAL_HWND, GUI_INTERNAL_X11): self.gui_dialog = PluginGUI(self, self.m_pluginInfo['name'], guiInfo['resizable']) self.gui_dialog.hide() Carla.Host.set_gui_container(self.m_pluginId, unwrapinstance(self.gui_dialog.getContainer())) elif guiType in (GUI_EXTERNAL_LV2, GUI_EXTERNAL_SUIL, GUI_EXTERNAL_OSC): pass else: self.b_gui.setEnabled(False) self.connect(self.led_enable, SIGNAL("clicked(bool)"), SLOT("slot_setActive(bool)")) self.connect(self.dial_drywet, SIGNAL("sliderMoved(int)"), SLOT("slot_setDryWet(int)")) self.connect(self.dial_vol, SIGNAL("sliderMoved(int)"), SLOT("slot_setVolume(int)")) self.connect(self.dial_b_left, SIGNAL("sliderMoved(int)"), SLOT("slot_setBalanceLeft(int)")) self.connect(self.dial_b_right, SIGNAL("sliderMoved(int)"), SLOT("slot_setBalanceRight(int)")) self.connect(self.b_gui, SIGNAL("clicked(bool)"), SLOT("slot_guiClicked(bool)")) self.connect(self.b_edit, SIGNAL("clicked(bool)"), SLOT("slot_editClicked(bool)")) self.connect(self.b_remove, SIGNAL("clicked()"), SLOT("slot_removeClicked()")) self.connect(self.dial_drywet, SIGNAL("customContextMenuRequested(QPoint)"), SLOT("slot_showCustomDialMenu()")) self.connect(self.dial_vol, SIGNAL("customContextMenuRequested(QPoint)"), SLOT("slot_showCustomDialMenu()")) self.connect(self.dial_b_left, SIGNAL("customContextMenuRequested(QPoint)"), SLOT("slot_showCustomDialMenu()")) self.connect(self.dial_b_right, SIGNAL("customContextMenuRequested(QPoint)"), SLOT("slot_showCustomDialMenu()")) self.check_gui_stuff() def set_active(self, active, sendGui=False, sendCallback=True): if sendGui: self.led_enable.setChecked(active) if sendCallback: Carla.Host.set_active(self.m_pluginId, active) if active: self.edit_dialog.keyboard.allNotesOff() def set_drywet(self, value, sendGui=False, sendCallback=True): if sendGui: self.dial_drywet.setValue(value) if sendCallback: Carla.Host.set_drywet(self.m_pluginId, float(value)/1000) message = self.tr("Output dry/wet (%i%%)" % int(value / 10)) self.dial_drywet.setStatusTip(message) Carla.gui.statusBar().showMessage(message) def set_volume(self, value, sendGui=False, sendCallback=True): if sendGui: self.dial_vol.setValue(value) if sendCallback: Carla.Host.set_volume(self.m_pluginId, float(value)/1000) message = self.tr("Output volume (%i%%)" % int(value / 10)) self.dial_vol.setStatusTip(message) Carla.gui.statusBar().showMessage(message) def set_balance_left(self, value, sendGui=False, sendCallback=True): if sendGui: self.dial_b_left.setValue(value) if sendCallback: Carla.Host.set_balance_left(self.m_pluginId, float(value)/1000) if value < 0: message = self.tr("Left Panning (%i%% Left)" % int(-value / 10)) elif value > 0: message = self.tr("Left Panning (%i%% Right)" % int(value / 10)) else: message = self.tr("Left Panning (Center)") self.dial_b_left.setStatusTip(message) Carla.gui.statusBar().showMessage(message) def set_balance_right(self, value, sendGui=False, sendCallback=True): if sendGui: self.dial_b_right.setValue(value) if sendCallback: Carla.Host.set_balance_right(self.m_pluginId, float(value)/1000) if value < 0: message = self.tr("Right Panning (%i%% Left)" % int(-value / 10)) elif value > 0: message = self.tr("Right Panning (%i%% Right)" % int(value / 10)) else: message = self.tr("Right Panning (Center)") self.dial_b_right.setStatusTip(message) Carla.gui.statusBar().showMessage(message) def setId(self, idx): self.m_pluginId = idx self.edit_dialog.m_pluginId = idx def setWidgetColor(self, color): if color == PALETTE_COLOR_WHITE: r = 110 g = 110 b = 110 borderR = 72 borderG = 72 borderB = 72 elif color == PALETTE_COLOR_RED: r = 110 g = 15 b = 15 borderR = 110 borderG = 45 borderB = 45 elif color == PALETTE_COLOR_GREEN: r = 15 g = 110 b = 15 borderR = 72 borderG = 110 borderB = 72 elif color == PALETTE_COLOR_BLUE: r = 15 g = 15 b = 110 borderR = 45 borderG = 45 borderB = 110 elif color == PALETTE_COLOR_YELLOW: r = 110 g = 110 b = 15 borderR = 110 borderG = 110 borderB = 110 elif color == PALETTE_COLOR_ORANGE: r = 180 g = 110 b = 15 borderR = 155 borderG = 110 borderB = 60 elif color == PALETTE_COLOR_BROWN: r = 110 g = 35 b = 15 borderR = 110 borderG = 60 borderB = 45 elif color == PALETTE_COLOR_PINK: r = 110 g = 15 b = 110 borderR = 110 borderG = 45 borderB = 110 else: r = 35 g = 35 b = 35 borderR = 60 borderG = 60 borderB = 60 self.setStyleSheet(""" QFrame#PluginWidget { background-image: url(:/bitmaps/textures/metal_9-512px.jpg); background-repeat: repeat-x; background-position: top left; } QLabel#label_name { color: white; } QWidget#widget_buttons { background-color: rgb(%i, %i, %i); border-left: 1px solid rgb(%i, %i, %i); border-right: 1px solid rgb(%i, %i, %i); border-bottom: 1px solid rgb(%i, %i, %i); border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; } QPushButton#b_gui:hover, QPushButton#b_edit:hover, QPushButton#b_remove:hover { background-color: rgb(%i, %i, %i); border: 1px solid rgb(%i, %i, %i); border-radius: 3px; } QFrame#frame_name { background-color: rgb(%i, %i, %i); border: 1px solid rgb(%i, %i, %i); border-radius: 4px; } QFrame#frame_controls { background-image: url(:/bitmaps/carla_knobs1.png); background-color: rgb(35, 35, 35); border: 1px solid rgb(35, 35, 35); border-radius: 4px; } QFrame#frame_peaks { background-color: rgb(35, 35, 35); border: 1px solid rgb(35, 35, 35); border-radius: 4px; } """ % ( # QWidget#widget_buttons borderR, borderG, borderB, borderR, borderG, borderB, borderR, borderG, borderB, borderR, borderG, borderB, # QPushButton#b_* r, g, b, borderR, borderG, borderB, # QFrame#frame_name r, g, b, borderR, borderG, borderB )) def recheck_hints(self, hints): self.m_pluginInfo['hints'] = hints self.dial_drywet.setEnabled(hints & PLUGIN_CAN_DRYWET) self.dial_vol.setEnabled(hints & PLUGIN_CAN_VOLUME) self.dial_b_left.setEnabled(hints & PLUGIN_CAN_BALANCE) self.dial_b_right.setEnabled(hints & PLUGIN_CAN_BALANCE) self.b_gui.setEnabled(hints & PLUGIN_HAS_GUI) def getSaveXMLContent(self): Carla.Host.prepare_for_save(self.m_pluginId) if self.m_pluginInfo['type'] == PLUGIN_INTERNAL: typeStr = "Internal" elif self.m_pluginInfo['type'] == PLUGIN_LADSPA: typeStr = "LADSPA" elif self.m_pluginInfo['type'] == PLUGIN_DSSI: typeStr = "DSSI" elif self.m_pluginInfo['type'] == PLUGIN_LV2: typeStr = "LV2" elif self.m_pluginInfo['type'] == PLUGIN_VST: typeStr = "VST" elif self.m_pluginInfo['type'] == PLUGIN_GIG: typeStr = "GIG" elif self.m_pluginInfo['type'] == PLUGIN_SF2: typeStr = "SF2" elif self.m_pluginInfo['type'] == PLUGIN_SFZ: typeStr = "SFZ" else: typeStr = "Unknown" x_save_state_dict = deepcopy(save_state_dict) # ---------------------------- # Basic info x_save_state_dict['Type'] = typeStr x_save_state_dict['Name'] = self.m_pluginInfo['name'] x_save_state_dict['Label'] = self.m_pluginInfo['label'] x_save_state_dict['Binary'] = self.m_pluginInfo['binary'] x_save_state_dict['UniqueID'] = self.m_pluginInfo['uniqueId'] # ---------------------------- # Internals x_save_state_dict['Active'] = self.led_enable.isChecked() x_save_state_dict['DryWet'] = float(self.dial_drywet.value()) / 1000 x_save_state_dict['Volume'] = float(self.dial_vol.value()) / 1000 x_save_state_dict['Balance-Left'] = float(self.dial_b_left.value()) / 1000 x_save_state_dict['Balance-Right'] = float(self.dial_b_right.value()) / 1000 # ---------------------------- # Current Program if self.edit_dialog.cb_programs.currentIndex() >= 0: x_save_state_dict['CurrentProgramIndex'] = self.edit_dialog.cb_programs.currentIndex() x_save_state_dict['CurrentProgramName'] = self.edit_dialog.cb_programs.currentText() # ---------------------------- # Current MIDI Program if self.edit_dialog.cb_midi_programs.currentIndex() >= 0: midiProgramData = Carla.Host.get_midi_program_data(self.m_pluginId, self.edit_dialog.cb_midi_programs.currentIndex()) x_save_state_dict['CurrentMidiBank'] = midiProgramData['bank'] x_save_state_dict['CurrentMidiProgram'] = midiProgramData['program'] # ---------------------------- # Parameters sampleRate = Carla.Host.get_sample_rate() parameterCount = Carla.Host.get_parameter_count(self.m_pluginId) for i in range(parameterCount): parameterInfo = Carla.Host.get_parameter_info(self.m_pluginId, i) parameterData = Carla.Host.get_parameter_data(self.m_pluginId, i) if parameterData['type'] != PARAMETER_INPUT: continue x_save_state_parameter = deepcopy(save_state_parameter) x_save_state_parameter['index'] = parameterData['index'] x_save_state_parameter['name'] = cString(parameterInfo['name']) x_save_state_parameter['symbol'] = cString(parameterInfo['symbol']) x_save_state_parameter['value'] = Carla.Host.get_current_parameter_value(self.m_pluginId, parameterData['index']) x_save_state_parameter['midiCC'] = parameterData['midiCC'] x_save_state_parameter['midiChannel'] = parameterData['midiChannel'] + 1 if parameterData['hints'] & PARAMETER_USES_SAMPLERATE: x_save_state_parameter['value'] /= sampleRate x_save_state_dict['Parameters'].append(x_save_state_parameter) # ---------------------------- # Custom Data customDataCount = Carla.Host.get_custom_data_count(self.m_pluginId) for i in range(customDataCount): customData = Carla.Host.get_custom_data(self.m_pluginId, i) if customData['type'] == CUSTOM_DATA_INVALID: continue x_save_state_custom_data = deepcopy(save_state_custom_data) x_save_state_custom_data['type'] = getCustomDataTypeString(customData['type']) x_save_state_custom_data['key'] = cString(customData['key']) x_save_state_custom_data['value'] = cString(customData['value']) x_save_state_dict['CustomData'].append(x_save_state_custom_data) # ---------------------------- # Chunk if self.m_pluginInfo['hints'] & PLUGIN_USES_CHUNKS: x_save_state_dict['Chunk'] = cString(Carla.Host.get_chunk_data(self.m_pluginId)) # ---------------------------- # Generate XML for this plugin content = "" content += " \n" content += " %s\n" % x_save_state_dict['Type'] content += " %s\n" % xmlSafeString(x_save_state_dict['Name'], True) if self.m_pluginInfo['type'] == PLUGIN_LV2: content += " %s\n" % xmlSafeString(x_save_state_dict['Label'], True) else: if x_save_state_dict['Label'] and self.m_pluginInfo['type'] not in (PLUGIN_GIG, PLUGIN_SF2, PLUGIN_SFZ): content += " \n" % xmlSafeString(x_save_state_dict['Label'], True) content += " %s\n" % xmlSafeString(x_save_state_dict['Binary'], True) if self.m_pluginInfo['type'] in (PLUGIN_LADSPA, PLUGIN_VST): content += " %li\n" % x_save_state_dict['UniqueID'] content += " \n" content += "\n" content += " \n" content += " %s\n" % "Yes" if x_save_state_dict['Active'] else "No" content += " %f\n" % x_save_state_dict['DryWet'] content += " %f\n" % x_save_state_dict['Volume'] content += " %f\n" % x_save_state_dict['Balance-Left'] content += " %f\n" % x_save_state_dict['Balance-Right'] for parameter in x_save_state_dict['Parameters']: content += "\n" content += " \n" content += " %i\n" % parameter['index'] content += " %s\n" % xmlSafeString(parameter['name'], True) if parameter['symbol']: content += " %s\n" % xmlSafeString(parameter['symbol'], True) strValue = "{0:f}".format(Decimal("%g" % parameter['value'])) content += " %s\n" % strValue if parameter['midiCC'] > 0: content += " %i\n" % parameter['midiCC'] content += " %i\n" % parameter['midiChannel'] content += " \n" if x_save_state_dict['CurrentProgramIndex'] >= 0: content += "\n" content += " %i\n" % x_save_state_dict['CurrentProgramIndex'] content += " %s\n" % xmlSafeString(x_save_state_dict['CurrentProgramName'], True) if x_save_state_dict['CurrentMidiBank'] >= 0 and x_save_state_dict['CurrentMidiProgram'] >= 0: content += "\n" content += " %i\n" % x_save_state_dict['CurrentMidiBank'] content += " %i\n" % x_save_state_dict['CurrentMidiProgram'] for customData in x_save_state_dict['CustomData']: content += "\n" content += " \n" content += " %s\n" % customData['type'] content += " %s\n" % xmlSafeString(customData['key'], True) if customData['type'] in ("string", "chunk", "binary"): content += " \n" content += "%s\n" % xmlSafeString(customData['value'], True) content += " \n" else: content += " %s\n" % xmlSafeString(customData['value'], True) content += " \n" if x_save_state_dict['Chunk']: content += "\n" content += " \n" content += "%s\n" % x_save_state_dict['Chunk'] content += " \n" content += " \n" return content def loadStateDict(self, content): # --------------------------------------------------------------------- # Part 1 - set custom data (except binary/chunks) for customData in content['CustomData']: if customData['type'] not in (CUSTOM_DATA_BINARY, CUSTOM_DATA_CHUNK): Carla.Host.set_custom_data(self.m_pluginId, customData['type'], customData['key'], customData['value']) # --------------------------------------------------------------------- # Part 2 - set program programId = -1 if content['CurrentProgramName']: programCount = Carla.Host.get_program_count(self.m_pluginId) testProgramName = cString(Carla.Host.get_program_name(self.m_pluginId, content['CurrentProgramIndex'])) # Program index and name matches if content['CurrentProgramName'] == testProgramName: programId = content['CurrentProgramIndex'] # index < count elif content['CurrentProgramIndex'] < programCount: programId = content['CurrentProgramIndex'] # index not valid, try to find by name else: for i in range(programCount): testProgramName = cString(Carla.Host.get_program_name(self.m_pluginId, i)) if content['CurrentProgramName'] == testProgramName: programId = i break # set program now, if valid if programId >= 0: Carla.Host.set_program(self.m_pluginId, programId) self.edit_dialog.set_program(programId) # --------------------------------------------------------------------- # Part 3 - set midi program if content['CurrentMidiBank'] >= 0 and content['CurrentMidiProgram'] >= 0: midiProgramCount = Carla.Host.get_midi_program_count(self.m_pluginId) for i in range(midiProgramCount): midiProgramData = Carla.Host.get_midi_program_data(self.m_pluginId, i) if midiProgramData['bank'] == content['CurrentMidiBank'] and midiProgramData['program'] == content['CurrentMidiProgram']: Carla.Host.set_midi_program(self.m_pluginId, i) self.edit_dialog.set_midi_program(i) break # --------------------------------------------------------------------- # Part 4a - get plugin parameter symbols paramSymbols = [] # (index, symbol) for parameter in content['Parameters']: if parameter['symbol']: paramInfo = Carla.Host.get_parameter_info(self.m_pluginId, parameter['index']) if paramInfo['symbol']: paramSymbols.append((parameter['index'], cString(paramInfo['symbol']))) # --------------------------------------------------------------------- # Part 4b - set parameter values (carefully) sampleRate = Carla.Host.get_sample_rate() for parameter in content['Parameters']: index = -1 if content['Type'] == "LADSPA": # Try to set by symbol, otherwise use index if parameter['symbol']: for paramIndex, paramSymbol in paramSymbols: if parameter['symbol'] == paramSymbol: index = paramIndex break else: index = parameter['index'] else: index = parameter['index'] elif content['Type'] == "LV2": # Symbol only if parameter['symbol']: for paramIndex, paramSymbol in paramSymbols: if parameter['symbol'] == paramSymbol: index = paramIndex break else: print("Failed to find LV2 parameter symbol for '%s')" % parameter['symbol']) else: print("LV2 Plugin parameter '%s' has no symbol" % parameter['name']) else: # Index only index = parameter['index'] # Now set parameter if index >= 0: paramData = Carla.Host.get_parameter_data(self.m_pluginId, parameter['index']) if paramData['hints'] & PARAMETER_USES_SAMPLERATE: parameter['value'] *= sampleRate Carla.Host.set_parameter_value(self.m_pluginId, index, parameter['value']) Carla.Host.set_parameter_midi_cc(self.m_pluginId, index, parameter['midiCC']) Carla.Host.set_parameter_midi_channel(self.m_pluginId, index, parameter['midiChannel']-1) else: print("Could not set parameter data for '%s')" % parameter['name']) # --------------------------------------------------------------------- # Part 5 - set chunk data for customData in content['CustomData']: if customData['type'] in (CUSTOM_DATA_BINARY, CUSTOM_DATA_CHUNK): Carla.Host.set_custom_data(self.m_pluginId, customData['type'], customData['key'], customData['value']) if content['Chunk']: Carla.Host.set_chunk_data(self.m_pluginId, content['Chunk']) # --------------------------------------------------------------------- # Part 6 - set internal stuff self.set_drywet(content['DryWet'] * 1000, True, True) self.set_volume(content['Volume'] * 1000, True, True) self.set_balance_left(content['Balance-Left'] * 1000, True, True) self.set_balance_right(content['Balance-Right'] * 1000, True, True) self.edit_dialog.do_reload_all() self.set_active(content['Active'], True, True) def check_gui_stuff(self): # Input peaks if self.m_peaksInputCount > 0: if self.m_peaksInputCount > 1: peak1 = Carla.Host.get_input_peak_value(self.m_pluginId, 1) peak2 = Carla.Host.get_input_peak_value(self.m_pluginId, 2) ledState = bool(peak1 != 0.0 or peak2 != 0.0) self.peak_in.displayMeter(1, peak1) self.peak_in.displayMeter(2, peak2) else: peak = Carla.Host.get_input_peak_value(self.m_pluginId, 1) ledState = bool(peak != 0.0) self.peak_in.displayMeter(1, peak) if self.m_lastGreenLedState != ledState: self.led_audio_in.setChecked(ledState) self.m_lastGreenLedState = ledState # Output peaks if self.m_peaksOutputCount > 0: if self.m_peaksOutputCount > 1: peak1 = Carla.Host.get_output_peak_value(self.m_pluginId, 1) peak2 = Carla.Host.get_output_peak_value(self.m_pluginId, 2) ledState = bool(peak1 != 0.0 or peak2 != 0.0) self.peak_out.displayMeter(1, peak1) self.peak_out.displayMeter(2, peak2) else: peak = Carla.Host.get_output_peak_value(self.m_pluginId, 1) ledState = bool(peak != 0.0) self.peak_out.displayMeter(1, peak) if self.m_lastBlueLedState != ledState: self.led_audio_out.setChecked(ledState) self.m_lastBlueLedState = ledState def check_gui_stuff2(self): # Parameter Activity LED if self.m_parameterIconTimer == ICON_STATE_ON: self.m_parameterIconTimer = ICON_STATE_WAIT self.led_control.setChecked(True) elif self.m_parameterIconTimer == ICON_STATE_WAIT: self.m_parameterIconTimer = ICON_STATE_OFF elif self.m_parameterIconTimer == ICON_STATE_OFF: self.m_parameterIconTimer = ICON_STATE_NULL self.led_control.setChecked(False) # Update edit dialog self.edit_dialog.updatePlugin() @pyqtSlot(bool) def slot_setActive(self, yesno): self.set_active(yesno, False, True) @pyqtSlot(int) def slot_setDryWet(self, value): self.set_drywet(value, False, True) @pyqtSlot(int) def slot_setVolume(self, value): self.set_volume(value, False, True) @pyqtSlot(int) def slot_setBalanceLeft(self, value): self.set_balance_left(value, False, True) @pyqtSlot(int) def slot_setBalanceRight(self, value): self.set_balance_right(value, False, True) @pyqtSlot(bool) def slot_guiClicked(self, show): if self.gui_dialog: self.gui_dialog.setVisible(show) Carla.Host.show_gui(self.m_pluginId, show) @pyqtSlot(bool) def slot_editClicked(self, show): self.edit_dialog.setVisible(show) @pyqtSlot() def slot_removeClicked(self): Carla.gui.remove_plugin(self.m_pluginId, True) @pyqtSlot() def slot_showCustomDialMenu(self): dialName = self.sender().objectName() if dialName == "dial_drywet": minimum = 0 maximum = 100 default = 100 label = "Dry/Wet" elif dialName == "dial_vol": minimum = 0 maximum = 127 default = 100 label = "Volume" elif dialName == "dial_b_left": minimum = -100 maximum = 100 default = -100 label = "Balance-Left" elif dialName == "dial_b_right": minimum = -100 maximum = 100 default = 100 label = "Balance-Right" else: minimum = 0 maximum = 100 default = 100 label = "Unknown" current = self.sender().value() / 10 menu = QMenu(self) actReset = menu.addAction(self.tr("Reset (%i%%)" % default)) menu.addSeparator() actMinimum = menu.addAction(self.tr("Set to Minimum (%i%%)" % minimum)) actCenter = menu.addAction(self.tr("Set to Center")) actMaximum = menu.addAction(self.tr("Set to Maximum (%i%%)" % maximum)) menu.addSeparator() actSet = menu.addAction(self.tr("Set value...")) if label not in ("Balance-Left", "Balance-Right"): menu.removeAction(actCenter) actSelected = menu.exec_(QCursor.pos()) if actSelected == actSet: valueTry = QInputDialog.getInteger(self, self.tr("Set value"), label, current, minimum, maximum, 1) if valueTry[1]: value = valueTry[0] * 10 else: value = None elif actSelected == actMinimum: value = minimum * 10 elif actSelected == actMaximum: value = maximum * 10 elif actSelected == actReset: value = default * 10 elif actSelected == actCenter: value = 0 else: return if label == "Dry/Wet": self.set_drywet(value, True, True) elif label == "Volume": self.set_volume(value, True, True) elif label == "Balance-Left": self.set_balance_left(value, True, True) elif label == "Balance-Right": self.set_balance_right(value, True, True) def paintEvent(self, event): painter = QPainter(self) painter.setPen(self.m_color1) painter.drawLine(0, 3, self.width(), 3) painter.drawLine(0, self.height() - 4, self.width(), self.height() - 4) painter.setPen(self.m_color2) painter.drawLine(0, 2, self.width(), 2) painter.drawLine(0, self.height() - 3, self.width(), self.height() - 3) painter.setPen(self.m_color3) painter.drawLine(0, 1, self.width(), 1) painter.drawLine(0, self.height() - 2, self.width(), self.height() - 2) painter.setPen(self.m_color4) painter.drawLine(0, 0, self.width(), 0) painter.drawLine(0, self.height() - 1, self.width(), self.height() - 1) QFrame.paintEvent(self, event) # Plugin GUI class PluginGUI(QDialog): def __init__(self, parent, pluginName, resizable): QDialog.__init__(self, parent) self.m_firstShow = True self.m_resizable = resizable self.m_geometry = None self.vbLayout = QVBoxLayout(self) self.vbLayout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.vbLayout) self.container = GuiContainer(self) self.vbLayout.addWidget(self.container) self.setNewSize(50, 50) self.setWindowTitle("%s (GUI)" % pluginName) if WINDOWS and not resizable: self.setWindowFlags(self.windowFlags() | Qt.MSWindowsFixedSizeDialogHint) def getContainer(self): return self.container def setNewSize(self, width, height): if width < 30: width = 30 if height < 30: height = 30 if self.m_resizable: self.resize(width, height) else: self.setFixedSize(width, height) self.container.setFixedSize(width, height) def setVisible(self, yesNo): if yesNo: if self.m_firstShow: self.m_firstShow = False self.restoreGeometry("") elif self.m_geometry and not self.m_geometry.isNull(): self.restoreGeometry(self.m_geometry) else: self.m_geometry = self.saveGeometry() QDialog.setVisible(self, yesNo) def hideEvent(self, event): event.accept() self.close() def closeEvent(self, event): if event.spontaneous(): self.parent().b_gui.setChecked(False) QDialog.closeEvent(self, event) def done(self, r): QDialog.done(self, r) self.close()