// 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);
}