1744 lines
58 KiB
C++
1744 lines
58 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.
|
|
*/
|
|
|
|
/**
|
|
* This file uses code adapted from VCVRack's CV_MIDI.cpp and midi.hpp
|
|
* Copyright (C) 2016-2021 VCV.
|
|
*
|
|
* 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 (at your option) any later version.
|
|
*/
|
|
|
|
#include "plugincontext.hpp"
|
|
#include "Expander.hpp"
|
|
|
|
#ifndef HEADLESS
|
|
# include "ImGuiWidget.hpp"
|
|
# include "ModuleWidgets.hpp"
|
|
# include "extra/FileBrowserDialog.hpp"
|
|
# include "extra/ScopedPointer.hpp"
|
|
# include "extra/Thread.hpp"
|
|
# include "../../src/extra/SharedResourcePointer.hpp"
|
|
#else
|
|
# include "extra/Mutex.hpp"
|
|
#endif
|
|
|
|
#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
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
// strcasestr
|
|
|
|
#ifdef DISTRHO_OS_WINDOWS
|
|
# include <shlwapi.h>
|
|
namespace ildaeil {
|
|
inline const char* strcasestr(const char* const haystack, const char* const needle)
|
|
{
|
|
return StrStrIA(haystack, needle);
|
|
}
|
|
// using strcasestr = StrStrIA;
|
|
}
|
|
#else
|
|
namespace ildaeil {
|
|
using ::strcasestr;
|
|
}
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
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);
|
|
static void projectLoadedFromDSP(void* ui);
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
static Mutex sPluginInfoLoadMutex;
|
|
|
|
#ifndef HEADLESS
|
|
struct JuceInitializer {
|
|
JuceInitializer() { carla_juce_init(); }
|
|
~JuceInitializer() { carla_juce_cleanup(); }
|
|
};
|
|
#endif
|
|
|
|
struct IldaeilModule : Module {
|
|
enum ParamIds {
|
|
NUM_PARAMS
|
|
};
|
|
enum InputIds {
|
|
INPUT1,
|
|
INPUT2,
|
|
NUM_INPUTS
|
|
};
|
|
enum OutputIds {
|
|
OUTPUT1,
|
|
OUTPUT2,
|
|
NUM_OUTPUTS
|
|
};
|
|
enum LightIds {
|
|
NUM_LIGHTS
|
|
};
|
|
|
|
#ifndef HEADLESS
|
|
SharedResourcePointer<JuceInitializer> juceInitializer;
|
|
#endif
|
|
|
|
const CardinalPluginContext* const pcontext;
|
|
|
|
const NativePluginDescriptor* fCarlaPluginDescriptor = nullptr;
|
|
NativePluginHandle fCarlaPluginHandle = nullptr;
|
|
|
|
NativeHostDescriptor fCarlaHostDescriptor = {};
|
|
CarlaHostHandle fCarlaHostHandle = nullptr;
|
|
|
|
NativeTimeInfo fCarlaTimeInfo;
|
|
|
|
void* fUI = nullptr;
|
|
bool canUseBridges = true;
|
|
|
|
float audioDataIn1[BUFFER_SIZE];
|
|
float audioDataIn2[BUFFER_SIZE];
|
|
float audioDataOut1[BUFFER_SIZE];
|
|
float audioDataOut2[BUFFER_SIZE];
|
|
unsigned audioDataFill = 0;
|
|
int64_t lastBlockFrame = -1;
|
|
CardinalExpanderFromCarlaMIDIToCV* midiOutExpander = nullptr;
|
|
|
|
volatile bool resetMeterIn = true;
|
|
volatile bool resetMeterOut = true;
|
|
float meterInL = 0.0f;
|
|
float meterInR = 0.0f;
|
|
float meterOutL = 0.0f;
|
|
float meterOutR = 0.0f;
|
|
|
|
IldaeilModule()
|
|
: pcontext(static_cast<CardinalPluginContext*>(APP))
|
|
{
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
|
|
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);
|
|
}
|
|
std::memset(audioDataOut1, 0, sizeof(audioDataOut1));
|
|
std::memset(audioDataOut2, 0, sizeof(audioDataOut2));
|
|
|
|
fCarlaPluginDescriptor = carla_get_native_rack_plugin();
|
|
DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginDescriptor != nullptr,);
|
|
|
|
memset(&fCarlaHostDescriptor, 0, sizeof(fCarlaHostDescriptor));
|
|
memset(&fCarlaTimeInfo, 0, sizeof(fCarlaTimeInfo));
|
|
|
|
fCarlaHostDescriptor.handle = this;
|
|
fCarlaHostDescriptor.resourceDir = carla_get_library_folder();
|
|
fCarlaHostDescriptor.uiName = "Ildaeil";
|
|
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,);
|
|
|
|
#if defined(CARLA_OS_MAC)
|
|
if (system::exists("~/Applications/Carla.app"))
|
|
{
|
|
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "~/Applications/Carla.app/Contents/MacOS");
|
|
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "~/Applications/Carla.app/Contents/MacOS/resources");
|
|
}
|
|
else if (system::exists("/Applications/Carla.app"))
|
|
{
|
|
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/Applications/Carla.app/Contents/MacOS");
|
|
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/Applications/Carla.app/Contents/MacOS/resources");
|
|
}
|
|
#elif defined(CARLA_OS_WIN)
|
|
const std::string winBinaryDir = system::join(asset::systemDir, "Carla");
|
|
|
|
if (system::exists(winBinaryDir))
|
|
{
|
|
const std::string winResourceDir = system::join(winBinaryDir, "resources");
|
|
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, winBinaryDir.c_str());
|
|
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, winResourceDir.c_str());
|
|
}
|
|
#else
|
|
if (system::exists("/usr/local/lib/carla"))
|
|
{
|
|
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/usr/local/lib/carla");
|
|
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/usr/local/share/carla/resources");
|
|
}
|
|
else if (system::exists("/usr/lib/carla"))
|
|
{
|
|
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/usr/lib/carla");
|
|
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/usr/share/carla/resources");
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
canUseBridges = false;
|
|
|
|
static bool warningShown = false;
|
|
if (! warningShown)
|
|
{
|
|
warningShown = true;
|
|
async_dialog_message("Carla is not installed on this system, bridged plugins will not work");
|
|
}
|
|
}
|
|
|
|
if (const char* const path = std::getenv("LV2_PATH"))
|
|
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LV2, path);
|
|
|
|
#ifdef CARLA_OS_MAC
|
|
carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PREFER_UI_BRIDGES, 0, nullptr);
|
|
#endif
|
|
|
|
fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_HOST_USES_EMBED,
|
|
0, 0, nullptr, 0.0f);
|
|
|
|
fCarlaPluginDescriptor->activate(fCarlaPluginHandle);
|
|
}
|
|
|
|
~IldaeilModule() 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_GET_FILE_PATH:
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
{
|
|
const MutexLocker cml(sPluginInfoLoadMutex);
|
|
engine->loadProjectInternal(xml, true);
|
|
}
|
|
|
|
projectLoadedFromDSP(fUI);
|
|
}
|
|
|
|
void process(const ProcessArgs& args) override
|
|
{
|
|
if (fCarlaPluginHandle == nullptr)
|
|
return;
|
|
|
|
const unsigned i = audioDataFill++;
|
|
|
|
audioDataIn1[i] = inputs[INPUT1].getVoltage() * 0.1f;
|
|
audioDataIn2[i] = inputs[INPUT2].getVoltage() * 0.1f;
|
|
outputs[OUTPUT1].setVoltage(audioDataOut1[i] * 10.0f);
|
|
outputs[OUTPUT2].setVoltage(audioDataOut2[i] * 10.0f);
|
|
|
|
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;
|
|
float* ins[2] = { audioDataIn1, audioDataIn2 };
|
|
float* outs[2] = { audioDataOut1, audioDataOut2 };
|
|
|
|
if (resetMeterIn)
|
|
meterInL = meterInR = 0.0f;
|
|
|
|
meterInL = std::max(meterInL, d_findMaxNormalizedFloat(audioDataIn1, BUFFER_SIZE));
|
|
meterInR = std::max(meterInR, d_findMaxNormalizedFloat(audioDataIn2, BUFFER_SIZE));
|
|
|
|
fCarlaPluginDescriptor->process(fCarlaPluginHandle, ins, outs, BUFFER_SIZE, midiEvents, midiEventCount);
|
|
|
|
if (resetMeterOut)
|
|
meterOutL = meterOutR = 0.0f;
|
|
|
|
meterOutL = std::max(meterOutL, d_findMaxNormalizedFloat(audioDataOut1, BUFFER_SIZE));
|
|
meterOutR = std::max(meterOutR, d_findMaxNormalizedFloat(audioDataOut2, BUFFER_SIZE));
|
|
|
|
resetMeterIn = resetMeterOut = false;
|
|
}
|
|
}
|
|
|
|
void onReset() override
|
|
{
|
|
resetMeterIn = resetMeterOut = true;
|
|
midiOutExpander = nullptr;
|
|
}
|
|
|
|
void onSampleRateChange(const SampleRateChangeEvent& e) override
|
|
{
|
|
if (fCarlaPluginHandle == nullptr)
|
|
return;
|
|
|
|
resetMeterIn = resetMeterOut = true;
|
|
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(IldaeilModule)
|
|
};
|
|
|
|
// -----------------------------------------------------------------------------------------------------------
|
|
|
|
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<IldaeilModule*>(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<IldaeilModule*>(handle)->hostGetTimeInfo();
|
|
}
|
|
|
|
static bool host_write_midi_event(const NativeHostHandle handle, const NativeMidiEvent* const event)
|
|
{
|
|
if (CardinalExpanderFromCarlaMIDIToCV* const expander = static_cast<IldaeilModule*>(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 void host_ui_closed(NativeHostHandle handle)
|
|
{
|
|
d_stdout("%s %p", __FUNCTION__, handle);
|
|
}
|
|
|
|
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<IldaeilModule*>(handle)->hostDispatcher(opcode, index, value, ptr, opt);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
#ifndef HEADLESS
|
|
struct IldaeilWidget : ImGuiWidget, IdleCallback, Thread {
|
|
static constexpr const uint kButtonHeight = 20;
|
|
|
|
struct PluginInfoCache {
|
|
char* name;
|
|
char* label;
|
|
|
|
PluginInfoCache()
|
|
: name(nullptr),
|
|
label(nullptr) {}
|
|
|
|
~PluginInfoCache()
|
|
{
|
|
std::free(name);
|
|
std::free(label);
|
|
}
|
|
};
|
|
|
|
struct PluginGenericUI {
|
|
char* title;
|
|
uint parameterCount;
|
|
struct Parameter {
|
|
char* name;
|
|
char* printformat;
|
|
uint32_t rindex;
|
|
bool boolean, bvalue, log, readonly;
|
|
float min, max, power;
|
|
Parameter()
|
|
: name(nullptr),
|
|
printformat(nullptr),
|
|
rindex(0),
|
|
boolean(false),
|
|
bvalue(false),
|
|
log(false),
|
|
readonly(false),
|
|
min(0.0f),
|
|
max(1.0f) {}
|
|
~Parameter()
|
|
{
|
|
std::free(name);
|
|
std::free(printformat);
|
|
}
|
|
}* parameters;
|
|
float* values;
|
|
|
|
PluginGenericUI()
|
|
: title(nullptr),
|
|
parameterCount(0),
|
|
parameters(nullptr),
|
|
values(nullptr) {}
|
|
|
|
~PluginGenericUI()
|
|
{
|
|
std::free(title);
|
|
delete[] parameters;
|
|
delete[] values;
|
|
}
|
|
};
|
|
|
|
enum {
|
|
kDrawingLoading,
|
|
kDrawingPluginError,
|
|
kDrawingPluginList,
|
|
kDrawingPluginGenericUI,
|
|
kDrawingErrorInit,
|
|
kDrawingErrorDraw
|
|
} fDrawingState = kDrawingLoading;
|
|
|
|
enum {
|
|
kIdleInit,
|
|
kIdleInitPluginAlreadyLoaded,
|
|
kIdleLoadSelectedPlugin,
|
|
kIdlePluginLoadedFromDSP,
|
|
kIdleResetPlugin,
|
|
kIdleShowCustomUI,
|
|
kIdleHidePluginUI,
|
|
kIdleGiveIdleToUI,
|
|
kIdleChangePluginType,
|
|
kIdleNothing
|
|
} fIdleState = kIdleInit;
|
|
|
|
PluginType fPluginType = PLUGIN_LV2;
|
|
PluginType fNextPluginType = fPluginType;
|
|
uint fPluginCount = 0;
|
|
int fPluginSelected = -1;
|
|
bool fPluginScanningFinished = false;
|
|
bool fPluginHasCustomUI = false;
|
|
bool fPluginHasOutputParameters = false;
|
|
bool fPluginRunning = false;
|
|
bool fPluginWillRunInBridgeMode = false;
|
|
PluginInfoCache* fPlugins = nullptr;
|
|
ScopedPointer<PluginGenericUI> fPluginGenericUI;
|
|
|
|
bool fPluginSearchActive = false;
|
|
bool fPluginSearchFirstShow = false;
|
|
char fPluginSearchString[0xff] = {};
|
|
|
|
String fPopupError;
|
|
|
|
FileBrowserHandle fileBrowserHandle = nullptr;
|
|
|
|
bool idleCallbackActive = false;
|
|
IldaeilModule* const module;
|
|
|
|
IldaeilWidget(IldaeilModule* const m)
|
|
: ImGuiWidget(),
|
|
module(m)
|
|
{
|
|
std::strcpy(fPluginSearchString, "Search...");
|
|
|
|
if (m != nullptr)
|
|
{
|
|
if (m->fCarlaHostHandle == nullptr)
|
|
{
|
|
fDrawingState = kDrawingErrorInit;
|
|
fIdleState = kIdleNothing;
|
|
fPopupError = "Ildaeil backend failed to init properly, cannot continue.";
|
|
return;
|
|
}
|
|
|
|
if (checkIfPluginIsLoaded())
|
|
fIdleState = kIdleInitPluginAlreadyLoaded;
|
|
|
|
m->fUI = this;
|
|
}
|
|
else
|
|
{
|
|
fDrawingState = kDrawingPluginList;
|
|
fIdleState = kIdleNothing;
|
|
}
|
|
}
|
|
|
|
~IldaeilWidget() override
|
|
{
|
|
if (module != nullptr && module->fCarlaHostHandle != nullptr)
|
|
{
|
|
if (idleCallbackActive)
|
|
{
|
|
module->pcontext->removeIdleCallback(this);
|
|
|
|
if (fileBrowserHandle != nullptr)
|
|
fileBrowserClose(fileBrowserHandle);
|
|
}
|
|
|
|
if (fPluginRunning)
|
|
carla_show_custom_ui(module->fCarlaHostHandle, 0, false);
|
|
|
|
carla_set_engine_option(module->fCarlaHostHandle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0");
|
|
|
|
module->fUI = nullptr;
|
|
}
|
|
|
|
if (isThreadRunning())
|
|
stopThread(-1);
|
|
|
|
fPluginGenericUI = nullptr;
|
|
|
|
delete[] fPlugins;
|
|
}
|
|
|
|
bool checkIfPluginIsLoaded()
|
|
{
|
|
const CarlaHostHandle handle = module->fCarlaHostHandle;
|
|
|
|
if (carla_get_current_plugin_count(handle) != 0)
|
|
{
|
|
const uint hints = carla_get_plugin_info(handle, 0)->hints;
|
|
|
|
fPluginRunning = true;
|
|
fPluginHasCustomUI = hints & PLUGIN_HAS_CUSTOM_UI;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void projectLoadedFromDSP()
|
|
{
|
|
if (checkIfPluginIsLoaded())
|
|
fIdleState = kIdlePluginLoadedFromDSP;
|
|
}
|
|
|
|
void changeParameterFromDSP(const uint32_t index, const float value)
|
|
{
|
|
if (PluginGenericUI* const ui = fPluginGenericUI)
|
|
{
|
|
for (uint32_t i=0; i < ui->parameterCount; ++i)
|
|
{
|
|
if (ui->parameters[i].rindex != index)
|
|
continue;
|
|
|
|
ui->values[i] = value;
|
|
|
|
if (ui->parameters[i].boolean)
|
|
ui->parameters[i].bvalue = value > ui->parameters[i].min;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
setDirty(true);
|
|
}
|
|
|
|
void openFileFromDSP(bool /* isDir */, const char* const title, const char* /* filter */)
|
|
{
|
|
DISTRHO_SAFE_ASSERT_RETURN(idleCallbackActive,);
|
|
DISTRHO_SAFE_ASSERT_RETURN(fPluginType == PLUGIN_INTERNAL || fPluginType == PLUGIN_LV2,);
|
|
|
|
const CardinalPluginContext* const pcontext = module->pcontext;
|
|
|
|
// FIXME isEmbed
|
|
FileBrowserOptions opts;
|
|
opts.title = title;
|
|
fileBrowserHandle = fileBrowserCreate(true, pcontext->nativeWindowId, pcontext->window->pixelRatio, opts);
|
|
}
|
|
|
|
void createOrUpdatePluginGenericUI(const CarlaHostHandle handle)
|
|
{
|
|
const CarlaPluginInfo* const info = carla_get_plugin_info(handle, 0);
|
|
|
|
fDrawingState = kDrawingPluginGenericUI;
|
|
fPluginHasCustomUI = info->hints & PLUGIN_HAS_CUSTOM_UI;
|
|
if (fPluginGenericUI == nullptr)
|
|
createPluginGenericUI(handle, info);
|
|
else
|
|
updatePluginGenericUI(handle);
|
|
|
|
setDirty(true);
|
|
}
|
|
|
|
void hidePluginUI(const CarlaHostHandle handle)
|
|
{
|
|
DISTRHO_SAFE_ASSERT_RETURN(fPluginRunning,);
|
|
|
|
carla_show_custom_ui(handle, 0, false);
|
|
}
|
|
|
|
void createPluginGenericUI(const CarlaHostHandle handle, const CarlaPluginInfo* const info)
|
|
{
|
|
PluginGenericUI* const ui = new PluginGenericUI;
|
|
|
|
String title(info->name);
|
|
title += " by ";
|
|
title += info->maker;
|
|
ui->title = title.getAndReleaseBuffer();
|
|
|
|
fPluginHasOutputParameters = false;
|
|
|
|
const uint32_t pcount = ui->parameterCount = carla_get_parameter_count(handle, 0);
|
|
|
|
// make count of valid parameters
|
|
for (uint32_t i=0; i < pcount; ++i)
|
|
{
|
|
const ParameterData* const pdata = carla_get_parameter_data(handle, 0, i);
|
|
|
|
if ((pdata->hints & PARAMETER_IS_ENABLED) == 0x0)
|
|
{
|
|
--ui->parameterCount;
|
|
continue;
|
|
}
|
|
|
|
if (pdata->type == PARAMETER_OUTPUT)
|
|
fPluginHasOutputParameters = true;
|
|
}
|
|
|
|
ui->parameters = new PluginGenericUI::Parameter[ui->parameterCount];
|
|
ui->values = new float[ui->parameterCount];
|
|
|
|
// now safely fill in details
|
|
for (uint32_t i=0, j=0; i < pcount; ++i)
|
|
{
|
|
const ParameterData* const pdata = carla_get_parameter_data(handle, 0, i);
|
|
|
|
if ((pdata->hints & PARAMETER_IS_ENABLED) == 0x0)
|
|
continue;
|
|
|
|
const CarlaParameterInfo* const pinfo = carla_get_parameter_info(handle, 0, i);
|
|
const ::ParameterRanges* const pranges = carla_get_parameter_ranges(handle, 0, i);
|
|
|
|
String printformat;
|
|
|
|
if (pdata->hints & PARAMETER_IS_INTEGER)
|
|
printformat = "%.0f ";
|
|
else
|
|
printformat = "%.3f ";
|
|
|
|
printformat += pinfo->unit;
|
|
|
|
PluginGenericUI::Parameter& param(ui->parameters[j]);
|
|
param.name = strdup(pinfo->name);
|
|
param.printformat = printformat.getAndReleaseBuffer();
|
|
param.rindex = i;
|
|
param.boolean = pdata->hints & PARAMETER_IS_BOOLEAN;
|
|
param.log = pdata->hints & PARAMETER_IS_LOGARITHMIC;
|
|
param.readonly = pdata->type != PARAMETER_INPUT || (pdata->hints & PARAMETER_IS_READ_ONLY);
|
|
param.min = pranges->min;
|
|
param.max = pranges->max;
|
|
|
|
ui->values[j] = carla_get_current_parameter_value(handle, 0, i);
|
|
|
|
if (param.boolean)
|
|
param.bvalue = ui->values[j] > param.min;
|
|
else
|
|
param.bvalue = false;
|
|
|
|
++j;
|
|
}
|
|
|
|
fPluginGenericUI = ui;
|
|
}
|
|
|
|
void updatePluginGenericUI(const CarlaHostHandle handle)
|
|
{
|
|
PluginGenericUI* const ui = fPluginGenericUI;
|
|
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
|
|
|
|
for (uint32_t i=0; i < ui->parameterCount; ++i)
|
|
{
|
|
ui->values[i] = carla_get_current_parameter_value(handle, 0, ui->parameters[i].rindex);
|
|
|
|
if (ui->parameters[i].boolean)
|
|
ui->parameters[i].bvalue = ui->values[i] > ui->parameters[i].min;
|
|
}
|
|
}
|
|
|
|
void loadPlugin(const CarlaHostHandle handle, const char* const label)
|
|
{
|
|
if (fPluginRunning)
|
|
{
|
|
carla_show_custom_ui(handle, 0, false);
|
|
carla_replace_plugin(handle, 0);
|
|
}
|
|
|
|
carla_set_engine_option(handle, ENGINE_OPTION_PREFER_PLUGIN_BRIDGES, fPluginWillRunInBridgeMode, nullptr);
|
|
|
|
const MutexLocker cml(sPluginInfoLoadMutex);
|
|
|
|
if (carla_add_plugin(handle, BINARY_NATIVE, fPluginType, nullptr, nullptr,
|
|
label, 0, 0x0, PLUGIN_OPTIONS_NULL))
|
|
{
|
|
fPluginRunning = true;
|
|
fPluginGenericUI = nullptr;
|
|
createOrUpdatePluginGenericUI(handle);
|
|
}
|
|
else
|
|
{
|
|
fPopupError = carla_get_last_error(handle);
|
|
d_stdout("got error: %s", fPopupError.buffer());
|
|
fDrawingState = kDrawingPluginError;
|
|
}
|
|
|
|
setDirty(true);
|
|
}
|
|
|
|
void onContextCreate(const ContextCreateEvent& e) override
|
|
{
|
|
ImGuiWidget::onContextCreate(e);
|
|
widgetCreated();
|
|
}
|
|
|
|
void onContextDestroy(const ContextDestroyEvent& e) override
|
|
{
|
|
widgetDestroyed();
|
|
ImGuiWidget::onContextDestroy(e);
|
|
}
|
|
|
|
void onAdd(const AddEvent& e) override
|
|
{
|
|
ImGuiWidget::onAdd(e);
|
|
widgetCreated();
|
|
}
|
|
|
|
void onRemove(const RemoveEvent& e) override
|
|
{
|
|
widgetDestroyed();
|
|
ImGuiWidget::onRemove(e);
|
|
}
|
|
|
|
void widgetCreated()
|
|
{
|
|
if (module == nullptr)
|
|
return;
|
|
|
|
if (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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void widgetDestroyed()
|
|
{
|
|
if (module == nullptr)
|
|
return;
|
|
|
|
if (const CarlaHostHandle handle = module->fCarlaHostHandle)
|
|
{
|
|
const CardinalPluginContext* const pcontext = module->pcontext;
|
|
|
|
module->fCarlaHostDescriptor.uiParentId = 0;
|
|
carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0");
|
|
|
|
if (idleCallbackActive)
|
|
{
|
|
idleCallbackActive = false;
|
|
pcontext->removeIdleCallback(this);
|
|
|
|
if (fileBrowserHandle != nullptr)
|
|
{
|
|
fileBrowserClose(fileBrowserHandle);
|
|
fileBrowserHandle = nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void idleCallback() override
|
|
{
|
|
const CarlaHostHandle handle = module->fCarlaHostHandle;
|
|
DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr,);
|
|
|
|
carla_juce_idle();
|
|
|
|
if (fileBrowserHandle != nullptr && fileBrowserIdle(fileBrowserHandle))
|
|
{
|
|
if (const char* const path = fileBrowserGetPath(fileBrowserHandle))
|
|
carla_set_custom_data(handle, 0, CUSTOM_DATA_TYPE_PATH, "file", path);
|
|
|
|
fileBrowserClose(fileBrowserHandle);
|
|
fileBrowserHandle = nullptr;
|
|
}
|
|
|
|
if (fDrawingState == kDrawingPluginGenericUI && fPluginGenericUI != nullptr && fPluginHasOutputParameters)
|
|
{
|
|
updatePluginGenericUI(handle);
|
|
setDirty(true);
|
|
}
|
|
|
|
switch (fIdleState)
|
|
{
|
|
case kIdleInit:
|
|
fIdleState = kIdleNothing;
|
|
startThread();
|
|
break;
|
|
|
|
case kIdleInitPluginAlreadyLoaded:
|
|
fIdleState = kIdleNothing;
|
|
createOrUpdatePluginGenericUI(handle);
|
|
startThread();
|
|
break;
|
|
|
|
case kIdlePluginLoadedFromDSP:
|
|
fIdleState = kIdleNothing;
|
|
createOrUpdatePluginGenericUI(handle);
|
|
break;
|
|
|
|
case kIdleLoadSelectedPlugin:
|
|
fIdleState = kIdleNothing;
|
|
loadSelectedPlugin(handle);
|
|
break;
|
|
|
|
case kIdleResetPlugin:
|
|
fIdleState = kIdleNothing;
|
|
loadPlugin(handle, carla_get_plugin_info(handle, 0)->label);
|
|
break;
|
|
|
|
case kIdleShowCustomUI:
|
|
fIdleState = kIdleGiveIdleToUI;
|
|
carla_show_custom_ui(handle, 0, true);
|
|
break;
|
|
|
|
case kIdleHidePluginUI:
|
|
fIdleState = kIdleNothing;
|
|
carla_show_custom_ui(handle, 0, false);
|
|
break;
|
|
|
|
case kIdleGiveIdleToUI:
|
|
module->fCarlaPluginDescriptor->ui_idle(module->fCarlaPluginHandle);
|
|
break;
|
|
|
|
case kIdleChangePluginType:
|
|
fIdleState = kIdleNothing;
|
|
fPluginSelected = -1;
|
|
if (isThreadRunning())
|
|
stopThread(-1);
|
|
fPluginType = fNextPluginType;
|
|
startThread();
|
|
break;
|
|
|
|
case kIdleNothing:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void loadSelectedPlugin(const CarlaHostHandle handle)
|
|
{
|
|
DISTRHO_SAFE_ASSERT_RETURN(fPluginSelected >= 0,);
|
|
|
|
const PluginInfoCache& info(fPlugins[fPluginSelected]);
|
|
|
|
const char* label = nullptr;
|
|
|
|
switch (fPluginType)
|
|
{
|
|
case PLUGIN_INTERNAL:
|
|
case PLUGIN_AU:
|
|
// case PLUGIN_JSFX:
|
|
case PLUGIN_SFZ:
|
|
label = info.label;
|
|
break;
|
|
case PLUGIN_LV2: {
|
|
const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
|
|
DISTRHO_SAFE_ASSERT_RETURN(slash != nullptr,);
|
|
label = slash+1;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
DISTRHO_SAFE_ASSERT_RETURN(label != nullptr,);
|
|
|
|
d_stdout("Loading %s...", info.name);
|
|
loadPlugin(handle, label);
|
|
}
|
|
|
|
void run() override
|
|
{
|
|
const char* path;
|
|
switch (fPluginType)
|
|
{
|
|
case PLUGIN_LV2:
|
|
path = std::getenv("LV2_PATH");
|
|
break;
|
|
default:
|
|
path = nullptr;
|
|
break;
|
|
}
|
|
|
|
if (path != nullptr)
|
|
carla_set_engine_option(module->fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, fPluginType, path);
|
|
|
|
fPluginCount = 0;
|
|
delete[] fPlugins;
|
|
|
|
uint count;
|
|
|
|
{
|
|
const MutexLocker cml(sPluginInfoLoadMutex);
|
|
|
|
d_stdout("Will scan plugins now...");
|
|
count = carla_get_cached_plugin_count(fPluginType, path);
|
|
d_stdout("Scanning found %u plugins", count);
|
|
}
|
|
|
|
if (fDrawingState == kDrawingLoading)
|
|
{
|
|
fDrawingState = kDrawingPluginList;
|
|
fPluginSearchFirstShow = true;
|
|
}
|
|
|
|
if (count != 0)
|
|
{
|
|
fPlugins = new PluginInfoCache[count];
|
|
|
|
for (uint i=0, j; i < count && ! shouldThreadExit(); ++i)
|
|
{
|
|
const MutexLocker cml(sPluginInfoLoadMutex);
|
|
|
|
const CarlaCachedPluginInfo* const info = carla_get_cached_plugin_info(fPluginType, i);
|
|
DISTRHO_SAFE_ASSERT_CONTINUE(info != nullptr);
|
|
|
|
if (! info->valid)
|
|
continue;
|
|
if (info->audioIns != 0 && info->audioIns != 2)
|
|
continue;
|
|
if (info->midiIns != 0 && info->midiIns != 1)
|
|
continue;
|
|
if (info->midiOuts != 0 && info->midiOuts != 1)
|
|
continue;
|
|
|
|
if (fPluginType == PLUGIN_INTERNAL)
|
|
{
|
|
if (std::strcmp(info->label, "audiogain_s") == 0)
|
|
continue;
|
|
if (std::strcmp(info->label, "cv2audio") == 0)
|
|
continue;
|
|
if (std::strcmp(info->label, "lfo") == 0)
|
|
continue;
|
|
if (std::strcmp(info->label, "midi2cv") == 0)
|
|
continue;
|
|
if (std::strcmp(info->label, "midithrough") == 0)
|
|
continue;
|
|
if (std::strcmp(info->label, "3bandsplitter") == 0)
|
|
continue;
|
|
}
|
|
|
|
j = fPluginCount;
|
|
fPlugins[j].name = strdup(info->name);
|
|
fPlugins[j].label = strdup(info->label);
|
|
++fPluginCount;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fPlugins = nullptr;
|
|
}
|
|
|
|
if (! shouldThreadExit())
|
|
fPluginScanningFinished = true;
|
|
}
|
|
|
|
void drawImGui() override
|
|
{
|
|
switch (fDrawingState)
|
|
{
|
|
case kDrawingLoading:
|
|
drawLoading();
|
|
break;
|
|
case kDrawingPluginError:
|
|
ImGui::OpenPopup("Plugin Error");
|
|
// call ourselves again with the plugin list
|
|
fDrawingState = kDrawingPluginList;
|
|
drawImGui();
|
|
break;
|
|
case kDrawingPluginList:
|
|
drawPluginList();
|
|
break;
|
|
case kDrawingPluginGenericUI:
|
|
drawTopBar();
|
|
drawGenericUI();
|
|
break;
|
|
case kDrawingErrorInit:
|
|
fDrawingState = kDrawingErrorDraw;
|
|
drawError(true);
|
|
break;
|
|
case kDrawingErrorDraw:
|
|
drawError(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void drawError(const bool open)
|
|
{
|
|
const float scaleFactor = getScaleFactor();
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
|
ImGui::SetNextWindowSize(ImVec2(box.size.x * scaleFactor, box.size.y * scaleFactor));
|
|
|
|
const int flags = ImGuiWindowFlags_NoSavedSettings
|
|
| ImGuiWindowFlags_NoTitleBar
|
|
| ImGuiWindowFlags_NoResize
|
|
| ImGuiWindowFlags_NoCollapse
|
|
| ImGuiWindowFlags_NoScrollbar
|
|
| ImGuiWindowFlags_NoScrollWithMouse;
|
|
|
|
if (ImGui::Begin("Error Window", nullptr, flags))
|
|
{
|
|
if (open)
|
|
ImGui::OpenPopup("Engine Error");
|
|
|
|
const int pflags = ImGuiWindowFlags_NoSavedSettings
|
|
| ImGuiWindowFlags_NoResize
|
|
| ImGuiWindowFlags_NoCollapse
|
|
| ImGuiWindowFlags_NoScrollbar
|
|
| ImGuiWindowFlags_NoScrollWithMouse
|
|
| ImGuiWindowFlags_AlwaysAutoResize
|
|
| ImGuiWindowFlags_AlwaysUseWindowPadding;
|
|
|
|
if (ImGui::BeginPopupModal("Engine Error", nullptr, pflags))
|
|
{
|
|
ImGui::TextUnformatted(fPopupError.buffer(), nullptr);
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void drawTopBar()
|
|
{
|
|
const float scaleFactor = getScaleFactor();
|
|
const float padding = ImGui::GetStyle().WindowPadding.y * 2;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
|
ImGui::SetNextWindowSize(ImVec2(box.size.x * scaleFactor, kButtonHeight * scaleFactor + padding));
|
|
|
|
const int flags = ImGuiWindowFlags_NoSavedSettings
|
|
| ImGuiWindowFlags_NoTitleBar
|
|
| ImGuiWindowFlags_NoResize
|
|
| ImGuiWindowFlags_NoCollapse
|
|
| ImGuiWindowFlags_NoScrollbar
|
|
| ImGuiWindowFlags_NoScrollWithMouse;
|
|
|
|
if (ImGui::Begin("Current Plugin", nullptr, flags))
|
|
{
|
|
if (ImGui::Button("Pick Another..."))
|
|
{
|
|
fIdleState = kIdleHidePluginUI;
|
|
fDrawingState = kDrawingPluginList;
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Button("Reset"))
|
|
fIdleState = kIdleResetPlugin;
|
|
|
|
if (fDrawingState == kDrawingPluginGenericUI && fPluginHasCustomUI)
|
|
{
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Button("Show Custom GUI"))
|
|
fIdleState = kIdleShowCustomUI;
|
|
}
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void setupMainWindowPos()
|
|
{
|
|
const float scaleFactor = getScaleFactor();
|
|
|
|
float y = 0;
|
|
float width = box.size.x * scaleFactor;
|
|
float height = box.size.y * scaleFactor;
|
|
|
|
if (fDrawingState == kDrawingPluginGenericUI)
|
|
{
|
|
y = kButtonHeight * scaleFactor + ImGui::GetStyle().WindowPadding.y * 2 - scaleFactor;
|
|
height -= y;
|
|
}
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(0, y));
|
|
ImGui::SetNextWindowSize(ImVec2(width, height));
|
|
}
|
|
|
|
void drawGenericUI()
|
|
{
|
|
setupMainWindowPos();
|
|
|
|
PluginGenericUI* const ui = fPluginGenericUI;
|
|
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
|
|
|
|
const int pflags = ImGuiWindowFlags_NoSavedSettings
|
|
| ImGuiWindowFlags_NoResize
|
|
| ImGuiWindowFlags_NoCollapse
|
|
| ImGuiWindowFlags_AlwaysAutoResize;
|
|
|
|
if (ImGui::Begin(ui->title, nullptr, pflags))
|
|
{
|
|
const CarlaHostHandle handle = module->fCarlaHostHandle;
|
|
|
|
for (uint32_t i=0; i < ui->parameterCount; ++i)
|
|
{
|
|
PluginGenericUI::Parameter& param(ui->parameters[i]);
|
|
|
|
if (param.readonly)
|
|
{
|
|
ImGui::BeginDisabled();
|
|
ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat, ImGuiSliderFlags_NoInput);
|
|
ImGui::EndDisabled();
|
|
continue;
|
|
}
|
|
|
|
if (param.boolean)
|
|
{
|
|
if (ImGui::Checkbox(param.name, &ui->parameters[i].bvalue))
|
|
{
|
|
if (ImGui::IsItemActivated())
|
|
{
|
|
carla_set_parameter_touch(handle, 0, param.rindex, true);
|
|
// editParameter(0, true);
|
|
}
|
|
|
|
ui->values[i] = ui->parameters[i].bvalue ? ui->parameters[i].max : ui->parameters[i].min;
|
|
carla_set_parameter_value(handle, 0, param.rindex, ui->values[i]);
|
|
// setParameterValue(0, ui->values[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const bool ret = param.log
|
|
? ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat, 2.0f)
|
|
: ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat);
|
|
if (ret)
|
|
{
|
|
if (ImGui::IsItemActivated())
|
|
{
|
|
carla_set_parameter_touch(handle, 0, param.rindex, true);
|
|
// editParameter(0, true);
|
|
}
|
|
|
|
carla_set_parameter_value(handle, 0, param.rindex, ui->values[i]);
|
|
// setParameterValue(0, ui->values[i]);
|
|
}
|
|
}
|
|
|
|
if (ImGui::IsItemDeactivated())
|
|
{
|
|
carla_set_parameter_touch(handle, 0, param.rindex, false);
|
|
// editParameter(0, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void drawLoading()
|
|
{
|
|
setupMainWindowPos();
|
|
|
|
if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
|
|
ImGui::TextUnformatted("Loading...", nullptr);
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void drawPluginList()
|
|
{
|
|
static const char* pluginTypes[] = {
|
|
getPluginTypeAsString(PLUGIN_INTERNAL),
|
|
getPluginTypeAsString(PLUGIN_LV2),
|
|
};
|
|
|
|
setupMainWindowPos();
|
|
|
|
if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
|
|
{
|
|
const int pflags = ImGuiWindowFlags_NoSavedSettings
|
|
| ImGuiWindowFlags_NoResize
|
|
| ImGuiWindowFlags_NoCollapse
|
|
| ImGuiWindowFlags_NoScrollbar
|
|
| ImGuiWindowFlags_NoScrollWithMouse
|
|
| ImGuiWindowFlags_AlwaysAutoResize;
|
|
|
|
if (ImGui::BeginPopupModal("Plugin Error", nullptr, pflags))
|
|
{
|
|
ImGui::TextWrapped("Failed to load plugin, error was:\n%s", fPopupError.buffer());
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::Button("Ok"))
|
|
ImGui::CloseCurrentPopup();
|
|
|
|
ImGui::SameLine();
|
|
ImGui::Dummy(ImVec2(500 * getScaleFactor(), 1));
|
|
ImGui::EndPopup();
|
|
}
|
|
else if (fPluginSearchFirstShow)
|
|
{
|
|
fPluginSearchFirstShow = false;
|
|
ImGui::SetKeyboardFocusHere();
|
|
}
|
|
|
|
if (ImGui::InputText("##pluginsearch", fPluginSearchString, sizeof(fPluginSearchString)-1,
|
|
ImGuiInputTextFlags_CharsNoBlank|ImGuiInputTextFlags_AutoSelectAll))
|
|
fPluginSearchActive = true;
|
|
|
|
if (ImGui::IsKeyDown(ImGuiKey_Escape))
|
|
fPluginSearchActive = false;
|
|
|
|
ImGui::SameLine();
|
|
ImGui::PushItemWidth(-1.0f);
|
|
|
|
int current;
|
|
switch (fPluginType)
|
|
{
|
|
case PLUGIN_LV2:
|
|
current = 1;
|
|
break;
|
|
default:
|
|
current = 0;
|
|
break;
|
|
}
|
|
|
|
if (ImGui::Combo("##plugintypes", ¤t, pluginTypes, ARRAY_SIZE(pluginTypes)))
|
|
{
|
|
fIdleState = kIdleChangePluginType;
|
|
switch (current)
|
|
{
|
|
case 0:
|
|
fNextPluginType = PLUGIN_INTERNAL;
|
|
break;
|
|
case 1:
|
|
fNextPluginType = PLUGIN_LV2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ImGui::BeginDisabled(!fPluginScanningFinished || fPluginSelected < 0);
|
|
|
|
if (ImGui::Button("Load Plugin"))
|
|
fIdleState = kIdleLoadSelectedPlugin;
|
|
|
|
if (fPluginType != PLUGIN_INTERNAL && (module == nullptr || module->canUseBridges))
|
|
{
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("Run in bridge mode", &fPluginWillRunInBridgeMode);
|
|
}
|
|
|
|
ImGui::EndDisabled();
|
|
|
|
if (fPluginRunning)
|
|
{
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Button("Cancel"))
|
|
fDrawingState = kDrawingPluginGenericUI;
|
|
}
|
|
|
|
if (ImGui::BeginChild("pluginlistwindow"))
|
|
{
|
|
if (ImGui::BeginTable("pluginlist", 2, ImGuiTableFlags_NoSavedSettings))
|
|
{
|
|
const char* const search = fPluginSearchActive && fPluginSearchString[0] != '\0' ? fPluginSearchString : nullptr;
|
|
|
|
switch (fPluginType)
|
|
{
|
|
case PLUGIN_INTERNAL:
|
|
case PLUGIN_AU:
|
|
case PLUGIN_SFZ:
|
|
// case PLUGIN_JSFX:
|
|
ImGui::TableSetupColumn("Name");
|
|
ImGui::TableSetupColumn("Label");
|
|
ImGui::TableHeadersRow();
|
|
break;
|
|
case PLUGIN_LV2:
|
|
ImGui::TableSetupColumn("Name");
|
|
ImGui::TableSetupColumn("URI");
|
|
ImGui::TableHeadersRow();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
for (uint i=0; i<fPluginCount; ++i)
|
|
{
|
|
const PluginInfoCache& info(fPlugins[i]);
|
|
|
|
if (search != nullptr && ildaeil::strcasestr(info.name, search) == nullptr)
|
|
continue;
|
|
|
|
bool selected = fPluginSelected >= 0 && static_cast<uint>(fPluginSelected) == i;
|
|
|
|
switch (fPluginType)
|
|
{
|
|
case PLUGIN_INTERNAL:
|
|
case PLUGIN_AU:
|
|
// case PLUGIN_JSFX:
|
|
case PLUGIN_SFZ:
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::Selectable(info.name, &selected);
|
|
ImGui::TableSetColumnIndex(1);
|
|
ImGui::Selectable(info.label, &selected);
|
|
break;
|
|
case PLUGIN_LV2: {
|
|
const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
|
|
DISTRHO_SAFE_ASSERT_CONTINUE(slash != nullptr);
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::Selectable(info.name, &selected);
|
|
ImGui::TableSetColumnIndex(1);
|
|
ImGui::Selectable(slash+1, &selected);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (selected)
|
|
fPluginSelected = i;
|
|
}
|
|
|
|
ImGui::EndTable();
|
|
}
|
|
ImGui::EndChild();
|
|
}
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilWidget)
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
static void host_ui_parameter_changed(const NativeHostHandle handle, const uint32_t index, const float value)
|
|
{
|
|
if (IldaeilWidget* const ui = static_cast<IldaeilWidget*>(static_cast<IldaeilModule*>(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 (IldaeilWidget* const ui = static_cast<IldaeilWidget*>(static_cast<IldaeilModule*>(handle)->fUI))
|
|
ui->openFileFromDSP(isDir, title, filter);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static void projectLoadedFromDSP(void* const ui)
|
|
{
|
|
if (IldaeilWidget* const uiw = static_cast<IldaeilWidget*>(ui))
|
|
uiw->projectLoadedFromDSP();
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
struct IldaeilNanoMeterIn : NanoMeter {
|
|
IldaeilModule* const module;
|
|
|
|
IldaeilNanoMeterIn(IldaeilModule* const m)
|
|
: module(m)
|
|
{
|
|
withBackground = false;
|
|
}
|
|
|
|
void updateMeters() override
|
|
{
|
|
if (module == nullptr || module->resetMeterIn)
|
|
return;
|
|
|
|
// Only fetch new values once DSP side is updated
|
|
gainMeterL = module->meterInL;
|
|
gainMeterR = module->meterInR;
|
|
module->resetMeterIn = true;
|
|
}
|
|
};
|
|
|
|
struct IldaeilNanoMeterOut : NanoMeter {
|
|
IldaeilModule* const module;
|
|
|
|
IldaeilNanoMeterOut(IldaeilModule* const m)
|
|
: module(m)
|
|
{
|
|
withBackground = false;
|
|
}
|
|
|
|
void updateMeters() override
|
|
{
|
|
if (module == nullptr || module->resetMeterOut)
|
|
return;
|
|
|
|
// Only fetch new values once DSP side is updated
|
|
gainMeterL = module->meterOutL;
|
|
gainMeterR = module->meterOutR;
|
|
module->resetMeterOut = true;
|
|
}
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
struct IldaeilModuleWidget : ModuleWidgetWithSideScrews<26> {
|
|
bool hasLeftSideExpander = false;
|
|
bool hasRightSideExpander = false;
|
|
IldaeilWidget* ildaeilWidget = nullptr;
|
|
|
|
IldaeilModuleWidget(IldaeilModule* const module)
|
|
{
|
|
setModule(module);
|
|
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Ildaeil.svg")));
|
|
createAndAddScrews();
|
|
|
|
if (module == nullptr || module->pcontext != nullptr)
|
|
{
|
|
ildaeilWidget = new IldaeilWidget(module);
|
|
ildaeilWidget->box.pos = Vec(3 * RACK_GRID_WIDTH, 0);
|
|
ildaeilWidget->box.size = Vec(box.size.x - 6 * RACK_GRID_WIDTH, box.size.y);
|
|
addChild(ildaeilWidget);
|
|
}
|
|
|
|
for (uint i=0; i<IldaeilModule::NUM_INPUTS; ++i)
|
|
createAndAddInput(i);
|
|
|
|
for (uint i=0; i<IldaeilModule::NUM_OUTPUTS; ++i)
|
|
createAndAddOutput(i);
|
|
|
|
IldaeilNanoMeterIn* const meterIn = new IldaeilNanoMeterIn(module);
|
|
meterIn->box.pos = Vec(2.0f, startY + padding * 2);
|
|
meterIn->box.size = Vec(RACK_GRID_WIDTH * 3 - 2.0f, box.size.y - meterIn->box.pos.y - 19.0f);
|
|
addChild(meterIn);
|
|
|
|
IldaeilNanoMeterOut* const meterOut = new IldaeilNanoMeterOut(module);
|
|
meterOut->box.pos = Vec(box.size.x - RACK_GRID_WIDTH * 3 + 1.0f, startY + padding * 2);
|
|
meterOut->box.size = Vec(RACK_GRID_WIDTH * 3 - 2.0f, box.size.y - meterOut->box.pos.y - 19.0f);
|
|
addChild(meterOut);
|
|
}
|
|
|
|
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)
|
|
{
|
|
// i == 0
|
|
nvgBeginPath(args.vg);
|
|
nvgRect(args.vg, box.size.x - 19, 90 - 19, 18, 49 - 4);
|
|
nvgFillColor(args.vg, nvgRGB(0xd0, 0xd0, 0xd0));
|
|
nvgFill(args.vg);
|
|
|
|
// gradient for i > 0
|
|
nvgBeginPath(args.vg);
|
|
nvgRect(args.vg, box.size.x - 19, 90 + 49 - 23, 18, 49 * 5);
|
|
nvgFillPaint(args.vg, nvgLinearGradient(args.vg,
|
|
box.size.x - 19, 0, box.size.x - 1, 0,
|
|
nvgRGBA(0xd0, 0xd0, 0xd0, 0), nvgRGB(0xd0, 0xd0, 0xd0)));
|
|
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, box.size.x - 19, y, 18, 4);
|
|
nvgFillColor(args.vg, nvgRGB(col1, col2, col2));
|
|
nvgFill(args.vg);
|
|
}
|
|
}
|
|
|
|
drawOutputJacksArea(args.vg, 2);
|
|
|
|
ModuleWidgetWithSideScrews<26>::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;
|
|
|
|
ModuleWidgetWithSideScrews<26>::step();
|
|
}
|
|
|
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilModuleWidget)
|
|
};
|
|
#else
|
|
static void host_ui_parameter_changed(NativeHostHandle, uint32_t, float) {}
|
|
static const char* host_ui_open_file(NativeHostHandle, bool, const char*, const char*) { return nullptr; }
|
|
static void projectLoadedFromDSP(void*) {}
|
|
struct IldaeilModuleWidget : ModuleWidget {
|
|
IldaeilModuleWidget(IldaeilModule* const module) {
|
|
setModule(module);
|
|
|
|
addInput(createInput<PJ301MPort>({}, module, IldaeilModule::INPUT1));
|
|
addInput(createInput<PJ301MPort>({}, module, IldaeilModule::INPUT2));
|
|
addOutput(createOutput<PJ301MPort>({}, module, IldaeilModule::OUTPUT1));
|
|
addOutput(createOutput<PJ301MPort>({}, module, IldaeilModule::OUTPUT2));
|
|
}
|
|
};
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
Model* modelIldaeil = createModel<IldaeilModule, IldaeilModuleWidget>("Ildaeil");
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|