diff --git a/CHANGELOG.md b/CHANGELOG.md index eddb5de4..b4303b2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,20 @@ In this document, Mod is Ctrl on Windows/Linux and Cmd on Mac. -### 1.1.5 (in development) +### ??? (in development) + + +### 1.1.5 (2019-09-29) - Swap order of tags and brands in Module Browser. -- Disable smoothing for MIDI CC buttons in MIDI-Map. +- Add View > Frame rate menu bar item. +- Hide menu and scrollbars when fullscreen. +- Add key command (F3) for engine CPU meter. +- Add numpad key commands. - Automatically unzip update on Mac. +- Stop worker threads when engine is paused to save CPU. +- Core + - Disable smoothing for MIDI CC buttons in MIDI-Map. + - Fix sustain pedal release bug when using polyphonic mode in MIDI-CV. - API - Add libsamplerate library. diff --git a/Core.json b/Core.json index b09cd51f..0b97ed89 100644 --- a/Core.json +++ b/Core.json @@ -1,7 +1,7 @@ { "slug": "Core", "name": "Core", - "version": "1.1.4", + "version": "1.1.5", "license": "GPL-3.0-only", "author": "VCV", "brand": "VCV", diff --git a/Makefile b/Makefile index 6577d50a..02e0cfa5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ RACK_DIR ?= . VERSION := 1.dev.$(shell git rev-parse --short HEAD) -# VERSION := 1.1.4 +# VERSION := 1.1.5 FLAGS += -DVERSION=$(VERSION) FLAGS += -Iinclude -Idep/include diff --git a/dep/Makefile b/dep/Makefile index 501d1d0e..35fbcd32 100755 --- a/dep/Makefile +++ b/dep/Makefile @@ -106,32 +106,32 @@ $(jansson): jansson-2.12 $(MAKE) -C jansson-2.12 $(MAKE) -C jansson-2.12 install -openssl-1.1.1b: - $(WGET) "https://www.openssl.org/source/openssl-1.1.1b.tar.gz" - $(SHA256) openssl-1.1.1b.tar.gz 5c557b023230413dfb0756f3137a13e6d726838ccd1430888ad15bfb2b43ea4b - $(UNTAR) openssl-1.1.1b.tar.gz - rm openssl-1.1.1b.tar.gz +openssl-1.1.1d: + $(WGET) "https://www.openssl.org/source/openssl-1.1.1d.tar.gz" + $(SHA256) openssl-1.1.1d.tar.gz 1e3a91bc1f9dfce01af26026f856e064eab4c8ee0a8f457b5ae30b40b8b711f2 + $(UNTAR) openssl-1.1.1d.tar.gz + rm openssl-1.1.1d.tar.gz -$(openssl): openssl-1.1.1b +$(openssl): openssl-1.1.1d @# ./config ignores CFLAGS, so hack it in with CC - cd openssl-1.1.1b && CC="$(CC) $(CFLAGS)" ./config --prefix="$(DEP_PATH)" - $(MAKE) -C openssl-1.1.1b - $(MAKE) -C openssl-1.1.1b install_sw + cd openssl-1.1.1d && CC="$(CC) $(CFLAGS)" ./config --prefix="$(DEP_PATH)" + $(MAKE) -C openssl-1.1.1d + $(MAKE) -C openssl-1.1.1d install_sw -curl-7.64.1: - $(WGET) "https://curl.haxx.se/download/curl-7.64.1.tar.gz" - $(SHA256) curl-7.64.1.tar.gz 432d3f466644b9416bc5b649d344116a753aeaa520c8beaf024a90cba9d3d35d - $(UNTAR) curl-7.64.1.tar.gz - rm curl-7.64.1.tar.gz +curl-7.66.0: + $(WGET) "https://curl.haxx.se/download/curl-7.66.0.tar.gz" + $(SHA256) curl-7.66.0.tar.gz d0393da38ac74ffac67313072d7fe75b1fa1010eb5987f63f349b024a36b7ffb + $(UNTAR) curl-7.66.0.tar.gz + rm curl-7.66.0.tar.gz CURL_FLAGS += --disable-ftp --disable-file --disable-ldap --disable-ldaps --disable-rtsp --disable-proxy --disable-dict --disable-telnet --disable-tftp --disable-pop3 --disable-imap --disable-smb --disable-smtp --disable-gopher --disable-manual --disable-shared --disable-symbol-hiding CURL_FLAGS += --without-zlib --without-libpsl --without-libmetalink --without-libssh2 --without-librtmp --without-winidn --without-libidn2 --without-nghttp2 --without-brotli CURL_FLAGS += --with-ssl="$(DEP_PATH)" -$(libcurl): $(openssl) curl-7.64.1 - cd curl-7.64.1 && PKG_CONFIG_PATH= $(CONFIGURE) $(CURL_FLAGS) - $(MAKE) -C curl-7.64.1 - $(MAKE) -C curl-7.64.1 install +$(libcurl): $(openssl) curl-7.66.0 + cd curl-7.66.0 && PKG_CONFIG_PATH= $(CONFIGURE) $(CURL_FLAGS) + $(MAKE) -C curl-7.66.0 + $(MAKE) -C curl-7.66.0 install libzip-1.5.2: $(WGET) "https://libzip.org/download/libzip-1.5.2.tar.gz" @@ -246,7 +246,7 @@ $(pffft): jpommier-pffft-29e4f76ac53b # Helpers -src: glew-2.1.0 glfw jansson-2.12 speexdsp-SpeexDSP-1.2rc3 openssl-1.1.1b curl-7.64.1 libzip-1.5.2 zlib-1.2.11 rtmidi-4.0.0 rtaudio nanovg nanosvg oui-blendish osdialog jpommier-pffft-29e4f76ac53b +src: glew-2.1.0 glfw jansson-2.12 speexdsp-SpeexDSP-1.2rc3 openssl-1.1.1d curl-7.66.0 libzip-1.5.2 zlib-1.2.11 rtmidi-4.0.0 rtaudio nanovg nanosvg oui-blendish osdialog jpommier-pffft-29e4f76ac53b clean: git clean -fdx diff --git a/helper.py b/helper.py index c4e388bc..51fa42fa 100755 --- a/helper.py +++ b/helper.py @@ -169,6 +169,7 @@ def create_manifest(slug, plugin_dir="."): manifest['manualUrl'] = input_default("Manual website URL (optional)", manifest.get('manualUrl', "")) manifest['sourceUrl'] = input_default("Source code URL (optional)", manifest.get('sourceUrl', "")) manifest['donateUrl'] = input_default("Donate URL (optional)", manifest.get('donateUrl', "")) + manifest['changelogUrl'] = manifest.get('changelogUrl', "") if 'modules' not in manifest: manifest['modules'] = [] diff --git a/include/app.hpp b/include/app.hpp index 3304b3db..84fd5760 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -49,7 +49,7 @@ void appDestroy(); App* appGet(); /** Accesses the global App pointer */ -#define APP appGet() +#define APP rack::appGet() } // namespace rack diff --git a/include/asset.hpp b/include/asset.hpp index b1ba8760..9ccdb2bd 100644 --- a/include/asset.hpp +++ b/include/asset.hpp @@ -31,6 +31,8 @@ extern std::string pluginsPath; extern std::string settingsPath; extern std::string autosavePath; extern std::string templatePath; +// Only defined on Mac +extern std::string bundlePath; } // namespace asset diff --git a/include/componentlibrary.hpp b/include/componentlibrary.hpp index 6cb66dd3..888fa3fd 100644 --- a/include/componentlibrary.hpp +++ b/include/componentlibrary.hpp @@ -39,6 +39,174 @@ static const NVGcolor SCHEME_PURPLE = nvgRGB(0xd5, 0x2b, 0xed); static const NVGcolor SCHEME_LIGHT_GRAY = nvgRGB(0xe6, 0xe6, 0xe6); static const NVGcolor SCHEME_DARK_GRAY = nvgRGB(0x17, 0x17, 0x17); + +//////////////////// +// Lights +//////////////////// + +/* +Many of these classes use CRTP (https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). + +To use a red light with its default base class for example, use `RedLight` or `TRedLight<>`. (They are synonymous.) + +Use the `TBase` template argument if you want a different base class. +E.g. `RectangleLight` + +Although this paradigm might seem confusing at first, it ends up being extremely simple in your plugin code and perfect for "decorating" your classes with appearance traits and behavioral properties. +For example, need a slider with a green LED? Just use + + createLightParamCentered>(...) +*/ + +template +struct TGrayModuleLightWidget : TBase { + TGrayModuleLightWidget() { + this->bgColor = nvgRGB(0x5a, 0x5a, 0x5a); + this->borderColor = nvgRGBA(0, 0, 0, 0x60); + } +}; +typedef TGrayModuleLightWidget<> GrayModuleLightWidget; + +template +struct TRedLight : TBase { + TRedLight() { + this->addBaseColor(SCHEME_RED); + } +}; +typedef TRedLight<> RedLight; + +template +struct TGreenLight : TBase { + TGreenLight() { + this->addBaseColor(SCHEME_GREEN); + } +}; +typedef TGreenLight<> GreenLight; + +template +struct TYellowLight : TBase { + TYellowLight() { + this->addBaseColor(SCHEME_YELLOW); + } +}; +typedef TYellowLight<> YellowLight; + +template +struct TBlueLight : TBase { + TBlueLight() { + this->addBaseColor(SCHEME_BLUE); + } +}; +typedef TBlueLight<> BlueLight; + +template +struct TWhiteLight : TBase { + TWhiteLight() { + this->addBaseColor(SCHEME_WHITE); + } +}; +typedef TWhiteLight<> WhiteLight; + +/** Reads two adjacent lightIds, so `lightId` and `lightId + 1` must be defined */ +template +struct TGreenRedLight : TBase { + TGreenRedLight() { + this->addBaseColor(SCHEME_GREEN); + this->addBaseColor(SCHEME_RED); + } +}; +typedef TGreenRedLight<> GreenRedLight; + +template +struct TRedGreenBlueLight : TBase { + TRedGreenBlueLight() { + this->addBaseColor(SCHEME_RED); + this->addBaseColor(SCHEME_GREEN); + this->addBaseColor(SCHEME_BLUE); + } +}; +typedef TRedGreenBlueLight<> RedGreenBlueLight; + +/** Based on the size of 5mm LEDs */ +template +struct LargeLight : TBase { + LargeLight() { + this->box.size = app::mm2px(math::Vec(5.179, 5.179)); + } +}; + +/** Based on the size of 3mm LEDs */ +template +struct MediumLight : TBase { + MediumLight() { + this->box.size = app::mm2px(math::Vec(3.176, 3.176)); + } +}; + +/** Based on the size of 2mm LEDs */ +template +struct SmallLight : TBase { + SmallLight() { + this->box.size = app::mm2px(math::Vec(2.176, 2.176)); + } +}; + +/** Based on the size of 1mm LEDs */ +template +struct TinyLight : TBase { + TinyLight() { + this->box.size = app::mm2px(math::Vec(1.088, 1.088)); + } +}; + +template +struct RectangleLight : TBase { + void drawLight(const widget::Widget::DrawArgs& args) override { + nvgBeginPath(args.vg); + nvgRect(args.vg, 0, 0, this->box.size.x, this->box.size.y); + + // Background + if (this->bgColor.a > 0.0) { + nvgFillColor(args.vg, this->bgColor); + nvgFill(args.vg); + } + + // Foreground + if (this->color.a > 0.0) { + nvgFillColor(args.vg, this->color); + nvgFill(args.vg); + } + + // Border + if (this->borderColor.a > 0.0) { + nvgStrokeWidth(args.vg, 0.5); + nvgStrokeColor(args.vg, this->borderColor); + nvgStroke(args.vg); + } + } +}; + +/** A light for displaying on top of PB61303. Must add a color by subclassing or templating. */ +template +struct LEDBezelLight : TBase { + LEDBezelLight() { + this->bgColor = color::BLACK_TRANSPARENT; + this->box.size = app::mm2px(math::Vec(6.0, 6.0)); + } +}; + +/** A light to displayed over PB61303. Must add a color by subclassing or templating. +Don't add this as a child of the PB61303 itself. Instead, just place it over it as a sibling in the scene graph, offset by app::mm2px(math::Vec(0.5, 0.5)). +*/ +template +struct PB61303Light : TBase { + PB61303Light() { + this->bgColor = color::BLACK_TRANSPARENT; + this->box.size = app::mm2px(math::Vec(9.0, 9.0)); + } +}; + + //////////////////// // Knobs //////////////////// @@ -390,142 +558,97 @@ struct LEDSliderWhite : LEDSlider { } }; -//////////////////// -// Ports -//////////////////// - -struct PJ301MPort : app::SvgPort { - PJ301MPort() { - setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/PJ301M.svg"))); +struct LEDSliderHorizontal : app::SvgSlider { + LEDSliderHorizontal() { + horizontal = true; + maxHandlePos = app::mm2px(math::Vec(22.078, 0.738).plus(math::Vec(0, 2))); + minHandlePos = app::mm2px(math::Vec(0.738, 0.738).plus(math::Vec(0, 2))); + setBackgroundSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/LEDSliderHorizontal.svg"))); } }; -struct PJ3410Port : app::SvgPort { - PJ3410Port() { - setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/PJ3410.svg"))); - } -}; - -struct CL1362Port : app::SvgPort { - CL1362Port() { - setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/CL1362.svg"))); - } -}; - -//////////////////// -// Lights -//////////////////// +template +struct LightSlider : TBase { + app::ModuleLightWidget* light; -struct GrayModuleLightWidget : app::ModuleLightWidget { - GrayModuleLightWidget() { - bgColor = nvgRGB(0x5a, 0x5a, 0x5a); - borderColor = nvgRGBA(0, 0, 0, 0x60); + LightSlider() { + light = new RectangleLight; + this->addChild(light); } -}; -struct RedLight : GrayModuleLightWidget { - RedLight() { - addBaseColor(SCHEME_RED); + void setFirstLightId(int firstLightId) { + if (this->paramQuantity) + light->module = this->paramQuantity->module; + light->firstLightId = firstLightId; } -}; -struct GreenLight : GrayModuleLightWidget { - GreenLight() { - addBaseColor(SCHEME_GREEN); + void step() override { + TBase::step(); + // Move center of light to center of handle + light->box.pos = this->handle->box.pos + .plus(this->handle->box.size.div(2)) + .minus(light->box.size.div(2)); } }; -struct YellowLight : GrayModuleLightWidget { - YellowLight() { - addBaseColor(SCHEME_YELLOW); +template +struct LEDLightSlider : LightSlider { + LEDLightSlider() { + this->setHandleSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/LEDSliderHandle.svg"))); + this->light->box.size = app::mm2px(math::Vec(1.524, 3.276)); } }; -struct BlueLight : GrayModuleLightWidget { - BlueLight() { - addBaseColor(SCHEME_BLUE); +template +struct LEDLightSliderHorizontal : LightSlider { + LEDLightSliderHorizontal() { + this->setHandleSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/LEDSliderHorizontalHandle.svg"))); + this->light->box.size = app::mm2px(math::Vec(3.276, 1.524)); } }; -struct WhiteLight : GrayModuleLightWidget { - WhiteLight() { - addBaseColor(SCHEME_WHITE); - } -}; -/** Reads two adjacent lightIds, so `lightId` and `lightId + 1` must be defined */ -struct GreenRedLight : GrayModuleLightWidget { - GreenRedLight() { - addBaseColor(SCHEME_GREEN); - addBaseColor(SCHEME_RED); - } -}; +//////////////////// +// Ports +//////////////////// -struct RedGreenBlueLight : GrayModuleLightWidget { - RedGreenBlueLight() { - addBaseColor(SCHEME_RED); - addBaseColor(SCHEME_GREEN); - addBaseColor(SCHEME_BLUE); +struct PJ301MPort : app::SvgPort { + PJ301MPort() { + setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/PJ301M.svg"))); } }; -/** Based on the size of 5mm LEDs */ -template -struct LargeLight : BASE { - LargeLight() { - this->box.size = app::mm2px(math::Vec(5.179, 5.179)); +struct PJ3410Port : app::SvgPort { + PJ3410Port() { + setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/PJ3410.svg"))); } }; -/** Based on the size of 3mm LEDs */ -template -struct MediumLight : BASE { - MediumLight() { - this->box.size = app::mm2px(math::Vec(3.176, 3.176)); +struct CL1362Port : app::SvgPort { + CL1362Port() { + setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/CL1362.svg"))); } }; -/** Based on the size of 2mm LEDs */ -template -struct SmallLight : BASE { - SmallLight() { - this->box.size = app::mm2px(math::Vec(2.176, 2.176)); - } -}; -/** Based on the size of 1mm LEDs */ -template -struct TinyLight : BASE { - TinyLight() { - this->box.size = app::mm2px(math::Vec(1.088, 1.088)); - } -}; +//////////////////// +// Switches +//////////////////// -/** A light for displaying on top of PB61303. Must add a color by subclassing or templating. */ -template -struct LEDBezelLight : BASE { - LEDBezelLight() { - this->bgColor = color::BLACK_TRANSPARENT; - this->box.size = app::mm2px(math::Vec(6.0, 6.0)); +template +struct LatchingSwitch : TSwitch { + LatchingSwitch() { + this->momentary = false; } }; -/** A light to displayed over PB61303. Must add a color by subclassing or templating. -Don't add this as a child of the PB61303 itself. Instead, just place it over it as a sibling in the scene graph, offset by app::mm2px(math::Vec(0.5, 0.5)). -*/ -template -struct PB61303Light : BASE { - PB61303Light() { - this->bgColor = color::BLACK_TRANSPARENT; - this->box.size = app::mm2px(math::Vec(9.0, 9.0)); +template +struct MomentarySwitch : TSwitch { + MomentarySwitch() { + this->momentary = true; } }; - -//////////////////// -// Switches -//////////////////// - struct NKK : app::SvgSwitch { NKK() { addFrame(APP->window->loadSvg(asset::system("res/ComponentLibrary/NKK_0.svg"))); @@ -595,6 +718,24 @@ struct LEDBezel : app::SvgSwitch { } }; +template +struct LEDLightBezel : LEDBezel { + app::ModuleLightWidget* light; + + LEDLightBezel() { + light = new LEDBezelLight; + // Move center of light to center of box + light->box.pos = box.size.div(2).minus(light->box.size.div(2)); + addChild(light); + } + + void setFirstLightId(int firstLightId) { + if (paramQuantity) + light->module = paramQuantity->module; + light->firstLightId = firstLightId; + } +}; + struct PB61303 : app::SvgSwitch { PB61303() { momentary = true; @@ -618,6 +759,46 @@ struct ScrewBlack : app::SvgScrew { } }; +struct SegmentDisplay : widget::Widget { + int lightsLen = 0; + bool vertical = false; + float margin = app::mm2px(0.5); + + void draw(const DrawArgs& args) override { + // Background + nvgBeginPath(args.vg); + nvgRect(args.vg, 0, 0, box.size.x, box.size.y); + nvgFillColor(args.vg, color::BLACK); + nvgFill(args.vg); + Widget::draw(args); + } + + template + void setLights(engine::Module* module, int firstLightId, int lightsLen) { + clearChildren(); + this->lightsLen = lightsLen; + float r = (vertical ? box.size.y : box.size.x) - margin; + for (int i = 0; i < lightsLen; i++) { + float p = float(i) / lightsLen; + app::ModuleLightWidget* light = new RectangleLight; + if (vertical) { + light->box.pos.y = p * r + margin; + light->box.size.y = r / lightsLen - margin; + light->box.size.x = box.size.x; + } + else { + light->box.pos.x = p * r + margin; + light->box.size.x = r / lightsLen - margin; + light->box.size.y = box.size.y; + } + light->module = module; + light->firstLightId = firstLightId; + firstLightId += light->baseColors.size(); + addChild(light); + } + } +}; + } // namespace componentlibrary } // namespace rack diff --git a/include/helpers.hpp b/include/helpers.hpp index fb26c1c3..ebde5c3a 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -124,6 +124,21 @@ TModuleLightWidget* createLightCentered(math::Vec pos, engine::Module* module, i return o; } +/** Creates a param with a light and calls setFirstLightId() on it. */ +template +TParamWidget* createLightParam(math::Vec pos, engine::Module* module, int paramId, int firstLightId) { + TParamWidget* o = createParam(pos, module, paramId); + o->setFirstLightId(firstLightId); + return o; +} + +template +TParamWidget* createLightParamCentered(math::Vec pos, engine::Module* module, int paramId, int firstLightId) { + TParamWidget* o = createParamCentered(pos, module, paramId); + o->setFirstLightId(firstLightId); + return o; +} + template TMenuLabel * createMenuLabel(std::string text) { TMenuLabel* o = new TMenuLabel; diff --git a/include/logger.hpp b/include/logger.hpp index 9e94bfea..cf356019 100644 --- a/include/logger.hpp +++ b/include/logger.hpp @@ -9,10 +9,10 @@ will print something like [0.123 debug myfile.cpp:45] error: 67 */ -#define DEBUG(format, ...) logger::log(rack::logger::DEBUG_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) -#define INFO(format, ...) logger::log(rack::logger::INFO_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) -#define WARN(format, ...) logger::log(rack::logger::WARN_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) -#define FATAL(format, ...) logger::log(rack::logger::FATAL_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) +#define DEBUG(format, ...) rack::logger::log(rack::logger::DEBUG_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) +#define INFO(format, ...) rack::logger::log(rack::logger::INFO_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) +#define WARN(format, ...) rack::logger::log(rack::logger::WARN_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) +#define FATAL(format, ...) rack::logger::log(rack::logger::FATAL_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) namespace rack { diff --git a/include/network.hpp b/include/network.hpp index 1843bb8d..3d815e2b 100644 --- a/include/network.hpp +++ b/include/network.hpp @@ -18,6 +18,7 @@ enum Method { METHOD_DELETE, }; +void init(); /** Requests a JSON API URL over HTTP(S), using the data as the query (GET) or the body (POST, etc) Caller must json_decref(). */ diff --git a/include/rack.hpp b/include/rack.hpp index 59f03910..9771e064 100644 --- a/include/rack.hpp +++ b/include/rack.hpp @@ -100,19 +100,19 @@ namespace rack { -/** Define this macro before including this header to prevent common namespaces from being included in the main `rack::` namespace. */ -#ifndef RACK_FLATTEN_NAMESPACES - // Import some namespaces for convenience - using namespace logger; - using namespace math; - using namespace widget; - using namespace ui; - using namespace app; - using plugin::Plugin; - using plugin::Model; - using namespace engine; - using namespace componentlibrary; -#endif +// Import some namespaces for convenience +using namespace logger; +using namespace math; +using namespace widget; +using namespace ui; +using namespace app; +using plugin::Plugin; +using plugin::Model; +using namespace engine; +using namespace componentlibrary; + +// Import namespace recursively to solve the problem of calling `rack::DEBUG(...)` which expands to `rack::rack::logger(...)`. +namespace rack = rack; } // namespace rack diff --git a/include/settings.hpp b/include/settings.hpp index 767e0cd8..a2fbe86f 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -34,8 +34,7 @@ extern int threadCount; extern bool paramTooltip; extern bool cpuMeter; extern bool lockModules; -extern float frameRateLimit; -extern bool frameRateSync; +extern int frameSwapInterval; extern float autosavePeriod; extern bool skipLoadOnLaunch; extern std::string patchPath; diff --git a/include/window.hpp b/include/window.hpp index c9fa36d6..ad639b24 100644 --- a/include/window.hpp +++ b/include/window.hpp @@ -5,6 +5,7 @@ #include #include #define GLEW_STATIC +#define GLEW_NO_GLU #include #include #include @@ -89,6 +90,7 @@ struct Window { void setFullScreen(bool fullScreen); bool isFullScreen(); bool isFrameOverdue(); + double getMonitorRefreshRate(); std::shared_ptr loadFont(const std::string& filename); std::shared_ptr loadImage(const std::string& filename); diff --git a/res/ComponentLibrary/LEDSliderHandle.svg b/res/ComponentLibrary/LEDSliderHandle.svg new file mode 100644 index 00000000..737514c0 --- /dev/null +++ b/res/ComponentLibrary/LEDSliderHandle.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/res/ComponentLibrary/LEDSliderHorizontal.svg b/res/ComponentLibrary/LEDSliderHorizontal.svg new file mode 100644 index 00000000..fac8fc37 --- /dev/null +++ b/res/ComponentLibrary/LEDSliderHorizontal.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/res/ComponentLibrary/LEDSliderHorizontalHandle.svg b/res/ComponentLibrary/LEDSliderHorizontalHandle.svg new file mode 100644 index 00000000..3673923f --- /dev/null +++ b/res/ComponentLibrary/LEDSliderHorizontalHandle.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/app/LightWidget.cpp b/src/app/LightWidget.cpp index 2cf46933..3df6dce8 100644 --- a/src/app/LightWidget.cpp +++ b/src/app/LightWidget.cpp @@ -12,7 +12,7 @@ void LightWidget::draw(const DrawArgs& args) { } void LightWidget::drawLight(const DrawArgs& args) { - float radius = box.size.x / 2.0; + float radius = std::min(box.size.x, box.size.y) / 2.0; nvgBeginPath(args.vg); nvgCircle(args.vg, radius, radius, radius); @@ -38,7 +38,7 @@ void LightWidget::drawLight(const DrawArgs& args) { } void LightWidget::drawHalo(const DrawArgs& args) { - float radius = box.size.x / 2.0; + float radius = std::min(box.size.x, box.size.y) / 2.0; float oradius = 4.0 * radius; nvgBeginPath(args.vg); diff --git a/src/app/MenuBar.cpp b/src/app/MenuBar.cpp index a2bb9500..f351c989 100644 --- a/src/app/MenuBar.cpp +++ b/src/app/MenuBar.cpp @@ -327,6 +327,30 @@ struct CursorLockItem : ui::MenuItem { } }; +struct FrameRateValueItem : ui::MenuItem { + int frameSwapInterval; + void onAction(const event::Action& e) override { + settings::frameSwapInterval = frameSwapInterval; + } +}; + +struct FrameRateItem : ui::MenuItem { + ui::Menu* createChildMenu() override { + ui::Menu* menu = new ui::Menu; + + for (int i = 1; i <= 6; i++) { + double frameRate = APP->window->getMonitorRefreshRate() / i; + + FrameRateValueItem* item = new FrameRateValueItem; + item->frameSwapInterval = i; + item->text = string::f("%.0lf Hz", frameRate); + item->rightText += CHECKMARK(settings::frameSwapInterval == i); + menu->addChild(item); + } + return menu; + } +}; + struct FullscreenItem : ui::MenuItem { void onAction(const event::Action& e) override { APP->window->setFullScreen(!APP->window->isFullScreen()); @@ -366,6 +390,10 @@ struct ViewButton : MenuButton { cableTensionSlider->box.size.x = 200.0; menu->addChild(cableTensionSlider); + FrameRateItem* frameRateItem = new FrameRateItem; + frameRateItem->text = "Frame rate"; + menu->addChild(frameRateItem); + FullscreenItem* fullscreenItem = new FullscreenItem; fullscreenItem->text = "Fullscreen"; fullscreenItem->rightText = "F11"; @@ -477,7 +505,8 @@ struct EngineButton : MenuButton { CpuMeterItem* cpuMeterItem = new CpuMeterItem; cpuMeterItem->text = "CPU meter"; - cpuMeterItem->rightText = CHECKMARK(settings::cpuMeter); + cpuMeterItem->rightText = "F3 "; + cpuMeterItem->rightText += CHECKMARK(settings::cpuMeter); menu->addChild(cpuMeterItem); SampleRateItem* sampleRateItem = new SampleRateItem; diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp index 17432edf..e274eb59 100644 --- a/src/app/ModuleBrowser.cpp +++ b/src/app/ModuleBrowser.cpp @@ -219,8 +219,7 @@ struct ModelBox : widget::OpaqueWidget { void setTooltip(ui::Tooltip* tooltip) { if (this->tooltip) { - this->tooltip->parent->removeChild(this->tooltip); - delete this->tooltip; + this->tooltip->requestDelete(); this->tooltip = NULL; } diff --git a/src/app/ParamWidget.cpp b/src/app/ParamWidget.cpp index 1127f192..50a6691f 100644 --- a/src/app/ParamWidget.cpp +++ b/src/app/ParamWidget.cpp @@ -72,6 +72,9 @@ struct ParamTooltip : ui::Tooltip { Tooltip::step(); // Position at bottom-right of parameter box.pos = paramWidget->getAbsoluteOffset(paramWidget->box.size).round(); + // Fit inside parent (copied from Tooltip.cpp) + assert(parent); + box = box.nudge(parent->box.zeroPos()); } }; diff --git a/src/app/RackScrollWidget.cpp b/src/app/RackScrollWidget.cpp index 0bd00cf0..da6e132a 100644 --- a/src/app/RackScrollWidget.cpp +++ b/src/app/RackScrollWidget.cpp @@ -77,8 +77,21 @@ void RackScrollWidget::step() { void RackScrollWidget::draw(const DrawArgs& args) { - // DEBUG("%f %f %f %f", RECT_ARGS(args.clipBox)); + // Hide scrollbars if full screen + bool fullscreen = APP->window->isFullScreen(); + bool horizontalVisible; + bool verticalVisible; + if (fullscreen) { + horizontalVisible = horizontalScrollBar->visible; + verticalVisible = verticalScrollBar->visible; + horizontalScrollBar->visible = false; + verticalScrollBar->visible = false; + } ScrollWidget::draw(args); + if (fullscreen) { + horizontalScrollBar->visible = horizontalVisible; + verticalScrollBar->visible = verticalVisible; + } } void RackScrollWidget::onHoverKey(const event::HoverKey& e) { diff --git a/src/app/Scene.cpp b/src/app/Scene.cpp index 72c25f3e..add47c34 100644 --- a/src/app/Scene.cpp +++ b/src/app/Scene.cpp @@ -23,7 +23,6 @@ Scene::Scene() { menuBar = createMenuBar(); addChild(menuBar); - rackScroll->box.pos.y = menuBar->box.size.y; moduleBrowser = moduleBrowserCreate(); moduleBrowser->hide(); @@ -34,6 +33,10 @@ Scene::~Scene() { } void Scene::step() { + bool fullscreen = APP->window->isFullScreen(); + menuBar->visible = !fullscreen; + rackScroll->box.pos.y = menuBar->visible ? menuBar->box.size.y : 0; + // Resize owned descendants menuBar->box.size.x = box.size.x; rackScroll->box.size = box.size.minus(rackScroll->box.pos); @@ -61,96 +64,76 @@ void Scene::onHoverKey(const event::HoverKey& e) { return; if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { - switch (e.key) { - case GLFW_KEY_N: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - APP->patch->resetDialog(); - e.consume(this); - } - } break; - case GLFW_KEY_Q: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - APP->window->close(); - e.consume(this); - } - } break; - case GLFW_KEY_O: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - APP->patch->loadDialog(); - e.consume(this); - } - if ((e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { - APP->patch->revertDialog(); - e.consume(this); - } - } break; - case GLFW_KEY_S: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - APP->patch->saveDialog(); - e.consume(this); - } - if ((e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { - APP->patch->saveAsDialog(); - e.consume(this); - } - } break; - case GLFW_KEY_Z: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - APP->history->undo(); - e.consume(this); - } - if ((e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { - APP->history->redo(); - e.consume(this); - } - } break; - case GLFW_KEY_MINUS: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - float zoom = settings::zoom; - zoom *= 2; - zoom = std::ceil(zoom - 0.01f) - 1; - zoom /= 2; - settings::zoom = zoom; - e.consume(this); - } - } break; - case GLFW_KEY_EQUAL: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - float zoom = settings::zoom; - zoom *= 2; - zoom = std::floor(zoom + 0.01f) + 1; - zoom /= 2; - settings::zoom = zoom; - e.consume(this); - } - } break; - case GLFW_KEY_0: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - settings::zoom = 0.f; - e.consume(this); - } - } break; - case GLFW_KEY_ENTER: { - if ((e.mods & RACK_MOD_MASK) == 0) { - moduleBrowser->show(); - } - e.consume(this); - } break; - case GLFW_KEY_F1: { - if ((e.mods & RACK_MOD_MASK) == 0) { - std::thread t([] { - system::openBrowser("https://vcvrack.com/manual/"); - }); - t.detach(); - e.consume(this); - } - } break; - case GLFW_KEY_F11: { - if ((e.mods & RACK_MOD_MASK) == 0) { - APP->window->setFullScreen(!APP->window->isFullScreen()); - e.consume(this); - } - } break; + if (e.key == GLFW_KEY_N && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + APP->patch->resetDialog(); + e.consume(this); + } + else if (e.key == GLFW_KEY_Q && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + APP->window->close(); + e.consume(this); + } + else if (e.key == GLFW_KEY_O && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + APP->patch->loadDialog(); + e.consume(this); + } + else if (e.key == GLFW_KEY_O && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { + APP->patch->revertDialog(); + e.consume(this); + } + else if (e.key == GLFW_KEY_S && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + APP->patch->saveDialog(); + e.consume(this); + } + else if (e.key == GLFW_KEY_S && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { + APP->patch->saveAsDialog(); + e.consume(this); + } + else if (e.key == GLFW_KEY_Z && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + APP->history->undo(); + e.consume(this); + } + else if (e.key == GLFW_KEY_Z && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { + APP->history->redo(); + e.consume(this); + } + else if ((e.key == GLFW_KEY_MINUS || e.key == GLFW_KEY_KP_SUBTRACT) && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + float zoom = settings::zoom; + zoom *= 2; + zoom = std::ceil(zoom - 0.01f) - 1; + zoom /= 2; + settings::zoom = zoom; + e.consume(this); + } + else if ((e.key == GLFW_KEY_EQUAL || e.key == GLFW_KEY_KP_ADD) && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + float zoom = settings::zoom; + zoom *= 2; + zoom = std::floor(zoom + 0.01f) + 1; + zoom /= 2; + settings::zoom = zoom; + e.consume(this); + } + else if ((e.key == GLFW_KEY_0 || e.key == GLFW_KEY_KP_0) && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + settings::zoom = 0.f; + e.consume(this); + } + else if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && (e.mods & RACK_MOD_MASK) == 0) { + moduleBrowser->show(); + e.consume(this); + } + else if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) { + std::thread t([] { + system::openBrowser("https://vcvrack.com/manual/"); + }); + t.detach(); + e.consume(this); + } + else if (e.key == GLFW_KEY_F3 && (e.mods & RACK_MOD_MASK) == 0) { + settings::cpuMeter ^= true; + e.consume(this); + } + else if (e.key == GLFW_KEY_F11 && (e.mods & RACK_MOD_MASK) == 0) { + APP->window->setFullScreen(!APP->window->isFullScreen()); + e.consume(this); } } } diff --git a/src/asset.cpp b/src/asset.cpp index 522623cc..4b1d2915 100644 --- a/src/asset.cpp +++ b/src/asset.cpp @@ -37,9 +37,19 @@ void init() { #if defined ARCH_MAC CFBundleRef bundle = CFBundleGetMainBundle(); assert(bundle); + + CFURLRef bundleUrl = CFBundleCopyBundleURL(bundle); + char bundleBuf[PATH_MAX]; + Boolean success = CFURLGetFileSystemRepresentation(bundleUrl, TRUE, (UInt8*) bundleBuf, sizeof(bundleBuf)); + assert(success); + bundlePath = bundleBuf; + // If the bundle path doesn't end with ".app", assume it's a fake app bundle run from the command line. + if (string::filenameExtension(string::filename(bundlePath)) != "app") + bundlePath = ""; + CFURLRef resourcesUrl = CFBundleCopyResourcesDirectoryURL(bundle); char resourcesBuf[PATH_MAX]; - Boolean success = CFURLGetFileSystemRepresentation(resourcesUrl, TRUE, (UInt8*) resourcesBuf, sizeof(resourcesBuf)); + success = CFURLGetFileSystemRepresentation(resourcesUrl, TRUE, (UInt8*) resourcesBuf, sizeof(resourcesBuf)); assert(success); CFRelease(resourcesUrl); systemDir = resourcesBuf; @@ -145,6 +155,7 @@ std::string pluginsPath; std::string settingsPath; std::string autosavePath; std::string templatePath; +std::string bundlePath; } // namespace asset diff --git a/src/core/MIDI_CV.cpp b/src/core/MIDI_CV.cpp index 270829f9..e001432c 100644 --- a/src/core/MIDI_CV.cpp +++ b/src/core/MIDI_CV.cpp @@ -158,9 +158,9 @@ struct MIDI_CV : Module { // note on case 0x9: { if (msg.getValue() > 0) { - int c = (polyMode == MPE_MODE) ? msg.getChannel() : assignChannel(msg.getNote()); + int c = msg.getChannel(); + pressNote(msg.getNote(), &c); velocities[c] = msg.getValue(); - pressNote(msg.getNote(), c); } else { // For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released. @@ -297,17 +297,24 @@ struct MIDI_CV : Module { } } - void pressNote(uint8_t note, int channel) { + void pressNote(uint8_t note, int* channel) { // Remove existing similar note auto it = std::find(heldNotes.begin(), heldNotes.end(), note); if (it != heldNotes.end()) heldNotes.erase(it); // Push note heldNotes.push_back(note); + // Determine actual channel + if (polyMode == MPE_MODE) { + // Channel is already decided for us + } + else { + *channel = assignChannel(note); + } // Set note - notes[channel] = note; - gates[channel] = true; - retriggerPulses[channel].trigger(1e-3); + notes[*channel] = note; + gates[*channel] = true; + retriggerPulses[*channel].trigger(1e-3); } void releaseNote(uint8_t note) { @@ -336,24 +343,15 @@ struct MIDI_CV : Module { } void pressPedal() { + if (pedal) + return; pedal = true; } void releasePedal() { + if (!pedal) + return; pedal = false; - // Clear all gates - for (int c = 0; c < 16; c++) { - gates[c] = false; - } - // Add back only the gates from heldNotes - for (uint8_t note : heldNotes) { - // Find note's channels - for (int c = 0; c < channels; c++) { - if (notes[c] == note) { - gates[c] = true; - } - } - } // Set last note if monophonic if (channels == 1) { if (!heldNotes.empty()) { @@ -361,6 +359,20 @@ struct MIDI_CV : Module { notes[0] = lastNote; } } + // Clear notes that are not held if polyphonic + else { + for (int c = 0; c < channels; c++) { + if (!gates[c]) + continue; + gates[c] = false; + for (uint8_t note : heldNotes) { + if (notes[c] == note) { + gates[c] = true; + break; + } + } + } + } } void setChannels(int channels) { diff --git a/src/core/MIDI_Map.cpp b/src/core/MIDI_Map.cpp index 111d964e..5d703c94 100644 --- a/src/core/MIDI_Map.cpp +++ b/src/core/MIDI_Map.cpp @@ -92,7 +92,7 @@ struct MIDI_Map : Module { Module* module = paramHandles[id].module; if (!module) continue; - // Get ParamQuantity + // Get ParamQuantity from ParamHandle int paramId = paramHandles[id].paramId; ParamQuantity* paramQuantity = module->paramQuantities[paramId]; if (!paramQuantity) @@ -105,7 +105,9 @@ struct MIDI_Map : Module { filterInitialized[id] = true; continue; } - // Set param if value has been initialized + // Check if CC has been set by the MIDI device + if (values[cc] < 0) + continue; float value = values[cc] / 127.f; // Detect behavior from MIDI buttons. if (std::fabs(valueFilters[id].out - value) >= 1.f) { diff --git a/src/dep.cpp b/src/dep.cpp index 3a677cdd..9b4961ad 100644 --- a/src/dep.cpp +++ b/src/dep.cpp @@ -1,6 +1,7 @@ // This source file compiles those annoying implementation-in-header libraries #define GLEW_STATIC +#define GLEW_NO_GLU #include #include diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 82c22915..ee0e64b8 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -429,12 +429,12 @@ static void Engine_run(Engine* that) { aheadTime = 0.0; } - // Launch workers - if (internal->threadCount != settings::threadCount || internal->realTime != settings::realTime) { - Engine_relaunchWorkers(that, settings::threadCount, settings::realTime); - } - if (!internal->paused) { + // Launch workers + if (internal->threadCount != settings::threadCount || internal->realTime != settings::realTime) { + Engine_relaunchWorkers(that, settings::threadCount, settings::realTime); + } + std::lock_guard lock(internal->mutex); // Update expander pointers @@ -448,6 +448,12 @@ static void Engine_run(Engine* that) { Engine_step(that); } } + else { + // Stop workers while closed + if (internal->threadCount != 1) { + Engine_relaunchWorkers(that, 1, settings::realTime); + } + } double stepTime = mutexSteps * internal->sampleTime; aheadTime += stepTime; diff --git a/src/main.cpp b/src/main.cpp index 6ee1bb2a..6ca694a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -68,7 +69,7 @@ int main(int argc, char* argv[]) { // Parse command line arguments int c; opterr = 0; - while ((c = getopt(argc, argv, "dhp:s:u:")) != -1) { + while ((c = getopt(argc, argv, "dht:s:u:")) != -1) { switch (c) { case 'd': { settings::devMode = true; @@ -76,14 +77,11 @@ int main(int argc, char* argv[]) { case 'h': { settings::headless = true; } break; -#if !defined ARCH_MAC - // Due to Mac app translocation and Apple adding a -psn... flag when launched, disable screenshots on Mac for now. - case 'p': { + case 't': { screenshot = true; // If parsing number failed, use default value - sscanf(optarg, "%f", &screenshotZoom); + std::sscanf(optarg, "%f", &screenshotZoom); } break; -#endif case 's': { asset::systemDir = optarg; } break; @@ -97,6 +95,7 @@ int main(int argc, char* argv[]) { patchPath = argv[optind]; } + // Initialize environment asset::init(); logger::init(); @@ -145,6 +144,7 @@ int main(int argc, char* argv[]) { INFO("Initializing environment"); random::init(); + network::init(); midi::init(); rtmidiInit(); bridgeInit(); diff --git a/src/network.cpp b/src/network.cpp index 2fe28477..694a4952 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -30,6 +30,13 @@ static size_t writeStringCallback(char* ptr, size_t size, size_t nmemb, void* us } +void init() { + // curl_easy_init() calls this automatically, but it's good to make sure this is done on the main thread before other threads are spawned. + // https://curl.haxx.se/libcurl/c/curl_easy_init.html + curl_global_init(CURL_GLOBAL_ALL); +} + + json_t* requestJson(Method method, std::string url, json_t* dataJ) { CURL* curl = createCurl(); char* reqStr = NULL; diff --git a/src/patch.cpp b/src/patch.cpp index e67251b4..7f793332 100644 --- a/src/patch.cpp +++ b/src/patch.cpp @@ -33,14 +33,16 @@ void PatchManager::init(std::string path) { return; } - // To prevent launch crashes, if Rack crashes between now and 15 seconds from now, the "skipAutosaveOnLaunch" property will remain in settings.json, so that in the next launch, the broken autosave will not be loaded. - bool oldSkipLoadOnLaunch = settings::skipLoadOnLaunch; - settings::skipLoadOnLaunch = true; - settings::save(asset::settingsPath); - settings::skipLoadOnLaunch = false; - if (oldSkipLoadOnLaunch && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, "Rack has recovered from a crash, possibly caused by a faulty module in your patch. Clear your patch and start over?")) { - this->path = ""; - return; + if (!settings::devMode) { + // To prevent launch crashes, if Rack crashes between now and 15 seconds from now, the "skipAutosaveOnLaunch" property will remain in settings.json, so that in the next launch, the broken autosave will not be loaded. + bool oldSkipLoadOnLaunch = settings::skipLoadOnLaunch; + settings::skipLoadOnLaunch = true; + settings::save(asset::settingsPath); + settings::skipLoadOnLaunch = false; + if (oldSkipLoadOnLaunch && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, "Rack has recovered from a crash, possibly caused by a faulty module in your patch. Clear your patch and start over?")) { + this->path = ""; + return; + } } // Load autosave diff --git a/src/plugin.cpp b/src/plugin.cpp index 91c0d0fd..c0090db7 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -71,7 +71,7 @@ static InitCallback loadLibrary(Plugin* plugin) { throw UserException(string::f("Failed to load library %s: code %d", libraryFilename.c_str(), error)); } #else - void* handle = dlopen(libraryFilename.c_str(), RTLD_NOW); + void* handle = dlopen(libraryFilename.c_str(), RTLD_NOW | RTLD_LOCAL); if (!handle) { throw UserException(string::f("Failed to load library %s: %s", libraryFilename.c_str(), dlerror())); } diff --git a/src/settings.cpp b/src/settings.cpp index 273c7121..eb3a5c0a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -29,8 +29,7 @@ int threadCount = 1; bool paramTooltip = false; bool cpuMeter = false; bool lockModules = false; -float frameRateLimit = 70.0; -bool frameRateSync = true; +int frameSwapInterval = 1; float autosavePeriod = 15.0; bool skipLoadOnLaunch = false; std::string patchPath; @@ -75,9 +74,7 @@ json_t* toJson() { json_object_set_new(rootJ, "lockModules", json_boolean(lockModules)); - json_object_set_new(rootJ, "frameRateLimit", json_real(frameRateLimit)); - - json_object_set_new(rootJ, "frameRateSync", json_boolean(frameRateSync)); + json_object_set_new(rootJ, "frameSwapInterval", json_integer(frameSwapInterval)); json_object_set_new(rootJ, "autosavePeriod", json_real(autosavePeriod)); @@ -160,13 +157,9 @@ void fromJson(json_t* rootJ) { if (lockModulesJ) lockModules = json_boolean_value(lockModulesJ); - json_t* frameRateLimitJ = json_object_get(rootJ, "frameRateLimit"); - if (frameRateLimitJ) - frameRateLimit = json_number_value(frameRateLimitJ); - - json_t* frameRateSyncJ = json_object_get(rootJ, "frameRateSync"); - if (frameRateSyncJ) - frameRateSync = json_boolean_value(frameRateSyncJ); + json_t* frameSwapIntervalJ = json_object_get(rootJ, "frameSwapInterval"); + if (frameSwapIntervalJ) + frameSwapInterval = json_integer_value(frameSwapIntervalJ); json_t* autosavePeriodJ = json_object_get(rootJ, "autosavePeriod"); if (autosavePeriodJ) diff --git a/src/updater.cpp b/src/updater.cpp index 15a93fcc..ed2fbf90 100644 --- a/src/updater.cpp +++ b/src/updater.cpp @@ -61,24 +61,33 @@ void update() { if (downloadUrl == "") return; -#if defined ARCH_WIN || defined ARCH_MAC // Download update std::string filename = string::filename(network::urlPath(downloadUrl)); std::string path = asset::user(filename); - INFO("Download update %s to %s", downloadUrl.c_str(), path.c_str()); + INFO("Downloading update %s to %s", downloadUrl.c_str(), path.c_str()); network::requestDownload(downloadUrl, path, &progress); -#endif #if defined ARCH_WIN // Launch the installer INFO("Launching update %s", path.c_str()); system::runProcessDetached(path); #elif defined ARCH_MAC - // Unzip app using Apple's unzipper, since Rack's unzipper doesn't handle the metadata stuff correctly. - std::string cmd = "open \"" + path + "\""; + std::string cmd; + // std::string appPath = asset::userDir + "/Rack.app"; + // cmd = "rm -rf '" + appPath + "'"; + // std::system(cmd.c_str()); + // // Unzip app using Apple's unzipper, since Rack's unzipper doesn't handle the metadata stuff correctly. + // cmd = "unzip -q '" + path + "' -d '" + asset::userDir + "'"; + // std::system(cmd.c_str()); + // // Open app in Finder + // cmd = "open -R '" + appPath + "'"; + // std::system(cmd.c_str()); + + // Open Archive Utility + cmd = "open '" + path + "'"; std::system(cmd.c_str()); -#else - system::openBrowser(downloadUrl); +#elif defined ARCH_LIN + system::openFolder(asset::user("")); #endif APP->window->close(); diff --git a/src/window.cpp b/src/window.cpp index 3cab776f..41b75ac9 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -95,6 +95,8 @@ struct Window::Internal { int lastWindowHeight = 0; bool ignoreNextMouseDelta = false; + int frameSwapInterval = -1; + double monitorRefreshRate = -1; }; @@ -248,8 +250,9 @@ Window::Window() { glfwSetInputMode(win, GLFW_LOCK_KEY_MODS, 1); glfwMakeContextCurrent(win); - // Enable v-sync - glfwSwapInterval(settings::frameRateSync ? 1 : 0); + glfwSwapInterval(1); + const GLFWvidmode* monitorMode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + internal->monitorRefreshRate = monitorMode->refreshRate; // Set window callbacks glfwSetWindowSizeCallback(win, windowSizeCallback); @@ -325,7 +328,10 @@ Window::~Window() { void Window::run() { frame = 0; while (!glfwWindowShouldClose(win)) { - frameTimeStart = glfwGetTime(); + double frameTime = glfwGetTime(); + // double frameRate = 1.0 / (frameTime - frameTimeStart); + // DEBUG("%g fps", frameRate); + frameTimeStart = frameTime; // Make event handlers and step() have a clean nanovg context nvgReset(vg); @@ -333,8 +339,14 @@ void Window::run() { // Poll events glfwPollEvents(); - // In case glfwPollEvents() set another OpenGL context + + // In case glfwPollEvents() sets another OpenGL context glfwMakeContextCurrent(win); + if (settings::frameSwapInterval != internal->frameSwapInterval) { + internal->frameSwapInterval = settings::frameSwapInterval; + glfwSwapInterval(settings::frameSwapInterval); + } + // Call cursorPosCallback every frame, not just when the mouse moves { double xpos, ypos; @@ -396,23 +408,10 @@ void Window::run() { glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); nvgEndFrame(vg); - - glfwSwapBuffers(win); } - // Limit frame rate - double frameTimeEnd = glfwGetTime(); - if (settings::frameRateLimit > 0.0) { - double frameDuration = frameTimeEnd - frameTimeStart; - double waitDuration = 1.0 / settings::frameRateLimit - frameDuration; - if (waitDuration > 0.0) { - std::this_thread::sleep_for(std::chrono::duration(waitDuration)); - } - } + glfwSwapBuffers(win); - // Compute actual frame rate - frameTimeEnd = glfwGetTime(); - // DEBUG("%g fps", 1 / (endTime - startTime)); frame++; } } @@ -523,10 +522,15 @@ bool Window::isFullScreen() { } bool Window::isFrameOverdue() { - if (settings::frameRateLimit == 0.0) + if (settings::frameSwapInterval == 0) return false; double frameDuration = glfwGetTime() - frameTimeStart; - return frameDuration > 1.0 / settings::frameRateLimit; + double frameDeadline = settings::frameSwapInterval / internal->monitorRefreshRate; + return frameDuration > frameDeadline; +} + +double Window::getMonitorRefreshRate() { + return internal->monitorRefreshRate; } std::shared_ptr Window::loadFont(const std::string& filename) {