Browse Source

Merge branch 'master' of https://github.com/VCVRack/Rack

tags/v0.6.0
Andrew Belt 7 years ago
parent
commit
210bb70ed9
20 changed files with 508 additions and 332 deletions
  1. +1
    -0
      .gitignore
  2. +11
    -14
      Makefile
  3. +2
    -2
      README.md
  4. +5
    -0
      dep.mk
  5. +1
    -1
      include/ui.hpp
  6. +33
    -22
      include/util/common.hpp
  7. +3
    -3
      plugin.mk
  8. +132
    -38
      src/Core/QuadMIDIToCVInterface.cpp
  9. +2
    -2
      src/app/ModuleBrowser.cpp
  10. +1
    -0
      src/app/ModuleWidget.cpp
  11. +7
    -2
      src/app/PluginManagerWidget.cpp
  12. +1
    -40
      src/app/RackScene.cpp
  13. +13
    -11
      src/app/RackWidget.cpp
  14. +2
    -2
      src/asset.cpp
  15. +5
    -10
      src/main.cpp
  16. +175
    -155
      src/plugin.cpp
  17. +34
    -17
      src/util/logger.cpp
  18. +12
    -8
      src/util/string.cpp
  19. +67
    -4
      src/util/system.cpp
  20. +1
    -1
      src/window.cpp

+ 1
- 0
.gitignore View File

@@ -1,5 +1,6 @@
/Rack /Rack
/Rack.exe /Rack.exe
/Rack.res
/libRack.a /libRack.a
/autosave.json /autosave.json
/settings.json /settings.json


+ 11
- 14
Makefile View File

@@ -22,7 +22,7 @@ ifeq ($(ARCH), lin)
-lpthread -lGL -ldl \ -lpthread -lGL -ldl \
$(shell pkg-config --libs gtk+-2.0) \ $(shell pkg-config --libs gtk+-2.0) \
-Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl -Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl
TARGET = Rack
TARGET := Rack
endif endif


ifeq ($(ARCH), mac) ifeq ($(ARCH), mac)
@@ -31,8 +31,8 @@ ifeq ($(ARCH), mac)
LDFLAGS += -stdlib=libc++ -lpthread -ldl \ LDFLAGS += -stdlib=libc++ -lpthread -ldl \
-framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo \ -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo \
-Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl -Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl
TARGET = Rack
BUNDLE = dist/$(TARGET).app
TARGET := Rack
BUNDLE := dist/$(TARGET).app
endif endif


ifeq ($(ARCH), win) ifeq ($(ARCH), win)
@@ -42,8 +42,8 @@ ifeq ($(ARCH), win)
-lgdi32 -lopengl32 -lcomdlg32 -lole32 \ -lgdi32 -lopengl32 -lcomdlg32 -lole32 \
-Ldep/lib -lglew32 -lglfw3dll -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl \ -Ldep/lib -lglew32 -lglfw3dll -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl \
-Wl,-Bstatic -ljansson -lspeexdsp -Wl,-Bstatic -ljansson -lspeexdsp
TARGET = Rack.exe
OBJECTS = Rack.res
TARGET := Rack.exe
OBJECTS += Rack.res
endif endif




@@ -132,8 +132,7 @@ ifeq ($(ARCH), mac)


otool -L $(BUNDLE)/Contents/MacOS/$(TARGET) otool -L $(BUNDLE)/Contents/MacOS/$(TARGET)


mkdir -p $(BUNDLE)/Contents/Resources/plugins
cp -R plugins/Fundamental/dist/Fundamental $(BUNDLE)/Contents/Resources/plugins
cp plugins/Fundamental/dist/Fundamental-*.zip $(BUNDLE)/Contents/Resources/Fundamental.zip
# Make DMG image # Make DMG image
cd dist && ln -s /Applications Applications cd dist && ln -s /Applications Applications
cd dist && hdiutil create -srcfolder . -volname Rack -ov -format UDZO Rack-$(VERSION)-$(ARCH).dmg cd dist && hdiutil create -srcfolder . -volname Rack -ov -format UDZO Rack-$(VERSION)-$(ARCH).dmg
@@ -157,8 +156,7 @@ ifeq ($(ARCH), win)
cp dep/bin/librtaudio.dll dist/Rack/ cp dep/bin/librtaudio.dll dist/Rack/
cp dep/bin/libcrypto-1_1-x64.dll dist/Rack/ cp dep/bin/libcrypto-1_1-x64.dll dist/Rack/
cp dep/bin/libssl-1_1-x64.dll dist/Rack/ cp dep/bin/libssl-1_1-x64.dll dist/Rack/
mkdir -p dist/Rack/plugins
cp -R plugins/Fundamental/dist/Fundamental dist/Rack/plugins/
cp plugins/Fundamental/dist/Fundamental-*.zip dist/Rack/Fundamental.zip
# Make ZIP # Make ZIP
cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack
# Make NSIS installer # Make NSIS installer
@@ -180,17 +178,16 @@ ifeq ($(ARCH), lin)
cp dep/lib/librtmidi.so.4 dist/Rack/ cp dep/lib/librtmidi.so.4 dist/Rack/
cp dep/lib/libssl.so.1.1 dist/Rack/ cp dep/lib/libssl.so.1.1 dist/Rack/
cp dep/lib/libcrypto.so.1.1 dist/Rack/ cp dep/lib/libcrypto.so.1.1 dist/Rack/
mkdir -p dist/Rack/plugins
cp -R plugins/Fundamental/dist/Fundamental dist/Rack/plugins/
cp plugins/Fundamental/dist/Fundamental-*.zip dist/Rack/Fundamental.zip
# Make ZIP # Make ZIP
cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack
endif endif


# Rack SDK distribution # Rack SDK distribution
mkdir -p dist/Rack-SDK mkdir -p dist/Rack-SDK
cp -R LICENSE* res dist/Rack-SDK/
cp -R include dist/Rack-SDK/
cp LICENSE* dist/Rack-SDK/
cp *.mk dist/Rack-SDK/ cp *.mk dist/Rack-SDK/
cp -R include dist/Rack-SDK/
mkdir -p dist/Rack-SDK/dep/ mkdir -p dist/Rack-SDK/dep/
cp -R dep/include dist/Rack-SDK/dep/ cp -R dep/include dist/Rack-SDK/dep/
ifeq ($(ARCH), win) ifeq ($(ARCH), win)
@@ -200,7 +197,7 @@ endif




