Browse Source

Merge branch 'v1' of https://github.com/VCVRack/Rack into XCUR

pull/1514/head^2
The XOR 5 years ago
parent
commit
205b8344bc
35 changed files with 809 additions and 321 deletions
  1. +12
    -2
      CHANGELOG.md
  2. +1
    -1
      Core.json
  3. +1
    -1
      Makefile
  4. +19
    -19
      dep/Makefile
  5. +1
    -0
      helper.py
  6. +1
    -1
      include/app.hpp
  7. +2
    -0
      include/asset.hpp
  8. +281
    -100
      include/componentlibrary.hpp
  9. +15
    -0
      include/helpers.hpp
  10. +4
    -4
      include/logger.hpp
  11. +1
    -0
      include/network.hpp
  12. +13
    -13
      include/rack.hpp
  13. +1
    -2
      include/settings.hpp
  14. +2
    -0
      include/window.hpp
  15. +66
    -0
      res/ComponentLibrary/LEDSliderHandle.svg
  16. +71
    -0
      res/ComponentLibrary/LEDSliderHorizontal.svg
  17. +66
    -0
      res/ComponentLibrary/LEDSliderHorizontalHandle.svg
  18. +2
    -2
      src/app/LightWidget.cpp
  19. +30
    -1
      src/app/MenuBar.cpp
  20. +1
    -2
      src/app/ModuleBrowser.cpp
  21. +3
    -0
      src/app/ParamWidget.cpp
  22. +14
    -1
      src/app/RackScrollWidget.cpp
  23. +74
    -91
      src/app/Scene.cpp
  24. +12
    -1
      src/asset.cpp
  25. +31
    -19
      src/core/MIDI_CV.cpp
  26. +4
    -2
      src/core/MIDI_Map.cpp
  27. +1
    -0
      src/dep.cpp
  28. +11
    -5
      src/engine/Engine.cpp
  29. +6
    -6
      src/main.cpp
  30. +7
    -0
      src/network.cpp
  31. +10
    -8
      src/patch.cpp
  32. +1
    -1
      src/plugin.cpp
  33. +5
    -12
      src/settings.cpp
  34. +16
    -7
      src/updater.cpp
  35. +24
    -20
      src/window.cpp

+ 12
- 2
CHANGELOG.md View File

@@ -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.



+ 1
- 1
Core.json View File

@@ -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",


+ 1
- 1
Makefile View File

@@ -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


+ 19
- 19
dep/Makefile View File

@@ -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


+ 1
- 0
helper.py View File

@@ -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'] = []


+ 1
- 1
include/app.hpp View File

@@ -49,7 +49,7 @@ void appDestroy();
App* appGet();

/** Accesses the global App pointer */
#define APP appGet()
#define APP rack::appGet()


} // namespace rack

+ 2
- 0
include/asset.hpp View File

@@ -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


+ 281
- 100
include/componentlibrary.hpp View File

