Browse Source

Merge remote-tracking branch 'upstream/v1-gpl' into v1-gpl

pull/1278/head
Silvio Kunaschk 6 years ago
parent
commit
5db9b3f57c
15 changed files with 3898 additions and 98 deletions
  1. +25
    -0
      LICENSE-dist.txt
  2. +3
    -4
      Makefile
  3. +4
    -5
      compile.mk
  4. +1
    -1
      helper.py
  5. +3
    -3
      include/engine/Light.hpp
  6. +11
    -5
      include/engine/Module.hpp
  7. +0
    -3
      include/engine/Param.hpp
  8. +3727
    -0
      include/force_link_glibc_2.23.h
  9. +1
    -1
      include/math.hpp
  10. +2
    -0
      plugin.mk
  11. +6
    -6
      src/app/ModuleLightWidget.cpp
  12. +16
    -7
      src/engine/Engine.cpp
  13. +33
    -11
      src/engine/Module.cpp
  14. +0
    -28
      src/engine/Param.cpp
  15. +66
    -24
      template.vcv

+ 25
- 0
LICENSE-dist.txt View File

@@ -512,3 +512,28 @@ EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
SOFTWARE. SOFTWARE.


# glibc_version_header

MIT License

Copyright (c) 2018 Tom Mason

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.

+ 3
- 4
Makefile View File

@@ -1,9 +1,8 @@
RACK_DIR ?= . RACK_DIR ?= .
VERSION = 1.dev.$(shell git rev-parse --short HEAD)
VERSION := 1.dev.$(shell git rev-parse --short HEAD)


FLAGS += -DVERSION=$(VERSION) FLAGS += -DVERSION=$(VERSION)
FLAGS += -Iinclude
FLAGS += -Idep/include -Idep/lib/libzip/include
FLAGS += -Iinclude -Idep/include


include arch.mk include arch.mk


@@ -125,7 +124,7 @@ ifdef ARCH_MAC
cp plugins/Fundamental/dist/*.zip dist/$(TARGET).app/Contents/Resources/Fundamental.zip cp plugins/Fundamental/dist/*.zip dist/$(TARGET).app/Contents/Resources/Fundamental.zip
# Clean up and sign bundle # Clean up and sign bundle
xattr -cr dist/$(TARGET).app xattr -cr dist/$(TARGET).app
codesign --sign "VCV" --verbose dist/$(TARGET).app
codesign --sign "Developer ID Application: Andrew Belt (VRF26934X5)" --verbose dist/$(TARGET).app
codesign --verify --verbose dist/$(TARGET).app codesign --verify --verbose dist/$(TARGET).app
spctl --assess --verbose dist/$(TARGET).app spctl --assess --verbose dist/$(TARGET).app
# Make ZIP # Make ZIP


+ 4
- 5
compile.mk View File

@@ -15,15 +15,13 @@ FLAGS += -g
FLAGS += -O3 -march=nocona -funsafe-math-optimizations FLAGS += -O3 -march=nocona -funsafe-math-optimizations
# Warnings # Warnings
FLAGS += -Wall -Wextra -Wno-unused-parameter FLAGS += -Wall -Wextra -Wno-unused-parameter

ifneq ($(ARCH), mac)
CXXFLAGS += -Wsuggest-override
endif
# C++ standard
CXXFLAGS += -std=c++11 CXXFLAGS += -std=c++11


# Architecture-independent flags
ifdef ARCH_LIN ifdef ARCH_LIN
FLAGS += -DARCH_LIN FLAGS += -DARCH_LIN
CXXFLAGS += -Wsuggest-override
endif endif
ifdef ARCH_MAC ifdef ARCH_MAC
FLAGS += -DARCH_MAC FLAGS += -DARCH_MAC
@@ -36,6 +34,7 @@ endif
ifdef ARCH_WIN ifdef ARCH_WIN
FLAGS += -DARCH_WIN FLAGS += -DARCH_WIN
FLAGS += -D_USE_MATH_DEFINES FLAGS += -D_USE_MATH_DEFINES
CXXFLAGS += -Wsuggest-override
endif endif


CFLAGS += $(FLAGS) CFLAGS += $(FLAGS)


+ 1
- 1
helper.py View File

@@ -8,7 +8,7 @@ import xml.etree.ElementTree




# Version check # Version check
f"Python 3.6 is required"
f"Python 3.6+ is required"




class UserException(Exception): class UserException(Exception):


+ 3
- 3
include/engine/Light.hpp View File

@@ -14,16 +14,16 @@ struct Light {


/** Sets the brightness immediately with no light decay. */ /** Sets the brightness immediately with no light decay. */
void setBrightness(float brightness) { void setBrightness(float brightness) {
value = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f;
value = (brightness > 0.f) ? brightness : 0.f;
} }


