Browse Source

Use Engine frame instead of seconds timestamp for midi::Message.

tags/v2.0.0
Andrew Belt 3 years ago
parent
commit
5043d0e10a
13 changed files with 96 additions and 66 deletions
  1. +15
    -15
      include/dsp/midi.hpp
  2. +3
    -3
      include/engine/Engine.hpp
  3. +6
    -2
      include/midi.hpp
  4. +5
    -5
      src/core/CV_CC.cpp
  5. +7
    -7
      src/core/CV_Gate.cpp
  6. +2
    -2
      src/core/CV_MIDI.cpp
  7. +2
    -2
      src/core/MIDI_CC.cpp
  8. +2
    -2
      src/core/MIDI_CV.cpp
  9. +2
    -2
      src/core/MIDI_Gate.cpp
  10. +2
    -2
      src/core/MIDI_Map.cpp
  11. +2
    -3
      src/engine/Engine.cpp
  12. +17
    -6
      src/midi.cpp
  13. +31
    -15
      src/rtmidi.cpp

+ 15
- 15
include/dsp/midi.hpp View File

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




+ 3
- 3
include/engine/Engine.hpp View File

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


+ 6
- 2
include/midi.hpp View File

@@ -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) {}



+ 5
- 5
src/core/CV_CC.cpp View File

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


+ 7
- 7
src/core/CV_Gate.cpp View File

@@ -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];


+ 2
- 2
src/core/CV_MIDI.cpp View File

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


+ 2
- 2
src/core/MIDI_CC.cpp View File

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


+ 2
- 2
src/core/MIDI_CV.cpp View File

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


+ 2
- 2
src/core/MIDI_Gate.cpp View File

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


+ 2
- 2
src/core/MIDI_Map.cpp View File

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


+ 2
- 3
src/engine/Engine.cpp View File

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




+ 17
- 6
src/midi.cpp View File

@@ -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;



+ 31
- 15
src/rtmidi.cpp View File

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


Loading…
Cancel
Save