Browse Source

Initial implementation of MIDI learn (per plugin, not global)

Closes #26

Signed-off-by: falkTX <falktx@falktx.com>
tags/v2.2.0-RC1
falkTX 4 years ago
parent
commit
c3f05e4e96
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
18 changed files with 229 additions and 76 deletions
  1. +6
    -1
      source/backend/CarlaBackend.h
  2. +3
    -2
      source/backend/CarlaEngine.hpp
  3. +7
    -0
      source/backend/CarlaPlugin.hpp
  4. +5
    -0
      source/backend/engine/CarlaEngineData.cpp
  5. +6
    -4
      source/backend/engine/CarlaEngineJack.cpp
  6. +5
    -4
      source/backend/engine/CarlaEnginePorts.cpp
  7. +59
    -0
      source/backend/plugin/CarlaPlugin.cpp
  8. +11
    -0
      source/backend/plugin/CarlaPluginBridge.cpp
  9. +1
    -0
      source/backend/plugin/CarlaPluginInternal.cpp
  10. +5
    -3
      source/backend/plugin/CarlaPluginInternal.hpp
  11. +12
    -6
      source/backend/plugin/CarlaPluginJuce.cpp
  12. +12
    -6
      source/backend/plugin/CarlaPluginLADSPADSSI.cpp
  13. +12
    -6
      source/backend/plugin/CarlaPluginLV2.cpp
  14. +16
    -8
      source/backend/plugin/CarlaPluginNative.cpp
  15. +12
    -6
      source/backend/plugin/CarlaPluginVST2.cpp
  16. +10
    -4
      source/frontend/carla_backend.py
  17. +44
    -24
      source/frontend/carla_widgets.py
  18. +3
    -2
      source/utils/CarlaStateUtils.cpp

+ 6
- 1
source/backend/CarlaBackend.h View File

@@ -779,10 +779,15 @@ typedef enum {
*/
CONTROL_INDEX_MIDI_PITCHBEND = 131,

/*!
* Special value to indicate MIDI learn.
*/
CONTROL_INDEX_MIDI_LEARN = 132,

/*!
* Highest index allowed for mappings.
*/
CONTROL_INDEX_MAX_ALLOWED = CONTROL_INDEX_MIDI_PITCHBEND
CONTROL_INDEX_MAX_ALLOWED = CONTROL_INDEX_MIDI_LEARN

} SpecialMappedControlIndex;



+ 3
- 2
source/backend/CarlaEngine.hpp View File

