@@ -23,7 +23,7 @@ struct MidiGenerator { | |||||
bool start; | bool start; | ||||
bool stop; | bool stop; | ||||
bool cont; | bool cont; | ||||
int64_t timestamp = -1; | |||||
int64_t frame = -1; | |||||
MidiGenerator() { | MidiGenerator() { | ||||
reset(); | reset(); | ||||
@@ -56,7 +56,7 @@ struct MidiGenerator { | |||||
m.setStatus(0x8); | m.setStatus(0x8); | ||||
m.setNote(note); | m.setNote(note); | ||||
m.setValue(0); | m.setValue(0); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
onMessage(m); | onMessage(m); | ||||
} | } | ||||
} | } | ||||
@@ -76,7 +76,7 @@ struct MidiGenerator { | |||||
m.setStatus(0x8); | m.setStatus(0x8); | ||||
m.setNote(notes[c]); | m.setNote(notes[c]); | ||||
m.setValue(vels[c]); | m.setValue(vels[c]); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
onMessage(m); | onMessage(m); | ||||
} | } | ||||
if (changedNote || enabledGate) { | if (changedNote || enabledGate) { | ||||
@@ -85,7 +85,7 @@ struct MidiGenerator { | |||||
m.setStatus(0x9); | m.setStatus(0x9); | ||||
m.setNote(note); | m.setNote(note); | ||||
m.setValue(vels[c]); | m.setValue(vels[c]); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
onMessage(m); | onMessage(m); | ||||
} | } | ||||
notes[c] = note; | notes[c] = note; | ||||
@@ -101,7 +101,7 @@ struct MidiGenerator { | |||||
m.setStatus(0xa); | m.setStatus(0xa); | ||||
m.setNote(notes[c]); | m.setNote(notes[c]); | ||||
m.setValue(val); | m.setValue(val); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
onMessage(m); | onMessage(m); | ||||
} | } | ||||
@@ -114,7 +114,7 @@ struct MidiGenerator { | |||||
m.setSize(2); | m.setSize(2); | ||||
m.setStatus(0xd); | m.setStatus(0xd); | ||||
m.setNote(val); | m.setNote(val); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
onMessage(m); | onMessage(m); | ||||
} | } | ||||
@@ -127,7 +127,7 @@ struct MidiGenerator { | |||||
m.setStatus(0xb); | m.setStatus(0xb); | ||||
m.setNote(id); | m.setNote(id); | ||||
m.setValue(cc); | m.setValue(cc); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
onMessage(m); | onMessage(m); | ||||
} | } | ||||
@@ -160,7 +160,7 @@ struct MidiGenerator { | |||||
m.setStatus(0xe); | m.setStatus(0xe); | ||||
m.setNote(pw & 0x7f); | m.setNote(pw & 0x7f); | ||||
m.setValue((pw >> 7) & 0x7f); | m.setValue((pw >> 7) & 0x7f); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
onMessage(m); | onMessage(m); | ||||
} | } | ||||
@@ -174,7 +174,7 @@ struct MidiGenerator { | |||||
m.setSize(1); | m.setSize(1); | ||||
m.setStatus(0xf); | m.setStatus(0xf); | ||||
m.setChannel(0x8); | m.setChannel(0x8); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
onMessage(m); | onMessage(m); | ||||
} | } | ||||
} | } | ||||
@@ -189,7 +189,7 @@ struct MidiGenerator { | |||||
m.setSize(1); | m.setSize(1); | ||||
m.setStatus(0xf); | m.setStatus(0xf); | ||||
m.setChannel(0xa); | m.setChannel(0xa); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
onMessage(m); | onMessage(m); | ||||
} | } | ||||
} | } | ||||
@@ -204,7 +204,7 @@ struct MidiGenerator { | |||||
m.setSize(1); | m.setSize(1); | ||||
m.setStatus(0xf); | m.setStatus(0xf); | ||||
m.setChannel(0xb); | m.setChannel(0xb); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
onMessage(m); | onMessage(m); | ||||
} | } | ||||
} | } | ||||
@@ -219,16 +219,16 @@ struct MidiGenerator { | |||||
m.setSize(1); | m.setSize(1); | ||||
m.setStatus(0xf); | m.setStatus(0xf); | ||||
m.setChannel(0xc); | m.setChannel(0xc); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
onMessage(m); | 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. | /** Returns the number of audio samples since the Engine was created. | ||||
*/ | */ | ||||
int64_t getFrame(); | 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. | /** Returns the frame when stepBlock() was last called. | ||||
*/ | */ | ||||
int64_t getBlockFrame(); | int64_t getBlockFrame(); | ||||
@@ -20,8 +20,12 @@ namespace midi { | |||||
struct Message { | struct Message { | ||||
/** Initialized to 3 empty bytes. */ | /** Initialized to 3 empty bytes. */ | ||||
std::vector<uint8_t> 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) {} | Message() : bytes(3) {} | ||||
@@ -7,7 +7,7 @@ namespace core { | |||||
struct CCMidiOutput : midi::Output { | struct CCMidiOutput : midi::Output { | ||||
int lastValues[128]; | int lastValues[128]; | ||||
double timestamp = 0.0; | |||||
double frame = 0.0; | |||||
CCMidiOutput() { | CCMidiOutput() { | ||||
reset(); | reset(); | ||||
@@ -29,12 +29,12 @@ struct CCMidiOutput : midi::Output { | |||||
m.setStatus(0xb); | m.setStatus(0xb); | ||||
m.setNote(cc); | m.setNote(cc); | ||||
m.setValue(value); | m.setValue(value); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
sendMessage(m); | 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 | else | ||||
return; | return; | ||||
midiOutput.setTimestamp(APP->engine->getFrameTime()); | |||||
midiOutput.setFrame(args.frame + APP->engine->getBlockFrames()); | |||||
for (int i = 0; i < 16; i++) { | for (int i = 0; i < 16; i++) { | ||||
int value = (int) std::round(inputs[CC_INPUTS + i].getVoltage() / 10.f * 127); | int value = (int) std::round(inputs[CC_INPUTS + i].getVoltage() / 10.f * 127); | ||||
@@ -8,7 +8,7 @@ namespace core { | |||||
struct GateMidiOutput : midi::Output { | struct GateMidiOutput : midi::Output { | ||||
int vels[128]; | int vels[128]; | ||||
bool lastGates[128]; | bool lastGates[128]; | ||||
double timestamp = 0.0; | |||||
double frame = 0.0; | |||||
GateMidiOutput() { | GateMidiOutput() { | ||||
reset(); | reset(); | ||||
@@ -30,7 +30,7 @@ struct GateMidiOutput : midi::Output { | |||||
m.setStatus(0x8); | m.setStatus(0x8); | ||||
m.setNote(note); | m.setNote(note); | ||||
m.setValue(0); | m.setValue(0); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
sendMessage(m); | sendMessage(m); | ||||
lastGates[note] = false; | lastGates[note] = false; | ||||
} | } | ||||
@@ -47,7 +47,7 @@ struct GateMidiOutput : midi::Output { | |||||
m.setStatus(0x9); | m.setStatus(0x9); | ||||
m.setNote(note); | m.setNote(note); | ||||
m.setValue(vels[note]); | m.setValue(vels[note]); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
sendMessage(m); | sendMessage(m); | ||||
} | } | ||||
else if (!gate && lastGates[note]) { | else if (!gate && lastGates[note]) { | ||||
@@ -56,14 +56,14 @@ struct GateMidiOutput : midi::Output { | |||||
m.setStatus(0x8); | m.setStatus(0x8); | ||||
m.setNote(note); | m.setNote(note); | ||||
m.setValue(vels[note]); | m.setValue(vels[note]); | ||||
m.timestamp = timestamp; | |||||
m.frame = frame; | |||||
sendMessage(m); | sendMessage(m); | ||||
} | } | ||||
lastGates[note] = gate; | 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 { | 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++) { | for (int i = 0; i < 16; i++) { | ||||
int note = learnedNotes[i]; | int note = learnedNotes[i]; | ||||
@@ -6,7 +6,7 @@ namespace core { | |||||
struct MidiOutput : dsp::MidiGenerator<PORT_MAX_CHANNELS>, midi::Output { | 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); | Output::sendMessage(message); | ||||
} | } | ||||
@@ -76,7 +76,7 @@ struct CV_MIDI : Module { | |||||
if (rateLimiterTriggered) | if (rateLimiterTriggered) | ||||
rateLimiterTimer.time -= rateLimiterPeriod; | 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++) { | 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); | 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 { | void process(const ProcessArgs& args) override { | ||||
while (!midiInput.queue.empty()) { | while (!midiInput.queue.empty()) { | ||||
midi::Message& msg = midiInput.queue.front(); | 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; | break; | ||||
processMessage(msg); | processMessage(msg); | ||||
midiInput.queue.pop(); | midiInput.queue.pop(); | ||||
@@ -123,8 +123,8 @@ struct MIDI_CV : Module { | |||||
void process(const ProcessArgs& args) override { | void process(const ProcessArgs& args) override { | ||||
while (!midiInput.queue.empty()) { | while (!midiInput.queue.empty()) { | ||||
midi::Message& msg = midiInput.queue.front(); | 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; | break; | ||||
processMessage(msg); | processMessage(msg); | ||||
midiInput.queue.pop(); | midiInput.queue.pop(); | ||||
@@ -66,8 +66,8 @@ struct MIDI_Gate : Module { | |||||
void process(const ProcessArgs& args) override { | void process(const ProcessArgs& args) override { | ||||
while (!midiInput.queue.empty()) { | while (!midiInput.queue.empty()) { | ||||
midi::Message& msg = midiInput.queue.front(); | 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; | break; | ||||
processMessage(msg); | processMessage(msg); | ||||
midiInput.queue.pop(); | midiInput.queue.pop(); | ||||
@@ -84,8 +84,8 @@ struct MIDI_Map : Module { | |||||
while (!midiInput.queue.empty()) { | while (!midiInput.queue.empty()) { | ||||
midi::Message& msg = midiInput.queue.front(); | 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; | break; | ||||
processMessage(msg); | processMessage(msg); | ||||
midiInput.queue.pop(); | 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 <midi.hpp> | ||||
#include <string.hpp> | #include <string.hpp> | ||||
#include <system.hpp> | #include <system.hpp> | ||||
#include <context.hpp> | |||||
#include <engine/Engine.hpp> | |||||
namespace rack { | namespace rack { | ||||
@@ -44,15 +46,24 @@ void InputDevice::unsubscribe(Input* input) { | |||||
subscribed.erase(it); | subscribed.erase(it); | ||||
} | } | ||||
void InputDevice::onMessage(const Message &message) { | |||||
// Set timestamp to now if unset | |||||
void InputDevice::onMessage(const Message& message) { | |||||
Message msg = message; | Message msg = message; | ||||
if (msg.timestamp == 0.0) | |||||
msg.timestamp = system::getTime(); | |||||
for (Input* input : subscribed) { | for (Input* input : subscribed) { | ||||
// We're probably in the MIDI driver's thread, so set the Rack context. | // We're probably in the MIDI driver's thread, so set the Rack context. | ||||
contextSet(input->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 | // Filter channel if message is not a system MIDI message | ||||
if (msg.getStatus() != 0xf && input->channel >= 0 && msg.getChannel() != input->channel) | if (msg.getStatus() != 0xf && input->channel >= 0 && msg.getChannel() != input->channel) | ||||
continue; | continue; | ||||
@@ -245,7 +256,7 @@ std::vector<int> Input::getChannels() { | |||||
return channels; | return channels; | ||||
} | } | ||||
void InputQueue::onMessage(const Message &message) { | |||||
void InputQueue::onMessage(const Message& message) { | |||||
if ((int) queue.size() >= queueMaxSize) | if ((int) queue.size() >= queueMaxSize) | ||||
return; | return; | ||||
// Push to queue | // Push to queue | ||||
@@ -326,7 +337,7 @@ std::vector<int> Output::getChannels() { | |||||
return channels; | return channels; | ||||
} | } | ||||
void Output::sendMessage(const Message &message) { | |||||
void Output::sendMessage(const Message& message) { | |||||
if (!outputDevice) | if (!outputDevice) | ||||
return; | return; | ||||
@@ -16,6 +16,8 @@ | |||||
#include <midi.hpp> | #include <midi.hpp> | ||||
#include <string.hpp> | #include <string.hpp> | ||||
#include <system.hpp> | #include <system.hpp> | ||||
#include <context.hpp> | |||||
#include <engine/Engine.hpp> | |||||
namespace rack { | namespace rack { | ||||
@@ -78,29 +80,32 @@ struct RtMidiInputDevice : midi::InputDevice { | |||||
midi::Message msg; | midi::Message msg; | ||||
msg.bytes = std::vector<uint8_t>(message->begin(), message->end()); | 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); | 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 { | struct RtMidiOutputDevice : midi::OutputDevice { | ||||
RtMidiOut* rtMidiOut; | RtMidiOut* rtMidiOut; | ||||
std::string name; | 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::thread thread; | ||||
std::mutex mutex; | std::mutex mutex; | ||||
std::condition_variable cv; | std::condition_variable cv; | ||||
bool stopped = false; | bool stopped = false; | ||||
RtMidiOutputDevice(int driverId, int deviceId) : messageQueue(messageEarlier) { | |||||
RtMidiOutputDevice(int driverId, int deviceId) { | |||||
try { | try { | ||||
rtMidiOut = new RtMidiOut((RtMidi::Api) driverId, "VCV Rack"); | rtMidiOut = new RtMidiOut((RtMidi::Api) driverId, "VCV Rack"); | ||||
} | } | ||||
@@ -137,10 +142,21 @@ struct RtMidiOutputDevice : midi::OutputDevice { | |||||
return name; | 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); | std::lock_guard<decltype(mutex)> lock(mutex); | ||||
messageQueue.push(message); | |||||
messageQueue.push(ms); | |||||
cv.notify_one(); | cv.notify_one(); | ||||
} | } | ||||
@@ -159,8 +175,8 @@ struct RtMidiOutputDevice : midi::OutputDevice { | |||||
} | } | ||||
else { | else { | ||||
// Get earliest message | // 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. | // 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. | // 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 | // Send and remove from queue | ||||
sendMessageNow(message); | |||||
sendMessageNow(ms.message); | |||||
messageQueue.pop(); | messageQueue.pop(); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
void sendMessageNow(const midi::Message &message) { | |||||
void sendMessageNow(const midi::Message& message) { | |||||
try { | try { | ||||
rtMidiOut->sendMessage(message.bytes.data(), message.bytes.size()); | rtMidiOut->sendMessage(message.bytes.data(), message.bytes.size()); | ||||
} | } | ||||