Signed-off-by: falkTX <falktx@falktx.com>tags/22.07
| @@ -17,6 +17,9 @@ | |||
| #pragma once | |||
| #ifndef __EMSCRIPTEN__ | |||
| #define GHC_OS_DETECTED | |||
| #define GHC_OS_LINUX | |||
| #endif | |||
| #include_next <ghc/filesystem.hpp> | |||
| @@ -100,6 +100,10 @@ std::string getSpecialPath(const SpecialPath type) | |||
| } | |||
| #endif | |||
| #ifdef DISTRHO_OS_WASM | |||
| char* patchStorageSlug = nullptr; | |||
| #endif | |||
| std::string homeDir() | |||
| { | |||
| # ifdef ARCH_WIN | |||
| @@ -25,6 +25,10 @@ | |||
| # define REMOTE_HOST_PORT "2228" | |||
| #endif | |||
| #ifdef DISTRHO_OS_WASM | |||
| # define CARDINAL_IMPORTED_TEMPLATE_FILENAME "/imported.vcv" | |||
| #endif | |||
| extern const std::string CARDINAL_VERSION; | |||
| namespace rack { | |||
| @@ -53,6 +57,10 @@ enum SpecialPath { | |||
| std::string getSpecialPath(SpecialPath type); | |||
| #endif | |||
| #ifdef DISTRHO_OS_WASM | |||
| extern char* patchStorageSlug; | |||
| #endif | |||
| } // namespace rack | |||
| namespace patchUtils { | |||
| @@ -34,24 +34,38 @@ | |||
| # undef DEBUG | |||
| #endif | |||
| #ifdef HAVE_LIBLO | |||
| # ifdef HEADLESS | |||
| # include <lo/lo.h> | |||
| # include "extra/Thread.hpp" | |||
| # endif | |||
| # include "CardinalCommon.hpp" | |||
| #if defined(HAVE_LIBLO) && defined(HEADLESS) | |||
| # include <lo/lo.h> | |||
| # include "extra/Thread.hpp" | |||
| #endif | |||
| #include <list> | |||
| #include "CardinalCommon.hpp" | |||
| #include "DistrhoPluginUtils.hpp" | |||
| #include "PluginContext.hpp" | |||
| #include "extra/Base64.hpp" | |||
| #ifndef DISTRHO_OS_WASM | |||
| #ifdef DISTRHO_OS_WASM | |||
| # include <emscripten/emscripten.h> | |||
| #else | |||
| # include "extra/SharedResourcePointer.hpp" | |||
| #endif | |||
| #if CARDINAL_VARIANT_FX | |||
| # define CARDINAL_FACTORY_TEMPLATE_NAME "template-fx.vcv" | |||
| #elif CARDINAL_VARIANT_SYNTH | |||
| # define CARDINAL_FACTORY_TEMPLATE_NAME "template-synth.vcv" | |||
| #else | |||
| # define CARDINAL_FACTORY_TEMPLATE_NAME "template.vcv" | |||
| #endif | |||
| #ifdef DISTRHO_OS_WASM | |||
| # define CARDINAL_TEMPLATE_NAME "template-wasm.vcv" | |||
| #else | |||
| # define CARDINAL_TEMPLATE_NAME CARDINAL_FACTORY_TEMPLATE_NAME | |||
| #endif | |||
| static const constexpr uint kCardinalStateBaseCount = 3; // patch, screenshot, comment | |||
| #ifndef HEADLESS | |||
| @@ -63,14 +77,6 @@ static const constexpr uint kCardinalStateCount = kCardinalStateBaseCount + 2; / | |||
| static const constexpr uint kCardinalStateCount = kCardinalStateBaseCount; | |||
| #endif | |||
| #if CARDINAL_VARIANT_FX | |||
| # define CARDINAL_TEMPLATE_NAME "template-fx.vcv" | |||
| #elif CARDINAL_VARIANT_SYNTH | |||
| # define CARDINAL_TEMPLATE_NAME "template-synth.vcv" | |||
| #else | |||
| # define CARDINAL_TEMPLATE_NAME "template.vcv" | |||
| #endif | |||
| namespace rack { | |||
| namespace engine { | |||
| void Engine_setAboutToClose(Engine*); | |||
| @@ -97,6 +103,21 @@ bool d_isDiffHigherThanLimit(const T& v1, const T& v2, const T& limit) | |||
| // ----------------------------------------------------------------------------------------------------------- | |||
| #ifdef DISTRHO_OS_WASM | |||
| EM_JS(char*, getPatchStorageSlug, (), { | |||
| var searchParams = new URLSearchParams(window.location.search); | |||
| var patch = searchParams.get('patchstorage'); | |||
| if (!patch) | |||
| return null; | |||
| var length = lengthBytesUTF8(patch) + 1; | |||
| var str = _malloc(length); | |||
| stringToUTF8(patch, str, length); | |||
| return str; | |||
| }); | |||
| #endif | |||
| // ----------------------------------------------------------------------------------------------------------- | |||
| struct Initializer | |||
| #if defined(HAVE_LIBLO) && defined(HEADLESS) | |||
| : public Thread | |||
| @@ -107,6 +128,7 @@ struct Initializer | |||
| CardinalBasePlugin* oscPlugin = nullptr; | |||
| #endif | |||
| std::string templatePath; | |||
| std::string factoryTemplatePath; | |||
| Initializer(const CardinalBasePlugin* const plugin) | |||
| { | |||
| @@ -158,6 +180,7 @@ struct Initializer | |||
| asset::bundlePath = system::join(resourcePath, "PluginManifests"); | |||
| asset::systemDir = resourcePath; | |||
| templatePath = system::join(asset::systemDir, CARDINAL_TEMPLATE_NAME); | |||
| factoryTemplatePath = system::join(asset::systemDir, CARDINAL_FACTORY_TEMPLATE_NAME); | |||
| } | |||
| } | |||
| @@ -170,6 +193,7 @@ struct Initializer | |||
| if (system::exists(system::join(asset::systemDir, "res"))) | |||
| { | |||
| templatePath = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR CARDINAL_TEMPLATE_NAME; | |||
| factoryTemplatePath = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR CARDINAL_FACTORY_TEMPLATE_NAME; | |||
| } | |||
| // If source code dir does not exist use install target prefix as system dir | |||
| else | |||
| @@ -187,17 +211,20 @@ struct Initializer | |||
| asset::systemDir = CARDINAL_PLUGIN_PREFIX "/share/cardinal"; | |||
| #endif | |||
| if (! asset::systemDir.empty()) | |||
| { | |||
| asset::bundlePath = system::join(asset::systemDir, "PluginManifests"); | |||
| templatePath = system::join(asset::systemDir, CARDINAL_TEMPLATE_NAME); | |||
| } | |||
| asset::bundlePath = system::join(asset::systemDir, "PluginManifests"); | |||
| templatePath = system::join(asset::systemDir, CARDINAL_TEMPLATE_NAME); | |||
| factoryTemplatePath = system::join(asset::systemDir, CARDINAL_FACTORY_TEMPLATE_NAME); | |||
| } | |||
| } | |||
| asset::userDir = asset::systemDir; | |||
| } | |||
| #ifdef DISTRHO_OS_WASM | |||
| if ((patchStorageSlug = getPatchStorageSlug()) != nullptr) | |||
| templatePath = CARDINAL_IMPORTED_TEMPLATE_FILENAME; | |||
| #endif | |||
| // Log environment | |||
| INFO("%s %s v%s", APP_NAME.c_str(), APP_EDITION.c_str(), APP_VERSION.c_str()); | |||
| INFO("%s", system::getOperatingSystemInfo().c_str()); | |||
| @@ -206,6 +233,7 @@ struct Initializer | |||
| INFO("System directory: %s", asset::systemDir.c_str()); | |||
| INFO("User directory: %s", asset::userDir.c_str()); | |||
| INFO("Template patch: %s", templatePath.c_str()); | |||
| INFO("System template patch: %s", factoryTemplatePath.c_str()); | |||
| // Report to user if something is wrong with the installation | |||
| if (asset::systemDir.empty()) | |||
| @@ -564,6 +592,7 @@ public: | |||
| context->patch = new rack::patch::Manager; | |||
| context->patch->autosavePath = fAutosavePath; | |||
| context->patch->templatePath = fInitializer->templatePath; | |||
| context->patch->factoryTemplatePath = fInitializer->factoryTemplatePath; | |||
| context->event = new rack::widget::EventState; | |||
| context->scene = new rack::app::Scene; | |||
| @@ -572,28 +601,33 @@ public: | |||
| if (! isDummyInstance()) | |||
| context->window = new rack::window::Window; | |||
| context->patch->loadTemplate(); | |||
| context->scene->rackScroll->reset(); | |||
| #ifdef DISTRHO_OS_WASM | |||
| if (rack::patchStorageSlug == nullptr) | |||
| #endif | |||
| { | |||
| context->patch->loadTemplate(); | |||
| context->scene->rackScroll->reset(); | |||
| } | |||
| #if defined(HAVE_LIBLO) && defined(HEADLESS) | |||
| #if defined(HAVE_LIBLO) && defined(HEADLESS) | |||
| fInitializer->oscPlugin = this; | |||
| #endif | |||
| #endif | |||
| } | |||
| ~CardinalPlugin() override | |||
| { | |||
| #if defined(HAVE_LIBLO) && defined(HEADLESS) | |||
| #if defined(HAVE_LIBLO) && defined(HEADLESS) | |||
| fInitializer->oscPlugin = nullptr; | |||
| #endif | |||
| #endif | |||
| { | |||
| const ScopedContext sc(this); | |||
| context->patch->clear(); | |||
| // do a little dance to prevent context scene deletion from saving to temp dir | |||
| #ifndef HEADLESS | |||
| #ifndef HEADLESS | |||
| const ScopedValueSetter<bool> svs(rack::settings::headless, true); | |||
| #endif | |||
| #endif | |||
| Engine_setAboutToClose(context->engine); | |||
| delete context; | |||
| } | |||
| @@ -31,11 +31,12 @@ | |||
| #include <window/Window.hpp> | |||
| #ifdef DISTRHO_OS_WASM | |||
| #include <ui/Button.hpp> | |||
| #include <ui/Label.hpp> | |||
| #include <ui/MenuOverlay.hpp> | |||
| #include <ui/SequentialLayout.hpp> | |||
| #include "CardinalCommon.hpp" | |||
| # include <ui/Button.hpp> | |||
| # include <ui/Label.hpp> | |||
| # include <ui/MenuOverlay.hpp> | |||
| # include <ui/SequentialLayout.hpp> | |||
| # include "CardinalCommon.hpp" | |||
| # include <emscripten/emscripten.h> | |||
| #endif | |||
| #ifdef NDEBUG | |||
| @@ -189,6 +190,104 @@ struct WasmWelcomeDialog : rack::widget::OpaqueWidget | |||
| Widget::draw(args); | |||
| } | |||
| }; | |||
| struct WasmPatchStorageLoadingDialog : rack::widget::OpaqueWidget | |||
| { | |||
| static const constexpr float margin = 10; | |||
| rack::ui::MenuOverlay* overlay; | |||
| WasmPatchStorageLoadingDialog() | |||
| { | |||
| using rack::ui::Label; | |||
| using rack::ui::MenuOverlay; | |||
| using rack::ui::SequentialLayout; | |||
| box.size = rack::math::Vec(300, 50); | |||
| SequentialLayout* const layout = new SequentialLayout; | |||
| layout->box.pos = rack::math::Vec(0, 0); | |||
| layout->box.size = box.size; | |||
| layout->orientation = SequentialLayout::VERTICAL_ORIENTATION; | |||
| layout->margin = rack::math::Vec(margin, margin); | |||
| layout->spacing = rack::math::Vec(margin, margin); | |||
| layout->wrap = false; | |||
| addChild(layout); | |||
| Label* const label = new Label; | |||
| label->box.size.x = box.size.x - 2*margin; | |||
| label->box.size.y = box.size.y - 2*margin - 40; | |||
| label->fontSize = 16; | |||
| label->text = "Load patch from PatchStorage...\n"; | |||
| layout->addChild(label); | |||
| overlay = new MenuOverlay; | |||
| overlay->bgColor = nvgRGBAf(0, 0, 0, 0.33); | |||
| overlay->addChild(this); | |||
| APP->scene->addChild(overlay); | |||
| } | |||
| void step() override | |||
| { | |||
| OpaqueWidget::step(); | |||
| box.pos = parent->box.size.minus(box.size).div(2).round(); | |||
| } | |||
| void draw(const DrawArgs& args) override | |||
| { | |||
| bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0); | |||
| Widget::draw(args); | |||
| } | |||
| }; | |||
| static void downloadPatchStorageFailed(const char* const filename) | |||
| { | |||
| d_stdout("downloadPatchStorageFailed %s", filename); | |||
| CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP); | |||
| CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(context->ui); | |||
| if (ui->psDialog != nullptr) | |||
| { | |||
| ui->psDialog->overlay->requestDelete(); | |||
| asyncDialog::create("Failed to fetch patch from PatchStorage"); | |||
| } | |||
| using namespace rack; | |||
| context->patch->templatePath = system::join(asset::systemDir, "template-synth.vcv"); // FIXME | |||
| context->patch->loadTemplate(); | |||
| context->scene->rackScroll->reset(); | |||
| } | |||
| static void downloadPatchStorageSucceeded(const char* const filename) | |||
| { | |||
| d_stdout("downloadPatchStorageSucceeded %s | %s", filename, APP->patch->templatePath.c_str()); | |||
| CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP); | |||
| CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(context->ui); | |||
| ui->psDialog->overlay->requestDelete(); | |||
| ui->psDialog = nullptr; | |||
| if (FILE* f = fopen(filename, "r")) | |||
| { | |||
| uint8_t buf[8] = {}; | |||
| fread(buf, 8, 1, f); | |||
| d_stdout("read patch %x %x %x %x %x %x %x %x", | |||
| buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7]); | |||
| fclose(f); | |||
| } | |||
| try { | |||
| context->patch->load(CARDINAL_IMPORTED_TEMPLATE_FILENAME); | |||
| } catch (rack::Exception& e) { | |||
| const std::string message = rack::string::f("Could not load patch: %s", e.what()); | |||
| asyncDialog::create(message.c_str()); | |||
| return; | |||
| } | |||
| context->scene->rackScroll->reset(); | |||
| context->patch->path = ""; | |||
| context->history->setSaved(); | |||
| } | |||
| #endif | |||
| // ----------------------------------------------------------------------------------------------------------- | |||
| @@ -288,7 +387,21 @@ public: | |||
| } | |||
| #ifdef DISTRHO_OS_WASM | |||
| new WasmWelcomeDialog(); | |||
| if (rack::patchStorageSlug != nullptr) | |||
| { | |||
| std::string url("/patchstorage.php?slug="); | |||
| url += rack::patchStorageSlug; | |||
| std::free(rack::patchStorageSlug); | |||
| rack::patchStorageSlug = nullptr; | |||
| psDialog = new WasmPatchStorageLoadingDialog(); | |||
| emscripten_async_wget(url.c_str(), context->patch->templatePath.c_str(), | |||
| downloadPatchStorageSucceeded, downloadPatchStorageFailed); | |||
| } | |||
| else | |||
| { | |||
| new WasmWelcomeDialog(); | |||
| } | |||
| #endif | |||
| context->window->step(); | |||
| @@ -122,10 +122,6 @@ void handleHostParameterDrag(const CardinalPluginContext* pcontext, uint index, | |||
| // ----------------------------------------------------------------------------------------------------------- | |||
| struct CardinalAudioDevice; | |||
| struct CardinalMidiInputDevice; | |||
| struct CardinalMidiOutputDevice; | |||
| CardinalPluginContext* getRackContextFromPlugin(void* ptr); | |||
| class CardinalBasePlugin : public Plugin { | |||
| @@ -139,12 +135,18 @@ public: | |||
| }; | |||
| #ifndef HEADLESS | |||
| struct WasmPatchStorageLoadingDialog; | |||
| class CardinalBaseUI : public UI { | |||
| public: | |||
| CardinalPluginContext* const context; | |||
| bool saving; | |||
| bool savingUncompressed; | |||
| #ifdef DISTRHO_OS_WASM | |||
| WasmPatchStorageLoadingDialog* psDialog; | |||
| #endif | |||
| // for 3rd party modules | |||
| std::function<void(char* path)> filebrowseraction; | |||
| FileBrowserHandle filebrowserhandle; | |||
| @@ -154,6 +156,9 @@ public: | |||
| context(getRackContextFromPlugin(getPluginInstancePointer())), | |||
| saving(false), | |||
| savingUncompressed(false), | |||
| #ifdef DISTRHO_OS_WASM | |||
| psDialog(nullptr), | |||
| #endif | |||
| filebrowseraction(), | |||
| filebrowserhandle(nullptr) | |||
| { | |||
| @@ -0,0 +1,43 @@ | |||
| <?php | |||
| $slug = filter_input(INPUT_GET, 'slug', FILTER_SANITIZE_ENCODED); | |||
| if (!$slug) { http_response_code(404); die(); } | |||
| $api = 'https://patchstorage.com/api/alpha'; | |||
| $search = file_get_contents($api.'/patches?platform=7834&slug='.$slug); | |||
| if (!$search) { http_response_code(404); die(); } | |||
| $searchJ = json_decode($search, true); | |||
| if (!$searchJ) { http_response_code(404); die(); } | |||
| if (!$searchJ[0]) { http_response_code(404); die(); } | |||
| $patchId = $searchJ[0]['id']; | |||
| if (!$patchId) { http_response_code(404); die(); } | |||
| $patchDetails = file_get_contents($api.'/patches/'.$patchId); | |||
| if (!$patchDetails) { http_response_code(404); die(); } | |||
| $patchDetailsJ = json_decode($patchDetails, true); | |||
| if (!$patchDetailsJ) { http_response_code(404); die(); } | |||
| $patchFiles = $patchDetailsJ['files']; | |||
| if (!$patchFiles) { http_response_code(404); die(); } | |||
| $patchFileDetails = $patchFiles[0]; | |||
| if (!$patchFileDetails) { http_response_code(404); die(); } | |||
| if (!$patchFileDetails['filename']) { http_response_code(404); die(); } | |||
| if (!$patchFileDetails['url']) { http_response_code(404); die(); } | |||
| $contents = file_get_contents($patchFileDetails['url']); | |||
| if (!$contents) { http_response_code(404); die(); } | |||
| header('Content-Description: File Transfer'); | |||
| header('Content-Type: application/octet-stream'); | |||
| header('Content-Disposition: attachment; filename="'.$patchFileDetails['filename'].'"'); | |||
| header('Expires: 0'); | |||
| header('Cache-Control: must-revalidate'); | |||
| header('Pragma: public'); | |||
| header('Content-Length: ' . strlen($contents)); | |||
| flush(); | |||
| die($contents); | |||
| ?> | |||