@@ -178,6 +178,7 @@ struct CARLA_API EngineControlEvent {
EngineControlEventType type; //!< Control-Event type.
uint16_t param; //!< Parameter Id, midi bank or midi program.
float value; //!< Parameter value, normalized to 0.0f<->1.0f.
bool handled; //!< Indicates that event was handled/received at least once.

/*!
* Convert this control event into MIDI data.
@@ -575,12 +576,12 @@ public:
* Get the event at @a index.
* @note You must only call this for input ports.
*/
virtual const EngineEvent& getEvent(uint32_t index) const noexcept;
virtual EngineEvent& getEvent(uint32_t index) const noexcept;

/*!
* Get the event at @a index, faster unchecked version.
*/
virtual const EngineEvent& getEventUnchecked(uint32_t index) const noexcept;
virtual EngineEvent& getEventUnchecked(uint32_t index) const noexcept;

/*!
* Write a control event into the buffer.


+ 7
- 0
source/backend/CarlaPlugin.hpp View File

@@ -51,6 +51,7 @@ class CarlaEngineEventPort;
class CarlaEngineCVSourcePorts;
class CarlaEngineBridge;
struct CarlaStateSave;
struct EngineEvent;

// -----------------------------------------------------------------------

@@ -891,6 +892,12 @@ public:
*/
CarlaEngineEventPort* getDefaultEventOutPort() const noexcept;

/*!
* Check if the plugin is interested on MIDI learn, and if so, map this event to the parameter that wants it.
* Event MUST be of control type and not have been handled before.
*/
void checkForMidiLearn(EngineEvent& event) noexcept;

/*!
* Get the plugin's type native handle.
* This will be LADSPA_Handle, LV2_Handle, etc.


+ 5
- 0
source/backend/engine/CarlaEngineData.cpp View File

@@ -108,18 +108,21 @@ void EngineEvent::fillFromMidiData(const uint8_t size, const uint8_t* const data
ctrl.type = kEngineControlEventTypeMidiBank;
ctrl.param = midiBank;
ctrl.value = 0.0f;
ctrl.handled = true;
}
else if (midiControl == MIDI_CONTROL_ALL_SOUND_OFF)
{
ctrl.type = kEngineControlEventTypeAllSoundOff;
ctrl.param = 0;
ctrl.value = 0.0f;
ctrl.handled = true;
}
else if (midiControl == MIDI_CONTROL_ALL_NOTES_OFF)
{
ctrl.type = kEngineControlEventTypeAllNotesOff;
ctrl.param = 0;
ctrl.value = 0.0f;
ctrl.handled = true;
}
else
{
@@ -130,6 +133,7 @@ void EngineEvent::fillFromMidiData(const uint8_t size, const uint8_t* const data
ctrl.type = kEngineControlEventTypeParameter;
ctrl.param = midiControl;
ctrl.value = float(midiValue)/127.0f;
ctrl.handled = false;
}
}
else if (midiStatus == MIDI_STATUS_PROGRAM_CHANGE)
@@ -143,6 +147,7 @@ void EngineEvent::fillFromMidiData(const uint8_t size, const uint8_t* const data
ctrl.type = kEngineControlEventTypeMidiProgram;
ctrl.param = midiProgram;
ctrl.value = 0.0f;
ctrl.handled = true;
}
else
{


+ 6
- 4
source/backend/engine/CarlaEngineJack.cpp View File

@@ -124,7 +124,9 @@ static /* */ PortNameToId kPortNameToIdFallbackNC = { 0, 0, { '\0' }, { '\0'
static const PortNameToId kPortNameToIdFallback = { 0, 0, { '\0' }, { '\0' } };
static const ConnectionToId kConnectionToIdFallback = { 0, 0, 0, 0, 0 };
#endif
static const EngineEvent kFallbackJackEngineEvent = { kEngineEventTypeNull, 0, 0, {{ kEngineControlEventTypeNull, 0, 0.0f }} };
static EngineEvent kFallbackJackEngineEvent = {
kEngineEventTypeNull, 0, 0, {{ kEngineControlEventTypeNull, 0, 0.0f, true }}
};

// -----------------------------------------------------------------------
// Carla Engine Port removal helper
@@ -460,7 +462,7 @@ public:
} CARLA_SAFE_EXCEPTION_RETURN("jack_midi_get_event_count", 0);
}

const EngineEvent& getEvent(const uint32_t index) const noexcept override
EngineEvent& getEvent(const uint32_t index) const noexcept override
{
if (fJackPort == nullptr)
return CarlaEngineEventPort::getEvent(index);
@@ -471,7 +473,7 @@ public:
return getEventUnchecked(index);
}

const EngineEvent& getEventUnchecked(uint32_t index) const noexcept override
EngineEvent& getEventUnchecked(uint32_t index) const noexcept override
{
#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
if (index < fCvSourceEventCount)
@@ -529,7 +531,7 @@ public:

uint8_t data[3] = { 0, 0, 0 };

EngineControlEvent ctrlEvent = { type, param, value };
EngineControlEvent ctrlEvent = { type, param, value, false };
const uint8_t size = ctrlEvent.convertToMidiData(channel, data);

if (size == 0)


+ 5
- 4
source/backend/engine/CarlaEnginePorts.cpp View File

@@ -31,8 +31,9 @@ CARLA_BACKEND_START_NAMESPACE
// -----------------------------------------------------------------------
// Fallback data

static const EngineEvent kFallbackEngineEvent = { kEngineEventTypeNull, 0, 0, {{ kEngineControlEventTypeNull, 0, 0.0f }} };
//static CarlaEngineEventCV kFallbackEngineEventCV = { nullptr, (uint32_t)-1, 0.0f };
static EngineEvent kFallbackEngineEvent = {
kEngineEventTypeNull, 0, 0, {{ kEngineControlEventTypeNull, 0, 0.0f, true }}
};

// -----------------------------------------------------------------------
// Carla Engine port (Abstract)
@@ -169,7 +170,7 @@ uint32_t CarlaEngineEventPort::getEventCount() const noexcept
return i;
}

const EngineEvent& CarlaEngineEventPort::getEvent(const uint32_t index) const noexcept
EngineEvent& CarlaEngineEventPort::getEvent(const uint32_t index) const noexcept
{
CARLA_SAFE_ASSERT_RETURN(kIsInput, kFallbackEngineEvent);
CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr, kFallbackEngineEvent);
@@ -179,7 +180,7 @@ const EngineEvent& CarlaEngineEventPort::getEvent(const uint32_t index) const no
return fBuffer[index];
}

