--- a/src/JackHandler.h Wed Oct 14 17:46:05 2020 +0200
+++ b/src/JackHandler.h Sat Oct 17 19:36:30 2020 +0200
@@ -58,13 +58,19 @@
struct MidiMessage {
uint8_t buffer[4096] = {0};
size_t size;
- jack_nframes_t time;
+ /**
+ * Time in micro seconds;
+ * starts at the beginning of the playback.
+ */
+ relpipe::common::type::Integer time;
};
/**
* JACK callbacks (called from the real-time thread)
*/
class RealTimeContext {
+ private:
+ jack_nframes_t startFrame = 0;
public:
jack_client_t* jackClient = nullptr;
jack_port_t* jackPort = nullptr;
@@ -76,16 +82,31 @@
const int RING_BUFFER_SIZE = 100;
int processCallback(jack_nframes_t frames) {
- const size_t queuedMessages = jack_ringbuffer_read_space(ringBuffer) / sizeof (MidiMessage);
+ jack_nframes_t lastFrame = jack_last_frame_time(jackClient);
+
void* jackPortBuffer = jack_port_get_buffer(jackPort, frames); // jack_port_get_buffer() must be called outside the loop, otherwise it will multiply the MIDI events
- jack_midi_clear_buffer(jackPortBuffer); // TODO: clean buffer?
- for (size_t i = 0; i < queuedMessages && i < frames; i++) {
- // TODO: correct timing?
- MidiMessage m;
- jack_ringbuffer_read(ringBuffer, (char*) &m, sizeof (MidiMessage));
- jack_midi_data_t* midiData = jack_midi_event_reserve(jackPortBuffer, m.time, m.size);
- if (midiData) memcpy(midiData, m.buffer, m.size);
- else /* error: not enough space TODO: store and send later */;
+ jack_midi_clear_buffer(jackPortBuffer);
+ MidiMessage m;
+ while (jack_ringbuffer_peek(ringBuffer, (char*) &m, sizeof (m)) == sizeof (m)) {
+ if (startFrame == 0) startFrame = lastFrame;
+
+ jack_nframes_t eventFrame = std::max(0L, (m.time * jack_get_sample_rate(jackClient) / 1000 / 1000) - (lastFrame - startFrame));
+ // If std::max() does its job, the message comes from the past and missed its cycle → we will send it now rather than lose it completely.
+
+ if (eventFrame < frames) {
+ jack_midi_data_t* midiData = jack_midi_event_reserve(jackPortBuffer, eventFrame, m.size);
+ if (midiData) {
+ memcpy(midiData, m.buffer, m.size);
+ jack_ringbuffer_read_advance(ringBuffer, sizeof (m));
+ } // else = error: not enough space; will be kept in the ring buffer and sent in the next cycle
+ } else {
+ /**
+ * This message does not belong to this cycle.
+ * Its time will come later.
+ * For now, it stays in the ring buffer.
+ */
+ break;
+ }
}
if (pthread_mutex_trylock(&processingLock) == 0) {
@@ -146,6 +167,7 @@
}
Event event = Event::UNKNOWN;
+ relpipe::common::type::Integer time;
relpipe::common::type::Integer channel;
relpipe::common::type::Boolean noteOn;
relpipe::common::type::Integer notePitch;
@@ -249,6 +271,7 @@
const auto attributeName = rel.attributes[rec.attributeIndex].getAttributeName();
if (attributeName == L"event") rec.event = rec.parseEventType(value);
+ else if (attributeName == L"time") rec.time = std::stoll(value);
else if (attributeName == L"channel") rec.channel = std::stoi(value);
else if (attributeName == L"controller_id") rec.controllerId = std::stoi(value);
else if (attributeName == L"controller_value") rec.controllerValue = std::stoi(value);
@@ -263,13 +286,12 @@
if (rec.attributeIndex == rel.attributes.size()) {
- while (continueProcessing && jack_ringbuffer_write_space(realTimeContext.ringBuffer) < sizeof (MidiMessage)) waitForRTCycle(); // should not happen, the real-time thread should be faster; see also note in endOfPipe()
+ while (continueProcessing && jack_ringbuffer_write_space(realTimeContext.ringBuffer) < sizeof (MidiMessage)) waitForRTCycle(); // wait, if we are faster than the real-time thread
if (!continueProcessing) return;
MidiMessage m;
- // TODO: correct timing?
- m.time = 0;
+ m.time = rec.time;
m.size = 0;
if (rec.event == RelationContext::RecordContext::Event::NOTE) {