float getBrightness() { float getBrightness() {
return std::sqrt(value);
return value;
} }


/** Emulates light decay with slow fall but immediate rise. */ /** Emulates light decay with slow fall but immediate rise. */
void setSmoothBrightness(float brightness, float deltaTime) { void setSmoothBrightness(float brightness, float deltaTime) {
float v = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f;
float v = (brightness > 0.f) ? brightness : 0.f;
if (v < value) { if (v < value) {
// Fade out light // Fade out light
const float lambda = 30.f; const float lambda = 30.f;


+ 11
- 5
include/engine/Module.hpp View File

@@ -41,26 +41,32 @@ struct Module {
int leftModuleId = -1; int leftModuleId = -1;
/** Pointer to the left Module, or NULL if nonexistent. */ /** Pointer to the left Module, or NULL if nonexistent. */
Module *leftModule = NULL; Module *leftModule = NULL;
/** Double buffer for receiving messages from adjacent modules.
If this module receives messages from adjacent modules, allocate both message buffers with identical blocks of memory (arrays, structs, etc).
/** Double buffer for receiving messages from the left adjacent module.
If this module intends to receive messages from the left, allocate both message buffers with identical blocks of memory (arrays, structs, etc).
Remember to free the buffer in the Module destructor. Remember to free the buffer in the Module destructor.
Example: Example:


leftProducerMessage = new MyModuleMessage; leftProducerMessage = new MyModuleMessage;
leftConsumerMessage = new MyModuleMessage; leftConsumerMessage = new MyModuleMessage;


At the end of each timestep, the buffers are flipped/swapped.
Modules must check a foreign module's `model` before attempting to write its message buffer.
Once the module is checked, you can reinterpret_cast its leftProducerMessage for speed.


You may choose for the Module to write to its own message buffer for consumption by other modules.
As long as this convention is followed by the left Module, this is fine.
Once you write a message, set leftMessageFlipRequested to true, to request that the messages are flipped at the end of the timestep.
Thus, message-passing has 1-sample latency.

You may choose for the Module to write to its own message buffer for consumption by other modules, i.e. "pull" rather than "push".
As long as this convention is followed by the left module, this is fine.
*/ */
void *leftProducerMessage = NULL; void *leftProducerMessage = NULL;
void *leftConsumerMessage = NULL; void *leftConsumerMessage = NULL;
bool leftMessageFlipRequested = false;


int rightModuleId = -1; int rightModuleId = -1;
Module *rightModule = NULL; Module *rightModule = NULL;
void *rightProducerMessage = NULL; void *rightProducerMessage = NULL;
void *rightConsumerMessage = NULL; void *rightConsumerMessage = NULL;
bool rightMessageFlipRequested = true;


/** Seconds spent in the process() method, with exponential smoothing. /** Seconds spent in the process() method, with exponential smoothing.
Only written when CPU timing is enabled, since time measurement is expensive. Only written when CPU timing is enabled, since time measurement is expensive.


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

@@ -19,9 +19,6 @@ struct Param {
void setValue(float value) { void setValue(float value) {
this->value = value; this->value = value;
} }

json_t *toJson();
void fromJson(json_t *rootJ);
}; };






+ 3727
- 0
include/force_link_glibc_2.23.h
File diff suppressed because it is too large
View File


+ 1
- 1
include/math.hpp View File

@@ -120,7 +120,7 @@ inline float normalizeZero(float x) {
See https://en.wikipedia.org/wiki/Euclidean_division. See https://en.wikipedia.org/wiki/Euclidean_division.
*/ */
inline float eucMod(float a, float b) { inline float eucMod(float a, float b) {
int mod = std::fmod(a, b);
float mod = std::fmod(a, b);
if (mod < 0.f) { if (mod < 0.f) {
mod += b; mod += b;
} }


+ 2
- 0
plugin.mk View File

@@ -23,6 +23,8 @@ ifdef ARCH_LIN
LDFLAGS += -shared LDFLAGS += -shared
TARGET := plugin.so TARGET := plugin.so
RACK_USER_DIR ?= $(HOME)/.Rack RACK_USER_DIR ?= $(HOME)/.Rack
# Link to glibc 2.23
FLAGS += -include $(RACK_DIR)/include/force_link_glibc_2.23.h
endif endif


ifdef ARCH_MAC ifdef ARCH_MAC


+ 6
- 6
src/app/ModuleLightWidget.cpp View File

@@ -12,13 +12,13 @@ void ModuleLightWidget::step() {
assert(module->lights.size() >= firstLightId + baseColors.size()); assert(module->lights.size() >= firstLightId + baseColors.size());


for (size_t i = 0; i < baseColors.size(); i++) { for (size_t i = 0; i < baseColors.size(); i++) {
float brightness = module->lights[firstLightId + i].getBrightness();
if (!std::isfinite(brightness))
brightness = 0.f;
float b = module->lights[firstLightId + i].getBrightness();
if (!std::isfinite(b))
b = 0.f;
b = math::clamp(b, 0.f, 1.f);
// Because LEDs are nonlinear, this seems to look more natural. // Because LEDs are nonlinear, this seems to look more natural.
brightness = std::sqrt(brightness);
brightness = math::clamp(brightness, 0.f, 1.f);
brightnesses[i] = brightness;
b = std::sqrt(b);
brightnesses[i] = b;
} }
} }
else { else {


+ 16
- 7
src/engine/Engine.cpp View File

@@ -302,10 +302,17 @@ static void Engine_step(Engine *that) {
for (Cable *cable : that->internal->cables) { for (Cable *cable : that->internal->cables) {
cable->step(); cable->step();
} }
// Swap messages of all modules

// Flip messages for each module
for (Module *module : that->internal->modules) { for (Module *module : that->internal->modules) {
std::swap(module->leftProducerMessage, module->leftConsumerMessage);
std::swap(module->rightProducerMessage, module->rightConsumerMessage);
if (module->leftMessageFlipRequested) {
std::swap(module->leftProducerMessage, module->leftConsumerMessage);
module->leftMessageFlipRequested = false;
}
if (module->rightMessageFlipRequested) {
std::swap(module->rightProducerMessage, module->rightConsumerMessage);
module->rightMessageFlipRequested = false;
}
} }
} }


@@ -495,6 +502,7 @@ void Engine::addModule(Module *module) {
} }
// Add module // Add module
internal->modules.push_back(module); internal->modules.push_back(module);
// Trigger Add event
module->onAdd(); module->onAdd();
// Update ParamHandles // Update ParamHandles
for (ParamHandle *paramHandle : internal->paramHandles) { for (ParamHandle *paramHandle : internal->paramHandles) {
@@ -507,6 +515,9 @@ void Engine::removeModule(Module *module) {
assert(module); assert(module);
VIPLock vipLock(internal->vipMutex); VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex); std::lock_guard<std::recursive_mutex> lock(internal->mutex);
// Check that the module actually exists
auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
assert(it != internal->modules.end());
// If a param is being smoothed on this module, stop smoothing it immediately // If a param is being smoothed on this module, stop smoothing it immediately
if (module == internal->smoothModule) { if (module == internal->smoothModule) {
internal->smoothModule = NULL; internal->smoothModule = NULL;
@@ -532,11 +543,9 @@ void Engine::removeModule(Module *module) {
m->rightModule = NULL; m->rightModule = NULL;
} }
} }
// Check that the module actually exists
auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
assert(it != internal->modules.end());
// Remove the module
// Trigger Remove event
module->onRemove(); module->onRemove();
// Remove module
internal->modules.erase(it); internal->modules.erase(it);
} }




+ 33
- 11
src/engine/Module.cpp View File

@@ -34,9 +34,19 @@ json_t *Module::toJson() {


// params // params
json_t *paramsJ = json_array(); json_t *paramsJ = json_array();
for (Param &param : params) {
json_t *paramJ = param.toJson();
json_array_append_new(paramsJ, paramJ);
for (size_t paramId = 0; paramId < params.size(); paramId++) {
// Don't serialize unbounded Params
if (!paramQuantities[paramId]->isBounded())
continue;

json_t *paramJ = json_object();

json_object_set_new(paramJ, "id", json_integer(paramId));

float value = params[paramId].getValue();
json_object_set_new(paramJ, "value", json_real(value));

json_array_append(paramsJ, paramJ);
} }
json_object_set_new(rootJ, "params", paramsJ); json_object_set_new(rootJ, "params", paramsJ);


@@ -67,17 +77,29 @@ void Module::fromJson(json_t *rootJ) {
size_t i; size_t i;
json_t *paramJ; json_t *paramJ;
json_array_foreach(paramsJ, i, paramJ) { json_array_foreach(paramsJ, i, paramJ) {
uint32_t paramId = i;
// Get paramId // Get paramId
// Legacy v0.6.0 to <v1.0
json_t *paramIdJ = json_object_get(paramJ, "paramId");
if (paramIdJ) {
json_t *paramIdJ = json_object_get(paramJ, "id");
// Legacy v0.6 to <v1
if (!paramIdJ)
paramIdJ = json_object_get(paramJ, "paramId");
size_t paramId;
if (paramIdJ)
paramId = json_integer_value(paramIdJ); paramId = json_integer_value(paramIdJ);
}
// Use index if all else fails
else
paramId = i;

// Check ID bounds
if (paramId >= params.size())
continue;

// Check that the Param is bounded
if (!paramQuantities[paramId]->isBounded())
continue;


if (paramId < params.size()) {
params[paramId].fromJson(paramJ);
}
json_t *valueJ = json_object_get(paramJ, "value");
if (valueJ)
params[paramId].setValue(json_number_value(valueJ));
} }


// bypass // bypass


+ 0
- 28
src/engine/Param.cpp View File

@@ -1,28 +0,0 @@
#include "engine/Param.hpp"
#include "random.hpp"
#include "math.hpp"


namespace rack {
namespace engine {


json_t *Param::toJson() {
json_t *rootJ = json_object();

// TODO Handle unbounded case
json_object_set_new(rootJ, "value", json_real(value));

return rootJ;
}

void Param::fromJson(json_t *rootJ) {
// TODO Handle unbounded case
json_t *valueJ = json_object_get(rootJ, "value");
if (valueJ)
value = json_number_value(valueJ);
}


} // namespace engine
} // namespace rack

+ 66
- 24
template.vcv View File

@@ -1,13 +1,22 @@
{ {
"version": "1.dev",
"version": "1.dev.71fab3d",
"modules": [ "modules": [
{ {
"plugin": "Core", "plugin": "Core",
"version": "1.dev",
"version": "1.dev.7bd9894",
"model": "AudioInterface", "model": "AudioInterface",
"params": [], "params": [],
"data": { "data": {
"audio": {
"driver": 1,
"offset": 1,
"maxChannels": 8,
"sampleRate": 44100,
"blockSize": 256
}
}, },
"leftModuleId": 2,
"rightModuleId": 8,
"id": 1, "id": 1,
"pos": [ "pos": [
57, 57,
@@ -20,21 +29,28 @@
"model": "VCMixer", "model": "VCMixer",
"params": [ "params": [
{ {
"id": 0,
"value": 1.0 "value": 1.0
}, },
{ {
"id": 1,
"value": 1.0 "value": 1.0
}, },
{ {
"id": 2,
"value": 1.0 "value": 1.0
}, },
{ {
"id": 3,
"value": 1.0 "value": 1.0
}, },
{ {
"id": 4,
"value": 1.0 "value": 1.0
} }
], ],
"leftModuleId": 7,
"rightModuleId": 1,
"id": 2, "id": 2,
"pos": [ "pos": [
47, 47,
@@ -47,27 +63,36 @@
"model": "VCO", "model": "VCO",
"params": [ "params": [
{ {
"id": 0,
"value": 1.0 "value": 1.0
}, },
{ {
"id": 1,
"value": 1.0 "value": 1.0
}, },
{ {
"id": 2,
"value": 0.0 "value": 0.0
}, },
{ {
"id": 3,
"value": 0.0 "value": 0.0
}, },
{ {
"id": 4,
"value": 0.0 "value": 0.0
}, },
{ {
"id": 5,
"value": 0.5 "value": 0.5
}, },
{ {
"id": 6,
"value": 0.0 "value": 0.0
} }
], ],
"leftModuleId": 5,
"rightModuleId": 4,
"id": 3, "id": 3,
"pos": [ "pos": [
8, 8,
@@ -80,21 +105,28 @@
"model": "VCF", "model": "VCF",
"params": [ "params": [
{ {
"value": 0.0599999949
"id": 0,
"value": 0.0464999937
}, },
{ {
"id": 1,
"value": 0.5 "value": 0.5
}, },
{ {
"id": 2,
"value": 0.236999944 "value": 0.236999944
}, },
{ {
"id": 3,
"value": 1.0 "value": 1.0
}, },
{ {
"id": 4,
"value": 0.0 "value": 0.0
} }
], ],
"leftModuleId": 3,
"rightModuleId": 6,
"id": 4, "id": 4,
"pos": [ "pos": [
18, 18,
@@ -103,20 +135,22 @@
}, },
{ {
"plugin": "Core", "plugin": "Core",
"version": "1.dev",
"version": "1.dev.7bd9894",
"model": "MIDIToCVInterface", "model": "MIDIToCVInterface",
"params": [], "params": [],
"data": { "data": {
"divisions": [
24,
6
],
"channels": 1,
"polyMode": 0,
"clockDivision": 24,
"lastPitch": 8192,
"lastMod": 0,
"midi": { "midi": {
"driver": -11, "driver": -11,
"deviceName": "QWERTY keyboard (US)", "deviceName": "QWERTY keyboard (US)",
"channel": -1 "channel": -1
} }
}, },
"rightModuleId": 3,
"id": 5, "id": 5,
"pos": [ "pos": [
0, 0,
@@ -129,18 +163,24 @@
"model": "ADSR", "model": "ADSR",
"params": [ "params": [
{ {
"value": 0.25
"id": 0,
"value": 0.247000009
}, },
{ {
"value": 0.671000004
"id": 1,
"value": 0.725000203
}, },
{ {
"value": 0.358999938
"id": 2,
"value": 0.361999929
}, },
{ {
"value": 0.5
"id": 3,
"value": 0.55850035
} }
], ],
"leftModuleId": 4,
"rightModuleId": 7,
"id": 6, "id": 6,
"pos": [ "pos": [
26, 26,
@@ -153,27 +193,35 @@
"model": "Scope", "model": "Scope",
"params": [ "params": [
{ {
"id": 0,
"value": 0.0 "value": 0.0
}, },
{ {
"id": 1,
"value": 0.0 "value": 0.0
}, },
{ {
"id": 2,
"value": 0.0 "value": 0.0
}, },
{ {
"id": 3,
"value": 0.0 "value": 0.0
}, },
{ {
"id": 4,
"value": 14.0 "value": 14.0
}, },
{ {
"id": 5,
"value": 0.0 "value": 0.0
}, },
{ {
"id": 6,
"value": 0.0 "value": 0.0
}, },
{ {
"id": 7,
"value": 0.0 "value": 0.0
} }
], ],
@@ -181,6 +229,8 @@
"lissajous": 0, "lissajous": 0,
"external": 0 "external": 0
}, },
"leftModuleId": 6,
"rightModuleId": 2,
"id": 7, "id": 7,
"pos": [ "pos": [
34, 34,
@@ -189,10 +239,11 @@
}, },
{ {
"plugin": "Core", "plugin": "Core",
"version": "1.dev",
"version": "1.dev.7bd9894",
"model": "Notes", "model": "Notes",
"params": [], "params": [],
"text": "Demo patch instructions:\n\n1. Choose an audio driver and device in the AUDIO module. \n\n2. Use the QWERTY and ZXCVB rows of your keyboard to generate MIDI notes and convert them to 1V/oct and GATE signals.\n\n3. Drag knobs up/down. Hold CTRL (CMD on Mac) while dragging to fine-tune. Right-click knobs to edit or reset their value. \n\n4. Drag cables to patch them between inputs and outputs. Stack multiple cables on outputs by holding CTRL (CMD on Mac) and dragging from an output. \n",
"leftModuleId": 1,
"text": "Demo patch instructions:\n\n1. Choose an audio driver and device in the AUDIO module.\n\n2. Use the QWERTY and ZXCVB rows of your keyboard to generate MIDI notes, which are converted to 1V/oct and GATE signals.\n\n3. Drag knobs up/down. Hold Ctrl (Cmd on Mac) while dragging to fine-tune. Right-click knobs to edit, or double-click to initialize.\n\n4. Drag cables to patch them between inputs and outputs. Stack multiple cables on outputs by holding Ctrl (Cmd on Mac) and dragging from an output.\n\n5. Right-click on an empty rack space to add new modules.",
"id": 8, "id": 8,
"pos": [ "pos": [
67, 67,
@@ -200,9 +251,9 @@
] ]
} }
], ],
"id": 19,
"cables": [ "cables": [
{ {
"id": 1,
"outputModuleId": 5, "outputModuleId": 5,
"outputId": 0, "outputId": 0,
"inputModuleId": 3, "inputModuleId": 3,
@@ -210,7 +261,6 @@
"color": "#c91847" "color": "#c91847"
}, },
{ {
"id": 2,
"outputModuleId": 3, "outputModuleId": 3,
"outputId": 2, "outputId": 2,
"inputModuleId": 4, "inputModuleId": 4,
@@ -218,7 +268,6 @@
"color": "#0c8e15" "color": "#0c8e15"
}, },
{ {
"id": 3,
"outputModuleId": 2, "outputModuleId": 2,
"outputId": 0, "outputId": 0,
"inputModuleId": 1, "inputModuleId": 1,
@@ -226,7 +275,6 @@
"color": "#0986ad" "color": "#0986ad"
}, },
{ {
"id": 4,
"outputModuleId": 2, "outputModuleId": 2,
"outputId": 0, "outputId": 0,
"inputModuleId": 1, "inputModuleId": 1,
@@ -234,7 +282,6 @@
"color": "#c9b70e" "color": "#c9b70e"
}, },
{ {
"id": 5,
"outputModuleId": 6, "outputModuleId": 6,
"outputId": 0, "outputId": 0,
"inputModuleId": 4, "inputModuleId": 4,
@@ -242,7 +289,6 @@
"color": "#c91847" "color": "#c91847"
}, },
{ {
"id": 6,
"outputModuleId": 5, "outputModuleId": 5,
"outputId": 1, "outputId": 1,
"inputModuleId": 6, "inputModuleId": 6,
@@ -250,7 +296,6 @@
"color": "#c9b70e" "color": "#c9b70e"
}, },
{ {
"id": 7,
"outputModuleId": 6, "outputModuleId": 6,
"outputId": 0, "outputId": 0,
"inputModuleId": 2, "inputModuleId": 2,
@@ -258,7 +303,6 @@
"color": "#0c8e15" "color": "#0c8e15"
}, },
{ {
"id": 8,
"outputModuleId": 4, "outputModuleId": 4,
"outputId": 0, "outputId": 0,
"inputModuleId": 2, "inputModuleId": 2,
@@ -266,7 +310,6 @@
"color": "#0986ad" "color": "#0986ad"
}, },
{ {
"id": 9,
"outputModuleId": 2, "outputModuleId": 2,
"outputId": 1, "outputId": 1,
"inputModuleId": 7, "inputModuleId": 7,
@@ -274,7 +317,6 @@
"color": "#c9b70e" "color": "#c9b70e"
}, },
{ {
"id": 10,
"outputModuleId": 5, "outputModuleId": 5,
"outputId": 6, "outputId": 6,
"inputModuleId": 6, "inputModuleId": 6,


Loading…
Cancel
Save