const EngineEvent& CarlaEngineEventPort::getEventUnchecked(const uint32_t index) const noexcept
EngineEvent& CarlaEngineEventPort::getEventUnchecked(const uint32_t index) const noexcept
{
return fBuffer[index];
}


+ 59
- 0
source/backend/plugin/CarlaPlugin.cpp View File

@@ -21,6 +21,7 @@
#include "CarlaBackendUtils.hpp"
#include "CarlaBase64Utils.hpp"
#include "CarlaMathUtils.hpp"
#include "CarlaMIDI.h"
#include "CarlaPluginUI.hpp"
#include "CarlaScopeUtils.hpp"
#include "CarlaStringList.hpp"
@@ -1789,6 +1790,11 @@ void CarlaPlugin::setParameterMappedControlIndex(const uint32_t parameterId, con
paramData.mappedControlIndex = index;

#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
if (index == CONTROL_INDEX_MIDI_LEARN)
pData->midiLearnParameterIndex = static_cast<int32_t>(parameterId);
else
pData->midiLearnParameterIndex = -1;

pData->engine->callback(sendCallback, sendOsc,
ENGINE_CALLBACK_PARAMETER_MAPPED_CONTROL_INDEX_CHANGED,
pData->id,
@@ -2258,6 +2264,33 @@ void CarlaPlugin::idle()
0, 0.0f, nullptr);
}
} break;

case kPluginPostRtEventMidiLearn: {
CARLA_SAFE_ASSERT_BREAK(event.value1 >= 0 && event.value1 < MAX_MIDI_CHANNELS);
CARLA_SAFE_ASSERT_BREAK(event.value2 >= 0 && event.value2 < MAX_MIDI_NOTE);

if (event.sendCallback)
{
const int32_t parameterId = event.value1;
const int32_t midiCC = event.value2;
const int32_t midiChannel = event.value2;

pData->engine->callback(true, true,
ENGINE_CALLBACK_PARAMETER_MAPPED_CONTROL_INDEX_CHANGED,
pData->id,
parameterId,
midiCC,
0, 0.0f, nullptr);

pData->engine->callback(true, true,
ENGINE_CALLBACK_PARAMETER_MIDI_CHANNEL_CHANGED,
pData->id,
parameterId,
midiChannel,
0, 0.0f, nullptr);

}
} break;
}
}
}
@@ -2405,6 +2438,7 @@ void CarlaPlugin::uiIdle()
{
case kPluginPostRtEventNull:
case kPluginPostRtEventDebug:
case kPluginPostRtEventMidiLearn:
break;

case kPluginPostRtEventParameterChange:
@@ -2538,6 +2572,31 @@ CarlaEngineEventPort* CarlaPlugin::getDefaultEventOutPort() const noexcept
return pData->event.portOut;
}

void CarlaPlugin::checkForMidiLearn(EngineEvent& event) noexcept
{
if (pData->midiLearnParameterIndex < 0)
return;
if (event.ctrl.param == MIDI_CONTROL_BANK_SELECT || event.ctrl.param == MIDI_CONTROL_BANK_SELECT__LSB)
return;
if (event.ctrl.param >= MAX_MIDI_CONTROL)
return;

const uint32_t parameterId = static_cast<uint32_t>(pData->midiLearnParameterIndex);
CARLA_SAFE_ASSERT_UINT2_RETURN(parameterId < pData->param.count, parameterId, pData->param.count,);

ParameterData& paramData(pData->param.data[parameterId]);
CARLA_SAFE_ASSERT_INT_RETURN(paramData.mappedControlIndex == CONTROL_INDEX_MIDI_LEARN,
paramData.mappedControlIndex,);

event.ctrl.handled = true;
paramData.mappedControlIndex = static_cast<int16_t>(event.ctrl.param);
paramData.midiChannel = event.channel;

pData->postponeRtEvent(kPluginPostRtEventMidiLearn, true,
pData->midiLearnParameterIndex, event.ctrl.param, event.channel, 0.0f);
pData->midiLearnParameterIndex = -1;
}

