| @@ -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()); | |||
| } | |||