src/relpipe-in-jack.cpp
branchv_0
changeset 0 c8c8ec34120f
child 1 001b956610ca
equal deleted inserted replaced
-1:000000000000 0:c8c8ec34120f
       
     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 #include <cstdlib>
       
    18 #include <cstring>
       
    19 #include <memory>
       
    20 #include <unistd.h>
       
    21 #include <signal.h>
       
    22 #include <pthread.h>
       
    23 #include <sys/mman.h>
       
    24 
       
    25 #include <jack/jack.h>
       
    26 #include <jack/midiport.h>
       
    27 #include <jack/ringbuffer.h>
       
    28 
       
    29 #include <relpipe/writer/RelationalWriter.h>
       
    30 #include <relpipe/writer/RelpipeWriterException.h>
       
    31 #include <relpipe/writer/AttributeMetadata.h>
       
    32 #include <relpipe/writer/Factory.h>
       
    33 #include <relpipe/writer/TypeId.h>
       
    34 #include <relpipe/cli/CLI.h>
       
    35 
       
    36 #include "JackException.h"
       
    37 
       
    38 using namespace relpipe::cli;
       
    39 using namespace relpipe::writer;
       
    40 using namespace relpipe::in::jack;
       
    41 
       
    42 static jack_port_t* jackPort = nullptr;
       
    43 static jack_ringbuffer_t* ringBuffer = nullptr;
       
    44 static pthread_mutex_t messageThreadLock = PTHREAD_MUTEX_INITIALIZER;
       
    45 static pthread_cond_t dataReady = PTHREAD_COND_INITIALIZER;
       
    46 
       
    47 static bool continueProcessing = true;
       
    48 
       
    49 const int RING_BUFFER_SIZE = 100;
       
    50 
       
    51 struct MidiMessage {
       
    52 	uint8_t buffer[4096];
       
    53 	uint32_t size;
       
    54 	uint32_t time;
       
    55 };
       
    56 
       
    57 int enqueueMessage(jack_nframes_t frames, void* arg) {
       
    58 	void* buffer = jack_port_get_buffer(jackPort, frames);
       
    59 	if (buffer == nullptr) throw JackException(L"Unable to get port buffer."); // TODO: exception in RT callback?
       
    60 
       
    61 	for (jack_nframes_t i = 0, eventCount = jack_midi_get_event_count(buffer); i < eventCount; i++) {
       
    62 		jack_midi_event_t event;
       
    63 		int noData = jack_midi_event_get(&event, buffer, i);
       
    64 		if (noData) continue;
       
    65 
       
    66 		if (event.size > sizeof (MidiMessage::buffer)) {
       
    67 			// TODO: should not printf in RT callback:
       
    68 			fwprintf(stderr, L"Error: MIDI message was too large → skipping event. Maximum allowed size: %lu bytes.\n", sizeof (MidiMessage::buffer));
       
    69 		} else if (jack_ringbuffer_write_space(ringBuffer) >= sizeof (MidiMessage)) {
       
    70 			MidiMessage m;
       
    71 			m.time = event.time;
       
    72 			m.size = event.size;
       
    73 			memcpy(m.buffer, event.buffer, event.size);
       
    74 			jack_ringbuffer_write(ringBuffer, (const char *) &m, sizeof (MidiMessage));
       
    75 		} else {
       
    76 			// TODO: should not printf in RT callback:
       
    77 			fwprintf(stderr, L"Error: ring buffer is full → skipping event.\n");
       
    78 		}
       
    79 	}
       
    80 
       
    81 	// TODO: just count skipped events and bytes and report them in next successful message instead of printing to STDERR
       
    82 
       
    83 	if (pthread_mutex_trylock(&messageThreadLock) == 0) {
       
    84 		pthread_cond_signal(&dataReady);
       
    85 		pthread_mutex_unlock(&messageThreadLock);
       
    86 	}
       
    87 
       
    88 	return 0;
       
    89 }
       
    90 
       
    91 static void writeRecord(std::shared_ptr<RelationalWriter> writer,
       
    92 		string_t eventType, integer_t channel,
       
    93 		boolean_t noteOn, integer_t pitch, integer_t velocity,
       
    94 		integer_t controllerId, integer_t value) {
       
    95 	writer->writeAttribute(eventType);
       
    96 	writer->writeAttribute(&channel, typeid (channel));
       
    97 	writer->writeAttribute(&noteOn, typeid (noteOn));
       
    98 	writer->writeAttribute(&pitch, typeid (pitch));
       
    99 	writer->writeAttribute(&velocity, typeid (velocity));
       
   100 	writer->writeAttribute(&controllerId, typeid (controllerId));
       
   101 	writer->writeAttribute(&value, typeid (value));
       
   102 }
       
   103 
       
   104 static void processMessage(std::shared_ptr<RelationalWriter> writer, MidiMessage* event) {
       
   105 	if (event->size == 0) {
       
   106 		return;
       
   107 	} else {
       
   108 		uint8_t type = event->buffer[0] & 0xF0;
       
   109 		uint8_t channel = event->buffer[0] & 0x0F;
       
   110 
       
   111 		// TODO: write timestamp, message number
       
   112 		// TODO: write raw buffer in hex
       
   113 
       
   114 		if ((type == 0x90 || type == 0x80) && event->size == 3) {
       
   115 			writeRecord(writer, L"note", channel, type == 0x90, event->buffer[1], event->buffer[2], 0, 0);
       
   116 		} else if (type == 0xB0 && event->size == 3) {
       
   117 			writeRecord(writer, L"control", channel, false, 0, 0, event->buffer[1], event->buffer[2]);
       
   118 		}
       
   119 	}
       
   120 }
       
   121 
       
   122 static void finish(int sig) {
       
   123 	continueProcessing = false;
       
   124 }
       
   125 
       
   126 void processJackStream(ostream &output) {
       
   127 	// Relation headers:
       
   128 	std::shared_ptr<RelationalWriter> writer(Factory::create(output));
       
   129 	vector<AttributeMetadata> metadata;
       
   130 	metadata.push_back({L"event", TypeId::STRING});
       
   131 	metadata.push_back({L"channel", TypeId::INTEGER});
       
   132 	metadata.push_back({L"note_on", TypeId::BOOLEAN});
       
   133 	metadata.push_back({L"note_pitch", TypeId::INTEGER});
       
   134 	metadata.push_back({L"note_velocity", TypeId::INTEGER});
       
   135 	metadata.push_back({L"controller_id", TypeId::INTEGER});
       
   136 	metadata.push_back({L"controller_value", TypeId::INTEGER});
       
   137 	writer->startRelation(L"midi", metadata, true);
       
   138 	output.flush();
       
   139 
       
   140 	// Initialize JACK connection:
       
   141 	std::string clientName = "relpipe-in-jack";
       
   142 	jack_client_t* client = jack_client_open(clientName.c_str(), JackNullOption, nullptr);
       
   143 	if (client == nullptr) throw JackException(L"Could not create JACK client.");
       
   144 
       
   145 	ringBuffer = jack_ringbuffer_create(RING_BUFFER_SIZE * sizeof (MidiMessage));
       
   146 
       
   147 	jack_set_process_callback(client, enqueueMessage, nullptr);
       
   148 	// TODO: report also other events (connections etc.)
       
   149 
       
   150 	jackPort = jack_port_register(client, "input", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
       
   151 	if (jackPort == nullptr) throw JackException(L"Could not register port.");
       
   152 
       
   153 	if (mlockall(MCL_CURRENT | MCL_FUTURE)) fwprintf(stderr, L"Warning: Can not lock memory.\n");
       
   154 
       
   155 	int jackError = jack_activate(client);
       
   156 	if (jackError) throw JackException(L"Could not activate client.");
       
   157 
       
   158 	signal(SIGHUP, finish);
       
   159 	signal(SIGINT, finish);
       
   160 
       
   161 
       
   162 	// Process messages from the ring buffer queue:
       
   163 	pthread_mutex_lock(&messageThreadLock);
       
   164 	while (continueProcessing) {
       
   165 		const size_t queuedMessages = jack_ringbuffer_read_space(ringBuffer) / sizeof (MidiMessage);
       
   166 		for (size_t i = 0; i < queuedMessages; ++i) {
       
   167 			MidiMessage m;
       
   168 			jack_ringbuffer_read(ringBuffer, (char*) &m, sizeof (MidiMessage));
       
   169 			processMessage(writer, &m);
       
   170 			output.flush();
       
   171 		}
       
   172 		pthread_cond_wait(&dataReady, &messageThreadLock);
       
   173 	}
       
   174 	pthread_mutex_unlock(&messageThreadLock);
       
   175 
       
   176 	// Close JACK connection:
       
   177 	jack_deactivate(client);
       
   178 	jack_client_close(client);
       
   179 	jack_ringbuffer_free(ringBuffer);
       
   180 }
       
   181 
       
   182 int main(int argc, char** argv) {
       
   183 	setlocale(LC_ALL, "");
       
   184 	CLI::untieStdIO();
       
   185 	CLI cli(argc, argv);
       
   186 	// TODO: options, CLI parsing, configurable attributes
       
   187 	// TODO: separate handler class
       
   188 
       
   189 	int resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR;
       
   190 
       
   191 	try {
       
   192 		processJackStream(cout);
       
   193 		resultCode = CLI::EXIT_CODE_SUCCESS;
       
   194 	} catch (JackException e) {
       
   195 		fwprintf(stderr, L"Caught JACK exception: %ls\n", e.getMessge().c_str());
       
   196 		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());
       
   197 		resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR;
       
   198 	} catch (RelpipeWriterException e) {
       
   199 		fwprintf(stderr, L"Caught Writer exception: %ls\n", e.getMessge().c_str());
       
   200 		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());
       
   201 		resultCode = CLI::EXIT_CODE_DATA_ERROR;
       
   202 	}
       
   203 
       
   204 	return resultCode;
       
   205 }