#include <stdio.h>
#include <stdlib.h>
#define MAIN_TRACK 0

char track_name[500];
char note_event[100];
char song_name[500];
unsigned int totaltime = 0;
char trackname_declared = 0;
int *lead_notes_time, *lead_notes, *lead_notes_used, lead_numnotes = 0;
FILE *chart;

unsigned int fread4(FILE *file)
{
    unsigned int quad;
    fread(&quad, 4, 1, file);
    quad = (quad>>24) | ((quad<<8) & 0x00FF0000) | ((quad>>8) & 0x0000FF00) | (quad<<24);
    return quad;
}

unsigned short fread2(FILE *file)
{
    unsigned short bi;
    fread(&bi, 2, 1, file);
    bi = (bi >> 8) | (bi << 8);
    return bi;
}

unsigned long ReadVarLen(FILE *infile)
{
    register unsigned long value;
    register unsigned char c;

    if ( (value = getc(infile)) & 0x80 )
    {
       value &= 0x7F;
       do
       {
         value = (value << 7) + ((c = getc(infile)) & 0x7F);
       } while (c & 0x80);
    }

    return(value);
}

void readnote(FILE *midi)
{
     unsigned char note, speed;
     if (strcmp(track_name, "PART GUITAR") == 0 || strcmp(track_name, "PART BASS") == 0 || strcmp(track_name, "PART GUITAR COOP") == 0 || strcmp(track_name, "PART RHYTHM") == 0  || strcmp(track_name, "T1 GEMS") == 0)
     {
        note = fgetc(midi);
        speed = fgetc(midi);                       
        lead_numnotes++;
        lead_notes_time = realloc(lead_notes_time, lead_numnotes * 4);
        lead_notes = realloc(lead_notes, lead_numnotes * 4);
        lead_notes_used = realloc(lead_notes_used, lead_numnotes * 4);
        lead_notes_time[lead_numnotes - 1] = totaltime;
        lead_notes[lead_numnotes - 1] = note;
        lead_notes_used[lead_numnotes - 1] = 0; 
        //printf("HI (%i)!", lead_numnotes);
        //system("PAUSE");
     }
     else if (strcmp(note_event, "Note-on") == 0 || strcmp(note_event, "Note-off") == 0)
     {
        note = fgetc(midi);
        speed = fgetc(midi);
         //printf("%s > Note %i, Speed %i\n", note_event, note, speed);
     }
     else if (strcmp(note_event, "Parameter") == 0 || strcmp(note_event, "Pitch") == 0 || strcmp(note_event, "Note aftertouch") == 0)
     {
        note = fgetc(midi);
        speed = fgetc(midi);
        //printf("%s > Value 1 %i, Value 2 %i\n", note_event, note, speed);
     }
     else if (strcmp(note_event, "Program") == 0 || strcmp(note_event, "Channel aftertouch") == 0)
     {
          note = fgetc(midi);
          //printf("%s > Value 1 %i\n", note_event, note);
     }
     else
     {
         printf("Unhandled type!\n");
         getch();
         exit(-1);
     }
}

void write_notes(int first_note)
{
     int i, j, notelength;
     for (i = 0; i < lead_numnotes; i++)
     {
         // Huge comparison to see if it's a note start from the level or a SP phrase
         if ((lead_notes_used[i] == 0 && lead_notes[i] >= first_note && lead_notes[i] < first_note + 5) || (lead_notes_used[i] == 0 && lead_notes[i] == first_note + 7))
         {
            lead_notes_used[i] = 1;
            // Find length...
            for (j = i + 1; j < lead_numnotes; j++)
            {
                if (lead_notes[i] == lead_notes[j])
                {
                   notelength = lead_notes_time[j] - lead_notes_time[i];
                   lead_notes_used[j] = 1;
                   if (notelength <= 160)
                      notelength = 0;
                   break;
                }
            }
            // Write to chart
            if (lead_notes[i] == first_note + 7)
               fprintf(chart, "\t%i = S 2 %i\n", lead_notes_time[i], notelength);
            else
               fprintf(chart, "\t%i = N %i %i\n", lead_notes_time[i], lead_notes[i] - first_note, notelength);
         }
     }
}
         
