--- ../Rack/src/engine/Engine.cpp	2022-09-21 19:49:12.200540736 +0100
+++ Engine.cpp	2022-12-29 16:15:36.061769776 +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>
@@ -6,183 +33,39 @@
 #include <atomic>
 #include <tuple>
 #include <pmmintrin.h>
+#include <unordered_map>
 
 #include <engine/Engine.hpp>
+#include <engine/TerminalModule.hpp>
 #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
 
-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;
-		});
-	}
-};
+#include "../CardinalRemote.hpp"
+#include "DistrhoUtils.hpp"
 
 
-/** 2-phase barrier based on spin-locking.
-*/
-struct SpinBarrier {
-	std::atomic<int> count{0};
-	std::atomic<uint8_t> step{0};
-	int threads = 0;
+// known terminal modules
+extern std::vector<rack::plugin::Model*> hostTerminalModels;
 
-	/** 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();
-};
+namespace rack {
+namespace engine {
 
 
 struct Engine::Internal {
 	std::vector<Module*> modules;
+	std::vector<TerminalModule*> terminalModules;
 	std::vector<Cable*> cables;
 	std::set<ParamHandle*> paramHandles;
-	Module* masterModule = NULL;
 
 	// moduleId
 	std::map<int64_t, Module*> modulesCache;
@@ -198,7 +81,9 @@
 	int64_t blockFrame = 0;
 	double blockTime = 0.0;
 	int blockFrames = 0;
+	bool aboutToClose = false;
 
+#ifndef HEADLESS
 	// Meter
 	int meterCount = 0;
 	double meterTotal = 0.0;
@@ -206,33 +91,21 @@
 	double meterLastTime = -INFINITY;
 	double meterLastAverage = 0.0;
 	double meterLastMax = 0.0;
+#endif
 
 	// Parameter smoothing
 	Module* smoothModule = NULL;
 	int smoothParamId = 0;
 	float smoothValue = 0.f;
 
+	// Remote control
+	remoteUtils::RemoteDetails* remoteDetails = nullptr;
+
 	/** Mutex that guards the Engine state, such as settings, Modules, and Cables.
 	Writers lock when mutating the engine's state or stepping the block.
 	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,76 +133,11 @@
 }
 
 
-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];
 	// Match number of polyphonic channels to output port
-	int channels = output->channels;
+	const int channels = output->channels;
 	// Copy all voltages from output to input
 	for (int c = 0; c < channels; c++) {
 		float v = output->voltages[c];
@@ -346,6 +154,53 @@
 }
 
 
+static void Port_step(Port* that, float deltaTime) {
+	// Set plug lights
+	if (that->channels == 0) {
+		that->plugLights[0].setBrightness(0.f);
+		that->plugLights[1].setBrightness(0.f);
+		that->plugLights[2].setBrightness(0.f);
+	}
+	else if (that->channels == 1) {
+		float v = that->getVoltage() / 10.f;
+		that->plugLights[0].setSmoothBrightness(-v, deltaTime);
+		that->plugLights[1].setSmoothBrightness(v, deltaTime);
+		that->plugLights[2].setBrightness(0.f);
+	}
+	else {
+		float v = that->getVoltageRMS() / 10.f;
+		that->plugLights[0].setBrightness(0.f);
+		that->plugLights[1].setBrightness(0.f);
+		that->plugLights[2].setSmoothBrightness(v, deltaTime);
+	}
+}
+
+
+static void TerminalModule__doProcess(TerminalModule* terminalModule, const Module::ProcessArgs& args, bool input) {
+	// Step module
+	if (input) {
+		terminalModule->processTerminalInput(args);
+		for (Output& output : terminalModule->outputs) {
+			for (Cable* cable : output.cables)
+				Cable_step(cable);
+		}
+	} else {
+		terminalModule->processTerminalOutput(args);
+	}
+
+	// Iterate ports to step plug lights
+	if (args.frame % 7 /* PORT_DIVIDER */ == 0) {
+		float portTime = args.sampleTime * 7 /* PORT_DIVIDER */;
+		for (Input& input : terminalModule->inputs) {
+			Port_step(&input, portTime);
+		}
+		for (Output& output : terminalModule->outputs) {
+			Port_step(&output, portTime);
+		}
+	}
+}
+
+
 /** Steps a single frame
 */
 static void Engine_stepFrame(Engine* that) {
@@ -358,10 +213,16 @@
 		float smoothValue = internal->smoothValue;
 		Param* smoothParam = &smoothModule->params[smoothParamId];
 		float value = smoothParam->value;
-		// Use decay rate of roughly 1 graphics frame
-		const float smoothLambda = 60.f;
-		float newValue = value + (smoothValue - value) * smoothLambda * internal->sampleTime;
-		if (value == newValue) {
+		float newValue;
+		if (internal->remoteDetails != nullptr) {
+			newValue = value;
+			sendParamChangeToRemote(internal->remoteDetails, smoothModule->id, smoothParamId, value);
+		} else {
+			// Use decay rate of roughly 1 graphics frame
+			const float smoothLambda = 60.f;
+			newValue = value + (smoothValue - value) * smoothLambda * internal->sampleTime;
+		}
+		if (d_isEqual(value, newValue)) {
 			// Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats)
 			smoothParam->setValue(smoothValue);
 			internal->smoothModule = NULL;
@@ -372,13 +233,8 @@
 		}
 	}
 
-	// Step cables
-	for (Cable* cable : that->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 +245,32 @@
 		}
 	}
 
