@@ -23,7 +23,7 @@ struct MidiGenerator { | |||
bool start; | |||
bool stop; | |||
bool cont; | |||
int64_t timestamp = -1; | |||
int64_t frame = -1; | |||
MidiGenerator() { | |||
reset(); | |||
@@ -56,7 +56,7 @@ struct MidiGenerator { | |||
m.setStatus(0x8); | |||
m.setNote(note); | |||
m.setValue(0); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
onMessage(m); | |||
} | |||
} | |||
@@ -76,7 +76,7 @@ struct MidiGenerator { | |||
m.setStatus(0x8); | |||
m.setNote(notes[c]); | |||
m.setValue(vels[c]); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
onMessage(m); | |||
} | |||
if (changedNote || enabledGate) { | |||
@@ -85,7 +85,7 @@ struct MidiGenerator { | |||
m.setStatus(0x9); | |||
m.setNote(note); | |||
m.setValue(vels[c]); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
onMessage(m); | |||
} | |||
notes[c] = note; | |||
@@ -101,7 +101,7 @@ struct MidiGenerator { | |||
m.setStatus(0xa); | |||
m.setNote(notes[c]); | |||
m.setValue(val); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
onMessage(m); | |||
} | |||
@@ -114,7 +114,7 @@ struct MidiGenerator { | |||
m.setSize(2); | |||
m.setStatus(0xd); | |||
m.setNote(val); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
onMessage(m); | |||
} | |||
@@ -127,7 +127,7 @@ struct MidiGenerator { | |||
m.setStatus(0xb); | |||
m.setNote(id); | |||
m.setValue(cc); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
onMessage(m); | |||
} | |||
@@ -160,7 +160,7 @@ struct MidiGenerator { | |||
m.setStatus(0xe); | |||
m.setNote(pw & 0x7f); | |||
m.setValue((pw >> 7) & 0x7f); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
onMessage(m); | |||
} | |||
@@ -174,7 +174,7 @@ struct MidiGenerator { | |||
m.setSize(1); | |||
m.setStatus(0xf); | |||
m.setChannel(0x8); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
onMessage(m); | |||
} | |||
} | |||
@@ -189,7 +189,7 @@ struct MidiGenerator { | |||
m.setSize(1); | |||
m.setStatus(0xf); | |||
m.setChannel(0xa); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
onMessage(m); | |||
} | |||
} | |||
@@ -204,7 +204,7 @@ struct MidiGenerator { | |||
m.setSize(1); | |||
m.setStatus(0xf); | |||
m.setChannel(0xb); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
onMessage(m); | |||
} | |||
} | |||
@@ -219,16 +219,16 @@ struct MidiGenerator { | |||
m.setSize(1); | |||
m.setStatus(0xf); | |||
m.setChannel(0xc); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
onMessage(m); | |||
} | |||
} | |||
void setTimestamp(int64_t timestamp) { | |||
this->timestamp = timestamp; | |||
void setFrame(int64_t frame) { | |||
this->frame = frame; | |||
} | |||
virtual void onMessage(const midi::Message &message) {} | |||
virtual void onMessage(const midi::Message& message) {} | |||
}; | |||
@@ -66,10 +66,10 @@ struct Engine { | |||
/** Returns the number of audio samples since the Engine was created. | |||
*/ | |||
int64_t getFrame(); | |||
/** Returns the estimated time corresponding to the current frame, based on the time of when stepBlock() was last called. | |||
Calculated by `stepTime + framesSinceStep / sampleRate`. | |||
/** Sets the frame of the next stepBlock() call. | |||
There is no reason to reset the frame in standalone Rack. | |||
*/ | |||
double getFrameTime(); | |||
void setFrame(int64_t frame); | |||
/** Returns the frame when stepBlock() was last called. | |||
*/ | |||
int64_t getBlockFrame(); | |||
@@ -20,8 +20,12 @@ namespace midi { | |||
struct Message { | |||
/** Initialized to 3 empty bytes. */ | |||
std::vector<uint8_t> bytes; | |||
/** Timestamp of MIDI message in nanoseconds. NAN if not set. */ | |||
double timestamp = NAN; | |||
/** The Engine frame timestamp of the Message. | |||
For output messages, the frame when the message was generated. | |||
For input messages, the frame when it is intended to be processed. | |||
-1 for undefined, to be sent or processed immediately. | |||
*/ | |||
int64_t frame = -1; | |||
Message() : bytes(3) {} | |||
@@ -7,7 +7,7 @@ namespace core { | |||
struct CCMidiOutput : midi::Output { | |||
int lastValues[128]; | |||
double timestamp = 0.0; | |||
double frame = 0.0; | |||
CCMidiOutput() { | |||
reset(); | |||
@@ -29,12 +29,12 @@ struct CCMidiOutput : midi::Output { | |||
m.setStatus(0xb); | |||
m.setNote(cc); | |||
m.setValue(value); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
sendMessage(m); | |||
} | |||
void setTimestamp(double timestamp) { | |||
this->timestamp = timestamp; | |||
void setFrame(double frame) { | |||
this->frame = frame; | |||
} | |||
}; | |||
@@ -83,7 +83,7 @@ struct CV_CC : Module { | |||
else | |||
return; | |||
midiOutput.setTimestamp(APP->engine->getFrameTime()); | |||
midiOutput.setFrame(args.frame + APP->engine->getBlockFrames()); | |||
for (int i = 0; i < 16; i++) { | |||
int value = (int) std::round(inputs[CC_INPUTS + i].getVoltage() / 10.f * 127); | |||
@@ -8,7 +8,7 @@ namespace core { | |||
struct GateMidiOutput : midi::Output { | |||
int vels[128]; | |||
bool lastGates[128]; | |||
double timestamp = 0.0; | |||
double frame = 0.0; | |||
GateMidiOutput() { | |||
reset(); | |||
@@ -30,7 +30,7 @@ struct GateMidiOutput : midi::Output { | |||
m.setStatus(0x8); | |||
m.setNote(note); | |||
m.setValue(0); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
sendMessage(m); | |||
lastGates[note] = false; | |||
} | |||
@@ -47,7 +47,7 @@ struct GateMidiOutput : midi::Output { | |||
m.setStatus(0x9); | |||
m.setNote(note); | |||
m.setValue(vels[note]); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
sendMessage(m); | |||
} | |||
else if (!gate && lastGates[note]) { | |||
@@ -56,14 +56,14 @@ struct GateMidiOutput : midi::Output { | |||
m.setStatus(0x8); | |||
m.setNote(note); | |||
m.setValue(vels[note]); | |||
m.timestamp = timestamp; | |||
m.frame = frame; | |||
sendMessage(m); | |||
} | |||
lastGates[note] = gate; | |||
} | |||
void setTimestamp(double timestamp) { | |||
this->timestamp = timestamp; | |||
void setFrame(double frame) { | |||
this->frame = frame; | |||
} | |||
}; | |||
@@ -107,7 +107,7 @@ struct CV_Gate : Module { | |||
} | |||
void process(const ProcessArgs& args) override { | |||
midiOutput.setTimestamp(APP->engine->getFrameTime()); | |||
midiOutput.setFrame(args.frame + APP->engine->getBlockFrames()); | |||
for (int i = 0; i < 16; i++) { | |||
int note = learnedNotes[i]; | |||
@@ -6,7 +6,7 @@ namespace core { | |||
struct MidiOutput : dsp::MidiGenerator<PORT_MAX_CHANNELS>, midi::Output { | |||
void onMessage(const midi::Message &message) override { | |||
void onMessage(const midi::Message& message) override { | |||
Output::sendMessage(message); | |||
} | |||
@@ -76,7 +76,7 @@ struct CV_MIDI : Module { | |||
if (rateLimiterTriggered) | |||
rateLimiterTimer.time -= rateLimiterPeriod; | |||
midiOutput.setTimestamp(APP->engine->getFrameTime()); | |||
midiOutput.setFrame(args.frame + APP->engine->getBlockFrames()); | |||
for (int c = 0; c < inputs[PITCH_INPUT].getChannels(); c++) { | |||
int vel = (int) std::round(inputs[VEL_INPUT].getNormalPolyVoltage(10.f * 100 / 127, c) / 10.f * 127); | |||
@@ -72,8 +72,8 @@ struct MIDI_CC : Module { | |||
void process(const ProcessArgs& args) override { | |||
while (!midiInput.queue.empty()) { | |||
midi::Message& msg = midiInput.queue.front(); | |||
// Don't process MIDI message until its timestamp corresponds with the audio frame time when played back in the next block. | |||
if (msg.timestamp + APP->engine->getBlockDuration() > APP->engine->getFrameTime()) | |||
// Don't process MIDI message until we've reached its frame. | |||
if (msg.frame > args.frame) | |||
break; | |||
processMessage(msg); | |||
midiInput.queue.pop(); | |||
@@ -123,8 +123,8 @@ struct MIDI_CV : Module { | |||
void process(const ProcessArgs& args) override { | |||
while (!midiInput.queue.empty()) { | |||
midi::Message& msg = midiInput.queue.front(); | |||
// Don't process MIDI message until its timestamp corresponds with the audio frame time when played back in the next block. | |||
if (msg.timestamp + APP->engine->getBlockDuration() > APP->engine->getFrameTime()) | |||
// Don't process MIDI message until we've reached its frame. | |||
if (msg.frame > args.frame) | |||
break; | |||
processMessage(msg); | |||
midiInput.queue.pop(); | |||
@@ -66,8 +66,8 @@ struct MIDI_Gate : Module { | |||
void process(const ProcessArgs& args) override { | |||
while (!midiInput.queue.empty()) { | |||
midi::Message& msg = midiInput.queue.front(); | |||
// Don't process MIDI message until its timestamp corresponds with the audio frame time when played back in the next block. | |||
if (msg.timestamp + APP->engine->getBlockDuration() > APP->engine->getFrameTime()) | |||
// Don't process MIDI message until we've reached its frame. | |||
if (msg.frame > args.frame) | |||
break; | |||
processMessage(msg); | |||
midiInput.queue.pop(); | |||
@@ -84,8 +84,8 @@ struct MIDI_Map : Module { | |||
while (!midiInput.queue.empty()) { | |||
midi::Message& msg = midiInput.queue.front(); | |||
// Don't process MIDI message until its timestamp corresponds with the audio frame time when played back in the next block. | |||
if (msg.timestamp + APP->engine->getBlockDuration() > APP->engine->getFrameTime()) | |||
// Don't process MIDI message until we've reached its frame. | |||
if (msg.frame > args.frame) | |||
break; | |||
processMessage(msg); | |||
midiInput.queue.pop(); | |||
@@ -618,9 +618,8 @@ int64_t Engine::getFrame() { | |||
} | |||
double Engine::getFrameTime() { | |||
double timeSinceBlock = (internal->frame - internal->blockFrame) * internal->sampleTime; | |||
return internal->blockTime + timeSinceBlock; | |||
void Engine::setFrame(int64_t frame) { | |||
internal->frame = frame; | |||
} | |||
@@ -4,6 +4,8 @@ | |||
#include <midi.hpp> | |||
#include <string.hpp> | |||
#include <system.hpp> | |||
#include <context.hpp> | |||
#include <engine/Engine.hpp> | |||
namespace rack { | |||
@@ -44,15 +46,24 @@ void InputDevice::unsubscribe(Input* input) { | |||
subscribed.erase(it); | |||
} | |||
void InputDevice::onMessage(const Message &message) { | |||
// Set timestamp to now if unset | |||
void InputDevice::onMessage(const Message& message) { | |||
Message msg = message; | |||
if (msg.timestamp == 0.0) | |||
msg.timestamp = system::getTime(); | |||
for (Input* input : subscribed) { | |||
// We're probably in the MIDI driver's thread, so set the Rack context. | |||
contextSet(input->context); | |||
// Set timestamp to now if unset | |||
if (message.frame < 0) { | |||
double deltaTime = system::getTime() - APP->engine->getBlockTime(); | |||
int deltaFrames = std::floor(deltaTime * APP->engine->getSampleRate()); | |||
int64_t nextBlockFrame = APP->engine->getBlockFrame() + APP->engine->getBlockFrames(); | |||
msg.frame = nextBlockFrame + deltaFrames; | |||
} | |||
else { | |||
msg.frame = message.frame; | |||
} | |||
// Filter channel if message is not a system MIDI message | |||
if (msg.getStatus() != 0xf && input->channel >= 0 && msg.getChannel() != input->channel) | |||
continue; | |||
@@ -245,7 +256,7 @@ std::vector<int> Input::getChannels() { | |||
return channels; | |||
} | |||
void InputQueue::onMessage(const Message &message) { | |||
void InputQueue::onMessage(const Message& message) { | |||
if ((int) queue.size() >= queueMaxSize) | |||
return; | |||
// Push to queue | |||
@@ -326,7 +337,7 @@ std::vector<int> Output::getChannels() { | |||
return channels; | |||
} | |||
void Output::sendMessage(const Message &message) { | |||
void Output::sendMessage(const Message& message) { | |||
if (!outputDevice) | |||
return; | |||
@@ -16,6 +16,8 @@ | |||
#include <midi.hpp> | |||
#include <string.hpp> | |||
#include <system.hpp> | |||
#include <context.hpp> | |||
#include <engine/Engine.hpp> | |||
namespace rack { | |||
@@ -78,29 +80,32 @@ struct RtMidiInputDevice : midi::InputDevice { | |||
midi::Message msg; | |||
msg.bytes = std::vector<uint8_t>(message->begin(), message->end()); | |||
// Don't set msg.frame from timeStamp here, because it's set in onMessage(). | |||
midiInputDevice->onMessage(msg); | |||
} | |||
}; | |||
/** 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 { | |||
RtMidiOut* rtMidiOut; | |||
std::string name; | |||
std::priority_queue<midi::Message, std::vector<midi::Message>, decltype(messageEarlier)> messageQueue; | |||
struct MessageSchedule { | |||
midi::Message message; | |||
double timestamp; | |||
bool operator<(const MessageSchedule& other) const { | |||
return timestamp > other.timestamp; | |||
} | |||
}; | |||
std::priority_queue<MessageSchedule, std::vector<MessageSchedule>> messageQueue; | |||
std::thread thread; | |||
std::mutex mutex; | |||
std::condition_variable cv; | |||
bool stopped = false; | |||
RtMidiOutputDevice(int driverId, int deviceId) : messageQueue(messageEarlier) { | |||
RtMidiOutputDevice(int driverId, int deviceId) { | |||
try { | |||
rtMidiOut = new RtMidiOut((RtMidi::Api) driverId, "VCV Rack"); | |||
} | |||
@@ -137,10 +142,21 @@ struct RtMidiOutputDevice : midi::OutputDevice { | |||
return name; | |||
} | |||
void sendMessage(const midi::Message &message) override { | |||
// sendMessageNow(message); | |||
void sendMessage(const midi::Message& message) override { | |||
// If frame is undefined, send message immediately | |||
if (message.frame < 0) { | |||
sendMessageNow(message); | |||
return; | |||
} | |||
// Schedule message to be sent by worker thread | |||
MessageSchedule ms; | |||
ms.message = message; | |||
// Compute time in next Engine block to send message | |||
double deltaTime = (message.frame - APP->engine->getBlockFrame()) * APP->engine->getSampleTime(); | |||
ms.timestamp = APP->engine->getBlockTime() + deltaTime; | |||
std::lock_guard<decltype(mutex)> lock(mutex); | |||
messageQueue.push(message); | |||
messageQueue.push(ms); | |||
cv.notify_one(); | |||
} | |||
@@ -159,8 +175,8 @@ struct RtMidiOutputDevice : midi::OutputDevice { | |||
} | |||
else { | |||
// Get earliest message | |||
const midi::Message& message = messageQueue.top(); | |||
double duration = message.timestamp - system::getTime(); | |||
const MessageSchedule& ms = messageQueue.top(); | |||
double duration = ms.timestamp - system::getTime(); | |||
// 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 NAN. | |||
@@ -170,13 +186,13 @@ struct RtMidiOutputDevice : midi::OutputDevice { | |||
} | |||
// Send and remove from queue | |||
sendMessageNow(message); | |||
sendMessageNow(ms.message); | |||
messageQueue.pop(); | |||
} | |||
} | |||
} | |||
void sendMessageNow(const midi::Message &message) { | |||
void sendMessageNow(const midi::Message& message) { | |||
try { | |||
rtMidiOut->sendMessage(message.bytes.data(), message.bytes.size()); | |||
} | |||