diff --git a/include/dsp/midi.hpp b/include/dsp/midi.hpp index e19ca3e2..ae60939b 100644 --- a/include/dsp/midi.hpp +++ b/include/dsp/midi.hpp @@ -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) {} }; diff --git a/include/engine/Engine.hpp b/include/engine/Engine.hpp index 2af1f846..dde89ec5 100644 --- a/include/engine/Engine.hpp +++ b/include/engine/Engine.hpp @@ -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(); diff --git a/include/midi.hpp b/include/midi.hpp index 421b0adc..24178354 100644 --- a/include/midi.hpp +++ b/include/midi.hpp @@ -20,8 +20,12 @@ namespace midi { struct Message { /** Initialized to 3 empty bytes. */ std::vector 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) {} diff --git a/src/core/CV_CC.cpp b/src/core/CV_CC.cpp index 49e3cd93..91fe6f83 100644 --- a/src/core/CV_CC.cpp +++ b/src/core/CV_CC.cpp @@ -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); diff --git a/src/core/CV_Gate.cpp b/src/core/CV_Gate.cpp index 02b7ec38..c04efcdd 100644 --- a/src/core/CV_Gate.cpp +++ b/src/core/CV_Gate.cpp @@ -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]; diff --git a/src/core/CV_MIDI.cpp b/src/core/CV_MIDI.cpp index b3247e6d..f7bd0c4d 100644 --- a/src/core/CV_MIDI.cpp +++ b/src/core/CV_MIDI.cpp @@ -6,7 +6,7 @@ namespace core { struct MidiOutput : dsp::MidiGenerator, 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); diff --git a/src/core/MIDI_CC.cpp b/src/core/MIDI_CC.cpp index 818c5c7d..bcd1dc90 100644 --- a/src/core/MIDI_CC.cpp +++ b/src/core/MIDI_CC.cpp @@ -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(); diff --git a/src/core/MIDI_CV.cpp b/src/core/MIDI_CV.cpp index 4952c773..e7fa7912 100644 --- a/src/core/MIDI_CV.cpp +++ b/src/core/MIDI_CV.cpp @@ -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(); diff --git a/src/core/MIDI_Gate.cpp b/src/core/MIDI_Gate.cpp index 1849a37b..725b34a3 100644 --- a/src/core/MIDI_Gate.cpp +++ b/src/core/MIDI_Gate.cpp @@ -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(); diff --git a/src/core/MIDI_Map.cpp b/src/core/MIDI_Map.cpp index a47493ac..b8d51fca 100644 --- a/src/core/MIDI_Map.cpp +++ b/src/core/MIDI_Map.cpp @@ -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(); diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index d6b5d519..b789bba4 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -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; } diff --git a/src/midi.cpp b/src/midi.cpp index cdba4b63..c2dd1b78 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include 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 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 Output::getChannels() { return channels; } -void Output::sendMessage(const Message &message) { +void Output::sendMessage(const Message& message) { if (!outputDevice) return; diff --git a/src/rtmidi.cpp b/src/rtmidi.cpp index 47e63263..3060984b 100644 --- a/src/rtmidi.cpp +++ b/src/rtmidi.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include namespace rack { @@ -78,29 +80,32 @@ struct RtMidiInputDevice : midi::InputDevice { midi::Message msg; msg.bytes = std::vector(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, decltype(messageEarlier)> messageQueue; + struct MessageSchedule { + midi::Message message; + double timestamp; + + bool operator<(const MessageSchedule& other) const { + return timestamp > other.timestamp; + } + }; + std::priority_queue> 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 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()); }