src/relpipe-in-jack.cpp
branchv_0
changeset 1 001b956610ca
parent 0 c8c8ec34120f
child 2 e5f0d3f92eb4
equal deleted inserted replaced
0:c8c8ec34120f 1:001b956610ca
    12  * GNU General Public License for more details.
    12  * GNU General Public License for more details.
    13  *
    13  *
    14  * You should have received a copy of the GNU General Public License
    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/>.
    15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
    16  */
    16  */
    17 #include <cstdlib>
       
    18 #include <cstring>
       
    19 #include <memory>
    17 #include <memory>
    20 #include <unistd.h>
    18 #include <csignal>
    21 #include <signal.h>
       
    22 #include <pthread.h>
       
    23 #include <sys/mman.h>
       
    24 
    19 
    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>
    20 #include <relpipe/writer/RelpipeWriterException.h>
    31 #include <relpipe/writer/AttributeMetadata.h>
       
    32 #include <relpipe/writer/Factory.h>
    21 #include <relpipe/writer/Factory.h>
    33 #include <relpipe/writer/TypeId.h>
       
    34 #include <relpipe/cli/CLI.h>
    22 #include <relpipe/cli/CLI.h>
    35 
    23 
    36 #include "JackException.h"
    24 #include "JackException.h"
       
    25 #include "JackCommand.h"
    37 
    26 
    38 using namespace relpipe::cli;
    27 using namespace relpipe::cli;
    39 using namespace relpipe::writer;
    28 using namespace relpipe::writer;
    40 using namespace relpipe::in::jack;
    29 using namespace relpipe::in::jack;
    41 
    30 
    42 static jack_port_t* jackPort = nullptr;
    31 static std::shared_ptr<JackCommand> jackCommand = 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 
    32 
    47 static bool continueProcessing = true;
    33 void finish(int sig) {
    48 
    34 	if (jackCommand) jackCommand->finish(sig);
    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 }
    35 }
   181 
    36 
   182 int main(int argc, char** argv) {
    37 int main(int argc, char** argv) {
   183 	setlocale(LC_ALL, "");
    38 	setlocale(LC_ALL, "");
   184 	CLI::untieStdIO();
    39 	CLI::untieStdIO();
   185 	CLI cli(argc, argv);
    40 	CLI cli(argc, argv);
   186 	// TODO: options, CLI parsing, configurable attributes
    41 	// TODO: options, CLI parsing, configurable attributes
   187 	// TODO: separate handler class
       
   188 
       
   189 	int resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR;
    42 	int resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR;
   190 
    43 
   191 	try {
    44 	try {
   192 		processJackStream(cout);
    45 		signal(SIGHUP, finish);
       
    46 		signal(SIGINT, finish);
       
    47 		jackCommand.reset(new JackCommand());
       
    48 		jackCommand->processJackStream(cout);
   193 		resultCode = CLI::EXIT_CODE_SUCCESS;
    49 		resultCode = CLI::EXIT_CODE_SUCCESS;
   194 	} catch (JackException e) {
    50 	} catch (JackException e) {
   195 		fwprintf(stderr, L"Caught JACK exception: %ls\n", e.getMessge().c_str());
    51 		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());
    52 		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;
    53 		resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR;