// Guitar Virtuoso: generate MIDI from a Guitar Hero controller
// Adam Sampson <ats@offog.org>

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>

#include <linux/input.h>

#include <alsa/asoundlib.h>

/*

Guitar Hero controller on Allan's adaptor:

Keys: (0 = off, 1 = on)
293 = green (left fret)
289 = red
288 = yellow
290 = blue
291 = orange (right fret)
297 = select (left)
296 = start (right)
292 = tilt

Absolute:
17 = strum (1 = down, 0 = centre, -1 = up)
1, 6 = strum also! (255 = down, 127 = centre, 0 = up)

Apparently no mapping for the tremelo arm...

 */

void die(const char *s) {
	fprintf(stderr, "%s\n", s);
	exit(1);
}

double daytime(void) {
	struct timeval tv;

	gettimeofday(&tv, NULL);
	return tv.tv_sec + (tv.tv_usec / 1000000.0L);
}

typedef enum {
	NONE = 0,
	FRET0,
	FRET1,
	FRET2,
	FRET3,
	FRET4,
	SELECT,
	START,
	TILT,
	STRUM,
	TREMELO
} guitar_key;

int fd;

void open_input(void) {
	fd = open("/dev/input/event2", O_RDONLY);
	if (fd < 0)
		die("can't open device");
}

void get_key(guitar_key *key, int *value) {
	*key = NONE;

	do {
		struct input_event ev;
		if (read(fd, &ev, sizeof ev) != sizeof ev)
			die("read from device failed");

		switch (ev.type) {
		case EV_KEY:
			switch (ev.code) {
			case 293: *key = FRET0; break;
			case 289: *key = FRET1; break;
			case 288: *key = FRET2; break;
			case 290: *key = FRET3; break;
			case 291: *key = FRET4; break;
			case 297: *key = SELECT; break;
			case 296: *key = START; break;
			case 292: *key = TILT; break;
			}
			*value = ev.value;
			break;
		case EV_ABS:
			switch (ev.code) {
			case 17: *key = STRUM; break;
			}
			*value = ev.value;
			break;
		}
	} while (*key == NONE);

	printf("key %d %d\n", *key, *value);
}

/* scale, for example:

   -2 -1 0  1  2  3  4  5  6  7  8  9  10 11 12
   Bb B  C  C# D  Eb E  F  F# G  Ab A  Bb B  C  ...

   99 = no note
 */

#define MAXNOTES 2
const int notes[32][MAXNOTES] = {
	{ 99, 99 },	// -----
	{ -2, 99 },	// #----
	{ 0, 99 },	// -#---
	{ -1, 99 },	// ##---
	{ 5, 99 },	// --#--
	{ 0, 7 },	// #-#--
	{ 3, 99 },	// -##--
	{ 2, 99 },	// ###--
	{ 7, 99 },	// ---#-
	{ -2, 5 },	// #--#-
	{ 3, 10 },	// -#-#-
	{ -1, 6 },	// ##-#-
	{ 6, 99 },	// --##-
	{ 0, 5 },	// #-##-
	{ 4, 99 },	// -###-
	{ 0, 6 },	// ####-
	{ 12, 99 },	// ----#
	{ -2, 12 },	// #---#
	{ 0, 12 },	// -#--#
	{ 3, 8 },	// ##--#
	{ 5, 12 },	// --#-#
	{ 0, 10 },	// #-#-#
	{ 7, 10 },	// -##-#
	{ 3, 99 },	// ###-#
	{ 10, 99 },	// ---##
	{ 9, 99 },	// #--##
	{ 10, 12 },	// -#-##
	{ 1, 8 },	// ##-##
	{ 11, 99 },	// --###
	{ 8, 99 },	// #-###
	{ 7, 12 },	// -####
	{ -1, 11 },	// #####
};

static snd_seq_t *seq;
static int port;

