@@ -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; | ||||