// alsabridge.c (C) 2010 Gordon JC Pearce
// GPL version 3 applies, can't be bothered including it verbatim
//
// based on seqdemo.c by Matthias Nagorni
//
// Compile with:
// gcc alsabridge.c -o alsabridge -lasound
// Expects a serial device on /dev/ttyUSB0 (typically an Arduino)
// Receives MIDI messages and sends them over serial


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <alsa/asoundlib.h>
#include <termios.h>
#include <getopt.h>

int debug=0;
char *port;

snd_seq_t *open_seq();
void midi_action(snd_seq_t *seq_handle);

int serial;
char buf[32];

snd_seq_t *open_seq() {

  snd_seq_t *seq_handle;
  int portid;

  if (snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_INPUT, 0) < 0) {
    fprintf(stderr, "Error opening ALSA sequencer.\n");
    exit(1);
  }
  snd_seq_set_client_name(seq_handle, "ALSABridge");
  if ((portid = snd_seq_create_simple_port(seq_handle, "ALSA to Serial Bridge",
            SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
            SND_SEQ_PORT_TYPE_APPLICATION)) < 0) {
    fprintf(stderr, "Error creating sequencer port.\n");
    exit(1);
  }
  return(seq_handle);
}

char note;
void midi_action(snd_seq_t *seq_handle) {

  snd_seq_event_t *ev;

  do {
    snd_seq_event_input(seq_handle, &ev);
    switch (ev->type) {
      case SND_SEQ_EVENT_CONTROLLER: 
        if (debug) printf("Control event on Channel %2d: %d%5d\n",
                ev->data.control.channel, ev->data.control.param, ev->data.control.value);
                
        sprintf(buf, "%c", 0xB0);
        write(serial, buf, 1);
        sprintf(buf, "%c", (char)ev->data.control.param);
        write(serial, buf, 1);
        sprintf(buf, "%c", (char)ev->data.control.value);
        write(serial, buf, 1);
        break;
      case SND_SEQ_EVENT_PITCHBEND:
        if (debug) printf("Pitchbender event on Channel %2d: %d %5d\n", 
                ev->data.control.channel, ev->data.control.param, ev->data.control.value);
        break;
      case SND_SEQ_EVENT_NOTEON:
        if (debug) printf("Note On event on Channel %2d: %5d %d\n",
                ev->data.control.channel, ev->data.note.note, ev->data.note.velocity);
        sprintf(buf, "%c", 0x90);
        write(serial, buf, 1);
        sprintf(buf, "%c", (char)ev->data.note.note);
        write(serial, buf, 1);
        sprintf(buf, "%c", (char)ev->data.note.velocity);
        write(serial, buf, 1);
        break;   
      case SND_SEQ_EVENT_NOTEOFF: 
        if (debug) printf("Note Off event on Channel %2d: %5d\n",         
                ev->data.control.channel, ev->data.note.note); 
        sprintf(buf, "%c", 0x80);
        write(serial, buf, 1);
        sprintf(buf, "%c", (char)ev->data.note.note);
        write(serial, buf, 1);
        sprintf(buf, "%c", (char)ev->data.note.velocity);
        write(serial, buf, 1);   
        break;
      case SND_SEQ_EVENT_PGMCHANGE:
        if (debug) printf("Program change event on Channel %2d: %5d\n",
                ev->data.control.channel, ev->data.control.value);
        sprintf(buf, "%c", 0xC0);
        write(serial, buf, 1);
        sprintf(buf, "%c", (char)ev->data.control.value);
        write(serial, buf, 1);
        break;     
    }
    snd_seq_free_event(ev);
  } while (snd_seq_event_input_pending(seq_handle, 0) > 0);
}

int main(int argc, char *argv[]) {

  static const char options[] = "hdp:";
  
  snd_seq_t *seq_handle;
  snd_seq_addr_t source, dest;
  snd_seq_port_subscribe_t *subs;
  int npfd, c;
  struct pollfd *pfd;
  

  struct termios oldtio, newtio;
    
  while ((c = getopt_long(argc, argv, options,
		     		NULL, NULL)) != -1) {
		switch (c) {
		case 'h':
            printf("alsabridge\n\t-h\t this message\n\t-v\tprint MIDI messages\n\t-p <port>\t autoconnect MIDI port\n");
			return 0;
		case 'd':
            debug = 1;
            break;
	    case 'p':
	        port = optarg;
	        printf("autoconnect %s\n", port);
	        break;
	    default:
	        printf("'amidi -h' for options\n");
	        return 1;
        }
    }
  
  seq_handle = open_seq();
  // FIXME - handle errors more gracefully, or even at all
  if (port) {
  printf("%x\n", snd_seq_parse_address(seq_handle, &source, port));
  printf("%x\n", snd_seq_parse_address(seq_handle, &dest, "ALSABridge"));
  
  // taken from aconnect
  	int queue = 0, convert_time = 0, convert_real = 0, exclusive = 0;
  snd_seq_port_subscribe_alloca(&subs);
  snd_seq_port_subscribe_set_sender(subs, &source);
  snd_seq_port_subscribe_set_dest(subs, &dest);
  
  snd_seq_port_subscribe_set_queue(subs, queue);
  snd_seq_port_subscribe_set_exclusive(subs, exclusive);
  snd_seq_port_subscribe_set_time_update(subs, convert_time);
  snd_seq_port_subscribe_set_time_real(subs, convert_real);
  
  if (snd_seq_get_port_subscription(seq_handle, subs) == 0) {
			snd_seq_close(seq_handle);
			printf("Connection is already subscribed\n");
			return 1;
		}
		if (snd_seq_subscribe_port(seq_handle, subs) < 0) {
			snd_seq_close(seq_handle);
			printf("Connection failed (%s)\n", snd_strerror(errno));
			return 1;
		}
  } else {
    printf("no port\n");
  }
  serial = open("/dev/ttyUSB1", O_RDWR | O_NOCTTY );
  printf("Serial port handle is %x\n",serial);
  	bzero(&newtio, sizeof(newtio));
    newtio.c_cflag = B57600 | CRTSCTS | CS8 | CLOCAL | CREAD;
      //newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
      newtio.c_iflag = IGNPAR;
      newtio.c_oflag = 0;
      newtio.c_lflag = 0;       //ICANON;
      newtio.c_cc[VMIN]=1;
      newtio.c_cc[VTIME]=0;

  npfd = snd_seq_poll_descriptors_count(seq_handle, POLLIN);
  pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd));
  snd_seq_poll_descriptors(seq_handle, pfd, npfd, POLLIN);
  while (1) {
    if (poll(pfd, npfd, 100000) > 0) {
      midi_action(seq_handle);

    }  
  }
}


