/*
 * Copyright 2012 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <math.h>

#include <cstdlib>

#ifdef HAVE_NEON
#include <cpu-features.h>
#endif

#include "synth.h"
#include "sin.h"
#include "fm_op_kernel.h"

#ifdef HAVE_NEONx
static bool hasNeon() {
  return true;
  return (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0;
}

extern "C"
void neon_fm_kernel(const int *in, const int *busin, int *out, int count,
  int32_t phase0, int32_t freq, int32_t gain1, int32_t dgain);

const int32_t __attribute__ ((aligned(16))) zeros[N] = {0};

#else
static bool hasNeon() {
  return false;
}
#endif

void FmOpKernel::compute(int32_t *output, const int32_t *input,
                         int32_t phase0, int32_t freq,
                         int32_t gain1, int32_t gain2, bool add) {
  int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N;
  int32_t gain = gain1;
  int32_t phase = phase0;
  if (hasNeon()) {
#ifdef HAVE_NEON
    neon_fm_kernel(input, add ? output : zeros, output, N,
      phase0, freq, gain, dgain);
#endif
  } else {
    if (add) {
      for (int i = 0; i < N; i++) {
        gain += dgain;
        int32_t y = Sin::lookup(phase + input[i]);
        int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24;
        output[i] += y1;
        phase += freq;
      }
    } else {
      for (int i = 0; i < N; i++) {
        gain += dgain;
        int32_t y = Sin::lookup(phase + input[i]);
        int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24;
        output[i] = y1;
        phase += freq;
      }
    }
  }
}

void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq,
                              int32_t gain1, int32_t gain2, bool add) {
  int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N;
  int32_t gain = gain1;
  int32_t phase = phase0;
  if (hasNeon()) {
#ifdef HAVE_NEON
    neon_fm_kernel(zeros, add ? output : zeros, output, N,
      phase0, freq, gain, dgain);
#endif
  } else {
    if (add) {
      for (int i = 0; i < N; i++) {
        gain += dgain;
        int32_t y = Sin::lookup(phase);
        int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24;
        output[i] += y1;
        phase += freq;
      }
    } else {
      for (int i = 0; i < N; i++) {
        gain += dgain;
        int32_t y = Sin::lookup(phase);
        int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24;          
        output[i] = y1;
        phase += freq;
      }
    }
  }
}

#define noDOUBLE_ACCURACY
#define HIGH_ACCURACY

void FmOpKernel::compute_fb(int32_t *output, int32_t phase0, int32_t freq,
                            int32_t gain1, int32_t gain2,
                            int32_t *fb_buf, int fb_shift, bool add) {
  int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N;
  int32_t gain = gain1;
  int32_t phase = phase0;
  int32_t y0 = fb_buf[0];
  int32_t y = fb_buf[1];
  if (add) {
    for (int i = 0; i < N; i++) {
      gain += dgain;
      int32_t scaled_fb = (y0 + y) >> (fb_shift + 1);
      y0 = y;
      y = Sin::lookup(phase + scaled_fb);
      y = ((int64_t)y * (int64_t)gain) >> 24;
      output[i] += y;
      phase += freq;
    }
  } else {
    for (int i = 0; i < N; i++) {
      gain += dgain;
      int32_t scaled_fb = (y0 + y) >> (fb_shift + 1);
      y0 = y;
      y = Sin::lookup(phase + scaled_fb);
      y = ((int64_t)y * (int64_t)gain) >> 24;
      output[i] = y;
      phase += freq;
    }
  }
  fb_buf[0] = y0;
  fb_buf[1] = y;
}

////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////

// Experimental sine wave generators below
#if 0
// Results: accuracy 64.3 mean, 170 worst case
// high accuracy: 5.0 mean, 49 worst case
void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq,
                              int32_t gain1, int32_t gain2, bool add) {
    int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N;
    int32_t gain = gain1;
    int32_t phase = phase0;
#ifdef HIGH_ACCURACY
    int32_t u = Sin::compute10(phase << 6);
    u = ((int64_t)u * gain) >> 30;
    int32_t v = Sin::compute10((phase << 6) + (1 << 28));  // quarter cycle
    v = ((int64_t)v * gain) >> 30;
    int32_t s = Sin::compute10(freq << 6);
    int32_t c = Sin::compute10((freq << 6) + (1 << 28));
#else
    int32_t u = Sin::compute(phase);
    u = ((int64_t)u * gain) >> 24;
    int32_t v = Sin::compute(phase + (1 << 22));  // quarter cycle
    v = ((int64_t)v * gain) >> 24;
    int32_t s = Sin::compute(freq) << 6;
    int32_t c = Sin::compute(freq + (1 << 22)) << 6;
#endif
    for (int i = 0; i < N; i++) {
        output[i] = u;
        int32_t t = ((int64_t)v * (int64_t)c - (int64_t)u * (int64_t)s) >> 30;
        u = ((int64_t)u * (int64_t)c + (int64_t)v * (int64_t)s) >> 30;
        v = t;
    }
}
#endif

#if 0
// Results: accuracy 392.3 mean, 15190 worst case (near freq = 0.5)
// for freq < 0.25, 275.2 mean, 716 worst
// high accuracy: 57.4 mean, 7559 worst
//  freq < 0.25: 17.9 mean, 78 worst
void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq,
                              int32_t gain1, int32_t gain2, bool add) {
    int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N;
    int32_t gain = gain1;
    int32_t phase = phase0;
#ifdef HIGH_ACCURACY
    int32_t u = floor(gain * sin(phase * (M_PI / (1 << 23))) + 0.5);
    int32_t v = floor(gain * cos((phase - freq * 0.5) * (M_PI / (1 << 23))) + 0.5);
    int32_t a = floor((1 << 25) * sin(freq * (M_PI / (1 << 24))) + 0.5);
#else
    int32_t u = Sin::compute(phase);
    u = ((int64_t)u * gain) >> 24;
    int32_t v = Sin::compute(phase + (1 << 22) - (freq >> 1));
    v = ((int64_t)v * gain) >> 24;
    int32_t a = Sin::compute(freq >> 1) << 1;
#endif
    for (int i = 0; i < N; i++) {
        output[i] = u;
        v -= ((int64_t)a * (int64_t)u) >> 24;
        u += ((int64_t)a * (int64_t)v) >> 24;
    }
}
#endif

#if 0
// Results: accuracy 370.0 mean, 15480 worst case (near freq = 0.5)
// with double accuracy initialization: mean 1.55, worst 58 (near freq = 0)
// with high accuracy: mean 4.2, worst 292 (near freq = 0.5)
void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq,
                              int32_t gain1, int32_t gain2, bool add) {
    int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N;
    int32_t gain = gain1;
    int32_t phase = phase0;
#ifdef DOUBLE_ACCURACY
    int32_t u = floor((1 << 30) * sin(phase * (M_PI / (1 << 23))) + 0.5);
    double a_d = sin(freq * (M_PI / (1 << 24)));
    int32_t v = floor((1LL << 31) * a_d * cos((phase - freq * 0.5) *
                                              (M_PI / (1 << 23))) + 0.5);
    int32_t aa = floor((1LL << 31) * a_d * a_d + 0.5);
#else
#ifdef HIGH_ACCURACY
    int32_t u = Sin::compute10(phase << 6);
    int32_t v = Sin::compute10((phase << 6) + (1 << 28) - (freq << 5));
    int32_t a = Sin::compute10(freq << 5);
    v = ((int64_t)v * (int64_t)a) >> 29;
    int32_t aa = ((int64_t)a * (int64_t)a) >> 29;
#else
    int32_t u = Sin::compute(phase) << 6;
    int32_t v = Sin::compute(phase + (1 << 22) - (freq >> 1));
    int32_t a = Sin::compute(freq >> 1);
    v = ((int64_t)v * (int64_t)a) >> 17;
    int32_t aa = ((int64_t)a * (int64_t)a) >> 17;
#endif
#endif
    
    if (aa < 0) aa = (1 << 31) - 1;
    for (int i = 0; i < N; i++) {
        gain += dgain;
        output[i] = ((int64_t)u * (int64_t)gain) >> 30;
        v -= ((int64_t)aa * (int64_t)u) >> 29;
        u += v;
    }
}
#endif

#if 0
// Results:: accuracy 112.3 mean, 4262 worst (near freq = 0.5)
// high accuracy 2.9 mean, 143 worst
void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq,
                              int32_t gain1, int32_t gain2, bool add) {
    int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N;
    int32_t gain = gain1;
    int32_t phase = phase0;
#ifdef HIGH_ACCURACY
    int32_t u = Sin::compute10(phase << 6);
    int32_t lastu = Sin::compute10((phase - freq) << 6);
    int32_t a = Sin::compute10((freq << 6) + (1 << 28)) << 1;
#else
    int32_t u = Sin::compute(phase) << 6;
    int32_t lastu = Sin::compute(phase - freq) << 6;
    int32_t a = Sin::compute(freq + (1 << 22)) << 7;
#endif
    if (a < 0 && freq < 256) a = (1 << 31) - 1;
    if (a > 0 && freq > 0x7fff00) a = -(1 << 31);
    for (int i = 0; i < N; i++) {
        gain += dgain;
        output[i] = ((int64_t)u * (int64_t)gain) >> 30;
        //output[i] = u;
        int32_t newu = (((int64_t)u * (int64_t)a) >> 30) - lastu;
        lastu = u;
        u = newu;
    }
}
#endif

