From b573cbf07bc3907bfad169d659ae32c536bb014c Mon Sep 17 00:00:00 2001 From: bsp2 Date: Sun, 5 May 2019 17:40:09 +0200 Subject: [PATCH] add Core.HalfNotes and Core.ParamProxy modules (self-editing patches) --- include/global_ui.hpp | 4 +- make.objects | 4 +- src/Core/Core.cpp | 4 + src/Core/HalfNotes.cpp | 49 ++ src/Core/ParamProxy.cpp | 374 ++++++++++++++ src/app/ModuleWidget.cpp | 3 +- src/engine.cpp | 13 +- .../res/ComponentLibrary/LEDButtonLit.svg | 75 +++ vst2_bin/res/Core/HalfNotes.svg | 113 +++++ vst2_bin/res/Core/ParamProxy.svg | 475 ++++++++++++++++++ 10 files changed, 1105 insertions(+), 9 deletions(-) create mode 100644 src/Core/HalfNotes.cpp create mode 100644 src/Core/ParamProxy.cpp create mode 100644 vst2_bin/res/ComponentLibrary/LEDButtonLit.svg create mode 100644 vst2_bin/res/Core/HalfNotes.svg create mode 100644 vst2_bin/res/Core/ParamProxy.svg diff --git a/include/global_ui.hpp b/include/global_ui.hpp index e4cdcab7..6c4177bc 100644 --- a/include/global_ui.hpp +++ b/include/global_ui.hpp @@ -97,7 +97,8 @@ struct GlobalUI { const ParamWidget *last_param_widget; // never dereferenced, may have already been deleted. unset after redraw(). int last_param_gid; // updated during redraw() float last_param_value; // updated in onMouseMove() and onChange(). corresponding param may not exist anymore. - float value_clipboard; + float value_clipboard; // last copied value + int gid_clipboard; // param GID of last copied value (-1 if none) TextField *tf_id; TextField *tf_value; bool b_lock; // true=don't update info (e.g. when receiving VST parameter updates from host) @@ -151,6 +152,7 @@ struct GlobalUI { param_info.last_param_gid = 0; param_info.last_param_value = 0.0f; param_info.value_clipboard = 0.0f; + param_info.gid_clipboard = -1; param_info.tf_id = NULL; param_info.tf_value = NULL; param_info.b_lock = false; diff --git a/make.objects b/make.objects index 7f077c3d..bf450a57 100644 --- a/make.objects +++ b/make.objects @@ -96,7 +96,9 @@ HOST_OBJ= \ src/Core/MIDIToCVInterface.o \ src/Core/MIDITriggerToCVInterface.o \ src/Core/Notes.o \ - src/Core/QuadMIDIToCVInterface.o + src/Core/QuadMIDIToCVInterface.o \ + src/Core/ParamProxy.o \ + src/Core/HalfNotes.o LIB_OBJ= \ $(COMMON_OBJ) \ diff --git a/src/Core/Core.cpp b/src/Core/Core.cpp index c9603214..68b42618 100644 --- a/src/Core/Core.cpp +++ b/src/Core/Core.cpp @@ -11,6 +11,8 @@ RACK_PLUGIN_MODEL_DECLARE(Core, MIDICCToCVInterface); RACK_PLUGIN_MODEL_DECLARE(Core, MIDITriggerToCVInterface); RACK_PLUGIN_MODEL_DECLARE(Core, Blank); RACK_PLUGIN_MODEL_DECLARE(Core, Notes); +RACK_PLUGIN_MODEL_DECLARE(Core, ParamProxy); +RACK_PLUGIN_MODEL_DECLARE(Core, HalfNotes); #undef SLUG #define SLUG Core @@ -24,6 +26,8 @@ RACK_PLUGIN_INIT(Core) { RACK_PLUGIN_MODEL_ADD(Core, MIDITriggerToCVInterface); RACK_PLUGIN_MODEL_ADD(Core, Blank); RACK_PLUGIN_MODEL_ADD(Core, Notes); + RACK_PLUGIN_MODEL_ADD(Core, ParamProxy); + RACK_PLUGIN_MODEL_ADD(Core, HalfNotes); } #endif // RACK_HOST diff --git a/src/Core/HalfNotes.cpp b/src/Core/HalfNotes.cpp new file mode 100644 index 00000000..e3df629e --- /dev/null +++ b/src/Core/HalfNotes.cpp @@ -0,0 +1,49 @@ +#include "global_pre.hpp" +#include "Core.hpp" +#include "global.hpp" + +using namespace rack; + + + +struct HalfNotesWidget : ModuleWidget { + TextField *textField; + + HalfNotesWidget(Module *module) : ModuleWidget(module) { + setPanel(SVG::load(assetGlobal("res/Core/HalfNotes.svg"))); + + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + textField = Widget::create(mm2px(Vec(3.39962f, 14.8373f))); + textField->box.size = mm2px(Vec(74.480f*0.46f, 102.753f)); + textField->multiline = true; + addChild(textField); + } + + json_t *toJson() override { + json_t *rootJ = ModuleWidget::toJson(); + + // text + json_object_set_new(rootJ, "text", json_string(textField->text.c_str())); + + return rootJ; + } + + void fromJson(json_t *rootJ) override { + ModuleWidget::fromJson(rootJ); + + // text + json_t *textJ = json_object_get(rootJ, "text"); + if (textJ) + textField->text = json_string_value(textJ); + } +}; + + +RACK_PLUGIN_MODEL_INIT(Core, HalfNotes) { + Model *modelHalfNotes = Model::create("Core", "HalfNotes", "Half Notes", BLANK_TAG); + return modelHalfNotes; +} diff --git a/src/Core/ParamProxy.cpp b/src/Core/ParamProxy.cpp new file mode 100644 index 00000000..3a76f454 --- /dev/null +++ b/src/Core/ParamProxy.cpp @@ -0,0 +1,374 @@ +/* +Copyright (c) 2019 bsp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "global_pre.hpp" +#include "Core.hpp" +#include "global_ui.hpp" + +using namespace rack; + +extern void rack::engineSetParam(Module *module, int paramId, float value, bool bVSTAutomate); + +namespace rack { +extern bool vst2_find_module_and_paramid_by_unique_paramid(int uniqueParamId, Module**retModule, int *retParamId); +} + +struct ParamProxy; + +// struct TrigButton : TL1105 { +// struct ParamProxyTrigButton : CKD6 { +struct ParamProxyTrigButton : LEDButton { + ParamWidget *id_widget; + + ParamProxyTrigButton() { + addFrame(SVG::load(assetGlobal("res/ComponentLibrary/LEDButtonLit.svg"))); + } + + void onAction(EventAction &e) override; + void onDragEnd(EventDragEnd &e) override; +}; + +struct NullButton : SVGSwitch, ToggleSwitch { + NullButton() { + addFrame(SVG::load(assetPlugin("res/null.svg"))); + addFrame(SVG::load(assetPlugin("res/null.svg"))); + } +}; + +struct RoundSmallBlackKnobParamId : RoundSmallBlackKnob { + RoundSmallBlackKnobParamId() { + } + + void onChange(EventChange &e) override; +}; + +struct ParamProxy : Module { + + static const uint32_t NUM_PARAM_ROWS = 8u; + + enum RowParamIds { + ROW_PARAM_CONSTVAL, + ROW_PARAM_MIN, + ROW_PARAM_MAX, + ROW_PARAM_PARAMID, + ROW_PARAM_LEARNFROMCLIPBOARD, + NUM_ROW_PARAMS + }; + + enum ParamIds { + PARAM_0_CONSTVAL, + PARAM_0_MIN, + PARAM_0_MAX, + PARAM_0_PARAMID, + PARAM_0_LEARNFROMCLIPBOARD, + + PARAM_1_CONSTVAL, + PARAM_1_MIN, + PARAM_1_MAX, + PARAM_1_PARAMID, + PARAM_1_LEARNFROMCLIPBOARD, + + PARAM_2_CONSTVAL, + PARAM_2_MIN, + PARAM_2_MAX, + PARAM_2_PARAMID, + PARAM_2_LEARNFROMCLIPBOARD, + + PARAM_3_CONSTVAL, + PARAM_3_MIN, + PARAM_3_MAX, + PARAM_3_PARAMID, + PARAM_3_LEARNFROMCLIPBOARD, + + PARAM_4_CONSTVAL, + PARAM_4_MIN, + PARAM_4_MAX, + PARAM_4_PARAMID, + PARAM_4_LEARNFROMCLIPBOARD, + + PARAM_5_CONSTVAL, + PARAM_5_MIN, + PARAM_5_MAX, + PARAM_5_PARAMID, + PARAM_5_LEARNFROMCLIPBOARD, + + PARAM_6_CONSTVAL, + PARAM_6_MIN, + PARAM_6_MAX, + PARAM_6_PARAMID, + PARAM_6_LEARNFROMCLIPBOARD, + + PARAM_7_CONSTVAL, + PARAM_7_MIN, + PARAM_7_MAX, + PARAM_7_PARAMID, + PARAM_7_LEARNFROMCLIPBOARD, + + NUM_PARAMS + }; + + enum InputIds { + INPUT_0_CV, + INPUT_1_CV, + INPUT_2_CV, + INPUT_3_CV, + INPUT_4_CV, + INPUT_5_CV, + INPUT_6_CV, + INPUT_7_CV, + NUM_INPUTS + }; + + enum OutputIds { + NUM_OUTPUTS + }; + + enum LightIds { + NUM_LIGHTS + }; + + bool b_update_leds; + float last_output_state[NUM_PARAM_ROWS]; + + void resetOutputState(void) { + for(uint32_t rowIdx = 0u; rowIdx < ParamProxy::NUM_PARAM_ROWS; rowIdx++) + { + last_output_state[rowIdx] = 0.0f; + } + } + + ParamProxy() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + b_update_leds = true; + resetOutputState(); + } + + void step() override; +}; + +void ParamProxy::step() { + + int rowParamBaseId = ParamProxy::PARAM_0_CONSTVAL; + + for(uint32_t rowIdx = 0u; rowIdx < ParamProxy::NUM_PARAM_ROWS; rowIdx++) + { + float curVal; + + if(inputs[rowIdx].active) + { + // Use CV input when a cable's plugged in + curVal = inputs[rowIdx].value * (1.0f / 10.0f); + + if(curVal < 0.0f) + curVal = 0.0f; + else if(curVal > 1.0f) + curVal = 1.0f; + } + else + { + // Use constant value when nothing's connected + curVal = params[rowParamBaseId + ParamProxy::ROW_PARAM_CONSTVAL].value; + } + + int targetParamGID = int(params[rowParamBaseId + ParamProxy::ROW_PARAM_PARAMID].value); + + if(targetParamGID > 0) + { + // Rescale to min/max range + float minVal = params[rowParamBaseId + ParamProxy::ROW_PARAM_MIN].value; + float maxVal = params[rowParamBaseId + ParamProxy::ROW_PARAM_MAX].value; + if(minVal > maxVal) + { + float t = minVal; + minVal = maxVal; + maxVal = t; + } + curVal = minVal + (maxVal - minVal) * curVal; + + if(curVal != last_output_state[rowIdx]) + { + last_output_state[rowIdx] = curVal; + + // Find target module + param + // (todo) should we lock global_ui->app.mtx_param for this ? + Module *module; + int paramId; + if(vst2_find_module_and_paramid_by_unique_paramid(targetParamGID, &module, ¶mId)) + { + ModuleWidget *moduleWidget = global_ui->app.gRackWidget->findModuleWidgetByModule(module); + if(NULL != moduleWidget) + { + // Find + ParamWidget *paramWidget = moduleWidget->findParamWidgetByParamId(paramId); + if(NULL != paramWidget) + { + if(isfinite(paramWidget->minValue) && isfinite(paramWidget->maxValue)) + { + // De-Normalize parameter + global_ui->param_info.b_lock = true; + float paramRange = (paramWidget->maxValue - paramWidget->minValue); + if(paramRange > 0.0f) + { + float value = (curVal * paramRange) + paramWidget->minValue; + // Dprintf("ParamProxy: paramId=%d value=%f min=%f max=%f\n", paramId, value, paramWidget->minValue, paramWidget->maxValue); + engineSetParam(module, paramId, value, false/*bVSTAutomate*/); + + // Update UI widget + paramWidget->setValue(value); + } + global_ui->param_info.b_lock = false; + } + } + } + } // if find paramid + } // if value has changed + } // if targetParamGID > 0 + + // Next param row + rowParamBaseId += ParamProxy::NUM_ROW_PARAMS; + + } // loop param rows +} + + +struct ParamProxyWidget : ModuleWidget { + ParamWidget *bt_widgets[ParamProxy::NUM_PARAM_ROWS]; + + ParamProxyWidget(ParamProxy *module); + + void draw(NVGcontext *vg) override; + + void fromJson(json_t *rootJ) override { + ModuleWidget::fromJson(rootJ); + ParamProxy *ppMod = dynamic_cast(module); + ppMod->b_update_leds = true; + ppMod->resetOutputState(); + } +}; + +ParamProxyWidget::ParamProxyWidget(ParamProxy *module) : ModuleWidget(module) { + setPanel(SVG::load(assetGlobal("res/Core/ParamProxy.svg"))); + + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + + sF32 cy = 63.0f; + int rowParamBaseId = ParamProxy::PARAM_0_CONSTVAL; + + for(uint32_t rowIdx = 0u; rowIdx < ParamProxy::NUM_PARAM_ROWS; rowIdx++) + { + sF32 cx = 2.0f; + sF32 cyk = cy + 1.2f; // knob + sF32 cyt = cy + 3.3f; // copyfromclipboard button + +#define PORT_STX 27.0f +#define KNOB_STX 25.0f + + // CV Input + addInput(Port::create(Vec(cx, cy), Port::INPUT, module, ParamProxy::INPUT_0_CV + int32_t(rowIdx))); + cx += PORT_STX; + + // Const Val + addParam(ParamWidget::create(Vec(cx, cyk), module, rowParamBaseId + ParamProxy::ROW_PARAM_CONSTVAL, 0.0f, 1.0f, 0.5f)); + cx += KNOB_STX; + + // Min Val + addParam(ParamWidget::create(Vec(cx, cyk), module, rowParamBaseId + ParamProxy::ROW_PARAM_MIN, 0.0f, 1.0f, 0.0f)); + cx += KNOB_STX; + + // Max Val + addParam(ParamWidget::create(Vec(cx, cyk), module, rowParamBaseId + ParamProxy::ROW_PARAM_MAX, 0.0f, 1.0f, 1.0f)); + cx += KNOB_STX; + + // Param Id + ParamWidget *idWidget = ParamWidget::create(Vec(cx, cyk), module, rowParamBaseId + ParamProxy::ROW_PARAM_PARAMID, 0.0f, 10000.0f, 0.0f); + addParam(idWidget); + cx += KNOB_STX; + + // Learn from clipboard button + ParamProxyTrigButton *bt = ParamWidget::create(Vec(cx, cyt), module, rowParamBaseId + ParamProxy::ROW_PARAM_LEARNFROMCLIPBOARD, 0.0f, 1.0f, 0.0f); + bt->id_widget = idWidget; + bt_widgets[rowIdx] = bt; + addParam(bt); + + // Next param row (aligned to row height of Core.Notes module) + cy += 36.0f; + rowParamBaseId += ParamProxy::NUM_ROW_PARAMS; + } + + module->b_update_leds = true; +} + +void ParamProxyWidget::draw(NVGcontext *vg) { + + // Highlight button when the corresponding row has a valid target param + ParamProxy *ppMod = dynamic_cast(module); + if(ppMod->b_update_leds) + { + ppMod->b_update_leds = false; + int paramIdParamId = ParamProxy::PARAM_0_PARAMID; + + for(uint32_t rowIdx = 0u; rowIdx < ParamProxy::NUM_PARAM_ROWS; rowIdx++) + { + float btState = (module->params[paramIdParamId].value > 0.0f) ? 1.0f : 0.0f; + bt_widgets[rowIdx]->setValue(btState); + paramIdParamId += ParamProxy::NUM_ROW_PARAMS; + } + + } + + ModuleWidget::draw(vg); +} + +void RoundSmallBlackKnobParamId::onChange(EventChange &e) { + RoundSmallBlackKnob::onChange(e); + ParamProxy *ppMod = dynamic_cast(module); + ppMod->b_update_leds = true; +} + +void ParamProxyTrigButton::onAction(EventAction &e) { + if(-1 != rack::global_ui->param_info.gid_clipboard) + { + printf("xxx ParamProxyTrigButton: copy clipboard param id %d to proxy param %d\n", + rack::global_ui->param_info.gid_clipboard, + paramId - 1/*ROW_PARAM_PARAMID*/ + ); + float gidf = float(rack::global_ui->param_info.gid_clipboard); + module->params[paramId - 1/*ROW_PARAM_PARAMID*/].value = gidf; + id_widget->setValue(gidf); + setValue((gidf > 0.0f) ? 1.0f : 0.0f); + ParamProxy *ppMod = dynamic_cast(module); + ppMod->b_update_leds = true; + } +} + +void ParamProxyTrigButton::onDragEnd(EventDragEnd &e) { + ParamProxy *ppMod = dynamic_cast(module); + ppMod->b_update_leds = true; +} + +RACK_PLUGIN_MODEL_INIT(Core, ParamProxy) { + Model *modelParamProxy = Model::create("Core", "ParamProxy", "ParamProxy", CONTROLLER_TAG); + return modelParamProxy; +} diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 91c4b0fe..c48d67f9 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -389,7 +389,8 @@ void ModuleWidget::onHoverKey(EventHoverKey &e) { case 'w': if (windowIsModPressed() && !windowIsShiftPressed()) { global_ui->param_info.value_clipboard = global_ui->param_info.last_param_value; - printf("xxx CopyParamItem: value=%f\n", global_ui->param_info.value_clipboard); + global_ui->param_info.gid_clipboard = global_ui->param_info.last_param_gid; + printf("xxx CopyParamItem: value=%f id=%d\n", global_ui->param_info.value_clipboard, global_ui->param_info.gid_clipboard); e.consumed = true; return; } diff --git a/src/engine.cpp b/src/engine.cpp index 454f1f6b..32a40e47 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -29,9 +29,10 @@ extern void log_printf(const char *logData, ...); #define Dprintf printf #endif // USE_LOG_PRINTF -namespace rack { +namespace rack { +bool vst2_find_module_and_paramid_by_unique_paramid(int uniqueParamId, Module**retModule, int *retParamId); float Light::getBrightness() { // LEDs are diodes, so don't allow reverse current. @@ -268,7 +269,7 @@ static int loc_vst2_find_unique_param_by_module_and_paramid(Module *module, int return module->vst2_unique_param_base_id + paramId; } -static bool loc_vst2_find_module_and_paramid_by_unique_paramid(int uniqueParamId, Module**retModule, int *retParamId) { +bool vst2_find_module_and_paramid_by_unique_paramid(int uniqueParamId, Module**retModule, int *retParamId) { if(uniqueParamId >= 0) { if(uniqueParamId < VST2_MAX_UNIQUE_PARAM_IDS) @@ -360,7 +361,7 @@ void vst2_handle_queued_params(void) { { Module *module; int paramId; - if(loc_vst2_find_module_and_paramid_by_unique_paramid(qp.unique_id, &module, ¶mId)) + if(vst2_find_module_and_paramid_by_unique_paramid(qp.unique_id, &module, ¶mId)) { ModuleWidget *moduleWidget = global_ui->app.gRackWidget->findModuleWidgetByModule(module); if(NULL != moduleWidget) @@ -412,7 +413,7 @@ void vst2_handle_queued_params(void) { float vst2_get_param(int uniqueParamId) { Module *module; int paramId; - if(loc_vst2_find_module_and_paramid_by_unique_paramid(uniqueParamId, &module, ¶mId)) + if(vst2_find_module_and_paramid_by_unique_paramid(uniqueParamId, &module, ¶mId)) { if(sUI(paramId) < sUI(module->params.size())) // paranoia { @@ -436,10 +437,10 @@ float vst2_get_param(int uniqueParamId) { return 0.0f; } -void vst2_get_param_name (int uniqueParamId, char *s, int sMaxLen) { +void vst2_get_param_name(int uniqueParamId, char *s, int sMaxLen) { Module *module; int paramId; - if(loc_vst2_find_module_and_paramid_by_unique_paramid(uniqueParamId, &module, ¶mId)) + if(vst2_find_module_and_paramid_by_unique_paramid(uniqueParamId, &module, ¶mId)) { if(sUI(paramId) < sUI(module->params.size())) // paranoia { diff --git a/vst2_bin/res/ComponentLibrary/LEDButtonLit.svg b/vst2_bin/res/ComponentLibrary/LEDButtonLit.svg new file mode 100644 index 00000000..40f3bfab --- /dev/null +++ b/vst2_bin/res/ComponentLibrary/LEDButtonLit.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/vst2_bin/res/Core/HalfNotes.svg b/vst2_bin/res/Core/HalfNotes.svg new file mode 100644 index 00000000..0ccb88ef --- /dev/null +++ b/vst2_bin/res/Core/HalfNotes.svg @@ -0,0 +1,113 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/vst2_bin/res/Core/ParamProxy.svg b/vst2_bin/res/Core/ParamProxy.svg new file mode 100644 index 00000000..d2a581e3 --- /dev/null +++ b/vst2_bin/res/Core/ParamProxy.svg @@ -0,0 +1,475 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +