# HG changeset patch # User František Kučera # Date 1601717827 -7200 # Node ID 1c0b9981eb6d55066886302b21494fe694eb2c40 # Parent 6aa881c10efd8d85860079017fa026ced4eeb89c move real-time callbacks to a separate context diff -r 6aa881c10efd -r 1c0b9981eb6d 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> convertor; // TODO: local system encoding - jack_client_t* jackClient = nullptr; - jack_port_t* jackPort = nullptr; - jack_ringbuffer_t* ringBuffer = nullptr; - std::atomic 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 (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); -} - } } }