/******************************************************************************/
/*									      */
/*			    SAMPLE SUPPORT FOR RAINE			      */
/*									      */
/*			  modified by Hiromitsu Shioya			      */
/* Author : Emmanuel Anne						      */
/******************************************************************************/
//
// This is the main sound driver for raine right now.
// The seal driver is ported from this driver.
//
/* This is now the reference sound driver for raine.
   SDL allows an incredible flexibility.

   There is clearly an imprecision in the soundcards when playing a 22Khz sound for
   almost nothing, I tested it in streams.c a difference of 1 byte makes the sound
   late or in advance for base_len. And the precise base_len can't be guessed apparently.

   In allegro this problem is without solution, see the 2 versions of sasound.c in its
   directory (in windows, allegro works. Apparently its sampling functions lack precision
   in linux, but I didn't try to find out why).

   In SDL, a handler is called precisely when needed to update this sound buffer.
   Then when we update the streams buffers we check the difference between the point
   writen to by the streams and the point read by sdl. If the difference is changing
   too much, we just drop or add a sound frame (it can't be heared at all).

   This can't be adapted to allegro, the differences vary too much in allegro to be
   usable, and there is no callback.

   Notice : there are still some pieces of very old code from before version 0.28
   in this file. I should clean it up one day... maybe.
*/

#ifdef RAINE_DEBUG
/* Normally when you mix different voices, you are supposed to check if the result
   gets too loud. But we can avoid this by setting the volume of the different sound chips
   to reasonable values. That's exactly what mame is already doing. Nice idea, it's faster
   and since the sound has not to be bounded it probably sounds better too.
   So this setting is here only to know if our volume is still too loud when in debug
   mode (prints out on stderr) */
#define TEST_OVERFLOW
#endif

#include <time.h>
#include "raine.h"
#include "sasound.h"
#include "games.h"
#include "debug.h"
#include "timer.h"
#include "profile.h" // fps

// This define is required for the new glibc in linux to have access the usefull
// definitions in math.h (why doing something simple when it can be complicated ?)
#define __USE_ISOC99 1
#include <math.h> // double operations for resampling
#include "SDL.h"
#include "SDL_audio.h"

#define UINT unsigned int
#define DWORD int

static UINT8 counter[MAX_STREAM_CHANNELS];

int RaineSoundCardTotal,
  RaineSoundCard;

/* Avoid to uncomment USE_8BITS unless you finish 8bit support... which is
    pretty useless nowdays ! */

//#define USE_8BITS 1

/*

dump channels (debug)

*/

//#define DUMP_CHANNELS

#ifdef DUMP_CHANNELS
FILE *stream_out[NUMVOICES];
#endif

SoundRec      *SndMachine = NULL, snd_entry;

/* audio related stuff */

static SAMPLE *lpWave[NUMVOICES];

// The "normal" version of PlayStream does not use samples nor voicexs.
// It just uses "streams" ! What a mess !

static int	   playing[NUMVOICES];
static UINT16	   *vout[NUMVOICES],*vend[NUMVOICES],*vout_stream[NUMVOICES];

int	    audio_sample_rate;

static int reserved_channel = 0,opened_audio = 0;

static int stream_buffer_max;

static int pause_sound;

void saCheckPlayStream( void );

void saSetVolume( int channel, int data )
{
  // For SDL, we mix the sound in software, see update_recording in streams.c
}

void saSetPan( int channel, int data )
{
}

#include "streams.c"

/*******************************************************************************************/
/*  sa???Sound										   */
/*******************************************************************************************/

/******************************************/
/*    update sound			  */
/******************************************/

void saUpdateSound( int nowclock )
{
   if( ! GameSound ) return;
   if( ! RaineSoundCard ) return;
   if( ! audio_sample_rate ) return;
   if( ! SndMachine ) return;

   if( nowclock ){
     //int i;
     // This part is called for each frame, which *should* be 60
  // times/sec, but it can be less (if the game slows down)
      streams_sh_update();
   }

}

int enh_stereo = 0;

extern int max_mixer_volume;

