
/*
 * --- GSMP-COPYRIGHT-NOTE-BEGIN ---
 * 
 * This copyright note is auto-generated by ./scripts/Create-CopyPatch.
 * Please add additional copyright information _after_ the line containing
 * the GSMP-COPYRIGHT-NOTE-END tag. Otherwise it might get removed by
 * the ./scripts/Create-CopyPatch script. Do not edit this copyright text!
 * 
 * GSMP: pcm/src/MADHelper.cc
 * General Sound Manipulation Program is Copyright (C) 2000 - 2004
 *   Valentin Ziegler and René Rebe
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2. A copy of the GNU General
 * Public License can be found in the file LICENSE.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANT-
 * ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 * 
 * --- GSMP-COPYRIGHT-NOTE-END ---
 */

#include <sys/types.h>
#include <fcntl.h>

#include <ostream>

#include "MADHelper.hh"

using namespace GSMP;

#define MP3_SAMPLES_PER_FRAME 1152
#define XING_MAGIC     (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g')

enum {
  XING_FRAMES = 0x00000001L,
  XING_BYTES  = 0x00000002L,
  XING_TOC    = 0x00000004L,
  XING_SCALE  = 0x00000008L
};

/*****************
 * MADFrameCache * 
 *****************/

MADFrameCache::MADFrameCache () {
  std::cout << __PRETTY_FUNCTION__ << " "
	    << this << std::endl;
  used = 0;
}
    
MADFrameCache::~MADFrameCache () {
  std::cout << __PRETTY_FUNCTION__ << " "
	    << this << std::endl;
  if (used > 0) {
    std::cout << __PRETTY_FUNCTION__
	      << " BUG: used counter >0 in  dtor!" << std::endl;
  }
}
    
void MADFrameCache::Use () {
  // SigC::Threads::MLock a (lock);
  ++ used;
}

void MADFrameCache::Unuse () {
  //SigC::Threads::MLock a (lock);
  -- used;
}

/*************
 * MADHelper *
 *************/

MADHelper::MADHelper ()
{
  m_fd = 0;
  m_frame_cache = 0;
}

MADHelper::~MADHelper ()
{
  if (m_fd > 0)
    close (m_fd);
  
  if (m_frame_cache != 0) {
    m_frame_cache->Unuse ();
    if (m_frame_cache->used == 0)
      delete m_frame_cache;
  }
}

bool MADHelper::Open (const std::string& fname)
{
  m_fname = fname;
  
  if (m_frame_cache != 0) {
    m_frame_cache->Unuse ();
    if (m_frame_cache->used == 0)
      delete m_frame_cache;
  }
  
  // if (cache_partner != 0)
  //   m_frame_cache = cache_partner->m_frame_cache;
  // else
  m_frame_cache = new MADFrameCache;
  
  m_frame_cache->Use ();
  
  m_fd = open (m_fname.c_str (), O_RDONLY);
  if (m_fd <= 0)
    return false;
  
  mad_stream_init (&m_stream);
  mad_frame_init (&m_frame);
  mad_synth_init (&m_synth);
  
  if (!ScanHeader () ) {
    // TODO: nice generic way to free mad_*_init info
    return false;
  }
  
  // UN*X style hacks ;-)
  if ( (m_size = lseek (m_fd, 0, SEEK_END) ) < 0)
    std::cout << __PRETTY_FUNCTION__
	      << " BUG: seek error ???" << std::endl;
  if (lseek (m_fd, 0, SEEK_SET) < 0)
    std::cout << __PRETTY_FUNCTION__
	      << " BUG: seek error ???" << std::endl;
  
  m_synth.pcm.length = 0;
  m_samplecount = 0;
  m_timer = mad_timer_zero;
  
  // very inaccurate way to get the frame length :-(
  std::cout << "try to get pcm frame count" << std::endl;
  if (m_xing.flags & XING_FRAMES) {
    /* Rene: get via XING ? */
    m_length = m_frame.header.duration;
    mad_timer_multiply (&m_length, m_xing.frames);
  }
  else {
    /* estimate playing time from file size */
    
    mad_timer_set (&m_length, 0, 1, m_frame.header.bitrate / 8);

    mad_timer_multiply (&m_length, m_size);
  }
  
  if (lseek (m_fd, 0, SEEK_SET) < 0)
    std::cout << __PRETTY_FUNCTION__
	      << " BUG: seek error ???" << std::endl;
  
  m_rate   = 0;
  m_frames = -1;
  m_buflen = 0;
  
  // Rene's new info stuff
  m_samplerate =  m_frame.header.samplerate;
  if (m_frame.header.mode == MAD_MODE_SINGLE_CHANNEL)
    m_channels = 1;
  else
    m_channels = 2;
  
  m_byte_of_mp3frame = 0;
  m_sample_of_mp3frame = 0;
  
  // UGLY UGLY HACK HACK !!!!! :
  // TODO: I think we need more precise initial values, and should pre-scan
  // the file to get the exact rame count
  // this would also speedup initial seeks and would not require locking
  // in later thread access since the frame map is complete after the initial
  // initialization ...
  m_pcm_frames = (m_length.seconds + 1) * m_samplerate;
  
  std::cout << __PRETTY_FUNCTION__
	    <<  " Initial parsed values: frames: "
	    << m_pcm_frames
	    << ", rate: " << m_samplerate
	    << ", channels: " << m_channels << std::endl;
  
  return true;
}

