| @@ -41,7 +41,6 @@ struct ModuleWidget : OpaqueWidget { | |||
| void fromJson(json_t *root); | |||
| void disconnectPorts(); | |||
| void resetParams(); | |||
| void cloneParams(ModuleWidget *source); | |||
| void draw(NVGcontext *vg); | |||
| @@ -5,6 +5,21 @@ | |||
| namespace rack { | |||
| //////////////////// | |||
| // Colors | |||
| //////////////////// | |||
| #define COLOR_BLACK_TRANSPARENT nvgRGBA(0x00, 0x00, 0x00, 0x00) | |||
| #define COLOR_BLACK nvgRGB(0x00, 0x00, 0x00) | |||
| #define COLOR_WHITE nvgRGB(0xff, 0xff, 0xff) | |||
| #define COLOR_RED nvgRGB(0xed, 0x2c, 0x24) | |||
| #define COLOR_ORANGE nvgRGB(0xf2, 0xb1, 0x20) | |||
| #define COLOR_YELLOW nvgRGB(0xf9, 0xdf, 0x1c) | |||
| #define COLOR_GREEN nvgRGB(0x90, 0xc7, 0x3e) | |||
| #define COLOR_CYAN nvgRGB(0x22, 0xe6, 0xef) | |||
| #define COLOR_BLUE nvgRGB(0x29, 0xb2, 0xef) | |||
| #define COLOR_PURPLE nvgRGB(0xd5, 0x2b, 0xed) | |||
| //////////////////// | |||
| // Knobs | |||
| //////////////////// | |||
| @@ -380,19 +395,19 @@ struct ColorValueLight : ValueLight { | |||
| struct RedValueLight : ColorValueLight { | |||
| RedValueLight() { | |||
| baseColor = nvgRGB(0xed, 0x2c, 0x24); | |||
| baseColor = COLOR_RED; | |||
| } | |||
| }; | |||
| struct YellowValueLight : ColorValueLight { | |||
| YellowValueLight() { | |||
| baseColor = nvgRGB(0xf9, 0xdf, 0x1c); | |||
| baseColor = COLOR_YELLOW; | |||
| } | |||
| }; | |||
| struct GreenValueLight : ColorValueLight { | |||
| GreenValueLight() { | |||
| baseColor = nvgRGB(0x90, 0xc7, 0x3e); | |||
| baseColor = COLOR_GREEN; | |||
| } | |||
| }; | |||
| @@ -408,8 +423,19 @@ struct PolarityLight : ValueLight { | |||
| struct GreenRedPolarityLight : PolarityLight { | |||
| GreenRedPolarityLight() { | |||
| posColor = nvgRGB(0x90, 0xc7, 0x3e); | |||
| negColor = nvgRGB(0xed, 0x2c, 0x24); | |||
| posColor = COLOR_GREEN; | |||
| negColor = COLOR_RED; | |||
| } | |||
| }; | |||
| struct ModeValueLight : ValueLight { | |||
| std::vector<NVGcolor> colors; | |||
| void step() { | |||
| int mode = clampi((int)roundf(getf(value)), 0, colors.size()); | |||
| color = colors[mode]; | |||
| } | |||
| void addColor(NVGcolor color) { | |||
| colors.push_back(color); | |||
| } | |||
| }; | |||
| @@ -517,7 +543,8 @@ struct ScrewBlack : SVGScrew { | |||
| struct LightPanel : Panel { | |||
| LightPanel() { | |||
| backgroundColor = nvgRGB(0xe8, 0xe8, 0xe8); | |||
| // backgroundColor = nvgRGB(0xe8, 0xe8, 0xe8); | |||
| backgroundColor = nvgRGB(0xf4, 0xf4, 0xf4); | |||
| borderColor = nvgRGB(0xac, 0xac, 0xac); | |||
| } | |||
| }; | |||
| @@ -74,6 +74,13 @@ struct SimpleFFT { | |||
| }; | |||
| typedef void (*stepCallback)(float x, const float y[], float dydt[]); | |||
| /** Solve an ODE system using the 1st order Euler method */ | |||
| void stepEuler(stepCallback f, float x, float dx, float y[], int len); | |||
| /** Solve an ODE system using the 4th order Runge-Kutta method */ | |||
| void stepRK4(stepCallback f, float x, float dx, float y[], int len); | |||
| /** A simple cyclic buffer. | |||
| S must be a power of 2. | |||
| push() is constant time O(1) | |||
| @@ -247,10 +254,14 @@ struct SampleRateConverter { | |||
| ~SampleRateConverter() { | |||
| src_delete(state); | |||
| } | |||
| /** output_sample_rate / input_sample_rate */ | |||
| void setRatio(float r) { | |||
| src_set_ratio(state, r); | |||
| data.src_ratio = r; | |||
| } | |||
| void setRatioSmooth(float r) { | |||
| data.src_ratio = r; | |||
| } | |||
| /** `in` and `out` are interlaced with the number of channels */ | |||
| void process(const Frame<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *out, int *outFrames) { | |||
| // Old versions of libsamplerate use float* here instead of const float* | |||
| @@ -268,6 +279,65 @@ struct SampleRateConverter { | |||
| }; | |||
| /** Perform a direct convolution | |||
| x[-len + 1] to x[0] must be defined | |||
| */ | |||
| inline float convolve(const float *x, const float *kernel, int len) { | |||
| float y = 0.0; | |||
| for (int i = 0; i < len; i++) { | |||
| y += x[-i] * kernel[i]; | |||
| } | |||
| return y; | |||
| } | |||
| inline void blackmanHarrisWindow(float *x, int n) { | |||
| const float a0 = 0.35875; | |||
| const float a1 = 0.48829; | |||
| const float a2 = 0.14128; | |||
| const float a3 = 0.01168; | |||
| for (int i = 0; i < n; i++) { | |||
| x[i] *= a0 | |||
| - a1 * cosf(2 * M_PI * i / (n - 1)) | |||
| + a2 * cosf(4 * M_PI * i / (n - 1)) | |||
| - a3 * cosf(6 * M_PI * i / (n - 1)); | |||
| } | |||
| } | |||
| inline void boxcarFIR(float *x, int n, float cutoff) { | |||
| for (int i = 0; i < n; i++) { | |||
| float t = (float)i / (n - 1) * 2.0 - 1.0; | |||
| x[i] = sincf(t * n * cutoff); | |||
| } | |||
| } | |||
| template<int OVERSAMPLE, int QUALITY> | |||
| struct Decimator { | |||
| DoubleRingBuffer<float, OVERSAMPLE*QUALITY> inBuffer; | |||
| float kernel[OVERSAMPLE*QUALITY]; | |||
| Decimator(float cutoff = 0.9) { | |||
| boxcarFIR(kernel, OVERSAMPLE*QUALITY, cutoff * 0.5 / OVERSAMPLE); | |||
| blackmanHarrisWindow(kernel, OVERSAMPLE*QUALITY); | |||
| // The sum of the kernel should be 1 | |||
| float sum = 0.0; | |||
| for (int i = 0; i < OVERSAMPLE*QUALITY; i++) { | |||
| sum += kernel[i]; | |||
| } | |||
| for (int i = 0; i < OVERSAMPLE*QUALITY; i++) { | |||
| kernel[i] /= sum; | |||
| } | |||
| } | |||
| float process(float *in) { | |||
| memcpy(inBuffer.endData(), in, OVERSAMPLE*sizeof(float)); | |||
| inBuffer.endIncr(OVERSAMPLE); | |||
| float out = convolve(inBuffer.endData() + OVERSAMPLE*QUALITY, kernel, OVERSAMPLE*QUALITY); | |||
| // Ignore the ring buffer's start position | |||
| return out; | |||
| } | |||
| }; | |||
| // Pre-made minBLEP samples in minBLEP.cpp | |||
| extern const float minblep_16_32[]; | |||
| @@ -340,4 +410,28 @@ struct PeakFilter { | |||
| }; | |||
| struct SlewLimiter { | |||
| float rise = 1.0; | |||
| float fall = 1.0; | |||
| float out = 0.0; | |||
| float process(float in) { | |||
| float delta = clampf(in - out, -fall, rise); | |||
| out += delta; | |||
| return out; | |||
| } | |||
| }; | |||
| /** Triggered when input value rises above 0.0 */ | |||
| struct Trigger { | |||
| float lastIn = 0.0; | |||
| /** Returns whether a trigger is detected */ | |||
| bool process(float in) { | |||
| bool triggered = (lastIn <= 0.0 && in > 0.0); | |||
| lastIn = in; | |||
| return triggered; | |||
| } | |||
| }; | |||
| } // namespace rack | |||
| @@ -1,5 +1,6 @@ | |||
| #pragma once | |||
| #include <vector> | |||
| #include <jansson.h> | |||
| #include "util.hpp" | |||
| @@ -20,6 +21,8 @@ struct Module { | |||
| /** Advances the module by 1 audio frame with duration 1.0 / gSampleRate */ | |||
| virtual void step() {} | |||
| virtual json_t *toJsonData() { return NULL; } | |||
| virtual void fromJsonData(json_t *root) {} | |||
| }; | |||
| struct Wire { | |||
| @@ -17,6 +17,11 @@ inline int maxi(int a, int b) { | |||
| return a > b ? a : b; | |||
| } | |||
| /** Limits a value between a minimum and maximum */ | |||
| inline int clampi(int x, int min, int max) { | |||
| return x > max ? max : x < min ? min : x; | |||
| } | |||
| inline int absi(int a) { | |||
| return a >= 0 ? a : -a; | |||
| } | |||
| @@ -49,30 +54,14 @@ inline float sgnf(float x) { | |||
| return copysignf(1.0, x); | |||
| } | |||
| inline float radtodeg(float x) { | |||
| return x * (180.0 / M_PI); | |||
| } | |||
| inline float degtorad(float x) { | |||
| return x * (M_PI / 180.0); | |||
| } | |||
| /** Limits a value between a minimum and maximum | |||
| If min > max for some reason, returns min | |||
| */ | |||
| /** Limits a value between a minimum and maximum */ | |||
| inline float clampf(float x, float min, float max) { | |||
| if (x > max) | |||
| x = max; | |||
| if (x < min) | |||
| x = min; | |||
| return x; | |||
| return x > max ? max : x < min ? min : x; | |||
| } | |||
| /** If the magnitude of x if less than eps, return 0 */ | |||
| inline float chopf(float x, float eps) { | |||
| if (x < eps && x > -eps) | |||
| return 0.0; | |||
| return x; | |||
| return -eps < x && x < eps ? 0.0 : x; | |||
| } | |||
| inline float mapf(float x, float xMin, float xMax, float yMin, float yMax) { | |||
| @@ -56,6 +56,11 @@ json_t *ModuleWidget::toJson() { | |||
| json_array_append_new(paramsJ, paramJ); | |||
| } | |||
| json_object_set_new(root, "params", paramsJ); | |||
| // data | |||
| json_t *dataJ = module ? module->toJsonData() : NULL; | |||
| if (dataJ) { | |||
| json_object_set_new(root, "data", dataJ); | |||
| } | |||
| return root; | |||
| } | |||
| @@ -76,6 +81,12 @@ void ModuleWidget::fromJson(json_t *root) { | |||
| params[paramId]->fromJson(paramJ); | |||
| } | |||
| } | |||
| // data | |||
| json_t *dataJ = json_object_get(root, "data"); | |||
| if (dataJ && module) { | |||
| module->fromJsonData(dataJ); | |||
| } | |||
| } | |||
| void ModuleWidget::disconnectPorts() { | |||
| @@ -93,13 +104,6 @@ void ModuleWidget::resetParams() { | |||
| } | |||
| } | |||
| void ModuleWidget::cloneParams(ModuleWidget *source) { | |||
| assert(params.size() == source->params.size()); | |||
| for (size_t i = 0; i < params.size(); i++) { | |||
| params[i]->setValue(source->params[i]->value); | |||
| } | |||
| } | |||
| void ModuleWidget::draw(NVGcontext *vg) { | |||
| Widget::draw(vg); | |||
| bndBevel(vg, 0.0, 0.0, box.size.x, box.size.y); | |||
| @@ -157,9 +161,11 @@ struct CloneModuleMenuItem : MenuItem { | |||
| void onAction() { | |||
| // Create new module from model | |||
| ModuleWidget *clonedModuleWidget = moduleWidget->model->createModuleWidget(); | |||
| json_t *moduleJ = moduleWidget->toJson(); | |||
| clonedModuleWidget->fromJson(moduleJ); | |||
| json_decref(moduleJ); | |||
| clonedModuleWidget->requestedPos = moduleWidget->box.pos; | |||
| clonedModuleWidget->requested = true; | |||
| clonedModuleWidget->cloneParams(moduleWidget); | |||
| gRackWidget->moduleContainer->addChild(clonedModuleWidget); | |||
| } | |||
| }; | |||
| @@ -14,4 +14,44 @@ const float minblep_16_32[] = { | |||
| }; | |||
| void stepEuler(stepCallback f, float x, float dx, float y[], int len) { | |||
| float k[len]; | |||
| f(x, y, k); | |||
| for (int i = 0; i < len; i++) { | |||
| y[i] += dx * k[i]; | |||
| } | |||
| } | |||
| void stepRK4(stepCallback f, float x, float dx, float y[], int len) { | |||
| float k1[len]; | |||
| float k2[len]; | |||
| float k3[len]; | |||
| float k4[len]; | |||
| float yi[len]; | |||
| f(x, y, k1); | |||
| for (int i = 0; i < len; i++) { | |||
| yi[i] = y[i] + k1[i] * dx / 2.0; | |||
| } | |||
| f(x + dx / 2.0, yi, k2); | |||
| for (int i = 0; i < len; i++) { | |||
| yi[i] = y[i] + k2[i] * dx / 2.0; | |||
| } | |||
| f(x + dx / 2.0, yi, k3); | |||
| for (int i = 0; i < len; i++) { | |||
| yi[i] = y[i] + k3[i] * dx; | |||
| } | |||
| f(x + dx, yi, k4); | |||
| for (int i = 0; i < len; i++) { | |||
| y[i] += dx * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0; | |||
| } | |||
| } | |||
| } // namespace rack | |||