@@ -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 |