jack-cc-map.cpp (3410B)
1 #include <jack/midiport.h> 2 3 #include "jack.hpp" 4 5 #include <memory> 6 #include <cassert> 7 8 #include <cmath> 9 #include <cstring> 10 11 #include <regex.h> 12 //#include <assert.h> 13 //#include <stdio.h> 14 //#include <string.h> 15 16 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*a)) 17 //#define min(a,b) \ 18 // ({ __typeof__ (a) _a = (a); \ 19 // __typeof__ (b) _b = (b); \ 20 // _a < _b ? _a : _b; }) 21 22 23 #include <thread> 24 #include <chrono> 25 #include <iostream> 26 #include <iomanip> 27 28 using namespace std::literals::chrono_literals; 29 30 jack::client client("CC-map", JackNoStartServer); 31 jack::port p1(client, "in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); 32 jack::port p2(client, "out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); 33 34 unsigned char remap[128]; 35 36 int cb_process(jack_nframes_t nframes, void* arg) 37 { 38 void* in = jack_port_get_buffer(*p1, nframes); 39 void* out = jack_port_get_buffer(*p2, nframes); 40 41 jack_midi_clear_buffer(out); 42 43 jack_midi_event_t event; 44 for (uint32_t i = 0; not jack_midi_event_get(&event, in, i); ++i) 45 { 46 jack_midi_data_t* buf = jack_midi_event_reserve(out, event.time, event.size); 47 memcpy(buf, event.buffer, event.size); 48 49 for (unsigned i = 0; i < event.size; ++i) 50 { 51 if (~buf[0] & 0x80) 52 continue; 53 54 if ((buf[0] >> 4 & 0x7) != 3) // Controller change 55 continue; 56 57 buf[1] = remap[buf[1]]; 58 } 59 } 60 61 return 0; 62 } 63 64 extern char const* names[98]; 65 66 unsigned find_cc(char const* str) 67 { 68 for (unsigned i = 0; i < ARRAY_SIZE(names); ++i) 69 if (names[i] && !strcmp(str, names[i])) 70 return i; 71 72 printf("'%s' is not a known MIDI CC\n", str); 73 exit(1); 74 } 75 76 unsigned check_cc_in_range(char const* str) 77 { 78 unsigned long val = strtoul(str, NULL, 10); 79 80 if (val < 128) 81 return val; 82 83 printf("%s is too large to be a MIDI CC", str); 84 exit(1); 85 } 86 87 void check_regerror(int err, regex_t* preg) 88 { 89 if (!err) 90 return; 91 92 char buf[256]; 93 regerror(err, preg, buf, sizeof(buf)); 94 std::cerr << buf << '\n'; 95 exit(1); 96 } 97 98 int main(int argc, char* argv[]) 99 { 100 for (unsigned i = 0; i < 128; ++i) 101 remap[i] = i; 102 103 char const re[] = "^(([[:digit:]]+)|([[:alnum:]]+))=(([[:digit:]]+)|([[:alnum:]]+))$"; 104 regex_t preg; 105 106 check_regerror(regcomp(&preg, re, REG_EXTENDED), &preg); 107 108 if (argc < 2) 109 { 110 std::cerr << "Usage:\n" 111 " jack-cc-map IN=OUT ...\n\n" 112 "IN and OUT can either be:\n" 113 " a number from 0 to 127\n" 114 " OR\n" 115 " a symbolic name\n\n" 116 "CC messages will be routed from IN to OUT\n"; 117 return 1; 118 } 119 120 while (*++argv) 121 { 122 unsigned in, out; 123 char const* const str = *argv; 124 regmatch_t pmatch[7]; 125 126 int res = regexec(&preg, str, ARRAY_SIZE(pmatch), pmatch, 0); 127 if (res == REG_NOMATCH) 128 { 129 std::cerr << "Bad mapping '" << str << "' is not in format IN=OUT\n"; 130 return 1; 131 } 132 133 check_regerror(res, &preg); 134 135 for (unsigned j = 0; j < ARRAY_SIZE(pmatch); ++j) 136 { 137 if (pmatch[j].rm_so == -1) 138 continue; 139 140 char buf[128]; 141 size_t len = pmatch[j].rm_eo - pmatch[j].rm_so; 142 len = std::min(len, ARRAY_SIZE(buf) - 1); 143 144 strncpy(buf, str + pmatch[j].rm_so, len)[len] = '\0'; 145 146 switch (j) 147 { 148 case 2: 149 in = check_cc_in_range(buf); 150 break; 151 case 5: 152 out = check_cc_in_range(buf); 153 break; 154 case 3: 155 in = find_cc(buf); 156 break; 157 case 6: 158 out = find_cc(buf); 159 break; 160 } 161 } 162 163 remap[in] = out; 164 } 165 166 regfree(&preg); 167 168 client.set_process_callback(cb_process, nullptr); 169 client.activate(); 170 171 for (;;) 172 std::this_thread::sleep_for(100ms); 173 174 return 0; 175 }