# Obviously this will only work if you have the private keys to my server # Obviously this will only work if you have the private keys to my server
UPLOAD_URL = vortico@vcvrack.com:files/
UPLOAD_URL := vortico@vcvrack.com:files/
upload: dist distplugins upload: dist distplugins
ifeq ($(ARCH), mac) ifeq ($(ARCH), mac)
rsync dist/*.dmg $(UPLOAD_URL) -zP rsync dist/*.dmg $(UPLOAD_URL) -zP


+ 2
- 2
README.md View File

@@ -38,7 +38,7 @@ pacman -S git make tar unzip mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake autocon


On Arch Linux: On Arch Linux:
``` ```
pacman -S git gcc make cmake tar unzip curl
pacman -S git gcc make cmake tar unzip zip curl
``` ```


Other distro build instructions coming soon. Other distro build instructions coming soon.
@@ -51,7 +51,7 @@ Clone this repository with `git clone https://github.com/VCVRack/Rack.git` and `
Make sure there are no spaces in your path, as this breaks many build systems. Make sure there are no spaces in your path, as this breaks many build systems.


The `master` branch contains the latest public code and breaks its plugin [API](https://en.wikipedia.org/wiki/Application_programming_interface) and [ABI](https://en.wikipedia.org/wiki/Application_binary_interface) frequently. The `master` branch contains the latest public code and breaks its plugin [API](https://en.wikipedia.org/wiki/Application_programming_interface) and [ABI](https://en.wikipedia.org/wiki/Application_binary_interface) frequently.
If you wish to build a previous version of Rack which is API/ABI-compatible with an official Rack release, check out the desired branch with `git checkout v0.5` for example.
If you wish to build a version of Rack which is API/ABI-compatible with an official Rack release, check out the desired branch with `git checkout v0.5` for example.


Clone submodules. Clone submodules.




+ 5
- 0
dep.mk View File

@@ -28,5 +28,10 @@ $(DEPS): export CXXFLAGS = $(DEP_CXXFLAGS)
$(DEPS): export LDFLAGS = $(DEP_LDFLAGS) $(DEPS): export LDFLAGS = $(DEP_LDFLAGS)


dep: $(DEPS) dep: $(DEPS)
@echo
@echo "#######################################"
@echo "# All dependencies built successfully #"
@echo "#######################################"
@echo


.PHONY: dep .PHONY: dep

+ 1
- 1
include/ui.hpp View File

@@ -14,7 +14,7 @@ namespace rack {
//////////////////// ////////////////////


/** Positions children in a row/column based on their widths/heights */ /** Positions children in a row/column based on their widths/heights */
struct SequentialLayout : virtual Widget {
struct SequentialLayout : VirtualWidget {
enum Orientation { enum Orientation {
HORIZONTAL_ORIENTATION, HORIZONTAL_ORIENTATION,
VERTICAL_ORIENTATION, VERTICAL_ORIENTATION,


+ 33
- 22
include/util/common.hpp View File

@@ -8,6 +8,7 @@
#include <assert.h> #include <assert.h>


#include <string> #include <string>
#include <vector>
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>


@@ -15,23 +16,28 @@
// Handy macros // Handy macros
//////////////////// ////////////////////


/** Surrounds raw text with quotes
/** Concatenates two literals or two macros
Example: Example:
printf("Hello " STRINGIFY(world))
will expand to
printf("Hello " "world")
and of course the C++ lexer/parser will then concatenate the string literals
#define COUNT 42
CONCAT(myVariable, COUNT)
expands to
myVariable42
*/ */
#define STRINGIFY(x) #x
/** Converts a macro to a string literal
#define CONCAT_LITERAL(x, y) x ## y
#define CONCAT(x, y) CONCAT_LITERAL(x, y)

/** Surrounds raw text with quotes
Example: Example:
#define NAME "world" #define NAME "world"
printf("Hello " TOSTRING(NAME)) printf("Hello " TOSTRING(NAME))
will expand to
expands to
printf("Hello " "world") printf("Hello " "world")
and of course the C++ lexer/parser then concatenates the string literals.
*/ */
#define TOSTRING(x) STRINGIFY(x)
#define TOSTRING_LITERAL(x) #x
#define TOSTRING(x) TOSTRING_LITERAL(x)


/** Produces the length of a static array in number of elements */
#define LENGTHOF(arr) (sizeof(arr) / sizeof((arr)[0])) #define LENGTHOF(arr) (sizeof(arr) / sizeof((arr)[0]))


/** Reserve space for `count` enums starting with `name`. /** Reserve space for `count` enums starting with `name`.
@@ -95,10 +101,7 @@ DeferWrapper<F> deferWrapper(F f) {
return DeferWrapper<F>(f); return DeferWrapper<F>(f);
} }


#define DEFER_1(x, y) x##y
#define DEFER_2(x, y) DEFER_1(x, y)
#define DEFER_3(x) DEFER_2(x, __COUNTER__)
#define defer(code) auto DEFER_3(_defer_) = deferWrapper([&]() code)
#define defer(code) auto CONCAT(x, __COUNTER__) = deferWrapper([&]() code)


//////////////////// ////////////////////
// Random number generator // Random number generator
@@ -123,34 +126,42 @@ inline float DEPRECATED randomf() {return randomUniform();}


/** Converts a printf format string and optional arguments into a std::string */ /** Converts a printf format string and optional arguments into a std::string */
std::string stringf(const char *format, ...); std::string stringf(const char *format, ...);
std::string lowercase(std::string s);
std::string uppercase(std::string s);
std::string stringLowercase(std::string s);
std::string stringUppercase(std::string s);


/** Truncates and adds "..." to a string, not exceeding `len` characters */ /** Truncates and adds "..." to a string, not exceeding `len` characters */
std::string ellipsize(std::string s, size_t len);
bool startsWith(std::string str, std::string prefix);
std::string stringEllipsize(std::string s, size_t len);
bool stringStartsWith(std::string str, std::string prefix);
bool stringEndsWith(std::string str, std::string suffix);


std::string extractDirectory(std::string path);
std::string extractFilename(std::string path);
std::string extractExtension(std::string path);
/** Extracts portions of a path */
std::string stringDirectory(std::string path);
std::string stringFilename(std::string path);
std::string stringExtension(std::string path);


//////////////////// ////////////////////
// Operating-system specific utilities // Operating-system specific utilities
// system.cpp // system.cpp
//////////////////// ////////////////////


std::vector<std::string> systemListEntries(std::string path);
bool systemIsFile(std::string path);
bool systemIsDirectory(std::string path);
void systemCopy(std::string srcPath, std::string destPath);

/** Opens a URL, also happens to work with PDFs and folders. /** Opens a URL, also happens to work with PDFs and folders.
Shell injection is possible, so make sure the URL is trusted or hard coded. Shell injection is possible, so make sure the URL is trusted or hard coded.
May block, so open in a new thread. May block, so open in a new thread.
*/ */
void openBrowser(std::string url);
void systemOpenBrowser(std::string url);


//////////////////// ////////////////////
// Debug logger // Debug logger
// logger.cpp // logger.cpp
//////////////////// ////////////////////


extern FILE *gLogFile;
void loggerInit();
void loggerDestroy();
void debug(const char *format, ...); void debug(const char *format, ...);
void info(const char *format, ...); void info(const char *format, ...);
void warn(const char *format, ...); void warn(const char *format, ...);


+ 3
- 3
plugin.mk View File

@@ -15,17 +15,17 @@ include $(RACK_DIR)/arch.mk


ifeq ($(ARCH), lin) ifeq ($(ARCH), lin)
LDFLAGS += -shared LDFLAGS += -shared
TARGET = plugin.so
TARGET := plugin.so
endif endif


ifeq ($(ARCH), mac) ifeq ($(ARCH), mac)
LDFLAGS += -shared -undefined dynamic_lookup LDFLAGS += -shared -undefined dynamic_lookup
TARGET = plugin.dylib
TARGET := plugin.dylib
endif endif


ifeq ($(ARCH), win) ifeq ($(ARCH), win)
LDFLAGS += -shared -L$(RACK_DIR) -lRack LDFLAGS += -shared -L$(RACK_DIR) -lRack
TARGET = plugin.dll
TARGET := plugin.dll
endif endif






+ 132
- 38
src/Core/QuadMIDIToCVInterface.cpp View File

@@ -1,5 +1,6 @@
#include "Core.hpp" #include "Core.hpp"
#include "midi.hpp" #include "midi.hpp"
#include "dsp/digital.hpp"


#include <algorithm> #include <algorithm>


@@ -26,6 +27,9 @@ struct QuadMIDIToCVInterface : Module {


enum PolyMode { enum PolyMode {
ROTATE_MODE, ROTATE_MODE,
// Added REUSE option that reuses a channel when receiving the same note.
// Good when using sustain pedal so it doesn't "stack" unisons ... not sure this is the best name but it is descriptive...
REUSE_MODE,
RESET_MODE, RESET_MODE,
REASSIGN_MODE, REASSIGN_MODE,
UNISON_MODE, UNISON_MODE,
@@ -39,13 +43,17 @@ struct QuadMIDIToCVInterface : Module {
}; };


NoteData noteData[128]; NoteData noteData[128];
std::vector<uint8_t> heldNotes;
// cachedNotes : UNISON_MODE and REASSIGN_MODE cache all played notes. The other polyModes cache stolen notes (after the 4th one).
std::vector<uint8_t> cachedNotes;
uint8_t notes[4]; uint8_t notes[4];
bool gates[4]; bool gates[4];
// gates set to TRUE by pedal and current gate. FALSE by pedal.
bool pedalgates[4];
bool pedal; bool pedal;
int rotateIndex; int rotateIndex;
int stealIndex;


QuadMIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS), heldNotes(128) {
QuadMIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS), cachedNotes(128) {
onReset(); onReset();
} }


@@ -70,71 +78,107 @@ struct QuadMIDIToCVInterface : Module {
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
notes[i] = 60; notes[i] = 60;
gates[i] = false; gates[i] = false;
pedalgates[i] = false;
} }
pedal = false; pedal = false;
rotateIndex = 0;
rotateIndex = -1;
cachedNotes.clear();
} }


void pressNote(uint8_t note) {
// 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);
int getPolyIndex(int nowIndex) {
for (int i = 0; i < 4; i++) {
nowIndex++;
if (nowIndex > 3)
nowIndex = 0;
if (!(gates[nowIndex] || pedalgates[nowIndex])) {
stealIndex = nowIndex;
return nowIndex;
}
}
// All taken = steal (stealIndex always rotates)
stealIndex++;
if (stealIndex > 3)
stealIndex = 0;
if ((polyMode < REASSIGN_MODE) && (gates[stealIndex]))
cachedNotes.push_back(notes[stealIndex]);
return stealIndex;
}


void pressNote(uint8_t note) {
// Set notes and gates // Set notes and gates
switch (polyMode) { switch (polyMode) {
case ROTATE_MODE: { case ROTATE_MODE: {
rotateIndex = getPolyIndex(rotateIndex);
} break; } break;


case RESET_MODE: {
case REUSE_MODE: {
bool reuse = false;
for (int i = 0; i < 4; i++) {
if (notes[i] == note) {
rotateIndex = i;
reuse = true;
break;
}
}
if (!reuse)
rotateIndex = getPolyIndex(rotateIndex);
} break;


case RESET_MODE: {
rotateIndex = getPolyIndex(-1);
} break; } break;


case REASSIGN_MODE: { case REASSIGN_MODE: {

cachedNotes.push_back(note);
rotateIndex = getPolyIndex(-1);
} break; } break;


case UNISON_MODE: { case UNISON_MODE: {
cachedNotes.push_back(note);
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
notes[i] = note; notes[i] = note;
gates[i] = true; gates[i] = true;
pedalgates[i] = pedal;
// reTrigger[i].trigger(1e-3);
} }
return;
} break; } break;


default: break; default: break;
} }
// Set notes and gates
// if (gates[rotateIndex] || pedalgates[rotateIndex])
// reTrigger[rotateIndex].trigger(1e-3);
notes[rotateIndex] = note;
gates[rotateIndex] = true;
pedalgates[rotateIndex] = pedal;
} }


