#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<assert.h>
#include<stdbool.h>

#define DEVICE "/dev/midi00"

static int fd;

static int pitch_adjust = 0;

static void print_note(FILE *stream, unsigned char note) {
	const char *notes[] = 
	{"C ","C#","D ","D#","E ","F ","F#","G ","G#","A ","A#","B "};
	fprintf(stream,"%s%i",notes[note%12],note/12);
}




static void handle_realtime(unsigned char message) {
	switch(message) {
	case 0xf8:
		printf("."); fflush(stdout); break;
		break;
	case 0xFA:
		printf("Realtime Start\n");break;
		break;
	case 0xFB:
		printf("Realtime Continue\n");break;
		break;
	case 0xFC:
		printf("Realtime Stop\n");break;
		break;
	case 0xFF:
		printf("Realtime Reset\n");break;
		break;
	case 0xFE:
		printf("@");fflush(stdout);break;
		break;
	default:
		printf("Unknown Realtime: 0x%X\n",message);
	}
}


static void handle_random(unsigned char *buf) {
	switch(buf[0]) {
	case 0xf1:
		printf("Midi Time Code: 0x%X 0x%X\n",buf[1],buf[2]);
		break;
	case 0xf2:
		printf("Song Position Pointer: 0x%X 0x%X\n",buf[1],buf[2]);
		break;
	case 0xf3:
		printf("Song Select: 0x%X\n",buf[1]);
		break;
	case 0xf6:
		printf("Tune Request\n");
		break;
	}
}

static void handle_channelop(unsigned char *buf) {
	switch(buf[0]&0xf0) {
	case 0x80:
		if(buf[1]>=0x3c)
			buf[0] = buf[0]+1;
		buf[1]+=pitch_adjust;
		buf[2] = (unsigned char)(((int)buf[2]*127)/100);
		printf("Channel %i: ",(buf[0]&0x0f)+1);
		printf("Note Off: ");
		print_note(stdout,buf[1]);
		printf(" 0x%X\n",buf[2]);
		break;
	case 0x90:
		if(buf[1]>=0x3c)
			buf[0] = buf[0]+1;
		buf[1]+=pitch_adjust;
		buf[2] = (unsigned char)(((int)buf[2]*127)/100);
		printf("Channel %i: ",(buf[0]&0x0f)+1);
		printf("Note On: ");
		print_note(stdout,buf[1]);
		printf(" 0x%X\n",buf[2]);
		break;
	case 0xA0:
		buf[1]+=pitch_adjust;
		printf("Channel %i: ",(buf[0]&0x0f)+1);
		printf("Polymorphic Aftertouch: ");
		print_note(stdout,buf[1]);
		printf(" 0x%X\n",buf[2]);
		break;
	case 0xB0:
		printf("Channel %i: ",(buf[0]&0x0f)+1);
		printf("Control Change: 0x%X 0x%X\n",buf[1],buf[2]);
		break;
	case 0xC0:
		printf("Channel %i: ",(buf[0]&0x0f)+1);
		printf("Program Change: 0x%X\n",buf[1]);
		break;
	case 0xD0:
		printf("Channel %i: ",(buf[0]&0x0f)+1);
		printf("Channel Aftertouch: 0x%X\n",buf[1]);
		break;
	case 0xE0:
		printf("Channel %i: ",(buf[0]&0x0f)+1);
		printf("Pitch Wheel: 0x%X 0x%X\n",buf[1],buf[2]);
		break;
	default:
		assert(!"Internal Error, unknown channelcommand.");
	}
}


static void midi_put(unsigned char c) {
	static unsigned char buf[3];
	static int next = 0;
	static int needed = 0;
	//fprintf(stderr,"got 0x%X\n",c);
	if(needed) {
		buf[next++] = c;
		if(!(--needed)) {
			if(buf[0]<0xf0)
				handle_channelop(buf);
			else
				handle_random(buf);
			next = 0;
		}
		return;
	}
	if(c<0x80) {
		printf("Unexpected data: 0x%X\n",c);
		return;
	}
	buf[next++] = c;
	if(c < 0xf0) {
		if(c< 0xC0 || c >= 0xe0) 
			needed = 2;
		else needed = 1;
		return;
	}
	switch (c) {
		case 0xf0: 
			assert(!"Internal error, sysex got through");return;
		case 0xf1:
		case 0xf2:
			needed = 2; return;
		case 0xf3:
			needed = 1;return;
		case 0xf6:
			handle_random(buf); return;
		case 0xf7:
			return;
		default:
			printf("Unknown Status: 0x%X\n",c);
	}
}


int main (int argc,char *argv[]) {
	int i;
	bool sysex = false;
	char *fname;
	unsigned char buf[256];
	if (argc > 1)
		fname = argv[1];
	else
		fname = DEVICE;
	fd = open(fname, O_RDWR);
	if(fd < 0) {
		perror(fname);
		exit(1);	
	}
	for(;;) {
		int r = read(fd,buf,256);
		if(!r) break;
		for(i=0;i<r;i++) {
			if(buf[i]>=0xF8) {
				handle_realtime(buf[0]);
				continue;
			}
			if(buf[i]==0xF0) {
				sysex=true;
				continue;
			}
			if(buf[i]<0x80) {
				if(!sysex)
					midi_put(buf[i]);
				else {
//	fprintf(stderr,"sysex pruning 0x%X\n",buf[i]);
				
				}
				continue;
			}
			if(buf[i]>=0x80) {
				sysex = false;
				if(buf[i]!=0xf7)
					midi_put(buf[i]);
			}
		}

	}
	return 0;
}
