#include <stdio.h>
#include <sdl-widgets.h>
#include "fft.h"

const int
  spect_size=800, // spectrum window
  ww_size=800,    // wave window 
  fftw_size=670,  // fft-sample window
  peaks_max=16,   // spectrum peaks
  SAMPLE_RATE=44100;
WinBase *top_win;

bool debug;
const char *infile=0,                // input wave file
           *patch_file="out.bb-pat"; // output patch file
SDL_Thread *play_thread;

static Uint32 cGreen,cLightYellow,cWaveBgr,cLightGrey,cWave,
              rgbWaveBgr,rgbWave,cAlert;

template <class T>
struct Arr {
  T *buf;
  Uint32 dim;
  T nul;
  Arr<T>():dim(0) { }
  void init(Uint32 d) { dim=d; buf=new T[dim]; }
  void init(Uint32 d,T b) { dim=d; buf=new T[dim]; for (Uint32 i=0;i<dim;++i) buf[i]=b; }
  T& operator[](Uint32 ind) {
    if (ind<dim) return buf[ind];
    if (debug) abort();
    alert("array index=%d (expected 0 - %d)",ind,dim-1);
    return nul;
  }
};

Uint8 *keystate=SDL_GetKeyState(0);

struct Gui {
  BgrWin *spectrum_win,
         *wave_win,
         *fft_win;
  HSlider *fine,
          *course,
          *freq_mult,
          *basefreq_div;
  TextWin *info;
  Button *play;
  RButWin *playmode,
          *loopmode;
  DialogWin *dialog;
  Gui();
  int f_c_value();
  void draw_info(int fft_nr);
} *gui;

struct SpectData {
  float base_freq,
        peaks[peaks_max],
        magn[peaks_max];
  int maxmag,   // max peak value
      lst_sline;  // last spect line
  char nr_peaks;
  bool skipped,
       bf_valid;  // base freq valid?
  Arr<short> spectlines; // buffer for spectrum lines, lines are minimum 2 pixels apart
  SpectData();
};

struct Wav2Patch {
  int called,     // eval() called
      nr_fft_samples,
      nr_wav_samples,
      buf_size,   // fft buffer size
      wwh,        // wave window height
      the_xpos1,  // selection in wave window, red
      the_xpos2,  // black
      the_fft_xpos1, // black
      the_fft_xpos2, // red
      the_fft_xpos3, // blue
      mult_spl,   // multiplier spectrum line
      data_start, // patch play start
      loop_start,loop_end,
      root_freq,
      basefreq_div;// base freq divider
  Uint8 play_mode,  // 0: wave file, 1: looping
        loop_mode,  // 0: normal, 1: pingpong, 2: up+down
        loop_state; // 0: starting, 1: loop_start -> loop_end, 2: loop_end -> loop_start
  bool wav_stop,
       i_am_playing,
       stop_requested;
  Uint8 *wav_buffer;
  float *hamming,
        mid_C,        // midi nr for middle-C, default 48
        spect_thresh, // spectrum-line threshold
        ind_f,        // index shortbuf[]
        freq_mult;    // play frequency multiplier
  Arr<SpectData> spectdata;
  short *shortbuf;  // contents of the wav file
  char *wavepoints; // idem if small number of points
  struct WavLin {
    char yhi,ylo;
  } *wavelines;
  Wav2Patch():
    buf_size(0),
    data_start(0),loop_start(0),loop_end(0),
    root_freq(0),
    basefreq_div(1),
    play_mode(0),
    loop_mode(0),
    loop_state(0),
    wav_stop(false),
    mid_C(48.0),
    spect_thresh(0.2),
    ind_f(0.),
    freq_mult(1.),
    wavepoints(0),wavelines(0) {
  }
  void init();
  void load_wav();
  void eval(float *buffer1,float *buffer2);
  void eval_wavf();
  void fill_wavelines();
  void sel_wave(int xpos,int butnr);
  void set_loop(int xpos,int butnr);
  void draw_fft_sample(int nr);
  void wav_listen(Uint8 *stream,int len);
  bool write_patch(FILE*);
  bool test_loop();
  void draw_waves(BgrWin* bgr);
  void draw_waveline(int xpos,Uint32 col);
  void draw_loopline(int xpos,Uint32 col);
} w2p;

static int max(int a,int b) { return a>=b ? a : b; }
static int min(int a,int b) { return a<=b ? a : b; }
static int minmax(int a, int x, int b) { return x>=b ? b : x<=a ? a : x; }
static int idiv(int a,int b) { return (2*a+b)/(2*b); }

SpectData::SpectData():
    base_freq(1.),
    maxmag(0),
    lst_sline(-1),
    nr_peaks(-1),
    skipped(false),
    bf_valid(false) {
}

void Wav2Patch::init() {
  the_xpos1=the_xpos2=the_fft_xpos3=the_fft_xpos1=the_fft_xpos2=-1;
  hamming=new float[buf_size];
  const float A = 0.54,
              B = 0.46;
  for (int i=0;i<buf_size;++i) {
    float ind = i*2*M_PI / (buf_size-1);
    hamming[i]=A-B*cos(ind);
  }
}

