/* Copyright (C) 2004 Ian Esten This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* TODO: * set input/output thread priorities */ #include #include #include #include #include #include #define MIDI_IN_BUFSIZE 256 #define OUTPUT_FIFO_PATH "/dev/shm/jack_midi_output_fifo" #define INPUT_FIFO_PATH "/dev/shm/jack_midi_input_fifo" typedef int bool; #define false 0 #define true 1 typedef struct { snd_rawmidi_t* raw; /* rawmidi handle */ jack_port_t* jack_port; /* jack port */ jack_midi_data_t read_buf[MIDI_IN_BUFSIZE]; /* buffer to read() into */ size_t bytes_left; /* # of bytes left to decode in read_buf */ size_t message_size; /* # of bytes in current message of port */ jack_midi_data_t last_status; /* last status byte seen in stream */ jack_midi_data_t last_msb; /* last msb seen (for pitch wheel) */ jack_midi_data_t* write_buffer; /* current event buffer being written to */ void* port_buf; /* buffer to store input events */ void* port_buf_rd; /* buffer pointer to buffer for process to copy from */ void* port_buf_wr; /* buffer pointer to store decoded input events */ } rawmidi_hw_inport_t; typedef struct { snd_rawmidi_t* raw; /* rawmidi handle */ jack_port_t* jack_port; /* jack port */ size_t bytes_left; /* # of bytes left to decode in read_buf */ jack_midi_data_t last_status; /* last status byte seen in stream */ void* port_buf; /* buffer to store events for output */ void* port_buf_rd; /* buffer to store events to be written to hw output */ void* port_buf_wr; /* buffer pointer to buffer for process to copy to */ int last_write_loc; } rawmidi_hw_outport_t; jack_client_t* jack_midi_client; /* the jack client */ rawmidi_hw_inport_t* in_ports; /* array of input port structs */ int num_inputs; /* number of input ports */ rawmidi_hw_outport_t* out_ports; /* array of output port structs */ int num_outputs; /* number of output ports */ volatile int running = 0; /* for stopping the threads */ jack_nframes_t bufsize = 0; /* aka nframes */ jack_nframes_t sample_rate = 0; struct pollfd* in_pfds; /* array of input pfds for poll() */ int nmsg = 0; /* number of messages received (for debugging) */ pthread_t input_pthread, output_pthread; /* the input and output pthreads */ volatile int output_event_count = 0; struct pollfd input_fifo_pfd; int input_fifo = -1; /* for syncing process and input thread */ int output_fifo = -1; /* for syncing process and output thread */ jack_midi_data_t decode_buf[MIDI_IN_BUFSIZE]; /* buffer to decode the bytes from read() into */ rawmidi_hw_inport_t* rawmidi_hw_inport_nmalloc(jack_nframes_t nframes, int nports) { int i; rawmidi_hw_inport_t* rm; rm = malloc(nports * sizeof(rawmidi_hw_inport_t)); /* has to be a double buffer here. if it isn't, there is no way to * prevent writing events that should be at the beginning of the * next buffer at the end of the current buffer. */ for(i=0; iraw, port->read_buf + port->bytes_left, MIDI_IN_BUFSIZE-port->bytes_left); printf("read %d bytes: ", bytes_read); for (i=0; iread_buf[i]); printf(" with %d bytes left in buffer\n", port->bytes_left); if(bytes_read > 0) { i = port->bytes_left; port->bytes_left += bytes_read; bytes_read = port->bytes_left; for(; iread_buf[i] >= 0x80) switch(port->read_buf[i]) /* status byte */ { /* write old event if there is a valid status byte at the start of the buffer * */ case 0xf6: /* tune request */ /* tune request resets running status */ port->last_status = 0; case 0xf8: /* timing clock */ case 0xfa: /* start */ case 0xfb: /* continue */ case 0xfc: /* stop */ case 0xfe: /* active sensing */ case 0xff: /* reset */ /* write 1 byte system real-time message to port buffer here */ nmsg++; printf("got a 1 byte system real-time msg. byte = 0x%x\n", port->read_buf[i]); /*jack_midi_data_t* event = time_and_write_event(j, 1, bufsize); *event = port->read_buf[i];*/ jack_midi_data_t *ev_buf_www = time_and_write_event(j, 1, bufsize); /* if(ev_buf_www == NULL) printf("got NULL buffer\n");*/ if(ev_buf_www) *ev_buf_www = port->read_buf[i]; port->bytes_left--; continue; case 0xf9: /* undefined */ case 0xfd: /* undefined */ printf("illegal byte: 0x%x\n", port->read_buf[i]); port->last_status = 0; port->bytes_left--; /* this should never ever happen. */ continue; case 0xe0 ... 0xef: /* pitch wheel change */ message_size = 3; port->last_status = port->read_buf[i]; /* get 3 byte msg area in port buffer */ nmsg++; printf("got a 3 byte msg (pitch wheel). byte 0 is 0x%x.\n", port->read_buf[i]); port->write_buffer = time_and_write_event(j, message_size, bufsize); if(port->write_buffer) { *(port->write_buffer) = port->read_buf[i]; /*write msb in case its not transmitted */ port->write_buffer[2] = port->last_msb; } port->bytes_left--; port->message_size = 2; continue; case 0x80 ... 0x8f: /* note off */ case 0x90 ... 0x9f: /* note on */ case 0xa0 ... 0xaf: /* polyphonic key pressure */ case 0xb0 ... 0xbf: /* controller change */ message_size = 3; port->last_status = port->read_buf[i]; /* get 3 byte msg area in port buffer */ nmsg++; printf("got a 3 byte msg. byte 0 is 0x%x.\n", port->read_buf[i]); port->write_buffer = time_and_write_event(j, message_size, bufsize); if(port->write_buffer) *(port->write_buffer) = port->read_buf[i]; port->bytes_left--; port->message_size = 2; continue; case 0xc0 ... 0xcf: /* program change */ case 0xd0 ... 0xdf: /* aftertouch */ message_size = 2; port->last_status = port->read_buf[i]; /* get 2 byte msg area in port buffer */ nmsg++; printf("got a 2 byte msg. byte 0 is 0x%x.\n", port->read_buf[i]); port->write_buffer = time_and_write_event(j, message_size, bufsize); /* if(port->write_buffer == NULL) printf("got NULL buffer\n");*/ if(port->write_buffer) *(port->write_buffer) = port->read_buf[i]; port->bytes_left--; port->message_size = 1; continue; case 0xf7: /* sysex end */ nmsg++; port->message_size++; printf("got a sysex end. sysex message length is %d.\n", port->message_size); /* sysex resets running status */ port->last_status = port->read_buf[i]; /* write the sysex message if it is short enough */ /* need to think carefully about how to handle sysex events * that are longer than MIDI_IN_BUFSIZE */ if(port->message_size > MIDI_IN_BUFSIZE) port->bytes_left -= port->message_size % MIDI_IN_BUFSIZE; else port->bytes_left -= port->message_size; continue; /*sysex id or last_status is sysex */ case 0xf0: /* sysex start */ /* sysex resets running status */ printf("got a sysex start message. byte = 0x%x, i = %d\n", port->read_buf[i], i); /* start counting the size of the message */ port->last_status = port->read_buf[i]; port->message_size = 1; continue; default: printf("byte 0x%x not handled.\n", port->read_buf[i]); port->bytes_left--; continue; } else switch(port->last_status) { case 0xe0 ... 0xef: /* pitch wheel change */ port->bytes_left--; if(port->message_size == 1) port->last_msb = port->read_buf[i]; if(port->message_size) { if(port->write_buffer) port->write_buffer[3-port->message_size] = port->read_buf[i]; port->message_size--; printf("byte: 0x%x\n", port->read_buf[i]); } else { /* new 2 byte running status. add in last status byte received */ nmsg++; port->write_buffer = time_and_write_event(j, 3, bufsize); if(port->write_buffer) { *(port->write_buffer) = port->last_status; *(port->write_buffer + 1) = port->read_buf[i]; port->write_buffer[2] = port->last_msb; port->message_size = 1; } } continue; case 0x80 ... 0x8f: /* note off */ case 0x90 ... 0x9f: /* note on */ case 0xa0 ... 0xaf: /* polyphonic key pressure */ case 0xb0 ... 0xbf: /* controller change */ port->bytes_left--; if(port->message_size) { if(port->write_buffer) port->write_buffer[3-port->message_size] = port->read_buf[i]; port->message_size--; printf("byte: 0x%x\n", port->read_buf[i]); } else { /* new 2 byte running status. add in last status byte received */ nmsg++; port->write_buffer = time_and_write_event(j, 3, bufsize); if(port->write_buffer) { *(port->write_buffer) = port->last_status; *(port->write_buffer + 1) = port->read_buf[i]; port->message_size = 1; } } continue; case 0xc0 ... 0xcf: /* program change */ case 0xd0 ... 0xdf: /* aftertouch */ port->bytes_left--; if(port->message_size) { if(port->write_buffer) port->write_buffer[2-port->message_size] = port->read_buf[i]; port->message_size--; printf("byte: 0x%x\n", port->read_buf[i]); continue; } else { /* new 1 byte running status. add in last status byte received */ nmsg++; printf("got a 1 byte running status msg.\n"); port->write_buffer = time_and_write_event(j, 2, bufsize); /* if(port->write_buffer == NULL) printf("got NULL buffer\n");*/ if(port->write_buffer) { *(port->write_buffer) = port->last_status; *(port->write_buffer + 1) = port->read_buf[i]; port->message_size = 0; } } case 0xf0: /* sysex */ printf("got sysex data byte 0x%x\n", port->read_buf[i]); port->message_size++; break; default: if( (port->last_status == 0xf9) || (port->last_status == 0xfd) || (port->last_status == 0xf7) ) { /* discard bytes without bit 15 set that come after sysex * end or after undefined status bytes */ printf("discarding byte: 0x%x, because last_status is 0x%x\n", port->read_buf[i], port->last_status); port->bytes_left--; continue; } } } } /* move incomplete message to beginning of buffer */ if(port->bytes_left) { printf("moving %d bytes of incomplete message to beginning of buffer.\n\n", port->bytes_left); memmove(port->read_buf, port->read_buf + port->bytes_left, port->bytes_left); } } } printf("jack midi input thread exiting\n"); } /* problems still to be solved in output_thread: * when buffers are swapped, last_status needs to be copied to the new buffer */ void* output_thread(void* arg) { jack_midi_event_t write_event, next_event; jack_midi_port_info_t *write_port_info, *next_port_info; jack_nframes_t out_old_time = 0, out_new_time = 0, /*out_old_running_time = 0, out_new_running_time = 0,*/ timer_time; int optimise = 0, i, retval, print_index; int write_index = 0, err = 0; void* temp; struct timespec output_timer; const jack_nframes_t sample_period_nsec = 1000000000 / jack_get_sample_rate(jack_midi_client); struct pollfd output_fifo_pfd; output_fifo_pfd.fd = output_fifo; output_fifo_pfd.events = POLLERR | POLLIN | POLLPRI | POLLHUP | POLLNVAL; char fifo_read_c; printf("jack midi output thread started\n"); while(running) { /* Recipe: * wait on fifo until there are >0 events to output * */ poll(&output_fifo_pfd, 1, -1); printf("poll returned, %d events to process\n", output_event_count); /* snd_rawmidi_drain(out_ports[write_index].raw);*/ if((output_event_count > 0) /*&& !output_locked*/) { printf("looking for event to write\n"); /* find new event here */ /* also find how long we should sleep for until it is time to write it * and pass that to nanosleep instead of using nanosleep(sleep_time, NULL) */ write_event.time = bufsize+1; for(i=0; i= next_port_info->event_count) continue; else if( (err = jack_midi_event_get(&next_event, out_ports[i].port_buf_rd, out_ports[i].last_write_loc, bufsize)) < 0 ) continue; /* if next event on buffer i timestamp is smaller */ if(write_event.time >= next_event.time) { write_index = i; write_event = next_event; } } /* write the next event into the port buffer so that it is ready to be * drained at the correct time */ /* the status byte stuff is wrong... */ /* optimise = ( (out_ports[write_index].last_status < 0xf0) && (write_event.buffer[0] == out_ports[write_index].last_status) ) ? 1 : 0;*/ printf("about to write an event: t = %d; s = %d, d = ", write_event.time, write_event.size); for(print_index=0; print_index= 0x80)) ? *(write_event.buffer) : out_ports[write_index].last_status; output_event_count--; timer_time = jack_frames_since_cycle_start(jack_midi_client); if(write_event.time - timer_time >= sample_rate) { /* implemented just to make sure! (where we need to wait for >1sec) */ output_timer.tv_sec = (write_event.time - timer_time)/sample_rate; output_timer.tv_nsec = sample_period_nsec*(write_event.time - sample_rate*output_timer.tv_sec); } else { output_timer.tv_sec = 0; output_timer.tv_nsec = sample_period_nsec * (write_event.time - timer_time); } nanosleep(&output_timer, NULL); } else { /* this segment of code is reached when there are no events to output, * we must empty the fifo and poll it until there is something to do. */ retval = read(output_fifo, &fifo_read_c, sizeof(fifo_read_c)); /* if(retval < 0) printf("error reading output_fifo (%d). output_event_count = %d\n", retval, output_event_count); else printf("read %d bytes from output_fifo\n", retval);*/ } out_old_time = out_new_time; } /* Cleanup things to do: * flush the output buffer to get rid of pending events */ printf("jack midi output thread exiting\n"); } void input_cleanup() { int i; for(i=0; i 0) pthread_join(input_pthread, NULL); printf("input cleanup done\n"); } void input_setup() { int err = 0, i = 0, j = 0; snd_rawmidi_info_t* rmi; const char* alsa_name; char jack_name[256]; char *envvar; char portname[256]; int portnamelen = 0; rawmidi_hw_inport_t *port_temp = NULL, *swap_port_temp = NULL; char last_envvar_char = '0'; snd_rawmidi_info_t* input_rm_info; snd_rawmidi_info_malloc(&input_rm_info); num_inputs = 0; port_temp = rawmidi_hw_inport_nmalloc(bufsize, 1); printf("opening input ports\n"); if(1) { /* look in .jackrc/environment for: * midi devices to open * input pool size */ envvar = getenv("JACK_MIDI_IN_DEVICES"); if(envvar == NULL) envvar = "hw_0,0,0"; else if(strcmp(envvar, "") == 0) return; printf("envvar string is ""%s""\n", envvar); strcpy(jack_name, "capture_"); while(last_envvar_char != '\0') { last_envvar_char = envvar[i]; if((envvar[i] == ' ') || (envvar[i] == ':') || (envvar[i] == '\0')) { portname[portnamelen] = '\0'; err = snd_rawmidi_open(&(port_temp[num_inputs].raw), NULL, portname, 0 /*O_RDONLY*/); if(err < 0) { fprintf(stderr, "Error opening midi input device %s: %s\n", portname, snd_strerror(err)); } else { if(snd_rawmidi_info(port_temp[num_inputs].raw, input_rm_info) < 0) printf("opened alsa rawmidi port \"%s\", but could not get snd_rawmidi_info!\n", portname); else printf("opened alsa rawmidi port \"%s\" (alsa name is \"%s\")\n", portname, snd_rawmidi_info_get_subdevice_name(input_rm_info)); j = 0; while( (portname[j] < '0') || (portname[j] > '9') ) j++; strcpy(&jack_name[strlen("capture_")], &portname[j]); port_temp[num_inputs].jack_port = jack_port_register(jack_midi_client, jack_name, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); if(port_temp[num_inputs].jack_port) { printf("opened jack input port \"%s\"\n", jack_name); /* copy existing ports into temp buffer, leaving newest port intact in temp */ /*memcpy(port_temp, in_ports, num_inputs*sizeof(rawmidi_hw_inport_t));*/ for(j=0; j 0) { if(mkfifo(INPUT_FIFO_PATH, 0666) < 0) { jack_error ("cannot create midi input FIFO" " [%s] (%s)\n", INPUT_FIFO_PATH, strerror (errno)); input_cleanup(); num_inputs = 0; input_fifo = -1; return; } if ((input_fifo = open (INPUT_FIFO_PATH, O_RDWR|O_CREAT|O_NONBLOCK, 0666)) < 0) { jack_error ("cannot open fifo [%s] (%s)", INPUT_FIFO_PATH, strerror (errno)); input_cleanup(); num_inputs = 0; input_fifo = -1; return; } input_fifo_pfd.fd = input_fifo; input_fifo_pfd.events = POLLIN | POLLPRI; } else input_fifo = -1; } void output_cleanup() { int i; for(i=0; i 0) pthread_join(output_pthread, NULL); close(output_fifo); unlink(OUTPUT_FIFO_PATH); printf("output cleanup done\n"); } void output_setup() { int err = 0, i = 0, j = 0; snd_rawmidi_info_t* rmi; const char* alsa_name; char jack_name[256]; char *envvar; char portname[256]; int portnamelen; rawmidi_hw_outport_t *port_temp = NULL, *swap_port_temp = NULL; char last_envvar_char = '0'; snd_rawmidi_info_t* output_rm_info; snd_rawmidi_info_malloc(&output_rm_info); num_outputs = 0; port_temp = rawmidi_hw_outport_nmalloc(bufsize, 1); printf("opening output ports\n"); envvar = getenv("JACK_MIDI_OUT_DEVICES"); if(envvar == NULL) envvar = "hw_0,0,0"; else if(strcmp(envvar, "") == 0) return; printf("envvar string is ""%s""\n", envvar); strcpy(jack_name, "playback_"); while(last_envvar_char != '\0') { last_envvar_char = envvar[i]; if((envvar[i] == ' ') || (envvar[i] == ':') || (envvar[i] == '\0')) { portname[portnamelen] = '\0'; err = snd_rawmidi_open(NULL, &(port_temp[num_outputs].raw), portname, 0 /*O_WRONLY*/); if(err < 0) { fprintf(stderr, "Error opening midi output device %s: %s\n", portname, snd_strerror(err)); } else { if(snd_rawmidi_info(port_temp[num_outputs].raw, output_rm_info) < 0) printf("opened alsa rawmidi port \"%s\", but could not get snd_rawmidi_info!\n", portname); else printf("opened alsa rawmidi port \"%s\" (alsa name is \"%s\")\n", portname, snd_rawmidi_info_get_subdevice_name(output_rm_info)); j = 0; while( (portname[j] < '0') || (portname[j] > '9') ) j++; strcpy(&jack_name[strlen("playback_")], &portname[j]); port_temp[num_outputs].jack_port = jack_port_register(jack_midi_client, jack_name, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); if(port_temp[num_outputs].jack_port) { printf("opened jack output port \"%s\"\n", jack_name); /* copy existing ports into temp buffer, leaving newest port intact in temp */ for(j=0; j 0) { if(mkfifo(OUTPUT_FIFO_PATH, 0666) < 0) { printf("Error: cannot create midi output FIFO" " [%s] (%s)\n", OUTPUT_FIFO_PATH, strerror (errno)); output_cleanup(); num_outputs = 0; output_fifo = -1; return; } if ((output_fifo = open (OUTPUT_FIFO_PATH, O_RDWR | O_NONBLOCK, 0666)) < 0) { printf("Error: cannot open fifo [%s] (%s)", OUTPUT_FIFO_PATH, strerror (errno)); output_cleanup(); num_outputs = 0; output_fifo = -1; return; } } else output_fifo = -1; if(port_temp) rawmidi_hw_outport_nfree(port_temp, num_outputs+1); printf("opened %d output ports. output_fifo is %d\n", num_outputs, output_fifo); } int process(jack_nframes_t nframes, void *arg) { int i, j, num_events; void* buf; void* temp; jack_midi_event_t *src_ev, *dest_ev; int retval; char fifo_data; jack_nframes_t* port_event_count; /* Things to do here: * optimize output buffer(s). might want to do this in the output thread * signal output thread * copy input data to ports */ if(num_inputs) read(input_fifo, &fifo_data, sizeof(fifo_data)); if( (output_event_count > 0) && (num_outputs > 0) ) { /* there are still events to write, but it is too late... empty fifo */ /* in here, we could also handle the unwritten events, or at least notify */ retval = read(output_fifo, &fifo_data, sizeof(fifo_data)); /* printf("read returned %d, output_fifo is: %d\n", retval, output_fifo);*/ } for(i=0; ievent_count, i);*/ temp = out_ports[i].port_buf_wr; out_ports[i].port_buf_wr = out_ports[i].port_buf_rd; out_ports[i].port_buf_rd = temp; out_ports[i].last_write_loc = 0; output_event_count += (jack_midi_port_get_info(temp, bufsize))->event_count; } /* wake input and output threads */ if( (output_event_count > 0) && (num_outputs > 0) ) { write(output_fifo, &fifo_data, sizeof(fifo_data)); } if(num_inputs > 0) write(input_fifo, &fifo_data, sizeof(fifo_data)); } int buffer_size(jack_nframes_t nframes, void *arg) { /* Things to do here: * suspend i/o threads, then reallocate midi input and output buffers */ } void start_freewheel(int starting, void *arg) { char fifo_data; if(starting) { if(num_inputs > 0) pthread_cancel(input_pthread); if(num_outputs > 0) { write(output_fifo, &fifo_data, sizeof(fifo_data)); pthread_cancel(output_pthread); } } else { if(num_inputs > 0) pthread_create(&input_pthread, NULL, input_thread, NULL); if(num_outputs > 0) pthread_create(&output_pthread, NULL, output_thread, NULL); } } void jack_shutdown(void *arg) { char fifo_data; running = 0; printf("jack_shutdown called, goodbye!\n"); write(output_fifo, &fifo_data, sizeof(fifo_data)); input_cleanup(); output_cleanup(); } #ifdef USE_INTERNAL_CLIENT void jack_finish(void *arg) { jack_shutdown(arg); } int jack_initialize (jack_client_t *client, const char *load_init) { char fifo_data; jack_midi_client = client; #else int main() { char fifo_data; jack_midi_client = jack_client_new("alsa_rawmidi"); if(jack_midi_client == NULL) { printf("jackmidiio could not create client. is jackd running?\n"); return 1; } #endif /* USE_INTERNAL_CLIENT */ bufsize = jack_get_buffer_size(jack_midi_client); sample_rate = jack_get_sample_rate(jack_midi_client); /* find rawmidi ports and open them */ /* also should allocate memory in these */ input_setup(); output_setup(); /* create output thread */ running = 1; if(num_outputs > 0) pthread_create(&output_pthread, NULL, output_thread, NULL); /* register callbacks and start */ jack_on_shutdown (jack_midi_client, jack_shutdown, NULL); jack_set_freewheel_callback(jack_midi_client, start_freewheel, NULL); jack_set_process_callback(jack_midi_client, process, NULL); jack_activate(jack_midi_client); write(input_fifo, &fifo_data, sizeof(fifo_data)); /* create input thread */ if(num_inputs > 0) pthread_create(&input_pthread, NULL, input_thread, NULL); while(running) sleep(1); printf("jackmidiio exiting\n"); /* jack_deactivate(jack_midi_client);*/ return 0; }