bool MADHelper::Close ()
{
  // do we have an open file?
  if (m_fd == 0)
    return false;
  
  bool closed = (close (m_fd) >= 0);
  m_fd = 0;
  
  if (m_frame_cache != 0)
    {
      m_frame_cache->Unuse ();
      
      if (m_frame_cache->used == 0)
	delete m_frame_cache;
      
      m_frame_cache = 0;
    }
  
  Cleanup ();
  
  return closed;
}

long MADHelper::Size ()
{
  return m_pcm_frames;
}

int MADHelper::Channels ()
{
  return m_channels;
}

int MADHelper::Rate ()
{
  return m_samplerate;
}

bool MADHelper::Read (int size, mad_fixed_t** buffer)
{
  std::cout << __PRETTY_FUNCTION__
	    << " size: " << size << std::endl;
  
  if (m_fd <= 0)
    return false;

  // loop to decode mp3-frames until we have the requested size
  bool t_eof = false;
  while (size > 0 && !t_eof)
    {
      int avail = m_synth.pcm.length - m_samplecount;
      
      std::cout << __PRETTY_FUNCTION__
		<< " avail: " << avail << std::endl;
      
      if (avail > size)
	avail = size;
      
      // s.th. buffered ??
      if (avail > 0) {
	
	// dither and clip
	for (int ch = 0; ch < m_synth.pcm.channels; ++ch)
	  {
	    mad_fixed_t* src = m_synth.pcm.samples [ch] + m_samplecount;
	    mad_fixed_t* dest = buffer [ch];
	    MADDither& t_dither = dither[ch];
	    int t_avail = avail;
	    
	    while (t_avail--) {
	      *dest = t_dither.DoDither (*src);
	      ++ src; ++ dest;
	    }
	    
	    buffer [ch] += avail;
	}
	
	size -= avail;
	m_samplecount += avail;
      }  
      else { // decode next frame
	t_eof = ! DecodeNext (true);
	
	mad_synth_frame (&m_synth, &m_frame);
	mad_timer_add (&m_timer, m_frame.header.duration);
	
	m_samplecount = 0;
      } // end else decode next frame
    }
  
  // the requested size is not available->zero buffer
  if (size > 0) {
    std::cout << __PRETTY_FUNCTION__
	      << " Read: filling with zero" << std::endl;
    for (int i = 0; i < size * 2; ++ i) // stereo see above
      buffer [i] = 0;
  }

  return true;
}