void Wav2Patch::load_wav() {
  SDL_AudioSpec wav_spec;
  Uint32 wav_length;

  if (!SDL_LoadWAV(infile, &wav_spec, &wav_buffer, &wav_length)) {
    printf("Problems: %s\n",SDL_GetError());
    exit(1);
  }
  if (wav_spec.channels!=1) {
    alert("nr channels = %d, should be 1",wav_spec.channels);
    exit(1);
  }
  nr_wav_samples=wav_length/2;
  nr_fft_samples=max(1,nr_wav_samples*2/buf_size-1); // times 2, minus 1, because buffers overlap
}

void index_of_peak(float* buf,int i,float& peak,float& magn,bool &valid) {
   float y0=buf[i-2],
         y1=buf[i-1],
         y2=buf[i];
/* y=a*x*x + b*x + c
   x=0,1,2
   y=0,y1,y2
   c=0

   y1=  a+  b  -> a=-y1 + y2/2
   y2=4*a+2*b     b=2*y1 - y2/2
*/
  if (y0>y1 || y1<y2) { valid=false; peak=magn=0.; return; }
  if (fabs(y0-y1)<0.01) { peak=0.5; magn=y1; return; }
  if (fabs(y1-y2)<0.01) { peak=1.5; magn=y1; return; }
  y1-=y0;
  y2-=y0;
  const float
    a=-y1+y2/2,
    b=2*y1-y2/2;
  peak=-b/(2*a);
  magn=y0-b*b/(4*a);
}

bool near_eq(float a,float b) {  // nearly equal?
  float div=a/b;
  return div<1.06 && div>0.94;
}

void Wav2Patch::eval(float *buffer,float *buffer2) { // buffer size = buf_size
  int i,
      lst_top=-1,
      maxval=0,
      maxmag=0,
      mag;
  float toploc[peaks_max],  // location of ampl tops
        topmagn[peaks_max];
  bool above_thresh=false;
  SpectData *sd;
  sd=&spectdata[called];
  for (i=0;i<buf_size;i+=2) {  // replace with abs value
    float re,im;
    re=buffer[i]; im=buffer[i+1];
    buffer[i/2]=hypot(re,im);
    if (buffer2) {
      re=buffer2[i]; im=buffer2[i+1];
      buffer[i/2]+=hypot(re,im); // add buffer2 to buffer
    }
    mag=lrint(buffer[i/2]);
    if (maxmag<mag) maxmag=mag; // find max value
  }
  sd->maxmag=maxmag;
  // now buf_size/2 buffer values valid
  if (maxmag<200) {
    sd->skipped=true;
    return;
  }
  for (i=2;i<buf_size/2;++i) { // start at i=2 because buffer[i-2] will be used
    mag=lrint(buffer[i]);
    if (mag>int(maxmag*spect_thresh)) {
      if (!above_thresh) {
        above_thresh=true;
        if (lst_top>=peaks_max-1) break;
        ++lst_top;
        maxval=0;
        for (;i<buf_size/2;++i) {
          mag=lrint(buffer[i]);
          if (maxval<=mag) {
            maxval=mag;
          }
          else { // 1 position past local top
            bool valid=true;
            float peak,ampl;
            index_of_peak(buffer,i,peak,ampl,valid);
            if (!valid) { --lst_top; break; }
            toploc[lst_top]=i-2+peak;
            topmagn[lst_top]=ampl;
            if (debug)
              printf("called=%d i=%2d toploc[%d]=%.1f (%.1f %.1f %.1f)\n",
                     called,i,lst_top,toploc[lst_top],buffer[i-2],buffer[i-1],buffer[i]);
            for (;i<buf_size/2;++i)
              if (buffer[i]<int(maxmag*spect_thresh) || buffer[i]<buffer[i+1]) // too small, or increasing again
                break;
            above_thresh=false;
            break;
          }
        }
      }
    }
    else
      above_thresh=false;
    }
    for (i=0;i<buf_size/2 && i<spect_size/2;++i) {
      mag=lrint(buffer[i]);
      if (mag && maxmag>10) {
        sd->spectlines[i]=mag * mult_spl / maxmag;
        sd->lst_sline=i;
      }
      else
        sd->spectlines[i]=0;
  }
  float tmp,
        freq=0.;
  bool valid=false;
  for (i=0;i<=lst_top;++i) {
    if (debug) printf("freq=%.1f valid=%d\n",freq,valid);
    if (toploc[i]>1.) {
      if (!valid) { valid=true; freq=toploc[i]; }
      else if (near_eq(toploc[i],1.5*freq)) tmp=toploc[i]/3;
      else if (near_eq(toploc[i],2*freq))   tmp=toploc[i]/2;
      else if (near_eq(toploc[i],3*freq))   tmp=toploc[i]/3;
      else if (near_eq(toploc[i],4*freq))   tmp=toploc[i]/4;
      else if (toploc[i]>4*freq) break;
    }
    else {
      valid=false; break;
    }
  }
  if (valid) {
    sd->base_freq=freq;
    sd->nr_peaks=lst_top;
    sd->bf_valid=true;
    for (i=0;i<=lst_top;++i) {
      sd->peaks[i]=toploc[i];
      sd->magn[i]=topmagn[i]/maxmag;
    }
  }
  else sd->nr_peaks=-1;
}

