| @@ -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 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | ||||
| CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | 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 | SOURCES += lib/noc/noc_file_dialog.c | ||||
| CFLAGS += -DNOC_FILE_DIALOG_GTK $(shell pkg-config --cflags gtk+-2.0) | CFLAGS += -DNOC_FILE_DIALOG_GTK $(shell pkg-config --cflags gtk+-2.0) | ||||
| CXXFLAGS += -DLINUX | 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) | $(shell pkg-config --libs gtk+-2.0) | ||||
| TARGET = Rack | TARGET = Rack | ||||
| endif | endif | ||||
| @@ -27,7 +27,7 @@ CXX = clang++ | |||||
| SOURCES += lib/noc/noc_file_dialog.m | SOURCES += lib/noc/noc_file_dialog.m | ||||
| CFLAGS += -DNOC_FILE_DIALOG_OSX | CFLAGS += -DNOC_FILE_DIALOG_OSX | ||||
| CXXFLAGS += -DAPPLE -stdlib=libc++ -I$(HOME)/local/include | 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 | TARGET = Rack | ||||
| Rack.app: $(TARGET) | Rack.app: $(TARGET) | ||||
| @@ -40,12 +40,14 @@ CC = x86_64-w64-mingw32-gcc | |||||
| CXX = x86_64-w64-mingw32-g++ | CXX = x86_64-w64-mingw32-g++ | ||||
| SOURCES += lib/noc/noc_file_dialog.c | SOURCES += lib/noc/noc_file_dialog.c | ||||
| CFLAGS += -DNOC_FILE_DIALOG_WIN32 | 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 | -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 \ | -L$(HOME)/pkg/portaudio-r1891-build/lib/x64/ReleaseMinDependency -lportaudio_x64 \ | ||||
| -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows | -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows | ||||
| TARGET = Rack.exe | TARGET = Rack.exe | ||||
| @@ -13,6 +13,7 @@ Install dependencies | |||||
| - [jansson](http://www.digip.org/jansson/) | - [jansson](http://www.digip.org/jansson/) | ||||
| - [portaudio](http://www.portaudio.com/) | - [portaudio](http://www.portaudio.com/) | ||||
| - [portmidi](http://portmedia.sourceforge.net/portmidi/) | - [portmidi](http://portmedia.sourceforge.net/portmidi/) | ||||
| - [libsamplerate](http://www.mega-nerd.com/SRC/) | |||||
| - GTK+-2.0 if Linux (for file open/save dialog) | - GTK+-2.0 if Linux (for file open/save dialog) | ||||
| Run `make ARCH=linux` or `make ARCH=windows` or `make ARCH=apple` | Run `make ARCH=linux` or `make ARCH=windows` or `make ARCH=apple` | ||||
| @@ -57,6 +57,7 @@ extern Vec gMousePos; | |||||
| extern Widget *gHoveredWidget; | extern Widget *gHoveredWidget; | ||||
| extern Widget *gDraggedWidget; | extern Widget *gDraggedWidget; | ||||
| extern Widget *gSelectedWidget; | extern Widget *gSelectedWidget; | ||||
| extern int gGuiFrame; | |||||
| void guiInit(); | void guiInit(); | ||||
| void guiDestroy(); | void guiDestroy(); | ||||
| @@ -82,10 +83,13 @@ struct Wire; | |||||
| struct Module { | struct Module { | ||||
| std::vector<float> params; | 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*> inputs; | ||||
| std::vector<float*> outputs; | std::vector<float*> outputs; | ||||
| /** For CPU usage */ | |||||
| float cpuTime = 0.0; | |||||
| virtual ~Module() {} | 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 | /** 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) { | inline float clampf(float x, float min, float max) { | ||||
| if (x > max) | if (x > max) | ||||
| @@ -22,6 +22,13 @@ inline float clampf(float x, float min, float max) { | |||||
| return x; | 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) { | inline float mapf(float x, float xMin, float xMax, float yMin, float yMax) { | ||||
| return yMin + (x - xMin) / (xMax - xMin) * (yMax - yMin); | return yMin + (x - xMin) / (xMax - xMin) * (yMax - yMin); | ||||
| } | } | ||||
| @@ -43,12 +50,22 @@ inline float quadraticBipolar(float x) { | |||||
| return x >= 0.0 ? x2 : -x2; | return x >= 0.0 ? x2 : -x2; | ||||
| } | } | ||||
| inline float cubic(float x) { | |||||
| // optimal with --fast-math | |||||
| return x*x*x; | |||||
| } | |||||
| inline float quarticBipolar(float x) { | inline float quarticBipolar(float x) { | ||||
| float x2 = x*x; | float x2 = x*x; | ||||
| float x4 = x2*x2; | float x4 = x2*x2; | ||||
| return x >= 0.0 ? x4 : -x4; | 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 | // Euclidean modulus, always returns 0 <= mod < base for positive base | ||||
| // Assumes this architecture's division is non-Euclidean | // Assumes this architecture's division is non-Euclidean | ||||
| inline int eucMod(int a, int base) { | 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` | /** Linearly interpolate an array `p` with index `x` | ||||
| Assumes that the array at `p` is of length at least ceil(x)+1. | 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 | // 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 | // 2D float vector | ||||
| @@ -79,12 +79,12 @@ struct Widget { | |||||
| /** Called when a widget responds to `onMouseDown` for a left button press */ | /** Called when a widget responds to `onMouseDown` for a left button press */ | ||||
| virtual void onDragStart() {} | 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 */ | /** Called when a widget responds to `onMouseMove` and is being dragged */ | ||||
| virtual void onDragMove(Vec mouseRel) {} | virtual void onDragMove(Vec mouseRel) {} | ||||
| /** Called when a widget responds to `onMouseUp` for a left button release and a widget is being dragged */ | /** Called when a widget responds to `onMouseUp` for a left button release and a widget is being dragged */ | ||||
| virtual void onDragDrop(Widget *origin) {} | 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 onAction() {} | ||||
| virtual void onChange() {} | virtual void onChange() {} | ||||
| @@ -217,7 +217,9 @@ struct Button : OpaqueWidget { | |||||
| } | } | ||||
| void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
| void onMouseEnter(); | void onMouseEnter(); | ||||
| void onMouseLeave() ; | |||||
| void onMouseLeave(); | |||||
| void onDragStart(); | |||||
| void onDragEnd(); | |||||
| void onDragDrop(Widget *origin); | void onDragDrop(Widget *origin); | ||||
| }; | }; | ||||
| @@ -339,7 +341,6 @@ struct RackWidget : OpaqueWidget { | |||||
| json_t *toJson(); | json_t *toJson(); | ||||
| void fromJson(json_t *root); | void fromJson(json_t *root); | ||||
| int frame = 0; | |||||
| void repositionModule(ModuleWidget *module); | void repositionModule(ModuleWidget *module); | ||||
| void step(); | void step(); | ||||
| void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
| @@ -27,6 +27,8 @@ Widget *gHoveredWidget = NULL; | |||||
| Widget *gDraggedWidget = NULL; | Widget *gDraggedWidget = NULL; | ||||
| Widget *gSelectedWidget = NULL; | Widget *gSelectedWidget = NULL; | ||||
| int gGuiFrame; | |||||
| static GLFWwindow *window = NULL; | static GLFWwindow *window = NULL; | ||||
| static NVGcontext *vg = NULL; | static NVGcontext *vg = NULL; | ||||
| @@ -225,8 +227,10 @@ void guiRun() { | |||||
| glfwGetWindowSize(window, &width, &height); | glfwGetWindowSize(window, &width, &height); | ||||
| windowSizeCallback(window, width, height); | windowSizeCallback(window, width, height); | ||||
| } | } | ||||
| gGuiFrame = 0; | |||||
| double lastTime = 0.0; | double lastTime = 0.0; | ||||
| while(!glfwWindowShouldClose(window)) { | while(!glfwWindowShouldClose(window)) { | ||||
| gGuiFrame++; | |||||
| glfwPollEvents(); | glfwPollEvents(); | ||||
| { | { | ||||
| double xpos, ypos; | double xpos, ypos; | ||||
| @@ -94,6 +94,7 @@ void pluginInit() { | |||||
| void pluginDestroy() { | void pluginDestroy() { | ||||
| for (Plugin *plugin : gPlugins) { | for (Plugin *plugin : gPlugins) { | ||||
| // TODO unload plugin with `dlclose` or `FreeLibrary` | |||||
| delete plugin; | delete plugin; | ||||
| } | } | ||||
| gPlugins.clear(); | gPlugins.clear(); | ||||
| @@ -73,8 +73,18 @@ void rackStep() { | |||||
| } | } | ||||
| } | } | ||||
| // Step all modules | // Step all modules | ||||
| std::chrono::time_point<std::chrono::high_resolution_clock> start, end; | |||||
| for (Module *module : modules) { | for (Module *module : modules) { | ||||
| // Start clock for CPU usage | |||||
| start = std::chrono::high_resolution_clock::now(); | |||||
| // Step module by one frame | |||||
| module->step(); | 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 { | namespace rack { | ||||
| // TODO | |||||
| // Convert this to xoroshiro128+ and custom normal dist implementation | |||||
| static std::random_device rd; | 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 | } // namespace rack | ||||
| @@ -15,6 +15,14 @@ void Button::onMouseLeave() { | |||||
| state = BND_DEFAULT; | state = BND_DEFAULT; | ||||
| } | } | ||||
| void Button::onDragStart() { | |||||
| state = BND_ACTIVE; | |||||
| } | |||||
| void Button::onDragEnd() { | |||||
| state = BND_HOVER; | |||||
| } | |||||
| void Button::onDragDrop(Widget *origin) { | void Button::onDragDrop(Widget *origin) { | ||||
| if (origin == this) { | if (origin == this) { | ||||
| onAction(); | onAction(); | ||||
| @@ -8,7 +8,7 @@ void ModulePanel::draw(NVGcontext *vg) { | |||||
| nvgRect(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); | nvgRect(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); | ||||
| NVGpaint paint; | NVGpaint paint; | ||||
| // Background gradient | // Background gradient | ||||
| Vec c = box.getTopRight(); | |||||
| Vec c = box.pos; | |||||
| float length = box.size.norm(); | float length = box.size.norm(); | ||||
| paint = nvgRadialGradient(vg, c.x, c.y, 0.0, length, highlightColor, backgroundColor); | paint = nvgRadialGradient(vg, c.x, c.y, 0.0, length, highlightColor, backgroundColor); | ||||
| nvgFillPaint(vg, paint); | nvgFillPaint(vg, paint); | ||||
| @@ -98,6 +98,15 @@ void ModuleWidget::cloneParams(ModuleWidget *source) { | |||||
| void ModuleWidget::draw(NVGcontext *vg) { | void ModuleWidget::draw(NVGcontext *vg) { | ||||
| Widget::draw(vg); | Widget::draw(vg); | ||||
| bndBevel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); | 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() { | void ModuleWidget::onDragStart() { | ||||
| @@ -8,9 +8,8 @@ Port::Port() { | |||||
| spriteOffset = Vec(-18, -18); | spriteOffset = Vec(-18, -18); | ||||
| spriteSize = Vec(56, 56); | spriteSize = Vec(56, 56); | ||||
| spriteFilename = "res/port.png"; | spriteFilename = "res/port.png"; | ||||
| std::uniform_int_distribution<> dist(0, 4); | |||||
| index = dist(rng); | |||||
| index = randomu32() % 5; | |||||
| } | } | ||||
| Port::~Port() { | Port::~Port() { | ||||
| @@ -248,8 +248,7 @@ void RackWidget::step() { | |||||
| // Autosave every 15 seconds | // Autosave every 15 seconds | ||||
| // (This is alpha software, expect crashes!) | // (This is alpha software, expect crashes!) | ||||
| if (++frame >= 60*15) { | |||||
| frame = 0; | |||||
| if (gGuiFrame % (60*15) == 0) { | |||||
| savePatch("autosave.json"); | savePatch("autosave.json"); | ||||
| } | } | ||||
| @@ -9,8 +9,7 @@ Screw::Screw() { | |||||
| spriteSize = Vec(29, 29); | spriteSize = Vec(29, 29); | ||||
| spriteFilename = "res/screw.png"; | spriteFilename = "res/screw.png"; | ||||
| std::uniform_int_distribution<> dist(0, 4); | |||||
| index = dist(rng); | |||||
| index = randomu32() % 5; | |||||
| } | } | ||||