/******************************************/
/*    setup sound			  */
/******************************************/
BOOL saInitSoundCard( int soundcard, int sample_rate )
{

   int i,id;
   RaineSoundCard = 1;
   /* install a digital sound driver */
     // Normally, soundcard =0 means no sound in raine.
     // I will try not to break this to keep compatibility with the other
     // sources...
   id = sound_card_id(soundcard);

/*    if ( SDL_Init(SDL_INIT_AUDIO) < 0 ) { */
/*      fprintf(stderr, "Couldn't initialize SDL: %s\n",SDL_GetError()); */
/*      exit(1); */
/*    } */
/*    atexit(SDL_Quit); */

   for( i = 0; i < NUMVOICES; i++ ){

      lpWave[i]  = 0;
      playing[i] = 0;

#ifdef DUMP_CHANNELS
      sprintf(s,"stream%02d.raw",i);
      stream_out[i] = fopen(s,"wb");
#endif
   }

   stream_buffer_max = STREAM_BUFFER_MAXB;

   //reserved_channel = 0;

   pause_sound = 0;		/* pause flag off */
   if(!init_sound_emulators()) {
     return FALSE;  // Everything fine
   }

   return TRUE;
}

/******************************************/
/*    setup sound			  */
/******************************************/

void init_sound_list(void)
{
  const SOUND_INFO *sound_src; // games/games.h
  int ta;

  sound_src = current_game->sound_list;

  if(sound_src){

    saStopSoundEmulators();

    for( ta = 0; ta < SND_CONTROL_MAX; ta++ ){
      SndMachine->init[ta] = SOUND_NONE;
      SndMachine->intf[ta] = NULL;
    }

    SndMachine->first = 0;
    SndMachine->control_max = 0;

    ta = 0;

    while(sound_src[ta].interface){

      SndMachine->init[ta] = sound_src[ta].type;
      SndMachine->intf[ta] = sound_src[ta].interface;

      ta++;

    }

    SndMachine->control_max = ta;

    GameSound = 1;

  }
}

/******************************************/
/*    destroy sound			  */
/******************************************/

void saDestroyChannel( int chan )
{
  if( lpWave[chan] ){
    destroy_sample( lpWave[chan] );
    lpWave[chan] = 0;
    playing[chan] = 0;
  }
}

void saDestroySound( int remove_all_resources )
{
   int i;

#ifdef RAINE_DEBUG
   print_debug("saDestroySound: Removing SEAL\n");
#endif

   //pause_raine_ym3812();

   if(remove_all_resources){
     saStopSoundEmulators();
   }

   if (opened_audio)
     SDL_CloseAudio();

   for( i = 0; i < NUMVOICES; i++ ){
      saDestroyChannel(i);

#ifdef DUMP_CHANNELS
      fclose(stream_out[i]);
#endif

   }
#ifdef USE_COMPENS
   reset_streams();
#endif

#ifdef RAINE_DEBUG
   print_debug("saDestroySound: OK\n");
#endif
   opened_audio = 0;
}

void sa_pause_sound(void)
{
   pause_sound	    = 1;

   //pause_raine_ym3812();
   SDL_PauseAudio(1);
}

void sa_unpause_sound(void)
{
  pause_sound	   = 0;
  saDestroySound(0);
}

/*******************************************************************************************/
/*******************************************************************************************/
/******************************************/
/*    play samples			  */
/******************************************/
#if 0
// This was supposed to resample the voice, but it does not work well at all !!!
INT16 get_average(signed short *din, int distance, double rapport) {
  int start = distance*rapport;
  int len = (distance+1)*rapport - start;
  int nb = 0;
  int i;
  return din[start];
  for (i=start; i<start+len; i++) {
    nb += din[i];
  }
  return nb/len;
}
#endif

static int first_update,expected_var;