@@ -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<RedLight>`

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<LEDLightSlider<GreenLight>>(...)
*/

template <typename TBase = app::ModuleLightWidget>
struct TGrayModuleLightWidget : TBase {
TGrayModuleLightWidget() {
this->bgColor = nvgRGB(0x5a, 0x5a, 0x5a);
this->borderColor = nvgRGBA(0, 0, 0, 0x60);
}
};
typedef TGrayModuleLightWidget<> GrayModuleLightWidget;

template <typename TBase = GrayModuleLightWidget>
struct TRedLight : TBase {
TRedLight() {
this->addBaseColor(SCHEME_RED);
}
};
typedef TRedLight<> RedLight;

template <typename TBase = GrayModuleLightWidget>
struct TGreenLight : TBase {
TGreenLight() {
this->addBaseColor(SCHEME_GREEN);
}
};
typedef TGreenLight<> GreenLight;

template <typename TBase = GrayModuleLightWidget>
struct TYellowLight : TBase {
TYellowLight() {
this->addBaseColor(SCHEME_YELLOW);
}
};
typedef TYellowLight<> YellowLight;

template <typename TBase = GrayModuleLightWidget>
struct TBlueLight : TBase {
TBlueLight() {
this->addBaseColor(SCHEME_BLUE);
}
};
typedef TBlueLight<> BlueLight;

template <typename TBase = GrayModuleLightWidget>
struct TWhiteLight : TBase {
TWhiteLight() {
this->addBaseColor(SCHEME_WHITE);
}
};
typedef TWhiteLight<> WhiteLight;

/** Reads two adjacent lightIds, so `lightId` and `lightId + 1` must be defined */
template <typename TBase = GrayModuleLightWidget>
struct TGreenRedLight : TBase {
TGreenRedLight() {
this->addBaseColor(SCHEME_GREEN);
this->addBaseColor(SCHEME_RED);
}
};
typedef TGreenRedLight<> GreenRedLight;

template <typename TBase = GrayModuleLightWidget>
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 <typename TBase>
struct LargeLight : TBase {
LargeLight() {
this->box.size = app::mm2px(math::Vec(5.179, 5.179));
}
};

/** Based on the size of 3mm LEDs */
template <typename TBase>
struct MediumLight : TBase {
MediumLight() {
this->box.size = app::mm2px(math::Vec(3.176, 3.176));
}
};

/** Based on the size of 2mm LEDs */
template <typename TBase>
struct SmallLight : TBase {
SmallLight() {
this->box.size = app::mm2px(math::Vec(2.176, 2.176));
}
};

/** Based on the size of 1mm LEDs */
template <typename TBase>
struct TinyLight : TBase {
TinyLight() {
this->box.size = app::mm2px(math::Vec(1.088, 1.088));
}
};

template <typename TBase>
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 <typename TBase>
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 <typename TBase>
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 <typename TBase, typename TLightBase = RedLight>
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<TLightBase>;
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 <typename TLightBase = RedLight>
struct LEDLightSlider : LightSlider<LEDSlider, TLightBase> {
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 <typename TLightBase = RedLight>
struct LEDLightSliderHorizontal : LightSlider<LEDSliderHorizontal, TLightBase> {
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 <typename BASE>
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 <typename BASE>
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 <typename BASE>
struct SmallLight : BASE {
SmallLight() {
this->box.size = app::mm2px(math::Vec(2.176, 2.176));
}
};

/** Based on the size of 1mm LEDs */
template <typename BASE>
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 <typename BASE>
struct LEDBezelLight : BASE {
LEDBezelLight() {
this->bgColor = color::BLACK_TRANSPARENT;
this->box.size = app::mm2px(math::Vec(6.0, 6.0));
template <typename TSwitch>
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 <typename BASE>
struct PB61303Light : BASE {
PB61303Light() {
this->bgColor = color::BLACK_TRANSPARENT;
this->box.size = app::mm2px(math::Vec(9.0, 9.0));
template <typename TSwitch>
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 <typename TLightBase = WhiteLight>
struct LEDLightBezel : LEDBezel {
app::ModuleLightWidget* light;

LEDLightBezel() {
light = new LEDBezelLight<TLightBase>;
// 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 <typename TLightBase = WhiteLight>
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<TLightBase>;
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

+ 15
- 0
include/helpers.hpp View File

@@ -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 <class TParamWidget>
TParamWidget* createLightParam(math::Vec pos, engine::Module* module, int paramId, int firstLightId) {
TParamWidget* o = createParam<TParamWidget>(pos, module, paramId);
o->setFirstLightId(firstLightId);
return o;
}

template <class TParamWidget>
TParamWidget* createLightParamCentered(math::Vec pos, engine::Module* module, int paramId, int firstLightId) {
TParamWidget* o = createParamCentered<TParamWidget>(pos, module, paramId);
o->setFirstLightId(firstLightId);
return o;
}

template <class TMenuLabel = ui::MenuLabel>
TMenuLabel * createMenuLabel(std::string text) {
TMenuLabel* o = new TMenuLabel;


+ 4
- 4
include/logger.hpp View File

@@ -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 {


+ 1
- 0
include/network.hpp View File

@@ -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().
*/


+ 13
- 13
include/rack.hpp View File

@@ -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

+ 1
- 2
include/settings.hpp View File

@@ -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;


+ 2
- 0
include/window.hpp View File

@@ -5,6 +5,7 @@
#include <memory>
#include <map>
#define GLEW_STATIC
#define GLEW_NO_GLU
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <nanovg.h>
@@ -89,6 +90,7 @@ struct Window {
void setFullScreen(bool fullScreen);
bool isFullScreen();
bool isFrameOverdue();
double getMonitorRefreshRate();

std::shared_ptr<Font> loadFont(const std::string& filename);
std::shared_ptr<Image> loadImage(const std::string& filename);


+ 66
- 0
res/ComponentLibrary/LEDSliderHandle.svg View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1.52414mm"
height="4.1423802mm"
viewBox="0 0 1.52414 4.1423802"
version="1.1"
id="svg164153"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="LEDSliderHandle.svg">
<defs
id="defs164147" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="7.9195959"
inkscape:cx="-23.101371"
inkscape:cy="2.1230781"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1600"
inkscape:window-height="882"
inkscape:window-x="0"
inkscape:window-y="18"
inkscape:window-maximized="0" />
<metadata
id="metadata164150">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-118.96512,-95.791547)">
<path
style="fill:#5c5c5c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 120.48926,95.791547 v 4.14238 h -1.52414 v -4.14238 z m 0,0"
id="path162650"
inkscape:connector-curvature="0" />
</g>
</svg>

+ 71
- 0
res/ComponentLibrary/LEDSliderHorizontal.svg View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="27mm"
height="7mm"
viewBox="0 0 27 7.0000001"
version="1.1"
id="svg163547"
sodipodi:docname="LEDSliderHorizontal.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<defs
id="defs163541" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="197.63783"
inkscape:cy="37.645666"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="2"
fit-margin-right="2"
fit-margin-bottom="0"
inkscape:window-width="1600"
inkscape:window-height="882"
inkscape:window-x="0"
inkscape:window-y="18"
inkscape:window-maximized="0"
inkscape:snap-bbox="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-page="true"
inkscape:snap-nodes="false"
inkscape:snap-others="false" />
<metadata
id="metadata163544">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-105.35713,-115.26781)">
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 132.35708,120.26782 h -26.9999 v -3.00002 h 26.9999 z m 0,0"
id="path159840"
inkscape:connector-curvature="0" />
</g>
</svg>

+ 66
- 0
res/ComponentLibrary/LEDSliderHorizontalHandle.svg View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="4.1423802mm"
height="1.52414mm"
viewBox="0 0 4.1423802 1.52414"
version="1.1"
id="svg164153"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="LEDSliderHandleHorizontal.svg">
<defs
id="defs164147" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="7.9195959"
inkscape:cx="48.871998"
inkscape:cy="-10.251291"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1600"
inkscape:window-height="882"
inkscape:window-x="0"
inkscape:window-y="18"
inkscape:window-maximized="0" />
<metadata
id="metadata164150">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-118.96512,-98.409787)">
<path
style="fill:#5c5c5c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 123.1075,99.933583 h -4.14238 v -1.52414 h 4.14238 z m 0,0"
id="path162650"
inkscape:connector-curvature="0" />
</g>
</svg>

+ 2
- 2
src/app/LightWidget.cpp View File

@@ -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);


+ 30
- 1
src/app/MenuBar.cpp View File

@@ -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;


+ 1
- 2
src/app/ModuleBrowser.cpp View File

@@ -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;
}



+ 3
- 0
src/app/ParamWidget.cpp View File

@@ -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());
}
};



+ 14
- 1
src/app/RackScrollWidget.cpp View File

@@ -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) {


+ 74
- 91
src/app/Scene.cpp View File

@@ -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);
}
}
}


+ 12
- 1
src/asset.cpp View File

@@ -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


+ 31
- 19
src/core/MIDI_CV.cpp View File

@@ -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) {


+ 4
- 2
src/core/MIDI_Map.cpp View File

@@ -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) {


+ 1
- 0
src/dep.cpp View File

@@ -1,6 +1,7 @@
// This source file compiles those annoying implementation-in-header libraries

#define GLEW_STATIC
#define GLEW_NO_GLU
#include <GL/glew.h>

#include <nanovg.h>


+ 11
- 5
src/engine/Engine.cpp View File

@@ -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<std::recursive_mutex> 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;


+ 6
- 6
src/main.cpp View File

@@ -17,6 +17,7 @@
#include <system.hpp>
#include <string.hpp>
#include <updater.hpp>
#include <network.hpp>

#include <osdialog.h>
#include <thread>
@@ -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();


+ 7
- 0
src/network.cpp View File

@@ -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;


+ 10
- 8
src/patch.cpp View File

@@ -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


+ 1
- 1
src/plugin.cpp View File

@@ -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()));
}


+ 5
- 12
src/settings.cpp View File

@@ -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)


+ 16
- 7
src/updater.cpp View File

@@ -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();


+ 24
- 20
src/window.cpp View File

@@ -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<double>(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<Font> Window::loadFont(const std::string& filename) {


Loading…
Cancel
Save