#include #include #include #include #include #include #include #include #include "engine.hpp" #include "util.hpp" namespace rack { float gSampleRate; static bool running = false; static std::mutex mutex; static std::thread thread; static VIPMutex vipMutex; static std::set modules; // Merely used for keeping track of which module inputs point to which module outputs, to prevent pointer mistakes and make the rack API more rigorous static std::set wires; // Parameter interpolation static Module *smoothModule = NULL; static int smoothParamId; static float smoothValue; void engineInit() { gSampleRate = 44100.0; } void engineDestroy() { // Make sure there are no wires or modules in the rack on destruction. This suggests that a module failed to remove itself before the GUI was destroyed. assert(wires.empty()); assert(modules.empty()); } static void engineStep() { // Param interpolation if (smoothModule) { float value = smoothModule->params[smoothParamId]; const float lambda = 60.0; // decay rate is 1 graphics frame const float snap = 0.0001; float delta = smoothValue - value; if (fabsf(delta) < snap) { smoothModule->params[smoothParamId] = smoothValue; smoothModule = NULL; } else { value += delta * lambda / gSampleRate; smoothModule->params[smoothParamId] = value; } } // Step all modules // std::chrono::time_point start, end; for (Module *module : modules) { // Start clock for CPU usage // start = std::chrono::high_resolution_clock::now(); // Step module by one frame module->step(); // Stop clock and smooth step time value // end = std::chrono::high_resolution_clock::now(); // std::chrono::duration diff = end - start; // float elapsed = diff.count() * gSampleRate; // const float lambda = 1.0; // module->cpuTime += (elapsed - module->cpuTime) * lambda / gSampleRate; } // Step cables by moving their output values to inputs for (Wire *wire : wires) { wire->inputValue = wire->outputValue; wire->outputValue = 0.0; } } static void engineRun() { // Set CPU to denormals-are-zero mode // http://carlh.net/plugins/denormals.php _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); // Every time the engine waits and locks a mutex, it steps this many frames const int mutexSteps = 64; // Time in seconds that the engine is rushing ahead of the estimated clock time float ahead = 0.0; auto lastTime = std::chrono::high_resolution_clock::now(); while (running) { vipMutex.wait(); { std::lock_guard lock(mutex); for (int i = 0; i < mutexSteps; i++) { engineStep(); } } float stepTime = mutexSteps / gSampleRate; ahead += stepTime; auto currTime = std::chrono::high_resolution_clock::now(); const float aheadFactor = 2.0; ahead -= aheadFactor * std::chrono::duration(currTime - lastTime).count(); lastTime = currTime; ahead = fmaxf(ahead, 0.0); // Avoid pegging the CPU at 100% when there are no "blocking" modules like AudioInterface, but still step audio at a reasonable rate // The number of steps to wait before possibly sleeping const float aheadMax = 1.0; // seconds if (ahead > aheadMax) { std::this_thread::sleep_for(std::chrono::duration(stepTime)); } } } void engineStart() { running = true; thread = std::thread(engineRun); } void engineStop() { running = false; thread.join(); } void engineAddModule(Module *module) { assert(module); VIPLock vipLock(vipMutex); std::lock_guard lock(mutex); // Check that the module is not already added assert(modules.find(module) == modules.end()); modules.insert(module); } void engineRemoveModule(Module *module) { assert(module); VIPLock vipLock(vipMutex); std::lock_guard lock(mutex); // If a param is being smoothed on this module, remove it immediately if (module == smoothModule) { smoothModule = NULL; } // Check that all wires are disconnected for (Wire *wire : wires) { assert(wire->outputModule != module); assert(wire->inputModule != module); } auto it = modules.find(module); if (it != modules.end()) { modules.erase(it); } } void engineAddWire(Wire *wire) { assert(wire); VIPLock vipLock(vipMutex); std::lock_guard lock(mutex); // Check that the wire is not already added assert(wires.find(wire) == wires.end()); assert(wire->outputModule); assert(wire->inputModule); // Check that the inputs/outputs are not already used by another cable for (Wire *wire2 : wires) { assert(wire2 != wire); assert(!(wire2->outputModule == wire->outputModule && wire2->outputId == wire->outputId)); assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId)); } // Add the wire wires.insert(wire); // Connect the wire to inputModule wire->inputModule->inputs[wire->inputId] = &wire->inputValue; wire->outputModule->outputs[wire->outputId] = &wire->outputValue; } void engineRemoveWire(Wire *wire) { assert(wire); VIPLock vipLock(vipMutex); std::lock_guard lock(mutex); // Disconnect wire from inputModule wire->inputModule->inputs[wire->inputId] = NULL; wire->outputModule->outputs[wire->outputId] = NULL; // Remove the wire auto it = wires.find(wire); assert(it != wires.end()); wires.erase(it); } void engineSetParamSmooth(Module *module, int paramId, float value) { VIPLock vipLock(vipMutex); std::lock_guard lock(mutex); // Since only one param can be smoothed at a time, if another param is currently being smoothed, skip to its final state if (smoothModule && !(smoothModule == module && smoothParamId == paramId)) { smoothModule->params[smoothParamId] = smoothValue; } smoothModule = module; smoothParamId = paramId; smoothValue = value; } } // namespace rack