commit 9ba76539b372bfc6f6215e0a665ebbbd80d598c1
parent 1ddc9e9e4a6bb61f7f567453707602d6e37c2311
Author: Henry Wilson <henry@henryandlizzy.uk>
Date:   Thu, 25 Nov 2021 21:41:40 +0000
add jack-cc-map
Diffstat:
| A | jack-cc-map | | | 0 |  | 
| A | jack-cc-map.cpp | | | 276 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| M | makefile | | | 11 | ++++++++++- | 
| A | names.c | | | 48 | ++++++++++++++++++++++++++++++++++++++++++++++++ | 
4 files changed, 334 insertions(+), 1 deletion(-)
diff --git a/jack-cc-map b/jack-cc-map
Binary files differ.
diff --git a/jack-cc-map.cpp b/jack-cc-map.cpp
@@ -0,0 +1,276 @@
+#include <jack/jack.h>
+#include <jack/midiport.h>
+#include <jack/ringbuffer.h>
+
+#include <memory>
+#include <cassert>
+
+#include <cmath>
+#include <cstring>
+
+#include <regex.h>
+//#include <assert.h>
+//#include <stdio.h>
+//#include <string.h>
+
+#define ARRAY_SIZE(a)   (sizeof(a) / sizeof(*a))
+//#define min(a,b) \
+//   ({ __typeof__ (a) _a = (a); \
+//       __typeof__ (b) _b = (b); \
+//     _a < _b ? _a : _b; })
+
+
+#include <thread>
+#include <chrono>
+#include <iostream>
+#include <iomanip>
+
+using namespace std::literals::chrono_literals;
+
+namespace jack {
+
+struct client
+{
+	client() = default;
+	client(char const* name, jack_options_t options)
+	:	handle(create(name, options), deleter)
+	{}
+
+	void activate(void)
+	{
+		assert(not jack_activate(**this));
+	}
+
+	void set_process_callback(JackProcessCallback cb, void* arg)
+	{
+		jack_set_process_callback(**this, cb, arg);
+	}
+	void set_buffer_size_callback(JackBufferSizeCallback cb, void* arg)
+	{
+		jack_set_buffer_size_callback(**this, cb, arg);
+	}
+	void set_sample_rate_callback(JackSampleRateCallback cb, void* arg)
+	{
+		jack_set_sample_rate_callback(**this, cb, arg);
+	}
+
+private:
+	static jack_client_t* create(char const* name, jack_options_t options)
+	{
+		jack_status_t status;
+		jack_client_t *ptr = jack_client_open(name, options, &status);;
+		if (ptr)
+			return ptr;
+
+		std::exit(1);
+	}
+
+	static void deleter(jack_client_t* const p)
+	{
+		assert(not jack_deactivate(p));
+	}
+
+	std::unique_ptr<jack_client_t, void (*)(jack_client_t*)> handle = {nullptr, deleter};
+
+	jack_client_t* operator *(void)
+	{
+		return handle.get();
+	}
+
+	friend struct port;
+};
+
+struct port
+{
+	port() = default;
+
+	port(client& _c, char const* name, char const* type, unsigned long flags, unsigned long buffer_size)
+	:	c(*_c)
+	,	p(jack_port_register(c, name, type, flags, buffer_size))
+	{
+		assert(p);
+	}
+	~port()
+	{
+		if (p)
+			jack_port_unregister(c, p);
+	}
+	port(port const&) = delete;
+	port& operator =(port const&) = delete;
+
+	port(port&& old)
+	:	c(old.c)
+	,	p(old.p)
+	{
+		old.p = nullptr;
+	}
+	port& operator =(port&& old)
+	{
+		if (this != &old)
+		{
+			if (p)
+				jack_port_unregister(c, p);
+
+			c = old.c;
+			p = old.p;
+
+			old.p = nullptr;
+		}
+		return *this;
+	}
+
+	jack_port_t* operator *(void)
+	{
+		return p;
+	}
+
+private:
+	jack_client_t* c = nullptr;
+	jack_port_t* p = nullptr;
+};
+
+}
+
+jack::client client("CC-map", JackNoStartServer);
+jack::port p1(client, "in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
+jack::port p2(client, "out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);
+
+unsigned char remap[128];
+
+int cb_process(jack_nframes_t nframes, void* arg)
+{
+	void* in = jack_port_get_buffer(*p1, nframes);
+	void* out = jack_port_get_buffer(*p2, nframes);
+
+	jack_midi_clear_buffer(out);
+
+	jack_midi_event_t event;
+	for (uint32_t i = 0; not jack_midi_event_get(&event, in, i); ++i)
+	{
+		jack_midi_data_t* buf = jack_midi_event_reserve(out, event.time, event.size);
+		memcpy(buf, event.buffer, event.size);
+
+		for (unsigned i = 0; i < event.size; ++i)
+		{
+			if (~buf[0] & 0x80)
+				continue;
+
+			if ((buf[0] >> 4 & 0x7) != 3) // Controller change
+				continue;
+
+			buf[1] = remap[buf[1]];
+		}
+	}
+
+	return 0;
+}
+
+extern char const* names[98];
+
+unsigned find_cc(char const* str)
+{
+	for (unsigned i = 0; i < ARRAY_SIZE(names); ++i)
+		if (names[i] && !strcmp(str, names[i]))
+			return i;
+
+	printf("'%s' is not a known MIDI CC\n", str);
+	exit(1);
+}
+
+unsigned check_cc_in_range(char const* str)
+{
+	unsigned long val = strtoul(str, NULL, 10);
+
+	if (val < 128)
+		return val;
+
+	printf("%s is too large to be a MIDI CC", str);
+	exit(1);
+}
+
+void check_regerror(int err, regex_t* preg)
+{
+	if (!err)
+		return;
+
+	char buf[256];
+	regerror(err, preg, buf, sizeof(buf));
+	puts(buf);
+	exit(1);
+}
+
+int main(int argc, char* argv[])
+{
+	for (unsigned i = 0; i < 128; ++i)
+		remap[i] = i;
+
+	char const re[] = "^(([[:digit:]]+)|([[:alnum:]]+))=(([[:digit:]]+)|([[:alnum:]]+))$";
+	regex_t preg;
+
+	check_regerror(regcomp(&preg, re, REG_EXTENDED), &preg);
+
+	if (argc < 2)
+	{
+		puts("Usage:\n"
+		"    jack-cc-map IN=OUT ...\n\n"
+		"IN and OUT can either be:\n"
+		"    a number from 0 to 127\n"
+		"  OR\n"
+		"    a symbolic name\n\n"
+		"CC messages will be routed from IN to OUT");
+	}
+
+	while (*++argv)
+	{
+		unsigned in, out;
+		char const* const str = *argv;
+		regmatch_t pmatch[7];
+
+		int res = regexec(&preg, str, ARRAY_SIZE(pmatch), pmatch, 0);
+		if (res == REG_NOMATCH)
+			continue;
+
+		check_regerror(res, &preg);
+
+
+		for (unsigned j = 0; j < ARRAY_SIZE(pmatch); ++j)
+		{
+			if (pmatch[j].rm_so == -1)
+				continue;
+
+			char buf[128];
+			size_t len = pmatch[j].rm_eo - pmatch[j].rm_so;
+			len = std::min(len, ARRAY_SIZE(buf) - 1);
+
+			strncpy(buf, str + pmatch[j].rm_so, len)[len] = '\0';
+
+			switch (j)
+			{
+			case 2:
+				in = check_cc_in_range(buf);
+				break;
+			case 5:
+				out = check_cc_in_range(buf);
+				break;
+			case 3:
+				in = find_cc(buf);
+				break;
+			case 6:
+				out = find_cc(buf);
+				break;
+			}
+		}
+
+		remap[in] = out;
+	}
+
+	regfree(&preg);
+
+	client.set_process_callback(cb_process, nullptr);
+	client.activate();
+
+	for (;;)
+		std::this_thread::sleep_for(100ms);
+
+	return 0;
+}
diff --git a/makefile b/makefile
@@ -1,4 +1,13 @@
 CXXFLAGS := -std=c++20
 LDLIBS := -ljack
 
-main:
+targets := main jack-cc-map
+
+all: $(targets)
+
+jack-cc-map: jack-cc-map.cpp names.o
+
+clean:
+	rm $(targets) *.o *.d
+
+.PHONY: all clean
diff --git a/names.c b/names.c
@@ -0,0 +1,48 @@
+char const* const names[98] =
+{
+	[0] = "bank",
+	[1] = "mod",
+	[2] = "breath",
+	[4] = "foot",
+	[5] = "portamento-time",
+	[6] = "data-entry-msb",
+	[7] = "volume",
+	[8] = "balance",
+	[10] = "pan",
+	[11] = "expression",
+	[12] = "effect-1",
+	[13] = "effect-2",
+	[16] = "general-purpose-1",
+	[17] = "general-purpose-2",
+	[18] = "general-purpose-3",
+	[19] = "general-purpose-4",
+	[64] = "sustain",
+	[65] = "portamento",
+	[66] = "sostenuto",
+	[67] = "soft",
+	[68] = "legato",
+	[69] = "hold-2",
+	[70] = "variation",
+	[71] = "timbre",
+	[72] = "release-time",
+	[73] = "attack-time",
+	[74] = "brightness",
+	[75] = "sound-controller-6",
+	[76] = "sound-controller-7",
+	[77] = "sound-controller-8",
+	[78] = "sound-controller-9",
+	[79] = "sound-controller-10",
+	[80] = "general-purpose-5",
+	[81] = "general-purpose-6",
+	[82] = "general-purpose-7",
+	[83] = "general-purpose-8",
+	[84] = "portamento-control",
+	[88] = "velocity-prefix",
+	[91] = "effects-1-depth",
+	[92] = "effects-2-depth",
+	[93] = "effects-3-depth",
+	[94] = "effects-4-depth",
+	[95] = "effects-5-depth",
+	[96] = "data-increment",
+	[97] = "data-decrement",
+};