/*!\file audioHelper.c
 *
 * \brief éléments audio pouvant être utiles pour la démo.
 * \author Farès BELHADJ, amsi@ai.univ-paris8.fr
 * \date May 5, 2014 - February 26, 2016
 */

#include "audioHelper.h"

/*!\brief pointeur vers la musique chargée par SDL_Mixer */
static Mix_Music * _mmusic = NULL;

/*!\brief pointeur vers le flux audio.
 * \see ahGetAudioStream
 * \see ahSetAudioStream
 */
static Uint8 * _audioStream = NULL;
/*!\brief longueur du flux audio.
 * \see ahGetAudioStreamLength
 * \see ahSetAudioStream
 */
static int _audioStreamLength = 0;

/*!\brief renvoie le pointeur vers le flux audio.
 * \return le pointeur vers le flux audio.
 */
Uint8 * ahGetAudioStream(void) {
  return _audioStream;
}

/*!\brief renvoie la longueur du flux audio.
 * \return la longueur du flux audio.
 */
int ahGetAudioStreamLength(void) {
  return _audioStreamLength;
}

/*!\brief met à jour le pointeur vers le flux audio et sa longueur.
 */
void ahSetAudioStream(Uint8 * audioStream, int audioStreamLength) {
  _audioStream = audioStream;
  _audioStreamLength = audioStreamLength;
}

Uint32 ahPathLength(void) {
  Sint16 * s = (Sint16 *)ahGetAudioStream();
  int l = ahGetAudioStreamLength() / sizeof *s;
  Uint32 pl = 0;
  for(int i = 0; i < l - 1; ++i)
    pl += abs(s[i] - s[i + 1]);
  return pl / (l - 1);
}

float ahAverage(void) {
  Sint16 * s = (Sint16 *)ahGetAudioStream();
  int l = ahGetAudioStreamLength() / sizeof *s;
  float a = 0;
  for(int i = 0; i < l; ++i)
    a += s[i];
  return a / l;
}

static const Sint16 _max_vol = 32767;
static Uint32 _fo = 0, _fo_duration = 0;
static float _gvol = 1.0f;
static int _step = 0;
static void fade_out(Uint32 duration) {
  _fo = SDL_GetTicks();
  _fo_duration = duration;;
}

static inline void update_gvol(void) {
  if(_fo) {
    Uint32 dt = SDL_GetTicks() - _fo;
    if(dt >= _fo_duration) {
      _gvol = 0.0f;
      _fo = 0;
    } else
      _gvol = (_fo_duration - dt) / (float)_fo_duration;
  }
}

static inline float surand(void) {
  return 1.0f - rand() / (0.5f * RAND_MAX + 0.5f);
}

static int _neige = 0;
void ahSetNeige(int status) {
  _neige = status;
}

void ahSetStep(int step) {
  _step = step;
}

static inline void neige(Uint8 *stream, int len) {
  static const Sint16 max_vol = _max_vol;
  static Sint16 cur_vol = 0;
  static float pc = 5.0f;
  Sint16 * s = (Sint16 *)stream;
  Sint16 amax = (Sint16)(_max_vol * _gvol);
  len = len / sizeof *s;
  for(int i = 0; i < len; ++i) {
    s[i] += (Sint16)(_gvol * cur_vol * surand());
    if(s[i] >=  amax) s[i] =  amax;
    if(s[i] <= -amax) s[i] = -amax;
  }
  if(_neige) {
    if(cur_vol < max_vol)
      cur_vol += (pc *= 1.01f);
    else
      cur_vol = max_vol;
  } else {
    if(cur_vol > 0)
      cur_vol -= (pc *= 1.1f);
    else
      cur_vol = 0;
  }
}

#include "animations.h"

static const float _2PI = 6.28318530718f;
static inline void abeep(Uint8 *stream, int len) {
  static float x = 0;
  static float freq = 2.0f * 440.0f;
  MON_TEMPS;
  float pulse;
  if(duree < 8.0f) {
    pulse = tan(_2PI * duree / 2.0f);
    pulse = pulse > 0.0f ? 0.0f : pow(-pulse, 100.0f);
  } else {
    static float samp = 1.0f, vit = 1.0f, accel = 0.0005f;
    if(samp < 0.0f) return;
    pulse = tan(vit * _2PI * duree / 2.0f);
    pulse = pulse > 0.0f ? 0.0f :  samp * pow(-pulse, 100.0f / pow(vit, 100.0f));
    vit   += accel;
    accel += 0.00003f;
    if(duree > 16.0f) {
      float v = (30.f - duree);
      samp = v > 0.0f ? pow( v / (30.0f - 16.0f), 1.2f) : -1.0f;
      //samp -= 0.03f;
    }
  }
  pulse = pulse > 1.0f ? 1.0f : pulse;
  float amp = _max_vol * _gvol * pulse;
  Sint16 * s = (Sint16 *)stream;
  len = len / sizeof *s;
  for(int i = 0; i < len; i += 2) {
    int a = s[i + 0]  + amp * sin(x);
    a = a >= (1 << 15) ? (1 << 15) - 1 : a;
    a = a < -(1 << 15) ? -(1 << 15) : a;
    s[i + 0] = a;
    
    a = s[i + 1]  + amp * cos(x);
    a = a >= (1 << 15) ? (1 << 15) - 1 : a;
    a = a < -(1 << 15) ? -(1 << 15) : a;
    s[i + 1] = a;

    x += freq * _2PI / 48000.0f;
    if (x >= _2PI)
      x -= _2PI;
  }
}
static inline void abeep2(Uint8 *stream, int len) {
  static float x = 0, vol = 0.5f;
  static float freq = 1.6f * 440.0f;
  MON_TEMPS;
  Sint16 * s = (Sint16 *)stream;
  len = len / sizeof *s;
  if(duree > 6.8f) {
    vol = 0.5f * (1.0f - (duree - 6.8f) / 0.2f);
    if(vol < 0.0f) vol = 0.0f;
  }
  for(int i = 0; i < len; ++i) {
    s[i]  = vol * _max_vol * _gvol * (0.8f + 0.2f * fabs(sin(10.0f * _2PI * duree))) * sin(x);
    x += freq * _2PI / 48000.0f;
    if (x >= _2PI)
      x -= _2PI;
  }
}


