
/*
   Dechord -- analyse chord name and generate chord

   This file by Guillaume Laurent, glaurent@worldnet.fr
   Copyright 1996 Guillaume Laurent

   Used in Rosegarden under the same redistribution
   rights as the rest of the Rosegarden source
*/

#include "Dechord.h"

static regexp *GetRoot, *IsMinor, *GetSus, *IsDim, *IsAug, *IsPowc, *Get7, *GetAdd,
  *GetAlt5, *Get6, *GetAlt9, *GetAlt11, *GetAlt13, *GetAltBass;

void InitRegs(void)
{
  Begin("InitRegs");
  GetRoot = regcomp(GET_ROOT);
  IsMinor = regcomp(IS_MINOR);
  GetSus = regcomp(GET_SUS);
  IsDim = regcomp(IS_DIM);
  IsAug = regcomp(IS_AUG);
  IsPowc = regcomp(IS_POWC);
  Get7 = regcomp(GET_7TH);
  GetAdd = regcomp(GET_ADD);
  GetAlt5 = regcomp(GET_ALT5);
  Get6 = regcomp(GET_6);
  GetAlt9 = regcomp(GET_ALT9);
  GetAlt11 = regcomp(GET_ALT11);
  GetAlt13 = regcomp(GET_ALT13);
  GetAltBass = regcomp(GET_ALTBASS);
  /* gee, what a nice piece of code this is */
  End;
}

void FreeRegs(void)
{
  Begin("FreeRegs");
  free(GetRoot);
  free(IsMinor);
  free(GetSus);
  free(IsDim); free(IsAug); free(IsPowc); free(Get7);
  free(GetAdd); free(GetAlt5); free(Get6);
  free(GetAlt9); free(GetAlt11); free(GetAlt13); free(GetAltBass);
  End;
}

NoteVoice TransposeVoice(NoteVoice root, int interval,
			  ClefTag currentClef, Boolean sharpsOrFlats)
{
  Begin("TransposeVoice");

  return (MidiPitchToVoice(VoiceToMidiPitch(&root, currentClef)
			   + interval, sharpsOrFlats));
}

Pitch NoteToPitch(char Note, ClefTag currentClef)
{
  Pitch p;
  Begin("NoteToPitch");

  if((Note < 'A') && (Note > 'G')) Error("wrong note");

  p = Note - 'E' /* - 7 ?? */ + ClefPitchOffset(currentClef);
  Return(p);
}

Chord* ArrayToChord(char ChordArray[],
		   NoteVoice voicesBuf[], int lastNote,
		   ClefTag currentClef, Boolean sharpsOrFlats)
{
  int i;
  NoteVoice *voices, root = voicesBuf[0];
  Begin("ArrayToChord");

  for(i=lastNote; i < CHORDLENGTH; i++) {
    if(ChordArray[i])
      voicesBuf[lastNote++] = TransposeVoice(root, i, currentClef,
					     sharpsOrFlats);
  }

  voices = (NoteVoice*)XtMalloc(lastNote * sizeof(NoteVoice));
  memcpy(voices, voicesBuf, (size_t)(lastNote * sizeof(NoteVoice)));

  return(NewChord(NULL, voices, lastNote,
		  ModNone, Crotchet, 0));
}

int CompleteChord(int code, char ChordArray[])
{
  Begin("CompleteChord");
  switch(code) {

  case 13 :
    if((!ChordArray[DIM_ELEVENTH]) &&
       (!ChordArray[ELEVENTH]) &&
       (!ChordArray[AUG_ELEVENTH]))
      ChordArray[ELEVENTH] = 1;

  case 11 :
    if((!ChordArray[DIM_NINTH]) &&
       (!ChordArray[NINTH]) &&
       (!ChordArray[AUG_NINTH]))
      ChordArray[NINTH] = 1;
    
  case 9 :
    if((!ChordArray[MINOR_SEVENTH]) &&
       (!ChordArray[MAJOR_SEVENTH]))
      ChordArray[MINOR_SEVENTH] = 1;
  }

  Return(UNCOMPLETE);
}


