command-line utility to translate between Mackie HUI and Open Sound Control
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

444 lines
12KB

  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 9
  15. enum display_type {
  16. LABEL,
  17. VPOT
  18. };
  19. typedef struct {
  20. char zone;
  21. char port;
  22. void* io;
  23. } Hui_MIDI_port_t;
  24. typedef struct {
  25. int type;
  26. int id;
  27. void* io;
  28. } Hui_display_port_t;
  29. typedef struct {
  30. snd_rawmidi_t* midiin;
  31. snd_rawmidi_t* midiout;
  32. lo_address osc_out;
  33. lo_server_thread osc_in;
  34. unsigned char hui_in_zone;
  35. int hui_in_fader_hi[8];
  36. Hui_MIDI_port_t MIDIouts[PORT_COUNT];
  37. Hui_display_port_t display_outs[DISPLAY_COUNT];
  38. } housicIO;
  39. typedef struct {
  40. char type;
  41. char* address;
  42. } Item;
  43. char *ZONES[] = {
  44. "channel_strip_1",
  45. "channel_strip_2",
  46. "channel_strip_3",
  47. "channel_strip_4",
  48. "channel_strip_5",
  49. "channel_strip_6",
  50. "channel_strip_7",
  51. "channel_strip_8",
  52. "keyboard_shortcuts",
  53. "window",
  54. "channel_selection",
  55. "assignment_1",
  56. "assignment_2",
  57. "cursor",
  58. "transport_main",
  59. "transport_add1",
  60. "transport_add2",
  61. "monitor_input",
  62. "monitor_output",
  63. "num_pad_1",
  64. "num_pad_2",
  65. "num_pad_3",
  66. "timecode",
  67. "auto_enable",
  68. "auto_mode",
  69. "status_group",
  70. "edit",
  71. "fn_keys",
  72. "parameter_edit",
  73. "misc"
  74. };
  75. char *BUTTONS[ZONE_COUNT][8] = {
  76. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  77. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  78. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  79. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  80. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  81. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  82. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  83. {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"},
  84. {"ctrl","shift","editmode","undo","alt","option","edittool","save"},
  85. {"mix","edit","transprt","mem-loc","status","alt"},
  86. {"chan_left","bank_left","chanl_right","bank_right"},
  87. {"output","input","pan","send_e","send_d","send_c","send_b","send_a"},
  88. {"assign","default","suspend","shift","mute","bypass","recrdyall"},
  89. {"down","left","mode","right","up","scrub","shuttle"},
  90. {"talkback","rewind","fast_fwd","stop","play","record"},
  91. {"rtz","end","on_line","loop","quick_punch"},
  92. {"audition","pre","in","out","post"},
  93. {"input_3 ","input_2","input_1","mute","discrete"},
  94. {"output_3","output_2","output_1","dim","mono"},
  95. {"0","1","4","2","5",".","3","6"},
  96. {"enter","+"},
  97. {"7","8","9","-","clr","=","divide","multiply"},
  98. {"timecode","feet","beats","rudesolo"},
  99. {"plug_in","pan","fader","sendmute","send","mute"},
  100. {"trim","latch","read","off","write","touch"},
  101. {"phase","monitor","auto","suspend","create","group"},
  102. {"paste","cut","capture","delete","copy","separate"},
  103. {"f1","f2","f3","f4","f5","f6","f7","f8"},
  104. {"insert","assign","select_1","select_2","select_3","select_4","bypass","compare"},
  105. {"switch_1","switch_2","click","beep"}
  106. };
  107. // function declarations:
  108. void alsa_error (const char *format, ...);
  109. void* midiinfunction (void * arg);
  110. void* ping (void * arg);
  111. void midi_in_dispatch (void * arg, unsigned char * m);
  112. void lo_error (int num, const char *m, const char *path);
  113. int osc_in_handler (const char *path, const char *types, lo_arg ** argv,
  114. int argc, void *data, void *user_data);
  115. void hui_in_button (void *arg, unsigned char m);
  116. void hui_in_scroll (void *arg, unsigned char port, unsigned char vv);
  117. void hui_in_fader (void *arg, unsigned char fader, int val);
  118. void osc_send (void *arg, Item el, float value);
  119. void register_osc_receives(void *arg);
  120. int osc_in_button_handler(const char *path, const char *types, lo_arg ** argv,
  121. int argc, void *data, void *user_data);
  122. int osc_in_label_handler (const char *path, const char *types, lo_arg ** argv,
  123. int argc, void *data, void *user_data);
  124. int main(int argc, char *argv[]) {
  125. int status;
  126. int mode = SND_RAWMIDI_SYNC;
  127. pthread_t midiinthread;
  128. pthread_t pingthread;
  129. housicIO IOs = {NULL,NULL,0,NULL,0,{0,0,0,0,0,0,0,0},0};
  130. IOs.osc_out = lo_address_new(NULL,"7771");
  131. IOs.osc_in = lo_server_thread_new("7770", lo_error);
  132. const char* portname = "virtual";
  133. // MIDI
  134. if ((status = snd_rawmidi_open(NULL, &IOs.midiout, portname, mode)) < 0) {
  135. alsa_error("Problem opening MIDI output: %s", snd_strerror(status));
  136. exit(1);
  137. }
  138. if ((status = snd_rawmidi_open(&IOs.midiin, NULL, portname, mode)) < 0) {
  139. alsa_error("Problem opening MIDI input: %s", snd_strerror(status));
  140. exit(1);
  141. }
  142. status = pthread_create(&pingthread,NULL, ping, &IOs);
  143. status = pthread_create(&midiinthread, NULL, midiinfunction, &IOs);
  144. // OSC
  145. register_osc_receives(&IOs);
  146. //lo_server_thread_add_method(IOs.osc_in, NULL, NULL, osc_in_handler, NULL);
  147. lo_server_thread_start(IOs.osc_in);
  148. // wait for MIDI thread to end (will not happen)
  149. pthread_join(midiinthread, NULL);
  150. return 0;
  151. }
  152. //////////////////////////////
  153. //
  154. // ping -- It is important to send a ping in regular intervals. This
  155. // will keep the HUI in online mode.
  156. //
  157. void *ping(void *arg) {
  158. housicIO* IOs = (housicIO*)arg;
  159. char message[] = {0x90,0x00,0x00};
  160. while (1) {
  161. sleep(1);
  162. snd_rawmidi_write(IOs->midiout,message,3);
  163. }
  164. }
  165. //////////////////////////////
  166. //
  167. // midiinfunction -- Thread function which waits around until a MIDI
  168. // input byte arrives and then react correspondingly
  169. //
  170. void register_osc_receives(void *arg) {
  171. housicIO* IOs = (housicIO*)arg;
  172. int zone_c;
  173. int button_c;
  174. char address[32];
  175. int address_c = 0;
  176. for (zone_c=0; zone_c<ZONE_COUNT; zone_c++) {
  177. for (button_c=0; button_c<8; button_c++) {
  178. if (BUTTONS[zone_c][button_c]) {
  179. Hui_MIDI_port_t out = {zone_c,button_c,IOs};
  180. IOs->MIDIouts[address_c] = out;
  181. sprintf(address,"/%s/%s",ZONES[zone_c],BUTTONS[zone_c][button_c]);
  182. lo_server_thread_add_method(IOs->osc_in,
  183. address,
  184. "i",
  185. osc_in_button_handler,
  186. &IOs->MIDIouts[address_c]);
  187. address_c++;
  188. }
  189. }
  190. }
  191. int i;
  192. int display_c = 0;
  193. for (i=0;i<9;i++) {
  194. Hui_display_port_t out = {LABEL,i,IOs};
  195. IOs->display_outs[display_c] = out;
  196. sprintf(address,"/label/%d",i);
  197. lo_server_thread_add_method(IOs->osc_in,
  198. address,
  199. "s",
  200. osc_in_label_handler,
  201. &IOs->display_outs[display_c]);
  202. display_c++;
  203. }
  204. }
  205. int osc_in_label_handler(const char *path, const char *types, lo_arg ** argv,
  206. int argc, void *data, void *user_data) {
  207. char* val;
  208. val = (char *)(lo_arg*) argv[0]->s;
  209. printf("%s: %d: %x\n",types,argc, argv);
  210. return 1;
  211. }
  212. //////////////////////////////
  213. //
  214. // osc_in_button_handler -- called when a button type message is
  215. // received and should be sent to the HUI
  216. //
  217. int osc_in_button_handler(const char *path, const char *types, lo_arg ** argv,
  218. int argc, void *data, void *user_data) {
  219. Hui_MIDI_port_t* out = (Hui_MIDI_port_t*)user_data;
  220. housicIO* IOs = (housicIO*) out->io;
  221. char value;
  222. int status;
  223. // char message[6];
  224. char out_data;
  225. value = argv[0]->i;
  226. out_data = value ? 0x40 : 0x00;
  227. out_data |= out->port;
  228. unsigned char message[6] = {0xb0,0x0c,out->zone,0xb0,0x2c,out_data};
  229. status = snd_rawmidi_write(IOs->midiout,message,6);
  230. printf("%s - %x %x %x %x %x %x\n",path,
  231. message[0],message[1],message[2],message[3],message[4],message[5]);
  232. return 1;
  233. }
  234. //////////////////////////////
  235. //
  236. // midiinfunction -- Thread function which waits around until a MIDI
  237. // input byte arrives and then react correspondingly
  238. //
  239. void *midiinfunction(void *arg) {
  240. housicIO* IOs = (housicIO*)arg;
  241. snd_rawmidi_t* midiin = IOs->midiin;
  242. int status;
  243. int i = 0;
  244. char buffer[3];
  245. unsigned char message[3];
  246. while (1) {
  247. if ((status = snd_rawmidi_read(midiin, buffer, 3)) < 0) {
  248. alsa_error("Problem reading MIDI input: %s", snd_strerror(status));
  249. }
  250. // in case of MIDI running status, value bytes need to not override status byte
  251. for (i=1; i<=status;i++) {
  252. message[3-i] = (unsigned char) buffer[status-i];
  253. }
  254. if (status==2) {
  255. message[0] = 0xb0;
  256. } else if(0) { // only for debugging
  257. printf("%d: %x %x %x - %x %x %x\n", status,
  258. buffer[0],buffer[1],buffer[2],
  259. message[0], message[1], message[2]);
  260. }
  261. midi_in_dispatch(IOs,message);
  262. fflush(stdout);
  263. }
  264. }
  265. //////////////////////////////
  266. //
  267. // midi_in_dispatch -- Thread function which waits around until a MIDI
  268. // input byte arrives and then react correspondingly
  269. //
  270. void midi_in_dispatch(void *arg, unsigned char *m) {
  271. housicIO* IOs = (housicIO*)arg;
  272. int fader_value;
  273. if (m[0] == 0xb0) {
  274. // received a CC message on MIDIchannel 1
  275. if (m[1] == 0x0f) {
  276. // received a zone select
  277. IOs->hui_in_zone = m[2];
  278. } else if (m[1] == 0x2f) { // switches
  279. hui_in_button(IOs,m[2]);
  280. } else if ((m[1] & 0xf0) == 0x40) { // V-pots:
  281. hui_in_scroll(IOs,m[1] & 0xf, m[2]);
  282. } else if (m[1] == 0x0d) { // jog wheel:
  283. hui_in_scroll(IOs,0xd,m[2]);
  284. } else if (m[1] < 0x08) { // fader Hi
  285. IOs->hui_in_fader_hi[m[1]] = (int) m[2];
  286. } else if ((m[1] & 0xf0) == 0x20) { // fader Lo
  287. fader_value = (IOs->hui_in_fader_hi[m[1] & 0x0f] << 2) | (m[2] >> 5);
  288. hui_in_fader(IOs,m[1] & 0x0f,fader_value);
  289. }
  290. }
  291. }
  292. //////////////////////////////
  293. //
  294. // hui_in_fader -- fader positions
  295. //
  296. void hui_in_fader(void *arg, unsigned char port, int val) {
  297. housicIO* IOs = (housicIO*)arg;
  298. char address[32];
  299. sprintf(address, "/fader/%d", port + 1);
  300. Item ee = {'f',address};
  301. osc_send(IOs, ee, val);
  302. }
  303. //////////////////////////////
  304. //
  305. // hui_in_scroll -- user interacted with a button on the HUI
  306. //
  307. void hui_in_scroll(void *arg, unsigned char port, unsigned char vv) {
  308. housicIO* IOs = (housicIO*)arg;
  309. char address[32];
  310. sprintf(address, "/vpot/%d", port + 1);
  311. Item ee = {'s', address};
  312. if (vv > 0x40) {
  313. osc_send(IOs, ee, vv - 0x40);
  314. } else if (vv < 0x40) {
  315. osc_send(IOs, ee, -vv);
  316. }
  317. }
  318. //////////////////////////////
  319. //
  320. // hui_in_button -- user interacted with a button on the HUI
  321. //
  322. void hui_in_button(void *arg, unsigned char m) {
  323. housicIO* IOs = (housicIO*)arg;
  324. char address[32];
  325. char zone = IOs->hui_in_zone;
  326. int dir = m & 0xf0;
  327. int port = m & 0x0f;
  328. sprintf(address, "/%s/%s",ZONES[zone],BUTTONS[zone][port]);
  329. Item ee = {'b',address};
  330. if (dir == 0x40) {
  331. // down
  332. osc_send(IOs, ee, 1);
  333. } else if (dir == 0x00) {
  334. // up
  335. osc_send(IOs, ee, 0);
  336. }
  337. }
  338. //////////////////////////////
  339. //
  340. // osc_send --
  341. //
  342. void osc_send(void *arg, Item el, float value) {
  343. housicIO* IOs = (housicIO*)arg;
  344. lo_send(IOs->osc_out, el.address, "f", value );
  345. }
  346. //////////////////////////////
  347. //
  348. // osc_in_handler -- receive incoming OSC messages
  349. //
  350. int osc_in_handler (const char *path, const char *types, lo_arg ** argv,
  351. int argc, void *data, void *user_data) {
  352. int i;
  353. printf("path: <%s>\n", path);
  354. for (i = 0; i < argc; i++) {
  355. printf("arg %d '%c' ", i, types[i]);
  356. lo_arg_pp((lo_type)types[i], argv[i]);
  357. printf("\n");
  358. }
  359. printf("\n");
  360. fflush(stdout);
  361. return 1;
  362. }
  363. //////////////////////////////
  364. //
  365. // error -- print error message
  366. //
  367. void alsa_error(const char *format, ...) {
  368. va_list ap;
  369. va_start(ap, format);
  370. vfprintf(stderr, format, ap);
  371. va_end(ap);
  372. putc('\n', stderr);
  373. }
  374. void lo_error(int num, const char *msg, const char *path)
  375. {
  376. printf("liblo server error %d in path %s: %s\n", num, path, msg);
  377. fflush(stdout);
  378. }