| @@ -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); | |||||
| } | } | ||||
| } | } | ||||