bool MADHelper::Seek (int64_t pos)
{
  // temp. modified to decode at least two frames ...
  std::cout << __PRETTY_FUNCTION__ << std::endl;
  
  if (m_fd <= 0)
    return false;
  
  int t_seek_frame = pos / MP3_SAMPLES_PER_FRAME;
  int t_jump_frame = t_seek_frame - t_seek_frame % 10;
  if (t_jump_frame > t_seek_frame - 2)
    t_jump_frame = t_seek_frame - 2;
  
  long t_jump_pos = 0;
  
  // byte pos cached ??
  {
    // SigC::Threads::MLock a (m_frame_cache->lock);
    
    /*
b    for (int i = 0; i < m_frame_cache->frames.size (); ++ i)
      std::cout << "  frame: " << i << "; pos: " << m_frame_cache->frames [i] << std::endl;
    */
    
    unsigned int t_cache_key = t_jump_frame / 10;
    if (m_frame_cache->frames.size () < t_cache_key)
      {
	t_cache_key = m_frame_cache->frames.size ();
	if (t_cache_key > 0)
	  -- t_cache_key;
      }
    
    if (t_cache_key > 0)
      t_jump_pos = m_frame_cache->frames [t_cache_key];
    
    t_jump_frame = t_cache_key * 10;
  }
  
  std::cout << __PRETTY_FUNCTION__
	    << " seek: jump2: frame: " << t_jump_frame
	    << ", pos: " << t_jump_pos << std::endl;
  
  // move file-pointer to jump pos
  
  if (lseek (m_fd, t_jump_pos, SEEK_SET) < 0)
    std::cout << __PRETTY_FUNCTION__
	      << " BUG: seek error ???" << std::endl;
  
  m_frames = t_jump_frame;
  m_buflen = 0;
  m_sample_of_mp3frame = t_jump_frame * MP3_SAMPLES_PER_FRAME;
  m_byte_of_mp3frame = t_jump_pos;
  
  // I hope this resets some internal stuff an DecodeNext has to read
  // in the next turn ??
  mad_stream_buffer (&m_stream, m_buffer, 0);
  
  mad_frame_mute (&m_frame);
  mad_synth_mute (&m_synth);
  
  // ok - after jumping to best matching or last cached frame seek to
  // the right one
  while (m_frames < t_seek_frame - 2) {
    std::cout << "seek: decode next header. is: " << m_frames
	 << ", seek: " << t_seek_frame << std::endl;
    DecodeNext (false); // false->only decode the headers
  }
  
  mad_synth_mute (&m_synth);
  
  while (m_frames < t_seek_frame) {
    std::cout << __PRETTY_FUNCTION__
	      << " seek: decode next frame (really). is: " << m_frames
	      << ", seek: " << t_seek_frame << std::endl;
    DecodeNext (true); // true->decode frame, too
    mad_synth_frame (&m_synth, &m_frame);
  }
  
  // adjust m_samplecount (outputted samples from synthed samples)
  m_samplecount = pos - m_sample_of_mp3frame;
  
  std::cout << __PRETTY_FUNCTION__
	    << " seek: m_samplecount: " << m_samplecount << std::endl;

  if (m_samplecount >= MP3_SAMPLES_PER_FRAME)
    std::cout << __PRETTY_FUNCTION__
	      << " seek: BUG: samplecount to large!!!" << std::endl;
  
  return true;
}

void MADHelper::Cleanup ()
{
  std::cout << __PRETTY_FUNCTION__ << std::endl;
  
  mad_synth_finish (&m_synth);
  mad_frame_finish (&m_frame);
  mad_stream_finish (&m_stream);
}

bool MADHelper::ParseXing (struct mad_bitptr ptr, unsigned int bitlen)
{
  if (bitlen < 64 || mad_bit_read (&ptr, 32) != XING_MAGIC)
    goto fail;

  m_xing.flags = mad_bit_read (&ptr, 32);
  bitlen -= 64;

  if (m_xing.flags & XING_FRAMES) {
    if (bitlen < 32)
      goto fail;

    m_xing.frames = mad_bit_read (&ptr, 32);
    bitlen -= 32;
  }

  if (m_xing.flags & XING_BYTES) {
    if (bitlen < 32)
      goto fail;

    m_xing.bytes = mad_bit_read (&ptr, 32);
    bitlen -= 32;
  }

  if (m_xing.flags & XING_TOC) {
    int i;

    if (bitlen < 800)
      goto fail;

    for (i = 0; i < 100; ++ i)
      m_xing.toc[i] = (unsigned char) mad_bit_read (&ptr, 8);

    bitlen -= 800;
  }

  if (m_xing.flags & XING_SCALE) {
    if (bitlen < 32)
      goto fail;

    m_xing.scale = mad_bit_read (&ptr, 32);
    bitlen -= 32;
  }
  
  return true;

 fail:
  m_xing.flags = 0;
  std::cout << __PRETTY_FUNCTION__
	    << " returning failure" << std::endl;
  return false;
}

