@@ -16,6 +16,7 @@ struct Knob : ParamWidget { | |||||
bool snap = false; | bool snap = false; | ||||
float snapValue = NAN; | float snapValue = NAN; | ||||
void onHover(const event::Hover &e) override; | |||||
void onButton(const event::Button &e) override; | void onButton(const event::Button &e) override; | ||||
void onDragStart(const event::DragStart &e) override; | void onDragStart(const event::DragStart &e) override; | ||||
void onDragEnd(const event::DragEnd &e) override; | void onDragEnd(const event::DragEnd &e) override; | ||||
@@ -1,6 +1,6 @@ | |||||
#pragma once | #pragma once | ||||
#include "app/common.hpp" | #include "app/common.hpp" | ||||
#include "app/Knob.hpp" | |||||
#include "app/SliderKnob.hpp" | |||||
#include "widgets/FramebufferWidget.hpp" | #include "widgets/FramebufferWidget.hpp" | ||||
#include "widgets/SVGWidget.hpp" | #include "widgets/SVGWidget.hpp" | ||||
@@ -11,7 +11,7 @@ namespace rack { | |||||
/** Behaves like a knob but linearly moves an SVGWidget between two points. | /** Behaves like a knob but linearly moves an SVGWidget between two points. | ||||
Can be used for horizontal or vertical linear faders. | Can be used for horizontal or vertical linear faders. | ||||
*/ | */ | ||||
struct SVGSlider : Knob { | |||||
struct SVGSlider : SliderKnob { | |||||
FramebufferWidget *fb; | FramebufferWidget *fb; | ||||
SVGWidget *background; | SVGWidget *background; | ||||
SVGWidget *handle; | SVGWidget *handle; | ||||
@@ -0,0 +1,19 @@ | |||||
#pragma once | |||||
#include "app/common.hpp" | |||||
#include "app/Knob.hpp" | |||||
namespace rack { | |||||
struct SliderKnob : Knob { | |||||
void onHover(const event::Hover &e) override { | |||||
ParamWidget::onHover(e); | |||||
} | |||||
void onButton(const event::Button &e) override { | |||||
ParamWidget::onButton(e); | |||||
} | |||||
}; | |||||
} // namespace rack |
@@ -19,7 +19,8 @@ void alignedDelete(T *p) { | |||||
/** Real-valued FFT context | /** Real-valued FFT context | ||||
Wraps PFFFT (https://bitbucket.org/jpommier/pffft/) | |||||
Wrapper for PFFFT (https://bitbucket.org/jpommier/pffft/) | |||||
`length` must be a multiple of 32. | |||||
*/ | */ | ||||
struct RealFFT { | struct RealFFT { | ||||
PFFFT_Setup *setup; | PFFFT_Setup *setup; | ||||
@@ -83,6 +84,9 @@ struct RealFFT { | |||||
}; | }; | ||||
/** Complex-valued FFT context | |||||
`length` must be a multiple of 16. | |||||
*/ | |||||
struct ComplexFFT { | struct ComplexFFT { | ||||
PFFFT_Setup *setup; | PFFFT_Setup *setup; | ||||
int length; | int length; | ||||
@@ -8,9 +8,6 @@ | |||||
namespace rack { | namespace rack { | ||||
static const float SLIDER_SENSITIVITY = 0.001f; | |||||
struct Slider : OpaqueWidget { | struct Slider : OpaqueWidget { | ||||
BNDwidgetState state = BND_DEFAULT; | BNDwidgetState state = BND_DEFAULT; | ||||
Quantity *quantity = NULL; | Quantity *quantity = NULL; | ||||
@@ -10,11 +10,18 @@ namespace rack { | |||||
static const float KNOB_SENSITIVITY = 0.0015f; | static const float KNOB_SENSITIVITY = 0.0015f; | ||||
void Knob::onHover(const event::Hover &e) { | |||||
math::Vec c = box.size.div(2); | |||||
float dist = e.pos.minus(c).norm(); | |||||
if (dist <= c.x) { | |||||
ParamWidget::onHover(e); | |||||
} | |||||
} | |||||
void Knob::onButton(const event::Button &e) { | void Knob::onButton(const event::Button &e) { | ||||
float r = box.size.x / 2; | |||||
math::Vec c = box.size.div(2); | math::Vec c = box.size.div(2); | ||||
float dist = e.pos.minus(c).norm(); | float dist = e.pos.minus(c).norm(); | ||||
if (dist <= r) { | |||||
if (dist <= c.x) { | |||||
ParamWidget::onButton(e); | ParamWidget::onButton(e); | ||||
} | } | ||||
} | } | ||||
@@ -286,7 +286,7 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||||
nvgRect(vg, | nvgRect(vg, | ||||
0, box.size.y - 20, | 0, box.size.y - 20, | ||||
65, 20); | 65, 20); | ||||
nvgFillColor(vg, nvgRGBAf(0, 0, 0, 0.5)); | |||||
nvgFillColor(vg, nvgRGBAf(0, 0, 0, 0.75)); | |||||
nvgFill(vg); | nvgFill(vg); | ||||
std::string cpuText = string::f("%.2f ÎĽs", module->cpuTime * 1e6f); | std::string cpuText = string::f("%.2f ÎĽs", module->cpuTime * 1e6f); | ||||
@@ -58,34 +58,44 @@ float ParamQuantity::getDefaultValue() { | |||||
float ParamQuantity::getDisplayValue() { | float ParamQuantity::getDisplayValue() { | ||||
if (!module) | if (!module) | ||||
return Quantity::getDisplayValue(); | return Quantity::getDisplayValue(); | ||||
if (getParam()->displayBase == 0.f) { | |||||
float displayBase = getParam()->displayBase; | |||||
if (displayBase == 0.f) { | |||||
// Linear | // Linear | ||||
return getSmoothValue() * getParam()->displayMultiplier; | return getSmoothValue() * getParam()->displayMultiplier; | ||||
} | } | ||||
else if (getParam()->displayBase == 1.f) { | |||||
else if (displayBase == 1.f) { | |||||
// Fixed (special case of exponential) | // Fixed (special case of exponential) | ||||
return getParam()->displayMultiplier; | return getParam()->displayMultiplier; | ||||
} | } | ||||
else if (displayBase < 0.f) { | |||||
// Logarithmic | |||||
return std::log(getSmoothValue()) / std::log(-displayBase) * getParam()->displayMultiplier; | |||||
} | |||||
else { | else { | ||||
// Exponential | // Exponential | ||||
return std::pow(getParam()->displayBase, getSmoothValue()) * getParam()->displayMultiplier; | |||||
return std::pow(displayBase, getSmoothValue()) * getParam()->displayMultiplier; | |||||
} | } | ||||
} | } | ||||
void ParamQuantity::setDisplayValue(float displayValue) { | void ParamQuantity::setDisplayValue(float displayValue) { | ||||
if (!module) | if (!module) | ||||
return; | return; | ||||
if (getParam()->displayBase == 0.f) { | |||||
float displayBase = getParam()->displayBase; | |||||
if (displayBase == 0.f) { | |||||
// Linear | // Linear | ||||
setValue(displayValue / getParam()->displayMultiplier); | setValue(displayValue / getParam()->displayMultiplier); | ||||
} | } | ||||
else if (getParam()->displayBase == 1.f) { | |||||
else if (displayBase == 1.f) { | |||||
// Fixed | // Fixed | ||||
setValue(getParam()->displayMultiplier); | setValue(getParam()->displayMultiplier); | ||||
} | } | ||||
else if (displayBase < 0.f) { | |||||
// Logarithmic | |||||
setValue(std::pow(displayBase, displayValue / getParam()->displayMultiplier)); | |||||
} | |||||
else { | else { | ||||
// Exponential | // Exponential | ||||
setValue(std::log(displayValue / getParam()->displayMultiplier) / std::log(getParam()->displayBase)); | |||||
setValue(std::log(displayValue / getParam()->displayMultiplier) / std::log(displayBase)); | |||||
} | } | ||||
} | } | ||||
@@ -0,0 +1,81 @@ | |||||
#include "dsp/minblep.hpp" | |||||
#include "dsp/fft.hpp" | |||||
namespace rack { | |||||
namespace dsp { | |||||
void minBlepImpulse(int z, int o, float *output) { | |||||
// Symmetric sinc array with `z` zero-crossings on each side | |||||
int n = 2 * z * o; | |||||
float *x = alignedNew<float>(n); | |||||
for (int i = 0; i < n; i++) { | |||||
float p = math::rescale((float) i, 0.f, (float) (n - 1), (float) -z, (float) z); | |||||
x[i] = sinc(p); | |||||
} | |||||
// Apply window | |||||
blackmanHarrisWindow(x, n); | |||||
// Real cepstrum | |||||
float *fx = alignedNew<float>(2*n); | |||||
RealFFT rfft(n); | |||||
rfft.rfft(x, fx); | |||||
// fx = log(abs(fx)) | |||||
fx[0] = std::log(std::abs(fx[0])); | |||||
for (int i = 1; i < n; i++) { | |||||
fx[2*i] = std::log(std::hypot(fx[2*i], fx[2*i+1])); | |||||
fx[2*i+1] = 0.f; | |||||
} | |||||
fx[1] = std::log(std::abs(fx[1])); | |||||
// Clamp values in case we have -inf | |||||
for (int i = 0; i < 2*n; i++) { | |||||
fx[i] = std::max(-30.f, fx[i]); | |||||
} | |||||
rfft.irfft(fx, x); | |||||
rfft.scale(x); | |||||
// Minimum-phase reconstruction | |||||
for (int i = 1; i < n / 2; i++) { | |||||
x[i] *= 2.f; | |||||
} | |||||
for (int i = (n + 1) / 2; i < n; i++) { | |||||
x[i] = 0.f; | |||||
} | |||||
rfft.rfft(x, fx); | |||||
// fx = exp(fx) | |||||
fx[0] = std::exp(fx[0]); | |||||
for (int i = 1; i < n; i++) { | |||||
float re = std::exp(fx[2*i]); | |||||
float im = fx[2*i+1]; | |||||
fx[2*i] = re * std::cos(im); | |||||
fx[2*i+1] = re * std::sin(im); | |||||
} | |||||
fx[1] = std::exp(fx[1]); | |||||
rfft.irfft(fx, x); | |||||
rfft.scale(x); | |||||
// Integrate | |||||
float total = 0.f; | |||||
for (int i = 0; i < n; i++) { | |||||
total += x[i]; | |||||
x[i] = total; | |||||
} | |||||
// Normalize | |||||
float norm = 1.f / x[n - 1]; | |||||
for (int i = 0; i < n; i++) { | |||||
x[i] *= norm; | |||||
} | |||||
std::memcpy(output, x, n * sizeof(float)); | |||||
// Cleanup | |||||
alignedDelete(x); | |||||
alignedDelete(fx); | |||||
} | |||||
} // namespace dsp | |||||
} // namespace rack |
@@ -4,6 +4,9 @@ | |||||
namespace rack { | namespace rack { | ||||
static const float SENSITIVITY = 0.001f; | |||||
Slider::Slider() { | Slider::Slider() { | ||||
box.size.y = BND_WIDGET_HEIGHT; | box.size.y = BND_WIDGET_HEIGHT; | ||||
} | } | ||||
@@ -26,7 +29,7 @@ void Slider::onDragStart(const event::DragStart &e) { | |||||
void Slider::onDragMove(const event::DragMove &e) { | void Slider::onDragMove(const event::DragMove &e) { | ||||
if (quantity) { | if (quantity) { | ||||
quantity->moveScaledValue(SLIDER_SENSITIVITY * e.mouseDelta.x); | |||||
quantity->moveScaledValue(SENSITIVITY * e.mouseDelta.x); | |||||
} | } | ||||
} | } | ||||