Closes #26 Signed-off-by: falkTX <falktx@falktx.com>tags/v2.2.0-RC1
@@ -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; | |||
@@ -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. | |||
@@ -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. | |||
@@ -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 | |||
{ | |||
@@ -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) | |||
@@ -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]; | |||
} | |||
@@ -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; | |||
@@ -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) | |||
{ | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -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, | |||
} | |||
@@ -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 | |||
@@ -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") | |||
{ | |||