From 60d2283010602959cce1c5caa43fd655ffa05543 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Sun, 8 Dec 2019 04:44:02 -0500 Subject: [PATCH] Add timestamp to MIDI message. Make MIDI-* modules wait until a message is `stepFrames` frames old until processing it, improving MIDI stability. Add Engine::getStepFrame, getStepTime, and getStepFrames. --- include/engine/Engine.hpp | 8 +++++++- include/midi.hpp | 7 ++----- include/system.hpp | 2 +- src/core/MIDI_CC.cpp | 10 ++++++++-- src/core/MIDI_CV.cpp | 10 ++++++++-- src/core/MIDI_Gate.cpp | 10 ++++++++-- src/core/MIDI_Map.cpp | 10 ++++++++-- src/engine/Engine.cpp | 26 ++++++++++++++++++++++++-- src/midi.cpp | 20 ++++++++------------ src/rtaudio.cpp | 4 +--- 10 files changed, 75 insertions(+), 32 deletions(-) diff --git a/include/engine/Engine.hpp b/include/engine/Engine.hpp index a6462a1a..5a7d5c1d 100644 --- a/include/engine/Engine.hpp +++ b/include/engine/Engine.hpp @@ -37,7 +37,13 @@ struct Engine { void yieldWorkers(); /** Returns the number of audio samples since the Engine's first sample. */ - uint64_t getFrame(); + long getFrame(); + /** Returns the frame when step() was last called. */ + long getStepFrame(); + /** Returns the timestamp in nanoseconds when step() was last called. */ + long getStepTime(); + /** Returns the total number of frames in the current step() call. */ + int getStepFrames(); // Modules /** Adds a module to the rack engine. diff --git a/include/midi.hpp b/include/midi.hpp index ee0de4e4..6b8e6f1c 100644 --- a/include/midi.hpp +++ b/include/midi.hpp @@ -17,6 +17,8 @@ namespace midi { struct Message { /** Initialized to 3 empty bytes. */ std::vector bytes; + /** Timestamp of MIDI message in nanoseconds. Negative if not set. */ + long timestamp = -1; Message() : bytes(3) {} @@ -216,11 +218,6 @@ struct InputQueue : Input { int queueMaxSize = 8192; std::queue queue; void onMessage(const Message &message) override; - bool empty(); - /** Returns Message from first in queue. - You must check empty(). If the queue is empty, the behavior of this method is undefined. - */ - Message shift(); }; diff --git a/include/system.hpp b/include/system.hpp index e6760d93..54cc05a7 100644 --- a/include/system.hpp +++ b/include/system.hpp @@ -36,7 +36,7 @@ int getLogicalCoreCount(); void setThreadName(const std::string& name); /** Returns the caller's human-readable stack trace with "\n"-separated lines. */ std::string getStackTrace(); -/** Gets the current number of nanoseconds since the epoch. +/** Returns the current number of nanoseconds since the epoch. Currently uses std::chrono::high_resolution_clock. */ long getNanoseconds(); diff --git a/src/core/MIDI_CC.cpp b/src/core/MIDI_CC.cpp index 3346402d..3de25fe0 100644 --- a/src/core/MIDI_CC.cpp +++ b/src/core/MIDI_CC.cpp @@ -48,9 +48,15 @@ struct MIDI_CC : Module { } void process(const ProcessArgs& args) override { - while (!midiInput.empty()) { - midi::Message msg = midiInput.shift(); + // Process MIDI messages only if they arrived `stepFrames` frames ago. + while (!midiInput.queue.empty()) { + midi::Message& msg = midiInput.queue.front(); + long msgTime = msg.timestamp + long(APP->engine->getStepFrames() * APP->engine->getSampleTime() * 1e9); + long frameTime = APP->engine->getStepTime() + long((APP->engine->getFrame() - APP->engine->getStepFrame()) * APP->engine->getSampleTime() * 1e9); + if (msgTime > frameTime) + break; processMessage(msg); + midiInput.queue.pop(); } for (int i = 0; i < 16; i++) { diff --git a/src/core/MIDI_CV.cpp b/src/core/MIDI_CV.cpp index 4a7ff19b..80f9f4ea 100644 --- a/src/core/MIDI_CV.cpp +++ b/src/core/MIDI_CV.cpp @@ -119,9 +119,15 @@ struct MIDI_CV : Module { } void process(const ProcessArgs& args) override { - while (!midiInput.empty()) { - midi::Message msg = midiInput.shift(); + // Process MIDI messages only if they arrived `stepFrames` frames ago. + while (!midiInput.queue.empty()) { + midi::Message& msg = midiInput.queue.front(); + long msgTime = msg.timestamp + long(APP->engine->getStepFrames() * APP->engine->getSampleTime() * 1e9); + long frameTime = APP->engine->getStepTime() + long((APP->engine->getFrame() - APP->engine->getStepFrame()) * APP->engine->getSampleTime() * 1e9); + if (msgTime > frameTime) + break; processMessage(msg); + midiInput.queue.pop(); } outputs[CV_OUTPUT].setChannels(channels); diff --git a/src/core/MIDI_Gate.cpp b/src/core/MIDI_Gate.cpp index f24acb41..17ee8d48 100644 --- a/src/core/MIDI_Gate.cpp +++ b/src/core/MIDI_Gate.cpp @@ -55,9 +55,15 @@ struct MIDI_Gate : Module { } void process(const ProcessArgs& args) override { - while (!midiInput.empty()) { - midi::Message msg = midiInput.shift(); + // Process MIDI messages only if they arrived `stepFrames` frames ago. + while (!midiInput.queue.empty()) { + midi::Message& msg = midiInput.queue.front(); + long msgTime = msg.timestamp + long(APP->engine->getStepFrames() * APP->engine->getSampleTime() * 1e9); + long frameTime = APP->engine->getStepTime() + long((APP->engine->getFrame() - APP->engine->getStepFrame()) * APP->engine->getSampleTime() * 1e9); + if (msgTime > frameTime) + break; processMessage(msg); + midiInput.queue.pop(); } for (int i = 0; i < 16; i++) { diff --git a/src/core/MIDI_Map.cpp b/src/core/MIDI_Map.cpp index 600df8c1..500395e4 100644 --- a/src/core/MIDI_Map.cpp +++ b/src/core/MIDI_Map.cpp @@ -78,9 +78,15 @@ struct MIDI_Map : Module { void process(const ProcessArgs& args) override { if (divider.process()) { - while (!midiInput.empty()) { - midi::Message msg = midiInput.shift(); + // Process MIDI messages only if they arrived `stepFrames` frames ago. + while (!midiInput.queue.empty()) { + midi::Message& msg = midiInput.queue.front(); + long msgTime = msg.timestamp + long(APP->engine->getStepFrames() * APP->engine->getSampleTime() * 1e9); + long frameTime = APP->engine->getStepTime() + long((APP->engine->getFrame() - APP->engine->getStepFrame()) * APP->engine->getSampleTime() * 1e9); + if (msgTime > frameTime) + break; processMessage(msg); + midiInput.queue.pop(); } // Step channels diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index bf1471b6..d2a6208d 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -151,7 +151,10 @@ struct Engine::Internal { float sampleRate = 0.f; float sampleTime = 0.f; - uint64_t frame = 0; + long frame = 0; + long stepFrame = 0; + long stepTime = 0; + int stepFrames = 0; Module* primaryModule = NULL; int nextModuleId = 0; @@ -433,6 +436,10 @@ void Engine::step(int frames) { initMXCSR(); random::init(); + internal->stepFrame = internal->frame; + internal->stepTime = system::getNanoseconds(); + internal->stepFrames = frames; + // Set sample rate if (internal->sampleRate != settings::sampleRate) { internal->sampleRate = settings::sampleRate; @@ -514,12 +521,27 @@ void Engine::yieldWorkers() { } -uint64_t Engine::getFrame() { +long Engine::getFrame() { // No lock, for performance return internal->frame; } +long Engine::getStepFrame() { + return internal->stepFrame; +} + + +long Engine::getStepTime() { + return internal->stepTime; +} + + +int Engine::getStepFrames() { + return internal->stepFrames; +} + + void Engine::addModule(Module* module) { std::lock_guard lock(internal->mutex); assert(module); diff --git a/src/midi.cpp b/src/midi.cpp index b8527279..b296fb0f 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -26,10 +27,15 @@ void InputDevice::unsubscribe(Input* input) { } void InputDevice::onMessage(const Message &message) { + // Set timestamp if unset + Message msg = message; + if (msg.timestamp < 0) + msg.timestamp = system::getNanoseconds(); + for (Input* input : subscribed) { // Filter channel - if (input->channel < 0 || message.getStatus() == 0xf || message.getChannel() == input->channel) { - input->onMessage(message); + if (input->channel < 0 || msg.getStatus() == 0xf || msg.getChannel() == input->channel) { + input->onMessage(msg); } } } @@ -170,16 +176,6 @@ void InputQueue::onMessage(const Message &message) { queue.push(message); } -bool InputQueue::empty() { - return queue.empty(); -} - -Message InputQueue::shift() { - Message msg = queue.front(); - queue.pop(); - return msg; -} - //////////////////// // Output //////////////////// diff --git a/src/rtaudio.cpp b/src/rtaudio.cpp index dd8fc9ef..8a10e094 100644 --- a/src/rtaudio.cpp +++ b/src/rtaudio.cpp @@ -162,11 +162,9 @@ struct RtAudioDevice : audio::Device { std::vector getBlockSizes() override { std::vector blockSizes; - for (int i = 5; i < 12; i++) { + for (int i = 5; i <= 12; i++) { blockSizes.push_back(1 << i); - blockSizes.push_back((1 << i) / 2 * 3); } - blockSizes.push_back(1 << 12); return blockSizes; } int getBlockSize() override {