/*
    SDL_mixer:  An audio mixer library based on the SDL library
    Copyright (C) 1997-2004 Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Sam Lantinga
    slouken@libsdl.org
*/

#ifdef MP3_MAD_MUSIC

#include <string.h>

#include "music_mad.h"

mad_data *
mad_openFile(const char *filename, SDL_AudioSpec *mixer) {
  SDL_RWops *rw;

  rw = SDL_RWFromFile(filename, "rb");
  if (rw == NULL) {
	return NULL;
  }

  return mad_openFileRW(rw, mixer);
}

mad_data *
mad_openFileRW(SDL_RWops *rw, SDL_AudioSpec *mixer) {
  mad_data *mp3_mad;

  mp3_mad = (mad_data *)malloc(sizeof(mad_data));
  mp3_mad->rw = rw;
  mad_stream_init(&mp3_mad->stream);
  mad_frame_init(&mp3_mad->frame);
  mad_synth_init(&mp3_mad->synth);
  mp3_mad->frames_read = 0;
  mad_timer_reset(&mp3_mad->next_frame_start);
  mp3_mad->volume = MIX_MAX_VOLUME;
  mp3_mad->status = 0;
  mp3_mad->output_begin = 0;
  mp3_mad->output_end = 0;
  mp3_mad->mixer = *mixer;

  return mp3_mad;
}

void
mad_closeFile(mad_data *mp3_mad) {
  SDL_FreeRW(mp3_mad->rw);
  mad_stream_finish(&mp3_mad->stream);
  mad_frame_finish(&mp3_mad->frame);
  mad_synth_finish(&mp3_mad->synth);

  free(mp3_mad);
}

/* Starts the playback. */
void
mad_start(mad_data *mp3_mad) {
  mp3_mad->status |= MS_playing;
}

/* Stops the playback. */
void 
mad_stop(mad_data *mp3_mad) {
  mp3_mad->status &= ~MS_playing;
}

/* Returns true if the playing is engaged, false otherwise. */
int
mad_isPlaying(mad_data *mp3_mad) {
  return ((mp3_mad->status & MS_playing) != 0);
}

/* Reads the next frame from the file.  Returns true on success or
   false on failure. */
static int
read_next_frame(mad_data *mp3_mad) {
  if (mp3_mad->stream.buffer == NULL || 
	  mp3_mad->stream.error == MAD_ERROR_BUFLEN) {
	size_t read_size;
	size_t remaining;
	unsigned char *read_start;
	
	/* There might be some bytes in the buffer left over from last
	   time.  If so, move them down and read more bytes following
	   them. */
	if (mp3_mad->stream.next_frame != NULL) {
	  remaining = mp3_mad->stream.bufend - mp3_mad->stream.next_frame;
	  memmove(mp3_mad->input_buffer, mp3_mad->stream.next_frame, remaining);
	  read_start = mp3_mad->input_buffer + remaining;
	  read_size = MAD_INPUT_BUFFER_SIZE - remaining;
	  
	} else {
	  read_size = MAD_INPUT_BUFFER_SIZE;
	  read_start = mp3_mad->input_buffer;
	  remaining = 0;
	}

	/* Now read additional bytes from the input file. */
	read_size = SDL_RWread(mp3_mad->rw, read_start, 1, read_size);
	
	if (read_size <= 0) {
	  if ((mp3_mad->status & (MS_input_eof | MS_input_error)) == 0) {
		if (read_size == 0) {
		  mp3_mad->status |= MS_input_eof;
		} else {
		  mp3_mad->status |= MS_input_error;
		}
		
		/* At the end of the file, we must stuff MAD_BUFFER_GUARD
		   number of 0 bytes. */
		memset(read_start + read_size, 0, MAD_BUFFER_GUARD);
		read_size += MAD_BUFFER_GUARD;
	  }
	}
	
	/* Now feed those bytes into the libmad stream. */
	mad_stream_buffer(&mp3_mad->stream, mp3_mad->input_buffer,
					  read_size + remaining);
	mp3_mad->stream.error = MAD_ERROR_NONE;
  }
  
  /* Now ask libmad to extract a frame from the data we just put in
	 its buffer. */
  if (mad_frame_decode(&mp3_mad->frame, &mp3_mad->stream)) {
	if (MAD_RECOVERABLE(mp3_mad->stream.error)) {
	  return 0;
	  
	} else if (mp3_mad->stream.error == MAD_ERROR_BUFLEN) {
	  return 0;
	  
	} else {
	  mp3_mad->status |= MS_decode_error;
	  return 0;
	}
  }
  
  mp3_mad->frames_read++;
  mad_timer_add(&mp3_mad->next_frame_start, mp3_mad->frame.header.duration);

  return 1;
}

/* Scale a MAD sample to 16 bits for output. */
static signed int
scale(mad_fixed_t sample) {
  /* round */
  sample += (1L << (MAD_F_FRACBITS - 16));

  /* clip */
  if (sample >= MAD_F_ONE)
    sample = MAD_F_ONE - 1;
  else if (sample < -MAD_F_ONE)
    sample = -MAD_F_ONE;

  /* quantize */
  return sample >> (MAD_F_FRACBITS + 1 - 16);
}

/* Once the frame has been read, copies its samples into the output
   buffer. */
