#include "tunings.h"
#include <math.h>
#include <assert.h>
#include <ctype.h>
#include <stdio.h>

static int floor_div(int a, int b) {
  int c = a/b;
  if (b*c > a)
    return c-1;
  else
    return c;
}

float cents(float ratio) {
  return log(ratio)*(1200.0/log(2.0));
}

void makeequal(float octaveratio, float numer, float denom, float outfreqs[128]) {
  for(int i=0;i<128;i++) {
    outfreqs[i]=256.0*pow(octaveratio,(i-60.0)*numer/denom);
  }
}

void makeoctaves(float scaleratios[12], float octaveratio, float outfreqs[128]) {
  for(int i=0;i<128;i++) {
    int oct = floor_div(i-60,12);
    int note = (i-60)-oct*12;
    assert(note >= 0 && note < 12);
    outfreqs[i] = 256.0 * pow(octaveratio,oct) * scaleratios[note];
    assert(outfreqs[i] > 0);
  }
}

const int fifthslayout_normal[12] = {
  0, // C
  7, // C#
  2, // D
  -3, // Eb
  4, // E
  -1, // F
  6, // F#
  1, // G
  8, // G#
  3, // A
  -2, // Bb
  5, // B
};

void makemeantone(float octavecents, float fifthcents, const int fifthslayout[12],float outfreqs[128]) {
  float octavetable[12];
  /*
    int fifths[12] = {
    0, // C
    7, // C#
    2, // D
    9, // Eb
    4, // E
    -1, // F
    6, // F#
    1, // G
    8, // G#
    3, // A
    -2, // Bb
    5, // B
    };
  */
  /*
    int fifths[12] = {
    0,  // C
    -17, // Ebbb/Db-
    2,  // D
    -15, // Fbb/Eb-
    -8, // Fb/E-
    -1, // F
    -6, // Gb/F#-
    1,  // G
    -16, // Bbbb/G#--
    -9, // Bbb/A-
    -14, // Cbb/Bb-
    -7, // Cb/B-
    };
  */
  /*
    int fifths[12] = {
    0,  // C
    -5, // Db
    2,  // D
    -15, // Fbb
    -8, // Fb
    -13, // Gbb
    -6, // Gb
    1,  // G
    -16, // Bbbb
    3, // A
    -14, // Cbb
    -7, // Cb
    };
  */
  /*
    int fifths[12] = {
    0,  // C
    -5, // Db
    2,  // D
    -15, // Fbb
    -8, // Fb
    -13, // Gbb
    -6, // Gb
    1,  // G
    -16, // Bbbb
    3, // A
    -14, // Cbb
    -7, // Cb
    };
  */

  float octave=pow(2.0,octavecents/1200.0);
  float fifth=pow(2.0,fifthcents/1200.0);

  for(int i=0;i<12;i++) {
    float ratio = powf(fifth,fifthslayout[i]);
    while(ratio < 1.0)
      ratio *= octave;
    while(ratio >= octave)
      ratio /= octave;
    octavetable[i] = ratio;
    assert(octavetable[i] > 0);
  }

  makeoctaves(octavetable, octave, outfreqs);
}

