////////////////////////////////
// Signal Processing Functions

// basic signals

MR4TH_SYMBOL_STATIC DSP_Signal*
dsp_new_ptr(Arena *arena, F32 *samples, U64 sample_count, F32 samples_per_second){
  DSP_Signal *signal = push_array(arena, DSP_Signal, 1);
  signal->samples = samples;
  signal->sample_count = sample_count;
  signal->samples_per_second = samples_per_second;
  signal->delta_t = 1.f/samples_per_second;
  return(signal);
}

MR4TH_SYMBOL_STATIC DSP_Signal*
dsp_new(Arena *arena, U64 sample_count, F32 samples_per_second){
  F32 *samples = push_array(arena, F32, sample_count);
  DSP_Signal *signal = dsp_new_ptr(arena, samples, sample_count, samples_per_second);
  return(signal);
}

MR4TH_SYMBOL_STATIC DSP_Signal*
dsp_new_from_buffer(Arena *arena, BOP_Data *buffer, F32 sample_per_second){
  Assert(buffer->el_size == 4);
  
  DSP_Signal *signal = dsp_new_ptr(arena, (F32*)buffer->data, buffer->el_count, sample_per_second);
  return(signal);
}

MR4TH_SYMBOL_STATIC DSP_Signal*
dsp_copy(Arena *arena, DSP_Signal *signal){
  DSP_Signal *result = push_array(arena, DSP_Signal, 1);
  F32 *samples = push_array(arena, F32, signal->sample_count);
  MemoryCopy(samples, signal->samples, sizeof(*samples)*signal->sample_count);
  
  result->samples = samples;
  result->sample_count = signal->sample_count;
  result->samples_per_second = signal->samples_per_second;
  result->delta_t = signal->delta_t;
  return(result);
}

// resampling

MR4TH_SYMBOL_STATIC DSP_Signal*
dsp_resample(Arena *arena, DSP_Signal *signal, F32 dst_sps){
  DSP_Signal *result = 0;
  
  if (dst_sps == signal->samples_per_second){
    result = dsp_copy(arena, signal);
  }
  else{
    U64 resample_count = dsp_resample_count(signal->sample_count,
                                            signal->samples_per_second,
                                            dst_sps);
    F32 *samples = push_array(arena, F32, resample_count);
    dsp_resample_raw(samples, resample_count, signal->samples, signal->sample_count);
    
    result = dsp_new_ptr(arena, samples, resample_count, dst_sps);
  }
  
  return(result);
}

MR4TH_SYMBOL_STATIC U64
dsp_resample_count(U64 src_count, F32 src_sps, F32 dst_sps){
  F32 dst_count = src_count*dst_sps/src_sps;
  U64 result = (U64)(ceil_F32(dst_count));
  return(result);
}

MR4TH_SYMBOL_STATIC void
dsp_resample_raw(F32 *dst, U64 dst_count, F32 *src, U64 src_count){
  F32 ratio = (F32)src_count/(F32)dst_count;
  for (U64 di = 0; di < dst_count; di += 1){
    F32 st = (F32)(di)*ratio;
    U64 si = (U64)st;
    
    F32 st_frac = frac_F32(st);
    F32 l = 0.f;
    
    F32 s0 = src[si];
    F32 s1 = s0;
    if (st_frac < 0.5f && si > 0){
      s1 = src[si - 1];
      l = 0.5f + st_frac;
    }
    else if (st_frac >= 0.5f && si + 1 < src_count){
      s1 = src[si + 1];
      l = 1.5f - st_frac;
    }
    
    dst[di] = s0*l + s1 *(1.f - l);
  }
}

// mixing

MR4TH_SYMBOL_STATIC void
dsp_add_inplace(DSP_Signal *dst, DSP_Signal *src){
  Assert(dst->samples_per_second == src->samples_per_second);
  Assert(dst->sample_count >= src->sample_count);
  {
    F32 *srcptr = src->samples;
    F32 *dstptr = dst->samples;
    U64 sample_count = src->sample_count;
    for (U64 i = 0; i < sample_count;
         i += 1, srcptr += 1, dstptr += 1){
      *dstptr += (*srcptr);
    }
  }
}

