move real-time callbacks to a separate context v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sat, 03 Oct 2020 11:37:07 +0200
branchv_0
changeset 11 1c0b9981eb6d
parent 10 6aa881c10efd
child 12 f56620b95180
move real-time callbacks to a separate context
src/JackHandler.h
--- a/src/JackHandler.h	Sat Oct 03 00:05:43 2020 +0200
+++ b/src/JackHandler.h	Sat Oct 03 11:37:07 2020 +0200
@@ -52,14 +52,8 @@
 	Configuration& configuration;
 	std::wstring_convert<std::codecvt_utf8<wchar_t>> convertor; // TODO: local system encoding
 
-	jack_client_t* jackClient = nullptr;
-	jack_port_t* jackPort = nullptr;
-	jack_ringbuffer_t* ringBuffer = nullptr;
-
 	std::atomic<bool> continueProcessing{true};
 
-	const int RING_BUFFER_SIZE = 100;
-
 	/**
 	 * Is passed through the ring buffer
 	 * from the relpipe-reading thread to the jack-writing thread (callback).
@@ -71,6 +65,37 @@
 	};
 
 	/**
+	 * JACK callbacks (called from the real-time thread)
+	 */
+	class RealTimeContext {
+	public:
+		jack_client_t* jackClient = nullptr;
+		jack_port_t* jackPort = nullptr;
+		jack_ringbuffer_t* ringBuffer = nullptr;
+
+		const int RING_BUFFER_SIZE = 100;
+
+		int processCallback(jack_nframes_t frames) {
+			const size_t queuedMessages = jack_ringbuffer_read_space(ringBuffer) / sizeof (MidiMessage);
+			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);
+				memcpy(midiData, m.buffer, m.size);
+			}
+			return 0;
+		}
+
+		static int processCallback(jack_nframes_t frames, void* arg) {
+			return static_cast<RealTimeContext*> (arg)->processCallback(frames);
+		}
+
+	} realTimeContext;
+
+	/**
 	 * Temporary storage of values read from relational input.
 	 */
 	class RelationContext {
@@ -123,24 +148,24 @@
 	JackHandler(Configuration& configuration) : configuration(configuration) {
 		// Initialize JACK connection:
 		std::string clientName = convertor.to_bytes(configuration.jackClientName);
-		jackClient = jack_client_open(clientName.c_str(), JackNullOption, nullptr);
-		if (jackClient == nullptr) throw JackException(L"Could not create JACK client.");
+		realTimeContext.jackClient = jack_client_open(clientName.c_str(), JackNullOption, nullptr);
+		if (realTimeContext.jackClient == nullptr) throw JackException(L"Could not create JACK client.");
 
-		ringBuffer = jack_ringbuffer_create(RING_BUFFER_SIZE * sizeof (MidiMessage));
+		realTimeContext.ringBuffer = jack_ringbuffer_create(realTimeContext.RING_BUFFER_SIZE * sizeof (MidiMessage));
 
-		jack_set_process_callback(jackClient, relpipe::out::jack::dequeueMessages, this);
+		jack_set_process_callback(realTimeContext.jackClient, RealTimeContext::processCallback, &realTimeContext);
 		// TODO: report also other events (connections etc.)
 
-		jackPort = jack_port_register(jackClient, "output", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);
-		if (jackPort == nullptr) throw JackException(L"Could not register port.");
+		realTimeContext.jackPort = jack_port_register(realTimeContext.jackClient, "output", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);
+		if (realTimeContext.jackPort == nullptr) throw JackException(L"Could not register port.");
 
 		if (mlockall(MCL_CURRENT | MCL_FUTURE)) fwprintf(stderr, L"Warning: Can not lock memory.\n");
 
-		int jackError = jack_activate(jackClient);
+		int jackError = jack_activate(realTimeContext.jackClient);
 		if (jackError) throw JackException(L"Could not activate client.");
 
 		// Wait for a port connection, because it does not make much sense to send MIDI events nowhere:
-		while (jack_port_connected(jackPort) < configuration.requiredJackConnections) usleep(10000);
+		while (jack_port_connected(realTimeContext.jackPort) < configuration.requiredJackConnections) usleep(10000);
 
 		// TODO: configurable auto-connection to another client/port
 	}
@@ -162,7 +187,7 @@
 			else if (attributeName == L"note_velocity") relationContext.indexOfNoteVelocity = i;
 			else if (attributeName == L"raw") relationContext.indexOfRaw = i;
 		}
-		
+
 		// TODO: check also data types and skipt relation if important attributes are missing
 		// TODO: configurable relation name
 	}
@@ -196,7 +221,7 @@
 		relationContext.recordContext.attributeIndex++;
 
 		if (relationContext.recordContext.attributeIndex == relationContext.attributeCount) {
-			if (jack_ringbuffer_write_space(ringBuffer) >= sizeof (MidiMessage)) {
+			if (jack_ringbuffer_write_space(realTimeContext.ringBuffer) >= sizeof (MidiMessage)) {
 				MidiMessage m;
 
 				// TODO: convert relationContext.recordContext to m
@@ -206,7 +231,7 @@
 				m.buffer[1] = 0x24;
 				m.buffer[2] = 0x40;
 
-				jack_ringbuffer_write(ringBuffer, (const char *) &m, sizeof (m));
+				jack_ringbuffer_write(realTimeContext.ringBuffer, (const char *) &m, sizeof (m));
 			} else {
 				fwprintf(stderr, L"Error: ring buffer is full → skipping event.\n");
 			}
@@ -220,47 +245,25 @@
 		// TODO: send optional (configurable) MIDI events
 
 		// Wait until the ring buffer is empty
-		while (continueProcessing && jack_ringbuffer_read_space(ringBuffer)) usleep(1000);
+		while (continueProcessing && jack_ringbuffer_read_space(realTimeContext.ringBuffer)) usleep(1000);
 		usleep(1000000);
 		// TODO: better waiting (ringBuffer might be empty, but events have not been sent to JACK yet)
 		// TODO: optionally mute all; probably enabled by default
 	}
 
-	/**
-	 * TODO: use separate class/instance for JACK callbacks to minimize scope and prevent mistakes.
-	 */
-	int dequeueMessages(jack_nframes_t frames) {
-		const size_t queuedMessages = jack_ringbuffer_read_space(ringBuffer) / sizeof (MidiMessage);
-		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);
-			memcpy(midiData, m.buffer, m.size);
-		}
-		return 0;
-	}
-
 	void finish(int sig) {
 		continueProcessing = false;
 	}
 
 	virtual ~JackHandler() {
 		// Close JACK connection:
-		jack_deactivate(jackClient);
-		jack_client_close(jackClient);
-		jack_ringbuffer_free(ringBuffer);
+		jack_deactivate(realTimeContext.jackClient);
+		jack_client_close(realTimeContext.jackClient);
+		jack_ringbuffer_free(realTimeContext.ringBuffer);
 	}
 
 };
 
-int dequeueMessages(jack_nframes_t frames, void* arg) {
-	JackHandler* instance = (JackHandler*) arg;
-	return instance->dequeueMessages(frames);
-}
-
 }
 }
 }