Browse Source

Add param touch to engine. Add param learning to MIDI-Map.

tags/v1.0.0
Andrew Belt 5 years ago
parent
commit
7458b5d709
11 changed files with 268 additions and 100 deletions
  1. +16
    -7
      include/dsp/filter.hpp
  2. +16
    -2
      include/engine/Engine.hpp
  3. +7
    -0
      include/engine/Module.hpp
  4. +3
    -0
      include/engine/Param.hpp
  5. +0
    -20
      include/engine/ParamMap.hpp
  6. +0
    -1
      include/rack.hpp
  7. +1
    -1
      src/Core/MIDI_CC.cpp
  8. +157
    -42
      src/Core/MIDI_Map.cpp
  9. +8
    -0
      src/app/ParamWidget.cpp
  10. +60
    -0
      src/engine/Engine.cpp
  11. +0
    -27
      src/engine/ParamMap.cpp

+ 16
- 7
include/dsp/filter.hpp View File

@@ -87,20 +87,29 @@ struct ExponentialSlewLimiter {
\f$ \frac{dy}{dt} = x \lambda \f$.
*/
struct ExponentialFilter {
float out = 0.f;
float out;
float lambda = 0.f;

ExponentialFilter() {
reset();
}

void reset() {
out = 0.f;
out = NAN;
}

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;
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;
}



+ 16
- 2
include/engine/Engine.hpp View File

@@ -29,7 +29,11 @@ struct Engine {
float getSampleTime();

// 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 removeModule(Module *module);
Module *getModule(int moduleId);
@@ -38,7 +42,11 @@ struct Engine {
void bypassModule(Module *module, bool bypass);

// 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 removeCable(Cable *cable);

@@ -47,6 +55,12 @@ struct Engine {
float getParam(Module *module, int paramId);
void setSmoothParam(Module *module, int paramId, float value);
float getSmoothParam(Module *module, int paramId);
void setTouchedParam(Module *module, int paramId);
void getTouchedParam(Module *&module, int &paramId);

// ModuleHandles
void addModuleHandle(ModuleHandle *moduleHandle);
void removeModuleHandle(ModuleHandle *moduleHandle);
};




+ 7
- 0
include/engine/Module.hpp View File

@@ -56,5 +56,12 @@ struct Module {
};


struct ModuleHandle {
int id = -1;
/** Automatically set when added to the Engine. */
Module *module = NULL;
};


} // namespace engine
} // namespace rack

+ 3
- 0
include/engine/Param.hpp View File

@@ -81,6 +81,9 @@ struct Param {
return value;
}

/* Clamps and set the value.
Don't call this directly from Modules. Use `APP->engine->setParam()`.
*/
void setValue(float value) {
this->value = math::clamp(value, minValue, maxValue);
}


+ 0
- 20
include/engine/ParamMap.hpp View File

@@ -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

+ 0
- 1
include/rack.hpp View File

@@ -72,7 +72,6 @@
#include "engine/Module.hpp"
#include "engine/Param.hpp"
#include "engine/Cable.hpp"
#include "engine/ParamMap.hpp"

#include "plugin/Plugin.hpp"
#include "plugin/Model.hpp"


+ 1
- 1
src/Core/MIDI_CC.cpp View File

@@ -91,7 +91,7 @@ struct MIDI_CC : Module {
}
// Allow CC to be negative if the 8th bit is set.
// The gamepad driver abuses this, for example.
values[cc] = msg.data2;
values[cc] = clamp(msg.data2, -127, 127);
}

json_t *dataToJson() override {


+ 157
- 42
src/Core/MIDI_Map.cpp View File

@@ -16,26 +16,38 @@ struct MIDI_Map : Module {
};

midi::InputQueue midiInput;
int8_t values[128];
int learningId;
int lastLearnedCc;
int learnedCcs[8];
ModuleHandle learnedModuleHandles[8];
int learnedParamIds[8];
int8_t values[128];
dsp::ExponentialFilter valueFilters[8];
ParamMap paramMaps[8];

MIDI_Map() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
for (int i = 0; i < 8; i++) {
valueFilters[i].lambda = 40.f;
valueFilters[i].lambda = 60.f;
}
onReset();
}

