| @@ -27,7 +27,7 @@ struct CableWidget : widget::OpaqueWidget { | |||
| math::Vec getOutputPos(); | |||
| math::Vec getInputPos(); | |||
| 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 drawPlugs(const widget::DrawContext &ctx); | |||
| }; | |||
| @@ -23,75 +23,6 @@ inline float sinc(float 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 | |||
| inline float amplitudeToDb(float amp) { | |||
| @@ -3,6 +3,7 @@ | |||
| #include "dsp/frame.hpp" | |||
| #include "dsp/ringbuffer.hpp" | |||
| #include "dsp/fir.hpp" | |||
| #include "dsp/window.hpp" | |||
| #include <assert.h> | |||
| #include <string.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 { | |||
| int id = 0; | |||
| int id = -1; | |||
| Module *outputModule = NULL; | |||
| int outputId; | |||
| Module *inputModule = NULL; | |||
| @@ -14,7 +14,7 @@ namespace engine { | |||
| struct Module { | |||
| /** Automatically generated by the engine. */ | |||
| int id = 0; | |||
| int id = -1; | |||
| std::vector<Param> params; | |||
| std::vector<Output> outputs; | |||
| 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/ringbuffer.hpp" | |||
| #include "dsp/vumeter.hpp" | |||
| #include "dsp/window.hpp" | |||
| namespace rack { | |||
| @@ -146,9 +146,6 @@ json_t *CableWidget::toJson() { | |||
| assert(isComplete()); | |||
| 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, "outputId", json_integer(cable->outputId)); | |||
| json_object_set_new(rootJ, "inputModuleId", json_integer(cable->inputModule->id)); | |||
| @@ -160,20 +157,30 @@ json_t *CableWidget::toJson() { | |||
| 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 | |||
| if (APP->patch->isLegacy(1)) { | |||
| @@ -26,6 +26,8 @@ float ParamQuantity::getSmoothValue() { | |||
| void ParamQuantity::setValue(float value) { | |||
| if (!module) | |||
| return; | |||
| if (!std::isfinite(value)) | |||
| return; | |||
| // This setter clamps the value | |||
| getParam()->setValue(value); | |||
| } | |||
| @@ -229,7 +229,13 @@ json_t *RackWidget::toJson() { | |||
| if (!cw->isComplete()) | |||
| 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); | |||
| @@ -241,18 +247,21 @@ void RackWidget::fromJson(json_t *rootJ) { | |||
| json_t *modulesJ = json_object_get(rootJ, "modules"); | |||
| if (!modulesJ) | |||
| return; | |||
| std::map<int, ModuleWidget*> moduleWidgets; | |||
| size_t moduleIndex; | |||
| json_t *moduleJ; | |||
| json_array_foreach(modulesJ, moduleIndex, moduleJ) { | |||
| ModuleWidget *moduleWidget = moduleFromJson(moduleJ); | |||
| 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 | |||
| json_t *idJ = json_object_get(moduleJ, "id"); | |||
| int id = 0; | |||
| if (idJ) | |||
| id = json_integer_value(idJ); | |||
| moduleWidget->module->id = json_integer_value(idJ); | |||
| // pos | |||
| json_t *posJ = json_object_get(moduleJ, "pos"); | |||
| double x, y; | |||
| @@ -266,13 +275,6 @@ void RackWidget::fromJson(json_t *rootJ) { | |||
| 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); | |||
| } | |||
| else { | |||
| @@ -295,11 +297,20 @@ void RackWidget::fromJson(json_t *rootJ) { | |||
| json_array_foreach(cablesJ, cableIndex, cableJ) { | |||
| // Create a unserialize cable | |||
| CableWidget *cw = new CableWidget; | |||
| cw->fromJson(cableJ, moduleWidgets); | |||
| cw->fromJson(cableJ); | |||
| if (!cw->isComplete()) { | |||
| delete cw; | |||
| 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); | |||
| } | |||
| } | |||
| @@ -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 | |||
| if (streamTime == 0.0) { | |||
| system::setThreadName("Audio"); | |||
| system::setThreadRealTime(); | |||
| } | |||
| audioIO->processStream((const float *) inputBuffer, (float *) outputBuffer, nFrames); | |||
| return 0; | |||
| @@ -1,5 +1,6 @@ | |||
| #include "dsp/minblep.hpp" | |||
| #include "dsp/fft.hpp" | |||
| #include "dsp/window.hpp" | |||
| namespace rack { | |||
| @@ -100,8 +100,6 @@ struct EngineWorker { | |||
| void start() { | |||
| thread = std::thread([&] { | |||
| system::setThreadName("Engine worker"); | |||
| system::setThreadRealTime(); | |||
| run(); | |||
| }); | |||
| } | |||
| @@ -115,6 +113,8 @@ struct EngineWorker { | |||
| } | |||
| void run() { | |||
| system::setThreadName("Engine worker"); | |||
| system::setThreadRealTime(); | |||
| while (running) { | |||
| step(); | |||
| } | |||
| @@ -134,8 +134,8 @@ struct Engine::Internal { | |||
| float sampleTime; | |||
| float sampleRateRequested; | |||
| int nextModuleId = 1; | |||
| int nextCableId = 1; | |||
| int nextModuleId = 0; | |||
| int nextCableId = 0; | |||
| // Parameter smoothing | |||
| 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; | |||
| int threadCount = internal->threadCount; | |||
| 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]; | |||
| if (!module->bypass) { | |||
| // Step module | |||
| @@ -389,17 +389,19 @@ void Engine::addModule(Module *module) { | |||
| auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | |||
| assert(it == internal->modules.end()); | |||
| // Set ID | |||
| if (module->id == 0) { | |||
| if (module->id < 0) { | |||
| // Automatically assign ID | |||
| module->id = internal->nextModuleId++; | |||
| } | |||
| else { | |||
| // Manual ID | |||
| assert(module->id < internal->nextModuleId); | |||
| // Check that the ID is not already taken | |||
| for (Module *m : internal->modules) { | |||
| assert(module->id != m->id); | |||
| } | |||
| if (module->id >= internal->nextModuleId) { | |||
| internal->nextModuleId = module->id + 1; | |||
| } | |||
| } | |||
| // Add module | |||
| internal->modules.push_back(module); | |||
| @@ -424,7 +426,7 @@ void Engine::removeModule(Module *module) { | |||
| // Remove the module | |||
| internal->modules.erase(it); | |||
| // Remove id | |||
| module->id = 0; | |||
| module->id = -1; | |||
| } | |||
| void Engine::resetModule(Module *module) { | |||
| @@ -493,17 +495,19 @@ void Engine::addCable(Cable *cable) { | |||
| assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId)); | |||
| } | |||
| // Set ID | |||
| if (cable->id == 0) { | |||
| if (cable->id < 0) { | |||
| // Automatically assign ID | |||
| cable->id = internal->nextCableId++; | |||
| } | |||
| else { | |||
| // Manual ID | |||
| assert(cable->id < internal->nextCableId); | |||
| // Check that the ID is not already taken | |||
| for (Cable *w : internal->cables) { | |||
| assert(cable->id != w->id); | |||
| } | |||
| if (cable->id >= internal->nextCableId) { | |||
| internal->nextCableId = cable->id + 1; | |||
| } | |||
| } | |||
| // Add the cable | |||
| internal->cables.push_back(cable); | |||
| @@ -524,7 +528,7 @@ void Engine::removeCable(Cable *cable) { | |||
| internal->cables.erase(it); | |||
| Engine_updateConnected(this); | |||
| // Remove ID | |||
| cable->id = 0; | |||
| cable->id = -1; | |||
| } | |||
| void Engine::setParam(Module *module, int paramId, float value) { | |||
| @@ -124,7 +124,7 @@ void ParamChange::redo() { | |||
| void CableAdd::setCable(app::CableWidget *cw) { | |||
| assert(cw->cable); | |||
| assert(cw->cable->id > 0); | |||
| assert(cw->cable->id >= 0); | |||
| cableId = cw->cable->id; | |||
| assert(cw->cable->outputModule); | |||
| outputModuleId = cw->cable->outputModule->id; | |||
| @@ -209,9 +209,9 @@ Window::Window() { | |||
| 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); | |||
| if (settings.windowSize.isZero()) { | |||