int FindThird(char ChordString[], char ChordArray[])
{
  char buffer[255];
  Begin("FindThird");
  memset(buffer, 0, 255*sizeof(char));

  ChordArray[ROOT] = 1; /* gotta have a root, no matter what */

  if(regexec(IsDim, ChordString)) { /* is it diminished ? */
    ChordArray[MINOR_THIRD] = 1;
    ChordArray[DIM_FIFTH] = 1;
    ChordArray[DIM_SEVENTH] = 1;
    Return(COMPLETE);
  }

  if(regexec(IsAug, ChordString)) { /* is it augmented ? */
    ChordArray[MAJOR_THIRD] = 1;
    ChordArray[AUG_FIFTH] = 1;
    Return(COMPLETE);
  }

  if(regexec(IsPowc, ChordString)) { /* is it a power chord ? */
    ChordArray[FIFTH] = 1;
    Return(COMPLETE);
  }

  if(regexec(IsMinor, ChordString))
     ChordArray[MINOR_THIRD] = 1; /* it's a minor chord */
  else if(regexec(GetSus, ChordString)) {  /* it can be a suspended chord */
    regsub(GetSus, "&", buffer);
    if(buffer[3] == '2') ChordArray[MAJOR_SECOND] = 1; /* sus2 */
    else ChordArray[FOURTH] = 1; /* sus4 */
  }
  else ChordArray[MAJOR_THIRD] = 1;

  Return(UNCOMPLETE);
}

int FindFifth(char ChordString[], char ChordArray[])
{
  char buffer[255];
  Begin("FindFifth");
  memset(buffer, 0, 255*sizeof(char));

  if(regexec(GetAlt5, ChordString)) {
    regsub(GetAlt5, "&", buffer);
    if((buffer[0] == 'b') || (buffer[0] == '-'))
      ChordArray[DIM_FIFTH] = 1;
    else ChordArray[AUG_FIFTH] = 1;
  }
  else ChordArray[FIFTH] = 1;

  Return(UNCOMPLETE);
}

int FindSixth(char ChordString[], char ChordArray[])
{
  Begin("FindSixth");
  if(regexec(Get6, ChordString)) ChordArray[SIXTH] = 1;
  Return(UNCOMPLETE);
}

int FindSeventh(char ChordString[], char ChordArray[])
{
  char buffer[255];
  Begin("FindSeventh");
  memset(buffer, 0, 255*sizeof(char));

  if(regexec(Get7, ChordString)) {
    regsub(Get7, "&", buffer);
    if(buffer[0] == 'j') ChordArray[MAJOR_SEVENTH] = 1;
    else ChordArray[MINOR_SEVENTH] = 1;
  }
  Return(UNCOMPLETE);
}

int FindAdd(char ChordString[], char ChordArray[])
{
  char buffer[255];
  Begin("FindAdd");
  memset(buffer, 0, 255*sizeof(char));

  if(regexec(GetAdd, ChordString)) {
    regsub(GetAdd, "&", buffer);
    if(buffer[1] == '9') ChordArray[NINTH] = 1;
    else if(buffer[2] == '1') ChordArray[ELEVENTH] = 1;
    else ChordArray[THIRTEENTH] = 1;
  }
  Return(UNCOMPLETE);
}

int FindNinth(char ChordString[], char ChordArray[])
{
  int retcode = UNCOMPLETE;
  char buffer[255];
  Begin("FindNinth");
  memset(buffer, 0, 255*sizeof(char));

  /* if Chord[NINTH] Return(UNCOMPLETE); */ 
  /* not the function's job to check if it should have been called in the
     first place */
  
  if(regexec(GetAlt9, ChordString)) {
    regsub(GetAlt9, "&", buffer);
    if((buffer[0] == 'b') || (buffer[0] == '-')) ChordArray[DIM_NINTH] = 1;
    else if((buffer[0] == '#') || (buffer[0] == '+')) ChordArray[AUG_NINTH] = 1;
    else {
      ChordArray[NINTH] = 1;
      retcode = COMPLETE;
    }

    CompleteChord(9, ChordArray);

  }
  Return(retcode);
}

int FindEleventh(char ChordString[], char ChordArray[])
{
  int retcode = UNCOMPLETE;
  char buffer[255];
  Begin("FindEleventh");
  memset(buffer, 0, 255*sizeof(char));

  if(regexec(GetAlt11, ChordString)) {
    regsub(GetAlt11, "&", buffer);
    if((buffer[0] == 'b') || (buffer[0] == '-')) ChordArray[DIM_ELEVENTH] = 1;
    else if((buffer[0] == '#') || (buffer[0] == '+')) ChordArray[AUG_ELEVENTH] = 1;
    else {
      ChordArray[ELEVENTH] = 1;
      retcode = COMPLETE;
    }

    CompleteChord(11, ChordArray);
  }
  Return(retcode);
}

