Browse Source

Merge branch 'stackable-inputs' into v2

tags/v2.5.0
Andrew Belt 7 months ago
parent
commit
c2a403bab8
4 changed files with 132 additions and 103 deletions
  1. +1
    -0
      include/app/RackWidget.hpp
  2. +33
    -54
      src/app/PortWidget.cpp
  3. +10
    -0
      src/app/RackWidget.cpp
  4. +88
    -49
      src/engine/Engine.cpp

+ 1
- 0
include/app/RackWidget.hpp View File

@@ -127,6 +127,7 @@ struct RackWidget : widget::OpaqueWidget {
/** Returns the most recently added complete cable connected to the given Port, i.e. the top of the stack. */ /** Returns the most recently added complete cable connected to the given Port, i.e. the top of the stack. */
CableWidget* getTopCable(PortWidget* port); CableWidget* getTopCable(PortWidget* port);
CableWidget* getCable(int64_t cableId); CableWidget* getCable(int64_t cableId);
CableWidget* getCable(PortWidget* outputPort, PortWidget* inputPort);
std::vector<CableWidget*> getCompleteCables(); std::vector<CableWidget*> getCompleteCables();
/** Returns all cables attached to port, complete or not. */ /** Returns all cables attached to port, complete or not. */
std::vector<CableWidget*> getCablesOnPort(PortWidget* port); std::vector<CableWidget*> getCablesOnPort(PortWidget* port);


+ 33
- 54
src/app/PortWidget.cpp View File

@@ -250,7 +250,7 @@ void PortWidget::createContextMenu() {
!topCw !topCw
)); ));


