diff --git a/include/midi.hpp b/include/midi.hpp index c537b810..468e3700 100644 --- a/include/midi.hpp +++ b/include/midi.hpp @@ -1,6 +1,5 @@ #pragma once #include -#include #include #include @@ -248,10 +247,20 @@ struct Input : Port { }; +/** An Input port that stores incoming MIDI messages and releases them when ready according to their frame timestamp. +*/ struct InputQueue : Input { - int queueMaxSize = 8192; - std::queue queue; + struct Internal; + Internal* internal; + + InputQueue(); + ~InputQueue(); void onMessage(const Message& message) override; + /** Pops and returns the next message (by setting `messageOut`) if its frame timestamp is `maxFrame` or earlier. + Returns whether a message was returned. + */ + bool tryPop(Message* messageOut, int64_t maxFrame); + size_t size(); }; diff --git a/src/core/MIDI_CC.cpp b/src/core/MIDI_CC.cpp index 69295569..37bdce6d 100644 --- a/src/core/MIDI_CC.cpp +++ b/src/core/MIDI_CC.cpp @@ -71,13 +71,9 @@ struct MIDI_CC : Module { } void process(const ProcessArgs& args) override { - while (!midiInput.queue.empty()) { - const midi::Message& msg = midiInput.queue.front(); - // Don't process MIDI message until we've reached its frame. - if (msg.frame > args.frame) - break; + midi::Message msg; + while (midiInput.tryPop(&msg, args.frame)) { processMessage(msg); - midiInput.queue.pop(); } int channels = mpeMode ? 16 : 1; diff --git a/src/core/MIDI_CV.cpp b/src/core/MIDI_CV.cpp index 93e8b988..6bca6609 100644 --- a/src/core/MIDI_CV.cpp +++ b/src/core/MIDI_CV.cpp @@ -125,13 +125,12 @@ struct MIDI_CV : Module { } void process(const ProcessArgs& args) override { - while (!midiInput.queue.empty()) { - const midi::Message& msg = midiInput.queue.front(); - // Don't process MIDI message until we've reached its frame. - if (msg.frame > args.frame) - break; + if (args.frame % 1000 == 0) + printf("queue size %lu\n", midiInput.size()); + + midi::Message msg; + while (midiInput.tryPop(&msg, args.frame)) { processMessage(msg); - midiInput.queue.pop(); } outputs[PITCH_OUTPUT].setChannels(channels); diff --git a/src/core/MIDI_Gate.cpp b/src/core/MIDI_Gate.cpp index d0727339..e3e2c141 100644 --- a/src/core/MIDI_Gate.cpp +++ b/src/core/MIDI_Gate.cpp @@ -66,13 +66,9 @@ struct MIDI_Gate : Module { } void process(const ProcessArgs& args) override { - while (!midiInput.queue.empty()) { - const midi::Message& msg = midiInput.queue.front(); - // Don't process MIDI message until we've reached its frame. - if (msg.frame > args.frame) - break; + midi::Message msg; + while (midiInput.tryPop(&msg, args.frame)) { processMessage(msg); - midiInput.queue.pop(); } int channels = mpeMode ? 16 : 1; diff --git a/src/core/MIDI_Map.cpp b/src/core/MIDI_Map.cpp index 1430cfde..4d4c7041 100644 --- a/src/core/MIDI_Map.cpp +++ b/src/core/MIDI_Map.cpp @@ -82,13 +82,9 @@ struct MIDI_Map : Module { if (!divider.process()) return; - while (!midiInput.queue.empty()) { - const midi::Message& msg = midiInput.queue.front(); - // Don't process MIDI message until we've reached its frame. - if (msg.frame > args.frame) - break; + midi::Message msg; + while (midiInput.tryPop(&msg, args.frame)) { processMessage(msg); - midiInput.queue.pop(); } // Step channels diff --git a/src/midi.cpp b/src/midi.cpp index 379b4fd1..155bdf82 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -273,13 +274,72 @@ std::vector Input::getChannels() { return channels; } +//////////////////// +// InputQueue +//////////////////// + +static const size_t InputQueue_maxSize = 8192; + +struct InputQueue_Compare { + bool operator()(const Message& a, const Message& b) { + return a.frame > b.frame; + } +}; + +struct InputQueue_Queue : std::priority_queue, InputQueue_Compare> { + void reserve(size_t capacity) { + c.reserve(capacity); + } + void clear() { + // Messing with the protected container is dangerous, but completely clearing it should be fine. + c.clear(); + } +}; + +struct InputQueue::Internal { + InputQueue_Queue queue; +}; + +InputQueue::InputQueue() { + internal = new Internal; + internal->queue.reserve(InputQueue_maxSize); +} + +InputQueue::~InputQueue() { + delete internal; +} + void InputQueue::onMessage(const Message& message) { - if ((int) queue.size() >= queueMaxSize) + if (internal->queue.size() >= InputQueue_maxSize) return; // Push to queue - queue.push(message); + internal->queue.push(message); } +bool InputQueue::tryPop(Message* messageOut, int64_t maxFrame) { + if (!internal->queue.empty()) { + const Message& msg = internal->queue.top(); + if (msg.frame <= maxFrame) { + *messageOut = msg; + internal->queue.pop(); + return true; + } + + // If next MIDI message is too far in the future, clear the queue. + // This solves the issue of unconsumed messages getting stuck in the future when a DAW rewinds the engine frame. + int futureFrames = 2 * APP->engine->getBlockFrames(); + if (msg.frame - maxFrame > futureFrames) { + internal->queue.clear(); + } + } + return false; +} + +size_t InputQueue::size() { + return internal->queue.size(); +} + + //////////////////// // Output ////////////////////