| @@ -1,6 +1,5 @@ | |||||
| #pragma once | #pragma once | ||||
| #include <vector> | #include <vector> | ||||
| #include <queue> | |||||
| #include <set> | #include <set> | ||||
| #include <jansson.h> | #include <jansson.h> | ||||
| @@ -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 { | struct InputQueue : Input { | ||||
| int queueMaxSize = 8192; | |||||
| std::queue<Message> queue; | |||||
| struct Internal; | |||||
| Internal* internal; | |||||
| InputQueue(); | |||||
| ~InputQueue(); | |||||
| void onMessage(const Message& message) override; | 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(); | |||||
| }; | }; | ||||
| @@ -25,6 +25,7 @@ namespace settings { | |||||
| extern std::string settingsPath; | extern std::string settingsPath; | ||||
| extern bool devMode; | extern bool devMode; | ||||
| extern bool headless; | extern bool headless; | ||||
| extern bool isPlugin; | |||||
| // Persistent state, serialized to settings.json. | // Persistent state, serialized to settings.json. | ||||
| @@ -71,13 +71,9 @@ struct MIDI_CC : Module { | |||||
| } | } | ||||
| void process(const ProcessArgs& args) override { | 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); | processMessage(msg); | ||||
| midiInput.queue.pop(); | |||||
| } | } | ||||
| int channels = mpeMode ? 16 : 1; | int channels = mpeMode ? 16 : 1; | ||||
| @@ -125,13 +125,9 @@ struct MIDI_CV : Module { | |||||
| } | } | ||||
| void process(const ProcessArgs& args) override { | 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); | processMessage(msg); | ||||
| midiInput.queue.pop(); | |||||
| } | } | ||||
| outputs[PITCH_OUTPUT].setChannels(channels); | outputs[PITCH_OUTPUT].setChannels(channels); | ||||
| @@ -66,13 +66,9 @@ struct MIDI_Gate : Module { | |||||
| } | } | ||||
| void process(const ProcessArgs& args) override { | 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); | processMessage(msg); | ||||
| midiInput.queue.pop(); | |||||
| } | } | ||||
| int channels = mpeMode ? 16 : 1; | int channels = mpeMode ? 16 : 1; | ||||
| @@ -82,13 +82,9 @@ struct MIDI_Map : Module { | |||||
| if (!divider.process()) | if (!divider.process()) | ||||
| return; | 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); | processMessage(msg); | ||||
| midiInput.queue.pop(); | |||||
| } | } | ||||
| // Step channels | // Step channels | ||||
| @@ -1,5 +1,6 @@ | |||||
| #include <map> | #include <map> | ||||
| #include <utility> | #include <utility> | ||||
| #include <queue> | |||||
| #include <midi.hpp> | #include <midi.hpp> | ||||
| #include <string.hpp> | #include <string.hpp> | ||||
| @@ -273,13 +274,72 @@ std::vector<int> Input::getChannels() { | |||||
| return channels; | 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<Message, std::vector<Message>, 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) { | void InputQueue::onMessage(const Message& message) { | ||||
| if ((int) queue.size() >= queueMaxSize) | |||||
| if (internal->queue.size() >= InputQueue_maxSize) | |||||
| return; | return; | ||||
| // Push to queue | // 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 | // Output | ||||
| //////////////////// | //////////////////// | ||||
| @@ -18,6 +18,7 @@ namespace settings { | |||||
| std::string settingsPath; | std::string settingsPath; | ||||
| bool devMode = false; | bool devMode = false; | ||||
| bool headless = false; | bool headless = false; | ||||
| bool isPlugin = false; | |||||
| std::string token; | std::string token; | ||||
| bool windowMaximized = false; | bool windowMaximized = false; | ||||