static void my_callback(void *userdata, Uint8 *stream, int len)
{
  int i,channel;
  short *wstream = (short*) stream;
  len /= 2;
  memset(stream,0,len*2);


  if (first_update) {
    /* Normally this callback was supposed to handle buffer lengths close to what is
       updated at every frame. But windows does not support lengths < 4096 for quite
       a few of its drivers (soundblaster live for example).
       So now we try to guess a resonable delay between the update points */
    int count = (enh_stereo && SamplePan[channel] == PAN_LEFT ?
		 2 + 1 : 2);
    first_update = 0;
    expected_var = count*len/2;
    for (channel=0; channel<NUMVOICES; channel++) {
      if( playing[channel] ){
	vout_stream[channel] = vout[channel] - expected_var;
	if (vout_stream[channel] < (UINT16*)lpWave[channel]->data) {
	  vout_stream[channel] = vend[channel] - (((UINT16*)lpWave[channel]->data) - vout_stream[channel]);
	  // printf("après corr chan %d : pos %d pos2 %d max %d expected_var %d\n",channel,vout[channel]-((UINT16*)lpWave[channel]->data),vout_stream[channel]-(UINT16*)lpWave[channel]->data,vend[channel]-(UINT16*)lpWave[channel]->data,expected_var);
	}
      }
    }
    expected_var -= len/2;
    // printf("expected var %d len %d\n",expected_var,len);
  }

  for (channel=0; channel<NUMVOICES; channel++) {
    if( playing[channel] ){
      int volume = SampleVol[channel];
      int vol_l = (255-SamplePan[channel])*volume/255;
      int vol_r = (SamplePan[channel])*volume/255;
      signed short *din=((signed short*)vout_stream[channel]);
      // vol_l>>=1;
      // vol_r>>=1;
#if 0

      /* This code produces ugly results.
	 By chance, SDL_OpenAudio can handle directly the required 96 Khz for namco,
	 so for now, I won't bother with that. (maybe the resampling is hardware with
	 SDL_OpenAudio). Of course it obliges to play all the channels at the same
         frequency, but for now it's ok.
	 Anyway I keep this code here in case one day it becomes usefull and we can
	 fix it... !!!
      */

      if (stream_sample_rate[channel] != audio_sample_rate) {
	// printf("remix chan %d to %d len %d\n",channel,stream_sample_rate[channel],len);
	/* Resampling, double precision, needed for nameco sound */
	double rapport = (double)stream_sample_rate[channel]/audio_sample_rate;
	INT16 sample;
	int end = trunc(len/2*rapport);
	if (vout_stream[channel ] + end > vend[channel]) {
	  int len2 = (vend[channel] - vout_stream[channel])*2/rapport;
	  for (i=0; i<len2; i+=2) {
	    sample = get_average(din,i,rapport);
	    *wstream++ += sample*vol_l/255;
	    *wstream++ += sample*vol_r/255;
	  }
	  din = (signed short*)lpWave[channel]->data;
	  len -= len2;
	  for (i=0; i<len; i+=2) {
	    sample = get_average(din,i,rapport);
	    *wstream++ += sample*vol_l/255;
	    *wstream++ += sample*vol_r/255;
	  }
	  end = trunc(len/2*rapport);
	  din += end;
	  wstream = (INT16*) stream;
	  len += len2;
	} else {
	  for (i=0; i<len; i+=2) {
	    sample = get_average(din,i,rapport);
	    INT16 left = sample*vol_l/255;
	    INT16 right = sample*vol_r/255;
	    wstream[i] += left;
	    wstream[i+1] += right;
	  }
	  din += end;
	}
      } else
#endif
	if (vout_stream[channel] + len/2 > vend[channel]) {
	  /* Overlap of the buffer */
	  int len2 = (vend[channel] - vout_stream[channel])*2;
	  for (i=0; i<len2; i+=2) {
	    *wstream++ += *(din)*vol_l/255;
	    *wstream++ += *(din++)*vol_r/255;
	  }
	  din = (signed short*)lpWave[channel]->data;
	  len -= len2;
	  for (i=0; i<len; i+=2) {
	    *wstream++ += *(din)*vol_l/255;
	    *wstream++ += *(din++)*vol_r/255;
	  }
	  wstream = (INT16*) stream;
	  len += len2;
	} else {
	  /* normal buffer, no resample */
	  for (i=0; i<len; i+=2) {
	    INT16 left = *(din)*vol_l/255;
	    INT16 right = *(din++)*vol_r/255;
#ifdef TEST_OVERFLOW
	    INT32 sample = wstream[i]+left;
	    if (sample > 0x7fff) {
	      printf("overflow left %x\n",sample);
	      sample = 0x7fff;
	    } else if (sample < -0x8000) {
	      printf("underflow left %x\n",sample);
	      sample = -0x8000;
	    }
	    wstream[i] = sample;
	    sample = wstream[i+1] + right;
	    if (sample > 0x7fff) {
	      printf("overflow right %x\n",sample);
	      sample = 0x7fff;
	    } else if (sample < -0x8000) {
	      printf("underflow right %x\n",sample);
	      sample = -0x8000;
	    }
	    wstream[i+1] = sample;
#else
	    wstream[i] += left;
	    wstream[i+1] += right;
#endif

	  }
	}
      vout_stream[channel] = din;

    }
  }
  if (recording) {
    if (mixing_buff_len != len) {
      mixing_buff_len = len;
    }
    memcpy(mixing_buff,stream,len*2);
    updated_recording++;
  }
}

