// housic - translate Mackie HUI MIDI data to OSC and back // // Copyright (C) 2020 Dominik Schmidt-Philipp // // This program is free software: you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software // Foundation, either version 3 of the License, or (at your option) any later // version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along with // this program. If not, see . /* * Programmer: Dominik Schmidt-Philipp * Filename: main.c * * Dedicated to seleomlivet * */ #include #include #include #include #define ZONE_COUNT 0x1e #define PORT_COUNT 196 #define DISPLAY_COUNT 38 #define HDR 0xf0, 0x00, 0x00, 0x66, 0x05, 0x00 enum display_type { LABEL, VPOT, MAIN_DISPLAY, TIMECODE, METER }; /* enum vpot_mode { */ /* POINTER, */ /* CENTERED_FILL, */ /* FILL, */ /* WIDTH */ /* }; */ typedef struct { char zone; char port; void* io; } Hui_MIDI_port_t; typedef struct { int type; int id; char state; void* io; } Hui_display_port_t; typedef struct { snd_rawmidi_t* midiin; snd_rawmidi_t* midiout; lo_address osc_out; lo_server_thread osc_in; unsigned char hui_in_zone; int hui_in_fader_hi[8]; Hui_MIDI_port_t MIDIouts[PORT_COUNT]; Hui_display_port_t display_outs[DISPLAY_COUNT]; } housicIO; typedef struct { char type; char* address; } Item; char *ZONES[] = { "channel_strip_1", "channel_strip_2", "channel_strip_3", "channel_strip_4", "channel_strip_5", "channel_strip_6", "channel_strip_7", "channel_strip_8", "keyboard_shortcuts", "window", "channel_selection", "assignment_1", "assignment_2", "cursor", "transport_main", "transport_add1", "transport_add2", "monitor_input", "monitor_output", "num_pad_1", "num_pad_2", "num_pad_3", "timecode", "auto_enable", "auto_mode", "status_group", "edit", "fn_keys", "parameter_edit", "misc" }; char *BUTTONS[ZONE_COUNT][8] = { {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"}, {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"}, {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"}, {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"}, {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"}, {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"}, {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"}, {"fader","select","mute","solo","auto","v-sel","insert","rec_rdy"}, {"ctrl","shift","editmode","undo","alt","option","edittool","save"}, {"mix","edit","transprt","mem-loc","status","alt"}, {"chan_left","bank_left","chanl_right","bank_right"}, {"output","input","pan","send_e","send_d","send_c","send_b","send_a"}, {"assign","default","suspend","shift","mute","bypass","recrdyall"}, {"down","left","mode","right","up","scrub","shuttle"}, {"talkback","rewind","fast_fwd","stop","play","record"}, {"rtz","end","on_line","loop","quick_punch"}, {"audition","pre","in","out","post"}, {"input_3 ","input_2","input_1","mute","discrete"}, {"output_3","output_2","output_1","dim","mono"}, {"0","1","4","2","5",".","3","6"}, {"enter","+"}, {"7","8","9","-","clr","=","divide","multiply"}, {"timecode","feet","beats","rudesolo"}, {"plug_in","pan","fader","sendmute","send","mute"}, {"trim","latch","read","off","write","touch"}, {"phase","monitor","auto","suspend","create","group"}, {"paste","cut","capture","delete","copy","separate"}, {"f1","f2","f3","f4","f5","f6","f7","f8"}, {"insert","assign","select_1","select_2","select_3","select_4","bypass","compare"}, {"switch_1","switch_2","click","beep"} }; // function declarations: void alsa_error (const char *format, ...); void* midiinfunction (void * arg); void* ping (void * arg); void midi_in_dispatch (void * arg, unsigned char * m); void lo_error (int num, const char *m, const char *path); int osc_in_handler (const char *path, const char *types, lo_arg ** argv, int argc, void *data, void *user_data); void hui_in_button (void *arg, unsigned char m); void hui_in_scroll (void *arg, unsigned char port, unsigned char vv); void hui_in_fader (void *arg, unsigned char fader, int val); void osc_send (void *arg, Item el, float value); void register_osc_receives(void *arg); int osc_in_button_handler(const char *path, const char *types, lo_arg ** argv, int argc, void *data, void *user_data); int osc_in_display_handler (const char *path, const char *types, lo_arg ** argv, int argc, void *data, void *user_data); int osc_in_vpot_handler (const char *path, const char *types, lo_arg ** argv, int argc, void *data, void *user_data); int osc_in_meter_handler (const char *path, const char *types, lo_arg ** argv, int argc, void *data, void *user_data); int main(int argc, char *argv[]) { int status; int mode = SND_RAWMIDI_SYNC; pthread_t midiinthread; pthread_t pingthread; housicIO IOs = {NULL,NULL,0,NULL,0,{0,0,0,0,0,0,0,0},0}; IOs.osc_out = lo_address_new(NULL,"7771"); IOs.osc_in = lo_server_thread_new("7770", lo_error); const char* portname = "virtual"; // MIDI if ((status = snd_rawmidi_open(NULL, &IOs.midiout, portname, mode)) < 0) { alsa_error("Problem opening MIDI output: %s", snd_strerror(status)); exit(1); } if ((status = snd_rawmidi_open(&IOs.midiin, NULL, portname, mode)) < 0) { alsa_error("Problem opening MIDI input: %s", snd_strerror(status)); exit(1); } status = pthread_create(&pingthread,NULL, ping, &IOs); status = pthread_create(&midiinthread, NULL, midiinfunction, &IOs); // OSC register_osc_receives(&IOs); //lo_server_thread_add_method(IOs.osc_in, NULL, NULL, osc_in_handler, NULL); lo_server_thread_start(IOs.osc_in); // wait for MIDI thread to end (will not happen) pthread_join(midiinthread, NULL); return 0; } ////////////////////////////// // // ping -- It is important to send a ping in regular intervals. This // will keep the HUI in online mode. // void *ping(void *arg) { housicIO* IOs = (housicIO*)arg; char message[] = {0x90,0x00,0x00}; while (1) { sleep(1); snd_rawmidi_write(IOs->midiout,message,3); } } ////////////////////////////// // // midiinfunction -- Thread function which waits around until a MIDI // input byte arrives and then react correspondingly // void register_osc_receives(void *arg) { housicIO* IOs = (housicIO*)arg; int zone_c; int button_c; char address[32]; int address_c = 0; for (zone_c=0; zone_cMIDIouts[address_c] = out; sprintf(address,"/%s/%s",ZONES[zone_c],BUTTONS[zone_c][button_c]); lo_server_thread_add_method(IOs->osc_in, address, "i", osc_in_button_handler, &IOs->MIDIouts[address_c]); address_c++; } } } int i; int display_c = 0; // Labels for (i=0;i<9;i++) { Hui_display_port_t out = {LABEL, i, 0, IOs}; IOs->display_outs[display_c] = out; sprintf(address,"/label/%d",i); lo_server_thread_add_method(IOs->osc_in, address, "s", osc_in_display_handler, &IOs->display_outs[display_c]); display_c++; } // Main display: for (i=0;i<8;i++) { Hui_display_port_t out = {MAIN_DISPLAY, i, 0, IOs}; IOs->display_outs[display_c] = out; sprintf(address,"/main_display/%d",i); lo_server_thread_add_method(IOs->osc_in, address, "s", osc_in_display_handler, &IOs->display_outs[display_c]); display_c++; } // Timecode Hui_display_port_t out = {TIMECODE, 0, 0, IOs}; IOs->display_outs[display_c] = out; sprintf(address,"/timecode"); lo_server_thread_add_method(IOs->osc_in, address, NULL, osc_in_display_handler, &IOs->display_outs[display_c]); display_c++; // Vpots for (i=0;i<12;i++) { Hui_display_port_t out = {VPOT, i, 0, IOs}; IOs->display_outs[display_c] = out; sprintf(address,"/vpot/%d",i+1); lo_server_thread_add_method(IOs->osc_in, address, NULL, osc_in_vpot_handler, &IOs->display_outs[display_c]); display_c++; } // Meters for (i=0;i<8;i++) { Hui_display_port_t out = {METER, i, 0, IOs}; IOs->display_outs[display_c] = out; sprintf(address,"/meter/%d",i+1); lo_server_thread_add_method(IOs->osc_in, address, "ff", osc_in_meter_handler, &IOs->display_outs[display_c]); display_c++; } } int osc_in_meter_handler(const char *path, const char *types, lo_arg ** argv, int argc, void *data, void *user_data) { Hui_display_port_t* out = (Hui_display_port_t*)user_data; housicIO* IOs = (housicIO*) out->io; unsigned char message[3]; int i; char val; char side = 0; if (out->type == METER) { message[0] = 0xa0; message[1] = (0x0 | out->id); side = (char)argv[0]->f; if (side) { side = 0x10; } val = (char)argv[1]->f; if ( val <= 0xc) { message[2] = side | val; snd_rawmidi_write(IOs->midiout,message,3); } } } int osc_in_vpot_handler(const char *path, const char *types, lo_arg ** argv, int argc, void *data, void *user_data) { Hui_display_port_t* out = (Hui_display_port_t*)user_data; housicIO* IOs = (housicIO*) out->io; unsigned char message[3]; int i; int size; if (out->type == VPOT) { size = 3; message[0] = 0xb0; for (i=0; is == 'p' ) { out->state = (out->state & 0x0f) | 0x00; } else if (argv[i]->s == 'c' ) { out->state = (out->state & 0x0f) | 0x10; } else if (argv[i]->s == 'f' ) { out->state = (out->state & 0x0f) | 0x20; } else if (argv[i]->s == 'w' ) { out->state = (out->state & 0x0f) | 0x30; } } else if (types[i] == 'f') { if (argv[i]->f < 12) { out->state = ((unsigned char)argv[i]->f & 0x0f) | (out->state & 0xf0); } } } message[1] = (0x10 | out->id); message[2] = out->state; snd_rawmidi_write(IOs->midiout,message,size); } } int osc_in_display_handler(const char *path, const char *types, lo_arg ** argv, int argc, void *data, void *user_data) { Hui_display_port_t* out = (Hui_display_port_t*)user_data; housicIO* IOs = (housicIO*) out->io; char* val = (char*)argv[0]; int i; int size; char message[52] = {HDR}; char timecode[8]; if (out->type == LABEL) { size=13; message[6] = 0x10; message[7] = out->id; for (i=0; i<4; i++) { message[8+i] = val[i] ? val[i] & 0x7f : 0x20; } } else if (out->type == MAIN_DISPLAY) { size=19; message[6] = 0x12; message[7] = out->id; for (i=0; i<10; i++) { message[8+i] = (val[i] > 0x10) ? val[i] & 0x7f : 0x20; } } else if (out->type == TIMECODE) { size=0; message[6] = 0x11; printf("%s \n",types); for (i=0; if & 0xf; size++; } else if (types[i] == 's') { if (argv[i]->s == '.') { // a dot behind a digit has 5th bit set timecode[size-1] = timecode[size-1] + 0x10; } } } for (i=0;itype == METER) { } message[size-1] = 0xf7; snd_rawmidi_write(IOs->midiout,message,size); return 1; } ////////////////////////////// // // osc_in_button_handler -- called when a button type message is // received and should be sent to the HUI // int osc_in_button_handler(const char *path, const char *types, lo_arg ** argv, int argc, void *data, void *user_data) { Hui_MIDI_port_t* out = (Hui_MIDI_port_t*)user_data; housicIO* IOs = (housicIO*) out->io; char value; int status; // char message[6]; char out_data; value = argv[0]->i; out_data = value ? 0x40 : 0x00; out_data |= out->port; unsigned char message[6] = {0xb0,0x0c,out->zone,0xb0,0x2c,out_data}; status = snd_rawmidi_write(IOs->midiout,message,6); //printf("%s - %x %x %x %x %x %x\n",path, // message[0],message[1],message[2],message[3],message[4],message[5]); return 1; } ////////////////////////////// // // midiinfunction -- Thread function which waits around until a MIDI // input byte arrives and then react correspondingly // void *midiinfunction(void *arg) { housicIO* IOs = (housicIO*)arg; snd_rawmidi_t* midiin = IOs->midiin; int status; int i = 0; char buffer[3]; unsigned char message[3]; while (1) { if ((status = snd_rawmidi_read(midiin, buffer, 3)) < 0) { alsa_error("Problem reading MIDI input: %s", snd_strerror(status)); } // in case of MIDI running status, value bytes need to not override status byte for (i=1; i<=status;i++) { message[3-i] = (unsigned char) buffer[status-i]; } if (status==2) { message[0] = 0xb0; } else if(0) { // only for debugging printf("%d: %x %x %x - %x %x %x\n", status, buffer[0],buffer[1],buffer[2], message[0], message[1], message[2]); } midi_in_dispatch(IOs,message); fflush(stdout); } } ////////////////////////////// // // midi_in_dispatch -- Thread function which waits around until a MIDI // input byte arrives and then react correspondingly // void midi_in_dispatch(void *arg, unsigned char *m) { housicIO* IOs = (housicIO*)arg; int fader_value; if (m[0] == 0xb0) { // received a CC message on MIDIchannel 1 if (m[1] == 0x0f) { // received a zone select IOs->hui_in_zone = m[2]; } else if (m[1] == 0x2f) { // switches hui_in_button(IOs,m[2]); } else if ((m[1] & 0xf0) == 0x40) { // V-pots: hui_in_scroll(IOs,m[1] & 0xf, m[2]); } else if (m[1] == 0x0d) { // jog wheel: hui_in_scroll(IOs,0xd,m[2]); } else if (m[1] < 0x08) { // fader Hi IOs->hui_in_fader_hi[m[1]] = (int) m[2]; } else if ((m[1] & 0xf0) == 0x20) { // fader Lo fader_value = (IOs->hui_in_fader_hi[m[1] & 0x0f] << 2) | (m[2] >> 5); hui_in_fader(IOs,m[1] & 0x0f,fader_value); } } } ////////////////////////////// // // hui_in_fader -- fader positions // void hui_in_fader(void *arg, unsigned char port, int val) { housicIO* IOs = (housicIO*)arg; char address[32]; sprintf(address, "/fader/%d", port + 1); Item ee = {'f',address}; osc_send(IOs, ee, val); } ////////////////////////////// // // hui_in_scroll -- user interacted with a button on the HUI // void hui_in_scroll(void *arg, unsigned char port, unsigned char vv) { housicIO* IOs = (housicIO*)arg; char address[32]; sprintf(address, "/vpot/%d", port + 1); Item ee = {'s', address}; if (vv > 0x40) { osc_send(IOs, ee, vv - 0x40); } else if (vv < 0x40) { osc_send(IOs, ee, -vv); } } ////////////////////////////// // // hui_in_button -- user interacted with a button on the HUI // void hui_in_button(void *arg, unsigned char m) { housicIO* IOs = (housicIO*)arg; char address[32]; char zone = IOs->hui_in_zone; int dir = m & 0xf0; int port = m & 0x0f; sprintf(address, "/%s/%s",ZONES[zone],BUTTONS[zone][port]); Item ee = {'b',address}; if (dir == 0x40) { // down osc_send(IOs, ee, 1); } else if (dir == 0x00) { // up osc_send(IOs, ee, 0); } } ////////////////////////////// // // osc_send -- // void osc_send(void *arg, Item el, float value) { housicIO* IOs = (housicIO*)arg; lo_send(IOs->osc_out, el.address, "f", value ); } ////////////////////////////// // // osc_in_handler -- receive incoming OSC messages // int osc_in_handler (const char *path, const char *types, lo_arg ** argv, int argc, void *data, void *user_data) { int i; printf("path: <%s>\n", path); for (i = 0; i < argc; i++) { printf("arg %d '%c' ", i, types[i]); lo_arg_pp((lo_type)types[i], argv[i]); printf("\n"); } printf("\n"); fflush(stdout); return 1; } ////////////////////////////// // // error -- print error message // void alsa_error(const char *format, ...) { va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); putc('\n', stderr); } void lo_error(int num, const char *msg, const char *path) { printf("liblo server error %d in path %s: %s\n", num, path, msg); fflush(stdout); }