timing (first version) v_0 v0.17
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sat, 17 Oct 2020 19:36:30 +0200
branchv_0
changeset 27 45a1742b9854
parent 26 a6d332fa816c
child 28 1886fe2da4af
timing (first version)
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) {