if (type == engine::Port::INPUT) {
{
PortCloneCableItem* item = createMenuItem<PortCloneCableItem>("Duplicate top cable", RACK_MOD_CTRL_NAME "+drag"); PortCloneCableItem* item = createMenuItem<PortCloneCableItem>("Duplicate top cable", RACK_MOD_CTRL_NAME "+drag");
item->disabled = !topCw; item->disabled = !topCw;
item->pw = this; item->pw = this;
@@ -261,11 +261,9 @@ void PortWidget::createContextMenu() {
menu->addChild(new ui::MenuSeparator); menu->addChild(new ui::MenuSeparator);


// New cable items // New cable items
bool createCableDisabled = (type == engine::Port::INPUT) && topCw;
for (NVGcolor color : settings::cableColors) { for (NVGcolor color : settings::cableColors) {
// Include extra leading spaces for the color circle // Include extra leading spaces for the color circle
PortCreateCableItem* item = createMenuItem<PortCreateCableItem>("New cable", "Click+drag"); PortCreateCableItem* item = createMenuItem<PortCreateCableItem>("New cable", "Click+drag");
item->disabled = createCableDisabled;
item->pw = this; item->pw = this;
item->color = color; item->color = color;
menu->addChild(item); menu->addChild(item);
@@ -366,35 +364,31 @@ void PortWidget::onDragStart(const DragStartEvent& e) {
}); });


CableWidget* cw = NULL; CableWidget* cw = NULL;
if (internal->overrideCreateCable) {
int mods = APP->window->getMods();
if (internal->overrideCreateCable || (mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
// Create cable with Ctrl+drag or PortCreateCableItem
// Keep cable NULL. Will be created below // Keep cable NULL. Will be created below
} }
else if (internal->overrideCloneCw || (APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL) {
if (type == engine::Port::OUTPUT) {
// Ctrl-clicking an output creates a new cable.
// Keep cable NULL. Will be created below
}
else {
// Ctrl-clicking an input clones the cable already patched to it.
CableWidget* cloneCw;
if (internal->overrideCloneCw)
cloneCw = internal->overrideCloneCw;
else if (internal->overrideCloneCw || (mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) {
// Clone top cable with Ctrl+shift+drag or PortCloneCableItem
CableWidget* cloneCw = internal->overrideCloneCw;
if (!cloneCw)
cloneCw = APP->scene->rack->getTopCable(this);

if (cloneCw) {
cw = new CableWidget;
cw->color = cloneCw->color;
if (type == engine::Port::OUTPUT)
cw->inputPort = cloneCw->inputPort;
else else
cloneCw = APP->scene->rack->getTopCable(this);

if (cloneCw) {
cw = new CableWidget;
cw->color = cloneCw->color;
cw->outputPort = cloneCw->outputPort; cw->outputPort = cloneCw->outputPort;
cw->updateCable();
}
cw->updateCable();
} }
} }
else { else {
// Grab cable on top of stack // Grab cable on top of stack
if (internal->overrideCw)
cw = internal->overrideCw;
else
cw = internal->overrideCw;
if (!cw)
cw = APP->scene->rack->getTopCable(this); cw = APP->scene->rack->getTopCable(this);


if (cw) { if (cw) {
@@ -414,13 +408,6 @@ void PortWidget::onDragStart(const DragStartEvent& e) {
} }


if (!cw) { if (!cw) {
// Check that inputs don't already have a cable
if (type == engine::Port::INPUT) {
CableWidget* topCw = APP->scene->rack->getTopCable(this);
if (topCw)
return;
}

// Create a new cable // Create a new cable
cw = new CableWidget; cw = new CableWidget;


@@ -465,52 +452,44 @@ void PortWidget::onDragEnd(const DragEndEvent& e) {




void PortWidget::onDragDrop(const DragDropEvent& e) { void PortWidget::onDragDrop(const DragDropEvent& e) {
// HACK: Only delete tooltip if we're not (normal) dragging it.
if (e.origin == this)
createTooltip();

if (e.button != GLFW_MOUSE_BUTTON_LEFT) if (e.button != GLFW_MOUSE_BUTTON_LEFT)
return; return;


// Reject ports if this is an input port and something is already plugged into it
if (type == engine::Port::INPUT) {
if (APP->scene->rack->getTopCable(this))
return;
}
// HACK: Only delete tooltip if we're not (normal) dragging it.
if (e.origin == this)
createTooltip();


CableWidget* cw = APP->scene->rack->getIncompleteCable(); CableWidget* cw = APP->scene->rack->getIncompleteCable();
if (cw) { if (cw) {
cw->hoveredOutputPort = cw->hoveredInputPort = NULL; cw->hoveredOutputPort = cw->hoveredInputPort = NULL;
if (type == engine::Port::OUTPUT)
if (type == engine::Port::OUTPUT && cw->inputPort && !APP->scene->rack->getCable(this, cw->inputPort)) {
cw->outputPort = this; cw->outputPort = this;
else
}
if (type == engine::Port::INPUT && cw->outputPort && !APP->scene->rack->getCable(cw->outputPort, this)) {
cw->inputPort = this; cw->inputPort = this;
}
cw->updateCable(); cw->updateCable();
} }
} }




void PortWidget::onDragEnter(const DragEnterEvent& e) { void PortWidget::onDragEnter(const DragEnterEvent& e) {
PortWidget* pw = dynamic_cast<PortWidget*>(e.origin);
if (pw) {
createTooltip();
}

if (e.button != GLFW_MOUSE_BUTTON_LEFT) if (e.button != GLFW_MOUSE_BUTTON_LEFT)
return; return;


// Reject ports if this is an input port and something is already plugged into it
if (type == engine::Port::INPUT) {
if (APP->scene->rack->getTopCable(this))
return;
PortWidget* pw = dynamic_cast<PortWidget*>(e.origin);
if (pw) {
createTooltip();
} }


CableWidget* cw = APP->scene->rack->getIncompleteCable(); CableWidget* cw = APP->scene->rack->getIncompleteCable();
if (cw) { if (cw) {
if (type == engine::Port::OUTPUT)
if (type == engine::Port::OUTPUT && cw->inputPort && !APP->scene->rack->getCable(this, cw->inputPort)) {
cw->hoveredOutputPort = this; cw->hoveredOutputPort = this;
else
}
if (type == engine::Port::INPUT && cw->outputPort && !APP->scene->rack->getCable(cw->outputPort, this)) {
cw->hoveredInputPort = this; cw->hoveredInputPort = this;
}
} }
} }


@@ -529,7 +508,7 @@ void PortWidget::onDragLeave(const DragLeaveEvent& e) {
if (cw) { if (cw) {
if (type == engine::Port::OUTPUT) if (type == engine::Port::OUTPUT)
cw->hoveredOutputPort = NULL; cw->hoveredOutputPort = NULL;
else
if (type == engine::Port::INPUT)
cw->hoveredInputPort = NULL; cw->hoveredInputPort = NULL;
} }
} }


+ 10
- 0
src/app/RackWidget.cpp View File

@@ -1467,6 +1467,16 @@ CableWidget* RackWidget::getCable(int64_t cableId) {
return NULL; return NULL;
} }


CableWidget* RackWidget::getCable(PortWidget* outputPort, PortWidget* inputPort) {
for (widget::Widget* w : internal->cableContainer->children) {
CableWidget* cw = dynamic_cast<CableWidget*>(w);
assert(cw);
if (cw->outputPort == outputPort && cw->inputPort == inputPort)
return cw;
}
return NULL;
}

std::vector<CableWidget*> RackWidget::getCompleteCables() { std::vector<CableWidget*> RackWidget::getCompleteCables() {
std::vector<CableWidget*> cws; std::vector<CableWidget*> cws;
cws.reserve(internal->cableContainer->children.size()); cws.reserve(internal->cableContainer->children.size());


+ 88
- 49
src/engine/Engine.cpp View File

@@ -184,6 +184,10 @@ struct Engine::Internal {
std::map<int64_t, Cable*> cablesCache; std::map<int64_t, Cable*> cablesCache;
// (moduleId, paramId) // (moduleId, paramId)
std::map<std::tuple<int64_t, int>, ParamHandle*> paramHandlesCache; std::map<std::tuple<int64_t, int>, ParamHandle*> paramHandlesCache;
/** Cache of cables connected to each input
Only connected inputs are allowed.
*/
std::map<Input*, std::vector<Cable*>> inputCablesCache;


float sampleRate = 0.f; float sampleRate = 0.f;
float sampleTime = 0.f; float sampleTime = 0.f;
@@ -319,27 +323,6 @@ static void Engine_stepWorker(Engine* that, int threadId) {
} }




static void Cable_step(Cable* that) {
Output* output = &that->outputModule->outputs[that->outputId];
Input* input = &that->inputModule->inputs[that->inputId];
// Match number of polyphonic channels to output port
int channels = output->channels;
// Copy all voltages from output to input
for (int c = 0; c < channels; c++) {
float v = output->voltages[c];
// Set 0V if infinite or NaN
if (!std::isfinite(v))
v = 0.f;
input->voltages[c] = v;
}
// Set higher channel voltages to 0
for (int c = channels; c < input->channels; c++) {
input->voltages[c] = 0.f;
}
input->channels = channels;
}


/** Steps a single frame /** Steps a single frame
*/ */
static void Engine_stepFrame(Engine* that) { static void Engine_stepFrame(Engine* that) {
@@ -372,9 +355,43 @@ static void Engine_stepFrame(Engine* that) {
Engine_stepWorker(that, 0); Engine_stepWorker(that, 0);
internal->workerBarrier.wait(); internal->workerBarrier.wait();


// Step cables
for (Cable* cable : that->internal->cables) {
Cable_step(cable);
// Step cables for each input
for (const auto& pair : internal->inputCablesCache) {
Input* input = pair.first;
const std::vector<Cable*>& cables = pair.second;
// Clear input voltages up to old number of input channels
for (int c = 0; c < input->channels; c++) {
input->voltages[c] = 0.f;
}
// Find max number of channels
uint8_t channels = 1;
for (Cable* cable : cables) {
Output* output = &cable->outputModule->outputs[cable->outputId];
channels = std::max(channels, output->channels);
}
input->channels = channels;
// Sum all outputs to input value
for (Cable* cable : cables) {
Output* output = &cable->outputModule->outputs[cable->outputId];

auto finitize = [](float x) {
return std::isfinite(x) ? x : 0.f;
};

// Sum monophonic value to all input channels
if (output->channels == 1) {
float value = finitize(output->voltages[0]);
for (int c = 0; c < channels; c++) {
input->voltages[c] += value;
}
}
// Sum polyphonic values to each input channel
else {
for (int c = 0; c < output->channels; c++) {
input->voltages[c] += finitize(output->voltages[c]);
}
}
}
} }


// Flip messages for each module // Flip messages for each module
@@ -898,34 +915,41 @@ void Engine::addCable_NoLock(Cable* cable) {
// Check cable properties // Check cable properties
assert(cable->inputModule); assert(cable->inputModule);
assert(cable->outputModule); assert(cable->outputModule);
Input& input = cable->inputModule->inputs[cable->inputId];
Output& output = cable->outputModule->outputs[cable->outputId];
bool inputWasConnected = false;
bool outputWasConnected = false; bool outputWasConnected = false;
for (Cable* cable2 : internal->cables) { for (Cable* cable2 : internal->cables) {
// Check that the cable is not already added // Check that the cable is not already added
assert(cable2 != cable); assert(cable2 != cable);
// Check that the input is not already used by another cable
assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId));
// Check that cable isn't similar to another cable
// assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId && cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId));
// Check if input is already connected to a cable
if (cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId)
inputWasConnected = true;
// Check if output is already connected to a cable // Check if output is already connected to a cable
if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId)
outputWasConnected = true; outputWasConnected = true;
} }
// Set ID if unset or collides with an existing ID // Set ID if unset or collides with an existing ID
while (cable->id < 0 || internal->cablesCache.find(cable->id) != internal->cablesCache.end()) { while (cable->id < 0 || internal->cablesCache.find(cable->id) != internal->cablesCache.end()) {
// Randomly generate ID
// Generate random 52-bit ID
cable->id = random::u64() % (1ull << 53); cable->id = random::u64() % (1ull << 53);
} }
// Add the cable // Add the cable
internal->cables.push_back(cable); internal->cables.push_back(cable);
internal->cablesCache[cable->id] = cable;
// Set input as connected
Input& input = cable->inputModule->inputs[cable->inputId];
input.channels = 1;
// Set output as connected, which might already be connected
Output& output = cable->outputModule->outputs[cable->outputId];
if (output.channels == 0) {
// Set default number of input/output channels
if (!inputWasConnected) {
input.channels = 1;
}
if (!outputWasConnected) {
output.channels = 1; output.channels = 1;
} }
// Add caches
internal->cablesCache[cable->id] = cable;
internal->inputCablesCache[&input].push_back(cable);
// Dispatch input port event // Dispatch input port event
{
if (!inputWasConnected) {
Module::PortChangeEvent e; Module::PortChangeEvent e;
e.connecting = true; e.connecting = true;
e.type = Port::INPUT; e.type = Port::INPUT;
@@ -951,42 +975,58 @@ void Engine::removeCable(Cable* cable) {


void Engine::removeCable_NoLock(Cable* cable) { void Engine::removeCable_NoLock(Cable* cable) {
assert(cable); assert(cable);
Input& input = cable->inputModule->inputs[cable->inputId];
Output& output = cable->outputModule->outputs[cable->outputId];
// Check that the cable is already added // Check that the cable is already added
auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); auto it = std::find(internal->cables.begin(), internal->cables.end(), cable);
assert(it != internal->cables.end()); assert(it != internal->cables.end());
// Remove the cable
// Remove cable caches
{
auto& v = internal->inputCablesCache[&input];
auto it = std::find(v.begin(), v.end(), cable);
assert(it != v.end());
v.erase(it);
// Remove input from cache if no cables are connected
if (v.empty()) {
internal->inputCablesCache.erase(&input);
}
}
internal->cablesCache.erase(cable->id); internal->cablesCache.erase(cable->id);
// Remove cable
internal->cables.erase(it); internal->cables.erase(it);
// Set input as disconnected
Input& input = cable->inputModule->inputs[cable->inputId];
input.channels = 0;
// Clear input values
for (uint8_t c = 0; c < PORT_MAX_CHANNELS; c++) {
input.setVoltage(0.f, c);
}
// Check if output is still connected to a cable
// Check if input/output is still connected to a cable
bool inputIsConnected = false;
bool outputIsConnected = false; bool outputIsConnected = false;
for (Cable* cable2 : internal->cables) { for (Cable* cable2 : internal->cables) {
if (cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId) {
inputIsConnected = true;
}
if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) { if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) {
outputIsConnected = true; outputIsConnected = true;
break;
}
}
// Set input as disconnected if disconnected from all cables
if (!inputIsConnected) {
input.channels = 0;
// Clear input values
for (uint8_t c = 0; c < PORT_MAX_CHANNELS; c++) {
input.setVoltage(0.f, c);
} }
} }
// Set output as disconnected if disconnected from all cables // Set output as disconnected if disconnected from all cables
if (!outputIsConnected) { if (!outputIsConnected) {
Output& output = cable->outputModule->outputs[cable->outputId];
output.channels = 0; output.channels = 0;
// Don't clear output values // Don't clear output values
} }
// Dispatch input port event // Dispatch input port event
{
if (!inputIsConnected) {
Module::PortChangeEvent e; Module::PortChangeEvent e;
e.connecting = false; e.connecting = false;
e.type = Port::INPUT; e.type = Port::INPUT;
e.portId = cable->inputId; e.portId = cable->inputId;
cable->inputModule->onPortChange(e); cable->inputModule->onPortChange(e);
} }
// Dispatch output port event if its state went from connected to disconnected.
// Dispatch output port event
if (!outputIsConnected) { if (!outputIsConnected) {
Module::PortChangeEvent e; Module::PortChangeEvent e;
e.connecting = false; e.connecting = false;
@@ -1249,7 +1289,6 @@ void Engine::fromJson(json_t* rootJ) {
catch (Exception& e) { catch (Exception& e) {
WARN("Cannot load cable: %s", e.what()); WARN("Cannot load cable: %s", e.what());
delete cable; delete cable;
// Don't log exceptions because missing modules create unnecessary complaining when cables try to connect to them.
continue; continue;
} }
} }


Loading…
Cancel
Save