bool MADHelper::ScanHeader ()
{
  struct mad_stream t_stream;
  struct mad_frame t_frame;
  unsigned char t_buffer [8192];
  unsigned int buflen = 0;
  int count = 0;
  bool result = true;
  
  mad_stream_init (&t_stream);
  mad_frame_init (&t_frame);
  
  m_xing.flags = 0;

  while (1) {
    if (buflen < sizeof (t_buffer) ) {
      int bytes;
      
      if ( (bytes = read (m_fd, t_buffer + buflen,
			  sizeof (t_buffer) - buflen) ) < 0) {
	result = false;
	break;
      }
      
      buflen += bytes;
    }
    
    mad_stream_buffer (&t_stream, t_buffer, buflen);
    
    while (1) {
      if (mad_frame_decode (&t_frame, &t_stream) == -1) {
	if (!MAD_RECOVERABLE (t_stream.error) )
	  break;
	
	continue;
      }
      
      if (count++ || (ParseXing (t_stream.anc_ptr, t_stream.anc_bitlen) ) )
	break;
    }
    
    if (count || t_stream.error != MAD_ERROR_BUFLEN)
      break;
    
    memmove (t_buffer, t_stream.next_frame,
	     buflen = &t_buffer [buflen] - t_stream.next_frame);
  }
  
  if (count) {
    m_frame.header = t_frame.header;
  }
  else
    result = false;
  
  mad_frame_finish (&t_frame);
  mad_stream_finish (&t_stream);
  
  if (!result)
    std::cout << __PRETTY_FUNCTION__
	      << " returning failure" << std::endl;
  return result;
}

bool MADHelper::DecodeNext (bool frame_too)
{
  // bytes of compressed stream left ??
  if (m_byte_of_mp3frame >= m_size) {
    std::cout << __PRETTY_FUNCTION__
	      << " end of mp3 stream reached!" << std::endl;
    
    m_pcm_frames = m_sample_of_mp3frame + m_samplecount;
    
    return false;
  }
  
  // save size of current frame (if one got already decoded)
  int t_frame_bytes;
  if (m_frames >= 0)
    t_frame_bytes = m_stream.next_frame - m_stream.this_frame;
  else {
    std::cout << __PRETTY_FUNCTION__
	      << " first frame DEBUG: " << std::endl
	      << "this_f: " << (void*) m_stream.this_frame
	      << ", next_f: " << (void*) m_stream.next_frame
	      << ", buffer_ptr: " << (void*) m_buffer << std::endl;
    t_frame_bytes = 0;
  }
  
  bool t_eof = false;
  bool t_done = false;
  while (!t_done && !t_eof)
    {
      int error;
      if (frame_too) {
	error = mad_frame_decode (&m_frame, &m_stream);
      }
      else {
	mad_header t_header;
	error = mad_header_decode (&t_header, &m_stream);
      }
      if (error != -1)
	t_done = true;
      else // read more data
	{
	  // buffer data is not enough - recycle last buffered and read more
	  int bytes = 0;
	  
	  if (MAD_RECOVERABLE (m_stream.error) ) {
	    std::cout << __PRETTY_FUNCTION__
		      << " we triggered a recoverable MAD error: "
		      << m_stream.error << std::endl;
	    continue;
	  }
      
	  if (m_buflen > sizeof (m_buffer) ) {
	    std::cout << __PRETTY_FUNCTION__
		      << "m_buflen incorrect: " << m_buflen << "!!"
		      << std::endl;
	    m_buflen = sizeof (m_buflen);
	  }
      
	  // data for next frame left
	  if (m_stream.next_frame != 0) {
	    int t_reuse_length = m_buffer + m_buflen - m_stream.next_frame;
	
	    if (m_stream.next_frame + t_reuse_length > m_buffer + sizeof(m_buffer) )
	      std::cout << __PRETTY_FUNCTION__
			<< " memcopy out of range!! ommited!!!"
			<< std::endl;
	    else
	      memmove (m_buffer, m_stream.next_frame, t_reuse_length);
	
	    m_buflen = t_reuse_length;
	  }
	  else
	    std::cout << __PRETTY_FUNCTION__
		      << " info: next_frame == 0!!" << std::endl;
      
	  // how much to fill ??
	  bytes = sizeof (m_buffer) - m_buflen;
	  if (m_buflen + bytes > sizeof (m_buffer) )
	    std::cout << "!!!! read bytes too large ... !!!!" << std::endl;
      
	  if ( (bytes = read (m_fd, m_buffer + m_buflen, bytes) ) < 0)
	    {
	      std::cout << __PRETTY_FUNCTION__
			<< " read error - file too short?!" << std::endl;
	      return false;
	    }
      
	  if (bytes == 0) {
	    std::cout << __PRETTY_FUNCTION__
		      << " eof case during POSIX read?!" << std::endl;
	    t_eof = true;
	  }
	  m_buflen += bytes;
	  if (m_buflen > sizeof(m_buffer) ) {
	    std::cout << __PRETTY_FUNCTION__
		      << " buflen too large after read?!->corrected!!!"
		      << std::endl;
	    m_buflen = sizeof(m_buffer);
	  }
      
	  mad_stream_buffer (&m_stream, m_buffer, m_buflen);
	} // end if mad error
    } // end while not done
  
  // update states
  if (m_frames >= 0) {
    // fix pcm_frames in one mp3 frame - I hope ...
    m_sample_of_mp3frame += MP3_SAMPLES_PER_FRAME; 
    m_byte_of_mp3frame += t_frame_bytes;
  }
  ++ m_frames;
  
  std::cout << __PRETTY_FUNCTION__
	    << " " << m_frames << ": byte_pos: " << m_byte_of_mp3frame
	    << ", sample_pos: " << m_sample_of_mp3frame << std::endl;
  
  // add to pos cache ???
  if ( (m_frames) % 10 == 0)
    {
      // SigC::Threads::MLock a (m_frame_cache->lock);
      
      //std::cout << "cached frame infos: " << m_frame_cache->frames.size () << std::endl;
      
      unsigned int cache_key = m_frames / 10;
      if (m_frame_cache->frames.size () == cache_key)
	{
	  std::cout << __PRETTY_FUNCTION__
		    << " adding: info for: " << cache_key
		    << ", frame: " << m_frames
		    << ", byte_pos: " << m_byte_of_mp3frame
		    << ", sample_pos: " << m_sample_of_mp3frame
		    << " to cache." << std::endl;
	  m_frame_cache->frames.push_back (m_byte_of_mp3frame);
	}
    }
  
  return true;
}