void Wav2Patch::fill_wavelines() {
  int n1,n2,n3,
      step=idiv(nr_wav_samples,ww_size),
      div=0x10000/wwh;
  wavelines=new WavLin[ww_size];

  for (n1=0;n1<ww_size;++n1) {
    int yhi=0,
        ylo=0;
    for (n2=0;n2<step;++n2) {
      n3=step*n1; // n1*nr_wav_samples/ww_size can overflow!
      if (n3+n2<nr_wav_samples) {
        int val=shortbuf[n3+n2]/div;
        if (yhi<val) yhi=val;
        if (ylo>val) ylo=val;
      }
    }
    wavelines[n1].yhi=yhi;
    wavelines[n1].ylo=ylo;
  }
}

void Wav2Patch::draw_waveline(int xpos,Uint32 col) {
  int x=xpos+gui->wave_win->tw_area.x,
      y=gui->wave_win->tw_area.y;
  if (col) {
    vlineColor(top_win->win,x,y,y+wwh-1,col);
    SDL_UpdateRects(top_win->win,1,rp(x,y,1,wwh));
  }
  else    // erase
    gui->wave_win->blit_upd(rp(xpos,0,1,wwh));
}

void draw_marks(SpectData &sd,int sl_dist) {
  int n,x;
  for (n=0;n<=sd.nr_peaks;++n) {
    x=lrint(sd.peaks[n] * sl_dist);
    trigonColor(gui->spectrum_win->win,x-3,0,x+3,0,x,5,0x00d000ff);
  }
  if (sd.bf_valid) {
    x=lrint(sd.base_freq * sl_dist);
    trigonColor(gui->spectrum_win->win,x-3,0,x+3,0,x,5,0xff0000ff);
  }
}

void draw_spectrum(BgrWin *spwin) {
  int n,
      bs=w2p.buf_size,
      nr=gui->f_c_value(),
      sl_dist= bs==512 ? 8 : bs==8192 ? 2 : 4;
  if (nr<w2p.nr_fft_samples) {
    static int ybot=spwin->area.h-20,
               ythresh=ybot-int(w2p.mult_spl*w2p.spect_thresh);
    int start=0,
        stop=spect_size;
    for (n=start;n<stop;n+=10)
      hlineColor(spwin->win,n,n+5,ythresh,0xff0000ff);
    SpectData &sd=w2p.spectdata[nr];
    for (n=0;n<spect_size/sl_dist && n<=sd.lst_sline;++n)
      if (sd.spectlines[n]>0)
        vlineColor(spwin->win,sl_dist*n,ybot,ybot-sd.spectlines[n],0xff);
    if (!sd.skipped)
      draw_marks(sd,sl_dist);
  }
  int dist_250hz=bs*250*sl_dist/SAMPLE_RATE;
  static int y=spwin->area.h-18;
  SDL_FillRect(spwin->win,rp(0,y,spect_size,18),cBackground);
  for (n=0;;++n) {
    int x=n*dist_250hz;
    if (x>=spect_size) break;
    vlineColor(spwin->win,x,y,y+6,0xff0000ff);
    if (n==0)
      draw_ttf->draw_string(spwin->win,"0",Point(0,y+5));
    else {
      char txt[20];
      if (n%4) {
        if (n%2==0) {
          sprintf(txt,"%.1f",n/4.);
          draw_ttf->draw_string(spwin->win,txt,Point(x-8,y+5));
        }
        else if (bs>=2048) {
          sprintf(txt,"%.2f",n/4.);
          draw_ttf->draw_string(spwin->win,txt,Point(x-8,y+5));
        }
      }
      else {
        sprintf(txt,"%dKHz",n/4);
        draw_ttf->draw_string(spwin->win,txt,Point(x-8,y+5));
      }
    }
  }
  spwin->blit_upd(0);
}

void incr_slval(Id,int x,int y,int butnr) {
  switch (butnr) {
    case SDL_BUTTON_LEFT:
    case SDL_BUTTON_RIGHT: {
      int nr=gui->f_c_value();
      if (butnr==SDL_BUTTON_LEFT) { if (nr>0) --nr; else break; }
      else { if (nr<w2p.nr_fft_samples-1) ++nr; else break; }
      if (gui->course) {
        int val=nr/100;
        set_text(gui->course->text,"%d",val*100);
        gui->course->set_hsval(val,false,true);   // not fire, draw
      }
      gui->fine->set_hsval(nr%100,true,true); // fire, draw
    }
    break;
  }
}

