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