/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2002 Dave Chapman
 *
 * This file contains significant code from two other projects:
 *
 * 1) madldd - a sample application to use libmad
 * 2) CoolPlayer - a win32 audio player that also uses libmad
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#ifdef MPEG_PLAY

#include <string.h>
#include <stdlib.h>
#include <file.h>
#include <lcd.h>
#include <button.h>
#include "id3.h"

#include <stdio.h>
#include <mad.h>

#include "sound.h"

/* The "dither" code to convert the 24-bit samples produced by libmad was
   taken from the coolplayer project - coolplayer.sourceforge.net */

struct dither {
	mad_fixed_t error[3];
	mad_fixed_t random;
};
# define SAMPLE_DEPTH	16
# define scale(x, y)	dither((x), (y))

struct mad_stream  Stream;
struct mad_frame  Frame;
struct mad_synth  Synth;
mad_timer_t      Timer;

/*
 * 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;
}

#define INPUT_BUFFER_SIZE  (5*8192)
#define OUTPUT_BUFFER_SIZE  8192 /* Must be an integer multiple of 4. */
void mpeg_play(char* fname)
{
    unsigned char InputBuffer[INPUT_BUFFER_SIZE],
        OutputBuffer[OUTPUT_BUFFER_SIZE],
        *OutputPtr=OutputBuffer;
    const unsigned char  *OutputBufferEnd=OutputBuffer+OUTPUT_BUFFER_SIZE;
    int Status=0, i, fd;
    unsigned long FrameCount=0;
    sound_t sound;
    mp3entry mp3;
    register signed int s0, s1;
    static struct dither d0, d1;
  
    mp3info(&mp3, fname);

    init_sound(&sound);

    /* Configure sound device for this file - always select Stereo because
       some sound cards don't support mono */
    config_sound(&sound,mp3.frequency,2);

    if ((fd=open(fname,O_RDONLY)) < 0) {
        fprintf(stderr,"could not open %s\n",fname);
        return;
    }

    /* First the structures used by libmad must be initialized. */
    mad_stream_init(&Stream);
    mad_frame_init(&Frame);
    mad_synth_init(&Synth);
    mad_timer_reset(&Timer);

    do {
        if (button_get()) 
            break; 

        if (Stream.buffer==NULL || Stream.error==MAD_ERROR_BUFLEN) {
            size_t ReadSize,Remaining;
            unsigned char *ReadStart;

            if(Stream.next_frame!=NULL) {
                Remaining=Stream.bufend-Stream.next_frame;
                memmove(InputBuffer,Stream.next_frame,Remaining);
                ReadStart=InputBuffer+Remaining;
                ReadSize=INPUT_BUFFER_SIZE-Remaining;
            } else {
                ReadSize=INPUT_BUFFER_SIZE,
                    ReadStart=InputBuffer,
                    Remaining=0;
            }

            if ((ReadSize=read(fd,ReadStart,ReadSize)) < 0) {
                fprintf(stderr,"end of input stream\n");
                break;
            }

            mad_stream_buffer(&Stream,InputBuffer,ReadSize+Remaining);
            Stream.error=0;
        }

        if(mad_frame_decode(&Frame,&Stream)) {
            if(MAD_RECOVERABLE(Stream.error)) {
                fprintf(stderr,"recoverable frame level error\n");
                fflush(stderr);
                continue;
            } else {
                if(Stream.error==MAD_ERROR_BUFLEN) {
                    continue;
                } else {
                    fprintf(stderr,"unrecoverable frame level error\n");
                    Status=1;
                    break;
                }
            }
        }

        FrameCount++;
        mad_timer_add(&Timer,Frame.header.duration);
        
        mad_synth_frame(&Synth,&Frame);

        for(i=0;i<Synth.pcm.length;i++) {
            unsigned short  Sample;
            
            /* Left channel */
            Sample=scale(Synth.pcm.samples[0][i],&d0);
            *(OutputPtr++)=Sample&0xff;
            *(OutputPtr++)=Sample>>8;
            
            /* Right channel. If the decoded stream is monophonic then
             * the right output channel is the same as the left one.
             */
            if(MAD_NCHANNELS(&Frame.header)==2) {
                Sample=scale(Synth.pcm.samples[1][i],&d1);
            }

            *(OutputPtr++)=Sample&0xff;
            *(OutputPtr++)=Sample>>8;
            
            /* Flush the buffer if it is full. */
            if (OutputPtr==OutputBufferEnd) {
                if (output_sound(&sound, OutputBuffer,
                                 OUTPUT_BUFFER_SIZE)!=OUTPUT_BUFFER_SIZE) {
                    fprintf(stderr,"PCM write error.\n");
                    Status=2;
                    break;
                }
                OutputPtr=OutputBuffer;
            }
            }
    }while(1);
    
    /* Mad is no longer used, the structures that were initialized must
     * now be cleared.
     */
    mad_synth_finish(&Synth);
    mad_frame_finish(&Frame);
    mad_stream_finish(&Stream);

    /* If the output buffer is not empty and no error occured during
     * the last write, then flush it. */
    if(OutputPtr!=OutputBuffer && Status!=2)
        {
            size_t  BufferSize=OutputPtr-OutputBuffer;

            if(write(sound,OutputBuffer,1,BufferSize)!=BufferSize)
                {
                    fprintf(stderr,"PCM write error\n");
                    Status=2;
                }
        }

    /* Accounting report if no error occured. */
    if(!Status)
        {
            char  Buffer[80];

            mad_timer_string(Timer,Buffer,"%lu:%02lu.%03u",
                             MAD_UNITS_MINUTES,MAD_UNITS_MILLISECONDS,0);
            fprintf(stderr,"%lu frames decoded (%s).\n",FrameCount,Buffer);
        }

    close_sound(&sound);
    /* That's the end of the world (in the H. G. Wells way). */
    return;
}


#endif