void makejust(float outfreqs[128]) {
  float octavetable[12];
  octavetable[0]=3.0/4.0;
  octavetable[1]=7.0/9.0;
  octavetable[2]=5.0/6.0;
  octavetable[3]=7.0/8.0;
  octavetable[4]=15.0/16.0;
  octavetable[5]=1.0;
  octavetable[6]=10.0/9.0;
  octavetable[7]=9.0/8.0;
  octavetable[8]=7.0/6.0;
  octavetable[9]=5.0/4.0;
  octavetable[10]=21.0/16.0;
  octavetable[11]=4.0/3.0;

  makeoctaves(octavetable,2.0,outfreqs);

    /*
    octavetable[0]=1.0;
    octavetable[1]=10/9.0;
    octavetable[2]=9.0/8.0;
    octavetable[3]=5.0/4.0;
    octavetable[4]=81.0/64.0;
    octavetable[5]=4.0/3.0;
    octavetable[6]=45.0/32.0;
    octavetable[7]=3.0/2.0;
    octavetable[8]=5.0/3.0;
    octavetable[9]=27.0/16.0;
    octavetable[10]=16.0/9.0;
    octavetable[11]=15.0/8.0;
    */
    /*
    octavetable[0]=1.0;
    octavetable[1]=25.0/24.0;
    octavetable[2]=9.0/8.0;
    octavetable[3]=75.0/64.0;
    octavetable[4]=5.0/4.0;
    octavetable[5]=4.0/3.0;
    octavetable[6]=45.0/32.0;
    octavetable[7]=3.0/2.0;
    octavetable[8]=25.0/16.0;
    octavetable[9]=5.0/3.0;
    octavetable[10]=225.0/128.0;
    octavetable[11]=15.0/8.0;
    */
    /*
    octavetable[0]=1.0/1.0;
    octavetable[1]=8.0/7.0;
    octavetable[2]=7.0/6.0;
    octavetable[3]=6.0/5.0;
    octavetable[4]=5.0/4.0;
    octavetable[5]=4.0/3.0;
    octavetable[6]=7.0/5.0;
    octavetable[7]=3.0/2.0;
    octavetable[8]=8.0/5.0;
    octavetable[9]=5.0/3.0;
    octavetable[10]=7.0/4.0;
    octavetable[11]=9.0/5.0;
    */
    /*
    octavetable[0]=1.0/1.0;
    octavetable[1]=35.0/32.0;
    octavetable[2]=9.0/8.0;
    octavetable[3]=5.0/4.0;
    octavetable[4]=81.0/64.0;
    octavetable[5]=21.0/16.0;
    octavetable[6]=45.0/32.0;
    octavetable[7]=3.0/2.0;
    octavetable[8]=25.0/16.0;
    octavetable[9]=27.0/16.0;
    octavetable[10]=7.0/4.0;
    octavetable[11]=15.0/8.0;
    */
    /*
    octavetable[0]=1.0/1.0;
    octavetable[1]=32.0/31.0;
    octavetable[2]=16.0/15.0;
    octavetable[3]=1.0/1.0;
    octavetable[4]=1.0/1.0;
    octavetable[5]=4.0/3.0;
    octavetable[6]=1.0/1.0;
    octavetable[7]=3.0/2.0;
    octavetable[8]=48.0/31.0;
    octavetable[9]=8.0/5.0;
    octavetable[10]=1.0/1.0;
    octavetable[11]=1.0/1.0;
    */
}

int parsenote(const char* str) {
  int fifths=0;
  int c;
  switch(c=toupper(*str++)) {
  case 'F': fifths = -1; break;
  case 'C': fifths = 0; break;
  case 'G': fifths = 1; break;
  case 'D': fifths = 2; break;
  case 'A': fifths = 3; break;
  case 'E': fifths = 4; break;
  case 'B': fifths = 5; break;
  case '\0':
    printf("warning: parsenote: empty string\n");
    return 0;
  default:
    printf("warning: unknown note name '%c'\n",c);
  }
  while(1) {
    switch(c=*str++) {
    case '\0': return fifths;
    case '#': fifths += 7; break;
    case 'b': fifths -= 7; break;
    case '+': fifths += 12; break;
    case '-': fifths -= 12; break;
    default:
      printf("warning: unknonwn accent '%c'\n",c);
    };
  }
}

int parseratio(const char* s, int* out_numer, int* out_denom) {
  int n = 0;
  if(!isdigit(*s))
    return 0;
  while(isdigit(*s)) {
    n = n * 10 + (*s - '0');
    s++;
  }

  if (*s++ != '/')
    return 0;

  int d = 0;
  if (!isdigit(*s))
    return 0;
  while(isdigit(*s)) {
    d = d * 10 + (*s - '0');
    s++;
  }
  *out_numer = n;
  *out_denom = d;
  return 1;
}
