diff --git a/source/frontend/carla_backend_qt.py b/source/frontend/carla_backend_qt.py index 44cfd79fe..008521cea 100644 --- a/source/frontend/carla_backend_qt.py +++ b/source/frontend/carla_backend_qt.py @@ -39,6 +39,7 @@ class CarlaHostSignals(QObject): ParameterValueChangedCallback = pyqtSignal(int, int, float) ParameterDefaultChangedCallback = pyqtSignal(int, int, float) ParameterMappedControlIndexChangedCallback = pyqtSignal(int, int, int) + ParameterMappedRangeChangedCallback = pyqtSignal(int, int, float, float) ParameterMidiChannelChangedCallback = pyqtSignal(int, int, int) ProgramChangedCallback = pyqtSignal(int, int) MidiProgramChangedCallback = pyqtSignal(int, int) diff --git a/source/frontend/carla_host.py b/source/frontend/carla_host.py index d4c27da56..34be2173e 100644 --- a/source/frontend/carla_host.py +++ b/source/frontend/carla_host.py @@ -2751,6 +2751,9 @@ def engineCallback(host, action, pluginId, value1, value2, value3, valuef, value host.ParameterDefaultChangedCallback.emit(pluginId, value1, valuef) elif action == ENGINE_CALLBACK_PARAMETER_MAPPED_CONTROL_INDEX_CHANGED: host.ParameterMappedControlIndexChangedCallback.emit(pluginId, value1, value2) + elif action == ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED: + minimum, maximum = (float(v) for v in valueStr.split(":", 2)) + host.ParameterMappedRangeChangedCallback.emit(pluginId, value1, minimum, maximum) elif action == ENGINE_CALLBACK_PARAMETER_MIDI_CHANNEL_CHANGED: host.ParameterMidiChannelChangedCallback.emit(pluginId, value1, value2) elif action == ENGINE_CALLBACK_PROGRAM_CHANGED: diff --git a/source/frontend/carla_shared.py b/source/frontend/carla_shared.py index 0b58c3d44..98a3a9519 100644 --- a/source/frontend/carla_shared.py +++ b/source/frontend/carla_shared.py @@ -119,58 +119,44 @@ del envPATH # Static MIDI CC list MIDI_CC_LIST = ( - "0x01 Modulation", - "0x02 Breath", - "0x03 (Undefined)", - "0x04 Foot", - "0x05 Portamento", - "0x07 Volume", - "0x08 Balance", - "0x09 (Undefined)", - "0x0A Pan", - "0x0B Expression", - "0x0C FX Control 1", - "0x0D FX Control 2", - "0x0E (Undefined)", - "0x0F (Undefined)", - "0x10 General Purpose 1", - "0x11 General Purpose 2", - "0x12 General Purpose 3", - "0x13 General Purpose 4", - "0x14 (Undefined)", - "0x15 (Undefined)", - "0x16 (Undefined)", - "0x17 (Undefined)", - "0x18 (Undefined)", - "0x19 (Undefined)", - "0x1A (Undefined)", - "0x1B (Undefined)", - "0x1C (Undefined)", - "0x1D (Undefined)", - "0x1E (Undefined)", - "0x1F (Undefined)", - "0x46 Control 1 [Variation]", - "0x47 Control 2 [Timbre]", - "0x48 Control 3 [Release]", - "0x49 Control 4 [Attack]", - "0x4A Control 5 [Brightness]", - "0x4B Control 6 [Decay]", - "0x4C Control 7 [Vib Rate]", - "0x4D Control 8 [Vib Depth]", - "0x4E Control 9 [Vib Delay]", - "0x4F Control 10 [Undefined]", - "0x50 General Purpose 5", - "0x51 General Purpose 6", - "0x52 General Purpose 7", - "0x53 General Purpose 8", - "0x54 Portamento Control", - "0x5B FX 1 Depth [Reverb]", - "0x5C FX 2 Depth [Tremolo]", - "0x5D FX 3 Depth [Chorus]", - "0x5E FX 4 Depth [Detune]", - "0x5F FX 5 Depth [Phaser]" + "01 [0x01] Modulation", + "02 [0x02] Breath", + "04 [0x04] Foot", + "05 [0x05] Portamento", + "07 [0x07] Volume", + "02 [0x08] Balance", + "10 [0x0A] Pan", + "11 [0x0B] Expression", + "12 [0x0C] FX Control 1", + "13 [0x0D] FX Control 2", + "16 [0x10] General Purpose 1", + "17 [0x11] General Purpose 2", + "18 [0x12] General Purpose 3", + "19 [0x13] General Purpose 4", + "70 [0x46] Control 1 [Variation]", + "71 [0x47] Control 2 [Timbre]", + "72 [0x48] Control 3 [Release]", + "73 [0x49] Control 4 [Attack]", + "74 [0x4A] Control 5 [Brightness]", + "75 [0x4B] Control 6 [Decay]", + "76 [0x4C] Control 7 [Vib Rate]", + "77 [0x4D] Control 8 [Vib Depth]", + "78 [0x4E] Control 9 [Vib Delay]", + "79 [0x4F] Control 10 [Undefined]", + "80 [0x50] General Purpose 5", + "81 [0x51] General Purpose 6", + "82 [0x52] General Purpose 7", + "83 [0x53] General Purpose 8", + "84 [0x54] Portamento Control", + "91 [0x5B] FX 1 Depth [Reverb]", + "92 [0x5C] FX 2 Depth [Tremolo]", + "93 [0x5D] FX 3 Depth [Chorus]", + "94 [0x5E] FX 4 Depth [Detune]", + "95 [0x5F] FX 5 Depth [Phaser]" ) +MAX_MIDI_CC_LIST_ITEM = 95 + # ------------------------------------------------------------------------------------------------------------ # PatchCanvas defines diff --git a/source/frontend/carla_skin.py b/source/frontend/carla_skin.py index 902069dd8..8b9a0327a 100644 --- a/source/frontend/carla_skin.py +++ b/source/frontend/carla_skin.py @@ -287,6 +287,7 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): host.ParameterValueChangedCallback.connect(self.slot_handleParameterValueChangedCallback) host.ParameterDefaultChangedCallback.connect(self.slot_handleParameterDefaultChangedCallback) host.ParameterMappedControlIndexChangedCallback.connect(self.slot_handleParameterMappedControlIndexChangedCallback) + host.ParameterMappedRangeChangedCallback.connect(self.slot_handleParameterMappedRangeChangedCallback) host.ParameterMidiChannelChangedCallback.connect(self.slot_handleParameterMidiChannelChangedCallback) host.ProgramChangedCallback.connect(self.slot_handleProgramChangedCallback) host.MidiProgramChangedCallback.connect(self.slot_handleMidiProgramChangedCallback) @@ -326,6 +327,11 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): if self.fPluginId == pluginId: self.setParameterMappedControlIndex(index, ctrl) + @pyqtSlot(int, int, float, float) + def slot_handleParameterMappedRangeChangedCallback(self, pluginId, index, minimum, maximum): + if self.fPluginId == pluginId: + self.setParameterMappedRange(index, minimum, maximum) + @pyqtSlot(int, int, int) def slot_handleParameterMidiChannelChangedCallback(self, pluginId, index, channel): if self.fPluginId == pluginId: @@ -741,6 +747,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): def setParameterMappedControlIndex(self, parameterId, control): self.fEditDialog.setParameterMappedControlIndex(parameterId, control) + def setParameterMappedRange(self, parameterId, minimum, maximum): + self.fEditDialog.setParameterMappedRange(parameterId, minimum, maximum) + def setParameterMidiChannel(self, parameterId, channel): self.fEditDialog.setParameterMidiChannel(parameterId, channel) diff --git a/source/frontend/carla_widgets.py b/source/frontend/carla_widgets.py index 904bc6ddb..8f9fcee40 100755 --- a/source/frontend/carla_widgets.py +++ b/source/frontend/carla_widgets.py @@ -234,11 +234,13 @@ class PluginParameter(QWidget): # ------------------------------------------------------------- # Internal stuff - self.fMappedCtrl = CONTROL_VALUE_NONE - self.fMidiChannel = 1 - self.fParameterId = pInfo['index'] - self.fPluginId = pluginId - self.fTabIndex = tabIndex + self.fMappedCtrl = pInfo['mappedControlIndex'] + self.fMappedMinimum = pInfo['mappedMinimum'] + self.fMappedMaximum = pInfo['mappedMaximum'] + self.fMidiChannel = pInfo['midiChannel'] + self.fParameterId = pInfo['index'] + self.fPluginId = pluginId + self.fTabIndex = tabIndex # ------------------------------------------------------------- # Set-up GUI @@ -322,6 +324,10 @@ class PluginParameter(QWidget): 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 @@ -339,6 +345,9 @@ class PluginParameter(QWidget): else: title = self.tr("Mapped to CC %i, channel %i" % (self.fMappedCtrl, self.fMidiChannel)) + if self.fMappedCtrl != CONTROL_VALUE_NONE: + title += " (min %g, max %g)" % (self.fMappedMinimum, self.fMappedMaximum) + actTitle = menu.addAction(title) actTitle.setEnabled(False) @@ -373,12 +382,12 @@ class PluginParameter(QWidget): action = menuMIDI.addAction(cc) actCCs.append(action) - if self.fMappedCtrl >= 0: - ccx = int(cc.split(" ", 1)[0], 16) + if self.fMappedCtrl >= 0 and self.fMappedCtrl <= MAX_MIDI_CC_LIST_ITEM: + ccx = int(cc.split(" [", 1)[0]) if ccx > self.fMappedCtrl and not inlist: inlist = True - action = menuMIDI.addAction(self.tr("0x%x (Custom)" % self.fMappedCtrl)) + action = menuMIDI.addAction(self.tr("%02i [0x%x] (Custom)" % (self.fMappedCtrl, self.fMappedCtrl))) action.setCheckable(True) action.setChecked(True) actCCs.append(action) @@ -388,6 +397,12 @@ class PluginParameter(QWidget): 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%x] (Custom)" % (self.fMappedCtrl, self.fMappedCtrl))) + action.setCheckable(True) + action.setChecked(True) + actCCs.append(action) + actCustomCC = menuMIDI.addAction(self.tr("Custom...")) # TODO @@ -909,6 +924,8 @@ class PluginEdit(QDialog): 'stepSmall': paramRanges['stepSmall'], 'stepLarge': paramRanges['stepLarge'], 'mappedControlIndex': paramData['mappedControlIndex'], + 'mappedMinimum': paramData['mappedMinimum'], + 'mappedMaximum': paramData['mappedMaximum'], 'midiChannel': paramData['midiChannel']+1, 'comment': paramInfo['comment'], @@ -1058,6 +1075,12 @@ class PluginEdit(QDialog): 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: diff --git a/source/utils/CarlaStateUtils.hpp b/source/utils/CarlaStateUtils.hpp index c83f5b355..e98ba660a 100644 --- a/source/utils/CarlaStateUtils.hpp +++ b/source/utils/CarlaStateUtils.hpp @@ -35,8 +35,11 @@ struct CarlaStateSave { const char* symbol; float value; #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH - int16_t mappedControlIndex; - uint8_t midiChannel; + int16_t mappedControlIndex; + uint8_t midiChannel; + bool mappedRangeValid; + float mappedMinimum; + float mappedMaximum; #endif Parameter() noexcept;