diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index b4b89030..22ae806a 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,7 +2,7 @@ Search before opening an issue to make sure your topic is not a duplicate. Delet ## For bug reports: -Operating system, or "all" if known to exist on all three: +Operating system(s): Version if official Rack release, commit hash and/or branch if from source: ## For feature requests: diff --git a/Makefile b/Makefile index 6fe5b4d4..694a04f5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ VERSION = 0.6.0dev +RACK_DIR = . FLAGS += \ -Iinclude \ diff --git a/compile.mk b/compile.mk index 307c6fe7..45e126ad 100644 --- a/compile.mk +++ b/compile.mk @@ -2,8 +2,11 @@ ifdef VERSION FLAGS += -DVERSION=$(VERSION) endif +RACK_DIR ?= . +include $(RACK_DIR)/arch.mk + # Generate dependency files alongside the object files -FLAGS += -MMD +FLAGS += -MMD -MP FLAGS += -g # Optimization FLAGS += -O3 -march=nocona -ffast-math -fno-finite-math-only @@ -17,7 +20,6 @@ CXXFLAGS += -std=c++11 ifeq ($(ARCH), lin) FLAGS += -DARCH_LIN endif - ifeq ($(ARCH), mac) FLAGS += -DARCH_MAC CXXFLAGS += -stdlib=libc++ @@ -26,7 +28,6 @@ ifeq ($(ARCH), mac) FLAGS += $(MAC_SDK_FLAGS) LDFLAGS += $(MAC_SDK_FLAGS) endif - ifeq ($(ARCH), win) FLAGS += -DARCH_WIN FLAGS += -D_USE_MATH_DEFINES diff --git a/dep/Makefile b/dep/Makefile index 18d25e1d..5fe02eab 100755 --- a/dep/Makefile +++ b/dep/Makefile @@ -112,7 +112,7 @@ $(openssl): $(UNTAR) openssl-1.1.0g.tar.gz cd openssl-1.1.0g && ./config --prefix="$(LOCAL)" $(MAKE) -C openssl-1.1.0g - $(MAKE) -C openssl-1.1.0g install + $(MAKE) -C openssl-1.1.0g install_sw $(libcurl): $(openssl) $(WGET) https://github.com/curl/curl/releases/download/curl-7_56_0/curl-7.56.0.tar.gz @@ -145,7 +145,7 @@ ifeq ($(ARCH),win) RTAUDIO_FLAGS += -DAUDIO_WINDOWS_DS=ON -DAUDIO_WINDOWS_WASAPI=ON -DAUDIO_WINDOWS_ASIO=ON endif ifeq ($(ARCH),lin) -RTAUDIO_FLAGS += -DAUDIO_LINUX_ALSA=ON -DAUDIO_LINUX_PULSE=ON +RTAUDIO_FLAGS += -DAUDIO_LINUX_ALSA=ON endif ifdef RTAUDIO_ALL_APIS @@ -153,7 +153,7 @@ ifeq ($(ARCH),mac) RTAUDIO_FLAGS += -DAUDIO_UNIX_JACK=ON endif ifeq ($(ARCH),lin) -RTAUDIO_FLAGS += -DAUDIO_LINUX_JACK=ON +RTAUDIO_FLAGS += -DAUDIO_LINUX_PULSE=ON -DAUDIO_UNIX_JACK=ON endif endif diff --git a/include/app.hpp b/include/app.hpp index 4e136f8b..3fae0af5 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -158,6 +158,8 @@ struct RackWidget : OpaqueWidget { void openDialog(); void saveDialog(); void saveAsDialog(); + /** If `lastPath` is defined, ask the user to reload it */ + void revert(); void savePatch(std::string filename); void loadPatch(std::string filename); json_t *toJson(); @@ -171,6 +173,7 @@ struct RackWidget : OpaqueWidget { bool requestModuleBox(ModuleWidget *m, Rect box); /** Moves a module to the closest non-colliding position */ bool requestModuleBoxNearest(ModuleWidget *m, Rect box); + void step() override; void draw(NVGcontext *vg) override; @@ -183,13 +186,6 @@ struct RackRail : TransparentWidget { void draw(NVGcontext *vg) override; }; -struct AddModuleWindow : Window { - Vec modulePos; - - AddModuleWindow(); - void step() override; -}; - struct Panel : TransparentWidget { NVGcolor backgroundColor; std::shared_ptr backgroundImage; @@ -326,7 +322,7 @@ struct MomentarySwitch : virtual Switch { // IO widgets //////////////////// -struct LedDisplay : Widget { +struct LedDisplay : VirtualWidget { void draw(NVGcontext *vg) override; }; @@ -488,7 +484,7 @@ struct Toolbar : OpaqueWidget { void draw(NVGcontext *vg) override; }; -struct PluginManagerWidget : Widget { +struct PluginManagerWidget : VirtualWidget { Widget *loginWidget; Widget *manageWidget; Widget *downloadWidget; @@ -524,8 +520,12 @@ extern RackScene *gRackScene; extern RackWidget *gRackWidget; extern Toolbar *gToolbar; -void sceneInit(); -void sceneDestroy(); +void appInit(); +void appDestroy(); +void appModuleBrowserCreate(); +json_t *appModuleBrowserToJson(); +void appModuleBrowserFromJson(json_t *rootJ); + json_t *colorToJson(NVGcolor color); NVGcolor jsonToColor(json_t *colorJ); diff --git a/include/dsp/ringbuffer.hpp b/include/dsp/ringbuffer.hpp index 439c053d..73506e91 100644 --- a/include/dsp/ringbuffer.hpp +++ b/include/dsp/ringbuffer.hpp @@ -8,56 +8,76 @@ namespace rack { /** A simple cyclic buffer. S must be a power of 2. -push() is constant time O(1) +Thread-safe for single producers and consumers. */ -template +template struct RingBuffer { T data[S]; - int start = 0; - int end = 0; + size_t start = 0; + size_t end = 0; - int mask(int i) const { - return i & (S - 1); + size_t mask(size_t i) const { + return i & (S - 1); } void push(T t) { - int i = mask(end++); + size_t i = mask(end++); data[i] = t; } + void pushBuffer(const T *t, int n) { + size_t i = mask(end); + size_t e1 = i + n; + size_t e2 = (e1 < S) ? e1 : S; + memcpy(&data[i], t, sizeof(T) * (e2 - i)); + if (e1 > S) { + memcpy(data, &t[S - i], sizeof(T) * (e1 - S)); + } + end += n; + } T shift() { return data[mask(start++)]; } + void shiftBuffer(T *t, size_t n) { + size_t i = mask(start); + size_t s1 = i + n; + size_t s2 = (s1 < S) ? s1 : S; + memcpy(t, &data[i], sizeof(T) * (s2 - i)); + if (s1 > S) { + memcpy(&t[S - i], data, sizeof(T) * (s1 - S)); + } + start += n; + } void clear() { start = end; } bool empty() const { - return start >= end; + return start == end; } bool full() const { - return end - start >= S; + return end - start == S; } - int size() const { + size_t size() const { return end - start; } - int capacity() const { + size_t capacity() const { return S - size(); } }; /** A cyclic buffer which maintains a valid linear array of size S by keeping a copy of the buffer in adjacent memory. S must be a power of 2. -push() is constant time O(2) relative to RingBuffer +Thread-safe for single producers and consumers? */ -template +template struct DoubleRingBuffer { T data[S*2]; - int start = 0; - int end = 0; + size_t start = 0; + size_t end = 0; - int mask(int i) const { + size_t mask(size_t i) const { return i & (S - 1); } void push(T t) { - int i = mask(end++); + size_t i = mask(end++); data[i] = t; data[i + S] = t; } @@ -68,15 +88,15 @@ struct DoubleRingBuffer { start = end; } bool empty() const { - return start >= end; + return start == end; } bool full() const { - return end - start >= S; + return end - start == S; } - int size() const { + size_t size() const { return end - start; } - int capacity() const { + size_t capacity() const { return S - size(); } /** Returns a pointer to S consecutive elements for appending. @@ -86,16 +106,16 @@ struct DoubleRingBuffer { T *endData() { return &data[mask(end)]; } - void endIncr(int n) { - int e = mask(end); - int e1 = e + n; - int e2 = min(e1, S); + void endIncr(size_t n) { + size_t e = mask(end); + size_t e1 = e + n; + size_t e2 = (e1 < S) ? e1 : S; // Copy data forward - memcpy(data + S + e, data + e, sizeof(T) * (e2 - e)); + memcpy(&data[S + e], &data[e], sizeof(T) * (e2 - e)); if (e1 > S) { // Copy data backward from the doubled block to the main block - memcpy(data, data + S, sizeof(T) * (e1 - S)); + memcpy(data, &data[S], sizeof(T) * (e1 - S)); } end += n; } @@ -105,7 +125,7 @@ struct DoubleRingBuffer { const T *startData() const { return &data[mask(start)]; } - void startIncr(int n) { + void startIncr(size_t n) { start += n; } }; @@ -114,17 +134,18 @@ struct DoubleRingBuffer { The linear array of S elements are moved back to the start of the block once it outgrows past the end. This happens every N - S pushes, so the push() time is O(1 + S / (N - S)). For example, a float buffer of size 64 in a block of size 1024 is nearly as efficient as RingBuffer. +Not thread-safe. */ -template +template struct AppleRingBuffer { T data[N]; - int start = 0; - int end = 0; + size_t start = 0; + size_t end = 0; void returnBuffer() { // move end block to beginning - // may overlap, but that's okay - int s = size(); + // may overlap, but memmove handles that correctly + size_t s = size(); memmove(data, &data[start], sizeof(T) * s); start = 0; end = s; @@ -139,20 +160,20 @@ struct AppleRingBuffer { return data[start++]; } bool empty() const { - return start >= end; + return start == end; } bool full() const { - return end - start >= S; + return end - start == S; } - int size() const { + size_t size() const { return end - start; } - int capacity() const { + size_t capacity() const { return S - size(); } /** Returns a pointer to S consecutive elements for appending, requesting to append n elements. */ - T *endData(int n) { + T *endData(size_t n) { if (end + n > N) { returnBuffer(); } @@ -161,7 +182,7 @@ struct AppleRingBuffer { /** Actually increments the end position Must be called after endData(), and `n` must be at most the `n` passed to endData() */ - void endIncr(int n) { + void endIncr(size_t n) { end += n; } /** Returns a pointer to S consecutive elements for consumption @@ -170,7 +191,7 @@ struct AppleRingBuffer { const T *startData() const { return &data[start]; } - void startIncr(int n) { + void startIncr(size_t n) { // This is valid as long as n < S start += n; } diff --git a/include/engine.hpp b/include/engine.hpp index 1c3ebcd6..5806c271 100644 --- a/include/engine.hpp +++ b/include/engine.hpp @@ -18,7 +18,10 @@ struct Light { void setBrightness(float brightness) { value = (brightness > 0.f) ? brightness * brightness : 0.f; } - void setBrightnessSmooth(float brightness); + /** Emulates slow fall (but immediate rise) of LED brightness. + `frames` rescales the timestep. For example, if your module calls this method every 16 frames, use 16.0. + */ + void setBrightnessSmooth(float brightness, float frames = 1.f); }; struct Input { diff --git a/include/plugin.hpp b/include/plugin.hpp index 54be270c..3b8a6f0e 100644 --- a/include/plugin.hpp +++ b/include/plugin.hpp @@ -105,6 +105,8 @@ bool pluginIsDownloading(); float pluginGetDownloadProgress(); std::string pluginGetDownloadName(); std::string pluginGetLoginStatus(); +Plugin *pluginGetPlugin(std::string pluginSlug); +Model *pluginGetModel(std::string pluginSlug, std::string modelSlug); extern std::list gPlugins; diff --git a/include/tags.hpp b/include/tags.hpp index 97f33052..e8564f5e 100644 --- a/include/tags.hpp +++ b/include/tags.hpp @@ -9,12 +9,14 @@ namespace rack { To see comments, turn word wrap on. I'm using inline comments so I can automatically sort the list when more tags are added. */ enum ModelTag { + NO_TAG, // Don't use this in `Model::create(...)`. Instead, just omit the tags entirely. AMPLIFIER_TAG, + ARPEGGIATOR_TAG, ATTENUATOR_TAG, BLANK_TAG, CHORUS_TAG, - CLOCK_TAG, CLOCK_MODULATOR_TAG, // Clock dividers, multipliers, etc. + CLOCK_TAG, COMPRESSOR_TAG, CONTROLLER_TAG, // Use only if the artist "performs" with this module. Knobs are not sufficient. Examples: on-screen keyboard, XY pad. DELAY_TAG, diff --git a/include/ui.hpp b/include/ui.hpp index 482fbcf7..7cfc8773 100644 --- a/include/ui.hpp +++ b/include/ui.hpp @@ -5,15 +5,51 @@ namespace rack { +//////////////////// +// Layouts (layouts.cpp) +//////////////////// + +/** Positions children in a row/column based on their widths/heights */ +struct SequentialLayout : virtual Widget { + enum Orientation { + HORIZONTAL_ORIENTATION, + VERTICAL_ORIENTATION, + }; + Orientation orientation = HORIZONTAL_ORIENTATION; + enum Alignment { + LEFT_ALIGNMENT, + CENTER_ALIGNMENT, + RIGHT_ALIGNMENT, + }; + Alignment alignment = LEFT_ALIGNMENT; + /** Space between adjacent elements */ + float spacing = 0.0; + void step() override; +}; + +//////////////////// +// Blendish UI elements +//////////////////// -struct Label : Widget { +struct Label : VirtualWidget { std::string text; + enum Alignment { + LEFT_ALIGNMENT, + CENTER_ALIGNMENT, + RIGHT_ALIGNMENT, + }; + Alignment alignment = LEFT_ALIGNMENT; Label() { box.size.y = BND_WIDGET_HEIGHT; } void draw(NVGcontext *vg) override; }; +struct List : OpaqueWidget { + void step() override; + void draw(NVGcontext *vg) override; +}; + /** Deletes itself from parent when clicked */ struct MenuOverlay : OpaqueWidget { void step() override; @@ -33,7 +69,7 @@ struct Menu : OpaqueWidget { box.size = Vec(0, 0); } ~Menu(); - // Resizes menu and calls addChild() + /** Deprecated. Just use addChild(child) instead */ void pushChild(Widget *child) DEPRECATED { addChild(child); } @@ -47,7 +83,6 @@ struct MenuEntry : OpaqueWidget { MenuEntry() { box.size = Vec(0, BND_WIDGET_HEIGHT); } - template static T *create() { T *o = Widget::create(Vec()); @@ -89,7 +124,7 @@ struct MenuItem : MenuEntry { struct WindowOverlay : OpaqueWidget { }; -struct Window : OpaqueWidget { +struct WindowWidget : OpaqueWidget { std::string title; void draw(NVGcontext *vg) override; void onDragMove(EventDragMove &e) override; @@ -139,22 +174,7 @@ struct Slider : OpaqueWidget, QuantityWidget { void onMouseDown(EventMouseDown &e) override; }; -/** Parent must be a ScrollWidget */ -struct ScrollBar : OpaqueWidget { - enum { VERTICAL, HORIZONTAL } orientation; - BNDwidgetState state = BND_DEFAULT; - float offset = 0.0; - float size = 0.0; - - ScrollBar() { - box.size = Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); - } - void draw(NVGcontext *vg) override; - void onDragStart(EventDragStart &e) override; - void onDragMove(EventDragMove &e) override; - void onDragEnd(EventDragEnd &e) override; -}; - +struct ScrollBar; /** Handles a container with ScrollBar */ struct ScrollWidget : OpaqueWidget { Widget *container; @@ -163,19 +183,24 @@ struct ScrollWidget : OpaqueWidget { Vec offset; ScrollWidget(); + void scrollTo(Rect r); void draw(NVGcontext *vg) override; void step() override; void onMouseMove(EventMouseMove &e) override; void onScroll(EventScroll &e) override; + void onHoverKey(EventHoverKey &e) override; }; struct TextField : OpaqueWidget { std::string text; std::string placeholder; bool multiline = false; - int begin = 0; - int end = 0; - int dragPos = 0; + /** The index of the text cursor */ + int cursor = 0; + /** The index of the other end of the selection. + If nothing is selected, this is equal to `cursor`. + */ + int selection = 0; TextField() { box.size.y = BND_WIDGET_HEIGHT; @@ -186,7 +211,10 @@ struct TextField : OpaqueWidget { void onFocus(EventFocus &e) override; void onText(EventText &e) override; void onKey(EventKey &e) override; - void insertText(std::string newText); + /** Inserts text at the cursor, replacing the selection if necessary */ + void insertText(std::string text); + /** Replaces the entire text */ + void setText(std::string text); virtual int getTextPosition(Vec mousePos); virtual void onTextChange() {} }; @@ -202,7 +230,7 @@ struct ProgressBar : QuantityWidget { void draw(NVGcontext *vg) override; }; -struct Tooltip : Widget { +struct Tooltip : VirtualWidget { void step() override; void draw(NVGcontext *vg) override; }; diff --git a/include/util/math.hpp b/include/util/math.hpp index a57ec3eb..9eb2e441 100644 --- a/include/util/math.hpp +++ b/include/util/math.hpp @@ -2,6 +2,7 @@ #include "util/common.hpp" #include // for global namespace functions #include // for std::isfinite, etc +#include // for std::abs, etc // Use a few standard math functions without std:: @@ -14,7 +15,7 @@ using std::isnormal; namespace rack { //////////////////// -// basic integer functions (suffixed with "i") +// basic integer functions //////////////////// inline int min(int a, int b) { @@ -28,8 +29,8 @@ inline int max(int a, int b) { /** Limits a value between a minimum and maximum Assumes min <= max */ -inline int clamp(int x, int minimum, int maximum) { - return min(max(x, minimum), maximum); +inline int clamp(int x, int min, int max) { + return rack::min(rack::max(x, min), max); } /** Euclidean modulus, always returns 0 <= mod < base for positive base. @@ -54,7 +55,7 @@ inline bool ispow2(int n) { } //////////////////// -// basic float functions (suffixed with "f") +// basic float functions //////////////////// /** Returns 1.f for positive numbers and -1.f for negative numbers (including positive/negative zero) */ @@ -74,11 +75,11 @@ inline bool isNear(float a, float b, float epsilon = 1.0e-6f) { /** Limits a value between a minimum and maximum Assumes min <= max */ -inline float clamp(float x, float minimum, float maximum) { - return fminf(fmaxf(x, minimum), maximum); +inline float clamp(float x, float min, float max) { + return fminf(fmaxf(x, min), max); } -/** Limits a value between a minimum and maximum +/** Limits a value between a min and max If min > max, switches the two values */ inline float clamp2(float x, float min, float max) { @@ -180,6 +181,7 @@ struct Vec { return isfinite(x) && isfinite(y); } Vec clamp(Rect bound); + Vec clamp2(Rect bound); }; @@ -260,8 +262,14 @@ struct Rect { inline Vec Vec::clamp(Rect bound) { return Vec( - clamp2(x, bound.pos.x, bound.pos.x + bound.size.x), - clamp2(y, bound.pos.y, bound.pos.y + bound.size.y)); + rack::clamp(x, bound.pos.x, bound.pos.x + bound.size.x), + rack::clamp(y, bound.pos.y, bound.pos.y + bound.size.y)); +} + +inline Vec Vec::clamp2(Rect bound) { + return Vec( + rack::clamp2(x, bound.pos.x, bound.pos.x + bound.size.x), + rack::clamp2(y, bound.pos.y, bound.pos.y + bound.size.y)); } diff --git a/include/widgets.hpp b/include/widgets.hpp index 98757813..0cc73451 100644 --- a/include/widgets.hpp +++ b/include/widgets.hpp @@ -44,7 +44,9 @@ struct SVG { // Base widget //////////////////// -/** A node in the 2D scene graph */ +/** A node in the 2D scene graph +Never inherit from Widget directly. Instead, inherit from VirtualWidget declared below. +*/ struct Widget { /** Stores position and size */ Rect box = Rect(Vec(), Vec(INFINITY, INFINITY)); @@ -152,7 +154,11 @@ struct Widget { } }; -struct TransformWidget : Widget { +/** Instead of inheriting from Widget directly, inherit from VirtualWidget to guarantee that only one copy of Widget's member variables are used by each instance of the Widget hierarchy. +*/ +struct VirtualWidget : virtual Widget {}; + +struct TransformWidget : VirtualWidget { /** The transformation matrix */ float transform[6]; TransformWidget(); @@ -164,7 +170,7 @@ struct TransformWidget : Widget { void draw(NVGcontext *vg) override; }; -struct ZoomWidget : Widget { +struct ZoomWidget : VirtualWidget { float zoom = 1.0; Vec getRelativeOffset(Vec v, Widget *relative) override; Rect getViewport(Rect r) override; @@ -183,7 +189,7 @@ struct ZoomWidget : Widget { //////////////////// /** Widget that does not respond to events */ -struct TransparentWidget : virtual Widget { +struct TransparentWidget : VirtualWidget { void onMouseDown(EventMouseDown &e) override {} void onMouseUp(EventMouseUp &e) override {} void onMouseMove(EventMouseMove &e) override {} @@ -191,7 +197,7 @@ struct TransparentWidget : virtual Widget { }; /** Widget that automatically responds to all mouse events but gives a chance for children to respond instead */ -struct OpaqueWidget : virtual Widget { +struct OpaqueWidget : VirtualWidget { void onMouseDown(EventMouseDown &e) override { Widget::onMouseDown(e); if (!e.target) @@ -216,7 +222,7 @@ struct OpaqueWidget : virtual Widget { } }; -struct SpriteWidget : virtual Widget { +struct SpriteWidget : VirtualWidget { Vec spriteOffset; Vec spriteSize; std::shared_ptr spriteImage; @@ -224,7 +230,7 @@ struct SpriteWidget : virtual Widget { void draw(NVGcontext *vg) override; }; -struct SVGWidget : virtual Widget { +struct SVGWidget : VirtualWidget { std::shared_ptr svg; /** Sets the box size to the svg image size */ void wrap(); @@ -237,7 +243,7 @@ struct SVGWidget : virtual Widget { When `dirty` is true, its children will be re-rendered on the next call to step() override. Events are not passed to the underlying scene. */ -struct FramebufferWidget : virtual Widget { +struct FramebufferWidget : VirtualWidget { /** Set this to true to re-render the children to the framebuffer the next time it is drawn */ bool dirty = true; /** A margin in pixels around the children in the framebuffer @@ -257,7 +263,8 @@ struct FramebufferWidget : virtual Widget { void onZoom(EventZoom &e) override; }; -struct QuantityWidget : virtual Widget { +/** A Widget representing a float value */ +struct QuantityWidget : VirtualWidget { float value = 0.0; float minValue = 0.0; float maxValue = 1.0; diff --git a/src/core/AudioInterface.cpp b/src/Core/AudioInterface.cpp similarity index 98% rename from src/core/AudioInterface.cpp rename to src/Core/AudioInterface.cpp index a9f2c39d..2359a428 100644 --- a/src/core/AudioInterface.cpp +++ b/src/Core/AudioInterface.cpp @@ -4,7 +4,7 @@ #include #include #include -#include "core.hpp" +#include "Core.hpp" #include "audio.hpp" #include "dsp/samplerate.hpp" #include "dsp/ringbuffer.hpp" @@ -58,7 +58,7 @@ struct AudioInterfaceIO : AudioIO { if (numOutputs > 0) { std::unique_lock lock(audioMutex); auto cond = [&] { - return outputBuffer.size() >= length; + return (outputBuffer.size() >= (size_t) length); }; if (audioCv.wait_for(lock, audioTimeout, cond)) { // Consume audio block @@ -187,7 +187,7 @@ void AudioInterface::step() { // Wait until outputs are needed std::unique_lock lock(audioIO.engineMutex); auto cond = [&] { - return audioIO.outputBuffer.size() < audioIO.blockSize; + return (audioIO.outputBuffer.size() < (size_t) audioIO.blockSize); }; if (audioIO.engineCv.wait_for(lock, audioTimeout, cond)) { // Push converted output diff --git a/src/core/Blank.cpp b/src/Core/Blank.cpp similarity index 99% rename from src/core/Blank.cpp rename to src/Core/Blank.cpp index 2069accd..13fa5d08 100644 --- a/src/core/Blank.cpp +++ b/src/Core/Blank.cpp @@ -1,4 +1,4 @@ -#include "core.hpp" +#include "Core.hpp" using namespace rack; diff --git a/src/core/core.cpp b/src/Core/Core.cpp similarity index 94% rename from src/core/core.cpp rename to src/Core/Core.cpp index 178ddef1..89ead4ec 100644 --- a/src/core/core.cpp +++ b/src/Core/Core.cpp @@ -1,4 +1,4 @@ -#include "core.hpp" +#include "Core.hpp" void init(rack::Plugin *p) { diff --git a/src/core/core.hpp b/src/Core/Core.hpp similarity index 100% rename from src/core/core.hpp rename to src/Core/Core.hpp diff --git a/src/core/MIDICCToCVInterface.cpp b/src/Core/MIDICCToCVInterface.cpp similarity index 99% rename from src/core/MIDICCToCVInterface.cpp rename to src/Core/MIDICCToCVInterface.cpp index 8b280cdb..e01b7bd2 100644 --- a/src/core/MIDICCToCVInterface.cpp +++ b/src/Core/MIDICCToCVInterface.cpp @@ -1,4 +1,4 @@ -#include "core.hpp" +#include "Core.hpp" #include "midi.hpp" #include "dsp/filter.hpp" diff --git a/src/core/MIDIToCVInterface.cpp b/src/Core/MIDIToCVInterface.cpp similarity index 99% rename from src/core/MIDIToCVInterface.cpp rename to src/Core/MIDIToCVInterface.cpp index 0df5eec0..cbd5405e 100644 --- a/src/core/MIDIToCVInterface.cpp +++ b/src/Core/MIDIToCVInterface.cpp @@ -1,4 +1,4 @@ -#include "core.hpp" +#include "Core.hpp" #include "midi.hpp" #include "dsp/filter.hpp" diff --git a/src/core/MIDITriggerToCVInterface.cpp b/src/Core/MIDITriggerToCVInterface.cpp similarity index 99% rename from src/core/MIDITriggerToCVInterface.cpp rename to src/Core/MIDITriggerToCVInterface.cpp index de4bb272..54eef226 100644 --- a/src/core/MIDITriggerToCVInterface.cpp +++ b/src/Core/MIDITriggerToCVInterface.cpp @@ -1,4 +1,4 @@ -#include "core.hpp" +#include "Core.hpp" #include "midi.hpp" #include "dsp/filter.hpp" diff --git a/src/core/MidiClockToCV.cpp b/src/Core/MidiClockToCV.cpp similarity index 100% rename from src/core/MidiClockToCV.cpp rename to src/Core/MidiClockToCV.cpp diff --git a/src/core/MidiIO.cpp b/src/Core/MidiIO.cpp similarity index 100% rename from src/core/MidiIO.cpp rename to src/Core/MidiIO.cpp diff --git a/src/core/MidiIO.hpp b/src/Core/MidiIO.hpp similarity index 100% rename from src/core/MidiIO.hpp rename to src/Core/MidiIO.hpp diff --git a/src/core/Notes.cpp b/src/Core/Notes.cpp similarity index 98% rename from src/core/Notes.cpp rename to src/Core/Notes.cpp index a113f265..7dc1c08f 100644 --- a/src/core/Notes.cpp +++ b/src/Core/Notes.cpp @@ -1,4 +1,4 @@ -#include "core.hpp" +#include "Core.hpp" using namespace rack; diff --git a/src/core/QuadMIDIToCVInterface.cpp b/src/Core/QuadMIDIToCVInterface.cpp similarity index 99% rename from src/core/QuadMIDIToCVInterface.cpp rename to src/Core/QuadMIDIToCVInterface.cpp index 1466d2d3..c39d05af 100644 --- a/src/core/QuadMIDIToCVInterface.cpp +++ b/src/Core/QuadMIDIToCVInterface.cpp @@ -1,4 +1,4 @@ -#include "core.hpp" +#include "Core.hpp" #include "midi.hpp" diff --git a/src/app/AddModuleWindow.cpp b/src/app/AddModuleWindow.cpp deleted file mode 100644 index f80a4c73..00000000 --- a/src/app/AddModuleWindow.cpp +++ /dev/null @@ -1,292 +0,0 @@ -#include "app.hpp" -#include "plugin.hpp" -#include -#include -#include - - -namespace rack { - - -static std::string sManufacturer; -static Model *sModel = NULL; -static std::string sFilter; - - -struct ListMenu : OpaqueWidget { - void draw(NVGcontext *vg) override { - Widget::draw(vg); - } - - void step() override { - Widget::step(); - - box.size.y = 0; - for (Widget *child : children) { - if (!child->visible) - continue; - // Increase height, set position of child - child->box.pos = Vec(0, box.size.y); - box.size.y += child->box.size.y; - child->box.size.x = box.size.x; - } - } -}; - - -struct UrlItem : MenuItem { - std::string url; - void onAction(EventAction &e) override { - std::thread t(openBrowser, url); - t.detach(); - } -}; - - -struct MetadataMenu : ListMenu { - Model *model = NULL; - - void step() override { - if (model != sModel) { - model = sModel; - clearChildren(); - - if (model) { - // Tag list - if (!model->tags.empty()) { - for (ModelTag tag : model->tags) { - addChild(construct(&MenuLabel::text, gTagNames[tag])); - } - addChild(construct()); - } - - // Plugin name - std::string pluginName = model->plugin->slug; - if (!model->plugin->version.empty()) { - pluginName += " v"; - pluginName += model->plugin->version; - } - addChild(construct(&MenuLabel::text, pluginName)); - - // Plugin metadata - if (!model->plugin->website.empty()) { - addChild(construct(&MenuItem::text, "Website", &UrlItem::url, model->plugin->website)); - } - if (!model->plugin->manual.empty()) { - addChild(construct(&MenuItem::text, "Manual", &UrlItem::url, model->plugin->manual)); - } - if (!model->plugin->path.empty()) { - addChild(construct(&MenuItem::text, "Browse directory", &UrlItem::url, model->plugin->path)); - } - } - } - - ListMenu::step(); - } -}; - - -static bool isModelMatch(Model *model, std::string search) { - // Build content string - std::string str; - str += model->manufacturer; - str += " "; - str += model->name; - str += " "; - str += model->slug; - for (ModelTag tag : model->tags) { - str += " "; - str += gTagNames[tag]; - } - str = lowercase(str); - search = lowercase(search); - return (str.find(search) != std::string::npos); -} - - -struct ModelItem : MenuItem { - Model *model; - void onAction(EventAction &e) override { - ModuleWidget *moduleWidget = model->createModuleWidget(); - gRackWidget->moduleContainer->addChild(moduleWidget); - // Move module nearest to the mouse position - Rect box; - box.size = moduleWidget->box.size; - AddModuleWindow *w = getAncestorOfType(); - box.pos = w->modulePos.minus(box.getCenter()); - gRackWidget->requestModuleBoxNearest(moduleWidget, box); - } - void onMouseEnter(EventMouseEnter &e) override { - sModel = model; - MenuItem::onMouseEnter(e); - } -}; - - -struct ModelMenu : ListMenu { - std::string manufacturer; - std::string filter; - - void step() override { - if (manufacturer != sManufacturer) { - manufacturer = sManufacturer; - filter = ""; - clearChildren(); - addChild(construct(&MenuLabel::text, manufacturer)); - // Add models for the selected manufacturer - for (Plugin *plugin : gPlugins) { - for (Model *model : plugin->models) { - if (model->manufacturer == manufacturer) { - addChild(construct(&MenuItem::text, model->name, &ModelItem::model, model)); - } - } - } - } - - if (filter != sFilter) { - filter = sFilter; - // Make all children invisible - for (Widget *child : children) { - child->visible = false; - } - // Make children with a matching model visible - for (Widget *child : children) { - ModelItem *item = dynamic_cast(child); - if (!item) - continue; - - if (isModelMatch(item->model, filter)) { - item->visible = true; - } - } - } - - ListMenu::step(); - } -}; - - -struct ManufacturerItem : MenuItem { - Model *model; - void onAction(EventAction &e) override { - sManufacturer = text; - e.consumed = false; - } -}; - - -struct ManufacturerMenu : ListMenu { - std::string filter; - - ManufacturerMenu() { - addChild(construct(&MenuLabel::text, "Manufacturers")); - - // Collect manufacturer names - std::set manufacturers; - for (Plugin *plugin : gPlugins) { - for (Model *model : plugin->models) { - manufacturers.insert(model->manufacturer); - } - } - // Add menu item for each manufacturer name - for (std::string manufacturer : manufacturers) { - addChild(construct(&MenuItem::text, manufacturer)); - } - } - - void step() override { - if (filter != sFilter) { - // Make all children invisible - for (Widget *child : children) { - child->visible = false; - } - // Make children with a matching model visible - for (Widget *child : children) { - MenuItem *item = dynamic_cast(child); - if (!item) - continue; - - std::string manufacturer = item->text; - for (Plugin *plugin : gPlugins) { - for (Model *model : plugin->models) { - if (model->manufacturer == manufacturer) { - if (isModelMatch(model, sFilter)) { - item->visible = true; - } - } - } - } - } - filter = sFilter; - } - - ListMenu::step(); - } -}; - - -struct SearchModuleField : TextField { - void onTextChange() override { - sFilter = text; - } -}; - - -AddModuleWindow::AddModuleWindow() { - box.size = Vec(600, 300); - title = "Add module"; - - float posY = BND_NODE_TITLE_HEIGHT; - - // Search - SearchModuleField *searchField = new SearchModuleField(); - searchField->box.pos.y = posY; - posY += searchField->box.size.y; - searchField->box.size.x = box.size.x; - searchField->text = sFilter; - gFocusedWidget = searchField; - { - EventFocus eFocus; - searchField->onFocus(eFocus); - searchField->onTextChange(); - } - addChild(searchField); - - // Manufacturers - ManufacturerMenu *manufacturerMenu = new ManufacturerMenu(); - manufacturerMenu->box.size.x = 200; - - ScrollWidget *manufacturerScroll = new ScrollWidget(); - manufacturerScroll->container->addChild(manufacturerMenu); - manufacturerScroll->box.pos = Vec(0, posY); - manufacturerScroll->box.size = Vec(200, box.size.y - posY); - addChild(manufacturerScroll); - - // Models - ModelMenu *modelMenu = new ModelMenu(); - modelMenu->box.size.x = 200; - - ScrollWidget *modelScroll = new ScrollWidget(); - modelScroll->container->addChild(modelMenu); - modelScroll->box.pos = Vec(200, posY); - modelScroll->box.size = Vec(200, box.size.y - posY); - addChild(modelScroll); - - // Metadata - MetadataMenu *metadataMenu = new MetadataMenu(); - metadataMenu->box.size.x = 200; - - ScrollWidget *metadataScroll = new ScrollWidget(); - metadataScroll->container->addChild(metadataMenu); - metadataScroll->box.pos = Vec(400, posY); - metadataScroll->box.size = Vec(200, box.size.y - posY); - addChild(metadataScroll); -} - - -void AddModuleWindow::step() { - Widget::step(); -} - - -} // namespace rack diff --git a/src/app/LedDisplay.cpp b/src/app/LedDisplay.cpp index f3688389..2ffb8acd 100644 --- a/src/app/LedDisplay.cpp +++ b/src/app/LedDisplay.cpp @@ -84,10 +84,11 @@ void LedDisplayTextField::draw(NVGcontext *vg) { NVGcolor highlightColor = color; highlightColor.a = 0.5; - int cend = (this == gFocusedWidget) ? end : -1; + int begin = min(cursor, selection); + int end = (this == gFocusedWidget) ? max(cursor, selection) : -1; bndIconLabelCaret(vg, textOffset.x, textOffset.y, box.size.x - 2*textOffset.x, box.size.y - 2*textOffset.y, - -1, color, 12, text.c_str(), highlightColor, begin, cend); + -1, color, 12, text.c_str(), highlightColor, begin, end); bndSetFont(gGuiFont->handle); } diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp new file mode 100644 index 00000000..ae6db67a --- /dev/null +++ b/src/app/ModuleBrowser.cpp @@ -0,0 +1,559 @@ +#include "app.hpp" +#include "plugin.hpp" +#include "window.hpp" +#include +#include + + +#define BND_LABEL_FONT_SIZE 13 + + +namespace rack { + + +static std::set sFavoriteModels; + + +bool isMatch(std::string s, std::string search) { + s = lowercase(s); + search = lowercase(search); + return (s.find(search) != std::string::npos); +} + +static bool isModelMatch(Model *model, std::string search) { + if (search.empty()) + return true; + std::string s; + s += model->plugin->slug; + s += " "; + s += model->manufacturer; + s += " "; + s += model->name; + s += " "; + s += model->slug; + for (ModelTag tag : model->tags) { + s += " "; + s += gTagNames[tag]; + } + return isMatch(s, search); +} + + +struct FavoriteRadioButton : RadioButton { + Model *model = NULL; + + void onAction(EventAction &e) override; +}; + + +struct SeparatorItem : OpaqueWidget { + SeparatorItem() { + box.size.y = BND_WIDGET_HEIGHT; + } + + void setText(std::string text) { + clearChildren(); + Label *label = Widget::create