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.exe
/Rack.res
/libRack.a
/autosave.json
/settings.json


+ 11
- 14
Makefile View File

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

ifeq ($(ARCH), mac)
@@ -31,8 +31,8 @@ ifeq ($(ARCH), mac)
LDFLAGS += -stdlib=libc++ -lpthread -ldl \
-framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo \
-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

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


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

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
cd dist && ln -s /Applications Applications
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/libcrypto-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
cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack
# Make NSIS installer
@@ -180,17 +178,16 @@ ifeq ($(ARCH), lin)
cp dep/lib/librtmidi.so.4 dist/Rack/
cp dep/lib/libssl.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
cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack
endif

# Rack SDK distribution
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 -R include dist/Rack-SDK/
mkdir -p dist/Rack-SDK/dep/
cp -R dep/include dist/Rack-SDK/dep/
ifeq ($(ARCH), win)
@@ -200,7 +197,7 @@ endif


# 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
ifeq ($(ARCH), mac)
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:
```
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.
@@ -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.

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.



+ 5
- 0
dep.mk View File

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

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

.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 */
struct SequentialLayout : virtual Widget {
struct SequentialLayout : VirtualWidget {
enum Orientation {
HORIZONTAL_ORIENTATION,
VERTICAL_ORIENTATION,


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

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

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

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

/** Surrounds raw text with quotes
/** Concatenates two literals or two macros
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:
#define NAME "world"
printf("Hello " TOSTRING(NAME))
will expand to
expands to
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]))

/** Reserve space for `count` enums starting with `name`.
@@ -95,10 +101,7 @@ DeferWrapper<F> 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
@@ -123,34 +126,42 @@ inline float DEPRECATED randomf() {return randomUniform();}

/** Converts a printf format string and optional arguments into a std::string */
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 */
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
// 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.
Shell injection is possible, so make sure the URL is trusted or hard coded.
May block, so open in a new thread.
*/
void openBrowser(std::string url);
void systemOpenBrowser(std::string url);

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

extern FILE *gLogFile;
void loggerInit();
void loggerDestroy();
void debug(const char *format, ...);
void info(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)
LDFLAGS += -shared
TARGET = plugin.so
TARGET := plugin.so
endif

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

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




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

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

#include <algorithm>

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

enum PolyMode {
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,
REASSIGN_MODE,
UNISON_MODE,
@@ -39,13 +43,17 @@ struct QuadMIDIToCVInterface : Module {
};

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];
bool gates[4];
// gates set to TRUE by pedal and current gate. FALSE by pedal.
bool pedalgates[4];
bool pedal;
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();
}

@@ -70,71 +78,107 @@ struct QuadMIDIToCVInterface : Module {
for (int i = 0; i < 4; i++) {
notes[i] = 60;
gates[i] = false;
pedalgates[i] = 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
switch (polyMode) {
case ROTATE_MODE: {
rotateIndex = getPolyIndex(rotateIndex);
} 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;

case REASSIGN_MODE: {

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

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

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;

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++) {
notes[i] = *it2;
notes[i] = backnote;
gates[i] = true;
}
}
@@ -145,18 +189,57 @@ struct QuadMIDIToCVInterface : Module {
}
} 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() {
pedal = true;
for (int i = 0; i < 4; i++) {
pedalgates[i] = gates[i];
}
}

void releasePedal() {
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 {
@@ -167,14 +250,19 @@ struct QuadMIDIToCVInterface : Module {

for (int i = 0; i < 4; i++) {
uint8_t lastNote = notes[i];
uint8_t lastGate = (gates[i] || pedalgates[i]);
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].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) {
// filter MIDI channel
if ((midiInput.channel > -1) && (midiInput.channel != msg.channel()))
return;

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

menu->addChild(MenuEntry::create());
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++) {
PolyphonyItem *item = MenuItem::create<PolyphonyItem>(polyModeNames[i], CHECKMARK(module->polyMode == i));
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) {
s = lowercase(s);
search = lowercase(search);
s = stringLowercase(s);
search = stringLowercase(search);
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_array_foreach(paramsJ, i, paramJ) {
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.
if (i < params.size()) {
// Create upgraded version of param JSON object


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

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


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

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

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

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


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

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

@@ -9,27 +8,6 @@
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() {
scrollWidget = new RackScrollWidget();
{
@@ -46,12 +24,6 @@ RackScene::RackScene() {
gToolbar = new Toolbar();
addChild(gToolbar);
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() {
@@ -68,17 +40,6 @@ void RackScene::step() {
Scene::step();

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


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

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

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);
if (path) {
loadPatch(path);
@@ -88,13 +88,13 @@ void RackWidget::saveDialog() {
}

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

if (path) {
std::string pathStr = path;
free(path);
std::string extension = extractExtension(pathStr);
std::string extension = stringExtension(pathStr);
if (extension.empty()) {
pathStr += ".vcv";
}
@@ -225,15 +225,14 @@ void RackWidget::fromJson(json_t *rootJ) {
json_t *versionJ = json_object_get(rootJ, "version");
if (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.
// (We now use Module::params/inputs/outputs indices.)
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;
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) {
info("Loading patch using legacy mode %d", legacy);
@@ -246,9 +245,10 @@ void RackWidget::fromJson(json_t *rootJ) {
size_t moduleId;
json_t *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");
if (!pluginSlugJ) continue;
@@ -259,7 +259,7 @@ void RackWidget::fromJson(json_t *rootJ) {

Model *model = pluginGetModel(pluginSlug, modelSlug);
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;
}

@@ -292,6 +292,8 @@ void RackWidget::fromJson(json_t *rootJ) {
Port *outputPort = NULL;
Port *inputPort = NULL;
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];
inputPort = inputModuleWidget->inputs[inputId];
}
@@ -324,7 +326,7 @@ void RackWidget::fromJson(json_t *rootJ) {

// Display a message if we have something to say
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 dir;
#if defined(RELEASE)
#if RELEASE
#if ARCH_MAC
CFBundleRef bundle = CFBundleGetMainBundle();
assert(bundle);
@@ -53,7 +53,7 @@ std::string assetGlobal(std::string filename) {

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


+ 5
- 10
src/main.cpp View File

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

#include <unistd.h>


using namespace rack;


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

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

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

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

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

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 <string.h>
#include <unistd.h>
@@ -13,7 +18,7 @@
#include <zip.h>
#include <jansson.h>

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

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


namespace rack {


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

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

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

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

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

// Load dynamic/shared library
#if ARCH_WIN
SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
@@ -70,14 +80,14 @@ static int loadPlugin(std::string path) {
SetErrorMode(0);
if (!handle) {
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);
if (!handle) {
warn("Failed to load library %s: %s", libraryFilename.c_str(), dlerror());
return -1;
return false;
}
#endif

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

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

// 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
gPlugins.push_back(plugin);
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) {
@@ -223,11 +158,11 @@ static bool syncPlugin(json_t *pluginJ, bool dryRun) {

json_t *downloadsJ = json_object_get(pluginJ, "downloads");
if (downloadsJ) {
#if defined(ARCH_WIN)
#if ARCH_WIN
#define DOWNLOADS_ARCH "win"
#elif defined(ARCH_MAC)
#elif ARCH_MAC
#define DOWNLOADS_ARCH "mac"
#elif defined(ARCH_LIN)
#elif ARCH_LIN
#define DOWNLOADS_ARCH "lin"
#endif
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 = "";
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) {
if (gToken.empty())
return false;
@@ -378,51 +445,6 @@ bool pluginSync(bool dryRun) {
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) {
json_t *reqJ = json_object();
json_object_set(reqJ, "email", json_string(email.c_str()));
@@ -498,6 +520,4 @@ Model *pluginGetModel(std::string pluginSlug, std::string modelSlug) {
}




} // namespace rack

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

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


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, ...) {
va_list args;
va_start(args, format);
fprintf(gLogFile, "[debug] ");
vfprintf(gLogFile, format, args);
fprintf(gLogFile, "\n");
fflush(gLogFile);
printLog("debug", format, args);
va_end(args);
}

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

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

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



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

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

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

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

std::string ellipsize(std::string s, size_t len) {
std::string stringEllipsize(std::string s, size_t len) {
if (s.size() <= len)
return s;
else
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;
}

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());
std::string directory = dirname(pathDup);
free(pathDup);
return directory;
}

std::string extractFilename(std::string path) {
std::string stringFilename(std::string path) {
char *pathDup = strdup(path.c_str());
std::string filename = basename(pathDup);
free(pathDup);
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)
return "";
return ext + 1;


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

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

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

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


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
std::string command = "xdg-open " + url;
(void)system(command.c_str());
(void) system(command.c_str());
#endif
#if ARCH_MAC
std::string command = "open " + url;


+ 1
- 1
src/window.cpp View File

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


Loading…
Cancel
Save