|
@@ -1,4 +1,9 @@ |
|
|
|
|
|
#include <vector> |
|
|
#include <map> |
|
|
#include <map> |
|
|
|
|
|
#include <queue> |
|
|
|
|
|
#include <thread> |
|
|
|
|
|
#include <mutex> |
|
|
|
|
|
#include <condition_variable> |
|
|
|
|
|
|
|
|
#pragma GCC diagnostic push |
|
|
#pragma GCC diagnostic push |
|
|
#ifndef __clang__ |
|
|
#ifndef __clang__ |
|
@@ -9,6 +14,7 @@ |
|
|
|
|
|
|
|
|
#include <rtmidi.hpp> |
|
|
#include <rtmidi.hpp> |
|
|
#include <midi.hpp> |
|
|
#include <midi.hpp> |
|
|
|
|
|
#include <system.hpp> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace rack { |
|
|
namespace rack { |
|
@@ -53,18 +59,34 @@ struct RtMidiInputDevice : midi::InputDevice { |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Makes priority_queue prioritize by the earliest MIDI message. */ |
|
|
|
|
|
static auto messageEarlier = [](const midi::Message& a, const midi::Message& b) { |
|
|
|
|
|
return a.timestamp > b.timestamp; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct RtMidiOutputDevice : midi::OutputDevice { |
|
|
struct RtMidiOutputDevice : midi::OutputDevice { |
|
|
RtMidiOut* rtMidiOut; |
|
|
RtMidiOut* rtMidiOut; |
|
|
std::string name; |
|
|
std::string name; |
|
|
|
|
|
|
|
|
RtMidiOutputDevice(int driverId, int deviceId) { |
|
|
|
|
|
|
|
|
std::priority_queue<midi::Message, std::vector<midi::Message>, decltype(messageEarlier)> messageQueue; |
|
|
|
|
|
|
|
|
|
|
|
std::thread thread; |
|
|
|
|
|
std::mutex mutex; |
|
|
|
|
|
std::condition_variable cv; |
|
|
|
|
|
bool stopped = false; |
|
|
|
|
|
|
|
|
|
|
|
RtMidiOutputDevice(int driverId, int deviceId) : messageQueue(messageEarlier) { |
|
|
rtMidiOut = new RtMidiOut((RtMidi::Api) driverId, "VCV Rack"); |
|
|
rtMidiOut = new RtMidiOut((RtMidi::Api) driverId, "VCV Rack"); |
|
|
assert(rtMidiOut); |
|
|
assert(rtMidiOut); |
|
|
name = rtMidiOut->getPortName(deviceId); |
|
|
name = rtMidiOut->getPortName(deviceId); |
|
|
rtMidiOut->openPort(deviceId, "VCV Rack output"); |
|
|
rtMidiOut->openPort(deviceId, "VCV Rack output"); |
|
|
|
|
|
|
|
|
|
|
|
start(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
~RtMidiOutputDevice() { |
|
|
~RtMidiOutputDevice() { |
|
|
|
|
|
stop(); |
|
|
rtMidiOut->closePort(); |
|
|
rtMidiOut->closePort(); |
|
|
delete rtMidiOut; |
|
|
delete rtMidiOut; |
|
|
} |
|
|
} |
|
@@ -74,8 +96,58 @@ struct RtMidiOutputDevice : midi::OutputDevice { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void sendMessage(const midi::Message &message) override { |
|
|
void sendMessage(const midi::Message &message) override { |
|
|
std::vector<unsigned char> bytes(message.bytes.begin(), message.bytes.end()); |
|
|
|
|
|
rtMidiOut->sendMessage(&bytes); |
|
|
|
|
|
|
|
|
// sendMessageNow(message); |
|
|
|
|
|
std::lock_guard<decltype(mutex)> lock(mutex); |
|
|
|
|
|
messageQueue.push(message); |
|
|
|
|
|
cv.notify_one(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Consumer thread methods |
|
|
|
|
|
|
|
|
|
|
|
void start() { |
|
|
|
|
|
thread = std::thread(&RtMidiOutputDevice::run, this); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void run() { |
|
|
|
|
|
std::unique_lock<decltype(mutex)> lock(mutex); |
|
|
|
|
|
while (!stopped) { |
|
|
|
|
|
if (messageQueue.empty()) { |
|
|
|
|
|
// No messages. Wait on the CV to be notified. |
|
|
|
|
|
cv.wait(lock); |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
// Get earliest message |
|
|
|
|
|
const midi::Message& message = messageQueue.top(); |
|
|
|
|
|
int64_t duration = message.timestamp - system::getNanoseconds(); |
|
|
|
|
|
|
|
|
|
|
|
// If we need to wait, release the lock and wait for the timeout, or if the CV is notified. |
|
|
|
|
|
// This correctly handles MIDI messages with no timestamp, because duration will be negative. |
|
|
|
|
|
if ((duration > 0) && cv.wait_for(lock, std::chrono::nanoseconds(duration)) != std::cv_status::timeout) |
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
// Send and remove from queue |
|
|
|
|
|
sendMessageNow(message); |
|
|
|
|
|
messageQueue.pop(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void sendMessageNow(const midi::Message &message) { |
|
|
|
|
|
try { |
|
|
|
|
|
rtMidiOut->sendMessage(message.bytes.data(), message.bytes.size()); |
|
|
|
|
|
} |
|
|
|
|
|
catch (RtMidiError& e) { |
|
|
|
|
|
// Ignore error |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void stop() { |
|
|
|
|
|
{ |
|
|
|
|
|
std::lock_guard<decltype(mutex)> lock(mutex); |
|
|
|
|
|
stopped = true; |
|
|
|
|
|
cv.notify_one(); |
|
|
|
|
|
} |
|
|
|
|
|
thread.join(); |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|