jack-tools

A handful of JACK audio tools
git clone git://henryandlizzy.uk/jack-tools
Log | Files | Refs

commit 9ba76539b372bfc6f6215e0a665ebbbd80d598c1
parent 1ddc9e9e4a6bb61f7f567453707602d6e37c2311
Author: Henry Wilson <henry@henryandlizzy.uk>
Date:   Thu, 25 Nov 2021 21:41:40 +0000

add jack-cc-map

Diffstat:
Ajack-cc-map | 0
Ajack-cc-map.cpp | 276+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmakefile | 11++++++++++-
Anames.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", +};