-	// 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;
+
+	// Process terminal inputs first
+	for (TerminalModule* terminalModule : internal->terminalModules) {
+		TerminalModule__doProcess(terminalModule, processArgs, true);
+	}
+
+	// Step each module and cables
+	for (Module* module : internal->modules) {
+		module->doProcess(processArgs);
+		for (Output& output : module->outputs) {
+			for (Cable* cable : output.cables)
+				Cable_step(cable);
+		}
+	}
+
+	// Process terminal outputs last
+	for (TerminalModule* terminalModule : internal->terminalModules) {
+		TerminalModule__doProcess(terminalModule, processArgs, false);
+	}
 
-	internal->frame++;
+	++internal->frame;
 }
 
 
@@ -414,35 +289,119 @@
 }
 
 
+template<typename T>
+using IdentityDictionary = std::unordered_map<T, T>;
+
+template<typename T>
+inline bool dictContains(IdentityDictionary<T>& dict, T key) {
+	return dict.find(key) != dict.end();
+}
+
+template<typename T>
+inline void dictAdd(IdentityDictionary<T>& dict, T key) {
+	dict[key] = key;
+}
+
+static void Engine_storeTerminalModulesIDs(std::vector<TerminalModule*> terminalModules, IdentityDictionary<int64_t>& terminalModulesIDs) {
+	for (TerminalModule* terminalModule : terminalModules)
+		dictAdd(terminalModulesIDs, terminalModule->id);
+}
+
+static void Engine_orderModule(Module* module, IdentityDictionary<Module*>& touchedModules, std::vector<Module*>& orderedModules, IdentityDictionary<int64_t>& terminalModulesIDs) {
+	if (!dictContains(touchedModules, module) && !dictContains(terminalModulesIDs, module->id)) { // Ignore feedback loops and terminal modules
+		dictAdd(touchedModules, module);
+		for (Output& output : module->outputs) {
+			for (Cable* cable : output.cables) {
+				Module* receiver = cable->inputModule; // The input to the cable is the receiving module
+				Engine_orderModule(receiver, touchedModules, orderedModules, terminalModulesIDs);
+			}
+		}
+		orderedModules.push_back(module);
+	}
+}
+
+static void Engine_assignOrderedModules(std::vector<Module*>& modules, std::vector<Module*>& orderedModules) {
+	std::reverse(orderedModules.begin(), orderedModules.end()); // These are stored bottom up
+	if (orderedModules.size() == modules.size()) {
+		for (unsigned int i = 0; i < orderedModules.size(); i++)
+			modules[i] = orderedModules[i];
+	}
+}
+
+#if DEBUG_ORDERED_MODULES
+static void Engine_debugOrderedModules(std::vector<Module*>& modules) {
+	printf("\n--- Ordered modules ---\n");
+	for (unsigned int i = 0; i < modules.size(); i++)
+		printf("%d) %s - %ld\n", i, modules[i]->model->getFullName().c_str(), modules[i]->id);
+}
+#endif
+
+/** Order the modules so that they always read the most recent sample from their inputs
+*/
+static void Engine_orderModules(Engine* that) {
+	Engine::Internal* internal = that->internal;
+
+	IdentityDictionary<int64_t> terminalModulesIDs;
+	Engine_storeTerminalModulesIDs(internal->terminalModules, terminalModulesIDs);
+
+	IdentityDictionary<Module*> touchedModules;
+	std::vector<Module*> orderedModules; 
+	orderedModules.reserve(internal->modules.size());
+	for (Module* module : internal->modules)
+		Engine_orderModule(module, touchedModules, orderedModules, terminalModulesIDs);
+
+	Engine_assignOrderedModules(internal->modules, orderedModules);
+
+#if DEBUG_ORDERED_MODULES
+	Engine_debugOrderedModules(internal->modules);
+#endif
+}
+
+
 static void Engine_updateConnected(Engine* that) {
 	// Find disconnected ports
-	std::set<Port*> disconnectedPorts;
+	std::set<Input*> disconnectedInputs;
+	std::set<Output*> disconnectedOutputs;
 	for (Module* module : that->internal->modules) {
 		for (Input& input : module->inputs) {
-			disconnectedPorts.insert(&input);
+			disconnectedInputs.insert(&input);
 		}
 		for (Output& output : module->outputs) {
-			disconnectedPorts.insert(&output);
+			disconnectedOutputs.insert(&output);
+		}
+	}
+	for (TerminalModule* terminalModule : that->internal->terminalModules) {
+		for (Input& input : terminalModule->inputs) {
+			disconnectedInputs.insert(&input);
+		}
+		for (Output& output : terminalModule->outputs) {
+			disconnectedOutputs.insert(&output);
 		}
 	}
 	for (Cable* cable : that->internal->cables) {
 		// Connect input
 		Input& input = cable->inputModule->inputs[cable->inputId];
-		auto inputIt = disconnectedPorts.find(&input);
-		if (inputIt != disconnectedPorts.end())
-			disconnectedPorts.erase(inputIt);
+		auto inputIt = disconnectedInputs.find(&input);
+		if (inputIt != disconnectedInputs.end())
+			disconnectedInputs.erase(inputIt);
 		Port_setConnected(&input);
 		// Connect output
 		Output& output = cable->outputModule->outputs[cable->outputId];
-		auto outputIt = disconnectedPorts.find(&output);
-		if (outputIt != disconnectedPorts.end())
-			disconnectedPorts.erase(outputIt);
+		auto outputIt = disconnectedOutputs.find(&output);
+		if (outputIt != disconnectedOutputs.end())
+			disconnectedOutputs.erase(outputIt);
 		Port_setConnected(&output);
 	}
 	// Disconnect ports that have no cable
-	for (Port* port : disconnectedPorts) {
-		Port_setDisconnected(port);
+	for (Input* input : disconnectedInputs) {
+		Port_setDisconnected(input);
+	}
+	for (Output* output : disconnectedOutputs) {
+		Port_setDisconnected(output);
+		DISTRHO_SAFE_ASSERT(output->cables.empty());
 	}
+	// Order the modules according to their connections
+	Engine_orderModules(that);
 }
 
 
@@ -460,37 +419,23 @@
 
 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->terminalModules.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;
 }