#ifdef TO_BE_USED

/*
 * NAME:                round()
 * DESCRIPTION: round and scale sample
 */
static __inline
signed int round(mad_fixed_t sample, struct dither *dither)
{
        enum {
                MIN = -MAD_F_ONE,
                MAX =  MAD_F_ONE - 1
        };

        /* round */
        sample += (1L << (MAD_F_FRACBITS - SAMPLE_DEPTH));

        /* clip */
        if (sample > MAX)
                sample = MAX;
        else if (sample < MIN)
                sample = MIN;

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

/*
 * NAME:                prng()
 * DESCRIPTION: 32-bit pseudo-random number generator
 */
static __inline
unsigned long prng(unsigned long state)
{
  return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
}

/*
 * NAME:                dither()
 * DESCRIPTION: dither and scale sample
 */
static __inline
signed int dither(mad_fixed_t sample, struct dither *dither)
{
        unsigned int scalebits;
        mad_fixed_t output, mask, random;

        enum {
                MIN = -MAD_F_ONE,
                MAX =  MAD_F_ONE - 1
        };

        /* noise shape */
        sample += dither->error[0] - dither->error[1] + dither->error[2];

        dither->error[2] = dither->error[1];
        dither->error[1] = dither->error[0] / 2;

        /* bias */
        output = sample + (1L << (MAD_F_FRACBITS + 1 - SAMPLE_DEPTH - 1));

        scalebits = MAD_F_FRACBITS + 1 - SAMPLE_DEPTH;
        mask = (1L << scalebits) - 1;

        /* dither */
        random  = prng(dither->random);
        output += (random & mask) - (dither->random & mask);

        dither->random = random;

        /* clip */
        if (output > MAX) {
                output = MAX;

                if (sample > MAX)
                        sample = MAX;
        }
        else if (output < MIN) {
                output = MIN;

                if (sample < MIN)
                        sample = MIN;
        }

        /* quantize */
        output &= ~mask;

        /* error feedback */
        dither->error[0] = sample - output;

        /* scale */
        return output >> scalebits;
}

/*
 * NAME:                pack_pcm()
 * DESCRIPTION: scale and dither MAD output
 */
static
void pack_pcm(unsigned char **pcm, unsigned int nsamples,
                          mad_fixed_t const *ch1, mad_fixed_t const *ch2)
{
        register signed int s0, s1;
        static struct dither d0, d1;

        if (ch2) {  /* stereo */
                while (nsamples--) {
                        s0 = scale(*ch1++, &d0);
                        s1 = scale(*ch2++, &d1);
# if SAMPLE_DEPTH == 16
                        (*pcm)[0 + 0] = s0 >> 0;
                        (*pcm)[0 + 1] = s0 >> 8;
                        (*pcm)[2 + 0] = s1 >> 0;
                        (*pcm)[2 + 1] = s1 >> 8;

                        *pcm += 2 * 2;
# elif SAMPLE_DEPTH == 8
                        (*pcm)[0] = s0 ^ 0x80;
                        (*pcm)[1] = s1 ^ 0x80;

                        *pcm += 2;
# else
#  error "bad SAMPLE_DEPTH"
# endif
                }
        }
        else {  /* mono */
                while (nsamples--) {
                        s0 = scale(*ch1++, &d0);

# if SAMPLE_DEPTH == 16
                        (*pcm)[0] = s0 >> 0;
                        (*pcm)[1] = s0 >> 8;

                        *pcm += 2;
# elif SAMPLE_DEPTH == 8
                        *(*pcm)++ = s0 ^ 0x80;
# endif
                }
        }
}


/*
 * NAME:                codec->OpenFile()
 * DESCRIPTION: open a new MPEG audio stream
 */
BOOL CPP_OMMP3_OpenFile(CPs_CoDecModule* pModule, char const *path)
{
        CPs_CoDec_MPEG *context = (CPs_CoDec_MPEG *)pModule->m_pModuleCookie;
        CP_CHECKOBJECT(context);

        cleanup(context);

        CP_TRACE1("CPI_CoDec__OpenFile: \"%s\"", path);

        context->file = CreateFile(path, GENERIC_READ,
                                                           FILE_SHARE_READ | FILE_SHARE_WRITE, 0,
                                                           OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
        if (context->file == INVALID_HANDLE_VALUE) {
                CP_TRACE0("CPI_CoDec__OpenFile: failed");
                return FALSE;
        }

        mad_stream_init(&context->stream);
        mad_frame_init(&context->frame);
        mad_synth_init(&context->synth);

        if (scan_header(context->file,
                                        &context->frame.header, &context->xing) == -1) {
                CP_TRACE0("CPI_CoDec__OpenFile: failed to read file header");
                cleanup(context);
                return FALSE;
        }

        SetFilePointer(context->file, 0, 0, FILE_BEGIN);

        context->size             = GetFileSize(context->file, 0);
        context->synth.pcm.length = 0;
        context->samplecount      = 0;
        context->timer            = mad_timer_zero;

        if (context->xing.flags & XING_FRAMES) {
                context->length = context->frame.header.duration;
                mad_timer_multiply(&context->length, context->xing.frames);
        }
        else {
                /* estimate playing time from file size */

                mad_timer_set(&context->length, 0,
                                          1, context->frame.header.bitrate / 8);
                mad_timer_multiply(&context->length, context->size);
        }

        context->rate   = 0;
        context->frames = 0;

        context->info.m_iFileLength_Secs =
                mad_timer_count(context->length, MAD_UNITS_SECONDS);
        context->info.m_iBitRate_Kbs     = context->frame.header.bitrate / 1000;
        context->info.m_iFreq_Hz         = context->frame.header.samplerate;
        context->info.m_bStereo          =
                context->frame.header.mode == MAD_MODE_SINGLE_CHANNEL ? FALSE : TRUE;
        context->info.m_b16bit           = (SAMPLE_DEPTH == 16);

        context->buflen = 0;

        return TRUE;
}

/*
 * NAME:                codec->Seek()
 * DESCRIPTION: seek to a specific location
 */
void CPP_OMMP3_Seek(CPs_CoDecModule* pModule, int const numer, int const denom)
{
        CPs_CoDec_MPEG *context = (CPs_CoDec_MPEG *)pModule->m_pModuleCookie;
        double fraction;
        unsigned long position;

        CP_CHECKOBJECT(context);

        if (context->file == INVALID_HANDLE_VALUE) {
                CP_TRACE0("CPI_CoDec__Seek: no file is open");
                return;
        }

        if (numer < 0 || denom <= 0) {
                CP_TRACE0("CPI_CoDec__Seek: numer < 0 || denom <= 0");
                return;
        }

        fraction = (double) numer / denom;
        position = (unsigned long)
                (mad_timer_count(context->length, MAD_UNITS_MILLISECONDS) * fraction);

        mad_timer_set(&context->timer, position / 1000, position % 1000, 1000);

        if (context->xing.flags & XING_TOC) {
                int percent, p1, p2;

                percent = (int) (fraction * 100);
                p1 = (percent < 100) ? context->xing.toc[percent    ] : 0x100;
                p2 = (percent <  99) ? context->xing.toc[percent + 1] : 0x100;

                fraction = (p1 + (p2 - p1) * (fraction * 100 - percent)) / 0x100;
        }

        SetFilePointer(context->file, (LONG) (context->size * fraction), 0,
                                   FILE_BEGIN);
        if (ReadFile(context->file, context->buffer, sizeof(context->buffer),
                                 &context->buflen, 0) == FALSE)
                context->buflen = 0;

        mad_stream_buffer(&context->stream, context->buffer, context->buflen);

        mad_frame_mute(&context->frame);
        mad_synth_mute(&context->synth);

        if (numer) {
                int skip;

                skip = 2;
                do {
                        if (mad_frame_decode(&context->frame, &context->stream) == 0) {
                                mad_timer_add(&context->timer, context->frame.header.duration);
                                if (--skip == 0)
                                        mad_synth_frame(&context->synth, &context->frame);
                        }
                        else if (!MAD_RECOVERABLE(context->stream.error))
                                break;
                }
                while (skip);
        }

        context->synth.pcm.length = 0;
        context->samplecount      = 0;
}

/*
 * NAME:                codec->GetPCMBlock()
 * DESCRIPTION: decode and return the next PCM block
 */
BOOL CPP_OMMP3_GetPCMBlock(CPs_CoDecModule* pModule, void *block, DWORD *size)
{
        CPs_CoDec_MPEG *context = (CPs_CoDec_MPEG *)pModule->m_pModuleCookie;
        unsigned char *samples = block;
        unsigned int nsamples;

        CP_CHECKOBJECT(context);
        CP_ASSERT(context->file != INVALID_HANDLE_VALUE);

        nsamples = (*size / (SAMPLE_DEPTH / 8)) >>
                (context->info.m_bStereo ? 1 : 0);
        *size = 0;

        while (nsamples) {
                unsigned int count, bitrate;

                count = context->synth.pcm.length - context->samplecount;
                if (count > nsamples)
                        count = nsamples;

                if (count) {
                        mad_fixed_t const *ch1, *ch2;

                        ch1 = context->synth.pcm.samples[0] + context->samplecount;
                        ch2 = context->synth.pcm.samples[1] + context->samplecount;

                        if (context->info.m_bStereo == FALSE)
                                ch2 = 0;
                        else if (context->synth.pcm.channels == 1)
                                ch2 = ch1;

                        pack_pcm(&samples, count, ch1, ch2);

                        context->samplecount += count;
                        nsamples             -= count;

                        if (nsamples == 0)
                                break;
                }

                while (mad_frame_decode(&context->frame, &context->stream) == -1) {
                        DWORD bytes;

                        if (MAD_RECOVERABLE(context->stream.error))
                                continue;

                        if (context->stream.next_frame) {
                                memmove(context->buffer, context->stream.next_frame,
                                                context->buflen = context->buffer +
                                                context->buflen - context->stream.next_frame);
                        }

                        if (ReadFile(context->file, context->buffer +
                                                 context->buflen, sizeof(context->buffer) -
                                                 context->buflen, &bytes, 0) == FALSE || bytes == 0)
                                return FALSE;

                        mad_stream_buffer(&context->stream,
                                                          context->buffer, context->buflen += bytes);
                }

                bitrate = context->frame.header.bitrate / 1000;

                context->rate += bitrate;
                context->frames++;

                context->info.m_iBitRate_Kbs = bitrate;

                mad_synth_frame(&context->synth, &context->frame);

                context->samplecount = 0;

                mad_timer_add(&context->timer, context->frame.header.duration);
        }

        *size = samples - (unsigned char *) block;

        return TRUE;
}

#endif
