@@ -41,7 +41,6 @@ struct ModuleWidget : OpaqueWidget { | |||||
void fromJson(json_t *root); | void fromJson(json_t *root); | ||||
void disconnectPorts(); | void disconnectPorts(); | ||||
void resetParams(); | void resetParams(); | ||||
void cloneParams(ModuleWidget *source); | |||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
@@ -5,6 +5,21 @@ | |||||
namespace rack { | 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 | // Knobs | ||||
//////////////////// | //////////////////// | ||||
@@ -380,19 +395,19 @@ struct ColorValueLight : ValueLight { | |||||
struct RedValueLight : ColorValueLight { | struct RedValueLight : ColorValueLight { | ||||
RedValueLight() { | RedValueLight() { | ||||
baseColor = nvgRGB(0xed, 0x2c, 0x24); | |||||
baseColor = COLOR_RED; | |||||
} | } | ||||
}; | }; | ||||
struct YellowValueLight : ColorValueLight { | struct YellowValueLight : ColorValueLight { | ||||
YellowValueLight() { | YellowValueLight() { | ||||
baseColor = nvgRGB(0xf9, 0xdf, 0x1c); | |||||
baseColor = COLOR_YELLOW; | |||||
} | } | ||||
}; | }; | ||||
struct GreenValueLight : ColorValueLight { | struct GreenValueLight : ColorValueLight { | ||||
GreenValueLight() { | GreenValueLight() { | ||||
baseColor = nvgRGB(0x90, 0xc7, 0x3e); | |||||
baseColor = COLOR_GREEN; | |||||
} | } | ||||
}; | }; | ||||
@@ -408,8 +423,19 @@ struct PolarityLight : ValueLight { | |||||
struct GreenRedPolarityLight : PolarityLight { | struct GreenRedPolarityLight : PolarityLight { | ||||
GreenRedPolarityLight() { | 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 { | struct LightPanel : Panel { | ||||
LightPanel() { | LightPanel() { | ||||
backgroundColor = nvgRGB(0xe8, 0xe8, 0xe8); | |||||
// backgroundColor = nvgRGB(0xe8, 0xe8, 0xe8); | |||||
backgroundColor = nvgRGB(0xf4, 0xf4, 0xf4); | |||||
borderColor = nvgRGB(0xac, 0xac, 0xac); | 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. | /** A simple cyclic buffer. | ||||
S must be a power of 2. | S must be a power of 2. | ||||
push() is constant time O(1) | push() is constant time O(1) | ||||
@@ -247,10 +254,14 @@ struct SampleRateConverter { | |||||
~SampleRateConverter() { | ~SampleRateConverter() { | ||||
src_delete(state); | src_delete(state); | ||||
} | } | ||||
/** output_sample_rate / input_sample_rate */ | |||||
void setRatio(float r) { | void setRatio(float r) { | ||||
src_set_ratio(state, r); | src_set_ratio(state, r); | ||||
data.src_ratio = r; | data.src_ratio = r; | ||||
} | } | ||||
void setRatioSmooth(float r) { | |||||
data.src_ratio = r; | |||||
} | |||||
/** `in` and `out` are interlaced with the number of channels */ | /** `in` and `out` are interlaced with the number of channels */ | ||||
void process(const Frame<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *out, int *outFrames) { | void process(const Frame<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *out, int *outFrames) { | ||||
// Old versions of libsamplerate use float* here instead of const float* | // 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 | // Pre-made minBLEP samples in minBLEP.cpp | ||||
extern const float minblep_16_32[]; | 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 | } // namespace rack |
@@ -1,5 +1,6 @@ | |||||
#pragma once | #pragma once | ||||
#include <vector> | #include <vector> | ||||
#include <jansson.h> | |||||
#include "util.hpp" | #include "util.hpp" | ||||
@@ -20,6 +21,8 @@ struct Module { | |||||
/** Advances the module by 1 audio frame with duration 1.0 / gSampleRate */ | /** Advances the module by 1 audio frame with duration 1.0 / gSampleRate */ | ||||
virtual void step() {} | virtual void step() {} | ||||
virtual json_t *toJsonData() { return NULL; } | |||||
virtual void fromJsonData(json_t *root) {} | |||||
}; | }; | ||||
struct Wire { | struct Wire { | ||||
@@ -17,6 +17,11 @@ inline int maxi(int a, int b) { | |||||
return a > b ? a : 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) { | inline int absi(int a) { | ||||
return a >= 0 ? a : -a; | return a >= 0 ? a : -a; | ||||
} | } | ||||
@@ -49,30 +54,14 @@ inline float sgnf(float x) { | |||||
return copysignf(1.0, 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) { | 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 */ | /** If the magnitude of x if less than eps, return 0 */ | ||||
inline float chopf(float x, float eps) { | 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) { | 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_array_append_new(paramsJ, paramJ); | ||||
} | } | ||||
json_object_set_new(root, "params", paramsJ); | 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; | return root; | ||||
} | } | ||||
@@ -76,6 +81,12 @@ void ModuleWidget::fromJson(json_t *root) { | |||||
params[paramId]->fromJson(paramJ); | params[paramId]->fromJson(paramJ); | ||||
} | } | ||||
} | } | ||||
// data | |||||
json_t *dataJ = json_object_get(root, "data"); | |||||
if (dataJ && module) { | |||||
module->fromJsonData(dataJ); | |||||
} | |||||
} | } | ||||
void ModuleWidget::disconnectPorts() { | 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) { | void ModuleWidget::draw(NVGcontext *vg) { | ||||
Widget::draw(vg); | Widget::draw(vg); | ||||
bndBevel(vg, 0.0, 0.0, box.size.x, box.size.y); | bndBevel(vg, 0.0, 0.0, box.size.x, box.size.y); | ||||
@@ -157,9 +161,11 @@ struct CloneModuleMenuItem : MenuItem { | |||||
void onAction() { | void onAction() { | ||||
// Create new module from model | // Create new module from model | ||||
ModuleWidget *clonedModuleWidget = moduleWidget->model->createModuleWidget(); | ModuleWidget *clonedModuleWidget = moduleWidget->model->createModuleWidget(); | ||||
json_t *moduleJ = moduleWidget->toJson(); | |||||
clonedModuleWidget->fromJson(moduleJ); | |||||
json_decref(moduleJ); | |||||
clonedModuleWidget->requestedPos = moduleWidget->box.pos; | clonedModuleWidget->requestedPos = moduleWidget->box.pos; | ||||
clonedModuleWidget->requested = true; | clonedModuleWidget->requested = true; | ||||
clonedModuleWidget->cloneParams(moduleWidget); | |||||
gRackWidget->moduleContainer->addChild(clonedModuleWidget); | 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 | } // namespace rack |