#include <ImprovedNoise.h>
static inline void audio_perlin(Uint8 *stream, int len) {
  static const Sint16 max_vol = _max_vol / 3;
  static Sint16 cur_vol = 0;
  static float x = 0, px = 0.003;
  Sint16 * s = (Sint16 *)stream;
  Sint16 amax = (Sint16)(_max_vol * _gvol);
  len = len / sizeof *s;
  for(int i = 0; i < len; ++i, x += px) {
    float no = _gvol * cur_vol * (2.0 * noise(x, 0.5 * x, 0.5) - 1.0);
    s[i] = (Sint16)no;
    if(s[i] >=  amax) s[i] =  amax;
    if(s[i] <= -amax) s[i] = -amax;
    px += 0.000000005f;
  }
  if(cur_vol < max_vol)
    cur_vol += len >> 7;
}

/*!\brief Cette fonction est appelée quand l'audio est joué et met 
 * dans \a stream les données audio de longueur \a len.
 * \param udata pour user data, données passées par l'utilisateur, ici NULL.
 * \param stream flux de données audio.
 * \param len longueur de \a stream.
 */
static void mixCallback(void *udata, Uint8 *stream, int len) {
  update_gvol();
  ahSetAudioStream(stream, len);
  if(_step == 0) {
    audio_perlin(stream, len);
    if(_neige)
      neige(stream, len);
  } else if(_step == 1) {
    _neige = 0;
    neige(stream, len);
  } else if(_step == 2) {
    abeep(stream, len);
  } else if(_step == 3) {
    if(!Mix_PlayingMusic())
      Mix_PlayMusic(_mmusic, 1);
    abeep(stream, len);
  } else if(_step == 4) {
    abeep2(stream, len);
  }
  gl4dhUpdateWithAudio();
  ahSetAudioStream(NULL, 0);
}


void partie1(void) {
  SDL_Init(SDL_INIT_AUDIO);
  SDL_AudioSpec spec, aspec; // the specs of our piece of "music"                                                                                                                                                
  SDL_zero(spec);
  spec.freq = 48000; //declare specs                                                                                                                                                                             
  spec.format = AUDIO_S16SYS;
  spec.channels = 2;
  spec.samples = 1024;
  spec.callback = mixCallback;
  spec.userdata = NULL;

  int id;
  if ((id = SDL_OpenAudioDevice(NULL, 0, &spec, &aspec, SDL_AUDIO_ALLOW_ANY_CHANGE)) <= 0) {
    fprintf(stderr, "Couldn't open audio: %s\n", SDL_GetError());
    exit(-1);
  }
  SDL_PauseAudioDevice(id, 0);
  gl4dhStartingSignal();
  SDL_Delay(53460);
}

/*!\brief Cette fonction initialise les paramètres SDL_Mixer et charge
 *  le fichier audio.
 */
void ahInitAudio(const char * file) {
  //partie1();
  int mixFlags = MIX_INIT_MOD, res;
  res = Mix_Init(mixFlags);
  if( (res & mixFlags) != mixFlags ) {
    fprintf(stderr, "Mix_Init: Erreur lors de l'initialisation de la bibliotheque SDL_Mixer\n");
    fprintf(stderr, "Mix_Init: %s\n", Mix_GetError());
    exit(-3);
  }
  if(Mix_OpenAudio(48000, AUDIO_S16LSB, 2, 1024) < 0)
    exit(-4);
  if(!(_mmusic = Mix_LoadMUS(file))) {
    fprintf(stderr, "Erreur lors du Mix_LoadMUS: %s\n", Mix_GetError());
    exit(-5);
  }
  Mix_SetPostMix(mixCallback, NULL);
  //if(!Mix_PlayingMusic())
  //  Mix_PlayMusic(_mmusic, 1);
}

/*!\brief Libère l'audio.
 *
 * \param fadeout_duration, durée du fadeout en milisecondes
 */
void ahClean(Uint32 fadeout_duration) {
  fade_out(fadeout_duration);
  if(Mix_PlayingMusic())
    Mix_FadeOutMusic(fadeout_duration);  
  SDL_Delay(fadeout_duration);
  if(_mmusic) {
    if(Mix_PlayingMusic())
      Mix_HaltMusic();
    Mix_FreeMusic(_mmusic);
    _mmusic = NULL;
  }
  Mix_CloseAudio();
  Mix_Quit();
}