MR4TH_SYMBOL_STATIC DSP_Signal*
dsp_mix2(Arena *arena, DSP_Signal *s0, F32 a0, DSP_Signal *s1, F32 a1){
  Assert(s0->samples_per_second == s1->samples_per_second);
  
  // determine sample count
  U64 max_sample_count = Max(s0->sample_count, s1->sample_count);
  U64 min_sample_count = Min(s0->sample_count, s1->sample_count);
  
  // allocate output samples
  F32 *d = push_array(arena, F32, max_sample_count);
  
  // mix signals into output samples
  {
    F32 *sptr0 = s0->samples;
    F32 *sptr1 = s1->samples;
    F32 *dptr  = d;
    U64 i = 0;
    for (; i < min_sample_count;
         i += 1, sptr0 += 1, sptr1 += 1, dptr += 1){
      *dptr = (*sptr0)*a0 + (*sptr1)*a1;
    }
    if (i < s0->sample_count){
      for (; i < max_sample_count;
           i += 1, sptr0 += 1, dptr += 1){
        *dptr = (*sptr0)*a0;
      }
    }
    else{
      for (; i < max_sample_count;
           i += 1, sptr1 += 1, dptr += 1){
        *dptr = (*sptr1)*a0;
      }
    }
  }
  
  // return output signal
  DSP_Signal *result = dsp_new_ptr(arena, d, max_sample_count, s0->samples_per_second);
  return(result);
}

// filters

MR4TH_SYMBOL_STATIC void
dsp_low_pass(DSP_Signal *signal, F32 cutoff_hz){
  F32 alpha = dsp_low_pass_alpha(signal->delta_t, cutoff_hz);
  dsp_low_pass_raw(alpha, signal->samples, signal->sample_count);
}

MR4TH_SYMBOL_STATIC void
dsp_low_pass_raw(F32 alpha, F32 *samples, U64 count){
  // TODO(allen): low-level optimization
  // low pass:  Y = (1 - alpha)*[-1]Y + alpha*X
  
  F32 *ptr = samples;
  F32 *opl = samples + count;
  if (ptr < opl){
    F32 n_alpha = 1.f - alpha;
    F32 v = alpha*(*ptr);
    *ptr = v;
    for (; ptr < opl; ptr += 1){
      v = alpha*(*ptr) + n_alpha*v;
      *ptr = v;
    }
  }
}

MR4TH_SYMBOL_STATIC F32
dsp_low_pass_alpha(F32 rate_delta_t, F32 cutoff_hz){
  F32 rc = 1.f/(tau_F32*cutoff_hz);
  F32 alpha = rate_delta_t/(rate_delta_t + rc);
  return(alpha);
}

MR4TH_SYMBOL_STATIC void
dsp_high_pass(DSP_Signal *signal, F32 cutoff_hz){
  F32 alpha = dsp_high_pass_alpha(signal->delta_t, cutoff_hz);
  dsp_high_pass_raw(alpha, signal->samples, signal->sample_count);
}

MR4TH_SYMBOL_STATIC void
dsp_high_pass_raw(F32 alpha, F32 *samples, U64 count){
  // TODO(allen): low-level optimization
  // high pass: Y = alpha*([-1]Y + X - [-1]X)
  
  F32 *ptr = samples;
  F32 *opl = samples + count;
  if (ptr < opl){
    F32 d = 0;
    ptr += 1;
    for (; ptr < opl; ptr += 1){
      F32 x = *ptr;
      F32 y = alpha*(d + x);
      *ptr = y;
      d = y - x;
    }
  }
}

MR4TH_SYMBOL_STATIC F32
dsp_high_pass_alpha(F32 rate_delta_t, F32 cutoff_hz){
  F32 rc = 1.f/(tau_F32*cutoff_hz);
  F32 alpha = rc/(rc + rate_delta_t);
  return(alpha);
}

// fourier transform

MR4TH_SYMBOL_STATIC void
dsp_samples_from_signal_window(DSP_Signal *signal, U64 first, U64 count,
                               F32 **samples_out, U64 *sample_count_out){
  F32 *samples = signal->samples + first;
  U64 sample_count = count;
  if (first + count > signal->sample_count){
    if (first < signal->sample_count){
      sample_count = signal->sample_count - first;
    }
    else{
      sample_count = 0;
    }
  }
  *samples_out = samples;
  *sample_count_out = sample_count;
}