@@ -519,18 +464,22 @@
 		removeModule_NoLock(module);
 		delete module;
 	}
+	std::vector<TerminalModule*> terminalModules = internal->terminalModules;
+	for (TerminalModule* terminalModule : terminalModules) {
+		removeModule_NoLock(terminalModule);
+		delete terminalModule;
+	}
 }
 
 
 void Engine::stepBlock(int frames) {
+#ifndef HEADLESS
 	// Start timer before locking
 	double startTime = system::getTime();
+#endif
 
-	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,18 +492,14 @@
 		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++;
 
+#ifndef HEADLESS
 	// Stop timer
 	double endTime = system::getTime();
 	double meter = (endTime - startTime) / (frames * internal->sampleTime);
@@ -572,47 +517,20 @@
 		internal->meterTotal = 0.0;
 		internal->meterMax = 0.0;
 	}
-
-	// Reset MXCSR back to original value
-	_mm_setcsr(csr);
+#endif
 }
 
 
 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;
 }
 
 
@@ -635,20 +553,13 @@
 	for (Module* module : internal->modules) {
 		module->onSampleRateChange(e);
 	}
+	for (TerminalModule* terminalModule : internal->terminalModules) {
+		terminalModule->onSampleRateChange(e);
+	}
 }
 
 
 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 +569,6 @@
 
 
 void Engine::yieldWorkers() {
-	internal->workerBarrier.yield();
 }
 
 
