/* midi code is partly taken from robert ham's jack rack */

#ifdef HAVE_ALSA

#include <unistd.h>
#include <signal.h>
#include <poll.h>
#include <pthread.h>
#include <string.h>
#include <math.h>

#include "midi.h"
#include "looperdata.h"

static void * midi_run (void * data);


midi_info_t* midi_info_new (speciallist_t* looperdatalist) {
  	midi_info_t* minfo;
  	
  	minfo 			= malloc (sizeof (midi_info_t));
  	minfo->seq      	= NULL;
  	minfo->quit    		= 0;
	minfo->looperdatalist	= looperdatalist;
	printf ("creating midi thread	\n");

  	pthread_create (&minfo->midithread, NULL, midi_run, minfo);
  	
  	return minfo;  
}

void midi_info_destroy (midi_info_t* minfo){
  	minfo->quit = 1;
  	pthread_join (minfo->midithread, NULL);
	free(minfo);
}

snd_seq_t *open_seq(const char* name) {
  	snd_seq_t *seq_handle;
  	int portid;

  	if (snd_seq_open(&seq_handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
    		fprintf(stderr, "Error opening ALSA sequencer.\n");
    		return NULL; // exit(1);
  	}

  	snd_seq_set_client_name(seq_handle, name);
  	if ((portid = snd_seq_create_simple_port(seq_handle, "midi_input",
            	SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
            	SND_SEQ_PORT_TYPE_APPLICATION)) < 0) {
    		fprintf(stderr, "Error creating sequencer port.\n");
    		return NULL; // exit(1);
  	}
  	return(seq_handle);
}

static double midi2pitch (int note){
/*
	double pitch = pow(2.,(double)(note - 57)/12.);
*/
	double pitch = pow(2.,(double)(note - 48.)/12.);

	return pitch;
}

void midi_action(midi_info_t *minfo) {
  	snd_seq_event_t *ev;
	looper_data_t *ld;
	double note = 0.0;
	double vol = 0.0;

  	do {
    		snd_seq_event_input(minfo->seq, &ev);
/*		printf ("event: type %d\n",ev->type);*/
    		switch (ev->type) {
      			case SND_SEQ_EVENT_CONTROLLER: 
/*
        			fprintf(stderr, "Control event on Channel %2d: %d, %5d       \n",
                			ev->data.control.channel, ev->data.control.param, ev->data.control.value);
*/
        		break;

	/* PITCHBEND */
      			case SND_SEQ_EVENT_PITCHBEND:
/*
        			fprintf(stderr, "Pitchbender event on Channel %2d: %5d   \n", 
                			ev->data.control.channel, ev->data.control.value);
*/
				vol = 1. + (double)ev->data.control.value / 16384.; /* bendrange = -8192 - 8192 */
				ld = speciallist_get_first_nolock (minfo->looperdatalist);
                                while(ld){
					int mc = looperdata_get_midichannel (ld);
                                        if ((mc < 0) || (mc == ev->data.control.channel)){
                                        	looperdata_set_grainpitchbend (ld, vol);
					}
                                        ld = speciallist_get_next_nolock (minfo->looperdatalist, ld);
                                }
        			break;
      			case SND_SEQ_EVENT_NOTEON:
				note = midi2pitch(ev->data.note.note);
/*				printf ("note %d becomes pitch %f\n",ev->data.note.note, note);*/
                                vol = (double)ev->data.note.velocity / 127.;

/*
        			fprintf(stderr, "Note On event on Channel %2d: %d (%lf) (v:%d/%lf) \n",
					ev->data.control.channel, ev->data.note.note,note, ev->data.note.velocity,vol);
*/
					
				ld = speciallist_get_first_nolock (minfo->looperdatalist);
       				while(ld){
					int mc = looperdata_get_midichannel (ld);
					if ((mc < 0) || (mc == ev->data.control.channel)){
						looperdata_set_grainpitchtablevalue (ld, note, vol);
					}
               				ld = speciallist_get_next_nolock (minfo->looperdatalist, ld);
       				}
       				break;        
			case SND_SEQ_EVENT_NOTEOFF:
				note = midi2pitch(ev->data.note.note);
/*
				fprintf(stderr, "Note Off event on Channel %2d: %d (%lf) (0) \n",
                                        ev->data.control.channel, ev->data.note.note,note);
*/

				ld = speciallist_get_first_nolock (minfo->looperdatalist);
				while(ld){
					int mc = looperdata_get_midichannel (ld);
					if ((mc < 0) || (mc == ev->data.control.channel)){
						looperdata_set_grainpitchtablevalue (ld, note, 0.);
					}
					ld = speciallist_get_next_nolock (minfo->looperdatalist, ld);
				}
				break;
    		}
    		snd_seq_free_event(ev);
  	} while (snd_seq_event_input_pending(minfo->seq, 0) > 0);
}

static void midi_process (midi_info_t *minfo){
/*	printf ("poll\n");*/
	if (poll(minfo->pfds, minfo->seq_nfds, 40) > 0) {
/*		printf ("action\n");*/
      		midi_action(minfo);
    	}    
}


static void* midi_run (void * data) {
  	midi_info_t* minfo = (midi_info_t*) data;
  
  	/* this is dealt with by the jack thread */
  	signal (SIGHUP, SIG_IGN);

  	minfo->seq = open_seq("kluppe");
	if (!minfo->seq) return NULL;
	minfo->seq_nfds = snd_seq_poll_descriptors_count(minfo->seq, POLLIN);
	minfo->pfds = (struct pollfd*)alloca(minfo->seq_nfds * sizeof(struct pollfd));
	snd_seq_poll_descriptors(minfo->seq, minfo->pfds, minfo->seq_nfds, POLLIN);
	/* http://howtos.linux.com/howtos/MIDI-HOWTO-9.shtml */
  
  	while (!minfo->quit){
      		midi_process (minfo);
  	}
  
  	free (minfo->pfds);
  	return NULL;
}

#endif /* HAVE_ALSA */
