command-line utility to translate between Mackie HUI and Open Sound Control
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

4 anos atrás
4 anos atrás
4 anos atrás
4 anos atrás
4 anos atrás
4 anos atrás
4 anos atrás
4 anos atrás
4 anos atrás
4 anos atrás
4 anos atrás
4 anos atrás
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  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. }