void releaseNote(uint8_t note) { void releaseNote(uint8_t note) {
// Remove the note // Remove the note
auto it = std::find(heldNotes.begin(), heldNotes.end(), note);
if (it != heldNotes.end())
heldNotes.erase(it);
// Hold note if pedal is pressed
if (pedal)
return;
// Set last note
switch (polyMode) {
case ROTATE_MODE: {

} break;

case RESET_MODE: {

} break;
auto it = std::find(cachedNotes.begin(), cachedNotes.end(), note);
if (it != cachedNotes.end())
cachedNotes.erase(it);


switch (polyMode) {
case REASSIGN_MODE: { case REASSIGN_MODE: {

for (int i = 0; i < 4; i++) {
if (i < (int) cachedNotes.size()) {
if (!pedalgates[i])
notes[i] = cachedNotes[i];
pedalgates[i] = pedal;
}
else {
gates[i] = false;
}
}
} break; } break;


case UNISON_MODE: { case UNISON_MODE: {
if (!heldNotes.empty()) {
auto it2 = heldNotes.end();
it2--;
if (!cachedNotes.empty()) {
uint8_t backnote = cachedNotes.back();
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
notes[i] = *it2;
notes[i] = backnote;
gates[i] = true; gates[i] = true;
} }
} }
@@ -145,18 +189,57 @@ struct QuadMIDIToCVInterface : Module {
} }
} break; } break;


default: break;
// default ROTATE_MODE REUSE_MODE RESET_MODE
default: {
for (int i = 0; i < 4; i++) {
if (notes[i] == note) {
if (pedalgates[i]) {
gates[i] = false;
}
else if (!cachedNotes.empty()) {
notes[i] = cachedNotes.back();
cachedNotes.pop_back();
}
else {
gates[i] = false;
}
}
}
} break;
} }

} }


void pressPedal() { void pressPedal() {
pedal = true; pedal = true;
for (int i = 0; i < 4; i++) {
pedalgates[i] = gates[i];
}
} }


void releasePedal() { void releasePedal() {
pedal = false; pedal = false;
releaseNote(255);
// When pedal is off, recover notes for pressed keys (if any) after they were already being "cycled" out by pedal-sustained notes.
for (int i = 0; i < 4; i++) {
pedalgates[i] = false;
if (!cachedNotes.empty()) {
if (polyMode < REASSIGN_MODE) {
notes[i] = cachedNotes.back();
cachedNotes.pop_back();
gates[i] = true;
}
}
}
if (polyMode == REASSIGN_MODE) {
for (int i = 0; i < 4; i++) {
if (i < (int) cachedNotes.size()) {
notes[i] = cachedNotes[i];
gates[i] = true;
}
else {
gates[i] = false;
}
}
}
} }


