From a69ce989035fa9563df33cb2ace12eeaf254e2da Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Sat, 14 Jan 2017 02:59:32 -0500 Subject: [PATCH] Added libsamplerate dependency, some general purpose DSP code --- LICENSE-dist.txt | 29 ++++++ Makefile | 16 ++-- README.md | 1 + include/Rack.hpp | 8 +- include/dsp.hpp | 172 +++++++++++++++++++++++++++++++++++ include/util.hpp | 33 +++++-- include/widgets.hpp | 9 +- src/gui.cpp | 4 + src/plugin.cpp | 1 + src/rack.cpp | 10 ++ src/util.cpp | 20 +++- src/widgets/Button.cpp | 8 ++ src/widgets/ModulePanel.cpp | 2 +- src/widgets/ModuleWidget.cpp | 9 ++ src/widgets/Port.cpp | 5 +- src/widgets/RackWidget.cpp | 3 +- src/widgets/Screw.cpp | 3 +- 17 files changed, 305 insertions(+), 28 deletions(-) create mode 100644 include/dsp.hpp diff --git a/LICENSE-dist.txt b/LICENSE-dist.txt index 2e879745..9f5a8d8d 100644 --- a/LICENSE-dist.txt +++ b/LICENSE-dist.txt @@ -206,3 +206,32 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +# libsamplerate + +Copyright (c) 2012-2016, Erik de Castro Lopo +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile index 1439dc97..b6a253c5 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ CXX = g++ SOURCES += lib/noc/noc_file_dialog.c CFLAGS += -DNOC_FILE_DIALOG_GTK $(shell pkg-config --cflags gtk+-2.0) CXXFLAGS += -DLINUX -LDFLAGS += -rdynamic -lpthread -lGL -lGLEW -lglfw -ldl -ljansson -lportaudio -lportmidi \ +LDFLAGS += -rdynamic -lpthread -lGL -lGLEW -lglfw -ldl -ljansson -lportaudio -lportmidi -lsamplerate \ $(shell pkg-config --libs gtk+-2.0) TARGET = Rack endif @@ -27,7 +27,7 @@ CXX = clang++ SOURCES += lib/noc/noc_file_dialog.m CFLAGS += -DNOC_FILE_DIALOG_OSX CXXFLAGS += -DAPPLE -stdlib=libc++ -I$(HOME)/local/include -LDFLAGS += -stdlib=libc++ -L$(HOME)/local/lib -lpthread -lglew -lglfw3 -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo -ldl -ljansson -lportaudio -lportmidi +LDFLAGS += -stdlib=libc++ -L$(HOME)/local/lib -lpthread -lglew -lglfw3 -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo -ldl -ljansson -lportaudio -lportmidi -lsamplerate TARGET = Rack Rack.app: $(TARGET) @@ -40,12 +40,14 @@ CC = x86_64-w64-mingw32-gcc CXX = x86_64-w64-mingw32-g++ SOURCES += lib/noc/noc_file_dialog.c CFLAGS += -DNOC_FILE_DIALOG_WIN32 -CXXFLAGS += -DWINDOWS -D_USE_MATH_DEFINES \ +CXXFLAGS += -DWINDOWS -D_USE_MATH_DEFINES -DGLEW_STATIC \ -I$(HOME)/pkg/portaudio-r1891-build/include -LDFLAGS += -lpthread \ - -lglfw3 -lgdi32 -lopengl32 -lglew32 \ - -lcomdlg32 -lole32 \ - -ljansson -lportmidi \ +LDFLAGS += \ + -Wl,-Bstatic,--whole-archive \ + -lglfw3 -lgdi32 -lglew32 -ljansson -lsamplerate \ + -Wl,-Bdynamic,--no-whole-archive \ + -lpthread -lopengl32 -lcomdlg32 -lole32 \ + -lportmidi \ -L$(HOME)/pkg/portaudio-r1891-build/lib/x64/ReleaseMinDependency -lportaudio_x64 \ -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows TARGET = Rack.exe diff --git a/README.md b/README.md index e88181fd..fdcf383d 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Install dependencies - [jansson](http://www.digip.org/jansson/) - [portaudio](http://www.portaudio.com/) - [portmidi](http://portmedia.sourceforge.net/portmidi/) +- [libsamplerate](http://www.mega-nerd.com/SRC/) - GTK+-2.0 if Linux (for file open/save dialog) Run `make ARCH=linux` or `make ARCH=windows` or `make ARCH=apple` diff --git a/include/Rack.hpp b/include/Rack.hpp index 1f651bb4..44f576bc 100644 --- a/include/Rack.hpp +++ b/include/Rack.hpp @@ -57,6 +57,7 @@ extern Vec gMousePos; extern Widget *gHoveredWidget; extern Widget *gDraggedWidget; extern Widget *gSelectedWidget; +extern int gGuiFrame; void guiInit(); void guiDestroy(); @@ -82,10 +83,13 @@ struct Wire; struct Module { std::vector params; - // Pointers to voltage values at each port - // If value is NULL, the input/output is disconnected + /** Pointers to voltage values at each port + If value is NULL, the input/output is disconnected + */ std::vector inputs; std::vector outputs; + /** For CPU usage */ + float cpuTime = 0.0; virtual ~Module() {} diff --git a/include/dsp.hpp b/include/dsp.hpp new file mode 100644 index 00000000..3806b6e4 --- /dev/null +++ b/include/dsp.hpp @@ -0,0 +1,172 @@ +#pragma once + +#include +#include +#include + + +namespace rack { + + +/** S must be a power of 2 */ +template +struct RingBuffer { + T data[S] = {}; + size_t start = 0; + size_t end = 0; + + size_t mask(size_t i) { + return i & (S - 1); + } + void push(T t) { + size_t i = mask(end++); + data[i] = t; + } + T shift() { + return data[mask(start++)]; + } + bool empty() { + return start >= end; + } + bool full() { + return end - start >= S; + } + size_t size() { + return end - start; + } +}; + + +/** S must be a power of 2 */ +template +struct DoubleRingBuffer { + T data[S*2] = {}; + size_t start = 0; + size_t end = 0; + + size_t mask(size_t i) { + return i & (S - 1); + } + void push(T t) { + size_t i = mask(end++); + data[i] = t; + data[i + S] = t; + } + T shift() { + return data[mask(start++)]; + } + bool empty() { + return start >= end; + } + bool full() { + return end - start >= S; + } + size_t size() { + return end - start; + } +}; + + +template +struct AppleRingBuffer { + T data[N] = {}; + size_t start = 0; + size_t end = 0; + + void push(T t) { + data[end++] = t; + if (end >= N) { + // move end block to beginning + memmove(data, &data[N - S], sizeof(T) * S); + start -= N - S; + end = S; + } + } + T shift() { + return data[start++]; + } + bool empty() { + return start >= end; + } + bool full() { + return end - start >= S; + } + size_t size() { + return end - start; + } +}; + + +template +struct SampleRateConverter { + SRC_STATE *state; + SRC_DATA data; + + SampleRateConverter() { + int error; + state = src_new(SRC_SINC_FASTEST, 1, &error); + assert(!error); + + data.src_ratio = 1.0; + data.end_of_input = false; + } + ~SampleRateConverter() { + src_delete(state); + } + void setRatio(float r) { + data.src_ratio = r; + } + void push(const float *in, int length) { + float out[S]; + data.data_in = in; + data.input_frames = length; + data.data_out = out; + data.output_frames = S; + src_process(state, &data); + } + void push(float in) { + push(&in, 1); + } +}; + + +template +struct Decimator { + SRC_STATE *state; + SRC_DATA data; + + Decimator() { + int error; + state = src_new(SRC_SINC_FASTEST, 1, &error); + assert(!error); + + data.data_in = NULL; + data.data_out = NULL; + data.input_frames = OVERSAMPLE; + data.output_frames = 1; + data.end_of_input = false; + data.src_ratio = 1.0 / OVERSAMPLE; + } + ~Decimator() { + src_delete(state); + } + /** input must be length OVERSAMPLE */ + float process(float *input) { + float output[1]; + data.data_in = input; + data.data_out = output; + src_process(state, &data); + if (data.output_frames_gen > 0) { + return output[0]; + } + else { + return 0.0; + } + } + void reset() { + src_reset(state); + } +}; + + +} // namespace rack diff --git a/include/util.hpp b/include/util.hpp index aa14bee6..ee1b4a4a 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -12,7 +12,7 @@ namespace rack { //////////////////// /** Limits a value between a minimum and maximum -If min > max for some reason, returns min; +If min > max for some reason, returns min */ inline float clampf(float x, float min, float max) { if (x > max) @@ -22,6 +22,13 @@ inline float clampf(float x, float min, float max) { return 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; +} + inline float mapf(float x, float xMin, float xMax, float yMin, float yMax) { return yMin + (x - xMin) / (xMax - xMin) * (yMax - yMin); } @@ -43,12 +50,22 @@ inline float quadraticBipolar(float x) { return x >= 0.0 ? x2 : -x2; } +inline float cubic(float x) { + // optimal with --fast-math + return x*x*x; +} + inline float quarticBipolar(float x) { float x2 = x*x; float x4 = x2*x2; return x >= 0.0 ? x4 : -x4; } +inline float quintic(float x) { + // optimal with --fast-math + return x*x*x*x*x; +} + // Euclidean modulus, always returns 0 <= mod < base for positive base // Assumes this architecture's division is non-Euclidean inline int eucMod(int a, int base) { @@ -68,17 +85,21 @@ inline void setf(float *p, float v) { /** Linearly interpolate an array `p` with index `x` Assumes that the array at `p` is of length at least ceil(x)+1. */ -inline float interpf(float *p, float x) { - int i = x; - x -= i; - return crossf(p[i], p[i+1], x); +inline float interpf(const float *p, float x) { + int xi = x; + float xf = x - xi; + return crossf(p[xi], p[xi+1], xf); } //////////////////// // RNG //////////////////// -extern std::mt19937 rng; +uint32_t randomu32(); +/** Returns a uniform random float in the interval [0.0, 1.0) */ +float randomf(); +/** Returns a normal random number with mean 0 and std dev 1 */ +float randomNormal(); //////////////////// // 2D float vector diff --git a/include/widgets.hpp b/include/widgets.hpp index 73e49444..f5189a08 100644 --- a/include/widgets.hpp +++ b/include/widgets.hpp @@ -79,12 +79,12 @@ struct Widget { /** Called when a widget responds to `onMouseDown` for a left button press */ virtual void onDragStart() {} + /** Called when the left button is released and this widget is being dragged */ + virtual void onDragEnd() {} /** Called when a widget responds to `onMouseMove` and is being dragged */ virtual void onDragMove(Vec mouseRel) {} /** Called when a widget responds to `onMouseUp` for a left button release and a widget is being dragged */ virtual void onDragDrop(Widget *origin) {} - /** Called when the left button is released and this widget is being dragged */ - virtual void onDragEnd() {} virtual void onAction() {} virtual void onChange() {} @@ -217,7 +217,9 @@ struct Button : OpaqueWidget { } void draw(NVGcontext *vg); void onMouseEnter(); - void onMouseLeave() ; + void onMouseLeave(); + void onDragStart(); + void onDragEnd(); void onDragDrop(Widget *origin); }; @@ -339,7 +341,6 @@ struct RackWidget : OpaqueWidget { json_t *toJson(); void fromJson(json_t *root); - int frame = 0; void repositionModule(ModuleWidget *module); void step(); void draw(NVGcontext *vg); diff --git a/src/gui.cpp b/src/gui.cpp index d3cf587d..4313abb0 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -27,6 +27,8 @@ Widget *gHoveredWidget = NULL; Widget *gDraggedWidget = NULL; Widget *gSelectedWidget = NULL; +int gGuiFrame; + static GLFWwindow *window = NULL; static NVGcontext *vg = NULL; @@ -225,8 +227,10 @@ void guiRun() { glfwGetWindowSize(window, &width, &height); windowSizeCallback(window, width, height); } + gGuiFrame = 0; double lastTime = 0.0; while(!glfwWindowShouldClose(window)) { + gGuiFrame++; glfwPollEvents(); { double xpos, ypos; diff --git a/src/plugin.cpp b/src/plugin.cpp index a41211ce..41e46f31 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -94,6 +94,7 @@ void pluginInit() { void pluginDestroy() { for (Plugin *plugin : gPlugins) { + // TODO unload plugin with `dlclose` or `FreeLibrary` delete plugin; } gPlugins.clear(); diff --git a/src/rack.cpp b/src/rack.cpp index 6c6c88dd..11d5947b 100644 --- a/src/rack.cpp +++ b/src/rack.cpp @@ -73,8 +73,18 @@ void rackStep() { } } // Step all modules + std::chrono::time_point start, end; for (Module *module : modules) { + // Start clock for CPU usage + start = std::chrono::high_resolution_clock::now(); + // Step module by one frame module->step(); + // Stop clock and smooth step time value + end = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff = end - start; + float elapsed = diff.count() * SAMPLE_RATE; + const float lambda = 1.0; + module->cpuTime += (elapsed - module->cpuTime) * lambda / SAMPLE_RATE; } } diff --git a/src/util.cpp b/src/util.cpp index 56531514..38c89f49 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -3,7 +3,25 @@ namespace rack { +// TODO +// Convert this to xoroshiro128+ and custom normal dist implementation + static std::random_device rd; -std::mt19937 rng(rd()); +static std::mt19937 rng(rd()); +static std::uniform_real_distribution uniformDist; +static std::normal_distribution normalDist; + +uint32_t randomu32() { + return rng(); +} + +float randomf() { + return uniformDist(rng); +} + +float randomNormal(){ + return normalDist(rng); +} + } // namespace rack diff --git a/src/widgets/Button.cpp b/src/widgets/Button.cpp index 87e46565..4a286453 100644 --- a/src/widgets/Button.cpp +++ b/src/widgets/Button.cpp @@ -15,6 +15,14 @@ void Button::onMouseLeave() { state = BND_DEFAULT; } +void Button::onDragStart() { + state = BND_ACTIVE; +} + +void Button::onDragEnd() { + state = BND_HOVER; +} + void Button::onDragDrop(Widget *origin) { if (origin == this) { onAction(); diff --git a/src/widgets/ModulePanel.cpp b/src/widgets/ModulePanel.cpp index 98757df2..413725f4 100644 --- a/src/widgets/ModulePanel.cpp +++ b/src/widgets/ModulePanel.cpp @@ -8,7 +8,7 @@ void ModulePanel::draw(NVGcontext *vg) { nvgRect(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); NVGpaint paint; // Background gradient - Vec c = box.getTopRight(); + Vec c = box.pos; float length = box.size.norm(); paint = nvgRadialGradient(vg, c.x, c.y, 0.0, length, highlightColor, backgroundColor); nvgFillPaint(vg, paint); diff --git a/src/widgets/ModuleWidget.cpp b/src/widgets/ModuleWidget.cpp index 2358948b..91b2af1d 100644 --- a/src/widgets/ModuleWidget.cpp +++ b/src/widgets/ModuleWidget.cpp @@ -98,6 +98,15 @@ void ModuleWidget::cloneParams(ModuleWidget *source) { void ModuleWidget::draw(NVGcontext *vg) { Widget::draw(vg); bndBevel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); + + // CPU usage text + if (module) { + char text[32]; + snprintf(text, sizeof(text), "%.2f%%", module->cpuTime * 10); + nvgSave(vg); + bndSlider(vg, box.pos.x, box.pos.y, box.size.x, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, module->cpuTime, text, NULL); + nvgRestore(vg); + } } void ModuleWidget::onDragStart() { diff --git a/src/widgets/Port.cpp b/src/widgets/Port.cpp index 335cf459..122e506b 100644 --- a/src/widgets/Port.cpp +++ b/src/widgets/Port.cpp @@ -8,9 +8,8 @@ Port::Port() { spriteOffset = Vec(-18, -18); spriteSize = Vec(56, 56); spriteFilename = "res/port.png"; - - std::uniform_int_distribution<> dist(0, 4); - index = dist(rng); + + index = randomu32() % 5; } Port::~Port() { diff --git a/src/widgets/RackWidget.cpp b/src/widgets/RackWidget.cpp index 5719309e..de9d6d83 100644 --- a/src/widgets/RackWidget.cpp +++ b/src/widgets/RackWidget.cpp @@ -248,8 +248,7 @@ void RackWidget::step() { // Autosave every 15 seconds // (This is alpha software, expect crashes!) - if (++frame >= 60*15) { - frame = 0; + if (gGuiFrame % (60*15) == 0) { savePatch("autosave.json"); } diff --git a/src/widgets/Screw.cpp b/src/widgets/Screw.cpp index 402056f9..0cf9caeb 100644 --- a/src/widgets/Screw.cpp +++ b/src/widgets/Screw.cpp @@ -9,8 +9,7 @@ Screw::Screw() { spriteSize = Vec(29, 29); spriteFilename = "res/screw.png"; - std::uniform_int_distribution<> dist(0, 4); - index = dist(rng); + index = randomu32() % 5; }