void Gui::draw_info(int fft_nr) {
  info->reset();
  char buf[10][50];
  char (*txt)[50]=buf;
  snprintf(*txt++,50,"%s",infile);
  snprintf(*txt++,50,"fft-sample %d",fft_nr);

  SpectData *sd=&w2p.spectdata[fft_nr];
  w2p.root_freq=int(sd->base_freq*1024)*SAMPLE_RATE/w2p.buf_size; // used by write_patch()
  snprintf(*txt++,50,"base freq %.1f",w2p.root_freq/1024.);
  sprintf(*txt++,"max peak %d",sd->maxmag);
  sprintf(*txt++,"fft window %d",w2p.buf_size);
  sprintf(*txt++,"loop %d,%d,%d",w2p.data_start,w2p.loop_start,w2p.loop_end);
  txt[0][0]=0;
  for (txt=buf;txt[0][0];++txt)
    info->add_text(txt[0],txt[1][0]==0);
}

void Wav2Patch::set_loop(int xpos,int butnr) {
  int wave_ind=min(gui->f_c_value() * buf_size/2 + xpos*buf_size/fftw_size,nr_wav_samples-1);
  switch (butnr) {
    case SDL_BUTTON_LEFT:
      data_start=wave_ind;
      if (the_fft_xpos3>=0)
        gui->fft_win->blit_upd(rp(the_fft_xpos3,0,1,wwh));
      the_fft_xpos3=xpos;
      draw_loopline(xpos,0x0000ffff);
      break;
    case SDL_BUTTON_MIDDLE:
      loop_start=wave_ind;
      if (the_fft_xpos1>=0)
        gui->fft_win->blit_upd(rp(the_fft_xpos1,0,1,wwh));
      the_fft_xpos1=xpos;
      draw_loopline(xpos,0xff);
      break;
    case SDL_BUTTON_RIGHT:
      loop_end=wave_ind;
      if (the_fft_xpos2>=0)
        gui->fft_win->blit_upd(rp(the_fft_xpos2,0,1,wwh));
      the_fft_xpos2=xpos;
      draw_loopline(xpos,0xff0000ff);
      break;
  }
  gui->draw_info(gui->f_c_value());
}

void Wav2Patch::sel_wave(int xpos,int butnr) {
  if (butnr==SDL_BUTTON_LEFT) {
    int fft_nr=min(nr_fft_samples-1,idiv(xpos*nr_fft_samples,ww_size));
    if (gui->course) {
      int val=fft_nr/100;
      set_text(gui->course->text,"%d",val*100);
      gui->course->set_hsval(val,false,true);   // not fire, draw
    }
    gui->fine->set_hsval(fft_nr%100,true,true); // fire, draw
  }
}

void Wav2Patch::eval_wavf() {
  int n;
  float *buffer1=new float[buf_size];
  short *sbuf1=new short[buf_size];
  spectdata.init(nr_fft_samples);
  for (n=0;n<nr_fft_samples;++n)
    spectdata[n].spectlines.init(min(buf_size/2,spect_size/2));
  shortbuf=reinterpret_cast<short*>(wav_buffer);
  // anti-alias:
  // for (n=0;n<nr_wav_samples-3;++n) shortbuf[n]=(shortbuf[n]+2*shortbuf[n+1]+2*shortbuf[n+2]+shortbuf[n+3])/6;
  int step=nr_wav_samples/ww_size;
  if (step>20)
    fill_wavelines();
  else {
    int div=0x10000/wwh;
    wavepoints=new char[ww_size];
    for (int i=0;i<ww_size;++i) wavepoints[i]=shortbuf[i*step] / div + wwh/2;
  }
  int interval=buf_size;
  for (called=0;called<nr_fft_samples;++called) {
    short *sbuf=shortbuf+called*interval/2; // sbuf overlaps half of preceeding sbuf
    for (n=0;n<buf_size;++n)
      sbuf1[n]= n<nr_wav_samples ? sbuf[n] : 0;
    for (n=0;n<buf_size;++n)
      buffer1[n]=sbuf1[n]*hamming[n];
    rfft(buffer1,buf_size/2,true);
    eval(buffer1,0);
  }
}

void draw_lines(WinBase* wb,char *pts,int size,Uint32 col) {
  for (int i=0;i<size-1;++i) {
    lineColor(wb->win,i,pts[i],i+1,pts[i+1],col);
  }
}

static void draw_waves(BgrWin* bgr) { w2p.draw_waves(bgr); }

void Wav2Patch::draw_waves(BgrWin* wwin) {
  wwin->clear();
  if (wavepoints)
    draw_lines(wwin,wavepoints,ww_size,rgbWave);
  else {
    const int mid1=wwh/2;
    for (int xpos=0;xpos<ww_size;++xpos) {
      WavLin& wl=wavelines[xpos];
      if (wl.yhi>0 || wl.ylo<0)
        vlineColor(wwin->win,xpos,min(wl.yhi+mid1,wwh-1),max(wl.ylo+mid1,0),rgbWave);
      else
        pixelColor(wwin->win,xpos,mid1,rgbWave);
    }
  }
}

