| @@ -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 <erikd@mega-nerd.com> | |||
| 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. | |||
| @@ -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 | |||
| @@ -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` | |||
| @@ -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<float> 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<float*> inputs; | |||
| std::vector<float*> outputs; | |||
| /** For CPU usage */ | |||
| float cpuTime = 0.0; | |||
| virtual ~Module() {} | |||
| @@ -0,0 +1,172 @@ | |||
| #pragma once | |||
| #include <assert.h> | |||
| #include <string.h> | |||
| #include <samplerate.h> | |||
| namespace rack { | |||
| /** S must be a power of 2 */ | |||
| template <typename T, size_t S> | |||
| 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 <typename T, size_t S> | |||
| 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 <typename T, size_t S, size_t N> | |||
| 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 <size_t S> | |||
| 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 <size_t OVERSAMPLE> | |||
| 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 | |||
| @@ -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 | |||
| @@ -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); | |||
| @@ -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; | |||
| @@ -94,6 +94,7 @@ void pluginInit() { | |||
| void pluginDestroy() { | |||
| for (Plugin *plugin : gPlugins) { | |||
| // TODO unload plugin with `dlclose` or `FreeLibrary` | |||
| delete plugin; | |||
| } | |||
| gPlugins.clear(); | |||
| @@ -73,8 +73,18 @@ void rackStep() { | |||
| } | |||
| } | |||
| // Step all modules | |||
| std::chrono::time_point<std::chrono::high_resolution_clock> 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<float> diff = end - start; | |||
| float elapsed = diff.count() * SAMPLE_RATE; | |||
| const float lambda = 1.0; | |||
| module->cpuTime += (elapsed - module->cpuTime) * lambda / SAMPLE_RATE; | |||
| } | |||
| } | |||
| @@ -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<float> uniformDist; | |||
| static std::normal_distribution<float> normalDist; | |||
| uint32_t randomu32() { | |||
| return rng(); | |||
| } | |||
| float randomf() { | |||
| return uniformDist(rng); | |||
| } | |||
| float randomNormal(){ | |||
| return normalDist(rng); | |||
| } | |||
| } // namespace rack | |||
| @@ -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(); | |||
| @@ -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); | |||
| @@ -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() { | |||
| @@ -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() { | |||
| @@ -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"); | |||
| } | |||
| @@ -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; | |||
| } | |||