void* CarlaPlugin::getNativeHandle() const noexcept
{
return nullptr;


+ 11
- 0
source/backend/plugin/CarlaPluginBridge.cpp View File

@@ -1365,6 +1365,17 @@ public:

case kEngineControlEventTypeParameter:
#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
// non-midi
if (event.channel == kEngineEventNonMidiChannel)
{
const uint32_t k = ctrlEvent.param;
CARLA_SAFE_ASSERT_CONTINUE(k < pData->param.count);

// value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.value);
// setParameterValueRT(k, value, true);
continue;
}

// Control backend stuff
if (event.channel == pData->ctrlChannel)
{


+ 1
- 0
source/backend/plugin/CarlaPluginInternal.cpp View File

@@ -673,6 +673,7 @@ CarlaPlugin::ProtectedData::ProtectedData(CarlaEngine* const eng, const uint idx
ctrlChannel(0),
extraHints(0x0),
#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
midiLearnParameterIndex(-1),
transientTryCounter(0),
transientFirstTry(true),
#endif


+ 5
- 3
source/backend/plugin/CarlaPluginInternal.hpp View File

@@ -86,7 +86,8 @@ enum PluginPostRtEventType {
kPluginPostRtEventProgramChange, // index
kPluginPostRtEventMidiProgramChange, // index
kPluginPostRtEventNoteOn, // channel, note, velo
kPluginPostRtEventNoteOff // channel, note
kPluginPostRtEventNoteOff, // channel, note
kPluginPostRtEventMidiLearn // param, cc, channel
};

/*!
@@ -244,8 +245,9 @@ struct CarlaPlugin::ProtectedData {
int8_t ctrlChannel;
uint extraHints;
#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
uint transientTryCounter;
bool transientFirstTry;
int32_t midiLearnParameterIndex;
uint transientTryCounter;
bool transientFirstTry;
#endif

// data 1


+ 12
- 6
source/backend/plugin/CarlaPluginJuce.cpp View File

@@ -864,7 +864,7 @@ public:

for (uint32_t i=0, numEvents=pData->event.portIn->getEventCount(); i < numEvents; ++i)
{
const EngineEvent& event(pData->event.portIn->getEvent(i));
EngineEvent& event(pData->event.portIn->getEvent(i));

if (event.time >= frames)
continue;
@@ -875,7 +875,7 @@ public:
break;

case kEngineEventTypeControl: {
const EngineControlEvent& ctrlEvent(event.ctrl);
EngineControlEvent& ctrlEvent(event.ctrl);

switch (ctrlEvent.type)
{
@@ -892,6 +892,7 @@ public:
const uint32_t k = ctrlEvent.param;
CARLA_SAFE_ASSERT_CONTINUE(k < pData->param.count);

ctrlEvent.handled = true;
value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.value);
setParameterValueRT(k, value, true);
continue;
@@ -902,17 +903,17 @@ public:
{
if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0)
{
ctrlEvent.handled = true;
value = ctrlEvent.value;
setDryWetRT(value, true);
}

if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0)
else if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0)
{
ctrlEvent.handled = true;
value = ctrlEvent.value*127.0f/100.0f;
setVolumeRT(value, true);
}

if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0)
else if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0)
{
float left, right;
value = ctrlEvent.value/0.5f - 1.0f;
@@ -933,6 +934,7 @@ public:
right = 1.0f;
}

ctrlEvent.handled = true;
setBalanceLeftRT(left, true);
setBalanceRightRT(right, true);
}
@@ -951,6 +953,7 @@ public:
if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMABLE) == 0)
continue;

ctrlEvent.handled = true;
value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.value);
setParameterValueRT(k, value, true);
}
@@ -965,6 +968,9 @@ public:
fMidiBuffer.addEvent(midiData, 3, static_cast<int>(event.time));
}

if (! ctrlEvent.handled)
checkForMidiLearn(event);

break;
} // case kEngineControlEventTypeParameter



+ 12
- 6
source/backend/plugin/CarlaPluginLADSPADSSI.cpp View File

@@ -1602,7 +1602,7 @@ public:

for (uint32_t i=0, numEvents=pData->event.portIn->getEventCount(); i < numEvents; ++i)
{
const EngineEvent& event(pData->event.portIn->getEvent(i));
EngineEvent& event(pData->event.portIn->getEvent(i));

uint32_t eventTime = event.time;
CARLA_SAFE_ASSERT_UINT2_CONTINUE(eventTime < frames, eventTime, frames);
@@ -1637,7 +1637,7 @@ public:
break;

case kEngineEventTypeControl: {
const EngineControlEvent& ctrlEvent(event.ctrl);
EngineControlEvent& ctrlEvent(event.ctrl);

switch (ctrlEvent.type)
{
@@ -1654,6 +1654,7 @@ public:
const uint32_t k = ctrlEvent.param;
CARLA_SAFE_ASSERT_CONTINUE(k < pData->param.count);

ctrlEvent.handled = true;
value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.value);
setParameterValueRT(k, value, true);
continue;
@@ -1664,17 +1665,17 @@ public:
{
if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0)
{
ctrlEvent.handled = true;
value = ctrlEvent.value;
setDryWetRT(value, true);
}

if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0)
else if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0)
{
ctrlEvent.handled = true;
value = ctrlEvent.value*127.0f/100.0f;
setVolumeRT(value, true);
}

if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0)
else if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0)
{
float left, right;
value = ctrlEvent.value/0.5f - 1.0f;
@@ -1695,6 +1696,7 @@ public:
right = 1.0f;
}

ctrlEvent.handled = true;
setBalanceLeftRT(left, true);
setBalanceRightRT(right, true);
}
@@ -1712,6 +1714,7 @@ public:
if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMABLE) == 0)
continue;

ctrlEvent.handled = true;
value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.value);
setParameterValueRT(k, value, true);
}
@@ -1731,6 +1734,9 @@ public:
seqEvent.data.control.value = int8_t(ctrlEvent.value*127.0f);
}

if (! ctrlEvent.handled)
checkForMidiLearn(event);

break;
} // case kEngineControlEventTypeParameter



+ 12
- 6
source/backend/plugin/CarlaPluginLV2.cpp View File

@@ -3749,7 +3749,7 @@ public:

for (uint32_t i=0; i < numEvents; ++i)
{
const EngineEvent& event(fEventsIn.ctrl->port->getEvent(i));
EngineEvent& event(fEventsIn.ctrl->port->getEvent(i));

uint32_t eventTime = event.time;
CARLA_SAFE_ASSERT_UINT2_CONTINUE(eventTime < frames, eventTime, frames);
@@ -3821,7 +3821,7 @@ public:
break;

case kEngineEventTypeControl: {
const EngineControlEvent& ctrlEvent(event.ctrl);
EngineControlEvent& ctrlEvent(event.ctrl);

switch (ctrlEvent.type)
{
@@ -3838,6 +3838,7 @@ public:
const uint32_t k = ctrlEvent.param;
CARLA_SAFE_ASSERT_CONTINUE(k < pData->param.count);

ctrlEvent.handled = true;
value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.value);
setParameterValueRT(k, value, true);
continue;
@@ -3848,17 +3849,17 @@ public:
{
if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0)
{
ctrlEvent.handled = true;
value = ctrlEvent.value;
setDryWetRT(value, true);
}

if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0)
else if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0)
{
ctrlEvent.handled = true;
value = ctrlEvent.value*127.0f/100.0f;
setVolumeRT(value, true);
}

if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0)
else if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0)
{
float left, right;
value = ctrlEvent.value/0.5f - 1.0f;
@@ -3879,6 +3880,7 @@ public:
right = 1.0f;
}

ctrlEvent.handled = true;
setBalanceLeftRT(left, true);
setBalanceRightRT(right, true);
}
@@ -3897,6 +3899,7 @@ public:
if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMABLE) == 0)
continue;

ctrlEvent.handled = true;
value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.value);
setParameterValueRT(k, value, true);
}
@@ -3920,6 +3923,9 @@ public:
lv2midi_put_event(&evInMidiStates[fEventsIn.ctrlIndex], mtime, 3, midiData);
}

if (! ctrlEvent.handled)
checkForMidiLearn(event);

break;
} // case kEngineControlEventTypeParameter



+ 16
- 8
source/backend/plugin/CarlaPluginNative.cpp View File

@@ -97,7 +97,9 @@ CARLA_BACKEND_START_NAMESPACE
// Fallback data

static const CustomData kCustomDataFallback = { nullptr, nullptr, nullptr };
static const EngineEvent kNullEngineEvent = { kEngineEventTypeNull, 0, 0, {} };
static EngineEvent kNullEngineEvent = {
kEngineEventTypeNull, 0, 0, {{ kEngineControlEventTypeNull, 0, 0.0f, true }}
};

// -----------------------------------------------------------------------

@@ -1684,7 +1686,7 @@ public:
}
}

const EngineEvent& findNextEvent()
EngineEvent& findNextEvent()
{
if (fMidiIn.count == 1)
{
@@ -1909,7 +1911,7 @@ public:

for (;;)
{
const EngineEvent& event(findNextEvent());
EngineEvent& event(findNextEvent());

if (event.type == kEngineEventTypeNull)
break;
@@ -1959,7 +1961,7 @@ public:
break;

case kEngineEventTypeControl: {
const EngineControlEvent& ctrlEvent = event.ctrl;
EngineControlEvent& ctrlEvent(event.ctrl);

switch (ctrlEvent.type)
{
@@ -1976,6 +1978,7 @@ public:
const uint32_t k = ctrlEvent.param;
CARLA_SAFE_ASSERT_CONTINUE(k < pData->param.count);

ctrlEvent.handled = true;
value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.value);
setParameterValueRT(k, value, true);
continue;
@@ -1986,17 +1989,17 @@ public:
{
if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) > 0)
{
ctrlEvent.handled = true;
value = ctrlEvent.value;
setDryWetRT(value, true);
}

if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) > 0)
else if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) > 0)
{
ctrlEvent.handled = true;
value = ctrlEvent.value*127.0f/100.0f;
setVolumeRT(value, true);
}

if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) > 0)
else if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) > 0)
{
float left, right;
value = ctrlEvent.value/0.5f - 1.0f;
@@ -2017,6 +2020,7 @@ public:
right = 1.0f;
}

ctrlEvent.handled = true;
setBalanceLeftRT(left, true);
setBalanceRightRT(right, true);
}
@@ -2034,6 +2038,7 @@ public:
if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMABLE) == 0)
continue;

ctrlEvent.handled = true;
value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.value);
setParameterValueRT(k, value, true);
}
@@ -2053,6 +2058,9 @@ public:
nativeEvent.size = 3;
}

if (! ctrlEvent.handled)
checkForMidiLearn(event);

break;
} // case kEngineControlEventTypeParameter



+ 12
- 6
source/backend/plugin/CarlaPluginVST2.cpp View File

@@ -1302,7 +1302,7 @@ public:

for (uint32_t i=0, numEvents = pData->event.portIn->getEventCount(); i < numEvents; ++i)
{
const EngineEvent& event(pData->event.portIn->getEvent(i));
EngineEvent& event(pData->event.portIn->getEvent(i));

uint32_t eventTime = event.time;
CARLA_SAFE_ASSERT_UINT2_CONTINUE(eventTime < frames, eventTime, frames);
@@ -1337,7 +1337,7 @@ public:
break;

case kEngineEventTypeControl: {
const EngineControlEvent& ctrlEvent(event.ctrl);
EngineControlEvent& ctrlEvent(event.ctrl);

switch (ctrlEvent.type)
{
@@ -1354,6 +1354,7 @@ public:
const uint32_t k = ctrlEvent.param;
CARLA_SAFE_ASSERT_CONTINUE(k < pData->param.count);

ctrlEvent.handled = true;
value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.value);
setParameterValueRT(k, value, true);
continue;
@@ -1364,17 +1365,17 @@ public:
{
if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0)
{
ctrlEvent.handled = true;
value = ctrlEvent.value;
setDryWetRT(value, true);
}

if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0)
else if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0)
{
ctrlEvent.handled = true;
value = ctrlEvent.value*127.0f/100.0f;
setVolumeRT(value, true);
}

if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0)
else if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0)
{
float left, right;
value = ctrlEvent.value/0.5f - 1.0f;
@@ -1395,6 +1396,7 @@ public:
right = 1.0f;
}

ctrlEvent.handled = true;
setBalanceLeftRT(left, true);
setBalanceRightRT(right, true);
}
@@ -1413,6 +1415,7 @@ public:
if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMABLE) == 0)
continue;

ctrlEvent.handled = true;
value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.value);
setParameterValueRT(k, value, true);
}
@@ -1433,6 +1436,9 @@ public:
vstMidiEvent.midiData[2] = char(ctrlEvent.value*127.0f);
}

if (! ctrlEvent.handled)
checkForMidiLearn(event);

break;
} // case kEngineControlEventTypeParameter



+ 10
- 4
source/frontend/carla_backend.py View File

@@ -562,13 +562,19 @@ PARAMETER_MAX = -9
# @see ParameterData::mappedControlIndex

# Unused control index, meaning no mapping is enabled.
CONTROL_VALUE_NONE = -1
CONTROL_INDEX_NONE = -1

# CV control index, meaning the parameter is exposed as CV port.
CONTROL_VALUE_CV = 130
CONTROL_INDEX_CV = 130

# Special value to indicate MIDI pitchbend.
CONTROL_VALUE_MIDI_PITCHBEND = 131
CONTROL_INDEX_MIDI_PITCHBEND = 131

# Special value to indicate MIDI learn.
CONTROL_INDEX_MIDI_LEARN = 132

# Special value to indicate MIDI pitchbend.
CONTROL_INDEX_MAX_ALLOWED = CONTROL_INDEX_MIDI_LEARN

# ---------------------------------------------------------------------------------------------------------------------
# Engine Callback Opcode
@@ -1227,7 +1233,7 @@ PyParameterData = {
'index': PARAMETER_NULL,
'rindex': -1,
'midiChannel': 0,
'mappedControlIndex': CONTROL_VALUE_NONE,
'mappedControlIndex': CONTROL_INDEX_NONE,
'mappedMinimum': 0.0,
'mappedMaximum': 0.0,
}


+ 44
- 24
source/frontend/carla_widgets.py View File

@@ -73,9 +73,10 @@ from carla_backend import (
PARAMETER_USES_CUSTOM_TEXT,
PARAMETER_CAN_BE_CV_CONTROLLED,
PARAMETER_INPUT, PARAMETER_OUTPUT,
CONTROL_VALUE_NONE,
CONTROL_VALUE_MIDI_PITCHBEND,
CONTROL_VALUE_CV
CONTROL_INDEX_NONE,
CONTROL_INDEX_MIDI_PITCHBEND,
CONTROL_INDEX_MIDI_LEARN,
CONTROL_INDEX_CV
)

from carla_shared import (
@@ -390,10 +391,14 @@ class PluginParameter(QWidget):
self.ui.l_name.setFixedWidth(width)

def updateStatusLabel(self):
if self.fMappedCtrl == CONTROL_VALUE_NONE:
if self.fMappedCtrl == CONTROL_INDEX_NONE:
text = self.tr("Unmapped")
elif self.fMappedCtrl == CONTROL_VALUE_CV:
elif self.fMappedCtrl == CONTROL_INDEX_CV:
text = self.tr("CV export")
elif self.fMappedCtrl == CONTROL_INDEX_MIDI_PITCHBEND:
text = self.tr("PBend Ch%i" % (self.fMidiChannel,))
elif self.fMappedCtrl == CONTROL_INDEX_MIDI_LEARN:
text = self.tr("MIDI Learn")
else:
text = self.tr("CC%i Ch%i" % (self.fMappedCtrl, self.fMidiChannel))

@@ -403,14 +408,18 @@ class PluginParameter(QWidget):
def slot_optionsCustomMenu(self):
menu = QMenu(self)

if self.fMappedCtrl == CONTROL_VALUE_NONE:
if self.fMappedCtrl == CONTROL_INDEX_NONE:
title = self.tr("Unmapped")
elif self.fMappedCtrl == CONTROL_VALUE_CV:
elif self.fMappedCtrl == CONTROL_INDEX_CV:
title = self.tr("Exposed as CV port")
elif self.fMappedCtrl == CONTROL_INDEX_MIDI_PITCHBEND:
title = self.tr("Mapped to MIDI Pitchbend, channel %i" % (self.fMidiChannel,))
elif self.fMappedCtrl == CONTROL_INDEX_MIDI_LEARN:
title = self.tr("MIDI Learn active")
else:
title = self.tr("Mapped to MIDI control %i, channel %i" % (self.fMappedCtrl, self.fMidiChannel))

if self.fMappedCtrl != CONTROL_VALUE_NONE:
if self.fMappedCtrl != CONTROL_INDEX_NONE:
title += " (range: %g-%g)" % (self.fMappedMinimum, self.fMappedMaximum)

actTitle = menu.addAction(title)
@@ -420,14 +429,14 @@ class PluginParameter(QWidget):

actUnmap = menu.addAction(self.tr("Unmap"))

if self.fMappedCtrl == CONTROL_VALUE_NONE:
if self.fMappedCtrl == CONTROL_INDEX_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:
if self.fMappedCtrl == CONTROL_INDEX_CV:
actCV.setCheckable(True)
actCV.setChecked(True)
else:
@@ -435,9 +444,18 @@ class PluginParameter(QWidget):

menu.addSection("MIDI")

actLearn = menu.addAction(self.tr("MIDI Learn"))

if self.fMappedCtrl == CONTROL_INDEX_MIDI_LEARN:
actLearn.setCheckable(True)
actLearn.setChecked(True)

menuMIDI = menu.addMenu(self.tr("MIDI Control"))

if self.fMappedCtrl not in (CONTROL_VALUE_NONE, CONTROL_VALUE_CV, CONTROL_VALUE_MIDI_PITCHBEND):
if self.fMappedCtrl not in (CONTROL_INDEX_NONE,
CONTROL_INDEX_CV,
CONTROL_INDEX_MIDI_PITCHBEND,
CONTROL_INDEX_MIDI_LEARN):
action = menuMIDI.menuAction()
action.setCheckable(True)
action.setChecked(True)
@@ -475,7 +493,7 @@ class PluginParameter(QWidget):
# TODO
#actPitchbend = menu.addAction(self.tr("MIDI Pitchbend"))

#if self.fMappedCtrl == CONTROL_VALUE_MIDI_PITCHBEND:
#if self.fMappedCtrl == CONTROL_INDEX_MIDI_PITCHBEND:
#actPitchbend.setCheckable(True)
#actPitchbend.setChecked(True)

@@ -490,8 +508,8 @@ class PluginParameter(QWidget):
action.setCheckable(True)
action.setChecked(True)

if self.fMappedCtrl != CONTROL_VALUE_NONE:
if self.fMappedCtrl == CONTROL_VALUE_CV:
if self.fMappedCtrl != CONTROL_INDEX_NONE:
if self.fMappedCtrl == CONTROL_INDEX_CV:
menu.addSection("Range (Scaled CV input)")
else:
menu.addSection("Range (MIDI bounds)")
@@ -517,8 +535,8 @@ class PluginParameter(QWidget):
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.fMinimum if self.fMappedCtrl != CONTROL_INDEX_CV else -9e6,
self.fMaximum if self.fMappedCtrl != CONTROL_INDEX_CV else 9e6,
self.fDecimalPoints)
if not ok:
return
@@ -532,8 +550,8 @@ class PluginParameter(QWidget):
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.fMinimum if self.fMappedCtrl != CONTROL_INDEX_CV else -9e6,
self.fMaximum if self.fMappedCtrl != CONTROL_INDEX_CV else 9e6,
self.fDecimalPoints)
if not ok:
return
@@ -543,11 +561,9 @@ class PluginParameter(QWidget):
return

if actSel == actUnmap:
ctrl = CONTROL_VALUE_NONE
ctrl = CONTROL_INDEX_NONE
elif actSel == actCV:
ctrl = CONTROL_VALUE_CV
elif actSel in actCCs:
ctrl = int(actSel.text().split(" ", 1)[0].replace("&",""), 10)
ctrl = CONTROL_INDEX_CV
elif actSel == actCustomCC:
value = self.fMappedCtrl if self.fMappedCtrl >= 0x01 and self.fMappedCtrl <= 0x77 else 1
ctrl, ok = QInputDialog.getInt(self,
@@ -557,8 +573,12 @@ class PluginParameter(QWidget):
0x01, 0x77, 1)
if not ok:
return
#elif actSel in actPitchbend:
#ctrl = CONTROL_VALUE_MIDI_PITCHBEND
#elif actSel == actPitchbend:
#ctrl = CONTROL_INDEX_MIDI_PITCHBEND
elif actSel == actLearn:
ctrl = CONTROL_INDEX_MIDI_LEARN
elif actSel in actCCs:
ctrl = int(actSel.text().split(" ", 1)[0].replace("&",""), 10)
else:
return



+ 3
- 2
source/utils/CarlaStateUtils.cpp View File

@@ -1,6 +1,6 @@
/*
* Carla State utils
* Copyright (C) 2012-2018 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2020 Filipe Coelho <falktx@falktx.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -455,7 +455,8 @@ bool CarlaStateSave::fillFromXmlElement(const XmlElement* const xmlElement)
{
const int ctrl(pText.getIntValue());
if (ctrl > CONTROL_INDEX_NONE && ctrl <= CONTROL_INDEX_MAX_ALLOWED)
stateParameter->mappedControlIndex = static_cast<int16_t>(ctrl);
if (ctrl != CONTROL_INDEX_MIDI_LEARN)
stateParameter->mappedControlIndex = static_cast<int16_t>(ctrl);
}
else if (pTag == "MappedMinimum")
{


Loading…
Cancel
Save