@@ -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 | |||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE | |||
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. |
@@ -1,9 +1,8 @@ | |||
RACK_DIR ?= . | |||
VERSION = 1.dev.$(shell git rev-parse --short HEAD) | |||
VERSION := 1.dev.$(shell git rev-parse --short HEAD) | |||
FLAGS += -DVERSION=$(VERSION) | |||
FLAGS += -Iinclude | |||
FLAGS += -Idep/include -Idep/lib/libzip/include | |||
FLAGS += -Iinclude -Idep/include | |||
include arch.mk | |||
@@ -125,7 +124,7 @@ ifdef ARCH_MAC | |||
cp plugins/Fundamental/dist/*.zip dist/$(TARGET).app/Contents/Resources/Fundamental.zip | |||
# Clean up and sign bundle | |||
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 | |||
spctl --assess --verbose dist/$(TARGET).app | |||
# Make ZIP | |||
@@ -15,15 +15,13 @@ FLAGS += -g | |||
FLAGS += -O3 -march=nocona -funsafe-math-optimizations | |||
# Warnings | |||
FLAGS += -Wall -Wextra -Wno-unused-parameter | |||
ifneq ($(ARCH), mac) | |||
CXXFLAGS += -Wsuggest-override | |||
endif | |||
# C++ standard | |||
CXXFLAGS += -std=c++11 | |||
# Architecture-independent flags | |||
ifdef ARCH_LIN | |||
FLAGS += -DARCH_LIN | |||
CXXFLAGS += -Wsuggest-override | |||
endif | |||
ifdef ARCH_MAC | |||
FLAGS += -DARCH_MAC | |||
@@ -36,6 +34,7 @@ endif | |||
ifdef ARCH_WIN | |||
FLAGS += -DARCH_WIN | |||
FLAGS += -D_USE_MATH_DEFINES | |||
CXXFLAGS += -Wsuggest-override | |||
endif | |||
CFLAGS += $(FLAGS) | |||
@@ -8,7 +8,7 @@ import xml.etree.ElementTree | |||
# Version check | |||
f"Python 3.6 is required" | |||
f"Python 3.6+ is required" | |||
class UserException(Exception): | |||
@@ -14,16 +14,16 @@ struct Light { | |||
/** Sets the brightness immediately with no light decay. */ | |||
void setBrightness(float brightness) { | |||
value = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f; | |||
value = (brightness > 0.f) ? brightness : 0.f; | |||
} | |||
float getBrightness() { | |||
return std::sqrt(value); | |||
return value; | |||
} | |||
/** Emulates light decay with slow fall but immediate rise. */ | |||
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) { | |||
// Fade out light | |||
const float lambda = 30.f; | |||
@@ -41,26 +41,32 @@ struct Module { | |||
int leftModuleId = -1; | |||
/** Pointer to the left Module, or NULL if nonexistent. */ | |||
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. | |||
Example: | |||
leftProducerMessage = 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 *leftConsumerMessage = NULL; | |||
bool leftMessageFlipRequested = false; | |||
int rightModuleId = -1; | |||
Module *rightModule = NULL; | |||
void *rightProducerMessage = NULL; | |||
void *rightConsumerMessage = NULL; | |||
bool rightMessageFlipRequested = true; | |||
/** Seconds spent in the process() method, with exponential smoothing. | |||
Only written when CPU timing is enabled, since time measurement is expensive. | |||
@@ -19,9 +19,6 @@ struct Param { | |||
void setValue(float value) { | |||
this->value = value; | |||
} | |||
json_t *toJson(); | |||
void fromJson(json_t *rootJ); | |||
}; | |||
@@ -120,7 +120,7 @@ inline float normalizeZero(float x) { | |||
See https://en.wikipedia.org/wiki/Euclidean_division. | |||
*/ | |||
inline float eucMod(float a, float b) { | |||
int mod = std::fmod(a, b); | |||
float mod = std::fmod(a, b); | |||
if (mod < 0.f) { | |||
mod += b; | |||
} | |||
@@ -23,6 +23,8 @@ ifdef ARCH_LIN | |||
LDFLAGS += -shared | |||
TARGET := plugin.so | |||
RACK_USER_DIR ?= $(HOME)/.Rack | |||
# Link to glibc 2.23 | |||
FLAGS += -include $(RACK_DIR)/include/force_link_glibc_2.23.h | |||
endif | |||
ifdef ARCH_MAC | |||
@@ -12,13 +12,13 @@ void ModuleLightWidget::step() { | |||
assert(module->lights.size() >= firstLightId + baseColors.size()); | |||
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. | |||
brightness = std::sqrt(brightness); | |||
brightness = math::clamp(brightness, 0.f, 1.f); | |||
brightnesses[i] = brightness; | |||
b = std::sqrt(b); | |||
brightnesses[i] = b; | |||
} | |||
} | |||
else { | |||
@@ -302,10 +302,17 @@ static void Engine_step(Engine *that) { | |||
for (Cable *cable : that->internal->cables) { | |||
cable->step(); | |||
} | |||
// Swap messages of all modules | |||
// Flip messages for each module | |||
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 | |||
internal->modules.push_back(module); | |||
// Trigger Add event | |||
module->onAdd(); | |||
// Update ParamHandles | |||
for (ParamHandle *paramHandle : internal->paramHandles) { | |||
@@ -507,6 +515,9 @@ void Engine::removeModule(Module *module) { | |||
assert(module); | |||
VIPLock vipLock(internal->vipMutex); | |||
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 (module == internal->smoothModule) { | |||
internal->smoothModule = NULL; | |||
@@ -532,11 +543,9 @@ void Engine::removeModule(Module *module) { | |||
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(); | |||
// Remove module | |||
internal->modules.erase(it); | |||
} | |||
@@ -34,9 +34,19 @@ json_t *Module::toJson() { | |||
// params | |||
json_t *paramsJ = json_array(); | |||
for (Param ¶m : 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); | |||
@@ -67,17 +77,29 @@ void Module::fromJson(json_t *rootJ) { | |||
size_t i; | |||
json_t *paramJ; | |||
json_array_foreach(paramsJ, i, paramJ) { | |||
uint32_t paramId = i; | |||
// 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); | |||
} | |||
// 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 | |||
@@ -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 |
@@ -1,13 +1,22 @@ | |||
{ | |||
"version": "1.dev", | |||
"version": "1.dev.71fab3d", | |||
"modules": [ | |||
{ | |||
"plugin": "Core", | |||
"version": "1.dev", | |||
"version": "1.dev.7bd9894", | |||
"model": "AudioInterface", | |||
"params": [], | |||
"data": { | |||
"audio": { | |||
"driver": 1, | |||
"offset": 1, | |||
"maxChannels": 8, | |||
"sampleRate": 44100, | |||
"blockSize": 256 | |||
} | |||
}, | |||
"leftModuleId": 2, | |||
"rightModuleId": 8, | |||
"id": 1, | |||
"pos": [ | |||
57, | |||
@@ -20,21 +29,28 @@ | |||
"model": "VCMixer", | |||
"params": [ | |||
{ | |||
"id": 0, | |||
"value": 1.0 | |||
}, | |||
{ | |||
"id": 1, | |||
"value": 1.0 | |||
}, | |||
{ | |||
"id": 2, | |||
"value": 1.0 | |||
}, | |||
{ | |||
"id": 3, | |||
"value": 1.0 | |||
}, | |||
{ | |||
"id": 4, | |||
"value": 1.0 | |||
} | |||
], | |||
"leftModuleId": 7, | |||
"rightModuleId": 1, | |||
"id": 2, | |||
"pos": [ | |||
47, | |||
@@ -47,27 +63,36 @@ | |||
"model": "VCO", | |||
"params": [ | |||
{ | |||
"id": 0, | |||
"value": 1.0 | |||
}, | |||
{ | |||
"id": 1, | |||
"value": 1.0 | |||
}, | |||
{ | |||
"id": 2, | |||
"value": 0.0 | |||
}, | |||
{ | |||
"id": 3, | |||
"value": 0.0 | |||
}, | |||
{ | |||
"id": 4, | |||
"value": 0.0 | |||
}, | |||
{ | |||
"id": 5, | |||
"value": 0.5 | |||
}, | |||
{ | |||
"id": 6, | |||
"value": 0.0 | |||
} | |||
], | |||
"leftModuleId": 5, | |||
"rightModuleId": 4, | |||
"id": 3, | |||
"pos": [ | |||
8, | |||
@@ -80,21 +105,28 @@ | |||
"model": "VCF", | |||
"params": [ | |||
{ | |||
"value": 0.0599999949 | |||
"id": 0, | |||
"value": 0.0464999937 | |||
}, | |||
{ | |||
"id": 1, | |||
"value": 0.5 | |||
}, | |||
{ | |||
"id": 2, | |||
"value": 0.236999944 | |||
}, | |||
{ | |||
"id": 3, | |||
"value": 1.0 | |||
}, | |||
{ | |||
"id": 4, | |||
"value": 0.0 | |||
} | |||
], | |||
"leftModuleId": 3, | |||
"rightModuleId": 6, | |||
"id": 4, | |||
"pos": [ | |||
18, | |||
@@ -103,20 +135,22 @@ | |||
}, | |||
{ | |||
"plugin": "Core", | |||
"version": "1.dev", | |||
"version": "1.dev.7bd9894", | |||
"model": "MIDIToCVInterface", | |||
"params": [], | |||
"data": { | |||
"divisions": [ | |||
24, | |||
6 | |||
], | |||
"channels": 1, | |||
"polyMode": 0, | |||
"clockDivision": 24, | |||
"lastPitch": 8192, | |||
"lastMod": 0, | |||
"midi": { | |||
"driver": -11, | |||
"deviceName": "QWERTY keyboard (US)", | |||
"channel": -1 | |||
} | |||
}, | |||
"rightModuleId": 3, | |||
"id": 5, | |||
"pos": [ | |||
0, | |||
@@ -129,18 +163,24 @@ | |||
"model": "ADSR", | |||
"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, | |||
"pos": [ | |||
26, | |||
@@ -153,27 +193,35 @@ | |||
"model": "Scope", | |||
"params": [ | |||
{ | |||
"id": 0, | |||
"value": 0.0 | |||
}, | |||
{ | |||
"id": 1, | |||
"value": 0.0 | |||
}, | |||
{ | |||
"id": 2, | |||
"value": 0.0 | |||
}, | |||
{ | |||
"id": 3, | |||
"value": 0.0 | |||
}, | |||
{ | |||
"id": 4, | |||
"value": 14.0 | |||
}, | |||
{ | |||
"id": 5, | |||
"value": 0.0 | |||
}, | |||
{ | |||
"id": 6, | |||
"value": 0.0 | |||
}, | |||
{ | |||
"id": 7, | |||
"value": 0.0 | |||
} | |||
], | |||
@@ -181,6 +229,8 @@ | |||
"lissajous": 0, | |||
"external": 0 | |||
}, | |||
"leftModuleId": 6, | |||
"rightModuleId": 2, | |||
"id": 7, | |||
"pos": [ | |||
34, | |||
@@ -189,10 +239,11 @@ | |||
}, | |||
{ | |||
"plugin": "Core", | |||
"version": "1.dev", | |||
"version": "1.dev.7bd9894", | |||
"model": "Notes", | |||
"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, | |||
"pos": [ | |||
67, | |||
@@ -200,9 +251,9 @@ | |||
] | |||
} | |||
], | |||
"id": 19, | |||
"cables": [ | |||
{ | |||
"id": 1, | |||
"outputModuleId": 5, | |||
"outputId": 0, | |||
"inputModuleId": 3, | |||
@@ -210,7 +261,6 @@ | |||
"color": "#c91847" | |||
}, | |||
{ | |||
"id": 2, | |||
"outputModuleId": 3, | |||
"outputId": 2, | |||
"inputModuleId": 4, | |||
@@ -218,7 +268,6 @@ | |||
"color": "#0c8e15" | |||
}, | |||
{ | |||
"id": 3, | |||
"outputModuleId": 2, | |||
"outputId": 0, | |||
"inputModuleId": 1, | |||
@@ -226,7 +275,6 @@ | |||
"color": "#0986ad" | |||
}, | |||
{ | |||
"id": 4, | |||
"outputModuleId": 2, | |||
"outputId": 0, | |||
"inputModuleId": 1, | |||
@@ -234,7 +282,6 @@ | |||
"color": "#c9b70e" | |||
}, | |||
{ | |||
"id": 5, | |||
"outputModuleId": 6, | |||
"outputId": 0, | |||
"inputModuleId": 4, | |||
@@ -242,7 +289,6 @@ | |||
"color": "#c91847" | |||
}, | |||
{ | |||
"id": 6, | |||
"outputModuleId": 5, | |||
"outputId": 1, | |||
"inputModuleId": 6, | |||
@@ -250,7 +296,6 @@ | |||
"color": "#c9b70e" | |||
}, | |||
{ | |||
"id": 7, | |||
"outputModuleId": 6, | |||
"outputId": 0, | |||
"inputModuleId": 2, | |||
@@ -258,7 +303,6 @@ | |||
"color": "#0c8e15" | |||
}, | |||
{ | |||
"id": 8, | |||
"outputModuleId": 4, | |||
"outputId": 0, | |||
"inputModuleId": 2, | |||
@@ -266,7 +310,6 @@ | |||
"color": "#0986ad" | |||
}, | |||
{ | |||
"id": 9, | |||
"outputModuleId": 2, | |||
"outputId": 1, | |||
"inputModuleId": 7, | |||
@@ -274,7 +317,6 @@ | |||
"color": "#c9b70e" | |||
}, | |||
{ | |||
"id": 10, | |||
"outputModuleId": 5, | |||
"outputId": 6, | |||
"inputModuleId": 6, | |||