@@ -27,7 +27,7 @@ struct CableWidget : widget::OpaqueWidget { | |||||
math::Vec getOutputPos(); | math::Vec getOutputPos(); | ||||
math::Vec getInputPos(); | math::Vec getInputPos(); | ||||
json_t *toJson(); | json_t *toJson(); | ||||
void fromJson(json_t *rootJ, const std::map<int, ModuleWidget*> &moduleWidgets); | |||||
void fromJson(json_t *rootJ); | |||||
void draw(const widget::DrawContext &ctx) override; | void draw(const widget::DrawContext &ctx) override; | ||||
void drawPlugs(const widget::DrawContext &ctx); | void drawPlugs(const widget::DrawContext &ctx); | ||||
}; | }; | ||||
@@ -23,75 +23,6 @@ inline float sinc(float x) { | |||||
return std::sin(x) / x; | return std::sin(x) / x; | ||||
} | } | ||||
// Window functions | |||||
/** Hann window function | |||||
p: proportion from [0, 1], usually `i / (len - 1)` | |||||
https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windows | |||||
*/ | |||||
inline float hann(float p) { | |||||
return 0.5f * (1.f - std::cos(2*M_PI * p)); | |||||
} | |||||
/** Applies the Hann window to a signal `x` */ | |||||
inline void hannWindow(float *x, int len) { | |||||
for (int i = 0; i < len; i++) { | |||||
x[i] *= hann((float) i / (len - 1)); | |||||
} | |||||
} | |||||
/** Blackman window function | |||||
https://en.wikipedia.org/wiki/Window_function#Blackman_window | |||||
A typical alpha value is 0.16. | |||||
*/ | |||||
inline float blackman(float alpha, float p) { | |||||
return | |||||
+ (1 - alpha) / 2.f | |||||
- 1 / 2.f * std::cos(2*M_PI * p) | |||||
+ alpha / 2.f * std::cos(4*M_PI * p); | |||||
} | |||||
inline void blackmanWindow(float alpha, float *x, int len) { | |||||
for (int i = 0; i < len; i++) { | |||||
x[i] *= blackman(alpha, (float) i / (len - 1)); | |||||
} | |||||
} | |||||
/** Blackman-Nuttall window function | |||||
https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Nuttall_window | |||||
*/ | |||||
inline float blackmanNuttall(float p) { | |||||
return | |||||
+ 0.3635819f | |||||
- 0.4891775f * std::cos(2*M_PI * p) | |||||
+ 0.1365995f * std::cos(4*M_PI * p) | |||||
- 0.0106411f * std::cos(6*M_PI * p); | |||||
} | |||||
inline void blackmanNuttallWindow(float *x, int len) { | |||||
for (int i = 0; i < len; i++) { | |||||
x[i] *= blackmanNuttall((float) i / (len - 1)); | |||||
} | |||||
} | |||||
/** Blackman-Harris window function | |||||
https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window | |||||
*/ | |||||
inline float blackmanHarris(float p) { | |||||
return | |||||
+ 0.35875f | |||||
- 0.48829f * std::cos(2*M_PI * p) | |||||
+ 0.14128f * std::cos(4*M_PI * p) | |||||
- 0.01168f * std::cos(6*M_PI * p); | |||||
} | |||||
inline void blackmanHarrisWindow(float *x, int len) { | |||||
for (int i = 0; i < len; i++) { | |||||
x[i] *= blackmanHarris((float) i / (len - 1)); | |||||
} | |||||
} | |||||
// Conversion functions | // Conversion functions | ||||
inline float amplitudeToDb(float amp) { | inline float amplitudeToDb(float amp) { | ||||
@@ -3,6 +3,7 @@ | |||||
#include "dsp/frame.hpp" | #include "dsp/frame.hpp" | ||||
#include "dsp/ringbuffer.hpp" | #include "dsp/ringbuffer.hpp" | ||||
#include "dsp/fir.hpp" | #include "dsp/fir.hpp" | ||||
#include "dsp/window.hpp" | |||||
#include <assert.h> | #include <assert.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <speex/speex_resampler.h> | #include <speex/speex_resampler.h> | ||||
@@ -0,0 +1,78 @@ | |||||
#pragma once | |||||
#include "math.hpp" | |||||
namespace rack { | |||||
namespace dsp { | |||||
/** Hann window function | |||||
p: proportion from [0, 1], usually `i / (len - 1)` | |||||
https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windows | |||||
*/ | |||||
inline float hann(float p) { | |||||
return 0.5f * (1.f - std::cos(2*M_PI * p)); | |||||
} | |||||
/** Applies the Hann window to a signal `x` */ | |||||
inline void hannWindow(float *x, int len) { | |||||
for (int i = 0; i < len; i++) { | |||||
x[i] *= hann((float) i / (len - 1)); | |||||
} | |||||
} | |||||
/** Blackman window function | |||||
https://en.wikipedia.org/wiki/Window_function#Blackman_window | |||||
A typical alpha value is 0.16. | |||||
*/ | |||||
inline float blackman(float alpha, float p) { | |||||
return | |||||
+ (1 - alpha) / 2.f | |||||
- 1 / 2.f * std::cos(2*M_PI * p) | |||||
+ alpha / 2.f * std::cos(4*M_PI * p); | |||||
} | |||||
inline void blackmanWindow(float alpha, float *x, int len) { | |||||
for (int i = 0; i < len; i++) { | |||||
x[i] *= blackman(alpha, (float) i / (len - 1)); | |||||
} | |||||
} | |||||
/** Blackman-Nuttall window function | |||||
https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Nuttall_window | |||||
*/ | |||||
inline float blackmanNuttall(float p) { | |||||
return | |||||
+ 0.3635819f | |||||
- 0.4891775f * std::cos(2*M_PI * p) | |||||
+ 0.1365995f * std::cos(4*M_PI * p) | |||||
- 0.0106411f * std::cos(6*M_PI * p); | |||||
} | |||||
inline void blackmanNuttallWindow(float *x, int len) { | |||||
for (int i = 0; i < len; i++) { | |||||
x[i] *= blackmanNuttall((float) i / (len - 1)); | |||||
} | |||||
} | |||||
/** Blackman-Harris window function | |||||
https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window | |||||
*/ | |||||
inline float blackmanHarris(float p) { | |||||
return | |||||
+ 0.35875f | |||||
- 0.48829f * std::cos(2*M_PI * p) | |||||
+ 0.14128f * std::cos(4*M_PI * p) | |||||
- 0.01168f * std::cos(6*M_PI * p); | |||||
} | |||||
inline void blackmanHarrisWindow(float *x, int len) { | |||||
for (int i = 0; i < len; i++) { | |||||
x[i] *= blackmanHarris((float) i / (len - 1)); | |||||
} | |||||
} | |||||
} // namespace dsp | |||||
} // namespace rack |
@@ -8,7 +8,7 @@ namespace engine { | |||||
struct Cable { | struct Cable { | ||||
int id = 0; | |||||
int id = -1; | |||||
Module *outputModule = NULL; | Module *outputModule = NULL; | ||||
int outputId; | int outputId; | ||||
Module *inputModule = NULL; | Module *inputModule = NULL; | ||||
@@ -14,7 +14,7 @@ namespace engine { | |||||
struct Module { | struct Module { | ||||
/** Automatically generated by the engine. */ | /** Automatically generated by the engine. */ | ||||
int id = 0; | |||||
int id = -1; | |||||
std::vector<Param> params; | std::vector<Param> params; | ||||
std::vector<Output> outputs; | std::vector<Output> outputs; | ||||
std::vector<Input> inputs; | std::vector<Input> inputs; | ||||
@@ -0,0 +1,18 @@ | |||||
#pragma once | |||||
#include "common.hpp" | |||||
#include "math.hpp" | |||||
#include <jansson.h> | |||||
namespace rack { | |||||
namespace engine { | |||||
struct ParamMap { | |||||
int moduleId; | |||||
int paramId; | |||||
}; | |||||
} // namespace engine | |||||
} // namespace rack |
@@ -88,6 +88,7 @@ | |||||
#include "dsp/resampler.hpp" | #include "dsp/resampler.hpp" | ||||
#include "dsp/ringbuffer.hpp" | #include "dsp/ringbuffer.hpp" | ||||
#include "dsp/vumeter.hpp" | #include "dsp/vumeter.hpp" | ||||
#include "dsp/window.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -146,9 +146,6 @@ json_t *CableWidget::toJson() { | |||||
assert(isComplete()); | assert(isComplete()); | ||||
json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
// This is just here for fun. It is not used in fromJson(), since cableIds are not preserved across multiple launches of Rack. | |||||
json_object_set_new(rootJ, "id", json_integer(cable->id)); | |||||
json_object_set_new(rootJ, "outputModuleId", json_integer(cable->outputModule->id)); | json_object_set_new(rootJ, "outputModuleId", json_integer(cable->outputModule->id)); | ||||
json_object_set_new(rootJ, "outputId", json_integer(cable->outputId)); | json_object_set_new(rootJ, "outputId", json_integer(cable->outputId)); | ||||
json_object_set_new(rootJ, "inputModuleId", json_integer(cable->inputModule->id)); | json_object_set_new(rootJ, "inputModuleId", json_integer(cable->inputModule->id)); | ||||
@@ -160,20 +157,30 @@ json_t *CableWidget::toJson() { | |||||
return rootJ; | return rootJ; | ||||
} | } | ||||
void CableWidget::fromJson(json_t *rootJ, const std::map<int, ModuleWidget*> &moduleWidgets) { | |||||
int outputModuleId = json_integer_value(json_object_get(rootJ, "outputModuleId")); | |||||
int outputId = json_integer_value(json_object_get(rootJ, "outputId")); | |||||
int inputModuleId = json_integer_value(json_object_get(rootJ, "inputModuleId")); | |||||
int inputId = json_integer_value(json_object_get(rootJ, "inputId")); | |||||
// Get module widgets | |||||
auto outputModuleIt = moduleWidgets.find(outputModuleId); | |||||
auto inputModuleIt = moduleWidgets.find(inputModuleId); | |||||
if (outputModuleIt == moduleWidgets.end() || inputModuleIt == moduleWidgets.end()) | |||||
return; | |||||
ModuleWidget *outputModule = outputModuleIt->second; | |||||
ModuleWidget *inputModule = inputModuleIt->second; | |||||
void CableWidget::fromJson(json_t *rootJ) { | |||||
// outputModuleId | |||||
json_t *outputModuleIdJ = json_object_get(rootJ, "outputModuleId"); | |||||
if (!outputModuleIdJ) return; | |||||
int outputModuleId = json_integer_value(outputModuleIdJ); | |||||
ModuleWidget *outputModule = APP->scene->rackWidget->getModule(outputModuleId); | |||||
if (!outputModule) return; | |||||
// inputModuleId | |||||
json_t *inputModuleIdJ = json_object_get(rootJ, "inputModuleId"); | |||||
if (!inputModuleIdJ) return; | |||||
int inputModuleId = json_integer_value(inputModuleIdJ); | |||||
ModuleWidget *inputModule = APP->scene->rackWidget->getModule(inputModuleId); | |||||
if (!inputModule) return; | |||||
// outputId | |||||
json_t *outputIdJ = json_object_get(rootJ, "outputId"); | |||||
if (!outputIdJ) return; | |||||
int outputId = json_integer_value(outputIdJ); | |||||
// inputId | |||||
json_t *inputIdJ = json_object_get(rootJ, "inputId"); | |||||
if (!inputIdJ) return; | |||||
int inputId = json_integer_value(inputIdJ); | |||||
// Set ports | // Set ports | ||||
if (APP->patch->isLegacy(1)) { | if (APP->patch->isLegacy(1)) { | ||||
@@ -26,6 +26,8 @@ float ParamQuantity::getSmoothValue() { | |||||
void ParamQuantity::setValue(float value) { | void ParamQuantity::setValue(float value) { | ||||
if (!module) | if (!module) | ||||
return; | return; | ||||
if (!std::isfinite(value)) | |||||
return; | |||||
// This setter clamps the value | // This setter clamps the value | ||||
getParam()->setValue(value); | getParam()->setValue(value); | ||||
} | } | ||||
@@ -229,7 +229,13 @@ json_t *RackWidget::toJson() { | |||||
if (!cw->isComplete()) | if (!cw->isComplete()) | ||||
continue; | continue; | ||||
json_array_append_new(cablesJ, cw->toJson()); | |||||
json_t *cableJ = cw->toJson(); | |||||
{ | |||||
// id | |||||
json_object_set_new(rootJ, "id", json_integer(cw->cable->id)); | |||||
} | |||||
json_array_append_new(cablesJ, cableJ); | |||||
} | } | ||||
json_object_set_new(rootJ, "cables", cablesJ); | json_object_set_new(rootJ, "cables", cablesJ); | ||||
@@ -241,18 +247,21 @@ void RackWidget::fromJson(json_t *rootJ) { | |||||
json_t *modulesJ = json_object_get(rootJ, "modules"); | json_t *modulesJ = json_object_get(rootJ, "modules"); | ||||
if (!modulesJ) | if (!modulesJ) | ||||
return; | return; | ||||
std::map<int, ModuleWidget*> moduleWidgets; | |||||
size_t moduleIndex; | size_t moduleIndex; | ||||
json_t *moduleJ; | json_t *moduleJ; | ||||
json_array_foreach(modulesJ, moduleIndex, moduleJ) { | json_array_foreach(modulesJ, moduleIndex, moduleJ) { | ||||
ModuleWidget *moduleWidget = moduleFromJson(moduleJ); | ModuleWidget *moduleWidget = moduleFromJson(moduleJ); | ||||
if (moduleWidget) { | if (moduleWidget) { | ||||
// Before 1.0, the module ID was the index in the "modules" array | |||||
if (APP->patch->isLegacy(2)) { | |||||
moduleWidget->module->id = moduleIndex; | |||||
} | |||||
// id | // id | ||||
json_t *idJ = json_object_get(moduleJ, "id"); | json_t *idJ = json_object_get(moduleJ, "id"); | ||||
int id = 0; | |||||
if (idJ) | if (idJ) | ||||
id = json_integer_value(idJ); | |||||
moduleWidget->module->id = json_integer_value(idJ); | |||||
// pos | // pos | ||||
json_t *posJ = json_object_get(moduleJ, "pos"); | json_t *posJ = json_object_get(moduleJ, "pos"); | ||||
double x, y; | double x, y; | ||||
@@ -266,13 +275,6 @@ void RackWidget::fromJson(json_t *rootJ) { | |||||
moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE); | moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE); | ||||
} | } | ||||
if (APP->patch->isLegacy(2)) { | |||||
// Before 1.0, the module ID was the index in the "modules" array | |||||
moduleWidgets[moduleIndex] = moduleWidget; | |||||
} | |||||
else { | |||||
moduleWidgets[id] = moduleWidget; | |||||
} | |||||
addModule(moduleWidget); | addModule(moduleWidget); | ||||
} | } | ||||
else { | else { | ||||
@@ -295,11 +297,20 @@ void RackWidget::fromJson(json_t *rootJ) { | |||||
json_array_foreach(cablesJ, cableIndex, cableJ) { | json_array_foreach(cablesJ, cableIndex, cableJ) { | ||||
// Create a unserialize cable | // Create a unserialize cable | ||||
CableWidget *cw = new CableWidget; | CableWidget *cw = new CableWidget; | ||||
cw->fromJson(cableJ, moduleWidgets); | |||||
cw->fromJson(cableJ); | |||||
if (!cw->isComplete()) { | if (!cw->isComplete()) { | ||||
delete cw; | delete cw; | ||||
continue; | continue; | ||||
} | } | ||||
// Before 1.0, cables IDs were not used, so just use the index of the "cables" array. | |||||
if (APP->patch->isLegacy(2)) { | |||||
cw->cable->id = cableIndex; | |||||
} | |||||
// id | |||||
json_t *idJ = json_object_get(cableJ, "id"); | |||||
if (idJ) | |||||
cw->cable->id = json_integer_value(idJ); | |||||
addCable(cw); | addCable(cw); | ||||
} | } | ||||
} | } | ||||
@@ -211,6 +211,7 @@ static int rtCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrame | |||||
// Exploit the stream time to run code on startup of the audio thread | // Exploit the stream time to run code on startup of the audio thread | ||||
if (streamTime == 0.0) { | if (streamTime == 0.0) { | ||||
system::setThreadName("Audio"); | system::setThreadName("Audio"); | ||||
system::setThreadRealTime(); | |||||
} | } | ||||
audioIO->processStream((const float *) inputBuffer, (float *) outputBuffer, nFrames); | audioIO->processStream((const float *) inputBuffer, (float *) outputBuffer, nFrames); | ||||
return 0; | return 0; | ||||
@@ -1,5 +1,6 @@ | |||||
#include "dsp/minblep.hpp" | #include "dsp/minblep.hpp" | ||||
#include "dsp/fft.hpp" | #include "dsp/fft.hpp" | ||||
#include "dsp/window.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -100,8 +100,6 @@ struct EngineWorker { | |||||
void start() { | void start() { | ||||
thread = std::thread([&] { | thread = std::thread([&] { | ||||
system::setThreadName("Engine worker"); | |||||
system::setThreadRealTime(); | |||||
run(); | run(); | ||||
}); | }); | ||||
} | } | ||||
@@ -115,6 +113,8 @@ struct EngineWorker { | |||||
} | } | ||||
void run() { | void run() { | ||||
system::setThreadName("Engine worker"); | |||||
system::setThreadRealTime(); | |||||
while (running) { | while (running) { | ||||
step(); | step(); | ||||
} | } | ||||
@@ -134,8 +134,8 @@ struct Engine::Internal { | |||||
float sampleTime; | float sampleTime; | ||||
float sampleRateRequested; | float sampleRateRequested; | ||||
int nextModuleId = 1; | |||||
int nextCableId = 1; | |||||
int nextModuleId = 0; | |||||
int nextCableId = 0; | |||||
// Parameter smoothing | // Parameter smoothing | ||||
Module *smoothModule = NULL; | Module *smoothModule = NULL; | ||||
@@ -209,13 +209,13 @@ static void Engine_setWorkerCount(Engine *engine, int workerCount) { | |||||
} | } | ||||
} | } | ||||
static void Engine_stepModules(Engine *engine, int id) { | |||||
static void Engine_stepModules(Engine *engine, int threadId) { | |||||
Engine::Internal *internal = engine->internal; | Engine::Internal *internal = engine->internal; | ||||
int threadCount = internal->threadCount; | int threadCount = internal->threadCount; | ||||
int modulesLen = internal->modules.size(); | int modulesLen = internal->modules.size(); | ||||
for (int i = id; i < modulesLen; i += threadCount) { | |||||
for (int i = threadId; i < modulesLen; i += threadCount) { | |||||
Module *module = internal->modules[i]; | Module *module = internal->modules[i]; | ||||
if (!module->bypass) { | if (!module->bypass) { | ||||
// Step module | // Step module | ||||
@@ -389,17 +389,19 @@ void Engine::addModule(Module *module) { | |||||
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()); | ||||
// Set ID | // Set ID | ||||
if (module->id == 0) { | |||||
if (module->id < 0) { | |||||
// Automatically assign ID | // Automatically assign ID | ||||
module->id = internal->nextModuleId++; | module->id = internal->nextModuleId++; | ||||
} | } | ||||
else { | else { | ||||
// Manual ID | // Manual ID | ||||
assert(module->id < internal->nextModuleId); | |||||
// Check that the ID is not already taken | // Check that the ID is not already taken | ||||
for (Module *m : internal->modules) { | for (Module *m : internal->modules) { | ||||
assert(module->id != m->id); | assert(module->id != m->id); | ||||
} | } | ||||
if (module->id >= internal->nextModuleId) { | |||||
internal->nextModuleId = module->id + 1; | |||||
} | |||||
} | } | ||||
// Add module | // Add module | ||||
internal->modules.push_back(module); | internal->modules.push_back(module); | ||||
@@ -424,7 +426,7 @@ void Engine::removeModule(Module *module) { | |||||
// Remove the module | // Remove the module | ||||
internal->modules.erase(it); | internal->modules.erase(it); | ||||
// Remove id | // Remove id | ||||
module->id = 0; | |||||
module->id = -1; | |||||
} | } | ||||
void Engine::resetModule(Module *module) { | void Engine::resetModule(Module *module) { | ||||
@@ -493,17 +495,19 @@ void Engine::addCable(Cable *cable) { | |||||
assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId)); | assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId)); | ||||
} | } | ||||
// Set ID | // Set ID | ||||
if (cable->id == 0) { | |||||
if (cable->id < 0) { | |||||
// Automatically assign ID | // Automatically assign ID | ||||
cable->id = internal->nextCableId++; | cable->id = internal->nextCableId++; | ||||
} | } | ||||
else { | else { | ||||
// Manual ID | // Manual ID | ||||
assert(cable->id < internal->nextCableId); | |||||
// Check that the ID is not already taken | // Check that the ID is not already taken | ||||
for (Cable *w : internal->cables) { | for (Cable *w : internal->cables) { | ||||
assert(cable->id != w->id); | assert(cable->id != w->id); | ||||
} | } | ||||
if (cable->id >= internal->nextCableId) { | |||||
internal->nextCableId = cable->id + 1; | |||||
} | |||||
} | } | ||||
// Add the cable | // Add the cable | ||||
internal->cables.push_back(cable); | internal->cables.push_back(cable); | ||||
@@ -524,7 +528,7 @@ void Engine::removeCable(Cable *cable) { | |||||
internal->cables.erase(it); | internal->cables.erase(it); | ||||
Engine_updateConnected(this); | Engine_updateConnected(this); | ||||
// Remove ID | // Remove ID | ||||
cable->id = 0; | |||||
cable->id = -1; | |||||
} | } | ||||
void Engine::setParam(Module *module, int paramId, float value) { | void Engine::setParam(Module *module, int paramId, float value) { | ||||
@@ -124,7 +124,7 @@ void ParamChange::redo() { | |||||
void CableAdd::setCable(app::CableWidget *cw) { | void CableAdd::setCable(app::CableWidget *cw) { | ||||
assert(cw->cable); | assert(cw->cable); | ||||
assert(cw->cable->id > 0); | |||||
assert(cw->cable->id >= 0); | |||||
cableId = cw->cable->id; | cableId = cw->cable->id; | ||||
assert(cw->cable->outputModule); | assert(cw->cable->outputModule); | ||||
outputModuleId = cw->cable->outputModule->id; | outputModuleId = cw->cable->outputModule->id; | ||||
@@ -209,9 +209,9 @@ Window::Window() { | |||||
exit(1); | exit(1); | ||||
} | } | ||||
float pixelRatio; | |||||
glfwGetWindowContentScale(win, &pixelRatio, NULL); | |||||
INFO("Pixel ratio: %f", pixelRatio); | |||||
float contentScale; | |||||
glfwGetWindowContentScale(win, &contentScale, NULL); | |||||
INFO("Window content scale: %f", contentScale); | |||||
glfwSetWindowSizeLimits(win, 800, 600, GLFW_DONT_CARE, GLFW_DONT_CARE); | glfwSetWindowSizeLimits(win, 800, 600, GLFW_DONT_CARE, GLFW_DONT_CARE); | ||||
if (settings.windowSize.isZero()) { | if (settings.windowSize.isZero()) { | ||||