unsigned short *reference;

void saPlayBufferedStreamedSampleBase( int channel, signed char *data, int len, int freq, int volume, int bits , int pan ){
  /* This version works at low level, creating a sample, and following its
     advancement directly in the voice_position... */
  int i;
  unsigned short *dout,*dfin;
  signed short *din;
  // fprintf(stderr,"saPlayBuffer %d freq %d bits %d pan %d len %d\n",channel,freq,bits,pan,len);
  if( audio_sample_rate == 0 || channel >= NUMVOICES )	return;
  if( SndMachine == NULL )  return;
  if( !playing[channel] ){
#ifdef USE_COMPENS
    int fin = stream_buffer_max * freq * 2 / fps;
#else
    int fin = stream_buffer_max * len;
#endif
    if( lpWave[channel] ){
      destroy_sample( lpWave[channel] );
      lpWave[channel] = 0;
    }

    if (!(lpWave[channel] = create_sample(16,0,freq,fin/2+4))){
      lpWave[channel] = 0;
      return;
    }

    memset( lpWave[channel]->data, 0, fin );
    dfin=(short*) (((char*)lpWave[channel]->data)+fin);
    vend[channel] = dfin;
    counter[channel] = 0;

    playing[channel] = 1;	/* use front surface */

    init_mixing_buff(len);

    /**** make sound temp. buffer ****/
    if (enh_stereo && SamplePan[channel] == PAN_LEFT)
      dout=(short*) (((char*)lpWave[channel]->data)+len*(MODEB_UPDATE_COUNT+1)); //+len*MODEB_UPDATE_COUNT);
    else
      dout=(short*) (((char*)lpWave[channel]->data)+len*MODEB_UPDATE_COUNT); //+len*MODEB_UPDATE_COUNT);
    din = ((signed short*)data);
    //	      memcpy( dout, din, len );
    for (i=0; i<len; i+=2){
      *(dout++) = *(din++);
    }

    update_recording(channel,data);
    vout[channel] = dout;

    if (dout ==dfin){
      dout=(short*) (((char*)lpWave[channel]->data));
    }
#ifdef DUMP_CHANNELS
    fwrite( lpWave[channel]->data+len*MODEB_UPDATE_COUNT, 1, len, stream_out[channel]);
#endif

    if (!opened_audio) {
      SDL_AudioSpec spec,gotspec;
      int nb=0;
      spec.freq = freq; // audio_sample_rate;
      spec.format = AUDIO_S16LSB;
      spec.channels = 2;
      spec.samples  = len/2;
      while (spec.samples) {
	spec.samples >>= 1;
	nb++;
      }
      /* Some windows drivers are buggy, ie soundblaster apparently.
	 It's quite a shame : the soundcard is supposed to accept these buffer sizes,
	 and it works perfectly in linux, but it fails in windows (lots of cracks).
	 So it's best to force the buffer to a "reasonable" value. */
      /* Actually we must keep it as small as possible if we want to still be able to
	 make corrections on sound delays. The formula below is the best I could find
	 while still avoiding the cracks in windows */
      spec.samples = 512 * audio_sample_rate / 11025; // 1 << (nb);
      spec.userdata = NULL;
      spec.callback = my_callback;
      if ( SDL_OpenAudio(&spec, &gotspec) < 0 ) {
	fprintf(stderr, "Couldn't open audio: %s\n", SDL_GetError());
	exit(2);
      }
      opened_audio = 1;
      SDL_PauseAudio(0);
    }
    first_update = 1;
    /* vout_stream is initalized at its first update.
       It's because the updates do not start at cpu frame 0, so it's better to wait */
    reference = NULL;
  } else{

    if (!first_update && channel == 0) {
      /* This piece of code controls that the delay between the streams produced
	 and what the soundcard plays stays constant. Amazingly small after all ! */
      static int nb1,moy1;
      if (!reference) {
	reference = vout[0];
	nb1 = moy1 = 0;
      }
      if (reference == vout[0]) { // constant, since base_len (streams.c) is constant
	if (nb1 < 3) {
	  int inc = vout[0] - vout_stream[0];
	  if (inc < 0) { //  && abs(inc) > expected_var) {
	    int delta = stream_buffer_max * len/2;
	    if (abs(inc ) > abs(inc + delta )) {
	      // printf("correction inc vout %d vout_stream %d moy1 %d expected %d\n",vout[0]-((UINT16*)lpWave[channel]->data),vout_stream[0]-(UINT16*)lpWave[channel]->data,moy1,expected_var);
	      inc += delta;
	    }
	  }
	  moy1 += inc;
	  nb1++;
	} else {
	  moy1/=3;
	  // printf("moy %d expected %d\n",moy1,expected_var);
	  if (moy1 > expected_var + len/2) {
	    more_stream = (expected_var - moy1)/(len/2); // drop next frame
	  } else if (moy1 < expected_var - len/2) {
	    more_stream = (expected_var - moy1)/(len/2); // drop next frame
	  }
	  moy1 = nb1 = 0;
	}
      }
    }

    dout=vout[channel];
    din = ((signed short*)data);
    dfin = vend[channel];
    if (dout+len/2 > dfin) {
      memcpy(dout,din,(dfin-dout)*2);
      len -= (dfin - dout)*2;
      din += (dfin - dout);
      dout=(short*)lpWave[channel]->data;
    }
    memcpy(dout,din,len);

    dout += len/2;
    if (dout >=dfin){
      dout=(short*)lpWave[channel]->data;

    }

#ifdef DUMP_CHANNELS

    fwrite( lpWave[channel]->data+len*s_pos, 1, len, stream_out[channel]);
#endif
    vout[channel] = dout;

    // more than count frames of advance : more stream !
#if 0
    pos -= count*len/2;
    if (pos > th_pos && pos > 0) {
      more_stream = 1;
    } else
      more_stream = 0;
#endif
  }
}