void open_seq(void) {
	snd_seq_port_info_t *pi;
	const snd_seq_addr_t *our_addr;
	snd_seq_addr_t midi_addr;
	snd_seq_port_subscribe_t *subs;
	int rc;

	rc = snd_seq_open (&seq, "default", SND_SEQ_OPEN_OUTPUT, 0);
	if (rc < 0)
		die ("cannot open sequencer");

	snd_seq_set_client_name (seq, "virtuoso");

	port = snd_seq_create_simple_port (seq, "MIDI out",
	           SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ,
	           SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);

	if (snd_seq_port_info_malloc (&pi) < 0)
		die ("cannot alloc port info");

	if (snd_seq_get_port_info (seq, port, pi) < 0)
		die ("cannot get port info");
	our_addr = snd_seq_port_info_get_addr (pi);

	midi_addr.client = 16;
	midi_addr.port = 0;

	snd_seq_port_subscribe_alloca (&subs);
	snd_seq_port_subscribe_set_sender (subs, our_addr);
	snd_seq_port_subscribe_set_dest (subs, &midi_addr);
	snd_seq_subscribe_port (seq, subs);

	snd_seq_port_info_free (pi);
}

int channel = 0;

void send_midi(int note, int on) {
	printf("sending %d %d on channel %d\n", note, on, channel + 1);

	snd_seq_event_t ev;
	snd_seq_ev_clear(&ev);
	snd_seq_ev_set_direct(&ev);
	snd_seq_ev_set_source(&ev, port);
	snd_seq_ev_set_subs(&ev);

	if (on) {
		snd_seq_ev_set_noteon(&ev, channel, note, 127);
	} else {
		snd_seq_ev_set_noteoff(&ev, channel, note, 0);
	}

	snd_seq_event_output(seq, &ev);
	snd_seq_drain_output(seq);
}

int frets = 0;
int base = 69;
#define NNOTES 128
int in_use[NNOTES];
double last_update = 0.0L;

struct base {
	int note;
	int channel;
	const char *name;
};
// A below middle C = 69
#define NUMBASES 6
const struct base bases[NUMBASES] = {
	{ 57, 0, "A lead" },
	{ 52, 0, "E lead" },
	{ 50, 0, "D lead" },
	{ 45, 1, "A bass" },
	{ 40, 1, "E bass" },
	{ 38, 1, "D bass" }
};

int notes_on = 0;

void release_notes(void) {
	for (int i = 0; i < NNOTES; i++) {
		if (in_use[i]) {
			notes_on--;
			send_midi(i, 0);
			in_use[i] = 0;
		}
	}
}
void update_notes(int strummed) {
	if (strummed)
		release_notes();
	// FIXME: This is wrong -- it'll retrigger notes that are already
	// sounding during a hammer-on (and get the count wrong).
	for (int i = 0; i < MAXNOTES; i++) {
		int n = notes[frets][i];
		if (n != 99) {
			n += base;
			notes_on++;
			send_midi(n, 1);
			in_use[n] = 2;
		}
	}
	for (int i = 0; i < NNOTES; i++) {
		switch (in_use[i]) {
		case 1:
			notes_on--;
			send_midi(i, 0);
			in_use[i] = 0;
			break;
		case 2:
			in_use[i] = 1;
			break;
		}
	}
}

int main(int argc, char **argv) {
	open_input();
	open_seq();

	for (int i = 0; i < NNOTES; i++)
		in_use[i] = 0;

	int basenumber = 0;
	double last_fret = 0.0;

	while (1) {
		guitar_key key;
		int value;
		get_key(&key, &value);

		base = bases[basenumber].note;
		channel = bases[basenumber].channel;

		int ofrets = frets;
		switch (key) {
		case FRET0:
		case FRET1:
		case FRET2:
		case FRET3:
		case FRET4:
			frets = (frets & ~(1 << (key - FRET0))) | (value << (key - FRET0));
			double now = daytime();
			printf("frets now %02x - %f - %d\n", frets, now - last_fret, notes_on);

			int diff = ofrets ^ frets;
			// It's a hammer-on/pull-off if it's one bit different from the old value,
			// and the old value wasn't 0, and it wasn't too quick after the last one.
			int hammer = (diff & (diff - 1)) == 0 && diff != 0
			             && ofrets != 0
			             && notes_on > 0
			             && (now - last_fret) > 0.001
			             && (now - last_fret) < 0.100;

			if (hammer && 0) {
				printf("hammer-on detected\n");
				update_notes(0);
			} else {
				release_notes();
			}

			last_fret = now;
			break;

		case STRUM:
			if (value != 0)
				update_notes(1);
			break;

		case SELECT:
		case START:
			if (value != 0) {
				basenumber = (basenumber + (key == SELECT ? (NUMBASES - 1) : 1)) % NUMBASES;
				printf(">>> Base %s <<<\n", bases[basenumber].name);
			}
			break;
		}
	}

	return 0;
}
