src/JackCommand.h
branchv_0
changeset 1 001b956610ca
parent 0 c8c8ec34120f
child 2 e5f0d3f92eb4
equal deleted inserted replaced
0:c8c8ec34120f 1:001b956610ca
       
     1 /**
       
     2  * Relational pipes
       
     3  * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
       
     4  *
       
     5  * This program is free software: you can redistribute it and/or modify
       
     6  * it under the terms of the GNU General Public License as published by
       
     7  * the Free Software Foundation, version 3 of the License.
       
     8  *
       
     9  * This program is distributed in the hope that it will be useful,
       
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
       
    12  * GNU General Public License for more details.
       
    13  *
       
    14  * You should have received a copy of the GNU General Public License
       
    15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
       
    16  */
       
    17 #pragma once
       
    18 
       
    19 #include <cstdlib>
       
    20 #include <cstring>
       
    21 #include <memory>
       
    22 #include <unistd.h>
       
    23 #include <pthread.h>
       
    24 #include <sys/mman.h>
       
    25 #include <atomic>
       
    26 
       
    27 #include <jack/jack.h>
       
    28 #include <jack/midiport.h>
       
    29 #include <jack/ringbuffer.h>
       
    30 
       
    31 #include <relpipe/writer/RelationalWriter.h>
       
    32 #include <relpipe/writer/RelpipeWriterException.h>
       
    33 #include <relpipe/writer/AttributeMetadata.h>
       
    34 #include <relpipe/writer/Factory.h>
       
    35 #include <relpipe/writer/TypeId.h>
       
    36 #include <relpipe/cli/CLI.h>
       
    37 
       
    38 #include "JackException.h"
       
    39 
       
    40 using namespace relpipe::writer;
       
    41 
       
    42 namespace relpipe {
       
    43 namespace in {
       
    44 namespace jack {
       
    45 
       
    46 int enqueueMessage(jack_nframes_t frames, void* arg);
       
    47 
       
    48 class JackCommand {
       
    49 private:
       
    50 	jack_port_t* jackPort = nullptr;
       
    51 	jack_ringbuffer_t* ringBuffer = nullptr;
       
    52 	pthread_mutex_t messageThreadLock = PTHREAD_MUTEX_INITIALIZER;
       
    53 	pthread_cond_t dataReady = PTHREAD_COND_INITIALIZER;
       
    54 
       
    55 	std::atomic<bool> continueProcessing{true};
       
    56 
       
    57 	const int RING_BUFFER_SIZE = 100;
       
    58 
       
    59 	struct MidiMessage {
       
    60 		uint8_t buffer[4096];
       
    61 		uint32_t size;
       
    62 		uint32_t time;
       
    63 	};
       
    64 
       
    65 public:
       
    66 
       
    67 	int enqueueMessage(jack_nframes_t frames) {
       
    68 		void* buffer = jack_port_get_buffer(jackPort, frames);
       
    69 		if (buffer == nullptr) throw JackException(L"Unable to get port buffer."); // TODO: exception in RT callback?
       
    70 
       
    71 		for (jack_nframes_t i = 0, eventCount = jack_midi_get_event_count(buffer); i < eventCount; i++) {
       
    72 			jack_midi_event_t event;
       
    73 			int noData = jack_midi_event_get(&event, buffer, i);
       
    74 			if (noData) continue;
       
    75 
       
    76 			if (event.size > sizeof (MidiMessage::buffer)) {
       
    77 				// TODO: should not printf in RT callback:
       
    78 				fwprintf(stderr, L"Error: MIDI message was too large → skipping event. Maximum allowed size: %lu bytes.\n", sizeof (MidiMessage::buffer));
       
    79 			} else if (jack_ringbuffer_write_space(ringBuffer) >= sizeof (MidiMessage)) {
       
    80 				MidiMessage m;
       
    81 				m.time = event.time;
       
    82 				m.size = event.size;
       
    83 				memcpy(m.buffer, event.buffer, event.size);
       
    84 				jack_ringbuffer_write(ringBuffer, (const char *) &m, sizeof (MidiMessage));
       
    85 			} else {
       
    86 				// TODO: should not printf in RT callback:
       
    87 				fwprintf(stderr, L"Error: ring buffer is full → skipping event.\n");
       
    88 			}
       
    89 		}
       
    90 
       
    91 		// TODO: just count skipped events and bytes and report them in next successful message instead of printing to STDERR
       
    92 
       
    93 		if (pthread_mutex_trylock(&messageThreadLock) == 0) {
       
    94 			pthread_cond_signal(&dataReady);
       
    95 			pthread_mutex_unlock(&messageThreadLock);
       
    96 		}
       
    97 
       
    98 		return 0;
       
    99 	}
       
   100 
       
   101 private:
       
   102 
       
   103 	static void writeRecord(std::shared_ptr<RelationalWriter> writer,
       
   104 			string_t eventType, integer_t channel,
       
   105 			boolean_t noteOn, integer_t pitch, integer_t velocity,
       
   106 			integer_t controllerId, integer_t value) {
       
   107 		writer->writeAttribute(eventType);
       
   108 		writer->writeAttribute(&channel, typeid (channel));
       
   109 		writer->writeAttribute(&noteOn, typeid (noteOn));
       
   110 		writer->writeAttribute(&pitch, typeid (pitch));
       
   111 		writer->writeAttribute(&velocity, typeid (velocity));
       
   112 		writer->writeAttribute(&controllerId, typeid (controllerId));
       
   113 		writer->writeAttribute(&value, typeid (value));
       
   114 	}
       
   115 
       
   116 	void processMessage(std::shared_ptr<RelationalWriter> writer, MidiMessage* event) {
       
   117 		if (event->size == 0) {
       
   118 			return;
       
   119 		} else {
       
   120 			uint8_t type = event->buffer[0] & 0xF0;
       
   121 			uint8_t channel = event->buffer[0] & 0x0F;
       
   122 
       
   123 			// TODO: write timestamp, message number
       
   124 			// TODO: write raw buffer in hex
       
   125 
       
   126 			if ((type == 0x90 || type == 0x80) && event->size == 3) {
       
   127 				writeRecord(writer, L"note", channel, type == 0x90, event->buffer[1], event->buffer[2], 0, 0);
       
   128 			} else if (type == 0xB0 && event->size == 3) {
       
   129 				writeRecord(writer, L"control", channel, false, 0, 0, event->buffer[1], event->buffer[2]);
       
   130 			}
       
   131 		}
       
   132 	}
       
   133 
       
   134 public:
       
   135 
       
   136 	void finish(int sig) {
       
   137 		continueProcessing = false;
       
   138 	}
       
   139 
       
   140 	void processJackStream(ostream &output) {
       
   141 		// Relation headers:
       
   142 		std::shared_ptr<RelationalWriter> writer(Factory::create(output));
       
   143 		vector<AttributeMetadata> metadata;
       
   144 		metadata.push_back({L"event", TypeId::STRING});
       
   145 		metadata.push_back({L"channel", TypeId::INTEGER});
       
   146 		metadata.push_back({L"note_on", TypeId::BOOLEAN});
       
   147 		metadata.push_back({L"note_pitch", TypeId::INTEGER});
       
   148 		metadata.push_back({L"note_velocity", TypeId::INTEGER});
       
   149 		metadata.push_back({L"controller_id", TypeId::INTEGER});
       
   150 		metadata.push_back({L"controller_value", TypeId::INTEGER});
       
   151 		writer->startRelation(L"midi", metadata, true);
       
   152 		output.flush();
       
   153 
       
   154 		// Initialize JACK connection:
       
   155 		std::string clientName = "relpipe-in-jack";
       
   156 		jack_client_t* client = jack_client_open(clientName.c_str(), JackNullOption, nullptr);
       
   157 		if (client == nullptr) throw JackException(L"Could not create JACK client.");
       
   158 
       
   159 		ringBuffer = jack_ringbuffer_create(RING_BUFFER_SIZE * sizeof (MidiMessage));
       
   160 
       
   161 		jack_set_process_callback(client, relpipe::in::jack::enqueueMessage, this);
       
   162 		// TODO: report also other events (connections etc.)
       
   163 
       
   164 		jackPort = jack_port_register(client, "input", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
       
   165 		if (jackPort == nullptr) throw JackException(L"Could not register port.");
       
   166 
       
   167 		if (mlockall(MCL_CURRENT | MCL_FUTURE)) fwprintf(stderr, L"Warning: Can not lock memory.\n");
       
   168 
       
   169 		int jackError = jack_activate(client);
       
   170 		if (jackError) throw JackException(L"Could not activate client.");
       
   171 
       
   172 		// Process messages from the ring buffer queue:
       
   173 		pthread_mutex_lock(&messageThreadLock);
       
   174 		while (continueProcessing) {
       
   175 			const size_t queuedMessages = jack_ringbuffer_read_space(ringBuffer) / sizeof (MidiMessage);
       
   176 			for (size_t i = 0; i < queuedMessages; ++i) {
       
   177 				MidiMessage m;
       
   178 				jack_ringbuffer_read(ringBuffer, (char*) &m, sizeof (MidiMessage));
       
   179 				processMessage(writer, &m);
       
   180 				output.flush();
       
   181 			}
       
   182 			pthread_cond_wait(&dataReady, &messageThreadLock);
       
   183 		}
       
   184 		pthread_mutex_unlock(&messageThreadLock);
       
   185 
       
   186 		// Close JACK connection:
       
   187 		jack_deactivate(client);
       
   188 		jack_client_close(client);
       
   189 		jack_ringbuffer_free(ringBuffer);
       
   190 	}
       
   191 
       
   192 };
       
   193 
       
   194 int enqueueMessage(jack_nframes_t frames, void* arg) {
       
   195 	JackCommand* instance = (JackCommand*) arg;
       
   196 	return instance->enqueueMessage(frames);
       
   197 }
       
   198 
       
   199 }
       
   200 }
       
   201 }