From 13cbc78f2330b6a422ed557a10fde740718b8b9b Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Thu, 22 Mar 2018 12:13:48 -0400 Subject: [PATCH 01/12] Add timestamp to logger, remove version check at boot and .vcv file upgrade notification --- include/util/common.hpp | 3 ++- src/app/ModuleWidget.cpp | 1 + src/app/RackScene.cpp | 38 ------------------------------ src/app/RackWidget.cpp | 14 ++++++----- src/main.cpp | 11 ++------- src/util/logger.cpp | 51 ++++++++++++++++++++++++++-------------- 6 files changed, 47 insertions(+), 71 deletions(-) diff --git a/include/util/common.hpp b/include/util/common.hpp index 4f392459..2856abdc 100644 --- a/include/util/common.hpp +++ b/include/util/common.hpp @@ -150,7 +150,8 @@ void openBrowser(std::string url); // logger.cpp //////////////////// -extern FILE *gLogFile; +void loggerInit(); +void loggerDestroy(); void debug(const char *format, ...); void info(const char *format, ...); void warn(const char *format, ...); diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index fdacb6c4..4fd88670 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -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 diff --git a/src/app/RackScene.cpp b/src/app/RackScene.cpp index 906f7bb9..01bbf71c 100644 --- a/src/app/RackScene.cpp +++ b/src/app/RackScene.cpp @@ -9,27 +9,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 +25,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 +41,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) { diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index f9e3917d..e410fb9d 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -225,8 +225,6 @@ 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. @@ -234,6 +232,7 @@ void RackWidget::fromJson(json_t *rootJ) { int legacy = 0; if (startsWith(version, "0.3.") || startsWith(version, "0.4.") || startsWith(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]; } diff --git a/src/main.cpp b/src/main.cpp index 9443ebeb..bc17ecb7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,11 +14,7 @@ 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 +60,7 @@ int main(int argc, char* argv[]) { bridgeDestroy(); engineDestroy(); pluginDestroy(); - -#ifdef RELEASE - fclose(gLogFile); -#endif + loggerDestroy(); return 0; } diff --git a/src/util/logger.cpp b/src/util/logger.cpp index 1c6d8963..5d833ea4 100644 --- a/src/util/logger.cpp +++ b/src/util/logger.cpp @@ -1,49 +1,66 @@ #include "util/common.hpp" +#include "asset.hpp" #include 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"); + gLogFile = fopen(logFilename.c_str(), "w"); + #endif +} + +void loggerDestroy() { + #ifdef RELEASE + fclose(gLogFile); + #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(nowTime - startTime).count(); + printTimestamp(); + fprintf(logFile, "[%s %.03f] ", 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); } From 6a18b51f8e478eb6caec30d358bbe804e462e811 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Thu, 22 Mar 2018 12:19:48 -0400 Subject: [PATCH 02/12] Fix order of logger --- src/util/logger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/logger.cpp b/src/util/logger.cpp index 5d833ea4..cb446c6b 100644 --- a/src/util/logger.cpp +++ b/src/util/logger.cpp @@ -30,7 +30,7 @@ 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(nowTime - startTime).count(); printTimestamp(); - fprintf(logFile, "[%s %.03f] ", duration / 1000.0, type); + fprintf(logFile, "[%.03f %s] ", duration / 1000.0, type); vfprintf(logFile, format, args); fprintf(logFile, "\n"); fflush(logFile); From b5e1bd436581a7f363467bf80d8df0d27e4ac8e5 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 23 Mar 2018 06:52:12 -0400 Subject: [PATCH 03/12] Add scratch filesystemListDirectory --- .gitignore | 1 + src/main.cpp | 46 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d22540ef..cb42c66b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /Rack /Rack.exe +/Rack.res /libRack.a /autosave.json /settings.json diff --git a/src/main.cpp b/src/main.cpp index bc17ecb7..45df303a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,13 +6,57 @@ #include "settings.hpp" #include "asset.hpp" #include "bridge.hpp" -#include #include "osdialog.h" +#include + +#if defined(ARCH_WIN) + #include +#else + #include +#endif + using namespace rack; + +std::vector filesystemListDirectory(std::string path) { + std::vector filenames; +#if defined(ARCH_WIN) + WIN32_FIND_DATA findData; + HANDLE findHandle = FindFirstFile((path + "/*").c_str(), &findData); + if (findHandle != INVALID_HANDLE_VALUE) { + do { + std::string filename = findData.cFileName; + if (filename == "." || filename == "..") + continue; + filenames.push_back(path + "/" + filename); + } while (FindNextFile(findHandle, &findData)); + FindClose(findHandle); + } +#else + DIR *dir = opendir(path.c_str()); + if (dir) { + struct dirent *d; + while ((d = readdir(dir))) { + std::string filename = d->f_name; + if (filename == "." || filename == "..") + continue; + filenames.push_back(path + "/" + filename); + } + closedir(dir); + } +#endif + return filenames; +} + int main(int argc, char* argv[]) { + + for (std::string filename : filesystemListDirectory("plugins")) { + debug("%s", filename.c_str()); + } + return 0; + randomInit(); loggerInit(); From 3177ec30fc7c9e2e0e27272d47c20b99acad16c1 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 23 Mar 2018 06:53:11 -0400 Subject: [PATCH 04/12] Use POSIX implementation of filesystemListDirectory --- src/main.cpp | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 45df303a..b2a12a8e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,11 +10,7 @@ #include -#if defined(ARCH_WIN) - #include -#else - #include -#endif +#include using namespace rack; @@ -22,31 +18,17 @@ using namespace rack; std::vector filesystemListDirectory(std::string path) { std::vector filenames; -#if defined(ARCH_WIN) - WIN32_FIND_DATA findData; - HANDLE findHandle = FindFirstFile((path + "/*").c_str(), &findData); - if (findHandle != INVALID_HANDLE_VALUE) { - do { - std::string filename = findData.cFileName; - if (filename == "." || filename == "..") - continue; - filenames.push_back(path + "/" + filename); - } while (FindNextFile(findHandle, &findData)); - FindClose(findHandle); - } -#else DIR *dir = opendir(path.c_str()); if (dir) { struct dirent *d; while ((d = readdir(dir))) { - std::string filename = d->f_name; + std::string filename = d->d_name; if (filename == "." || filename == "..") continue; filenames.push_back(path + "/" + filename); } closedir(dir); } -#endif return filenames; } From ef64c132870b0a00978309fb8b2f6025fb04e842 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 23 Mar 2018 07:13:52 -0400 Subject: [PATCH 05/12] Add systemOpenBrowser --- include/util/common.hpp | 34 +++--- src/app/PluginManagerWidget.cpp | 8 +- src/app/RackScene.cpp | 1 - src/main.cpp | 24 ---- src/plugin.cpp | 203 +++++++++++++++++--------------- src/util/logger.cpp | 14 +-- src/util/string.cpp | 4 + src/util/system.cpp | 26 +++- 8 files changed, 165 insertions(+), 149 deletions(-) diff --git a/include/util/common.hpp b/include/util/common.hpp index 2856abdc..055bd13c 100644 --- a/include/util/common.hpp +++ b/include/util/common.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -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 deferWrapper(F f) { return DeferWrapper(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 @@ -129,6 +132,7 @@ std::string uppercase(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); +bool endsWith(std::string str, std::string suffix); std::string extractDirectory(std::string path); std::string extractFilename(std::string path); @@ -139,11 +143,13 @@ std::string extractExtension(std::string path); // system.cpp //////////////////// +std::vector systemListDirectory(std::string path); + /** 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 diff --git a/src/app/PluginManagerWidget.cpp b/src/app/PluginManagerWidget.cpp index 914f0faa..1d3165e7 100644 --- a/src/app/PluginManagerWidget.cpp +++ b/src/app/PluginManagerWidget.cpp @@ -62,7 +62,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 +128,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(); } }; diff --git a/src/app/RackScene.cpp b/src/app/RackScene.cpp index 01bbf71c..2a87a8f2 100644 --- a/src/app/RackScene.cpp +++ b/src/app/RackScene.cpp @@ -1,7 +1,6 @@ #include "app.hpp" #include "window.hpp" #include "util/request.hpp" -#include "osdialog.h" #include #include diff --git a/src/main.cpp b/src/main.cpp index b2a12a8e..6cba8c13 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,35 +10,11 @@ #include -#include - using namespace rack; -std::vector filesystemListDirectory(std::string path) { - std::vector 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; -} - int main(int argc, char* argv[]) { - - for (std::string filename : filesystemListDirectory("plugins")) { - debug("%s", filename.c_str()); - } - return 0; - randomInit(); loggerInit(); diff --git a/src/plugin.cpp b/src/plugin.cpp index 0f7725ce..dd028fb0 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -1,5 +1,10 @@ -#include +#include "plugin.hpp" +#include "app.hpp" +#include "asset.hpp" +#include "util/request.hpp" +#include "osdialog.h" +#include #include #include #include @@ -13,7 +18,7 @@ #include #include -#if defined(ARCH_WIN) +#if ARCH_WIN #include #include #define mkdir(_dir, _perms) _mkdir(_dir) @@ -22,14 +27,10 @@ #endif #include -#include "plugin.hpp" -#include "app.hpp" -#include "asset.hpp" -#include "util/request.hpp" - namespace rack { + std::list gPlugins; std::string gToken; @@ -73,7 +74,7 @@ static int loadPlugin(std::string path) { warn("Failed to load library %s: %d", libraryFilename.c_str(), error); return -1; } -#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()); @@ -86,7 +87,7 @@ 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) { @@ -117,80 +118,6 @@ static int loadPlugin(std::string path) { 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; -} - static bool syncPlugin(json_t *pluginJ, bool dryRun) { json_t *slugJ = json_object_get(pluginJ, "slug"); if (!slugJ) @@ -223,11 +150,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,15 +208,6 @@ 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; } @@ -378,6 +296,95 @@ bool pluginSync(bool dryRun) { return available; } +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); + } +} + +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; +} + +static void extractPackages(std::string path) { + std::string message; + + for (std::string packagePath : systemListDirectory(path)) { + if (endsWith(packagePath, ".zip")) { + // Extract package + if (!extractZip(packagePath.c_str(), path.c_str())) { + message += stringf("Could not extract package %s\n", path); + continue; + } + // Remove package + remove(packagePath.c_str()); + } + } + if (!message.empty()) { + osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); + } +} + //////////////////// // plugin API //////////////////// @@ -401,6 +408,8 @@ void pluginInit() { // Load plugins from local directory std::string localPlugins = assetLocal("plugins"); mkdir(localPlugins.c_str(), 0755); + info("Unzipping plugins from %s", localPlugins.c_str()); + extractPackages(localPlugins); info("Loading plugins from %s", localPlugins.c_str()); loadPlugins(localPlugins); } @@ -408,10 +417,10 @@ void pluginInit() { void pluginDestroy() { for (Plugin *plugin : gPlugins) { // Free library handle -#if defined(ARCH_WIN) +#if ARCH_WIN if (plugin->handle) FreeLibrary((HINSTANCE)plugin->handle); -#elif defined(ARCH_LIN) || defined(ARCH_MAC) +#else if (plugin->handle) dlclose(plugin->handle); #endif diff --git a/src/util/logger.cpp b/src/util/logger.cpp index cb446c6b..e1e362b2 100644 --- a/src/util/logger.cpp +++ b/src/util/logger.cpp @@ -11,16 +11,16 @@ static auto startTime = std::chrono::high_resolution_clock::now(); void loggerInit() { - #ifdef RELEASE - std::string logFilename = assetLocal("log.txt"); - gLogFile = fopen(logFilename.c_str(), "w"); - #endif +#ifdef RELEASE + std::string logFilename = assetLocal("log.txt"); + logFile = fopen(logFilename.c_str(), "w"); +#endif } void loggerDestroy() { - #ifdef RELEASE - fclose(gLogFile); - #endif +#ifdef RELEASE + fclose(logFile); +#endif } static void printTimestamp() { diff --git a/src/util/string.cpp b/src/util/string.cpp index e2a1b77c..be70a81b 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -45,6 +45,10 @@ bool startsWith(std::string str, std::string prefix) { return str.substr(0, prefix.size()) == prefix; } +bool endsWith(std::string str, std::string suffix) { + return str.substr(str.size() - suffix.size(), suffix.size()) == suffix; +} + std::string extractDirectory(std::string path) { char *pathDup = strdup(path.c_str()); std::string directory = dirname(pathDup); diff --git a/src/util/system.cpp b/src/util/system.cpp index dcc63774..340aef8f 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1,18 +1,36 @@ #include "util/common.hpp" +#include + #if ARCH_WIN -#include -#include + #include + #include #endif namespace rack { -void openBrowser(std::string url) { +std::vector systemListDirectory(std::string path) { + std::vector 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; +} + +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; From 158a396170bc26ec4540b7f2bc68343ad00fc93a Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 23 Mar 2018 07:36:43 -0400 Subject: [PATCH 06/12] Add prefix to string functions --- include/util/common.hpp | 19 ++++++++++--------- src/app/ModuleBrowser.cpp | 4 ++-- src/app/RackScene.cpp | 2 +- src/app/RackWidget.cpp | 8 ++++---- src/plugin.cpp | 2 +- src/util/string.cpp | 18 +++++++++--------- src/window.cpp | 2 +- 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/include/util/common.hpp b/include/util/common.hpp index 055bd13c..f593e27c 100644 --- a/include/util/common.hpp +++ b/include/util/common.hpp @@ -126,17 +126,18 @@ 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); -bool endsWith(std::string str, std::string suffix); - -std::string extractDirectory(std::string path); -std::string extractFilename(std::string path); -std::string extractExtension(std::string path); +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); + +/** 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 diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp index 7f71a8da..f17c4c26 100644 --- a/src/app/ModuleBrowser.cpp +++ b/src/app/ModuleBrowser.cpp @@ -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); } diff --git a/src/app/RackScene.cpp b/src/app/RackScene.cpp index 2a87a8f2..980d214d 100644 --- a/src/app/RackScene.cpp +++ b/src/app/RackScene.cpp @@ -90,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; } diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index e410fb9d..c6cb4f25 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -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"; } @@ -230,7 +230,7 @@ void RackWidget::fromJson(json_t *rootJ) { // 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"; } diff --git a/src/plugin.cpp b/src/plugin.cpp index dd028fb0..a5af23c7 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -370,7 +370,7 @@ static void extractPackages(std::string path) { std::string message; for (std::string packagePath : systemListDirectory(path)) { - if (endsWith(packagePath, ".zip")) { + if (stringExtension(packagePath) == "zip") { // Extract package if (!extractZip(packagePath.c_str(), path.c_str())) { message += stringf("Could not extract package %s\n", path); diff --git a/src/util/string.cpp b/src/util/string.cpp index be70a81b..05d958b4 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -24,47 +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; } -bool endsWith(std::string str, std::string suffix) { +bool stringEndsWith(std::string str, std::string suffix) { return str.substr(str.size() - suffix.size(), suffix.size()) == suffix; } -std::string extractDirectory(std::string path) { +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; diff --git a/src/window.cpp b/src/window.cpp index 06bc2493..4ffeafab 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -436,7 +436,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()); From acbe3370f84feefd580741e9c3af53e3b414ae0e Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 23 Mar 2018 11:33:35 -0400 Subject: [PATCH 07/12] Plugin extracter and loader --- Makefile | 9 +- include/ui.hpp | 2 +- include/util/common.hpp | 5 +- src/app/PluginManagerWidget.cpp | 1 + src/asset.cpp | 4 +- src/plugin.cpp | 267 +++++++++++++++++--------------- src/util/system.cpp | 47 +++++- 7 files changed, 196 insertions(+), 139 deletions(-) diff --git a/Makefile b/Makefile index dd25a63c..aa014b79 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -179,8 +177,7 @@ 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 diff --git a/include/ui.hpp b/include/ui.hpp index 39708aab..981b938e 100644 --- a/include/ui.hpp +++ b/include/ui.hpp @@ -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, diff --git a/include/util/common.hpp b/include/util/common.hpp index f593e27c..735e686d 100644 --- a/include/util/common.hpp +++ b/include/util/common.hpp @@ -144,7 +144,10 @@ std::string stringExtension(std::string path); // system.cpp //////////////////// -std::vector systemListDirectory(std::string path); +std::vector 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. diff --git a/src/app/PluginManagerWidget.cpp b/src/app/PluginManagerWidget.cpp index 1d3165e7..7fd7573f 100644 --- a/src/app/PluginManagerWidget.cpp +++ b/src/app/PluginManagerWidget.cpp @@ -53,6 +53,7 @@ struct SyncButton : Button { PluginManagerWidget::PluginManagerWidget() { + box.size.y = BND_WIDGET_HEIGHT; float margin = 5; diff --git a/src/asset.cpp b/src/asset.cpp index 25384e65..13ee0e7e 100644 --- a/src/asset.cpp +++ b/src/asset.cpp @@ -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()); diff --git a/src/plugin.cpp b/src/plugin.cpp index a5af23c7..d9c0237e 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -53,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"; @@ -64,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); @@ -71,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; } #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 @@ -92,7 +101,7 @@ static int loadPlugin(std::string path) { #endif if (!initCallback) { warn("Failed to read init() symbol in %s", libraryFilename.c_str()); - return -2; + return false; } // Construct and initialize Plugin instance @@ -102,20 +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; + return true; } static bool syncPlugin(json_t *pluginJ, bool dryRun) { @@ -212,100 +220,18 @@ static bool syncPlugin(json_t *pluginJ, bool dryRun) { return true; } -bool pluginSync(bool dryRun) { - if (gToken.empty()) - return false; - - bool available = false; - - if (!dryRun) { - isDownloading = true; - downloadProgress = 0.0; - downloadName = "Updating plugins..."; - } - - json_t *resJ = NULL; - json_t *communityResJ = NULL; - - try { - // Download plugin slugs - json_t *reqJ = json_object(); - json_object_set(reqJ, "version", json_string(gApplicationVersion.c_str())); - json_object_set(reqJ, "token", json_string(gToken.c_str())); - resJ = requestJson(METHOD_GET, gApiHost + "/plugins", reqJ); - json_decref(reqJ); - if (!resJ) - throw std::runtime_error("No response from server"); - - json_t *errorJ = json_object_get(resJ, "error"); - if (errorJ) - throw std::runtime_error(json_string_value(errorJ)); - - // Download community plugins - communityResJ = requestJson(METHOD_GET, gApiHost + "/community/plugins", NULL); - if (!communityResJ) - throw std::runtime_error("No response from server"); - - json_t *communityErrorJ = json_object_get(communityResJ, "error"); - if (communityErrorJ) - throw std::runtime_error(json_string_value(communityErrorJ)); - - // Check each plugin in list of plugin slugs - json_t *pluginSlugsJ = json_object_get(resJ, "plugins"); - json_t *communityPluginsJ = json_object_get(communityResJ, "plugins"); - - size_t index; - json_t *pluginSlugJ; - json_array_foreach(pluginSlugsJ, index, pluginSlugJ) { - std::string slug = json_string_value(pluginSlugJ); - // Search for plugin slug in community - size_t communityIndex; - json_t *communityPluginJ = NULL; - json_array_foreach(communityPluginsJ, communityIndex, communityPluginJ) { - json_t *communitySlugJ = json_object_get(communityPluginJ, "slug"); - if (!communitySlugJ) - continue; - std::string communitySlug = json_string_value(communitySlugJ); - if (slug == communitySlug) - break; - } - - // Sync plugin - if (syncPlugin(communityPluginJ, dryRun)) { - available = true; - } - else { - warn("Plugin %s not found in community", slug.c_str()); - } - } - } - catch (std::runtime_error &e) { - warn("Plugin sync error: %s", e.what()); - } - - if (communityResJ) - json_decref(communityResJ); - - if (resJ) - json_decref(resJ); - - if (!dryRun) { - isDownloading = false; - } - - return available; -} - 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); + 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()); } - closedir(dir); + } + if (!message.empty()) { + message += "See log for details."; + osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); } } @@ -336,7 +262,7 @@ static int extractZipHandle(zip_t *za, const char *dir) { continue; while (1) { - char buffer[4096]; + char buffer[1<<15]; int len = zip_fread(zf, buffer, sizeof(buffer)); if (len <= 0) break; @@ -352,28 +278,30 @@ static int extractZipHandle(zip_t *za, const char *dir) { return 0; } -static int extractZip(const char *filename, const char *dir) { +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; - if (!err) { - err = extractZipHandle(za, dir); - } - - zip_close(za); + err = extractZipHandle(za, path); return err; } static void extractPackages(std::string path) { std::string message; - for (std::string packagePath : systemListDirectory(path)) { + 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", path); + if (extractZip(packagePath.c_str(), path.c_str())) { + message += stringf("Could not extract package %s\n", packagePath.c_str()); continue; } // Remove package @@ -386,31 +314,32 @@ static void extractPackages(std::string path) { } //////////////////// -// plugin API +// public API //////////////////// void pluginInit() { tagsInit(); - // TODO - // If `/plugins/Fundamental` doesn't exist, unzip global Fundamental.zip package into `/plugins` - - // TODO - // Find all ZIP packages in `/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 + // Get local plugins directory std::string localPlugins = assetLocal("plugins"); mkdir(localPlugins.c_str(), 0755); - info("Unzipping plugins from %s", localPlugins.c_str()); + +#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); - info("Loading plugins from %s", localPlugins.c_str()); loadPlugins(localPlugins); } @@ -432,6 +361,90 @@ void pluginDestroy() { gPlugins.clear(); } +bool pluginSync(bool dryRun) { + if (gToken.empty()) + return false; + + bool available = false; + + if (!dryRun) { + isDownloading = true; + downloadProgress = 0.0; + downloadName = "Updating plugins..."; + } + + json_t *resJ = NULL; + json_t *communityResJ = NULL; + + try { + // Download plugin slugs + json_t *reqJ = json_object(); + json_object_set(reqJ, "version", json_string(gApplicationVersion.c_str())); + json_object_set(reqJ, "token", json_string(gToken.c_str())); + resJ = requestJson(METHOD_GET, gApiHost + "/plugins", reqJ); + json_decref(reqJ); + if (!resJ) + throw std::runtime_error("No response from server"); + + json_t *errorJ = json_object_get(resJ, "error"); + if (errorJ) + throw std::runtime_error(json_string_value(errorJ)); + + // Download community plugins + communityResJ = requestJson(METHOD_GET, gApiHost + "/community/plugins", NULL); + if (!communityResJ) + throw std::runtime_error("No response from server"); + + json_t *communityErrorJ = json_object_get(communityResJ, "error"); + if (communityErrorJ) + throw std::runtime_error(json_string_value(communityErrorJ)); + + // Check each plugin in list of plugin slugs + json_t *pluginSlugsJ = json_object_get(resJ, "plugins"); + json_t *communityPluginsJ = json_object_get(communityResJ, "plugins"); + + size_t index; + json_t *pluginSlugJ; + json_array_foreach(pluginSlugsJ, index, pluginSlugJ) { + std::string slug = json_string_value(pluginSlugJ); + // Search for plugin slug in community + size_t communityIndex; + json_t *communityPluginJ = NULL; + json_array_foreach(communityPluginsJ, communityIndex, communityPluginJ) { + json_t *communitySlugJ = json_object_get(communityPluginJ, "slug"); + if (!communitySlugJ) + continue; + std::string communitySlug = json_string_value(communitySlugJ); + if (slug == communitySlug) + break; + } + + // Sync plugin + if (syncPlugin(communityPluginJ, dryRun)) { + available = true; + } + else { + warn("Plugin %s not found in community", slug.c_str()); + } + } + } + catch (std::runtime_error &e) { + warn("Plugin sync error: %s", e.what()); + } + + if (communityResJ) + json_decref(communityResJ); + + if (resJ) + json_decref(resJ); + + if (!dryRun) { + isDownloading = false; + } + + return available; +} + void pluginLogIn(std::string email, std::string password) { json_t *reqJ = json_object(); json_object_set(reqJ, "email", json_string(email.c_str())); @@ -507,6 +520,4 @@ Model *pluginGetModel(std::string pluginSlug, std::string modelSlug) { } - - } // namespace rack diff --git a/src/util/system.cpp b/src/util/system.cpp index 340aef8f..e4ef26ae 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1,6 +1,7 @@ #include "util/common.hpp" #include +#include #if ARCH_WIN #include @@ -11,7 +12,7 @@ namespace rack { -std::vector systemListDirectory(std::string path) { +std::vector systemListEntries(std::string path) { std::vector filenames; DIR *dir = opendir(path.c_str()); if (dir) { @@ -27,6 +28,50 @@ std::vector systemListDirectory(std::string path) { 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; From 8f30d48d1f349ea265dfe619e5278835bc652854 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 23 Mar 2018 11:54:31 -0400 Subject: [PATCH 08/12] Small build system tweaks --- Makefile | 16 ++++++++-------- plugin.mk | 6 +++--- src/app/RackWidget.cpp | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index aa014b79..2cb12884 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -184,9 +184,9 @@ 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) @@ -196,7 +196,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 diff --git a/plugin.mk b/plugin.mk index 1493c0e9..e1b78bcb 100644 --- a/plugin.mk +++ b/plugin.mk @@ -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 diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index c6cb4f25..11710bed 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -326,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()); } } From 0e40a86ba3d467d28529ec2e4d5364bb748f37b2 Mon Sep 17 00:00:00 2001 From: dllmusic <34119160+dllmusic@users.noreply.github.com> Date: Fri, 23 Mar 2018 23:32:19 -0400 Subject: [PATCH 09/12] QuadMIDIToCV with all poly modes Overview of functionality: ROTATE > Incoming note always takes the next available ch. REUSE > If incoming note is repeated (already assigned to one ch) takes that ch, if not, it takes the next available ch. RESET > Incoming note always takes the lowest available ch. REASSIGN > Similar to RESET but when releasing notes they are reassigned continuously from ch 0 (keeping the order) UNISON > Incoming note takes all 4 ch. When receiving more than 4 notes "stealing" occurs, always taking the next ch. When keys are released, still pressed notes that where stolen are recovered. In modes other than REUSE (or UNISON), repeating a note with sustain pedal rotates it over the channels producing "unison". When using sustain pedal and playing and releasing many notes, still pressed notes are stolen, but they are recovered when sustain pedal is off. I tested every possible combination of note(s) on off / sustain on off. also added a pulseGen for re-triggering notes when necessary (I also added the MIDI channel if on MidiMessage) --- src/Core/QuadMIDIToCVInterface.cpp | 207 +++++++++++++++++++++-------- 1 file changed, 151 insertions(+), 56 deletions(-) diff --git a/src/Core/QuadMIDIToCVInterface.cpp b/src/Core/QuadMIDIToCVInterface.cpp index dca10b66..be036661 100644 --- a/src/Core/QuadMIDIToCVInterface.cpp +++ b/src/Core/QuadMIDIToCVInterface.cpp @@ -26,7 +26,10 @@ struct QuadMIDIToCVInterface : Module { enum PolyMode { ROTATE_MODE, - RESET_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, NUM_MODES @@ -39,13 +42,20 @@ struct QuadMIDIToCVInterface : Module { }; NoteData noteData[128]; - std::vector heldNotes; + // cachedNotes : UNISON_MODE and REASSIGN_MODE cache all played notes. The other polyModes cache stealed notes (after 4th one). + std::vector 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; + + // retrigger for stolen notes (when gates already open) + PulseGenerator reTrigger[4]; - 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,93 +80,174 @@ 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(); + } + + 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 rotate) + stealIndex ++; + if (stealIndex > 3) + stealIndex = 0; + if ((polyMode < REASSIGN_MODE) && (gates[stealIndex])) + cachedNotes.push_back(notes[stealIndex]); + return stealIndex; } 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); - - // Set notes and gates + // 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; + //...it could be just "legato" for Unison mode without this... + 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 + // Remove the note + auto it = std::find(cachedNotes.begin(), cachedNotes.end(), note); + if (it != cachedNotes.end()) + cachedNotes.erase(it); + switch (polyMode) { - case ROTATE_MODE: { - - } break; - - case RESET_MODE: { - - } break; - - case REASSIGN_MODE: { - - } break; - + case REASSIGN_MODE: { + int held = static_cast(cachedNotes.size()); + if (held > 4) + held = 4; + for (int i = 0; i < held; i++) { + if (!pedalgates[i]) + notes[i] = cachedNotes.at(i); + pedalgates[i] = pedal; + } + for (int i = held; i < 4; i++) { + gates[i] = false; + } + } break; + case UNISON_MODE: { - if (!heldNotes.empty()) { - auto it2 = heldNotes.end(); - it2--; - for (int i = 0; i < 4; i++) { - notes[i] = *it2; - gates[i] = true; - } - } - else { - for (int i = 0; i < 4; i++) { - gates[i] = false; - } - } + if (!cachedNotes.empty()) { + uint8_t backnote = cachedNotes.back(); + for (int i = 0; i < 4; i++) { + notes[i] = backnote; + gates[i] = true; + } + } + else { + for (int i = 0; i < 4; i++) { + gates[i] = false; + } + } } 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); + pedal = false; + /* When pedal is off: Recover notes for still-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) { + int held = static_cast(cachedNotes.size()); + if (held > 4) + held = 4; + for (int i = 0; i < held; i++) { + notes[i] = cachedNotes.at(i); + gates[i] = true; + } + for (int i = held; i < 4; i++) { + gates[i] = false; + } + } } void step() override { @@ -170,11 +261,15 @@ struct QuadMIDIToCVInterface : Module { outputs[CV_OUTPUT + i].value = (lastNote - 60) / 12.f; outputs[GATE_OUTPUT + i].value = gates[i] ? 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: { From 2162c0836609340391d4d3bce47ba73185b0f2e0 Mon Sep 17 00:00:00 2001 From: dllmusic <34119160+dllmusic@users.noreply.github.com> Date: Fri, 23 Mar 2018 23:47:21 -0400 Subject: [PATCH 10/12] Update QuadMIDIToCVInterface.cpp --- src/Core/QuadMIDIToCVInterface.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/QuadMIDIToCVInterface.cpp b/src/Core/QuadMIDIToCVInterface.cpp index be036661..b7e27c05 100644 --- a/src/Core/QuadMIDIToCVInterface.cpp +++ b/src/Core/QuadMIDIToCVInterface.cpp @@ -258,8 +258,9 @@ struct QuadMIDIToCVInterface : Module { for (int i = 0; i < 4; i++) { uint8_t lastNote = notes[i]; + uint8_t lastGate = ((gates[i] || pedalgates[i]) && (!(reTrigger[i].process(engineGetSampleTime())))); 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[AFTERTOUCH_OUTPUT + i].value = rescale(noteData[lastNote].aftertouch, 0, 127, 0.f, 10.f); } From 8d6dccd5458a77de232d1ebdb1d1d883e09747f6 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Sat, 24 Mar 2018 01:14:58 -0400 Subject: [PATCH 11/12] Tweak README --- README.md | 4 ++-- dep.mk | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c3dd2689..5ad519b0 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/dep.mk b/dep.mk index 8c462830..1ee1c7b4 100644 --- a/dep.mk +++ b/dep.mk @@ -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 From 9b9f2a9f6fc23e122270914f00d5b69477754ba8 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Sat, 24 Mar 2018 06:16:19 -0400 Subject: [PATCH 12/12] Refactor MIDI-4, remove retriggering on GATE output --- src/Core/QuadMIDIToCVInterface.cpp | 228 ++++++++++++++--------------- 1 file changed, 113 insertions(+), 115 deletions(-) diff --git a/src/Core/QuadMIDIToCVInterface.cpp b/src/Core/QuadMIDIToCVInterface.cpp index b7e27c05..ff04101d 100644 --- a/src/Core/QuadMIDIToCVInterface.cpp +++ b/src/Core/QuadMIDIToCVInterface.cpp @@ -1,5 +1,6 @@ #include "Core.hpp" #include "midi.hpp" +#include "dsp/digital.hpp" #include @@ -26,10 +27,10 @@ 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...*/ + // 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, UNISON_MODE, NUM_MODES @@ -42,18 +43,15 @@ struct QuadMIDIToCVInterface : Module { }; NoteData noteData[128]; - // cachedNotes : UNISON_MODE and REASSIGN_MODE cache all played notes. The other polyModes cache stealed notes (after 4th one). - std::vector cachedNotes; + // cachedNotes : UNISON_MODE and REASSIGN_MODE cache all played notes. The other polyModes cache stolen notes (after the 4th one). + std::vector cachedNotes; uint8_t notes[4]; bool gates[4]; - // gates set to TRUE by pedal and current gate. FALSE by pedal. - bool pedalgates[4]; + // gates set to TRUE by pedal and current gate. FALSE by pedal. + bool pedalgates[4]; bool pedal; int rotateIndex; - int stealIndex; - - // retrigger for stolen notes (when gates already open) - PulseGenerator reTrigger[4]; + int stealIndex; QuadMIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS), cachedNotes(128) { onReset(); @@ -80,34 +78,34 @@ struct QuadMIDIToCVInterface : Module { for (int i = 0; i < 4; i++) { notes[i] = 60; gates[i] = false; - pedalgates[i] = false; + pedalgates[i] = false; } pedal = false; rotateIndex = -1; - cachedNotes.clear(); + cachedNotes.clear(); } - - int getPolyIndex (int nowIndex) { - for (int i = 0; i < 4; i++) { - nowIndex ++; - if (nowIndex > 3) + + int getPolyIndex(int nowIndex) { + for (int i = 0; i < 4; i++) { + nowIndex++; + if (nowIndex > 3) nowIndex = 0; - if (!(gates[nowIndex] || pedalgates[nowIndex])) { + if (!(gates[nowIndex] || pedalgates[nowIndex])) { stealIndex = nowIndex; return nowIndex; - } + } } - // All taken = steal (stealIndex always rotate) - stealIndex ++; + // All taken = steal (stealIndex always rotates) + stealIndex++; if (stealIndex > 3) - stealIndex = 0; + 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) { case ROTATE_MODE: { rotateIndex = getPolyIndex(rotateIndex); @@ -141,8 +139,7 @@ struct QuadMIDIToCVInterface : Module { notes[i] = note; gates[i] = true; pedalgates[i] = pedal; - //...it could be just "legato" for Unison mode without this... - reTrigger[i].trigger(1e-3); + // reTrigger[i].trigger(1e-3); } return; } break; @@ -150,104 +147,99 @@ struct QuadMIDIToCVInterface : Module { default: break; } // Set notes and gates - if (gates[rotateIndex] || pedalgates[rotateIndex]) - reTrigger[rotateIndex].trigger(1e-3); + // 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(cachedNotes.begin(), cachedNotes.end(), note); - if (it != cachedNotes.end()) - cachedNotes.erase(it); - + // Remove the note + auto it = std::find(cachedNotes.begin(), cachedNotes.end(), note); + if (it != cachedNotes.end()) + cachedNotes.erase(it); + switch (polyMode) { - case REASSIGN_MODE: { - int held = static_cast(cachedNotes.size()); - if (held > 4) - held = 4; - for (int i = 0; i < held; i++) { - if (!pedalgates[i]) - notes[i] = cachedNotes.at(i); - pedalgates[i] = pedal; - } - for (int i = held; i < 4; i++) { - gates[i] = false; - } - } break; - + 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 (!cachedNotes.empty()) { - uint8_t backnote = cachedNotes.back(); - for (int i = 0; i < 4; i++) { - notes[i] = backnote; - gates[i] = true; - } - } - else { - for (int i = 0; i < 4; i++) { - gates[i] = false; - } - } + if (!cachedNotes.empty()) { + uint8_t backnote = cachedNotes.back(); + for (int i = 0; i < 4; i++) { + notes[i] = backnote; + gates[i] = true; + } + } + else { + for (int i = 0; i < 4; i++) { + gates[i] = false; + } + } + } 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; - - // 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]; - } + for (int i = 0; i < 4; i++) { + pedalgates[i] = gates[i]; + } } void releasePedal() { - pedal = false; - /* When pedal is off: Recover notes for still-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) { - int held = static_cast(cachedNotes.size()); - if (held > 4) - held = 4; - for (int i = 0; i < held; i++) { - notes[i] = cachedNotes.at(i); - gates[i] = true; - } - for (int i = held; i < 4; i++) { - gates[i] = false; - } - } + pedal = false; + // 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 { @@ -258,7 +250,7 @@ struct QuadMIDIToCVInterface : Module { for (int i = 0; i < 4; i++) { uint8_t lastNote = notes[i]; - uint8_t lastGate = ((gates[i] || pedalgates[i]) && (!(reTrigger[i].process(engineGetSampleTime())))); + uint8_t lastGate = (gates[i] || pedalgates[i]); outputs[CV_OUTPUT + i].value = (lastNote - 60) / 12.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); @@ -268,9 +260,9 @@ struct QuadMIDIToCVInterface : Module { void processMessage(MidiMessage msg) { // filter MIDI channel - if ((midiInput.channel > -1) && (midiInput.channel != msg.channel())) - return; - + if ((midiInput.channel > -1) && (midiInput.channel != msg.channel())) + return; + switch (msg.status()) { // note off case 0x8: { @@ -358,7 +350,13 @@ struct QuadMIDIToCVInterfaceWidget : ModuleWidget { menu->addChild(MenuEntry::create()); menu->addChild(MenuLabel::create("Polyphony mode")); - std::vector polyModeNames = {"Rotate", "Reset", "Reassign", "Unison"}; + std::vector polyModeNames = { + "Rotate", + "Reuse", + "Reset", + "Reassign", + "Unison" + }; for (int i = 0; i < QuadMIDIToCVInterface::NUM_MODES; i++) { PolyphonyItem *item = MenuItem::create(polyModeNames[i], CHECKMARK(module->polyMode == i)); item->module = module;