MR4TH_SYMBOL_STATIC DSP_Spectrum*
dsp_spectrum_from_samples(Arena *arena, F32 *samples, U64 sample_count, U64 count){
  U64 spectrum_count = (count + 1)/2;
  DSP_Spectrum *spectrum = push_array(arena, DSP_Spectrum, 1);
  F32 *buffer = push_array(arena, F32, spectrum_count*2);
  F32 *rl = buffer;
  F32 *im = buffer + spectrum_count;
  
  for (U64 i = 0; i < spectrum_count; i += 1){
    // X[i] = SUM(j) x[j]*e^(-i*2*pi*i*j/count)
    
    // pre-calculate: e^(-i*2*pi*i/count)
    //                Rl(e^(-i*2*pi*i/count)) = cos(-2*pi*i/count)
    //                Im(e^(-i*2*pi*i/count)) = sin(-2*pi*i/count)
    F32 unit_rl = cos_F32(-((F32)i)/count);
    F32 unit_im = sin_F32(-((F32)i)/count);
    
    F32 mul_accum_rl = 1.f;
    F32 mul_accum_im = 0.f;
    
    F32 total_accum_rl = 0.f;
    F32 total_accum_im = 0.f;
    
    for (U64 j = 0; j < count; j += 1){
      
      // get x[j]  (we can assume `sample_im == 0`)
      F32 sample_rl = 0.f;
      if (j < sample_count){
        sample_rl = samples[j];
      }
      
      // total_accum = total_accum + x[j]*mul_accum
      total_accum_rl += (sample_rl*mul_accum_rl);
      total_accum_im += (sample_rl*mul_accum_im);
      
      // mul_accum = mul_accum*unit;
      F32 new_mul_accum_rl = (mul_accum_rl*unit_rl - mul_accum_im*unit_im);
      F32 new_mul_accum_im = (mul_accum_rl*unit_im + mul_accum_im*unit_rl);
      mul_accum_rl = new_mul_accum_rl;
      mul_accum_im = new_mul_accum_im;
    }
    
    // save X[i]
    rl[i] = total_accum_rl;
    im[i] = total_accum_im;
  }
  
  spectrum->rl = rl;
  spectrum->im = im;
  spectrum->count = spectrum_count;
  return(spectrum);
}

// decibel math

MR4TH_SYMBOL_STATIC F32
dsp_scalar_from_db(F32 db){
  F32 div_db = db/10.f;
  F32 exp_db = pow_F32(10.f, div_db);
  return(exp_db);
}

MR4TH_SYMBOL_STATIC F32
dsp_db_from_scalar(F32 a){
  MR4TH_SYMBOL_STATIC F32 normalize_log10 = 0;
  if (normalize_log10 == 0){
    normalize_log10 = 1.f/ln_F32(10.f);
  }
  F32 db = 10.f*ln_F32(a)*normalize_log10;
  return(db);
}

////////////////////////////////
// Serialization

MR4TH_SYMBOL_STATIC String8
dsp_spectrum_serialize(Arena *arena, DSP_Spectrum *spectrum){
  U64 size = sizeof(F32)*spectrum->count*2;
  U8 *buf = push_array(arena, U8, size);
  
  U8 *ptr = buf;
  
  MemoryCopy(ptr, spectrum->rl, sizeof(*spectrum->rl)*spectrum->count);
  ptr += sizeof(*spectrum->rl)*spectrum->count;
  
  MemoryCopy(ptr, spectrum->im, sizeof(*spectrum->im)*spectrum->count);
  ptr += sizeof(*spectrum->im)*spectrum->count;
  
  String8 data = CLiteral(String8){buf, size};
  return(data);
}

MR4TH_SYMBOL_STATIC DSP_Spectrum*
dsp_spectrum_deserialize(Arena *arena, String8 data){
  U64 count = data.size/(sizeof(F32)*2);
  U64 total_count = count*2;
  
  DSP_Spectrum *spectrum = push_array(arena, DSP_Spectrum, 1);
  F32 *buffer = push_array(arena, F32, total_count);
  F32 *rl = buffer;
  F32 *im = buffer + count;
  
  MemoryCopy(buffer, data.str, sizeof(F32)*total_count);
  
  spectrum->rl = rl;
  spectrum->im = im;
  spectrum->count = count;
  return(spectrum);
}
