Signed-off-by: falkTX <falktx@falktx.com>tags/22.02
| @@ -0,0 +1,12 @@ | |||
| #!/bin/bash | |||
| set -e | |||
| diff -U3 ../Rack/dep/oui-blendish/blendish.c blendish.c > diffs/blendish.c.diff | |||
| diff -U3 ../Rack/src/common.cpp common.cpp > diffs/common.cpp.diff | |||
| diff -U3 ../Rack/src/context.cpp context.cpp > diffs/context.cpp.diff | |||
| diff -U3 ../Rack/src/app/MenuBar.cpp MenuBar.cpp > diffs/MenuBar.cpp.diff | |||
| diff -U3 ../Rack/src/app/Scene.cpp Scene.cpp > diffs/Scene.cpp.diff | |||
| diff -U3 ../Rack/src/engine/Engine.cpp Engine.cpp > diffs/Engine.cpp.diff | |||
| diff -U3 ../Rack/src/plugin/Model.cpp Model.cpp > diffs/Model.cpp.diff | |||
| diff -U3 ../Rack/src/window/Window.cpp Window.cpp > diffs/Window.cpp.diff | |||
| @@ -0,0 +1,761 @@ | |||
| --- ../Rack/src/engine/Engine.cpp 2022-01-15 14:44:46.395281005 +0000 | |||
| +++ Engine.cpp 2022-01-23 17:13:03.200930905 +0000 | |||
| @@ -1,3 +1,30 @@ | |||
| +/* | |||
| + * DISTRHO Cardinal Plugin | |||
| + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| + * | |||
| + * 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. | |||
| + */ | |||
| + | |||
| +/** | |||
| + * This file is an edited version of VCVRack's engine/Engine.cpp | |||
| + * Copyright (C) 2016-2021 VCV. | |||
| + * | |||
| + * 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 (at your option) any later version. | |||
| + */ | |||
| + | |||
| #include <algorithm> | |||
| #include <set> | |||
| #include <thread> | |||
| @@ -11,178 +38,25 @@ | |||
| #include <settings.hpp> | |||
| #include <system.hpp> | |||
| #include <random.hpp> | |||
| -#include <context.hpp> | |||
| #include <patch.hpp> | |||
| #include <plugin.hpp> | |||
| #include <mutex.hpp> | |||
| +#include <helpers.hpp> | |||
| + | |||
| +#ifdef NDEBUG | |||
| +# undef DEBUG | |||
| +#endif | |||
| +#include "DistrhoUtils.hpp" | |||
| namespace rack { | |||
| namespace engine { | |||
| -static void initMXCSR() { | |||
| - // Set CPU to flush-to-zero (FTZ) and denormals-are-zero (DAZ) mode | |||
| - // https://software.intel.com/en-us/node/682949 | |||
| - _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); | |||
| - _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); | |||
| - // Reset other flags | |||
| - _MM_SET_ROUNDING_MODE(_MM_ROUND_NEAREST); | |||
| -} | |||
| - | |||
| - | |||
| -/** Barrier based on mutexes. | |||
| -Not finished or tested, do not use. | |||
| -*/ | |||
| -struct Barrier { | |||
| - int count = 0; | |||
| - uint8_t step = 0; | |||
| - int threads = 0; | |||
| - | |||
| - std::mutex mutex; | |||
| - std::condition_variable cv; | |||
| - | |||
| - void setThreads(int threads) { | |||
| - this->threads = threads; | |||
| - } | |||
| - | |||
| - void wait() { | |||
| - std::unique_lock<std::mutex> lock(mutex); | |||
| - uint8_t s = step; | |||
| - if (++count >= threads) { | |||
| - // We're the last thread. Reset next phase. | |||
| - count = 0; | |||
| - // Allow other threads to exit wait() | |||
| - step++; | |||
| - cv.notify_all(); | |||
| - return; | |||
| - } | |||
| - | |||
| - cv.wait(lock, [&] { | |||
| - return step != s; | |||
| - }); | |||
| - } | |||
| -}; | |||
| - | |||
| - | |||
| -/** 2-phase barrier based on spin-locking. | |||
| -*/ | |||
| -struct SpinBarrier { | |||
| - std::atomic<int> count{0}; | |||
| - std::atomic<uint8_t> step{0}; | |||
| - int threads = 0; | |||
| - | |||
| - /** Must be called when no threads are calling wait(). | |||
| - */ | |||
| - void setThreads(int threads) { | |||
| - this->threads = threads; | |||
| - } | |||
| - | |||
| - void wait() { | |||
| - uint8_t s = step; | |||
| - if (count.fetch_add(1, std::memory_order_acquire) + 1 >= threads) { | |||
| - // We're the last thread. Reset next phase. | |||
| - count = 0; | |||
| - // Allow other threads to exit wait() | |||
| - step++; | |||
| - return; | |||
| - } | |||
| - | |||
| - // Spin until the last thread begins waiting | |||
| - while (true) { | |||
| - if (step.load(std::memory_order_relaxed) != s) | |||
| - return; | |||
| - __builtin_ia32_pause(); | |||
| - } | |||
| - } | |||
| -}; | |||
| - | |||
| - | |||
| -/** Barrier that spin-locks until yield() is called, and then all threads switch to a mutex. | |||
| -yield() should be called if it is likely that all threads will block for a while and continuing to spin-lock is unnecessary. | |||
| -Saves CPU power after yield is called. | |||
| -*/ | |||
| -struct HybridBarrier { | |||
| - std::atomic<int> count{0}; | |||
| - std::atomic<uint8_t> step{0}; | |||
| - int threads = 0; | |||
| - | |||
| - std::atomic<bool> yielded{false}; | |||
| - std::mutex mutex; | |||
| - std::condition_variable cv; | |||
| - | |||
| - void setThreads(int threads) { | |||
| - this->threads = threads; | |||
| - } | |||
| - | |||
| - void yield() { | |||
| - yielded = true; | |||
| - } | |||
| - | |||
| - void wait() { | |||
| - uint8_t s = step; | |||
| - if (count.fetch_add(1, std::memory_order_acquire) + 1 >= threads) { | |||
| - // We're the last thread. Reset next phase. | |||
| - count = 0; | |||
| - bool wasYielded = yielded; | |||
| - yielded = false; | |||
| - // Allow other threads to exit wait() | |||
| - step++; | |||
| - if (wasYielded) { | |||
| - std::unique_lock<std::mutex> lock(mutex); | |||
| - cv.notify_all(); | |||
| - } | |||
| - return; | |||
| - } | |||
| - | |||
| - // Spin until the last thread begins waiting | |||
| - while (!yielded.load(std::memory_order_relaxed)) { | |||
| - if (step.load(std::memory_order_relaxed) != s) | |||
| - return; | |||
| - __builtin_ia32_pause(); | |||
| - } | |||
| - | |||
| - // Wait on mutex CV | |||
| - std::unique_lock<std::mutex> lock(mutex); | |||
| - cv.wait(lock, [&] { | |||
| - return step != s; | |||
| - }); | |||
| - } | |||
| -}; | |||
| - | |||
| - | |||
| -struct EngineWorker { | |||
| - Engine* engine; | |||
| - int id; | |||
| - std::thread thread; | |||
| - bool running = false; | |||
| - | |||
| - void start() { | |||
| - assert(!running); | |||
| - running = true; | |||
| - thread = std::thread([&] { | |||
| - run(); | |||
| - }); | |||
| - } | |||
| - | |||
| - void requestStop() { | |||
| - running = false; | |||
| - } | |||
| - | |||
| - void join() { | |||
| - assert(thread.joinable()); | |||
| - thread.join(); | |||
| - } | |||
| - | |||
| - void run(); | |||
| -}; | |||
| - | |||
| - | |||
| struct Engine::Internal { | |||
| std::vector<Module*> modules; | |||
| std::vector<Cable*> cables; | |||
| std::set<ParamHandle*> paramHandles; | |||
| - Module* masterModule = NULL; | |||
| // moduleId | |||
| std::map<int64_t, Module*> modulesCache; | |||
| @@ -217,22 +91,6 @@ | |||
| Readers lock when using the engine's state. | |||
| */ | |||
| SharedMutex mutex; | |||
| - /** Mutex that guards stepBlock() so it's not called simultaneously. | |||
| - */ | |||
| - std::mutex blockMutex; | |||
| - | |||
| - int threadCount = 0; | |||
| - std::vector<EngineWorker> workers; | |||
| - HybridBarrier engineBarrier; | |||
| - HybridBarrier workerBarrier; | |||
| - std::atomic<int> workerModuleIndex; | |||
| - // For worker threads | |||
| - Context* context; | |||
| - | |||
| - bool fallbackRunning = false; | |||
| - std::thread fallbackThread; | |||
| - std::mutex fallbackMutex; | |||
| - std::condition_variable fallbackCv; | |||
| }; | |||
| @@ -260,71 +118,6 @@ | |||
| } | |||
| -static void Engine_relaunchWorkers(Engine* that, int threadCount) { | |||
| - Engine::Internal* internal = that->internal; | |||
| - if (threadCount == internal->threadCount) | |||
| - return; | |||
| - | |||
| - if (internal->threadCount > 0) { | |||
| - // Stop engine workers | |||
| - for (EngineWorker& worker : internal->workers) { | |||
| - worker.requestStop(); | |||
| - } | |||
| - internal->engineBarrier.wait(); | |||
| - | |||
| - // Join and destroy engine workers | |||
| - for (EngineWorker& worker : internal->workers) { | |||
| - worker.join(); | |||
| - } | |||
| - internal->workers.resize(0); | |||
| - } | |||
| - | |||
| - // Configure engine | |||
| - internal->threadCount = threadCount; | |||
| - | |||
| - // Set barrier counts | |||
| - internal->engineBarrier.setThreads(threadCount); | |||
| - internal->workerBarrier.setThreads(threadCount); | |||
| - | |||
| - if (threadCount > 0) { | |||
| - // Create and start engine workers | |||
| - internal->workers.resize(threadCount - 1); | |||
| - for (int id = 1; id < threadCount; id++) { | |||
| - EngineWorker& worker = internal->workers[id - 1]; | |||
| - worker.id = id; | |||
| - worker.engine = that; | |||
| - worker.start(); | |||
| - } | |||
| - } | |||
| -} | |||
| - | |||
| - | |||
| -static void Engine_stepWorker(Engine* that, int threadId) { | |||
| - Engine::Internal* internal = that->internal; | |||
| - | |||
| - // int threadCount = internal->threadCount; | |||
| - int modulesLen = internal->modules.size(); | |||
| - | |||
| - // Build ProcessArgs | |||
| - Module::ProcessArgs processArgs; | |||
| - processArgs.sampleRate = internal->sampleRate; | |||
| - processArgs.sampleTime = internal->sampleTime; | |||
| - processArgs.frame = internal->frame; | |||
| - | |||
| - // Step each module | |||
| - while (true) { | |||
| - // Choose next module | |||
| - // First-come-first serve module-to-thread allocation algorithm | |||
| - int i = internal->workerModuleIndex++; | |||
| - if (i >= modulesLen) | |||
| - break; | |||
| - | |||
| - Module* module = internal->modules[i]; | |||
| - module->doProcess(processArgs); | |||
| - } | |||
| -} | |||
| - | |||
| - | |||
| static void Cable_step(Cable* that) { | |||
| Output* output = &that->outputModule->outputs[that->outputId]; | |||
| Input* input = &that->inputModule->inputs[that->inputId]; | |||
| @@ -373,12 +166,12 @@ | |||
| } | |||
| // Step cables | |||
| - for (Cable* cable : that->internal->cables) { | |||
| + for (Cable* cable : internal->cables) { | |||
| Cable_step(cable); | |||
| } | |||
| // Flip messages for each module | |||
| - for (Module* module : that->internal->modules) { | |||
| + for (Module* module : internal->modules) { | |||
| if (module->leftExpander.messageFlipRequested) { | |||
| std::swap(module->leftExpander.producerMessage, module->leftExpander.consumerMessage); | |||
| module->leftExpander.messageFlipRequested = false; | |||
| @@ -389,13 +182,18 @@ | |||
| } | |||
| } | |||
| - // Step modules along with workers | |||
| - internal->workerModuleIndex = 0; | |||
| - internal->engineBarrier.wait(); | |||
| - Engine_stepWorker(that, 0); | |||
| - internal->workerBarrier.wait(); | |||
| + // Build ProcessArgs | |||
| + Module::ProcessArgs processArgs; | |||
| + processArgs.sampleRate = internal->sampleRate; | |||
| + processArgs.sampleTime = internal->sampleTime; | |||
| + processArgs.frame = internal->frame; | |||
| + | |||
| + // Step each module | |||
| + for (Module* module : internal->modules) { | |||
| + module->doProcess(processArgs); | |||
| + } | |||
| - internal->frame++; | |||
| + ++internal->frame; | |||
| } | |||
| @@ -460,37 +258,22 @@ | |||
| Engine::Engine() { | |||
| internal = new Internal; | |||
| - | |||
| - internal->context = contextGet(); | |||
| - setSuggestedSampleRate(0.f); | |||
| } | |||
| Engine::~Engine() { | |||
| - // Stop fallback thread if running | |||
| - { | |||
| - std::lock_guard<std::mutex> lock(internal->fallbackMutex); | |||
| - internal->fallbackRunning = false; | |||
| - internal->fallbackCv.notify_all(); | |||
| - } | |||
| - if (internal->fallbackThread.joinable()) | |||
| - internal->fallbackThread.join(); | |||
| - | |||
| - // Shut down workers | |||
| - Engine_relaunchWorkers(this, 0); | |||
| - | |||
| // Clear modules, cables, etc | |||
| clear(); | |||
| // Make sure there are no cables or modules in the rack on destruction. | |||
| // If this happens, a module must have failed to remove itself before the RackWidget was destroyed. | |||
| - assert(internal->cables.empty()); | |||
| - assert(internal->modules.empty()); | |||
| - assert(internal->paramHandles.empty()); | |||
| - | |||
| - assert(internal->modulesCache.empty()); | |||
| - assert(internal->cablesCache.empty()); | |||
| - assert(internal->paramHandlesCache.empty()); | |||
| + DISTRHO_SAFE_ASSERT(internal->cables.empty()); | |||
| + DISTRHO_SAFE_ASSERT(internal->modules.empty()); | |||
| + DISTRHO_SAFE_ASSERT(internal->paramHandles.empty()); | |||
| + | |||
| + DISTRHO_SAFE_ASSERT(internal->modulesCache.empty()); | |||
| + DISTRHO_SAFE_ASSERT(internal->cablesCache.empty()); | |||
| + DISTRHO_SAFE_ASSERT(internal->paramHandlesCache.empty()); | |||
| delete internal; | |||
| } | |||
| @@ -526,11 +309,8 @@ | |||
| // Start timer before locking | |||
| double startTime = system::getTime(); | |||
| - std::lock_guard<std::mutex> stepLock(internal->blockMutex); | |||
| SharedLock<SharedMutex> lock(internal->mutex); | |||
| // Configure thread | |||
| - uint32_t csr = _mm_getcsr(); | |||
| - initMXCSR(); | |||
| random::init(); | |||
| internal->blockFrame = internal->frame; | |||
| @@ -543,16 +323,11 @@ | |||
| Engine_updateExpander_NoLock(this, module, true); | |||
| } | |||
| - // Launch workers | |||
| - Engine_relaunchWorkers(this, settings::threadCount); | |||
| - | |||
| // Step individual frames | |||
| for (int i = 0; i < frames; i++) { | |||
| Engine_stepFrame(this); | |||
| } | |||
| - yieldWorkers(); | |||
| - | |||
| internal->block++; | |||
| // Stop timer | |||
| @@ -572,47 +347,19 @@ | |||
| internal->meterTotal = 0.0; | |||
| internal->meterMax = 0.0; | |||
| } | |||
| - | |||
| - // Reset MXCSR back to original value | |||
| - _mm_setcsr(csr); | |||
| } | |||
| void Engine::setMasterModule(Module* module) { | |||
| - if (module == internal->masterModule) | |||
| - return; | |||
| - std::lock_guard<SharedMutex> lock(internal->mutex); | |||
| - setMasterModule_NoLock(module); | |||
| } | |||
| void Engine::setMasterModule_NoLock(Module* module) { | |||
| - if (module == internal->masterModule) | |||
| - return; | |||
| - | |||
| - if (internal->masterModule) { | |||
| - // Dispatch UnsetMasterEvent | |||
| - Module::UnsetMasterEvent e; | |||
| - internal->masterModule->onUnsetMaster(e); | |||
| - } | |||
| - | |||
| - internal->masterModule = module; | |||
| - | |||
| - if (internal->masterModule) { | |||
| - // Dispatch SetMasterEvent | |||
| - Module::SetMasterEvent e; | |||
| - internal->masterModule->onSetMaster(e); | |||
| - } | |||
| - | |||
| - // Wake up fallback thread if master module was unset | |||
| - if (!internal->masterModule) { | |||
| - internal->fallbackCv.notify_all(); | |||
| - } | |||
| } | |||
| Module* Engine::getMasterModule() { | |||
| - return internal->masterModule; | |||
| + return nullptr; | |||
| } | |||
| @@ -639,16 +386,6 @@ | |||
| void Engine::setSuggestedSampleRate(float suggestedSampleRate) { | |||
| - if (settings::sampleRate > 0) { | |||
| - setSampleRate(settings::sampleRate); | |||
| - } | |||
| - else if (suggestedSampleRate > 0) { | |||
| - setSampleRate(suggestedSampleRate); | |||
| - } | |||
| - else { | |||
| - // Fallback sample rate | |||
| - setSampleRate(44100.f); | |||
| - } | |||
| } | |||
| @@ -658,7 +395,6 @@ | |||
| void Engine::yieldWorkers() { | |||
| - internal->workerBarrier.yield(); | |||
| } | |||
| @@ -738,10 +474,10 @@ | |||
| void Engine::addModule(Module* module) { | |||
| std::lock_guard<SharedMutex> lock(internal->mutex); | |||
| - assert(module); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(module,); | |||
| // Check that the module is not already added | |||
| auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | |||
| - assert(it == internal->modules.end()); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(it == internal->modules.end(),); | |||
| // Set ID if unset or collides with an existing ID | |||
| while (module->id < 0 || internal->modulesCache.find(module->id) != internal->modulesCache.end()) { | |||
| // Randomly generate ID | |||
| @@ -773,10 +509,14 @@ | |||
| void Engine::removeModule_NoLock(Module* module) { | |||
| - assert(module); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(module,); | |||
| // Check that the module actually exists | |||
| auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | |||
| - assert(it != internal->modules.end()); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(it != internal->modules.end(),); | |||
| + // Remove from widgets cache | |||
| + CardinalPluginModelHelper* const helper = dynamic_cast<CardinalPluginModelHelper*>(module->model); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(helper != nullptr,); | |||
| + helper->removeCachedModuleWidget(module); | |||
| // Dispatch RemoveEvent | |||
| Module::RemoveEvent eRemove; | |||
| module->onRemove(eRemove); | |||
| @@ -785,18 +525,14 @@ | |||
| if (paramHandle->moduleId == module->id) | |||
| paramHandle->module = NULL; | |||
| } | |||
| - // Unset master module | |||
| - if (getMasterModule() == module) { | |||
| - setMasterModule_NoLock(NULL); | |||
| - } | |||
| // If a param is being smoothed on this module, stop smoothing it immediately | |||
| if (module == internal->smoothModule) { | |||
| internal->smoothModule = NULL; | |||
| } | |||
| // Check that all cables are disconnected | |||
| for (Cable* cable : internal->cables) { | |||
| - assert(cable->inputModule != module); | |||
| - assert(cable->outputModule != module); | |||
| + DISTRHO_SAFE_ASSERT(cable->inputModule != module); | |||
| + DISTRHO_SAFE_ASSERT(cable->outputModule != module); | |||
| } | |||
| // Update expanders of other modules | |||
| for (Module* m : internal->modules) { | |||
| @@ -844,7 +580,7 @@ | |||
| void Engine::resetModule(Module* module) { | |||
| std::lock_guard<SharedMutex> lock(internal->mutex); | |||
| - assert(module); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(module,); | |||
| Module::ResetEvent eReset; | |||
| module->onReset(eReset); | |||
| @@ -853,7 +589,7 @@ | |||
| void Engine::randomizeModule(Module* module) { | |||
| std::lock_guard<SharedMutex> lock(internal->mutex); | |||
| - assert(module); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(module,); | |||
| Module::RandomizeEvent eRandomize; | |||
| module->onRandomize(eRandomize); | |||
| @@ -861,7 +597,7 @@ | |||
| void Engine::bypassModule(Module* module, bool bypassed) { | |||
| - assert(module); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(module,); | |||
| if (module->isBypassed() == bypassed) | |||
| return; | |||
| @@ -946,16 +682,16 @@ | |||
| void Engine::addCable(Cable* cable) { | |||
| std::lock_guard<SharedMutex> lock(internal->mutex); | |||
| - assert(cable); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(cable,); | |||
| // Check cable properties | |||
| - assert(cable->inputModule); | |||
| - assert(cable->outputModule); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(cable->inputModule,); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(cable->outputModule,); | |||
| bool outputWasConnected = false; | |||
| for (Cable* cable2 : internal->cables) { | |||
| // Check that the cable is not already added | |||
| - assert(cable2 != cable); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(cable2 != cable,); | |||
| // Check that the input is not already used by another cable | |||
| - assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId)); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId),); | |||
| // Get connected status of output, to decide whether we need to call a PortChangeEvent. | |||
| // It's best to not trust `cable->outputModule->outputs[cable->outputId]->isConnected()` | |||
| if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) | |||
| @@ -996,10 +732,10 @@ | |||
| void Engine::removeCable_NoLock(Cable* cable) { | |||
| - assert(cable); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(cable,); | |||
| // Check that the cable is already added | |||
| auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); | |||
| - assert(it != internal->cables.end()); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(it != internal->cables.end(),); | |||
| // Remove the cable | |||
| internal->cablesCache.erase(cable->id); | |||
| internal->cables.erase(it); | |||
| @@ -1085,11 +821,11 @@ | |||
| std::lock_guard<SharedMutex> lock(internal->mutex); | |||
| // New ParamHandles must be blank. | |||
| // This means we don't have to refresh the cache. | |||
| - assert(paramHandle->moduleId < 0); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(paramHandle->moduleId < 0,); | |||
| // Check that the ParamHandle is not already added | |||
| auto it = internal->paramHandles.find(paramHandle); | |||
| - assert(it == internal->paramHandles.end()); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(it == internal->paramHandles.end(),); | |||
| // Add it | |||
| internal->paramHandles.insert(paramHandle); | |||
| @@ -1106,7 +842,7 @@ | |||
| void Engine::removeParamHandle_NoLock(ParamHandle* paramHandle) { | |||
| // Check that the ParamHandle is already added | |||
| auto it = internal->paramHandles.find(paramHandle); | |||
| - assert(it != internal->paramHandles.end()); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(it != internal->paramHandles.end(),); | |||
| // Remove it | |||
| paramHandle->module = NULL; | |||
| @@ -1143,7 +879,7 @@ | |||
| void Engine::updateParamHandle_NoLock(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite) { | |||
| // Check that it exists | |||
| auto it = internal->paramHandles.find(paramHandle); | |||
| - assert(it != internal->paramHandles.end()); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(it != internal->paramHandles.end(),); | |||
| // Set IDs | |||
| paramHandle->moduleId = moduleId; | |||
| @@ -1197,11 +933,6 @@ | |||
| } | |||
| json_object_set_new(rootJ, "cables", cablesJ); | |||
| - // masterModule | |||
| - if (internal->masterModule) { | |||
| - json_object_set_new(rootJ, "masterModuleId", json_integer(internal->masterModule->id)); | |||
| - } | |||
| - | |||
| return rootJ; | |||
| } | |||
| @@ -1225,14 +956,20 @@ | |||
| } | |||
| catch (Exception& e) { | |||
| WARN("Cannot load model: %s", e.what()); | |||
| - APP->patch->log(e.what()); | |||
| + // APP->patch->log(e.what()); | |||
| continue; | |||
| } | |||
| // Create module | |||
| - INFO("Creating module %s", model->getFullName().c_str()); | |||
| - Module* module = model->createModule(); | |||
| - assert(module); | |||
| + Module* const module = model->createModule(); | |||
| + DISTRHO_SAFE_ASSERT_CONTINUE(module != nullptr); | |||
| + | |||
| + // Create the widget too, needed by a few modules | |||
| + CardinalPluginModelHelper* const helper = dynamic_cast<CardinalPluginModelHelper*>(model); | |||
| + DISTRHO_SAFE_ASSERT_CONTINUE(helper != nullptr); | |||
| + | |||
| + app::ModuleWidget* const moduleWidget = helper->createModuleWidgetFromEngineLoad(module); | |||
| + DISTRHO_SAFE_ASSERT_CONTINUE(moduleWidget != nullptr); | |||
| try { | |||
| // This doesn't need a lock because the Module is not added to the Engine yet. | |||
| @@ -1248,7 +985,8 @@ | |||
| } | |||
| catch (Exception& e) { | |||
| WARN("Cannot load module: %s", e.what()); | |||
| - APP->patch->log(e.what()); | |||
| + // APP->patch->log(e.what()); | |||
| + helper->removeCachedModuleWidget(module); | |||
| delete module; | |||
| continue; | |||
| } | |||
| @@ -1285,67 +1023,10 @@ | |||
| continue; | |||
| } | |||
| } | |||
| - | |||
| - // masterModule | |||
| - json_t* masterModuleIdJ = json_object_get(rootJ, "masterModuleId"); | |||
| - if (masterModuleIdJ) { | |||
| - Module* masterModule = getModule(json_integer_value(masterModuleIdJ)); | |||
| - setMasterModule(masterModule); | |||
| - } | |||
| -} | |||
| - | |||
| - | |||
| -void EngineWorker::run() { | |||
| - // Configure thread | |||
| - contextSet(engine->internal->context); | |||
| - system::setThreadName(string::f("Worker %d", id)); | |||
| - initMXCSR(); | |||
| - random::init(); | |||
| - | |||
| - while (true) { | |||
| - engine->internal->engineBarrier.wait(); | |||
| - if (!running) | |||
| - return; | |||
| - Engine_stepWorker(engine, id); | |||
| - engine->internal->workerBarrier.wait(); | |||
| - } | |||
| -} | |||
| - | |||
| - | |||
| -static void Engine_fallbackRun(Engine* that) { | |||
| - system::setThreadName("Engine fallback"); | |||
| - contextSet(that->internal->context); | |||
| - | |||
| - while (that->internal->fallbackRunning) { | |||
| - if (!that->getMasterModule()) { | |||
| - // Step blocks and wait | |||
| - double start = system::getTime(); | |||
| - int frames = std::floor(that->getSampleRate() / 60); | |||
| - that->stepBlock(frames); | |||
| - double end = system::getTime(); | |||
| - | |||
| - double duration = frames * that->getSampleTime() - (end - start); | |||
| - if (duration > 0.0) { | |||
| - std::this_thread::sleep_for(std::chrono::duration<double>(duration)); | |||
| - } | |||
| - } | |||
| - else { | |||
| - // Wait for master module to be unset, or for the request to stop running | |||
| - std::unique_lock<std::mutex> lock(that->internal->fallbackMutex); | |||
| - that->internal->fallbackCv.wait(lock, [&]() { | |||
| - return !that->internal->fallbackRunning || !that->getMasterModule(); | |||
| - }); | |||
| - } | |||
| - } | |||
| } | |||
| void Engine::startFallbackThread() { | |||
| - if (internal->fallbackThread.joinable()) | |||
| - return; | |||
| - | |||
| - internal->fallbackRunning = true; | |||
| - internal->fallbackThread = std::thread(Engine_fallbackRun, this); | |||
| } | |||
| @@ -0,0 +1,704 @@ | |||
| --- ../Rack/src/app/MenuBar.cpp 2022-01-15 14:44:46.391280963 +0000 | |||
| +++ MenuBar.cpp 2022-01-23 17:13:16.500279828 +0000 | |||
| @@ -1,8 +1,33 @@ | |||
| +/* | |||
| + * DISTRHO Cardinal Plugin | |||
| + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| + * | |||
| + * 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. | |||
| + */ | |||
| + | |||
| +/** | |||
| + * This file is an edited version of VCVRack's app/MenuBar.cpp | |||
| + * Copyright (C) 2016-2021 VCV. | |||
| + * | |||
| + * 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 (at your option) any later version. | |||
| + */ | |||
| + | |||
| #include <thread> | |||
| #include <utility> | |||
| -#include <osdialog.h> | |||
| - | |||
| #include <app/MenuBar.hpp> | |||
| #include <app/TipWindow.hpp> | |||
| #include <widget/OpaqueWidget.hpp> | |||
| @@ -25,6 +50,11 @@ | |||
| #include <patch.hpp> | |||
| #include <library.hpp> | |||
| +#ifdef HAVE_LIBLO | |||
| +# include <lo/lo.h> | |||
| +#endif | |||
| + | |||
| +#include "../CardinalCommon.hpp" | |||
| namespace rack { | |||
| namespace app { | |||
| @@ -48,80 +78,108 @@ | |||
| }; | |||
| -struct NotificationIcon : widget::Widget { | |||
| - void draw(const DrawArgs& args) override { | |||
| - nvgBeginPath(args.vg); | |||
| - float radius = 4; | |||
| - nvgCircle(args.vg, radius, radius, radius); | |||
| - nvgFillColor(args.vg, nvgRGBf(1.0, 0.0, 0.0)); | |||
| - nvgFill(args.vg); | |||
| - nvgStrokeColor(args.vg, nvgRGBf(0.5, 0.0, 0.0)); | |||
| - nvgStroke(args.vg); | |||
| - } | |||
| -}; | |||
| - | |||
| - | |||
| //////////////////// | |||
| // File | |||
| //////////////////// | |||
| struct FileButton : MenuButton { | |||
| + const bool isStandalone; | |||
| + | |||
| +#ifdef HAVE_LIBLO | |||
| + bool oscConnected = false; | |||
| + lo_server oscServer = nullptr; | |||
| + | |||
| + static int osc_handler(const char* const path, const char* const types, lo_arg** argv, const int argc, lo_message, void* const self) | |||
| + { | |||
| + d_stdout("osc_handler(\"%s\", \"%s\", %p, %i)", path, types, argv, argc); | |||
| + | |||
| + if (std::strcmp(path, "/resp") == 0 && argc == 2 && types[0] == 's' && types[1] == 's') { | |||
| + d_stdout("osc_handler(\"%s\", ...) - got resp | '%s' '%s'", path, &argv[0]->s, &argv[1]->s); | |||
| + if (std::strcmp(&argv[0]->s, "hello") == 0 && std::strcmp(&argv[1]->s, "ok") == 0) | |||
| + static_cast<FileButton*>(self)->oscConnected = true; | |||
| + } | |||
| + return 0; | |||
| + } | |||
| + | |||
| + ~FileButton() { | |||
| + lo_server_free(oscServer); | |||
| + } | |||
| +#endif | |||
| + | |||
| + FileButton(const bool standalone) | |||
| + : MenuButton(), isStandalone(standalone) {} | |||
| + | |||
| void onAction(const ActionEvent& e) override { | |||
| ui::Menu* menu = createMenu(); | |||
| menu->cornerFlags = BND_CORNER_TOP; | |||
| menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); | |||
| menu->addChild(createMenuItem("New", RACK_MOD_CTRL_NAME "+N", []() { | |||
| - APP->patch->loadTemplateDialog(); | |||
| + patchUtils::loadTemplateDialog(); | |||
| })); | |||
| - menu->addChild(createMenuItem("Open", RACK_MOD_CTRL_NAME "+O", []() { | |||
| - APP->patch->loadDialog(); | |||
| + menu->addChild(createMenuItem("Open / Import...", RACK_MOD_CTRL_NAME "+O", []() { | |||
| + patchUtils::loadDialog(); | |||
| })); | |||
| - menu->addChild(createSubmenuItem("Open recent", "", [](ui::Menu* menu) { | |||
| - for (const std::string& path : settings::recentPatchPaths) { | |||
| - std::string name = system::getStem(path); | |||
| - menu->addChild(createMenuItem(name, "", [=]() { | |||
| - APP->patch->loadPathDialog(path); | |||
| - })); | |||
| - } | |||
| - }, settings::recentPatchPaths.empty())); | |||
| - | |||
| menu->addChild(createMenuItem("Save", RACK_MOD_CTRL_NAME "+S", []() { | |||
| - APP->patch->saveDialog(); | |||
| - })); | |||
| - | |||
| - menu->addChild(createMenuItem("Save as", RACK_MOD_CTRL_NAME "+Shift+S", []() { | |||
| - APP->patch->saveAsDialog(); | |||
| + // NOTE: will do nothing if path is empty, intentionally | |||
| + patchUtils::saveDialog(APP->patch->path); | |||
| + }, APP->patch->path.empty())); | |||
| + | |||
| + menu->addChild(createMenuItem("Save as / Export...", RACK_MOD_CTRL_NAME "+Shift+S", []() { | |||
| + patchUtils::saveAsDialog(); | |||
| })); | |||
| - menu->addChild(createMenuItem("Save a copy", "", []() { | |||
| - APP->patch->saveAsDialog(false); | |||
| - })); | |||
| +#ifdef HAVE_LIBLO | |||
| + if (oscServer == nullptr || !oscConnected) { | |||
| + menu->addChild(createMenuItem("Connect to MOD", "", [this]() { | |||
| + if (oscServer == nullptr) { | |||
| + oscServer = lo_server_new_with_proto(nullptr, LO_UDP, nullptr); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr,); | |||
| + lo_server_add_method(oscServer, "/resp", nullptr, osc_handler, this); | |||
| + } | |||
| + const lo_address addr = lo_address_new_with_proto(LO_UDP, REMOTE_HOST, REMOTE_HOST_PORT); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr,); | |||
| + lo_send(addr, "/hello", ""); | |||
| + lo_address_free(addr); | |||
| + })); | |||
| + } else { | |||
| + menu->addChild(createMenuItem("Deploy to MOD", "F7", []() { | |||
| + patchUtils::deployToMOD(); | |||
| + })); | |||
| + } | |||
| +#endif | |||
| menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() { | |||
| - APP->patch->revertDialog(); | |||
| - }, APP->patch->path == "")); | |||
| - | |||
| - menu->addChild(createMenuItem("Overwrite template", "", []() { | |||
| - APP->patch->saveTemplateDialog(); | |||
| - })); | |||
| + patchUtils::revertDialog(); | |||
| + }, APP->patch->path.empty())); | |||
| menu->addChild(new ui::MenuSeparator); | |||
| // Load selection | |||
| menu->addChild(createMenuItem("Import selection", "", [=]() { | |||
| - APP->scene->rack->loadSelectionDialog(); | |||
| + patchUtils::loadSelectionDialog(); | |||
| }, false, true)); | |||
| - menu->addChild(new ui::MenuSeparator); | |||
| + if (isStandalone) { | |||
| + menu->addChild(new ui::MenuSeparator); | |||
| - menu->addChild(createMenuItem("Quit", RACK_MOD_CTRL_NAME "+Q", []() { | |||
| - APP->window->close(); | |||
| - })); | |||
| + menu->addChild(createMenuItem("Quit", RACK_MOD_CTRL_NAME "+Q", []() { | |||
| + APP->window->close(); | |||
| + })); | |||
| + }; | |||
| } | |||
| + | |||
| +#ifdef HAVE_LIBLO | |||
| + void step() override { | |||
| + MenuButton::step(); | |||
| + if (oscServer != nullptr) { | |||
| + while (lo_server_recv_noblock(oscServer, 0) != 0) {} | |||
| + } | |||
| + } | |||
| +#endif | |||
| }; | |||
| @@ -166,7 +224,7 @@ | |||
| menu->addChild(new ui::MenuSeparator); | |||
| - APP->scene->rack->appendSelectionContextMenu(menu); | |||
| + patchUtils::appendSelectionContextMenu(menu); | |||
| } | |||
| }; | |||
| @@ -256,7 +314,7 @@ | |||
| return settings::cableTension; | |||
| } | |||
| float getDefaultValue() override { | |||
| - return 0.5; | |||
| + return 0.75; | |||
| } | |||
| float getDisplayValue() override { | |||
| return getValue() * 100; | |||
| @@ -421,28 +479,9 @@ | |||
| haloBrightnessSlider->box.size.x = 250.0; | |||
| menu->addChild(haloBrightnessSlider); | |||
| - double frameRate = APP->window->getMonitorRefreshRate() / settings::frameSwapInterval; | |||
| - menu->addChild(createSubmenuItem("Frame rate", string::f("%.0f Hz", frameRate), [=](ui::Menu* menu) { | |||
| - for (int i = 1; i <= 6; i++) { | |||
| - double frameRate = APP->window->getMonitorRefreshRate() / i; | |||
| - menu->addChild(createCheckMenuItem(string::f("%.0f Hz", frameRate), "", | |||
| - [=]() {return settings::frameSwapInterval == i;}, | |||
| - [=]() {settings::frameSwapInterval = i;} | |||
| - )); | |||
| - } | |||
| - })); | |||
| - | |||
| - bool fullscreen = APP->window->isFullScreen(); | |||
| - std::string fullscreenText = "F11"; | |||
| - if (fullscreen) | |||
| - fullscreenText += " " CHECKMARK_STRING; | |||
| - menu->addChild(createMenuItem("Fullscreen", fullscreenText, [=]() { | |||
| - APP->window->setFullScreen(!fullscreen); | |||
| - })); | |||
| - | |||
| menu->addChild(new ui::MenuSeparator); | |||
| - menu->addChild(createBoolPtrMenuItem("Lock cursor while dragging params", "", &settings::allowCursorLock)); | |||
| + // menu->addChild(createBoolPtrMenuItem("Hide cursor while dragging", "", &settings::allowCursorLock)); | |||
| static const std::vector<std::string> knobModeLabels = { | |||
| "Linear", | |||
| @@ -467,6 +506,21 @@ | |||
| menu->addChild(knobScrollSensitivitySlider); | |||
| menu->addChild(createBoolPtrMenuItem("Lock module positions", "", &settings::lockModules)); | |||
| + | |||
| + static const std::vector<std::string> rateLimitLabels = { | |||
| + "None", | |||
| + "2x", | |||
| + "4x", | |||
| + }; | |||
| + static const std::vector<int> rateLimits = {0, 1, 2}; | |||
| + menu->addChild(createSubmenuItem("Update rate limit", rateLimitLabels[settings::rateLimit], [=](ui::Menu* menu) { | |||
| + for (int rateLimit : rateLimits) { | |||
| + menu->addChild(createCheckMenuItem(rateLimitLabels[rateLimit], "", | |||
| + [=]() {return settings::rateLimit == rateLimit;}, | |||
| + [=]() {settings::rateLimit = rateLimit;} | |||
| + )); | |||
| + } | |||
| + })); | |||
| } | |||
| }; | |||
| @@ -476,47 +530,6 @@ | |||
| //////////////////// | |||
| -struct SampleRateItem : ui::MenuItem { | |||
| - ui::Menu* createChildMenu() override { | |||
| - ui::Menu* menu = new ui::Menu; | |||
| - | |||
| - // Auto sample rate | |||
| - std::string rightText; | |||
| - if (settings::sampleRate == 0) { | |||
| - float sampleRate = APP->engine->getSampleRate(); | |||
| - rightText += string::f("(%g kHz) ", sampleRate / 1000.f); | |||
| - } | |||
| - menu->addChild(createCheckMenuItem("Auto", rightText, | |||
| - [=]() {return settings::sampleRate == 0;}, | |||
| - [=]() {settings::sampleRate = 0;} | |||
| - )); | |||
| - | |||
| - // Power-of-2 oversample times 44.1kHz or 48kHz | |||
| - for (int i = -2; i <= 4; i++) { | |||
| - for (int j = 0; j < 2; j++) { | |||
| - float oversample = std::pow(2.f, i); | |||
| - float sampleRate = (j == 0) ? 44100.f : 48000.f; | |||
| - sampleRate *= oversample; | |||
| - | |||
| - std::string text = string::f("%g kHz", sampleRate / 1000.f); | |||
| - std::string rightText; | |||
| - if (oversample > 1.f) { | |||
| - rightText += string::f("(%.0fx)", oversample); | |||
| - } | |||
| - else if (oversample < 1.f) { | |||
| - rightText += string::f("(1/%.0fx)", 1.f / oversample); | |||
| - } | |||
| - menu->addChild(createCheckMenuItem(text, rightText, | |||
| - [=]() {return settings::sampleRate == sampleRate;}, | |||
| - [=]() {settings::sampleRate = sampleRate;} | |||
| - )); | |||
| - } | |||
| - } | |||
| - return menu; | |||
| - } | |||
| -}; | |||
| - | |||
| - | |||
| struct EngineButton : MenuButton { | |||
| void onAction(const ActionEvent& e) override { | |||
| ui::Menu* menu = createMenu(); | |||
| @@ -529,269 +542,6 @@ | |||
| menu->addChild(createMenuItem("Performance meters", cpuMeterText, [=]() { | |||
| settings::cpuMeter ^= true; | |||
| })); | |||
| - | |||
| - menu->addChild(createMenuItem<SampleRateItem>("Sample rate", RIGHT_ARROW)); | |||
| - | |||
| - menu->addChild(createSubmenuItem("Threads", string::f("%d", settings::threadCount), [=](ui::Menu* menu) { | |||
| - // BUG This assumes SMT is enabled. | |||
| - int cores = system::getLogicalCoreCount() / 2; | |||
| - | |||
| - for (int i = 1; i <= 2 * cores; i++) { | |||
| - std::string rightText; | |||
| - if (i == cores) | |||
| - rightText += "(most modules)"; | |||
| - else if (i == 1) | |||
| - rightText += "(lowest CPU usage)"; | |||
| - menu->addChild(createCheckMenuItem(string::f("%d", i), rightText, | |||
| - [=]() {return settings::threadCount == i;}, | |||
| - [=]() {settings::threadCount = i;} | |||
| - )); | |||
| - } | |||
| - })); | |||
| - } | |||
| -}; | |||
| - | |||
| - | |||
| -//////////////////// | |||
| -// Plugins | |||
| -//////////////////// | |||
| - | |||
| - | |||
| -struct AccountPasswordField : ui::PasswordField { | |||
| - ui::MenuItem* logInItem; | |||
| - void onAction(const ActionEvent& e) override { | |||
| - logInItem->doAction(); | |||
| - } | |||
| -}; | |||
| - | |||
| - | |||
| -struct LogInItem : ui::MenuItem { | |||
| - ui::TextField* emailField; | |||
| - ui::TextField* passwordField; | |||
| - | |||
| - void onAction(const ActionEvent& e) override { | |||
| - std::string email = emailField->text; | |||
| - std::string password = passwordField->text; | |||
| - std::thread t([=] { | |||
| - library::logIn(email, password); | |||
| - library::checkUpdates(); | |||
| - }); | |||
| - t.detach(); | |||
| - e.unconsume(); | |||
| - } | |||
| - | |||
| - void step() override { | |||
| - text = "Log in"; | |||
| - rightText = library::loginStatus; | |||
| - MenuItem::step(); | |||
| - } | |||
| -}; | |||
| - | |||
| - | |||
| -struct SyncUpdatesItem : ui::MenuItem { | |||
| - void step() override { | |||
| - if (library::updateStatus != "") { | |||
| - text = library::updateStatus; | |||
| - } | |||
| - else if (library::isSyncing) { | |||
| - text = "Updating..."; | |||
| - } | |||
| - else if (!library::hasUpdates()) { | |||
| - text = "Up-to-date"; | |||
| - } | |||
| - else { | |||
| - text = "Update all"; | |||
| - } | |||
| - | |||
| - disabled = library::isSyncing || !library::hasUpdates(); | |||
| - MenuItem::step(); | |||
| - } | |||
| - | |||
| - void onAction(const ActionEvent& e) override { | |||
| - std::thread t([=] { | |||
| - library::syncUpdates(); | |||
| - }); | |||
| - t.detach(); | |||
| - e.unconsume(); | |||
| - } | |||
| -}; | |||
| - | |||
| - | |||
| -struct SyncUpdateItem : ui::MenuItem { | |||
| - std::string slug; | |||
| - | |||
| - void setUpdate(const std::string& slug) { | |||
| - this->slug = slug; | |||
| - | |||
| - auto it = library::updateInfos.find(slug); | |||
| - if (it == library::updateInfos.end()) | |||
| - return; | |||
| - library::UpdateInfo update = it->second; | |||
| - | |||
| - text = update.name; | |||
| - } | |||
| - | |||
| - ui::Menu* createChildMenu() override { | |||
| - auto it = library::updateInfos.find(slug); | |||
| - if (it == library::updateInfos.end()) | |||
| - return NULL; | |||
| - library::UpdateInfo update = it->second; | |||
| - | |||
| - if (update.changelogUrl == "") | |||
| - return NULL; | |||
| - | |||
| - ui::Menu* menu = new ui::Menu; | |||
| - | |||
| - std::string changelogUrl = update.changelogUrl; | |||
| - menu->addChild(createMenuItem("Changelog", "", [=]() { | |||
| - system::openBrowser(changelogUrl); | |||
| - })); | |||
| - | |||
| - return menu; | |||
| - } | |||
| - | |||
| - void step() override { | |||
| - disabled = library::isSyncing; | |||
| - | |||
| - auto it = library::updateInfos.find(slug); | |||
| - if (it != library::updateInfos.end()) { | |||
| - library::UpdateInfo update = it->second; | |||
| - | |||
| - if (update.downloaded) { | |||
| - rightText = CHECKMARK_STRING; | |||
| - disabled = true; | |||
| - } | |||
| - else if (slug == library::updateSlug) { | |||
| - rightText = string::f("%.0f%%", library::updateProgress * 100.f); | |||
| - } | |||
| - else { | |||
| - rightText = ""; | |||
| - plugin::Plugin* p = plugin::getPlugin(slug); | |||
| - if (p) { | |||
| - rightText += p->version + " → "; | |||
| - } | |||
| - rightText += update.version; | |||
| - } | |||
| - } | |||
| - | |||
| - MenuItem::step(); | |||
| - } | |||
| - | |||
| - void onAction(const ActionEvent& e) override { | |||
| - std::thread t([=] { | |||
| - library::syncUpdate(slug); | |||
| - }); | |||
| - t.detach(); | |||
| - e.unconsume(); | |||
| - } | |||
| -}; | |||
| - | |||
| - | |||
| -struct LibraryMenu : ui::Menu { | |||
| - LibraryMenu() { | |||
| - refresh(); | |||
| - } | |||
| - | |||
| - void step() override { | |||
| - // Refresh menu when appropriate | |||
| - if (library::refreshRequested) { | |||
| - library::refreshRequested = false; | |||
| - refresh(); | |||
| - } | |||
| - Menu::step(); | |||
| - } | |||
| - | |||
| - void refresh() { | |||
| - setChildMenu(NULL); | |||
| - clearChildren(); | |||
| - | |||
| - if (settings::devMode) { | |||
| - addChild(createMenuLabel("Disabled in development mode")); | |||
| - } | |||
| - else if (!library::isLoggedIn()) { | |||
| - addChild(createMenuItem("Register VCV account", "", [=]() { | |||
| - system::openBrowser("https://vcvrack.com/login"); | |||
| - })); | |||
| - | |||
| - ui::TextField* emailField = new ui::TextField; | |||
| - emailField->placeholder = "Email"; | |||
| - emailField->box.size.x = 240.0; | |||
| - addChild(emailField); | |||
| - | |||
| - AccountPasswordField* passwordField = new AccountPasswordField; | |||
| - passwordField->placeholder = "Password"; | |||
| - passwordField->box.size.x = 240.0; | |||
| - passwordField->nextField = emailField; | |||
| - emailField->nextField = passwordField; | |||
| - addChild(passwordField); | |||
| - | |||
| - LogInItem* logInItem = new LogInItem; | |||
| - logInItem->emailField = emailField; | |||
| - logInItem->passwordField = passwordField; | |||
| - passwordField->logInItem = logInItem; | |||
| - addChild(logInItem); | |||
| - } | |||
| - else { | |||
| - addChild(createMenuItem("Log out", "", [=]() { | |||
| - library::logOut(); | |||
| - })); | |||
| - | |||
| - addChild(createMenuItem("Browse VCV Library", "", [=]() { | |||
| - system::openBrowser("https://library.vcvrack.com/"); | |||
| - })); | |||
| - | |||
| - SyncUpdatesItem* syncItem = new SyncUpdatesItem; | |||
| - syncItem->text = "Update all"; | |||
| - addChild(syncItem); | |||
| - | |||
| - if (!library::updateInfos.empty()) { | |||
| - addChild(new ui::MenuSeparator); | |||
| - addChild(createMenuLabel("Updates")); | |||
| - | |||
| - for (auto& pair : library::updateInfos) { | |||
| - SyncUpdateItem* updateItem = new SyncUpdateItem; | |||
| - updateItem->setUpdate(pair.first); | |||
| - addChild(updateItem); | |||
| - } | |||
| - } | |||
| - } | |||
| - } | |||
| -}; | |||
| - | |||
| - | |||
| -struct LibraryButton : MenuButton { | |||
| - NotificationIcon* notification; | |||
| - | |||
| - LibraryButton() { | |||
| - notification = new NotificationIcon; | |||
| - addChild(notification); | |||
| - } | |||
| - | |||
| - void onAction(const ActionEvent& e) override { | |||
| - ui::Menu* menu = createMenu<LibraryMenu>(); | |||
| - menu->cornerFlags = BND_CORNER_TOP; | |||
| - menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); | |||
| - // Check for updates when menu is opened | |||
| - std::thread t([&]() { | |||
| - system::setThreadName("Library"); | |||
| - library::checkUpdates(); | |||
| - }); | |||
| - t.detach(); | |||
| - } | |||
| - | |||
| - void step() override { | |||
| - notification->box.pos = math::Vec(0, 0); | |||
| - notification->visible = library::hasUpdates(); | |||
| - | |||
| - // Popup when updates finish downloading | |||
| - if (library::restartRequested) { | |||
| - library::restartRequested = false; | |||
| - if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been downloaded. Close and re-launch Rack to load new updates.")) { | |||
| - APP->window->close(); | |||
| - } | |||
| - } | |||
| - | |||
| - MenuButton::step(); | |||
| } | |||
| }; | |||
| @@ -802,63 +552,24 @@ | |||
| struct HelpButton : MenuButton { | |||
| - NotificationIcon* notification; | |||
| - | |||
| - HelpButton() { | |||
| - notification = new NotificationIcon; | |||
| - addChild(notification); | |||
| - } | |||
| - | |||
| void onAction(const ActionEvent& e) override { | |||
| ui::Menu* menu = createMenu(); | |||
| menu->cornerFlags = BND_CORNER_TOP; | |||
| menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); | |||
| - menu->addChild(createMenuItem("Tips", "", [=]() { | |||
| - APP->scene->addChild(tipWindowCreate()); | |||
| - })); | |||
| - | |||
| - menu->addChild(createMenuItem("User manual", "F1", [=]() { | |||
| + menu->addChild(createMenuItem("Rack User manual", "F1", [=]() { | |||
| system::openBrowser("https://vcvrack.com/manual/"); | |||
| })); | |||
| - menu->addChild(createMenuItem("VCVRack.com", "", [=]() { | |||
| - system::openBrowser("https://vcvrack.com/"); | |||
| - })); | |||
| - | |||
| - menu->addChild(createMenuItem("Open user folder", "", [=]() { | |||
| - system::openDirectory(asset::user("")); | |||
| + menu->addChild(createMenuItem("Cardinal Project page", "", [=]() { | |||
| + system::openBrowser("https://github.com/DISTRHO/Cardinal/"); | |||
| })); | |||
| - if (library::isAppUpdateAvailable()) { | |||
| - menu->addChild(new ui::MenuSeparator); | |||
| - | |||
| - menu->addChild(createMenuItem("Update " + APP_NAME, APP_VERSION + " → " + library::appVersion, [=]() { | |||
| - system::openBrowser(library::appDownloadUrl); | |||
| - })); | |||
| - | |||
| - menu->addChild(createMenuItem("Review changelog", "", [=]() { | |||
| - system::openBrowser(library::appChangelogUrl); | |||
| - })); | |||
| - } | |||
| - else if (!settings::autoCheckUpdates && !settings::devMode) { | |||
| - menu->addChild(createMenuItem("Check for " + APP_NAME + " update", "", [=]() { | |||
| - std::thread t([&]() { | |||
| - library::checkAppUpdate(); | |||
| - }); | |||
| - t.detach(); | |||
| - }, false, true)); | |||
| - } | |||
| - | |||
| menu->addChild(new ui::MenuSeparator); | |||
| - menu->addChild(createMenuLabel(APP_NAME + " " + APP_EDITION_NAME + " " + APP_VERSION)); | |||
| - } | |||
| + menu->addChild(createMenuLabel(APP_EDITION + " " + APP_EDITION_NAME)); | |||
| - void step() override { | |||
| - notification->box.pos = math::Vec(0, 0); | |||
| - notification->visible = library::isAppUpdateAvailable(); | |||
| - MenuButton::step(); | |||
| + menu->addChild(createMenuLabel("Rack " + APP_VERSION + " Compatible")); | |||
| } | |||
| }; | |||
| @@ -908,7 +619,9 @@ | |||
| struct MenuBar : widget::OpaqueWidget { | |||
| MeterLabel* meterLabel; | |||
| - MenuBar() { | |||
| + MenuBar(const bool isStandalone) | |||
| + : widget::OpaqueWidget() | |||
| + { | |||
| const float margin = 5; | |||
| box.size.y = BND_WIDGET_HEIGHT + 2 * margin; | |||
| @@ -917,7 +630,7 @@ | |||
| layout->spacing = math::Vec(0, 0); | |||
| addChild(layout); | |||
| - FileButton* fileButton = new FileButton; | |||
| + FileButton* fileButton = new FileButton(isStandalone); | |||
| fileButton->text = "File"; | |||
| layout->addChild(fileButton); | |||
| @@ -933,10 +646,6 @@ | |||
| engineButton->text = "Engine"; | |||
| layout->addChild(engineButton); | |||
| - LibraryButton* libraryButton = new LibraryButton; | |||
| - libraryButton->text = "Library"; | |||
| - layout->addChild(libraryButton); | |||
| - | |||
| HelpButton* helpButton = new HelpButton; | |||
| helpButton->text = "Help"; | |||
| layout->addChild(helpButton); | |||
| @@ -971,7 +680,11 @@ | |||
| widget::Widget* createMenuBar() { | |||
| - menuBar::MenuBar* menuBar = new menuBar::MenuBar; | |||
| + return new widget::Widget; | |||
| +} | |||
| + | |||
| +widget::Widget* createMenuBar(const bool isStandalone) { | |||
| + menuBar::MenuBar* menuBar = new menuBar::MenuBar(isStandalone); | |||
| return menuBar; | |||
| } | |||
| @@ -0,0 +1,129 @@ | |||
| --- ../Rack/src/plugin/Model.cpp 2021-10-17 13:57:23.257633662 +0100 | |||
| +++ Model.cpp 2022-01-23 17:13:22.080013846 +0000 | |||
| @@ -1,3 +1,30 @@ | |||
| +/* | |||
| + * DISTRHO Cardinal Plugin | |||
| + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| + * | |||
| + * 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. | |||
| + */ | |||
| + | |||
| +/** | |||
| + * This file is an edited version of VCVRack's plugin/Model.cpp | |||
| + * Copyright (C) 2016-2021 VCV. | |||
| + * | |||
| + * 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 (at your option) any later version. | |||
| + */ | |||
| + | |||
| #include <algorithm> | |||
| #include <plugin/Model.hpp> | |||
| @@ -17,7 +44,7 @@ | |||
| void Model::fromJson(json_t* rootJ) { | |||
| - assert(plugin); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(plugin != nullptr,); | |||
| json_t* nameJ = json_object_get(rootJ, "name"); | |||
| if (nameJ) | |||
| @@ -54,11 +81,6 @@ | |||
| if (manualUrlJ) | |||
| manualUrl = json_string_value(manualUrlJ); | |||
| - // modularGridUrl | |||
| - json_t* modularGridUrlJ = json_object_get(rootJ, "modularGridUrl"); | |||
| - if (modularGridUrlJ) | |||
| - modularGridUrl = json_string_value(modularGridUrlJ); | |||
| - | |||
| // hidden | |||
| json_t* hiddenJ = json_object_get(rootJ, "hidden"); | |||
| // Use `disabled` as an alias which was deprecated in Rack 2.0 | |||
| @@ -73,7 +95,7 @@ | |||
| std::string Model::getFullName() { | |||
| - assert(plugin); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(plugin, {}); | |||
| return plugin->getBrand() + " " + name; | |||
| } | |||
| @@ -95,7 +117,7 @@ | |||
| } | |||
| -void Model::appendContextMenu(ui::Menu* menu, bool inBrowser) { | |||
| +void Model::appendContextMenu(ui::Menu* menu, bool) { | |||
| // plugin | |||
| menu->addChild(createMenuItem("Plugin: " + plugin->name, "", [=]() { | |||
| system::openBrowser(plugin->pluginUrl); | |||
| @@ -132,18 +154,6 @@ | |||
| menu->addChild(new ui::MenuSeparator); | |||
| - // VCV Library page | |||
| - menu->addChild(createMenuItem("VCV Library page", "", [=]() { | |||
| - system::openBrowser("https://library.vcvrack.com/" + plugin->slug + "/" + slug); | |||
| - })); | |||
| - | |||
| - // modularGridUrl | |||
| - if (modularGridUrl != "") { | |||
| - menu->addChild(createMenuItem("ModularGrid page", "", [=]() { | |||
| - system::openBrowser(modularGridUrl); | |||
| - })); | |||
| - } | |||
| - | |||
| // manual | |||
| std::string manualUrl = getManualUrl(); | |||
| if (manualUrl != "") { | |||
| @@ -172,35 +182,15 @@ | |||
| system::openBrowser(plugin->changelogUrl); | |||
| })); | |||
| } | |||
| - | |||
| - // plugin folder | |||
| - if (plugin->path != "") { | |||
| - menu->addChild(createMenuItem("Open plugin folder", "", [=]() { | |||
| - system::openDirectory(plugin->path); | |||
| - })); | |||
| - } | |||
| - | |||
| - // Favorite | |||
| - std::string favoriteRightText = inBrowser ? (RACK_MOD_CTRL_NAME "+click") : ""; | |||
| - if (isFavorite()) | |||
| - favoriteRightText += " " CHECKMARK_STRING; | |||
| - menu->addChild(createMenuItem("Favorite", favoriteRightText, | |||
| - [=]() { | |||
| - setFavorite(!isFavorite()); | |||
| - } | |||
| - )); | |||
| } | |||
| bool Model::isFavorite() { | |||
| - const settings::ModuleInfo* mi = settings::getModuleInfo(plugin->slug, slug); | |||
| - return mi && mi->favorite; | |||
| + return false; | |||
| } | |||
| -void Model::setFavorite(bool favorite) { | |||
| - settings::ModuleInfo& mi = settings::moduleInfos[plugin->slug][slug]; | |||
| - mi.favorite = favorite; | |||
| +void Model::setFavorite(bool) { | |||
| } | |||
| @@ -0,0 +1,234 @@ | |||
| --- ../Rack/src/app/Scene.cpp 2021-12-14 21:35:44.414568198 +0000 | |||
| +++ Scene.cpp 2022-01-23 17:13:24.715889665 +0000 | |||
| @@ -1,3 +1,30 @@ | |||
| +/* | |||
| + * DISTRHO Cardinal Plugin | |||
| + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| + * | |||
| + * 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. | |||
| + */ | |||
| + | |||
| +/** | |||
| + * This file is an edited version of VCVRack's app/Scene.cpp | |||
| + * Copyright (C) 2016-2021 VCV. | |||
| + * | |||
| + * 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 (at your option) any later version. | |||
| + */ | |||
| + | |||
| #include <thread> | |||
| #include <osdialog.h> | |||
| @@ -14,31 +41,49 @@ | |||
| #include <patch.hpp> | |||
| #include <asset.hpp> | |||
| +#include "../CardinalCommon.hpp" | |||
| + | |||
| namespace rack { | |||
| namespace app { | |||
| struct ResizeHandle : widget::OpaqueWidget { | |||
| - math::Vec size; | |||
| - | |||
| void draw(const DrawArgs& args) override { | |||
| + nvgStrokeColor(args.vg, nvgRGBf(1, 1, 1)); | |||
| + nvgStrokeWidth(args.vg, 1); | |||
| + | |||
| nvgBeginPath(args.vg); | |||
| - nvgMoveTo(args.vg, box.size.x, box.size.y); | |||
| + nvgMoveTo(args.vg, box.size.x, 0); | |||
| nvgLineTo(args.vg, 0, box.size.y); | |||
| - nvgLineTo(args.vg, box.size.x, 0); | |||
| - nvgClosePath(args.vg); | |||
| - nvgFillColor(args.vg, nvgRGBAf(1, 1, 1, 0.15)); | |||
| - nvgFill(args.vg); | |||
| - } | |||
| + nvgStroke(args.vg); | |||
| - void onDragStart(const DragStartEvent& e) override { | |||
| - size = APP->window->getSize(); | |||
| - } | |||
| + nvgBeginPath(args.vg); | |||
| + nvgMoveTo(args.vg, box.size.x + 5, 0); | |||
| + nvgLineTo(args.vg, 0, box.size.y + 5); | |||
| + nvgStroke(args.vg); | |||
| + | |||
| + nvgBeginPath(args.vg); | |||
| + nvgMoveTo(args.vg, box.size.x + 10, 0); | |||
| + nvgLineTo(args.vg, 0, box.size.y + 10); | |||
| + nvgStroke(args.vg); | |||
| + | |||
| + nvgStrokeColor(args.vg, nvgRGBf(0, 0, 0)); | |||
| + | |||
| + nvgBeginPath(args.vg); | |||
| + nvgMoveTo(args.vg, box.size.x+1, 0); | |||
| + nvgLineTo(args.vg, 0, box.size.y+1); | |||
| + nvgStroke(args.vg); | |||
| - void onDragMove(const DragMoveEvent& e) override { | |||
| - size = size.plus(e.mouseDelta); | |||
| - APP->window->setSize(size.round()); | |||
| + nvgBeginPath(args.vg); | |||
| + nvgMoveTo(args.vg, box.size.x + 6, 0); | |||
| + nvgLineTo(args.vg, 0, box.size.y + 6); | |||
| + nvgStroke(args.vg); | |||
| + | |||
| + nvgBeginPath(args.vg); | |||
| + nvgMoveTo(args.vg, box.size.x + 11, 0); | |||
| + nvgLineTo(args.vg, 0, box.size.y + 11); | |||
| + nvgStroke(args.vg); | |||
| } | |||
| }; | |||
| @@ -46,12 +91,15 @@ | |||
| struct Scene::Internal { | |||
| ResizeHandle* resizeHandle; | |||
| - double lastAutosaveTime = 0.0; | |||
| - | |||
| bool heldArrowKeys[4] = {}; | |||
| }; | |||
| +void hideResizeHandle(Scene* scene) { | |||
| + scene->internal->resizeHandle->hide(); | |||
| +} | |||
| + | |||
| + | |||
| Scene::Scene() { | |||
| internal = new Internal; | |||
| @@ -67,13 +115,8 @@ | |||
| browser->hide(); | |||
| addChild(browser); | |||
| - if (settings::showTipsOnLaunch) { | |||
| - addChild(tipWindowCreate()); | |||
| - } | |||
| - | |||
| internal->resizeHandle = new ResizeHandle; | |||
| - internal->resizeHandle->box.size = math::Vec(15, 15); | |||
| - internal->resizeHandle->hide(); | |||
| + internal->resizeHandle->box.size = math::Vec(16, 16); | |||
| addChild(internal->resizeHandle); | |||
| } | |||
| @@ -89,32 +132,13 @@ | |||
| void Scene::step() { | |||
| - if (APP->window->isFullScreen()) { | |||
| - // Expand RackScrollWidget to cover entire screen if fullscreen | |||
| - rackScroll->box.pos.y = 0; | |||
| - } | |||
| - else { | |||
| - // Always show MenuBar if not fullscreen | |||
| - menuBar->show(); | |||
| - rackScroll->box.pos.y = menuBar->box.size.y; | |||
| - } | |||
| - | |||
| internal->resizeHandle->box.pos = box.size.minus(internal->resizeHandle->box.size); | |||
| // Resize owned descendants | |||
| menuBar->box.size.x = box.size.x; | |||
| + rackScroll->box.pos.y = menuBar->box.size.y; | |||
| rackScroll->box.size = box.size.minus(rackScroll->box.pos); | |||
| - // Autosave periodically | |||
| - if (settings::autosaveInterval > 0.0) { | |||
| - double time = system::getTime(); | |||
| - if (time - internal->lastAutosaveTime >= settings::autosaveInterval) { | |||
| - internal->lastAutosaveTime = time; | |||
| - APP->patch->saveAutosave(); | |||
| - settings::save(); | |||
| - } | |||
| - } | |||
| - | |||
| // Scroll RackScrollWidget with arrow keys | |||
| math::Vec arrowDelta; | |||
| if (internal->heldArrowKeys[0]) { | |||
| @@ -172,7 +196,7 @@ | |||
| if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
| // DEBUG("key '%d '%c' scancode %d '%c' keyName '%s'", e.key, e.key, e.scancode, e.scancode, e.keyName.c_str()); | |||
| if (e.keyName == "n" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| - APP->patch->loadTemplateDialog(); | |||
| + patchUtils::loadTemplateDialog(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "q" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| @@ -180,19 +204,20 @@ | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "o" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| - APP->patch->loadDialog(); | |||
| + patchUtils::loadDialog(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "o" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { | |||
| - APP->patch->revertDialog(); | |||
| + patchUtils::revertDialog(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "s" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| - APP->patch->saveDialog(); | |||
| + // NOTE: will do nothing if path is empty, intentionally | |||
| + patchUtils::saveDialog(APP->patch->path); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "s" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { | |||
| - APP->patch->saveAsDialog(); | |||
| + patchUtils::saveAsDialog(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "z" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| @@ -232,10 +257,8 @@ | |||
| settings::cpuMeter ^= true; | |||
| e.consume(this); | |||
| } | |||
| - if (e.key == GLFW_KEY_F11 && (e.mods & RACK_MOD_MASK) == 0) { | |||
| - APP->window->setFullScreen(!APP->window->isFullScreen()); | |||
| - // The MenuBar will be hidden when the mouse moves over the RackScrollWidget. | |||
| - // menuBar->hide(); | |||
| + if (e.key == GLFW_KEY_F7 && (e.mods & RACK_MOD_MASK) == 0) { | |||
| + patchUtils::deployToMOD(); | |||
| e.consume(this); | |||
| } | |||
| @@ -326,13 +349,6 @@ | |||
| // Key commands that can be overridden by children | |||
| if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
| - // Alternate key command for exiting fullscreen, since F11 doesn't work reliably on Mac due to "Show desktop" OS binding. | |||
| - if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) { | |||
| - if (APP->window->isFullScreen()) { | |||
| - APP->window->setFullScreen(false); | |||
| - e.consume(this); | |||
| - } | |||
| - } | |||
| if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| rack->pasteClipboardAction(); | |||
| e.consume(this); | |||
| @@ -351,7 +367,7 @@ | |||
| std::string extension = system::getExtension(path); | |||
| if (extension == ".vcv") { | |||
| - APP->patch->loadPathDialog(path); | |||
| + patchUtils::loadPathDialog(path); | |||
| e.consume(this); | |||
| return; | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| --- ../Rack/dep/oui-blendish/blendish.c 2021-10-17 13:57:24.613620711 +0100 | |||
| +++ blendish.c 2021-12-13 09:36:22.182673256 +0000 | |||
| @@ -61,7 +61,7 @@ | |||
| } | |||
| #else | |||
| - #define BND_INLINE inline | |||
| + #define BND_INLINE static inline | |||
| #define bnd_fminf(a, b) fminf(a, b) | |||
| #define bnd_fmaxf(a, b) fmaxf(a, b) | |||
| #define bnd_fmin(a, b) fmin(a, b) | |||
| @@ -1061,7 +1061,7 @@ | |||
| // search horizontal position | |||
| static NVGglyphPosition glyphs[BND_MAX_GLYPHS]; | |||
| int nglyphs = nvgTextGlyphPositions( | |||
| - ctx, x, y, rows[row].start, rows[row].end + 1, glyphs, BND_MAX_GLYPHS); | |||
| + ctx, x, y, rows[row].start, rows[row].end, glyphs, BND_MAX_GLYPHS); | |||
| int col, p = 0; | |||
| for (col = 0; col < nglyphs && glyphs[col].x < px; ++col) | |||
| p = glyphs[col].str - label; | |||
| @@ -1083,7 +1083,7 @@ | |||
| if (nrows == 0) return; | |||
| *cx = rows[r].minx; | |||
| nglyphs = nvgTextGlyphPositions( | |||
| - ctx, x, y, rows[r].start, rows[r].end+1, glyphs, BND_MAX_GLYPHS); | |||
| + ctx, x, y, rows[r].start, rows[r].end, glyphs, BND_MAX_GLYPHS); | |||
| for (int i=0; i < nglyphs; ++i) { | |||
| *cx=glyphs[i].x; | |||
| if (glyphs[i].str == caret) break; | |||
| @@ -0,0 +1,69 @@ | |||
| --- ../Rack/src/common.cpp 2021-11-23 19:57:23.719015894 +0000 | |||
| +++ common.cpp 2022-01-23 17:13:08.824652617 +0000 | |||
| @@ -1,6 +1,38 @@ | |||
| +/* | |||
| + * DISTRHO Cardinal Plugin | |||
| + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| + * | |||
| + * 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. | |||
| + */ | |||
| + | |||
| +/** | |||
| + * This file is an edited version of VCVRack's common.cpp | |||
| + * Copyright (C) 2016-2021 VCV. | |||
| + * | |||
| + * 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 (at your option) any later version. | |||
| + */ | |||
| + | |||
| #include <common.hpp> | |||
| #include <string.hpp> | |||
| +#ifdef NDEBUG | |||
| +# undef DEBUG | |||
| +#endif | |||
| + | |||
| +#include "DistrhoPluginUtils.hpp" | |||
| #if defined ARCH_WIN | |||
| #include <windows.h> | |||
| @@ -14,20 +46,21 @@ | |||
| namespace rack { | |||
| - | |||
| -const std::string APP_NAME = "VCV Rack"; | |||
| -const std::string APP_EDITION = "Free"; | |||
| -const std::string APP_EDITION_NAME = "Free"; | |||
| +const std::string APP_NAME = "Cardinal"; | |||
| +const std::string APP_EDITION = getPluginFormatName(); | |||
| +const std::string APP_EDITION_NAME = "Audio Plugin"; | |||
| const std::string APP_VERSION_MAJOR = "2"; | |||
| -const std::string APP_VERSION = TOSTRING(_APP_VERSION); | |||
| +const std::string APP_VERSION = "2.0"; | |||
| #if defined ARCH_WIN | |||
| const std::string APP_OS = "win"; | |||
| #elif ARCH_MAC | |||
| const std::string APP_OS = "mac"; | |||
| #elif defined ARCH_LIN | |||
| const std::string APP_OS = "lin"; | |||
| +#else | |||
| + #error ARCH_LIN undefined | |||
| #endif | |||
| -const std::string API_URL = "https://api.vcvrack.com"; | |||
| +const std::string API_URL = ""; | |||
| Exception::Exception(const char* format, ...) { | |||
| @@ -0,0 +1,57 @@ | |||
| --- ../Rack/src/context.cpp 2022-01-15 14:44:46.391280963 +0000 | |||
| +++ context.cpp 2022-01-23 17:13:11.652514338 +0000 | |||
| @@ -1,3 +1,30 @@ | |||
| +/* | |||
| + * DISTRHO Cardinal Plugin | |||
| + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| + * | |||
| + * 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. | |||
| + */ | |||
| + | |||
| +/** | |||
| + * This file is an edited version of VCVRack's context.cpp | |||
| + * Copyright (C) 2016-2021 VCV. | |||
| + * | |||
| + * 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 (at your option) any later version. | |||
| + */ | |||
| + | |||
| #include <context.hpp> | |||
| #include <window/Window.hpp> | |||
| #include <patch.hpp> | |||
| @@ -6,9 +33,13 @@ | |||
| #include <history.hpp> | |||
| #include <settings.hpp> | |||
| +#ifdef NDEBUG | |||
| +# undef DEBUG | |||
| +#endif | |||
| -namespace rack { | |||
| +#include "DistrhoUtils.hpp" | |||
| +namespace rack { | |||
| Context::~Context() { | |||
| // Deleting NULL is safe in C++. | |||
| @@ -44,7 +75,7 @@ | |||
| static thread_local Context* threadContext = NULL; | |||
| Context* contextGet() { | |||
| - assert(threadContext); | |||
| + DISTRHO_SAFE_ASSERT(threadContext != nullptr); | |||
| return threadContext; | |||
| } | |||