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",
+};