void draw_fft_sample(BgrWin*) { w2p.draw_fft_sample(gui->f_c_value()); }

void Wav2Patch::draw_loopline(int xpos,Uint32 col) {
  static Point pt(gui->fft_win->tw_area.x,gui->fft_win->tw_area.y);
  vlineColor(top_win->win,xpos+pt.x,pt.y,wwh-1+pt.y,col);
  SDL_UpdateRects(top_win->win,1,rp(xpos+pt.x,pt.y,1,wwh));
}

void Wav2Patch::draw_fft_sample(int nr) {
  static int mid=wwh/2,
             div=0x10000/wwh;
  int n;
  short *sbuf=shortbuf+nr*buf_size/2; // buffers overlap
  char points[fftw_size];
  for (n=0;n<fftw_size;++n) {
    int ind=n*buf_size/fftw_size;
    if (ind > nr_wav_samples) break;
    points[n]=sbuf[ind]/div+mid;
  }
  draw_lines(gui->fft_win,points,n,rgbWave);
  hlineColor(gui->fft_win->win,0,fftw_size-1,mid,0xa0a0a0ff);
  gui->fft_win->blit_upd(0);
  int xpos;
  xpos=(loop_start - nr * buf_size/2) * fftw_size / buf_size;
  if (xpos>0 && xpos<fftw_size) { the_fft_xpos1=xpos; draw_loopline(xpos,0xff); }
  xpos=(loop_end - nr * buf_size/2) * fftw_size / buf_size;
  if (xpos>0 && xpos<fftw_size) { the_fft_xpos2=xpos; draw_loopline(xpos,0xff0000ff); }
  xpos=(data_start - nr * buf_size/2) * fftw_size / buf_size;
  if (xpos>0 && xpos<fftw_size) { the_fft_xpos2=xpos; draw_loopline(xpos,0x0000ffff); }
}

void select_wave(Id,int xpos,int ypos,int butnr) { w2p.sel_wave(xpos,butnr); }

void set_loop(Id,int xpos,int ypos,int butnr) { w2p.set_loop(xpos,butnr); }

int Gui::f_c_value() {
  int val=fine->value();
  if (course) val+=100*course->value();
  return min(w2p.nr_fft_samples-1,val);
}

void fine_course_cmd() {
  int fcval=gui->f_c_value();
  gui->spectrum_win->clear();
  draw_spectrum(gui->spectrum_win);
  gui->draw_info(fcval);
  int xpos=int(float(fcval) * w2p.buf_size / 2 * ww_size / w2p.nr_wav_samples); // float arithmetic
  if (w2p.the_xpos1!=xpos) {
    if (w2p.the_xpos1>=0)
      w2p.draw_waveline(w2p.the_xpos1,0);
    w2p.the_xpos1=xpos;
    w2p.draw_waveline(xpos,0xff0000ff);
  }
  gui->fft_win->clear();
  w2p.draw_fft_sample(fcval);
}

void fine_cmd(HSlider* hsl,int fire,bool) { set_text(hsl->text,"%d",hsl->value()); fine_course_cmd(); }
void course_cmd(HSlider* hsl,int fire,bool) { set_text(hsl->text,"%d",hsl->value()*100); fine_course_cmd(); }

void wave_listen(void*,Uint8 *stream, int len) { w2p.wav_listen(stream,len); }

void init_audio() {
  SDL_AudioSpec *ask=new SDL_AudioSpec,
                *got=new SDL_AudioSpec;
  ask->freq=SAMPLE_RATE;
  ask->format=AUDIO_S16SYS;
  ask->channels=2;
  ask->samples=1024;
  ask->callback=wave_listen;
  ask->userdata=0;
  if ( SDL_OpenAudio(ask, got) < 0 ) {
     alert("Couldn't open audio: %s",SDL_GetError());
     exit(1);
  }
  //printf("samplerate=%d samples=%d channels=%d format=%d (AUDIO_S16LSB=%d) size=%d\n",
  //     got->freq,got->samples,got->channels,got->format,AUDIO_S16LSB,got->size);
  SDL_PauseAudio(0);
}