int FindThirteenth(char ChordString[], char ChordArray[])
{
  int retcode = UNCOMPLETE;
  char buffer[255];
  Begin("FindThirteenth");
  memset(buffer, 0, 255*sizeof(char));
 
  if(regexec(GetAlt13, ChordString)) {
    regsub(GetAlt13, "&", buffer);
    if((buffer[0] == 'b') || (buffer[0] == '-')) ChordArray[DIM_THIRTEENTH] = 1;
    else if((buffer[0] == '#') || (buffer[0] == '+')) ChordArray[AUG_THIRTEENTH] = 1;
    else {
      ChordArray[THIRTEENTH] = 1;
      retcode = COMPLETE;
    }

    CompleteChord(13, ChordArray);

  }
  Return(UNCOMPLETE);
}


Chord* SpellChord(char ChordString[], ClefTag currentClef, Boolean sharpsOrFlats)
{
  int match, i, IsComplete, lastNote = 0;
  static Boolean initWasDone = FALSE;
  Pitch rootPitch = 0, altBassPitch = 0;
  NoteMods modifiers = ModNone;
  char ChordArray[CHORDLENGTH];
  char RootNote[2], AltBass[3]; /* extra char is for leading slash */
  NoteVoice voicesBuf[CHORDLENGTH];
  Begin("SpellChord");


  if(!initWasDone) {
    InitRegs();
    initWasDone = TRUE;
  }

  /* Zero out a few variables */
  RootNote[1] = 0;
  lastNote = 0;
  memset(voicesBuf, 0, (size_t)CHORDLENGTH * sizeof(NoteVoice));
  memset(ChordArray, 0, (size_t)CHORDLENGTH * sizeof(char));
  
  match = regexec(GetRoot, ChordString); /* Get Root Note (ie tonic) */

  if(!match) {
    fputs("Can't recognize root note\n", stderr);
    Return(NULL);
  }

  regsub(GetRoot, "&", RootNote);

  rootPitch = NoteToPitch(RootNote[0], currentClef); 
  switch(RootNote[1])
    {
    case 'b' : modifiers = ModFlat; break;
    case '#' : modifiers = ModSharp; break;
    default : modifiers = ModNone;
    }

  NewNoteVoice(&(voicesBuf[0]), rootPitch, modifiers); lastNote++;

  for(i = 0; i < strlen(RootNote); i++) ChordString[i] = ' ';
  /* remove root from arg string */

  if(regexec(GetAltBass, ChordString)) { /* Get possible altered bass */
    regsub(GetAltBass, "\\1", AltBass);
    altBassPitch = NoteToPitch(AltBass[0], currentClef) - 7; 
    switch(AltBass[1])
      {
      case 'b' : modifiers = ModFlat; break;
      case '#' : modifiers = ModSharp; break;
      default : modifiers = ModNone;
      }
    NewNoteVoice(&(voicesBuf[1]), altBassPitch, modifiers); lastNote++;
  }

  /* Now analyse the chord */
  IsComplete = FindThird(ChordString, ChordArray);
  if(!IsComplete)
    IsComplete = FindFifth(ChordString, ChordArray);
  if(!IsComplete)
    IsComplete = FindSixth(ChordString, ChordArray);
  if(!IsComplete)
    IsComplete = FindSeventh(ChordString, ChordArray);
  if(!IsComplete)
    IsComplete = FindAdd(ChordString, ChordArray);
  if((!IsComplete) && (!ChordArray[NINTH])) /* an added 9th 11th or 13th */
    IsComplete = FindNinth(ChordString, ChordArray); /* may be found by FindAdd */
  if((!IsComplete) && (!ChordArray[ELEVENTH]))
    IsComplete = FindEleventh(ChordString, ChordArray);
  if((!IsComplete) && (!ChordArray[THIRTEENTH]))
    IsComplete = FindThirteenth(ChordString, ChordArray);


  /* FreeRegs(); Should be called on editor's exit */

  Return(ArrayToChord(ChordArray, voicesBuf, lastNote,
		      currentClef, sharpsOrFlats));

}