~MIDI_Map() {
for (int i = 0; i < 8; i++) {
unloadModuleHandle(i);
}
}

void onReset() override {
learningId = -1;
lastLearnedCc = -1;
for (int i = 0; i < 8; i++) {
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();
}
@@ -48,23 +60,43 @@ struct MIDI_Map : Module {

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++) {
// Get module
int moduleId = paramMaps[i].moduleId;
if (moduleId < 0)
int cc = learnedCcs[i];
if (cc < 0)
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)
continue;
// Get param
int paramId = paramMaps[i].paramId;
int paramId = learnedParamIds[i];
Param *param = &module->params[paramId];
if (!param->isBounded())
continue;
// 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);
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) {
uint8_t cc = msg.getNote();
// 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();
}

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 *rootJ = json_object();

@@ -101,11 +160,17 @@ struct MIDI_Map : Module {
}
json_object_set_new(rootJ, "ccs", ccsJ);

json_t *paramMapsJ = json_array();
json_t *moduleIdsJ = json_array();
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());
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++) {
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;
}

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 {
@@ -170,8 +253,6 @@ struct MIDI_MapChoice : LedDisplayChoice {
if (!module)
return;
if (module->learningId == id) {
text = "Mapping...";
color.a = 1.0;
bgColor = color;
bgColor.a = 0.15;

@@ -180,22 +261,56 @@ struct MIDI_MapChoice : LedDisplayChoice {
APP->event->setSelected(this);
}
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 {
text = "Unmapped";
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;
}
};




+ 8
- 0
src/app/ParamWidget.cpp View File

@@ -4,6 +4,7 @@
#include "app/Scene.hpp"
#include "app/ParamQuantity.hpp"
#include "app.hpp"
#include "engine/Engine.hpp"
#include "settings.hpp"
#include "random.hpp"
#include "history.hpp"
@@ -143,6 +144,13 @@ void ParamWidget::draw(const widget::DrawContext &ctx) {
}

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
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT && (e.mods & WINDOW_MOD_MASK) == 0) {
createContextMenu();


+ 60
- 0
src/engine/Engine.cpp View File

@@ -127,6 +127,7 @@ struct EngineWorker {
struct Engine::Internal {
std::vector<Module*> modules;
std::vector<Cable*> cables;
std::vector<ModuleHandle*> moduleHandles;
bool paused = false;

bool running = false;
@@ -149,6 +150,9 @@ struct Engine::Internal {
std::vector<EngineWorker> workers;
SpinBarrier engineBarrier;
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.
assert(internal->cables.empty());
assert(internal->modules.empty());
assert(internal->moduleHandles.empty());

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
param->setValue(smoothValue);
internal->smoothModule = NULL;
internal->smoothParamId = 0;
}
else {
param->value = newValue;
@@ -400,6 +406,11 @@ void Engine::addModule(Module *module) {
internal->nextModuleId = module->id + 1;
}
}
// Update ModuleHandle
for (ModuleHandle *moduleHandle : internal->moduleHandles) {
if (moduleHandle->id == module->id)
moduleHandle->module = module;
}
// Add module
internal->modules.push_back(module);
}
@@ -417,6 +428,16 @@ void Engine::removeModule(Module *module) {
assert(cable->outputModule != 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
auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
assert(it != internal->modules.end());
@@ -537,6 +558,11 @@ void Engine::removeCable(Cable *cable) {

void Engine::setParam(Module *module, int paramId, float value) {
// 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;
}

@@ -551,6 +577,7 @@ void Engine::setSmoothParam(Module *module, int paramId, float value) {
}
internal->smoothParamId = paramId;
internal->smoothValue = value;
// Set this last so the above values are valid as soon as it is set
internal->smoothModule = module;
}

@@ -560,6 +587,39 @@ float Engine::getSmoothParam(Module *module, int paramId) {
return getParam(module, paramId);
}

void Engine::setTouchedParam(Module *module, int paramId) {
internal->touchedModule = module;
internal->touchedParamId = paramId;
}

void Engine::getTouchedParam(Module *&module, int &paramId) {
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() {
engine->internal->engineBarrier.wait();


+ 0
- 27
src/engine/ParamMap.cpp View File

@@ -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

Loading…
Cancel
Save