#ifdef USE_8BITS
/******************************************/
/*    play samples			  */
/******************************************/
void saPlayStreamedSampleBase( int channel, signed char *data, int len, int freq, int volume, int bits , int pan ){
  // This one should leave most of the sync work to allegro
  int pos;
	void *buff; // position in the stream
  unsigned short *dout;
  signed short *din;
  int i;
  if (bits == 8) {
    fprintf(stderr,"error: Can't play 8 bits\n");
    // Just because I don't want to bother with this now.
    return;
  }
  if( audio_sample_rate == 0 || channel >= NUMVOICES )	return;
  if( SndMachine == NULL )  return;
  if( !playing[channel] ){
    if( stream[channel] ){
      stop_audio_stream(stream[channel]);
      free_audio_stream_buffer(stream[channel]);
      stream[channel] = NULL;
    }

    printf("playing %d at freq %d\n",channel,freq);
    if (!(stream[channel] = play_audio_stream(len,bits,0,freq,volume,pan))){
      return;
    }
    playing[channel] = 1;	/* use front surface */

    // Wait for the buffer to be ready...
    while (!(buff = get_audio_stream_buffer(stream[channel])));
    //print_debug("first stream entry. [%d:%d:%d:%d]\n", channel, len, freq, volume );

  }

  if (!(buff = get_audio_stream_buffer(stream[channel]))) {
    fprintf(stderr,"init stream impossible : buffer NULL\n");
    return;
  }
  //	fprintf(stderr,"len memcpy : %d\n",len);
  dout=buff;
  din = ((signed short*)data);
  for (i=0; i<len; i+=2)
    *(dout++) = *(din++)^0x8000;

  //fprintf(stderr,"set chanel vol = %d\n",volume);
}
#endif

/******************************************************************************/
/*									      */
/*			  SOUND CHANNEL ALLOCATION			      */
/*									      */
/******************************************************************************/

int saGetPlayChannels( int request )
{
   int ret_value = reserved_channel;
   reserved_channel += request;
   return ret_value;
}

void saResetPlayChannels( void )
{
   reserved_channel = 0;
}

/******************************************************************************/
/*									      */
/*			    SOUND CARD INFORMATION			      */
/*									      */
/******************************************************************************/

char *sound_card_name( int num )
{
   int id = sound_card_id(num);

   RaineSoundCardTotal = 2;

   if (id == 0)
     return "Silence";
   return "SDL (autodetect)";

}

int sound_card_id( int i )
{
  return i; // for now no id in sdl
}

/******************************* END OF FILE **********************************/