void Wav2Patch::wav_listen(Uint8 *stream,int len) {
  short *buffer=reinterpret_cast<short*>(stream);
  int i;
  SDL_LockAudio(); 
  int xpos=min(ww_size,int(ind_f * ww_size / nr_wav_samples)); // float arithmetic, else overflow
  // int xpos=min(ww_size,int(ind_f) * ww_size / nr_wav_samples);
  if (xpos!=the_xpos2) {
    send_uev('updi',the_xpos2,xpos);
    the_xpos2=xpos;
  }
  if (play_mode!=0 && !test_loop()) {
    wav_stop=true;
    return;
  }
  for (i=0;i<len/4;++i) {
    int val=0,
        ind=max(0,int(ind_f));
    if (play_mode==0) {  // normal
      if (ind < nr_wav_samples)
        val=shortbuf[ind];
      else
        wav_stop=true;
    }
    else {
      if (loop_mode==2 && loop_state>0)
        val=(shortbuf[ind]+shortbuf[loop_end+loop_start-ind]) * 0.7;
      else
        val=shortbuf[ind];
    }
    buffer[2*i]=buffer[2*i+1]=val;
    int range=loop_end-loop_start;
    if (play_mode==0) {
      ind_f += freq_mult;
    }
    else {
      switch (loop_mode) {
        case 0: // normal
        case 2: // up+down
          ind_f += freq_mult;
          if (ind_f > loop_end) {
            ind_f -= range;
            if (loop_state==0) loop_state=1;
          }
          break;
        case 1: // pingpong
          if (loop_state==2) {
            ind_f -= freq_mult;
            if (ind_f < loop_start) loop_state=1;
          }
          else {
            ind_f += freq_mult;
            if (ind_f > loop_end) loop_state=2;
            else if (loop_state==0 && ind_f > loop_start) loop_state=1;
          }
          break;
       }
     }
  }
  SDL_UnlockAudio();
}

bool Wav2Patch::test_loop() {
  if (loop_end==0 || loop_end>=nr_wav_samples) { alert("loop end = %d",loop_end); return false; }
  if (loop_start<data_start) { alert("loop start = %d, data start = %d",loop_start,data_start); return false; }
  if (loop_end<loop_start+5) { alert("loop start = %d, loop end = %d",loop_start,loop_end); return false; }
  return true;
}

bool Wav2Patch::write_patch(FILE* out) {
  if (root_freq<20000 || root_freq>20000000) { alert("root freq = %d",root_freq); return false; }
  Uint8 dum8;
  char comment[100];
  snprintf(comment,100,"%s loop:%d,%d,%d",infile,data_start,loop_start,loop_end);
  if (basefreq_div!=1) { int sl=strlen(comment); snprintf(comment+sl,100-sl," basefreq-div:%d",basefreq_div); }
  if (fwrite("BB-PATCH",8,1,out)!=1 ||
      (dum8=2, fwrite(&dum8,1,1,out)!=1) || // format
      fwrite(comment,100,1,out)!=1 ||
      (dum8=1, fwrite(&dum8,1,1,out)!=1)    // nr samples
     ) {
    alert("write_patch: initialization failed");
    return false;
  }
  Uint16 dum16;
  Uint32 dum32;
  const int tail=10;
  Uint32 data_size=min(nr_wav_samples,loop_end-data_start+tail),
         data_size1=data_size,
         loop_start1=loop_start-data_start,
         loop_end1=loop_end-data_start,
         root_freq1=root_freq/basefreq_div;
  short *shortbuf1=shortbuf+data_start;
  if (debug) printf("d_size=%u l_start=%u l_end=%u root_f=%u\n",
    data_size1,loop_start1,loop_end1,root_freq1); 
  if ((dum32=data_size1, fwrite(&dum32,4,1,out)!=1) ||
      (dum32=loop_start1, fwrite(&dum32,4,1,out)!=1) ||
      (dum32=loop_end1, fwrite(&dum32,4,1,out)!=1) ||
      fwrite(&loop_mode,1,1,out)!=1 ||
      (dum16=SAMPLE_RATE,fwrite(&dum16,2,1,out)!=1) ||
      fwrite(&root_freq1,4,1,out)!=1) {
    alert("write_patch: write problem");
    return false;
  }
  if (fwrite(shortbuf1,data_size1*2,1,out)!=1) {
    alert("write_patch: problem writing buffer");
    return false;
  }
  return true;
}

void arrow(SDL_Surface* win,int id2,int) {
  int x,y;
  switch (id2) {
    case 2:
      x=6; y=10;
      trigonColor(win,x,y-5,x+8,y,x,y+5,0xff);
      break;
    case 0:
      x=10; y=7;
      filledTrigonColor(win,x,y-4,x,y+4,x-6,y,0xff);
      break;
    case 1:
      x=4; y=7;
      filledTrigonColor(win,x,y-4,x+6,y,x,y+4,0xff);
      break;
   }
}

void square(SDL_Surface* win,int,int) {
  rectangleColor(win,6,6,12,13,0xff0000ff);
}

int play_threadfun(void* data) {
  switch (w2p.play_mode) {
    case 0:  // play mode: input wave
      w2p.ind_f=gui->f_c_value() * w2p.buf_size/2;
      break;
    default:  // play mode: looped patch
      w2p.ind_f=w2p.data_start;
      if (!w2p.test_loop()) goto end;
  }
  if (SDL_GetAudioStatus()!=SDL_AUDIO_PLAYING)
    init_audio();
  w2p.wav_stop=false;
  while (!w2p.wav_stop && !w2p.stop_requested)
    SDL_Delay(100);
  SDL_CloseAudio();
  end:
  w2p.i_am_playing=false;
  send_uev('arro');  // for play button
  return 0;
}

