diff --git a/source/backend/CarlaEngine.hpp b/source/backend/CarlaEngine.hpp index fc3bcad60..68d93780c 100644 --- a/source/backend/CarlaEngine.hpp +++ b/source/backend/CarlaEngine.hpp @@ -385,6 +385,14 @@ public: return kIsInput; } + /*! + * Get the index offset as passed in the constructor. + */ + uint32_t getIndexOffset() const noexcept + { + return kIndexOffset; + } + /*! * Get this ports' engine client. */ @@ -494,9 +502,24 @@ public: return fBuffer; } + /*! + * Get min/max range for this CV port. + */ + void getRange(float& min, float& max) const noexcept + { + min = fMinimum; + max = fMaximum; + } + + /*! + * Set min/max range for this CV port. + */ + void setRange(float min, float max) noexcept; + #ifndef DOXYGEN protected: float* fBuffer; + float fMinimum, fMaximum; CARLA_DECLARE_NON_COPY_CLASS(CarlaEngineCVPort) #endif @@ -537,6 +560,11 @@ public: */ void removeCVSource(CarlaEngineCVPort* port) noexcept; + /*! + * Remove a CV port as a source of events. + */ + void mixWithCvBuffer(const float* buffer, uint32_t frames, uint32_t indexOffset) noexcept; + /*! * Initialize the port's internal buffer for @a engine. */ diff --git a/source/backend/engine/CarlaEngineInternal.hpp b/source/backend/engine/CarlaEngineInternal.hpp index 32d0efd86..90f7a4286 100644 --- a/source/backend/engine/CarlaEngineInternal.hpp +++ b/source/backend/engine/CarlaEngineInternal.hpp @@ -215,6 +215,7 @@ struct EnginePluginData { struct CarlaEngineEventCV { CarlaEngineCVPort* cvPort; float previousValue; + uint32_t indexOffset; }; struct CarlaEngineEventPort::ProtectedData { diff --git a/source/backend/engine/CarlaEnginePorts.cpp b/source/backend/engine/CarlaEnginePorts.cpp index 979bcc7f0..210e8f5a6 100644 --- a/source/backend/engine/CarlaEnginePorts.cpp +++ b/source/backend/engine/CarlaEnginePorts.cpp @@ -20,6 +20,8 @@ #include "CarlaMathUtils.hpp" #include "CarlaMIDI.h" +#include "lv2/lv2.h" + CARLA_BACKEND_START_NAMESPACE // ----------------------------------------------------------------------- @@ -71,7 +73,9 @@ void CarlaEngineAudioPort::initBuffer() noexcept CarlaEngineCVPort::CarlaEngineCVPort(const CarlaEngineClient& client, const bool isInputPort, const uint32_t indexOffset) noexcept : CarlaEnginePort(client, isInputPort, indexOffset), - fBuffer(nullptr) + fBuffer(nullptr), + fMinimum(-1.0f), + fMaximum(1.0f) { carla_debug("CarlaEngineCVPort::CarlaEngineCVPort(%s)", bool2str(isInputPort)); } @@ -85,6 +89,26 @@ void CarlaEngineCVPort::initBuffer() noexcept { } +void CarlaEngineCVPort::setRange(const float min, const float max) noexcept +{ + fMinimum = min; + fMaximum = max; + + char strBufMin[STR_MAX]; + char strBufMax[STR_MAX]; + carla_zeroChars(strBufMin, STR_MAX); + carla_zeroChars(strBufMax, STR_MAX); + + { + const CarlaScopedLocale csl; + std::snprintf(strBufMin, STR_MAX-1, "%f", static_cast(min)); + std::snprintf(strBufMax, STR_MAX-1, "%f", static_cast(max)); + } + + setMetaData(LV2_CORE__minimum, strBufMin, ""); + setMetaData(LV2_CORE__maximum, strBufMax, ""); +} + // ----------------------------------------------------------------------- // Carla Engine Event port @@ -107,7 +131,7 @@ void CarlaEngineEventPort::addCVSource(CarlaEngineCVPort* const port) noexcept CARLA_SAFE_ASSERT_RETURN(port->isInput(),); carla_debug("CarlaEngineEventPort::addCVSource(%p)", port); - const CarlaEngineEventCV ecv { port, 0.0f }; + const CarlaEngineEventCV ecv { port, 0.0f, port->getIndexOffset() }; pData->cvs.append(ecv); } @@ -121,17 +145,69 @@ void CarlaEngineEventPort::removeCVSource(CarlaEngineCVPort* const port) noexcep (void)port; } +static CarlaEngineEventCV kFallbackEngineEventCV = { nullptr, 0.0f, (uint32_t)-1 }; + +void CarlaEngineEventPort::mixWithCvBuffer(const float* const buffer, + const uint32_t frames, + const uint32_t indexOffset) noexcept +{ + CARLA_SAFE_ASSERT_RETURN(pData->buffer != nullptr,) + CARLA_SAFE_ASSERT_RETURN(kIsInput,); + + uint32_t eventIndex = 0; + float v, min, max; + + for (; eventIndex < kMaxEngineEventInternalCount; ++eventIndex) + { + if (pData->buffer[eventIndex].type == kEngineEventTypeNull) + break; + } + + if (eventIndex == kMaxEngineEventInternalCount) + return; + + for (LinkedList::Itenerator it = pData->cvs.begin2(); it.valid(); it.next()) + { + CarlaEngineEventCV& ecv(it.getValue(kFallbackEngineEventCV)); + + if (ecv.indexOffset != indexOffset) + continue; + CARLA_SAFE_ASSERT_RETURN(ecv.cvPort != nullptr,); + + float previousValue = ecv.previousValue; + ecv.cvPort->getRange(min, max); + + for (uint32_t i=0; ibuffer[i++]); + + event.type = kEngineEventTypeControl; + event.time = i; + event.channel = 0xFF; + + event.ctrl.type = kEngineControlEventTypeParameter; + event.ctrl.param = static_cast(indexOffset); + event.ctrl.value = carla_fixedValue(0.0f, 1.0f, (v - min) / (max - min)); + } + } + + ecv.previousValue = previousValue; + break; + } +} + void CarlaEngineEventPort::initBuffer() noexcept { if (pData->processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || pData->processMode == ENGINE_PROCESS_MODE_BRIDGE) pData->buffer = kClient.getEngine().getInternalEventBuffer(kIsInput); else if (pData->processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! kIsInput) carla_zeroStructs(pData->buffer, kMaxEngineEventInternalCount); - - for (LinkedList::Itenerator it = pData->cvs.begin2(); it.valid(); it.next()) - { - // TODO append events to buffer - } } uint32_t CarlaEngineEventPort::getEventCount() const noexcept diff --git a/source/backend/plugin/CarlaPluginLADSPADSSI.cpp b/source/backend/plugin/CarlaPluginLADSPADSSI.cpp index e96b2451f..cc3921bef 100644 --- a/source/backend/plugin/CarlaPluginLADSPADSSI.cpp +++ b/source/backend/plugin/CarlaPluginLADSPADSSI.cpp @@ -1238,6 +1238,7 @@ public: // Parameter as CV CarlaEngineCVPort* const cvPort = (CarlaEngineCVPort*)pData->client->addPort(kEnginePortTypeCV, portName, true, i); + cvPort->setRange(pData->param.ranges[i].min, pData->param.ranges[i].max); pData->event.portIn->addCVSource(cvPort); } } @@ -1494,7 +1495,9 @@ public: } } - void process(const float** const audioIn, float** const audioOut, const float** const, float** const, const uint32_t frames) override + void process(const float** const audioIn, float** const audioOut, + const float** const cvIn, float** const, + const uint32_t frames) override { // -------------------------------------------------------------------------------------------------------- // Check if active @@ -1574,6 +1577,19 @@ public: } // End of MIDI Input (External) + // ---------------------------------------------------------------------------------------------------- + // CV Control Input + + for (uint32_t i=0, j=0; i < pData->param.count; ++i) + { + if (pData->param.data[i].type != PARAMETER_INPUT) + continue; + + const uint32_t cvIndex = j++; + + pData->event.portIn->mixWithCvBuffer(cvIn[cvIndex], frames, i); + } + // ---------------------------------------------------------------------------------------------------- // Event Input (System) @@ -1636,12 +1652,37 @@ public: break; case kEngineControlEventTypeParameter: { + float value; + #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH + // via CV + if (event.channel == 0xFF) + { + const uint32_t k = ctrlEvent.param; + CARLA_SAFE_ASSERT_CONTINUE(k < pData->param.count); + + if (pData->param.data[k].hints & PARAMETER_IS_BOOLEAN) + { + value = (ctrlEvent.value < 0.5f) ? pData->param.ranges[k].min : pData->param.ranges[k].max; + } + else + { + if (pData->param.data[k].hints & PARAMETER_IS_LOGARITHMIC) + value = pData->param.ranges[k].getUnnormalizedLogValue(ctrlEvent.value); + else + value = pData->param.ranges[k].getUnnormalizedValue(ctrlEvent.value); + + if (pData->param.data[k].hints & PARAMETER_IS_INTEGER) + value = std::rint(value); + } + + setParameterValueRT(k, value, true); + continue; + } + // Control backend stuff if (event.channel == pData->ctrlChannel) { - float value; - if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0) { value = ctrlEvent.value; @@ -1692,8 +1733,6 @@ public: if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMABLE) == 0) continue; - float value; - if (pData->param.data[k].hints & PARAMETER_IS_BOOLEAN) { value = (ctrlEvent.value < 0.5f) ? pData->param.ranges[k].min : pData->param.ranges[k].max;