#include "snd.h"

#define ENV_BUFFER_SIZE 128
static int env_buffer_size = 0;
static float *env_buffer = NULL;
static char env_white_space[5]={' ','(',')','\t','\''};
static char expr_buf[128];

env *free_env(env *e)
{
  if (e)
    {
      if (e->data) free(e->data);
      free(e);
    }
  return(NULL);
}

env *copy_env(env *e)
{
  env *ne;
  int i;
  if (e)
    {
      ne = (env *)calloc(1,sizeof(env));
      ne->pts = e->pts;
      ne->data_size = e->pts*2;
      ne->data = (float *)calloc(e->pts*2,sizeof(float));
      for (i=0;i<e->pts*2;i++) ne->data[i] = e->data[i];
      ne->base = e->base; /* was 1.0? 20-Aug-98 */
      return(ne);
    }
  return(NULL);
}

char *env_to_string(env *e)
{
  int i,j;
  char *news;
  if (e)
    {
      news = (char *)calloc(4 + (e->pts*2*8),sizeof(char));
      news[0]='\'';
      news[1]='(';
      news[2]='\0';
      for (i=0,j=0;i<e->pts;i++,j+=2)
	{
	  sprintf(expr_buf,"%.3f %.3f ",e->data[j],e->data[j+1]);
	  strcat(news,expr_buf);
	}
      strcat(news,")");
    }
  else
    {
      news = copy_string("nil");
    }
  return(news);
}

env *make_envelope(float *env_buffer, int len)
{
  env *e;
  int i,flen;
  if (len == 2) flen = 4; else flen = len;
  e = (env *)calloc(1,sizeof(env));
  e->data = (float *)calloc(flen,sizeof(float));
  e->data_size = flen;
  e->pts = flen/2;
  for (i=0;i<len;i++) e->data[i] = env_buffer[i];
  if ((flen == 4) && (len == 2)) {e->data[2] = e->data[0]+1.0; e->data[3] = e->data[1];} /* fixup degenerate envelope */
  e->base = 1.0;
  return(e);
}

env *scan_envelope(char *str)
{
  char *tok;
  int i;
  float f;
  if ((str) && (*str))
    {
      i = 0;
      if (env_buffer_size == 0)
	{
	  env_buffer_size = ENV_BUFFER_SIZE;
	  env_buffer = (float *)calloc(ENV_BUFFER_SIZE,sizeof(float));
	}
      if ((*str) == '\'') str++;
      if ((*str) == '(') str++;
      tok = strtok(str,env_white_space);
      while (tok)
	{
	  sscanf(tok,"%f",&f);
	  env_buffer[i]=f;
	  i++;
	  if (i == env_buffer_size)
	    {
	      env_buffer_size *= 2;
	      env_buffer = (float *)realloc(env_buffer,env_buffer_size * sizeof(float));
	    }
	  tok = strtok(NULL,env_white_space);
	}
      if ((i==0) || (i&1)) return(NULL); /* no data or odd length ? */
      return(make_envelope(env_buffer,i));
    }
  return(NULL);
}

static int env_ok(env* e)
{
  int i,j;
  /* check for x axis increasing */
  for (i=0,j=0;i<e->pts-1;i++,j+=2)
    {
      if (e->data[j] >= e->data[j+2]) return(j+2);
    }
  return(0);
}

static void report_env_error(snd_info *sp, env* e, int err)
{
  float diff;
  int digits;
  diff = e->data[err-2] - e->data[err];
  if (diff >= 1.0) digits = 0; else {if (diff >= .1) digits = 1; else {if (diff >= .01) digits = 2; else digits = 3;}}
  sprintf(expr_buf,snd_string_x_axis_not_increasing,digits,e->data[err-2],(diff == 0.0) ? "=" : ">",digits,e->data[err]);
  report_in_minibuffer(sp,expr_buf);
}

static void just_clear_minibuffer(snd_info *sp)
{
  clear_minibuffer_prompt(sp);
  expr_buf[0] = '\0';
  text_set_string(snd_widget(sp,W_snd_info),expr_buf);
}

