@@ -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()) { | |||