void step() override { void step() override {
@@ -167,14 +250,19 @@ struct QuadMIDIToCVInterface : Module {


for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
uint8_t lastNote = notes[i]; uint8_t lastNote = notes[i];
uint8_t lastGate = (gates[i] || pedalgates[i]);
outputs[CV_OUTPUT + i].value = (lastNote - 60) / 12.f; outputs[CV_OUTPUT + i].value = (lastNote - 60) / 12.f;
outputs[GATE_OUTPUT + i].value = gates[i] ? 10.f : 0.f;
outputs[GATE_OUTPUT + i].value = lastGate ? 10.f : 0.f;
outputs[VELOCITY_OUTPUT + i].value = rescale(noteData[lastNote].velocity, 0, 127, 0.f, 10.f); outputs[VELOCITY_OUTPUT + i].value = rescale(noteData[lastNote].velocity, 0, 127, 0.f, 10.f);
outputs[VELOCITY_OUTPUT + i].value = rescale(noteData[lastNote].aftertouch, 0, 127, 0.f, 10.f);
outputs[AFTERTOUCH_OUTPUT + i].value = rescale(noteData[lastNote].aftertouch, 0, 127, 0.f, 10.f);
} }
} }


void processMessage(MidiMessage msg) { void processMessage(MidiMessage msg) {
// filter MIDI channel
if ((midiInput.channel > -1) && (midiInput.channel != msg.channel()))
return;

switch (msg.status()) { switch (msg.status()) {
// note off // note off
case 0x8: { case 0x8: {
@@ -262,7 +350,13 @@ struct QuadMIDIToCVInterfaceWidget : ModuleWidget {


menu->addChild(MenuEntry::create()); menu->addChild(MenuEntry::create());
menu->addChild(MenuLabel::create("Polyphony mode")); menu->addChild(MenuLabel::create("Polyphony mode"));
std::vector<std::string> polyModeNames = {"Rotate", "Reset", "Reassign", "Unison"};
std::vector<std::string> polyModeNames = {
"Rotate",
"Reuse",
"Reset",
"Reassign",
"Unison"
};
for (int i = 0; i < QuadMIDIToCVInterface::NUM_MODES; i++) { for (int i = 0; i < QuadMIDIToCVInterface::NUM_MODES; i++) {
PolyphonyItem *item = MenuItem::create<PolyphonyItem>(polyModeNames[i], CHECKMARK(module->polyMode == i)); PolyphonyItem *item = MenuItem::create<PolyphonyItem>(polyModeNames[i], CHECKMARK(module->polyMode == i));
item->module = module; item->module = module;


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

@@ -18,8 +18,8 @@ static ModelTag sTagFilter = NO_TAG;




bool isMatch(std::string s, std::string search) { bool isMatch(std::string s, std::string search) {
s = lowercase(s);
search = lowercase(search);
s = stringLowercase(s);
search = stringLowercase(search);
return (s.find(search) != std::string::npos); return (s.find(search) != std::string::npos);
} }




+ 1
- 0
src/app/ModuleWidget.cpp View File

@@ -114,6 +114,7 @@ void ModuleWidget::fromJson(json_t *rootJ) {
json_t *paramJ; json_t *paramJ;
json_array_foreach(paramsJ, i, paramJ) { json_array_foreach(paramsJ, i, paramJ) {
if (legacy && legacy <= 1) { if (legacy && legacy <= 1) {
// Legacy 1 mode
// The index in the array we're iterating is the index of the ParamWidget in the params vector. // The index in the array we're iterating is the index of the ParamWidget in the params vector.
if (i < params.size()) { if (i < params.size()) {
// Create upgraded version of param JSON object // Create upgraded version of param JSON object


+ 7
- 2
src/app/PluginManagerWidget.cpp View File

@@ -53,6 +53,7 @@ struct SyncButton : Button {




PluginManagerWidget::PluginManagerWidget() { PluginManagerWidget::PluginManagerWidget() {
box.size.y = BND_WIDGET_HEIGHT; box.size.y = BND_WIDGET_HEIGHT;
float margin = 5; float margin = 5;


@@ -62,7 +63,9 @@ PluginManagerWidget::PluginManagerWidget() {


struct RegisterButton : Button { struct RegisterButton : Button {
void onAction(EventAction &e) override { void onAction(EventAction &e) override {
std::thread t(openBrowser, "https://vcvrack.com/");
std::thread t([&]() {
systemOpenBrowser("https://vcvrack.com/");
});
t.detach(); t.detach();
} }
}; };
@@ -126,7 +129,9 @@ PluginManagerWidget::PluginManagerWidget() {


struct ManageButton : Button { struct ManageButton : Button {
void onAction(EventAction &e) override { void onAction(EventAction &e) override {
std::thread t(openBrowser, "https://vcvrack.com/");
std::thread t([&]() {
systemOpenBrowser("https://vcvrack.com/");
});
t.detach(); t.detach();
} }
}; };


+ 1
- 40
src/app/RackScene.cpp View File

@@ -1,7 +1,6 @@
#include "app.hpp" #include "app.hpp"
#include "window.hpp" #include "window.hpp"
#include "util/request.hpp" #include "util/request.hpp"
#include "osdialog.h"
#include <string.h> #include <string.h>
#include <thread> #include <thread>


@@ -9,27 +8,6 @@
namespace rack { namespace rack {




static std::string newVersion = "";


#if defined(RELEASE)
static void checkVersion() {
json_t *resJ = requestJson(METHOD_GET, gApiHost + "/version", NULL);

if (resJ) {
json_t *versionJ = json_object_get(resJ, "version");
if (versionJ) {
const char *version = json_string_value(versionJ);
if (version && strlen(version) > 0 && version != gApplicationVersion) {
newVersion = version;
}
}
json_decref(resJ);
}
}
#endif


RackScene::RackScene() { RackScene::RackScene() {
scrollWidget = new RackScrollWidget(); scrollWidget = new RackScrollWidget();
{ {
@@ -46,12 +24,6 @@ RackScene::RackScene() {
gToolbar = new Toolbar(); gToolbar = new Toolbar();
addChild(gToolbar); addChild(gToolbar);
scrollWidget->box.pos.y = gToolbar->box.size.y; scrollWidget->box.pos.y = gToolbar->box.size.y;

// Check for new version
#if defined(RELEASE)
std::thread versionThread(checkVersion);
versionThread.detach();
#endif
} }


void RackScene::step() { void RackScene::step() {
@@ -68,17 +40,6 @@ void RackScene::step() {
Scene::step(); Scene::step();


zoomWidget->box.size = gRackWidget->box.size.mult(zoomWidget->zoom); zoomWidget->box.size = gRackWidget->box.size.mult(zoomWidget->zoom);

// Version popup message
if (!newVersion.empty()) {
std::string versionMessage = stringf("Rack %s is available.\n\nYou have Rack %s.\n\nWould you like to download the new version on the website?", newVersion.c_str(), gApplicationVersion.c_str());
if (osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, versionMessage.c_str())) {
std::thread t(openBrowser, "https://vcvrack.com/");
t.detach();
windowClose();
}
newVersion = "";
}
} }


void RackScene::draw(NVGcontext *vg) { void RackScene::draw(NVGcontext *vg) {
@@ -129,7 +90,7 @@ void RackScene::onHoverKey(EventHoverKey &e) {
void RackScene::onPathDrop(EventPathDrop &e) { void RackScene::onPathDrop(EventPathDrop &e) {
if (e.paths.size() >= 1) { if (e.paths.size() >= 1) {
const std::string& firstPath = e.paths.front(); const std::string& firstPath = e.paths.front();
if (extractExtension(firstPath) == "vcv") {
if (stringExtension(firstPath) == "vcv") {
gRackWidget->loadPatch(firstPath); gRackWidget->loadPatch(firstPath);
e.consumed = true; e.consumed = true;
} }


+ 13
- 11
src/app/RackWidget.cpp View File

@@ -69,7 +69,7 @@ void RackWidget::reset() {
} }


void RackWidget::openDialog() { void RackWidget::openDialog() {
std::string dir = lastPath.empty() ? assetLocal("") : extractDirectory(lastPath);
std::string dir = lastPath.empty() ? assetLocal("") : stringDirectory(lastPath);
char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, NULL); char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, NULL);
if (path) { if (path) {
loadPatch(path); loadPatch(path);
@@ -88,13 +88,13 @@ void RackWidget::saveDialog() {
} }


void RackWidget::saveAsDialog() { void RackWidget::saveAsDialog() {
std::string dir = lastPath.empty() ? assetLocal("") : extractDirectory(lastPath);
std::string dir = lastPath.empty() ? assetLocal("") : stringDirectory(lastPath);
char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), "Untitled.vcv", NULL); char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), "Untitled.vcv", NULL);


if (path) { if (path) {
std::string pathStr = path; std::string pathStr = path;
free(path); free(path);
std::string extension = extractExtension(pathStr);
std::string extension = stringExtension(pathStr);
if (extension.empty()) { if (extension.empty()) {
pathStr += ".vcv"; pathStr += ".vcv";
} }
@@ -225,15 +225,14 @@ void RackWidget::fromJson(json_t *rootJ) {
json_t *versionJ = json_object_get(rootJ, "version"); json_t *versionJ = json_object_get(rootJ, "version");
if (versionJ) { if (versionJ) {
version = json_string_value(versionJ); version = json_string_value(versionJ);
if (!version.empty() && gApplicationVersion != version)
message += stringf("This patch was created with Rack %s. Saving it will convert it to a Rack %s patch.\n\n", version.c_str(), gApplicationVersion.c_str());
} }


// Detect old patches with ModuleWidget::params/inputs/outputs indices. // Detect old patches with ModuleWidget::params/inputs/outputs indices.
// (We now use Module::params/inputs/outputs indices.) // (We now use Module::params/inputs/outputs indices.)
int legacy = 0; int legacy = 0;
if (startsWith(version, "0.3.") || startsWith(version, "0.4.") || startsWith(version, "0.5.") || version == "" || version == "dev") {
if (stringStartsWith(version, "0.3.") || stringStartsWith(version, "0.4.") || stringStartsWith(version, "0.5.") || version == "" || version == "dev") {
legacy = 1; legacy = 1;
message += "This patch was created with Rack 0.5 or earlier. Saving it will convert it to a Rack 0.6+ patch.\n\n";
} }
if (legacy) { if (legacy) {
info("Loading patch using legacy mode %d", legacy); info("Loading patch using legacy mode %d", legacy);
@@ -246,9 +245,10 @@ void RackWidget::fromJson(json_t *rootJ) {
size_t moduleId; size_t moduleId;
json_t *moduleJ; json_t *moduleJ;
json_array_foreach(modulesJ, moduleId, moduleJ) { json_array_foreach(modulesJ, moduleId, moduleJ) {
// Set legacy property
if (legacy)
json_object_set_new(moduleJ, "legacy", json_integer(legacy));
// Add "legacy" property if in legacy mode
if (legacy) {
json_object_set(moduleJ, "legacy", json_integer(legacy));
}


json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); json_t *pluginSlugJ = json_object_get(moduleJ, "plugin");
if (!pluginSlugJ) continue; if (!pluginSlugJ) continue;
@@ -259,7 +259,7 @@ void RackWidget::fromJson(json_t *rootJ) {


Model *model = pluginGetModel(pluginSlug, modelSlug); Model *model = pluginGetModel(pluginSlug, modelSlug);
if (!model) { if (!model) {
message += stringf("Could not find module \"%s\" in plugin \"%s\"\n", modelSlug.c_str(), pluginSlug.c_str());
message += stringf("Could not find module \"%s\" of plugin \"%s\"\n", modelSlug.c_str(), pluginSlug.c_str());
continue; continue;
} }


@@ -292,6 +292,8 @@ void RackWidget::fromJson(json_t *rootJ) {
Port *outputPort = NULL; Port *outputPort = NULL;
Port *inputPort = NULL; Port *inputPort = NULL;
if (legacy && legacy <= 1) { if (legacy && legacy <= 1) {
// Legacy 1 mode
// The index of the "ports" array is the index of the Port in the `outputs` and `inputs` vector.
outputPort = outputModuleWidget->outputs[outputId]; outputPort = outputModuleWidget->outputs[outputId];
inputPort = inputModuleWidget->inputs[inputId]; inputPort = inputModuleWidget->inputs[inputId];
} }
@@ -324,7 +326,7 @@ void RackWidget::fromJson(json_t *rootJ) {


// Display a message if we have something to say // Display a message if we have something to say
if (!message.empty()) { if (!message.empty()) {
osdialog_message(OSDIALOG_INFO, OSDIALOG_OK, message.c_str());
osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
} }
} }




+ 2
- 2
src/asset.cpp View File

@@ -25,7 +25,7 @@ namespace rack {


std::string assetGlobal(std::string filename) { std::string assetGlobal(std::string filename) {
std::string dir; std::string dir;
#if defined(RELEASE)
#if RELEASE
#if ARCH_MAC #if ARCH_MAC
CFBundleRef bundle = CFBundleGetMainBundle(); CFBundleRef bundle = CFBundleGetMainBundle();
assert(bundle); assert(bundle);
@@ -53,7 +53,7 @@ std::string assetGlobal(std::string filename) {


std::string assetLocal(std::string filename) { std::string assetLocal(std::string filename) {
std::string dir; std::string dir;
#if defined(RELEASE)
#if RELEASE
#if ARCH_MAC #if ARCH_MAC
// Get home directory // Get home directory
struct passwd *pw = getpwuid(getuid()); struct passwd *pw = getpwuid(getuid());


+ 5
- 10
src/main.cpp View File

@@ -6,19 +6,17 @@
#include "settings.hpp" #include "settings.hpp"
#include "asset.hpp" #include "asset.hpp"
#include "bridge.hpp" #include "bridge.hpp"
#include <unistd.h>
#include "osdialog.h" #include "osdialog.h"


#include <unistd.h>



using namespace rack; using namespace rack;



int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
randomInit(); randomInit();

#ifdef RELEASE
std::string logFilename = assetLocal("log.txt");
gLogFile = fopen(logFilename.c_str(), "w");
#endif
loggerInit();


info("Rack %s", gApplicationVersion.c_str()); info("Rack %s", gApplicationVersion.c_str());


@@ -64,10 +62,7 @@ int main(int argc, char* argv[]) {
bridgeDestroy(); bridgeDestroy();
engineDestroy(); engineDestroy();
pluginDestroy(); pluginDestroy();

#ifdef RELEASE
fclose(gLogFile);
#endif
loggerDestroy();


return 0; return 0;
} }

+ 175
- 155
src/plugin.cpp View File

@@ -1,5 +1,10 @@
#include <stdio.h>
#include "plugin.hpp"
#include "app.hpp"
#include "asset.hpp"
#include "util/request.hpp"
#include "osdialog.h"


#include <stdio.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
@@ -13,7 +18,7 @@
#include <zip.h> #include <zip.h>
#include <jansson.h> #include <jansson.h>


#if defined(ARCH_WIN)
#if ARCH_WIN
#include <windows.h> #include <windows.h>
#include <direct.h> #include <direct.h>
#define mkdir(_dir, _perms) _mkdir(_dir) #define mkdir(_dir, _perms) _mkdir(_dir)
@@ -22,14 +27,10 @@
#endif #endif
#include <dirent.h> #include <dirent.h>


#include "plugin.hpp"
#include "app.hpp"
#include "asset.hpp"
#include "util/request.hpp"



namespace rack { namespace rack {



std::list<Plugin*> gPlugins; std::list<Plugin*> gPlugins;
std::string gToken; std::string gToken;


@@ -52,8 +53,11 @@ void Plugin::addModel(Model *model) {
models.push_back(model); models.push_back(model);
} }


////////////////////
// private API
////////////////////


static int loadPlugin(std::string path) {
static bool loadPlugin(std::string path) {
std::string libraryFilename; std::string libraryFilename;
#if ARCH_LIN #if ARCH_LIN
libraryFilename = path + "/" + "plugin.so"; libraryFilename = path + "/" + "plugin.so";
@@ -63,6 +67,12 @@ static int loadPlugin(std::string path) {
libraryFilename = path + "/" + "plugin.dylib"; libraryFilename = path + "/" + "plugin.dylib";
#endif #endif


// Check file existence
if (!systemIsFile(libraryFilename)) {
warn("Plugin file %s does not exist", libraryFilename.c_str());
return false;
}

// Load dynamic/shared library // Load dynamic/shared library
#if ARCH_WIN #if ARCH_WIN
SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
@@ -70,14 +80,14 @@ static int loadPlugin(std::string path) {
SetErrorMode(0); SetErrorMode(0);
if (!handle) { if (!handle) {
int error = GetLastError(); int error = GetLastError();
warn("Failed to load library %s: %d", libraryFilename.c_str(), error);
return -1;
warn("Failed to load library %s: code %d", libraryFilename.c_str(), error);
return false;
} }
#elif ARCH_LIN || ARCH_MAC
#else
void *handle = dlopen(libraryFilename.c_str(), RTLD_NOW); void *handle = dlopen(libraryFilename.c_str(), RTLD_NOW);
if (!handle) { if (!handle) {
warn("Failed to load library %s: %s", libraryFilename.c_str(), dlerror()); warn("Failed to load library %s: %s", libraryFilename.c_str(), dlerror());
return -1;
return false;
} }
#endif #endif


@@ -86,12 +96,12 @@ static int loadPlugin(std::string path) {
InitCallback initCallback; InitCallback initCallback;
#if ARCH_WIN #if ARCH_WIN
initCallback = (InitCallback) GetProcAddress(handle, "init"); initCallback = (InitCallback) GetProcAddress(handle, "init");
#elif ARCH_LIN || ARCH_MAC
#else
initCallback = (InitCallback) dlsym(handle, "init"); initCallback = (InitCallback) dlsym(handle, "init");
#endif #endif
if (!initCallback) { if (!initCallback) {
warn("Failed to read init() symbol in %s", libraryFilename.c_str()); warn("Failed to read init() symbol in %s", libraryFilename.c_str());
return -2;
return false;
} }


// Construct and initialize Plugin instance // Construct and initialize Plugin instance
@@ -101,94 +111,19 @@ static int loadPlugin(std::string path) {
initCallback(plugin); initCallback(plugin);


// Reject plugin if slug already exists // Reject plugin if slug already exists
for (Plugin *p : gPlugins) {
if (plugin->slug == p->slug) {
warn("Plugin \"%s\" is already loaded, not attempting to load it again", p->slug.c_str());
// TODO
// Fix memory leak with `plugin` here
return -1;
}
Plugin *oldPlugin = pluginGetPlugin(plugin->slug);
if (oldPlugin) {
warn("Plugin \"%s\" is already loaded, not attempting to load it again", plugin->slug.c_str());
// TODO
// Fix memory leak with `plugin` here
return false;
} }


// Add plugin to list // Add plugin to list
gPlugins.push_back(plugin); gPlugins.push_back(plugin);
info("Loaded plugin %s", libraryFilename.c_str()); info("Loaded plugin %s", libraryFilename.c_str());


return 0;
}

static void loadPlugins(std::string path) {
DIR *dir = opendir(path.c_str());
if (dir) {
struct dirent *d;
while ((d = readdir(dir))) {
if (d->d_name[0] == '.')
continue;
loadPlugin(path + "/" + d->d_name);
}
closedir(dir);
}
}

////////////////////
// plugin helpers
////////////////////

static int extractZipHandle(zip_t *za, const char *dir) {
int err = 0;
for (int i = 0; i < zip_get_num_entries(za, 0); i++) {
zip_stat_t zs;
err = zip_stat_index(za, i, 0, &zs);
if (err)
return err;
int nameLen = strlen(zs.name);

char path[MAXPATHLEN];
snprintf(path, sizeof(path), "%s/%s", dir, zs.name);

if (zs.name[nameLen - 1] == '/') {
err = mkdir(path, 0755);
if (err && errno != EEXIST)
return err;
}
else {
zip_file_t *zf = zip_fopen_index(za, i, 0);
if (!zf)
return 1;

FILE *outFile = fopen(path, "wb");
if (!outFile)
continue;

while (1) {
char buffer[4096];
int len = zip_fread(zf, buffer, sizeof(buffer));
if (len <= 0)
break;
fwrite(buffer, 1, len, outFile);
}

err = zip_fclose(zf);
if (err)
return err;
fclose(outFile);
}
}
return 0;
}

static int extractZip(const char *filename, const char *dir) {
int err = 0;
zip_t *za = zip_open(filename, 0, &err);
if (!za)
return 1;

if (!err) {
err = extractZipHandle(za, dir);
}

zip_close(za);
return err;
return true;
} }


static bool syncPlugin(json_t *pluginJ, bool dryRun) { static bool syncPlugin(json_t *pluginJ, bool dryRun) {
@@ -223,11 +158,11 @@ static bool syncPlugin(json_t *pluginJ, bool dryRun) {


json_t *downloadsJ = json_object_get(pluginJ, "downloads"); json_t *downloadsJ = json_object_get(pluginJ, "downloads");
if (downloadsJ) { if (downloadsJ) {
#if defined(ARCH_WIN)
#if ARCH_WIN
#define DOWNLOADS_ARCH "win" #define DOWNLOADS_ARCH "win"
#elif defined(ARCH_MAC)
#elif ARCH_MAC
#define DOWNLOADS_ARCH "mac" #define DOWNLOADS_ARCH "mac"
#elif defined(ARCH_LIN)
#elif ARCH_LIN
#define DOWNLOADS_ARCH "lin" #define DOWNLOADS_ARCH "lin"
#endif #endif
json_t *archJ = json_object_get(downloadsJ, DOWNLOADS_ARCH); json_t *archJ = json_object_get(downloadsJ, DOWNLOADS_ARCH);
@@ -281,19 +216,151 @@ static bool syncPlugin(json_t *pluginJ, bool dryRun) {
} }
} }


// Unzip file
int err = extractZip(zipPath.c_str(), pluginsDir.c_str());
if (!err) {
// Delete zip
remove(zipPath.c_str());
// Load plugin
// loadPlugin(pluginPath);
}

downloadName = ""; downloadName = "";
return true; return true;
} }


static void loadPlugins(std::string path) {
std::string message;
for (std::string pluginPath : systemListEntries(path)) {
if (!systemIsDirectory(pluginPath))
continue;
if (!loadPlugin(pluginPath)) {
message += stringf("Could not load plugin %s\n", pluginPath.c_str());
}
}
if (!message.empty()) {
message += "See log for details.";
osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
}
}

static int extractZipHandle(zip_t *za, const char *dir) {
int err = 0;
for (int i = 0; i < zip_get_num_entries(za, 0); i++) {
zip_stat_t zs;
err = zip_stat_index(za, i, 0, &zs);
if (err)
return err;
int nameLen = strlen(zs.name);

char path[MAXPATHLEN];
snprintf(path, sizeof(path), "%s/%s", dir, zs.name);

if (zs.name[nameLen - 1] == '/') {
err = mkdir(path, 0755);
if (err && errno != EEXIST)
return err;
}
else {
zip_file_t *zf = zip_fopen_index(za, i, 0);
if (!zf)
return 1;

FILE *outFile = fopen(path, "wb");
if (!outFile)
continue;

while (1) {
char buffer[1<<15];
int len = zip_fread(zf, buffer, sizeof(buffer));
if (len <= 0)
break;
fwrite(buffer, 1, len, outFile);
}

err = zip_fclose(zf);
if (err)
return err;
fclose(outFile);
}
}
return 0;
}

static int extractZip(const char *filename, const char *path) {
int err = 0;
zip_t *za = zip_open(filename, 0, &err);
if (!za)
return 1;
defer({
zip_close(za);
});
if (err)
return err;

err = extractZipHandle(za, path);
return err;
}

static void extractPackages(std::string path) {
std::string message;

for (std::string packagePath : systemListEntries(path)) {
if (stringExtension(packagePath) == "zip") {
info("Extracting package %s", packagePath.c_str());
// Extract package
if (extractZip(packagePath.c_str(), path.c_str())) {
message += stringf("Could not extract package %s\n", packagePath.c_str());
continue;
}
// Remove package
remove(packagePath.c_str());
}
}
if (!message.empty()) {
osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
}
}

////////////////////
// public API
////////////////////

void pluginInit() {
tagsInit();

// Load core
// This function is defined in core.cpp
Plugin *corePlugin = new Plugin();
init(corePlugin);
gPlugins.push_back(corePlugin);

// Get local plugins directory
std::string localPlugins = assetLocal("plugins");
mkdir(localPlugins.c_str(), 0755);

#if RELEASE
// Copy Fundamental package to plugins directory if folder does not exist
std::string fundamentalDest = localPlugins + "/Fundamental.zip";
if (!systemIsDirectory(localPlugins + "/Fundamental") && !systemIsFile(fundamentalDest)) {
systemCopy(assetGlobal("Fundamental.zip"), fundamentalDest);
}
#endif

// Extract packages and load plugins
extractPackages(localPlugins);
loadPlugins(localPlugins);
}

void pluginDestroy() {
for (Plugin *plugin : gPlugins) {
// Free library handle
#if ARCH_WIN
if (plugin->handle)
FreeLibrary((HINSTANCE)plugin->handle);
#else
if (plugin->handle)
dlclose(plugin->handle);
#endif

// For some reason this segfaults.
// It might be best to let them leak anyway, because "crash on exit" issues would occur with badly-written plugins.
// delete plugin;
}
gPlugins.clear();
}

bool pluginSync(bool dryRun) { bool pluginSync(bool dryRun) {
if (gToken.empty()) if (gToken.empty())
return false; return false;
@@ -378,51 +445,6 @@ bool pluginSync(bool dryRun) {
return available; return available;
} }


////////////////////
// plugin API
////////////////////

void pluginInit() {
tagsInit();

// TODO
// If `<local>/plugins/Fundamental` doesn't exist, unzip global Fundamental.zip package into `<local>/plugins`

// TODO
// Find all ZIP packages in `<local>/plugins` and unzip them.
// Display error if failure

// Load core
// This function is defined in core.cpp
Plugin *corePlugin = new Plugin();
init(corePlugin);
gPlugins.push_back(corePlugin);

// Load plugins from local directory
std::string localPlugins = assetLocal("plugins");
mkdir(localPlugins.c_str(), 0755);
info("Loading plugins from %s", localPlugins.c_str());
loadPlugins(localPlugins);
}

void pluginDestroy() {
for (Plugin *plugin : gPlugins) {
// Free library handle
#if defined(ARCH_WIN)
if (plugin->handle)
FreeLibrary((HINSTANCE)plugin->handle);
#elif defined(ARCH_LIN) || defined(ARCH_MAC)
if (plugin->handle)
dlclose(plugin->handle);
#endif

// For some reason this segfaults.
// It might be best to let them leak anyway, because "crash on exit" issues would occur with badly-written plugins.
// delete plugin;
}
gPlugins.clear();
}

void pluginLogIn(std::string email, std::string password) { void pluginLogIn(std::string email, std::string password) {
json_t *reqJ = json_object(); json_t *reqJ = json_object();
json_object_set(reqJ, "email", json_string(email.c_str())); json_object_set(reqJ, "email", json_string(email.c_str()));
@@ -498,6 +520,4 @@ Model *pluginGetModel(std::string pluginSlug, std::string modelSlug) {
} }






} // namespace rack } // namespace rack

+ 34
- 17
src/util/logger.cpp View File

@@ -1,49 +1,66 @@
#include "util/common.hpp" #include "util/common.hpp"
#include "asset.hpp"
#include <stdarg.h> #include <stdarg.h>




namespace rack { namespace rack {




FILE *gLogFile = stderr;
static FILE *logFile = stderr;
static auto startTime = std::chrono::high_resolution_clock::now();


void loggerInit() {
#ifdef RELEASE
std::string logFilename = assetLocal("log.txt");
logFile = fopen(logFilename.c_str(), "w");
#endif
}

void loggerDestroy() {
#ifdef RELEASE
fclose(logFile);
#endif
}

static void printTimestamp() {
}

static void printLog(const char *type, const char *format, va_list args) {
auto nowTime = std::chrono::high_resolution_clock::now();
int duration = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - startTime).count();
printTimestamp();
fprintf(logFile, "[%.03f %s] ", duration / 1000.0, type);
vfprintf(logFile, format, args);
fprintf(logFile, "\n");
fflush(logFile);
}


void debug(const char *format, ...) { void debug(const char *format, ...) {
va_list args; va_list args;
va_start(args, format); va_start(args, format);
fprintf(gLogFile, "[debug] ");
vfprintf(gLogFile, format, args);
fprintf(gLogFile, "\n");
fflush(gLogFile);
printLog("debug", format, args);
va_end(args); va_end(args);
} }


void info(const char *format, ...) { void info(const char *format, ...) {
va_list args; va_list args;
va_start(args, format); va_start(args, format);
fprintf(gLogFile, "[info] ");
vfprintf(gLogFile, format, args);
fprintf(gLogFile, "\n");
fflush(gLogFile);
printLog("info", format, args);
va_end(args); va_end(args);
} }


void warn(const char *format, ...) { void warn(const char *format, ...) {
va_list args; va_list args;
va_start(args, format); va_start(args, format);
fprintf(gLogFile, "[warning] ");
vfprintf(gLogFile, format, args);
fprintf(gLogFile, "\n");
fflush(gLogFile);
printLog("warn", format, args);
va_end(args); va_end(args);
} }


void fatal(const char *format, ...) { void fatal(const char *format, ...) {
va_list args; va_list args;
va_start(args, format); va_start(args, format);
fprintf(gLogFile, "[fatal] ");
vfprintf(gLogFile, format, args);
fprintf(gLogFile, "\n");
fflush(gLogFile);
printLog("fatal", format, args);
va_end(args); va_end(args);
} }




+ 12
- 8
src/util/string.cpp View File

@@ -24,43 +24,47 @@ std::string stringf(const char *format, ...) {
return s; return s;
} }


std::string lowercase(std::string s) {
std::string stringLowercase(std::string s) {
std::transform(s.begin(), s.end(), s.begin(), ::tolower); std::transform(s.begin(), s.end(), s.begin(), ::tolower);
return s; return s;
} }


std::string uppercase(std::string s) {
std::string stringUppercase(std::string s) {
std::transform(s.begin(), s.end(), s.begin(), ::toupper); std::transform(s.begin(), s.end(), s.begin(), ::toupper);
return s; return s;
} }


std::string ellipsize(std::string s, size_t len) {
std::string stringEllipsize(std::string s, size_t len) {
if (s.size() <= len) if (s.size() <= len)
return s; return s;
else else
return s.substr(0, len - 3) + "..."; return s.substr(0, len - 3) + "...";
} }


bool startsWith(std::string str, std::string prefix) {
bool stringStartsWith(std::string str, std::string prefix) {
return str.substr(0, prefix.size()) == prefix; return str.substr(0, prefix.size()) == prefix;
} }


std::string extractDirectory(std::string path) {
bool stringEndsWith(std::string str, std::string suffix) {
return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
}

std::string stringDirectory(std::string path) {
char *pathDup = strdup(path.c_str()); char *pathDup = strdup(path.c_str());
std::string directory = dirname(pathDup); std::string directory = dirname(pathDup);
free(pathDup); free(pathDup);
return directory; return directory;
} }


std::string extractFilename(std::string path) {
std::string stringFilename(std::string path) {
char *pathDup = strdup(path.c_str()); char *pathDup = strdup(path.c_str());
std::string filename = basename(pathDup); std::string filename = basename(pathDup);
free(pathDup); free(pathDup);
return filename; return filename;
} }


std::string extractExtension(std::string path) {
const char *ext = strrchr(path.c_str(), '.');
std::string stringExtension(std::string path) {
const char *ext = strrchr(stringFilename(path).c_str(), '.');
if (!ext) if (!ext)
return ""; return "";
return ext + 1; return ext + 1;


+ 67
- 4
src/util/system.cpp View File

@@ -1,18 +1,81 @@
#include "util/common.hpp" #include "util/common.hpp"


#include <dirent.h>
#include <sys/stat.h>

#if ARCH_WIN #if ARCH_WIN
#include <windows.h>
#include <shellapi.h>
#include <windows.h>
#include <shellapi.h>
#endif #endif




namespace rack { namespace rack {




void openBrowser(std::string url) {
std::vector<std::string> systemListEntries(std::string path) {
std::vector<std::string> filenames;
DIR *dir = opendir(path.c_str());
if (dir) {
struct dirent *d;
while ((d = readdir(dir))) {
std::string filename = d->d_name;
if (filename == "." || filename == "..")
continue;
filenames.push_back(path + "/" + filename);
}
closedir(dir);
}
return filenames;
}

bool systemExists(std::string path) {
struct stat statbuf;
return (stat(path.c_str(), &statbuf) == 0);
}

bool systemIsFile(std::string path) {
struct stat statbuf;
if (stat(path.c_str(), &statbuf))
return false;
return S_ISREG(statbuf.st_mode);
}

bool systemIsDirectory(std::string path) {
struct stat statbuf;
if (stat(path.c_str(), &statbuf))
return false;
return S_ISDIR(statbuf.st_mode);
}

void systemCopy(std::string srcPath, std::string destPath) {
// Open files
FILE *source = fopen(srcPath.c_str(), "rb");
if (!source) return;
defer({
fclose(source);
});
FILE *dest = fopen(destPath.c_str(), "wb");
if (!dest) return;
defer({
fclose(dest);
});
// Copy buffer
const int bufferSize = (1<<15);
char buffer[bufferSize];
while (1) {
size_t size = fread(buffer, 1, bufferSize, source);
if (size == 0)
break;
size = fwrite(buffer, 1, size, dest);
if (size == 0)
break;
}
}

void systemOpenBrowser(std::string url) {
#if ARCH_LIN #if ARCH_LIN
std::string command = "xdg-open " + url; std::string command = "xdg-open " + url;
(void)system(command.c_str());
(void) system(command.c_str());
#endif #endif
#if ARCH_MAC #if ARCH_MAC
std::string command = "open " + url; std::string command = "open " + url;


+ 1
- 1
src/window.cpp View File

@@ -439,7 +439,7 @@ void windowRun() {
windowTitle += gApplicationVersion; windowTitle += gApplicationVersion;
if (!gRackWidget->lastPath.empty()) { if (!gRackWidget->lastPath.empty()) {
windowTitle += " - "; windowTitle += " - ";
windowTitle += extractFilename(gRackWidget->lastPath);
windowTitle += stringFilename(gRackWidget->lastPath);
} }
if (windowTitle != lastWindowTitle) { if (windowTitle != lastWindowTitle) {
glfwSetWindowTitle(gWindow, windowTitle.c_str()); glfwSetWindowTitle(gWindow, windowTitle.c_str());


Loading…
Cancel
Save