int main(int argc, char *argv[])
{
    printf("Chart Generator 0.2\n-------------------\nWARNING: If one MIDI file is not converted correctly, try importing it in a GH2 copy with GHex, then extract it again!\n\n");
    
    // Check args
    char midifilename[500];
    char chartfilename[500];
    
    printf("Enter the name of the GH MIDI file: ");
    gets(midifilename);
    printf("Enter the name for the .chart (Feedback format) that will be generated: ");
    gets(chartfilename);
    printf("\n");
    // Open the MIDI file
    FILE *midi;
    midi = fopen(midifilename, "rb");
    if (midi == NULL)
    {
       printf("Can't open %s for reading!\n", midifilename);
       getch();
       exit(-1);
    }
    
    // Open the CHART file
    chart = fopen(chartfilename, "w");
    if (chart == NULL)
    {
       printf("Can't open %s for writing!\n", chartfilename);
       getch();
       exit(-1);
    }
    
    // Read the MThd chunk...
    char mthd[5];
    unsigned int mthdlength;
    unsigned short mthdformat, mthdnumtracks, mthddivision;
    fgets(mthd, 5, midi);
    mthdlength = fread4(midi);
    mthdformat = fread2(midi);
    mthdnumtracks = fread2(midi);
    mthddivision = fread2(midi);
    
    if (strcmp(mthd, "MThd") != 0)
    {
       printf("Invalid MIDI file!\n");
       getch();
       exit(-1);
    }
    else
    {
        printf("Valid MIDI file! (%i bytes): Format %i, %i tracks, %i \"division\"\n", mthdlength, mthdformat, mthdnumtracks, mthddivision);
    }
    
    // Parse all MTrk chunks...
    int i;
    for (i = 0; i < mthdnumtracks; i++)
    {
        char mtrk[5];
        unsigned int mtrklength, trkstart;
        fgets(mtrk, 5, midi);
        mtrklength = fread4(midi);
        trkstart = ftell(midi);
        if (strcmp(mtrk, "MTrk") != 0)
        {
           printf("Invalid MIDI track!\n");
           getch();
           exit(-1);
        }
        else
        {
            //printf("Valid MTrk chunk! (%i bytes)\n", mtrklength);
        }
        // Add new things for the chart...
        totaltime = 0;
        trackname_declared = 0;
        while (ftell(midi) < trkstart + mtrklength)
        {
              unsigned char type;
              unsigned int time;
              time = ReadVarLen(midi);
              totaltime += time;
              //printf("%i (+%i): ", totaltime, time);
              
              type = fgetc(midi);
              // Event
              if (type == 0xFF)
              {
                 unsigned char typeff;
                 typeff = fgetc(midi);
                 if (typeff == 0x03)
                 {
                    // Track name
                    unsigned int strlength;
                    char *string;
                    strlength = ReadVarLen(midi) + 1;
                    string = malloc(strlength);
                    if (string == NULL)
                    {
                       printf("Can't alloc. %i bytes of memory!\n", strlength);
                       getch();
                       exit(-1);
                    }
                    fgets(string, strlength, midi);
                    if (i == MAIN_TRACK)
                    {                       
                       strcpy(song_name, string);
                    }
                    printf("Track name > %s\n", string);
                    strcpy(track_name, string);
                    free(string);
                 }
                 else if (typeff == 0x01)
                 {
                    // Text (copy-paste >_>)
                    unsigned int strlength;
                    char *string;
                    strlength = ReadVarLen(midi) + 1;
                    string = malloc(strlength);
                    if (string == NULL)
                    {
                       printf("Can't alloc. %i bytes of memory!\n", strlength);
                       getch();
                       exit(-1);
                    }
                    fgets(string, strlength, midi);
                    if (strcmp(track_name, "EVENTS") == 0)
                    {
                       if (trackname_declared == 0)
                       {
                          trackname_declared = 1;
                          fprintf(chart, "[Events]\n");
                          fprintf(chart, "{\n");
                       }
                       // Strip the [ and ] if it's GH2...
                       if (string[0] == '[' && string[strlen(string) - 1] == ']')
                       {
                           string += 1;
                           string[strlen(string) - 1] = 0;
                       }
                       fprintf(chart, "\t%i = E \"%s\"\n", totaltime, string);
                    }
                    else
                    {
                        //printf("Text > %s\n", string);
                    }
                    free(string);
                 }
                 else if (typeff == 0x58)
                 {
                      // Time signature
                      unsigned int timesiglength;
                      unsigned char num, denum, clocks, notated32;
                      timesiglength = ReadVarLen(midi);
                      if (timesiglength != 4)
                      {
                         printf("Invalid time signature length!\n");
                         getch();
                         exit(-1);
                      }
                      num = fgetc(midi);
                      denum = fgetc(midi);
                      clocks = fgetc(midi);
                      notated32 = fgetc(midi);
                      if (i == MAIN_TRACK)
                      {
                          if (trackname_declared == 0)
                          {
                             trackname_declared = 1;
                             fprintf(chart, "[SyncTrack]\n");
                             fprintf(chart, "{\n");
                          }
                          fprintf(chart, "\t%i = TS %i\n", totaltime, num);
                      }
                      else
                      {
                          //printf("Time signature > %i/%i, %i MIDI clocks, %i notated 32nds in a 1/4\n", num, denum, clocks, notated32);
                      }
                 }
                 else if (typeff == 0x51)
                 {
                      // Tempo
                      unsigned int tempolength, tempo = 0;
                      tempolength = ReadVarLen(midi);
                      if (tempolength != 3)
                      {
                         printf("Invalid tempo length!\n");
                         getch();
                         exit(-1);
                      }
                      fread(&tempo, 3, 1, midi);
                      // Swap Endians
                      tempo = (tempo>>24) | ((tempo<<8) & 0x00FF0000) | ((tempo>>8) & 0x0000FF00) | (tempo<<24);
                      // Shift more...
                      tempo >>= 8;
                      if (i == MAIN_TRACK)
                      {
                            int bpm_fback = round(60000000.0 / tempo * 1000);
                            if (trackname_declared == 0)
                            {
                               trackname_declared = 1;
                               fprintf(chart, "[SyncTrack]\n");
                               fprintf(chart, "{\n");
                            }
                            fprintf(chart, "\t%i = B %i\n", totaltime, bpm_fback);
                      }
                      else
                      {
                          //printf("Tempo > %i (%f BPM)\n", tempo, 60000000.0 / tempo);    
                      }
                 }
                 else if (typeff == 0x2F)
                 {
                      unsigned char length;
                      length = fgetc(midi);
                      if (length != 0)
                      {
                         printf("Invalid track end length!\n");
                         getch();
                         exit(-1);
                      }
                      //printf("MTrk End\n\n");
                 }
                 else if (typeff == 0x7F)
                 {
                      unsigned int length;
                      length = ReadVarLen(midi);
                      fseek(midi, length, SEEK_CUR);
                      //printf("Propietary event > %i bytes\n", length);
                 }     
                 else if (typeff == 0x20)
                 {
                      unsigned char channel;
                      channel = fgetc(midi);
                      //printf("Set channel > %i\n", channel);
                 }
                 else if (typeff == 0x05)
                 {   
                    // Lyric (copy-paste >_>)
                    unsigned int strlength;
                    char *string;
                    strlength = ReadVarLen(midi) + 1;
                    string = malloc(strlength);
                    if (string == NULL)
                    {
                       printf("Can't alloc. %i bytes of memory!\n", strlength);
                       exit(-1);
                    }
                    fgets(string, strlength, midi);
                    //printf("Lyric > %s\n", string);
                    free(string);
                 }                   
                 else
                 {
                     printf("Unknown 0xFF%X event!\n", typeff);
                     getch();
                     exit(-1);
                 }
              }
              else if (type < 128)
              {
                   fseek(midi, -1, SEEK_CUR);
                   readnote(midi);
              }
              else if (type >= 0x80 && type < 0x90)
              {
                   //printf("Define note-off\n");
                   strcpy(note_event, "Note-off");
                   readnote(midi);
              }
              else if (type >= 0x90 && type < 0xA0)
              {
                   //printf("Define note-on\n");
                   strcpy(note_event, "Note-on");
                   readnote(midi);
              }
              else if (type >= 0xA0 && type < 0xB0)
              {
                   //printf("Define parameter + ");
                   strcpy(note_event, "Note aftertouch");
                   readnote(midi);
              }
              else if (type >= 0xB0 && type < 0xC0)
              {
                   //printf("Define parameter + ");
                   strcpy(note_event, "Parameter");
                   readnote(midi);
              }
              else if (type >= 0xC0 && type < 0xD0)
              {
                   //printf("Define program + ");
                   strcpy(note_event, "Program");
                   readnote(midi);
              }
              else if (type >= 0xD0 && type < 0xE0)
              {
                   //printf("Define program + ");
                   strcpy(note_event, "Channel aftertouch");
                   readnote(midi);
              }
              else if (type >= 0xE0 && type < 0xF0)
              {
                   //printf("Define pitch + ");
                   strcpy(note_event, "Pitch");
                   readnote(midi);
              }
              else
              {
                  printf("Unknown flag %X!\n", type);
                  getch();
                  exit(-1);
              }
        }
        /* Write to chart */
        if (strcmp(track_name, "EVENTS") == 0 && trackname_declared == 1)
        {
           fprintf(chart, "}\n");
        }
        /* [Song] */
        if (i == MAIN_TRACK)
        {
           fprintf(chart, "}\n"); /* Close synctrack */
           fprintf(chart, "[Song]\n");
           fprintf(chart, "{\n");
           fprintf(chart, "\tName = \"%s\"\n", song_name);
    	   fprintf(chart, "\tArtist = \"%s authors\"\n", song_name);
    	   fprintf(chart, "\tCharter = \"someone\"\n");
     	   fprintf(chart, "\tOffset = 0\n");
     	   fprintf(chart, "\tResolution = %i\n", mthddivision);
     	   fprintf(chart, "\tPlayer2 = bass\n");
     	   fprintf(chart, "\tDifficulty = 0\n");
     	   fprintf(chart, "\tPreviewStart = 0.00\n");
     	   fprintf(chart, "\tPreviewEnd = 0.00\n");
     	   fprintf(chart, "\tGenre = \"rock\"\n");
     	   fprintf(chart, "\tMediaType = \"cd\"\n");
     	   fprintf(chart, "\tMusicStream = \"%s.ogg\"\n", song_name);
           fprintf(chart, "}\n");
        }
        /* Guitar! */
        if (strcmp(track_name, "PART GUITAR") == 0 || strcmp(track_name, "T1 GEMS") == 0)
        {
            // Parse the lead tracks (E-M-H-X)
            if (lead_numnotes > 0)
            {
               // Easy...
               fprintf(chart, "[EasySingle]\n");
               fprintf(chart, "{\n");
               write_notes(60);
               fprintf(chart, "}\n");
               fprintf(chart, "[MediumSingle]\n");
               fprintf(chart, "{\n");
               write_notes(72);
               fprintf(chart, "}\n");
               fprintf(chart, "[HardSingle]\n");
               fprintf(chart, "{\n");
               write_notes(84);
               fprintf(chart, "}\n");
               fprintf(chart, "[ExpertSingle]\n");
               fprintf(chart, "{\n");
               write_notes(96);
               fprintf(chart, "}\n");
               
               // Delete from memory
               lead_numnotes = 0;
            }
        }
        /* Guitar Coop! */
        if (strcmp(track_name, "PART GUITAR COOP") == 0)
        {
            // Parse the bass tracks (E-M-H-X)
            if (lead_numnotes > 0)
            {
               // Easy...
               fprintf(chart, "[EasyDoubleGuitar]\n");
               fprintf(chart, "{\n");
               write_notes(60);
               fprintf(chart, "}\n");
               fprintf(chart, "[MediumDoubleGuitar]\n");
               fprintf(chart, "{\n");
               write_notes(72);
               fprintf(chart, "}\n");
               fprintf(chart, "[HardDoubleGuitar]\n");
               fprintf(chart, "{\n");
               write_notes(84);
               fprintf(chart, "}\n");
               fprintf(chart, "[ExpertDoubleGuitar]\n");
               fprintf(chart, "{\n");
               write_notes(96);
               fprintf(chart, "}\n");
               
               // Delete from memory
               lead_numnotes = 0;
            }
        }
        /* Bass! */
        if (strcmp(track_name, "PART BASS") == 0 || strcmp(track_name, "PART RHYTHM") == 0)
        {
            // Parse the bass tracks (E-M-H-X)
            if (lead_numnotes > 0)
            {
               // Easy...
               fprintf(chart, "[EasyDoubleBass]\n");
               fprintf(chart, "{\n");
               write_notes(60);
               fprintf(chart, "}\n");
               fprintf(chart, "[MediumDoubleBass]\n");
               fprintf(chart, "{\n");
               write_notes(72);
               fprintf(chart, "}\n");
               fprintf(chart, "[HardDoubleBass]\n");
               fprintf(chart, "{\n");
               write_notes(84);
               fprintf(chart, "}\n");
               fprintf(chart, "[ExpertDoubleBass]\n");
               fprintf(chart, "{\n");
               write_notes(96);
               fprintf(chart, "}\n");
               
               // Delete from memory
               lead_numnotes = 0;
            }
        }
    }
    // For the last track
    if (trackname_declared == 1)
    {
       fprintf(chart, "}\n");
    }
    free(lead_notes_time);    
    free(lead_notes);
    free(lead_notes_used);

    printf("\n/-------------------------\\\n|MIDI Parsed & Converted!|\n\\-------------------------/\n");
    printf("Press any key to exit!\n");
    getch();
    return 0;
}