void play_cmd(Id) {
  if (!w2p.i_am_playing) {
    w2p.i_am_playing=true;
    w2p.stop_requested=false;
    gui->play->label.draw_cmd=square;
    play_thread=SDL_CreateThread(play_threadfun, 0); // if exit: w2p.i_am_playing = false
  }
  else
    w2p.stop_requested=true;
}

void dial_cmd(const char* text,int cmd_id) {
  FILE *patch=fopen(text,"w");
  if (!patch) { alert("patch file '%s' not opened",text); return; }
  patch_file=strdup(text);
  if (w2p.write_patch(patch))
    gui->dialog->dialog_label("patch file");
  fclose(patch);
}

void button_cmd(Id id) {
  switch (id.id1) {
    case 'prha': {
        if (w2p.i_am_playing) return;
        int fcv=gui->f_c_value();
        SpectData &sd=w2p.spectdata[fcv];
        puts("Spectrum:");
        for (int i=0;i<=sd.nr_peaks;++i)
          printf("  %.2f,%.2f\n",sd.peaks[i]/sd.base_freq,sd.magn[i]);
      }
      break;
    case 'wrpa':
      gui->dialog->dialog_label("patch name:",cAlert);
      gui->dialog->dialog_def(patch_file,dial_cmd,0);
      break;
    case 'ok':
      gui->dialog->dok();
      break;
    case 'sh': // shift end-line
      if (id.id2==0) {
        if (w2p.loop_end>w2p.loop_start+5) --w2p.loop_end;
      }
      else if (id.id2==1) {
        if (w2p.loop_end<w2p.nr_wav_samples-1) ++w2p.loop_end;
      }
      gui->draw_info(gui->f_c_value());
      break;
    default: alert("button cmd?");
  }
}

void hslider_cmd(HSlider *sl,int fire,bool rel) {
  switch (sl->id.id1) {
    case 'fmul': {
        const float arr[9]={ 0.25,0.35,0.5,0.7,1,1.4,2,2.8,4 };
        w2p.freq_mult=arr[sl->value()];
        set_text(sl->text,"%.2f",w2p.freq_mult);
      }
      break;
    case 'bfmu': {
        const float arr[4]={ 4,3,2,1 };
        w2p.basefreq_div=arr[sl->value()];
        set_text(sl->text,"1/%d",w2p.basefreq_div);
      }
      break;
  }
}

void draw_topw() {
  top_win->clear();
  top_win->border(gui->spectrum_win,1);
  top_win->border(gui->wave_win,1);
  top_win->border(gui->fft_win,1);
}

void rbw_cmd(Id id,int nr,int fire) {
  switch (id.id1) {
    case 'plmo': w2p.play_mode=nr; w2p.loop_state=0; break;
    case 'lomo': w2p.loop_mode=nr; w2p.loop_state=0; w2p.ind_f=w2p.loop_start; break;
    default: alert("rbw_cmd?");
  }
}

