command-line utility to translate between Mackie HUI and Open Sound Control
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

640 lines
18KB

  1. // housic - translate Mackie HUI MIDI data to OSC and back
  2. //
  3. // Copyright (C) 2020 Dominik Schmidt-Philipp
  4. //
  5. // This program is free software: you can redistribute it and/or modify it under
  6. // the terms of the GNU General Public License as published by the Free Software
  7. // Foundation, either version 3 of the License, or (at your option) any later
  8. // version.
  9. //
  10. // This program is distributed in the hope that it will be useful, but WITHOUT
  11. // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  12. // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License along with
  15. // this program. If not, see <http s ://www.gnu.org/licenses/>.
  16. /*
  17. * Programmer: Dominik Schmidt-Philipp <schmidt-philipp@kulturteknologi.no>
  18. * Filename: main.c
  19. *
  20. * Dedicated to seleomlivet
  21. *
  22. */
  23. #include <alsa/asoundlib.h>
  24. #include <pthread.h>
  25. #include <lo/lo.h>
  26. #include <unistd.h>
  27. #define ZONE_COUNT 0x1e
  28. #define PORT_COUNT 196
  29. #define DISPLAY_COUNT 38
  30. #define HDR 0xf0, 0x00, 0x00, 0x66, 0x05, 0x00
  31. enum display_type {
  32. LABEL,
  33. VPOT,
  34. MAIN_DISPLAY,
  35. TIMECODE,
  36. METER
  37. };
  38. /* enum vpot_mode { */
  39. /* POINTER, */
  40. /* CENTERED_FILL, */
  41. /* FILL, */
  42. /* WIDTH */
  43. /* }; */
  44. typedef struct {
  45. char zone;
  46. char port;
  47. void* io;
  48. } Hui_MIDI_port_t;
  49. typedef struct {
  50. int type;
  51. int id;
  52. char state;
  53. void* io;
  54. } Hui_display_port_t;
  55. typedef struct {
  56. snd_rawmidi_t* midiin;
  57. snd_rawmidi_t* midiout;
  58. lo_address osc_out;
  59. lo_server_thread osc_in;
  60. unsigned char hui_in_zone;
  61. int hui_in_fader_hi[8];
  62. Hui_MIDI_port_t MIDIouts[PORT_COUNT];
  63. Hui_display_port_t display_outs[DISPLAY_COUNT];
  64. } housicIO;
  65. typedef struct {
  66. char type;
  67. char* address;
  68. } Item;
  69. char *ZONES[] = {
  70. "channel_strip_1",
  71. "channel_strip_2",
  72. "channel_strip_3",
  73. "channel_strip_4",
  74. "channel_strip_5",
  75. "channel_strip_6",
  76. "channel_strip_7",
  77. "channel_strip_8",
  78. "keyboard_shortcuts",
  79. "window",
  80. "channel_selection",
  81. "assignment_1",
  82. "assignment_2",
  83. "cursor",
  84. "transport_main",
  85. "transport_add1",
  86. "transport_add2",
  87. "monitor_input",
  88. "monitor_output",
  89. "num_pad_1",
  90. "num_pad_2",
  91. "num_pad_3",
  92. "timecode",
  93. "auto_enable",
  94. "auto_mode",
  95. "status_group",
  96. "edit",
  97. "fn_keys",
  98. "parameter_edit",
  99. "misc"
  100. };
  101. char *BUTTONS[ZONE_COUNT][8] = {
  102. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  103. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  104. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  105. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  106. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  107. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  108. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  109. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  110. {"ctrl","shift","editmode","undo","alt","option","edittool","save"},
  111. {"mix","edit","transprt","mem-loc","status","alt"},
  112. {"chan_left","bank_left","chanl_right","bank_right"},
  113. {"output","input","pan","send_e","send_d","send_c","send_b","send_a"},
  114. {"assign","default","suspend","shift","mute","bypass","recrdyall"},
  115. {"down","left","mode","right","up","scrub","shuttle"},
  116. {"talkback","rewind","fast_fwd","stop","play","record"},
  117. {"rtz","end","on_line","loop","quick_punch"},
  118. {"audition","pre","in","out","post"},
  119. {"input_3 ","input_2","input_1","mute","discrete"},
  120. {"output_3","output_2","output_1","dim","mono"},
  121. {"0","1","4","2","5",".","3","6"},
  122. {"enter","+"},
  123. {"7","8","9","-","clr","=","divide","multiply"},
  124. {"timecode","feet","beats","rudesolo"},
  125. {"plug_in","pan","fader","sendmute","send","mute"},
  126. {"trim","latch","read","off","write","touch"},
  127. {"phase","monitor","auto","suspend","create","group"},
  128. {"paste","cut","capture","delete","copy","separate"},
  129. {"f1","f2","f3","f4","f5","f6","f7","f8"},
  130. {"insert","assign","select_1","select_2","select_3","select_4","bypass","compare"},
  131. {"switch_1","switch_2","click","beep"}
  132. };
  133. // function declarations:
  134. void alsa_error (const char *format, ...);
  135. void* midiinfunction (void * arg);
  136. void* ping (void * arg);
  137. void midi_in_dispatch (void * arg, unsigned char * m);
  138. void lo_error (int num, const char *m, const char *path);
  139. int osc_in_handler (const char *path, const char *types, lo_arg ** argv,
  140. int argc, void *data, void *user_data);
  141. void hui_in_button (void *arg, unsigned char m);
  142. void hui_in_scroll (void *arg, unsigned char port, unsigned char vv);
  143. void hui_in_fader (void *arg, unsigned char fader, int val);
  144. void osc_send (void *arg, Item el, float value);
  145. void register_osc_receives(void *arg);
  146. int osc_in_button_handler(const char *path, const char *types, lo_arg ** argv,
  147. int argc, void *data, void *user_data);
  148. int osc_in_display_handler (const char *path, const char *types, lo_arg ** argv,
  149. int argc, void *data, void *user_data);
  150. int osc_in_vpot_handler (const char *path, const char *types, lo_arg ** argv,
  151. int argc, void *data, void *user_data);
  152. int osc_in_meter_handler (const char *path, const char *types, lo_arg ** argv,
  153. int argc, void *data, void *user_data);
  154. int main(int argc, char *argv[]) {
  155. int status;
  156. int mode = SND_RAWMIDI_SYNC;
  157. pthread_t midiinthread;
  158. pthread_t pingthread;
  159. housicIO IOs = {NULL,NULL,0,NULL,0,{0,0,0,0,0,0,0,0},0};
  160. IOs.osc_out = lo_address_new(NULL,"7771");
  161. IOs.osc_in = lo_server_thread_new("7770", lo_error);
  162. const char* portname = "virtual";
  163. // MIDI
  164. if ((status = snd_rawmidi_open(NULL, &IOs.midiout, portname, mode)) < 0) {
  165. alsa_error("Problem opening MIDI output: %s", snd_strerror(status));
  166. exit(1);
  167. }
  168. if ((status = snd_rawmidi_open(&IOs.midiin, NULL, portname, mode)) < 0) {
  169. alsa_error("Problem opening MIDI input: %s", snd_strerror(status));
  170. exit(1);
  171. }
  172. status = pthread_create(&pingthread,NULL, ping, &IOs);
  173. status = pthread_create(&midiinthread, NULL, midiinfunction, &IOs);
  174. // OSC
  175. register_osc_receives(&IOs);
  176. //lo_server_thread_add_method(IOs.osc_in, NULL, NULL, osc_in_handler, NULL);
  177. lo_server_thread_start(IOs.osc_in);
  178. // wait for MIDI thread to end (will not happen)
  179. pthread_join(midiinthread, NULL);
  180. return 0;
  181. }
  182. //////////////////////////////
  183. //
  184. // ping -- It is important to send a ping in regular intervals. This
  185. // will keep the HUI in online mode.
  186. //
  187. void *ping(void *arg) {
  188. housicIO* IOs = (housicIO*)arg;
  189. char message[] = {0x90,0x00,0x00};
  190. while (1) {
  191. sleep(1);
  192. snd_rawmidi_write(IOs->midiout,message,3);
  193. }
  194. }
  195. //////////////////////////////
  196. //
  197. // midiinfunction -- Thread function which waits around until a MIDI
  198. // input byte arrives and then react correspondingly
  199. //
  200. void register_osc_receives(void *arg) {
  201. housicIO* IOs = (housicIO*)arg;
  202. int zone_c;
  203. int button_c;
  204. char address[32];
  205. int address_c = 0;
  206. for (zone_c=0; zone_c<ZONE_COUNT; zone_c++) {
  207. for (button_c=0; button_c<8; button_c++) {
  208. if (BUTTONS[zone_c][button_c]) {
  209. Hui_MIDI_port_t out = {zone_c,button_c,IOs};
  210. IOs->MIDIouts[address_c] = out;
  211. sprintf(address,"/%s/%s",ZONES[zone_c],BUTTONS[zone_c][button_c]);
  212. lo_server_thread_add_method(IOs->osc_in,
  213. address,
  214. "i",
  215. osc_in_button_handler,
  216. &IOs->MIDIouts[address_c]);
  217. address_c++;
  218. }
  219. }
  220. }
  221. int i;
  222. int display_c = 0;
  223. // Labels
  224. for (i=0;i<9;i++) {
  225. Hui_display_port_t out = {LABEL, i, 0, IOs};
  226. IOs->display_outs[display_c] = out;
  227. sprintf(address,"/label/%d",i);
  228. lo_server_thread_add_method(IOs->osc_in,
  229. address,
  230. "s",
  231. osc_in_display_handler,
  232. &IOs->display_outs[display_c]);
  233. display_c++;
  234. }
  235. // Main display:
  236. for (i=0;i<8;i++) {
  237. Hui_display_port_t out = {MAIN_DISPLAY, i, 0, IOs};
  238. IOs->display_outs[display_c] = out;
  239. sprintf(address,"/main_display/%d",i);
  240. lo_server_thread_add_method(IOs->osc_in,
  241. address,
  242. "s",
  243. osc_in_display_handler,
  244. &IOs->display_outs[display_c]);
  245. display_c++;
  246. }
  247. // Timecode
  248. Hui_display_port_t out = {TIMECODE, 0, 0, IOs};
  249. IOs->display_outs[display_c] = out;
  250. sprintf(address,"/timecode");
  251. lo_server_thread_add_method(IOs->osc_in,
  252. address,
  253. NULL,
  254. osc_in_display_handler,
  255. &IOs->display_outs[display_c]);
  256. display_c++;
  257. // Vpots
  258. for (i=0;i<12;i++) {
  259. Hui_display_port_t out = {VPOT, i, 0, IOs};
  260. IOs->display_outs[display_c] = out;
  261. sprintf(address,"/vpot/%d",i+1);
  262. lo_server_thread_add_method(IOs->osc_in,
  263. address,
  264. NULL,
  265. osc_in_vpot_handler,
  266. &IOs->display_outs[display_c]);
  267. display_c++;
  268. }
  269. // Meters
  270. for (i=0;i<8;i++) {
  271. Hui_display_port_t out = {METER, i, 0, IOs};
  272. IOs->display_outs[display_c] = out;
  273. sprintf(address,"/meter/%d",i+1);
  274. lo_server_thread_add_method(IOs->osc_in,
  275. address,
  276. "ff",
  277. osc_in_meter_handler,
  278. &IOs->display_outs[display_c]);
  279. display_c++;
  280. }
  281. }
  282. int osc_in_meter_handler(const char *path, const char *types, lo_arg ** argv,
  283. int argc, void *data, void *user_data) {
  284. Hui_display_port_t* out = (Hui_display_port_t*)user_data;
  285. housicIO* IOs = (housicIO*) out->io;
  286. unsigned char message[3];
  287. int i;
  288. char val;
  289. char side = 0;
  290. if (out->type == METER) {
  291. message[0] = 0xa0;
  292. message[1] = (0x0 | out->id);
  293. side = (char)argv[0]->f;
  294. if (side) {
  295. side = 0x10;
  296. }
  297. val = (char)argv[1]->f;
  298. if ( val <= 0xc) {
  299. message[2] = side | val;
  300. snd_rawmidi_write(IOs->midiout,message,3);
  301. }
  302. }
  303. }
  304. int osc_in_vpot_handler(const char *path, const char *types, lo_arg ** argv,
  305. int argc, void *data, void *user_data) {
  306. Hui_display_port_t* out = (Hui_display_port_t*)user_data;
  307. housicIO* IOs = (housicIO*) out->io;
  308. unsigned char message[3];
  309. int i;
  310. int size;
  311. if (out->type == VPOT) {
  312. size = 3;
  313. message[0] = 0xb0;
  314. for (i=0; i<argc; i++) {
  315. if (types[i] == 's') {
  316. // p: POINTER
  317. // c: CENTERED_FILL
  318. // f: FILL
  319. // w: WIDTH
  320. if (argv[i]->s == 'p' ) {
  321. out->state = (out->state & 0x0f) | 0x00;
  322. } else if (argv[i]->s == 'c' ) {
  323. out->state = (out->state & 0x0f) | 0x10;
  324. } else if (argv[i]->s == 'f' ) {
  325. out->state = (out->state & 0x0f) | 0x20;
  326. } else if (argv[i]->s == 'w' ) {
  327. out->state = (out->state & 0x0f) | 0x30;
  328. }
  329. } else if (types[i] == 'f') {
  330. if (argv[i]->f < 12) {
  331. out->state = ((unsigned char)argv[i]->f & 0x0f) | (out->state & 0xf0);
  332. }
  333. }
  334. }
  335. message[1] = (0x10 | out->id);
  336. message[2] = out->state;
  337. snd_rawmidi_write(IOs->midiout,message,size);
  338. }
  339. }
  340. int osc_in_display_handler(const char *path, const char *types, lo_arg ** argv,
  341. int argc, void *data, void *user_data) {
  342. Hui_display_port_t* out = (Hui_display_port_t*)user_data;
  343. housicIO* IOs = (housicIO*) out->io;
  344. char* val = (char*)argv[0];
  345. int i;
  346. int size;
  347. char message[52] = {HDR};
  348. char timecode[8];
  349. if (out->type == LABEL) {
  350. size=13;
  351. message[6] = 0x10;
  352. message[7] = out->id;
  353. for (i=0; i<4; i++) {
  354. message[8+i] = val[i] ? val[i] & 0x7f : 0x20;
  355. }
  356. } else if (out->type == MAIN_DISPLAY) {
  357. size=19;
  358. message[6] = 0x12;
  359. message[7] = out->id;
  360. for (i=0; i<10; i++) {
  361. message[8+i] = (val[i] > 0x10) ? val[i] & 0x7f : 0x20;
  362. }
  363. } else if (out->type == TIMECODE) {
  364. size=0;
  365. message[6] = 0x11;
  366. printf("%s \n",types);
  367. for (i=0; i<argc; i++) {
  368. // size keeps track of how many digits have been entered
  369. // make sure we only read 8 digits
  370. if (types[i] == 'f' && size < 8) {
  371. timecode[size] = (unsigned char) argv[i]->f & 0xf;
  372. size++;
  373. } else if (types[i] == 's') {
  374. if (argv[i]->s == '.') {
  375. // a dot behind a digit has 5th bit set
  376. timecode[size-1] = timecode[size-1] + 0x10;
  377. }
  378. }
  379. }
  380. for (i=0;i<size;i++) {
  381. // write timecode to message with a leading LSB
  382. // also LSB must be below 10 because it has no point (read doc)
  383. message[7+i] = i ? timecode[size-1-i]: timecode[size-1-i] & 0xf;
  384. }
  385. message[7+size] = 0xf7;
  386. size += 8;
  387. } else if (out->type == METER) {
  388. }
  389. message[size-1] = 0xf7;
  390. snd_rawmidi_write(IOs->midiout,message,size);
  391. return 1;
  392. }
  393. //////////////////////////////
  394. //
  395. // osc_in_button_handler -- called when a button type message is
  396. // received and should be sent to the HUI
  397. //
  398. int osc_in_button_handler(const char *path, const char *types, lo_arg ** argv,
  399. int argc, void *data, void *user_data) {
  400. Hui_MIDI_port_t* out = (Hui_MIDI_port_t*)user_data;
  401. housicIO* IOs = (housicIO*) out->io;
  402. char value;
  403. int status;
  404. // char message[6];
  405. char out_data;
  406. value = argv[0]->i;
  407. out_data = value ? 0x40 : 0x00;
  408. out_data |= out->port;
  409. unsigned char message[6] = {0xb0,0x0c,out->zone,0xb0,0x2c,out_data};
  410. status = snd_rawmidi_write(IOs->midiout,message,6);
  411. //printf("%s - %x %x %x %x %x %x\n",path,
  412. // message[0],message[1],message[2],message[3],message[4],message[5]);
  413. return 1;
  414. }
  415. //////////////////////////////
  416. //
  417. // midiinfunction -- Thread function which waits around until a MIDI
  418. // input byte arrives and then react correspondingly
  419. //
  420. void *midiinfunction(void *arg) {
  421. housicIO* IOs = (housicIO*)arg;
  422. snd_rawmidi_t* midiin = IOs->midiin;
  423. int status;
  424. int i = 0;
  425. char buffer[3];
  426. unsigned char message[3];
  427. while (1) {
  428. if ((status = snd_rawmidi_read(midiin, buffer, 3)) < 0) {
  429. alsa_error("Problem reading MIDI input: %s", snd_strerror(status));
  430. }
  431. // in case of MIDI running status, value bytes need to not override status byte
  432. for (i=1; i<=status;i++) {
  433. message[3-i] = (unsigned char) buffer[status-i];
  434. }
  435. if (status==2) {
  436. message[0] = 0xb0;
  437. } else if(0) { // only for debugging
  438. printf("%d: %x %x %x - %x %x %x\n", status,
  439. buffer[0],buffer[1],buffer[2],
  440. message[0], message[1], message[2]);
  441. }
  442. midi_in_dispatch(IOs,message);
  443. fflush(stdout);
  444. }
  445. }
  446. //////////////////////////////
  447. //
  448. // midi_in_dispatch -- Thread function which waits around until a MIDI
  449. // input byte arrives and then react correspondingly
  450. //
  451. void midi_in_dispatch(void *arg, unsigned char *m) {
  452. housicIO* IOs = (housicIO*)arg;
  453. int fader_value;
  454. if (m[0] == 0xb0) {
  455. // received a CC message on MIDIchannel 1
  456. if (m[1] == 0x0f) {
  457. // received a zone select
  458. IOs->hui_in_zone = m[2];
  459. } else if (m[1] == 0x2f) { // switches
  460. hui_in_button(IOs,m[2]);
  461. } else if ((m[1] & 0xf0) == 0x40) { // V-pots:
  462. hui_in_scroll(IOs,m[1] & 0xf, m[2]);
  463. } else if (m[1] == 0x0d) { // jog wheel:
  464. hui_in_scroll(IOs,0xd,m[2]);
  465. } else if (m[1] < 0x08) { // fader Hi
  466. IOs->hui_in_fader_hi[m[1]] = (int) m[2];
  467. } else if ((m[1] & 0xf0) == 0x20) { // fader Lo
  468. fader_value = (IOs->hui_in_fader_hi[m[1] & 0x0f] << 2) | (m[2] >> 5);
  469. hui_in_fader(IOs,m[1] & 0x0f,fader_value);
  470. }
  471. }
  472. }
  473. //////////////////////////////
  474. //
  475. // hui_in_fader -- fader positions
  476. //
  477. void hui_in_fader(void *arg, unsigned char port, int val) {
  478. housicIO* IOs = (housicIO*)arg;
  479. char address[32];
  480. sprintf(address, "/fader/%d", port + 1);
  481. Item ee = {'f',address};
  482. osc_send(IOs, ee, val);
  483. }
  484. //////////////////////////////
  485. //
  486. // hui_in_scroll -- user interacted with a button on the HUI
  487. //
  488. void hui_in_scroll(void *arg, unsigned char port, unsigned char vv) {
  489. housicIO* IOs = (housicIO*)arg;
  490. char address[32];
  491. sprintf(address, "/vpot/%d", port + 1);
  492. Item ee = {'s', address};
  493. if (vv > 0x40) {
  494. osc_send(IOs, ee, vv - 0x40);
  495. } else if (vv < 0x40) {
  496. osc_send(IOs, ee, -vv);
  497. }
  498. }
  499. //////////////////////////////
  500. //
  501. // hui_in_button -- user interacted with a button on the HUI
  502. //
  503. void hui_in_button(void *arg, unsigned char m) {
  504. housicIO* IOs = (housicIO*)arg;
  505. char address[32];
  506. char zone = IOs->hui_in_zone;
  507. int dir = m & 0xf0;
  508. int port = m & 0x0f;
  509. sprintf(address, "/%s/%s",ZONES[zone],BUTTONS[zone][port]);
  510. Item ee = {'b',address};
  511. if (dir == 0x40) {
  512. // down
  513. osc_send(IOs, ee, 1);
  514. } else if (dir == 0x00) {
  515. // up
  516. osc_send(IOs, ee, 0);
  517. }
  518. }
  519. //////////////////////////////
  520. //
  521. // osc_send --
  522. //
  523. void osc_send(void *arg, Item el, float value) {
  524. housicIO* IOs = (housicIO*)arg;
  525. lo_send(IOs->osc_out, el.address, "f", value );
  526. }
  527. //////////////////////////////
  528. //
  529. // osc_in_handler -- receive incoming OSC messages
  530. //
  531. int osc_in_handler (const char *path, const char *types, lo_arg ** argv,
  532. int argc, void *data, void *user_data) {
  533. int i;
  534. printf("path: <%s>\n", path);
  535. for (i = 0; i < argc; i++) {
  536. printf("arg %d '%c' ", i, types[i]);
  537. lo_arg_pp((lo_type)types[i], argv[i]);
  538. printf("\n");
  539. }
  540. printf("\n");
  541. fflush(stdout);
  542. return 1;
  543. }
  544. //////////////////////////////
  545. //
  546. // error -- print error message
  547. //
  548. void alsa_error(const char *format, ...) {
  549. va_list ap;
  550. va_start(ap, format);
  551. vfprintf(stderr, format, ap);
  552. va_end(ap);
  553. putc('\n', stderr);
  554. }
  555. void lo_error(int num, const char *msg, const char *path)
  556. {
  557. printf("liblo server error %d in path %s: %s\n", num, path, msg);
  558. fflush(stdout);
  559. }