env *scan_envelope_and_report_error(snd_info *sp, char *str, int *err)
{
  /* every envelope access goes through this procedure first */
  env *e = NULL;
  (*err) = 0;
  if ((str) && (*str))
    {
      if (isalpha(str[0]))
	e = name_to_env(str);
      else e = scan_envelope(str);
      if (!e)
	{
	  (*err) = -1;
	  if (sp) 
	    if (isalpha(str[0]))
	      {
		sprintf(expr_buf,snd_string_cant_find,str);
		report_in_minibuffer(sp,expr_buf);
	      }
	    else report_in_minibuffer(sp,snd_string_odd_length_env);
	  return(NULL);
	}
      else
	{
	  (*err) = env_ok(e);
	  if ((sp) && (*err)) report_env_error(sp,e,(*err));
	}
      if ((*err) == 0)
	{
	  if (sp) just_clear_minibuffer(sp);
	  return(e); 
	}
      else 
	{
	  if (e) free_env(e);
	  return(NULL);
	}
    }
  else return(NULL);
}

float *magify_env(env *e, int dur, float scaler)
{ /* from magify-seg, mus.lisp, with less worry about special cases */
  int i,j,curx;
  float x0,y0,x1,y1,xmag;
  float *result;
  if (!e) return(NULL);
  x1 = e->data[0];
  xmag = (float)dur/(float)(e->data[e->pts*2 - 2] - x1);
  y1 = e->data[1];
  result = (float *)calloc(e->pts*2-2,sizeof(float));
  for (j=0,i=2;i<e->pts*2;i+=2,j+=2)
    {
      x0 = x1;
      x1 = e->data[i];
      y0 = y1;
      y1 = e->data[i+1];
      curx = xmag*(x1-x0)+0.5;
      if (curx < 1) curx = 1;
      result[j] = curx;
      if (y0 == y1) result[j+1]=0.0;
      else result[j+1] = scaler*(y1-y0)/(float)curx;
    }
  return(result);
}

double *dmagify_env(env *e, int dur, float scaler)
{ /* same as magify_env but using doubles for extreme durations */
  int i,j,curx;
  double x0,y0,x1,y1,xmag;
  double *result;
  if (!e) return(NULL);
  x1 = e->data[0];
  xmag = (double)dur/(double)(e->data[e->pts*2 - 2] - x1);
  y1 = e->data[1];
  result = (double *)calloc(e->pts*2-2,sizeof(double));
  for (j=0,i=2;i<e->pts*2;i+=2,j+=2)
    {
      x0 = x1;
      x1 = e->data[i];
      y0 = y1;
      y1 = e->data[i+1];
      curx = xmag*(x1-x0)+0.5;
      if (curx < 1) curx = 1;
      result[j] = curx;
      if (y0 == y1) result[j+1]=0.0;
      else result[j+1] = scaler*(y1-y0)/(double)curx;
    }
  return(result);
}

float *fixup_exp_env(env *e, float *offset, float *scaler, float base)
{
  float min_y,max_y,val = 0.0,tmp = 0.0,b,b1;
  int flat,len,i;
  float *result = NULL;
  if (!e) return(NULL);
  if ((base <= 0.0) || (base == 1.0)) return(NULL);
  min_y = (*offset) + (*scaler) * e->data[1];
  max_y = min_y;
  b = 1.0 / log(base);
  b1 = base-1.0;
  len = e->pts*2;
  result = (float *)calloc(len,sizeof(float));
  result[0] = e->data[0];
  result[1] = min_y;
  for (i=2;i<len;i+=2)
    {
      tmp = (*offset) + (*scaler) * e->data[i+1];
      result[i] = e->data[i];
      result[i+1] = tmp;
      if (tmp<min_y) min_y = tmp;
      if (tmp>max_y) max_y = tmp;
    }
  flat = (min_y == max_y);
  if (!flat) val = 1.0 / (max_y - min_y);
  for (i=1;i<len;i+=2)
    {
      if (flat) 
	tmp = 1.0;
      else tmp = val * (result[i] - min_y);
      result[i] = log(1.0+(tmp*b1)) * b;
    }
  (*scaler) = (max_y - min_y) / b1;
  (*offset) = min_y;
  return(result);
}

