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