@@ -698,17 +608,25 @@
 
 
 double Engine::getMeterAverage() {
+#ifndef HEADLESS
 	return internal->meterLastAverage;
+#else
+	return 0.0;
+#endif
 }
 
 
 double Engine::getMeterMax() {
+#ifndef HEADLESS
 	return internal->meterLastMax;
+#else
+	return 0.0;
+#endif
 }
 
 
 size_t Engine::getNumModules() {
-	return internal->modules.size();
+	return internal->modules.size() + internal->terminalModules.size();
 }
 
 
@@ -718,8 +636,12 @@
 	for (Module* m : internal->modules) {
 		if (i >= len)
 			break;
-		moduleIds[i] = m->id;
-		i++;
+		moduleIds[i++] = m->id;
+	}
+	for (TerminalModule* m : internal->terminalModules) {
+		if (i >= len)
+			break;
+		moduleIds[i++] = m->id;
 	}
 	return i;
 }
@@ -728,27 +650,43 @@
 std::vector<int64_t> Engine::getModuleIds() {
 	SharedLock<SharedMutex> lock(internal->mutex);
 	std::vector<int64_t> moduleIds;
-	moduleIds.reserve(internal->modules.size());
+	moduleIds.reserve(getNumModules());
 	for (Module* m : internal->modules) {
 		moduleIds.push_back(m->id);
 	}
+	for (TerminalModule* tm : internal->terminalModules) {
+		moduleIds.push_back(tm->id);
+	}
 	return moduleIds;
 }
 
 
+static TerminalModule* asTerminalModule(Module* const module) {
+	const plugin::Model* const model = module->model;
+	if (std::find(hostTerminalModels.begin(), hostTerminalModels.end(), model) != hostTerminalModels.end())
+		return static_cast<TerminalModule*>(module);
+	return nullptr;
+}
+
+
 void Engine::addModule(Module* module) {
 	std::lock_guard<SharedMutex> lock(internal->mutex);
-	assert(module);
+	DISTRHO_SAFE_ASSERT_RETURN(module != nullptr,);
 	// 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(),);
+	auto tit = std::find(internal->terminalModules.begin(), internal->terminalModules.end(), module);
+	DISTRHO_SAFE_ASSERT_RETURN(tit == internal->terminalModules.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
 		module->id = random::u64() % (1ull << 53);
 	}
 	// Add module
-	internal->modules.push_back(module);
+	if (TerminalModule* const terminalModule = asTerminalModule(module))
+		internal->terminalModules.push_back(terminalModule);
+	else
+		internal->modules.push_back(module);
 	internal->modulesCache[module->id] = module;
 	// Dispatch AddEvent
 	Module::AddEvent eAdd;
@@ -763,6 +701,9 @@
 		if (paramHandle->moduleId == module->id)
 			paramHandle->module = module;
 	}
+#if DEBUG_ORDERED_MODULES
+	printf("New module: %s - %ld\n", module->model->getFullName().c_str(), module->id);
+#endif
 }
 
 
@@ -772,11 +713,11 @@
 }
 
 
-void Engine::removeModule_NoLock(Module* module) {
-	assert(module);
-	// Check that the module actually exists
-	auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
-	assert(it != internal->modules.end());
+static void removeModule_NoLock_common(Engine::Internal* internal, Module* module) {
+	// 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 +726,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) {
@@ -809,14 +746,31 @@
 			m->rightExpander.module = NULL;
 		}
 	}
-	// Remove module
-	internal->modulesCache.erase(module->id);
-	internal->modules.erase(it);
 	// Reset expanders
 	module->leftExpander.moduleId = -1;
 	module->leftExpander.module = NULL;
 	module->rightExpander.moduleId = -1;
 	module->rightExpander.module = NULL;
