// Guitar Virtuoso: generate MIDI from a Guitar Hero controller // Adam Sampson #include #include #include #include #include #include #include #include #include /* 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; }