| @@ -87,20 +87,29 @@ struct ExponentialSlewLimiter { | |||||
| \f$ \frac{dy}{dt} = x \lambda \f$. | \f$ \frac{dy}{dt} = x \lambda \f$. | ||||
| */ | */ | ||||
| struct ExponentialFilter { | struct ExponentialFilter { | ||||
| float out = 0.f; | |||||
| float out; | |||||
| float lambda = 0.f; | float lambda = 0.f; | ||||
| ExponentialFilter() { | |||||
| reset(); | |||||
| } | |||||
| void reset() { | void reset() { | ||||
| out = 0.f; | |||||
| out = NAN; | |||||
| } | } | ||||
| float process(float deltaTime, float in) { | float process(float deltaTime, float in) { | ||||
| float y = out + (in - out) * lambda * deltaTime; | |||||
| // If no change was detected, assume float granularity is too small and snap output to input | |||||
| if (out == y) | |||||
| if (std::isnan(out)) { | |||||
| out = in; | out = in; | ||||
| else | |||||
| out = y; | |||||
| } | |||||
| else { | |||||
| float y = out + (in - out) * lambda * deltaTime; | |||||
| // If no change was detected, assume float granularity is too small and snap output to input | |||||
| if (out == y) | |||||
| out = in; | |||||
| else | |||||
| out = y; | |||||
| } | |||||
| return out; | return out; | ||||
| } | } | ||||
| @@ -29,7 +29,11 @@ struct Engine { | |||||
| float getSampleTime(); | float getSampleTime(); | ||||
| // Modules | // Modules | ||||
| /** Does not transfer pointer ownership. */ | |||||
| /** Adds a module to the rack engine. | |||||
| The module ID must not be taken by another module. | |||||
| If the module ID is -1, an ID is automatically assigned. | |||||
| Does not transfer pointer ownership. | |||||
| */ | |||||
| void addModule(Module *module); | void addModule(Module *module); | ||||
| void removeModule(Module *module); | void removeModule(Module *module); | ||||
| Module *getModule(int moduleId); | Module *getModule(int moduleId); | ||||
| @@ -38,7 +42,11 @@ struct Engine { | |||||
| void bypassModule(Module *module, bool bypass); | void bypassModule(Module *module, bool bypass); | ||||
| // Cables | // Cables | ||||
| /** Does not transfer pointer ownership. */ | |||||
| /** Adds a cable to the rack engine. | |||||
| The cable ID must not be taken by another cable. | |||||
| If the cable ID is -1, an ID is automatically assigned. | |||||
| Does not transfer pointer ownership. | |||||
| */ | |||||
| void addCable(Cable *cable); | void addCable(Cable *cable); | ||||
| void removeCable(Cable *cable); | void removeCable(Cable *cable); | ||||
| @@ -47,6 +55,12 @@ struct Engine { | |||||
| float getParam(Module *module, int paramId); | float getParam(Module *module, int paramId); | ||||
| void setSmoothParam(Module *module, int paramId, float value); | void setSmoothParam(Module *module, int paramId, float value); | ||||
| float getSmoothParam(Module *module, int paramId); | float getSmoothParam(Module *module, int paramId); | ||||
| void setTouchedParam(Module *module, int paramId); | |||||
| void getTouchedParam(Module *&module, int ¶mId); | |||||
| // ModuleHandles | |||||
| void addModuleHandle(ModuleHandle *moduleHandle); | |||||
| void removeModuleHandle(ModuleHandle *moduleHandle); | |||||
| }; | }; | ||||
| @@ -56,5 +56,12 @@ struct Module { | |||||
| }; | }; | ||||
| struct ModuleHandle { | |||||
| int id = -1; | |||||
| /** Automatically set when added to the Engine. */ | |||||
| Module *module = NULL; | |||||
| }; | |||||
| } // namespace engine | } // namespace engine | ||||
| } // namespace rack | } // namespace rack | ||||
| @@ -81,6 +81,9 @@ struct Param { | |||||
| return value; | return value; | ||||
| } | } | ||||
| /* Clamps and set the value. | |||||
| Don't call this directly from Modules. Use `APP->engine->setParam()`. | |||||
| */ | |||||
| void setValue(float value) { | void setValue(float value) { | ||||
| this->value = math::clamp(value, minValue, maxValue); | this->value = math::clamp(value, minValue, maxValue); | ||||
| } | } | ||||
| @@ -1,20 +0,0 @@ | |||||
| #pragma once | |||||
| #include "common.hpp" | |||||
| #include <jansson.h> | |||||
| namespace rack { | |||||
| namespace engine { | |||||
| struct ParamMap { | |||||
| int moduleId = -1; | |||||
| int paramId = -1; | |||||
| json_t *toJson(); | |||||
| void fromJson(json_t *rootJ); | |||||
| }; | |||||
| } // namespace engine | |||||
| } // namespace rack | |||||
| @@ -72,7 +72,6 @@ | |||||
| #include "engine/Module.hpp" | #include "engine/Module.hpp" | ||||
| #include "engine/Param.hpp" | #include "engine/Param.hpp" | ||||
| #include "engine/Cable.hpp" | #include "engine/Cable.hpp" | ||||
| #include "engine/ParamMap.hpp" | |||||
| #include "plugin/Plugin.hpp" | #include "plugin/Plugin.hpp" | ||||
| #include "plugin/Model.hpp" | #include "plugin/Model.hpp" | ||||
| @@ -91,7 +91,7 @@ struct MIDI_CC : Module { | |||||
| } | } | ||||
| // Allow CC to be negative if the 8th bit is set. | // Allow CC to be negative if the 8th bit is set. | ||||
| // The gamepad driver abuses this, for example. | // The gamepad driver abuses this, for example. | ||||
| values[cc] = msg.data2; | |||||
| values[cc] = clamp(msg.data2, -127, 127); | |||||
| } | } | ||||
| json_t *dataToJson() override { | json_t *dataToJson() override { | ||||
| @@ -16,26 +16,38 @@ struct MIDI_Map : Module { | |||||
| }; | }; | ||||
| midi::InputQueue midiInput; | midi::InputQueue midiInput; | ||||
| int8_t values[128]; | |||||
| int learningId; | int learningId; | ||||
| int lastLearnedCc; | |||||
| int learnedCcs[8]; | int learnedCcs[8]; | ||||
| ModuleHandle learnedModuleHandles[8]; | |||||
| int learnedParamIds[8]; | |||||
| int8_t values[128]; | |||||
| dsp::ExponentialFilter valueFilters[8]; | dsp::ExponentialFilter valueFilters[8]; | ||||
| ParamMap paramMaps[8]; | |||||
| MIDI_Map() { | MIDI_Map() { | ||||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | ||||
| for (int i = 0; i < 8; i++) { | for (int i = 0; i < 8; i++) { | ||||
| valueFilters[i].lambda = 40.f; | |||||
| valueFilters[i].lambda = 60.f; | |||||
| } | } | ||||
| onReset(); | onReset(); | ||||
| } | } | ||||
| ~MIDI_Map() { | |||||
| for (int i = 0; i < 8; i++) { | |||||
| unloadModuleHandle(i); | |||||
| } | |||||
| } | |||||
| void onReset() override { | void onReset() override { | ||||
| learningId = -1; | learningId = -1; | ||||
| lastLearnedCc = -1; | |||||
| for (int i = 0; i < 8; i++) { | for (int i = 0; i < 8; i++) { | ||||
| learnedCcs[i] = -1; | learnedCcs[i] = -1; | ||||
| unloadModuleHandle(i); | |||||
| learnedModuleHandles[i].id = -1; | |||||
| learnedParamIds[i] = 0; | |||||
| valueFilters[i].reset(); | |||||
| } | |||||
| for (int i = 0; i < 128; i++) { | |||||
| values[i] = -1; | |||||
| } | } | ||||
| midiInput.reset(); | midiInput.reset(); | ||||
| } | } | ||||
| @@ -48,23 +60,43 @@ struct MIDI_Map : Module { | |||||
| float deltaTime = APP->engine->getSampleTime(); | float deltaTime = APP->engine->getSampleTime(); | ||||
| // Check touched params when learning | |||||
| if (learningId >= 0) { | |||||
| Module *module; | |||||
| int paramId; | |||||
| APP->engine->getTouchedParam(module, paramId); | |||||
| APP->engine->setTouchedParam(NULL, 0); | |||||
| if (module) { | |||||
| unloadModuleHandle(learningId); | |||||
| learnedModuleHandles[learningId].id = module->id; | |||||
| loadModuleHandle(learningId); | |||||
| learnedParamIds[learningId] = paramId; | |||||
| commitLearn(); | |||||
| } | |||||
| } | |||||
| // Step channels | |||||
| for (int i = 0; i < 8; i++) { | for (int i = 0; i < 8; i++) { | ||||
| // Get module | |||||
| int moduleId = paramMaps[i].moduleId; | |||||
| if (moduleId < 0) | |||||
| int cc = learnedCcs[i]; | |||||
| if (cc < 0) | |||||
| continue; | continue; | ||||
| Module *module = APP->engine->getModule(moduleId); | |||||
| // Check if CC value has been set | |||||
| if (values[cc] < 0) | |||||
| continue; | |||||
| // Get module | |||||
| Module *module = learnedModuleHandles[i].module; | |||||
| if (!module) | if (!module) | ||||
| continue; | continue; | ||||
| // Get param | // Get param | ||||
| int paramId = paramMaps[i].paramId; | |||||
| int paramId = learnedParamIds[i]; | |||||
| Param *param = &module->params[paramId]; | Param *param = &module->params[paramId]; | ||||
| if (!param->isBounded()) | if (!param->isBounded()) | ||||
| continue; | continue; | ||||
| // Set param | // Set param | ||||
| float v = rescale(values[i], 0, 127, param->minValue, param->maxValue); | |||||
| float v = rescale(values[cc], 0, 127, 0.f, 1.f); | |||||
| v = valueFilters[i].process(deltaTime, v); | v = valueFilters[i].process(deltaTime, v); | ||||
| module->params[paramId].setValue(v); | |||||
| v = rescale(v, 0.f, 1.f, param->minValue, param->maxValue); | |||||
| APP->engine->setParam(module, paramId, v); | |||||
| } | } | ||||
| } | } | ||||
| @@ -81,17 +113,44 @@ struct MIDI_Map : Module { | |||||
| void processCC(midi::Message msg) { | void processCC(midi::Message msg) { | ||||
| uint8_t cc = msg.getNote(); | uint8_t cc = msg.getNote(); | ||||
| // Learn | // Learn | ||||
| if (learningId >= 0 && values[cc] != msg.data2) { | |||||
| if (lastLearnedCc != cc) { | |||||
| learnedCcs[learningId] = cc; | |||||
| lastLearnedCc = cc; | |||||
| if (++learningId >= 8) | |||||
| learningId = -1; | |||||
| } | |||||
| if (learningId >= 0 && values[cc] != msg.getValue()) { | |||||
| learnedCcs[learningId] = cc; | |||||
| commitLearn(); | |||||
| } | } | ||||
| values[cc] = msg.getValue(); | values[cc] = msg.getValue(); | ||||
| } | } | ||||
| void loadModuleHandle(int i) { | |||||
| if (learnedModuleHandles[i].id >= 0) { | |||||
| APP->engine->addModuleHandle(&learnedModuleHandles[i]); | |||||
| } | |||||
| } | |||||
| void unloadModuleHandle(int i) { | |||||
| if (learnedModuleHandles[i].id >= 0) { | |||||
| APP->engine->removeModuleHandle(&learnedModuleHandles[i]); | |||||
| } | |||||
| } | |||||
| void commitLearn() { | |||||
| if (learningId < 0) | |||||
| return; | |||||
| if (learnedModuleHandles[learningId].id < 0) | |||||
| return; | |||||
| if (learnedCcs[learningId] < 0) | |||||
| return; | |||||
| learningId++; | |||||
| if (learningId >= 8) | |||||
| learningId = -1; | |||||
| } | |||||
| void clearLearn(int id) { | |||||
| learnedCcs[id] = -1; | |||||
| unloadModuleHandle(id); | |||||
| learnedModuleHandles[id].id = -1; | |||||
| loadModuleHandle(id); | |||||
| } | |||||
| json_t *dataToJson() override { | json_t *dataToJson() override { | ||||
| json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
| @@ -101,11 +160,17 @@ struct MIDI_Map : Module { | |||||
| } | } | ||||
| json_object_set_new(rootJ, "ccs", ccsJ); | json_object_set_new(rootJ, "ccs", ccsJ); | ||||
| json_t *paramMapsJ = json_array(); | |||||
| json_t *moduleIdsJ = json_array(); | |||||
| for (int i = 0; i < 8; i++) { | for (int i = 0; i < 8; i++) { | ||||
| json_array_append_new(paramMapsJ, paramMaps[i].toJson()); | |||||
| json_array_append_new(moduleIdsJ, json_integer(learnedModuleHandles[i].id)); | |||||
| } | } | ||||
| json_object_set_new(rootJ, "paramMaps", paramMapsJ); | |||||
| json_object_set_new(rootJ, "moduleIds", moduleIdsJ); | |||||
| json_t *paramIdsJ = json_array(); | |||||
| for (int i = 0; i < 8; i++) { | |||||
| json_array_append_new(paramIdsJ, json_integer(learnedParamIds[i])); | |||||
| } | |||||
| json_object_set_new(rootJ, "paramIds", paramIdsJ); | |||||
| json_object_set_new(rootJ, "midi", midiInput.toJson()); | json_object_set_new(rootJ, "midi", midiInput.toJson()); | ||||
| return rootJ; | return rootJ; | ||||
| @@ -121,12 +186,23 @@ struct MIDI_Map : Module { | |||||
| } | } | ||||
| } | } | ||||
| json_t *paramMapsJ = json_object_get(rootJ, "paramMaps"); | |||||
| if (paramMapsJ) { | |||||
| json_t *moduleIdsJ = json_object_get(rootJ, "moduleIds"); | |||||
| if (moduleIdsJ) { | |||||
| for (int i = 0; i < 8; i++) { | for (int i = 0; i < 8; i++) { | ||||
| json_t *paramMapJ = json_array_get(paramMapsJ, i); | |||||
| if (paramMapJ) | |||||
| paramMaps[i].fromJson(paramMapJ); | |||||
| json_t *moduleIdJ = json_array_get(moduleIdsJ, i); | |||||
| unloadModuleHandle(i); | |||||
| if (moduleIdJ) | |||||
| learnedModuleHandles[i].id = json_integer_value(moduleIdJ); | |||||
| loadModuleHandle(i); | |||||
| } | |||||
| } | |||||
| json_t *paramIdsJ = json_object_get(rootJ, "paramIds"); | |||||
| if (paramIdsJ) { | |||||
| for (int i = 0; i < 8; i++) { | |||||
| json_t *paramIdJ = json_array_get(paramIdsJ, i); | |||||
| if (paramIdJ) | |||||
| learnedParamIds[i] = json_integer_value(paramIdJ); | |||||
| } | } | ||||
| } | } | ||||
| @@ -145,10 +221,17 @@ struct MIDI_MapChoice : LedDisplayChoice { | |||||
| this->module = module; | this->module = module; | ||||
| } | } | ||||
| void onAction(const event::Action &e) override { | |||||
| if (!module) | |||||
| return; | |||||
| module->lastLearnedCc = -1; | |||||
| void onButton(const event::Button &e) override { | |||||
| if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||||
| APP->engine->setTouchedParam(NULL, 0); | |||||
| e.consume(this); | |||||
| } | |||||
| if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | |||||
| if (module) { | |||||
| module->clearLearn(id); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| void onSelect(const event::Select &e) override { | void onSelect(const event::Select &e) override { | ||||
| @@ -170,8 +253,6 @@ struct MIDI_MapChoice : LedDisplayChoice { | |||||
| if (!module) | if (!module) | ||||
| return; | return; | ||||
| if (module->learningId == id) { | if (module->learningId == id) { | ||||
| text = "Mapping..."; | |||||
| color.a = 1.0; | |||||
| bgColor = color; | bgColor = color; | ||||
| bgColor.a = 0.15; | bgColor.a = 0.15; | ||||
| @@ -180,22 +261,56 @@ struct MIDI_MapChoice : LedDisplayChoice { | |||||
| APP->event->setSelected(this); | APP->event->setSelected(this); | ||||
| } | } | ||||
| else { | else { | ||||
| if (module->learnedCcs[id] >= 0) { | |||||
| text = string::f("CC%d", module->learnedCcs[id]); | |||||
| color.a = 1.0; | |||||
| bgColor = nvgRGBA(0, 0, 0, 0); | |||||
| bgColor = nvgRGBA(0, 0, 0, 0); | |||||
| // HACK | |||||
| if (APP->event->selectedWidget == this) | |||||
| APP->event->setSelected(NULL); | |||||
| } | |||||
| text = ""; | |||||
| color.a = 1.0; | |||||
| if (module->learnedCcs[id] >= 0) { | |||||
| text += string::f("CC%d ", module->learnedCcs[id]); | |||||
| } | |||||
| if (module->learnedModuleHandles[id].id >= 0) { | |||||
| text += getParamName(); | |||||
| } | |||||
| if (!(module->learnedCcs[id] >= 0) && !(module->learnedModuleHandles[id].id >= 0)) { | |||||
| if (module->learningId == id) { | |||||
| text = "Mapping..."; | |||||
| } | } | ||||
| else { | else { | ||||
| text = "Unmapped"; | text = "Unmapped"; | ||||
| color.a = 0.5; | color.a = 0.5; | ||||
| bgColor = nvgRGBA(0, 0, 0, 0); | |||||
| } | } | ||||
| // HACK | |||||
| if (APP->event->selectedWidget == this) | |||||
| APP->event->setSelected(NULL); | |||||
| } | } | ||||
| } | } | ||||
| std::string getParamName() { | |||||
| if (!module) | |||||
| return ""; | |||||
| ModuleHandle *moduleHandle = &module->learnedModuleHandles[id]; | |||||
| if (moduleHandle->id < 0) | |||||
| return ""; | |||||
| ModuleWidget *mw = APP->scene->rackWidget->getModule(moduleHandle->id); | |||||
| if (!mw) | |||||
| return ""; | |||||
| // Get the Module from the ModuleWidget instead of the ModuleHandle. | |||||
| // I think this is more elegant since this method is called in the app world instead of the engine world. | |||||
| Module *m = mw->module; | |||||
| if (!m) | |||||
| return ""; | |||||
| int paramId = module->learnedParamIds[id]; | |||||
| if (paramId >= (int) m->params.size()) | |||||
| return ""; | |||||
| Param *param = &m->params[paramId]; | |||||
| std::string s; | |||||
| s += mw->model->name; | |||||
| s += " "; | |||||
| s += param->label; | |||||
| return s; | |||||
| } | |||||
| }; | }; | ||||
| @@ -4,6 +4,7 @@ | |||||
| #include "app/Scene.hpp" | #include "app/Scene.hpp" | ||||
| #include "app/ParamQuantity.hpp" | #include "app/ParamQuantity.hpp" | ||||
| #include "app.hpp" | #include "app.hpp" | ||||
| #include "engine/Engine.hpp" | |||||
| #include "settings.hpp" | #include "settings.hpp" | ||||
| #include "random.hpp" | #include "random.hpp" | ||||
| #include "history.hpp" | #include "history.hpp" | ||||
| @@ -143,6 +144,13 @@ void ParamWidget::draw(const widget::DrawContext &ctx) { | |||||
| } | } | ||||
| void ParamWidget::onButton(const event::Button &e) { | void ParamWidget::onButton(const event::Button &e) { | ||||
| // Touch parameter | |||||
| if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & WINDOW_MOD_MASK) == 0) { | |||||
| if (paramQuantity) { | |||||
| APP->engine->setTouchedParam(paramQuantity->module, paramQuantity->paramId); | |||||
| } | |||||
| } | |||||
| // Right click to open context menu | // Right click to open context menu | ||||
| if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT && (e.mods & WINDOW_MOD_MASK) == 0) { | if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT && (e.mods & WINDOW_MOD_MASK) == 0) { | ||||
| createContextMenu(); | createContextMenu(); | ||||
| @@ -127,6 +127,7 @@ struct EngineWorker { | |||||
| struct Engine::Internal { | struct Engine::Internal { | ||||
| std::vector<Module*> modules; | std::vector<Module*> modules; | ||||
| std::vector<Cable*> cables; | std::vector<Cable*> cables; | ||||
| std::vector<ModuleHandle*> moduleHandles; | |||||
| bool paused = false; | bool paused = false; | ||||
| bool running = false; | bool running = false; | ||||
| @@ -149,6 +150,9 @@ struct Engine::Internal { | |||||
| std::vector<EngineWorker> workers; | std::vector<EngineWorker> workers; | ||||
| SpinBarrier engineBarrier; | SpinBarrier engineBarrier; | ||||
| SpinBarrier workerBarrier; | SpinBarrier workerBarrier; | ||||
| Module *touchedModule = NULL; | |||||
| int touchedParamId = 0; | |||||
| }; | }; | ||||
| @@ -173,6 +177,7 @@ Engine::~Engine() { | |||||
| // If this happens, a module must have failed to remove itself before the RackWidget was destroyed. | // If this happens, a module must have failed to remove itself before the RackWidget was destroyed. | ||||
| assert(internal->cables.empty()); | assert(internal->cables.empty()); | ||||
| assert(internal->modules.empty()); | assert(internal->modules.empty()); | ||||
| assert(internal->moduleHandles.empty()); | |||||
| delete internal; | delete internal; | ||||
| } | } | ||||
| @@ -235,6 +240,7 @@ static void Engine_step(Engine *engine) { | |||||
| // Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats), or if newValue is out of bounds | // Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats), or if newValue is out of bounds | ||||
| param->setValue(smoothValue); | param->setValue(smoothValue); | ||||
| internal->smoothModule = NULL; | internal->smoothModule = NULL; | ||||
| internal->smoothParamId = 0; | |||||
| } | } | ||||
| else { | else { | ||||
| param->value = newValue; | param->value = newValue; | ||||
| @@ -400,6 +406,11 @@ void Engine::addModule(Module *module) { | |||||
| internal->nextModuleId = module->id + 1; | internal->nextModuleId = module->id + 1; | ||||
| } | } | ||||
| } | } | ||||
| // Update ModuleHandle | |||||
| for (ModuleHandle *moduleHandle : internal->moduleHandles) { | |||||
| if (moduleHandle->id == module->id) | |||||
| moduleHandle->module = module; | |||||
| } | |||||
| // Add module | // Add module | ||||
| internal->modules.push_back(module); | internal->modules.push_back(module); | ||||
| } | } | ||||
| @@ -417,6 +428,16 @@ void Engine::removeModule(Module *module) { | |||||
| assert(cable->outputModule != module); | assert(cable->outputModule != module); | ||||
| assert(cable->inputModule != module); | assert(cable->inputModule != module); | ||||
| } | } | ||||
| // Remove touched param | |||||
| if (internal->touchedModule == module) { | |||||
| internal->touchedModule = NULL; | |||||
| internal->touchedParamId = 0; | |||||
| } | |||||
| // Update ModuleHandle | |||||
| for (ModuleHandle *moduleHandle : internal->moduleHandles) { | |||||
| if (moduleHandle->id == module->id) | |||||
| moduleHandle->module = NULL; | |||||
| } | |||||
| // Check that the module actually exists | // Check that the module actually exists | ||||
| auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | ||||
| assert(it != internal->modules.end()); | assert(it != internal->modules.end()); | ||||
| @@ -537,6 +558,11 @@ void Engine::removeCable(Cable *cable) { | |||||
| void Engine::setParam(Module *module, int paramId, float value) { | void Engine::setParam(Module *module, int paramId, float value) { | ||||
| // TODO Does this need to be thread-safe? | // TODO Does this need to be thread-safe? | ||||
| // If being smoothed, cancel smoothing | |||||
| if (internal->smoothModule == module && internal->smoothParamId == paramId) { | |||||
| internal->smoothModule = NULL; | |||||
| internal->smoothParamId = 0; | |||||
| } | |||||
| module->params[paramId].value = value; | module->params[paramId].value = value; | ||||
| } | } | ||||
| @@ -551,6 +577,7 @@ void Engine::setSmoothParam(Module *module, int paramId, float value) { | |||||
| } | } | ||||
| internal->smoothParamId = paramId; | internal->smoothParamId = paramId; | ||||
| internal->smoothValue = value; | internal->smoothValue = value; | ||||
| // Set this last so the above values are valid as soon as it is set | |||||
| internal->smoothModule = module; | internal->smoothModule = module; | ||||
| } | } | ||||
| @@ -560,6 +587,39 @@ float Engine::getSmoothParam(Module *module, int paramId) { | |||||
| return getParam(module, paramId); | return getParam(module, paramId); | ||||
| } | } | ||||
| void Engine::setTouchedParam(Module *module, int paramId) { | |||||
| internal->touchedModule = module; | |||||
| internal->touchedParamId = paramId; | |||||
| } | |||||
| void Engine::getTouchedParam(Module *&module, int ¶mId) { | |||||
| module = internal->touchedModule; | |||||
| paramId = internal->touchedParamId; | |||||
| } | |||||
| void Engine::addModuleHandle(ModuleHandle *moduleHandle) { | |||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||||
| // Check that the ModuleHandle is not already added | |||||
| auto it = std::find(internal->moduleHandles.begin(), internal->moduleHandles.end(), moduleHandle); | |||||
| assert(it == internal->moduleHandles.end()); | |||||
| moduleHandle->module = getModule(moduleHandle->id); | |||||
| internal->moduleHandles.push_back(moduleHandle); | |||||
| } | |||||
| void Engine::removeModuleHandle(ModuleHandle *moduleHandle) { | |||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||||
| moduleHandle->module = NULL; | |||||
| // Check that the ModuleHandle is already added | |||||
| auto it = std::find(internal->moduleHandles.begin(), internal->moduleHandles.end(), moduleHandle); | |||||
| assert(it != internal->moduleHandles.end()); | |||||
| internal->moduleHandles.erase(it); | |||||
| } | |||||
| void EngineWorker::step() { | void EngineWorker::step() { | ||||
| engine->internal->engineBarrier.wait(); | engine->internal->engineBarrier.wait(); | ||||
| @@ -1,27 +0,0 @@ | |||||
| #include "engine/ParamMap.hpp" | |||||
| namespace rack { | |||||
| namespace engine { | |||||
| json_t *ParamMap::toJson() { | |||||
| json_t *rootJ = json_object(); | |||||
| json_object_set_new(rootJ, "moduleId", json_integer(moduleId)); | |||||
| json_object_set_new(rootJ, "paramId", json_integer(paramId)); | |||||
| return rootJ; | |||||
| } | |||||
| void ParamMap::fromJson(json_t *rootJ) { | |||||
| json_t *moduleIdJ = json_object_get(rootJ, "moduleId"); | |||||
| if (moduleIdJ) | |||||
| moduleId = json_integer_value(moduleIdJ); | |||||
| json_t *paramIdJ = json_object_get(rootJ, "paramId"); | |||||
| if (paramIdJ) | |||||
| paramId = json_integer_value(paramIdJ); | |||||
| } | |||||
| } // namespace engine | |||||
| } // namespace rack | |||||