static void
decode_frame(mad_data *mp3_mad) {
  struct mad_pcm *pcm;
  unsigned int nchannels, nsamples;
  mad_fixed_t const *left_ch, *right_ch;
  unsigned char *out;
  int ret;

  mad_synth_frame(&mp3_mad->synth, &mp3_mad->frame);
  pcm = &mp3_mad->synth.pcm;
  out = mp3_mad->output_buffer + mp3_mad->output_end;

  if ((mp3_mad->status & MS_cvt_decoded) == 0) {
	mp3_mad->status |= MS_cvt_decoded;

	/* The first frame determines some key properties of the stream.
	   In particular, it tells us enough to set up the convert
	   structure now. */
	SDL_BuildAudioCVT(&mp3_mad->cvt, AUDIO_S16, pcm->channels, mp3_mad->frame.header.samplerate, mp3_mad->mixer.format, mp3_mad->mixer.channels, mp3_mad->mixer.freq);
  }

  /* pcm->samplerate contains the sampling frequency */

  nchannels = pcm->channels;
  nsamples  = pcm->length;
  left_ch   = pcm->samples[0];
  right_ch  = pcm->samples[1];

  while (nsamples--) {
    signed int sample;

    /* output sample(s) in 16-bit signed little-endian PCM */

    sample = scale(*left_ch++);
    *out++ = ((sample >> 0) & 0xff);
    *out++ = ((sample >> 8) & 0xff);

    if (nchannels == 2) {
      sample = scale(*right_ch++);
      *out++ = ((sample >> 0) & 0xff);
      *out++ = ((sample >> 8) & 0xff);
    }
  }

  mp3_mad->output_end = out - mp3_mad->output_buffer;
  /*assert(mp3_mad->output_end <= MAD_OUTPUT_BUFFER_SIZE);*/
}

void
mad_getSamples(mad_data *mp3_mad, Uint8 *stream, int len) {
  int bytes_remaining;
  int num_bytes;
  Uint8 *out;

  if ((mp3_mad->status & MS_playing) == 0) {
	/* We're not supposed to be playing, so send silence instead. */
	memset(stream, 0, len);
	return;
  }

  out = stream;
  bytes_remaining = len;
  while (bytes_remaining > 0) {
	if (mp3_mad->output_end == mp3_mad->output_begin) {
	  /* We need to get a new frame. */
	  mp3_mad->output_begin = 0;
	  mp3_mad->output_end = 0;
	  if (!read_next_frame(mp3_mad)) {
		if ((mp3_mad->status & MS_error_flags) != 0) {
		  /* Couldn't read a frame; either an error condition or
			 end-of-file.  Stop. */
		  memset(out, 0, bytes_remaining);
		  mp3_mad->status &= ~MS_playing;
		  return;
		}
	  } else {
		decode_frame(mp3_mad);

		/* Now convert the frame data to the appropriate format for
		   output. */
		mp3_mad->cvt.buf = mp3_mad->output_buffer;
		mp3_mad->cvt.len = mp3_mad->output_end;
		
		mp3_mad->output_end = (int)(mp3_mad->output_end * mp3_mad->cvt.len_ratio);
		/*assert(mp3_mad->output_end <= MAD_OUTPUT_BUFFER_SIZE);*/
		SDL_ConvertAudio(&mp3_mad->cvt);
	  }
	}

	num_bytes = mp3_mad->output_end - mp3_mad->output_begin;
	if (bytes_remaining < num_bytes) {
	  num_bytes = bytes_remaining;
	}

	if (mp3_mad->volume == MIX_MAX_VOLUME) {
	  memcpy(out, mp3_mad->output_buffer + mp3_mad->output_begin, num_bytes);
	} else {
	  SDL_MixAudio(out, mp3_mad->output_buffer + mp3_mad->output_begin,
				   num_bytes, mp3_mad->volume);
	}
	out += num_bytes;
	mp3_mad->output_begin += num_bytes;
	bytes_remaining -= num_bytes;
  }
}

void
mad_seek(mad_data *mp3_mad, double position) {
  mad_timer_t target;
  int int_part;

  int_part = (int)position;
  mad_timer_set(&target, int_part, 
				(int)((position - int_part) * 1000000), 1000000);

  if (mad_timer_compare(mp3_mad->next_frame_start, target) > 0) {
	/* In order to seek backwards in a VBR file, we have to rewind and
	   start again from the beginning.  This isn't necessary if the
	   file happens to be CBR, of course; in that case we could seek
	   directly to the frame we want.  But I leave that little
	   optimization for the future developer who discovers she really
	   needs it. */
	mp3_mad->frames_read = 0;
	mad_timer_reset(&mp3_mad->next_frame_start);
	mp3_mad->status &= ~MS_error_flags;
	mp3_mad->output_begin = 0;
	mp3_mad->output_end = 0;

	SDL_RWseek(mp3_mad->rw, 0, SEEK_SET);
  }

  /* Now we have to skip frames until we come to the right one.
	 Again, only truly necessary if the file is VBR. */
  while (mad_timer_compare(mp3_mad->next_frame_start, target) < 0) {
	if (!read_next_frame(mp3_mad)) {
	  if ((mp3_mad->status & MS_error_flags) != 0) {
		/* Couldn't read a frame; either an error condition or
		   end-of-file.  Stop. */
		mp3_mad->status &= ~MS_playing;
		return;
	  }
	}
  }

  /* Here we are, at the beginning of the frame that contains the
	 target time.  Ehh, I say that's close enough.  If we wanted to,
	 we could get more precise by decoding the frame now and counting
	 the appropriate number of samples out of it. */
}

void
mad_setVolume(mad_data *mp3_mad, int volume) {
  mp3_mad->volume = volume;
}


#endif  /* MP3_MAD_MUSIC */
