command-line utility to translate between Mackie HUI and Open Sound Control
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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