+	// Remove module
+	internal->modulesCache.erase(module->id);
+}
+
+
+void Engine::removeModule_NoLock(Module* module) {
+	DISTRHO_SAFE_ASSERT_RETURN(module,);
+	// Check that the module actually exists
+	if (TerminalModule* const terminalModule = asTerminalModule(module)) {
+		auto tit = std::find(internal->terminalModules.begin(), internal->terminalModules.end(), terminalModule);
+		DISTRHO_SAFE_ASSERT_RETURN(tit != internal->terminalModules.end(),);
+		removeModule_NoLock_common(internal, module);
+		internal->terminalModules.erase(tit);
+	}
+	else {
+		auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
+		DISTRHO_SAFE_ASSERT_RETURN(it != internal->modules.end(),);
+		removeModule_NoLock_common(internal, module);
+		internal->modules.erase(it);
+	}
 }
 
 
@@ -824,7 +778,8 @@
 	SharedLock<SharedMutex> lock(internal->mutex);
 	// TODO Performance could be improved by searching modulesCache, but more testing would be needed to make sure it's always valid.
 	auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
-	return it != internal->modules.end();
+	auto tit = std::find(internal->terminalModules.begin(), internal->terminalModules.end(), module);
+	return it != internal->modules.end() && tit != internal->terminalModules.end();
 }
 
 
@@ -844,7 +799,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 +808,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 +816,7 @@
 
 
 void Engine::bypassModule(Module* module, bool bypassed) {
-	assert(module);
+	DISTRHO_SAFE_ASSERT_RETURN(module,);
 	if (module->isBypassed() == bypassed)
 		return;
 
@@ -907,11 +862,17 @@
 
 
 void Engine::prepareSave() {
+	if (internal->aboutToClose)
+		return;
 	SharedLock<SharedMutex> lock(internal->mutex);
 	for (Module* module : internal->modules) {
 		Module::SaveEvent e;
 		module->onSave(e);
 	}
+	for (TerminalModule* terminalModule : internal->terminalModules) {
+		Module::SaveEvent e;
+		terminalModule->onSave(e);
+	}
 }
 
 
@@ -946,16 +907,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)
@@ -969,6 +930,8 @@
 	// Add the cable
 	internal->cables.push_back(cable);
 	internal->cablesCache[cable->id] = cable;
+	// Add the cable's zero-latency shortcut
+	cable->outputModule->outputs[cable->outputId].cables.push_back(cable);
 	Engine_updateConnected(this);
 	// Dispatch input port event
 	{
@@ -996,10 +959,12 @@
 
 
 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's zero-latency shortcut
+	cable->outputModule->outputs[cable->outputId].cables.remove(cable);
 	// Remove the cable
 	internal->cablesCache.erase(cable->id);
 	internal->cables.erase(it);
@@ -1053,6 +1018,9 @@
 		internal->smoothModule = NULL;
 		internal->smoothParamId = 0;
 	}
+	if (internal->remoteDetails != nullptr) {
+		sendParamChangeToRemote(internal->remoteDetails, module->id, paramId, value);
+	}
 	module->params[paramId].value = value;
 }
 
@@ -1085,11 +1053,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 +1074,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 +1111,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;
@@ -1187,6 +1155,10 @@
 		json_t* moduleJ = module->toJson();
 		json_array_append_new(modulesJ, moduleJ);
 	}
+	for (TerminalModule* terminalModule : internal->terminalModules) {
+		json_t* terminalModuleJ = terminalModule->toJson();
+		json_array_append_new(modulesJ, terminalModuleJ);
+	}
 	json_object_set_new(rootJ, "modules", modulesJ);
 
 	// cables
@@ -1197,11 +1169,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 +1192,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 +1221,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 +1259,20 @@
 			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();
-	}
+void Engine::startFallbackThread() {
 }
 
 
-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_setAboutToClose(Engine* const engine) {
+	engine->internal->aboutToClose = true;
 }
 
 
-void Engine::startFallbackThread() {
-	if (internal->fallbackThread.joinable())
-		return;
-
-	internal->fallbackRunning = true;
-	internal->fallbackThread = std::thread(Engine_fallbackRun, this);
+void Engine_setRemoteDetails(Engine* const engine, remoteUtils::RemoteDetails* const remoteDetails) {
+	engine->internal->remoteDetails = remoteDetails;
 }
 
 
