# HG changeset patch # User František Kučera # Date 1602956190 -7200 # Node ID 45a1742b985470df2110724091bddffe6e665233 # Parent a6d332fa816cdc54b59366cd4cf8468ad85b5c57 timing (first version) diff -r a6d332fa816c -r 45a1742b9854 src/JackHandler.h --- 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) {