Gui::Gui() {
  int y=16;
  spectrum_win=new BgrWin(top_win,Rect(2,y,spect_size,100),"spectrum",draw_spectrum,incr_slval,0,0,cLightYellow);
  w2p.mult_spl=spectrum_win->area.h-26;

  y+=124;
  int nr=w2p.nr_fft_samples/100;
  if (nr>0) {
    fine=new HSlider(top_win,1,Rect(2,y,spect_size,0),0,99,"fft sample - fine",fine_cmd);
    fine->lab_left="0"; fine->lab_right="99";
    char *txt=new char[10];
    sprintf(txt,"%d",nr);
    course=new HSlider(top_win,1,Rect(2,y+42,minmax(60,nr*10+10,spect_size),0),0,nr,"fft sample - course",course_cmd);
    course->lab_left="0"; course->lab_right=txt;
  }
  else {
    nr=w2p.nr_fft_samples;
    char *txt=new char[10];
    sprintf(txt,"%d",nr-1);
    fine=new HSlider(top_win,1,Rect(2,y,min(nr*10+10,spect_size),0),0,nr-1,"FFT SAMPLE",fine_cmd);
    fine->lab_left="0"; fine->lab_right=txt;
    if (nr==1) fine->hidden=true; // in this case slider not useful
    course=0;
  }

  y+=88;
  w2p.wwh=80;
  wave_win=new BgrWin(top_win,Rect(2,y,ww_size,w2p.wwh),"wave file",draw_waves,select_wave,0,0,cWaveBgr);

  y+=102;
  info=new TextWin(top_win,Style(1,0),Rect(2,y,126,0),6,"info");
  info->bgcol=cLightGrey;

  new Button(top_win,Style(1,1),Rect(690,y-18,0,0),arrow,button_cmd,Id('sh',0));
  new Button(top_win,Style(1,1),Rect(710,y-18,140,0),Label("shift end-line",arrow),button_cmd,Id('sh',1));

  fft_win=new BgrWin(top_win,Rect(132,y,fftw_size,w2p.wwh),"fft window",draw_fft_sample,set_loop,0,0,cWaveBgr);

  y+=w2p.wwh+15;
  play=new Button(top_win,Style(0,1),Rect(10,y,20,20),arrow,play_cmd,Id('play',2));

  playmode=new RButWin(top_win,1,Rect(50,y+10,90,2*TDIST),"play mode",false,rbw_cmd,'plmo');
  playmode->add_rbut("wave file");
  playmode->add_rbut("looped patch");

  loopmode=new RButWin(top_win,1,Rect(150,y+6,90,3*TDIST),"loop mode",false,rbw_cmd,'lomo');
  loopmode->add_rbut("normal");
  loopmode->add_rbut("pingpong");
  loopmode->add_rbut("up+down");

  freq_mult=new HSlider(top_win,1,Rect(230,y+6,90,0),0,8,"freq mult",hslider_cmd,'fmul');
  freq_mult->value()=4;
  set_text(freq_mult->text,"1.0");

  basefreq_div=new HSlider(top_win,1,Rect(328,y+6,60,0),0,3,"basefreq div",hslider_cmd,'bfmu');
  basefreq_div->value()=3;
  set_text(basefreq_div->text,"1/1");

  new Button(top_win,1,Rect(410,y,140,0),"print spectrum",button_cmd,'prha');
  new Button(top_win,1,Rect(410,y+18,140,0),"write patch...",button_cmd,'wrpa');

  dialog=new DialogWin(top_win,Rect(520,y,150,0));
  new Button(top_win,0,Rect(676,y+14,18,0),"ok",button_cmd,'ok');
}
/*
void handle_key_event(SDL_keysym *key,bool down) {
  if (!down) return;
  switch (key->sym) {
    case SDLK_s:
      break;
    default:
      alert("unexpected key");
  }
}
*/
void handle_user_event(int cmd,int par1,int par2) {
  switch (cmd) {
    case 'updi':  // update black select line in wave display (par1 = previous xpos, par2 = xpos)
      if (par1>=0 && par1!=w2p.the_xpos1) // the_xpos1 = the red line
        w2p.draw_waveline(par1,0);
      if (par2!=w2p.the_xpos1) w2p.draw_waveline(par2,0xff);
      break;
    case 'arro':
      gui->play->label.draw_cmd=arrow;
      gui->play->draw_blit_upd();
      break;
    case 'redl':
      if (par1>0) w2p.draw_waveline(par1,0xff0000ff);
      break;
    default: alert("handle_uev: cmd=%d",cmd);
  }
}

int main(int argc, char ** argv) {
  int nr;
  for (int an=1;an<argc;++an) {
    if (argv[an][0]=='-') {
      if (!strcmp(argv[an],"-h")) {
        puts("Usage:");
        puts("  wav2patch [options...] <wave file>");
        puts("Options:");
        puts("  -h           : print usage info and exit");
        puts("  -db          : debug");
        puts("  -win <nr>    : fft window = <nr> samples (between 512 and 8192, default: 2048)");
        puts("  -th <value>  : threshold level for spectrum lines, default: 0.2");
        exit(0);
      }
      if (!strcmp(argv[an],"-db"))
        debug=true;
      else if (!strcmp(argv[an],"-win")) {
        if (++an>=argc) err("-win parameter missing");
        nr=atoi(argv[an]);
        if (nr<500 || nr>8192) err("-win option = %d, expected 512 - 8192",nr);
        for (int shift=0;shift<5;++shift) {
          if (nr<(512<<shift)) {
            w2p.buf_size=512<<shift; break;
          }
        }
      }
      else if (!strcmp(argv[an],"-th")) {
        if (++an>=argc) err("-th parameter missing");
        float th=atof(argv[an]);
        if (th<0.019 || th>1.0) err("bad -th value %.2f (expected 0.02 - 1.0)",th);
        w2p.spect_thresh=th;
      }
      else
        err("Unexpected option %s (use -h for help)",argv[an]);
    }
    else infile=argv[an];
  }
  if (!infile) err("no wave file (use -h for help)");

  if (!w2p.buf_size) w2p.buf_size=2048;
  w2p.init();
  w2p.load_wav();

  top_win=new TopWin("Wav2Patch",Rect(100,0,806,478),SDL_INIT_VIDEO|SDL_INIT_AUDIO,0,draw_topw);
  top_win->bgcol=cForeground;
  handle_uev=handle_user_event;
  //handle_kev=handle_key_event;
  cGreen=calc_color(0x00c000);
  cWave=calc_color(0x009000);
  rgbWave=0x009000ff;
  cWaveBgr=calc_color(0xd0e0f0); // sky blue
  rgbWaveBgr=0xd0e0f0ff;
  cLightYellow=calc_color(0xffffd0);
  cLightGrey=calc_color(0xe0e0e0);
  cAlert=calc_color(0xffa0a0);
  gui=new Gui();
  w2p.eval_wavf();
  fine_course_cmd(); // to draw all windows
  send_uev('redl',w2p.the_xpos1); // to draw red line, at start of get_events()
  get_events();
  SDL_FreeWAV(w2p.wav_buffer);
  return 0;
}
