1
Fork 0
cardinal/plugins/Cardinal/src/Carla.cpp

762 lines
27 KiB
C++

/*
* DISTRHO Cardinal Plugin
* Copyright (C) 2021-2022 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
* published by the Free Software Foundation; either version 3 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 LICENSE file.
*/
#include "plugincontext.hpp"
#include "Expander.hpp"
#include "ModuleWidgets.hpp"
#include "CarlaNativePlugin.h"
#include "CarlaBackendUtils.hpp"
#include "CarlaEngine.hpp"
#include "water/streams/MemoryOutputStream.h"
#include "water/xml/XmlDocument.h"
#ifndef CARDINAL_SYSDEPS
// private method that takes ownership, we can use it to avoid superfulous allocations
extern "C" {
json_t *jsonp_stringn_nocheck_own(const char* value, size_t len);
}
#endif
#define BUFFER_SIZE 128
// generates a warning if this is defined as anything else
#define CARLA_API
// --------------------------------------------------------------------------------------------------------------------
using namespace CARLA_BACKEND_NAMESPACE;
static uint32_t host_get_buffer_size(NativeHostHandle);
static double host_get_sample_rate(NativeHostHandle);
static bool host_is_offline(NativeHostHandle);
static const NativeTimeInfo* host_get_time_info(NativeHostHandle handle);
static bool host_write_midi_event(NativeHostHandle handle, const NativeMidiEvent* event);
static void host_ui_parameter_changed(NativeHostHandle handle, uint32_t index, float value);
static void host_ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program);
static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value);
static void host_ui_closed(NativeHostHandle handle);
static const char* host_ui_open_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter);
static const char* host_ui_save_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter);
static intptr_t host_dispatcher(NativeHostHandle handle, NativeHostDispatcherOpcode opcode, int32_t index, intptr_t value, void* ptr, float opt);
// --------------------------------------------------------------------------------------------------------------------
struct CarlaModule : Module {
enum ParamIds {
BIPOLAR_INPUTS,
BIPOLAR_OUTPUTS,
NUM_PARAMS
};
enum InputIds {
AUDIO_INPUT1,
AUDIO_INPUT2,
CV_INPUT1,
NUM_INPUTS = CV_INPUT1 + 8
};
enum OutputIds {
AUDIO_OUTPUT1,
AUDIO_OUTPUT2,
CV_OUTPUT1,
NUM_OUTPUTS = CV_OUTPUT1 + 8
};
enum LightIds {
NUM_LIGHTS
};
const CardinalPluginContext* const pcontext;
const NativePluginDescriptor* fCarlaPluginDescriptor = nullptr;
NativePluginHandle fCarlaPluginHandle = nullptr;
NativeHostDescriptor fCarlaHostDescriptor = {};
CarlaHostHandle fCarlaHostHandle = nullptr;
NativeTimeInfo fCarlaTimeInfo;
void* fUI = nullptr;
float dataIn[NUM_INPUTS][BUFFER_SIZE];
float dataOut[NUM_OUTPUTS][BUFFER_SIZE];
float* dataInPtr[NUM_INPUTS];
float* dataOutPtr[NUM_OUTPUTS];
unsigned audioDataFill = 0;
int64_t lastBlockFrame = -1;
CardinalExpanderFromCarlaMIDIToCV* midiOutExpander = nullptr;
std::string patchStorage;
#ifdef CARLA_OS_WIN
// must keep string pointer valid
std::string winResourceDir;
#endif
CarlaModule()
: pcontext(static_cast<CardinalPluginContext*>(APP))
{
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
configParam<SwitchQuantity>(BIPOLAR_INPUTS, 0.f, 1.f, 1.f, "Bipolar CV Inputs")->randomizeEnabled = false;
configParam<SwitchQuantity>(BIPOLAR_OUTPUTS, 0.f, 1.f, 1.f, "Bipolar CV Outputs")->randomizeEnabled = false;
for (uint i=0; i<NUM_INPUTS; ++i)
dataInPtr[i] = dataIn[i];
for (uint i=0; i<NUM_OUTPUTS; ++i)
dataOutPtr[i] = dataOut[i];
for (uint i=0; i<2; ++i)
{
const char name[] = { 'A','u','d','i','o',' ','#',static_cast<char>('0'+i+1),'\0' };
configInput(i, name);
configOutput(i, name);
}
for (uint i=2; i<NUM_INPUTS; ++i)
{
const char name[] = { 'C','V',' ','#',static_cast<char>('0'+i-1),'\0' };
configInput(i, name);
configOutput(i, name);
}
const char* binaryDir = nullptr;
const char* resourceDir = nullptr;
#if defined(CARLA_OS_MAC)
if (system::exists("~/Applications/Carla.app"))
{
binaryDir = "~/Applications/Carla.app/Contents/MacOS";
resourceDir = "~/Applications/Carla.app/Contents/MacOS/resources";
}
else if (system::exists("/Applications/Carla.app"))
{
binaryDir = "/Applications/Carla.app/Contents/MacOS";
resourceDir = "/Applications/Carla.app/Contents/MacOS/resources";
}
#elif defined(CARLA_OS_WIN)
const std::string winBinaryDir = system::join(asset::systemDir, "Carla");
if (system::exists(winBinaryDir))
{
winResourceDir = system::join(winBinaryDir, "resources");
binaryDir = winBinaryDir.c_str();
resourceDir = winResourceDir.c_str();
}
#else
if (system::exists("/usr/local/lib/carla"))
{
binaryDir = "/usr/local/lib/carla";
resourceDir = "/usr/local/share/carla/resources";
}
else if (system::exists("/usr/lib/carla"))
{
binaryDir = "/usr/lib/carla";
resourceDir = "/usr/share/carla/resources";
}
#endif
if (binaryDir == nullptr)
{
static bool warningShown = false;
if (! warningShown)
{
warningShown = true;
async_dialog_message("Carla is not installed on this system, the Carla module will do nothing");
}
return;
}
std::memset(dataOut, 0, sizeof(dataOut));
fCarlaPluginDescriptor = carla_get_native_patchbay_cv8_plugin();
DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginDescriptor != nullptr,);
memset(&fCarlaHostDescriptor, 0, sizeof(fCarlaHostDescriptor));
memset(&fCarlaTimeInfo, 0, sizeof(fCarlaTimeInfo));
fCarlaHostDescriptor.handle = this;
fCarlaHostDescriptor.resourceDir = resourceDir;
fCarlaHostDescriptor.uiName = "Carla";
fCarlaHostDescriptor.uiParentId = 0;
fCarlaHostDescriptor.get_buffer_size = host_get_buffer_size;
fCarlaHostDescriptor.get_sample_rate = host_get_sample_rate;
fCarlaHostDescriptor.is_offline = host_is_offline;
fCarlaHostDescriptor.get_time_info = host_get_time_info;
fCarlaHostDescriptor.write_midi_event = host_write_midi_event;
fCarlaHostDescriptor.ui_parameter_changed = host_ui_parameter_changed;
fCarlaHostDescriptor.ui_midi_program_changed = host_ui_midi_program_changed;
fCarlaHostDescriptor.ui_custom_data_changed = host_ui_custom_data_changed;
fCarlaHostDescriptor.ui_closed = host_ui_closed;
fCarlaHostDescriptor.ui_open_file = host_ui_open_file;
fCarlaHostDescriptor.ui_save_file = host_ui_save_file;
fCarlaHostDescriptor.dispatcher = host_dispatcher;
fCarlaPluginHandle = fCarlaPluginDescriptor->instantiate(&fCarlaHostDescriptor);
DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginHandle != nullptr,);
fCarlaHostHandle = carla_create_native_plugin_host_handle(fCarlaPluginDescriptor, fCarlaPluginHandle);
DISTRHO_SAFE_ASSERT_RETURN(fCarlaHostHandle != nullptr,);
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, binaryDir);
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, resourceDir);
fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_HOST_USES_EMBED,
0, 0, nullptr, 0.0f);
fCarlaPluginDescriptor->activate(fCarlaPluginHandle);
}
~CarlaModule() override
{
if (fCarlaPluginHandle != nullptr)
fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
if (fCarlaHostHandle != nullptr)
carla_host_handle_free(fCarlaHostHandle);
if (fCarlaPluginHandle != nullptr)
fCarlaPluginDescriptor->cleanup(fCarlaPluginHandle);
}
const NativeTimeInfo* hostGetTimeInfo() const noexcept
{
return &fCarlaTimeInfo;
}
intptr_t hostDispatcher(const NativeHostDispatcherOpcode opcode,
const int32_t index, const intptr_t value, void* const ptr, const float opt)
{
switch (opcode)
{
// cannnot be supported
case NATIVE_HOST_OPCODE_HOST_IDLE:
break;
// other stuff
case NATIVE_HOST_OPCODE_NULL:
case NATIVE_HOST_OPCODE_UPDATE_PARAMETER:
case NATIVE_HOST_OPCODE_UPDATE_MIDI_PROGRAM:
case NATIVE_HOST_OPCODE_RELOAD_PARAMETERS:
case NATIVE_HOST_OPCODE_RELOAD_MIDI_PROGRAMS:
case NATIVE_HOST_OPCODE_RELOAD_ALL:
case NATIVE_HOST_OPCODE_UI_UNAVAILABLE:
case NATIVE_HOST_OPCODE_INTERNAL_PLUGIN:
case NATIVE_HOST_OPCODE_QUEUE_INLINE_DISPLAY:
case NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER:
case NATIVE_HOST_OPCODE_REQUEST_IDLE:
case NATIVE_HOST_OPCODE_UI_RESIZE:
case NATIVE_HOST_OPCODE_PREVIEW_BUFFER_DATA:
// TESTING
d_stdout("dispatcher %i, %i, %li, %p, %f", opcode, index, value, ptr, opt);
break;
case NATIVE_HOST_OPCODE_GET_FILE_PATH:
return (intptr_t)(void*)patchStorage.c_str();
break;
}
return 0;
}
json_t* dataToJson() override
{
if (fCarlaHostHandle == nullptr)
return nullptr;
CarlaEngine* const engine = carla_get_engine_from_handle(fCarlaHostHandle);
water::MemoryOutputStream projectState;
engine->saveProjectInternal(projectState);
const size_t dataSize = projectState.getDataSize();
#ifndef CARDINAL_SYSDEPS
return jsonp_stringn_nocheck_own(static_cast<const char*>(projectState.getDataAndRelease()), dataSize);
#else
return json_stringn(static_cast<const char*>(projectState.getData()), dataSize);
#endif
}
void dataFromJson(json_t* const rootJ) override
{
if (fCarlaHostHandle == nullptr)
return;
const char* const projectState = json_string_value(rootJ);
DISTRHO_SAFE_ASSERT_RETURN(projectState != nullptr,);
CarlaEngine* const engine = carla_get_engine_from_handle(fCarlaHostHandle);
water::XmlDocument xml(projectState);
engine->loadProjectInternal(xml, true);
}
void onAdd(const AddEvent&) override
{
patchStorage = getPatchStorageDirectory();
}
void process(const ProcessArgs& args) override
{
if (fCarlaPluginHandle == nullptr)
return;
const float inputOffset = params[BIPOLAR_INPUTS].getValue() > 0.1f ? -5.0f : 0.0f;
const float outputOffset = params[BIPOLAR_OUTPUTS].getValue() > 0.1f ? -5.0f : 0.0f;
const unsigned k = audioDataFill++;
for (uint i=0; i<2; ++i)
dataIn[i][k] = inputs[i].getVoltage() * 0.1f;
for (uint i=2; i<NUM_INPUTS; ++i)
dataIn[i][k] = inputs[i].getVoltage() + inputOffset;
for (uint i=0; i<2; ++i)
outputs[i].setVoltage(dataOut[i][k] * 10.0f);
for (uint i=2; i<NUM_OUTPUTS; ++i)
outputs[i].setVoltage(dataOut[i][k] + outputOffset);
if (audioDataFill == BUFFER_SIZE)
{
const int64_t blockFrame = pcontext->engine->getBlockFrame();
// Update time position if running a new audio block
if (lastBlockFrame != blockFrame)
{
lastBlockFrame = blockFrame;
fCarlaTimeInfo.playing = pcontext->playing;
fCarlaTimeInfo.frame = pcontext->frame;
fCarlaTimeInfo.bbt.valid = pcontext->bbtValid;
fCarlaTimeInfo.bbt.bar = pcontext->bar;
fCarlaTimeInfo.bbt.beat = pcontext->beat;
fCarlaTimeInfo.bbt.tick = pcontext->tick;
fCarlaTimeInfo.bbt.barStartTick = pcontext->barStartTick;
fCarlaTimeInfo.bbt.beatsPerBar = pcontext->beatsPerBar;
fCarlaTimeInfo.bbt.beatType = pcontext->beatType;
fCarlaTimeInfo.bbt.ticksPerBeat = pcontext->ticksPerBeat;
fCarlaTimeInfo.bbt.beatsPerMinute = pcontext->beatsPerMinute;
}
// or advance time by BUFFER_SIZE frames if still under the same audio block
else if (fCarlaTimeInfo.playing)
{
fCarlaTimeInfo.frame += BUFFER_SIZE;
// adjust BBT as well
if (fCarlaTimeInfo.bbt.valid)
{
const double samplesPerTick = 60.0 * args.sampleRate
/ fCarlaTimeInfo.bbt.beatsPerMinute
/ fCarlaTimeInfo.bbt.ticksPerBeat;
int32_t newBar = fCarlaTimeInfo.bbt.bar;
int32_t newBeat = fCarlaTimeInfo.bbt.beat;
double newTick = fCarlaTimeInfo.bbt.tick + (double)BUFFER_SIZE / samplesPerTick;
while (newTick >= fCarlaTimeInfo.bbt.ticksPerBeat)
{
newTick -= fCarlaTimeInfo.bbt.ticksPerBeat;
if (++newBeat > fCarlaTimeInfo.bbt.beatsPerBar)
{
newBeat = 1;
++newBar;
fCarlaTimeInfo.bbt.barStartTick += fCarlaTimeInfo.bbt.beatsPerBar * fCarlaTimeInfo.bbt.ticksPerBeat;
}
}
fCarlaTimeInfo.bbt.bar = newBar;
fCarlaTimeInfo.bbt.beat = newBeat;
fCarlaTimeInfo.bbt.tick = newTick;
}
}
NativeMidiEvent* midiEvents;
uint midiEventCount;
if (CardinalExpanderFromCVToCarlaMIDI* const midiInExpander = leftExpander.module != nullptr && leftExpander.module->model == modelExpanderInputMIDI
? static_cast<CardinalExpanderFromCVToCarlaMIDI*>(leftExpander.module)
: nullptr)
{
midiEvents = midiInExpander->midiEvents;
midiEventCount = midiInExpander->midiEventCount;
midiInExpander->midiEventCount = midiInExpander->frame = 0;
}
else
{
midiEvents = nullptr;
midiEventCount = 0;
}
if ((midiOutExpander = rightExpander.module != nullptr && rightExpander.module->model == modelExpanderOutputMIDI
? static_cast<CardinalExpanderFromCarlaMIDIToCV*>(rightExpander.module)
: nullptr))
midiOutExpander->midiEventCount = 0;
audioDataFill = 0;
fCarlaPluginDescriptor->process(fCarlaPluginHandle, dataInPtr, dataOutPtr, BUFFER_SIZE, midiEvents, midiEventCount);
}
}
void onReset() override
{
midiOutExpander = nullptr;
}
void onSampleRateChange(const SampleRateChangeEvent& e) override
{
if (fCarlaPluginHandle == nullptr)
return;
midiOutExpander = nullptr;
fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED,
0, 0, nullptr, e.sampleRate);
fCarlaPluginDescriptor->activate(fCarlaPluginHandle);
}
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaModule)
};
static_assert((int)CarlaModule::NUM_INPUTS == (int)CarlaModule::NUM_OUTPUTS, "inputs must match outputs");
// -----------------------------------------------------------------------------------------------------------
static uint32_t host_get_buffer_size(const NativeHostHandle handle)
{
return BUFFER_SIZE;
}
static double host_get_sample_rate(const NativeHostHandle handle)
{
const CardinalPluginContext* const pcontext = static_cast<CarlaModule*>(handle)->pcontext;
DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr, 48000.0);
return pcontext->sampleRate;
}
static bool host_is_offline(NativeHostHandle)
{
return false;
}
static const NativeTimeInfo* host_get_time_info(const NativeHostHandle handle)
{
return static_cast<CarlaModule*>(handle)->hostGetTimeInfo();
}
static bool host_write_midi_event(const NativeHostHandle handle, const NativeMidiEvent* const event)
{
if (CardinalExpanderFromCarlaMIDIToCV* const expander = static_cast<CarlaModule*>(handle)->midiOutExpander)
{
if (expander->midiEventCount == CardinalExpanderFromCarlaMIDIToCV::MAX_MIDI_EVENTS)
return false;
NativeMidiEvent& expanderEvent(expander->midiEvents[expander->midiEventCount++]);
carla_copyStruct(expanderEvent, *event);
return true;
}
return false;
}
static void host_ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program)
{
d_stdout("%s %p %u %u %u", __FUNCTION__, handle, channel, bank, program);
}
static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value)
{
d_stdout("%s %p %s %s", __FUNCTION__, handle, key, value);
}
static const char* host_ui_save_file(NativeHostHandle, bool, const char*, const char*)
{
return nullptr;
}
static intptr_t host_dispatcher(const NativeHostHandle handle, const NativeHostDispatcherOpcode opcode,
const int32_t index, const intptr_t value, void* const ptr, const float opt)
{
return static_cast<CarlaModule*>(handle)->hostDispatcher(opcode, index, value, ptr, opt);
}
// --------------------------------------------------------------------------------------------------------------------
#ifndef HEADLESS
struct CarlaModuleWidget : ModuleWidgetWith9HP, IdleCallback {
CarlaModule* const module;
bool hasLeftSideExpander = false;
bool hasRightSideExpander = false;
bool idleCallbackActive = false;
bool visible = false;
CarlaModuleWidget(CarlaModule* const m)
: module(m)
{
setModule(module);
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Carla.svg")));
createAndAddScrews();
for (uint i=0; i<CarlaModule::NUM_INPUTS; ++i)
createAndAddInput(i);
for (uint i=0; i<CarlaModule::NUM_OUTPUTS; ++i)
createAndAddOutput(i);
}
~CarlaModuleWidget() override
{
if (module != nullptr && module->fCarlaHostHandle != nullptr)
{
module->fUI = nullptr;
if (visible)
module->fCarlaPluginDescriptor->ui_show(module->fCarlaPluginHandle, false);
module->fCarlaHostDescriptor.uiParentId = 0;
carla_set_engine_option(module->fCarlaHostHandle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0");
}
}
void onContextCreate(const ContextCreateEvent& e) override
{
ModuleWidget::onContextCreate(e);
widgetCreated();
}
void onContextDestroy(const ContextDestroyEvent& e) override
{
widgetDestroyed();
ModuleWidget::onContextDestroy(e);
}
void onAdd(const AddEvent& e) override
{
ModuleWidget::onAdd(e);
widgetCreated();
}
void onRemove(const RemoveEvent& e) override
{
widgetDestroyed();
ModuleWidget::onRemove(e);
}
void widgetCreated()
{
if (module == nullptr || module->pcontext == nullptr || module->fCarlaHostHandle == nullptr)
return;
const CarlaHostHandle handle = module->fCarlaHostHandle;
const CardinalPluginContext* const pcontext = module->pcontext;
char winIdStr[24];
std::snprintf(winIdStr, sizeof(winIdStr), "%llx", (ulonglong)pcontext->nativeWindowId);
module->fCarlaHostDescriptor.uiParentId = pcontext->nativeWindowId;
carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, winIdStr);
if (pcontext->window != nullptr)
carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_UI_SCALE, pcontext->window->pixelRatio*1000, nullptr);
if (! idleCallbackActive)
idleCallbackActive = pcontext->addIdleCallback(this);
module->fUI = this;
}
void widgetDestroyed()
{
if (module == nullptr || module->pcontext == nullptr || module->fCarlaHostHandle == nullptr)
return;
const CarlaHostHandle handle = module->fCarlaHostHandle;
const CardinalPluginContext* const pcontext = module->pcontext;
module->fUI = nullptr;
if (visible)
{
visible = false;
module->fCarlaPluginDescriptor->ui_show(module->fCarlaPluginHandle, false);
}
if (idleCallbackActive)
{
idleCallbackActive = false;
pcontext->removeIdleCallback(this);
}
module->fCarlaHostDescriptor.uiParentId = 0;
carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0");
}
void idleCallback() override
{
if (module != nullptr && module->fCarlaHostHandle != nullptr && visible)
module->fCarlaPluginDescriptor->ui_idle(module->fCarlaPluginHandle);
}
void draw(const DrawArgs& args) override
{
drawBackground(args.vg);
if (hasLeftSideExpander)
{
nvgBeginPath(args.vg);
nvgRect(args.vg, 1, 90 - 19, 18, 49 * 6 - 4);
nvgFillPaint(args.vg, nvgLinearGradient(args.vg, 0, 0, 18, 0, nvgRGB(0xd0, 0xd0, 0xd0), nvgRGBA(0xd0, 0xd0, 0xd0, 0)));
nvgFill(args.vg);
for (int i=1; i<6; ++i)
{
const float y = 90 + 49 * i - 23;
const int col1 = 0x18 + static_cast<int>((y / box.size.y) * (0x21 - 0x18) + 0.5f);
const int col2 = 0x19 + static_cast<int>((y / box.size.y) * (0x22 - 0x19) + 0.5f);
nvgBeginPath(args.vg);
nvgRect(args.vg, 1, y, 18, 4);
nvgFillColor(args.vg, nvgRGB(col1, col2, col2));
nvgFill(args.vg);
}
}
if (hasRightSideExpander)
{
nvgFillColor(args.vg, nvgRGB(0xd0, 0xd0, 0xd0));
for (int i=0; i<6; ++i)
{
const float y = 90 + 49 * i - 19;
nvgBeginPath(args.vg);
nvgRect(args.vg, box.size.x - 19, y, 18, 49 - 4);
nvgFill(args.vg);
}
}
drawOutputJacksArea(args.vg, CarlaModule::NUM_INPUTS);
setupTextLines(args.vg);
drawTextLine(args.vg, 0, "Audio 1");
drawTextLine(args.vg, 1, "Audio 2");
drawTextLine(args.vg, 2, "CV 1");
drawTextLine(args.vg, 3, "CV 2");
drawTextLine(args.vg, 4, "CV 3");
drawTextLine(args.vg, 5, "CV 4");
drawTextLine(args.vg, 6, "CV 5");
drawTextLine(args.vg, 7, "CV 6");
drawTextLine(args.vg, 8, "CV 7");
drawTextLine(args.vg, 9, "CV 8");
ModuleWidgetWith9HP::draw(args);
}
void step() override
{
hasLeftSideExpander = module != nullptr
&& module->leftExpander.module != nullptr
&& module->leftExpander.module->model == modelExpanderInputMIDI;
hasRightSideExpander = module != nullptr
&& module->rightExpander.module != nullptr
&& module->rightExpander.module->model == modelExpanderOutputMIDI;
ModuleWidgetWith9HP::step();
}
void showUI()
{
visible = true;
module->fCarlaPluginDescriptor->ui_show(module->fCarlaPluginHandle, true);
}
void appendContextMenu(ui::Menu* const menu) override
{
if (module == nullptr || module->pcontext == nullptr || module->fCarlaHostHandle == nullptr)
return;
menu->addChild(new ui::MenuSeparator);
menu->addChild(createCheckMenuItem(visible ? "Bring GUI to Front" : "Show GUI", "",
[=]() {return visible;},
[=]() {showUI();}
));
menu->addChild(createCheckMenuItem("Bipolar CV Inputs", "",
[=]() {return module->params[CarlaModule::BIPOLAR_INPUTS].getValue() > 0.1f;},
[=]() {module->params[CarlaModule::BIPOLAR_INPUTS].setValue(1.0f - module->params[CarlaModule::BIPOLAR_INPUTS].getValue());}
));
menu->addChild(createCheckMenuItem("Bipolar CV Outputs", "",
[=]() {return module->params[CarlaModule::BIPOLAR_OUTPUTS].getValue() > 0.1f;},
[=]() {module->params[CarlaModule::BIPOLAR_OUTPUTS].setValue(1.0f - module->params[CarlaModule::BIPOLAR_OUTPUTS].getValue());}
));
}
void onDoubleClick(const DoubleClickEvent& e) override
{
if (module == nullptr || module->pcontext == nullptr || module->fCarlaHostHandle == nullptr)
return;
e.consume(this);
showUI();
}
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaModuleWidget)
};
static void host_ui_closed(NativeHostHandle handle)
{
if (CarlaModuleWidget* const ui = static_cast<CarlaModuleWidget*>(static_cast<CarlaModule*>(handle)->fUI))
ui->visible = false;
}
#else
static void host_ui_closed(NativeHostHandle) {}
struct CarlaModuleWidget : ModuleWidget {
CarlaModuleWidget(CarlaModule* const module) {
setModule(module);
for (uint i=0; i<CarlaModule::NUM_INPUTS; ++i)
addInput(createInput<PJ301MPort>({}, module, i));
for (uint i=0; i<CarlaModule::NUM_OUTPUTS; ++i)
addOutput(createOutput<PJ301MPort>({}, module, i));
}
};
#endif
// --------------------------------------------------------------------------------------------------------------------
static void host_ui_parameter_changed(const NativeHostHandle handle, const uint32_t index, const float value)
{
// if (CarlaWidget* const ui = static_cast<CarlaWidget*>(static_cast<CarlaModule*>(handle)->fUI))
// ui->changeParameterFromDSP(index, value);
}
static const char* host_ui_open_file(const NativeHostHandle handle,
const bool isDir, const char* const title, const char* const filter)
{
// if (CarlaWidget* const ui = static_cast<CarlaWidget*>(static_cast<CarlaModule*>(handle)->fUI))
// ui->openFileFromDSP(isDir, title, filter);
return nullptr;
}
// --------------------------------------------------------------------------------------------------------------------
Model* modelCarla = createModel<CarlaModule, CarlaModuleWidget>("Carla");
// --------------------------------------------------------------------------------------------------------------------