diff --git a/patches/init/mini.vcv b/patches/init/mini.vcv new file mode 100644 index 0000000..640ce76 --- /dev/null +++ b/patches/init/mini.vcv @@ -0,0 +1,119 @@ +{ + "version": "2.1", + "zoom": 1.0, + "modules": [ + { + "id": 2799203590388841, + "plugin": "Cardinal", + "model": "TextEditor", + "version": "2.0", + "params": [], + "leftModuleId": 4, + "data": { + "filepath": "", + "lang": "None", + "etext": "Welcome to Cardinal!\n\nThis is the mini variant\nIt has 2 audio ports, 5 CV ports, plus MIDI\n\nThe most relevant modules for host\nintegration are in this default patch\n\nHave fun!\n\n", + "width": 23 + }, + "pos": [ + 42, + 0 + ] + }, + { + "id": 2, + "plugin": "Cardinal", + "model": "HostMIDI", + "version": "2.0", + "params": [], + "leftModuleId": 7249509538355161, + "rightModuleId": 3, + "data": { + "pwRange": 0.0, + "smooth": false, + "channels": 1, + "polyMode": 0, + "lastPitch": 8192, + "lastMod": 0, + "inputChannel": 0, + "outputChannel": 0 + }, + "pos": [ + 16, + 0 + ] + }, + { + "id": 3, + "plugin": "Cardinal", + "model": "HostTime", + "version": "2.0", + "params": [], + "leftModuleId": 2, + "rightModuleId": 4, + "pos": [ + 25, + 0 + ] + }, + { + "id": 4, + "plugin": "Cardinal", + "model": "HostParameters", + "version": "2.0", + "params": [], + "leftModuleId": 3, + "rightModuleId": 2799203590388841, + "pos": [ + 33, + 0 + ] + }, + { + "id": 7249509538355161, + "plugin": "Cardinal", + "model": "HostCV", + "version": "2.0", + "params": [ + { + "value": 0.0, + "id": 0 + }, + { + "value": 0.0, + "id": 1 + }, + { + "value": 0.0, + "id": 2 + }, + { + "value": 0.0, + "id": 3 + } + ], + "leftModuleId": 3606136179759592, + "rightModuleId": 2, + "pos": [ + 8, + 0 + ] + }, + { + "id": 3606136179759592, + "plugin": "Cardinal", + "model": "HostAudio2", + "version": "2.0", + "params": [], + "rightModuleId": 7249509538355161, + "data": { + "dcFilter": false + }, + "pos": [ + 0, + 0 + ] + } + ], + "cables": [] +} diff --git a/plugins/Makefile b/plugins/Makefile index d8d7ac7..616c612 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -311,6 +311,11 @@ endif PLUGIN_FILES += $(filter-out Fundamental/src/plugin.cpp,$(wildcard Fundamental/src/*.cpp)) PLUGIN_FILES += Fundamental/src/dr_wav.c +MINIPLUGIN_FILES += Fundamental/src/ADSR.cpp +MINIPLUGIN_FILES += Fundamental/src/LFO.cpp +MINIPLUGIN_FILES += Fundamental/src/VCF.cpp +MINIPLUGIN_FILES += Fundamental/src/VCO.cpp + # modules/types which are present in other plugins FUNDAMENTAL_CUSTOM = $(DRWAV) @@ -1294,7 +1299,7 @@ endif ifeq ($(NOPLUGINS),true) TARGETS = noplugins$(TARGET_SUFFIX).a else -TARGETS = plugins$(TARGET_SUFFIX).a plugins-mini.a +TARGETS = plugins$(TARGET_SUFFIX).a plugins-mini$(TARGET_SUFFIX).a endif all: $(TARGETS) @@ -1375,11 +1380,14 @@ JACK_RESOURCES += $(CURDIR)/surgext/build/surge-data/wavetables JACK_RESOURCES += $(CURDIR)/surgext/build/surge-data/windows.wt endif -MINIPLUGIN_LIST = Cardinal -MINIRESOURCE_FILES = $(wildcard Cardinal/res/*.svg) - RESOURCE_FILES += Cardinal/res/Miku/Miku.png +MINIPLUGIN_LIST = Cardinal Fundamental +MINIRESOURCE_FILES = $(wildcard Cardinal/res/*.svg) +MINIRESOURCE_FILES += $(wildcard Fundamental/res/*.svg) +MINIRESOURCE_FILES += $(wildcard Fundamental/res/components/*.svg) +MINIRESOURCE_FILES += Fundamental/presets + # MOD builds only have LV2 main and FX variant ifeq ($(MOD_BUILD),true) diff --git a/plugins/plugins-mini.cpp b/plugins/plugins-mini.cpp index afdd096..e0fa9d2 100644 --- a/plugins/plugins-mini.cpp +++ b/plugins/plugins-mini.cpp @@ -23,11 +23,15 @@ // Cardinal (built-in) #include "Cardinal/src/plugin.hpp" +// Fundamental +#include "Fundamental/src/plugin.hpp" + // known terminal modules std::vector hostTerminalModels; // plugin instances Plugin* pluginInstance__Cardinal; +Plugin* pluginInstance__Fundamental; namespace rack { @@ -175,9 +179,47 @@ static void initStatic__Cardinal() } } +static void initStatic__Fundamental() +{ + Plugin* const p = new Plugin; + pluginInstance__Fundamental = p; + + const StaticPluginLoader spl(p, "Fundamental"); + if (spl.ok()) + { + p->addModel(modelADSR); + p->addModel(modelLFO); + p->addModel(modelVCF); + p->addModel(modelVCO); + spl.removeModule("VCO2"); + spl.removeModule("VCA-1"); + spl.removeModule("VCA"); + spl.removeModule("LFO2"); + spl.removeModule("Delay"); + spl.removeModule("Mixer"); + spl.removeModule("VCMixer"); + spl.removeModule("8vert"); + spl.removeModule("Mutes"); + spl.removeModule("Pulses"); + spl.removeModule("Scope"); + spl.removeModule("SEQ3"); + spl.removeModule("SequentialSwitch1"); + spl.removeModule("SequentialSwitch2"); + spl.removeModule("Octave"); + spl.removeModule("Quantizer"); + spl.removeModule("Split"); + spl.removeModule("Merge"); + spl.removeModule("Sum"); + spl.removeModule("MidSide"); + spl.removeModule("Noise"); + spl.removeModule("Random"); + } +} + void initStaticPlugins() { initStatic__Cardinal(); + initStatic__Fundamental(); } void destroyStaticPlugins() diff --git a/src/CardinalCommon.cpp b/src/CardinalCommon.cpp index ea61c0f..d3177c8 100644 --- a/src/CardinalCommon.cpp +++ b/src/CardinalCommon.cpp @@ -62,12 +62,14 @@ # error wrong build #endif -#if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS -# define HEADLESS +#if defined(CARDINAL_COMMON_DSP_ONLY) || defined(HEADLESS) +# define HEADLESS_BEHAVIOUR #endif #if CARDINAL_VARIANT_FX # define CARDINAL_TEMPLATE_NAME "init/fx.vcv" +#elif CARDINAL_VARIANT_MINI +# define CARDINAL_TEMPLATE_NAME "init/mini.vcv" #elif CARDINAL_VARIANT_NATIVE # define CARDINAL_TEMPLATE_NAME "init/native.vcv" #elif CARDINAL_VARIANT_SYNTH @@ -93,10 +95,12 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------------------------------------------- +#ifndef HEADLESS void handleHostParameterDrag(const CardinalPluginContext* pcontext, uint index, bool started) { DISTRHO_SAFE_ASSERT_RETURN(pcontext->ui != nullptr,); + #ifndef CARDINAL_COMMON_DSP_ONLY if (started) { pcontext->ui->editParameter(index, true); @@ -106,26 +110,40 @@ void handleHostParameterDrag(const CardinalPluginContext* pcontext, uint index, { pcontext->ui->editParameter(index, false); } + #endif } +#endif // -------------------------------------------------------------------------------------------------------------------- +#ifndef HEADLESS bool CardinalPluginContext::addIdleCallback(IdleCallback* const cb) const { - if (ui == nullptr) - return false; + #ifndef CARDINAL_COMMON_DSP_ONLY + if (ui != nullptr) + { + ui->addIdleCallback(cb); + return true; + } + #else + // unused + (void)cb; + #endif - ui->addIdleCallback(cb); - return true; + return false; } void CardinalPluginContext::removeIdleCallback(IdleCallback* const cb) const { - if (ui == nullptr) - return; - - ui->removeIdleCallback(cb); + #ifndef CARDINAL_COMMON_DSP_ONLY + if (ui != nullptr) + ui->removeIdleCallback(cb); + #else + // unused + (void)cb; + #endif } +#endif void CardinalPluginContext::writeMidiMessage(const rack::midi::Message& message, const uint8_t channel) { @@ -296,7 +314,7 @@ Initializer::Initializer(const CardinalBasePlugin* const plugin, const CardinalB settings::skipLoadOnLaunch = true; settings::showTipsOnLaunch = false; settings::windowPos = math::Vec(0, 0); -#ifdef HEADLESS +#ifdef HEADLESS_BEHAVIOUR settings::headless = true; #endif @@ -348,17 +366,17 @@ Initializer::Initializer(const CardinalBasePlugin* const plugin, const CardinalB if (!system::exists(system::join(asset::systemDir, "res"))) #endif { - #if defined(DISTRHO_OS_WASM) + #if defined(DISTRHO_OS_WASM) asset::systemDir = "/resources"; - #elif defined(ARCH_MAC) + #elif defined(ARCH_MAC) asset::systemDir = "/Library/Application Support/Cardinal"; - #elif defined(ARCH_WIN) + #elif defined(ARCH_WIN) const std::string commonprogfiles = getSpecialPath(kSpecialPathCommonProgramFiles); if (! commonprogfiles.empty()) asset::systemDir = system::join(commonprogfiles, "Cardinal"); - #else + #else asset::systemDir = CARDINAL_PLUGIN_PREFIX "/share/cardinal"; - #endif + #endif asset::bundlePath = system::join(asset::systemDir, "PluginManifests"); } @@ -542,7 +560,7 @@ namespace patchUtils using namespace rack; -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR static void promptClear(const char* const message, const std::function action) { if (APP->history->isSaved() || APP->scene->rack->hasModules()) @@ -554,7 +572,7 @@ static void promptClear(const char* const message, const std::function a void loadDialog() { -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR promptClear("The current patch is unsaved. Clear it and open a new patch?", []() { std::string dir; if (! APP->patch->path.empty()) @@ -579,7 +597,7 @@ void loadDialog() void loadPathDialog(const std::string& path, const bool asTemplate) { -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR promptClear("The current patch is unsaved. Clear it and open the new patch?", [path, asTemplate]() { APP->patch->loadAction(path); @@ -618,7 +636,7 @@ void loadSelectionDialog() void loadTemplateDialog() { -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR promptClear("The current patch is unsaved. Clear it and start a new patch?", []() { APP->patch->loadTemplate(); }); @@ -627,7 +645,7 @@ void loadTemplateDialog() void revertDialog() { -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR if (APP->patch->path.empty()) return; promptClear("Revert patch to the last saved state?", []{ @@ -638,7 +656,7 @@ void revertDialog() void saveDialog(const std::string& path) { -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR if (path.empty()) { return; } @@ -656,7 +674,7 @@ void saveDialog(const std::string& path) #endif } -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR static void saveAsDialog(const bool uncompressed) { std::string dir; @@ -683,14 +701,14 @@ static void saveAsDialog(const bool uncompressed) void saveAsDialog() { -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR saveAsDialog(false); #endif } void saveAsDialogUncompressed() { -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR saveAsDialog(true); #endif } @@ -716,7 +734,7 @@ void async_dialog_filebrowser(const bool saving, const char* const title, const std::function action) { -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR CardinalPluginContext* const pcontext = static_cast(APP); DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,); @@ -739,14 +757,14 @@ void async_dialog_filebrowser(const bool saving, void async_dialog_message(const char* const message) { -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR asyncDialog::create(message); #endif } void async_dialog_message(const char* const message, const std::function action) { -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR asyncDialog::create(message, action); #endif } @@ -754,7 +772,7 @@ void async_dialog_message(const char* const message, const std::function void async_dialog_text_input(const char* const message, const char* const text, const std::function action) { -#ifndef HEADLESS +#ifndef HEADLESS_BEHAVIOUR asyncDialog::textInput(message, text, action); #endif } diff --git a/src/CardinalCommon.hpp b/src/CardinalCommon.hpp index 601fad4..31a57d1 100644 --- a/src/CardinalCommon.hpp +++ b/src/CardinalCommon.hpp @@ -104,6 +104,7 @@ START_NAMESPACE_DISTRHO class CardinalBasePlugin; class CardinalBaseUI; +struct CardinalPluginContext; struct Initializer #ifdef CARDINAL_INIT_OSC_THREAD @@ -124,6 +125,10 @@ struct Initializer #endif }; +#ifndef HEADLESS +void handleHostParameterDrag(const CardinalPluginContext* pcontext, uint index, bool started); +#endif + END_NAMESPACE_DISTRHO // ----------------------------------------------------------------------------------------------------------- diff --git a/src/CardinalMini/CardinalCommon-UI.cpp b/src/CardinalMini/CardinalCommon-UI.cpp new file mode 100644 index 0000000..f118c4e --- /dev/null +++ b/src/CardinalMini/CardinalCommon-UI.cpp @@ -0,0 +1,19 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021-2022 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the LICENSE file. + */ + +#define CARDINAL_COMMON_UI_ONLY +#include "CardinalCommon.cpp" diff --git a/src/CardinalMini/CardinalCommon.cpp b/src/CardinalMini/CardinalCommon.cpp deleted file mode 120000 index 76b4b5f..0000000 --- a/src/CardinalMini/CardinalCommon.cpp +++ /dev/null @@ -1 +0,0 @@ -../CardinalCommon.cpp \ No newline at end of file diff --git a/src/CardinalMini/CardinalCommon.cpp b/src/CardinalMini/CardinalCommon.cpp new file mode 100644 index 0000000..24dffcd --- /dev/null +++ b/src/CardinalMini/CardinalCommon.cpp @@ -0,0 +1,19 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021-2022 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the LICENSE file. + */ + +#define CARDINAL_COMMON_DSP_ONLY +#include "../CardinalCommon.cpp" diff --git a/src/CardinalPlugin.cpp b/src/CardinalPlugin.cpp index 7ec6012..cd011dc 100644 --- a/src/CardinalPlugin.cpp +++ b/src/CardinalPlugin.cpp @@ -609,7 +609,27 @@ protected: switch (index) { case 0: - state.hints = kStateIsBase64Blob | kStateIsOnlyForDSP; + state.hints = kStateIsBase64Blob; + #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS + state.hints |= kStateIsOnlyForDSP; + #endif + if (FILE* const f = std::fopen(context->patch->factoryTemplatePath.c_str(), "r")) + { + std::fseek(f, 0, SEEK_END); + if (const long fileSize = std::ftell(f)) + { + std::fseek(f, 0, SEEK_SET); + char* const fileContent = new char[fileSize]; + + if (std::fread(fileContent, fileSize, 1, f) == 1) + { + state.defaultValue = String::asBase64(fileContent, fileSize); + } + + delete[] fileContent; + } + std::fclose(f); + } state.key = "patch"; state.label = "Patch"; break; @@ -624,12 +644,17 @@ protected: state.label = "Comment"; break; case 3: - state.hints = kStateIsOnlyForUI; + state.hints = 0x0; + #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS + state.hints |= kStateIsOnlyForDSP; + #endif + state.defaultValue = "{}"; state.key = "moduleInfos"; state.label = "moduleInfos"; break; case 4: state.hints = kStateIsOnlyForUI; + // state.defaultValue = String("%d:%d", DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT); state.key = "windowSize"; state.label = "Window size"; break; diff --git a/src/CardinalUI.cpp b/src/CardinalUI.cpp index 451dd9b..b3b7b23 100644 --- a/src/CardinalUI.cpp +++ b/src/CardinalUI.cpp @@ -51,6 +51,7 @@ #include "CardinalCommon.hpp" #include "PluginContext.hpp" #include "WindowParameters.hpp" +#include "extra/Base64.hpp" #ifndef DISTRHO_OS_WASM # include "extra/SharedResourcePointer.hpp" @@ -376,8 +377,7 @@ public: context->history = new rack::history::State; context->patch = new rack::patch::Manager; context->patch->autosavePath = fAutosavePath; - context->patch->templatePath = fInitializer->templatePath; - context->patch->factoryTemplatePath = fInitializer->factoryTemplatePath; + context->patch->templatePath = context->patch->factoryTemplatePath = fInitializer->templatePath; context->event = new rack::widget::EventState; context->scene = new rack::app::Scene; @@ -385,10 +385,7 @@ public: context->window = new rack::window::Window; - context->patch->loadTemplate(); context->scene->rackScroll->reset(); - // swap to factory template after first load - context->patch->templatePath = context->patch->factoryTemplatePath; #endif Window& window(getWindow()); @@ -806,17 +803,59 @@ protected: void stateChanged(const char* const key, const char* const value) override { - if (std::strcmp(key, "windowSize") != 0) - return; + #if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS + if (std::strcmp(key, "patch") == 0) + { + if (fAutosavePath.empty()) + return; + + const std::vector data(d_getChunkFromBase64String(value)); + + DISTRHO_SAFE_ASSERT_RETURN(data.size() >= 4,); + + rack::system::removeRecursively(fAutosavePath); + rack::system::createDirectories(fAutosavePath); + + static constexpr const char zstdMagic[] = "\x28\xb5\x2f\xfd"; + + if (std::memcmp(data.data(), zstdMagic, sizeof(zstdMagic)) != 0) + { + FILE* const f = std::fopen(rack::system::join(fAutosavePath, "patch.json").c_str(), "w"); + DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,); + + std::fwrite(data.data(), data.size(), 1, f); + std::fclose(f); + } + else + { + try { + rack::system::unarchiveToDirectory(data, fAutosavePath); + } DISTRHO_SAFE_EXCEPTION_RETURN("setState unarchiveToDirectory",); + } + + const ScopedContext sc(this); - int width = 0; - int height = 0; - std::sscanf(value, "%i:%i", &width, &height); + try { + context->patch->loadAutosave(); + } DISTRHO_SAFE_EXCEPTION_RETURN("setState loadAutosave",); + + return; + } + #endif - if (width > 0 && height > 0) + if (std::strcmp(key, "windowSize") == 0) { - const double scaleFactor = getScaleFactor(); - setSize(width * scaleFactor, height * scaleFactor); + int width = 0; + int height = 0; + std::sscanf(value, "%d:%d", &width, &height); + + if (width > 0 && height > 0) + { + const double scaleFactor = getScaleFactor(); + setSize(width * scaleFactor, height * scaleFactor); + } + + return; } } diff --git a/src/Makefile.cardinal.mk b/src/Makefile.cardinal.mk index baa9f9d..93ba533 100644 --- a/src/Makefile.cardinal.mk +++ b/src/Makefile.cardinal.mk @@ -248,14 +248,12 @@ endif ifeq ($(CARDINAL_VARIANT),mini) ifneq ($(HEADLESS)$(MOD_BUILD),true) FILES_UI = CardinalUI.cpp -FILES_UI += CardinalCommon.cpp +FILES_UI += CardinalCommon-UI.cpp FILES_UI += common.cpp FILES_UI += glfw.cpp FILES_UI += Window.cpp EXTRA_UI_DEPENDENCIES = $(subst -headless,,$(EXTRA_DSP_DEPENDENCIES)) -EXTRA_UI_LIBS = -Wl,--start-group EXTRA_UI_LIBS += $(subst -headless,,$(EXTRA_DSP_LIBS)) -EXTRA_UI_LIBS += -Wl,--end-group endif endif diff --git a/src/PluginContext.hpp b/src/PluginContext.hpp index 2e8a612..629bda2 100644 --- a/src/PluginContext.hpp +++ b/src/PluginContext.hpp @@ -124,10 +124,6 @@ struct CardinalPluginContext : rack::Context { #endif }; -#ifndef HEADLESS -void handleHostParameterDrag(const CardinalPluginContext* pcontext, uint index, bool started); -#endif - // ----------------------------------------------------------------------------------------------------------- CardinalPluginContext* getRackContextFromPlugin(void* ptr); diff --git a/src/custom/RemoteNanoVG.cpp b/src/custom/RemoteNanoVG.cpp index ef7c653..73651db 100644 --- a/src/custom/RemoteNanoVG.cpp +++ b/src/custom/RemoteNanoVG.cpp @@ -21,27 +21,27 @@ # error wrong build #endif -#if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS -# define HEADLESS -#endif - -#ifndef HEADLESS -# include "OpenGL.hpp" -#endif +// #if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS +// # define HEADLESS +// #endif +// +// #ifndef HEADLESS +// # include "OpenGL.hpp" +// #endif #include "nanovg.h" -#ifdef HEADLESS +// #ifdef HEADLESS struct NVGLUframebuffer; void nvgluBindFramebuffer(NVGLUframebuffer* fb) {} NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags) { return nullptr; } void nvgluDeleteFramebuffer(NVGLUframebuffer* fb) {} -#else -# define NANOVG_GLES2_IMPLEMENTATION -# define NANOVG_FBO_VALID 1 -# include "nanovg_gl.h" -# include "nanovg_gl_utils.h" -#endif +// #else +// # define NANOVG_GLES2_IMPLEMENTATION +// # define NANOVG_FBO_VALID 1 +// # include "nanovg_gl.h" +// # include "nanovg_gl_utils.h" +// #endif #if defined(__GNUC__) && (__GNUC__ >= 6) # pragma GCC diagnostic push @@ -72,7 +72,7 @@ GLFWAPI double glfwGetTime(void) { return 0.0; } } -#ifndef HEADLESS -# define STB_IMAGE_WRITE_IMPLEMENTATION -# include "../src/Rack/dep/glfw/deps/stb_image_write.h" -#endif +// #ifndef HEADLESS +// # define STB_IMAGE_WRITE_IMPLEMENTATION +// # include "../src/Rack/dep/glfw/deps/stb_image_write.h" +// #endif